urbanopt-rnm-us 0.2.0 → 0.4.0
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/CHANGELOG.md +11 -0
- data/Gemfile +6 -0
- data/LICENSE.md +2 -2
- data/README.md +7 -0
- data/Rakefile +33 -2
- data/catalogs/extended_catalog.json +1928 -1796
- data/lib/urbanopt/rnm/api_client.rb +11 -11
- data/lib/urbanopt/rnm/capacitor_opendss.rb +1 -1
- data/lib/urbanopt/rnm/carson_eq.rb +1 -1
- data/lib/urbanopt/rnm/consumers.rb +8 -7
- data/lib/urbanopt/rnm/conversion_to_opendss.rb +1 -1
- data/lib/urbanopt/rnm/geojson_input.rb +3 -3
- data/lib/urbanopt/rnm/input_files.rb +2 -2
- data/lib/urbanopt/rnm/logger.rb +1 -1
- data/lib/urbanopt/rnm/oh_ug_rate.rb +1 -1
- data/lib/urbanopt/rnm/post_processor.rb +14 -16
- data/lib/urbanopt/rnm/processor_opendss_catalog.rb +1 -1
- data/lib/urbanopt/rnm/prosumers.rb +52 -52
- data/lib/urbanopt/rnm/rnm_us_catalog_conversion.rb +2 -2
- data/lib/urbanopt/rnm/runner.rb +12 -3
- data/lib/urbanopt/rnm/scenario_report.rb +2 -1
- data/lib/urbanopt/rnm/substation_location.rb +1 -1
- data/lib/urbanopt/rnm/transformer_opendss.rb +2 -6
- data/lib/urbanopt/rnm/validation/main_validation.py +51 -0
- data/lib/urbanopt/rnm/validation/opendss_interface.py +234 -0
- data/lib/urbanopt/rnm/validation/plot_lib.py +222 -0
- data/lib/urbanopt/rnm/validation.rb +75 -0
- data/lib/urbanopt/rnm/version.rb +2 -2
- data/lib/urbanopt/rnm/wires_class.rb +1 -1
- data/lib/urbanopt/rnm/wires_opendss.rb +8 -8
- data/lib/urbanopt/rnm.rb +2 -1
- data/lib/urbanopt-rnm.rb +1 -1
- data/opendss_catalog.json +2216 -0
- data/urbanopt-rnm-us-gem.gemspec +1 -1
- metadata +9 -4
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt (tm), Copyright (c) 2019-
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -61,9 +61,9 @@ module URBANopt
|
|
61
61
|
|
62
62
|
@use_localhost = use_localhost
|
63
63
|
if @use_localhost
|
64
|
-
@base_api = 'http://0.0.0.0:8080/api/
|
64
|
+
@base_api = 'http://0.0.0.0:8080/api/v2/'
|
65
65
|
else
|
66
|
-
@base_api = 'https://rnm.urbanopt.net/api/
|
66
|
+
@base_api = 'https://rnm.urbanopt.net/api/v2/'
|
67
67
|
end
|
68
68
|
|
69
69
|
puts "Running RNM-US at #{@base_api}"
|
@@ -197,21 +197,21 @@ module URBANopt
|
|
197
197
|
else
|
198
198
|
# edge case, check for results
|
199
199
|
if data['results'].nil?
|
200
|
-
puts
|
200
|
+
puts 'got a 200 but results are null...trying again'
|
201
201
|
tries += 1
|
202
202
|
sleep(3)
|
203
203
|
else
|
204
204
|
# get results
|
205
205
|
@results = data['results'] || []
|
206
206
|
|
207
|
-
puts
|
207
|
+
puts 'downloading results'
|
208
208
|
# download results
|
209
209
|
download_results
|
210
210
|
return @results
|
211
211
|
end
|
212
212
|
end
|
213
213
|
else
|
214
|
-
puts
|
214
|
+
puts 'no status yet...trying again'
|
215
215
|
tries += 1
|
216
216
|
sleep(3)
|
217
217
|
end
|
@@ -231,10 +231,10 @@ module URBANopt
|
|
231
231
|
sleep(3)
|
232
232
|
end
|
233
233
|
end
|
234
|
-
rescue =>
|
234
|
+
rescue StandardError => e
|
235
235
|
@@logger.error("Error retrieving simulation #{@sim_id}.")
|
236
|
-
@@logger.error(
|
237
|
-
raise
|
236
|
+
@@logger.error(e.message)
|
237
|
+
raise e.message
|
238
238
|
end
|
239
239
|
end
|
240
240
|
if !done
|
@@ -268,7 +268,7 @@ module URBANopt
|
|
268
268
|
|
269
269
|
File.open(file_path, 'wb') { |f| f.write streamed.join }
|
270
270
|
puts "RNM-US results.zip downloaded to #{@rnm_dir}"
|
271
|
-
|
271
|
+
|
272
272
|
# unzip
|
273
273
|
Zip::File.open(file_path) do |zip_file|
|
274
274
|
zip_file.each do |f|
|
@@ -277,7 +277,7 @@ module URBANopt
|
|
277
277
|
zip_file.extract(f, f_path) unless File.exist?(f_path)
|
278
278
|
end
|
279
279
|
end
|
280
|
-
puts
|
280
|
+
puts 'results.zip extracted'
|
281
281
|
# delete zip
|
282
282
|
File.delete(file_path)
|
283
283
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt (tm), Copyright (c) 2019-
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt (tm), Copyright (c) 2019-
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt (tm), Copyright (c) 2019-
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -79,7 +79,7 @@ module URBANopt
|
|
79
79
|
yearly_profile_node_reactive = []
|
80
80
|
nodes_per_bldg, area, medium_voltage = av_peak_cons_per_building_type(folder['building_types'])
|
81
81
|
# the default variables are defined (i.e. type and rurality type)
|
82
|
-
puts
|
82
|
+
puts 'consumers 82'
|
83
83
|
closest_node = building_map[3].split('_')[1].to_i # refers to the node, found in the class above
|
84
84
|
node = closest_node
|
85
85
|
cont = 1
|
@@ -144,10 +144,10 @@ module URBANopt
|
|
144
144
|
case peak_apparent_power
|
145
145
|
when 0..@lv_limit[:single_phase] # set by the catalog limits
|
146
146
|
phases = 1
|
147
|
-
voltage_default = 0.
|
147
|
+
voltage_default = 0.12
|
148
148
|
when @lv_limit[:single_phase]..@lv_limit[:three_phase] # defined from the catalog (from the wires)
|
149
149
|
phases = 3
|
150
|
-
voltage_default = 0.
|
150
|
+
voltage_default = 0.48
|
151
151
|
# MV and 3 phases untill 16 MVA, defined by the SMART-DS project
|
152
152
|
when @lv_limit[:three_phase]..16000
|
153
153
|
phases = 3
|
@@ -176,7 +176,7 @@ module URBANopt
|
|
176
176
|
conservative_factor = 0.8 # considered as a reasonable assumption, but this value could be changed
|
177
177
|
average_peak_folder = JSON.parse(File.read(@average_building_peak_catalog_path))
|
178
178
|
for i in 0..feature_file.length - 1
|
179
|
-
area = feature_file[i].
|
179
|
+
area = feature_file[i].key?('floor_area') ? (feature_file[i]['floor_area']).round(2) : feature_file[i]['floor_area_sqft'].round(2)
|
180
180
|
building_type = feature_file[i]['building_type'] # it specifies the type of building, sometimes it is directly the sub-type
|
181
181
|
counter = 0 # counter to find number of buildings type belonging to same "category"
|
182
182
|
average_peak_folder.each do |building_class|
|
@@ -201,6 +201,7 @@ module URBANopt
|
|
201
201
|
average_peak = mixed_use_av_peak # average peak per mixed use considering the building types which are in this building
|
202
202
|
area = area_mixed_use
|
203
203
|
end
|
204
|
+
|
204
205
|
nodes_per_bldg = (average_peak / (@lv_limit[:three_phase] * @power_factor * conservative_factor)).to_f.ceil # computing number of nodes per building
|
205
206
|
if nodes_per_bldg > @max_num_lv_nodes # to define this as an input in the geojson file
|
206
207
|
nodes_per_bldg = 1
|
@@ -215,10 +216,10 @@ module URBANopt
|
|
215
216
|
# the method passes as arguments the urbanopt json and csv output file for each feature and the building coordinates previously calculated
|
216
217
|
# and the "extreme" hour used to plan the network
|
217
218
|
def customer_files_load(csv_feature_report, json_feature_report, building_map, building_nodes, hour)
|
218
|
-
n_timestep_per_hour = json_feature_report[
|
219
|
+
n_timestep_per_hour = json_feature_report['timesteps_per_hour'].to_i
|
219
220
|
profiles = Hash.new { |h, k| h[k] = [] }
|
220
221
|
single_values = Hash.new(0)
|
221
|
-
hours = 24 * n_timestep_per_hour -1
|
222
|
+
hours = 24 * n_timestep_per_hour - 1
|
222
223
|
feature_type = json_feature_report['program']['building_types'][0]['building_type']
|
223
224
|
residential_building_types = ['Single-Family Detached', 'Single-Family Attached', 'Multifamily', 'Single-Family', 'Multifamily Detached (2 to 4 units)', 'Multifamily Detached (5 or more units)']
|
224
225
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt (tm), Copyright (c) 2019-
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt (tm), Copyright (c) 2019-
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -143,7 +143,7 @@ module URBANopt
|
|
143
143
|
# creating a method passing the GeoJSON file from URBANopt as the argument to define options that can be modified by the user
|
144
144
|
# streets and building and primary substation coordinates
|
145
145
|
# and returning the street coordinates array, the building coordinates array and the tot number of buildings in the project
|
146
|
-
def coordinates_feature_hash(geojson_hash,scenario_features=[])
|
146
|
+
def coordinates_feature_hash(geojson_hash, scenario_features = [])
|
147
147
|
i = 0 # index representing the number of street_nodes
|
148
148
|
building_number = 0 # variable created to keep track the number of buildings in the project
|
149
149
|
street_number = 0 # variable created to keep track the number of streets in the project
|
@@ -209,7 +209,7 @@ module URBANopt
|
|
209
209
|
end
|
210
210
|
street_coordinates[street_number] = each_street
|
211
211
|
street_number += 1
|
212
|
-
elsif street['geometry']['type'] == 'Polygon' && street['properties']['type'] == 'Building'
|
212
|
+
elsif street['geometry']['type'] == 'Polygon' && street['properties']['type'] == 'Building' && scenario_features.include?(street['properties']['id'])
|
213
213
|
for k in 0..street['geometry']['coordinates'].length - 1
|
214
214
|
h = 0 # index representing number of nodes for each single building
|
215
215
|
building = [] # array containing every building node coordinates and id of 1 building
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt (tm), Copyright (c) 2019-
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -87,7 +87,7 @@ module URBANopt
|
|
87
87
|
# lines with the highest capacity
|
88
88
|
catalog['LINES'][1].each do |key, v|
|
89
89
|
(0..catalog['LINES'][1][key].length - 1).each do |ii|
|
90
|
-
if catalog['LINES'][1][key][ii]['Voltage(kV)']
|
90
|
+
if Float(catalog['LINES'][1][key][ii]['Voltage(kV)']) < 1
|
91
91
|
if catalog['LINES'][1][key][ii]['Line geometry'][0]['phase'] != 'N'
|
92
92
|
wire = catalog['LINES'][1][key][ii]['Line geometry'][0]['wire']
|
93
93
|
else
|
data/lib/urbanopt/rnm/logger.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt (tm), Copyright (c) 2019-
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt (tm), Copyright (c) 2019-
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt (tm), Copyright (c) 2019-
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -59,7 +59,7 @@ module URBANopt
|
|
59
59
|
@reopt = reopt
|
60
60
|
end
|
61
61
|
|
62
|
-
##
|
62
|
+
##
|
63
63
|
# Post Process report and feature file
|
64
64
|
##
|
65
65
|
def post_process
|
@@ -68,11 +68,10 @@ module URBANopt
|
|
68
68
|
puts "RNM results were added to scenario report and feature file. New files can be found in #{@results_dir}"
|
69
69
|
end
|
70
70
|
|
71
|
-
##
|
71
|
+
##
|
72
72
|
# Generate Scenario report
|
73
73
|
##
|
74
74
|
def generate_report
|
75
|
-
|
76
75
|
# calculate rnm statistics
|
77
76
|
rnm_stats = calculate_stats
|
78
77
|
|
@@ -80,17 +79,16 @@ module URBANopt
|
|
80
79
|
scenario = get_scenario
|
81
80
|
|
82
81
|
# merge stats with scenario report (before feature_reports section)
|
83
|
-
|
82
|
+
|
84
83
|
scenario['scenario_report']['rnm_results'] = rnm_stats
|
85
|
-
|
84
|
+
|
86
85
|
# save back to scenario directory as scenario_report_rnm.json
|
87
|
-
File.open(File.join(@scenario_dir, @report_filename),
|
86
|
+
File.open(File.join(@scenario_dir, @report_filename), 'w') do |f|
|
88
87
|
f.write(JSON.pretty_generate(scenario))
|
89
88
|
end
|
90
|
-
|
91
89
|
end
|
92
90
|
|
93
|
-
##
|
91
|
+
##
|
94
92
|
# Load Scenario Report
|
95
93
|
##
|
96
94
|
def get_scenario
|
@@ -98,16 +96,15 @@ module URBANopt
|
|
98
96
|
# get reopt scenario report
|
99
97
|
return JSON.parse(File.read(File.join(@scenario_dir, 'feature_optimization.json')))
|
100
98
|
else
|
101
|
-
# get default scenario report
|
99
|
+
# get default scenario report
|
102
100
|
return JSON.parse(File.read(File.join(@scenario_dir, 'default_scenario_report.json')))
|
103
101
|
end
|
104
102
|
end
|
105
103
|
|
106
|
-
##
|
104
|
+
##
|
107
105
|
# Generate new GeoJSON file
|
108
106
|
##
|
109
107
|
def generate_feature_file
|
110
|
-
|
111
108
|
# get results GeoJSON file and read in
|
112
109
|
results = JSON.parse(File.read(File.join(@results_dir, 'GeoJSON', 'Distribution_system.json')))
|
113
110
|
|
@@ -117,7 +114,7 @@ module URBANopt
|
|
117
114
|
end
|
118
115
|
|
119
116
|
# save back to scenario directory as features_and_rnm.json
|
120
|
-
|
117
|
+
File.open(File.join(@scenario_dir, @geojson_filename), 'w') do |f|
|
121
118
|
f.write(JSON.pretty_generate(@feature_file))
|
122
119
|
end
|
123
120
|
end
|
@@ -132,6 +129,7 @@ module URBANopt
|
|
132
129
|
stats['demand_generation_planning'] = []
|
133
130
|
@results['Demand/generation and number of consumers/distributed generators'].each do |item|
|
134
131
|
rec = {}
|
132
|
+
puts "ITEM VOLTAGE LEVEL: #{item['Voltage level']}, item type: #{item['Type'].strip}"
|
135
133
|
case item['Voltage level']
|
136
134
|
when 'LV'
|
137
135
|
rec['type'] = "Low Voltage (LV) #{item['Type'].strip}"
|
@@ -155,7 +153,7 @@ module URBANopt
|
|
155
153
|
|
156
154
|
# lines LV and MV
|
157
155
|
stats['electrical_lines_length'] = {}
|
158
|
-
km_to_mi = 0.621371
|
156
|
+
km_to_mi = 0.621371
|
159
157
|
@results['Length of overhead and underground electrical lines'].each do |item|
|
160
158
|
case item['Voltage level']
|
161
159
|
when 'Lines LV'
|
@@ -181,8 +179,8 @@ module URBANopt
|
|
181
179
|
@results['Summary'].each do |item|
|
182
180
|
case item['Level']
|
183
181
|
when 'LV'
|
184
|
-
|
185
|
-
|
182
|
+
stats['costs']['investment']['low_voltage_network'] = item['Investment cost']
|
183
|
+
stats['costs']['yearly_maintenance']['low_voltage_network'] = item['Preventive maintenance (yearly)']
|
186
184
|
when 'MV'
|
187
185
|
stats['costs']['investment']['medium_voltage_network'] = item['Investment cost']
|
188
186
|
stats['costs']['yearly_maintenance']['medium_voltage_network'] = item['Preventive maintenance (yearly)']
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt (tm), Copyright (c) 2019-
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt (tm), Copyright (c) 2019-
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -84,8 +84,8 @@ module URBANopt
|
|
84
84
|
# in the hour with generation max peak
|
85
85
|
# in the hour with storage max peak
|
86
86
|
|
87
|
-
for i in 0..profiles_planning[:planning_profile_cust_active].length-1
|
88
|
-
hourly_app_power = ((profiles_planning[:planning_profile_cust_active][i] + profiles_planning[:planning_profile_storage_active][i] - profiles_planning[:planning_profile_dg_active][i])
|
87
|
+
for i in 0..profiles_planning[:planning_profile_cust_active].length - 1
|
88
|
+
hourly_app_power = ((profiles_planning[:planning_profile_cust_active][i] + profiles_planning[:planning_profile_storage_active][i] - profiles_planning[:planning_profile_dg_active][i]) / @power_factor).abs
|
89
89
|
if hourly_app_power > peak_app_power_node
|
90
90
|
peak_app_power_node = hourly_app_power
|
91
91
|
end
|
@@ -103,9 +103,9 @@ module URBANopt
|
|
103
103
|
@profile_customer_p.push([id, 48, profiles_planning[:planning_profile_cust_active]])
|
104
104
|
@profile_customer_p_ext.push([id, 8760, profiles[:yearly_profile_cust_active]])
|
105
105
|
@profile_customer_q_ext.push([id, 8760, profiles[:yearly_profile_cust_reactive]])
|
106
|
-
|
107
|
-
if der_capacity[:storage]
|
108
|
-
@customers.push([building_map, id_batt, voltage_default, single_values[:peak_active_power_storage],single_values[:peak_reactive_power_storage], phases])
|
106
|
+
|
107
|
+
if !der_capacity[:storage].nil? && der_capacity[:storage] > 0
|
108
|
+
@customers.push([building_map, id_batt, voltage_default, single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], phases])
|
109
109
|
@customers_ext.push([building_map, id_batt, voltage_default, single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], phases, area, height, (single_values[:energy]).round(2), single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], users])
|
110
110
|
@profile_customer_q.push([id_batt, 48, profiles_planning[:planning_profile_storage_reactive]])
|
111
111
|
@profile_customer_p.push([id_batt, 48, profiles_planning[:planning_profile_storage_active]])
|
@@ -138,7 +138,7 @@ module URBANopt
|
|
138
138
|
cont = 1
|
139
139
|
cont_reverse = 1
|
140
140
|
nodes_consumers = nodes_per_bldg - 1
|
141
|
-
|
141
|
+
|
142
142
|
for i in 1..nodes_per_bldg
|
143
143
|
coordinates = building_map
|
144
144
|
node = closest_node + cont # to set the new nodes with enough distance among each others
|
@@ -146,10 +146,10 @@ module URBANopt
|
|
146
146
|
if i > 1 && node <= building_nodes.length - 2
|
147
147
|
coordinates = building_nodes[node] # take the closest building node index to the street and pass the nodes after it
|
148
148
|
cont += 1
|
149
|
-
elsif i > 1
|
149
|
+
elsif i > 1
|
150
150
|
coordinates = building_nodes[node_reverse]
|
151
151
|
cont_reverse += 1
|
152
|
-
end
|
152
|
+
end
|
153
153
|
# this condition is used to firstly place the building consumption nodes and then the last node
|
154
154
|
# to be placed is the one referred to DG and battery for the building
|
155
155
|
if i < nodes_per_bldg # considering the consumers nodes
|
@@ -159,11 +159,11 @@ module URBANopt
|
|
159
159
|
peak_reactive_power_cons = (single_values[:peak_reactive_power_cons] / nodes_consumers).round(2)
|
160
160
|
voltage_default, phases = voltage_values(peak_active_power_cons / @power_factor)
|
161
161
|
for k in 0..profiles_planning[:planning_profile_cust_active].length - 1
|
162
|
-
|
163
|
-
|
162
|
+
planning_profile_node_active[k] = (profiles_planning[:planning_profile_cust_active][k] / nodes_consumers).round(2)
|
163
|
+
planning_profile_node_reactive[k] = (profiles_planning[:planning_profile_cust_reactive][k] / nodes_consumers).round(2)
|
164
164
|
end
|
165
165
|
for k in 0..profiles[:yearly_profile_cust_active].length - 1
|
166
|
-
yearly_profile_node_active[k] = (profiles[:yearly_profile_cust_active][k] / nodes_consumers).round(2)
|
166
|
+
yearly_profile_node_active[k] = (profiles[:yearly_profile_cust_active][k] / nodes_consumers).round(2)
|
167
167
|
yearly_profile_node_reactive[k] = (profiles[:yearly_profile_cust_reactive][k] / nodes_consumers).round(2)
|
168
168
|
end
|
169
169
|
@customers.push([coordinates, id, voltage_default, peak_active_power_cons, peak_reactive_power_cons, phases])
|
@@ -174,7 +174,7 @@ module URBANopt
|
|
174
174
|
@profile_customer_q_ext.push([id, 8760, yearly_profile_node_reactive])
|
175
175
|
else
|
176
176
|
# considering the DG and battery
|
177
|
-
voltage_default, phases = voltage_values(der_capacity[:dg]) #assuming that the pv capacity is always higher than battery capacity
|
177
|
+
voltage_default, phases = voltage_values(der_capacity[:dg]) # assuming that the pv capacity is always higher than battery capacity
|
178
178
|
id_dg = "#{coordinates[3]}_DG"
|
179
179
|
id_batt = "#{coordinates[3]}_battery"
|
180
180
|
coordinates.pop
|
@@ -183,7 +183,7 @@ module URBANopt
|
|
183
183
|
@dg_profile_q.push([id_dg, 48, profiles_planning[:planning_profile_dg_reactive]])
|
184
184
|
@profile_dg_p_extended.push([id_dg, 8760, profiles[:yearly_profile_dg_active]])
|
185
185
|
@profile_dg_q_extended.push([id_dg, 8760, profiles[:yearly_profile_dg_reactive]])
|
186
|
-
if der_capacity[:storage]
|
186
|
+
if !der_capacity[:storage].nil? && der_capacity[:storage] > 0
|
187
187
|
@customers.push([coordinates, id_batt, voltage_default, single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], phases])
|
188
188
|
@customers_ext.push([coordinates, id_batt, voltage_default, single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], phases, area, height, (single_values[:energy]).round(2), single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], users])
|
189
189
|
@profile_customer_q.push([id_batt, 48, profiles_planning[:planning_profile_storage_reactive]])
|
@@ -201,10 +201,10 @@ module URBANopt
|
|
201
201
|
case peak_apparent_power
|
202
202
|
when -10000..@lv_limit[:single_phase] # set by the catalog limits
|
203
203
|
phases = 1
|
204
|
-
voltage_default = 0.
|
204
|
+
voltage_default = 0.12
|
205
205
|
when @lv_limit[:single_phase]..@lv_limit[:three_phase] # defined from the catalog (from the wires)
|
206
206
|
phases = 3
|
207
|
-
voltage_default = 0.
|
207
|
+
voltage_default = 0.48
|
208
208
|
# MV and 3 phases untill 16 MVA defined by SMART-DS project
|
209
209
|
when @lv_limit[:three_phase]..16000
|
210
210
|
phases = 3
|
@@ -249,15 +249,15 @@ module URBANopt
|
|
249
249
|
average_peak_folder = JSON.parse(File.read(@average_building_peak_catalog_path))
|
250
250
|
for i in 0..feature_file.length - 1
|
251
251
|
area = feature_file[i].key?('floor_area') ? (feature_file[i]['floor_area']).round(2) : (feature_file[i]['floor_area_sqft']).round(2)
|
252
|
-
building_type = feature_file[i]['building_type'] #it specifies the type of building, sometimes it is directly the sub-type
|
252
|
+
building_type = feature_file[i]['building_type'] # it specifies the type of building, sometimes it is directly the sub-type
|
253
253
|
counter = 0 # counter to find number of buildings type belonging to same "category"
|
254
254
|
average_peak_folder.each do |building_class|
|
255
|
-
if
|
255
|
+
if building_type == building_class['building type'] || building_type == building_class['sub-type']
|
256
256
|
average_peak = (building_class['average peak demand (kW/ft2)'].to_f * area).to_f.round(4) # finding the average peak considering the floor area of the bilding under consideration
|
257
257
|
average_peak_by_size[counter] = average_peak
|
258
258
|
floor_area[counter] = (building_class['floor_area (ft2)'] - area).abs # minimum difference among area and area from the prototypes defined by DOE
|
259
259
|
counter += 1
|
260
|
-
|
260
|
+
# in this way I don t consider residential and I assume it s average_peak = 0, it is ok because we assume always 1 node per RES consumers, single-detached family houses
|
261
261
|
end
|
262
262
|
end
|
263
263
|
if counter > 1
|
@@ -273,21 +273,21 @@ module URBANopt
|
|
273
273
|
average_peak = mixed_use_av_peak # average peak per mixed use considering the building types which are in this building
|
274
274
|
area = area_mixed_use
|
275
275
|
end
|
276
|
-
nodes_per_bldg = (
|
277
|
-
if nodes_per_bldg > @max_num_lv_nodes #that it is equal to how it was before
|
276
|
+
nodes_per_bldg = (average_peak / (@lv_limit[:three_phase] * @power_factor * conservative_factor)).to_f.ceil # computing number of nodes per building
|
277
|
+
if nodes_per_bldg > @max_num_lv_nodes # that it is equal to how it was before
|
278
278
|
nodes_per_bldg = 1
|
279
279
|
@medium_voltage = true
|
280
280
|
end
|
281
|
-
|
282
|
-
|
281
|
+
|
282
|
+
nodes_per_bldg += 1 # tacking into account the extra node for distributed generation and the battery
|
283
283
|
return nodes_per_bldg, area
|
284
284
|
end
|
285
285
|
|
286
286
|
# method to order profiles consistently
|
287
287
|
def profiles_planning_creation(profiles_planning, power, single_values, i, hours, power_factor)
|
288
|
-
profiles_planning[:planning_profile_cust_active][i] = power[
|
288
|
+
profiles_planning[:planning_profile_cust_active][i] = power['REopt:Electricity:Load:Total(kw)'].to_f
|
289
289
|
profiles_planning[:planning_profile_storage_active][i] = power['REopt:Electricity:Grid:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Generator:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:PV:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Wind:ToBattery(kw)'].to_f - power['REopt:Electricity:Storage:ToLoad(kw)'].to_f - power['REopt:Electricity:Storage:ToGrid(kw)'].to_f
|
290
|
-
profiles_planning[:planning_profile_dg_active][i] = power[
|
290
|
+
profiles_planning[:planning_profile_dg_active][i] = power['REopt:ElectricityProduced:Total(kw)'].to_f
|
291
291
|
profiles_planning[:planning_profile_cust_reactive][i] = profiles_planning[:planning_profile_cust_active][i] * Math.tan(Math.acos(power_factor))
|
292
292
|
profiles_planning[:planning_profile_storage_reactive][i] = profiles_planning[:planning_profile_storage_active][i] * Math.tan(Math.acos(power_factor))
|
293
293
|
profiles_planning[:planning_profile_dg_reactive][i] = profiles_planning[:planning_profile_dg_active][i] * Math.tan(Math.acos(power_factor))
|
@@ -301,7 +301,7 @@ module URBANopt
|
|
301
301
|
end
|
302
302
|
if profiles_planning[:planning_profile_dg_active][i] > single_values[:peak_active_power_dg]
|
303
303
|
single_values[:peak_active_power_dg] = profiles_planning[:planning_profile_dg_active][i]
|
304
|
-
single_values[:peak_reactive_power_dg] = single_values[:peak_active_power_dg] *
|
304
|
+
single_values[:peak_reactive_power_dg] = single_values[:peak_active_power_dg] * Math.tan(Math.acos(power_factor))
|
305
305
|
end
|
306
306
|
return profiles_planning, single_values
|
307
307
|
end
|
@@ -313,13 +313,13 @@ module URBANopt
|
|
313
313
|
# and the "extreme" hours used to plan the network
|
314
314
|
def prosumer_files_load(csv_feature_report, json_feature_report, building_map, building_nodes, hour)
|
315
315
|
# add variable to include how many timestep per hour, so the profiles become 48 * n_timestep_per_hour
|
316
|
-
n_timestep_per_hour = json_feature_report[
|
317
|
-
profiles_planning = Hash.new{|h, k| h[k] = Array.new(48*n_timestep_per_hour, 0)} # initializing each profile hash to 0 for the number of intervals considered for the planning of the network
|
318
|
-
|
316
|
+
n_timestep_per_hour = json_feature_report['timesteps_per_hour'].to_i
|
317
|
+
profiles_planning = Hash.new { |h, k| h[k] = Array.new(48 * n_timestep_per_hour, 0) } # initializing each profile hash to 0 for the number of intervals considered for the planning of the network
|
318
|
+
profiles = Hash.new { |h, k| h[k] = [] }
|
319
319
|
single_values = Hash.new(0)
|
320
320
|
@medium_voltage = false
|
321
|
-
hours = 24 * n_timestep_per_hour -1 # change name, maybe to intervals
|
322
|
-
feature_type = json_feature_report['program']['building_types'][0][
|
321
|
+
hours = 24 * n_timestep_per_hour - 1 # change name, maybe to intervals
|
322
|
+
feature_type = json_feature_report['program']['building_types'][0]['building_type']
|
323
323
|
residential_building_types = ['Single-Family Detached', 'Single-Family Attached', 'Multifamily', 'Single-Family', 'Multifamily Detached (2 to 4 units)', 'Multifamily Detached (5 or more units)']
|
324
324
|
|
325
325
|
# finding the index where to start computing and saving the info, from the value of the "worst-case hour" for the max peak consumption of the district
|
@@ -332,49 +332,49 @@ module URBANopt
|
|
332
332
|
profile_start_min = hour.hour_index_min_comm - ((hour.peak_hour_min_comm.split(':')[0].to_i + (hour.peak_hour_min_comm.split(':')[1].to_i / 60)) * n_timestep_per_hour)
|
333
333
|
end
|
334
334
|
# finding the index where to start computing and saving the info, from the value of the "most extreme hours" for the max peak consumption of the district
|
335
|
-
k = 0
|
336
|
-
i = hours +1 # to represent the 24 hours in case of max_net_generation day
|
335
|
+
k = 0 # index for each hour of the year represented in the csv file
|
336
|
+
i = hours + 1 # to represent the 24 hours in case of max_net_generation day
|
337
337
|
j = 0 # to represent the 24 hours in case of peak_demand_day
|
338
338
|
h_cons_batt = 0
|
339
339
|
h_dg_max = 0 # hour with max DG generation
|
340
340
|
h_stor_max = 0 # hour with max storage absorption
|
341
341
|
max_peak = 0
|
342
342
|
CSV.foreach(csv_feature_report, headers: true) do |power|
|
343
|
-
@power_factor = power[
|
344
|
-
profiles[:yearly_profile_cust_active].push(power[
|
343
|
+
@power_factor = power['Electricity:Facility Power(kW)'].to_f / power['Electricity:Facility Apparent Power(kVA)'].to_f
|
344
|
+
profiles[:yearly_profile_cust_active].push(power['REopt:Electricity:Load:Total(kw)'].to_f)
|
345
345
|
profiles[:yearly_profile_cust_reactive].push(profiles[:yearly_profile_cust_active][k] * Math.tan(Math.acos(@power_factor)))
|
346
|
-
profiles[:yearly_profile_dg_active].push(power[
|
346
|
+
profiles[:yearly_profile_dg_active].push(power['REopt:ElectricityProduced:Total(kw)'].to_f)
|
347
347
|
profiles[:yearly_profile_dg_reactive].push(profiles[:yearly_profile_dg_active][k] * Math.tan(Math.acos(@power_factor)))
|
348
348
|
profiles[:yearly_profile_storage_active].push(power['REopt:Electricity:Grid:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Generator:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:PV:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Wind:ToBattery(kw)'].to_f - power['REopt:Electricity:Storage:ToLoad(kw)'].to_f - power['REopt:Electricity:Storage:ToGrid(kw)'].to_f)
|
349
349
|
profiles[:yearly_profile_storage_reactive].push(profiles[:yearly_profile_storage_active][k] * Math.tan(Math.acos(@power_factor)))
|
350
|
-
single_values[:energy] += power[
|
351
|
-
single_values[:energy_dg] += power[
|
350
|
+
single_values[:energy] += power['REopt:Electricity:Load:Total(kw)'].to_f # calculating the yearly energy consumed by each feature
|
351
|
+
single_values[:energy_dg] += power['REopt:ElectricityProduced:Total(kw)'].to_f
|
352
352
|
single_values[:energy_storage] += power['REopt:Electricity:Grid:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Generator:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:PV:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Wind:ToBattery(kw)'].to_f - power['REopt:Electricity:Storage:ToLoad(kw)'].to_f - power['REopt:Electricity:Storage:ToGrid(kw)'].to_f
|
353
353
|
case k
|
354
|
-
when profile_start_min..profile_start_min +
|
355
|
-
profiles_planning, single_values =
|
356
|
-
|
357
|
-
when profile_start_max..profile_start_max +
|
358
|
-
profiles_planning, single_values =
|
359
|
-
j+=1
|
354
|
+
when profile_start_min..profile_start_min + hours
|
355
|
+
profiles_planning, single_values = profiles_planning_creation(profiles_planning, power, single_values, i, hours, power_factor)
|
356
|
+
i += 1
|
357
|
+
when profile_start_max..profile_start_max + hours
|
358
|
+
profiles_planning, single_values = profiles_planning_creation(profiles_planning, power, single_values, j, hours, power_factor)
|
359
|
+
j += 1
|
360
360
|
end
|
361
|
-
k+=1
|
361
|
+
k += 1
|
362
362
|
end
|
363
363
|
height = (json_feature_report['program']['maximum_roof_height_ft']).round(2)
|
364
364
|
users = json_feature_report['program']['number_of_residential_units']
|
365
|
-
der_capacity =
|
365
|
+
der_capacity = sum_dg(json_feature_report['distributed_generation'])
|
366
366
|
if @only_lv_consumers
|
367
|
-
nodes_per_bldg, area =
|
367
|
+
nodes_per_bldg, area = av_peak_cons_per_building_type(json_feature_report['program']['building_types'])
|
368
368
|
if @max_num_nodes == 1
|
369
|
-
|
369
|
+
construct_prosumer_general(profiles, profiles_planning, single_values, building_map, area, height, users, der_capacity)
|
370
370
|
else
|
371
|
-
|
371
|
+
construct_prosumer_lv(nodes_per_bldg, profiles, profiles_planning, single_values, building_map, building_nodes, area, height, users, der_capacity)
|
372
372
|
end
|
373
373
|
else
|
374
|
-
area = json_feature_report['program'].
|
374
|
+
area = json_feature_report['program'].key?('floor_area') ? (json_feature_report['program']['floor_area']).round(2) : (json_feature_report['program']['floor_area_sqft']).round(2)
|
375
375
|
# associating 2 nodes (consumers & DG and battery in the same node) per building considering the consumer, the battery and DG
|
376
|
-
|
377
|
-
end
|
376
|
+
construct_prosumer_general(profiles, profiles_planning, single_values, building_map, area, height, users, der_capacity)
|
377
|
+
end
|
378
378
|
end
|
379
379
|
end
|
380
380
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt (tm), Copyright (c) 2019-
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -105,7 +105,7 @@ module URBANopt
|
|
105
105
|
csv << ["<#{key}>"]
|
106
106
|
if ext_catalog[key].is_a?(Hash) # defining the section under consideration is an Hash or an Array
|
107
107
|
if key == 'OTHERS'
|
108
|
-
|
108
|
+
ext_catalog[key]['UTM Zone'] = utm_zone.to_s
|
109
109
|
end
|
110
110
|
ext_catalog[key].each do |k, v|
|
111
111
|
row = []
|
data/lib/urbanopt/rnm/runner.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt (tm), Copyright (c) 2019-
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -126,8 +126,7 @@ module URBANopt
|
|
126
126
|
def get_scenario_features
|
127
127
|
@num_header_rows = 1
|
128
128
|
features = []
|
129
|
-
CSV.foreach(@scenario_csv_path, :
|
130
|
-
|
129
|
+
CSV.foreach(@scenario_csv_path, headers: true) do |row|
|
131
130
|
break if row[0].nil?
|
132
131
|
|
133
132
|
# gets +feature_id+ and append to list
|
@@ -172,6 +171,16 @@ module URBANopt
|
|
172
171
|
@rnm_pp = URBANopt::RNM::PostProcessor.new(@results, @run_dir, @feature_file)
|
173
172
|
@rnm_pp.post_process
|
174
173
|
end
|
174
|
+
|
175
|
+
|
176
|
+
##
|
177
|
+
# Run OpenDSS validation
|
178
|
+
##
|
179
|
+
def run_validation
|
180
|
+
# generate RNM-US input files
|
181
|
+
validation = URBANopt::RNM::Validation.new(@run_dir)
|
182
|
+
validation.run_validation
|
183
|
+
end
|
175
184
|
end
|
176
185
|
end
|
177
186
|
end
|