openstudio-workflow 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -72
  3. data/README.md +48 -48
  4. data/Rakefile +36 -36
  5. data/lib/openstudio/workflow/adapters/input/local.rb +240 -240
  6. data/lib/openstudio/workflow/adapters/output/local.rb +95 -95
  7. data/lib/openstudio/workflow/adapters/output/socket.rb +91 -91
  8. data/lib/openstudio/workflow/adapters/output/web.rb +66 -66
  9. data/lib/openstudio/workflow/adapters/output_adapter.rb +147 -147
  10. data/lib/openstudio/workflow/job.rb +22 -22
  11. data/lib/openstudio/workflow/jobs/resources/monthly_report.idf +222 -222
  12. data/lib/openstudio/workflow/jobs/run_energyplus.rb +49 -49
  13. data/lib/openstudio/workflow/jobs/run_ep_measures.rb +55 -55
  14. data/lib/openstudio/workflow/jobs/run_initialization.rb +167 -167
  15. data/lib/openstudio/workflow/jobs/run_os_measures.rb +69 -69
  16. data/lib/openstudio/workflow/jobs/run_postprocess.rb +53 -53
  17. data/lib/openstudio/workflow/jobs/run_preprocess.rb +69 -69
  18. data/lib/openstudio/workflow/jobs/run_reporting_measures.rb +98 -98
  19. data/lib/openstudio/workflow/jobs/run_translation.rb +61 -61
  20. data/lib/openstudio/workflow/multi_delegator.rb +46 -46
  21. data/lib/openstudio/workflow/registry.rb +137 -137
  22. data/lib/openstudio/workflow/run.rb +299 -299
  23. data/lib/openstudio/workflow/time_logger.rb +53 -53
  24. data/lib/openstudio/workflow/util/energyplus.rb +564 -564
  25. data/lib/openstudio/workflow/util/io.rb +33 -33
  26. data/lib/openstudio/workflow/util/measure.rb +588 -586
  27. data/lib/openstudio/workflow/util/model.rb +100 -100
  28. data/lib/openstudio/workflow/util/post_process.rb +187 -187
  29. data/lib/openstudio/workflow/util/weather_file.rb +108 -108
  30. data/lib/openstudio/workflow/util.rb +14 -14
  31. data/lib/openstudio/workflow/version.rb +24 -24
  32. data/lib/openstudio/workflow_json.rb +426 -426
  33. data/lib/openstudio/workflow_runner.rb +215 -215
  34. data/lib/openstudio-workflow.rb +49 -49
  35. metadata +3 -3
