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,1633 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ r"""
3
+ Matching
4
+
5
+ This module implements the functions pertaining to matching of undirected
6
+ graphs. A *matching* in a graph is a set of pairwise nonadjacent links
7
+ (nonloop edges). In other words, a matching in a graph is the edge set of an
8
+ 1-regular subgraph. A matching is called a *perfect* *matching* if it the
9
+ subgraph generated by a set of matching edges spans the graph, i.e. it's the
10
+ edge set of an 1-regular spanning subgraph.
11
+
12
+ The implemented methods are listed below:
13
+
14
+ .. csv-table::
15
+ :class: contentstable
16
+ :widths: 30, 70
17
+ :delim: |
18
+
19
+ :meth:`~has_perfect_matching` | Return whether the graph has a perfect matching
20
+ :meth:`~is_bicritical` | Check if the graph is bicritical
21
+ :meth:`~is_factor_critical` | Check whether the graph is factor-critical
22
+ :meth:`~is_matching_covered` | Check if the graph is matching covered
23
+ :meth:`~matching` | Return a maximum weighted matching of the graph represented by the list of its edges
24
+ :meth:`~perfect_matchings` | Return an iterator over all perfect matchings of the graph
25
+ :meth:`~M_alternating_even_mark` | Return the vertices reachable from the provided vertex via an even alternating path starting with a non-matching edge
26
+
27
+ AUTHORS:
28
+
29
+ - Robert L. Miller (2006-10-22): initial implementations
30
+
31
+ - Janmenjaya Panda (2024-06-17): added
32
+ :meth:`~M_alternating_even_mark`,
33
+ :meth:`~is_bicritical` and
34
+ :meth:`~is_matching_covered`
35
+
36
+
37
+ Methods
38
+ -------
39
+ """
40
+
41
+ # ****************************************************************************
42
+ # Copyright (C) 2006 Robert L. Miller
43
+ # 2024 Janmenjaya Panda
44
+ #
45
+ # This program is free software: you can redistribute it and/or modify
46
+ # it under the terms of the GNU General Public License as published by
47
+ # the Free Software Foundation, either version 2 of the License, or
48
+ # (at your option) any later version.
49
+ # https://www.gnu.org/licenses/
50
+ # ****************************************************************************
51
+
52
+ from sage.rings.integer import Integer
53
+ from sage.graphs.views import EdgesView
54
+
55
+
56
+ def has_perfect_matching(G, algorithm='Edmonds', solver=None, verbose=0,
57
+ *, integrality_tolerance=1e-3):
58
+ r"""
59
+ Return whether the graph has a perfect matching.
60
+
61
+ INPUT:
62
+
63
+ - ``algorithm`` -- string (default: ``'Edmonds'``)
64
+
65
+ - ``'Edmonds'`` uses Edmonds' algorithm as implemented in NetworkX to
66
+ find a matching of maximal cardinality, then check whether this
67
+ cardinality is half the number of vertices of the graph.
68
+
69
+ - ``'LP_matching'`` uses a Linear Program to find a matching of
70
+ maximal cardinality, then check whether this cardinality is half the
71
+ number of vertices of the graph.
72
+
73
+ - ``'LP'`` uses a Linear Program formulation of the perfect matching
74
+ problem: put a binary variable ``b[e]`` on each edge `e`, and for
75
+ each vertex `v`, require that the sum of the values of the edges
76
+ incident to `v` is 1.
77
+
78
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer
79
+ Linear Programming (MILP) solver to be used. If set to ``None``, the
80
+ default one is used. For more information on MILP solvers and which
81
+ default solver is used, see the method :meth:`solve
82
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
83
+ :class:`MixedIntegerLinearProgram
84
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
85
+
86
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity:
87
+ set to 0 by default, which means quiet (only useful when
88
+ ``algorithm == "LP_matching"`` or ``algorithm == "LP"``)
89
+
90
+ - ``integrality_tolerance`` -- float; parameter for use with MILP
91
+ solvers over an inexact base ring; see
92
+ :meth:`MixedIntegerLinearProgram.get_values`.
93
+
94
+ OUTPUT: boolean
95
+
96
+ EXAMPLES::
97
+
98
+ sage: graphs.PetersenGraph().has_perfect_matching() # needs networkx
99
+ True
100
+ sage: graphs.WheelGraph(6).has_perfect_matching() # needs networkx
101
+ True
102
+ sage: graphs.WheelGraph(5).has_perfect_matching() # needs networkx
103
+ False
104
+ sage: graphs.PetersenGraph().has_perfect_matching(algorithm='LP_matching') # needs sage.numerical.mip
105
+ True
106
+ sage: graphs.WheelGraph(6).has_perfect_matching(algorithm='LP_matching') # needs sage.numerical.mip
107
+ True
108
+ sage: graphs.WheelGraph(5).has_perfect_matching(algorithm='LP_matching')
109
+ False
110
+ sage: graphs.PetersenGraph().has_perfect_matching(algorithm='LP_matching') # needs sage.numerical.mip
111
+ True
112
+ sage: graphs.WheelGraph(6).has_perfect_matching(algorithm='LP_matching') # needs sage.numerical.mip
113
+ True
114
+ sage: graphs.WheelGraph(5).has_perfect_matching(algorithm='LP_matching')
115
+ False
116
+
117
+ TESTS::
118
+
119
+ sage: G = graphs.EmptyGraph()
120
+ sage: all(G.has_perfect_matching(algorithm=algo) # needs networkx
121
+ ....: for algo in ['Edmonds', 'LP_matching', 'LP'])
122
+ True
123
+
124
+ Be careful with isolated vertices::
125
+
126
+ sage: G = graphs.PetersenGraph()
127
+ sage: G.add_vertex(11)
128
+ sage: any(G.has_perfect_matching(algorithm=algo) # needs networkx
129
+ ....: for algo in ['Edmonds', 'LP_matching', 'LP'])
130
+ False
131
+ """
132
+ if G.order() % 2:
133
+ return False
134
+
135
+ if algorithm == "Edmonds":
136
+ return len(G) == 2*G.matching(value_only=True,
137
+ use_edge_labels=False,
138
+ algorithm='Edmonds')
139
+ elif algorithm == "LP_matching":
140
+ return len(G) == 2*G.matching(value_only=True,
141
+ use_edge_labels=False,
142
+ algorithm='LP',
143
+ solver=solver,
144
+ verbose=verbose,
145
+ integrality_tolerance=integrality_tolerance)
146
+ elif algorithm == "LP":
147
+ from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException
148
+ p = MixedIntegerLinearProgram(solver=solver)
149
+ b = p.new_variable(binary=True)
150
+ for v in G:
151
+ edges = G.edges_incident(v, labels=False)
152
+ if not edges:
153
+ return False
154
+ p.add_constraint(p.sum(b[frozenset(e)] for e in edges) == 1)
155
+ try:
156
+ p.solve(log=verbose)
157
+ return True
158
+ except MIPSolverException:
159
+ return False
160
+ raise ValueError('algorithm must be set to "Edmonds", "LP_matching" or "LP"')
161
+
162
+
163
+ def is_bicritical(G, matching=None, algorithm='Edmonds', coNP_certificate=False,
164
+ solver=None, verbose=0, *, integrality_tolerance=0.001):
165
+ r"""
166
+ Check if the graph is bicritical.
167
+
168
+ A nontrivial graph `G` is *bicritical* if `G - u - v` has a perfect
169
+ matching for any two distinct vertices `u` and `v` of `G`. Bicritical
170
+ graphs are special kind of matching covered graphs. Each maximal barrier of
171
+ a bicritical graph is a singleton. Thus, for a bicritical graph, the
172
+ canonical partition of the vertex set is the set of sets where each set is
173
+ an indiviudal vertex. Three-connected bicritical graphs, aka *bricks*, play
174
+ an important role in the theory of matching covered graphs.
175
+
176
+ This method implements the algorithm proposed in [LZ2001]_ and we
177
+ assume that a connected graph of order two is bicritical, whereas a
178
+ disconnected graph of the same order is not. The time complexity of
179
+ the algorithm is `\mathcal{O}(|V| \cdot |E|)`, if a perfect matching of
180
+ the graph is given, where `|V|` and `|E|` are the order and the size of
181
+ the graph respectively. Otherwise, time complexity may be dominated by
182
+ the time needed to compute a maximum matching of the graph.
183
+
184
+ Note that a :class:`ValueError` is returned if the graph has loops or if
185
+ the graph is trivial, i.e., it has at most one vertex.
186
+
187
+ INPUT:
188
+
189
+ - ``matching`` -- (default: ``None``); a perfect matching of the
190
+ graph, that can be given using any valid input format of
191
+ :class:`~sage.graphs.graph.Graph`.
192
+
193
+ If set to ``None``, a matching is computed using the other parameters.
194
+
195
+ - ``algorithm`` -- string (default: ``'Edmonds'``); the algorithm to be
196
+ used to compute a maximum matching of the graph among
197
+
198
+ - ``'Edmonds'`` selects Edmonds' algorithm as implemented in NetworkX,
199
+
200
+ - ``'LP'`` uses a Linear Program formulation of the matching problem.
201
+
202
+ - ``coNP_certificate`` -- boolean (default: ``False``); if set to
203
+ ``True`` a set of pair of vertices (say `u` and `v`) is returned such
204
+ that `G - u - v` does not have a perfect matching if `G` is not
205
+ bicritical or otherwise ``None`` is returned.
206
+
207
+ - ``solver`` -- string (default: ``None``); specify a Mixed Integer
208
+ Linear Programming (MILP) solver to be used. If set to ``None``, the
209
+ default one is used. For more information on MILP solvers and which
210
+ default solver is used, see the method :meth:`solve
211
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
212
+ :class:`MixedIntegerLinearProgram
213
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
214
+
215
+ - ``verbose`` -- integer (default: ``0``); sets the level of verbosity:
216
+ set to 0 by default, which means quiet (only useful when ``algorithm
217
+ == 'LP'``).
218
+
219
+ - ``integrality_tolerance`` -- float; parameter for use with MILP
220
+ solvers over an inexact base ring; see
221
+ :meth:`MixedIntegerLinearProgram.get_values`.
222
+
223
+ OUTPUT:
224
+
225
+ - A boolean indicating whether the graph is bicritical or not.
226
+
227
+ - If ``coNP_certificate`` is set to ``True``, a set of pair of vertices
228
+ is returned in case the graph is not bicritical otherwise ``None`` is
229
+ returned.
230
+
231
+ EXAMPLES:
232
+
233
+ The Petersen graph is bicritical::
234
+
235
+ sage: G = graphs.PetersenGraph()
236
+ sage: G.is_bicritical() # needs networkx
237
+ True
238
+
239
+ A graph (without a self-loop) is bicritical if and only if the underlying
240
+ simple graph is bicritical::
241
+
242
+ sage: G = graphs.PetersenGraph()
243
+ sage: G.allow_multiple_edges(True)
244
+ sage: G.add_edge(0, 5)
245
+ sage: G.is_bicritical() # needs networkx
246
+ True
247
+
248
+ A nontrivial circular ladder graph whose order is not divisible by 4 is bicritical::
249
+
250
+ sage: G = graphs.CircularLadderGraph(5)
251
+ sage: G.is_bicritical() # needs networkx
252
+ True
253
+
254
+ The graph obtained by splicing two bicritical graph is also bicritical.
255
+ For instance, `K_4` with one extra (multiple) edge (say `G := K_4^+`) is
256
+ bicritical. Let `H := K_4^+ \odot K_4^+` such that `H` is free of multiple
257
+ edge. The graph `H` is also bicritical::
258
+
259
+ sage: G = graphs.CompleteGraph(4)
260
+ sage: G.allow_multiple_edges(True)
261
+ sage: G.add_edge(0, 1)
262
+ sage: G.is_bicritical() # needs networkx
263
+ True
264
+ sage: H = Graph()
265
+ sage: H.add_edges([
266
+ ....: (0, 1), (0, 2), (0, 3), (0, 4), (1, 2),
267
+ ....: (1, 5), (2, 5), (3, 4), (3, 5), (4, 5)
268
+ ....: ])
269
+ sage: H.is_bicritical() # needs networkx
270
+ True
271
+
272
+ A graph (of order more than two) with more that one component is not bicritical::
273
+
274
+ sage: G = graphs.CycleGraph(4)
275
+ sage: G += graphs.CycleGraph(6)
276
+ sage: G.connected_components_number()
277
+ 2
278
+ sage: G.is_bicritical() # needs networkx
279
+ False
280
+
281
+ A graph (of order more than two) with a cut-vertex is not bicritical::
282
+
283
+ sage: G = graphs.CycleGraph(6)
284
+ sage: G.add_edges([(5, 6), (5, 7), (6, 7)])
285
+ sage: G.is_cut_vertex(5)
286
+ True
287
+ sage: G.has_perfect_matching() # needs networkx
288
+ True
289
+ sage: G.is_bicritical() # needs networkx
290
+ False
291
+
292
+ A connected graph of order two is assumed to be bicritical, whereas the
293
+ disconnected graph of the same order is not::
294
+
295
+ sage: G = graphs.CompleteBipartiteGraph(1, 1)
296
+ sage: G.is_bicritical() # needs networkx
297
+ True
298
+ sage: G = graphs.CompleteBipartiteGraph(2, 0)
299
+ sage: G.is_bicritical() # needs networkx
300
+ False
301
+
302
+ A bipartite graph of order three or more is not bicritical::
303
+
304
+ sage: G = graphs.CompleteBipartiteGraph(3, 3)
305
+ sage: G.has_perfect_matching() # needs networkx
306
+ True
307
+ sage: G.is_bicritical() # needs networkx
308
+ False
309
+
310
+ One may specify a matching::
311
+
312
+ sage: G = graphs.WheelGraph(10)
313
+ sage: M = G.matching() # needs networkx
314
+ sage: G.is_bicritical(matching=M) # needs networkx
315
+ True
316
+ sage: H = graphs.HexahedralGraph()
317
+ sage: N = H.matching() # needs networkx
318
+ sage: H.is_bicritical(matching=N) # needs networkx
319
+ False
320
+
321
+ One may ask for a co-`\mathcal{NP}` certificate::
322
+
323
+ sage: G = graphs.CompleteGraph(14)
324
+ sage: G.is_bicritical(coNP_certificate=True) # needs networkx
325
+ (True, None)
326
+ sage: H = graphs.CircularLadderGraph(20)
327
+ sage: M = H.matching() # needs networkx
328
+ sage: H.is_bicritical(matching=M, coNP_certificate=True) # needs networkx
329
+ (False, {0, 2})
330
+
331
+ TESTS:
332
+
333
+ If the graph is trivial::
334
+
335
+ sage: G = Graph()
336
+ sage: G.is_bicritical() # needs networkx
337
+ Traceback (most recent call last):
338
+ ...
339
+ ValueError: the graph is trivial
340
+ sage: H = graphs.CycleGraph(1)
341
+ sage: H.is_bicritical() # needs networkx
342
+ Traceback (most recent call last):
343
+ ...
344
+ ValueError: the graph is trivial
345
+
346
+ Providing with a wrong matching::
347
+
348
+ sage: # needs networkx
349
+ sage: G = graphs.CompleteGraph(6)
350
+ sage: M = Graph(G.matching())
351
+ sage: M.add_edges([(0, 1), (0, 2)])
352
+ sage: G.is_bicritical(matching=M)
353
+ Traceback (most recent call last):
354
+ ...
355
+ ValueError: the input is not a matching
356
+ sage: N = Graph(G.matching())
357
+ sage: N.add_edge(6, 7)
358
+ sage: G.is_bicritical(matching=N)
359
+ Traceback (most recent call last):
360
+ ...
361
+ ValueError: the input is not a matching of the graph
362
+ sage: J = Graph()
363
+ sage: J.add_edges([(0, 1), (2, 3)])
364
+ sage: G.is_bicritical(matching=J)
365
+ Traceback (most recent call last):
366
+ ...
367
+ ValueError: the input is not a perfect matching of the graph
368
+
369
+ Providing with a graph with a self-loop::
370
+
371
+ sage: G = graphs.CompleteGraph(4)
372
+ sage: G.allow_loops(True)
373
+ sage: G.add_edge(0, 0)
374
+ sage: G.is_bicritical() # needs networkx
375
+ Traceback (most recent call last):
376
+ ...
377
+ ValueError: This method is not known to work on graphs with loops.
378
+ Perhaps this method can be updated to handle them, but in the meantime
379
+ if you want to use it please disallow loops using allow_loops().
380
+
381
+ REFERENCES:
382
+
383
+ - [LM2024]_
384
+
385
+ - [LZ2001]_
386
+
387
+ .. SEEALSO::
388
+ :meth:`~sage.graphs.graph.Graph.is_factor_critical`,
389
+ :meth:`~sage.graphs.graph.Graph.is_matching_covered`
390
+
391
+ AUTHORS:
392
+
393
+ - Janmenjaya Panda (2024-06-17)
394
+ """
395
+ # The graph must be simple
396
+ G._scream_if_not_simple(allow_multiple_edges=True)
397
+
398
+ # The graph must be nontrivial
399
+ if G.order() < 2:
400
+ raise ValueError("the graph is trivial")
401
+
402
+ # A graph of order two is assumed to be bicritical
403
+ if G.order() == 2:
404
+ if G.is_connected():
405
+ return (True, None) if coNP_certificate else True
406
+ else:
407
+ return (False, None) if coNP_certificate else False
408
+
409
+ # The graph must have an even number of vertices
410
+ if G.order() % 2:
411
+ return (False, set(list(G)[:2])) if coNP_certificate else False
412
+
413
+ # The graph must be connected
414
+ if not G.is_connected():
415
+ if not coNP_certificate:
416
+ return False
417
+
418
+ components = G.connected_components(sort=False)
419
+
420
+ # Check if there is an odd component with at least three vertices
421
+ for component in components:
422
+ if len(component) % 2 and len(component) > 2:
423
+ return (False, set(component[:2]))
424
+
425
+ # Check if there are at least two even components
426
+ components_of_even_order = [component for component in components if len(component) % 2 == 0]
427
+ if len(components_of_even_order) > 1:
428
+ return (False, set([components_of_even_order[0][0], components_of_even_order[1][0]]))
429
+
430
+ # Or otherwise there is at most one even component with at least two trivial odd components
431
+ u, v = None, None
432
+
433
+ for component in components:
434
+ if u is not None and not len(component) % 2:
435
+ v = component[0]
436
+ return (False, set([u, v]))
437
+ elif len(component) == 1:
438
+ u = component[0]
439
+
440
+ # Bipartite graphs of order at least three are not bicritical
441
+ if G.is_bipartite():
442
+ if not coNP_certificate:
443
+ return False
444
+
445
+ A, B = G.bipartite_sets()
446
+
447
+ if len(A) > 1:
448
+ return (False, set(list(A)[:2]))
449
+ return (False, set(list(B)[:2]))
450
+
451
+ from sage.graphs.graph import Graph
452
+ if matching:
453
+ # The input matching must be a valid perfect matching of the graph
454
+ M = Graph(matching)
455
+ if any(d != 1 for d in M.degree()):
456
+ raise ValueError("the input is not a matching")
457
+
458
+ if any(not G.has_edge(edge) for edge in M.edge_iterator()):
459
+ raise ValueError("the input is not a matching of the graph")
460
+
461
+ if (G.order() != M.order()) or (G.order() != 2*M.size()):
462
+ raise ValueError("the input is not a perfect matching of the graph")
463
+ else:
464
+ # A maximum matching of the graph is computed
465
+ M = Graph(G.matching(algorithm=algorithm, solver=solver, verbose=verbose,
466
+ integrality_tolerance=integrality_tolerance))
467
+
468
+ # It must be a perfect matching
469
+ if G.order() != M.order():
470
+ u, v = next(M.edge_iterator(labels=False))
471
+ return (False, set([u, v])) if coNP_certificate else False
472
+
473
+ # G is bicritical if and only if for each vertex u with its M-matched neighbor being v,
474
+ # every vertex of the graph distinct from v must be reachable from u through an even length
475
+ # M-alternating uv-path starting with an edge not in M and ending with an edge in M
476
+
477
+ for u in G:
478
+ v = next(M.neighbor_iterator(u))
479
+
480
+ even = M_alternating_even_mark(G, u, M)
481
+
482
+ for w in G:
483
+ if w != v and w not in even:
484
+ return (False, set([v, w])) if coNP_certificate else False
485
+
486
+ return (True, None) if coNP_certificate else True
487
+
488
+
489
+ def is_factor_critical(G, matching=None, algorithm='Edmonds', solver=None, verbose=0,
490
+ *, integrality_tolerance=0.001):
491
+ r"""
492
+ Check whether the graph is factor-critical.
493
+
494
+ A graph of order `n` is *factor-critical* if every subgraph of `n-1`
495
+ vertices have a perfect matching, hence `n` must be odd. See
496
+ :wikipedia:`Factor-critical_graph` for more details.
497
+
498
+ This method implements the algorithm proposed in [LR2004]_ and we assume
499
+ that a graph of order one is factor-critical. The time complexity of the
500
+ algorithm is linear if a near perfect matching is given as input (i.e.,
501
+ a matching such that all vertices but one are incident to an edge of the
502
+ matching). Otherwise, the time complexity is dominated by the time
503
+ needed to compute a maximum matching of the graph.
504
+
505
+ INPUT:
506
+
507
+ - ``matching`` -- (default: ``None``); a near perfect matching of the
508
+ graph, that is a matching such that all vertices of the graph but one
509
+ are incident to an edge of the matching. It can be given using any
510
+ valid input format of :class:`~sage.graphs.graph.Graph`.
511
+
512
+ If set to ``None``, a matching is computed using the other parameters.
513
+
514
+ - ``algorithm`` -- string (default: ``'Edmonds'``); the algorithm to use
515
+ to compute a maximum matching of the graph among
516
+
517
+ - ``'Edmonds'`` selects Edmonds' algorithm as implemented in NetworkX
518
+
519
+ - ``'LP'`` uses a Linear Program formulation of the matching problem
520
+
521
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer
522
+ Linear Programming (MILP) solver to be used. If set to ``None``, the
523
+ default one is used. For more information on MILP solvers and which
524
+ default solver is used, see the method :meth:`solve
525
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
526
+ :class:`MixedIntegerLinearProgram
527
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
528
+
529
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity:
530
+ set to 0 by default, which means quiet (only useful when ``algorithm
531
+ == "LP"``)
532
+
533
+ - ``integrality_tolerance`` -- float; parameter for use with MILP
534
+ solvers over an inexact base ring; see
535
+ :meth:`MixedIntegerLinearProgram.get_values`.
536
+
537
+ EXAMPLES:
538
+
539
+ Odd length cycles and odd cliques of order at least 3 are
540
+ factor-critical graphs::
541
+
542
+ sage: [graphs.CycleGraph(2*i + 1).is_factor_critical() for i in range(5)] # needs networkx
543
+ [True, True, True, True, True]
544
+ sage: [graphs.CompleteGraph(2*i + 1).is_factor_critical() for i in range(5)] # needs networkx
545
+ [True, True, True, True, True]
546
+
547
+ More generally, every Hamiltonian graph with an odd number of vertices
548
+ is factor-critical::
549
+
550
+ sage: G = graphs.RandomGNP(15, .2)
551
+ sage: G.add_path([0..14])
552
+ sage: G.add_edge(14, 0)
553
+ sage: G.is_hamiltonian() # needs sage.numerical.mip
554
+ True
555
+ sage: G.is_factor_critical() # needs networkx
556
+ True
557
+
558
+ Friendship graphs are non-Hamiltonian factor-critical graphs::
559
+
560
+ sage: [graphs.FriendshipGraph(i).is_factor_critical() for i in range(1, 5)] # needs networkx
561
+ [True, True, True, True]
562
+
563
+ Bipartite graphs are not factor-critical::
564
+
565
+ sage: G = graphs.RandomBipartite(randint(1, 10), randint(1, 10), .5) # needs numpy
566
+ sage: G.is_factor_critical() # needs numpy
567
+ False
568
+
569
+ Graphs with even order are not factor critical::
570
+
571
+ sage: G = graphs.RandomGNP(10, .5)
572
+ sage: G.is_factor_critical()
573
+ False
574
+
575
+ One can specify a matching::
576
+
577
+ sage: F = graphs.FriendshipGraph(4)
578
+ sage: M = F.matching() # needs networkx
579
+ sage: F.is_factor_critical(matching=M) # needs networkx
580
+ True
581
+ sage: F.is_factor_critical(matching=Graph(M)) # needs networkx
582
+ True
583
+
584
+ TESTS:
585
+
586
+ Giving a wrong matching::
587
+
588
+ sage: G = graphs.RandomGNP(15, .3)
589
+ sage: while not G.is_biconnected():
590
+ ....: G = graphs.RandomGNP(15, .3)
591
+ sage: M = G.matching() # needs networkx
592
+ sage: G.is_factor_critical(matching=M[:-1]) # needs networkx
593
+ Traceback (most recent call last):
594
+ ...
595
+ ValueError: the input is not a near perfect matching of the graph
596
+ sage: G.is_factor_critical(matching=G.edges(sort=True))
597
+ Traceback (most recent call last):
598
+ ...
599
+ ValueError: the input is not a matching
600
+ sage: M = [(2*i, 2*i + 1) for i in range(9)]
601
+ sage: G.is_factor_critical(matching=M)
602
+ Traceback (most recent call last):
603
+ ...
604
+ ValueError: the input is not a matching of the graph
605
+ """
606
+ if G.order() == 1:
607
+ return True
608
+
609
+ # The graph must have an odd number of vertices, be 2-edge connected, so
610
+ # without bridges, and not bipartite
611
+ if (not G.order() % 2 or not G.is_connected() or
612
+ list(G.bridges()) or G.is_bipartite()):
613
+ return False
614
+
615
+ from sage.graphs.graph import Graph
616
+ if matching:
617
+ # We check that the input matching is a valid near perfect matching
618
+ # of the graph.
619
+ M = Graph(matching)
620
+ if any(d != 1 for d in M.degree()):
621
+ raise ValueError("the input is not a matching")
622
+ if not M.is_subgraph(G, induced=False):
623
+ raise ValueError("the input is not a matching of the graph")
624
+ if (G.order() != M.order() + 1) or (G.order() != 2*M.size() + 1):
625
+ raise ValueError("the input is not a near perfect matching of the graph")
626
+ else:
627
+ # We compute a maximum matching of the graph
628
+ M = Graph(G.matching(algorithm=algorithm, solver=solver, verbose=verbose,
629
+ integrality_tolerance=integrality_tolerance))
630
+
631
+ # It must be a near-perfect matching
632
+ if G.order() != M.order() + 1:
633
+ return False
634
+
635
+ # We find the unsaturated vertex u, i.e., the only vertex of the graph
636
+ # not in M
637
+ for u in G:
638
+ if u not in M:
639
+ break
640
+
641
+ # We virtually build an M-alternating tree T
642
+ from queue import Queue
643
+ Q = Queue()
644
+ Q.put(u)
645
+ even = set([u])
646
+ odd = set()
647
+ pred = {u: u}
648
+ rank = {u: 0}
649
+
650
+ while not Q.empty():
651
+ x = Q.get()
652
+ for y in G.neighbor_iterator(x):
653
+ if y in odd:
654
+ continue
655
+ elif y in even:
656
+ # Search for the nearest common ancestor t of x and y
657
+ P = [x]
658
+ R = [y]
659
+ while P[-1] != R[-1]:
660
+ if rank[P[-1]] > rank[R[-1]]:
661
+ P.append(pred[P[-1]])
662
+ elif rank[P[-1]] < rank[R[-1]]:
663
+ R.append(pred[R[-1]])
664
+ else:
665
+ P.append(pred[P[-1]])
666
+ R.append(pred[R[-1]])
667
+ t = P.pop()
668
+ R.pop()
669
+ # Set t as pred of all vertices of the chains and add
670
+ # vertices marked odd to the queue
671
+ import itertools
672
+
673
+ for a in itertools.chain(P, R):
674
+ pred[a] = t
675
+ rank[a] = rank[t] + 1
676
+ if a in odd:
677
+ even.add(a)
678
+ odd.discard(a)
679
+ Q.put(a)
680
+ else: # y has not been visited yet
681
+ z = next(M.neighbor_iterator(y))
682
+ odd.add(y)
683
+ even.add(z)
684
+ Q.put(z)
685
+ pred[y] = x
686
+ pred[z] = y
687
+ rank[y] = rank[x] + 1
688
+ rank[z] = rank[y] + 1
689
+
690
+ # The graph is factor critical if all vertices are marked even
691
+ return len(even) == G.order()
692
+
693
+
694
+ def is_matching_covered(G, matching=None, algorithm='Edmonds', coNP_certificate=False,
695
+ solver=None, verbose=0, *, integrality_tolerance=0.001):
696
+ r"""
697
+ Check if the graph is matching covered.
698
+
699
+ A connected nontrivial graph wherein each edge participates in some
700
+ perfect matching is called a *matching* *covered* *graph*.
701
+
702
+ If a perfect matching of the graph is provided, for bipartite graph,
703
+ this method implements a linear time algorithm as proposed in [LM2024]_
704
+ that is based on the following theorem:
705
+
706
+ Given a connected bipartite graph `G[A, B]` with a perfect matching
707
+ `M`. Construct a directed graph `D` from `G` such that `V(D) := V(G)`
708
+ and for each edge in `G` direct the corresponding edge from `A` to `B`
709
+ in `D`, if it is in `M` or otherwise direct it from `B` to `A`. The
710
+ graph `G` is matching covered if and only if `D` is strongly connected.
711
+
712
+ For nonbipartite graph, if a perfect matching of the graph is provided,
713
+ this method implements an `\mathcal{O}(|V| \cdot |E|)` algorithm, where
714
+ `|V|` and `|E|` are the order and the size of the graph respectively.
715
+ This implementation is inspired by the `M`-`alternating` `tree` `search`
716
+ method explained in [LZ2001]_. For nonbipartite graph, the
717
+ implementation is based on the following theorem:
718
+
719
+ Given a nonbipartite graph `G` with a perfect matching `M`. The
720
+ graph `G` is matching covered if and only if for each edge `uv`
721
+ not in `M`, there exists an `M`-`alternating` odd length `uv`-path
722
+ starting and ending with edges not in `M`.
723
+
724
+ The time complexity may be dominated by the time needed to compute a
725
+ maximum matching of the graph, in case a perfect matching is not
726
+ provided. Also, note that for a disconnected or a trivial or a
727
+ graph with a loop, a :class:`ValueError` is returned.
728
+
729
+ INPUT:
730
+
731
+ - ``matching`` -- (default: ``None``); a perfect matching of the
732
+ graph, that can be given using any valid input format of
733
+ :class:`~sage.graphs.graph.Graph`.
734
+
735
+ If set to ``None``, a matching is computed using the other parameters.
736
+
737
+ - ``algorithm`` -- string (default: ``'Edmonds'``); the algorithm to be
738
+ used to compute a maximum matching of the graph among
739
+
740
+ - ``'Edmonds'`` selects Edmonds' algorithm as implemented in NetworkX,
741
+
742
+ - ``'LP'`` uses a Linear Program formulation of the matching problem.
743
+
744
+ - ``coNP_certificate`` -- boolean (default: ``False``); if set to
745
+ ``True`` an edge of the graph, that does not participate in any
746
+ perfect matching, is returned if `G` is not matching covered or
747
+ otherwise ``None`` is returned.
748
+
749
+ - ``solver`` -- string (default: ``None``); specify a Mixed Integer
750
+ Linear Programming (MILP) solver to be used. If set to ``None``, the
751
+ default one is used. For more information on MILP solvers and which
752
+ default solver is used, see the method :meth:`solve
753
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
754
+ :class:`MixedIntegerLinearProgram
755
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
756
+
757
+ - ``verbose`` -- integer (default: ``0``); sets the level of verbosity:
758
+ set to 0 by default, which means quiet (only useful when ``algorithm
759
+ == 'LP'``).
760
+
761
+ - ``integrality_tolerance`` -- float; parameter for use with MILP
762
+ solvers over an inexact base ring; see
763
+ :meth:`MixedIntegerLinearProgram.get_values`.
764
+
765
+ OUTPUT:
766
+
767
+ - A boolean indicating whether the graph is matching covered or not.
768
+
769
+ - If ``coNP_certificate`` is set to ``True``, an edge is returned in
770
+ case the graph is not matching covered otherwise ``None`` is
771
+ returned.
772
+
773
+ EXAMPLES:
774
+
775
+ The Petersen graph is matching covered::
776
+
777
+ sage: G = graphs.PetersenGraph()
778
+ sage: G.is_matching_covered() # needs networkx
779
+ True
780
+
781
+ A graph (without a self-loop) is matching covered if and only if the
782
+ underlying simple graph is matching covered::
783
+
784
+ sage: G = graphs.PetersenGraph()
785
+ sage: G.allow_multiple_edges(True)
786
+ sage: G.add_edge(0, 5)
787
+ sage: G.is_matching_covered() # needs networkx
788
+ True
789
+
790
+ A corollary to Tutte's fundamental result [Tut1947]_, as a
791
+ strengthening of Petersen's Theorem, states that every 2-connected
792
+ cubic graph is matching covered::
793
+
794
+ sage: G = Graph()
795
+ sage: G.add_edges([
796
+ ....: (0, 1), (0, 2), (0, 3),
797
+ ....: (1, 2), (1, 4), (2, 4),
798
+ ....: (3, 5), (3, 6), (4, 7),
799
+ ....: (5, 6), (5, 7), (6, 7)
800
+ ....: ])
801
+ sage: G.vertex_connectivity()
802
+ 2
803
+ sage: degree_sequence = G.degree_sequence()
804
+ sage: min(degree_sequence) == max(degree_sequence) == 3
805
+ True
806
+ sage: G.is_matching_covered() # needs networkx
807
+ True
808
+
809
+ A connected bipartite graph `G[A, B]`, with `|A| = |B| \geq 2`, is
810
+ matching covered if and only if `|N(X)| \geq |X| + 1`, for all
811
+ `X \subset A` such that `1 \leq |X| \leq |A| - 1`. For instance,
812
+ the Hexahedral graph is matching covered, but not the path graphs on
813
+ even number of vertices, even though they have a perfect matching::
814
+
815
+ sage: G = graphs.HexahedralGraph()
816
+ sage: G.is_bipartite()
817
+ True
818
+ sage: G.is_matching_covered() # needs networkx
819
+ True
820
+ sage: P = graphs.PathGraph(10)
821
+ sage: P.is_bipartite()
822
+ True
823
+ sage: M = Graph(P.matching()) # needs networkx
824
+ sage: set(P) == set(M) # needs networkx
825
+ True
826
+ sage: P.is_matching_covered() # needs networkx
827
+ False
828
+
829
+ A connected bipartite graph `G[A, B]` of order six or more is matching
830
+ covered if and only if `G - a - b` has a perfect matching for some
831
+ vertex `a` in `A` and some vertex `b` in `B`::
832
+
833
+ sage: G = graphs.CircularLadderGraph(8)
834
+ sage: G.is_bipartite()
835
+ True
836
+ sage: G.is_matching_covered() # needs networkx
837
+ True
838
+ sage: A, B = G.bipartite_sets()
839
+ sage: import random
840
+ sage: a = random.choice(list(A))
841
+ sage: b = random.choice(list(B))
842
+ sage: G.delete_vertices([a, b])
843
+ sage: M = Graph(G.matching()) # needs networkx
844
+ sage: set(M) == set(G) # needs networkx
845
+ True
846
+ sage: cycle1 = graphs.CycleGraph(4)
847
+ sage: cycle2 = graphs.CycleGraph(6)
848
+ sage: cycle2.relabel(lambda v: v + 4)
849
+ sage: H = Graph()
850
+ sage: H.add_edges(cycle1.edges() + cycle2.edges())
851
+ sage: H.add_edge(3, 4)
852
+ sage: H.is_bipartite()
853
+ True
854
+ sage: H.is_matching_covered() # needs networkx
855
+ False
856
+ sage: H.delete_vertices([3, 4])
857
+ sage: N = Graph(H.matching()) # needs networkx
858
+ sage: set(N) == set(H) # needs networkx
859
+ False
860
+
861
+ One may specify a matching::
862
+
863
+ sage: # needs networkx
864
+ sage: G = graphs.WheelGraph(20)
865
+ sage: M = Graph(G.matching())
866
+ sage: G.is_matching_covered(matching=M)
867
+ True
868
+ sage: J = graphs.CycleGraph(4)
869
+ sage: J.add_edge(0, 2)
870
+ sage: N = J.matching()
871
+ sage: J.is_matching_covered(matching=N)
872
+ False
873
+
874
+ One may ask for a co-`\mathcal{NP}` certificate::
875
+
876
+ sage: # needs networkx
877
+ sage: G = graphs.CompleteGraph(14)
878
+ sage: G.is_matching_covered(coNP_certificate=True)
879
+ (True, None)
880
+ sage: H = graphs.PathGraph(20)
881
+ sage: M = H.matching()
882
+ sage: H.is_matching_covered(matching=M, coNP_certificate=True)
883
+ (False, (2, 1, None))
884
+
885
+ TESTS:
886
+
887
+ If the graph is not connected::
888
+
889
+ sage: cycle1 = graphs.CycleGraph(4)
890
+ sage: cycle2 = graphs.CycleGraph(6)
891
+ sage: cycle2.relabel(lambda v: v + 4)
892
+ sage: G = Graph()
893
+ sage: G.add_edges(cycle1.edges() + cycle2.edges())
894
+ sage: len(G.connected_components(sort=False))
895
+ 2
896
+ sage: G.is_matching_covered()
897
+ Traceback (most recent call last):
898
+ ...
899
+ ValueError: the graph is not connected
900
+
901
+ If the graph is trivial::
902
+
903
+ sage: G = Graph()
904
+ sage: G.is_matching_covered()
905
+ Traceback (most recent call last):
906
+ ...
907
+ ValueError: the graph is trivial
908
+ sage: H = graphs.CycleGraph(1)
909
+ sage: H.is_matching_covered()
910
+ Traceback (most recent call last):
911
+ ...
912
+ ValueError: the graph is trivial
913
+
914
+ Providing with a wrong matching::
915
+
916
+ sage: # needs networkx
917
+ sage: G = graphs.CompleteGraph(6)
918
+ sage: M = Graph(G.matching())
919
+ sage: M.add_edges([(0, 1), (0, 2)])
920
+ sage: G.is_matching_covered(matching=M)
921
+ Traceback (most recent call last):
922
+ ...
923
+ ValueError: the input is not a matching
924
+ sage: N = Graph(G.matching())
925
+ sage: N.add_edge(6, 7)
926
+ sage: G.is_matching_covered(matching=N)
927
+ Traceback (most recent call last):
928
+ ...
929
+ ValueError: the input is not a matching of the graph
930
+ sage: J = Graph()
931
+ sage: J.add_edges([(0, 1), (2, 3)])
932
+ sage: G.is_matching_covered(matching=J)
933
+ Traceback (most recent call last):
934
+ ...
935
+ ValueError: the input is not a perfect matching of the graph
936
+
937
+ Providing with a graph with a self-loop::
938
+
939
+ sage: G = graphs.PetersenGraph()
940
+ sage: G.allow_loops(True)
941
+ sage: G.add_edge(0, 0)
942
+ sage: G.is_matching_covered()
943
+ Traceback (most recent call last):
944
+ ...
945
+ ValueError: This method is not known to work on graphs with loops. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow loops using allow_loops().
946
+
947
+ REFERENCES:
948
+
949
+ - [LM2024]_
950
+
951
+ - [LZ2001]_
952
+
953
+ - [Tut1947]_
954
+
955
+ .. SEEALSO::
956
+ :meth:`~sage.graphs.graph.Graph.is_factor_critical`,
957
+ :meth:`~sage.graphs.graph.Graph.is_bicritical`
958
+
959
+ AUTHORS:
960
+
961
+ - Janmenjaya Panda (2024-06-23)
962
+ """
963
+ G._scream_if_not_simple(allow_multiple_edges=True)
964
+
965
+ # The graph must be nontrivial
966
+ if G.order() < 2:
967
+ raise ValueError("the graph is trivial")
968
+
969
+ # The graph must be connected
970
+ if not G.is_connected():
971
+ raise ValueError("the graph is not connected")
972
+
973
+ # The graph must have an even order
974
+ if G.order() % 2:
975
+ return (False, next(G.edge_iterator())) if coNP_certificate else False
976
+
977
+ # If the underlying simple graph is a complete graph of order two,
978
+ # the graph is matching covered
979
+ if G.order() == 2:
980
+ return (True, None) if coNP_certificate else True
981
+
982
+ from sage.graphs.graph import Graph
983
+ if matching:
984
+ # The input matching must be a valid perfect matching of the graph
985
+ M = Graph(matching)
986
+
987
+ if any(d != 1 for d in M.degree()):
988
+ raise ValueError("the input is not a matching")
989
+
990
+ if any(not G.has_edge(edge) for edge in M.edge_iterator()):
991
+ raise ValueError("the input is not a matching of the graph")
992
+
993
+ if (G.order() != M.order()) or (G.order() != 2*M.size()):
994
+ raise ValueError("the input is not a perfect matching of the graph")
995
+ else:
996
+ # A maximum matching of the graph is computed
997
+ M = Graph(G.matching(algorithm=algorithm, solver=solver, verbose=verbose,
998
+ integrality_tolerance=integrality_tolerance))
999
+
1000
+ # It must be a perfect matching
1001
+ if G.order() != M.order():
1002
+ return (False, next(M.edge_iterator())) if coNP_certificate else False
1003
+
1004
+ # Biparite graph:
1005
+ #
1006
+ # Given a connected bipartite graph G[A, B] with a perfect matching M.
1007
+ # Construct a directed graph D from G such that V(D) := V(G) and
1008
+ # for each edge in G direct the corresponding edge from A to B in D,
1009
+ # if it is in M or otherwise direct it from B to A. The graph G is
1010
+ # matching covered if and only if D is strongly connected.
1011
+
1012
+ if G.is_bipartite():
1013
+ A, _ = G.bipartite_sets()
1014
+ color = dict()
1015
+
1016
+ for u in G:
1017
+ color[u] = 0 if u in A else 1
1018
+
1019
+ from sage.graphs.digraph import DiGraph
1020
+ H = DiGraph()
1021
+
1022
+ for u, v in G.edge_iterator(labels=False):
1023
+ if color[u]:
1024
+ u, v = v, u
1025
+
1026
+ H.add_edge((u, v))
1027
+ if next(M.neighbor_iterator(u)) == v:
1028
+ H.add_edge((v, u))
1029
+
1030
+ # Check if H is strongly connected using Kosaraju's algorithm
1031
+ def dfs(v, visited, neighbor_iterator):
1032
+ stack = [v] # a stack of vertices
1033
+
1034
+ while stack:
1035
+ v = stack.pop()
1036
+ visited.add(v)
1037
+
1038
+ for u in neighbor_iterator(v):
1039
+ if u not in visited:
1040
+ stack.append(u)
1041
+
1042
+ root = next(H.vertex_iterator())
1043
+
1044
+ visited_in = set()
1045
+ dfs(root, visited_in, H.neighbor_in_iterator)
1046
+
1047
+ visited_out = set()
1048
+ dfs(root, visited_out, H.neighbor_out_iterator)
1049
+
1050
+ for edge in H.edge_iterator():
1051
+ u, v, _ = edge
1052
+ if (u not in visited_out) or (v not in visited_in):
1053
+ if not M.has_edge(edge):
1054
+ return (False, edge) if coNP_certificate else False
1055
+
1056
+ return (True, None) if coNP_certificate else True
1057
+
1058
+ # Nonbipartite graph:
1059
+ #
1060
+ # Given a nonbipartite graph G with a perfect matching M. The graph G is
1061
+ # matching covered if and only if for each edge uv not in M, there exists
1062
+ # an M-alternating odd length uv-path starting and ending with edges not
1063
+ # in M.
1064
+
1065
+ for u in G:
1066
+ v = next(M.neighbor_iterator(u))
1067
+
1068
+ even = M_alternating_even_mark(G, u, M)
1069
+
1070
+ for w in G.neighbor_iterator(v):
1071
+ if w != u and w not in even:
1072
+ return (False, (v, w)) if coNP_certificate else False
1073
+
1074
+ return (True, None) if coNP_certificate else True
1075
+
1076
+
1077
+ def matching(G, value_only=False, algorithm='Edmonds',
1078
+ use_edge_labels=False, solver=None, verbose=0,
1079
+ *, integrality_tolerance=1e-3):
1080
+ r"""
1081
+ Return a maximum weighted matching of the graph represented by the list
1082
+ of its edges.
1083
+
1084
+ For more information, see the :wikipedia:`Matching_(graph_theory)`.
1085
+
1086
+ Given a graph `G` such that each edge `e` has a weight `w_e`, a maximum
1087
+ matching is a subset `S` of the edges of `G` of maximum weight such that
1088
+ no two edges of `S` are incident with each other.
1089
+
1090
+ As an optimization problem, it can be expressed as:
1091
+
1092
+ .. MATH::
1093
+
1094
+ \mbox{Maximize : }&\sum_{e\in G.edges()} w_e b_e\\
1095
+ \mbox{Such that : }&\forall v \in G,
1096
+ \sum_{(u,v)\in G.edges()} b_{(u,v)}\leq 1\\
1097
+ &\forall x\in G, b_x\mbox{ is a binary variable}
1098
+
1099
+ INPUT:
1100
+
1101
+ - ``value_only`` -- boolean (default: ``False``); when set to ``True``,
1102
+ only the cardinal (or the weight) of the matching is returned
1103
+
1104
+ - ``algorithm`` -- string (default: ``'Edmonds'``)
1105
+
1106
+ - ``'Edmonds'`` selects Edmonds' algorithm as implemented in NetworkX
1107
+
1108
+ - ``'LP'`` uses a Linear Program formulation of the matching problem
1109
+
1110
+ - ``use_edge_labels`` -- boolean (default: ``False``)
1111
+
1112
+ - when set to ``True``, computes a weighted matching where each edge
1113
+ is weighted by its label (if an edge has no label, `1` is assumed)
1114
+
1115
+ - when set to ``False``, each edge has weight `1`
1116
+
1117
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer
1118
+ Linear Programming (MILP) solver to be used. If set to ``None``, the
1119
+ default one is used. For more information on MILP solvers and which
1120
+ default solver is used, see the method :meth:`solve
1121
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1122
+ :class:`MixedIntegerLinearProgram
1123
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
1124
+
1125
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity:
1126
+ set to 0 by default, which means quiet (only useful when ``algorithm
1127
+ == "LP"``)
1128
+
1129
+ - ``integrality_tolerance`` -- float; parameter for use with MILP
1130
+ solvers over an inexact base ring; see
1131
+ :meth:`MixedIntegerLinearProgram.get_values`.
1132
+
1133
+ OUTPUT:
1134
+
1135
+ - When ``value_only=False`` (default), this method returns an
1136
+ :class:`EdgesView` containing the edges of a maximum matching of `G`.
1137
+
1138
+ - When ``value_only=True``, this method returns the sum of the
1139
+ weights (default: ``1``) of the edges of a maximum matching of `G`.
1140
+ The type of the output may vary according to the type of the edge
1141
+ labels and the algorithm used.
1142
+
1143
+ ALGORITHM:
1144
+
1145
+ The problem is solved using Edmond's algorithm implemented in NetworkX,
1146
+ or using Linear Programming depending on the value of ``algorithm``.
1147
+
1148
+ EXAMPLES:
1149
+
1150
+ Maximum matching in a Pappus Graph::
1151
+
1152
+ sage: g = graphs.PappusGraph()
1153
+ sage: g.matching(value_only=True) # needs sage.networkx
1154
+ 9
1155
+
1156
+ Same test with the Linear Program formulation::
1157
+
1158
+ sage: g = graphs.PappusGraph()
1159
+ sage: g.matching(algorithm='LP', value_only=True) # needs sage.numerical.mip
1160
+ 9
1161
+
1162
+ .. PLOT::
1163
+
1164
+ g = graphs.PappusGraph()
1165
+ sphinx_plot(g.plot(edge_colors={"red":g.matching()}))
1166
+
1167
+ TESTS:
1168
+
1169
+ When ``use_edge_labels`` is set to ``False``, with Edmonds' algorithm
1170
+ and LP formulation::
1171
+
1172
+ sage: g = Graph([(0,1,0), (1,2,999), (2,3,-5)])
1173
+ sage: sorted(g.matching()) # needs sage.networkx
1174
+ [(0, 1, 0), (2, 3, -5)]
1175
+ sage: sorted(g.matching(algorithm='LP')) # needs sage.numerical.mip
1176
+ [(0, 1, 0), (2, 3, -5)]
1177
+
1178
+ When ``use_edge_labels`` is set to ``True``, with Edmonds' algorithm and
1179
+ LP formulation::
1180
+
1181
+ sage: g = Graph([(0,1,0), (1,2,999), (2,3,-5)])
1182
+ sage: g.matching(use_edge_labels=True) # needs sage.networkx
1183
+ [(1, 2, 999)]
1184
+ sage: g.matching(algorithm='LP', use_edge_labels=True) # needs sage.numerical.mip
1185
+ [(1, 2, 999)]
1186
+
1187
+ With loops and multiedges::
1188
+
1189
+ sage: edge_list = [(0,0,5), (0,1,1), (0,2,2), (0,3,3), (1,2,6)
1190
+ ....: , (1,2,3), (1,3,3), (2,3,3)]
1191
+ sage: g = Graph(edge_list, loops=True, multiedges=True)
1192
+ sage: m = g.matching(use_edge_labels=True) # needs sage.networkx
1193
+ sage: type(m) # needs sage.networkx
1194
+ <class 'sage.graphs.views.EdgesView'>
1195
+ sage: sorted(m) # needs sage.networkx
1196
+ [(0, 3, 3), (1, 2, 6)]
1197
+
1198
+ TESTS:
1199
+
1200
+ If ``algorithm`` is set to anything different from ``'Edmonds'`` or
1201
+ ``'LP'``, an exception is raised::
1202
+
1203
+ sage: g = graphs.PappusGraph()
1204
+ sage: g.matching(algorithm='somethingdifferent')
1205
+ Traceback (most recent call last):
1206
+ ...
1207
+ ValueError: algorithm must be set to either "Edmonds" or "LP"
1208
+ """
1209
+ from sage.graphs.generic_graph import _weight_if_real as weight
1210
+
1211
+ W = {}
1212
+ L = {}
1213
+ for u, v, l in G.edge_iterator():
1214
+ if u is v:
1215
+ continue
1216
+ fuv = frozenset((u, v))
1217
+ if fuv not in L or (use_edge_labels and W[fuv] < weight(l)):
1218
+ L[fuv] = l
1219
+ if use_edge_labels:
1220
+ W[fuv] = weight(l)
1221
+
1222
+ if algorithm == "Edmonds":
1223
+ import networkx
1224
+ g = networkx.Graph()
1225
+ if use_edge_labels:
1226
+ for (u, v), w in W.items():
1227
+ g.add_edge(u, v, weight=w)
1228
+ else:
1229
+ for u, v in L:
1230
+ g.add_edge(u, v)
1231
+ d = networkx.max_weight_matching(g)
1232
+ if value_only:
1233
+ if use_edge_labels:
1234
+ return sum(W[frozenset(e)] for e in d)
1235
+ return Integer(len(d))
1236
+
1237
+ from sage.graphs.graph import Graph
1238
+ return EdgesView(Graph([(u, v, L[frozenset((u, v))]) for u, v in d],
1239
+ format='list_of_edges'))
1240
+
1241
+ elif algorithm == "LP":
1242
+ g = G
1243
+ from sage.numerical.mip import MixedIntegerLinearProgram
1244
+ # returns the weight of an edge considering it may not be
1245
+ # weighted ...
1246
+ p = MixedIntegerLinearProgram(maximization=True, solver=solver)
1247
+ b = p.new_variable(binary=True)
1248
+ if use_edge_labels:
1249
+ p.set_objective(p.sum(w * b[fe] for fe, w in W.items()))
1250
+ else:
1251
+ p.set_objective(p.sum(b[fe] for fe in L))
1252
+ # for any vertex v, there is at most one edge incident to v in
1253
+ # the maximum matching
1254
+ for v in g:
1255
+ p.add_constraint(p.sum(b[frozenset(e)] for e in G.edge_iterator(vertices=[v], labels=False)
1256
+ if e[0] != e[1]), max=1)
1257
+
1258
+ p.solve(log=verbose)
1259
+ b = p.get_values(b, convert=bool, tolerance=integrality_tolerance)
1260
+ if value_only:
1261
+ if use_edge_labels:
1262
+ return sum(w for fe, w in W.items() if b[fe])
1263
+ return Integer(sum(1 for fe in L if b[fe]))
1264
+
1265
+ from sage.graphs.graph import Graph
1266
+ return EdgesView(Graph([(u, v, L[frozenset((u, v))])
1267
+ for u, v in L if b[frozenset((u, v))]],
1268
+ format='list_of_edges'))
1269
+
1270
+ raise ValueError('algorithm must be set to either "Edmonds" or "LP"')
1271
+
1272
+
1273
+ def perfect_matchings(G, labels=False):
1274
+ r"""
1275
+ Return an iterator over all perfect matchings of the graph.
1276
+
1277
+ ALGORITHM:
1278
+
1279
+ Choose a vertex `v`, then recurse through all edges incident to `v`,
1280
+ removing one edge at a time whenever an edge is added to a matching.
1281
+
1282
+ INPUT:
1283
+
1284
+ - ``labels`` -- boolean (default: ``False``); when ``True``, the edges
1285
+ in each perfect matching are triples (containing the label as the
1286
+ third element), otherwise the edges are pairs.
1287
+
1288
+ .. SEEALSO::
1289
+
1290
+ :meth:`matching`
1291
+
1292
+ EXAMPLES::
1293
+
1294
+ sage: G=graphs.GridGraph([2,3])
1295
+ sage: for m in G.perfect_matchings():
1296
+ ....: print(sorted(m))
1297
+ [((0, 0), (0, 1)), ((0, 2), (1, 2)), ((1, 0), (1, 1))]
1298
+ [((0, 0), (1, 0)), ((0, 1), (0, 2)), ((1, 1), (1, 2))]
1299
+ [((0, 0), (1, 0)), ((0, 1), (1, 1)), ((0, 2), (1, 2))]
1300
+
1301
+ sage: G = graphs.CompleteGraph(4)
1302
+ sage: for m in G.perfect_matchings(labels=True):
1303
+ ....: print(sorted(m))
1304
+ [(0, 1, None), (2, 3, None)]
1305
+ [(0, 2, None), (1, 3, None)]
1306
+ [(0, 3, None), (1, 2, None)]
1307
+
1308
+ sage: G = Graph([[1,-1,'a'], [2,-2, 'b'], [1,-2,'x'], [2,-1,'y']])
1309
+ sage: sorted(sorted(m) for m in G.perfect_matchings(labels=True))
1310
+ [[(-2, 1, 'x'), (-1, 2, 'y')], [(-2, 2, 'b'), (-1, 1, 'a')]]
1311
+
1312
+ sage: G = graphs.CompleteGraph(8)
1313
+ sage: mpc = G.matching_polynomial().coefficients(sparse=False)[0] # needs sage.libs.flint
1314
+ sage: len(list(G.perfect_matchings())) == mpc # needs sage.libs.flint
1315
+ True
1316
+
1317
+ sage: G = graphs.PetersenGraph().copy(immutable=True)
1318
+ sage: [sorted(m) for m in G.perfect_matchings()]
1319
+ [[(0, 1), (2, 3), (4, 9), (5, 7), (6, 8)],
1320
+ [(0, 1), (2, 7), (3, 4), (5, 8), (6, 9)],
1321
+ [(0, 4), (1, 2), (3, 8), (5, 7), (6, 9)],
1322
+ [(0, 4), (1, 6), (2, 3), (5, 8), (7, 9)],
1323
+ [(0, 5), (1, 2), (3, 4), (6, 8), (7, 9)],
1324
+ [(0, 5), (1, 6), (2, 7), (3, 8), (4, 9)]]
1325
+
1326
+ sage: list(Graph().perfect_matchings())
1327
+ [[]]
1328
+
1329
+ sage: G = graphs.CompleteGraph(5)
1330
+ sage: list(G.perfect_matchings())
1331
+ []
1332
+ """
1333
+ if not G:
1334
+ yield []
1335
+ return
1336
+ if G.order() % 2 or any(len(cc) % 2 for cc in G.connected_components(sort=False)):
1337
+ return
1338
+
1339
+ def rec(G):
1340
+ """
1341
+ Iterator over all perfect matchings of a simple graph `G`.
1342
+ """
1343
+ if not G:
1344
+ yield []
1345
+ return
1346
+ if G.order() % 2 == 0:
1347
+ v = next(G.vertex_iterator())
1348
+ Nv = list(G.neighbor_iterator(v))
1349
+ G.delete_vertex(v)
1350
+ for u in Nv:
1351
+ Nu = list(G.neighbor_iterator(u))
1352
+ G.delete_vertex(u)
1353
+ for partial_matching in rec(G):
1354
+ partial_matching.append((u, v))
1355
+ yield partial_matching
1356
+ G.add_vertex(u)
1357
+ G.add_edges((u, nu) for nu in Nu)
1358
+ G.add_vertex(v)
1359
+ G.add_edges((v, nv) for nv in Nv)
1360
+
1361
+ # We create a mutable copy of the graph and remove its loops, if any
1362
+ G_copy = G.copy(immutable=False)
1363
+ G_copy.allow_loops(False)
1364
+
1365
+ # We create a mapping from frozen unlabeled edges to (labeled) edges.
1366
+ # This ease for instance the manipulation of multiedges (if any)
1367
+ edges = {}
1368
+ for e in G_copy.edges(sort=False, labels=labels):
1369
+ f = frozenset(e[:2])
1370
+ if f in edges:
1371
+ edges[f].append(e)
1372
+ else:
1373
+ edges[f] = [e]
1374
+
1375
+ # We now get rid of multiple edges, if any
1376
+ G_copy.allow_multiple_edges(False)
1377
+
1378
+ # For each unlabeled matching, we yield all its possible labelings
1379
+ import itertools
1380
+
1381
+ for m in rec(G_copy):
1382
+ yield from itertools.product(*[edges[frozenset(e)] for e in m])
1383
+
1384
+
1385
+ def M_alternating_even_mark(G, vertex, matching):
1386
+ r"""
1387
+ Return the vertices reachable from ``vertex`` via an even alternating path
1388
+ starting with a non-matching edge.
1389
+
1390
+ This method implements the algorithm proposed in [LR2004]_. Note that
1391
+ the complexity of the algorithm is linear in number of edges.
1392
+
1393
+ INPUT:
1394
+
1395
+ - ``vertex`` -- a vertex of the graph
1396
+
1397
+ - ``matching`` -- a matching of the graph; it can be given using any
1398
+ valid input format of :class:`~sage.graphs.graph.Graph`
1399
+
1400
+ OUTPUT:
1401
+
1402
+ - ``even`` -- the set of vertices each of which is reachable from the
1403
+ provided vertex through a path starting with an edge not in the
1404
+ matching and ending with an edge in the matching; note that a note that a
1405
+ :class:`ValueError` is returned if the graph is not simple
1406
+
1407
+ EXAMPLES:
1408
+
1409
+ Show the list of required vertices for a graph `G` with a matching `M`
1410
+ for a vertex `u`::
1411
+
1412
+ sage: # needs networkx
1413
+ sage: G = graphs.CycleGraph(3)
1414
+ sage: M = G.matching()
1415
+ sage: M
1416
+ [(0, 2, None)]
1417
+ sage: from sage.graphs.matching import M_alternating_even_mark
1418
+ sage: S0 = M_alternating_even_mark(G, 0, M)
1419
+ sage: S0
1420
+ {0}
1421
+ sage: S1 = M_alternating_even_mark(G, 1, M)
1422
+ sage: S1
1423
+ {0, 1, 2}
1424
+
1425
+ The result is equivalent for the underlying simple graph of the provided
1426
+ graph, if the other parameters provided are the same::
1427
+
1428
+ sage: # needs networkx
1429
+ sage: G = graphs.CompleteBipartiteGraph(3, 3)
1430
+ sage: G.allow_multiple_edges(True)
1431
+ sage: G.add_edge(0, 3)
1432
+ sage: M = G.matching()
1433
+ sage: u = 0
1434
+ sage: from sage.graphs.matching import M_alternating_even_mark
1435
+ sage: S = M_alternating_even_mark(G, u, M)
1436
+ sage: S
1437
+ {0, 1, 2}
1438
+ sage: T = M_alternating_even_mark(G.to_simple(), u, M)
1439
+ sage: T
1440
+ {0, 1, 2}
1441
+
1442
+ For a factor critical graph `G` (for instance, a wheel graph of an odd
1443
+ order) with a near perfect matching `M` and `u` being the (unique)
1444
+ `M`-exposed vertex, each vertex in `G` is reachable from `u` through an
1445
+ even length `M`-alternating path as described above::
1446
+
1447
+ sage: # needs networkx
1448
+ sage: G = graphs.WheelGraph(11)
1449
+ sage: M = Graph(G.matching())
1450
+ sage: G.is_factor_critical(M)
1451
+ True
1452
+ sage: for v in G:
1453
+ ....: if v not in M:
1454
+ ....: break
1455
+ ....:
1456
+ sage: from sage.graphs.matching import M_alternating_even_mark
1457
+ sage: S = M_alternating_even_mark(G, v, M)
1458
+ sage: S == set(G)
1459
+ True
1460
+
1461
+ For a matching covered graph `G` (for instance, `K_4 \odot K_{3,3}`) with a
1462
+ perfect matching `M` and for some vertex `u` with `v` being its `M`-matched
1463
+ neighbor, each neighbor of `v` is reachable from `u` through an even length
1464
+ `M`-alternating path as described above::
1465
+
1466
+ sage: # needs networkx
1467
+ sage: G = Graph()
1468
+ sage: G.add_edges([
1469
+ ....: (0, 2), (0, 3), (0, 4), (1, 2),
1470
+ ....: (1, 3), (1, 4), (2, 5), (3, 6),
1471
+ ....: (4, 7), (5, 6), (5, 7), (6, 7)
1472
+ ....: ])
1473
+ sage: M = Graph(G.matching())
1474
+ sage: G.is_matching_covered(M)
1475
+ True
1476
+ sage: u = 0
1477
+ sage: v = next(M.neighbor_iterator(u))
1478
+ sage: from sage.graphs.matching import M_alternating_even_mark
1479
+ sage: S = M_alternating_even_mark(G, u, M)
1480
+ sage: (set(G.neighbor_iterator(v))).issubset(S)
1481
+ True
1482
+
1483
+ For a bicritical graph `G` (for instance, the Petersen graph) with a
1484
+ perfect matching `M` and for some vertex `u` with its `M`-matched neighbor
1485
+ being `v`, each vertex of the graph distinct from `v` is reachable from `u`
1486
+ through an even length `M`-alternating path as described above::
1487
+
1488
+ sage: # needs networkx
1489
+ sage: G = graphs.PetersenGraph()
1490
+ sage: M = Graph(G.matching())
1491
+ sage: G.is_bicritical(M)
1492
+ True
1493
+ sage: import random
1494
+ sage: u = random.choice(list(G))
1495
+ sage: v = next(M.neighbor_iterator(u))
1496
+ sage: from sage.graphs.matching import M_alternating_even_mark
1497
+ sage: S = M_alternating_even_mark(G, u, M)
1498
+ sage: S == (set(G) - {v})
1499
+ True
1500
+
1501
+ TESTS:
1502
+
1503
+ Giving a wrong vertex::
1504
+
1505
+ sage: # needs networkx
1506
+ sage: G = graphs.HexahedralGraph()
1507
+ sage: M = G.matching()
1508
+ sage: u = G.order()
1509
+ sage: from sage.graphs.matching import M_alternating_even_mark
1510
+ sage: S = M_alternating_even_mark(G, u, M)
1511
+ Traceback (most recent call last):
1512
+ ...
1513
+ ValueError: '8' is not a vertex of the graph
1514
+
1515
+ Giving a wrong matching::
1516
+
1517
+ sage: # needs networkx
1518
+ sage: from sage.graphs.matching import M_alternating_even_mark
1519
+ sage: G = graphs.CompleteGraph(6)
1520
+ sage: M = [(0, 1), (0, 2)]
1521
+ sage: u = 0
1522
+ sage: S = M_alternating_even_mark(G, u, M)
1523
+ Traceback (most recent call last):
1524
+ ...
1525
+ ValueError: the input is not a matching
1526
+ sage: G = graphs.CompleteBipartiteGraph(3, 3)
1527
+ sage: M = [(2*i, 2*i + 1) for i in range(4)]
1528
+ sage: u = 0
1529
+ sage: S = M_alternating_even_mark(G, u, M)
1530
+ Traceback (most recent call last):
1531
+ ...
1532
+ ValueError: the input is not a matching of the graph
1533
+
1534
+ REFERENCES:
1535
+
1536
+ - [LR2004]_
1537
+
1538
+ .. SEEALSO::
1539
+ :meth:`~sage.graphs.graph.Graph.is_factor_critical`,
1540
+ :meth:`~sage.graphs.graph.Graph.is_matching_covered`,
1541
+ :meth:`~sage.graphs.graph.Graph.is_bicritical`
1542
+
1543
+ AUTHORS:
1544
+
1545
+ - Janmenjaya Panda (2024-06-17)
1546
+ """
1547
+ # The input vertex must be a valid vertex of the graph
1548
+ if vertex not in G:
1549
+ raise ValueError("'{}' is not a vertex of the graph".format(vertex))
1550
+
1551
+ # The result is equivalent for the underlying simple graph of the provided
1552
+ # graph. So, the underlying simple graph is considered for implementational
1553
+ # simplicity.
1554
+ G_simple = G.to_simple()
1555
+
1556
+ # The input matching must be a valid matching of the graph
1557
+ from sage.graphs.graph import Graph
1558
+ M = Graph(matching)
1559
+ if any(d != 1 for d in M.degree()):
1560
+ raise ValueError("the input is not a matching")
1561
+
1562
+ if any(not G.has_edge(edge) for edge in M.edge_iterator()):
1563
+ raise ValueError("the input is not a matching of the graph")
1564
+
1565
+ # Build an M-alternating tree T rooted at vertex
1566
+ import itertools
1567
+ from queue import Queue
1568
+
1569
+ q = Queue()
1570
+ q.put(vertex)
1571
+
1572
+ even = set([vertex])
1573
+ odd = set()
1574
+ predecessor = {vertex: vertex}
1575
+ rank = {vertex: 0}
1576
+
1577
+ if vertex in M:
1578
+ u = next(M.neighbor_iterator(vertex))
1579
+ predecessor[u] = None
1580
+ rank[u] = -1
1581
+ odd.add(u)
1582
+
1583
+ while not q.empty():
1584
+ x = q.get()
1585
+ for y in G_simple.neighbor_iterator(x):
1586
+ if y in odd:
1587
+ continue
1588
+ elif y in even:
1589
+ # Search t := LCA(x, y)
1590
+ ancestor_x = [x]
1591
+ ancestor_y = [y]
1592
+
1593
+ # Loop over until the nearest common ancestor of x and y is reached
1594
+ while ancestor_x[-1] != ancestor_y[-1]:
1595
+ if rank[ancestor_x[-1]] > rank[ancestor_y[-1]]:
1596
+ ancestor_x.append(predecessor[ancestor_x[-1]])
1597
+
1598
+ elif rank[ancestor_x[-1]] < rank[ancestor_y[-1]]:
1599
+ ancestor_y.append(predecessor[ancestor_y[-1]])
1600
+
1601
+ else:
1602
+ ancestor_x.append(predecessor[ancestor_x[-1]])
1603
+ ancestor_y.append(predecessor[ancestor_y[-1]])
1604
+
1605
+ lcs = ancestor_x.pop()
1606
+ ancestor_y.pop()
1607
+ # Set t as pred of all vertices of the chains and add
1608
+ # vertices marked odd to the queue
1609
+ next_rank_to_lcs_rank = rank[lcs] + 1
1610
+
1611
+ for a in itertools.chain(ancestor_x, ancestor_y):
1612
+ predecessor[a] = lcs
1613
+ rank[a] = next_rank_to_lcs_rank
1614
+
1615
+ if a in odd:
1616
+ even.add(a)
1617
+ odd.discard(a)
1618
+ q.put(a)
1619
+
1620
+ elif y in M:
1621
+ # y has not been visited yet
1622
+ z = next(M.neighbor_iterator(y))
1623
+ odd.add(y)
1624
+ even.add(z)
1625
+ q.put(z)
1626
+
1627
+ predecessor[y] = x
1628
+ predecessor[z] = y
1629
+
1630
+ rank[y] = rank[x] + 1
1631
+ rank[z] = rank[y] + 1
1632
+
1633
+ return even