urbanopt-cli 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -182,8 +182,8 @@
182
182
  <md-card-title-text>
183
183
  <span class="md-headline">{{ scenario.name }}</span>
184
184
  <span class="md-subhead">Annual Net Energy - {{
185
- annualNetChartData[scenario.name] }} (kWh)</span>
186
- <span class="md-subhead">Monthly Net Energy (kWh)</span>
185
+ annualNetChartData[scenario.name] }} </span>
186
+ <span class="md-subhead">Monthly Net Energy </span>
187
187
  <span ng-if="scenario.complete_simulation!=true" class="md-subhead error">{{errorText}}</span>
188
188
  </md-card-title-text>
189
189
  </md-card-title>
@@ -229,7 +229,7 @@
229
229
  $scope.monthlyFuelChartOptions = {
230
230
  chart: {
231
231
  type: 'multiBarChart',
232
- color: ['#187C7C', '#8CC025', '#CE2828'],
232
+ color: ['#187C7C', '#8CC025', '#CE2828', '#AFECE7', '#F19953'],
233
233
  height: 320,
234
234
  margin: {
235
235
  top: 45,
@@ -373,9 +373,9 @@
373
373
 
374
374
  var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
375
375
 
376
- var datasets = ['Electricity:Facility', 'ElectricityProduced:Facility', 'Gas:Facility'];
376
+ var datasets = ['Electricity:Facility', 'ElectricityProduced:Facility', 'NaturalGas:Facility', 'Propane:Facility', 'FuelOilNo2:Facility', 'OtherFuels:Facility'];
377
377
 
378
- var endUseKeys = ['Heating:Electricity', 'Cooling:Electricity', 'InteriorLights:Electricity', 'ExteriorLights:Electricity', 'InteriorEquipment:Electricity', 'Fans:Electricity', 'Pumps:Electricity', 'HeatRejection:Electricity', 'WaterSystems:Electricity'];
378
+ var endUseKeys = ['Heating:Electricity', 'Cooling:Electricity', 'InteriorLights:Electricity', 'ExteriorLights:Electricity', 'InteriorEquipment:Electricity', 'ExteriorEquipment:Electricity', 'Fans:Electricity', 'Pumps:Electricity', 'HeatRejection:Electricity', 'WaterSystems:Electricity'];
379
379
 
380
380
  var applicableEndUseKeys = [];
381
381
 
@@ -419,44 +419,106 @@
419
419
  _.forEach($scope.scenarios, function (scenario) {
420
420
 
421
421
  if(scenario['complete_simulation'] == true){
422
+
422
423
  // monthly fuel use
423
424
  $scope.monthlyFuelChartData[scenario.name] = [];
425
+ var changeToKbtu = false;
426
+ var kbtu_datasets = ['NaturalGas:Facility', 'Propane:Facility', 'FuelOilNo2:Facility', 'OtherFuels:Facility'];
427
+ // first iterate through all kbtu datasets to see if you'll need to change to kBtu units
428
+ _.forEach(kbtu_datasets, function (kbtu_dataset) {
429
+ var values = scenario.monthly_values[kbtu_dataset];
430
+ if (!(values.every(item => item === 0))) {
431
+ changeToKbtu = true;
432
+ }
433
+ });
434
+
424
435
  _.forEach(datasets, function (dataset) {
436
+
425
437
  var values = scenario.monthly_values[dataset];
438
+ var datasetUnit = "";
426
439
 
427
440
  if (dataset == 'Electricity:Facility') {
428
- var datasetUnit = 'Electricity:Facility(kWh)';
429
- } else if (dataset == 'ElectricityProduced:Facility') {
430
- var datasetUnit = 'ElectricityProduced:Facility(kWh)'
431
- } else if (dataset == 'Gas:Facility') {
432
- var datasetUnit = 'Gas:Facility(kBtu)'
433
- };
434
-
435
- $scope.monthlyFuelChartData[scenario.name].push({
436
- key: datasetUnit,
437
- values: _.map(values, function (value, i) {
438
- value;
439
- monthlyFuelYMax = _.max([monthlyFuelYMax, value]);
440
- return {
441
- x: months[i],
442
- y: value
443
- };
444
- })
445
- });
441
+ // first check if there is data to include
442
+ if (!(values.every(item => item === 0))) {
443
+ console.log(changeToKbtu);
444
+ if (changeToKbtu) {
445
+ var kBtuValues = [];
446
+ var kwhValues = scenario.monthly_values[dataset]
447
+ _.forEach(kwhValues, function (kwhValue) {
448
+ new_value = kwhValue*3.4121416; //convert kwh to kbtu
449
+ kBtuValues.push(new_value);
450
+ })
451
+ values = kBtuValues;
452
+ datasetUnit = 'Electricity:Facility(kBtu)';
453
+ } else {
454
+ datasetUnit = 'Electricity:Facility(kWh)';
455
+ }
456
+ }
457
+ } else if (dataset == 'ElectricityProduced:Facility') {
458
+ // first check if there is data to include
459
+ if (!(values.every(item => item === 0))) {
460
+ if (changeToKbtu) {
461
+ var kBtuValues = [];
462
+ var kwhValues = scenario.monthly_values[dataset];
463
+ _.forEach(kwhValues, function (kwhValue) {
464
+ new_value = kwhValue*3.4121416; //convert kwh to kbtu
465
+ kBtuValues.push(new_value);
466
+ })
467
+ values = kBtuValues;
468
+ datasetUnit = 'ElectricityProduced:Facility(kBtu)';
469
+
470
+ } else {
471
+ datasetUnit = 'ElectricityProduced:Facility(kWh)'
472
+ }
473
+ }
474
+ } else {
475
+ // everything else gets kBtu for now
476
+ if (!(values.every(item => item === 0))) {
477
+ datasetUnit = dataset + '(kBtu)';
478
+ }
479
+ };
480
+ // if datasetUnit is empty string, there is no data so don't push it in the array
481
+ if (datasetUnit != ""){
482
+ $scope.monthlyFuelChartData[scenario.name].push({
483
+ key: datasetUnit,
484
+ values: _.map(values, function (value, i) {
485
+ value;
486
+ monthlyFuelYMax = _.max([monthlyFuelYMax, value]);
487
+ return {
488
+ x: months[i],
489
+ y: value
490
+ };
491
+ })
492
+ });
493
+ }
446
494
  });
447
- $scope.monthlyFuelChartOptions.chart['yDomain'] = [0, _.round(monthlyFuelYMax)];
448
495
  }
449
496
 
450
497
  // monthly net use
451
-
452
498
  if(scenario['complete_simulation'] == true){
453
499
  $scope.monthlyNetChartData[scenario.name] = [];
500
+ var values = scenario.monthly_values['NaturalGas:Facility'];
501
+ var changeToKbtu = false;
502
+ if (!(values.every(item => item === 0))) {
503
+ changeToKbtu = true
504
+ }
505
+ if (changeToKbtu){
506
+ var unit = ' (kBtu)'
507
+ }
508
+ else {
509
+ var unit = ' (kWh)'
510
+ }
511
+
454
512
  $scope.annualNetChartData[scenario.name] = 0;
455
513
  $scope.monthlyNetChartData[scenario.name].push({
456
- key: 'Net Energy Use',
514
+ key: 'Net Energy Use' + unit,
457
515
  values: _.map(months, function (month, i) {
458
-
459
- var value = scenario.monthly_values['Electricity:Facility'][i] - scenario.monthly_values['ElectricityProduced:Facility'][i] + (scenario.monthly_values['Gas:Facility'][i]*0.293); //kBtu to kWh
516
+ if (changeToKbtu){
517
+ var value = scenario.monthly_values['Electricity:Facility'][i]*3.41 - scenario.monthly_values['ElectricityProduced:Facility'][i]*3.41 + scenario.monthly_values['NaturalGas:Facility'][i]; //Values are in kBtu
518
+ }
519
+ else {
520
+ var value = scenario.monthly_values['Electricity:Facility'][i] - scenario.monthly_values['ElectricityProduced:Facility'][i]; //Values are in kWh
521
+ }
460
522
  value;
461
523
  $scope.annualNetChartData[scenario.name] += value;
462
524
  monthlyNetYMin = _.min([monthlyNetYMin, value]);
@@ -488,7 +550,7 @@
488
550
  }]
489
551
  });
490
552
  });
491
- $scope.annualEndUseChartOptions.chart['yDomain'] = [0, _.round(1.1 * annualTotalYMax)];
553
+ $scope.annualEndUseChartOptions.chart['yDomain'] = [0, _.round(annualTotalYMax)];
492
554
 
493
555
  }
494
556
  });