@@ -1,100 +1,100 @@
1
- module OpenStudio
2
- module Workflow
3
- module Util
4
- # Manages routine tasks involving OpenStudio::Model or OpenStudio::Workflow objects, such as loading, saving, and
5
- # translating them.
6
- #
7
- module Model
8
- # Method to create / load an OSM file
9
- #
10
- # @param [String] osm_path The full path to an OSM file to load
11
- # @param [Object] logger An optional logger to use for finding the OSM model
12
- # @return [Object] The return from this method is a loaded OSM or a failure.
13
- #
14
- def load_osm(osm_path, logger)
15
- logger.info 'Loading OSM model'
16
-
17
- # Load the model and return it
18
- logger.info "Reading in OSM model #{osm_path}"
19
-
20
- loaded_model = nil
21
- begin
22
- translator = OpenStudio::OSVersion::VersionTranslator.new
23
- loaded_model = translator.loadModel(osm_path)
24
- rescue
25
- # TODO: get translator working in embedded.
26
- # Need to embed idd files
27
- logger.warn 'OpenStudio VersionTranslator could not be loaded'
28
- loaded_model = OpenStudio::Model::Model.load(osm_path)
29
- end
30
- raise "Failed to load OSM file #{osm_path}" if loaded_model.empty?
31
- loaded_model.get
32
- end
33
-
34
- # Method to create / load an IDF file
35
- #
36
- # @param [String] idf_path Full path to the IDF
37
- # @param [Object] logger An optional logger to use for finding the idf model
38
- # @return [Object] The return from this method is a loaded IDF or a failure.
39
- #
40
- def load_idf(idf_path, logger)
41
- logger.info 'Loading IDF model'
42
-
43
- # Load the IDF into a workspace object and return it
44
- logger.info "Reading in IDF model #{idf_path}"
45
-
46
- idf = OpenStudio::Workspace.load(idf_path)
47
- raise "Failed to load IDF file #{idf_path}" if idf.empty?
48
- idf.get
49
- end
50
-
51
- # Translates a OpenStudio model object into an OpenStudio IDF object
52
- #
53
- # @param [Object] model the OpenStudio::Model instance to translate into an OpenStudio::Workspace object -- see
54
- # the OpenStudio SDK for details on the process
55
- # @return [Object] Returns and OpenStudio::Workspace object
56
- # @todo (rhorsey) rescue errors here
57
- #
58
- def translate_to_energyplus(model, logger = nil)
59
- logger = ::Logger.new(STDOUT) unless logger
60
- logger.info 'Translate object to EnergyPlus IDF in preparation for EnergyPlus'
61
- a = ::Time.now
62
- # ensure objects exist for reporting purposes
63
- model.getFacility
64
- model.getBuilding
65
- forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
66
- model_idf = forward_translator.translateModel(model)
67
- b = ::Time.now
68
- logger.info "Translate object to EnergyPlus IDF took #{b.to_f - a.to_f}"
69
- model_idf
70
- end
71
-
72
- # Saves an OpenStudio model object to file
73
- #
74
- # @param [Object] model The OpenStudio::Model instance to save to file
75
- # @param [String] save_directory Folder to save the model in
76
- # @param [String] name ('in.osm') Option to define a non-standard name
77
- # @return [String] OSM file name
78
- #
79
- def save_osm(model, save_directory, name = 'in.osm')
80
- osm_filename = File.join(save_directory.to_s, name.to_s)
81
- File.open(osm_filename, 'w') { |f| f << model.to_s }
82
- osm_filename
83
- end
84
-
85
- # Saves an OpenStudio IDF model object to file
86
- #
87
- # @param [Object] model The OpenStudio::Workspace instance to save to file
88
- # @param [String] save_directory Folder to save the model in
89
- # @param [String] name ('in.osm') Option to define a non-standard name
90
- # @return [String] IDF file name
91
- #
92
- def save_idf(model_idf, save_directory, name = 'in.idf')
93
- idf_filename = File.join(save_directory.to_s, name.to_s)
94
- File.open(idf_filename, 'w') { |f| f << model_idf.to_s }
95
- idf_filename
96
- end
97
- end
98
- end
99
- end
100
- end
1
+ module OpenStudio
2
+ module Workflow
3
+ module Util
4
+ # Manages routine tasks involving OpenStudio::Model or OpenStudio::Workflow objects, such as loading, saving, and
5
+ # translating them.
6
+ #
7
+ module Model
8
+ # Method to create / load an OSM file
9
+ #
10
+ # @param [String] osm_path The full path to an OSM file to load
11
+ # @param [Object] logger An optional logger to use for finding the OSM model
12
+ # @return [Object] The return from this method is a loaded OSM or a failure.
13
+ #
14
+ def load_osm(osm_path, logger)
15
+ logger.info 'Loading OSM model'
16
+
17
+ # Load the model and return it
18
+ logger.info "Reading in OSM model #{osm_path}"
19
+
20
+ loaded_model = nil
21
+ begin
22
+ translator = OpenStudio::OSVersion::VersionTranslator.new
23
+ loaded_model = translator.loadModel(osm_path)
24
+ rescue
25
+ # TODO: get translator working in embedded.
26
+ # Need to embed idd files
27
+ logger.warn 'OpenStudio VersionTranslator could not be loaded'
28
+ loaded_model = OpenStudio::Model::Model.load(osm_path)
29
+ end
30
+ raise "Failed to load OSM file #{osm_path}" if loaded_model.empty?
31
+ loaded_model.get
32
+ end
33
+
34
+ # Method to create / load an IDF file
35
+ #
36
+ # @param [String] idf_path Full path to the IDF
37
+ # @param [Object] logger An optional logger to use for finding the idf model
38
+ # @return [Object] The return from this method is a loaded IDF or a failure.
39
+ #
40
+ def load_idf(idf_path, logger)
41
+ logger.info 'Loading IDF model'
42
+
43
+ # Load the IDF into a workspace object and return it
44
+ logger.info "Reading in IDF model #{idf_path}"
45
+
46
+ idf = OpenStudio::Workspace.load(idf_path)
47
+ raise "Failed to load IDF file #{idf_path}" if idf.empty?
48
+ idf.get
49
+ end
50
+
51
+ # Translates a OpenStudio model object into an OpenStudio IDF object
52
+ #
53
+ # @param [Object] model the OpenStudio::Model instance to translate into an OpenStudio::Workspace object -- see
54
+ # the OpenStudio SDK for details on the process
55
+ # @return [Object] Returns and OpenStudio::Workspace object
56
+ # @todo (rhorsey) rescue errors here
57
+ #
58
+ def translate_to_energyplus(model, logger = nil)
59
+ logger = ::Logger.new(STDOUT) unless logger
60
+ logger.info 'Translate object to EnergyPlus IDF in preparation for EnergyPlus'
61
+ a = ::Time.now
62
+ # ensure objects exist for reporting purposes
63
+ model.getFacility
64
+ model.getBuilding
65
+ forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
66
+ model_idf = forward_translator.translateModel(model)
67
+ b = ::Time.now
68
+ logger.info "Translate object to EnergyPlus IDF took #{b.to_f - a.to_f}"
69
+ model_idf
70
+ end
71
+
72
+ # Saves an OpenStudio model object to file
73
+ #
74
+ # @param [Object] model The OpenStudio::Model instance to save to file
75
+ # @param [String] save_directory Folder to save the model in
76
+ # @param [String] name ('in.osm') Option to define a non-standard name
77
+ # @return [String] OSM file name
78
+ #
79
+ def save_osm(model, save_directory, name = 'in.osm')
80
+ osm_filename = File.join(save_directory.to_s, name.to_s)
81
+ File.open(osm_filename, 'w') { |f| f << model.to_s }
82
+ osm_filename
83
+ end
84
+
85
+ # Saves an OpenStudio IDF model object to file
86
+ #
87
+ # @param [Object] model The OpenStudio::Workspace instance to save to file
88
+ # @param [String] save_directory Folder to save the model in
89
+ # @param [String] name ('in.osm') Option to define a non-standard name
90
+ # @return [String] IDF file name
91
+ #
92
+ def save_idf(model_idf, save_directory, name = 'in.idf')
93
+ idf_filename = File.join(save_directory.to_s, name.to_s)
94
+ File.open(idf_filename, 'w') { |f| f << model_idf.to_s }
95
+ idf_filename
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -1,187 +1,187 @@
1
- module OpenStudio
2
- module Workflow
3
- module Util
4
- require 'openstudio/workflow/util/measure'
5
- require 'csv'
6
- require 'rexml/document'
7
-
8
- # This module serves as a wrapper around various post-processing tasks used to manage outputs
9
- # @todo (rhorsey) ummmm. So some of this is pretty ugly. Since @dmacumber had ideas about this maybe he can figure
10
- # out what to do about it all
11
- # @todo (nlong) the output adapter restructure will frack up the extraction method royally
12
- #
13
- module PostProcess
14
- # This method loads a sql file into OpenStudio and returns it
15
- #
16
- # @param [String] sql_file Absolute path to the sql file to be loaded
17
- # @return [Object, nil] The OpenStudio::SqlFile object, or nil if it could not be found
18
- #
19
- def load_sql_file(sql_file)
20
- return nil unless File.exist? sql_file
21
- OpenStudio::SqlFile.new(@sql_filename)
22
- end
23
-
24
- # This method parses all sorts of stuff which something needs
25
- #
26
- # @param [String] run_dir The directory that the simulation was run in
27
- # @return [Hash, Hash] results and objective_function (which may be empty) are returned
28
- # @todo (rhorsey) fix the description
29
- #
30
- def run_extract_inputs_and_outputs(run_dir, logger)
31
- # For xml, the measure attributes are in the measure_attributes_xml.json file
32
- # TODO: somehow pass the metadata around on which JSONs to suck into the database
33
- results = {}
34
- # Inputs are in the measure_attributes.json file
35
- if File.exist? "#{run_dir}/measure_attributes.json"
36
- h = JSON.parse(File.read("#{run_dir}/measure_attributes.json"), symbolize_names: true)
37
- h = rename_hash_keys(h, logger)
38
- results.merge! h
39
- end
40
-
41
- logger.info 'Saving the result hash to file'
42
- File.open("#{run_dir}/results.json", 'w') { |f| f << JSON.pretty_generate(results) }
43
-
44
- objective_functions = {}
45
- if @registry[:analysis]
46
- logger.info 'Iterating over Analysis JSON Output Variables'
47
- # Save the objective functions to the object for sending back to the simulation executive
48
- analysis_json = @registry[:analysis]
49
- if analysis_json[:analysis] && analysis_json[:analysis][:output_variables]
50
- analysis_json[:analysis][:output_variables].each do |variable|
51
- # determine which ones are the objective functions (code smell: todo: use enumerator)
52
- if variable[:objective_function]
53
- logger.info "Looking for objective function #{variable[:name]}"
54
- # TODO: move this to cleaner logic. Use ostruct?
55
- k, v = variable[:name].split('.')
56
-
57
- # look for the objective function key and make sure that it is not nil. False is an okay obj function.
58
- if results.key?(k.to_sym) && !results[k.to_sym][v.to_sym].nil?
59
- objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = results[k.to_sym][v.to_sym]
60
- if variable[:objective_function_target]
61
- logger.info "Found objective function target for #{variable[:name]}"
62
- objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_target].to_f
63
- end
64
- if variable[:scaling_factor]
65
- logger.info "Found scaling factor for #{variable[:name]}"
66
- objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = variable[:scaling_factor].to_f
67
- end
68
- if variable[:objective_function_group]
69
- logger.info "Found objective function group for #{variable[:name]}"
70
- objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_group].to_f
71
- end
72
- else
73
- logger.warn "No results for objective function #{variable[:name]}"
74
- objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = Float::MAX
75
- objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = nil
76
- objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = nil
77
- objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = nil
78
- end
79
- end
80
- end
81
- end
82
- end
83
-
84
- return results, objective_functions
85
- end
86
-
87
- # Remove any invalid characters in the measure attribute keys. Periods and Pipes are the most problematic
88
- # because mongo does not allow hash keys with periods, and the pipes are used in the map/reduce method that
89
- # was written to speed up the data write in openstudio-server. Also remove any trailing underscores and spaces
90
- #
91
- # @param [Hash] hash Any hash with potentially problematic characters
92
- # @param [Logger] logger Logger to write to
93
- #
94
- def rename_hash_keys(hash, logger)
95
- # @todo should we log the name changes?
96
- regex = /[|!@#\$%^&\*\(\)\{\}\\\[\];:'",<.>\/?\+=]+/
97
-
98
- rename_keys = lambda do |h|
99
- if Hash === h
100
- h.each_key do |key|
101
- if key.to_s =~ regex
102
- logger.warn "Renaming result key '#{key}' to remove invalid characters"
103
- end
104
- end
105
- Hash[h.map { |k, v| [k.to_s.gsub(regex, '_').squeeze('_').gsub(/[_\s]+$/, '').chomp.to_sym, rename_keys[v]] }]
106
- else
107
- h
108
- end
109
- end
110
-
111
- rename_keys[hash]
112
- end
113
-
114
-
115
- # Save reports to a common directory
116
- #
117
- # @param [String] run_dir
118
- # @param [String] directory
119
- # @param [String] logger
120
- #
121
- def gather_reports(run_dir, directory, workflow_json, logger)
122
- logger.info run_dir
123
- logger.info directory
124
-
125
- FileUtils.mkdir_p "#{directory}/reports"
126
-
127
- # try to find the energyplus result file
128
- eplus_html = "#{run_dir}/eplustbl.htm"
129
- if File.exist? eplus_html
130
- # do some encoding on the html if possible
131
- html = File.read(eplus_html)
132
- html = html.force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
133
- logger.info "Saving EnergyPlus HTML report to #{directory}/reports/eplustbl.html"
134
- File.open("#{directory}/reports/eplustbl.html", 'w') { |f| f << html }
135
- end
136
-
137
- # Also, find any "report*.*" files
138
- Dir["#{run_dir}/*/report*.*"].each do |report|
139
- # HRH: This is a temporary work-around to support PAT 2.1 pretty names AND the CLI while we roll a WorkflowJSON solution
140
- measure_dir_name = File.dirname(report).split(File::SEPARATOR).last.gsub(/[0-9][0-9][0-9]_/, '')
141
- measure_xml_path = File.absolute_path(File.join(File.dirname(report), '../../..', 'measures',
142
- measure_dir_name, 'measure.xml'))
143
- logger.info "measure_xml_path: #{measure_xml_path}"
144
- if File.exists? measure_xml_path
145
- measure_xml = REXML::Document.new File.read(measure_xml_path)
146
- measure_class_name = OpenStudio.toUnderscoreCase(measure_xml.root.elements['class_name'].text)
147
- else
148
- measure_class_name = OpenStudio.toUnderscoreCase(measure_dir_name)
149
- end
150
- file_ext = File.extname(report)
151
- append_str = File.basename(report, '.*')
152
- new_file_name = "#{directory}/reports/#{measure_class_name}_#{append_str}#{file_ext}"
153
- logger.info "Saving report #{report} to #{new_file_name}"
154
- FileUtils.copy report, new_file_name
155
- end
156
-
157
- # Remove empty directories in run folder
158
- Dir["#{run_dir}/*"].select { |d| File.directory? d }.select { |d| (Dir.entries(d) - %w(. ..)).empty? }.each do |d|
159
- logger.info "Removing empty directory #{d}"
160
- Dir.rmdir d
161
- end
162
- end
163
-
164
-
165
- # A general post-processing step which could be made significantly more modular
166
- #
167
- # @param [String] run_dir
168
- #
169
- def cleanup(run_dir, directory, logger)
170
-
171
-
172
- paths_to_rm = []
173
- # paths_to_rm << Pathname.glob("#{run_dir}/*.osm")
174
- # paths_to_rm << Pathname.glob("#{run_dir}/*.idf") # keep the idfs
175
- # paths_to_rm << Pathname.glob("*.audit")
176
- # paths_to_rm << Pathname.glob("*.bnd")
177
- # paths_to_rm << Pathname.glob("#{run_dir}/*.eso")
178
- paths_to_rm << Pathname.glob("#{run_dir}/*.mtr")
179
- paths_to_rm << Pathname.glob("#{run_dir}/*.epw")
180
- #paths_to_rm << Pathname.glob("#{run_dir}/*.mtd")
181
- #paths_to_rm << Pathname.glob("#{run_dir}/*.rdd")
182
- paths_to_rm.each { |p| FileUtils.rm_rf(p) }
183
- end
184
- end
185
- end
186
- end
187
- end
1
+ module OpenStudio
2
+ module Workflow
3
+ module Util
4
+ require 'openstudio/workflow/util/measure'
5
+ require 'csv'
6
+ require 'rexml/document'
7
+
8
+ # This module serves as a wrapper around various post-processing tasks used to manage outputs
9
+ # @todo (rhorsey) ummmm. So some of this is pretty ugly. Since @dmacumber had ideas about this maybe he can figure
10
+ # out what to do about it all
11
+ # @todo (nlong) the output adapter restructure will frack up the extraction method royally
12
+ #
13
+ module PostProcess
14
+ # This method loads a sql file into OpenStudio and returns it
15
+ #
16
+ # @param [String] sql_file Absolute path to the sql file to be loaded
17
+ # @return [Object, nil] The OpenStudio::SqlFile object, or nil if it could not be found
18
+ #
19
+ def load_sql_file(sql_file)
20
+ return nil unless File.exist? sql_file
21
+ OpenStudio::SqlFile.new(@sql_filename)
22
+ end
23
+
24
+ # This method parses all sorts of stuff which something needs
25
+ #
26
+ # @param [String] run_dir The directory that the simulation was run in
27
+ # @return [Hash, Hash] results and objective_function (which may be empty) are returned
28
+ # @todo (rhorsey) fix the description
29
+ #
30
+ def run_extract_inputs_and_outputs(run_dir, logger)
31
+ # For xml, the measure attributes are in the measure_attributes_xml.json file
32
+ # TODO: somehow pass the metadata around on which JSONs to suck into the database
33
+ results = {}
34
+ # Inputs are in the measure_attributes.json file
35
+ if File.exist? "#{run_dir}/measure_attributes.json"
36
+ h = JSON.parse(File.read("#{run_dir}/measure_attributes.json"), symbolize_names: true)
37
+ h = rename_hash_keys(h, logger)
38
+ results.merge! h
39
+ end
40
+
41
+ logger.info 'Saving the result hash to file'
42
+ File.open("#{run_dir}/results.json", 'w') { |f| f << JSON.pretty_generate(results) }
43
+
44
+ objective_functions = {}
45
+ if @registry[:analysis]
46
+ logger.info 'Iterating over Analysis JSON Output Variables'
47
+ # Save the objective functions to the object for sending back to the simulation executive
48
+ analysis_json = @registry[:analysis]
49
+ if analysis_json[:analysis] && analysis_json[:analysis][:output_variables]
50
+ analysis_json[:analysis][:output_variables].each do |variable|
51
+ # determine which ones are the objective functions (code smell: todo: use enumerator)
52
+ if variable[:objective_function]
53
+ logger.info "Looking for objective function #{variable[:name]}"
54
+ # TODO: move this to cleaner logic. Use ostruct?
55
+ k, v = variable[:name].split('.')
56
+
57
+ # look for the objective function key and make sure that it is not nil. False is an okay obj function.
58
+ if results.key?(k.to_sym) && !results[k.to_sym][v.to_sym].nil?
59
+ objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = results[k.to_sym][v.to_sym]
60
+ if variable[:objective_function_target]
61
+ logger.info "Found objective function target for #{variable[:name]}"
62
+ objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_target].to_f
63
+ end
64
+ if variable[:scaling_factor]
65
+ logger.info "Found scaling factor for #{variable[:name]}"
66
+ objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = variable[:scaling_factor].to_f
67
+ end
68
+ if variable[:objective_function_group]
69
+ logger.info "Found objective function group for #{variable[:name]}"
70
+ objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_group].to_f
71
+ end
72
+ else
73
+ logger.warn "No results for objective function #{variable[:name]}"
74
+ objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = Float::MAX
75
+ objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = nil
76
+ objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = nil
77
+ objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = nil
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ return results, objective_functions
85
+ end
86
+
87
+ # Remove any invalid characters in the measure attribute keys. Periods and Pipes are the most problematic
88
+ # because mongo does not allow hash keys with periods, and the pipes are used in the map/reduce method that
89
+ # was written to speed up the data write in openstudio-server. Also remove any trailing underscores and spaces
90
+ #
91
+ # @param [Hash] hash Any hash with potentially problematic characters
92
+ # @param [Logger] logger Logger to write to
93
+ #
94
+ def rename_hash_keys(hash, logger)
95
+ # @todo should we log the name changes?
96
+ regex = /[|!@#\$%^&\*\(\)\{\}\\\[\];:'",<.>\/?\+=]+/
97
+
98
+ rename_keys = lambda do |h|
99
+ if Hash === h
100
+ h.each_key do |key|
101
+ if key.to_s =~ regex
102
+ logger.warn "Renaming result key '#{key}' to remove invalid characters"
103
+ end
104
+ end
105
+ Hash[h.map { |k, v| [k.to_s.gsub(regex, '_').squeeze('_').gsub(/[_\s]+$/, '').chomp.to_sym, rename_keys[v]] }]
106
+ else
107
+ h
108
+ end
109
+ end
110
+
111
+ rename_keys[hash]
112
+ end
113
+
114
+
115
+ # Save reports to a common directory
116
+ #
117
+ # @param [String] run_dir
118
+ # @param [String] directory
119
+ # @param [String] logger
120
+ #
121
+ def gather_reports(run_dir, directory, workflow_json, logger)
122
+ logger.info run_dir
123
+ logger.info directory
124
+
125
+ FileUtils.mkdir_p "#{directory}/reports"
126
+
127
+ # try to find the energyplus result file
128
+ eplus_html = "#{run_dir}/eplustbl.htm"
129
+ if File.exist? eplus_html
130
+ # do some encoding on the html if possible
131
+ html = File.read(eplus_html)
132
+ html = html.force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
133
+ logger.info "Saving EnergyPlus HTML report to #{directory}/reports/eplustbl.html"
134
+ File.open("#{directory}/reports/eplustbl.html", 'w') { |f| f << html }
135
+ end
136
+
137
+ # Also, find any "report*.*" files
138
+ Dir["#{run_dir}/*/report*.*"].each do |report|
139
+ # HRH: This is a temporary work-around to support PAT 2.1 pretty names AND the CLI while we roll a WorkflowJSON solution
140
+ measure_dir_name = File.dirname(report).split(File::SEPARATOR).last.gsub(/[0-9][0-9][0-9]_/, '')
141
+ measure_xml_path = File.absolute_path(File.join(File.dirname(report), '../../..', 'measures',
142
+ measure_dir_name, 'measure.xml'))
143
+ logger.info "measure_xml_path: #{measure_xml_path}"
144
+ if File.exists? measure_xml_path
145
+ measure_xml = REXML::Document.new File.read(measure_xml_path)
146
+ measure_class_name = OpenStudio.toUnderscoreCase(measure_xml.root.elements['class_name'].text)
147
+ else
148
+ measure_class_name = OpenStudio.toUnderscoreCase(measure_dir_name)
149
+ end
150
+ file_ext = File.extname(report)
151
+ append_str = File.basename(report, '.*')
152
+ new_file_name = "#{directory}/reports/#{measure_class_name}_#{append_str}#{file_ext}"
153
+ logger.info "Saving report #{report} to #{new_file_name}"
154
+ FileUtils.copy report, new_file_name
155
+ end
156
+
157
+ # Remove empty directories in run folder
158
+ Dir["#{run_dir}/*"].select { |d| File.directory? d }.select { |d| (Dir.entries(d) - %w(. ..)).empty? }.each do |d|
159
+ logger.info "Removing empty directory #{d}"
160
+ Dir.rmdir d
161
+ end
162
+ end
163
+
164
+
165
+ # A general post-processing step which could be made significantly more modular
166
+ #
167
+ # @param [String] run_dir
168
+ #
169
+ def cleanup(run_dir, directory, logger)
170
+
171
+
172
+ paths_to_rm = []
173
+ # paths_to_rm << Pathname.glob("#{run_dir}/*.osm")
174
+ # paths_to_rm << Pathname.glob("#{run_dir}/*.idf") # keep the idfs
175
+ # paths_to_rm << Pathname.glob("*.audit")
176
+ # paths_to_rm << Pathname.glob("*.bnd")
177
+ # paths_to_rm << Pathname.glob("#{run_dir}/*.eso")
178
+ paths_to_rm << Pathname.glob("#{run_dir}/*.mtr")
179
+ paths_to_rm << Pathname.glob("#{run_dir}/*.epw")
180
+ #paths_to_rm << Pathname.glob("#{run_dir}/*.mtd")
181
+ #paths_to_rm << Pathname.glob("#{run_dir}/*.rdd")
182
+ paths_to_rm.each { |p| FileUtils.rm_rf(p) }
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end