openstudio-workflow 1.3.4 → 1.3.5

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 +89 -77
  3. data/README.md +67 -93
  4. data/Rakefile +36 -36
  5. data/lib/openstudio-workflow.rb +65 -65
  6. data/lib/openstudio/workflow/adapters/input/local.rb +311 -324
  7. data/lib/openstudio/workflow/adapters/output/local.rb +158 -161
  8. data/lib/openstudio/workflow/adapters/output/socket.rb +106 -107
  9. data/lib/openstudio/workflow/adapters/output/web.rb +82 -82
  10. data/lib/openstudio/workflow/adapters/output_adapter.rb +163 -163
  11. data/lib/openstudio/workflow/job.rb +57 -57
  12. data/lib/openstudio/workflow/jobs/resources/monthly_report.idf +222 -222
  13. data/lib/openstudio/workflow/jobs/run_energyplus.rb +70 -70
  14. data/lib/openstudio/workflow/jobs/run_ep_measures.rb +73 -73
  15. data/lib/openstudio/workflow/jobs/run_initialization.rb +203 -203
  16. data/lib/openstudio/workflow/jobs/run_os_measures.rb +89 -89
  17. data/lib/openstudio/workflow/jobs/run_postprocess.rb +73 -73
  18. data/lib/openstudio/workflow/jobs/run_preprocess.rb +104 -104
  19. data/lib/openstudio/workflow/jobs/run_reporting_measures.rb +118 -118
  20. data/lib/openstudio/workflow/jobs/run_translation.rb +84 -84
  21. data/lib/openstudio/workflow/multi_delegator.rb +62 -62
  22. data/lib/openstudio/workflow/registry.rb +172 -172
  23. data/lib/openstudio/workflow/run.rb +342 -328
  24. data/lib/openstudio/workflow/time_logger.rb +96 -96
  25. data/lib/openstudio/workflow/util.rb +49 -49
  26. data/lib/openstudio/workflow/util/energyplus.rb +575 -605
  27. data/lib/openstudio/workflow/util/io.rb +68 -68
  28. data/lib/openstudio/workflow/util/measure.rb +658 -650
  29. data/lib/openstudio/workflow/util/model.rb +151 -151
  30. data/lib/openstudio/workflow/util/post_process.rb +235 -238
  31. data/lib/openstudio/workflow/util/weather_file.rb +143 -143
  32. data/lib/openstudio/workflow/version.rb +40 -40
  33. data/lib/openstudio/workflow_json.rb +475 -476
  34. data/lib/openstudio/workflow_runner.rb +263 -268
  35. metadata +24 -24
