urbanopt-rnm-us 0.1.0 → 0.2.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: bfd1fdcbaf0908d6ec884147fd9a26c78429181063cadb15fd5210e89bacd83e
4
- data.tar.gz: d8ca20d16669801961c405b8609f6a8adf21bfc2c5862e604e74f90732cece6d
3
+ metadata.gz: 977f93affe9c5d526cd2cb0af1232f443e07693a78d45da7827563f9f804c30e
4
+ data.tar.gz: 03e3f2642b6ddc140b36d9385e0a77790e1308a1f5bd1b96647775d03f4e4830
5
5
  SHA512:
6
- metadata.gz: 9364a3a2cd150e57ee2dff2e1f070065fdc83b35dc519361e9c076393e7f3ee9b290899fef8952b26eae42abc8872989fe5ad8a7ef00b7a1a35397273578a3c7
7
- data.tar.gz: 0fcaf071315fac36a51abace8a64f3278d80c854fd6d85bbb7a63affa774cd0a09e7c4196e2aa0e71ee446d7e8f9cd8349e3c068b67d4ae7c235b82f03433a79
6
+ metadata.gz: e86574dc52de1655eace0ba423350343257cb234a2e110a2670fd80163fe07fc2f6b7ab7cf56decb02260bcf307b8620ddde128bb9c157ceeb6a94f59487dd16
7
+ data.tar.gz: 8e7bba90ab83607932f8eecc5bda1487d86c5da2864a32d92fd88d3ae20d4aa18f8567244042dc66a9e640304aa65a0ec44f9090f6a2472124aa191be27db523
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## Version 0.2.0
4
+
5
+ Date Range 11/09/21 - 11/22/21
6
+
7
+ - Updated dependencies for OpenStudio 3.3
8
+
9
+ ## Version 0.1.3
10
+
11
+ Date Range 11/02/21 - 11/08/21
12
+
13
+ - Fix [#11](https://github.com/urbanopt/urbanopt-rnm-us-gem/issues/11), results files are not downloading in project directory for large projects
14
+
15
+ - Fix [#16](https://github.com/urbanopt/urbanopt-rnm-us-gem/issues/16), fix residential enums to be consistent across files and fix typo in multifamily
16
+
17
+ ## Version 0.1.2
18
+
19
+ Date Range 10/29/21 - 11/01/21
20
+
21
+ - Fix [#13](https://github.com/urbanopt/urbanopt-rnm-us-gem/issues/13), update rubyzip dependency to fix conflict
22
+
23
+ ## Version 0.1.1
24
+
25
+ Date Range 07/22/21 - 10/28/21
26
+
27
+ - Fixed [#10]( https://github.com/urbanopt/urbanopt-rnm-us-gem/issues/7 ), Fix peak profile generation for prosumer profiles
28
+ - Fixed [#8]( https://github.com/urbanopt/urbanopt-rnm-us-gem/issues/8 ), Use timestep_per_hour defined in UO feature report for the interval reporting
29
+ - Fixed [#10]( https://github.com/urbanopt/urbanopt-rnm-us-gem/issues/10 ), Add other residential types to prosumer/consumer calculations
30
+
3
31
  ## Version 0.1.0
4
32
 
5
33
  Initial version of the RNM-US gem.
data/Jenkinsfile ADDED
@@ -0,0 +1,10 @@
1
+ //Jenkins pipelines are stored in shared libaries. Please see: https://github.com/tijcolem/nrel_cbci_jenkins_libs
2
+
3
+ @Library('cbci_shared_libs') _
4
+
5
+ // Build for PR to develop branch only.
6
+ if ((env.CHANGE_ID) && (env.CHANGE_TARGET) ) { // check if set
7
+
8
+ urbanopt_rnm_us()
9
+
10
+ }
@@ -177,52 +177,70 @@ module URBANopt
177
177
  # prepare results directory
178
178
  prepare_results_dir
179
179
 
180
- max_tries = 10
180
+ max_tries = 20
181
181
  tries = 0
182
182
  puts "attempting to retrieve results for simulation #{@sim_id}"
183
183
  while !done && (max_tries != tries)
184
- resp = conn.get("simulations/#{@sim_id}")
185
- if resp.status == 200
186
- data = JSON.parse(resp.body)
187
- if data['status'] && ['failed', 'completed'].include?(data['status'])
188
- # done
189
- done = true
190
- if data['status'] == 'failed'
191
- if data['results'] && data['results']['message']
192
- puts "Simulation Error: #{data['results']['message']}"
184
+ begin
185
+ resp = conn.get("simulations/#{@sim_id}")
186
+ if resp.status == 200
187
+ data = JSON.parse(resp.body)
188
+ if data['status'] && ['failed', 'completed'].include?(data['status'])
189
+ # done
190
+ done = true
191
+ if data['status'] == 'failed'
192
+ if data['results'] && data['results']['message']
193
+ puts "Simulation Error: #{data['results']['message']}"
194
+ else
195
+ puts 'Simulation Error!'
196
+ end
193
197
  else
194
- puts 'Simulation Error!'
198
+ # edge case, check for results
199
+ if data['results'].nil?
200
+ puts "got a 200 but results are null...trying again"
201
+ tries += 1
202
+ sleep(3)
203
+ else
204
+ # get results
205
+ @results = data['results'] || []
206
+
207
+ puts "downloading results"
208
+ # download results
209
+ download_results
210
+ return @results
211
+ end
195
212
  end
196
213
  else
197
- # get results
198
- @results = data['results'] || []
199
-
200
- # download results
201
- download_results
202
-
203
- return @results
214
+ puts "no status yet...trying again"
215
+ tries += 1
216
+ sleep(3)
204
217
  end
218
+
205
219
  else
220
+ puts("ERROR retrieving: #{resp.body}")
206
221
  tries += 1
207
- sleep(1)
208
- end
209
222
 
210
- else
211
- puts("ERROR retrieving: #{resp.body}")
212
- tries += 1
213
-
214
- if tries == max_tries
215
- # now raise the error
216
- msg = "Error retrieving simulation #{@sim_id}. error code: #{resp.status}"
217
- @@logger.error(msg)
218
- raise msg
219
- else
220
- # try again
221
- puts("TRYING AGAIN...#{tries}")
222
- sleep(3)
223
+ if tries == max_tries
224
+ # now raise the error
225
+ msg = "Error retrieving simulation #{@sim_id}. error code: #{resp.status}"
226
+ @@logger.error(msg)
227
+ raise msg
228
+ else
229
+ # try again
230
+ puts("TRYING AGAIN...#{tries}")
231
+ sleep(3)
232
+ end
223
233
  end
234
+ rescue => error
235
+ @@logger.error("Error retrieving simulation #{@sim_id}.")
236
+ @@logger.error(error.message)
237
+ raise error.message
224
238
  end
225
239
  end
240
+ if !done
241
+ @@logger.error("Error retrieving simulation #{@sim_id}.")
242
+ raise 'Simulation not retrieved...maximum tries reached'
243
+ end
226
244
  end
227
245
 
228
246
  ##
@@ -243,14 +261,14 @@ module URBANopt
243
261
  streamed << chunk
244
262
  end
245
263
  end
246
- # puts("STATUS: #{resp.status}, #{resp.body}")
247
264
 
248
265
  if resp.status == 200
249
266
 
250
267
  file_path = File.join(@rnm_dir, 'results', 'results.zip')
251
268
 
252
269
  File.open(file_path, 'wb') { |f| f.write streamed.join }
253
- # puts "RNM-US results.zip downloaded to #{@rnm_dir}"
270
+ puts "RNM-US results.zip downloaded to #{@rnm_dir}"
271
+
254
272
  # unzip
255
273
  Zip::File.open(file_path) do |zip_file|
256
274
  zip_file.each do |f|
@@ -259,7 +277,7 @@ module URBANopt
259
277
  zip_file.extract(f, f_path) unless File.exist?(f_path)
260
278
  end
261
279
  end
262
-
280
+ puts "results.zip extracted"
263
281
  # delete zip
264
282
  File.delete(file_path)
265
283
 
@@ -79,6 +79,7 @@ module URBANopt
79
79
  yearly_profile_node_reactive = []
80
80
  nodes_per_bldg, area, medium_voltage = av_peak_cons_per_building_type(folder['building_types'])
81
81
  # the default variables are defined (i.e. type and rurality type)
82
+ puts "consumers 82"
82
83
  closest_node = building_map[3].split('_')[1].to_i # refers to the node, found in the class above
83
84
  node = closest_node
84
85
  cont = 1
@@ -175,7 +176,7 @@ module URBANopt
175
176
  conservative_factor = 0.8 # considered as a reasonable assumption, but this value could be changed
176
177
  average_peak_folder = JSON.parse(File.read(@average_building_peak_catalog_path))
177
178
  for i in 0..feature_file.length - 1
178
- area = (feature_file[i]['floor_area']).round(2)
179
+ area = feature_file[i].has_key?('floor_area') ? (feature_file[i]['floor_area']).round(2) : feature_file[i]['floor_area_sqft'].round(2)
179
180
  building_type = feature_file[i]['building_type'] # it specifies the type of building, sometimes it is directly the sub-type
180
181
  counter = 0 # counter to find number of buildings type belonging to same "category"
181
182
  average_peak_folder.each do |building_class|
@@ -214,16 +215,18 @@ module URBANopt
214
215
  # the method passes as arguments the urbanopt json and csv output file for each feature and the building coordinates previously calculated
215
216
  # and the "extreme" hour used to plan the network
216
217
  def customer_files_load(csv_feature_report, json_feature_report, building_map, building_nodes, hour)
218
+ n_timestep_per_hour = json_feature_report["timesteps_per_hour"].to_i
217
219
  profiles = Hash.new { |h, k| h[k] = [] }
218
220
  single_values = Hash.new(0)
219
- hours = 23
221
+ hours = 24 * n_timestep_per_hour -1
220
222
  feature_type = json_feature_report['program']['building_types'][0]['building_type']
221
- residential_building_types = 'Single-Family Detached' # add the other types
223
+ residential_building_types = ['Single-Family Detached', 'Single-Family Attached', 'Multifamily', 'Single-Family', 'Multifamily Detached (2 to 4 units)', 'Multifamily Detached (5 or more units)']
224
+
222
225
  # finding the index where to start computing and saving the info, from the value of the "worst-case hour" for the max peak consumption of the district
223
226
  if residential_building_types.include? feature_type
224
- profile_start_max = hour.hour_index_max_res - hour.peak_hour_max_res
227
+ profile_start_max = hour.hour_index_max_res - ((hour.peak_hour_max_res.split(':')[0].to_i + (hour.peak_hour_max_res.split(':')[1].to_i / 60)) * n_timestep_per_hour)
225
228
  else
226
- profile_start_max = hour.hour_index_max_comm - hour.peak_hour_max_comm
229
+ profile_start_max = hour.hour_index_max_comm - ((hour.peak_hour_max_comm.split(':')[0].to_i + (hour.peak_hour_max_comm.split(':')[1].to_i / 60)) * n_timestep_per_hour)
227
230
  end
228
231
  k = 0 # index for each hour of the year represented in the csv file
229
232
  i = 0 # to represent the 24 hours of a day
@@ -74,7 +74,7 @@ module URBANopt
74
74
 
75
75
  # method defined for the case of a single node where both battery, DG and consumers are placed
76
76
  # evaluation of the peak power in each node to define the type of connection (e.g. voltage level and n phases)
77
- def construct_prosumer_general(profiles, single_values, building_map, area, height, users, der_capacity)
77
+ def construct_prosumer_general(profiles, profiles_planning, single_values, building_map, area, height, users, der_capacity)
78
78
  id = building_map[3]
79
79
  id_dg = "#{building_map[3]}_DG"
80
80
  id_batt = "#{building_map[3]}_battery"
@@ -84,8 +84,8 @@ module URBANopt
84
84
  # in the hour with generation max peak
85
85
  # in the hour with storage max peak
86
86
 
87
- for i in 0..profiles[:planning_profile_cust_active].length - 1
88
- hourly_app_power = ((profiles[:planning_profile_cust_active][i] + profiles[:planning_profile_storage_active][i] - profiles[:planning_profile_dg_active][i]) / @power_factor).abs
87
+ for i in 0..profiles_planning[:planning_profile_cust_active].length-1
88
+ hourly_app_power = ((profiles_planning[:planning_profile_cust_active][i] + profiles_planning[:planning_profile_storage_active][i] - profiles_planning[:planning_profile_dg_active][i])/@power_factor).abs
89
89
  if hourly_app_power > peak_app_power_node
90
90
  peak_app_power_node = hourly_app_power
91
91
  end
@@ -99,22 +99,22 @@ module URBANopt
99
99
  end
100
100
  @customers.push([building_map, id, voltage_default, single_values[:peak_active_power_cons], single_values[:peak_reactive_power_cons], phases])
101
101
  @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])
102
- @profile_customer_q.push([id, 48, profiles[:planning_profile_cust_reactive]])
103
- @profile_customer_p.push([id, 48, profiles[:planning_profile_cust_active]])
102
+ @profile_customer_q.push([id, 48, profiles_planning[:planning_profile_cust_reactive]])
103
+ @profile_customer_p.push([id, 48, profiles_planning[:planning_profile_cust_active]])
104
104
  @profile_customer_p_ext.push([id, 8760, profiles[:yearly_profile_cust_active]])
105
105
  @profile_customer_q_ext.push([id, 8760, profiles[:yearly_profile_cust_reactive]])
106
-
107
- if !der_capacity[:storage].nil? && der_capacity[:storage] > 0
108
- @customers.push([building_map, id_batt, voltage_default, single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], phases])
106
+
107
+ if der_capacity[:storage] != nil && der_capacity[:storage] > 0
108
+ @customers.push([building_map, id_batt, voltage_default, single_values[:peak_active_power_storage],single_values[:peak_reactive_power_storage], phases])
109
109
  @customers_ext.push([building_map, id_batt, voltage_default, single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], phases, area, height, (single_values[:energy]).round(2), single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], users])
110
- @profile_customer_q.push([id_batt, 48, profiles[:planning_profile_storage_reactive]])
111
- @profile_customer_p.push([id_batt, 48, profiles[:planning_profile_storage_active]])
110
+ @profile_customer_q.push([id_batt, 48, profiles_planning[:planning_profile_storage_reactive]])
111
+ @profile_customer_p.push([id_batt, 48, profiles_planning[:planning_profile_storage_active]])
112
112
  @profile_customer_p_ext.push([id_batt, 8760, profiles[:yearly_profile_storage_active]])
113
113
  @profile_customer_q_ext.push([id_batt, 8760, profiles[:yearly_profile_storage_reactive]])
114
114
  end
115
115
  @dg.push([building_map, id_dg, voltage_default, der_capacity[:dg], single_values[:peak_active_power_dg].round(2), single_values[:peak_reactive_power_dg].round(2), phases])
116
- @dg_profile_p.push([id_dg, 48, profiles[:planning_profile_dg_active]])
117
- @dg_profile_q.push([id_dg, 48, profiles[:planning_profile_dg_reactive]])
116
+ @dg_profile_p.push([id_dg, 48, profiles_planning[:planning_profile_dg_active]])
117
+ @dg_profile_q.push([id_dg, 48, profiles_planning[:planning_profile_dg_reactive]])
118
118
  @profile_dg_p_extended.push([id_dg, 8760, profiles[:yearly_profile_dg_active]])
119
119
  @profile_dg_q_extended.push([id_dg, 8760, profiles[:yearly_profile_dg_reactive]])
120
120
  end
@@ -127,7 +127,7 @@ module URBANopt
127
127
  # this method is called only if the user sets the option of "only LV nodes" to true
128
128
  # defining a certain numb of nodes for each building and distributing the peak power values equally
129
129
  # among the nodes of each building
130
- def construct_prosumer_lv(nodes_per_bldg = 0, profiles, single_values, building_map, building_nodes, area, height, users, der_capacity)
130
+ def construct_prosumer_lv(nodes_per_bldg = 0, profiles, profiles_planning, single_values, building_map, building_nodes, area, height, users, der_capacity)
131
131
  # the default variables are defined (i.e. type and rurality type)
132
132
  planning_profile_node_active = []
133
133
  planning_profile_node_reactive = []
@@ -138,6 +138,7 @@ module URBANopt
138
138
  cont = 1
139
139
  cont_reverse = 1
140
140
  nodes_consumers = nodes_per_bldg - 1
141
+
141
142
  for i in 1..nodes_per_bldg
142
143
  coordinates = building_map
143
144
  node = closest_node + cont # to set the new nodes with enough distance among each others
@@ -145,10 +146,10 @@ module URBANopt
145
146
  if i > 1 && node <= building_nodes.length - 2
146
147
  coordinates = building_nodes[node] # take the closest building node index to the street and pass the nodes after it
147
148
  cont += 1
148
- elsif i > 1
149
+ elsif i > 1
149
150
  coordinates = building_nodes[node_reverse]
150
151
  cont_reverse += 1
151
- end
152
+ end
152
153
  # this condition is used to firstly place the building consumption nodes and then the last node
153
154
  # to be placed is the one referred to DG and battery for the building
154
155
  if i < nodes_per_bldg # considering the consumers nodes
@@ -157,12 +158,12 @@ module URBANopt
157
158
  peak_active_power_cons = (single_values[:peak_active_power_cons] / nodes_consumers).round(2)
158
159
  peak_reactive_power_cons = (single_values[:peak_reactive_power_cons] / nodes_consumers).round(2)
159
160
  voltage_default, phases = voltage_values(peak_active_power_cons / @power_factor)
160
- for k in 0..profiles[:planning_profile_cust_active].length - 1
161
- planning_profile_node_active[k] = (profiles[:planning_profile_cust_active][k] / nodes_consumers).round(2)
162
- planning_profile_node_reactive[k] = (profiles[:planning_profile_cust_reactive][k] / nodes_consumers).round(2)
161
+ for k in 0..profiles_planning[:planning_profile_cust_active].length - 1
162
+ planning_profile_node_active[k] = (profiles_planning[:planning_profile_cust_active][k] / nodes_consumers).round(2)
163
+ planning_profile_node_reactive[k] = (profiles_planning[:planning_profile_cust_reactive][k] / nodes_consumers).round(2)
163
164
  end
164
165
  for k in 0..profiles[:yearly_profile_cust_active].length - 1
165
- yearly_profile_node_active[k] = (profiles[:yearly_profile_cust_active][k] / nodes_consumers).round(2)
166
+ yearly_profile_node_active[k] = (profiles[:yearly_profile_cust_active][k] / nodes_consumers).round(2)
166
167
  yearly_profile_node_reactive[k] = (profiles[:yearly_profile_cust_reactive][k] / nodes_consumers).round(2)
167
168
  end
168
169
  @customers.push([coordinates, id, voltage_default, peak_active_power_cons, peak_reactive_power_cons, phases])
@@ -173,20 +174,20 @@ module URBANopt
173
174
  @profile_customer_q_ext.push([id, 8760, yearly_profile_node_reactive])
174
175
  else
175
176
  # considering the DG and battery
176
- voltage_default, phases = voltage_values(der_capacity[:dg]) # assuming that the pv capacity is always higher than battery capacity
177
+ voltage_default, phases = voltage_values(der_capacity[:dg]) #assuming that the pv capacity is always higher than battery capacity
177
178
  id_dg = "#{coordinates[3]}_DG"
178
179
  id_batt = "#{coordinates[3]}_battery"
179
180
  coordinates.pop
180
181
  @dg.push([coordinates, id_dg, voltage_default, der_capacity[:dg], single_values[:peak_active_power_dg].round(2), single_values[:peak_reactive_power_dg].round(2), phases])
181
- @dg_profile_p.push([id_dg, 48, profiles[:planning_profile_dg_active]])
182
- @dg_profile_q.push([id_dg, 48, profiles[:planning_profile_dg_reactive]])
182
+ @dg_profile_p.push([id_dg, 48, profiles_planning[:planning_profile_dg_active]])
183
+ @dg_profile_q.push([id_dg, 48, profiles_planning[:planning_profile_dg_reactive]])
183
184
  @profile_dg_p_extended.push([id_dg, 8760, profiles[:yearly_profile_dg_active]])
184
185
  @profile_dg_q_extended.push([id_dg, 8760, profiles[:yearly_profile_dg_reactive]])
185
- if !der_capacity[:storage].nil? && der_capacity[:storage] > 0
186
+ if der_capacity[:storage] != nil && der_capacity[:storage] > 0
186
187
  @customers.push([coordinates, id_batt, voltage_default, single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], phases])
