urbanopt-rnm-us 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -61,9 +61,9 @@ module URBANopt
61
61
 
62
62
  @use_localhost = use_localhost
63
63
  if @use_localhost
64
- @base_api = 'http://0.0.0.0:8080/api/v1/'
64
+ @base_api = 'http://0.0.0.0:8080/api/v2/'
65
65
  else
66
- @base_api = 'https://rnm.urbanopt.net/api/v1/'
66
+ @base_api = 'https://rnm.urbanopt.net/api/v2/'
67
67
  end
68
68
 
69
69
  puts "Running RNM-US at #{@base_api}"
@@ -144,10 +144,10 @@ module URBANopt
144
144
  case peak_apparent_power
145
145
  when 0..@lv_limit[:single_phase] # set by the catalog limits
146
146
  phases = 1
147
- voltage_default = 0.416
147
+ voltage_default = 0.12
148
148
  when @lv_limit[:single_phase]..@lv_limit[:three_phase] # defined from the catalog (from the wires)
149
149
  phases = 3
150
- voltage_default = 0.416
150
+ voltage_default = 0.48
151
151
  # MV and 3 phases untill 16 MVA, defined by the SMART-DS project
152
152
  when @lv_limit[:three_phase]..16000
153
153
  phases = 3
@@ -201,6 +201,7 @@ module URBANopt
201
201
  average_peak = mixed_use_av_peak # average peak per mixed use considering the building types which are in this building
202
202
  area = area_mixed_use
203
203
  end
204
+
204
205
  nodes_per_bldg = (average_peak / (@lv_limit[:three_phase] * @power_factor * conservative_factor)).to_f.ceil # computing number of nodes per building
205
206
  if nodes_per_bldg > @max_num_lv_nodes # to define this as an input in the geojson file
206
207
  nodes_per_bldg = 1
@@ -87,7 +87,7 @@ module URBANopt
87
87
  # lines with the highest capacity
88
88
  catalog['LINES'][1].each do |key, v|
89
89
  (0..catalog['LINES'][1][key].length - 1).each do |ii|
90
- if catalog['LINES'][1][key][ii]['Voltage(kV)'] == '0.416'
90
+ if Float(catalog['LINES'][1][key][ii]['Voltage(kV)']) < 1
91
91
  if catalog['LINES'][1][key][ii]['Line geometry'][0]['phase'] != 'N'
92
92
  wire = catalog['LINES'][1][key][ii]['Line geometry'][0]['wire']
93
93
  else
@@ -201,10 +201,10 @@ module URBANopt
201
201
  case peak_apparent_power
202
202
  when -10000..@lv_limit[:single_phase] # set by the catalog limits
203
203
  phases = 1
204
- voltage_default = 0.416
204
+ voltage_default = 0.12
205
205
  when @lv_limit[:single_phase]..@lv_limit[:three_phase] # defined from the catalog (from the wires)
206
206
  phases = 3
207
- voltage_default = 0.416
207
+ voltage_default = 0.48
208
208
  # MV and 3 phases untill 16 MVA defined by SMART-DS project
209
209
  when @lv_limit[:three_phase]..16000
210
210
  phases = 3
@@ -171,6 +171,16 @@ module URBANopt
171
171
  @rnm_pp = URBANopt::RNM::PostProcessor.new(@results, @run_dir, @feature_file)
172
172
  @rnm_pp.post_process
173
173
  end
174
+
175
+
176
+ ##
177
+ # Run OpenDSS validation
178
+ ##
179
+ def run_validation
180
+ # generate RNM-US input files
181
+ validation = URBANopt::RNM::Validation.new(@run_dir)
182
+ validation.run_validation
183
+ end
174
184
  end
175
185
  end
176
186
  end
@@ -52,11 +52,7 @@ module URBANopt
52
52
  hash[:resistance] = trafo['Low-voltage-side short-circuit resistance (ohms)'].to_f.round(2)
53
53
  hash[:reactance] = trafo['Reactance (p.u. transf)'].to_f.round(2)
54
54
  hash[:phases] = trafo['Nphases']
55
- if trafo['Nphases'] == '3'
56
- hash[:is_center_tap] = false
57
- else
58
- hash[:is_center_tap] = true
59
- end
55
+ hash[:Centertap] = trafo['Centertap']
60
56
  hash[:high_voltage] = trafo['Primary Voltage (kV)']
