urbanopt-rnm-us 0.4.0 → 0.5.0
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 +5 -0
- data/Gemfile +6 -5
- data/README.md +15 -1
- data/Rakefile +31 -6
- data/lib/urbanopt/rnm/api_client.rb +8 -0
- data/lib/urbanopt/rnm/consumers.rb +14 -2
- data/lib/urbanopt/rnm/input_files.rb +18 -0
- data/lib/urbanopt/rnm/prosumers.rb +17 -1
- data/lib/urbanopt/rnm/validation/main_validation.py +135 -33
- data/lib/urbanopt/rnm/validation/opendss_interface.py +458 -79
- data/lib/urbanopt/rnm/validation/plot_lib.py +430 -124
- data/lib/urbanopt/rnm/validation/report.py +370 -0
- data/lib/urbanopt/rnm/validation.rb +8 -6
- data/lib/urbanopt/rnm/version.rb +1 -1
- data/requirements.txt +9 -0
- data/urbanopt-rnm-us-gem.gemspec +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07dfa016051b9b8499a11ba5d8066a73c06131d628af10adb2dfc8c9848e7c30
|
4
|
+
data.tar.gz: a6e9f7af3ee4e92951d9225caa5eddfb8f97b20b0e1a5d95898a3f4a132405e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b25a77720880cc85ed29adabb35280a8e308692004183ef7da0a70ae0f4dd4e56c18d5299697490e8ed98e62e8007465ec31f80279c505595df23b144cbf16e5
|
7
|
+
data.tar.gz: e6a40b39b6f1f20f96df5379bf02c73205e00981e60bdf25dccaadb8780dd24522e18dcf423f3497fa7c03f6dd180cdc54531bd150666f2a33612b3a2b40c883
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
@@ -9,8 +9,9 @@ allow_local = ENV['FAVOR_LOCAL_GEMS']
|
|
9
9
|
# Below is an example of how to configure the gemfile for developing with local gems
|
10
10
|
# modify as appropriate
|
11
11
|
|
12
|
-
if allow_local && File.exists?('../urbanopt-geojson-gem')
|
13
|
-
|
14
|
-
elsif allow_local
|
15
|
-
|
16
|
-
end
|
12
|
+
# if allow_local && File.exists?('../urbanopt-geojson-gem')
|
13
|
+
# gem 'urbanopt-geojson', path: '../urbanopt-geojson-gem'
|
14
|
+
# elsif allow_local
|
15
|
+
# gem 'urbanopt-geojson', github: 'URBANopt/urbanopt-geojson-gem', branch: 'develop'
|
16
|
+
# end
|
17
|
+
|
data/README.md
CHANGED
@@ -47,7 +47,21 @@ bundle exec rake create_opendss_catalog[/desired/path/to/opendss_catalog.json]
|
|
47
47
|
| API Version | RNM-US Gem Version | RNM-US exe Version |
|
48
48
|
| ----------- | ----------- | ---------------- |
|
49
49
|
| v1 | 0.3.0 and earlier | RNM-US_20220819 |
|
50
|
-
| v2 | 0.4.0 | RNM-
|
50
|
+
| v2 | 0.4.0 | RNM-US_20221018 |
|
51
|
+
|
52
|
+
|
53
|
+
## Validation Functionality
|
54
|
+
|
55
|
+
The validation and results visualization functionality is written in python. Follow these steps if you would like to use it.
|
56
|
+
|
57
|
+
1. Install python (3.10) if you do not already have it installed
|
58
|
+
1. Clone the repo to your computer
|
59
|
+
1. cd into the repo directory
|
60
|
+
1. run `bundle install` to install the required ruby dependencies
|
61
|
+
1. run `pip install -r requirements.txt` to install the required python dependencies for the validation module
|
62
|
+
1. create input files and run the simulation as usual
|
63
|
+
1. run `bundle exec rake run_validation[/path/to/scenario/csv]` to run the validation
|
64
|
+
|
51
65
|
|
52
66
|
## Testing
|
53
67
|
|
data/Rakefile
CHANGED
@@ -148,6 +148,29 @@ task :run_simulation, [:scenario_csv_path, :reopt, :use_localhost] do |t, args|
|
|
148
148
|
puts '...done!'
|
149
149
|
end
|
150
150
|
|
151
|
+
# Full Runner workflow (mimics UO CLI functionality)
|
152
|
+
# pass in the path to the scenario csv, geojson path, whether this is a reopt analysis (true/false), and whether to use localhost RNM API (true/false)
|
153
|
+
desc 'Full Runner workflow'
|
154
|
+
task :full_runner_workflow, [:scenario_csv_path, :geojson_path, :reopt, :use_localhost] do |t, args|
|
155
|
+
# todo: could allow passing in extended catalog, average peak catalog, and opendss_catalog flags too
|
156
|
+
# if no path passed in, use default:
|
157
|
+
scenario_csv = args[:scenario_csv_path] || 'spec/test/example_project/run/baseline_scenario'
|
158
|
+
geojson_path = args[:geojson_path] || 'spec/test/example_project/example_project_with_network_and_streets'
|
159
|
+
root_dir, scenario_file_name = File.split(File.expand_path(scenario_csv))
|
160
|
+
scenario_name = File.basename(scenario_file_name, File.extname(scenario_file_name))
|
161
|
+
run_dir = File.join(root_dir, 'run', scenario_name.downcase)
|
162
|
+
reopt = args[:reopt] || false
|
163
|
+
reopt = reopt == 'true'
|
164
|
+
use_local = args[:use_localhost] || false
|
165
|
+
|
166
|
+
runner = URBANopt::RNM::Runner.new(scenario_name, run_dir, scenario_csv, geojson_path, reopt: reopt)
|
167
|
+
runner.create_simulation_files
|
168
|
+
runner.run(use_local)
|
169
|
+
runner.post_process
|
170
|
+
|
171
|
+
puts '...done!'
|
172
|
+
end
|
173
|
+
|
151
174
|
# Create opendss catalog from extended catalog
|
152
175
|
# pass in the path and filename where the OpenDSS catalog should be saved
|
153
176
|
desc 'Create OpenDSS catalog'
|
@@ -169,20 +192,22 @@ end
|
|
169
192
|
# run validation
|
170
193
|
# pass in the path to the scenario csv
|
171
194
|
desc 'Run Validation'
|
172
|
-
task :run_validation, [:scenario_csv_path, :
|
195
|
+
task :run_validation, [:scenario_csv_path, :use_numeric_ids] do |t, args|
|
173
196
|
#Exammple to run validation
|
174
|
-
#bundle exec rake run_validation[D:/.../urbanopt-rnm-us-gem/spec/files/example_project/baseline_scenario.csv]
|
197
|
+
#bundle exec rake run_validation[D:/.../urbanopt-rnm-us-gem/spec/files/example_project/baseline_scenario.csv,true]
|
198
|
+
|
175
199
|
puts 'Running OpenDSS validation'
|
200
|
+
|
176
201
|
# if no path passed in, use default:
|
177
202
|
scenario_csv = args[:scenario_csv_path] || 'spec/test/example_project/run/baseline_scenario'
|
178
203
|
root_dir, scenario_file_name = File.split(File.expand_path(scenario_csv))
|
179
204
|
scenario_name = File.basename(scenario_file_name, File.extname(scenario_file_name))
|
180
205
|
run_dir = File.join(root_dir, 'run', scenario_name.downcase)
|
181
|
-
|
182
206
|
rnm_dir = File.join(run_dir, 'rnm-us')
|
183
207
|
|
184
|
-
|
185
|
-
|
208
|
+
#Use numeric ids (for the hierarchical plot of the network)
|
209
|
+
use_numeric_ids = args[:use_numeric_ids] || false
|
210
|
+
use_numeric_ids = use_numeric_ids == 'true'
|
186
211
|
|
187
212
|
if !File.exist?(rnm_dir)
|
188
213
|
puts rnm_dir
|
@@ -190,7 +215,7 @@ task :run_validation, [:scenario_csv_path, :reopt, :use_localhost] do |t, args|
|
|
190
215
|
end
|
191
216
|
|
192
217
|
puts "run dir path: #{run_dir}"
|
193
|
-
validation = URBANopt::RNM::Validation.new(rnm_dir)
|
218
|
+
validation = URBANopt::RNM::Validation.new(rnm_dir,use_numeric_ids)
|
194
219
|
validation.run_validation()
|
195
220
|
|
196
221
|
puts '...done!'
|
@@ -108,6 +108,7 @@ module URBANopt
|
|
108
108
|
end
|
109
109
|
|
110
110
|
if !missing_files.empty?
|
111
|
+
puts "RNM DIR: #{@rnm_dir}"
|
111
112
|
raise "Input Files missing in directory: #{missing_files.join(',')}"
|
112
113
|
end
|
113
114
|
|
@@ -281,6 +282,13 @@ module URBANopt
|
|
281
282
|
# delete zip
|
282
283
|
File.delete(file_path)
|
283
284
|
|
285
|
+
# check if zip is empty
|
286
|
+
if Dir.empty? File.join(@rnm_dir, 'results')
|
287
|
+
msg = "Error in simulation: Results.zip empty"
|
288
|
+
@@logger.error(msg)
|
289
|
+
raise msg
|
290
|
+
end
|
291
|
+
|
284
292
|
else
|
285
293
|
msg = "Error retrieving results for #{the_sim_id}. error code: #{resp.status}. #{resp.body}"
|
286
294
|
@@logger.error(msg)
|
@@ -45,7 +45,7 @@ require 'csv'
|
|
45
45
|
module URBANopt
|
46
46
|
module RNM
|
47
47
|
class Consumers
|
48
|
-
attr_accessor :customers, :customers_ext, :profile_customer_p, :profile_customer_q, :profile_customer_p_ext, :profile_customer_q_ext, :power_factor
|
48
|
+
attr_accessor :customers, :customers_ext, :profile_customer_p, :profile_customer_q, :profile_customer_p_ext, :profile_customer_q_ext, :power_factor, :profile_date_time, :profile_date_time_ext
|
49
49
|
|
50
50
|
# initializing all the attributes to build the inputs files required by the RNM-US model
|
51
51
|
def initialize(reopt, only_lv_consumers = false, max_num_lv_nodes, average_building_peak_catalog_path, lv_limit)
|
@@ -55,8 +55,10 @@ module URBANopt
|
|
55
55
|
@max_num_lv_nodes = max_num_lv_nodes
|
56
56
|
@customers = []
|
57
57
|
@customers_ext = []
|
58
|
+
@profile_date_time = []
|
58
59
|
@profile_customer_p = []
|
59
60
|
@profile_customer_q = []
|
61
|
+
@profile_date_time_ext = []
|
60
62
|
@profile_customer_p_ext = []
|
61
63
|
@profile_customer_q_ext = []
|
62
64
|
@power_factor = power_factor
|
@@ -73,8 +75,10 @@ module URBANopt
|
|
73
75
|
# while the 2nd option is run in case "only LV" set to false and the consumption for each building will be placed in a single node
|
74
76
|
def construct_consumer(profiles, single_values, building_map, building_nodes, height, users, folder)
|
75
77
|
if @only_lv_consumers
|
78
|
+
planning_date_time = []
|
76
79
|
planning_profile_node_active = []
|
77
80
|
planning_profile_node_reactive = []
|
81
|
+
yearly_date_time = []
|
78
82
|
yearly_profile_node_active = []
|
79
83
|
yearly_profile_node_reactive = []
|
80
84
|
nodes_per_bldg, area, medium_voltage = av_peak_cons_per_building_type(folder['building_types'])
|
@@ -106,19 +110,23 @@ module URBANopt
|
|
106
110
|
else
|
107
111
|
voltage_default, phases = voltage_values(peak_active_power_cons / @power_factor)
|
108
112
|
end
|
109
|
-
|
113
|
+
|
110
114
|
for k in 0..profiles[:planning_profile_cust_active].length - 1
|
115
|
+
planning_date_time[k]=profiles[:planning_date_time][k]
|
111
116
|
planning_profile_node_active[k] = ((profiles[:planning_profile_cust_active][k]) / nodes_per_bldg).round(2)
|
112
117
|
planning_profile_node_reactive[k] = ((profiles[:planning_profile_cust_reactive][k]) / nodes_per_bldg).round(2)
|
113
118
|
end
|
114
119
|
for k in 0..profiles[:yearly_profile_cust_active].length - 1
|
120
|
+
yearly_date_time[k]=profiles[:yearly_date_time][k]
|
115
121
|
yearly_profile_node_active[k] = ((profiles[:yearly_profile_cust_active][k]) / nodes_per_bldg).round(2)
|
116
122
|
yearly_profile_node_reactive[k] = ((profiles[:yearly_profile_cust_reactive][k]) / nodes_per_bldg).round(2)
|
117
123
|
end
|
118
124
|
@customers.push([coordinates, voltage_default, peak_active_power_cons, peak_reactive_power_cons, phases])
|
119
125
|
@customers_ext.push([coordinates, voltage_default, peak_active_power_cons, peak_reactive_power_cons, phases, area, height, (single_values[:energy] / nodes_per_bldg).round(2), peak_active_power_cons, peak_reactive_power_cons, users])
|
126
|
+
@profile_date_time.push([planning_date_time])
|
120
127
|
@profile_customer_q.push([id, 24, planning_profile_node_reactive])
|
121
128
|
@profile_customer_p.push([id, 24, planning_profile_node_active])
|
129
|
+
@profile_date_time_ext.push([yearly_date_time])
|
122
130
|
@profile_customer_p_ext.push([id, 8760, yearly_profile_node_active])
|
123
131
|
@profile_customer_q_ext.push([id, 8760, yearly_profile_node_reactive])
|
124
132
|
|
@@ -131,8 +139,10 @@ module URBANopt
|
|
131
139
|
voltage_default, phases = voltage_values(single_values[:peak_active_power_cons] / @power_factor * 0.9) # applying safety factor
|
132
140
|
@customers.push([building_map, voltage_default, single_values[:peak_active_power_cons], single_values[:peak_reactive_power_cons], phases])
|
133
141
|
@customers_ext.push([building_map, voltage_default, single_values[:peak_active_power_cons], single_values[:peak_reactive_power_cons], phases, area, height, (single_values[:energy]).round(2), single_values[:peak_active_power_cons], single_values[:peak_reactive_power_cons], users])
|
142
|
+
@profile_date_time.push([profiles[:planning_date_time]])
|
134
143
|
@profile_customer_q.push([id, 24, profiles[:planning_profile_cust_reactive]])
|
135
144
|
@profile_customer_p.push([id, 24, profiles[:planning_profile_cust_active]])
|
145
|
+
@profile_date_time_ext.push([profiles[:yearly_date_time]])
|
136
146
|
@profile_customer_p_ext.push([id, 8760, profiles[:yearly_profile_cust_active]])
|
137
147
|
@profile_customer_q_ext.push([id, 8760, profiles[:yearly_profile_cust_reactive]])
|
138
148
|
end
|
@@ -234,10 +244,12 @@ module URBANopt
|
|
234
244
|
# content = CSV.foreach(csv_feature_report, headers: true) do |power|
|
235
245
|
CSV.foreach(csv_feature_report, headers: true) do |power|
|
236
246
|
@power_factor = power['Electricity:Facility Power(kW)'].to_f / power['Electricity:Facility Apparent Power(kVA)'].to_f
|
247
|
+
profiles[:yearly_date_time].push(power['Datetime'])
|
237
248
|
profiles[:yearly_profile_cust_active].push(power['Electricity:Facility Power(kW)'].to_f)
|
238
249
|
profiles[:yearly_profile_cust_reactive].push(profiles[:yearly_profile_cust_active][k] * Math.tan(Math.acos(@power_factor)))
|
239
250
|
single_values[:energy] += power['REopt:Electricity:Load:Total(kw)'].to_f # calculating the yearly energy consumed by each feature
|
240
251
|
if k >= profile_start_max && k <= profile_start_max + hours
|
252
|
+
profiles[:planning_date_time].push(power['Datetime'])
|
241
253
|
profiles[:planning_profile_cust_active].push(power['Electricity:Facility Power(kW)'].to_f)
|
242
254
|
if power['Electricity:Facility Power(kW)'].to_f > single_values[:peak_active_power_cons]
|
243
255
|
single_values[:peak_active_power_cons] = power['Electricity:Facility Power(kW)'].to_f
|
@@ -258,6 +258,10 @@ module URBANopt
|
|
258
258
|
File.open(File.join(@run_dir, @rnm_dirname, 'customers_ext.txt'), 'w+') do |g|
|
259
259
|
g.puts(prosumers.customers_ext.map { |w| w.join(';') })
|
260
260
|
end
|
261
|
+
File.open(File.join(@run_dir, @rnm_dirname, 'timestamps.csv'), 'w+') do |g|
|
262
|
+
g.puts("Datetime\n")
|
263
|
+
g.puts(prosumers.profile_date_time[0].map { |w| w.join("\n") })
|
264
|
+
end
|
261
265
|
File.open(File.join(@run_dir, @rnm_dirname, 'cust_profile_p.txt'), 'w+') do |g|
|
262
266
|
g.puts(prosumers.profile_customer_p.map { |w| w.join(';') })
|
263
267
|
end
|
@@ -267,6 +271,10 @@ module URBANopt
|
|
267
271
|
# CSV.open(File.join(@run_dir, @rnm_dirname, "cust_profile_q_extendido.csv"), "w") do |csv|
|
268
272
|
# csv << [prosumers.profile_customer_q_ext]
|
269
273
|
# end
|
274
|
+
File.open(File.join(@run_dir, @rnm_dirname, 'timestamps_extendido.csv'), 'w+') do |g|
|
275
|
+
g.puts("Datetime\n")
|
276
|
+
g.puts(prosumers.profile_date_time_ext[0].map { |w| w.join("\n") })
|
277
|
+
end
|
270
278
|
File.open(File.join(@run_dir, @rnm_dirname, 'cust_profile_q_extendido.txt'), 'w+') do |g|
|
271
279
|
g.puts(prosumers.profile_customer_q_ext.map { |w| w.join(';') })
|
272
280
|
end
|
@@ -289,6 +297,7 @@ module URBANopt
|
|
289
297
|
File.open(File.join(@run_dir, @rnm_dirname, 'gen_profile_p_extendido.txt'), 'w+') do |g|
|
290
298
|
g.puts(prosumers.profile_dg_p_extended.map { |w| w.join(';') })
|
291
299
|
end
|
300
|
+
ficheros_entrada_inc.push('Timestamps;timestamps.csv;timestamps_extendido.csv')
|
292
301
|
ficheros_entrada_inc.push('CClienteGreenfield;customers_ext.txt;cust_profile_p.txt;cust_profile_q.txt;cust_profile_p_extendido.txt;cust_profile_q_extendido.txt')
|
293
302
|
ficheros_entrada_inc.push('CGeneradorGreenfield;generators.txt;gen_profile_p.txt;gen_profile_q.txt;gen_profile_p_extendido.txt;gen_profile_q_extendido.txt')
|
294
303
|
ficheros_entrada_inc.push('END')
|
@@ -302,18 +311,27 @@ module URBANopt
|
|
302
311
|
File.open(File.join(@run_dir, @rnm_dirname, 'customers_ext.txt'), 'w+') do |g|
|
303
312
|
g.puts(consumers.customers_ext.map { |w| w.join(';') })
|
304
313
|
end
|
314
|
+
File.open(File.join(@run_dir, @rnm_dirname, 'timestamps.csv'), 'w+') do |g|
|
315
|
+
g.puts("Datetime\n")
|
316
|
+
g.puts(consumers.profile_date_time[0].map { |w| w.join("\n") })
|
317
|
+
end
|
305
318
|
File.open(File.join(@run_dir, @rnm_dirname, 'cust_profile_p.txt'), 'w+') do |g|
|
306
319
|
g.puts(consumers.profile_customer_p.map { |w| w.join(';') })
|
307
320
|
end
|
308
321
|
File.open(File.join(@run_dir, @rnm_dirname, 'cust_profile_q.txt'), 'w+') do |g|
|
309
322
|
g.puts(consumers.profile_customer_q.map { |w| w.join(';') })
|
310
323
|
end
|
324
|
+
File.open(File.join(@run_dir, @rnm_dirname, 'timestamps_extendido.csv'), 'w+') do |g|
|
325
|
+
g.puts("Datetime\n")
|
326
|
+
g.puts(consumers.profile_date_time_ext[0].map { |w| w.join("\n") })
|
327
|
+
end
|
311
328
|
File.open(File.join(@run_dir, @rnm_dirname, 'cust_profile_q_extendido.txt'), 'w+') do |g|
|
312
329
|
g.puts(consumers.profile_customer_q_ext.map { |w| w.join(';') })
|
313
330
|
end
|
314
331
|
File.open(File.join(@run_dir, @rnm_dirname, 'cust_profile_p_extendido.txt'), 'w+') do |g|
|
315
332
|
g.puts(consumers.profile_customer_p_ext.map { |w| w.join(';') })
|
316
333
|
end
|
334
|
+
ficheros_entrada_inc.push('Timestamps;timestamps.csv;timestamps_extendido.csv')
|
317
335
|
ficheros_entrada_inc.push('CClienteGreenfield;customers_ext.txt;cust_profile_p.txt;cust_profile_q.txt;cust_profile_p_extendido.txt;cust_profile_q_extendido.txt')
|
318
336
|
ficheros_entrada_inc.push('END')
|
319
337
|
File.open(File.join(@run_dir, @rnm_dirname, 'ficheros_entrada_inc.txt'), 'w+') do |g|
|
@@ -45,7 +45,7 @@ module URBANopt
|
|
45
45
|
# creating a class that creates the consumers input required by the RNM-US model,
|
46
46
|
# according to their geographic location, energy consumption and peak demand, and power consumption profiles
|
47
47
|
class Prosumers
|
48
|
-
attr_accessor :customers, :customers_ext, :profile_customer_p, :profile_customer_q, :profile_customer_p_ext, :profile_customer_q_ext, :dg, :dg_profile_p, :dg_profile_q, :profile_dg_p_extended, :profile_dg_q_extended, :power_factor
|
48
|
+
attr_accessor :customers, :customers_ext, :profile_customer_p, :profile_customer_q, :profile_customer_p_ext, :profile_customer_q_ext, :dg, :dg_profile_p, :dg_profile_q, :profile_dg_p_extended, :profile_dg_q_extended, :power_factor, :profile_date_time, :profile_date_time_ext
|
49
49
|
|
50
50
|
# initializing all the attributes to build the inputs files required by the RNM-US model
|
51
51
|
def initialize(reopt, only_lv_consumers = false, max_num_lv_nodes, average_building_peak_catalog_path, lv_limit)
|
@@ -55,8 +55,10 @@ module URBANopt
|
|
55
55
|
@max_num_lv_nodes = max_num_lv_nodes
|
56
56
|
@customers = []
|
57
57
|
@customers_ext = []
|
58
|
+
@profile_date_time = []
|
58
59
|
@profile_customer_p = []
|
59
60
|
@profile_customer_q = []
|
61
|
+
@profile_date_time_ext = []
|
60
62
|
@profile_customer_p_ext = []
|
61
63
|
@profile_customer_q_ext = []
|
62
64
|
@dg = []
|
@@ -99,16 +101,20 @@ module URBANopt
|
|
99
101
|
end
|
100
102
|
@customers.push([building_map, id, voltage_default, single_values[:peak_active_power_cons], single_values[:peak_reactive_power_cons], phases])
|
101
103
|
@customers_ext.push([building_map, id, voltage_default, single_values[:peak_active_power_cons], single_values[:peak_reactive_power_cons], phases, area, height, (single_values[:energy]).round(2), single_values[:peak_active_power_cons], single_values[:peak_reactive_power_cons], users])
|
104
|
+
@profile_date_time.push([profiles_planning[:planning_date_time]])
|
102
105
|
@profile_customer_q.push([id, 48, profiles_planning[:planning_profile_cust_reactive]])
|
103
106
|
@profile_customer_p.push([id, 48, profiles_planning[:planning_profile_cust_active]])
|
107
|
+
@profile_date_time_ext.push([profiles[:yearly_date_time]])
|
104
108
|
@profile_customer_p_ext.push([id, 8760, profiles[:yearly_profile_cust_active]])
|
105
109
|
@profile_customer_q_ext.push([id, 8760, profiles[:yearly_profile_cust_reactive]])
|
106
110
|
|
107
111
|
if !der_capacity[:storage].nil? && der_capacity[:storage] > 0
|
108
112
|
@customers.push([building_map, id_batt, voltage_default, single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], phases])
|
109
113
|
@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])
|
114
|
+
@profile_date_time.push([profiles_planning[:planning_date_time]])
|
110
115
|
@profile_customer_q.push([id_batt, 48, profiles_planning[:planning_profile_storage_reactive]])
|
111
116
|
@profile_customer_p.push([id_batt, 48, profiles_planning[:planning_profile_storage_active]])
|
117
|
+
@profile_date_time_ext.push([profiles[:yearly_date_time]])
|
112
118
|
@profile_customer_p_ext.push([id_batt, 8760, profiles[:yearly_profile_storage_active]])
|
113
119
|
@profile_customer_q_ext.push([id_batt, 8760, profiles[:yearly_profile_storage_reactive]])
|
114
120
|
end
|
@@ -129,8 +135,10 @@ module URBANopt
|
|
129
135
|
# among the nodes of each building
|
130
136
|
def construct_prosumer_lv(nodes_per_bldg = 0, profiles, profiles_planning, single_values, building_map, building_nodes, area, height, users, der_capacity)
|
131
137
|
# the default variables are defined (i.e. type and rurality type)
|
138
|
+
planning_date_time = []
|
132
139
|
planning_profile_node_active = []
|
133
140
|
planning_profile_node_reactive = []
|
141
|
+
yearly_date_time = []
|
134
142
|
yearly_profile_node_active = []
|
135
143
|
yearly_profile_node_reactive = []
|
136
144
|
closest_node = building_map[3].split('_')[1].to_i # refers to the closest node of the building in consideration to the street
|
@@ -159,17 +167,21 @@ module URBANopt
|
|
159
167
|
peak_reactive_power_cons = (single_values[:peak_reactive_power_cons] / nodes_consumers).round(2)
|
160
168
|
voltage_default, phases = voltage_values(peak_active_power_cons / @power_factor)
|
161
169
|
for k in 0..profiles_planning[:planning_profile_cust_active].length - 1
|
170
|
+
planning_date_time[k]=profiles_planning[:planning_date_time][k]
|
162
171
|
planning_profile_node_active[k] = (profiles_planning[:planning_profile_cust_active][k] / nodes_consumers).round(2)
|
163
172
|
planning_profile_node_reactive[k] = (profiles_planning[:planning_profile_cust_reactive][k] / nodes_consumers).round(2)
|
164
173
|
end
|
165
174
|
for k in 0..profiles[:yearly_profile_cust_active].length - 1
|
175
|
+
yearly_date_time[k]=profiles[:yearly_date_time][k]
|
166
176
|
yearly_profile_node_active[k] = (profiles[:yearly_profile_cust_active][k] / nodes_consumers).round(2)
|
167
177
|
yearly_profile_node_reactive[k] = (profiles[:yearly_profile_cust_reactive][k] / nodes_consumers).round(2)
|
168
178
|
end
|
169
179
|
@customers.push([coordinates, id, voltage_default, peak_active_power_cons, peak_reactive_power_cons, phases])
|
170
180
|
@customers_ext.push([coordinates, id, voltage_default, peak_active_power_cons, peak_reactive_power_cons, phases, area, height, (single_values[:energy] / nodes_consumers).round(2), peak_active_power_cons, peak_reactive_power_cons, users])
|
181
|
+
@profile_date_time.push([planning_date_time])
|
171
182
|
@profile_customer_q.push([id, 48, planning_profile_node_reactive])
|
172
183
|
@profile_customer_p.push([id, 48, planning_profile_node_active])
|
184
|
+
@profile_date_time_ext.push([yearly_date_time])
|
173
185
|
@profile_customer_p_ext.push([id, 8760, yearly_profile_node_active])
|
174
186
|
@profile_customer_q_ext.push([id, 8760, yearly_profile_node_reactive])
|
175
187
|
else
|
@@ -186,8 +198,10 @@ module URBANopt
|
|
186
198
|
if !der_capacity[:storage].nil? && der_capacity[:storage] > 0
|
187
199
|
@customers.push([coordinates, id_batt, voltage_default, single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], phases])
|
188
200
|
@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])
|
201
|
+
@profile_date_time.push([profiles[:planning_date_time]])
|
189
202
|
@profile_customer_q.push([id_batt, 48, profiles_planning[:planning_profile_storage_reactive]])
|
190
203
|
@profile_customer_p.push([id_batt, 48, profiles_planning[:planning_profile_storage_active]])
|
204
|
+
@profile_date_time_ext.push([profiles[:yearly_date_time]])
|
191
205
|
@profile_customer_p_ext.push([id_batt, 8760, profiles[:yearly_profile_storage_active]])
|
192
206
|
@profile_customer_q_ext.push([id_batt, 8760, profiles[:yearly_profile_storage_reactive]])
|
193
207
|
end
|
@@ -285,6 +299,7 @@ module URBANopt
|
|
285
299
|
|
286
300
|
# method to order profiles consistently
|
287
301
|
def profiles_planning_creation(profiles_planning, power, single_values, i, hours, power_factor)
|
302
|
+
profiles_planning[:planning_date_time][i]=power['Datetime']
|
288
303
|
profiles_planning[:planning_profile_cust_active][i] = power['REopt:Electricity:Load:Total(kw)'].to_f
|
289
304
|
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
305
|
profiles_planning[:planning_profile_dg_active][i] = power['REopt:ElectricityProduced:Total(kw)'].to_f
|
@@ -341,6 +356,7 @@ module URBANopt
|
|
341
356
|
max_peak = 0
|
342
357
|
CSV.foreach(csv_feature_report, headers: true) do |power|
|
343
358
|
@power_factor = power['Electricity:Facility Power(kW)'].to_f / power['Electricity:Facility Apparent Power(kVA)'].to_f
|
359
|
+
profiles[:yearly_date_time].push(power['Datetime'])
|
344
360
|
profiles[:yearly_profile_cust_active].push(power['REopt:Electricity:Load:Total(kw)'].to_f)
|
345
361
|
profiles[:yearly_profile_cust_reactive].push(profiles[:yearly_profile_cust_active][k] * Math.tan(Math.acos(@power_factor)))
|
346
362
|
profiles[:yearly_profile_dg_active].push(power['REopt:ElectricityProduced:Total(kw)'].to_f)
|
@@ -7,45 +7,147 @@ import math
|
|
7
7
|
import networkx as nx
|
8
8
|
import opendss_interface
|
9
9
|
import plot_lib
|
10
|
+
import report
|
11
|
+
import os
|
12
|
+
|
10
13
|
|
11
14
|
class Validation:
|
12
|
-
def __init__(self, folder):
|
13
|
-
|
14
|
-
|
15
|
+
def __init__(self, folder,sb_numeric_ids):
|
16
|
+
"""Initialices the folder variables"""
|
17
|
+
self.main_folder = folder #Main uppper level folder (needed to search for OpenDSS files)
|
18
|
+
self.folder=folder+'/Validation' #Folder where the Validation results are saved
|
19
|
+
self.b_numeric_ids=sb_numeric_ids.lower()=='true' #Use numeric IDs in the hierarchical plot of the network
|
20
|
+
self.mkdir(self.folder) #It creates the validation folder if it does not exist
|
21
|
+
|
22
|
+
def mkdir(self,directory):
|
23
|
+
"""Checkes whether the folder exists, if not it creates it"""
|
24
|
+
if (not os.path.exists(directory)):
|
25
|
+
os.mkdir(directory)
|
26
|
+
|
27
|
+
def make_dirs(self):
|
28
|
+
"""Creates all the subfolder, it they don't exist"""
|
29
|
+
path=self.folder+'/'
|
30
|
+
self.mkdir(path+'Voltage')
|
31
|
+
self.mkdir(path+'Voltage/CSV')
|
32
|
+
self.mkdir(path+'Voltage/Figures')
|
33
|
+
self.mkdir(path+'Unbalance')
|
34
|
+
self.mkdir(path+'Unbalance/CSV')
|
35
|
+
self.mkdir(path+'Unbalance/Figures')
|
36
|
+
self.mkdir(path+'Loading')
|
37
|
+
self.mkdir(path+'Loading/CSV')
|
38
|
+
self.mkdir(path+'Loading/Figures')
|
39
|
+
self.mkdir(path+'Losses')
|
40
|
+
self.mkdir(path+'Losses/CSV')
|
41
|
+
self.mkdir(path+'Losses/Figures')
|
42
|
+
self.mkdir(path+'Loads')
|
43
|
+
self.mkdir(path+'Loads/Figures')
|
44
|
+
self.mkdir(path+'Equipment')
|
45
|
+
self.mkdir(path+'Equipment/Figures')
|
46
|
+
self.mkdir(path+'Network')
|
47
|
+
self.mkdir(path+'Network/Figures')
|
48
|
+
self.mkdir(path+'Summary')
|
49
|
+
|
50
|
+
def define_ranges(self):
|
51
|
+
"""Defines the allowed ranges (to determine violations), the limits (to plot the lines in the plots), and the display ranges (for the axis in the figures)"""
|
52
|
+
#Voltages
|
53
|
+
v_range_voltage={}
|
54
|
+
v_range_voltage['allowed_range']=(0.95, 1.05)
|
55
|
+
v_range_voltage['limits']=[0.95, 1.05]
|
56
|
+
v_range_voltage['display_range']=(0.85, 1.15)
|
57
|
+
#Lodaing
|
58
|
+
v_range_loading={}
|
59
|
+
v_range_loading['allowed_range']=(0, 1)
|
60
|
+
v_range_loading['limits']=[1]
|
61
|
+
v_range_loading['display_range']=(0, 1.3)
|
62
|
+
#Unbalance
|
63
|
+
v_range_unbalance={}
|
64
|
+
v_range_unbalance['allowed_range']=(0,0.02)
|
65
|
+
v_range_unbalance['limits']=[0.02]
|
66
|
+
v_range_unbalance['display_range']=(0,0.021)
|
67
|
+
#Show all variables option
|
68
|
+
v_range_show_all={}
|
69
|
+
v_range_show_all['allowed_range']=()
|
70
|
+
v_range_show_all['limits']=[]
|
71
|
+
v_range_show_all['display_range']=()
|
72
|
+
|
73
|
+
return v_range_voltage,v_range_loading,v_range_unbalance,v_range_show_all
|
74
|
+
|
75
|
+
|
15
76
|
def main_validation(self):
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
77
|
+
"""Carries out the whole validation of the distribution system"""
|
78
|
+
#Path and file name
|
79
|
+
master_file_full_path = self.main_folder + '/dss_files/' + 'Master.dss' #Path for the Master file
|
80
|
+
#Number of periods (always 12 months)
|
81
|
+
num_periods=12
|
82
|
+
#Start and end index
|
83
|
+
#Define only to simulate only a subset of hours
|
84
|
+
#Set to None to use all timestamps
|
85
|
+
start_index = None #Default initial index (0/None)
|
86
|
+
end_index = None #Default final index, 1 year, 8760h (8760/None)
|
25
87
|
|
26
|
-
#
|
27
|
-
|
28
|
-
#
|
88
|
+
#Create sub-folders
|
89
|
+
self.make_dirs()
|
90
|
+
#Define the ranges of voltage, loading, unbalance and a default range to show all the values
|
91
|
+
v_range_voltage,v_range_loading,v_range_unbalance,v_range_show_all=self.define_ranges()
|
29
92
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
edges=myopendss_io.get_edges()
|
38
|
-
myplot_lib=plot_lib.Plot_Lib(folder)
|
39
|
-
myplot_lib.plot_hist('Voltage',v_voltage_yearly,v_voltage_period,v_range_voltage,40,num_periods,v_limits_voltage)
|
40
|
-
myplot_lib.plot_hist('Loading',v_loading_yearly,v_loading_period,v_range_loading,80,num_periods,v_limits_loading)
|
41
|
-
myplot_lib.plot_losses(v_subs_losses_yearly,v_line_losses_yearly)
|
42
|
-
myplot_lib.plot_graph(edges,v_dict_voltage,v_range_voltage,v_dict_loading,v_range_loading,dict_buses_element)
|
93
|
+
#For tests
|
94
|
+
#start_index = 2000 #Default initial index
|
95
|
+
#end_index = 4000 #Simulate few hours (still broken down in 12 periods/months)
|
96
|
+
#v_range_voltage['allowed_range']=(0.975, 1.025) #More stringent limmits than the standard ones to show violations
|
97
|
+
#v_range_loading['allowed_range']=(0,0.3)
|
98
|
+
#v_range_unbalance['allowed_range']=(0, 4e-5)
|
99
|
+
#v_range_unbalance['allowed_range']=(0, 2.1e-5)
|
43
100
|
|
101
|
+
#Run power flow iteratively and obtain the results
|
102
|
+
myopendss_io=opendss_interface.OpenDSS_Interface(folder,self.b_numeric_ids)
|
103
|
+
v_dict_buses_ids,v_dict_ids_buses,v_dict_voltage,v_voltage_yearly,v_voltage_period,v_power_yearly,v_power_period,v_dict_loading,v_loading_yearly,v_loading_period,v_dict_losses,v_subs_losses_yearly,v_line_losses_yearly,dict_buses_element,v_dict_loads,v_loads_kw_yearly,v_loads_kw_period,v_loads_kvar_yearly,v_loads_kvar_period,v_total_load_kw_yearly,v_total_load_kvar_yearly, v_loads_kw, v_loads_kvar, v_dict_unbalance,v_unbalance_yearly,v_unbalance_period,dict_lines,v_lines_norm_amps,dict_transformers,v_transformers_kva,timestamps,v_months,v_hours=myopendss_io.solve_powerflow_iteratively(num_periods,start_index,end_index,master_file_full_path,v_range_voltage,v_range_loading,v_range_unbalance)
|
44
104
|
|
105
|
+
#Save voltage, unbalance, loading and losses results in CSV files
|
106
|
+
myopendss_io.write_dict('Voltage/CSV',v_dict_voltage,v_range_show_all,'Voltages (p.u.)','Buses',v_dict_buses_ids,timestamps,v_hours)
|
107
|
+
myopendss_io.write_dict('Voltage/CSV',v_dict_voltage,v_range_voltage,'Voltage Violations (p.u.)','Buses',v_dict_buses_ids,timestamps,v_hours)
|
108
|
+
myopendss_io.write_dict('Unbalance/CSV',v_dict_unbalance,v_range_show_all,'Unbalance (p.u.)','Buses',v_dict_buses_ids,timestamps,v_hours)
|
109
|
+
myopendss_io.write_dict('Unbalance/CSV',v_dict_unbalance,v_range_unbalance,'Unbalance Violations (p.u.)','Buses',v_dict_buses_ids,timestamps,v_hours)
|
110
|
+
myopendss_io.write_dict('Loading/CSV',v_dict_loading,v_range_show_all,'Loading (p.u.)','Branches',None,timestamps,v_hours)
|
111
|
+
myopendss_io.write_dict('Loading/CSV',v_dict_loading,v_range_loading,'Loading Violations (p.u.)','Branches',None,timestamps,v_hours)
|
112
|
+
myopendss_io.write_dict('losses/CSV',v_dict_losses,v_range_show_all,'Losses','Branches',None,timestamps,v_hours)
|
113
|
+
if self.b_numeric_ids:
|
114
|
+
myopendss_io.write_id_dict('Network/Figures','IDs_Buses',v_dict_buses_ids)
|
115
|
+
#Get the edges of the network (for later making a hierarchical representation of the network)
|
116
|
+
closed_edges,open_edges=myopendss_io.get_edges(v_dict_buses_ids)
|
117
|
+
#Plot all the figures
|
118
|
+
myplot_lib=plot_lib.Plot_Lib(folder,self.b_numeric_ids)
|
119
|
+
#Voltage
|
120
|
+
myplot_lib.plot_hist('Voltage','Voltage (p.u.)',v_voltage_yearly,v_voltage_period,v_range_voltage,num_periods,40,v_months)
|
121
|
+
myplot_lib.plot_violin_monthly('Voltage/Figures','Voltage (p.u.)',v_voltage_yearly,v_voltage_period,v_range_voltage,num_periods,v_months)
|
122
|
+
#Unbalance
|
123
|
+
myplot_lib.plot_hist('Unbalance','Unbalance (p.u.)',v_unbalance_yearly,v_unbalance_period,v_range_unbalance,num_periods,40,v_months)
|
124
|
+
myplot_lib.plot_violin_monthly('Unbalance/Figures','Unbalance (p.u.)',v_unbalance_yearly,v_unbalance_period,v_range_unbalance,num_periods,v_months)
|
125
|
+
#Loading
|
126
|
+
myplot_lib.plot_hist('Loading','Loading (p.u.)',v_loading_yearly,v_loading_period,v_range_loading,num_periods,80,v_months)
|
127
|
+
myplot_lib.plot_violin_monthly('Loading/Figures','Loading (p.u.)',v_loading_yearly,v_loading_period,v_range_loading,num_periods,v_months)
|
128
|
+
#Loads and load shpaes
|
129
|
+
myplot_lib.plot_duration_curve('Loads/Figures',v_total_load_kw_yearly,v_total_load_kvar_yearly,False,v_hours)
|
130
|
+
myplot_lib.plot_violin_monthly_two_vars('Loads/Figures','Loads',v_loads_kw_yearly,v_loads_kw_period,v_loads_kvar_yearly,v_loads_kvar_period,v_range_show_all,num_periods,v_months)
|
131
|
+
myplot_lib.plot_violin('Loads/Figures','Loads Peak (kW)',v_loads_kw,v_range_show_all)
|
132
|
+
#Losses
|
133
|
+
myplot_lib.plot_duration_curve('Losses/Figures',v_subs_losses_yearly,v_line_losses_yearly,True,v_hours)
|
134
|
+
#Equipment parameters
|
135
|
+
myplot_lib.plot_violin('Equipment/Figures','Power Line - Normal Amps (A)',v_lines_norm_amps,v_range_show_all)
|
136
|
+
myplot_lib.plot_violin('Equipment/Figures','Transformer (kVA)',v_transformers_kva,v_range_show_all)
|
137
|
+
#Summary operational report
|
138
|
+
myreport=report.Report(folder,self.b_numeric_ids)
|
139
|
+
myreport.write_summary_operational_report('Summary',v_dict_voltage,v_range_voltage,v_dict_unbalance,v_range_unbalance,v_dict_loading,v_range_loading,v_dict_loads)
|
140
|
+
#Hierarchical representation of the network
|
141
|
+
myplot_lib.plot_graph('Network/Figures',closed_edges,open_edges,v_dict_voltage,v_range_voltage,v_dict_loading,v_range_loading,dict_buses_element,v_dict_buses_ids,v_dict_ids_buses)
|
142
|
+
|
45
143
|
|
144
|
+
#def main_validation(folder): #Example: uncomment to make the script run from a function
|
145
|
+
#folder = './files/'
|
46
146
|
if __name__ == "__main__":
|
47
|
-
|
48
|
-
#python main_validation.py files
|
49
|
-
folder = sys.argv[1]
|
50
|
-
|
51
|
-
valid
|
147
|
+
"""Runs direclty as a script if called from the command window"""
|
148
|
+
#Example of use: python main_validation.py files
|
149
|
+
folder = sys.argv[1] #Use the folder specified in the arguments
|
150
|
+
sb_numeric_ids = sys.argv[2] #Use numeric IDs in the hierarchical plot of the network
|
151
|
+
valid=Validation(folder,sb_numeric_ids)
|
152
|
+
valid.main_validation() #Call the main validation function
|
153
|
+
|