187
188
  @customers_ext.push([coordinates, id_batt, voltage_default, single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], phases, area, height, (single_values[:energy]).round(2), single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], users])
188
- @profile_customer_q.push([id_batt, 48, profiles[:planning_profile_storage_reactive]])
189
- @profile_customer_p.push([id_batt, 48, profiles[:planning_profile_storage_active]])
189
+ @profile_customer_q.push([id_batt, 48, profiles_planning[:planning_profile_storage_reactive]])
190
+ @profile_customer_p.push([id_batt, 48, profiles_planning[:planning_profile_storage_active]])
190
191
  @profile_customer_p_ext.push([id_batt, 8760, profiles[:yearly_profile_storage_active]])
191
192
  @profile_customer_q_ext.push([id_batt, 8760, profiles[:yearly_profile_storage_reactive]])
192
193
  end
@@ -247,16 +248,16 @@ module URBANopt
247
248
  conservative_factor = 0.8 # considered as a reasonable assumption, but this value could be changed
248
249
  average_peak_folder = JSON.parse(File.read(@average_building_peak_catalog_path))
249
250
  for i in 0..feature_file.length - 1
250
- area = (feature_file[i]['floor_area']).round(2)
251
- building_type = feature_file[i]['building_type'] # it specifies the type of building, sometimes it is directly the sub-type
251
+ area = feature_file[i].key?('floor_area') ? (feature_file[i]['floor_area']).round(2) : (feature_file[i]['floor_area_sqft']).round(2)
252
+ building_type = feature_file[i]['building_type'] #it specifies the type of building, sometimes it is directly the sub-type
252
253
  counter = 0 # counter to find number of buildings type belonging to same "category"
