From de74fbd90054f6ef4f783ece1b193addd7790926 Mon Sep 17 00:00:00 2001 From: Cyril Bouvier Date: Fri, 21 Jun 2024 13:10:38 +0200 Subject: [PATCH 1/4] graphs: fix lex_BFS for DiGraph --- src/sage/graphs/traversals.pyx | 35 +++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/sage/graphs/traversals.pyx b/src/sage/graphs/traversals.pyx index 13fc1c2775a..93fdaa8e73f 100644 --- a/src/sage/graphs/traversals.pyx +++ b/src/sage/graphs/traversals.pyx @@ -76,8 +76,19 @@ def _is_valid_lex_BFS_order(G, L): sage: G = DiGraph("I?O@??A?CCA?A??C??") sage: _is_valid_lex_BFS_order(G, [0, 7, 1, 2, 3, 4, 5, 8, 6, 9]) + False + sage: _is_valid_lex_BFS_order(G, G.lex_BFS()) + True + sage: H = G.to_undirected() + sage: _is_valid_lex_BFS_order(H, G.lex_BFS()) + True + sage: _is_valid_lex_BFS_order(G, H.lex_BFS()) True """ + # Convert G to a simple undirected graph + if G.has_loops() or G.has_multiple_edges() or G.is_directed(): + G = G.to_simple(immutable=False, to_undirected=True) + cdef int n = G.order() if set(L) != set(G): @@ -86,11 +97,9 @@ def _is_valid_lex_BFS_order(G, L): cdef dict L_inv = {u: i for i, u in enumerate(L)} cdef int pos_a, pos_b, pos_c - neighbors = G.neighbor_in_iterator if G.is_directed() else G.neighbor_iterator - for pos_a in range(n - 1, -1, -1): a = L[pos_a] - for c in neighbors(a): + for c in G.neighbor_iterator(a): pos_c = L_inv[c] if pos_c > pos_a: continue @@ -99,7 +108,7 @@ def _is_valid_lex_BFS_order(G, L): if G.has_edge(c, b): continue if any(L_inv[d] < pos_c and not G.has_edge(d, a) - for d in neighbors(b)): + for d in G.neighbor_iterator(b)): # The condition is satisfied for a < b < c continue return False @@ -258,6 +267,9 @@ def lex_BFS(G, reverse=False, tree=False, initial_vertex=None, algorithm="fast") algorithm is in `O(n + m)`, and our implementation follows that complexity. See [HMPV2000]_ and next section for more details. + Loops and multiple edges are ignored during the computation of lex_BFS and + directed graphs are converted to undirected graphs. + ALGORITHM: The ``"fast"`` algorithm is the `O(n + m)` time algorithm proposed in @@ -314,10 +326,11 @@ def lex_BFS(G, reverse=False, tree=False, initial_vertex=None, algorithm="fast") The method also works for directed graphs:: sage: G = DiGraph([(1, 2), (2, 3), (1, 3)]) - sage: G.lex_BFS(initial_vertex=2, algorithm="slow") - [2, 3, 1] - sage: G.lex_BFS(initial_vertex=2, algorithm="fast") - [2, 3, 1] + sage: correct_anwsers = [[2, 1, 3], [2, 3, 1]] + sage: G.lex_BFS(initial_vertex=2, algorithm="slow") in correct_anwsers + True + sage: G.lex_BFS(initial_vertex=2, algorithm="fast") in correct_anwsers + True For a Chordal Graph, a reversed Lex BFS is a Perfect Elimination Order:: @@ -407,9 +420,9 @@ def lex_BFS(G, reverse=False, tree=False, initial_vertex=None, algorithm="fast") if tree: from sage.graphs.digraph import DiGraph - # Loops and multiple edges are not needed in Lex BFS - if G.has_loops() or G.has_multiple_edges(): - G = G.to_simple(immutable=False) + # Convert G to a simple undirected graph + if G.has_loops() or G.has_multiple_edges() or G.is_directed(): + G = G.to_simple(immutable=False, to_undirected=True) cdef size_t n = G.order() if not n: From 7ba93346487a33364a0cdffe93f2dc0e06d356cc Mon Sep 17 00:00:00 2001 From: Cyril Bouvier Date: Sun, 23 Jun 2024 15:12:13 +0200 Subject: [PATCH 2/4] graphs: in traversals algorithm, set the copy of the graph to immutable --- src/sage/graphs/traversals.pyx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/graphs/traversals.pyx b/src/sage/graphs/traversals.pyx index 93fdaa8e73f..19bd88ea51c 100644 --- a/src/sage/graphs/traversals.pyx +++ b/src/sage/graphs/traversals.pyx @@ -87,7 +87,7 @@ def _is_valid_lex_BFS_order(G, L): """ # Convert G to a simple undirected graph if G.has_loops() or G.has_multiple_edges() or G.is_directed(): - G = G.to_simple(immutable=False, to_undirected=True) + G = G.to_simple(immutable=True, to_undirected=True) cdef int n = G.order() @@ -422,7 +422,7 @@ def lex_BFS(G, reverse=False, tree=False, initial_vertex=None, algorithm="fast") # Convert G to a simple undirected graph if G.has_loops() or G.has_multiple_edges() or G.is_directed(): - G = G.to_simple(immutable=False, to_undirected=True) + G = G.to_simple(immutable=True, to_undirected=True) cdef size_t n = G.order() if not n: @@ -600,7 +600,7 @@ def lex_UP(G, reverse=False, tree=False, initial_vertex=None): # Loops and multiple edges are not needed in Lex UP if G.allows_loops() or G.allows_multiple_edges(): - G = G.to_simple(immutable=False) + G = G.to_simple(immutable=True) cdef int nV = G.order() @@ -772,7 +772,7 @@ def lex_DFS(G, reverse=False, tree=False, initial_vertex=None): # Loops and multiple edges are not needed in Lex DFS if G.allows_loops() or G.allows_multiple_edges(): - G = G.to_simple(immutable=False) + G = G.to_simple(immutable=True) cdef int nV = G.order() @@ -946,7 +946,7 @@ def lex_DOWN(G, reverse=False, tree=False, initial_vertex=None): # Loops and multiple edges are not needed in Lex DOWN if G.allows_loops() or G.allows_multiple_edges(): - G = G.to_simple(immutable=False) + G = G.to_simple(immutable=True) cdef int nV = G.order() From 1b87ce0f7210abcc145c11f2c8c0956c28f2f878 Mon Sep 17 00:00:00 2001 From: Cyril Bouvier Date: Mon, 24 Jun 2024 09:18:03 +0200 Subject: [PATCH 3/4] graphs: fix lex_UP, lex_DOWN and lex_DFS for DiGraph --- src/sage/graphs/traversals.pyx | 42 ++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/sage/graphs/traversals.pyx b/src/sage/graphs/traversals.pyx index 19bd88ea51c..af78c3c8572 100644 --- a/src/sage/graphs/traversals.pyx +++ b/src/sage/graphs/traversals.pyx @@ -509,6 +509,9 @@ def lex_UP(G, reverse=False, tree=False, initial_vertex=None): - ``initial_vertex`` -- (default: ``None``); the first vertex to consider + Loops and multiple edges are ignored during the computation of lex_UP and + directed graphs are converted to undirected graphs. + ALGORITHM: This algorithm maintains for each vertex left in the graph a code @@ -549,8 +552,9 @@ def lex_UP(G, reverse=False, tree=False, initial_vertex=None): The method also works for directed graphs:: sage: G = DiGraph([(1, 2), (2, 3), (1, 3)]) - sage: G.lex_UP(initial_vertex=2) - [2, 3, 1] + sage: correct_anwsers = [[2, 1, 3], [2, 3, 1]] + sage: G.lex_UP(initial_vertex=2) in correct_anwsers + True Different orderings for different traversals:: @@ -598,9 +602,9 @@ def lex_UP(G, reverse=False, tree=False, initial_vertex=None): if initial_vertex is not None and initial_vertex not in G: raise ValueError("'{}' is not a graph vertex".format(initial_vertex)) - # Loops and multiple edges are not needed in Lex UP - if G.allows_loops() or G.allows_multiple_edges(): - G = G.to_simple(immutable=True) + # Convert G to a simple undirected graph + if G.has_loops() or G.has_multiple_edges() or G.is_directed(): + G = G.to_simple(immutable=True, to_undirected=True) cdef int nV = G.order() @@ -682,6 +686,9 @@ def lex_DFS(G, reverse=False, tree=False, initial_vertex=None): - ``initial_vertex`` -- (default: ``None``); the first vertex to consider + Loops and multiple edges are ignored during the computation of lex_DFS and + directed graphs are converted to undirected graphs. + ALGORITHM: This algorithm maintains for each vertex left in the graph a code @@ -721,8 +728,9 @@ def lex_DFS(G, reverse=False, tree=False, initial_vertex=None): The method also works for directed graphs:: sage: G = DiGraph([(1, 2), (2, 3), (1, 3)]) - sage: G.lex_DFS(initial_vertex=2) - [2, 3, 1] + sage: correct_anwsers = [[2, 1, 3], [2, 3, 1]] + sage: G.lex_DFS(initial_vertex=2) in correct_anwsers + True Different orderings for different traversals:: @@ -770,9 +778,9 @@ def lex_DFS(G, reverse=False, tree=False, initial_vertex=None): if initial_vertex is not None and initial_vertex not in G: raise ValueError("'{}' is not a graph vertex".format(initial_vertex)) - # Loops and multiple edges are not needed in Lex DFS - if G.allows_loops() or G.allows_multiple_edges(): - G = G.to_simple(immutable=True) + # Convert G to a simple undirected graph + if G.has_loops() or G.has_multiple_edges() or G.is_directed(): + G = G.to_simple(immutable=True, to_undirected=True) cdef int nV = G.order() @@ -855,6 +863,9 @@ def lex_DOWN(G, reverse=False, tree=False, initial_vertex=None): - ``initial_vertex`` -- (default: ``None``); the first vertex to consider + Loops and multiple edges are ignored during the computation of lex_DOWN and + directed graphs are converted to undirected graphs. + ALGORITHM: This algorithm maintains for each vertex left in the graph a code @@ -895,8 +906,9 @@ def lex_DOWN(G, reverse=False, tree=False, initial_vertex=None): The method also works for directed graphs:: sage: G = DiGraph([(1, 2), (2, 3), (1, 3)]) - sage: G.lex_DOWN(initial_vertex=2) - [2, 3, 1] + sage: correct_anwsers = [[2, 1, 3], [2, 3, 1]] + sage: G.lex_DOWN(initial_vertex=2) in correct_anwsers + True Different orderings for different traversals:: @@ -944,9 +956,9 @@ def lex_DOWN(G, reverse=False, tree=False, initial_vertex=None): if initial_vertex is not None and initial_vertex not in G: raise ValueError("'{}' is not a graph vertex".format(initial_vertex)) - # Loops and multiple edges are not needed in Lex DOWN - if G.allows_loops() or G.allows_multiple_edges(): - G = G.to_simple(immutable=True) + # Convert G to a simple undirected graph + if G.has_loops() or G.has_multiple_edges() or G.is_directed(): + G = G.to_simple(immutable=True, to_undirected=True) cdef int nV = G.order() From 3b1afb1844fc985c7bb1208dcc5a5a9c417f5aec Mon Sep 17 00:00:00 2001 From: cyrilbouvier Date: Mon, 24 Jun 2024 10:49:05 +0200 Subject: [PATCH 4/4] graphs: traversals.pyx: update doc to use `` --- src/sage/graphs/traversals.pyx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sage/graphs/traversals.pyx b/src/sage/graphs/traversals.pyx index af78c3c8572..f3fbc78cd44 100644 --- a/src/sage/graphs/traversals.pyx +++ b/src/sage/graphs/traversals.pyx @@ -267,8 +267,8 @@ def lex_BFS(G, reverse=False, tree=False, initial_vertex=None, algorithm="fast") algorithm is in `O(n + m)`, and our implementation follows that complexity. See [HMPV2000]_ and next section for more details. - Loops and multiple edges are ignored during the computation of lex_BFS and - directed graphs are converted to undirected graphs. + Loops and multiple edges are ignored during the computation of ``lex_BFS`` + and directed graphs are converted to undirected graphs. ALGORITHM: @@ -509,8 +509,8 @@ def lex_UP(G, reverse=False, tree=False, initial_vertex=None): - ``initial_vertex`` -- (default: ``None``); the first vertex to consider - Loops and multiple edges are ignored during the computation of lex_UP and - directed graphs are converted to undirected graphs. + Loops and multiple edges are ignored during the computation of ``lex_UP`` + and directed graphs are converted to undirected graphs. ALGORITHM: @@ -686,8 +686,8 @@ def lex_DFS(G, reverse=False, tree=False, initial_vertex=None): - ``initial_vertex`` -- (default: ``None``); the first vertex to consider - Loops and multiple edges are ignored during the computation of lex_DFS and - directed graphs are converted to undirected graphs. + Loops and multiple edges are ignored during the computation of ``lex_DFS`` + and directed graphs are converted to undirected graphs. ALGORITHM: @@ -863,8 +863,8 @@ def lex_DOWN(G, reverse=False, tree=False, initial_vertex=None): - ``initial_vertex`` -- (default: ``None``); the first vertex to consider - Loops and multiple edges are ignored during the computation of lex_DOWN and - directed graphs are converted to undirected graphs. + Loops and multiple edges are ignored during the computation of ``lex_DOWN`` + and directed graphs are converted to undirected graphs. ALGORITHM: