openstudio-workflow 1.3.3 → 1.3.4

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