urbanopt-rnm-us 0.4.0 → 0.5.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 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
+