@@ -1,151 +1,151 @@
1
- # *******************************************************************************
2
- # OpenStudio(R), Copyright (c) 2008-2018, Alliance for Sustainable Energy, LLC.
3
- # All rights reserved.
4
- # Redistribution and use in source and binary forms, with or without
5
- # modification, are permitted provided that the following conditions are met:
6
- #
7
- # (1) Redistributions of source code must retain the above copyright notice,
8
- # this list of conditions and the following disclaimer.
9
- #
10
- # (2) Redistributions in binary form must reproduce the above copyright notice,
11
- # this list of conditions and the following disclaimer in the documentation
12
- # and/or other materials provided with the distribution.
13
- #
14
- # (3) Neither the name of the copyright holder nor the names of any contributors
15
- # may be used to endorse or promote products derived from this software without
16
- # specific prior written permission from the respective party.
17
- #
18
- # (4) Other than as required in clauses (1) and (2), distributions in any form
19
- # of modifications or other derivative works may not use the "OpenStudio"
20
- # trademark, "OS", "os", or any other confusingly similar designation without
21
- # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
- #
23
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
- # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES
27
- # GOVERNMENT, OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
- # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
- # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
- # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
- # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
33
- # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
- # *******************************************************************************
35
-
36
- module OpenStudio
37
- module Workflow
38
- module Util
39
- # Manages routine tasks involving OpenStudio::Model or OpenStudio::Workflow objects, such as loading, saving, and
40
- # translating them.
41
- #
42
- module Model
43
- # Method to create / load an OSM file
44
- #
45
- # @param [String] osm_path The full path to an OSM file to load
46
- # @param [Object] logger An optional logger to use for finding the OSM model
47
- # @return [Object] The return from this method is a loaded OSM or a failure.
48
- #
49
- def load_osm(osm_path, logger)
50
- logger.info 'Loading OSM model'
51
-
52
- # Load the model and return it
53
- logger.info "Reading in OSM model #{osm_path}"
54
-
55
- loaded_model = nil
56
- begin
57
- translator = OpenStudio::OSVersion::VersionTranslator.new
58
- loaded_model = translator.loadModel(osm_path)
59
- rescue
60
- # TODO: get translator working in embedded.
61
- # Need to embed idd files
62
- logger.warn 'OpenStudio VersionTranslator could not be loaded'
63
- loaded_model = OpenStudio::Model::Model.load(osm_path)
64
- end
65
- raise "Failed to load OSM file #{osm_path}" if loaded_model.empty?
66
- loaded_model.get
67
- end
68
-
69
- # Method to create / load an IDF file
70
- #
71
- # @param [String] idf_path Full path to the IDF
72
- # @param [Object] logger An optional logger to use for finding the idf model
73
- # @return [Object] The return from this method is a loaded IDF or a failure.
74
- #
75
- def load_idf(idf_path, logger)
76
- logger.info 'Loading IDF model'
77
-
78
- # Load the IDF into a workspace object and return it
79
- logger.info "Reading in IDF model #{idf_path}"
80
-
81
- idf = OpenStudio::Workspace.load(idf_path)
82
- raise "Failed to load IDF file #{idf_path}" if idf.empty?
83
- idf.get
84
- end
85
-
86
- # Translates a OpenStudio model object into an OpenStudio IDF object
87
- #
88
- # @param [Object] model the OpenStudio::Model instance to translate into an OpenStudio::Workspace object -- see
89
- # the OpenStudio SDK for details on the process
90
- # @return [Object] Returns and OpenStudio::Workspace object
91
- # @todo (rhorsey) rescue errors here
92
- #
93
- def translate_to_energyplus(model, logger = nil)
94
- logger = ::Logger.new(STDOUT) unless logger
95
- logger.info 'Translate object to EnergyPlus IDF in preparation for EnergyPlus'
96
- a = ::Time.now
97
- # ensure objects exist for reporting purposes
98
- model.getFacility
99
- model.getBuilding
100
- forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
101
- model_idf = forward_translator.translateModel(model)
102
- b = ::Time.now
103
- logger.info "Translate object to EnergyPlus IDF took #{b.to_f - a.to_f}"
104
- model_idf
105
- end
106
-
107
- # Saves an OpenStudio model object to file
108
- #
109
- # @param [Object] model The OpenStudio::Model instance to save to file
110
- # @param [String] save_directory Folder to save the model in
111
- # @param [String] name ('in.osm') Option to define a non-standard name
112
- # @return [String] OSM file name
113
- #
114
- def save_osm(model, save_directory, name = 'in.osm')
115
- osm_filename = File.join(save_directory.to_s, name.to_s)
116
- File.open(osm_filename, 'w') do |f|
117
- f << model.to_s
118
- # make sure data is written to the disk one way or the other
119
- begin
120
- f.fsync
121
- rescue
122
- f.flush
123
- end
124
- end
125
- osm_filename
126
- end
127
-
128
- # Saves an OpenStudio IDF model object to file
129
- #
130
- # @param [Object] model The OpenStudio::Workspace instance to save to file
131
- # @param [String] save_directory Folder to save the model in
132
- # @param [String] name ('in.osm') Option to define a non-standard name
133
- # @return [String] IDF file name
134
- #
135
- def save_idf(model_idf, save_directory, name = 'in.idf')
136
- idf_filename = File.join(save_directory.to_s, name.to_s)
137
- File.open(idf_filename, 'w') do |f|
138
- f << model_idf.to_s
139
- # make sure data is written to the disk one way or the other
140
- begin
141
- f.fsync
142
- rescue
143
- f.flush
144
- end
145
- end
146
- idf_filename
147
- end
148
- end
149
- end
150
- end
151
- end
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
3
+ # All rights reserved.
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # (1) Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # (3) Neither the name of the copyright holder nor the names of any contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission from the respective party.
17
+ #
18
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
19
+ # of modifications or other derivative works may not use the "OpenStudio"
20
+ # trademark, "OS", "os", or any other confusingly similar designation without
21
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES
27
+ # GOVERNMENT, OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
33
+ # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # *******************************************************************************
35
+
36
+ module OpenStudio
37
+ module Workflow
38
+ module Util
39
+ # Manages routine tasks involving OpenStudio::Model or OpenStudio::Workflow objects, such as loading, saving, and
40
+ # translating them.
41
+ #
42
+ module Model
43
+ # Method to create / load an OSM file
44
+ #
45
+ # @param [String] osm_path The full path to an OSM file to load
46
+ # @param [Object] logger An optional logger to use for finding the OSM model
47
+ # @return [Object] The return from this method is a loaded OSM or a failure.
48
+ #
49
+ def load_osm(osm_path, logger)
50
+ logger.info 'Loading OSM model'
51
+
52
+ # Load the model and return it
53
+ logger.info "Reading in OSM model #{osm_path}"
54
+
55
+ loaded_model = nil
56
+ begin
57
+ translator = OpenStudio::OSVersion::VersionTranslator.new
58
+ loaded_model = translator.loadModel(osm_path)
59
+ rescue StandardError
60
+ # TODO: get translator working in embedded.
61
+ # Need to embed idd files
62
+ logger.warn 'OpenStudio VersionTranslator could not be loaded'
63
+ loaded_model = OpenStudio::Model::Model.load(osm_path)
64
+ end
65
+ raise "Failed to load OSM file #{osm_path}" if loaded_model.empty?
66
+ loaded_model.get
67
+ end
68
+
69
+ # Method to create / load an IDF file
70
+ #
71
+ # @param [String] idf_path Full path to the IDF
72
+ # @param [Object] logger An optional logger to use for finding the idf model
73
+ # @return [Object] The return from this method is a loaded IDF or a failure.
74
+ #
75
+ def load_idf(idf_path, logger)
76
+ logger.info 'Loading IDF model'
77
+
78
+ # Load the IDF into a workspace object and return it
79
+ logger.info "Reading in IDF model #{idf_path}"
80
+
81
+ idf = OpenStudio::Workspace.load(idf_path)
82
+ raise "Failed to load IDF file #{idf_path}" if idf.empty?
83
+ idf.get
84
+ end
85
+
86
+ # Translates a OpenStudio model object into an OpenStudio IDF object
87
+ #
88
+ # @param [Object] model the OpenStudio::Model instance to translate into an OpenStudio::Workspace object -- see
89
+ # the OpenStudio SDK for details on the process
90
+ # @return [Object] Returns and OpenStudio::Workspace object
91
+ # @todo (rhorsey) rescue errors here
92
+ #
93
+ def translate_to_energyplus(model, logger = nil)
94
+ logger ||= ::Logger.new(STDOUT)
95
+ logger.info 'Translate object to EnergyPlus IDF in preparation for EnergyPlus'
96
+ a = ::Time.now
97
+ # ensure objects exist for reporting purposes
98
+ model.getFacility
99
+ model.getBuilding
100
+ forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
101
+ model_idf = forward_translator.translateModel(model)
102
+ b = ::Time.now
103
+ logger.info "Translate object to EnergyPlus IDF took #{b.to_f - a.to_f}"
104
+ model_idf
105
+ end
106
+
107
+ # Saves an OpenStudio model object to file
108
+ #
109
+ # @param [Object] model The OpenStudio::Model instance to save to file
110
+ # @param [String] save_directory Folder to save the model in
111
+ # @param [String] name ('in.osm') Option to define a non-standard name
112
+ # @return [String] OSM file name
113
+ #
114
+ def save_osm(model, save_directory, name = 'in.osm')
115
+ osm_filename = File.join(save_directory.to_s, name.to_s)
116
+ File.open(osm_filename, 'w') do |f|
117
+ f << model.to_s
118
+ # make sure data is written to the disk one way or the other
119
+ begin
120
+ f.fsync
121
+ rescue StandardError
122
+ f.flush
123
+ end
124
+ end
125
+ osm_filename
126
+ end
127
+
128
+ # Saves an OpenStudio IDF model object to file
129
+ #
130
+ # @param [Object] model The OpenStudio::Workspace instance to save to file
131
+ # @param [String] save_directory Folder to save the model in
132
+ # @param [String] name ('in.osm') Option to define a non-standard name
133
+ # @return [String] IDF file name
134
+ #
135
+ def save_idf(model_idf, save_directory, name = 'in.idf')
136
+ idf_filename = File.join(save_directory.to_s, name.to_s)
137
+ File.open(idf_filename, 'w') do |f|
138
+ f << model_idf.to_s
139
+ # make sure data is written to the disk one way or the other
140
+ begin
141
+ f.fsync
142
+ rescue StandardError
143
+ f.flush
144
+ end
145
+ end
146
+ idf_filename
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -1,238 +1,235 @@
1
- # *******************************************************************************
2
- # OpenStudio(R), Copyright (c) 2008-2018, Alliance for Sustainable Energy, LLC.
3
- # All rights reserved.
4
- # Redistribution and use in source and binary forms, with or without
5
- # modification, are permitted provided that the following conditions are met:
6
- #
7
- # (1) Redistributions of source code must retain the above copyright notice,
8
- # this list of conditions and the following disclaimer.
9
- #
10
- # (2) Redistributions in binary form must reproduce the above copyright notice,
11
- # this list of conditions and the following disclaimer in the documentation
12
- # and/or other materials provided with the distribution.
13
- #
14
- # (3) Neither the name of the copyright holder nor the names of any contributors
15
- # may be used to endorse or promote products derived from this software without
16
- # specific prior written permission from the respective party.
17
- #
18
- # (4) Other than as required in clauses (1) and (2), distributions in any form
19
- # of modifications or other derivative works may not use the "OpenStudio"
20
- # trademark, "OS", "os", or any other confusingly similar designation without
21
- # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
- #
23
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
- # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES
27
- # GOVERNMENT, OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
- # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
- # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
- # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
- # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
33
- # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
- # *******************************************************************************
35
-
36
- module OpenStudio
37
- module Workflow
38
- module Util
39
- require 'openstudio/workflow/util/measure'
40
- require 'csv'
41
- require 'rexml/document'
42
-
43
- # This module serves as a wrapper around various post-processing tasks used to manage outputs
44
- # @todo (rhorsey) ummmm. So some of this is pretty ugly. Since @dmacumber had ideas about this maybe he can figure
45
- # out what to do about it all
46
- # @todo (nlong) the output adapter restructure will frack up the extraction method royally
47
- #
48
- module PostProcess
49
- # This method loads a sql file into OpenStudio and returns it
50
- #
51
- # @param [String] sql_file Absolute path to the sql file to be loaded
52
- # @return [Object, nil] The OpenStudio::SqlFile object, or nil if it could not be found
53
- #
54
- def load_sql_file(sql_file)
55
- return nil unless File.exist? sql_file
56
- OpenStudio::SqlFile.new(@sql_filename)
57
- end
58
-
59
- # This method parses all sorts of stuff which something needs
60
- #
61
- # @param [String] run_dir The directory that the simulation was run in
62
- # @return [Hash, Hash] results and objective_function (which may be empty) are returned
63
- # @todo (rhorsey) fix the description
64
- #
65
- def run_extract_inputs_and_outputs(run_dir, logger)
66
- # For xml, the measure attributes are in the measure_attributes_xml.json file
67
- # TODO: somehow pass the metadata around on which JSONs to suck into the database
68
- results = {}
69
- # Inputs are in the measure_attributes.json file
70
- if File.exist? "#{run_dir}/measure_attributes.json"
71
- h = JSON.parse(File.read("#{run_dir}/measure_attributes.json"), symbolize_names: true)
72
- h = rename_hash_keys(h, logger)
73
- results.merge! h
74
- end
75
-
76
- logger.info 'Saving the result hash to file'
77
- File.open("#{run_dir}/results.json", 'w') do |f|
78
- f << JSON.pretty_generate(results)
79
- # make sure data is written to the disk one way or the other
80
- begin
81
- f.fsync
82
- rescue
83
- f.flush
84
- end
85
- end
86
-
87
- objective_functions = {}
88
- if @registry[:analysis]
89
- logger.info 'Iterating over Analysis JSON Output Variables'
90
- # Save the objective functions to the object for sending back to the simulation executive
91
- analysis_json = @registry[:analysis]
92
- if analysis_json[:analysis] && analysis_json[:analysis][:output_variables]
93
- analysis_json[:analysis][:output_variables].each do |variable|
94
- # determine which ones are the objective functions (code smell: todo: use enumerator)
95
- if variable[:objective_function]
96
- logger.info "Looking for objective function #{variable[:name]}"
97
- # TODO: move this to cleaner logic. Use ostruct?
98
- k, v = variable[:name].split('.')
99
-
100
- # look for the objective function key and make sure that it is not nil. False is an okay obj function.
101
- if results.key?(k.to_sym) && !results[k.to_sym][v.to_sym].nil?
102
- objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = results[k.to_sym][v.to_sym]
103
- if variable[:objective_function_target]
104
- logger.info "Found objective function target for #{variable[:name]}"
105
- objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_target].to_f
106
- end
107
- if variable[:scaling_factor]
108
- logger.info "Found scaling factor for #{variable[:name]}"
109
- objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = variable[:scaling_factor].to_f
110
- end
111
- if variable[:objective_function_group]
112
- logger.info "Found objective function group for #{variable[:name]}"
113
- objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_group].to_f
114
- end
115
- else
116
- logger.warn "No results for objective function #{variable[:name]}"
117
- objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = Float::MAX
118
- objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = nil
119
- objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = nil
120
- objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = nil
121
- end
122
- end
123
- end
124
- end
125
- end
126
-
127
- return results, objective_functions
128
- end
129
-
130
- # Remove any invalid characters in the measure attribute keys. Periods and Pipes are the most problematic
131
- # because mongo does not allow hash keys with periods, and the pipes are used in the map/reduce method that
132
- # was written to speed up the data write in openstudio-server. Also remove any trailing underscores and spaces
133
- #
134
- # @param [Hash] hash Any hash with potentially problematic characters
135
- # @param [Logger] logger Logger to write to
136
- #
137
- def rename_hash_keys(hash, logger)
138
- # @todo should we log the name changes?
139
- regex = /[|!@#\$%^&\*\(\)\{\}\\\[\];:'",<.>\/?\+=]+/
140
-
141
- rename_keys = lambda do |h|
142
- if Hash === h
143
- h.each_key do |key|
144
- if key.to_s =~ regex
145
- logger.warn "Renaming result key '#{key}' to remove invalid characters"
146
- end
147
- end
148
- Hash[h.map { |k, v| [k.to_s.gsub(regex, '_').squeeze('_').gsub(/[_\s]+$/, '').chomp.to_sym, rename_keys[v]] }]
149
- else
150
- h
151
- end
152
- end
153
-
154
- rename_keys[hash]
155
- end
156
-
157
-
158
- # Save reports to a common directory
159
- #
160
- # @param [String] run_dir
161
- # @param [String] directory
162
- # @param [String] logger
163
- #
164
- def gather_reports(run_dir, directory, workflow_json, logger)
165
- logger.info run_dir
166
- logger.info directory
167
-
168
- FileUtils.mkdir_p "#{directory}/reports"
169
-
170
- # try to find the energyplus result file
171
- eplus_html = "#{run_dir}/eplustbl.htm"
172
- if File.exist? eplus_html
173
- # do some encoding on the html if possible
174
- html = File.read(eplus_html)
175
- html = html.force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
176
- logger.info "Saving EnergyPlus HTML report to #{directory}/reports/eplustbl.html"
177
- File.open("#{directory}/reports/eplustbl.html", 'w') do |f|
178
- f << html
179
- # make sure data is written to the disk one way or the other
180
- begin
181
- f.fsync
182
- rescue
183
- f.flush
184
- end
185
- end
186
- end
187
-
188
- # Also, find any "report*.*" files
189
- Dir["#{run_dir}/*/report*.*"].each do |report|
190
- # HRH: This is a temporary work-around to support PAT 2.1 pretty names AND the CLI while we roll a WorkflowJSON solution
191
- measure_dir_name = File.dirname(report).split(File::SEPARATOR).last.gsub(/[0-9][0-9][0-9]_/, '')
192
- measure_xml_path = File.absolute_path(File.join(File.dirname(report), '../../..', 'measures',
193
- measure_dir_name, 'measure.xml'))
194
- logger.info "measure_xml_path: #{measure_xml_path}"
195
- if File.exists? measure_xml_path
196
- measure_xml = REXML::Document.new File.read(measure_xml_path)
197
- measure_class_name = OpenStudio.toUnderscoreCase(measure_xml.root.elements['class_name'].text)
198
- else
199
- measure_class_name = OpenStudio.toUnderscoreCase(measure_dir_name)
200
- end
201
- file_ext = File.extname(report)
202
- append_str = File.basename(report, '.*')
203
- new_file_name = "#{directory}/reports/#{measure_class_name}_#{append_str}#{file_ext}"
204
- logger.info "Saving report #{report} to #{new_file_name}"
205
- FileUtils.copy report, new_file_name
206
- end
207
-
208
- # Remove empty directories in run folder
209
- Dir["#{run_dir}/*"].select { |d| File.directory? d }.select { |d| (Dir.entries(d) - %w(. ..)).empty? }.each do |d|
210
- logger.info "Removing empty directory #{d}"
211
- Dir.rmdir d
212
- end
213
- end
214
-
215
-
216
- # A general post-processing step which could be made significantly more modular
217
- #
218
- # @param [String] run_dir
219
- #
220
- def cleanup(run_dir, directory, logger)
221
-
222
-
223
- paths_to_rm = []
224
- # paths_to_rm << Pathname.glob("#{run_dir}/*.osm")
225
- # paths_to_rm << Pathname.glob("#{run_dir}/*.idf") # keep the idfs
226
- # paths_to_rm << Pathname.glob("*.audit")
227
- # paths_to_rm << Pathname.glob("*.bnd")
228
- # paths_to_rm << Pathname.glob("#{run_dir}/*.eso")
229
- paths_to_rm << Pathname.glob("#{run_dir}/*.mtr")
230
- paths_to_rm << Pathname.glob("#{run_dir}/*.epw")
231
- #paths_to_rm << Pathname.glob("#{run_dir}/*.mtd")
232
- #paths_to_rm << Pathname.glob("#{run_dir}/*.rdd")
233
- paths_to_rm.each { |p| FileUtils.rm_rf(p) }
234
- end
235
- end
236
- end
237
- end
238
- end
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
3
+ # All rights reserved.
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # (1) Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # (3) Neither the name of the copyright holder nor the names of any contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission from the respective party.
17
+ #
18
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
19
+ # of modifications or other derivative works may not use the "OpenStudio"
20
+ # trademark, "OS", "os", or any other confusingly similar designation without
21
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES
27
+ # GOVERNMENT, OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
33
+ # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # *******************************************************************************
35
+
36
+ module OpenStudio
37
+ module Workflow
38
+ module Util
39
+ require 'openstudio/workflow/util/measure'
40
+ require 'csv'
41
+
42
+ # This module serves as a wrapper around various post-processing tasks used to manage outputs
43
+ # @todo (rhorsey) ummmm. So some of this is pretty ugly. Since @dmacumber had ideas about this maybe he can figure
44
+ # out what to do about it all
45
+ # @todo (nlong) the output adapter restructure will frack up the extraction method royally
46
+ #
47
+ module PostProcess
48
+ # This method loads a sql file into OpenStudio and returns it
49
+ #
50
+ # @param [String] sql_file Absolute path to the sql file to be loaded
51
+ # @return [Object, nil] The OpenStudio::SqlFile object, or nil if it could not be found
52
+ #
53
+ def load_sql_file(sql_file)
54
+ return nil unless File.exist? sql_file
55
+ OpenStudio::SqlFile.new(@sql_filename)
56
+ end
57
+
58
+ # This method parses all sorts of stuff which something needs
59
+ #
60
+ # @param [String] run_dir The directory that the simulation was run in
61
+ # @return [Hash, Hash] results and objective_function (which may be empty) are returned
62
+ # @todo (rhorsey) fix the description
63
+ #
64
+ def run_extract_inputs_and_outputs(run_dir, logger)
65
+ # For xml, the measure attributes are in the measure_attributes_xml.json file
66
+ # TODO: somehow pass the metadata around on which JSONs to suck into the database
67
+ results = {}
68
+ # Inputs are in the measure_attributes.json file
69
+ if File.exist? "#{run_dir}/measure_attributes.json"
70
+ h = JSON.parse(File.read("#{run_dir}/measure_attributes.json"), symbolize_names: true)
71
+ h = rename_hash_keys(h, logger)
72
+ results.merge! h
73
+ end
74
+
75
+ logger.info 'Saving the result hash to file'
76
+ File.open("#{run_dir}/results.json", 'w') do |f|
77
+ f << JSON.pretty_generate(results)
78
+ # make sure data is written to the disk one way or the other
79
+ begin
80
+ f.fsync
81
+ rescue StandardError
82
+ f.flush
83
+ end
84
+ end
85
+
86
+ objective_functions = {}
87
+ if @registry[:analysis]
88
+ logger.info 'Iterating over Analysis JSON Output Variables'
89
+ # Save the objective functions to the object for sending back to the simulation executive
90
+ analysis_json = @registry[:analysis]
91
+ if analysis_json[:analysis] && analysis_json[:analysis][:output_variables]
92
+ analysis_json[:analysis][:output_variables].each do |variable|
93
+ # determine which ones are the objective functions (code smell: todo: use enumerator)
94
+ if variable[:objective_function]
95
+ logger.info "Looking for objective function #{variable[:name]}"
96
+ # TODO: move this to cleaner logic. Use ostruct?
97
+ k, v = variable[:name].split('.')
98
+
99
+ # look for the objective function key and make sure that it is not nil. False is an okay obj function.
100
+ if results.key?(k.to_sym) && !results[k.to_sym][v.to_sym].nil?
101
+ objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = results[k.to_sym][v.to_sym]
102
+ if variable[:objective_function_target]
103
+ logger.info "Found objective function target for #{variable[:name]}"
104
+ objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_target].to_f
105
+ end
106
+ if variable[:scaling_factor]
107
+ logger.info "Found scaling factor for #{variable[:name]}"
108
+ objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = variable[:scaling_factor].to_f
109
+ end
110
+ if variable[:objective_function_group]
111
+ logger.info "Found objective function group for #{variable[:name]}"
112
+ objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_group].to_f
113
+ end
114
+ else
115
+ logger.warn "No results for objective function #{variable[:name]}"
116
+ objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = Float::MAX
117
+ objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = nil
118
+ objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = nil
119
+ objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = nil
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ return results, objective_functions
127
+ end
128
+
129
+ # Remove any invalid characters in the measure attribute keys. Periods and Pipes are the most problematic
130
+ # because mongo does not allow hash keys with periods, and the pipes are used in the map/reduce method that
131
+ # was written to speed up the data write in openstudio-server. Also remove any trailing underscores and spaces
132
+ #
133
+ # @param [Hash] hash Any hash with potentially problematic characters
134
+ # @param [Logger] logger Logger to write to
135
+ #
136
+ def rename_hash_keys(hash, logger)
137
+ # @todo should we log the name changes?
138
+ regex = /[|!@#\$%^&\*\(\)\{\}\\\[\];:'",<.>\/?\+=]+/
139
+
140
+ rename_keys = lambda do |h|
141
+ if Hash === h
142
+ h.each_key do |key|
143
+ if key.to_s =~ regex
144
+ logger.warn "Renaming result key '#{key}' to remove invalid characters"
145
+ end
146
+ end
147
+ Hash[h.map { |k, v| [k.to_s.gsub(regex, '_').squeeze('_').gsub(/[_\s]+$/, '').chomp.to_sym, rename_keys[v]] }]
148
+ else
149
+ h
150
+ end
151
+ end
152
+
153
+ rename_keys[hash]
154
+ end
155
+
156
+ # Save reports to a common directory
157
+ #
158
+ # @param [String] run_dir
159
+ # @param [String] directory
160
+ # @param [String] logger
161
+ #
162
+ def gather_reports(run_dir, directory, workflow_json, logger)
163
+ logger.info run_dir
164
+ logger.info directory
165
+
166
+ FileUtils.mkdir_p "#{directory}/reports"
167
+
168
+ # try to find the energyplus result file
169
+ eplus_html = "#{run_dir}/eplustbl.htm"
170
+ if File.exist? eplus_html
171
+ # do some encoding on the html if possible
172
+ html = File.read(eplus_html)
173
+ html = html.force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
174
+ logger.info "Saving EnergyPlus HTML report to #{directory}/reports/eplustbl.html"
175
+ File.open("#{directory}/reports/eplustbl.html", 'w') do |f|
176
+ f << html
177
+ # make sure data is written to the disk one way or the other
178
+ begin
179
+ f.fsync
180
+ rescue StandardError
181
+ f.flush
182
+ end
183
+ end
184
+ end
185
+
186
+ # Also, find any "report*.*" files
187
+ Dir["#{run_dir}/*/report*.*"].each do |report|
188
+ # HRH: This is a temporary work-around to support PAT 2.1 pretty names AND the CLI while we roll a WorkflowJSON solution
189
+ measure_dir_name = File.dirname(report).split(File::SEPARATOR).last.gsub(/[0-9][0-9][0-9]_/, '')
190
+ measure_xml_path = File.absolute_path(File.join(File.dirname(report), '../../..', 'measures',
191
+ measure_dir_name, 'measure.xml'))
192
+ logger.info "measure_xml_path: #{measure_xml_path}"
193
+ if File.exist? measure_xml_path
194
+ # REXML is slow, so we lazy load only as needed
195
+ require 'rexml/document'
196
+ measure_xml = REXML::Document.new File.read(measure_xml_path)
197
+ measure_class_name = OpenStudio.toUnderscoreCase(measure_xml.root.elements['class_name'].text)
198
+ else
199
+ measure_class_name = OpenStudio.toUnderscoreCase(measure_dir_name)
200
+ end
201
+ file_ext = File.extname(report)
202
+ append_str = File.basename(report, '.*')
203
+ new_file_name = "#{directory}/reports/#{measure_class_name}_#{append_str}#{file_ext}"
204
+ logger.info "Saving report #{report} to #{new_file_name}"
205
+ FileUtils.copy report, new_file_name
206
+ end
207
+
208
+ # Remove empty directories in run folder
209
+ Dir["#{run_dir}/*"].select { |d| File.directory? d }.select { |d| (Dir.entries(d) - ['.', '..']).empty? }.each do |d|
210
+ logger.info "Removing empty directory #{d}"
211
+ Dir.rmdir d
212
+ end
213
+ end
214
+
215
+ # A general post-processing step which could be made significantly more modular
216
+ #
217
+ # @param [String] run_dir
218
+ #
219
+ def cleanup(run_dir, directory, logger)
220
+ paths_to_rm = []
221
+ # paths_to_rm << Pathname.glob("#{run_dir}/*.osm")
222
+ # paths_to_rm << Pathname.glob("#{run_dir}/*.idf") # keep the idfs
223
+ # paths_to_rm << Pathname.glob("*.audit")
224
+ # paths_to_rm << Pathname.glob("*.bnd")
225
+ # paths_to_rm << Pathname.glob("#{run_dir}/*.eso")
226
+ paths_to_rm << Pathname.glob("#{run_dir}/*.mtr")
227
+ paths_to_rm << Pathname.glob("#{run_dir}/*.epw")
228
+ # paths_to_rm << Pathname.glob("#{run_dir}/*.mtd")
229
+ # paths_to_rm << Pathname.glob("#{run_dir}/*.rdd")
230
+ paths_to_rm.each { |p| FileUtils.rm_rf(p) }
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end