Class: OpenStudio::Analysis::Formulation

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(display_name) ⇒ Object

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

Parameters:

  • display_name (String)

    Display name of the project.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/openstudio/analysis/formulation.rb', line 54

def initialize(display_name)
  @display_name = display_name
  @analysis_type = nil
  @outputs = []
  @workflow = OpenStudio::Analysis::Workflow.new
  # Initialize child objects (expect workflow)
  @weather_file = WeatherFile.new
  @seed_model = SeedModel.new
  @algorithm = OpenStudio::Analysis::AlgorithmAttributes.new
  @download_zip = true
  @download_reports = true
  @download_osw = true
  @download_osm = true
  @cli_debug = "--debug"
  @cli_verbose = "--verbose"
  @initialize_worker_timeout = 28800
  @run_workflow_timeout = 28800
  @upload_results_timeout = 28800

  # Analysis Zip attributes
  @weather_files = SupportFiles.new
  @seed_models = SupportFiles.new
  @worker_inits = SupportFiles.new
  @worker_finalizes = SupportFiles.new
  @libraries = SupportFiles.new
  @server_scripts = ServerScripts.new
end

Instance Attribute Details

#algorithmObject

Returns the value of attribute algorithm.



30
31
32
# File 'lib/openstudio/analysis/formulation.rb', line 30

def algorithm
  @algorithm
end

#analysis_typeObject

Returns the value of attribute analysis_type.



26
27
28
# File 'lib/openstudio/analysis/formulation.rb', line 26

def analysis_type
  @analysis_type
end

#cli_debugObject

Returns the value of attribute cli_debug.



36
37
38
# File 'lib/openstudio/analysis/formulation.rb', line 36

def cli_debug
  @cli_debug
end

#cli_verboseObject

Returns the value of attribute cli_verbose.



37
38
39
# File 'lib/openstudio/analysis/formulation.rb', line 37

def cli_verbose
  @cli_verbose
end

#display_nameObject

Returns the value of attribute display_name.



28
29
30
# File 'lib/openstudio/analysis/formulation.rb', line 28

def display_name
  @display_name
end

#download_osmObject

Returns the value of attribute download_osm.



35
36
37
# File 'lib/openstudio/analysis/formulation.rb', line 35

def download_osm
  @download_osm
end

#download_oswObject

Returns the value of attribute download_osw.



34
35
36
# File 'lib/openstudio/analysis/formulation.rb', line 34

def download_osw
  @download_osw
end

#download_reportsObject

Returns the value of attribute download_reports.



33
34
35
# File 'lib/openstudio/analysis/formulation.rb', line 33

def download_reports
  @download_reports
end

#download_zipObject

Returns the value of attribute download_zip.



32
33
34
# File 'lib/openstudio/analysis/formulation.rb', line 32

def download_zip
  @download_zip
end

#initialize_worker_timeoutObject

Returns the value of attribute initialize_worker_timeout.



38
39
40
# File 'lib/openstudio/analysis/formulation.rb', line 38

def initialize_worker_timeout
  @initialize_worker_timeout
end

#librariesObject (readonly)

Returns the value of attribute libraries.



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

def libraries
  @libraries
end

#osw_pathObject

Returns the value of attribute osw_path.



31
32
33
# File 'lib/openstudio/analysis/formulation.rb', line 31

def osw_path
  @osw_path
end

#outputsObject (readonly)

Returns the value of attribute outputs.



27
28
29
# File 'lib/openstudio/analysis/formulation.rb', line 27

def outputs
  @outputs
end

#run_workflow_timeoutObject

Returns the value of attribute run_workflow_timeout.



39
40
41
# File 'lib/openstudio/analysis/formulation.rb', line 39

def run_workflow_timeout
  @run_workflow_timeout
end

#seed_modelObject

Returns the value of attribute seed_model.



24
25
26
# File 'lib/openstudio/analysis/formulation.rb', line 24

def seed_model
  @seed_model
end

#seed_modelsObject (readonly)

Returns the value of attribute seed_models.



44
45
46
# File 'lib/openstudio/analysis/formulation.rb', line 44

def seed_models
  @seed_models
end

#server_scriptsObject (readonly)

Returns the value of attribute server_scripts.



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

