Class: OpenStudio::Analysis::WorkflowStep

Inherits:
Object
  • Object
show all
Defined in:
lib/openstudio/analysis/workflow_step.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeObject

Create an instance of the OpenStudio::Analysis::WorkflowStep



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/openstudio/analysis/workflow_step.rb', line 64

def initialize
  @name = ''
  @display_name = ''

  # The type of item being added (ModelMeasure, EnergyPlusMeasure, ...)
  @type = nil

  @measure_definition_class_name = nil
  @measure_definition_directory = nil
  @measure_definition_directory_local = nil
  @measure_definition_display_name = nil
  @measure_definition_name = nil
  @measure_definition_name_xml = nil
  @measure_definition_uuid = nil
  @measure_definition_version_uuid = nil
  @uuid = nil
  @version_uuid = nil
  @description = nil
  #@taxonomy = nil #BLB dont do this now
  @arguments = []

  @arguments << {
    display_name: 'Skip Entire Measure',
    display_name_short: 'Skip',
    name: '__SKIP__',
    value_type: 'boolean',
    default_value: false,
    value: false
  }

  # TODO: eventually the variables should be its own class. This would then be an array of Variable objects.
  @variables = []
end

Instance Attribute Details

#argumentsObject (readonly)

Returns the value of attribute arguments.



58
59
60
# File 'lib/openstudio/analysis/workflow_step.rb', line 58

def arguments
  @arguments
end

#descriptionObject

Returns the value of attribute description.



55
56
57
# File 'lib/openstudio/analysis/workflow_step.rb', line 55

def description
  @description
end

#display_nameObject

Returns the value of attribute display_name.



43
44
45
# File 'lib/openstudio/analysis/workflow_step.rb', line 43

def display_name
  @display_name
end

#measure_definition_class_nameObject

Returns the value of attribute measure_definition_class_name.



45
46
47
# File 'lib/openstudio/analysis/workflow_step.rb', line 45

def measure_definition_class_name
  @measure_definition_class_name
end

#measure_definition_directoryObject

Returns the value of attribute measure_definition_directory.



46
47
48
# File 'lib/openstudio/analysis/workflow_step.rb', line 46

def measure_definition_directory
  @measure_definition_directory
end

#measure_definition_directory_localObject

Returns the value of attribute measure_definition_directory_local.



47
48
49
# File 'lib/openstudio/analysis/workflow_step.rb', line 47

def measure_definition_directory_local
  @measure_definition_directory_local
end

#measure_definition_display_nameObject

Returns the value of attribute measure_definition_display_name.



48
49
50
# File 'lib/openstudio/analysis/workflow_step.rb', line 48

def measure_definition_display_name
  @measure_definition_display_name
end

#measure_definition_nameObject

Returns the value of attribute measure_definition_name.



49
50
51
# File 'lib/openstudio/analysis/workflow_step.rb', line 49

def measure_definition_name
  @measure_definition_name
end

#measure_definition_name_xmlObject

Returns the value of attribute measure_definition_name_xml.



50
51
52
# File 'lib/openstudio/analysis/workflow_step.rb', line 50

def measure_definition_name_xml
  @measure_definition_name_xml
end

#measure_definition_uuidObject

Returns the value of attribute measure_definition_uuid.



51
52
53
# File 'lib/openstudio/analysis/workflow_step.rb', line 51

def measure_definition_uuid
  @measure_definition_uuid
end

#measure_definition_version_uuidObject

Returns the value of attribute measure_definition_version_uuid.



52
53
54
# File 'lib/openstudio/analysis/workflow_step.rb', line 52

def measure_definition_version_uuid
  @measure_definition_version_uuid
end

#nameObject

Returns the value of attribute name.



42
43
44
# File 'lib/openstudio/analysis/workflow_step.rb', line 42

def name
  @name
end

#taxonomyObject

Returns the value of attribute taxonomy.



56
57
58
# File 'lib/openstudio/analysis/workflow_step.rb', line 56

def taxonomy
  @taxonomy
end

#typeObject

Returns the value of attribute type.



41
42
43
# File 'lib/openstudio/analysis/workflow_step.rb', line 41

def type
  @type
end

#uuidObject

Returns the value of attribute uuid.



53
54
55
# File 'lib/openstudio/analysis/workflow_step.rb', line 53

def uuid
  @uuid
end

#variablesObject (readonly)

Returns the value of attribute variables.



59
60
61
# File 'lib/openstudio/analysis/workflow_step.rb', line 59

def variables
  @variables
end

#version_uuidObject

Returns the value of attribute version_uuid.



