openstudio-analysis 0.4.4 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e28750e00afea6e55e5852a8629ddf0e7cec2760
4
- data.tar.gz: 8fdc2d023f6c151220fda7b06e5f01930cb4b379
3
+ metadata.gz: a460438de48ec2ba07da54a58e5c2b4df31495b0
4
+ data.tar.gz: 710522dfd0cb39a17a6f41dff61a11caf250776e
5
5
  SHA512:
6
- metadata.gz: 00526e6a206c1721906344469701b526dd1cfa0d0693c3be8f79a5240239321f6339a010a149de13bf3edb673a07e4188afdff31e3dbbc3b27a0c35b8e63e624
7
- data.tar.gz: 834f5922be6edb2f5cbf550c1e0d0bb6daa0ed3109538d440ebec09195efc753892238ba80cd9707d7790330760da42385516d0836c385eba0059cc6259087da
6
+ metadata.gz: 97237d9a0fdb49cc9b6e5cb400eaf7dd51b503de37bb457689b38ccbe7e2f977ac9cd8ed969bc8df5ce059bc2ba156ceb3f98b9846ead7002881ba5ab20e0959
7
+ data.tar.gz: f7a7cc8e3191c816cc41ed3b1fdfd3f8739eef45d4828c6f43b0ba62ea097214da807024bf249b77e48e4825e39b349b7467e1b6855551212d7186f2ab401e6d
@@ -1,6 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.0.0
4
- - 1.9.3
5
- - jruby-19mode
6
-
3
+ - 2.0.0-p451
4
+ - 2.1
@@ -1,6 +1,13 @@
1
1
  OpenStudio Analysis Gem Change Log
2
2
  ==================================
3
3
 
4
+ Version Unreleased
5
+ ------------------
6
+ * Add diag analysis type to server_api run method
7
+ * Remove support for Rubies < 2.0.
8
+ * Add json extension to formulation name upon save if none exists
9
+ * Add zip extension to formulation zip upon save if none exists
10
+
4
11
  Version 0.4.4
5
12
  ------------------
6
13
  * Increment objective function count only if they are true
data/Gemfile CHANGED
@@ -2,9 +2,12 @@ source 'http://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
+ gem 'openstudio-aws', github: 'NREL/OpenStudio-aws-gem', branch: 'develop'
6
+ gem 'colored', '~> 1.2'
7
+
5
8
  group :test do
6
9
  gem 'coveralls', require: false
7
- gem 'rspec', '~> 3.3.0'
10
+ gem 'rspec', '~> 3.4'
8
11
  gem 'ci_reporter_rspec'
9
12
  gem 'rubocop', '~> 0.31'
10
13
  gem 'rubocop-checkstyle_formatter', '~> 0.2'
data/README.md CHANGED
@@ -19,35 +19,52 @@ There are two ways to create an OpenStudio Analysis description:
19
19
  * Programmatically
20
20
 
21
21
  ```
22
- analysis = OpenStudio::Analysis.create
23
- analysis.seed_model = "local/dir/seed.osm"
24
- analysis.name = "Analysis Name"
22
+ analysis = OpenStudio::Analysis.create('Analysis Name')
23
+ analysis.seed_model = 'local/dir/seed.osm'
24
+ analysis.weather_file = 'local/dir/USA_CO_Golden-NREL.724666_TMY3.epw'
25
25
 
26
26
  # override existing workflow from a file by
27
27
  analysis.workflow = OpenStudio::Analysis::Workflow.load_from_file(...)
28
28
 
29
29
  # add measures to the workflow
30
30
  wf = analysis.workflow
31
- wf.add_measure_from_path("path_to_measure")
32
- wf.add_measure_from_path("path_to_measure_2")
33
-
34
- # or allow the system to search for the measure based on default_measure_paths
35
- OpenStudio::Analysis.measure_paths = ['measures', '../project_specific_measures']
36
- wf.add_measure_by_name('measure_name')
31
+ def add_measure_from_path(instance_name, instance_display_name, local_path_to_measure)
32
+ wf.add_measure_from_path('instance_name', 'Display name', 'path_to_measure')
33
+ wf.add_measure_from_path('instance_name_2', 'Display name two', 'path_to_measure_2')
37
34
 
38
35
  # make a measure's argument a variable
39
- m = wf.add_measure("path_to_measure_3")
36
+ m = wf.add_measure_from_path('instance_name_3', 'Display name three', 'path_to_measure_3')
40
37
  m.make_variable('variable_argument_name', 'discrete')
41
38
 
42
- m = wf.add_measure('path_to_measure_4')
39
+ m = wf.add_measure_from_path('instance_name_4', 'Display name four', 'path_to_measure_4')
43
40
  m.make_variable('variable_argument_name', 'pivot')
44
- m.argument_static_value('variable_argument_name', value)
41
+ m.argument_value('variable_argument_name', value)
45
42
 
43
+ # Save off the analysis files and a static data point
44
+ run_dir = 'local/run'
45
+ analysis.save("#{run_dir}/analysis.json")
46
+ analysis.save_zip("#{run_dir}/analysis.zip")
47
+ analysis.save_static_data_point("#{run_dir}/data_point.zip")
46
48
  ```
