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,613 @@
1
+ import opendssdirect as dss
2
+ import pandas as pd
3
+ import matplotlib.pyplot as plt
4
+ import numpy as np
5
+ import sys as sys
6
+ import math
7
+ import networkx as nx
8
+ from datetime import datetime
9
+
10
+ class OpenDSS_Interface:
11
+ def __init__(self, folder,b_numeric_ids):
12
+ """Initialices the folder variables"""
13
+ self.main_folder = folder
14
+ self.folder=folder+'/Validation'
15
+ self.b_numeric_ids=b_numeric_ids
16
+
17
+ def remove_terminal(self,bus):
18
+ """Removes the terminal from the bus name"""
19
+ if isinstance(bus,str):
20
+ return bus.split('.')[0] #(everything to the right of point ".")
21
+ else:
22
+ return bus
23
+
24
+ def is_to_be_analyzed(self,name):
25
+ """Determines if an element has to be analyzed"""
26
+ b_analyzed=False
27
+ if name.startswith('Line.padswitch'): #RNM specific #We only condider power lines and tarnsformers as branches
28
+ b_analyzed=False
29
+ elif name.startswith('Line.breaker'): #RNM specific
30
+ b_analyzed=False
31
+ elif name.startswith('Line.fuse'): #RNM specific
32
+ b_analyzed=False
33
+ elif name.startswith('Capacitor'):
34
+ b_analyzed=False
35
+ elif name.startswith('Line.l'): #RNM specific
36
+ b_analyzed=True
37
+ elif name.startswith('Transformer'):
38
+ b_analyzed=True
39
+ else:
40
+ print("Component type was not explicitly consiered in the validation module. It is not analyzed.")
41
+ print(name)
42
+ return b_analyzed
43
+
44
+
45
+
46
+ def extract_period(self,v_value_i,v_value_period,i,end_index,num_periods,month):
47
+ """#Assign v_value_i to the corresponding month"""
48
+ v_value_period[month-1].extend(v_value_i)
49
+ return v_value_period
50
+
51
+ def add_to_dictionary(self,dict_all,dict_i):
52
+ """Adds dict_i to the dictonary dict_all"""
53
+ for idx,name in enumerate(dict_i):
54
+ if name in dict_all: #if not empty
55
+ dict_all[name].append(dict_i[name])
56
+ else:
57
+ dict_all[name]=[dict_i[name]]
58
+
59
+ def dss_run_command(self,command):
60
+ """Runs an OpenDSS Direct command"""
61
+ #Run command
62
+ output=dss.run_command(command)
63
+ #If it has any output, print it
64
+ if (len(output)>0):
65
+ print(output)
66
+
67
+
68
+ def get_time_stamp(self):
69
+ """Read the timestamps and obtain the raw and the formatted version"""
70
+ #Select file
71
+ timestamp_file = self.main_folder + '/profiles/' + 'timestamps.csv'
72
+ #Read file
73
+ timestamps = []
74
+ with open(timestamp_file) as csv_data_file:
75
+ for row in csv_data_file:
76
+ timestamps.append(row.strip())
77
+ #remove the header
78
+ timestamps.pop(0)
79
+ #Define date time format
80
+ dt_format = '%Y/%m/%d %H:%M:%S'
81
+ #Convert to datetime structure
82
+ timestamps_datetime= [datetime.strptime(i,dt_format) for i in timestamps]
83
+ return timestamps,timestamps_datetime
84
+
85
+
86
+ def get_all_voltage(self):
87
+ """Computes over and under voltages for all buses"""
88
+ #Get bus names
89
+ bus_names = dss.Circuit.AllBusNames()
90
+ #Init variables
91
+ dict_voltage = {}
92
+ v_voltage = [0 for _ in range(len(bus_names))]
93
+ #For each bus
94
+ for idx,b in enumerate(bus_names):
95
+ #Set it as active bus
96
+ dss.Circuit.SetActiveBus(b)
97
+ #Get voltage and angle
98
+ vang = dss.Bus.puVmagAngle()
99
+ #Get voltage magnitude
100
+ if len(vang[::2]) > 0:
101
+ #Average of the voltages in all the phases, discarding the angles
102
+ vmag = sum(vang[::2])/(len(vang)/2)
103
+ else:
104
+ vmag = 0
105
+ #Add voltage magnitude to dictionary and to list of voltages
106
+ dict_voltage[b] = vmag
107
+ v_voltage[idx]=vmag
108
+
109
+ return dict_voltage,v_voltage
110
+
111
+
112
+ def get_all_unbalance(self):
113
+ """Computes voltage unbalance for all buses"""
114
+ # Based on IEEE standard 141-1993 https://www.sciencedirect.com/science/article/pii/S0378779620304594
115
+ #Get bus names
116
+ bus_names = dss.Circuit.AllBusNames()
117
+ #Init variables
118
+ dict_voltage = {}
119
+ v_voltage = [0 for _ in range(len(bus_names))]
120
+ #For each bus
121
+ for idx,b in enumerate(bus_names):
122
+ dss.Circuit.SetActiveBus(b)
123
+ #Set it as active bus
124
+ #Get voltage and angle
125
+ vang = dss.Bus.puVmagAngle()
126
+ #Evaluate the unbalance
127
+ if len(vang[::2]) ==3: #if three-phase
128
+ vmedio = sum(vang[::2])/(len(vang)/2) #Average of the voltages in all the phases, discarding the angles
129
+ va=vang[0] #Phase A
130
+ vb=vang[2] #Phase B
131
+ vc=vang[4] #Phase C
132
+ vmax=max(abs(va-vmedio),abs(vb-vmedio),abs(vc-vmedio)) #Phase Voltage Unbalance Rate (PVUR) (based on IEEE)
133
+ elif len(vang[::2]) ==2: #If two phase
134
+ vmedio = sum(vang[::2])/(len(vang)/2) #Average of the voltages in all the phases, discarding the angles
135
+ va=vang[0] #Phase A
136
+ vb=vang[2] #Phase B
137
+ vmax=max(abs(va-vmedio),abs(vb-vmedio)) #Phase Voltage Unbalance Rate (PVUR)
138
+ elif len(vang[::2]) ==1: #If single-phase
139
+ vmax = 0 #No unblance
140
+ else: #Not other cases are considered
141
+ print("Value: "+str(vang)+"Lend: "+str(len(vang)))
142
+ raise Exception("Voltage is not single-, two- or three-phase")
143
+ #Add unbalance to dictionary and to list of unbalances
144
+ dict_voltage[b] = vmax
145
+ v_voltage[idx]=vmax
146
+ return dict_voltage,v_voltage
147
+
148
+
149
+ def get_all_loads(self):
150
+ """Get all loads peak kW and kVAr"""
151
+ #Init variables
152
+ dict_loads = {}
153
+ myrange=range(0,dss.Loads.Count())
154
+ v_loads_kw = [0 for _ in myrange]
155
+ v_loads_kvar = [0 for _ in myrange]
156
+ #For each load
157
+ for idx in myrange:
158
+ #Set load index
159
+ dss.Loads.Idx(idx+1)
160
+ #Get kW of the load
161
+ kw = dss.Loads.kW()
162
+ #Get kVAr of the load
163
+ kvar = dss.Loads.kvar()
164
+ #Get load name
165
+ name = 'LOAD.'+dss.Loads.Name()
166
+ #Add load to dictionary and to list of load kW/kVAr
167
+ dict_loads[name] = kw
168
+ v_loads_kw[idx]=kw
169
+ v_loads_kvar[idx]=kvar
170
+ return dict_loads,v_loads_kw,v_loads_kvar
171
+
172
+ def get_all_loadshapes(self,i):
173
+ """Get all loadshapes"""
174
+ dict_loads = {}
175
+ #Init variables
176
+ myrange=range(0,dss.LoadShape.Count())
177
+ v_loads_kw = [0 for _ in myrange]
178
+ v_loads_kvar = [0 for _ in myrange]
179
+ #For each loadshape
180
+ for idx in myrange:
181
+ #Set loadshape index
182
+ dss.LoadShape.Idx(idx+1)
183
+ #Get hourly kW
184
+ kw = dss.LoadShape.PMult()
185
+ #Get hourly kVAr
186
+ kvar = dss.LoadShape.QMult()
187
+ #Get load name
188
+ name = 'LOAD.'+dss.LoadShape.Name()
189
+ #If not default load (discard it)
190
+ if not(name=='LOAD.default'):
191
+ if len(kw)>1:
192
+ #Add load to dictionary and to list of load kW
193
+ dict_loads[name] = kw[i]
194
+ v_loads_kw[idx]=kw[i]
195
+ if len(kvar)>1:
196
+ #Add to list of load kW
197
+ v_loads_kvar[idx]=kvar[i]
198
+
199
+ return dict_loads,v_loads_kw,v_loads_kvar
200
+
201
+
202
+ def get_all_buses_loads(self,i):
203
+ """Get the load of all the buses"""
204
+ #Get data from load shapes
205
+ dict_loads,v_loads_kw,v_loads_kvar=self.get_all_loadshapes(i)
206
+ #Get all bus names
207
+ bus_names = dss.Circuit.AllBusNames()
208
+ #Init dict
209
+ dict_buses_loads = {}
210
+ #For each bus
211
+ for idx,b in enumerate(bus_names):
212
+ #Set it to the active bus
213
+ dss.Circuit.SetActiveBus(b)
214
+ #Get its loads
215
+ loads = dss.Bus.LoadList()
216
+ #Init its load to zero
217
+ load_bus=0
218
+ #For each load in the bus
219
+ for l in loads:
220
+ #Assign it the load in the loadshape
221
+ load_bus=load_bus+dict_loads[l+"_profile"] #RNM-US specific (the load shapes names are the name of the laods + "_profile")
222
+ #Add to dictionary
223
+ dict_buses_loads[b] = load_bus
224
+ return dict_buses_loads,v_loads_kw,v_loads_kvar
225
+
226
+
227
+ def get_all_buses_ids(self):
228
+ """Get the load of all the buses"""
229
+ #Get all bus names
230
+ bus_names = dss.Circuit.AllBusNames()
231
+ #Init dict
232
+ dict_buses_ids = {}
233
+ dict_ids_buses = {}
234
+ #Init numeric identifier
235
+ num_id=1
236
+ #For each bus
237
+ for idx,b in enumerate(bus_names):
238
+ if b=='st_mat': #RNM-US specific (st_mat is the slack bus)
239
+ dict_buses_ids[b] = str(0)
240
+ dict_ids_buses[str(0)] = b
241
+ else:
242
+ dict_buses_ids[b] = str(num_id)
243
+ dict_ids_buses[str(num_id)] = b
244
+ num_id=num_id+1
245
+ return dict_buses_ids,dict_ids_buses
246
+
247
+ def get_all_lines(self):
248
+ """Gets the normal ampacity of power lines"""
249
+ #Init variables
250
+ dict_lines = {}
251
+ myrange=range(0,dss.Lines.Count())
252
+ v_lines_norm_amps = []
253
+ #For each power line
254
+ for idx in myrange:
255
+ #Set power line index
256
+ dss.Lines.Idx(idx+1)
257
+ #Get the normal amapcity
258
+ normal_amps = dss.Lines.NormAmps()
259
+ #Get the power line name
260
+ name = 'Line.'+dss.Lines.Name()
261
+ #If it is a power line
262
+ if name.startswith("Line.l("): #RNM-US specific (all power lines start with "Line.l("). This discards for example fuses, switches, ....
263
+ #Add to dictionary and to list
264
+ dict_lines[name] = normal_amps
265
+ v_lines_norm_amps.append(normal_amps)
266
+
267
+ return dict_lines,v_lines_norm_amps
268
+
269
+ def get_all_transformers(self):
270
+ """Gets the size of transformers"""
271
+ #Init variables
272
+ dict_transformers = {}
273
+ myrange=range(0,dss.Transformers.Count())
274
+ v_transformers_kva = []
275
+ #For each transformer
276
+ for idx in myrange:
277
+ #Set the transformer index
278
+ dss.Transformers.Idx(idx+1)
279
+ #Get the transformer size in kVA
280
+ kva = dss.Transformers.kVA()
281
+ #Get the transformer size
282
+ name = 'Transformer.'+dss.Transformers.Name()
283
+ #If it is a distribution transformer
284
+ if name.startswith("Transformer.tr("): #Distribution transformer, RNM-US specific (all distribution transformers start with "Transformer.tr("). This discards for example transformers in primary substations
285
+ #Add to dictionary and to list
286
+ dict_transformers[name] = kva
287
+ v_transformers_kva.append(kva)
288
+ return dict_transformers,v_transformers_kva
289
+
290
+
291
+ def get_all_power(self):
292
+ """Computes power in all circuits (not used, loading is measured instead)"""
293
+ #Get all element names
294
+ circuit_names = dss.Circuit.AllElementNames()
295
+ #Init variables
296
+ dict_power = {}
297
+ v_power = [0 for _ in range(len(circuit_names))]
298
+ #For each circuit
299
+ for idx,b in enumerate(circuit_names):
300
+ #Set the active element
301
+ dss.Circuit.SetActiveElement(b)
302
+ #Calculates the power through the circuit
303
+ power = dss.CktElement.Powers()
304
+ if len(power[::2]) > 0:
305
+ poweravg = sum(power[::2])/(len(power)/2)
306
+ else:
307
+ poweravg = 0
308
+ #Add to dictionary and to list
309
+ dict_power[b] = poweravg
310
+ v_power[idx]=poweravg
311
+
312
+ return dict_power,v_power
313
+
314
+
315
+ def get_all_loading(self):
316
+ """Computes loading in all circuits"""
317
+ #Get all element names
318
+ circuit_names = dss.Circuit.AllElementNames()
319
+ #Init variables
320
+ dict_loading = {}
321
+ dict_buses_element={} #Associate the element to the buses (this has the inconvenient that only associates one element to each pair of buses)
322
+ v_loading = [0 for _ in range(len(circuit_names))]
323
+ #For each circuit
324
+ for idx,element in enumerate(circuit_names):
325
+ #Set the active element
326
+ dss.Circuit.SetActiveElement(element)
327
+ #Get the buses in the elment
328
+ buses = dss.CktElement.BusNames()
329
+ #Evaluate only if it is a branch (two buses)
330
+ if (len(buses)>=2):
331
+ #Obtain the current through the element
332
+ current = dss.CktElement.CurrentsMagAng()
333
+ #Obtain the number of terminals
334
+ num_terminals=dss.CktElement.NumTerminals()
335
+ #Obtain the current magnitude (first terminal only, because in transformers NormalAmps gives the normal ampacity of the first winding) (to compare the same magnitudes)
336
+ currentmag = current[0]
337
+ #Obtain the normal amapcity
338
+ nominal_current = dss.CktElement.NormalAmps()
339
+ #Transformers have applied a 1.1 factor in the calculation of NormalAmps
340
+ #See library that OpenDSSdirect uses in https://github.com/dss-extensions/dss_capi/blob/master/src/PDElements/Transformer.pas
341
+ #in particular line code: AmpRatings[i] := 1.1 * kVARatings[i] / Fnphases / Vfactor;
342
+ #Remove (do not use) the 1.1 margin set in the library to the nominal current
343
+ if (element.startswith("Transformer")):
344
+ nominal_current=nominal_current/1.1
345
+ #If the element is to be analyzed and has a nominal current (this discards vsources)
346
+ if (nominal_current>0 and self.is_to_be_analyzed(element)):
347
+ #Add to dictionaries and to vector
348
+ dict_loading[element] = currentmag/nominal_current
349
+ v_loading[idx]=currentmag/nominal_current
350
+ bus1to2=self.remove_terminal(buses[0])+'-->'+self.remove_terminal(buses[1])
351
+ dict_buses_element[bus1to2]=element
352
+ return dict_loading,v_loading,dict_buses_element
353
+
354
+ def get_all_losses(self):
355
+ """Computes losses in all circuits"""
356
+ #Get all element names
357
+ circuit_names = dss.Circuit.AllElementNames()
358
+ #Init variables
359
+ dict_losses = {}
360
+ v_losses = [0 for _ in range(len(circuit_names))]
361
+ total_losses=0
362
+ #For each element
363
+ for idx,element in enumerate(circuit_names):
364
+ #Set it as active element
365
+ dss.Circuit.SetActiveElement(element)
366
+ #Get buses names in element
367
+ buses = dss.CktElement.BusNames()
368
+ #only if it is a branch (two buses)
369
+ if (len(buses)>=2):
370
+ #Get nominal current
371
+ nominal_current = dss.CktElement.NormalAmps()
372
+ #If it has nominal current (this discards vsources)
373
+ if (nominal_current>0):
374
+ #Get hte losses
375
+ losses = dss.CktElement.Losses()
376
+ if len(losses) ==2:
377
+ lossesavg = (losses[0]) #[0] to take active losses
378
+ else:
379
+ print("Error - not correctly reading losses")
380
+ lossesavg=0
381
+ #Convert to kW (becase CktElement.Losses is the exeption that return losses in W)
382
+ lossesavg=lossesavg/1000
383
+ #If element is to be analized
384
+ if self.is_to_be_analyzed(element):
385
+ #Add to dictionary an to list
386
+ dict_losses[element] = lossesavg
387
+ v_losses[idx]=lossesavg
388
+ return dict_losses,total_losses
389
+
390
+ def get_total_subs_losses(self):
391
+ """Computes total substation losses"""
392
+ return dss.Circuit.SubstationLosses()[0] #Real part
393
+
394
+ def get_total_line_losses(self):
395
+ """Computes total power line losses"""
396
+ return dss.Circuit.LineLosses()[0] #Real part
397
+
398
+ def get_edges(self,v_dict_buses_ids):
399
+ """Gets the edges of the distribution system (to obtain the graph of the network)"""
400
+ #Get all element names
401
+ circuit_names = dss.Circuit.AllElementNames()
402
+ #Init variable
403
+ closed_edges=[]
404
+ open_edges=[]
405
+ #For all elements
406
+ for idx,element in enumerate(circuit_names):
407
+ #Set it as active element
408
+ dss.Circuit.SetActiveElement(element)
409
+ #Get the bus names
410
+ buses = dss.CktElement.BusNames()
411
+ #Only if it is a branch
412
+ if (len(buses)>=2): #There can be 3 buses in single-phase center-tap transformer, in this case the two last ones are equals (different terminals only) and we can take just the 2 first ones
413
+ #Avoid cases bus1=bus2 (after removing terminals)
414
+ if (self.remove_terminal(buses[0])!=self.remove_terminal(buses[1])):
415
+ #Identify if enabled #RNM-US specific (open loops are modelled with enabled=n)
416
+ b_enabled=dss.CktElement.Enabled()
417
+ #if Enabled (i.e. it it is closed)
418
+ if (b_enabled):
419
+ #Add to edges
420
+ #remove terminal from the bus name (everything to the right of point)
421
+ #closed_edges.append([(self.remove_terminal(buses[0]),self.remove_terminal(buses[1]))])
422
+ if (self.b_numeric_ids):
423
+ closed_edges.append((v_dict_buses_ids[self.remove_terminal(buses[0])],v_dict_buses_ids[self.remove_terminal(buses[1])]))
424
+ else:
425
+ closed_edges.append((self.remove_terminal(buses[0]),self.remove_terminal(buses[1])))
426
+ elif (not b_enabled): #if not Enabled (i.e. it it is open)
427
+ #open_edges.append([(self.remove_terminal(buses[0]),self.remove_terminal(buses[1]))])
428
+ if (self.b_numeric_ids):
429
+ open_edges.append((v_dict_buses_ids[self.remove_terminal(buses[0])],v_dict_buses_ids[self.remove_terminal(buses[1])]))
430
+ else:
431
+ open_edges.append((self.remove_terminal(buses[0]),self.remove_terminal(buses[1])))
432
+ return closed_edges,open_edges
433
+
434
+
435
+
436
+ def is_violation(self,value,v_range):
437
+ """Obtain number of violations (hours) of a bus"""
438
+ #Init to zero
439
+ num=0
440
+ #If out of range
441
+ if value<v_range['allowed_range'][0] or value>=v_range['allowed_range'][1]:
442
+ num=1 #Number of violations=1
443
+ else: #Else
444
+ num=0 #Number of violations=0
445
+ return num
446
+
447
+
448
+
449
+ def get_num_violations(self,v_value,v_range,name,dict_loads):
450
+ """"Obtain number of violations (hours) of a bus"""
451
+ #Init to zero
452
+ num_violations=0
453
+ #For each value
454
+ for idx2,value in enumerate(v_value):
455
+ #If no dict of loads, add 1 if there is a violation in that value (if it is outside of the allowed range)
456
+ if (dict_loads is None):
457
+ num_violations=num_violations+self.is_violation(value,v_range)
458
+ #If there is a dict of loads
459
+ elif (name in dict_loads):
460
+ if (dict_loads[name][idx2]>0): #only compute if there is load
461
+ #If there is a vioaltion, add the load (to compute the energy delivered with violations)
462
+ num_violations=num_violations+self.is_violation(value,v_range)*dict_loads[name][idx2]
463
+ return num_violations
464
+
465
+
466
+ def write_dict(self,subfolder,v_dict,v_range,type,component,v_dict_buses_ids,timestamps,v_hours):
467
+ """Writes the dictionary to a file"""
468
+ #Path and file name
469
+ output_file_full_path = self.folder + '/' + subfolder + '/' + type + '_' + component + '.csv'
470
+ # Write directly as a CSV file with headers on first line
471
+ with open(output_file_full_path, 'w') as fp:
472
+ #Header: ID, hours
473
+ for idx,name in enumerate(v_dict):
474
+ if (self.b_numeric_ids and not component=='Branches'):
475
+ fp.write(',/ '+'Date,'+','.join(str(value) for idx2,value in enumerate(timestamps)) + '\n')
476
+ fp.write('Num. ID,bus / '+'Hour,'+','.join(str(value) for value in v_hours) + '\n')
477
+ else:
478
+ fp.write('Date,'+','.join(str(value) for idx2,value in enumerate(timestamps)) + '\n')
479
+ fp.write('Hour,'+','.join(str(value) for value in v_hours) + '\n')
480
+ break
481
+ #Write matrix
482
+ for idx,name in enumerate(v_dict):
483
+ #Init list
484
+ truncated_values=[]
485
+ #For each one
486
+ for idx2,value in enumerate(v_dict[name]):
487
+ #if it is outsie of the allowed range
488
+ if not(v_range['allowed_range']) or value<v_range['allowed_range'][0] or value>=v_range['allowed_range'][1]:
489
+ truncated_values.append("{:.7f}".format(value)) #Add the value
490
+ else: #else
491
+ truncated_values.append("") #Fill with an empty variable
492
+ #Write to file
493
+ if v_dict_buses_ids is None or not self.b_numeric_ids:
494
+ fp.write(name+','+','.join(truncated_values)+'\n')
495
+ else:
496
+ fp.write(str(v_dict_buses_ids[name])+','+name+','+','.join(truncated_values)+'\n')
497
+
498
+
499
+ def write_id_dict(self,subfolder,type,v_dict_buses_ids):
500
+ """Writes the dictionary to a file"""
501
+ #Path and file name
502
+ output_file_full_path = self.folder + '/' + subfolder + '/' + type + '.csv'
503
+ # Write directly as a CSV file with headers on first line
504
+ with open(output_file_full_path, 'w') as fp:
505
+ #Header: ID, bus
506
+ fp.write('Num. ID,bus' + '\n')
507
+ for idx,name in enumerate(v_dict_buses_ids):
508
+ fp.write(str(v_dict_buses_ids[name])+','+name+'\n')
509
+
510
+
511
+ def solve_powerflow_iteratively(self,num_periods,start_index,end_index,location,v_range_voltage,v_range_loading,v_range_unbalance):
512
+ """Solves the power flow iteratively"""
513
+ #Get timestamps
514
+ timestamps,timestamps_datetime=self.get_time_stamp()
515
+ if start_index is None or start_index<0:
516
+ start_index=0
517
+ if end_index is None or end_index>len(timestamps):
518
+ end_index=len(timestamps)
519
+ v_hours_sim=range(start_index,end_index,1) #Hours to run OpenDSS
520
+ v_hours=range(start_index+1,end_index+1,1) #Hours for outputting
521
+ #Por flow solving mode (hourly)
522
+ self.dss_run_command("Clear")
523
+ self.dss_run_command('Redirect '+location)
524
+ self.dss_run_command("solve mode = snap")
525
+ self.dss_run_command("Set mode=yearly stepsize=1h number="+str(start_index+1))
526
+ #Additional initializations
527
+ #Init vectors
528
+ v_months=[[] for _ in range(num_periods)] #Months
529
+ v_voltage_yearly=[] #Yearly votlage
530
+ v_voltage_period=[[] for _ in range(num_periods)] #Montly voltage
531
+ v_unbalance_yearly=[] #Yearly unbalance
532
+ v_unbalance_period=[[] for _ in range(num_periods)] #Monthly unbalance
533
+ v_power_yearly=[] #Yearly power
534
+ v_power_period=[[] for _ in range(num_periods)] #Montly pwoer
535
+ v_loading_yearly=[] #Yearly loading
536
+ v_loading_period=[[] for _ in range(num_periods)] #Montly loading
537
+ v_subs_losses_yearly=[] #Yearly substation losses
538
+ v_line_losses_yearly=[] #Yearly power line losses
539
+ v_loads_kw_yearly=[] #Yearly kW of loads (for violing plots)
540
+ v_loads_kw_period=[[] for _ in range(num_periods)] #Montly kW of loads (for violing plots)
541
+ v_loads_kvar_yearly=[] #Yearly kVAr of loads (for violing plots)
542
+ v_loads_kvar_period=[[] for _ in range(num_periods)]#Montly kVAr of loads (for violing plots)
543
+ v_total_load_kw_yearly=[] #Yearly total kW of loads (for duration curve)
544
+ v_total_load_kvar_yearly=[] #Yearly total kVAr of loads (for duration curve)
545
+ v_dict_voltage={} #Dict of voltages
546
+ v_dict_unbalance={} #Dict of unbalances
547
+ v_dict_loading={} #Dict of loading
548
+ v_dict_losses={} #Dict of losses
549
+ v_dict_loads={} #Dict of loads
550
+ old_percentage_str="" #Variable for tracking progress
551
+ #Get buses ids
552
+ v_dict_buses_ids,v_dict_ids_buses=self.get_all_buses_ids()
553
+ #For each hour
554
+ for i in v_hours_sim:
555
+ #Solve power flow in that hour
556
+ self.dss_run_command("Solve")
557
+ #Build month vector
558
+ month=timestamps_datetime[i].month
559
+ if not month in v_months:
560
+ v_months[month-1]=month-1
561
+ #Get voltages
562
+ dict_voltage_i, v_voltage_i = self.get_all_voltage()
563
+ self.add_to_dictionary(v_dict_voltage,dict_voltage_i)
564
+ v_voltage_yearly.extend(v_voltage_i)
565
+ v_voltage_period=self.extract_period(v_voltage_i,v_voltage_period,i,end_index,num_periods,month)
566
+ #Get voltage unbalance
567
+ dict_unbalance_i, v_unbalance_i = self.get_all_unbalance()
568
+ self.add_to_dictionary(v_dict_unbalance,dict_unbalance_i)
569
+ v_unbalance_yearly.extend(v_unbalance_i)
570
+ v_unbalance_period=self.extract_period(v_unbalance_i,v_unbalance_period,i,end_index,num_periods,month)
571
+ #Get power
572
+ dict_power_i, v_power_i = self.get_all_power()
573
+ v_power_yearly.extend(v_power_i)
574
+ v_power_period=self.extract_period(v_power_i,v_power_period,i,end_index,num_periods,month)
575
+ #Get loading
576
+ dict_loading_i, v_loading_i,dict_buses_element = self.get_all_loading()
577
+ self.add_to_dictionary(v_dict_loading,dict_loading_i)
578
+ v_loading_yearly.extend(v_loading_i)
579
+ v_loading_period=self.extract_period(v_loading_i,v_loading_period,i,end_index,num_periods,month)
580
+ #Get dict losses
581
+ dict_losses_i, v_losses_i = self.get_all_losses()
582
+ self.add_to_dictionary(v_dict_losses,dict_losses_i)
583
+ #Get losses
584
+ subs_losses_i = self.get_total_subs_losses()
585
+ v_subs_losses_yearly.append(subs_losses_i)
586
+ line_losses_i = self.get_total_line_losses()
587
+ v_line_losses_yearly.append(line_losses_i)
588
+ #Get loads shapes
589
+ dict_loads_i, v_loads_kw_i, v_loads_kvar_i= self.get_all_buses_loads(i)
590
+ self.add_to_dictionary(v_dict_loads,dict_loads_i)
591
+ v_loads_kw_yearly.extend(v_loads_kw_i)
592
+ v_loads_kw_period=self.extract_period(v_loads_kw_i,v_loads_kw_period,i,end_index,num_periods,month)
593
+ v_loads_kvar_yearly.extend(v_loads_kvar_i)
594
+ v_loads_kvar_period=self.extract_period(v_loads_kvar_i,v_loads_kvar_period,i,end_index,num_periods,month)
595
+ #Get total peak load
596
+ v_total_load_kw_yearly.append(sum(v_loads_kw_i))
597
+ v_total_load_kvar_yearly.append(sum(v_loads_kvar_i))
598
+ #Print progress (disabled because the Ruby gem does not output the print messages)
599
+ #percentage_str="{:.0f}".format(100*i/end_index)+"%"
600
+ #if (percentage_str!=old_percentage_str):
601
+ # print(percentage_str)
602
+ #old_percentage_str=percentage_str
603
+ #Get loads shapes
604
+ dict_loads, v_loads_kw, v_loads_kvar= self.get_all_buses_loads(i)
605
+ #Get lines normal amps
606
+ dict_lines,v_lines_norm_amps=self.get_all_lines()
607
+ #Get transformers size
608
+ dict_transformers,v_transformers_kva=self.get_all_transformers()
609
+ #Get only the timestamps simulated
610
+ timestamps=timestamps[start_index:end_index]
611
+ return v_dict_buses_ids,v_dict_ids_buses,v_dict_voltage,v_voltage_yearly,v_voltage_period,v_power_yearly,v_power_period,v_dict_loading,v_loading_yearly,v_loading_period,v_dict_losses,v_subs_losses_yearly,v_line_losses_yearly,dict_buses_element,v_dict_loads,v_loads_kw_yearly,v_loads_kw_period,v_loads_kvar_yearly,v_loads_kvar_period,v_total_load_kw_yearly,v_total_load_kvar_yearly, v_loads_kw, v_loads_kvar,v_dict_unbalance,v_unbalance_yearly,v_unbalance_period,dict_lines,v_lines_norm_amps,dict_transformers,v_transformers_kva,timestamps,v_months,v_hours
612
+
613
+