urbanopt-rnm-us 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/CHANGELOG.md +11 -0
- data/Gemfile +6 -5
- data/README.md +21 -0
- data/Rakefile +56 -0
- data/catalogs/extended_catalog.json +1928 -1796
- data/lib/urbanopt/rnm/api_client.rb +10 -2
- data/lib/urbanopt/rnm/consumers.rb +17 -4
- data/lib/urbanopt/rnm/input_files.rb +19 -1
- data/lib/urbanopt/rnm/prosumers.rb +19 -3
- data/lib/urbanopt/rnm/runner.rb +10 -0
- data/lib/urbanopt/rnm/transformer_opendss.rb +1 -5
- data/lib/urbanopt/rnm/validation/main_validation.py +153 -0
- data/lib/urbanopt/rnm/validation/opendss_interface.py +613 -0
- data/lib/urbanopt/rnm/validation/plot_lib.py +528 -0
- data/lib/urbanopt/rnm/validation/report.py +370 -0
- data/lib/urbanopt/rnm/validation.rb +77 -0
- data/lib/urbanopt/rnm/version.rb +1 -1
- data/lib/urbanopt/rnm.rb +1 -0
- data/opendss_catalog.json +2216 -0
- data/requirements.txt +9 -0
- data/urbanopt-rnm-us-gem.gemspec +1 -1
- metadata +11 -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)
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# *********************************************************************************
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
|
3
|
+
# contributors. All rights reserved.
|
4
|
+
|
5
|
+
# Redistribution and use in source and binary forms, with or without modification,
|
6
|
+
# are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
# Redistributions of source code must retain the above copyright notice, this list
|
9
|
+
# of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
# Redistributions in binary form must reproduce the above copyright notice, this
|
12
|
+
# list of conditions and the following disclaimer in the documentation and/or other
|
13
|
+
# materials provided with the distribution.
|
14
|
+
|
15
|
+
# Neither the name of the copyright holder nor the names of its contributors may be
|
16
|
+
# used to endorse or promote products derived from this software without specific
|
17
|
+
# prior written permission.
|
18
|
+
|
19
|
+
# Redistribution of this software, without modification, must refer to the software
|
20
|
+
# by the same designation. Redistribution of a modified version of this software
|
21
|
+
# (i) may not refer to the modified version by the same designation, or by any
|
22
|
+
# confusingly similar designation, and (ii) must refer to the underlying software
|
23
|
+
# originally provided by Alliance as "URBANopt". Except to comply with the foregoing,
|
24
|
+
# the term "URBANopt", or any confusingly similar designation may not be used to
|
25
|
+
# refer to any modified version of this software or any modified version of the
|
26
|
+
# underlying software originally provided by Alliance without the prior written
|
27
|
+
# consent of Alliance.
|
28
|
+
|
29
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
30
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
31
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
32
|
+
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
33
|
+
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
34
|
+
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
35
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
36
|
+
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
37
|
+
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
38
|
+
# OF THE POSSIBILITY OF SUCH DAMAGE.
|
39
|
+
# *********************************************************************************
|
40
|
+
|
41
|
+
require 'urbanopt/rnm/logger'
|
42
|
+
|
43
|
+
module URBANopt
|
44
|
+
module RNM
|
45
|
+
# Class for OpenDSS validation (runs python script)
|
46
|
+
class Validation
|
47
|
+
##
|
48
|
+
# Initialize attributes: ++run directory+
|
49
|
+
##
|
50
|
+
# [parameters:]
|
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,b_numeric_ids)
|
53
|
+
# absolute path
|
54
|
+
@rnm_full_path = rnm_full_path
|
55
|
+
@opendss_full_path=File.join(@rnm_full_path,'results/OpenDSS')
|
56
|
+
@b_numeric_ids=b_numeric_ids
|
57
|
+
if !Dir.exist?(@opendss_full_path)
|
58
|
+
puts 'Error: folder does not exist'+@opendss_full_path
|
59
|
+
raise 'No OpenDSS directory found for this scenario...run simulation first.'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
##
|
65
|
+
# Run validation
|
66
|
+
##
|
67
|
+
def run_validation()
|
68
|
+
puts "Initiating OpenDSS validation in folder"
|
69
|
+
puts @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}`
|
73
|
+
puts log
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/urbanopt/rnm/version.rb
CHANGED
data/lib/urbanopt/rnm.rb
CHANGED