Skip to content

Commit dfa5625

Browse files
committed
keep feng, pnc algorithm and minor fix
1 parent 845f7e3 commit dfa5625

File tree

1 file changed

+164
-32
lines changed

1 file changed

+164
-32
lines changed

src/sage/graphs/path_enumeration.pyx

Lines changed: 164 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ This module is meant for all functions related to path enumeration in graphs.
1313
:func:`all_paths` | Return the list of all paths between a pair of vertices.
1414
:func:`yen_k_shortest_simple_paths` | Return an iterator over the simple paths between a pair of vertices in increasing order of weights.
1515
:func:`nc_k_shortest_simple_paths` | Return an iterator over the simple paths between a pair of vertices in increasing order of weights.
16+
:func:`feng_k_shortest_simple_paths` | Return an iterator over the simple paths between a pair of vertices in increasing order of weights.
17+
:func:`pnc_k_shortest_simple_paths` | Return an iterator over the simple paths between a pair of vertices in increasing order of weights.
1618
:func:`all_paths_iterator` | Return an iterator over the paths of ``self``.
1719
:func:`all_simple_paths` | Return a list of all the simple paths of ``self`` starting with one of the given vertices.
1820
:func:`shortest_simple_paths` | Return an iterator over the simple paths between a pair of vertices.
@@ -566,34 +568,23 @@ def shortest_simple_paths(self, source, target, weight_function=None,
566568
if algorithm is None:
567569
algorithm = "Feng" if self.is_directed() else "Yen"
568570

569-
if algorithm == "Feng":
571+
if algorithm in ("Feng", "PNC"):
570572
if not self.is_directed():
571-
raise ValueError("Feng's algorithm works only for directed graphs")
573+
raise ValueError(f"{algorithm}'s algorithm works only for directed graphs")
572574

573575
yield from nc_k_shortest_simple_paths(self, source=source, target=target,
574576
weight_function=weight_function,
575577
by_weight=by_weight, check_weight=check_weight,
576578
report_edges=report_edges,
577579
labels=labels, report_weight=report_weight,
578-
algorithm="normal")
580+
postponed=algorithm == "PNC")
579581

580582
elif algorithm == "Yen":
581583
yield from yen_k_shortest_simple_paths(self, source=source, target=target,
582584
weight_function=weight_function,
583585
by_weight=by_weight, check_weight=check_weight,
584586
report_edges=report_edges,
585587
labels=labels, report_weight=report_weight)
586-
587-
elif algorithm == "PNC":
588-
if not self.is_directed():
589-
raise ValueError("PNC's algorithm works only for directed graphs")
590-
591-
yield from nc_k_shortest_simple_paths(self, source=source, target=target,
592-
weight_function=weight_function,
593-
by_weight=by_weight, check_weight=check_weight,
594-
report_edges=report_edges,
595-
labels=labels, report_weight=report_weight,
596-
algorithm="postponed")
597588
else:
598589
raise ValueError('unknown algorithm "{}"'.format(algorithm))
599590