def server_scripts
  @server_scripts
end

#upload_results_timeoutObject

Returns the value of attribute upload_results_timeout.



40
41
42
# File 'lib/openstudio/analysis/formulation.rb', line 40

def upload_results_timeout
  @upload_results_timeout
end

#weather_fileObject

Returns the value of attribute weather_file.



25
26
27
# File 'lib/openstudio/analysis/formulation.rb', line 25

def weather_file
  @weather_file
end

#weather_filesObject (readonly)

the attributes below are used for packaging data into the analysis zip file



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

def weather_files
  @weather_files
end

#worker_finalizesObject (readonly)

Returns the value of attribute worker_finalizes.



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

def worker_finalizes
  @worker_finalizes
end

#worker_initsObject (readonly)

Returns the value of attribute worker_inits.



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

def worker_inits
  @worker_inits
end

#workflowObject

Returns the value of attribute workflow.



29
30
31
# File 'lib/openstudio/analysis/formulation.rb', line 29

def workflow
  @workflow
end

Class Method Details

.from_hash(h, seed_dir = nil, weather_dir = nil) ⇒ Object

Load the analysis JSON from a hash (with symbolized keys)



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
# File 'lib/openstudio/analysis/formulation.rb', line 345

def self.from_hash(h, seed_dir = nil, weather_dir = nil)
  o = OpenStudio::Analysis::Formulation.new(h[:analysis][:display_name])

  version = 1
  if version == 1
    h[:analysis][:output_variables].each do |ov|
      o.add_output(ov)
    end

    o.workflow = OpenStudio::Analysis::Workflow.load(workflow: h[:analysis][:problem][:workflow])

    if weather_dir
      o.weather_file "#{weather_path}/#{File.basename(h[:analysis][:weather_file][:path])}"
    else
      o.weather_file = h[:analysis][:weather_file][:path]
    end

    if seed_dir
      o.seed_model "#{weather_path}/#{File.basename(h[:analysis][:seed][:path])}"
    else
      o.seed_model = h[:analysis][:seed][:path]
    end
  else
    raise "Version #{version} not defined for #{self.class} and #{__method__}"
  end

  o
end

Instance Method Details

#add_directory_to_zip(zipfile, local_directory, relative_zip_directory) ⇒ Object



746
747
748
749
750
751
752
753
# File 'lib/openstudio/analysis/formulation.rb', line 746

def add_directory_to_zip(zipfile, local_directory, relative_zip_directory)
  # puts "Add Directory #{local_directory}"
  Dir[File.join(local_directory.to_s, '**', '**')].each do |file|
    # puts "Adding File #{file}"
    zipfile.add(file.sub(local_directory, relative_zip_directory), file)
  end
  zipfile
end

#add_directory_to_zip_osa(zipfile, local_directory, relative_zip_directory) ⇒ Object



575
576
577
578
579
580
581
582
# File 'lib/openstudio/analysis/formulation.rb', line 575

def add_directory_to_zip_osa(zipfile, local_directory, relative_zip_directory)
  puts "Add Directory #{local_directory}"
  Dir[File.join(local_directory.to_s, '**', '**')].each do |file|
    puts "Adding File #{file}"
    zipfile.add(file.sub(local_directory, relative_zip_directory), file)
  end
  zipfile
end

#add_output(output_hash) ⇒ Object

Add an output of interest to the problem formulation

Parameters:

  • output_hash (Hash)

    Hash of the output variable in the legacy format

Options Hash (output_hash):

  • :display_name (String)

    Name to display

  • :display_name_short (String)

    A shorter display name

  • :metadata_id (String)

    Link to DEnCity ID in which this output corresponds

  • :name (String)

    Unique machine name of the variable. Typically this is measure.attribute

  • :export (String)

    Export the variable to CSV and dataframes from OpenStudio-server

  • :visualize (String)

    Visualize the variable in the plots on OpenStudio-server

  • :units (String)

    Units of the variable as a string

  • :variable_type (String)

    Data type of the variable

  • :objective_function (Boolean)

    Whether or not this output is an objective function. Default: false

  • :objective_function_index (Integer)

    Index of the objective function. Default: nil

  • :objective_function_target (Float)

    Target for the objective function to reach (if defined). Default: nil

  • :scaling_factor (Float)

    How to scale the objective function(s). Default: nil

  • :objective_function_group (Integer)

    If grouping objective functions, then group ID. Default: nil



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
# File 'lib/openstudio/analysis/formulation.rb', line 218