61
57
  hash[:low_voltage] = trafo['Secondary Voltage (kV)']
62
58
  hash[:connection] = trafo['connection']
@@ -0,0 +1,51 @@
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
+ import opendss_interface
9
+ import plot_lib
10
+
11
+ class Validation:
12
+ def __init__(self, folder):
13
+ self.folder = folder
14
+
15
+ def main_validation(self):
16
+ master_file_full_path = folder + '/dss_files/' + 'Master.dss'
17
+ start_index = 0
18
+ num_periods=12
19
+ end_index = 8760
20
+ v_range_voltage=(0.9, 1.1)
21
+ v_limits_voltage=[0.95,1.05]
22
+ v_range_loading=(0,1.3)
23
+ v_limits_loading=[1]
24
+ v_range_show_all=(0,0)
25
+
26
+ #For tests
27
+ #end_index = 24
28
+ #v_range_voltage=(0.975, 1.025)
29
+
30
+ myopendss_io=opendss_interface.OpenDSS_Interface(folder)
31
+ 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=myopendss_io.solve_powerflow_iteratively(num_periods,start_index,end_index,master_file_full_path,v_range_voltage,v_range_loading)
32
+ myopendss_io.write_dict(v_dict_voltage,v_range_show_all,'Voltages (p.u.)','Buses')
33
+ myopendss_io.write_dict(v_dict_voltage,v_range_voltage,'Voltage Violations (p.u.)','Buses')
34
+ myopendss_io.write_dict(v_dict_loading,v_range_show_all,'Loading (p.u.)','Branches')
35
+ myopendss_io.write_dict(v_dict_loading,v_range_loading,'Loading Violations (p.u.)','Branches')
36
+ myopendss_io.write_dict(v_dict_losses,v_range_show_all,'Losses','Branches')
37
+ edges=myopendss_io.get_edges()
38
+ myplot_lib=plot_lib.Plot_Lib(folder)
39
+ myplot_lib.plot_hist('Voltage',v_voltage_yearly,v_voltage_period,v_range_voltage,40,num_periods,v_limits_voltage)
40
+ myplot_lib.plot_hist('Loading',v_loading_yearly,v_loading_period,v_range_loading,80,num_periods,v_limits_loading)
41
+ myplot_lib.plot_losses(v_subs_losses_yearly,v_line_losses_yearly)
42
+ myplot_lib.plot_graph(edges,v_dict_voltage,v_range_voltage,v_dict_loading,v_range_loading,dict_buses_element)
43
+
44
+
45
+
46
+ if __name__ == "__main__":
47
+ #Example to run it in command window
48
+ #python main_validation.py files
49
+ folder = sys.argv[1]
50
+ valid=Validation(folder)
51
+ valid.main_validation()
@@ -0,0 +1,234 @@
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
+
9
+ class OpenDSS_Interface:
10
+ def __init__(self, folder):
11
+ self.folder = folder
12
+
13
+ def remove_terminal(self,bus):
14
+ if isinstance(bus,str):
15
+ return bus.split('.')[0]
16
+ else:
17
+ return bus
18
+
19
+
20
+ def extract_period(self,v_value_i,v_value_period,i,end_index,num_periods):
21
+ for j in range(num_periods):
22
+ if (i<=end_index*j/num_periods):
23
+ v_value_period[j].extend(v_value_i)
24
+ break
25
+ return v_value_period
26
+
27
+ def add_to_dictionary(self,v_dict_voltage,dict_voltage_i):
28
+ for idx,name in enumerate(dict_voltage_i):
29
+ if name in v_dict_voltage: #if not empty
30
+ v_dict_voltage[name].append(dict_voltage_i[name])
31
+ else:
32
+ v_dict_voltage[name]=[dict_voltage_i[name]]
33
+
34
+ def dss_run_command(self,command):
35
+ output=dss.run_command(command)
36
+ if (len(output)>0):
37
+ print(output)
38
+
39
+
40
+ def get_all_voltage(self):
41
+ """Computes over and under voltages for all buses"""
42
+ bus_names = dss.Circuit.AllBusNames()
43
+ dict_voltage = {}
44
+ v_voltage = [0 for _ in range(len(bus_names))]
45
+ for idx,b in enumerate(bus_names):
46
+ dss.Circuit.SetActiveBus(b)
47
+ vang = dss.Bus.puVmagAngle()
48
+ if len(vang[::2]) > 0:
49
+ vmag = sum(vang[::2])/(len(vang)/2)
50
+ else:
51
+ vmag = 0
52
+ dict_voltage[b] = vmag
53
+ v_voltage[idx]=vmag
54
+
55
+ return dict_voltage,v_voltage
56
+
57
+ def get_all_power(self):
58
+ """Computes power in all circuits"""
59
+ circuit_names = dss.Circuit.AllElementNames()
60
+ dict_power = {}
61
+ v_power = [0 for _ in range(len(circuit_names))]
62
+ for idx,b in enumerate(circuit_names):
63
+ dss.Circuit.SetActiveElement(b)
64
+ power = dss.CktElement.Powers()
65
+ if len(power[::2]) > 0:
66
+ poweravg = sum(power[::2])/(len(power)/2)
67
+ else:
68
+ poweravg = 0
69
+ dict_power[b] = poweravg
70
+ v_power[idx]=poweravg
71
+
72
+ return dict_power,v_power
73
+
74
+
75
+ def get_all_loading(self):
76
+ """Computes loading in all circuits"""
77
+ circuit_names = dss.Circuit.AllElementNames()
78
+ dict_loading = {}
79
+ dict_buses_element={} #Associate the element to the buses (this has the inconvenient that only associates one element to each pair of buses)
80
+ v_loading = [0 for _ in range(len(circuit_names))]
81
+ for idx,element in enumerate(circuit_names):
82
+ dss.Circuit.SetActiveElement(element)
83
+ #only if it is a branch (two buses)
84
+ buses = dss.CktElement.BusNames()
85
+ if (len(buses)>=2):
86
+ current = dss.CktElement.CurrentsMagAng()
87
+ num_terminals=dss.CktElement.NumTerminals()
88
+ if len(current[::2]) > 0:
89
+ #currentmag = sum(current[::2])/len(current[::2])
90
+ #Take only the average of 1 terminal (discarding phases so every 2)
91
+ lenc=len(current[::2])
92
+ stop=round(2*lenc/num_terminals)
93
+ #print(current[:stop:2])
94
+ currentmag = sum(current[:stop:2])/lenc
95
+ else:
96
+ currentmag = 0
97
+ currentmag = current[0]
98
+ nominal_current = dss.CktElement.NormalAmps()
99
+ #Transformers have applied a 1.1 factor in the calculation of NormalAmps
100
+ #See library that OpenDSSdirect uses in https://github.com/dss-extensions/dss_capi/blob/master/src/PDElements/Transformer.pas
101
+ #in particular line code AmpRatings[i] := 1.1 * kVARatings[i] / Fnphases / Vfactor;
102
+ if (element.startswith("Transformer")):
103
+ nominal_current=nominal_current/1.1
104
+ if (nominal_current>0):
105
+ dict_loading[element] = currentmag/nominal_current
106
+ v_loading[idx]=currentmag/nominal_current
107
+ bus1to2=self.remove_terminal(buses[0])+'-->'+self.remove_terminal(buses[1])
108
+ dict_buses_element[bus1to2]=element
109
+
110
+ return dict_loading,v_loading,dict_buses_element
111
+
112
+ def get_all_losses(self):
113
+ """Computes losses in all circuits"""
114
+ circuit_names = dss.Circuit.AllElementNames()
115
+ dict_losses = {}
116
+ v_losses = [0 for _ in range(len(circuit_names))]
117
+ total_losses=0
118
+ for idx,element in enumerate(circuit_names):
119
+ dss.Circuit.SetActiveElement(element)
120
+ #only if it is a branch (two buses)
121
+ buses = dss.CktElement.BusNames()
122
+ if (len(buses)>=2):
123
+ #next if check discards vsources
124
+ nominal_current = dss.CktElement.NormalAmps()
125
+ if (nominal_current>0):
126
+ losses = dss.CktElement.Losses()
127
+ if len(losses) ==2:
128
+ lossesavg = (losses[0]) #Verify this is correct, if not abs() they range from + to -, [0] to take active losses
129
+ else:
130
+ print("error - not correctly reading losses")
131
+ lossesavg=0
132
+ #Convert to kW. This function is the exeption that return losses in W
133
+ lossesavg=lossesavg/1000
134
+ dict_losses[element] = lossesavg
135
+ v_losses[idx]=lossesavg
136
+
137
+ return dict_losses,total_losses
138
+
139
+ def get_total_subs_losses(self):
140
+ """Computes total substation losses"""
141
+ return dss.Circuit.SubstationLosses()[0] #Real part
142
+
143
+ def get_total_line_losses(self):
144
+ """Computes total line losses"""
145
+ return dss.Circuit.LineLosses()[0] #Real part
146
+
147
+ def get_edges(self):
148
+ edges=[]
149
+ circuit_names = dss.Circuit.AllElementNames()
150
+ for idx,element in enumerate(circuit_names):
151
+ dss.Circuit.SetActiveElement(element)
152
+ buses = dss.CktElement.BusNames()
153
+ #Only if it is a branch
154
+ 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
155
+ #remove terminal from the bus name (everything to the right of point)
156
+ edges.append([(self.remove_terminal(buses[0]),self.remove_terminal(buses[1]))])
157
+ return edges
158
+
159
+ def write_dict(self,v_dict,v_range,type,component):
160
+ output_file_full_path = self.folder + '/' + type + '_' + component + '.csv'
161
+ # Write directly as a CSV file with headers on first line
162
+ with open(output_file_full_path, 'w') as fp:
163
+ #Header: ID, hours (consider adding day, month in future)
164
+ for idx,name in enumerate(v_dict):
165
+ fp.write('Hour,'+','.join(str(idx2) for idx2,value in enumerate(v_dict[name])) + '\n')
166
+ break
167
+ #Write matrix
168
+ for idx,name in enumerate(v_dict):
169
+ #Truncate list to limits
170
+ truncated_values=[]
171
+ for idx2,value in enumerate(v_dict[name]):
172
+ if value<v_range[0] or value>=v_range[1]:
173
+ truncated_values.append(str(value))
174
+ else:
175
+ truncated_values.append("")
176
+ fp.write(name+','+','.join(truncated_values)+'\n')
177
+ #truncated_values=[i for i, lower, upper in zip(v_dict_voltage[name], [v_range_voltage[1]]*len(v_dict_voltage[name]), [v_range_voltage[1]]*len(v_dict_voltage[name])) if i <lower or i>upper]
178
+ #fp.write(name+','+','.join(map(str,v_dict_voltage[name]))+'\n')
179
+
180
+
181
+ def solve_powerflow_iteratively(self,num_periods,start_index,end_index,location,v_range_voltage,v_range_loading):
182
+ #Por flow solving mode
183
+ self.dss_run_command("Clear")
184
+ self.dss_run_command('Redirect '+location)
185
+ self.dss_run_command("solve mode = snap")
186
+ self.dss_run_command("Set mode=yearly stepsize=1h number=1")
187
+ #Init vectors
188
+ v_voltage_yearly=[]
189
+ v_voltage_period=[[] for _ in range(num_periods)]
190
+ v_power_yearly=[]
191
+ v_power_period=[[] for _ in range(num_periods)]
192
+ v_loading_yearly=[]
193
+ v_loading_period=[[] for _ in range(num_periods)]
194
+ v_subs_losses_yearly=[]
195
+ v_line_losses_yearly=[]
196
+ v_dict_voltage={}
197
+ v_dict_loading={}
198
+ v_dict_losses={}
199
+ #Additional initializations
200
+ my_range=range(start_index,end_index,1)
201
+ old_percentage_str="" #Variable for tracking progress
202
+ for i in my_range:
203
+ #Solve power flow
204
+ self.dss_run_command("Solve")
205
+ #Get voltages
206
+ dict_voltage_i, v_voltage_i = self.get_all_voltage()
207
+ self.add_to_dictionary(v_dict_voltage,dict_voltage_i)
208
+ v_voltage_yearly.extend(v_voltage_i)
209
+ self.extract_period(v_voltage_i,v_voltage_period,i,end_index,num_periods)
210
+ #Get power
211
+ dict_power_i, v_power_i = self.get_all_power()
212
+ v_power_yearly.extend(v_power_i)
213
+ v_power_period=self.extract_period(v_power_i,v_power_period,i,end_index,num_periods)
214
+ #Get loading
215
+ dict_loading_i, v_loading_i,dict_buses_element = self.get_all_loading()
216
+ self.add_to_dictionary(v_dict_loading,dict_loading_i)
217
+ v_loading_yearly.extend(v_loading_i)
218
+ v_loading_period=self.extract_period(v_loading_i,v_loading_period,i,end_index,num_periods)
219
+ #Get dict losses
220
+ dict_losses_i, v_losses_i = self.get_all_losses()
221
+ self.add_to_dictionary(v_dict_losses,dict_losses_i)
222
+ #Get losses
223
+ subs_losses_i = self.get_total_subs_losses()
224
+ v_subs_losses_yearly.append(subs_losses_i)
225
+ line_losses_i = self.get_total_line_losses()
226
+ v_line_losses_yearly.append(line_losses_i)
227
+ #Print progress
228
+ #percentage_str="{:.0f}".format(100*i/end_index)+"%"
229
+ #if (percentage_str!=old_percentage_str):
230
+ # print(percentage_str)
231
+ #old_percentage_str=percentage_str
232
+ return 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
233
+
234
+
@@ -0,0 +1,222 @@
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
+ import opendss_interface
9
+
10
+ class Plot_Lib:
11
+ def __init__(self, folder):
12
+ self.folder = folder
13
+
14
+ def remove_terminal(self,bus):
15
+ myopendss_io=opendss_interface.OpenDSS_Interface(self.folder)
16
+ bus=myopendss_io.remove_terminal(bus)
17
+ return bus
18
+
19
+
20
+ def plot_hist(self,type,v_value,v_value_period,v_range,num_bins,num_periods,v_limits):
21
+ output_file_full_path_fig = self.folder + '/' + type + ' Histogram (p.u.).png'
22
+ output_file_full_path_csv = self.folder + '/' + type + ' Histogram (p.u.).csv'
23
+ plt.figure
24
+ plt.grid(True)
25
+ v_legend=["" for _ in range(num_periods+2)]
26
+ matrix=np.empty((num_bins,num_periods+2)) #Matrix for writting to file (index+periods+yearly)
27
+ for j in range(num_periods):
28
+ plt.xlim(v_range)
29
+ v_weights = np.ones_like(v_value_period[j]) / len(v_value_period[j])
30
+ counts, bins = np.histogram(v_value_period[j], range=v_range, bins=num_bins, weights=v_weights)
31
+ if j==0:
32
+ v_legend[0]=type
33
+ matrix[:,0]=bins[:num_bins:]
34
+ matrix[:,j+1]=counts
35
+ v_legend[j+1]= "M"+str(j+1)
36
+ plt.plot(bins[:-1]+(bins[1]-bins[0])*0.5, counts)
37
+
38
+ v_weights = np.ones_like(v_value) / len(v_value)
39
+ counts, bins = np.histogram(v_value, range=v_range, bins=num_bins, weights=v_weights)
40
+ matrix[:,num_periods+1]=counts
41
+ v_legend[num_periods+1]='Yearly'
42
+ plt.hist(bins[:-1], bins, weights=counts)
43
+ plt.legend(v_legend[1:num_periods+2:])
44
+ #Write line with the limits
45
+ for j in range(len(v_limits)):
46
+ h = plt.axvline(v_limits[j], color='r', linestyle='--')
47
+ #plt.text(0.76, 120000, '1,596 buses out of limits ', fontsize = 10)
48
+ plt.xlabel(type+' (p.u.)')
49
+ plt.ylabel('Frequency (p.u.)')
50
+ plt.savefig(output_file_full_path_fig, dpi=300)
51
+ plt.show()
52
+ #Save to file
53
+ # Write directly as a CSV file with headers on first line
54
+ with open(output_file_full_path_csv, 'w') as fp:
55
+ fp.write(','.join(v_legend) + '\n')
56
+ np.savetxt(fp, matrix, '%s', ',')
57
+ #Deprecated with dataframes
58
+ #pd_values = pd.DataFrame(matrix,index=bins)
59
+ #pd_values.to_csv(output_file_full_path)
60
+
61
+
62
+ def plot_losses(self,v_subs_losses_yearly,v_line_losses_yearly):
63
+ output_file_full_path_fig = self.folder + '/' + 'Losses' + '.png'
64
+ plt.figure
65
+ plt.plot(sorted(v_subs_losses_yearly,reverse=True))
66
+ plt.plot(sorted(v_line_losses_yearly,reverse=True))
67
+ plt.plot(sorted(np.add(v_subs_losses_yearly,v_line_losses_yearly),reverse=True))
68
+ plt.legend(['Substation losses','Line losses','Total losses'])
69
+ plt.xlabel('Hour (h)')
70
+ plt.ylabel('Losses (kWh)')
71
+ plt.savefig(output_file_full_path_fig)
72
+ plt.show()
73
+
74
+
75
+ def get_graph(self,edges):
76
+ graph=nx.Graph()
77
+ for idx,element in enumerate(edges):
78
+ graph.add_edges_from(element)
79
+ return graph
80
+
81
+ def get_locations(self,graph,bus,locations=[],x_locations_levels={},parent=None,level=0,visited_buses=[]):
82
+ #Add to the list of visited buses
83
+ if visited_buses:
84
+ visited_buses.append(bus)
85
+ else:
86
+ visited_buses=[bus]
87
+ #Obtain the buses connected to this one
88
+ connected_buses = list(graph.neighbors(bus))
89
+ #Explore downstream levels
90
+ x_downstream_locations=[]
91
+ for downstream_bus in connected_buses:
92
+ #Remove the terminal from the bus nmae
93
+ 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)
97
+ if downstream_bus==bus:
98
+ graph.remove_edge(bus,downstream_bus)
99
+ else:
100
+ #Explore downstream the graph (recursive search)
101
+ if downstream_bus!=parent and not downstream_bus in visited_buses:
102
+ x_loc,locations=self.get_locations(graph,downstream_bus,locations,x_locations_levels,bus,level+1,visited_buses)
103
+ x_downstream_locations.append(x_loc)
104
+ #For the upper levels, it takes the average of the downstream buses
105
+ if x_downstream_locations:
106
+ loc=(sum(x_downstream_locations)/len(x_downstream_locations),-level);
107
+ else:
108
+ if x_locations_levels:
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
+ for lev in sorted(x_locations_levels):
112
+ if lev<=level:
113
+ x_next_location=x_locations_levels[lev]+1
114
+ #Assign location x, y
115
+ loc=(x_next_location,-level)
116
+ else:
117
+ #Default position for first bus
118
+ loc=(1,-level)
119
+ #Assign x location of this level
120
+ x_locations_levels[level]=loc[0]
121
+ #update locations
122
+ if locations:
123
+ locations[bus]=loc
124
+ else:
125
+ 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
+ def plot_graph(self,edges,v_dict_voltage,v_range,v_dict_loading,v_range_loading,dict_buses_element):
133
+ output_file_full_path_fig = self.folder + '/' + 'Network Violations' + '.png'
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')
143
+ dic_num_violations_v={}
144
+ for node in graph:
145
+ #Truncate list to limits
146
+ dic_num_violations_v[node]=0
147
+ for idx2,value in enumerate(v_dict_voltage[node]):
148
+ if value<v_range[0] or value>=v_range[1]:
149
+ dic_num_violations_v[node]=dic_num_violations_v[node]+1
150
+ #Make colormap
151
+ color_map_v=[]
152
+ max_violations_v=max(dic_num_violations_v.values())
153
+ for node in graph:
154
+ if max_violations_v==0:
155
+ intensity=0
156
+ else:
157
+ intensity=dic_num_violations_v[node]/max_violations_v
158
+ #color_map_v.append(cmap_nodes(intensity))
159
+ color_map_v.append(intensity)
160
+ #Obtain number of violations of each branch
161
+ dic_num_violations_l={}
162
+ for edge in graph.edges():
163
+ #Truncate list to limits
164
+ dic_num_violations_l[edge]=0
165
+ #bus1to2=self.remove_terminal(edge[0])+'-->'+self.remove_terminal(edge[1])
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=[]
178
+ max_violations_l=max(dic_num_violations_l.values())
179
+ for edge in graph.edges():
180
+ if max_violations_l==0:
181
+ intensity=0
182
+ else:
183
+ intensity=dic_num_violations_l[edge]/max_violations_l
184
+ #color_map_v.append(cmap_nodes(intensity))
185
+ color_map_l.append(intensity)
186
+ #Obtain min and max of x locations
187
+ max_x=0
188
+ max_y=0
189
+ for idx,name in enumerate(locations):
190
+ if (max_x<locations[name][0]):
191
+ max_x=locations[name][0]
192
+ if (max_y<-locations[name][1]):
193
+ max_y=-locations[name][1]
194
+ #plt.figure(figsize=(max_x,max_y))
195
+ unitary_size=0.5
196
+ ratio=16/9
197
+ maximum=max(max_x,max_y)*unitary_size
198
+ plt.figure(figsize=(maximum*ratio,maximum))
199
+ nodes = nx.draw_networkx_nodes(graph, pos=locations, node_color=color_map_v, cmap=cmap)
200
+ edges=nx.draw_networkx_edges(graph,pos=locations, edge_color=color_map_l,width=4,edge_cmap=cmap)
201
+ nx.draw_networkx_labels(graph, pos=locations)
202
+ num_ticks_v=5
203
+ ticks_v = np.linspace(0, 1, num_ticks_v)
204
+ labels_v = np.linspace(0, max_violations_v, num_ticks_v)
205
+ cbar=plt.colorbar(nodes,ticks=ticks_v)
206
+ cbar.ax.set_yticklabels(["{:4.2f}".format(i) for i in labels_v]) # add the labels
207
+ cbar.set_label("Voltage violations (h)", fontsize=10, y=0.5, rotation=90)
208
+ cbar.ax.yaxis.set_label_position('left')
209
+ num_ticks_l=5
210
+ ticks_l = np.linspace(0, 1, num_ticks_l)
211
+ labels_l = np.linspace(0, max_violations_l, num_ticks_l)
212
+ cbar=plt.colorbar(edges,ticks=ticks_l)
213
+ cbar.ax.set_yticklabels(["{:4.2f}".format(i) for i in labels_l]) # add the labels
214
+ cbar.set_label("Thermal limit violations (h)", fontsize=10, y=0.5, rotation=90)
215
+ cbar.ax.yaxis.set_label_position('left')
216
+ plt.axis('off')
217
+ wm = plt.get_current_fig_manager()
218
+ wm.window.state('zoomed')
219
+ plt.savefig(output_file_full_path_fig, dpi=300)
220
+ plt.show()
221
+
222
+
@@ -0,0 +1,75 @@
1
+ # *********************************************************************************
2
+ # URBANopt (tm), Copyright (c) 2019-2022, Alliance for Sustainable Energy, LLC, and other
3
+ # contributors. All rights reserved.
4
+
5
+ # Redistribution and use in source and binary forms, with or without modification,
6
+ # are permitted provided that the following conditions are met:
7
+
8
+ # Redistributions of source code must retain the above copyright notice, this list
9
+ # of conditions and the following disclaimer.
10
+
11
+ # Redistributions in binary form must reproduce the above copyright notice, this
12
+ # list of conditions and the following disclaimer in the documentation and/or other
13
+ # materials provided with the distribution.
14
+
15
+ # Neither the name of the copyright holder nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without specific
17
+ # prior written permission.
18
+
19
+ # Redistribution of this software, without modification, must refer to the software
20
+ # by the same designation. Redistribution of a modified version of this software
21
+ # (i) may not refer to the modified version by the same designation, or by any
22
+ # confusingly similar designation, and (ii) must refer to the underlying software
23
+ # originally provided by Alliance as "URBANopt". Except to comply with the foregoing,
24
+ # the term "URBANopt", or any confusingly similar designation may not be used to
25
+ # refer to any modified version of this software or any modified version of the
26
+ # underlying software originally provided by Alliance without the prior written
27
+ # consent of Alliance.
28
+
29
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
30
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
31
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
32
+ # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
34
+ # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
36
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
37
+ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
38
+ # OF THE POSSIBILITY OF SUCH DAMAGE.
39
+ # *********************************************************************************
40
+
41
+ require 'urbanopt/rnm/logger'
42
+
43
+ module URBANopt
44
+ module RNM
45
+ # Class for OpenDSS validation (runs python script)
46
+ class Validation
47
+ ##
48
+ # Initialize attributes: ++run directory+
49
+ ##
50
+ # [parameters:]
51
+ # * +rnm_dirname+ - _String_ - name of RNM-US directory that will contain the input files (within the scenario directory)
52
+ def initialize(rnm_full_path)
53
+ # absolute path
54
+ @rnm_full_path = rnm_full_path
55
+ @opendss_full_path=File.join(@rnm_full_path,'results/OpenDSS')
56
+ if !Dir.exist?(@opendss_full_path)
57
+ puts 'Error: folder does not exist'+@rnm_full_path
58
+ end
59
+ end
60
+
61
+
62
+ ##
63
+ # Run validation
64
+ ##
65
+ def run_validation()
66
+ puts "Initating OpenDSS validation in folder"
67
+ 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}`
71
+ puts log
72
+ end
73
+ end
74
+ end
75
+ end