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