@@ -36,7 +36,11 @@ from itertools import product
3636from sage.misc.misc_c import prod
3737from libcpp.queue cimport priority_queue
3838from libcpp.pair cimport pair
39+ from libcpp.vector cimport vector
40+
41+ from sage.data_structures.pairing_heap cimport PairingHeap
3942from sage.rings.integer_ring import ZZ
43+
4044import copy
4145
4246
@@ -1515,49 +1519,56 @@ def pnc_k_shortest_simple_paths(self, source, target, weight_function=None,
15151519 G.delete_edges(G.incoming_edges(source, labels = False ))
15161520 G.delete_edges(G.outgoing_edges(target, labels = False ))
15171521
1522+ # relabel the graph so that vertices are named with integers
1523+ cdef list int_to_vertex = list (G)
1524+ cdef dict vertex_to_int = {u: i for i, u in enumerate (int_to_vertex)}
1525+ G.relabel(perm = vertex_to_int, inplace = True )
1526+ cdef int id_source = vertex_to_int[source]
1527+ cdef int id_target = vertex_to_int[target]
1528+
1529+ def relabeled_weight_function (e , wf = weight_function):
1530+ return wf((int_to_vertex[e[0 ]], int_to_vertex[e[1 ]], e[2 ]))
1531+
15181532 by_weight, weight_function = G._get_weight_function(by_weight = by_weight,
1519- weight_function = weight_function,
1533+ weight_function = (relabeled_weight_function if weight_function else None ) ,
15201534 check_weight = check_weight)
15211535
15221536 def reverse_weight_function (e ):
15231537 return weight_function((e[1 ], e[0 ], e[2 ]))
15241538
1525- cdef dict edge_labels
1526- edge_labels = {(e[ 0 ], e[ 1 ]): e for e in G.edge_iterator()}
1527-
1528- cdef dict edge_wt
1529- edge_wt = {(e[0 ], e[1 ]): weight_function(e) for e in G.edge_iterator()}
1539+ cdef dict original_edge_labels = {(u, v): (int_to_vertex[u], int_to_vertex[v], label)
1540+ for u, v, label in G.edge_iterator()}
1541+ cdef dict original_edges = {(u, v): (int_to_vertex[u], int_to_vertex[v])
1542+ for u, v in G.edge_iterator( labels = False )}
1543+ cdef dict edge_wt = {(e[0 ], e[1 ]): weight_function(e) for e in G.edge_iterator()}
15301544
15311545 # The first shortest path tree T_0
15321546 from sage.graphs.base.boost_graph import shortest_paths
15331547 cdef dict dist
15341548 cdef dict successor
15351549 reverse_graph = G.reverse()
1536- dist, successor = shortest_paths(reverse_graph, target , weight_function = reverse_weight_function,
1550+ dist, successor = shortest_paths(reverse_graph, id_target , weight_function = reverse_weight_function,
15371551 algorithm = ' Dijkstra_Boost' )
15381552 cdef set unnecessary_vertices = set (G) - set (dist) # no path to target
1539- if source in unnecessary_vertices: # no path from source to target
1553+ if id_source in unnecessary_vertices: # no path from source to target
15401554 return
1555+ G.delete_vertices(unnecessary_vertices)
15411556
15421557 # sidetrack cost
15431558 cdef dict sidetrack_cost = {(e[0 ], e[1 ]): weight_function(e) + dist[e[1 ]] - dist[e[0 ]]
1544- for e in G.edge_iterator()
1545- if e[0 ] in dist and e[1 ] in dist}
1546-
1547- def sidetrack_length (path ):
1548- return sum (sidetrack_cost[e] for e in zip (path, path[1 :]))
1559+ for e in G.edge_iterator() if e[0 ] in dist and e[1 ] in dist}
15491560
15501561 # v-t path in the first shortest path tree T_0
15511562 def tree_path (v ):
15521563 path = [v]
1553- while v != target :
1564+ while v != id_target :
15541565 v = successor[v]
15551566 path.append(v)
15561567 return path
15571568
15581569 # shortest path
1559- shortest_path = tree_path(source )
1560- cdef double shortest_path_length = dist[source ]
1570+ shortest_path = tree_path(id_source )
1571+ cdef double shortest_path_length = dist[id_source ]
15611572
15621573 # idx of paths
15631574 cdef dict idx_to_path = {0 : shortest_path}
@@ -1568,9 +1579,66 @@ def pnc_k_shortest_simple_paths(self, source, target, weight_function=None,
15681579 # (i.e. real length = cost + shortest_path_length in T_0)
15691580 cdef priority_queue[pair[pair[double , bint], pair[int , int ]]] candidate_paths
15701581
1571- # shortest path function for weighted/unweighted graph using reduced weights
1572- shortest_path_func = G._backend.bidirectional_dijkstra_special
1582+ # ancestor_idx_vec[v] := the first vertex of ``path[:t+1]`` or ``id_target`` reachable by
1583+ # edges of first shortest path tree from v.
1584+ cdef vector[int ] ancestor_idx_vec = [- 1 for _ in range (len (G))]
1585+
1586+ def ancestor_idx_func (v , t , target_idx ):
1587+ if ancestor_idx_vec[v] != - 1 :
1588+ if ancestor_idx_vec[v] <= t or ancestor_idx_vec[v] == target_idx:
1589+ return ancestor_idx_vec[v]
1590+ ancestor_idx_vec[v] = ancestor_idx_func(successor[v], t, target_idx)
1591+ return ancestor_idx_vec[v]
1592+
1593+ # used inside shortest_path_to_green
1594+ cdef PairingHeap[int , double ] pq = PairingHeap[int , double ]()
1595+ cdef dict dist_in_func = {}
1596+ cdef dict pred = {}
1597+
1598+ # calculate shortest path from dev to one of green vertices
1599+ def shortest_path_to_green (dev , exclude_vertices ):
1600+ t = len (exclude_vertices)
1601+ ancestor_idx_vec[id_target] = t + 1
1602+ # clear
1603+ while not pq.empty():
1604+ pq.pop()
1605+ dist_in_func.clear()
1606+ pred.clear()
1607+
1608+ pq.push(dev, 0 )
1609+ dist_in_func[dev] = 0
1610+
1611+ while not pq.empty():
1612+ v, d = pq.top()
1613+ pq.pop()
1614+
1615+ if ancestor_idx_func(v, t, t + 1 ) == t + 1 : # green
1616+ path = []
1617+ while v in pred:
1618+ path.append(v)
1619+ v = pred[v]
1620+ path.append(dev)
1621+ path.reverse()
1622+ return (d, path)
1623+
1624+ if d > dist_in_func.get(v, float (' inf' )):
1625+ continue # already found a better path
1626+
1627+ for u in G.neighbor_out_iterator(v):
1628+ if u in exclude_vertices:
1629+ continue
1630+ new_dist = d + sidetrack_cost[(v, u)]
1631+ if new_dist < dist_in_func.get(u, float (' inf' )):
1632+ dist_in_func[u] = new_dist
1633+ pred[u] = v
1634+ if pq.contains(u):
1635+ if pq.value(u) > new_dist:
1636+ pq.decrease(u, new_dist)
1637+ else :
1638+ pq.push(u, new_dist)
1639+ return
15731640
1641+ cdef int i, deviation_i
15741642 candidate_paths.push(((0 , True ), (0 , 0 )))
15751643 while candidate_paths.size():
15761644 (negative_cost, is_simple), (path_idx, dev_idx) = candidate_paths.top()
@@ -1580,65 +1648,54 @@ def pnc_k_shortest_simple_paths(self, source, target, weight_function=None,
15801648 path = idx_to_path[path_idx]
15811649 del idx_to_path[path_idx]
15821650
1583- # ancestor_idx_dict[v] := the first vertex of ``path[:t+1]`` or ``path[-1]`` reachable by
1584- # edges of first shortest path tree from v when enumerating deviating edges
1585- # from ``path[t]``.
1586- ancestor_idx_dict = {v: i for i, v in enumerate (path)}
1587-
1588- def ancestor_idx_func (v , t , len_path ):
1589- if v not in successor:
1590- # target vertex is not reachable from v
1591- return - 1
1592- if v in ancestor_idx_dict:
1593- if ancestor_idx_dict[v] <= t or ancestor_idx_dict[v] == len_path - 1 :
1594- return ancestor_idx_dict[v]
1595- ancestor_idx_dict[v] = ancestor_idx_func(successor[v], t, len_path)
1596- return ancestor_idx_dict[v]
1651+ for i in range (ancestor_idx_vec.size()):
1652+ ancestor_idx_vec[i] = - 1
1653+ for i, v in enumerate (path):
1654+ ancestor_idx_vec[v] = i
15971655
15981656 if is_simple:
15991657 # output
16001658 if report_edges and labels:
1601- P = [edge_labels [e] for e in zip (path, path[1 :])]
1659+ P = [original_edge_labels [e] for e in zip (path, path[1 :])]
16021660 elif report_edges:
1603- P = list ( zip (path, path[1 :]))
1661+ P = [original_edges[e] for e in zip (path, path[1 :])]
16041662 else :
1605- P = path
1663+ P = [int_to_vertex[v] for v in path]
16061664 if report_weight:
16071665 yield (shortest_path_length + cost, P)
16081666 else :
16091667 yield P
16101668
16111669 # GET DEVIATION PATHS
16121670 original_cost = cost
1613- for deviation_i in range (len (path) - 1 , dev_idx - 1 , - 1 ):
1671+ former_part = set (path)
1672+ for deviation_i in range (len (path) - 2 , dev_idx - 1 , - 1 ):
16141673 for e in G.outgoing_edge_iterator(path[deviation_i]):
1615- if e[1 ] in path[:deviation_i + 2 ]: # e[1] is red or e in path
1616- continue
1617- ancestor_idx = ancestor_idx_func(e[1 ], deviation_i, len (path))
1618- if ancestor_idx == - 1 :
1674+ if e[1 ] in former_part: # e[1] is red or e in path
16191675 continue
1620- new_path = path[:deviation_i + 1 ] + tree_path(e[1 ])
1676+ ancestor_idx = ancestor_idx_func(e[1 ], deviation_i, len (path) - 1 )
1677+ new_is_simple = ancestor_idx > deviation_i
1678+ # no need to compute tree_path if new_is_simple is False
1679+ new_path = path[:deviation_i + 1 ] + (tree_path(e[1 ]) if new_is_simple else [e[1 ]])
16211680 new_path_idx = idx
16221681 idx_to_path[new_path_idx] = new_path
16231682 idx += 1
16241683 new_cost = original_cost + sidetrack_cost[(e[0 ], e[1 ])]
1625- new_is_simple = ancestor_idx > deviation_i
16261684 candidate_paths.push(((- new_cost, new_is_simple), (new_path_idx, deviation_i + 1 )))
16271685 if deviation_i == dev_idx:
16281686 continue
16291687 original_cost -= sidetrack_cost[(path[deviation_i - 1 ], path[deviation_i])]
1688+ former_part.remove(path[deviation_i + 1 ])
16301689 else :
1631- # get a path to target in G \ path[:dev_idx]
1632- deviation = shortest_path_func(path[dev_idx], target,
1633- exclude_vertices = unnecessary_vertices.union(path[:dev_idx]),
1634- reduced_weight = sidetrack_cost)
1635- if not deviation:
1690+ deviations = shortest_path_to_green(path[dev_idx], set (path[:dev_idx]))
1691+ if not deviations:
16361692 continue # no path to target in G \ path[:dev_idx]
1637- new_path = path[:dev_idx] + deviation
1693+ deviation_weight, deviation = deviations
1694+ new_path = path[:dev_idx] + deviation[:- 1 ] + tree_path(deviation[- 1 ])
16381695 new_path_idx = idx
16391696 idx_to_path[new_path_idx] = new_path
16401697 idx += 1
1641- new_cost = sidetrack_length(new_path)
1698+ new_cost = cost + deviation_weight
16421699 candidate_paths.push(((- new_cost, True ), (new_path_idx, dev_idx)))
16431700
16441701
0 commit comments