diff --git a/src/doc/en/reference/graphs/index.rst b/src/doc/en/reference/graphs/index.rst index 34c5f134839..91b0f2d3cd7 100644 --- a/src/doc/en/reference/graphs/index.rst +++ b/src/doc/en/reference/graphs/index.rst @@ -14,6 +14,7 @@ Graph objects and methods sage/graphs/graph sage/graphs/digraph sage/graphs/bipartite_graph + sage/graphs/matching_covered_graph sage/graphs/views Constructors and databases diff --git a/src/sage/graphs/all.py b/src/sage/graphs/all.py index 14f4eb9204b..a42a1d7210f 100644 --- a/src/sage/graphs/all.py +++ b/src/sage/graphs/all.py @@ -9,6 +9,7 @@ from sage.graphs.graph import Graph from sage.graphs.digraph import DiGraph from sage.graphs.bipartite_graph import BipartiteGraph +from sage.graphs.matching_covered_graph import MatchingCoveredGraph import sage.graphs.weakly_chordal import sage.graphs.lovasz_theta import sage.graphs.partial_cube diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 44a5e8f6a63..101952109c3 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -14285,7 +14285,7 @@ def _subgraph_by_adding(self, vertices=None, edges=None, edge_property=None, imm or (v, u) in edges_to_keep_unlabeled)): edges_to_keep.append((u, v, l)) else: - s_vertices = set(vertices) + s_vertices = set(G.vertices()) if vertices is None else set(vertices) edges_to_keep = [e for e in self.edges(vertices=vertices, sort=False, sort_vertices=False) if e[0] in s_vertices and e[1] in s_vertices] diff --git a/src/sage/graphs/matching.py b/src/sage/graphs/matching.py index 457ccc16a75..c8eea2bb005 100644 --- a/src/sage/graphs/matching.py +++ b/src/sage/graphs/matching.py @@ -55,7 +55,7 @@ def has_perfect_matching(G, algorithm='Edmonds', solver=None, verbose=0, *, integrality_tolerance=1e-3): r""" - Return whether the graph has a perfect matching + Return whether the graph has a perfect matching. INPUT: @@ -162,7 +162,7 @@ def has_perfect_matching(G, algorithm='Edmonds', solver=None, verbose=0, def is_bicritical(G, matching=None, algorithm='Edmonds', coNP_certificate=False, solver=None, verbose=0, *, integrality_tolerance=0.001): r""" - Check if the graph is bicritical + Check if the graph is bicritical. A nontrivial graph `G` is *bicritical* if `G - u - v` has a perfect matching for any two distinct vertices `u` and `v` of `G`. Bicritical @@ -270,12 +270,9 @@ def is_bicritical(G, matching=None, algorithm='Edmonds', coNP_certificate=False, A graph (of order more than two) with more that one component is not bicritical:: - sage: cycle1 = graphs.CycleGraph(4) - sage: cycle2 = graphs.CycleGraph(6) - sage: cycle2.relabel(lambda v: v + 4) - sage: G = Graph() - sage: G.add_edges(cycle1.edges() + cycle2.edges()) - sage: len(G.connected_components(sort=False)) + sage: G = graphs.CycleGraph(4) + sage: G += graphs.CycleGraph(6) + sage: G.connected_components_number() 2 sage: G.is_bicritical() False @@ -449,27 +446,25 @@ def is_bicritical(G, matching=None, algorithm='Edmonds', coNP_certificate=False, return (False, set(list(A)[:2])) return (False, set(list(B)[:2])) - # A graph (without a self-loop) is bicritical if and only if the underlying - # simple graph is bicritical - G_simple = G.to_simple() - from sage.graphs.graph import Graph if matching: # The input matching must be a valid perfect matching of the graph M = Graph(matching) if any(d != 1 for d in M.degree()): raise ValueError("the input is not a matching") - if any(not G_simple.has_edge(edge) for edge in M.edge_iterator()): + + if any(not G.has_edge(edge) for edge in M.edge_iterator()): raise ValueError("the input is not a matching of the graph") - if (G_simple.order() != M.order()) or (G_simple.order() != 2*M.size()): + + if (G.order() != M.order()) or (G.order() != 2*M.size()): raise ValueError("the input is not a perfect matching of the graph") else: # A maximum matching of the graph is computed - M = Graph(G_simple.matching(algorithm=algorithm, solver=solver, verbose=verbose, + M = Graph(G.matching(algorithm=algorithm, solver=solver, verbose=verbose, integrality_tolerance=integrality_tolerance)) # It must be a perfect matching - if G_simple.order() != M.order(): + if G.order() != M.order(): u, v = next(M.edge_iterator(labels=False)) return (False, set([u, v])) if coNP_certificate else False @@ -477,12 +472,12 @@ def is_bicritical(G, matching=None, algorithm='Edmonds', coNP_certificate=False, # every vertex of the graph distinct from v must be reachable from u through an even length # M-alternating uv-path starting with an edge not in M and ending with an edge in M - for u in G_simple: + for u in G: v = next(M.neighbor_iterator(u)) - even = M_alternating_even_mark(G_simple, u, M) + even = M_alternating_even_mark(G, u, M) - for w in G_simple: + for w in G: if w != v and w not in even: return (False, set([v, w])) if coNP_certificate else False @@ -980,27 +975,26 @@ def is_matching_covered(G, matching=None, algorithm='Edmonds', coNP_certificate= if G.order() == 2: return (True, None) if coNP_certificate else True - # A graph (without a self-loop) is matching covered if and only if the - # underlying simple graph is matching covered - G_simple = G.to_simple() - from sage.graphs.graph import Graph if matching: # The input matching must be a valid perfect matching of the graph M = Graph(matching) + if any(d != 1 for d in M.degree()): raise ValueError("the input is not a matching") - if any(not G_simple.has_edge(edge) for edge in M.edge_iterator()): + + if any(not G.has_edge(edge) for edge in M.edge_iterator()): raise ValueError("the input is not a matching of the graph") - if (G_simple.order() != M.order()) or (G_simple.order() != 2*M.size()): + + if (G.order() != M.order()) or (G.order() != 2*M.size()): raise ValueError("the input is not a perfect matching of the graph") else: # A maximum matching of the graph is computed - M = Graph(G_simple.matching(algorithm=algorithm, solver=solver, verbose=verbose, + M = Graph(G.matching(algorithm=algorithm, solver=solver, verbose=verbose, integrality_tolerance=integrality_tolerance)) # It must be a perfect matching - if G_simple.order() != M.order(): + if G.order() != M.order(): return (False, next(M.edge_iterator())) if coNP_certificate else False # Biparite graph: @@ -1011,17 +1005,17 @@ def is_matching_covered(G, matching=None, algorithm='Edmonds', coNP_certificate= # if it is in M or otherwise direct it from B to A. The graph G is # matching covered if and only if D is strongly connected. - if G_simple.is_bipartite(): - A, _ = G_simple.bipartite_sets() + if G.is_bipartite(): + A, _ = G.bipartite_sets() color = dict() - for u in G_simple: + for u in G: color[u] = 0 if u in A else 1 from sage.graphs.digraph import DiGraph H = DiGraph() - for u, v in G_simple.edge_iterator(labels=False): + for u, v in G.edge_iterator(labels=False): if color[u]: u, v = v, u @@ -1075,12 +1069,12 @@ def dfs(J, v, visited, orientation): # an M-alternating odd length uv-path starting and ending with edges not # in M. - for u in G_simple: + for u in G: v = next(M.neighbor_iterator(u)) - even = M_alternating_even_mark(G_simple, u, M) + even = M_alternating_even_mark(G, u, M) - for w in G_simple.neighbor_iterator(v): + for w in G.neighbor_iterator(v): if w != u and w not in even: return (False, (v, w)) if coNP_certificate else False @@ -1092,7 +1086,7 @@ def matching(G, value_only=False, algorithm='Edmonds', *, integrality_tolerance=1e-3): r""" Return a maximum weighted matching of the graph represented by the list - of its edges + of its edges. For more information, see the :wikipedia:`Matching_(graph_theory)`. @@ -1291,7 +1285,7 @@ def weight(x): def perfect_matchings(G, labels=False): r""" - Return an iterator over all perfect matchings of the graph + Return an iterator over all perfect matchings of the graph. ALGORITHM: @@ -1404,7 +1398,7 @@ def rec(G): def M_alternating_even_mark(G, vertex, matching): r""" Return the vertices reachable from ``vertex`` via an even alternating path - starting with a non-matching edge + starting with a non-matching edge. This method implements the algorithm proposed in [LR2004]_. Note that the complexity of the algorithm is linear in number of edges. @@ -1570,7 +1564,8 @@ def M_alternating_even_mark(G, vertex, matching): M = Graph(matching) if any(d != 1 for d in M.degree()): raise ValueError("the input is not a matching") - if any(not G_simple.has_edge(edge) for edge in M.edge_iterator()): + + if any(not G.has_edge(edge) for edge in M.edge_iterator()): raise ValueError("the input is not a matching of the graph") # Build an M-alternating tree T rooted at vertex @@ -1605,8 +1600,10 @@ def M_alternating_even_mark(G, vertex, matching): while ancestor_x[-1] != ancestor_y[-1]: if rank[ancestor_x[-1]] > rank[ancestor_y[-1]]: ancestor_x.append(predecessor[ancestor_x[-1]]) + elif rank[ancestor_x[-1]] < rank[ancestor_y[-1]]: ancestor_y.append(predecessor[ancestor_y[-1]]) + else: ancestor_x.append(predecessor[ancestor_x[-1]]) ancestor_y.append(predecessor[ancestor_y[-1]]) @@ -1616,6 +1613,7 @@ def M_alternating_even_mark(G, vertex, matching): # Set t as pred of all vertices of the chains and add # vertices marked odd to the queue next_rank_to_lcs_rank = rank[lcs] + 1 + for a in itertools.chain(ancestor_x, ancestor_y): predecessor[a] = lcs rank[a] = next_rank_to_lcs_rank diff --git a/src/sage/graphs/matching_covered_graph.py b/src/sage/graphs/matching_covered_graph.py new file mode 100644 index 00000000000..2a9b8916c90 --- /dev/null +++ b/src/sage/graphs/matching_covered_graph.py @@ -0,0 +1,2093 @@ +r""" +Matching covered graphs + +This module implements functions and operations pertaining to matching covered +graphs. + +A *matching* in a graph is a set of pairwise nonadjacent links +(nonloop edges). In other words, a matching in a graph is the edge set of an +1-regular subgraph. A matching is called a *perfect* *matching* if it the +subgraph generated by a set of matching edges spans the graph, i.e. it's the +edge set of an 1-regular spanning subgraph. A connected nontrivial graph is +called *matching* *covered* if each edge participates in some perfect matching. + +{INDEX_OF_METHODS} + +REFERENCES: + +- This methods of this module has been adopted and inspired by the book of + Lucchesi and Murty --- *Perfect Matchings: a theory of matching covered + graphs* [LM2024]_. + +AUTHORS: + +- Janmenjaya Panda (2024-06-14): initial version + +.. TODO:: + + The following methods are to be incorporated in + :class:`~MatchingCoveredGraph`: + + .. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + ``__hash__()`` | Compute a hash for ``self``, if ``self`` is immutable. + ``_subgraph_by_deleting()`` | Return the matching covered subgraph containing the provided vertices and edges. + + **Overwritten Methods** + + .. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + ``add_clique()`` | Add a clique to the graph with the provided vertices. + ``add_cycle()`` | Add a cycle to the graph with the provided vertices. + ``add_path()`` | Add a path to the graph with the provided vertices. + ``cartesian_product()`` | Return the Cartesian product of ``self`` and ``other``. + ``clear()`` | Empties the graph of vertices and edges and removes name, associated objects, and position information. + ``complement()`` | Return the complement of the graph. + ``contract_edge()`` | Contract an edge from ``u`` to ``v``. + ``contract_edges()`` | Contract edges from an iterable container. + ``degree_constrained_subgraph()`` | Return a degree-constrained matching covered subgraph. + ``delete_edge()`` | Delete the edge from ``u`` to ``v``. + ``delete_edges()`` | Delete edges from an iterable container. + ``delete_multiedge()`` | Delete all edges from ``u`` to ``v``. + ``disjoint_union()`` | Return the disjoint union of ``self`` and ``other``. + ``disjunctive_product()`` | Return the disjunctive product of ``self`` and ``other``. + ``has_loops()`` | Return whether there are loops in the matching covered graph. + ``is_biconnected()`` | Check if the matching covered graph is biconnected. + ``is_block_graph()`` | Check whether the matching covered graph is a block graph. + ``is_cograph()`` | Check whether the matching covered graph is cograph. + ``is_forest()`` | Check if the matching covered graph is a forest, i.e. a disjoint union of trees. + ``is_matching_covered()`` | Check if the graph is matching covered. + ``is_path()`` | Check whether the graph is a path. + ``is_subgraph()`` | Check whether the matching covered graph is a subgraph of ``other``. + ``is_tree()`` | Check whether the matching covered graph is a tree. + ``join()`` | Return the join of ``self`` and ``other``. + ``lexicographic_product()`` | Return the lexicographic product of ``self`` and ``other``. + ``load_afile()`` | Load the matching covered graph specified in the given file into the current object. + ``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. + ``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``. + ``save_afile()`` | Save the graph to file in alist format. + ``strong_product()`` | Return the strong product of ``self`` and ``other``. + ``subdivide_edge()`` | Subdivide an edge `k` times. + ``subdivide_edges()`` | Subdivide `k` times edges from an iterable container. + ``subgraph()`` | Return the matching covered subgraph containing the given vertices and edges. + ``subgraph_search()`` | Return a copy of (matching covered) ``G`` in ``self``. + ``subgraph_search_count()`` | Return the number of labelled occurrences of (matching covered) ``G`` in ``self``. + ``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. + ``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``. + + **Barriers and canonical partition** + + .. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + ``canonical_partition()`` | Return the canonical partition of the (matching covered) graph. + ``maximal_barrier()`` | Return the (unique) maximal barrier of the (matching covered) graph containing the (provided) vertex. + + **Bricks, braces and tight cut decomposition** + + .. csv-table:: + :class: contentstable + :widths: 30, 70 + :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. + ``number_of_petersen_bricks()`` | Return the number of Petersen bricks. + ``tight_cut_decomposition()`` | Return a tight cut decomposition. + + **Removability and ear decomposition** + + .. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + ``add_ear()`` | Add an ear to the graph with the provided end vertices number of internal vertices. + ``bisubdivide_edge()`` | Bisubdivide an edge `k` times. + ``bisubdivide_edges()`` | Bisubdivide `k` times edges from an iterable container. + ``efficient_ear_decomposition()`` | Return a matching covered ear decomposition computed at the fastest possible time. + ``is_removable_double_ear()`` | Check whether the pair of ears form a removable double ear. + ``is_removable_doubleton()`` | Check whether the pair of edges constitute a removable doubleton. + ``is_removable_ear()`` | Check whether the ear is removable. + ``is_removable_edge()`` | Check whether the edge is removable. + ``optimal_ear_decomposition()`` | Return an optimal ear decomposition. + ``removable_double_ears()`` | Return a list of removable double ears. + ``removable_doubletons()`` | Return a list of removable doubletons. + ``removable_ears()`` | Return a list of removable ears. + ``removable_edges()`` | Return a :class:`~EdgesView` of removable edges. + ``retract()`` | Compute the retract of the (matching covered) graph. + + **Generating bricks and braces** + + .. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + ``brace_generation_sequence()`` | Return a McCuaig brace generation sequence of the (provided) brace. + ``brick_generation_sequence()`` | Return a Norine-Thomas brick generation sequence of the (provided) brick. + ``is_mccuaig_brace()`` | Check if the brace is a McCuaig brace. + ``is_norine_thomas_brick()`` | Check if the brick is a Norine-Thomas brick. + + +Methods +------- +""" + +# **************************************************************************** +# Copyright (C) 2024 Janmenjaya Panda +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** +from sage.graphs.graph import Graph +from sage.misc.rest_index_of_methods import doc_index, gen_thematic_rest_table_index + +class MatchingCoveredGraph(Graph): + r""" + Matching covered graph + + INPUT: + + - ``data`` -- can be any of the following: + + - Empty or ``None`` (throws a :exc:`ValueError` as the graph must be + nontrival). + + - An arbitrary graph. + + - ``matching`` -- (default: ``None``); a perfect matching of the + graph, that can be given using any valid input format of + :class:`~sage.graphs.graph.Graph`. + + If set to ``None``, a matching is computed using the other parameters. + + - ``algorithm`` -- string (default: ``'Edmonds'``); the algorithm to be + used to compute a maximum matching of the graph among + + - ``'Edmonds'`` selects Edmonds' algorithm as implemented in NetworkX, + + - ``'LP'`` uses a Linear Program formulation of the matching problem. + + - ``solver`` -- string (default: ``None``); specify a Mixed Integer + Linear Programming (MILP) solver to be used. If set to ``None``, the + default one is used. For more information on MILP solvers and which + default solver is used, see the method :meth:`solve + ` of the class + :class:`MixedIntegerLinearProgram + `. + + - ``verbose`` -- integer (default: ``0``); sets the level of verbosity: + set to 0 by default, which means quiet (only useful when ``algorithm + == 'LP'``). + + - ``integrality_tolerance`` -- float; parameter for use with MILP + solvers over an inexact base ring; see + :meth:`MixedIntegerLinearProgram.get_values`. + + OUTPUT: + + - An object of the class :class:`~MatchingCoveredGraph` if the input is + valid and the graph is matching covered, or otherwise an error is thrown. + + .. NOTE:: + + All remaining arguments are passed to the ``Graph`` constructor + + EXAMPLES: + + Generating an object of the class ``MatchingCoveredGraph`` from the + provided instance of ``Graph`` without providing any other information:: + + sage: G = MatchingCoveredGraph(graphs.PetersenGraph()) + sage: G + Matching covered petersen graph: graph on 10 vertices + sage: sorted(G.get_matching()) + [(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)] + + sage: G = graphs.StaircaseGraph(4) + sage: H = MatchingCoveredGraph(G) + sage: H + Matching covered staircase graph: graph on 8 vertices + sage: H == G + True + sage: sorted(H.get_matching()) + [(0, 1, None), (2, 7, None), (3, 6, None), (4, 5, None)] + + sage: G = Graph({0: [1, 2, 3, 4], 1: [2, 5], + ....: 2: [5], 3: [4, 5], 4: [5]}) + sage: H = MatchingCoveredGraph(G) + sage: H + Matching covered graph on 6 vertices + sage: H == G + True + sage: sorted(H.get_matching()) + [(0, 4, None), (1, 2, None), (3, 5, None)] + + sage: # needs networkx + sage: import networkx + sage: G = Graph(networkx.complete_bipartite_graph(12, 12)) + sage: H = MatchingCoveredGraph(G) + sage: H + Matching covered graph on 24 vertices + sage: H == G + True + sage: sorted(H.get_matching()) + [(0, 15, None), (1, 14, None), (2, 13, None), (3, 12, None), + (4, 23, None), (5, 22, None), (6, 21, None), (7, 20, None), + (8, 19, None), (9, 18, None), (10, 17, None), (11, 16, None)] + + sage: G = Graph('E|fG', sparse=True) + sage: H = MatchingCoveredGraph(G) + sage: H + Matching covered graph on 6 vertices + sage: H == G + True + sage: sorted(H.get_matching()) + [(0, 5, None), (1, 2, None), (3, 4, None)] + + sage: # needs sage.modules + sage: M = Matrix([(0,1,0,0,1,1,0,0,0,0), + ....: (1,0,1,0,0,0,1,0,0,0), + ....: (0,1,0,1,0,0,0,1,0,0), + ....: (0,0,1,0,1,0,0,0,1,0), + ....: (1,0,0,1,0,0,0,0,0,1), + ....: (1,0,0,0,0,0,0,1,1,0), + ....: (0,1,0,0,0,0,0,0,1,1), + ....: (0,0,1,0,0,1,0,0,0,1), + ....: (0,0,0,1,0,1,1,0,0,0), + ....: (0,0,0,0,1,0,1,1,0,0)]) + sage: M + [0 1 0 0 1 1 0 0 0 0] + [1 0 1 0 0 0 1 0 0 0] + [0 1 0 1 0 0 0 1 0 0] + [0 0 1 0 1 0 0 0 1 0] + [1 0 0 1 0 0 0 0 0 1] + [1 0 0 0 0 0 0 1 1 0] + [0 1 0 0 0 0 0 0 1 1] + [0 0 1 0 0 1 0 0 0 1] + [0 0 0 1 0 1 1 0 0 0] + [0 0 0 0 1 0 1 1 0 0] + sage: G = Graph(M) + sage: H = MatchingCoveredGraph(G) + sage: H == G + True + sage: sorted(H.get_matching()) + [(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)] + + sage: # needs sage.modules + sage: M = Matrix([(-1, 0, 0, 0, 1, 0, 0, 0, 0, 0,-1, 0, 0, 0, 0), + ....: ( 1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 0), + ....: ( 0, 1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0), + ....: ( 0, 0, 1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0), + ....: ( 0, 0, 0, 1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1), + ....: ( 0, 0, 0, 0, 0,-1, 0, 0, 0, 1, 1, 0, 0, 0, 0), + ....: ( 0, 0, 0, 0, 0, 0, 0, 1,-1, 0, 0, 1, 0, 0, 0), + ....: ( 0, 0, 0, 0, 0, 1,-1, 0, 0, 0, 0, 0, 1, 0, 0), + ....: ( 0, 0, 0, 0, 0, 0, 0, 0, 1,-1, 0, 0, 0, 1, 0), + ....: ( 0, 0, 0, 0, 0, 0, 1,-1, 0, 0, 0, 0, 0, 0, 1)]) + sage: M + [-1 0 0 0 1 0 0 0 0 0 -1 0 0 0 0] + [ 1 -1 0 0 0 0 0 0 0 0 0 -1 0 0 0] + [ 0 1 -1 0 0 0 0 0 0 0 0 0 -1 0 0] + [ 0 0 1 -1 0 0 0 0 0 0 0 0 0 -1 0] + [ 0 0 0 1 -1 0 0 0 0 0 0 0 0 0 -1] + [ 0 0 0 0 0 -1 0 0 0 1 1 0 0 0 0] + [ 0 0 0 0 0 0 0 1 -1 0 0 1 0 0 0] + [ 0 0 0 0 0 1 -1 0 0 0 0 0 1 0 0] + [ 0 0 0 0 0 0 0 0 1 -1 0 0 0 1 0] + [ 0 0 0 0 0 0 1 -1 0 0 0 0 0 0 1] + sage: G = Graph(M) + sage: H = MatchingCoveredGraph(G) + sage: H == G + True + sage: sorted(H.get_matching()) + [(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)] + + sage: G = Graph([(0, 1), (0, 3), (0, 4), (1, 2), (1, 5), (2, 3), + ....: (2, 6), (3, 7), (4, 5), (4, 7), (5, 6), (6, 7)]) + sage: H = MatchingCoveredGraph(G) + sage: H == G + True + sage: sorted(H.get_matching()) + [(0, 4, None), (1, 5, None), (2, 6, None), (3, 7, None)] + + sage: # optional - python_igraph + sage: import igraph + sage: G = Graph(igraph.Graph([(0, 1), (0, 3), (1, 2), (2, 3)])) + sage: H = MatchingCoveredGraph(G) + sage: H + Matching covered graph on 4 vertices + sage: sorted(H.get_matching()) + [(0, 3, {}), (1, 2, {})] + + One may specify a perfect matching:: + + sage: P = graphs.PetersenGraph() + sage: M = P.matching() + sage: sorted(M) + [(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)] + sage: G = MatchingCoveredGraph(P, matching=M) + sage: G + Matching covered petersen graph: graph on 10 vertices + sage: P == G + True + sage: sorted(G.get_matching()) + [(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)] + sage: sorted(G.get_matching()) == sorted(M) + True + + sage: G = graphs.TruncatedBiwheelGraph(14) + sage: M = G.matching() + sage: sorted(M) + [(0, 27, None), (1, 26, None), (2, 3, None), (4, 5, None), + (6, 7, None), (8, 9, None), (10, 11, None), (12, 13, None), + (14, 15, None), (16, 17, None), (18, 19, None), (20, 21, None), + (22, 23, None), (24, 25, None)] + sage: H = MatchingCoveredGraph(G, M) + sage: H + Matching covered truncated biwheel graph: graph on 28 vertices + sage: H == G + True + sage: sorted(H.get_matching()) == sorted(M) + True + + One may specify some keyword arguments:: + + sage: G = Graph([(0, 1, 5)], {'weighted': True}) + sage: kwds = { + ....: 'loops': False, + ....: 'multiedges': True, + ....: 'pos': {0: (0, 0), 1: (1, 1)} + ....: } + sage: H = MatchingCoveredGraph(G, **kwds) + sage: H + Matching covered multi-graph on 2 vertices + sage: H.add_edge(0, 1) + sage: H.edges() + [(0, 1, None), (0, 1, 5)] + + TESTS: + + An empty graph is not matching covered:: + + sage: G = Graph() + sage: H = MatchingCoveredGraph(G) + Traceback (most recent call last): + ... + ValueError: the graph is trivial + sage: G = MatchingCoveredGraph() + Traceback (most recent call last): + ... + ValueError: the graph is trivial + + Providing with a graph that is not connected:: + + sage: G = graphs.CycleGraph(4) + sage: G += graphs.CycleGraph(6) + sage: G.connected_components_number() + 2 + sage: H = MatchingCoveredGraph(G) + Traceback (most recent call last): + ... + ValueError: the graph is not connected + + Make sure that self-loops are not allowed for a matching covered graph:: + + sage: P = graphs.PetersenGraph() + sage: kwds = {'loops': True} + sage: G = MatchingCoveredGraph(P, **kwds) + Traceback (most recent call last): + ... + ValueError: loops are not allowed in matching covered graphs + sage: G = MatchingCoveredGraph(P) + sage: G.allows_loops() + False + sage: G.allow_loops(True) + Traceback (most recent call last): + ... + ValueError: loops are not allowed in matching covered graphs + sage: G.add_edge(0, 0) + Traceback (most recent call last): + ... + ValueError: loops are not allowed in matching covered graphs + sage: H = MatchingCoveredGraph(P, loops=True) + Traceback (most recent call last): + ... + ValueError: loops are not allowed in matching covered graphs + + Make sure that multiple edges are allowed for a matching covered graph (by + default it is off and can be modified to be allowed):: + + sage: P = graphs.PetersenGraph() + sage: G = MatchingCoveredGraph(P) + sage: G + Matching covered petersen graph: graph on 10 vertices + sage: G.allows_multiple_edges() + False + sage: G.size() + 15 + sage: G.allow_multiple_edges(True) + sage: G.allows_multiple_edges() + True + sage: G.add_edge(next(P.edge_iterator())) + sage: G.size() + 16 + sage: G + Matching covered petersen graph: multi-graph on 10 vertices + sage: H = MatchingCoveredGraph(P, multiedges=True) + sage: H.allows_multiple_edges() + True + sage: H.add_edge(next(P.edge_iterator())) + sage: H.size() + 16 + sage: H + Matching covered petersen graph: multi-graph on 10 vertices + + Providing with a connected nontrivial graph free of self-loops that is + not matching covered:: + + sage: G = graphs.CompleteGraph(11) + sage: H = MatchingCoveredGraph(G) + Traceback (most recent call last): + ... + ValueError: input graph is not matching covered + sage: G = Graph({0: [1, 6, 11], 1: [2, 4], 2: [3, 5], 3: [4, 5], + ....: 4: [5], 6: [7, 9], 7: [8, 10], 8: [9, 10], 9: [10], + ....: 11: [12, 14], 12: [13, 15], 13: [14, 15], 14: [15]}) + sage: H = MatchingCoveredGraph(G) + Traceback (most recent call last): + ... + ValueError: input graph is not matching covered + + sage: # needs networkx + sage: import networkx + sage: G = Graph(networkx.complete_bipartite_graph(2, 12)) + sage: H = MatchingCoveredGraph(G) + Traceback (most recent call last): + ... + ValueError: input graph is not matching covered + sage: G = Graph('F~~~w') + sage: H = MatchingCoveredGraph(G) + Traceback (most recent call last): + ... + ValueError: input graph is not matching covered + + sage: # needs sage.modules + sage: M = Matrix([(0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0), + ....: (1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + ....: (0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + ....: (0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + ....: (0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + ....: (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + ....: (1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0), + ....: (0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0), + ....: (0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0), + ....: (0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0), + ....: (0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0), + ....: (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0), + ....: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1), + ....: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1), + ....: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1), + ....: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0)]) + sage: M + [0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0] + [1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0] + [0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0] + [0 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0] + [0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0] + [0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0] + [1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0] + [0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0] + [0 0 0 0 0 0 0 1 0 1 1 0 0 0 0 0] + [0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0] + [0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0] + [1 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0] + [0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1] + [0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1] + [0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1] + [0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0] + sage: G = Graph(M) + sage: H = MatchingCoveredGraph(G) + Traceback (most recent call last): + ... + ValueError: input graph is not matching covered + + sage: # needs sage.modules + sage: M = Matrix([(1, 1, 0, 0, 0, 0), + ....: (0, 0, 1, 1, 0, 0), + ....: (0, 0, 1, 0, 1, 0), + ....: (1, 0, 0, 0, 0, 1), + ....: (0, 1, 0, 1, 1, 1)]) + sage: G = Graph(M) + sage: H = MatchingCoveredGraph(G) + Traceback (most recent call last): + ... + ValueError: input graph is not matching covered + sage: G = Graph([(11, 12), (11, 14), (0, 1), (0, 11), (0, 6), (1, 2), + ....: (1, 4), (2, 3), (2, 5), (3, 4), (3, 5), (4, 5), + ....: (6, 7), (6, 9), (7, 8), (7, 10), (8, 9), (8, 10), + ....: (9, 10), (12, 13), (12, 15), (13, 14), (13, 15), + ....: (14, 15)]) + sage: H = MatchingCoveredGraph(G) + Traceback (most recent call last): + ... + ValueError: input graph is not matching covered + + sage: # optional - python_igraph + sage: import igraph + sage: G = Graph(igraph.Graph([(0, 1), (0, 2), (0, 3), (1, 2), (2, 3)])) + sage: H = MatchingCoveredGraph(G) + Traceback (most recent call last): + ... + ValueError: input graph is not matching covered + + Providing with a wrong matching:: + + sage: P = graphs.PetersenGraph() + sage: M = str('0') + sage: H = MatchingCoveredGraph(P, matching=M) + Traceback (most recent call last): + ... + RuntimeError: the string seems corrupt: valid characters are + ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ + sage: N = str('graph') + sage: J = MatchingCoveredGraph(P, matching=N) + Traceback (most recent call last): + ... + RuntimeError: the string (graph) seems corrupt: for n = 40, + the string is too short + + sage: G = graphs.CompleteGraph(6) + sage: M = Graph(G.matching()) + sage: M.add_edges([(0, 1), (0, 2)]) + sage: H = MatchingCoveredGraph(G, matching=M) + Traceback (most recent call last): + ... + ValueError: the input is not a matching + sage: N = Graph(G.matching()) + sage: N.add_edge(6, 7) + sage: H = MatchingCoveredGraph(G, matching=N) + Traceback (most recent call last): + ... + ValueError: the input is not a matching of the graph + sage: J = Graph() + sage: J.add_edges([(0, 1), (2, 3)]) + sage: H = MatchingCoveredGraph(G, matching=J) + Traceback (most recent call last): + ... + ValueError: the input is not a perfect matching of the graph + + Note that data shall be one of empty or ``None`` or an instance of + ``Graph`` or an instance of ``MatchingCoveredGraph``. Otherwise a + :exc:`ValueError` is returned:: + + sage: D = digraphs.Complete(10) + sage: D + Complete digraph: Digraph on 10 vertices + sage: G = MatchingCoveredGraph(D) + Traceback (most recent call last): + ... + TypeError: input data is of unknown type + """ + + def __init__(self, data=None, matching=None, algorithm='Edmonds', + solver=None, verbose=0, integrality_tolerance=0.001, + *args, **kwds): + r""" + Create a matching covered graph, that is a connected nontrivial graph + wherein each edge participates in some perfect matching. + + See documentation ``MatchingCoveredGraph?`` for detailed information. + """ + success = False + + if not kwds: + kwds = {'loops': False} + else: + if 'loops' in kwds and kwds['loops']: + raise ValueError('loops are not allowed in ' + 'matching covered graphs') + kwds['loops'] = False + + if data is None: + raise ValueError('the graph is trivial') + + elif isinstance(data, MatchingCoveredGraph): + Graph.__init__(self, data, *args, **kwds) + success = True + + elif isinstance(data, Graph): + try: + self._upgrade_from_graph(data=data, matching=matching, + algorithm=algorithm, + solver=solver, verbose=verbose, + integrality_tolerance=integrality_tolerance, + *args, **kwds) + success = True + + except Exception as exception: + raise exception + + if success: + if matching: + # The input matching is a valid perfect matching of the graph + self._matching = matching + + else: + self._matching = Graph(self).matching() + + else: + raise TypeError('input data is of unknown type') + + def __repr__(self): + r""" + Return a short string representation of the (matching covered) graph. + + EXAMPLES: + + If the string representation of the (matching covered) graph does not + contain the term 'matching covered', it's used as the prefix:: + + sage: G = graphs.CompleteGraph(10) + sage: H = MatchingCoveredGraph(G) + sage: H + Matching covered complete graph: graph on 10 vertices + + sage: G = graphs.HexahedralGraph() + sage: H = MatchingCoveredGraph(BipartiteGraph(G)) + sage: H # An object of the class MatchingCoveredGraph + Matching covered hexahedron: graph on 8 vertices + + In case the string representation of the (matching covered) graph + contains the term 'matching covered', the representation remains as it + is:: + + sage: G = graphs.CompleteGraph(10) + sage: H = MatchingCoveredGraph(G) + sage: H + Matching covered complete graph: graph on 10 vertices + sage: J = MatchingCoveredGraph(H) + sage: J + Matching covered complete graph: graph on 10 vertices + sage: G = graphs.HexahedralGraph() + sage: H = BipartiteGraph(MatchingCoveredGraph(G)) + sage: H # An object of the class BipartiteGraph + Bipartite hexahedron: graph on 8 vertices + sage: J = MatchingCoveredGraph(H) + sage: J # An object of the class MatchingCoveredGraph + Matching covered hexahedron: graph on 8 vertices + """ + s = Graph._repr_(self).lower() + if "matching covered" in s: + return s.capitalize() + return "".join(["Matching covered ", s]) + + def _subgraph_by_adding(self, vertices=None, edges=None, edge_property=None, immutable=None): + r""" + Return the matching covered subgraph containing the given vertices and edges. + + The edges also satisfy the edge_property, if it is not None. The + subgraph is created by creating a new empty graph and adding the + necessary vertices, edges, and other properties. + + .. NOTE:: + + This method overwrites the + :meth:`~sage.graphs.generic_graph.GenericGraph._subgraph_by_adding` + method to ensure that resultant subgraph is also matching covered. + + INPUT: + + - ``vertices`` -- (default: ``None``) an iterable container of + vertices, e.g. a list, set, graph, file or numeric array. If not + passed (i.e., ``None``), defaults to the entire graph. + + - ``edges`` -- a single edge or an iterable container of edges (e.g., a + list, set, file, numeric array, etc.). By default (``edges=None``), + all edges are assumed and the returned graph is an induced + subgraph. In the case of multiple edges, specifying an edge as `(u,v)` + means to keep all edges `(u,v)`, regardless of the label. + + - ``edge_property`` -- function (default: ``None``); a function that + inputs an edge and outputs a boolean value, i.e., a edge ``e`` in + ``edges`` is kept if ``edge_property(e) == True`` + + - ``immutable`` -- boolean (default: ``None``); whether to create a + mutable/immutable subgraph. ``immutable=None`` (default) means that + the graph and its subgraph will behave the same way. + + OUTPUT: + + - An instance of :class:`~MatchingCoveredGraph` is returned if the + subgraph obtained is matching covered, otherwise a :exc:`ValueError` + is thrown. + + EXAMPLES: + + Ladder graphs are matching covered subgraphs of a staircase graph, + which is also matching covered:: + + sage: G = MatchingCoveredGraph(graphs.StaircaseGraph(4)) + sage: H = G._subgraph_by_adding(vertices=[0..5]) + sage: H.order(), H.size() + (6, 7) + sage: H + Matching covered subgraph of (staircase graph): graph on 6 vertices + sage: H.is_isomorphic(graphs.LadderGraph(3)) + True + + Cycle graphs are matching covered subgraphs of a biwheel graph, which + is also matching covered:: + + sage: G = MatchingCoveredGraph(graphs.BiwheelGraph(5)) + sage: H = G._subgraph_by_adding(vertices=[0..7], + ....: edges=[(u, (u+1) % 8) for u in range(8)]) + sage: H.order(), H.size() + (8, 8) + sage: H + Matching covered subgraph of (biwheel graph): graph on 8 vertices + sage: H.is_isomorphic(graphs.CycleGraph(8)) + True + + One may pass no value for any of the input arguments; in such a case, + the whole matching covered graph will be returned:: + + sage: T = graphs.TwinplexGraph() + sage: G = MatchingCoveredGraph(T) + sage: J = G._subgraph_by_adding() + sage: G == J + True + + One may use the ``edge_property`` argument:: + + sage: G = Graph(multiedges=True) + sage: G.add_edges([ + ....: (0, 1, 'label'), (0, 2), (0, 3), (0, 4), + ....: (0, 5), (1, 2, 'label'), (1, 2), (1, 5), + ....: (2, 5), (3, 4), (3, 5), (4, 5) + ....: ]) + sage: H = MatchingCoveredGraph(G) + sage: J = H._subgraph_by_adding(vertices=[0, 1, 2, 5], edge_property= + ....: (lambda edge: + ....: (edge[0] in [1, 2]) != (edge[1] in [1, 2])) + ....: ) + sage: J.order(), J.size() + (4, 4) + sage: J + Matching covered subgraph of (): multi-graph on 4 vertices + sage: J.is_isomorphic(graphs.CompleteBipartiteGraph(2, 2)) + True + + We may specify the subgraph to be immutable:: + + sage: M = graphs.MoebiusLadderGraph(4) + sage: G = MatchingCoveredGraph(M) + sage: H = G._subgraph_by_adding(edge_property= + ....: (lambda edge: abs(edge[0] - edge[1]) != 4), + ....: immutable=True) + sage: H.order(), H.size() + (8, 8) + sage: H + Matching covered subgraph of (moebius ladder graph): graph on 8 vertices + sage: H.is_isomorphic(graphs.CycleGraph(8)) + True + sage: H.is_immutable() + True + sage: C = graphs.CubeplexGraph() + sage: D = MatchingCoveredGraph(C) + sage: I = D._subgraph_by_adding(immutable=True) + sage: (I == D) and (I.is_immutable()) + True + sage: J = D._subgraph_by_adding(vertices=D.vertices(), immutable=True) + sage: (J == D) and (J.is_immutable()) + True + + An error is thrown if the subgraph is not matching covered:: + + sage: P = graphs.PetersenGraph() + sage: G = MatchingCoveredGraph(P) + sage: H = G._subgraph_by_adding(vertices=[]) + Traceback (most recent call last): + ... + ValueError: the graph is trivial + sage: H = G._subgraph_by_adding(edge_property= + ....: (lambda edge: edge[0] == 0) + ....: ) + Traceback (most recent call last): + ... + ValueError: the graph is not connected + sage: H = G._subgraph_by_adding(vertices=[1, 2, 3]) + Traceback (most recent call last): + ... + ValueError: input graph is not matching covered + """ + if immutable is None: + immutable = self.is_immutable() + + if edges is None and edge_property is None: + if vertices is None: + G = self.copy() + G.name('Matching covered subgraph of ({})'.format(self.name())) + if immutable: + G = G.copy(immutable=True) + + return G + + else: + # Check if all existent vertices are there + all_existent_vertices = True + for vertex in self: + if vertex not in vertices: + all_existent_vertices = False + break + + if all_existent_vertices: + G = self.copy() + G.name('Matching covered subgraph of ({})'.format(self.name())) + if immutable: + G = G.copy(immutable=True) + + return G + + G = Graph(self, weighted=self._weighted, loops=self.allows_loops(), + multiedges=self.allows_multiple_edges()) + + H = G._subgraph_by_adding(vertices=vertices, edges=edges, + edge_property=edge_property, + immutable=False) + + try: + H = MatchingCoveredGraph(H) + H.name('Matching covered subgraph of ({})'.format(self.name())) + if immutable: + H = H.copy(immutable=True) + + return H + + except Exception as exception: + raise exception + + def _upgrade_from_graph(self, data=None, matching=None, algorithm='Edmonds', + solver=None, verbose=0, integrality_tolerance=0.001, + *args, **kwds): + r""" + Upgrade the given graph to a matching covered graph if eligible. + + See documentation ``MatchingCoveredGraph?`` for detailed information. + """ + try: + check = Graph.is_matching_covered(G=data, matching=matching, + algorithm=algorithm, + coNP_certificate=False, + solver=solver, verbose=verbose, + integrality_tolerance=integrality_tolerance) + + if check: + Graph.__init__(self, data, *args, **kwds) + else: + raise ValueError("input graph is not matching covered") + + except Exception as exception: + raise exception + + @doc_index('Overwritten methods') + def add_edge(self, u, v=None, label=None): + r""" + Add an edge from vertex ``u`` to vertex ``v``. + + .. NOTE:: + + This method overwrites the + :meth:`~sage.graphs.generic_graph.GenericGraph.add_edge` method + to ensure that resultant graph is also matching covered. + + INPUT: + + The following forms are all accepted: + + - G.add_edge(1, 2) + - G.add_edge((1, 2)) + - G.add_edges([(1, 2)]) + - G.add_edge(1, 2, 'label') + - G.add_edge((1, 2, 'label')) + - G.add_edges([(1, 2, 'label')]) + + OUTPUT: + + - If an edge is provided with a valid format, but addition of the edge + leaves the resulting graph not being matching covered, a + :exc:`ValueError` is returned without any alteration to the existing + matching covered graph. If the addition of the edge preserves the + property of matching covered, then the graph is updated and nothing + is returned. + + - If the edge is provided with an invalid format, a :exc:`ValueError` + is returned. + + WARNING: + + The following intuitive input results in nonintuitive output, + even though the resulting graph behind the intuition might be matching + covered:: + + sage: P = graphs.WheelGraph(6) + sage: G = MatchingCoveredGraph(P) + sage: G.add_edge((1, 4), 'label') + Traceback (most recent call last): + ... + ValueError: the graph obtained after the addition of edge + (((1, 4), 'label', None)) is not matching covered + sage: G.edges(sort=False) + [(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None), + (0, 5, None), (1, 2, None), (1, 5, None), (2, 3, None), + (3, 4, None), (4, 5, None)] + + The key word ``label`` must be used:: + + sage: W = graphs.WheelGraph(6) + sage: G = MatchingCoveredGraph(W) + sage: G.add_edge((1, 4), label='label') + sage: G.edges(sort=False) # No alteration to the existing graph + [(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None), + (0, 5, None), (1, 2, None), (1, 4, 'label'), (1, 5, None), + (2, 3, None), (3, 4, None), (4, 5, None)] + + An expression, analogous to the syntax mentioned above may be used:: + + sage: S = graphs.StaircaseGraph(4) + sage: G = MatchingCoveredGraph(S) + sage: G.add_edge(0, 5) + sage: G.edges(sort=False) + [(0, 1, None), (0, 3, None), (0, 5, None), (0, 6, None), + (1, 2, None), (1, 4, None), (2, 5, None), (2, 7, None), + (3, 4, None), (3, 6, None), (4, 5, None), (5, 7, None), + (6, 7, None)] + sage: G.add_edge((2, 3)) + sage: G.edges(sort=False) + [(0, 1, None), (0, 3, None), (0, 5, None), (0, 6, None), + (1, 2, None), (1, 4, None), (2, 3, None), (2, 5, None), + (2, 7, None), (3, 4, None), (3, 6, None), (4, 5, None), + (5, 7, None), (6, 7, None)] + sage: G.add_edges([(0, 4)]) + sage: G.edges(sort=False) + [(0, 1, None), (0, 3, None), (0, 4, None), (0, 5, None), + (0, 6, None), (1, 2, None), (1, 4, None), (2, 3, None), + (2, 5, None), (2, 7, None), (3, 4, None), (3, 6, None), + (4, 5, None), (5, 7, None), (6, 7, None)] + sage: G.add_edge(2, 4, 'label') + sage: G.edges(sort=False) + [(0, 1, None), (0, 3, None), (0, 4, None), (0, 5, None), + (0, 6, None), (1, 2, None), (1, 4, None), (2, 3, None), + (2, 4, 'label'), (2, 5, None), (2, 7, None), (3, 4, None), + (3, 6, None), (4, 5, None), (5, 7, None), (6, 7, None)] + sage: G.add_edge((4, 6, 'label')) + sage: G.edges(sort=False) + [(0, 1, None), (0, 3, None), (0, 4, None), (0, 5, None), + (0, 6, None), (1, 2, None), (1, 4, None), (2, 3, None), + (2, 4, 'label'), (2, 5, None), (2, 7, None), (3, 4, None), + (3, 6, None), (4, 5, None), (4, 6, 'label'), (5, 7, None), + (6, 7, None)] + sage: G.add_edges([(4, 7, 'label')]) + sage: G.edges(sort=False) + [(0, 1, None), (0, 3, None), (0, 4, None), (0, 5, None), + (0, 6, None), (1, 2, None), (1, 4, None), (2, 3, None), + (2, 4, 'label'), (2, 5, None), (2, 7, None), (3, 4, None), + (3, 6, None), (4, 5, None), (4, 6, 'label'), (4, 7, 'label'), + (5, 7, None), (6, 7, None)] + + Note that the ``weight`` of the edge shall be input as the ``label``:: + + sage: G.add_edge((1, 3), label=5) + sage: G.edges() + [(0, 1, None), (0, 3, None), (0, 4, None), (0, 5, None), + (0, 6, None), (1, 2, None), (1, 3, 5), (1, 4, None), + (2, 3, None), (2, 4, 'label'), (2, 5, None), (2, 7, None), + (3, 4, None), (3, 6, None), (4, 5, None), (4, 6, 'label'), + (4, 7, 'label'), (5, 7, None), (6, 7, None)] + sage: G.add_edge((2, 4, 6), label=6) + Traceback (most recent call last): + ... + ValueError: the graph obtained after the addition of edge + (((2, 4, 6), None, 6)) is not matching covered + + Vertex name cannot be ``None``, so:: + + sage: W = graphs.WheelGraph(6) + sage: H = MatchingCoveredGraph(W) + sage: H.add_edge(None, 1) + Traceback (most recent call last): + ... + ValueError: the graph obtained after the addition of edge + ((None, 1, None)) is not matching covered + sage: H.edges(sort=False) # No alteration to the existing graph + [(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None), + (0, 5, None), (1, 2, None), (1, 5, None), (2, 3, None), + (3, 4, None), (4, 5, None)] + sage: H.add_edge(None, None) + Traceback (most recent call last): + ... + ValueError: the graph obtained after the addition of edge + ((None, None, None)) is not matching covered + sage: H.edges(sort=False) # No alteration to the existing graph + [(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None), + (0, 5, None), (1, 2, None), (1, 5, None), (2, 3, None), + (3, 4, None), (4, 5, None)] + + EXAMPLES: + + Adding an already existing edge:: + + sage: P = graphs.PetersenGraph() + sage: G = MatchingCoveredGraph(P) + sage: G.add_edge(next(G.edge_iterator())) + sage: P == G + True + sage: G.size() + 15 + sage: G.allow_multiple_edges(True) + sage: G.add_edge(0, 1) + sage: G.size() + 16 + + Adding an edge such that the resulting graph is matching covered:: + + sage: P = graphs.PetersenGraph() + sage: G = MatchingCoveredGraph(P) + sage: G.add_edge(1, 4) + sage: G.edges(sort=False) + [(0, 1, None), (0, 4, None), (0, 5, None), (1, 2, None), + (1, 4, None), (1, 6, None), (2, 3, None), (2, 7, None), + (3, 4, None), (3, 8, None), (4, 9, None), (5, 7, None), + (5, 8, None), (6, 8, None), (6, 9, None), (7, 9, None)] + + Adding an edge with both the incident vertices being existent such + that the resulting graph is not matching covered:: + + sage: C = graphs.CycleGraph(4) + sage: G = MatchingCoveredGraph(C) + sage: G.add_edge(0, 2) + Traceback (most recent call last): + ... + ValueError: the graph obtained after the addition of edge + ((0, 2, None)) is not matching covered + sage: G.edges(sort=False) # No alteration to the existing graph + [(0, 1, None), (0, 3, None), (1, 2, None), (2, 3, None)] + + Adding an edge with exactly one incident vertex that is nonexistent + throws a :exc:`ValueError` exception, as the resulting graph would + have an odd order:: + + sage: C = graphs.CycleGraph(4) + sage: G = MatchingCoveredGraph(C) + sage: G.add_edge(0, 4) + Traceback (most recent call last): + ... + ValueError: the graph obtained after the addition of edge + ((0, 4, None)) is not matching covered + sage: G.edges(sort=False) # No alteration to the existing graph + [(0, 1, None), (0, 3, None), (1, 2, None), (2, 3, None)] + + Adding an edge with both the incident vertices that is nonexistent + throws a :exc:`ValueError` exception, as the resulting graph would + have been disconnected:: + + sage: C = graphs.CycleGraph(4) + sage: G = MatchingCoveredGraph(C) + sage: G.add_edge(4, 5) + Traceback (most recent call last): + ... + ValueError: the graph obtained after the addition of edge + ((4, 5, None)) is not matching covered + sage: G.edges(sort=False) # No alteration to the existing graph + [(0, 1, None), (0, 3, None), (1, 2, None), (2, 3, None)] + + Adding a self-loop:: + + sage: H = graphs.HeawoodGraph() + sage: G = MatchingCoveredGraph(H) + sage: v = next(G.vertex_iterator()) + sage: G.add_edge(v, v) + Traceback (most recent call last): + ... + ValueError: loops are not allowed in matching covered graphs + """ + if label is None: + if v is None: + try: + u, v, label = u + except Exception: + try: + u, v = u + except Exception: + pass + + else: + if v is None: + try: + u, v = u + except Exception: + pass + + if u in self and v in self: + if u == v: + raise ValueError('loops are not allowed in ' + 'matching covered graphs') + + # TODO: A ligher incremental method to check whether the new graph + # is matching covered instead of creating a new graph and checking + + G = Graph(self, multiedges=self.allows_multiple_edges()) + G.add_edge(u, v, label=label) + + try: + self.__init__(data=G, matching=self.get_matching()) + + except Exception: + raise ValueError('the graph obtained after the addition of ' + 'edge (%s) is not matching covered' + % str((u, v, label))) + + else: + # At least one of u or v is a nonexistent vertex. + # Thus, the resulting graph is either disconnected + # or has an odd order, hence not matching covered + raise ValueError('the graph obtained after the addition of edge ' + '(%s) is not matching covered' + % str((u, v, label))) + + @doc_index('Overwritten methods') + def add_edges(self, edges, loops=False): + r""" + Add edges from an iterable container. + + .. NOTE:: + + This method overwrites the + :meth:`~sage.graphs.generic_graph.GenericGraph.add_edges` method + to ensure that resultant graph is also matching covered. + + INPUT: + + - ``edges`` -- an iterable of edges, given either as ``(u, v)`` + or ``(u, v, 'label')`` + + - ``loops`` -- boolean (default: ``False``); note that this shall + always be set to either ``False`` or ``None`` (since matching covered + graphs are free of loops), in which case all the loops + ``(v, v, 'label')`` are removed from the iterator. If ``loops`` is + set to ``True``, a :exc:`ValueError` is thrown. + + OUTPUT: + + - If ``loops`` is set to ``True``, a :exc:`ValueError` is returned. + + - If ``edges`` is provided with a valid format, but addition of the + edges leave the resulting graph not being matching covered, a + :exc:`ValueError` is returned without any alteration to the existing + matching covered graph. If the addition of the edges preserves the + property of matching covered, then the graph is updated and nothing + is returned. + + - If ``edges`` is provided with an invalid format, a :exc:`ValueError` + is returned. + + EXAMPLES: + + Providing with an empty list of edges:: + + sage: C = graphs.CycleGraph(6) + sage: G = MatchingCoveredGraph(C) + sage: G.add_edges([]) + sage: G == C + True + + Adding some edges, the incident vertices of each of which are existent, + such that the resulting graph is matching covered:: + + sage: S = graphs.StaircaseGraph(4) + sage: G = MatchingCoveredGraph(S) + sage: F = [(0, 4), (2, 4), (4, 6), (4, 7)] + sage: G.add_edges(F) + sage: G.edges(sort=True) + [(0, 1, None), (0, 3, None), (0, 4, None), (0, 6, None), + (1, 2, None), (1, 4, None), (2, 4, None), (2, 5, None), + (2, 7, None), (3, 4, None), (3, 6, None), (4, 5, None), + (4, 6, None), (4, 7, None), (5, 7, None), (6, 7, None)] + + Adding some edges, at least one of the incident vertices of some of + which are nonexistent such that the resulting graph is matching + covered:: + + sage: C = graphs.CycleGraph(8) + sage: G = MatchingCoveredGraph(C) + sage: F = [(0, 9), (1, 8), (2, 9), (3, 8), + ....: (4, 9), (5, 8), (6, 9), (7, 8)] + sage: G.add_edges(F) + sage: G.edges(sort=True) + [(0, 1, None), (0, 7, None), (0, 9, None), (1, 2, None), + (1, 8, None), (2, 3, None), (2, 9, None), (3, 4, None), + (3, 8, None), (4, 5, None), (4, 9, None), (5, 6, None), + (5, 8, None), (6, 7, None), (6, 9, None), (7, 8, None)] + sage: G.is_isomorphic(graphs.BiwheelGraph(5)) + True + + Adding a removable double ear to a matching covered graph:: + + sage: H = graphs.HexahedralGraph() + sage: G = MatchingCoveredGraph(H) + sage: F = {(0, 8, None), (1, 10), (4, 11, 'label'), + ....: (5, 9), (8, 9), (10, 11)} + sage: G.add_edges(F) + sage: G.edges(sort=True) + [(0, 1, None), (0, 3, None), (0, 4, None), (0, 8, None), + (1, 2, None), (1, 5, None), (1, 10, None), (2, 3, None), + (2, 6, None), (3, 7, None), (4, 5, None), (4, 7, None), + (4, 11, 'label'), (5, 6, None), (5, 9, None), (6, 7, None), + (8, 9, None), (10, 11, None)] + + Adding some edges, the incident vertices of each of which are existent, + such that the resulting graph is NOT matching covered:: + + sage: C = graphs.CycleGraph(6) + sage: G = MatchingCoveredGraph(C) + sage: F = [(0, 2), (3, 5)] + sage: G.add_edges(F) + Traceback (most recent call last): + ... + ValueError: the resulting graph after the addition ofthe edges is not matching covered + + Adding some edges, at least one of the incident vertices of some of + which are nonexistent such that the resulting graph is NOT matching + covered:: + + sage: H = graphs.HexahedralGraph() + sage: G = MatchingCoveredGraph(H) + sage: F = [(3, 8), (6, 9), (8, 9)] + sage: G.add_edges(F) + Traceback (most recent call last): + ... + ValueError: the resulting graph after the addition ofthe edges is not matching covered + sage: I = [(0, 8), (1, 9)] + sage: G.add_edges(I) + Traceback (most recent call last): + ... + ValueError: the resulting graph after the addition ofthe edges is not matching covered + sage: J = [(u, 8) for u in range(8)] + sage: G.add_edges(J) + Traceback (most recent call last): + ... + ValueError: odd order is not allowed for matching covered graphs + + Setting the parameter ``loops`` to either ``False`` or ``None``:: + + sage: W = graphs.WheelGraph(6) + sage: G = MatchingCoveredGraph(W) + sage: F = [(0, 0), (1, 3), (2, 4)] + sage: G.add_edges(edges=F, loops=False) + sage: G.edges(sort=True) + [(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None), + (0, 5, None), (1, 2, None), (1, 3, None), (1, 5, None), + (2, 3, None), (2, 4, None), (3, 4, None), (4, 5, None)] + sage: J = [(1, 1), (3, 5)] + sage: G.add_edges(edges=J, loops=True) + Traceback (most recent call last): + ... + ValueError: loops are not allowed in matching covered graphs + sage: G.edges(sort=True) + [(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None), + (0, 5, None), (1, 2, None), (1, 3, None), (1, 5, None), + (2, 3, None), (2, 4, None), (3, 4, None), (4, 5, None)] + + Setting the parameter ``loops`` to ``True``:: + + sage: P = graphs.PetersenGraph() + sage: G = MatchingCoveredGraph(P) + sage: F = [(0, 0), (0, 2), (0, 3)] + sage: G.add_edges(edges=F, loops=True) + Traceback (most recent call last): + ... + ValueError: loops are not allowed in matching covered graphs + + Adding a multiple edge:: + + sage: S = graphs.StaircaseGraph(4) + sage: G = MatchingCoveredGraph(S) + sage: G.allow_multiple_edges(True) + sage: F = [(0, 1, 'label'), (0, 4), (1, 2)] + sage: G.add_edges(F) + sage: G.edges(sort=False) + [(0, 1, None), (0, 1, 'label'), (0, 3, None), (0, 4, None), + (0, 6, None), (1, 2, None), (1, 2, None), (1, 4, None), + (2, 5, None), (2, 7, None), (3, 4, None), (3, 6, None), + (4, 5, None), (5, 7, None), (6, 7, None)] + + TESTS: + + Providing with an edge in ``edges`` that has 0 values to unpack:: + + sage: W = graphs.WagnerGraph() + sage: G = MatchingCoveredGraph(W) + sage: G.add_edges([()]) + Traceback (most recent call last): + ... + ValueError: need more than 0 values to unpack + + Providing with an edge in ``edges`` that has precisely one value to unpack:: + + sage: T = graphs.TruncatedBiwheelGraph(10) + sage: G = MatchingCoveredGraph(T) + sage: G.add_edges([(0, )]) + Traceback (most recent call last): + ... + ValueError: need more than 1 value to unpack + + Providing with an edge in ``edges`` that has more than 3 values to unpack:: + + sage: B = graphs.BiwheelGraph(5) + sage: G = MatchingCoveredGraph(B) + sage: G.add_edges([(0, 1, 2, 3, 4)]) + Traceback (most recent call last): + ... + ValueError: too many values to unpack (expected 2) + + Providing with an edge of unknown data type:: + + sage: M = graphs.MurtyGraph() + sage: G = MatchingCoveredGraph(M) + sage: F = ['', 'edge', None, 1234] + sage: G.add_edges(F) + Traceback (most recent call last): + ... + TypeError: input edges is of unknown type + """ + if loops: + raise ValueError('loops are not allowed in ' + 'matching covered graphs') + + if not edges: # do nothing + return + + for edge in edges: + if isinstance(edge, tuple): + if len(edge) == 0: + raise ValueError('need more than 0 values to unpack') + + elif len(edge) == 1: + raise ValueError('need more than 1 value to unpack') + + elif len(edge) > 3: + raise ValueError('too many values to unpack (expected 2)') + + else: + raise TypeError('input edges is of unknown type') + + # Remove potentially duplicated edges + edges = list(set(edges)) + + # Remove all the loops from edges + for edge in edges: + if edge[0] == edge[1]: + edges.remove(edge) + + # Check if all the incident vertices of the input edges are existent + new_vertices = list(set([x for u, v, *_ in edges for x in [u, v]])) + + for vertex in new_vertices[:]: + if vertex in self: + new_vertices.remove(vertex) + + # Throw error if the no. of new vertices is odd + if len(new_vertices) % 2: + raise ValueError('odd order is not allowed for ' + 'matching covered graphs') + + try: + G = Graph(self, multiedges=self.allows_multiple_edges()) + G.add_edges(edges=edges, loops=loops) + + # Check if G has a vertex with at most 1 neighbor + if any(len(G.neighbors(v)) <= 1 for v in G): + raise ValueError('the resulting graph after the addition of' + 'the edges is not matching covered') + + # If all the vertices are existent, the existing perfect matching + # can be used. + if not new_vertices: + self.__init__(data=G, matching=self.get_matching()) + + else: + # Check if the existing perfect matching may be extended to a + # perfect matching of the new graph + edges_with_two_new_vertices = [] + + for edge in edges: + if edge[0] in new_vertices and edge[1] in new_vertices: + edges_with_two_new_vertices.append(edge) + + H = Graph(data=edges_with_two_new_vertices, format='list_of_edges') + M = Graph(self.get_matching()).union(Graph(H.matching())) + + # Check if M is a perfect matching of the resulting graph + if (G.order() != 2*M.size()): + M = None + + self.__init__(data=G, matching=M) + + except Exception: + raise ValueError('the resulting graph after the addition of' + 'the edges is not matching covered') + + @doc_index('Overwritten methods') + def add_vertex(self, name=None): + r""" + Add a vertex to the (matching covered) graph. + + .. NOTE:: + + This method overwrites the + :meth:`~sage.graphs.generic_graph.GenericGraph.add_vertex` method + to ensure that isolated vertices are forbidden in + :class:`~MatchingCoveredGraph`. + + INPUT: + + - ``name`` -- an immutable object (default: ``None``); when no name is + specified (default), then the new vertex will be represented by the + least integer not already representing a vertex. ``name`` must be an + immutable object (e.g., an integer, a tuple, etc.). + + OUTPUT: + + - If ``name`` specifies an existing vertex, then nothing is done. + Otherwise a :exc:`ValueError` is returned with no change to the + existing (matching covered) graph is returned since matching covered + graphs are free of isolated vertices. + + EXAMPLES: + + Adding an existing vertex:: + + sage: P = graphs.PetersenGraph() + sage: P + Petersen graph: Graph on 10 vertices + sage: G = MatchingCoveredGraph(P) + sage: G + Matching covered petersen graph: graph on 10 vertices + sage: u = next(G.vertex_iterator()) + sage: G.add_vertex(u) + sage: G + Matching covered petersen graph: graph on 10 vertices + + Adding a new/ non-existing vertex:: + + sage: G.add_vertex() + Traceback (most recent call last): + ... + ValueError: isolated vertices are not allowed in matching covered graphs + sage: u = 100 + sage: G.add_vertex(u) + Traceback (most recent call last): + ... + ValueError: isolated vertices are not allowed in matching covered graphs + """ + if name not in self: + raise ValueError('isolated vertices are not allowed in ' + 'matching covered graphs') + + @doc_index('Overwritten methods') + def add_vertices(self, vertices): + r""" + Add vertices to the (matching covered) graph from an iterable container + of vertices. + + .. NOTE:: + + This method overwrites the + :meth:`~sage.graphs.generic_graph.GenericGraph.add_vertices` method + to ensure that isolated vertices are forbidden in + :class:`~MatchingCoveredGraph`. + + INPUT: + + - ``vertices`` -- iterator container of vertex labels. A new label is + created, used and returned in the output list for all ``None`` values + in ``vertices``. + + OUTPUT: + + - If all of the vertices are existing vertices of the (matching + covered) graph, then nothing is done; otherwise a :exc:`ValueError` + is returned with no change to the existing (matching covered) graph + since matching covered graphs are free of isolated vertices. + + EXAMPLES: + + Adding a list of already existing vertices:: + + sage: T = graphs.TruncatedBiwheelGraph(15) + sage: T + Truncated biwheel graph: Graph on 30 vertices + sage: G = MatchingCoveredGraph(T) + sage: G + Matching covered truncated biwheel graph: graph on 30 vertices + sage: S = [0, 1, 2, 3] # We choose 4 existing vertices + sage: G.add_vertices(S) + sage: G + Matching covered truncated biwheel graph: graph on 30 vertices + + Adding a list of vertices in which at least one is non-existent or + ``None`` or possibly both:: + + sage: T = graphs.CompleteGraph(2) + sage: T + Complete graph: Graph on 2 vertices + sage: G = MatchingCoveredGraph(T) + sage: G + Matching covered complete graph: graph on 2 vertices + sage: S1 = [2, 3, 4] + sage: G.add_vertices(S1) + Traceback (most recent call last): + ... + ValueError: isolated vertices are not allowed in matching covered graphs + sage: S2 = [None, None] + sage: G.add_vertices(S2) + Traceback (most recent call last): + ... + ValueError: isolated vertices are not allowed in matching covered graphs + sage: S3 = [2, None, None, 5] + sage: G.add_vertices(S3) + Traceback (most recent call last): + ... + ValueError: isolated vertices are not allowed in matching covered graphs + """ + if any(vertex not in self for vertex in vertices): + raise ValueError('isolated vertices are not allowed in ' + 'matching covered graphs') + + @doc_index('Overwritten methods') + def allow_loops(self, new, check=True): + r""" + Change whether loops are allowed in (matching covered) graphs. + + .. NOTE:: + + This method overwrites the + :meth:`~sage.graphs.generic_graph.GenericGraph.allow_loops` method + to ensure that loops are forbidden in :class:`~MatchingCoveredGraph`. + + INPUT: + + - ``new`` -- boolean + + - ``check`` -- boolean (default: ``True``); whether to remove existing + loops from the graph when the new status is ``False``. It is an + argument in + :meth:`~sage.graphs.generic_graph.GenericGraph.allow_loops` method + and is not used in this overwritten one. + + OUTPUT: + + - A :exc:`ValueError` is returned with no change to the existing + (matching covered) graph if ``new`` is ``True`` since a matching + covered graph, by definition, is free of self-loops. If ``new`` is + set to ``False``, there is no output. + + EXAMPLES: + + Petersen graph is matching covered:: + + sage: P = graphs.PetersenGraph() + sage: P.is_matching_covered() + True + sage: G = MatchingCoveredGraph(P) + sage: G.allow_loops(True) + Traceback (most recent call last): + ... + ValueError: loops are not allowed in matching covered graphs + """ + if new: + raise ValueError('loops are not allowed in ' + 'matching covered graphs') + + @doc_index('Overwritten methods') + def allows_loops(self): + r""" + Return whether loops are permitted in (matching covered) graphs. + + .. NOTE:: + + This method overwrites the + :meth:`~sage.graphs.generic_graph.GenericGraph.allows_loops` method + to show that loops are forbidden in :class:`~MatchingCoveredGraph`. + + OUTPUT: + + - A boolean value ``False`` is returned, since matching covered graphs, + by definition, are free of loops. + + EXAMPLES: + + Petersen graph is matching covered:: + + sage: P = graphs.PetersenGraph() + sage: P.is_matching_covered() + True + sage: G = MatchingCoveredGraph(P) + sage: G.allows_loops() + False + """ + return False + + @doc_index('Overwritten methods') + def delete_vertex(self, vertex, in_order=False): + r""" + Delete a vertex, removing all incident edges. + + .. NOTE:: + + This method overwrites the + :meth:`~sage.graphs.generic_graph.GenericGraph.delete_vertex` + method to ensure that an odd order is forbidden in + :class:`~MatchingCoveredGraph`. + + INPUT: + + - ``vertex`` -- a vertex that is to be deleted. + + - ``in_order`` -- boolean (default: ``False``); if ``True``, this + deletes the `i`-th vertex in the sorted list of vertices, i.e. + ``G.vertices(sort=True)[i]`` + + OUTPUT: + + - Deleting a non-existent vertex raises a :exc:`ValueError` exception; + also a (different) :exc:`ValueError` is returned on deleting an + existing vertex since matching covered graphs are of even order. In + both cases no modifications are made to the existing (matching + covered) graph. + + EXAMPLES: + + Deleting a non-existent vertex:: + + sage: W = graphs.WheelGraph(12) + sage: G = MatchingCoveredGraph(W) + sage: u = 100 + sage: G.delete_vertex(u) + Traceback (most recent call last): + ... + ValueError: vertex (100) not in the graph + sage: G.delete_vertex(vertex=u, in_order=True) + Traceback (most recent call last): + ... + ValueError: vertex (100) not in the graph + + Deleting an existing vertex:: + + sage: W = graphs.WheelGraph(12) + sage: G = MatchingCoveredGraph(W) + sage: u = next(G.vertex_iterator()) + sage: G.delete_vertex(u) + Traceback (most recent call last): + ... + ValueError: odd order is not allowed for matching covered graphs + sage: G.delete_vertex(vertex=u, in_order=True) + Traceback (most recent call last): + ... + ValueError: odd order is not allowed for matching covered graphs + """ + if vertex not in self: + raise ValueError('vertex (%s) not in the graph' % str(vertex)) + + if in_order: + vertex = self.vertices(sort=True)[vertex] + + raise ValueError('odd order is not allowed for ' + 'matching covered graphs') + + @doc_index('Overwritten methods') + def delete_vertices(self, vertices): + r""" + Delete specified vertices form ``self``. + + This method deletes the vertices from the iterable container + ``vertices`` from ``self`` along with incident edges. An error is + raised if the resulting graph is not matching covered. + + .. NOTE:: + + This method overwrites the + :meth:`~sage.graphs.generic_graph.GenericGraph.delete_vertices` + method to ensure that an odd order is forbidden in + :class:`~MatchingCoveredGraph`. + + INPUT: + + - ``vertices`` -- a list/ set of vertices that are to be deleted. + + OUTPUT: + + - Deleting a non-existent vertex will raise a :exc:`ValueError` + exception, in which case none of the vertices in ``vertices`` + is deleted. + + - If all of the vertices in the list/ set provided exist in the graph, + but the resulting graph after deletion of all of those is not + matching covered, then a :exc:`ValueError` exception is raised + without any alterations to the existing (matching covered) graph, + otherwise the vertices are deleted and nothing is returned. + + EXAMPLES: + + Providing with an empty list of vertices:: + + sage: C = graphs.CycleGraph(6) + sage: G = MatchingCoveredGraph(C) + sage: G.delete_vertices([]) + sage: G == C + True + + Removing all the existent vertices:: + + sage: M = graphs.MoebiusLadderGraph(10) + sage: G = MatchingCoveredGraph(M) + sage: S = list(G.vertices()) + sage: G.delete_vertices(S) + Traceback (most recent call last): + ... + ValueError: the resulting graph after the removal of the vertices + is trivial, therefore is not matching covered + + Providing with a list of vertices with at least one non-existent + vertex:: + + sage: S = graphs.StaircaseGraph(4) + sage: S + Staircase graph: Graph on 8 vertices + sage: G = MatchingCoveredGraph(S) + sage: G + Matching covered staircase graph: graph on 8 vertices + sage: T = list(range(5, 20, 2)) + sage: G.delete_vertices(T) + Traceback (most recent call last): + ... + ValueError: vertex (9) not in the graph + + Removing an odd no. of distinct vertices from + a matching covered graph:: + + sage: P = graphs.PetersenGraph() + sage: G = MatchingCoveredGraph(P) + sage: S = [0, 1, 2, 10, 10, 100] + sage: G.delete_vertices(S) + Traceback (most recent call last): + ... + ValueError: an odd no. of distinct vertices can not be + removed from a matching covered graph + + Providing with a list of existent vertices whose deletion results in a + graph which is not matching covered:: + + sage: S = graphs.StaircaseGraph(4) + sage: S + Staircase graph: Graph on 8 vertices + sage: G = MatchingCoveredGraph(S) + sage: G + Matching covered staircase graph: graph on 8 vertices + sage: T = [1, 4] + sage: G.delete_vertices(T) + Traceback (most recent call last): + ... + ValueError: the resulting graph after the removal of + the vertices is not matching covered + + Providing with a list of existent vertices after the deletion of which + the resulting graph is still matching covered; note that in the + following example, after the deletion of two vertices from a staircase + graph, the resulting graph is NOT a staircase graph + (see :issue:`38768`):: + + sage: S = graphs.StaircaseGraph(4) + sage: S + Staircase graph: Graph on 8 vertices + sage: G = MatchingCoveredGraph(S) + sage: G + Matching covered staircase graph: graph on 8 vertices + sage: T = [6, 7] + sage: G.delete_vertices(T) + sage: G # Matching covered graph on 6 vertices + Matching covered staircase graph: graph on 6 vertices + """ + if not vertices: # do nothing + return + + # Remove potentially duplicated vertices + vertices = set(vertices) + + if len(vertices) % 2: # try to remove an odd number of vertices + raise ValueError('an odd no. of distinct vertices can not be ' + 'removed from a matching covered graph') + + for vertex in vertices: + if vertex not in self: + raise ValueError('vertex (%s) not in the graph' % str(vertex)) + + if self.order() == len(vertices): + raise ValueError('the resulting graph after the removal of the ' + 'vertices is trivial, therefore is not ' + 'matching covered') + + try: + G = Graph(self, multiedges=self.allows_multiple_edges()) + G.delete_vertices(vertices) + + M = Graph(self.get_matching()) + + M.delete_vertices(vertices) + # The resulting matching after the removal of the input vertices + # must be a valid perfect matching of the resulting graph obtained + # after the removal of the vertices + + if (G.order() != 2*M.size()): + M = None + + self.__init__(data=G, matching=M) + + except Exception: + raise ValueError('the resulting graph after the removal of ' + 'the vertices is not matching covered') + + @doc_index('Miscellaneous methods') + def get_matching(self): + r""" + Return an :class:`~EdgesView` of ``self._matching``. + + OUTPUT: + + - This method returns :class:`EdgesView` of the edges of a + perfect matching of the (matching covered) graph. + + EXAMPLES: + + If one specifies a perfect matching while initializing the object, the + value of ``self._matching`` is captures the same matching:: + + sage: P = graphs.PetersenGraph() + sage: M = [(0, 1), (2, 3), (4, 9), (5, 7), (6, 8)] + sage: G = MatchingCoveredGraph(P, M) + sage: sorted(G.get_matching()) + [(0, 1), (2, 3), (4, 9), (5, 7), (6, 8)] + sage: M == sorted(G.get_matching()) + True + + If no matching is specified while initilizing a matching covered graph, + a perfect is computed + :meth:`~sage.graphs.graph.Graph.matching` and that is captured as + ``self._matching``:: + + sage: P = graphs.PetersenGraph() + sage: M = P.matching() + sage: G = MatchingCoveredGraph(P) + sage: sorted(G.get_matching()) + [(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)] + sage: sorted(M) == sorted(G.get_matching()) + True + """ + return self._matching + + @doc_index('Overwritten methods') + def has_perfect_matching(G, algorithm='Edmonds', solver=None, verbose=0, + *, integrality_tolerance=1e-3): + r""" + Check whether the graph has a perfect matching. + + .. NOTE:: + + This method overwrites the + :meth:`~sage.graphs.graph.Graph.has_perfect_matching` method in + order to return ``True`` (provided the input arguments are valid) + as matching covered graphs always admit a perfect matching. + + INPUT: + + - ``algorithm`` -- string (default: ``'Edmonds'``) + + - ``'Edmonds'`` uses Edmonds' algorithm as implemented in NetworkX to + find a matching of maximal cardinality, then check whether this + cardinality is half the number of vertices of the graph. + + - ``'LP_matching'`` uses a Linear Program to find a matching of + maximal cardinality, then check whether this cardinality is half the + number of vertices of the graph. + + - ``'LP'`` uses a Linear Program formulation of the perfect matching + problem: put a binary variable ``b[e]`` on each edge `e`, and for + each vertex `v`, require that the sum of the values of the edges + incident to `v` is 1. + + - ``solver`` -- string (default: ``None``); specifies a Mixed Integer + Linear Programming (MILP) solver to be used. If set to ``None``, the + default one is used. For more information on MILP solvers and which + default solver is used, see the method :meth:`solve + ` of the class + :class:`MixedIntegerLinearProgram + `. + + - ``verbose`` -- integer (default: 0); sets the level of verbosity: + set to 0 by default, which means quiet (only useful when + ``algorithm == "LP_matching"`` or ``algorithm == "LP"``) + + - ``integrality_tolerance`` -- float; parameter for use with MILP + solvers over an inexact base ring; see + :meth:`MixedIntegerLinearProgram.get_values`. + + OUTPUT: + + - If the input arguments are valid, a boolean (``True``) is returned as + a maximum matching of a matching covered graph is always a perfect + matching, otherwise a :exc:`~ValueError` is raised. + + EXAMPLES: + + Note that regardless of the algorithm (as long as the input arguments + are in valid format), the method always returns the boolean ``True``:: + + sage: P = graphs.PetersenGraph() + sage: P.has_perfect_matching() # Calls Graph.has_perfect_matching() + True + sage: G = MatchingCoveredGraph(P) + sage: G.has_perfect_matching() # Calls MatchingCoveredGraph.has_perfect_matching() + True + sage: W = graphs.WheelGraph(6) + sage: H = MatchingCoveredGraph(W) + sage: H.has_perfect_matching(algorithm='LP_matching') + True + + Providing with an algorithm, that is not one of ``'Edmonds'``, + ``'LP_matching'`` or ``'LP'``:: + + sage: S = graphs.StaircaseGraph(4) + sage: J = MatchingCoveredGraph(S) + sage: J.has_perfect_matching(algorithm='algorithm') + Traceback (most recent call last): + ... + ValueError: algorithm must be set to 'Edmonds', + 'LP_matching' or 'LP' + """ + if algorithm in ['Edmonds', 'LP_matching', 'LP']: + return True + + raise ValueError('algorithm must be set to \'Edmonds\', ' + '\'LP_matching\' or \'LP\'') + + @doc_index('Miscellaneous methods') + def update_matching(self, matching): + r""" + Update the perfect matching captured in ``self._matching``. + + INPUT: + + - ``matching`` -- a perfect matching of the graph, that can be given + using any valid input format of :class:`~sage.graphs.graph.Graph`. + + OUTPUT: + + - If ``matching`` is a valid perfect matching of the graph, then + ``self._matching`` gets updated to this provided matching, or + otherwise an exception is returned without any alterations to + ``self._matching``. + + EXAMPLES: + + Providing with a valid perfect matching of the graph:: + + sage: P = graphs.PetersenGraph() + sage: G = MatchingCoveredGraph(P) + sage: sorted(G.get_matching()) + [(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)] + sage: M = [(0, 1), (2, 3), (4, 9), (5, 7), (6, 8)] + sage: G.update_matching(M) + sage: sorted(G.get_matching()) + [(0, 1, None), (2, 3, None), (4, 9, None), (5, 7, None), (6, 8, None)] + + TESTS: + + Providing with a wrong matching:: + + sage: P = graphs.PetersenGraph() + sage: G = MatchingCoveredGraph(P) + sage: sorted(G.get_matching()) + [(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)] + sage: S = str('0') + sage: G.update_matching(S) + Traceback (most recent call last): + ... + RuntimeError: the string seems corrupt: valid characters are + ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ + sage: T = str('graph') + sage: G.update_matching(T) + Traceback (most recent call last): + ... + RuntimeError: the string (graph) seems corrupt: for n = 40, + the string is too short + sage: M = Graph(G.matching()) + sage: M.add_edges([(0, 1), (0, 2)]) + sage: G.update_matching(M) + Traceback (most recent call last): + ... + ValueError: the input is not a matching + sage: N = Graph(G.matching()) + sage: N.add_edge(10, 11) + sage: G.update_matching(N) + Traceback (most recent call last): + ... + ValueError: the input is not a matching of the graph + sage: J = Graph() + sage: J.add_edges([(0, 1), (2, 3)]) + sage: G.update_matching(J) + Traceback (most recent call last): + ... + ValueError: the input is not a perfect matching of the graph + """ + try: + M = Graph(matching) + + if any(d != 1 for d in M.degree()): + raise ValueError("the input is not a matching") + + if any(not self.has_edge(edge) for edge in M.edge_iterator()): + raise ValueError("the input is not a matching of the graph") + + if (self.order() != M.order()): + raise ValueError("the input is not a perfect matching of the graph") + + self._matching = M.edges() + + except Exception as exception: + raise exception + + +__doc__ = __doc__.replace('{INDEX_OF_METHODS}', gen_thematic_rest_table_index(MatchingCoveredGraph, only_local_functions=False)) \ No newline at end of file diff --git a/src/sage/graphs/meson.build b/src/sage/graphs/meson.build index dbc1808f107..d88f1942daf 100644 --- a/src/sage/graphs/meson.build +++ b/src/sage/graphs/meson.build @@ -36,6 +36,7 @@ py.install_sources( 'isgci.py', 'lovasz_theta.py', 'matching.py', + 'matching_covered_graph.py', 'mcqd.pxd', 'orientations.py', 'partial_cube.py',