passagemath-graphs 10.6.1rc1__cp310-cp310-musllinux_1_2_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
- passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
- passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
- passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
- passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
- passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
- passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
- sage/all__sagemath_graphs.py +39 -0
- sage/combinat/abstract_tree.py +2723 -0
- sage/combinat/all__sagemath_graphs.py +34 -0
- sage/combinat/binary_tree.py +5306 -0
- sage/combinat/cluster_algebra_quiver/all.py +22 -0
- sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
- sage/combinat/cluster_algebra_quiver/interact.py +124 -0
- sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
- sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
- sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
- sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
- sage/combinat/designs/MOLS_handbook_data.py +570 -0
- sage/combinat/designs/all.py +58 -0
- sage/combinat/designs/bibd.py +1655 -0
- sage/combinat/designs/block_design.py +1071 -0
- sage/combinat/designs/covering_array.py +269 -0
- sage/combinat/designs/covering_design.py +530 -0
- sage/combinat/designs/database.py +5615 -0
- sage/combinat/designs/design_catalog.py +122 -0
- sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/designs_pyx.pxd +21 -0
- sage/combinat/designs/designs_pyx.pyx +993 -0
- sage/combinat/designs/difference_family.py +3951 -0
- sage/combinat/designs/difference_matrices.py +279 -0
- sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
- sage/combinat/designs/ext_rep.py +1064 -0
- sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
- sage/combinat/designs/group_divisible_designs.py +361 -0
- sage/combinat/designs/incidence_structures.py +2357 -0
- sage/combinat/designs/latin_squares.py +581 -0
- sage/combinat/designs/orthogonal_arrays.py +2244 -0
- sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
- sage/combinat/designs/resolvable_bibd.py +815 -0
- sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
- sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/subhypergraph_search.pyx +530 -0
- sage/combinat/designs/twographs.py +306 -0
- sage/combinat/finite_state_machine.py +14874 -0
- sage/combinat/finite_state_machine_generators.py +2006 -0
- sage/combinat/graph_path.py +448 -0
- sage/combinat/interval_posets.py +3908 -0
- sage/combinat/nu_tamari_lattice.py +269 -0
- sage/combinat/ordered_tree.py +1446 -0
- sage/combinat/posets/all.py +46 -0
- sage/combinat/posets/bubble_shuffle.py +247 -0
- sage/combinat/posets/cartesian_product.py +493 -0
- sage/combinat/posets/d_complete.py +182 -0
- sage/combinat/posets/elements.py +273 -0
- sage/combinat/posets/forest.py +30 -0
- sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/hasse_cython.pyx +174 -0
- sage/combinat/posets/hasse_diagram.py +3672 -0
- sage/combinat/posets/hochschild_lattice.py +158 -0
- sage/combinat/posets/incidence_algebras.py +794 -0
- sage/combinat/posets/lattices.py +5117 -0
- sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/linear_extension_iterator.pyx +292 -0
- sage/combinat/posets/linear_extensions.py +1037 -0
- sage/combinat/posets/mobile.py +275 -0
- sage/combinat/posets/moebius_algebra.py +776 -0
- sage/combinat/posets/poset_examples.py +2178 -0
- sage/combinat/posets/posets.py +9360 -0
- sage/combinat/rooted_tree.py +1070 -0
- sage/combinat/shard_order.py +239 -0
- sage/combinat/tamari_lattices.py +384 -0
- sage/combinat/yang_baxter_graph.py +923 -0
- sage/databases/all__sagemath_graphs.py +1 -0
- sage/databases/knotinfo_db.py +1231 -0
- sage/ext_data/all__sagemath_graphs.py +1 -0
- sage/ext_data/graphs/graph_plot_js.html +330 -0
- sage/ext_data/kenzo/CP2.txt +45 -0
- sage/ext_data/kenzo/CP3.txt +349 -0
- sage/ext_data/kenzo/CP4.txt +4774 -0
- sage/ext_data/kenzo/README.txt +49 -0
- sage/ext_data/kenzo/S4.txt +20 -0
- sage/graphs/all.py +42 -0
- sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/asteroidal_triples.pyx +320 -0
- sage/graphs/base/all.py +1 -0
- sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/boost_graph.pxd +106 -0
- sage/graphs/base/boost_graph.pyx +3045 -0
- sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/c_graph.pxd +106 -0
- sage/graphs/base/c_graph.pyx +5096 -0
- sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/dense_graph.pxd +28 -0
- sage/graphs/base/dense_graph.pyx +801 -0
- sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/graph_backends.pxd +5 -0
- sage/graphs/base/graph_backends.pyx +797 -0
- sage/graphs/base/overview.py +85 -0
- sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/sparse_graph.pxd +90 -0
- sage/graphs/base/sparse_graph.pyx +1653 -0
- sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_dense_graph.pxd +5 -0
- sage/graphs/base/static_dense_graph.pyx +1032 -0
- sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_backend.pxd +27 -0
- sage/graphs/base/static_sparse_backend.pyx +1583 -0
- sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_graph.pxd +37 -0
- sage/graphs/base/static_sparse_graph.pyx +1375 -0
- sage/graphs/bipartite_graph.py +2732 -0
- sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/centrality.pyx +1038 -0
- sage/graphs/cographs.py +519 -0
- sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/comparability.pyx +851 -0
- sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/connectivity.pxd +157 -0
- sage/graphs/connectivity.pyx +4813 -0
- sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/convexity_properties.pxd +16 -0
- sage/graphs/convexity_properties.pyx +870 -0
- sage/graphs/digraph.py +4754 -0
- sage/graphs/digraph_generators.py +1993 -0
- sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/distances_all_pairs.pxd +12 -0
- sage/graphs/distances_all_pairs.pyx +2938 -0
- sage/graphs/domination.py +1363 -0
- sage/graphs/dot2tex_utils.py +100 -0
- sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/edge_connectivity.pyx +1215 -0
- sage/graphs/generators/all.py +1 -0
- sage/graphs/generators/basic.py +1769 -0
- sage/graphs/generators/chessboard.py +538 -0
- sage/graphs/generators/classical_geometries.py +1611 -0
- sage/graphs/generators/degree_sequence.py +235 -0
- sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generators/distance_regular.pyx +2846 -0
- sage/graphs/generators/families.py +4759 -0
- sage/graphs/generators/intersection.py +565 -0
- sage/graphs/generators/platonic_solids.py +262 -0
- sage/graphs/generators/random.py +2623 -0
- sage/graphs/generators/smallgraphs.py +5741 -0
- sage/graphs/generators/world_map.py +724 -0
- sage/graphs/generic_graph.py +26867 -0
- sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generic_graph_pyx.pxd +34 -0
- sage/graphs/generic_graph_pyx.pyx +1673 -0
- sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/genus.pyx +622 -0
- sage/graphs/graph.py +9645 -0
- sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_coloring.pyx +2284 -0
- sage/graphs/graph_database.py +1177 -0
- sage/graphs/graph_decompositions/all.py +1 -0
- sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
- sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
- sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
- sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
- sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
- sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/graph_products.pyx +508 -0
- sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
- sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
- sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
- sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
- sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
- sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
- sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
- sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
- sage/graphs/graph_editor.py +82 -0
- sage/graphs/graph_generators.py +3314 -0
- sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_generators_pyx.pyx +95 -0
- sage/graphs/graph_input.py +812 -0
- sage/graphs/graph_latex.py +2064 -0
- sage/graphs/graph_list.py +410 -0
- sage/graphs/graph_plot.py +1756 -0
- sage/graphs/graph_plot_js.py +338 -0
- sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/hyperbolicity.pyx +1704 -0
- sage/graphs/hypergraph_generators.py +364 -0
- sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/independent_sets.pxd +13 -0
- sage/graphs/independent_sets.pyx +402 -0
- sage/graphs/isgci.py +1033 -0
- sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/isoperimetric_inequalities.pyx +489 -0
- sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/line_graph.pyx +743 -0
- sage/graphs/lovasz_theta.py +77 -0
- sage/graphs/matching.py +1633 -0
- sage/graphs/matching_covered_graph.py +3590 -0
- sage/graphs/orientations.py +1489 -0
- sage/graphs/partial_cube.py +459 -0
- sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/path_enumeration.pyx +2040 -0
- sage/graphs/pq_trees.py +1129 -0
- sage/graphs/print_graphs.py +201 -0
- sage/graphs/schnyder.py +865 -0
- sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/spanning_tree.pyx +1457 -0
- sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/strongly_regular_db.pyx +3340 -0
- sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/traversals.pxd +9 -0
- sage/graphs/traversals.pyx +1872 -0
- sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/trees.pxd +15 -0
- sage/graphs/trees.pyx +310 -0
- sage/graphs/tutte_polynomial.py +713 -0
- sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/views.pyx +794 -0
- sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/weakly_chordal.pyx +604 -0
- sage/groups/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
- sage/knots/all.py +6 -0
- sage/knots/free_knotinfo_monoid.py +507 -0
- sage/knots/gauss_code.py +291 -0
- sage/knots/knot.py +682 -0
- sage/knots/knot_table.py +284 -0
- sage/knots/knotinfo.py +2900 -0
- sage/knots/link.py +4715 -0
- sage/sandpiles/all.py +13 -0
- sage/sandpiles/examples.py +225 -0
- sage/sandpiles/sandpile.py +6365 -0
- sage/topology/all.py +22 -0
- sage/topology/cell_complex.py +1214 -0
- sage/topology/cubical_complex.py +1976 -0
- sage/topology/delta_complex.py +1806 -0
- sage/topology/filtered_simplicial_complex.py +744 -0
- sage/topology/moment_angle_complex.py +823 -0
- sage/topology/simplicial_complex.py +5160 -0
- sage/topology/simplicial_complex_catalog.py +92 -0
- sage/topology/simplicial_complex_examples.py +1680 -0
- sage/topology/simplicial_complex_homset.py +205 -0
- sage/topology/simplicial_complex_morphism.py +836 -0
- sage/topology/simplicial_set.py +4102 -0
- sage/topology/simplicial_set_catalog.py +55 -0
- sage/topology/simplicial_set_constructions.py +2954 -0
- sage/topology/simplicial_set_examples.py +865 -0
- sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,3590 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-graphs
|
2
|
+
# sage.doctest: needs networkx
|
3
|
+
r"""
|
4
|
+
Matching covered graphs
|
5
|
+
|
6
|
+
This module implements functions and operations pertaining to matching covered
|
7
|
+
graphs.
|
8
|
+
|
9
|
+
A *matching* in a graph is a set of pairwise nonadjacent links
|
10
|
+
(nonloop edges). In other words, a matching in a graph is the edge set of an
|
11
|
+
1-regular subgraph. A matching is called a *perfect* *matching* if it the
|
12
|
+
subgraph generated by a set of matching edges spans the graph, i.e. it's the
|
13
|
+
edge set of an 1-regular spanning subgraph. A connected nontrivial graph is
|
14
|
+
called *matching* *covered* if each edge participates in some perfect matching.
|
15
|
+
|
16
|
+
{INDEX_OF_METHODS}
|
17
|
+
|
18
|
+
REFERENCES:
|
19
|
+
|
20
|
+
- This methods of this module has been adopted and inspired by the book of
|
21
|
+
Lucchesi and Murty --- *Perfect Matchings: a theory of matching covered
|
22
|
+
graphs* [LM2024]_.
|
23
|
+
|
24
|
+
AUTHORS:
|
25
|
+
|
26
|
+
- Janmenjaya Panda (2024-06-14): initial version
|
27
|
+
|
28
|
+
.. TODO::
|
29
|
+
|
30
|
+
The following methods are to be incorporated in
|
31
|
+
:class:`~MatchingCoveredGraph`:
|
32
|
+
|
33
|
+
.. csv-table::
|
34
|
+
:class: contentstable
|
35
|
+
:widths: 30, 70
|
36
|
+
:delim: |
|
37
|
+
|
38
|
+
``__hash__()`` | Compute a hash for ``self``, if ``self`` is immutable.
|
39
|
+
``_subgraph_by_deleting()`` | Return the matching covered subgraph containing the provided vertices and edges.
|
40
|
+
|
41
|
+
**Overwritten Methods**
|
42
|
+
|
43
|
+
.. csv-table::
|
44
|
+
:class: contentstable
|
45
|
+
:widths: 30, 70
|
46
|
+
:delim: |
|
47
|
+
|
48
|
+
``add_clique()`` | Add a clique to the graph with the provided vertices.
|
49
|
+
``add_cycle()`` | Add a cycle to the graph with the provided vertices.
|
50
|
+
``add_path()`` | Add a path to the graph with the provided vertices.
|
51
|
+
``cartesian_product()`` | Return the Cartesian product of ``self`` and ``other``.
|
52
|
+
``clear()`` | Empties the graph of vertices and edges and removes name, associated objects, and position information.
|
53
|
+
``complement()`` | Return the complement of the graph.
|
54
|
+
``contract_edge()`` | Contract an edge from ``u`` to ``v``.
|
55
|
+
``contract_edges()`` | Contract edges from an iterable container.
|
56
|
+
``degree_constrained_subgraph()`` | Return a degree-constrained matching covered subgraph.
|
57
|
+
``delete_edge()`` | Delete the edge from ``u`` to ``v``.
|
58
|
+
``delete_edges()`` | Delete edges from an iterable container.
|
59
|
+
``delete_multiedge()`` | Delete all edges from ``u`` to ``v``.
|
60
|
+
``disjoint_union()`` | Return the disjoint union of ``self`` and ``other``.
|
61
|
+
``disjunctive_product()`` | Return the disjunctive product of ``self`` and ``other``.
|
62
|
+
``is_biconnected()`` | Check if the matching covered graph is biconnected.
|
63
|
+
``is_block_graph()`` | Check whether the matching covered graph is a block graph.
|
64
|
+
``is_cograph()`` | Check whether the matching covered graph is cograph.
|
65
|
+
``is_forest()`` | Check if the matching covered graph is a forest, i.e. a disjoint union of trees.
|
66
|
+
``is_matching_covered()`` | Check if the graph is matching covered.
|
67
|
+
``is_path()`` | Check whether the graph is a path.
|
68
|
+
``is_subgraph()`` | Check whether the matching covered graph is a subgraph of ``other``.
|
69
|
+
``is_tree()`` | Check whether the matching covered graph is a tree.
|
70
|
+
``join()`` | Return the join of ``self`` and ``other``.
|
71
|
+
``lexicographic_product()`` | Return the lexicographic product of ``self`` and ``other``.
|
72
|
+
``load_afile()`` | Load the matching covered graph specified in the given file into the current object.
|
73
|
+
``merge_vertices()`` | Merge vertices.
|
74
|
+
``minor()`` | Return the vertices of a minor isomorphic to `H` in the current graph.
|
75
|
+
``random_subgraph()`` | Return a random matching covered subgraph containing each vertex with probability ``p``.
|
76
|
+
``save_afile()`` | Save the graph to file in alist format.
|
77
|
+
``strong_product()`` | Return the strong product of ``self`` and ``other``.
|
78
|
+
``subdivide_edge()`` | Subdivide an edge `k` times.
|
79
|
+
``subdivide_edges()`` | Subdivide `k` times edges from an iterable container.
|
80
|
+
``subgraph()`` | Return the matching covered subgraph containing the given vertices and edges.
|
81
|
+
``subgraph_search()`` | Return a copy of (matching covered) ``G`` in ``self``.
|
82
|
+
``subgraph_search_count()`` | Return the number of labelled occurrences of (matching covered) ``G`` in ``self``.
|
83
|
+
``subgraph_search_iterator()`` | Return an iterator over the labelled copies of (matching covered) ``G`` in ``self``.
|
84
|
+
``tensor_product()`` | Return the tensor product of ``self`` and ``other``.
|
85
|
+
``to_undirected()`` | Return an undirected Graph instance of the matching covered graph.
|
86
|
+
``topological_minor()`` | Return a topological `H`-minor from ``self`` if one exists.
|
87
|
+
``transitive_closure()`` | Return the transitive closure of the matching covered graph.
|
88
|
+
``transitive_reduction()`` | Return a transitive reduction of the matching covered graph.
|
89
|
+
``union()`` | Return the union of ``self`` and ``other``.
|
90
|
+
|
91
|
+
**Bricks, braces and tight cut decomposition**
|
92
|
+
|
93
|
+
.. csv-table::
|
94
|
+
:class: contentstable
|
95
|
+
:widths: 30, 70
|
96
|
+
:delim: |
|
97
|
+
|
98
|
+
``bricks_and_braces()`` | Return the list of (underlying simple graph of) the bricks and braces of the (matching covered) graph.
|
99
|
+
``number_of_braces()`` | Return the number of braces.
|
100
|
+
``number_of_bricks()`` | Return the number of bricks.
|
101
|
+
``number_of_petersen_bricks()`` | Return the number of Petersen bricks.
|
102
|
+
``tight_cut_decomposition()`` | Return a maximal set of laminar nontrivial tight cuts and a corresponding vertex set partition.
|
103
|
+
|
104
|
+
**Removability and ear decomposition**
|
105
|
+
|
106
|
+
.. csv-table::
|
107
|
+
:class: contentstable
|
108
|
+
:widths: 30, 70
|
109
|
+
:delim: |
|
110
|
+
|
111
|
+
``add_ear()`` | Add an ear to the graph with the provided end vertices number of internal vertices.
|
112
|
+
``bisubdivide_edge()`` | Bisubdivide an edge `k` times.
|
113
|
+
``bisubdivide_edges()`` | Bisubdivide `k` times edges from an iterable container.
|
114
|
+
``efficient_ear_decomposition()`` | Return a matching covered ear decomposition computed at the fastest possible time.
|
115
|
+
``is_removable_double_ear()`` | Check whether the pair of ears form a removable double ear.
|
116
|
+
``is_removable_doubleton()`` | Check whether the pair of edges constitute a removable doubleton.
|
117
|
+
``is_removable_ear()`` | Check whether the ear is removable.
|
118
|
+
``is_removable_edge()`` | Check whether the edge is removable.
|
119
|
+
``optimal_ear_decomposition()`` | Return an optimal ear decomposition.
|
120
|
+
``removable_double_ears()`` | Return a list of removable double ears.
|
121
|
+
``removable_doubletons()`` | Return a list of removable doubletons.
|
122
|
+
``removable_ears()`` | Return a list of removable ears.
|
123
|
+
``removable_edges()`` | Return a :class:`~EdgesView` of removable edges.
|
124
|
+
``retract()`` | Compute the retract of the (matching covered) graph.
|
125
|
+
|
126
|
+
**Generating bricks and braces**
|
127
|
+
|
128
|
+
.. csv-table::
|
129
|
+
:class: contentstable
|
130
|
+
:widths: 30, 70
|
131
|
+
:delim: |
|
132
|
+
|
133
|
+
``brace_generation_sequence()`` | Return a McCuaig brace generation sequence of the (provided) brace.
|
134
|
+
``brick_generation_sequence()`` | Return a Norine-Thomas brick generation sequence of the (provided) brick.
|
135
|
+
``is_mccuaig_brace()`` | Check if the brace is a McCuaig brace.
|
136
|
+
``is_norine_thomas_brick()`` | Check if the brick is a Norine-Thomas brick.
|
137
|
+
|
138
|
+
|
139
|
+
Methods
|
140
|
+
-------
|
141
|
+
"""
|
142
|
+
|
143
|
+
# ****************************************************************************
|
144
|
+
# Copyright (C) 2024 Janmenjaya Panda <janmenjaya.panda.22@gmail.com>
|
145
|
+
#
|
146
|
+
# This program is free software: you can redistribute it and/or modify
|
147
|
+
# it under the terms of the GNU General Public License as published by
|
148
|
+
# the Free Software Foundation, either version 2 of the License, or
|
149
|
+
# (at your option) any later version.
|
150
|
+
# https://www.gnu.org/licenses/
|
151
|
+
# ****************************************************************************
|
152
|
+
from sage.graphs.graph import Graph
|
153
|
+
from sage.misc.rest_index_of_methods import doc_index, gen_thematic_rest_table_index
|
154
|
+
|
155
|
+
|
156
|
+
class MatchingCoveredGraph(Graph):
|
157
|
+
r"""
|
158
|
+
Matching covered graph
|
159
|
+
|
160
|
+
INPUT:
|
161
|
+
|
162
|
+
- ``data`` -- can be any of the following:
|
163
|
+
|
164
|
+
- Empty or ``None`` (throws a :exc:`ValueError` as the graph must be
|
165
|
+
nontrival).
|
166
|
+
|
167
|
+
- An arbitrary graph.
|
168
|
+
|
169
|
+
- ``matching`` -- (default: ``None``); a perfect matching of the
|
170
|
+
graph, that can be given using any valid input format of
|
171
|
+
:class:`~sage.graphs.graph.Graph`.
|
172
|
+
|
173
|
+
If set to ``None``, a matching is computed using the other parameters.
|
174
|
+
|
175
|
+
- ``algorithm`` -- string (default: ``'Edmonds'``); the algorithm to be
|
176
|
+
used to compute a maximum matching of the graph among
|
177
|
+
|
178
|
+
- ``'Edmonds'`` selects Edmonds' algorithm as implemented in NetworkX,
|
179
|
+
|
180
|
+
- ``'LP'`` uses a Linear Program formulation of the matching problem.
|
181
|
+
|
182
|
+
- ``solver`` -- string (default: ``None``); specify a Mixed Integer
|
183
|
+
Linear Programming (MILP) solver to be used. If set to ``None``, the
|
184
|
+
default one is used. For more information on MILP solvers and which
|
185
|
+
default solver is used, see the method :meth:`solve
|
186
|
+
<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
|
187
|
+
:class:`MixedIntegerLinearProgram
|
188
|
+
<sage.numerical.mip.MixedIntegerLinearProgram>`.
|
189
|
+
|
190
|
+
- ``verbose`` -- integer (default: ``0``); sets the level of verbosity:
|
191
|
+
set to 0 by default, which means quiet (only useful when ``algorithm
|
192
|
+
== 'LP'``).
|
193
|
+
|
194
|
+
- ``integrality_tolerance`` -- float; parameter for use with MILP
|
195
|
+
solvers over an inexact base ring; see
|
196
|
+
:meth:`MixedIntegerLinearProgram.get_values`.
|
197
|
+
|
198
|
+
OUTPUT:
|
199
|
+
|
200
|
+
- An object of the class :class:`~MatchingCoveredGraph` if the input is
|
201
|
+
valid and the graph is matching covered, or otherwise an error is thrown.
|
202
|
+
|
203
|
+
.. NOTE::
|
204
|
+
|
205
|
+
All remaining arguments are passed to the ``Graph`` constructor
|
206
|
+
|
207
|
+
EXAMPLES:
|
208
|
+
|
209
|
+
Generating an object of the class ``MatchingCoveredGraph`` from the
|
210
|
+
provided instance of ``Graph`` without providing any other information::
|
211
|
+
|
212
|
+
sage: G = MatchingCoveredGraph(graphs.PetersenGraph())
|
213
|
+
sage: G
|
214
|
+
Matching covered petersen graph: graph on 10 vertices
|
215
|
+
sage: sorted(G.get_matching())
|
216
|
+
[(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)]
|
217
|
+
|
218
|
+
sage: G = graphs.StaircaseGraph(4)
|
219
|
+
sage: H = MatchingCoveredGraph(G)
|
220
|
+
sage: H
|
221
|
+
Matching covered staircase graph: graph on 8 vertices
|
222
|
+
sage: H == G
|
223
|
+
True
|
224
|
+
sage: sorted(H.get_matching())
|
225
|
+
[(0, 1, None), (2, 7, None), (3, 6, None), (4, 5, None)]
|
226
|
+
|
227
|
+
sage: G = Graph({0: [1, 2, 3, 4], 1: [2, 5],
|
228
|
+
....: 2: [5], 3: [4, 5], 4: [5]})
|
229
|
+
sage: H = MatchingCoveredGraph(G)
|
230
|
+
sage: H
|
231
|
+
Matching covered graph on 6 vertices
|
232
|
+
sage: H == G
|
233
|
+
True
|
234
|
+
sage: sorted(H.get_matching())
|
235
|
+
[(0, 4, None), (1, 2, None), (3, 5, None)]
|
236
|
+
|
237
|
+
sage: # needs networkx
|
238
|
+
sage: import networkx
|
239
|
+
sage: G = Graph(networkx.complete_bipartite_graph(12, 12))
|
240
|
+
sage: H = MatchingCoveredGraph(G)
|
241
|
+
sage: H
|
242
|
+
Matching covered graph on 24 vertices
|
243
|
+
sage: H == G
|
244
|
+
True
|
245
|
+
sage: sorted(H.get_matching())
|
246
|
+
[(0, 15, None), (1, 14, None), (2, 13, None), (3, 12, None),
|
247
|
+
(4, 23, None), (5, 22, None), (6, 21, None), (7, 20, None),
|
248
|
+
(8, 19, None), (9, 18, None), (10, 17, None), (11, 16, None)]
|
249
|
+
|
250
|
+
sage: G = Graph('E|fG', sparse=True)
|
251
|
+
sage: H = MatchingCoveredGraph(G)
|
252
|
+
sage: H
|
253
|
+
Matching covered graph on 6 vertices
|
254
|
+
sage: H == G
|
255
|
+
True
|
256
|
+
sage: sorted(H.get_matching())
|
257
|
+
[(0, 5, None), (1, 2, None), (3, 4, None)]
|
258
|
+
|
259
|
+
sage: # needs sage.modules
|
260
|
+
sage: M = Matrix([(0,1,0,0,1,1,0,0,0,0),
|
261
|
+
....: (1,0,1,0,0,0,1,0,0,0),
|
262
|
+
....: (0,1,0,1,0,0,0,1,0,0),
|
263
|
+
....: (0,0,1,0,1,0,0,0,1,0),
|
264
|
+
....: (1,0,0,1,0,0,0,0,0,1),
|
265
|
+
....: (1,0,0,0,0,0,0,1,1,0),
|
266
|
+
....: (0,1,0,0,0,0,0,0,1,1),
|
267
|
+
....: (0,0,1,0,0,1,0,0,0,1),
|
268
|
+
....: (0,0,0,1,0,1,1,0,0,0),
|
269
|
+
....: (0,0,0,0,1,0,1,1,0,0)])
|
270
|
+
sage: M
|
271
|
+
[0 1 0 0 1 1 0 0 0 0]
|
272
|
+
[1 0 1 0 0 0 1 0 0 0]
|
273
|
+
[0 1 0 1 0 0 0 1 0 0]
|
274
|
+
[0 0 1 0 1 0 0 0 1 0]
|
275
|
+
[1 0 0 1 0 0 0 0 0 1]
|
276
|
+
[1 0 0 0 0 0 0 1 1 0]
|
277
|
+
[0 1 0 0 0 0 0 0 1 1]
|
278
|
+
[0 0 1 0 0 1 0 0 0 1]
|
279
|
+
[0 0 0 1 0 1 1 0 0 0]
|
280
|
+
[0 0 0 0 1 0 1 1 0 0]
|
281
|
+
sage: G = Graph(M)
|
282
|
+
sage: H = MatchingCoveredGraph(G)
|
283
|
+
sage: H == G
|
284
|
+
True
|
285
|
+
sage: sorted(H.get_matching())
|
286
|
+
[(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)]
|
287
|
+
|
288
|
+
sage: # needs sage.modules
|
289
|
+
sage: M = Matrix([(-1, 0, 0, 0, 1, 0, 0, 0, 0, 0,-1, 0, 0, 0, 0),
|
290
|
+
....: ( 1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0, 0),
|
291
|
+
....: ( 0, 1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0, 0),
|
292
|
+
....: ( 0, 0, 1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1, 0),
|
293
|
+
....: ( 0, 0, 0, 1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0,-1),
|
294
|
+
....: ( 0, 0, 0, 0, 0,-1, 0, 0, 0, 1, 1, 0, 0, 0, 0),
|
295
|
+
....: ( 0, 0, 0, 0, 0, 0, 0, 1,-1, 0, 0, 1, 0, 0, 0),
|
296
|
+
....: ( 0, 0, 0, 0, 0, 1,-1, 0, 0, 0, 0, 0, 1, 0, 0),
|
297
|
+
....: ( 0, 0, 0, 0, 0, 0, 0, 0, 1,-1, 0, 0, 0, 1, 0),
|
298
|
+
....: ( 0, 0, 0, 0, 0, 0, 1,-1, 0, 0, 0, 0, 0, 0, 1)])
|
299
|
+
sage: M
|
300
|
+
[-1 0 0 0 1 0 0 0 0 0 -1 0 0 0 0]
|
301
|
+
[ 1 -1 0 0 0 0 0 0 0 0 0 -1 0 0 0]
|
302
|
+
[ 0 1 -1 0 0 0 0 0 0 0 0 0 -1 0 0]
|
303
|
+
[ 0 0 1 -1 0 0 0 0 0 0 0 0 0 -1 0]
|
304
|
+
[ 0 0 0 1 -1 0 0 0 0 0 0 0 0 0 -1]
|
305
|
+
[ 0 0 0 0 0 -1 0 0 0 1 1 0 0 0 0]
|
306
|
+
[ 0 0 0 0 0 0 0 1 -1 0 0 1 0 0 0]
|
307
|
+
[ 0 0 0 0 0 1 -1 0 0 0 0 0 1 0 0]
|
308
|
+
[ 0 0 0 0 0 0 0 0 1 -1 0 0 0 1 0]
|
309
|
+
[ 0 0 0 0 0 0 1 -1 0 0 0 0 0 0 1]
|
310
|
+
sage: G = Graph(M)
|
311
|
+
sage: H = MatchingCoveredGraph(G)
|
312
|
+
sage: H == G
|
313
|
+
True
|
314
|
+
sage: sorted(H.get_matching())
|
315
|
+
[(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)]
|
316
|
+
|
317
|
+
sage: G = Graph([(0, 1), (0, 3), (0, 4), (1, 2), (1, 5), (2, 3),
|
318
|
+
....: (2, 6), (3, 7), (4, 5), (4, 7), (5, 6), (6, 7)])
|
319
|
+
sage: H = MatchingCoveredGraph(G)
|
320
|
+
sage: H == G
|
321
|
+
True
|
322
|
+
sage: sorted(H.get_matching())
|
323
|
+
[(0, 4, None), (1, 5, None), (2, 6, None), (3, 7, None)]
|
324
|
+
|
325
|
+
sage: # optional - python_igraph
|
326
|
+
sage: import igraph
|
327
|
+
sage: G = Graph(igraph.Graph([(0, 1), (0, 3), (1, 2), (2, 3)]))
|
328
|
+
sage: H = MatchingCoveredGraph(G)
|
329
|
+
sage: H
|
330
|
+
Matching covered graph on 4 vertices
|
331
|
+
sage: sorted(H.get_matching())
|
332
|
+
[(0, 3, {}), (1, 2, {})]
|
333
|
+
|
334
|
+
One may specify a perfect matching::
|
335
|
+
|
336
|
+
sage: P = graphs.PetersenGraph()
|
337
|
+
sage: M = P.matching()
|
338
|
+
sage: sorted(M)
|
339
|
+
[(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)]
|
340
|
+
sage: G = MatchingCoveredGraph(P, matching=M)
|
341
|
+
sage: G
|
342
|
+
Matching covered petersen graph: graph on 10 vertices
|
343
|
+
sage: P == G
|
344
|
+
True
|
345
|
+
sage: sorted(G.get_matching())
|
346
|
+
[(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)]
|
347
|
+
sage: sorted(G.get_matching()) == sorted(M)
|
348
|
+
True
|
349
|
+
|
350
|
+
sage: G = graphs.TruncatedBiwheelGraph(14)
|
351
|
+
sage: M = G.matching()
|
352
|
+
sage: sorted(M)
|
353
|
+
[(0, 27, None), (1, 26, None), (2, 3, None), (4, 5, None),
|
354
|
+
(6, 7, None), (8, 9, None), (10, 11, None), (12, 13, None),
|
355
|
+
(14, 15, None), (16, 17, None), (18, 19, None), (20, 21, None),
|
356
|
+
(22, 23, None), (24, 25, None)]
|
357
|
+
sage: H = MatchingCoveredGraph(G, M)
|
358
|
+
sage: H
|
359
|
+
Matching covered truncated biwheel graph: graph on 28 vertices
|
360
|
+
sage: H == G
|
361
|
+
True
|
362
|
+
sage: sorted(H.get_matching()) == sorted(M)
|
363
|
+
True
|
364
|
+
|
365
|
+
One may specify some keyword arguments::
|
366
|
+
|
367
|
+
sage: G = Graph([(0, 1, 5)], {'weighted': True})
|
368
|
+
sage: kwds = {
|
369
|
+
....: 'loops': False,
|
370
|
+
....: 'multiedges': True,
|
371
|
+
....: 'pos': {0: (0, 0), 1: (1, 1)}
|
372
|
+
....: }
|
373
|
+
sage: H = MatchingCoveredGraph(G, **kwds)
|
374
|
+
sage: H
|
375
|
+
Matching covered multi-graph on 2 vertices
|
376
|
+
sage: H.add_edge(0, 1)
|
377
|
+
sage: H.edges()
|
378
|
+
[(0, 1, None), (0, 1, 5)]
|
379
|
+
|
380
|
+
TESTS:
|
381
|
+
|
382
|
+
An empty graph is not matching covered::
|
383
|
+
|
384
|
+
sage: G = Graph()
|
385
|
+
sage: H = MatchingCoveredGraph(G)
|
386
|
+
Traceback (most recent call last):
|
387
|
+
...
|
388
|
+
ValueError: the graph is trivial
|
389
|
+
sage: G = MatchingCoveredGraph()
|
390
|
+
Traceback (most recent call last):
|
391
|
+
...
|
392
|
+
ValueError: the graph is trivial
|
393
|
+
|
394
|
+
Providing with a graph that is not connected::
|
395
|
+
|
396
|
+
sage: G = graphs.CycleGraph(4)
|
397
|
+
sage: G += graphs.CycleGraph(6)
|
398
|
+
sage: G.connected_components_number()
|
399
|
+
2
|
400
|
+
sage: H = MatchingCoveredGraph(G)
|
401
|
+
Traceback (most recent call last):
|
402
|
+
...
|
403
|
+
ValueError: the graph is not connected
|
404
|
+
|
405
|
+
Make sure that self-loops are not allowed for a matching covered graph::
|
406
|
+
|
407
|
+
sage: P = graphs.PetersenGraph()
|
408
|
+
sage: kwds = {'loops': True}
|
409
|
+
sage: G = MatchingCoveredGraph(P, **kwds)
|
410
|
+
Traceback (most recent call last):
|
411
|
+
...
|
412
|
+
ValueError: loops are not allowed in matching covered graphs
|
413
|
+
sage: G = MatchingCoveredGraph(P)
|
414
|
+
sage: G.allows_loops()
|
415
|
+
False
|
416
|
+
sage: G.allow_loops(True)
|
417
|
+
Traceback (most recent call last):
|
418
|
+
...
|
419
|
+
ValueError: loops are not allowed in matching covered graphs
|
420
|
+
sage: G.add_edge(0, 0)
|
421
|
+
Traceback (most recent call last):
|
422
|
+
...
|
423
|
+
ValueError: loops are not allowed in matching covered graphs
|
424
|
+
sage: H = MatchingCoveredGraph(P, loops=True)
|
425
|
+
Traceback (most recent call last):
|
426
|
+
...
|
427
|
+
ValueError: loops are not allowed in matching covered graphs
|
428
|
+
|
429
|
+
Make sure that multiple edges are allowed for a matching covered graph (by
|
430
|
+
default it is off and can be modified to be allowed)::
|
431
|
+
|
432
|
+
sage: P = graphs.PetersenGraph()
|
433
|
+
sage: G = MatchingCoveredGraph(P)
|
434
|
+
sage: G
|
435
|
+
Matching covered petersen graph: graph on 10 vertices
|
436
|
+
sage: G.allows_multiple_edges()
|
437
|
+
False
|
438
|
+
sage: G.size()
|
439
|
+
15
|
440
|
+
sage: G.allow_multiple_edges(True)
|
441
|
+
sage: G.allows_multiple_edges()
|
442
|
+
True
|
443
|
+
sage: G.add_edge(next(P.edge_iterator()))
|
444
|
+
sage: G.size()
|
445
|
+
16
|
446
|
+
sage: G
|
447
|
+
Matching covered petersen graph: multi-graph on 10 vertices
|
448
|
+
sage: H = MatchingCoveredGraph(P, multiedges=True)
|
449
|
+
sage: H.allows_multiple_edges()
|
450
|
+
True
|
451
|
+
sage: H.add_edge(next(P.edge_iterator()))
|
452
|
+
sage: H.size()
|
453
|
+
16
|
454
|
+
sage: H
|
455
|
+
Matching covered petersen graph: multi-graph on 10 vertices
|
456
|
+
|
457
|
+
Providing with a connected nontrivial graph free of self-loops that is
|
458
|
+
not matching covered::
|
459
|
+
|
460
|
+
sage: G = graphs.CompleteGraph(11)
|
461
|
+
sage: H = MatchingCoveredGraph(G)
|
462
|
+
Traceback (most recent call last):
|
463
|
+
...
|
464
|
+
ValueError: input graph is not matching covered
|
465
|
+
sage: G = Graph({0: [1, 6, 11], 1: [2, 4], 2: [3, 5], 3: [4, 5],
|
466
|
+
....: 4: [5], 6: [7, 9], 7: [8, 10], 8: [9, 10], 9: [10],
|
467
|
+
....: 11: [12, 14], 12: [13, 15], 13: [14, 15], 14: [15]})
|
468
|
+
sage: H = MatchingCoveredGraph(G)
|
469
|
+
Traceback (most recent call last):
|
470
|
+
...
|
471
|
+
ValueError: input graph is not matching covered
|
472
|
+
|
473
|
+
sage: # needs networkx
|
474
|
+
sage: import networkx
|
475
|
+
sage: G = Graph(networkx.complete_bipartite_graph(2, 12))
|
476
|
+
sage: H = MatchingCoveredGraph(G)
|
477
|
+
Traceback (most recent call last):
|
478
|
+
...
|
479
|
+
ValueError: input graph is not matching covered
|
480
|
+
sage: G = Graph('F~~~w')
|
481
|
+
sage: H = MatchingCoveredGraph(G)
|
482
|
+
Traceback (most recent call last):
|
483
|
+
...
|
484
|
+
ValueError: input graph is not matching covered
|
485
|
+
|
486
|
+
sage: # needs sage.modules
|
487
|
+
sage: M = Matrix([(0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0),
|
488
|
+
....: (1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
489
|
+
....: (0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
490
|
+
....: (0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
491
|
+
....: (0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
492
|
+
....: (0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
493
|
+
....: (1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0),
|
494
|
+
....: (0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0),
|
495
|
+
....: (0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0),
|
496
|
+
....: (0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0),
|
497
|
+
....: (0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0),
|
498
|
+
....: (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0),
|
499
|
+
....: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1),
|
500
|
+
....: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1),
|
501
|
+
....: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1),
|
502
|
+
....: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0)])
|
503
|
+
sage: M
|
504
|
+
[0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0]
|
505
|
+
[1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0]
|
506
|
+
[0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0]
|
507
|
+
[0 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0]
|
508
|
+
[0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0]
|
509
|
+
[0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0]
|
510
|
+
[1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0]
|
511
|
+
[0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0]
|
512
|
+
[0 0 0 0 0 0 0 1 0 1 1 0 0 0 0 0]
|
513
|
+
[0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0]
|
514
|
+
[0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0]
|
515
|
+
[1 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0]
|
516
|
+
[0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1]
|
517
|
+
[0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1]
|
518
|
+
[0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1]
|
519
|
+
[0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0]
|
520
|
+
sage: G = Graph(M)
|
521
|
+
sage: H = MatchingCoveredGraph(G)
|
522
|
+
Traceback (most recent call last):
|
523
|
+
...
|
524
|
+
ValueError: input graph is not matching covered
|
525
|
+
|
526
|
+
sage: # needs sage.modules
|
527
|
+
sage: M = Matrix([(1, 1, 0, 0, 0, 0),
|
528
|
+
....: (0, 0, 1, 1, 0, 0),
|
529
|
+
....: (0, 0, 1, 0, 1, 0),
|
530
|
+
....: (1, 0, 0, 0, 0, 1),
|
531
|
+
....: (0, 1, 0, 1, 1, 1)])
|
532
|
+
sage: G = Graph(M)
|
533
|
+
sage: H = MatchingCoveredGraph(G)
|
534
|
+
Traceback (most recent call last):
|
535
|
+
...
|
536
|
+
ValueError: input graph is not matching covered
|
537
|
+
sage: G = Graph([(11, 12), (11, 14), (0, 1), (0, 11), (0, 6), (1, 2),
|
538
|
+
....: (1, 4), (2, 3), (2, 5), (3, 4), (3, 5), (4, 5),
|
539
|
+
....: (6, 7), (6, 9), (7, 8), (7, 10), (8, 9), (8, 10),
|
540
|
+
....: (9, 10), (12, 13), (12, 15), (13, 14), (13, 15),
|
541
|
+
....: (14, 15)])
|
542
|
+
sage: H = MatchingCoveredGraph(G)
|
543
|
+
Traceback (most recent call last):
|
544
|
+
...
|
545
|
+
ValueError: input graph is not matching covered
|
546
|
+
|
547
|
+
sage: # optional - python_igraph
|
548
|
+
sage: import igraph
|
549
|
+
sage: G = Graph(igraph.Graph([(0, 1), (0, 2), (0, 3), (1, 2), (2, 3)]))
|
550
|
+
sage: H = MatchingCoveredGraph(G)
|
551
|
+
Traceback (most recent call last):
|
552
|
+
...
|
553
|
+
ValueError: input graph is not matching covered
|
554
|
+
|
555
|
+
Providing with a wrong matching::
|
556
|
+
|
557
|
+
sage: P = graphs.PetersenGraph()
|
558
|
+
sage: M = str('0')
|
559
|
+
sage: H = MatchingCoveredGraph(P, matching=M)
|
560
|
+
Traceback (most recent call last):
|
561
|
+
...
|
562
|
+
RuntimeError: the string seems corrupt: valid characters are
|
563
|
+
?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
|
564
|
+
sage: N = str('graph')
|
565
|
+
sage: J = MatchingCoveredGraph(P, matching=N)
|
566
|
+
Traceback (most recent call last):
|
567
|
+
...
|
568
|
+
RuntimeError: the string (graph) seems corrupt: for n = 40,
|
569
|
+
the string is too short
|
570
|
+
|
571
|
+
sage: G = graphs.CompleteGraph(6)
|
572
|
+
sage: M = Graph(G.matching())
|
573
|
+
sage: M.add_edges([(0, 1), (0, 2)])
|
574
|
+
sage: H = MatchingCoveredGraph(G, matching=M)
|
575
|
+
Traceback (most recent call last):
|
576
|
+
...
|
577
|
+
ValueError: the input is not a matching
|
578
|
+
sage: N = Graph(G.matching())
|
579
|
+
sage: N.add_edge(6, 7)
|
580
|
+
sage: H = MatchingCoveredGraph(G, matching=N)
|
581
|
+
Traceback (most recent call last):
|
582
|
+
...
|
583
|
+
ValueError: the input is not a matching of the graph
|
584
|
+
sage: J = Graph()
|
585
|
+
sage: J.add_edges([(0, 1), (2, 3)])
|
586
|
+
sage: H = MatchingCoveredGraph(G, matching=J)
|
587
|
+
Traceback (most recent call last):
|
588
|
+
...
|
589
|
+
ValueError: the input is not a perfect matching of the graph
|
590
|
+
|
591
|
+
Note that data shall be one of empty or ``None`` or an instance of
|
592
|
+
``Graph`` or an instance of ``MatchingCoveredGraph``. Otherwise a
|
593
|
+
:exc:`ValueError` is returned::
|
594
|
+
|
595
|
+
sage: D = digraphs.Complete(10)
|
596
|
+
sage: D
|
597
|
+
Complete digraph: Digraph on 10 vertices
|
598
|
+
sage: G = MatchingCoveredGraph(D)
|
599
|
+
Traceback (most recent call last):
|
600
|
+
...
|
601
|
+
TypeError: input data is of unknown type
|
602
|
+
"""
|
603
|
+
|
604
|
+
def __init__(self, data=None, matching=None, algorithm='Edmonds',
|
605
|
+
solver=None, verbose=0, integrality_tolerance=0.001,
|
606
|
+
*args, **kwds):
|
607
|
+
r"""
|
608
|
+
Create a matching covered graph, that is a connected nontrivial graph
|
609
|
+
wherein each edge participates in some perfect matching.
|
610
|
+
|
611
|
+
See documentation ``MatchingCoveredGraph?`` for detailed information.
|
612
|
+
"""
|
613
|
+
success = False
|
614
|
+
|
615
|
+
if not kwds:
|
616
|
+
kwds = {'loops': False}
|
617
|
+
else:
|
618
|
+
if 'loops' in kwds and kwds['loops']:
|
619
|
+
raise ValueError('loops are not allowed in '
|
620
|
+
'matching covered graphs')
|
621
|
+
kwds['loops'] = False
|
622
|
+
|
623
|
+
if data is None:
|
624
|
+
raise ValueError('the graph is trivial')
|
625
|
+
|
626
|
+
elif isinstance(data, MatchingCoveredGraph):
|
627
|
+
Graph.__init__(self, data, *args, **kwds)
|
628
|
+
success = True
|
629
|
+
|
630
|
+
elif isinstance(data, Graph):
|
631
|
+
try:
|
632
|
+
self._upgrade_from_graph(data=data, matching=matching,
|
633
|
+
algorithm=algorithm,
|
634
|
+
solver=solver, verbose=verbose,
|
635
|
+
integrality_tolerance=integrality_tolerance,
|
636
|
+
*args, **kwds)
|
637
|
+
success = True
|
638
|
+
|
639
|
+
except Exception as exception:
|
640
|
+
raise exception
|
641
|
+
|
642
|
+
if success:
|
643
|
+
if matching:
|
644
|
+
# The input matching is a valid perfect matching of the graph
|
645
|
+
self._matching = matching
|
646
|
+
|
647
|
+
else:
|
648
|
+
self._matching = Graph(self).matching()
|
649
|
+
|
650
|
+
else:
|
651
|
+
raise TypeError('input data is of unknown type')
|
652
|
+
|
653
|
+
def __repr__(self):
|
654
|
+
r"""
|
655
|
+
Return a short string representation of the (matching covered) graph.
|
656
|
+
|
657
|
+
EXAMPLES:
|
658
|
+
|
659
|
+
If the string representation of the (matching covered) graph does not
|
660
|
+
contain the term 'matching covered', it's used as the prefix::
|
661
|
+
|
662
|
+
sage: G = graphs.CompleteGraph(10)
|
663
|
+
sage: H = MatchingCoveredGraph(G)
|
664
|
+
sage: H
|
665
|
+
Matching covered complete graph: graph on 10 vertices
|
666
|
+
|
667
|
+
sage: G = graphs.HexahedralGraph()
|
668
|
+
sage: H = MatchingCoveredGraph(BipartiteGraph(G))
|
669
|
+
sage: H # An object of the class MatchingCoveredGraph
|
670
|
+
Matching covered hexahedron: graph on 8 vertices
|
671
|
+
|
672
|
+
In case the string representation of the (matching covered) graph
|
673
|
+
contains the term 'matching covered', the representation remains as it
|
674
|
+
is::
|
675
|
+
|
676
|
+
sage: G = graphs.CompleteGraph(10)
|
677
|
+
sage: H = MatchingCoveredGraph(G)
|
678
|
+
sage: H
|
679
|
+
Matching covered complete graph: graph on 10 vertices
|
680
|
+
sage: J = MatchingCoveredGraph(H)
|
681
|
+
sage: J
|
682
|
+
Matching covered complete graph: graph on 10 vertices
|
683
|
+
sage: G = graphs.HexahedralGraph()
|
684
|
+
sage: H = BipartiteGraph(MatchingCoveredGraph(G))
|
685
|
+
sage: H # An object of the class BipartiteGraph
|
686
|
+
Bipartite hexahedron: graph on 8 vertices
|
687
|
+
sage: J = MatchingCoveredGraph(H)
|
688
|
+
sage: J # An object of the class MatchingCoveredGraph
|
689
|
+
Matching covered hexahedron: graph on 8 vertices
|
690
|
+
"""
|
691
|
+
s = Graph._repr_(self).lower()
|
692
|
+
if "matching covered" in s:
|
693
|
+
return s.capitalize()
|
694
|
+
return "".join(["Matching covered ", s])
|
695
|
+
|
696
|
+
def _subgraph_by_adding(self, vertices=None, edges=None, edge_property=None, immutable=None):
|
697
|
+
r"""
|
698
|
+
Return the matching covered subgraph containing the given vertices and edges.
|
699
|
+
|
700
|
+
The edges also satisfy the edge_property, if it is not None. The
|
701
|
+
subgraph is created by creating a new empty graph and adding the
|
702
|
+
necessary vertices, edges, and other properties.
|
703
|
+
|
704
|
+
.. NOTE::
|
705
|
+
|
706
|
+
This method overwrites the
|
707
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph._subgraph_by_adding`
|
708
|
+
method to ensure that resultant subgraph is also matching covered.
|
709
|
+
|
710
|
+
INPUT:
|
711
|
+
|
712
|
+
- ``vertices`` -- (default: ``None``) an iterable container of
|
713
|
+
vertices, e.g. a list, set, graph, file or numeric array. If not
|
714
|
+
passed (i.e., ``None``), defaults to the entire graph.
|
715
|
+
|
716
|
+
- ``edges`` -- a single edge or an iterable container of edges (e.g., a
|
717
|
+
list, set, file, numeric array, etc.). By default (``edges=None``),
|
718
|
+
all edges are assumed and the returned graph is an induced
|
719
|
+
subgraph. In the case of multiple edges, specifying an edge as `(u,v)`
|
720
|
+
means to keep all edges `(u,v)`, regardless of the label.
|
721
|
+
|
722
|
+
- ``edge_property`` -- function (default: ``None``); a function that
|
723
|
+
inputs an edge and outputs a boolean value, i.e., a edge ``e`` in
|
724
|
+
``edges`` is kept if ``edge_property(e) == True``
|
725
|
+
|
726
|
+
- ``immutable`` -- boolean (default: ``None``); whether to create a
|
727
|
+
mutable/immutable subgraph. ``immutable=None`` (default) means that
|
728
|
+
the graph and its subgraph will behave the same way.
|
729
|
+
|
730
|
+
OUTPUT:
|
731
|
+
|
732
|
+
- An instance of :class:`~MatchingCoveredGraph` is returned if the
|
733
|
+
subgraph obtained is matching covered, otherwise a :exc:`ValueError`
|
734
|
+
is thrown.
|
735
|
+
|
736
|
+
EXAMPLES:
|
737
|
+
|
738
|
+
Ladder graphs are matching covered subgraphs of a staircase graph,
|
739
|
+
which is also matching covered::
|
740
|
+
|
741
|
+
sage: G = MatchingCoveredGraph(graphs.StaircaseGraph(4))
|
742
|
+
sage: H = G._subgraph_by_adding(vertices=[0..5])
|
743
|
+
sage: H.order(), H.size()
|
744
|
+
(6, 7)
|
745
|
+
sage: H
|
746
|
+
Matching covered subgraph of (staircase graph): graph on 6 vertices
|
747
|
+
sage: H.is_isomorphic(graphs.LadderGraph(3))
|
748
|
+
True
|
749
|
+
|
750
|
+
Cycle graphs are matching covered subgraphs of a biwheel graph, which
|
751
|
+
is also matching covered::
|
752
|
+
|
753
|
+
sage: G = MatchingCoveredGraph(graphs.BiwheelGraph(5))
|
754
|
+
sage: H = G._subgraph_by_adding(vertices=[0..7],
|
755
|
+
....: edges=[(u, (u+1) % 8) for u in range(8)])
|
756
|
+
sage: H.order(), H.size()
|
757
|
+
(8, 8)
|
758
|
+
sage: H
|
759
|
+
Matching covered subgraph of (biwheel graph): graph on 8 vertices
|
760
|
+
sage: H.is_isomorphic(graphs.CycleGraph(8))
|
761
|
+
True
|
762
|
+
|
763
|
+
One may pass no value for any of the input arguments; in such a case,
|
764
|
+
the whole matching covered graph will be returned::
|
765
|
+
|
766
|
+
sage: T = graphs.TwinplexGraph()
|
767
|
+
sage: G = MatchingCoveredGraph(T)
|
768
|
+
sage: J = G._subgraph_by_adding()
|
769
|
+
sage: G == J
|
770
|
+
True
|
771
|
+
|
772
|
+
One may use the ``edge_property`` argument::
|
773
|
+
|
774
|
+
sage: G = Graph(multiedges=True)
|
775
|
+
sage: G.add_edges([
|
776
|
+
....: (0, 1, 'label'), (0, 2), (0, 3), (0, 4),
|
777
|
+
....: (0, 5), (1, 2, 'label'), (1, 2), (1, 5),
|
778
|
+
....: (2, 5), (3, 4), (3, 5), (4, 5)
|
779
|
+
....: ])
|
780
|
+
sage: H = MatchingCoveredGraph(G)
|
781
|
+
sage: J = H._subgraph_by_adding(vertices=[0, 1, 2, 5], edge_property=
|
782
|
+
....: (lambda edge:
|
783
|
+
....: (edge[0] in [1, 2]) != (edge[1] in [1, 2]))
|
784
|
+
....: )
|
785
|
+
sage: J.order(), J.size()
|
786
|
+
(4, 4)
|
787
|
+
sage: J
|
788
|
+
Matching covered subgraph of (): multi-graph on 4 vertices
|
789
|
+
sage: J.is_isomorphic(graphs.CompleteBipartiteGraph(2, 2))
|
790
|
+
True
|
791
|
+
|
792
|
+
We may specify the subgraph to be immutable::
|
793
|
+
|
794
|
+
sage: M = graphs.MoebiusLadderGraph(4)
|
795
|
+
sage: G = MatchingCoveredGraph(M)
|
796
|
+
sage: H = G._subgraph_by_adding(edge_property=
|
797
|
+
....: (lambda edge: abs(edge[0] - edge[1]) != 4),
|
798
|
+
....: immutable=True)
|
799
|
+
sage: H.order(), H.size()
|
800
|
+
(8, 8)
|
801
|
+
sage: H
|
802
|
+
Matching covered subgraph of (moebius ladder graph): graph on 8 vertices
|
803
|
+
sage: H.is_isomorphic(graphs.CycleGraph(8))
|
804
|
+
True
|
805
|
+
sage: H.is_immutable()
|
806
|
+
True
|
807
|
+
sage: C = graphs.CubeplexGraph()
|
808
|
+
sage: D = MatchingCoveredGraph(C)
|
809
|
+
sage: I = D._subgraph_by_adding(immutable=True)
|
810
|
+
sage: (I == D) and (I.is_immutable())
|
811
|
+
True
|
812
|
+
sage: J = D._subgraph_by_adding(vertices=D.vertices(), immutable=True)
|
813
|
+
sage: (J == D) and (J.is_immutable())
|
814
|
+
True
|
815
|
+
|
816
|
+
An error is thrown if the subgraph is not matching covered::
|
817
|
+
|
818
|
+
sage: P = graphs.PetersenGraph()
|
819
|
+
sage: G = MatchingCoveredGraph(P)
|
820
|
+
sage: H = G._subgraph_by_adding(vertices=[])
|
821
|
+
Traceback (most recent call last):
|
822
|
+
...
|
823
|
+
ValueError: the graph is trivial
|
824
|
+
sage: H = G._subgraph_by_adding(edge_property=
|
825
|
+
....: (lambda edge: edge[0] == 0)
|
826
|
+
....: )
|
827
|
+
Traceback (most recent call last):
|
828
|
+
...
|
829
|
+
ValueError: the graph is not connected
|
830
|
+
sage: H = G._subgraph_by_adding(vertices=[1, 2, 3])
|
831
|
+
Traceback (most recent call last):
|
832
|
+
...
|
833
|
+
ValueError: input graph is not matching covered
|
834
|
+
"""
|
835
|
+
if immutable is None:
|
836
|
+
immutable = self.is_immutable()
|
837
|
+
|
838
|
+
if edges is None and edge_property is None:
|
839
|
+
if vertices is None:
|
840
|
+
G = self.copy()
|
841
|
+
G.name('Matching covered subgraph of ({})'.format(self.name()))
|
842
|
+
if immutable:
|
843
|
+
G = G.copy(immutable=True)
|
844
|
+
|
845
|
+
return G
|
846
|
+
|
847
|
+
else:
|
848
|
+
# Check if all existent vertices are there
|
849
|
+
all_existent_vertices = True
|
850
|
+
for vertex in self:
|
851
|
+
if vertex not in vertices:
|
852
|
+
all_existent_vertices = False
|
853
|
+
break
|
854
|
+
|
855
|
+
if all_existent_vertices:
|
856
|
+
G = self.copy()
|
857
|
+
G.name('Matching covered subgraph of ({})'.format(self.name()))
|
858
|
+
if immutable:
|
859
|
+
G = G.copy(immutable=True)
|
860
|
+
|
861
|
+
return G
|
862
|
+
|
863
|
+
G = Graph(self, weighted=self._weighted, loops=self.allows_loops(),
|
864
|
+
multiedges=self.allows_multiple_edges())
|
865
|
+
|
866
|
+
H = G._subgraph_by_adding(vertices=vertices, edges=edges,
|
867
|
+
edge_property=edge_property,
|
868
|
+
immutable=False)
|
869
|
+
|
870
|
+
try:
|
871
|
+
H = MatchingCoveredGraph(H)
|
872
|
+
H.name('Matching covered subgraph of ({})'.format(self.name()))
|
873
|
+
if immutable:
|
874
|
+
H = H.copy(immutable=True)
|
875
|
+
|
876
|
+
return H
|
877
|
+
|
878
|
+
except Exception as exception:
|
879
|
+
raise exception
|
880
|
+
|
881
|
+
def _upgrade_from_graph(self, data=None, matching=None, algorithm='Edmonds',
|
882
|
+
solver=None, verbose=0, integrality_tolerance=0.001,
|
883
|
+
*args, **kwds):
|
884
|
+
r"""
|
885
|
+
Upgrade the given graph to a matching covered graph if eligible.
|
886
|
+
|
887
|
+
See documentation ``MatchingCoveredGraph?`` for detailed information.
|
888
|
+
"""
|
889
|
+
try:
|
890
|
+
check = Graph.is_matching_covered(G=data, matching=matching,
|
891
|
+
algorithm=algorithm,
|
892
|
+
coNP_certificate=False,
|
893
|
+
solver=solver, verbose=verbose,
|
894
|
+
integrality_tolerance=integrality_tolerance)
|
895
|
+
|
896
|
+
if check:
|
897
|
+
Graph.__init__(self, data, *args, **kwds)
|
898
|
+
else:
|
899
|
+
raise ValueError("input graph is not matching covered")
|
900
|
+
|
901
|
+
except Exception as exception:
|
902
|
+
raise exception
|
903
|
+
|
904
|
+
@doc_index('Overwritten methods')
|
905
|
+
def add_edge(self, u, v=None, label=None):
|
906
|
+
r"""
|
907
|
+
Add an edge from vertex ``u`` to vertex ``v``.
|
908
|
+
|
909
|
+
.. NOTE::
|
910
|
+
|
911
|
+
This method overwrites the
|
912
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.add_edge` method
|
913
|
+
to ensure that resultant graph is also matching covered.
|
914
|
+
|
915
|
+
INPUT:
|
916
|
+
|
917
|
+
The following forms are all accepted:
|
918
|
+
|
919
|
+
- G.add_edge(1, 2)
|
920
|
+
- G.add_edge((1, 2))
|
921
|
+
- G.add_edges([(1, 2)])
|
922
|
+
- G.add_edge(1, 2, 'label')
|
923
|
+
- G.add_edge((1, 2, 'label'))
|
924
|
+
- G.add_edges([(1, 2, 'label')])
|
925
|
+
|
926
|
+
OUTPUT:
|
927
|
+
|
928
|
+
- If an edge is provided with a valid format, but addition of the edge
|
929
|
+
leaves the resulting graph not being matching covered, a
|
930
|
+
:exc:`ValueError` is returned without any alteration to the existing
|
931
|
+
matching covered graph. If the addition of the edge preserves the
|
932
|
+
property of matching covered, then the graph is updated and nothing
|
933
|
+
is returned.
|
934
|
+
|
935
|
+
- If the edge is provided with an invalid format, a :exc:`ValueError`
|
936
|
+
is returned.
|
937
|
+
|
938
|
+
WARNING:
|
939
|
+
|
940
|
+
The following intuitive input results in nonintuitive output,
|
941
|
+
even though the resulting graph behind the intuition might be matching
|
942
|
+
covered::
|
943
|
+
|
944
|
+
sage: P = graphs.WheelGraph(6)
|
945
|
+
sage: G = MatchingCoveredGraph(P)
|
946
|
+
sage: G.add_edge((1, 4), 'label')
|
947
|
+
Traceback (most recent call last):
|
948
|
+
...
|
949
|
+
ValueError: the graph obtained after the addition of edge
|
950
|
+
(((1, 4), 'label', None)) is not matching covered
|
951
|
+
sage: G.edges(sort=False)
|
952
|
+
[(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None),
|
953
|
+
(0, 5, None), (1, 2, None), (1, 5, None), (2, 3, None),
|
954
|
+
(3, 4, None), (4, 5, None)]
|
955
|
+
|
956
|
+
The key word ``label`` must be used::
|
957
|
+
|
958
|
+
sage: W = graphs.WheelGraph(6)
|
959
|
+
sage: G = MatchingCoveredGraph(W)
|
960
|
+
sage: G.add_edge((1, 4), label='label')
|
961
|
+
sage: G.edges(sort=False) # No alteration to the existing graph
|
962
|
+
[(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None),
|
963
|
+
(0, 5, None), (1, 2, None), (1, 4, 'label'), (1, 5, None),
|
964
|
+
(2, 3, None), (3, 4, None), (4, 5, None)]
|
965
|
+
|
966
|
+
An expression, analogous to the syntax mentioned above may be used::
|
967
|
+
|
968
|
+
sage: S = graphs.StaircaseGraph(4)
|
969
|
+
sage: G = MatchingCoveredGraph(S)
|
970
|
+
sage: G.add_edge(0, 5)
|
971
|
+
sage: G.edges(sort=False)
|
972
|
+
[(0, 1, None), (0, 3, None), (0, 5, None), (0, 6, None),
|
973
|
+
(1, 2, None), (1, 4, None), (2, 5, None), (2, 7, None),
|
974
|
+
(3, 4, None), (3, 6, None), (4, 5, None), (5, 7, None),
|
975
|
+
(6, 7, None)]
|
976
|
+
sage: G.add_edge((2, 3))
|
977
|
+
sage: G.edges(sort=False)
|
978
|
+
[(0, 1, None), (0, 3, None), (0, 5, None), (0, 6, None),
|
979
|
+
(1, 2, None), (1, 4, None), (2, 3, None), (2, 5, None),
|
980
|
+
(2, 7, None), (3, 4, None), (3, 6, None), (4, 5, None),
|
981
|
+
(5, 7, None), (6, 7, None)]
|
982
|
+
sage: G.add_edges([(0, 4)])
|
983
|
+
sage: G.edges(sort=False)
|
984
|
+
[(0, 1, None), (0, 3, None), (0, 4, None), (0, 5, None),
|
985
|
+
(0, 6, None), (1, 2, None), (1, 4, None), (2, 3, None),
|
986
|
+
(2, 5, None), (2, 7, None), (3, 4, None), (3, 6, None),
|
987
|
+
(4, 5, None), (5, 7, None), (6, 7, None)]
|
988
|
+
sage: G.add_edge(2, 4, 'label')
|
989
|
+
sage: G.edges(sort=False)
|
990
|
+
[(0, 1, None), (0, 3, None), (0, 4, None), (0, 5, None),
|
991
|
+
(0, 6, None), (1, 2, None), (1, 4, None), (2, 3, None),
|
992
|
+
(2, 4, 'label'), (2, 5, None), (2, 7, None), (3, 4, None),
|
993
|
+
(3, 6, None), (4, 5, None), (5, 7, None), (6, 7, None)]
|
994
|
+
sage: G.add_edge((4, 6, 'label'))
|
995
|
+
sage: G.edges(sort=False)
|
996
|
+
[(0, 1, None), (0, 3, None), (0, 4, None), (0, 5, None),
|
997
|
+
(0, 6, None), (1, 2, None), (1, 4, None), (2, 3, None),
|
998
|
+
(2, 4, 'label'), (2, 5, None), (2, 7, None), (3, 4, None),
|
999
|
+
(3, 6, None), (4, 5, None), (4, 6, 'label'), (5, 7, None),
|
1000
|
+
(6, 7, None)]
|
1001
|
+
sage: G.add_edges([(4, 7, 'label')])
|
1002
|
+
sage: G.edges(sort=False)
|
1003
|
+
[(0, 1, None), (0, 3, None), (0, 4, None), (0, 5, None),
|
1004
|
+
(0, 6, None), (1, 2, None), (1, 4, None), (2, 3, None),
|
1005
|
+
(2, 4, 'label'), (2, 5, None), (2, 7, None), (3, 4, None),
|
1006
|
+
(3, 6, None), (4, 5, None), (4, 6, 'label'), (4, 7, 'label'),
|
1007
|
+
(5, 7, None), (6, 7, None)]
|
1008
|
+
|
1009
|
+
Note that the ``weight`` of the edge shall be input as the ``label``::
|
1010
|
+
|
1011
|
+
sage: G.add_edge((1, 3), label=5)
|
1012
|
+
sage: G.edges()
|
1013
|
+
[(0, 1, None), (0, 3, None), (0, 4, None), (0, 5, None),
|
1014
|
+
(0, 6, None), (1, 2, None), (1, 3, 5), (1, 4, None),
|
1015
|
+
(2, 3, None), (2, 4, 'label'), (2, 5, None), (2, 7, None),
|
1016
|
+
(3, 4, None), (3, 6, None), (4, 5, None), (4, 6, 'label'),
|
1017
|
+
(4, 7, 'label'), (5, 7, None), (6, 7, None)]
|
1018
|
+
sage: G.add_edge((2, 4, 6), label=6)
|
1019
|
+
Traceback (most recent call last):
|
1020
|
+
...
|
1021
|
+
ValueError: the graph obtained after the addition of edge
|
1022
|
+
(((2, 4, 6), None, 6)) is not matching covered
|
1023
|
+
|
1024
|
+
Vertex name cannot be ``None``, so::
|
1025
|
+
|
1026
|
+
sage: W = graphs.WheelGraph(6)
|
1027
|
+
sage: H = MatchingCoveredGraph(W)
|
1028
|
+
sage: H.add_edge(None, 1)
|
1029
|
+
Traceback (most recent call last):
|
1030
|
+
...
|
1031
|
+
ValueError: the graph obtained after the addition of edge
|
1032
|
+
((None, 1, None)) is not matching covered
|
1033
|
+
sage: H.edges(sort=False) # No alteration to the existing graph
|
1034
|
+
[(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None),
|
1035
|
+
(0, 5, None), (1, 2, None), (1, 5, None), (2, 3, None),
|
1036
|
+
(3, 4, None), (4, 5, None)]
|
1037
|
+
sage: H.add_edge(None, None)
|
1038
|
+
Traceback (most recent call last):
|
1039
|
+
...
|
1040
|
+
ValueError: the graph obtained after the addition of edge
|
1041
|
+
((None, None, None)) is not matching covered
|
1042
|
+
sage: H.edges(sort=False) # No alteration to the existing graph
|
1043
|
+
[(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None),
|
1044
|
+
(0, 5, None), (1, 2, None), (1, 5, None), (2, 3, None),
|
1045
|
+
(3, 4, None), (4, 5, None)]
|
1046
|
+
|
1047
|
+
EXAMPLES:
|
1048
|
+
|
1049
|
+
Adding an already existing edge::
|
1050
|
+
|
1051
|
+
sage: P = graphs.PetersenGraph()
|
1052
|
+
sage: G = MatchingCoveredGraph(P)
|
1053
|
+
sage: G.add_edge(next(G.edge_iterator()))
|
1054
|
+
sage: P == G
|
1055
|
+
True
|
1056
|
+
sage: G.size()
|
1057
|
+
15
|
1058
|
+
sage: G.allow_multiple_edges(True)
|
1059
|
+
sage: G.add_edge(0, 1)
|
1060
|
+
sage: G.size()
|
1061
|
+
16
|
1062
|
+
|
1063
|
+
Adding an edge such that the resulting graph is matching covered::
|
1064
|
+
|
1065
|
+
sage: P = graphs.PetersenGraph()
|
1066
|
+
sage: G = MatchingCoveredGraph(P)
|
1067
|
+
sage: G.add_edge(1, 4)
|
1068
|
+
sage: G.edges(sort=False)
|
1069
|
+
[(0, 1, None), (0, 4, None), (0, 5, None), (1, 2, None),
|
1070
|
+
(1, 4, None), (1, 6, None), (2, 3, None), (2, 7, None),
|
1071
|
+
(3, 4, None), (3, 8, None), (4, 9, None), (5, 7, None),
|
1072
|
+
(5, 8, None), (6, 8, None), (6, 9, None), (7, 9, None)]
|
1073
|
+
|
1074
|
+
Adding an edge with both the incident vertices being existent such
|
1075
|
+
that the resulting graph is not matching covered::
|
1076
|
+
|
1077
|
+
sage: C = graphs.CycleGraph(4)
|
1078
|
+
sage: G = MatchingCoveredGraph(C)
|
1079
|
+
sage: G.add_edge(0, 2)
|
1080
|
+
Traceback (most recent call last):
|
1081
|
+
...
|
1082
|
+
ValueError: the graph obtained after the addition of edge
|
1083
|
+
((0, 2, None)) is not matching covered
|
1084
|
+
sage: G.edges(sort=False) # No alteration to the existing graph
|
1085
|
+
[(0, 1, None), (0, 3, None), (1, 2, None), (2, 3, None)]
|
1086
|
+
|
1087
|
+
Adding an edge with exactly one incident vertex that is nonexistent
|
1088
|
+
throws a :exc:`ValueError` exception, as the resulting graph would
|
1089
|
+
have an odd order::
|
1090
|
+
|
1091
|
+
sage: C = graphs.CycleGraph(4)
|
1092
|
+
sage: G = MatchingCoveredGraph(C)
|
1093
|
+
sage: G.add_edge(0, 4)
|
1094
|
+
Traceback (most recent call last):
|
1095
|
+
...
|
1096
|
+
ValueError: the graph obtained after the addition of edge
|
1097
|
+
((0, 4, None)) is not matching covered
|
1098
|
+
sage: G.edges(sort=False) # No alteration to the existing graph
|
1099
|
+
[(0, 1, None), (0, 3, None), (1, 2, None), (2, 3, None)]
|
1100
|
+
|
1101
|
+
Adding an edge with both the incident vertices that is nonexistent
|
1102
|
+
throws a :exc:`ValueError` exception, as the resulting graph would
|
1103
|
+
have been disconnected::
|
1104
|
+
|
1105
|
+
sage: C = graphs.CycleGraph(4)
|
1106
|
+
sage: G = MatchingCoveredGraph(C)
|
1107
|
+
sage: G.add_edge(4, 5)
|
1108
|
+
Traceback (most recent call last):
|
1109
|
+
...
|
1110
|
+
ValueError: the graph obtained after the addition of edge
|
1111
|
+
((4, 5, None)) is not matching covered
|
1112
|
+
sage: G.edges(sort=False) # No alteration to the existing graph
|
1113
|
+
[(0, 1, None), (0, 3, None), (1, 2, None), (2, 3, None)]
|
1114
|
+
|
1115
|
+
Adding a self-loop::
|
1116
|
+
|
1117
|
+
sage: H = graphs.HeawoodGraph()
|
1118
|
+
sage: G = MatchingCoveredGraph(H)
|
1119
|
+
sage: v = next(G.vertex_iterator())
|
1120
|
+
sage: G.add_edge(v, v)
|
1121
|
+
Traceback (most recent call last):
|
1122
|
+
...
|
1123
|
+
ValueError: loops are not allowed in matching covered graphs
|
1124
|
+
"""
|
1125
|
+
if label is None:
|
1126
|
+
if v is None:
|
1127
|
+
try:
|
1128
|
+
u, v, label = u
|
1129
|
+
except Exception:
|
1130
|
+
try:
|
1131
|
+
u, v = u
|
1132
|
+
except Exception:
|
1133
|
+
pass
|
1134
|
+
|
1135
|
+
else:
|
1136
|
+
if v is None:
|
1137
|
+
try:
|
1138
|
+
u, v = u
|
1139
|
+
except Exception:
|
1140
|
+
pass
|
1141
|
+
|
1142
|
+
if u in self and v in self:
|
1143
|
+
if u == v:
|
1144
|
+
raise ValueError('loops are not allowed in '
|
1145
|
+
'matching covered graphs')
|
1146
|
+
|
1147
|
+
# If (u, v, label) is a multiple edge/ an existing edge
|
1148
|
+
if self.has_edge(u, v):
|
1149
|
+
self._backend.add_edge(u, v, label, self._directed)
|
1150
|
+
return
|
1151
|
+
|
1152
|
+
# Check if there exists an M-alternating odd uv path starting and
|
1153
|
+
# ending with edges in self._matching
|
1154
|
+
from sage.graphs.matching import M_alternating_even_mark
|
1155
|
+
w = next((b if a == u else a) for a, b, *_ in self.get_matching() if u in (a, b))
|
1156
|
+
|
1157
|
+
if v in M_alternating_even_mark(self, w, self.get_matching()):
|
1158
|
+
# There exists a perfect matching containing the edge (u, v, label)
|
1159
|
+
self._backend.add_edge(u, v, label, self._directed)
|
1160
|
+
return
|
1161
|
+
|
1162
|
+
raise ValueError('the graph obtained after the addition of edge '
|
1163
|
+
'(%s) is not matching covered' % str((u, v, label)))
|
1164
|
+
|
1165
|
+
@doc_index('Overwritten methods')
|
1166
|
+
def add_edges(self, edges, loops=False):
|
1167
|
+
r"""
|
1168
|
+
Add edges from an iterable container.
|
1169
|
+
|
1170
|
+
.. NOTE::
|
1171
|
+
|
1172
|
+
This method overwrites the
|
1173
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.add_edges` method
|
1174
|
+
to ensure that resultant graph is also matching covered.
|
1175
|
+
|
1176
|
+
INPUT:
|
1177
|
+
|
1178
|
+
- ``edges`` -- an iterable of edges, given either as ``(u, v)``
|
1179
|
+
or ``(u, v, 'label')``. If an edge is provided in the format
|
1180
|
+
``(u, v)``, the label is set to ``None``.
|
1181
|
+
|
1182
|
+
- ``loops`` -- boolean (default: ``False``); note that this shall
|
1183
|
+
always be set to either ``False`` or ``None`` (since matching covered
|
1184
|
+
graphs are free of loops), in which case all the loops
|
1185
|
+
``(v, v, 'label')`` are removed from the iterator. If ``loops`` is
|
1186
|
+
set to ``True``, a :exc:`ValueError` is thrown.
|
1187
|
+
|
1188
|
+
- Please note that all the loops present in the iterator are ignored,
|
1189
|
+
provided that ``loops`` is set to ``False`` or ``None``.
|
1190
|
+
|
1191
|
+
OUTPUT:
|
1192
|
+
|
1193
|
+
- If ``loops`` is set to ``True``, a :exc:`ValueError` is returned.
|
1194
|
+
|
1195
|
+
- If ``edges`` is provided with a valid format, but addition of the
|
1196
|
+
edges leave the resulting graph not being matching covered, a
|
1197
|
+
:exc:`ValueError` is returned without any alteration to the existing
|
1198
|
+
matching covered graph. If the addition of the edges preserves the
|
1199
|
+
property of matching covered, then the graph is updated and nothing
|
1200
|
+
is returned.
|
1201
|
+
|
1202
|
+
- If ``edges`` is provided with an invalid format, a :exc:`ValueError`
|
1203
|
+
is returned.
|
1204
|
+
|
1205
|
+
EXAMPLES:
|
1206
|
+
|
1207
|
+
Providing with an empty list of edges::
|
1208
|
+
|
1209
|
+
sage: C = graphs.CycleGraph(6)
|
1210
|
+
sage: G = MatchingCoveredGraph(C)
|
1211
|
+
sage: G.add_edges([])
|
1212
|
+
sage: G == C
|
1213
|
+
True
|
1214
|
+
|
1215
|
+
Adding some edges, the incident vertices of each of which are existent,
|
1216
|
+
such that the resulting graph is matching covered::
|
1217
|
+
|
1218
|
+
sage: S = graphs.StaircaseGraph(4)
|
1219
|
+
sage: G = MatchingCoveredGraph(S)
|
1220
|
+
sage: F = [(0, 4), (2, 4), (4, 6), (4, 7)]
|
1221
|
+
sage: G.add_edges(F)
|
1222
|
+
sage: G.edges(sort=True, sort_vertices=True)
|
1223
|
+
[(0, 1, None), (0, 3, None), (0, 4, None), (0, 6, None),
|
1224
|
+
(1, 2, None), (1, 4, None), (2, 4, None), (2, 5, None),
|
1225
|
+
(2, 7, None), (3, 4, None), (3, 6, None), (4, 5, None),
|
1226
|
+
(4, 6, None), (4, 7, None), (5, 7, None), (6, 7, None)]
|
1227
|
+
|
1228
|
+
Adding some edges, at least one of the incident vertices of some of
|
1229
|
+
which are nonexistent such that the resulting graph is matching
|
1230
|
+
covered::
|
1231
|
+
|
1232
|
+
sage: C = graphs.CycleGraph(8)
|
1233
|
+
sage: G = MatchingCoveredGraph(C)
|
1234
|
+
sage: F = [(0, 9), (1, 8), (2, 9), (3, 8),
|
1235
|
+
....: (4, 9), (5, 8), (6, 9), (7, 8)]
|
1236
|
+
sage: G.add_edges(F)
|
1237
|
+
sage: G.edges(sort=True, sort_vertices=True)
|
1238
|
+
[(0, 1, None), (0, 7, None), (0, 9, None), (1, 2, None),
|
1239
|
+
(1, 8, None), (2, 3, None), (2, 9, None), (3, 4, None),
|
1240
|
+
(3, 8, None), (4, 5, None), (4, 9, None), (5, 6, None),
|
1241
|
+
(5, 8, None), (6, 7, None), (6, 9, None), (7, 8, None)]
|
1242
|
+
sage: G.is_isomorphic(graphs.BiwheelGraph(5))
|
1243
|
+
True
|
1244
|
+
|
1245
|
+
Adding a removable double ear to a matching covered graph::
|
1246
|
+
|
1247
|
+
sage: H = graphs.HexahedralGraph()
|
1248
|
+
sage: G = MatchingCoveredGraph(H)
|
1249
|
+
sage: F = {(0, 8, None), (1, 10), (4, 11, 'label'),
|
1250
|
+
....: (5, 9), (8, 9), (10, 11)}
|
1251
|
+
sage: G.add_edges(F)
|
1252
|
+
sage: G.edges(sort=True, sort_vertices=True)
|
1253
|
+
[(0, 1, None), (0, 3, None), (0, 4, None), (0, 8, None),
|
1254
|
+
(1, 2, None), (1, 5, None), (1, 10, None), (2, 3, None),
|
1255
|
+
(2, 6, None), (3, 7, None), (4, 5, None), (4, 7, None),
|
1256
|
+
(4, 11, 'label'), (5, 6, None), (5, 9, None), (6, 7, None),
|
1257
|
+
(8, 9, None), (10, 11, None)]
|
1258
|
+
|
1259
|
+
Adding some edges, the incident vertices of each of which are existent,
|
1260
|
+
such that the resulting graph is NOT matching covered::
|
1261
|
+
|
1262
|
+
sage: C = graphs.CycleGraph(6)
|
1263
|
+
sage: G = MatchingCoveredGraph(C)
|
1264
|
+
sage: F = [(0, 2), (3, 5)]
|
1265
|
+
sage: G.add_edges(F)
|
1266
|
+
Traceback (most recent call last):
|
1267
|
+
...
|
1268
|
+
ValueError: the resulting graph after the addition ofthe edges is not matching covered
|
1269
|
+
|
1270
|
+
Adding some edges, at least one of the incident vertices of some of
|
1271
|
+
which are nonexistent such that the resulting graph is NOT matching
|
1272
|
+
covered::
|
1273
|
+
|
1274
|
+
sage: H = graphs.HexahedralGraph()
|
1275
|
+
sage: G = MatchingCoveredGraph(H)
|
1276
|
+
sage: F = [(3, 8), (6, 9), (8, 9)]
|
1277
|
+
sage: G.add_edges(F)
|
1278
|
+
Traceback (most recent call last):
|
1279
|
+
...
|
1280
|
+
ValueError: the resulting graph after the addition ofthe edges is not matching covered
|
1281
|
+
sage: I = [(0, 8), (1, 9)]
|
1282
|
+
sage: G.add_edges(I)
|
1283
|
+
Traceback (most recent call last):
|
1284
|
+
...
|
1285
|
+
ValueError: the resulting graph after the addition ofthe edges is not matching covered
|
1286
|
+
sage: J = [(u, 8) for u in range(8)]
|
1287
|
+
sage: G.add_edges(J)
|
1288
|
+
Traceback (most recent call last):
|
1289
|
+
...
|
1290
|
+
ValueError: odd order is not allowed for matching covered graphs
|
1291
|
+
|
1292
|
+
Setting the parameter ``loops`` to either ``False`` or ``None``::
|
1293
|
+
|
1294
|
+
sage: W = graphs.WheelGraph(6)
|
1295
|
+
sage: G = MatchingCoveredGraph(W)
|
1296
|
+
sage: F = [(0, 0), (1, 3), (2, 4)]
|
1297
|
+
sage: G.add_edges(edges=F, loops=False)
|
1298
|
+
sage: G.edges(sort=True, sort_vertices=True)
|
1299
|
+
[(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None),
|
1300
|
+
(0, 5, None), (1, 2, None), (1, 3, None), (1, 5, None),
|
1301
|
+
(2, 3, None), (2, 4, None), (3, 4, None), (4, 5, None)]
|
1302
|
+
sage: J = [(1, 1), (3, 5)]
|
1303
|
+
sage: G.add_edges(edges=J, loops=True)
|
1304
|
+
Traceback (most recent call last):
|
1305
|
+
...
|
1306
|
+
ValueError: loops are not allowed in matching covered graphs
|
1307
|
+
sage: G.edges(sort=True, sort_vertices=True)
|
1308
|
+
[(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None),
|
1309
|
+
(0, 5, None), (1, 2, None), (1, 3, None), (1, 5, None),
|
1310
|
+
(2, 3, None), (2, 4, None), (3, 4, None), (4, 5, None)]
|
1311
|
+
|
1312
|
+
Setting the parameter ``loops`` to ``True``::
|
1313
|
+
|
1314
|
+
sage: P = graphs.PetersenGraph()
|
1315
|
+
sage: G = MatchingCoveredGraph(P)
|
1316
|
+
sage: F = [(0, 0), (0, 2), (0, 3)]
|
1317
|
+
sage: G.add_edges(edges=F, loops=True)
|
1318
|
+
Traceback (most recent call last):
|
1319
|
+
...
|
1320
|
+
ValueError: loops are not allowed in matching covered graphs
|
1321
|
+
|
1322
|
+
Adding a multiple edge::
|
1323
|
+
|
1324
|
+
sage: S = graphs.StaircaseGraph(4)
|
1325
|
+
sage: G = MatchingCoveredGraph(S)
|
1326
|
+
sage: G.allow_multiple_edges(True)
|
1327
|
+
sage: F = [(0, 1, 'label'), (0, 4), (1, 2)]
|
1328
|
+
sage: G.add_edges(F)
|
1329
|
+
sage: G.edges(sort=False)
|
1330
|
+
[(0, 1, None), (0, 1, 'label'), (0, 3, None), (0, 4, None),
|
1331
|
+
(0, 6, None), (1, 2, None), (1, 2, None), (1, 4, None),
|
1332
|
+
(2, 5, None), (2, 7, None), (3, 4, None), (3, 6, None),
|
1333
|
+
(4, 5, None), (5, 7, None), (6, 7, None)]
|
1334
|
+
sage: H = [(0, 1)] * 4
|
1335
|
+
sage: G.add_edges(H)
|
1336
|
+
sage: G.edge_label(0, 1)
|
1337
|
+
[None, None, None, None, None, 'label']
|
1338
|
+
|
1339
|
+
TESTS:
|
1340
|
+
|
1341
|
+
Providing with a non-iterable of edges::
|
1342
|
+
|
1343
|
+
sage: K2 = graphs.CompleteGraph(2)
|
1344
|
+
sage: G = MatchingCoveredGraph(K2)
|
1345
|
+
sage: G.add_edges(1234)
|
1346
|
+
Traceback (most recent call last):
|
1347
|
+
...
|
1348
|
+
ValueError: expected an iterable of edges,
|
1349
|
+
but got a non-iterable object
|
1350
|
+
|
1351
|
+
Providing with an edge in ``edges`` that has 0 values to unpack::
|
1352
|
+
|
1353
|
+
sage: W = graphs.WagnerGraph()
|
1354
|
+
sage: G = MatchingCoveredGraph(W)
|
1355
|
+
sage: G.add_edges([()])
|
1356
|
+
Traceback (most recent call last):
|
1357
|
+
...
|
1358
|
+
ValueError: need more than 1 value to unpack for edge: ()
|
1359
|
+
|
1360
|
+
Providing with an edge in ``edges`` that has precisely one value to unpack::
|
1361
|
+
|
1362
|
+
sage: T = graphs.TruncatedBiwheelGraph(10)
|
1363
|
+
sage: G = MatchingCoveredGraph(T)
|
1364
|
+
sage: G.add_edges([(0, )])
|
1365
|
+
Traceback (most recent call last):
|
1366
|
+
...
|
1367
|
+
ValueError: need more than 1 value to unpack for edge: (0,)
|
1368
|
+
|
1369
|
+
Providing with an edge in ``edges`` that has more than 3 values to unpack::
|
1370
|
+
|
1371
|
+
sage: B = graphs.BiwheelGraph(5)
|
1372
|
+
sage: G = MatchingCoveredGraph(B)
|
1373
|
+
sage: G.add_edges([(0, 1, 2, 3, 4)])
|
1374
|
+
Traceback (most recent call last):
|
1375
|
+
...
|
1376
|
+
ValueError: too many values to unpack (expected 2) for edge: (0, 1, 2, 3, 4)
|
1377
|
+
|
1378
|
+
Providing with an edge of unknown data type::
|
1379
|
+
|
1380
|
+
sage: M = graphs.MurtyGraph()
|
1381
|
+
sage: G = MatchingCoveredGraph(M)
|
1382
|
+
sage: F = [None, 'edge', None]
|
1383
|
+
sage: G.add_edges(F)
|
1384
|
+
Traceback (most recent call last):
|
1385
|
+
...
|
1386
|
+
TypeError: input edge None is of unknown type
|
1387
|
+
"""
|
1388
|
+
if loops:
|
1389
|
+
raise ValueError('loops are not allowed in '
|
1390
|
+
'matching covered graphs')
|
1391
|
+
|
1392
|
+
if not edges: # do nothing
|
1393
|
+
return
|
1394
|
+
|
1395
|
+
from collections.abc import Iterable
|
1396
|
+
if not isinstance(edges, Iterable):
|
1397
|
+
raise ValueError('expected an iterable of edges, '
|
1398
|
+
'but got a non-iterable object')
|
1399
|
+
|
1400
|
+
links = [] # to extract the nonloop input edges
|
1401
|
+
for edge in edges:
|
1402
|
+
if hasattr(edge, '__len__'):
|
1403
|
+
if len(edge) <= 1:
|
1404
|
+
raise ValueError('need more than 1 value to unpack '
|
1405
|
+
f'for edge: {edge}')
|
1406
|
+
|
1407
|
+
elif len(edge) > 3:
|
1408
|
+
raise ValueError('too many values to unpack (expected 2) '
|
1409
|
+
f'for edge: {edge}')
|
1410
|
+
|
1411
|
+
else:
|
1412
|
+
raise TypeError(f'input edge {edge} is of unknown type')
|
1413
|
+
|
1414
|
+
u, v, l = None, None, None
|
1415
|
+
|
1416
|
+
if len(edge) == 2:
|
1417
|
+
u, v = edge
|
1418
|
+
else:
|
1419
|
+
u, v, l = edge
|
1420
|
+
|
1421
|
+
if u != v:
|
1422
|
+
links.append((u, v, l))
|
1423
|
+
|
1424
|
+
# If each of the input edges is existent
|
1425
|
+
if (self.allows_multiple_edges()
|
1426
|
+
and all(self.has_edge(*edge) for edge in links)):
|
1427
|
+
self._backend.add_edges(links, self._directed)
|
1428
|
+
return
|
1429
|
+
|
1430
|
+
# Check if all the incident vertices of the input edges are existent
|
1431
|
+
new_vertices = {x for u, v, _ in links for x in (u, v)
|
1432
|
+
if x not in self}
|
1433
|
+
|
1434
|
+
# Throw error if the no. of new vertices is odd
|
1435
|
+
if len(new_vertices) % 2:
|
1436
|
+
raise ValueError('odd order is not allowed for '
|
1437
|
+
'matching covered graphs')
|
1438
|
+
|
1439
|
+
try:
|
1440
|
+
G = Graph(self, multiedges=self.allows_multiple_edges())
|
1441
|
+
G.add_edges(edges=links, loops=loops)
|
1442
|
+
|
1443
|
+
# Check if G has a vertex with at most 1 neighbor
|
1444
|
+
if any(len(G.neighbors(v)) <= 1 for v in G):
|
1445
|
+
raise ValueError('the resulting graph after the addition of'
|
1446
|
+
'the edges is not matching covered')
|
1447
|
+
|
1448
|
+
# If all the vertices are existent, the existing perfect matching
|
1449
|
+
# can be used.
|
1450
|
+
if not new_vertices:
|
1451
|
+
self.__init__(data=G, matching=self.get_matching())
|
1452
|
+
|
1453
|
+
else:
|
1454
|
+
# Check if the existing perfect matching may be extended to a
|
1455
|
+
# perfect matching of the new graph
|
1456
|
+
links_with_two_new_vertices = []
|
1457
|
+
|
1458
|
+
for edge in links:
|
1459
|
+
if edge[0] in new_vertices and edge[1] in new_vertices:
|
1460
|
+
links_with_two_new_vertices.append(edge)
|
1461
|
+
|
1462
|
+
M = Graph(data=links_with_two_new_vertices, format='list_of_edges')
|
1463
|
+
M.add_edges(self.get_matching())
|
1464
|
+
|
1465
|
+
# Check if M is a perfect matching of the resulting graph
|
1466
|
+
if (G.order() != 2*M.size()):
|
1467
|
+
M = None
|
1468
|
+
|
1469
|
+
self.__init__(data=G, matching=M)
|
1470
|
+
|
1471
|
+
except Exception:
|
1472
|
+
raise ValueError('the resulting graph after the addition of'
|
1473
|
+
'the edges is not matching covered')
|
1474
|
+
|
1475
|
+
@doc_index('Overwritten methods')
|
1476
|
+
def add_vertex(self, name=None):
|
1477
|
+
r"""
|
1478
|
+
Add a vertex to the (matching covered) graph.
|
1479
|
+
|
1480
|
+
.. NOTE::
|
1481
|
+
|
1482
|
+
This method overwrites the
|
1483
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.add_vertex` method
|
1484
|
+
to ensure that isolated vertices are forbidden in
|
1485
|
+
:class:`~MatchingCoveredGraph`.
|
1486
|
+
|
1487
|
+
INPUT:
|
1488
|
+
|
1489
|
+
- ``name`` -- an immutable object (default: ``None``); when no name is
|
1490
|
+
specified (default), then the new vertex will be represented by the
|
1491
|
+
least integer not already representing a vertex. ``name`` must be an
|
1492
|
+
immutable object (e.g., an integer, a tuple, etc.).
|
1493
|
+
|
1494
|
+
OUTPUT:
|
1495
|
+
|
1496
|
+
- If ``name`` specifies an existing vertex, then nothing is done.
|
1497
|
+
Otherwise a :exc:`ValueError` is returned with no change to the
|
1498
|
+
existing (matching covered) graph is returned since matching covered
|
1499
|
+
graphs are free of isolated vertices.
|
1500
|
+
|
1501
|
+
EXAMPLES:
|
1502
|
+
|
1503
|
+
Adding an existing vertex::
|
1504
|
+
|
1505
|
+
sage: P = graphs.PetersenGraph()
|
1506
|
+
sage: P
|
1507
|
+
Petersen graph: Graph on 10 vertices
|
1508
|
+
sage: G = MatchingCoveredGraph(P)
|
1509
|
+
sage: G
|
1510
|
+
Matching covered petersen graph: graph on 10 vertices
|
1511
|
+
sage: u = next(G.vertex_iterator())
|
1512
|
+
sage: G.add_vertex(u)
|
1513
|
+
sage: G
|
1514
|
+
Matching covered petersen graph: graph on 10 vertices
|
1515
|
+
|
1516
|
+
Adding a new/ non-existing vertex::
|
1517
|
+
|
1518
|
+
sage: G.add_vertex()
|
1519
|
+
Traceback (most recent call last):
|
1520
|
+
...
|
1521
|
+
ValueError: isolated vertices are not allowed in matching covered graphs
|
1522
|
+
sage: u = 100
|
1523
|
+
sage: G.add_vertex(u)
|
1524
|
+
Traceback (most recent call last):
|
1525
|
+
...
|
1526
|
+
ValueError: isolated vertices are not allowed in matching covered graphs
|
1527
|
+
"""
|
1528
|
+
if name not in self:
|
1529
|
+
raise ValueError('isolated vertices are not allowed in '
|
1530
|
+
'matching covered graphs')
|
1531
|
+
|
1532
|
+
@doc_index('Overwritten methods')
|
1533
|
+
def add_vertices(self, vertices):
|
1534
|
+
r"""
|
1535
|
+
Add vertices to the (matching covered) graph from an iterable container
|
1536
|
+
of vertices.
|
1537
|
+
|
1538
|
+
.. NOTE::
|
1539
|
+
|
1540
|
+
This method overwrites the
|
1541
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.add_vertices` method
|
1542
|
+
to ensure that isolated vertices are forbidden in
|
1543
|
+
:class:`~MatchingCoveredGraph`.
|
1544
|
+
|
1545
|
+
INPUT:
|
1546
|
+
|
1547
|
+
- ``vertices`` -- iterator container of vertex labels. A new label is
|
1548
|
+
created, used and returned in the output list for all ``None`` values
|
1549
|
+
in ``vertices``.
|
1550
|
+
|
1551
|
+
OUTPUT:
|
1552
|
+
|
1553
|
+
- If all of the vertices are existing vertices of the (matching
|
1554
|
+
covered) graph, then nothing is done; otherwise a :exc:`ValueError`
|
1555
|
+
is returned with no change to the existing (matching covered) graph
|
1556
|
+
since matching covered graphs are free of isolated vertices.
|
1557
|
+
|
1558
|
+
EXAMPLES:
|
1559
|
+
|
1560
|
+
Adding a list of already existing vertices::
|
1561
|
+
|
1562
|
+
sage: T = graphs.TruncatedBiwheelGraph(15)
|
1563
|
+
sage: T
|
1564
|
+
Truncated biwheel graph: Graph on 30 vertices
|
1565
|
+
sage: G = MatchingCoveredGraph(T)
|
1566
|
+
sage: G
|
1567
|
+
Matching covered truncated biwheel graph: graph on 30 vertices
|
1568
|
+
sage: S = [0, 1, 2, 3] # We choose 4 existing vertices
|
1569
|
+
sage: G.add_vertices(S)
|
1570
|
+
sage: G
|
1571
|
+
Matching covered truncated biwheel graph: graph on 30 vertices
|
1572
|
+
|
1573
|
+
Adding a list of vertices in which at least one is non-existent or
|
1574
|
+
``None`` or possibly both::
|
1575
|
+
|
1576
|
+
sage: T = graphs.CompleteGraph(2)
|
1577
|
+
sage: T
|
1578
|
+
Complete graph: Graph on 2 vertices
|
1579
|
+
sage: G = MatchingCoveredGraph(T)
|
1580
|
+
sage: G
|
1581
|
+
Matching covered complete graph: graph on 2 vertices
|
1582
|
+
sage: S1 = [2, 3, 4]
|
1583
|
+
sage: G.add_vertices(S1)
|
1584
|
+
Traceback (most recent call last):
|
1585
|
+
...
|
1586
|
+
ValueError: isolated vertices are not allowed in matching covered graphs
|
1587
|
+
sage: S2 = [None, None]
|
1588
|
+
sage: G.add_vertices(S2)
|
1589
|
+
Traceback (most recent call last):
|
1590
|
+
...
|
1591
|
+
ValueError: isolated vertices are not allowed in matching covered graphs
|
1592
|
+
sage: S3 = [2, None, None, 5]
|
1593
|
+
sage: G.add_vertices(S3)
|
1594
|
+
Traceback (most recent call last):
|
1595
|
+
...
|
1596
|
+
ValueError: isolated vertices are not allowed in matching covered graphs
|
1597
|
+
"""
|
1598
|
+
if any(vertex not in self for vertex in vertices):
|
1599
|
+
raise ValueError('isolated vertices are not allowed in '
|
1600
|
+
'matching covered graphs')
|
1601
|
+
|
1602
|
+
@doc_index('Overwritten methods')
|
1603
|
+
def allow_loops(self, new, check=True):
|
1604
|
+
r"""
|
1605
|
+
Change whether loops are allowed in (matching covered) graphs.
|
1606
|
+
|
1607
|
+
.. NOTE::
|
1608
|
+
|
1609
|
+
This method overwrites the
|
1610
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.allow_loops` method
|
1611
|
+
to ensure that loops are forbidden in :class:`~MatchingCoveredGraph`.
|
1612
|
+
|
1613
|
+
INPUT:
|
1614
|
+
|
1615
|
+
- ``new`` -- boolean
|
1616
|
+
|
1617
|
+
- ``check`` -- boolean (default: ``True``); whether to remove existing
|
1618
|
+
loops from the graph when the new status is ``False``. It is an
|
1619
|
+
argument in
|
1620
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.allow_loops` method
|
1621
|
+
and is not used in this overwritten one.
|
1622
|
+
|
1623
|
+
OUTPUT:
|
1624
|
+
|
1625
|
+
- A :exc:`ValueError` is returned with no change to the existing
|
1626
|
+
(matching covered) graph if ``new`` is ``True`` since a matching
|
1627
|
+
covered graph, by definition, is free of self-loops. If ``new`` is
|
1628
|
+
set to ``False``, there is no output.
|
1629
|
+
|
1630
|
+
EXAMPLES:
|
1631
|
+
|
1632
|
+
Petersen graph is matching covered::
|
1633
|
+
|
1634
|
+
sage: P = graphs.PetersenGraph()
|
1635
|
+
sage: P.is_matching_covered()
|
1636
|
+
True
|
1637
|
+
sage: G = MatchingCoveredGraph(P)
|
1638
|
+
sage: G.allow_loops(True)
|
1639
|
+
Traceback (most recent call last):
|
1640
|
+
...
|
1641
|
+
ValueError: loops are not allowed in matching covered graphs
|
1642
|
+
|
1643
|
+
.. SEEALSO::
|
1644
|
+
|
1645
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops`
|
1646
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops`
|
1647
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges`
|
1648
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices`
|
1649
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops`
|
1650
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops`
|
1651
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops`
|
1652
|
+
"""
|
1653
|
+
if new:
|
1654
|
+
raise ValueError('loops are not allowed in '
|
1655
|
+
'matching covered graphs')
|
1656
|
+
|
1657
|
+
@doc_index('Overwritten methods')
|
1658
|
+
def allows_loops(self):
|
1659
|
+
r"""
|
1660
|
+
Return whether loops are permitted in (matching covered) graphs.
|
1661
|
+
|
1662
|
+
.. NOTE::
|
1663
|
+
|
1664
|
+
This method overwrites the
|
1665
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.allows_loops` method
|
1666
|
+
to show that loops are forbidden in :class:`~MatchingCoveredGraph`.
|
1667
|
+
|
1668
|
+
OUTPUT:
|
1669
|
+
|
1670
|
+
- A boolean value ``False`` is returned, since matching covered graphs,
|
1671
|
+
by definition, are free of loops.
|
1672
|
+
|
1673
|
+
EXAMPLES:
|
1674
|
+
|
1675
|
+
Petersen graph is matching covered::
|
1676
|
+
|
1677
|
+
sage: P = graphs.PetersenGraph()
|
1678
|
+
sage: P.is_matching_covered()
|
1679
|
+
True
|
1680
|
+
sage: G = MatchingCoveredGraph(P)
|
1681
|
+
sage: G.allows_loops()
|
1682
|
+
False
|
1683
|
+
|
1684
|
+
.. SEEALSO::
|
1685
|
+
|
1686
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops`
|
1687
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops`
|
1688
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges`
|
1689
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices`
|
1690
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops`
|
1691
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops`
|
1692
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops`
|
1693
|
+
"""
|
1694
|
+
return False
|
1695
|
+
|
1696
|
+
@doc_index('Barriers and canonical partition')
|
1697
|
+
def canonical_partition(self):
|
1698
|
+
r"""
|
1699
|
+
Return the canonical partition of the (matching covered) graph.
|
1700
|
+
|
1701
|
+
For a matching covered graph `G`, a subset `B` of the vertex set `V` is
|
1702
|
+
a barrier if `|B| = o(G - B)`, where `|B|` denotes the cardinality of
|
1703
|
+
the set `B` and `o(G - B)` denotes the number of odd components in the
|
1704
|
+
graph `G - B`. And a barrier `B` is a maximal barrier if `C` is not a
|
1705
|
+
barrier for each `C` such that `B \subset C \subseteq V`.
|
1706
|
+
|
1707
|
+
Note that in a matching covered graph, each vertex belongs to a unique
|
1708
|
+
maximal barrier. The maximal barriers of a matching covered graph
|
1709
|
+
partitions its vertex set and the partition of the vertex set of a
|
1710
|
+
matching covered graph into its maximal barriers is called as its
|
1711
|
+
*canonical* *partition*.
|
1712
|
+
|
1713
|
+
OUTPUT:
|
1714
|
+
|
1715
|
+
- A list of sets that constitute a (canonical) partition of the vertex
|
1716
|
+
set, wherein each set is a (unique) maximal barrier of the (matching
|
1717
|
+
covered) graph.
|
1718
|
+
|
1719
|
+
EXAMPLES:
|
1720
|
+
|
1721
|
+
Show the maximal barrier of the graph `K_4 \odot K_{3, 3}`::
|
1722
|
+
|
1723
|
+
sage: G = Graph([
|
1724
|
+
....: (0, 2), (0, 3), (0, 4), (1, 2),
|
1725
|
+
....: (1, 3), (1, 4), (2, 5), (3, 6),
|
1726
|
+
....: (4, 7), (5, 6), (5, 7), (6, 7)
|
1727
|
+
....: ])
|
1728
|
+
sage: H = MatchingCoveredGraph(G)
|
1729
|
+
sage: H.canonical_partition()
|
1730
|
+
[{0}, {1}, {2, 3, 4}, {5}, {6}, {7}]
|
1731
|
+
|
1732
|
+
For a bicritical graph (for instance, the Petersen graph), the
|
1733
|
+
canonical partition constitutes of only singleton sets each containing
|
1734
|
+
an individual vertex::
|
1735
|
+
|
1736
|
+
sage: P = graphs.PetersenGraph()
|
1737
|
+
sage: G = MatchingCoveredGraph(P)
|
1738
|
+
sage: G.canonical_partition()
|
1739
|
+
[{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}]
|
1740
|
+
|
1741
|
+
For a bipartite matching covered graph (for instance, the Hexahedral
|
1742
|
+
graph), the canonical partition consists of two sets each of which
|
1743
|
+
corresponds to the individual color class::
|
1744
|
+
|
1745
|
+
sage: H = graphs.HexahedralGraph()
|
1746
|
+
sage: G = MatchingCoveredGraph(H)
|
1747
|
+
sage: G.canonical_partition()
|
1748
|
+
[{0, 2, 5, 7}, {1, 3, 4, 6}]
|
1749
|
+
sage: B = BipartiteGraph(H)
|
1750
|
+
sage: list(B.bipartition()) == G.canonical_partition()
|
1751
|
+
True
|
1752
|
+
|
1753
|
+
REFERENCES:
|
1754
|
+
|
1755
|
+
- [LM2024]_
|
1756
|
+
|
1757
|
+
.. SEEALSO::
|
1758
|
+
|
1759
|
+
- :meth:`~sage.graphs.graph.Graph.is_bicritical`
|
1760
|
+
- :meth:`~sage.graphs.graph.Graph.is_matching_covered`
|
1761
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.maximal_barrier`
|
1762
|
+
"""
|
1763
|
+
visited = set()
|
1764
|
+
|
1765
|
+
maximal_barriers = []
|
1766
|
+
for v in self:
|
1767
|
+
if v not in visited:
|
1768
|
+
B = self.maximal_barrier(v)
|
1769
|
+
visited.update(B)
|
1770
|
+
maximal_barriers.append(B)
|
1771
|
+
|
1772
|
+
return maximal_barriers
|
1773
|
+
|
1774
|
+
@doc_index('Overwritten methods')
|
1775
|
+
def delete_vertex(self, vertex, in_order=False):
|
1776
|
+
r"""
|
1777
|
+
Delete a vertex, removing all incident edges.
|
1778
|
+
|
1779
|
+
.. NOTE::
|
1780
|
+
|
1781
|
+
This method overwrites the
|
1782
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.delete_vertex`
|
1783
|
+
method to ensure that an odd order is forbidden in
|
1784
|
+
:class:`~MatchingCoveredGraph`.
|
1785
|
+
|
1786
|
+
INPUT:
|
1787
|
+
|
1788
|
+
- ``vertex`` -- a vertex that is to be deleted.
|
1789
|
+
|
1790
|
+
- ``in_order`` -- boolean (default: ``False``); if ``True``, this
|
1791
|
+
deletes the `i`-th vertex in the sorted list of vertices, i.e.
|
1792
|
+
``G.vertices(sort=True)[i]``
|
1793
|
+
|
1794
|
+
OUTPUT:
|
1795
|
+
|
1796
|
+
- Deleting a non-existent vertex raises a :exc:`ValueError` exception;
|
1797
|
+
also a (different) :exc:`ValueError` is returned on deleting an
|
1798
|
+
existing vertex since matching covered graphs are of even order. In
|
1799
|
+
both cases no modifications are made to the existing (matching
|
1800
|
+
covered) graph.
|
1801
|
+
|
1802
|
+
EXAMPLES:
|
1803
|
+
|
1804
|
+
Deleting a non-existent vertex::
|
1805
|
+
|
1806
|
+
sage: W = graphs.WheelGraph(12)
|
1807
|
+
sage: G = MatchingCoveredGraph(W)
|
1808
|
+
sage: u = 100
|
1809
|
+
sage: G.delete_vertex(u)
|
1810
|
+
Traceback (most recent call last):
|
1811
|
+
...
|
1812
|
+
ValueError: vertex (100) not in the graph
|
1813
|
+
sage: G.delete_vertex(vertex=u, in_order=True)
|
1814
|
+
Traceback (most recent call last):
|
1815
|
+
...
|
1816
|
+
ValueError: vertex (100) not in the graph
|
1817
|
+
|
1818
|
+
Deleting an existing vertex::
|
1819
|
+
|
1820
|
+
sage: W = graphs.WheelGraph(12)
|
1821
|
+
sage: G = MatchingCoveredGraph(W)
|
1822
|
+
sage: u = next(G.vertex_iterator())
|
1823
|
+
sage: G.delete_vertex(u)
|
1824
|
+
Traceback (most recent call last):
|
1825
|
+
...
|
1826
|
+
ValueError: odd order is not allowed for matching covered graphs
|
1827
|
+
sage: G.delete_vertex(vertex=u, in_order=True)
|
1828
|
+
Traceback (most recent call last):
|
1829
|
+
...
|
1830
|
+
ValueError: odd order is not allowed for matching covered graphs
|
1831
|
+
"""
|
1832
|
+
if vertex not in self:
|
1833
|
+
raise ValueError('vertex (%s) not in the graph' % str(vertex))
|
1834
|
+
|
1835
|
+
if in_order:
|
1836
|
+
vertex = self.vertices(sort=True)[vertex]
|
1837
|
+
|
1838
|
+
raise ValueError('odd order is not allowed for '
|
1839
|
+
'matching covered graphs')
|
1840
|
+
|
1841
|
+
@doc_index('Overwritten methods')
|
1842
|
+
def delete_vertices(self, vertices):
|
1843
|
+
r"""
|
1844
|
+
Delete specified vertices form ``self``.
|
1845
|
+
|
1846
|
+
This method deletes the vertices from the iterable container
|
1847
|
+
``vertices`` from ``self`` along with incident edges. An error is
|
1848
|
+
raised if the resulting graph is not matching covered.
|
1849
|
+
|
1850
|
+
.. NOTE::
|
1851
|
+
|
1852
|
+
This method overwrites the
|
1853
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.delete_vertices`
|
1854
|
+
method to ensure that an odd order is forbidden in
|
1855
|
+
:class:`~MatchingCoveredGraph`.
|
1856
|
+
|
1857
|
+
INPUT:
|
1858
|
+
|
1859
|
+
- ``vertices`` -- a list/ set of vertices that are to be deleted.
|
1860
|
+
|
1861
|
+
OUTPUT:
|
1862
|
+
|
1863
|
+
- Deleting a non-existent vertex will raise a :exc:`ValueError`
|
1864
|
+
exception, in which case none of the vertices in ``vertices``
|
1865
|
+
is deleted.
|
1866
|
+
|
1867
|
+
- If all of the vertices in the list/ set provided exist in the graph,
|
1868
|
+
but the resulting graph after deletion of all of those is not
|
1869
|
+
matching covered, then a :exc:`ValueError` exception is raised
|
1870
|
+
without any alterations to the existing (matching covered) graph,
|
1871
|
+
otherwise the vertices are deleted and nothing is returned.
|
1872
|
+
|
1873
|
+
EXAMPLES:
|
1874
|
+
|
1875
|
+
Providing with an empty list of vertices::
|
1876
|
+
|
1877
|
+
sage: C = graphs.CycleGraph(6)
|
1878
|
+
sage: G = MatchingCoveredGraph(C)
|
1879
|
+
sage: G.delete_vertices([])
|
1880
|
+
sage: G == C
|
1881
|
+
True
|
1882
|
+
|
1883
|
+
Removing all the existent vertices::
|
1884
|
+
|
1885
|
+
sage: M = graphs.MoebiusLadderGraph(10)
|
1886
|
+
sage: G = MatchingCoveredGraph(M)
|
1887
|
+
sage: S = list(G.vertices())
|
1888
|
+
sage: G.delete_vertices(S)
|
1889
|
+
Traceback (most recent call last):
|
1890
|
+
...
|
1891
|
+
ValueError: the resulting graph after the removal of the vertices
|
1892
|
+
is trivial, therefore is not matching covered
|
1893
|
+
|
1894
|
+
Providing with a list of vertices with at least one non-existent
|
1895
|
+
vertex::
|
1896
|
+
|
1897
|
+
sage: S = graphs.StaircaseGraph(4)
|
1898
|
+
sage: S
|
1899
|
+
Staircase graph: Graph on 8 vertices
|
1900
|
+
sage: G = MatchingCoveredGraph(S)
|
1901
|
+
sage: G
|
1902
|
+
Matching covered staircase graph: graph on 8 vertices
|
1903
|
+
sage: T = list(range(5, 20, 2))
|
1904
|
+
sage: G.delete_vertices(T)
|
1905
|
+
Traceback (most recent call last):
|
1906
|
+
...
|
1907
|
+
ValueError: vertex (9) not in the graph
|
1908
|
+
|
1909
|
+
Removing an odd no. of distinct vertices from
|
1910
|
+
a matching covered graph::
|
1911
|
+
|
1912
|
+
sage: P = graphs.PetersenGraph()
|
1913
|
+
sage: G = MatchingCoveredGraph(P)
|
1914
|
+
sage: S = [0, 1, 2, 10, 10, 100]
|
1915
|
+
sage: G.delete_vertices(S)
|
1916
|
+
Traceback (most recent call last):
|
1917
|
+
...
|
1918
|
+
ValueError: an odd no. of distinct vertices can not be
|
1919
|
+
removed from a matching covered graph
|
1920
|
+
|
1921
|
+
Providing with a list of existent vertices whose deletion results in a
|
1922
|
+
graph which is not matching covered::
|
1923
|
+
|
1924
|
+
sage: S = graphs.StaircaseGraph(4)
|
1925
|
+
sage: S
|
1926
|
+
Staircase graph: Graph on 8 vertices
|
1927
|
+
sage: G = MatchingCoveredGraph(S)
|
1928
|
+
sage: G
|
1929
|
+
Matching covered staircase graph: graph on 8 vertices
|
1930
|
+
sage: T = [1, 4]
|
1931
|
+
sage: G.delete_vertices(T)
|
1932
|
+
Traceback (most recent call last):
|
1933
|
+
...
|
1934
|
+
ValueError: the resulting graph after the removal of
|
1935
|
+
the vertices is not matching covered
|
1936
|
+
|
1937
|
+
Providing with a list of existent vertices after the deletion of which
|
1938
|
+
the resulting graph is still matching covered; note that in the
|
1939
|
+
following example, after the deletion of two vertices from a staircase
|
1940
|
+
graph, the resulting graph is NOT a staircase graph
|
1941
|
+
(see :issue:`38768`)::
|
1942
|
+
|
1943
|
+
sage: S = graphs.StaircaseGraph(4)
|
1944
|
+
sage: S
|
1945
|
+
Staircase graph: Graph on 8 vertices
|
1946
|
+
sage: G = MatchingCoveredGraph(S)
|
1947
|
+
sage: G
|
1948
|
+
Matching covered staircase graph: graph on 8 vertices
|
1949
|
+
sage: T = [6, 7]
|
1950
|
+
sage: G.delete_vertices(T)
|
1951
|
+
sage: G # Matching covered graph on 6 vertices
|
1952
|
+
Matching covered staircase graph: graph on 6 vertices
|
1953
|
+
"""
|
1954
|
+
if not vertices: # do nothing
|
1955
|
+
return
|
1956
|
+
|
1957
|
+
# Remove potentially duplicated vertices
|
1958
|
+
vertices = set(vertices)
|
1959
|
+
|
1960
|
+
if len(vertices) % 2: # try to remove an odd number of vertices
|
1961
|
+
raise ValueError('an odd no. of distinct vertices can not be '
|
1962
|
+
'removed from a matching covered graph')
|
1963
|
+
|
1964
|
+
for vertex in vertices:
|
1965
|
+
if vertex not in self:
|
1966
|
+
raise ValueError('vertex (%s) not in the graph' % str(vertex))
|
1967
|
+
|
1968
|
+
if self.order() == len(vertices):
|
1969
|
+
raise ValueError('the resulting graph after the removal of the '
|
1970
|
+
'vertices is trivial, therefore is not '
|
1971
|
+
'matching covered')
|
1972
|
+
|
1973
|
+
try:
|
1974
|
+
G = Graph(self, multiedges=self.allows_multiple_edges())
|
1975
|
+
G.delete_vertices(vertices)
|
1976
|
+
|
1977
|
+
M = Graph(self.get_matching())
|
1978
|
+
|
1979
|
+
M.delete_vertices(vertices)
|
1980
|
+
# The resulting matching after the removal of the input vertices
|
1981
|
+
# must be a valid perfect matching of the resulting graph obtained
|
1982
|
+
# after the removal of the vertices
|
1983
|
+
|
1984
|
+
if (G.order() != 2*M.size()):
|
1985
|
+
M = None
|
1986
|
+
|
1987
|
+
self.__init__(data=G, matching=M)
|
1988
|
+
|
1989
|
+
except Exception:
|
1990
|
+
raise ValueError('the resulting graph after the removal of '
|
1991
|
+
'the vertices is not matching covered')
|
1992
|
+
|
1993
|
+
@doc_index('Miscellaneous methods')
|
1994
|
+
def get_matching(self):
|
1995
|
+
r"""
|
1996
|
+
Return an :class:`~EdgesView` of ``self._matching``.
|
1997
|
+
|
1998
|
+
OUTPUT:
|
1999
|
+
|
2000
|
+
- This method returns :class:`EdgesView` of the edges of a
|
2001
|
+
perfect matching of the (matching covered) graph.
|
2002
|
+
|
2003
|
+
EXAMPLES:
|
2004
|
+
|
2005
|
+
If one specifies a perfect matching while initializing the object, the
|
2006
|
+
value of ``self._matching`` is the same matching::
|
2007
|
+
|
2008
|
+
sage: P = graphs.PetersenGraph()
|
2009
|
+
sage: M = [(0, 1), (2, 3), (4, 9), (5, 7), (6, 8)]
|
2010
|
+
sage: G = MatchingCoveredGraph(P, M)
|
2011
|
+
sage: sorted(G.get_matching())
|
2012
|
+
[(0, 1), (2, 3), (4, 9), (5, 7), (6, 8)]
|
2013
|
+
sage: M == sorted(G.get_matching())
|
2014
|
+
True
|
2015
|
+
|
2016
|
+
If no matching is specified while initializing a matching
|
2017
|
+
covered graph, a perfect matching is computed
|
2018
|
+
:meth:`~sage.graphs.graph.Graph.matching` and that is captured as
|
2019
|
+
``self._matching``::
|
2020
|
+
|
2021
|
+
sage: P = graphs.PetersenGraph()
|
2022
|
+
sage: M = P.matching()
|
2023
|
+
sage: G = MatchingCoveredGraph(P)
|
2024
|
+
sage: sorted(G.get_matching())
|
2025
|
+
[(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)]
|
2026
|
+
sage: sorted(M) == sorted(G.get_matching())
|
2027
|
+
True
|
2028
|
+
"""
|
2029
|
+
return self._matching
|
2030
|
+
|
2031
|
+
@doc_index('Barriers and canonical partition')
|
2032
|
+
def maximal_barrier(self, vertex):
|
2033
|
+
r"""
|
2034
|
+
Return the (unique) maximal barrier containing the vertex.
|
2035
|
+
|
2036
|
+
For a matching covered graph `G`, a subset `B` of the vertex set `V` is
|
2037
|
+
a barrier if `|B| = o(G - B)`, where `|B|` denotes the cardinality of
|
2038
|
+
the set `B` and `o(G - B)` denotes the number of odd components in the
|
2039
|
+
graph `G - B`. And a barrier `B` is a maximal barrier if `C` is not a
|
2040
|
+
barrier for each `C` such that `B \subset C \subseteq V`.
|
2041
|
+
|
2042
|
+
In a matching covered graph, each vertex belongs to a unique maximal
|
2043
|
+
barrier, which is a consequence of the following theorem.
|
2044
|
+
|
2045
|
+
.. RUBRIC:: Theorem [LM2024]_:
|
2046
|
+
|
2047
|
+
Let `u` and `v` be any two vertices in a matchable graph `G`. Then the
|
2048
|
+
graph `G - u - v` is matchable if and only if there is no barrier of
|
2049
|
+
`G` which contains both `u` and `v`.
|
2050
|
+
|
2051
|
+
And in order to find the vertices that do not lie in the maximal
|
2052
|
+
barrier containing the provided vertex in linear time we take
|
2053
|
+
inspiration of the `M` alternating tree seach method [LR2004]_.
|
2054
|
+
|
2055
|
+
INPUT:
|
2056
|
+
|
2057
|
+
- ``vertex`` -- a vertex of the graph
|
2058
|
+
|
2059
|
+
OUTPUT:
|
2060
|
+
|
2061
|
+
- A :exc:`~ValueError` is returned if ``vertex`` is not a vertex of the
|
2062
|
+
graph, otherwise a set of vertices that constitute the (unique)
|
2063
|
+
maximal barrier containing the vertex is returned.
|
2064
|
+
|
2065
|
+
EXAMPLES:
|
2066
|
+
|
2067
|
+
The graph `K_4 \odot K_{3, 3}` is matching covered. Show the set of
|
2068
|
+
vertices in the (unique) maximal barrier containing the vertex `2`::
|
2069
|
+
|
2070
|
+
sage: G = Graph([
|
2071
|
+
....: (0, 2), (0, 3), (0, 4), (1, 2),
|
2072
|
+
....: (1, 3), (1, 4), (2, 5), (3, 6),
|
2073
|
+
....: (4, 7), (5, 6), (5, 7), (6, 7)
|
2074
|
+
....: ])
|
2075
|
+
sage: H = MatchingCoveredGraph(G)
|
2076
|
+
sage: B = H.maximal_barrier(2)
|
2077
|
+
sage: B
|
2078
|
+
{2, 3, 4}
|
2079
|
+
|
2080
|
+
Let `B` be a maximal barrier of a matching covered graph `G` (which is,
|
2081
|
+
of course, a matchable graph). The graph, `J := G - B` has no even
|
2082
|
+
component::
|
2083
|
+
|
2084
|
+
sage: J = G.copy()
|
2085
|
+
sage: J.delete_vertices(B)
|
2086
|
+
sage: all(len(K)%2 != 0 for K in J.connected_components(sort=True))
|
2087
|
+
True
|
2088
|
+
|
2089
|
+
Let `B` be a maximal barrier in a matching covered graph `G` and let
|
2090
|
+
`M` be a perfect matching of `G`. If `K` is an odd component of
|
2091
|
+
`J := G - B`, then `M \cap \partial_G(K)` has precisely one edge and if
|
2092
|
+
`v` is the end of that edge in `V(K)`, then `M \cap E(K)` is a perfect
|
2093
|
+
matching of `K - v`::
|
2094
|
+
|
2095
|
+
sage: K = J.subgraph(vertices=(J.connected_components(sort=True))[0])
|
2096
|
+
sage: # Let F := \partial_G(K) and T := M \cap F
|
2097
|
+
sage: F = [edge for edge in G.edge_iterator()
|
2098
|
+
....: if (edge[0] in K and edge[1] not in K)
|
2099
|
+
....: or (edge[0] not in K and edge[1] in K)
|
2100
|
+
....: ]
|
2101
|
+
sage: M = H.get_matching()
|
2102
|
+
sage: T = [edge for edge in F if edge in M]
|
2103
|
+
sage: len(T) == 1
|
2104
|
+
True
|
2105
|
+
sage: v = T[0][0] if T[0][0] in K else T[0][1]
|
2106
|
+
sage: # Let N := M \cap E(K) and L := K - v
|
2107
|
+
sage: N = Graph([edge for edge in K.edge_iterator() if edge in M])
|
2108
|
+
sage: L = K.copy()
|
2109
|
+
sage: L.delete_vertex(v)
|
2110
|
+
sage: # Check if N is a perfect matching of L
|
2111
|
+
sage: L.order() == 2*N.size()
|
2112
|
+
True
|
2113
|
+
|
2114
|
+
Let `B` be a maximal barrier of a matching covered graph `G` (which is,
|
2115
|
+
of course, a matchable graph). The graph induced by each component of
|
2116
|
+
`G - B` is factor critical::
|
2117
|
+
|
2118
|
+
sage: all((K.subgraph(vertices=connected_component)).is_factor_critical()
|
2119
|
+
....: for connected_component in K.connected_components(sort=True)
|
2120
|
+
....: )
|
2121
|
+
True
|
2122
|
+
|
2123
|
+
For a bicritical graph (for instance, the Petersen graph), for each
|
2124
|
+
vertex the maximal barrier is a singleton set containing only that
|
2125
|
+
vertex::
|
2126
|
+
|
2127
|
+
sage: P = graphs.PetersenGraph()
|
2128
|
+
sage: G = MatchingCoveredGraph(P)
|
2129
|
+
sage: u = 0
|
2130
|
+
sage: set([u]) == G.maximal_barrier(u)
|
2131
|
+
True
|
2132
|
+
|
2133
|
+
In a bipartite matching covered graph (for instance, the Hexahedral
|
2134
|
+
graph), for a vertex, the maximal barrier is the set of vertices of
|
2135
|
+
the color class that the particular vertex belongs to. In other words,
|
2136
|
+
there are precisely two maximal barriers in a bipartite matching
|
2137
|
+
covered graph, that is, the vertex sets of the individual color class::
|
2138
|
+
|
2139
|
+
sage: G = graphs.HexahedralGraph()
|
2140
|
+
sage: H = MatchingCoveredGraph(G)
|
2141
|
+
sage: A, _ = H.bipartite_sets()
|
2142
|
+
sage: # needs random
|
2143
|
+
sage: import random
|
2144
|
+
sage: a = random.choice(list(A))
|
2145
|
+
sage: A == H.maximal_barrier(a)
|
2146
|
+
True
|
2147
|
+
|
2148
|
+
Maximal barriers of matching covered graph constitute a partition of
|
2149
|
+
its vertex set::
|
2150
|
+
|
2151
|
+
sage: S = set()
|
2152
|
+
sage: for v in H:
|
2153
|
+
....: B = tuple(sorted(list(H.maximal_barrier(v))))
|
2154
|
+
....: S.add(B)
|
2155
|
+
sage: S = list(S)
|
2156
|
+
sage: # Check that S is a partition of the vertex set of H
|
2157
|
+
sage: # Part 1: Check if S spans the vertex set of H
|
2158
|
+
sage: sorted([u for B in S for u in B]) == sorted(list(H))
|
2159
|
+
True
|
2160
|
+
sage: # Part 2: Check if each maximal barrier in S is disjoint
|
2161
|
+
sage: is_disjoint = True
|
2162
|
+
sage: for i in range(len(S)):
|
2163
|
+
....: for j in range(i+1, len(S)):
|
2164
|
+
....: c = [v for v in S[i] if v in S[j]]
|
2165
|
+
....: is_disjoint = (len(c) == 0)
|
2166
|
+
sage: is_disjoint
|
2167
|
+
True
|
2168
|
+
|
2169
|
+
TESTS:
|
2170
|
+
|
2171
|
+
Providing with a nonexistent vertex::
|
2172
|
+
|
2173
|
+
sage: P = graphs.PetersenGraph()
|
2174
|
+
sage: G = MatchingCoveredGraph(P)
|
2175
|
+
sage: G.maximal_barrier('')
|
2176
|
+
Traceback (most recent call last):
|
2177
|
+
...
|
2178
|
+
ValueError: vertex not in the graph
|
2179
|
+
sage: G.maximal_barrier(100)
|
2180
|
+
Traceback (most recent call last):
|
2181
|
+
...
|
2182
|
+
ValueError: vertex 100 not in the graph
|
2183
|
+
|
2184
|
+
REFERENCES:
|
2185
|
+
|
2186
|
+
- [LZ2004]_
|
2187
|
+
- [LM2024]_
|
2188
|
+
|
2189
|
+
.. SEEALSO::
|
2190
|
+
|
2191
|
+
- :meth:`~sage.graphs.graph.Graph.is_bicritical`
|
2192
|
+
- :meth:`~sage.graphs.graph.Graph.is_matching_covered`
|
2193
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.canonical_partition`
|
2194
|
+
"""
|
2195
|
+
if vertex not in self:
|
2196
|
+
raise ValueError('vertex {} not in the graph'.format(vertex))
|
2197
|
+
|
2198
|
+
# u: The M neighbor of vertex
|
2199
|
+
matching = self.get_matching()
|
2200
|
+
u = next((a if b == vertex else b) for a, b, *_ in matching if vertex in [a, b])
|
2201
|
+
|
2202
|
+
# Goal: Find the vertices w such that G - w - vertex is matchable.
|
2203
|
+
# In other words, there exists an odd length M-alternating vertex-w
|
2204
|
+
# path in G, starting and ending with edges in M. Alternatively, there
|
2205
|
+
# exists an even length M-alternating u-w path in the graph G - vertex
|
2206
|
+
# starting with an edge not in M and ending with and edge in M.
|
2207
|
+
|
2208
|
+
# even: The set of all such vertex w
|
2209
|
+
from sage.graphs.matching import M_alternating_even_mark
|
2210
|
+
even = M_alternating_even_mark(G=self, matching=matching,
|
2211
|
+
vertex=u)
|
2212
|
+
|
2213
|
+
B = set([vertex])
|
2214
|
+
B.update(v for v in self if v not in even)
|
2215
|
+
|
2216
|
+
return B
|
2217
|
+
|
2218
|
+
@doc_index('Overwritten methods')
|
2219
|
+
def has_loops(self) -> bool:
|
2220
|
+
r"""
|
2221
|
+
Check whether there are loops in the (matching covered) graph.
|
2222
|
+
|
2223
|
+
.. NOTE::
|
2224
|
+
|
2225
|
+
This method overwrites the
|
2226
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.has_loops` method in
|
2227
|
+
order to return ``False`` as matching covered graphs are always
|
2228
|
+
free of looped edges.
|
2229
|
+
|
2230
|
+
OUTPUT:
|
2231
|
+
|
2232
|
+
- A boolean ``False`` is returned since matching covered graphs, by
|
2233
|
+
definition, are free of self-loops.
|
2234
|
+
|
2235
|
+
EXAMPLES:
|
2236
|
+
|
2237
|
+
A matching covered graph, for instance the Petersen graph, is always free
|
2238
|
+
of loops::
|
2239
|
+
|
2240
|
+
sage: P = graphs.PetersenGraph()
|
2241
|
+
sage: G = MatchingCoveredGraph(P)
|
2242
|
+
sage: G
|
2243
|
+
Matching covered petersen graph: graph on 10 vertices
|
2244
|
+
sage: G.has_loops()
|
2245
|
+
False
|
2246
|
+
sage: G.allows_loops()
|
2247
|
+
False
|
2248
|
+
sage: G.add_edge(0, 0)
|
2249
|
+
Traceback (most recent call last):
|
2250
|
+
...
|
2251
|
+
ValueError: loops are not allowed in matching covered graphs
|
2252
|
+
|
2253
|
+
A matching covered graph may support multiple edges, still no
|
2254
|
+
loops are allowed::
|
2255
|
+
|
2256
|
+
sage: K = graphs.CompleteGraph(2)
|
2257
|
+
sage: G = MatchingCoveredGraph(K)
|
2258
|
+
sage: G.allow_multiple_edges(True)
|
2259
|
+
sage: G
|
2260
|
+
Matching covered complete graph: multi-graph on 2 vertices
|
2261
|
+
sage: G.add_edge(0, 1, 'label')
|
2262
|
+
sage: G.add_edge(0, 0)
|
2263
|
+
Traceback (most recent call last):
|
2264
|
+
...
|
2265
|
+
ValueError: loops are not allowed in matching covered graphs
|
2266
|
+
sage: G.edges(sort=False)
|
2267
|
+
[(0, 1, None), (0, 1, 'label')]
|
2268
|
+
sage: G.allows_loops()
|
2269
|
+
False
|
2270
|
+
sage: G.has_loops()
|
2271
|
+
False
|
2272
|
+
sage: G.allow_loops(True)
|
2273
|
+
Traceback (most recent call last):
|
2274
|
+
...
|
2275
|
+
ValueError: loops are not allowed in matching covered graphs
|
2276
|
+
|
2277
|
+
.. SEEALSO::
|
2278
|
+
|
2279
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops`
|
2280
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops`
|
2281
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges`
|
2282
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices`
|
2283
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops`
|
2284
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops`
|
2285
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops`
|
2286
|
+
"""
|
2287
|
+
return False
|
2288
|
+
|
2289
|
+
@doc_index('Overwritten methods')
|
2290
|
+
def has_perfect_matching(G, algorithm='Edmonds', solver=None, verbose=0,
|
2291
|
+
*, integrality_tolerance=1e-3):
|
2292
|
+
r"""
|
2293
|
+
Check whether the graph has a perfect matching.
|
2294
|
+
|
2295
|
+
.. NOTE::
|
2296
|
+
|
2297
|
+
This method overwrites the
|
2298
|
+
:meth:`~sage.graphs.graph.Graph.has_perfect_matching` method in
|
2299
|
+
order to return ``True`` (provided the input arguments are valid)
|
2300
|
+
as matching covered graphs always admit a perfect matching.
|
2301
|
+
|
2302
|
+
INPUT:
|
2303
|
+
|
2304
|
+
- ``algorithm`` -- string (default: ``'Edmonds'``)
|
2305
|
+
|
2306
|
+
- ``'Edmonds'`` uses Edmonds' algorithm as implemented in NetworkX to
|
2307
|
+
find a matching of maximal cardinality, then check whether this
|
2308
|
+
cardinality is half the number of vertices of the graph.
|
2309
|
+
|
2310
|
+
- ``'LP_matching'`` uses a Linear Program to find a matching of
|
2311
|
+
maximal cardinality, then check whether this cardinality is half the
|
2312
|
+
number of vertices of the graph.
|
2313
|
+
|
2314
|
+
- ``'LP'`` uses a Linear Program formulation of the perfect matching
|
2315
|
+
problem: put a binary variable ``b[e]`` on each edge `e`, and for
|
2316
|
+
each vertex `v`, require that the sum of the values of the edges
|
2317
|
+
incident to `v` is 1.
|
2318
|
+
|
2319
|
+
- ``solver`` -- string (default: ``None``); specifies a Mixed Integer
|
2320
|
+
Linear Programming (MILP) solver to be used. If set to ``None``, the
|
2321
|
+
default one is used. For more information on MILP solvers and which
|
2322
|
+
default solver is used, see the method :meth:`solve
|
2323
|
+
<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
|
2324
|
+
:class:`MixedIntegerLinearProgram
|
2325
|
+
<sage.numerical.mip.MixedIntegerLinearProgram>`.
|
2326
|
+
|
2327
|
+
- ``verbose`` -- integer (default: 0); sets the level of verbosity:
|
2328
|
+
set to 0 by default, which means quiet (only useful when
|
2329
|
+
``algorithm == "LP_matching"`` or ``algorithm == "LP"``)
|
2330
|
+
|
2331
|
+
- ``integrality_tolerance`` -- float; parameter for use with MILP
|
2332
|
+
solvers over an inexact base ring; see
|
2333
|
+
:meth:`MixedIntegerLinearProgram.get_values`.
|
2334
|
+
|
2335
|
+
OUTPUT:
|
2336
|
+
|
2337
|
+
- If the input arguments are valid, a boolean (``True``) is returned as
|
2338
|
+
a maximum matching of a matching covered graph is always a perfect
|
2339
|
+
matching, otherwise a :exc:`~ValueError` is raised.
|
2340
|
+
|
2341
|
+
EXAMPLES:
|
2342
|
+
|
2343
|
+
Note that regardless of the algorithm (as long as the input arguments
|
2344
|
+
are in valid format), the method always returns the boolean ``True``::
|
2345
|
+
|
2346
|
+
sage: P = graphs.PetersenGraph()
|
2347
|
+
sage: P.has_perfect_matching() # Calls Graph.has_perfect_matching()
|
2348
|
+
True
|
2349
|
+
sage: G = MatchingCoveredGraph(P)
|
2350
|
+
sage: G.has_perfect_matching() # Calls MatchingCoveredGraph.has_perfect_matching()
|
2351
|
+
True
|
2352
|
+
sage: W = graphs.WheelGraph(6)
|
2353
|
+
sage: H = MatchingCoveredGraph(W)
|
2354
|
+
sage: H.has_perfect_matching(algorithm='LP_matching')
|
2355
|
+
True
|
2356
|
+
|
2357
|
+
Providing with an algorithm, that is not one of ``'Edmonds'``,
|
2358
|
+
``'LP_matching'`` or ``'LP'``::
|
2359
|
+
|
2360
|
+
sage: S = graphs.StaircaseGraph(4)
|
2361
|
+
sage: J = MatchingCoveredGraph(S)
|
2362
|
+
sage: J.has_perfect_matching(algorithm='algorithm')
|
2363
|
+
Traceback (most recent call last):
|
2364
|
+
...
|
2365
|
+
ValueError: algorithm must be set to 'Edmonds',
|
2366
|
+
'LP_matching' or 'LP'
|
2367
|
+
"""
|
2368
|
+
if algorithm in ['Edmonds', 'LP_matching', 'LP']:
|
2369
|
+
return True
|
2370
|
+
|
2371
|
+
raise ValueError('algorithm must be set to \'Edmonds\', '
|
2372
|
+
'\'LP_matching\' or \'LP\'')
|
2373
|
+
|
2374
|
+
@doc_index('Bricks, braces and tight cut decomposition')
|
2375
|
+
def is_brace(self, coNP_certificate=False):
|
2376
|
+
r"""
|
2377
|
+
Check if the (matching covered) graph is a brace.
|
2378
|
+
|
2379
|
+
A matching covered graph which is free of nontrivial tight cuts is
|
2380
|
+
called a *brace* if it is bipartite. Let `G := (A \cup B, E)` be a
|
2381
|
+
bipartite matching covered graph on six or more vertices. The
|
2382
|
+
following statements are equivalent [LM2024]_:
|
2383
|
+
|
2384
|
+
1. `G` is a brace (aka free of nontrivial tight cuts).
|
2385
|
+
2. `G - a_1 - a_2 - b_1 - b_2` has a perfect matching for any two
|
2386
|
+
distinct vertices `a_1` and `a_2` in `A` and any two distinct
|
2387
|
+
vertices `b_1` and `b_2` in `B`.
|
2388
|
+
3. `G` is two extendable (any two nonadjacent distinct edges can be
|
2389
|
+
extended to some perfect matching of `G`).
|
2390
|
+
4. `|N(X)| \geq |X| + 2`, for all `X ⊂ A` such that `0 < |X| <
|
2391
|
+
|A| - 1`, where `N(S) := \{b \mid (a, b) \in E ∧ a \in S\}` is called
|
2392
|
+
the neighboring set of `S`.
|
2393
|
+
5. `G - a - b` is matching covered, for some perfect matching `M` of
|
2394
|
+
`G` and for each edge `ab` in `M`.
|
2395
|
+
|
2396
|
+
We shall be using the 5th characterization mentioned above in order
|
2397
|
+
to determine whether the provided bipartite matching covered graph
|
2398
|
+
is a brace or not using *M*-alternating tree search [LZ2001]_.
|
2399
|
+
|
2400
|
+
INPUT:
|
2401
|
+
|
2402
|
+
- ``coNP_certificate`` -- boolean (default: ``False``)
|
2403
|
+
|
2404
|
+
OUTPUT:
|
2405
|
+
|
2406
|
+
- If the input matching covered graph is not bipartite, a
|
2407
|
+
:exc:`ValueError` is returned.
|
2408
|
+
|
2409
|
+
- If the input bipartite matching covered graph is a brace, a boolean
|
2410
|
+
``True`` is returned if ``coNP_certificate`` is set to ``False``
|
2411
|
+
otherwise a 5-tuple ``(True, None, None, None, None)`` is returned.
|
2412
|
+
|
2413
|
+
- If the input bipartite matching covered graph is not a brace, a
|
2414
|
+
boolean ``False`` is returned if ``coNP_certificate`` is set to
|
2415
|
+
``False`` otherwise a 5-tuple of
|
2416
|
+
|
2417
|
+
1. a boolean ``False``,
|
2418
|
+
|
2419
|
+
2. a list of edges constituting a nontrivial tight cut (which is a
|
2420
|
+
nontrivial barrier cut)
|
2421
|
+
|
2422
|
+
3. a set of vertices of one of the shores of the nontrivial tight cut
|
2423
|
+
|
2424
|
+
4. a string 'nontrivial tight cut'
|
2425
|
+
|
2426
|
+
5. a set of vertices showing the respective barrier
|
2427
|
+
|
2428
|
+
is returned.
|
2429
|
+
|
2430
|
+
EXAMPLES:
|
2431
|
+
|
2432
|
+
The complete graph on two vertices `K_2` is the smallest brace::
|
2433
|
+
|
2434
|
+
sage: K = graphs.CompleteGraph(2)
|
2435
|
+
sage: G = MatchingCoveredGraph(K)
|
2436
|
+
sage: G.is_brace()
|
2437
|
+
True
|
2438
|
+
|
2439
|
+
The cycle graph on four vertices `C_4` is a brace::
|
2440
|
+
|
2441
|
+
sage: C = graphs.CycleGraph(4)
|
2442
|
+
sage: G = MatchingCoveredGraph(C)
|
2443
|
+
sage: G.is_brace()
|
2444
|
+
True
|
2445
|
+
|
2446
|
+
Each graph that is isomorphic to a biwheel is a brace::
|
2447
|
+
|
2448
|
+
sage: B = graphs.BiwheelGraph(15)
|
2449
|
+
sage: G = MatchingCoveredGraph(B)
|
2450
|
+
sage: G.is_brace()
|
2451
|
+
True
|
2452
|
+
|
2453
|
+
A circular ladder graph of order eight or more on `2n` vertices for
|
2454
|
+
an even `n` is a brace::
|
2455
|
+
|
2456
|
+
sage: n = 10
|
2457
|
+
sage: CL = graphs.CircularLadderGraph(n)
|
2458
|
+
sage: G = MatchingCoveredGraph(CL)
|
2459
|
+
sage: G.is_brace()
|
2460
|
+
True
|
2461
|
+
|
2462
|
+
A moebius ladder graph of order six or more on `2n` vertices for an odd
|
2463
|
+
`n` is a brace::
|
2464
|
+
|
2465
|
+
sage: n = 11
|
2466
|
+
sage: ML = graphs.MoebiusLadderGraph(n)
|
2467
|
+
sage: G = MatchingCoveredGraph(ML)
|
2468
|
+
sage: G.is_brace()
|
2469
|
+
True
|
2470
|
+
|
2471
|
+
Note that the union of the above mentioned four families of braces,
|
2472
|
+
that are:
|
2473
|
+
|
2474
|
+
1. the biwheel graph ``BiwheelGraph(n)``,
|
2475
|
+
2. the circular ladder graph ``CircularLadderGraph(n)`` for even ``n``,
|
2476
|
+
3. the moebius ladder graph ``MoebiusLadderGraph(n)`` for odd ``n``,
|
2477
|
+
|
2478
|
+
is referred to as the *McCuaig* *family* *of* *braces.*
|
2479
|
+
|
2480
|
+
The only simple brace of order six is the complete graph of the same
|
2481
|
+
order, that is `K_{3, 3}`::
|
2482
|
+
|
2483
|
+
sage: L = list(graphs(6,
|
2484
|
+
....: lambda G: G.size() <= 15 and
|
2485
|
+
....: G.is_bipartite())
|
2486
|
+
....: )
|
2487
|
+
sage: L = list(G for G in L if G.is_connected() and
|
2488
|
+
....: G.is_matching_covered()
|
2489
|
+
....: )
|
2490
|
+
sage: M = list(MatchingCoveredGraph(G) for G in L)
|
2491
|
+
sage: B = list(G for G in M if G.is_brace())
|
2492
|
+
sage: K = graphs.CompleteBipartiteGraph(3, 3)
|
2493
|
+
sage: G = MatchingCoveredGraph(K)
|
2494
|
+
sage: next(iter(B)).is_isomorphic(G)
|
2495
|
+
True
|
2496
|
+
|
2497
|
+
The nonplanar `K_{3, 3}`-free brace Heawood graph is the unique cubic
|
2498
|
+
graph of girth six with the fewest number of vertices (that is 14).
|
2499
|
+
Note that by `K_{3, 3}`-free, it shows that the Heawood graph does not
|
2500
|
+
contain a subgraph that is isomophic to a graph obtained by
|
2501
|
+
bisubdivision of `K_{3, 3}`::
|
2502
|
+
|
2503
|
+
sage: K = graphs.CompleteBipartiteGraph(3, 3)
|
2504
|
+
sage: J = graphs.HeawoodGraph()
|
2505
|
+
sage: H = MatchingCoveredGraph(J)
|
2506
|
+
sage: H.is_brace() and not H.is_planar() and \
|
2507
|
+
....: H.is_regular(k=3) and H.girth() == 6
|
2508
|
+
True
|
2509
|
+
|
2510
|
+
Braces of order six or more are 3-connected::
|
2511
|
+
|
2512
|
+
sage: H = graphs.HexahedralGraph()
|
2513
|
+
sage: G = MatchingCoveredGraph(H)
|
2514
|
+
sage: G.is_brace() and G.is_triconnected()
|
2515
|
+
True
|
2516
|
+
|
2517
|
+
Braces of order four or more are 2-extendable::
|
2518
|
+
|
2519
|
+
sage: H = graphs.EllinghamHorton54Graph()
|
2520
|
+
sage: G = MatchingCoveredGraph(H)
|
2521
|
+
sage: G.is_brace()
|
2522
|
+
True
|
2523
|
+
sage: e = next(G.edge_iterator(labels=False)); f = None
|
2524
|
+
sage: for f in G.edge_iterator(labels=False):
|
2525
|
+
....: if not (set(e) & set(f)):
|
2526
|
+
....: break
|
2527
|
+
sage: S = [u for x in [e, f] for u in set(x)]
|
2528
|
+
sage: J = H.copy(); J.delete_vertices(S)
|
2529
|
+
sage: M = Graph(J.matching())
|
2530
|
+
sage: M.add_edges([e, f])
|
2531
|
+
sage: if all(d == 1 for d in M.degree()) and \
|
2532
|
+
....: G.order() == M.order() and \
|
2533
|
+
....: G.order() == 2*M.size():
|
2534
|
+
....: print(f'graph {G} is 2-extendable')
|
2535
|
+
graph Ellingham-Horton 54-graph is 2-extendable
|
2536
|
+
|
2537
|
+
Every edge in a brace of order at least six is removable::
|
2538
|
+
|
2539
|
+
sage: H = graphs.CircularLadderGraph(8)
|
2540
|
+
sage: G = MatchingCoveredGraph(H)
|
2541
|
+
sage: # len(G.removble_edges()) == G.size()
|
2542
|
+
# True
|
2543
|
+
|
2544
|
+
Every brace of order eight has the hexahedral graph as a spanning
|
2545
|
+
subgraph::
|
2546
|
+
|
2547
|
+
sage: H = graphs.HexahedralGraph()
|
2548
|
+
sage: L = list(graphs(8,
|
2549
|
+
....: lambda G: G.size() <= 28 and
|
2550
|
+
....: G.is_bipartite())
|
2551
|
+
....: )
|
2552
|
+
sage: L = list(G for G in L if G.is_connected() and
|
2553
|
+
....: G.is_matching_covered()
|
2554
|
+
....: )
|
2555
|
+
sage: M = list(MatchingCoveredGraph(G) for G in L)
|
2556
|
+
sage: B = list(G for G in M if G.is_brace())
|
2557
|
+
sage: C = list(G for G in M if Graph(G).subgraph_search(H) is not None)
|
2558
|
+
sage: B == C
|
2559
|
+
True
|
2560
|
+
|
2561
|
+
For every brace `G[A, B]` of order at least six, the graph
|
2562
|
+
`G - a_1 - a_2 - b_1 - b_2` has a perfect matching for any two distinct
|
2563
|
+
vertices `a_1` and `a_2` in `A` and any two distinct vertices `b_1` and
|
2564
|
+
`b_2` in `B`::
|
2565
|
+
|
2566
|
+
sage: H = graphs.CompleteBipartiteGraph(10, 10)
|
2567
|
+
sage: G = MatchingCoveredGraph(H)
|
2568
|
+
sage: G.is_brace()
|
2569
|
+
True
|
2570
|
+
sage: S = [0, 1, 10, 12]
|
2571
|
+
sage: G.delete_vertices(S)
|
2572
|
+
sage: G.has_perfect_matching()
|
2573
|
+
True
|
2574
|
+
|
2575
|
+
For a brace `G[A, B]` of order six or more, `|N(X)| \geq |X| + 2`, for
|
2576
|
+
all `X \subset A` such that `0 < |X| <|A| - 1`, where
|
2577
|
+
`N(S) := \{b | (a, b) \in E \^ a \in S\}` is called the neighboring set
|
2578
|
+
of `S`::
|
2579
|
+
|
2580
|
+
sage: H = graphs.MoebiusLadderGraph(15)
|
2581
|
+
sage: G = MatchingCoveredGraph(H)
|
2582
|
+
sage: G.is_brace()
|
2583
|
+
True
|
2584
|
+
sage: A, _ = G.bipartite_sets()
|
2585
|
+
sage: # needs random
|
2586
|
+
sage: X = random.sample(list(A), random.randint(1, len(A) - 1))
|
2587
|
+
sage: N = {v for u in X for v in G.neighbor_iterator(u)}
|
2588
|
+
sage: len(N) >= len(X) + 2
|
2589
|
+
True
|
2590
|
+
|
2591
|
+
For a brace `G` of order four or more with a perfect matching `M`, the
|
2592
|
+
graph `G - a - b` is matching covered for each edge `(a, b)` in `M`::
|
2593
|
+
|
2594
|
+
sage: H = graphs.HeawoodGraph()
|
2595
|
+
sage: G = MatchingCoveredGraph(H)
|
2596
|
+
sage: G.is_brace()
|
2597
|
+
True
|
2598
|
+
sage: M = G.get_matching()
|
2599
|
+
sage: L = []
|
2600
|
+
sage: for a, b, *_ in M:
|
2601
|
+
....: J = G.copy(); J.delete_vertices([a, b])
|
2602
|
+
....: if J.is_matching_covered():
|
2603
|
+
....: L.append(J)
|
2604
|
+
sage: len(L) == len(M)
|
2605
|
+
True
|
2606
|
+
|
2607
|
+
A cycle graph of order six or more is a bipartite matching covered
|
2608
|
+
graph, but is not a brace::
|
2609
|
+
|
2610
|
+
sage: C = graphs.CycleGraph(10)
|
2611
|
+
sage: G = MatchingCoveredGraph(C)
|
2612
|
+
sage: G.is_brace()
|
2613
|
+
False
|
2614
|
+
|
2615
|
+
A ladder graph of order six or more is a bipartite matching covered
|
2616
|
+
graph, that is not a brace. The tight cut decomposition of a ladder
|
2617
|
+
graph produces a list graphs the underlying graph of each of which
|
2618
|
+
is isomorphic to a 4-cycle::
|
2619
|
+
|
2620
|
+
sage: L = graphs.LadderGraph(10)
|
2621
|
+
sage: G = MatchingCoveredGraph(L)
|
2622
|
+
sage: G.is_brace()
|
2623
|
+
False
|
2624
|
+
|
2625
|
+
One may set the ``coNP_certificate`` to be ``True``::
|
2626
|
+
|
2627
|
+
sage: H = graphs.HexahedralGraph()
|
2628
|
+
sage: G = MatchingCoveredGraph(H)
|
2629
|
+
sage: G.is_brace(coNP_certificate=True)
|
2630
|
+
(True, None, None, None, None)
|
2631
|
+
sage: C = graphs.CycleGraph(6)
|
2632
|
+
sage: D = MatchingCoveredGraph(C)
|
2633
|
+
sage: is_brace, nontrivial_tight_cut, nontrivial_odd_component, \
|
2634
|
+
....: nontrivial_tight_cut_variant, cut_identifier = \
|
2635
|
+
....: D.is_brace(coNP_certificate=True)
|
2636
|
+
sage: is_brace is False
|
2637
|
+
True
|
2638
|
+
sage: J = C.subgraph(vertices=nontrivial_odd_component)
|
2639
|
+
sage: J.is_isomorphic(graphs.PathGraph(3))
|
2640
|
+
True
|
2641
|
+
sage: len(nontrivial_tight_cut) == 2
|
2642
|
+
True
|
2643
|
+
sage: nontrivial_tight_cut_variant
|
2644
|
+
'nontrivial barrier cut'
|
2645
|
+
sage: # Corresponding barrier
|
2646
|
+
sage: cut_identifier == {a for u, v, *_ in nontrivial_tight_cut for a in [u, v] \
|
2647
|
+
....: if a not in nontrivial_odd_component}
|
2648
|
+
True
|
2649
|
+
sage: for u, v, *_ in nontrivial_tight_cut:
|
2650
|
+
....: assert (u in nontrivial_odd_component and v not in nontrivial_odd_component)
|
2651
|
+
sage: L = graphs.LadderGraph(3) # A ladder graph with two constituent braces
|
2652
|
+
sage: G = MatchingCoveredGraph(L)
|
2653
|
+
sage: is_brace, nontrivial_tight_cut, nontrivial_odd_component, cut_variant, cut_identifier = \
|
2654
|
+
....: G.is_brace(coNP_certificate=True)
|
2655
|
+
sage: is_brace is False
|
2656
|
+
True
|
2657
|
+
sage: G1 = L.copy()
|
2658
|
+
sage: G1.merge_vertices(list(nontrivial_odd_component))
|
2659
|
+
sage: G1.to_simple().is_isomorphic(graphs.CycleGraph(4))
|
2660
|
+
True
|
2661
|
+
sage: G2 = L.copy()
|
2662
|
+
sage: G2.merge_vertices([v for v in G if v not in nontrivial_odd_component])
|
2663
|
+
sage: G2.to_simple().is_isomorphic(graphs.CycleGraph(4))
|
2664
|
+
True
|
2665
|
+
sage: cut_variant
|
2666
|
+
'nontrivial barrier cut'
|
2667
|
+
sage: cut_identifier == {a for u, v, *_ in nontrivial_tight_cut for a in [u, v] \
|
2668
|
+
....: if a not in nontrivial_odd_component}
|
2669
|
+
True
|
2670
|
+
sage: H = graphs.CompleteBipartiteGraph(3, 3)
|
2671
|
+
sage: H.delete_edge(0, 3)
|
2672
|
+
sage: G = MatchingCoveredGraph(H)
|
2673
|
+
sage: G.is_brace(coNP_certificate=True)
|
2674
|
+
(False,
|
2675
|
+
[(4, 1, None), (5, 1, None), (4, 2, None), (5, 2, None)],
|
2676
|
+
{0, 4, 5},
|
2677
|
+
'nontrivial barrier cut',
|
2678
|
+
{1, 2})
|
2679
|
+
|
2680
|
+
If the input matching covered graph is nonbipartite, a
|
2681
|
+
:exc:`ValueError` is thrown::
|
2682
|
+
|
2683
|
+
sage: K4 = graphs.CompleteGraph(4)
|
2684
|
+
sage: G = MatchingCoveredGraph(K4)
|
2685
|
+
sage: G.is_brace()
|
2686
|
+
Traceback (most recent call last):
|
2687
|
+
...
|
2688
|
+
ValueError: the input graph is not bipartite
|
2689
|
+
sage: P = graphs.PetersenGraph()
|
2690
|
+
sage: H = MatchingCoveredGraph(P)
|
2691
|
+
sage: H.is_brace(coNP_certificate=True)
|
2692
|
+
Traceback (most recent call last):
|
2693
|
+
...
|
2694
|
+
ValueError: the input graph is not bipartite
|
2695
|
+
|
2696
|
+
.. SEEALSO::
|
2697
|
+
|
2698
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.is_brick`
|
2699
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.bricks_and_braces`
|
2700
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_braces`
|
2701
|
+
"""
|
2702
|
+
if not self.is_bipartite():
|
2703
|
+
raise ValueError('the input graph is not bipartite')
|
2704
|
+
|
2705
|
+
if self.order() < 6:
|
2706
|
+
return (True, None, None, None, None) if coNP_certificate else True
|
2707
|
+
|
2708
|
+
A, B = self.bipartite_sets()
|
2709
|
+
matching = set(self.get_matching())
|
2710
|
+
matching_neighbor = {x: y for u, v, *_ in matching for x, y in [(u, v), (v, u)]}
|
2711
|
+
|
2712
|
+
for e in matching:
|
2713
|
+
u, v, *_ = e
|
2714
|
+
|
2715
|
+
# Let G denote the undirected graph self, and
|
2716
|
+
# let the graph H(e) := G — u — v
|
2717
|
+
H = Graph(self, multiedges=False)
|
2718
|
+
H.delete_vertices([u, v])
|
2719
|
+
|
2720
|
+
if not H.is_connected() or not H.is_matching_covered(list(matching - set([e]))):
|
2721
|
+
if not coNP_certificate:
|
2722
|
+
return False
|
2723
|
+
|
2724
|
+
# Construct the digraph D(e)(A ∪ B, F) defined as follows:
|
2725
|
+
from sage.graphs.digraph import DiGraph
|
2726
|
+
D = DiGraph()
|
2727
|
+
|
2728
|
+
# For each edge (a, b) in E(H(e)) ∩ M with a in A, b —> a in D(e).
|
2729
|
+
# For each edge (a, b) in E(H(e)) with a in A, a —> b in D(e).
|
2730
|
+
for a, b in H.edge_iterator(labels=False, sort_vertices=True):
|
2731
|
+
|
2732
|
+
if a in B:
|
2733
|
+
a, b = b, a
|
2734
|
+
|
2735
|
+
D.add_edge((a, b))
|
2736
|
+
if matching_neighbor[a] == b:
|
2737
|
+
D.add_edge((b, a))
|
2738
|
+
|
2739
|
+
# H(e) is matching covered iff D(e) is strongly connected.
|
2740
|
+
# Check if D(e) is strongly connected using Kosaraju's algorithm
|
2741
|
+
def dfs(x, visited, neighbor_iterator):
|
2742
|
+
stack = [x] # a stack of xertices
|
2743
|
+
|
2744
|
+
while stack:
|
2745
|
+
x = stack.pop()
|
2746
|
+
visited.add(x)
|
2747
|
+
|
2748
|
+
for y in neighbor_iterator(x):
|
2749
|
+
if y not in visited:
|
2750
|
+
stack.append(y)
|
2751
|
+
|
2752
|
+
root = next(D.vertex_iterator())
|
2753
|
+
|
2754
|
+
visited_in = set()
|
2755
|
+
dfs(root, visited_in, D.neighbor_in_iterator)
|
2756
|
+
|
2757
|
+
# Since D(e) is not strongly connected, it has a directed cut T(e).
|
2758
|
+
# Note that by definition of D(e), it follows that T(e) ⊆ E(H(e)) — M.
|
2759
|
+
# Thus, T(e) is a cut of H(e), which has a shore X such that every edge of T(e) is
|
2760
|
+
# incident with a vertex in X ∩ B.
|
2761
|
+
|
2762
|
+
# Moreover, M — e is a perfect matching of H(e), and thus, |X ∩ A| = |X ∩ B|
|
2763
|
+
# Consequently, Y := X + v is a shore of a nontrivial tight cut T of G
|
2764
|
+
|
2765
|
+
if len(visited_in) != D.order():
|
2766
|
+
X = visited_in
|
2767
|
+
else:
|
2768
|
+
X = set()
|
2769
|
+
dfs(root, X, D.neighbor_out_iterator)
|
2770
|
+
|
2771
|
+
color_class = None
|
2772
|
+
|
2773
|
+
for a, b in H.edge_iterator(labels=False, sort_vertices=True):
|
2774
|
+
if (a in X) ^ (b in X):
|
2775
|
+
x = a if a in A else b
|
2776
|
+
color_class = x not in X
|
2777
|
+
break
|
2778
|
+
|
2779
|
+
# Obtain the color class Z ∈ {A, B} such that X ∩ Z is a vertex cover for T(e)
|
2780
|
+
# Thus, obtain Y := X + v
|
2781
|
+
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)
|
2782
|
+
|
2783
|
+
# Compute the nontrivial tight cut C := ∂(Y)
|
2784
|
+
C = [(x, y, w) if x in X else (y, x, w)
|
2785
|
+
for x, y, w in self.edge_iterator(sort_vertices=True)
|
2786
|
+
if (x in X) ^ (y in X)]
|
2787
|
+
|
2788
|
+
# Obtain the barrier Z
|
2789
|
+
Z = None
|
2790
|
+
|
2791
|
+
if (u in X and u in A) or (v in X and v in A):
|
2792
|
+
Z = {b for b in B if b not in X}
|
2793
|
+
else:
|
2794
|
+
Z = {a for a in A if a not in X}
|
2795
|
+
|
2796
|
+
return (False, C, set(X), 'nontrivial barrier cut', Z)
|
2797
|
+
|
2798
|
+
return (True, None, None, None, None) if coNP_certificate else True
|
2799
|
+
|
2800
|
+
@doc_index('Bricks, braces and tight cut decomposition')
|
2801
|
+
def is_brick(self, coNP_certificate=False):
|
2802
|
+
r"""
|
2803
|
+
Check if the (matching covered) graph is a brick.
|
2804
|
+
|
2805
|
+
A matching covered graph which is free of nontrivial tight cuts is
|
2806
|
+
called a *brick* if it is nonbipartite. A nonbipartite matching covered
|
2807
|
+
graph is a brick if and only if it is 3-connected and bicritical
|
2808
|
+
[LM2024]_.
|
2809
|
+
|
2810
|
+
INPUT:
|
2811
|
+
|
2812
|
+
- ``coNP_certificate`` -- boolean (default: ``False``)
|
2813
|
+
|
2814
|
+
OUTPUT:
|
2815
|
+
|
2816
|
+
- If the input matching covered graph is bipartite, a :exc:`ValueError`
|
2817
|
+
is returned.
|
2818
|
+
|
2819
|
+
- If the input nonbipartite matching covered graph is a brick, a
|
2820
|
+
boolean ``True`` is returned if ``coNP_certificate`` is set to
|
2821
|
+
``False``, otherwise a 5-tuple ``(True, None, None, None, None)`` is
|
2822
|
+
returned.
|
2823
|
+
|
2824
|
+
- If the input nonbipartite matching covered graph is not a brick, a
|
2825
|
+
boolean ``False`` is returned if ``coNP_certificate`` is set to
|
2826
|
+
``False``.
|
2827
|
+
|
2828
|
+
- If ``coNP_certificate`` is set to ``True`` and the input nonbipartite
|
2829
|
+
graph is not a brick, a 5-tuple of
|
2830
|
+
|
2831
|
+
1. a boolean ``False``,
|
2832
|
+
|
2833
|
+
2. a list of lists of edges, each list constituting a nontrivial
|
2834
|
+
tight cut collectively representing a laminar tight cut,
|
2835
|
+
|
2836
|
+
3. a list of set of vertices of one of the shores of those respective
|
2837
|
+
nontrivial tight cuts:
|
2838
|
+
|
2839
|
+
#. In case of nontrivial barrier cuts, each of the shores is a
|
2840
|
+
nontrivial odd component with respect to a nontrivial barrier,
|
2841
|
+
thus the returned list forms mutually exclusive collection of
|
2842
|
+
(odd) sets.
|
2843
|
+
|
2844
|
+
#. Otherwise each of the nontrivial tight cuts is a 2-separation
|
2845
|
+
cut, each of the shores form a subset sequence, with the
|
2846
|
+
`i` th shore being a proper subset of the `i + 1` th shore.
|
2847
|
+
|
2848
|
+
4. a string showing whether the nontrivial tight cuts are barrier
|
2849
|
+
cuts (if the string is 'nontrivial barrier cut'), or 2-separation
|
2850
|
+
cuts (if the string is 'nontrivial 2-separation cut')
|
2851
|
+
|
2852
|
+
5. a set of vertices showing the respective barrier if the
|
2853
|
+
nontrivial tight cuts are barrier cuts, or otherwise
|
2854
|
+
a set of two vertices constituting the corresponding
|
2855
|
+
two vertex cut (in this case the nontrivial tight cuts are
|
2856
|
+
2-separation cuts)
|
2857
|
+
|
2858
|
+
is returned.
|
2859
|
+
|
2860
|
+
EXAMPLES:
|
2861
|
+
|
2862
|
+
The complete graph on four vertices `K_4` is the smallest brick::
|
2863
|
+
|
2864
|
+
sage: K = graphs.CompleteGraph(4)
|
2865
|
+
sage: G = MatchingCoveredGraph(K)
|
2866
|
+
sage: G.is_brick()
|
2867
|
+
True
|
2868
|
+
|
2869
|
+
The triangular circular ladder (a graph on six vertices), aka
|
2870
|
+
`\overline{C_6}` is a brick::
|
2871
|
+
|
2872
|
+
sage: C6Bar = graphs.CircularLadderGraph(3)
|
2873
|
+
sage: G = MatchingCoveredGraph(C6Bar)
|
2874
|
+
sage: G.is_brick()
|
2875
|
+
True
|
2876
|
+
|
2877
|
+
Each of Petersen graph, Bicorn graph, Tricorn graph, Cubeplex graph,
|
2878
|
+
Twinplex graph, Wagner graph is a brick::
|
2879
|
+
|
2880
|
+
sage: MatchingCoveredGraph(graphs.PetersenGraph()).is_brick() and \
|
2881
|
+
....: MatchingCoveredGraph(graphs.StaircaseGraph(4)).is_brick() and \
|
2882
|
+
....: MatchingCoveredGraph(graphs.TricornGraph()).is_brick() and \
|
2883
|
+
....: MatchingCoveredGraph(graphs.CubeplexGraph()).is_brick() and \
|
2884
|
+
....: MatchingCoveredGraph(graphs.TwinplexGraph()).is_brick() and \
|
2885
|
+
....: MatchingCoveredGraph(graphs.WagnerGraph()).is_brick()
|
2886
|
+
True
|
2887
|
+
|
2888
|
+
The Murty graph is the smallest simple brick that is not odd-intercyclic::
|
2889
|
+
|
2890
|
+
sage: M = graphs.MurtyGraph()
|
2891
|
+
sage: G = MatchingCoveredGraph(M)
|
2892
|
+
sage: G.is_brick()
|
2893
|
+
True
|
2894
|
+
|
2895
|
+
A circular ladder graph of order six or more on `2n` vertices for an
|
2896
|
+
odd `n` is a brick::
|
2897
|
+
|
2898
|
+
sage: n = 11
|
2899
|
+
sage: CL = graphs.CircularLadderGraph(n)
|
2900
|
+
sage: G = MatchingCoveredGraph(CL)
|
2901
|
+
sage: G.is_brick()
|
2902
|
+
True
|
2903
|
+
|
2904
|
+
A moebius ladder graph of order eight or more on `2n` vertices for an
|
2905
|
+
even `n` is a brick::
|
2906
|
+
|
2907
|
+
sage: n = 10
|
2908
|
+
sage: ML = graphs.MoebiusLadderGraph(n)
|
2909
|
+
sage: G = MatchingCoveredGraph(ML)
|
2910
|
+
sage: G.is_brick()
|
2911
|
+
True
|
2912
|
+
|
2913
|
+
A wheel graph of an even order is a brick::
|
2914
|
+
|
2915
|
+
sage: W = graphs.WheelGraph(10)
|
2916
|
+
sage: G = MatchingCoveredGraph(W)
|
2917
|
+
sage: G.is_brick()
|
2918
|
+
True
|
2919
|
+
|
2920
|
+
A graph that is isomorphic to a truncated biwheel graph is a brick::
|
2921
|
+
|
2922
|
+
sage: TB = graphs.TruncatedBiwheelGraph(15)
|
2923
|
+
sage: G = MatchingCoveredGraph(TB)
|
2924
|
+
sage: G.is_brick()
|
2925
|
+
True
|
2926
|
+
|
2927
|
+
Each of the graphs in the staircase graph family with order eight or
|
2928
|
+
more is a brick::
|
2929
|
+
|
2930
|
+
sage: ST = graphs.StaircaseGraph(9)
|
2931
|
+
sage: G = MatchingCoveredGraph(ST)
|
2932
|
+
sage: G.is_brick()
|
2933
|
+
True
|
2934
|
+
|
2935
|
+
Bricks are 3-connected::
|
2936
|
+
|
2937
|
+
sage: P = graphs.PetersenGraph()
|
2938
|
+
sage: G = MatchingCoveredGraph(P)
|
2939
|
+
sage: G.is_brick()
|
2940
|
+
True
|
2941
|
+
sage: G.is_triconnected()
|
2942
|
+
True
|
2943
|
+
|
2944
|
+
Bricks are bicritical::
|
2945
|
+
|
2946
|
+
sage: P = graphs.PetersenGraph()
|
2947
|
+
sage: G = MatchingCoveredGraph(P)
|
2948
|
+
sage: G.is_brick()
|
2949
|
+
True
|
2950
|
+
sage: G.is_bicritical()
|
2951
|
+
True
|
2952
|
+
|
2953
|
+
Examples of nonbipartite matching covered graphs that are not
|
2954
|
+
bricks::
|
2955
|
+
|
2956
|
+
sage: H = Graph([
|
2957
|
+
....: (0, 3), (0, 4), (0, 7),
|
2958
|
+
....: (1, 3), (1, 5), (1, 7),
|
2959
|
+
....: (2, 3), (2, 6), (2, 7),
|
2960
|
+
....: (4, 5), (4, 6), (5, 6)
|
2961
|
+
....: ])
|
2962
|
+
sage: G = MatchingCoveredGraph(H)
|
2963
|
+
sage: G.is_bipartite()
|
2964
|
+
False
|
2965
|
+
sage: G.is_bicritical()
|
2966
|
+
False
|
2967
|
+
sage: G.is_triconnected()
|
2968
|
+
True
|
2969
|
+
sage: G.is_brick()
|
2970
|
+
False
|
2971
|
+
sage: H = Graph([
|
2972
|
+
....: (0, 1), (0, 2), (0, 3), (0, 4), (1, 2),
|
2973
|
+
....: (1, 5), (2, 5), (3, 4), (3, 5), (4, 5)
|
2974
|
+
....: ])
|
2975
|
+
sage: G = MatchingCoveredGraph(H)
|
2976
|
+
sage: G.is_bipartite()
|
2977
|
+
False
|
2978
|
+
sage: G.is_bicritical()
|
2979
|
+
True
|
2980
|
+
sage: G.is_triconnected()
|
2981
|
+
False
|
2982
|
+
sage: G.is_brick()
|
2983
|
+
False
|
2984
|
+
|
2985
|
+
One may set the ``coNP_certificate`` to be ``True``::
|
2986
|
+
|
2987
|
+
sage: K4 = graphs.CompleteGraph(4)
|
2988
|
+
sage: G = MatchingCoveredGraph(K4)
|
2989
|
+
sage: G.is_brick(coNP_certificate=True)
|
2990
|
+
(True, None, None, None, None)
|
2991
|
+
sage: # K(4) ⊙ K(3, 3) is nonbipartite but not a brick
|
2992
|
+
sage: H = graphs.MurtyGraph(); H.delete_edge(0, 1)
|
2993
|
+
sage: G = MatchingCoveredGraph(H)
|
2994
|
+
sage: G.is_brick(coNP_certificate=True)
|
2995
|
+
(False, [[(5, 2, None), (6, 3, None), (7, 4, None)]], [{5, 6, 7}],
|
2996
|
+
'nontrivial barrier cut', {2, 3, 4})
|
2997
|
+
sage: H = Graph([
|
2998
|
+
....: (0, 12), (0, 13), (0, 15), (1, 4), (1, 13), (1, 14),
|
2999
|
+
....: (1, 19), (2, 4), (2, 13), (2, 14), (2, 17), (3, 9),
|
3000
|
+
....: (3, 13), (3, 16), (3, 21), (4, 6), (4, 7), (5, 7),
|
3001
|
+
....: (5, 8), (5, 12), (6, 8), (6, 11), (7, 10), (8, 9),
|
3002
|
+
....: (9, 10), (10, 11), (11, 12), (14, 15), (14, 16), (15, 16),
|
3003
|
+
....: (17, 18), (17, 21), (18, 19), (18, 20), (19, 20), (20, 21)
|
3004
|
+
....: ])
|
3005
|
+
sage: G = MatchingCoveredGraph(H)
|
3006
|
+
sage: G.is_brick(coNP_certificate=True)
|
3007
|
+
(False,
|
3008
|
+
[[(12, 0, None), (4, 1, None), (4, 2, None), (9, 3, None)],
|
3009
|
+
[(19, 1, None), (17, 2, None), (21, 3, None)],
|
3010
|
+
[(15, 0, None), (14, 1, None), (14, 2, None), (16, 3, None)]],
|
3011
|
+
[{4, 5, 6, 7, 8, 9, 10, 11, 12}, {17, 18, 19, 20, 21}, {14, 15, 16}],
|
3012
|
+
'nontrivial barrier cut', {0, 1, 2, 3})
|
3013
|
+
sage: J = Graph([
|
3014
|
+
....: (0, 1), (0, 2), (0, 3), (0, 4), (0, 5),
|
3015
|
+
....: (0, 6), (0, 7), (0, 8), (0, 9), (0, 10),
|
3016
|
+
....: (1, 2), (1, 11), (2, 11), (3, 4), (3, 11),
|
3017
|
+
....: (4, 11), (5, 6), (5, 11), (6, 11), (7, 8),
|
3018
|
+
....: (7, 11), (8, 11), (9, 10), (9, 11), (10, 11)
|
3019
|
+
....: ])
|
3020
|
+
sage: G = MatchingCoveredGraph(J)
|
3021
|
+
sage: G.is_brick(coNP_certificate=True)
|
3022
|
+
(False,
|
3023
|
+
[[(0, 3, None),
|
3024
|
+
(0, 4, None),
|
3025
|
+
(0, 5, None),
|
3026
|
+
(0, 6, None),
|
3027
|
+
(0, 7, None),
|
3028
|
+
(0, 8, None),
|
3029
|
+
(0, 9, None),
|
3030
|
+
(0, 10, None),
|
3031
|
+
(1, 11, None),
|
3032
|
+
(2, 11, None)],
|
3033
|
+
[(0, 5, None),
|
3034
|
+
(0, 6, None),
|
3035
|
+
(0, 7, None),
|
3036
|
+
(0, 8, None),
|
3037
|
+
(0, 9, None),
|
3038
|
+
(0, 10, None),
|
3039
|
+
(1, 11, None),
|
3040
|
+
(2, 11, None),
|
3041
|
+
(3, 11, None),
|
3042
|
+
(4, 11, None)],
|
3043
|
+
[(0, 7, None),
|
3044
|
+
(0, 8, None),
|
3045
|
+
(0, 9, None),
|
3046
|
+
(0, 10, None),
|
3047
|
+
(1, 11, None),
|
3048
|
+
(2, 11, None),
|
3049
|
+
(3, 11, None),
|
3050
|
+
(4, 11, None),
|
3051
|
+
(5, 11, None),
|
3052
|
+
(6, 11, None)],
|
3053
|
+
[(0, 9, None),
|
3054
|
+
(0, 10, None),
|
3055
|
+
(1, 11, None),
|
3056
|
+
(2, 11, None),
|
3057
|
+
(3, 11, None),
|
3058
|
+
(4, 11, None),
|
3059
|
+
(5, 11, None),
|
3060
|
+
(6, 11, None),
|
3061
|
+
(7, 11, None),
|
3062
|
+
(8, 11, None)]],
|
3063
|
+
[{0, 1, 2},
|
3064
|
+
{0, 1, 2, 3, 4},
|
3065
|
+
{0, 1, 2, 3, 4, 5, 6},
|
3066
|
+
{0, 1, 2, 3, 4, 5, 6, 7, 8}],
|
3067
|
+
'nontrivial 2-separation cut',
|
3068
|
+
{0, 11})
|
3069
|
+
|
3070
|
+
If the input matching covered graph is bipartite, a
|
3071
|
+
:exc:`ValueError` is thrown::
|
3072
|
+
|
3073
|
+
sage: H = graphs.HexahedralGraph()
|
3074
|
+
sage: G = MatchingCoveredGraph(H)
|
3075
|
+
sage: G.is_brick()
|
3076
|
+
Traceback (most recent call last):
|
3077
|
+
...
|
3078
|
+
ValueError: the input graph is bipartite
|
3079
|
+
sage: J = graphs.HeawoodGraph()
|
3080
|
+
sage: G = MatchingCoveredGraph(J)
|
3081
|
+
sage: G.is_brick(coNP_certificate=True)
|
3082
|
+
Traceback (most recent call last):
|
3083
|
+
...
|
3084
|
+
ValueError: the input graph is bipartite
|
3085
|
+
|
3086
|
+
.. SEEALSO::
|
3087
|
+
|
3088
|
+
- :meth:`~sage.graphs.graph.Graph.is_bicritical`
|
3089
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.is_brace`
|
3090
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.bricks_and_braces`
|
3091
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_bricks`
|
3092
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_petersen_bricks`
|
3093
|
+
"""
|
3094
|
+
if self.is_bipartite():
|
3095
|
+
raise ValueError('the input graph is bipartite')
|
3096
|
+
|
3097
|
+
# Check if G is bicritical
|
3098
|
+
bicritical, certificate = self.is_bicritical(coNP_certificate=True)
|
3099
|
+
|
3100
|
+
if not bicritical:
|
3101
|
+
if not coNP_certificate:
|
3102
|
+
return False
|
3103
|
+
|
3104
|
+
# G has a pair of vertices u, v such that G - u - v is not matching
|
3105
|
+
# covered, thus has a nontrivial barrier B containing both u and v.
|
3106
|
+
u, _ = certificate
|
3107
|
+
B = self.maximal_barrier(u)
|
3108
|
+
|
3109
|
+
H = Graph(self)
|
3110
|
+
H.delete_vertices(B)
|
3111
|
+
|
3112
|
+
# Let K be a nontrivial odd component of H := G - B. Note that
|
3113
|
+
# there exists at least one such K since G is nonbipartite
|
3114
|
+
nontrivial_odd_components = [
|
3115
|
+
set(component) for component in H.connected_components(sort=True)
|
3116
|
+
if len(component) % 2 and len(component) > 1
|
3117
|
+
]
|
3118
|
+
|
3119
|
+
# Find a laminar set of nontrivial barrier cuts
|
3120
|
+
C = [[(u, v, w) if u in nontrivial_odd_component else (v, u, w)
|
3121
|
+
for u, v, w in self.edge_iterator()
|
3122
|
+
if (u in nontrivial_odd_component) ^ (v in nontrivial_odd_component)]
|
3123
|
+
for nontrivial_odd_component in nontrivial_odd_components]
|
3124
|
+
|
3125
|
+
return (False, C, nontrivial_odd_components, 'nontrivial barrier cut', B)
|
3126
|
+
|
3127
|
+
# Check if G is 3-connected
|
3128
|
+
if self.is_triconnected():
|
3129
|
+
return (True, None, None, None, None) if coNP_certificate else True
|
3130
|
+
|
3131
|
+
# G has a 2-vertex cut
|
3132
|
+
# Compute the SPQR-tree decomposition
|
3133
|
+
spqr_tree = self.spqr_tree()
|
3134
|
+
two_vertex_cut = []
|
3135
|
+
|
3136
|
+
# Check for 2-vertex cuts in a P node
|
3137
|
+
# Since the graph is matching covered, it is free of cut vertices
|
3138
|
+
# It can be shown using counting arguments that the spqr tree
|
3139
|
+
# decomposition for a bicritical graph, that is 2-connected but not
|
3140
|
+
# 3-connected, is free of 'S' nodes
|
3141
|
+
for u in spqr_tree:
|
3142
|
+
if u[0] == 'P':
|
3143
|
+
two_vertex_cut.extend(u[1])
|
3144
|
+
break
|
3145
|
+
|
3146
|
+
# If no 2-vertex cut found, look for R nodes
|
3147
|
+
if not two_vertex_cut:
|
3148
|
+
from collections import Counter
|
3149
|
+
R_frequency = Counter()
|
3150
|
+
|
3151
|
+
for t, g in spqr_tree:
|
3152
|
+
if t == 'R':
|
3153
|
+
R_frequency.update(g)
|
3154
|
+
|
3155
|
+
# R frequency must be at least 2,
|
3156
|
+
# since the graph is 2-connected but not 3-connected
|
3157
|
+
two_vertex_cut = [u for u, f in R_frequency.items() if f >= 2][:2]
|
3158
|
+
|
3159
|
+
# We obtain a 2-vertex cut (u, v)
|
3160
|
+
H = Graph(self)
|
3161
|
+
H.delete_vertices(two_vertex_cut)
|
3162
|
+
|
3163
|
+
# Check if all components of H are odd
|
3164
|
+
components = H.connected_components(sort=True)
|
3165
|
+
|
3166
|
+
# Find a nontrivial odd component
|
3167
|
+
nontrivial_tight_cut_variation = 'nontrivial 2-separation cut'
|
3168
|
+
nontrivial_odd_components = []
|
3169
|
+
|
3170
|
+
for index, component in enumerate(components):
|
3171
|
+
if index == len(components) - 1:
|
3172
|
+
continue
|
3173
|
+
elif not index:
|
3174
|
+
nontrivial_odd_components.append(set(components[0] + [two_vertex_cut[0]]))
|
3175
|
+
else:
|
3176
|
+
nontrivial_odd_component = nontrivial_odd_components[-1].copy()
|
3177
|
+
nontrivial_odd_component.update(component)
|
3178
|
+
nontrivial_odd_components.append(nontrivial_odd_component)
|
3179
|
+
|
3180
|
+
C = [[(u, v, w) if u in nontrivial_odd_component else (v, u, w)
|
3181
|
+
for u, v, w in self.edge_iterator()
|
3182
|
+
if (u in nontrivial_odd_component) ^ (v in nontrivial_odd_component)]
|
3183
|
+
for nontrivial_odd_component in nontrivial_odd_components]
|
3184
|
+
|
3185
|
+
# Edge (u, v, w) in C are formatted so that u is in a nontrivial odd component
|
3186
|
+
return (False, C, nontrivial_odd_components, nontrivial_tight_cut_variation, set(two_vertex_cut)) if coNP_certificate else False
|
3187
|
+
|
3188
|
+
@doc_index('Overwritten methods')
|
3189
|
+
def loop_edges(self, labels=True):
|
3190
|
+
r"""
|
3191
|
+
Return a list of all loops in the (matching covered) graph.
|
3192
|
+
|
3193
|
+
.. NOTE::
|
3194
|
+
|
3195
|
+
This method overwrites the
|
3196
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.loop_edges` method
|
3197
|
+
in order to return an empty list as matching covered graphs are
|
3198
|
+
free of looped edges.
|
3199
|
+
|
3200
|
+
INPUT:
|
3201
|
+
|
3202
|
+
- ``labels`` -- boolean (default: ``True``); whether returned edges
|
3203
|
+
have labels (``(u,v,l)``) or not (``(u,v)``).
|
3204
|
+
|
3205
|
+
OUTPUT:
|
3206
|
+
|
3207
|
+
- A list capturing the edges that are loops in the matching covered
|
3208
|
+
graph; note that, the list is empty since matching covered graphs do
|
3209
|
+
not contain any looped edges.
|
3210
|
+
|
3211
|
+
EXAMPLES:
|
3212
|
+
|
3213
|
+
A matching covered graph, for instance the Heawood graph, by
|
3214
|
+
definition, is always free of loops::
|
3215
|
+
|
3216
|
+
sage: H = graphs.HeawoodGraph()
|
3217
|
+
sage: G = MatchingCoveredGraph(H)
|
3218
|
+
sage: G
|
3219
|
+
Matching covered heawood graph: graph on 14 vertices
|
3220
|
+
sage: G.add_edge(0, 0)
|
3221
|
+
Traceback (most recent call last):
|
3222
|
+
...
|
3223
|
+
ValueError: loops are not allowed in matching covered graphs
|
3224
|
+
sage: G.loops()
|
3225
|
+
[]
|
3226
|
+
sage: G.loop_edges()
|
3227
|
+
[]
|
3228
|
+
|
3229
|
+
A matching covered graph may support multiple edges, still no
|
3230
|
+
loops are allowed::
|
3231
|
+
|
3232
|
+
sage: C = graphs.CycleGraph(4)
|
3233
|
+
sage: G = MatchingCoveredGraph(C)
|
3234
|
+
sage: G.allow_multiple_edges(True)
|
3235
|
+
sage: G
|
3236
|
+
Matching covered cycle graph: multi-graph on 4 vertices
|
3237
|
+
sage: G.add_edge(0, 1, 'label')
|
3238
|
+
sage: G.add_edge(0, 0)
|
3239
|
+
Traceback (most recent call last):
|
3240
|
+
...
|
3241
|
+
ValueError: loops are not allowed in matching covered graphs
|
3242
|
+
sage: G.edges(sort=False)
|
3243
|
+
[(0, 1, None), (0, 1, 'label'), (0, 3, None), (1, 2, None), (2, 3, None)]
|
3244
|
+
sage: G.loops()
|
3245
|
+
[]
|
3246
|
+
sage: G.loop_edges()
|
3247
|
+
[]
|
3248
|
+
|
3249
|
+
One may set the ``label`` to either ``True`` or ``False``::
|
3250
|
+
|
3251
|
+
sage: G.loop_edges(labels=False)
|
3252
|
+
[]
|
3253
|
+
sage: G.loops(labels=True)
|
3254
|
+
[]
|
3255
|
+
|
3256
|
+
.. SEEALSO::
|
3257
|
+
|
3258
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops`
|
3259
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops`
|
3260
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops`
|
3261
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices`
|
3262
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops`
|
3263
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops`
|
3264
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops`
|
3265
|
+
"""
|
3266
|
+
return []
|
3267
|
+
|
3268
|
+
@doc_index('Overwritten methods')
|
3269
|
+
def loop_vertices(self):
|
3270
|
+
r"""
|
3271
|
+
Return a list of vertices with loops.
|
3272
|
+
|
3273
|
+
.. NOTE::
|
3274
|
+
|
3275
|
+
This method overwrites the
|
3276
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.loop_vertices`
|
3277
|
+
method in order to return an empty list as matching covered graphs
|
3278
|
+
are free of vertices that have looped edges.
|
3279
|
+
|
3280
|
+
OUTPUT:
|
3281
|
+
|
3282
|
+
- A list capturing the vertices that have loops in the matching covered
|
3283
|
+
graph; note that, the list is empty since matching covered graphs do
|
3284
|
+
not contain any looped edges.
|
3285
|
+
|
3286
|
+
EXAMPLES:
|
3287
|
+
|
3288
|
+
A matching covered graph, for instance the Möbius graph of order 8, by
|
3289
|
+
definition, is always free of loops::
|
3290
|
+
|
3291
|
+
sage: M = graphs.MoebiusLadderGraph(4)
|
3292
|
+
sage: G = MatchingCoveredGraph(M)
|
3293
|
+
sage: G
|
3294
|
+
Matching covered moebius ladder graph: graph on 8 vertices
|
3295
|
+
sage: G.add_edge(0, 0)
|
3296
|
+
Traceback (most recent call last):
|
3297
|
+
...
|
3298
|
+
ValueError: loops are not allowed in matching covered graphs
|
3299
|
+
sage: G.loop_vertices()
|
3300
|
+
[]
|
3301
|
+
|
3302
|
+
A matching covered graph may support multiple edges, still no
|
3303
|
+
loops are allowed::
|
3304
|
+
|
3305
|
+
sage: S = graphs.StaircaseGraph(4)
|
3306
|
+
sage: G = MatchingCoveredGraph(S)
|
3307
|
+
sage: G.allow_multiple_edges(True)
|
3308
|
+
sage: G
|
3309
|
+
Matching covered staircase graph: multi-graph on 8 vertices
|
3310
|
+
sage: G.add_edge(0, 1, 'label')
|
3311
|
+
sage: G.add_edge(0, 0)
|
3312
|
+
Traceback (most recent call last):
|
3313
|
+
...
|
3314
|
+
ValueError: loops are not allowed in matching covered graphs
|
3315
|
+
sage: G.edges(sort=False)
|
3316
|
+
[(0, 1, None), (0, 1, 'label'), (0, 3, None), (0, 6, None),
|
3317
|
+
(1, 2, None), (1, 4, None), (2, 5, None), (2, 7, None),
|
3318
|
+
(3, 4, None), (3, 6, None), (4, 5, None), (5, 7, None),
|
3319
|
+
(6, 7, None)]
|
3320
|
+
sage: G.loop_vertices()
|
3321
|
+
[]
|
3322
|
+
|
3323
|
+
.. SEEALSO::
|
3324
|
+
|
3325
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops`
|
3326
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops`
|
3327
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops`
|
3328
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges`
|
3329
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops`
|
3330
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops`
|
3331
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops`
|
3332
|
+
"""
|
3333
|
+
return []
|
3334
|
+
|
3335
|
+
loops = loop_edges
|
3336
|
+
|
3337
|
+
@doc_index('Overwritten methods')
|
3338
|
+
def number_of_loops(self):
|
3339
|
+
r"""
|
3340
|
+
Return the number of edges that are loops.
|
3341
|
+
|
3342
|
+
.. NOTE::
|
3343
|
+
|
3344
|
+
This method overwrites the
|
3345
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.number_of_loops`
|
3346
|
+
method in order to return 0 as matching covered graphs are free
|
3347
|
+
of looped edges.
|
3348
|
+
|
3349
|
+
OUTPUT:
|
3350
|
+
|
3351
|
+
- An integer, 0 is returned, since matching covered graphs do not
|
3352
|
+
contain zero loops.
|
3353
|
+
|
3354
|
+
EXAMPLES:
|
3355
|
+
|
3356
|
+
A matching covered graph, for instance the Truncated biwheel graph,
|
3357
|
+
by definition, is always free of loops::
|
3358
|
+
|
3359
|
+
sage: T = graphs.TruncatedBiwheelGraph(5)
|
3360
|
+
sage: G = MatchingCoveredGraph(T)
|
3361
|
+
sage: G
|
3362
|
+
Matching covered truncated biwheel graph: graph on 10 vertices
|
3363
|
+
sage: G.add_edge(0, 0)
|
3364
|
+
Traceback (most recent call last):
|
3365
|
+
...
|
3366
|
+
ValueError: loops are not allowed in matching covered graphs
|
3367
|
+
sage: G.loop_vertices()
|
3368
|
+
[]
|
3369
|
+
sage: G.number_of_loops()
|
3370
|
+
0
|
3371
|
+
|
3372
|
+
A matching covered graph may support multiple edges, still no
|
3373
|
+
loops are allowed::
|
3374
|
+
|
3375
|
+
sage: B = graphs.BiwheelGraph(4)
|
3376
|
+
sage: G = MatchingCoveredGraph(B)
|
3377
|
+
sage: G.allow_multiple_edges(True)
|
3378
|
+
sage: G
|
3379
|
+
Matching covered biwheel graph: multi-graph on 8 vertices
|
3380
|
+
sage: G.add_edge(0, 1, 'label')
|
3381
|
+
sage: G.add_edge(0, 0)
|
3382
|
+
Traceback (most recent call last):
|
3383
|
+
...
|
3384
|
+
ValueError: loops are not allowed in matching covered graphs
|
3385
|
+
sage: G.edges(sort=False)
|
3386
|
+
[(0, 1, None), (0, 1, 'label'), (0, 5, None), (0, 7, None),
|
3387
|
+
(1, 2, None), (1, 6, None), (2, 3, None), (2, 7, None),
|
3388
|
+
(3, 4, None), (3, 6, None), (4, 5, None), (4, 7, None),
|
3389
|
+
(5, 6, None)]
|
3390
|
+
sage: G.loop_vertices()
|
3391
|
+
[]
|
3392
|
+
sage: G.number_of_loops()
|
3393
|
+
0
|
3394
|
+
|
3395
|
+
.. SEEALSO::
|
3396
|
+
|
3397
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops`
|
3398
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops`
|
3399
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops`
|
3400
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges`
|
3401
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices`
|
3402
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops`
|
3403
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.remove_loops`
|
3404
|
+
"""
|
3405
|
+
return 0
|
3406
|
+
|
3407
|
+
@doc_index('Overwritten methods')
|
3408
|
+
def remove_loops(self, vertices=None):
|
3409
|
+
r"""
|
3410
|
+
Remove loops on vertices in ``vertices``.
|
3411
|
+
|
3412
|
+
.. NOTE::
|
3413
|
+
|
3414
|
+
This method overwrites the
|
3415
|
+
:meth:`~sage.graphs.generic_graph.GenericGraph.remove_loops` method
|
3416
|
+
in order to return without any alteration as matching covered
|
3417
|
+
graphs are free of looped edges.
|
3418
|
+
|
3419
|
+
INPUT:
|
3420
|
+
|
3421
|
+
- ``vertices`` -- (default: ``None``) iterator container of vertex
|
3422
|
+
labels corresponding to which the looped edges are to be removed. If
|
3423
|
+
``vertices`` is ``None``, remove all loops.
|
3424
|
+
|
3425
|
+
OUTPUT:
|
3426
|
+
|
3427
|
+
- Nothing is returned, as a matching covered graph is already devoid of
|
3428
|
+
any loops.
|
3429
|
+
|
3430
|
+
EXAMPLES:
|
3431
|
+
|
3432
|
+
A matching covered graph, for instance the Wheel graph of order six, is
|
3433
|
+
always free of loops::
|
3434
|
+
|
3435
|
+
sage: W = graphs.WheelGraph(6)
|
3436
|
+
sage: G = MatchingCoveredGraph(W)
|
3437
|
+
sage: G
|
3438
|
+
Matching covered wheel graph: graph on 6 vertices
|
3439
|
+
sage: G.add_edge(0, 0)
|
3440
|
+
Traceback (most recent call last):
|
3441
|
+
...
|
3442
|
+
ValueError: loops are not allowed in matching covered graphs
|
3443
|
+
sage: G.remove_loops()
|
3444
|
+
sage: G.edges(sort=True)
|
3445
|
+
[(0, 1, None), (0, 2, None), (0, 3, None), (0, 4, None),
|
3446
|
+
(0, 5, None), (1, 2, None), (1, 5, None), (2, 3, None),
|
3447
|
+
(3, 4, None), (4, 5, None)]
|
3448
|
+
|
3449
|
+
A matching covered graph may support multiple edges, still no
|
3450
|
+
loops are allowed::
|
3451
|
+
|
3452
|
+
sage: K = graphs.CompleteGraph(2)
|
3453
|
+
sage: G = MatchingCoveredGraph(K)
|
3454
|
+
sage: G.allow_multiple_edges(True)
|
3455
|
+
sage: G
|
3456
|
+
Matching covered complete graph: multi-graph on 2 vertices
|
3457
|
+
sage: G.add_edge(0, 1, 'label')
|
3458
|
+
sage: G.add_edge(0, 0)
|
3459
|
+
Traceback (most recent call last):
|
3460
|
+
...
|
3461
|
+
ValueError: loops are not allowed in matching covered graphs
|
3462
|
+
sage: G.edges(sort=False)
|
3463
|
+
[(0, 1, None), (0, 1, 'label')]
|
3464
|
+
sage: G.remove_loops(vertices=[0, 1])
|
3465
|
+
sage: G.edges(sort=False)
|
3466
|
+
[(0, 1, None), (0, 1, 'label')]
|
3467
|
+
sage: G.remove_loops(vertices=[0..100])
|
3468
|
+
|
3469
|
+
Note that the parameter ``vertices`` must be either ``None`` or an
|
3470
|
+
iterable::
|
3471
|
+
|
3472
|
+
sage: G.remove_loops(vertices='')
|
3473
|
+
sage: G.edges(sort=False)
|
3474
|
+
[(0, 1, None), (0, 1, 'label')]
|
3475
|
+
sage: G.remove_loops(vertices=None)
|
3476
|
+
sage: G.edges(sort=False)
|
3477
|
+
[(0, 1, None), (0, 1, 'label')]
|
3478
|
+
sage: G.remove_loops(vertices=0)
|
3479
|
+
Traceback (most recent call last):
|
3480
|
+
...
|
3481
|
+
TypeError: 'Integer' object is not iterable
|
3482
|
+
sage: G.remove_loops(vertices=False)
|
3483
|
+
Traceback (most recent call last):
|
3484
|
+
...
|
3485
|
+
TypeError: 'bool' object is not iterable
|
3486
|
+
|
3487
|
+
.. SEEALSO::
|
3488
|
+
|
3489
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allow_loops`
|
3490
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.allows_loops`
|
3491
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.has_loops`
|
3492
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_edges`
|
3493
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loop_vertices`
|
3494
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.loops`
|
3495
|
+
- :meth:`~sage.graphs.matching_covered_graph.MatchingCoveredGraph.number_of_loops`
|
3496
|
+
"""
|
3497
|
+
from collections.abc import Iterable
|
3498
|
+
|
3499
|
+
if vertices is not None and not isinstance(vertices, Iterable):
|
3500
|
+
raise TypeError(f'\'{vertices.__class__.__name__}\' '
|
3501
|
+
'object is not iterable')
|
3502
|
+
|
3503
|
+
@doc_index('Miscellaneous methods')
|
3504
|
+
def update_matching(self, matching):
|
3505
|
+
r"""
|
3506
|
+
Update the perfect matching captured in ``self._matching``.
|
3507
|
+
|
3508
|
+
INPUT:
|
3509
|
+
|
3510
|
+
- ``matching`` -- a perfect matching of the graph, that can be given
|
3511
|
+
using any valid input format of :class:`~sage.graphs.graph.Graph`.
|
3512
|
+
|
3513
|
+
OUTPUT:
|
3514
|
+
|
3515
|
+
- If ``matching`` is a valid perfect matching of the graph, then
|
3516
|
+
``self._matching`` gets updated to this provided matching, or
|
3517
|
+
otherwise an exception is returned without any alterations to
|
3518
|
+
``self._matching``.
|
3519
|
+
|
3520
|
+
EXAMPLES:
|
3521
|
+
|
3522
|
+
Providing with a valid perfect matching of the graph::
|
3523
|
+
|
3524
|
+
sage: P = graphs.PetersenGraph()
|
3525
|
+
sage: G = MatchingCoveredGraph(P)
|
3526
|
+
sage: sorted(G.get_matching())
|
3527
|
+
[(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)]
|
3528
|
+
sage: M = [(0, 1), (2, 3), (4, 9), (5, 7), (6, 8)]
|
3529
|
+
sage: G.update_matching(M)
|
3530
|
+
sage: sorted(G.get_matching())
|
3531
|
+
[(0, 1, None), (2, 3, None), (4, 9, None), (5, 7, None), (6, 8, None)]
|
3532
|
+
|
3533
|
+
TESTS:
|
3534
|
+
|
3535
|
+
Providing with a wrong matching::
|
3536
|
+
|
3537
|
+
sage: P = graphs.PetersenGraph()
|
3538
|
+
sage: G = MatchingCoveredGraph(P)
|
3539
|
+
sage: sorted(G.get_matching())
|
3540
|
+
[(0, 5, None), (1, 6, None), (2, 7, None), (3, 8, None), (4, 9, None)]
|
3541
|
+
sage: S = str('0')
|
3542
|
+
sage: G.update_matching(S)
|
3543
|
+
Traceback (most recent call last):
|
3544
|
+
...
|
3545
|
+
RuntimeError: the string seems corrupt: valid characters are
|
3546
|
+
?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
|
3547
|
+
sage: T = str('graph')
|
3548
|
+
sage: G.update_matching(T)
|
3549
|
+
Traceback (most recent call last):
|
3550
|
+
...
|
3551
|
+
RuntimeError: the string (graph) seems corrupt: for n = 40,
|
3552
|
+
the string is too short
|
3553
|
+
sage: M = Graph(G.matching())
|
3554
|
+
sage: M.add_edges([(0, 1), (0, 2)])
|
3555
|
+
sage: G.update_matching(M)
|
3556
|
+
Traceback (most recent call last):
|
3557
|
+
...
|
3558
|
+
ValueError: the input is not a matching
|
3559
|
+
sage: N = Graph(G.matching())
|
3560
|
+
sage: N.add_edge(10, 11)
|
3561
|
+
sage: G.update_matching(N)
|
3562
|
+
Traceback (most recent call last):
|
3563
|
+
...
|
3564
|
+
ValueError: the input is not a matching of the graph
|
3565
|
+
sage: J = Graph()
|
3566
|
+
sage: J.add_edges([(0, 1), (2, 3)])
|
3567
|
+
sage: G.update_matching(J)
|
3568
|
+
Traceback (most recent call last):
|
3569
|
+
...
|
3570
|
+
ValueError: the input is not a perfect matching of the graph
|
3571
|
+
"""
|
3572
|
+
try:
|
3573
|
+
M = Graph(matching)
|
3574
|
+
|
3575
|
+
if any(d != 1 for d in M.degree()):
|
3576
|
+
raise ValueError("the input is not a matching")
|
3577
|
+
|
3578
|
+
if any(not self.has_edge(edge) for edge in M.edge_iterator()):
|
3579
|
+
raise ValueError("the input is not a matching of the graph")
|
3580
|
+
|
3581
|
+
if (self.order() != M.order()):
|
3582
|
+
raise ValueError("the input is not a perfect matching of the graph")
|
3583
|
+
|
3584
|
+
self._matching = M.edges()
|
3585
|
+
|
3586
|
+
except Exception as exception:
|
3587
|
+
raise exception
|
3588
|
+
|
3589
|
+
|
3590
|
+
__doc__ = __doc__.replace('{INDEX_OF_METHODS}', gen_thematic_rest_table_index(MatchingCoveredGraph, only_local_functions=False))
|