253
254
  average_peak_folder.each do |building_class|
254
- if building_type == building_class['building type'] || building_type == building_class['sub-type']
255
+ if (building_type == building_class["building type"] || building_type == building_class["sub-type"])
255
256
  average_peak = (building_class['average peak demand (kW/ft2)'].to_f * area).to_f.round(4) # finding the average peak considering the floor area of the bilding under consideration
256
257
  average_peak_by_size[counter] = average_peak
257
258
  floor_area[counter] = (building_class['floor_area (ft2)'] - area).abs # minimum difference among area and area from the prototypes defined by DOE
258
259
  counter += 1
259
- # in this way I don t consider residential and I assume it s average_peak = 0, it is ok because we assume always 1 node per RES consumers, single-detached family houses
260
+ # in this way I don t consider residential and I assume it s average_peak = 0, it is ok because we assume always 1 node per RES consumers, single-detached family houses
260
261
  end
261
262
  end
262
263
  if counter > 1
@@ -272,100 +273,108 @@ module URBANopt
272
273
  average_peak = mixed_use_av_peak # average peak per mixed use considering the building types which are in this building
273
274
  area = area_mixed_use
274
275
  end
275
- nodes_per_bldg = (average_peak / (@lv_limit[:three_phase] * @power_factor * conservative_factor)).to_f.ceil # computing number of nodes per building
276
- if nodes_per_bldg > @max_num_lv_nodes # defined as reasonable maximum
276
+ nodes_per_bldg = ((average_peak / (@lv_limit[:three_phase] * @power_factor * conservative_factor)).to_f).ceil # computing number of nodes per building
277
+ if nodes_per_bldg > @max_num_lv_nodes #that it is equal to how it was before
277
278
  nodes_per_bldg = 1
