Skip to content

Commit a5f8082

Browse files
author
Release Manager
committed
sagemathgh-40547: Update NC k shortest simple path for Undirected graphs <!-- ^ Please provide a concise and informative title. --> <!-- ^ Don't put issue numbers in the title, do this in the PR description below. --> <!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method to calculate 1 + 2". --> <!-- v Describe your changes below in detail. --> <!-- v Why is this change required? What problem does it solve? --> <!-- v If this PR resolves an open issue, please link to it here. For example, "Fixes sagemath#12345". --> Update nc_k_shortest_simple_path algorithm for undirected graphs. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [ ] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - sagemath#12345: short description why this is a dependency --> <!-- - sagemath#34567: ... --> sagemath#40510 URL: sagemath#40547 Reported by: Yuta Inoue Reviewer(s): David Coudert
2 parents a7d331a + 7fb197d commit a5f8082

File tree

1 file changed

+36
-24
lines changed

1 file changed

+36
-24
lines changed

src/sage/graphs/path_enumeration.pyx

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -332,12 +332,13 @@ def shortest_simple_paths(self, source, target, weight_function=None,
332332
supported:
333333
334334
- ``'Yen'`` -- Yen's algorithm [Yen1970]_
335+
(:meth:`~sage.graphs.path_enumeration.yen_k_shortest_simple_paths`)
335336
336-
- ``'Feng'`` -- an improved version of Yen's algorithm but that works only
337-
for directed graphs [Feng2014]_
337+
- ``'Feng'`` -- an improved version of Yen's algorithm [Feng2014]_
338+
(:meth:`~sage.graphs.path_enumeration.feng_k_shortest_simple_paths`)
338339
339-
- ``'PNC'`` -- an improved version of Feng's algorithm. This also works only
340-
for directed graphs [ACN2023]_
340+
- ``'PNC'`` -- an improved version of Feng's algorithm [ACN2023]_
341+
(:meth:`~sage.graphs.path_enumeration.pnc_k_shortest_simple_paths`)
341342
342343
- ``report_edges`` -- boolean (default: ``False``); whether to report paths
343344
as list of vertices (default) or list of edges. When set to ``False``, the
@@ -471,13 +472,6 @@ def shortest_simple_paths(self, source, target, weight_function=None,
471472
('101', '011', 1),
472473
('011', '111', 1)])]
473474
474-
Feng's algorithm cannot be used on undirected graphs::
475-
476-
sage: list(graphs.PathGraph(2).shortest_simple_paths(0, 1, algorithm='Feng'))
477-
Traceback (most recent call last):
478-
...
479-
ValueError: Feng's algorithm works only for directed graphs
480-
481475
If the algorithm is not implemented::
482476
483477
sage: list(g.shortest_simple_paths(1, 5, algorithm='tip top'))
@@ -528,7 +522,7 @@ def shortest_simple_paths(self, source, target, weight_function=None,
528522
sage: s == t
529523
True
530524
531-
Check that "Yen" and "Feng" provide same results on random digraphs::
525+
Check that "Yen", "Feng" and "PNC" provide same results on random digraphs::
532526
533527
sage: G = digraphs.RandomDirectedGNP(30, .05)
534528
sage: while not G.is_strongly_connected():
@@ -546,6 +540,23 @@ def shortest_simple_paths(self, source, target, weight_function=None,
546540
....: raise ValueError(f"something goes wrong u={u}, v={v}, G={G.edges()}!")
547541
....: if i == 100:
548542
....: break
543+
544+
Check that "Yen", "Feng" and "PNC" provide same results on random undirected graphs::
545+
546+
sage: G = graphs.RandomGNP(30, .5)
547+
sage: for u, v in list(G.edges(labels=False, sort=False)):
548+
....: G.set_edge_label(u, v, randint(1, 10))
549+
sage: V = G.vertices(sort=False)
550+
sage: shuffle(V)
551+
sage: u, v = V[:2]
552+
sage: it_Y = G.shortest_simple_paths(u, v, by_weight=True, report_weight=True, algorithm='Yen')
553+
sage: it_F = G.shortest_simple_paths(u, v, by_weight=True, report_weight=True, algorithm='Feng')
554+
sage: it_P = G.shortest_simple_paths(u, v, by_weight=True, report_weight=True, algorithm='PNC')
555+
sage: for i, (y, f, p) in enumerate(zip(it_Y, it_F, it_P)):
556+
....: if y[0] != f[0] or y[0] != p[0]:
557+
....: raise ValueError(f"something goes wrong u={u}, v={v}, G={G.edges()}!")
558+
....: if i == 100:
559+
....: break
549560
"""
550561
if source not in self:
551562
raise ValueError("vertex '{}' is not in the graph".format(source))
@@ -569,9 +580,6 @@ def shortest_simple_paths(self, source, target, weight_function=None,
569580
algorithm = "Feng" if self.is_directed() else "Yen"
570581

571582
if algorithm in ("Feng", "PNC"):
572-
if not self.is_directed():
573-
raise ValueError(f"{algorithm}'s algorithm works only for directed graphs")
574-
575583
yield from nc_k_shortest_simple_paths(self, source=source, target=target,
576584
weight_function=weight_function,
577585
by_weight=by_weight, check_weight=check_weight,
@@ -897,8 +905,6 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
897905
Return an iterator over the simple paths between a pair of vertices in
898906
increasing order of weights.
899907
900-
Works only for directed graphs.
901-
902908
For unweighted graphs, paths are returned in order of increasing number
903909
of edges.
904910
@@ -1001,6 +1007,14 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
10011007
(40.0, [(1, 2, 20), (2, 5, 20)]),
10021008
(60.0, [(1, 4, 30), (4, 5, 30)])]
10031009
1010+
Algorithm works for undirected graphs as well::
1011+
1012+
sage: g = Graph([(1, 2, 20), (1, 3, 10), (1, 4, 30), (2, 5, 20), (3, 5, 10), (4, 5, 30)])
1013+
sage: list(nc_k_shortest_simple_paths(g, 5, 1, by_weight=True))
1014+
[[5, 3, 1], [5, 2, 1], [5, 4, 1]]
1015+
sage: [len(P) for P in nc_k_shortest_simple_paths(g, 5, 1)]
1016+
[3, 3, 3]
1017+
10041018
TESTS::
10051019
10061020
sage: from sage.graphs.path_enumeration import nc_k_shortest_simple_paths
@@ -1150,9 +1164,6 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
11501164
sage: for i in range(len(A) - 1):
11511165
....: assert A[i] <= A[i + 1]
11521166
"""
1153-
if not self.is_directed():
1154-
raise ValueError("this algorithm works only for directed graphs")
1155-
11561167
if source not in self:
11571168
raise ValueError("vertex '{}' is not in the graph".format(source))
11581169
if target not in self:
@@ -1164,6 +1175,11 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
11641175

11651176
if self.has_loops() or self.allows_multiple_edges():
11661177
G = self.to_simple(to_undirected=False, keep_label='min', immutable=False)
1178+
if not G.is_directed():
1179+
G = G.to_directed()
1180+
elif not self.is_directed():
1181+
# Turn the graph into a mutable directed graph
1182+
G = self.to_directed(data_structure='sparse')
11671183
else:
11681184
G = self.copy(immutable=False)
11691185

@@ -1416,8 +1432,6 @@ def feng_k_shortest_simple_paths(self, source, target, weight_function=None,
14161432
Return an iterator over the simple paths between a pair of vertices in
14171433
increasing order of weights.
14181434
1419-
Works only for directed graphs.
1420-
14211435
For unweighted graphs, paths are returned in order of increasing number
14221436
of edges.
14231437
@@ -1487,8 +1501,6 @@ def pnc_k_shortest_simple_paths(self, source, target, weight_function=None,
14871501
Return an iterator over the simple paths between a pair of vertices in
14881502
increasing order of weights.
14891503
1490-
Works only for directed graphs.
1491-
14921504
In case of weighted graphs, negative weights are not allowed.
14931505
14941506
If ``source`` is the same vertex as ``target``, then ``[[source]]`` is

0 commit comments

Comments
 (0)