@@ -502,4 +564,4 @@
502
564
  </script>
503
565
 
504
566
  </body>
505
- </html>
567
+ </html>
data/lib/uo_cli.rb CHANGED
@@ -41,9 +41,7 @@ require 'fileutils'
41
41
  require 'json'
42
42
  require 'openssl'
43
43
  require 'open3'
44
- require_relative '../developer_nrel_key'
45
- require 'pycall/import'
46
- include PyCall::Import
44
+ require 'yaml'
47
45
 
48
46
  module URBANopt
49
47
  module CLI
@@ -54,6 +52,7 @@ module URBANopt
54
52
  'opendss' => 'Run OpenDSS simulation',
55
53
  'process' => 'Post-process URBANopt simulations for additional insights',
56
54
  'visualize' => 'Visualize and compare results for features and scenarios',
55
+ 'validate' => 'Validate results with custom rules',
57
56
  'delete' => 'Delete simulations for a specified scenario'
58
57
  }.freeze
59
58
 
@@ -88,9 +87,12 @@ module URBANopt
88
87
  banner "\nURBANopt #{cmd}:\n \n"
89
88
 
90
89
  opt :project_folder, "\nCreate project directory in your current folder. Name the directory\n" \
91
- "Add additional tag to specify the method for creating geometry, or use the default urban geometry creation method to create building geometry from geojson coordinates with core and perimeter zoning\n" \
90
+ "Add additional tags to specify the method for creating geometry, or use the default urban geometry creation method to create building geometry from geojson coordinates with core and perimeter zoning\n" \
92
91
  'Example: uo create --project-folder urbanopt_example_project', type: String, short: :p
93
92
 
93
+ opt :electric, "\nCreate default project with FeatureFile containing electrical network\n" \
94
+ "Example: uo create --project-folder urbanopt_example_project --electric", short: :l
95
+
94
96
  opt :create_bar, "\nCreate building geometry and add space types using the create bar from building type ratios measure\n" \
95
97
  "Refer to https://docs.urbanopt.net/ for more details about the workflow\n" \
96
98
  "Used with --project-folder\n" \
@@ -157,16 +159,34 @@ module URBANopt
157
159
 
158
160
  opt :scenario, "\nRun OpenDSS simulations for <scenario>\n" \
159
161
  "Requires --feature also be specified\n" \