278
279
  @medium_voltage = true
279
280
  end
280
-
281
- nodes_per_bldg += 1 # tacking into account the extra node for distributed generation and the battery
282
-
281
+
282
+ nodes_per_bldg += 1 # tacking into account the extra node for distributed generation and the battery
283
283
  return nodes_per_bldg, area
284
284
  end
285
285
 
286
+ # method to order profiles consistently
287
+ def profiles_planning_creation(profiles_planning, power, single_values, i, hours, power_factor)
288
+ profiles_planning[:planning_profile_cust_active][i] = power["REopt:Electricity:Load:Total(kw)"].to_f
289
+ profiles_planning[:planning_profile_storage_active][i] = power['REopt:Electricity:Grid:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Generator:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:PV:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Wind:ToBattery(kw)'].to_f - power['REopt:Electricity:Storage:ToLoad(kw)'].to_f - power['REopt:Electricity:Storage:ToGrid(kw)'].to_f
290
+ profiles_planning[:planning_profile_dg_active][i] = power["REopt:ElectricityProduced:Total(kw)"].to_f
291
+ profiles_planning[:planning_profile_cust_reactive][i] = profiles_planning[:planning_profile_cust_active][i] * Math.tan(Math.acos(power_factor))
292
+ profiles_planning[:planning_profile_storage_reactive][i] = profiles_planning[:planning_profile_storage_active][i] * Math.tan(Math.acos(power_factor))
293
+ profiles_planning[:planning_profile_dg_reactive][i] = profiles_planning[:planning_profile_dg_active][i] * Math.tan(Math.acos(power_factor))
294
+ if profiles_planning[:planning_profile_cust_active][i] > single_values[:peak_active_power_cons]
295
+ single_values[:peak_active_power_cons] = profiles_planning[:planning_profile_cust_active][i]
296
+ single_values[:peak_reactive_power_cons] = single_values[:peak_active_power_cons] * Math.tan(Math.acos(power_factor))
297
+ end
298
+ if profiles_planning[:planning_profile_storage_active][i] > single_values[:peak_active_power_storage]
299
+ single_values[:peak_active_power_storage] = profiles_planning[:planning_profile_storage_active][i]
300
+ single_values[:peak_reactive_power_storage] = single_values[:peak_active_power_storage] * Math.tan(Math.acos(power_factor))
301
+ end
302
+ if profiles_planning[:planning_profile_dg_active][i] > single_values[:peak_active_power_dg]
303
+ single_values[:peak_active_power_dg] = profiles_planning[:planning_profile_dg_active][i]
304
+ single_values[:peak_reactive_power_dg] = single_values[:peak_active_power_dg] * (Math.tan(Math.acos(power_factor)))
305
+ end
306
+ return profiles_planning, single_values
307
+ end
308
+
286
309
  # defining a method for the customers and generators files creation:
