openstudio-workflow 1.0.0.pat1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +16 -68
  4. data/Rakefile +9 -9
  5. data/bin/openstudio_cli +786 -0
  6. data/lib/openstudio/workflow/adapters/input/local.rb +97 -0
  7. data/lib/openstudio/workflow/adapters/output/local.rb +90 -0
  8. data/lib/openstudio/workflow/adapters/output/socket.rb +70 -0
  9. data/lib/openstudio/workflow/{jobs/run_preflight/run_preflight.rb → adapters/output/web.rb} +37 -19
  10. data/lib/openstudio/workflow/{adapter.rb → adapters/output_adapter.rb} +53 -51
  11. data/lib/openstudio/workflow/job.rb +22 -0
  12. data/lib/openstudio/workflow/jobs/{run_energyplus → resources}/monthly_report.idf +0 -0
  13. data/lib/openstudio/workflow/jobs/run_energyplus.rb +49 -0
  14. data/lib/openstudio/workflow/jobs/run_ep_measures.rb +55 -0
  15. data/lib/openstudio/workflow/jobs/run_initialization.rb +136 -0
  16. data/lib/openstudio/workflow/jobs/run_os_measures.rb +59 -0
  17. data/lib/openstudio/workflow/jobs/run_postprocess.rb +53 -0
  18. data/lib/openstudio/workflow/jobs/run_preprocess.rb +81 -0
  19. data/lib/openstudio/workflow/jobs/run_reporting_measures.rb +86 -0
  20. data/lib/openstudio/workflow/jobs/run_translation.rb +49 -0
  21. data/lib/openstudio/workflow/multi_delegator.rb +1 -3
  22. data/lib/openstudio/workflow/registry.rb +137 -0
  23. data/lib/openstudio/workflow/run.rb +182 -221
  24. data/lib/openstudio/workflow/time_logger.rb +1 -1
  25. data/lib/openstudio/workflow/util/energyplus.rb +564 -0
  26. data/lib/openstudio/workflow/util/io.rb +33 -0
  27. data/lib/openstudio/workflow/util/measure.rb +520 -0
  28. data/lib/openstudio/workflow/util/model.rb +100 -0
  29. data/lib/openstudio/workflow/util/post_process.rb +177 -0
  30. data/lib/openstudio/workflow/util/weather_file.rb +108 -0
  31. data/lib/openstudio/workflow/util.rb +14 -0
  32. data/lib/openstudio/workflow/version.rb +1 -1
  33. data/lib/openstudio/workflow_json.rb +399 -0
  34. data/lib/openstudio/workflow_runner.rb +213 -0
  35. data/lib/openstudio-workflow.rb +13 -118
  36. metadata +45 -85
  37. data/lib/openstudio/extended_runner.rb +0 -105
  38. data/lib/openstudio/workflow/adapters/local.rb +0 -101
  39. data/lib/openstudio/workflow/adapters/mongo.rb +0 -227
  40. data/lib/openstudio/workflow/jobs/lib/apply_measures.rb +0 -253
  41. data/lib/openstudio/workflow/jobs/run_energyplus/run_energyplus.rb +0 -314
  42. data/lib/openstudio/workflow/jobs/run_openstudio/run_openstudio.rb +0 -230
  43. data/lib/openstudio/workflow/jobs/run_postprocess/run_postprocess.rb +0 -110
  44. data/lib/openstudio/workflow/jobs/run_reporting_measures/run_reporting_measures.rb +0 -471
  45. data/lib/openstudio/workflow/jobs/run_runmanager/run_runmanager.rb +0 -247
  46. data/lib/openstudio/workflow/jobs/run_xml/run_xml.rb +0 -279