54
55
56
# File 'lib/openstudio/analysis/workflow_step.rb', line 54

def version_uuid
  @version_uuid
end

Class Method Details

.from_analysis_hash(instance_name, instance_display_name, path_to_measure, hash, options = {}) ⇒ Object

Read the workflow item from a analysis hash. Can we combine measure hash and analysis hash?

Parameters:

  • instance_name (String)

    Machine name of the instance

  • instance_display_name (String)

    Display name of the instance

  • 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.

  • hash (Hash)

    Measure hash in the format of the measure.xml converted to JSON (from the Analysis Spreadsheet project)

  • options (Hash) (defaults to: {})

    Optional arguments

Options Hash (options):

  • :ignore_not_found (Boolean)

    Do not raise an exception if the measure could not be found on the machine

Returns:

  • (Object)

    Returns the OpenStudio::Analysis::WorkflowStep



402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
# File 'lib/openstudio/analysis/workflow_step.rb', line 402

def self.from_analysis_hash(instance_name, instance_display_name, path_to_measure, hash, options = {})
  # TODO: Validate the hash
  # TODO: validate that the measure exists?

  if File.directory? path_to_measure
    path_to_measure = File.join(path_to_measure, 'measure.rb')
  end

  # verify that the path to the measure is a path and not a file. If it is make it a path
  if File.exist?(path_to_measure) && File.file?(path_to_measure)
    path_to_measure = File.dirname(path_to_measure)
  else
    raise "Could not find measure '#{instance_name}' in '#{path_to_measure}'" unless options[:ignore_not_found]
  end

  # Extract the directo
  path_to_measure_local = path_to_measure
  path_to_measure = "./measures/#{File.basename(path_to_measure)}"

  # map the BCL hash format into the OpenStudio WorkflowStep format
  s = OpenStudio::Analysis::WorkflowStep.new

  # add the instance and display name
  s.name = instance_name
  s.display_name = instance_display_name

  # definition of the measure
  s.measure_definition_class_name = hash[:measure_definition_class_name]
  s.measure_definition_directory = path_to_measure
  s.measure_definition_directory_local = path_to_measure_local
  s.measure_definition_display_name = hash[:measure_definition_display_name]
  s.measure_definition_name = hash[:measure_definition_name]
  # name_xml is not used right now but eventually should be used to compare the hash[:name] and the hash[:name_xml]
  s.measure_definition_name_xml = hash[:measure_definition_name_xml]
  s.measure_definition_uuid = hash[:measure_definition_uuid]
  s.measure_definition_version_uuid = hash[:measure_definition_version_uuid]
  s.uuid = hash[:uuid] if hash[:uuid] 
  s.version_uuid = hash[:version_uuid] if hash[:version_uuid]
  s.description = hash[:description] if hash[:description] 
  #s.taxonomy = hash[:taxonomy] if hash[:taxonomy] #BLB dont do this, its a Tags array of Tag

  s.type = hash[:measure_type] # this is actually the measure type
  hash[:arguments]&.each do |arg|
    # warn the user to we need to deprecate variable_type and use value_type (which is what os server uses)
    var_type = arg[:value_type]

    if var_type == 'choice'
      # WARN the user that the measure had a "choice data type"
      var_type = 'string'
    end

    if var_type.downcase == 'double'
      default_value = arg[:default_value].to_f
      value = arg[:value].to_f
    elsif var_type.downcase == 'integer'
      default_value = arg[:default_value].to_i
      value = arg[:value].to_i            
    elsif var_type.downcase == 'boolean'
      default_value = (arg[:default_value].downcase == "true")  #convert the string 'true'/'false' to boolean
      value = (arg[:value].downcase == "true")  #convert the string 'true'/'false' to boolean
    else
      default_value = arg[:default_value]
      value = arg[:value]
    end
    
    if !arg[:display_name_short].nil?
      display_name_short = arg[:display_name_short]
    else
      display_name_short = arg[:display_name]
    end
    
    s.arguments << {
      display_name: arg[:display_name],
      display_name_short: display_name_short,
      name: arg[:name],
      value_type: var_type,
      default_value: default_value,
      value: value
    }
  end

  hash[:variables]&.each do |variable|
    # add the arguments first
    s.arguments << {
      display_name: variable[:argument][:display_name],
      display_name_short: variable[:argument][:display_name_short],
      name: variable[:argument][:name],
      value_type: variable[:argument][:value_type],
      default_value: variable[:argument][:default_value],
      value: variable[:argument][:default_value]
    }

    var_options = {}
    var_options[:variable_type] = variable[:variable_type]
    var_options[:variable_display_name_short] = variable[:display_name_short]
    var_options[:static_value] = variable[:static_value]
    distribution = variable[:uncertainty_description]
    distribution[:minimum] = variable[:minimum]
    distribution[:mean] = distribution[:attributes].find { |a| a[:name] == 'modes' }[:value]
    distribution[:maximum] = variable[:maximum]
    distribution[:standard_deviation] = distribution[:attributes].find { |a| a[:name] == 'stddev' }[:value]
    distribution[:step_size] = distribution[:attributes].find { |a| a[:name] == 'delta_x' }[:value]
    s.make_variable(variable[:argument][:name], variable[:display_name], distribution, var_options)
  end

  s
