openstudio-workflow 1.0.0.pat1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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