openstudio-workflow 1.3.4 → 1.3.5

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 (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