Skip to content

Commit 52228c9

Browse files
committed
fix bug of getting non-simple path in pnc algorithm
1 parent 20141b7 commit 52228c9

File tree

1 file changed

+25
-15
lines changed

1 file changed

+25
-15
lines changed

src/sage/graphs/path_enumeration.pyx

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,10 +1432,9 @@ def pnc_k_shortest_simple_paths(self, source, target, weight_function=None,
14321432
returned.
14331433
14341434
OUTPUT: iterator
1435-
14361435
ALGORITHM:
14371436
1438-
This algorithm is based on the `feng_k_shortest_simple_paths` algorithm
1437+
This algorithm is based on the ``feng_k_shortest_simple_paths`` algorithm
14391438
in [Feng2014]_, but postpones the shortest path tree computation when non-simple
14401439
deviations occur. See Postponed Node Classification algorithm in [ACN2023]_
14411440
for the algorithm description.
@@ -1469,8 +1468,8 @@ def pnc_k_shortest_simple_paths(self, source, target, weight_function=None,
14691468
(22.0, [(5, 2, 5), (2, 0, 5), (0, 4, 2), (4, 3, 3), (3, 1, 7)]),
14701469
(23.0, [(5, 2, 5), (2, 0, 5), (0, 3, 1), (3, 4, 2), (4, 1, 10)]),
14711470
(25.0, [(5, 4, 9), (4, 0, 8), (0, 3, 1), (3, 1, 7)]),
1472-
(26.0, [(5, 4, 9), (4, 0, 8), (0, 3, 1), (3, 2, 4), (2, 1, 4)]),
14731471
(26.0, [(5, 4, 9), (4, 0, 8), (0, 1, 9)]),
1472+
(26.0, [(5, 4, 9), (4, 0, 8), (0, 3, 1), (3, 2, 4), (2, 1, 4)]),
14741473
(30.0, [(5, 4, 9), (4, 3, 3), (3, 2, 4), (2, 0, 5), (0, 1, 9)])]
14751474
sage: g = DiGraph(graphs.Grid2dGraph(2, 6).relabel(inplace=False))
14761475
sage: for u, v in g.edge_iterator(labels=False):
@@ -1479,15 +1478,22 @@ def pnc_k_shortest_simple_paths(self, source, target, weight_function=None,
14791478
[4.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 8.0, 8.0,
14801479
8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 10.0, 10.0, 10.0, 10.0]
14811480
1482-
Same tests as yen_k_shortest_simple_paths::
1481+
Same tests as ``yen_k_shortest_simple_paths``::
14831482
1484-
sage: g = DiGraph([(1, 2, 20), (1, 3, 10), (1, 4, 30), (2, 5, 20), (3, 5, 10), (4, 5, 30)])
14851483
sage: g = DiGraph([(1, 2, 1), (2, 3, 1), (3, 4, 1), (4, 5, 1),
14861484
....: (1, 7, 1), (7, 8, 1), (8, 5, 1), (1, 6, 1),
14871485
....: (6, 9, 1), (9, 5, 1), (4, 2, 1), (9, 3, 1),
14881486
....: (9, 10, 1), (10, 5, 1), (9, 11, 1), (11, 10, 1)])
14891487
sage: [w for w, P in pnc_k_shortest_simple_paths(g, 1, 5, by_weight=True, report_weight=True)]
14901488
[3.0, 3.0, 4.0, 4.0, 5.0, 5.0]
1489+
1490+
More tests::
1491+
1492+
sage: D = graphs.Grid2dGraph(5, 5).relabel(inplace=False).to_directed()
1493+
sage: A = [w for w, P in pnc_k_shortest_simple_paths(D, 0, 24, report_weight=True)]
1494+
sage: assert len(A) == 8512
1495+
sage: for i in range(len(A) - 1):
1496+
....: assert A[i] <= A[i + 1]
14911497
"""
14921498
if not self.is_directed():
14931499
raise ValueError("this algorithm works only for directed graphs")
@@ -1574,17 +1580,19 @@ def pnc_k_shortest_simple_paths(self, source, target, weight_function=None,
15741580
path = idx_to_path[path_idx]
15751581
del idx_to_path[path_idx]
15761582

1577-
# ancestor_idx_dict[v] := the first vertex reachable by edges of
1578-
# first shortest path tree from v
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]``.
15791586
ancestor_idx_dict = {v: i for i, v in enumerate(path)}
15801587

1581-
def ancestor_idx_func(v):
1582-
if v in ancestor_idx_dict:
1583-
return ancestor_idx_dict[v]
1588+
def ancestor_idx_func(v, t):
15841589
if v not in successor:
15851590
# target vertex is not reachable from v
15861591
return -1
1587-
ancestor_idx_dict[v] = ancestor_idx_func(successor[v])
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)
15881596
return ancestor_idx_dict[v]
15891597

15901598
if is_simple:
@@ -1601,12 +1609,12 @@ def pnc_k_shortest_simple_paths(self, source, target, weight_function=None,
16011609
yield P
16021610

16031611
# GET DEVIATION PATHS
1604-
original_cost = sidetrack_length(path[:dev_idx + 1])
1605-
for deviation_i in range(dev_idx, len(path) - 1):
1612+
original_cost = cost
1613+
for deviation_i in range(len(path) - 1, dev_idx - 1, -1):
16061614
for e in G.outgoing_edge_iterator(path[deviation_i]):
16071615
if e[1] in path[:deviation_i + 2]: # e[1] is red or e in path
16081616
continue
1609-
ancestor_idx = ancestor_idx_func(e[1])
1617+
ancestor_idx = ancestor_idx_func(e[1], deviation_i)
16101618
if ancestor_idx == -1:
16111619
continue
16121620
new_path = path[:deviation_i + 1] + tree_path(e[1])
@@ -1616,7 +1624,9 @@ def pnc_k_shortest_simple_paths(self, source, target, weight_function=None,
16161624
new_cost = original_cost + sidetrack_cost[(e[0], e[1])]
16171625
new_is_simple = ancestor_idx > deviation_i
16181626
candidate_paths.push(((-new_cost, new_is_simple), (new_path_idx, deviation_i + 1)))
1619-
original_cost += sidetrack_cost[(path[deviation_i], path[deviation_i + 1])]
1627+
if deviation_i == dev_idx:
1628+
continue
1629+
original_cost -= sidetrack_cost[(path[deviation_i - 1], path[deviation_i])]
16201630
else:
16211631
# get a path to target in G \ path[:dev_idx]
16221632
deviation = shortest_path_func(path[dev_idx], target,

0 commit comments

Comments
 (0)