160
- 'Example: uo opendss --scenario baseline_scenario-2.csv --feature example_project.json', default: 'baseline_scenario.csv', required: true
162
+ 'Example: uo opendss --scenario baseline_scenario-2.csv --feature example_project.json', default: 'baseline_scenario.csv'
161
163
 
162
164
  opt :feature, "\nRun OpenDSS simulations according to <featurefile>\n" \
163
165
  "Requires --scenario also be specified\n" \
164
- 'Example: uo opendss --scenario baseline_scenario.csv --feature example_project.json', default: 'example_project.json', required: true
166
+ 'Example: uo opendss --scenario baseline_scenario.csv --feature example_project.json', default: 'example_project_with_electric_network.json'
165
167
 
166
168
  opt :equipment, "\nRun OpenDSS simulations using <equipmentfile>. If not specified, the electrical_database.json from urbanopt-ditto-reader will be used.\n" \
167
- 'Example: uo opendss --scenario baseline_scenario.csv --feature example_project.json'
169
+ 'Example: uo opendss --scenario baseline_scenario.csv --feature example_project.json', type: String, short: :e
170
+
171
+ opt :timestep, "\nNumber of minutes per timestep in the OpenDSS simulation.\n" \
172
+ "Optional, defaults to analog of simulation timestep set in the FeatureFile\n" \
173
+ "Example: uo opendss --scenario baseline_scenario.csv --feature example_project.json --timestep 15", type: Integer, short: :t
174
+
175
+ opt :start_time, "\nBeginning of the period for OpenDSS analysis\n" \
176
+ "Optional, defaults to beginning of simulation time\n" \
177
+ "Example: uo opendss --scenario baseline_scenario.csv --feature example_project.json --start-time '2017/01/15 01:00:00'\n" \
178
+ "Ensure you have quotes around the timestamp, to allow for the space between date & time.", type: String
179
+
180
+ opt :end_time, "\nEnd of the period for OpenDSS analysis\n" \
181
+ "Optional, defaults to end of simulation time\n" \
182
+ "Example: uo opendss --scenario baseline_scenario.csv --feature example_project.json --end-time '2017/01/16 01:00:00'\n" \
183
+ "Ensure you have quotes around the timestamp, to allow for the space between date & time.", type: String
168
184
 
169
- opt :reopt, "\nRun with additional REopt functionality."
185
+ opt :reopt, "\nRun with additional REopt functionality.\n" \
186
+ "Example: uo opendss --scenario baseline_scenario.csv --feature example_project.json --reopt", short: :r
187
+
188
+ opt :config, "\nRun OpenDSS using a json config file to specify the above settings.\n" \
189
+ "Example: uo opendss --config path/to/config.json", type: String, short: :c
170
190
  end
171
191
  end
172
192
 
@@ -180,7 +200,7 @@ module URBANopt
180
200
 
181
201
  opt :opendss, "\nPost-process with OpenDSS"
182
202
 
183
- opt :reopt_scenario, "\nOptimize for entire scenario with REopt\n" \
203
+ opt :reopt_scenario, "\nOptimize for entire scenario with REopt. Used with the --reopt-scenario-assumptions-file to specify the assumptions to use.\n" \
184
204
  'Example: uo process --reopt-scenario'
185
205
 
186
206
  opt :reopt_feature, "\nOptimize for each building individually with REopt\n" \
@@ -189,6 +209,9 @@ module URBANopt
189
209
  opt :with_database, "\nInclude a sql database output of post-processed results\n" \
190
210
  'Example: uo process --default --with-database'
191
211
 
212
+ opt :reopt_scenario_assumptions_file, "\nPath to the scenario REopt assumptions JSON file you want to use. Use with the --reopt-scenario post-processor. " \
213
+ "If not specified, the reopt/base_assumptions.json file will be used", type: String, short: :a
214
+
192
215
  opt :scenario, "\nSelect which scenario to optimize", default: 'baseline_scenario.csv', required: true
193
216
 
194
217
  opt :feature, "\nSelect which FeatureFile to use", default: 'example_project.json', required: true
@@ -211,6 +234,23 @@ module URBANopt
211
234
  end
212
235
  end
213
236
 
237
+ # Define validation commands
238
+ def opt_validate
239
+ @subopts = Optimist.options do
240
+ banner "\nURBANopt #{@command}:\n \n"
241
+
242
+ opt :eui, "\nCompare eui results in feature reports to limits in validation_schema.yaml\n" \
243
+ "Provide path to the validation_schema.yaml file in your project directory\n" \
244
+ "Example: uo validate --eui validation_schema.yaml", type: String
245
+
246
+ opt :scenario, "\nProvide the scenario CSV file to validate features from that scenario\n", type: String, required: true
247
+
248
+ opt :feature, "\nProvide the Feature JSON file to include info about each feature\n", type: String, required: true
249
+
250
+ opt :units, "\nSI (kWh/m2/yr) or IP (kBtu/ft2/yr)", type: String, default: 'IP'
251
+ end
252
+ end
253
+
214
254
  def opt_delete
215
255
  cmd = @command
216
256
  @subopts = Optimist.options do
@@ -228,22 +268,22 @@ module URBANopt
228
268
 
229
269
  # Pull out feature and scenario filenames and paths
230
270
  if @opthash.subopts[:scenario_file]
231
- @feature_path, @feature_name = File.split(File.absolute_path(@opthash.subopts[:scenario_file]))
271
+ @feature_path, @feature_name = File.split(File.expand_path(@opthash.subopts[:scenario_file]))
232
272
  end
233
273
  # FIXME: Can this be combined with the above block? This isn't very DRY