end

.from_measure_hash(instance_name, instance_display_name, path_to_measure, hash, options = {}) ⇒ Object

Read the workflow item from a measure hash.

Parameters:

  • instance_name (String)

    Machine name of the instance

  • instance_display_name (String)

    Display name of the instance

  • 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.

  • hash (Hash)

    Measure hash in the format of a converted measure.xml hash (from the Analysis Spreadsheet project)

  • options (Hash) (defaults to: {})

    Optional arguments

Options Hash (options):

  • :ignore_not_found (Boolean)

    Do not raise an exception if the measure could not be found on the machine

Returns:

  • (Object)

    Returns the OpenStudio::Analysis::WorkflowStep



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/openstudio/analysis/workflow_step.rb', line 300

def self.from_measure_hash(instance_name, instance_display_name, path_to_measure, hash, options = {})
  if File.directory? path_to_measure
    path_to_measure = File.join(path_to_measure, 'measure.rb')
  end

  # verify that the path to the measure is a path and not a file. If it is make it a path
  if File.exist?(path_to_measure) && File.file?(path_to_measure)
    path_to_measure = File.dirname(path_to_measure)
  else
    raise "Could not find measure '#{instance_name}' in '#{path_to_measure}'" unless options[:ignore_not_found]
  end

  # Extract the directory
  path_to_measure_local = path_to_measure
  path_to_measure = "./measures/#{File.basename(path_to_measure)}"

  # map the BCL hash format into the OpenStudio WorkflowStep format
  s = OpenStudio::Analysis::WorkflowStep.new

  # add the instance and display name
  s.name = instance_name
  s.display_name = instance_display_name

  # definition of the measure
  s.measure_definition_class_name = hash[:classname]
  s.measure_definition_directory = path_to_measure
  s.measure_definition_directory_local = path_to_measure_local
  s.measure_definition_display_name = hash[:display_name]
  s.measure_definition_name = hash[:name]
  # name_xml is not used right now but eventually should be used to compare the hash[:name] and the hash[:name_xml]
  s.measure_definition_name_xml = hash[:name_xml]
  s.measure_definition_uuid = hash[:uid]
  s.measure_definition_version_uuid = hash[:version_id]
  s.uuid = hash[:uid]
  s.version_uuid = hash[:version_id]
  s.description = hash[:description]
  #s.taxonomy = hash[:taxonomy]   #BLB dont do this now

  # do not allow the choice variable_type

  s.type = hash[:measure_type] # this is actually the measure type
  hash[:arguments]&.each do |arg|
    # warn the user to we need to deprecate variable_type and use value_type (which is what os server uses)
    var_type = arg[:variable_type] ? arg[:variable_type].downcase : arg[:value_type]

    if var_type == 'choice'
      # WARN the user that the measure had a "choice data type"
      var_type = 'string'
    end

    
    if var_type.downcase == 'double'
      default_value = arg[:default_value].to_f
    elsif var_type.downcase == 'integer'
      default_value = arg[:default_value].to_i  
    elsif var_type.downcase == 'boolean'
      default_value = (arg[:default_value].downcase == "true")  #convert the string 'true'/'false' to boolean
    else
      default_value = arg[:default_value]
    end
    
    if !arg[:display_name_short].nil?
      display_name_short = arg[:display_name_short]
    else
      display_name_short = arg[:display_name]
    end
    
    s.arguments << {
      display_name: arg[:display_name],
      display_name_short: display_name_short,
      name: arg[:name],
      value_type: var_type,
      default_value: default_value,
      value: default_value
    }
  end

  # Load the arguments of variables, but do not make them variables. This format is more about arugments, than variables
  hash[:variables]&.each do |variable|
    # add the arguments first
    s.arguments << {
      display_name: variable[:argument][:display_name],
      display_name_short: variable[:argument][:display_name_short],
      name: variable[:argument][:name],
      value_type: variable[:argument][:value_type],
      default_value: variable[:argument][:default_value],
      value: variable[:argument][:default_value]
    }
  end

  s
