passagemath-graphs 10.6.1rc1__cp310-cp310-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
  2. passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
  3. passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
  5. passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
  6. passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
  7. passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
  8. sage/all__sagemath_graphs.py +39 -0
  9. sage/combinat/abstract_tree.py +2723 -0
  10. sage/combinat/all__sagemath_graphs.py +34 -0
  11. sage/combinat/binary_tree.py +5306 -0
  12. sage/combinat/cluster_algebra_quiver/all.py +22 -0
  13. sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
  14. sage/combinat/cluster_algebra_quiver/interact.py +124 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
  18. sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
  19. sage/combinat/designs/MOLS_handbook_data.py +570 -0
  20. sage/combinat/designs/all.py +58 -0
  21. sage/combinat/designs/bibd.py +1655 -0
  22. sage/combinat/designs/block_design.py +1071 -0
  23. sage/combinat/designs/covering_array.py +269 -0
  24. sage/combinat/designs/covering_design.py +530 -0
  25. sage/combinat/designs/database.py +5615 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  28. sage/combinat/designs/designs_pyx.pxd +21 -0
  29. sage/combinat/designs/designs_pyx.pyx +993 -0
  30. sage/combinat/designs/difference_family.py +3951 -0
  31. sage/combinat/designs/difference_matrices.py +279 -0
  32. sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  33. sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
  34. sage/combinat/designs/ext_rep.py +1064 -0
  35. sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
  36. sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
  37. sage/combinat/designs/group_divisible_designs.py +361 -0
  38. sage/combinat/designs/incidence_structures.py +2357 -0
  39. sage/combinat/designs/latin_squares.py +581 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2244 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
  44. sage/combinat/designs/resolvable_bibd.py +815 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
  47. sage/combinat/designs/subhypergraph_search.pyx +530 -0
  48. sage/combinat/designs/twographs.py +306 -0
  49. sage/combinat/finite_state_machine.py +14874 -0
  50. sage/combinat/finite_state_machine_generators.py +2006 -0
  51. sage/combinat/graph_path.py +448 -0
  52. sage/combinat/interval_posets.py +3908 -0
  53. sage/combinat/nu_tamari_lattice.py +269 -0
  54. sage/combinat/ordered_tree.py +1446 -0
  55. sage/combinat/posets/all.py +46 -0
  56. sage/combinat/posets/bubble_shuffle.py +247 -0
  57. sage/combinat/posets/cartesian_product.py +493 -0
  58. sage/combinat/posets/d_complete.py +182 -0
  59. sage/combinat/posets/elements.py +273 -0
  60. sage/combinat/posets/forest.py +30 -0
  61. sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
  62. sage/combinat/posets/hasse_cython.pyx +174 -0
  63. sage/combinat/posets/hasse_diagram.py +3672 -0
  64. sage/combinat/posets/hochschild_lattice.py +158 -0
  65. sage/combinat/posets/incidence_algebras.py +794 -0
  66. sage/combinat/posets/lattices.py +5117 -0
  67. sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
  68. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  69. sage/combinat/posets/linear_extensions.py +1037 -0
  70. sage/combinat/posets/mobile.py +275 -0
  71. sage/combinat/posets/moebius_algebra.py +776 -0
  72. sage/combinat/posets/poset_examples.py +2178 -0
  73. sage/combinat/posets/posets.py +9360 -0
  74. sage/combinat/rooted_tree.py +1070 -0
  75. sage/combinat/shard_order.py +239 -0
  76. sage/combinat/tamari_lattices.py +384 -0
  77. sage/combinat/yang_baxter_graph.py +923 -0
  78. sage/databases/all__sagemath_graphs.py +1 -0
  79. sage/databases/knotinfo_db.py +1231 -0
  80. sage/ext_data/all__sagemath_graphs.py +1 -0
  81. sage/ext_data/graphs/graph_plot_js.html +330 -0
  82. sage/ext_data/kenzo/CP2.txt +45 -0
  83. sage/ext_data/kenzo/CP3.txt +349 -0
  84. sage/ext_data/kenzo/CP4.txt +4774 -0
  85. sage/ext_data/kenzo/README.txt +49 -0
  86. sage/ext_data/kenzo/S4.txt +20 -0
  87. sage/graphs/all.py +42 -0
  88. sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
  89. sage/graphs/asteroidal_triples.pyx +320 -0
  90. sage/graphs/base/all.py +1 -0
  91. sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  92. sage/graphs/base/boost_graph.pxd +106 -0
  93. sage/graphs/base/boost_graph.pyx +3045 -0
  94. sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  95. sage/graphs/base/c_graph.pxd +106 -0
  96. sage/graphs/base/c_graph.pyx +5096 -0
  97. sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  98. sage/graphs/base/dense_graph.pxd +28 -0
  99. sage/graphs/base/dense_graph.pyx +801 -0
  100. sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
  101. sage/graphs/base/graph_backends.pxd +5 -0
  102. sage/graphs/base/graph_backends.pyx +797 -0
  103. sage/graphs/base/overview.py +85 -0
  104. sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  105. sage/graphs/base/sparse_graph.pxd +90 -0
  106. sage/graphs/base/sparse_graph.pyx +1653 -0
  107. sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  108. sage/graphs/base/static_dense_graph.pxd +5 -0
  109. sage/graphs/base/static_dense_graph.pyx +1032 -0
  110. sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
  111. sage/graphs/base/static_sparse_backend.pxd +27 -0
  112. sage/graphs/base/static_sparse_backend.pyx +1583 -0
  113. sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  114. sage/graphs/base/static_sparse_graph.pxd +37 -0
  115. sage/graphs/base/static_sparse_graph.pyx +1375 -0
  116. sage/graphs/bipartite_graph.py +2732 -0
  117. sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
  118. sage/graphs/centrality.pyx +1038 -0
  119. sage/graphs/cographs.py +519 -0
  120. sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/comparability.pyx +851 -0
  122. sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  123. sage/graphs/connectivity.pxd +157 -0
  124. sage/graphs/connectivity.pyx +4813 -0
  125. sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
  126. sage/graphs/convexity_properties.pxd +16 -0
  127. sage/graphs/convexity_properties.pyx +870 -0
  128. sage/graphs/digraph.py +4754 -0
  129. sage/graphs/digraph_generators.py +1993 -0
  130. sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
  131. sage/graphs/distances_all_pairs.pxd +12 -0
  132. sage/graphs/distances_all_pairs.pyx +2938 -0
  133. sage/graphs/domination.py +1363 -0
  134. sage/graphs/dot2tex_utils.py +100 -0
  135. sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  136. sage/graphs/edge_connectivity.pyx +1215 -0
  137. sage/graphs/generators/all.py +1 -0
  138. sage/graphs/generators/basic.py +1769 -0
  139. sage/graphs/generators/chessboard.py +538 -0
  140. sage/graphs/generators/classical_geometries.py +1611 -0
  141. sage/graphs/generators/degree_sequence.py +235 -0
  142. sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
  143. sage/graphs/generators/distance_regular.pyx +2846 -0
  144. sage/graphs/generators/families.py +4759 -0
  145. sage/graphs/generators/intersection.py +565 -0
  146. sage/graphs/generators/platonic_solids.py +262 -0
  147. sage/graphs/generators/random.py +2623 -0
  148. sage/graphs/generators/smallgraphs.py +5741 -0
  149. sage/graphs/generators/world_map.py +724 -0
  150. sage/graphs/generic_graph.py +26867 -0
  151. sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  152. sage/graphs/generic_graph_pyx.pxd +34 -0
  153. sage/graphs/generic_graph_pyx.pyx +1673 -0
  154. sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
  155. sage/graphs/genus.pyx +622 -0
  156. sage/graphs/graph.py +9645 -0
  157. sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
  158. sage/graphs/graph_coloring.pyx +2284 -0
  159. sage/graphs/graph_database.py +1177 -0
  160. sage/graphs/graph_decompositions/all.py +1 -0
  161. sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  163. sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
  165. sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  167. sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
  168. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  169. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  170. sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/graph_products.pyx +508 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  173. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  174. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  176. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  177. sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  179. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  180. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  181. sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
  182. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  183. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  184. sage/graphs/graph_editor.py +82 -0
  185. sage/graphs/graph_generators.py +3314 -0
  186. sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  187. sage/graphs/graph_generators_pyx.pyx +95 -0
  188. sage/graphs/graph_input.py +812 -0
  189. sage/graphs/graph_latex.py +2064 -0
  190. sage/graphs/graph_list.py +410 -0
  191. sage/graphs/graph_plot.py +1756 -0
  192. sage/graphs/graph_plot_js.py +338 -0
  193. sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
  194. sage/graphs/hyperbolicity.pyx +1704 -0
  195. sage/graphs/hypergraph_generators.py +364 -0
  196. sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  197. sage/graphs/independent_sets.pxd +13 -0
  198. sage/graphs/independent_sets.pyx +402 -0
  199. sage/graphs/isgci.py +1033 -0
  200. sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/isoperimetric_inequalities.pyx +489 -0
  202. sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  203. sage/graphs/line_graph.pyx +743 -0
  204. sage/graphs/lovasz_theta.py +77 -0
  205. sage/graphs/matching.py +1633 -0
  206. sage/graphs/matching_covered_graph.py +3590 -0
  207. sage/graphs/orientations.py +1489 -0
  208. sage/graphs/partial_cube.py +459 -0
  209. sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
  210. sage/graphs/path_enumeration.pyx +2040 -0
  211. sage/graphs/pq_trees.py +1129 -0
  212. sage/graphs/print_graphs.py +201 -0
  213. sage/graphs/schnyder.py +865 -0
  214. sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/spanning_tree.pyx +1457 -0
  216. sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/strongly_regular_db.pyx +3340 -0
  218. sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
  219. sage/graphs/traversals.pxd +9 -0
  220. sage/graphs/traversals.pyx +1872 -0
  221. sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
  222. sage/graphs/trees.pxd +15 -0
  223. sage/graphs/trees.pyx +310 -0
  224. sage/graphs/tutte_polynomial.py +713 -0
  225. sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/views.pyx +794 -0
  227. sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
  228. sage/graphs/weakly_chordal.pyx +604 -0
  229. sage/groups/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  231. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
  233. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  234. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  235. sage/knots/all.py +6 -0
  236. sage/knots/free_knotinfo_monoid.py +507 -0
  237. sage/knots/gauss_code.py +291 -0
  238. sage/knots/knot.py +682 -0
  239. sage/knots/knot_table.py +284 -0
  240. sage/knots/knotinfo.py +2900 -0
  241. sage/knots/link.py +4715 -0
  242. sage/sandpiles/all.py +13 -0
  243. sage/sandpiles/examples.py +225 -0
  244. sage/sandpiles/sandpile.py +6365 -0
  245. sage/topology/all.py +22 -0
  246. sage/topology/cell_complex.py +1214 -0
  247. sage/topology/cubical_complex.py +1976 -0
  248. sage/topology/delta_complex.py +1806 -0
  249. sage/topology/filtered_simplicial_complex.py +744 -0
  250. sage/topology/moment_angle_complex.py +823 -0
  251. sage/topology/simplicial_complex.py +5160 -0
  252. sage/topology/simplicial_complex_catalog.py +92 -0
  253. sage/topology/simplicial_complex_examples.py +1680 -0
  254. sage/topology/simplicial_complex_homset.py +205 -0
  255. sage/topology/simplicial_complex_morphism.py +836 -0
  256. sage/topology/simplicial_set.py +4102 -0
  257. sage/topology/simplicial_set_catalog.py +55 -0
  258. sage/topology/simplicial_set_constructions.py +2954 -0
  259. sage/topology/simplicial_set_examples.py +865 -0
  260. sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,2357 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ r"""
3
+ Incidence structures (i.e. hypergraphs, i.e. set systems)
4
+
5
+ An incidence structure is specified by a list of points, blocks, or an incidence
6
+ matrix ([1]_, [2]_). :class:`IncidenceStructure` instances have the following methods:
7
+
8
+ {METHODS_OF_IncidenceStructure}
9
+
10
+ REFERENCES:
11
+
12
+ .. [1] Block designs and incidence structures from wikipedia,
13
+ :wikipedia:`Block_design`
14
+ :wikipedia:`Incidence_structure`
15
+
16
+ .. [2] \E. Assmus, J. Key, Designs and their codes, CUP, 1992.
17
+
18
+ AUTHORS:
19
+
20
+ - Peter Dobcsanyi and David Joyner (2007-2008)
21
+
22
+ This is a significantly modified form of part of the module block_design.py
23
+ (version 0.6) written by Peter Dobcsanyi peter@designtheory.org.
24
+
25
+ - Vincent Delecroix (2014): major rewrite
26
+
27
+ Methods
28
+ -------
29
+ """
30
+ # **************************************************************************
31
+ # Copyright (C) 2007 #
32
+ # #
33
+ # Peter Dobcsanyi and David Joyner #
34
+ # <peter@designtheory.org> <wdjoyner@gmail.com> #
35
+ # #
36
+ # #
37
+ # Distributed under the terms of the GNU General Public License (GPL) #
38
+ # as published by the Free Software Foundation; either version 2 of #
39
+ # the License, or (at your option) any later version. #
40
+ # https://www.gnu.org/licenses/ #
41
+ # **************************************************************************
42
+ from __future__ import annotations
43
+
44
+ from sage.misc.latex import latex
45
+ from sage.misc.lazy_import import lazy_import
46
+ from sage.rings.integer import Integer
47
+ from sage.sets.set import Set
48
+
49
+ lazy_import('sage.libs.gap.libgap', 'libgap')
50
+
51
+
52
+ class IncidenceStructure:
53
+ r"""
54
+ A base class for incidence structures (i.e. hypergraphs, i.e. set systems)
55
+
56
+ An incidence structure (i.e. hypergraph, i.e. set system) can be defined
57
+ from a collection of blocks (i.e. sets, i.e. edges), optionally with an
58
+ explicit ground set (i.e. point set, i.e. vertex set). Alternatively they
59
+ can be defined from a binary incidence matrix.
60
+
61
+ INPUT:
62
+
63
+ - ``points`` -- (i.e. ground set, i.e. vertex set) the underlying set. If
64
+ ``points`` is an integer `v`, then the set is considered to be `\{0, ...,
65
+ v-1\}`.
66
+
67
+ .. NOTE::
68
+
69
+ The following syntax, where ``points`` is omitted, automatically
70
+ defines the ground set as the union of the blocks::
71
+
72
+ sage: H = IncidenceStructure([['a','b','c'],['c','d','e']])
73
+ sage: sorted(H.ground_set())
74
+ ['a', 'b', 'c', 'd', 'e']
75
+
76
+ - ``blocks`` -- (i.e. edges, i.e. sets) the blocks defining the incidence
77
+ structure; can be any iterable
78
+
79
+ - ``incidence_matrix`` -- a binary incidence matrix; each column represents
80
+ a set
81
+
82
+ - ``name`` -- string (such as "Fano plane")
83
+
84
+ - ``check`` -- whether to check the input
85
+
86
+ - ``copy`` -- (use with caution) if set to ``False`` then ``blocks`` must be
87
+ a list of lists of integers. The list will not be copied but will be
88
+ modified in place (each block is sorted, and the whole list is
89
+ sorted). Your ``blocks`` object will become the
90
+ :class:`IncidenceStructure` instance's internal data.
91
+
92
+ EXAMPLES:
93
+
94
+ An incidence structure can be constructed by giving the number of points and
95
+ the list of blocks::
96
+
97
+ sage: IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])
98
+ Incidence structure with 7 points and 7 blocks
99
+
100
+ Only providing the set of blocks is sufficient. In this case, the ground set
101
+ is defined as the union of the blocks::
102
+
103
+ sage: IncidenceStructure([[1,2,3],[2,3,4]])
104
+ Incidence structure with 4 points and 2 blocks
105
+
106
+ Or by its adjacency matrix (a `\{0,1\}`-matrix in which rows are indexed by
107
+ points and columns by blocks)::
108
+
109
+ sage: m = matrix([[0,1,0],[0,0,1],[1,0,1],[1,1,1]]) # needs sage.modules
110
+ sage: IncidenceStructure(m) # needs sage.modules
111
+ Incidence structure with 4 points and 3 blocks
112
+
113
+ The points can be any (hashable) object::
114
+
115
+ sage: V = [(0,'a'),(0,'b'),(1,'a'),(1,'b')]
116
+ sage: B = [(V[0],V[1],V[2]), (V[1],V[2]), (V[0],V[2])]
117
+ sage: I = IncidenceStructure(V, B)
118
+ sage: I.ground_set()
119
+ [(0, 'a'), (0, 'b'), (1, 'a'), (1, 'b')]
120
+ sage: I.blocks()
121
+ [[(0, 'a'), (0, 'b'), (1, 'a')], [(0, 'a'), (1, 'a')], [(0, 'b'), (1, 'a')]]
122
+
123
+ The order of the points and blocks does not matter as they are sorted on
124
+ input (see :issue:`11333`)::
125
+
126
+ sage: A = IncidenceStructure([0,1,2], [[0],[0,2]])
127
+ sage: B = IncidenceStructure([1,0,2], [[0],[2,0]])
128
+ sage: B == A
129
+ True
130
+
131
+ sage: C = BlockDesign(2, [[0], [1,0]])
132
+ sage: D = BlockDesign(2, [[0,1], [0]])
133
+ sage: C == D
134
+ True
135
+
136
+ If you care for speed, you can set ``copy`` to ``False``, but in that
137
+ case, your input must be a list of lists and the ground set must be `{0,
138
+ ..., v-1}`::
139
+
140
+ sage: blocks = [[0,1],[2,0],[1,2]] # a list of lists of integers
141
+ sage: I = IncidenceStructure(3, blocks, copy=False)
142
+ sage: I._blocks is blocks
143
+ True
144
+ """
145
+ def __init__(self, points=None, blocks=None, incidence_matrix=None,
146
+ name=None, check=True, copy=True):
147
+ r"""
148
+ TESTS::
149
+
150
+ sage: IncidenceStructure(3, [[4]])
151
+ Traceback (most recent call last):
152
+ ...
153
+ ValueError: Block [4] is not contained in the point set
154
+
155
+ sage: IncidenceStructure(3, [[0,1],[0,2]], check=True)
156
+ Incidence structure with 3 points and 2 blocks
157
+
158
+ sage: IncidenceStructure(2, [[0,1,2,3,4,5]], check=False)
159
+ Incidence structure with 2 points and 1 blocks
160
+
161
+ We avoid to convert to integers when the points are not (but compare
162
+ equal to integers because of coercion)::
163
+
164
+ sage: # needs sage.rings.finite_rings
165
+ sage: V = GF(5)
166
+ sage: e0,e1,e2,e3,e4 = V
167
+ sage: [e0,e1,e2,e3,e4] == list(range(5)) # coercion makes them equal
168
+ True
169
+ sage: blocks = [[e0,e1,e2],[e0,e1],[e2,e4]]
170
+ sage: I = IncidenceStructure(V, blocks)
171
+ sage: type(I.ground_set()[0])
172
+ <class 'sage.rings.finite_rings.integer_mod.IntegerMod_int'>
173
+ sage: type(I.blocks()[0][0])
174
+ <class 'sage.rings.finite_rings.integer_mod.IntegerMod_int'>
175
+
176
+ TESTS::
177
+
178
+ sage: IncidenceStructure([])
179
+ Incidence structure with 0 points and 0 blocks
180
+ """
181
+ from sage.structure.element import Matrix
182
+
183
+ # Reformatting input
184
+ if isinstance(points, Matrix):
185
+ assert incidence_matrix is None, "'incidence_matrix' cannot be defined when 'points' is a matrix"
186
+ assert blocks is None, "'blocks' cannot be defined when 'points' is a matrix"
187
+ incidence_matrix = points
188
+ points = blocks = None
189
+ elif points is not None and blocks is None:
190
+ blocks = points
191
+ points = set().union(*blocks)
192
+ if points:
193
+ assert incidence_matrix is None, "'incidence_matrix' cannot be defined when 'points' is defined"
194
+
195
+ if incidence_matrix:
196
+ from sage.matrix.constructor import matrix
197
+ M = matrix(incidence_matrix)
198
+ v = M.nrows()
199
+ self._points = list(range(v))
200
+ self._point_to_index = None
201
+ self._blocks = sorted(M.nonzero_positions_in_column(i) for i in range(M.ncols()))
202
+
203
+ else:
204
+ if isinstance(points, (int, Integer)):
205
+ self._points = list(range(points))
206
+ self._point_to_index = None
207
+ else:
208
+ self._points = list(points)
209
+ if self._points == list(range(len(points))) and all(isinstance(x, (int, Integer)) for x in self._points):
210
+ self._point_to_index = None
211
+ else:
212
+ self._point_to_index = {e: i for i, e in enumerate(self._points)}
213
+
214
+ if check:
215
+ for block in blocks:
216
+ if any(x not in self._points for x in block):
217
+ raise ValueError("Block {} is not contained in the point set".format(block))
218
+ if len(block) != len(set(block)):
219
+ raise ValueError("Repeated element in block {}".format(block))
220
+
221
+ if self._point_to_index:
222
+ # translate everything to integers between 0 and v-1
223
+ blocks = [sorted(self._point_to_index[e] for e in block) for block in blocks]
224
+ elif copy:
225
+ # create a new list made of sorted blocks
226
+ blocks = [sorted(block) for block in blocks]
227
+ else:
228
+ # sort the data but avoid copying it
229
+ for b in blocks:
230
+ b.sort()
231
+
232
+ blocks.sort()
233
+ self._blocks = blocks
234
+
235
+ self._name = str(name) if name is not None else 'IncidenceStructure'
236
+ self._classes = None
237
+ self._canonical_label = None
238
+
239
+ def __iter__(self):
240
+ """
241
+ Iterator over the blocks.
242
+
243
+ EXAMPLES::
244
+
245
+ sage: sts = designs.steiner_triple_system(9)
246
+ sage: list(sts)
247
+ [[0, 1, 5], [0, 2, 4], [0, 3, 6], [0, 7, 8], [1, 2, 3], [1, 4, 7],
248
+ [1, 6, 8], [2, 5, 8], [2, 6, 7], [3, 4, 8], [3, 5, 7], [4, 5, 6]]
249
+
250
+ sage: b = IncidenceStructure('ab', ['a','ab'])
251
+ sage: it = iter(b)
252
+ sage: next(it)
253
+ ['a']
254
+ sage: next(it)
255
+ ['a', 'b']
256
+ """
257
+ if self._point_to_index is None:
258
+ for b in self._blocks:
259
+ yield b[:]
260
+ else:
261
+ for b in self._blocks:
262
+ yield [self._points[i] for i in b]
263
+
264
+ def __repr__(self):
265
+ """
266
+ A print method.
267
+
268
+ EXAMPLES::
269
+
270
+ sage: BD = IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])
271
+ sage: BD
272
+ Incidence structure with 7 points and 7 blocks
273
+ """
274
+ return 'Incidence structure with {} points and {} blocks'.format(
275
+ self.num_points(), self.num_blocks())
276
+
277
+ __str__ = __repr__
278
+
279
+ def __eq__(self, other):
280
+ """
281
+ Test whether the two incidence structures are equal.
282
+
283
+ TESTS::
284
+
285
+ sage: blocks = [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]
286
+ sage: BD1 = IncidenceStructure(7, blocks)
287
+ sage: M = BD1.incidence_matrix() # needs sage.modules
288
+ sage: BD2 = IncidenceStructure(incidence_matrix=M) # needs sage.modules
289
+ sage: BD1 == BD2 # needs sage.modules
290
+ True
291
+
292
+ sage: e1 = frozenset([0,1])
293
+ sage: e2 = frozenset([2])
294
+ sage: sorted([e1,e2]) == [e1,e2]
295
+ True
296
+ sage: sorted([e2,e1]) == [e2,e1]
297
+ True
298
+ sage: I1 = IncidenceStructure([e1,e2], [[e1],[e1,e2]])
299
+ sage: I2 = IncidenceStructure([e1,e2], [[e2,e1],[e1]])
300
+ sage: I3 = IncidenceStructure([e2,e1], [[e1,e2],[e1]])
301
+ sage: I1 == I2 and I2 == I1 and I1 == I3 and I3 == I1 and I2 == I3 and I3 == I2
302
+ True
303
+ """
304
+ # We are extra careful in this method since we cannot assume that a
305
+ # total order is defined on the point set.
306
+ if not isinstance(other, IncidenceStructure):
307
+ return False
308
+
309
+ if self._points == other._points:
310
+ return self._blocks == other._blocks
311
+
312
+ if (self.num_points() != other.num_points() or
313
+ self.num_blocks() != other.num_blocks()):
314
+ return False
315
+
316
+ p_to_i = self._point_to_index if self._point_to_index else list(range(self.num_points()))
317
+
318
+ if any(p not in p_to_i for p in other.ground_set()):
319
+ return False
320
+
321
+ other_blocks = sorted(sorted(p_to_i[p] for p in b) for b in other.blocks())
322
+ return self._blocks == other_blocks
323
+
324
+ def __ne__(self, other):
325
+ r"""
326
+ Difference test.
327
+
328
+ EXAMPLES::
329
+
330
+ sage: BD1 = IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])
331
+ sage: M = BD1.incidence_matrix() # needs sage.modules
332
+ sage: BD2 = IncidenceStructure(incidence_matrix=M) # needs sage.modules
333
+ sage: BD1 != BD2 # needs sage.modules
334
+ False
335
+ """
336
+ return not self == other
337
+
338
+ def __contains__(self, block):
339
+ r"""
340
+ Test if a block belongs to the incidence structure.
341
+
342
+ INPUT:
343
+
344
+ - ``block`` -- a block
345
+
346
+ EXAMPLES::
347
+
348
+ sage: [1,2,3,4] in IncidenceStructure([[1,2,3,4]])
349
+ True
350
+ sage: [1,2,4,3] in IncidenceStructure([[1,2,3,4]])
351
+ True
352
+ sage: [1,2,"3",4] in IncidenceStructure([[1,2,3,4]])
353
+ False
354
+ sage: [1,2,"3",4] in IncidenceStructure([[1,2,"3",4]])
355
+ True
356
+
357
+ More complicated examples::
358
+
359
+ sage: str="I had a dream of a time when a 3-lines patch does not kill one hour"
360
+ sage: sets = Subsets(str.split(), 4)
361
+ sage: IS = IncidenceStructure(sets) # a complete 4-uniform hypergraph
362
+ sage: ["I", "dream", "of", "one"] in IS
363
+ True
364
+ sage: ["does", "patch", "kill", "dream"] in IS
365
+ True
366
+ sage: ["Am", "I", "finally", "done ?"] in IS
367
+ False
368
+ sage: IS = designs.ProjectiveGeometryDesign(3, 1, GF(2), # needs sage.combinat sage.modules
369
+ ....: point_coordinates=False)
370
+ sage: [3,8,7] in IS # needs sage.combinat sage.modules
371
+ True
372
+ sage: [3,8,9] in IS # needs sage.combinat sage.modules
373
+ False
374
+ """
375
+ try:
376
+ iter(block)
377
+ except TypeError:
378
+ return False
379
+
380
+ # Relabel to 0,...,n-1 if necessary
381
+ if self._point_to_index is not None:
382
+ try:
383
+ block = [self._point_to_index[x] for x in block]
384
+ except KeyError:
385
+ return False
386
+
387
+ return sorted(block) in self._blocks
388
+
389
+ def canonical_label(self):
390
+ r"""
391
+ Return a canonical label for the incidence structure.
392
+
393
+ A canonical label is relabeling of the points into integers
394
+ `\{0,...,n-1\}` such that isomorphic incidence structures are
395
+ relabelled to equal objects.
396
+
397
+ EXAMPLES::
398
+
399
+ sage: # needs sage.schemes
400
+ sage: fano1 = designs.balanced_incomplete_block_design(7,3)
401
+ sage: fano2 = designs.projective_plane(2)
402
+ sage: fano1 == fano2
403
+ False
404
+ sage: fano1.relabel(fano1.canonical_label())
405
+ sage: fano2.relabel(fano2.canonical_label())
406
+ sage: fano1 == fano2
407
+ True
408
+ """
409
+ if self._canonical_label is None:
410
+ from sage.graphs.graph import Graph
411
+ g = Graph()
412
+ n = self.num_points()
413
+ g.add_edges((i+n, x) for i, b in enumerate(self._blocks) for x in b)
414
+ canonical_label = g.canonical_label([list(range(n)), list(range(n, n+self.num_blocks()))], certificate=True)[1]
415
+ canonical_label = [canonical_label[x] for x in range(n)]
416
+ self._canonical_label = canonical_label
417
+
418
+ return dict(zip(self._points, self._canonical_label))
419
+
420
+ def is_isomorphic(self, other, certificate=False):
421
+ r"""
422
+ Return whether the two incidence structures are isomorphic.
423
+
424
+ INPUT:
425
+
426
+ - ``other`` -- an incidence structure
427
+
428
+ - ``certificate`` -- boolean (default: ``False``); whether to return an
429
+ isomorphism from ``self`` to ``other`` instead of a boolean answer
430
+
431
+ EXAMPLES::
432
+
433
+ sage: # needs sage.schemes
434
+ sage: fano1 = designs.balanced_incomplete_block_design(7,3)
435
+ sage: fano2 = designs.projective_plane(2)
436
+ sage: fano1.is_isomorphic(fano2)
437
+ True
438
+ sage: fano1.is_isomorphic(fano2,certificate=True)
439
+ {0: 0, 1: 1, 2: 2, 3: 6, 4: 4, 5: 3, 6: 5}
440
+
441
+ TESTS::
442
+
443
+ sage: # needs sage.symbolic
444
+ sage: IS = IncidenceStructure([["A",5,pi],["A",5,"Wouhou"],
445
+ ....: ["A","Wouhou",(9,9)],[pi,12]])
446
+ sage: IS2 = IS.copy()
447
+ sage: IS2.relabel(IS2.canonical_label())
448
+ sage: IS.is_isomorphic(IS2)
449
+ True
450
+ sage: canon = IS.is_isomorphic(IS2, certificate=True)
451
+ sage: IS.relabel(canon)
452
+ sage: IS==IS2
453
+ True
454
+
455
+ sage: IS2 = IncidenceStructure([[1,2]])
456
+ sage: IS2.is_isomorphic(IS) # needs sage.symbolic
457
+ False
458
+ sage: IS2.is_isomorphic(IS, certificate=True) # needs sage.symbolic
459
+ {}
460
+
461
+ Checking whether two :class:`IncidenceStructure` are isomorphic
462
+ incidentally computes their canonical label (if necessary). Thus,
463
+ subsequent calls to :meth:`is_isomorphic` will be faster::
464
+
465
+ sage: # needs sage.schemes
466
+ sage: IS1 = designs.projective_plane(3)
467
+ sage: IS2 = IS1.relabel(Permutations(IS1.ground_set()).random_element(),
468
+ ....: inplace=False)
469
+ sage: IS2 = IncidenceStructure(IS2.blocks())
470
+ sage: IS1._canonical_label is None and IS2._canonical_label is None
471
+ True
472
+ sage: IS1.is_isomorphic(IS2)
473
+ True
474
+ sage: IS1._canonical_label is None or IS2._canonical_label is None
475
+ False
476
+ """
477
+ if (self.num_points() != other.num_points() or
478
+ self.num_blocks() != other.num_blocks() or
479
+ sorted(self.block_sizes()) != sorted(other.block_sizes())):
480
+ return {} if certificate else False
481
+
482
+ A_canon = self.canonical_label()
483
+ B_canon = other.canonical_label()
484
+
485
+ A = self.relabel(A_canon, inplace=False)
486
+ B = other.relabel(B_canon, inplace=False)
487
+
488
+ if A == B:
489
+ if certificate:
490
+ B_canon_rev = {y: x for x, y in B_canon.items()}
491
+ return {x: B_canon_rev[xint] for x, xint in A_canon.items()}
492
+ else:
493
+ return True
494
+ else:
495
+ return {} if certificate else False
496
+
497
+ def isomorphic_substructures_iterator(self, H2, induced=False):
498
+ r"""
499
+ Iterate over all copies of ``H2`` contained in ``self``.
500
+
501
+ A hypergraph `H_1` contains an isomorphic copy of a hypergraph `H_2` if
502
+ there exists an injection `f:V(H_2)\mapsto V(H_1)` such that for any set
503
+ `S_2\in E(H_2)` the set `S_1=f(S2)` belongs to `E(H_1)`.
504
+
505
+ It is an *induced* copy if no other set of `E(H_1)` is contained in
506
+ `f(V(H_2))`, i.e. `|E(H_2)|=\{S:S\in E(H_1)\text{ and }f(V(H_2))\}`.
507
+
508
+ This function lists all such injections. In particular, the number of
509
+ copies of `H` in itself is equal to *the size of its automorphism
510
+ group*.
511
+
512
+ See :mod:`~sage.combinat.designs.subhypergraph_search` for more information.
513
+
514
+ INPUT:
515
+
516
+ - ``H2`` -- an :class:`IncidenceStructure` object
517
+
518
+ - ``induced`` -- boolean (default: ``False``); whether to require the copies to be
519
+ induced
520
+
521
+ EXAMPLES:
522
+
523
+ How many distinct `C_5` in Petersen's graph ? ::
524
+
525
+ sage: P = graphs.PetersenGraph()
526
+ sage: C = graphs.CycleGraph(5)
527
+ sage: IP = IncidenceStructure(P.edges(sort=True, labels=False))
528
+ sage: IC = IncidenceStructure(C.edges(sort=True, labels=False))
529
+ sage: sum(1 for _ in IP.isomorphic_substructures_iterator(IC))
530
+ 120
531
+
532
+ As the automorphism group of `C_5` has size 10, the number of distinct
533
+ unlabelled copies is 12. Let us check that all functions returned
534
+ correspond to an actual `C_5` subgraph::
535
+
536
+ sage: for f in IP.isomorphic_substructures_iterator(IC):
537
+ ....: assert all(P.has_edge(f[x],f[y]) for x,y in C.edges(sort=True, labels=False))
538
+
539
+ The number of induced copies, in this case, is the same::
540
+
541
+ sage: sum(1 for _ in IP.isomorphic_substructures_iterator(IC,induced=True))
542
+ 120
543
+
544
+ They begin to differ if we make one vertex universal::
545
+
546
+ sage: P.add_edges([(0,x) for x in P], loops=False)
547
+ sage: IP = IncidenceStructure(P.edges(sort=True, labels=False))
548
+ sage: IC = IncidenceStructure(C.edges(sort=True, labels=False))
549
+ sage: sum(1 for _ in IP.isomorphic_substructures_iterator(IC))
550
+ 420
551
+ sage: sum(1 for _ in IP.isomorphic_substructures_iterator(IC,induced=True))
552
+ 60
553
+
554
+ The number of copies of `H` in itself is the size of its automorphism
555
+ group::
556
+
557
+ sage: H = designs.projective_plane(3) # needs sage.schemes
558
+ sage: sum(1 for _ in H.isomorphic_substructures_iterator(H)) # needs sage.schemes
559
+ 5616
560
+ sage: H.automorphism_group().cardinality() # needs sage.groups sage.schemes
561
+ 5616
562
+ """
563
+ from sage.combinat.designs.subhypergraph_search import SubHypergraphSearch
564
+ return SubHypergraphSearch(self, H2, induced=induced)
565
+
566
+ def copy(self):
567
+ r"""
568
+ Return a copy of the incidence structure.
569
+
570
+ EXAMPLES::
571
+
572
+ sage: IS = IncidenceStructure([[1,2,3,"e"]], name='Test')
573
+ sage: IS
574
+ Incidence structure with 4 points and 1 blocks
575
+ sage: copy(IS)
576
+ Incidence structure with 4 points and 1 blocks
577
+ sage: [1, 2, 3, 'e'] in copy(IS)
578
+ True
579
+ sage: copy(IS)._name
580
+ 'Test'
581
+ """
582
+ IS = IncidenceStructure(self._blocks,
583
+ name=self._name,
584
+ check=False)
585
+ IS.relabel(dict(zip(range(self.num_points()), self._points)))
586
+ IS._canonical_label = None if self._canonical_label is None else self._canonical_label[:]
587
+
588
+ return IS
589
+
590
+ __copy__ = copy
591
+
592
+ def induced_substructure(self, points):
593
+ r"""
594
+ Return the substructure induced by a set of points.
595
+
596
+ The substructure induced in `\mathcal H` by a set `X\subseteq V(\mathcal
597
+ H)` of points is the incidence structure `\mathcal H_X` defined on `X`
598
+ whose sets are all `S\in \mathcal H` such that `S\subseteq X`.
599
+
600
+ INPUT:
601
+
602
+ - ``points`` -- set of points
603
+
604
+ .. NOTE::
605
+
606
+ This method goes over all sets of ``self`` before building a new
607
+ :class:`IncidenceStructure` (which involves some relabelling and
608
+ sorting). It probably should not be called in a performance-critical
609
+ code.
610
+
611
+ EXAMPLES:
612
+
613
+ A Fano plane with one point removed::
614
+
615
+ sage: F = designs.steiner_triple_system(7)
616
+ sage: F.induced_substructure([0..5])
617
+ Incidence structure with 6 points and 4 blocks
618
+
619
+ TESTS::
620
+
621
+ sage: F.induced_substructure([0..50])
622
+ Traceback (most recent call last):
623
+ ...
624
+ ValueError: 7 is not a point of the incidence structure
625
+ sage: F.relabel(dict(enumerate("abcdefg")))
626
+ sage: F.induced_substructure("abc")
627
+ Incidence structure with 3 points and ...
628
+ sage: F.induced_substructure("Y")
629
+ Traceback (most recent call last):
630
+ ...
631
+ ValueError: 'Y' is not a point of the incidence structure
632
+ """
633
+ # Checking the input
634
+ if self._point_to_index is None:
635
+ n = self.num_points()
636
+ for x in points:
637
+ x = int(x)
638
+ if x < 0 or x >= n:
639
+ raise ValueError("{} is not a point of the incidence structure".format(x))
640
+ int_points = points
641
+ else:
642
+ try:
643
+ int_points = [self._point_to_index[x] for x in points]
644
+ except KeyError as bad_pt:
645
+ raise ValueError("{} is not a point of the incidence structure".format(bad_pt))
646
+
647
+ int_points = set(int_points)
648
+ return IncidenceStructure(points,
649
+ [[self._points[x] for x in S]
650
+ for S in self._blocks
651
+ if int_points.issuperset(S)])
652
+
653
+ def trace(self, points, min_size=1, multiset=True):
654
+ r"""
655
+ Return the trace of a set of points.
656
+
657
+ Given an hypergraph `\mathcal H`, the *trace* of a set `X` of points in
658
+ `\mathcal H` is the hypergraph whose blocks are all non-empty `S \cap X`
659
+ where `S \in \mathcal H`.
660
+
661
+ INPUT:
662
+
663
+ - ``points`` -- set of points
664
+
665
+ - ``min_size`` -- integer (default: 1); minimum size of the sets to
666
+ keep. By default all empty sets are discarded, i.e. ``min_size=1``
667
+
668
+ - ``multiset`` -- boolean (default: ``True``); whether to keep multiple
669
+ copies of the same set
670
+
671
+ .. NOTE::
672
+
673
+ This method goes over all sets of ``self`` before building a new
674
+ :class:`IncidenceStructure` (which involves some relabelling and
675
+ sorting). It probably should not be called in a performance-critical
676
+ code.
677
+
678
+ EXAMPLES:
679
+
680
+ A Baer subplane of order 2 (i.e. a Fano plane) in a projective plane of order 4::
681
+
682
+ sage: # needs sage.schemes
683
+ sage: P4 = designs.projective_plane(4)
684
+ sage: F = designs.projective_plane(2)
685
+ sage: for x in Subsets(P4.ground_set(),7):
686
+ ....: if P4.trace(x,min_size=2).is_isomorphic(F):
687
+ ....: break
688
+ sage: subplane = P4.trace(x,min_size=2); subplane
689
+ Incidence structure with 7 points and 7 blocks
690
+ sage: subplane.is_isomorphic(F)
691
+ True
692
+
693
+ TESTS::
694
+
695
+ sage: # needs sage.schemes
696
+ sage: F.trace([0..50])
697
+ Traceback (most recent call last):
698
+ ...
699
+ ValueError: 7 is not a point of the incidence structure
700
+ sage: F.relabel(dict(enumerate("abcdefg")))
701
+ sage: F.trace("abc")
702
+ Incidence structure with 3 points and ...
703
+ sage: F.trace("Y")
704
+ Traceback (most recent call last):
705
+ ...
706
+ ValueError: 'Y' is not a point of the incidence structure
707
+ """
708
+ # Checking the input
709
+ if self._point_to_index is None:
710
+ n = self.num_points()
711
+ int_points = frozenset(int(x) for x in points)
712
+ for x in int_points:
713
+ if x < 0 or x >= n:
714
+ raise ValueError("{} is not a point of the incidence structure".format(x))
715
+ else:
716
+ try:
717
+ int_points = frozenset(self._point_to_index[x] for x in points)
718
+ except KeyError as bad_pt:
719
+ raise ValueError("{} is not a point of the incidence structure".format(bad_pt))
720
+
721
+ blocks = [int_points.intersection(S) for S in self._blocks]
722
+ if min_size:
723
+ blocks = [S for S in blocks if len(S) >= min_size]
724
+ if not multiset:
725
+ blocks = set(blocks)
726
+ IS = IncidenceStructure(blocks)
727
+ IS.relabel({i: self._points[i] for i in int_points})
728
+ return IS
729
+
730
+ def ground_set(self):
731
+ r"""
732
+ Return the ground set (i.e the list of points).
733
+
734
+ EXAMPLES::
735
+
736
+ sage: IncidenceStructure(3, [[0,1],[0,2]]).ground_set()
737
+ [0, 1, 2]
738
+ """
739
+ return self._points[:]
740
+
741
+ def num_points(self):
742
+ r"""
743
+ Return the size of the ground set.
744
+
745
+ EXAMPLES::
746
+
747
+ sage: designs.DesarguesianProjectivePlaneDesign(2).num_points()
748
+ 7
749
+ sage: B = IncidenceStructure(4, [[0,1],[0,2],[0,3],[1,2], [1,2,3]])
750
+ sage: B.num_points()
751
+ 4
752
+ """
753
+ return len(self._points)
754
+
755
+ def num_blocks(self):
756
+ r"""
757
+ Return the number of blocks.
758
+
759
+ EXAMPLES::
760
+
761
+ sage: designs.DesarguesianProjectivePlaneDesign(2).num_blocks()
762
+ 7
763
+ sage: B = IncidenceStructure(4, [[0,1],[0,2],[0,3],[1,2], [1,2,3]])
764
+ sage: B.num_blocks()
765
+ 5
766
+ """
767
+ return len(self._blocks)
768
+
769
+ def blocks(self):
770
+ """
771
+ Return the list of blocks.
772
+
773
+ EXAMPLES::
774
+
775
+ sage: BD = IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])
776
+ sage: BD.blocks()
777
+ [[0, 1, 2], [0, 3, 4], [0, 5, 6], [1, 3, 5], [1, 4, 6], [2, 3, 6], [2, 4, 5]]
778
+ """
779
+ if self._point_to_index is None:
780
+ return [b[:] for b in self._blocks]
781
+ else:
782
+ return [[self._points[i] for i in b] for b in self._blocks]
783
+
784
+ def block_sizes(self):
785
+ r"""
786
+ Return the set of block sizes.
787
+
788
+ EXAMPLES::
789
+
790
+ sage: BD = IncidenceStructure(8, [[0,1,3],[1,4,5,6],[1,2],[5,6,7]])
791
+ sage: BD.block_sizes()
792
+ [3, 2, 4, 3]
793
+ sage: BD = IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])
794
+ sage: BD.block_sizes()
795
+ [3, 3, 3, 3, 3, 3, 3]
796
+ """
797
+ return [len(b) for b in self._blocks]
798
+
799
+ def degree(self, p=None, subset=False):
800
+ r"""
801
+ Return the degree of a point ``p`` (or a set of points).
802
+
803
+ The degree of a point (or set of points) is the number of blocks that
804
+ contain it.
805
+
806
+ INPUT:
807
+
808
+ - ``p`` -- a point (or a set of points) of the incidence structure
809
+
810
+ - ``subset`` -- boolean (default: ``False``); whether to interpret the
811
+ argument as a set of point or as a point (default)
812
+
813
+ EXAMPLES::
814
+
815
+ sage: designs.steiner_triple_system(9).degree(3)
816
+ 4
817
+ sage: designs.steiner_triple_system(9).degree({1,2},subset=True)
818
+ 1
819
+
820
+ TESTS::
821
+
822
+ sage: designs.steiner_triple_system(9).degree(subset=True)
823
+ Traceback (most recent call last):
824
+ ...
825
+ ValueError: subset must be False when p is None
826
+ """
827
+ if p is None:
828
+ if subset is True:
829
+ raise ValueError("subset must be False when p is None")
830
+
831
+ # degree of a point
832
+ if not subset:
833
+ if self._point_to_index:
834
+ p = self._point_to_index.get(p, -1)
835
+ else:
836
+ p = p if (p >= 0 and p < len(self._points)) else -1
837
+ return sum((p in b) for b in self._blocks) if p != -1 else 0
838
+
839
+ # degree of a set
840
+ else:
841
+ if self._point_to_index:
842
+ p = set(self._point_to_index.get(x, -1) for x in p)
843
+ else:
844
+ p = set(p) if all(x >= 0 and x < len(self._points) for x in p) else set([-1])
845
+
846
+ return sum(p.issubset(b) for b in self._blocks) if -1 not in p else 0
847
+
848
+ def degrees(self, size=None):
849
+ r"""
850
+ Return the degree of all sets of given size, or the degree of all points.
851
+
852
+ The degree of a point (or set of point) is the number of blocks that
853
+ contain it.
854
+
855
+ INPUT:
856
+
857
+ - ``size`` -- integer; return the degree of all subsets of points of
858
+ cardinality ``size``. When ``size=None``, the function outputs the
859
+ degree of all points.
860
+
861
+ .. NOTE::
862
+
863
+ When ``size=None`` the output is indexed by the points. When
864
+ ``size=1`` it is indexed by tuples of size 1. This is the same
865
+ information, stored slightly differently.
866
+
867
+ OUTPUT: a dictionary whose values are degrees and keys are either:
868
+
869
+ - the points of the incidence structure if ``size=None`` (default)
870
+
871
+ - the subsets of size ``size`` of the points stored as tuples
872
+
873
+ EXAMPLES::
874
+
875
+ sage: IncidenceStructure([[1,2,3],[1,4]]).degrees(2)
876
+ {(1, 2): 1, (1, 3): 1, (1, 4): 1, (2, 3): 1, (2, 4): 0, (3, 4): 0}
877
+
878
+ In a Steiner triple system, all pairs have degree 1::
879
+
880
+ sage: S13 = designs.steiner_triple_system(13)
881
+ sage: all(v == 1 for v in S13.degrees(2).values())
882
+ True
883
+ """
884
+ if size is None:
885
+ d = [0]*self.num_points()
886
+ for b in self._blocks:
887
+ for x in b:
888
+ d[x] += 1
889
+ return {p: d[i] for i, p in enumerate(self._points)}
890
+ else:
891
+ from itertools import combinations
892
+ d = {t: 0 for t in combinations(range(self.num_points()), size)}
893
+ for b in self._blocks:
894
+ for s in combinations(b, size):
895
+ d[s] += 1
896
+ if self._point_to_index:
897
+ return {tuple([self._points[x] for x in s]): v for s, v in d.items()}
898
+ else:
899
+ return d
900
+
901
+ def rank(self):
902
+ r"""
903
+ Return the rank of the hypergraph (the maximum size of a block).
904
+
905
+ EXAMPLES::
906
+
907
+ sage: h = Hypergraph(8, [[0,1,3],[1,4,5,6],[1,2]])
908
+ sage: h.rank()
909
+ 4
910
+ """
911
+ return max(len(b) for b in self._blocks)
912
+
913
+ def is_regular(self, r=None) -> bool | int:
914
+ r"""
915
+ Test whether the incidence structure is `r`-regular.
916
+
917
+ An incidence structure is said to be `r`-regular if all its points are
918
+ incident with exactly `r` blocks.
919
+
920
+ INPUT:
921
+
922
+ - ``r`` -- integer
923
+
924
+ OUTPUT:
925
+
926
+ If ``r`` is defined, a boolean is returned. If ``r`` is set to ``None``
927
+ (default), the method returns either ``False`` or the integer ``r`` such
928
+ that the incidence structure is `r`-regular.
929
+
930
+ .. WARNING::
931
+
932
+ In case of `0`-regular incidence structure, beware that ``if not
933
+ H.is_regular()`` is a satisfied condition.
934
+
935
+ EXAMPLES::
936
+
937
+ sage: designs.balanced_incomplete_block_design(7,3).is_regular() # needs sage.schemes
938
+ 3
939
+ sage: designs.balanced_incomplete_block_design(7,3).is_regular(r=3) # needs sage.schemes
940
+ True
941
+ sage: designs.balanced_incomplete_block_design(7,3).is_regular(r=4) # needs sage.schemes
942
+ False
943
+
944
+ TESTS::
945
+
946
+ sage: IncidenceStructure([]).is_regular()
947
+ Traceback (most recent call last):
948
+ ...
949
+ ValueError: This incidence structure has no points.
950
+ """
951
+ if self.num_points() == 0:
952
+ raise ValueError("This incidence structure has no points.")
953
+ count = [0] * self.num_points()
954
+ for b in self._blocks:
955
+ for x in b:
956
+ count[x] += 1
957
+ scount = set(count)
958
+ if len(scount) != 1:
959
+ return False
960
+ if r is None:
961
+ return scount.pop()
962
+ return scount.pop() == r
963
+
964
+ def is_uniform(self, k=None) -> bool | int:
965
+ r"""
966
+ Test whether the incidence structure is `k`-uniform
967
+
968
+ An incidence structure is said to be `k`-uniform if all its blocks have
969
+ size `k`.
970
+
971
+ INPUT:
972
+
973
+ - ``k`` -- integer
974
+
975
+ OUTPUT:
976
+
977
+ If ``k`` is defined, a boolean is returned. If ``k`` is set to ``None``
978
+ (default), the method returns either ``False`` or the integer ``k`` such
979
+ that the incidence structure is `k`-uniform.
980
+
981
+ .. WARNING::
982
+
983
+ In case of `0`-uniform incidence structure, beware that ``if not
984
+ H.is_uniform()`` is a satisfied condition.
985
+
986
+ EXAMPLES::
987
+
988
+ sage: designs.balanced_incomplete_block_design(7,3).is_uniform() # needs sage.schemes
989
+ 3
990
+ sage: designs.balanced_incomplete_block_design(7,3).is_uniform(k=3) # needs sage.schemes
991
+ True
992
+ sage: designs.balanced_incomplete_block_design(7,3).is_uniform(k=4) # needs sage.schemes
993
+ False
994
+
995
+ TESTS::
996
+
997
+ sage: IncidenceStructure([]).is_uniform()
998
+ Traceback (most recent call last):
999
+ ...
1000
+ ValueError: This incidence structure has no blocks.
1001
+ """
1002
+ if self.num_blocks() == 0:
1003
+ raise ValueError("This incidence structure has no blocks.")
1004
+ sizes = set(self.block_sizes())
1005
+ if len(sizes) != 1:
1006
+ return False
1007
+ if k is None:
1008
+ return sizes.pop()
1009
+ return sizes.pop() == k
1010
+
1011
+ def is_connected(self) -> bool:
1012
+ r"""
1013
+ Test whether the design is connected.
1014
+
1015
+ EXAMPLES::
1016
+
1017
+ sage: IncidenceStructure(3, [[0,1],[0,2]]).is_connected()
1018
+ True
1019
+ sage: IncidenceStructure(4, [[0,1],[2,3]]).is_connected()
1020
+ False
1021
+ """
1022
+ from sage.sets.disjoint_set import DisjointSet
1023
+ D = DisjointSet(self.num_points())
1024
+ for B in self._blocks:
1025
+ x = B[0]
1026
+ for i in range(1, len(B)):
1027
+ D.union(x, B[i])
1028
+ return D.number_of_subsets() == 1
1029
+
1030
+ def is_simple(self) -> bool:
1031
+ r"""
1032
+ Test whether this design is simple (i.e. no repeated block).
1033
+
1034
+ EXAMPLES::
1035
+
1036
+ sage: IncidenceStructure(3, [[0,1],[1,2],[0,2]]).is_simple()
1037
+ True
1038
+ sage: IncidenceStructure(3, [[0],[0]]).is_simple()
1039
+ False
1040
+
1041
+ sage: V = [(0,'a'),(0,'b'),(1,'a'),(1,'b')]
1042
+ sage: B = [[V[0],V[1]], [V[1],V[2]]]
1043
+ sage: I = IncidenceStructure(V, B)
1044
+ sage: I.is_simple()
1045
+ True
1046
+ sage: I2 = IncidenceStructure(V, B*2)
1047
+ sage: I2.is_simple()
1048
+ False
1049
+ """
1050
+ B = self._blocks
1051
+ return all(B[i] != B[i + 1] for i in range(len(B) - 1))
1052
+
1053
+ def _gap_(self):
1054
+ """
1055
+ Return the GAP string describing the design.
1056
+
1057
+ EXAMPLES::
1058
+
1059
+ sage: BD = IncidenceStructure(7,[[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]])
1060
+ sage: BD._gap_()
1061
+ 'BlockDesign(7,[[1, 2, 3], [1, 4, 5], [1, 6, 7], [2, 4, 6], [2, 5, 7], [3, 4, 7], [3, 5, 6]])'
1062
+ """
1063
+ v = self.num_points()
1064
+ gB = [[x + 1 for x in b] for b in self._blocks]
1065
+ return "BlockDesign({},{})".format(v, gB)
1066
+
1067
+ def _libgap_(self):
1068
+ """
1069
+ Return the design as a GAP record.
1070
+
1071
+ EXAMPLES::
1072
+
1073
+ sage: D = IncidenceStructure(4, [[0,2],[1,2,3],[2,3]])
1074
+ sage: D._libgap_() # optional - gap_package_design
1075
+ rec( blocks := [ [ 1, 3 ], [ 2, 3, 4 ], [ 3, 4 ] ],
1076
+ isBlockDesign := true, v := 4 )
1077
+ """
1078
+ libgap.load_package("design")
1079
+ v = self.num_points()
1080
+ gB = [[x + 1 for x in b] for b in self._blocks]
1081
+ return libgap.BlockDesign(v, gB)
1082
+
1083
+ def intersection_graph(self, sizes=None):
1084
+ r"""
1085
+ Return the intersection graph of the incidence structure.
1086
+
1087
+ The vertices of this graph are the :meth:`blocks` of the incidence
1088
+ structure. Two of them are adjacent if the size of their intersection
1089
+ belongs to the set ``sizes``.
1090
+
1091
+ INPUT:
1092
+
1093
+ - ``sizes`` -- list/set of integers; for convenience, setting
1094
+ ``sizes`` to ``5`` has the same effect as ``sizes=[5]``. When set to
1095
+ ``None`` (default), behaves as ``sizes=PositiveIntegers()``.
1096
+
1097
+ EXAMPLES:
1098
+
1099
+ The intersection graph of a
1100
+ :func:`~sage.combinat.designs.bibd.balanced_incomplete_block_design` is
1101
+ a :meth:`strongly regular graph <Graph.is_strongly_regular>` (when it is
1102
+ not trivial)::
1103
+
1104
+ sage: BIBD = designs.balanced_incomplete_block_design(19,3)
1105
+ sage: G = BIBD.intersection_graph(1)
1106
+ sage: G.is_strongly_regular(parameters=True)
1107
+ (57, 24, 11, 9)
1108
+ """
1109
+ from sage.sets.positive_integers import PositiveIntegers
1110
+ from sage.graphs.graph import Graph
1111
+ from sage.sets.set import Set
1112
+ if sizes is None:
1113
+ sizes = PositiveIntegers()
1114
+ elif sizes in PositiveIntegers():
1115
+ sizes = (sizes,)
1116
+ V = [Set(v) for v in self]
1117
+ return Graph([V, lambda x, y: len(x & y) in sizes], loops=False)
1118
+
1119
+ def incidence_matrix(self):
1120
+ r"""
1121
+ Return the incidence matrix `A` of the design. A is a `(v \times b)`
1122
+ matrix defined by: ``A[i,j] = 1`` if ``i`` is in block ``B_j`` and 0
1123
+ otherwise.
1124
+
1125
+ EXAMPLES::
1126
+
1127
+ sage: BD = IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],
1128
+ ....: [1,4,6],[2,3,6],[2,4,5]])
1129
+ sage: BD.block_sizes()
1130
+ [3, 3, 3, 3, 3, 3, 3]
1131
+ sage: BD.incidence_matrix() # needs sage.modules
1132
+ [1 1 1 0 0 0 0]
1133
+ [1 0 0 1 1 0 0]
1134
+ [1 0 0 0 0 1 1]
1135
+ [0 1 0 1 0 1 0]
1136
+ [0 1 0 0 1 0 1]
1137
+ [0 0 1 1 0 0 1]
1138
+ [0 0 1 0 1 1 0]
1139
+
1140
+ sage: I = IncidenceStructure('abc', ('ab','abc','ac','c'))
1141
+ sage: I.incidence_matrix() # needs sage.modules
1142
+ [1 1 1 0]
1143
+ [1 1 0 0]
1144
+ [0 1 1 1]
1145
+ """
1146
+ from sage.matrix.constructor import matrix
1147
+ from sage.rings.integer_ring import ZZ
1148
+ A = matrix(ZZ, self.num_points(), self.num_blocks(), sparse=True)
1149
+ for j, b in enumerate(self._blocks):
1150
+ for i in b:
1151
+ A[i, j] = 1
1152
+ return A
1153
+
1154
+ def incidence_graph(self, labels=False):
1155
+ r"""
1156
+ Return the incidence graph of the incidence structure.
1157
+
1158
+ A point and a block are adjacent in this graph whenever they are
1159
+ incident.
1160
+
1161
+ INPUT:
1162
+
1163
+ - ``labels`` -- boolean; whether to return a graph whose vertices are
1164
+ integers, or labelled elements
1165
+
1166
+ - ``labels is False`` -- default; in this case the first vertices
1167
+ of the graphs are the elements of :meth:`ground_set`, and appear
1168
+ in the same order. Similarly, the following vertices represent the
1169
+ elements of :meth:`blocks`, and appear in the same order.
1170
+
1171
+ - ``labels is True``, the points keep their original labels, and the
1172
+ blocks are :func:`Set <Set>` objects.
1173
+
1174
+ Note that the labelled incidence graph can be incorrect when
1175
+ blocks are repeated, and on some (rare) occasions when the
1176
+ elements of :meth:`ground_set` mix :func:`Set` and non-:func:`Set
1177
+ <Set>` objects.
1178
+
1179
+ EXAMPLES::
1180
+
1181
+ sage: BD = IncidenceStructure(7, [[0,1,2],[0,3,4],[0,5,6],[1,3,5],
1182
+ ....: [1,4,6],[2,3,6],[2,4,5]])
1183
+ sage: BD.incidence_graph() # needs sage.modules
1184
+ Bipartite graph on 14 vertices
1185
+ sage: A = BD.incidence_matrix() # needs sage.modules
1186
+ sage: Graph(block_matrix([[A*0, A], # needs sage.modules
1187
+ ....: [A.transpose(),A*0]])) == BD.incidence_graph()
1188
+ True
1189
+
1190
+ TESTS:
1191
+
1192
+ With ``labels = True``::
1193
+
1194
+ sage: BD.incidence_graph(labels=True).has_edge(0,Set([0,1,2]))
1195
+ True
1196
+ """
1197
+ if labels:
1198
+ from sage.graphs.graph import Graph
1199
+ from sage.sets.set import Set
1200
+ G = Graph()
1201
+ G.add_vertices(self.ground_set())
1202
+ for b in self.blocks():
1203
+ b = Set(b)
1204
+ G.add_vertex(b)
1205
+ G.add_edges((b, x) for x in b)
1206
+ return G
1207
+
1208
+ else:
1209
+ from sage.graphs.bipartite_graph import BipartiteGraph
1210
+ A = self.incidence_matrix()
1211
+ return BipartiteGraph(A)
1212
+
1213
+ def is_berge_cyclic(self):
1214
+ r"""
1215
+ Check whether ``self`` is a Berge-Cyclic uniform hypergraph.
1216
+
1217
+ A `k`-uniform Berge cycle (named after Claude Berge) of length `\ell`
1218
+ is a cyclic list of distinct `k`-sets `F_1,\ldots,F_\ell`, `\ell>1`,
1219
+ and distinct vertices `C = \{v_1,\ldots,v_\ell\}` such that for each
1220
+ `1\le i\le \ell`, `F_i` contains `v_i` and `v_{i+1}` (where `v_{l+1} =
1221
+ v_1`).
1222
+
1223
+ A uniform hypergraph is Berge-cyclic if its incidence graph is cyclic.
1224
+ It is called "Berge-acyclic" otherwise.
1225
+
1226
+ For more information, see [Fag1983]_ and :wikipedia:`Hypergraph`.
1227
+
1228
+ EXAMPLES::
1229
+
1230
+ sage: Hypergraph(5, [[1, 2, 3], [2, 3, 4]]).is_berge_cyclic() # needs sage.modules
1231
+ True
1232
+ sage: Hypergraph(6, [[1, 2, 3], [3, 4, 5]]).is_berge_cyclic() # needs sage.modules
1233
+ False
1234
+
1235
+ TESTS::
1236
+
1237
+ sage: Hypergraph(5, [[1, 2, 3], [2, 3]]).is_berge_cyclic()
1238
+ Traceback (most recent call last):
1239
+ ...
1240
+ TypeError: Berge cycles are defined for uniform hypergraphs only
1241
+ """
1242
+ if not self.is_uniform():
1243
+ raise TypeError("Berge cycles are defined for uniform hypergraphs only")
1244
+
1245
+ return not self.incidence_graph().is_forest()
1246
+
1247
+ def complement(self, uniform=False):
1248
+ r"""
1249
+ Return the complement of the incidence structure.
1250
+
1251
+ Two different definitions of "complement" are made available, according
1252
+ to the value of ``uniform``.
1253
+
1254
+ INPUT:
1255
+
1256
+ - ``uniform`` -- boolean
1257
+
1258
+ - if set to ``False`` (default), returns the incidence structure whose
1259
+ blocks are the complements of all blocks of the incidence structure.
1260
+
1261
+ - If set to ``True`` and the incidence structure is `k`-uniform,
1262
+ returns the incidence structure whose blocks are all `k`-sets of the
1263
+ ground set that do not appear in ``self``.
1264
+
1265
+ EXAMPLES:
1266
+
1267
+ The complement of a
1268
+ :class:`~sage.combinat.designs.bibd.BalancedIncompleteBlockDesign` is
1269
+ also a `2`-design::
1270
+
1271
+ sage: bibd = designs.balanced_incomplete_block_design(13,4) # needs sage.schemes
1272
+ sage: bibd.is_t_design(return_parameters=True) # needs sage.schemes
1273
+ (True, (2, 13, 4, 1))
1274
+ sage: bibd.complement().is_t_design(return_parameters=True) # needs sage.schemes
1275
+ (True, (2, 13, 9, 6))
1276
+
1277
+ The "uniform" complement of a graph is a graph::
1278
+
1279
+ sage: g = graphs.PetersenGraph()
1280
+ sage: G = IncidenceStructure(g.edges(sort=True, labels=False))
1281
+ sage: H = G.complement(uniform=True)
1282
+ sage: h = Graph(H.blocks())
1283
+ sage: g == h
1284
+ False
1285
+ sage: g == h.complement()
1286
+ True
1287
+
1288
+ TESTS::
1289
+
1290
+ sage: bibd.relabel({i:str(i) for i in bibd.ground_set()}) # needs sage.schemes
1291
+ sage: bibd.complement().ground_set() # needs sage.schemes
1292
+ ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
1293
+
1294
+ sage: I = IncidenceStructure('abc', ['ab','ac','bc'])
1295
+ sage: I.is_t_design(return_parameters=True)
1296
+ (True, (2, 3, 2, 1))
1297
+ """
1298
+ if uniform:
1299
+ k = self.is_uniform()
1300
+ if k is False:
1301
+ raise ValueError("The incidence structure is not uniform.")
1302
+
1303
+ blocks = []
1304
+ num_blocks = self.num_blocks()
1305
+ i = 0
1306
+ from itertools import combinations
1307
+ for B in combinations(range(self.num_points()), k):
1308
+ B = list(B)
1309
+ while i < num_blocks and self._blocks[i] < B:
1310
+ i += 1
1311
+ if i < num_blocks and self._blocks[i] == B:
1312
+ i += 1
1313
+ continue
1314
+ blocks.append(B)
1315
+ I = IncidenceStructure(blocks, copy=False)
1316
+ else:
1317
+ X = set(range(self.num_points()))
1318
+ I = IncidenceStructure([X.difference(B) for B in self._blocks])
1319
+
1320
+ I.relabel({i: self._points[i] for i in range(self.num_points())})
1321
+ return I
1322
+
1323
+ def relabel(self, perm=None, inplace=True):
1324
+ r"""
1325
+ Relabel the ground set.
1326
+
1327
+ INPUT:
1328
+
1329
+ - ``perm`` -- can be one of
1330
+
1331
+ - a dictionary -- then each point ``p`` (which should be a key of
1332
+ ``d``) is relabeled to ``d[p]``
1333
+
1334
+ - a list or a tuple of length ``n`` -- the first point returned by
1335
+ :meth:`ground_set` is relabeled to ``l[0]``, the second to
1336
+ ``l[1]``, ...
1337
+
1338
+ - ``None`` -- the incidence structure is relabeled to be on
1339
+ `\{0,1,...,n-1\}` in the ordering given by :meth:`ground_set`
1340
+
1341
+ - ``inplace`` -- boolean (default: ``False``); if ``True`` then return
1342
+ a relabeled graph and does not touch ``self``
1343
+
1344
+ EXAMPLES::
1345
+
1346
+ sage: # needs sage.schemes
1347
+ sage: TD = designs.transversal_design(5,5)
1348
+ sage: TD.relabel({i: chr(97+i) for i in range(25)})
1349
+ sage: TD.ground_set()
1350
+ ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
1351
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y']
1352
+ sage: TD.blocks()[:3]
1353
+ [['a', 'f', 'k', 'p', 'u'], ['a', 'g', 'm', 's', 'y'], ['a', 'h', 'o', 'q', 'x']]
1354
+
1355
+ Relabel to integer points::
1356
+
1357
+ sage: TD.relabel() # needs sage.schemes
1358
+ sage: TD.blocks()[:3] # needs sage.schemes
1359
+ [[0, 5, 10, 15, 20], [0, 6, 12, 18, 24], [0, 7, 14, 16, 23]]
1360
+
1361
+ TESTS:
1362
+
1363
+ Check that the relabel is consistent on a fixed incidence structure::
1364
+
1365
+ sage: I = IncidenceStructure([0,1,2,3,4],
1366
+ ....: [[0,1,3],[0,2,4],[2,3,4],[0,1]])
1367
+ sage: I.relabel()
1368
+ sage: from itertools import permutations
1369
+ sage: for p in permutations([0,1,2,3,4]):
1370
+ ....: J = I.relabel(p,inplace=False)
1371
+ ....: if I == J: print(p)
1372
+ (0, 1, 2, 3, 4)
1373
+ (0, 1, 4, 3, 2)
1374
+
1375
+ And one can also verify that we have exactly two automorphisms::
1376
+
1377
+ sage: I.automorphism_group() # needs sage.groups
1378
+ Permutation Group with generators [(2,4)]
1379
+ """
1380
+ if not inplace:
1381
+ from copy import copy
1382
+ G = copy(self)
1383
+ G.relabel(perm=perm, inplace=True)
1384
+ return G
1385
+
1386
+ if perm is None:
1387
+ self._points = list(range(self.num_points()))
1388
+ self._point_to_index = None
1389
+ return
1390
+
1391
+ if isinstance(perm, (list, tuple)):
1392
+ perm = dict(zip(self._points, perm))
1393
+
1394
+ if not isinstance(perm, dict):
1395
+ raise ValueError("perm argument must be None, a list or a dictionary")
1396
+
1397
+ if len(set(perm.values())) != len(perm):
1398
+ raise ValueError("two points are getting relabelled with the same name")
1399
+
1400
+ self._points = [perm[x] for x in self._points]
1401
+ if self._points == list(range(self.num_points())):
1402
+ self._point_to_index = None
1403
+ else:
1404
+ self._point_to_index = {v: i for i, v in enumerate(self._points)}
1405
+
1406
+ # __hash__ = None
1407
+ # This object is mutable because of .relabel()
1408
+
1409
+ #####################
1410
+ # real computations #
1411
+ #####################
1412
+
1413
+ def packing(self, solver=None, verbose=0, *, integrality_tolerance=1e-3):
1414
+ r"""
1415
+ Return a maximum packing.
1416
+
1417
+ A maximum packing in a hypergraph is collection of disjoint sets/blocks
1418
+ of maximal cardinality. This problem is NP-complete in general, and in
1419
+ particular on 3-uniform hypergraphs. It is solved here with an Integer
1420
+ Linear Program.
1421
+
1422
+ For more information, see the :wikipedia:`Packing_in_a_hypergraph`.
1423
+
1424
+ INPUT:
1425
+
1426
+ - ``solver`` -- (default: ``None``) specify a Mixed Integer Linear
1427
+ Programming (MILP) solver to be used. If set to ``None``, the default
1428
+ one is used. For more information on LP solvers and which default
1429
+ solver is used, see the method :meth:`solve
1430
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1431
+ :class:`MixedIntegerLinearProgram
1432
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
1433
+
1434
+ - ``verbose`` -- integer (default: 0); sets the level of
1435
+ verbosity. Set to 0 by default, which means quiet.
1436
+
1437
+ - ``integrality_tolerance`` -- parameter for use with MILP solvers over
1438
+ an inexact base ring; see
1439
+ :meth:`MixedIntegerLinearProgram.get_values`.
1440
+
1441
+ EXAMPLES::
1442
+
1443
+ sage: P = IncidenceStructure([[1,2],[3,4],[2,3]]).packing() # needs sage.numerical.mip
1444
+ sage: sorted(sorted(b) for b in P) # needs sage.numerical.mip
1445
+ [[1, 2], [3, 4]]
1446
+ sage: len(designs.steiner_triple_system(9).packing()) # needs sage.numerical.mip
1447
+ 3
1448
+ """
1449
+ from sage.numerical.mip import MixedIntegerLinearProgram
1450
+
1451
+ # List of blocks containing a given point x
1452
+ d = [[] for _ in self._points]
1453
+ for i, B in enumerate(self._blocks):
1454
+ for x in B:
1455
+ d[x].append(i)
1456
+
1457
+ p = MixedIntegerLinearProgram(solver=solver)
1458
+ b = p.new_variable(binary=True)
1459
+ for x, L in enumerate(d): # Set of disjoint blocks
1460
+ p.add_constraint(p.sum([b[i] for i in L]) <= 1)
1461
+
1462
+ # Maximum number of blocks
1463
+ p.set_objective(p.sum([b[i] for i in range(self.num_blocks())]))
1464
+
1465
+ p.solve(log=verbose)
1466
+
1467
+ values = p.get_values(b, convert=bool, tolerance=integrality_tolerance)
1468
+ return [[self._points[x] for x in self._blocks[i]]
1469
+ for i, v in values.items() if v]
1470
+
1471
+ def is_t_design(self, t=None, v=None, k=None, l=None, return_parameters=False):
1472
+ r"""
1473
+ Test whether ``self`` is a `t-(v,k,l)` design.
1474
+
1475
+ A `t-(v,k,\lambda)` (sometimes called `t`-design for short) is a block
1476
+ design in which:
1477
+
1478
+ - the underlying set has cardinality `v`
1479
+ - the blocks have size `k`
1480
+ - each `t`-subset of points is covered by `\lambda` blocks
1481
+
1482
+ INPUT:
1483
+
1484
+ - ``t``, ``v``, ``k``, ``l`` -- integers; their value is set to
1485
+ ``None`` by default. The function tests whether the design is a
1486
+ `t-(v,k,l)` design using the provided values and guesses the
1487
+ others. Note that ``l`` cannot be specified if ``t`` is not.
1488
+
1489
+ - ``return_parameters`` -- boolean; whether to return the parameters of
1490
+ the `t`-design. If set to ``True``, the function returns a pair
1491
+ ``(boolean_answer,(t,v,k,l))``.
1492
+
1493
+ EXAMPLES::
1494
+
1495
+ sage: fano_blocks = [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]
1496
+ sage: BD = IncidenceStructure(7, fano_blocks)
1497
+ sage: BD.is_t_design()
1498
+ True
1499
+ sage: BD.is_t_design(return_parameters=True)
1500
+ (True, (2, 7, 3, 1))
1501
+ sage: BD.is_t_design(2, 7, 3, 1)
1502
+ True
1503
+ sage: BD.is_t_design(1, 7, 3, 3)
1504
+ True
1505
+ sage: BD.is_t_design(0, 7, 3, 7)
1506
+ True
1507
+
1508
+ sage: BD.is_t_design(0,6,3,7) or BD.is_t_design(0,7,4,7) or BD.is_t_design(0,7,3,8)
1509
+ False
1510
+
1511
+ sage: BD = designs.AffineGeometryDesign(3, 1, GF(2)) # needs sage.combinat sage.modules
1512
+ sage: BD.is_t_design(1) # needs sage.combinat sage.modules
1513
+ True
1514
+ sage: BD.is_t_design(2) # needs sage.combinat sage.modules
1515
+ True
1516
+
1517
+ Steiner triple and quadruple systems are other names for `2-(v,3,1)` and
1518
+ `3-(v,4,1)` designs::
1519
+
1520
+ sage: S3_9 = designs.steiner_triple_system(9)
1521
+ sage: S3_9.is_t_design(2,9,3,1)
1522
+ True
1523
+
1524
+ sage: blocks = designs.steiner_quadruple_system(8)
1525
+ sage: S4_8 = IncidenceStructure(8, blocks)
1526
+ sage: S4_8.is_t_design(3,8,4,1)
1527
+ True
1528
+
1529
+ sage: blocks = designs.steiner_quadruple_system(14)
1530
+ sage: S4_14 = IncidenceStructure(14, blocks)
1531
+ sage: S4_14.is_t_design(3,14,4,1)
1532
+ True
1533
+
1534
+ Some examples of Witt designs that need the gap database::
1535
+
1536
+ sage: # optional - gap_package_design
1537
+ sage: BD = designs.WittDesign(9)
1538
+ sage: BD.is_t_design(2,9,3,1)
1539
+ True
1540
+ sage: W12 = designs.WittDesign(12)
1541
+ sage: W12.is_t_design(5,12,6,1)
1542
+ True
1543
+ sage: W12.is_t_design(4)
1544
+ True
1545
+
1546
+ Further examples::
1547
+
1548
+ sage: D = IncidenceStructure(4,[[],[]])
1549
+ sage: D.is_t_design(return_parameters=True)
1550
+ (True, (0, 4, 0, 2))
1551
+
1552
+ sage: D = IncidenceStructure(4, [[0,1],[0,2],[0,3]])
1553
+ sage: D.is_t_design(return_parameters=True)
1554
+ (True, (0, 4, 2, 3))
1555
+
1556
+ sage: D = IncidenceStructure(4, [[0],[1],[2],[3]])
1557
+ sage: D.is_t_design(return_parameters=True)
1558
+ (True, (1, 4, 1, 1))
1559
+
1560
+ sage: D = IncidenceStructure(4,[[0,1],[2,3]])
1561
+ sage: D.is_t_design(return_parameters=True)
1562
+ (True, (1, 4, 2, 1))
1563
+
1564
+ sage: D = IncidenceStructure(4, [list(range(4))])
1565
+ sage: D.is_t_design(return_parameters=True)
1566
+ (True, (4, 4, 4, 1))
1567
+
1568
+ TESTS::
1569
+
1570
+ sage: blocks = designs.steiner_quadruple_system(8)
1571
+ sage: S4_8 = IncidenceStructure(8, blocks)
1572
+ sage: R = list(range(15))
1573
+ sage: [(v,k,l) for v in R for k in R for l in R if S4_8.is_t_design(3,v,k,l)]
1574
+ [(8, 4, 1)]
1575
+ sage: [(v,k,l) for v in R for k in R for l in R if S4_8.is_t_design(2,v,k,l)]
1576
+ [(8, 4, 3)]
1577
+ sage: [(v,k,l) for v in R for k in R for l in R if S4_8.is_t_design(1,v,k,l)]
1578
+ [(8, 4, 7)]
1579
+ sage: [(v,k,l) for v in R for k in R for l in R if S4_8.is_t_design(0,v,k,l)]
1580
+ [(8, 4, 14)]
1581
+
1582
+ sage: # needs sage.rings.finite_rings
1583
+ sage: A = designs.AffineGeometryDesign(3, 1, GF(2))
1584
+ sage: A.is_t_design(return_parameters=True)
1585
+ (True, (2, 8, 2, 1))
1586
+ sage: A = designs.AffineGeometryDesign(4, 2, GF(2))
1587
+ sage: A.is_t_design(return_parameters=True)
1588
+ (True, (3, 16, 4, 1))
1589
+
1590
+ sage: I = IncidenceStructure(2, [])
1591
+ sage: I.is_t_design(return_parameters=True)
1592
+ (True, (0, 2, 0, 0))
1593
+ sage: I = IncidenceStructure(2, [[0],[0,1]])
1594
+ sage: I.is_t_design(return_parameters=True)
1595
+ (False, (0, 0, 0, 0))
1596
+
1597
+ Verify that :issue:`38454` is fixed::
1598
+
1599
+ sage: I = IncidenceStructure(points=[0,1,2,3,4,5],
1600
+ ....: blocks=[[0,1], [1,2], [0,2]])
1601
+ sage: I.is_t_design(return_parameters=True)
1602
+ (True, (0, 6, 2, 3))
1603
+ """
1604
+ from sage.arith.misc import binomial
1605
+
1606
+ # Missing parameters ?
1607
+ if v is None:
1608
+ v = self.num_points()
1609
+
1610
+ if k is None:
1611
+ k = len(self._blocks[0]) if self._blocks else 0
1612
+
1613
+ if l is not None and t is None:
1614
+ raise ValueError("t must be set when l=None")
1615
+
1616
+ b = self.num_blocks()
1617
+
1618
+ # Trivial wrong answers
1619
+ if (any(len(block) != k for block in self._blocks) or # non k-uniform
1620
+ v != self.num_points()):
1621
+ return (False, (0, 0, 0, 0)) if return_parameters else False
1622
+
1623
+ # Trivial case t>k
1624
+ if (t is not None and t > k):
1625
+ if (l is None or l == 0):
1626
+ return (True, (t, v, k, 0)) if return_parameters else True
1627
+ else:
1628
+ return (False, (0, 0, 0, 0)) if return_parameters else False
1629
+
1630
+ # Trivial case k=0
1631
+ if k == 0:
1632
+ if (l is None or l == 0):
1633
+ return (True, (0, v, k, b)) if return_parameters else True
1634
+ else:
1635
+ return (False, (0, 0, 0, 0)) if return_parameters else False
1636
+
1637
+ # Trivial case k=v (includes v=0)
1638
+ if k == v:
1639
+ if t is None:
1640
+ t = v
1641
+ if l is None or b == l:
1642
+ return (True, (t, v, k, b)) if return_parameters else True
1643
+ else:
1644
+ return (True, (0, 0, 0, 0)) if return_parameters else False
1645
+
1646
+ # Handbook of combinatorial design theorem II.4.8:
1647
+ #
1648
+ # a t-(v,k,l) is also a t'-(v,k,l')
1649
+ # for t' < t and l' = l* binomial(v-t',t-t') / binomial(k-t',t-t')
1650
+ #
1651
+ # We look for the largest t such that self is a t-design
1652
+ from itertools import combinations
1653
+ for tt in (range(1, k + 1) if t is None else [t]):
1654
+ # is lambda an integer?
1655
+ if (b * binomial(k, tt)) % binomial(v, tt):
1656
+ tt -= 1
1657
+ break
1658
+
1659
+ s = {}
1660
+ for block in self._blocks:
1661
+ for i in combinations(block, tt):
1662
+ s[i] = s.get(i, 0) + 1
1663
+
1664
+ if (len(s) != binomial(v, tt)) or (len(set(s.values())) != 1):
1665
+ tt -= 1
1666
+ break
1667
+
1668
+ ll = (b * binomial(k, tt)) // binomial(v, tt)
1669
+
1670
+ if ((t is not None and t != tt) or
1671
+ (l is not None and l != ll)):
1672
+ return (False, (0, 0, 0, 0)) if return_parameters else False
1673
+ else:
1674
+ if tt == 0:
1675
+ ll = b
1676
+ return (True, (tt, v, k, ll)) if return_parameters else True
1677
+
1678
+ def is_generalized_quadrangle(self, verbose=False, parameters=False):
1679
+ r"""
1680
+ Test if the incidence structure is a generalized quadrangle.
1681
+
1682
+ An incidence structure is a generalized quadrangle iff (see [BH2012]_,
1683
+ section 9.6):
1684
+
1685
+ - two blocks intersect on at most one point.
1686
+
1687
+ - For every point `p` not in a block `B`, there is a unique block `B'`
1688
+ intersecting both `\{p\}` and `B`
1689
+
1690
+ It is a *regular* generalized quadrangle if furthermore:
1691
+
1692
+ - it is `s+1`-:meth:`uniform <is_uniform>` for some positive integer `s`.
1693
+
1694
+ - it is `t+1`-:meth:`regular <is_regular>` for some positive integer `t`.
1695
+
1696
+ For more information, see the :wikipedia:`Generalized_quadrangle`.
1697
+
1698
+ .. NOTE::
1699
+
1700
+ Some references (e.g. [PT2009]_ or
1701
+ :wikipedia:`Generalized_quadrangle`) only allow *regular*
1702
+ generalized quadrangles. To use such a definition, see the
1703
+ ``parameters`` optional argument described below, or the methods
1704
+ :meth:`is_regular` and :meth:`is_uniform`.
1705
+
1706
+ INPUT:
1707
+
1708
+ - ``verbose`` -- boolean; whether to print an explanation when the
1709
+ instance is not a generalized quadrangle
1710
+
1711
+ - ``parameters`` -- (boolean; ``False``); if set to ``True``, the
1712
+ function returns a pair ``(s,t)`` instead of ``True`` answers. In this
1713
+ case, `s` and `t` are the integers defined above if they exist (each
1714
+ can be set to ``False`` otherwise).
1715
+
1716
+ EXAMPLES::
1717
+
1718
+ sage: h = designs.CremonaRichmondConfiguration() # needs networkx
1719
+ sage: h.is_generalized_quadrangle() # needs networkx
1720
+ True
1721
+
1722
+ This is actually a *regular* generalized quadrangle::
1723
+
1724
+ sage: h.is_generalized_quadrangle(parameters=True) # needs networkx
1725
+ (2, 2)
1726
+
1727
+ TESTS::
1728
+
1729
+ sage: H = IncidenceStructure((2*graphs.CompleteGraph(3)).edges(sort=True, labels=False))
1730
+ sage: H.is_generalized_quadrangle(verbose=True) # needs sage.modules
1731
+ Some point is at distance >3 from some block.
1732
+ False
1733
+
1734
+ sage: G = graphs.CycleGraph(5)
1735
+ sage: B = list(G.subgraph_search_iterator(graphs.PathGraph(3), # needs sage.modules
1736
+ ....: return_graphs=False))
1737
+ sage: H = IncidenceStructure(B) # needs sage.modules
1738
+ sage: H.is_generalized_quadrangle(verbose=True) # needs sage.modules
1739
+ Two blocks intersect on >1 points.
1740
+ False
1741
+
1742
+ sage: hypergraphs.CompleteUniform(4,2).is_generalized_quadrangle(verbose=1) # needs sage.modules
1743
+ Some point has two projections on some line.
1744
+ False
1745
+ """
1746
+ # The distance between a point and a line in the incidence graph is odd
1747
+ # and must be <= 3. Thus, the diameter is at most 4
1748
+ g = self.incidence_graph()
1749
+ if g.diameter() > 4:
1750
+ if verbose:
1751
+ print("Some point is at distance >3 from some block.")
1752
+ return False
1753
+
1754
+ # There is a unique projection of a point on a line. Thus, the girth of
1755
+ # g is at least 7
1756
+ girth = g.girth()
1757
+ if girth == 4:
1758
+ if verbose:
1759
+ print("Two blocks intersect on >1 points.")
1760
+ return False
1761
+ elif girth == 6:
1762
+ if verbose:
1763
+ print("Some point has two projections on some line.")
1764
+ return False
1765
+
1766
+ if parameters:
1767
+ s = self.is_uniform()
1768
+ t = self.is_regular()
1769
+ s = s - 1 if (s is not False and s >= 2) else False
1770
+ t = t - 1 if (t is not False and t >= 2) else False
1771
+ return (s, t)
1772
+ else:
1773
+ return True
1774
+
1775
+ def dual(self, algorithm=None):
1776
+ """
1777
+ Return the dual of the incidence structure.
1778
+
1779
+ INPUT:
1780
+
1781
+ - ``algorithm`` -- whether to use Sage's implementation
1782
+ (``algorithm=None``, default) or use GAP's (``algorithm='gap'``)
1783
+
1784
+ .. NOTE::
1785
+
1786
+ The ``algorithm='gap'`` option requires GAP's Design package
1787
+ (included in the ``gap_packages`` Sage spkg).
1788
+
1789
+ EXAMPLES:
1790
+
1791
+ The dual of a projective plane is a projective plane::
1792
+
1793
+ sage: PP = designs.DesarguesianProjectivePlaneDesign(4) # needs sage.rings.finite_rings
1794
+ sage: PP.dual().is_t_design(return_parameters=True) # needs sage.modules sage.rings.finite_rings
1795
+ (True, (2, 21, 5, 1))
1796
+
1797
+ TESTS::
1798
+
1799
+ sage: D = IncidenceStructure(4, [[0,2],[1,2,3],[2,3]]); D
1800
+ Incidence structure with 4 points and 3 blocks
1801
+ sage: D.dual() # needs sage.modules
1802
+ Incidence structure with 3 points and 4 blocks
1803
+ sage: print(D.dual(algorithm='gap')) # optional - gap_package_design
1804
+ Incidence structure with 3 points and 4 blocks
1805
+ sage: blocks = [[0,1,2],[0,3,4],[0,5,6],[1,3,5],[1,4,6],[2,3,6],[2,4,5]]
1806
+ sage: BD = IncidenceStructure(7, blocks, name='FanoPlane'); BD
1807
+ Incidence structure with 7 points and 7 blocks
1808
+ sage: print(BD.dual(algorithm='gap')) # optional - gap_package_design
1809
+ Incidence structure with 7 points and 7 blocks
1810
+ sage: BD.dual() # needs sage.modules
1811
+ Incidence structure with 7 points and 7 blocks
1812
+
1813
+ REFERENCE:
1814
+
1815
+ - Leonard Soicher, :gap_package:`Design package manual <design/htm/CHAP003.htm>`
1816
+ """
1817
+ if algorithm == "gap":
1818
+ libgap.load_package("design")
1819
+ DD = libgap(self).DualBlockDesign()
1820
+ v = DD['v'].sage()
1821
+ gB = [[x - 1 for x in b] for b in DD['blocks'].sage()]
1822
+ return IncidenceStructure(list(range(v)), gB, name=None, check=False)
1823
+
1824
+ return IncidenceStructure(
1825
+ incidence_matrix=self.incidence_matrix().transpose(),
1826
+ check=False)
1827
+
1828
+ def automorphism_group(self):
1829
+ r"""
1830
+ Return the subgroup of the automorphism group of the incidence graph
1831
+ which respects the P B partition. It is (isomorphic to) the automorphism
1832
+ group of the block design, although the degrees differ.
1833
+
1834
+ EXAMPLES::
1835
+
1836
+ sage: # needs sage.groups sage.rings.finite_rings
1837
+ sage: P = designs.DesarguesianProjectivePlaneDesign(2); P
1838
+ (7,3,1)-Balanced Incomplete Block Design
1839
+ sage: G = P.automorphism_group()
1840
+ sage: G.is_isomorphic(PGL(3,2))
1841
+ True
1842
+ sage: G
1843
+ Permutation Group with generators [...]
1844
+ sage: G.cardinality()
1845
+ 168
1846
+
1847
+ A non self-dual example::
1848
+
1849
+ sage: IS = IncidenceStructure(list(range(4)), [[0,1,2,3],[1,2,3]])
1850
+ sage: IS.automorphism_group().cardinality() # needs sage.groups
1851
+ 6
1852
+ sage: IS.dual().automorphism_group().cardinality() # needs sage.groups sage.modules
1853
+ 1
1854
+
1855
+ Examples with non-integer points::
1856
+
1857
+ sage: I = IncidenceStructure('abc', ('ab','ac','bc'))
1858
+ sage: I.automorphism_group() # needs sage.groups
1859
+ Permutation Group with generators [('b','c'), ('a','b')]
1860
+ sage: IncidenceStructure([[(1,2),(3,4)]]).automorphism_group() # needs sage.groups
1861
+ Permutation Group with generators [((1,2),(3,4))]
1862
+ """
1863
+ from sage.graphs.graph import Graph
1864
+ from sage.groups.perm_gps.permgroup import PermutationGroup
1865
+ g = Graph()
1866
+ n = self.num_points()
1867
+ g.add_edges((i + n, x) for i, b in enumerate(self._blocks) for x in b)
1868
+ ag = g.automorphism_group(partition=[list(range(n)),
1869
+ list(range(n, n + self.num_blocks()))])
1870
+
1871
+ if self._point_to_index:
1872
+ gens = [[tuple([self._points[i] for i in cycle if (not cycle or cycle[0] < n)])
1873
+ for cycle in g.cycle_tuples()]
1874
+ for g in ag.gens()]
1875
+ else:
1876
+ gens = [[tuple(cycle) for cycle in g.cycle_tuples() if (not cycle or cycle[0] < n)]
1877
+ for g in ag.gens()]
1878
+
1879
+ return PermutationGroup(gens, domain=self._points)
1880
+
1881
+ def is_resolvable(self, certificate=False, solver=None, verbose=0, check=True,
1882
+ *, integrality_tolerance=1e-3):
1883
+ r"""
1884
+ Test whether the hypergraph is resolvable.
1885
+
1886
+ A hypergraph is said to be resolvable if its sets can be partitionned
1887
+ into classes, each of which is a partition of the ground set.
1888
+
1889
+ .. NOTE::
1890
+
1891
+ This problem is solved using an Integer Linear Program, and GLPK
1892
+ (the default LP solver) has been reported to be very slow on some
1893
+ instances. If you hit this wall, consider installing a more powerful
1894
+ MILP solver (CPLEX, Gurobi, ...).
1895
+
1896
+ INPUT:
1897
+
1898
+ - ``certificate`` -- boolean; whether to return the classes along with
1899
+ the binary answer (see examples below)
1900
+
1901
+ - ``solver`` -- (default: ``None``) specify a Mixed Integer Linear
1902
+ Programming (MILP) solver to be used. If set to ``None``, the default
1903
+ one is used. For more information on MILP solvers and which default
1904
+ solver is used, see the method :meth:`solve
1905
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1906
+ :class:`MixedIntegerLinearProgram
1907
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
1908
+
1909
+ - ``verbose`` -- integer (default: 0); sets the level of
1910
+ verbosity. Set to 0 by default, which means quiet.
1911
+
1912
+ - ``check`` -- boolean (default: ``True``); whether to check that
1913
+ output is correct before returning it. As this is expected to be
1914
+ useless, you may want to disable it whenever you want speed.
1915
+
1916
+ - ``integrality_tolerance`` -- parameter for use with MILP solvers over
1917
+ an inexact base ring; see :meth:`MixedIntegerLinearProgram.get_values`
1918
+
1919
+ EXAMPLES:
1920
+
1921
+ Some resolvable designs::
1922
+
1923
+ sage: TD = designs.transversal_design(2,2,resolvable=True)
1924
+ sage: TD.is_resolvable()
1925
+ True
1926
+
1927
+ sage: AG = designs.AffineGeometryDesign(3,1,GF(2)) # needs sage.combinat sage.modules
1928
+ sage: AG.is_resolvable() # needs sage.combinat sage.modules
1929
+ True
1930
+
1931
+ Their classes::
1932
+
1933
+ sage: b, cls = TD.is_resolvable(True)
1934
+ sage: b
1935
+ True
1936
+ sage: cls # random
1937
+ [[[0, 3], [1, 2]], [[1, 3], [0, 2]]]
1938
+
1939
+ sage: # needs sage.combinat
1940
+ sage: b, cls = AG.is_resolvable(True)
1941
+ sage: b
1942
+ True
1943
+ sage: cls # random
1944
+ [[[6, 7], [4, 5], [0, 1], [2, 3]],
1945
+ [[5, 7], [0, 4], [3, 6], [1, 2]],
1946
+ [[0, 2], [4, 7], [1, 3], [5, 6]],
1947
+ [[3, 4], [0, 7], [1, 5], [2, 6]],
1948
+ [[3, 7], [1, 6], [0, 5], [2, 4]],
1949
+ [[0, 6], [2, 7], [1, 4], [3, 5]],
1950
+ [[4, 6], [0, 3], [2, 5], [1, 7]]]
1951
+
1952
+ A non-resolvable design::
1953
+
1954
+ sage: Fano = designs.balanced_incomplete_block_design(7,3) # needs sage.schemes
1955
+ sage: Fano.is_resolvable() # needs sage.schemes
1956
+ False
1957
+ sage: Fano.is_resolvable(True) # needs sage.schemes
1958
+ (False, [])
1959
+
1960
+ TESTS::
1961
+
1962
+ sage: # needs sage.combinat
1963
+ sage: _, cls1 = AG.is_resolvable(certificate=True)
1964
+ sage: _, cls2 = AG.is_resolvable(certificate=True)
1965
+ sage: cls1 is cls2
1966
+ False
1967
+ """
1968
+ if self._classes is None:
1969
+ degrees = set(self.degrees().values())
1970
+ if len(degrees) != 1:
1971
+ self._classes = False
1972
+ else:
1973
+ from sage.numerical.mip import MixedIntegerLinearProgram
1974
+ from sage.numerical.mip import MIPSolverException
1975
+ n_classes = degrees.pop()
1976
+ p = MixedIntegerLinearProgram(solver=solver)
1977
+ b = p.new_variable(binary=True)
1978
+ domain = list(range(self.num_points()))
1979
+
1980
+ # Lists of blocks containing i for every i
1981
+ dual = [[] for _ in domain]
1982
+ for i, B in enumerate(self._blocks):
1983
+ for x in B:
1984
+ dual[x].append(i)
1985
+
1986
+ # Each class is a partition
1987
+ for t in range(n_classes):
1988
+ for x in domain:
1989
+ p.add_constraint(p.sum(b[t, i] for i in dual[x]) == 1)
1990
+
1991
+ # Each set appears exactly once
1992
+ for i in range(len(self._blocks)):
1993
+ p.add_constraint(p.sum(b[t, i] for t in range(n_classes)) == 1)
1994
+
1995
+ try:
1996
+ p.solve(log=verbose)
1997
+ except MIPSolverException:
1998
+ self._classes = False
1999
+ else:
2000
+ # each class is stored as the list of indices of its blocks
2001
+ self._classes = [[] for _ in range(n_classes)]
2002
+ for (t, i), v in p.get_values(b, convert=bool, tolerance=integrality_tolerance).items():
2003
+ if v:
2004
+ self._classes[t].append(self._blocks[i])
2005
+
2006
+ if check and self._classes is not False:
2007
+ assert sorted(id(c) for cls in self._classes for c in cls) == sorted(id(b) for b in self._blocks), "some set does not appear exactly once"
2008
+ domain = list(range(self.num_points()))
2009
+ for i, c in enumerate(self._classes):
2010
+ assert sorted(sum(c, [])) == domain, "class {} is not a partition".format(i)
2011
+
2012
+ if self._classes is False:
2013
+ return (False, []) if certificate else False
2014
+
2015
+ if certificate:
2016
+ if self._point_to_index is None:
2017
+ classes = [[block[:] for block in classs] for classs in self._classes]
2018
+ else:
2019
+ classes = [[[self._points[i] for i in block] for block in classs] for classs in self._classes]
2020
+
2021
+ return (True, classes)
2022
+
2023
+ else:
2024
+ return True
2025
+
2026
+ def coloring(self, k=None, solver=None, verbose=0,
2027
+ *, integrality_tolerance=1e-3):
2028
+ r"""
2029
+ Compute a (weak) `k`-coloring of the hypergraph.
2030
+
2031
+ A weak coloring of a hypergraph `\mathcal H` is an assignment of colors
2032
+ to its vertices such that no set is monochromatic.
2033
+
2034
+ INPUT:
2035
+
2036
+ - ``k`` -- integer; compute a coloring with `k` colors if an integer is
2037
+ provided, otherwise returns an optimal coloring (i.e. with the minimum
2038
+ possible number of colors).
2039
+
2040
+ - ``solver`` -- (default: ``None``) specify a Mixed Integer Linear
2041
+ Programming (MILP) solver to be used. If set to ``None``, the default
2042
+ one is used. For more information on MILP solvers and which default
2043
+ solver is used, see the method :meth:`solve
2044
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
2045
+ :class:`MixedIntegerLinearProgram
2046
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
2047
+
2048
+ - ``verbose`` -- nonnegative integer (default: `0`); set the level
2049
+ of verbosity you want from the linear program solver. Since the
2050
+ problem is `NP`-complete, its solving may take some time depending on
2051
+ the graph. A value of `0` means that there will be no message printed by
2052
+ the solver.
2053
+
2054
+ - ``integrality_tolerance`` -- parameter for use with MILP solvers over
2055
+ an inexact base ring; see :meth:`MixedIntegerLinearProgram.get_values`
2056
+
2057
+ EXAMPLES:
2058
+
2059
+ The Fano plane has chromatic number 3::
2060
+
2061
+ sage: len(designs.steiner_triple_system(7).coloring()) # needs sage.numerical.mip
2062
+ 3
2063
+
2064
+ One admissible 3-coloring::
2065
+
2066
+ sage: designs.steiner_triple_system(7).coloring() # not tested # needs sage.numerical.mip
2067
+ [[0, 2, 5, 1], [4, 3], [6]]
2068
+
2069
+ The chromatic number of a graph is equal to the chromatic number of its
2070
+ 2-uniform corresponding hypergraph::
2071
+
2072
+ sage: g = graphs.PetersenGraph()
2073
+ sage: H = IncidenceStructure(g.edges(sort=True, labels=False))
2074
+ sage: len(g.coloring())
2075
+ 3
2076
+ sage: len(H.coloring()) # needs sage.numerical.mip
2077
+ 3
2078
+ """
2079
+ if k is None:
2080
+ for k in range(self.num_points() + 1):
2081
+ try:
2082
+ return self.coloring(k)
2083
+ except ValueError:
2084
+ pass
2085
+
2086
+ if k == 0:
2087
+ if self.num_points():
2088
+ raise ValueError("Only empty hypergraphs are 0-chromatic")
2089
+ return []
2090
+ elif any(len(x) == 1 for x in self._blocks):
2091
+ raise RuntimeError("No coloring can be defined "
2092
+ "when there is a set of size 1")
2093
+ elif k == 1:
2094
+ if any(self._blocks):
2095
+ raise ValueError("This hypergraph contains a set. "
2096
+ "It is not 1-chromatic")
2097
+ return [self.ground_set()]
2098
+
2099
+ from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException
2100
+ p = MixedIntegerLinearProgram(solver=solver)
2101
+ b = p.new_variable(binary=True)
2102
+
2103
+ for x in range(self.num_points()):
2104
+ p.add_constraint(p.sum(b[x, i] for i in range(k)) == 1)
2105
+
2106
+ for s in self._blocks:
2107
+ for i in range(k):
2108
+ p.add_constraint(p.sum(b[x, i] for x in s) <= len(s) - 1)
2109
+
2110
+ try:
2111
+ p.solve(log=verbose)
2112
+ except MIPSolverException:
2113
+ raise ValueError("This hypergraph is not {}-colorable".format(k))
2114
+
2115
+ col = [[] for _ in range(k)]
2116
+
2117
+ for (x, i), v in p.get_values(b, convert=bool, tolerance=integrality_tolerance).items():
2118
+ if v:
2119
+ col[i].append(self._points[x])
2120
+
2121
+ return col
2122
+
2123
+ def edge_coloring(self) -> list:
2124
+ r"""
2125
+ Compute a proper edge-coloring.
2126
+
2127
+ A proper edge-coloring is an assignment of colors to the sets of the
2128
+ incidence structure such that two sets with non-empty intersection
2129
+ receive different colors. The coloring returned minimizes the number of
2130
+ colors.
2131
+
2132
+ OUTPUT: a partition of the sets into color classes
2133
+
2134
+ EXAMPLES::
2135
+
2136
+ sage: # needs cliquer
2137
+ sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H
2138
+ Incidence structure with 6 points and 4 blocks
2139
+ sage: C = H.edge_coloring()
2140
+ sage: C # random
2141
+ [[[3, 4, 5]], [[2, 3, 4]], [[4, 5, 6], [1, 2, 3]]]
2142
+ sage: Set(map(Set,sum(C,[]))) == Set(map(Set,H.blocks()))
2143
+ True
2144
+ """
2145
+ from sage.graphs.graph import Graph
2146
+ blocks = self.blocks()
2147
+ blocks_sets = [frozenset(b) for b in blocks]
2148
+ g = Graph([list(range(self.num_blocks())),
2149
+ lambda x, y: len(blocks_sets[x] & blocks_sets[y])],
2150
+ loops=False)
2151
+ return [[blocks[i] for i in C] for C in g.coloring(algorithm='MILP')]
2152
+
2153
+ def _spring_layout(self):
2154
+ r"""
2155
+ Return a spring layout for the points.
2156
+
2157
+ The layout is computed by creating a graph `G` on the points *and* sets
2158
+ of the incidence structure. Each set is then made adjacent in `G` with
2159
+ all points it contains before a spring layout is computed for this
2160
+ graph. The position of the points in the graph gives the position of the
2161
+ points in the final drawing.
2162
+
2163
+ .. NOTE::
2164
+
2165
+ This method also returns the position of the "fake" points,
2166
+ i.e. those representing the sets.
2167
+
2168
+ EXAMPLES::
2169
+
2170
+ sage: # needs sage.plot
2171
+ sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H
2172
+ Incidence structure with 6 points and 4 blocks
2173
+ sage: L = H._spring_layout()
2174
+ sage: L # random
2175
+ {1: (0.238, -0.926),
2176
+ 2: (0.672, -0.518),
2177
+ 3: (0.449, -0.225),
2178
+ 4: (0.782, 0.225),
2179
+ 5: (0.558, 0.518),
2180
+ 6: (0.992, 0.926),
2181
+ {3, 4, 5}: (0.504, 0.173),
2182
+ {2, 3, 4}: (0.727, -0.173),
2183
+ {4, 5, 6}: (0.838, 0.617),
2184
+ {1, 2, 3}: (0.393, -0.617)}
2185
+ sage: all(v in L for v in H.ground_set())
2186
+ True
2187
+ sage: all(v in L for v in map(Set, H.blocks()))
2188
+ True
2189
+ """
2190
+ from sage.graphs.graph import Graph
2191
+
2192
+ g = Graph()
2193
+ for s in map(Set, self.blocks()):
2194
+ for x in s:
2195
+ g.add_edge((0, s), (1, x))
2196
+
2197
+ _ = g.plot(iterations=50000, save_pos=True)
2198
+
2199
+ # The values are rounded as TikZ does not like accuracy.
2200
+ return {k[1]: (round(x, 3), round(y, 3))
2201
+ for k, (x, y) in g.get_pos().items()}
2202
+
2203
+ def _latex_(self) -> str:
2204
+ r"""
2205
+ Return a TikZ representation of the incidence structure.
2206
+
2207
+ EXAMPLES::
2208
+
2209
+ sage: H = Hypergraph([{1,2,3},{2,3,4},{3,4,5},{4,5,6}]); H
2210
+ Incidence structure with 6 points and 4 blocks
2211
+ sage: view(H) # not tested
2212
+
2213
+ With sets of size 4::
2214
+
2215
+ sage: g = graphs.Grid2dGraph(5,5)
2216
+ sage: C4 = graphs.CycleGraph(4)
2217
+ sage: sets = Set(map(Set, g.subgraph_search_iterator(C4, # needs sage.modules
2218
+ ....: return_graphs=False)))
2219
+ sage: H = Hypergraph(sets) # needs sage.modules
2220
+ sage: view(H) # not tested # needs sage.modules sage.plot
2221
+
2222
+ TESTS::
2223
+
2224
+ # verify that :issue:`30976` is fixed
2225
+ sage: IS = IncidenceStructure([1,2,3], [[1,2], [2,3]])
2226
+ sage: if latex.has_file("tikz.sty"): # optional - latex
2227
+ ....: IS._latex_()
2228
+ ...UserWarning:
2229
+ The hypergraph is drawn as a set of closed curves...
2230
+ \begin{tikzpicture}...
2231
+ \draw... -- ...;
2232
+ \draw... -- ...;
2233
+ \draw node...;
2234
+ \draw node...;
2235
+ \draw node...;
2236
+ \end{tikzpicture}
2237
+ """
2238
+ from sage.functions.trig import arctan2
2239
+
2240
+ from warnings import warn
2241
+ warn("\nThe hypergraph is drawn as a set of closed curves. The curve "
2242
+ "representing a set S goes **THROUGH** the points contained "
2243
+ "in S.\n A point which is encircled by a curve but is not located "
2244
+ "on its boundary is **NOT** included in the corresponding set.\n"
2245
+ "\n"
2246
+ "The colors are picked for readability and have no other meaning.")
2247
+
2248
+ latex.add_package_to_preamble_if_available("tikz")
2249
+
2250
+ if not latex.has_file("tikz.sty"):
2251
+ raise RuntimeError("You must have TikZ installed in order "
2252
+ "to draw a hypergraph.")
2253
+
2254
+ domain = self.ground_set()
2255
+ pos = self._spring_layout()
2256
+ tex = "\\begin{tikzpicture}[scale=3]\n"
2257
+
2258
+ colors = ["black", "red", "green", "blue", "cyan",
2259
+ "magenta", "yellow", "pink", "brown"]
2260
+ colored_sets = [(s, i) for i, S in enumerate(self.edge_coloring()) for s in S]
2261
+
2262
+ # Prints each set with its color
2263
+ for s, i in colored_sets:
2264
+ current_color = colors[i % len(colors)]
2265
+
2266
+ if len(s) == 2:
2267
+ s = list(s)
2268
+ tex += ("\\draw[color="+str(current_color)+"," +
2269
+ "line width=.1cm,opacity = .6] " +
2270
+ str(pos[s[0]])+" -- "+str(pos[s[1]])+";\n")
2271
+ continue
2272
+
2273
+ tex += ("\\draw[color="+str(current_color)+","
2274
+ "line width=.1cm,opacity = .6,"
2275
+ "line cap=round,"
2276
+ "line join=round]"
2277
+ "plot [smooth cycle,tension=1] coordinates {")
2278
+
2279
+ # Reorders the vertices of s according to their angle with the
2280
+ # "center", i.e. the vertex representing the set s
2281
+ cx, cy = pos[Set(s)]
2282
+ s = [pos[_] for _ in s]
2283
+ s = sorted(s, key=lambda x_y: arctan2(x_y[0] - cx, x_y[1] - cy))
2284
+
2285
+ for x in s:
2286
+ tex += str(x)+" "
2287
+ tex += "};\n"
2288
+
2289
+ # Prints each vertex
2290
+ for v in domain:
2291
+ tex += "\\draw node[fill,circle,scale=.5,label={90:$"+latex(v)+"$}] at "+str(pos[v])+" {};\n"
2292
+
2293
+ tex += "\\end{tikzpicture}"
2294
+ return tex
2295
+
2296
+ def is_spread(self, spread) -> bool:
2297
+ r"""
2298
+ Check whether the input is a spread for ``self``.
2299
+
2300
+ A spread of an incidence structure `(P, B)` is a subset of `B` which
2301
+ forms a partition of `P`.
2302
+
2303
+ INPUT:
2304
+
2305
+ - ``spread`` -- iterable; defines the spread
2306
+
2307
+ EXAMPLES::
2308
+
2309
+ sage: E = IncidenceStructure([[1, 2, 3], [4, 5, 6], [1, 5, 6]])
2310
+ sage: E.is_spread([[1, 2, 3], [4, 5, 6]])
2311
+ True
2312
+ sage: E.is_spread([1, 2, 3, 4, 5, 6])
2313
+ Traceback (most recent call last):
2314
+ ...
2315
+ TypeError: 'sage.rings.integer.Integer' object is not iterable
2316
+ sage: E.is_spread([[1, 2, 3, 4], [5, 6]])
2317
+ False
2318
+
2319
+ Order of blocks or of points within each block doesn't matter::
2320
+
2321
+ sage: E = IncidenceStructure([[1, 2, 3], [4, 5, 6], [1, 5, 6]])
2322
+ sage: E.is_spread([[5, 6, 4], [3, 1, 2]])
2323
+ True
2324
+
2325
+ TESTS::
2326
+
2327
+ sage: E = IncidenceStructure([])
2328
+ sage: E.is_spread([])
2329
+ True
2330
+ sage: E = IncidenceStructure([[1]])
2331
+ sage: E.is_spread([])
2332
+ False
2333
+ sage: E.is_spread([[1]])
2334
+ True
2335
+ sage: E = IncidenceStructure([[1], [1]])
2336
+ sage: E.is_spread([[1]])
2337
+ True
2338
+ """
2339
+
2340
+ points = set(self.ground_set())
2341
+ allBlocks = set(map(frozenset, self.blocks()))
2342
+ for block in spread:
2343
+ sblock = set(block)
2344
+
2345
+ if sblock not in allBlocks:
2346
+ return False
2347
+
2348
+ if not points.issuperset(sblock):
2349
+ return False
2350
+
2351
+ points.difference_update(sblock)
2352
+
2353
+ return not points
2354
+
2355
+
2356
+ from sage.misc.rest_index_of_methods import gen_rest_table_index
2357
+ __doc__ = __doc__.format(METHODS_OF_IncidenceStructure=gen_rest_table_index(IncidenceStructure))