47
49
 
50
+ If you would like to run the data point, then you can use the OpenStudio-workflow gem.
48
51
 
49
- ## Testing
52
+ ```
53
+ require 'openstudio-workflow'
54
+
55
+ run_dir = 'local/run'
56
+ OpenStudio::Workflow.extract_archive("#{run_dir}/analysis.zip", run_dir)
57
+
58
+ options = {
59
+ problem_filename: 'analysis.json',
60
+ datapoint_filename: 'data_point.json',
61
+ analysis_root_path: run_dir
62
+ }
63
+ k = OpenStudio::Workflow.load 'Local', run_dir, options
64
+ k.run
65
+ ```
50
66
 
67
+ ## Testing
51
68
 
52
69
  This gem used RSpec for testing. To test simply run `rspec` at the command line.
53
70
 
@@ -29,6 +29,7 @@ require 'openstudio/analysis/algorithm_attributes'
29
29
 
30
30
  # translators
31
31
  require 'openstudio/analysis/translator/excel'
32
+ require 'openstudio/analysis/translator/datapoints'
32
33
 
33
34
  # helpers / core_ext
34
35
  require 'openstudio/helpers/string'
@@ -20,5 +20,39 @@ module OpenStudio
20
20
  excel.process
21
21
  excel.analyses
22
22
  end
23
+
24
+ # Load an set of batch datapoints from a csv. This will create a analysis
25
+ # of type 'batch_datapoints' which requires 'batch_run'
26
+ def self.from_csv(filename)
27
+ csv = OpenStudio::Analysis::Translator::Datapoints.new(filename)
28
+ csv.process
29
+ csv.analysis
30
+ end
31
+
32
+ # Retrieve aws instance options from a project. This will return a hash
33
+ def self.aws_instance_options(filename)
34
+ if File.extname(filename) == '.xlsx'
35
+ excel = OpenStudio::Analysis::Translator::Excel.new(filename)
36
+ excel.process
37
+ options = {
38
+ os_server_version: excel.settings['openstudio_server_version'],
39
+ server_instance_type: excel.settings['server_instance_type'],
40
+ worker_instance_type: excel.settings['worker_instance_type'],
41
+ worker_node_number: excel.settings['worker_nodes'].to_i,
42
+ user_id: excel.settings['user_id'],
43
+ aws_tags: excel.aws_tags,
44
+ analysis_type: excel.analyses.first.analysis_type,
45
+ cluster_name: excel.cluster_name
46
+ }
47
+ elsif File.extname(filename) == '.csv'
48
+ csv = OpenStudio::Analysis::Translator::Datapoints.new(filename)
49
+ csv.process
50
+ options = csv.settings
51
+ else
52
+ fail 'Invalid file extension'
53
+ end
54
+
55
+ return options
56
+ end
23
57
  end
24
58
  end
@@ -153,7 +153,7 @@ module OpenStudio
153
153
 
154
154
  if @seed_model[:file]
155
155
  h[:analysis][:seed] = {
156
- file_type: File.extname(@seed_model[:file]).gsub('.', '').upcase,
156
+ file_type: File.extname(@seed_model[:file]).delete('.').upcase,
157
157
  path: "./seed/#{File.basename(@seed_model[:file])}"
158
158
  }
159
159
  else
@@ -171,7 +171,7 @@ module OpenStudio
171
171
 
172
172
  if wf
173
173
  h[:analysis][:weather_file] = {
174
- file_type: File.extname(wf[:file]).gsub('.', '').upcase,
174
+ file_type: File.extname(wf[:file]).delete('.').upcase,
175
175
  path: "./weather/#{File.basename(wf[:file])}"
176
176
  }
