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.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/CHANGELOG.md +20 -0
- data/CMakeLists.txt +10 -10
- data/FindOpenStudioSDK.cmake +6 -6
- data/Gemfile +9 -9
- data/example_files/Gemfile +3 -3
- data/example_files/example_project.json +35 -20
- data/example_files/example_project_with_electric_network.json +2116 -0
- data/example_files/mappers/Baseline.rb +4 -1
- data/example_files/mappers/EvCharging.rb +131 -0
- data/example_files/mappers/base_workflow.osw +26 -1
- data/example_files/validation_schema.yaml +149 -0
- data/example_files/visualization/input_visualization_feature.html +213 -56
- data/example_files/visualization/input_visualization_scenario.html +92 -30
- data/lib/uo_cli.rb +204 -90
- data/lib/uo_cli/version.rb +1 -1
- data/uo_cli.gemspec +4 -6
- metadata +15 -27
- data/developer_nrel_key.rb +0 -31
@@ -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] }}
|
186
|
-
<span class="md-subhead">Monthly Net Energy
|
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', '
|
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
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
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
|
-
|
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(
|
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
|
-
|
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
|
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'
|
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: '
|
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
|
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.
|
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.
|
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.
|
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
|
-
|
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(
|
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(
|
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.
|
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.
|
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
|
-
|
377
|
-
|
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('
|
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! '
|
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('
|
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
|
556
|
+
puts 'Checking for urbanopt-ditto-reader...'
|
507
557
|
|
508
|
-
stdout, stderr, status = Open3.capture3('
|
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 = /^
|
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
|
573
|
+
results[:message] = "Found urbanopt-ditto-reader version #{version}"
|
524
574
|
puts "...#{results[:message]}"
|
525
575
|
results[:reader] = true
|
526
|
-
puts "
|
576
|
+
puts "urbanopt-ditto-reader check done. \n\n"
|
527
577
|
return results
|
528
578
|
else
|
529
|
-
results[:message] = '
|
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] = '
|
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:
|
667
|
+
abort("\nYou must install urbanopt-ditto-reader to use this workflow: pip install urbanopt-ditto-reader \n")
|
621
668
|
end
|
622
669
|
|
623
|
-
|
624
|
-
|
625
|
-
|
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
|
-
|
631
|
-
if !File.exist?(File.join(run_dir,
|
632
|
-
abort("
|
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("
|
636
|
-
end
|
637
|
-
|
638
|
-
#
|
639
|
-
#
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
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
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
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', @
|
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(&:
|
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', @
|
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
|
-
|
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, '
|
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
|
-
|
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
|
-
|
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
|
-
|
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',
|
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
|
-
|
821
|
-
|
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
|