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