234
274
  # One solution would be changing scenario_file to feature.
235
275
  # Would that be confusing when creating a ScenarioFile from the FeatureFile?
236
276
  if @opthash.subopts[:feature]
237
- @feature_path, @feature_name = File.split(File.absolute_path(@opthash.subopts[:feature]))
277
+ @feature_path, @feature_name = File.split(File.expand_path(@opthash.subopts[:feature]))
238
278
  end
239
279
  if @opthash.subopts[:scenario]
240
- @root_dir, @scenario_file_name = File.split(File.absolute_path(@opthash.subopts[:scenario]))
280
+ @root_dir, @scenario_file_name = File.split(File.expand_path(@opthash.subopts[:scenario]))
281
+ @scenario_name = File.basename(@scenario_file_name, File.extname(@scenario_file_name))
241
282
  end
242
283
 
243
284
  # Simulate energy usage as defined by ScenarioCSV\
244
285
  def self.run_func
245
- name = File.basename(@scenario_file_name, File.extname(@scenario_file_name))
246
- run_dir = File.join(@root_dir, 'run', name.downcase)
286
+ run_dir = File.join(@root_dir, 'run', @scenario_name.downcase)
247
287
  csv_file = File.join(@root_dir, @scenario_file_name)
248
288
  featurefile = File.join(@root_dir, @feature_name)
249
289
  mapper_files_dir = File.join(@root_dir, 'mappers')
@@ -261,9 +301,9 @@ module URBANopt
261
301
  # TODO: Better way of grabbing assumptions file than the first file in the folder
262
302
  reopt_files_dir_contents_list = Dir.children(reopt_files_dir.to_s)
263
303
  reopt_assumptions_filename = File.basename(reopt_files_dir_contents_list[0])
264
- scenario_output = URBANopt::Scenario::REoptScenarioCSV.new(name, @root_dir, run_dir, feature_file, mapper_files_dir, csv_file, num_header_rows, reopt_files_dir, reopt_assumptions_filename)
304
+ scenario_output = URBANopt::Scenario::REoptScenarioCSV.new(@scenario_name.downcase, @root_dir, run_dir, feature_file, mapper_files_dir, csv_file, num_header_rows, reopt_files_dir, reopt_assumptions_filename)
265
305
  else
266
- scenario_output = URBANopt::Scenario::ScenarioCSV.new(name, @root_dir, run_dir, feature_file, mapper_files_dir, csv_file, num_header_rows)
306
+ scenario_output = URBANopt::Scenario::ScenarioCSV.new(@scenario_name.downcase, @root_dir, run_dir, feature_file, mapper_files_dir, csv_file, num_header_rows)
267
307
  end
268
308
  scenario_output
269
309
  end
@@ -272,7 +312,7 @@ module URBANopt
272
312
  # params\
273
313
  # +feature_file_path+:: _string_ Path to a FeatureFile
274
314
  def self.create_scenario_csv_file(feature_id)
275
- feature_file_json = JSON.parse(File.read(File.absolute_path(@opthash.subopts[:scenario_file])), symbolize_names: true)
315
+ feature_file_json = JSON.parse(File.read(File.expand_path(@opthash.subopts[:scenario_file])), symbolize_names: true)
276
316
  Dir["#{@feature_path}/mappers/*.rb"].each do |mapper_file|
277
317
  mapper_name = File.basename(mapper_file, File.extname(mapper_file))
278
318
  scenario_file_name = if feature_id == 'SKIP'
@@ -305,7 +345,7 @@ module URBANopt
305
345
  # params \
306
346
  # +existing_scenario_file+:: _string_ - Name of existing ScenarioFile
307
347
  def self.create_reopt_scenario_file(existing_scenario_file)
308
- existing_path, existing_name = File.split(File.absolute_path(existing_scenario_file))
348
+ existing_path, existing_name = File.split(File.expand_path(existing_scenario_file))
309
349
 
310
350
  # make reopt folder
311
351
  Dir.mkdir File.join(existing_path, 'reopt')
@@ -363,6 +403,9 @@ module URBANopt
363
403
  # copy gemfile
364
404
  FileUtils.cp(File.join(path_item, 'Gemfile'), dir_name)
365
405
 
406
+ # copy validation schema
407
+ FileUtils.cp(File.join(path_item, 'validation_schema.yaml'), dir_name)
408
+
366
409
  # copy weather files
367
410
  weather_files = File.join(path_item, 'weather')
368
411
  Pathname.new(weather_files).children.each { |weather_file| FileUtils.cp(weather_file, File.join(dir_name, 'weather')) }
@@ -371,10 +414,16 @@ module URBANopt
371
414
  viz_files = File.join(path_item, 'visualization')
372
415
  Pathname.new(viz_files).children.each { |viz_file| FileUtils.cp(viz_file, File.join(dir_name, 'visualization')) }
373
416
 
417
+ if @opthash.subopts[:electric] == true
418
+ FileUtils.cp(File.join(path_item, 'example_project_with_electric_network.json'), dir_name)
419
+ end
420
+
374
421
  if @opthash.subopts[:floorspace] == false
375
422
 
376
- # copy feature file
377
- FileUtils.cp(File.join(path_item, 'example_project.json'), dir_name)
423
+ if @opthash.subopts[:electric] != true
424
+ # copy feature file
425
+ FileUtils.cp(File.join(path_item, 'example_project.json'), dir_name)
426
+ end
378
427
 
379
428
  # copy osm
380
429
  FileUtils.cp(File.join(path_item, 'osm_building/7.osm'), File.join(dir_name, 'osm_building'))