287
310
  # obtaining all the needed input from each feature_report.csv file (active & apparent power and tot energy consumed and produced)
288
311
  # and from each feature_report.json file (area, height, number of users, DG capacity)
289
312
  # the method passes as arguments the urbanopt json and csv output file for each feature and the building coordinates previously calculated
290
313
  # and the "extreme" hours used to plan the network
291
314
  def prosumer_files_load(csv_feature_report, json_feature_report, building_map, building_nodes, hour)
292
- profiles = Hash.new { |h, k| h[k] = [] }
315
+ # add variable to include how many timestep per hour, so the profiles become 48 * n_timestep_per_hour
316
+ n_timestep_per_hour = json_feature_report["timesteps_per_hour"].to_i
317
+ profiles_planning = Hash.new{|h, k| h[k] = Array.new(48*n_timestep_per_hour, 0)} # initializing each profile hash to 0 for the number of intervals considered for the planning of the network
318
+ profiles = Hash.new{|h, k| h[k] = []}
293
319
  single_values = Hash.new(0)
294
320
  @medium_voltage = false
295
- hours = 23
296
- feature_type = json_feature_report['program']['building_types'][0]['building_type']
297
- residential_building_types = 'Single-Family Detached' # add the other types
321
+ hours = 24 * n_timestep_per_hour -1 # change name, maybe to intervals
322
+ feature_type = json_feature_report['program']['building_types'][0]["building_type"]
323
+ residential_building_types = ['Single-Family Detached', 'Single-Family Attached', 'Multifamily', 'Single-Family', 'Multifamily Detached (2 to 4 units)', 'Multifamily Detached (5 or more units)']
324
+
298
325
  # finding the index where to start computing and saving the info, from the value of the "worst-case hour" for the max peak consumption of the district
326
+ # considering num timestep per hours and the fact that each day starts from 1 am
299
327
  if residential_building_types.include? feature_type
