From fe94c3f639ec2c57f82b78db806ea142bd4ef6ce Mon Sep 17 00:00:00 2001 From: dcoudert Date: Mon, 30 Dec 2024 17:42:57 +0100 Subject: [PATCH 1/5] bidirectional dijkstra using pairing heap --- src/sage/data_structures/pairing_heap.pxd | 1 + src/sage/graphs/base/c_graph.pyx | 224 +++++++++++++++++++++- src/sage/graphs/generic_graph.py | 2 +- 3 files changed, 216 insertions(+), 11 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pxd b/src/sage/data_structures/pairing_heap.pxd index d749cc7ec31..875e654c457 100644 --- a/src/sage/data_structures/pairing_heap.pxd +++ b/src/sage/data_structures/pairing_heap.pxd @@ -30,6 +30,7 @@ cdef extern from "./pairing_heap.h" namespace "pairing_heap": void decrease(TypeOfItem, TypeOfValue) except + bint contains(TypeOfItem) TypeOfValue value(TypeOfItem) except + + size_t size() cdef cppclass PairingHeapNodePy: PyObject * value # value associated with the item diff --git a/src/sage/graphs/base/c_graph.pyx b/src/sage/graphs/base/c_graph.pyx index 00a535c3335..f3db3dce905 100644 --- a/src/sage/graphs/base/c_graph.pyx +++ b/src/sage/graphs/base/c_graph.pyx @@ -52,6 +52,7 @@ from libcpp.pair cimport pair from sage.rings.integer_ring import ZZ from cysignals.memory cimport check_allocarray, sig_free from sage.data_structures.bitset cimport FrozenBitset +from sage.data_structures.pairing_heap cimport PairingHeap cdef extern from "Python.h": @@ -3868,7 +3869,7 @@ cdef class CGraphBackend(GenericGraphBackend): return shortest_path - def bidirectional_dijkstra(self, x, y, weight_function=None, + def bidirectional_dijkstra_old(self, x, y, weight_function=None, distance_flag=False): r""" Return the shortest path or distance from ``x`` to ``y`` using a @@ -3899,15 +3900,15 @@ cdef class CGraphBackend(GenericGraphBackend): sage: G = Graph(graphs.PetersenGraph()) sage: for (u, v) in G.edges(sort=True, labels=None): - ....: G.set_edge_label(u, v, 1) - sage: G.shortest_path(0, 1, by_weight=True) + ....: G.set_edge_label(u, v, 1) + sage: G._backend.bidirectional_dijkstra_old(0, 1) [0, 1] - sage: G.shortest_path_length(0, 1, by_weight=True) + sage: G._backend.bidirectional_dijkstra_old(0, 1, distance_flag=True) 1 sage: G = DiGraph([(1, 2, {'weight':1}), (1, 3, {'weight':5}), (2, 3, {'weight':1})]) - sage: G.shortest_path(1, 3, weight_function=lambda e:e[2]['weight']) + sage: G._backend.bidirectional_dijkstra_old(1, 3, weight_function=lambda e:e[2]['weight']) [1, 2, 3] - sage: G.shortest_path_length(1, 3, weight_function=lambda e:e[2]['weight']) + sage: G._backend.bidirectional_dijkstra_old(1, 3, weight_function=lambda e:e[2]['weight'], distance_flag=True) 2 TESTS: @@ -3915,21 +3916,21 @@ cdef class CGraphBackend(GenericGraphBackend): Bugfix from :issue:`7673` :: sage: G = Graph([(0, 1, 9), (0, 2, 8), (1, 2, 7)]) - sage: G.shortest_path_length(0, 1, by_weight=True) + sage: G._backend.bidirectional_dijkstra_old(0, 1, distance_flag=True) 9 Bugfix from :issue:`28221` :: sage: G = Graph([(0, 1, 9.2), (0, 2, 4.5), (1, 2, 4.6)]) - sage: G.shortest_path_length(0, 1, by_weight=True) + sage: G._backend.bidirectional_dijkstra_old(0, 1, distance_flag=True) 9.1 Bugfix from :issue:`27464` :: sage: G = DiGraph({0: [1, 2], 1: [4], 2: [3, 4], 4: [5], 5: [6]}, multiedges=True) sage: for u, v in list(G.edges(labels=None, sort=False)): - ....: G.set_edge_label(u, v, 1) - sage: G.distance(0, 5, by_weight=true) + ....: G.set_edge_label(u, v, 1) + sage: G._backend.bidirectional_dijkstra_old(0, 5, distance_flag=true) 3 """ if x == y: @@ -4067,6 +4068,209 @@ cdef class CGraphBackend(GenericGraphBackend): return shortest_path + def bidirectional_dijkstra(self, x, y, weight_function=None, + distance_flag=False): + r""" + Return the shortest path or distance from ``x`` to ``y`` using a + bidirectional version of Dijkstra's algorithm. + + INPUT: + + - ``x`` -- the starting vertex in the shortest path from ``x`` to ``y`` + + - ``y`` -- the end vertex in the shortest path from ``x`` to ``y`` + + - ``weight_function`` -- function (default: ``None``); a function that + inputs an edge ``(u, v, l)`` and outputs its weight. If ``None``, we + use the edge label ``l`` as a weight, if ``l`` is not ``None``, else + ``1`` as a weight. + + - ``distance_flag`` -- boolean (default: ``False``); when set to + ``True``, the shortest path distance from ``x`` to ``y`` is returned + instead of the path. + + OUTPUT: + + - A list of vertices in the shortest path from ``x`` to ``y`` or + distance from ``x`` to ``y`` is returned depending upon the value of + parameter ``distance_flag`` + + EXAMPLES:: + + sage: G = Graph(graphs.PetersenGraph()) + sage: for (u, v) in G.edges(sort=True, labels=None): + ....: G.set_edge_label(u, v, 1) + sage: G.shortest_path(0, 1, by_weight=True) + [0, 1] + sage: G.shortest_path_length(0, 1, by_weight=True) + 1 + sage: G = DiGraph([(1, 2, {'weight':1}), (1, 3, {'weight':5}), (2, 3, {'weight':1})]) + sage: G.shortest_path(1, 3, weight_function=lambda e:e[2]['weight']) + [1, 2, 3] + sage: G.shortest_path_length(1, 3, weight_function=lambda e:e[2]['weight']) + 2 + + TESTS: + + Bugfix from :issue:`7673` :: + + sage: G = Graph([(0, 1, 9), (0, 2, 8), (1, 2, 7)]) + sage: G.shortest_path_length(0, 1, by_weight=True) + 9 + + Bugfix from :issue:`28221` :: + + sage: G = Graph([(0, 1, 9.2), (0, 2, 4.5), (1, 2, 4.6)]) + sage: G.shortest_path_length(0, 1, by_weight=True) + 9.1 + + Bugfix from :issue:`27464` :: + + sage: G = DiGraph({0: [1, 2], 1: [4], 2: [3, 4], 4: [5], 5: [6]}, multiedges=True) + sage: for u, v in list(G.edges(labels=None, sort=False)): + ....: G.set_edge_label(u, v, 1) + sage: G.distance(0, 5, by_weight=true) + 3 + """ + if x == y: + if distance_flag: + return 0 + else: + return [x] + + # As for shortest_path, the roles of x and y are symmetric, hence we + # define dictionaries like pred_current and pred_other, which + # represent alternatively pred_x or pred_y according to the side + # studied. + cdef int x_int = self.get_vertex(x) + cdef int y_int = self.get_vertex(y) + cdef int v = 0 + cdef int w = 0 + cdef int pred + cdef int side + cdef double distance + + # Each vertex knows its predecessors in the search, for each side + cdef dict pred_x = {} + cdef dict pred_y = {} + cdef dict pred_current + + # Stores the distances from x and y + cdef dict dist_x = {} + cdef dict dist_y = {} + cdef dict dist_current + cdef dict dist_other + + # We use 2 min-heap data structures (pairing heaps), one for the + # exploration from x and the other for the reverse exploration to y. + # Each heap associates to a vertex a pair (distance, pred). + cdef PairingHeap[int, pair[double, int]] px = PairingHeap[int, pair[double, int]]() + cdef PairingHeap[int, pair[double, int]] py = PairingHeap[int, pair[double, int]]() + cdef PairingHeap[int, pair[double, int]] * ptmp + px.push(x_int, (0, x_int)) + py.push(y_int, (0, y_int)) + + cdef list neighbors + + # Meeting_vertex is a vertex discovered through x and through y + # which defines the shortest path found + # (of length shortest_path_length). + cdef int meeting_vertex = -1 + cdef double shortest_path_length + cdef double f_tmp + + if weight_function is None: + def weight_function(e): + return 1 if e[2] is None else e[2] + + # As long as the current side (x or y) is not totally explored ... + while not (px.empty() and py.empty()): + if (px.empty() or + (not py.empty() and px.top_value().first > py.top_value().first)): + side = -1 + ptmp = &py + else: # px is not empty + side = 1 + ptmp = &px + v, (distance, pred) = ptmp.top() + if meeting_vertex != -1 and distance > shortest_path_length: + break + ptmp.pop() + + if side == 1: + dist_current, dist_other = dist_x, dist_y + pred_current = pred_x + neighbors = self.cg().out_neighbors(v) + else: + dist_current, dist_other = dist_y, dist_x + pred_current = pred_y + neighbors = self.cg().in_neighbors(v) + + dist_current[v] = distance + if not distance_flag: + pred_current[v] = pred + + if v in dist_other: + f_tmp = distance + dist_other[v] + if meeting_vertex == -1 or f_tmp < shortest_path_length: + meeting_vertex = v + shortest_path_length = f_tmp + + for w in neighbors: + # If w has not yet been extracted from the heap, we check if we + # can improve its path + if w not in dist_current: + v_obj = self.vertex_label(v) + w_obj = self.vertex_label(w) + if side == -1: + v_obj, w_obj = w_obj, v_obj + if self._multiple_edges: + edge_label = min(weight_function((v_obj, w_obj, l)) for l in self.get_edge_label(v_obj, w_obj)) + else: + edge_label = weight_function((v_obj, w_obj, self.get_edge_label(v_obj, w_obj))) + if edge_label < 0: + raise ValueError("the graph contains an edge with negative weight") + f_tmp = distance + edge_label + if ptmp.contains(w): + if ptmp.value(w).first > f_tmp: + ptmp.decrease(w, (f_tmp, v)) + else: + ptmp.push(w, (f_tmp, v)) + + # No meeting point has been found + if meeting_vertex == -1: + if distance_flag: + from sage.rings.infinity import Infinity + return Infinity + return [] + + if distance_flag: + if shortest_path_length in ZZ: + return int(shortest_path_length) + return shortest_path_length + + # build the shortest path and returns it. + cdef list shortest_path = [] + w = meeting_vertex + while w != x_int: + shortest_path.append(self.vertex_label(w)) + w = pred_x[w] + + shortest_path.append(x) + shortest_path.reverse() + + if meeting_vertex == y_int: + return shortest_path + + w = pred_y[meeting_vertex] + while w != y_int: + shortest_path.append(self.vertex_label(w)) + w = pred_y[w] + + shortest_path.append(y) + + return shortest_path + def shortest_path_all_vertices(self, v, cutoff=None, distance_flag=False): r""" diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 101952109c3..38b6b816134 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -17338,7 +17338,7 @@ def shortest_path(self, u, v, by_weight=False, algorithm=None, sage: D.shortest_path(4, 8, algorithm='Dijkstra_Bid_NetworkX') # needs networkx [4, 3, 2, 1, 8] sage: D.shortest_path(4, 9, algorithm='Dijkstra_Bid') - [4, 3, 19, 0, 10, 9] + [4, 3, 2, 1, 8, 9] sage: D.shortest_path(5, 5) [5] sage: D.delete_edges(D.edges_incident(13)) From 13c7a89d6eef9d04ce54fffc8d2c3f5863765a51 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Fri, 10 Jan 2025 15:36:34 +0100 Subject: [PATCH 2/5] #39228: remove old method --- src/sage/graphs/base/c_graph.pyx | 199 ------------------------------- 1 file changed, 199 deletions(-) diff --git a/src/sage/graphs/base/c_graph.pyx b/src/sage/graphs/base/c_graph.pyx index f3db3dce905..46464a9835f 100644 --- a/src/sage/graphs/base/c_graph.pyx +++ b/src/sage/graphs/base/c_graph.pyx @@ -3869,205 +3869,6 @@ cdef class CGraphBackend(GenericGraphBackend): return shortest_path - def bidirectional_dijkstra_old(self, x, y, weight_function=None, - distance_flag=False): - r""" - Return the shortest path or distance from ``x`` to ``y`` using a - bidirectional version of Dijkstra's algorithm. - - INPUT: - - - ``x`` -- the starting vertex in the shortest path from ``x`` to ``y`` - - - ``y`` -- the end vertex in the shortest path from ``x`` to ``y`` - - - ``weight_function`` -- function (default: ``None``); a function that - inputs an edge ``(u, v, l)`` and outputs its weight. If ``None``, we - use the edge label ``l`` as a weight, if ``l`` is not ``None``, else - ``1`` as a weight. - - - ``distance_flag`` -- boolean (default: ``False``); when set to - ``True``, the shortest path distance from ``x`` to ``y`` is returned - instead of the path. - - OUTPUT: - - - A list of vertices in the shortest path from ``x`` to ``y`` or - distance from ``x`` to ``y`` is returned depending upon the value of - parameter ``distance_flag`` - - EXAMPLES:: - - sage: G = Graph(graphs.PetersenGraph()) - sage: for (u, v) in G.edges(sort=True, labels=None): - ....: G.set_edge_label(u, v, 1) - sage: G._backend.bidirectional_dijkstra_old(0, 1) - [0, 1] - sage: G._backend.bidirectional_dijkstra_old(0, 1, distance_flag=True) - 1 - sage: G = DiGraph([(1, 2, {'weight':1}), (1, 3, {'weight':5}), (2, 3, {'weight':1})]) - sage: G._backend.bidirectional_dijkstra_old(1, 3, weight_function=lambda e:e[2]['weight']) - [1, 2, 3] - sage: G._backend.bidirectional_dijkstra_old(1, 3, weight_function=lambda e:e[2]['weight'], distance_flag=True) - 2 - - TESTS: - - Bugfix from :issue:`7673` :: - - sage: G = Graph([(0, 1, 9), (0, 2, 8), (1, 2, 7)]) - sage: G._backend.bidirectional_dijkstra_old(0, 1, distance_flag=True) - 9 - - Bugfix from :issue:`28221` :: - - sage: G = Graph([(0, 1, 9.2), (0, 2, 4.5), (1, 2, 4.6)]) - sage: G._backend.bidirectional_dijkstra_old(0, 1, distance_flag=True) - 9.1 - - Bugfix from :issue:`27464` :: - - sage: G = DiGraph({0: [1, 2], 1: [4], 2: [3, 4], 4: [5], 5: [6]}, multiedges=True) - sage: for u, v in list(G.edges(labels=None, sort=False)): - ....: G.set_edge_label(u, v, 1) - sage: G._backend.bidirectional_dijkstra_old(0, 5, distance_flag=true) - 3 - """ - if x == y: - if distance_flag: - return 0 - else: - return [x] - - # As for shortest_path, the roles of x and y are symmetric, hence we - # define dictionaries like pred_current and pred_other, which - # represent alternatively pred_x or pred_y according to the side - # studied. - cdef int x_int = self.get_vertex(x) - cdef int y_int = self.get_vertex(y) - cdef int v = 0 - cdef int w = 0 - cdef int pred - cdef int side - cdef double distance - - # Each vertex knows its predecessors in the search, for each side - cdef dict pred_x = {} - cdef dict pred_y = {} - cdef dict pred_current - cdef dict pred_other - - # Stores the distances from x and y - cdef dict dist_x = {} - cdef dict dist_y = {} - cdef dict dist_current - cdef dict dist_other - - # Lists of vertices who are left to be explored. They are represented - # as pairs of pair and pair: ((distance, side), (predecessor, name)). - # 1 indicates x's side, -1 indicates y's, the distance being - # defined relatively. - cdef priority_queue[pair[pair[double, int], pair[int, int]]] pq - pq.push(((0, 1), (x_int, x_int))) - pq.push(((0, -1), (y_int, y_int))) - cdef list neighbors - - cdef list shortest_path = [] - - # Meeting_vertex is a vertex discovered through x and through y - # which defines the shortest path found - # (of length shortest_path_length). - cdef int meeting_vertex = -1 - - if weight_function is None: - def weight_function(e): - return 1 if e[2] is None else e[2] - - # As long as the current side (x or y) is not totally explored ... - while not pq.empty(): - (distance, side), (pred, v) = pq.top() - # priority_queue by default is max heap - # negative value of distance is stored in priority_queue to get - # minimum distance - distance = -distance - pq.pop() - if meeting_vertex != -1 and distance > shortest_path_length: - break - - if side == 1: - dist_current, dist_other = dist_x, dist_y - pred_current, pred_other = pred_x, pred_y - else: - dist_current, dist_other = dist_y, dist_x - pred_current, pred_other = pred_y, pred_x - - if v not in dist_current: - if not distance_flag: - pred_current[v] = pred - dist_current[v] = distance - - if v in dist_other: - f_tmp = distance + dist_other[v] - if meeting_vertex == -1 or f_tmp < shortest_path_length: - meeting_vertex = v - shortest_path_length = f_tmp - - if side == 1: - neighbors = self.cg().out_neighbors(v) - else: - neighbors = self.cg().in_neighbors(v) - for w in neighbors: - # If the neighbor is new, adds its non-found neighbors to - # the queue. - if w not in dist_current: - v_obj = self.vertex_label(v) - w_obj = self.vertex_label(w) - if side == -1: - v_obj, w_obj = w_obj, v_obj - if self._multiple_edges: - edge_label = min(weight_function((v_obj, w_obj, l)) for l in self.get_edge_label(v_obj, w_obj)) - else: - edge_label = weight_function((v_obj, w_obj, self.get_edge_label(v_obj, w_obj))) - if edge_label < 0: - raise ValueError("the graph contains an edge with negative weight") - # priority_queue is by default max_heap - # negative value of distance + edge_label is stored in - # priority_queue to get minimum distance - pq.push(((-(distance + edge_label), side), (v, w))) - - # No meeting point has been found - if meeting_vertex == -1: - if distance_flag: - from sage.rings.infinity import Infinity - return Infinity - return [] - else: - # build the shortest path and returns it. - if distance_flag: - if shortest_path_length in ZZ: - return int(shortest_path_length) - else: - return shortest_path_length - w = meeting_vertex - - while w != x_int: - shortest_path.append(self.vertex_label(w)) - w = pred_x[w] - - shortest_path.append(x) - shortest_path.reverse() - - if meeting_vertex == y_int: - return shortest_path - - w = pred_y[meeting_vertex] - while w != y_int: - shortest_path.append(self.vertex_label(w)) - w = pred_y[w] - shortest_path.append(y) - - return shortest_path - def bidirectional_dijkstra(self, x, y, weight_function=None, distance_flag=False): r""" From 0c35641da9d3e8fe44086a439ca05a12af9f615a Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 11 Jan 2025 09:15:07 +0100 Subject: [PATCH 3/5] #39228: use pairing heap in bidirectional_dijkstra_special --- src/sage/graphs/base/c_graph.pyx | 183 +++++++++++++-------------- src/sage/graphs/path_enumeration.pyx | 50 ++++---- 2 files changed, 114 insertions(+), 119 deletions(-) diff --git a/src/sage/graphs/base/c_graph.pyx b/src/sage/graphs/base/c_graph.pyx index 46464a9835f..953c3983f2d 100644 --- a/src/sage/graphs/base/c_graph.pyx +++ b/src/sage/graphs/base/c_graph.pyx @@ -46,7 +46,7 @@ method :meth:`realloc `. from sage.data_structures.bitset_base cimport * from sage.rings.integer cimport smallInteger from sage.arith.long cimport pyobject_to_long -from libcpp.queue cimport priority_queue, queue +from libcpp.queue cimport queue from libcpp.stack cimport stack from libcpp.pair cimport pair from sage.rings.integer_ring import ZZ @@ -3738,7 +3738,6 @@ cdef class CGraphBackend(GenericGraphBackend): cdef dict pred_x = {} cdef dict pred_y = {} cdef dict pred_current - cdef dict pred_other # Stores the distances from x and y cdef dict dist_x = {} @@ -3746,95 +3745,90 @@ cdef class CGraphBackend(GenericGraphBackend): cdef dict dist_current cdef dict dist_other - # Lists of vertices who are left to be explored. They are represented - # as pairs of pair and pair: ((distance, side), (predecessor, name)). - # 1 indicates x's side, -1 indicates y's, the distance being - # defined relatively. - cdef priority_queue[pair[pair[double, int], pair[int, int]]] pq - pq.push(((0, 1), (x_int, x_int))) - pq.push(((0, -1), (y_int, y_int))) - cdef list neighbors - - cdef list shortest_path = [] + # We use 2 min-heap data structures (pairing heaps), one for the + # exploration from x and the other for the reverse exploration to y. + # Each heap associates to a vertex a pair (distance, pred). + cdef PairingHeap[int, pair[double, int]] px = PairingHeap[int, pair[double, int]]() + cdef PairingHeap[int, pair[double, int]] py = PairingHeap[int, pair[double, int]]() + cdef PairingHeap[int, pair[double, int]] * ptmp + px.push(x_int, (0, x_int)) + py.push(y_int, (0, y_int)) # Meeting_vertex is a vertex discovered through x and through y # which defines the shortest path found # (of length shortest_path_length). cdef int meeting_vertex = -1 + cdef double shortest_path_length + cdef double f_tmp if reduced_weight is not None: def weight_function(e): return reduced_weight[(e[0], e[1])] # As long as the current side (x or y) is not totally explored ... - while not pq.empty(): - (distance, side), (pred, v) = pq.top() - # priority_queue by default is max heap - # negative value of distance is stored in priority_queue to get - # minimum distance - distance = -distance - pq.pop() + while not (px.empty() and py.empty()): + if (px.empty() or + (not py.empty() and px.top_value().first > py.top_value().first)): + side = -1 + ptmp = &py + else: # px is not empty + side = 1 + ptmp = &px + v, (distance, pred) = ptmp.top() if meeting_vertex != -1 and distance > shortest_path_length: break + ptmp.pop() if side == 1: dist_current, dist_other = dist_x, dist_y - pred_current, pred_other = pred_x, pred_y + pred_current = pred_x + nbr_iter = self.cg().out_neighbors(v) else: dist_current, dist_other = dist_y, dist_x - pred_current, pred_other = pred_y, pred_x - - if v not in dist_current: - if not distance_flag: - pred_current[v] = pred - dist_current[v] = distance - - if v in dist_other: - f_tmp = distance + dist_other[v] - if meeting_vertex == -1 or f_tmp < shortest_path_length: - meeting_vertex = v - shortest_path_length = f_tmp - if side == 1: - nbr = self.cg().out_neighbors(v) - else: - nbr = self.cg().in_neighbors(v) + pred_current = pred_y + nbr_iter = self.cg().in_neighbors(v) - if not exclude_e and not exclude_v: - neighbors = [] - for n in nbr: - if include_v and n not in include_vertices_int: - continue - neighbors.append(n) - else: - neighbors = [] - for w in nbr: - if exclude_v and w in exclude_vertices_int: - continue - if (exclude_e and - ((side == 1 and (v, w) in exclude_edges_int) or - (side == -1 and (w, v) in exclude_edges_int))): - continue - if include_v and w not in include_vertices_int: - continue - neighbors.append(w) - for w in neighbors: - # If the neighbor is new, adds its non-found neighbors to - # the queue. - if w not in dist_current: - v_obj = self.vertex_label(v) - w_obj = self.vertex_label(w) - if side == -1: - v_obj, w_obj = w_obj, v_obj - if self._multiple_edges: - edge_label = min(weight_function((v_obj, w_obj, l)) for l in self.get_edge_label(v_obj, w_obj)) - else: - edge_label = weight_function((v_obj, w_obj, self.get_edge_label(v_obj, w_obj))) - if edge_label < 0: - raise ValueError("the graph contains an edge with negative weight") - # priority_queue is by default max_heap - # negative value of distance + edge_label is stored in - # priority_queue to get minimum distance - pq.push(((-(distance + edge_label), side), (v, w))) + dist_current[v] = distance + if not distance_flag: + pred_current[v] = pred + + if v in dist_other: + f_tmp = distance + dist_other[v] + if meeting_vertex == -1 or f_tmp < shortest_path_length: + meeting_vertex = v + shortest_path_length = f_tmp + + if not exclude_e and not exclude_v: + neighbors = (w for w in nbr_iter + if not include_v or w in include_vertices_int) + else: + neighbors = (w for w in nbr_iter + if ((not exclude_v or w not in exclude_vertices_int) and + (not exclude_e or + ((side == 1 and (v, w) not in exclude_edges_int) or + (side == -1 and (w, v) not in exclude_edges_int))) and + (not include_v or w in include_vertices_int))) + + for w in neighbors: + # If w has not yet been extracted from the heap, we check if we + # can improve its path + if w not in dist_current: + v_obj = self.vertex_label(v) + w_obj = self.vertex_label(w) + if side == -1: + v_obj, w_obj = w_obj, v_obj + if self._multiple_edges: + edge_label = min(weight_function((v_obj, w_obj, l)) for l in self.get_edge_label(v_obj, w_obj)) + else: + edge_label = weight_function((v_obj, w_obj, self.get_edge_label(v_obj, w_obj))) + if edge_label < 0: + raise ValueError("the graph contains an edge with negative weight") + f_tmp = distance + edge_label + if ptmp.contains(w): + if ptmp.value(w).first > f_tmp: + ptmp.decrease(w, (f_tmp, v)) + else: + ptmp.push(w, (f_tmp, v)) # No meeting point has been found if meeting_vertex == -1: @@ -3842,32 +3836,33 @@ cdef class CGraphBackend(GenericGraphBackend): from sage.rings.infinity import Infinity return Infinity return [] - else: - # build the shortest path and returns it. - if distance_flag: - if shortest_path_length in ZZ: - return int(shortest_path_length) - else: - return shortest_path_length - w = meeting_vertex - while w != x_int: - shortest_path.append(self.vertex_label(w)) - w = pred_x[w] + if distance_flag: + if shortest_path_length in ZZ: + return int(shortest_path_length) + return shortest_path_length - shortest_path.append(x) - shortest_path.reverse() + # build the shortest path and return it. + cdef list shortest_path = [] + w = meeting_vertex + while w != x_int: + shortest_path.append(self.vertex_label(w)) + w = pred_x[w] + + shortest_path.append(x) + shortest_path.reverse() - if meeting_vertex == y_int: - return shortest_path + if meeting_vertex == y_int: + return shortest_path - w = pred_y[meeting_vertex] - while w != y_int: - shortest_path.append(self.vertex_label(w)) - w = pred_y[w] - shortest_path.append(y) + w = pred_y[meeting_vertex] + while w != y_int: + shortest_path.append(self.vertex_label(w)) + w = pred_y[w] - return shortest_path + shortest_path.append(y) + + return shortest_path def bidirectional_dijkstra(self, x, y, weight_function=None, distance_flag=False): @@ -4050,7 +4045,7 @@ cdef class CGraphBackend(GenericGraphBackend): return int(shortest_path_length) return shortest_path_length - # build the shortest path and returns it. + # build the shortest path and return it. cdef list shortest_path = [] w = meeting_vertex while w != x_int: diff --git a/src/sage/graphs/path_enumeration.pyx b/src/sage/graphs/path_enumeration.pyx index 50b992692e4..aa16b1dc7b1 100644 --- a/src/sage/graphs/path_enumeration.pyx +++ b/src/sage/graphs/path_enumeration.pyx @@ -361,7 +361,7 @@ def shortest_simple_paths(self, source, target, weight_function=None, ....: report_edges=True, report_weight=True)) [(20, [(1, 3), (3, 5)]), (40, [(1, 2), (2, 5)]), (60, [(1, 4), (4, 5)])] sage: list(g.shortest_simple_paths(1, 5, report_edges=True, report_weight=True)) - [(2, [(1, 4), (4, 5)]), (2, [(1, 3), (3, 5)]), (2, [(1, 2), (2, 5)])] + [(2, [(1, 2), (2, 5)]), (2, [(1, 3), (3, 5)]), (2, [(1, 4), (4, 5)])] sage: list(g.shortest_simple_paths(1, 5, by_weight=True, report_edges=True)) [[(1, 3), (3, 5)], [(1, 2), (2, 5)], [(1, 4), (4, 5)]] sage: list(g.shortest_simple_paths(1, 5, by_weight=True, algorithm='Feng', @@ -432,12 +432,12 @@ def shortest_simple_paths(self, source, target, weight_function=None, ....: (6, 9, 1), (9, 5, 1), (4, 2, 1), (9, 3, 1), ....: (9, 10, 1), (10, 5, 1), (9, 11, 1), (11, 10, 1)]) sage: list(g.shortest_simple_paths(1, 5, algorithm='Feng')) - [[1, 7, 8, 5], - [1, 6, 9, 5], - [1, 6, 9, 10, 5], + [[1, 6, 9, 5], + [1, 7, 8, 5], [1, 2, 3, 4, 5], - [1, 6, 9, 3, 4, 5], - [1, 6, 9, 11, 10, 5]] + [1, 6, 9, 10, 5], + [1, 6, 9, 11, 10, 5], + [1, 6, 9, 3, 4, 5]] sage: # needs sage.combinat sage: G = digraphs.DeBruijn(2, 3) @@ -957,11 +957,11 @@ def feng_k_shortest_simple_paths(self, source, target, weight_function=None, sage: list(feng_k_shortest_simple_paths(g, 1, 5, by_weight=True)) [[1, 3, 5], [1, 2, 5], [1, 4, 5]] sage: list(feng_k_shortest_simple_paths(g, 1, 5)) - [[1, 4, 5], [1, 3, 5], [1, 2, 5]] + [[1, 2, 5], [1, 3, 5], [1, 4, 5]] sage: list(feng_k_shortest_simple_paths(g, 1, 1)) [[1]] sage: list(feng_k_shortest_simple_paths(g, 1, 5, report_edges=True, labels=True)) - [[(1, 4, 30), (4, 5, 30)], [(1, 3, 10), (3, 5, 10)], [(1, 2, 20), (2, 5, 20)]] + [[(1, 2, 20), (2, 5, 20)], [(1, 3, 10), (3, 5, 10)], [(1, 4, 30), (4, 5, 30)]] sage: list(feng_k_shortest_simple_paths(g, 1, 5, report_edges=True, labels=True, by_weight=True)) [[(1, 3, 10), (3, 5, 10)], [(1, 2, 20), (2, 5, 20)], [(1, 4, 30), (4, 5, 30)]] sage: list(feng_k_shortest_simple_paths(g, 1, 5, report_edges=True, labels=True, by_weight=True, report_weight=True)) @@ -974,7 +974,7 @@ def feng_k_shortest_simple_paths(self, source, target, weight_function=None, sage: list(feng_k_shortest_simple_paths(g, 1, 6, by_weight = True)) [[1, 3, 5, 6], [1, 2, 5, 6], [1, 4, 5, 6], [1, 6]] sage: list(feng_k_shortest_simple_paths(g, 1, 6)) - [[1, 6], [1, 4, 5, 6], [1, 3, 5, 6], [1, 2, 5, 6]] + [[1, 6], [1, 2, 5, 6], [1, 3, 5, 6], [1, 4, 5, 6]] sage: list(feng_k_shortest_simple_paths(g, 1, 6, report_edges=True, labels=True, by_weight=True, report_weight=True)) [(25, [(1, 3, 10), (3, 5, 10), (5, 6, 5)]), (45, [(1, 2, 20), (2, 5, 20), (5, 6, 5)]), @@ -982,9 +982,9 @@ def feng_k_shortest_simple_paths(self, source, target, weight_function=None, (100, [(1, 6, 100)])] sage: list(feng_k_shortest_simple_paths(g, 1, 6, report_edges=True, labels=True, report_weight=True)) [(1, [(1, 6, 100)]), - (3, [(1, 4, 30), (4, 5, 30), (5, 6, 5)]), + (3, [(1, 2, 20), (2, 5, 20), (5, 6, 5)]), (3, [(1, 3, 10), (3, 5, 10), (5, 6, 5)]), - (3, [(1, 2, 20), (2, 5, 20), (5, 6, 5)])] + (3, [(1, 4, 30), (4, 5, 30), (5, 6, 5)])] sage: from sage.graphs.path_enumeration import feng_k_shortest_simple_paths sage: g = DiGraph([(1, 2, 5), (2, 3, 0), (1, 4, 2), (4, 5, 1), (5, 3, 0)]) sage: list(feng_k_shortest_simple_paths(g, 1, 3, by_weight=True)) @@ -1031,30 +1031,30 @@ def feng_k_shortest_simple_paths(self, source, target, weight_function=None, (27, [(1, 2, 1), (2, 3, 1), (3, 8, 5), (8, 9, 2), (9, 11, 10), (11, 6, 8)]), (105, [(1, 2, 1), (2, 3, 1), (3, 4, 1), (4, 5, 2), (5, 6, 100)])] sage: list(feng_k_shortest_simple_paths(g, 1, 6)) - [[1, 2, 3, 8, 9, 6], + [[1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 7, 6], - [1, 2, 3, 4, 5, 6], - [1, 2, 3, 8, 9, 10, 6], - [1, 2, 3, 8, 9, 11, 6]] + [1, 2, 3, 8, 9, 6], + [1, 2, 3, 8, 9, 11, 6], + [1, 2, 3, 8, 9, 10, 6]] sage: from sage.graphs.path_enumeration import feng_k_shortest_simple_paths sage: g = DiGraph([(1, 2, 1), (2, 3, 1), (3, 4, 1), (4, 5, 1), ....: (1, 7, 1), (7, 8, 1), (8, 5, 1), (1, 6, 1), ....: (6, 9, 1), (9, 5, 1), (4, 2, 1), (9, 3, 1), ....: (9, 10, 1), (10, 5, 1), (9, 11, 1), (11, 10, 1)]) sage: list(feng_k_shortest_simple_paths(g, 1, 5)) - [[1, 7, 8, 5], - [1, 6, 9, 5], - [1, 6, 9, 10, 5], + [[1, 6, 9, 5], + [1, 7, 8, 5], [1, 2, 3, 4, 5], - [1, 6, 9, 3, 4, 5], - [1, 6, 9, 11, 10, 5]] - sage: list(feng_k_shortest_simple_paths(g, 1, 5, by_weight=True)) - [[1, 7, 8, 5], - [1, 6, 9, 5], [1, 6, 9, 10, 5], + [1, 6, 9, 11, 10, 5], + [1, 6, 9, 3, 4, 5]] + sage: list(feng_k_shortest_simple_paths(g, 1, 5, by_weight=True)) + [[1, 6, 9, 5], + [1, 7, 8, 5], [1, 2, 3, 4, 5], - [1, 6, 9, 3, 4, 5], - [1, 6, 9, 11, 10, 5]] + [1, 6, 9, 10, 5], + [1, 6, 9, 11, 10, 5], + [1, 6, 9, 3, 4, 5]] sage: from sage.graphs.path_enumeration import feng_k_shortest_simple_paths sage: g = DiGraph([(1, 2, 5), (6, 3, 0), (2, 6, 6), (1, 4, 15), ....: (4, 5, 1), (4, 3, 0), (7, 1, 2), (8, 7, 1)]) From 4abac57821fc7b6bd03e9f4a380581d8f31efa79 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 11 Jan 2025 09:26:30 +0100 Subject: [PATCH 4/5] #39228: reorder imports --- src/sage/graphs/base/c_graph.pyx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/sage/graphs/base/c_graph.pyx b/src/sage/graphs/base/c_graph.pyx index 953c3983f2d..87ef188cc9c 100644 --- a/src/sage/graphs/base/c_graph.pyx +++ b/src/sage/graphs/base/c_graph.pyx @@ -43,16 +43,18 @@ method :meth:`realloc `. # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.data_structures.bitset_base cimport * -from sage.rings.integer cimport smallInteger -from sage.arith.long cimport pyobject_to_long +from cysignals.memory cimport check_allocarray, sig_free +from libcpp.pair cimport pair from libcpp.queue cimport queue from libcpp.stack cimport stack -from libcpp.pair cimport pair -from sage.rings.integer_ring import ZZ -from cysignals.memory cimport check_allocarray, sig_free + +from sage.arith.long cimport pyobject_to_long from sage.data_structures.bitset cimport FrozenBitset +from sage.data_structures.bitset_base cimport * from sage.data_structures.pairing_heap cimport PairingHeap +from sage.rings.integer cimport smallInteger + +from sage.rings.integer_ring import ZZ cdef extern from "Python.h": From 3962a937238ee4b2fbc3271c34e2839428b40ff0 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 11 Jan 2025 09:34:17 +0100 Subject: [PATCH 5/5] #39228: remove trailing whitespaces --- src/sage/graphs/base/c_graph.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/base/c_graph.pyx b/src/sage/graphs/base/c_graph.pyx index 87ef188cc9c..982dc51cca7 100644 --- a/src/sage/graphs/base/c_graph.pyx +++ b/src/sage/graphs/base/c_graph.pyx @@ -3850,7 +3850,7 @@ cdef class CGraphBackend(GenericGraphBackend): while w != x_int: shortest_path.append(self.vertex_label(w)) w = pred_x[w] - + shortest_path.append(x) shortest_path.reverse()