@@ -387,6 +436,7 @@ module URBANopt
387
436
  FileUtils.cp(File.join(path_item, 'mappers/Baseline.rb'), File.join(dir_name, 'mappers'))
388
437
  FileUtils.cp(File.join(path_item, 'mappers/HighEfficiency.rb'), File.join(dir_name, 'mappers'))
389
438
  FileUtils.cp(File.join(path_item, 'mappers/ThermalStorage.rb'), File.join(dir_name, 'mappers'))
439
+ FileUtils.cp(File.join(path_item, 'mappers/EvCharging.rb'), File.join(dir_name, 'mappers'))
390
440
 
391
441
  # copy osw file
392
442
  FileUtils.cp(File.join(path_item, 'mappers/base_workflow.osw'), File.join(dir_name, 'mappers'))
@@ -464,7 +514,7 @@ module URBANopt
464
514
  puts 'Checking system.....'
465
515
 
466
516
  # platform agnostic
467
- stdout, stderr, status = Open3.capture3('python -V')
517
+ stdout, stderr, status = Open3.capture3('python3 -V')
468
518
  if stderr && !stderr == ''
469
519
  # error
470
520
  results[:message] = "ERROR: #{stderr}"
@@ -473,7 +523,7 @@ module URBANopt
473
523
  end
474
524
 
475
525
  # check version
476
- stdout.slice! 'Python '
526
+ stdout.slice! 'Python3 '
477
527
  if stdout[0].to_i == 2 || (stdout[0].to_i == 3 && stdout[2].to_i < 7)
478
528
  # global python version is not 3.7+
479
529
  results[:message] = "ERROR: Python version must be at least 3.7. Found python with version #{stdout}."
@@ -484,7 +534,7 @@ module URBANopt
484
534
  end
485
535
 
486
536
  # check pip
487
- stdout, stderr, status = Open3.capture3('pip -V')
537
+ stdout, stderr, status = Open3.capture3('pip3 -V')
488
538
  if stderr && !stderr == ''
489
539
  # error
490
540
  results[:message] = "ERROR finding pip: #{stderr}"
@@ -503,9 +553,9 @@ module URBANopt
503
553
  def self.check_reader
504
554
  results = { reader: false, message: '' }
505
555
 
506
- puts 'Checking for UrbanoptDittoReader...'
556
+ puts 'Checking for urbanopt-ditto-reader...'
507
557
 
508
- stdout, stderr, status = Open3.capture3('pip list')
558
+ stdout, stderr, status = Open3.capture3('pip3 list')
509
559
  if stderr && !stderr == ''
510
560
  # error
511
561
  results[:message] = 'ERROR running pip list'
@@ -513,25 +563,25 @@ module URBANopt
513
563
  return results
514
564
  end
515
565
 
516
- res = /^UrbanoptDittoReader.*$/.match(stdout)
566
+ res = /^urbanopt-ditto-reader.*$/.match(stdout)
517
567
  if res
518
568
  # extract version
519
569
  version = /\d+.\d+.\d+/.match(res.to_s)
520
570
  path = res.to_s.split(' ')[-1]
521
571
  puts "...path: #{path}"
522
572
  if version
523
- results[:message] = "Found UrbanoptDittoReader version #{version}"
573
+ results[:message] = "Found urbanopt-ditto-reader version #{version}"
524
574
  puts "...#{results[:message]}"
525
575
  results[:reader] = true
526
- puts "UrbanoptDittoReader check done. \n\n"
576
+ puts "urbanopt-ditto-reader check done. \n\n"
527
577
  return results
528
578
  else
529
- results[:message] = 'UrbanoptDittoReader version not found.'
579
+ results[:message] = 'urbanopt-ditto-reader version not found.'
530
580
  return results
531
581
  end
532
582
  else
533
583
  # no ditto reader
534
- results[:message] = 'UrbanoptDittoReader not found.'
584
+ results[:message] = 'urbanopt-ditto-reader not found.'
535
585
  return results
536
586
  end
537
587
  end
@@ -593,10 +643,7 @@ module URBANopt
593
643
  # Run simulations
594
644
  if @opthash.command == 'run' && @opthash.subopts[:scenario] && @opthash.subopts[:feature]
595
645
  if @opthash.subopts[:scenario].to_s.include? '-'
596
- @scenario_folder = @scenario_file_name.split(/\W+/)[0].capitalize.to_s
597
646
  @feature_id = (@feature_name.split(/\W+/)[1]).to_s
598
- else
599
- @scenario_folder = @scenario_file_name.split('.')[0].capitalize.to_s
600
647
  end
601
648
  puts "\nSimulating features of '#{@feature_name}' as directed by '#{@scenario_file_name}'...\n\n"
602
649
  scenario_runner = URBANopt::Scenario::ScenarioRunnerOSW.new
@@ -617,54 +664,65 @@ module URBANopt
617
664
  res = check_reader
618
665
  if res[:reader] == false
619
666
  puts "\nURBANopt ditto reader error: #{res[:message]}"
620
- abort("\nYou must install urbanopt-ditto-reader to use this workflow: https://github.com/urbanopt/urbanopt-ditto-reader \n")
667
+ abort("\nYou must install urbanopt-ditto-reader to use this workflow: pip install urbanopt-ditto-reader \n")
621
668
  end
622
669
 
