openstudio-workflow 1.3.3 → 1.3.4

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