def add_output(output_hash)
  # Check if the name is already been added.
  exist = @outputs.find_index { |o| o[:name] == output_hash[:name] }
  # if so, update the fields but keep objective_function_index the same
  if exist
    original = @outputs[exist]
    if original[:objective_function] && !output_hash[:objective_function]
      return @outputs
    end
    output = original.merge(output_hash)
    output[:objective_function_index] = original[:objective_function_index]
    @outputs[exist] = output
  else
    output = {
      units: '',
      objective_function: false,
      objective_function_index: nil,
      objective_function_target: nil,
      #set default to nil or 1 if objective_function is true and this is not set
      objective_function_group: (output_hash[:objective_function] ? 1 : nil),
      scaling_factor: nil,
      #set default to false or true if objective_function is true and this is not set
      visualize: (output_hash[:objective_function] ? true : false),
      metadata_id: nil,
      export: true,
    }.merge(output_hash)
    #set display_name default to be name if its not set
    output[:display_name] = output_hash[:display_name] ? output_hash[:display_name] : output_hash[:name]
    #set display_name_short default to be display_name if its not set, this can be null if :display_name not set
    output[:display_name_short] = output_hash[:display_name_short] ? output_hash[:display_name_short] : output_hash[:display_name]
    # if the variable is an objective_function, then increment and
    # assign and objective function index
    if output[:objective_function]
      values = @outputs.select { |o| o[:objective_function] }
      output[:objective_function_index] = values.size
    end

    @outputs << output
  end

  @outputs
end

#convert_osw(osw_filename, *measure_paths) ⇒ Object

convert an OSW to an OSA osw_filename is the full path to the OSW file assumes the associated files and directories are in the same location

/example.osw
/measures
/seeds
/weather


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
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# File 'lib/openstudio/analysis/formulation.rb', line 454

