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,1780 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ # sage.doctest: needs sage.rings.finite_rings
3
+ r"""
4
+ Orthogonal arrays (build recursive constructions)
5
+
6
+ This module implements several constructions of
7
+ :mod:`Orthogonal Arrays<sage.combinat.designs.orthogonal_arrays>`.
8
+ As their input can be complex, they all have a counterpart in the
9
+ :mod:`~sage.combinat.designs.orthogonal_arrays_find_recursive` module
10
+ that automatically computes it.
11
+
12
+ All these constructions are automatically queried when the
13
+ :func:`~sage.combinat.designs.orthogonal_arrays.orthogonal_array` function is
14
+ called.
15
+
16
+ .. csv-table::
17
+ :class: contentstable
18
+ :widths: 30, 70
19
+ :delim: |
20
+
21
+ :func:`~sage.combinat.designs.orthogonal_arrays_build_recursive.construction_3_3` | Return an `OA(k,nm+i)`.
22
+ :func:`~sage.combinat.designs.orthogonal_arrays_build_recursive.construction_3_4` | Return a `OA(k,nm+rs)`.
23
+ :func:`~sage.combinat.designs.orthogonal_arrays_build_recursive.construction_3_5` | Return an `OA(k,nm+r+s+t)`.
24
+ :func:`~sage.combinat.designs.orthogonal_arrays_build_recursive.construction_3_6` | Return a `OA(k,nm+i)`.
25
+ :func:`~sage.combinat.designs.orthogonal_arrays_build_recursive.construction_q_x` | Return an `OA(k,(q-1)*(q-x)+x+2)` using the `q-x` construction.
26
+ :func:`OA_and_oval` | Return a `OA(q+1,q)` whose blocks contains `\leq 2` zeroes in the last `q` columns.
27
+ :func:`thwart_lemma_3_5` | Return an `OA(k,nm+a+b+c+d)`.
28
+ :func:`thwart_lemma_4_1` | Return an `OA(k,nm+4(n-2))`.
29
+ :func:`three_factor_product` | Return an `OA(k+1,n_1n_2n_3)`.
30
+ :func:`brouwer_separable_design` | Return a `OA(k,t(q^2+q+1)+x)` using Brouwer's result on separable designs.
31
+
32
+ Functions
33
+ ---------
34
+ """
35
+ from itertools import repeat
36
+ from .orthogonal_arrays import orthogonal_array, wilson_construction, is_orthogonal_array
37
+
38
+
39
+ def construction_3_3(k, n, m, i, explain_construction=False):
40
+ r"""
41
+ Return an `OA(k,nm+i)`.
42
+
43
+ This is Wilson's construction with `i` truncated columns of size 1 and such
44
+ that a block `B_0` of the incomplete OA intersects all truncated columns. As
45
+ a consequence, all other blocks intersect only `0` or `1` of the last `i`
46
+ columns. This allow to consider the block `B_0` only up to its first `k`
47
+ coordinates and then use a `OA(k,i)` instead of a `OA(k,m+i) - i.OA(k,1)`.
48
+
49
+ This is construction 3.3 from [AC07]_.
50
+
51
+ INPUT:
52
+
53
+ - ``k``, ``n``, ``m``, ``i`` -- integers such that the following designs are
54
+ available: `OA(k,n)`, `OA(k,m)`, `OA(k,m+1)`, `OA(k,r)`
55
+
56
+ - ``explain_construction`` -- boolean; return a string describing
57
+ the construction
58
+
59
+ .. SEEALSO::
60
+
61
+ :func:`~sage.combinat.designs.orthogonal_arrays_find_recursive.find_construction_3_3`
62
+
63
+ EXAMPLES::
64
+
65
+ sage: from sage.combinat.designs.orthogonal_arrays_find_recursive import find_construction_3_3
66
+ sage: from sage.combinat.designs.orthogonal_arrays_build_recursive import construction_3_3
67
+ sage: from sage.combinat.designs.orthogonal_arrays import is_orthogonal_array
68
+ sage: k = 11; n = 177
69
+ sage: is_orthogonal_array(construction_3_3(*find_construction_3_3(k,n)[1]),k,n,2) # needs sage.schemes
70
+ True
71
+
72
+ sage: print(designs.orthogonal_arrays.explain_construction(9,91)) # needs sage.schemes
73
+ Construction 3.3 with n=11,m=8,i=3 from:
74
+ Julian R. Abel, Nicholas Cavenagh
75
+ Concerning eight mutually orthogonal latin squares,
76
+ Vol. 15, n.3, pp. 255-261,
77
+ Journal of Combinatorial Designs, 2007
78
+ """
79
+ from .orthogonal_arrays import wilson_construction, OA_relabel, incomplete_orthogonal_array
80
+ if explain_construction:
81
+ return (("Construction 3.3 with n={},m={},i={} from:\n"
82
+ " Julian R. Abel, Nicholas Cavenagh\n" +
83
+ " Concerning eight mutually orthogonal latin squares,\n" +
84
+ " Vol. 15, n.3, pp. 255-261,\n" +
85
+ " Journal of Combinatorial Designs, 2007").format(n,m,i))
86
+
87
+ # Builds an OA(k+i,n) containing a block [0]*(k+i)
88
+ OA = incomplete_orthogonal_array(k+i,n,(1,))
89
+ OA = [[(x+1) % n for x in B] for B in OA]
90
+
91
+ # Truncated version
92
+ OA = [B[:k]+[0 if x == 0 else None for x in B[k:]] for B in OA]
93
+
94
+ OA = wilson_construction(OA,k,n,m,[1]*i,check=False)[:-i]
95
+ matrix = [list(range(m)) + list(range(n*m, n*m+i))] * k
96
+ OA.extend(OA_relabel(orthogonal_array(k,m+i),k,m+i,matrix=matrix))
97
+ assert is_orthogonal_array(OA,k,n*m+i)
98
+ return OA
99
+
100
+
101
+ def construction_3_4(k, n, m, r, s, explain_construction=False):
102
+ r"""
103
+ Return a `OA(k,nm+rs)`.
104
+
105
+ This is Wilson's construction applied to a truncated `OA(k+r+1,n)` with `r`
106
+ columns of size `1` and one column of size `s`.
107
+
108
+ The unique elements of the `r` truncated columns are picked so that a block
109
+ `B_0` contains them all.
110
+
111
+ - If there exists an `OA(k,m+r+1)` the column of size `s` is truncated in
112
+ order to intersect `B_0`.
113
+
114
+ - Otherwise, if there exists an `OA(k,m+r)`, the last column must not
115
+ intersect `B_0`
116
+
117
+ This is construction 3.4 from [AC07]_.
118
+
119
+ INPUT:
120
+
121
+ - ``k``, ``n``, ``m``, ``r``, ``s`` -- integers; we assume that `s<n` and
122
+ `1\leq r,s`
123
+
124
+ The following designs must be available: `OA(k,n)`, `OA(k,m)`,
125
+ `OA(k,m+1)`, `OA(k,m+2)`, `OA(k,s)`. Additionally, it requires either a
126
+ `OA(k,m+r)` or a `OA(k,m+r+1)`.
127
+
128
+ - ``explain_construction`` -- boolean; return a string describing
129
+ the construction
130
+
131
+ .. SEEALSO::
132
+
133
+ :func:`~sage.combinat.designs.orthogonal_arrays_find_recursive.find_construction_3_4`
134
+
135
+ EXAMPLES::
136
+
137
+ sage: from sage.combinat.designs.orthogonal_arrays_find_recursive import find_construction_3_4
138
+ sage: from sage.combinat.designs.orthogonal_arrays_build_recursive import construction_3_4
139
+ sage: from sage.combinat.designs.orthogonal_arrays import is_orthogonal_array
140
+ sage: k = 8; n = 196
141
+ sage: is_orthogonal_array(construction_3_4(*find_construction_3_4(k,n)[1]),k,n,2) # needs sage.schemes
142
+ True
143
+
144
+ sage: print(designs.orthogonal_arrays.explain_construction(8,164)) # needs sage.schemes
145
+ Construction 3.4 with n=23,m=7,r=2,s=1 from:
146
+ Julian R. Abel, Nicholas Cavenagh
147
+ Concerning eight mutually orthogonal latin squares,
148
+ Vol. 15, n.3, pp. 255-261,
149
+ Journal of Combinatorial Designs, 2007
150
+ """
151
+ if explain_construction:
152
+ return ("Construction 3.4 with n={},m={},r={},s={} from:\n" +
153
+ " Julian R. Abel, Nicholas Cavenagh\n" +
154
+ " Concerning eight mutually orthogonal latin squares,\n" +
155
+ " Vol. 15, n.3, pp. 255-261,\n" +
156
+ " Journal of Combinatorial Designs, 2007").format(n,m,r,s)
157
+
158
+ from .orthogonal_arrays import wilson_construction, OA_relabel
159
+ assert s < n
160
+ master_design = orthogonal_array(k+r+1,n)
161
+
162
+ # Defines the first k+r columns of the matrix of labels
163
+ matrix = [list(range(n))] * k + [[None]*n]*(r) + [[None]*n]
164
+ B0 = master_design[0]
165
+ for i in range(k,k+r):
166
+ matrix[i][B0[i]] = 0
167
+
168
+ # Last column
169
+ if orthogonal_array(k, m+r ,existence=True):
170
+ last_group = [x for x in range(s+1) if x != B0[-1]][:s]
171
+ elif orthogonal_array(k,m+r+1,existence=True):
172
+ last_group = [x for x in range(s+1) if x != B0[-1]][:s-1] + [B0[-1]]
173
+ else:
174
+ raise RuntimeError
175
+
176
+ for i, x in enumerate(last_group):
177
+ matrix[-1][x] = i
178
+
179
+ OA = OA_relabel(master_design,k+r+1,n, matrix=matrix)
180
+ OA = wilson_construction(OA,k,n,m,[1]*r+[s],check=False)
181
+ return OA
182
+
183
+
184
+ def construction_3_5(k, n, m, r, s, t, explain_construction=False):
185
+ r"""
186
+ Return an `OA(k,nm+r+s+t)`.
187
+
188
+ This is exactly Wilson's construction with three truncated groups
189
+ except we make sure that all blocks have size `>k`, so we don't
190
+ need a `OA(k,m+0)` but only `OA(k,m+1)`, `OA(k,m+2)` ,`OA(k,m+3)`.
191
+
192
+ This is construction 3.5 from [AC07]_.
193
+
194
+ INPUT:
195
+
196
+ - ``k``, ``n``, ``m`` -- integers
197
+
198
+ - ``r``, ``s``, ``t`` -- integers; sizes of the three truncated groups,
199
+ such that `r\leq s` and `(q-r-1)(q-s) \geq (q-s-1)*(q-r)`
200
+
201
+ - ``explain_construction`` -- boolean; return a string describing
202
+ the construction
203
+
204
+ The following designs must be available : `OA(k,n)`, `OA(k,r)`, `OA(k,s)`,
205
+ `OA(k,t)`, `OA(k,m+1)`, `OA(k,m+2)`, `OA(k,m+3)`.
206
+
207
+ .. SEEALSO::
208
+
209
+ :func:`~sage.combinat.designs.orthogonal_arrays_find_recursive.find_construction_3_5`
210
+
211
+ EXAMPLES::
212
+
213
+ sage: from sage.combinat.designs.orthogonal_arrays_find_recursive import find_construction_3_5
214
+ sage: from sage.combinat.designs.orthogonal_arrays_build_recursive import construction_3_5
215
+ sage: from sage.combinat.designs.orthogonal_arrays import is_orthogonal_array
216
+ sage: k=8;n=111
217
+ sage: is_orthogonal_array(construction_3_5(*find_construction_3_5(k,n)[1]),k,n,2) # needs sage.schemes
218
+ True
219
+
220
+ sage: print(designs.orthogonal_arrays.explain_construction(8,90)) # needs sage.schemes
221
+ Construction 3.5 with n=11,m=6,r=8,s=8,t=8 from:
222
+ Julian R. Abel, Nicholas Cavenagh
223
+ Concerning eight mutually orthogonal latin squares,
224
+ Vol. 15, n.3, pp. 255-261,
225
+ Journal of Combinatorial Designs, 2007
226
+ """
227
+ from .orthogonal_arrays import wilson_construction, OA_relabel
228
+ assert r <= s
229
+ q = n
230
+ assert (q-r-1)*(q-s) >= (q-s-1)*(q-r)
231
+
232
+ if explain_construction:
233
+ return (("Construction 3.5 with n={},m={},r={},s={},t={} from:\n"
234
+ " Julian R. Abel, Nicholas Cavenagh\n" +
235
+ " Concerning eight mutually orthogonal latin squares,\n" +
236
+ " Vol. 15, n.3, pp. 255-261,\n" +
237
+ " Journal of Combinatorial Designs, 2007").format(n,m,r,s,t))
238
+
239
+ master_design = orthogonal_array(k+3,q)
240
+
241
+ # group k+1 has cardinality r
242
+ # group k+2 has cardinality s
243
+ # group k+3 has cardinality t
244
+
245
+ # Taking q-s blocks going through 0 in the last block
246
+ blocks_crossing_0 = [B[-3:] for B in master_design if B[-1] == 0][:q-s]
247
+
248
+ # defining the undeleted points of the groups k+1,k+2
249
+ group_k_1 = [x[0] for x in blocks_crossing_0]
250
+ group_k_1 = [x for x in range(q) if x not in group_k_1][:r]
251
+
252
+ group_k_2 = [x[1] for x in blocks_crossing_0]
253
+ group_k_2 = [x for x in range(q) if x not in group_k_2][:s]
254
+
255
+ # All blocks that have a deleted point in groups k+1 and k+2 MUST contain a
256
+ # point in group k+3
257
+ group_k_3 = [B[-1] for B in master_design if B[-3] not in group_k_1 and B[-2] not in group_k_2]
258
+ group_k_3 = list(set(group_k_3))
259
+ assert len(group_k_3) <= t
260
+ group_k_3.extend(x for x in range(q) if x not in group_k_3)
261
+ group_k_3 = group_k_3[:t]
262
+
263
+ # Relabelling the OA
264
+ r1 = [None]*q
265
+ r2 = [None]*q
266
+ r3 = [None]*q
267
+ for i, x in enumerate(group_k_1):
268
+ r1[x] = i
269
+ for i, x in enumerate(group_k_2):
270
+ r2[x] = i
271
+ for i, x in enumerate(group_k_3):
272
+ r3[x] = i
273
+
274
+ OA = OA_relabel(master_design, k+3,q, matrix=[list(range(q))]*k+[r1,r2,r3])
275
+ OA = wilson_construction(OA,k,q,m,[r,s,t], check=False)
276
+ return OA
277
+
278
+
279
+ def construction_3_6(k, n, m, i, explain_construction=False):
280
+ r"""
281
+ Return a `OA(k,nm+i)`.
282
+
283
+ This is Wilson's construction with `r` columns of order `1`, in which each
284
+ block intersects at most two truncated columns. Such a design exists when
285
+ `n` is a prime power and is returned by :func:`OA_and_oval`.
286
+
287
+ INPUT:
288
+
289
+ - ``k``, ``n``, ``m``, ``i`` -- integers; `n` must be a prime power. The
290
+ following designs must be available: `OA(k+r,q)`, `OA(k,m)`, `OA(k,m+1)`,
291
+ `OA(k,m+2)`
292
+
293
+ - ``explain_construction`` -- boolean; return a string describing
294
+ the construction
295
+
296
+ This is construction 3.6 from [AC07]_.
297
+
298
+ .. SEEALSO::
299
+
300
+ - :func:`~sage.combinat.designs.orthogonal_arrays_find_recursive.find_construction_3_6`
301
+
302
+ - :func:`OA_and_oval`
303
+
304
+ EXAMPLES::
305
+
306
+ sage: from sage.combinat.designs.orthogonal_arrays_find_recursive import find_construction_3_6
307
+ sage: from sage.combinat.designs.orthogonal_arrays_build_recursive import construction_3_6
308
+ sage: from sage.combinat.designs.orthogonal_arrays import is_orthogonal_array
309
+ sage: k=8;n=95
310
+ sage: is_orthogonal_array(construction_3_6(*find_construction_3_6(k,n)[1]),k,n,2) # needs sage.schemes
311
+ True
312
+
313
+ sage: print(designs.orthogonal_arrays.explain_construction(10,756)) # needs sage.schemes
314
+ Construction 3.6 with n=16,m=47,i=4 from:
315
+ Julian R. Abel, Nicholas Cavenagh
316
+ Concerning eight mutually orthogonal latin squares,
317
+ Vol. 15, n.3, pp. 255-261,
318
+ Journal of Combinatorial Designs, 2007
319
+ """
320
+ if explain_construction:
321
+ return (("Construction 3.6 with n={},m={},i={} from:\n"
322
+ " Julian R. Abel, Nicholas Cavenagh\n" +
323
+ " Concerning eight mutually orthogonal latin squares,\n" +
324
+ " Vol. 15, n.3, pp. 255-261,\n" +
325
+ " Journal of Combinatorial Designs, 2007").format(n,m,i))
326
+
327
+ from .orthogonal_arrays import wilson_construction
328
+ OA = OA_and_oval(n)
329
+ OA = [B[:k+i] for B in OA]
330
+ OA = [B[:k] + [x if x == 0 else None for x in B[k:]] for B in OA]
331
+ OA = wilson_construction(OA,k,n,m,[1]*i)
332
+ assert is_orthogonal_array(OA,k,n*m+i)
333
+ return OA
334
+
335
+
336
+ def OA_and_oval(q, *, solver=None, integrality_tolerance=1e-3):
337
+ r"""
338
+ Return a `OA(q+1,q)` whose blocks contains `\leq 2` zeroes in the last `q`
339
+ columns.
340
+
341
+ This `OA` is build from a projective plane of order `q`, in which there
342
+ exists an oval `O` of size `q+1` (i.e. a set of `q+1` points no three of
343
+ which are [colinear/contained in a common set of the projective plane]).
344
+
345
+ Removing an element `x\in O` and all sets that contain it, we obtain a
346
+ `TD(q+1,q)` in which `O` intersects all columns except one. As `O` is an
347
+ oval, no block of the `TD` intersects it more than twice.
348
+
349
+ INPUT:
350
+
351
+ - ``q`` -- a prime power
352
+
353
+ - ``solver`` -- (default: ``None``) specify a Mixed Integer Linear
354
+ Programming (MILP) solver to be used. If set to ``None``, the default one
355
+ is used. For more information on MILP solvers and which default solver is
356
+ used, see the method :meth:`solve
357
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
358
+ :class:`MixedIntegerLinearProgram
359
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
360
+
361
+ - ``integrality_tolerance`` -- parameter for use with MILP solvers over an
362
+ inexact base ring; see :meth:`MixedIntegerLinearProgram.get_values`
363
+
364
+ .. NOTE::
365
+
366
+ This function is called by :func:`construction_3_6`, an implementation
367
+ of Construction 3.6 from [AC07]_.
368
+
369
+ EXAMPLES::
370
+
371
+ sage: from sage.combinat.designs.orthogonal_arrays_build_recursive import OA_and_oval
372
+ sage: _ = OA_and_oval
373
+ """
374
+ from sage.arith.misc import is_prime_power
375
+ from sage.combinat.designs.block_design import projective_plane
376
+ from .orthogonal_arrays import OA_relabel
377
+
378
+ assert is_prime_power(q)
379
+ B = projective_plane(q, check=False)
380
+
381
+ # We compute the oval with a linear program
382
+ from sage.numerical.mip import MixedIntegerLinearProgram
383
+ p = MixedIntegerLinearProgram(solver=solver)
384
+ b = p.new_variable(binary=True)
385
+ V = B.ground_set()
386
+ p.add_constraint(p.sum([b[i] for i in V]) == q+1)
387
+ for bl in B:
388
+ p.add_constraint(p.sum([b[i] for i in bl]) <= 2)
389
+ p.solve()
390
+ b = p.get_values(b, convert=bool, tolerance=integrality_tolerance)
391
+ oval = [x for x,i in b.items() if i]
392
+ assert len(oval) == q+1
393
+
394
+ # We remove one element from the oval
395
+ x = oval.pop()
396
+ oval.sort()
397
+
398
+ # We build the TD by relabelling the point set, and removing those which
399
+ # contain x.
400
+ r = {}
401
+
402
+ # Make sure that the first set containing x in B is the one
403
+ # which contains no other oval point
404
+ B = sorted(B, key=lambda b: any(xx in oval for xx in b))
405
+
406
+ BB = []
407
+ for b in B:
408
+ if x in b:
409
+ for xx in b:
410
+ if xx == x:
411
+ continue
412
+ r[xx] = len(r)
413
+ else:
414
+ BB.append(b)
415
+
416
+ assert len(r) == (q+1)*q # all points except x have an image
417
+ assert len(set(r.values())) == len(r) # the images are different
418
+
419
+ # Relabelling/sorting the blocks and the oval
420
+ BB = [[r[xx] for xx in b] for b in BB]
421
+ oval = [r[xx] for xx in oval]
422
+
423
+ for b in BB:
424
+ b.sort()
425
+ oval.sort()
426
+
427
+ # Turning the TD into an OA
428
+ BB = [[xx % q for xx in b] for b in BB]
429
+ oval = [xx % q for xx in oval]
430
+ assert len(oval) == q
431
+
432
+ # We relabel the "oval" as relabelled as [0,...,0]
433
+ OA = OA_relabel(BB+([[0]+oval]),q+1,q,blocks=[[0]+oval])
434
+ OA = [[(x+1) % q for x in B] for B in OA]
435
+ OA.remove([0]*(q+1))
436
+
437
+ assert all(sum([xx == 0 for xx in b[1:]]) <= 2 for b in OA)
438
+ return OA
439
+
440
+
441
+ def construction_q_x(k, q, x, check=True, explain_construction=False):
442
+ r"""
443
+ Return an `OA(k,(q-1)*(q-x)+x+2)` using the `q-x` construction.
444
+
445
+ Let `v=(q-1)*(q-x)+x+2`. If there exists a projective plane of order `q`
446
+ (e.g. when `q` is a prime power) and `0<x<q` then there exists a
447
+ `(v-1,\{q-x-1,q-x+1\})`-GDD of type `(q-1)^{q-x}(x+1)^1` (see [Greig99]_ or
448
+ Theorem 2.50, section IV.2.3 of [DesignHandbook]_). By adding to the ground
449
+ set one point contained in all groups of the GDD, one obtains a
450
+ `(v,\{q-x-1,q-x+1,q,x+2\})`-PBD with exactly one set of size `x+2`.
451
+
452
+ Thus, assuming that we have the following:
453
+
454
+ - `OA(k,q-x-1)-(q-x-1).OA(k,1)`
455
+ - `OA(k,q-x+1)-(q-x+1).OA(k,1)`
456
+ - `OA(k,q)-q.OA(k,1)`
457
+ - `OA(k,x+2)`
458
+
459
+ Then we can build from the PBD an `OA(k,v)`.
460
+
461
+ Construction of the PBD (shared by Julian R. Abel):
462
+
463
+ Start with a resolvable `(q^2,q,1)`-BIBD and put the points into a `q\times q`
464
+ array so that rows form a parallel class and columns form another.
465
+
466
+ Now delete:
467
+
468
+ - All `x(q-1)` points from the first `x` columns and not in the first
469
+ row
470
+
471
+ - All `q-x` points in the last `q-x` columns AND the first row.
472
+
473
+ Then add a point `p_1` to the blocks that are rows. Add a second point
474
+ `p_2` to the `q-x` blocks that are columns of size `q-1`, plus the first
475
+ row of size `x+1`.
476
+
477
+ INPUT:
478
+
479
+ - ``k``, ``q``, ``x`` -- integers such that `0<x<q` and such that Sage can
480
+ build:
481
+
482
+ - A projective plane of order `q`
483
+ - `OA(k,q-x-1)-(q-x-1).OA(k,1)`
484
+ - `OA(k,q-x+1)-(q-x+1).OA(k,1)`
485
+ - `OA(k,q)-q.OA(k,1)`
486
+ - `OA(k,x+2)`
487
+
488
+ - ``check`` -- boolean (default: ``True``); whether to check that output is
489
+ correct before returning it. As this is expected to be useless, you may
490
+ want to disable it whenever you want speed.
491
+
492
+ - ``explain_construction`` -- boolean; return a string describing
493
+ the construction
494
+
495
+ .. SEEALSO::
496
+
497
+ - :func:`~sage.combinat.designs.orthogonal_arrays_find_recursive.find_q_x`
498
+ - :func:`~sage.combinat.designs.block_design.projective_plane`
499
+ - :func:`~sage.combinat.designs.orthogonal_arrays.orthogonal_array`
500
+ - :func:`~sage.combinat.designs.orthogonal_arrays.OA_from_PBD`
501
+
502
+ EXAMPLES::
503
+
504
+ sage: from sage.combinat.designs.orthogonal_arrays_build_recursive import construction_q_x
505
+ sage: _ = construction_q_x(9,16,6) # needs sage.schemes
506
+
507
+ sage: print(designs.orthogonal_arrays.explain_construction(9,158)) # needs sage.schemes
508
+ (q-x)-construction with q=16,x=6 from:
509
+ Malcolm Greig,
510
+ Designs from projective planes and PBD bases,
511
+ vol. 7, num. 5, pp. 341--374,
512
+ Journal of Combinatorial Designs, 1999
513
+
514
+ REFERENCES:
515
+
516
+ .. [Greig99] Designs from projective planes and PBD bases
517
+ Malcolm Greig
518
+ Journal of Combinatorial Designs
519
+ vol. 7, num. 5, pp. 341--374
520
+ 1999
521
+ """
522
+ from sage.combinat.designs.orthogonal_arrays import OA_from_PBD
523
+ from sage.combinat.designs.orthogonal_arrays import incomplete_orthogonal_array
524
+
525
+ if explain_construction:
526
+ return ("(q-x)-construction with q={},x={} from:\n" +
527
+ " Malcolm Greig,\n" +
528
+ " Designs from projective planes and PBD bases,\n" +
529
+ " vol. 7, num. 5, pp. 341--374,\n" +
530
+ " Journal of Combinatorial Designs, 1999").format(q, x)
531
+
532
+ n = (q-1)*(q-x)+x+2
533
+
534
+ # We obtain the qxq matrix from a OA(q,q)-q.OA(1,q). We will need to add
535
+ # blocks corresponding to the rows/columns
536
+ OA = incomplete_orthogonal_array(q,q,(1,)*q)
537
+ TD = [[i*q+xx for i, xx in enumerate(B)] for B in OA]
538
+
539
+ # Add rows, extended with p1 and p2
540
+ p1 = q**2
541
+ p2 = p1 + 1
542
+ TD.extend([ii*q + i for ii in range(q)] + [p1] for i in range(1, q))
543
+ TD.append([ii*q for ii in range(q)] + [p1, p2])
544
+
545
+ # Add Columns. We do not add some columns which would have size 1 after we
546
+ # delete points.
547
+ #
548
+ # TD.extend([range(i*q,(i+1)*q) for i in range(x)])
549
+ TD.extend(list(range(i*q,(i+1)*q))+[p2] for i in range(x,q))
550
+
551
+ points_to_delete = set([i*q+j for i in range(x) for j in range(1,q)]+[i*q for i in range(x,q)])
552
+ points_to_keep = set(range(q**2+2))-points_to_delete
553
+ relabel = {i:j for j,i in enumerate(points_to_keep)}
554
+
555
+ # PBD is a (n,[q,q-x-1,q-x+1,x+2])-PBD
556
+ PBD = [[relabel[xx] for xx in B if xx not in points_to_delete] for B in TD]
557
+
558
+ # Taking the unique block of size x+2
559
+ assert list(map(len,PBD)).count(x+2) == 1
560
+ for B in PBD:
561
+ if len(B) == x+2:
562
+ break
563
+
564
+ # We call OA_from_PBD without the block of size x+2 as there may not exist a
565
+ # OA(k,x+2)-(x+2).OA(k,1)
566
+ PBD.remove(B)
567
+ OA = OA_from_PBD(k,(q-1)*(q-x)+x+2,PBD,check=False)
568
+
569
+ # Filling the hole
570
+ for xx in B:
571
+ OA.remove([xx]*k)
572
+
573
+ for BB in orthogonal_array(k, x+2):
574
+ OA.append([B[x] for x in BB])
575
+
576
+ if check:
577
+ assert is_orthogonal_array(OA,k,n,2)
578
+
579
+ return OA
580
+
581
+
582
+ def thwart_lemma_3_5(k, n, m, a, b, c, d=0, complement=False, explain_construction=False):
583
+ r"""
584
+ Return an `OA(k,nm+a+b+c+d)`.
585
+
586
+ *(When `d=0`)*
587
+
588
+ According to [Thwarts]_ when `n` is a prime power and `a+b+c\leq n+1`, one
589
+ can build an `OA(k+3,n)` with three truncated columns of sizes `a,b,c` in
590
+ such a way that all blocks have size `\leq k+2`.
591
+
592
+ (in order to build a `OA(k,nm+a+b+c)` the following designs must also exist:
593
+ `OA(k,a)`, `OA(k,b)`, `OA(k,c)`, `OA(k,m+0)`, `OA(k,m+1)`, `OA(k,m+2)`)
594
+
595
+ Considering the complement of each truncated column, it is also possible to
596
+ build an `OA(k+3,n)` with three truncated columns of sizes `a,b,c` in such a
597
+ way that all blocks have size `>k` whenever `(n-a)+(n-b)+(n-c)\leq n+1`.
598
+
599
+ (in order to build a `OA(k,nm+a+b+c)` the following designs must also exist:
600
+ `OA(k,a)`, `OA(k,b)`, `OA(k,c)`, `OA(k,m+1)`, `OA(k,m+2)`, `OA(k,m+3)`)
601
+
602
+ Here is the proof of Lemma 3.5 from [Thwarts]_ enriched with explanations
603
+ from Julian R. Abel:
604
+
605
+ For any prime power `n` one can build `k-1` MOLS by associating to every
606
+ nonzero `x\in \mathbb F_n` the latin square:
607
+
608
+ .. MATH::
609
+
610
+ M_x(i,j) = i+x*j \text{ where }i,j\in \mathbb F_n
611
+
612
+ In particular `M_1(i,j)=i+j`, whose `n` columns and lines are indexed by
613
+ the elements of `\mathbb F_n`. If we order the elements of `\mathbb F_n`
614
+ as `0,1,...,n-1,x+0,...,x+n-1,x^2+0,...` and reorder the columns
615
+ and lines of `M_1` accordingly, the top-left `a\times b` squares
616
+ contains at most `a+b-1` distinct symbols.
617
+
618
+ *(When* `d\neq 0` *)*
619
+
620
+ If there exists an `OA(k+3,n)` with three truncated columns of sizes `a,b,c`
621
+ in such a way that all blocks have size `\leq k+2`, by truncating
622
+ arbitrarily another column to size `d` one obtains an `OA` with 4 truncated
623
+ columns whose blocks miss at least one value. Thus, following the proof
624
+ again one can build an `OA(k+4)` with four truncated columns of sizes
625
+ `a,b,c,d` with blocks of size `\leq k+3`.
626
+
627
+ (in order to build a `OA(k,nm+a+b+c+d)` the following designs must also
628
+ exist: `OA(k,a)`, `OA(k,b)`, `OA(k,c)`, `OA(k,d)`, `OA(k,m+0)`, `OA(k,m+1)`,
629
+ `OA(k,m+2)`, `OA(k,m+3)`)
630
+
631
+ As before, this also shows that one can build an `OA(k+4,n)` with four
632
+ truncated columns of sizes `a,b,c,d` in such a way that all blocks have size
633
+ `>k` whenever `(n-a)+(n-b)+(n-c)\leq n+1`
634
+
635
+ (in order to build a `OA(k,nm+a+b+c+d)` the following designs must also
636
+ exist: `OA(k,n-a)`, `OA(k,n-b)`, `OA(k,n-c)`, `OA(k,d)`, `OA(k,m+1)`,
637
+ `OA(k,m+2)`, `OA(k,m+3)`, `OA(k,m+4)`)
638
+
639
+ INPUT:
640
+
641
+ - ``k``, ``n``, ``m``, ``a``, ``b``, ``c``, ``d`` -- integers which must
642
+ satisfy the constraints above. In particular, `a+b+c\leq n+1` must hold
643
+ By default, `d=0`.
644
+
645
+ - ``complement`` -- boolean; whether to complement the sets, i.e. follow
646
+ the `n-a,n-b,n-c` variant described above
647
+
648
+ - ``explain_construction`` -- boolean; return a string describing
649
+ the construction
650
+
651
+ .. SEEALSO::
652
+
653
+ - :func:`~sage.combinat.designs.orthogonal_arrays_find_recursive.find_thwart_lemma_3_5`
654
+
655
+ EXAMPLES::
656
+
657
+ sage: from sage.combinat.designs.orthogonal_arrays_build_recursive import thwart_lemma_3_5
658
+ sage: from sage.combinat.designs.designs_pyx import is_orthogonal_array
659
+ sage: OA = thwart_lemma_3_5(6,23,7,5,7,8) # needs sage.schemes
660
+ sage: is_orthogonal_array(OA,6,23*7+5+7+8,2) # needs sage.schemes
661
+ True
662
+
663
+ sage: print(designs.orthogonal_arrays.explain_construction(10,408)) # needs sage.schemes
664
+ Lemma 4.1 with n=13,m=28 from:
665
+ Charles J.Colbourn, Jeffrey H. Dinitz, Mieczyslaw Wojtas,
666
+ Thwarts in transversal designs,
667
+ Designs, Codes and Cryptography 5, no. 3 (1995): 189-197.
668
+
669
+ With sets of parameters from [Thwarts]_::
670
+
671
+ sage: l = [
672
+ ....: [11, 27, 78, 16, 17, 25, 0],
673
+ ....: [12, 19, 208, 11, 13, 16, 0],
674
+ ....: [12, 19, 208, 13, 13, 16, 0],
675
+ ....: [10, 13, 78, 9, 9, 13, 1],
676
+ ....: [10, 13, 79, 9, 9, 13, 1]]
677
+ sage: for k,n,m,a,b,c,d in l: # not tested -- too long
678
+ ....: OA = thwart_lemma_3_5(k,n,m,a,b,c,d,complement=True)
679
+ ....: assert is_orthogonal_array(OA,k,n*m+a+b+c+d,verbose=True)
680
+
681
+ sage: print(designs.orthogonal_arrays.explain_construction(10,1046)) # needs sage.schemes
682
+ Lemma 3.5 with n=13,m=79,a=9,b=1,c=0,d=9 from:
683
+ Charles J.Colbourn, Jeffrey H. Dinitz, Mieczyslaw Wojtas,
684
+ Thwarts in transversal designs,
685
+ Designs, Codes and Cryptography 5, no. 3 (1995): 189-197.
686
+
687
+ REFERENCE:
688
+
689
+ .. [Thwarts] Thwarts in transversal designs
690
+ Charles J.Colbourn, Jeffrey H. Dinitz, Mieczyslaw Wojtas.
691
+ Designs, Codes and Cryptography 5, no. 3 (1995): 189-197.
692
+ """
693
+ from sage.arith.misc import is_prime_power
694
+ from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF
695
+
696
+ if complement:
697
+ a,b,c = n-a,n-b,n-c
698
+
699
+ if explain_construction:
700
+ return ("Lemma 3.5 with n={},m={},a={},b={},c={},d={} from:\n" +
701
+ " Charles J.Colbourn, Jeffrey H. Dinitz, Mieczyslaw Wojtas,\n" +
702
+ " Thwarts in transversal designs,\n" +
703
+ " Designs, Codes and Cryptography 5, no. 3 (1995): 189-197.").format(n,m,a,b,c,d)
704
+
705
+ assert is_prime_power(n), "n(={}) must be a prime power".format(n)
706
+ assert a <= n and b <= n and c <= n and d <= n, "a,b,c,d (={},{},{},{}) must be <=n(={})".format(a,b,c,d,n)
707
+ assert a+b+c <= n+1, "{}={}+{}+{}=a+b+c>n+1={}+1 violates the assumptions".format(a+b+c,a,b,c,n)
708
+ assert k+3+bool(d) <= n+1, "There exists no OA({},{}).".format(k+3+bool(d),n)
709
+ G = GF(n,prefix='x')
710
+ G_set = sorted(G) # sorted by lexicographic order, G[1] = 1
711
+ assert G_set[0] == G.zero() and G_set[1] == G.one(), "problem with the ordering of {}".format(G)
712
+ G_to_int = {v:i for i,v in enumerate(G_set)}
713
+
714
+ # Builds an OA(n+1,n) whose last n-1 columns are
715
+ #
716
+ # \forall x \in G and x!=0, C_x(i,j) = i+x*j
717
+ #
718
+ # (only the necessary columns are built)
719
+ OA = [[G_to_int[i+x*j] for i in G_set for j in G_set] for x in G_set[1:k+2+bool(d)]]
720
+ # Adding the first two trivial columns
721
+ OA.insert(0,[j for i in range(n) for j in range(n)])
722
+ OA.insert(0,[i for i in range(n) for j in range(n)])
723
+ OA = sorted(zip(*OA))
724
+
725
+ # Moves the first three columns to the end
726
+ OA = [list(B[3:]+B[:3]) for B in OA]
727
+
728
+ # Set of values in the axb square
729
+ third_complement = set(B[-1] for B in OA if B[-3] < a and B[-2] < b)
730
+
731
+ assert n - len(third_complement) >= c
732
+
733
+ # The keepers
734
+ first_set = list(range(a))
735
+ second_set = list(range(b))
736
+ third_set = [x for x in range(n) if x not in third_complement][:c]
737
+
738
+ last_sets = [first_set, second_set, third_set]
739
+
740
+ if complement:
741
+ last_sets = [set(range(n)).difference(s) for s in last_sets]
742
+
743
+ sizes = [len(_) for _ in last_sets]
744
+ last_sets_dict = [{v:i for i,v in enumerate(s)} for s in last_sets]
745
+
746
+ # Truncating the OA
747
+ for i,D in enumerate(last_sets_dict):
748
+ kk = len(OA[0])-3+i
749
+ for R in OA:
750
+ R[kk] = D[R[kk]] if R[kk] in D else None
751
+
752
+ if d:
753
+ for R in OA:
754
+ if R[-4] >= d:
755
+ R[-4] = None
756
+ sizes.insert(0,d)
757
+
758
+ return wilson_construction(OA,k,n,m,sizes, check=False)
759
+
760
+
761
+ def thwart_lemma_4_1(k, n, m, explain_construction=False):
762
+ r"""
763
+ Return an `OA(k,nm+4(n-2))`.
764
+
765
+ Implements Lemma 4.1 from [Thwarts]_.
766
+
767
+ If `n\equiv 0,1\pmod{3}` is a prime power, then there exists a truncated
768
+ `OA(n+1,n)` whose last four columns have size `n-2` and intersect every
769
+ block on `1,3` or `4` values. Consequently, if there exists an
770
+ `OA(k,m+1)`, `OA(k,m+3)`, `OA(k,m+4)` and a `OA(k,n-2)` then there
771
+ exists an `OA(k,nm+4(n-2)`
772
+
773
+ Proof: form the transversal design by removing one point of the
774
+ `AG(2,3)` (Affine Geometry) contained in the Desarguesian Projective
775
+ Plane `PG(2,n)`.
776
+
777
+ The affine geometry on 9 points contained in the projective geometry
778
+ `PG(2,n)` is given explicitly in [OS64]_ (Thanks to Julian R. Abel for
779
+ finding the reference!).
780
+
781
+ INPUT:
782
+
783
+ - ``k``, ``n``, ``m`` -- integers
784
+
785
+ - ``explain_construction`` -- boolean; return a string describing
786
+ the construction
787
+
788
+ .. SEEALSO::
789
+
790
+ - :func:`~sage.combinat.designs.orthogonal_arrays_find_recursive.find_thwart_lemma_4_1`
791
+
792
+ EXAMPLES::
793
+
794
+ sage: print(designs.orthogonal_arrays.explain_construction(10,408)) # needs sage.schemes
795
+ Lemma 4.1 with n=13,m=28 from:
796
+ Charles J.Colbourn, Jeffrey H. Dinitz, Mieczyslaw Wojtas,
797
+ Thwarts in transversal designs,
798
+ Designs, Codes and Cryptography 5, no. 3 (1995): 189-197.
799
+
800
+
801
+ REFERENCES:
802
+
803
+ .. [OS64] Finite projective planes with affine subplanes,
804
+ T. G. Ostrom and F. A. Sherk.
805
+ Canad. Math. Bull vol7 num.4 (1964)
806
+ """
807
+ from sage.rings.finite_rings.finite_field_constructor import FiniteField
808
+ from sage.arith.misc import is_prime_power
809
+ from .block_design import DesarguesianProjectivePlaneDesign
810
+ from itertools import chain
811
+
812
+ if explain_construction:
813
+ return ("Lemma 4.1 with n={},m={} from:\n" +
814
+ " Charles J.Colbourn, Jeffrey H. Dinitz, Mieczyslaw Wojtas,\n" +
815
+ " Thwarts in transversal designs,\n" +
816
+ " Designs, Codes and Cryptography 5, no. 3 (1995): 189-197.").format(n,m)
817
+
818
+ assert is_prime_power(n), "n(={}) must be a prime power"
819
+ assert k+4 <= n+1
820
+
821
+ q = n
822
+ K = FiniteField(q, 'x')
823
+ relabel = {x: i for i, x in enumerate(K)}
824
+ PG = DesarguesianProjectivePlaneDesign(q, check=False,
825
+ point_coordinates=False).blocks()
826
+
827
+ if q % 3 == 0:
828
+ t = K.one()
829
+ elif q % 3 == 1:
830
+ t = K.multiplicative_generator()**((q - 1)//3)
831
+ else:
832
+ raise ValueError("q(={}) must be congruent to 0 or 1 mod 3".format(q))
833
+
834
+ # The projective plane is labelled with integer coordinates. This code
835
+ # relabels to integers the following points (given by homogeneous
836
+ # coordinates in the projective space):
837
+ #
838
+ # - (1+t,t,1+t), (1,1,1), (1+t,t,t), (1,1,2), (0,0,1), (1,0,1), (0,1,1+t),
839
+ # (0,1,1), (1,0,-t)
840
+ points = [(1+t,t,1+t), (1,1,1), (1+t,t,t), (1,1,2), (0,0,1), (1,0,1), (0,1,1+t), (0,1,1), (1,0,-t)]
841
+ points = [[K(c) for c in t] for t in points] # triples of K^3
842
+ AG_2_3 = []
843
+ for x,y,z in points:
844
+ if z != 0:
845
+ x, y, z = x / z, y / z, K.one()
846
+ AG_2_3.append(relabel[x]+n*relabel[y])
847
+ elif y != 0:
848
+ x, y = x / y, K.one()
849
+ AG_2_3.append(q**2+relabel[x])
850
+ else:
851
+ AG_2_3.append(q**2+q)
852
+
853
+ AG_2_3 = set(AG_2_3)
854
+
855
+ # All blocks of PG should intersect 'AG_2_3' on !=2 AG_2_3.
856
+ assert all(len(AG_2_3.intersection(B)) != 2 for B in PG)
857
+
858
+ p = list(AG_2_3)[0]
859
+ # We now build a TD from the PG by removing p, in such a way that the last
860
+ # two elements of the last 4 columns are elements of AG_2_3
861
+ blocks = []
862
+ columns = []
863
+ for B in PG:
864
+ if p not in B:
865
+ blocks.append(B)
866
+ else:
867
+ B.remove(p)
868
+ columns.append(B)
869
+
870
+ # The columns containing elements from the AG are the last ones, and those
871
+ # elements should be the last two
872
+ columns.sort(key=lambda x:len(AG_2_3.intersection(x)))
873
+ for i in range(4):
874
+ columns[-i-1].sort(key=lambda x: int(x in AG_2_3))
875
+
876
+ relabel = {v:i for i,v in enumerate(chain(columns))}
877
+
878
+ TD = [sorted(relabel[x] for x in B) for B in blocks]
879
+
880
+ # We build the OA, removing unnecessary columns
881
+ OA = [[x % q for x in B[-k-4:]] for B in TD]
882
+ for B in OA:
883
+ for i in range(4):
884
+ if B[k+i] >= n-2:
885
+ B[k+i] = None
886
+
887
+ return wilson_construction(OA,k,n,m,[n-2,]*4,check=False)
888
+
889
+
890
+ def three_factor_product(k, n1, n2, n3, check=False, explain_construction=False):
891
+ r"""
892
+ Return an `OA(k+1,n_1n_2n_3)`.
893
+
894
+ The three factor product construction from [DukesLing14]_ does the following:
895
+
896
+ If `n_1\leq n_2\leq n_3` are such that there exists an
897
+ `OA(k,n_1)`, `OA(k+1,n_2)` and `OA(k+1,n_3)`, then there exists a
898
+ `OA(k+1,n_1n_2n_3)`.
899
+
900
+ It works with a modified product of orthogonal arrays ([Rees93]_, [Rees00]_)
901
+ which keeps track of parallel classes in the `OA` (the definition is given
902
+ for transversal designs).
903
+
904
+ A subset of blocks in an `TD(k,n)` is called a `c`-parallel class if
905
+ every point is covered exactly `c` times. A 1-parallel class is a
906
+ parallel class.
907
+
908
+ The modified product:
909
+
910
+ If there exists an `OA(k,n_1)`, and if there exists an `OA(k,n_2)` whose
911
+ blocks are partitionned into `s` `n_1`-parallel classes and `n_2-sn_1`
912
+ parallel classes, then there exists an `OA(k,n_1n_2)` whose blocks can
913
+ be partitionned into `sn_1^2` parallel classes and
914
+ `(n_1n_2-sn_1^2)/n_1=n_2-sn_1` `n_1`-parallel classes.
915
+
916
+ Proof:
917
+
918
+ - The product of the blocks of a parallel class with an `OA(k,n_1)`
919
+ yields an `n_1`-parallel class of an `OA(k,n_1n_2)`.
920
+
921
+ - The product of the blocks of a `n_1`-parallel class of `OA(k,n_2)`
922
+ with an `OA(k,n_1)` can be done in such a way that it yields `n_1n_2`
923
+ parallel classes of `OA(k,n_1n_2)`. Those classes cover exactly the
924
+ pairs that would have been covered with the usual product.
925
+
926
+ This can be achieved by simple cyclic permutations. Let us build the
927
+ product of the `n_1`-parallel class `\mathcal P\subseteq OA(k,n_2)`
928
+ with `OA(k,n_1)`: when computing the product of `P\in\mathcal P` with
929
+ `B^1\in OA(k,n_1)` the `i`-th coordinate should not be `(B^1_i,P_i)`
930
+ but `(B^1_i+r,P_i)` (the sum is mod `n_1`) where `r` is the number of
931
+ blocks of `\mathcal P` we have already processed whose `i`-th
932
+ coordinate is equal to `P_i` (note that `r< n_1` as `\mathcal P` is
933
+ `n_1`-parallel).
934
+
935
+ With these tools, one can obtain the designs promised by the three factors
936
+ construction applied to `k,n_1,n_2,n_3` (thanks to Julian R. Abel's help):
937
+
938
+ 1) Let `s` be the largest integer `\leq n_3/n_1`. Apply the product
939
+ construction to `OA(k,n_1)` and a resolvable `OA(k,n_3)` whose blocks
940
+ are partitionned into `s` `n_1`-parallel classes and `n_3-sn_1`
941
+ parallel classes. It results in a `OA(k,n_1n_3)` partitionned into
942
+ `sn_1^2` parallel classes plus `(n_1n_3-sn_1^2)/n_1=n_3-sn_1`
943
+ `n_1`-parallel classes.
944
+
945
+ 2) Add `n_3-n_1` parallel classes to every `n_1`-parallel class to turn
946
+ them into `n_3`-parallel classes. Apply the product construction to
947
+ this partitionned `OA(k,n_1n_3)` with a resolvable `OA(k,n_2)`.
948
+
949
+ 3) As `OA(k,n_2)` is resolvable, the `n_2`-parallel classes of
950
+ `OA(k,n_1n_2n_3)` are actually the union of `n_2` parallel classes,
951
+ thus the `OA(k,n_1n_2n_3)` is resolvable and can be turned into an
952
+ `OA(k+1,n_1n_2n_3)`
953
+
954
+ INPUT:
955
+
956
+ - ``k``, ``n1``, ``n2``, ``n3`` -- integers
957
+
958
+ - ``check`` -- boolean; whether to check that everything is going smoothly
959
+ while the design is being built. It is disabled by default, as the
960
+ constructor of orthogonal arrays checks the final design anyway.
961
+
962
+ - ``explain_construction`` -- boolean; return a string describing
963
+ the construction
964
+
965
+ .. SEEALSO::
966
+
967
+ - :func:`~sage.combinat.designs.orthogonal_arrays_find_recursive.find_three_factor_product`
968
+
969
+ EXAMPLES::
970
+
971
+ sage: # needs sage.schemes
972
+ sage: from sage.combinat.designs.designs_pyx import is_orthogonal_array
973
+ sage: from sage.combinat.designs.orthogonal_arrays_build_recursive import three_factor_product
974
+ sage: OA = three_factor_product(4,4,4,4)
975
+ sage: is_orthogonal_array(OA,5,64)
976
+ True
977
+ sage: OA = three_factor_product(4,3,4,5)
978
+ sage: is_orthogonal_array(OA,5,60)
979
+ True
980
+ sage: OA = three_factor_product(5,4,5,7)
981
+ sage: is_orthogonal_array(OA,6,140)
982
+ True
983
+ sage: OA = three_factor_product(9,8,9,9) # long time
984
+ sage: is_orthogonal_array(OA,10,8*9*9) # long time
985
+ True
986
+ sage: print(designs.orthogonal_arrays.explain_construction(10,648))
987
+ Three-factor product with n=8.9.9 from:
988
+ Peter J. Dukes, Alan C.H. Ling,
989
+ A three-factor product construction for mutually orthogonal latin squares,
990
+ https://arxiv.org/abs/1401.1466
991
+
992
+ REFERENCE:
993
+
994
+ .. [DukesLing14] A three-factor product construction for mutually orthogonal latin squares,
995
+ Peter J. Dukes, Alan C.H. Ling,
996
+ :arxiv:`1401.1466`
997
+
998
+ .. [Rees00] Truncated Transversal Designs: A New Lower Bound on the Number of Idempotent MOLS of Side,
999
+ Rolf S. Rees,
1000
+ Journal of Combinatorial Theory, Series A 90.2 (2000): 257-266.
1001
+
1002
+ .. [Rees93] Two new direct product-type constructions for resolvable group-divisible designs,
1003
+ Rolf S. Rees,
1004
+ Journal of Combinatorial Designs 1.1 (1993): 15-26.
1005
+ """
1006
+ assert n1 <= n2 <= n3
1007
+
1008
+ if explain_construction:
1009
+ return ("Three-factor product with n={}.{}.{} from:\n" +
1010
+ " Peter J. Dukes, Alan C.H. Ling,\n" +
1011
+ " A three-factor product construction for mutually orthogonal latin squares,\n" +
1012
+ " https://arxiv.org/abs/1401.1466").format(n1, n2, n3)
1013
+
1014
+ def assert_c_partition(classs, k, n, c):
1015
+ r"""
1016
+ Makes sure that ``classs`` contains blocks `B` of size `k` such that the list of
1017
+ ``B[i]`` covers `[n]` exactly `c` times for every index `i`.
1018
+ """
1019
+ c = int(c)
1020
+ assert all(len(B) == k for B in classs), "A block has length {}!=k(={})".format(len(B),k)
1021
+ assert len(classs) == n*c, "not the right number of blocks"
1022
+ for p in zip(*classs):
1023
+ assert all(x == i//c for i,x in enumerate(sorted(p))), "A class is not c(={})-parallel".format(c)
1024
+
1025
+ def product_with_parallel_classes(OA1, k, g1, g2, g1_parall, parall, check=True):
1026
+ r"""
1027
+ Return the product of two OA while keeping track of parallel classes.
1028
+
1029
+ INPUT:
1030
+
1031
+ - ``OA1`` -- (an `OA(k,g_1)`
1032
+
1033
+ - ``k``, ``g1``, ``g2`` -- integers
1034
+
1035
+ - ``g1_parall`` -- list of `g_1`-parallel classes
1036
+
1037
+ - ``parall`` -- list of parallel classes
1038
+
1039
+ .. NOTE::
1040
+
1041
+ The list ``g1_parall+parall`` should be an `OA(k,g_2)`
1042
+
1043
+ OUTPUT:
1044
+
1045
+ Two lists of classes ``g1_parall`` and ``parallel`` which are respectively
1046
+ `g_1`-parallel and parallel classes such that ``g1_parall+parallel`` is an
1047
+ ``OA(k,g1*g2)``.
1048
+ """
1049
+ if check:
1050
+ for classs in g1_parall:
1051
+ assert_c_partition(classs,k,g2,g1)
1052
+ for classs in parall:
1053
+ assert_c_partition(classs,k,g2,1)
1054
+
1055
+ # New parallel classes, built from a g1-parallel class with shifted copies
1056
+ # of OA1
1057
+
1058
+ new_parallel_classes = []
1059
+ for classs2 in g1_parall:
1060
+
1061
+ # Keep track of how many times we saw each point of [k]x[g2]
1062
+ count = [[0]*g2 for _ in range(k)]
1063
+
1064
+ copies_of_OA1 = []
1065
+ for B2 in classs2:
1066
+ copy_of_OA1 = []
1067
+
1068
+ shift = [count[i][x2] for i,x2 in enumerate(B2)]
1069
+ assert max(shift) < g1
1070
+
1071
+ for B1 in OA1:
1072
+ copy_of_OA1.append([x2*g1+(x1+sh) % g1 for sh,x1,x2 in zip(shift,B1,B2)])
1073
+
1074
+ copies_of_OA1.append(copy_of_OA1)
1075
+
1076
+ # Update the counts
1077
+ for i,x2 in enumerate(B2):
1078
+ count[i][x2] += 1
1079
+
1080
+ new_parallel_classes.extend([list(_) for _ in zip(*copies_of_OA1)])
1081
+
1082
+ # New g1-parallel classes, each one built from the product of a parallel
1083
+ # class with a OA1
1084
+
1085
+ new_g1_parallel_classes = []
1086
+ for classs2 in parall:
1087
+ disjoint_copies_of_OA1 = []
1088
+ for B2 in classs2:
1089
+ for B1 in OA1:
1090
+ disjoint_copies_of_OA1.append([x2*g1+x1 for x1,x2 in zip(B1,B2)])
1091
+ new_g1_parallel_classes.append(disjoint_copies_of_OA1)
1092
+
1093
+ # Check our stuff before we return it
1094
+ if check:
1095
+ for classs in new_g1_parallel_classes:
1096
+ assert_c_partition(classs, k, g2 * g1, g1)
1097
+ for classs in new_parallel_classes:
1098
+ assert_c_partition(classs, k, g2 * g1, 1)
1099
+
1100
+ return new_g1_parallel_classes, new_parallel_classes
1101
+
1102
+ # The three factors product construction begins !
1103
+ #
1104
+ # OA1 and resolvable OA2 and OA3
1105
+ OA1 = orthogonal_array(k,n1)
1106
+ OA3 = sorted(orthogonal_array(k+1,n3))
1107
+ OA3 = [B[1:] for B in OA3]
1108
+ OA2 = orthogonal_array(k+1,n2)
1109
+ OA2.sort()
1110
+ OA2 = [B[1:] for B in OA2]
1111
+
1112
+ # We split OA3 into as many n1-parallel classes as possible, i.e. n3//n1 classes of size n1*n3
1113
+ OA3_n1_parall = [OA3[i:i+n1*n3] for i in range(0,(n3-n1)*n3,n1*n3)]
1114
+
1115
+ # Leftover blocks become parallel classes. We must split them into slices of
1116
+ # length n3
1117
+ OA3_parall = [OA3[i:i+n3] for i in range(len(OA3_n1_parall)*n1*n3, len(OA3), n3)]
1118
+
1119
+ # First product: OA1 and OA3
1120
+ n1_parall, parall = product_with_parallel_classes(OA1,k,n1,n3,OA3_n1_parall,OA3_parall,check=check)
1121
+
1122
+ if check:
1123
+ OA_13 = [block for classs in parall+n1_parall for block in classs]
1124
+ assert is_orthogonal_array(OA_13,k,n1*n3,2,1)
1125
+
1126
+ # Add parallel classes to turn the n1-parall classes into n2-parallel classes
1127
+ for classs in n1_parall:
1128
+ for i in range(n2-n1):
1129
+ classs.extend(parall.pop())
1130
+
1131
+ n2_parall = n1_parall
1132
+ del n1_parall
1133
+
1134
+ # We compute the product of OA2 with our decomposition of OA1xOA2 into
1135
+ # n2-parallel classes and parallel classes
1136
+ n2_parall, parall = product_with_parallel_classes(OA2,k,n2,n1*n3,n2_parall,parall,check=check)
1137
+ for n2_classs in n2_parall:
1138
+ for i in range(n2):
1139
+ partition = [B for j in range(n1*n3) for B in n2_classs[j*n2**2+i*n2:j*n2**2+(i+1)*n2]]
1140
+ parall.append(partition)
1141
+
1142
+ # That's what we fought for: this design is resolvable, so let's add a last
1143
+ # column to them
1144
+ for i,classs in enumerate(parall):
1145
+ for B in classs:
1146
+ B.append(i)
1147
+
1148
+ OA = [block for classs in parall for block in classs]
1149
+
1150
+ if check:
1151
+ assert is_orthogonal_array(OA,k+1,n1*n2*n3,2,1)
1152
+
1153
+ return OA
1154
+
1155
+
1156
+ def _reorder_matrix(matrix):
1157
+ r"""
1158
+ Return a matrix which is obtained from ``matrix`` by permutation of each row
1159
+ in which each column contain every symbol exactly once.
1160
+
1161
+ The input must be a `N \times k` matrix with entries in `\{0,\ldots,N-1\}`
1162
+ such that:
1163
+
1164
+ - the symbols on each row are distinct (and hence can be identified with
1165
+ subsets of `\{0,\ldots,N-1\}`),
1166
+ - each symbol appear exactly `k` times.
1167
+
1168
+ The problem is equivalent to an edge coloring of a bipartite graph. This
1169
+ function is used by :func:`brouwer_separable_design`.
1170
+
1171
+ EXAMPLES::
1172
+
1173
+ sage: from sage.combinat.designs.orthogonal_arrays_build_recursive import _reorder_matrix
1174
+ sage: N = 4; k = 3
1175
+ sage: M = [[0,1,2],[0,1,3],[0,2,3],[1,2,3]]
1176
+ sage: M2 = _reorder_matrix(M) # needs sage.numerical.mip
1177
+ sage: all(set(M2[i][0] for i in range(N)) == set(range(N)) for i in range(k)) # needs sage.numerical.mip
1178
+ True
1179
+
1180
+ sage: M = [list(range(10))] * 10
1181
+ sage: N = k = 10
1182
+ sage: M2 = _reorder_matrix(M) # needs sage.numerical.mip
1183
+ sage: all(set(M2[i][0] for i in range(N)) == set(range(N)) for i in range(k)) # needs sage.numerical.mip
1184
+ True
1185
+ """
1186
+ from sage.graphs.graph import Graph
1187
+
1188
+ N = len(matrix)
1189
+ k = len(matrix[0])
1190
+
1191
+ g = Graph()
1192
+ g.add_edges((x,N+i) for i,S in enumerate(matrix) for x in S)
1193
+ matrix = []
1194
+ for _ in range(k):
1195
+ matching = g.matching(algorithm='LP')
1196
+ col = [0]*N
1197
+ for x,i,_ in matching:
1198
+ if i < N:
1199
+ x,i = i,x
1200
+ col[i-N] = x
1201
+ matrix.append(col)
1202
+ g.delete_edges(matching)
1203
+
1204
+ return list(zip(*matrix))
1205
+
1206
+
1207
+ def brouwer_separable_design(k, t, q, x, check=False, verbose=False, explain_construction=False):
1208
+ r"""
1209
+ Return a `OA(k,t(q^2+q+1)+x)` using Brouwer's result on separable designs.
1210
+
1211
+ This method is an implementation of Brouwer's construction presented in
1212
+ [Brouwer80]_. It consists in a systematic application of the usual
1213
+ transformation from PBD to OA, applied to a specific PBD.
1214
+
1215
+ **Baer subplanes**
1216
+
1217
+ When `q` is a prime power, the projective plane `PG(2,q^2)` can be
1218
+ partitionned into subplanes `PG(2,q)` (called Baer subplanes), giving
1219
+ `PG(2,q^2)=B_1\cup \dots\cup B_{q^2-q+1}`. As a result, every line of the
1220
+ `PG(2,q^2)` intersects one of the subplane on `q+1` points and all others on
1221
+ `1` point.
1222
+
1223
+ The `OA` are built by considering `B_1\cup\dots\cup B_t`, for a total of
1224
+ `t(q^2+q+1)` points (to which `x` new points are then added). The blocks of
1225
+ this subdesign belong to two categories:
1226
+
1227
+ * The blocks of size `t`: they come from the lines which intersect a
1228
+ `B_i` on `q+1` points for some `i>t`. The blocks of size `t` can be partitionned
1229
+ into `q^2-q+t-1` parallel classes according to their associated subplane `B_i`
1230
+ with `i>t`.
1231
+
1232
+ * The blocks of size `q+t`: those blocks form a symmetric design, as every
1233
+ point is incident with `q+t` of them.
1234
+
1235
+ **Constructions**
1236
+
1237
+ In the following, we write `N=t(q^2+q+1)+x`. The code is also heavily
1238
+ commented, and will clear any doubt.
1239
+
1240
+ * i) `x=0`: in that case we build a resolvable `OA(k-1,N)` that will then be
1241
+ completed into an `OA(k,N)`.
1242
+
1243
+ * *Sets of size* `t`)
1244
+
1245
+ We take the product of each parallel class with the parallel classes
1246
+ of a resolvable `OA(k-1,t)-t.OA(k-1,t)`, yielding new parallel
1247
+ classes.
1248
+
1249
+ * *Sets of size* `q+t`)
1250
+
1251
+ A `N \times (q+t)` array is built whose rows are the sets of size
1252
+ `q+t` such that every value appears once per column. For each block of
1253
+ a `OA(k-1,q+t)-(q+t).OA(k-1,t)`, the product with the rows of the
1254
+ matrix yields a parallel class.
1255
+
1256
+ * ii) `x=q+t`
1257
+
1258
+ * *Sets of size* `t`)
1259
+
1260
+ Each set of size `t` gives a `OA(k,t)-t.OA(k,1)`, except if there is
1261
+ only one parallel class in which case a `OA(k,t)` is sufficient.
1262
+
1263
+ * *Sets of size* `q+t`)
1264
+
1265
+ A `(N-x) \times (q+t)` array `M` is built whose `N-x` rows are the
1266
+ sets of size `q+t` such that every value appears once per column. For
1267
+ each of the new `x=q+t` points `p_1,\dots,p_{q+t}` we build a matrix
1268
+ `M_i` obtained from `M` by adding a column equal to `(p_i,p_i,p_i\dots
1269
+ )`. We add to the OA the product of all rows of the `M_i` with the
1270
+ block of the `x=q+t` parallel classes of a resolvable
1271
+ `OA(k,t+q+1)-(t+q+1).OA(k,1)`.
1272
+
1273
+ * *Set of size* `x`) An `OA(k,x)`
1274
+
1275
+ * iii) `x = q^2-q+1-t`
1276
+
1277
+ * *Sets of size* `t`)
1278
+
1279
+ All blocks of the `i`-th parallel class are extended with the `i`-th
1280
+ new point. The blocks are then replaced by a `OA(k,t+1)-(t+1).OA(k,1)`
1281
+ or, if there is only one parallel class (i.e. `x=1`) by a
1282
+ `OA(k,t+1)-OA(k,1)`.
1283
+
1284
+ * *Set of size* `q+t`)
1285
+
1286
+ They are replaced by `OA(k,q+t)-(q+t).OA(k,1)`.
1287
+
1288
+ * *Set of size* `x`) An `OA(k,x)`
1289
+
1290
+ * iv) `x = q^2+1`
1291
+
1292
+ * *Sets of size* `t`)
1293
+
1294
+ All blocks of the `i`-th parallel class are extended with the `i`-th
1295
+ new point (the other `x-q-t` new points are not touched at this
1296
+ step). The blocks are then replaced by a `OA(k,t+1)-(t+1).OA(k,1)` or,
1297
+ if there is only one parallel class (i.e. `x=1`) by a
1298
+ `OA(k,t+1)-OA(k,1)`.
1299
+
1300
+ * *Sets of size* `q+t`) Same as for ii)
1301
+
1302
+ * *Set of size* `x`) An `OA(k,x)`
1303
+
1304
+ * v) `0<x<q^2-q+1-t`
1305
+
1306
+ * *Sets of size* `t`)
1307
+
1308
+ The blocks of the first `x` parallel class are extended with the `x`
1309
+ new points, and replaced with `OA(k.t+1)-(t+1).OA(k,1)` or, if `x=1`,
1310
+ by `OA(k.t+1)-.OA(k,1)`
1311
+
1312
+ The blocks of the other parallel classes are replaced by
1313
+ `OA(k,t)-t.OA(k,t)` or, if there is only one class left, by
1314
+ `OA(k,t)-OA(k,t)`
1315
+
1316
+ * *Sets of size* `q+t`)
1317
+
1318
+ They are replaced with `OA(k,q+t)-(q+t).OA(k,1)`.
1319
+
1320
+ * *Set of size* `x`) An `OA(k,x)`
1321
+
1322
+ * vi) `t+q<x<q^2+1`
1323
+
1324
+ * *Sets of size* `t`) Same as in v) with an `x` equal to `x-q+t`.
1325
+
1326
+ * *Sets of size* `t`) Same as in vii)
1327
+
1328
+ * *Set of size* `x`) An `OA(k,x)`
1329
+
1330
+ INPUT:
1331
+
1332
+ - ``k``, ``t``, ``q``, ``x`` -- integers
1333
+
1334
+ - ``check`` -- boolean (default: ``False``); whether to check that output
1335
+ is correct before returning it
1336
+
1337
+ - ``verbose`` -- boolean; whether to print some information on the
1338
+ construction and parameters being used
1339
+
1340
+ - ``explain_construction`` -- boolean; return a string describing
1341
+ the construction
1342
+
1343
+ .. SEEALSO::
1344
+
1345
+ - :func:`~sage.combinat.designs.orthogonal_arrays_find_recursive.find_brouwer_separable_design`
1346
+
1347
+ REFERENCES:
1348
+
1349
+ .. [Brouwer80] A Series of Separable Designs with Application to Pairwise Orthogonal Latin Squares,
1350
+ Andries E. Brouwer,
1351
+ Vol. 1, n. 1, pp. 39-41,
1352
+ European Journal of Combinatorics, 1980
1353
+ http://www.sciencedirect.com/science/article/pii/S0195669880800199
1354
+
1355
+ EXAMPLES:
1356
+
1357
+ Test all possible cases::
1358
+
1359
+ sage: # needs conway_polynomials sage.schemes
1360
+ sage: from sage.combinat.designs.orthogonal_arrays_build_recursive import brouwer_separable_design
1361
+ sage: k,q,t=4,4,3; _=brouwer_separable_design(k,q,t,0,verbose=True)
1362
+ Case i) with k=4,q=3,t=4,x=0
1363
+ sage: k,q,t=3,3,3; _=brouwer_separable_design(k,t,q,t+q,verbose=True,check=True)
1364
+ Case ii) with k=3,q=3,t=3,x=6,e3=1
1365
+ sage: k,q,t=3,3,6; _=brouwer_separable_design(k,t,q,t+q,verbose=True,check=True)
1366
+ Case ii) with k=3,q=3,t=6,x=9,e3=0
1367
+ sage: k,q,t=3,3,6; _=brouwer_separable_design(k,t,q,q**2-q+1-t,verbose=True,check=True)
1368
+ Case iii) with k=3,q=3,t=6,x=1,e2=0
1369
+ sage: k,q,t=3,4,6; _=brouwer_separable_design(k,t,q,q**2-q+1-t,verbose=True,check=True)
1370
+ Case iii) with k=3,q=4,t=6,x=7,e2=1
1371
+ sage: k,q,t=3,4,6; _=brouwer_separable_design(k,t,q,q**2+1,verbose=True,check=True)
1372
+ Case iv) with k=3,q=4,t=6,x=17,e4=1
1373
+ sage: k,q,t=3,2,2; _=brouwer_separable_design(k,t,q,q**2+1,verbose=True,check=True)
1374
+ Case iv) with k=3,q=2,t=2,x=5,e4=0
1375
+ sage: k,q,t=3,4,7; _=brouwer_separable_design(k,t,q,3,verbose=True,check=True)
1376
+ Case v) with k=3,q=4,t=7,x=3,e1=1,e2=1
1377
+ sage: k,q,t=3,4,7; _=brouwer_separable_design(k,t,q,1,verbose=True,check=True)
1378
+ Case v) with k=3,q=4,t=7,x=1,e1=1,e2=0
1379
+ sage: k,q,t=3,4,7; _=brouwer_separable_design(k,t,q,q**2-q-t,verbose=True,check=True)
1380
+ Case v) with k=3,q=4,t=7,x=5,e1=0,e2=1
1381
+ sage: k,q,t=5,4,7; _=brouwer_separable_design(k,t,q,t+q+3,verbose=True,check=True)
1382
+ Case vi) with k=5,q=4,t=7,x=14,e3=1,e4=1
1383
+ sage: k,q,t=5,4,8; _=brouwer_separable_design(k,t,q,t+q+1,verbose=True,check=True)
1384
+ Case vi) with k=5,q=4,t=8,x=13,e3=1,e4=0
1385
+ sage: k,q,t=5,4,8; _=brouwer_separable_design(k,t,q,q**2,verbose=True,check=True)
1386
+ Case vi) with k=5,q=4,t=8,x=16,e3=0,e4=1
1387
+
1388
+ sage: print(designs.orthogonal_arrays.explain_construction(10, 189)) # needs sage.schemes
1389
+ Brouwer's separable design construction with t=9,q=4,x=0 from:
1390
+ Andries E. Brouwer,
1391
+ A series of separable designs with application to pairwise orthogonal Latin squares
1392
+ Vol. 1, n. 1, pp. 39-41,
1393
+ European Journal of Combinatorics, 1980
1394
+ """
1395
+ from sage.combinat.designs.orthogonal_arrays import OA_from_PBD
1396
+ from .difference_family import difference_family
1397
+ from .orthogonal_arrays import incomplete_orthogonal_array
1398
+ from sage.arith.misc import is_prime_power
1399
+
1400
+ if explain_construction:
1401
+ return ("Brouwer's separable design construction with t={},q={},x={} from:\n" +
1402
+ " Andries E. Brouwer,\n" +
1403
+ " A series of separable designs with application to pairwise orthogonal Latin squares\n" +
1404
+ " Vol. 1, n. 1, pp. 39-41,\n" +
1405
+ " European Journal of Combinatorics, 1980").format(t,q,x)
1406
+
1407
+ ###########################################################
1408
+ # Part 1: compute the separable PBD on t(q^2+q+1) points. #
1409
+ ###########################################################
1410
+
1411
+ assert t < q**2-q+1
1412
+ assert x >= 0
1413
+ assert is_prime_power(q)
1414
+ N2 = q**4+q**2+1
1415
+ N1 = q**2 + q + 1
1416
+
1417
+ # A projective plane on (q^2-q+1)*(q^2+q+1)=q^4+q^2+1 points
1418
+ B = difference_family(N2,q**2+1,1)[1][0]
1419
+ BIBD = [[(xx+i) % N2 for xx in B] for i in range(N2)]
1420
+
1421
+ # Each congruence class mod q^2-q+1 yields a Baer subplane. Let's check that:
1422
+ m = q**2-q+1
1423
+ for i in range(m):
1424
+ for B in BIBD:
1425
+ assert sum((xx % m) == i for xx in B) in [1,q+1], sum((xx % m) == i for xx in B)
1426
+
1427
+ # We are only interested by the points of the first t Baer subplanes (each
1428
+ # has size q**2+q+1). Note that each block of the projective plane:
1429
+ #
1430
+ # - Intersects one Baer plane on q+1 points.
1431
+ # - Intersects all other Baer planes on 1 point.
1432
+ #
1433
+ # When the design its truncated to its first t Baer subplanes, all blocks
1434
+ # now have size t or t+q, and cover t(q^2+q+1) points.
1435
+ #
1436
+ # 1) The blocks of size t can be partitionned into q**2-q+1-t parallel
1437
+ # classes, according to the Baer plane in which they contain q+1
1438
+ # elements.
1439
+ #
1440
+ # 2) The blocks of size q+t are a symmetric design
1441
+
1442
+ blocks_of_size_q_plus_t = []
1443
+ partition_of_blocks_of_size_t = [[] for _ in repeat(None, m - t)]
1444
+
1445
+ relabel = {i+j*m: N1*i+j for i in range(t) for j in range(N1)}
1446
+
1447
+ for B in BIBD:
1448
+ # Find the Baer subplane which B intersects on more than 1 point
1449
+ B_mod = sorted(xx % m for xx in B)
1450
+ while B_mod.pop(0) != B_mod[0]:
1451
+ pass
1452
+ plane = B_mod[0]
1453
+ if plane < t:
1454
+ blocks_of_size_q_plus_t.append([relabel[xx] for xx in B if xx % m < t])
1455
+ else:
1456
+ partition_of_blocks_of_size_t[plane-t].append([relabel[xx] for xx in B if xx % m < t])
1457
+
1458
+ ###########################################################################
1459
+ # Separable design built !
1460
+ # ------------------------
1461
+ #
1462
+ # At this point we have a PBD on t*(q**2+q+1) points. Its blocks are
1463
+ # split into:
1464
+ #
1465
+ # - partition_of_blocks_of_size_t : contains all blocks of size t split into
1466
+ # q^2-q+t-1 parallel classes.
1467
+ #
1468
+ # - blocks_of_size_q_plus_t : contains all t*(q**2+q+1)blocks of size q+t,
1469
+ # covering the same number of points: it is a
1470
+ # symmetric design.
1471
+ ###########################################################################
1472
+
1473
+ ##############################################
1474
+ # Part 2: Build an OA on t(q^2+q+1)+x points #
1475
+ ##############################################
1476
+
1477
+ e1 = int(x != q**2-q-t)
1478
+ e2 = int(x != 1)
1479
+ e3 = int(x != q**2)
1480
+ e4 = int(x != t+q+1)
1481
+ N = t*N1+x
1482
+
1483
+ # i)
1484
+ if x == 0:
1485
+
1486
+ if verbose:
1487
+ print("Case i) with k={},q={},t={},x={}".format(k, q, t, x))
1488
+
1489
+ # 1) We build a resolvable OA(k-1,t)-t.OA(k-1,1).
1490
+ # With it, from every parallel class with blocks of size t we build a
1491
+ # parallel class of a resolvable OA(k-1,N)
1492
+
1493
+ rOA_N_classes = []
1494
+
1495
+ # A resolvable OA(k-1,t)-t.OA(k-1,1)
1496
+ OA_t = incomplete_orthogonal_array(k-1,t,[1]*t,resolvable=True)
1497
+ OA_t_classes = [OA_t[i*t:(i+1)*t] for i in range(t-1)]
1498
+
1499
+ # We can now build (t-1)(q^2-q+1-t) parallel classes of the resolvable
1500
+ # OA(k-1,N)
1501
+ for PBD_parallel_class in partition_of_blocks_of_size_t:
1502
+ for OA_class in OA_t_classes:
1503
+ rOA_N_classes.append([[B[x] for x in BB]
1504
+ for BB in OA_class
1505
+ for B in PBD_parallel_class])
1506
+
1507
+ # 2) We build a Nx(q+t) matrix such that:
1508
+ #
1509
+ # a) Each row is a set of size q+t of the PBD
1510
+ # b) an element appears exactly once per column.
1511
+ #
1512
+ # (This is equivalent to an edge coloring of the (bipartite) incidence
1513
+ # graph of points and sets)
1514
+
1515
+ block_of_size_q_plus_t = _reorder_matrix(blocks_of_size_q_plus_t)
1516
+
1517
+ # 3) We now create blocks of an OA(k-1,N) as the product of
1518
+ # a) A set of size q+t (i.e. a row of the matrix)
1519
+ # b) An OA(k-1,q+t)-(q+t).OA(k-1,1)
1520
+ #
1521
+ # Thanks to the ordering of the points in each set of size q+t, the
1522
+ # product of a block B of the incomplete OA with all blocks of size q+t
1523
+ # yields a parallel class of an OA(k-1,N)
1524
+ OA = incomplete_orthogonal_array(k-1,q+t,[1]*(q+t))
1525
+ for B in OA:
1526
+ rOA_N_classes.append([[R[x] for x in B] for R in block_of_size_q_plus_t])
1527
+
1528
+ # 4) A last parallel class with blocks [0,0,...], [1,1,...],...
1529
+ rOA_N_classes.append([[i]*(k-1) for i in range(N)])
1530
+
1531
+ # 5) We now build the OA(k,N) from the N parallel classes of our resolvable OA(k-1,N)
1532
+ OA = [B for classs in rOA_N_classes for B in classs]
1533
+ for i,B in enumerate(OA):
1534
+ B.append(i//N)
1535
+
1536
+ # ii)
1537
+ elif (x == t+q and
1538
+ orthogonal_array(k+e3, t ,existence=True) and
1539
+ orthogonal_array( k , t+q ,existence=True) and
1540
+ orthogonal_array( k+1,t+q+1,existence=True)):
1541
+
1542
+ if verbose:
1543
+ print("Case ii) with k={},q={},t={},x={},e3={}".format(k,q,t,x,e3))
1544
+
1545
+ # The sets of size t:
1546
+ #
1547
+ # This is the usual OA_from_PBD replacement. If there is only one class
1548
+ # an OA(k,t) can be used instead of an OA(k+1,t)
1549
+
1550
+ if x == q**2:
1551
+ assert e3 == 0, "equivalent to x==q^2"
1552
+ assert len(partition_of_blocks_of_size_t) == 1, "also equivalent to exactly one partition into sets of size t"
1553
+ OA = [[B[xx] for xx in R] for R in orthogonal_array(k,t) for B in partition_of_blocks_of_size_t[0]]
1554
+ else:
1555
+ OA = OA_from_PBD(k,N,sum(partition_of_blocks_of_size_t,[]),check=False)[:-N]
1556
+ OA.extend([i]*k for i in range(N-x))
1557
+
1558
+ # The sets of size q+t:
1559
+ #
1560
+ # We build an OA(k,t+q+1)-(t+q+1).OA(k,t+q+1) and the reordered
1561
+ # matrix. We then compute the product of every parallel class of the OA
1562
+ # (x classes in total) with the rows of the ordered matrix (extended
1563
+ # with one of the new x points).
1564
+
1565
+ # Resolvable OA(k,t+q+1)-(t+q+1).OA(k,t+q+1)
1566
+ OA_tq1 = incomplete_orthogonal_array(k,t+q+1,[1]*(t+q+1),resolvable=True)
1567
+ OA_tq1_classes = [OA_tq1[i*(t+q+1):(i+1)*(t+q+1)] for i in range(t+q)]
1568
+
1569
+ blocks_of_size_q_plus_t = _reorder_matrix(blocks_of_size_q_plus_t)
1570
+
1571
+ for i,classs in enumerate(OA_tq1_classes):
1572
+ OA.extend([R[xx] if xx < t+q else N-i-1 for xx in B]
1573
+ for R in blocks_of_size_q_plus_t for B in classs)
1574
+
1575
+ # The set of size x
1576
+ OA.extend([N-1-xx for xx in R] for R in orthogonal_array(k,x))
1577
+
1578
+ # iii)
1579
+ elif (x == q**2-q+1-t and
1580
+ orthogonal_array( k , x ,existence=True) and # d0
1581
+ orthogonal_array(k+e2, t+1 ,existence=True) and # d2-e2
1582
+ orthogonal_array(k+1 , t+q ,existence=True)): # d3-e1
1583
+ if verbose:
1584
+ print("Case iii) with k={},q={},t={},x={},e2={}".format(k,q,t,x,e2))
1585
+
1586
+ OA = []
1587
+
1588
+ # Each of the x partition into blocks of size t is extended with one of
1589
+ # the new x points.
1590
+
1591
+ if x == 1:
1592
+ assert e2 == 0, "equivalent to x=1"
1593
+ # There is one partition into blocks of size t, which we extend with
1594
+ # the new vertex. The OA on t+1 points does not have to be resolvable.
1595
+
1596
+ OA.extend([B[xx] if xx < t else N-1 for xx in R]
1597
+ for R in incomplete_orthogonal_array(k,t+1,[1])
1598
+ for B in partition_of_blocks_of_size_t[0])
1599
+
1600
+ else:
1601
+ assert e2 == 1, "equivalent to x!=1"
1602
+ # Extending the x partitions into blocks of size t with
1603
+ # each of the new x points.
1604
+
1605
+ for i,partition in enumerate(partition_of_blocks_of_size_t):
1606
+ for B in partition:
1607
+ B.append(N-i-1)
1608
+ OA = OA_from_PBD(k,N,sum(partition_of_blocks_of_size_t,[]),check=False)[:-x]
1609
+
1610
+ # The blocks of size q+t are covered with a resolvable OA(k,q+t)
1611
+ OA.extend(OA_from_PBD(k,N,blocks_of_size_q_plus_t,check=False)[:-N])
1612
+
1613
+ # The set of size x
1614
+ OA.extend([N-xx-1 for xx in B] for B in orthogonal_array(k,x))
1615
+
1616
+ # iv)
1617
+ elif (x == q**2 + 1 and
1618
+ orthogonal_array(k, x, existence=True) and # d0
1619
+ orthogonal_array(k + e4, t + 1, existence=True) and # d2 - e4
1620
+ orthogonal_array(k + 1, t + q + 1, existence=True)): # d4 - 1
1621
+
1622
+ if verbose:
1623
+ print(f"Case iv) with k={k},q={q},t={t},x={x},e4={e4}")
1624
+
1625
+ # Sets of size t:
1626
+ #
1627
+ # All partitions of t-sets are extended with as many new points
1628
+
1629
+ if e4 == 0:
1630
+ # Only one partition into t-sets. The OA(k,t+1) needs not be resolvable
1631
+ OA = [[B[xx] if xx < t else N-x for xx in R]
1632
+ for R in incomplete_orthogonal_array(k,t+1,[1])
1633
+ for B in partition_of_blocks_of_size_t[0]]
1634
+ else:
1635
+ for i,classs in enumerate(partition_of_blocks_of_size_t):
1636
+ for B in classs:
1637
+ B.append(N-x+i)
1638
+ OA = OA_from_PBD(k,N,sum(partition_of_blocks_of_size_t,[]),check=False)[:-x]
1639
+
1640
+ # The sets of size q+t:
1641
+ #
1642
+ # We build an OA(k,t+q+1)-(t+q+1).OA(k,t+q+1) and the reordered
1643
+ # matrix. We then compute the product of every parallel class of the OA
1644
+ # (q+t classes in total) with the rows of the ordered matrix (extended
1645
+ # with the last q+t new points).
1646
+
1647
+ # Resolvable OA(k,t+q+1)-(t+q+1).OA(k,t+q+1)
1648
+ OA_tq1 = incomplete_orthogonal_array(k,t+q+1,[1]*(t+q+1),resolvable=True)
1649
+ OA_tq1_classes = [OA_tq1[i*(t+q+1):(i+1)*(t+q+1)] for i in range(t+q)]
1650
+
1651
+ blocks_of_size_q_plus_t = _reorder_matrix(blocks_of_size_q_plus_t)
1652
+
1653
+ for i,classs in enumerate(OA_tq1_classes):
1654
+ OA.extend([R[xx] if xx < t+q else N-i-1 for xx in B] for R in blocks_of_size_q_plus_t for B in classs)
1655
+
1656
+ # Set of size x
1657
+ OA_k_x = orthogonal_array(k,x)
1658
+ OA.extend([N-i-1 for i in R] for R in OA_k_x)
1659
+
1660
+ # v)
1661
+ elif (0 < x and x < q**2-q+1-t and (e1 or e2) and # The result is wrong when e1=e2=0
1662
+ orthogonal_array(k ,x ,existence=True) and # d0
1663
+ orthogonal_array(k+e1,t ,existence=True) and # d1-e1
1664
+ orthogonal_array(k+e2,t+1,existence=True) and # d2-e2
1665
+ orthogonal_array(k+1,t+q,existence=True)): # d3-1
1666
+ if verbose:
1667
+ print("Case v) with k={},q={},t={},x={},e1={},e2={}".format(k,q,t,x,e1,e2))
1668
+
1669
+ OA = []
1670
+
1671
+ # Sets of size t+1
1672
+ #
1673
+ # We extend x partitions into blocks of size t with the new x elements
1674
+ if e2:
1675
+ assert x != 1, "equivalent to e2==1"
1676
+ for i,classs in enumerate(partition_of_blocks_of_size_t[:x]):
1677
+ for B in classs:
1678
+ B.append(N-1-i)
1679
+ OA.extend(OA_from_PBD(k,N,sum(partition_of_blocks_of_size_t[:x],[]),check=False)[:-N])
1680
+
1681
+ else:
1682
+ assert x == 1, "equivalent to e2==0"
1683
+ # Only one class, the OA(k,t+1) need not be resolvable.
1684
+ OA.extend([B[xx] if xx < t else N-1 for xx in R]
1685
+ for R in incomplete_orthogonal_array(k,t+1,[1])
1686
+ for B in partition_of_blocks_of_size_t[0])
1687
+
1688
+ # Sets of size t
1689
+ if e1:
1690
+ assert x != q**2-q-t, "equivalent to e1=1"
1691
+ OA.extend(OA_from_PBD(k,N,sum(partition_of_blocks_of_size_t[x:],[]),check=False)[:-N])
1692
+ else:
1693
+ assert x == q**2-q-t, "equivalent to e1=0"
1694
+ # Only one class. The OA(k,t) needs not be resolvable
1695
+ OA.extend([B[xx] for xx in R] for R in orthogonal_array(k,t) for B in partition_of_blocks_of_size_t[-1])
1696
+
1697
+ if e1 and e2:
1698
+ OA.extend([i]*k for i in range(N-x))
1699
+
1700
+ if e1 == 0 and e2 == 0:
1701
+ raise RuntimeError("Brouwer's construction does not work for case v) with e2=e1=0")
1702
+
1703
+ # Sets of size q+t
1704
+ OA.extend(OA_from_PBD(k,N,blocks_of_size_q_plus_t,check=False)[:-N])
1705
+
1706
+ # Set of size x
1707
+ OA.extend([N-i-1 for i in R] for R in orthogonal_array(k,x))
1708
+
1709
+ # vi)
1710
+ elif (t+q < x and x < q**2+1 and (e3 or e4) and # The result is wrong when e3=e4=0
1711
+ orthogonal_array(k ,x ,existence=True) and # d0
1712
+ orthogonal_array(k+e3,t ,existence=True) and # d1-e3
1713
+ orthogonal_array(k+e4,t+1 ,existence=True) and # d2-e4
1714
+ orthogonal_array(k+1,t+q+1,existence=True)): # d4-1
1715
+ if verbose:
1716
+ print("Case vi) with k={},q={},t={},x={},e3={},e4={}".format(k,q,t,x,e3,e4))
1717
+
1718
+ OA = []
1719
+
1720
+ # Sets of size t+1
1721
+ #
1722
+ # All x-(q+t) parallel classes with blocks of size t are extended with
1723
+ # x-(q+t) of the new points.
1724
+ if e4:
1725
+ assert x != q+t+1, "equivalent to e4=1"
1726
+ for i,classs in enumerate(partition_of_blocks_of_size_t[:x-(q+t)]):
1727
+ for B in classs:
1728
+ B.append(N-x+i)
1729
+ OA.extend(OA_from_PBD(k,N,sum(partition_of_blocks_of_size_t[:x-(q+t)],[]),check=False)[:-N])
1730
+ else:
1731
+ assert x == q+t+1, "equivalent to e4=0"
1732
+ # Only one class. The OA(k,t+1) needs not be resolvable.
1733
+ OA.extend([B[xx] if xx < t else N-x for xx in R]
1734
+ for R in incomplete_orthogonal_array(k,t+1,[1])
1735
+ for B in partition_of_blocks_of_size_t[0])
1736
+
1737
+ # Sets of size t
1738
+ if e3:
1739
+ assert x != q**2, "equivalent to e3=1"
1740
+ OA.extend(OA_from_PBD(k,N,sum(partition_of_blocks_of_size_t[x-(q+t):],[]),check=False)[:-N])
1741
+ else:
1742
+ assert x == q**2, "equivalent to e3=0"
1743
+ # Only one class. The OA(k,t) needs not be resolvable.
1744
+ OA.extend([B[xx] for xx in R]
1745
+ for R in orthogonal_array(k,t)
1746
+ for B in partition_of_blocks_of_size_t[-1])
1747
+
1748
+ if e3 and e4:
1749
+ OA.extend([i]*k for i in range(N-x))
1750
+
1751
+ elif e3 == 0 and e4 == 0:
1752
+ raise RuntimeError("Brouwer's construction does not work for case v) with e3=e4=0")
1753
+
1754
+ # The sets of size q+t:
1755
+ #
1756
+ # We build an OA(k,t+q+1)-(t+q+1).OA(k,t+q+1) and the reordered
1757
+ # matrix. We then compute the product of every parallel class of the OA
1758
+ # (q+t classes in total) with the rows of the ordered matrix (extended
1759
+ # with the last q+t new points).
1760
+
1761
+ # Resolvable OA(k,t+q+1)-(t+q+1).OA(k,t+q+1)
1762
+ OA_tq1 = incomplete_orthogonal_array(k,t+q+1,[1]*(t+q+1),resolvable=True)
1763
+ OA_tq1_classes = [OA_tq1[i*(t+q+1):(i+1)*(t+q+1)] for i in range(t+q)]
1764
+
1765
+ blocks_of_size_q_plus_t = _reorder_matrix(blocks_of_size_q_plus_t)
1766
+
1767
+ for i,classs in enumerate(OA_tq1_classes):
1768
+ OA.extend([R[xx] if xx < t+q else N-i-1 for xx in B]
1769
+ for R in blocks_of_size_q_plus_t
1770
+ for B in classs)
1771
+
1772
+ # Set of size x
1773
+ OA.extend([N-xx-1 for xx in B] for B in orthogonal_array(k,x))
1774
+
1775
+ else:
1776
+ raise ValueError("this input is not handled by Brouwer's result")
1777
+
1778
+ if check:
1779
+ assert is_orthogonal_array(OA,k,N,2,1)
1780
+ return OA