Aufgrund einer Störung des s3 Storage, könnten in nächster Zeit folgende GitLab Funktionen nicht zur Verfügung stehen: LFS, Container Registry, Job Artifacs, Uploads (Wiki, Bilder, Projekt-Exporte). Wir bitten um Verständnis. Es wird mit Hochdruck an der Behebung des Problems gearbeitet. Weitere Informationen zur Störung des Object Storage finden Sie hier: https://maintenance.itc.rwth-aachen.de/ticket/status/messages/59-object-storage-pilot

Commit 831ab63f authored by Alberto Dognini's avatar Alberto Dognini
Browse files

Upload Service Restoration functions

parent 27e1c1ea
# Copyright © 2018, RWTH Aachen University
# Authors: Alberto Dognini, Abhinav Sadu
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# A copy of the GNU General Public License is in the LICENSE file
# in the top level directory of this source tree.
# coding=utf8
import sys
import os
import time
import xlsxwriter
import pandas as pd
import networkx as net
import math
import cmath
from numpy import linalg
import numpy as np
import psycopg2
from datetime import datetime,timedelta
from dsse_functions_FLISR_1 import connDB, DSSEVRI1
#CODE AT THE END OF THE FILE; AFTER FUNCTIONS AND CLASSES
#FLISR algorithm function, iterate the research of restoration path and check multiple faults/modifications of the grid:
def flisr_algorithm(prin_v, switches_excel, lines_excel, loads_excel, nodes_excel, ltype_excel, general):
######################### run the FLISR ##################################
subgrid = creation_grid(switches_excel, lines_excel)
[set_SS, alive_loads, grid_lines, grid_switches, grid_loads, grid_nodes, tripped_nodes, all_possible_loads, alive_grid] = flisr_loads(subgrid, nodes_excel, loads_excel, lines_excel, switches_excel, ltype_excel)
if all_possible_loads == 0:
[to_be_closed, final_path, load, subst, all_possible_loads] = flisr_dijkstra(set_SS, alive_loads, grid_lines, grid_switches, grid_loads, grid_nodes, ltype_excel, loads_excel, nodes_excel, alive_grid, general, prin_v)
else:
to_be_closed = 0
return to_be_closed, subgrid, set_SS, switches_excel, lines_excel, loads_excel, nodes_excel, all_possible_loads
print('No more faults happened during FLISR')
print('FLISR completed successfully')
#########################################################################################
##################### Final Updation of FLISR completion results of FLISR #########
if not final_path == []:
print('the connection between load', load, 'and substation', subst, ' is:', final_path)
print('close the switches', to_be_closed, 'immediately!!!!')
return to_be_closed, subgrid, set_SS, switches_excel, lines_excel, loads_excel, nodes_excel, all_possible_loads
else:
to_be_closed = 0
all_possible_loads == 1
print('The FLISR is concluded, no more loads can be reconnected with a suitable path')
return to_be_closed, subgrid, set_SS, switches_excel, lines_excel, loads_excel, nodes_excel, all_possible_loads
###############################################################################
### FUNCTION TO CREATE THE GRID OF CLOSED OR NO SWITCHES; TO BE USED FOR STATE ESTIMATION ###
def creation_grid(switches_excel, lines_excel):
# building the graph and verifying that the line is operating
for i, elrow in lines_excel.iterrows():
if elrow[5] == 1:
grid_lines.add_edge(int(elrow[3]), int(elrow[4]), attr_dict=elrow[0:].to_dict())
#####additional graph for switches (to check which tripped)#####
# creating the graph of switches
for i, elrow in switches_excel.iterrows():
grid_switches.add_edge(int(elrow[2]), int(elrow[3]), attr_dict=elrow[0:].to_dict())
subgrid = net.Graph((source, target, attr) for source, target, attr in grid_lines.edges.data(True)) # copy of total graph
for (p, x, d) in grid_switches.edges(data=True):
if (d['attr_dict']['status'] == 0): # removal of open switch lines
subgrid.remove_edge(p, x)
print('edges', subgrid.edges())
print('FLISR : Step 1 : Creating the real time grid topology')
return subgrid;
###############################################################################
############## FUNCTION TO IDENTIFY THE LOADS TO BE CONNECTED #################
def flisr_loads(subgrid, nodes_excel, loads_excel, lines_excel, switches_excel, ltype_excel):
# building the graph and verifying that the line is operating
for i, elrow in lines_excel.iterrows():
if elrow[5] == 1:
grid_lines.add_edge(int(elrow[3]), int(elrow[4]), attr_dict=elrow[0:].to_dict())
# add attribute column containing the line impedance and complex impedance
net.set_edge_attributes(grid_lines, 'impedance line', 1) # at the beginning, adding a new column attribute with a random value
net.set_edge_attributes(grid_lines, 'complex impedance', 1)
net.set_edge_attributes(grid_lines, 'admittance', 1)
net.set_edge_attributes(grid_lines, 'complex admittance', 1)
for (a, b, c) in grid_lines.edges(data=True):
for o, elrow in ltype_excel.iterrows():
if elrow[1] == c['attr_dict']['l_code']:
length = float(c['attr_dict']['length'])
R_direct = float(elrow[4]) * length
X_direct = float(elrow[5]) * length
B_direct = float(elrow[6]) * length
G_direct = float(elrow[7]) * length
complex_impedance = R_direct + 1j * X_direct
impedance_mag = abs(complex_impedance)
c['attr_dict']['impedance line'] = impedance_mag # then, change the value with the true one
c['attr_dict']['complex impedance'] = complex_impedance
complex_admittance = G_direct + 1j * B_direct
c['attr_dict']['complex admittance'] = complex_admittance
######additional graph for nodes (to check the reference)#####
# creating the graph of nodes
for i, elrow in nodes_excel.iterrows():
grid_nodes.add_node(int(elrow[0]), attr_dict=elrow[1:].to_dict())
n_key = []
ref_ss_val=[]
for (n, n_attr) in grid_nodes.nodes(data=True):
n_key.append(n)
ref_ss_val.append(n_attr['attr_dict']['reference_ss_node'])
ref_node= dict(zip(n_key, ref_ss_val))
# determine the number of substation present in the grid
SS = []
for (p, d) in grid_nodes.nodes(data=True):
SS.append(ref_node[p]) # list with all the reference nodes (substations) with duplicates
set_SS = set(SS) # set in order to take each element only once
num_SS = len(set_SS) # number of substations in the grid
#####additional graph for switches (to check which tripped)#####
# creating the graph of switches
for i, elrow in switches_excel.iterrows():
grid_switches.add_edge(int(elrow[2]), int(elrow[3]), attr_dict=elrow[1:].to_dict())
######additional graph for loads (to check the importance)#####
# creating the graph of loads
for i, elrow in loads_excel.iterrows():
grid_loads.add_node(int(elrow[4]), attr_dict=elrow[1:].to_dict()) # changed the dict from 3 to 1
####################################################################
######### IDENTIFY THE BRANCHES AFFECTED BY THE FAULT ##############
# finding the nodes involved, from the switches that tripped --> TO BE USED FOR THE EXTERNAL CHECK IN "FLISR Algorithm"
tripped_nodes = []
for (p, x, d) in grid_switches.edges(data=True):
if d['attr_dict']['switch_tripped'] == 0:
tripped_nodes.extend([p, x])
##### IMPORTANT!!!! in case of bus-tie, it takes also nodes on healthy branch. But successively, they are excluded since there is a path between nodes and SS (feeder switch closed)
# determine the lost loads, checking connectivity with all substations
lost_loads = []
for (p, d) in grid_nodes.nodes(data=True):
count = 0
for i in set_SS:
if not (net.has_path(subgrid, p, i)):
count += 1
if count == num_SS:
lost_loads.append(p)
##############################################################################
####### eliminate from the graph the switches tripped due to the fault #######
for (p, x, d) in grid_switches.edges(data=True):
if d['attr_dict']['switch_tripped'] == 0:
grid_lines.remove_edge(p, x)
#### remove the loads that are permanent lost (in tripped zone) ####
multiple_loads = []
for i in lost_loads: # iterates the load(s) to be restored
for x in set_SS: # iterates the substations
if net.has_path(grid_lines, x, i, ):
multiple_loads.append(i) # loads that can be connected
alive_loads = set(multiple_loads)
### define the graph to be used for the algorithm #####
# remove the tie-unit that connects two alive portions of the network
for (p, x, d) in grid_switches.edges(data=True):
if d['attr_dict']['status'] == 0: # identify the tie unit
if p not in lost_loads and x not in lost_loads: # verify that both the sides are energized
grid_lines.remove_edge(p, x)
#### remove the lost loads from grid_lines ######
permanent_lost = [val for val in lost_loads if val not in alive_loads]
alive_grid_iter = grid_lines.copy()
alive_grid = grid_lines.copy()
print('alive grid',alive_grid.edges(data=True))
for (a, b) in alive_grid_iter.nodes(data=True):
for c in permanent_lost:
if c in alive_grid.nodes() and a == c:
alive_grid.remove_node(c)
if alive_loads: # list not empty
print(
'FLISR : Step 2 : Creating the complete grid with the nodes , lines, and loads of the disconnected grid and identifying the non faulty section of the fauty feeder')
print(grid_lines.edges())
all_possible_loads = 0
else:
all_possible_loads = 1
print('all loads have been reconnected')
return set_SS, alive_loads, grid_lines, grid_switches, grid_loads, grid_nodes, tripped_nodes, all_possible_loads, alive_grid;
############## SELECT THE LOAD TO BE RESTORED #######################
def selecting_loads(grid_loads, alive_loads):
# creating a graph with all the lost loads
graph_lost_loads = grid_loads.subgraph(alive_loads)
# create a list with nodes having the highest cost grade
cost_loads = []
# increasing grade from 1 until finding the corresponding of lost loads
i = 1
while not cost_loads:
for (p, d) in graph_lost_loads.nodes(data=True):
if d['attr_dict']['cost'] == i:
cost_loads.append(int(p))
i = i + 1
# creating a graph with the max cost grade loads
graph_cost_loads = graph_lost_loads.subgraph(cost_loads)
restored_loads = [] # initialize a new list, definitive loads
# verify if there are more loads with the same grade, so to select the max power
if len(cost_loads) > 1:
i = 2e6
while not restored_loads:
for (p, d) in graph_cost_loads.nodes(data=True):
if d['attr_dict']['active_p'] == i:
restored_loads.append(p)
i = i - 1
else: # if there is only one load, doesn't check the power
restored_loads = cost_loads
print('Loads to be restored:', restored_loads)
return restored_loads
#############################################################################
def flisr_dijkstra(set_SS, alive_loads, grid_lines, grid_switches, grid_loads, grid_nodes, line_type, loads_updated, nodes_updated, alive_grid, general, prin_v):
to_be_closed = []
solution = 0
vbase = nodes_updated.loc[1, 'vbase']
pbase = float(general['pbase']) # define base power, to compute p.u. losses
###### call the function to determine the loads to be restored############
restored_loads = selecting_loads(grid_loads, alive_loads)
while solution == 0:
dec_mat = np.empty((0, 4), int)
container = []
###### CARRY OUT THE DIJKSTRA ALGORITHM TO FIND THE MINIMUM PATH #############
for x in set_SS: # iterates the substations
for i in restored_loads: # iterates the load(s) to be restored
if net.has_path(grid_lines, x,i): # necessary to verify the existence of path, Dijkstra cannot be empty
shortest_path = net.dijkstra_path(grid_lines, x, i,'impedance line') # Dijkstra compute the path according to the specific line impedance
shortest_graph = grid_lines.subgraph(shortest_path) # creating a subgraph with only lines involved in the dijkstra path
print('proposed shortest path:', shortest_graph.edges())
######### to verify that the closing path doesn't include two switches (two tie units): network no more radial ###########
count = 0 #
for (p, m, d) in shortest_graph.edges(data=True):
for (k, n, v) in grid_switches.edges(data=True):
if (p == k and m == n) or (p == n and m == k):
if (v['attr_dict']['status'] == 0):
count += 1
if count > 1:
print('No radial path between substation', x, 'and load', i) #
continue # stop the current for loop, look for a new dijkstra path
##########################################################################################################################
# remove from the graph "alive_grid" the open switches and add the proposed tie-switch
alive_grid_iter_1 = alive_grid.copy()
for (a, b, c) in grid_switches.edges(data=True):
for (d, e, f) in alive_grid_iter_1.edges(data=True):
if (a == d and b == e) and c['attr_dict']['status'] == 0: # and ((a,b) in alive_grid.nodes()):
alive_grid.remove_edge(a, b)
if (a == e and b == d) and c['attr_dict']['status'] == 0: # and ((a,b) in alive_grid.nodes()):
alive_grid.remove_edge(b, a)
alive_grid = (net.compose(alive_grid,shortest_graph)).copy() # merge witht the new shortest graph, in order to include the proposed closed tie-switch
##### INITIALIZATION OF STATE ESTIMATION MEASUREMENTS ####
error = 0
power_loss = 0
pload = 0
min_line_relat = [9999, 1, 1, 9999, 1, 1, 9999, 1, 1]
#### SUB-DIVISION OF THE NETWORK; CREATING ONE GRAPH FOR EACH SUBSTATION #####
for z in set_SS:
SS_alive_grid_iter = alive_grid.copy()
SS_alive_grid = alive_grid.copy()
for (y, t) in SS_alive_grid_iter.nodes(data=True):
if (not net.has_path(SS_alive_grid, z, y)) and y in SS_alive_grid.nodes():
SS_alive_grid.remove_node(y)
print('grid', z, 'composed by:', SS_alive_grid.edges())
### CALL THE FUNCTION TO DEFINE THE INPUT FOR THE STATE ESTIMATOR
graph_to_SE, mapping, reverse_mapping = graph_mapping_for_SE(SS_alive_grid, grid_nodes)
griddata = convert_graph_to_SE_linedata(graph_to_SE, line_type, grid_nodes, pbase, vbase)
measdata_to_SE, pload = Measurements_to_SE(mapping, grid_loads, SS_alive_grid, pbase, pload)
### STATE ESTIMATION FUNCTION - for the portion of the grid
dssedata1 = DSSEVRI1(griddata, measdata_to_SE)
temp1 = np.zeros((dssedata1.nstate, 1))
temp2 = np.zeros((dssedata1.nstate, 1))
for h in range(dssedata1.nstate):
for key in reverse_mapping.keys():
if int(dssedata1.dsseinstance[h].itemid1) == key:
temp1[h, 0] = reverse_mapping[key]
# dssedata2.dsseinstance[h].itemid1=reverse_mapping[key]
if int(dssedata1.dsseinstance[h].itemid2) == key:
temp2[h, 0] = reverse_mapping[key]
# dssedata2.dsseinstance[h].itemid2=reverse_mapping[key]
for h in range(dssedata1.nstate):
dssedata1.dsseinstance[h].itemid1 = temp1[h, 0]
dssedata1.dsseinstance[h].itemid2 = temp2[h, 0]
error, power_loss, min_line_relat = check_dijkstra(dssedata1, grid_nodes, nodes_updated,line_type, SS_alive_grid, error, power_loss, min_line_relat, pbase)
if error == 0:
### GET STATE ESTIMATION RESULTS!!!!!
# carry out computation of path losses, and add the weighting factor u1
graph_path = grid_switches.subgraph(shortest_path) # create a subgraph with edges having switches
graph_path_unfrozen = net.Graph()
graph_path_unfrozen = graph_path.copy()
for (a, b, c) in graph_path.edges(data=True):
if c['attr_dict']['status'] == 1: # keep only the open switches (to be closed)
graph_path_unfrozen.remove_edge(a, b)
graph_path = graph_path_unfrozen.copy()
to_be_closed = list(net.get_edge_attributes(graph_path, 'name').values())
subst = x
load = i
final_path = shortest_path
container.append(to_be_closed) # save the specific info of this solution
container.append(shortest_path)
container.append(i)
container.append(x)
temp_mat = np.array([power_loss, min_line_relat[0], min_line_relat[3], min_line_relat[6]]) # add row to decision matrix as new possible solution
print('most consumed line bewteen nodes', min_line_relat[1], min_line_relat[2],'is equal to', min_line_relat[0])
print('most consumed line bewteen nodes', min_line_relat[4], min_line_relat[5],'is equal to', min_line_relat[3])
print('most consumed line bewteen nodes', min_line_relat[7], min_line_relat[8],'is equal to', min_line_relat[6])
dec_mat = np.vstack((dec_mat, temp_mat))
solution = 1 # solution found
### REPEAT THE CHECK WITH HIGHER AMPACITY LIMIT
if solution == 0:
print('increase the ampacity limit by 20%')
# increase the amax limit of 20% to stay in temporary overload condition
for row, elrow in line_type.iterrows():
old_amax = elrow[2]
line_type.loc[row, 'amax'] = old_amax * 1.2
###### CARRY OUT THE DIJKSTRA ALGORITHM TO FIND THE MINIMUM PATH #############
for x in set_SS: # iterates the substations
for i in restored_loads: # iterates the load(s) to be restored
if net.has_path(grid_lines, x,i): # necessary to verify the existence of path, Dijkstra cannot be empty
shortest_path = net.dijkstra_path(grid_lines, x, i,'impedance line') # Dijkstra compute the path according to the specific line impedance
shortest_graph = grid_lines.subgraph(shortest_path) # creating a subgraph with only lines involved in the dijkstra path
print('proposed shortest path:', shortest_graph.edges())
######### to verify that the closing path doesn't include two switches (two tie units): network no more radial ###########
count = 0 #
for (p, m, d) in shortest_graph.edges(data=True):
for (k, n, v) in grid_switches.edges(data=True):
if (p == k and m == n) or (p == n and m == k):
if (v['attr_dict']['status'] == 0):
count += 1
if count > 1:
print('No radial path between substation', x, 'and load', i) #
continue # stop the current for loop, look for a new dijkstra path
##########################################################################################################################
# remove from the graph "alive_grid" the open switches and add the proposed tie-switch
alive_grid_iter = alive_grid.copy()
for (a, b, c) in grid_switches.edges(data=True):
for (d, e, f) in alive_grid_iter.edges(data=True):
if (a == d and b == e) and c['attr_dict']['status'] == 0: # and ((a,b) in alive_grid.nodes()):
alive_grid.remove_edge(a, b)
if (a == e and b == d) and c['attr_dict']['status'] == 0: # and ((a,b) in alive_grid.nodes()):
alive_grid.remove_edge(b, a)
alive_grid = (net.compose(alive_grid,shortest_graph)).copy() # merge witht the new shortest graph, in order to include the proposed closed tie-switch
##### INITIALIZATION OF STATE ESTIMATION MEASUREMENTS ####
error = 0
power_loss = 0
pload = 0
min_line_relat = [9999, 1, 1, 9999, 1, 1, 9999, 1, 1]
#### SUB-DIVISION OF THE NETWORK; CREATING ONE GRAPH FOR EACH SUBSTATION #####
for z in set_SS:
SS_alive_grid = alive_grid.copy()
SS_alive_grid_iter = alive_grid.copy()
for (y, t) in SS_alive_grid_iter.nodes(data=True):
if (not net.has_path(SS_alive_grid, z, y)) and y in SS_alive_grid.nodes():
SS_alive_grid.remove_node(y)
print('grid', z, 'composed by:', SS_alive_grid.edges())
### CALL THE FUNCTION TO DEFINE THE INPUT FOR THE STATE ESTIMATOR
graph_to_SE, mapping, reverse_mapping = graph_mapping_for_SE(SS_alive_grid, grid_nodes)
griddata = convert_graph_to_SE_linedata(graph_to_SE, line_type, grid_nodes, pbase,
vbase)
measdata_to_SE, pload = Measurements_to_SE(mapping, grid_loads, SS_alive_grid, pbase,
pload)
### STATE ESTIMATION FUNCTION - for the portion of the grid
dssedata1 = DSSEVRI1(griddata, measdata_to_SE)
temp1 = np.zeros((dssedata1.nstate, 1))
temp2 = np.zeros((dssedata1.nstate, 1))
for h in range(dssedata1.nstate):
for key in reverse_mapping.keys():
if int(dssedata1.dsseinstance[h].itemid1) == key:
temp1[h, 0] = reverse_mapping[key]
# dssedata2.dsseinstance[h].itemid1=reverse_mapping[key]
if int(dssedata1.dsseinstance[h].itemid2) == key:
temp2[h, 0] = reverse_mapping[key]
# dssedata2.dsseinstance[h].itemid2=reverse_mapping[key]
for h in range(dssedata1.nstate):
dssedata1.dsseinstance[h].itemid1 = temp1[h, 0]
dssedata1.dsseinstance[h].itemid2 = temp2[h, 0]
error, power_loss, min_line_relat = check_dijkstra(dssedata1, grid_nodes, nodes_updated,line_type, SS_alive_grid, error, power_loss, min_line_relat, pbase)
if error == 0:
### GET STATE ESTIMATION RESULTS!!!!!
# carry out computation of path losses, and add the weighting factor u1
graph_path = grid_switches.subgraph(
shortest_path) # create a subgraph with edges having switches
for (a, b, c) in graph_path.edges(data=True):
if c['attr_dict']['status'] == 1: # keep only the open switches (to be closed)
graph_path.remove_edge(a, b)
to_be_closed = list(net.get_edge_attributes(graph_path, 'name').values())
subst = x
load = i
final_path = shortest_path
container.append(to_be_closed) # save the specific info of this solution
container.append(shortest_path)
container.append(i)
container.append(x)
temp_mat = np.array([power_loss, min_line_relat[0], min_line_relat[3], min_line_relat[
6]]) # add row to decision matrix as new possible solution
dec_mat = np.vstack((dec_mat, temp_mat))
solution = 1 # solution found
if solution == 1:
print('ATTENTION!!! This load can be restored only with overload condition of the network, this is an emergency temporary configuration!')
for row, elrow in line_type.iterrows(): # restoring original nominal amax value
new_amax = elrow[2]
line_type.loc[row, 'amax'] = new_amax / 1.2
if solution == 1: # compute the best solution according to TOPSIS method
print('dec_mat', dec_mat)
row_sums = dec_mat.sum(axis=0) # normalization of decision matrix according to columns
ndec_mat = dec_mat / row_sums[np.newaxis, :]
print('ndec_mat', ndec_mat)
weig_mat = np.diag(prin_v) # creation of diagonal matrix with weights from principal eigenvector
wdec_mat = np.matmul(ndec_mat, weig_mat) # calculation of weighted normalized decision matrix
print('wdec_mat', wdec_mat)
pos_neg = [0, 1, 1,1] # 0 means negative criteria (to be minimized), LOSSES, 1 means positive criteria (to be maximized), LINE UTILIZATION FACTOR
pis = []
nis = []
for j in range(len(wdec_mat[0])): # iterate the columns (criteria)
if pos_neg[j] == 1:
pis.append(np.amax(wdec_mat[:, j])) # compute the ideal positive and negative solutions
nis.append(np.amin(wdec_mat[:, j]))
else:
pis.append(np.amin(wdec_mat[:, j]))
nis.append(np.amax(wdec_mat[:, j]))
pis_mat = np.asarray(pis) # convertion from list to array
nis_mat = np.asarray(nis)
pis_mat_1 = pis_mat # duplication for following operations
nis_mat_1 = nis_mat
print('pis_mat', pis_mat)
print('nis_mat', nis_mat)
for i in range(len(wdec_mat) - 1): # add new identical array
pis_mat = np.vstack(
(pis_mat, pis_mat_1)) # create matrix with ideal solution as number of possibilities
nis_mat = np.vstack((nis_mat, nis_mat_1))
pos_dif = wdec_mat - pis_mat
neg_dif = wdec_mat - nis_mat
sqr_pos_dif = np.square(pos_dif) # square of the matrix
sqr_neg_dif = np.square(neg_dif)
s_pos = np.sqrt(sqr_pos_dif.sum(axis=1)) # root square of the sum along the row
s_neg = np.sqrt(sqr_neg_dif.sum(axis=1))
sol = []
for i in range(len(s_pos)): # iterate the columns (possibilities)
closeness = s_neg[i] / (s_pos[i] + s_neg[i]) # calculation of closeness to ideal solution
sol.append(closeness)
print('Closeness to ideal solution:', sol)
idx_sol = np.argmax(sol) # index of solution having the highest closeness to ideal solution
to_be_closed = container[
4 * idx_sol] # retrieve the information related to the identified best solution
final_path = container[4 * idx_sol + 1]
load = container[4 * idx_sol + 2]
subst = container[4 * idx_sol + 3]
if solution == 0: # this loads cannot be connected, look for other loads
for w in restored_loads: # remove the inspected loads from the selected list
alive_loads.remove(w)
if alive_loads: # if the list is not empty, look for new loads and repeat the dijkstra
restored_loads = selecting_loads(grid_loads, alive_loads)
else: # otherwise stop the process
solution = 1
if not to_be_closed:
print('no more load can be reconnected with a suitable path')
all_possible_loads = 1
final_path = []
load = []
subst = []
else:
print(
'FLISR : Step 3 : identifying the Optimal reconfigurable path for restoring the non faulty section of faulty feeder')
all_possible_loads = 0
return to_be_closed, final_path, load, subst, all_possible_loads;
###############################################################################
###### FUNCTION TO CHECK THE RESPECT OF NETWORK RADIALITY AND LINE CAPABILITY##########
def check_dijkstra(dssedata, grid_nodes, nodes_excel,ltype_excel, SS_alive_grid, error, power_loss, min_line_relat, pbase):
#########################################################################
# HERE THE VOLTAGE LEVEL CHECK TAKES PLACE, of the whole grid
# import outcomes from STATE ESTIMATION
for o, elrow in nodes_excel.iterrows(): # iterate the nodes, all the grid must be checked
for q in range(dssedata.nstate):
if int(dssedata.dsseinstance[q].itemid1) == elrow[0] and int(dssedata.dsseinstance[q].mtype) == 1: # it should be the LN voltage, TO BE CHECKED!!!
if (float(dssedata.dsseinstance[q].estimationvalue) + 3 * float(dssedata.dsseinstance[q].estimationaccuracy)) > 1.1 or (float(dssedata.dsseinstance[q].estimationvalue) + 3 * float(dssedata.dsseinstance[q].estimationaccuracy)) < 0.9: # removed the conversion to absolute value: * float(elrow[4]): #Check voltage level in range 0.9 -> 1.1
print('Voltage level exceeds the limits at node', elrow[1], ' being ', (float(dssedata.dsseinstance[q].estimationvalue) + 3 * float(dssedata.dsseinstance[q].estimationaccuracy)),'% so this network configuration is not acceptable')
error = 1
return error, power_loss, min_line_relat
# print('In this network configuration, voltage limits are respected for all the nodes')
####### load the nodes voltage
no_key = []
vbase_val=[]
for (n, n_attr) in grid_nodes.nodes(data=True):
no_key.append(n)
vbase_val.append(n_attr['attr_dict']['vbase'])
voltages_complete = dict(zip(no_key, vbase_val))
# HERE THE CURRENT CAPACITY CHECK TAKES PLACE, of the whole grid
for (a, b, c) in SS_alive_grid.edges(data=True):
Ibase = pbase / (math.sqrt(3) * float(voltages_complete[a]))
for o, elrow in ltype_excel.iterrows():
if elrow[1] == c['attr_dict']['l_code']:
amax = elrow[2]
for r in range(dssedata.nstate):
# acquire voltage magnitude and phase (with uncertainties), for node a and b
if int(dssedata.dsseinstance[r].itemid1) == a and int(dssedata.dsseinstance[r].mtype) == 1: # and int(dssedata.dsseinstance[r].phase)=='AN': #it should be the LN voltage, TO BE CHECKED!!! phase A taken only as reference (everything symmetrical)
V1_mag = float(dssedata.dsseinstance[r].estimationvalue) * float(voltages_complete[a]) # get the p.u. voltage and multiply by nominal voltage
u_V1_mag = float(dssedata.dsseinstance[r].estimationaccuracy) * float(voltages_complete[a])