@@ -13,6 +13,8 @@ This module is meant for all functions related to path enumeration in graphs.
13
13
:func:`all_paths` | Return the list of all paths between a pair of vertices.
14
14
:func:`yen_k_shortest_simple_paths` | Return an iterator over the simple paths between a pair of vertices in increasing order of weights.
15
15
: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.
16
18
:func:`all_paths_iterator` | Return an iterator over the paths of ``self``.
17
19
:func:`all_simple_paths` | Return a list of all the simple paths of ``self`` starting with one of the given vertices.
18
20
: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,
566
568
if algorithm is None :
567
569
algorithm = " Feng" if self .is_directed() else " Yen"
568
570
569
- if algorithm == " Feng" :
571
+ if algorithm in ( " Feng" , " PNC " ) :
570
572
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" )
572
574
573
575
yield from nc_k_shortest_simple_paths(self , source = source, target = target,
574
576
weight_function = weight_function,
575
577
by_weight = by_weight, check_weight = check_weight,
576
578
report_edges = report_edges,
577
579
labels = labels, report_weight = report_weight,
578
- algorithm = " normal " )
580
+ postponed = algorithm == " PNC " )
579
581
580
582
elif algorithm == " Yen" :
581
583
yield from yen_k_shortest_simple_paths(self , source = source, target = target,
582
584
weight_function = weight_function,
583
585
by_weight = by_weight, check_weight = check_weight,
584
586
report_edges = report_edges,
585
587
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" )
597
588
else :
598
589
raise ValueError (' unknown algorithm "{}"' .format(algorithm))
599
590
@@ -901,7 +892,7 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
901
892
by_weight = False , check_weight = True ,
902
893
report_edges = False ,
903
894
labels = False , report_weight = False ,
904
- algorithm = " normal " ):
895
+ postponed = False ):
905
896
r """
906
897
Return an iterator over the simple paths between a pair of vertices in
907
898
increasing order of weights.
@@ -949,13 +940,13 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
949
940
the path between ``source`` and ``target`` is returned. Otherwise a
950
941
tuple of path length and path is returned.
951
942
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.
955
946
956
947
ALGORITHM:
957
948
958
- - ``algorithm = "normal" ``
949
+ - ``postponed=False ``
959
950
This algorithm can be divided into two parts. Firstly, it determines the
960
951
shortest path from ``source`` to ``target``. Then, it determines all the
961
952
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,
979
970
980
971
See [Feng2014 ]_ for more details on this algorithm.
981
972
982
- - ``algorithm = " postponed" ``
973
+ - ``postponed=True ``
983
974
This algorithm is based on the the above algorithm in [Feng2014 ]_, but
984
975
postpones the shortest path tree computation when non-simple deviations
985
976
occur. See Postponed Node Classification algorithm in [ACN2023 ]_ for the
986
977
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`` .
988
979
989
980
EXAMPLES::
990
981
@@ -994,9 +985,9 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
994
985
[(20.0, [1, 3, 5 ]), ( 40. 0, [1, 2, 5 ]) , ( 60. 0, [1, 4, 5 ]) ]
995
986
sage: list( nc_k_shortest_simple_paths( g, 1, 5, report_weight=True))
996
987
[(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 ))
998
989
[(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 ))
1000
991
[(2.0, [1, 2, 5 ]), ( 2. 0, [1, 4, 5 ]) , ( 2. 0, [1, 3, 5 ]) ]
1001
992
1002
993
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,
1113
1104
( 17. 0, [(2, 0, 5), (0, 4, 2), (4, 3, 3), (3, 1, 7) ]) ,
1114
1105
( 18. 0, [(2, 0, 5), (0, 3, 1), (3, 4, 2), (4, 1, 10) ]) ]
1115
1106
1116
- The test when ``algorithm=" postponed" ``::
1107
+ The test when ``postponed=True ``::
1117
1108
1118
1109
sage: g = DiGraph( [(0, 1, 9), (0, 3, 1), (0, 4, 2), (1, 6, 4),
1119
1110
....: (1, 7, 1), (2, 0, 5), (2, 1, 4), (2, 7, 1),
1120
1111
....: (3, 1, 7), (3, 2, 4), (3, 4, 2), (4, 0, 8),
1121
1112
....: (4, 1, 10), (4, 3, 3), (4, 7, 10), (5, 2, 5),
1122
1113
....: (5, 4, 9), (6, 2, 9) ], weighted=True)
1123
1114
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 ))
1125
1116
[(9.0, [(5, 2, 5), (2, 1, 4) ]),
1126
1117
( 18. 0, [(5, 2, 5), (2, 0, 5), (0, 3, 1), (3, 1, 7) ]) ,
1127
1118
( 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,
1138
1129
sage: g = DiGraph( graphs. Grid2dGraph( 2, 6) . relabel( inplace=False))
1139
1130
sage: for u, v in g. edge_iterator( labels=False) :
1140
1131
.... : 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 ) ]
1142
1133
[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,
1143
1134
8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 10.0, 10.0, 10.0, 10.0 ]
1144
1135
@@ -1148,19 +1139,17 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
1148
1139
....: (1, 7, 1), (7, 8, 1), (8, 5, 1), (1, 6, 1),
1149
1140
....: (6, 9, 1), (9, 5, 1), (4, 2, 1), (9, 3, 1),
1150
1141
....: (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 ) ]
1152
1143
[3.0, 3.0, 4.0, 4.0, 5.0, 5.0 ]
1153
1144
1154
1145
More tests::
1155
1146
1156
1147
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 ) ]
1158
1149
sage: assert len( A) == 8512
1159
1150
sage: for i in range( len( A) - 1) :
1160
1151
.... : assert A[i ] <= A[i + 1 ]
1161
1152
"""
1162
- if algorithm != " normal" and algorithm != " postponed" :
1163
- raise ValueError (" algorithm {} is unknown." .format(algorithm))
1164
1153
if not self .is_directed():
1165
1154
raise ValueError (" this algorithm works only for directed graphs" )
1166
1155
@@ -1305,7 +1294,9 @@ def nc_k_shortest_simple_paths(self, source, target, weight_function=None,
1305
1294
# (i.e. real length = cost + shortest_path_length in T_0)
1306
1295
# this is used in the "postponed" algorithm
1307
1296
cdef priority_queue[pair[pair[double , bint], pair[int , int ]]] candidate_paths2
1308
- if algorithm == " normal" :
1297
+
1298
+ if not postponed:
1299
+
1309
1300
candidate_paths1.push((0 , (0 , 0 )))
1310
1301
while candidate_paths1.size():
1311
1302
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,
1353
1344
continue
1354
1345
original_cost -= sidetrack_cost[(path[deviation_i - 1 ], path[deviation_i])]
1355
1346
former_part.remove(path[deviation_i])
1356
- elif algorithm == " postponed" :
1347
+
1348
+ else :
1349
+
1357
1350
candidate_paths2.push(((0 , True ), (0 , 0 )))
1358
1351
while candidate_paths2.size():
1359
1352
(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,
1415
1408
candidate_paths2.push(((- new_cost, True ), (new_path_idx, dev_idx)))
1416
1409
1417
1410
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
+
1418
1550
def _all_paths_iterator (self , vertex , ending_vertices = None ,
1419
1551
simple = False , max_length = None , trivial = False ,
1420
1552
use_multiedges = False , report_edges = False ,
0 commit comments