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,2244 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ # sage.doctest: needs sage.rings.finite_rings sage.schemes
3
+ r"""
4
+ Orthogonal arrays (OA)
5
+
6
+ This module gathers some construction related to orthogonal arrays (or
7
+ transversal designs). One can build an `OA(k,n)` (or check that it can be built)
8
+ from the Sage console with ``designs.orthogonal_arrays.build``::
9
+
10
+ sage: OA = designs.orthogonal_arrays.build(4,8)
11
+
12
+ See also the modules :mod:`~sage.combinat.designs.orthogonal_arrays_build_recursive` or
13
+ :mod:`~sage.combinat.designs.orthogonal_arrays_find_recursive` for recursive
14
+ constructions.
15
+
16
+ This module defines the following functions:
17
+
18
+ .. csv-table::
19
+ :class: contentstable
20
+ :widths: 30, 70
21
+ :delim: |
22
+
23
+ :meth:`orthogonal_array` | Return an orthogonal array of parameters `k,n,t`.
24
+ :meth:`transversal_design` | Return a transversal design of parameters `k,n`.
25
+ :meth:`incomplete_orthogonal_array` | Return an `OA(k,n)-\sum_{1\leq i\leq x} OA(k,s_i)`.
26
+
27
+
28
+ .. csv-table::
29
+ :class: contentstable
30
+ :widths: 30, 70
31
+ :delim: |
32
+
33
+ :meth:`is_transversal_design` | Check that a given set of blocks ``B`` is a transversal design.
34
+ :meth:`~sage.combinat.designs.designs_pyx.is_orthogonal_array` | Check that the integer matrix `OA` is an `OA(k,n,t)`.
35
+ :meth:`wilson_construction` | Return a `OA(k,rm+u)` from a truncated `OA(k+s,r)` by Wilson's construction.
36
+ :meth:`TD_product` | Return the product of two transversal designs.
37
+ :meth:`OA_find_disjoint_blocks` | Return `x` disjoint blocks contained in a given `OA(k,n)`.
38
+ :meth:`OA_relabel` | Return a relabelled version of the OA.
39
+ :meth:`OA_standard_label` | Return a version of the OA relabelled to symbols `(0,\dots,n-1)`.
40
+ :meth:`OA_from_quasi_difference_matrix` | Return an Orthogonal Array from a Quasi-Difference matrix
41
+ :meth:`OA_from_Vmt` | Return an Orthogonal Array from a `V(m,t)`
42
+ :meth:`OA_from_PBD` | Return an `OA(k,n)` from a PBD
43
+ :meth:`OA_n_times_2_pow_c_from_matrix` | Return an `OA(k, \vert G\vert \cdot 2^c)` from a constrained `(G,k-1,2)`-difference matrix.
44
+ :meth:`OA_from_wider_OA` | Return the first `k` columns of `OA`.
45
+ :meth:`QDM_from_Vmt` | Return a QDM a `V(m,t)`
46
+
47
+
48
+ REFERENCES:
49
+
50
+ -- [CD1996]_
51
+
52
+ Functions
53
+ ---------
54
+ """
55
+
56
+ from sage.categories.sets_cat import EmptySetError
57
+ from sage.misc.unknown import Unknown
58
+ from .designs_pyx import is_orthogonal_array
59
+ from .group_divisible_designs import GroupDivisibleDesign
60
+ from .designs_pyx import _OA_cache_set, _OA_cache_get, _OA_cache_construction_available
61
+
62
+
63
+ def transversal_design(k, n, resolvable=False, check=True, existence=False):
64
+ r"""
65
+ Return a transversal design of parameters `k,n`.
66
+
67
+ A transversal design of parameters `k, n` is a collection `\mathcal{S}` of
68
+ subsets of `V = V_1 \cup \cdots \cup V_k` (where the *groups* `V_i` are
69
+ disjoint and have cardinality `n`) such that:
70
+
71
+ * Any `S \in \mathcal{S}` has cardinality `k` and intersects each group on
72
+ exactly one element.
73
+
74
+ * Any two elements from distincts groups are contained in exactly one
75
+ element of `\mathcal{S}`.
76
+
77
+ More general definitions sometimes involve a `\lambda` parameter, and we
78
+ assume here that `\lambda=1`.
79
+
80
+ For more information on transversal designs, see
81
+ `<http://mathworld.wolfram.com/TransversalDesign.html>`_.
82
+
83
+ INPUT:
84
+
85
+ - ``n``, ``k`` -- integers; if ``k is None`` it is set to the largest value
86
+ available
87
+
88
+ - ``resolvable`` -- boolean; set to ``True`` if you want the design to be
89
+ resolvable (see
90
+ :meth:`sage.combinat.designs.incidence_structures.IncidenceStructure.is_resolvable`). The
91
+ `n` classes of the resolvable design are obtained as the first `n` blocks,
92
+ then the next `n` blocks, etc ... Set to ``False`` by default.
93
+
94
+ - ``check`` -- boolean (default: ``True``); whether to check that output is
95
+ correct before returning it. As this is expected to be useless, you may
96
+ want to disable it whenever you want speed.
97
+
98
+ - ``existence`` -- boolean; instead of building the design, return:
99
+
100
+ - ``True`` -- meaning that Sage knows how to build the design
101
+
102
+ - ``Unknown`` -- meaning that Sage does not know how to build the
103
+ design, but that the design may exist (see :mod:`sage.misc.unknown`)
104
+
105
+ - ``False`` -- meaning that the design does not exist
106
+
107
+ .. NOTE::
108
+
109
+ When ``k=None`` and ``existence=True`` the function returns an
110
+ integer, i.e. the largest `k` such that we can build a `TD(k,n)`.
111
+
112
+ OUTPUT: the kind of output depends on the input:
113
+
114
+ - if ``existence=False`` (the default) then the output is a list of lists
115
+ that represent a `TD(k,n)` with
116
+ `V_1=\{0,\dots,n-1\},\dots,V_k=\{(k-1)n,\dots,kn-1\}`
117
+
118
+ - if ``existence=True`` and ``k`` is an integer, then the function returns a
119
+ troolean: either ``True``, ``Unknown`` or ``False``
120
+
121
+ - if ``existence=True`` and ``k=None`` then the output is the largest value
122
+ of ``k`` for which Sage knows how to compute a `TD(k,n)`.
123
+
124
+ .. SEEALSO::
125
+
126
+ :func:`orthogonal_array` -- a transversal design `TD(k,n)` is equivalent to an
127
+ orthogonal array `OA(k,n,2)`.
128
+
129
+ EXAMPLES::
130
+
131
+ sage: TD = designs.transversal_design(5,5); TD
132
+ Transversal Design TD(5,5)
133
+ sage: TD.blocks()
134
+ [[0, 5, 10, 15, 20], [0, 6, 12, 18, 24], [0, 7, 14, 16, 23],
135
+ [0, 8, 11, 19, 22], [0, 9, 13, 17, 21], [1, 5, 14, 18, 22],
136
+ [1, 6, 11, 16, 21], [1, 7, 13, 19, 20], [1, 8, 10, 17, 24],
137
+ [1, 9, 12, 15, 23], [2, 5, 13, 16, 24], [2, 6, 10, 19, 23],
138
+ [2, 7, 12, 17, 22], [2, 8, 14, 15, 21], [2, 9, 11, 18, 20],
139
+ [3, 5, 12, 19, 21], [3, 6, 14, 17, 20], [3, 7, 11, 15, 24],
140
+ [3, 8, 13, 18, 23], [3, 9, 10, 16, 22], [4, 5, 11, 17, 23],
141
+ [4, 6, 13, 15, 22], [4, 7, 10, 18, 21], [4, 8, 12, 16, 20],
142
+ [4, 9, 14, 19, 24]]
143
+
144
+ Some examples of the maximal number of transversal Sage is able to build::
145
+
146
+ sage: TD_4_10 = designs.transversal_design(4,10)
147
+ sage: designs.transversal_design(5,10,existence=True)
148
+ Unknown
149
+
150
+ For prime powers, there is an explicit construction which gives a
151
+ `TD(n+1,n)`::
152
+
153
+ sage: designs.transversal_design(4, 3, existence=True)
154
+ True
155
+ sage: designs.transversal_design(674, 673, existence=True)
156
+ True
157
+
158
+ For other values of ``n`` it depends::
159
+
160
+ sage: designs.transversal_design(7, 6, existence=True)
161
+ False
162
+ sage: designs.transversal_design(4, 6, existence=True)
163
+ Unknown
164
+ sage: designs.transversal_design(3, 6, existence=True)
165
+ True
166
+
167
+ sage: designs.transversal_design(11, 10, existence=True)
168
+ False
169
+ sage: designs.transversal_design(4, 10, existence=True)
170
+ True
171
+ sage: designs.transversal_design(5, 10, existence=True)
172
+ Unknown
173
+
174
+ sage: designs.transversal_design(7, 20, existence=True)
175
+ Unknown
176
+ sage: designs.transversal_design(6, 12, existence=True)
177
+ True
178
+ sage: designs.transversal_design(7, 12, existence=True)
179
+ True
180
+ sage: designs.transversal_design(8, 12, existence=True)
181
+ Unknown
182
+
183
+ sage: designs.transversal_design(6, 20, existence = True)
184
+ True
185
+ sage: designs.transversal_design(7, 20, existence = True)
186
+ Unknown
187
+
188
+ If you ask for a transversal design that Sage is not able to build then an
189
+ :exc:`EmptySetError` or a :exc:`NotImplementedError` is raised::
190
+
191
+ sage: designs.transversal_design(47, 100)
192
+ Traceback (most recent call last):
193
+ ...
194
+ NotImplementedError: I don't know how to build a TD(47,100)!
195
+ sage: designs.transversal_design(55, 54)
196
+ Traceback (most recent call last):
197
+ ...
198
+ EmptySetError: There exists no TD(55,54)!
199
+
200
+ Those two errors correspond respectively to the cases where Sage answer
201
+ ``Unknown`` or ``False`` when the parameter ``existence`` is set to
202
+ ``True``::
203
+
204
+ sage: designs.transversal_design(47, 100, existence=True)
205
+ Unknown
206
+ sage: designs.transversal_design(55, 54, existence=True)
207
+ False
208
+
209
+ If for a given `n` you want to know the largest `k` for which Sage is able
210
+ to build a `TD(k,n)` just call the function with `k` set to ``None`` and
211
+ ``existence`` set to ``True`` as follows::
212
+
213
+ sage: designs.transversal_design(None, 6, existence=True)
214
+ 3
215
+ sage: designs.transversal_design(None, 20, existence=True)
216
+ 6
217
+ sage: designs.transversal_design(None, 30, existence=True)
218
+ 6
219
+ sage: designs.transversal_design(None, 120, existence=True)
220
+ 9
221
+
222
+ TESTS:
223
+
224
+ The case when `n=1`::
225
+
226
+ sage: designs.transversal_design(5,1).blocks()
227
+ [[0, 1, 2, 3, 4]]
228
+
229
+ Obtained through Wilson's decomposition::
230
+
231
+ sage: _ = designs.transversal_design(4,38)
232
+
233
+ Obtained through product decomposition::
234
+
235
+ sage: _ = designs.transversal_design(6,60)
236
+ sage: _ = designs.transversal_design(5,60) # checks some tricky divisibility error
237
+
238
+ For small values of the parameter ``n`` we check the coherence of the
239
+ function :func:`transversal_design`::
240
+
241
+ sage: for n in range(2,25): # long time (15s)
242
+ ....: i = 2
243
+ ....: while designs.transversal_design(i, n, existence=True) is True:
244
+ ....: i += 1
245
+ ....: _ = designs.transversal_design(i-1, n)
246
+ ....: assert designs.transversal_design(None, n, existence=True) == i - 1
247
+ ....: j = i
248
+ ....: while designs.transversal_design(j, n, existence=True) is Unknown:
249
+ ....: try:
250
+ ....: _ = designs.transversal_design(j, n)
251
+ ....: raise AssertionError("no NotImplementedError")
252
+ ....: except NotImplementedError:
253
+ ....: pass
254
+ ....: j += 1
255
+ ....: k = j
256
+ ....: while k < n+4:
257
+ ....: assert designs.transversal_design(k, n, existence=True) is False
258
+ ....: try:
259
+ ....: _ = designs.transversal_design(k, n)
260
+ ....: raise AssertionError("no EmptySetError")
261
+ ....: except EmptySetError:
262
+ ....: pass
263
+ ....: k += 1
264
+ ....: print("%2d: (%2d, %2d)"%(n,i,j))
265
+ 2: ( 4, 4)
266
+ 3: ( 5, 5)
267
+ 4: ( 6, 6)
268
+ 5: ( 7, 7)
269
+ 6: ( 4, 7)
270
+ 7: ( 9, 9)
271
+ 8: (10, 10)
272
+ 9: (11, 11)
273
+ 10: ( 5, 11)
274
+ 11: (13, 13)
275
+ 12: ( 8, 14)
276
+ 13: (15, 15)
277
+ 14: ( 7, 15)
278
+ 15: ( 7, 17)
279
+ 16: (18, 18)
280
+ 17: (19, 19)
281
+ 18: ( 8, 20)
282
+ 19: (21, 21)
283
+ 20: ( 7, 22)
284
+ 21: ( 8, 22)
285
+ 22: ( 6, 23)
286
+ 23: (25, 25)
287
+ 24: (10, 26)
288
+
289
+ The special case `n=1`::
290
+
291
+ sage: designs.transversal_design(3, 1).blocks()
292
+ [[0, 1, 2]]
293
+ sage: designs.transversal_design(None, 1, existence=True)
294
+ +Infinity
295
+ sage: designs.transversal_design(None, 1)
296
+ Traceback (most recent call last):
297
+ ...
298
+ ValueError: there is no upper bound on k when 0<=n<=1
299
+
300
+ Resolvable TD::
301
+
302
+ sage: k,n = 5,15
303
+ sage: TD = designs.transversal_design(k,n,resolvable=True)
304
+ sage: TD.is_resolvable()
305
+ True
306
+ sage: r = designs.transversal_design(None,n,resolvable=True,existence=True)
307
+ sage: non_r = designs.transversal_design(None,n,existence=True)
308
+ sage: r + 1 == non_r
309
+ True
310
+ """
311
+ if resolvable:
312
+ if existence:
313
+ return orthogonal_array(k,n,resolvable=True,existence=True)
314
+ else:
315
+ OA = orthogonal_array(k,n,resolvable=True,check=False)
316
+ # the call to TransversalDesign will sort the block so we can not
317
+ # rely on the order *after* the call
318
+ blocks = [[i*n+c for i,c in enumerate(B)] for B in OA]
319
+ classes = [blocks[i:i+n] for i in range(0,n*n,n)]
320
+ TD = TransversalDesign(blocks,k,n,check=check,copy=False)
321
+ TD._classes = classes
322
+ return TD
323
+
324
+ # Is k is None we find the largest available
325
+ if k is None:
326
+ if n == 0 or n == 1:
327
+ if existence:
328
+ from sage.rings.infinity import Infinity
329
+ return Infinity
330
+ raise ValueError("there is no upper bound on k when 0<=n<=1")
331
+
332
+ k = orthogonal_array(None,n,existence=True)
333
+ if existence:
334
+ return k
335
+
336
+ if existence and _OA_cache_get(k,n) is not None:
337
+ return _OA_cache_get(k,n)
338
+
339
+ if n == 1:
340
+ if existence:
341
+ return True
342
+ TD = [list(range(k))]
343
+
344
+ elif k >= n+2:
345
+ if existence:
346
+ return False
347
+ raise EmptySetError("No Transversal Design exists when k>=n+2 if n>=2")
348
+
349
+ # Section 6.6 of [Stinson2004]
350
+ elif orthogonal_array(k, n, existence=True) is not Unknown:
351
+
352
+ # Forwarding non-existence results
353
+ if orthogonal_array(k, n, existence=True):
354
+ if existence:
355
+ return True
356
+ else:
357
+ if existence:
358
+ return False
359
+ raise EmptySetError("There exists no TD({},{})!".format(k,n))
360
+
361
+ OA = orthogonal_array(k,n, check=False)
362
+ TD = [[i*n+c for i,c in enumerate(l)] for l in OA]
363
+
364
+ else:
365
+ if existence:
366
+ return Unknown
367
+ raise NotImplementedError("I don't know how to build a TD({},{})!".format(k,n))
368
+
369
+ return TransversalDesign(TD,k,n,check=check)
370
+
371
+
372
+ class TransversalDesign(GroupDivisibleDesign):
373
+ r"""
374
+ Class for Transversal Designs.
375
+
376
+ INPUT:
377
+
378
+ - ``blocks`` -- collection of blocks
379
+
380
+ - ``k``, ``n`` -- integers; parameters of the transversal design. They can
381
+ be set to ``None`` (default) in which case their value is determined by
382
+ the blocks.
383
+
384
+ - ``check`` -- boolean (default: ``True``); whether to check that the
385
+ design is indeed a transversal design with the right parameters
386
+
387
+ EXAMPLES::
388
+
389
+ sage: designs.transversal_design(None,5)
390
+ Transversal Design TD(6,5)
391
+ sage: designs.transversal_design(None,30)
392
+ Transversal Design TD(6,30)
393
+ sage: designs.transversal_design(None,36)
394
+ Transversal Design TD(10,36)
395
+ """
396
+ def __init__(self, blocks, k=None, n=None, check=True, **kwds):
397
+ r"""
398
+ Constructor of the class.
399
+
400
+ EXAMPLES::
401
+
402
+ sage: designs.transversal_design(None,5)
403
+ Transversal Design TD(6,5)
404
+ """
405
+ from math import sqrt
406
+ if k is None:
407
+ if blocks:
408
+ k = len(blocks[0])
409
+ else:
410
+ k = 0
411
+ if n is None:
412
+ n = round(sqrt(len(blocks)))
413
+
414
+ self._n = n
415
+ self._k = k
416
+
417
+ if check:
418
+ assert is_transversal_design(blocks,k,n)
419
+
420
+ GroupDivisibleDesign.__init__(self,
421
+ k*n,
422
+ [list(range(i*n,(i+1)*n)) for i in range(k)],
423
+ blocks,
424
+ check=False,
425
+ **kwds)
426
+
427
+ def __repr__(self):
428
+ r"""
429
+ Return a string describing the transversal design.
430
+
431
+ EXAMPLES::
432
+
433
+ sage: designs.transversal_design(None,5)
434
+ Transversal Design TD(6,5)
435
+ sage: designs.transversal_design(None,30)
436
+ Transversal Design TD(6,30)
437
+ sage: designs.transversal_design(None,36)
438
+ Transversal Design TD(10,36)
439
+ """
440
+ return "Transversal Design TD({},{})".format(self._k,self._n)
441
+
442
+
443
+ def is_transversal_design(B, k, n, verbose=False):
444
+ r"""
445
+ Check that a given set of blocks ``B`` is a transversal design.
446
+
447
+ See :func:`~sage.combinat.designs.orthogonal_arrays.transversal_design`
448
+ for a definition.
449
+
450
+ INPUT:
451
+
452
+ - ``B`` -- the list of blocks
453
+
454
+ - ``k``, ``n`` -- integers
455
+
456
+ - ``verbose`` -- boolean; whether to display information about what is
457
+ going wrong
458
+
459
+ .. NOTE::
460
+
461
+ The transversal design must have `\{0, \ldots, kn-1\}` as a ground set,
462
+ partitioned as `k` sets of size `n`: `\{0, \ldots, k-1\} \sqcup
463
+ \{k, \ldots, 2k-1\} \sqcup \cdots \sqcup \{k(n-1), \ldots, kn-1\}`.
464
+
465
+ EXAMPLES::
466
+
467
+ sage: TD = designs.transversal_design(5, 5, check=True) # indirect doctest
468
+ sage: from sage.combinat.designs.orthogonal_arrays import is_transversal_design
469
+ sage: is_transversal_design(TD, 5, 5)
470
+ True
471
+ sage: is_transversal_design(TD, 4, 4)
472
+ False
473
+ """
474
+ return is_orthogonal_array([[x % n for x in R] for R in B],k,n,verbose=verbose)
475
+
476
+
477
+ def wilson_construction(OA, k, r, m, u, check=True, explain_construction=False):
478
+ r"""
479
+ Return a `OA(k,rm+\sum_i u_i)` from a truncated `OA(k+s,r)` by Wilson's
480
+ construction.
481
+
482
+ **Simple form:**
483
+
484
+ Let `OA` be a truncated `OA(k+s,r)` with `s` truncated columns of sizes
485
+ `u_1,...,u_s`, whose blocks have sizes in `\{k+b_1,...,k+b_t\}`. If there
486
+ exist:
487
+
488
+ - An `OA(k,m+b_i) - b_i.OA(k,1)` for every `1\leq i\leq t`
489
+
490
+ - An `OA(k,u_i)` for every `1\leq i\leq s`
491
+
492
+ Then there exists an `OA(k,rm+\sum u_i)`. The construction is a
493
+ generalization of Lemma 3.16 in [HananiBIBD]_.
494
+
495
+ **Brouwer-Van Rees form:**
496
+
497
+ Let `OA` be a truncated `OA(k+s,r)` with `s` truncated columns of sizes
498
+ `u_1,...,u_s`. Let the set `H_i` of the `u_i` points of column `k+i` be
499
+ partitionned into `\sum_j H_{ij}`. Let `m_{ij}` be integers
500
+ such that:
501
+
502
+ - For `0\leq i <l` there exists an `OA(k,\sum_j m_{ij}|H_{ij}|)`
503
+
504
+ - For any block `B\in OA` intersecting the sets `H_{ij(i)}` there exists an
505
+ `OA(k,m+\sum_i m_{ij})-\sum_i OA(k,m_{ij(j)})`.
506
+
507
+ Then there exists an `OA(k,rm+\sum_{i,j}m_{ij})`. This construction appears
508
+ in [BvR1982]_.
509
+
510
+ INPUT:
511
+
512
+ - ``OA`` -- an incomplete orthogonal array with `k+s` columns. The elements
513
+ of a column of size `c` must belong to `\{0,...,c\}`. The missing entries
514
+ of a block are represented by ``None`` values. If ``OA=None``, it is
515
+ defined as a truncated orthogonal arrays with `k+s` columns.
516
+
517
+ - ``k``, ``r``, ``m`` -- integers
518
+
519
+ - ``u`` -- list; two cases depending on the form to use:
520
+
521
+ - Simple form: a list of length `s` such that column ``k+i`` has size
522
+ ``u[i]``. The untruncated points of column ``k+i`` are assumed to be
523
+ ``[0,...,u[i]-1]``.
524
+
525
+ - Brouwer-Van Rees form: a list of length `s` such that ``u[i]`` is the
526
+ list of pairs `(m_{i0},|H_{i0}|),...,(m_{ip_i},|H_{ip_i}|)`. The
527
+ untruncated points of column ``k+i`` are assumed to be `[0,...,u_i-1]`
528
+ where `u_i=\sum_j |H_{ip_i}|`. Besides, the first `|H_{i0}|` points
529
+ represent `H_{i0}`, the next `|H_{i1}|` points represent `H_{i1}`,
530
+ etc...
531
+
532
+ - ``explain_construction`` -- boolean; return a string describing
533
+ the construction
534
+
535
+ - ``check`` -- boolean (default: ``True``); whether to check that output is
536
+ correct before returning it. As this is expected to be useless, you may
537
+ want to disable it whenever you want speed.
538
+
539
+ REFERENCE:
540
+
541
+ .. [HananiBIBD] Balanced incomplete block designs and related designs,
542
+ Haim Hanani,
543
+ Discrete Mathematics 11.3 (1975) pages 255-369.
544
+
545
+ EXAMPLES::
546
+
547
+ sage: from sage.combinat.designs.orthogonal_arrays import wilson_construction
548
+ sage: from sage.combinat.designs.orthogonal_arrays import OA_relabel
549
+ sage: from sage.combinat.designs.orthogonal_arrays_find_recursive import find_wilson_decomposition_with_one_truncated_group
550
+ sage: total = 0
551
+ sage: for k in range(3,8):
552
+ ....: for n in range(1,30):
553
+ ....: if find_wilson_decomposition_with_one_truncated_group(k,n):
554
+ ....: total += 1
555
+ ....: f, args = find_wilson_decomposition_with_one_truncated_group(k,n)
556
+ ....: _ = f(*args)
557
+ sage: total
558
+ 41
559
+
560
+ sage: print(designs.orthogonal_arrays.explain_construction(7,58))
561
+ Wilson's construction n=8.7+1+1 with master design OA(7+2,8)
562
+ sage: print(designs.orthogonal_arrays.explain_construction(9,115))
563
+ Wilson's construction n=13.8+11 with master design OA(9+1,13)
564
+ sage: print(wilson_construction(None,5,11,21,[[(5,5)]],explain_construction=True))
565
+ Brouwer-van Rees construction n=11.21+(5.5) with master design OA(5+1,11)
566
+ sage: print(wilson_construction(None,71,17,21,[[(4,9),(1,1)],[(9,9),(1,1)]],explain_construction=True))
567
+ Brouwer-van Rees construction n=17.21+(9.4+1.1)+(9.9+1.1) with master design OA(71+2,17)
568
+
569
+ An example using the Brouwer-van Rees generalization::
570
+
571
+ sage: from sage.combinat.designs.orthogonal_arrays import is_orthogonal_array
572
+ sage: from sage.combinat.designs.orthogonal_arrays import wilson_construction
573
+ sage: OA = designs.orthogonal_arrays.build(6,11)
574
+ sage: OA = [[x if (i<5 or x<5) else None for i,x in enumerate(R)] for R in OA]
575
+ sage: OAb = wilson_construction(OA,5,11,21,[[(5,5)]])
576
+ sage: is_orthogonal_array(OAb,5,256)
577
+ True
578
+ """
579
+ # Converting the input to Brouwer-Van Rees form
580
+ try:
581
+ if u:
582
+ int(u[0])
583
+ except TypeError:
584
+ pass
585
+ else:
586
+ u = [[(1,uu)] for uu in u]
587
+
588
+ n_trunc = len(u)
589
+
590
+ if explain_construction:
591
+ if not u:
592
+ return ("Product of orthogonal arrays n={}.{}").format(r,m)
593
+ elif all(len(uu) == 1 and uu[0][0] == 1 for uu in u):
594
+ return ("Wilson's construction n={}.{}+{} with master design OA({}+{},{})"
595
+ .format(r, m, "+".join(str(x) for ((_,x),) in u), k, n_trunc, r))
596
+ else:
597
+ return ("Brouwer-van Rees construction n={}.{}+{} with master design OA({}+{},{})"
598
+ .format(r, m,
599
+ "+".join("(" + "+".join(str(x)+"."+str(mul) for mul,x in uu) + ")"
600
+ for uu in u),
601
+ k, n_trunc, r))
602
+
603
+ if OA is None:
604
+ master_design = orthogonal_array(k+n_trunc,r,check=False)
605
+ matrix = [list(range(r))]*k
606
+ for uu in u:
607
+ uu = sum(x[1] for x in uu)
608
+ matrix.append(list(range(uu))+[None]*(r-uu))
609
+ master_design = OA_relabel(master_design, k+n_trunc, r, matrix=matrix)
610
+ else:
611
+ master_design = OA
612
+
613
+ for c in u:
614
+ assert all(m_ij >= 0 and h_size >= 0 for m_ij,h_size in c)
615
+ assert sum(h_size for m_ij,h_size in c) <= r
616
+
617
+ # Associates a point ij from a truncated column k+i to
618
+ #
619
+ # - its corresponding multiplier
620
+ # - its corresponding set of points in the final design.
621
+ point_to_mij = []
622
+ point_to_point_set = []
623
+ n = r*m
624
+ for i,partition in enumerate(u):
625
+ column_i_point_to_mij = []
626
+ column_i_point_to_point_set = []
627
+ for mij,h_size in partition:
628
+ for _ in range(h_size):
629
+ column_i_point_to_mij.append(mij)
630
+ column_i_point_to_point_set.append(list(range(n,n+mij)))
631
+ n += mij
632
+ point_to_mij.append(column_i_point_to_mij)
633
+ point_to_point_set.append(column_i_point_to_point_set)
634
+
635
+ # the set of ij associated with each block
636
+ block_to_ij = lambda B: ((i,j) for i,j in enumerate(B[k:]) if j is not None)
637
+
638
+ # The different profiles (set of mij associated with each block)
639
+ block_profiles = set(tuple(point_to_mij[i][j] for i,j in block_to_ij(B)) for B in master_design)
640
+
641
+ # For each block meeting multipliers m_ij(0),...,m_ij(s) we need a
642
+ # OA(k,m+\sum m_{ij(i)})-\sum OA(k,\sum m_{ij(i)})
643
+ OA_incomplete = {profile: incomplete_orthogonal_array(k, m+sum(profile),
644
+ profile) for profile in block_profiles}
645
+
646
+ # For each truncated column k+i partitionned into H_{i0},...,H_{ip_i} we
647
+ # need a OA(k,\sum_j m_{ij} * |H_{ij}|)
648
+ OA_k_u = {sum(c): orthogonal_array(k, sum(c)) for c in point_to_mij}
649
+
650
+ # Building the actual design !
651
+ OA = []
652
+ for B in master_design:
653
+ # The missing entries belong to the last n_trunc columns
654
+ assert all(x is not None for x in B[:k])
655
+
656
+ # We replace the block of profile m_{ij(0)},...,m_{ij(s)} with a
657
+ # OA(k,m+\sum_i m_ij(i)) properly relabelled
658
+ matrix = [list(range(i*m,(i+1)*m)) for i in B[:k]]
659
+ profile = []
660
+ for i,j in block_to_ij(B):
661
+ profile.append(point_to_mij[i][j])
662
+ for C in matrix:
663
+ C.extend(point_to_point_set[i][j])
664
+
665
+ OA.extend(OA_relabel(OA_incomplete[tuple(profile)],k,m+sum(profile),matrix=matrix))
666
+
667
+ # The missing OA(k,uu)
668
+ for i in range(n_trunc):
669
+ length = sum(point_to_mij[i])
670
+ OA.extend(OA_relabel(OA_k_u[length],
671
+ k,
672
+ length,
673
+ matrix=[sum(point_to_point_set[i],[])]*k))
674
+
675
+ if check:
676
+ from .designs_pyx import is_orthogonal_array
677
+ assert is_orthogonal_array(OA,k,n,2)
678
+
679
+ return OA
680
+
681
+
682
+ def TD_product(k, TD1, n1, TD2, n2, check=True):
683
+ r"""
684
+ Return the product of two transversal designs.
685
+
686
+ From a transversal design `TD_1` of parameters `k,n_1` and a transversal
687
+ design `TD_2` of parameters `k,n_2`, this function returns a transversal
688
+ design of parameters `k,n` where `n=n_1\times n_2`.
689
+
690
+ Formally, if the groups of `TD_1` are `V^1_1,\dots,V^1_k` and the groups of
691
+ `TD_2` are `V^2_1,\dots,V^2_k`, the groups of the product design are
692
+ `V^1_1\times V^2_1,\dots,V^1_k\times V^2_k` and its blocks are the
693
+ `\{(x^1_1,x^2_1),\dots,(x^1_k,x^2_k)\}` where `\{x^1_1,\dots,x^1_k\}` is a
694
+ block of `TD_1` and `\{x^2_1,\dots,x^2_k\}` is a block of `TD_2`.
695
+
696
+ INPUT:
697
+
698
+ - ``TD1``, ``TD2`` -- transversal designs
699
+
700
+ - ``k``, ``n1``, ``n2`` -- integers
701
+
702
+ - ``check`` -- boolean (default: ``True``); whether to check that output is
703
+ correct before returning it. As this is expected to be useless, you may
704
+ want to disable it whenever you want speed.
705
+
706
+ .. NOTE::
707
+
708
+ This function uses transversal designs with
709
+ `V_1=\{0,\dots,n-1\},\dots,V_k=\{(k-1)n,\dots,kn-1\}` both as input and
710
+ output.
711
+
712
+ EXAMPLES::
713
+
714
+ sage: from sage.combinat.designs.orthogonal_arrays import TD_product
715
+ sage: TD1 = designs.transversal_design(6,7)
716
+ sage: TD2 = designs.transversal_design(6,12)
717
+ sage: TD6_84 = TD_product(6,TD1,7,TD2,12)
718
+ """
719
+ N = n1*n2
720
+ TD = []
721
+ for X1 in TD1:
722
+ for X2 in TD2:
723
+ TD.append([x1 * n2 + (x2 % n2) for x1, x2 in zip(X1, X2)])
724
+ if check:
725
+ assert is_transversal_design(TD,k,N)
726
+
727
+ return TD
728
+
729
+
730
+ def orthogonal_array(k, n, t=2, resolvable=False, check=True, existence=False, explain_construction=False):
731
+ r"""
732
+ Return an orthogonal array of parameters `k,n,t`.
733
+
734
+ An orthogonal array of parameters `k,n,t` is a matrix with `k` columns
735
+ filled with integers from `[n]` in such a way that for any `t` columns, each
736
+ of the `n^t` possible rows occurs exactly once. In
737
+ particular, the matrix has `n^t` rows.
738
+
739
+ More general definitions sometimes involve a `\lambda` parameter, and we
740
+ assume here that `\lambda=1`.
741
+
742
+ An orthogonal array is said to be *resolvable* if it corresponds to a
743
+ resolvable transversal design (see
744
+ :meth:`sage.combinat.designs.incidence_structures.IncidenceStructure.is_resolvable`).
745
+
746
+ For more information on orthogonal arrays, see
747
+ :wikipedia:`Orthogonal_array`.
748
+
749
+ INPUT:
750
+
751
+ - ``k`` -- integer; number of columns. If ``k`` is ``None`` it is set to the
752
+ largest value available.
753
+
754
+ - ``n`` -- integer; number of symbols
755
+
756
+ - ``t`` -- integer (default: 2); strength of the array
757
+
758
+ - ``resolvable`` -- boolean (default: ``False``); set to ``True`` if you
759
+ want the design to be resolvable. The `n` classes of the resolvable
760
+ design are obtained as the first `n` blocks, then the next `n` blocks,
761
+ etc.
762
+
763
+ - ``check`` -- boolean (default: ``True``); whether to check that output is
764
+ correct before returning it. As this is expected to be useless, you may
765
+ want to disable it whenever you want speed.
766
+
767
+ - ``existence`` -- boolean; instead of building the design, return:
768
+
769
+ - ``True`` -- meaning that Sage knows how to build the design
770
+
771
+ - ``Unknown`` -- meaning that Sage does not know how to build the
772
+ design, but that the design may exist (see :mod:`sage.misc.unknown`)
773
+
774
+ - ``False`` -- meaning that the design does not exist
775
+
776
+ .. NOTE::
777
+
778
+ When ``k=None`` and ``existence=True`` the function returns an
779
+ integer, i.e. the largest `k` such that we can build a `OA(k,n)`.
780
+
781
+ - ``explain_construction`` -- boolean; return a string describing
782
+ the construction
783
+
784
+ OUTPUT: the kind of output depends on the input:
785
+
786
+ - if ``existence=False`` (the default) then the output is a list of lists
787
+ that represent an orthogonal array with parameters ``k`` and ``n``
788
+
789
+ - if ``existence=True`` and ``k`` is an integer, then the function returns a
790
+ troolean: either ``True``, ``Unknown`` or ``False``
791
+
792
+ - if ``existence=True`` and ``k=None`` then the output is the largest value
793
+ of ``k`` for which Sage knows how to compute a `TD(k,n)`.
794
+
795
+ .. NOTE::
796
+
797
+ This method implements theorems from [Stinson2004]_. See the code's
798
+ documentation for details.
799
+
800
+ .. SEEALSO::
801
+
802
+ When `t=2` an orthogonal array is also a transversal design (see
803
+ :func:`transversal_design`) and a family of mutually orthogonal latin
804
+ squares (see
805
+ :func:`~sage.combinat.designs.latin_squares.mutually_orthogonal_latin_squares`).
806
+
807
+ TESTS:
808
+
809
+ The special cases `n=0,1`::
810
+
811
+ sage: designs.orthogonal_arrays.build(3,0)
812
+ []
813
+ sage: designs.orthogonal_arrays.build(3,1)
814
+ [[0, 0, 0]]
815
+ sage: designs.orthogonal_arrays.largest_available_k(0)
816
+ +Infinity
817
+ sage: designs.orthogonal_arrays.largest_available_k(1)
818
+ +Infinity
819
+ sage: designs.orthogonal_arrays.build(16,0)
820
+ []
821
+ sage: designs.orthogonal_arrays.build(16,1)
822
+ [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
823
+
824
+ when `t>2` and `k=None`::
825
+
826
+ sage: t = 3
827
+ sage: designs.orthogonal_arrays.largest_available_k(5,t=t) == t
828
+ True
829
+ sage: _ = designs.orthogonal_arrays.build(t,5,t)
830
+ """
831
+ assert n >= 0, "n(={}) must be nonnegative".format(n)
832
+
833
+ # A resolvable OA(k,n) is an OA(k+1,n)
834
+ if resolvable:
835
+ assert t == 2, "resolvable designs are only handled when t=2"
836
+ if existence and k is not None:
837
+ return orthogonal_array(k+1,n,existence=True)
838
+ if k is None:
839
+ k = orthogonal_array(None,n,existence=True)-1
840
+ if existence:
841
+ return k
842
+ OA = sorted(orthogonal_array(k+1,n,check=check))
843
+ return [B[1:] for B in OA]
844
+
845
+ # If k is set to None we find the largest value available
846
+ if k is None:
847
+ if existence:
848
+ return largest_available_k(n,t)
849
+ elif n == 0 or n == 1:
850
+ raise ValueError("there is no upper bound on k when 0<=n<=1")
851
+ else:
852
+ k = largest_available_k(n,t)
853
+
854
+ if k < t:
855
+ raise ValueError("undefined for k<t")
856
+
857
+ if existence and _OA_cache_get(k,n) is not None and t == 2:
858
+ return _OA_cache_get(k,n)
859
+
860
+ from .block_design import projective_plane
861
+ from .database import OA_constructions, MOLS_constructions, QDM
862
+ from .orthogonal_arrays_find_recursive import find_recursive_construction
863
+ from .difference_matrices import difference_matrix
864
+
865
+ may_be_available = _OA_cache_construction_available(k,n) is not False
866
+
867
+ if n <= 1:
868
+ if existence:
869
+ return True
870
+ if explain_construction:
871
+ return "Trivial construction"
872
+ OA = [[0]*k]*n
873
+
874
+ elif k >= n+t:
875
+ # When t=2 then k<n+t as it is equivalent to the existence of n-1 MOLS.
876
+ # When t>2 the submatrix defined by the rows whose first t-2 elements
877
+ # are 0s yields a OA with t=2 and k-(t-2) columns. Thus k-(t-2) < n+2,
878
+ # i.e. k<n+t.
879
+ if existence:
880
+ return False
881
+ msg = "There exists no OA({},{}) as k(={})>n+t-1={}".format(k,n,k,n+t-1)
882
+ if explain_construction:
883
+ return msg
884
+ raise EmptySetError(msg)
885
+
886
+ elif k <= t:
887
+ if existence:
888
+ return True
889
+ if explain_construction:
890
+ return "Trivial construction [n]^k"
891
+
892
+ from itertools import product
893
+ return [list(x) for x in product(range(n), repeat=k)]
894
+
895
+ elif t != 2:
896
+ if existence:
897
+ return Unknown
898
+ msg = "Only trivial orthogonal arrays are implemented for t>=2"
899
+ if explain_construction:
900
+ return msg
901
+ raise NotImplementedError(msg)
902
+
903
+ elif k <= 3:
904
+ if existence:
905
+ return True
906
+ if explain_construction:
907
+ return "Cyclic latin square"
908
+ return [[i,j,(i+j) % n] for i in range(n) for j in range(n)]
909
+
910
+ # projective spaces are equivalent to OA(n+1,n,2)
911
+ elif (projective_plane(n, existence=True) is True or
912
+ (k == n+1 and projective_plane(n, existence=True) is False)):
913
+ _OA_cache_set(n+1,n,projective_plane(n, existence=True))
914
+ if k == n+1:
915
+ if existence:
916
+ return projective_plane(n, existence=True)
917
+ if explain_construction:
918
+ return "From a projective plane of order {}".format(n)
919
+ from .block_design import projective_plane_to_OA
920
+ p = projective_plane(n, check=False)
921
+ OA = projective_plane_to_OA(p, check=False)
922
+ else:
923
+ if existence:
924
+ return True
925
+ if explain_construction:
926
+ return "From a projective plane of order {}".format(n)
927
+ from .block_design import projective_plane_to_OA
928
+ p = projective_plane(n, check=False)
929
+ OA = [l[:k] for l in projective_plane_to_OA(p, check=False)]
930
+
931
+ # Constructions from the database (OA)
932
+ elif may_be_available and n in OA_constructions and k <= OA_constructions[n][0]:
933
+ _OA_cache_set(OA_constructions[n][0],n,True)
934
+ if existence:
935
+ return True
936
+ if explain_construction:
937
+ return "the database contains an OA({},{})".format(OA_constructions[n][0],n)
938
+ _, construction = OA_constructions[n]
939
+
940
+ OA = OA_from_wider_OA(construction(),k)
941
+
942
+ # Constructions from the database II (MOLS: Section 6.5.1 from [Stinson2004])
943
+ elif may_be_available and n in MOLS_constructions and k-2 <= MOLS_constructions[n][0]:
944
+ _OA_cache_set(MOLS_constructions[n][0]+2,n,True)
945
+
946
+ if existence:
947
+ return True
948
+ elif explain_construction:
949
+ return "the database contains {} MOLS of order {}".format(MOLS_constructions[n][0],n)
950
+ else:
951
+ construction = MOLS_constructions[n][1]
952
+ mols = construction()
953
+ OA = [[i,j]+[m[i,j] for m in mols]
954
+ for i in range(n) for j in range(n)]
955
+ OA = OA_from_wider_OA(OA,k)
956
+
957
+ # Constructions from the database III (Quasi-difference matrices)
958
+ elif (may_be_available and
959
+ (n, 1) in QDM and
960
+ any(kk >= k and mu <= lmbda and (orthogonal_array(k,u,existence=True) is True) for (_,lmbda,mu,u),(kk,_) in QDM[n,1].items())):
961
+ _OA_cache_set(k,n,True)
962
+
963
+ for (nn, lmbda, mu, u), (kk, f) in QDM[n,1].items():
964
+ if (kk >= k and
965
+ mu <= lmbda and
966
+ (orthogonal_array(k,u,existence=True) is True)):
967
+ if existence:
968
+ return True
969
+ elif explain_construction:
970
+ return "the database contains a ({},{};{},{};{})-quasi difference matrix".format(nn,k,lmbda,mu,u)
971
+ G,M = f()
972
+ M = [R[:k] for R in M]
973
+ OA = OA_from_quasi_difference_matrix(M,G,add_col=False)
974
+ break
975
+
976
+ # From Difference Matrices
977
+ elif may_be_available and difference_matrix(n,k-1,existence=True) is True:
978
+ _OA_cache_set(k,n,True)
979
+ if existence:
980
+ return True
981
+ if explain_construction:
982
+ return "from a ({},{})-difference matrix".format(n,k-1)
983
+ G,M = difference_matrix(n,k-1)
984
+ OA = OA_from_quasi_difference_matrix(M,G,add_col=True)
985
+
986
+ elif may_be_available and find_recursive_construction(k,n):
987
+ _OA_cache_set(k,n,True)
988
+ if existence:
989
+ return True
990
+ f,args = find_recursive_construction(k,n)
991
+ if explain_construction:
992
+ return f(*args,explain_construction=True)
993
+ OA = f(*args)
994
+
995
+ else:
996
+ _OA_cache_set(k,n,Unknown)
997
+ if existence:
998
+ return Unknown
999
+ elif explain_construction:
1000
+ return "No idea"
1001
+ raise NotImplementedError("I don't know how to build an OA({},{})!".format(k,n))
1002
+
1003
+ if check:
1004
+ assert is_orthogonal_array(OA,k,n,t,verbose=1), "Sage built an incorrect OA({},{}) O_o".format(k,n)
1005
+
1006
+ return OA
1007
+
1008
+
1009
+ def largest_available_k(n, t=2):
1010
+ r"""
1011
+ Return the largest `k` such that Sage can build an `OA(k,n)`.
1012
+
1013
+ INPUT:
1014
+
1015
+ - ``n`` -- integer
1016
+
1017
+ - ``t`` -- integer (default: 2); strength of the array
1018
+
1019
+ EXAMPLES::
1020
+
1021
+ sage: designs.orthogonal_arrays.largest_available_k(0)
1022
+ +Infinity
1023
+ sage: designs.orthogonal_arrays.largest_available_k(1)
1024
+ +Infinity
1025
+ sage: designs.orthogonal_arrays.largest_available_k(10)
1026
+ 4
1027
+ sage: designs.orthogonal_arrays.largest_available_k(27)
1028
+ 28
1029
+ sage: designs.orthogonal_arrays.largest_available_k(100)
1030
+ 10
1031
+ sage: designs.orthogonal_arrays.largest_available_k(-1)
1032
+ Traceback (most recent call last):
1033
+ ...
1034
+ ValueError: n(=-1) was expected to be >=0
1035
+ """
1036
+ from .block_design import projective_plane
1037
+ if n < 0:
1038
+ raise ValueError("n(={}) was expected to be >=0".format(n))
1039
+ if t < 0:
1040
+ raise ValueError("t(={}) was expected to be >=0".format(t))
1041
+ if n == 0 or n == 1:
1042
+ from sage.rings.infinity import Infinity
1043
+ return Infinity
1044
+ elif t == 2:
1045
+ if projective_plane(n,existence=True) is True:
1046
+ return n+1
1047
+ else:
1048
+ k = 1
1049
+ while _OA_cache_construction_available(k+1,n) is True:
1050
+ k = k+1
1051
+ else:
1052
+ k = t-1
1053
+
1054
+ while orthogonal_array(k+1,n,t,existence=True) is True:
1055
+ k += 1
1056
+ return k
1057
+
1058
+
1059
+ def incomplete_orthogonal_array(k, n, holes, resolvable=False, existence=False):
1060
+ r"""
1061
+ Return an `OA(k,n)-\sum_{1\leq i\leq x} OA(k,s_i)`.
1062
+
1063
+ An `OA(k,n)-\sum_{1\leq i\leq x} OA(k,s_i)` is an orthogonal array from
1064
+ which have been removed disjoint `OA(k,s_1),...,OA(k,s_x)`. If there exist
1065
+ `OA(k,s_1),...,OA(k,s_x)` they can be used to fill the holes and give rise
1066
+ to an `OA(k,n)`.
1067
+
1068
+ A very useful particular case (see e.g. the Wilson construction in
1069
+ :func:`wilson_construction`) is when all `s_i=1`. In that case the
1070
+ incomplete design is a `OA(k,n)-x.OA(k,1)`. Such design is equivalent to
1071
+ transversal design `TD(k,n)` from which has been removed `x` disjoint
1072
+ blocks.
1073
+
1074
+ INPUT:
1075
+
1076
+ - ``k``, ``n`` -- integers
1077
+
1078
+ - ``holes`` -- list of integers respective sizes of the holes to be found
1079
+
1080
+ - ``resolvable`` -- boolean (default: ``False``); set to ``True`` if you
1081
+ want the design to be resolvable. The classes of the resolvable design
1082
+ are obtained as the first `n` blocks, then the next `n` blocks, etc.
1083
+
1084
+ - ``existence`` -- boolean; instead of building the design, return:
1085
+
1086
+ - ``True`` -- meaning that Sage knows how to build the design
1087
+
1088
+ - ``Unknown`` -- meaning that Sage does not know how to build the
1089
+ design, but that the design may exist (see :mod:`sage.misc.unknown`)
1090
+
1091
+ - ``False`` -- meaning that the design does not exist
1092
+
1093
+ .. NOTE::
1094
+
1095
+ By convention, the ground set is always `V = \{0, ..., n-1\}`.
1096
+
1097
+ If all holes have size 1, in the incomplete orthogonal array returned by
1098
+ this function the holes are `\{n-1, ..., n-s_1\}^k`,
1099
+ `\{n-s_1-1,...,n-s_1-s_2\}^k`, etc.
1100
+
1101
+ More generally, if ``holes`` is equal to `u1,...,uk`, the `i`-th hole is
1102
+ the set of points `\{n-\sum_{j\geq i}u_j,...,n-\sum_{j\geq i+1}u_j\}^k`.
1103
+
1104
+ .. SEEALSO::
1105
+
1106
+ :func:`OA_find_disjoint_blocks`
1107
+
1108
+ EXAMPLES::
1109
+
1110
+ sage: IOA = designs.incomplete_orthogonal_array(3,3,[1,1,1])
1111
+ sage: IOA
1112
+ [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]]
1113
+ sage: missing_blocks = [[0,0,0],[1,1,1],[2,2,2]]
1114
+ sage: from sage.combinat.designs.orthogonal_arrays import is_orthogonal_array
1115
+ sage: is_orthogonal_array(IOA + missing_blocks,3,3,2)
1116
+ True
1117
+
1118
+ TESTS:
1119
+
1120
+ Affine planes and projective planes::
1121
+
1122
+ sage: for q in range(2,100):
1123
+ ....: if is_prime_power(q):
1124
+ ....: assert designs.incomplete_orthogonal_array(q,q,[1]*q,existence=True)
1125
+ ....: assert not designs.incomplete_orthogonal_array(q+1,q,[1]*2,existence=True)
1126
+
1127
+ Further tests::
1128
+
1129
+ sage: designs.incomplete_orthogonal_array(8,4,[1,1,1],existence=True)
1130
+ False
1131
+ sage: designs.incomplete_orthogonal_array(5,10,[1,1,1],existence=True)
1132
+ Unknown
1133
+ sage: designs.incomplete_orthogonal_array(5,10,[1,1,1])
1134
+ Traceback (most recent call last):
1135
+ ...
1136
+ NotImplementedError: I don't know how to build an OA(5,10)!
1137
+ sage: designs.incomplete_orthogonal_array(4,3,[1,1])
1138
+ Traceback (most recent call last):
1139
+ ...
1140
+ EmptySetError: There is no OA(n+1,n) - 2.OA(n+1,1) as all blocks intersect in a projective plane.
1141
+ sage: n=10
1142
+ sage: k=designs.orthogonal_arrays.largest_available_k(n)
1143
+ sage: designs.incomplete_orthogonal_array(k,n,[1,1,1],existence=True)
1144
+ True
1145
+ sage: _ = designs.incomplete_orthogonal_array(k,n,[1,1,1])
1146
+ sage: _ = designs.incomplete_orthogonal_array(k,n,[1])
1147
+
1148
+ A resolvable `OA(k,n)-n.OA(k,1)`. We check that extending each class and
1149
+ adding the `[i,i,...]` blocks turns it into an `OA(k+1,n)`.::
1150
+
1151
+ sage: from sage.combinat.designs.orthogonal_arrays import is_orthogonal_array
1152
+ sage: k,n=5,7
1153
+ sage: OA = designs.incomplete_orthogonal_array(k,n,[1]*n,resolvable=True)
1154
+ sage: classes = [OA[i*n:(i+1)*n] for i in range(n-1)]
1155
+ sage: for classs in classes: # The design is resolvable !
1156
+ ....: assert(len(set(col))==n for col in zip(*classs))
1157
+ sage: OA.extend([[i]*(k) for i in range(n)])
1158
+ sage: for i,R in enumerate(OA):
1159
+ ....: R.append(i//n)
1160
+ sage: is_orthogonal_array(OA,k+1,n)
1161
+ True
1162
+
1163
+ Non-existent resolvable incomplete OA::
1164
+
1165
+ sage: designs.incomplete_orthogonal_array(9,13,[1]*10,resolvable=True,existence=True)
1166
+ False
1167
+ sage: designs.incomplete_orthogonal_array(9,13,[1]*10,resolvable=True)
1168
+ Traceback (most recent call last):
1169
+ ...
1170
+ EmptySetError: There is no resolvable incomplete OA(9,13) whose holes' sizes sum to 10<n(=13)
1171
+
1172
+ Error message for big holes::
1173
+
1174
+ sage: designs.incomplete_orthogonal_array(6,4*9,[9,9,8])
1175
+ Traceback (most recent call last):
1176
+ ...
1177
+ NotImplementedError: I was not able to build this OA(6,36)-OA(6,8)-2.OA(6,9)
1178
+
1179
+ 10 holes of size 9 through the product construction::
1180
+
1181
+ sage: # long time
1182
+ sage: iOA = designs.incomplete_orthogonal_array(10,153,[9]*10)
1183
+ sage: OA9 = designs.orthogonal_arrays.build(10,9)
1184
+ sage: for i in range(10):
1185
+ ....: iOA.extend([[153-9*(i+1)+x for x in B] for B in OA9])
1186
+ sage: is_orthogonal_array(iOA,10,153)
1187
+ True
1188
+
1189
+ An `OA(9,82)-OA(9,9)-OA(9,1)`::
1190
+
1191
+ sage: ioa = designs.incomplete_orthogonal_array(9,82,[9,1])
1192
+ sage: ioa.extend([[x+72 for x in B] for B in designs.orthogonal_arrays.build(9,9)])
1193
+ sage: ioa.extend([[x+81 for x in B] for B in designs.orthogonal_arrays.build(9,1)])
1194
+ sage: is_orthogonal_array(ioa,9,82,verbose=1)
1195
+ True
1196
+
1197
+ An `OA(9,82)-OA(9,9)-2.OA(9,1)` in different orders::
1198
+
1199
+ sage: ioa = designs.incomplete_orthogonal_array(9,82,[1,9,1])
1200
+ sage: ioa.extend([[x+71 for x in B] for B in designs.orthogonal_arrays.build(9,1)])
1201
+ sage: ioa.extend([[x+72 for x in B] for B in designs.orthogonal_arrays.build(9,9)])
1202
+ sage: ioa.extend([[x+81 for x in B] for B in designs.orthogonal_arrays.build(9,1)])
1203
+ sage: is_orthogonal_array(ioa,9,82,verbose=1)
1204
+ True
1205
+ sage: ioa = designs.incomplete_orthogonal_array(9,82,[9,1,1])
1206
+ sage: ioa.extend([[x+71 for x in B] for B in designs.orthogonal_arrays.build(9,9)])
1207
+ sage: ioa.extend([[x+80 for x in B] for B in designs.orthogonal_arrays.build(9,1)])
1208
+ sage: ioa.extend([[x+81 for x in B] for B in designs.orthogonal_arrays.build(9,1)])
1209
+ sage: is_orthogonal_array(ioa,9,82,verbose=1)
1210
+ True
1211
+
1212
+ Three holes of size 1::
1213
+
1214
+ sage: ioa = designs.incomplete_orthogonal_array(3,6,[1,1,1])
1215
+ sage: ioa.extend([[i]*3 for i in [3,4,5]])
1216
+ sage: is_orthogonal_array(ioa,3,6,verbose=1)
1217
+ True
1218
+ """
1219
+ from sage.combinat.designs.database import QDM
1220
+ for h in holes:
1221
+ if h < 0:
1222
+ raise ValueError("Holes must have size >=0, but {} was in the list").format(h)
1223
+
1224
+ holes = [h for h in holes if h > 0]
1225
+
1226
+ if not holes:
1227
+ return orthogonal_array(k,n,existence=existence,resolvable=resolvable)
1228
+
1229
+ sum_of_holes = sum(holes)
1230
+ number_of_holes = len(holes)
1231
+ max_hole = max(holes)
1232
+ min_hole = min(holes)
1233
+
1234
+ if sum_of_holes > n:
1235
+ if existence:
1236
+ return False
1237
+ raise EmptySetError("The total size of holes must be smaller or equal than the size of the ground set")
1238
+
1239
+ if (max_hole == 1 and
1240
+ resolvable and
1241
+ sum_of_holes != n):
1242
+ if existence:
1243
+ return False
1244
+ raise EmptySetError("There is no resolvable incomplete OA({},{}) whose holes' sizes sum to {}<n(={})".format(k, n, sum_of_holes, n))
1245
+
1246
+ # resolvable OA(k,n)-n.OA(k,1) ==> equivalent to OA(k+1,n)
1247
+ if max_hole == 1 and resolvable:
1248
+ if existence:
1249
+ return orthogonal_array(k+1,n,existence=True)
1250
+
1251
+ OA = sorted(orthogonal_array(k+1,n))
1252
+ OA = [B[1:] for B in OA]
1253
+
1254
+ # We now relabel the points so that the last n blocks are the [i,i,...]
1255
+ relabel = [[0]*n for _ in range(k)]
1256
+ for i,B in enumerate(OA[-n:]):
1257
+ for ii,xx in enumerate(B):
1258
+ relabel[ii][xx] = i
1259
+
1260
+ OA = [[relabel[i][xx] for i,xx in enumerate(B)] for B in OA]
1261
+
1262
+ # Let's drop the last blocks
1263
+ assert all(OA[-n+i] == [i]*k for i in range(n)), "The last n blocks should be [i,i,...]"
1264
+ return OA[:-n]
1265
+
1266
+ # Easy case
1267
+ elif max_hole == 1 and number_of_holes <= 1:
1268
+ if existence:
1269
+ return orthogonal_array(k,n,existence=True)
1270
+ OA = orthogonal_array(k,n)
1271
+ independent_set = OA[:number_of_holes]
1272
+
1273
+ # This is lemma 2.3 from [BvR1982]_
1274
+ #
1275
+ # If k>3 and n>(k-1)u and there exists an OA(k,n)-OA(k,u), then there exists
1276
+ # an OA(k,n)-OA(k,u)-2.OA(k,1)
1277
+ elif (k >= 3 and
1278
+ 2 <= number_of_holes <= 3 and
1279
+ n > (k-1)*max_hole and
1280
+ holes.count(1) == number_of_holes-1 and
1281
+ incomplete_orthogonal_array(k,n,[max_hole],existence=True)):
1282
+ if existence:
1283
+ return True
1284
+
1285
+ # The 1<=?<=2 other holes of size 1 can be picked greedily as the
1286
+ # conflict graph is regular and not complete (see proof of lemma 2.3)
1287
+ #
1288
+ # This code is a bit awkward for max_hole may be equal to 1, and the
1289
+ # holes have to be correctly ordered in the output.
1290
+ IOA = incomplete_orthogonal_array(k,n,[max_hole])
1291
+
1292
+ # place the big hole where it belongs
1293
+ i = holes.index(max_hole)
1294
+ holes[i] = [[ii]*k for ii in range(n-max_hole,n)]
1295
+
1296
+ # place the first hole of size 1
1297
+ i = holes.index(1)
1298
+ for h1 in IOA:
1299
+ if all(x < n-max_hole for x in h1):
1300
+ break
1301
+ holes[i] = [h1]
1302
+ IOA.remove(h1)
1303
+
1304
+ # place the potential second hole of size 1
1305
+ if number_of_holes == 3:
1306
+ i = holes.index(1)
1307
+ for h2 in IOA:
1308
+ if all(h1[j] != x and x < n-max_hole for j,x in enumerate(h2)):
1309
+ break
1310
+ holes[i] = [h2]
1311
+ IOA.remove(h2)
1312
+
1313
+ holes = sum(holes, [])
1314
+ holes = [list(h) for h in zip(*holes)]
1315
+
1316
+ # Building the relabel matrix
1317
+ for l in holes:
1318
+ for i in range(n):
1319
+ if i not in l:
1320
+ l.insert(0,i)
1321
+ for i in range(len(holes)):
1322
+ holes[i] = {v:i for i,v in enumerate(holes[i])}
1323
+
1324
+ IOA = OA_relabel(IOA,k,n,matrix=holes)
1325
+ return IOA
1326
+
1327
+ elif max_hole == 1 and number_of_holes >= 2 and k == n+1:
1328
+ if existence:
1329
+ return False
1330
+ raise EmptySetError(("There is no OA(n+1,n) - {}.OA(n+1,1) as all blocks "
1331
+ "intersect in a projective plane.").format(number_of_holes))
1332
+
1333
+ # Holes of size 1 from OA(k+1,n)
1334
+ elif max_hole == 1 and orthogonal_array(k+1,n,existence=True) is True:
1335
+ if existence:
1336
+ return True
1337
+ OA = orthogonal_array(k+1,n)
1338
+ independent_set = [B[:-1] for B in OA if B[-1] == 0][:number_of_holes]
1339
+ OA = [B[:-1] for B in OA]
1340
+
1341
+ elif max_hole == 1 and orthogonal_array(k,n,existence=True) is True:
1342
+ OA = orthogonal_array(k,n)
1343
+ try:
1344
+ independent_set = OA_find_disjoint_blocks(OA,k,n,number_of_holes)
1345
+ except ValueError:
1346
+ if existence:
1347
+ return Unknown
1348
+ raise NotImplementedError("I was not able to build this OA({},{})-{}.OA({},1)".format(k,n,number_of_holes,k))
1349
+ if existence:
1350
+ return True
1351
+ independent_set = OA_find_disjoint_blocks(OA,k,n,number_of_holes)
1352
+
1353
+ elif max_hole == 1 and orthogonal_array(k,n,existence=True) is not True:
1354
+ return orthogonal_array(k,n,existence=existence)
1355
+
1356
+ # From a quasi-difference matrix
1357
+ elif (number_of_holes == 1 and
1358
+ any(uu == sum_of_holes and mu <= 1 and lmbda == 1 and k <= kk + 1
1359
+ for (nn,lmbda,mu,uu),(kk,_) in QDM.get((n,1),{}).items())):
1360
+ for (nn,lmbda,mu,uu),(kk,f) in QDM[n,1].items():
1361
+ if uu == sum_of_holes and mu <= 1 and lmbda == 1 and k <= kk + 1:
1362
+ break
1363
+ G,M = f()
1364
+ OA = OA_from_quasi_difference_matrix(M,G,fill_hole=False)
1365
+ return [B[:k] for B in OA]
1366
+
1367
+ # Equal holes [h,h,...] with h>1 through OA product construction
1368
+ #
1369
+ # (i.e. OA(k,n1)-x.OA(k,1) and OA(k,n2) ==> OA(k,n1.n2)-x.OA(k,n2) )
1370
+ elif (min_hole > 1 and
1371
+ max_hole == min_hole and
1372
+ n % min_hole == 0 and # h divides n
1373
+ orthogonal_array(k,min_hole,existence=True) and # OA(k,h)
1374
+ incomplete_orthogonal_array(k,n//min_hole,[1]*number_of_holes,existence=True)): # OA(k,n/h)-x.OA(k,1)
1375
+ if existence:
1376
+ return True
1377
+ h = min_hole
1378
+ iOA1 = incomplete_orthogonal_array(k,n//holes[0],[1]*number_of_holes)
1379
+ iOA2 = orthogonal_array(k,h)
1380
+
1381
+ return [[B1[i]*h+B2[i] for i in range(k)]
1382
+ for B1 in iOA1
1383
+ for B2 in iOA2]
1384
+ else:
1385
+ if existence:
1386
+ return Unknown
1387
+ # format the list of holes
1388
+ f = lambda x: "" if x == 1 else "{}.".format(x)
1389
+ holes_string = "".join("-{}OA({},{})".format(f(holes.count(x)),k,x) for x in sorted(set(holes)))
1390
+ raise NotImplementedError("I was not able to build this OA({},{}){}".format(k,n,holes_string))
1391
+
1392
+ assert number_of_holes == len(independent_set)
1393
+
1394
+ for B in independent_set:
1395
+ OA.remove(B)
1396
+
1397
+ OA = OA_relabel(OA,k,n,blocks=independent_set)
1398
+
1399
+ return OA
1400
+
1401
+
1402
+ def OA_find_disjoint_blocks(OA, k, n, x,
1403
+ *, solver=None, integrality_tolerance=1e-3):
1404
+ r"""
1405
+ Return `x` disjoint blocks contained in a given `OA(k,n)`.
1406
+
1407
+ `x` blocks of an `OA` are said to be disjoint if they all have
1408
+ different values for a every given index, i.e. if they correspond to
1409
+ disjoint blocks in the `TD` associated with the `OA`.
1410
+
1411
+ INPUT:
1412
+
1413
+ - ``OA`` -- an orthogonal array
1414
+
1415
+ - ``k``, ``n``, ``x`` -- integers
1416
+
1417
+ - ``solver`` -- (default: ``None``) specify a Mixed Integer Linear
1418
+ Programming (MILP) solver to be used. If set to ``None``, the default one
1419
+ is used. For more information on MILP solvers and which default solver is
1420
+ used, see the method :meth:`solve
1421
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1422
+ :class:`MixedIntegerLinearProgram
1423
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
1424
+
1425
+ - ``integrality_tolerance`` -- parameter for use with MILP solvers over an
1426
+ inexact base ring; see :meth:`MixedIntegerLinearProgram.get_values`
1427
+
1428
+ .. SEEALSO::
1429
+
1430
+ :func:`incomplete_orthogonal_array`
1431
+
1432
+ EXAMPLES::
1433
+
1434
+ sage: from sage.combinat.designs.orthogonal_arrays import OA_find_disjoint_blocks
1435
+ sage: k=3;n=4;x=3
1436
+ sage: Bs = OA_find_disjoint_blocks(designs.orthogonal_arrays.build(k,n),k,n,x)
1437
+ sage: assert len(Bs) == x
1438
+ sage: for i in range(k):
1439
+ ....: assert len(set([B[i] for B in Bs])) == x
1440
+ sage: OA_find_disjoint_blocks(designs.orthogonal_arrays.build(k,n),k,n,5)
1441
+ Traceback (most recent call last):
1442
+ ...
1443
+ ValueError: There does not exist 5 disjoint blocks in this OA(3,4)
1444
+ """
1445
+ # Computing an independent set of order x with a Linear Program
1446
+ from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException
1447
+ p = MixedIntegerLinearProgram(solver=solver)
1448
+ b = p.new_variable(binary=True)
1449
+ p.add_constraint(p.sum(b[i] for i in range(len(OA))) == x)
1450
+
1451
+ # t[i][j] lists of blocks of the OA whose i'th component is j
1452
+ t = [[[] for _ in range(n)] for _ in range(k)]
1453
+ for c,B in enumerate(OA):
1454
+ for i,j in enumerate(B):
1455
+ t[i][j].append(c)
1456
+
1457
+ for R in t:
1458
+ for L in R:
1459
+ p.add_constraint(p.sum(b[i] for i in L) <= 1)
1460
+
1461
+ try:
1462
+ p.solve()
1463
+ except MIPSolverException:
1464
+ raise ValueError("There does not exist {} disjoint blocks in this OA({},{})".format(x,k,n))
1465
+
1466
+ b = p.get_values(b, convert=bool, tolerance=integrality_tolerance)
1467
+ independent_set = [OA[i] for i,v in b.items() if v]
1468
+ return independent_set
1469
+
1470
+
1471
+ def OA_relabel(OA, k, n, blocks=tuple(), matrix=None, symbol_list=None):
1472
+ r"""
1473
+ Return a relabelled version of the OA.
1474
+
1475
+ INPUT:
1476
+
1477
+ - ``OA`` -- an OA, or rather a list of blocks of length `k`, each
1478
+ of which contains integers from `0` to `n-1`
1479
+
1480
+ - ``k``, ``n`` -- integers
1481
+
1482
+ - ``blocks`` -- list of blocks; relabels the integers of the OA
1483
+ from `[0..n-1]` into `[0..n-1]` in such a way that the `i`
1484
+ blocks from ``block`` are respectively relabeled as
1485
+ ``[n-i,...,n-i]``, ..., ``[n-1,...,n-1]``. Thus, the blocks from
1486
+ this list are expected to have disjoint values for each
1487
+ coordinate.
1488
+
1489
+ If set to the empty list (default) no such relabelling is
1490
+ performed.
1491
+
1492
+ - ``matrix`` -- a matrix of dimensions `k,n` such that if the i th
1493
+ coordinate of a block is `x`, this `x` will be relabelled with
1494
+ ``matrix[i][x]``. This is not necessarily an integer between `0`
1495
+ and `n-1`, and it is not necessarily an integer either. This is
1496
+ performed *after* the previous relabelling.
1497
+
1498
+ If set to ``None`` (default) no such relabelling is performed.
1499
+
1500
+ - ``symbol_list`` -- list of the desired symbols for the
1501
+ relabelled OA. If this is not ``None``, the same relabelling is
1502
+ done on all blocks such that the index of an element in
1503
+ symbol_list is its preimage in the relabelling map.
1504
+
1505
+ .. NOTE::
1506
+
1507
+ A ``None`` coordinate in one block remains a ``None``
1508
+ coordinate in the final block.
1509
+
1510
+ EXAMPLES::
1511
+
1512
+ sage: from sage.combinat.designs.orthogonal_arrays import OA_relabel
1513
+ sage: OA = designs.orthogonal_arrays.build(3,2)
1514
+ sage: OA_relabel(OA,3,2,matrix=[["A","B"],["C","D"],["E","F"]])
1515
+ [['A', 'C', 'E'], ['A', 'D', 'F'], ['B', 'C', 'F'], ['B', 'D', 'E']]
1516
+
1517
+ sage: TD = OA_relabel(OA,3,2,matrix=[[0,1],[2,3],[4,5]]); TD
1518
+ [[0, 2, 4], [0, 3, 5], [1, 2, 5], [1, 3, 4]]
1519
+ sage: from sage.combinat.designs.orthogonal_arrays import is_transversal_design
1520
+ sage: is_transversal_design(TD,3,2)
1521
+ True
1522
+
1523
+ sage: OA = designs.orthogonal_arrays.build(3,2)
1524
+ sage: OA_relabel(OA, 3, 2, symbol_list=['A', 'B'])
1525
+ [['A', 'A', 'A'], ['A', 'B', 'B'], ['B', 'A', 'B'], ['B', 'B', 'A']]
1526
+
1527
+ Making sure that ``[2,2,2,2]`` is a block of `OA(4,3)`. We do this
1528
+ by relabelling block ``[0,0,0,0]`` which belongs to the design::
1529
+
1530
+ sage: designs.orthogonal_arrays.build(4,3)
1531
+ [[0, 0, 0, 0], [0, 1, 2, 1], [0, 2, 1, 2], [1, 0, 2, 2], [1, 1, 1, 0], [1, 2, 0, 1], [2, 0, 1, 1], [2, 1, 0, 2], [2, 2, 2, 0]]
1532
+ sage: OA_relabel(designs.orthogonal_arrays.build(4,3),4,3,blocks=[[0,0,0,0]])
1533
+ [[2, 2, 2, 2], [2, 0, 1, 0], [2, 1, 0, 1], [0, 2, 1, 1], [0, 0, 0, 2], [0, 1, 2, 0], [1, 2, 0, 0], [1, 0, 2, 1], [1, 1, 1, 2]]
1534
+
1535
+ TESTS::
1536
+
1537
+ sage: OA_relabel(designs.orthogonal_arrays.build(3,2),3,2,blocks=[[0,1],[0,1]])
1538
+ Traceback (most recent call last):
1539
+ ...
1540
+ RuntimeError: Two block have the same coordinate for one of the k dimensions
1541
+ """
1542
+ if blocks:
1543
+ l = []
1544
+ for i, B in enumerate(zip(*blocks)): # the blocks are disjoint
1545
+ if len(B) != len(set(B)):
1546
+ raise RuntimeError("Two block have the same coordinate for one of the k dimensions")
1547
+
1548
+ l.append(dict(zip([xx for xx in range(n) if xx not in B] + list(B),range(n))))
1549
+
1550
+ OA = [[l[i][x] for i,x in enumerate(R)] for R in OA]
1551
+
1552
+ if matrix:
1553
+ OA = [[matrix[i][j] if j is not None else None for i,j in enumerate(R)] for R in OA]
1554
+
1555
+ if symbol_list:
1556
+ mapping = dict(enumerate(symbol_list))
1557
+ OA = [[mapping[element] for element in row] for row in OA]
1558
+ return OA
1559
+
1560
+
1561
+ def OA_standard_label(OA):
1562
+ r"""
1563
+ Return the inputted OA with entries relabelled as integers [0,...,n-1].
1564
+
1565
+ INPUT:
1566
+
1567
+ - ``OA`` -- list of lists with symbols as entries that are not
1568
+ necessarily integers
1569
+
1570
+ EXAMPLES::
1571
+
1572
+ sage: from sage.combinat.designs.orthogonal_arrays import OA_standard_label
1573
+ sage: C = [['a', 'a', 'a', 'b'],
1574
+ ....: ['a', 'a', 'b', 'a'],
1575
+ ....: ['a', 'b', 'a', 'a'],
1576
+ ....: ['b', 'a', 'a', 'a'],
1577
+ ....: ['b', 'b', 'b', 'b']]
1578
+ sage: OA_standard_label(C)
1579
+ [[0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0], [1, 0, 0, 0], [1, 1, 1, 1]]
1580
+ """
1581
+ symbol_list = sorted({x for l in OA for x in l})
1582
+ mapping = {symbol: index for index, symbol in enumerate(symbol_list)}
1583
+ return [[mapping[element] for element in row] for row in OA]
1584
+
1585
+
1586
+ def OA_n_times_2_pow_c_from_matrix(k, c, G, A, Y, check=True):
1587
+ r"""
1588
+ Return an `OA(k, |G| \cdot 2^c)` from a constrained `(G,k-1,2)`-difference
1589
+ matrix.
1590
+
1591
+ This construction appears in [AC1994]_ and [Ab1995]_.
1592
+
1593
+ Let `G` be an additive Abelian group. We denote by `H` a `GF(2)`-hyperplane
1594
+ in `GF(2^c)`.
1595
+
1596
+ Let `A` be a `(k-1) \times 2|G|` array with entries in `G \times GF(2^c)`
1597
+ and `Y` be a vector with `k-1` entries in `GF(2^c)`. Let `B` and `C` be
1598
+ respectively the part of the array that belong to `G` and `GF(2^c)`.
1599
+
1600
+ The input `A` and `Y` must satisfy the following conditions. For any `i \neq
1601
+ j` and `g \in G`:
1602
+
1603
+ - there are exactly two values of `s` such that `B_{i,s} - B_{j,s} = g`
1604
+ (i.e. `B` is a `(G,k-1,2)`-difference matrix),
1605
+
1606
+ - let `s_1` and `s_2` denote the two values of `s` given above, then exactly
1607
+ one of `C_{i,s_1} - C_{j,s_1}` and `C_{i,s_2} - C_{j,s_2}` belongs to the
1608
+ `GF(2)`-hyperplane `(Y_i - Y_j) \cdot H` (we implicitly assumed that `Y_i
1609
+ \not= Y_j`).
1610
+
1611
+ Under these conditions, it is easy to check that the array whose `k-1` rows
1612
+ of length `|G|\cdot 2^c` indexed by `1 \leq i \leq k-1` given by `A_{i,s} +
1613
+ (0, Y_i \cdot v)` where `1\leq s \leq 2|G|,v\in H` is a `(G \times
1614
+ GF(2^c),k-1,1)`-difference matrix.
1615
+
1616
+ INPUT:
1617
+
1618
+ - ``k``, ``c`` -- integers
1619
+
1620
+ - ``G`` -- an additive Abelian group
1621
+
1622
+ - ``A`` -- a matrix with entries in `G \times GF(2^c)`
1623
+
1624
+ - ``Y`` -- a vector with entries in `GF(2^c)`
1625
+
1626
+ - ``check`` -- boolean (default: ``True``); whether to check that output is
1627
+ correct before returning it. As this is expected to be useless, you may
1628
+ want to disable it whenever you want speed.
1629
+
1630
+ .. NOTE::
1631
+
1632
+ By convention, a multiplicative generator `w` of `GF(2^c)^*` is fixed
1633
+ (inside the function). The hyperplane `H` is the one spanned by `w^0,
1634
+ w^1, \ldots, w^{c-1}`. The `GF(2^c)` part of the input matrix `A` and
1635
+ vector `Y` are given in the following form: the integer `i` corresponds
1636
+ to the element `w^i` and ``None`` corresponds to `0`.
1637
+
1638
+ .. SEEALSO::
1639
+
1640
+ Several examples use this construction:
1641
+
1642
+ - :func:`~sage.combinat.designs.database.OA_9_40`
1643
+ - :func:`~sage.combinat.designs.database.OA_11_80`
1644
+ - :func:`~sage.combinat.designs.database.OA_15_112`
1645
+ - :func:`~sage.combinat.designs.database.OA_11_160`
1646
+ - :func:`~sage.combinat.designs.database.OA_16_176`
1647
+ - :func:`~sage.combinat.designs.database.OA_16_208`
1648
+ - :func:`~sage.combinat.designs.database.OA_15_224`
1649
+ - :func:`~sage.combinat.designs.database.OA_20_352`
1650
+ - :func:`~sage.combinat.designs.database.OA_20_416`
1651
+ - :func:`~sage.combinat.designs.database.OA_20_544`
1652
+ - :func:`~sage.combinat.designs.database.OA_11_640`
1653
+ - :func:`~sage.combinat.designs.database.OA_15_896`
1654
+
1655
+ EXAMPLES::
1656
+
1657
+ sage: from sage.combinat.designs.orthogonal_arrays import OA_n_times_2_pow_c_from_matrix
1658
+ sage: from sage.combinat.designs.designs_pyx import is_orthogonal_array
1659
+ sage: A = [
1660
+ ....: [(0,None),(0,None),(0,None),(0,None),(0,None),(0,None),(0,None),(0,None),(0,None),(0,None)],
1661
+ ....: [(0,None),(1,None), (2,2), (3,2), (4,2),(2,None),(3,None),(4,None), (0,2), (1,2)],
1662
+ ....: [(0,None), (2,5), (4,5), (1,2), (3,6), (3,4), (0,0), (2,1), (4,1), (1,6)],
1663
+ ....: [(0,None), (3,4), (1,4), (4,0), (2,5),(3,None), (1,0), (4,1), (2,2), (0,3)],
1664
+ ....: ]
1665
+ sage: Y = [None, 0, 1, 6]
1666
+ sage: OA = OA_n_times_2_pow_c_from_matrix(5,3,GF(5),A,Y)
1667
+ sage: is_orthogonal_array(OA,5,40,2)
1668
+ True
1669
+
1670
+ sage: A[0][0] = (1,None)
1671
+ sage: OA_n_times_2_pow_c_from_matrix(5,3,GF(5),A,Y)
1672
+ Traceback (most recent call last):
1673
+ ...
1674
+ ValueError: the first part of the matrix A must be a
1675
+ (G,k-1,2)-difference matrix
1676
+
1677
+ sage: A[0][0] = (0,0)
1678
+ sage: OA_n_times_2_pow_c_from_matrix(5,3,GF(5),A,Y)
1679
+ Traceback (most recent call last):
1680
+ ...
1681
+ ValueError: B_2,0 - B_0,0 = B_2,6 - B_0,6 but the associated part of the
1682
+ matrix C does not satisfies the required condition
1683
+ """
1684
+ from sage.rings.finite_rings.finite_field_constructor import FiniteField
1685
+ from itertools import combinations
1686
+ from .designs_pyx import is_difference_matrix
1687
+
1688
+ G_card = G.cardinality()
1689
+
1690
+ if len(A) != k-1 or any(len(a) != 2*G_card for a in A):
1691
+ raise ValueError("A must be a (k-1) x (2|G|) array")
1692
+ if len(Y) != k-1:
1693
+ raise ValueError("Y must be a (k-1)-vector")
1694
+
1695
+ F = FiniteField(2**c,'w')
1696
+ GG = G.cartesian_product(F)
1697
+
1698
+ # dictionary from integers to elements of GF(2^c): i -> w^i, None -> 0
1699
+ w = F.multiplicative_generator()
1700
+ r = {i:w**i for i in range(2**c-1)}
1701
+ r[None] = F.zero()
1702
+
1703
+ # check that the first part of the matrix A is a (G,k-1,2)-difference matrix
1704
+ B = [[G(a) for a,b in R] for R in A]
1705
+ if check and not is_difference_matrix(list(zip(*B)),G,k-1,2):
1706
+ raise ValueError("the first part of the matrix A must be a "
1707
+ "(G,k-1,2)-difference matrix")
1708
+
1709
+ # convert:
1710
+ # the matrix A to a matrix over G \times GF(2^c)
1711
+ # the vector Y to a vector over GF(2^c)
1712
+ A = [[GG((G(a),r[b])) for a,b in R] for R in A]
1713
+ Y = [r[b] for b in Y]
1714
+
1715
+ # make the list of the elements of GF(2^c) which belong to the
1716
+ # GF(2)-subspace <w^0,...,w^(c-2)> (that is the GF(2)-hyperplane orthogonal
1717
+ # to w^(c-1))
1718
+ H = [sum((r[i] for i in S), F.zero()) for s in range(c) for S in combinations(range(c-1),s)]
1719
+ assert len(H) == 2**(c-1)
1720
+
1721
+ # check that the second part of the matrix A satisfy the conditions
1722
+ if check:
1723
+ G_card = G.cardinality()
1724
+ for i in range(len(B)):
1725
+ for j in range(i):
1726
+ g_to_col_indices = {g: [] for g in G}
1727
+ YY = Y[i] - Y[j]
1728
+ Hij = {YY * v for v in H}
1729
+ for s in range(2 * G_card):
1730
+ g_to_col_indices[B[i][s] - B[j][s]].append(s)
1731
+ for s1, s2 in g_to_col_indices.values():
1732
+ v1 = A[i][s1][1] - A[j][s1][1]
1733
+ v2 = A[i][s2][1] - A[j][s2][1]
1734
+
1735
+ if (v1 in Hij) == (v2 in Hij):
1736
+ raise ValueError("B_{},{} - B_{},{} = B_{},{} - B_{},{} but"
1737
+ " the associated part of the matrix C does not satisfies"
1738
+ " the required condition".format(i,s1,j,s1,i,s2,j,s2))
1739
+
1740
+ # build the quasi difference matrix and return the associated OA
1741
+ Mb = [[e+GG((G.zero(),x*v)) for v in H for e in R] for x, R in zip(Y, A)]
1742
+ return OA_from_quasi_difference_matrix(list(zip(*Mb)),GG,add_col=True)
1743
+
1744
+
1745
+ def OA_from_quasi_difference_matrix(M, G, add_col=True, fill_hole=True):
1746
+ r"""
1747
+ Return an Orthogonal Array from a Quasi-Difference matrix.
1748
+
1749
+ **Difference Matrices**
1750
+
1751
+ Let `G` be a group of order `g`. A *difference matrix* `M` is a `g\times k`
1752
+ matrix with entries from `G` such that for any `1\leq i < j < k` the set
1753
+ `\{d_{li}-d_{lj}:1\leq l \leq g\}` is equal to `G`.
1754
+
1755
+ By concatenating the `g` matrices `M+x` (where `x\in G`), one obtains a
1756
+ matrix of size `g^2\times x` which is also an `OA(k,g)`.
1757
+
1758
+ **Quasi-difference Matrices**
1759
+
1760
+ A quasi-difference matrix is a difference matrix with missing entries. The
1761
+ construction above can be applied again in this case, where the missing
1762
+ entries in each column of `M` are replaced by unique values on which `G` has
1763
+ a trivial action.
1764
+
1765
+ This produces an incomplete orthogonal array with a "hole" (i.e. missing
1766
+ rows) of size 'u' (i.e. the number of missing values per column of `M`). If
1767
+ there exists an `OA(k,u)`, then adding the rows of this `OA(k,u)` to the
1768
+ incomplete orthogonal array should lead to an OA...
1769
+
1770
+ **Formal definition** (from the Handbook of Combinatorial Designs [DesignHandbook]_)
1771
+
1772
+ Let `G` be an abelian group of order `n`. A
1773
+ `(n,k;\lambda,\mu;u)`-quasi-difference matrix (QDM) is a matrix `Q=(q_{ij})`
1774
+ with `\lambda(n-1+2u)+\mu` rows and `k` columns, with each entry either
1775
+ empty or containing an element of `G`. Each column contains exactly `\lambda
1776
+ u` entries, and each row contains at most one empty entry. Furthermore, for
1777
+ each `1 \leq i < j \leq k` the multiset
1778
+
1779
+ .. MATH::
1780
+
1781
+ \{ q_{li} - q_{lj}: 1 \leq l \leq \lambda (n-1+2u)+\mu, \text{ with }q_{li}\text{ and }q_{lj}\text{ not empty}\}
1782
+
1783
+ contains every nonzero element of `G` exactly `\lambda` times, and contains
1784
+ 0 exactly `\mu` times.
1785
+
1786
+ **Construction**
1787
+
1788
+ If a `(n,k;\lambda,\mu;u)`-QDM exists and `\mu \leq \lambda`, then an
1789
+ `ITD_\lambda (k,n+u;u)` exists. Start with a `(n,k;\lambda,\mu;u)`-QDM `A`
1790
+ over the group `G`. Append `\lambda-\mu` rows of zeroes. Then select `u`
1791
+ elements `\infty_1,\dots,\infty_u` not in `G`, and replace the empty
1792
+ entries, each by one of these infinite symbols, so that `\infty_i` appears
1793
+ exactly once in each column. Develop the resulting matrix over the group `G`
1794
+ (leaving infinite symbols fixed), to obtain a `\lambda (n^2+2nu)\times k`
1795
+ matrix `T`. Then `T` is an orthogonal array with `k` columns and index
1796
+ `\lambda`, having `n+u` symbols and one hole of size `u`.
1797
+
1798
+ Adding to `T` an `OA(k,u)` with elements `\infty_1,\dots,\infty_u` yields
1799
+ the `ITD_\lambda(k,n+u;u)`.
1800
+
1801
+ For more information, see the Handbook of Combinatorial Designs
1802
+ [DesignHandbook]_ or
1803
+ `<http://web.cs.du.edu/~petr/milehigh/2013/Colbourn.pdf>`_.
1804
+
1805
+ INPUT:
1806
+
1807
+ - ``M`` -- the difference matrix whose entries belong to ``G``
1808
+
1809
+ - ``G`` -- a group
1810
+
1811
+ - ``add_col`` -- boolean; whether to add a column to the final OA equal to
1812
+ `(x_1,\dots,x_g,x_1,\dots,x_g,\dots)` where `G=\{x_1,\dots,x_g\}`
1813
+
1814
+ - ``fill_hole`` -- boolean; whether to return the incomplete orthogonal
1815
+ array, or complete it with the `OA(k,u)` (default). When ``fill_hole is
1816
+ None``, no block of the incomplete OA contains more than one value `\geq
1817
+ |G|`.
1818
+
1819
+ EXAMPLES::
1820
+
1821
+ sage: _ = designs.orthogonal_arrays.build(6,20) # indirect doctest
1822
+ """
1823
+ Gn = int(G.cardinality())
1824
+ k = len(M[0])+bool(add_col)
1825
+
1826
+ G_to_int = {x:i for i,x in enumerate(G)}
1827
+
1828
+ # A cache for addition in G
1829
+ G_sum = [[0] * Gn for _ in range(Gn)]
1830
+ for x, i in G_to_int.items():
1831
+ for xx, ii in G_to_int.items():
1832
+ G_sum[i][ii] = G_to_int[x + xx]
1833
+
1834
+ # Convert M to integers
1835
+ M = [[None if x is None else G_to_int[G(x)] for x in line] for line in M]
1836
+
1837
+ # Each line is expanded by [g+x for x in line for g in G] then relabeled
1838
+ # with integers. Missing values are also handled.
1839
+ new_M = []
1840
+ for line in zip(*M):
1841
+ inf = Gn
1842
+ new_line = []
1843
+ for x in line:
1844
+ if x is None:
1845
+ new_line.extend([inf]*Gn)
1846
+ inf = inf + 1
1847
+ else:
1848
+ new_line.extend(G_sum[x])
1849
+ new_M.append(new_line)
1850
+
1851
+ if add_col:
1852
+ new_M.append([i//Gn for i in range(len(new_line))])
1853
+
1854
+ # new_M = transpose(new_M)
1855
+ new_M = list(zip(*new_M))
1856
+
1857
+ # Filling holes with a smaller orthogonal array
1858
+ if inf > Gn and fill_hole:
1859
+ for L in orthogonal_array(k,inf-Gn,2):
1860
+ new_M.append(tuple([x+Gn for x in L]))
1861
+
1862
+ return new_M
1863
+
1864
+
1865
+ def OA_from_Vmt(m, t, V):
1866
+ r"""
1867
+ Return an Orthogonal Array from a `V(m,t)`.
1868
+
1869
+ INPUT:
1870
+
1871
+ - ``m``, ``t`` -- integers
1872
+
1873
+ - ``V`` -- the vector `V(m,t)`
1874
+
1875
+ .. SEEALSO::
1876
+
1877
+ - :func:`QDM_from_Vmt`
1878
+
1879
+ - :func:`OA_from_quasi_difference_matrix`
1880
+
1881
+ EXAMPLES::
1882
+
1883
+ sage: _ = designs.orthogonal_arrays.build(6,46) # indirect doctest
1884
+ """
1885
+ Fq, M = QDM_from_Vmt(m,t,V)
1886
+ return OA_from_quasi_difference_matrix(M,Fq,add_col=False)
1887
+
1888
+
1889
+ def QDM_from_Vmt(m, t, V):
1890
+ r"""
1891
+ Return a QDM from a `V(m,t)`.
1892
+
1893
+ **Definition**
1894
+
1895
+ Let `q` be a prime power and let `q=mt+1` for `m,t` integers. Let `\omega`
1896
+ be a primitive element of `\GF{q}`. A `V(m,t)` vector is a vector
1897
+ `(a_1,\dots,a_{m+1}` for which, for each `1\leq k < m`, the differences
1898
+
1899
+ .. MATH::
1900
+
1901
+ \{a_{i+k}-a_i:1\leq i \leq m+1,i+k\neq m+2\}
1902
+
1903
+ represent the `m` cyclotomic classes of `\GF{mt+1}` (compute subscripts
1904
+ modulo `m+2`). In other words, for fixed `k`, is
1905
+ `a_{i+k}-a_i=\omega^{mx+\alpha}` and `a_{j+k}-a_j=\omega^{my+\beta}` then
1906
+ `\alpha\not\equiv\beta \mod{m}`
1907
+
1908
+ *Construction of a quasi-difference matrix from a `V(m,t)` vector*
1909
+
1910
+ Starting with a `V(m,t)` vector `(a_1,\dots,a_{m+1})`, form a single row of
1911
+ length `m+2` whose first entry is empty, and whose remaining entries are
1912
+ `(a_1,\dots,a_{m+1})`. Form `t` rows by multiplying this row by the `t` th
1913
+ roots, i.e. the powers of `\omega^m`. From each of these `t` rows, form
1914
+ `m+2` rows by taking the `m+2` cyclic shifts of the row. The result is a
1915
+ `(a,m+2;1,0;t)-QDM`.
1916
+
1917
+ For more information, refer to the Handbook of Combinatorial Designs
1918
+ [DesignHandbook]_.
1919
+
1920
+ INPUT:
1921
+
1922
+ - ``m``, ``t`` -- integers
1923
+
1924
+ - ``V`` -- the vector `V(m,t)`
1925
+
1926
+ .. SEEALSO::
1927
+
1928
+ :func:`OA_from_quasi_difference_matrix`
1929
+
1930
+ EXAMPLES::
1931
+
1932
+ sage: _ = designs.orthogonal_arrays.build(6,46) # indirect doctest
1933
+ """
1934
+ from sage.rings.finite_rings.finite_field_constructor import FiniteField
1935
+ q = m*t+1
1936
+ Fq = FiniteField(q, 'x')
1937
+ w = Fq.multiplicative_generator()
1938
+
1939
+ M = []
1940
+ wm = w**m
1941
+ for i in range(t):
1942
+ L = [None]
1943
+ for e in V:
1944
+ L.append(e*wm**i)
1945
+ for ii in range(m+2):
1946
+ M.append(L[-ii:]+L[:-ii]) # cyclic shift
1947
+
1948
+ M.append([0]*(m+2))
1949
+
1950
+ return Fq, M
1951
+
1952
+
1953
+ def OA_from_PBD(k, n, PBD, check=True):
1954
+ r"""
1955
+ Return an `OA(k,n)` from a PBD.
1956
+
1957
+ **Construction**
1958
+
1959
+ Let `\mathcal B` be a `(n,K,1)`-PBD. If there exists for every `i\in K` a
1960
+ `TD(k,i)-i\times TD(k,1)` (i.e. if there exist `k` idempotent MOLS), then
1961
+ one can obtain a `OA(k,n)` by concatenating:
1962
+
1963
+ - A `TD(k,i)-i\times TD(k,1)` defined over the elements of `B` for every `B
1964
+ \in \mathcal B`.
1965
+
1966
+ - The rows `(i,...,i)` of length `k` for every `i\in [n]`.
1967
+
1968
+ .. NOTE::
1969
+
1970
+ This function raises an exception when Sage is unable to build the
1971
+ necessary designs.
1972
+
1973
+ INPUT:
1974
+
1975
+ - ``k``, ``n`` -- integers
1976
+
1977
+ - ``PBD`` -- a PBD on `0, \ldots, n-1`
1978
+
1979
+ EXAMPLES:
1980
+
1981
+ We start from the example VI.1.2 from the [DesignHandbook]_ to build an
1982
+ `OA(3,10)`::
1983
+
1984
+ sage: from sage.combinat.designs.orthogonal_arrays import OA_from_PBD
1985
+ sage: from sage.combinat.designs.designs_pyx import is_orthogonal_array
1986
+ sage: pbd = [[0,1,2,3],[0,4,5,6],[0,7,8,9],[1,4,7],[1,5,8],
1987
+ ....: [1,6,9],[2,4,9],[2,5,7],[2,6,8],[3,4,8],[3,5,9],[3,6,7]]
1988
+ sage: oa = OA_from_PBD(3,10,pbd)
1989
+ sage: is_orthogonal_array(oa, 3, 10)
1990
+ True
1991
+
1992
+ But we cannot build an `OA(4,10)` for this PBD (although there
1993
+ exists an `OA(4,10)`::
1994
+
1995
+ sage: OA_from_PBD(4,10,pbd)
1996
+ Traceback (most recent call last):
1997
+ ...
1998
+ EmptySetError: There is no OA(n+1,n) - 3.OA(n+1,1)
1999
+ as all blocks intersect in a projective plane.
2000
+
2001
+ Or an `OA(3,6)` (as the PBD has 10 points)::
2002
+
2003
+ sage: _ = OA_from_PBD(3,6,pbd)
2004
+ Traceback (most recent call last):
2005
+ ...
2006
+ RuntimeError: PBD is not a valid Pairwise Balanced Design on [0,...,5]
2007
+ """
2008
+ # Size of the sets of the PBD
2009
+ K = set(map(len,PBD))
2010
+
2011
+ if check:
2012
+ from .designs_pyx import is_pairwise_balanced_design
2013
+ if not is_pairwise_balanced_design(PBD, n, K):
2014
+ raise RuntimeError("PBD is not a valid Pairwise Balanced Design on [0,...,{}]".format(n-1))
2015
+
2016
+ # Building the IOA
2017
+ OAs = {i:incomplete_orthogonal_array(k,i,(1,)*i) for i in K}
2018
+
2019
+ OA = []
2020
+ # For every block B of the PBD we add to the OA rows covering all pairs of
2021
+ # (distinct) coordinates within the elements of B.
2022
+ for S in PBD:
2023
+ for B in OAs[len(S)]:
2024
+ OA.append([S[i] for i in B])
2025
+
2026
+ # Adding the 0..0, 1..1, 2..2 .... rows
2027
+ for i in range(n):
2028
+ OA.append([i]*k)
2029
+
2030
+ if check:
2031
+ assert is_orthogonal_array(OA,k,n,2)
2032
+
2033
+ return OA
2034
+
2035
+
2036
+ def OA_from_wider_OA(OA, k):
2037
+ r"""
2038
+ Return the first `k` columns of `OA`.
2039
+
2040
+ If `OA` has `k` columns, this function returns `OA` immediately.
2041
+
2042
+ INPUT:
2043
+
2044
+ - ``OA`` -- an orthogonal array
2045
+
2046
+ - ``k`` -- integer
2047
+
2048
+ EXAMPLES::
2049
+
2050
+ sage: from sage.combinat.designs.orthogonal_arrays import OA_from_wider_OA
2051
+ sage: OA_from_wider_OA(designs.orthogonal_arrays.build(6,20,2),1)[:5]
2052
+ [(19,), (19,), (19,), (19,), (19,)]
2053
+ sage: _ = designs.orthogonal_arrays.build(5,46) # indirect doctest
2054
+ """
2055
+ if len(OA[0]) == k:
2056
+ return OA
2057
+ return [L[:k] for L in OA]
2058
+
2059
+
2060
+ class OAMainFunctions:
2061
+ r"""
2062
+ Functions related to orthogonal arrays.
2063
+
2064
+ An orthogonal array of parameters `k,n,t` is a matrix with `k` columns
2065
+ filled with integers from `[n]` in such a way that for any `t` columns, each
2066
+ of the `n^t` possible rows occurs exactly once. In particular, the matrix
2067
+ has `n^t` rows.
2068
+
2069
+ For more information on orthogonal arrays, see
2070
+ :wikipedia:`Orthogonal_array`.
2071
+
2072
+ From here you have access to:
2073
+
2074
+ - :meth:`build(k,n,t=2) <build>`: return an orthogonal array with the given
2075
+ parameters.
2076
+ - :meth:`is_available(k,n,t=2) <is_available>`: answer whether there is a
2077
+ construction available in Sage for a given set of parameters.
2078
+ - :meth:`exists(k,n,t=2) <exists>`: answer whether an orthogonal array with
2079
+ these parameters exist.
2080
+ - :meth:`largest_available_k(n,t=2) <largest_available_k>`: return the
2081
+ largest integer `k` such that Sage knows how to build an `OA(k,n)`.
2082
+ - :meth:`explain_construction(k,n,t=2) <explain_construction>`: return a
2083
+ string that explains the construction that Sage uses to build an
2084
+ `OA(k,n)`.
2085
+
2086
+ EXAMPLES::
2087
+
2088
+ sage: designs.orthogonal_arrays.build(3,2)
2089
+ [[0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 0]]
2090
+
2091
+ sage: designs.orthogonal_arrays.build(5,5)
2092
+ [[0, 0, 0, 0, 0], [0, 1, 2, 3, 4], [0, 2, 4, 1, 3],
2093
+ [0, 3, 1, 4, 2], [0, 4, 3, 2, 1], [1, 0, 4, 3, 2],
2094
+ [1, 1, 1, 1, 1], [1, 2, 3, 4, 0], [1, 3, 0, 2, 4],
2095
+ [1, 4, 2, 0, 3], [2, 0, 3, 1, 4], [2, 1, 0, 4, 3],
2096
+ [2, 2, 2, 2, 2], [2, 3, 4, 0, 1], [2, 4, 1, 3, 0],
2097
+ [3, 0, 2, 4, 1], [3, 1, 4, 2, 0], [3, 2, 1, 0, 4],
2098
+ [3, 3, 3, 3, 3], [3, 4, 0, 1, 2], [4, 0, 1, 2, 3],
2099
+ [4, 1, 3, 0, 2], [4, 2, 0, 3, 1], [4, 3, 2, 1, 0],
2100
+ [4, 4, 4, 4, 4]]
2101
+
2102
+ What is the largest value of `k` for which Sage knows how to compute a
2103
+ `OA(k,14,2)`?::
2104
+
2105
+ sage: designs.orthogonal_arrays.largest_available_k(14)
2106
+ 6
2107
+
2108
+ If you ask for an orthogonal array that does not exist, then you will
2109
+ either obtain an :exc:`EmptySetError` (if it knows that such an orthogonal
2110
+ array does not exist) or a :exc:`NotImplementedError`::
2111
+
2112
+ sage: designs.orthogonal_arrays.build(4,2)
2113
+ Traceback (most recent call last):
2114
+ ...
2115
+ EmptySetError: There exists no OA(4,2) as k(=4)>n+t-1=3
2116
+ sage: designs.orthogonal_arrays.build(12,20)
2117
+ Traceback (most recent call last):
2118
+ ...
2119
+ NotImplementedError: I don't know how to build an OA(12,20)!
2120
+ """
2121
+ def __init__(self, *args, **kwds):
2122
+ r"""
2123
+ There is nothing here.
2124
+
2125
+ TESTS::
2126
+
2127
+ sage: designs.orthogonal_arrays(4,5) # indirect doctest
2128
+ Traceback (most recent call last):
2129
+ ...
2130
+ RuntimeError: This is not a function but a class. You want to call the designs.orthogonal_arrays.* functions
2131
+ """
2132
+ raise RuntimeError("This is not a function but a class. You want to call the designs.orthogonal_arrays.* functions")
2133
+
2134
+ largest_available_k = staticmethod(largest_available_k)
2135
+
2136
+ @staticmethod
2137
+ def explain_construction(k, n, t=2):
2138
+ r"""
2139
+ Return a string describing how to builds an `OA(k,n)`.
2140
+
2141
+ INPUT:
2142
+
2143
+ - ``k``, ``n``, ``t`` -- integers; parameters of the orthogonal array
2144
+
2145
+ EXAMPLES::
2146
+
2147
+ sage: designs.orthogonal_arrays.explain_construction(9,565)
2148
+ "Wilson's construction n=23.24+13 with master design OA(9+1,23)"
2149
+ sage: designs.orthogonal_arrays.explain_construction(10,154)
2150
+ 'the database contains a (137,10;1,0;17)-quasi difference matrix'
2151
+ """
2152
+ return orthogonal_array(k,n,t,explain_construction=True)
2153
+
2154
+ @staticmethod
2155
+ def build(k, n, t=2, resolvable=False):
2156
+ r"""
2157
+ Return an `OA(k,n)` of strength `t`.
2158
+
2159
+ An orthogonal array of parameters `k,n,t` is a matrix with `k`
2160
+ columns filled with integers from `[n]` in such a way that for any
2161
+ `t` columns, each of the `n^t` possible rows occurs exactly
2162
+ once. In particular, the matrix has `n^t` rows.
2163
+
2164
+ More general definitions sometimes involve a `\lambda` parameter, and we
2165
+ assume here that `\lambda=1`.
2166
+
2167
+ For more information on orthogonal arrays, see
2168
+ :wikipedia:`Orthogonal_array`.
2169
+
2170
+ INPUT:
2171
+
2172
+ - ``k``, ``n``, ``t`` -- integers; parameters of the orthogonal array
2173
+
2174
+ - ``resolvable`` -- boolean (default: ``False``); set to ``True`` if
2175
+ you want the design to be resolvable. The `n` classes of the
2176
+ resolvable design are obtained as the first `n` blocks, then the next
2177
+ `n` blocks, etc.
2178
+
2179
+ EXAMPLES::
2180
+
2181
+ sage: designs.orthogonal_arrays.build(3,3,resolvable=True) # indirect doctest
2182
+ [[0, 0, 0],
2183
+ [1, 2, 1],
2184
+ [2, 1, 2],
2185
+ [0, 2, 2],
2186
+ [1, 1, 0],
2187
+ [2, 0, 1],
2188
+ [0, 1, 1],
2189
+ [1, 0, 2],
2190
+ [2, 2, 0]]
2191
+ sage: OA_7_50 = designs.orthogonal_arrays.build(7,50) # indirect doctest
2192
+ """
2193
+ return orthogonal_array(k,n,t,resolvable=resolvable)
2194
+
2195
+ @staticmethod
2196
+ def exists(k, n, t=2):
2197
+ r"""
2198
+ Return the existence status of an `OA(k,n)`.
2199
+
2200
+ INPUT:
2201
+
2202
+ - ``k``, ``n``, ``t`` -- integers; parameters of the orthogonal array
2203
+
2204
+ .. WARNING::
2205
+
2206
+ The function does not only return booleans, but ``True``,
2207
+ ``False``, or ``Unknown``.
2208
+
2209
+ .. SEEALSO::
2210
+
2211
+ :meth:`is_available`
2212
+
2213
+ EXAMPLES::
2214
+
2215
+ sage: designs.orthogonal_arrays.exists(3,6) # indirect doctest
2216
+ True
2217
+ sage: designs.orthogonal_arrays.exists(4,6) # indirect doctest
2218
+ Unknown
2219
+ sage: designs.orthogonal_arrays.exists(7,6) # indirect doctest
2220
+ False
2221
+ """
2222
+ return orthogonal_array(k,n,t,existence=True)
2223
+
2224
+ @staticmethod
2225
+ def is_available(k, n, t=2):
2226
+ r"""
2227
+ Return whether Sage can build an `OA(k,n)`.
2228
+
2229
+ INPUT:
2230
+
2231
+ - ``k``, ``n``, ``t`` -- integers; parameters of the orthogonal array
2232
+
2233
+ .. SEEALSO::
2234
+
2235
+ :meth:`exists`
2236
+
2237
+ EXAMPLES::
2238
+
2239
+ sage: designs.orthogonal_arrays.is_available(3,6) # indirect doctest
2240
+ True
2241
+ sage: designs.orthogonal_arrays.is_available(4,6) # indirect doctest
2242
+ False
2243
+ """
2244
+ return orthogonal_array(k,n,t,existence=True) is True