177
177
  else
@@ -249,10 +249,12 @@ module OpenStudio
249
249
 
250
250
  # save the file to JSON. Will overwrite the file if it already exists
251
251
  #
252
- # @param filename [String] Name of file to create. It will create the directory and override the file if it exists.
252
+ # @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.
253
253
  # @param version [Integer] Version of the format to return
254
254
  # @return [Boolean]
255
255
  def save(filename, version = 1)
256
+ filename += '.json' if File.extname(filename) == ''
257
+
256
258
  FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
257
259
  File.open(filename, 'w') { |f| f << JSON.pretty_generate(to_hash(version)) }
258
260
 
@@ -261,9 +263,13 @@ module OpenStudio
261
263
 
262
264
  # save the data point JSON with the variables set to the static values. Will overwrite the file if it already exists
263
265
  #
266
+ # @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.
264
267
  # @param version [Integer] Version of the format to return
265
268
  # @return [Boolean]
266
269
  def save_static_data_point(filename, version = 1)
270
+ filename += '.json' if File.extname(filename) == ''
271
+
272
+ FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
267
273
  File.open(filename, 'w') { |f| f << JSON.pretty_generate(to_static_data_point_hash(version)) }
268
274
 
269
275
  true
@@ -271,9 +277,11 @@ module OpenStudio
271
277
 
272
278
  # save the analysis zip file which contains the measures, seed model, weather file, and init/final scripts
273
279
  #
274
- # @param filename [String] Name of file to create. It will create the directory and override the file if it exists.
280
+ # @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.
275
281
  # @return [Boolean]
276
282
  def save_zip(filename)
283
+ filename += '.zip' if File.extname(filename) == ''
284
+
277
285
  FileUtils.mkdir_p File.dirname(filename) unless Dir.exist? File.dirname(filename)
278
286
 
279
287
  save_analysis_zip(filename)
@@ -5,6 +5,9 @@ module OpenStudio
5
5
  class ServerApi
6
6
  attr_reader :hostname
7
7
 
8
+ # Define set of anlaysis methods require batch_run to be queued after them
9
+ BATCH_RUN_METHODS = %w(lhs preflight single_run repeat_run doe diag baseline_perturbation batch_datapoints)
10
+
8
11
  def initialize(options = {})
9
12
  defaults = { hostname: 'http://localhost:8080' }
10
13
  options = defaults.merge(options)
@@ -762,6 +765,80 @@ module OpenStudio
762
765
  analysis_id
763
766
  end
764
767
 
768
+ def run_baseline_perturbation(formulation_filename, analysis_zip_filename)
769
+ project_options = {}
770
+ project_id = new_project(project_options)
771
+
772
+ analysis_options = {
773
+ formulation_file: formulation_filename,
774
+ upload_file: analysis_zip_filename,
775
+ reset_uuids: true
776
+ }
777
+ analysis_id = new_analysis(project_id, analysis_options)
778
+
779
+ run_options = {
780
+ analysis_action: 'start',
781
+ without_delay: false,
782
+ analysis_type: 'baseline_perturbation',
783
+ allow_multiple_jobs: true,
784
+ use_server_as_worker: true,
785
+ simulate_data_point_filename: 'simulate_data_point.rb',
786
+ run_data_point_filename: 'run_openstudio_workflow_monthly.rb'
787
+ }
788
+ start_analysis(analysis_id, run_options)
789
+
790
+ run_options = {
791
+ analysis_action: 'start',
792
+ without_delay: false, # run in background
793
+ analysis_type: 'batch_run',
794
+ allow_multiple_jobs: true,
795
+ use_server_as_worker: true,
796
+ simulate_data_point_filename: 'simulate_data_point.rb',
797
+ run_data_point_filename: 'run_openstudio_workflow_monthly.rb'
798
+ }
799
+ start_analysis(analysis_id, run_options)
800
+
801
+ analysis_id
802
+ end
803
+
804
+ def run_batch_datapoints(formulation_filename, analysis_zip_filename)
805
+ project_options = {}
806
+ project_id = new_project(project_options)
807
+
808
+ puts 'In run_batch_datapoints'
809
+
810
+ analysis_options = {
811
+ formulation_file: formulation_filename,
812
+ upload_file: analysis_zip_filename,
813
+ reset_uuids: true
814
+ }
815
+ analysis_id = new_analysis(project_id, analysis_options)
816
+
817
+ run_options = {
818
+ analysis_action: 'start',
819
+ without_delay: false,
820
+ analysis_type: 'batch_datapoints',
821
+ allow_multiple_jobs: true,
822
+ use_server_as_worker: true,
823
+ simulate_data_point_filename: 'simulate_data_point.rb',
824
+ run_data_point_filename: 'run_openstudio_workflow_monthly.rb'
825
+ }
826
+ start_analysis(analysis_id, run_options)
827
+
828
+ run_options = {
829
+ analysis_action: 'start',
830
+ without_delay: false, # run in background
831
+ analysis_type: 'batch_run',
832
+ allow_multiple_jobs: true,
833
+ use_server_as_worker: true,
834
+ simulate_data_point_filename: 'simulate_data_point.rb',
835
+ run_data_point_filename: 'run_openstudio_workflow_monthly.rb'
836
+ }
837
+ start_analysis(analysis_id, run_options)
838
+
839
+ analysis_id
840
+ end
841
+
765
842
  def run_analysis_detailed(formulation_filename, analysis_zip_filename, analysis_type,
766
843
  allow_multiple_jobs = true, server_as_worker = true,
767
844
  run_data_point_filename = 'run_openstudio_workflow_monthly.rb')
