openstudio-analysis 1.3.6 → 1.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,857 +1,881 @@
1
- # *******************************************************************************
2
- # OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
3
- # See also https://openstudio.net/license
4
- # *******************************************************************************
5
-
6
- # OpenStudio formulation class handles the generation of the OpenStudio Analysis format.
7
- module OpenStudio
8
- module Analysis
9
- SeedModel = Struct.new(:file)
10
- WeatherFile = Struct.new(:file)
11
-
12
- @@measure_paths = ['./measures']
13
- # List of paths to look for measures when adding them. This currently only is used when loading an
14
- # analysis hash file. It looks in the order of the measure_paths. As soon as it finds one, it stops.
15
- def self.measure_paths
16
- @@measure_paths
17
- end
18
-
19
- def self.measure_paths=(new_array)
20
- @@measure_paths = new_array
21
- end
22
-
23
- class Formulation
24
- attr_reader :seed_model
25
- attr_reader :weather_file
26
- attr_reader :analysis_type
27
- attr_reader :outputs
28
- attr_accessor :display_name
29
- attr_accessor :workflow
30
- attr_accessor :algorithm
31
- attr_accessor :osw_path
32
- attr_accessor :download_zip
33
- attr_accessor :download_reports
34
- attr_accessor :download_osw
35
- attr_accessor :download_osm
36
- attr_accessor :cli_debug
37
- attr_accessor :cli_verbose
38
- attr_accessor :initialize_worker_timeout
39
- attr_accessor :run_workflow_timeout
40
- attr_accessor :upload_results_timeout
41
-
42
- # the attributes below are used for packaging data into the analysis zip file
43
- attr_reader :weather_files
44
- attr_reader :seed_models
45
- attr_reader :worker_inits
46
- attr_reader :worker_finalizes
47
- attr_reader :libraries
48
- attr_reader :server_scripts
49
-
50
- # Create an instance of the OpenStudio::Analysis::Formulation
51
- #
52
- # @param display_name [String] Display name of the project.
53
- # @return [Object] An OpenStudio::Analysis::Formulation object
54
- def initialize(display_name)
55
- @display_name = display_name
56
- @analysis_type = nil
57
- @outputs = []
58
- @workflow = OpenStudio::Analysis::Workflow.new
59
- # Initialize child objects (expect workflow)
60
- @weather_file = WeatherFile.new
61
- @seed_model = SeedModel.new
62
- @algorithm = OpenStudio::Analysis::AlgorithmAttributes.new
63
- @download_zip = true
64
- @download_reports = true
65
- @download_osw = true
66
- @download_osm = true
67
- @cli_debug = "--debug"
68
- @cli_verbose = "--verbose"
69
- @initialize_worker_timeout = 28800
70
- @run_workflow_timeout = 28800
71
- @upload_results_timeout = 28800
72
-
73
- # Analysis Zip attributes
74
- @weather_files = SupportFiles.new
75
- @seed_models = SupportFiles.new
76
- @worker_inits = SupportFiles.new
77
- @worker_finalizes = SupportFiles.new
78
- @libraries = SupportFiles.new
79
- @server_scripts = ServerScripts.new
80
- end
81
-
82
- # Define the type of analysis which is going to be running
83
- #
84
- # @param name [String] Name of the algorithm/analysis. (e.g. rgenoud, lhs, single_run)
85
- # allowed values are ANALYSIS_TYPES = ['spea_nrel', 'rgenoud', 'nsga_nrel', 'lhs', 'preflight',
86
- # 'morris', 'sobol', 'doe', 'fast99', 'ga', 'gaisl',
87
- # 'single_run', 'repeat_run', 'batch_run']
88
- def analysis_type=(value)
89
- if OpenStudio::Analysis::AlgorithmAttributes::ANALYSIS_TYPES.include?(value)
90
- @analysis_type = value
91
- else
92
- raise "Invalid analysis type. Allowed types: #{OpenStudio::Analysis::AlgorithmAttributes::ANALYSIS_TYPES}"
93
- end
94
- end
95
-
96
- # Path to the seed model
97
- #
98
- # @param path [String] Path to the seed model. This should be relative.
99
- def seed_model=(file)
100
- @seed_model[:file] = file
101
- end
102
-
103
- # Path to the weather file (or folder). If it is a folder, then the measures will look for the weather file
104
- # by name in that folder.
105
- #
106
- # @param path [String] Path to the weather file or folder.
107
- def weather_file=(file)
108
- @weather_file[:file] = file
109
- end
110
-
111
- # Set the value for 'download_zip'
112
- #
113
- # @param value [Boolean] The value for 'download_zip'
114
- def download_zip=(value)
115
- if [true, false].include?(value)
116
- @download_zip = value
117
- else
118
- raise ArgumentError, "Invalid value for 'download_zip'. Only true or false allowed."
119
- end
120
- end
121
-
122
- # Set the value for 'download_reports'
123
- #
124
- # @param value [Boolean] The value for 'download_reports'
125
- def download_reports=(value)
126
- if [true, false].include?(value)
127
- @download_reports = value
128
- else
129
- raise ArgumentError, "Invalid value for 'download_reports'. Only true or false allowed."
130
- end
131
- end
132
-
133
- # Set the value for 'download_osw'
134
- #
135
- # @param value [Boolean] The value for 'download_osw'
136
- def download_osw=(value)
137
- if [true, false].include?(value)
138
- @download_osw = value
139
- else
140
- raise ArgumentError, "Invalid value for 'download_osw'. Only true or false allowed."
141
- end
142
- end
143
-
144
- # Set the value for 'download_osm'
145
- #
146
- # @param value [Boolean] The value for 'download_osm'
147
- def download_osm=(value)
148
- if [true, false].include?(value)
149
- @download_osm = value
150
- else
151
- raise ArgumentError, "Invalid value for 'download_osm'. Only true or false allowed."
152
- end
153
- end
154
-
155
- # Set the value for 'cli_debug'
156
- #
157
- # @param value [Boolean] The value for 'cli_debug'
158
- def cli_debug=(value)
159
- @cli_debug = value
160
- end
161
-
162
- # Set the value for 'cli_verbose'
163
- #
164
- # @param value [Boolean] The value for 'cli_verbose'
165
- def cli_verbose=(value)
166
- @cli_verbose = value
167
- end
168
-
169
- # Set the value for 'run_workflow_timeout'
170
- #
171
- # @param value [Integer] The value for 'run_workflow_timeout'
172
- def run_workflow_timeout=(value)
173
- if value.is_a?(Integer)
174
- @run_workflow_timeout = value
175
- else
176
- raise ArgumentError, "Invalid value for 'run_workflow_timeout'. Only integer values allowed."
177
- end
178
- end
179
-
180
- # Set the value for 'initialize_worker_timeout'
181
- #
182
- # @param value [Integer] The value for 'initialize_worker_timeout'
183
- def initialize_worker_timeout=(value)
184
- if value.is_a?(Integer)
185
- @initialize_worker_timeout = value
186
- else
187
- raise ArgumentError, "Invalid value for 'initialize_worker_timeout'. Only integer values allowed."
188
- end
189
- end
190
-
191
- # Set the value for 'upload_results_timeout'
192
- #
193
- # @param value [Integer] The value for 'upload_results_timeout'
194
- def upload_results_timeout=(value)
195
- if value.is_a?(Integer)
196
- @upload_results_timeout = value
197
- else
198
- raise ArgumentError, "Invalid value for 'upload_results_timeout'. Only integer values allowed."
199
- end
200
- end
201
-
202
- # Add an output of interest to the problem formulation
203
- #
204
- # @param output_hash [Hash] Hash of the output variable in the legacy format
205
- # @option output_hash [String] :display_name Name to display
206
- # @option output_hash [String] :display_name_short A shorter display name
207
- # @option output_hash [String] :metadata_id Link to DEnCity ID in which this output corresponds
208
- # @option output_hash [String] :name Unique machine name of the variable. Typically this is measure.attribute
209
- # @option output_hash [String] :export Export the variable to CSV and dataframes from OpenStudio-server
210
- # @option output_hash [String] :visualize Visualize the variable in the plots on OpenStudio-server
211
- # @option output_hash [String] :units Units of the variable as a string
212
- # @option output_hash [String] :variable_type Data type of the variable
213
- # @option output_hash [Boolean] :objective_function Whether or not this output is an objective function. Default: false
214
- # @option output_hash [Integer] :objective_function_index Index of the objective function. Default: nil
215
- # @option output_hash [Float] :objective_function_target Target for the objective function to reach (if defined). Default: nil
216
- # @option output_hash [Float] :scaling_factor How to scale the objective function(s). Default: nil
217
- # @option output_hash [Integer] :objective_function_group If grouping objective functions, then group ID. Default: nil
218
- def add_output(output_hash)
219
- # Check if the name is already been added.
220
- exist = @outputs.find_index { |o| o[:name] == output_hash[:name] }
221
- # if so, update the fields but keep objective_function_index the same
222
- if exist
223
- original = @outputs[exist]
224
- if original[:objective_function] && !output_hash[:objective_function]
225
- return @outputs
226
- end
227
- output = original.merge(output_hash)
228
- output[:objective_function_index] = original[:objective_function_index]
229
- @outputs[exist] = output
230
- else
231
- output = {
232
- units: '',
233
- objective_function: false,
234
- objective_function_index: nil,
235
- objective_function_target: nil,
236
- #set default to nil or 1 if objective_function is true and this is not set
237
- objective_function_group: (output_hash[:objective_function] ? 1 : nil),
238
- scaling_factor: nil,
239
- #set default to false or true if objective_function is true and this is not set
240
- visualize: (output_hash[:objective_function] ? true : false),
241
- metadata_id: nil,
242
- export: true,
243
- }.merge(output_hash)
244
- #set display_name default to be name if its not set
245
- output[:display_name] = output_hash[:display_name] ? output_hash[:display_name] : output_hash[:name]
246
- #set display_name_short default to be display_name if its not set, this can be null if :display_name not set
247
- output[:display_name_short] = output_hash[:display_name_short] ? output_hash[:display_name_short] : output_hash[:display_name]
248
- # if the variable is an objective_function, then increment and
249
- # assign and objective function index
250
- if output[:objective_function]
251
- values = @outputs.select { |o| o[:objective_function] }
252
- output[:objective_function_index] = values.size
253
- end
254
-
255
- @outputs << output
256
- end
257
-
258
- @outputs
259
- end
260
-
261
- # return the machine name of the analysis
262
- def name
263
- @display_name.to_underscore
264
- end
265
-
266
- # return a hash.
267
- #
268
- # @param version [Integer] Version of the format to return
269
- # @return [Hash]
270
- def to_hash(version = 1)
271
- # fail 'Must define an analysis type' unless @analysis_type
272
- if version == 1
273
- h = {
274
- analysis: {
275
- display_name: @display_name,
276
- name: name,
277
- output_variables: @outputs,
278
- problem: {
279
- analysis_type: @analysis_type,
280
- algorithm: algorithm.to_hash(version),
281
- workflow: workflow.to_hash(version)
282
- }
283
- }
284
- }
285
-
286
- if @seed_model[:file]
287
- h[:analysis][:seed] = {
288
- file_type: File.extname(@seed_model[:file]).delete('.').upcase,
289
- path: "./seed/#{File.basename(@seed_model[:file])}"
290
- }
291
- else
292
- h[:analysis][:seed] = nil
293
- end
294
-
295
- # silly catch for if weather_file is not set
296
- wf = nil
297
- if @weather_file[:file]
298
- wf = @weather_file
299
- elsif !@weather_files.empty?
300
- # get the first EPW file (not the first file)
301
- wf = @weather_files.find { |w| File.extname(w[:file]).casecmp('.epw').zero? }
302
- end
303
-
304
- if wf
305
- h[:analysis][:weather_file] = {
306
- file_type: File.extname(wf[:file]).delete('.').upcase,
307
- path: "./weather/#{File.basename(wf[:file])}"
308
- }
309
- else
310
- # log: could not find weather file
311
- warn 'Could not resolve a valid weather file. Check paths to weather files'
312
- end
313
-
314
- h[:analysis][:file_format_version] = version
315
- h[:analysis][:cli_debug] = @cli_debug
316
- h[:analysis][:cli_verbose] = @cli_verbose
317
- h[:analysis][:run_workflow_timeout] = @run_workflow_timeout
318
- h[:analysis][:upload_results_timeout] = @upload_results_timeout
319
- h[:analysis][:initialize_worker_timeout] = @initialize_worker_timeout
320
- h[:analysis][:download_zip] = @download_zip
321
- h[:analysis][:download_reports] = @download_reports
322
- h[:analysis][:download_osw] = @download_osw
323
- h[:analysis][:download_osm] = @download_osm
324
-
325
- #-BLB I dont think this does anything. server_scripts are run if they are in
326
- #the /scripts/analysis or /scripts/data_point directories
327
- #but nothing is ever checked in the OSA.
328
- #
329
- h[:analysis][:server_scripts] = {}
330
-
331
- # This is a hack right now, but after the initial hash is created go back and add in the objective functions
332
- # to the the algorithm as defined in the output_variables list
333
- ofs = @outputs.map { |i| i[:name] if i[:objective_function] }.compact
334
- if h[:analysis][:problem][:algorithm]
335
- h[:analysis][:problem][:algorithm][:objective_functions] = ofs
336
- end
337
-
338
- h
339
- else
340
- raise "Version #{version} not defined for #{self.class} and #{__method__}"
341
- end
342
- end
343
-
344
- # Load the analysis JSON from a hash (with symbolized keys)
345
- def self.from_hash(h, seed_dir = nil, weather_dir = nil)
346
- o = OpenStudio::Analysis::Formulation.new(h[:analysis][:display_name])
347
-
348
- version = 1
349
- if version == 1
350
- h[:analysis][:output_variables].each do |ov|
351
- o.add_output(ov)
352
- end
353
-
354
- o.workflow = OpenStudio::Analysis::Workflow.load(workflow: h[:analysis][:problem][:workflow])
355
-
356
- if weather_dir
357
- o.weather_file "#{weather_path}/#{File.basename(h[:analysis][:weather_file][:path])}"
358
- else
359
- o.weather_file = h[:analysis][:weather_file][:path]
360
- end
361
-
362
- if seed_dir
363
- o.seed_model "#{weather_path}/#{File.basename(h[:analysis][:seed][:path])}"
364
- else
365
- o.seed_model = h[:analysis][:seed][:path]
366
- end
367
- else
368
- raise "Version #{version} not defined for #{self.class} and #{__method__}"
369
- end
370
-
371
- o
372
- end
373
-
374
- # return a hash of the data point with the static variables set
375
- #
376
- # @param version [Integer] Version of the format to return
377
- # @return [Hash]
378
- def to_static_data_point_hash(version = 1)
379
- if version == 1
380
- static_hash = {}
381
- # TODO: this method should be on the workflow step and bubbled up to this interface
382
- @workflow.items.map do |item|
383
- item.variables.map { |v| static_hash[v[:uuid]] = v[:static_value] }
384
- end
385
-
386
- h = {
387
- data_point: {
388
- set_variable_values: static_hash,
389
- status: 'na',
390
- uuid: SecureRandom.uuid
391
- }
392
- }
393
- h
394
- end
395
- end
396
-
397
- # save the file to JSON. Will overwrite the file if it already exists
398
- #
399
- # @param 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.
400
- # @param version [Integer] Version of the format to return
401
- # @return [Boolean]
402
- def save(filename, version = 1)
403
- filename += '.json' if File.extname(filename) == ''
404
-
405
- FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
406
- File.open(filename, 'w') { |f| f << JSON.pretty_generate(to_hash(version)) }
407
-
408
- true
409
- end
410
-
411
- # save the data point JSON with the variables set to the static values. Will overwrite the file if it already exists
412
- #
413
- # @param 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.
414
- # @param version [Integer] Version of the format to return
415
- # @return [Boolean]
416
- def save_static_data_point(filename, version = 1)
417
- filename += '.json' if File.extname(filename) == ''
418
-
419
- FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
420
- File.open(filename, 'w') { |f| f << JSON.pretty_generate(to_static_data_point_hash(version)) }
421
-
422
- true
423
- end
424
-
425
- # save the analysis zip file which contains the measures, seed model, weather file, and init/final scripts
426
- #
427
- # @param 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.
428
- # @return [Boolean]
429
- def save_zip(filename)
430
- filename += '.zip' if File.extname(filename) == ''
431
-
432
- FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
433
-
434
- save_analysis_zip(filename)
435
- end
436
-
437
-
438
- def save_osa_zip(filename, all_weather_files = false, all_seed_files = false)
439
- filename += '.zip' if File.extname(filename) == ''
440
-
441
- FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
442
-
443
- save_analysis_zip_osa(filename, all_weather_files, all_seed_files)
444
- end
445
-
446
- # convert an OSW to an OSA
447
- # osw_filename is the full path to the OSW file
448
- # assumes the associated files and directories are in the same location
449
- # /example.osw
450
- # /measures
451
- # /seeds
452
- # /weather
453
- #
454
- def convert_osw(osw_filename, *measure_paths)
455
- # load OSW so we can loop over [:steps]
456
- if File.exist? osw_filename #will this work for both rel and abs paths?
457
- osw = JSON.parse(File.read(osw_filename), symbolize_names: true)
458
- @osw_path = File.expand_path(osw_filename)
459
- else
460
- raise "Could not find workflow file #{osw_filename}"
461
- end
462
-
463
- # set the weather and seed files if set in OSW
464
- # use :file_paths and look for files to set
465
- if osw[:file_paths]
466
- # seed_model, check if in OSW and not found in path search already
467
- if osw[:seed_file]
468
- osw[:file_paths].each do |path|
469
- puts "searching for seed at: #{File.join(File.expand_path(path), osw[:seed_file])}"
470
- if File.exist?(File.join(File.expand_path(path), osw[:seed_file]))
471
- puts "found seed_file: #{osw[:seed_file]}"
472
- self.seed_model = File.join(File.expand_path(path), osw[:seed_file])
473
- break
474
- end
475
- end
476
- else
477
- warn "osw[:seed_file] is not defined"
478
- end
479
-
480
- # weather_file, check if in OSW and not found in path search already
481
- if osw[:weather_file]
482
- osw[:file_paths].each do |path|
483
- puts "searching for weather at: #{File.join(File.expand_path(path), osw[:weather_file])}"
484
- if File.exist?(File.join(File.expand_path(path), osw[:weather_file]))
485
- puts "found weather_file: #{osw[:weather_file]}"
486
- self.weather_file = File.join(File.expand_path(path), osw[:weather_file])
487
- break
488
- end
489
- end
490
- else
491
- warn "osw[:weather_file] is not defined"
492
- end
493
-
494
- # file_paths is not defined in OSW, so warn and try to set
495
- else
496
- warn ":file_paths is not defined in the OSW."
497
- self.weather_file = osw[:weather_file] ? osw[:weather_file] : nil
498
- self.seed_model = osw[:seed_file] ? osw[:seed_file] : nil
499
- end
500
-
501
- #set analysis_type default to Single_Run
502
- self.analysis_type = 'single_run'
503
-
504
- #loop over OSW 'steps' and map over measures
505
- #there is no name/display name in the OSW. Just measure directory name
506
- #read measure.XML from directory to get name / display name
507
- #increment name by +_1 if there are duplicates
508
- #add measure
509
- #change default args to osw arg values
510
-
511
- osw[:steps].each do |step|
512
- #get measure directory
513
- measure_dir = step[:measure_dir_name]
514
- measure_name = measure_dir.split("measures/").last
515
- puts "measure_dir_name: #{measure_name}"
516
- #get XML
517
- # Loop over possible user defined *measure_paths, including the dir of the osw_filename path and :measure_paths, to find the measure,
518
- # then set measure_dir_abs_path to that path
519
- measure_dir_abs_path = ''
520
- 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') }
521
- puts "searching for xml's in: #{paths_to_parse}"
522
- xml = {}
523
- paths_to_parse.each do |path|
524
- if File.exist?(path)
525
- puts "found xml: #{path}"
526
- xml = parse_measure_xml(path)
527
- if !xml.empty?
528
- measure_dir_abs_path = path
529
- break
530
- end
531
- end
532
- end
533
- raise "measure #{measure_name} not found" if xml.empty?
534
- puts ""
535
- #add check for previous names _+1
536
- count = 1
537
- name = xml[:name]
538
- display_name = xml[:display_name]
539
- loop do
540
- measure = @workflow.find_measure(name)
541
- break if measure.nil?
542
-
543
- count += 1
544
- name = "#{xml[:name]}_#{count}"
545
- display_name = "#{xml[:display_name]} #{count}"
546
- end
547
- #Add Measure to workflow
548
- @workflow.add_measure_from_path(name, display_name, measure_dir_abs_path) #this forces to an absolute path which seems constent with PAT
549
- #@workflow.add_measure_from_path(name, display_name, measure_dir) #this uses the path in the OSW which could be relative
550
-
551
- #Change the default argument values to the osw values
552
- #1. find measure in @workflow
553
- m = @workflow.find_measure(name)
554
- #2. loop thru osw args
555
- #check if the :argument is missing from the measure step, it shouldnt be but just in case give a clean message
556
- if step[:arguments].nil?
557
- raise "measure #{name} step has no arguments: #{step}"
558
- else
559
- step[:arguments].each do |k,v|
560
- #check if argument is in measure, otherwise setting argument_value will crash
561
- raise "OSW arg: #{k} is not in Measure: #{name}" if m.arguments.find_all { |a| a[:name] == k.to_s }.empty?
562
- #set measure arg to match osw arg
563
- m.argument_value(k.to_s, v)
564
- end
565
- end
566
- end
567
- end
568
-
569
- private
570
-
571
- # New format for OSAs. Package up the seed, weather files, and measures
572
- # filename is the name of the file to be saved. ex: analysis.zip
573
- # it will parse the OSA and zip up all the files defined in the workflow
574
- def save_analysis_zip_osa(filename, all_weather_files = false, all_seed_files = false)
575
- def add_directory_to_zip_osa(zipfile, local_directory, relative_zip_directory)
576
- puts "Add Directory #{local_directory}"
577
- Dir[File.join(local_directory.to_s, '**', '**')].each do |file|
578
- puts "Adding File #{file}"
579
- zipfile.add(file.sub(local_directory, relative_zip_directory), file)
580
- end
581
- zipfile
582
- end
583
- #delete file if exists
584
- FileUtils.rm_f(filename) if File.exist?(filename)
585
- #get the full path to the OSW, since all Files/Dirs should be in same directory as the OSW
586
- puts "osw_path: #{@osw_path}"
587
- osw_full_path = File.dirname(File.expand_path(@osw_path))
588
- puts "osw_full_path: #{osw_full_path}"
589
-
590
- Zip::File.open(filename, create: true) do |zf|
591
- ## Weather files
592
- puts 'Adding Support Files: Weather'
593
- # check if weather file exists. use abs path. remove leading ./ from @weather_file path if there.
594
- # check if path is already absolute
595
- if @weather_file[:file]
596
- if File.exists?(@weather_file[:file])
597
- puts " Adding #{@weather_file[:file]}"
598
- #zf.add("weather/#{File.basename(@weather_file[:file])}", @weather_file[:file])
599
- base_name = File.basename(@weather_file[:file], ".*")
600
- puts "base_name: #{base_name}"
601
- # convert backslash on windows to forward slash so Dir.glob will work (in case user uses \)
602
- weather_dirname = File.dirname(@weather_file[:file]).gsub("\\", "/")
603
- puts "weather_dirname: #{weather_dirname}"
604
- # If all_weather_files is true, add all files in the directory to the zip.
605
- # Otherwise, add only files that match the base name.
606
- file_pattern = all_weather_files ? "*" : "#{base_name}.*"
607
- Dir.glob(File.join(weather_dirname, file_pattern)) do |file_path|
608
- puts "file_path: #{file_path}"
609
- puts "zip path: weather/#{File.basename(file_path)}"
610
- zf.add("weather/#{File.basename(file_path)}", file_path)
611
- end
612
- # make absolute path and check for file
613
- elsif File.exists?(File.join(osw_full_path,@weather_file[:file].sub(/^\.\//, '')))
614
- puts " Adding: #{File.join(osw_full_path,@weather_file[:file].sub(/^\.\//, ''))}"
615
- #zf.add("weather/#{File.basename(@weather_file[:file])}", File.join(osw_full_path,@weather_file[:file].sub(/^\.\//, '')))
616
- base_name = File.basename(@weather_file[:file].sub(/^\.\//, ''), ".*")
617
- puts "base_name2: #{base_name}"
618
- weather_dirname = File.dirname(File.join(osw_full_path,@weather_file[:file].sub(/^\.\//, ''))).gsub("\\", "/")
619
- puts "weather_dirname: #{weather_dirname}"
620
- file_pattern = all_weather_files ? "*" : "#{base_name}.*"
621
- Dir.glob(File.join(weather_dirname, file_pattern)) do |file_path|
622
- puts "file_path2: #{file_path}"
623
- puts "zip path2: weather/#{File.basename(file_path)}"
624
- zf.add("weather/#{File.basename(file_path)}", file_path)
625
- end
626
- else
627
- raise "weather_file[:file] does not exist at: #{File.join(osw_full_path,@weather_file[:file].sub(/^\.\//, ''))}"
628
- end
629
- else
630
- warn "weather_file[:file] is not defined"
631
- end
632
-
633
- ## Seed files
634
- puts 'Adding Support Files: Seed Models'
635
- #check if seed file exists. use abs path. remove leading ./ from @seed_model path if there.
636
- #check if path is already absolute
637
- if @seed_model[:file]
638
- if File.exists?(@seed_model[:file])
639
- puts " Adding #{@seed_model[:file]}"
640
- zf.add("seeds/#{File.basename(@seed_model[:file])}", @seed_model[:file])
641
- if all_seed_files
642
- seed_dirname = File.dirname(@seed_model[:file]).gsub("\\", "/")
643
- puts "seed_dirname: #{seed_dirname}"
644
- Dir.glob(File.join(seed_dirname, '*')) do |file_path|
645
- next if file_path == @seed_model[:file] # Skip if the file is the same as @seed_model[:file] so not added twice
646
- puts "file_path: #{file_path}"
647
- puts "zip path: seeds/#{File.basename(file_path)}"
648
- zf.add("seeds/#{File.basename(file_path)}", file_path)
649
- end
650
- end
651
- #make absolute path and check for file
652
- elsif File.exists?(File.join(osw_full_path,@seed_model[:file].sub(/^\.\//, '')))
653
- puts " Adding #{File.join(osw_full_path,@seed_model[:file].sub(/^\.\//, ''))}"
654
- zf.add("seeds/#{File.basename(@seed_model[:file])}", File.join(osw_full_path,@seed_model[:file].sub(/^\.\//, '')))
655
- if all_seed_files
656
- seed_dirname = File.dirname(File.join(osw_full_path,@seed_model[:file].sub(/^\.\//, ''))).gsub("\\", "/")
657
- puts "seed_dirname: #{seed_dirname}"
658
- Dir.glob(File.join(seed_dirname, '*')) do |file_path|
659
- next if file_path == File.join(osw_full_path,@seed_model[:file].sub(/^\.\//, '')) # Skip if the file is the same as @seed_model[:file] so not added twice
660
- puts "file_path: #{file_path}"
661
- puts "zip path: seeds/#{File.basename(file_path)}"
662
- zf.add("seeds/#{File.basename(file_path)}", file_path)
663
- end
664
- end
665
- else
666
- raise "seed_file[:file] does not exist at: #{File.join(osw_full_path,@seed_model[:file].sub(/^\.\//, ''))}"
667
- end
668
- else
669
- warn "seed_file[:file] is not defined"
670
- end
671
-
672
- puts 'Adding Support Files: Libraries'
673
- @libraries.each do |lib|
674
- raise "Libraries must specify their 'library_name' as metadata which becomes the directory upon zip" unless lib[:metadata][:library_name]
675
-
676
- if File.directory? lib[:file]
677
- Dir[File.join(lib[:file], '**', '**')].each do |file|
678
- puts " Adding #{file}"
679
- zf.add(file.sub(lib[:file], "lib/#{lib[:metadata][:library_name]}"), file)
680
- end
681
- else
682
- # just add the file to the zip
683
- puts " Adding #{lib[:file]}"
684
- zf.add(lib[:file], "lib/#{File.basename(lib[:file])}", lib[:file])
685
- end
686
- end
687
-
688
- puts 'Adding Support Files: Server Scripts'
689
- @server_scripts.each_with_index do |f, index|
690
- if f[:init_or_final] == 'finalization'
691
- file_name = 'finalization.sh'
692
- else
693
- file_name = 'initialization.sh'
694
- end
695
- if f[:server_or_data_point] == 'analysis'
696
- new_name = "scripts/analysis/#{file_name}"
697
- else
698
- new_name = "scripts/data_point/#{file_name}"
699
- end
700
- puts " Adding #{f[:file]} as #{new_name}"
701
- zf.add(new_name, f[:file])
702
-
703
- if f[:arguments]
704
- arg_file = "#{(new_name.sub(/\.sh\z/, ''))}.args"
705
- puts " Adding arguments as #{arg_file}"
706
- file = Tempfile.new('arg')
707
- file.write(f[:arguments])
708
- zf.add(arg_file, file)
709
- file.close
710
- end
711
- end
712
-
713
- ## Measures
714
- puts 'Adding Measures'
715
- added_measures = []
716
- # The list of the measures should always be there, but make sure they are uniq
717
- @workflow.each do |measure|
718
- measure_dir_to_add = measure.measure_definition_directory_local
719
-
720
- next if added_measures.include? measure_dir_to_add
721
-
722
- puts " Adding #{File.basename(measure_dir_to_add)}"
723
- Dir[File.join(measure_dir_to_add, '**')].each do |file|
724
- if File.directory?(file)
725
- if File.basename(file) == 'resources' || File.basename(file) == 'lib'
726
- #remove leading ./ from measure_definition_directory path if there.
727
- add_directory_to_zip_osa(zf, file, "#{measure.measure_definition_directory.sub(/^\.\//, '')}/#{File.basename(file)}")
728
- end
729
- else
730
- puts " Adding File #{file}"
731
- #remove leading ./ from measure.measure_definition_directory string with regex .sub(/^\.\//, '')
732
- zip_path_for_measures = file.sub(measure_dir_to_add, measure.measure_definition_directory.sub(/^\.\//, ''))
733
- #puts " zip_path_for_measures: #{zip_path_for_measures}"
734
- zf.add(zip_path_for_measures, file)
735
- end
736
- end
737
-
738
- added_measures << measure_dir_to_add
739
- end
740
- end
741
- end
742
-
743
- #keep legacy function
744
- # Package up the seed, weather files, and measures
745
- def save_analysis_zip(filename)
746
- def add_directory_to_zip(zipfile, local_directory, relative_zip_directory)
747
- # puts "Add Directory #{local_directory}"
748
- Dir[File.join(local_directory.to_s, '**', '**')].each do |file|
749
- # puts "Adding File #{file}"
750
- zipfile.add(file.sub(local_directory, relative_zip_directory), file)
751
- end
752
- zipfile
753
- end
754
-
755
- FileUtils.rm_f(filename) if File.exist?(filename)
756
-
757
- Zip::File.open(filename, Zip::File::CREATE) do |zf|
758
- ## Weather files
759
- # TODO: eventually remove the @weather_file attribute and grab the weather file out
760
- # of the @weather_files
761
- puts 'Adding Support Files: Weather'
762
- if @weather_file[:file] && !@weather_files.files.find { |f| @weather_file[:file] == f[:file] }
763
- # manually add the weather file
764
- puts " Adding #{@weather_file[:file]}"
765
- zf.add("./weather/#{File.basename(@weather_file[:file])}", @weather_file[:file])
766
- end
767
- @weather_files.each do |f|
768
- puts " Adding #{f[:file]}"
769
- zf.add("./weather/#{File.basename(f[:file])}", f[:file])
770
- end
771
-
772
- ## Seed files
773
- puts 'Adding Support Files: Seed Models'
774
- if @seed_model[:file] && !@seed_models.files.find { |f| @seed_model[:file] == f[:file] }
775
- # manually add the weather file
776
- puts " Adding #{@seed_model[:file]}"
777
- zf.add("./seed/#{File.basename(@seed_model[:file])}", @seed_model[:file])
778
- end
779
- @seed_models.each do |f|
780
- puts " Adding #{f[:file]}"
781
- zf.add("./seed/#{File.basename(f[:file])}", f[:file])
782
- end
783
-
784
- puts 'Adding Support Files: Libraries'
785
- @libraries.each do |lib|
786
- raise "Libraries must specify their 'library_name' as metadata which becomes the directory upon zip" unless lib[:metadata][:library_name]
787
-
788
- if File.directory? lib[:file]
789
- Dir[File.join(lib[:file], '**', '**')].each do |file|
790
- puts " Adding #{file}"
791
- zf.add(file.sub(lib[:file], "./lib/#{lib[:metadata][:library_name]}/"), file)
792
- end
793
- else
794
- # just add the file to the zip
795
- puts " Adding #{lib[:file]}"
796
- zf.add(lib[:file], "./lib/#{File.basename(lib[:file])}", lib[:file])
797
- end
798
- end
799
-
800
- puts 'Adding Support Files: Worker Initialization Scripts'
801
- @worker_inits.each_with_index do |f, index|
802
- ordered_file_name = "#{index.to_s.rjust(2, '0')}_#{File.basename(f[:file])}"
803
- puts " Adding #{f[:file]} as #{ordered_file_name}"
804
- zf.add(f[:file].sub(f[:file], "./scripts/worker_initialization//#{ordered_file_name}"), f[:file])
805
-
806
- if f[:metadata][:args]
807
- arg_file = "#{File.basename(ordered_file_name, '.*')}.args"
808
- file = Tempfile.new('arg')
809
- file.write(f[:metadata][:args])
810
- zf.add("./scripts/worker_initialization/#{arg_file}", file)
811
- file.close
812
- end
813
- end
814
-
815
- puts 'Adding Support Files: Worker Finalization Scripts'
816
- @worker_finalizes.each_with_index do |f, index|
817
- ordered_file_name = "#{index.to_s.rjust(2, '0')}_#{File.basename(f[:file])}"
818
- puts " Adding #{f[:file]} as #{ordered_file_name}"
819
- zf.add(f[:file].sub(f[:file], "scripts/worker_finalization/#{ordered_file_name}"), f[:file])
820
-
821
- if f[:metadata][:args]
822
- arg_file = "#{File.basename(ordered_file_name, '.*')}.args"
823
- file = Tempfile.new('arg')
824
- file.write(f[:metadata][:args])
825
- zf.add("scripts/worker_finalization/#{arg_file}", file)
826
- file.close
827
- end
828
- end
829
-
830
- ## Measures
831
- puts 'Adding Measures'
832
- added_measures = []
833
- # The list of the measures should always be there, but make sure they are uniq
834
- @workflow.each do |measure|
835
- measure_dir_to_add = measure.measure_definition_directory_local
836
-
837
- next if added_measures.include? measure_dir_to_add
838
-
839
- puts " Adding #{File.basename(measure_dir_to_add)}"
840
- Dir[File.join(measure_dir_to_add, '**')].each do |file|
841
- if File.directory?(file)
842
- if File.basename(file) == 'resources' || File.basename(file) == 'lib'
843
- add_directory_to_zip(zf, file, "#{measure.measure_definition_directory}/#{File.basename(file)}")
844
- end
845
- else
846
- # puts "Adding File #{file}"
847
- zf.add(file.sub(measure_dir_to_add, "#{measure.measure_definition_directory}/"), file)
848
- end
849
- end
850
-
851
- added_measures << measure_dir_to_add
852
- end
853
- end
854
- end
855
- end
856
- end
857
- end
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
3
+ # See also https://openstudio.net/license
4
+ # *******************************************************************************
5
+
6
+ # OpenStudio formulation class handles the generation of the OpenStudio Analysis format.
7
+ module OpenStudio
8
+ module Analysis
9
+ SeedModel = Struct.new(:file)
10
+ WeatherFile = Struct.new(:file)
11
+
12
+ @@measure_paths = ['./measures']
13
+ # List of paths to look for measures when adding them. This currently only is used when loading an
14
+ # analysis hash file. It looks in the order of the measure_paths. As soon as it finds one, it stops.
15
+ def self.measure_paths
16
+ @@measure_paths
17
+ end
18
+
19
+ def self.measure_paths=(new_array)
20
+ @@measure_paths = new_array
21
+ end
22
+
23
+ class Formulation
24
+ attr_reader :seed_model
25
+ attr_reader :weather_file
26
+ attr_reader :analysis_type
27
+ attr_reader :outputs
28
+ attr_accessor :display_name
29
+ attr_accessor :workflow
30
+ attr_accessor :algorithm
31
+ attr_accessor :osw_path
32
+ attr_accessor :download_zip
33
+ attr_accessor :download_reports
34
+ attr_accessor :download_osw
35
+ attr_accessor :download_osm
36
+ attr_accessor :cli_debug
37
+ attr_accessor :cli_verbose
38
+ attr_accessor :initialize_worker_timeout
39
+ attr_accessor :run_workflow_timeout
40
+ attr_accessor :upload_results_timeout
41
+
42
+
43
+ # the attributes below are used for packaging data into the analysis zip file
44
+ attr_reader :weather_files
45
+ attr_reader :seed_models
46
+ attr_reader :worker_inits
47
+ attr_reader :worker_finalizes
48
+ attr_reader :libraries
49
+ attr_reader :server_scripts
50
+ attr_reader :gem_files
51
+
52
+ # Create an instance of the OpenStudio::Analysis::Formulation
53
+ #
54
+ # @param display_name [String] Display name of the project.
55
+ # @return [Object] An OpenStudio::Analysis::Formulation object
56
+ def initialize(display_name)
57
+ @display_name = display_name
58
+ @analysis_type = nil
59
+ @outputs = []
60
+ @workflow = OpenStudio::Analysis::Workflow.new
61
+ # Initialize child objects (expect workflow)
62
+ @weather_file = WeatherFile.new
63
+ @seed_model = SeedModel.new
64
+ @algorithm = OpenStudio::Analysis::AlgorithmAttributes.new
65
+ @download_zip = true
66
+ @download_reports = true
67
+ @download_osw = true
68
+ @download_osm = true
69
+ @cli_debug = "--debug"
70
+ @cli_verbose = "--verbose"
71
+ @initialize_worker_timeout = 28800
72
+ @run_workflow_timeout = 28800
73
+ @upload_results_timeout = 28800
74
+
75
+ # Analysis Zip attributes
76
+ @weather_files = SupportFiles.new
77
+ @gem_files = SupportFiles.new
78
+ @seed_models = SupportFiles.new
79
+ @worker_inits = SupportFiles.new
80
+ @worker_finalizes = SupportFiles.new
81
+ @libraries = SupportFiles.new
82
+ @server_scripts = ServerScripts.new
83
+ end
84
+
85
+ # Define the type of analysis which is going to be running
86
+ #
87
+ # @param name [String] Name of the algorithm/analysis. (e.g. rgenoud, lhs, single_run)
88
+ # allowed values are ANALYSIS_TYPES = ['spea_nrel', 'rgenoud', 'nsga_nrel', 'lhs', 'preflight',
89
+ # 'morris', 'sobol', 'doe', 'fast99', 'ga', 'gaisl',
90
+ # 'single_run', 'repeat_run', 'batch_run']
91
+ def analysis_type=(value)
92
+ if OpenStudio::Analysis::AlgorithmAttributes::ANALYSIS_TYPES.include?(value)
93
+ @analysis_type = value
94
+ else
95
+ raise "Invalid analysis type. Allowed types: #{OpenStudio::Analysis::AlgorithmAttributes::ANALYSIS_TYPES}"
96
+ end
97
+ end
98
+
99
+ # Path to the seed model
100
+ #
101
+ # @param path [String] Path to the seed model. This should be relative.
102
+ def seed_model=(file)
103
+ @seed_model[:file] = file
104
+ end
105
+
106
+ # Path to the weather file (or folder). If it is a folder, then the measures will look for the weather file
107
+ # by name in that folder.
108
+ #
109
+ # @param path [String] Path to the weather file or folder.
110
+ def weather_file=(file)
111
+ @weather_file[:file] = file
112
+ end
113
+
114
+ # Set the value for 'download_zip'
115
+ #
116
+ # @param value [Boolean] The value for 'download_zip'
117
+ def download_zip=(value)
118
+ if [true, false].include?(value)
119
+ @download_zip = value
120
+ else
121
+ raise ArgumentError, "Invalid value for 'download_zip'. Only true or false allowed."
122
+ end
123
+ end
124
+
125
+ # Set the value for 'download_reports'
126
+ #
127
+ # @param value [Boolean] The value for 'download_reports'
128
+ def download_reports=(value)
129
+ if [true, false].include?(value)
130
+ @download_reports = value
131
+ else
132
+ raise ArgumentError, "Invalid value for 'download_reports'. Only true or false allowed."
133
+ end
134
+ end
135
+
136
+ # Set the value for 'download_osw'
137
+ #
138
+ # @param value [Boolean] The value for 'download_osw'
139
+ def download_osw=(value)
140
+ if [true, false].include?(value)
141
+ @download_osw = value
142
+ else
143
+ raise ArgumentError, "Invalid value for 'download_osw'. Only true or false allowed."
144
+ end
145
+ end
146
+
147
+ # Set the value for 'download_osm'
148
+ #
149
+ # @param value [Boolean] The value for 'download_osm'
150
+ def download_osm=(value)
151
+ if [true, false].include?(value)
152
+ @download_osm = value
153
+ else
154
+ raise ArgumentError, "Invalid value for 'download_osm'. Only true or false allowed."
155
+ end
156
+ end
157
+
158
+ # Set the value for 'cli_debug'
159
+ #
160
+ # @param value [Boolean] The value for 'cli_debug'
161
+ def cli_debug=(value)
162
+ @cli_debug = value
163
+ end
164
+
165
+ # Set the value for 'cli_verbose'
166
+ #
167
+ # @param value [Boolean] The value for 'cli_verbose'
168
+ def cli_verbose=(value)
169
+ @cli_verbose = value
170
+ end
171
+
172
+ # Set the value for 'run_workflow_timeout'
173
+ #
174
+ # @param value [Integer] The value for 'run_workflow_timeout'
175
+ def run_workflow_timeout=(value)
176
+ if value.is_a?(Integer)
177
+ @run_workflow_timeout = value
178
+ else
179
+ raise ArgumentError, "Invalid value for 'run_workflow_timeout'. Only integer values allowed."
180
+ end
181
+ end
182
+
183
+ # Set the value for 'initialize_worker_timeout'
184
+ #
185
+ # @param value [Integer] The value for 'initialize_worker_timeout'
186
+ def initialize_worker_timeout=(value)
187
+ if value.is_a?(Integer)
188
+ @initialize_worker_timeout = value
189
+ else
190
+ raise ArgumentError, "Invalid value for 'initialize_worker_timeout'. Only integer values allowed."
191
+ end
192
+ end
193
+
194
+ # Set the value for 'upload_results_timeout'
195
+ #
196
+ # @param value [Integer] The value for 'upload_results_timeout'
197
+ def upload_results_timeout=(value)
198
+ if value.is_a?(Integer)
199
+ @upload_results_timeout = value
200
+ else
201
+ raise ArgumentError, "Invalid value for 'upload_results_timeout'. Only integer values allowed."
202
+ end
203
+ end
204
+
205
+ # Add an output of interest to the problem formulation
206
+ #
207
+ # @param output_hash [Hash] Hash of the output variable in the legacy format
208
+ # @option output_hash [String] :display_name Name to display
209
+ # @option output_hash [String] :display_name_short A shorter display name
210
+ # @option output_hash [String] :metadata_id Link to DEnCity ID in which this output corresponds
211
+ # @option output_hash [String] :name Unique machine name of the variable. Typically this is measure.attribute
212
+ # @option output_hash [String] :export Export the variable to CSV and dataframes from OpenStudio-server
213
+ # @option output_hash [String] :visualize Visualize the variable in the plots on OpenStudio-server
214
+ # @option output_hash [String] :units Units of the variable as a string
215
+ # @option output_hash [String] :variable_type Data type of the variable
216
+ # @option output_hash [Boolean] :objective_function Whether or not this output is an objective function. Default: false
217
+ # @option output_hash [Integer] :objective_function_index Index of the objective function. Default: nil
218
+ # @option output_hash [Float] :objective_function_target Target for the objective function to reach (if defined). Default: nil
219
+ # @option output_hash [Float] :scaling_factor How to scale the objective function(s). Default: nil
220
+ # @option output_hash [Integer] :objective_function_group If grouping objective functions, then group ID. Default: nil
221
+ def add_output(output_hash)
222
+ # Check if the name is already been added.
223
+ exist = @outputs.find_index { |o| o[:name] == output_hash[:name] }
224
+ # if so, update the fields but keep objective_function_index the same
225
+ if exist
226
+ original = @outputs[exist]
227
+ if original[:objective_function] && !output_hash[:objective_function]
228
+ return @outputs
229
+ end
230
+ output = original.merge(output_hash)
231
+ output[:objective_function_index] = original[:objective_function_index]
232
+ @outputs[exist] = output
233
+ else
234
+ output = {
235
+ units: '',
236
+ objective_function: false,
237
+ objective_function_index: nil,
238
+ objective_function_target: nil,
239
+ #set default to nil or 1 if objective_function is true and this is not set
240
+ objective_function_group: (output_hash[:objective_function] ? 1 : nil),
241
+ scaling_factor: nil,
242
+ #set default to false or true if objective_function is true and this is not set
243
+ visualize: (output_hash[:objective_function] ? true : false),
244
+ metadata_id: nil,
245
+ export: true,
246
+ }.merge(output_hash)
247
+ #set display_name default to be name if its not set
248
+ output[:display_name] = output_hash[:display_name] ? output_hash[:display_name] : output_hash[:name]
249
+ #set display_name_short default to be display_name if its not set, this can be null if :display_name not set
250
+ output[:display_name_short] = output_hash[:display_name_short] ? output_hash[:display_name_short] : output_hash[:display_name]
251
+ # if the variable is an objective_function, then increment and
252
+ # assign and objective function index
253
+ if output[:objective_function]
254
+ values = @outputs.select { |o| o[:objective_function] }
255
+ output[:objective_function_index] = values.size
256
+ end
257
+
258
+ @outputs << output
259
+ end
260
+
261
+ @outputs
262
+ end
263
+
264
+ # return the machine name of the analysis
265
+ def name
266
+ @display_name.to_underscore
267
+ end
268
+
269
+ # return a hash.
270
+ #
271
+ # @param version [Integer] Version of the format to return
272
+ # @return [Hash]
273
+ def to_hash(version = 1)
274
+ # fail 'Must define an analysis type' unless @analysis_type
275
+ if version == 1
276
+ h = {
277
+ analysis: {
278
+ display_name: @display_name,
279
+ name: name,
280
+ output_variables: @outputs,
281
+ problem: {
282
+ analysis_type: @analysis_type,
283
+ algorithm: algorithm.to_hash(version),
284
+ workflow: workflow.to_hash(version)
285
+ }
286
+ }
287
+ }
288
+
289
+ if @seed_model[:file]
290
+ h[:analysis][:seed] = {
291
+ file_type: File.extname(@seed_model[:file]).delete('.').upcase,
292
+ path: "./seed/#{File.basename(@seed_model[:file])}"
293
+ }
294
+ else
295
+ h[:analysis][:seed] = nil
296
+ end
297
+
298
+ # silly catch for if weather_file is not set
299
+ wf = nil
300
+ if @weather_file[:file]
301
+ wf = @weather_file
302
+ elsif !@weather_files.empty?
303
+ # get the first EPW file (not the first file)
304
+ wf = @weather_files.find { |w| File.extname(w[:file]).casecmp('.epw').zero? }
305
+ end
306
+
307
+ if wf
308
+ h[:analysis][:weather_file] = {
309
+ file_type: File.extname(wf[:file]).delete('.').upcase,
310
+ path: "./weather/#{File.basename(wf[:file])}"
311
+ }
312
+ else
313
+ # log: could not find weather file
314
+ warn 'Could not resolve a valid weather file. Check paths to weather files'
315
+ end
316
+
317
+ h[:analysis][:file_format_version] = version
318
+ h[:analysis][:cli_debug] = @cli_debug
319
+ h[:analysis][:cli_verbose] = @cli_verbose
320
+ h[:analysis][:run_workflow_timeout] = @run_workflow_timeout
321
+ h[:analysis][:upload_results_timeout] = @upload_results_timeout
322
+ h[:analysis][:initialize_worker_timeout] = @initialize_worker_timeout
323
+ h[:analysis][:download_zip] = @download_zip
324
+ h[:analysis][:download_reports] = @download_reports
325
+ h[:analysis][:download_osw] = @download_osw
326
+ h[:analysis][:download_osm] = @download_osm
327
+
328
+ # If there are Gemfiles, then set the hash to use the :gemfile. The zip file method will
329
+ # add them to the root of the zip file.
330
+ if @gem_files.size.positive?
331
+ h[:analysis][:gemfile] = true
332
+ else
333
+ h[:analysis][:gemfile] = false
334
+ end
335
+
336
+ #-BLB I dont think this does anything. server_scripts are run if they are in
337
+ #the /scripts/analysis or /scripts/data_point directories
338
+ #but nothing is ever checked in the OSA.
339
+ h[:analysis][:server_scripts] = {}
340
+
341
+ # This is a hack right now, but after the initial hash is created go back and add in the objective functions
342
+ # to the the algorithm as defined in the output_variables list
343
+ ofs = @outputs.map { |i| i[:name] if i[:objective_function] }.compact
344
+ if h[:analysis][:problem][:algorithm]
345
+ h[:analysis][:problem][:algorithm][:objective_functions] = ofs
346
+ end
347
+
348
+
349
+
350
+ h
351
+ else
352
+ raise "Version #{version} not defined for #{self.class} and #{__method__}"
353
+ end
354
+ end
355
+
356
+ # Load the analysis JSON from a hash (with symbolized keys)
357
+ def self.from_hash(h, seed_dir = nil, weather_dir = nil)
358
+ o = OpenStudio::Analysis::Formulation.new(h[:analysis][:display_name])
359
+
360
+ version = 1
361
+ if version == 1
362
+ h[:analysis][:output_variables].each do |ov|
363
+ o.add_output(ov)
364
+ end
365
+
366
+ o.workflow = OpenStudio::Analysis::Workflow.load(workflow: h[:analysis][:problem][:workflow])
367
+
368
+ if weather_dir
369
+ o.weather_file "#{weather_path}/#{File.basename(h[:analysis][:weather_file][:path])}"
370
+ else
371
+ o.weather_file = h[:analysis][:weather_file][:path]
372
+ end
373
+
374
+ if seed_dir
375
+ o.seed_model "#{weather_path}/#{File.basename(h[:analysis][:seed][:path])}"
376
+ else
377
+ o.seed_model = h[:analysis][:seed][:path]
378
+ end
379
+ else
380
+ raise "Version #{version} not defined for #{self.class} and #{__method__}"
381
+ end
382
+
383
+ o
384
+ end
385
+
386
+ # return a hash of the data point with the static variables set
387
+ #
388
+ # @param version [Integer] Version of the format to return
389
+ # @return [Hash]
390
+ def to_static_data_point_hash(version = 1)
391
+ if version == 1
392
+ static_hash = {}
393
+ # TODO: this method should be on the workflow step and bubbled up to this interface
394
+ @workflow.items.map do |item|
395
+ item.variables.map { |v| static_hash[v[:uuid]] = v[:static_value] }
396
+ end
397
+
398
+ h = {
399
+ data_point: {
400
+ set_variable_values: static_hash,
401
+ status: 'na',
402
+ uuid: SecureRandom.uuid
403
+ }
404
+ }
405
+ h
406
+ end
407
+ end
408
+
409
+ # save the file to JSON. Will overwrite the file if it already exists
410
+ #
411
+ # @param 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.
412
+ # @param version [Integer] Version of the format to return
413
+ # @return [Boolean]
414
+ def save(filename, version = 1)
415
+ filename += '.json' if File.extname(filename) == ''
416
+
417
+ FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
418
+ File.open(filename, 'w') { |f| f << JSON.pretty_generate(to_hash(version)) }
419
+
420
+ true
421
+ end
422
+
423
+ # save the data point JSON with the variables set to the static values. Will overwrite the file if it already exists
424
+ #
425
+ # @param 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.
426
+ # @param version [Integer] Version of the format to return
427
+ # @return [Boolean]
428
+ def save_static_data_point(filename, version = 1)
429
+ filename += '.json' if File.extname(filename) == ''
430
+
431
+ FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
432
+ File.open(filename, 'w') { |f| f << JSON.pretty_generate(to_static_data_point_hash(version)) }
433
+
434
+ true
435
+ end
436
+
437
+ # save the analysis zip file which contains the measures, seed model, weather file, and init/final scripts
438
+ #
439
+ # @param 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.
440
+ # @return [Boolean]
441
+ def save_zip(filename)
442
+ filename += '.zip' if File.extname(filename) == ''
443
+
444
+ FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
445
+
446
+ save_analysis_zip(filename)
447
+ end
448
+
449
+
450
+ def save_osa_zip(filename, all_weather_files = false, all_seed_files = false)
451
+ filename += '.zip' if File.extname(filename) == ''
452
+
453
+ FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
454
+
455
+ save_analysis_zip_osa(filename, all_weather_files, all_seed_files)
456
+ end
457
+
458
+ # convert an OSW to an OSA
459
+ # osw_filename is the full path to the OSW file
460
+ # assumes the associated files and directories are in the same location
461
+ # /example.osw
462
+ # /measures
463
+ # /seeds
464
+ # /weather
465
+ #
466
+ def convert_osw(osw_filename, *measure_paths)
467
+ # load OSW so we can loop over [:steps]
468
+ if File.exist? osw_filename #will this work for both rel and abs paths?
469
+ osw = JSON.parse(File.read(osw_filename), symbolize_names: true)
470
+ @osw_path = File.expand_path(osw_filename)
471
+ else
472
+ raise "Could not find workflow file #{osw_filename}"
473
+ end
474
+
475
+ # set the weather and seed files if set in OSW
476
+ # use :file_paths and look for files to set
477
+ if osw[:file_paths]
478
+ # seed_model, check if in OSW and not found in path search already
479
+ if osw[:seed_file]
480
+ osw[:file_paths].each do |path|
481
+ puts "searching for seed at: #{File.join(File.expand_path(path), osw[:seed_file])}"
482
+ if File.exist?(File.join(File.expand_path(path), osw[:seed_file]))
483
+ puts "found seed_file: #{osw[:seed_file]}"
484
+ self.seed_model = File.join(File.expand_path(path), osw[:seed_file])
485
+ break
486
+ end
487
+ end
488
+ else
489
+ warn "osw[:seed_file] is not defined"
490
+ end
491
+
492
+ # weather_file, check if in OSW and not found in path search already
493
+ if osw[:weather_file]
494
+ osw[:file_paths].each do |path|
495
+ puts "searching for weather at: #{File.join(File.expand_path(path), osw[:weather_file])}"
496
+ if File.exist?(File.join(File.expand_path(path), osw[:weather_file]))
497
+ puts "found weather_file: #{osw[:weather_file]}"
498
+ self.weather_file = File.join(File.expand_path(path), osw[:weather_file])
499
+ break
500
+ end
501
+ end
502
+ else
503
+ warn "osw[:weather_file] is not defined"
504
+ end
505
+
506
+ # file_paths is not defined in OSW, so warn and try to set
507
+ else
508
+ warn ":file_paths is not defined in the OSW."
509
+ self.weather_file = osw[:weather_file] ? osw[:weather_file] : nil
510
+ self.seed_model = osw[:seed_file] ? osw[:seed_file] : nil
511
+ end
512
+
513
+ #set analysis_type default to Single_Run
514
+ self.analysis_type = 'single_run'
515
+
516
+ #loop over OSW 'steps' and map over measures
517
+ #there is no name/display name in the OSW. Just measure directory name
518
+ #read measure.XML from directory to get name / display name
519
+ #increment name by +_1 if there are duplicates
520
+ #add measure
521
+ #change default args to osw arg values
522
+
523
+ osw[:steps].each do |step|
524
+ #get measure directory
525
+ measure_dir = step[:measure_dir_name]
526
+ measure_name = measure_dir.split("measures/").last
527
+ puts "measure_dir_name: #{measure_name}"
528
+ #get XML
529
+ # Loop over possible user defined *measure_paths, including the dir of the osw_filename path and :measure_paths, to find the measure,
530
+ # then set measure_dir_abs_path to that path
531
+ measure_dir_abs_path = ''
532
+ 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') }
533
+ puts "searching for xml's in: #{paths_to_parse}"
534
+ xml = {}
535
+ paths_to_parse.each do |path|
536
+ if File.exist?(path)
537
+ puts "found xml: #{path}"
538
+ xml = parse_measure_xml(path)
539
+ if !xml.empty?
540
+ measure_dir_abs_path = path
541
+ break
542
+ end
543
+ end
544
+ end
545
+ raise "measure #{measure_name} not found" if xml.empty?
546
+ puts ""
547
+ #add check for previous names _+1
548
+ count = 1
549
+ name = xml[:name]
550
+ display_name = xml[:display_name]
551
+ loop do
552
+ measure = @workflow.find_measure(name)
553
+ break if measure.nil?
554
+
555
+ count += 1
556
+ name = "#{xml[:name]}_#{count}"
557
+ display_name = "#{xml[:display_name]} #{count}"
558
+ end
559
+ #Add Measure to workflow
560
+ @workflow.add_measure_from_path(name, display_name, measure_dir_abs_path) #this forces to an absolute path which seems constent with PAT
561
+ #@workflow.add_measure_from_path(name, display_name, measure_dir) #this uses the path in the OSW which could be relative
562
+
563
+ #Change the default argument values to the osw values
564
+ #1. find measure in @workflow
565
+ m = @workflow.find_measure(name)
566
+ #2. loop thru osw args
567
+ #check if the :argument is missing from the measure step, it shouldnt be but just in case give a clean message
568
+ if step[:arguments].nil?
569
+ raise "measure #{name} step has no arguments: #{step}"
570
+ else
571
+ step[:arguments].each do |k,v|
572
+ #check if argument is in measure, otherwise setting argument_value will crash
573
+ raise "OSW arg: #{k} is not in Measure: #{name}" if m.arguments.find_all { |a| a[:name] == k.to_s }.empty?
574
+ #set measure arg to match osw arg
575
+ m.argument_value(k.to_s, v)
576
+ end
577
+ end
578
+ end
579
+ end
580
+
581
+ private
582
+
583
+ # New format for OSAs. Package up the seed, weather files, and measures
584
+ # filename is the name of the file to be saved. ex: analysis.zip
585
+ # it will parse the OSA and zip up all the files defined in the workflow
586
+ def save_analysis_zip_osa(filename, all_weather_files = false, all_seed_files = false)
587
+ def add_directory_to_zip_osa(zipfile, local_directory, relative_zip_directory)
588
+ puts "Add Directory #{local_directory}"
589
+ Dir[File.join(local_directory.to_s, '**', '**')].each do |file|
590
+ puts "Adding File #{file}"
591
+ zipfile.add(file.sub(local_directory, relative_zip_directory), file)
592
+ end
593
+ zipfile
594
+ end
595
+ #delete file if exists
596
+ FileUtils.rm_f(filename) if File.exist?(filename)
597
+ #get the full path to the OSW, since all Files/Dirs should be in same directory as the OSW
598
+ puts "osw_path: #{@osw_path}"
599
+ osw_full_path = File.dirname(File.expand_path(@osw_path))
600
+ puts "osw_full_path: #{osw_full_path}"
601
+
602
+ Zip::File.open(filename, create: true) do |zf|
603
+ ## Weather files
604
+ puts 'Adding Support Files: Weather'
605
+ # check if weather file exists. use abs path. remove leading ./ from @weather_file path if there.
606
+ # check if path is already absolute
607
+ if @weather_file[:file]
608
+ if File.exists?(@weather_file[:file])
609
+ puts " Adding #{@weather_file[:file]}"
610
+ #zf.add("weather/#{File.basename(@weather_file[:file])}", @weather_file[:file])
611
+ base_name = File.basename(@weather_file[:file], ".*")
612
+ puts "base_name: #{base_name}"
613
+ # convert backslash on windows to forward slash so Dir.glob will work (in case user uses \)
614
+ weather_dirname = File.dirname(@weather_file[:file]).gsub("\\", "/")
615
+ puts "weather_dirname: #{weather_dirname}"
616
+ # If all_weather_files is true, add all files in the directory to the zip.
617
+ # Otherwise, add only files that match the base name.
618
+ file_pattern = all_weather_files ? "*" : "#{base_name}.*"
619
+ Dir.glob(File.join(weather_dirname, file_pattern)) do |file_path|
620
+ puts "file_path: #{file_path}"
621
+ puts "zip path: weather/#{File.basename(file_path)}"
622
+ zf.add("weather/#{File.basename(file_path)}", file_path)
623
+ end
624
+ # make absolute path and check for file
625
+ elsif File.exists?(File.join(osw_full_path,@weather_file[:file].sub(/^\.\//, '')))
626
+ puts " Adding: #{File.join(osw_full_path,@weather_file[:file].sub(/^\.\//, ''))}"
627
+ #zf.add("weather/#{File.basename(@weather_file[:file])}", File.join(osw_full_path,@weather_file[:file].sub(/^\.\//, '')))
628
+ base_name = File.basename(@weather_file[:file].sub(/^\.\//, ''), ".*")
629
+ puts "base_name2: #{base_name}"
630
+ weather_dirname = File.dirname(File.join(osw_full_path,@weather_file[:file].sub(/^\.\//, ''))).gsub("\\", "/")
631
+ puts "weather_dirname: #{weather_dirname}"
632
+ file_pattern = all_weather_files ? "*" : "#{base_name}.*"
633
+ Dir.glob(File.join(weather_dirname, file_pattern)) do |file_path|
634
+ puts "file_path2: #{file_path}"
635
+ puts "zip path2: weather/#{File.basename(file_path)}"
636
+ zf.add("weather/#{File.basename(file_path)}", file_path)
637
+ end
638
+ else
639
+ raise "weather_file[:file] does not exist at: #{File.join(osw_full_path,@weather_file[:file].sub(/^\.\//, ''))}"
640
+ end
641
+ else
642
+ warn "weather_file[:file] is not defined"
643
+ end
644
+
645
+ ## Seed files
646
+ puts 'Adding Support Files: Seed Models'
647
+ #check if seed file exists. use abs path. remove leading ./ from @seed_model path if there.
648
+ #check if path is already absolute
649
+ if @seed_model[:file]
650
+ if File.exists?(@seed_model[:file])
651
+ puts " Adding #{@seed_model[:file]}"
652
+ zf.add("seeds/#{File.basename(@seed_model[:file])}", @seed_model[:file])
653
+ if all_seed_files
654
+ seed_dirname = File.dirname(@seed_model[:file]).gsub("\\", "/")
655
+ puts "seed_dirname: #{seed_dirname}"
656
+ Dir.glob(File.join(seed_dirname, '*')) do |file_path|
657
+ next if file_path == @seed_model[:file] # Skip if the file is the same as @seed_model[:file] so not added twice
658
+ puts "file_path: #{file_path}"
659
+ puts "zip path: seeds/#{File.basename(file_path)}"
660
+ zf.add("seeds/#{File.basename(file_path)}", file_path)
661
+ end
662
+ end
663
+ #make absolute path and check for file
664
+ elsif File.exists?(File.join(osw_full_path,@seed_model[:file].sub(/^\.\//, '')))
665
+ puts " Adding #{File.join(osw_full_path,@seed_model[:file].sub(/^\.\//, ''))}"
666
+ zf.add("seeds/#{File.basename(@seed_model[:file])}", File.join(osw_full_path,@seed_model[:file].sub(/^\.\//, '')))
667
+ if all_seed_files
668
+ seed_dirname = File.dirname(File.join(osw_full_path,@seed_model[:file].sub(/^\.\//, ''))).gsub("\\", "/")
669
+ puts "seed_dirname: #{seed_dirname}"
670
+ Dir.glob(File.join(seed_dirname, '*')) do |file_path|
671
+ next if file_path == File.join(osw_full_path,@seed_model[:file].sub(/^\.\//, '')) # Skip if the file is the same as @seed_model[:file] so not added twice
672
+ puts "file_path: #{file_path}"
673
+ puts "zip path: seeds/#{File.basename(file_path)}"
674
+ zf.add("seeds/#{File.basename(file_path)}", file_path)
675
+ end
676
+ end
677
+ else
678
+ raise "seed_file[:file] does not exist at: #{File.join(osw_full_path,@seed_model[:file].sub(/^\.\//, ''))}"
679
+ end
680
+ else
681
+ warn "seed_file[:file] is not defined"
682
+ end
683
+
684
+ puts 'Adding Support Files: Libraries'
685
+ @libraries.each do |lib|
686
+ raise "Libraries must specify their 'library_name' as metadata which becomes the directory upon zip" unless lib[:metadata][:library_name]
687
+
688
+ if File.directory? lib[:file]
689
+ Dir[File.join(lib[:file], '**', '**')].each do |file|
690
+ puts " Adding #{file}"
691
+ zf.add(file.sub(lib[:file], "lib/#{lib[:metadata][:library_name]}"), file)
692
+ end
693
+ else
694
+ # just add the file to the zip
695
+ puts " Adding #{lib[:file]}"
696
+ zf.add(lib[:file], "lib/#{File.basename(lib[:file])}", lib[:file])
697
+ end
698
+ end
699
+
700
+ puts 'Adding Support Files: Server Scripts'
701
+ @server_scripts.each_with_index do |f, index|
702
+ if f[:init_or_final] == 'finalization'
703
+ file_name = 'finalization.sh'
704
+ else
705
+ file_name = 'initialization.sh'
706
+ end
707
+ if f[:server_or_data_point] == 'analysis'
708
+ new_name = "scripts/analysis/#{file_name}"
709
+ else
710
+ new_name = "scripts/data_point/#{file_name}"
711
+ end
712
+ puts " Adding #{f[:file]} as #{new_name}"
713
+ zf.add(new_name, f[:file])
714
+
715
+ if f[:arguments]
716
+ arg_file = "#{(new_name.sub(/\.sh\z/, ''))}.args"
717
+ puts " Adding arguments as #{arg_file}"
718
+ file = Tempfile.new('arg')
719
+ file.write(f[:arguments])
720
+ zf.add(arg_file, file)
721
+ file.close
722
+ end
723
+ end
724
+
725
+ puts 'Adding Gemfiles'
726
+ @gem_files.each do |f|
727
+ puts " Adding #{f[:file]}"
728
+ zf.add(File.basename(f[:file]), f[:file])
729
+ end
730
+
731
+ ## Measures
732
+ puts 'Adding Measures'
733
+ added_measures = []
734
+ # The list of the measures should always be there, but make sure they are uniq
735
+ @workflow.each do |measure|
736
+ measure_dir_to_add = measure.measure_definition_directory_local
737
+
738
+ next if added_measures.include? measure_dir_to_add
739
+
740
+ puts " Adding #{File.basename(measure_dir_to_add)}"
741
+ Dir[File.join(measure_dir_to_add, '**')].each do |file|
742
+ if File.directory?(file)
743
+ if File.basename(file) == 'resources' || File.basename(file) == 'lib'
744
+ #remove leading ./ from measure_definition_directory path if there.
745
+ add_directory_to_zip_osa(zf, file, "#{measure.measure_definition_directory.sub(/^\.\//, '')}/#{File.basename(file)}")
746
+ end
747
+ else
748
+ puts " Adding File #{file}"
749
+ #remove leading ./ from measure.measure_definition_directory string with regex .sub(/^\.\//, '')
750
+ zip_path_for_measures = file.sub(measure_dir_to_add, measure.measure_definition_directory.sub(/^\.\//, ''))
751
+ #puts " zip_path_for_measures: #{zip_path_for_measures}"
752
+ zf.add(zip_path_for_measures, file)
753
+ end
754
+ end
755
+
756
+ added_measures << measure_dir_to_add
757
+ end
758
+ end
759
+ end
760
+
761
+ #keep legacy function
762
+ # Package up the seed, weather files, and measures
763
+ def save_analysis_zip(filename)
764
+ def add_directory_to_zip(zipfile, local_directory, relative_zip_directory)
765
+ # puts "Add Directory #{local_directory}"
766
+ Dir[File.join(local_directory.to_s, '**', '**')].each do |file|
767
+ # puts "Adding File #{file}"
768
+ zipfile.add(file.sub(local_directory, relative_zip_directory), file)
769
+ end
770
+ zipfile
771
+ end
772
+
773
+ FileUtils.rm_f(filename) if File.exist?(filename)
774
+
775
+ Zip::File.open(filename, Zip::File::CREATE) do |zf|
776
+ ## Weather files
777
+ # TODO: eventually remove the @weather_file attribute and grab the weather file out
778
+ # of the @weather_files
779
+ puts 'Adding Support Files: Weather'
780
+ if @weather_file[:file] && !@weather_files.files.find { |f| @weather_file[:file] == f[:file] }
781
+ # manually add the weather file
782
+ puts " Adding #{@weather_file[:file]}"
783
+ zf.add("./weather/#{File.basename(@weather_file[:file])}", @weather_file[:file])
784
+ end
785
+ @weather_files.each do |f|
786
+ puts " Adding #{f[:file]}"
787
+ zf.add("./weather/#{File.basename(f[:file])}", f[:file])
788
+ end
789
+
790
+ ## Seed files
791
+ puts 'Adding Support Files: Seed Models'
792
+ if @seed_model[:file] && !@seed_models.files.find { |f| @seed_model[:file] == f[:file] }
793
+ # manually add the weather file
794
+ puts " Adding #{@seed_model[:file]}"
795
+ zf.add("./seed/#{File.basename(@seed_model[:file])}", @seed_model[:file])
796
+ end
797
+ @seed_models.each do |f|
798
+ puts " Adding #{f[:file]}"
799
+ zf.add("./seed/#{File.basename(f[:file])}", f[:file])
800
+ end
801
+
802
+ puts 'Adding Support Files: Libraries'
803
+ @libraries.each do |lib|
804
+ raise "Libraries must specify their 'library_name' as metadata which becomes the directory upon zip" unless lib[:metadata][:library_name]
805
+
806
+ if File.directory? lib[:file]
807
+ Dir[File.join(lib[:file], '**', '**')].each do |file|
808
+ puts " Adding #{file}"
809
+ zf.add(file.sub(lib[:file], "./lib/#{lib[:metadata][:library_name]}/"), file)
810
+ end
811
+ else
812
+ # just add the file to the zip
813
+ puts " Adding #{lib[:file]}"
814
+ zf.add(lib[:file], "./lib/#{File.basename(lib[:file])}", lib[:file])
815
+ end
816
+ end
817
+
818
+ puts 'Adding Support Files: Worker Initialization Scripts'
819
+ @worker_inits.each_with_index do |f, index|
820
+ ordered_file_name = "#{index.to_s.rjust(2, '0')}_#{File.basename(f[:file])}"
821
+ puts " Adding #{f[:file]} as #{ordered_file_name}"
822
+ zf.add(f[:file].sub(f[:file], "./scripts/worker_initialization//#{ordered_file_name}"), f[:file])
823
+
824
+ if f[:metadata][:args]
825
+ arg_file = "#{File.basename(ordered_file_name, '.*')}.args"
826
+ file = Tempfile.new('arg')
827
+ file.write(f[:metadata][:args])
828
+ zf.add("./scripts/worker_initialization/#{arg_file}", file)
829
+ file.close
830
+ end
831
+ end
832
+
833
+ puts 'Adding Support Files: Worker Finalization Scripts'
834
+ @worker_finalizes.each_with_index do |f, index|
835
+ ordered_file_name = "#{index.to_s.rjust(2, '0')}_#{File.basename(f[:file])}"
836
+ puts " Adding #{f[:file]} as #{ordered_file_name}"
837
+ zf.add(f[:file].sub(f[:file], "scripts/worker_finalization/#{ordered_file_name}"), f[:file])
838
+
839
+ if f[:metadata][:args]
840
+ arg_file = "#{File.basename(ordered_file_name, '.*')}.args"
841
+ file = Tempfile.new('arg')
842
+ file.write(f[:metadata][:args])
843
+ zf.add("scripts/worker_finalization/#{arg_file}", file)
844
+ file.close
845
+ end
846
+ end
847
+
848
+ puts 'Adding Gemfiles'
849
+ @gem_files.each do |f|
850
+ puts " Adding #{f}"
851
+ zf.add(File.basename(f), f)
852
+ end
853
+
854
+ ## Measures
855
+ puts 'Adding Measures'
856
+ added_measures = []
857
+ # The list of the measures should always be there, but make sure they are uniq
858
+ @workflow.each do |measure|
859
+ measure_dir_to_add = measure.measure_definition_directory_local
860
+
861
+ next if added_measures.include? measure_dir_to_add
862
+
863
+ puts " Adding #{File.basename(measure_dir_to_add)}"
864
+ Dir[File.join(measure_dir_to_add, '**')].each do |file|
865
+ if File.directory?(file)
866
+ if File.basename(file) == 'resources' || File.basename(file) == 'lib'
867
+ add_directory_to_zip(zf, file, "#{measure.measure_definition_directory}/#{File.basename(file)}")
868
+ end
869
+ else
870
+ # puts "Adding File #{file}"
871
+ zf.add(file.sub(measure_dir_to_add, "#{measure.measure_definition_directory}/"), file)
872
+ end
873
+ end
874
+
875
+ added_measures << measure_dir_to_add
876
+ end
877
+ end
878
+ end
879
+ end
880
+ end
881
+ end