openstudio-workflow 1.3.4 → 1.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -77
  3. data/README.md +67 -93
  4. data/Rakefile +36 -36
  5. data/lib/openstudio-workflow.rb +65 -65
  6. data/lib/openstudio/workflow/adapters/input/local.rb +311 -324
  7. data/lib/openstudio/workflow/adapters/output/local.rb +158 -161
  8. data/lib/openstudio/workflow/adapters/output/socket.rb +106 -107
  9. data/lib/openstudio/workflow/adapters/output/web.rb +82 -82
  10. data/lib/openstudio/workflow/adapters/output_adapter.rb +163 -163
  11. data/lib/openstudio/workflow/job.rb +57 -57
  12. data/lib/openstudio/workflow/jobs/resources/monthly_report.idf +222 -222
  13. data/lib/openstudio/workflow/jobs/run_energyplus.rb +70 -70
  14. data/lib/openstudio/workflow/jobs/run_ep_measures.rb +73 -73
  15. data/lib/openstudio/workflow/jobs/run_initialization.rb +203 -203
  16. data/lib/openstudio/workflow/jobs/run_os_measures.rb +89 -89
  17. data/lib/openstudio/workflow/jobs/run_postprocess.rb +73 -73
  18. data/lib/openstudio/workflow/jobs/run_preprocess.rb +104 -104
  19. data/lib/openstudio/workflow/jobs/run_reporting_measures.rb +118 -118
  20. data/lib/openstudio/workflow/jobs/run_translation.rb +84 -84
  21. data/lib/openstudio/workflow/multi_delegator.rb +62 -62
  22. data/lib/openstudio/workflow/registry.rb +172 -172
  23. data/lib/openstudio/workflow/run.rb +342 -328
  24. data/lib/openstudio/workflow/time_logger.rb +96 -96
  25. data/lib/openstudio/workflow/util.rb +49 -49
  26. data/lib/openstudio/workflow/util/energyplus.rb +575 -605
  27. data/lib/openstudio/workflow/util/io.rb +68 -68
  28. data/lib/openstudio/workflow/util/measure.rb +658 -650
  29. data/lib/openstudio/workflow/util/model.rb +151 -151
  30. data/lib/openstudio/workflow/util/post_process.rb +235 -238
  31. data/lib/openstudio/workflow/util/weather_file.rb +143 -143
  32. data/lib/openstudio/workflow/version.rb +40 -40
  33. data/lib/openstudio/workflow_json.rb +475 -476
  34. data/lib/openstudio/workflow_runner.rb +263 -268
  35. metadata +24 -24
@@ -1,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