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.
Files changed (260) hide show
  1. passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
  2. passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
  3. passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
  5. passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
  6. passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
  7. passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
  8. sage/all__sagemath_graphs.py +39 -0
  9. sage/combinat/abstract_tree.py +2723 -0
  10. sage/combinat/all__sagemath_graphs.py +34 -0
  11. sage/combinat/binary_tree.py +5306 -0
  12. sage/combinat/cluster_algebra_quiver/all.py +22 -0
  13. sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
  14. sage/combinat/cluster_algebra_quiver/interact.py +124 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
  18. sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
  19. sage/combinat/designs/MOLS_handbook_data.py +570 -0
  20. sage/combinat/designs/all.py +58 -0
  21. sage/combinat/designs/bibd.py +1655 -0
  22. sage/combinat/designs/block_design.py +1071 -0
  23. sage/combinat/designs/covering_array.py +269 -0
  24. sage/combinat/designs/covering_design.py +530 -0
  25. sage/combinat/designs/database.py +5615 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  28. sage/combinat/designs/designs_pyx.pxd +21 -0
  29. sage/combinat/designs/designs_pyx.pyx +993 -0
  30. sage/combinat/designs/difference_family.py +3951 -0
  31. sage/combinat/designs/difference_matrices.py +279 -0
  32. sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  33. sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
  34. sage/combinat/designs/ext_rep.py +1064 -0
  35. sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
  36. sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
  37. sage/combinat/designs/group_divisible_designs.py +361 -0
  38. sage/combinat/designs/incidence_structures.py +2357 -0
  39. sage/combinat/designs/latin_squares.py +581 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2244 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
  44. sage/combinat/designs/resolvable_bibd.py +815 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
  47. sage/combinat/designs/subhypergraph_search.pyx +530 -0
  48. sage/combinat/designs/twographs.py +306 -0
  49. sage/combinat/finite_state_machine.py +14874 -0
  50. sage/combinat/finite_state_machine_generators.py +2006 -0
  51. sage/combinat/graph_path.py +448 -0
  52. sage/combinat/interval_posets.py +3908 -0
  53. sage/combinat/nu_tamari_lattice.py +269 -0
  54. sage/combinat/ordered_tree.py +1446 -0
  55. sage/combinat/posets/all.py +46 -0
  56. sage/combinat/posets/bubble_shuffle.py +247 -0
  57. sage/combinat/posets/cartesian_product.py +493 -0
  58. sage/combinat/posets/d_complete.py +182 -0
  59. sage/combinat/posets/elements.py +273 -0
  60. sage/combinat/posets/forest.py +30 -0
  61. sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
  62. sage/combinat/posets/hasse_cython.pyx +174 -0
  63. sage/combinat/posets/hasse_diagram.py +3672 -0
  64. sage/combinat/posets/hochschild_lattice.py +158 -0
  65. sage/combinat/posets/incidence_algebras.py +794 -0
  66. sage/combinat/posets/lattices.py +5117 -0
  67. sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
  68. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  69. sage/combinat/posets/linear_extensions.py +1037 -0
  70. sage/combinat/posets/mobile.py +275 -0
  71. sage/combinat/posets/moebius_algebra.py +776 -0
  72. sage/combinat/posets/poset_examples.py +2178 -0
  73. sage/combinat/posets/posets.py +9360 -0
  74. sage/combinat/rooted_tree.py +1070 -0
  75. sage/combinat/shard_order.py +239 -0
  76. sage/combinat/tamari_lattices.py +384 -0
  77. sage/combinat/yang_baxter_graph.py +923 -0
  78. sage/databases/all__sagemath_graphs.py +1 -0
  79. sage/databases/knotinfo_db.py +1231 -0
  80. sage/ext_data/all__sagemath_graphs.py +1 -0
  81. sage/ext_data/graphs/graph_plot_js.html +330 -0
  82. sage/ext_data/kenzo/CP2.txt +45 -0
  83. sage/ext_data/kenzo/CP3.txt +349 -0
  84. sage/ext_data/kenzo/CP4.txt +4774 -0
  85. sage/ext_data/kenzo/README.txt +49 -0
  86. sage/ext_data/kenzo/S4.txt +20 -0
  87. sage/graphs/all.py +42 -0
  88. sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
  89. sage/graphs/asteroidal_triples.pyx +320 -0
  90. sage/graphs/base/all.py +1 -0
  91. sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  92. sage/graphs/base/boost_graph.pxd +106 -0
  93. sage/graphs/base/boost_graph.pyx +3045 -0
  94. sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  95. sage/graphs/base/c_graph.pxd +106 -0
  96. sage/graphs/base/c_graph.pyx +5096 -0
  97. sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  98. sage/graphs/base/dense_graph.pxd +28 -0
  99. sage/graphs/base/dense_graph.pyx +801 -0
  100. sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
  101. sage/graphs/base/graph_backends.pxd +5 -0
  102. sage/graphs/base/graph_backends.pyx +797 -0
  103. sage/graphs/base/overview.py +85 -0
  104. sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  105. sage/graphs/base/sparse_graph.pxd +90 -0
  106. sage/graphs/base/sparse_graph.pyx +1653 -0
  107. sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  108. sage/graphs/base/static_dense_graph.pxd +5 -0
  109. sage/graphs/base/static_dense_graph.pyx +1032 -0
  110. sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
  111. sage/graphs/base/static_sparse_backend.pxd +27 -0
  112. sage/graphs/base/static_sparse_backend.pyx +1583 -0
  113. sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  114. sage/graphs/base/static_sparse_graph.pxd +37 -0
  115. sage/graphs/base/static_sparse_graph.pyx +1375 -0
  116. sage/graphs/bipartite_graph.py +2732 -0
  117. sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
  118. sage/graphs/centrality.pyx +1038 -0
  119. sage/graphs/cographs.py +519 -0
  120. sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/comparability.pyx +851 -0
  122. sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  123. sage/graphs/connectivity.pxd +157 -0
  124. sage/graphs/connectivity.pyx +4813 -0
  125. sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
  126. sage/graphs/convexity_properties.pxd +16 -0
  127. sage/graphs/convexity_properties.pyx +870 -0
  128. sage/graphs/digraph.py +4754 -0
  129. sage/graphs/digraph_generators.py +1993 -0
  130. sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
  131. sage/graphs/distances_all_pairs.pxd +12 -0
  132. sage/graphs/distances_all_pairs.pyx +2938 -0
  133. sage/graphs/domination.py +1363 -0
  134. sage/graphs/dot2tex_utils.py +100 -0
  135. sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  136. sage/graphs/edge_connectivity.pyx +1215 -0
  137. sage/graphs/generators/all.py +1 -0
  138. sage/graphs/generators/basic.py +1769 -0
  139. sage/graphs/generators/chessboard.py +538 -0
  140. sage/graphs/generators/classical_geometries.py +1611 -0
  141. sage/graphs/generators/degree_sequence.py +235 -0
  142. sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
  143. sage/graphs/generators/distance_regular.pyx +2846 -0
  144. sage/graphs/generators/families.py +4759 -0
  145. sage/graphs/generators/intersection.py +565 -0
  146. sage/graphs/generators/platonic_solids.py +262 -0
  147. sage/graphs/generators/random.py +2623 -0
  148. sage/graphs/generators/smallgraphs.py +5741 -0
  149. sage/graphs/generators/world_map.py +724 -0
  150. sage/graphs/generic_graph.py +26867 -0
  151. sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  152. sage/graphs/generic_graph_pyx.pxd +34 -0
  153. sage/graphs/generic_graph_pyx.pyx +1673 -0
  154. sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
  155. sage/graphs/genus.pyx +622 -0
  156. sage/graphs/graph.py +9645 -0
  157. sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
  158. sage/graphs/graph_coloring.pyx +2284 -0
  159. sage/graphs/graph_database.py +1177 -0
  160. sage/graphs/graph_decompositions/all.py +1 -0
  161. sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  163. sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
  165. sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  167. sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
  168. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  169. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  170. sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/graph_products.pyx +508 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  173. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  174. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  176. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  177. sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  179. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  180. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  181. sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
  182. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  183. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  184. sage/graphs/graph_editor.py +82 -0
  185. sage/graphs/graph_generators.py +3314 -0
  186. sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  187. sage/graphs/graph_generators_pyx.pyx +95 -0
  188. sage/graphs/graph_input.py +812 -0
  189. sage/graphs/graph_latex.py +2064 -0
  190. sage/graphs/graph_list.py +410 -0
  191. sage/graphs/graph_plot.py +1756 -0
  192. sage/graphs/graph_plot_js.py +338 -0
  193. sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
  194. sage/graphs/hyperbolicity.pyx +1704 -0
  195. sage/graphs/hypergraph_generators.py +364 -0
  196. sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  197. sage/graphs/independent_sets.pxd +13 -0
  198. sage/graphs/independent_sets.pyx +402 -0
  199. sage/graphs/isgci.py +1033 -0
  200. sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/isoperimetric_inequalities.pyx +489 -0
  202. sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  203. sage/graphs/line_graph.pyx +743 -0
  204. sage/graphs/lovasz_theta.py +77 -0
  205. sage/graphs/matching.py +1633 -0
  206. sage/graphs/matching_covered_graph.py +3590 -0
  207. sage/graphs/orientations.py +1489 -0
  208. sage/graphs/partial_cube.py +459 -0
  209. sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
  210. sage/graphs/path_enumeration.pyx +2040 -0
  211. sage/graphs/pq_trees.py +1129 -0
  212. sage/graphs/print_graphs.py +201 -0
  213. sage/graphs/schnyder.py +865 -0
  214. sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/spanning_tree.pyx +1457 -0
  216. sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/strongly_regular_db.pyx +3340 -0
  218. sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
  219. sage/graphs/traversals.pxd +9 -0
  220. sage/graphs/traversals.pyx +1872 -0
  221. sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
  222. sage/graphs/trees.pxd +15 -0
  223. sage/graphs/trees.pyx +310 -0
  224. sage/graphs/tutte_polynomial.py +713 -0
  225. sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/views.pyx +794 -0
  227. sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
  228. sage/graphs/weakly_chordal.pyx +604 -0
  229. sage/groups/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  231. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
  233. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  234. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  235. sage/knots/all.py +6 -0
  236. sage/knots/free_knotinfo_monoid.py +507 -0
  237. sage/knots/gauss_code.py +291 -0
  238. sage/knots/knot.py +682 -0
  239. sage/knots/knot_table.py +284 -0
  240. sage/knots/knotinfo.py +2900 -0
  241. sage/knots/link.py +4715 -0
  242. sage/sandpiles/all.py +13 -0
  243. sage/sandpiles/examples.py +225 -0
  244. sage/sandpiles/sandpile.py +6365 -0
  245. sage/topology/all.py +22 -0
  246. sage/topology/cell_complex.py +1214 -0
  247. sage/topology/cubical_complex.py +1976 -0
  248. sage/topology/delta_complex.py +1806 -0
  249. sage/topology/filtered_simplicial_complex.py +744 -0
  250. sage/topology/moment_angle_complex.py +823 -0
  251. sage/topology/simplicial_complex.py +5160 -0
  252. sage/topology/simplicial_complex_catalog.py +92 -0
  253. sage/topology/simplicial_complex_examples.py +1680 -0
  254. sage/topology/simplicial_complex_homset.py +205 -0
  255. sage/topology/simplicial_complex_morphism.py +836 -0
  256. sage/topology/simplicial_set.py +4102 -0
  257. sage/topology/simplicial_set_catalog.py +55 -0
  258. sage/topology/simplicial_set_constructions.py +2954 -0
  259. sage/topology/simplicial_set_examples.py +865 -0
  260. 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))