end

Instance Method Details

#argument_namesArray

Return an array of the argument names

Returns:

  • (Array)

    Listing of argument names.



101
102
103
# File 'lib/openstudio/analysis/workflow_step.rb', line 101

def argument_names
  @arguments.map { |a| a[:name] }
end

#argument_value(argument_name, value) ⇒ Boolean

Set the value of an argument to ‘value`. The user is required to know the data type and pass it in accordingly

Parameters:

  • argument_name (String)

    The machine name of the argument that you want to set the value to

  • value

    The value to assign the argument

Returns:

  • (Boolean)

    True/false if it assigned it



110
111
112
113
114
115
116
117
118
119
120
# File 'lib/openstudio/analysis/workflow_step.rb', line 110

def argument_value(argument_name, value)
  a = @arguments.find_all { |a| a[:name] == argument_name }
  raise "could not find argument_name of #{argument_name} in measure #{name}. Valid argument names are #{argument_names}." if a.empty?
  raise "more than one argument with the same name of #{argument_name} in measure #{name}" if a.size > 1

  a = a.first

  a[:value] = value

  a[:value] == value
end

#find_variable_by_name(name) ⇒ Object

Return a variable by its name.

Parameters:

  • name (String)

    Name of the arugment that makes the variable.

Returns:

  • (Object)

    The variable object



126
127
128
129
130
# File 'lib/openstudio/analysis/workflow_step.rb', line 126

def find_variable_by_name(name)
  v = @variables.find { |v| v[:argument][:name] == name }

  v
end

#make_variable(argument_name, variable_display_name, distribution, options = {}) ⇒ Boolean

Tag a measure’s argument as a variable.

Parameters:

  • 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.

  • 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.

  • distribution (Hash)

    Hash describing the distribution of the variable.

  • variable_type (String)

    What type of variable, variable or pivot. Typically this is variable.

  • options (Hash) (defaults to: {})

    Values that define the variable.

Options Hash (distribution):

  • :type (String)

    Type of distribution. ‘discrete`, `uniform`, `triangle`, `normal`, `lognormal`, `integer_sequence`

  • :units (String)

    Units of the variable. This is legacy as previous OpenStudio measures did not specify units separately.

  • :minimum (String)

    Minimum value of the distribution, required for all distributions

  • :maximum (String)

    Maximum value of the distribution, required for all distributions

  • :standard_deviation (String)

    The standard deviation, if the distribution requires it.

  • :mode (String)

    The mean/mode of the distribution (if required)

  • :mean (String)

    Alias for the mode. If this is used it will override the mode

  • :relation_to_output (String)

    How is the variable correlates to the output of interest (for continuous distributions)

  • :step_size (String)

    Minimum step size (delta_x) of the variable (for continuous distributions)

  • :values (String)

    If discrete, then the values to run

  • :weights (String)

    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.

