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,2732 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ # autopep8: off
3
+ r"""
4
+ Bipartite graphs
5
+
6
+ This module implements bipartite graphs.
7
+
8
+ AUTHORS:
9
+
10
+ - Robert L. Miller (2008-01-20): initial version
11
+
12
+ - Ryan W. Hinton (2010-03-04): overrides for adding and deleting vertices
13
+ and edges
14
+
15
+ - Enjeck M. Cleopatra (2022): fixes incorrect partite sets and adds graph
16
+ creation from graph6 string
17
+
18
+ TESTS::
19
+
20
+ sage: B = graphs.CompleteBipartiteGraph(7, 9)
21
+ sage: loads(dumps(B)) == B
22
+ True
23
+
24
+ sage: B = BipartiteGraph(graphs.CycleGraph(4))
25
+ sage: B == B.copy()
26
+ True
27
+ sage: type(B.copy())
28
+ <class 'sage.graphs.bipartite_graph.BipartiteGraph'>
29
+ """
30
+ # ****************************************************************************
31
+ # Copyright (C) 2008 Robert L. Miller <rlmillster@gmail.com>
32
+ # 2018 Julian Rüth <julian.rueth@fsfe.org>
33
+ # 2022 Enjeck M. Cleopatra <enjeckc1e0@gmail.com>
34
+ #
35
+ # This program is free software: you can redistribute it and/or modify
36
+ # it under the terms of the GNU General Public License as published by
37
+ # the Free Software Foundation, either version 2 of the License, or
38
+ # (at your option) any later version.
39
+ # https://www.gnu.org/licenses/
40
+ # ****************************************************************************
41
+ from collections import defaultdict
42
+ from collections.abc import Iterable
43
+ import itertools
44
+
45
+ from .generic_graph import GenericGraph
46
+ from .graph import Graph
47
+ from sage.rings.integer import Integer
48
+ from sage.misc.cachefunc import cached_method
49
+ from sage.misc.lazy_import import lazy_import
50
+
51
+ lazy_import('networkx', ['MultiGraph', 'Graph'], as_=['networkx_MultiGraph', 'networkx_Graph'])
52
+
53
+
54
+ class BipartiteGraph(Graph):
55
+ r"""
56
+ Bipartite graph.
57
+
58
+ INPUT:
59
+
60
+ - ``data`` -- can be any of the following:
61
+
62
+ #. Empty or ``None`` (creates an empty graph).
63
+
64
+ #. An arbitrary graph.
65
+
66
+ #. A reduced adjacency matrix.
67
+
68
+ A reduced adjacency matrix contains only the non-redundant
69
+ portion of the full adjacency matrix for the bipartite graph.
70
+ Specifically, for zero matrices of the appropriate size, for
71
+ the reduced adjacency matrix ``H``, the full adjacency matrix
72
+ is ``[[0, H'], [H, 0]]``. The columns correspond to vertices
73
+ on the left, and the rows correspond to vertices on the
74
+ right.
75
+
76
+ #. A file in alist format.
77
+
78
+ The alist file format is described at
79
+ http://www.inference.phy.cam.ac.uk/mackay/codes/alist.html
80
+
81
+ #. A ``graph6`` string (see documentation of :meth:`~graph6_string`).
82
+
83
+ #. From a NetworkX bipartite graph.
84
+
85
+ - ``partition`` -- (default: ``None``) a tuple defining vertices of the left
86
+ and right partition of the graph. Partitions will be determined
87
+ automatically if ``partition`` is ``None``.
88
+
89
+ - ``check`` -- boolean (default: ``True``); if ``True``, an invalid input
90
+ partition raises an exception. In the other case offending edges simply
91
+ won't be included.
92
+
93
+ - ``loops`` -- ignored; bipartite graphs cannot have loops
94
+
95
+ - ``multiedges`` -- boolean (default: ``None``); whether to allow multiple
96
+ edges
97
+
98
+ - ``weighted`` -- boolean (default: ``None``); whether graph thinks of
99
+ itself as weighted or not. See ``self.weighted()``
100
+
101
+ - ``hash_labels`` -- boolean (default: ``None``); whether to include edge
102
+ labels during hashing. This parameter defaults to ``True`` if the graph is
103
+ weighted. This parameter is ignored if the graph is mutable.
104
+ Beware that trying to hash unhashable labels will raise an error.
105
+
106
+ .. NOTE::
107
+
108
+ All remaining arguments are passed to the ``Graph`` constructor
109
+
110
+ EXAMPLES:
111
+
112
+ #. No inputs or ``None`` for the input creates an empty graph::
113
+
114
+ sage: B = BipartiteGraph()
115
+ sage: type(B)
116
+ <class 'sage.graphs.bipartite_graph.BipartiteGraph'>
117
+ sage: B.order()
118
+ 0
119
+ sage: B == BipartiteGraph(None)
120
+ True
121
+
122
+ #. From a graph: without any more information, finds a bipartition::
123
+
124
+ sage: B = BipartiteGraph(graphs.CycleGraph(4))
125
+ sage: B = BipartiteGraph(graphs.CycleGraph(5))
126
+ Traceback (most recent call last):
127
+ ...
128
+ ValueError: input graph is not bipartite
129
+ sage: G = Graph({0: [5, 6], 1: [4, 5], 2: [4, 6], 3: [4, 5, 6]})
130
+ sage: B = BipartiteGraph(G)
131
+ sage: B == G
132
+ True
133
+ sage: B.left
134
+ {0, 1, 2, 3}
135
+ sage: B.right
136
+ {4, 5, 6}
137
+ sage: B = BipartiteGraph({0: [5, 6], 1: [4, 5], 2: [4, 6], 3: [4, 5, 6]})
138
+ sage: B == G
139
+ True
140
+ sage: B.left
141
+ {0, 1, 2, 3}
142
+ sage: B.right
143
+ {4, 5, 6}
144
+
145
+ #. If a ``Graph`` or ``DiGraph`` is used as data, you can specify
146
+ a partition using ``partition`` argument. Note that if such
147
+ graph is not bipartite, then Sage will raise an error. However,
148
+ if one specifies ``check=False``, the offending edges are
149
+ simply deleted (along with those vertices not appearing in
150
+ either list). We also lump creating one bipartite graph from
151
+ another into this category::
152
+
153
+ sage: P = graphs.PetersenGraph()
154
+ sage: partition = [list(range(5)), list(range(5, 10))]
155
+ sage: B = BipartiteGraph(P, partition)
156
+ Traceback (most recent call last):
157
+ ...
158
+ TypeError: input graph is not bipartite with respect to the given partition
159
+
160
+ sage: B = BipartiteGraph(P, partition, check=False)
161
+ sage: B.left
162
+ {0, 1, 2, 3, 4}
163
+ sage: B.show() # needs sage.plot
164
+
165
+ ::
166
+
167
+ sage: G = Graph({0: [5, 6], 1: [4, 5], 2: [4, 6], 3: [4, 5, 6]})
168
+ sage: B = BipartiteGraph(G)
169
+ sage: B2 = BipartiteGraph(B)
170
+ sage: B == B2
171
+ True
172
+ sage: B3 = BipartiteGraph(G, [list(range(4)), list(range(4, 7))])
173
+ sage: B3
174
+ Bipartite graph on 7 vertices
175
+ sage: B3 == B2
176
+ True
177
+
178
+ ::
179
+
180
+ sage: G = Graph({0: [], 1: [], 2: []})
181
+ sage: part = (list(range(2)), [2])
182
+ sage: B = BipartiteGraph(G, part)
183
+ sage: B2 = BipartiteGraph(B)
184
+ sage: B == B2
185
+ True
186
+
187
+ ::
188
+
189
+ sage: d = DiGraph(6)
190
+ sage: d.add_edge(0, 1)
191
+ sage: part=[[1, 2, 3], [0, 4, 5]]
192
+ sage: b = BipartiteGraph(d, part)
193
+ sage: b.left
194
+ {1, 2, 3}
195
+ sage: b.right
196
+ {0, 4, 5}
197
+
198
+ #. From a reduced adjacency matrix::
199
+
200
+ sage: # needs sage.modules
201
+ sage: M = Matrix([(1,1,1,0,0,0,0), (1,0,0,1,1,0,0),
202
+ ....: (0,1,0,1,0,1,0), (1,1,0,1,0,0,1)])
203
+ sage: M
204
+ [1 1 1 0 0 0 0]
205
+ [1 0 0 1 1 0 0]
206
+ [0 1 0 1 0 1 0]
207
+ [1 1 0 1 0 0 1]
208
+ sage: H = BipartiteGraph(M); H
209
+ Bipartite graph on 11 vertices
210
+ sage: H.edges(sort=True)
211
+ [(0, 7, None),
212
+ (0, 8, None),
213
+ (0, 10, None),
214
+ (1, 7, None),
215
+ (1, 9, None),
216
+ (1, 10, None),
217
+ (2, 7, None),
218
+ (3, 8, None),
219
+ (3, 9, None),
220
+ (3, 10, None),
221
+ (4, 8, None),
222
+ (5, 9, None),
223
+ (6, 10, None)]
224
+
225
+ ::
226
+
227
+ sage: M = Matrix([(1, 1, 2, 0, 0), (0, 2, 1, 1, 1), (0, 1, 2, 1, 1)]) # needs sage.modules
228
+ sage: B = BipartiteGraph(M, multiedges=True, sparse=True) # needs sage.modules
229
+ sage: B.edges(sort=True) # needs sage.modules
230
+ [(0, 5, None),
231
+ (1, 5, None),
232
+ (1, 6, None),
233
+ (1, 6, None),
234
+ (1, 7, None),
235
+ (2, 5, None),
236
+ (2, 5, None),
237
+ (2, 6, None),
238
+ (2, 7, None),
239
+ (2, 7, None),
240
+ (3, 6, None),
241
+ (3, 7, None),
242
+ (4, 6, None),
243
+ (4, 7, None)]
244
+
245
+ ::
246
+
247
+ sage: # needs sage.modules sage.rings.finite_rings
248
+ sage: F.<a> = GF(4)
249
+ sage: MS = MatrixSpace(F, 2, 3)
250
+ sage: M = MS.matrix([[0, 1, a + 1], [a, 1, 1]])
251
+ sage: B = BipartiteGraph(M, weighted=True, sparse=True)
252
+ sage: B.edges(sort=True)
253
+ [(0, 4, a), (1, 3, 1), (1, 4, 1), (2, 3, a + 1), (2, 4, 1)]
254
+ sage: B.weighted()
255
+ True
256
+
257
+ #. From an alist file::
258
+
259
+ sage: import tempfile
260
+ sage: with tempfile.NamedTemporaryFile(mode='w+t') as f:
261
+ ....: _ = f.write("7 4 \n 3 4 \n 3 3 1 3 1 1 1 \n\
262
+ ....: 3 3 3 4 \n 1 2 4 \n 1 3 4 \n 1 0 0 \n\
263
+ ....: 2 3 4 \n 2 0 0 \n 3 0 0 \n 4 0 0 \n\
264
+ ....: 1 2 3 0 \n 1 4 5 0 \n 2 4 6 0 \n\
265
+ ....: 1 2 4 7 \n")
266
+ ....: f.flush()
267
+ ....: B = BipartiteGraph(f.name)
268
+ sage: B.is_isomorphic(H) # needs sage.modules
269
+ True
270
+
271
+ #. From a ``graph6`` string::
272
+
273
+ sage: B = BipartiteGraph('Bo')
274
+ sage: B
275
+ Bipartite graph on 3 vertices
276
+ sage: B.left
277
+ {0}
278
+ sage: B.right
279
+ {1, 2}
280
+
281
+ ::
282
+
283
+ sage: B = BipartiteGraph('F?^T_\n', format='graph6')
284
+ sage: B.vertices(sort=True)
285
+ [0, 1, 2, 3, 4, 5, 6]
286
+ sage: B.edges(sort=True)
287
+ [(0, 5, None), (0, 6, None), (1, 4, None), (1, 5, None), (2, 4, None),
288
+ (2, 6, None), (3, 4, None), (3, 5, None), (3, 6, None)]
289
+ sage: B.left
290
+ {0, 1, 2, 3}
291
+ sage: B.right
292
+ {4, 5, 6}
293
+
294
+ ::
295
+ sage: B = BipartiteGraph('Bo', partition=[[0], [1, 2]])
296
+ sage: B.left
297
+ {0}
298
+ sage: B.right
299
+ {1, 2}
300
+
301
+ ::
302
+
303
+ sage: B = BipartiteGraph('F?^T_\n', partition=[[0, 1, 2], [3, 4, 5, 6]])
304
+ Traceback (most recent call last):
305
+ ...
306
+ TypeError: input graph is not bipartite with respect to the given partition
307
+
308
+ sage: B = BipartiteGraph('F?^T_\n', partition=[[0, 1, 2], [3, 4, 5, 6]], check=False)
309
+ sage: B.left
310
+ {0, 1, 2}
311
+ sage: B.show() # needs sage.plot
312
+
313
+ #. From a NetworkX bipartite graph::
314
+
315
+ sage: # needs networkx
316
+ sage: import networkx
317
+ sage: G = graphs.OctahedralGraph()
318
+ sage: N = networkx.make_clique_bipartite(G.networkx_graph())
319
+ sage: B = BipartiteGraph(N)
320
+
321
+ TESTS:
322
+
323
+ Make sure we can create a ``BipartiteGraph`` with keywords but no positional
324
+ arguments (:issue:`10958`)::
325
+
326
+ sage: B = BipartiteGraph(multiedges=True)
327
+ sage: B.allows_multiple_edges()
328
+ True
329
+
330
+ Ensure that we can construct a ``BipartiteGraph`` with isolated vertices via
331
+ the reduced adjacency matrix (:issue:`10356`)::
332
+
333
+ sage: # needs sage.modules
334
+ sage: a = BipartiteGraph(matrix(2, 2, [1, 0, 1, 0]))
335
+ sage: a
336
+ Bipartite graph on 4 vertices
337
+ sage: a.vertices(sort=True)
338
+ [0, 1, 2, 3]
339
+ sage: g = BipartiteGraph(matrix(4, 4, [1] * 4 + [0] * 12))
340
+ sage: g.vertices(sort=True)
341
+ [0, 1, 2, 3, 4, 5, 6, 7]
342
+ sage: sorted(g.left.union(g.right))
343
+ [0, 1, 2, 3, 4, 5, 6, 7]
344
+
345
+ Make sure that loops are not allowed (:issue:`23275`)::
346
+
347
+ sage: B = BipartiteGraph(loops=True)
348
+ Traceback (most recent call last):
349
+ ...
350
+ ValueError: loops are not allowed in bipartite graphs
351
+ sage: B = BipartiteGraph(loops=None)
352
+ sage: B.allows_loops()
353
+ False
354
+ sage: B.add_edge(0, 0)
355
+ Traceback (most recent call last):
356
+ ...
357
+ ValueError: cannot add edge from 0 to 0 in graph without loops
358
+ """
359
+
360
+ def __init__(self, data=None, partition=None, check=True, hash_labels=None, *args, **kwds):
361
+ """
362
+ Create a bipartite graph.
363
+
364
+ See documentation ``BipartiteGraph?`` for detailed information.
365
+
366
+ EXAMPLES::
367
+
368
+ sage: P = graphs.PetersenGraph()
369
+ sage: partition = [list(range(5)), list(range(5, 10))]
370
+ sage: B = BipartiteGraph(P, partition, check=False)
371
+
372
+ TESTS:
373
+
374
+ Check that :issue:`33249` is fixed::
375
+
376
+ sage: G = BipartiteGraph({2:[1], 3:[1], 4:[5]}, partition=([2,3,4],[1,5]))
377
+ sage: print(G.left, G.right)
378
+ {2, 3, 4} {1, 5}
379
+ sage: G = BipartiteGraph({2:[1], 3:[1]}, partition=([1,2],[3]), check=True)
380
+ Traceback (most recent call last):
381
+ ...
382
+ TypeError: input graph is not bipartite with respect to the given partition
383
+ sage: G = BipartiteGraph({2:[1], 3:[1], 4:[5]}, partition=([2,3,4],[1]))
384
+ Traceback (most recent call last):
385
+ ...
386
+ ValueError: not all vertices appear in partition
387
+ sage: G = BipartiteGraph({2:[1], 3:[1], 4:[5]}, partition=([2,3,4],[1, 2]))
388
+ Traceback (most recent call last):
389
+ ...
390
+ ValueError: the parts are not disjoint
391
+ sage: G = BipartiteGraph({2:[1], 3:[1], 4:[5]}, partition=([2, 3, 4], [1, 7]))
392
+ Traceback (most recent call last):
393
+ ...
394
+ LookupError: vertex (7) is not a vertex of the graph
395
+
396
+ Check that :issue:`39295` is fixed::
397
+
398
+ sage: # needs sage.modules
399
+ sage: B = BipartiteGraph(matrix([[1, 1], [1, 1]]), immutable=True)
400
+ sage: print(B.vertices(), B.edges())
401
+ [0, 1, 2, 3] [(0, 2, None), (0, 3, None), (1, 2, None), (1, 3, None)]
402
+ sage: B.add_vertices([4], left=True)
403
+ Traceback (most recent call last):
404
+ ...
405
+ ValueError: graph is immutable; please change a copy instead (use function copy())
406
+ """
407
+ if kwds is None:
408
+ kwds = {'loops': False}
409
+ else:
410
+ if 'loops' in kwds and kwds['loops']:
411
+ raise ValueError('loops are not allowed in bipartite graphs')
412
+ kwds['loops'] = False
413
+
414
+ if data is None:
415
+ if partition is not None and check:
416
+ if partition[0] or partition[1]:
417
+ raise ValueError("invalid partition")
418
+ Graph.__init__(self, **kwds)
419
+ self.left = set()
420
+ self.right = set()
421
+ self._hash_labels = hash_labels
422
+ return
423
+
424
+ # need to turn off partition checking for Graph.__init__() adding
425
+ # vertices and edges; methods are restored at the end of big "if"
426
+ # statement below
427
+ from types import MethodType
428
+ self.add_vertex = MethodType(Graph.add_vertex, self)
429
+ self.add_vertices = MethodType(Graph.add_vertices, self)
430
+ self.add_edge = MethodType(Graph.add_edge, self)
431
+ self.add_edges = MethodType(Graph.add_edges, self)
432
+ alist_file = True
433
+
434
+ from sage.structure.element import Matrix
435
+ if isinstance(data, BipartiteGraph):
436
+ Graph.__init__(self, data, *args, **kwds)
437
+ self.left = set(data.left)
438
+ self.right = set(data.right)
439
+ elif isinstance(data, str):
440
+ import os
441
+ alist_file = os.path.exists(data)
442
+ Graph.__init__(self, data=None if alist_file else data, *args, **kwds)
443
+
444
+ # methods; initialize left and right attributes
445
+ self.left = set()
446
+ self.right = set()
447
+
448
+ # determine partitions and populate self.left and self.right
449
+ if not alist_file:
450
+ if partition is not None:
451
+ left, right = set(partition[0]), set(partition[1])
452
+
453
+ # Some error checking.
454
+ if left & right:
455
+ raise ValueError("the parts are not disjoint")
456
+ if len(left) + len(right) != self.num_verts():
457
+ raise ValueError("not all vertices appear in partition")
458
+
459
+ if check:
460
+ if (any(left.intersection(self.neighbor_iterator(a)) for a in left) or
461
+ any(right.intersection(self.neighbor_iterator(a)) for a in right)):
462
+ raise TypeError("input graph is not bipartite with "
463
+ "respect to the given partition")
464
+ else:
465
+ for a in left:
466
+ a_nbrs = left.intersection(self.neighbor_iterator(a))
467
+ if a_nbrs:
468
+ self.delete_edges((a, b) for b in a_nbrs)
469
+ for a in right:
470
+ a_nbrs = right.intersection(self.neighbor_iterator(a))
471
+ if a_nbrs:
472
+ self.delete_edges((a, b) for b in a_nbrs)
473
+ self.left, self.right = left, right
474
+ else:
475
+ # Automatically get partitions if not provided
476
+ self._upgrade_from_graph()
477
+ elif isinstance(data, Matrix):
478
+ # sanity check for mutually exclusive keywords
479
+ if kwds.get("multiedges", False) and kwds.get("weighted", False):
480
+ raise TypeError("weighted multi-edge bipartite graphs from "
481
+ "reduced adjacency matrix not supported")
482
+ ncols = data.ncols()
483
+ nrows = data.nrows()
484
+ self.left = set(range(ncols))
485
+ self.right = set(range(ncols, nrows + ncols))
486
+
487
+ def edges():
488
+ if kwds.get("multiedges", False):
489
+ for ii in range(ncols):
490
+ for jj in range(nrows):
491
+ for _ in range(data[jj, ii]):
492
+ yield (ii, jj + ncols)
493
+ elif kwds.get("weighted", False):
494
+ for ii in range(ncols):
495
+ for jj in range(nrows):
496
+ if data[jj, ii]:
497
+ yield (ii, jj + ncols, data[jj, ii])
498
+ else:
499
+ for ii in range(ncols):
500
+ for jj in range(nrows):
501
+ if data[jj, ii]:
502
+ yield (ii, jj + ncols)
503
+
504
+ # ensure that construction works
505
+ # when immutable=True (issue #39295)
506
+ Graph.__init__(self, data=[range(nrows + ncols), edges()], format='vertices_and_edges', *args, **kwds)
507
+ else:
508
+ if partition is not None:
509
+ left, right = set(partition[0]), set(partition[1])
510
+ if isinstance(data, GenericGraph):
511
+ verts = left | right
512
+ if set(data) != verts:
513
+ data = data.subgraph(verts)
514
+ Graph.__init__(self, data, *args, **kwds)
515
+ if partition is not None:
516
+ # Some error checking.
517
+ if left & right:
518
+ raise ValueError("the parts are not disjoint")
519
+ if len(left) + len(right) != self.num_verts():
520
+ raise ValueError("not all vertices appear in partition")
521
+
522
+ if isinstance(data, (networkx_MultiGraph, networkx_Graph)):
523
+ if hasattr(data, "node_type"):
524
+ # Assume the graph is bipartite
525
+ self.left = set()
526
+ self.right = set()
527
+ for v in data.nodes_iter():
528
+ if data.node_type[v] == "Bottom":
529
+ self.left.add(v)
530
+ elif data.node_type[v] == "Top":
531
+ self.right.add(v)
532
+ else:
533
+ raise TypeError("NetworkX node_type defies bipartite "
534
+ "assumption (is not 'Top' or 'Bottom')")
535
+ elif partition:
536
+ if check:
537
+ if (any(left.intersection(self.neighbor_iterator(a)) for a in left) or
538
+ any(right.intersection(self.neighbor_iterator(a)) for a in right)):
539
+ raise TypeError("input graph is not bipartite with "
540
+ "respect to the given partition")
541
+ else:
542
+ for a in left:
543
+ a_nbrs = left.intersection(data.neighbor_iterator(a))
544
+ if a_nbrs:
545
+ self.delete_edges((a, b) for b in a_nbrs)
546
+ for a in right:
547
+ a_nbrs = right.intersection(data.neighbor_iterator(a))
548
+ if a_nbrs:
549
+ self.delete_edges((a, b) for b in a_nbrs)
550
+ self.left, self.right = left, right
551
+
552
+ # make sure we found a bipartition
553
+ if not (hasattr(self, "left") and hasattr(self, "right")):
554
+ self._upgrade_from_graph()
555
+
556
+ # restore vertex partition checking
557
+ del self.add_vertex
558
+ del self.add_vertices
559
+ del self.add_edge
560
+ del self.add_edges
561
+
562
+ # post-processing
563
+ if isinstance(data, str):
564
+ if alist_file:
565
+ self.load_afile(data)
566
+
567
+ if hash_labels is None and hasattr(data, '_hash_labels'):
568
+ hash_labels = data._hash_labels
569
+ self._hash_labels = hash_labels
570
+
571
+ return
572
+
573
+ @cached_method
574
+ def __hash__(self):
575
+ """
576
+ Compute a hash for ``self``, if ``self`` is immutable.
577
+
578
+ EXAMPLES::
579
+
580
+ sage: A = BipartiteGraph([(1, 2, 1)], immutable=True)
581
+ sage: B = BipartiteGraph([(1, 2, 33)], immutable=True)
582
+ sage: A.__hash__() == B.__hash__()
583
+ True
584
+ sage: A = BipartiteGraph([(1, 2, 1)], immutable=True, hash_labels=True)
585
+ sage: B = BipartiteGraph([(1, 2, 33)], immutable=True, hash_labels=True)
586
+ sage: A.__hash__() == B.__hash__()
587
+ False
588
+ sage: A = BipartiteGraph([(1, 2, 1)], immutable=True, weighted=True)
589
+ sage: B = BipartiteGraph([(1, 2, 33)], immutable=True, weighted=True)
590
+ sage: A.__hash__() == B.__hash__()
591
+ False
592
+
593
+ TESTS::
594
+
595
+ sage: A = BipartiteGraph([(1, 2, 1)], immutable=False)
596
+ sage: A.__hash__()
597
+ Traceback (most recent call last):
598
+ ...
599
+ TypeError: This graph is mutable, and thus not hashable. Create an immutable copy by `g.copy(immutable=True)`
600
+ sage: B = BipartiteGraph([(1, 2, {'length': 3})], immutable=True, hash_labels=True)
601
+ sage: B.__hash__()
602
+ Traceback (most recent call last):
603
+ ...
604
+ TypeError: unhashable type: 'dict'
605
+ """
606
+ if self.is_immutable():
607
+ # Determine whether to hash edge labels
608
+ use_labels = self._use_labels_for_hash()
609
+ edge_items = self.edge_iterator(labels=use_labels)
610
+ if self.allows_multiple_edges():
611
+ from collections import Counter
612
+ edge_items = Counter(edge_items).items()
613
+ return hash((frozenset(self.left), frozenset(self.right), frozenset(edge_items)))
614
+
615
+ raise TypeError("This graph is mutable, and thus not hashable. "
616
+ "Create an immutable copy by `g.copy(immutable=True)`")
617
+
618
+ def _upgrade_from_graph(self):
619
+ """
620
+ Set the left and right sets of vertices from the input graph.
621
+
622
+ TESTS::
623
+
624
+ sage: B = BipartiteGraph(Graph(1))
625
+ sage: B.left, B.right
626
+ ({0}, set())
627
+ sage: B = BipartiteGraph(Graph(2))
628
+ sage: B.left, B.right
629
+ ({0, 1}, set())
630
+ sage: B = BipartiteGraph(graphs.PathGraph(2))
631
+ sage: B.left, B.right
632
+ ({0}, {1})
633
+ sage: B = BipartiteGraph(graphs.PathGraph(3))
634
+ sage: B.left, B.right
635
+ ({0, 2}, {1})
636
+ sage: B = BipartiteGraph(graphs.CycleGraph(3))
637
+ Traceback (most recent call last):
638
+ ...
639
+ ValueError: input graph is not bipartite
640
+ """
641
+ ans, certif = GenericGraph.is_bipartite(self, certificate=True)
642
+ if not ans:
643
+ raise ValueError("input graph is not bipartite")
644
+ cols = defaultdict(set)
645
+ for k, v in certif.items():
646
+ cols[v].add(k)
647
+ self.left = cols[1]
648
+ self.right = cols[0]
649
+
650
+ def __repr__(self):
651
+ r"""
652
+ Return a short string representation of ``self``.
653
+
654
+ EXAMPLES::
655
+
656
+ sage: B = BipartiteGraph(graphs.CycleGraph(16))
657
+ sage: B
658
+ Bipartite cycle graph: graph on 16 vertices
659
+ """
660
+ s = Graph._repr_(self).lower()
661
+ if "bipartite" in s:
662
+ return s.capitalize()
663
+ return "".join(["Bipartite ", s])
664
+
665
+ def add_vertex(self, name=None, left=False, right=False):
666
+ """
667
+ Create an isolated vertex. If the vertex already exists, then
668
+ nothing is done.
669
+
670
+ INPUT:
671
+
672
+ - ``name`` -- (default: ``None``) name of the new vertex. If no name is
673
+ specified, then the vertex will be represented by the least
674
+ nonnegative integer not already representing a vertex. Name must be
675
+ an immutable object and cannot be ``None``.
676
+
677
+ - ``left`` -- boolean (default: ``False``); if ``True``, puts the new
678
+ vertex in the left partition
679
+
680
+ - ``right`` -- boolean (default: ``False``); if ``True``, puts the new
681
+ vertex in the right partition
682
+
683
+ Obviously, ``left`` and ``right`` are mutually exclusive.
684
+
685
+ As it is implemented now, if a graph `G` has a large number of vertices
686
+ with numeric labels, then ``G.add_vertex()`` could potentially be slow,
687
+ if name is ``None``.
688
+
689
+ OUTPUT:
690
+
691
+ - If ``name`` is ``None``, the new vertex name is returned. ``None``
692
+ otherwise.
693
+
694
+ EXAMPLES::
695
+
696
+ sage: G = BipartiteGraph()
697
+ sage: G.add_vertex(left=True)
698
+ 0
699
+ sage: G.add_vertex(right=True)
700
+ 1
701
+ sage: G.vertices(sort=True)
702
+ [0, 1]
703
+ sage: G.left
704
+ {0}
705
+ sage: G.right
706
+ {1}
707
+
708
+ TESTS:
709
+
710
+ Exactly one of ``left`` and ``right`` must be true::
711
+
712
+ sage: G = BipartiteGraph()
713
+ sage: G.add_vertex()
714
+ Traceback (most recent call last):
715
+ ...
716
+ RuntimeError: partition must be specified (e.g. left=True)
717
+ sage: G.add_vertex(left=True, right=True)
718
+ Traceback (most recent call last):
719
+ ...
720
+ RuntimeError: only one partition may be specified
721
+
722
+ Adding the same vertex must specify the same partition::
723
+
724
+ sage: bg = BipartiteGraph()
725
+ sage: bg.add_vertex(0, right=True)
726
+ sage: bg.add_vertex(0, right=True)
727
+ sage: bg.vertices(sort=False)
728
+ [0]
729
+ sage: bg.add_vertex(0, left=True)
730
+ Traceback (most recent call last):
731
+ ...
732
+ RuntimeError: cannot add duplicate vertex to other partition
733
+ """
734
+ # sanity check on partition specifiers
735
+ if left and right:
736
+ raise RuntimeError("only one partition may be specified")
737
+ if not (left or right):
738
+ raise RuntimeError("partition must be specified (e.g. left=True)")
739
+
740
+ # do nothing if we already have this vertex (idempotent)
741
+ if name is not None and name in self:
742
+ if ((left and name in self.left) or
743
+ (right and name in self.right)):
744
+ return
745
+ raise RuntimeError("cannot add duplicate vertex to other partition")
746
+
747
+ # add the vertex
748
+ retval = Graph.add_vertex(self, name)
749
+ if retval is not None:
750
+ name = retval
751
+
752
+ # add to proper partition
753
+ if left:
754
+ self.left.add(name)
755
+ else:
756
+ self.right.add(name)
757
+
758
+ return retval
759
+
760
+ def add_vertices(self, vertices, left=False, right=False):
761
+ """
762
+ Add vertices to the bipartite graph from an iterable container of
763
+ vertices.
764
+
765
+ Vertices that already exist in the graph will not be added again.
766
+
767
+ INPUT:
768
+
769
+ - ``vertices`` -- sequence of vertices to add
770
+
771
+ - ``left`` -- (default: ``False``) either ``True`` or sequence of same
772
+ length as ``vertices`` with boolean elements
773
+
774
+ - ``right`` -- (default: ``False``) either ``True`` or sequence of the
775
+ same length as ``vertices`` with boolean elements
776
+
777
+ Only one of ``left`` and ``right`` keywords should be provided. See
778
+ the examples below.
779
+
780
+ EXAMPLES::
781
+
782
+ sage: bg = BipartiteGraph()
783
+ sage: bg.add_vertices([0, 1, 2], left=True)
784
+ sage: bg.add_vertices([3, 4, 5], left=[True, False, True])
785
+ sage: bg.add_vertices([6, 7, 8], right=[True, False, True])
786
+ sage: bg.add_vertices([9, 10, 11], right=True)
787
+ sage: bg.left
788
+ {0, 1, 2, 3, 5, 7}
789
+ sage: bg.right
790
+ {4, 6, 8, 9, 10, 11}
791
+
792
+ TESTS::
793
+
794
+ sage: bg = BipartiteGraph()
795
+ sage: bg.add_vertices([0, 1, 2], left=True)
796
+ sage: bg.add_vertices([0, 1, 2], left=[True, True, True])
797
+ sage: bg.add_vertices([0, 1, 2], right=[False, False, False])
798
+ sage: bg.add_vertices([0, 1, 2], right=[False, False, False])
799
+ sage: bg.add_vertices([0, 1, 2])
800
+ Traceback (most recent call last):
801
+ ...
802
+ RuntimeError: partition must be specified (e.g. left=True)
803
+ sage: bg.add_vertices([0,1,2], left=True, right=True)
804
+ Traceback (most recent call last):
805
+ ...
806
+ RuntimeError: only one partition may be specified
807
+ sage: bg.add_vertices([0,1,2], right=True)
808
+ Traceback (most recent call last):
809
+ ...
810
+ RuntimeError: cannot add duplicate vertex to other partition
811
+ sage: (bg.left, bg.right)
812
+ ({0, 1, 2}, set())
813
+ """
814
+ # sanity check on partition specifiers
815
+ if left and right: # also triggered if both lists are specified
816
+ raise RuntimeError("only one partition may be specified")
817
+ if not (left or right):
818
+ raise RuntimeError("partition must be specified (e.g. left=True)")
819
+
820
+ # handle partitions
821
+ if left and (not isinstance(left, Iterable)):
822
+ new_left = set(vertices)
823
+ new_right = set()
824
+ elif right and (not isinstance(right, Iterable)):
825
+ new_left = set()
826
+ new_right = set(vertices)
827
+ else:
828
+ # simplify to always work with left
829
+ if right:
830
+ left = [not tf for tf in right]
831
+ new_left = set()
832
+ new_right = set()
833
+ for tf, vv in zip(left, vertices):
834
+ if tf:
835
+ new_left.add(vv)
836
+ else:
837
+ new_right.add(vv)
838
+
839
+ # check that we're not trying to add vertices to the wrong sets
840
+ # or that a vertex is to be placed in both
841
+ if ((new_left & self.right) or
842
+ (new_right & self.left) or
843
+ (new_right & new_left)):
844
+ raise RuntimeError("cannot add duplicate vertex to other partition")
845
+
846
+ # add vertices
847
+ Graph.add_vertices(self, vertices)
848
+ self.left.update(new_left)
849
+ self.right.update(new_right)
850
+
851
+ def delete_vertex(self, vertex, in_order=False):
852
+ """
853
+ Delete vertex, removing all incident edges.
854
+
855
+ Deleting a non-existent vertex will raise an exception.
856
+
857
+ INPUT:
858
+
859
+ - ``vertex`` -- a vertex to delete
860
+
861
+ - ``in_order`` -- boolean (default: ``False``); if ``True``, deletes the
862
+ `i`-th vertex in the sorted list of vertices,
863
+ i.e. ``G.vertices(sort=True)[i]``
864
+
865
+ EXAMPLES::
866
+
867
+ sage: B = BipartiteGraph(graphs.CycleGraph(4))
868
+ sage: B
869
+ Bipartite cycle graph: graph on 4 vertices
870
+ sage: B.delete_vertex(0)
871
+ sage: B
872
+ Bipartite cycle graph: graph on 3 vertices
873
+ sage: B.left
874
+ {2}
875
+ sage: B.edges(sort=True)
876
+ [(1, 2, None), (2, 3, None)]
877
+ sage: B.delete_vertex(3)
878
+ sage: B.right
879
+ {1}
880
+ sage: B.edges(sort=True)
881
+ [(1, 2, None)]
882
+ sage: B.delete_vertex(0)
883
+ Traceback (most recent call last):
884
+ ...
885
+ ValueError: vertex (0) not in the graph
886
+
887
+ ::
888
+
889
+ sage: g = Graph({'a': ['b'], 'c': ['b']})
890
+ sage: bg = BipartiteGraph(g) # finds bipartition
891
+ sage: bg.vertices(sort=True)
892
+ ['a', 'b', 'c']
893
+ sage: bg.delete_vertex('a')
894
+ sage: bg.edges(sort=True)
895
+ [('b', 'c', None)]
896
+ sage: bg.vertices(sort=True)
897
+ ['b', 'c']
898
+ sage: bg2 = BipartiteGraph(g)
899
+ sage: bg2.delete_vertex(0, in_order=True)
900
+ sage: bg2 == bg
901
+ True
902
+ """
903
+ # cache vertex lookup if requested
904
+ if in_order:
905
+ vertex = self.vertices(sort=True)[vertex]
906
+
907
+ # delete from the graph
908
+ Graph.delete_vertex(self, vertex)
909
+
910
+ # now remove from partition (exception already thrown for non-existent
911
+ # vertex)
912
+ if vertex in self.left:
913
+ self.left.remove(vertex)
914
+ elif vertex in self.right:
915
+ self.right.remove(vertex)
916
+ else:
917
+ raise RuntimeError("vertex (%s) not found in partitions" % vertex)
918
+
919
+ def delete_vertices(self, vertices):
920
+ """
921
+ Remove vertices from the bipartite graph taken from an iterable
922
+ sequence of vertices.
923
+
924
+ Deleting a non-existent vertex will raise an exception.
925
+
926
+ INPUT:
927
+
928
+ - ``vertices`` -- a sequence of vertices to remove
929
+
930
+ EXAMPLES::
931
+
932
+ sage: B = BipartiteGraph(graphs.CycleGraph(4))
933
+ sage: B
934
+ Bipartite cycle graph: graph on 4 vertices
935
+ sage: B.delete_vertices([0, 3])
936
+ sage: B
937
+ Bipartite cycle graph: graph on 2 vertices
938
+ sage: B.left
939
+ {2}
940
+ sage: B.right
941
+ {1}
942
+ sage: B.edges(sort=True)
943
+ [(1, 2, None)]
944
+ sage: B.delete_vertices([0])
945
+ Traceback (most recent call last):
946
+ ...
947
+ ValueError: vertex (0) not in the graph
948
+ """
949
+ # remove vertices from the graph
950
+ Graph.delete_vertices(self, vertices)
951
+
952
+ # now remove vertices from partition lists (exception already thrown
953
+ # for non-existent vertices)
954
+ for vertex in vertices:
955
+ if vertex in self.left:
956
+ self.left.remove(vertex)
957
+ elif vertex in self.right:
958
+ self.right.remove(vertex)
959
+ else:
960
+ raise RuntimeError("vertex (%s) not found in partitions" % vertex)
961
+
962
+ def _flip_vertices(self, vertices):
963
+ r"""
964
+ Helper method to flip the sides of a list of vertices.
965
+
966
+ INPUT:
967
+
968
+ - ``vertices`` -- an iterable container of vertices
969
+
970
+ TESTS::
971
+
972
+ sage: G = BipartiteGraph()
973
+ sage: G.add_vertices([0, 1, 2], left=[True, False, True])
974
+ sage: G.bipartition()
975
+ ({0, 2}, {1})
976
+ sage: G._flip_vertices([0, 1])
977
+ sage: G.bipartition()
978
+ ({1, 2}, {0})
979
+ sage: G._flip_vertices([7])
980
+ Traceback (most recent call last):
981
+ ...
982
+ RuntimeError: vertex (7) is neither in left nor in right
983
+ """
984
+ for vertex in vertices:
985
+ if vertex in self.left:
986
+ self.left.remove(vertex)
987
+ self.right.add(vertex)
988
+ elif vertex in self.right:
989
+ self.right.remove(vertex)
990
+ self.left.add(vertex)
991
+ else:
992
+ raise RuntimeError(f"vertex ({vertex}) is neither in left nor in right")
993
+
994
+ def add_edge(self, u, v=None, label=None):
995
+ r"""
996
+ Add an edge from `u` to `v`.
997
+
998
+ INPUT:
999
+
1000
+ - ``u`` -- the tail of an edge
1001
+
1002
+ - ``v`` -- (default: ``None``) the head of an edge. If ``v=None``, then
1003
+ attempt to understand ``u`` as a edge tuple
1004
+
1005
+ - ``label`` -- (default: ``None``) the label of the edge ``(u, v)``
1006
+
1007
+ The following forms are all accepted:
1008
+
1009
+ - ``G.add_edge(1, 2)``
1010
+ - ``G.add_edge((1, 2))``
1011
+ - ``G.add_edges([(1, 2)])``
1012
+ - ``G.add_edge(1, 2, 'label')``
1013
+ - ``G.add_edge((1, 2, 'label'))``
1014
+ - ``G.add_edges([(1, 2, 'label')])``
1015
+
1016
+ See :meth:`~sage.graphs.graph.Graph.add_edge` for more detail.
1017
+
1018
+ This method simply checks that the edge endpoints are in different
1019
+ partitions. If a new vertex is to be created, it will be added to the
1020
+ proper partition. If both vertices are created, the first one will be
1021
+ added to the left partition, the second to the right partition. If
1022
+ both vertices are in the same partition but different connected
1023
+ components, one of the components will be "flipped", i.e. each vertex
1024
+ will be put into whichever partition it's not currently in. This will
1025
+ allow for the graph to remain bipartite, without changing the edges or
1026
+ vertices.
1027
+
1028
+ TESTS::
1029
+
1030
+ sage: bg = BipartiteGraph()
1031
+ sage: bg.add_vertices([0, 1, 2], left=[True, False, True])
1032
+ sage: bg.add_edges([(0, 1), (2, 1)])
1033
+ sage: bg.add_edge(0, 2)
1034
+ Traceback (most recent call last):
1035
+ ...
1036
+ RuntimeError: edge vertices must lie in different partitions
1037
+ sage: bg.add_edge(0, 3); list(bg.right)
1038
+ [1, 3]
1039
+ sage: bg.add_edge(5, 6); 5 in bg.left; 6 in bg.right
1040
+ True
1041
+ True
1042
+ sage: G = BipartiteGraph()
1043
+ sage: G.add_edges([(0, 1), (3, 2)])
1044
+ sage: G.bipartition()
1045
+ ({0, 3}, {1, 2})
1046
+ sage: G.add_edge(1,2)
1047
+ sage: G.bipartition()
1048
+ ({0, 2}, {1, 3})
1049
+ """
1050
+ # logic for getting endpoints copied from generic_graph.py
1051
+ if label is None:
1052
+ if v is None:
1053
+ try:
1054
+ u, v, label = u
1055
+ except Exception:
1056
+ u, v = u
1057
+ label = None
1058
+ else:
1059
+ if v is None:
1060
+ u, v = u
1061
+
1062
+ # if endpoints are in the same partition
1063
+ if self.left.issuperset((u, v)) or self.right.issuperset((u, v)):
1064
+
1065
+ # get v's connected component
1066
+ v_connected_component = self.connected_component_containing_vertex(v, sort=False)
1067
+
1068
+ # if u is in it, then the edge still cannot exist
1069
+ if u in v_connected_component:
1070
+ raise RuntimeError("edge vertices must lie in different partitions")
1071
+
1072
+ # if not, we can "flip" the connected component
1073
+ # swapping which partition the vertices are in
1074
+ self._flip_vertices(v_connected_component)
1075
+
1076
+ # automatically decide partitions for the newly created vertices
1077
+ if u not in self:
1078
+ self.add_vertex(u, left=(v in self.right or v not in self), right=(v in self.left))
1079
+ if v not in self:
1080
+ self.add_vertex(v, left=(u in self.right), right=(u in self.left))
1081
+
1082
+ # add the edge
1083
+ Graph.add_edge(self, u, v, label)
1084
+
1085
+ def add_edges(self, edges, loops=True):
1086
+ """
1087
+ Add edges from an iterable container.
1088
+
1089
+ INPUT:
1090
+
1091
+ - ``edges`` -- an iterable of edges, given either as ``(u, v)``
1092
+ or ``(u, v, label)``
1093
+
1094
+ - ``loops`` -- ignored
1095
+
1096
+ See :meth:`~sage.graphs.graph.Graph.add_edges` for more detail.
1097
+
1098
+ This method simply checks that the edge endpoints are in different
1099
+ partitions. If a new vertex is to be created, it will be added to the
1100
+ proper partition. If both vertices are created, the first one will be
1101
+ added to the left partition, the second to the right partition. If
1102
+ both vertices are in the same partition but different connected
1103
+ components, one of the components will be "flipped", i.e. each vertex
1104
+ will be put into whichever partition it's not currently in. This will
1105
+ allow for the graph to remain bipartite, without changing the edges or
1106
+ vertices.
1107
+
1108
+ EXAMPLES::
1109
+
1110
+ sage: bg = BipartiteGraph()
1111
+ sage: bg.add_vertices([0, 1, 2], left=[True, False, True])
1112
+ sage: bg.add_edges([(0, 1), (2, 1)])
1113
+ sage: bg.add_edges([[0, 2]])
1114
+ Traceback (most recent call last):
1115
+ ...
1116
+ ValueError: the specified set of edges cannot be added
1117
+ while still preserving the bipartition property
1118
+ sage: G = BipartiteGraph()
1119
+ sage: G.add_edges([(0, 1), (3, 2), (1, 2)])
1120
+ sage: G.bipartition()
1121
+ ({0, 2}, {1, 3})
1122
+
1123
+
1124
+ Loops will raise an error::
1125
+
1126
+ sage: bg.add_edges([[0, 3], [3, 3]])
1127
+ Traceback (most recent call last):
1128
+ ...
1129
+ ValueError: the specified set of edges cannot be added
1130
+ while still preserving the bipartition property
1131
+
1132
+ Adding edges is fine as long as there exists a valid bipartition.
1133
+ Otherwise an error is raised without modifyiong the graph::
1134
+
1135
+ sage: G = BipartiteGraph()
1136
+ sage: G.add_edges([(0, 1), (2, 3)])
1137
+ sage: G.bipartition()
1138
+ ({0, 2}, {1, 3})
1139
+ sage: G.add_edges([(0,2), (0,3)])
1140
+ Traceback (most recent call last):
1141
+ ...
1142
+ ValueError: the specified set of edges cannot be added
1143
+ while still preserving the bipartition property
1144
+ sage: G.bipartition()
1145
+ ({0, 2}, {1, 3})
1146
+ sage: G.edges(labels=False, sort=True)
1147
+ [(0, 1), (2, 3)]
1148
+ """
1149
+ edges_to_add = []
1150
+ for edge in edges:
1151
+ try:
1152
+ if len(edge) == 3:
1153
+ u, v, label = edge
1154
+ else:
1155
+ u, v = edge
1156
+ label = None
1157
+ edges_to_add.append((u, v, label))
1158
+ except Exception:
1159
+ raise TypeError("cannot interpret {!r} as graph edge".format(edge))
1160
+
1161
+ # Check whether there exists a bipartition supporting the addition of
1162
+ # input edges to the current graph before adding any edge to the
1163
+ # graph. This way, if an error is raised, self is not modified
1164
+ vertex_in_left = self._check_bipartition_for_add_edges(edges_to_add)
1165
+
1166
+ if vertex_in_left is False:
1167
+ raise ValueError("the specified set of edges cannot be added while "
1168
+ "still preserving the bipartition property")
1169
+
1170
+ # If we get here, then we've found a valid bipartition.
1171
+ # We update the bipartition
1172
+ self.left.clear()
1173
+ self.right.clear()
1174
+ for v, left in vertex_in_left.items():
1175
+ if left:
1176
+ self.left.add(v)
1177
+ else:
1178
+ self.right.add(v)
1179
+
1180
+ # Each edge now has one endpoint in left and the other in right
1181
+ for u, v, label in edges_to_add:
1182
+ if u not in self:
1183
+ self.add_vertex(u, left=vertex_in_left[u], right=not vertex_in_left[u])
1184
+ if v not in self:
1185
+ self.add_vertex(v, left=vertex_in_left[v], right=not vertex_in_left[v])
1186
+
1187
+ self._backend.add_edge(u, v, label, self._directed)
1188
+
1189
+ def _check_bipartition_for_add_edges(self, edges):
1190
+ r"""
1191
+ Helper method for ``add_edges``.
1192
+
1193
+ This method checks whether the input list of edges can be added to the
1194
+ graph. More precisely, it checks whether there exists a bipartition of
1195
+ the vertices supporting the addition of input edges. If so it returns it
1196
+ as a mapping associating to each vertex a side of the bipartition.
1197
+ Otherwise, it returns ``False``.
1198
+
1199
+ INPUT:
1200
+
1201
+ - ``edges`` -- an iterable of edges, given either as ``(u, v)``
1202
+ or ``(u, v, label)``
1203
+
1204
+ TESTS::
1205
+
1206
+ sage: bg = BipartiteGraph()
1207
+ sage: bg.add_vertices([0, 1, 2, 3], left=[True, False, True, False])
1208
+ sage: b = bg._check_bipartition_for_add_edges([(0, 1), (3, 2), (1, 2)])
1209
+ sage: sorted(b.items())
1210
+ [(0, True), (1, False), (2, True), (3, False)]
1211
+ sage: b = bg._check_bipartition_for_add_edges([(0, 2)])
1212
+ sage: sorted(b.items())
1213
+ [(0, True), (1, False), (2, False), (3, False)]
1214
+ sage: bg.add_edges([(0, 1), (3, 2), (1, 2)])
1215
+ sage: bg._check_bipartition_for_add_edges([[0, 2]])
1216
+ False
1217
+ """
1218
+ # Map each vertex of the graph to a side
1219
+ vertex_in_left = {v: True for v in self.left}
1220
+ for v in self.right:
1221
+ vertex_in_left[v] = False
1222
+
1223
+ # Map each vertex to the connected component it belongs to
1224
+ vertex_to_component = {v: comp for comp in self.connected_components(sort=False)
1225
+ for v in comp}
1226
+
1227
+ for e in edges:
1228
+ u, v = e[:2]
1229
+ # if we haven't encountered either/both vertices, we choose a side
1230
+ # and extend components
1231
+ if u not in vertex_in_left:
1232
+ if v in vertex_in_left:
1233
+ vertex_in_left[u] = not vertex_in_left[v]
1234
+ else:
1235
+ vertex_in_left[u] = True
1236
+ vertex_in_left[v] = False
1237
+ vertex_to_component[v] = [v]
1238
+ vertex_to_component[v].append(u)
1239
+ vertex_to_component[u] = vertex_to_component[v]
1240
+
1241
+ elif v not in vertex_in_left:
1242
+ vertex_in_left[v] = not vertex_in_left[u]
1243
+ vertex_to_component[u].append(v)
1244
+ vertex_to_component[v] = vertex_to_component[u]
1245
+
1246
+ elif vertex_in_left[u] == vertex_in_left[v]:
1247
+ if vertex_to_component[u] is vertex_to_component[v]:
1248
+ # Same side and same component. We can't add that edge
1249
+ return False
1250
+
1251
+ # Otherwise, we flip the bipartition in v's component
1252
+ for w in vertex_to_component[v]:
1253
+ vertex_in_left[w] = not vertex_in_left[w]
1254
+
1255
+ # and merge the components
1256
+ comp_u = vertex_to_component[u]
1257
+ comp_u.extend(vertex_to_component[v])
1258
+ for w in vertex_to_component[v]:
1259
+ vertex_to_component[w] = comp_u
1260
+
1261
+ # Return the bipartition
1262
+ return vertex_in_left
1263
+
1264
+ def allow_loops(self, new, check=True):
1265
+ """
1266
+ Change whether loops are permitted in the (di)graph.
1267
+
1268
+ .. NOTE::
1269
+
1270
+ This method overwrite the
1271
+ :meth:`~sage.graphs.generic_graph.GenericGraph.allow_loops` method
1272
+ to ensure that loops are forbidden in :class:`~BipartiteGraph`.
1273
+
1274
+ INPUT:
1275
+
1276
+ - ``new`` -- boolean
1277
+
1278
+ EXAMPLES::
1279
+
1280
+ sage: B = BipartiteGraph()
1281
+ sage: B.allow_loops(True)
1282
+ Traceback (most recent call last):
1283
+ ...
1284
+ ValueError: loops are not allowed in bipartite graphs
1285
+ """
1286
+ if new is True:
1287
+ raise ValueError("loops are not allowed in bipartite graphs")
1288
+
1289
+ def is_bipartite(self, certificate=False):
1290
+ r"""
1291
+ Check whether the graph is bipartite.
1292
+
1293
+ This method always returns ``True`` as first value, plus a certificate
1294
+ when ``certificate == True``.
1295
+
1296
+ INPUT:
1297
+
1298
+ - ``certificate`` -- boolean (default: ``False``); whether to return a
1299
+ certificate. If set to ``True``, the certificate returned is a proper
1300
+ 2-coloring of the vertices.
1301
+
1302
+ .. SEEALSO:: :meth:`~GenericGraph.is_bipartite`
1303
+
1304
+ EXAMPLES::
1305
+
1306
+ sage: g = BipartiteGraph(graphs.RandomBipartite(3, 3, .5)) # needs numpy
1307
+ sage: g.is_bipartite() # needs numpy
1308
+ True
1309
+ sage: g.is_bipartite(certificate=True) # random # needs numpy
1310
+ (True, {(0, 0): 0, (0, 1): 0, (0, 2): 0, (1, 0): 1, (1, 1): 1, (1, 2): 1})
1311
+
1312
+ TESTS::
1313
+
1314
+ sage: BipartiteGraph().is_bipartite()
1315
+ True
1316
+ sage: BipartiteGraph().is_bipartite(certificate=True)
1317
+ (True, {})
1318
+ """
1319
+ if certificate:
1320
+ color = {u: 0 for u in self.left}
1321
+ color.update({u: 1 for u in self.right})
1322
+ return True, color
1323
+ return True
1324
+
1325
+ def complement(self):
1326
+ r"""
1327
+ Return a complement of this graph.
1328
+
1329
+ Given a simple :class:`~sage.graphs.bipartite_graph.BipartiteGraph`
1330
+ `G = (L, R, E)` with vertex set `L\cup R` and edge set `E`, this method
1331
+ returns a :class:`~sage.graphs.graph.Graph` `H = (V, F)`, where
1332
+ `V = L\cup R` and `F` is the set of edges of a complete graph of order
1333
+ `|V|` minus the edges in `E`.
1334
+
1335
+ .. WARNING::
1336
+
1337
+ This method returns the complement of a bipartite graph
1338
+ `G = (V = L \cup R, E)` with respect the complete graph of order
1339
+ `|V|`. If looking for the complement with respect the complete
1340
+ bipartite graph `K = (L, R, L\times R)`, use method
1341
+ :meth:`~sage.graphs.bipartite_graph.BipartiteGraph.complement_bipartite`.
1342
+
1343
+ .. SEEALSO::
1344
+
1345
+ :meth:`~sage.graphs.bipartite_graph.BipartiteGraph.complement_bipartite`
1346
+
1347
+ EXAMPLES::
1348
+
1349
+ sage: B = BipartiteGraph({1: [2, 4], 3: [4, 5]})
1350
+ sage: G = B.complement(); G
1351
+ Graph on 5 vertices
1352
+ sage: G.edges(sort=True, labels=False)
1353
+ [(1, 3), (1, 5), (2, 3), (2, 4), (2, 5), (4, 5)]
1354
+ sage: B.size() + G.size() == graphs.CompleteGraph(B.order()).size()
1355
+ True
1356
+ """
1357
+ # This is needed because complement() of generic graph
1358
+ # would return a graph of class BipartiteGraph that is
1359
+ # not bipartite. See issue #12376.
1360
+ return Graph(self).complement()
1361
+
1362
+ def complement_bipartite(self):
1363
+ r"""
1364
+ Return the bipartite complement of this bipartite graph.
1365
+
1366
+ Given a simple :class:`~sage.graphs.bipartite_graph.BipartiteGraph`
1367
+ `G = (L, R, E)` with vertex set `L\cup R` and edge set `E`, this
1368
+ method returns a :class:`~sage.graphs.bipartite_graph.BipartiteGraph`
1369
+ `H = (L\cup R, F)`, where `F` is the set of edges of a complete
1370
+ bipartite graph between vertex sets `L` and `R` minus the edges in `E`.
1371
+
1372
+ .. SEEALSO::
1373
+
1374
+ :meth:`~sage.graphs.bipartite_graph.BipartiteGraph.complement`
1375
+
1376
+ EXAMPLES:
1377
+
1378
+ sage: B = BipartiteGraph({0: [1, 2, 3]})
1379
+ sage: C = B.complement_bipartite()
1380
+ sage: C
1381
+ Bipartite graph on 4 vertices
1382
+ sage: C.is_bipartite()
1383
+ True
1384
+ sage: B.left == C.left and B.right == C.right
1385
+ True
1386
+ sage: C.size() == len(B.left)*len(B.right) - B.size()
1387
+ True
1388
+ sage: G = B.complement()
1389
+ sage: G.is_bipartite()
1390
+ False
1391
+ """
1392
+ self._scream_if_not_simple()
1393
+
1394
+ E = [e for e in itertools.product(self.left, self.right) if not self.has_edge(e)]
1395
+ H = BipartiteGraph([self, E], format='vertices_and_edges', partition=[self.left, self.right])
1396
+
1397
+ if self.name():
1398
+ H.name("complement-bipartite({})".format(self.name()))
1399
+ if self.is_immutable():
1400
+ return H.copy(immutable=True)
1401
+ return H
1402
+
1403
+ def to_undirected(self):
1404
+ """
1405
+ Return an undirected Graph (without bipartite constraint) of the given
1406
+ object.
1407
+
1408
+ EXAMPLES::
1409
+
1410
+ sage: BipartiteGraph(graphs.CycleGraph(6)).to_undirected()
1411
+ Cycle graph: Graph on 6 vertices
1412
+ """
1413
+ return Graph(self)
1414
+
1415
+ def bipartition(self):
1416
+ r"""
1417
+ Return the underlying bipartition of the bipartite graph.
1418
+
1419
+ EXAMPLES::
1420
+
1421
+ sage: B = BipartiteGraph(graphs.CycleGraph(4))
1422
+ sage: B.bipartition()
1423
+ ({0, 2}, {1, 3})
1424
+ """
1425
+ return (self.left, self.right)
1426
+
1427
+ def project_left(self):
1428
+ r"""
1429
+ Project ``self`` onto left vertices. Edges are 2-paths in the original.
1430
+
1431
+ EXAMPLES::
1432
+
1433
+ sage: B = BipartiteGraph(graphs.CycleGraph(20))
1434
+ sage: G = B.project_left()
1435
+ sage: G.order(), G.size()
1436
+ (10, 10)
1437
+ """
1438
+ G = Graph()
1439
+ G.add_vertices(self.left)
1440
+ for v in G:
1441
+ for u in self.neighbor_iterator(v):
1442
+ G.add_edges(((v, w) for w in self.neighbor_iterator(u)), loops=False)
1443
+ return G
1444
+
1445
+ def project_right(self):
1446
+ r"""
1447
+ Project ``self`` onto right vertices. Edges are 2-paths in the original.
1448
+
1449
+ EXAMPLES::
1450
+
1451
+ sage: B = BipartiteGraph(graphs.CycleGraph(20))
1452
+ sage: G = B.project_right()
1453
+ sage: G.order(), G.size()
1454
+ (10, 10)
1455
+
1456
+ TESTS:
1457
+
1458
+ Issue :issue:`25985` is fixed::
1459
+
1460
+ sage: B = BipartiteGraph(graphs.CycleGraph(6))
1461
+ sage: B.project_left().vertices(sort=True)
1462
+ [0, 2, 4]
1463
+ sage: B.project_right().vertices(sort=True)
1464
+ [1, 3, 5]
1465
+ """
1466
+ G = Graph()
1467
+ G.add_vertices(self.right)
1468
+ for v in G:
1469
+ for u in self.neighbor_iterator(v):
1470
+ G.add_edges(((v, w) for w in self.neighbor_iterator(u)), loops=False)
1471
+ return G
1472
+
1473
+ def plot(self, *args, **kwds):
1474
+ r"""
1475
+ Override Graph's plot function, to illustrate the bipartite nature.
1476
+
1477
+ EXAMPLES::
1478
+
1479
+ sage: B = BipartiteGraph(graphs.CycleGraph(20))
1480
+ sage: B.plot() # needs sage.plot
1481
+ Graphics object consisting of 41 graphics primitives
1482
+ """
1483
+ if "pos" not in kwds:
1484
+ kwds["pos"] = None
1485
+ if kwds["pos"] is None:
1486
+ if self.left:
1487
+ y = 0 if len(self.left) == 1 else 1
1488
+ pos = self._line_embedding(sorted(self.left), first=(-1, y), last=(-1, -y), return_dict=True)
1489
+ else:
1490
+ pos = {}
1491
+ if self.right:
1492
+ y = 0 if len(self.right) == 1 else 1
1493
+ pos.update(self._line_embedding(sorted(self.right), first=(1, y), last=(1, -y), return_dict=True))
1494
+ kwds["pos"] = pos
1495
+ return Graph.plot(self, *args, **kwds)
1496
+
1497
+ def matching_polynomial(self, algorithm='Godsil', name=None):
1498
+ r"""
1499
+ Compute the matching polynomial.
1500
+
1501
+ The *matching polynomial* is defined as in [God1993]_, where `p(G, k)`
1502
+ denotes the number of `k`-matchings (matchings with `k` edges) in `G` :
1503
+
1504
+ .. MATH::
1505
+
1506
+ \mu(x)=\sum_{k \geq 0} (-1)^k p(G,k) x^{n-2k}
1507
+
1508
+ INPUT:
1509
+
1510
+ - ``algorithm`` -- string (default: ``'Godsil'``); either "Godsil" or
1511
+ "rook"; "rook" is usually faster for larger graphs
1512
+
1513
+ - ``name`` -- string (default: ``None``); name of the variable in the
1514
+ polynomial, set to `x` when ``name`` is ``None``
1515
+
1516
+ EXAMPLES::
1517
+
1518
+ sage: BipartiteGraph(graphs.CubeGraph(3)).matching_polynomial() # needs sage.libs.flint
1519
+ x^8 - 12*x^6 + 42*x^4 - 44*x^2 + 9
1520
+
1521
+ ::
1522
+
1523
+ sage: x = polygen(QQ)
1524
+ sage: g = BipartiteGraph(graphs.CompleteBipartiteGraph(16, 16))
1525
+ sage: bool(factorial(16) * laguerre(16, x^2) # needs sage.symbolic
1526
+ ....: == g.matching_polynomial(algorithm='rook'))
1527
+ True
1528
+
1529
+ Compute the matching polynomial of a line with `60` vertices::
1530
+
1531
+ sage: from sage.functions.orthogonal_polys import chebyshev_U # needs sage.symbolic
1532
+ sage: g = next(graphs.trees(60))
1533
+ sage: (chebyshev_U(60, x/2) # needs sage.symbolic
1534
+ ....: == BipartiteGraph(g).matching_polynomial(algorithm='rook'))
1535
+ True
1536
+
1537
+ The matching polynomial of a tree is equal to its characteristic
1538
+ polynomial::
1539
+
1540
+ sage: g = graphs.RandomTree(20)
1541
+ sage: p = g.characteristic_polynomial() # needs sage.modules
1542
+ sage: p == BipartiteGraph(g).matching_polynomial(algorithm='rook') # needs sage.modules
1543
+ True
1544
+
1545
+ TESTS::
1546
+
1547
+ sage: # needs sage.modules
1548
+ sage: g = BipartiteGraph(matrix.ones(4, 3))
1549
+ sage: g.matching_polynomial() # needs sage.libs.flint
1550
+ x^7 - 12*x^5 + 36*x^3 - 24*x
1551
+ sage: g.matching_polynomial(algorithm='rook')
1552
+ x^7 - 12*x^5 + 36*x^3 - 24*x
1553
+ """
1554
+ if algorithm == "Godsil":
1555
+ return Graph.matching_polynomial(self, complement=False, name=name)
1556
+ elif algorithm == "rook":
1557
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
1558
+ A = self.reduced_adjacency_matrix()
1559
+ a = A.rook_vector()
1560
+ m = A.nrows()
1561
+ n = A.ncols()
1562
+ b = [0] * (m + n + 1)
1563
+ for i in range(min(m, n) + 1):
1564
+ b[m + n - 2 * i] = a[i] * (-1)**i
1565
+ if name is None:
1566
+ name = 'x'
1567
+ K = PolynomialRing(A.base_ring(), name)
1568
+ p = K(b)
1569
+ return p
1570
+ raise ValueError('algorithm must be one of "Godsil" or "rook"')
1571
+
1572
+ def perfect_matchings(self, labels=False):
1573
+ r"""
1574
+ Return an iterator over all perfect matchings of the bipartite graph.
1575
+
1576
+ ALGORITHM:
1577
+
1578
+ Choose a vertex `v` in the right set of vertices, then recurse through
1579
+ all edges incident to `v`, removing one edge at a time whenever an edge
1580
+ is added to a matching.
1581
+
1582
+ INPUT:
1583
+
1584
+ - ``labels`` -- boolean (default: ``False``); when ``True``, the edges
1585
+ in each perfect matching are triples (containing the label as the
1586
+ third element), otherwise the edges are pairs.
1587
+
1588
+ .. SEEALSO::
1589
+
1590
+ :meth:`~sage.graphs.graph.Graph.perfect_matchings`
1591
+ :meth:`matching`
1592
+
1593
+ EXAMPLES::
1594
+
1595
+ sage: B = BipartiteGraph({0: [5, 7], 1: [4, 6, 7], 2: [4, 5, 8],
1596
+ ....: 3: [4, 5, 6], 6: [9], 8: [9]})
1597
+ sage: len(list(B.perfect_matchings()))
1598
+ 6
1599
+ sage: G = Graph(B.edges(sort=False))
1600
+ sage: len(list(G.perfect_matchings()))
1601
+ 6
1602
+
1603
+ The algorithm ensures that for any edge of a perfect matching, the first
1604
+ vertex is on the left set of vertices and the second vertex in the right
1605
+ set::
1606
+
1607
+ sage: B = BipartiteGraph({0: [5, 7], 1: [4, 6, 7], 2: [4, 5, 8],
1608
+ ....: 3: [4, 5, 6], 6: [9], 8: [9]})
1609
+ sage: m = next(B.perfect_matchings(labels=False))
1610
+ sage: B.left
1611
+ {0, 1, 2, 3, 9}
1612
+ sage: B.right
1613
+ {4, 5, 6, 7, 8}
1614
+ sage: sorted(m)
1615
+ [(0, 7), (1, 4), (2, 5), (3, 6), (9, 8)]
1616
+ sage: all((u in B.left and v in B.right) for u, v in m)
1617
+ True
1618
+
1619
+ Multiple edges are taken into account::
1620
+
1621
+ sage: B = BipartiteGraph({0: [5, 7], 1: [4, 6, 7], 2: [4, 5, 8],
1622
+ ....: 3: [4, 5, 6], 6: [9], 8: [9]})
1623
+ sage: B.allow_multiple_edges(True)
1624
+ sage: B.add_edge(0, 7)
1625
+ sage: len(list(B.perfect_matchings()))
1626
+ 10
1627
+
1628
+
1629
+ Empty graph::
1630
+
1631
+ sage: list(BipartiteGraph().perfect_matchings())
1632
+ [[]]
1633
+
1634
+ Bipartite graph without perfect matching::
1635
+
1636
+ sage: B = BipartiteGraph(graphs.CompleteBipartiteGraph(3, 4))
1637
+ sage: list(B.perfect_matchings())
1638
+ []
1639
+
1640
+ Check that the number of perfect matchings of a complete bipartite graph
1641
+ is consistent with the matching polynomial::
1642
+
1643
+ sage: B = BipartiteGraph(graphs.CompleteBipartiteGraph(4, 4))
1644
+ sage: len(list(B.perfect_matchings()))
1645
+ 24
1646
+ sage: B.matching_polynomial(algorithm='rook')(0) # needs sage.modules
1647
+ 24
1648
+
1649
+ TESTS::
1650
+
1651
+ sage: B = BipartiteGraph(graphs.CompleteBipartiteGraph(3, 4))
1652
+ sage: B.left, B.right
1653
+ ({0, 1, 2}, {3, 4, 5, 6})
1654
+ sage: B.add_vertex(left=True)
1655
+ 7
1656
+ sage: B.left, B.right
1657
+ ({0, 1, 2, 7}, {3, 4, 5, 6})
1658
+ sage: list(B.perfect_matchings())
1659
+ []
1660
+ sage: B = BipartiteGraph(graphs.CompleteBipartiteGraph(3, 3))
1661
+ sage: B.add_vertex(left=True)
1662
+ 6
1663
+ sage: B.add_vertex(right=True)
1664
+ 7
1665
+ sage: list(B.perfect_matchings())
1666
+ []
1667
+ sage: G = Graph(B)
1668
+ sage: list(G.perfect_matchings())
1669
+ []
1670
+ """
1671
+ if not self:
1672
+ yield []
1673
+ return
1674
+ if len(self.left) != len(self.right):
1675
+ return
1676
+
1677
+ def rec(G):
1678
+ """
1679
+ Iterator over all perfect matchings of a simple bipartite graph `G`.
1680
+ """
1681
+ if not G:
1682
+ yield []
1683
+ return
1684
+ # Take an element from the right set
1685
+ v = next(iter(G.right))
1686
+ Nv = G.neighbors(v)
1687
+ # We must have at least one vertex in Nv
1688
+ if not Nv:
1689
+ return
1690
+ G.delete_vertex(v)
1691
+ for u in Nv:
1692
+ Nu = G.neighbors(u)
1693
+ G.delete_vertex(u)
1694
+ for partial_matching in rec(G):
1695
+ partial_matching.append((u, v))
1696
+ yield partial_matching
1697
+ G.add_vertex(u, left=True)
1698
+ G.add_edges((u, nu) for nu in Nu)
1699
+ G.add_vertex(v, right=True)
1700
+ G.add_edges((nv, v) for nv in Nv)
1701
+
1702
+ # We create a mutable copy of self
1703
+ G = self.copy(immutable=False)
1704
+
1705
+ # We create a mapping from frozen unlabeled edges to (labeled) edges.
1706
+ # This ease for instance the manipulation of multiedges (if any)
1707
+ edges = {}
1708
+ for e in G.edges(sort=False, labels=labels):
1709
+ f = frozenset(e[:2])
1710
+ if e[0] not in G.left:
1711
+ e = (e[1], e[0], e[2]) if labels else (e[1], e[0])
1712
+ if f in edges:
1713
+ edges[f].append(e)
1714
+ else:
1715
+ edges[f] = [e]
1716
+
1717
+ # We now get rid of multiple edges, if any
1718
+ G.allow_multiple_edges(False)
1719
+
1720
+ # For each unlabeled matching, we yield all its possible labelings
1721
+ for m in rec(G):
1722
+ yield from itertools.product(*[edges[frozenset(e)] for e in m])
1723
+
1724
+ def load_afile(self, fname):
1725
+ r"""
1726
+ Load into the current object the bipartite graph specified in the given
1727
+ file name.
1728
+
1729
+ This file should follow David MacKay's alist format, see
1730
+ http://www.inference.phy.cam.ac.uk/mackay/codes/data.html for examples
1731
+ and definition of the format.
1732
+
1733
+ EXAMPLES::
1734
+
1735
+ sage: import tempfile
1736
+ sage: with tempfile.NamedTemporaryFile(mode='w+t') as f:
1737
+ ....: _ = f.write("7 4 \n 3 4 \n 3 3 1 3 1 1 1 \n\
1738
+ ....: 3 3 3 4 \n 1 2 4 \n 1 3 4 \n\
1739
+ ....: 1 0 0 \n 2 3 4 \n 2 0 0 \n 3 0 0 \n\
1740
+ ....: 4 0 0 \n 1 2 3 0 \n 1 4 5 0 \n\
1741
+ ....: 2 4 6 0 \n 1 2 4 7 \n")
1742
+ ....: f.flush()
1743
+ ....: B = BipartiteGraph()
1744
+ ....: B2 = BipartiteGraph(f.name)
1745
+ ....: B.load_afile(f.name)
1746
+ Bipartite graph on 11 vertices
1747
+ sage: B.edges(sort=True)
1748
+ [(0, 7, None),
1749
+ (0, 8, None),
1750
+ (0, 10, None),
1751
+ (1, 7, None),
1752
+ (1, 9, None),
1753
+ (1, 10, None),
1754
+ (2, 7, None),
1755
+ (3, 8, None),
1756
+ (3, 9, None),
1757
+ (3, 10, None),
1758
+ (4, 8, None),
1759
+ (5, 9, None),
1760
+ (6, 10, None)]
1761
+ sage: B2 == B
1762
+ True
1763
+ """
1764
+ # open the file
1765
+ try:
1766
+ fi = open(fname)
1767
+ except OSError:
1768
+ print("unable to open file <<" + fname + ">>")
1769
+ return None
1770
+
1771
+ # read header information
1772
+ num_cols, num_rows = (int(_) for _ in fi.readline().split())
1773
+ # next are max_col_degree, max_row_degree, not used
1774
+ _ = [int(_) for _ in fi.readline().split()]
1775
+ col_degrees = [int(_) for _ in fi.readline().split()]
1776
+ row_degrees = [int(_) for _ in fi.readline().split()]
1777
+
1778
+ # sanity checks on header info
1779
+ if len(col_degrees) != num_cols:
1780
+ print("invalid Alist format: ")
1781
+ print("number of column degree entries does not match number of columns")
1782
+ return None
1783
+ if len(row_degrees) != num_rows:
1784
+ print("invalid Alist format: ")
1785
+ print("number of row degree entries does not match number of rows")
1786
+ return None
1787
+
1788
+ # clear out self
1789
+ self.clear()
1790
+ self.add_vertices(range(num_cols), left=True)
1791
+ self.add_vertices(range(num_cols, num_cols + num_rows), right=True)
1792
+
1793
+ # read adjacency information
1794
+ for cidx in range(num_cols):
1795
+ for ridx in map(int, fi.readline().split()):
1796
+ # A-list uses 1-based indices with 0s as place-holders
1797
+ if ridx > 0:
1798
+ self.add_edge(cidx, num_cols + ridx - 1)
1799
+
1800
+ # NOTE:: we could read in the row adjacency information as well to
1801
+ # double-check....
1802
+ # NOTE:: we could check the actual node degrees against the reported
1803
+ # node degrees....
1804
+
1805
+ # now we have all the edges in our graph, just fill in the
1806
+ # bipartite partitioning
1807
+ self.left = set(range(num_cols))
1808
+ self.right = set(range(num_cols, num_cols + num_rows))
1809
+
1810
+ # return self for chaining calls if desired
1811
+ return self
1812
+
1813
+ def save_afile(self, fname):
1814
+ r"""
1815
+ Save the graph to file in alist format.
1816
+
1817
+ Saves this graph to file in David MacKay's alist format, see
1818
+ http://www.inference.phy.cam.ac.uk/mackay/codes/data.html
1819
+ for examples and definition of the format.
1820
+
1821
+ EXAMPLES::
1822
+
1823
+ sage: # needs sage.modules
1824
+ sage: M = Matrix([(1,1,1,0,0,0,0), (1,0,0,1,1,0,0),
1825
+ ....: (0,1,0,1,0,1,0), (1,1,0,1,0,0,1)])
1826
+ sage: M
1827
+ [1 1 1 0 0 0 0]
1828
+ [1 0 0 1 1 0 0]
1829
+ [0 1 0 1 0 1 0]
1830
+ [1 1 0 1 0 0 1]
1831
+ sage: b = BipartiteGraph(M)
1832
+ sage: import tempfile
1833
+ sage: with tempfile.NamedTemporaryFile() as f:
1834
+ ....: b.save_afile(f.name)
1835
+ ....: b2 = BipartiteGraph(f.name)
1836
+ sage: b.is_isomorphic(b2)
1837
+ True
1838
+
1839
+ TESTS::
1840
+
1841
+ sage: import tempfile
1842
+ sage: f = tempfile.NamedTemporaryFile()
1843
+ sage: for order in range(3, 13, 3): # needs sage.combinat
1844
+ ....: num_chks = int(order / 3)
1845
+ ....: num_vars = order - num_chks
1846
+ ....: partition = (list(range(num_vars)), list(range(num_vars, num_vars+num_chks)))
1847
+ ....: for idx in range(100):
1848
+ ....: g = graphs.RandomGNP(order, 0.5)
1849
+ ....: try:
1850
+ ....: b = BipartiteGraph(g, partition, check=False)
1851
+ ....: b.save_afile(f.name)
1852
+ ....: b2 = BipartiteGraph(f.name)
1853
+ ....: if not b.is_isomorphic(b2):
1854
+ ....: print("Load/save failed for code with edges:")
1855
+ ....: print(b.edges(sort=True))
1856
+ ....: break
1857
+ ....: except Exception:
1858
+ ....: print("Exception encountered for graph of order "+ str(order))
1859
+ ....: print("with edges: ")
1860
+ ....: g.edges(sort=True)
1861
+ ....: raise
1862
+ sage: f.close() # this removes the file
1863
+ """
1864
+ # open the file
1865
+ try:
1866
+ fi = open(fname, "w")
1867
+ except OSError:
1868
+ print("Unable to open file <<" + fname + ">>.")
1869
+ return
1870
+
1871
+ # The alist file format assumes that vertices are labeled in [0..n-1]
1872
+ # with:
1873
+ # - vertices in left labeled in [0..|left|-1]
1874
+ # - vertices in right labeled in [|left|..n-1]
1875
+ # Then, for vertex `vidx`, it stores in the file the index + 1 of a
1876
+ # neighbor in the list right. We use appropriate mappings.
1877
+ # See http://www.inference.org.uk/mackay/codes/alist.html
1878
+
1879
+ # prep: handy lists, functions for extracting adjacent nodes
1880
+ vnodes = list(self.left)
1881
+ cnodes = list(self.right)
1882
+ max_vdeg = max(self.degree(vnodes))
1883
+ max_cdeg = max(self.degree(cnodes))
1884
+ vnode_to_str = {v: str(i + 1) for i, v in enumerate(vnodes)}
1885
+ cnode_to_str = {v: str(i + 1) for i, v in enumerate(cnodes)}
1886
+
1887
+ def vnbr_str(idx):
1888
+ return cnode_to_str[idx]
1889
+
1890
+ def cnbr_str(idx):
1891
+ return vnode_to_str[idx]
1892
+
1893
+ # write header information
1894
+ fi.write("%d %d\n" % (len(vnodes), len(cnodes)))
1895
+ fi.write("%d %d\n" % (max_vdeg, max_cdeg))
1896
+ fi.write(" ".join(map(str, self.degree(vnodes))) + "\n")
1897
+ fi.write(" ".join(map(str, self.degree(cnodes))) + "\n")
1898
+ for vidx in vnodes:
1899
+ nbrs = self.neighbors(vidx)
1900
+ fi.write(" ".join(map(vnbr_str, nbrs)))
1901
+ fi.write(" 0" * (max_vdeg - len(nbrs)) + "\n")
1902
+ for cidx in cnodes:
1903
+ nbrs = self.neighbors(cidx)
1904
+ fi.write(" ".join(map(cnbr_str, nbrs)))
1905
+ fi.write(" 0" * (max_cdeg - len(nbrs)) + "\n")
1906
+
1907
+ # done
1908
+ fi.close()
1909
+
1910
+ # return self for chaining calls if desired
1911
+ return
1912
+
1913
+ def reduced_adjacency_matrix(self, sparse=True, *, base_ring=None, **kwds):
1914
+ r"""
1915
+ Return the reduced adjacency matrix for the given graph.
1916
+
1917
+ A reduced adjacency matrix contains only the non-redundant portion of
1918
+ the full adjacency matrix for the bipartite graph. Specifically, for
1919
+ zero matrices of the appropriate size, for the reduced adjacency
1920
+ matrix ``H``, the full adjacency matrix is ``[[0, H'], [H, 0]]``.
1921
+
1922
+ By default, the matrix returned is over the integers.
1923
+
1924
+ INPUT:
1925
+
1926
+ - ``sparse`` -- boolean (default: ``True``); whether to return a sparse
1927
+ matrix
1928
+
1929
+ - ``base_ring`` -- a ring (default: ``None``); the base ring of the
1930
+ matrix space to use. By default, the base ring is ``ZZ`` if the graph
1931
+ is not weighted and otherwise the same ring as the (first) weights.
1932
+
1933
+ - ``**kwds`` -- other keywords to pass to
1934
+ :func:`~sage.matrix.constructor.matrix`
1935
+
1936
+ EXAMPLES:
1937
+
1938
+ Bipartite graphs that are not weighted will return a matrix over ZZ,
1939
+ unless a base ring is specified::
1940
+
1941
+ sage: # needs sage.modules
1942
+ sage: M = Matrix([(1,1,1,0,0,0,0), (1,0,0,1,1,0,0),
1943
+ ....: (0,1,0,1,0,1,0), (1,1,0,1,0,0,1)])
1944
+ sage: B = BipartiteGraph(M)
1945
+ sage: N = B.reduced_adjacency_matrix(); N
1946
+ [1 1 1 0 0 0 0]
1947
+ [1 0 0 1 1 0 0]
1948
+ [0 1 0 1 0 1 0]
1949
+ [1 1 0 1 0 0 1]
1950
+ sage: N == M
1951
+ True
1952
+ sage: N[0,0].parent()
1953
+ Integer Ring
1954
+ sage: N2 = B.reduced_adjacency_matrix(base_ring=RDF); N2
1955
+ [1.0 1.0 1.0 0.0 0.0 0.0 0.0]
1956
+ [1.0 0.0 0.0 1.0 1.0 0.0 0.0]
1957
+ [0.0 1.0 0.0 1.0 0.0 1.0 0.0]
1958
+ [1.0 1.0 0.0 1.0 0.0 0.0 1.0]
1959
+ sage: N2[0, 0].parent()
1960
+ Real Double Field
1961
+
1962
+ Multi-edge graphs also return a matrix over ZZ,
1963
+ unless a base ring is specified::
1964
+
1965
+ sage: # needs sage.modules
1966
+ sage: M = Matrix([(1,1,2,0,0), (0,2,1,1,1), (0,1,2,1,1)])
1967
+ sage: B = BipartiteGraph(M, multiedges=True, sparse=True)
1968
+ sage: N = B.reduced_adjacency_matrix()
1969
+ sage: N == M
1970
+ True
1971
+ sage: N[0,0].parent()
1972
+ Integer Ring
1973
+ sage: N2 = B.reduced_adjacency_matrix(base_ring=RDF)
1974
+ sage: N2[0, 0].parent()
1975
+ Real Double Field
1976
+
1977
+ Weighted graphs will return a matrix over the ring given by their
1978
+ (first) weights, unless a base ring is specified::
1979
+
1980
+ sage: # needs sage.modules sage.rings.finite_rings
1981
+ sage: F.<a> = GF(4)
1982
+ sage: MS = MatrixSpace(F, 2, 3)
1983
+ sage: M = MS.matrix([[0, 1, a+1], [a, 1, 1]])
1984
+ sage: B = BipartiteGraph(M, weighted=True, sparse=True)
1985
+ sage: N = B.reduced_adjacency_matrix(sparse=False)
1986
+ sage: N == M
1987
+ True
1988
+ sage: N[0,0].parent()
1989
+ Finite Field in a of size 2^2
1990
+ sage: N2 = B.reduced_adjacency_matrix(base_ring=F)
1991
+ sage: N2[0, 0].parent()
1992
+ Finite Field in a of size 2^2
1993
+
1994
+ TESTS::
1995
+
1996
+ sage: B = BipartiteGraph()
1997
+ sage: B.reduced_adjacency_matrix() # needs sage.modules
1998
+ []
1999
+ sage: M = Matrix([[0,0], [0,0]]) # needs sage.modules
2000
+ sage: BipartiteGraph(M).reduced_adjacency_matrix() == M # needs sage.modules
2001
+ True
2002
+ sage: M = Matrix([[10,2/3], [0,0]]) # needs sage.modules
2003
+ sage: B = BipartiteGraph(M, weighted=True, sparse=True) # needs sage.modules
2004
+ sage: M == B.reduced_adjacency_matrix() # needs sage.modules
2005
+ True
2006
+
2007
+ An error is raised if the specified base ring is not compatible with the
2008
+ type of the weights of the bipartite graph::
2009
+
2010
+ sage: # needs sage.modules sage.rings.finite_rings
2011
+ sage: F.<a> = GF(4)
2012
+ sage: MS = MatrixSpace(F, 2, 3)
2013
+ sage: M = MS.matrix([[0, 1, a+1], [a, 1, 1]])
2014
+ sage: B = BipartiteGraph(M, weighted=True, sparse=True)
2015
+ sage: B.reduced_adjacency_matrix(base_ring=RDF)
2016
+ Traceback (most recent call last):
2017
+ ...
2018
+ TypeError: float() argument must be a string or a ...number, not 'sage.rings.finite_rings.element_givaro.FiniteField_givaroElement'
2019
+ """
2020
+ if self.multiple_edges() and self.weighted():
2021
+ raise NotImplementedError(
2022
+ "don't know how to represent weights for a multigraph")
2023
+ if self.is_directed():
2024
+ raise NotImplementedError(
2025
+ "reduced adjacency matrix does not exist for directed graphs")
2026
+
2027
+ # Create mappings of left and right vertices to integers.
2028
+ # These mappings are used to translate an edge to its reduced adjacency
2029
+ # matrix position, that is: (row index, column index)
2030
+ left = {v: i for i, v in enumerate(sorted(self.left))}
2031
+ right = {v: i for i, v in enumerate(sorted(self.right))}
2032
+
2033
+ # create dictionary of edges, values are weights for weighted graph,
2034
+ # otherwise the number of edges (always 1 for simple graphs)
2035
+ D = {}
2036
+ if self.weighted():
2037
+ for v1, v2, weight in self.edge_iterator():
2038
+ if v1 in left:
2039
+ D[right[v2], left[v1]] = weight
2040
+ else:
2041
+ D[right[v1], left[v2]] = weight
2042
+ else:
2043
+ # if we're normal or multi-edge, just create the matrix over ZZ
2044
+ for v1, v2, name in self.edge_iterator():
2045
+ idx = (right[v2], left[v1]) if v1 in left else (right[v1], left[v2])
2046
+ if idx in D:
2047
+ D[idx] += 1
2048
+ else:
2049
+ D[idx] = 1
2050
+
2051
+ # now construct and return the matrix from the dictionary we created
2052
+ from sage.matrix.constructor import matrix
2053
+ if base_ring is None:
2054
+ return matrix(len(self.right), len(self.left), D, sparse=sparse, **kwds)
2055
+ return matrix(base_ring, len(self.right), len(self.left), D, sparse=sparse, **kwds)
2056
+
2057
+ def matching(self, value_only=False, algorithm=None,
2058
+ use_edge_labels=False, solver=None, verbose=0,
2059
+ *, integrality_tolerance=1e-3):
2060
+ r"""
2061
+ Return a maximum matching of the graph represented by the list of its
2062
+ edges.
2063
+
2064
+ Given a graph `G` such that each edge `e` has a weight `w_e`, a maximum
2065
+ matching is a subset `S` of the edges of `G` of maximum weight such that
2066
+ no two edges of `S` are incident with each other.
2067
+
2068
+ INPUT:
2069
+
2070
+ - ``value_only`` -- boolean (default: ``False``); when set to ``True``,
2071
+ only the cardinal (or the weight) of the matching is returned
2072
+
2073
+ - ``algorithm`` -- string (default: ``'Hopcroft-Karp'`` if
2074
+ ``use_edge_labels==False``, otherwise ``'Edmonds'``); algorithm to use
2075
+ among:
2076
+
2077
+ - ``'Hopcroft-Karp'`` selects the default bipartite graph algorithm as
2078
+ implemented in NetworkX
2079
+
2080
+ - ``'Eppstein'`` selects Eppstein's algorithm as implemented in
2081
+ NetworkX
2082
+
2083
+ - ``'Edmonds'`` selects Edmonds' algorithm as implemented in NetworkX
2084
+
2085
+ - ``'LP'`` uses a Linear Program formulation of the matching problem
2086
+
2087
+ - ``use_edge_labels`` -- boolean (default: ``False``)
2088
+
2089
+ - when set to ``True``, computes a weighted matching where each edge
2090
+ is weighted by its label (if an edge has no label, `1` is assumed);
2091
+ only if ``algorithm`` is ``'Edmonds'``, ``'LP'``
2092
+
2093
+ - when set to ``False``, each edge has weight `1`
2094
+
2095
+ - ``solver`` -- string (default: ``None``); specifies a Mixed
2096
+ Integer Linear Programming (MILP) solver to be used. If set
2097
+ to ``None``, the default one is used. For more information
2098
+ on MILP solvers and which default solver is used, see the
2099
+ method :meth:`solve
2100
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the
2101
+ class :class:`MixedIntegerLinearProgram
2102
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
2103
+
2104
+ - ``verbose`` -- integer (default: 0); sets the level of
2105
+ verbosity. Set to 0 by default, which means quiet.
2106
+
2107
+ - ``integrality_tolerance`` -- float; parameter for use with
2108
+ MILP solvers over an inexact base ring; see
2109
+ :meth:`MixedIntegerLinearProgram.get_values`.
2110
+
2111
+ .. SEEALSO::
2112
+
2113
+ - :wikipedia:`Matching_(graph_theory)`
2114
+ - :meth:`~Graph.matching`
2115
+
2116
+ EXAMPLES:
2117
+
2118
+ Maximum matching in a cycle graph::
2119
+
2120
+ sage: G = BipartiteGraph(graphs.CycleGraph(10))
2121
+ sage: G.matching() # needs networkx
2122
+ [(0, 1, None), (2, 3, None), (4, 5, None), (6, 7, None), (8, 9, None)]
2123
+
2124
+ The size of a maximum matching in a complete bipartite graph using
2125
+ Eppstein::
2126
+
2127
+ sage: G = BipartiteGraph(graphs.CompleteBipartiteGraph(4,5))
2128
+ sage: G.matching(algorithm='Eppstein', value_only=True) # needs networkx
2129
+ 4
2130
+
2131
+ TESTS:
2132
+
2133
+ If ``algorithm`` is not set to one of the supported algorithms, an
2134
+ exception is raised::
2135
+
2136
+ sage: G = BipartiteGraph(graphs.CompleteBipartiteGraph(4,5))
2137
+ sage: G.matching(algorithm='somethingdifferent')
2138
+ Traceback (most recent call last):
2139
+ ...
2140
+ ValueError: algorithm must be "Hopcroft-Karp", "Eppstein", "Edmonds" or "LP"
2141
+
2142
+ Maximum matching in a weighted bipartite graph::
2143
+
2144
+ sage: G = graphs.CycleGraph(4)
2145
+ sage: B = BipartiteGraph([(u,v,2) for u,v in G.edges(sort=True, labels=0)])
2146
+ sage: sorted(B.matching(use_edge_labels=True)) # needs networkx
2147
+ [(0, 3, 2), (1, 2, 2)]
2148
+ sage: B.matching(use_edge_labels=True, value_only=True) # needs networkx
2149
+ 4
2150
+ sage: B.matching(use_edge_labels=True, value_only=True, algorithm='Edmonds') # needs networkx
2151
+ 4
2152
+ sage: B.matching(use_edge_labels=True, value_only=True, algorithm='LP') # needs sage.numerical.mip
2153
+ 4
2154
+ sage: B.matching(use_edge_labels=True, value_only=True, algorithm='Eppstein')
2155
+ Traceback (most recent call last):
2156
+ ...
2157
+ ValueError: use_edge_labels cannot be used with "Hopcroft-Karp" or "Eppstein"
2158
+ sage: B.matching(use_edge_labels=True, value_only=True, algorithm='Hopcroft-Karp')
2159
+ Traceback (most recent call last):
2160
+ ...
2161
+ ValueError: use_edge_labels cannot be used with "Hopcroft-Karp" or "Eppstein"
2162
+ sage: B.matching(use_edge_labels=False, value_only=True, # needs networkx
2163
+ ....: algorithm='Hopcroft-Karp')
2164
+ 2
2165
+ sage: B.matching(use_edge_labels=False, value_only=True, # needs networkx
2166
+ ....: algorithm='Eppstein')
2167
+ 2
2168
+ sage: B.matching(use_edge_labels=False, value_only=True, algorithm='Edmonds') # needs networkx
2169
+ 2
2170
+ sage: B.matching(use_edge_labels=False, value_only=True, algorithm='LP') # needs sage.numerical.mip
2171
+ 2
2172
+
2173
+ With multiedges enabled::
2174
+
2175
+ sage: G = BipartiteGraph(graphs.CubeGraph(3))
2176
+ sage: for e in G.edges(sort=True):
2177
+ ....: G.set_edge_label(e[0], e[1], int(e[0]) + int(e[1]))
2178
+ sage: G.allow_multiple_edges(True)
2179
+ sage: G.matching(use_edge_labels=True, value_only=True) # needs networkx
2180
+ 444
2181
+
2182
+ Empty bipartite graph and bipartite graphs without edges::
2183
+
2184
+ sage: B = BipartiteGraph()
2185
+ sage: algorithms = ["Hopcroft-Karp", "Eppstein", "Edmonds", "LP"]
2186
+ sage: not any(B.matching(algorithm=algo) for algo in algorithms) # needs networkx
2187
+ True
2188
+ sage: all(B.matching(algorithm=algo, value_only=True) == 0 for algo in algorithms) # needs networkx
2189
+ True
2190
+ sage: B.add_vertex(1, left=True)
2191
+ sage: B.add_vertex(2, left=True)
2192
+ sage: B.add_vertex(3, right=True)
2193
+ sage: not any(B.matching(algorithm=algo) for algo in algorithms) # needs networkx
2194
+ True
2195
+ sage: all(B.matching(algorithm=algo, value_only=True) == 0 for algo in algorithms) # needs networkx
2196
+ True
2197
+ """
2198
+ if algorithm is None:
2199
+ algorithm = "Edmonds" if use_edge_labels else "Hopcroft-Karp"
2200
+
2201
+ if algorithm == "Hopcroft-Karp" or algorithm == "Eppstein":
2202
+ if use_edge_labels:
2203
+ raise ValueError('use_edge_labels cannot be used with '
2204
+ '"Hopcroft-Karp" or "Eppstein"')
2205
+ d = []
2206
+ if self.size():
2207
+ import networkx
2208
+ # NetworkX matching algorithms for bipartite graphs may fail
2209
+ # when the graph is not connected
2210
+ if not self.is_connected():
2211
+ CC = [g for g in self.connected_components_subgraphs() if g.size()]
2212
+ else:
2213
+ CC = [self]
2214
+ v2int = {v: i for i, v in enumerate(self)}
2215
+ for g in CC:
2216
+ h = g.networkx_graph()
2217
+ if algorithm == "Hopcroft-Karp":
2218
+ m = networkx.bipartite.hopcroft_karp_matching(h)
2219
+ else:
2220
+ m = networkx.bipartite.eppstein_matching(h)
2221
+ d.extend((u, v, g.edge_label(u, v)) for u, v in m.items()
2222
+ if v2int[u] < v2int[v])
2223
+
2224
+ if value_only:
2225
+ return Integer(len(d))
2226
+ return d
2227
+
2228
+ elif algorithm == "Edmonds" or algorithm == "LP":
2229
+ return Graph.matching(self, value_only=value_only,
2230
+ algorithm=algorithm,
2231
+ use_edge_labels=use_edge_labels,
2232
+ solver=solver, verbose=verbose,
2233
+ integrality_tolerance=integrality_tolerance)
2234
+ raise ValueError('algorithm must be "Hopcroft-Karp", '
2235
+ '"Eppstein", "Edmonds" or "LP"')
2236
+
2237
+ def vertex_cover(self, algorithm='Konig', value_only=False,
2238
+ reduction_rules=True, solver=None, verbose=0,
2239
+ *, integrality_tolerance=1e-3):
2240
+ r"""
2241
+ Return a minimum vertex cover of ``self`` represented by a set of vertices.
2242
+
2243
+ A minimum vertex cover of a graph is a set `S` of vertices such that
2244
+ each edge is incident to at least one element of `S`, and such that `S`
2245
+ is of minimum cardinality. For more information, see
2246
+ :wikipedia:`Vertex_cover`.
2247
+
2248
+ Equivalently, a vertex cover is defined as the complement of an
2249
+ independent set.
2250
+
2251
+ As an optimization problem, it can be expressed as follows:
2252
+
2253
+ .. MATH::
2254
+
2255
+ \mbox{Minimize : }&\sum_{v\in G} b_v\\
2256
+ \mbox{Such that : }&\forall (u,v) \in G.edges(sort=True), b_u+b_v\geq 1\\
2257
+ &\forall x\in G, b_x\mbox{ is a binary variable}
2258
+
2259
+ INPUT:
2260
+
2261
+ - ``algorithm`` -- string (default: ``'Konig'``); algorithm to use
2262
+ among:
2263
+
2264
+ - ``'Konig'`` will compute a minimum vertex cover using Konig's
2265
+ algorithm (:wikipedia:`Kőnig%27s_theorem_(graph_theory)`)
2266
+
2267
+ - ``'Cliquer'`` will compute a minimum vertex cover
2268
+ using the Cliquer package
2269
+
2270
+ - ``'MILP'`` will compute a minimum vertex cover through a mixed
2271
+ integer linear program
2272
+
2273
+ - ``'mcqd'`` will use the MCQD solver
2274
+ (`<http://www.sicmm.org/~konc/maxclique/>`_), and the MCQD
2275
+ package must be installed
2276
+
2277
+ - ``value_only`` -- boolean (default: ``False``); if set to ``True``,
2278
+ only the size of a minimum vertex cover is returned. Otherwise,
2279
+ a minimum vertex cover is returned as a list of vertices.
2280
+
2281
+ - ``reduction_rules`` -- (default: ``True``) specify if the reductions
2282
+ rules from kernelization must be applied as pre-processing or not.
2283
+ See [ACFLSS04]_ for more details. Note that depending on the instance,
2284
+ it might be faster to disable reduction rules. This parameter is
2285
+ currently ignored when ``algorithm == "Konig"``.
2286
+
2287
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer
2288
+ Linear Programming (MILP) solver to be used. If set to ``None``, the
2289
+ default one is used. For more information on MILP solvers and which
2290
+ default solver is used, see the method :meth:`solve
2291
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
2292
+ :class:`MixedIntegerLinearProgram
2293
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
2294
+
2295
+ - ``verbose`` -- integer (default: 0); sets the level of
2296
+ verbosity. Set to 0 by default, which means quiet.
2297
+
2298
+ - ``integrality_tolerance`` -- float; parameter for use with MILP
2299
+ solvers over an inexact base ring; see
2300
+ :meth:`MixedIntegerLinearProgram.get_values`.
2301
+
2302
+ EXAMPLES:
2303
+
2304
+ On the Cycle Graph::
2305
+
2306
+ sage: B = BipartiteGraph(graphs.CycleGraph(6))
2307
+ sage: len(B.vertex_cover()) # needs networkx
2308
+ 3
2309
+ sage: B.vertex_cover(value_only=True) # needs networkx
2310
+ 3
2311
+
2312
+ The two algorithms should return the same result::
2313
+
2314
+ sage: # needs networkx numpy
2315
+ sage: g = BipartiteGraph(graphs.RandomBipartite(10, 10, .5))
2316
+ sage: vc1 = g.vertex_cover(algorithm='Konig')
2317
+ sage: vc2 = g.vertex_cover(algorithm='Cliquer') # needs cliquer
2318
+ sage: len(vc1) == len(vc2) # needs cliquer
2319
+ True
2320
+
2321
+ TESTS:
2322
+
2323
+ Giving a non connected bipartite graph::
2324
+
2325
+ sage: B = BipartiteGraph(graphs.CycleGraph(4) * 2)
2326
+ sage: len(B.vertex_cover()) # needs networkx
2327
+ 4
2328
+
2329
+ Empty bipartite graph and bipartite graphs without edges::
2330
+
2331
+ sage: B = BipartiteGraph()
2332
+ sage: algorithms = ["Konig", "MILP"]
2333
+ sage: import sage.graphs.cliquer; algorithms += ["Cliquer"] # needs cliquer
2334
+ sage: all(B.vertex_cover(algorithm=algo) == [] for algo in algorithms)
2335
+ True
2336
+ sage: all(B.vertex_cover(algorithm=algo, value_only=True) == 0 for algo in algorithms)
2337
+ True
2338
+ sage: B.add_vertex(1, left=True)
2339
+ sage: B.add_vertex(2, left=True)
2340
+ sage: B.add_vertex(3, right=True)
2341
+ sage: all(B.vertex_cover(algorithm=algo) == [] for algo in algorithms)
2342
+ True
2343
+ sage: all(B.vertex_cover(algorithm=algo, value_only=True) == 0 for algo in algorithms)
2344
+ True
2345
+ """
2346
+ if algorithm != "Konig":
2347
+ return Graph.vertex_cover(self, algorithm=algorithm,
2348
+ value_only=value_only,
2349
+ reduction_rules=reduction_rules,
2350
+ solver=solver,
2351
+ verbose=verbose,
2352
+ integrality_tolerance=integrality_tolerance)
2353
+
2354
+ if not self.is_connected():
2355
+ VC = []
2356
+ for b in self.connected_components_subgraphs():
2357
+ if b.size():
2358
+ VC.extend(b.vertex_cover(algorithm='Konig'))
2359
+ if value_only:
2360
+ return sum(VC)
2361
+ return VC
2362
+
2363
+ M = Graph(self.matching())
2364
+ left = set(self.left)
2365
+ right = set(self.right)
2366
+
2367
+ # Initialize Z with vertices in left that are not involved in the
2368
+ # matching
2369
+ Z = left.difference(M.vertex_iterator())
2370
+
2371
+ # Alternate: extend Z with all vertices reachable by alternate paths
2372
+ # (match / non-match edges).
2373
+ X = set(Z)
2374
+ while X:
2375
+ # Follow non matched edges
2376
+ Y = set()
2377
+ for u in X:
2378
+ for v in self.neighbors(u):
2379
+ if v not in Z and not M.has_edge(u, v):
2380
+ Y.add(v)
2381
+ Z.update(Y)
2382
+
2383
+ # Follow matched edges
2384
+ X = set()
2385
+ for u in Y:
2386
+ for v in M.neighbor_iterator(u):
2387
+ if v not in Z:
2388
+ X.add(v)
2389
+ Z.update(X)
2390
+
2391
+ # The solution is (left \ Z) + (right \cap Z)
2392
+ VC = list((left.difference(Z)).union(right.intersection(Z)))
2393
+
2394
+ if value_only:
2395
+ return len(VC)
2396
+ return VC
2397
+
2398
+ def _subgraph_by_adding(self, vertices=None, edges=None, edge_property=None, immutable=None):
2399
+ r"""
2400
+ Return the subgraph containing the given vertices and edges.
2401
+
2402
+ The edges also satisfy the edge_property, if it is not None. The
2403
+ subgraph is created by creating a new empty graph and adding the
2404
+ necessary vertices, edges, and other properties.
2405
+
2406
+ INPUT:
2407
+
2408
+ - ``vertices`` -- list (default: ``None``); list of vertices
2409
+
2410
+ - ``edges`` -- (default: ``None``) either a single edge or an iterable
2411
+ container of edges (e.g., a list, set, file, numeric array, etc.). If
2412
+ no edges are specified, then all edges are assumed and the returned
2413
+ graph is an induced subgraph. In the case of multiple edges,
2414
+ specifying an edge as `(u, v)` means to keep all edges `(u, v)`,
2415
+ regardless of the label.
2416
+
2417
+ - ``edge_property`` -- (default: ``None``) if specified, this is
2418
+ expected to be a function on edges, which is intersected with the
2419
+ edges specified, if any are
2420
+
2421
+ - ``immutable`` -- boolean (default: ``None``); currently ignored for
2422
+ ``BipartiteGraph``
2423
+
2424
+ EXAMPLES::
2425
+
2426
+ sage: B = BipartiteGraph(graphs.CycleGraph(6))
2427
+ sage: H = B._subgraph_by_adding(vertices=B.left)
2428
+ sage: H.order(), H.size()
2429
+ (3, 0)
2430
+ sage: H = B._subgraph_by_adding(vertices=[0, 1])
2431
+ sage: H.order(), H.size()
2432
+ (2, 1)
2433
+ sage: H = B._subgraph_by_adding(vertices=[0, 1, 2], edges=[(0, 1)])
2434
+ sage: H.order(), H.size()
2435
+ (3, 1)
2436
+
2437
+ Using the property arguments::
2438
+
2439
+ sage: B = BipartiteGraph([(0, 1, 1), (0, 2, 0), (0, 3, 0), (3, 4, 1)])
2440
+ sage: H = B._subgraph_by_adding(vertices=B.vertices(sort=False), edge_property=(lambda e: e[2] == 1))
2441
+ sage: H.order(), H.size()
2442
+ (5, 2)
2443
+ """
2444
+ B = self.__class__(weighted=self._weighted, loops=self.allows_loops(),
2445
+ multiedges=self.allows_multiple_edges())
2446
+ B.name("Subgraph of ({})".format(self.name()))
2447
+ for u in vertices:
2448
+ if u in self.left:
2449
+ B.add_vertex(u, left=True)
2450
+ elif u in self.right:
2451
+ B.add_vertex(u, right=True)
2452
+ if edges is None:
2453
+ edges_to_keep = self.edge_boundary(B.left, B.right, sort=False)
2454
+ else:
2455
+ edges_to_keep_labeled = set(e for e in edges if len(e) == 3)
2456
+ edges_to_keep_unlabeled = set(e for e in edges if len(e) == 2)
2457
+ edges_to_keep = []
2458
+ for u, v, l in self.edge_boundary(B.left, B.right, sort=False):
2459
+ if ((u, v, l) in edges_to_keep_labeled
2460
+ or (v, u, l) in edges_to_keep_labeled
2461
+ or (u, v) in edges_to_keep_unlabeled
2462
+ or (v, u) in edges_to_keep_unlabeled):
2463
+ edges_to_keep.append((u, v, l))
2464
+
2465
+ if edge_property is not None:
2466
+ edges_to_keep = [e for e in edges_to_keep if edge_property(e)]
2467
+
2468
+ B.add_edges(edges_to_keep)
2469
+
2470
+ B._copy_attribute_from(self, '_pos')
2471
+ B._copy_attribute_from(self, '_assoc')
2472
+
2473
+ return B
2474
+
2475
+ def _subgraph_by_deleting(self, vertices=None, edges=None, inplace=False,
2476
+ edge_property=None, immutable=None):
2477
+ r"""
2478
+ Return the subgraph containing the given vertices and edges.
2479
+
2480
+ The edges also satisfy the edge_property, if it is not None. The
2481
+ subgraph is created by creating deleting things that are not needed.
2482
+
2483
+ INPUT:
2484
+
2485
+ - ``vertices`` -- list (default: ``None``); list of vertices
2486
+
2487
+ - ``edges`` -- (default: ``None``) either a single edge or an iterable
2488
+ container of edges (e.g., a list, set, file, numeric array, etc.). If
2489
+ no edges are specified, then all edges are assumed and the returned
2490
+ graph is an induced subgraph. In the case of multiple edges,
2491
+ specifying an edge as `(u, v)` means to keep all edges `(u, v)`,
2492
+ regardless of the label.
2493
+
2494
+ - ``inplace`` -- boolean (default: ``False``); when ``True``, the
2495
+ current graph is modified in place by deleting the extra vertices and
2496
+ edges. Otherwise a modified copy of the graph is returned
2497
+
2498
+ - ``edge_property`` -- (default: ``None``) if specified, this is
2499
+ expected to be a function on edges, which is intersected with the
2500
+ edges specified, if any are
2501
+
2502
+ - ``immutable`` -- boolean (default: ``None``); currently ignored for
2503
+ ``BipartiteGraph``
2504
+
2505
+ EXAMPLES::
2506
+
2507
+ sage: B = BipartiteGraph(graphs.CycleGraph(6))
2508
+ sage: H = B._subgraph_by_deleting(vertices=B.left)
2509
+ sage: H.order(), H.size()
2510
+ (3, 0)
2511
+ sage: H = B._subgraph_by_deleting(vertices=[0, 1])
2512
+ sage: H.order(), H.size()
2513
+ (2, 1)
2514
+ sage: H = B._subgraph_by_deleting(vertices=[0, 1, 2], edges=[(0, 1)])
2515
+ sage: H.order(), H.size()
2516
+ (3, 1)
2517
+
2518
+ Using the property arguments::
2519
+
2520
+ sage: B = BipartiteGraph([(0, 1, 1), (0, 2, 0), (0, 3, 0), (3, 4, 1)])
2521
+ sage: H = B._subgraph_by_deleting(vertices=B.vertices(sort=False), edge_property=(lambda e: e[2] == 1))
2522
+ sage: H.order(), H.size()
2523
+ (5, 2)
2524
+ """
2525
+ if inplace:
2526
+ B = self
2527
+ else:
2528
+ # We make a copy of the graph
2529
+ B = BipartiteGraph(data=self.edges(sort=True), partition=[self.left, self.right])
2530
+ B._copy_attribute_from(self, '_pos')
2531
+ B._copy_attribute_from(self, '_assoc')
2532
+ B.name("Subgraph of ({})".format(self.name()))
2533
+
2534
+ vertices = set(vertices)
2535
+ B.delete_vertices([v for v in B.vertex_iterator() if v not in vertices])
2536
+
2537
+ edges_to_delete = []
2538
+ if edges is not None:
2539
+ edges_to_keep_labeled = set(e for e in edges if len(e) == 3)
2540
+ edges_to_keep_unlabeled = set(e for e in edges if len(e) == 2)
2541
+ for u, v, l in B.edge_iterator():
2542
+ if ((u, v, l) not in edges_to_keep_labeled
2543
+ and (v, u, l) not in edges_to_keep_labeled
2544
+ and (u, v) not in edges_to_keep_unlabeled
2545
+ and (v, u) not in edges_to_keep_unlabeled):
2546
+ edges_to_delete.append((u, v, l))
2547
+ if edge_property is not None:
2548
+ # We might get duplicate edges, but this does handle the case of
2549
+ # multiple edges.
2550
+ edges_to_delete.extend(e for e in B.edge_iterator() if not edge_property(e))
2551
+
2552
+ B.delete_edges(edges_to_delete)
2553
+ return B
2554
+
2555
+ def canonical_label(self, partition=None, certificate=False,
2556
+ edge_labels=False, algorithm=None, return_graph=True,
2557
+ immutable=None):
2558
+ r"""
2559
+ Return the canonical graph.
2560
+
2561
+ A canonical graph is the representative graph of an isomorphism
2562
+ class by some canonization function `c`. If `G` and `H` are graphs,
2563
+ then `G \cong c(G)`, and `c(G) == c(H)` if and only if `G \cong H`.
2564
+
2565
+ See the :wikipedia:`Graph_canonization` for more information.
2566
+
2567
+ INPUT:
2568
+
2569
+ - ``partition`` -- if given, the canonical label with respect
2570
+ to this set partition will be computed. The default is the unit
2571
+ set partition.
2572
+
2573
+ - ``certificate`` -- boolean (default: ``False``); when set to
2574
+ ``True``, a dictionary mapping from the vertices of the (di)graph
2575
+ to its canonical label will also be returned
2576
+
2577
+ - ``edge_labels`` -- boolean (default: ``False``); when set to
2578
+ ``True``, allows only permutations respecting edge labels
2579
+
2580
+ - ``algorithm`` -- string (default: ``None``); the algorithm to use.
2581
+ Currently available:
2582
+
2583
+ * ``'bliss'``: use the optional package bliss
2584
+ (http://www.tcs.tkk.fi/Software/bliss/index.html);
2585
+ * ``'sage'``: always use Sage's implementation.
2586
+ * ``None`` (default): use bliss when available and possible
2587
+
2588
+ .. NOTE::
2589
+
2590
+ Make sure you always compare canonical forms obtained by the
2591
+ same algorithm.
2592
+
2593
+ - ``return_graph`` -- boolean (default: ``True``); when set to
2594
+ ``False``, returns the list of edges of the canonical graph
2595
+ instead of the canonical graph. Only available when ``'bliss'``
2596
+ is explicitly set as algorithm.
2597
+
2598
+ - ``immutable`` -- boolean (default: ``None``); whether to create a
2599
+ mutable/immutable (di)graph. ``immutable=None`` (default) means that
2600
+ the (di)graph and its canonical (di)graph will behave the same way.
2601
+
2602
+ EXAMPLES::
2603
+
2604
+ sage: B = BipartiteGraph( [(0, 4), (0, 5), (0, 6), (0, 8), (1, 5),
2605
+ ....: (1, 7), (1, 8), (2, 6), (2, 7), (2, 8),
2606
+ ....: (3, 4), (3, 7), (3, 8), (4, 9), (5, 9),
2607
+ ....: (6, 9), (7, 9)] )
2608
+ sage: C = B.canonical_label(partition=(B.left,B.right), algorithm='sage')
2609
+ sage: C
2610
+ Bipartite graph on 10 vertices
2611
+ sage: C.left
2612
+ {0, 1, 2, 3, 4}
2613
+ sage: C.right
2614
+ {5, 6, 7, 8, 9}
2615
+
2616
+ ::
2617
+
2618
+ sage: B = BipartiteGraph( [(0, 4), (0, 5), (0, 6), (0, 8), (1, 5),
2619
+ ....: (1, 7), (1, 8), (2, 6), (2, 7), (2, 8),
2620
+ ....: (3, 4), (3, 7), (3, 8), (4, 9), (5, 9),
2621
+ ....: (6, 9), (7, 9)] )
2622
+ sage: C, cert = B.canonical_label(partition=(B.left, B.right),
2623
+ ....: certificate=True, algorithm='sage')
2624
+ sage: C
2625
+ Bipartite graph on 10 vertices
2626
+ sage: C.left
2627
+ {0, 1, 2, 3, 4}
2628
+ sage: C.right
2629
+ {5, 6, 7, 8, 9}
2630
+ sage: cert == {0: 3, 1: 0, 2: 1, 3: 2, 4: 5, 5: 7, 6: 6, 7: 8, 8: 9, 9: 4}
2631
+ True
2632
+
2633
+ ::
2634
+
2635
+ sage: G = Graph({0: [5, 6], 1: [4, 5], 2: [4, 6], 3: [4, 5, 6]})
2636
+ sage: B = BipartiteGraph(G)
2637
+ sage: C = B.canonical_label(partition=(B.left, B.right),
2638
+ ....: edge_labels=True, algorithm='sage')
2639
+ sage: C.left
2640
+ {0, 1, 2, 3}
2641
+ sage: C.right
2642
+ {4, 5, 6}
2643
+
2644
+ TESTS:
2645
+
2646
+ Check that :issue:`38832` is fixed::
2647
+
2648
+ sage: # needs sage.modules
2649
+ sage: B = BipartiteGraph(matrix([[1, 1], [1, 1]]))
2650
+ sage: B.canonical_label()
2651
+ Bipartite graph on 4 vertices
2652
+ sage: B.canonical_label(certificate=True)[0]
2653
+ Bipartite graph on 4 vertices
2654
+ sage: B.canonical_label(edge_labels=True)
2655
+ Bipartite graph on 4 vertices
2656
+ sage: B.allow_multiple_edges(True)
2657
+ sage: B.add_edges(B.edges())
2658
+ sage: B.canonical_label()
2659
+ Bipartite multi-graph on 4 vertices
2660
+
2661
+ Check the behavior for immutable graphs::
2662
+
2663
+ sage: G = BipartiteGraph(graphs.CycleGraph(4))
2664
+ sage: G.canonical_label().is_immutable()
2665
+ False
2666
+ sage: G.canonical_label(immutable=True).is_immutable()
2667
+ True
2668
+ sage: G = BipartiteGraph(graphs.CycleGraph(4), immutable=True)
2669
+ sage: G.canonical_label().is_immutable()
2670
+ True
2671
+ sage: G.canonical_label(immutable=False).is_immutable()
2672
+ False
2673
+
2674
+ .. SEEALSO::
2675
+
2676
+ :meth:`~sage.graphs.generic_graph.GenericGraph.canonical_label()`
2677
+ """
2678
+ if certificate:
2679
+ C, cert = GenericGraph.canonical_label(self, partition=partition,
2680
+ certificate=certificate,
2681
+ edge_labels=edge_labels,
2682
+ algorithm=algorithm,
2683
+ return_graph=return_graph,
2684
+ immutable=immutable)
2685
+
2686
+ else:
2687
+ from sage.groups.perm_gps.partn_ref.refinement_graphs import search_tree
2688
+ from sage.graphs.graph import Graph
2689
+ from sage.graphs.generic_graph import graph_isom_equivalent_non_edge_labeled_graph
2690
+ from itertools import chain
2691
+
2692
+ cert = {}
2693
+
2694
+ if edge_labels or self.has_multiple_edges():
2695
+ G, partition, relabeling = graph_isom_equivalent_non_edge_labeled_graph(self, partition, return_relabeling=True)
2696
+ G_vertices = list(chain(*partition))
2697
+ G_to = {u: i for i, u in enumerate(G_vertices)}
2698
+ H = Graph(len(G_vertices))
2699
+ HB = H._backend
2700
+ for u, v in G.edge_iterator(labels=False):
2701
+ HB.add_edge(G_to[u], G_to[v], None, False)
2702
+ GC = HB.c_graph()[0]
2703
+ partition = [[G_to[vv] for vv in cell] for cell in partition]
2704
+ a, b, c = search_tree(GC, partition, certificate=True, dig=False)
2705
+ # c is a permutation to the canonical label of G,
2706
+ # which depends only on isomorphism class of self.
2707
+ cert = {v: c[G_to[relabeling[v]]] for v in self}
2708
+
2709
+ else:
2710
+ if partition is None:
2711
+ partition = self.bipartition()
2712
+ G_vertices = list(chain(*partition))
2713
+ G_to = {u: i for i, u in enumerate(G_vertices)}
2714
+ H = Graph(len(G_vertices))
2715
+ HB = H._backend
2716
+ for u, v in self.edge_iterator(labels=False):
2717
+ HB.add_edge(G_to[u], G_to[v], None, False)
2718
+ GC = HB.c_graph()[0]
2719
+ partition = [[G_to[vv] for vv in cell] for cell in partition]
2720
+ a, b, c = search_tree(GC, partition, certificate=True, dig=False)
2721
+ cert = {v: c[G_to[v]] for v in G_to}
2722
+
2723
+ if immutable is None:
2724
+ immutable = self.is_immutable()
2725
+ C = self.relabel(perm=cert, inplace=False, immutable=immutable)
2726
+
2727
+ C.left = {cert[v] for v in self.left}
2728
+ C.right = {cert[v] for v in self.right}
2729
+
2730
+ if certificate:
2731
+ return C, cert
2732
+ return C