@@ -774,6 +851,7 @@ module OpenStudio
774
851
  upload_file: analysis_zip_filename,
775
852
  reset_uuids: true
776
853
  }
854
+
777
855
  analysis_id = new_analysis(project_id, analysis_options)
778
856
 
779
857
  server_as_worker = true if analysis_type == 'optim' || analysis_type == 'rgenoud'
@@ -790,7 +868,7 @@ module OpenStudio
790
868
 
791
869
  # If the analysis is a staged analysis, then go ahead and run batch run because there is
792
870
  # no explicit way to tell the system to do it
793
- if %w(lhs preflight single_run repeat_run doe).include? analysis_type
871
+ if BATCH_RUN_METHODS.include? analysis_type
794
872
  run_options = {
795
873
  analysis_action: 'start',
796
874
  without_delay: false,
@@ -0,0 +1,409 @@
1
+ module OpenStudio
2
+ module Analysis
3
+ module Translator
4
+ class Datapoints
5
+ attr_reader :version
6
+ attr_reader :settings
7
+ attr_reader :variables
8
+ attr_reader :outputs
9
+ attr_reader :models
10
+ attr_reader :measure_paths
11
+ attr_reader :weather_paths
12
+ attr_reader :worker_inits
13
+ attr_reader :worker_finals
14
+ attr_reader :export_path
15
+ attr_reader :cluster_name
16
+ attr_reader :variables
17
+ attr_reader :algorithm
18
+ attr_reader :problem
19
+ attr_reader :run_setup
20
+ attr_reader :aws_tags
21
+
22
+ # remove these once we have classes to construct the JSON file
23
+ attr_accessor :name
24
+ attr_reader :analysis_name
25
+
26
+ # Methods to override instance variables
27
+
28
+ # Pass in the filename to read
29
+ def initialize(csv_filename)
30
+ @csv_filename = csv_filename
31
+ @root_path = File.expand_path(File.dirname(@csv_filename))
32
+
33
+ @csv = nil
34
+ # Try to read the spreadsheet as a roo object
35
+ if File.exist?(@csv_filename)
36
+ @csv = CSV.read(@csv_filename)
37
+ else
38
+ fail "File #{@csv_filename} does not exist"
39
+ end
40
+
41
+ # Remove nil rows and check row length
42
+ @csv.delete_if { |row| row.uniq.length == 1 && row.uniq[0].nil? }
43
+
44
+ # Initialize some other instance variables
45
+ @version = '0.0.1'
46
+ @analyses = [] # Array o OpenStudio::Analysis. Use method to access
47
+ @name = nil
48
+ @analysis_name = nil
49
+ @cluster_name = nil
50
+ @settings = {}
51
+ @weather_paths = []
52
+ @models = []
53
+ @other_files = []
54
+ @worker_inits = []
55
+ @worker_finals = []
56
+ @export_path = './export'
57
+ @measure_paths = []
58
+ @problem = {}
59
+ @algorithm = {}
60
+ @outputs = {}
61
+ @run_setup = {}
62
+ @aws_tags = []
63
+ end
64
+
65
+ def process
66
+ # Seperate CSV into meta and measure groups
67
+ measure_tag_index = nil
68
+ @csv.each_with_index { |row, index| measure_tag_index = index if row[0] == 'BEGIN-MEASURES' }
69
+ fail "ERROR: No 'BEGIN-MEASURES' tag found in input csv file." unless measure_tag_index
70
+ meta_rows = []
71
+ measure_rows = []
72
+ @csv.each_with_index do |_, index|
73
+ meta_rows << @csv[index] if index < measure_tag_index
74
+ measure_rows << @csv[index] if index > measure_tag_index
75
+ end
76
+
77
+ @setup = parse_csv_meta(meta_rows)
78
+
79
+ @version = Semantic::Version.new @version
80
+ fail "CSV interface version #{@version} is no longer supported. Please upgrade your csv interface to at least 0.0.1" if @version < '0.0.0'
81
+
82
+ @variables = parse_csv_measures(measure_rows)
83
+
84
+ # call validate to make sure everything that is needed exists (i.e. directories)
85
+ validate_analysis
86
+ end
87
+
88
+ # Helper methods to remove models and add new ones programatically. Note that these should
89
+ # be moved into a general analysis class
90
+ def delete_models
91
+ @models = []
92
+ end
93
+
94
+ def add_model(name, display_name, type, path)
95
+ @models << {
96
+ name: name,
97
+ display_name: display_name,
98
+ type: type,
99
+ path: path
100
+ }
101
+ end
102
+
103
+ def validate_analysis
104
+ # Setup the paths and do some error checking
105
+ @measure_paths.each do |mp|
106
+ fail "Measures directory '#{mp}' does not exist" unless Dir.exist?(mp)
107
+ end
108
+
109
+ @models.uniq!
110
+ fail 'No seed models defined in spreadsheet' if @models.empty?
111
+
112
+ @models.each do |model|
113
+ fail "Seed model does not exist: #{model[:path]}" unless File.exist?(model[:path])
114
+ end
115
+
116
+ @weather_paths.uniq!
117
+ fail 'No weather files found based on what is in the spreadsheet' if @weather_paths.empty?
118
+
119
+ @weather_paths.each do |wf|
120
+ fail "Weather file does not exist: #{wf}" unless File.exist?(wf)
121
+ end
122
+
123
+ # This can be a directory as well
124
+ @other_files.each do |f|
125
+ fail "Other files do not exist for: #{f[:path]}" unless File.exist?(f[:path])
126
+ end
127
+
128
+ @worker_inits.each do |f|
129
+ fail "Worker initialization file does not exist for: #{f[:path]}" unless File.exist?(f[:path])
130
+ end
131
+
132
+ @worker_finals.each do |f|
133
+ fail "Worker finalization file does not exist for: #{f[:path]}" unless File.exist?(f[:path])
134
+ end
135
+
136
+ FileUtils.mkdir_p(@export_path)
137
+
138
+ # verify that the measure display names are unique
139
+ # puts @variables.inspect
140
+ measure_display_names = @variables.map { |m| m[:measure_data][:display_name] }.compact
141
+ measure_display_names_mult = measure_display_names.select { |m| measure_display_names.count(m) > 1 }.uniq
142
+ if measure_display_names_mult && !measure_display_names_mult.empty?
143
+ fail "Measure Display Names are not unique for '#{measure_display_names_mult.join('\', \'')}'"
144
+ end
145
+
146
+ variable_names = @variables.map { |v| v[:vars].map { |hash| hash[:display_name] } }.flatten
147
+ dupes = variable_names.select { |e| variable_names.count(e) > 1 }.uniq
148
+ if dupes.count > 0
149
+ fail "duplicate variable names found in list #{dupes.inspect}"
150
+ end
151
+ end
152
+
153
+ # convert the data in excel's parsed data into an OpenStudio Analysis Object
154
+ # @seed_model [Hash] Seed model to set the new analysis to
155
+ # @append_model_name [Boolean] Append the name of the seed model to the display name
156
+ # @return [Object] An OpenStudio::Analysis
157
+ def analysis(seed_model = nil, append_model_name = false)
158
+ fail 'There are no seed models defined in the excel file. Please add one.' if @models.size == 0
159
+ fail 'There are more than one seed models defined in the excel file. This is not supported by the CSV Translator.' if @models.size > 1 && seed_model.nil?
160
+
161
+ seed_model = @models.first if seed_model.nil?
162
+
163
+ # Use the programmatic interface to make the analysis
164
+ # append the model name to the analysis name if requested (normally if there are more than 1 models in the spreadsheet)
165
+ display_name = append_model_name ? @name + ' ' + seed_model[:display_name] : @name
166
+
167
+ a = OpenStudio::Analysis.create(display_name)
168
+
169
+ @variables.each do |measure|
170
+ @measure_paths.each do |measure_path|
171
+ measure_dir_to_add = "#{measure_path}/#{measure[:measure_data][:classname]}"
172
+ if Dir.exist? measure_dir_to_add
173
+ if File.exist? "#{measure_dir_to_add}/measure.rb"
174
+ measure[:measure_data][:local_path_to_measure] = "#{measure_dir_to_add}/measure.rb"
175
+ break
176
+ else
177
+ fail "Measure in directory '#{measure_dir_to_add}' did not contain a measure.rb file"
178
+ end
179
+ end
180
+ end
181
+
182
+ fail "Could not find measure '#{measure['name']}' in directory named '#{measure['measure_file_name_directory']}' in the measure paths '#{@measure_paths.join(', ')}'" unless measure[:measure_data][:local_path_to_measure]
183
+
184
+ a.workflow.add_measure_from_csv(measure)
185
+ end
186
+
187
+ @other_files.each do |library|
188
+ a.libraries.add(library[:path], library_name: library[:lib_zip_name])
189
+ end
190
+
191
+ @worker_inits.each do |w|
192
+ a.worker_inits.add(w[:path], args: w[:args])
193
+ end
194
+
195
+ @worker_finals.each do |w|
196
+ a.worker_finalizes.add(w[:path], args: w[:args])
197
+ end
198
+
199
+ # Add in the outputs
200
+ @outputs.each do |o|
201
+ o = Hash[o.map { |k, v| [k.to_sym, v] }]
202
+ a.add_output(o)
203
+ end
204
+
205
+ a.analysis_type = @problem['analysis_type']
206
+
207
+ # clear out the seed files before adding new ones
208
+ a.seed_model = seed_model[:path]
209
+
210
+ # clear out the weather files before adding new ones
211
+ a.weather_files.clear
212
+ @weather_paths.each do |wp|
213
+ a.weather_files.add_files(wp)
214
+ end
215
+
216
+ a
217
+ end
218
+
219
+ protected
220
+
221
+ def parse_csv_meta(meta_rows)
222
+ # Convert to hash
223
+ config_hash = {}
224
+ meta_rows.each do |row|
225
+ config_hash[row[0].to_sym] = row[1]
226
+ end
227
+
228
+ # Assign required attributes
229
+ fail 'Require setting not found: version' unless config_hash[:version]
230
+ @version = config_hash[:version]
231
+
232
+ if config_hash[:analysis_name]
233
+ @name = config_hash[:analysis_name]
234
+ else
235
+ @name = SecureRandom.uuid
236
+ end
237
+ @analysis_name = @name.snake_case
238
+
239
+ fail 'Require setting not found: measure_path' unless config_hash[:measure_paths]
240
+ config_hash[:measure_paths] = [config_hash[:measure_paths]] unless config_hash[:measure_paths].respond_to?(:each)
241
+ config_hash[:measure_paths].each do |path|
242
+ if (Pathname.new path).absolute?
243
+ @measure_paths << path
244
+ else
245
+ @measure_paths << File.expand_path(File.join(@root_path, path))
246
+ end
247
+ end
248
+
249
+ fail 'Required setting not found: weather_paths' unless config_hash[:weather_paths]
250
+ config_hash[:weather_paths] = eval("#{config_hash[:weather_paths]}")
251
+ config_hash[:weather_paths].each do |path|
252
+ if (Pathname.new path).absolute?
253
+ @weather_paths << path
254
+ else
255
+ @weather_paths << File.expand_path(File.join(@root_path, path))
256
+ end
257
+ end
258
+
259
+ fail 'Required setting not found: models' unless config_hash[:models]
260
+ config_hash[:models] = [config_hash[:models]] unless config_hash[:models].respond_to?(:each)
261
+ config_hash[:models].each do |path|
262
+ model_name = File.basename(path).split('.')[0]
263
+ model_name = SecureRandom.uuid if model_name == ''
264
+ type = File.basename(path).split('.')[1].upcase
265
+ unless (Pathname.new path).absolute?
266
+ path = File.expand_path(File.join(@root_path, path))
267
+ end
268
+ @models << { name: model_name.snake_case, display_name: model_name, type: type, path: path }
269
+ end
270
+
271
+ # Assign optional attributes
272
+ if config_hash[:output]
273
+ path = File.expand_path(File.join(@root_path, config_hash[:output].to_s))
274
+ if File.exist? path
275
+ @outputs = MultiJson.load(File.read(path))
276
+ else
277
+ fail "Could not find output json: #{config_hash[:output]}"
278
+ end
279
+ end
280
+
281
+ if config_hash[:export_path]
282
+ if (Pathname.new config_hash[:export_path]).absolute?
283
+ @export_path = config_hash[:export_path]
284
+ else
285
+ @export_path = File.expand_path(File.join(@root_path, config_hash[:export_path]))
286
+ end
287
+ end
288
+
289
+ if config_hash[:library_path]
290
+ library_name = File.basename(config_hash[:library_path]).split('.')[0]
291
+ unless (Pathname.new config_hash[:library_path]).absolute?
292
+ config_hash[:library_path] = File.expand_path(File.join(@root_path, config_hash[:library_path]))
293
+ end
294
+ @other_files << { lib_zip_name: library_name, path: config_hash[:library_path] }
295
+ end
296
+
297
+ @run_setup[:allow_multiple_jobs] = config_hash[:allow_multiple_jobs].to_s.to_bool if config_hash[:allow_multiple_jobs]
298
+ @run_setup[:use_server_as_worker] = config_hash[:use_server_as_worker].to_s.to_bool if config_hash[:use_server_as_worker]
299
+
300
+ # Assign AWS settings
301
+ @settings[:proxy_port] = config_hash[:proxy_port] if config_hash[:proxy_port]
302
+ @settings[:cluster_name] = config_hash[:cluster_name] if config_hash[:cluster_name]
303
+ @settings[:user_id] = config_hash[:user_id] if config_hash[:user_id]
304
+ @settings[:os_server_version] = config_hash[:os_server_version] if config_hash[:os_server_version]
305
+ @settings[:server_instance_type] = config_hash[:server_instance_type] if config_hash[:server_instance_type]
306
+ @settings[:worker_instance_type] = config_hash[:worker_instance_type] if config_hash[:worker_instance_type]
307
+ @settings[:worker_node_number] = config_hash[:worker_node_number].to_i if config_hash[:worker_node_number]
308
+ @settings[:aws_tags] = config_hash[:aws_tags] if config_hash[:aws_tags]
309
+ @settings[:analysis_type] = 'batch_datapoints'
310
+ end
311
+
312
+ def parse_csv_measures(measure_rows)
313
+ # Build metadata required for parsing
314
+ measures = measure_rows[0].uniq.select { |measure| !measure.nil? }.map(&:to_sym)
315
+ measure_map = {}
316
+ measure_var_list = []
317
+ measures.each do |measure|
318
+ measure_map[measure] = {}
319
+ col_ind = (0..(measure_rows[0].length - 1)).to_a.select { |i| measure_rows[0][i] == measure.to_s }
320
+ col_ind.each do |var_ind|
321
+ tuple = measure.to_s + measure_rows[1][var_ind]
322
+ fail "Multiple measure_variable tuples found for '#{measure}_#{measure_rows[1][var_ind]}'. These tuples must be unique." if measure_var_list.include? tuple
323
+ measure_var_list << tuple
324
+ measure_map[measure][measure_rows[1][var_ind].to_sym] = var_ind
325
+ end
326
+ end
327
+
328
+ # For each measure load measure json and parse out critical variable requirements
329
+ data = []
330
+ measures.each_with_index do |measure, measure_index|
331
+ data[measure_index] = {}
332
+ measure_json = ''
333
+ for i in 0..(@measure_paths.length - 1)
334
+ if File.exist? File.join(@measure_paths[i], measure.to_s, 'measure.json')
335
+ measure_json = MultiJson.load(File.read(File.join(@measure_paths[i], measure.to_s, 'measure.json')))
336
+ break
337
+ end
338
+ end
339
+ fail "Could not find measure json #{measure}.json in measure_paths: '#{@measure_paths.join("\n")}'" if measure_json == ''
340
+ measure_data = {}
341
+ measure_data[:classname] = measure_json['classname']
342
+ measure_data[:name] = measure_json['name']
343
+ measure_data[:display_name] = measure_json['display_name']
344
+ measure_data[:measure_type] = measure_json['measure_type']
345
+ measure_data[:uid] = measure_json['uid']
346
+ measure_data[:version_id] = measure_json['version_id']
347
+ data[measure_index][:measure_data] = measure_data
348
+ data[measure_index][:vars] = []
349
+ vars = measure_map[measure]
350
+ vars.each do |var|
351
+ var = var[0]
352
+ var_hash = {}
353
+ var_json = measure_json['arguments'].select { |hash| hash['local_variable'] == var.to_s }[0]
354
+ fail "measure.json for measure #{measure} does not have an argument with local_variable == #{var}" if var_json.nil?
355
+ var_hash[:variable_type] = 'variable'
356
+ var_hash[:display_name] = measure_rows[2][measure_map[measure][var]]
357
+ var_hash[:display_name_short] = var_hash[:display_name]
358
+ var_hash[:name] = var_json['local_variable']
359
+ var_hash[:type] = var_json['variable_type'].downcase
360
+ var_hash[:units] = var_json['units']
361
+ var_hash[:distribution] = {}
362
+ case var_hash[:type].downcase
363
+ when 'bool', 'boolean' # is 'boolean' necessary? it's not in the enum catch
364
+ var_hash[:distribution][:values] = (3..(measure_rows.length - 1)).map { |value| measure_rows[value.to_i][measure_map[measure][var]].to_s == 'true' }
365
+ var_hash[:distribution][:maximum] = true
366
+ var_hash[:distribution][:minimum] = false
367
+ var_hash[:distribution][:mode] = var_hash[:distribution][:values].group_by { |i| i }.max { |x, y| x[1].length <=> y[1].length }[0]
368
+ when 'choice', 'string'
369
+ var_hash[:distribution][:values] = (3..(measure_rows.length) - 1).map { |value| measure_rows[value.to_i][measure_map[measure][var]].to_s }
370
+ var_hash[:distribution][:minimum] = var_hash[:distribution][:values].min
371
+ var_hash[:distribution][:maximum] = var_hash[:distribution][:values].max
372
+ var_hash[:distribution][:mode] = var_hash[:distribution][:values].group_by { |i| i }.max { |x, y| x[1].length <=> y[1].length }[0]
373
+ else
374
+ var_hash[:distribution][:values] = (3..(measure_rows.length - 1)).map { |value| eval(measure_rows[value.to_i][measure_map[measure][var]]) }
375
+ var_hash[:distribution][:minimum] = var_hash[:distribution][:values].map(&:to_i).min
376
+ var_hash[:distribution][:maximum] = var_hash[:distribution][:values].map(&:to_i).max
377
+ var_hash[:distribution][:mode] = var_hash[:distribution][:values].group_by { |i| i }.max { |x, y| x[1].length <=> y[1].length }[0]
378
+ end
379
+ var_hash[:distribution][:weights] = eval('[' + "#{1.0 / (measure_rows.length - 3)}," * (measure_rows.length - 3) + ']')
380
+ var_hash[:distribution][:type] = 'discrete'
381
+ var_hash[:distribution][:units] = var_hash[:units]
382
+ if var_hash[:type] == 'choice'
383
+ var_hash[:distribution][:enumerations] = var_json['choices']
384
+ elsif var_hash[:type] == 'bool'
385
+ var_hash[:distribution][:enumerations] = []
386
+ var_hash[:distribution][:enumerations] << 'true' # TODO: should this be a real bool?
387
+ var_hash[:distribution][:enumerations] << 'false'
388
+ end
389
+ data[measure_index][:vars] << var_hash
390
+ end
391
+ data[measure_index][:args] = []
392
+ measure_json['arguments'].each do |arg_json|
393
+ arg = {}
394
+ arg[:value_type] = arg_json['variable_type'].downcase
395
+ arg[:name] = arg_json['name']
396
+ arg[:display_name] = arg_json['display_name']
397
+ arg[:display_name_short] = arg_json['display_name']
398
+ arg[:default_value] = arg_json['default_value']
399
+ arg[:value] = arg_json['default_value']
400
+ data[measure_index][:args] << arg
401
+ end
402
+ end
403
+
404
+ data
405
+ end
406
+ end
407
+ end
408
+ end
409
+ end