openstudio-analysis 1.3.5 → 1.3.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,857 +1,857 @@
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
+ # 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