urbanopt-rnm-us 0.1.0 → 0.2.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: 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: []