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,68 +1,68 @@
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
- module IO
40
- def is_windows?
41
- win_patterns = [
42
- /bccwin/i,
43
- /cygwin/i,
44
- /djgpp/i,
45
- /mingw/i,
46
- /mswin/i,
47
- /wince/i
48
- ]
49
-
50
- case RUBY_PLATFORM
51
- when *win_patterns
52
- return true
53
- else
54
- return false
55
- end
56
- end
57
-
58
- def popen_command(command)
59
- result = command
60
- if is_windows?
61
- result = command.tr('/', '\\')
62
- end
63
- return result
64
- end
65
- end
66
- end
67
- end
68
- 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
+ module IO
40
+ def is_windows?
41
+ win_patterns = [
42
+ /bccwin/i,
43
+ /cygwin/i,
44
+ /djgpp/i,
45
+ /mingw/i,
46
+ /mswin/i,
47
+ /wince/i
48
+ ]
49
+
50
+ case RUBY_PLATFORM
51
+ when *win_patterns
52
+ return true
53
+ else
54
+ return false
55
+ end
56
+ end
57
+
58
+ def popen_command(command)
59
+ result = command
60
+ if is_windows?
61
+ result = command.tr('/', '\\')
62
+ end
63
+ return result
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,650 +1,658 @@
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
-
40
- # Handles all interaction with measure objects in the gem. This includes measure.xml and measure.rb files
41
- #
42
- module Measure
43
-
44
- # Wrapper method around #apply_measure to allow all measures of a type to be executed
45
- #
46
- # @param [String] measure_type Accepts OpenStudio::MeasureType argument
47
- # @param [Object] registry Hash access to objects
48
- # @param [Hash] options ({}) User-specified options used to override defaults
49
- # @option options [Object] :time_logger A special logger used to debug performance issues
50
- # @option options [Object] :output_adapter An output adapter to register measure transitions to
51
- # @param [Boolean] energyplus_output_requests If true then the energyPlusOutputRequests is called instead of the run method
52
- # @return [Void]
53
- #
54
- def apply_measures(measure_type, registry, options = {}, energyplus_output_requests = false)
55
-
56
- # DLM: time_logger is in the registry but docs say it is in options?
57
- registry[:time_logger].start "#{measure_type.valueName}:apply_measures" if registry[:time_logger]
58
-
59
- logger = registry[:logger]
60
- runner = registry[:runner]
61
- workflow_json = registry[:workflow_json]
62
-
63
- workflow_steps = workflow_json.workflowSteps
64
- fail "The 'steps' array of the OSW is required." unless workflow_steps
65
-
66
- logger.debug "Finding measures of type #{measure_type.valueName}"
67
- workflow_steps.each_index do |step_index|
68
-
69
- step = workflow_steps[step_index]
70
-
71
- if @registry[:openstudio_2]
72
- if !step.to_MeasureStep.empty?
73
- step = step.to_MeasureStep.get
74
- end
75
- end
76
-
77
- measure_dir_name = step.measureDirName
78
-
79
- measure_dir = workflow_json.findMeasure(measure_dir_name)
80
- fail "Cannot find #{measure_dir_name}" if measure_dir.empty?
81
- measure_dir = measure_dir.get
82
-
83
- measure = OpenStudio::BCLMeasure.load(measure_dir)
84
- fail "Cannot load measure at #{measure_dir}" if measure.empty?
85
- measure = measure.get
86
-
87
- class_name = measure.className
88
- measure_instance_type = measure.measureType
89
- if measure_instance_type == measure_type
90
- if energyplus_output_requests
91
- logger.info "Found measure #{class_name} of type #{measure_type.valueName}. Collecting EnergyPlus Output Requests now."
92
- apply_measure(registry, step, options, energyplus_output_requests)
93
- else
94
- logger.info "Found measure #{class_name} of type #{measure_type.valueName}. Applying now."
95
-
96
- # check if simulation has been halted
97
- halted = runner.halted
98
-
99
- # fast forward current step index to this index, skips any previous steps
100
- # DLM: this is needed when running reporting measures only
101
- if !halted
102
- while workflow_json.currentStepIndex < step_index
103
- workflow_json.incrementStep
104
- end
105
- end
106
-
107
- # DLM: why is output_adapter in options instead of registry?
108
- options[:output_adapter].communicate_transition("Applying #{class_name}", :measure) if options[:output_adapter]
109
- apply_measure(registry, step, options, energyplus_output_requests, halted)
110
- options[:output_adapter].communicate_transition("Applied #{class_name}", :measure) if options[:output_adapter]
111
- end
112
-
113
- logger.info 'Moving to the next workflow step.'
114
- else
115
- logger.debug "Passing measure #{class_name} of type #{measure_type.valueName}"
116
- end
117
- end
118
-
119
- registry[:time_logger].stop "#{measure_type.valueName}:apply_measures" if registry[:time_logger]
120
- end
121
-
122
- # Determine if a given workflow can find and load all measures defined in steps
123
- #
124
- # @param [Hash] workflow See the schema for an OSW defined in the spec folder of this repo. Note that this
125
- # method requires the OSW to have been loaded with symbolized keys
126
- # @param [String] directory The directory that will be passed to the find_measure_dir method
127
- # @return [true] If the method doesn't fail the workflow measures were validated
128
- #
129
- def validate_measures(registry, logger)
130
-
131
- logger = registry[:logger] if logger.nil?
132
- workflow_json = registry[:workflow_json]
133
-
134
- state = 'ModelMeasure'.to_MeasureType
135
- steps = workflow_json.workflowSteps
136
- steps.each_with_index do |step, index|
137
- begin
138
- logger.debug "Validating step #{index}"
139
-
140
- if @registry[:openstudio_2]
141
- if !step.to_MeasureStep.empty?
142
- step = step.to_MeasureStep.get
143
- end
144
- end
145
-
146
- # Verify the existence of the required files
147
- measure_dir_name = step.measureDirName
148
-
149
- measure_dir = workflow_json.findMeasure(measure_dir_name)
150
- fail "Cannot find measure #{measure_dir_name}" if measure_dir.empty?
151
- measure_dir = measure_dir.get
152
-
153
- measure = OpenStudio::BCLMeasure.load(measure_dir)
154
- fail "Cannot load measure at #{measure_dir}" if measure.empty?
155
- measure = measure.get
156
-
157
- class_name = measure.className
158
- measure_instance_type = measure.measureType
159
-
160
- # Ensure that measures are in order, i.e. no OS after E+, E+ or OS after Reporting
161
- if measure_instance_type == 'ModelMeasure'.to_MeasureType
162
- fail "OpenStudio measure #{measure_dir} called after transition to EnergyPlus." if state == 'EnergyPlusMeasure'.to_MeasureType
163
- fail "OpenStudio measure #{measure_dir} called after after Energyplus simulation." if state == 'ReportingMeasure'.to_MeasureType
164
- elsif measure_instance_type == "EnergyPlusMeasure".to_MeasureType
165
- state = 'EnergyPlusMeasure'.to_MeasureType if state == 'ModelMeasure'.to_MeasureType
166
- fail "EnergyPlus measure #{measure_dir} called after Energyplus simulation." if state == 'ReportingMeasure'.to_MeasureType
167
- elsif measure_instance_type == 'ReportingMeasure'.to_MeasureType
168
- state = 'ReportingMeasure'.to_MeasureType if state != 'ReportingMeasure'.to_MeasureType
169
- else
170
- fail "Error: MeasureType #{measure_instance_type.valueName} of measure #{measure_dir} is not supported"
171
- end
172
-
173
- logger.debug "Validated step #{index}"
174
- end
175
- end
176
- end
177
-
178
- # Sets the argument map for argument_map argument pair
179
- #
180
- # @param [Object] argument_map See the OpenStudio SDK for a description of the OSArgumentMap structure
181
- # @param [Object] argument_name, user defined argument name
182
- # @param [Object] argument_value, user defined argument value
183
- # @param [Object] logger, logger object
184
- # @return [Object] Returns an updated ArgumentMap object
185
- #
186
- def apply_arguments(argument_map, argument_name, argument_value, logger)
187
- unless argument_value.nil?
188
- logger.info "Setting argument value '#{argument_name}' to '#{argument_value}'"
189
-
190
- v = argument_map[argument_name.to_s]
191
- fail "Could not find argument '#{argument_name}' in argument_map" unless v
192
- value_set = v.setValue(argument_value)
193
- fail "Could not set argument '#{argument_name}' to value '#{argument_value}'" unless value_set
194
- argument_map[argument_name.to_s] = v.clone
195
- else
196
- logger.warn "Value for argument '#{argument_name}' not set in argument list therefore will use default"
197
- end
198
- end
199
-
200
- def apply_arguments_2(argument_map, argument_name, argument_value, logger)
201
- unless argument_value.nil?
202
- logger.info "Setting argument value '#{argument_name}' to '#{argument_value}'"
203
-
204
- v = argument_map[argument_name.to_s]
205
- fail "Could not find argument '#{argument_name}' in argument_map" unless v
206
- value_set = false
207
- variant_type = argument_value.variantType
208
- if variant_type == "String".to_VariantType
209
- argument_value = argument_value.valueAsString
210
- value_set = v.setValue(argument_value)
211
- elsif variant_type == "Double".to_VariantType
212
- argument_value = argument_value.valueAsDouble
213
- value_set = v.setValue(argument_value)
214
- elsif variant_type == "Integer".to_VariantType
215
- argument_value = argument_value.valueAsInteger
216
- value_set = v.setValue(argument_value)
217
- elsif variant_type == "Boolean".to_VariantType
218
- argument_value = argument_value.valueAsBoolean
219
- value_set = v.setValue(argument_value)
220
- end
221
- fail "Could not set argument '#{argument_name}' to value '#{argument_value}'" unless value_set
222
- argument_map[argument_name.to_s] = v.clone
223
- else
224
- logger.warn "Value for argument '#{argument_name}' not set in argument list therefore will use default"
225
- end
226
- end
227
-
228
- # Method to add measure info to WorkflowStepResult
229
- #
230
- # @param [Object] result Current WorkflowStepResult
231
- # @param [Object] measure Current BCLMeasure
232
- def add_result_measure_info(result, measure)
233
- begin
234
- result.setMeasureType(measure.measureType)
235
- result.setMeasureName(measure.name)
236
- result.setMeasureId(measure.uid)
237
- result.setMeasureVersionId(measure.versionId)
238
- version_modified = measure.versionModified
239
- if !version_modified.empty?
240
- result.setMeasureVersionModified(version_modified.get)
241
- end
242
- result.setMeasureXmlChecksum(measure.xmlChecksum)
243
- result.setMeasureClassName(measure.className)
244
- result.setMeasureDisplayName(measure.displayName)
245
- result.setMeasureTaxonomy(measure.taxonomyTag)
246
- rescue NameError
247
- end
248
- end
249
-
250
- # Method to allow for a single measure of any type to be run
251
- #
252
- # @param [String] directory Location of the datapoint directory to run. This is needed
253
- # independent of the adapter that is being used. Note that the simulation will actually run in 'run'
254
- # @param [Object] adapter An instance of the adapter class
255
- # @param [String] current_weather_filepath The path which will be used to set the runner and returned to update
256
- # the OSW for future measures and the simulation
257
- # @param [Object] model The model object being used in the measure, either a OSM or IDF
258
- # @param [Hash] step Definition of the to be run by the workflow
259
- # @option step [String] :measure_dir_name The name of the directory which contains the measure files
260
- # @option step [Object] :arguments name value hash which defines the arguments to the measure, e.g.
261
- # {has_bool: true, cost: 3.1}
262
- # @param output_attributes [Hash] The results of previous measure applications which are persisted through the
263
- # runner to allow measures to react to previous events in the workflow
264
- # @param [Hash] options ({}) User-specified options used to override defaults
265
- # @option options [Array] :measure_search_array Ordered set of measure directories used to search for
266
- # step[:measure_dir_name], e.g. ['measures', '../../measures']
267
- # @option options [Object] :time_logger Special logger used to debug performance issues
268
- # @param [Boolean] energyplus_output_requests If true then the energyPlusOutputRequests is called instead of the run method
269
- # @param [Boolean] halted True if the workflow has been halted and all measures should be skipped
270
- # @return [Hash, String] Returns two objects. The first is the (potentially) updated output_attributes hash, and
271
- # the second is the (potentially) updated current_weather_filepath
272
- #
273
- def apply_measure(registry, step, options = {}, energyplus_output_requests = false, halted = false)
274
-
275
- logger = registry[:logger]
276
- runner = registry[:runner]
277
- workflow_json = registry[:workflow_json]
278
- measure_dir_name = step.measureDirName
279
-
280
- run_dir = registry[:run_dir]
281
- fail 'No run directory set in the registry' unless run_dir
282
-
283
- output_attributes = registry[:output_attributes]
284
-
285
- # todo: get weather file from appropriate location
286
- @wf = registry[:wf]
287
- @model = registry[:model]
288
- @model_idf = registry[:model_idf]
289
- @sql_filename = registry[:sql]
290
-
291
- runner.setLastOpenStudioModel(@model) if @model
292
- #runner.setLastOpenStudioModelPath(const openstudio::path& lastOpenStudioModelPath); #DLM - deprecate?
293
- runner.setLastEnergyPlusWorkspace(@model_idf) if @model_idf
294
- #runner.setLastEnergyPlusWorkspacePath(const openstudio::path& lastEnergyPlusWorkspacePath); #DLM - deprecate?
295
- runner.setLastEnergyPlusSqlFilePath(@sql_filename) if @sql_filename
296
- runner.setLastEpwFilePath(@wf) if @wf
297
-
298
- logger.debug "Starting #{__method__} for #{measure_dir_name}"
299
- registry[:time_logger].start("Measure:#{measure_dir_name}") if registry[:time_logger]
300
- current_dir = Dir.pwd
301
-
302
- success = nil
303
- begin
304
-
305
- measure_dir = workflow_json.findMeasure(measure_dir_name)
306
- fail "Cannot find #{measure_dir_name}" if measure_dir.empty?
307
- measure_dir = measure_dir.get
308
-
309
- measure = OpenStudio::BCLMeasure.load(measure_dir)
310
- fail "Cannot load measure at #{measure_dir}" if measure.empty?
311
- measure = measure.get
312
-
313
- step_index = workflow_json.currentStepIndex
314
-
315
- measure_run_dir = File.join(run_dir, "#{step_index.to_s.rjust(3,'0')}_#{measure_dir_name}")
316
- logger.debug "Creating run directory for measure in #{measure_run_dir}"
317
- FileUtils.mkdir_p measure_run_dir
318
- Dir.chdir measure_run_dir
319
-
320
- if energyplus_output_requests
321
- logger.debug "energyPlusOutputRequests running in #{Dir.pwd}"
322
- else
323
- logger.debug "Apply measure running in #{Dir.pwd}"
324
- end
325
-
326
- class_name = measure.className
327
- measure_type = measure.measureType
328
-
329
- measure_path = measure.primaryRubyScriptPath
330
- fail "Measure does not have a primary ruby script specified" if measure_path.empty?
331
- measure_path = measure_path.get
332
- fail "#{measure_path} file does not exist" unless File.exist?(measure_path.to_s)
333
-
334
- logger.debug "Loading Measure from #{measure_path}"
335
-
336
- measure_object = nil
337
- result = nil
338
- begin
339
- load measure_path.to_s
340
- measure_object = Object.const_get(class_name).new
341
- rescue => e
342
-
343
- # add the error to the osw.out
344
- runner.registerError("#{e.message}\n\t#{e.backtrace.join("\n\t")}")
345
-
346
- # @todo (rhorsey) Clean up the error class here.
347
- log_message = "Error requiring measure #{__FILE__}. Failed with #{e.message}, #{e.backtrace.join("\n")}"
348
- raise log_message
349
- end
350
-
351
- arguments = nil
352
- skip_measure = false
353
- begin
354
-
355
- # Initialize arguments which may be model dependent, don't allow arguments method access to real model in case it changes something
356
- if measure_type == 'ModelMeasure'.to_MeasureType
357
- arguments = measure_object.arguments(@model.clone(true).to_Model)
358
- elsif measure_type == 'EnergyPlusMeasure'.to_MeasureType
359
- arguments = measure_object.arguments(@model_idf.clone(true))
360
- else measure_type == 'ReportingMeasure'.to_MeasureType
361
- arguments = measure_object.arguments
362
- end
363
-
364
- # Create argument map and initialize all the arguments
365
- argument_map = OpenStudio::Ruleset::OSArgumentMap.new
366
- if arguments
367
- arguments.each do |v|
368
- argument_map[v.name] = v.clone
369
- end
370
- end
371
-
372
- # Set argument values if they exist
373
- logger.debug "Iterating over arguments for workflow item '#{measure_dir_name}'"
374
- if step.arguments
375
-
376
- # handle skip first
377
- argument_value = step.arguments['__SKIP__']
378
- if !argument_value.nil?
379
-
380
- if registry[:openstudio_2]
381
- variant_type = argument_value.variantType
382
- if variant_type == "String".to_VariantType
383
- argument_value = argument_value.valueAsString
384
- elsif variant_type == "Double".to_VariantType
385
- argument_value = argument_value.valueAsDouble
386
- elsif variant_type == "Integer".to_VariantType
387
- argument_value = argument_value.valueAsInteger
388
- elsif variant_type == "Boolean".to_VariantType
389
- argument_value = argument_value.valueAsBoolean
390
- end
391
- end
392
-
393
- if argument_value.class == String
394
- argument_value = argument_value.downcase
395
- if argument_value == "false"
396
- skip_measure = false
397
- else
398
- skip_measure = true
399
- end
400
- elsif argument_value.class == Fixnum
401
- skip_measure = (argument_value != 0)
402
- elsif argument_value.class == Float
403
- skip_measure = (argument_value != 0.0)
404
- elsif argument_value.class == FalseClass
405
- skip_measure = false
406
- elsif argument_value.class == TrueClass
407
- skip_measure = true
408
- elsif argument_value.class == NilClass
409
- skip_measure = false
410
- end
411
- end
412
-
413
- # process other arguments
414
- step.arguments.each do |argument_name, argument_value|
415
-
416
- # don't validate choices if measure is being skipped
417
- next if skip_measure
418
-
419
- # already handled skip
420
- next if argument_name.to_s == '__SKIP__'
421
-
422
- # regular argument
423
- if registry[:openstudio_2]
424
- success = apply_arguments_2(argument_map, argument_name, argument_value, logger)
425
- else
426
- success = apply_arguments(argument_map, argument_name, argument_value, logger)
427
- end
428
- fail 'Could not set arguments' unless success
429
- end
430
-
431
- end
432
-
433
- # map any choice display names to choice values, in either set values or defaults
434
- argument_map.each_key do |argument_name|
435
-
436
- # don't validate choices if measure is being skipped
437
- next if skip_measure
438
-
439
- v = argument_map[argument_name]
440
- choice_values = v.choiceValues
441
- if !choice_values.empty?
442
- value = nil
443
- value = v.defaultValueAsString if v.hasDefaultValue
444
- value = v.valueAsString if v.hasValue
445
- if value && choice_values.index(value).nil?
446
- display_names = v.choiceValueDisplayNames
447
- i = display_names.index(value)
448
- if i && choice_values[i]
449
- logger.debug "Mapping display name '#{value}' to value '#{choice_values[i]}' for argument '#{argument_name}'"
450
- value_set = v.setValue(choice_values[i])
451
- fail "Could not set argument '#{argument_name}' to mapped value '#{choice_values[i]}'" unless value_set
452
- argument_map[argument_name.to_s] = v.clone
453
- end
454
- end
455
- end
456
- end
457
-
458
- rescue => e
459
-
460
- # add the error to the osw.out
461
- runner.registerError("#{e.message}\n\t#{e.backtrace.join("\n\t")}")
462
-
463
- log_message = "Error assigning argument in measure #{__FILE__}. Failed with #{e.message}, #{e.backtrace.join("\n")}"
464
- raise log_message
465
- end
466
-
467
- if skip_measure || halted
468
- if !energyplus_output_requests
469
- if halted
470
- # if halted then this measure will not get run, there are no results, not even "Skip"
471
- logger.info "Skipping measure '#{measure_dir_name}' because simulation halted"
472
-
473
- else
474
- logger.info "Skipping measure '#{measure_dir_name}'"
475
-
476
- # required to update current step, will do nothing if halted
477
- runner.prepareForUserScriptRun(measure_object)
478
-
479
- # don't want to log errors about arguments passed to skipped measures
480
- #runner.validateUserArguments(arguments, argument_map
481
-
482
- current_result = runner.result
483
- runner.incrementStep
484
- add_result_measure_info(current_result, measure)
485
- current_result.setStepResult('Skip'.to_StepResult)
486
- end
487
- end
488
- else
489
-
490
- begin
491
- if energyplus_output_requests
492
- logger.debug "Calling measure.energyPlusOutputRequests for '#{measure_dir_name}'"
493
- idf_objects = measure_object.energyPlusOutputRequests(runner, argument_map)
494
- num_added = 0
495
- idf_objects.each do |idf_object|
496
- num_added += OpenStudio::Workflow::Util::EnergyPlus.add_energyplus_output_request(@model_idf, idf_object)
497
- end
498
- logger.debug "Finished measure.energyPlusOutputRequests for '#{measure_dir_name}', #{num_added} output requests added"
499
- else
500
- logger.debug "Calling measure.run for '#{measure_dir_name}'"
501
- if measure_type == 'ModelMeasure'.to_MeasureType
502
- measure_object.run(@model, runner, argument_map)
503
- elsif measure_type == 'EnergyPlusMeasure'.to_MeasureType
504
- measure_object.run(@model_idf, runner, argument_map)
505
- elsif measure_type == 'ReportingMeasure'.to_MeasureType
506
- measure_object.run(runner, argument_map)
507
- end
508
- logger.debug "Finished measure.run for '#{measure_dir_name}'"
509
- end
510
-
511
- # Run garbage collector after every measure to help address race conditions
512
- GC.start
513
- rescue => e
514
-
515
- # add the error to the osw.out
516
- runner.registerError("#{e.message}\n\t#{e.backtrace.join("\n\t")}")
517
-
518
- result = runner.result
519
-
520
- if !energyplus_output_requests
521
- # incrementStep must be called after run
522
- runner.incrementStep
523
-
524
- add_result_measure_info(result, measure)
525
- end
526
-
527
- options[:output_adapter].communicate_measure_result(result) if options[:output_adapter]
528
-
529
- log_message = "Runner error #{__FILE__} failed with #{e.message}, #{e.backtrace.join("\n")}"
530
- raise log_message
531
- end
532
-
533
- # if doing output requests we are done now
534
- if energyplus_output_requests
535
- registry.register(:model_idf) { @model_idf }
536
- return
537
- end
538
-
539
- result = nil
540
- begin
541
- result = runner.result
542
-
543
- # incrementStep must be called after run
544
- runner.incrementStep
545
-
546
- add_result_measure_info(result, measure)
547
-
548
- options[:output_adapter].communicate_measure_result(result) if options[:output_adapter]
549
-
550
- errors = result.stepErrors
551
-
552
- fail "Measure #{measure_dir_name} reported an error with #{errors}" if errors.size != 0
553
- logger.debug "Running of measure '#{measure_dir_name}' completed. Post-processing measure output"
554
-
555
- # TODO: fix this
556
- #unless @wf == runner.weatherfile_path
557
- # logger.debug "Updating the weather file to be '#{runner.weatherfile_path}'"
558
- # registry.register(:wf) { runner.weatherfile_path }
559
- #end
560
-
561
- # @todo add note about why reassignment and not eval
562
- registry.register(:model) { @model }
563
- registry.register(:model_idf) { @model_idf }
564
- registry.register(:sql) { @sql_filename }
565
-
566
- if measure_type == 'ModelMeasure'.to_MeasureType
567
- # check if weather file has changed
568
- weather_file = @model.getOptionalWeatherFile
569
- if !weather_file.empty?
570
- weather_file_path = weather_file.get.path
571
- if weather_file_path.empty?
572
- logger.debug "Weather file object found in model but no path is given"
573
- else
574
- weather_file_path2 = workflow_json.findFile(weather_file_path.get)
575
- if weather_file_path2.empty?
576
- logger.warn "Could not find weather file '#{weather_file_path}' referenced in model"
577
- else
578
- if weather_file_path2.get.to_s != @wf
579
- logger.debug "Updating weather file path to '#{weather_file_path2.get.to_s}'"
580
- @wf = weather_file_path2.get.to_s
581
- registry.register(:wf) { @wf }
582
- end
583
- end
584
- end
585
- end
586
- end
587
-
588
- rescue => e
589
- log_message = "Runner error #{__FILE__} failed with #{e.message}, #{e.backtrace.join("\n")}"
590
- raise log_message
591
- end
592
-
593
- # DLM: this section creates the measure_attributes.json file which should be deprecated
594
- begin
595
- measure_name = step.name.is_initialized ? step.name.get : class_name
596
-
597
- output_attributes[measure_name.to_sym] = {} if output_attributes[measure_name.to_sym].nil?
598
-
599
- result.stepValues.each do |step_value|
600
- step_value_name = step_value.name
601
- step_value_type = step_value.variantType
602
-
603
- value = nil
604
- if (step_value_type == "String".to_VariantType)
605
- value = step_value.valueAsString
606
- elsif (step_value_type == "Double".to_VariantType)
607
- value = step_value.valueAsDouble
608
- elsif (step_value_type == "Integer".to_VariantType)
609
- value = step_value.valueAsInteger
610
- elsif (step_value_type == "Boolean".to_VariantType)
611
- value = step_value.valueAsBoolean
612
- end
613
-
614
- output_attributes[measure_name.to_sym][step_value_name] = value
615
- end
616
-
617
- # Add an applicability flag to all the measure results
618
- step_result = result.stepResult
619
- fail "Step Result not set" if step_result.empty?
620
- step_result = step_result.get
621
-
622
- if (step_result == "Skip".to_StepResult) || (step_result == "NA".to_StepResult)
623
- output_attributes[measure_name.to_sym][:applicable] = false
624
- else
625
- output_attributes[measure_name.to_sym][:applicable] = true
626
- end
627
- registry.register(:output_attributes) { output_attributes }
628
- rescue => e
629
- log_message = "#{__FILE__} failed with #{e.message}, #{e.backtrace.join("\n")}"
630
- logger.error log_message
631
- raise log_message
632
- end
633
-
634
- end
635
-
636
- rescue ScriptError, StandardError, NoMemoryError => e
637
- log_message = "#{__FILE__} failed with message #{e.message} in #{e.backtrace.join("\n")}"
638
- logger.error log_message
639
- raise log_message
640
- ensure
641
- Dir.chdir current_dir
642
- registry[:time_logger].stop("Measure:#{measure_dir_name}") if registry[:time_logger]
643
-
644
- logger.info "Finished #{__method__} for #{measure_dir_name} in #{@registry[:time_logger].delta("Measure:#{measure_dir_name}")} s" if registry[:time_logger]
645
- end
646
- end
647
- end
648
- end
649
- end
650
- 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
+
40
+ # Handles all interaction with measure objects in the gem. This includes measure.xml and measure.rb files
41
+ #
42
+ module Measure
43
+
44
+ # Wrapper method around #apply_measure to allow all measures of a type to be executed
45
+ #
46
+ # @param [String] measure_type Accepts OpenStudio::MeasureType argument
47
+ # @param [Object] registry Hash access to objects
48
+ # @param [Hash] options ({}) User-specified options used to override defaults
49
+ # @option options [Object] :time_logger A special logger used to debug performance issues
50
+ # @option options [Object] :output_adapter An output adapter to register measure transitions to
51
+ # @param [Boolean] energyplus_output_requests If true then the energyPlusOutputRequests is called instead of the run method
52
+ # @return [Void]
53
+ #
54
+ def apply_measures(measure_type, registry, options = {}, energyplus_output_requests = false)
55
+
56
+ # DLM: time_logger is in the registry but docs say it is in options?
57
+ registry[:time_logger].start "#{measure_type.valueName}:apply_measures" if registry[:time_logger]
58
+
59
+ logger = registry[:logger]
60
+ runner = registry[:runner]
61
+ workflow_json = registry[:workflow_json]
62
+
63
+ workflow_steps = workflow_json.workflowSteps
64
+ fail "The 'steps' array of the OSW is required." unless workflow_steps
65
+
66
+ logger.debug "Finding measures of type #{measure_type.valueName}"
67
+ workflow_steps.each_index do |step_index|
68
+
69
+ step = workflow_steps[step_index]
70
+
71
+ if @registry[:openstudio_2]
72
+ if !step.to_MeasureStep.empty?
73
+ step = step.to_MeasureStep.get
74
+ end
75
+ end
76
+
77
+ measure_dir_name = step.measureDirName
78
+
79
+ measure_dir = workflow_json.findMeasure(measure_dir_name)
80
+ fail "Cannot find #{measure_dir_name}" if measure_dir.empty?
81
+ measure_dir = measure_dir.get
82
+
83
+ measure = OpenStudio::BCLMeasure.load(measure_dir)
84
+ fail "Cannot load measure at #{measure_dir}" if measure.empty?
85
+ measure = measure.get
86
+
87
+ class_name = measure.className
88
+ measure_instance_type = measure.measureType
89
+ if measure_instance_type == measure_type
90
+ if energyplus_output_requests
91
+ logger.info "Found measure #{class_name} of type #{measure_type.valueName}. Collecting EnergyPlus Output Requests now."
92
+ apply_measure(registry, step, options, energyplus_output_requests)
93
+ else
94
+ logger.info "Found measure #{class_name} of type #{measure_type.valueName}. Applying now."
95
+
96
+ # check if simulation has been halted
97
+ halted = runner.halted
98
+
99
+ # fast forward current step index to this index, skips any previous steps
100
+ # DLM: this is needed when running reporting measures only
101
+ if !halted
102
+ while workflow_json.currentStepIndex < step_index
103
+ workflow_json.incrementStep
104
+ end
105
+ end
106
+
107
+ # DLM: why is output_adapter in options instead of registry?
108
+ options[:output_adapter].communicate_transition("Applying #{class_name}", :measure) if options[:output_adapter]
109
+ apply_measure(registry, step, options, energyplus_output_requests, halted)
110
+ options[:output_adapter].communicate_transition("Applied #{class_name}", :measure) if options[:output_adapter]
111
+ end
112
+
113
+ logger.info 'Moving to the next workflow step.'
114
+ else
115
+ logger.debug "Passing measure #{class_name} of type #{measure_type.valueName}"
116
+ end
117
+ end
118
+
119
+ registry[:time_logger].stop "#{measure_type.valueName}:apply_measures" if registry[:time_logger]
120
+ end
121
+
122
+ # Determine if a given workflow can find and load all measures defined in steps
123
+ #
124
+ # @param [Hash] workflow See the schema for an OSW defined in the spec folder of this repo. Note that this
125
+ # method requires the OSW to have been loaded with symbolized keys
126
+ # @param [String] directory The directory that will be passed to the find_measure_dir method
127
+ # @return [true] If the method doesn't fail the workflow measures were validated
128
+ #
129
+ def validate_measures(registry, logger)
130
+
131
+ logger = registry[:logger] if logger.nil?
132
+ workflow_json = registry[:workflow_json]
133
+
134
+ state = 'ModelMeasure'.to_MeasureType
135
+ steps = workflow_json.workflowSteps
136
+ steps.each_with_index do |step, index|
137
+ begin
138
+ logger.debug "Validating step #{index}"
139
+
140
+ if @registry[:openstudio_2]
141
+ if !step.to_MeasureStep.empty?
142
+ step = step.to_MeasureStep.get
143
+ end
144
+ end
145
+
146
+ # Verify the existence of the required files
147
+ measure_dir_name = step.measureDirName
148
+
149
+ measure_dir = workflow_json.findMeasure(measure_dir_name)
150
+ fail "Cannot find measure #{measure_dir_name}" if measure_dir.empty?
151
+ measure_dir = measure_dir.get
152
+
153
+ measure = OpenStudio::BCLMeasure.load(measure_dir)
154
+ fail "Cannot load measure at #{measure_dir}" if measure.empty?
155
+ measure = measure.get
156
+
157
+ class_name = measure.className
158
+ measure_instance_type = measure.measureType
159
+
160
+ # Ensure that measures are in order, i.e. no OS after E+, E+ or OS after Reporting
161
+ if measure_instance_type == 'ModelMeasure'.to_MeasureType
162
+ fail "OpenStudio measure #{measure_dir} called after transition to EnergyPlus." if state == 'EnergyPlusMeasure'.to_MeasureType
163
+ fail "OpenStudio measure #{measure_dir} called after after Energyplus simulation." if state == 'ReportingMeasure'.to_MeasureType
164
+ elsif measure_instance_type == "EnergyPlusMeasure".to_MeasureType
165
+ state = 'EnergyPlusMeasure'.to_MeasureType if state == 'ModelMeasure'.to_MeasureType
166
+ fail "EnergyPlus measure #{measure_dir} called after Energyplus simulation." if state == 'ReportingMeasure'.to_MeasureType
167
+ elsif measure_instance_type == 'ReportingMeasure'.to_MeasureType
168
+ state = 'ReportingMeasure'.to_MeasureType if state != 'ReportingMeasure'.to_MeasureType
169
+ else
170
+ fail "Error: MeasureType #{measure_instance_type.valueName} of measure #{measure_dir} is not supported"
171
+ end
172
+
173
+ logger.debug "Validated step #{index}"
174
+ end
175
+ end
176
+ end
177
+
178
+ # Sets the argument map for argument_map argument pair
179
+ #
180
+ # @param [Object] argument_map See the OpenStudio SDK for a description of the OSArgumentMap structure
181
+ # @param [Object] argument_name, user defined argument name
182
+ # @param [Object] argument_value, user defined argument value
183
+ # @param [Object] logger, logger object
184
+ # @return [Object] Returns an updated ArgumentMap object
185
+ #
186
+ def apply_arguments(argument_map, argument_name, argument_value, logger)
187
+ unless argument_value.nil?
188
+ logger.info "Setting argument value '#{argument_name}' to '#{argument_value}'"
189
+
190
+ v = argument_map[argument_name.to_s]
191
+ fail "Could not find argument '#{argument_name}' in argument_map" unless v
192
+ value_set = v.setValue(argument_value)
193
+ fail "Could not set argument '#{argument_name}' to value '#{argument_value}'" unless value_set
194
+ argument_map[argument_name.to_s] = v.clone
195
+ else
196
+ logger.warn "Value for argument '#{argument_name}' not set in argument list therefore will use default"
197
+ end
198
+ end
199
+
200
+ def apply_arguments_2(argument_map, argument_name, argument_value, logger)
201
+ unless argument_value.nil?
202
+ logger.info "Setting argument value '#{argument_name}' to '#{argument_value}'"
203
+
204
+ v = argument_map[argument_name.to_s]
205
+ fail "Could not find argument '#{argument_name}' in argument_map" unless v
206
+ value_set = false
207
+ variant_type = argument_value.variantType
208
+ if variant_type == "String".to_VariantType
209
+ argument_value = argument_value.valueAsString
210
+ value_set = v.setValue(argument_value)
211
+ elsif variant_type == "Double".to_VariantType
212
+ argument_value = argument_value.valueAsDouble
213
+ value_set = v.setValue(argument_value)
214
+ elsif variant_type == "Integer".to_VariantType
215
+ argument_value = argument_value.valueAsInteger
216
+ value_set = v.setValue(argument_value)
217
+ elsif variant_type == "Boolean".to_VariantType
218
+ argument_value = argument_value.valueAsBoolean
219
+ value_set = v.setValue(argument_value)
220
+ end
221
+ fail "Could not set argument '#{argument_name}' to value '#{argument_value}'" unless value_set
222
+ argument_map[argument_name.to_s] = v.clone
223
+ else
224
+ logger.warn "Value for argument '#{argument_name}' not set in argument list therefore will use default"
225
+ end
226
+ end
227
+
228
+ # Method to add measure info to WorkflowStepResult
229
+ #
230
+ # @param [Object] result Current WorkflowStepResult
231
+ # @param [Object] measure Current BCLMeasure
232
+ def add_result_measure_info(result, measure)
233
+ begin
234
+ result.setMeasureType(measure.measureType)
235
+ result.setMeasureName(measure.name)
236
+ result.setMeasureId(measure.uid)
237
+ result.setMeasureVersionId(measure.versionId)
238
+ version_modified = measure.versionModified
239
+ if !version_modified.empty?
240
+ result.setMeasureVersionModified(version_modified.get)
241
+ end
242
+ result.setMeasureXmlChecksum(measure.xmlChecksum)
243
+ result.setMeasureClassName(measure.className)
244
+ result.setMeasureDisplayName(measure.displayName)
245
+ result.setMeasureTaxonomy(measure.taxonomyTag)
246
+ rescue NameError
247
+ end
248
+ end
249
+
250
+ # Method to allow for a single measure of any type to be run
251
+ #
252
+ # @param [String] directory Location of the datapoint directory to run. This is needed
253
+ # independent of the adapter that is being used. Note that the simulation will actually run in 'run'
254
+ # @param [Object] adapter An instance of the adapter class
255
+ # @param [String] current_weather_filepath The path which will be used to set the runner and returned to update
256
+ # the OSW for future measures and the simulation
257
+ # @param [Object] model The model object being used in the measure, either a OSM or IDF
258
+ # @param [Hash] step Definition of the to be run by the workflow
259
+ # @option step [String] :measure_dir_name The name of the directory which contains the measure files
260
+ # @option step [Object] :arguments name value hash which defines the arguments to the measure, e.g.
261
+ # {has_bool: true, cost: 3.1}
262
+ # @param output_attributes [Hash] The results of previous measure applications which are persisted through the
263
+ # runner to allow measures to react to previous events in the workflow
264
+ # @param [Hash] options ({}) User-specified options used to override defaults
265
+ # @option options [Array] :measure_search_array Ordered set of measure directories used to search for
266
+ # step[:measure_dir_name], e.g. ['measures', '../../measures']
267
+ # @option options [Object] :time_logger Special logger used to debug performance issues
268
+ # @param [Boolean] energyplus_output_requests If true then the energyPlusOutputRequests is called instead of the run method
269
+ # @param [Boolean] halted True if the workflow has been halted and all measures should be skipped
270
+ # @return [Hash, String] Returns two objects. The first is the (potentially) updated output_attributes hash, and
271
+ # the second is the (potentially) updated current_weather_filepath
272
+ #
273
+ def apply_measure(registry, step, options = {}, energyplus_output_requests = false, halted = false)
274
+
275
+ logger = registry[:logger]
276
+ runner = registry[:runner]
277
+ workflow_json = registry[:workflow_json]
278
+ measure_dir_name = step.measureDirName
279
+
280
+ run_dir = registry[:run_dir]
281
+ fail 'No run directory set in the registry' unless run_dir
282
+
283
+ output_attributes = registry[:output_attributes]
284
+
285
+ # todo: get weather file from appropriate location
286
+ @wf = registry[:wf]
287
+ @model = registry[:model]
288
+ @model_idf = registry[:model_idf]
289
+ @sql_filename = registry[:sql]
290
+
291
+ runner.setLastOpenStudioModel(@model) if @model
292
+ #runner.setLastOpenStudioModelPath(const openstudio::path& lastOpenStudioModelPath); #DLM - deprecate?
293
+ runner.setLastEnergyPlusWorkspace(@model_idf) if @model_idf
294
+ #runner.setLastEnergyPlusWorkspacePath(const openstudio::path& lastEnergyPlusWorkspacePath); #DLM - deprecate?
295
+ runner.setLastEnergyPlusSqlFilePath(@sql_filename) if @sql_filename
296
+ runner.setLastEpwFilePath(@wf) if @wf
297
+
298
+ logger.debug "Starting #{__method__} for #{measure_dir_name}"
299
+ registry[:time_logger].start("Measure:#{measure_dir_name}") if registry[:time_logger]
300
+ current_dir = Dir.pwd
301
+
302
+ success = nil
303
+ begin
304
+
305
+ measure_dir = workflow_json.findMeasure(measure_dir_name)
306
+ fail "Cannot find #{measure_dir_name}" if measure_dir.empty?
307
+ measure_dir = measure_dir.get
308
+
309
+ measure = OpenStudio::BCLMeasure.load(measure_dir)
310
+ fail "Cannot load measure at #{measure_dir}" if measure.empty?
311
+ measure = measure.get
312
+
313
+ step_index = workflow_json.currentStepIndex
314
+
315
+ measure_run_dir = File.join(run_dir, "#{step_index.to_s.rjust(3,'0')}_#{measure_dir_name}")
316
+ logger.debug "Creating run directory for measure in #{measure_run_dir}"
317
+ FileUtils.mkdir_p measure_run_dir
318
+ Dir.chdir measure_run_dir
319
+
320
+ if energyplus_output_requests
321
+ logger.debug "energyPlusOutputRequests running in #{Dir.pwd}"
322
+ else
323
+ logger.debug "Apply measure running in #{Dir.pwd}"
324
+ end
325
+
326
+ class_name = measure.className
327
+ measure_type = measure.measureType
328
+
329
+ measure_path = measure.primaryRubyScriptPath
330
+ fail "Measure does not have a primary ruby script specified" if measure_path.empty?
331
+ measure_path = measure_path.get
332
+ fail "#{measure_path} file does not exist" unless File.exist?(measure_path.to_s)
333
+
334
+ logger.debug "Loading Measure from #{measure_path}"
335
+
336
+ measure_object = nil
337
+ result = nil
338
+ begin
339
+ load measure_path.to_s
340
+ measure_object = Object.const_get(class_name).new
341
+ rescue => e
342
+
343
+ # add the error to the osw.out
344
+ runner.registerError("#{e.message}\n\t#{e.backtrace.join("\n\t")}")
345
+
346
+ # @todo (rhorsey) Clean up the error class here.
347
+ log_message = "Error requiring measure #{__FILE__}. Failed with #{e.message}, #{e.backtrace.join("\n")}"
348
+ raise log_message
349
+ end
350
+
351
+ arguments = nil
352
+ skip_measure = false
353
+ begin
354
+
355
+ # Initialize arguments which may be model dependent, don't allow arguments method access to real model in case it changes something
356
+ if measure_type == 'ModelMeasure'.to_MeasureType
357
+ arguments = measure_object.arguments(@model.clone(true).to_Model)
358
+ elsif measure_type == 'EnergyPlusMeasure'.to_MeasureType
359
+ arguments = measure_object.arguments(@model_idf.clone(true))
360
+ else measure_type == 'ReportingMeasure'.to_MeasureType
361
+ # arity gives the number of expected arguments
362
+ # We handle the case where n_args == 0 for backward compatibility
363
+ n_args = measure_object.method(:arguments).arity
364
+ if (n_args == 0)
365
+ logger.warn "Reporting Measure at #{measure_path} is using the old format where the 'arguments' method does not take model. Please consider updating this to `def arguments(model)`."
366
+ arguments = measure_object.arguments
367
+ else
368
+ arguments = measure_object.arguments(@model.clone(true).to_Model)
369
+ end
370
+ end
371
+
372
+ # Create argument map and initialize all the arguments
373
+ argument_map = OpenStudio::Ruleset::OSArgumentMap.new
374
+ if arguments
375
+ arguments.each do |v|
376
+ argument_map[v.name] = v.clone
377
+ end
378
+ end
379
+
380
+ # Set argument values if they exist
381
+ logger.debug "Iterating over arguments for workflow item '#{measure_dir_name}'"
382
+ if step.arguments
383
+
384
+ # handle skip first
385
+ argument_value = step.arguments['__SKIP__']
386
+ if !argument_value.nil?
387
+
388
+ if registry[:openstudio_2]
389
+ variant_type = argument_value.variantType
390
+ if variant_type == "String".to_VariantType
391
+ argument_value = argument_value.valueAsString
392
+ elsif variant_type == "Double".to_VariantType
393
+ argument_value = argument_value.valueAsDouble
394
+ elsif variant_type == "Integer".to_VariantType
395
+ argument_value = argument_value.valueAsInteger
396
+ elsif variant_type == "Boolean".to_VariantType
397
+ argument_value = argument_value.valueAsBoolean
398
+ end
399
+ end
400
+
401
+ if argument_value.class == String
402
+ argument_value = argument_value.downcase
403
+ if argument_value == "false"
404
+ skip_measure = false
405
+ else
406
+ skip_measure = true
407
+ end
408
+ elsif argument_value.class == Fixnum
409
+ skip_measure = (argument_value != 0)
410
+ elsif argument_value.class == Float
411
+ skip_measure = (argument_value != 0.0)
412
+ elsif argument_value.class == FalseClass
413
+ skip_measure = false
414
+ elsif argument_value.class == TrueClass
415
+ skip_measure = true
416
+ elsif argument_value.class == NilClass
417
+ skip_measure = false
418
+ end
419
+ end
420
+
421
+ # process other arguments
422
+ step.arguments.each do |argument_name, argument_value|
423
+
424
+ # don't validate choices if measure is being skipped
425
+ next if skip_measure
426
+
427
+ # already handled skip
428
+ next if argument_name.to_s == '__SKIP__'
429
+
430
+ # regular argument
431
+ if registry[:openstudio_2]
432
+ success = apply_arguments_2(argument_map, argument_name, argument_value, logger)
433
+ else
434
+ success = apply_arguments(argument_map, argument_name, argument_value, logger)
435
+ end
436
+ fail 'Could not set arguments' unless success
437
+ end
438
+
439
+ end
440
+
441
+ # map any choice display names to choice values, in either set values or defaults
442
+ argument_map.each_key do |argument_name|
443
+
444
+ # don't validate choices if measure is being skipped
445
+ next if skip_measure
446
+
447
+ v = argument_map[argument_name]
448
+ choice_values = v.choiceValues
449
+ if !choice_values.empty?
450
+ value = nil
451
+ value = v.defaultValueAsString if v.hasDefaultValue
452
+ value = v.valueAsString if v.hasValue
453
+ if value && choice_values.index(value).nil?
454
+ display_names = v.choiceValueDisplayNames
455
+ i = display_names.index(value)
456
+ if i && choice_values[i]
457
+ logger.debug "Mapping display name '#{value}' to value '#{choice_values[i]}' for argument '#{argument_name}'"
458
+ value_set = v.setValue(choice_values[i])
459
+ fail "Could not set argument '#{argument_name}' to mapped value '#{choice_values[i]}'" unless value_set
460
+ argument_map[argument_name.to_s] = v.clone
461
+ end
462
+ end
463
+ end
464
+ end
465
+
466
+ rescue => e
467
+
468
+ # add the error to the osw.out
469
+ runner.registerError("#{e.message}\n\t#{e.backtrace.join("\n\t")}")
470
+
471
+ log_message = "Error assigning argument in measure #{__FILE__}. Failed with #{e.message}, #{e.backtrace.join("\n")}"
472
+ raise log_message
473
+ end
474
+
475
+ if skip_measure || halted
476
+ if !energyplus_output_requests
477
+ if halted
478
+ # if halted then this measure will not get run, there are no results, not even "Skip"
479
+ logger.info "Skipping measure '#{measure_dir_name}' because simulation halted"
480
+
481
+ else
482
+ logger.info "Skipping measure '#{measure_dir_name}'"
483
+
484
+ # required to update current step, will do nothing if halted
485
+ runner.prepareForUserScriptRun(measure_object)
486
+
487
+ # don't want to log errors about arguments passed to skipped measures
488
+ #runner.validateUserArguments(arguments, argument_map
489
+
490
+ current_result = runner.result
491
+ runner.incrementStep
492
+ add_result_measure_info(current_result, measure)
493
+ current_result.setStepResult('Skip'.to_StepResult)
494
+ end
495
+ end
496
+ else
497
+
498
+ begin
499
+ if energyplus_output_requests
500
+ logger.debug "Calling measure.energyPlusOutputRequests for '#{measure_dir_name}'"
501
+ idf_objects = measure_object.energyPlusOutputRequests(runner, argument_map)
502
+ num_added = 0
503
+ idf_objects.each do |idf_object|
504
+ num_added += OpenStudio::Workflow::Util::EnergyPlus.add_energyplus_output_request(@model_idf, idf_object)
505
+ end
506
+ logger.debug "Finished measure.energyPlusOutputRequests for '#{measure_dir_name}', #{num_added} output requests added"
507
+ else
508
+ logger.debug "Calling measure.run for '#{measure_dir_name}'"
509
+ if measure_type == 'ModelMeasure'.to_MeasureType
510
+ measure_object.run(@model, runner, argument_map)
511
+ elsif measure_type == 'EnergyPlusMeasure'.to_MeasureType
512
+ measure_object.run(@model_idf, runner, argument_map)
513
+ elsif measure_type == 'ReportingMeasure'.to_MeasureType
514
+ measure_object.run(runner, argument_map)
515
+ end
516
+ logger.debug "Finished measure.run for '#{measure_dir_name}'"
517
+ end
518
+
519
+ # Run garbage collector after every measure to help address race conditions
520
+ GC.start
521
+ rescue => e
522
+
523
+ # add the error to the osw.out
524
+ runner.registerError("#{e.message}\n\t#{e.backtrace.join("\n\t")}")
525
+
526
+ result = runner.result
527
+
528
+ if !energyplus_output_requests
529
+ # incrementStep must be called after run
530
+ runner.incrementStep
531
+
532
+ add_result_measure_info(result, measure)
533
+ end
534
+
535
+ options[:output_adapter].communicate_measure_result(result) if options[:output_adapter]
536
+
537
+ log_message = "Runner error #{__FILE__} failed with #{e.message}, #{e.backtrace.join("\n")}"
538
+ raise log_message
539
+ end
540
+
541
+ # if doing output requests we are done now
542
+ if energyplus_output_requests
543
+ registry.register(:model_idf) { @model_idf }
544
+ return
545
+ end
546
+
547
+ result = nil
548
+ begin
549
+ result = runner.result
550
+
551
+ # incrementStep must be called after run
552
+ runner.incrementStep
553
+
554
+ add_result_measure_info(result, measure)
555
+
556
+ options[:output_adapter].communicate_measure_result(result) if options[:output_adapter]
557
+
558
+ errors = result.stepErrors
559
+
560
+ fail "Measure #{measure_dir_name} reported an error with #{errors}" if errors.size != 0
561
+ logger.debug "Running of measure '#{measure_dir_name}' completed. Post-processing measure output"
562
+
563
+ # TODO: fix this
564
+ #unless @wf == runner.weatherfile_path
565
+ # logger.debug "Updating the weather file to be '#{runner.weatherfile_path}'"
566
+ # registry.register(:wf) { runner.weatherfile_path }
567
+ #end
568
+
569
+ # @todo add note about why reassignment and not eval
570
+ registry.register(:model) { @model }
571
+ registry.register(:model_idf) { @model_idf }
572
+ registry.register(:sql) { @sql_filename }
573
+
574
+ if measure_type == 'ModelMeasure'.to_MeasureType
575
+ # check if weather file has changed
576
+ weather_file = @model.getOptionalWeatherFile
577
+ if !weather_file.empty?
578
+ weather_file_path = weather_file.get.path
579
+ if weather_file_path.empty?
580
+ logger.debug "Weather file object found in model but no path is given"
581
+ else
582
+ weather_file_path2 = workflow_json.findFile(weather_file_path.get)
583
+ if weather_file_path2.empty?
584
+ logger.warn "Could not find weather file '#{weather_file_path}' referenced in model"
585
+ else
586
+ if weather_file_path2.get.to_s != @wf
587
+ logger.debug "Updating weather file path to '#{weather_file_path2.get.to_s}'"
588
+ @wf = weather_file_path2.get.to_s
589
+ registry.register(:wf) { @wf }
590
+ end
591
+ end
592
+ end
593
+ end
594
+ end
595
+
596
+ rescue => e
597
+ log_message = "Runner error #{__FILE__} failed with #{e.message}, #{e.backtrace.join("\n")}"
598
+ raise log_message
599
+ end
600
+
601
+ # DLM: this section creates the measure_attributes.json file which should be deprecated
602
+ begin
603
+ measure_name = step.name.is_initialized ? step.name.get : class_name
604
+
605
+ output_attributes[measure_name.to_sym] = {} if output_attributes[measure_name.to_sym].nil?
606
+
607
+ result.stepValues.each do |step_value|
608
+ step_value_name = step_value.name
609
+ step_value_type = step_value.variantType
610
+
611
+ value = nil
612
+ if (step_value_type == "String".to_VariantType)
613
+ value = step_value.valueAsString
614
+ elsif (step_value_type == "Double".to_VariantType)
615
+ value = step_value.valueAsDouble
616
+ elsif (step_value_type == "Integer".to_VariantType)
617
+ value = step_value.valueAsInteger
618
+ elsif (step_value_type == "Boolean".to_VariantType)
619
+ value = step_value.valueAsBoolean
620
+ end
621
+
622
+ output_attributes[measure_name.to_sym][step_value_name] = value
623
+ end
624
+
625
+ # Add an applicability flag to all the measure results
626
+ step_result = result.stepResult
627
+ fail "Step Result not set" if step_result.empty?
628
+ step_result = step_result.get
629
+
630
+ if (step_result == "Skip".to_StepResult) || (step_result == "NA".to_StepResult)
631
+ output_attributes[measure_name.to_sym][:applicable] = false
632
+ else
633
+ output_attributes[measure_name.to_sym][:applicable] = true
634
+ end
635
+ registry.register(:output_attributes) { output_attributes }
636
+ rescue => e
637
+ log_message = "#{__FILE__} failed with #{e.message}, #{e.backtrace.join("\n")}"
638
+ logger.error log_message
639
+ raise log_message
640
+ end
641
+
642
+ end
643
+
644
+ rescue ScriptError, StandardError, NoMemoryError => e
645
+ log_message = "#{__FILE__} failed with message #{e.message} in #{e.backtrace.join("\n")}"
646
+ logger.error log_message
647
+ raise log_message
648
+ ensure
649
+ Dir.chdir current_dir
650
+ registry[:time_logger].stop("Measure:#{measure_dir_name}") if registry[:time_logger]
651
+
652
+ logger.info "Finished #{__method__} for #{measure_dir_name} in #{@registry[:time_logger].delta("Measure:#{measure_dir_name}")} s" if registry[:time_logger]
653
+ end
654
+ end
655
+ end
656
+ end
657
+ end
658
+ end