@@ -0,0 +1,177 @@
1
+ module OpenStudio
2
+ module Workflow
3
+ module Util
4
+ require 'openstudio/workflow/util/measure'
5
+ require 'csv'
6
+
7
+ # This module serves as a wrapper around various post-processing tasks used to manage outputs
8
+ # @todo (rhorsey) ummmm. So some of this is pretty ugly. Since @dmacumber had ideas about this maybe he can figure
9
+ # out what to do about it all
10
+ # @todo (nlong) the output adapter restructure will frack up the extraction method royally
11
+ #
12
+ module PostProcess
13
+ # This method loads a sql file into OpenStudio and returns it
14
+ #
15
+ # @param [String] sql_file Absolute path to the sql file to be loaded
16
+ # @return [Object, nil] The OpenStudio::SqlFile object, or nil if it could not be found
17
+ #
18
+ def load_sql_file(sql_file)
19
+ return nil unless File.exist? sql_file
20
+ OpenStudio::SqlFile.new(@sql_filename)
21
+ end
22
+
23
+ # This method parses all sorts of stuff which something needs
24
+ #
25
+ # @param [String] run_dir The directory that the simulation was run in
26
+ # @return [Hash, Hash] results and objective_function (which may be empty) are returned
27
+ # @todo (rhorsey) fix the description
28
+ #
29
+ def run_extract_inputs_and_outputs(run_dir, logger)
30
+ # For xml, the measure attributes are in the measure_attributes_xml.json file
31
+ # TODO: somehow pass the metadata around on which JSONs to suck into the database
32
+ results = {}
33
+ # Inputs are in the measure_attributes.json file
34
+ if File.exist? "#{run_dir}/measure_attributes.json"
35
+ h = JSON.parse(File.read("#{run_dir}/measure_attributes.json"), symbolize_names: true)
36
+ h = rename_hash_keys(h, logger)
37
+ results.merge! h
38
+ end
39
+
40
+ logger.info 'Saving the result hash to file'
41
+ File.open("#{run_dir}/results.json", 'w') { |f| f << JSON.pretty_generate(results) }
42
+
43
+ objective_functions = {}
44
+ if @registry[:analysis]
45
+ logger.info 'Iterating over Analysis JSON Output Variables'
46
+ # Save the objective functions to the object for sending back to the simulation executive
47
+
48
+ if @analysis_json[:analysis] && @analysis_json[:analysis][:output_variables]
49
+ @analysis_json[:analysis][:output_variables].each do |variable|
50
+ # determine which ones are the objective functions (code smell: todo: use enumerator)
51
+ if variable[:objective_function]
52
+ logger.info "Looking for objective function #{variable[:name]}"
53
+ # TODO: move this to cleaner logic. Use ostruct?
54
+ k, v = variable[:name].split('.')
55
+
56
+ # look for the objective function key and make sure that it is not nil. False is an okay obj function.
57
+ if results.key?(k.to_sym) && !results[k.to_sym][v.to_sym].nil?
58
+ objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = results[k.to_sym][v.to_sym]
59
+ if variable[:objective_function_target]
60
+ logger.info "Found objective function target for #{variable[:name]}"
61
+ objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_target].to_f
62
+ end
63
+ if variable[:scaling_factor]
64
+ logger.info "Found scaling factor for #{variable[:name]}"
65
+ objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = variable[:scaling_factor].to_f
66
+ end
67
+ if variable[:objective_function_group]
68
+ logger.info "Found objective function group for #{variable[:name]}"
69
+ objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_group].to_f
70
+ end
71
+ else
72
+ logger.warn "No results for objective function #{variable[:name]}"
73
+ objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = Float::MAX
74
+ objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = nil
75
+ objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = nil
76
+ objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = nil
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ return results, objective_functions
84
+ end
85
+
86
+ # Remove any invalid characters in the measure attribute keys. Periods and Pipes are the most problematic
87
+ # because mongo does not allow hash keys with periods, and the pipes are used in the map/reduce method that
88
+ # was written to speed up the data write in openstudio-server. Also remove any trailing underscores and spaces
89
+ #
90
+ # @param [Hash] hash Any hash with potentially problematic characters
91
+ # @param [Logger] logger Logger to write to
92
+ #
93
+ def rename_hash_keys(hash, logger)
94
+ # @todo should we log the name changes?
95
+ regex = /[|!@#\$%^&\*\(\)\{\}\\\[\];:'",<.>\/?\+=]+/
96
+
97
+ rename_keys = lambda do |h|
98
+ if Hash === h
99
+ h.each_key do |key|
100
+ if key.to_s =~ regex
101
+ logger.warn "Renaming result key '#{key}' to remove invalid characters"
102
+ end
103
+ end
104
+ Hash[h.map { |k, v| [k.to_s.gsub(regex, '_').squeeze('_').gsub(/[_\s]+$/, '').chomp.to_sym, rename_keys[v]] }]
105
+ else
106
+ h
107
+ end
108
+ end
109
+
110
+ rename_keys[hash]
111
+ end
112
+
113
+
114
+ # Save reports to a common directory
115
+ #
116
+ # @param [String] run_dir
117
+ # @param [String] directory
118
+ # @param [String] logger
119
+ #
120
+ def gather_reports(run_dir, directory, logger)
121
+ logger.info run_dir
122
+ logger.info directory
123
+
124
+ FileUtils.mkdir_p "#{directory}/reports"
125
+
126
+ # try to find the energyplus result file
127
+ eplus_html = "#{run_dir}/eplustbl.htm"
128
+ if File.exist? eplus_html
129
+ # do some encoding on the html if possible
130
+ html = File.read(eplus_html)
131
+ html = html.force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
132
+ logger.info "Saving EnergyPlus HTML report to #{directory}/reports/eplustbl.html"
133
+ File.open("#{directory}/reports/eplustbl.html", 'w') { |f| f << html }
134
+ end
135
+
136
+ # Also, find any "report*.*" files
137
+ Dir["#{run_dir}/*/report*.*"].each do |report|
138
+ # get the parent directory of the file and snake case it
139
+ measure_class_name = File.basename(File.dirname(report))
140
+ file_ext = File.extname(report)
141
+ append_str = File.basename(report, '.*')
142
+ new_file_name = "#{directory}/reports/#{measure_class_name}_#{append_str}#{file_ext}"
143
+ logger.info "Saving report #{report} to #{new_file_name}"
144
+ FileUtils.copy report, new_file_name
145
+ end
146
+
147
+ # Remove empty directories in run folder
148
+ Dir["#{run_dir}/*"].select { |d| File.directory? d }.select { |d| (Dir.entries(d) - %w(. ..)).empty? }.each do |d|
149
+ logger.info "Removing empty directory #{d}"
150
+ Dir.rmdir d
151
+ end
152
+ end
153
+
154
+
155
+ # A general post-processing step which could be made significantly more modular
156
+ #
157
+ # @param [String] run_dir
158
+ #
159
+ def cleanup(run_dir, directory, logger)
160
+
161
+
162
+ paths_to_rm = []
163
+ # paths_to_rm << Pathname.glob("#{run_dir}/*.osm")
164
+ # paths_to_rm << Pathname.glob("#{run_dir}/*.idf") # keep the idfs
165
+ # paths_to_rm << Pathname.glob("*.audit")
166
+ # paths_to_rm << Pathname.glob("*.bnd")
167
+ # paths_to_rm << Pathname.glob("#{run_dir}/*.eso")
168
+ paths_to_rm << Pathname.glob("#{run_dir}/*.mtr")
169
+ paths_to_rm << Pathname.glob("#{run_dir}/*.epw")
170
+ paths_to_rm << Pathname.glob("#{run_dir}/*.mtd")
171
+ paths_to_rm << Pathname.glob("#{run_dir}/*.rdd")
172
+ paths_to_rm.each { |p| FileUtils.rm_rf(p) }
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,108 @@
1
+ module OpenStudio
2
+ module Workflow
3
+ module Util
4
+ # The current precedence rules for weather files are defined in this module. Best practice is to only use the
5
+ # #get_weather_file method, as it will be forward compatible
6
+ #
7
+ module WeatherFile
8
+ # Returns the weather file with precedence
9
+ #
10
+ # @param [String] directory The directory to append all relative directories to, see #get_weather_file_from_fs
11
+ # @param [String] wf The weather file being searched for. If not the name of the file this parameter should be
12
+ # the absolute path specifying it's location
13
+ # @param [Array] wf_search_array The set of precedence ordered relative directories to search for the wf in. A
14
+ # typical entry might look like `['files', '../../files', '../../weather']`
15
+ # @param [Object] model The OpenStudio::Model object to parse, see #get_weather_file_from_osm
16
+ # @return [String, nil] The weather file with precedence if defined, nil if not, and a failure if the wf is
17
+ # defined but not in the filesystem
18
+ #
19
+ def get_weather_file(directory, wf, wf_search_array, model, logger = nil)
20
+ # TODO: this logic needs some updating, weather file should come from current model, found using search paths
21
+ logger ||= ::Logger.new(STDOUT) unless logger
22
+ if wf
23
+ weather_file = get_weather_file_from_fs(directory, wf, wf_search_array, logger)
24
+ raise 'Could not locate the weather file in the filesystem. Please see the log' if weather_file == false
25
+ end
26
+ weather_file = get_weather_file_from_osm(model, logger) if weather_file.nil?
27
+ raise 'Could not locate the weather file in the filesystem. Please see the log' if weather_file == false
28
+ logger.warn 'The weather file could not be determined. Please see the log for details' unless weather_file
29
+ weather_file
30
+ end
31
+
32
+ private
33
+
34
+ # Returns the weather file from the model. If the weather file is defined in the model, then
35
+ # it checks the file paths to check if the model exists. This allows for a user to upload a
36
+ # weather file in a measure and then have the measure's path be used for the weather file.
37
+ #
38
+ # @todo (rhorsey) verify the description of this method, as it seems suspect
39
+ # @param [Object] model The OpenStudio::Model object to retrieve the weather file from
40
+ # @return [nil,false, String] If the result is nil the weather file was not defined in the model, if the result
41
+ # is false the weather file was set but could not be found on the filesystem, if a string the weather file was
42
+ # defined and it's existence verified
43
+ #
44
+ def get_weather_file_from_osm(model, logger)
45
+ wf = nil
46
+ # grab the weather file out of the OSM if it exists
47
+ if model.weatherFile.empty?
48
+ logger.warn 'No weather file defined in the model'
49
+ else
50
+ p = model.weatherFile.get.path.get.to_s.gsub('file://', '')
51
+ wf = if File.exist? p
52
+ File.absolute_path(p)
53
+ else
54
+ # this is the weather file from the OSM model
55
+ File.absolute_path(@model.weatherFile.get.path.get.to_s)
56
+ end
57
+ logger.info "The weather file path found in the model object: #{wf}"
58
+ unless File.exist? wf
59
+ logger.warn 'The weather file could not be found on the filesystem.'
60
+ wf = false
61
+ end
62
+ end
63
+ wf
64
+ end
65
+
66
+ # Returns the weather file defined in the OSW
67
+ #
68
+ # @param [String] directory The base directory to append all relative directories to
69
+ # @param [String] wf The weather file being searched for. If not the name of the file this parameter should be
70
+ # the absolute path specifying it's location
71
+ # @param [Array] wf_search_array The set of precedence ordered relative directories to search for the wf in. A
72
+ # typical entry might look like `['files', '../../files', '../../weather']`
73
+ # @return [nil, false, String] If the result is nil the weather file was not defined in the workflow, if the
74
+ # result is false the weather file was set but could not be found on the filesystem, if a string the weather
75
+ # file was defined and it's existence verified. The order of precedence for paths is as follows: 1 - an
76
+ # absolute path defined in wf, 2 - the wf_search_array, should it be defined, joined with the weather file and
77
+ # appended to the directory, with each entry in the array searched until the wf is found
78
+ #
79
+ def get_weather_file_from_fs(directory, wf, wf_search_array, logger)
80
+ raise "wf was defined as #{wf}. Please correct" unless wf
81
+ weather_file = nil
82
+ if Pathname.new(wf).absolute?
83
+ weather_file = wf
84
+ else
85
+ wf_search_array.each do |wf_dir|
86
+ logger.warn "The path #{wf_dir} does not exist" unless File.exist? File.join(directory, wf_dir)
87
+ next unless File.exist? File.join(directory, wf_dir)
88
+ if Dir.entries(File.join(directory, wf_dir)).include? File.basename(wf)
89
+ weather_file = File.absolute_path(File.join(directory, wf_dir, wf))
90
+ break
91
+ end
92
+ end
93
+ end
94
+ unless weather_file
95
+ logger.warn 'The weather file was not found on the filesystem'
96
+ return nil
97
+ end
98
+ logger.info "Weather file with precedence in the file system is #{weather_file}"
99
+ unless File.exist? weather_file
100
+ logger.warn 'The weather file could not be found on the filesystem'
101
+ weather_file = false
102
+ end
103
+ weather_file
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,14 @@
1
+ module OpenStudio
2
+ module Workflow
3
+ # Hard load utils for the moment
4
+ #
5
+ module Util
6
+ require 'openstudio/workflow/util/io'
7
+ require 'openstudio/workflow/util/measure'
8
+ require 'openstudio/workflow/util/weather_file'
9
+ require 'openstudio/workflow/util/model'
10
+ require 'openstudio/workflow/util/energyplus'
11
+ require 'openstudio/workflow/util/post_process'
12
+ end
13
+ end
14
+ end
@@ -19,6 +19,6 @@
19
19
 
20
20
  module OpenStudio
21
21
  module Workflow
22
- VERSION = '1.0.0.pat1'
22
+ VERSION = '1.0.0'.freeze # Suffixes must have periods (not dashes)
23
23
  end
24
24
  end