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.
- 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
|