Skip to content

Commit

Permalink
bugfix: add code that ensures bidirectionality of graph
Browse files Browse the repository at this point in the history
  • Loading branch information
piaulous committed Aug 23, 2023
1 parent 85a2b1b commit feef1d7
Show file tree
Hide file tree
Showing 3 changed files with 7 additions and 334 deletions.
2 changes: 1 addition & 1 deletion ding0/grid/lv_grid/graph_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ def remove_parallels_and_loops(G):
to_remove = []

# identify all the parallel edges in the MultiDiGraph
parallels = ((u, v) for u, v, k in G.edges(keys=True) if k > 0)
parallels = [(u, v) for u, v, k in G.edges(keys=True) if k > 0]

# remove the parallel edge with greater "weight" attribute value
for u, v in set(parallels):
Expand Down
4 changes: 4 additions & 0 deletions ding0/grid/mv_grid/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,10 @@ def reduce_graph_for_dist_matrix_calc(graph, nodes_to_keep):
graph = remove_parallels_and_loops(graph)
graph = remove_unloaded_deadends(graph, nodes_to_keep)
post_graph_no = len(graph)

# ensure graph bidirectionality
bidir_edges = [(v, u, data) for u, v, data in graph.edges(data=True) if (v, u) not in graph.edges]
graph.add_edges_from(bidir_edges)

return graph

Expand Down
335 changes: 2 additions & 333 deletions ding0/grid/mv_grid/urban_mv_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
from ding0.core.network.cable_distributors import MVCableDistributorDing0
import logging

#PAUl new
from ding0.grid.mv_grid.tools import get_edge_tuples_from_path, cut_line_by_distance, reduce_graph_for_dist_matrix_calc, \
calc_street_dist_matrix, conn_ding0_obj_to_osm_graph, get_shortest_path_shp_single_target, \
update_graphs, create_stub_dict, check_stub_criterion, update_stub_dict, split_graph_by_core, relabel_graph_nodes
Expand Down Expand Up @@ -171,7 +170,7 @@ def routing_solution_to_ding0_graph(mv_grid, core_graph, solution):
# create new ring object for route
ring = RingDing0(grid=mv_grid)

#PAUL new ring_demand # demand is apparent power [kVA]
# ring demand is apparent power [kVA]
ring._demand = sum([node.demand() for node in r._nodes])

# translate solution's node names to graph node objects using dict created before
Expand All @@ -190,13 +189,7 @@ def routing_solution_to_ding0_graph(mv_grid, core_graph, solution):
if isinstance(node1, LVLoadAreaCentreDing0):
node1.lv_load_area.ring = ring

# set branch length
# PAUL new: add straight LineString as geometry to branch, replaces calc_geo_dist
#b.geometry, b.length = calc_edge_geometry(node1, node2, srid=3035)
#b.length = calc_geo_dist(node1, node2, srid=3035) # new param: srid=3035

# calculate branch geometry and length for street courses
##TODO introduce, new fct::
line_shp, line_length, sp = get_shortest_path_shp_single_target(core_graph, node1, node2, return_path=True)

b.geometry = line_shp
Expand All @@ -220,7 +213,6 @@ def routing_solution_to_ding0_graph(mv_grid, core_graph, solution):
edges_graph.append((node1, node2, dict(branch=b)))

#osmid_branch_dict saves branches overlapping with osmid, format osmid: [branches]
#PAUL new
for osmid in sp:
if osmid in osmid_branch_dict: #set due to better performance while iterating later
osmid_branch_dict[osmid].add(b)
Expand Down Expand Up @@ -307,327 +299,4 @@ def solve(mv_grid, debug=False, anim=None):
mv_grid = mv_urban_connect(mv_grid, street_graph, core_graph, stub_graph, stub_dict, osmid_branch_dict)
# remove load area centre (has been shifted to mv_routing)

return mv_grid.graph