623
- name = File.basename(@scenario_file_name, File.extname(@scenario_file_name))
624
- run_dir = File.join(@root_dir, 'run', name.downcase)
625
- featurefile = File.join(@root_dir, @feature_name)
670
+ # If a config file is supplied, use the data specified there.
671
+ if @opthash.subopts[:config]
672
+ opendss_config = JSON.parse(File.read(File.expand_path(@opthash.subopts[:config])), symbolize_names: true)
673
+ config_scenario_file = opendss_config[:urbanopt_scenario_file]
674
+ config_root_dir = File.dirname(config_scenario_file)
675
+ config_scenario_name = File.basename(config_scenario_file, File.extname(config_scenario_file))
676
+ run_dir = File.join(config_root_dir, 'run', config_scenario_name.downcase)
677
+ featurefile = File.expand_path(opendss_config[:urbanopt_geojson_file])
678
+ # Otherwise use the user-supplied scenario & feature files
679
+ elsif @opthash.subopts[:scenario] && @opthash.subopts[:feature]
680
+ run_dir = File.join(@root_dir, 'run', @scenario_name.downcase)
681
+ featurefile = File.join(@root_dir, @feature_name)
682
+ end
626
683
 
627
684
  # Ensure building simulations have been run already
628
685
  begin
629
- feature_list = Pathname.new(run_dir).children.select(&:directory?)
630
- first_feature = File.basename(feature_list[0])
631
- if !File.exist?(File.join(run_dir, first_feature, 'eplusout.sql'))
632
- abort("\nERROR: URBANopt simulations are required before using opendss. Please run and process simulations, then try again.\n")
686
+ feature_list = Pathname.new(File.expand_path(run_dir)).children.select(&:directory?)
687
+ some_random_feature = File.basename(feature_list[0])
688
+ if !File.exist?(File.expand_path(File.join(run_dir, some_random_feature, 'eplusout.sql')))
689
+ abort("ERROR: URBANopt simulations are required before using opendss. Please run and process simulations, then try again.\n")
633
690
  end
634
691
  rescue Errno::ENOENT # Same abort message if there is no run_dir
635
- abort("\nERROR: URBANopt simulations are required before using opendss. Please run and process simulations, then try again.\n")
636
- end
637
-
638
- # TODO: make this work for virtualenv
639
- # TODO: document adding PYTHON env var
640
-
641
- # create config hash
642
- config = {}
643
-
644
- config['use_reopt'] = @opthash.subopts[:reopt] == true
645
- config['urbanopt_scenario'] = run_dir
646
- config['geojson_file'] = featurefile
647
- if @opthash.subopts[:equipment]
648
- config['equipment_file'] = @opthash.subopts[:equipment].to_s
692
+ abort("ERROR: URBANopt simulations are required before using opendss. Please run and process simulations, then try again.\n")
693
+ end
694
+
695
+ # We're calling the python cli that gets installed when the user installs ditto-reader.
696
+ # If ditto-reader is installed into a venv (recommended), that venv must be activated when this command is called.
697
+ ditto_cli_root = "ditto_reader_cli run-opendss "
698
+ if @opthash.subopts[:config]
699
+ ditto_cli_addition = "--config #{@opthash.subopts[:config]}"
700
+ elsif @opthash.subopts[:scenario] && @opthash.subopts[:feature]
701
+ ditto_cli_addition = "--scenario_file #{@opthash.subopts[:scenario]} --feature_file #{@opthash.subopts[:feature]}"
702
+ if @opthash.subopts[:reopt]
703
+ ditto_cli_addition += " --reopt"
704
+ end
705
+ if @opthash.subopts[:equipment]
706
+ ditto_cli_addition += " --equipment #{@opthash.subopts[:equipment]}"
707
+ end
708
+ if @opthash.subopts[:timestep]
709
+ ditto_cli_addition += " --timestep #{@opthash.subopts[:timestep]}"
710
+ end
711
+ if @opthash.subopts[:start_time]
712
+ ditto_cli_addition += " --start_time '#{@opthash.subopts[:start_time]}'"
713
+ end
714
+ if @opthash.subopts[:end_time]
715
+ ditto_cli_addition += " --end_time '#{@opthash.subopts[:end_time]}'"
716
+ end
717
+ else
718
+ abort("\nCommand must include ScenarioFile & FeatureFile, or a config file that specifies both. Please try again")
649
719
  end
650
- config['opendss_folder'] = File.join(config['urbanopt_scenario'], 'opendss')
651
-
652
- # TODO: allow users to specify ditto install location?
653
- # Currently using ditto within the urbanopt-ditto-reader install
654
-
655
- # run ditto_reader
656
- pyfrom 'urbanopt_ditto_reader', import: 'UrbanoptDittoReader'
657
-
658
720
  begin
659
- pconf = PyCall::Dict.new(config)
660
- r = UrbanoptDittoReader.new(pconf)
661
- r.run
662
- rescue StandardError => e
663
- abort("\nOpenDSS run did not complete successfully: #{e.message}")
721
+ system(ditto_cli_root + ditto_cli_addition)
722
+ rescue FileNotFoundError
723
+ abort("\nMust post-process results before running opendss. We recommend 'process --default'." \
724
+ "Once opendss is run, you may then 'process --opendss'")
664
725
  end
665
-
666
- puts "\nDone. Results located in #{config['opendss_folder']}\n"
667
-
668
726
  end
669
727
 
670
728
  # Post-process the scenario
@@ -676,16 +734,14 @@ module URBANopt
676
734
  puts 'Post-processing URBANopt results'
677
735
 
678
736
  # delete process_status.json
679
- process_filename = File.join(@root_dir, 'run', @scenario_file_name.split('.')[0].downcase, 'process_status.json')
737
+ process_filename = File.join(@root_dir, 'run', @scenario_name.downcase, 'process_status.json')
680
738
  FileUtils.rm_rf(process_filename) if File.exist?(process_filename)