def convert_osw(osw_filename, *measure_paths)
  # load OSW so we can loop over [:steps]
  if File.exist? osw_filename  #will this work for both rel and abs paths?
    osw = JSON.parse(File.read(osw_filename), symbolize_names: true)
    @osw_path = File.expand_path(osw_filename)
  else
    raise "Could not find workflow file #{osw_filename}"
  end
  
  # set the weather and seed files if set in OSW
  # use :file_paths and look for files to set
  if osw[:file_paths]        
    # seed_model, check if in OSW and not found in path search already
    if osw[:seed_file]
      osw[:file_paths].each do |path|
        puts "searching for seed at: #{File.join(File.expand_path(path), osw[:seed_file])}"
        if File.exist?(File.join(File.expand_path(path), osw[:seed_file]))
          puts "found seed_file: #{osw[:seed_file]}"
          self.seed_model = File.join(File.expand_path(path), osw[:seed_file])
          break
        end
      end  
    else 
      warn "osw[:seed_file] is not defined"            
    end

    # weather_file, check if in OSW and not found in path search already
    if osw[:weather_file]
      osw[:file_paths].each do |path|
        puts "searching for weather at: #{File.join(File.expand_path(path), osw[:weather_file])}"
        if File.exist?(File.join(File.expand_path(path), osw[:weather_file]))
          puts "found weather_file: #{osw[:weather_file]}"
          self.weather_file = File.join(File.expand_path(path), osw[:weather_file])
          break
        end 
      end
    else 
      warn "osw[:weather_file] is not defined"            
    end

  # file_paths is not defined in OSW, so warn and try to set 
  else
    warn ":file_paths is not defined in the OSW."
    self.weather_file = osw[:weather_file] ? osw[:weather_file] : nil
    self.seed_model = osw[:seed_file] ? osw[:seed_file] : nil
  end
  
  #set analysis_type default to Single_Run
  self.analysis_type = 'single_run'

  #loop over OSW 'steps' and map over measures
  #there is no name/display name in the OSW. Just measure directory name
  #read measure.XML from directory to get name / display name
  #increment name by +_1 if there are duplicates
  #add measure
  #change default args to osw arg values 
  
  osw[:steps].each do |step|
    #get measure directory
    measure_dir = step[:measure_dir_name]
    measure_name = measure_dir.split("measures/").last
    puts "measure_dir_name: #{measure_name}"
    #get XML
    # Loop over possible user defined *measure_paths, including the dir of the osw_filename path and :measure_paths, to find the measure, 
    # then set measure_dir_abs_path to that path
    measure_dir_abs_path = ''
    paths_to_parse = [File.dirname(osw_filename), osw[:measure_paths], *measure_paths].flatten.compact.map { |path| File.join(File.expand_path(path), measure_dir, 'measure.xml') }
    puts "searching for xml's in: #{paths_to_parse}"
    xml = {}
    paths_to_parse.each do |path|
      if File.exist?(path)
        puts "found xml: #{path}"
        xml = parse_measure_xml(path)
        if !xml.empty?
          measure_dir_abs_path = path
          break
        end
      end          
    end
    raise "measure #{measure_name} not found" if xml.empty?
    puts ""          
    #add check for previous names _+1
    count = 1
    name = xml[:name]
    display_name = xml[:display_name]
    loop do
      measure = @workflow.find_measure(name)
      break if measure.nil?

      count += 1
      name = "#{xml[:name]}_#{count}"
      display_name = "#{xml[:display_name]} #{count}"
    end   
    #Add Measure to workflow
    @workflow.add_measure_from_path(name, display_name, measure_dir_abs_path)  #this forces to an absolute path which seems constent with PAT
    #@workflow.add_measure_from_path(name, display_name, measure_dir)  #this uses the path in the OSW which could be relative          
    
    #Change the default argument values to the osw values
    #1. find measure in @workflow
    m = @workflow.find_measure(name)
    #2. loop thru osw args
    #check if the :argument is missing from the measure step, it shouldnt be but just in case give a clean message
    if step[:arguments].nil?
      raise "measure #{name} step has no arguments: #{step}"
    else          
      step[:arguments].each do |k,v|
        #check if argument is in measure, otherwise setting argument_value will crash
        raise "OSW arg: #{k} is not in Measure: #{name}" if m.arguments.find_all { |a| a[:name] == k.to_s }.empty?
        #set measure arg to match osw arg
        m.argument_value(k.to_s, v)
      end
    end
  end
end

#nameObject

return the machine name of the analysis



262
263
264
# File 'lib/openstudio/analysis/formulation.rb', line 262

def name
  @display_name.to_underscore
end

#save(filename, version = 1) ⇒ Boolean

save the file to JSON. Will overwrite the file if it already exists

Parameters:

  • filename (String)

    Name of file to create. It will create the directory and override the file if it exists. If no file extension is given, then it will use .json.

  • version (Integer) (defaults to: 1)

    Version of the format to return

Returns:

  • (Boolean)


402
403
404
405
406
407
408
409
# File 'lib/openstudio/analysis/formulation.rb', line 402

def save(filename, version = 1)
  filename += '.json' if File.extname(filename) == ''

  FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
  File.open(filename, 'w') { |f| f << JSON.pretty_generate(to_hash(version)) }

  true
end

#save_osa_zip(filename, all_weather_files = false, all_seed_files = false) ⇒ Object



438
439
440
441
442
443
444
# File 'lib/openstudio/analysis/formulation.rb', line 438

def save_osa_zip(filename, all_weather_files = false, all_seed_files = false)
  filename += '.zip' if File.extname(filename) == ''

  FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)

  save_analysis_zip_osa(filename, all_weather_files, all_seed_files)
end

#save_static_data_point(filename, version = 1) ⇒ Boolean

save the data point JSON with the variables set to the static values. Will overwrite the file if it already exists

Parameters:

  • filename (String)

    Name of file to create. It will create the directory and override the file if it exists. If no file extension is given, then it will use .json.

  • version (Integer) (defaults to: 1)

    Version of the format to return

Returns:

  • (Boolean)


416
417
418
419
420
421
422
423
# File 'lib/openstudio/analysis/formulation.rb', line 416

