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,528 @@
|
|
1
|
+
import opendssdirect as dss
|
2
|
+
import pandas as pd
|
3
|
+
#import matplotlib
|
4
|
+
import matplotlib.pyplot as plt
|
5
|
+
import numpy as np
|
6
|
+
import sys as sys
|
7
|
+
import math
|
8
|
+
import networkx as nx
|
9
|
+
import opendss_interface
|
10
|
+
import seaborn as sns
|
11
|
+
import plotly.graph_objects as go
|
12
|
+
from shapely.geometry import LineString
|
13
|
+
from shapely.geometry import Point
|
14
|
+
import geopandas as gpd
|
15
|
+
|
16
|
+
|
17
|
+
class Plot_Lib:
|
18
|
+
def __init__(self, folder,b_numeric_ids):
|
19
|
+
"""Initialices the folder variables"""
|
20
|
+
self.main_folder = folder
|
21
|
+
self.folder=folder+'/Validation'
|
22
|
+
self.b_numeric_ids=b_numeric_ids
|
23
|
+
|
24
|
+
def remove_terminal(self,bus):
|
25
|
+
"""Removes the terminal from the bus name"""
|
26
|
+
myopendss_io=opendss_interface.OpenDSS_Interface(self.folder,self.b_numeric_ids)
|
27
|
+
bus=myopendss_io.remove_terminal(bus)
|
28
|
+
return bus
|
29
|
+
|
30
|
+
|
31
|
+
def plot_hist(self,subfolder,type,v_value,v_value_period,v_range,num_periods,num_bins,v_months):
|
32
|
+
"""Plots an histogram"""
|
33
|
+
# Path and file name
|
34
|
+
output_file_full_path_fig = self.folder + '/' + subfolder + '/Figures/' + type + ' Histogram (p.u.).png'
|
35
|
+
output_file_full_path_csv = self.folder + '/' + subfolder + '/CSV/' + type + ' Histogram (p.u.).csv'
|
36
|
+
# New figure
|
37
|
+
# plt.figure
|
38
|
+
plt.clf()
|
39
|
+
# Activate figure grid
|
40
|
+
plt.grid(True)
|
41
|
+
# Evaluate number of valid periods
|
42
|
+
num_valid_periods=0
|
43
|
+
for j in v_months:
|
44
|
+
#If data in the month
|
45
|
+
if not j==[]:
|
46
|
+
num_valid_periods=num_valid_periods+1
|
47
|
+
# Init variables
|
48
|
+
v_legend=["" for _ in range(num_valid_periods+1)]
|
49
|
+
matrix=np.empty((num_bins,num_valid_periods+1)) #Matrix for writting to file (index+periods+yearly)
|
50
|
+
# Set xlim
|
51
|
+
if v_range['display_range']:
|
52
|
+
plt.xlim(v_range['display_range'])
|
53
|
+
# Incremental index for months with data
|
54
|
+
i=0
|
55
|
+
# For each month
|
56
|
+
for j in v_months:
|
57
|
+
#If data in the month
|
58
|
+
if not j==[]:
|
59
|
+
# Set the weight variable
|
60
|
+
v_weights = np.ones_like(v_value_period[j]) / len(v_value_period[j])
|
61
|
+
# Calculate the histogram of each month
|
62
|
+
counts, bins = np.histogram(v_value_period[j], range=v_range['display_range'], bins=num_bins, weights=v_weights)
|
63
|
+
# Update matrix and legend
|
64
|
+
# In subsequent iterations
|
65
|
+
matrix[:,i]=counts
|
66
|
+
v_legend[i]="M"+str(j+1)
|
67
|
+
# Plot the month
|
68
|
+
plt.plot(bins[:-1]+(bins[1]-bins[0])*0.5, counts)
|
69
|
+
# Increment index if data
|
70
|
+
i=i+1
|
71
|
+
# Set the weight variable
|
72
|
+
v_weights = np.ones_like(v_value) / len(v_value)
|
73
|
+
# Calculate the yearly histogram
|
74
|
+
counts, bins = np.histogram(v_value, range=v_range['display_range'], bins=num_bins, weights=v_weights)
|
75
|
+
# Update matrix and legend
|
76
|
+
matrix[:,i]=counts
|
77
|
+
v_legend[i]='Yearly'
|
78
|
+
# Plot histogram
|
79
|
+
plt.hist(bins[:-1], bins, weights=counts)
|
80
|
+
# Plot legend
|
81
|
+
#plt.legend(v_legend[1:num_periods+2:])
|
82
|
+
plt.legend(v_legend)
|
83
|
+
# Write line with the limits
|
84
|
+
for j in range(len(v_range['limits'])):
|
85
|
+
h = plt.axvline(v_range['limits'][j], color='r', linestyle='--')
|
86
|
+
# x,y lables
|
87
|
+
plt.xlabel(type)
|
88
|
+
plt.ylabel('Frequency (p.u.)')
|
89
|
+
# Save to file
|
90
|
+
plt.savefig(output_file_full_path_fig, dpi=300)
|
91
|
+
# Display
|
92
|
+
# plt.show()
|
93
|
+
# Save data to file
|
94
|
+
# Write directly as a CSV file with headers on first line
|
95
|
+
with open(output_file_full_path_csv, 'w') as fp:
|
96
|
+
fp.write(','.join(v_legend) + '\n')
|
97
|
+
np.savetxt(fp, matrix, '%.7f', ',')
|
98
|
+
|
99
|
+
def plot_violin(self,subfolder,type,v_value,v_range):
|
100
|
+
"""Make a figure with a violin plot"""
|
101
|
+
# Path and file name
|
102
|
+
output_file_full_path_fig = self.folder + '/' + subfolder + '/' + type + ' Violin Plot.png'
|
103
|
+
# New figure
|
104
|
+
# plt.figure
|
105
|
+
plt.clf()
|
106
|
+
# Write line with the limits
|
107
|
+
for j in range(len(v_range['limits'])):
|
108
|
+
h = plt.axhline(v_range['limits'][j], color='r', linestyle='--')
|
109
|
+
# Plot violtin
|
110
|
+
sns.violinplot(y=v_value, cut=0, color='orange')
|
111
|
+
# Strip plot (display points)
|
112
|
+
sns.stripplot(y=v_value, color='blue')
|
113
|
+
# y label
|
114
|
+
plt.ylabel(type)
|
115
|
+
# y limit
|
116
|
+
if v_range['display_range']:
|
117
|
+
plt.ylim(v_range['display_range'])
|
118
|
+
# Save figure to file
|
119
|
+
plt.savefig(output_file_full_path_fig, dpi=300)
|
120
|
+
# Display
|
121
|
+
# plt.show()
|
122
|
+
|
123
|
+
def plot_violin_two_vars(self,subfolder, type,v_value1,v_value2,v_range):
|
124
|
+
"""Make a figure with a violin plot of two variables (kW/kVAr)"""
|
125
|
+
# Path and file name
|
126
|
+
output_file_full_path_fig = self.folder + '/' + subfolder + '/' + type + ' Violin Plot.png'
|
127
|
+
# New figure
|
128
|
+
# plt.figure
|
129
|
+
plt.clf()
|
130
|
+
# Init variables
|
131
|
+
v_data=[]
|
132
|
+
v_type=[]
|
133
|
+
# Write line with the limits
|
134
|
+
for j in range(len(v_range['limits'])):
|
135
|
+
h = plt.axhline(v_range['limits'][j], color='r', linestyle='--')
|
136
|
+
# Extend lists for yearly var1
|
137
|
+
l_type=['kW' for _ in v_value1]
|
138
|
+
v_data.extend(v_value1)
|
139
|
+
v_type.extend(l_type)
|
140
|
+
# Extend lists for yearly var2
|
141
|
+
l_type=['kVAr' for _ in v_value2]
|
142
|
+
v_data.extend(v_value2)
|
143
|
+
v_type.extend(l_type)
|
144
|
+
# Display violin plot
|
145
|
+
sns.violinplot(y=v_data, hue=v_type, cut=0, split=True, palette = {'kW':'blue','kVAr':'orange'})
|
146
|
+
# Set y label
|
147
|
+
plt.ylabel(type)
|
148
|
+
# Set y limit
|
149
|
+
if v_range['display_range']:
|
150
|
+
plt.ylim(v_range['display_range'])
|
151
|
+
# Save figure to file
|
152
|
+
plt.savefig(output_file_full_path_fig, dpi=300)
|
153
|
+
# Display
|
154
|
+
# plt.show()
|
155
|
+
|
156
|
+
|
157
|
+
def plot_violin_monthly(self,subfolder, type,v_value,v_value_period,v_range,num_periods,v_months):
|
158
|
+
"""Make a violin plot with the monthly variation"""
|
159
|
+
# Path and file name
|
160
|
+
output_file_full_path_fig = self.folder + '/' + subfolder + '/' + type + ' Violin Plot.png'
|
161
|
+
# New figure
|
162
|
+
# plt.figure
|
163
|
+
plt.clf()
|
164
|
+
# Init variables
|
165
|
+
v_data=[]
|
166
|
+
v_month=[]
|
167
|
+
v_yearly=[]
|
168
|
+
# Write line with the limits
|
169
|
+
for j in range(len(v_range['limits'])):
|
170
|
+
h = plt.axhline(v_range['limits'][j], color='r', linestyle='--')
|
171
|
+
# Incremental index for valid months
|
172
|
+
i=0
|
173
|
+
# For each month
|
174
|
+
for j in v_months:
|
175
|
+
# If data in the month
|
176
|
+
if not j==[]:
|
177
|
+
# Extend lists for monthly
|
178
|
+
l_month=["M"+str(j+1) for _ in v_value_period[j]]
|
179
|
+
l_yearly=['Monthly' for _ in v_value_period[j]]
|
180
|
+
v_data.extend(v_value_period[j])
|
181
|
+
v_month.extend(l_month)
|
182
|
+
v_yearly.extend(l_yearly)
|
183
|
+
# Extend lists for yearly
|
184
|
+
l_month=["M"+str(j+1) for _ in v_value]
|
185
|
+
l_yearly=['Yearly' for _ in v_value]
|
186
|
+
v_data.extend(v_value)
|
187
|
+
v_month.extend(l_month)
|
188
|
+
v_yearly.extend(l_yearly)
|
189
|
+
#Increment auto-index if there is data
|
190
|
+
i=i+1
|
191
|
+
# Display violin plot
|
192
|
+
sns.violinplot(x=v_month, y=v_data, hue=v_yearly, cut=0, split=True)
|
193
|
+
# Set y label
|
194
|
+
plt.ylabel(type)
|
195
|
+
# Set y limit
|
196
|
+
if v_range['display_range']:
|
197
|
+
plt.ylim(v_range['display_range'])
|
198
|
+
# Save figure to file
|
199
|
+
plt.savefig(output_file_full_path_fig, dpi=300)
|
200
|
+
# Display
|
201
|
+
# plt.show()
|
202
|
+
|
203
|
+
def plot_violin_monthly_two_vars(self,subfolder, type,v_value1,v_value1_period,v_value2,v_value2_period,v_range,num_periods,v_months):
|
204
|
+
"""Make a violin plot for two variables (kW/kVAr) with the monthly variation"""
|
205
|
+
# Path and file name
|
206
|
+
output_file_full_path_fig = self.folder + '/' + subfolder + '/' + type + ' Violin Plot.png'
|
207
|
+
# New figure
|
208
|
+
# plt.figure
|
209
|
+
plt.clf()
|
210
|
+
# Init variables
|
211
|
+
v_data=[]
|
212
|
+
v_month=[]
|
213
|
+
v_type=[]
|
214
|
+
# Incremental index for valid months
|
215
|
+
i=0
|
216
|
+
for j in v_months:
|
217
|
+
#If there is data
|
218
|
+
if not j==[]:
|
219
|
+
# Extend lists for monthly var1
|
220
|
+
l_month=["M"+str(j+1) for _ in v_value1_period[j]]
|
221
|
+
l_type=['kW' for _ in v_value1_period[j]]
|
222
|
+
v_data.extend(v_value1_period[j])
|
223
|
+
v_month.extend(l_month)
|
224
|
+
v_type.extend(l_type)
|
225
|
+
# Extend lists for monthly var2
|
226
|
+
l_month=["M"+str(j+1) for _ in v_value2_period[j]]
|
227
|
+
l_type=['kVAr' for _ in v_value2_period[j]]
|
228
|
+
v_data.extend(v_value2_period[j])
|
229
|
+
v_month.extend(l_month)
|
230
|
+
v_type.extend(l_type)
|
231
|
+
#Increment auto-index if there is data
|
232
|
+
i=i+1
|
233
|
+
# Extend lists for yearly var1
|
234
|
+
l_month=['Yearly' for _ in v_value1]
|
235
|
+
l_type=['kW' for _ in v_value1]
|
236
|
+
v_data.extend(v_value1)
|
237
|
+
v_month.extend(l_month)
|
238
|
+
v_type.extend(l_type)
|
239
|
+
# Extend lists for yearly var2
|
240
|
+
l_month=['Yearly' for _ in v_value2]
|
241
|
+
l_type=['kVAr' for _ in v_value2]
|
242
|
+
v_data.extend(v_value2)
|
243
|
+
v_month.extend(l_month)
|
244
|
+
v_type.extend(l_type)
|
245
|
+
# Show violin plot
|
246
|
+
sns.violinplot(x=v_month, y=v_data, hue=v_type, cut=0, split=True)
|
247
|
+
# Show stripplot (points) (disabled because there are too many points)
|
248
|
+
# sns.stripplot(x=v_month, y=v_data, hue=v_type, color="k", alpha=0.8)
|
249
|
+
# Set x,y lables
|
250
|
+
plt.xlabel('Month')
|
251
|
+
plt.ylabel(type)
|
252
|
+
# Set y limit
|
253
|
+
if v_range['display_range']:
|
254
|
+
plt.ylim(v_range['display_range'])
|
255
|
+
# Save figure to file
|
256
|
+
plt.savefig(output_file_full_path_fig, dpi=300)
|
257
|
+
# Display
|
258
|
+
# plt.show()
|
259
|
+
|
260
|
+
def plot_duration_curve(self,subfolder, v1_yearly,v2_yearly,b_losses,v_hours):
|
261
|
+
"""Make a figure with the duration curve (yearly losses or load)"""
|
262
|
+
# New figure
|
263
|
+
# plt.figure
|
264
|
+
plt.clf()
|
265
|
+
# Display variable 1
|
266
|
+
plt.plot(v_hours,sorted(v1_yearly,reverse=True))
|
267
|
+
# Display variable 1
|
268
|
+
plt.plot(v_hours,sorted(v2_yearly,reverse=True))
|
269
|
+
# If displaying losses
|
270
|
+
if b_losses:
|
271
|
+
# Plot the added curve (lines + transformers)
|
272
|
+
plt.plot(v_hours,sorted(np.add(v1_yearly,v2_yearly),reverse=True))
|
273
|
+
# Set file path + name
|
274
|
+
output_file_full_path_fig = self.folder + '/' + subfolder + '/' + 'Losses' + '.png'
|
275
|
+
# Set legend
|
276
|
+
plt.legend(['Substation losses','Line losses','Total losses'])
|
277
|
+
# Set y label
|
278
|
+
plt.ylabel('Losses (kWh)')
|
279
|
+
else: # If displaying load
|
280
|
+
# Set file path + name
|
281
|
+
output_file_full_path_fig = self.folder + '/' + subfolder + '/' + 'Load' + '.png'
|
282
|
+
# Set legend
|
283
|
+
plt.legend(['kW','kVAr'])
|
284
|
+
# Set y label
|
285
|
+
plt.ylabel('Load')
|
286
|
+
# Set x label
|
287
|
+
plt.xlabel('Hour (h)')
|
288
|
+
# Save figure to file
|
289
|
+
plt.savefig(output_file_full_path_fig)
|
290
|
+
# Display
|
291
|
+
# plt.show()
|
292
|
+
|
293
|
+
|
294
|
+
def add_edges(self,graph,edges):
|
295
|
+
"""Add edges to the graph"""
|
296
|
+
# Populate it with the edges
|
297
|
+
for idx,element in enumerate(edges):
|
298
|
+
graph.add_edges_from([(element[0],element[1])])
|
299
|
+
return graph
|
300
|
+
|
301
|
+
def get_locations(self,graph,bus,locations=[],x_location_max_prev=0,parent=None,level=0,visited_buses=[]):
|
302
|
+
"""Get the locations of the buses in the graph"""
|
303
|
+
# Add to the list of visited buses
|
304
|
+
if visited_buses:
|
305
|
+
visited_buses.append(bus)
|
306
|
+
else:
|
307
|
+
visited_buses=[bus]
|
308
|
+
# Obtain the buses connected to this one
|
309
|
+
connected_buses = list(graph.neighbors(bus))
|
310
|
+
# Explore downstream levels
|
311
|
+
x_downstream_locations=[]
|
312
|
+
for downstream_bus in connected_buses:
|
313
|
+
# Remove the terminal from the bus name
|
314
|
+
downstream_bus=self.remove_terminal(downstream_bus)
|
315
|
+
# If the bus was already visited, remove from graph (if activated this would remove loops)
|
316
|
+
# if downstream_bus!=parent and downstream_bus in visited_buses and graph.has_edge(bus,downstream_bus) and b_remove_loops:
|
317
|
+
# Remove self loops (possible to happen because of terminals in buses)
|
318
|
+
if downstream_bus==bus:
|
319
|
+
graph.remove_edge(bus,downstream_bus)
|
320
|
+
else:
|
321
|
+
# Explore downstream the graph (recursive search)
|
322
|
+
if downstream_bus!=parent and not downstream_bus in visited_buses:
|
323
|
+
x_loc,locations,x_location_max_prev=self.get_locations(graph,downstream_bus,locations,x_location_max_prev,bus,level+1,visited_buses)
|
324
|
+
x_downstream_locations.append(x_loc)
|
325
|
+
# For the upper levels, it takes the average of the downstream buses
|
326
|
+
if x_downstream_locations:
|
327
|
+
loc=(sum(x_downstream_locations)/len(x_downstream_locations),-level);
|
328
|
+
else:
|
329
|
+
if x_location_max_prev:
|
330
|
+
# Pick up location from this level or the previous ones
|
331
|
+
# It is neccesary to sort it, to pick in the above for lev loop the x_next_location from the more downstream level
|
332
|
+
x_next_location=x_location_max_prev+1
|
333
|
+
# Assign location x, y
|
334
|
+
loc=(x_next_location,-level)
|
335
|
+
else:
|
336
|
+
# Default position for first bus
|
337
|
+
loc=(1,-level)
|
338
|
+
# Assign x location of this level
|
339
|
+
if x_location_max_prev<loc[0]:
|
340
|
+
x_location_max_prev=loc[0]
|
341
|
+
# update locations
|
342
|
+
if locations:
|
343
|
+
locations[bus]=loc
|
344
|
+
else:
|
345
|
+
locations={bus:loc}
|
346
|
+
# Return x location of this bus (all locations are provided in locations argument)
|
347
|
+
return loc[0],locations,x_location_max_prev
|
348
|
+
|
349
|
+
|
350
|
+
def get_dict_num_violations_v(self,graph,v_dict_voltage,v_range,v_dict_ids_buses):
|
351
|
+
"""It obtains number (hours) of voltage violations of each bus"""
|
352
|
+
myopendss_io=opendss_interface.OpenDSS_Interface(self.folder,self.b_numeric_ids)
|
353
|
+
# Init dict
|
354
|
+
dic_num_violations_v={}
|
355
|
+
# For each node
|
356
|
+
for node in graph:
|
357
|
+
# Get dict of violations
|
358
|
+
if (self.b_numeric_ids):
|
359
|
+
dic_num_violations_v[node]=myopendss_io.get_num_violations(v_dict_voltage[v_dict_ids_buses[node]],v_range,node,None)
|
360
|
+
else:
|
361
|
+
dic_num_violations_v[node]=myopendss_io.get_num_violations(v_dict_voltage[node],v_range,node,None)
|
362
|
+
return dic_num_violations_v
|
363
|
+
|
364
|
+
|
365
|
+
def get_dict_num_violations_l(self,graph,dict_buses_element,v_dict_loading,v_range_loading,v_dict_ids_buses):
|
366
|
+
"""It obtains number (hours) of violations of each branch"""
|
367
|
+
# Obtain number of violations (hours) of each branch
|
368
|
+
myopendss_io=opendss_interface.OpenDSS_Interface(self.folder,self.b_numeric_ids)
|
369
|
+
# Init dict
|
370
|
+
dic_num_violations_l={}
|
371
|
+
# For each granch
|
372
|
+
for edge in graph.edges():
|
373
|
+
# Obtain name from bus1 to bus2
|
374
|
+
if not self.b_numeric_ids:
|
375
|
+
bus1to2=self.remove_terminal(edge[0])+'-->'+self.remove_terminal(edge[1])
|
376
|
+
else:
|
377
|
+
bus1to2=self.remove_terminal(v_dict_ids_buses[edge[0]])+'-->'+self.remove_terminal(v_dict_ids_buses[edge[1]])
|
378
|
+
# Obtain name from bus2 to bus1
|
379
|
+
if not self.b_numeric_ids:
|
380
|
+
bus2to1=self.remove_terminal(edge[1])+'-->'+self.remove_terminal(edge[0])
|
381
|
+
else:
|
382
|
+
bus2to1=self.remove_terminal(v_dict_ids_buses[edge[1]])+'-->'+self.remove_terminal(v_dict_ids_buses[edge[0]])
|
383
|
+
# If bus1 to bus2 is the one that exists in the dictionary
|
384
|
+
if bus1to2 in dict_buses_element:
|
385
|
+
# Set element name
|
386
|
+
element=dict_buses_element[bus1to2]
|
387
|
+
# Evaluate number of violations
|
388
|
+
dic_num_violations_l[edge]=myopendss_io.get_num_violations(v_dict_loading[element],v_range_loading,element,None)
|
389
|
+
elif bus2to1 in dict_buses_element: #If bus2 to bus1 is the one that exists in the dictionary
|
390
|
+
# Set element name
|
391
|
+
element=dict_buses_element[bus2to1]
|
392
|
+
# Evaluate number of violations
|
393
|
+
dic_num_violations_l[edge]=myopendss_io.get_num_violations(v_dict_loading[element],v_range_loading,element,None)
|
394
|
+
else: # If the element does not exist in the dictionary, set number of violations to zero
|
395
|
+
dic_num_violations_l[edge]=0
|
396
|
+
return dic_num_violations_l
|
397
|
+
|
398
|
+
|
399
|
+
|
400
|
+
def plot_graph(self,subfolder,my_closed_edges,my_open_edges,v_dict_voltage,v_range,v_dict_loading,v_range_loading,dict_buses_element,v_dict_buses_ids,v_dict_ids_buses):
|
401
|
+
"""Plot a graph with the hierarchical representation of the network"""
|
402
|
+
# Path and file name
|
403
|
+
output_file_full_path_fig = self.folder + '/' + subfolder + '/' + 'Network Violations' + '.png'
|
404
|
+
# New figure
|
405
|
+
# plt.figure
|
406
|
+
plt.clf()
|
407
|
+
# Close the previous fig
|
408
|
+
plt.close()
|
409
|
+
# Create empty graph
|
410
|
+
mygraph=nx.Graph()
|
411
|
+
# Commented graph example
|
412
|
+
# mygraph.add_edges_from([(1,2), (1,3), (1,4), (1,5), (1,2), (2,6), (6,7), (7,1)])
|
413
|
+
# discard,locations=self.get_locations(mygraph,1)
|
414
|
+
# Populate the graph with the closed edges
|
415
|
+
mygraph=self.add_edges(mygraph,my_closed_edges)
|
416
|
+
mygraph_only_closed_edges=mygraph.copy()
|
417
|
+
# Populate the graph with the open edges
|
418
|
+
mygraph=self.add_edges(mygraph,my_open_edges)
|
419
|
+
# Get the locations of the buses
|
420
|
+
# WARNING: Locations are extracter from closed edges. This requires that there are no disconnected buses (e.g. two open switches in series)
|
421
|
+
if self.b_numeric_ids:
|
422
|
+
discard,locations,x_location_max_prev=self.get_locations(mygraph_only_closed_edges,str(0)) #function get_all_buses_ids sets slack bus to str(0)
|
423
|
+
else:
|
424
|
+
discard,locations,x_location_max_prev=self.get_locations(mygraph_only_closed_edges,'st_mat') #RNM-US specific (the slack bus of the distribution system is always "st_mat")
|
425
|
+
# Create colormap
|
426
|
+
cmap = plt.cm.get_cmap('jet')
|
427
|
+
# Take subset of colormap to avoid dark colors (with overlap with text)
|
428
|
+
cmap = cmap.from_list('trunc({n},{a:.3f},{b:.3f})'.format(n=cmap.name, a=0.15, b=0.75),cmap(np.linspace(0.25, 0.75, 120)))
|
429
|
+
# Init colormap variable
|
430
|
+
color_map_v=[]
|
431
|
+
# Get number of voltage valiations
|
432
|
+
dic_num_violations_v=self.get_dict_num_violations_v(mygraph,v_dict_voltage,v_range,v_dict_ids_buses)
|
433
|
+
# Obtain the maximum number of voltage violations in a bus
|
434
|
+
max_violations_v=max(dic_num_violations_v.values())
|
435
|
+
# if no violations, use only one color in the colormap
|
436
|
+
if max_violations_v==0:
|
437
|
+
cmap_v = cmap.from_list('trunc({n},{a:.3f},{b:.3f})'.format(n=cmap.name, a=0, b=0),cmap(np.linspace(0, 0, 2)))
|
438
|
+
else:
|
439
|
+
cmap_v = cmap
|
440
|
+
# Creater a colormap (color_map_v) o colour the buses according to their number of violations
|
441
|
+
for node in mygraph:
|
442
|
+
# If there are no violations, set all intensities to zero
|
443
|
+
if max_violations_v==0:
|
444
|
+
intensity=0
|
445
|
+
else:
|
446
|
+
intensity=dic_num_violations_v[node]/max_violations_v
|
447
|
+
# Add the intensity of the bus to the colormap
|
448
|
+
color_map_v.append(intensity)
|
449
|
+
# Init colormap variables
|
450
|
+
color_map_l_closed=[]
|
451
|
+
color_map_l_open=[]
|
452
|
+
# Get number of loading valiations
|
453
|
+
dic_num_violations_l=self.get_dict_num_violations_l(mygraph,dict_buses_element,v_dict_loading,v_range_loading,v_dict_ids_buses)
|
454
|
+
# Obtain the maximum number of loading violations in a branch
|
455
|
+
max_violations_l=max(dic_num_violations_l.values())
|
456
|
+
# if no violations, use only one color in the colormap
|
457
|
+
if max_violations_l==0:
|
458
|
+
cmap_l = cmap.from_list('trunc({n},{a:.3f},{b:.3f})'.format(n=cmap.name, a=0, b=0.01),cmap(np.linspace(0, 0.01, 2)))
|
459
|
+
else:
|
460
|
+
cmap_l = cmap
|
461
|
+
# Creater a colormap (color_map_l) o colour the closed branches according to their number of violations
|
462
|
+
for edge in mygraph_only_closed_edges.edges():
|
463
|
+
# If there are no violations, set all intensities to zero
|
464
|
+
if max_violations_l==0:
|
465
|
+
intensity=0
|
466
|
+
else:
|
467
|
+
intensity=dic_num_violations_l[edge]/max_violations_l
|
468
|
+
# Add the intensity of the branch to the colormap
|
469
|
+
color_map_l_closed.append(intensity)
|
470
|
+
# Creater a colormap for the open branches
|
471
|
+
for edge in mygraph.edges():
|
472
|
+
# Add the intensity of the branch to the colormap
|
473
|
+
if not edge in mygraph_only_closed_edges.edges():
|
474
|
+
color_map_l_open.append(0) #As it is a different graph, order may be different, but it does not matter because they are all zero
|
475
|
+
#Obtain min and max of x locations
|
476
|
+
max_x=0
|
477
|
+
max_y=0
|
478
|
+
for idx,name in enumerate(locations):
|
479
|
+
if (max_x<locations[name][0]):
|
480
|
+
max_x=locations[name][0]
|
481
|
+
if (max_y<-locations[name][1]):
|
482
|
+
max_y=-locations[name][1]
|
483
|
+
# Define the size of the figure
|
484
|
+
unitary_size=0.5
|
485
|
+
ratio=16/9
|
486
|
+
maximum=max(max_x,max_y)*unitary_size
|
487
|
+
if (maximum>40):
|
488
|
+
maximum=40
|
489
|
+
plt.figure(figsize=(maximum*ratio,maximum))
|
490
|
+
# Set transparency parameter
|
491
|
+
myalpha=0.6
|
492
|
+
# Draw the nodes
|
493
|
+
nodes = nx.draw_networkx_nodes(mygraph, pos=locations, node_color=color_map_v, cmap=cmap_v,alpha=myalpha)
|
494
|
+
# Draw the closed edges (solid lines)
|
495
|
+
edges=nx.draw_networkx_edges(mygraph_only_closed_edges,pos=locations, edge_color=color_map_l_closed,width=4,edge_cmap=cmap_l,alpha=myalpha)
|
496
|
+
# Show the buses names
|
497
|
+
nx.draw_networkx_labels(mygraph, pos=locations,font_size=8)
|
498
|
+
# Make the ticks, lables, and colorbar
|
499
|
+
num_ticks_v=5
|
500
|
+
ticks_v = np.linspace(0, 1, num_ticks_v)
|
501
|
+
labels_v = np.linspace(0, max_violations_v, num_ticks_v)
|
502
|
+
cbar=plt.colorbar(nodes,ticks=ticks_v)
|
503
|
+
cbar.ax.set_yticklabels(["{:4.2f}".format(i) for i in labels_v]) # add the labels
|
504
|
+
cbar.set_label("Voltage violations (h)", fontsize=10, y=0.5, rotation=90)
|
505
|
+
cbar.ax.yaxis.set_label_position('left')
|
506
|
+
num_ticks_l=5
|
507
|
+
ticks_l = np.linspace(0, 1, num_ticks_l)
|
508
|
+
labels_l = np.linspace(0, max_violations_l, num_ticks_l)
|
509
|
+
cbar=plt.colorbar(edges,ticks=ticks_l)
|
510
|
+
cbar.ax.set_yticklabels(["{:4.2f}".format(i) for i in labels_l]) # add the labels
|
511
|
+
cbar.set_label("Thermal limit violations (h)", fontsize=10, y=0.5, rotation=90)
|
512
|
+
cbar.ax.yaxis.set_label_position('left')
|
513
|
+
# Draw the open edges (dashed lines)
|
514
|
+
edges=nx.draw_networkx_edges(mygraph,edgelist=my_open_edges,pos=locations, edge_color=color_map_l_open,width=4,edge_cmap=cmap_l,style='--',alpha=myalpha)
|
515
|
+
# Don't display the axis in the figure
|
516
|
+
plt.axis('off')
|
517
|
+
# Maximize figure
|
518
|
+
wm = plt.get_current_fig_manager()
|
519
|
+
backend_name = plt.get_backend()
|
520
|
+
# this won't work on mac but should work on windows?
|
521
|
+
# macosx does not have a "window" attribute
|
522
|
+
if backend_name.lower() != 'macosx':
|
523
|
+
wm.window.state('zoomed')
|
524
|
+
|
525
|
+
# Save the figure to file
|
526
|
+
plt.savefig(output_file_full_path_fig, dpi=300)
|
527
|
+
# Display
|
528
|
+
plt.show()
|