300
- profile_start_max = hour.hour_index_max_res - hour.peak_hour_max_res
301
- profile_start_min = hour.hour_index_min_res - hour.peak_hour_min_res
328
+ profile_start_max = hour.hour_index_max_res - ((hour.peak_hour_max_res.split(':')[0].to_i + (hour.peak_hour_max_res.split(':')[1].to_i / 60)) * n_timestep_per_hour)
329
+ profile_start_min = hour.hour_index_min_res - ((hour.peak_hour_min_res.split(':')[0].to_i + (hour.peak_hour_min_res.split(':')[1].to_i / 60)) * n_timestep_per_hour)
302
330
  else
303
- profile_start_max = hour.hour_index_max_comm - hour.peak_hour_max_comm
304
- profile_start_min = hour.hour_index_min_comm - hour.peak_hour_min_comm
331
+ profile_start_max = hour.hour_index_max_comm - ((hour.peak_hour_max_comm.split(':')[0].to_i + (hour.peak_hour_max_comm.split(':')[1].to_i / 60)) * n_timestep_per_hour)
332
+ profile_start_min = hour.hour_index_min_comm - ((hour.peak_hour_min_comm.split(':')[0].to_i + (hour.peak_hour_min_comm.split(':')[1].to_i / 60)) * n_timestep_per_hour)
305
333
  end
306
334
  # finding the index where to start computing and saving the info, from the value of the "most extreme hours" for the max peak consumption of the district
307
- # profile_start_max = hour.hour_index_max - hour.peak_hour_max
308
- # profile_start_min = hour.hour_index_min - hour.peak_hour_min
309
- k = 0 # index for each hour of the year represented in the csv file
310
- i = 0 # to represent the 24 hours
335
+ k = 0 # index for each hour of the year represented in the csv file
336
+ i = hours +1 # to represent the 24 hours in case of max_net_generation day
337
+ j = 0 # to represent the 24 hours in case of peak_demand_day
311
338
  h_cons_batt = 0
312
339
  h_dg_max = 0 # hour with max DG generation
313
340
  h_stor_max = 0 # hour with max storage absorption
314
341
  max_peak = 0
315
- # content = CSV.foreach(csv_feature_report, headers: true) do |power|
316
342
  CSV.foreach(csv_feature_report, headers: true) do |power|
317
- @power_factor = power['Electricity:Facility Power(kW)'].to_f / power['Electricity:Facility Apparent Power(kVA)'].to_f
318
- profiles[:yearly_profile_cust_active].push(power['REopt:Electricity:Load:Total(kw)'].to_f)
343
+ @power_factor = power["Electricity:Facility Power(kW)"].to_f / power["Electricity:Facility Apparent Power(kVA)"].to_f
344
+ profiles[:yearly_profile_cust_active].push(power["REopt:Electricity:Load:Total(kw)"].to_f)
319
345
  profiles[:yearly_profile_cust_reactive].push(profiles[:yearly_profile_cust_active][k] * Math.tan(Math.acos(@power_factor)))
320
- profiles[:yearly_profile_dg_active].push(power['REopt:ElectricityProduced:Total(kw)'].to_f)
346
+ profiles[:yearly_profile_dg_active].push(power["REopt:ElectricityProduced:Total(kw)"].to_f)
321
347
  profiles[:yearly_profile_dg_reactive].push(profiles[:yearly_profile_dg_active][k] * Math.tan(Math.acos(@power_factor)))
322
348
  profiles[:yearly_profile_storage_active].push(power['REopt:Electricity:Grid:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Generator:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:PV:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Wind:ToBattery(kw)'].to_f - power['REopt:Electricity:Storage:ToLoad(kw)'].to_f - power['REopt:Electricity:Storage:ToGrid(kw)'].to_f)
323
349
  profiles[:yearly_profile_storage_reactive].push(profiles[:yearly_profile_storage_active][k] * Math.tan(Math.acos(@power_factor)))
324
- single_values[:energy] += power['REopt:Electricity:Load:Total(kw)'].to_f # calculating the yearly energy consumed by each feature
325
- single_values[:energy_dg] += power['REopt:ElectricityProduced:Total(kw)'].to_f
350
+ single_values[:energy] += power["REopt:Electricity:Load:Total(kw)"].to_f # calculating the yearly energy consumed by each feature
351
+ single_values[:energy_dg] += power["REopt:ElectricityProduced:Total(kw)"].to_f
326
352
  single_values[:energy_storage] += power['REopt:Electricity:Grid:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Generator:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:PV:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Wind:ToBattery(kw)'].to_f - power['REopt:Electricity:Storage:ToLoad(kw)'].to_f - power['REopt:Electricity:Storage:ToGrid(kw)'].to_f
327
- if k >= profile_start_max && k <= profile_start_max + hours || k >= profile_start_min && k <= profile_start_min + hours
328
- profiles[:planning_profile_cust_active].push(power['REopt:Electricity:Load:Total(kw)'].to_f)
329
- if profiles[:planning_profile_cust_active][i] > single_values[:peak_active_power_cons]
330
- single_values[:peak_active_power_cons] = profiles[:planning_profile_cust_active][i]
331
- single_values[:peak_reactive_power_cons] = single_values[:peak_active_power_cons] * Math.tan(Math.acos(power_factor))
332
- single_values[:h_cons_max] = i
333
- end
334
- profiles[:planning_profile_storage_active].push((power['REopt:Electricity:Grid:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Generator:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:PV:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Wind:ToBattery(kw)'].to_f - power['REopt:Electricity:Storage:ToLoad(kw)'].to_f - power['REopt:Electricity:Storage:ToGrid(kw)'].to_f))
335
- if profiles[:planning_profile_storage_active][i] > single_values[:peak_active_power_storage]
336
- single_values[:peak_active_power_storage] = profiles[:planning_profile_storage_active][i]
337
- single_values[:peak_reactive_power_storage] = single_values[:peak_active_power_storage] * Math.tan(Math.acos(power_factor))
338
- single_values[:h_stor_max] = i
339
- end
340
- profiles[:planning_profile_dg_active].push(power['REopt:ElectricityProduced:Total(kw)'].to_f)
341
- if profiles[:planning_profile_dg_active][i] > single_values[:peak_active_power_dg]
342
- single_values[:peak_active_power_dg] = profiles[:planning_profile_dg_active][i]
343
- single_values[:peak_reactive_power_dg] = single_values[:peak_active_power_dg] * Math.tan(Math.acos(power_factor))
344
- single_values[:h_dg_max] = i
345
- end
346
- profiles[:planning_profile_cust_reactive].push(profiles[:planning_profile_cust_active][i] * Math.tan(Math.acos(power_factor)))
347
- profiles[:planning_profile_storage_reactive].push(profiles[:planning_profile_storage_active][i] * Math.tan(Math.acos(power_factor)))
348
- profiles[:planning_profile_dg_reactive].push(profiles[:planning_profile_dg_active][i] * Math.tan(Math.acos(power_factor)))
349
- i += 1
353
+ case k
354
+ when profile_start_min..profile_start_min + (hours)
355
+ profiles_planning, single_values = self.profiles_planning_creation(profiles_planning, power, single_values, i, hours, power_factor)
356
+ i+=1
357
+ when profile_start_max..profile_start_max + (hours)
358
+ profiles_planning, single_values = self.profiles_planning_creation(profiles_planning, power, single_values, j, hours, power_factor)
359
+ j+=1
350
360
  end