@@ -901,7 +892,7 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
901892
by_weight=False, check_weight=True,
902893
report_edges=False,
903894
labels=False, report_weight=False,
904-
algorithm="normal"):
895+
postponed=False):
905896
r"""
906897
Return an iterator over the simple paths between a pair of vertices in
907898
increasing order of weights.
@@ -949,13 +940,13 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
949940
the path between ``source`` and ``target`` is returned. Otherwise a
950941
tuple of path length and path is returned.
951942
952-
- ``algorithm`` -- string (default: ``"normal"``); the algorithm to use.
953-
Possible values are ``"normal"`` and ``"postponed"``. See below for
954-
details.
943+
- ``postponed`` -- boolean (default: ``False``); if ``True``, the postponed
944+
node classification algorithm is used, otherwise the node classification
945+
algorithm is used. See below for details.
955946
956947
ALGORITHM:
957948
958-
- ``algorithm = "normal"``
949+
- ``postponed=False``
959950
This algorithm can be divided into two parts. Firstly, it determines the
960951
shortest path from ``source`` to ``target``. Then, it determines all the
961952
other `k`-shortest paths. This algorithm finds the deviations of previous
@@ -979,12 +970,12 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
979970
980971
See [Feng2014]_ for more details on this algorithm.
981972
982-
- ``algorithm = "postponed"``
973+
- ``postponed=True``
983974
This algorithm is based on the the above algorithm in [Feng2014]_, but
984975
postpones the shortest path tree computation when non-simple deviations
985976
occur. See Postponed Node Classification algorithm in [ACN2023]_ for the
986977
algorithm description. When not all simple paths are needed, this algorithm
987-
is more efficient than the normal algorithm.
978+
is more efficient than the algorithm for ``postponed=False``.
988979
989980
EXAMPLES::
990981
@@ -994,9 +985,9 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
994985
[(20.0, [1, 3, 5]), (40.0, [1, 2, 5]), (60.0, [1, 4, 5])]
995986
sage: list(nc_k_shortest_simple_paths(g, 1, 5, report_weight=True))
996987
[(2.0, [1, 2, 5]), (2.0, [1, 4, 5]), (2.0, [1, 3, 5])]
997-
sage: list(nc_k_shortest_simple_paths(g, 1, 5, by_weight=True, report_weight=True, algorithm="postponed"))
988+
sage: list(nc_k_shortest_simple_paths(g, 1, 5, by_weight=True, report_weight=True, postponed=True))
998989
[(20.0, [1, 3, 5]), (40.0, [1, 2, 5]), (60.0, [1, 4, 5])]
999-
sage: list(nc_k_shortest_simple_paths(g, 1, 5, report_weight=True, algorithm="postponed"))
990+
sage: list(nc_k_shortest_simple_paths(g, 1, 5, report_weight=True, postponed=True))
1000991
[(2.0, [1, 2, 5]), (2.0, [1, 4, 5]), (2.0, [1, 3, 5])]
1001992
1002993
sage: list(nc_k_shortest_simple_paths(g, 1, 1))
@@ -1113,15 +1104,15 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
11131104
(17.0, [(2, 0, 5), (0, 4, 2), (4, 3, 3), (3, 1, 7)]),
11141105
(18.0, [(2, 0, 5), (0, 3, 1), (3, 4, 2), (4, 1, 10)])]
11151106
1116-
The test when ``algorithm="postponed"``::
1107+
The test when ``postponed=True``::
11171108
11181109
sage: g = DiGraph([(0, 1, 9), (0, 3, 1), (0, 4, 2), (1, 6, 4),
11191110
....: (1, 7, 1), (2, 0, 5), (2, 1, 4), (2, 7, 1),
11201111
....: (3, 1, 7), (3, 2, 4), (3, 4, 2), (4, 0, 8),
11211112
....: (4, 1, 10), (4, 3, 3), (4, 7, 10), (5, 2, 5),
11221113
....: (5, 4, 9), (6, 2, 9)], weighted=True)
11231114
sage: list(nc_k_shortest_simple_paths(g, 5, 1, by_weight=True, report_weight=True,
1124-
....: labels=True, report_edges=True, algorithm="postponed"))
1115+
....: labels=True, report_edges=True, postponed=True))
11251116
[(9.0, [(5, 2, 5), (2, 1, 4)]),
11261117
(18.0, [(5, 2, 5), (2, 0, 5), (0, 3, 1), (3, 1, 7)]),
11271118
(19.0, [(5, 2, 5), (2, 0, 5), (0, 1, 9)]),
@@ -1138,7 +1129,7 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
11381129
sage: g = DiGraph(graphs.Grid2dGraph(2, 6).relabel(inplace=False))
11391130
sage: for u, v in g.edge_iterator(labels=False):
11401131
....: g.set_edge_label(u, v, 1)
1141-
sage: [w for w, P in nc_k_shortest_simple_paths(g, 5, 1, by_weight=True, report_weight=True, algorithm="postponed")]
1132+
sage: [w for w, P in nc_k_shortest_simple_paths(g, 5, 1, by_weight=True, report_weight=True, postponed=True)]
11421133
[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,
11431134
8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 10.0, 10.0, 10.0, 10.0]
11441135
@@ -1148,19 +1139,17 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
11481139
....: (1, 7, 1), (7, 8, 1), (8, 5, 1), (1, 6, 1),
11491140
....: (6, 9, 1), (9, 5, 1), (4, 2, 1), (9, 3, 1),
11501141
....: (9, 10, 1), (10, 5, 1), (9, 11, 1), (11, 10, 1)])
1151-
sage: [w for w, P in nc_k_shortest_simple_paths(g, 1, 5, by_weight=True, report_weight=True, algorithm="postponed")]
1142+
sage: [w for w, P in nc_k_shortest_simple_paths(g, 1, 5, by_weight=True, report_weight=True, postponed=True)]
11521143
[3.0, 3.0, 4.0, 4.0, 5.0, 5.0]
11531144
11541145
More tests::
11551146
11561147
sage: D = graphs.Grid2dGraph(5, 5).relabel(inplace=False).to_directed()
1157-
sage: A = [w for w, P in nc_k_shortest_simple_paths(D, 0, 24, report_weight=True, algorithm="postponed")]
1148+
sage: A = [w for w, P in nc_k_shortest_simple_paths(D, 0, 24, report_weight=True, postponed=True)]
11581149
sage: assert len(A) == 8512
11591150
sage: for i in range(len(A) - 1):
11601151
....: assert A[i] <= A[i + 1]
11611152
"""
1162-
if algorithm != "normal" and algorithm != "postponed":
1163-
raise ValueError("algorithm {} is unknown.".format(algorithm))
11641153
if not self.is_directed():
11651154
raise ValueError("this algorithm works only for directed graphs")
11661155