def save_static_data_point(filename, version = 1)
  filename += '.json' if File.extname(filename) == ''

  FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
  File.open(filename, 'w') { |f| f << JSON.pretty_generate(to_static_data_point_hash(version)) }

  true
end

#save_zip(filename) ⇒ Boolean

save the analysis zip file which contains the measures, seed model, weather file, and init/final scripts

Parameters:

  • filename (String)

    Name of file to create. It will create the directory and override the file if it exists. If no file extension is given, then it will use .json.

Returns:

  • (Boolean)


429
430
431
432
433
434
435
# File 'lib/openstudio/analysis/formulation.rb', line 429

def save_zip(filename)
  filename += '.zip' if File.extname(filename) == ''

  FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)

  save_analysis_zip(filename)
end

#to_hash(version = 1) ⇒ Hash

return a hash.

Parameters:

  • version (Integer) (defaults to: 1)

    Version of the format to return

Returns:



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
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
# File 'lib/openstudio/analysis/formulation.rb', line 270

def to_hash(version = 1)
  # fail 'Must define an analysis type' unless @analysis_type
  if version == 1
    h = {
      analysis: {
        display_name: @display_name,
        name: name,
        output_variables: @outputs,
        problem: {
          analysis_type: @analysis_type,
          algorithm: algorithm.to_hash(version),
          workflow: workflow.to_hash(version)
        }
      }
    }

    if @seed_model[:file]
      h[:analysis][:seed] = {
        file_type: File.extname(@seed_model[:file]).delete('.').upcase,
        path: "./seed/#{File.basename(@seed_model[:file])}"
      }
    else
      h[:analysis][:seed] = nil
    end

    # silly catch for if weather_file is not set
    wf = nil
    if @weather_file[:file]
      wf = @weather_file
    elsif !@weather_files.empty?
      # get the first EPW file (not the first file)
      wf = @weather_files.find { |w| File.extname(w[:file]).casecmp('.epw').zero? }
    end

    if wf
      h[:analysis][:weather_file] = {
        file_type: File.extname(wf[:file]).delete('.').upcase,
        path: "./weather/#{File.basename(wf[:file])}"
      }
    else
      # log: could not find weather file
      warn 'Could not resolve a valid weather file. Check paths to weather files'
    end

    h[:analysis][:file_format_version] = version
    h[:analysis][:cli_debug] = @cli_debug
    h[:analysis][:cli_verbose] = @cli_verbose
    h[:analysis][:run_workflow_timeout] = @run_workflow_timeout
    h[:analysis][:upload_results_timeout] = @upload_results_timeout
    h[:analysis][:initialize_worker_timeout] = @initialize_worker_timeout
    h[:analysis][:download_zip] = @download_zip
    h[:analysis][:download_reports] = @download_reports
    h[:analysis][:download_osw] = @download_osw
    h[:analysis][:download_osm] = @download_osm

    #-BLB I dont think this does anything. server_scripts are run if they are in 
    #the /scripts/analysis or /scripts/data_point directories
    #but nothing is ever checked in the OSA.
    #
    h[:analysis][:server_scripts] = {}

    # This is a hack right now, but after the initial hash is created go back and add in the objective functions
    # to the the algorithm as defined in the output_variables list
    ofs = @outputs.map { |i| i[:name] if i[:objective_function] }.compact
    if h[:analysis][:problem][:algorithm]
      h[:analysis][:problem][:algorithm][:objective_functions] = ofs
    end

    h
  else
    raise "Version #{version} not defined for #{self.class} and #{__method__}"
  end
end

#to_static_data_point_hash(version = 1) ⇒ Hash

return a hash of the data point with the static variables set

Parameters:

  • version (Integer) (defaults to: 1)

    Version of the format to return

Returns:



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/openstudio/analysis/formulation.rb', line 378

def to_static_data_point_hash(version = 1)
  if version == 1
    static_hash = {}
    # TODO: this method should be on the workflow step and bubbled up to this interface
    @workflow.items.map do |item|
      item.variables.map { |v| static_hash[v[:uuid]] = v[:static_value] }
    end

    h = {
      data_point: {
        set_variable_values: static_hash,
        status: 'na',
        uuid: SecureRandom.uuid
      }
    }
    h
  end
end