openstudio-analysis 1.3.4 → 1.3.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/openstudio-analysis.yml +40 -40
- data/.gitignore +21 -21
- data/.rubocop.yml +9 -9
- data/CHANGELOG.md +269 -261
- data/Gemfile +14 -14
- data/README.md +102 -102
- data/Rakefile +40 -40
- data/lib/openstudio/analysis/algorithm_attributes.rb +47 -47
- data/lib/openstudio/analysis/formulation.rb +857 -857
- data/lib/openstudio/analysis/server_api.rb +862 -846
- data/lib/openstudio/analysis/server_scripts.rb +108 -108
- data/lib/openstudio/analysis/support_files.rb +104 -104
- data/lib/openstudio/analysis/translator/datapoints.rb +454 -454
- data/lib/openstudio/analysis/translator/excel.rb +893 -893
- data/lib/openstudio/analysis/translator/workflow.rb +143 -143
- data/lib/openstudio/analysis/version.rb +12 -12
- data/lib/openstudio/analysis/workflow.rb +279 -279
- data/lib/openstudio/analysis/workflow_step.rb +523 -523
- data/lib/openstudio/analysis.rb +144 -144
- data/lib/openstudio/helpers/hash.rb +10 -10
- data/lib/openstudio/helpers/string.rb +36 -36
- data/lib/openstudio/helpers/utils.rb +36 -36
- data/lib/openstudio/weather/epw.rb +178 -178
- data/lib/openstudio-analysis.rb +47 -47
- data/openstudio-analysis.gemspec +38 -38
- data/update_license.rb +60 -60
- metadata +6 -6
@@ -1,523 +1,523 @@
|
|
1
|
-
# *******************************************************************************
|
2
|
-
# OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
|
3
|
-
# See also https://openstudio.net/license
|
4
|
-
# *******************************************************************************
|
5
|
-
|
6
|
-
# OpenStudio::Analysis::WorkflowStep is a class container for storing a measure. The generic name of step may be used later
|
7
|
-
# to include a workflow step on running EnergyPlus, radiance, etc.
|
8
|
-
module OpenStudio
|
9
|
-
module Analysis
|
10
|
-
class WorkflowStep
|
11
|
-
attr_accessor :type
|
12
|
-
attr_accessor :name
|
13
|
-
attr_accessor :display_name
|
14
|
-
|
15
|
-
attr_accessor :measure_definition_class_name
|
16
|
-
attr_accessor :measure_definition_directory
|
17
|
-
attr_accessor :measure_definition_directory_local
|
18
|
-
attr_accessor :measure_definition_display_name
|
19
|
-
attr_accessor :measure_definition_name
|
20
|
-
attr_accessor :measure_definition_name_xml
|
21
|
-
attr_accessor :measure_definition_uuid
|
22
|
-
attr_accessor :measure_definition_version_uuid
|
23
|
-
attr_accessor :uuid
|
24
|
-
attr_accessor :version_uuid
|
25
|
-
attr_accessor :description
|
26
|
-
attr_accessor :taxonomy
|
27
|
-
|
28
|
-
attr_reader :arguments
|
29
|
-
attr_reader :variables
|
30
|
-
|
31
|
-
# Create an instance of the OpenStudio::Analysis::WorkflowStep
|
32
|
-
#
|
33
|
-
# @return [Object] An OpenStudio::Analysis::WorkflowStep object
|
34
|
-
def initialize
|
35
|
-
@name = ''
|
36
|
-
@display_name = ''
|
37
|
-
|
38
|
-
# The type of item being added (ModelMeasure, EnergyPlusMeasure, ...)
|
39
|
-
@type = nil
|
40
|
-
|
41
|
-
@measure_definition_class_name = nil
|
42
|
-
@measure_definition_directory = nil
|
43
|
-
@measure_definition_directory_local = nil
|
44
|
-
@measure_definition_display_name = nil
|
45
|
-
@measure_definition_name = nil
|
46
|
-
@measure_definition_name_xml = nil
|
47
|
-
@measure_definition_uuid = nil
|
48
|
-
@measure_definition_version_uuid = nil
|
49
|
-
@uuid = nil
|
50
|
-
@version_uuid = nil
|
51
|
-
@description = nil
|
52
|
-
#@taxonomy = nil #BLB dont do this now
|
53
|
-
@arguments = []
|
54
|
-
|
55
|
-
@arguments << {
|
56
|
-
display_name: 'Skip Entire Measure',
|
57
|
-
display_name_short: 'Skip',
|
58
|
-
name: '__SKIP__',
|
59
|
-
value_type: 'boolean',
|
60
|
-
default_value: false,
|
61
|
-
value: false
|
62
|
-
}
|
63
|
-
|
64
|
-
# TODO: eventually the variables should be its own class. This would then be an array of Variable objects.
|
65
|
-
@variables = []
|
66
|
-
end
|
67
|
-
|
68
|
-
# Return an array of the argument names
|
69
|
-
#
|
70
|
-
# @return [Array] Listing of argument names.
|
71
|
-
def argument_names
|
72
|
-
@arguments.map { |a| a[:name] }
|
73
|
-
end
|
74
|
-
|
75
|
-
# Set the value of an argument to `value`. The user is required to know the data type and pass it in accordingly
|
76
|
-
#
|
77
|
-
# @param argument_name [String] The machine name of the argument that you want to set the value to
|
78
|
-
# @param value [] The value to assign the argument
|
79
|
-
# @return [Boolean] True/false if it assigned it
|
80
|
-
def argument_value(argument_name, value)
|
81
|
-
a = @arguments.find_all { |a| a[:name] == argument_name }
|
82
|
-
raise "could not find argument_name of #{argument_name} in measure #{name}. Valid argument names are #{argument_names}." if a.empty?
|
83
|
-
raise "more than one argument with the same name of #{argument_name} in measure #{name}" if a.size > 1
|
84
|
-
|
85
|
-
a = a.first
|
86
|
-
|
87
|
-
a[:value] = value
|
88
|
-
|
89
|
-
a[:value] == value
|
90
|
-
end
|
91
|
-
|
92
|
-
# Return a variable by its name.
|
93
|
-
#
|
94
|
-
# @param name [String] Name of the arugment that makes the variable.
|
95
|
-
# @return [Object] The variable object
|
96
|
-
def find_variable_by_name(name)
|
97
|
-
v = @variables.find { |v| v[:argument][:name] == name }
|
98
|
-
|
99
|
-
v
|
100
|
-
end
|
101
|
-
|
102
|
-
def remove_variable(variable_name)
|
103
|
-
v_index = @variables.find_index { |v| v[:argument][:name] == variable_name }
|
104
|
-
if v_index
|
105
|
-
@variables.delete_at(v_index)
|
106
|
-
return true
|
107
|
-
else
|
108
|
-
return false
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
# Tag a measure's argument as a variable.
|
113
|
-
#
|
114
|
-
# @param argument_name [String] The instance_name of the measure argument that is to be tagged. This is the same name as the argument's variable in the measure.rb file.
|
115
|
-
# @param variable_display_name [String] What the variable is called. It is best if the display name is self describing (i.e. does not need any other context). It can be the same as the argument display name.
|
116
|
-
# @param distribution [Hash] Hash describing the distribution of the variable.
|
117
|
-
# @option distribution [String] :type Type of distribution. `discrete`, `uniform`, `triangle`, `normal`, `lognormal`, `integer_sequence`
|
118
|
-
# @option distribution [String] :units Units of the variable. This is legacy as previous OpenStudio measures did not specify units separately.
|
119
|
-
# @option distribution [String] :minimum Minimum value of the distribution, required for all distributions
|
120
|
-
# @option distribution [String] :maximum Maximum value of the distribution, required for all distributions
|
121
|
-
# @option distribution [String] :standard_deviation The standard deviation, if the distribution requires it.
|
122
|
-
# @option distribution [String] :mode The mean/mode of the distribution (if required)
|
123
|
-
# @option distribution [String] :mean Alias for the mode. If this is used it will override the mode
|
124
|
-
# @option distribution [String] :relation_to_output How is the variable correlates to the output of interest (for continuous distributions)
|
125
|
-
# @option distribution [String] :step_size Minimum step size (delta_x) of the variable (for continuous distributions)
|
126
|
-
# @option distribution [String] :values If discrete, then the values to run
|
127
|
-
# @option distribution [String] :weights If discrete, then the weights for each of the discrete values, must be the same length as values, and sum to 1. If empty, then it will create this automatically to be uniform.
|
128
|
-
# @param variable_type [String] What type of variable, variable or pivot. Typically this is variable.
|
129
|
-
# @param options [Hash] Values that define the variable.
|
130
|
-
# @option options [String] :variable_type The type of variable, `variable` or `pivot`. By default this is a variable.
|
131
|
-
# @option options [String] :variable_display_name_short The short display name of the variable. Will be defaulted to the variable_display_name if not passed
|
132
|
-
# @return [Boolean] True / False if it was able to tag the measure argument
|
133
|
-
def make_variable(argument_name, variable_display_name, distribution, options = {})
|
134
|
-
options = { variable_type: 'variable' }.merge(options)
|
135
|
-
distribution[:mode] = distribution[:mean] if distribution.key? :mean
|
136
|
-
|
137
|
-
raise "Set the static value in the options 'options[:static_value]', not the distribution" if distribution[:static_value]
|
138
|
-
|
139
|
-
a = @arguments.find_all { |a| a[:name] == argument_name }
|
140
|
-
raise "could not find argument_name of #{argument_name} in measure #{name}. Valid argument names are #{argument_names}." if a.empty?
|
141
|
-
raise "more than one argument with the same name of #{argument_name} in measure #{name}" if a.size > 1
|
142
|
-
|
143
|
-
if distribution_valid?(distribution)
|
144
|
-
# grab the argument hash
|
145
|
-
a = a.first
|
146
|
-
|
147
|
-
# add more information to the argument
|
148
|
-
v = {}
|
149
|
-
v[:argument] = a
|
150
|
-
v[:display_name] = variable_display_name
|
151
|
-
v[:display_name_short] = options[:variable_display_name_short] ? options[:variable_display_name_short] : variable_display_name
|
152
|
-
v[:variable_type] = options[:variable_type]
|
153
|
-
|
154
|
-
v[:type] = distribution[:type]
|
155
|
-
v[:units] = distribution[:units] ? distribution[:units] : nil
|
156
|
-
v[:minimum] = distribution[:minimum]
|
157
|
-
v[:maximum] = distribution[:maximum]
|
158
|
-
v[:relation_to_output] = distribution[:relation_to_output] ? distribution[:relation_to_output] : nil
|
159
|
-
v[:mode] = distribution[:mode]
|
160
|
-
v[:static_value] = options[:static_value] if options[:static_value]
|
161
|
-
# TODO: Static value should be named default value or just value
|
162
|
-
|
163
|
-
# Always look for these attributes even if the distribution does not need them
|
164
|
-
v[:weights] = distribution[:weights] if distribution[:weights]
|
165
|
-
v[:values] = distribution[:values] if distribution[:values]
|
166
|
-
v[:standard_deviation] = distribution[:standard_deviation] if distribution[:standard_deviation]
|
167
|
-
v[:step_size] = distribution[:step_size] ? distribution[:step_size] : nil
|
168
|
-
|
169
|
-
# assign uuid and version id to the variable
|
170
|
-
v[:uuid] = SecureRandom.uuid
|
171
|
-
v[:version_uuid] = SecureRandom.uuid
|
172
|
-
@variables << v
|
173
|
-
end
|
174
|
-
|
175
|
-
true
|
176
|
-
end
|
177
|
-
|
178
|
-
# Convert the class into a hash. TODO: Make this smart based on the :type eventually
|
179
|
-
#
|
180
|
-
# @return [Hash] Returns the hash
|
181
|
-
def to_hash(version = 1, *a)
|
182
|
-
hash = {}
|
183
|
-
if version == 1
|
184
|
-
instance_variables.each do |var|
|
185
|
-
if var.to_s == '@type'
|
186
|
-
hash[:measure_type] = instance_variable_get(var)
|
187
|
-
elsif var.to_s == '@arguments'
|
188
|
-
hash[:arguments] = []
|
189
|
-
@arguments.each do |a|
|
190
|
-
# This will change in version 2 but right now, if the argument is a variable, then the argument will
|
191
|
-
# be in the variables hash, not the arguments hash.
|
192
|
-
next unless @variables.find { |v| v[:argument][:name] == a[:name] }.nil?
|
193
|
-
hash[:arguments] << a
|
194
|
-
end
|
195
|
-
elsif var.to_s == '@variables'
|
196
|
-
# skip until after looping over instance_variables
|
197
|
-
elsif var.to_s == '@__swigtype__'
|
198
|
-
# skip the swig variables caused by using the same namespace as OpenStudio
|
199
|
-
else
|
200
|
-
hash[var.to_s.delete('@')] = instance_variable_get(var)
|
201
|
-
end
|
202
|
-
|
203
|
-
# TODO: iterate over the variables and create UUIDs, or not?
|
204
|
-
end
|
205
|
-
|
206
|
-
# fix everything to support the legacy version
|
207
|
-
# we need to make a deep copy since multiple calls to .to_hash deletes :type, :mode, etc below
|
208
|
-
# and we still want those args to be avail for future calls, but not end up in the final OSA hash.
|
209
|
-
# without this, the v.delete() below (line ~278-281) will remove :type from @variables.
|
210
|
-
# this would be okay if there was only 1 call to .to_hash. but thats not guaranteed
|
211
|
-
variables_dup = Marshal.load(Marshal.dump(@variables))
|
212
|
-
hash[:variables] = variables_dup
|
213
|
-
|
214
|
-
# Clean up the variables to match the legacy format
|
215
|
-
hash[:variables].each_with_index do |v, index|
|
216
|
-
v[:variable_type] == 'pivot' ? v[:pivot] = true : v[:variable] = true
|
217
|
-
v[:static_value] = v[:argument][:default_value] unless v[:static_value]
|
218
|
-
@variables[index][:static_value] = v[:static_value]
|
219
|
-
|
220
|
-
v[:uncertainty_description] = {}
|
221
|
-
# In Version 0.5 the _uncertain text will be removed from distribution
|
222
|
-
if v[:type] =~ /uncertain/
|
223
|
-
v[:type].delete!('_uncertain')
|
224
|
-
end
|
225
|
-
v[:uncertainty_description][:type] = v[:type]
|
226
|
-
|
227
|
-
# This is not neatly coded. This should be a new object that knows how to write itself out.
|
228
|
-
v[:uncertainty_description][:attributes] = []
|
229
|
-
if v[:type] =~ /discrete/
|
230
|
-
new_h = {}
|
231
|
-
new_h[:name] = 'discrete'
|
232
|
-
|
233
|
-
# check the weights
|
234
|
-
new_h[:values_and_weights] = v.delete(:values).zip(v.delete(:weights)).map { |w| { value: w[0], weight: w[1] } }
|
235
|
-
v[:uncertainty_description][:attributes] << new_h
|
236
|
-
end
|
237
|
-
|
238
|
-
# always write out these attributes
|
239
|
-
v[:uncertainty_description][:attributes] << { name: 'lower_bounds', value: v[:minimum] }
|
240
|
-
v[:uncertainty_description][:attributes] << { name: 'upper_bounds', value: v[:maximum] }
|
241
|
-
v[:uncertainty_description][:attributes] << { name: 'modes', value: v[:mode] }
|
242
|
-
v[:uncertainty_description][:attributes] << { name: 'delta_x', value: v[:step_size] ? v[:step_size] : nil }
|
243
|
-
v[:uncertainty_description][:attributes] << { name: 'stddev', value: v[:standard_deviation] ? v[:standard_deviation] : nil }
|
244
|
-
|
245
|
-
v[:workflow_index] = index
|
246
|
-
|
247
|
-
# remove some remaining items
|
248
|
-
v.delete(:type)
|
249
|
-
v.delete(:mode) if v.key?(:mode)
|
250
|
-
v.delete(:step_size) if v.key?(:step_size)
|
251
|
-
v.delete(:standard_deviation) if v.key?(:standard_deviation)
|
252
|
-
end
|
253
|
-
|
254
|
-
else
|
255
|
-
raise "Do not know how to create the Hash for Version #{version}"
|
256
|
-
end
|
257
|
-
|
258
|
-
hash
|
259
|
-
end
|
260
|
-
|
261
|
-
# Read the workflow item from a measure hash.
|
262
|
-
#
|
263
|
-
# @param instance_name [String] Machine name of the instance
|
264
|
-
# @param instance_display_name [String] Display name of the instance
|
265
|
-
# @param path_to_measure [String] This is the local path to the measure directory, relative or absolute. It is used when zipping up all the measures.
|
266
|
-
# @param hash [Hash] Measure hash in the format of a converted measure.xml hash (from the Analysis Spreadsheet project)
|
267
|
-
# @param options [Hash] Optional arguments
|
268
|
-
# @option options [Boolean] :ignore_not_found Do not raise an exception if the measure could not be found on the machine
|
269
|
-
# @return [Object] Returns the OpenStudio::Analysis::WorkflowStep
|
270
|
-
def self.from_measure_hash(instance_name, instance_display_name, path_to_measure, hash, options = {})
|
271
|
-
if File.directory? path_to_measure
|
272
|
-
path_to_measure = File.join(path_to_measure, 'measure.rb')
|
273
|
-
end
|
274
|
-
|
275
|
-
# verify that the path to the measure is a path and not a file. If it is make it a path
|
276
|
-
if File.exist?(path_to_measure) && File.file?(path_to_measure)
|
277
|
-
path_to_measure = File.dirname(path_to_measure)
|
278
|
-
else
|
279
|
-
raise "Could not find measure '#{instance_name}' in '#{path_to_measure}'" unless options[:ignore_not_found]
|
280
|
-
end
|
281
|
-
|
282
|
-
# Extract the directory
|
283
|
-
path_to_measure_local = path_to_measure
|
284
|
-
path_to_measure = "./measures/#{File.basename(path_to_measure)}"
|
285
|
-
|
286
|
-
# map the BCL hash format into the OpenStudio WorkflowStep format
|
287
|
-
s = OpenStudio::Analysis::WorkflowStep.new
|
288
|
-
|
289
|
-
# add the instance and display name
|
290
|
-
s.name = instance_name
|
291
|
-
s.display_name = instance_display_name
|
292
|
-
|
293
|
-
# definition of the measure
|
294
|
-
s.measure_definition_class_name = hash[:classname]
|
295
|
-
s.measure_definition_directory = path_to_measure
|
296
|
-
s.measure_definition_directory_local = path_to_measure_local
|
297
|
-
s.measure_definition_display_name = hash[:display_name]
|
298
|
-
s.measure_definition_name = hash[:name]
|
299
|
-
# name_xml is not used right now but eventually should be used to compare the hash[:name] and the hash[:name_xml]
|
300
|
-
s.measure_definition_name_xml = hash[:name_xml]
|
301
|
-
s.measure_definition_uuid = hash[:uid]
|
302
|
-
s.measure_definition_version_uuid = hash[:version_id]
|
303
|
-
s.uuid = hash[:uid]
|
304
|
-
s.version_uuid = hash[:version_id]
|
305
|
-
s.description = hash[:description]
|
306
|
-
#s.taxonomy = hash[:taxonomy] #BLB dont do this now
|
307
|
-
|
308
|
-
# do not allow the choice variable_type
|
309
|
-
|
310
|
-
s.type = hash[:measure_type] # this is actually the measure type
|
311
|
-
hash[:arguments]&.each do |arg|
|
312
|
-
puts arg
|
313
|
-
# warn the user to we need to deprecate variable_type and use value_type (which is what os server uses)
|
314
|
-
var_type = arg[:variable_type] ? arg[:variable_type].downcase : arg[:value_type]
|
315
|
-
|
316
|
-
if var_type == 'choice'
|
317
|
-
# WARN the user that the measure had a "choice data type"
|
318
|
-
var_type = 'string'
|
319
|
-
end
|
320
|
-
|
321
|
-
|
322
|
-
if var_type.downcase == 'double'
|
323
|
-
default_value = arg[:default_value].to_f
|
324
|
-
elsif var_type.downcase == 'integer'
|
325
|
-
default_value = arg[:default_value].to_i
|
326
|
-
elsif var_type.downcase == 'boolean'
|
327
|
-
# In some cases a nil default is okay. It is seen as "non-existing" and
|
328
|
-
# needs to be passed through as such.
|
329
|
-
if arg[:default_value].nil?
|
330
|
-
default_value = nil
|
331
|
-
else
|
332
|
-
default_value = (arg[:default_value].downcase == "true") #convert the string 'true'/'false' to boolean
|
333
|
-
end
|
334
|
-
else
|
335
|
-
default_value = arg[:default_value]
|
336
|
-
end
|
337
|
-
|
338
|
-
if !arg[:display_name_short].nil?
|
339
|
-
display_name_short = arg[:display_name_short]
|
340
|
-
else
|
341
|
-
display_name_short = arg[:display_name]
|
342
|
-
end
|
343
|
-
|
344
|
-
s.arguments << {
|
345
|
-
display_name: arg[:display_name],
|
346
|
-
display_name_short: display_name_short,
|
347
|
-
name: arg[:name],
|
348
|
-
value_type: var_type,
|
349
|
-
default_value: default_value,
|
350
|
-
value: default_value
|
351
|
-
}
|
352
|
-
end
|
353
|
-
|
354
|
-
# Load the arguments of variables, but do not make them variables. This format is more about arugments, than variables
|
355
|
-
hash[:variables]&.each do |variable|
|
356
|
-
# add the arguments first
|
357
|
-
s.arguments << {
|
358
|
-
display_name: variable[:argument][:display_name],
|
359
|
-
display_name_short: variable[:argument][:display_name_short],
|
360
|
-
name: variable[:argument][:name],
|
361
|
-
value_type: variable[:argument][:value_type],
|
362
|
-
default_value: variable[:argument][:default_value],
|
363
|
-
value: variable[:argument][:default_value]
|
364
|
-
}
|
365
|
-
end
|
366
|
-
|
367
|
-
s
|
368
|
-
end
|
369
|
-
|
370
|
-
# Read the workflow item from a analysis hash. Can we combine measure hash and analysis hash?
|
371
|
-
#
|
372
|
-
# @param instance_name [String] Machine name of the instance
|
373
|
-
# @param instance_display_name [String] Display name of the instance
|
374
|
-
# @param path_to_measure [String] This is the local path to the measure directroy, relative or absolute. It is used when zipping up all the measures.
|
375
|
-
# @param hash [Hash] Measure hash in the format of the measure.xml converted to JSON (from the Analysis Spreadsheet project)
|
376
|
-
# @param options [Hash] Optional arguments
|
377
|
-
# @option options [Boolean] :ignore_not_found Do not raise an exception if the measure could not be found on the machine
|
378
|
-
# @return [Object] Returns the OpenStudio::Analysis::WorkflowStep
|
379
|
-
def self.from_analysis_hash(instance_name, instance_display_name, path_to_measure, hash, options = {})
|
380
|
-
# TODO: Validate the hash
|
381
|
-
# TODO: validate that the measure exists?
|
382
|
-
|
383
|
-
if File.directory? path_to_measure
|
384
|
-
path_to_measure = File.join(path_to_measure, 'measure.rb')
|
385
|
-
end
|
386
|
-
|
387
|
-
# verify that the path to the measure is a path and not a file. If it is make it a path
|
388
|
-
if File.exist?(path_to_measure) && File.file?(path_to_measure)
|
389
|
-
path_to_measure = File.dirname(path_to_measure)
|
390
|
-
else
|
391
|
-
raise "Could not find measure '#{instance_name}' in '#{path_to_measure}'" unless options[:ignore_not_found]
|
392
|
-
end
|
393
|
-
|
394
|
-
# Extract the directo
|
395
|
-
path_to_measure_local = path_to_measure
|
396
|
-
path_to_measure = "./measures/#{File.basename(path_to_measure)}"
|
397
|
-
|
398
|
-
# map the BCL hash format into the OpenStudio WorkflowStep format
|
399
|
-
s = OpenStudio::Analysis::WorkflowStep.new
|
400
|
-
|
401
|
-
# add the instance and display name
|
402
|
-
s.name = instance_name
|
403
|
-
s.display_name = instance_display_name
|
404
|
-
|
405
|
-
# definition of the measure
|
406
|
-
s.measure_definition_class_name = hash[:measure_definition_class_name]
|
407
|
-
s.measure_definition_directory = path_to_measure
|
408
|
-
s.measure_definition_directory_local = path_to_measure_local
|
409
|
-
s.measure_definition_display_name = hash[:measure_definition_display_name]
|
410
|
-
s.measure_definition_name = hash[:measure_definition_name]
|
411
|
-
# name_xml is not used right now but eventually should be used to compare the hash[:name] and the hash[:name_xml]
|
412
|
-
s.measure_definition_name_xml = hash[:measure_definition_name_xml]
|
413
|
-
s.measure_definition_uuid = hash[:measure_definition_uuid]
|
414
|
-
s.measure_definition_version_uuid = hash[:measure_definition_version_uuid]
|
415
|
-
s.uuid = hash[:uuid] if hash[:uuid]
|
416
|
-
s.version_uuid = hash[:version_uuid] if hash[:version_uuid]
|
417
|
-
s.description = hash[:description] if hash[:description]
|
418
|
-
#s.taxonomy = hash[:taxonomy] if hash[:taxonomy] #BLB dont do this, its a Tags array of Tag
|
419
|
-
|
420
|
-
s.type = hash[:measure_type] # this is actually the measure type
|
421
|
-
hash[:arguments]&.each do |arg|
|
422
|
-
# warn the user to we need to deprecate variable_type and use value_type (which is what os server uses)
|
423
|
-
var_type = arg[:value_type]
|
424
|
-
|
425
|
-
if var_type == 'choice'
|
426
|
-
# WARN the user that the measure had a "choice data type"
|
427
|
-
var_type = 'string'
|
428
|
-
end
|
429
|
-
|
430
|
-
if var_type.downcase == 'double'
|
431
|
-
default_value = arg[:default_value].to_f
|
432
|
-
value = arg[:value].to_f
|
433
|
-
elsif var_type.downcase == 'integer'
|
434
|
-
default_value = arg[:default_value].to_i
|
435
|
-
value = arg[:value].to_i
|
436
|
-
elsif var_type.downcase == 'boolean'
|
437
|
-
default_value = (arg[:default_value].downcase == "true") # convert the string 'true'/'false' to boolean
|
438
|
-
value = (arg[:value].downcase == "true") # convert the string 'true'/'false' to boolean
|
439
|
-
else
|
440
|
-
default_value = arg[:default_value]
|
441
|
-
value = arg[:value]
|
442
|
-
end
|
443
|
-
|
444
|
-
if !arg[:display_name_short].nil?
|
445
|
-
display_name_short = arg[:display_name_short]
|
446
|
-
else
|
447
|
-
display_name_short = arg[:display_name]
|
448
|
-
end
|
449
|
-
|
450
|
-
s.arguments << {
|
451
|
-
display_name: arg[:display_name],
|
452
|
-
display_name_short: display_name_short,
|
453
|
-
name: arg[:name],
|
454
|
-
value_type: var_type,
|
455
|
-
default_value: default_value,
|
456
|
-
value: value
|
457
|
-
}
|
458
|
-
end
|
459
|
-
|
460
|
-
hash[:variables]&.each do |variable|
|
461
|
-
# add the arguments first
|
462
|
-
s.arguments << {
|
463
|
-
display_name: variable[:argument][:display_name],
|
464
|
-
display_name_short: variable[:argument][:display_name_short],
|
465
|
-
name: variable[:argument][:name],
|
466
|
-
value_type: variable[:argument][:value_type],
|
467
|
-
default_value: variable[:argument][:default_value],
|
468
|
-
value: variable[:argument][:default_value]
|
469
|
-
}
|
470
|
-
|
471
|
-
var_options = {}
|
472
|
-
var_options[:variable_type] = variable[:variable_type]
|
473
|
-
var_options[:variable_display_name_short] = variable[:display_name_short]
|
474
|
-
var_options[:static_value] = variable[:static_value]
|
475
|
-
distribution = variable[:uncertainty_description]
|
476
|
-
distribution[:minimum] = variable[:minimum]
|
477
|
-
distribution[:mean] = distribution[:attributes].find { |a| a[:name] == 'modes' }[:value]
|
478
|
-
distribution[:maximum] = variable[:maximum]
|
479
|
-
distribution[:standard_deviation] = distribution[:attributes].find { |a| a[:name] == 'stddev' }[:value]
|
480
|
-
distribution[:step_size] = distribution[:attributes].find { |a| a[:name] == 'delta_x' }[:value]
|
481
|
-
s.make_variable(variable[:argument][:name], variable[:display_name], distribution, var_options)
|
482
|
-
end
|
483
|
-
|
484
|
-
s
|
485
|
-
end
|
486
|
-
|
487
|
-
private
|
488
|
-
|
489
|
-
# validate the arguments of the distribution
|
490
|
-
def distribution_valid?(d)
|
491
|
-
# regardless of uncertainty description the following must be defined
|
492
|
-
raise 'No distribution defined for variable' unless d.key? :type
|
493
|
-
raise 'No minimum defined for variable' unless d.key? :minimum
|
494
|
-
raise 'No maximum defined for variable' unless d.key? :maximum
|
495
|
-
raise 'No mean/mode defined for variable' unless d.key? :mode
|
496
|
-
|
497
|
-
if d[:type] =~ /uniform/
|
498
|
-
# Do we need to tell the user that we don't really need the mean/mode for uniform ?
|
499
|
-
elsif d[:type] =~ /discrete/
|
500
|
-
# require min, max, mode
|
501
|
-
raise 'No values passed for discrete distribution' unless d[:values] || d[:values].empty?
|
502
|
-
if d[:weights]
|
503
|
-
raise 'Weights are not the same length as values' unless d[:values].size == d[:weights].size
|
504
|
-
raise 'Weights do not sum up to one' unless d[:weights].reduce(:+).between?(0.99, 1.01) # allow a small error for now
|
505
|
-
else
|
506
|
-
fraction = 1 / d[:values].size.to_f
|
507
|
-
d[:weights] = [fraction] * d[:values].size
|
508
|
-
end
|
509
|
-
elsif d[:type] =~ /integer_sequence/
|
510
|
-
d[:weights] = 1
|
511
|
-
d[:values] = 1
|
512
|
-
elsif d[:type] =~ /triangle/
|
513
|
-
# requires min, max, mode
|
514
|
-
elsif d[:type] =~ /normal/ # both normal and lognormal
|
515
|
-
# require min, max, mode, stddev
|
516
|
-
raise 'No standard deviation for variable' unless d[:standard_deviation]
|
517
|
-
end
|
518
|
-
|
519
|
-
true
|
520
|
-
end
|
521
|
-
end
|
522
|
-
end
|
523
|
-
end
|
1
|
+
# *******************************************************************************
|
2
|
+
# OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
|
3
|
+
# See also https://openstudio.net/license
|
4
|
+
# *******************************************************************************
|
5
|
+
|
6
|
+
# OpenStudio::Analysis::WorkflowStep is a class container for storing a measure. The generic name of step may be used later
|
7
|
+
# to include a workflow step on running EnergyPlus, radiance, etc.
|
8
|
+
module OpenStudio
|
9
|
+
module Analysis
|
10
|
+
class WorkflowStep
|
11
|
+
attr_accessor :type
|
12
|
+
attr_accessor :name
|
13
|
+
attr_accessor :display_name
|
14
|
+
|
15
|
+
attr_accessor :measure_definition_class_name
|
16
|
+
attr_accessor :measure_definition_directory
|
17
|
+
attr_accessor :measure_definition_directory_local
|
18
|
+
attr_accessor :measure_definition_display_name
|
19
|
+
attr_accessor :measure_definition_name
|
20
|
+
attr_accessor :measure_definition_name_xml
|
21
|
+
attr_accessor :measure_definition_uuid
|
22
|
+
attr_accessor :measure_definition_version_uuid
|
23
|
+
attr_accessor :uuid
|
24
|
+
attr_accessor :version_uuid
|
25
|
+
attr_accessor :description
|
26
|
+
attr_accessor :taxonomy
|
27
|
+
|
28
|
+
attr_reader :arguments
|
29
|
+
attr_reader :variables
|
30
|
+
|
31
|
+
# Create an instance of the OpenStudio::Analysis::WorkflowStep
|
32
|
+
#
|
33
|
+
# @return [Object] An OpenStudio::Analysis::WorkflowStep object
|
34
|
+
def initialize
|
35
|
+
@name = ''
|
36
|
+
@display_name = ''
|
37
|
+
|
38
|
+
# The type of item being added (ModelMeasure, EnergyPlusMeasure, ...)
|
39
|
+
@type = nil
|
40
|
+
|
41
|
+
@measure_definition_class_name = nil
|
42
|
+
@measure_definition_directory = nil
|
43
|
+
@measure_definition_directory_local = nil
|
44
|
+
@measure_definition_display_name = nil
|
45
|
+
@measure_definition_name = nil
|
46
|
+
@measure_definition_name_xml = nil
|
47
|
+
@measure_definition_uuid = nil
|
48
|
+
@measure_definition_version_uuid = nil
|
49
|
+
@uuid = nil
|
50
|
+
@version_uuid = nil
|
51
|
+
@description = nil
|
52
|
+
#@taxonomy = nil #BLB dont do this now
|
53
|
+
@arguments = []
|
54
|
+
|
55
|
+
@arguments << {
|
56
|
+
display_name: 'Skip Entire Measure',
|
57
|
+
display_name_short: 'Skip',
|
58
|
+
name: '__SKIP__',
|
59
|
+
value_type: 'boolean',
|
60
|
+
default_value: false,
|
61
|
+
value: false
|
62
|
+
}
|
63
|
+
|
64
|
+
# TODO: eventually the variables should be its own class. This would then be an array of Variable objects.
|
65
|
+
@variables = []
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return an array of the argument names
|
69
|
+
#
|
70
|
+
# @return [Array] Listing of argument names.
|
71
|
+
def argument_names
|
72
|
+
@arguments.map { |a| a[:name] }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Set the value of an argument to `value`. The user is required to know the data type and pass it in accordingly
|
76
|
+
#
|
77
|
+
# @param argument_name [String] The machine name of the argument that you want to set the value to
|
78
|
+
# @param value [] The value to assign the argument
|
79
|
+
# @return [Boolean] True/false if it assigned it
|
80
|
+
def argument_value(argument_name, value)
|
81
|
+
a = @arguments.find_all { |a| a[:name] == argument_name }
|
82
|
+
raise "could not find argument_name of #{argument_name} in measure #{name}. Valid argument names are #{argument_names}." if a.empty?
|
83
|
+
raise "more than one argument with the same name of #{argument_name} in measure #{name}" if a.size > 1
|
84
|
+
|
85
|
+
a = a.first
|
86
|
+
|
87
|
+
a[:value] = value
|
88
|
+
|
89
|
+
a[:value] == value
|
90
|
+
end
|
91
|
+
|
92
|
+
# Return a variable by its name.
|
93
|
+
#
|
94
|
+
# @param name [String] Name of the arugment that makes the variable.
|
95
|
+
# @return [Object] The variable object
|
96
|
+
def find_variable_by_name(name)
|
97
|
+
v = @variables.find { |v| v[:argument][:name] == name }
|
98
|
+
|
99
|
+
v
|
100
|
+
end
|
101
|
+
|
102
|
+
def remove_variable(variable_name)
|
103
|
+
v_index = @variables.find_index { |v| v[:argument][:name] == variable_name }
|
104
|
+
if v_index
|
105
|
+
@variables.delete_at(v_index)
|
106
|
+
return true
|
107
|
+
else
|
108
|
+
return false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Tag a measure's argument as a variable.
|
113
|
+
#
|
114
|
+
# @param argument_name [String] The instance_name of the measure argument that is to be tagged. This is the same name as the argument's variable in the measure.rb file.
|
115
|
+
# @param variable_display_name [String] What the variable is called. It is best if the display name is self describing (i.e. does not need any other context). It can be the same as the argument display name.
|
116
|
+
# @param distribution [Hash] Hash describing the distribution of the variable.
|
117
|
+
# @option distribution [String] :type Type of distribution. `discrete`, `uniform`, `triangle`, `normal`, `lognormal`, `integer_sequence`
|
118
|
+
# @option distribution [String] :units Units of the variable. This is legacy as previous OpenStudio measures did not specify units separately.
|
119
|
+
# @option distribution [String] :minimum Minimum value of the distribution, required for all distributions
|
120
|
+
# @option distribution [String] :maximum Maximum value of the distribution, required for all distributions
|
121
|
+
# @option distribution [String] :standard_deviation The standard deviation, if the distribution requires it.
|
122
|
+
# @option distribution [String] :mode The mean/mode of the distribution (if required)
|
123
|
+
# @option distribution [String] :mean Alias for the mode. If this is used it will override the mode
|
124
|
+
# @option distribution [String] :relation_to_output How is the variable correlates to the output of interest (for continuous distributions)
|
125
|
+
# @option distribution [String] :step_size Minimum step size (delta_x) of the variable (for continuous distributions)
|
126
|
+
# @option distribution [String] :values If discrete, then the values to run
|
127
|
+
# @option distribution [String] :weights If discrete, then the weights for each of the discrete values, must be the same length as values, and sum to 1. If empty, then it will create this automatically to be uniform.
|
128
|
+
# @param variable_type [String] What type of variable, variable or pivot. Typically this is variable.
|
129
|
+
# @param options [Hash] Values that define the variable.
|
130
|
+
# @option options [String] :variable_type The type of variable, `variable` or `pivot`. By default this is a variable.
|
131
|
+
# @option options [String] :variable_display_name_short The short display name of the variable. Will be defaulted to the variable_display_name if not passed
|
132
|
+
# @return [Boolean] True / False if it was able to tag the measure argument
|
133
|
+
def make_variable(argument_name, variable_display_name, distribution, options = {})
|
134
|
+
options = { variable_type: 'variable' }.merge(options)
|
135
|
+
distribution[:mode] = distribution[:mean] if distribution.key? :mean
|
136
|
+
|
137
|
+
raise "Set the static value in the options 'options[:static_value]', not the distribution" if distribution[:static_value]
|
138
|
+
|
139
|
+
a = @arguments.find_all { |a| a[:name] == argument_name }
|
140
|
+
raise "could not find argument_name of #{argument_name} in measure #{name}. Valid argument names are #{argument_names}." if a.empty?
|
141
|
+
raise "more than one argument with the same name of #{argument_name} in measure #{name}" if a.size > 1
|
142
|
+
|
143
|
+
if distribution_valid?(distribution)
|
144
|
+
# grab the argument hash
|
145
|
+
a = a.first
|
146
|
+
|
147
|
+
# add more information to the argument
|
148
|
+
v = {}
|
149
|
+
v[:argument] = a
|
150
|
+
v[:display_name] = variable_display_name
|
151
|
+
v[:display_name_short] = options[:variable_display_name_short] ? options[:variable_display_name_short] : variable_display_name
|
152
|
+
v[:variable_type] = options[:variable_type]
|
153
|
+
|
154
|
+
v[:type] = distribution[:type]
|
155
|
+
v[:units] = distribution[:units] ? distribution[:units] : nil
|
156
|
+
v[:minimum] = distribution[:minimum]
|
157
|
+
v[:maximum] = distribution[:maximum]
|
158
|
+
v[:relation_to_output] = distribution[:relation_to_output] ? distribution[:relation_to_output] : nil
|
159
|
+
v[:mode] = distribution[:mode]
|
160
|
+
v[:static_value] = options[:static_value] if options[:static_value]
|
161
|
+
# TODO: Static value should be named default value or just value
|
162
|
+
|
163
|
+
# Always look for these attributes even if the distribution does not need them
|
164
|
+
v[:weights] = distribution[:weights] if distribution[:weights]
|
165
|
+
v[:values] = distribution[:values] if distribution[:values]
|
166
|
+
v[:standard_deviation] = distribution[:standard_deviation] if distribution[:standard_deviation]
|
167
|
+
v[:step_size] = distribution[:step_size] ? distribution[:step_size] : nil
|
168
|
+
|
169
|
+
# assign uuid and version id to the variable
|
170
|
+
v[:uuid] = SecureRandom.uuid
|
171
|
+
v[:version_uuid] = SecureRandom.uuid
|
172
|
+
@variables << v
|
173
|
+
end
|
174
|
+
|
175
|
+
true
|
176
|
+
end
|
177
|
+
|
178
|
+
# Convert the class into a hash. TODO: Make this smart based on the :type eventually
|
179
|
+
#
|
180
|
+
# @return [Hash] Returns the hash
|
181
|
+
def to_hash(version = 1, *a)
|
182
|
+
hash = {}
|
183
|
+
if version == 1
|
184
|
+
instance_variables.each do |var|
|
185
|
+
if var.to_s == '@type'
|
186
|
+
hash[:measure_type] = instance_variable_get(var)
|
187
|
+
elsif var.to_s == '@arguments'
|
188
|
+
hash[:arguments] = []
|
189
|
+
@arguments.each do |a|
|
190
|
+
# This will change in version 2 but right now, if the argument is a variable, then the argument will
|
191
|
+
# be in the variables hash, not the arguments hash.
|
192
|
+
next unless @variables.find { |v| v[:argument][:name] == a[:name] }.nil?
|
193
|
+
hash[:arguments] << a
|
194
|
+
end
|
195
|
+
elsif var.to_s == '@variables'
|
196
|
+
# skip until after looping over instance_variables
|
197
|
+
elsif var.to_s == '@__swigtype__'
|
198
|
+
# skip the swig variables caused by using the same namespace as OpenStudio
|
199
|
+
else
|
200
|
+
hash[var.to_s.delete('@')] = instance_variable_get(var)
|
201
|
+
end
|
202
|
+
|
203
|
+
# TODO: iterate over the variables and create UUIDs, or not?
|
204
|
+
end
|
205
|
+
|
206
|
+
# fix everything to support the legacy version
|
207
|
+
# we need to make a deep copy since multiple calls to .to_hash deletes :type, :mode, etc below
|
208
|
+
# and we still want those args to be avail for future calls, but not end up in the final OSA hash.
|
209
|
+
# without this, the v.delete() below (line ~278-281) will remove :type from @variables.
|
210
|
+
# this would be okay if there was only 1 call to .to_hash. but thats not guaranteed
|
211
|
+
variables_dup = Marshal.load(Marshal.dump(@variables))
|
212
|
+
hash[:variables] = variables_dup
|
213
|
+
|
214
|
+
# Clean up the variables to match the legacy format
|
215
|
+
hash[:variables].each_with_index do |v, index|
|
216
|
+
v[:variable_type] == 'pivot' ? v[:pivot] = true : v[:variable] = true
|
217
|
+
v[:static_value] = v[:argument][:default_value] unless v[:static_value]
|
218
|
+
@variables[index][:static_value] = v[:static_value]
|
219
|
+
|
220
|
+
v[:uncertainty_description] = {}
|
221
|
+
# In Version 0.5 the _uncertain text will be removed from distribution
|
222
|
+
if v[:type] =~ /uncertain/
|
223
|
+
v[:type].delete!('_uncertain')
|
224
|
+
end
|
225
|
+
v[:uncertainty_description][:type] = v[:type]
|
226
|
+
|
227
|
+
# This is not neatly coded. This should be a new object that knows how to write itself out.
|
228
|
+
v[:uncertainty_description][:attributes] = []
|
229
|
+
if v[:type] =~ /discrete/
|
230
|
+
new_h = {}
|
231
|
+
new_h[:name] = 'discrete'
|
232
|
+
|
233
|
+
# check the weights
|
234
|
+
new_h[:values_and_weights] = v.delete(:values).zip(v.delete(:weights)).map { |w| { value: w[0], weight: w[1] } }
|
235
|
+
v[:uncertainty_description][:attributes] << new_h
|
236
|
+
end
|
237
|
+
|
238
|
+
# always write out these attributes
|
239
|
+
v[:uncertainty_description][:attributes] << { name: 'lower_bounds', value: v[:minimum] }
|
240
|
+
v[:uncertainty_description][:attributes] << { name: 'upper_bounds', value: v[:maximum] }
|
241
|
+
v[:uncertainty_description][:attributes] << { name: 'modes', value: v[:mode] }
|
242
|
+
v[:uncertainty_description][:attributes] << { name: 'delta_x', value: v[:step_size] ? v[:step_size] : nil }
|
243
|
+
v[:uncertainty_description][:attributes] << { name: 'stddev', value: v[:standard_deviation] ? v[:standard_deviation] : nil }
|
244
|
+
|
245
|
+
v[:workflow_index] = index
|
246
|
+
|
247
|
+
# remove some remaining items
|
248
|
+
v.delete(:type)
|
249
|
+
v.delete(:mode) if v.key?(:mode)
|
250
|
+
v.delete(:step_size) if v.key?(:step_size)
|
251
|
+
v.delete(:standard_deviation) if v.key?(:standard_deviation)
|
252
|
+
end
|
253
|
+
|
254
|
+
else
|
255
|
+
raise "Do not know how to create the Hash for Version #{version}"
|
256
|
+
end
|
257
|
+
|
258
|
+
hash
|
259
|
+
end
|
260
|
+
|
261
|
+
# Read the workflow item from a measure hash.
|
262
|
+
#
|
263
|
+
# @param instance_name [String] Machine name of the instance
|
264
|
+
# @param instance_display_name [String] Display name of the instance
|
265
|
+
# @param path_to_measure [String] This is the local path to the measure directory, relative or absolute. It is used when zipping up all the measures.
|
266
|
+
# @param hash [Hash] Measure hash in the format of a converted measure.xml hash (from the Analysis Spreadsheet project)
|
267
|
+
# @param options [Hash] Optional arguments
|
268
|
+
# @option options [Boolean] :ignore_not_found Do not raise an exception if the measure could not be found on the machine
|
269
|
+
# @return [Object] Returns the OpenStudio::Analysis::WorkflowStep
|
270
|
+
def self.from_measure_hash(instance_name, instance_display_name, path_to_measure, hash, options = {})
|
271
|
+
if File.directory? path_to_measure
|
272
|
+
path_to_measure = File.join(path_to_measure, 'measure.rb')
|
273
|
+
end
|
274
|
+
|
275
|
+
# verify that the path to the measure is a path and not a file. If it is make it a path
|
276
|
+
if File.exist?(path_to_measure) && File.file?(path_to_measure)
|
277
|
+
path_to_measure = File.dirname(path_to_measure)
|
278
|
+
else
|
279
|
+
raise "Could not find measure '#{instance_name}' in '#{path_to_measure}'" unless options[:ignore_not_found]
|
280
|
+
end
|
281
|
+
|
282
|
+
# Extract the directory
|
283
|
+
path_to_measure_local = path_to_measure
|
284
|
+
path_to_measure = "./measures/#{File.basename(path_to_measure)}"
|
285
|
+
|
286
|
+
# map the BCL hash format into the OpenStudio WorkflowStep format
|
287
|
+
s = OpenStudio::Analysis::WorkflowStep.new
|
288
|
+
|
289
|
+
# add the instance and display name
|
290
|
+
s.name = instance_name
|
291
|
+
s.display_name = instance_display_name
|
292
|
+
|
293
|
+
# definition of the measure
|
294
|
+
s.measure_definition_class_name = hash[:classname]
|
295
|
+
s.measure_definition_directory = path_to_measure
|
296
|
+
s.measure_definition_directory_local = path_to_measure_local
|
297
|
+
s.measure_definition_display_name = hash[:display_name]
|
298
|
+
s.measure_definition_name = hash[:name]
|
299
|
+
# name_xml is not used right now but eventually should be used to compare the hash[:name] and the hash[:name_xml]
|
300
|
+
s.measure_definition_name_xml = hash[:name_xml]
|
301
|
+
s.measure_definition_uuid = hash[:uid]
|
302
|
+
s.measure_definition_version_uuid = hash[:version_id]
|
303
|
+
s.uuid = hash[:uid]
|
304
|
+
s.version_uuid = hash[:version_id]
|
305
|
+
s.description = hash[:description]
|
306
|
+
#s.taxonomy = hash[:taxonomy] #BLB dont do this now
|
307
|
+
|
308
|
+
# do not allow the choice variable_type
|
309
|
+
|
310
|
+
s.type = hash[:measure_type] # this is actually the measure type
|
311
|
+
hash[:arguments]&.each do |arg|
|
312
|
+
puts arg
|
313
|
+
# warn the user to we need to deprecate variable_type and use value_type (which is what os server uses)
|
314
|
+
var_type = arg[:variable_type] ? arg[:variable_type].downcase : arg[:value_type]
|
315
|
+
|
316
|
+
if var_type == 'choice'
|
317
|
+
# WARN the user that the measure had a "choice data type"
|
318
|
+
var_type = 'string'
|
319
|
+
end
|
320
|
+
|
321
|
+
|
322
|
+
if var_type.downcase == 'double'
|
323
|
+
default_value = arg[:default_value].to_f
|
324
|
+
elsif var_type.downcase == 'integer'
|
325
|
+
default_value = arg[:default_value].to_i
|
326
|
+
elsif var_type.downcase == 'boolean'
|
327
|
+
# In some cases a nil default is okay. It is seen as "non-existing" and
|
328
|
+
# needs to be passed through as such.
|
329
|
+
if arg[:default_value].nil?
|
330
|
+
default_value = nil
|
331
|
+
else
|
332
|
+
default_value = (arg[:default_value].downcase == "true") #convert the string 'true'/'false' to boolean
|
333
|
+
end
|
334
|
+
else
|
335
|
+
default_value = arg[:default_value]
|
336
|
+
end
|
337
|
+
|
338
|
+
if !arg[:display_name_short].nil?
|
339
|
+
display_name_short = arg[:display_name_short]
|
340
|
+
else
|
341
|
+
display_name_short = arg[:display_name]
|
342
|
+
end
|
343
|
+
|
344
|
+
s.arguments << {
|
345
|
+
display_name: arg[:display_name],
|
346
|
+
display_name_short: display_name_short,
|
347
|
+
name: arg[:name],
|
348
|
+
value_type: var_type,
|
349
|
+
default_value: default_value,
|
350
|
+
value: default_value
|
351
|
+
}
|
352
|
+
end
|
353
|
+
|
354
|
+
# Load the arguments of variables, but do not make them variables. This format is more about arugments, than variables
|
355
|
+
hash[:variables]&.each do |variable|
|
356
|
+
# add the arguments first
|
357
|
+
s.arguments << {
|
358
|
+
display_name: variable[:argument][:display_name],
|
359
|
+
display_name_short: variable[:argument][:display_name_short],
|
360
|
+
name: variable[:argument][:name],
|
361
|
+
value_type: variable[:argument][:value_type],
|
362
|
+
default_value: variable[:argument][:default_value],
|
363
|
+
value: variable[:argument][:default_value]
|
364
|
+
}
|
365
|
+
end
|
366
|
+
|
367
|
+
s
|
368
|
+
end
|
369
|
+
|
370
|
+
# Read the workflow item from a analysis hash. Can we combine measure hash and analysis hash?
|
371
|
+
#
|
372
|
+
# @param instance_name [String] Machine name of the instance
|
373
|
+
# @param instance_display_name [String] Display name of the instance
|
374
|
+
# @param path_to_measure [String] This is the local path to the measure directroy, relative or absolute. It is used when zipping up all the measures.
|
375
|
+
# @param hash [Hash] Measure hash in the format of the measure.xml converted to JSON (from the Analysis Spreadsheet project)
|
376
|
+
# @param options [Hash] Optional arguments
|
377
|
+
# @option options [Boolean] :ignore_not_found Do not raise an exception if the measure could not be found on the machine
|
378
|
+
# @return [Object] Returns the OpenStudio::Analysis::WorkflowStep
|
379
|
+
def self.from_analysis_hash(instance_name, instance_display_name, path_to_measure, hash, options = {})
|
380
|
+
# TODO: Validate the hash
|
381
|
+
# TODO: validate that the measure exists?
|
382
|
+
|
383
|
+
if File.directory? path_to_measure
|
384
|
+
path_to_measure = File.join(path_to_measure, 'measure.rb')
|
385
|
+
end
|
386
|
+
|
387
|
+
# verify that the path to the measure is a path and not a file. If it is make it a path
|
388
|
+
if File.exist?(path_to_measure) && File.file?(path_to_measure)
|
389
|
+
path_to_measure = File.dirname(path_to_measure)
|
390
|
+
else
|
391
|
+
raise "Could not find measure '#{instance_name}' in '#{path_to_measure}'" unless options[:ignore_not_found]
|
392
|
+
end
|
393
|
+
|
394
|
+
# Extract the directo
|
395
|
+
path_to_measure_local = path_to_measure
|
396
|
+
path_to_measure = "./measures/#{File.basename(path_to_measure)}"
|
397
|
+
|
398
|
+
# map the BCL hash format into the OpenStudio WorkflowStep format
|
399
|
+
s = OpenStudio::Analysis::WorkflowStep.new
|
400
|
+
|
401
|
+
# add the instance and display name
|
402
|
+
s.name = instance_name
|
403
|
+
s.display_name = instance_display_name
|
404
|
+
|
405
|
+
# definition of the measure
|
406
|
+
s.measure_definition_class_name = hash[:measure_definition_class_name]
|
407
|
+
s.measure_definition_directory = path_to_measure
|
408
|
+
s.measure_definition_directory_local = path_to_measure_local
|
409
|
+
s.measure_definition_display_name = hash[:measure_definition_display_name]
|
410
|
+
s.measure_definition_name = hash[:measure_definition_name]
|
411
|
+
# name_xml is not used right now but eventually should be used to compare the hash[:name] and the hash[:name_xml]
|
412
|
+
s.measure_definition_name_xml = hash[:measure_definition_name_xml]
|
413
|
+
s.measure_definition_uuid = hash[:measure_definition_uuid]
|
414
|
+
s.measure_definition_version_uuid = hash[:measure_definition_version_uuid]
|
415
|
+
s.uuid = hash[:uuid] if hash[:uuid]
|
416
|
+
s.version_uuid = hash[:version_uuid] if hash[:version_uuid]
|
417
|
+
s.description = hash[:description] if hash[:description]
|
418
|
+
#s.taxonomy = hash[:taxonomy] if hash[:taxonomy] #BLB dont do this, its a Tags array of Tag
|
419
|
+
|
420
|
+
s.type = hash[:measure_type] # this is actually the measure type
|
421
|
+
hash[:arguments]&.each do |arg|
|
422
|
+
# warn the user to we need to deprecate variable_type and use value_type (which is what os server uses)
|
423
|
+
var_type = arg[:value_type]
|
424
|
+
|
425
|
+
if var_type == 'choice'
|
426
|
+
# WARN the user that the measure had a "choice data type"
|
427
|
+
var_type = 'string'
|
428
|
+
end
|
429
|
+
|
430
|
+
if var_type.downcase == 'double'
|
431
|
+
default_value = arg[:default_value].to_f
|
432
|
+
value = arg[:value].to_f
|
433
|
+
elsif var_type.downcase == 'integer'
|
434
|
+
default_value = arg[:default_value].to_i
|
435
|
+
value = arg[:value].to_i
|
436
|
+
elsif var_type.downcase == 'boolean'
|
437
|
+
default_value = (arg[:default_value].downcase == "true") # convert the string 'true'/'false' to boolean
|
438
|
+
value = (arg[:value].downcase == "true") # convert the string 'true'/'false' to boolean
|
439
|
+
else
|
440
|
+
default_value = arg[:default_value]
|
441
|
+
value = arg[:value]
|
442
|
+
end
|
443
|
+
|
444
|
+
if !arg[:display_name_short].nil?
|
445
|
+
display_name_short = arg[:display_name_short]
|
446
|
+
else
|
447
|
+
display_name_short = arg[:display_name]
|
448
|
+
end
|
449
|
+
|
450
|
+
s.arguments << {
|
451
|
+
display_name: arg[:display_name],
|
452
|
+
display_name_short: display_name_short,
|
453
|
+
name: arg[:name],
|
454
|
+
value_type: var_type,
|
455
|
+
default_value: default_value,
|
456
|
+
value: value
|
457
|
+
}
|
458
|
+
end
|
459
|
+
|
460
|
+
hash[:variables]&.each do |variable|
|
461
|
+
# add the arguments first
|
462
|
+
s.arguments << {
|
463
|
+
display_name: variable[:argument][:display_name],
|
464
|
+
display_name_short: variable[:argument][:display_name_short],
|
465
|
+
name: variable[:argument][:name],
|
466
|
+
value_type: variable[:argument][:value_type],
|
467
|
+
default_value: variable[:argument][:default_value],
|
468
|
+
value: variable[:argument][:default_value]
|
469
|
+
}
|
470
|
+
|
471
|
+
var_options = {}
|
472
|
+
var_options[:variable_type] = variable[:variable_type]
|
473
|
+
var_options[:variable_display_name_short] = variable[:display_name_short]
|
474
|
+
var_options[:static_value] = variable[:static_value]
|
475
|
+
distribution = variable[:uncertainty_description]
|
476
|
+
distribution[:minimum] = variable[:minimum]
|
477
|
+
distribution[:mean] = distribution[:attributes].find { |a| a[:name] == 'modes' }[:value]
|
478
|
+
distribution[:maximum] = variable[:maximum]
|
479
|
+
distribution[:standard_deviation] = distribution[:attributes].find { |a| a[:name] == 'stddev' }[:value]
|
480
|
+
distribution[:step_size] = distribution[:attributes].find { |a| a[:name] == 'delta_x' }[:value]
|
481
|
+
s.make_variable(variable[:argument][:name], variable[:display_name], distribution, var_options)
|
482
|
+
end
|
483
|
+
|
484
|
+
s
|
485
|
+
end
|
486
|
+
|
487
|
+
private
|
488
|
+
|
489
|
+
# validate the arguments of the distribution
|
490
|
+
def distribution_valid?(d)
|
491
|
+
# regardless of uncertainty description the following must be defined
|
492
|
+
raise 'No distribution defined for variable' unless d.key? :type
|
493
|
+
raise 'No minimum defined for variable' unless d.key? :minimum
|
494
|
+
raise 'No maximum defined for variable' unless d.key? :maximum
|
495
|
+
raise 'No mean/mode defined for variable' unless d.key? :mode
|
496
|
+
|
497
|
+
if d[:type] =~ /uniform/
|
498
|
+
# Do we need to tell the user that we don't really need the mean/mode for uniform ?
|
499
|
+
elsif d[:type] =~ /discrete/
|
500
|
+
# require min, max, mode
|
501
|
+
raise 'No values passed for discrete distribution' unless d[:values] || d[:values].empty?
|
502
|
+
if d[:weights]
|
503
|
+
raise 'Weights are not the same length as values' unless d[:values].size == d[:weights].size
|
504
|
+
raise 'Weights do not sum up to one' unless d[:weights].reduce(:+).between?(0.99, 1.01) # allow a small error for now
|
505
|
+
else
|
506
|
+
fraction = 1 / d[:values].size.to_f
|
507
|
+
d[:weights] = [fraction] * d[:values].size
|
508
|
+
end
|
509
|
+
elsif d[:type] =~ /integer_sequence/
|
510
|
+
d[:weights] = 1
|
511
|
+
d[:values] = 1
|
512
|
+
elsif d[:type] =~ /triangle/
|
513
|
+
# requires min, max, mode
|
514
|
+
elsif d[:type] =~ /normal/ # both normal and lognormal
|
515
|
+
# require min, max, mode, stddev
|
516
|
+
raise 'No standard deviation for variable' unless d[:standard_deviation]
|
517
|
+
end
|
518
|
+
|
519
|
+
true
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
end
|