681
739
  results = []
682
740
 
683
- @scenario_folder = @scenario_file_name.split('.')[0].capitalize.to_s
684
741
  default_post_processor = URBANopt::Scenario::ScenarioDefaultPostProcessor.new(run_func)
685
742
  scenario_report = default_post_processor.run
686
743
  scenario_report.save
687
- scenario_report.feature_reports.each(&:save_json_report)
688
- scenario_report.feature_reports.each(&:save_csv_report)
744
+ scenario_report.feature_reports.each(&:save)
689
745
 
690
746
  if @opthash.subopts[:with_database] == true
691
747
  default_post_processor.create_scenario_db_file
@@ -696,7 +752,7 @@ module URBANopt
696
752
  results << { "process_type": 'default', "status": 'Complete', "timestamp": Time.now.strftime('%Y-%m-%dT%k:%M:%S.%L') }
697
753
  elsif @opthash.subopts[:opendss] == true
698
754
  puts "\nPost-processing OpenDSS results\n"
699
- opendss_folder = File.join(@root_dir, 'run', @scenario_file_name.split('.')[0], 'opendss')
755
+ opendss_folder = File.join(@root_dir, 'run', @scenario_name, 'opendss')
700
756
  if File.directory?(opendss_folder)
701
757
  opendss_folder_name = File.basename(opendss_folder)
702
758
  opendss_post_processor = URBANopt::Scenario::OpenDSSPostProcessor.new(scenario_report, opendss_results_dir_name = opendss_folder_name)
@@ -709,7 +765,13 @@ module URBANopt
709
765
  end
710
766
  elsif (@opthash.subopts[:reopt_scenario] == true) || (@opthash.subopts[:reopt_feature] == true)
711
767
  scenario_base = default_post_processor.scenario_base
712
- reopt_post_processor = URBANopt::REopt::REoptPostProcessor.new(scenario_report, scenario_base.scenario_reopt_assumptions_file, scenario_base.reopt_feature_assumptions, DEVELOPER_NREL_KEY)
768
+ # see if reopt-scenario-assumptions-file was passed in, otherwise use the default
769
+ scenario_assumptions = scenario_base.scenario_reopt_assumptions_file
770
+ if (@opthash.subopts[:reopt_scenario] == true && @opthash.subopts[:reopt_scenario_assumptions_file])
771
+ scenario_assumptions = File.expand_path(@opthash.subopts[:reopt_scenario_assumptions_file]).to_s
772
+ end
773
+ puts "\nRunning the REopt Scenario post-processor with scenario assumptions file: #{scenario_assumptions}\n"
774
+ reopt_post_processor = URBANopt::REopt::REoptPostProcessor.new(scenario_report, scenario_assumptions, scenario_base.reopt_feature_assumptions, DEVELOPER_NREL_KEY)
713
775
  if @opthash.subopts[:reopt_scenario] == true
714
776
  puts "\nPost-processing entire scenario with REopt\n"
715
777
  scenario_report_scenario = reopt_post_processor.run_scenario_report(scenario_report: scenario_report, save_name: 'scenario_optimization')
@@ -738,7 +800,6 @@ module URBANopt
738
800
  if !@opthash.subopts[:feature].to_s.include? (".json")
739
801
  abort("\nERROR: No Feature File specified. Please specify Feature File for creating scenario visualizations.\n")
740
802
  end
741
- @feature_path = File.split(File.absolute_path(@opthash.subopts[:feature]))[0]
742
803
  run_dir = File.join(@feature_path, 'run')
743
804
  scenario_folders = []
744
805
  scenario_report_exists = false
@@ -766,7 +827,7 @@ module URBANopt
766
827
  end
767
828
  end
768
829
  end
769
- html_out_path = File.join(@feature_path, '/run/scenario_comparison.html')
830
+ html_out_path = File.join(@feature_path, 'run', 'scenario_comparison.html')
770
831
  FileUtils.cp(html_in_path, html_out_path)
771
832
  puts "\nDone\n"
772
833
  end
@@ -776,14 +837,13 @@ module URBANopt
776
837
  if !@opthash.subopts[:scenario].to_s.include? (".csv")
777
838
  abort("\nERROR: No Scenario File specified. Please specify Scenario File for feature visualizations.\n")
778
839
  end
779
- @root_dir, @scenario_file_name = File.split(File.absolute_path(@opthash.subopts[:scenario]))
780
- name = File.basename(@scenario_file_name, File.extname(@scenario_file_name))
781
- run_dir = File.join(@root_dir, 'run', name.downcase)
840
+ run_dir = File.join(@root_dir, 'run', @scenario_name.downcase)
782
841
  feature_report_exists = false
783
- feature_id = CSV.read(File.absolute_path(@opthash.subopts[:scenario]), headers: true)
842
+ csv = CSV.read(File.expand_path(@opthash.subopts[:scenario]), headers: true)
843
+ feature_names = csv['Feature Name']
784
844
  feature_folders = []
785
845
  # loop through building feature ids from scenario csv
786
- feature_id['Feature Id'].each do |feature|
846
+ csv['Feature Id'].each do |feature|
787
847
  feature_report = File.join(run_dir, feature, 'feature_reports')
788
848
  if File.exist?(feature_report)
789
849
  feature_report_exists = true
@@ -794,7 +854,7 @@ module URBANopt
794
854
  end
795
855
  if feature_report_exists == true
796
856
  puts "\nCreating visualizations for Feature results in the Scenario\n"
