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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48d2e5b31538bcf737ce6e1d6d6c581dcfa8cddf779c6c2174590d9a9784a878
4
- data.tar.gz: fc4973c31698c5461451c675e47d4ae837aef99e314684e37d35caea0da18e2f
3
+ metadata.gz: 07dfa016051b9b8499a11ba5d8066a73c06131d628af10adb2dfc8c9848e7c30
4
+ data.tar.gz: a6e9f7af3ee4e92951d9225caa5eddfb8f97b20b0e1a5d95898a3f4a132405e3
5
5
  SHA512:
6
- metadata.gz: 800c18d91f1bb12ed9e46ca6a14a810d570fad5b4eb08e66e7dcca79507676545b53cb49db0f1046ec8b2dbdfeccc2d2c3eb3863b3f12699d79fd9bd1aa256a8
7
- data.tar.gz: 865a614718260125d16e49fa459d90826b8b86519ec8e75dc1ebdee61b12de8e386a6763ae89599e3fa050f5666b0b3a8ac4636f2f224ffd0b868b2b9e9007fb
6
+ metadata.gz: b25a77720880cc85ed29adabb35280a8e308692004183ef7da0a70ae0f4dd4e56c18d5299697490e8ed98e62e8007465ec31f80279c505595df23b144cbf16e5
7
+ data.tar.gz: e6a40b39b6f1f20f96df5379bf02c73205e00981e60bdf25dccaadb8780dd24522e18dcf423f3497fa7c03f6dd180cdc54531bd150666f2a33612b3a2b40c883
data/.gitignore CHANGED
@@ -29,4 +29,6 @@ runner.conf
29
29
  # ignore template files for now
30
30
  /template_inputs/*
31
31
  test_opendss_catalog.json
32
- *.sql
32
+ *.sql
33
+
34
+ *__pycache__
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## Version 0.5.0
4
+ Date Range 9/30/22 - 12/8/22
5
+
6
+ - Update dependencies for Extension Gem 0.6.0 and OpenStudio 3.5.0
7
+
3
8
  ## Version 0.4.0
4
9
  Date Range 05/10/22 - 9/30/22
5
10
 
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
- gem 'urbanopt-geojson', path: '../urbanopt-geojson-gem'
14
- elsif allow_local
15
- gem 'urbanopt-geojson', github: 'URBANopt/urbanopt-geojson-gem', branch: 'develop'
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-US_20220927 |
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, :reopt, :use_localhost] do |t, args|
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
- self.folder = folder
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
- master_file_full_path = folder + '/dss_files/' + 'Master.dss'
17
- start_index = 0
18
- num_periods=12
19
- end_index = 8760
20
- v_range_voltage=(0.9, 1.1)
21
- v_limits_voltage=[0.95,1.05]
22
- v_range_loading=(0,1.3)
23
- v_limits_loading=[1]
24
- v_range_show_all=(0,0)
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
- #For tests
27
- #end_index = 24
28
- #v_range_voltage=(0.975, 1.025)
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
- myopendss_io=opendss_interface.OpenDSS_Interface(folder)
31
- 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=myopendss_io.solve_powerflow_iteratively(num_periods,start_index,end_index,master_file_full_path,v_range_voltage,v_range_loading)
32
- myopendss_io.write_dict(v_dict_voltage,v_range_show_all,'Voltages (p.u.)','Buses')
33
- myopendss_io.write_dict(v_dict_voltage,v_range_voltage,'Voltage Violations (p.u.)','Buses')
34
- myopendss_io.write_dict(v_dict_loading,v_range_show_all,'Loading (p.u.)','Branches')
35
- myopendss_io.write_dict(v_dict_loading,v_range_loading,'Loading Violations (p.u.)','Branches')
36
- myopendss_io.write_dict(v_dict_losses,v_range_show_all,'Losses','Branches')
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
- #Example to run it in command window
48
- #python main_validation.py files
49
- folder = sys.argv[1]
50
- valid=Validation(folder)
51
- valid.main_validation()
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
+