urbanopt-cli 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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