From 66e972491a8aca28f13755b1f735fc0436a239d9 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sun, 1 Dec 2024 13:25:03 +0530 Subject: [PATCH 01/39] added is_brace() --- src/sage/graphs/matching_covered_graph.py | 356 +++++++++++++++++++++- 1 file changed, 355 insertions(+), 1 deletion(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 2a9b8916c90..fb335d9139c 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -107,7 +107,6 @@ :delim: | ``bricks_and_braces()`` | Return the list of (underlying simple graph of) the bricks and braces of the (matching covered) graph. - ``is_brace()`` | Check if the (matching covered) graph is a brace. ``is_brick()`` | Check if the (matching covered) graph is a brick. ``number_of_braces()`` | Return the number of braces. ``number_of_bricks()`` | Return the number of bricks. @@ -2003,6 +2002,361 @@ def has_perfect_matching(G, algorithm='Edmonds', solver=None, verbose=0, raise ValueError('algorithm must be set to \'Edmonds\', ' '\'LP_matching\' or \'LP\'') + @doc_index('Bricks, braces and tight cut decomposition') + def is_brace(self, coNP_certificate=False): + r""" + Check if the (matching covered) graph is a brace. + + A matching covered graph which is free of nontrivial tight cuts is + called a *brace* if it is bipartite. Let `G := (A \cup B, E)` be a + bipartite matching covered graph on six or more vertices. The + following statements are equivalent [LM2024]_: + + 1. `G` is a brace (aka free of nontrivial tight cuts). + 2. `G - a_1 - a_2 - b_1 - b_2` has a perfect matching for any two + distinct vertices `a_1` and `a_2` in `A` and any two distinct + vertices `b_1` and `b_2` in `B`. + 3. `G` is two extendable (any two nonadjacent distinct edges can be + extended to some perfect matching of `G`). + 4. `|N(X)| \geq |X| + 2`, for all `X ⊂ A` such that `0 < |X| < + |A| - 1`, where `N(S) := \{b | (a, b) \in E ^ a \in S\}` is called + the neighboring set of `S`. + 5. `G - a - b` is matching covered, for some perfect matching `M` of + `G` and for each edge `ab` in `M`. + + We shall be using the 5th characterization mentioned above in order + to determine whether the provided bipartite matching covered graph + is a brace or not using *M*-alternating tree search [LZ2001]_. + + INPUT: + + - ``coNP_certificate`` -- boolean (default: ``False``) + + OUTPUT: + + - If the input matching covered graph is not bipartite, a + :exc:`ValueError` is returned. + + - If the input bipartite matching covered graph is a brace, a boolean + ``True`` is returned if ``coNP_certificate`` is set to ``False`` + otherwise a pair ``(True, None, None)`` is returned. + + - If the input bipartite matching covered graph is not a brace, a + boolean ``False`` is returned if ``coNP_certificate`` is set to + ``False`` otherwise a tuple of boolean ``False``, a list of + edges constituting a nontrivial tight cut and a set of vertices of + one of the shores of the nontrivial tight cut is returned. + + EXAMPLES: + + The complete graph on two vertices `K_2` is the smallest brace:: + + sage: K = graphs.CompleteGraph(2) + sage: G = MatchingCoveredGraph(K) + sage: G.is_brace() + True + + The cycle graph on four vertices `C_4` is a brace:: + + sage: C = graphs.CycleGraph(4) + sage: G = MatchingCoveredGraph(C) + sage: G.is_brace() + True + + Each graph that is isomorphic to a biwheel is a brace:: + + sage: B = graphs.BiwheelGraph(15) + sage: G = MatchingCoveredGraph(B) + sage: G.is_brace() + True + + A circular ladder graph on `2n` vertices for `n \equiv 0 (\mod 2)` is + a brace:: + + sage: n = 10 + sage: CL = graphs.CircularLadderGraph(n) + sage: G = MatchingCoveredGraph(CL) + sage: G.is_brace() + True + + A moebius ladder graph on `2n` vertices for `n \equiv 1 (\mod 2)` is + a brace:: + + sage: n = 11 + sage: ML = graphs.MoebiusLadderGraph(n) + sage: G = MatchingCoveredGraph(ML) + sage: G.is_brace() + True + + Note that the union of the above mentioned four families of braces, + that are: + 1. the biwheel graph ``BiwheelGraph(n)``, + 2. the circular ladder graph ``CircularLadderGraph(n)`` for even ``n``, + 3. the moebius ladder graph ``MoebiusLadderGraph(n)`` for odd ``n``, + is referred to as the *McCuaig* *family* *of* *braces.* + + The only simple brace of order six is the complete graph of the same + order, that is `K_{3, 3}`:: + + sage: L = list(graphs(6, + ....: lambda G: G.size() <= 15 and + ....: G.is_bipartite()) + ....: ) + sage: L = list(G for G in L if G.is_connected() and + ....: G.is_matching_covered() + ....: ) + sage: M = list(MatchingCoveredGraph(G) for G in L) + sage: B = list(G for G in M if G.is_brace()) + sage: K = graphs.CompleteBipartiteGraph(3, 3) + sage: G = MatchingCoveredGraph(K) + sage: next(iter(B)).is_isomorphic(G) + True + + The nonplanar `K_{3, 3}`-free brace Heawood graph is the unique cubic + graph of girth six with the fewest number of vertices (that is 14). + Note that by `K_{3, 3}`-free, it shows that the Heawood graph does not + contain a subgraph that is isomophic to a graph obtained by + bisubdivision of `K_{3, 3}`:: + + sage: K = graphs.CompleteBipartiteGraph(3, 3) + sage: J = graphs.HeawoodGraph() + sage: H = MatchingCoveredGraph(J) + sage: H.is_brace() and not H.is_planar() and \ + ....: H.is_regular(k=3) and H.girth() == 6 + True + + Braces of order six or more are 3-connected:: + + sage: H = graphs.HexahedralGraph() + sage: G = MatchingCoveredGraph(H) + sage: G.is_brace() and G.is_triconnected() + True + + Braces of order four or more are 2-extendable:: + + sage: H = graphs.EllinghamHorton54Graph() + sage: G = MatchingCoveredGraph(H) + sage: G.is_brace() + True + sage: e = next(G.edge_iterator(labels=False)); f = None + sage: for f in G.edge_iterator(labels=False): + ....: if not (set(e) & set(f)): + ....: break + sage: S = [u for x in [e, f] for u in set(x)] + sage: J = H.copy(); J.delete_vertices(S) + sage: M = Graph(J.matching()) + sage: M.add_edges([e, f]) + sage: if all(d == 1 for d in M.degree()) and \ + ....: G.order() == M.order() and \ + ....: G.order() == 2*M.size(): + ....: print(f'graph {G} is 2-extendable') + graph Ellingham-Horton 54-graph is 2-extendable + + Every edge in a brace of order at least six is removable:: + + sage: H = graphs.CircularLadderGraph(8) + sage: G = MatchingCoveredGraph(H) + sage: # len(G.removble_edges()) == G.size() + # True + + Every brace of order eight has the hexahedral graph as a spanning + subgraph:: + + sage: H = graphs.HexahedralGraph() + sage: L = list(graphs(8, + ....: lambda G: G.size() <= 28 and + ....: G.is_bipartite()) + ....: ) + sage: L = list(G for G in L if G.is_connected() and + ....: G.is_matching_covered() + ....: ) + sage: M = list(MatchingCoveredGraph(G) for G in L) + sage: B = list(G for G in M if G.is_brace()) + sage: C = list(G for G in M if Graph(G).subgraph_search(H) is not None) + sage: B == C + True + + For every brace `G[A, B]` of order at least six, the graph + `G - a_1 - a_2 - b_1 - b_2` has a perfect matching for any two distinct + vertices `a_1` and `a_2` in `A` and any two distinct vertices `b_1` and + `b_2` in `B`:: + + sage: H = graphs.CompleteBipartiteGraph(10, 10) + sage: G = MatchingCoveredGraph(H) + sage: G.is_brace() + True + sage: S = [0, 1, 10, 12] + sage: G.delete_vertices(S) + sage: G.has_perfect_matching() + True + + For a brace `G[A, B]` of order six or more, `|N(X)| \geq |X| + 2`, for + all `X \subset A` such that `0 < |X| <|A| - 1`, where + `N(S) := \{b | (a, b) \in E ^ a \in S\}` is called the neighboring set + of `S`:: + + sage: H = graphs.MoebiusLadderGraph(15) + sage: G = MatchingCoveredGraph(H) + sage: G.is_brace() + True + sage: A, _ = G.bipartite_sets() + sage: # needs random + sage: X = random.sample(list(A), random.randint(1, len(A) - 1)) + sage: N = {v for u in X for v in G.neighbor_iterator(u)} + sage: len(N) >= len(X) + 2 + True + + For a brace `G` of order four or more with a perfect matching `M`, the + graph `G - a - b` is matching covered for each edge `(a, b)` in `M`:: + + sage: H = graphs.HeawoodGraph() + sage: G = MatchingCoveredGraph(H) + sage: G.is_brace() + True + sage: M = G.get_matching() + sage: L = [] + sage: for a, b, *_ in M: + ....: J = G.copy(); J.delete_vertices([a, b]) + ....: if J.is_matching_covered(): + ....: L.append(J) + sage: len(L) == len(M) + True + + A cycle graph of order six of more is a bipartite matching covered + graph, but is not a brace:: + + sage: C = graphs.CycleGraph(10) + sage: G = MatchingCoveredGraph(C) + sage: G.is_brace() + False + + One may set the ``coNP_certificate`` to be ``True``:: + + sage: H = graphs.HexahedralGraph() + sage: G = MatchingCoveredGraph(H) + sage: G.is_brace(coNP_certificate=True) + (True, None, None) + sage: C = graphs.CycleGraph(6) + sage: D = MatchingCoveredGraph(C) + sage: D.is_brace(coNP_certificate=True) + (False, [(0, 5, None), (2, 3, None)], {0, 1, 2}) + + If the input matching covered graph is nonbipartite, a + :exc:`ValueError` is thrown:: + + sage: K4 = graphs.CompleteGraph(4) + sage: G = MatchingCoveredGraph(K4) + sage: G.is_brace() + Traceback (most recent call last): + ... + ValueError: the input graph is not bipartite + sage: P = graphs.PetersenGraph() + sage: H = MatchingCoveredGraph(P) + sage: H.is_brace(coNP_certificate=True) + Traceback (most recent call last): + ... + ValueError: the input graph is not bipartite + """ + if not self.is_bipartite(): + raise ValueError('the input graph is not bipartite') + + if self.order() < 6: + return (True, None) if coNP_certificate else True + + color = {u: 0 if u in self.bipartite_sets()[0] else 1 for u in self} + matching = set(self.get_matching()) + matching_neighbor = {x: y for u, v, *_ in self.get_matching() for x, y in [(u, v), (v, u)]} + + for e in list(self.get_matching())[:]: + u, v, *_ = e + + # Let G denote the undirected graph self, and + # let the graph H(e) := G — u — v + H = Graph(self, multiedges=False) + H.delete_vertices([u, v]) + + if not H.is_matching_covered(list(matching - set([e]))): + if not coNP_certificate: + return False + + # Construct the digraph D(e)(A ∪ B, F) defined as follows: + from sage.graphs.digraph import DiGraph + D = DiGraph() + + # For each edge (a, b) in E(H(e)) ∩ M with a in A, b —> a in D(e). + # For each edge (a, b) in E(H(e)) with a in A, a —> b in D(e). + for a, b, *_ in H.edge_iterator(): + + if color[a]: + a, b = b, a + + D.add_edge((a, b)) + if matching_neighbor[a] == b: + D.add_edge((b, a)) + + D.show() + + # H(e) is matching covered iff D(e) is strongly connected. + # Check if D(e) is strongly connected using Kosaraju's algorithm + def dfs(J, v, visited, orientation): + stack = [v] # a stack of vertices + + while stack: + v = stack.pop() + + if v not in visited: + visited[v] = True + + if orientation == 'in': + for u in J.neighbors_out(v): + if u not in visited: + stack.append(u) + + elif orientation == 'out': + for u in J.neighbors_in(v): + if u not in visited: + stack.append(u) + else: + raise ValueError('Unknown orientation') + + root = next(D.vertex_iterator()) + + visited_in = {} + dfs(D, root, visited_in, 'in') + + visited_out = {} + dfs(D, root, visited_out, 'out') + + # Note that by definition of D(e), it follows that C ⊆ E(H(e)) — M. + # Thus, C is a cut of H(e), which has a shore X such that every edge of C is + # incident with a vertex in X ∩ B. + + # Moreover, M — e is a perfect matching of H(e), and thus, |X ∩ A| = |X ∩ B| + # Consequently, Y := X + v is a shore of a nontrivial tight cut T of G + + if not all(visited_in.values()): + X = {w for w in D if w in visited_in} + else: + X = {w for w in D if w in visited_out} + + # Compute the directed cut C of D(e) + C = [] + for a, b, *_ in H.edge_iterator(): + if (a in X) ^ (b in X): # Exclusive OR: one in X, the other not + x, y = (a, b) if color[a] == 0 else (b, a) + C.append([x, y]) + + # Obtain the color class Z ∈ {A, B} such that X ∩ Z is a vertex cover for C + color_class = 1 if C[0][0] not in X else 0 + X.add(u if (not color_class and color[v]) or (color_class and color[u]) else v) + + # Compute the nontrivial tight cut T := ∂(X) + T = [f for f in self.edge_iterator() if (f[0] in X) ^ (f[1] in X)] + + return False, T, X + + return (True, None, None) if coNP_certificate else True + @doc_index('Miscellaneous methods') def update_matching(self, matching): r""" From 86bd2e91c82568a38f1831540a2b0cd5c1bda0b5 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sun, 1 Dec 2024 13:26:05 +0530 Subject: [PATCH 02/39] updated the to do list --- src/sage/graphs/matching_covered_graph.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index fb335d9139c..85209f81930 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -72,6 +72,7 @@ ``loop_edges()`` | Return a list of all loops in the matching covered graph. ``loop_vertices()`` | Return a list of vertices with loops. ``merge_vertices()`` | Merge vertices. + ``minor()`` | Return the vertices of a minor isomorphic to `H` in the current graph. ``number_of_loops()`` | Return the number of edges that are loops. ``random_subgraph()`` | Return a random matching covered subgraph containing each vertex with probability ``p``. ``remove_loops()`` | Remove loops on vertices in ``vertices``. @@ -85,6 +86,7 @@ ``subgraph_search_iterator()`` | Return an iterator over the labelled copies of (matching covered) ``G`` in ``self``. ``tensor_product()`` | Return the tensor product of ``self`` and ``other``. ``to_undirected()`` | Return an undirected Graph instance of the matching covered graph. + ``topological_minor()`` | Return a topological `H`-minor from ``self`` if one exists. ``transitive_closure()`` | Return the transitive closure of the matching covered graph. ``transitive_reduction()`` | Return a transitive reduction of the matching covered graph. ``union()`` | Return the union of ``self`` and ``other``. From dc47497c7a8aa35229a2f2692b558e829b63a192 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sun, 1 Dec 2024 13:27:04 +0530 Subject: [PATCH 03/39] updated the digraph H --- src/sage/graphs/matching.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sage/graphs/matching.py b/src/sage/graphs/matching.py index c8eea2bb005..79dd5eeb19a 100644 --- a/src/sage/graphs/matching.py +++ b/src/sage/graphs/matching.py @@ -1019,10 +1019,9 @@ def is_matching_covered(G, matching=None, algorithm='Edmonds', coNP_certificate= if color[u]: u, v = v, u - if M.has_edge(u, v): - H.add_edge(u, v) - else: - H.add_edge(v, u) + H.add_edge((u, v)) + if next(M.neighbor_iterator(u)) == v: + H.add_edge((v, u)) # Check if H is strongly connected using Kosaraju's algorithm def dfs(J, v, visited, orientation): From e5d74e288f5b0997235ca6d3e274c3f4ba33c3c5 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sun, 1 Dec 2024 13:28:30 +0530 Subject: [PATCH 04/39] updated the doctest --- src/sage/graphs/matching.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/matching.py b/src/sage/graphs/matching.py index 79dd5eeb19a..df1b8e8d979 100644 --- a/src/sage/graphs/matching.py +++ b/src/sage/graphs/matching.py @@ -877,7 +877,7 @@ def is_matching_covered(G, matching=None, algorithm='Edmonds', coNP_certificate= sage: H = graphs.PathGraph(20) sage: M = H.matching() sage: H.is_matching_covered(matching=M, coNP_certificate=True) - (False, (1, 2, None)) + (False, (2, 1, None)) TESTS: From 4b438976d2a8602d3225fbe0ced7d9b7ec43a493 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sun, 1 Dec 2024 17:08:58 +0530 Subject: [PATCH 05/39] updated is_matching_covered() --- src/sage/graphs/matching.py | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/sage/graphs/matching.py b/src/sage/graphs/matching.py index df1b8e8d979..66e87683be8 100644 --- a/src/sage/graphs/matching.py +++ b/src/sage/graphs/matching.py @@ -1024,38 +1024,28 @@ def is_matching_covered(G, matching=None, algorithm='Edmonds', coNP_certificate= H.add_edge((v, u)) # Check if H is strongly connected using Kosaraju's algorithm - def dfs(J, v, visited, orientation): + def dfs(v, visited, neighbor_iterator): stack = [v] # a stack of vertices while stack: v = stack.pop() + visited.add(v) - if v not in visited: - visited[v] = True - - if orientation == 'in': - for u in J.neighbors_out(v): - if u not in visited: - stack.append(u) - - elif orientation == 'out': - for u in J.neighbors_in(v): - if u not in visited: - stack.append(u) - else: - raise ValueError('Unknown orientation') + for u in neighbor_iterator(v): + if u not in visited: + stack.append(u) root = next(H.vertex_iterator()) - visited_in = {} - dfs(H, root, visited_in, 'in') + visited_in = set() + dfs(root, visited_in, H.neighbor_in_iterator) - visited_out = {} - dfs(H, root, visited_out, 'out') + visited_out = set() + dfs(root, visited_out, H.neighbor_out_iterator) for edge in H.edge_iterator(): u, v, _ = edge - if (u not in visited_in) or (v not in visited_out): + if (u not in visited_out) or (v not in visited_in): if not M.has_edge(edge): return (False, edge) if coNP_certificate else False From 0eb894d871b90af7f62b90a1e83f17b121231127 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sun, 1 Dec 2024 17:09:23 +0530 Subject: [PATCH 06/39] updated is_brace() --- src/sage/graphs/matching_covered_graph.py | 42 ++++++++--------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 85209f81930..b398a672abe 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2263,9 +2263,9 @@ def is_brace(self, coNP_certificate=False): raise ValueError('the input graph is not bipartite') if self.order() < 6: - return (True, None) if coNP_certificate else True + return (True, None, None) if coNP_certificate else True - color = {u: 0 if u in self.bipartite_sets()[0] else 1 for u in self} + A, B = self.bipartite_sets() matching = set(self.get_matching()) matching_neighbor = {x: y for u, v, *_ in self.get_matching() for x, y in [(u, v), (v, u)]} @@ -2289,45 +2289,33 @@ def is_brace(self, coNP_certificate=False): # For each edge (a, b) in E(H(e)) with a in A, a —> b in D(e). for a, b, *_ in H.edge_iterator(): - if color[a]: + if a in B: a, b = b, a D.add_edge((a, b)) if matching_neighbor[a] == b: D.add_edge((b, a)) - D.show() - # H(e) is matching covered iff D(e) is strongly connected. # Check if D(e) is strongly connected using Kosaraju's algorithm - def dfs(J, v, visited, orientation): + def dfs(v, visited, neighbor_iterator): stack = [v] # a stack of vertices while stack: v = stack.pop() + visited.add(v) - if v not in visited: - visited[v] = True - - if orientation == 'in': - for u in J.neighbors_out(v): - if u not in visited: - stack.append(u) - - elif orientation == 'out': - for u in J.neighbors_in(v): - if u not in visited: - stack.append(u) - else: - raise ValueError('Unknown orientation') + for u in neighbor_iterator(v): + if u not in visited: + stack.append(u) root = next(D.vertex_iterator()) - visited_in = {} - dfs(D, root, visited_in, 'in') + visited_in = set() + dfs(root, visited_in, D.neighbor_in_iterator) - visited_out = {} - dfs(D, root, visited_out, 'out') + visited_out = set() + dfs(root, visited_out, D.neighbor_out_iterator) # Note that by definition of D(e), it follows that C ⊆ E(H(e)) — M. # Thus, C is a cut of H(e), which has a shore X such that every edge of C is @@ -2336,7 +2324,7 @@ def dfs(J, v, visited, orientation): # Moreover, M — e is a perfect matching of H(e), and thus, |X ∩ A| = |X ∩ B| # Consequently, Y := X + v is a shore of a nontrivial tight cut T of G - if not all(visited_in.values()): + if len(visited_in) != D.order(): X = {w for w in D if w in visited_in} else: X = {w for w in D if w in visited_out} @@ -2345,12 +2333,12 @@ def dfs(J, v, visited, orientation): C = [] for a, b, *_ in H.edge_iterator(): if (a in X) ^ (b in X): # Exclusive OR: one in X, the other not - x, y = (a, b) if color[a] == 0 else (b, a) + x, y = (a, b) if a in A else (b, a) C.append([x, y]) # Obtain the color class Z ∈ {A, B} such that X ∩ Z is a vertex cover for C color_class = 1 if C[0][0] not in X else 0 - X.add(u if (not color_class and color[v]) or (color_class and color[u]) else v) + X.add(u if (not color_class and u in A) or (color_class and u in B) else v) # Compute the nontrivial tight cut T := ∂(X) T = [f for f in self.edge_iterator() if (f[0] in X) ^ (f[1] in X)] From 451f1643382898e52d25fbc7040f783fc04a429c Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sun, 1 Dec 2024 17:16:18 +0530 Subject: [PATCH 07/39] removed an unnecessary space --- src/sage/graphs/matching_covered_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index b398a672abe..f148784d264 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2265,7 +2265,7 @@ def is_brace(self, coNP_certificate=False): if self.order() < 6: return (True, None, None) if coNP_certificate else True - A, B = self.bipartite_sets() + A, B = self.bipartite_sets() matching = set(self.get_matching()) matching_neighbor = {x: y for u, v, *_ in self.get_matching() for x, y in [(u, v), (v, u)]} From 74ea0275aa41c7dda1e87817c44ac8c846d71a1d Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sun, 1 Dec 2024 17:23:29 +0530 Subject: [PATCH 08/39] updated the documentation --- src/sage/graphs/matching_covered_graph.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index f148784d264..d96181a5d7d 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2021,7 +2021,7 @@ def is_brace(self, coNP_certificate=False): 3. `G` is two extendable (any two nonadjacent distinct edges can be extended to some perfect matching of `G`). 4. `|N(X)| \geq |X| + 2`, for all `X ⊂ A` such that `0 < |X| < - |A| - 1`, where `N(S) := \{b | (a, b) \in E ^ a \in S\}` is called + |A| - 1`, where `N(S) := \{b | (a, b) \in E \^ a \in S\}` is called the neighboring set of `S`. 5. `G - a - b` is matching covered, for some perfect matching `M` of `G` and for each edge `ab` in `M`. @@ -2092,9 +2092,11 @@ def is_brace(self, coNP_certificate=False): Note that the union of the above mentioned four families of braces, that are: + 1. the biwheel graph ``BiwheelGraph(n)``, 2. the circular ladder graph ``CircularLadderGraph(n)`` for even ``n``, 3. the moebius ladder graph ``MoebiusLadderGraph(n)`` for odd ``n``, + is referred to as the *McCuaig* *family* *of* *braces.* The only simple brace of order six is the complete graph of the same @@ -2194,7 +2196,7 @@ def is_brace(self, coNP_certificate=False): For a brace `G[A, B]` of order six or more, `|N(X)| \geq |X| + 2`, for all `X \subset A` such that `0 < |X| <|A| - 1`, where - `N(S) := \{b | (a, b) \in E ^ a \in S\}` is called the neighboring set + `N(S) := \{b | (a, b) \in E \^ a \in S\}` is called the neighboring set of `S`:: sage: H = graphs.MoebiusLadderGraph(15) From 9c3936becb79511807236c72320e5b8ebfda1783 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sun, 1 Dec 2024 19:55:09 +0530 Subject: [PATCH 09/39] updated is_brace() --- src/sage/graphs/matching_covered_graph.py | 36 +++++++++++------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index d96181a5d7d..b53b52cabeb 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2271,7 +2271,7 @@ def is_brace(self, coNP_certificate=False): matching = set(self.get_matching()) matching_neighbor = {x: y for u, v, *_ in self.get_matching() for x, y in [(u, v), (v, u)]} - for e in list(self.get_matching())[:]: + for e in matching: u, v, *_ = e # Let G denote the undirected graph self, and @@ -2316,36 +2316,34 @@ def dfs(v, visited, neighbor_iterator): visited_in = set() dfs(root, visited_in, D.neighbor_in_iterator) - visited_out = set() - dfs(root, visited_out, D.neighbor_out_iterator) - - # Note that by definition of D(e), it follows that C ⊆ E(H(e)) — M. - # Thus, C is a cut of H(e), which has a shore X such that every edge of C is + # Since D(e) is not strongly connected, it has a directed cut T(e). + # Note that by definition of D(e), it follows that T(e) ⊆ E(H(e)) — M. + # Thus, T(e) is a cut of H(e), which has a shore X such that every edge of T(e) is # incident with a vertex in X ∩ B. # Moreover, M — e is a perfect matching of H(e), and thus, |X ∩ A| = |X ∩ B| # Consequently, Y := X + v is a shore of a nontrivial tight cut T of G if len(visited_in) != D.order(): - X = {w for w in D if w in visited_in} + X = visited_in else: - X = {w for w in D if w in visited_out} + X = set() + dfs(root, X, D.neighbor_out_iterator) - # Compute the directed cut C of D(e) - C = [] - for a, b, *_ in H.edge_iterator(): - if (a in X) ^ (b in X): # Exclusive OR: one in X, the other not - x, y = (a, b) if a in A else (b, a) - C.append([x, y]) + for a, b in H.edge_iterator(labels=False): + if (a in X) ^ (b in X): + x = a if a in A else b + color_class = x not in X + break - # Obtain the color class Z ∈ {A, B} such that X ∩ Z is a vertex cover for C - color_class = 1 if C[0][0] not in X else 0 + # Obtain the color class Z ∈ {A, B} such that X ∩ Z is a vertex cover for T(e) + # Thus, obtain Y := X + v X.add(u if (not color_class and u in A) or (color_class and u in B) else v) - # Compute the nontrivial tight cut T := ∂(X) - T = [f for f in self.edge_iterator() if (f[0] in X) ^ (f[1] in X)] + # Compute the nontrivial tight cut C := ∂(Y) + C = [f for f in self.edge_iterator() if (f[0] in X) ^ (f[1] in X)] - return False, T, X + return False, C, X return (True, None, None) if coNP_certificate else True From 430b837d76d9f6132988e6bbe096438612932230 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Mon, 2 Dec 2024 08:20:22 +0530 Subject: [PATCH 10/39] updated is_brace() --- src/sage/graphs/matching_covered_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index b53b52cabeb..f702074d1c8 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2269,7 +2269,7 @@ def is_brace(self, coNP_certificate=False): A, B = self.bipartite_sets() matching = set(self.get_matching()) - matching_neighbor = {x: y for u, v, *_ in self.get_matching() for x, y in [(u, v), (v, u)]} + matching_neighbor = {x: y for u, v, *_ in matching for x, y in [(u, v), (v, u)]} for e in matching: u, v, *_ = e From b1b8b8b2955e2cc089b51ee0b0edea50addab58d Mon Sep 17 00:00:00 2001 From: Janmenjaya Panda <83154020+janmenjayap@users.noreply.github.com> Date: Thu, 5 Dec 2024 22:17:06 +0530 Subject: [PATCH 11/39] Updated math expression formatting Co-authored-by: user202729 <25191436+user202729@users.noreply.github.com> --- src/sage/graphs/matching_covered_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index f702074d1c8..71aabbbc0e0 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2021,7 +2021,7 @@ def is_brace(self, coNP_certificate=False): 3. `G` is two extendable (any two nonadjacent distinct edges can be extended to some perfect matching of `G`). 4. `|N(X)| \geq |X| + 2`, for all `X ⊂ A` such that `0 < |X| < - |A| - 1`, where `N(S) := \{b | (a, b) \in E \^ a \in S\}` is called + |A| - 1`, where `N(S) := \{b \mid (a, b) \in E ∧ a \in S\}` is called the neighboring set of `S`. 5. `G - a - b` is matching covered, for some perfect matching `M` of `G` and for each edge `ab` in `M`. From d319860597025df02396423c7c18e941471d0de8 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Fri, 13 Dec 2024 15:41:12 +0530 Subject: [PATCH 12/39] added is_brick() --- src/sage/graphs/matching_covered_graph.py | 99 ++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index a7d1904df9f..21374d90f20 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -94,7 +94,6 @@ :delim: | ``bricks_and_braces()`` | Return the list of (underlying simple graph of) the bricks and braces of the (matching covered) graph. - ``is_brick()`` | Check if the (matching covered) graph is a brick. ``number_of_braces()`` | Return the number of braces. ``number_of_bricks()`` | Return the number of bricks. ``number_of_petersen_bricks()`` | Return the number of Petersen bricks. @@ -2690,6 +2689,104 @@ def dfs(v, visited, neighbor_iterator): return (True, None, None) if coNP_certificate else True + + @doc_index('Bricks, braces and tight cut decomposition') + def is_brick(self, coNP_certificate=False): + r""" + Check if the (matching covered) graph is a brick. + + A matching covered graph which is free of nontrivial tight cuts is + called a *brick* if it is nonbipartite. A nonbipartite matching covered + graph is a brick if and only if it is 3-connected and bicritical + [LM2024]_. + + .. SEEALSO:: + + :meth:`~sage.graphs.graph.Graph.is_bicritical` + """ + if self.is_bipartite(): + raise ValueError('the input graph is bipartite') + + # Check if G is bicritical + bicritical, certificate = self.is_bicritical(coNP_certificate=True) + + if not bicritical: + if not coNP_certificate: + return False + + # G has a pair of vertices u, v such that G - u - v is not matching + # covered, thus has a nontrivial barrier B containing both u and v. + u, _ = certificate + B = self.maximal_barrier(u) + + H = Graph(self) + H.delete_vertices(B) + + # Let K be a nontrivial odd component of H := G - B. Note that + # there exists at least one such K since G is nonbipartite + nontrivial_odd_component = next( + (component for component in H.connected_components() + if len(component) % 2 and len(component) > 1), None + ) + + # Find a nontrivial barrier cut + C = [(u, v, w) if u in nontrivial_odd_component else (v, u, w) + for u, v, w in self.edge_iterator() + if (u in nontrivial_odd_component) ^ (v in nontrivial_odd_component)] + + return False, C, nontrivial_odd_component + + # Check if G is 3-connected + if self.is_triconnected(): + return (True, None, None) if coNP_certificate else True + + # G has a 2-vertex cut + # Compute the SPQR-tree decomposition + spqr_tree = self.spqr_tree() + two_vertex_cut = [] + + # Check for 2-vertex cuts in P and S nodes + for u in spqr_tree: + if u[0] == 'P': + two_vertex_cut.extend(u[1].vertices()) + break + elif u[0] == 'S' and u[1].order() > 3: + s_vertex_set = set(u[1].vertices()) + for v in u[1].vertices(): + s_vertex_set -= {v} | set(u[1].neighbors(v)) + two_vertex_cut.extend([v, next(iter(s_vertex_set))]) + break + + # If no 2-vertex cut found, look for R nodes + if not two_vertex_cut: + R_frequency = {u: 0 for u in self} + for u in spqr_tree.vertices(): + if u[0] == 'R': + for v in u[1].vertices(): + R_frequency[v] += 1 + two_vertex_cut = [u for u in self if R_frequency[u] >= 2][:2] + + # We obtain a 2-vertex cut (u, v) + H = Graph(self) + H.delete_vertices(two_vertex_cut) + + # Check if all components of H are odd + components = H.connected_components() + are_all_odd_components = all(len(c) % 2 for c in components) + + # Find a nontrivial odd component + if are_all_odd_components: + nontrivial_odd_component = next((c for c in components if len(c) > 1), None) + else: + nontrivial_odd_component = components[0] + [two_vertex_cut[0]] + + C = [(u, v, w) if u in nontrivial_odd_component else (v, u, w) + for u, v, w in self.edge_iterator() + if (u in nontrivial_odd_component) ^ (v in nontrivial_odd_component)] + + # Edge (u, v, w) in C are formatted so that u is in a nontrivial odd component + return (False, C, nontrivial_odd_component) if coNP_certificate else False + @doc_index('Overwritten methods') def loop_edges(self, labels=True): r""" From 564cbbbc71f7f3e3e1a73778bca2c993c89539bd Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Fri, 13 Dec 2024 15:41:48 +0530 Subject: [PATCH 13/39] updated is_brace() --- src/sage/graphs/matching_covered_graph.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 21374d90f20..58d39a8b938 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2683,7 +2683,9 @@ def dfs(v, visited, neighbor_iterator): X.add(u if (not color_class and u in A) or (color_class and u in B) else v) # Compute the nontrivial tight cut C := ∂(Y) - C = [f for f in self.edge_iterator() if (f[0] in X) ^ (f[1] in X)] + C = [(u, v, w) if u in X else (v, u, w) + for u, v, w in self.edge_iterator() + if (u in X) ^ (v in X)] return False, C, X From 0f6ca22d8047677f39eb9aa751cfddaa00f32efe Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 21 Dec 2024 11:26:41 +0530 Subject: [PATCH 14/39] updated is_brick() --- src/sage/graphs/matching_covered_graph.py | 167 +++++++++++++++++++--- 1 file changed, 151 insertions(+), 16 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 58d39a8b938..e9f90686b22 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2691,7 +2691,6 @@ def dfs(v, visited, neighbor_iterator): return (True, None, None) if coNP_certificate else True - @doc_index('Bricks, braces and tight cut decomposition') def is_brick(self, coNP_certificate=False): r""" @@ -2702,9 +2701,148 @@ def is_brick(self, coNP_certificate=False): graph is a brick if and only if it is 3-connected and bicritical [LM2024]_. + INPUT: + + - ``coNP_certificate`` -- boolean (default: ``False``) + + OUTPUT: + + - If the input matching covered graph is bipartite, a :exc:`ValueError` + is returned. + + - If the input nonbipartite matching covered graph is a brick, a + boolean ``True`` is returned if ``coNP_certificate`` is set to + ``False`` otherwise a tuple ``(True, None, None)`` is returned. + + - If the input nonbipartite matching covered graph is not a brick, a + boolean ``False`` is returned if ``coNP_certificate`` is set to + ``False`` otherwise a tuple of boolean ``False``, a list of + edges constituting a nontrivial tight cut and a set of vertices of + one of the shores of the nontrivial tight cut is returned. + + EXAMPLES: + + The complete graph on four vertices `K_4` is the smallest brick:: + + sage: K = graphs.CompleteGraph(4) + sage: G = MatchingCoveredGraph(K) + sage: G.is_brick() + True + + The triangular cicular ladder (a graph on six vertices), aka + `\overline{C_6}` is a brick:: + + sage: C6Bar = graphs.CircularLadderGraph(3) + sage: G = MatchingCoveredGraph(C6Bar) + sage: G.is_brick() + True + + Each of Petersen graph, Bicorn graph, Tricorn graph, Cubeplex graph, + Twinplex graph, Wagner graph is a brick:: + + sage: MatchingCoveredGraph(graphs.PetersenGraph()).is_brick() and \ + ....: MatchingCoveredGraph(graphs.StaircaseGraph(4)).is_brick() and \ + ....: MatchingCoveredGraph(graphs.TricornGraph()).is_brick() and \ + ....: MatchingCoveredGraph(graphs.CubeplexGraph()).is_brick() and \ + ....: MatchingCoveredGraph(graphs.TwinplexGraph()).is_brick() and \ + ....: MatchingCoveredGraph(graphs.WagnerGraph()).is_brick() + True + + The Murty graph is the smallest simple brick that is not odd-intercyclic:: + + sage: M = graphs.MurtyGraph() + sage: G = MatchingCoveredGraph(M) + sage: G.is_brick() + True + + A circular ladder graph of order six or more on `2n` vertices for an + odd `n` is a brick:: + + sage: n = 11 + sage: CL = graphs.CircularLadderGraph(n) + sage: G = MatchingCoveredGraph(CL) + sage: G.is_brick() + True + + A moebius ladder graph of order eight or more on `2n` vertices for an + even `n` is a brick:: + + sage: n = 10 + sage: ML = graphs.MoebiusLadderGraph(n) + sage: G = MatchingCoveredGraph(ML) + sage: G.is_brick() + True + + A wheel graph of an even order is a brick:: + + sage: W = graphs.WheelGraph(10) + sage: G = MatchingCoveredGraph(W) + sage: G.is_brick() + True + + A graph that is isomorphic to a truncated biwheel graph is a brick:: + + sage: TB = graphs.TruncatedBiwheelGraph(15) + sage: G = MatchingCoveredGraph(TB) + sage: G.is_brick() + True + + Each of the graphs in the staircase graph family with order eight or + more is a brick:: + + sage: ST = graphs.StaircaseGraph(9) + sage: G = MatchingCoveredGraph(ST) + sage: G.is_brick() + True + + Bricks are 3-connected:: + + sage: P = graphs.PetersenGraph() + sage: G = MatchingCoveredGraph(P) + sage: G.is_brick() + True + sage: G.is_triconnected() + True + + Bricks are bicritical:: + + sage: P = graphs.PetersenGraph() + sage: G = MatchingCoveredGraph(P) + sage: G.is_brick() + True + sage: G.is_bicritical() + True + + One may set the ``coNP_certificate`` to be ``True``:: + + sage: K4 = graphs.CompleteGraph(4) + sage: G = MatchingCoveredGraph(K4) + sage: G.is_brick(coNP_certificate=True) + sage: # K(4) + + If the input matching covered graph is bipartite, a + :exc:`ValueError` is thrown:: + + sage: H = graphs.HexahedralGraph() + sage: G = MatchingCoveredGraph(H) + sage: G.is_brick() + Traceback (most recent call last): + ... + ValueError: the input graph is bipartite + sage: J = graphs.HeawoodGraph() + sage: G = MatchingCoveredGraph(J) + sage: G.is_brick(coNP_certificate=True) + Traceback (most recent call last): + ... + ValueError: the input graph is bipartite + .. SEEALSO:: - :meth:`~sage.graphs.graph.Graph.is_bicritical` + - :meth:`~sage.graphs.graph.Graph.is_bicritical` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.is_brace` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.bricks_and_braces` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_bricks` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_petersen_bricks` """ if self.is_bipartite(): raise ValueError('the input graph is bipartite') @@ -2750,23 +2888,21 @@ def is_brick(self, coNP_certificate=False): # Check for 2-vertex cuts in P and S nodes for u in spqr_tree: if u[0] == 'P': - two_vertex_cut.extend(u[1].vertices()) + two_vertex_cut.extend(u[1]) break elif u[0] == 'S' and u[1].order() > 3: - s_vertex_set = set(u[1].vertices()) - for v in u[1].vertices(): - s_vertex_set -= {v} | set(u[1].neighbors(v)) - two_vertex_cut.extend([v, next(iter(s_vertex_set))]) - break + s_vertex_set = set(u[1]) + s_vertex_set -= {next(u[1].vertex_iterator())} | set(u[1].neighbors(next(u[1].vertex_iterator()))) + two_vertex_cut.extend([next(u[1].vertex_iterator()), next(iter(s_vertex_set))]) # If no 2-vertex cut found, look for R nodes if not two_vertex_cut: - R_frequency = {u: 0 for u in self} - for u in spqr_tree.vertices(): - if u[0] == 'R': - for v in u[1].vertices(): - R_frequency[v] += 1 - two_vertex_cut = [u for u in self if R_frequency[u] >= 2][:2] + from collections import Counter + R_frequency = Counter() + for t, g in spqr_tree: + if t == 'R': + R_frequency.update(g) + two_vertex_cut = [u for u, f in R_frequency.items() if f >= 2][:2] # We obtain a 2-vertex cut (u, v) H = Graph(self) @@ -2774,10 +2910,9 @@ def is_brick(self, coNP_certificate=False): # Check if all components of H are odd components = H.connected_components() - are_all_odd_components = all(len(c) % 2 for c in components) # Find a nontrivial odd component - if are_all_odd_components: + if all(len(c) % 2 for c in components): nontrivial_odd_component = next((c for c in components if len(c) > 1), None) else: nontrivial_odd_component = components[0] + [two_vertex_cut[0]] From adb95be3d242e4024cc1166b90a56c3a6604098a Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 21 Dec 2024 11:28:27 +0530 Subject: [PATCH 15/39] updated is_brace() --- src/sage/graphs/matching_covered_graph.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index e9f90686b22..7f3e249c128 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2383,7 +2383,7 @@ def is_brace(self, coNP_certificate=False): - If the input bipartite matching covered graph is a brace, a boolean ``True`` is returned if ``coNP_certificate`` is set to ``False`` - otherwise a pair ``(True, None, None)`` is returned. + otherwise a tuple ``(True, None, None)`` is returned. - If the input bipartite matching covered graph is not a brace, a boolean ``False`` is returned if ``coNP_certificate`` is set to @@ -2414,8 +2414,8 @@ def is_brace(self, coNP_certificate=False): sage: G.is_brace() True - A circular ladder graph on `2n` vertices for `n \equiv 0 (\mod 2)` is - a brace:: + A circular ladder graph of order eight or more on `2n` vertices for + an even `n` is a brace:: sage: n = 10 sage: CL = graphs.CircularLadderGraph(n) @@ -2423,8 +2423,8 @@ def is_brace(self, coNP_certificate=False): sage: G.is_brace() True - A moebius ladder graph on `2n` vertices for `n \equiv 1 (\mod 2)` is - a brace:: + A moebius ladder graph of order six or more on `2n` vertices for an odd + `n` is a brace:: sage: n = 11 sage: ML = graphs.MoebiusLadderGraph(n) @@ -2602,6 +2602,13 @@ def is_brace(self, coNP_certificate=False): Traceback (most recent call last): ... ValueError: the input graph is not bipartite + + .. SEEALSO:: + + - :meth:`~sage.graphs.graph.Graph.is_bicritical` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.is_brick` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.bricks_and_braces` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_braces` """ if not self.is_bipartite(): raise ValueError('the input graph is not bipartite') From e101d33ff0880461202244feafd404717be90d04 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 21 Dec 2024 11:29:29 +0530 Subject: [PATCH 16/39] updated see also --- src/sage/graphs/matching_covered_graph.py | 98 +++++++++++------------ 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 7f3e249c128..788ccb88467 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -1616,13 +1616,13 @@ def allow_loops(self, new, check=True): .. SEEALSO:: - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops` """ if new: raise ValueError('loops are not allowed in ' @@ -1657,13 +1657,13 @@ def allows_loops(self): .. SEEALSO:: - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops` """ return False @@ -2251,13 +2251,13 @@ def has_loops(self): .. SEEALSO:: - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops` """ return False @@ -3001,13 +3001,13 @@ def loop_edges(self, labels=True): .. SEEALSO:: - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops` """ return [] @@ -3068,13 +3068,13 @@ def loop_vertices(self): .. SEEALSO:: - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops` """ return [] @@ -3140,13 +3140,13 @@ def number_of_loops(self): .. SEEALSO:: - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops` """ return 0 @@ -3232,13 +3232,13 @@ def remove_loops(self, vertices=None): .. SEEALSO:: - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops`, - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops` """ from collections.abc import Iterable From b7c274e8fba1719b202d31b189948c4000630a81 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 21 Dec 2024 12:35:20 +0530 Subject: [PATCH 17/39] updated the doctests --- src/sage/graphs/matching_covered_graph.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 788ccb88467..37c291edd97 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2057,8 +2057,7 @@ def maximal_barrier(self, vertex): sage: J = G.copy() sage: J.delete_vertices(B) - sage: all(len(K)%2 != 0 for K in J.connected_components()) - ... + sage: all(len(K)%2 != 0 for K in J.connected_components(sort=True)) True Let `B` be a maximal barrier in a matching covered graph `G` and let @@ -2585,7 +2584,7 @@ def is_brace(self, coNP_certificate=False): sage: C = graphs.CycleGraph(6) sage: D = MatchingCoveredGraph(C) sage: D.is_brace(coNP_certificate=True) - (False, [(0, 5, None), (2, 3, None)], {0, 1, 2}) + (False, [(1, 2, None), (5, 4, None)], {0, 1, 5}) If the input matching covered graph is nonbipartite, a :exc:`ValueError` is thrown:: @@ -2825,7 +2824,12 @@ def is_brick(self, coNP_certificate=False): sage: K4 = graphs.CompleteGraph(4) sage: G = MatchingCoveredGraph(K4) sage: G.is_brick(coNP_certificate=True) - sage: # K(4) + (True, None, None) + sage: # K(4) ⊙ K(3, 3) is nonbipartite but not a brick + sage: H = graphs.MurtyGraph(); H.delete_edge(0, 1) + sage: G = MatchingCoveredGraph(H) + sage: G.is_brick(coNP_certificate=True) + (False, [(5, 2, None), (6, 3, None), (7, 4, None)], {5, 6, 7}) If the input matching covered graph is bipartite, a :exc:`ValueError` is thrown:: From b8fdab8619b38f1eb9791efd45205f99b07ef5fc Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 21 Dec 2024 12:40:32 +0530 Subject: [PATCH 18/39] updated connected components parameter --- src/sage/graphs/matching_covered_graph.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 37c291edd97..37a31b72bca 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2066,7 +2066,7 @@ def maximal_barrier(self, vertex): `v` is the end of that edge in `V(K)`, then `M \cap E(K)` is a perfect matching of `K - v`:: - sage: K = J.subgraph(vertices=(J.connected_components())[0]) + sage: K = J.subgraph(vertices=(J.connected_components(sort=True))[0]) sage: # Let F := \partial_G(K) and T := M \cap F sage: F = [edge for edge in G.edge_iterator() ....: if (edge[0] in K and edge[1] not in K) @@ -2090,7 +2090,7 @@ def maximal_barrier(self, vertex): `G - B` is factor critical:: sage: all((K.subgraph(vertices=connected_component)).is_factor_critical() - ....: for connected_component in K.connected_components() + ....: for connected_component in K.connected_components(sort=True) ....: ) True @@ -2876,7 +2876,7 @@ def is_brick(self, coNP_certificate=False): # Let K be a nontrivial odd component of H := G - B. Note that # there exists at least one such K since G is nonbipartite nontrivial_odd_component = next( - (component for component in H.connected_components() + (component for component in H.connected_components(sort=True) if len(component) % 2 and len(component) > 1), None ) @@ -2920,7 +2920,7 @@ def is_brick(self, coNP_certificate=False): H.delete_vertices(two_vertex_cut) # Check if all components of H are odd - components = H.connected_components() + components = H.connected_components(sort=True) # Find a nontrivial odd component if all(len(c) % 2 for c in components): From cb4d3d133aeced010a99131f8221a7c9fb7b6ecc Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 21 Dec 2024 12:43:24 +0530 Subject: [PATCH 19/39] updated the return statement of is_brick() --- src/sage/graphs/matching_covered_graph.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 37a31b72bca..328db2950e7 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2885,7 +2885,7 @@ def is_brick(self, coNP_certificate=False): for u, v, w in self.edge_iterator() if (u in nontrivial_odd_component) ^ (v in nontrivial_odd_component)] - return False, C, nontrivial_odd_component + return (False, C, set(nontrivial_odd_component)) # Check if G is 3-connected if self.is_triconnected(): @@ -2933,7 +2933,7 @@ def is_brick(self, coNP_certificate=False): if (u in nontrivial_odd_component) ^ (v in nontrivial_odd_component)] # Edge (u, v, w) in C are formatted so that u is in a nontrivial odd component - return (False, C, nontrivial_odd_component) if coNP_certificate else False + return (False, C, set(nontrivial_odd_component)) if coNP_certificate else False @doc_index('Overwritten methods') def loop_edges(self, labels=True): From d6701686e1ab15f010ac825ec3a019ec5c922743 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 21 Dec 2024 12:43:50 +0530 Subject: [PATCH 20/39] updated the return statement of is_brace() --- src/sage/graphs/matching_covered_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 328db2950e7..76c5cc2dc37 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2693,7 +2693,7 @@ def dfs(v, visited, neighbor_iterator): for u, v, w in self.edge_iterator() if (u in X) ^ (v in X)] - return False, C, X + return (False, C, set(X)) return (True, None, None) if coNP_certificate else True From 03771eea4f41b77ff77635873b28856fd36ed0b5 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 21 Dec 2024 19:32:59 +0530 Subject: [PATCH 21/39] updated is_brace() --- src/sage/graphs/matching_covered_graph.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 76c5cc2dc37..fce968cb1dd 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2584,7 +2584,7 @@ def is_brace(self, coNP_certificate=False): sage: C = graphs.CycleGraph(6) sage: D = MatchingCoveredGraph(C) sage: D.is_brace(coNP_certificate=True) - (False, [(1, 2, None), (5, 4, None)], {0, 1, 5}) + (False, [(0, 5, None), (2, 3, None)], {0, 1, 2}) If the input matching covered graph is nonbipartite, a :exc:`ValueError` is thrown:: @@ -2637,7 +2637,7 @@ def is_brace(self, coNP_certificate=False): # For each edge (a, b) in E(H(e)) ∩ M with a in A, b —> a in D(e). # For each edge (a, b) in E(H(e)) with a in A, a —> b in D(e). - for a, b, *_ in H.edge_iterator(): + for a, b in H.edge_iterator(labels=False, sort_vertices=True): if a in B: a, b = b, a @@ -2678,7 +2678,7 @@ def dfs(v, visited, neighbor_iterator): X = set() dfs(root, X, D.neighbor_out_iterator) - for a, b in H.edge_iterator(labels=False): + for a, b in H.edge_iterator(labels=False, sort_vertices=True): if (a in X) ^ (b in X): x = a if a in A else b color_class = x not in X @@ -2690,7 +2690,7 @@ def dfs(v, visited, neighbor_iterator): # Compute the nontrivial tight cut C := ∂(Y) C = [(u, v, w) if u in X else (v, u, w) - for u, v, w in self.edge_iterator() + for u, v, w in self.edge_iterator(sort_vertices=True) if (u in X) ^ (v in X)] return (False, C, set(X)) From 8a14b4c10bf11724ed3ccd1130e1ea4c604b7968 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 21 Dec 2024 19:33:42 +0530 Subject: [PATCH 22/39] updated is_brick() --- src/sage/graphs/matching_covered_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index fce968cb1dd..cf5feff8752 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2924,7 +2924,7 @@ def is_brick(self, coNP_certificate=False): # Find a nontrivial odd component if all(len(c) % 2 for c in components): - nontrivial_odd_component = next((c for c in components if len(c) > 1), None) + nontrivial_odd_component = next(c for c in components if len(c) > 1) else: nontrivial_odd_component = components[0] + [two_vertex_cut[0]] From d295290b1cd230b2780237355c5d60b1ffab8adc Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 21 Dec 2024 22:52:35 +0530 Subject: [PATCH 23/39] updated doctests --- src/sage/graphs/matching_covered_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index cf5feff8752..125289de79f 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2584,7 +2584,7 @@ def is_brace(self, coNP_certificate=False): sage: C = graphs.CycleGraph(6) sage: D = MatchingCoveredGraph(C) sage: D.is_brace(coNP_certificate=True) - (False, [(0, 5, None), (2, 3, None)], {0, 1, 2}) + (False, [(0, 1, None), (4, 3, None)], {0, 4, 5}) If the input matching covered graph is nonbipartite, a :exc:`ValueError` is thrown:: From b619b575e1d7b95c5c1520fb986b6a4b0ac310ce Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sun, 22 Dec 2024 08:35:34 +0530 Subject: [PATCH 24/39] updated the doctests --- src/sage/graphs/matching_covered_graph.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 125289de79f..d0910aef5c6 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2583,8 +2583,17 @@ def is_brace(self, coNP_certificate=False): (True, None, None) sage: C = graphs.CycleGraph(6) sage: D = MatchingCoveredGraph(C) - sage: D.is_brace(coNP_certificate=True) - (False, [(0, 1, None), (4, 3, None)], {0, 4, 5}) + sage: is_brace, nontrivial_tight_cut, nontrivial_odd_component = \ + ....: D.is_brace(coNP_certificate=True) + sage: is_brace is False + True + sage: J = C.subgraph(vertices=nontrivial_odd_component) + sage: J.is_isomorphic(graphs.PathGraph(3)) + True + sage: len(nontrivial_tight_cut) == 2 + True + sage: for u, v, *_ in nontrivial_tight_cut: + ....: assert (u in nontrivial_odd_component and v not in nontrivial_odd_component) If the input matching covered graph is nonbipartite, a :exc:`ValueError` is thrown:: From babb6bba75d5618cb3975390dd29081198574b2e Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Mon, 23 Dec 2024 19:33:01 +0530 Subject: [PATCH 25/39] introduced tight_cut_decomposition() --- src/sage/graphs/matching_covered_graph.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index d0910aef5c6..9c80721e0dd 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -97,7 +97,6 @@ ``number_of_braces()`` | Return the number of braces. ``number_of_bricks()`` | Return the number of bricks. ``number_of_petersen_bricks()`` | Return the number of Petersen bricks. - ``tight_cut_decomposition()`` | Return a tight cut decomposition. **Removability and ear decomposition** @@ -3261,6 +3260,14 @@ def remove_loops(self, vertices=None): return + @doc_index('Bricks, braces and tight cut decomposition') + def tight_cut_decomposition(self): + r""" + Return a maximal set of laminar nontrivial tight cuts and a + corresponding vertex set partition. + """ + raise NotImplementedError() + @doc_index('Miscellaneous methods') def update_matching(self, matching): r""" From 7455ac043fdf58072bca87f5a3d3cee751cce619 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Mon, 23 Dec 2024 19:48:14 +0530 Subject: [PATCH 26/39] removed a whitespace --- src/sage/graphs/matching_covered_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 9c80721e0dd..3cf282b1f09 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -3267,7 +3267,7 @@ def tight_cut_decomposition(self): corresponding vertex set partition. """ raise NotImplementedError() - + @doc_index('Miscellaneous methods') def update_matching(self, matching): r""" From e1208c89e8ebb7491458951b30d1eb10ef4b3c17 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Wed, 1 Jan 2025 20:09:07 +0530 Subject: [PATCH 27/39] updated is_brace() --- src/sage/graphs/matching_covered_graph.py | 33 ++++++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 3cf282b1f09..103e9975084 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2566,7 +2566,7 @@ def is_brace(self, coNP_certificate=False): sage: len(L) == len(M) True - A cycle graph of order six of more is a bipartite matching covered + A cycle graph of order six or more is a bipartite matching covered graph, but is not a brace:: sage: C = graphs.CycleGraph(10) @@ -2574,6 +2574,16 @@ def is_brace(self, coNP_certificate=False): sage: G.is_brace() False + A ladder graph of order six or more is a bipartite matching covered + graph, that is not a brace. The tight cut decomposition of a ladder + graph produces a list graphs the underlying graph of each of which + is isomorphic to a 4-cycle:: + + sage: L = graphs.LadderGraph(10) + sage: G = MatchingCoveredGraph(L) + sage: G.is_brace() + False + One may set the ``coNP_certificate`` to be ``True``:: sage: H = graphs.HexahedralGraph() @@ -2593,6 +2603,19 @@ def is_brace(self, coNP_certificate=False): True sage: for u, v, *_ in nontrivial_tight_cut: ....: assert (u in nontrivial_odd_component and v not in nontrivial_odd_component) + sage: L = graphs.LadderGraph(3) # A ladder graph with two constituent braces + sage: G = MatchingCoveredGraph(L) + sage: is_brace, nontrivial_tight_cut, nontrivial_odd_component = G.is_brace(coNP_certificate=True) + sage: is_brace is False + True + sage: G1 = L.copy() + sage: G1.merge_vertices(list(nontrivial_odd_component)) + sage: G1.to_simple().is_isomorphic(graphs.CycleGraph(4)) + True + sage: G2 = L.copy() + sage: G2.merge_vertices([v for v in G if v not in nontrivial_odd_component]) + sage: G2.to_simple().is_isomorphic(graphs.CycleGraph(4)) + True If the input matching covered graph is nonbipartite, a :exc:`ValueError` is thrown:: @@ -2612,7 +2635,6 @@ def is_brace(self, coNP_certificate=False): .. SEEALSO:: - - :meth:`~sage.graphs.graph.Graph.is_bicritical` - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.is_brick` - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.bricks_and_braces` - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_braces` @@ -2635,7 +2657,7 @@ def is_brace(self, coNP_certificate=False): H = Graph(self, multiedges=False) H.delete_vertices([u, v]) - if not H.is_matching_covered(list(matching - set([e]))): + if not H.is_connected()or not H.is_matching_covered(list(matching - set([e]))): if not coNP_certificate: return False @@ -2686,6 +2708,8 @@ def dfs(v, visited, neighbor_iterator): X = set() dfs(root, X, D.neighbor_out_iterator) + color_class = None + for a, b in H.edge_iterator(labels=False, sort_vertices=True): if (a in X) ^ (b in X): x = a if a in A else b @@ -2694,7 +2718,7 @@ def dfs(v, visited, neighbor_iterator): # Obtain the color class Z ∈ {A, B} such that X ∩ Z is a vertex cover for T(e) # Thus, obtain Y := X + v - X.add(u if (not color_class and u in A) or (color_class and u in B) else v) + X.add(u if (not color_class and u in A) or (color_class and u in B) or (color_class is None) else v) # Compute the nontrivial tight cut C := ∂(Y) C = [(u, v, w) if u in X else (v, u, w) @@ -2862,6 +2886,7 @@ def is_brick(self, coNP_certificate=False): - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.bricks_and_braces` - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_bricks` - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_petersen_bricks` + - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.tight_cut_decomposition` """ if self.is_bipartite(): raise ValueError('the input graph is bipartite') From e4ceffe1d63440668fdbd4801dd472567751313e Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 4 Jan 2025 19:12:29 +0530 Subject: [PATCH 28/39] updated is_brick() --- src/sage/graphs/matching_covered_graph.py | 160 +++++++++++++++++++--- 1 file changed, 138 insertions(+), 22 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 103e9975084..b4849ef4e23 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2750,13 +2750,38 @@ def is_brick(self, coNP_certificate=False): - If the input nonbipartite matching covered graph is a brick, a boolean ``True`` is returned if ``coNP_certificate`` is set to - ``False`` otherwise a tuple ``(True, None, None)`` is returned. + ``False`` otherwise a 4-tuple ``(True, None, None, None)`` is + returned. - If the input nonbipartite matching covered graph is not a brick, a boolean ``False`` is returned if ``coNP_certificate`` is set to - ``False`` otherwise a tuple of boolean ``False``, a list of - edges constituting a nontrivial tight cut and a set of vertices of - one of the shores of the nontrivial tight cut is returned. + ``False``. + + - If ``coNP_certificate`` is set to ``True`` and the input nonbipartite + graph is not a brick, a 4-tuple of + + 1. a boolean ``False``, + + 2. a list of list of edges each list constituting a nontrivial tight + cut collectively representing a laminar tight cut, + + 3. a list of set of vertices of one of the shores of those respective + nontrivial tight cuts. + + i. In case of nontrivial barrier cuts, each of the shores is a + nontrivial odd component wrt a nontrivial barrier, thus the + returned list forms mutually exclusive collection of (odd) + sets. + + ii. Otherwise each of the nontrivial tight cuts is a 2-separation + cut, each of the shores form a subset sequence, with the `i`th + shore being a proper subset of the `i + 1`th shore. + + 4. a string showing whether the nontrivial tight cuts are barrier + cuts (if the string is 'nontrivial barrier cuts'), or 2-separation + cuts (if the string is 'nontrivial 2-separation cuts') + + is returned. EXAMPLES: @@ -2856,12 +2881,84 @@ def is_brick(self, coNP_certificate=False): sage: K4 = graphs.CompleteGraph(4) sage: G = MatchingCoveredGraph(K4) sage: G.is_brick(coNP_certificate=True) - (True, None, None) + (True, None, None, None) sage: # K(4) ⊙ K(3, 3) is nonbipartite but not a brick sage: H = graphs.MurtyGraph(); H.delete_edge(0, 1) sage: G = MatchingCoveredGraph(H) sage: G.is_brick(coNP_certificate=True) - (False, [(5, 2, None), (6, 3, None), (7, 4, None)], {5, 6, 7}) + (False, [[(5, 2, None), (6, 3, None), (7, 4, None)]], [{5, 6, 7}], + 'nontrivial barrier cuts') + sage: H = Graph([ + ....: (0, 12), (0, 13), (0, 15), (1, 4), (1, 13), (1, 14), + ....: (1, 19), (2, 4), (2, 13), (2, 14), (2, 17), (3, 9), + ....: (3, 13), (3, 16), (3, 21), (4, 6), (4, 7), (5, 7), + ....: (5, 8), (5, 12), (6, 8), (6, 11), (7, 10), (8, 9), + ....: (9, 10), (10, 11), (11, 12), (14, 15), (14, 16), (15, 16), + ....: (17, 18), (17, 21), (18, 19), (18, 20), (19, 20), (20, 21) + ....: ]) + sage: G = MatchingCoveredGraph(H) + sage: G.is_brick(coNP_certificate=True) + (False, + [[(12, 0, None), (4, 1, None), (4, 2, None), (9, 3, None)], + [(19, 1, None), (17, 2, None), (21, 3, None)], + [(15, 0, None), (14, 1, None), (14, 2, None), (16, 3, None)]], + [{4, 5, 6, 7, 8, 9, 10, 11, 12}, {17, 18, 19, 20, 21}, {14, 15, 16}], + 'nontrivial barrier cuts') + sage: J = Graph([ + ....: (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), + ....: (0, 6), (0, 7), (0, 8), (0, 9), (0, 10), + ....: (1, 2), (1, 11), (2, 11), (3, 4), (3, 11), + ....: (4, 11), (5, 6), (5, 11), (6, 11), (7, 8), + ....: (7, 11), (8, 11), (9, 10), (9, 11), (10, 11) + ....: ]) + sage: G = MatchingCoveredGraph(J) + sage: G.is_brick(coNP_certificate=True) + (False, + [[(0, 3, None), + (0, 4, None), + (0, 5, None), + (0, 6, None), + (0, 7, None), + (0, 8, None), + (0, 9, None), + (0, 10, None), + (1, 11, None), + (2, 11, None)], + [(0, 5, None), + (0, 6, None), + (0, 7, None), + (0, 8, None), + (0, 9, None), + (0, 10, None), + (1, 11, None), + (2, 11, None), + (3, 11, None), + (4, 11, None)], + [(0, 7, None), + (0, 8, None), + (0, 9, None), + (0, 10, None), + (1, 11, None), + (2, 11, None), + (3, 11, None), + (4, 11, None), + (5, 11, None), + (6, 11, None)], + [(0, 9, None), + (0, 10, None), + (1, 11, None), + (2, 11, None), + (3, 11, None), + (4, 11, None), + (5, 11, None), + (6, 11, None), + (7, 11, None), + (8, 11, None)]], + [{0, 1, 2}, + {0, 1, 2, 3, 4}, + {0, 1, 2, 3, 4, 5, 6}, + {0, 1, 2, 3, 4, 5, 6, 7, 8}], + 'nontrivial 2-separation cuts') If the input matching covered graph is bipartite, a :exc:`ValueError` is thrown:: @@ -2908,21 +3005,22 @@ def is_brick(self, coNP_certificate=False): # Let K be a nontrivial odd component of H := G - B. Note that # there exists at least one such K since G is nonbipartite - nontrivial_odd_component = next( - (component for component in H.connected_components(sort=True) - if len(component) % 2 and len(component) > 1), None - ) + nontrivial_odd_components = [ + set(component) for component in H.connected_components(sort=True) + if len(component) % 2 and len(component) > 1 + ] - # Find a nontrivial barrier cut - C = [(u, v, w) if u in nontrivial_odd_component else (v, u, w) - for u, v, w in self.edge_iterator() - if (u in nontrivial_odd_component) ^ (v in nontrivial_odd_component)] + # Find a laminar set of nontrivial barrier cuts + C = [[(u, v, w) if u in nontrivial_odd_component else (v, u, w) + for u, v, w in self.edge_iterator() + if (u in nontrivial_odd_component) ^ (v in nontrivial_odd_component)] + for nontrivial_odd_component in nontrivial_odd_components] - return (False, C, set(nontrivial_odd_component)) + return (False, C, nontrivial_odd_components, 'nontrivial barrier cuts') # Check if G is 3-connected if self.is_triconnected(): - return (True, None, None) if coNP_certificate else True + return (True, None, None, None) if coNP_certificate else True # G has a 2-vertex cut # Compute the SPQR-tree decomposition @@ -2934,6 +3032,7 @@ def is_brick(self, coNP_certificate=False): if u[0] == 'P': two_vertex_cut.extend(u[1]) break + elif u[0] == 'S' and u[1].order() > 3: s_vertex_set = set(u[1]) s_vertex_set -= {next(u[1].vertex_iterator())} | set(u[1].neighbors(next(u[1].vertex_iterator()))) @@ -2943,6 +3042,7 @@ def is_brick(self, coNP_certificate=False): if not two_vertex_cut: from collections import Counter R_frequency = Counter() + for t, g in spqr_tree: if t == 'R': R_frequency.update(g) @@ -2956,17 +3056,33 @@ def is_brick(self, coNP_certificate=False): components = H.connected_components(sort=True) # Find a nontrivial odd component + nontrivial_tight_cut_variation = None + if all(len(c) % 2 for c in components): - nontrivial_odd_component = next(c for c in components if len(c) > 1) + nontrivial_tight_cut_variation = 'nontrivial barrier cuts' + nontrivial_odd_components = [set(component) for component in components if len(component) > 1] + else: - nontrivial_odd_component = components[0] + [two_vertex_cut[0]] + nontrivial_tight_cut_variation = 'nontrivial 2-separation cuts' + nontrivial_odd_components = [] + + for index, component in enumerate(components): + if index == len(components) - 1: + continue + elif not index: + nontrivial_odd_components.append(set(components[0] + [two_vertex_cut[0]])) + else: + nontrivial_odd_component = nontrivial_odd_components[-1].copy() + nontrivial_odd_component.update(component) + nontrivial_odd_components.append(nontrivial_odd_component) - C = [(u, v, w) if u in nontrivial_odd_component else (v, u, w) - for u, v, w in self.edge_iterator() - if (u in nontrivial_odd_component) ^ (v in nontrivial_odd_component)] + C = [[(u, v, w) if u in nontrivial_odd_component else (v, u, w) + for u, v, w in self.edge_iterator() + if (u in nontrivial_odd_component) ^ (v in nontrivial_odd_component)] + for nontrivial_odd_component in nontrivial_odd_components] # Edge (u, v, w) in C are formatted so that u is in a nontrivial odd component - return (False, C, set(nontrivial_odd_component)) if coNP_certificate else False + return (False, C, nontrivial_odd_components, nontrivial_tight_cut_variation) if coNP_certificate else False @doc_index('Overwritten methods') def loop_edges(self, labels=True): From 5133db5da3bad28b5d4b0a4153a4a443c8004862 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 4 Jan 2025 19:53:13 +0530 Subject: [PATCH 29/39] added tight_cut_decomposition() to todo list --- src/sage/graphs/matching_covered_graph.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index b4849ef4e23..6b81cc2b9dd 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -97,6 +97,7 @@ ``number_of_braces()`` | Return the number of braces. ``number_of_bricks()`` | Return the number of bricks. ``number_of_petersen_bricks()`` | Return the number of Petersen bricks. + ``tight_cut_decomposition()`` | Return a maximal set of laminar nontrivial tight cuts and a corresponding vertex set partition. **Removability and ear decomposition** @@ -2983,7 +2984,6 @@ def is_brick(self, coNP_certificate=False): - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.bricks_and_braces` - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_bricks` - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_petersen_bricks` - - :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.tight_cut_decomposition` """ if self.is_bipartite(): raise ValueError('the input graph is bipartite') @@ -3401,14 +3401,6 @@ def remove_loops(self, vertices=None): return - @doc_index('Bricks, braces and tight cut decomposition') - def tight_cut_decomposition(self): - r""" - Return a maximal set of laminar nontrivial tight cuts and a - corresponding vertex set partition. - """ - raise NotImplementedError() - @doc_index('Miscellaneous methods') def update_matching(self, matching): r""" From f2a5abe0fedc286fae9394a5a4582d3f55775ce9 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 4 Jan 2025 20:27:38 +0530 Subject: [PATCH 30/39] added a missing whitespace --- src/sage/graphs/matching_covered_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 6b81cc2b9dd..deefe46eeee 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2658,7 +2658,7 @@ def is_brace(self, coNP_certificate=False): H = Graph(self, multiedges=False) H.delete_vertices([u, v]) - if not H.is_connected()or not H.is_matching_covered(list(matching - set([e]))): + if not H.is_connected() or not H.is_matching_covered(list(matching - set([e]))): if not coNP_certificate: return False From f32ef0f083b8fcc5f0d816a8209ce94a7e3a1b3a Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 4 Jan 2025 20:38:19 +0530 Subject: [PATCH 31/39] fixed RST215 error --- src/sage/graphs/matching_covered_graph.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index deefe46eeee..c4545d964ad 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2769,14 +2769,14 @@ def is_brick(self, coNP_certificate=False): 3. a list of set of vertices of one of the shores of those respective nontrivial tight cuts. - i. In case of nontrivial barrier cuts, each of the shores is a - nontrivial odd component wrt a nontrivial barrier, thus the - returned list forms mutually exclusive collection of (odd) - sets. - - ii. Otherwise each of the nontrivial tight cuts is a 2-separation - cut, each of the shores form a subset sequence, with the `i`th - shore being a proper subset of the `i + 1`th shore. + - In case of nontrivial barrier cuts, each of the shores is a + nontrivial odd component wrt a nontrivial barrier, thus the + returned list forms mutually exclusive collection of (odd) + sets. + + - Otherwise each of the nontrivial tight cuts is a 2-separation + cut, each of the shores form a subset sequence, with the `i`th + shore being a proper subset of the `i + 1`th shore. 4. a string showing whether the nontrivial tight cuts are barrier cuts (if the string is 'nontrivial barrier cuts'), or 2-separation From fa42cd3943c2e3c462cb4681d6a7fcdece9287b8 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Mon, 13 Jan 2025 17:27:58 +0530 Subject: [PATCH 32/39] updated is_brick() --- src/sage/graphs/matching_covered_graph.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index c4545d964ad..d85845a667d 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2751,7 +2751,7 @@ def is_brick(self, coNP_certificate=False): - If the input nonbipartite matching covered graph is a brick, a boolean ``True`` is returned if ``coNP_certificate`` is set to - ``False`` otherwise a 4-tuple ``(True, None, None, None)`` is + ``False``, otherwise a 4-tuple ``(True, None, None, None)`` is returned. - If the input nonbipartite matching covered graph is not a brick, a @@ -2763,20 +2763,20 @@ def is_brick(self, coNP_certificate=False): 1. a boolean ``False``, - 2. a list of list of edges each list constituting a nontrivial tight - cut collectively representing a laminar tight cut, + 2. a list of lists of edges, each list constituting a nontrivial + tight cut collectively representing a laminar tight cut, 3. a list of set of vertices of one of the shores of those respective - nontrivial tight cuts. + nontrivial tight cuts: - - In case of nontrivial barrier cuts, each of the shores is a - nontrivial odd component wrt a nontrivial barrier, thus the - returned list forms mutually exclusive collection of (odd) - sets. + #. In case of nontrivial barrier cuts, each of the shores is a + nontrivial odd component with respect to a nontrivial barrier, + thus the returned list forms mutually exclusive collection of + (odd) sets. - - Otherwise each of the nontrivial tight cuts is a 2-separation - cut, each of the shores form a subset sequence, with the `i`th - shore being a proper subset of the `i + 1`th shore. + #. Otherwise each of the nontrivial tight cuts is a 2-separation + cut, each of the shores form a subset sequence, with the + `i` th shore being a proper subset of the `i + 1` th shore. 4. a string showing whether the nontrivial tight cuts are barrier cuts (if the string is 'nontrivial barrier cuts'), or 2-separation From cdaf8e780b9193faf9001dcee37751f8a4400caf Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Tue, 14 Jan 2025 18:28:09 +0530 Subject: [PATCH 33/39] updated is_brace() --- src/sage/graphs/matching_covered_graph.py | 72 ++++++++++++++++------- 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 6b4ac097b42..f54840f81ff 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2382,13 +2382,24 @@ def is_brace(self, coNP_certificate=False): - If the input bipartite matching covered graph is a brace, a boolean ``True`` is returned if ``coNP_certificate`` is set to ``False`` - otherwise a tuple ``(True, None, None)`` is returned. + otherwise a 5-tuple ``(True, None, None, None, None)`` is returned. - If the input bipartite matching covered graph is not a brace, a boolean ``False`` is returned if ``coNP_certificate`` is set to - ``False`` otherwise a tuple of boolean ``False``, a list of - edges constituting a nontrivial tight cut and a set of vertices of - one of the shores of the nontrivial tight cut is returned. + ``False`` otherwise a 5-tuple of + + 1. a boolean ``False``, + + 2. a list of edges constituting a nontrivial tight cut (which is a + nontrivial barrier cut) + + 3. a set of vertices of one of the shores of the nontrivial tight cut + + 4. a string 'nontrivial tight cut' + + 5. a set of vertices showing the respective barrier + + is returned. EXAMPLES: @@ -2590,10 +2601,11 @@ def is_brace(self, coNP_certificate=False): sage: H = graphs.HexahedralGraph() sage: G = MatchingCoveredGraph(H) sage: G.is_brace(coNP_certificate=True) - (True, None, None) + (True, None, None, None, None) sage: C = graphs.CycleGraph(6) sage: D = MatchingCoveredGraph(C) - sage: is_brace, nontrivial_tight_cut, nontrivial_odd_component = \ + sage: is_brace, nontrivial_tight_cut, nontrivial_odd_component, \ + ....: nontrivial_tight_cut_variant, cut_identifier = \ ....: D.is_brace(coNP_certificate=True) sage: is_brace is False True @@ -2602,11 +2614,18 @@ def is_brace(self, coNP_certificate=False): True sage: len(nontrivial_tight_cut) == 2 True + sage: nontrivial_tight_cut_variant + 'nontrivial barrier cut' + sage: # Corresponding barrier + sage: cut_identifier == {a for u, v, *_ in nontrivial_tight_cut for a in [u, v] \ + ....: if a not in nontrivial_odd_component} + True sage: for u, v, *_ in nontrivial_tight_cut: ....: assert (u in nontrivial_odd_component and v not in nontrivial_odd_component) sage: L = graphs.LadderGraph(3) # A ladder graph with two constituent braces sage: G = MatchingCoveredGraph(L) - sage: is_brace, nontrivial_tight_cut, nontrivial_odd_component = G.is_brace(coNP_certificate=True) + sage: is_brace, nontrivial_tight_cut, nontrivial_odd_component, cut_variant, cut_identifier = \ + ....: G.is_brace(coNP_certificate=True) sage: is_brace is False True sage: G1 = L.copy() @@ -2617,6 +2636,11 @@ def is_brace(self, coNP_certificate=False): sage: G2.merge_vertices([v for v in G if v not in nontrivial_odd_component]) sage: G2.to_simple().is_isomorphic(graphs.CycleGraph(4)) True + sage: cut_variant + 'nontrivial barrier cut' + sage: cut_identifier == {a for u, v, *_ in nontrivial_tight_cut for a in [u, v] \ + ....: if a not in nontrivial_odd_component} + True If the input matching covered graph is nonbipartite, a :exc:`ValueError` is thrown:: @@ -2644,7 +2668,7 @@ def is_brace(self, coNP_certificate=False): raise ValueError('the input graph is not bipartite') if self.order() < 6: - return (True, None, None) if coNP_certificate else True + return (True, None, None, None, None) if coNP_certificate else True A, B = self.bipartite_sets() matching = set(self.get_matching()) @@ -2679,16 +2703,16 @@ def is_brace(self, coNP_certificate=False): # H(e) is matching covered iff D(e) is strongly connected. # Check if D(e) is strongly connected using Kosaraju's algorithm - def dfs(v, visited, neighbor_iterator): - stack = [v] # a stack of vertices + def dfs(x, visited, neighbor_iterator): + stack = [x] # a stack of xertices while stack: - v = stack.pop() - visited.add(v) + x = stack.pop() + visited.add(x) - for u in neighbor_iterator(v): - if u not in visited: - stack.append(u) + for y in neighbor_iterator(x): + if y not in visited: + stack.append(y) root = next(D.vertex_iterator()) @@ -2722,13 +2746,21 @@ def dfs(v, visited, neighbor_iterator): X.add(u if (not color_class and u in A) or (color_class and u in B) or (color_class is None) else v) # Compute the nontrivial tight cut C := ∂(Y) - C = [(u, v, w) if u in X else (v, u, w) - for u, v, w in self.edge_iterator(sort_vertices=True) - if (u in X) ^ (v in X)] + C = [(x, y, w) if x in X else (y, x, w) + for x, y, w in self.edge_iterator(sort_vertices=True) + if (x in X) ^ (y in X)] + + # Obtain the barrier Z + Z = None + + if (u in X and u in A) or (v in X and v in A): + Z = {b for b in B if b not in X} + else: + Z = {a for a in A if a not in X} - return (False, C, set(X)) + return (False, C, set(X), 'nontrivial barrier cut', Z) - return (True, None, None) if coNP_certificate else True + return (True, None, None, None, None) if coNP_certificate else True @doc_index('Bricks, braces and tight cut decomposition') def is_brick(self, coNP_certificate=False): From 2b765b3d2a7be78dbc7537fdc0da15da073c72b7 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Tue, 14 Jan 2025 18:30:10 +0530 Subject: [PATCH 34/39] updated is_brick() --- src/sage/graphs/matching_covered_graph.py | 33 ++++++++++++++--------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index f54840f81ff..3ef6579e987 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2783,7 +2783,7 @@ def is_brick(self, coNP_certificate=False): - If the input nonbipartite matching covered graph is a brick, a boolean ``True`` is returned if ``coNP_certificate`` is set to - ``False``, otherwise a 4-tuple ``(True, None, None, None)`` is + ``False``, otherwise a 5-tuple ``(True, None, None, None, None)`` is returned. - If the input nonbipartite matching covered graph is not a brick, a @@ -2791,7 +2791,7 @@ def is_brick(self, coNP_certificate=False): ``False``. - If ``coNP_certificate`` is set to ``True`` and the input nonbipartite - graph is not a brick, a 4-tuple of + graph is not a brick, a 5-tuple of 1. a boolean ``False``, @@ -2811,8 +2811,14 @@ def is_brick(self, coNP_certificate=False): `i` th shore being a proper subset of the `i + 1` th shore. 4. a string showing whether the nontrivial tight cuts are barrier - cuts (if the string is 'nontrivial barrier cuts'), or 2-separation - cuts (if the string is 'nontrivial 2-separation cuts') + cuts (if the string is 'nontrivial barrier cut'), or 2-separation + cuts (if the string is 'nontrivial 2-separation cut') + + 5. a set of vertices showing the respective barrier if the + nontrivial tight cuts are barrier cuts, or otherwise + a set of two vertices constituting the corresponding + two vertex cut (in this case the nontrivial tight cuts are + 2-separation cuts) is returned. @@ -2914,13 +2920,13 @@ def is_brick(self, coNP_certificate=False): sage: K4 = graphs.CompleteGraph(4) sage: G = MatchingCoveredGraph(K4) sage: G.is_brick(coNP_certificate=True) - (True, None, None, None) + (True, None, None, None, None) sage: # K(4) ⊙ K(3, 3) is nonbipartite but not a brick sage: H = graphs.MurtyGraph(); H.delete_edge(0, 1) sage: G = MatchingCoveredGraph(H) sage: G.is_brick(coNP_certificate=True) (False, [[(5, 2, None), (6, 3, None), (7, 4, None)]], [{5, 6, 7}], - 'nontrivial barrier cuts') + 'nontrivial barrier cut', {2, 3, 4}) sage: H = Graph([ ....: (0, 12), (0, 13), (0, 15), (1, 4), (1, 13), (1, 14), ....: (1, 19), (2, 4), (2, 13), (2, 14), (2, 17), (3, 9), @@ -2936,7 +2942,7 @@ def is_brick(self, coNP_certificate=False): [(19, 1, None), (17, 2, None), (21, 3, None)], [(15, 0, None), (14, 1, None), (14, 2, None), (16, 3, None)]], [{4, 5, 6, 7, 8, 9, 10, 11, 12}, {17, 18, 19, 20, 21}, {14, 15, 16}], - 'nontrivial barrier cuts') + 'nontrivial barrier cut', {0, 1, 2, 3}) sage: J = Graph([ ....: (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), ....: (0, 6), (0, 7), (0, 8), (0, 9), (0, 10), @@ -2991,7 +2997,8 @@ def is_brick(self, coNP_certificate=False): {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6, 7, 8}], - 'nontrivial 2-separation cuts') + 'nontrivial 2-separation cut', + {0, 11}) If the input matching covered graph is bipartite, a :exc:`ValueError` is thrown:: @@ -3048,11 +3055,11 @@ def is_brick(self, coNP_certificate=False): if (u in nontrivial_odd_component) ^ (v in nontrivial_odd_component)] for nontrivial_odd_component in nontrivial_odd_components] - return (False, C, nontrivial_odd_components, 'nontrivial barrier cuts') + return (False, C, nontrivial_odd_components, 'nontrivial barrier cut', B) # Check if G is 3-connected if self.is_triconnected(): - return (True, None, None, None) if coNP_certificate else True + return (True, None, None, None, None) if coNP_certificate else True # G has a 2-vertex cut # Compute the SPQR-tree decomposition @@ -3091,11 +3098,11 @@ def is_brick(self, coNP_certificate=False): nontrivial_tight_cut_variation = None if all(len(c) % 2 for c in components): - nontrivial_tight_cut_variation = 'nontrivial barrier cuts' + nontrivial_tight_cut_variation = 'nontrivial barrier cut' nontrivial_odd_components = [set(component) for component in components if len(component) > 1] else: - nontrivial_tight_cut_variation = 'nontrivial 2-separation cuts' + nontrivial_tight_cut_variation = 'nontrivial 2-separation cut' nontrivial_odd_components = [] for index, component in enumerate(components): @@ -3114,7 +3121,7 @@ def is_brick(self, coNP_certificate=False): for nontrivial_odd_component in nontrivial_odd_components] # Edge (u, v, w) in C are formatted so that u is in a nontrivial odd component - return (False, C, nontrivial_odd_components, nontrivial_tight_cut_variation) if coNP_certificate else False + return (False, C, nontrivial_odd_components, nontrivial_tight_cut_variation, set(two_vertex_cut)) if coNP_certificate else False @doc_index('Overwritten methods') def loop_edges(self, labels=True): From 9a952219669192e080eeabc4c3bb16fe806fe350 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Tue, 14 Jan 2025 23:17:37 +0530 Subject: [PATCH 35/39] updated is_brick() --- src/sage/graphs/matching_covered_graph.py | 31 +++++++++-------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 3ef6579e987..c06d9a30aec 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -3095,25 +3095,18 @@ def is_brick(self, coNP_certificate=False): components = H.connected_components(sort=True) # Find a nontrivial odd component - nontrivial_tight_cut_variation = None - - if all(len(c) % 2 for c in components): - nontrivial_tight_cut_variation = 'nontrivial barrier cut' - nontrivial_odd_components = [set(component) for component in components if len(component) > 1] - - else: - nontrivial_tight_cut_variation = 'nontrivial 2-separation cut' - nontrivial_odd_components = [] - - for index, component in enumerate(components): - if index == len(components) - 1: - continue - elif not index: - nontrivial_odd_components.append(set(components[0] + [two_vertex_cut[0]])) - else: - nontrivial_odd_component = nontrivial_odd_components[-1].copy() - nontrivial_odd_component.update(component) - nontrivial_odd_components.append(nontrivial_odd_component) + nontrivial_tight_cut_variation = 'nontrivial 2-separation cut' + nontrivial_odd_components = [] + + for index, component in enumerate(components): + if index == len(components) - 1: + continue + elif not index: + nontrivial_odd_components.append(set(components[0] + [two_vertex_cut[0]])) + else: + nontrivial_odd_component = nontrivial_odd_components[-1].copy() + nontrivial_odd_component.update(component) + nontrivial_odd_components.append(nontrivial_odd_component) C = [[(u, v, w) if u in nontrivial_odd_component else (v, u, w) for u, v, w in self.edge_iterator() From 8395396406de76ac3e19dc07c29b95ec636c46dc Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sun, 26 Jan 2025 19:11:52 +0530 Subject: [PATCH 36/39] added doctests for is_brick() --- src/sage/graphs/matching_covered_graph.py | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index c06d9a30aec..ddf4def727b 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2915,6 +2915,38 @@ def is_brick(self, coNP_certificate=False): sage: G.is_bicritical() True + Examples of nonbipartite matching covered graphs that are not + bricks:: + + sage: H = Graph([ + ....: (0, 3), (0, 4), (0, 7), + ....: (1, 3), (1, 5), (1, 7), + ....: (2, 3), (2, 6), (2, 7), + ....: (4, 5), (4, 6), (5, 6) + ....: ]) + sage: G = MatchingCoveredGraph(H) + sage: G.is_bipartite() + False + sage: G.is_bicritical() + False + sage: G.is_triconnected() + True + sage: G.is_brick() + False + sage: H = Graph([ + ....: (0, 1), (0, 2), (0, 3), (0, 4), (1, 2), + ....: (1, 5), (2, 5), (3, 4), (3, 5), (4, 5) + ....: ]) + sage: G = MatchingCoveredGraph(H) + sage: G.is_bipartite() + False + sage: G.is_bicritical() + True + sage: G.is_triconnected() + False + sage: G.is_brick() + False + One may set the ``coNP_certificate`` to be ``True``:: sage: K4 = graphs.CompleteGraph(4) From 5d35cf43f899825b3f3bb5d80f91c9d702410fa9 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 1 Feb 2025 11:43:44 +0530 Subject: [PATCH 37/39] updated is_brick() --- src/sage/graphs/matching_covered_graph.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 0fec9cfc394..9ad39482a85 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -3098,17 +3098,16 @@ def is_brick(self, coNP_certificate=False): spqr_tree = self.spqr_tree() two_vertex_cut = [] - # Check for 2-vertex cuts in P and S nodes + # Check for 2-vertex cuts in a P node + # Since the graph is matching covered, it is free of cut vertices + # It can be shown using counting arguments that the spqr tree + # decomposition for a bicritical graph, that is 2-connected but not + # 3-connected, is free of 'S' nodes for u in spqr_tree: if u[0] == 'P': two_vertex_cut.extend(u[1]) break - elif u[0] == 'S' and u[1].order() > 3: - s_vertex_set = set(u[1]) - s_vertex_set -= {next(u[1].vertex_iterator())} | set(u[1].neighbors(next(u[1].vertex_iterator()))) - two_vertex_cut.extend([next(u[1].vertex_iterator()), next(iter(s_vertex_set))]) - # If no 2-vertex cut found, look for R nodes if not two_vertex_cut: from collections import Counter @@ -3117,6 +3116,9 @@ def is_brick(self, coNP_certificate=False): for t, g in spqr_tree: if t == 'R': R_frequency.update(g) + + # R frequency must be at least 2, + # since the graph is 2-connected but not 3-connected two_vertex_cut = [u for u, f in R_frequency.items() if f >= 2][:2] # We obtain a 2-vertex cut (u, v) From 44153509abe345129280a871dc66d2894012dd89 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 1 Feb 2025 12:31:20 +0530 Subject: [PATCH 38/39] added a doctest for is_brace() --- src/sage/graphs/matching_covered_graph.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 9ad39482a85..4ac0071b2a6 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2641,6 +2641,15 @@ def is_brace(self, coNP_certificate=False): sage: cut_identifier == {a for u, v, *_ in nontrivial_tight_cut for a in [u, v] \ ....: if a not in nontrivial_odd_component} True + sage: H = graphs.CompleteBipartiteGraph(3, 3) + sage: H.delete_edge(0, 3) + sage: G = MatchingCoveredGraph(G) + sage: G.is_brace(coNP_certificate=True) + (False, + [(1, 2, None), (1, 4, None), (3, 4, None)], + {0, 1, 3}, + 'nontrivial barrier cut', + {2, 4}) If the input matching covered graph is nonbipartite, a :exc:`ValueError` is thrown:: From e12674ae3be9299c6c1e9cc6e05ff8f1e03b8260 Mon Sep 17 00:00:00 2001 From: janmenjayap Date: Sat, 1 Feb 2025 12:40:47 +0530 Subject: [PATCH 39/39] updated is_brace() --- src/sage/graphs/matching_covered_graph.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py index 4ac0071b2a6..b52333443a9 100644 --- a/src/sage/graphs/matching_covered_graph.py +++ b/src/sage/graphs/matching_covered_graph.py @@ -2643,13 +2643,13 @@ def is_brace(self, coNP_certificate=False): True sage: H = graphs.CompleteBipartiteGraph(3, 3) sage: H.delete_edge(0, 3) - sage: G = MatchingCoveredGraph(G) + sage: G = MatchingCoveredGraph(H) sage: G.is_brace(coNP_certificate=True) (False, - [(1, 2, None), (1, 4, None), (3, 4, None)], - {0, 1, 3}, + [(4, 1, None), (5, 1, None), (4, 2, None), (5, 2, None)], + {0, 4, 5}, 'nontrivial barrier cut', - {2, 4}) + {1, 2}) If the input matching covered graph is nonbipartite, a :exc:`ValueError` is thrown::