lib/openstudio/analysis/formulation.rb in openstudio-analysis-0.4.1 vs lib/openstudio/analysis/formulation.rb in openstudio-analysis-0.4.2
- old
+ new
@@ -1,19 +1,25 @@
# OpenStudio formulation class handles the generation of the OpenStudio Analysis format.
module OpenStudio
module Analysis
- SeedModel = Struct.new(:path)
- WeatherFile = Struct.new(:path)
+ SeedModel = Struct.new(:file)
+ WeatherFile = Struct.new(:file)
class Formulation
attr_reader :seed_model
attr_reader :weather_file
+ attr_reader :analysis_type
attr_accessor :display_name
attr_accessor :workflow
attr_accessor :algorithm
- attr_reader :analysis_type
+ # the attributes below are used for packaging data into the analysis zip file
+ attr_reader :weather_files
+ attr_reader :seed_models
+ attr_reader :worker_inits
+ attr_reader :worker_finalizes
+ attr_reader :libraries
# Create an instance of the OpenStudio::Analysis::Formulation
#
# @param display_name [String] Display name of the project.
# @return [Object] An OpenStudio::Analysis::Formulation object
@@ -24,10 +30,18 @@
# Initialize child objects (expect workflow)
@weather_file = WeatherFile.new
@seed_model = SeedModel.new
@algorithm = OpenStudio::Analysis::AlgorithmAttributes.new
+
+ # Analysis Zip attributes
+ @weather_files = SupportFiles.new
+ @seed_models = SupportFiles.new
+ @worker_inits = SupportFiles.new
+ @worker_finalizes = SupportFiles.new
+ @libraries = SupportFiles.new
+ #@initialization_scripts = SupportFiles.new
end
# Initialize or return the current workflow object
#
# @return [Object] An OpenStudio::Analysis::Workflow object
@@ -41,20 +55,20 @@
attr_writer :analysis_type
# Path to the seed model
#
# @param path [String] Path to the seed model. This should be relative.
- def seed_model(path)
- @seed_model = SeedModel.new(path: path)
+ def seed_model(file)
+ @seed_model = SeedModel.new(file)
end
# Path to the weather file (or folder). If it is a folder, then the measures will look for the weather file
# by name in that folder.
#
# @param path [String] Path to the weather file or folder.
- def weather_file(path)
- @weather_file = WeatherFile.new(path: path)
+ def weather_file(file)
+ @weather_file = WeatherFile.new(file)
end
# Add an output of interest to the problem formulation
#
# @param output_hash [Hash] Hash of the output variable in the legacy format
@@ -71,16 +85,16 @@
# @option output_hash [Float] :objective_function_target Target for the objective function to reach (if defined). Default: nil
# @option output_hash [Float] :scaling_factor How to scale the objective function(s). Default: nil
# @option output_hash [Integer] :objective_function_group If grouping objective functions, then group ID. Default: nil
def add_output(output_hash)
output_hash = {
- units: '',
- objective_function: false,
- objective_function_index: nil,
- objective_function_target: nil,
- objective_function_group: nil,
- scaling_factor: nil
+ units: '',
+ objective_function: false,
+ objective_function_index: nil,
+ objective_function_target: nil,
+ objective_function_group: nil,
+ scaling_factor: nil
}.merge(output_hash)
# if the objective_function index is nil and the variable is an objective function, then increment and
# assign and objective function index
unless output_hash[:objective_function_index]
@@ -97,23 +111,23 @@
# @return [Hash]
def to_hash(version = 1)
# fail 'Must define an analysis type' unless @analysis_type
if version == 1
h = {
- analysis: {
- display_name: @display_name,
- name: @display_name.snake_case,
- output_variables: @outputs,
- problem: {
- analysis_type: @analysis_type,
- algorithm: algorithm.to_hash(version),
- workflow: workflow.to_hash(version)
- },
- seed: @seed_model[:path],
- weather_file: @weather_file[:path],
- file_format_version: version
- }
+ analysis: {
+ display_name: @display_name,
+ name: @display_name.snake_case,
+ output_variables: @outputs,
+ problem: {
+ analysis_type: @analysis_type,
+ algorithm: algorithm.to_hash(version),
+ workflow: workflow.to_hash(version)
+ },
+ seed: @seed_model[:file],
+ weather_file: @weather_file[:file],
+ file_format_version: version
+ }
}
# This is a hack right now, but after the initial hash is created go back and add in the objective functions
# to the the algorithm as defined in the output_variables list
ofs = @outputs.map { |i| i[:name] if i[:objective_function] }.compact
@@ -138,25 +152,27 @@
@workflow.items.map do |item|
item.variables.map { |v| static_hash[v[:uuid]] = v[:static_value] }
end
h = {
- data_point: {
- set_variable_values: static_hash,
- status: 'na',
- uuid: SecureRandom.uuid
- }
+ data_point: {
+ set_variable_values: static_hash,
+ status: 'na',
+ uuid: SecureRandom.uuid
+ }
}
h
end
end
# save the file to JSON. Will overwrite the file if it already exists
#
+ # @param filename [String] Name of file to create. It will create the directory and override the file if it exists.
# @param version [Integer] Version of the format to return
# @return [Boolean]
def save(filename, version = 1)
+ FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
File.open(filename, 'w') { |f| f << JSON.pretty_generate(to_hash(version)) }
true
end
@@ -166,9 +182,143 @@
# @return [Boolean]
def save_static_data_point(filename, version = 1)
File.open(filename, 'w') { |f| f << JSON.pretty_generate(to_static_data_point_hash(version)) }
true
+ end
+
+ # save the analysis zip file which contains the measures, seed model, weather file, and init/final scripts
+ #
+ # @param filename [String] Name of file to create. It will create the directory and override the file if it exists.
+ # @return [Boolean]
+ def save_zip(filename)
+ FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
+
+ save_analysis_zip(filename)
+ end
+
+ private
+
+ # Package up the seed, weather files, and measures
+ def save_analysis_zip(filename)
+ def add_directory_to_zip(zipfile, local_directory, relative_zip_directory)
+ # puts "Add Directory #{local_directory}"
+ Dir[File.join("#{local_directory}", '**', '**')].each do |file|
+ # puts "Adding File #{file}"
+ zipfile.add(file.sub(local_directory, relative_zip_directory), file)
+ end
+ zipfile
+ end
+
+ FileUtils.rm_f(filename) if File.exist?(filename)
+
+ Zip::File.open(filename, Zip::File::CREATE) do |zf|
+
+ ## Weather files
+ # TODO: eventually remove the @weather_file attribute and grab the weather file out
+ # of the @weather_files
+ puts "Adding Support Files: Weather"
+ if @weather_file[:file] && !@weather_files.files.find { |f| @weather_file[:file] == f[:file] }
+ #manually add the weather file
+ puts " Adding #{@weather_file[:file]}"
+ zf.add("./weather/#{File.basename(@weather_file[:file])}", @weather_file[:file])
+ end
+ @weather_files.each do |f|
+ puts " Adding #{f[:file]}"
+ zf.add("./weather/#{File.basename(f[:file])}", f[:file])
+ end
+
+ ## Seed files
+ puts "Adding Support Files: Seed Models"
+ if @seed_model[:file] && !@seed_models.files.find { |f| @seed_model[:file] == f[:file] }
+ #manually add the weather file
+ puts " Adding #{@seed_model[:file]}"
+ zf.add("./seed/#{File.basename(@seed_model[:file])}", @seed_model[:file])
+ end
+ @seed_models.each do |f|
+ puts " Adding #{f[:file]}"
+ zf.add("./seed/#{File.basename(f[:file])}", f[:file])
+ end
+
+ puts "Adding Support Files: Libraries"
+ @libraries.each do |lib|
+ fail "Libraries must specify their 'library_name' as metadata which becomes the directory upon zip" unless lib[:metadata][:library_name]
+
+ if File.directory? lib[:file]
+ Dir[File.join(lib[:file], '**', '**')].each do |file|
+ puts " Adding #{file}"
+ zf.add(file.sub(lib[:file], "./lib/#{lib[:metadata][:library_name]}/"), file)
+ end
+ else
+ # just add the file to the zip
+ puts " Adding #{lib[:file]}"
+ zf.add(lib[:file], "./lib/#{File.basename(lib[:file])}", lib[:file])
+ end
+ end
+
+ puts "Adding Support Files: Worker Initialization Scripts"
+ index = 0
+ @worker_inits.each do |f|
+ ordered_file_name = "#{index.to_s.rjust(2, '0')}_#{File.basename(f[:file])}"
+ puts " Adding #{f[:file]} as #{ordered_file_name}"
+ zf.add(f[:file].sub(f[:file], "./lib/worker_initialize/#{ordered_file_name}"), f[:file])
+
+ if f[:metadata][:args]
+ arg_file = "#{File.basename(ordered_file_name, '.*')}.args"
+ file = Tempfile.new('arg')
+ file.write(f[:metadata][:args])
+ zf.add("./lib/worker_initialize/#{arg_file}", file)
+ file.close
+ end
+
+ index += 1
+ end
+
+ puts "Adding Support Files: Worker Finalization Scripts"
+ index = 0
+ @worker_finalizes.each do |f|
+ ordered_file_name = "#{index.to_s.rjust(2, '0')}_#{File.basename(f[:file])}"
+ puts " Adding #{f[:file]} as #{ordered_file_name}"
+ zf.add(f[:file].sub(f[:file], "./lib/worker_finalize/#{ordered_file_name}"), f[:file])
+
+ if f[:metadata][:args]
+ arg_file = "#{File.basename(ordered_file_name, '.*')}.args"
+ file = Tempfile.new('arg')
+ file.write(f[:metadata][:args])
+ zf.add("./lib/worker_finalize/#{arg_file}", file)
+ file.close
+ end
+
+ index += 1
+ end
+
+ ## Measures
+ puts "Adding Measures"
+ added_measures = []
+ # The list of the measures should always be there, but make sure they are uniq
+ @workflow.each do |measure|
+ measure_dir_to_add = measure.measure_definition_directory
+
+ next if added_measures.include? measure_dir_to_add
+
+ puts " Adding #{File.basename(measure_dir_to_add)}"
+ Dir[File.join(measure_dir_to_add, '**')].each do |file|
+ if File.directory?(file)
+ if File.basename(file) == 'resources' || File.basename(file) == 'lib'
+ add_directory_to_zip(zf, file, "./measures/#{measure.name}/#{File.basename(file)}")
+ else
+ # puts "Skipping Directory #{File.basename(file)}"
+ end
+ else
+ # puts "Adding File #{file}"
+ # added_measures << measure_dir unless added_measures.include? measure_dir
+ zf.add(file.sub(measure_dir_to_add, "./measures/#{measure.name}/"), file)
+ end
+ end
+
+ added_measures << measure_dir_to_add
+ end
+ end
end
end
end
end