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.
@@ -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
@@ -40,6 +40,6 @@
40
40
 
41
41
  module URBANopt
42
42
  module RNM
43
- VERSION = '0.3.0'.freeze
43
+ VERSION = '0.5.0'.freeze
44
44
  end
45
45
  end
data/lib/urbanopt/rnm.rb CHANGED
@@ -57,3 +57,4 @@ require 'urbanopt/rnm/input_files'
57
57
  require 'urbanopt/rnm/runner'
58
58
  require 'urbanopt/rnm/api_client'
59
59
  require 'urbanopt/rnm/post_processor'
60
+ require 'urbanopt/rnm/validation'