'''def solve(mv_grid, debug=False, anim=None):
# TODO: check docstring
""" Do MV routing for given nodes in `graph`.
Translate data from node objects to appropriate format before.
Parameters
----------
graph: :networkx:`NetworkX Graph Obj< >`
NetworkX graph object with nodes
debug: bool, defaults to False
If True, information is printed while routing
anim: AnimationDing0
AnimationDing0 object
Returns
-------
:networkx:`NetworkX Graph Obj< >`
NetworkX graph object with nodes and edges
See Also
--------
ding0.tools.animation.AnimationDing0 : for a more detailed description on anim parameter.
"""
##### PAUL new:
agg_la_list=[]
# catch aggregated load area in MVGD
for load_area in mv_grid.grid_district._lv_load_areas:
if load_area.is_aggregated:
agg_load_area = load_area
agg_la_list.append(load_area)
if len(agg_la_list) > 1:
print('logger.warning')
# get osm street graph
osm_graph = agg_load_area.load_area_graph
# get mv station of MVGD
mv_station = mv_grid._station
#TODO create fct universally applicable, not just for MVStation
# add mv_station to graph
# get nearest node of mv_station and connect
osm_graph = conn_ding0_obj_to_osm_graph(osm_graph, mv_station)
#TODO: check distance between MVstation and Load area centre, create to LA centre if necessary, steps to make in connect generators
#remove unloaded stations from mv_grid
#remove load area centre, if no SPS and MVstation is in center
load_area_centre = agg_load_area.lv_load_area_centre
# get lv stations in agg load area as dict {osmid: lv_station} of strings
lv_stations = {str(lvgd.lv_grid._station.osm_id_node): str(lvgd.lv_grid._station) for lvgd in agg_load_area._lv_grid_districts}
# convert osm graph node names to str # rename osm graph nodes with ding0 name as str
nodes_to_str = {node: str(node) for node in osm_graph.nodes}
osm_graph = nx.relabel_nodes(osm_graph, nodes_to_str)
osm_graph = nx.relabel_nodes(osm_graph, lv_stations)
# prepare routing graph
# get nodes to keep
node_name_list = list(lv_stations.values())
node_name_list.append(str(mv_station))
#reduce graph to necessary size
osm_graph_red = reduce_graph_for_dist_matrix_calc(osm_graph, node_name_list)
#####
# translate DING0 graph to routing specs
specs = osm_graph_to_routing_specs_urban(agg_load_area, osm_graph_red, mv_station)
# create routing graph using specs
RoutingGraph = Graph(specs)
timeout = 30000
# create solver objects
savings_solver = savings.ClarkeWrightSolver()
local_search_solver = local_search.LocalSearchSolver()
start = time.time()
# create initial solution using Clarke and Wright Savings methods
savings_solution = savings_solver.solve(RoutingGraph, timeout, debug, anim)
if debug:
logger.debug('ClarkeWrightSolver solution:')
util.print_solution(savings_solution)
logger.debug('Elapsed time (seconds): {}'.format(time.time() - start))
#savings_solution.draw_network()
# PAUL new: start with return of savings solution
graph = routing_solution_to_ding0_graph(mv_grid, osm_graph_red, savings_solution)
return graph'''