351
- k += 1
361
+ k+=1
352
362
  end
353
363
  height = (json_feature_report['program']['maximum_roof_height_ft']).round(2)
354
364
  users = json_feature_report['program']['number_of_residential_units']
355
- der_capacity = sum_dg(json_feature_report['distributed_generation'])
365
+ der_capacity = self.sum_dg(json_feature_report['distributed_generation'])
356
366
  if @only_lv_consumers
357
- nodes_per_bldg, area = av_peak_cons_per_building_type(json_feature_report['program']['building_types'])
358
- if @max_num_lv_nodes == 1
359
- construct_prosumer_general(profiles, single_values, building_map, area, height, users, der_capacity)
367
+ nodes_per_bldg, area = self.av_peak_cons_per_building_type(json_feature_report['program']['building_types'])
368
+ if @max_num_nodes == 1
369
+ self.construct_prosumer_general(profiles, profiles_planning, single_values, building_map, area, height, users, der_capacity)
360
370
  else
361
- construct_prosumer_lv(nodes_per_bldg, profiles, single_values, building_map, building_nodes, area, height, users, der_capacity)
371
+ self.construct_prosumer_lv(nodes_per_bldg, profiles, profiles_planning, single_values, building_map, building_nodes, area, height, users, der_capacity)
362
372
  end
363
373
  else
364
- # this key seems to change between floor_area or floor_area_ft
365
- area = json_feature_report['program'].key?('floor_area') ? (json_feature_report['program']['floor_area']).round(2) : (json_feature_report['program']['floor_area_sqft']).round(2)
374
+ area = json_feature_report['program'].has_key?('floor_area') ? (json_feature_report['program']['floor_area']).round(2) : (json_feature_report['program']['floor_area_sqft']).round(2)
366
375
  # associating 2 nodes (consumers & DG and battery in the same node) per building considering the consumer, the battery and DG
367
- construct_prosumer_general(profiles, single_values, building_map, area, height, users, der_capacity)
368
- end
376
+ self.construct_prosumer_general(profiles, profiles_planning, single_values, building_map, area, height, users, der_capacity)
377
+ end
369
378
  end
370
379
  end
371
380
  end
@@ -73,23 +73,23 @@ module URBANopt
73
73
  (0..@commercial_consumption.length - 1).each do |j|
74
74
  if @commercial_consumption[j] > max_net_load_comm
75
75
  max_net_load_comm = @commercial_consumption[j]
76
- @peak_hour_max_comm = (@time[j].split(' ')[1]).split(':')[0].to_i # defined the most-stressing scenario
76
+ @peak_hour_max_comm = (@time[j].split(' ')[1]) # defined the most-stressing scenario
77
77
  @hour_index_max_comm = j
78
78
  end
79
79
  if @commercial_consumption[j] < min_net_load_comm
80
80
  min_net_load_comm = @commercial_consumption[j]
81
- @peak_hour_min_comm = (@time[j].split(' ')[1]).split(':')[0].to_i # defined the most-stressing scenario
81
+ @peak_hour_min_comm = (@time[j].split(' ')[1]) # defined the most-stressing scenario
82
82
  @hour_index_min_comm = j
83
83
  end
84
84
 
85
85
  if @res_consumption[j] > max_net_load_res
86
86
  max_net_load_res = @res_consumption[j]
87
- @peak_hour_max_res = (@time[j].split(' ')[1]).split(':')[0].to_i # defined the most-stressing scenario
87
+ @peak_hour_max_res = (@time[j].split(' ')[1]) # defined the most-stressing scenario
88
88
  @hour_index_max_res = j
89
89
  end
90
90
  if @res_consumption[j] < min_net_load_res
91
91
  min_net_load_res = @res_consumption[j]
92
- @peak_hour_min_res = (@time[j].split(' ')[1]).split(':')[0].to_i # defined the most-stressing scenario
92
+ @peak_hour_min_res = (@time[j].split(' ')[1]) # defined the most-stressing scenario
93
93
  @hour_index_min_res = j
94
94
  end
95
95
  end
@@ -98,12 +98,12 @@ module URBANopt
98
98
  (0..@commercial_consumption.length - 1).each do |j|
99
99
  if @commercial_consumption[j] > max_net_load_comm
100
100
  max_net_load_comm = @commercial_consumption[j]
101
- @peak_hour_max_comm = (@time[j].split(' ')[1]).split(':')[0].to_i # defined the most-stressing scenario
101
+ @peak_hour_max_comm = (@time[j].split(' ')[1]) # defined the most-stressing scenario
102
102
  @hour_index_max_comm = j
