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.
@@ -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'+@rnm_full_path
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 "Initating OpenDSS validation in folder"
68
+ puts "Initiating OpenDSS validation in folder"
67
69
  puts @opendss_full_path
68
- puts "This can take some minutes"
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
@@ -40,6 +40,6 @@
40
40
 
41
41
  module URBANopt
42
42
  module RNM
43
- VERSION = '0.4.0'.freeze
43
+ VERSION = '0.5.0'.freeze
44
44
  end
45
45
  end
data/requirements.txt ADDED
@@ -0,0 +1,9 @@
1
+ # python dependencies for RNM validation module
2
+ opendssdirect.py>=0.6.1
3
+ pandas>=1.2.5
4
+ matplotlib>=3.6.0
5
+ networkx
6
+ seaborn
7
+ plotly
8
+ shapely
9
+ geopandas
@@ -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.8.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.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-09-30 00:00:00.000000000 Z
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.8.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.8.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: []