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.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/CHANGELOG.md +5 -0
- data/Gemfile +6 -5
- data/README.md +15 -1
- data/Rakefile +31 -6
- data/lib/urbanopt/rnm/api_client.rb +8 -0
- data/lib/urbanopt/rnm/consumers.rb +14 -2
- data/lib/urbanopt/rnm/input_files.rb +18 -0
- data/lib/urbanopt/rnm/prosumers.rb +17 -1
- data/lib/urbanopt/rnm/validation/main_validation.py +135 -33
- data/lib/urbanopt/rnm/validation/opendss_interface.py +458 -79
- data/lib/urbanopt/rnm/validation/plot_lib.py +430 -124
- data/lib/urbanopt/rnm/validation/report.py +370 -0
- data/lib/urbanopt/rnm/validation.rb +8 -6
- data/lib/urbanopt/rnm/version.rb +1 -1
- data/requirements.txt +9 -0
- data/urbanopt-rnm-us-gem.gemspec +1 -1
- metadata +6 -4
@@ -1,188 +1,477 @@
|
|
1
1
|
import opendssdirect as dss
|
2
2
|
import pandas as pd
|
3
|
+
#import matplotlib
|
3
4
|
import matplotlib.pyplot as plt
|
4
5
|
import numpy as np
|
5
6
|
import sys as sys
|
6
7
|
import math
|
7
8
|
import networkx as nx
|
8
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
|
+
|
9
16
|
|
10
17
|
class Plot_Lib:
|
11
|
-
def __init__(self, folder):
|
12
|
-
|
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
|
13
23
|
|
14
24
|
def remove_terminal(self,bus):
|
15
|
-
|
25
|
+
"""Removes the terminal from the bus name"""
|
26
|
+
myopendss_io=opendss_interface.OpenDSS_Interface(self.folder,self.b_numeric_ids)
|
16
27
|
bus=myopendss_io.remove_terminal(bus)
|
17
28
|
return bus
|
18
29
|
|
19
30
|
|
20
|
-
def plot_hist(self,type,v_value,v_value_period,v_range,num_bins,
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
24
40
|
plt.grid(True)
|
25
|
-
|
26
|
-
|
27
|
-
for j in
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
plt.
|
37
|
-
|
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
|
38
72
|
v_weights = np.ones_like(v_value) / len(v_value)
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
42
79
|
plt.hist(bins[:-1], bins, weights=counts)
|
43
|
-
|
44
|
-
#
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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)
|
49
88
|
plt.ylabel('Frequency (p.u.)')
|
89
|
+
# Save to file
|
50
90
|
plt.savefig(output_file_full_path_fig, dpi=300)
|
51
|
-
|
52
|
-
#
|
91
|
+
# Display
|
92
|
+
# plt.show()
|
93
|
+
# Save data to file
|
53
94
|
# Write directly as a CSV file with headers on first line
|
54
95
|
with open(output_file_full_path_csv, 'w') as fp:
|
55
96
|
fp.write(','.join(v_legend) + '\n')
|
56
|
-
np.savetxt(fp, matrix, '
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
plt.
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
69
287
|
plt.xlabel('Hour (h)')
|
70
|
-
|
288
|
+
# Save figure to file
|
71
289
|
plt.savefig(output_file_full_path_fig)
|
72
|
-
|
290
|
+
# Display
|
291
|
+
# plt.show()
|
73
292
|
|
74
293
|
|
75
|
-
def
|
76
|
-
graph
|
294
|
+
def add_edges(self,graph,edges):
|
295
|
+
"""Add edges to the graph"""
|
296
|
+
# Populate it with the edges
|
77
297
|
for idx,element in enumerate(edges):
|
78
|
-
graph.add_edges_from(element)
|
298
|
+
graph.add_edges_from([(element[0],element[1])])
|
79
299
|
return graph
|
80
300
|
|
81
|
-
def get_locations(self,graph,bus,locations=[],
|
82
|
-
|
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
|
83
304
|
if visited_buses:
|
84
305
|
visited_buses.append(bus)
|
85
306
|
else:
|
86
307
|
visited_buses=[bus]
|
87
|
-
#Obtain the buses connected to this one
|
308
|
+
# Obtain the buses connected to this one
|
88
309
|
connected_buses = list(graph.neighbors(bus))
|
89
|
-
#Explore downstream levels
|
310
|
+
# Explore downstream levels
|
90
311
|
x_downstream_locations=[]
|
91
312
|
for downstream_bus in connected_buses:
|
92
|
-
#Remove the terminal from the bus
|
313
|
+
# Remove the terminal from the bus name
|
93
314
|
downstream_bus=self.remove_terminal(downstream_bus)
|
94
|
-
#If the bus was already visited, remove from graph (if activated this would remove loops)
|
95
|
-
#if downstream_bus!=parent and downstream_bus in visited_buses and graph.has_edge(bus,downstream_bus) and b_remove_loops:
|
96
|
-
#Remove self loops (possible to happen because of terminals in buses)
|
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)
|
97
318
|
if downstream_bus==bus:
|
98
319
|
graph.remove_edge(bus,downstream_bus)
|
99
320
|
else:
|
100
|
-
#Explore downstream the graph (recursive search)
|
321
|
+
# Explore downstream the graph (recursive search)
|
101
322
|
if downstream_bus!=parent and not downstream_bus in visited_buses:
|
102
|
-
x_loc,locations=self.get_locations(graph,downstream_bus,locations,
|
323
|
+
x_loc,locations,x_location_max_prev=self.get_locations(graph,downstream_bus,locations,x_location_max_prev,bus,level+1,visited_buses)
|
103
324
|
x_downstream_locations.append(x_loc)
|
104
|
-
#For the upper levels, it takes the average of the downstream buses
|
325
|
+
# For the upper levels, it takes the average of the downstream buses
|
105
326
|
if x_downstream_locations:
|
106
327
|
loc=(sum(x_downstream_locations)/len(x_downstream_locations),-level);
|
107
328
|
else:
|
108
|
-
if
|
109
|
-
#Pick up location from this level or the previous ones
|
110
|
-
#It is neccesary to sort it, to pick in the above for lev loop the x_next_location from the more downstream level
|
111
|
-
|
112
|
-
|
113
|
-
x_next_location=x_locations_levels[lev]+1
|
114
|
-
#Assign location x, y
|
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
|
115
334
|
loc=(x_next_location,-level)
|
116
335
|
else:
|
117
|
-
#Default position for first bus
|
336
|
+
# Default position for first bus
|
118
337
|
loc=(1,-level)
|
119
|
-
#Assign x location of this level
|
120
|
-
|
121
|
-
|
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
|
122
342
|
if locations:
|
123
343
|
locations[bus]=loc
|
124
344
|
else:
|
125
345
|
locations={bus:loc}
|
126
|
-
#Return x location of this bus (all locations are provided in locations argument)
|
127
|
-
return loc[0],locations
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
#output_file_full_path_fig = folder + '/' + type + '.png'
|
135
|
-
#Example
|
136
|
-
graph=nx.Graph()
|
137
|
-
#graph.add_edges_from([(1,2), (1,3), (1,4), (1,5), (1,2), (2,6), (6,7), (7,1)])
|
138
|
-
#discard,locations=self.get_locations(graph,1)
|
139
|
-
graph=self.get_graph(edges)
|
140
|
-
discard,locations=self.get_locations(graph,'st_mat')
|
141
|
-
#Obtain number of violations of each node
|
142
|
-
cmap = plt.cm.get_cmap('jet')
|
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
|
143
354
|
dic_num_violations_v={}
|
355
|
+
# For each node
|
144
356
|
for node in graph:
|
145
|
-
#
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
151
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
|
152
434
|
max_violations_v=max(dic_num_violations_v.values())
|
153
|
-
|
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
|
154
443
|
if max_violations_v==0:
|
155
444
|
intensity=0
|
156
445
|
else:
|
157
446
|
intensity=dic_num_violations_v[node]/max_violations_v
|
158
|
-
#
|
447
|
+
# Add the intensity of the bus to the colormap
|
159
448
|
color_map_v.append(intensity)
|
160
|
-
#
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
#bus2to1=self.remove_terminal(edge[1])+'-->'+self.remove_terminal(edge[0])
|
167
|
-
bus1to2=self.remove_terminal(edge[0])+'-->'+self.remove_terminal(edge[1])
|
168
|
-
bus2to1=self.remove_terminal(edge[1])+'-->'+self.remove_terminal(edge[0])
|
169
|
-
if bus1to2 in dict_buses_element:
|
170
|
-
element=dict_buses_element[bus1to2]
|
171
|
-
else:
|
172
|
-
element=dict_buses_element[bus2to1]
|
173
|
-
for idx2,value in enumerate(v_dict_loading[element]):
|
174
|
-
if value<v_range_loading[0] or value>=v_range_loading[1]:
|
175
|
-
dic_num_violations_l[edge]=dic_num_violations_l[edge]+1
|
176
|
-
#Make colormap
|
177
|
-
color_map_l=[]
|
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
|
178
455
|
max_violations_l=max(dic_num_violations_l.values())
|
179
|
-
|
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
|
180
464
|
if max_violations_l==0:
|
181
465
|
intensity=0
|
182
466
|
else:
|
183
467
|
intensity=dic_num_violations_l[edge]/max_violations_l
|
184
|
-
#
|
185
|
-
|
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
|
186
475
|
#Obtain min and max of x locations
|
187
476
|
max_x=0
|
188
477
|
max_y=0
|
@@ -191,14 +480,22 @@ class Plot_Lib:
|
|
191
480
|
max_x=locations[name][0]
|
192
481
|
if (max_y<-locations[name][1]):
|
193
482
|
max_y=-locations[name][1]
|
194
|
-
#
|
483
|
+
# Define the size of the figure
|
195
484
|
unitary_size=0.5
|
196
485
|
ratio=16/9
|
197
486
|
maximum=max(max_x,max_y)*unitary_size
|
487
|
+
if (maximum>40):
|
488
|
+
maximum=40
|
198
489
|
plt.figure(figsize=(maximum*ratio,maximum))
|
199
|
-
|
200
|
-
|
201
|
-
|
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
|
202
499
|
num_ticks_v=5
|
203
500
|
ticks_v = np.linspace(0, 1, num_ticks_v)
|
204
501
|
labels_v = np.linspace(0, max_violations_v, num_ticks_v)
|
@@ -213,10 +510,19 @@ class Plot_Lib:
|
|
213
510
|
cbar.ax.set_yticklabels(["{:4.2f}".format(i) for i in labels_l]) # add the labels
|
214
511
|
cbar.set_label("Thermal limit violations (h)", fontsize=10, y=0.5, rotation=90)
|
215
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
|
216
516
|
plt.axis('off')
|
517
|
+
# Maximize figure
|
217
518
|
wm = plt.get_current_fig_manager()
|
218
|
-
|
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
|
219
526
|
plt.savefig(output_file_full_path_fig, dpi=300)
|
527
|
+
# Display
|
220
528
|
plt.show()
|
221
|
-
|
222
|
-
|