'''
def routing_solution_to_ding0_graph_old(mv_grid, osm_graph, solution):
""" Insert `solution` from routing into `graph`
Parameters
----------
graph: :networkx:`NetworkX Graph Obj< >`
NetworkX graph object with nodes
solution: BaseSolution
Instance of `BaseSolution` or child class (e.g. `LocalSearchSolution`) (=solution from routing)
Returns
-------
:networkx:`NetworkX Graph Obj< >`
NetworkX graph object with nodes and edges
"""
# build node dict (name: obj) from graph nodes to map node names on node objects
node_list = {str(n): n for n in mv_grid.graph.nodes()}
# add edges from solution to graph
try:
depot = solution._nodes[solution._problem._depot.name()]
depot_node = node_list[depot.name()]
for r in solution.routes():
circ_breaker_pos = None
# if route has only one node and is not aggregated, it wouldn't be possible to add two lines from and to
# this node (undirected graph of NetworkX). So, as workaround, an additional MV cable distributor is added
# at nodes' position (resulting route: HV/MV_subst --- node --- cable_dist --- HV/MV_subst.
if len(r._nodes) == 1:
if not solution._problem._is_aggregated[r._nodes[0]._name]:
# create new cable dist
cable_dist = MVCableDistributorDing0(geo_data=node_list[r._nodes[0]._name].geo_data,
grid=mv_grid)
mv_grid.add_cable_distributor(cable_dist)
# create new node (as dummy) an allocate to route r
r.allocate([Node(name=repr(cable_dist), demand=0)])
# add it to node list and allocated-list manually
node_list[str(cable_dist)] = cable_dist
solution._problem._is_aggregated[str(cable_dist)] = False
# set circ breaker pos manually
circ_breaker_pos = 1
# build edge list
n1 = r._nodes[0:len(r._nodes)-1]
n2 = r._nodes[1:len(r._nodes)]
edges = list(zip(n1, n2))
edges.append((depot, r._nodes[0]))
edges.append((r._nodes[-1], depot))
# create MV Branch object for every edge in `edges`
mv_branches = [BranchDing0(grid=mv_grid) for _ in edges]
edges_with_branches = list(zip(edges, mv_branches))
# recalculate circuit breaker positions for final solution, create it and set associated branch.
# if circ. breaker position is not set manually (routes with more than one load area, see above)
if not circ_breaker_pos:
circ_breaker_pos = r.calc_circuit_breaker_position()
node1 = node_list[edges[circ_breaker_pos - 1][0].name()]
node2 = node_list[edges[circ_breaker_pos - 1][1].name()]
# ALTERNATIVE TO METHOD ABOVE: DO NOT CREATE 2 BRANCHES (NO RING) -> LA IS CONNECTED AS SATELLITE
# IF THIS IS COMMENTED-IN, THE IF-BLOCK IN LINE 87 HAS TO BE COMMENTED-OUT
# See issue #114
# ===============================
# do not add circuit breaker for routes which are aggregated load areas or
# routes that contain only one load area
# if not (node1 == depot_node and solution._problem._is_aggregated[edges[circ_breaker_pos - 1][1].name()] or
# node2 == depot_node and solution._problem._is_aggregated[edges[circ_breaker_pos - 1][0].name()] or
# len(r._nodes) == 1):
# ===============================
# do not add circuit breaker for routes which are aggregated load areas
if not (node1 == depot_node and solution._problem._is_aggregated[edges[circ_breaker_pos - 1][1].name()] or
node2 == depot_node and solution._problem._is_aggregated[edges[circ_breaker_pos - 1][0].name()]):
branch = mv_branches[circ_breaker_pos - 1]
##TODO introduce, new fct::
sp = nx.shortest_path(osm_graph, str(node1), str(node2), weight='length')
edge_path = get_edge_tuples_from_path(osm_graph, sp)
line_shp = linemerge([osm_graph.edges[edge]['geometry'] for edge in edge_path])
circ_breaker = CircuitBreakerDing0(grid=mv_grid, branch=branch, \
geo_data=cut_line_by_distance(line_shp, 0.5, normalized=True)[0])
branch.circuit_breaker = circ_breaker
# create new ring object for route
ring = RingDing0(grid=mv_grid)
# translate solution's node names to graph node objects using dict created before
# note: branch object is assigned to edge using an attribute ('branch' is used here), it can be accessed
# using the method `graph_edges()` of class `GridDing0`
edges_graph = []
for ((n1, n2), b) in edges_with_branches:
# get node objects
node1 = node_list[n1.name()]
node2 = node_list[n2.name()]
# set branch's ring attribute
b.ring = ring
# set LVLA's ring attribute
if isinstance(node1, LVLoadAreaCentreDing0):
node1.lv_load_area.ring = ring
# calculate branch geometry and length for straight LineStrings
# b.geometry, b.length = calc_edge_geometry(node1, node2, srid=3035)
# b.length = calc_geo_dist(node1, node2, srid=3035) # new param: srid=3035
# calculate branch geometry and length for street courses
sp = nx.shortest_path(osm_graph, n1.name(), n2.name(), weight='length')
edge_path = get_edge_tuples_from_path(osm_graph, sp)
line_shp = linemerge([osm_graph.edges[edge]['geometry'] for edge in edge_path])
b.geometry = line_shp
b.length = line_shp.length
# set branch kind and type
# 1) default
b.kind = mv_grid.default_branch_kind
b.type = mv_grid.default_branch_type
# 2) aggregated load area types
if node1 == depot_node and solution._problem._is_aggregated[n2.name()]:
b.connects_aggregated = True
b.kind = mv_grid.default_branch_kind_aggregated
b.type = mv_grid.default_branch_type_aggregated
elif node2 == depot_node and solution._problem._is_aggregated[n1.name()]:
b.connects_aggregated = True
b.kind = mv_grid.default_branch_kind_aggregated
b.type = mv_grid.default_branch_type_aggregated
# append to branch list
edges_graph.append((node1, node2, dict(branch=b)))
# add branches to graph
mv_grid.graph.add_edges_from(edges_graph)
except:
logger.exception(
'unexpected error while converting routing solution to DING0 graph (NetworkX).')
return mv_grid.graph
### old:
def ding0_graph_to_routing_specs(graph):
""" Build data dictionary from graph nodes for routing (translation)
Parameters
----------
graph: :networkx:`NetworkX Graph Obj< >`
NetworkX graph object with nodes
Returns
-------
:obj:`dict`
Data dictionary for routing.
See Also
--------
ding0.grid.mv_grid.models.models.Graph : for keys of return dict
"""
# get power factor for loads
cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load')
specs = {}
nodes_demands = {}
nodes_pos = {}
nodes_agg = {}
# check if there are only load areas of type aggregated and satellite
# -> treat satellites as normal load areas (allow for routing)
satellites_only = True
for node in graph.nodes():
if isinstance(node, LVLoadAreaCentreDing0):
if not node.lv_load_area.is_satellite and not node.lv_load_area.is_aggregated:
satellites_only = False
for node in graph.nodes():
# station is LV station
if isinstance(node, LVLoadAreaCentreDing0):
# only major stations are connected via MV ring
# (satellites in case of there're only satellites in grid district)
if not node.lv_load_area.is_satellite or satellites_only:
# get demand and position of node
# convert node's demand to int for performance purposes and to avoid that node
# allocation with subsequent deallocation results in demand<0 due to rounding errors.
nodes_demands[str(node)] = int(node.lv_load_area.peak_load / cos_phi_load)
nodes_pos[str(node)] = (node.geo_data.x, node.geo_data.y)
# get aggregation flag
if node.lv_load_area.is_aggregated:
nodes_agg[str(node)] = True
else:
nodes_agg[str(node)] = False
# station is MV station
elif isinstance(node, MVStationDing0):
nodes_demands[str(node)] = 0
nodes_pos[str(node)] = (node.geo_data.x, node.geo_data.y)
specs['DEPOT'] = str(node)
specs['BRANCH_KIND'] = node.grid.default_branch_kind
specs['BRANCH_TYPE'] = node.grid.default_branch_type
specs['V_LEVEL'] = node.grid.v_level
specs['NODE_COORD_SECTION'] = nodes_pos
specs['DEMAND'] = nodes_demands
specs['MATRIX'] = calc_geo_dist_matrix(nodes_pos)
specs['IS_AGGREGATED'] = nodes_agg
return specs
'''
return mv_grid.graph

0 comments on commit feef1d7

Please sign in to comment.