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