797
- URBANopt::Scenario::ResultVisualization.create_visualization(feature_folders, true)
857
+ URBANopt::Scenario::ResultVisualization.create_visualization(feature_folders, true, feature_names)
798
858
  vis_file_path = File.join(@root_dir, 'visualization')
799
859
  if !File.exist?(vis_file_path)
800
860
  Dir.mkdir File.join(@root_dir, 'visualization')
@@ -807,7 +867,7 @@ module URBANopt
807
867
  end
808
868
  end
809
869
  end
810
- html_out_path = File.join(@root_dir, 'run', name, 'feature_comparison.html')
870
+ html_out_path = File.join(@root_dir, 'run', @scenario_name, 'feature_comparison.html')
811
871
  FileUtils.cp(html_in_path, html_out_path)
812
872
  puts "\nDone\n"
813
873
  end
@@ -815,12 +875,66 @@ module URBANopt
815
875
 
816
876
  end
817
877
 
878
+ # Compare EUI in default_feature_reports.json with a user-editable set of bounds
879
+ if @opthash.command == 'validate'
880
+ puts "\nValidating:"
881
+ if !@opthash.subopts[:eui] && !@opthash.subopts[:foobar]
882
+ abort("\nERROR: No type of validation specified. Please enter a sub-command when using validate.\n")
883
+ end
884
+ # Validate EUI
885
+ if @opthash.subopts[:eui]
886
+ puts "Energy Use Intensity"
887
+ original_feature_file = JSON.parse(File.read(File.expand_path(@opthash.subopts[:feature])), symbolize_names: true)
888
+ # Build list of paths to each feature in the given Scenario
889
+ feature_ids = CSV.read(@opthash.subopts[:scenario], headers: true)
890
+ feature_list = []
891
+ feature_ids['Feature Id'].each do |feature|
892
+ if Dir.exist?(File.join(@root_dir, 'run', @scenario_name, feature))
893
+ feature_list << File.join(@root_dir, 'run', @scenario_name, feature)
894
+ else
895
+ puts "Warning: did not find a directory for FeatureID: #{feature} ...skipping"
896
+ end
897
+ end
898
+ validation_file_name = File.basename(@opthash.subopts[:eui])
899
+ validation_params = YAML.load_file(File.expand_path(@opthash.subopts[:eui]))
900
+ unit_value = validation_params['EUI'][@opthash.subopts[:units]]['Units']
901
+ # Validate EUI for only the features used in the scenario
902
+ original_feature_file[:features].each do |feature| # Loop through each feature in the scenario
903
+ next if !feature_ids['Feature Id'].include? feature[:properties][:id] # Skip features not in the scenario
904
+ feature_list.each do |feature_path| # Match ids in FeatureFile
905
+ next if feature_path.split('/')[-1] != feature[:properties][:id] # Skip until feature ids match
906
+ feature_dir_list = Pathname.new(feature_path).children.select(&:directory?) # Folders in the feature directory
907
+ feature_dir_list.each do |feature_dir|
908
+ next if !File.basename(feature_dir).include? "default_feature_reports" # Get the folder which can have a variable name
909
+ @json_feature_report = JSON.parse(File.read(File.join(feature_dir, 'default_feature_reports.json')), symbolize_names: true)
910
+ end
911
+ if !@json_feature_report[:reporting_periods][0][:site_EUI_kbtu_per_ft2]
912
+ abort("ERROR: No EUI present. Perhaps you didn't simulate an entire year?")
913
+ end
914
+ if @opthash.subopts[:units] == 'IP'
915
+ feature_eui_value = @json_feature_report[:reporting_periods][0][:site_EUI_kbtu_per_ft2]
916
+ elsif @opthash.subopts[:units] == 'SI'
917
+ feature_eui_value = @json_feature_report[:reporting_periods][0][:site_EUI_kwh_per_m2]
918
+ else
919
+ abort("\nERROR: Units type not recognized. Please use a valid option in the CLI")
920
+ end
921
+ building_type = feature[:properties][:building_type] # From FeatureFile
922
+ if feature_eui_value > validation_params['EUI'][@opthash.subopts[:units]][building_type]['max']
923
+ puts "\nFeature #{File.basename(feature_path)} EUI of #{feature_eui_value.round(2)} #{unit_value} is greater than the validation maximum."
924
+ elsif feature_eui_value < validation_params['EUI'][@opthash.subopts[:units]][building_type]['min']
925
+ puts "\nFeature #{File.basename(feature_path)} EUI of #{feature_eui_value.round(2)} #{unit_value} is less than the validation minimum."
926
+ else
927
+ puts "\nFeature #{File.basename(feature_path)} EUI of #{feature_eui_value.round(2)} #{unit_value} is within bounds set by #{validation_file_name}."
928
+ end
929
+ end
930
+ end
931
+ end
932
+ end
933
+
818
934
  # Delete simulations from a scenario
819
935
  if @opthash.command == 'delete'
820
- scenario_name = @scenario_file_name.split('.')[0]
821
- scenario_path = File.absolute_path(@root_dir)
822
- scenario_results_dir = File.join(scenario_path, 'run', scenario_name)
823
- puts "\nDeleting previous results from '#{@scenario_file_name}'...\n"
936
+ scenario_results_dir = File.join(@root_dir, 'run', @scenario_name.downcase)
937
+ puts "\nDeleting previous results from '#{@scenario_name}'...\n"
824
938
  FileUtils.rm_rf(scenario_results_dir)
825
939
  puts "\nDone\n"
826
940
  end