Options Hash (options):

  • :variable_type (String)

    The type of variable, ‘variable` or `pivot`. By default this is a variable.

  • :variable_display_name_short (String)

    The short display name of the variable. Will be defaulted to the variable_display_name if not passed

Returns:

  • (Boolean)

    True / False if it was able to tag the measure argument



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/openstudio/analysis/workflow_step.rb', line 163

def make_variable(argument_name, variable_display_name, distribution, options = {})
  options = { variable_type: 'variable' }.merge(options)
  distribution[:mode] = distribution[:mean] if distribution.key? :mean

  raise "Set the static value in the options 'options[:static_value]', not the distribution" if distribution[:static_value]

  a = @arguments.find_all { |a| a[:name] == argument_name }
  raise "could not find argument_name of #{argument_name} in measure #{name}. Valid argument names are #{argument_names}." if a.empty?
  raise "more than one argument with the same name of #{argument_name} in measure #{name}" if a.size > 1

  if distribution_valid?(distribution)
    # grab the argument hash
    a = a.first

    # add more information to the argument
    v = {}
    v[:argument] = a
    v[:display_name] = variable_display_name
    v[:display_name_short] = options[:variable_display_name_short] ? options[:variable_display_name_short] : variable_display_name
    v[:variable_type] = options[:variable_type]

    v[:type] = distribution[:type]
    v[:units] = distribution[:units] ? distribution[:units] : nil
    v[:minimum] = distribution[:minimum]
    v[:maximum] = distribution[:maximum]
    v[:relation_to_output] = distribution[:relation_to_output] ? distribution[:relation_to_output] : nil
    v[:mode] = distribution[:mode]
    v[:static_value] = options[:static_value] if options[:static_value]
    # TODO: Static value should be named default value or just value

    # Always look for these attributes even if the distribution does not need them
    v[:weights] = distribution[:weights] if distribution[:weights]
    v[:values] = distribution[:values] if distribution[:values]
    v[:standard_deviation] = distribution[:standard_deviation] if distribution[:standard_deviation]
    v[:step_size] = distribution[:step_size] ? distribution[:step_size] : nil

    # assign uuid and version id to the variable
    v[:uuid] = SecureRandom.uuid
    v[:version_uuid] = SecureRandom.uuid
    @variables << v
  end

  true
end

#remove_variable(variable_name) ⇒ Object



132
133
134
135
136
137
138
139
140
# File 'lib/openstudio/analysis/workflow_step.rb', line 132

def remove_variable(variable_name)
  v_index = @variables.find_index { |v| v[:argument][:name] == variable_name }
  if v_index
    @variables.delete_at(v_index)
    return true
  else
    return false
  end
end

#to_hash(version = 1, *a) ⇒ Hash

Convert the class into a hash. TODO: Make this smart based on the :type eventually

Returns:

  • (Hash)

    Returns the hash



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/openstudio/analysis/workflow_step.rb', line 211

def to_hash(version = 1, *a)
  hash = {}
  if version == 1
    instance_variables.each do |var|
      if var.to_s == '@type'
        hash[:measure_type] = instance_variable_get(var)
      elsif var.to_s == '@arguments'
        hash[:arguments] = []
        @arguments.each do |a|
          # This will change in version 2 but right now, if the argument is a variable, then the argument will
          # be in the variables hash, not the arguments hash.
          next unless @variables.find { |v| v[:argument][:name] == a[:name] }.nil?
          hash[:arguments] << a
        end
      elsif var.to_s == '@variables'
        # skip until after looping over instance_variables
      elsif var.to_s == '@__swigtype__'
        # skip the swig variables caused by using the same namespace as OpenStudio
      else
        hash[var.to_s.delete('@')] = instance_variable_get(var)
      end

      # TODO: iterate over the variables and create UUIDs, or not?
    end

    # fix everything to support the legacy version
    # we need to make a deep copy since multiple calls to .to_hash deletes :type, :mode, etc below
    # and we still want those args to be avail for future calls, but not end up in the final OSA hash.
    # without this, the v.delete() below (line ~278-281) will remove :type from @variables. 
    # this would be okay if there was only 1 call to .to_hash. but thats not guaranteed
    variables_dup = Marshal.load(Marshal.dump(@variables))
    hash[:variables] = variables_dup

    # Clean up the variables to match the legacy format
    hash[:variables].each_with_index do |v, index|
      v[:variable_type] == 'pivot' ? v[:pivot] = true : v[:variable] = true
      v[:static_value] = v[:argument][:default_value] unless v[:static_value]
      @variables[index][:static_value] = v[:static_value]

      v[:uncertainty_description] = {}
      # In Version 0.5 the _uncertain text will be removed from distribution
      if v[:type] =~ /uncertain/
        v[:type].delete!('_uncertain')
      end
      v[:uncertainty_description][:type] = v[:type]

      # This is not neatly coded. This should be a new object that knows how to write itself out.
      v[:uncertainty_description][:attributes] = []
      if v[:type] =~ /discrete/
        new_h = {}
        new_h[:name] = 'discrete'

        # check the weights
        new_h[:values_and_weights] = v.delete(:values).zip(v.delete(:weights)).map { |w| { value: w[0], weight: w[1] } }
        v[:uncertainty_description][:attributes] << new_h
      end

      # always write out these attributes
      v[:uncertainty_description][:attributes] << { name: 'lower_bounds', value: v[:minimum] }
      v[:uncertainty_description][:attributes] << { name: 'upper_bounds', value: v[:maximum] }
      v[:uncertainty_description][:attributes] << { name: 'modes', value: v[:mode] }
      v[:uncertainty_description][:attributes] << { name: 'delta_x', value: v[:step_size] ? v[:step_size] : nil }
      v[:uncertainty_description][:attributes] << { name: 'stddev', value: v[:standard_deviation] ? v[:standard_deviation] : nil }

      v[:workflow_index] = index

      # remove some remaining items
      v.delete(:type)
      v.delete(:mode) if v.key?(:mode)
      v.delete(:step_size) if v.key?(:step_size)
      v.delete(:standard_deviation) if v.key?(:standard_deviation)
    end

  else
    raise "Do not know how to create the Hash for Version #{version}"
  end

  hash
end