@@ -1305,7 +1294,9 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
13051294
# (i.e. real length = cost + shortest_path_length in T_0)
13061295
# this is used in the "postponed" algorithm
13071296
cdef priority_queue[pair[pair[double, bint], pair[int, int]]] candidate_paths2
1308-
if algorithm == "normal":
1297+
1298+
if not postponed:
1299+
13091300
candidate_paths1.push((0, (0, 0)))
13101301
while candidate_paths1.size():
13111302
negative_cost, (path_idx, dev_idx) = candidate_paths1.top()
@@ -1353,7 +1344,9 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
13531344
continue
13541345
original_cost -= sidetrack_cost[(path[deviation_i - 1], path[deviation_i])]
13551346
former_part.remove(path[deviation_i])
1356-
elif algorithm == "postponed":
1347+
1348+
else:
1349+
13571350
candidate_paths2.push(((0, True), (0, 0)))
13581351
while candidate_paths2.size():
13591352
(negative_cost, is_simple), (path_idx, dev_idx) = candidate_paths2.top()
@@ -1415,6 +1408,145 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
14151408
candidate_paths2.push(((-new_cost, True), (new_path_idx, dev_idx)))
14161409

14171410

1411+
def feng_k_shortest_simple_paths(self, source, target, weight_function=None,
1412+
by_weight=False, check_weight=True,
1413+
report_edges=False,
1414+
labels=False, report_weight=False):
1415+
r"""
1416+
Return an iterator over the simple paths between a pair of vertices in
1417+
increasing order of weights.
1418+
1419+
Works only for directed graphs.
1420+
1421+
For unweighted graphs, paths are returned in order of increasing number
1422+
of edges.
1423+
1424+
In case of weighted graphs, negative weights are not allowed.
1425+
1426+
If ``source`` is the same vertex as ``target``, then ``[[source]]`` is
1427+
returned -- a list containing the 1-vertex, 0-edge path ``source``.
1428+
1429+
The loops and the multiedges if present in the given graph are ignored and
1430+
only minimum of the edge labels is kept in case of multiedges.
1431+
1432+
INPUT:
1433+
1434+
- ``source`` -- a vertex of the graph, where to start
1435+
1436+
- ``target`` -- a vertex of the graph, where to end
1437+
1438+
- ``weight_function`` -- function (default: ``None``); a function that
1439+
takes as input an edge ``(u, v, l)`` and outputs its weight. If not
1440+
``None``, ``by_weight`` is automatically set to ``True``. If ``None``
1441+
and ``by_weight`` is ``True``, we use the edge label ``l`` as a
1442+
weight.
1443+
1444+
- ``by_weight`` -- boolean (default: ``False``); if ``True``, the edges
1445+
in the graph are weighted, otherwise all edges have weight 1
1446+
1447+
- ``check_weight`` -- boolean (default: ``True``); whether to check that
1448+
the ``weight_function`` outputs a number for each edge
1449+
1450+
- ``report_edges`` -- boolean (default: ``False``); whether to report
1451+
paths as list of vertices (default) or list of edges, if ``False``
1452+
then ``labels`` parameter is ignored
1453+
1454+
- ``labels`` -- boolean (default: ``False``); if ``False``, each edge
1455+
is simply a pair ``(u, v)`` of vertices. Otherwise a list of edges
1456+
along with its edge labels are used to represent the path.
1457+
1458+
- ``report_weight`` -- boolean (default: ``False``); if ``False``, just
1459+
the path between ``source`` and ``target`` is returned. Otherwise a
1460+
tuple of path length and path is returned.
1461+
1462+
ALGORITHM:
1463+
1464+
The same algorithm as :meth:`~sage.graphs.path_enumeration.nc_k_shortest_simple_paths`,
1465+
when ``postponed=False``.
1466+
1467+
EXAMPLES::
1468+
1469+
sage: from sage.graphs.path_enumeration import feng_k_shortest_simple_paths
1470+
sage: g = DiGraph([(1, 2, 20), (1, 3, 10), (1, 4, 30), (2, 5, 20), (3, 5, 10), (4, 5, 30)])
1471+
sage: list(feng_k_shortest_simple_paths(g, 1, 5, by_weight=True, report_weight=True))
1472+
[(20.0, [1, 3, 5]), (40.0, [1, 2, 5]), (60.0, [1, 4, 5])]
1473+
sage: list(feng_k_shortest_simple_paths(g, 1, 5, report_weight=True))
1474+
[(2.0, [1, 2, 5]), (2.0, [1, 4, 5]), (2.0, [1, 3, 5])]
1475+
"""
1476+
yield from nc_k_shortest_simple_paths(self, source, target, weight_function=weight_function,
1477+
by_weight=by_weight, check_weight=check_weight,
1478+
report_edges=report_edges, labels=labels,
1479+
report_weight=report_weight, postponed=False)
1480+
1481+
1482+
def pnc_k_shortest_simple_paths(self, source, target, weight_function=None,
1483+
by_weight=False, check_weight=True,
1484+
report_edges=False,
1485+
labels=False, report_weight=False):
1486+
r"""
1487+
Return an iterator over the simple paths between a pair of vertices in
1488+
increasing order of weights.
1489+
1490+
Works only for directed graphs.
1491+
1492+
In case of weighted graphs, negative weights are not allowed.
1493+
1494+
If ``source`` is the same vertex as ``target``, then ``[[source]]`` is
1495+
returned -- a list containing the 1-vertex, 0-edge path ``source``.
1496+
1497+
The loops and the multiedges if present in the given graph are ignored and
1498+
only minimum of the edge labels is kept in case of multiedges.
1499+
1500+
INPUT:
1501+
1502+
- ``source`` -- a vertex of the graph, where to start
1503+
1504+
- ``target`` -- a vertex of the graph, where to end
1505+
1506+
- ``weight_function`` -- function (default: ``None``); a function that
1507+
takes as input an edge ``(u, v, l)`` and outputs its weight. If not
1508+
``None``, ``by_weight`` is automatically set to ``True``. If ``None``
1509+
and ``by_weight`` is ``True``, we use the edge label ``l`` as a
1510+
weight.
1511+
1512+
- ``by_weight`` -- boolean (default: ``False``); if ``True``, the edges
1513+
in the graph are weighted, otherwise all edges have weight 1
1514+
1515+
- ``check_weight`` -- boolean (default: ``True``); whether to check that
1516+
the ``weight_function`` outputs a number for each edge
1517+
1518+
- ``report_edges`` -- boolean (default: ``False``); whether to report
1519+
paths as list of vertices (default) or list of edges, if ``False``
1520+
then ``labels`` parameter is ignored
1521+
1522+
- ``labels`` -- boolean (default: ``False``); if ``False``, each edge
1523+
is simply a pair ``(u, v)`` of vertices. Otherwise a list of edges
1524+
along with its edge labels are used to represent the path.
1525+
1526+
- ``report_weight`` -- boolean (default: ``False``); if ``False``, just
1527+
a path is returned. Otherwise a tuple of path length and path is
1528+
returned.
1529+
1530+
ALGORITHM:
1531+
1532+
The same algorithm as :meth:`~sage.graphs.path_enumeration.nc_k_shortest_simple_paths`,
1533+
when ``postponed=True``.
1534+
1535+
EXAMPLES::
1536+
1537+
sage: from sage.graphs.path_enumeration import pnc_k_shortest_simple_paths
1538+
sage: g = DiGraph([(1, 2, 20), (1, 3, 10), (1, 4, 30), (2, 5, 20), (3, 5, 10), (4, 5, 30)])
1539+
sage: list(pnc_k_shortest_simple_paths(g, 1, 5, by_weight=True, report_weight=True))
1540+
[(20.0, [1, 3, 5]), (40.0, [1, 2, 5]), (60.0, [1, 4, 5])]
1541+
sage: list(pnc_k_shortest_simple_paths(g, 1, 5, report_weight=True))
1542+
[(2.0, [1, 2, 5]), (2.0, [1, 4, 5]), (2.0, [1, 3, 5])]
1543+
"""
1544+
yield from nc_k_shortest_simple_paths(self, source, target, weight_function=weight_function,
1545+
by_weight=by_weight, check_weight=check_weight,
1546+
report_edges=report_edges, labels=labels,
1547+
report_weight=report_weight, postponed=True)
1548+
1549+
14181550
def _all_paths_iterator(self, vertex, ending_vertices=None,
14191551
simple=False, max_length=None, trivial=False,
14201552
use_multiedges=False, report_edges=False,

0 commit comments

Comments
 (0)