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 +4 -4
- data/.gitignore +3 -1
- data/CHANGELOG.md +5 -0
- data/Gemfile +6 -5
- data/README.md +15 -1
- data/Rakefile +31 -6
- data/lib/urbanopt/rnm/api_client.rb +8 -0
- data/lib/urbanopt/rnm/consumers.rb +14 -2
- data/lib/urbanopt/rnm/input_files.rb +18 -0
- data/lib/urbanopt/rnm/prosumers.rb +17 -1
- data/lib/urbanopt/rnm/validation/main_validation.py +135 -33
- data/lib/urbanopt/rnm/validation/opendss_interface.py +458 -79
- data/lib/urbanopt/rnm/validation/plot_lib.py +430 -124
- data/lib/urbanopt/rnm/validation/report.py +370 -0
- data/lib/urbanopt/rnm/validation.rb +8 -6
- data/lib/urbanopt/rnm/version.rb +1 -1
- data/requirements.txt +9 -0
- data/urbanopt-rnm-us-gem.gemspec +1 -1
- metadata +6 -4
@@ -0,0 +1,370 @@
|
|
1
|
+
import opendss_interface
|
2
|
+
import numpy as np
|
3
|
+
|
4
|
+
class Report:
|
5
|
+
def __init__(self, folder,b_numeric_ids):
|
6
|
+
"""Initialices the folder variables"""
|
7
|
+
self.main_folder = folder
|
8
|
+
self.folder=folder+'/Validation'
|
9
|
+
self.num_top_violations=5
|
10
|
+
self.b_numeric_ids=b_numeric_ids
|
11
|
+
|
12
|
+
def is_to_be_analyzed(self,name,label_type):
|
13
|
+
"""Determines if a network component has to be analyzed in the report"""
|
14
|
+
b_analyzed=False
|
15
|
+
if label_type=='All':
|
16
|
+
b_analyzed=True
|
17
|
+
elif not isinstance(name, str):
|
18
|
+
b_analyzed=True
|
19
|
+
elif name.startswith('Line.padswitch'): #RNM-US specific nomenclature #We only condider power lines and tarnsformers as branches
|
20
|
+
b_analyzed=False
|
21
|
+
elif name.startswith('Line.breaker'): #RNM-US specific nomenclature
|
22
|
+
b_analyzed=False
|
23
|
+
elif name.startswith('Line.fuse'): #RNM-US specific nomenclature
|
24
|
+
b_analyzed=False
|
25
|
+
elif name.startswith('Capacitor'):
|
26
|
+
b_analyzed=False
|
27
|
+
elif label_type=="LineTransformer":
|
28
|
+
if (name.startswith("Line.l") or name.startswith("Transformer")): #RNM-US specific nomenclature
|
29
|
+
b_analyzed=True
|
30
|
+
elif label_type=="Line" and name.startswith('Line.l'): #RNM-US specific nomenclature
|
31
|
+
b_analyzed=True
|
32
|
+
elif label_type=="Transformer" and name.startswith('Transformer'):
|
33
|
+
b_analyzed=True
|
34
|
+
elif name.startswith(label_type):
|
35
|
+
b_analyzed=True
|
36
|
+
return b_analyzed
|
37
|
+
|
38
|
+
def get_dict_num_violations(self,v_dict_voltage,v_range,label_type,dict_loads):
|
39
|
+
"""Obtains the number of violations (hours) of each bus"""
|
40
|
+
myopendss_io=opendss_interface.OpenDSS_Interface(self.folder,self.b_numeric_ids)
|
41
|
+
dic_num_violations_v={}
|
42
|
+
for name in v_dict_voltage:
|
43
|
+
if self.is_to_be_analyzed(name,label_type):
|
44
|
+
dic_num_violations_v[name]=myopendss_io.get_num_violations(v_dict_voltage[name],v_range,name,dict_loads)
|
45
|
+
return dic_num_violations_v
|
46
|
+
|
47
|
+
def transpose_dic(self,v_dict,label_type):
|
48
|
+
"""Transpose (t) the dict"""
|
49
|
+
#e.g. Instead of buses in rows and hours in columns, it puts hours in rows and buses in columns
|
50
|
+
keys = v_dict.keys()
|
51
|
+
matrix = [v_dict[i] for i in keys if self.is_to_be_analyzed(i,label_type)]
|
52
|
+
matrix_t={idx:list(i) for idx,i in enumerate(zip(*matrix))}
|
53
|
+
return matrix_t
|
54
|
+
|
55
|
+
def len(self,v_dict,label_type,dict_loads):
|
56
|
+
"""Obtains the number of elements in a dict vector (used to compute percentages)"""
|
57
|
+
#Init to zero
|
58
|
+
num=0
|
59
|
+
#For each key
|
60
|
+
for i in v_dict.keys():
|
61
|
+
#If element t is to be analyzed
|
62
|
+
if self.is_to_be_analyzed(i,label_type):
|
63
|
+
#If there is no dictionary of loads, or if the load is in the dictionary
|
64
|
+
if (dict_loads is None or i in dict_loads):
|
65
|
+
num=num+1 #Add 1
|
66
|
+
return num
|
67
|
+
|
68
|
+
def size(self,v_dict,label_type,dict_loads):
|
69
|
+
"""Obtains the number of elements in a dict matrix (used to compute percentages)"""
|
70
|
+
#Init to zero
|
71
|
+
num=0
|
72
|
+
#For each key
|
73
|
+
for i in v_dict.keys():
|
74
|
+
#If element t is to be analyzed
|
75
|
+
if self.is_to_be_analyzed(i,label_type):
|
76
|
+
#For each hour
|
77
|
+
for idx,j in enumerate(v_dict[i]):
|
78
|
+
if (dict_loads is None): #If there is no dictionary of loads
|
79
|
+
num=num+1 #Add 1
|
80
|
+
elif (i in dict_loads): #if the load is in the dictionary (there is a dictionary of loads when the energy is being evaluated)
|
81
|
+
num=num+dict_loads[i][idx] #Add the energy in that hour
|
82
|
+
return num
|
83
|
+
|
84
|
+
|
85
|
+
def count_nonzero(self,dict,label_type,dict_loads):
|
86
|
+
"""Counts non zero elements (to identify number of violations)"""
|
87
|
+
#Init to zero
|
88
|
+
num=0
|
89
|
+
#For each key
|
90
|
+
for i in dict:
|
91
|
+
#If it is not zero
|
92
|
+
if dict[i]!=0:
|
93
|
+
#If the element is to be analyzed
|
94
|
+
if self.is_to_be_analyzed(i,label_type):
|
95
|
+
#If there is no dictionary of loads, or if the load is in the dictionary
|
96
|
+
if (dict_loads is None or i in dict_loads):
|
97
|
+
num=num+1 #Add 1
|
98
|
+
return num
|
99
|
+
|
100
|
+
def sum(self,dict,label_type,dict_loads):
|
101
|
+
"""It sums all the values in the dict of the elements to be analyzed"""
|
102
|
+
#Init to zero
|
103
|
+
num=0
|
104
|
+
#For each key
|
105
|
+
for i in dict:
|
106
|
+
#If the element is to be analyzed
|
107
|
+
if self.is_to_be_analyzed(i,label_type):
|
108
|
+
#If there is no dictionary of loads, or if the load is in the dictionary
|
109
|
+
if (dict_loads is None or i in dict_loads):
|
110
|
+
num=num+dict[i] #Add the value in the dict
|
111
|
+
return num
|
112
|
+
|
113
|
+
def get_top_violations(self,v_dict,label_type):
|
114
|
+
"""Get the top violations (the elements that have the highest number of violations)"""
|
115
|
+
#Sort the vioations
|
116
|
+
sorted_violations=dict(sorted(v_dict.items(), key=lambda item: item[1],reverse=True))
|
117
|
+
#Init the variables
|
118
|
+
top_violations={}
|
119
|
+
num=0
|
120
|
+
#For each violation
|
121
|
+
for name in sorted_violations:
|
122
|
+
#Only the num_elements top, and only if they have violations
|
123
|
+
if (num<=self.num_top_violations and sorted_violations[name]>0):
|
124
|
+
#If the element is to be analyzed
|
125
|
+
if self.is_to_be_analyzed(name,label_type):
|
126
|
+
top_violations[name]=sorted_violations[name]
|
127
|
+
num=num+1
|
128
|
+
return top_violations
|
129
|
+
|
130
|
+
def get_stats(self,v_dict,v_range,label_type,dict_loads):
|
131
|
+
"""Obtains all the stats required to calculate a given metric"""
|
132
|
+
#Get dict of violations in each element
|
133
|
+
violations=self.get_dict_num_violations(v_dict,v_range,label_type,dict_loads)
|
134
|
+
#Top violations
|
135
|
+
top_violations=self.get_top_violations(violations,label_type)
|
136
|
+
#Number of elements with some violations
|
137
|
+
num_violations=self.count_nonzero(violations,label_type,dict_loads)
|
138
|
+
#Number of elements
|
139
|
+
pc_violations=num_violations/self.len(violations,label_type,dict_loads)
|
140
|
+
#Number of elements x hour with a violation
|
141
|
+
sum_violations=self.sum(violations,label_type,dict_loads)
|
142
|
+
#Number of elements x hour
|
143
|
+
pc_sum_violations=sum_violations/self.size(v_dict,label_type,dict_loads)
|
144
|
+
return num_violations,pc_violations,sum_violations,pc_sum_violations,top_violations
|
145
|
+
|
146
|
+
|
147
|
+
def assess_metric(self,v_dict,v_range,dict_metrics,label_component,label_violation,label_type,b_hours,dict_loads):
|
148
|
+
"""Obtains an individual metric"""
|
149
|
+
#Get required stats to calculate the metric
|
150
|
+
num_violations,pc_violations,sum_violations,pc_sum_violations,top_violations=self.get_stats(v_dict,v_range,label_type,dict_loads)
|
151
|
+
#Check that dict is not empty
|
152
|
+
if dict_loads is None:
|
153
|
+
#Assign labels
|
154
|
+
#For the function calculating the metrics it is needed to specify that it is only lines and transformers, but for label we can call it just "All"
|
155
|
+
if (label_type)=="LineTransformer":
|
156
|
+
label_type="All"
|
157
|
+
if not b_hours:
|
158
|
+
label=label_component+'_'+label_violation
|
159
|
+
else:
|
160
|
+
label='Hours'+'_'+label_violation
|
161
|
+
if label_violation=='Loading':
|
162
|
+
mylabel1='Num_'+label+'_Violations_'+label_type #e.g. number of buses with voltage violations
|
163
|
+
mylabel2='Percentage_'+label+'_Violations_'+label_type+'(%)'
|
164
|
+
else:
|
165
|
+
mylabel1='Num_'+label+'_Violations' #e.g. number of buses with voltage violations
|
166
|
+
mylabel2='Percentage_'+label+'_Violations(%)'
|
167
|
+
#Evaluate number and percentage of violations of the element
|
168
|
+
dict_metrics[mylabel1]=num_violations
|
169
|
+
dict_metrics[mylabel2]=pc_violations*100
|
170
|
+
#Optionally print them in the console
|
171
|
+
#print(mylabel1+': '+str(num_violations))
|
172
|
+
#print(mylabel2+': '+'{:.1f}'.format(pc_violations*100))
|
173
|
+
#If we are not evluating hours (for hours we do not evaluate the number of element x hour violations, because we have already calculated it for the elements)
|
174
|
+
if not b_hours:
|
175
|
+
#Assign labels
|
176
|
+
if dict_loads is None:
|
177
|
+
label='('+label_component+'_x_Hours)_'+label_violation
|
178
|
+
else:
|
179
|
+
label='Energy_kWh_'+label_violation
|
180
|
+
if label_violation=='Loading':
|
181
|
+
mylabel3='Num_'+label+'_Violations_'+label_type #e.g. number of buses*hours with voltage violations (it is the same calculated with hours and with buses)
|
182
|
+
mylabel4='Percentage_'+label+'_Violations_'+label_type+'(%)'
|
183
|
+
else:
|
184
|
+
mylabel3='Num_'+label+'_Violations' #e.g. number of buses*hours with voltage violations (it is the same calculated with hours and with buses)
|
185
|
+
mylabel4='Percentage_'+label+'_Violations(%)'
|
186
|
+
#Evalute number and percentage of violations of elements x hours
|
187
|
+
dict_metrics[mylabel3]=sum_violations
|
188
|
+
dict_metrics[mylabel4]=pc_sum_violations*100
|
189
|
+
#Optionally print them in the console
|
190
|
+
#print(mylabel3+': '+str(sum_violations))
|
191
|
+
#print(mylabel4+': '+'{:.1f}'.format(pc_sum_violations*100))
|
192
|
+
return dict_metrics,top_violations
|
193
|
+
|
194
|
+
def assess_metrics(self,v_dict,v_range,dict_metrics,label_component,label_violation,label_type,dict_loads):
|
195
|
+
"""Asseses all the metrics (elements (bus/branch), hours, or elements x hour) of a given type (voltage, unbalance or loading)"""
|
196
|
+
#Evaluate the metric of the element
|
197
|
+
dict_metrics,top_violations=self.assess_metric(v_dict,v_range,dict_metrics,label_component,label_violation,label_type,False,None)
|
198
|
+
#Evaluete the energy delivered with violations (only for voltages)
|
199
|
+
if ('voltage' in label_violation.lower() or 'unbalance' in label_violation.lower()):
|
200
|
+
dict_metrics,discard=self.assess_metric(v_dict,v_range,dict_metrics,label_component,label_violation,label_type,False,dict_loads)
|
201
|
+
#We transpose it to analyze hours instead of buses
|
202
|
+
v_dic_hours_violations=self.transpose_dic(v_dict,label_type)
|
203
|
+
#We evaluate the metric again, now for the hours
|
204
|
+
dict_metrics,discard=self.assess_metric(v_dic_hours_violations,v_range,dict_metrics,label_component,label_violation,label_type,True,None)
|
205
|
+
return dict_metrics,top_violations
|
206
|
+
|
207
|
+
def get_metrics(self,v_dict_voltage,v_range_voltage,v_dict_unbalance,v_range_unbalance,v_dict_loading,v_range_loading,dict_loads):
|
208
|
+
"""Calculates all the metrics"""
|
209
|
+
dict_metrics={}
|
210
|
+
#Voltage
|
211
|
+
dict_metrics,top_buses_voltage=self.assess_metrics(v_dict_voltage,v_range_voltage,dict_metrics,'Buses','Voltage','All',dict_loads)
|
212
|
+
#Under-Voltage
|
213
|
+
v_range_voltage_under=dict(v_range_voltage)
|
214
|
+
v_range_voltage_under['allowed_range']=(v_range_voltage['allowed_range'][0],float('inf'))
|
215
|
+
dict_metrics,top_buses_under=self.assess_metrics(v_dict_voltage,v_range_voltage_under,dict_metrics,'Buses','Under-voltage','All',dict_loads)
|
216
|
+
#Over-Voltage
|
217
|
+
v_range_voltage_over=dict(v_range_voltage)
|
218
|
+
v_range_voltage_over['allowed_range']=(0,v_range_voltage['allowed_range'][1])
|
219
|
+
dict_metrics,top_buses_over=self.assess_metrics(v_dict_voltage,v_range_voltage_over,dict_metrics,'Buses','Over-voltage','All',dict_loads)
|
220
|
+
#Unbalance
|
221
|
+
dict_metrics,top_buses_unbalance=self.assess_metrics(v_dict_unbalance,v_range_unbalance,dict_metrics,'Buses','Unbalance','All',dict_loads)
|
222
|
+
#Loading
|
223
|
+
dict_metrics,top_branches_violations=self.assess_metrics(v_dict_loading,v_range_loading,dict_metrics,'Branches','Loading','LineTransformer',None)
|
224
|
+
dict_metrics,top_lines_violations=self.assess_metrics(v_dict_loading,v_range_loading,dict_metrics,'Branches','Loading','Line',None)
|
225
|
+
dict_metrics,top_transformers_violations=self.assess_metrics(v_dict_loading,v_range_loading,dict_metrics,'Branches','Loading','Transformer',None)
|
226
|
+
return dict_metrics,top_buses_under,top_buses_over,top_buses_unbalance,top_lines_violations,top_transformers_violations
|
227
|
+
|
228
|
+
|
229
|
+
def write_raw_metrics(self,dict_metrics,top_buses_under,top_buses_over,top_buses_unbalance,top_lines_violations,top_transformers_violations):
|
230
|
+
"""Writes a raw file with all the metrics"""
|
231
|
+
#Path and file name
|
232
|
+
output_file_full_path = self.folder + '/Summary/' + 'Raw_Metrics' + '.csv'
|
233
|
+
#Open
|
234
|
+
with open(output_file_full_path, 'w') as fp:
|
235
|
+
#Write raw metrics
|
236
|
+
for idx,name in enumerate(dict_metrics):
|
237
|
+
fp.write(name+','+'{:.0f}'.format(dict_metrics[name])+'\n')
|
238
|
+
#Top under-voltage violations
|
239
|
+
fp.write('Top Under-Voltage Violations, ')
|
240
|
+
for idx,name in enumerate(top_buses_under):
|
241
|
+
fp.write(name+'('+'{:.0f}'.format(top_buses_under[name])+'h)')
|
242
|
+
if idx != len(top_buses_under)-1:
|
243
|
+
fp.write(', ')
|
244
|
+
fp.write('\n')
|
245
|
+
#Top over-voltage violations
|
246
|
+
fp.write('Top Over-Voltage Violations, ')
|
247
|
+
for idx,name in enumerate(top_buses_over):
|
248
|
+
fp.write(name+'('+'{:.0f}'.format(top_buses_over[name])+'h)')
|
249
|
+
if idx != len(top_buses_over)-1:
|
250
|
+
fp.write(', ')
|
251
|
+
|
252
|
+
fp.write('\n')
|
253
|
+
#Top unbalance violations
|
254
|
+
fp.write('Top Voltage Unbalance Violations, ')
|
255
|
+
for idx,name in enumerate(top_buses_unbalance):
|
256
|
+
fp.write(name+'('+'{:.0f}'.format(top_buses_unbalance[name])+'h)')
|
257
|
+
if idx != len(top_buses_unbalance)-1:
|
258
|
+
fp.write(', ')
|
259
|
+
fp.write('\n')
|
260
|
+
#Top power line thermal limit violations
|
261
|
+
fp.write('Top Power Line Thermal limit Violations, ')
|
262
|
+
for idx,name in enumerate(top_lines_violations):
|
263
|
+
fp.write(name+'('+'{:.0f}'.format(top_lines_violations[name])+'h)')
|
264
|
+
if idx != len(top_lines_violations)-1:
|
265
|
+
fp.write(', ')
|
266
|
+
fp.write('\n')
|
267
|
+
#Top transformer thermal limit violations
|
268
|
+
fp.write('Top Transformer Thermal limit Violations, ')
|
269
|
+
for idx,name in enumerate(top_transformers_violations):
|
270
|
+
fp.write(name+'('+'{:.0f}'.format(top_transformers_violations[name])+'h)')
|
271
|
+
if idx != len(top_transformers_violations)-1:
|
272
|
+
fp.write(', ')
|
273
|
+
fp.write('\n')
|
274
|
+
|
275
|
+
def write_formatted_metrics(self,dict_metrics,top_buses_under,top_buses_over,top_buses_unbalance,top_lines_violations,top_transformers_violations):
|
276
|
+
"""Write formatted summary operational report, presenting all the metrics in an organized way"""
|
277
|
+
#Path and file name
|
278
|
+
output_file_full_path = self.folder + '/Summary/' + 'Summary_Operational_Report' + '.csv'
|
279
|
+
#Open
|
280
|
+
with open(output_file_full_path, 'w') as fp:
|
281
|
+
#Voltage violations
|
282
|
+
fp.write('Voltage violations\n')
|
283
|
+
fp.write('Buses: ')
|
284
|
+
fp.write(' Total '+'{:.0f}'.format(dict_metrics['Num_Buses_Voltage_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Buses_Voltage_Violations(%)'])+'%)')
|
285
|
+
fp.write(' Under '+'{:.0f}'.format(dict_metrics['Num_Buses_Under-voltage_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Buses_Under-voltage_Violations(%)'])+'%)')
|
286
|
+
fp.write(' Over '+'{:.0f}'.format(dict_metrics['Num_Buses_Over-voltage_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Buses_Over-voltage_Violations(%)'])+'%)')
|
287
|
+
fp.write('\n')
|
288
|
+
fp.write('Hours: ')
|
289
|
+
fp.write(' Total '+'{:.0f}'.format(dict_metrics['Num_Hours_Voltage_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Hours_Voltage_Violations(%)'])+'%)')
|
290
|
+
fp.write(' Under '+'{:.0f}'.format(dict_metrics['Num_Hours_Under-voltage_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Hours_Under-voltage_Violations(%)'])+'%)')
|
291
|
+
fp.write(' Over '+'{:.0f}'.format(dict_metrics['Num_Hours_Over-voltage_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Hours_Over-voltage_Violations(%)'])+'%)')
|
292
|
+
fp.write('\n')
|
293
|
+
fp.write('(Buses x Hours): ')
|
294
|
+
fp.write(' Total '+'{:.0f}'.format(dict_metrics['Num_(Buses_x_Hours)_Voltage_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_(Buses_x_Hours)_Voltage_Violations(%)'])+'%)')
|
295
|
+
fp.write(' Under '+'{:.0f}'.format(dict_metrics['Num_(Buses_x_Hours)_Under-voltage_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_(Buses_x_Hours)_Under-voltage_Violations(%)'])+'%)')
|
296
|
+
fp.write(' Over '+'{:.0f}'.format(dict_metrics['Num_(Buses_x_Hours)_Over-voltage_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_(Buses_x_Hours)_Over-voltage_Violations(%)'])+'%)')
|
297
|
+
fp.write('\n')
|
298
|
+
fp.write('Energy_kWh: ')
|
299
|
+
fp.write(' Total '+'{:.0f}'.format(dict_metrics['Num_Energy_kWh_Voltage_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Energy_kWh_Voltage_Violations(%)'])+'%)')
|
300
|
+
fp.write(' Under '+'{:.0f}'.format(dict_metrics['Num_Energy_kWh_Under-voltage_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Energy_kWh_Under-voltage_Violations(%)'])+'%)')
|
301
|
+
fp.write(' Over '+'{:.0f}'.format(dict_metrics['Num_Energy_kWh_Over-voltage_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Energy_kWh_Over-voltage_Violations(%)'])+'%)')
|
302
|
+
fp.write('\n')
|
303
|
+
fp.write('\n')
|
304
|
+
#Unbalance violations
|
305
|
+
fp.write('Voltage unbalance violations\n')
|
306
|
+
fp.write('Buses: ')
|
307
|
+
fp.write(' Total '+'{:.0f}'.format(dict_metrics['Num_Buses_Unbalance_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Buses_Unbalance_Violations(%)'])+'%)')
|
308
|
+
fp.write('\n')
|
309
|
+
fp.write('Hours: ')
|
310
|
+
fp.write(' Total '+'{:.0f}'.format(dict_metrics['Num_Hours_Unbalance_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Hours_Unbalance_Violations(%)'])+'%)')
|
311
|
+
fp.write('\n')
|
312
|
+
fp.write('(Buses x Hours): ')
|
313
|
+
fp.write(' Total '+'{:.0f}'.format(dict_metrics['Num_(Buses_x_Hours)_Unbalance_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_(Buses_x_Hours)_Unbalance_Violations(%)'])+'%)')
|
314
|
+
fp.write('\n')
|
315
|
+
fp.write('Energy_kWh: ')
|
316
|
+
fp.write(' Total '+'{:.0f}'.format(dict_metrics['Num_Energy_kWh_Unbalance_Violations'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Energy_kWh_Unbalance_Violations(%)'])+'%)')
|
317
|
+
fp.write('\n')
|
318
|
+
fp.write('\n')
|
319
|
+
#Thermal limit violations
|
320
|
+
fp.write('Thermal limit violations\n')
|
321
|
+
fp.write('Branches: ')
|
322
|
+
fp.write(' Total '+'{:.0f}'.format(dict_metrics['Num_Branches_Loading_Violations_All'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Branches_Loading_Violations_All(%)'])+'%)')
|
323
|
+
fp.write(' Lines '+'{:.0f}'.format(dict_metrics['Num_Branches_Loading_Violations_Line'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Branches_Loading_Violations_Line(%)'])+'%)')
|
324
|
+
fp.write(' Transformers '+'{:.0f}'.format(dict_metrics['Num_Branches_Loading_Violations_Transformer'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Branches_Loading_Violations_Transformer(%)'])+'%)')
|
325
|
+
fp.write('\n')
|
326
|
+
fp.write('Hours: ')
|
327
|
+
fp.write(' Total '+'{:.0f}'.format(dict_metrics['Num_Hours_Loading_Violations_All'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Hours_Loading_Violations_All(%)'])+'%)')
|
328
|
+
fp.write(' Lines '+'{:.0f}'.format(dict_metrics['Num_Hours_Loading_Violations_Line'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Hours_Loading_Violations_Line(%)'])+'%)')
|
329
|
+
fp.write(' Transformers '+'{:.0f}'.format(dict_metrics['Num_Hours_Loading_Violations_Transformer'])+'('+'{:.0f}'.format(dict_metrics['Percentage_Hours_Loading_Violations_Transformer(%)'])+'%)')
|
330
|
+
fp.write('\n')
|
331
|
+
fp.write('(Branches x Hours): ')
|
332
|
+
fp.write(' Total '+'{:.0f}'.format(dict_metrics['Num_(Branches_x_Hours)_Loading_Violations_All'])+'('+'{:.0f}'.format(dict_metrics['Percentage_(Branches_x_Hours)_Loading_Violations_All(%)'])+'%)')
|
333
|
+
fp.write(' Lines '+'{:.0f}'.format(dict_metrics['Num_(Branches_x_Hours)_Loading_Violations_Line'])+'('+'{:.0f}'.format(dict_metrics['Percentage_(Branches_x_Hours)_Loading_Violations_Line(%)'])+'%)')
|
334
|
+
fp.write(' Transformers '+'{:.0f}'.format(dict_metrics['Num_(Branches_x_Hours)_Loading_Violations_Transformer'])+'('+'{:.0f}'.format(dict_metrics['Percentage_(Branches_x_Hours)_Loading_Violations_Transformer(%)'])+'%)')
|
335
|
+
fp.write('\n')
|
336
|
+
fp.write('\n')
|
337
|
+
#Top violations
|
338
|
+
fp.write('Top Under-Voltage Violations\n')
|
339
|
+
for idx,name in enumerate(top_buses_under):
|
340
|
+
fp.write(name+'('+'{:.0f}'.format(top_buses_under[name])+'h)')
|
341
|
+
fp.write('\n')
|
342
|
+
fp.write('\n')
|
343
|
+
fp.write('Top Over-Voltage Violations\n')
|
344
|
+
for idx,name in enumerate(top_buses_over):
|
345
|
+
fp.write(name+'('+'{:.0f}'.format(top_buses_over[name])+'h)')
|
346
|
+
fp.write('\n')
|
347
|
+
fp.write('\n')
|
348
|
+
fp.write('Top Unbalance Violations\n')
|
349
|
+
for idx,name in enumerate(top_buses_unbalance):
|
350
|
+
fp.write(name+'('+'{:.0f}'.format(top_buses_unbalance[name])+'h)')
|
351
|
+
fp.write('\n')
|
352
|
+
fp.write('\n')
|
353
|
+
fp.write('Top Power Line Thermal limit Violations\n')
|
354
|
+
for idx,name in enumerate(top_lines_violations):
|
355
|
+
fp.write(name+'('+'{:.0f}'.format(top_lines_violations[name])+'h)')
|
356
|
+
fp.write('\n')
|
357
|
+
fp.write('\n')
|
358
|
+
fp.write('Top Transformer Thermal limit Violations\n')
|
359
|
+
for idx,name in enumerate(top_transformers_violations):
|
360
|
+
fp.write(name+'('+'{:.0f}'.format(top_transformers_violations[name])+'h)')
|
361
|
+
fp.write('\n')
|
362
|
+
|
363
|
+
def write_summary_operational_report(self,subfolder,v_dict_voltage,v_range_voltage,v_dict_unbalance,v_range_unbalance,v_dict_loading,v_range_loading,v_dict_loads):
|
364
|
+
"""Calculate and write all the metrics and the summary operational report"""
|
365
|
+
#Calculate all the metrics
|
366
|
+
dict_metrics,top_buses_under,top_buses_over,top_buses_unbalance,top_lines_violations,top_transformers_violations=self.get_metrics(v_dict_voltage,v_range_voltage,v_dict_unbalance,v_range_unbalance,v_dict_loading,v_range_loading,v_dict_loads)
|
367
|
+
#Write a file with the raw metrics
|
368
|
+
self.write_raw_metrics(dict_metrics,top_buses_under,top_buses_over,top_buses_unbalance,top_lines_violations,top_transformers_violations)
|
369
|
+
#Write a file with summary operational report
|
370
|
+
self.write_formatted_metrics(dict_metrics,top_buses_under,top_buses_over,top_buses_unbalance,top_lines_violations,top_transformers_violations)
|
@@ -49,12 +49,14 @@ module URBANopt
|
|
49
49
|
##
|
50
50
|
# [parameters:]
|
51
51
|
# * +rnm_dirname+ - _String_ - name of RNM-US directory that will contain the input files (within the scenario directory)
|
52
|
-
def initialize(rnm_full_path)
|
52
|
+
def initialize(rnm_full_path,b_numeric_ids)
|
53
53
|
# absolute path
|
54
54
|
@rnm_full_path = rnm_full_path
|
55
55
|
@opendss_full_path=File.join(@rnm_full_path,'results/OpenDSS')
|
56
|
+
@b_numeric_ids=b_numeric_ids
|
56
57
|
if !Dir.exist?(@opendss_full_path)
|
57
|
-
puts 'Error: folder does not exist'+@
|
58
|
+
puts 'Error: folder does not exist'+@opendss_full_path
|
59
|
+
raise 'No OpenDSS directory found for this scenario...run simulation first.'
|
58
60
|
end
|
59
61
|
end
|
60
62
|
|
@@ -63,11 +65,11 @@ module URBANopt
|
|
63
65
|
# Run validation
|
64
66
|
##
|
65
67
|
def run_validation()
|
66
|
-
puts "
|
68
|
+
puts "Initiating OpenDSS validation in folder"
|
67
69
|
puts @opendss_full_path
|
68
|
-
puts "This can take
|
69
|
-
#puts `python ./lib/urbanopt/rnm/validation/main_validation.py #{@rnm_full_path}`
|
70
|
-
log=`python ./lib/urbanopt/rnm/validation/main_validation.py #{@opendss_full_path}`
|
70
|
+
puts "This can take several minutes"
|
71
|
+
# puts `python ./lib/urbanopt/rnm/validation/main_validation.py #{@rnm_full_path}`
|
72
|
+
log=`python ./lib/urbanopt/rnm/validation/main_validation.py #{@opendss_full_path} #{@b_numeric_ids}`
|
71
73
|
puts log
|
72
74
|
end
|
73
75
|
end
|
data/lib/urbanopt/rnm/version.rb
CHANGED
data/requirements.txt
ADDED
data/urbanopt-rnm-us-gem.gemspec
CHANGED
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_dependency 'faraday', '~> 1.0.1'
|
28
28
|
spec.add_dependency 'geoutm', '~> 1.0.2'
|
29
29
|
spec.add_dependency 'rubyzip', '~> 2.3.2'
|
30
|
-
spec.add_dependency 'urbanopt-geojson', '~> 0.
|
30
|
+
spec.add_dependency 'urbanopt-geojson', '~> 0.9.0'
|
31
31
|
|
32
32
|
spec.add_development_dependency 'bundler', '~> 2.1'
|
33
33
|
spec.add_development_dependency 'rake', '~> 13.0'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: urbanopt-rnm-us
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katherine Fleming
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-
|
12
|
+
date: 2022-12-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: certified
|
@@ -73,14 +73,14 @@ dependencies:
|
|
73
73
|
requirements:
|
74
74
|
- - "~>"
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: 0.
|
76
|
+
version: 0.9.0
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
81
|
- - "~>"
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version: 0.
|
83
|
+
version: 0.9.0
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
85
|
name: bundler
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -207,10 +207,12 @@ files:
|
|
207
207
|
- lib/urbanopt/rnm/validation/main_validation.py
|
208
208
|
- lib/urbanopt/rnm/validation/opendss_interface.py
|
209
209
|
- lib/urbanopt/rnm/validation/plot_lib.py
|
210
|
+
- lib/urbanopt/rnm/validation/report.py
|
210
211
|
- lib/urbanopt/rnm/version.rb
|
211
212
|
- lib/urbanopt/rnm/wires_class.rb
|
212
213
|
- lib/urbanopt/rnm/wires_opendss.rb
|
213
214
|
- opendss_catalog.json
|
215
|
+
- requirements.txt
|
214
216
|
- urbanopt-rnm-us-gem.gemspec
|
215
217
|
homepage: https://github.com/urbanopt/urbanopt-RNM-us-gem
|
216
218
|
licenses: []
|