103
103
  end
104
104
  if @res_consumption[j] > max_net_load_res
105
105
  max_net_load_res = @res_consumption[j]
106
- @peak_hour_max_res = (@time[j].split(' ')[1]).split(':')[0].to_i # defined the most-stressing scenario
106
+ @peak_hour_max_res = (@time[j].split(' ')[1]) # defined the most-stressing scenario
107
107
  @hour_index_max_res = j
108
108
  end
109
109
  end
@@ -112,7 +112,7 @@ module URBANopt
112
112
  def aggregate_consumption(file_csv, file_json, n_feature)
113
113
  feature_type = file_json['program']['building_types'][0]['building_type']
114
114
  # residential_building_types = "Single-Family Detached" #add the other types
115
- residential_building_types = ['Single-Family Detached', 'Single-Family Attached', 'MultiFamily', 'Single-Family', 'Multifamily Detached (2 to 4 units)', 'Multifamily Detached (5 or more units)'] # add the other types
115
+ residential_building_types = ['Single-Family Detached', 'Single-Family Attached', 'Multifamily', 'Single-Family', 'Multifamily Detached (2 to 4 units)', 'Multifamily Detached (5 or more units)'] # add the other types
116
116
  puts feature_type
117
117
  j = 0
118
118
  CSV.foreach(file_csv, headers: true) do |power|
@@ -40,6 +40,6 @@
40
40
 
41
41
  module URBANopt
42
42
  module RNM
43
- VERSION = '0.1.0'.freeze
43
+ VERSION = '0.2.0'.freeze
44
44
  end
45
45
  end
@@ -23,10 +23,11 @@ Gem::Specification.new do |spec|
23
23
  spec.require_paths = ['lib', 'catalogs']
24
24
  spec.required_ruby_version = '~> 2.7.0'
25
25
 
26
- spec.add_dependency 'faraday', '1.0.1'
27
- spec.add_dependency 'geoutm', '~>1.0.2'
28
- spec.add_dependency 'rubyzip', '2.3.0'
29
- spec.add_dependency 'urbanopt-geojson', '~> 0.6.5'
26
+ spec.add_dependency 'certified', '~> 1'
27
+ spec.add_dependency 'faraday', '~> 1.0.1'
28
+ spec.add_dependency 'geoutm', '~> 1.0.2'
29
+ spec.add_dependency 'rubyzip', '~> 2.3.2'
30
+ spec.add_dependency 'urbanopt-geojson', '~> 0.7.0'
30
31
 
31
32
  spec.add_development_dependency 'bundler', '~> 2.1'
32
33
  spec.add_development_dependency 'rake', '~> 13.0'
metadata CHANGED
@@ -1,28 +1,42 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: urbanopt-rnm-us
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katherine Fleming
8
8
  - Luca de Rosa
9
- autorequire:
9
+ autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2021-07-22 00:00:00.000000000 Z
12
+ date: 2021-12-07 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: certified
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1'
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: faraday
16
30
  requirement: !ruby/object:Gem::Requirement
17
31
  requirements:
18
- - - '='
32
+ - - "~>"
19
33
  - !ruby/object:Gem::Version
20
34
  version: 1.0.1
21
35
  type: :runtime
22
36
  prerelease: false
23
37
  version_requirements: !ruby/object:Gem::Requirement
24
38
  requirements:
25
- - - '='
39
+ - - "~>"
26
40
  - !ruby/object:Gem::Version
27
41
  version: 1.0.1
28
42
  - !ruby/object:Gem::Dependency
@@ -43,30 +57,30 @@ dependencies:
43
57
  name: rubyzip
44
58
  requirement: !ruby/object:Gem::Requirement
45
59
  requirements:
46
- - - '='
60
+ - - "~>"
47
61
  - !ruby/object:Gem::Version
48
- version: 2.3.0
62
+ version: 2.3.2
49
63
  type: :runtime
50
64
  prerelease: false
51
65
  version_requirements: !ruby/object:Gem::Requirement
52
66
  requirements:
53
- - - '='
67
+ - - "~>"
54
68
  - !ruby/object:Gem::Version
55
- version: 2.3.0
69
+ version: 2.3.2
56
70
  - !ruby/object:Gem::Dependency
57
71
  name: urbanopt-geojson
58
72
  requirement: !ruby/object:Gem::Requirement
59
73
  requirements:
60
74
  - - "~>"
61
75
  - !ruby/object:Gem::Version
62
- version: 0.6.5
76
+ version: 0.7.0
63
77
  type: :runtime
64
78
  prerelease: false
65
79
  version_requirements: !ruby/object:Gem::Requirement
66
80
  requirements:
67
81
  - - "~>"
68
82
  - !ruby/object:Gem::Version
69
- version: 0.6.5
83
+ version: 0.7.0
70
84
  - !ruby/object:Gem::Dependency
71
85
  name: bundler
72
86
  requirement: !ruby/object:Gem::Requirement
@@ -164,6 +178,7 @@ files:
164
178
  - CHANGELOG.md
165
179
  - CONTRIBUTING.md
166
180
  - Gemfile
181
+ - Jenkinsfile
167
182
  - LICENSE.md
168
183
  - README.md
169
184
  - Rakefile
@@ -195,7 +210,7 @@ files:
195
210
  homepage: https://github.com/urbanopt/urbanopt-RNM-us-gem
196
211
  licenses: []
197
212
  metadata: {}
198
- post_install_message:
213
+ post_install_message:
199
214
  rdoc_options: []
200
215
  require_paths:
201
216
  - lib
@@ -212,7 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
212
227
  version: '0'
213
228
  requirements: []
214
229
  rubygems_version: 3.1.4
215
- signing_key:
230
+ signing_key:
216
231
  specification_version: 4
217
232
  summary: Library to create input files for RNM-US
218
233
  test_files: []