diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index bc96846353c..4179c6ad773 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2018,6 +2018,11 @@ REFERENCES: Lexicographically: an efficient new strategy compared with others, 2020, https://hal.archives-ouvertes.fr/hal-02462764v1 +.. [DGK2014] \D. Đoković, O. Golubitsky and I.Kotsireas. + *Some New Orders of Hadamard and Skew-Hadamard Matrices*, + Journal of Combinatorial Designs 22(6) (2014): 270-277. + :doi:`10.1002/jcd.21358` + .. [DGMPPS2019] \N. Datta, A. Ghoshal, D. Mukhopadhyay, S. Patranabis, S. Picek, R. Sashukhan. "TRIFLE" https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/trifle-spec.pdf @@ -2105,6 +2110,12 @@ REFERENCES: .. [DK2013] John R. Doyle and David Krumm, *Computing algebraic numbers of bounded height*, :arxiv:`1111.4963v4` (2013). +.. [DK2016] \D. Ðokovic, I. Kotsireas. + *A class of cyclic (v; k1, k2, k3; λ) difference families + with v = 3 (mod 4) a prime*, + Special Matrices 4(1) (2016): 317-325. + :doi:`10.1515/spma-2016-0029` + .. [DLHK2007] \J. A. De Loera, D. C. Haws, M. Köppe, Ehrhart polynomials of matroid polytopes and polymatroids. Discrete & Computational Geometry, Volume @@ -5431,6 +5442,10 @@ REFERENCES: *Wide-open encryption design offers flexible implementations*; in Cryptologia, (1985), pp. 75-91. +.. [Seb1978] \J. Seberry. + *On Skew Hadamard Matrices*, + Ars Combinatoria 6 (1978): 255-276. + .. [Seb2017] \J. Seberry, *Orthogonal designs: Hadamard matrices, quadratic forms and algebras*. Springer 2017. :doi:`10.1007/978-3-319-59032-5` @@ -5579,6 +5594,16 @@ REFERENCES: :doi:`10.1007/978-1-4684-9322-1`, ISBN 978-1-4684-9322-1. +.. [Spe1975] \E. Spence. + *Hadamard matrices from relative difference sets*, + Journal of Combinatorial Theory, Series A 19(3) (1975): 287-300. + :doi:`10.1016/0097-3165(75)90054-0` + +.. [Spe1975b] \E. Spence. + *Skew-Hadamard Matrices of the Goethals-Seidel Type*, + Canadian Journal of Mathematics 27(3) (1975): 555-560. + :doi:`10.4153/cjm-1975-066-9` + .. [Spe1977] \E. Spence. *Skew-Hadamard matrices of order 2(q + 1)*, Discrete Mathematics 18(1) (1977): 79-85. @@ -5724,11 +5749,6 @@ REFERENCES: matrices, and characteristic polynomials without division* :doi:`10.1023/A:1021878507303` -.. [Spe1975] \E. Spence. - *Hadamard matrices from relative difference sets*, - Journal of Combinatorial Theory, Series A 19(3) (1975): 287-300. - :doi:`10.1016/0097-3165(75)90054-0` - .. [ST1981] \J. J. Seidel and D. E. Taylor, *Two-graphs, a second survey*. Algebraic methods in graph theory, Vol. I, II (Szeged, 1978), @@ -5933,6 +5953,11 @@ REFERENCES: Hall-Littlewood vertex operators and generalized Kostka polynomials. Adv. Math. 158 (2001), no. 1, 66-85. +.. [Sze1971] \G. Szekeres. + *Cyclotomy and complementary difference sets*, + Acta Arithmetica 18 (1971): 349-353. + :doi:`10.4064/aa-18-1-349-353` + .. [Sze1988] \G. Szekeres. *A note on skew type orthogonal ±1 matrices*, Combinatorics, Colloquia Mathematica Societatis, Janos Bolyai, 52 (1988): 489-498. @@ -6151,6 +6176,11 @@ REFERENCES: .. [Wal1970] David W. Walkup, "The lower bound conjecture for 3- and 4-manifolds", Acta Math. 125 (1970), 75-107. +.. [Wal1970b] \J. Wallis. + *(v, k, λ) Configurations and Hadamard matrices*, + Journal of the Australian Mathematical Society 11(3) (1970): 297-309. + :doi:`10.1017/S1446788700006674` + .. [Wal2001] Timothy Walsh, *Gray codes for involutions*, J. Combin. Math. Combin. Comput. **36** (2001), 95-118. http://www.info2.uqam.ca/~walsh_t/papers/Involutions%20paper.pdf diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index b486183cf6c..5dcaa816df2 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -95,9 +95,8 @@ def block_stabilizer(G, B): INPUT: - - ``G`` -- a group (additive or multiplicative). - - - ``B`` -- a subset of ``G``. + - ``G`` -- a group (additive or multiplicative) + - ``B`` -- a subset of ``G`` EXAMPLES:: @@ -137,12 +136,10 @@ def is_difference_family(G, D, v=None, k=None, l=None, verbose=False): INPUT: - ``G`` -- group of cardinality ``v`` - - ``D`` -- a set of ``k``-subsets of ``G`` - - ``v``, ``k`` and ``l`` -- optional parameters of the difference family - - - ``verbose`` - whether to print additional information + - ``verbose`` -- boolean (default: ``False``); whether to print additional + information .. SEEALSO:: @@ -708,9 +705,8 @@ def one_radical_difference_family(K, k): INPUT: - - ``K`` -- a finite field of cardinality `q`. - - - ``k`` -- a positive integer so that `k(k-1)` divides `q-1`. + - ``K`` -- a finite field of cardinality `q` + - ``k`` -- a positive integer so that `k(k-1)` divides `q-1` OUTPUT: @@ -833,18 +829,14 @@ def radical_difference_family(K, k, l=1, existence=False, check=True): INPUT: - ``K`` - a finite field - - - ``k`` -- positive integer, the size of the blocks - - - ``l`` -- the `\lambda` parameter (default to `1`) - + - ``k`` -- positive integer; the size of the blocks + - ``l`` -- integer (default: ``1``); the `\lambda` parameter - ``existence`` -- if ``True``, then return either ``True`` if Sage knows how to build such design, ``Unknown`` if it does not and ``False`` if it - knows that the design does not exist. - - - ``check`` -- boolean (default: ``True``). If ``True`` then the result of + knows that the design does not exist + - ``check`` -- boolean (default: ``True``); if ``True`` then the result of the computation is checked before being returned. This should not be - needed but ensures that the output is correct. + needed but ensures that the output is correct EXAMPLES:: @@ -946,9 +938,9 @@ def twin_prime_powers_difference_set(p, check=True): INPUT: - - ``check`` -- boolean (default: ``True``). If ``True`` then the result of + - ``check`` -- boolean (default: ``True``); if ``True``, then the result of the computation is checked before being returned. This should not be - needed but ensures that the output is correct. + needed but ensures that the output is correct EXAMPLES:: @@ -995,13 +987,12 @@ def are_mcfarland_1973_parameters(v, k, lmbda, return_parameters=False): INPUT: - - ``v``, ``k``, ``lmbda`` - (integers) parameters of the difference family - - - ``return_parameters`` -- (boolean, default ``False``) if ``True`` return a + - ``v``, ``k``, ``lmbda`` - integers; parameters of the difference family + - ``return_parameters`` -- boolean (default ``False``); if ``True``, return a pair ``(True, (q, s))`` so that ``(q,s)`` can be used in the function :func:`mcfarland_1973_construction` to actually build a ``(v,k,lmbda)``-difference family. Or ``(False, None)`` if the - construction is not possible. + construction is not possible EXAMPLES:: @@ -1074,7 +1065,7 @@ def mcfarland_1973_construction(q, s): INPUT: - - ``q``, ``s`` - (integers) parameters for the difference set (see the above + - ``q``, ``s`` - integers; parameters for the difference set (see the above formulas for the expression of ``v``, ``k``, ``l`` in terms of ``q`` and ``s``) @@ -1234,7 +1225,7 @@ def turyn_1965_3x3xK(k=4): - ``k`` -- either ``2`` (to get a difference set in `C_3 \times C_3 \times C_2 \times C_2`) or ``4`` (to get a difference set in `C_3 \times C_3 - \times C_3 \times C_4`). + \times C_3 \times C_4`) EXAMPLES:: @@ -1266,16 +1257,16 @@ def turyn_1965_3x3xK(k=4): def _is_periodic_sequence(seq, period): - r"""Check if the sequence is periodic with correct period. + r""" + Check if the sequence is periodic with correct period. The sequence should have length at least twice the period, so that periodicity can be checked. INPUT: - - ``seq`` -- the sequence to be tested (must have length at least twice the period). - - - ``period`` -- integer, the period that the sequence should have. + - ``seq`` -- the sequence to be tested (must have length at least twice the period) + - ``period`` -- integer; the period that the sequence should have EXAMPLES:: @@ -1303,7 +1294,8 @@ def _is_periodic_sequence(seq, period): return True def _create_m_sequence(q, n, check=True): - r"""Create an m-sequence over GF(q) with period `q^n - 1`. + r""" + Create an m-sequence over GF(q) with period `q^n - 1`. Given a prime power `q`, the m-sequence is created as described by [Zie1959]_ from a primitive function over the finite field `GF(q)`. @@ -1315,12 +1307,11 @@ def _create_m_sequence(q, n, check=True): INPUT: - - ``q`` -- a prime power. - - - ``n`` -- a nonnegative number. - - - ``check`` -- boolean (default True): if true, check that the result is a seqauence with correct period. - Setting it to false may speed up considerably the computation. + - ``q`` -- a prime power + - ``n`` -- a nonnegative number + - ``check`` -- boolean (default: ``True``); if ``True``, check that the + result is a sequence with correct period; detting it to ``False`` may + speed up considerably the computation EXAMPLES:: @@ -1366,16 +1357,16 @@ def _create_m_sequence(q, n, check=True): return seq[:period] def _get_submodule_of_order(G, order): - r"""Construct a submodule of the given order from group `G`. + r""" + Construct a submodule of the given order from group ``G``. This method tries to construct submodules from various elements of `G` until a submodule of the correct order is found. INPUT: - - ``G`` -- an additive abelian group. - - - ``order`` -- integer, the order of the desired syubmodule. + - ``G`` -- an additive abelian group + - ``order`` -- integer; the order of the desired submodule TESTS: @@ -1393,8 +1384,9 @@ def _get_submodule_of_order(G, order): return H return None -def relative_difference_set_from_m_sequence(q, N, check=True): - r"""Construct `R((q^N-1)/(q-1), q-1, q^{N-1}, q^{N-2})` where `q` is a prime power and `N\ge 2`. +def relative_difference_set_from_m_sequence(q, N, check=True, return_group=False): + r""" + Construct `R((q^N-1)/(q-1), q-1, q^{N-1}, q^{N-2})` where ``q`` is a prime power and `N\ge 2`. The relative difference set is constructed over the set of additive integers modulo `q^N-1`, as described in Theorem 5.1 of [EB1966]_. Given an m-sequence `(a_i)` of period `q^N-1`, the @@ -1402,18 +1394,24 @@ def relative_difference_set_from_m_sequence(q, N, check=True): INPUT: - - ``q`` -- a prime power. + - ``q`` -- a prime power + - ``N`` -- a nonnegative number + - ``check`` -- boolean (default: ``True``); if ``True``, check that the + result is a relative difference set before returning it + - ``return_group`` -- boolean (default: ``False``); if ``True``, the function + will also return the group from which the set is created - - ``N`` -- a nonnegative number. + OUTPUT: - - ``check`` -- boolean (default True). If true, check that the result is a relative difference - set before returning it. + If ``return_group=False``, the function return only the relative difference + set. Otherwise, it returns a tuple containing the group and the set. EXAMPLES:: sage: from sage.combinat.designs.difference_family import relative_difference_set_from_m_sequence - sage: relative_difference_set_from_m_sequence(2, 4) #random - [(0), (4), (5), (6), (7), (9), (11), (12)] + sage: relative_difference_set_from_m_sequence(2, 4, return_group=True) #random + (Additive abelian group isomorphic to Z/15, + [(0), (4), (5), (6), (7), (9), (11), (12)]) sage: relative_difference_set_from_m_sequence(8, 2, check=False) #random [(0), (6), (30), (40), (41), (44), (56), (61)] sage: relative_difference_set_from_m_sequence(6, 2) @@ -1425,14 +1423,14 @@ def relative_difference_set_from_m_sequence(q, N, check=True): sage: from sage.combinat.designs.difference_family import is_relative_difference_set, _get_submodule_of_order sage: q, N = 5, 3 - sage: G = AdditiveAbelianGroup([q^N-1]) + sage: G, D = relative_difference_set_from_m_sequence(q, N, check=False, return_group=True) sage: H = _get_submodule_of_order(G, q-1) - sage: is_relative_difference_set(relative_difference_set_from_m_sequence(q, N), G, H, ((q^N-1)//(q-1), q-1, q^(N-1), q^(N-2))) + sage: is_relative_difference_set(D, G, H, ((q^N-1)//(q-1), q-1, q^(N-1), q^(N-2))) True sage: q, N = 13, 2 - sage: G = AdditiveAbelianGroup([q^N-1]) + sage: G, D = relative_difference_set_from_m_sequence(q, N, check=False, return_group=True) sage: H = _get_submodule_of_order(G, q-1) - sage: is_relative_difference_set(relative_difference_set_from_m_sequence(q, N), G, H, ((q^N-1)//(q-1), q-1, q^(N-1), q^(N-2))) + sage: is_relative_difference_set(D, G, H, ((q^N-1)//(q-1), q-1, q^(N-1), q^(N-2))) True """ from sage.groups.additive_abelian.additive_abelian_group import AdditiveAbelianGroup @@ -1451,32 +1449,41 @@ def relative_difference_set_from_m_sequence(q, N, check=True): if check: H = _get_submodule_of_order(G, q-1) assert is_relative_difference_set(set1, G, H, (period // (q-1), q - 1, q**(N-1), q**(N-2))) + + if return_group: + return G, set1 return set1 -def relative_difference_set_from_homomorphism(q, N, d, check=True): - r"""Construct `R((q^N-1)/(q-1), n, q^{N-1}, q^{N-2}d)` where `nd = q-1`. +def relative_difference_set_from_homomorphism(q, N, d, check=True, return_group=False): + r""" + Construct `R((q^N-1)/(q-1), n, q^{N-1}, q^{N-2}d)` where `nd = q-1`. Given a prime power `q`, a number `N \ge 2` and integers `d` such that `d | q-1` we create the relative difference set using the construction from Corollary 5.1.1 of [EB1966]_. INPUT: - - ``q`` -- a prime power. - - - ``N`` -- an integer greater than 1. + - ``q`` -- a prime power + - ``N`` -- an integer greater than 1 + - ``d`` -- an integer which divides `q-1` + - ``check`` -- boolean (default: ``True``); if ``True``, check that the + result is a relative difference set before returning it + - ``return_group`` -- boolean (default: ``False``); if ``True``, the function + will also return the group from which the set is created - - ``d`` -- an integer which divides `q-1`. + OUTPUT: - - ``check`` -- boolean (default True). If true, check that the result is a relative difference - set before returning it. + If ``return_group=False``, the function return only the relative difference + set. Otherwise, it returns a tuple containing the group and the set. EXAMPLES:: sage: from sage.combinat.designs.difference_family import relative_difference_set_from_homomorphism sage: relative_difference_set_from_homomorphism(7, 2, 3) #random [(0), (3), (4), (2), (13), (7), (14)] - sage: relative_difference_set_from_homomorphism(9, 2, 4, check=False) #random - [(0), (4), (6), (13), (7), (12), (15), (8), (9)] + sage: relative_difference_set_from_homomorphism(9, 2, 4, check=False, return_group=True) #random + (Additive abelian group isomorphic to Z/80, + [(0), (4), (6), (13), (7), (12), (15), (8), (9)]) sage: relative_difference_set_from_homomorphism(9, 2, 5) Traceback (most recent call last): ... @@ -1486,14 +1493,14 @@ def relative_difference_set_from_homomorphism(q, N, d, check=True): sage: from sage.combinat.designs.difference_family import is_relative_difference_set, _get_submodule_of_order sage: q, N, d = 11, 2, 5 - sage: G = AdditiveAbelianGroup([(q^N-1)//d]) + sage: G, D = relative_difference_set_from_homomorphism(q, N, d, check=False, return_group=True) sage: H = _get_submodule_of_order(G, (q-1)//d) - sage: is_relative_difference_set(relative_difference_set_from_homomorphism(q, N, d), G, H, ((q**N-1)//(q-1), (q-1)//d, q**(N-1), q**(N-2)*d)) + sage: is_relative_difference_set(D, G, H, ((q**N-1)//(q-1), (q-1)//d, q**(N-1), q**(N-2)*d)) True sage: q, N, d = 9, 2, 4 - sage: G = AdditiveAbelianGroup([(q^N-1)//d]) + sage: G, D = relative_difference_set_from_homomorphism(q, N, d, check=False, return_group=True) sage: H = _get_submodule_of_order(G, (q-1)//d) - sage: is_relative_difference_set(relative_difference_set_from_homomorphism(q, N, d), G, H, ((q**N-1)//(q-1), (q-1)//d, q**(N-1), q**(N-2)*d)) + sage: is_relative_difference_set(D, G, H, ((q**N-1)//(q-1), (q-1)//d, q**(N-1), q**(N-2)*d)) True """ from sage.groups.additive_abelian.additive_abelian_group import AdditiveAbelianGroup @@ -1518,10 +1525,14 @@ def relative_difference_set_from_homomorphism(q, N, d, check=True): if check: H = _get_submodule_of_order(G2, (q-1) // d) assert is_relative_difference_set(second_diff_set, G2, H, ((q**N-1) // (q-1), (q-1) // d, q**(N-1), q**(N-2) * d)) + + if return_group: + return G2, second_diff_set return second_diff_set def is_relative_difference_set(R, G, H, params, verbose=False): - r"""Check if `R` is a difference set of `G` relative to `H`, with the given parameters. + r""" + Check if ``R`` is a difference set of ``G`` relative to ``H``, with the given parameters. This function checks that `G`, `H` and `R` have the orders specified in the parameters, and that `R` satisfies the definition of relative difference set (from [EB1966]_): the collection of @@ -1530,25 +1541,20 @@ def is_relative_difference_set(R, G, H, params, verbose=False): INPUT: - - ``R`` -- list, the relative diffeence set of length `k`. - - - ``G`` -- an additive abelian group of order `mn`. - - - ``H`` -- a submodule of ``G`` of order `n`. - - - ``params`` -- a tuple in the form `(m, n, k, d)`. - - - ``verbose`` -- boolean (default False). If true the function will be verbose - when the sequences do not satisfy the contraints. + - ``R`` -- list; the relative diffeence set of length `k` + - ``G`` -- an additive abelian group of order `mn` + - ``H`` -- list; a submodule of ``G`` of order `n` + - ``params`` -- a tuple in the form `(m, n, k, d)` + - ``verbose`` -- boolean (default: ``False``); if ``True``, the function + will be verbose when the sequences do not satisfy the contraints EXAMPLES:: sage: from sage.combinat.designs.difference_family import _get_submodule_of_order, relative_difference_set_from_m_sequence, is_relative_difference_set sage: q, N = 5, 2 - sage: G = AdditiveAbelianGroup([q^N - 1]) - sage: H = _get_submodule_of_order(G, q - 1) sage: params = ((q^N-1) // (q-1), q - 1, q^(N-1), q^(N-2)) - sage: R = relative_difference_set_from_m_sequence(q, N) + sage: G, R = relative_difference_set_from_m_sequence(q, N, return_group=True) + sage: H = _get_submodule_of_order(G, q - 1) sage: is_relative_difference_set(R, G, H, params) True @@ -1600,58 +1606,101 @@ def is_relative_difference_set(R, G, H, params, verbose=False): return True -def is_supplementary_difference_set(Ks, v, lmbda): - r"""Check that the sets in ``Ks`` are `n-\{v; k_1, ..., k_n; \lambda \}` supplementary difference sets. - - From the definition in [Spe1975]_: let `S_1, S_2, ..., S_n` be `n` subsets of an additive abelian group `G` of order `v` + +def is_supplementary_difference_set(Ks, v=None, lmbda=None, G=None, verbose=False): + r""" + Check that the sets in ``Ks`` are `n-\{v; k_1, ..., k_n; \lambda \}` supplementary + difference sets over group ``G`` of order ``v``. + + From the definition in [Spe1975]_: let `S_1, S_2, ..., S_n` be `n` subsets of a group `G` of order `v` such that `|S_i| = k_i`. If, for each `g \in G`, `g \neq 0`, the total number of solutions of `a_i - a'_i = g`, with `a_i, a'_i \in S_i` is `\lambda`, then `S_1, S_2, ..., S_n` are `n-\{v; k_1, ..., k_n; \lambda\}` supplementary difference sets. - INPUT: - - - ``Ks`` -- a list of sets to be checked. + One of the parameters ``v`` or ``G`` must always be specified. If ``G`` is not + given, the function will use an ``AdditiveAbelianGroup`` of order ``v``. - - ``v`` -- integer, the parameter `v` of the supplementary difference sets. + INPUT: - - ``lmbda`` -- integer, the parameter `\lambda` of the supplementary difference sets. + - ``Ks`` -- a list of sets to be checked + - ``v`` -- integer; the parameter `v` of the supplementary difference sets + - ``lmbda`` -- integer; the parameter `\lambda` of the supplementary difference sets + - ``G`` -- a group of order `v` + - ``verbose`` -- boolean (default: ``False``); if ``True``, the function will + be verbose when the sets do not satisfy the contraints EXAMPLES:: sage: from sage.combinat.designs.difference_family import supplementary_difference_set_from_rel_diff_set, is_supplementary_difference_set - sage: S1, S2, S3, S4 = supplementary_difference_set_from_rel_diff_set(17) - sage: is_supplementary_difference_set([S1, S2, S3, S4], 16, 16) + sage: G, [S1, S2, S3, S4] = supplementary_difference_set_from_rel_diff_set(17) + sage: is_supplementary_difference_set([S1, S2, S3, S4], lmbda=16, G=G) True - sage: is_supplementary_difference_set([S1, S2, S3, S4], 16, 14) + + The parameter ``v`` can be given instead of ``G``:: + + sage: is_supplementary_difference_set([S1, S2, S3, S4], v=16, lmbda=16) + True + sage: is_supplementary_difference_set([S1, S2, S3, S4], v=20, lmbda=16) + False + + If ``verbose=True``, the function will be verbose:: + + sage: is_supplementary_difference_set([S1, S2, S3, S4], lmbda=14, G=G, verbose=True) + Number of pairs with difference (1) is 16, but lambda is 14 False - sage: is_supplementary_difference_set([S1, S2, S3, S4], 20, 16) + + TESTS:: + + sage: is_supplementary_difference_set([[1], [1]], lmbda=0, G=Zmod(3)) + True + sage: is_supplementary_difference_set([S1, S2, S3, S4], v=17, lmbda=16, G=G) False + sage: is_supplementary_difference_set([S1, S2, S3, S4], G=G) + True + sage: is_supplementary_difference_set([S1, S2, S3, S4], lmbda=16) + Traceback (most recent call last): + ... + ValueError: one of G or v must be specified .. SEEALSO:: :func:`supplementary_difference_set_from_rel_diff_set` """ - from sage.groups.additive_abelian.additive_abelian_group import AdditiveAbelianGroup - G = AdditiveAbelianGroup([v]) - differences_counter = {} + if G is None and v is None: + raise ValueError('one of G or v must be specified') + + if G is None: + from sage.groups.additive_abelian.additive_abelian_group import AdditiveAbelianGroup + G = AdditiveAbelianGroup([v]) + + if v is not None and G.order() != v: + if verbose: + print(f'G has order {G.order()}, but it should be {v}') + return False + + differences_counter = {el: 0 for el in G} for K in Ks: for el1 in K: for el2 in K: - diff = G[el1] - G[el2] - if diff not in differences_counter: - differences_counter[diff] = 0 + diff = G(el1) - G(el2) differences_counter[diff] += 1 - for el in G: - if el == 0: + for key, diff in differences_counter.items(): + if key == 0: continue - elif el not in differences_counter or lmbda != differences_counter[el]: + if lmbda is None: + lmbda = diff + if diff != lmbda: + if verbose: + print(f'Number of pairs with difference {key} is {diff}, but lambda is {lmbda}') return False return True + def supplementary_difference_set_from_rel_diff_set(q, existence=False, check=True): - r"""Construct `4-\{2v; v, v+1, v, v; 2v\}` supplementary difference sets where `q=2v+1`. + r""" + Construct `4-\{2v; v, v+1, v, v; 2v\}` supplementary difference sets where `q=2v+1`. The sets are created from relative difference sets as detailed in Theorem 3.3 of [Spe1975]_. this construction requires that `q` is an odd prime power and that there exists `s \ge 0` such that `(q-(2^{s+1}+1))/2^{s+1}` is @@ -1664,31 +1713,30 @@ def supplementary_difference_set_from_rel_diff_set(q, existence=False, check=Tru INPUT: - - ``q`` -- an odd prime power. - - - ``existence`` -- boolean (dafault False). If true, only check whether the supplementary difference sets - can be constructed. - - - ``check`` -- boolean (default True). If true, check that the sets are supplementary difference sets - before returning them. + - ``q`` -- an odd prime power + - ``existence`` -- boolean (default: ``False``); If ``True``, only check + whether the supplementary difference sets can be constructed + - ``check`` -- boolean (default: ``True``); If ``True``, check that the sets + are supplementary difference sets before returning them OUTPUT: - If ``existence`` is false, the function returns the 4 sets (containing integers), or raises an - error if ``q`` does not satify the constraints. - If ``existence`` is true, the function returns a boolean representing whether supplementary difference - sets can be constructed. + If ``existence=False``, the function returns the 4 sets (containing integers), + or raises an error if ``q`` does not satify the constraints. + If ``existence=True``, the function returns a boolean representing whether + supplementary difference sets can be constructed. EXAMPLES:: sage: from sage.combinat.designs.difference_family import supplementary_difference_set_from_rel_diff_set sage: supplementary_difference_set_from_rel_diff_set(17) #random - ([0, 2, 5, 6, 8, 10, 13, 14], - [0, 1, 2, 6, 7, 9, 10, 14, 15], - [0, 1, 2, 6, 11, 12, 13, 15], - [0, 2, 6, 9, 11, 12, 13, 15]) + (Additive abelian group isomorphic to Z/16, + [[(1), (5), (6), (7), (9), (13), (14), (15)], + [(0), (2), (3), (5), (6), (10), (11), (13), (14)], + [(0), (1), (2), (3), (5), (6), (7), (12)], + [(0), (2), (3), (5), (6), (7), (9), (12)]]) - If existence is ``True``, the function returns a boolean:: + If ``existence=True``, the function returns a boolean:: sage: supplementary_difference_set_from_rel_diff_set(7, existence=True) False @@ -1698,9 +1746,11 @@ def supplementary_difference_set_from_rel_diff_set(q, existence=False, check=Tru TESTS:: sage: from sage.combinat.designs.difference_family import is_supplementary_difference_set - sage: is_supplementary_difference_set(supplementary_difference_set_from_rel_diff_set(17), 16, 16) + sage: G, sets = supplementary_difference_set_from_rel_diff_set(17, check=False) + sage: is_supplementary_difference_set(sets, lmbda=16, G=G) True - sage: is_supplementary_difference_set(supplementary_difference_set_from_rel_diff_set(9), 8, 8) + sage: G, sets = supplementary_difference_set_from_rel_diff_set(9, check=False) + sage: is_supplementary_difference_set(sets, lmbda=8, G=G) True sage: supplementary_difference_set_from_rel_diff_set(7) Traceback (most recent call last): @@ -1717,6 +1767,12 @@ def supplementary_difference_set_from_rel_diff_set(q, existence=False, check=Tru sage: supplementary_difference_set_from_rel_diff_set(1, existence=True) False + Check that the function works even when s > 1:: + + sage: G, sets = supplementary_difference_set_from_rel_diff_set(353, check=False) #long time + sage: is_supplementary_difference_set(sets, lmbda=352, G=G) #long time + True + .. SEEALSO:: :func:`is_supplementary_difference_set` @@ -1749,29 +1805,30 @@ def supplementary_difference_set_from_rel_diff_set(q, existence=False, check=Tru for d in set1: hall += P.monomial(d[0]) - T_2m = 0 - for i in range(2 * m): - T_2m += P.monomial(i) + def get_T(k): + T = P.monomial(0) -1 + for i in range(k): + T += P.monomial(i) + return T modulo = P.monomial(2*m) - 1 - diff = T_2m - (1+P.monomial(m))*hall + diff = get_T(2*m) - (1+P.monomial(m))*hall diff = diff.mod(modulo) exp1, exp2 = diff.exponents() a = (exp1+exp2-m) // 2 - alfa3 = (P.monomial(a) + hall).mod(modulo) - alfa4 = (P.monomial(a+m) + hall).mod(modulo) + psi3 = (P.monomial(a) + hall).mod(modulo) + psi4 = (P.monomial(a+m) + hall).mod(modulo) - psi3 = alfa3 - psi4 = alfa4 for i in range(s): - psi3 = (alfa3(P.monomial(2)) + P.monomial(1)*alfa4(P.monomial(2))).mod(P.monomial(4*m)-1) - psi4 = (alfa3(P.monomial(2)) + P.monomial(1)*(T_2m(P.monomial(2)) - alfa4(P.monomial(2)))).mod(P.monomial(4*m)-1) + m_start = 2**i * m + psi3, psi4 = (psi3(P.monomial(2)) + P.monomial(1)*psi4(P.monomial(2))).mod(P.monomial(4*m_start)-1), \ + (psi3(P.monomial(2)) + P.monomial(1)*(get_T(2*m_start)(P.monomial(2)) - psi4(P.monomial(2)))).mod(P.monomial(4*m_start)-1) # Construction of psi1, psi2 - set2 = relative_difference_set_from_m_sequence(q, 2, check=False) - s3 = get_fixed_relative_difference_set(set2) + G2, set2 = relative_difference_set_from_m_sequence(q, 2, check=False, return_group=True) + s3 = get_fixed_relative_difference_set(G2, set2) phi_exps = [] for i in range(len(s3)): @@ -1793,28 +1850,41 @@ def supplementary_difference_set_from_rel_diff_set(q, existence=False, check=Tru theta2 += P.monomial(exp) theta2 = theta2.mod(P.monomial(q-1) - 1) - phi1 = 0 - phi2 = 0 - for exp in phi_exps: - if exp % 2 == 0: - phi2 += P.monomial(exp) - else: - phi1 += P.monomial(exp) - psi1 = ((1 + P.monomial((q-1)//2)) * theta1).mod(P.monomial(q-1) - 1) psi2 = (1 + (1 + P.monomial((q-1)//2)) * theta2).mod(P.monomial(q-1) - 1) - K1 = list(map(Integer, psi1.exponents())) - K2 = list(map(Integer, psi2.exponents())) - K3 = list(map(Integer, psi3.exponents())) - K4 = list(map(Integer, psi4.exponents())) + from sage.groups.additive_abelian.additive_abelian_group import AdditiveAbelianGroup + G = AdditiveAbelianGroup([q-1]) + K1 = [G[x] for x in psi1.exponents()] + K2 = [G[x] for x in psi2.exponents()] + K3 = [G[x] for x in psi3.exponents()] + K4 = [G[x] for x in psi4.exponents()] + if check: - assert is_supplementary_difference_set([K1, K2, K3, K4], q-1, q-1) + assert is_supplementary_difference_set([K1, K2, K3, K4], lmbda=q-1, G=G) + + return G, [K1, K2, K3, K4] + - return K1, K2, K3, K4 +def supplementary_difference_set(q, existence=False, check=True): + r""" + Construct `4-\{2v; v, v+1, v, v; 2v\}` supplementary difference sets where `q=2v+1`. -def get_fixed_relative_difference_set(rel_diff_set, as_elements=False): - r"""Construct an equivalent relative difference set fixed by the size of the set. + This is a deprecated version of :func:`supplementary_difference_set_from_rel_diff_set`, + please use that instead. + """ + from sage.misc.superseded import deprecation + deprecation(35211, 'This function is deprecated, please use supplementary_difference_set_from_rel_diff_set instead.') + + if existence: + return supplementary_difference_set_from_rel_diff_set(q, existence=True) + _, s = supplementary_difference_set_from_rel_diff_set(q, check=check) + return s + + +def get_fixed_relative_difference_set(G, rel_diff_set, as_elements=False): + r""" + Construct an equivalent relative difference set fixed by the size of the set. Given a relative difference set `R(q+1, q-1, q, 1)`, it is possible to find a translation of this set fixed by `q` (see Section 3 of [Spe1975]_). We say that a set is fixed by `t` if @@ -1825,47 +1895,48 @@ def get_fixed_relative_difference_set(rel_diff_set, as_elements=False): INPUT: - - ``rel_diff_set`` -- the relative difference set. - - - ``as_elements`` -- boolean (default False). If true, the list returned will contain elements of the - abelian group (this may slow down the computation considerably). + - ``G`` -- a group, of which ``rel_diff_set`` is a subset + - ``rel_diff_set`` -- the relative difference set + - ``as_elements`` -- boolean (default: ``False``); if ``True``, the list + returned will contain elements of the abelian group (this may slow down + the computation considerably) OUTPUT: - By default, this function returns the set as a list of integers. However, if ``as_elements`` - is set to true it will return the set as a list containing elements of the abelian group. + By default, this function returns the set as a list of integers. However, if + ``as_elements=True`` it will return the set as a list containing elements of + the abelian group. If no such set can be found, the function will raise an error. EXAMPLES:: sage: from sage.combinat.designs.difference_family import relative_difference_set_from_m_sequence, get_fixed_relative_difference_set - sage: s1 = relative_difference_set_from_m_sequence(5, 2) - sage: get_fixed_relative_difference_set(s1) #random + sage: G, s1 = relative_difference_set_from_m_sequence(5, 2, return_group=True) + sage: get_fixed_relative_difference_set(G, s1) #random [2, 10, 19, 23, 0] - If ``as_elements`` is true, the reuslt will contain elements of the group:: + If ``as_elements=True``, the result will contain elements of the group:: - sage: get_fixed_relative_difference_set(s1, as_elements=True) #random + sage: get_fixed_relative_difference_set(G, s1, as_elements=True) #random [(2), (10), (19), (23), (0)] TESTS:: sage: from sage.combinat.designs.difference_family import is_fixed_relative_difference_set - sage: s1 = relative_difference_set_from_m_sequence(5, 2) - sage: s2 = get_fixed_relative_difference_set(s1, as_elements=True) + sage: G, s1 = relative_difference_set_from_m_sequence(5, 2, return_group=True) + sage: s2 = get_fixed_relative_difference_set(G, s1, as_elements=True) sage: is_fixed_relative_difference_set(s2, len(s2)) True - sage: s1 = relative_difference_set_from_m_sequence(9, 2) - sage: s2 = get_fixed_relative_difference_set(s1, as_elements=True) + sage: G, s1 = relative_difference_set_from_m_sequence(9, 2, return_group=True) + sage: s2 = get_fixed_relative_difference_set(G, s1, as_elements=True) sage: is_fixed_relative_difference_set(s2, len(s2)) True sage: type(s2[0]) - sage: s2 = get_fixed_relative_difference_set(s1) + sage: s2 = get_fixed_relative_difference_set(G, s1) sage: type(s2[0]) """ - G = rel_diff_set[0].parent() q = len(rel_diff_set) s2 = None @@ -1888,22 +1959,24 @@ def get_fixed_relative_difference_set(rel_diff_set, as_elements=False): return [G[x] for x in s3] return s3 + def is_fixed_relative_difference_set(R, q): - r"""Check if the relative difference set `R` is fixed by `q`. + r""" + Check if the relative difference set ``R`` is fixed by ``q``. A relative difference set `R` is fixed by `q` if `\{qd | d \in R\}= R` (see Section 3 of [Spe1975]_). INPUT: - - ``R`` -- the relative difference sets, as a list containing elements of the abelian group. - - - ``q`` -- an integer. + - ``R`` -- a list containing elements of an abelian group; the relative + difference set + - ``q`` -- an integer EXAMPLES:: sage: from sage.combinat.designs.difference_family import relative_difference_set_from_m_sequence, get_fixed_relative_difference_set, is_fixed_relative_difference_set - sage: s1 = relative_difference_set_from_m_sequence(7, 2) - sage: s2 = get_fixed_relative_difference_set(s1, as_elements=True) + sage: G, s1 = relative_difference_set_from_m_sequence(7, 2, return_group=True) + sage: s2 = get_fixed_relative_difference_set(G, s1, as_elements=True) sage: is_fixed_relative_difference_set(s2, len(s2)) True sage: G = AdditiveAbelianGroup([15]) @@ -1913,8 +1986,8 @@ def is_fixed_relative_difference_set(R, q): If the relative difference set does not contain elements of the group, the method returns false:: - sage: s1 = relative_difference_set_from_m_sequence(7, 2) - sage: s2 = get_fixed_relative_difference_set(s1, as_elements=False) + sage: G, s1 = relative_difference_set_from_m_sequence(7, 2, return_group=True) + sage: s2 = get_fixed_relative_difference_set(G, s1, as_elements=False) sage: is_fixed_relative_difference_set(s2, len(s2)) False """ @@ -1924,14 +1997,191 @@ def is_fixed_relative_difference_set(R, q): return True -def skew_supplementary_difference_set(n, existence=False, check=True): - r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where `S_1` is skew and `n_1 + n_2 + n_3 + n_4 = n+\lambda`. +def skew_supplementary_difference_set_over_polynomial_ring(n, existence=False, check=True): + r""" + Construct skew supplementary difference sets over a polynomial ring of order ``n``. + + The skew supplementary difference sets for `n=81, 169` are taken from [Djo1994a]_. + + INPUT: + + - ``n`` -- integer; the parameter of the supplementary difference sets + - ``existence`` -- boolean (default: ``False``); if ``True``, only check + whether the supplementary difference sets can be constructed + - ``check`` -- boolean (default: ``True``); if ``True``, check that the sets + are supplementary difference sets with `S_1` skew before returning them; + setting this parameter to ``False`` may speed up the computation considerably + + OUTPUT: + + If ``existence=False``, the function returns a Polynomial Ring of order ``n`` + and a list containing 4 sets, or raises an error if data for the given ``n`` + is not available. + If ``existence=True``, the function returns a boolean representing whether + skew supplementary difference sets can be constructed. + + EXAMPLES:: + + sage: from sage.combinat.designs.difference_family import skew_supplementary_difference_set_over_polynomial_ring + sage: G, [S1, S2, S3, S4] = skew_supplementary_difference_set_over_polynomial_ring(81) + + If ``existence=True``, the function returns a boolean:: + + sage: skew_supplementary_difference_set_over_polynomial_ring(81, existence=True) + True + sage: skew_supplementary_difference_set_over_polynomial_ring(17, existence=True) + False + + TESTS:: + + sage: skew_supplementary_difference_set_over_polynomial_ring(7) + Traceback (most recent call last): + ... + NotImplementedError: skew SDS of order 7 not yet implemented + """ + from sage.symbolic.ring import SymbolicRing + from sage.rings.finite_rings.integer_mod_ring import Zmod + + data = { + 81: (3, lambda x: x**4 - x**3 - 1, 16, 5, + [1, 2, 4, 6, 8, 10, 12, 14], [1, 2, 3, 4, 10, 11, 13], + [4, 5, 6, 8, 12, 13, 14], [2, 4, 5, 6, 7, 11, 12, 13, 15]), + 169: (13, lambda x: x**2 - 4*x + 6, 24, 7, + [0, 2, 5, 7, 9, 10, 12, 15, 16, 18, 21, 22], [0, 1, 2, 7, 8, 9, 13, 14, 18, 20, 23], + [1, 4, 6, 7, 9, 14, 16, 17, 20, 21, 23], [3, 5, 6, 9, 10, 12, 13, 14, 15, 17, 20]) + } + + if existence: + return n in data + + if n not in data: + raise NotImplementedError(f'skew SDS of order {n} not yet implemented') + + mod, poly, exp, order, ind1, ind2, ind3, ind4 = data[n] + + Z3 = Zmod(mod) + R = SymbolicRing() + x = R.var('x') + F = Z3.extension(poly(x)) + + H = [F.gen() ** (exp * i) for i in range(order)] + + cosets = [] + for i in range((n - 1) // (2 * order)): + cosets.append([F.gen()**i * el for el in H]) + cosets.append([-F.gen()**i * el for el in H]) + + def generate_set(index_set, cosets): + return sum((cosets[idx] for idx in index_set), []) + + S1 = generate_set(ind1, cosets) + S2 = generate_set(ind2, cosets) + S3 = generate_set(ind3, cosets) + S4 = generate_set(ind4, cosets) + + if check: + lmbda = len(S1) + len(S2) + len(S3) + len(S4) - n + assert is_supplementary_difference_set([S1, S2, S3, S4], lmbda=lmbda, G=F) + assert _is_skew_set(F, S1) + + return F, [S1, S2, S3, S4] + + +def skew_supplementary_difference_set_with_paley_todd(n, existence=False, check=True): + r""" + Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` skew supplementary difference sets where `S_1` is the Paley-Todd difference set. + + The skew SDS returned have the property that `n_1 + n_2 + n_3 + n_4 = n + \lambda`. + + This construction is described in [DK2016]_. The function contains, for each + value of `n`, a set `H` containing integers modulo `n`, and four sets `J, K, L`. + Then, these are used to construct `(n; k_2, k_3, k_4; \lambda_2)` difference family, + with `\lambda_2 = k_2 + k_3 + k_4 + (3n - 1) / 4`. Finally, these sets together + with the Paley-Todd difference set form a skew supplementary difference set. + + INPUT: + + - ``n`` -- integer; the parameter of the supplementary difference set + - ``existence`` -- boolean (default: ``False``); if ``True``, only check + whether the supplementary difference sets can be constructed + - ``check`` -- boolean (default: ``True``); if ``True``, check that the sets + are supplementary difference sets with `S_1` skew before returning them; + setting this parameter to ``False`` may speed up the computation considerably + + OUTPUT: + + If ``existence=False``, the function returns the group G of integers modulo + ``n`` and a list containing 4 sets, or raises an error if data for the given + ``n`` is not available. + If ``existence=True``, the function returns a boolean representing whether + skew supplementary difference sets can be constructed. + + EXAMPLES:: + + sage: from sage.combinat.designs.difference_family import skew_supplementary_difference_set_with_paley_todd + sage: G, [S1, S2, S3, S4] = skew_supplementary_difference_set_with_paley_todd(239) + + If existence is ``True``, the function returns a boolean:: + + sage: skew_supplementary_difference_set_with_paley_todd(239, existence=True) + True + sage: skew_supplementary_difference_set_with_paley_todd(17, existence=True) + False + + TESTS:: + + sage: skew_supplementary_difference_set_with_paley_todd(7) + Traceback (most recent call last): + ... + NotImplementedError: data for skew SDS of order 7 not yet implemented + """ + H_db = { + 239: [1, 10, 24, 44, 98, 100, 201], + } + + indices = { + 239: [[1, 3, 5, 6, 15, 17, 19, 28, 34, 38, 39, 57, 58, 63, 85, 95, 107], + [1, 3, 4, 5, 15, 16, 17, 18, 19, 21, 23, 29, 35, 45, 58, 63], + [0, 1, 4, 6, 7, 8, 13, 16, 18, 34, 35, 45, 47, 58, 63, 95]], + } + + if existence: + return n in H_db + + if n not in H_db: + raise NotImplementedError(f'data for skew SDS of order {n} not yet implemented') + + G = Zmod(n) + H = {G(el) for el in H_db[n]} + + def generate_subset(indices, H): + return list({el * idx for el in H for idx in indices}) + + from sage.arith.misc import quadratic_residues + + S1 = [G(el) for el in quadratic_residues(n) if el != 0] + S2 = generate_subset(indices[n][0], H) + S3 = generate_subset(indices[n][1], H) + S4 = generate_subset(indices[n][2], H) + + if check: + lmbda = len(S1) + len(S2) + len(S3) + len(S4) - n + assert is_supplementary_difference_set([S1, S2, S3, S4], lmbda=lmbda, G=G) + assert _is_skew_set(G, S1) + + return G, [S1, S2, S3, S4] + + +def skew_supplementary_difference_set(n, existence=False, check=True, return_group=False): + r""" + Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets, + where `S_1` is skew and `n_1 + n_2 + n_3 + n_4 = n+\lambda`. These sets are constructed from available data, as described in [Djo1994a]_. The set `S_1 \subset G` is always skew, i.e. `S_1 \cap (-S_1) = \emptyset` and `S_1 \cup (-S_1) = G \setminus \{0\}`. The data is taken from: - + * `n = 103, 151`: [Djo1994a]_ * `n = 67, 113, 127, 157, 163, 181, 241`: [Djo1992a]_ * `n = 37, 43`: [Djo1992b]_ @@ -1939,30 +2189,43 @@ def skew_supplementary_difference_set(n, existence=False, check=True): * `n = 97`: [Djo2008a]_ * `n = 109, 145, 247`: [Djo2008b]_ * `n = 73`: [Djo2023b]_ - - INPUT: + * `n = 213, 631`: [DGK2014]_ + * `n = 331`: [DK2016]_ - - ``n`` -- integer, the parameter of the supplementary difference set. + Additional skew Supplementary difference sets are built using the function + :func:`skew_supplementary_difference_set_over_polynomial_ring`, and + :func:`skew_supplementary_difference_set_with_paley_todd`. - - ``existence`` -- boolean (dafault False). If true, only check whether the supplementary difference sets - can be constructed. + INPUT: - - ``check`` -- boolean (default True). If true, check that the sets are supplementary difference sets - with `S_1` skew before returning them. Setting this parameter to False may speed up the computation considerably. + - ``n`` -- integer; the parameter of the supplementary difference set + - ``existence`` -- boolean (default: ``False``); if ``True``, only check + whether the supplementary difference sets can be constructed + - ``check`` -- boolean (default: ``True``); if ``True``, check that the sets + are supplementary difference sets with `S_1` skew before returning them; + setting this parameter to ``False`` may speed up the computation considerably + - ``return_group`` -- boolean (default: ``False``); if ``True``, the function + will also return the group from which the sets are created OUTPUT: - If ``existence`` is false, the function returns the 4 sets (containing integers modulo `n`), or raises an - error if data for the given ``n`` is not available. - If ``existence`` is true, the function returns a boolean representing whether skew supplementary difference - sets can be constructed. + If ``existence=False``, the function returns a list containing 4 sets, + or raises an error if data for the given ``n`` is not available. If + ``return_group=True`` the function will additionally return the group from + which the sets are created. + If ``existence=True``, the function returns a boolean representing whether + skew supplementary difference sets can be constructed. EXAMPLES:: sage: from sage.combinat.designs.difference_family import skew_supplementary_difference_set - sage: S1, S2, S3, S4 = skew_supplementary_difference_set(103) + sage: [S1, S2, S3, S4] = skew_supplementary_difference_set(39) - If existence is ``True``, the function returns a boolean :: + If ``return_group=True``, the function will also return the group:: + + sage: G, [S1, S2, S3, S4] = skew_supplementary_difference_set(103, return_group=True) + + If ``existence=True``, the function returns a boolean:: sage: skew_supplementary_difference_set(103, existence=True) True @@ -1972,15 +2235,15 @@ def skew_supplementary_difference_set(n, existence=False, check=True): TESTS:: sage: from sage.combinat.designs.difference_family import is_supplementary_difference_set, _is_skew_set - sage: S1, S2, S3, S4 = skew_supplementary_difference_set(113, check=False) - sage: is_supplementary_difference_set([S1, S2, S3, S4], 113, len(S1)+len(S2)+len(S3)+len(S4)-113) + sage: G, [S1, S2, S3, S4] = skew_supplementary_difference_set(113, check=False, return_group=True) + sage: is_supplementary_difference_set([S1, S2, S3, S4], lmbda=len(S1)+len(S2)+len(S3)+len(S4)-113, G=G) True - sage: _is_skew_set(S1, 113) + sage: _is_skew_set(G, S1) True - sage: S1, S2, S3, S4 = skew_supplementary_difference_set(67, check=False) - sage: is_supplementary_difference_set([S1, S2, S3, S4], 67, len(S1)+len(S2)+len(S3)+len(S4)-67) + sage: G, [S1, S2, S3, S4] = skew_supplementary_difference_set(67, check=False, return_group=True) + sage: is_supplementary_difference_set([S1, S2, S3, S4], lmbda=len(S1)+len(S2)+len(S3)+len(S4)-67, G=G) True - sage: _is_skew_set(S1, 67) + sage: _is_skew_set(G, S1) True sage: skew_supplementary_difference_set(7) Traceback (most recent call last): @@ -1990,6 +2253,8 @@ def skew_supplementary_difference_set(n, existence=False, check=True): False sage: skew_supplementary_difference_set(127, existence=True) True + sage: skew_supplementary_difference_set(81, existence=True) + True .. NOTE:: @@ -2043,7 +2308,7 @@ def skew_supplementary_difference_set(n, existence=False, check=True): [0, 1, 3, 6, 7, 9, 10, 12, 14, 15], [0, 1, 3, 4, 5, 7, 8, 9, 15, 16], [1, 4, 5, 6, 9, 10, 13, 14, 15, 16]], - 129: [[1, 2, 4, 7, 9, 11, 12, 14,16,18], + 129: [[1, 2, 4, 7, 9, 11, 12, 14, 16, 18], [0, 1, 2, 3, 9, 11, 14, 15, 19], [0, 1, 3, 6, 8, 10, 12, 16, 18, 19], [0, 3, 7, 8, 9, 10, 12, 14, 15, 17]], @@ -2067,6 +2332,10 @@ def skew_supplementary_difference_set(n, existence=False, check=True): [4, 5, 7, 8, 11, 14, 15, 16, 18, 19], [0, 4, 10, 11, 13, 15, 16, 18, 19], [2, 4, 5, 7, 11, 13, 15, 17, 19]], + 213: [[1, 2, 5, 6, 9, 11, 12, 14, 16, 19, 20, 23, 24, 26, 29, 30], + [3, 6, 8, 12, 13, 14, 15, 17, 20, 22, 23, 25, 26, 27, 28, 31], + [2, 3, 5, 7, 9, 13, 16, 17, 19, 21, 23, 24, 27, 28, 29], + [0, 5, 6, 9, 11, 13, 14, 17, 20, 22, 23, 26, 29, 31]], 217: [[0, 3, 5, 7, 8, 11, 12, 14], [1, 3, 4, 7, 9, 11, 12, 15], [3, 4, 5, 6, 7, 9, 10, 14, 15], [1, 3, 4, 5, 7, 8, 11, 13, 14]], 219: [[1, 3, 5, 6, 8, 11, 12, 15, 17, 18, 21, 22, 24], @@ -2085,6 +2354,14 @@ def skew_supplementary_difference_set(n, existence=False, check=True): [0, 1, 4, 5, 6, 8, 14, 15, 18, 21, 23], [0, 2, 4, 5, 7, 9, 10, 11, 14, 15, 16, 17, 25], [0, 1, 3, 4, 6, 14, 15, 16, 17, 18, 20, 22, 23, 25]], + 331: [[1, 2, 4, 7, 9, 10, 12, 15, 16, 18, 21, 22, 24, 26, 28], + [-1, 0, 2, 6, 9, 11, 12, 14, 15, 17, 20, 21, 24, 25, 28], + [-1, 0, 1, 5, 6, 7, 8, 9, 10, 12, 15, 18, 23, 28, 29], + [-1, 0, 3, 7, 8, 10, 11, 12, 14, 16, 19, 20, 21, 26, 29]], + 631: [[0, 2, 4, 6, 9, 10, 12, 15, 16, 18, 20, 23, 24, 26, 29, 30, 32, 35, 36, 38, 40], + [0, 1, 2, 4, 6, 8, 9, 10, 11, 12, 13, 14, 16, 20, 23, 28, 29, 30, 32, 36, 38, 41], + [0, 2, 3, 4, 6, 9, 10, 12, 14, 15, 16, 17, 18, 19, 20, 22, 24, 25, 26, 29, 34, 40], + [0, 2, 4, 5, 6, 7, 8, 10, 15, 16, 18, 22, 23, 24, 26, 30, 31, 33, 35, 36, 37, 38]], } # If the element is a list, that is the coset. @@ -2113,6 +2390,7 @@ def skew_supplementary_difference_set(n, existence=False, check=True): 157: [1, 2, 3, 5, 9, 15], 163: [1, 2, 3, 5, 6, 9, 10, 15, 18], 181: [1, 2, 3, 4, 6, 7, 8, 12, 13, 24], + 213: [1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 15, 20, 22, 30, 43, 71], 217: [1, 2, 4, 5, 7, 10, 19, [31, 62, 124]], 219: [1, 2, 3, 5, 7, 9, 11, 15, 19, 22, 23, 33, [73]], 241: [1, 2, 4, 5, 7, 13, 19, 35], @@ -2122,6 +2400,8 @@ def skew_supplementary_difference_set(n, existence=False, check=True): [14, 29, 40, 79, 113, 126, 146, 217, 224], [17, 25, 43, 49, 140, 142, 153, 194, 225], [19, 57, 171], [33, 34, 37, 50, 59, 86, 98, 141, 203], [35, 66, 68, 74, 100, 118, 159, 172, 196], [38, 95, 114]], 267: [1, 2, 3, 5, 7, 9, 10, 13, 14, 15, 19, 39, [89]], + 331: [1, 2, 4, 5, 7, 8, 10, 13, 14, 16, 19, 20, 28, 32, 56], + 631: [1, 2, 3, 4, 5, 6, 7, 9, 12, 14, 17, 18, 19, 21, 23, 27, 31, 35, 38, 42, 62], } H_db = { @@ -2146,31 +2426,51 @@ def skew_supplementary_difference_set(n, existence=False, check=True): 157: [1, 14, 16, 39, 46, 67, 75, 93, 99, 101, 108, 130, 153], 163: [1, 38, 40, 53, 58, 85, 104, 133, 140], 181: [1, 39, 43, 48, 62, 65, 73, 80, 132], + 213: [1, 37, 91, 103, 172, 187, 190], 217: [1, 8, 9, 25, 51, 64, 72, 78, 81, 142, 190, 191, 193, 200, 214], 219: [1, 4, 16, 37, 55, 64, 148, 154, 178], 241: [1, 15, 24, 54, 87, 91, 94, 98, 100, 119, 160, 183, 205, 225, 231], 247: [1, 9, 16, 55, 61, 81, 139, 144, 235], 267: [1, 4, 16, 64, 67, 91, 97, 121, 217, 223, 256], + 331: [1, 74, 80, 85, 111, 120, 167, 180, 270, 274, 293], + 631: [1, 8, 43, 64, 79, 188, 228, 242, 279, 310, 339, 344, 512, 562, 587], } + G, S1, S2, S3, S4 = None, [], [], [], [] + + if n in indices: + if existence: + return True + G, [S1, S2, S3, S4] = _construction_supplementary_difference_set(n, H_db[n], indices[n], cosets_gens[n], check=False) + elif skew_supplementary_difference_set_over_polynomial_ring(n, existence=True): + if existence: + return True + G, [S1, S2, S3, S4] = skew_supplementary_difference_set_over_polynomial_ring(n, check=False) + elif skew_supplementary_difference_set_with_paley_todd(n, existence=True): + if existence: + return True + G, [S1, S2, S3, S4] = skew_supplementary_difference_set_with_paley_todd(n, check=False) + if existence: - return n in indices + return False - if n not in indices: + if G is None: raise ValueError(f'Skew SDS of order {n} not yet implemented.') - S1, S2, S3, S4 = _construction_supplementary_difference_set(n, H_db[n], indices[n], cosets_gens[n], check=False) - if check: lmbda = len(S1) + len(S2) + len(S3) + len(S4) - n - assert is_supplementary_difference_set([S1, S2, S3, S4], n, lmbda) - assert _is_skew_set(S1, n) + assert is_supplementary_difference_set([S1, S2, S3, S4], lmbda=lmbda, G=G) + assert _is_skew_set(G, S1) - return S1, S2, S3, S4 + if return_group: + return G, [S1, S2, S3, S4] + return [S1, S2, S3, S4] def _construction_supplementary_difference_set(n, H, indices, cosets_gen, check=True): - r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where `n_1 + n_2 + n_3 + n_4 = n+\lambda`. + r""" + Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets, + where `n_1 + n_2 + n_3 + n_4 = n+\lambda`. This construction is described in [Djo1994a]_. @@ -2187,23 +2487,20 @@ def _construction_supplementary_difference_set(n, H, indices, cosets_gen, check= INPUT: - - ``n`` -- integer, the parameter of the supplementary difference set. - - - ``H`` -- list of integers, the set `H` used to construct the cosets. - - - ``indices`` -- list containing four list of integers, which are the sets - `J_1, J_2, J_3, J_4` described above. - - - ``cosets_gen`` -- list containing integers or list of integers, is the set `A` - described above. - - - ``check`` -- boolean (default True). If true, check that the sets - are supplementary difference sets. Setting this parameter to False may speed - up the computation considerably. + - ``n`` -- integer; the parameter of the supplementary difference set + - ``H`` -- list of integers; the set `H` used to construct the cosets + - ``indices`` -- list containing four lists of integers; the sets + `J_1, J_2, J_3, J_4` described above + - ``cosets_gen`` -- list containing integers or list of integers; the set `A` + described above + - ``check`` -- boolean (default: ``True``); if ``True``, check that the sets + are supplementary difference sets; setting this parameter to ``False`` may + speed up the computation considerably OUTPUT: - The function returns the 4 sets (containing integers modulo `n`). + The function returns the ring of integers modulo ``n`` and a list containing + the 4 sets. TESTS:: @@ -2212,23 +2509,25 @@ def _construction_supplementary_difference_set(n, H, indices, cosets_gen, check= sage: cosets_gen = [1, 2, 3, 5, 6, 9] sage: indices = [[0, 3, 5, 7, 9, 10], [0, 5, 6, 7, 8], [1, 2, 6, 7, 9], [2, 6, 8, 9, 10]] sage: _construction_supplementary_difference_set(37, H, indices, cosets_gen) - ([1, 10, 26, 35, 17, 22, 34, 7, 33, 32, 24, 18, 31, 14, 29, 9, 16, 12], - [1, 10, 26, 34, 7, 33, 5, 13, 19, 32, 24, 18, 6, 23, 8], - [36, 27, 11, 2, 20, 15, 5, 13, 19, 32, 24, 18, 31, 14, 29], - [2, 20, 15, 5, 13, 19, 6, 23, 8, 31, 14, 29, 9, 16, 12]) + (Ring of integers modulo 37, + [[32, 1, 33, 35, 34, 7, 9, 10, 12, 14, 16, 17, 18, 22, 24, 26, 29, 31], + [32, 1, 33, 34, 5, 6, 7, 8, 10, 13, 18, 19, 23, 24, 26], + [32, 2, 36, 5, 11, 13, 14, 15, 18, 19, 20, 24, 27, 29, 31], + [2, 5, 6, 8, 9, 12, 13, 14, 15, 16, 19, 20, 23, 29, 31]]) sage: H = [1, 16, 22] sage: cosets_gen = [1, 2, 3, 4, 6, 8, [13]] sage: indices = [[1, 3, 5, 6, 8, 10, 12], [0, 1, 5, 8, 12, 13], [1, 3, 4, 7, 9, 12, 13], [0, 1, 2, 3, 7, 8]] sage: _construction_supplementary_difference_set(39, H, indices, cosets_gen) - ([38, 23, 17, 37, 7, 34, 36, 30, 12, 4, 25, 10, 6, 18, 15, 8, 11, 20, 13], - [1, 16, 22, 38, 23, 17, 36, 30, 12, 6, 18, 15, 13, 26], - [38, 23, 17, 37, 7, 34, 3, 9, 27, 35, 14, 29, 33, 21, 24, 13, 26], - [1, 16, 22, 38, 23, 17, 2, 32, 5, 37, 7, 34, 35, 14, 29, 6, 18, 15]) + (Ring of integers modulo 39, + [[4, 6, 7, 8, 10, 11, 12, 13, 15, 17, 18, 20, 23, 25, 30, 34, 36, 37, 38], + [1, 36, 38, 6, 12, 13, 15, 16, 17, 18, 22, 23, 26, 30], + [3, 7, 9, 13, 14, 17, 21, 23, 24, 26, 27, 29, 33, 34, 35, 37, 38], + [32, 1, 2, 34, 35, 5, 38, 37, 7, 6, 14, 15, 16, 17, 18, 22, 23, 29]]) sage: H = [1, 4, 11, 16, 21, -2, -8] sage: cosets_gen = [1, 3, 7] sage: indices = [[1, 2, 4], [1, 2, 4], [0, 2, 3], [3, 4, -1]] - sage: sets = _construction_supplementary_difference_set(43, H, indices, cosets_gen, check=False) - sage: is_supplementary_difference_set(sets, 43, 35) + sage: G, sets = _construction_supplementary_difference_set(43, H, indices, cosets_gen, check=False) + sage: is_supplementary_difference_set(sets, lmbda=35, G=G) True .. SEEALSO:: @@ -2236,24 +2535,24 @@ def _construction_supplementary_difference_set(n, H, indices, cosets_gen, check= :func:`skew_supplementary_difference_set` """ def generate_set(index_set, cosets): - S = [] + S = set() for idx in index_set: if idx == -1: - S.append(Z(0)) + S.add(Z(0)) else: - S += cosets[idx] - return S + S = S.union(cosets[idx]) + return list(S) Z = Zmod(n) - H = list(map(Z, H)) + H = set(map(Z, H)) cosets = [] for el in cosets_gen: if isinstance(el, list): - even_coset = [Z(x) for x in el] + even_coset = {Z(x) for x in el} else: - even_coset = [x*el for x in H] - odd_coset = [-x for x in even_coset] + even_coset = {x*el for x in H} + odd_coset = {-x for x in even_coset} cosets.append(even_coset) cosets.append(odd_coset) @@ -2264,63 +2563,70 @@ def generate_set(index_set, cosets): if check: lmbda = len(S1) + len(S2) + len(S3) + len(S4) - n - assert is_supplementary_difference_set([S1, S2, S3, S4], n, lmbda) + assert is_supplementary_difference_set([S1, S2, S3, S4], lmbda=lmbda, G=Z) - return S1, S2, S3, S4 + return Z, [S1, S2, S3, S4] -def supplementary_difference_set(n, existence=False, check=True): - r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where `n_1 + n_2 + n_3 + n_4 = n+\lambda`. +def supplementary_difference_set_hadamard(n, existence=False, check=True): + r""" + Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets, + where `n_1 + n_2 + n_3 + n_4 = n+\lambda`. These sets are constructed from available data, as described in [Djo1994a]_. + The data is taken from: + + * `n = 191`: [Djo2008c]_ + * `n = 239`: [Djo1994b]_ + * `n = 251`: [DGK2014]_ - The data for `n=191` is taken from [Djo2008c]_, and data for `n=239` is from [Djo1994b]_. Additional SDS are constructed using :func:`skew_supplementary_difference_set`. INPUT: - - ``n`` -- integer, the parameter of the supplementary difference set. - - - ``existence`` -- boolean (default False). If true, only check whether the - supplementary difference sets can be constructed. - - - ``check`` -- boolean (default True). If true, check that the sets are - supplementary difference sets before returning them. Setting this parameter - to False may speed up the computation considerably. + - ``n`` -- integer; the parameter of the supplementary difference set + - ``existence`` -- boolean (default: ``False``); if ``True``, only check + whether the supplementary difference sets can be constructed + - ``check`` -- boolean (default: ``True``); if ``True``, check that the sets + are supplementary difference sets before returning them; Setting this + parameter to ``False`` may speed up the computation considerably OUTPUT: - If ``existence`` is false, the function returns the 4 sets (containing integers - modulo `n`), or raises an error if data for the given ``n`` is not available. - If ``existence`` is true, the function returns a boolean representing whether + If ``existence=False``, the function returns the ring of integers modulo + ``n`` and a list containing the 4 sets, or raises an error if data for the + given ``n`` is not available. + If ``existence=True``, the function returns a boolean representing whether skew supplementary difference sets can be constructed. EXAMPLES:: - sage: from sage.combinat.designs.difference_family import supplementary_difference_set - sage: S1, S2, S3, S4 = supplementary_difference_set(191) # long time + sage: from sage.combinat.designs.difference_family import supplementary_difference_set_hadamard + sage: G, [S1, S2, S3, S4] = supplementary_difference_set_hadamard(191) - If existence is ``True``, the function returns a boolean :: + If ``existence=True``, the function returns a boolean:: - sage: supplementary_difference_set(191, existence=True) + sage: supplementary_difference_set_hadamard(191, existence=True) True - sage: supplementary_difference_set(17, existence=True) + sage: supplementary_difference_set_hadamard(17, existence=True) False TESTS:: sage: from sage.combinat.designs.difference_family import is_supplementary_difference_set - sage: S1, S2, S3, S4 = supplementary_difference_set(191, check=False) - sage: is_supplementary_difference_set([S1, S2, S3, S4], 191, len(S1)+len(S2)+len(S3)+len(S4)-191) # long time + sage: G, [S1, S2, S3, S4] = supplementary_difference_set_hadamard(191, check=False) + sage: is_supplementary_difference_set([S1, S2, S3, S4], lmbda=len(S1)+len(S2)+len(S3)+len(S4)-191, G=G) + True + sage: G, [S1, S2, S3, S4] = supplementary_difference_set_hadamard(37, check=False) + sage: is_supplementary_difference_set([S1, S2, S3, S4], lmbda=len(S1)+len(S2)+len(S3)+len(S4)-37, G=G) True - sage: S1, S2, S3, S4 = supplementary_difference_set(37, check=False) - sage: supplementary_difference_set(7) + sage: supplementary_difference_set_hadamard(7) Traceback (most recent call last): ... ValueError: SDS of order 7 not yet implemented. - sage: supplementary_difference_set(7, existence=True) + sage: supplementary_difference_set_hadamard(7, existence=True) False - sage: supplementary_difference_set(127, existence=True) + sage: supplementary_difference_set_hadamard(127, existence=True) True """ @@ -2333,26 +2639,33 @@ def supplementary_difference_set(n, existence=False, check=True): [0, 1, 3, 7, 9, 12, 15, 18, 20, 22, 26, 28, 29, 30, 31, 32, 33], [2, 3, 4, 5, 8, 9, 10, 11, 13, 17, 19, 21, 22, 24, 27, 31, 32], [0, 1, 2, 3, 6, 7, 8, 11, 13, 15, 17, 18, 19, 22, 25, 26, 27, 32, 33]], + 251: [[2, 6, 8, 10, 12, 13, 14, 15, 16, 18, 19, 20, 21, 22, 23, 27, 28, 35, 36, 39, 41, 43, 44, 47, 48], + [2, 5, 10, 11, 17, 18, 21, 23, 24, 25, 26, 28, 29, 30, 34, 35, 38, 39, 40, 41, 42, 43, 44, 49], + [0, 2, 6, 7, 10, 11, 14, 15, 16, 18, 21, 22, 24, 26, 30, 35, 37, 38, 45, 46, 47, 48, 49], + [1, 2, 3, 4, 8, 9, 12, 17, 21, 22, 27, 28, 29, 30, 33, 34, 39, 41, 42, 43, 46, 47, 48]], } cosets_gens = { 191: [1, 2, 3, 4, 6, 8, 9, 11, 12, 13, 16, 17, 18, 19, 22, 32, 36, 38, 41], 239: [1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 14, 15, 18, 21, 28, 35, 42], + 251: [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 14, 15, 17, 18, 19, 21, 28, 30, 33, 34, 35, 41, 43, 45, 68], } H_db = { 191: [1, 39, 184, 109, 49], 239: [1, 10, 24, 44, 98, 100, 201], + 251: [1, 20, 113, 149, 219], } if existence: return n in indices or skew_supplementary_difference_set(n, existence=True) sets = None + G = None if n in indices: - sets = _construction_supplementary_difference_set(n, H_db[n], indices[n], cosets_gens[n], check=False) + G, sets = _construction_supplementary_difference_set(n, H_db[n], indices[n], cosets_gens[n], check=False) elif skew_supplementary_difference_set(n, existence=True): - sets = skew_supplementary_difference_set(n, check=False) + G, sets = skew_supplementary_difference_set(n, check=False, return_group=True) if sets is None: raise ValueError(f'SDS of order {n} not yet implemented.') @@ -2360,35 +2673,34 @@ def supplementary_difference_set(n, existence=False, check=True): S1, S2, S3, S4 = sets if check: lmbda = len(S1) + len(S2) + len(S3) + len(S4) - n - assert is_supplementary_difference_set([S1, S2, S3, S4], n, lmbda) + assert is_supplementary_difference_set([S1, S2, S3, S4], lmbda=lmbda, G=G) - return S1, S2, S3, S4 + return G, [S1, S2, S3, S4] -def _is_skew_set(S, n): - r"""Check if `S` is a skew set over the set of integers modulo `n`. +def _is_skew_set(G, S): + r""" + Check if ``S`` is a skew set over the group ``G``. From [Djo1994a]_, a set `S \subset G` (where `G` is a finite abelian group of order `n`) is of skew type if `S_1 \cap (-S_1) = \emptyset` and `S_1 \cup (-S_1) = G\setminus \{0\}`. INPUT: - - ``S`` -- the set to be checked, containing integers modulo `n`. - - - ``n`` -- the order of `G`. + - ``G`` -- a group + - ``S`` -- list containing elements of ``G``; the set to be checked EXAMPLES:: sage: from sage.combinat.designs.difference_family import _is_skew_set sage: Z5 = Zmod(5) - sage: _is_skew_set([Z5(1), Z5(2)], 5) + sage: _is_skew_set(Z5, [Z5(1), Z5(2)]) True - sage: _is_skew_set([Z5(1), Z5(2), Z5(3)], 5) + sage: _is_skew_set(Z5, [Z5(1), Z5(2), Z5(3)]) False - sage: _is_skew_set([Z5(1)], 5) + sage: _is_skew_set(Z5, [Z5(1)]) False """ - G = Zmod(n) for el in S: if -el in S: return False @@ -2399,6 +2711,395 @@ def _is_skew_set(S, n): return False return True + +def are_complementary_difference_sets(G, A, B, verbose=False): + r""" + Check if ``A`` and ``B`` are complementary difference sets over the group ``G``. + + According to [Sze1971]_, two sets `A`, `B` of size `m` are complementary + difference sets over a group `G` of size `2m+1` if: + + 1. they are `2-\{2m+1; m, m; m-1\}` supplementary difference sets + 2. `A` is skew, i.e. `a \in A` implies `-a \not \in A` + + INPUT: + + - ``G`` -- a group of odd order + - ``A`` -- a set of elements of ``G`` + - ``B`` -- a set of elements of ``G`` + - ``verbose`` -- boolean (default: ``False``); if ``True`` the function will + be verbose when the sets do not satisfy the contraints + + EXAMPLES:: + + sage: from sage.combinat.designs.difference_family import are_complementary_difference_sets + sage: are_complementary_difference_sets(Zmod(7), [1, 2, 4], [1, 2, 4]) + True + + If ``verbose=True``, the function will be verbose:: + + sage: are_complementary_difference_sets(Zmod(7), [1, 2, 5], [1, 2, 4], verbose=True) + The sets are not supplementary difference sets with lambda = 2 + False + + TESTS:: + + sage: are_complementary_difference_sets(Zmod(16), [1, 2, 4], [1, 2, 4]) + False + sage: are_complementary_difference_sets(Zmod(7), [1, 2, 4], [1, 2, 3, 4]) + False + sage: are_complementary_difference_sets(Zmod(19), [1, 4, 5, 6, 7, 9, 11, 16, 17], [1, 4, 5, 6, 7, 9, 11, 16, 17]) + True + + .. SEEALSO:: + + :func:`is_supplementary_difference_set` + """ + n = G.order() + + if n % 2 != 1: + if verbose: + print('G must have odd order') + return False + + m = (n - 1) // 2 + + if len(A) != m or len(B) != m: + if verbose: + print(f'A and B must have size {m}') + return False + + if not is_supplementary_difference_set([A, B], lmbda=m-1, G=G): + if verbose: + print(f'The sets are not supplementary difference sets with lambda = {m-1}') + return False + + if not _is_skew_set(G, A): + if verbose: + print('The set A is not skew') + return False + + return True + + +def complementary_difference_setsI(n, check=True): + r""" + Construct complementary difference sets in a group of order `n \cong 3 \mod 4`, `n` a prime power. + + Let `G` be a Galois Field of order `n`, where `n` satisfies the requirements + above. Let `A` be the set of non-zero quadratic elements in `G`, and `B = A`. + Then `A` and `B` are complementary difference sets over a group of order `n`. + This construction is described in [Sze1971]_. + + INPUT: + + - ``n`` -- integer; the order of the group `G` + - ``check`` -- boolean (default: ``True``); if ``True``, check that the sets + are complementary difference sets before returning them + + OUTPUT: + + The function returns the Galois field of order ``n`` and the two sets, or raises + an error if ``n`` does not satisfy the requirements of this construction. + + EXAMPLES:: + + sage: from sage.combinat.designs.difference_family import complementary_difference_setsI + sage: complementary_difference_setsI(19) + (Finite Field of size 19, + [1, 4, 5, 6, 7, 9, 11, 16, 17], + [1, 4, 5, 6, 7, 9, 11, 16, 17]) + + TESTS:: + + sage: from sage.combinat.designs.difference_family import are_complementary_difference_sets + sage: G, A, B = complementary_difference_setsI(23, check=False) + sage: are_complementary_difference_sets(G, A, B) + True + sage: complementary_difference_setsI(17) + Traceback (most recent call last): + ... + ValueError: the parameter 17 is not valid + sage: complementary_difference_setsI(15) + Traceback (most recent call last): + ... + ValueError: the parameter 15 is not valid + + .. SEEALSO:: + + :func:`are_complementary_difference_sets` + :func:`complementary_difference_sets` + """ + if not (n % 4 == 3 and is_prime_power(n)): + raise ValueError(f'the parameter {n} is not valid') + + from sage.rings.finite_rings.finite_field_constructor import GF + + G = GF(n, 'a') + A = list({x**2 for x in G if x**2 != 0}) + B = A + if check: + assert are_complementary_difference_sets(G, A, B) + + return G, A, B + + +def complementary_difference_setsII(n, check=True): + r""" + Construct complementary difference sets in a group of order `n = p^t`, where `p \cong 5 \mod 8` and `t \cong 1, 2, 3 \mod 4`. + + Consider a finite field `G` of order `n` and let `\rho` be the generator of + the corresponding multiplicative group. Then, there are two different constructions, + depending on whether `t` is even or odd. + + If `t \cong 2 \mod 4`, let `C_0` be the set of non-zero octic residues in `G`, + and let `C_i = \rho^i C_0` for `1 \le i \le 7`. + Then, `A = C_0 \cup C_1 \cup C_2 \cup C_3` and `B = C_0 \cup C_1 \cup C_6 \cup C_7`. + + If `t` is odd, let `C_0` be the set of non-zero fourth powers in `G`, and let + `C_i = \rho^i C_0` for `1 \le i \le 3`. + Then, `A = C_0 \cup C_1` and `B = C_0 \cup C_3`. + + For more details on this construction, see [Sze1971]_. + + INPUT: + + - ``n`` -- integer; the order of the group `G` + - ``check`` -- boolean (default: ``True``); if ``True``, check that the sets + are complementary difference sets before returning them; setting this to + ``False`` might speed up the computation for large values of ``n`` + + OUTPUT: + + The function returns the Galois field of order ``n`` and the two sets, or raises + an error if ``n`` does not satisfy the requirements of this construction. + + EXAMPLES:: + + sage: from sage.combinat.designs.difference_family import complementary_difference_setsII + sage: complementary_difference_setsII(5) + (Finite Field of size 5, [1, 2], [1, 3]) + + TESTS:: + + sage: from sage.combinat.designs.difference_family import are_complementary_difference_sets + sage: G, A, B = complementary_difference_setsII(25, check=False) + sage: are_complementary_difference_sets(G, A, B) + True + sage: G, A, B = complementary_difference_setsII(13, check=False) + sage: are_complementary_difference_sets(G, A, B) + True + sage: complementary_difference_setsII(49) + Traceback (most recent call last): + ... + ValueError: the parameter 49 is not valid + sage: complementary_difference_setsII(15) + Traceback (most recent call last): + ... + ValueError: the parameter 15 is not valid + + .. SEEALSO:: + + :func:`are_complementary_difference_sets` + :func:`complementary_difference_sets` + """ + p, t = is_prime_power(n, get_data=True) + if not (p % 8 == 5 and t > 0 and t % 4 in [1, 2, 3]): + raise ValueError(f'the parameter {n} is not valid') + + from sage.rings.finite_rings.finite_field_constructor import GF + G = GF(n, 'a') + A, B = None, None + + if t % 2 == 0: + rho = G.multiplicative_generator() + C0 = list({el**8 for el in G if el != 0}) + C1, C2, C3, C6, C7 = map(lambda i: [rho**i * el for el in C0], [1, 2, 3, 6, 7]) + A = C0 + C1 + C2 + C3 + B = C0 + C1 + C6 + C7 + else: + rho = G.multiplicative_generator() + C0 = list({el**4 for el in G if el**4 != 0}) + C1 = [rho * el for el in C0] + C3 = [rho**3 * el for el in C0] + A = C0 + C1 + B = C0 + C3 + + if check: + assert are_complementary_difference_sets(G, A, B) + + return G, A, B + + +def complementary_difference_setsIII(n, check=True): + r""" + Construct complementary difference sets in a group of order `n = 2m + 1`, where `4m + 3` is a prime power. + + Consider a finite field `G` of order `n` and let `\rho` be a primite element + of this group. Now let `Q` be the set of non zero quadratic residues in `G`, + and let `A = \{ a | \rho^{2a} - 1 \in Q\}`, `B' = \{ b | -(\rho^{2b} + 1) \in Q\}`. + Then `A` and `B = Q \setminus B'` are complementary difference sets over the ring + of integers modulo `n`. For more details, see [Sz1969]_. + + INPUT: + + - ``n`` -- integer; the order of the group over which the sets are constructed + - ``check`` -- boolean (default: ``True``); if ``True``, check that the sets + are complementary difference sets before returning them; setting this to + ``False`` might speed up the computation for large values of ``n`` + + OUTPUT: + + The function returns the Galois field of order ``n`` and the two sets, or raises + an error if ``n`` does not satisfy the requirements of this construction. + + EXAMPLES:: + + sage: from sage.combinat.designs.difference_family import complementary_difference_setsIII + sage: complementary_difference_setsIII(11) + (Ring of integers modulo 11, [1, 2, 5, 7, 8], [0, 1, 3, 8, 10]) + + TESTS:: + + sage: from sage.combinat.designs.difference_family import are_complementary_difference_sets + sage: G, A, B = complementary_difference_setsIII(21, check=False) + sage: are_complementary_difference_sets(G, A, B) + True + sage: G, A, B = complementary_difference_setsIII(65, check=False) + sage: are_complementary_difference_sets(G, A, B) + True + sage: complementary_difference_setsIII(10) + Traceback (most recent call last): + ... + ValueError: the parameter 10 is not valid + sage: complementary_difference_setsIII(17) + Traceback (most recent call last): + ... + ValueError: the parameter 17 is not valid + + .. SEEALSO:: + + :func:`are_complementary_difference_sets` + :func:`complementary_difference_sets` + """ + m = (n - 1) // 2 + q = 4*m + 3 + if n % 2 != 1 or not is_prime_power(q): + raise ValueError(f'the parameter {n} is not valid') + + from sage.rings.finite_rings.finite_field_constructor import GF + G = Zmod(n) + G2 = GF(q) + rho = G2.primitive_element() + + Q = [rho ** (2*b) for b in range(1, n+1)] + + A = [G(a) for a in range(n) if rho**(2*a) - 1 in Q] + B = [G(b) for b in range(n) if -rho**(2*b) - 1 not in Q] + + if check: + assert are_complementary_difference_sets(G, A, B) + + return G, A, B + + +def complementary_difference_sets(n, existence=False, check=True): + r""" + Compute complementary difference sets over a group of order `n = 2m + 1`. + + According to [Sze1971]_, two sets `A`, `B` of size `m` are complementary + difference sets over a group `G` of size `n = 2m + 1` if: + + 1. they are `2-\{2m+1; m, m; m-1\}` supplementary difference sets + 2. `A` is skew, i.e. `a \in A` implies `-a \not \in A` + + This method tries to call :func:`complementary_difference_setsI`, + :func:`complementary_difference_setsII` or :func:`complementary_difference_setsIII` + if the parameter `n` satisfies the requirements of one of these functions. + + INPUT: + + - ``n`` -- integer; the order of the group over which the sets are constructed + - ``existence`` -- boolean (default: ``False``); if ``True``, only check + whether the supplementary difference sets can be constructed + - ``check`` -- boolean (default: ``True``); if ``True``, check that the sets + are complementary difference sets before returning them; setting this to + ``False`` might speed up the computation for large values of ``n`` + + OUTPUT: + + If ``existence=False``, the function returns group ``G`` and two complementary + difference sets, or raises an error if data for the given ``n`` is not available. + If ``existence=True``, the function returns a boolean representing whether + complementary difference sets can be constructed for the given ``n``. + + EXAMPLES:: + + sage: from sage.combinat.designs.difference_family import complementary_difference_sets + sage: complementary_difference_sets(15) + (Ring of integers modulo 15, [1, 2, 4, 6, 7, 10, 12], [0, 1, 2, 6, 9, 13, 14]) + + If ``existence=True``, the function returns a boolean:: + + sage: complementary_difference_sets(15, existence=True) + True + sage: complementary_difference_sets(16, existence=True) + False + + TESTS:: + + sage: from sage.combinat.designs.difference_family import are_complementary_difference_sets + sage: G, A, B = complementary_difference_sets(29) + sage: are_complementary_difference_sets(G, A, B) + True + sage: G, A, B = complementary_difference_sets(65) + sage: are_complementary_difference_sets(G, A, B) + True + sage: complementary_difference_sets(10) + Traceback (most recent call last): + ... + ValueError: the parameter n must be odd + sage: complementary_difference_sets(17) + Traceback (most recent call last): + ... + NotImplementedError: complementary difference sets of order 17 are not implemented yet + + .. SEEALSO:: + + :func:`are_complementary_difference_sets` + """ + if n % 2 == 0: + if existence: + return False + raise ValueError('the parameter n must be odd') + + p, t = is_prime_power(n, get_data=True) + G, A, B = None, None, None + + if n % 4 == 3 and t > 0: + if existence: + return True + G, A, B = complementary_difference_setsI(n, check=False) + elif p % 8 == 5 and t > 0 and t % 4 in [1, 2, 3]: + if existence: + return True + G, A, B = complementary_difference_setsII(n, check=False) + elif is_prime_power(2*n + 1): + if existence: + return True + G, A, B = complementary_difference_setsIII(n, check=False) + + if existence: + return False + + if G is None: + raise NotImplementedError(f'complementary difference sets of order {n} are not implemented yet') + + if check: + assert are_complementary_difference_sets(G, A, B) + return G, A, B + + def difference_family(v, k, l=1, existence=False, explain_construction=False, check=True): r""" Return a (``k``, ``l``)-difference family on an Abelian group of cardinality ``v``. @@ -2421,24 +3122,21 @@ def difference_family(v, k, l=1, existence=False, explain_construction=False, ch INPUT: - ``v,k,l`` -- parameters of the difference family. If ``l`` is not provided - it is assumed to be ``1``. - + it is assumed to be ``1`` - ``existence`` -- if ``True``, then return either ``True`` if Sage knows how to build such design, ``Unknown`` if it does not and ``False`` if it - knows that the design does not exist. - + knows that the design does not exist - ``explain_construction`` -- instead of returning a difference family, - returns a string that explains the construction used. - - - ``check`` -- boolean (default: ``True``). If ``True`` then the result of + returns a string that explains the construction used + - ``check`` -- boolean (default: ``True``); if ``True``, then the result of the computation is checked before being returned. This should not be - needed but ensures that the output is correct. + needed but ensures that the output is correct OUTPUT: A pair ``(G,D)`` made of a group `G` and a difference family `D` on that - group. Or, if ``existence`` is ``True`` a troolean or if - ``explain_construction`` is ``True`` a string. + group. Or, if ``existence=True``` a troolean or if + ``explain_construction=True`` a string. EXAMPLES:: @@ -2524,7 +3222,7 @@ def difference_family(v, k, l=1, existence=False, explain_construction=False, ch 10: (9,8) 11: (2,1), (4,6), (5,2), (5,4), (6,3) 13: (2,1), (3,1), (3,2), (4,1), (4,3), (5,5), (6,5) - 15: (3,1), (4,6), (5,6), (7,3) + 15: (3,1), (4,6), (5,6), (7,3), (7,6) 16: (3,2), (5,4), (6,2) 17: (2,1), (4,3), (5,5), (8,7) 19: (2,1), (3,1), (3,2), (4,2), (6,5), (9,4), (9,8) @@ -2603,6 +3301,11 @@ def difference_family(v, k, l=1, existence=False, explain_construction=False, ch ....: v = p*(p+2); k = (v-1)/2; lmbda = (k-1)/2 ....: G,D = designs.difference_family(v,k,lmbda) + Check Complementary difference sets:: + + sage: for v in [15, 33, 35, 39, 51]: + ....: G, D = designs.difference_family(v, (v-1)//2, (v-1)//2-1) + Check the database:: sage: from sage.combinat.designs.database import DF,EDS @@ -2857,6 +3560,15 @@ def difference_family(v, k, l=1, existence=False, explain_construction=False, ch p,q = q,p G,D = twin_prime_powers_difference_set(p,check=False) + elif (v-1)//2 == k and (v-1)//2-1 == l and complementary_difference_sets(v, existence=True): + if existence: + return True + elif explain_construction: + return "Complementary difference sets" + else: + G, A, B = complementary_difference_sets(v) + D = [A, B] + else: if existence: return Unknown diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 83bcf34c508..6174fe3df50 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -29,13 +29,35 @@ matrix of order `n` exists if and only if `n= 1, 2` or `n` is a multiple of `4`. -The module below implements the Paley constructions (see for example -[Hora]_) and the Sylvester construction. It also allows you to pull a -Hadamard matrix from the database at [SloaHada]_. +The module below implements constructions of Hadamard and skew Hadamard matrices +for all known orders `\le 1000`, plus some more greater than `1000`. It also +allows you to pull a Hadamard matrix from the database at [SloaHada]_. + +The following code will test that a construction for all known orders `\le 4k` +is implemented. The assertion above can be verified by setting ``k=250`` +(note that it will take a long time to run):: + + sage: from sage.combinat.matrices.hadamard_matrix import (hadamard_matrix, + ....: skew_hadamard_matrix, is_hadamard_matrix, + ....: is_skew_hadamard_matrix) + sage: k = 20 + sage: unknown_hadamard = [668, 716, 892] + sage: unknown_skew_hadamard = [356, 404, 428, 476, 596, 612, 668, 708, 712, 716, + ....: 764, 772, 804, 808, 820, 836, 856, 892, 900, 916, + ....: 932, 940, 952, 980, 996] + sage: for n in range(1, k+1): + ....: if 4*n not in unknown_hadamard: + ....: H = hadamard_matrix(4*n, check=False) + ....: assert is_hadamard_matrix(H) + ....: if 4*n not in unknown_skew_hadamard: + ....: H = skew_hadamard_matrix(4*n, check=False) + ....: assert is_skew_hadamard_matrix(H) AUTHORS: - David Joyner (2009-05-17): initial version +- Matteo Cati (2023-03-18): implemented more constructions for Hadamard and skew + Hadamard matrices, to cover all known orders up to 1000. REFERENCES: @@ -57,10 +79,11 @@ from math import sqrt from urllib.request import urlopen -from sage.arith.misc import divisors, is_prime_power, is_square +from sage.arith.misc import divisors, is_prime_power, is_square, is_prime from sage.combinat.designs.difference_family import (get_fixed_relative_difference_set, relative_difference_set_from_homomorphism, - skew_supplementary_difference_set) + skew_supplementary_difference_set, + complementary_difference_sets) from sage.combinat.t_sequences import T_sequences_smallcases from sage.cpython.string import bytes_to_str from sage.rings.integer_ring import ZZ @@ -128,8 +151,7 @@ def hadamard_matrix_paleyI(n, normalize=True): INPUT: - ``n`` -- the matrix size - - - ``normalize`` (boolean) -- whether to normalize the result. + - ``normalize``-- boolean (default: ``True``); whether to normalize the result EXAMPLES: @@ -197,7 +219,7 @@ def symmetric_conference_matrix_paley(n): INPUT: - - ``n`` -- integer, the order of the symmetric conference matrix to consruct. + - ``n`` -- integer; the order of the symmetric conference matrix to construct EXAMPLES:: @@ -295,26 +317,25 @@ def hadamard_matrix_paleyII(n): def hadamard_matrix_miyamoto_construction(n, existence=False, check=True): r""" - Construct Hadamard matrix using Miyamoto construction. + Construct Hadamard matrix using the Miyamoto construction. If `q = n/4` is a prime power, and there exists an Hadamard matrix of order `q-1`, then a Hadamard matrix of order `n` can be constructed (see [Miy1991]_). INPUT: - - ``n`` -- integer, the order of the matrix to be constructed. - - - ``check`` -- boolean: if True (default), check the the matrix is a Hadamard - before returning. - - - ``existence`` -- boolean (default False): if True, only check if the matrix exists. + - ``n`` -- integer; the order of the matrix to be constructed + - ``check`` -- boolean (default: ``True``); if ``True``, check that the matrix + is a Hadamard before returning + - ``existence`` -- boolean (default: ``False``); if ``True``, only check if + the matrix exists OUTPUT: - If ``existence`` is false, returns the Hadamard matrix of order `n`. It raises + If ``existence=False``, returns the Hadamard matrix of order `n`. It raises an error if no data is available to construct the matrix of the given order, or if `n` does not satisfies the constraints. - If ``existence`` is true, returns a boolean representing whether the matrix + If ``existence=True``, returns a boolean representing whether the matrix can be constructed or not. EXAMPLES: @@ -411,15 +432,12 @@ def hadamard_matrix_williamson_type(a, b, c, d, check=True): INPUT: - - ``a`` -- (1,-1) list specifying the 1st row of `A`. - - - ``b`` -- (1,-1) list specifying the 1st row of `B`. - - - ``d`` -- (1,-1) list specifying the 1st row of `C`. - - - ``c`` -- (1,-1) list specifying the 1st row of `D`. - - - ``check`` (boolean) -- Whether to check that the output is an Hadamard matrix before returning it. + - ``a`` -- (1,-1) list; the 1st row of `A` + - ``b`` -- (1,-1) list; the 1st row of `B` + - ``d`` -- (1,-1) list; the 1st row of `C` + - ``c`` -- (1,-1) list; the 1st row of `D` + - ``check`` -- boolean (default: ``True``); whether to check that the output + is an Hadamard matrix before returning it EXAMPLES:: @@ -478,9 +496,9 @@ def williamson_type_quadruples_smallcases(n, existence=False): INPUT: - - ``n`` -- the order of the matrices to be returned - - - ``existence`` -- if true, only check that we have the quadruple (default false). + - ``n`` -- integer; the order of the matrices to be returned + - ``existence`` -- boolean (dafault: ``False``); if ``True``, only check that + we have the quadruple OUTPUT: @@ -584,11 +602,10 @@ def williamson_hadamard_matrix_smallcases(n, existence=False, check=True): INPUT: - - ``n`` -- the order of the matrix. - - - ``existence`` -- if true, only check that we can do the construction (default false). - - - ``check`` -- if true (default), check the result. + - ``n`` -- integer; the order of the matrix + - ``existence`` -- boolean (dafault: ``False``); if ``True``, only check that + we can do the construction + - ``check`` -- boolean (default: ``True``); if ``True`` check the result TESTS:: @@ -683,13 +700,10 @@ def construction_four_symbol_delta_code_I(X, Y, Z, W): INPUT: - - ``X`` -- a list, representing the first sequence (length `n+1`). - - - ``Y`` -- a list, representing the second sequence (length `n+1`). - - - ``Z`` -- a list, representing the third sequence (length `n`). - - - ``W`` -- a list, representing the fourth sequence (length `n`). + - ``X`` -- list; the first sequence (length `n+1`) + - ``Y`` -- list; the second sequence (length `n+1`) + - ``Z`` -- list; the third sequence (length `n`) + - ``W`` -- list; the fourth sequence (length `n`) OUTPUT: A tuple containing the 4-symbol `\delta` code of length `2n+1`. @@ -756,13 +770,10 @@ def construction_four_symbol_delta_code_II(X, Y, Z, W): INPUT: - - ``X`` -- a list, representing the first sequence (length `n+1`). - - - ``Y`` -- a list, representing the second sequence (length `n+1`). - - - ``Z`` -- a list, representing the third sequence (length `n`). - - - ``W`` -- a list, representing the fourth sequence (length `n`). + - ``X`` -- list; the first sequence (length `n+1`) + - ``Y`` -- list; the second sequence (length `n+1`) + - ``Z`` -- list; the third sequence (length `n`) + - ``W`` -- list; the fourth sequence (length `n`) OUTPUT: A tuple containing the four 4-symbol `\delta` code of length `4n+3`. @@ -814,7 +825,7 @@ def alternate(seq1, seq2): def four_symbol_delta_code_smallcases(n, existence=False): r""" - Return the 4-symobl `\delta` code of length `n` if available. + Return the 4-symobl `\delta` code of length ``n`` if available. The 4-symbol `\delta` codes are constructed using :func:`construction_four_symbol_delta_code_I` or :func:`construction_four_symbol_delta_code_II`. @@ -822,9 +833,9 @@ def four_symbol_delta_code_smallcases(n, existence=False): INPUT: - - ``n`` -- integer, the length of the desired 4-symbol `\delta` code. - - - ``existence`` -- boolean, if true only check if the sequences are available. + - ``n`` -- integer; the length of the desired 4-symbol `\delta` code + - ``existence`` -- boolean (default: ``False``); if ``True``, only check if + the sequences are available EXAMPLES:: @@ -893,13 +904,10 @@ def _construction_goethals_seidel_matrix(A, B, C, D): INPUT: - - ``A`` -- The first matrix used in the construction. - - - ``B`` -- The second matrix used in the construction. - - - ``C`` -- The third matrix used in the construction. - - - ``D`` -- The fourth matrix used in the construction. + - ``A`` -- The first matrix used in the construction + - ``B`` -- The second matrix used in the construction + - ``C`` -- The third matrix used in the construction + - ``D`` -- The fourth matrix used in the construction TESTS:: @@ -930,7 +938,8 @@ def _construction_goethals_seidel_matrix(A, B, C, D): def hadamard_matrix_from_sds(n, existence=False, check=True): - r"""Construction of Hadamard matrices from supplementary difference sets. + r""" + Construction of Hadamard matrices from supplementary difference sets. Given four SDS with parameters `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` with `n_1 + n_2 + n_3 + n_4 = n+\lambda` we can construct four (-1,+1) sequences `a_i = (a_{i,0},...,a_{i,n-1})` @@ -943,19 +952,18 @@ def hadamard_matrix_from_sds(n, existence=False, check=True): INPUT: - - ``n`` -- integer, the order of the matrix to be constructed. - - - ``check`` -- boolean: if True (default), check the the matrix is a Hadamard - before returning. - - - ``existence`` -- boolean (default False): if True, only check if the matrix exists. + - ``n`` -- integer; the order of the matrix to be constructed + - ``check`` -- boolean (default: ``True``); if ``True``, check that the matrix + is a Hadamard before returning + - ``existence`` -- boolean (default: ``False``); if ``True``, only check if the + matrix exists OUTPUT: - If ``existence`` is false, returns the Hadamard matrix of order `n`. It raises + If ``existence=False``, returns the Hadamard matrix of order `n`. It raises an error if no data is available to construct the matrix of the given order, or if `n` is not a multiple of `4`. - If ``existence`` is true, returns a boolean representing whether the matrix + If ``existence=True``, returns a boolean representing whether the matrix can be constructed or not. EXAMPLES: @@ -987,16 +995,16 @@ def hadamard_matrix_from_sds(n, existence=False, check=True): ... ValueError: n must be a positive multiple of four. """ - from sage.combinat.designs.difference_family import supplementary_difference_set + from sage.combinat.designs.difference_family import supplementary_difference_set_hadamard if n <= 0 or n % 4 != 0: raise ValueError(f'n must be a positive multiple of four.') t = n // 4 if existence: - return supplementary_difference_set(t, existence=True) + return supplementary_difference_set_hadamard(t, existence=True) - S1, S2, S3, S4 = supplementary_difference_set(t, check=False) + _, [S1, S2, S3, S4] = supplementary_difference_set_hadamard(t, check=False) a = [-1 if i in S1 else 1 for i in range(t)] b = [-1 if i in S2 else 1 for i in range(t)] c = [-1 if i in S3 else 1 for i in range(t)] @@ -1028,24 +1036,16 @@ def hadamard_matrix_cooper_wallis_construction(x1, x2, x3, x4, A, B, C, D, check INPUT: - - ``x1`` -- a list or vector, representing the first row of the circulant matrix `X_1`. - - - ``x2`` -- a list or vector, representing the first row of the circulant matrix `X_2`. - - - ``x3`` -- a list or vector, representing the first row of the circulant matrix `X_3`. - - - ``x4`` -- a list or vector, representing the first row of the circulant matrix `X_4`. - - - ``A`` -- the matrix described above. - - - ``B`` -- the matrix described above. - - - ``C`` -- the matrix described above. - - - ``D`` -- the matrix described above. - - - ``check`` -- a boolean, if true (default) check that the resulting matrix is Hadamard - before returing it. + - ``x1`` -- list or vector; the first row of the circulant matrix `X_1` + - ``x2`` -- list or vector; the first row of the circulant matrix `X_2` + - ``x3`` -- list or vector; the first row of the circulant matrix `X_3` + - ``x4`` -- list or vector; the first row of the circulant matrix `X_4` + - ``A`` -- the matrix described above + - ``B`` -- the matrix described above + - ``C`` -- the matrix described above + - ``D`` -- the matrix described above + - ``check`` -- boolean (default: ``True``); if ``True``, check that the resulting + matrix is Hadamard before returing it. EXAMPLES:: @@ -1118,17 +1118,17 @@ def hadamard_matrix_cooper_wallis_smallcases(n, check=True, existence=False): INPUT: - - ``n`` -- integer, the order of the matrix to be constructed. - - - ``check`` -- boolean: if True (default), check the the matrix is an Hadamard matrix before returning. - - - ``existence`` -- boolean (default False): if True, only check if matrix exists. + - ``n`` -- integer; the order of the matrix to be constructed + - ``check`` -- boolean (default: ``True``); if ``True``, check that the matrix + is an Hadamard matrix before returning + - ``existence`` -- boolean (default: ``False``); if ``True``, only check if + the matrix exists. OUTPUT: - If ``existence`` is false, returns the Hadamard matrix of order `n`. It raises an error if no data + If ``existence=False``, returns the Hadamard matrix of order `n`. It raises an error if no data is available to construct the matrix of the given order. - If ``existence`` is true, returns a boolean representing whether the matrix can be constructed or not. + If ``existence=True``, returns a boolean representing whether the matrix can be constructed or not. .. SEEALSO:: @@ -1210,14 +1210,15 @@ def _get_baumert_hall_units(n, existence=False): INPUT: - - ``n`` -- integer, the size of the Baumert-Hall units. - - - ``existence`` -- boolean (default False): if true only check whether the units can be contructed. + - ``n`` -- integer; the size of the Baumert-Hall units + - ``existence`` -- boolean (default: ``False``); if ``True``, only check whether + the units can be contructed OUTPUT: - If ``existence`` is true, return a boolean representing whether the Baumert-Hall units can - be constructed. Otherwise, return a tuple containing the four Baumert-Hall units. + If ``existence=True``, return a boolean representing whether the Baumert-Hall + units can be constructed. Otherwise, return a tuple containing the four + Baumert-Hall units. EXAMPLES:: @@ -1283,23 +1284,16 @@ def hadamard_matrix_turyn_type(a, b, c, d, e1, e2, e3, e4, check=True): INPUT: - - ``a`` -- 1,-1 list specifying the 1st row of `A`. - - - ``b`` -- 1,-1 list specifying the 1st row of `B`. - - - ``d`` -- 1,-1 list specifying the 1st row of `C`. - - - ``c`` -- 1,-1 list specifying the 1st row of `D`. - - - ``e1`` -- Matrix representing the first Baumert-Hall unit. - - - ``e2`` -- Matrix representing the second Baumert-Hall unit. - - - ``e3`` -- Matrix representing the third Baumert-Hall unit. - - - ``e4`` -- Matrix representing the fourth Baumert-Hall unit. - - - ``check`` -- Whether to check that the output is an Hadamard matrix before returning it. + - ``a`` -- 1,-1 list; the 1st row of `A` + - ``b`` -- 1,-1 list; the 1st row of `B` + - ``d`` -- 1,-1 list; the 1st row of `C` + - ``c`` -- 1,-1 list; the 1st row of `D` + - ``e1`` -- Matrix; the first Baumert-Hall unit + - ``e2`` -- Matrix; the second Baumert-Hall unit + - ``e3`` -- Matrix; the third Baumert-Hall unit + - ``e4`` -- Matrix; the fourth Baumert-Hall unit + - ``check`` -- boolean (default: ``True``); whether to check that the output + is an Hadamard matrix before returning it EXAMPLES:: @@ -1362,11 +1356,11 @@ def turyn_type_hadamard_matrix_smallcases(n, existence=False, check=True): INPUT: - - ``n`` -- integer, the order of the matrix to be constructed. - - - ``existence`` -- boolean (default False): if True, only check if matrix exists. - - - ``check`` -- boolean: if True (default), check the the matrix is an Hadamard matrix before returning. + - ``n`` -- integer; the order of the matrix to be constructed + - ``existence`` -- boolean (default: ``False``): if ``True``, only check if + the matrix exists + - ``check`` -- boolean (default: ``True``): if ``True``, check that the matrix + is an Hadamard matrix before returning EXAMPLES:: @@ -1407,7 +1401,8 @@ def turyn_type_hadamard_matrix_smallcases(n, existence=False, check=True): def hadamard_matrix_spence_construction(n, existence=False, check=True): - r"""Create an Hadamard matrix of order `n` using Spence construction. + r""" + Create an Hadamard matrix of order `n` using the Spence construction. This construction (detailed in [Spe1975]_), uses supplementary difference sets implemented in :func:`sage.combinat.designs.difference_family.supplementary_difference_set_from_rel_diff_set` to create the @@ -1415,16 +1410,17 @@ def hadamard_matrix_spence_construction(n, existence=False, check=True): INPUT: - - ``n`` -- integer, the order of the matrix to be constructed. - - - ``existence`` -- boolean (default False): if True, only check if matrix exists. - - - ``check`` -- bolean: if True (default), check the the matrix is an Hadamard matrix before returning. + - ``n`` -- integer; the order of the matrix to be constructed + - ``existence`` -- boolean (default: ``False``); if ``True``, only check if + the matrix exists + - ``check`` -- bolean (default: ``True``); if ``True``, check that the matrix + is an Hadamard matrix before returning OUTPUT: - If ``existence`` is true, returns a boolean representing whether the Hadamard matrix can - be constructed. Otherwise, returns the Hadamard matrix, or raises an error if it cannot be constructed. + If ``existence=True``, returns a boolean representing whether the Hadamard + matrix can be constructed. Otherwise, returns the Hadamard matrix, or raises + an error if it cannot be constructed. EXAMPLES:: @@ -1469,12 +1465,13 @@ def hadamard_matrix_spence_construction(n, existence=False, check=True): if not supplementary_difference_set_from_rel_diff_set(q, existence=True): raise ValueError(f'The order {n} is not covered by Spence construction.') - S1, S2, S3, S4 = supplementary_difference_set_from_rel_diff_set(q, check=False) + G, [S1, S2, S3, S4] = supplementary_difference_set_from_rel_diff_set(q, check=False) - A1 = matrix.circulant([1 if j in S1 else -1 for j in range(q-1)]) - A2 = matrix.circulant([1 if j in S4 else -1 for j in range(q-1)]) - A3 = matrix.circulant([1 if j in S3 else -1 for j in range(q-1)]) - A4 = matrix.circulant([1 if j in S2 else -1 for j in range(q-1)]) + Glist = list(G) + A1 = matrix.circulant([1 if j in S1 else -1 for j in Glist]) + A2 = matrix.circulant([1 if j in S4 else -1 for j in Glist]) + A3 = matrix.circulant([1 if j in S3 else -1 for j in Glist]) + A4 = matrix.circulant([1 if j in S2 else -1 for j in Glist]) P = matrix(ZZ, [[1 if (i + j) % (q-1) == 0 else 0 for i in range(1, q)] for j in range(1, q)]) @@ -1497,20 +1494,17 @@ def hadamard_matrix_spence_construction(n, existence=False, check=True): def is_hadamard_matrix(M, normalized=False, skew=False, verbose=False): r""" - Test if `M` is a Hadamard matrix. + Test if ``M`` is a Hadamard matrix. INPUT: - ``M`` -- a matrix - - - ``normalized`` (boolean) -- whether to test if ``M`` is a normalized - Hadamard matrix, i.e. has its first row/column filled with +1. - - - ``skew`` (boolean) -- whether to test if ``M`` is a skew - Hadamard matrix, i.e. `M=S+I` for `-S=S^\top`, and `I` the identity matrix. - - - ``verbose`` (boolean) -- whether to be verbose when the matrix is not - Hadamard. + - ``normalized`` -- boolean (default: ``False``); whether to test if ``M`` + is a normalized Hadamard matrix, i.e. has its first row/column filled with +1 + - ``skew`` -- boolean (default: ``False``); whether to test if ``M`` is a skew + Hadamard matrix, i.e. `M=S+I` for `-S=S^\top`, and `I` the identity matrix + - ``verbose`` -- boolean (default: ``False``); whether to be verbose when + the matrix is not Hadamard EXAMPLES:: @@ -1611,19 +1605,17 @@ def is_hadamard_matrix(M, normalized=False, skew=False, verbose=False): def is_skew_hadamard_matrix(M, normalized=False, verbose=False): r""" - Test if `M` is a skew Hadamard matrix. + Test if ``M`` is a skew Hadamard matrix. this is a wrapper around the function :func:`is_hadamard_matrix` INPUT: - ``M`` -- a matrix - - - ``normalized`` (boolean) -- whether to test if ``M`` is a skew-normalized - Hadamard matrix, i.e. has its first row filled with +1. - - - ``verbose`` (boolean) -- whether to be verbose when the matrix is not - skew Hadamard. + - ``normalized`` -- boolean (default: ``False``); whether to test if ``M`` + is a skew-normalized Hadamard matrix, i.e. has its first row filled with +1 + - ``verbose`` -- boolean (default: ``False``); whether to be verbose when the + matrix is not skew Hadamard EXAMPLES:: @@ -1648,25 +1640,24 @@ def hadamard_matrix(n, existence=False, check=True): r""" Tries to construct a Hadamard matrix using the available methods. - INPUT: + Currently all orders `\le 1000` for which a construction is + known are implemented. For `n > 1000`, only some orders are available. - - ``n`` (integer) -- dimension of the matrix + INPUT: - - ``existence`` (boolean) -- whether to build the matrix or merely query if - a construction is available in Sage. When set to ``True``, the function - returns: + - ``n`` -- integer; dimension of the matrix + - ``existence`` -- boolean (default: ``False``); whether to build the matrix + or merely query if a construction is available in Sage. When set to ``True``, + the function returns: - ``True`` -- meaning that Sage knows how to build the matrix - - ``Unknown`` -- meaning that Sage does not know how to build the matrix, although the matrix may exist (see :mod:`sage.misc.unknown`). - - ``False`` -- meaning that the matrix does not exist. - - ``check`` (boolean) -- whether to check that output is correct before + - ``check`` -- boolean (default: ``True``); whether to check that output is correct before returning it. As this is expected to be useless (but we are cautious - guys), you may want to disable it whenever you want speed. Set to ``True`` - by default. + guys), you may want to disable it whenever you want speed. EXAMPLES:: @@ -1880,9 +1871,8 @@ def regular_symmetric_hadamard_matrix_with_constant_diagonal(n, e, existence=Fal INPUT: - - ``n`` (integer) -- side of the matrix - - - ``e`` -- one of `-1` or `+1`, equal to the value of `\epsilon` + - ``n`` -- integer; side of the matrix + - ``e`` -- `-1` or `+1`; the value of `\epsilon` EXAMPLES:: @@ -2058,7 +2048,7 @@ def RSHCD_324(e): INPUT: - - ``e`` -- one of `-1` or `+1`, equal to the value of `\epsilon` + - ``e`` -- `-1` or `+1`; the value of `\epsilon` TESTS:: @@ -2104,10 +2094,9 @@ def _helper_payley_matrix(n, zero_position=True): INPUT: - - ``n`` -- an odd prime power. - - - ``zero_position`` -- if it is true (default), place 0 of ``F_n`` in the middle, - otherwise place it first. + - ``n`` -- an odd prime power + - ``zero_position`` -- boolean (default: ``True``); if it is true, place 0 + of ``F_n`` in the middle, otherwise place it first .. SEEALSO:: @@ -2246,13 +2235,12 @@ def williamson_goethals_seidel_skew_hadamard_matrix(a, b, c, d, check=True): INPUT: - - ``a`` -- 1,-1 list specifying the 1st row of `A` - - - ``b`` -- 1,-1 list specifying the 1st row of `B` - - - ``d`` -- 1,-1 list specifying the 1st row of `C` - - - ``c`` -- 1,-1 list specifying the 1st row of `D` + - ``a`` -- 1,-1 list; the 1st row of `A` + - ``b`` -- 1,-1 list; the 1st row of `B` + - ``d`` -- 1,-1 list; the 1st row of `C` + - ``c`` -- 1,-1 list; the 1st row of `D` + - ``check`` -- boolean (default: ``True``); if ``True``, check that the + resulting matrix is skew Hadamard before returning it EXAMPLES:: @@ -2293,15 +2281,14 @@ def skew_hadamard_matrix_spence_construction(n, check=True): INPUT: - - ``n`` -- A positive integer. - - - ``check`` -- boolean. If true (default), check that the resulting matrix is Hadamard - before returning it. + - ``n`` -- positive integer + - ``check`` -- boolean (default: ``True``); if ``True``, check that the + resulting matrix is Hadamard before returning it OUTPUT: - If `n` satisfies the requirements described above, the function returns a `n\times n` Hadamard matrix. - Otherwise, an exception is raised. + If ``n`` satisfies the requirements described above, the function returns a + `n\times n` Hadamard matrix. Otherwise, an exception is raised. EXAMPLES:: @@ -2330,8 +2317,8 @@ def skew_hadamard_matrix_spence_construction(n, check=True): if n % 4 != 0 or not is_prime_power(q) or q % 8 != 5: raise ValueError(f'The order {n} is not covered by the Spence construction.') - D = relative_difference_set_from_homomorphism(q, 2, (q-1)//4, check=False) - D_fixed = get_fixed_relative_difference_set(D) + G, D = relative_difference_set_from_homomorphism(q, 2, (q-1)//4, check=False, return_group=True) + D_fixed = get_fixed_relative_difference_set(G, D) D_union = D_fixed + [q+1+el for el in D_fixed] D_union = list(set([el % (4*(q+1)) for el in D_union])) @@ -2369,6 +2356,128 @@ def find_a(i): return williamson_goethals_seidel_skew_hadamard_matrix(a, b, c, d, check=check) +def skew_hadamard_matrix_spence_1975(n, existence=False, check=True): + r""" + Construct a skew Hadamard matrix of order `n = 4(1 + q + q^2)` using the + Spence construction. + + If `n = 4(1 + q + q^2)` where `q` is a prime power such that either + `1 + q + q^2` is a prime congruent to `3, 5, 7 \mod 8` or `3 + 2q + 2q^2` is + a prime power, then a skew Hadamard matrix of order `n` can be constructed using + the Goethals Seidel array. The four matrices `A, B, C, D` plugged into the + GS-array are created using complementary difference sets of order `1 + q + q^2` + (which exist if `q` satisfies the conditions above), and a cyclic planar + difference set with parameters `(1 + q^2 + q^4, 1 + q^2, 1)`. + These are constructed by the functions :func:`sage.combinat.designs.difference_family.complementary_difference_sets` + and :func:`sage.combinat.designs.difference_family.difference_family`. + + For more details, see [Spe1975b]_. + + INPUT: + + - ``n`` -- positive integer; the order of the matrix to be constructed + - ``existence`` -- boolean (default: ``False``); if ``True``, only return + whether the Hadamard matrix can be constructed + - ``check`` -- boolean (default: ``True``); check that the result + is a skew Hadamard matrix before returning it + + OUTPUT: + + If ``existence=False``, returns the skew Hadamard matrix of order `n`. It + raises an error if `n` does not satisfy the required conditions. + If ``existence=True``, returns a boolean representing whether the matrix + can be constructed or not. + + EXAMPLES:: + + sage: from sage.combinat.matrices.hadamard_matrix import skew_hadamard_matrix_spence_1975 + sage: skew_hadamard_matrix_spence_1975(52) + 52 x 52 dense matrix over Integer Ring... + + If ``existence`` is True, the function returns a boolean:: + + sage: skew_hadamard_matrix_spence_1975(52, existence=True) + True + + TESTS:: + + sage: from sage.combinat.matrices.hadamard_matrix import is_skew_hadamard_matrix + sage: is_skew_hadamard_matrix(skew_hadamard_matrix_spence_1975(52, check=False)) + True + sage: skew_hadamard_matrix_spence_1975(100, existence=True) + False + sage: skew_hadamard_matrix_spence_1975(31) + Traceback (most recent call last): + ... + ValueError: n is not in the form 4*(1+q+q^2) + sage: skew_hadamard_matrix_spence_1975(16) + Traceback (most recent call last): + ... + ValueError: n is not in the form 4*(1+q+q^2) + sage: skew_hadamard_matrix_spence_1975(292) + Traceback (most recent call last): + ... + ValueError: q=8 is not a valid parameter for this construction + """ + from sage.combinat.designs.difference_family import is_fixed_relative_difference_set, difference_family, complementary_difference_sets + + q = None + m = n // 4 + for i in range(m): + if 1 + i + i**2 == m and is_prime_power(i): + q = i + break + + if n % 4 != 0 or q is None: + if existence: + return False + raise ValueError('n is not in the form 4*(1+q+q^2)') + + is_valid = (is_prime(m) and m % 8 in [3, 5, 7]) or is_prime_power(3 + 2*q + 2*q**2) + if existence: + return is_valid + + if not is_valid: + raise ValueError(f'q={q} is not a valid parameter for this construction') + + G, sets = difference_family(1 + q**2 + q**4, 1 + q**2, 1) + + def get_fixed_set(s, G, q): + for el in G: + fixed_set = [el + x for x in s] + if is_fixed_relative_difference_set(fixed_set, q): + return fixed_set + assert False + + D = get_fixed_set(sets[0], G, q) + + D1 = [d for d in D if d % (1 - q + q**2) == 0] + Dnot = [el for el in D if el not in D1] + D2 = [] + + indices = set() + for i in range(len(Dnot)): + if i in indices: + continue + indices.add(i) + for j in range(i+1, len(Dnot)): + if j not in indices and Dnot[i] % m == Dnot[j] % m: + indices.add(j) + D2.append(Dnot[i]) + + D2 = [d % m for d in D2] + D1 = [d % m for d in D1] + + G2, U, V = complementary_difference_sets(m, check=False) + G2list = list(G2) + p = [-1 if j in U else 1 for j in G2list] + q = [-1 if j in V else 1 for j in G2list] + r = [1 if j % m in D1 or j % m in D2 else -1 for j in range(m)] + s = [1 if j % m in D2 else -1 for j in range(m)] + + return williamson_goethals_seidel_skew_hadamard_matrix(p, q, r, s, check=check) + + def GS_skew_hadamard_smallcases(n, existence=False, check=True): r""" Data for Williamson-Goethals-Seidel construction of skew Hadamard matrices @@ -2390,15 +2499,14 @@ def GS_skew_hadamard_smallcases(n, existence=False, check=True): INPUT: - - ``n`` -- the order of the matrix - - - ``existence`` -- if true (default), only check that we can do the construction - - - ``check`` -- if true (default), check the result. + - ``n`` -- integer; the order of the matrix + - ``existence`` -- boolean (default: ``True``); if ``True``, only check that + we can do the construction + - ``check`` -- boolean (default: ``False``): if ``True``, check the result TESTS:: - sage: from sage.combinat.matrices.hadamard_matrix import GS_skew_hadamard_smallcases + sage: from sage.combinat.matrices.hadamard_matrix import GS_skew_hadamard_smallcases, is_skew_hadamard_matrix sage: GS_skew_hadamard_smallcases(36) 36 x 36 dense matrix over Integer Ring... sage: GS_skew_hadamard_smallcases(52) @@ -2406,6 +2514,10 @@ def GS_skew_hadamard_smallcases(n, existence=False, check=True): sage: GS_skew_hadamard_smallcases(92) 92 x 92 dense matrix over Integer Ring... sage: GS_skew_hadamard_smallcases(100) + sage: is_skew_hadamard_matrix(GS_skew_hadamard_smallcases(324, check=False)) + True + sage: is_skew_hadamard_matrix(GS_skew_hadamard_smallcases(156, check=False)) + True """ WGS = williamson_goethals_seidel_skew_hadamard_matrix @@ -2439,16 +2551,214 @@ def pmtoZ(s): if skew_supplementary_difference_set(n//4, existence=True): t = n//4 - S1, S2, S3, S4 = skew_supplementary_difference_set(t, check=False) - a = [-1 if i in S1 else 1 for i in range(t)] - b = [-1 if i in S2 else 1 for i in range(t)] - c = [-1 if i in S3 else 1 for i in range(t)] - d = [-1 if i in S4 else 1 for i in range(t)] - return WGS(a, b, c, d, check=check) + + G, [S1, S2, S3, S4] = skew_supplementary_difference_set(t, check=False, return_group=True) + Glist = list(G) + + A = matrix([[-1 if y-x in S1 else +1 for y in Glist] for x in Glist]) + B = matrix([[-1 if y-x in S2 else +1 for y in Glist] for x in Glist]) + C = matrix([[-1 if y-x in S3 else +1 for y in Glist] for x in Glist]) + D = matrix([[-1 if y-x in S4 else +1 for y in Glist] for x in Glist]) + + H = _construction_goethals_seidel_matrix(A, B, C, D) + if check: + assert is_skew_hadamard_matrix(H) + return H return None +def skew_hadamard_matrix_from_orthogonal_design(n, existence=False, check=True): + r""" + Construct skew Hadamard matrices of order `mn(n - 1)` if suitable orthogonal designs exist. + + In [Seb1978]_ is proved that if amicable Hadamard matrices of order `n` and an orthogonal + design of type `(1, m, mn - m - 1)` in order `mn` exist, then a skew Hadamard matrix + of order `mn(n - 1)` can be constructed. The paper uses amicable orthogonal designs + instead of amicable Hadamard matrices, but the two are equivalent (see [Seb2017]_). + + Amicable Hadamard matrices are constructed using :func:`amicable_hadamard_matrices`, + and the orthogonal designs are constructed using the Goethals-Seidel array, + with data taken from [Seb2017]_. + + INPUT: + + - ``n`` -- positive integer; the order of the matrix to be constructed + - ``existence`` -- boolean (default: ``False``); if ``True``, only return + whether the skew Hadamard matrix can be constructed + - ``check`` -- boolean (default: ``True``); if ``True``, check that the result + is a skew Hadamard matrix before returning it + + OUTPUT: + + If ``existence=False``, returns the skew Hadamard matrix of order `n`. It + raises an error if a construction for order `n` is not yet implemented, or if + `n` does not satisfy the constraint. + If ``existence=True``, returns a boolean representing whether the matrix + can be constructed or not. + + EXAMPLES:: + + sage: from sage.combinat.matrices.hadamard_matrix import skew_hadamard_matrix_from_orthogonal_design + sage: skew_hadamard_matrix_from_orthogonal_design(756) + 756 x 756 dense matrix over Integer Ring... + + If ``existence`` is True, the function returns a boolean:: + + sage: skew_hadamard_matrix_from_orthogonal_design(200, existence=True) + False + + TESTS:: + + sage: from sage.combinat.matrices.hadamard_matrix import is_skew_hadamard_matrix + sage: is_skew_hadamard_matrix(skew_hadamard_matrix_from_orthogonal_design(756, check=False)) + True + sage: skew_hadamard_matrix_from_orthogonal_design(31) + Traceback (most recent call last): + ... + ValueError: n must be a multiple of 4 + sage: skew_hadamard_matrix_from_orthogonal_design(16) + Traceback (most recent call last): + ... + NotImplementedError: orthogonal designs for matrix of order 16 not yet implemented + """ + # We use value i to represent entries where variable x_i should be, and -i for -x_i + orthogonal_designs = { + (1, 1, 26): [[1, 3, 3, -3, 3, -3, -3], [2, 3, 3, -3, 3, -3, -3], + [3, 3, 3, -3, 3, 3, 3], [3, 3, -3, -3, -3, 3, -3]] + } + + if n % 4 != 0: + raise ValueError('n must be a multiple of 4') + + m1, m2 = None, None + for d in divisors(n)[1:-1]: + if (n//d) % (d-1) != 0: + continue + d1 = n // (d*(d - 1)) + if (1, d1, d1*d - d1 - 1) in orthogonal_designs and amicable_hadamard_matrices(d, existence=True): + m1 = d1 + m2 = d + + if m2 is None or m1 is None: + if existence: + return False + raise NotImplementedError(f'orthogonal designs for matrix of order {n} not yet implemented') + + if existence: + return True + + M, N = amicable_hadamard_matrices(m2, check=False) + M = normalise_hadamard(M, skew=True) + N = normalise_hadamard(N) + + P = M[1:, 1:] - I(m2 - 1) + D = N[1:, 1:] + + A1, A2, A3, A4 = map(matrix.circulant, orthogonal_designs[(1, m1, m1*m2 - m1 - 1)]) + OD = _construction_goethals_seidel_matrix(A1, A2, A3, A4) + + blocks = {1: P, -1: -P, 2: J(m2 - 1), -2: -J(m2 - 1), 3: D, -3: -D} + H = block_matrix([[blocks[el] for el in row] for row in OD]) + I(n) + + if check: + assert is_skew_hadamard_matrix(H) + + return H + + +def skew_hadamard_matrix_from_complementary_difference_sets(n, existence=False, check=True): + r""" + Construct a skew Hadamard matrix of order `n=4(m+1)` from complementary difference sets. + + If `A, B` are complementary difference sets over a group of order `2m+1`, then + they can be used to construct a skew Hadamard matrix, as described in [BS1969]_. + + The complementary difference sets are constructed using the function + :func:`sage.combinat.designs.difference_family.complementary_difference_sets`. + + INPUT: + + - ``n`` -- positive integer; the order of the matrix to be constructed + - ``existence`` -- boolean (default: ``False``); if ``True``, only return + whether the skew Hadamard matrix can be constructed + - ``check`` -- boolean (default: ``True``); if ``True``, check that the + result is a skew Hadamard matrix before returning it + + OUTPUT: + + If ``existence=False``, returns the skew Hadamard matrix of order `n`. It + raises an error if `n` does not satisfy the required conditions. + If ``existence=True``, returns a boolean representing whether the matrix + can be constructed or not. + + EXAMPLES:: + + sage: from sage.combinat.matrices.hadamard_matrix import skew_hadamard_matrix_from_complementary_difference_sets + sage: skew_hadamard_matrix_from_complementary_difference_sets(20) + 20 x 20 dense matrix over Integer Ring... + sage: skew_hadamard_matrix_from_complementary_difference_sets(52, existence=True) + True + + TESTS:: + + sage: from sage.combinat.matrices.hadamard_matrix import is_skew_hadamard_matrix + sage: is_skew_hadamard_matrix(skew_hadamard_matrix_from_complementary_difference_sets(24, check=False)) + True + sage: is_skew_hadamard_matrix(skew_hadamard_matrix_from_complementary_difference_sets(12, check=False)) + True + sage: skew_hadamard_matrix_from_complementary_difference_sets(31) + Traceback (most recent call last): + ... + ValueError: n must be 1, 2 or a multiple of four. + sage: skew_hadamard_matrix_from_complementary_difference_sets(100) + Traceback (most recent call last): + ... + NotImplementedError: hadamard matrix of order 100 from complementary difference sets is not implemented yet + sage: skew_hadamard_matrix_from_complementary_difference_sets(100, existence=True) + False + """ + + if n <= 0 or (n > 2 and n % 4 != 0): + raise ValueError('n must be 1, 2 or a multiple of four.') + + m = n//4 - 1 + + if existence: + return complementary_difference_sets(2*m+1, existence=True) + + if not complementary_difference_sets(2*m+1, existence=True): + raise NotImplementedError(f'hadamard matrix of order {n} from complementary difference sets is not implemented yet') + + G, A, B = complementary_difference_sets(2*m+1, check=False) + + m = n//4 - 1 + Glist = list(G) + + S = [[0 for i in range(n)] for j in range(n)] + for i in range(2*m + 1): + for j in range(2*m + 1): + S[2*m + 1 + i][2*m + 1 + j] = -1 if Glist[j] - Glist[i] in A else 1 + S[i][j] = -S[2*m + 1 + i][2*m + 1 + j] + S[2*m + 1 + j][i] = -1 if Glist[j] - Glist[i] in B else 1 + S[i][2*m + 1 + j] = -S[2*m + 1 + j][i] + S[4*m + 2][i] = -1 + S[4*m + 2][2*m + 1 + i] = 1 + S[i][4*m + 2] = 1 + S[i + 2*m + 1][4*m + 2] = -1 + for i in range(4*m + 3): + S[4*m + 3][i] = 1 + S[i][4*m + 3] = -1 + for i in range(4*m + 4): + S[i][i] = 1 + + H = matrix(S) + + if check: + assert is_hadamard_matrix(H, skew=True) + return H + + def skew_hadamard_matrix_whiteman_construction(n, existence=False, check=True): r""" Construct a skew Hadamard matrix of order `n=2(q+1)` where `q=p^t` is a prime power with `p \cong 5 \mod 8` and `t \cong 2 \mod 4`. @@ -2458,18 +2768,18 @@ def skew_hadamard_matrix_whiteman_construction(n, existence=False, check=True): INPUT: - - ``n`` -- A positive integer, the order of the matrix to be constructed. - - - ``existence`` -- boolean (default False). If True, only return whether the Hadamard matrix can be constructed. - - - ``check`` -- boolean: if True (default), check the the result is a skew Hadamard matrix - before returning it. + - ``n`` -- positive integer; the order of the matrix to be constructed + - ``existence`` -- boolean (default: ``False``); If ``True``, only return + whether the Hadamard matrix can be constructed + - ``check`` -- boolean (default: ``True``); if ``True``, check that the result + is a skew Hadamard matrix before returning it OUTPUT: - If ``existence`` is false, returns the skew Hadamard matrix of order `n`. It raises an error if `n` does - not satisfy the required conditions. - If ``existence`` is true, returns a boolean representing whether the matrix can be constructed or not. + If ``existence=False``, returns the skew Hadamard matrix of order `n`. It + raises an error if `n` does not satisfy the required conditions. + If ``existence=True``, returns a boolean representing whether the matrix can + be constructed or not. EXAMPLES:: @@ -2496,6 +2806,10 @@ def skew_hadamard_matrix_whiteman_construction(n, existence=False, check=True): ValueError: The order 100 is not covered by the Whiteman construction. sage: skew_hadamard_matrix_whiteman_construction(100, existence=True) False + + .. NOTE:: + + A more general version of this construction is :func:`skew_hadamard_matrix_from_complementary_difference_sets`. """ q = n // 2 - 1 @@ -2542,59 +2856,6 @@ def skew_hadamard_matrix_whiteman_construction(n, existence=False, check=True): return H -def skew_hadamard_matrix_324(): - r""" - Construct a skew Hadamard matrix of order 324. - - The construction is taken from [Djo1994a]_. It uses four supplementary difference sets `S_1, S_2, S_3, S_4`, - with `S_1` of skew type. These are then used to generate four matrices of order `81`, which are - inserted into the Goethals-Seidel array. - - EXAMPLES:: - - sage: from sage.combinat.matrices.hadamard_matrix import skew_hadamard_matrix_324 - sage: skew_hadamard_matrix_324() - 324 x 324 dense matrix over Integer Ring... - - TESTS:: - - sage: from sage.combinat.matrices.hadamard_matrix import is_hadamard_matrix - sage: is_hadamard_matrix(skew_hadamard_matrix_324(), skew=True) - True - """ - from sage.symbolic.ring import SymbolicRing - from sage.rings.finite_rings.integer_mod_ring import Zmod - - R = SymbolicRing() - x = R.var('x') - Z3 = Zmod(3) - F = Z3.extension(x**4 - x**3 - 1) - H = [(F.gen()**16)**i for i in range(10)] - - cosets = [] - for i in range(8): - cosets.append([F.gen()**i * el for el in H]) - cosets.append([-F.gen()**i * el for el in H]) - - def generate_set(index_set, cosets): - S = [] - for idx in index_set: - S += cosets[idx] - return S - - S1 = generate_set([1, 2, 4, 6, 8, 10, 12, 14], cosets) - S2 = generate_set([1, 2, 3, 4, 10, 11, 13], cosets) - S3 = generate_set([4, 5, 6, 8, 12, 13, 14], cosets) - S4 = generate_set([2, 4, 5, 6, 7, 11, 12, 13, 15], cosets) - - A = matrix([[-1 if y-x in S1 else +1 for y in F] for x in F]) - B = matrix([[-1 if y-x in S2 else +1 for y in F] for x in F]) - C = matrix([[-1 if y-x in S3 else +1 for y in F] for x in F]) - D = matrix([[-1 if y-x in S4 else +1 for y in F] for x in F]) - - return _construction_goethals_seidel_matrix(A, B, C, D) - - def skew_hadamard_matrix_from_good_matrices(a, b, c, d, check=True): r""" Construct skew Hadamard matrix from good matrices. @@ -2613,15 +2874,12 @@ def skew_hadamard_matrix_from_good_matrices(a, b, c, d, check=True): INPUT: - - ``a`` -- (1,-1) list specifying the 1st row of `A`. - - - ``b`` -- (1,-1) list specifying the 1st row of `B`. - - - ``d`` -- (1,-1) list specifying the 1st row of `C`. - - - ``c`` -- (1,-1) list specifying the 1st row of `D`. - - - ``check`` -- boolean: if True (default), check that the matrix is a skew Hadamard matrix before returning it. + - ``a`` -- (1,-1) list; the 1st row of `A` + - ``b`` -- (1,-1) list; the 1st row of `B` + - ``d`` -- (1,-1) list; the 1st row of `C` + - ``c`` -- (1,-1) list; the 1st row of `D` + - ``check`` -- boolean (default: ``True``); if ``True``, check that the matrix + is a skew Hadamard matrix before returning it EXAMPLES:: @@ -2701,17 +2959,19 @@ def skew_hadamard_matrix_from_good_matrices_smallcases(n, existence=False, check INPUT: - - ``n`` -- the order of the skew Hadamard matrix to be constructed. - - - ``existence`` -- boolean (default False). If True, only return whether the Hadamard matrix can be constructed. - - - ``check`` -- boolean: if True (default), check the the matrix is an Hadamard matrix before returning it. + - ``n`` -- integer; the order of the skew Hadamard matrix to be constructed + - ``existence`` -- boolean (default: ``False``); If ``True``, only return + whether the Hadamard matrix can be constructed + - ``check`` -- boolean (default: ``True``): if ``True``, check that the matrix + is an Hadamard matrix before returning it OUTPUT: - If ``existence`` is false, returns the skew Hadamard matrix of order `n`. It raises an error if no data - is available to construct the matrix of the given order. - If ``existence`` is true, returns a boolean representing whether the matrix can be constructed or not. + If ``existence=False``, returns the skew Hadamard matrix of order `n`. It + raises an error if no data is available to construct the matrix of the given + order. + If ``existence=True``, returns a boolean representing whether the matrix can + be constructed or not. EXAMPLES:: @@ -2791,34 +3051,29 @@ def pm_to_good_matrix(s, sign=1): def skew_hadamard_matrix(n, existence=False, skew_normalize=True, check=True): r""" - Tries to construct a skew Hadamard matrix + Tries to construct a skew Hadamard matrix. A Hadamard matrix `H` is called skew if `H=S-I`, for `I` the identity matrix - and `-S=S^\top`. Currently constructions from Section 14.1 of [Ha83]_ and few - more exotic ones are implemented. + and `-S=S^\top`. Currently all orders `\le 1000` for which a construction is + known are implemented. For `n > 1000`, only some orders are available. INPUT: - - ``n`` (integer) -- dimension of the matrix - - - ``existence`` (boolean) -- whether to build the matrix or merely query if - a construction is available in Sage. When set to ``True``, the function - returns: + - ``n`` -- integer; dimension of the matrix + - ``existence`` -- boolean (default: ``False``); whether to build the matrix + or merely query if a construction is available in Sage. When set to ``True``, + the function returns: - ``True`` -- meaning that Sage knows how to build the matrix - - ``Unknown`` -- meaning that Sage does not know how to build the matrix, but that the design may exist (see :mod:`sage.misc.unknown`). - - ``False`` -- meaning that the matrix does not exist. - - ``skew_normalize`` (boolean) -- whether to make the 1st row all-one, and - adjust the 1st column accordingly. Set to ``True`` by default. - - - ``check`` (boolean) -- whether to check that output is correct before - returning it. As this is expected to be useless (but we are cautious - guys), you may want to disable it whenever you want speed. Set to ``True`` - by default. + - ``skew_normalize`` -- boolean (default: ``True``); whether to make the 1st + row all-one, and adjust the 1st column accordingly + - ``check`` -- boolean (default: ``True``); whether to check that output is + correct before returning it. As this is expected to be useless (but we are + cautious guys), you may want to disable it whenever you want speed EXAMPLES:: @@ -2894,10 +3149,6 @@ def true(): if existence: return true() M = matrix([1]) - elif n == 324: - if existence: - return true() - M = skew_hadamard_matrix_324() elif skew_hadamard_matrix_from_good_matrices_smallcases(n, existence=True): if existence: return true() @@ -2910,10 +3161,18 @@ def true(): if existence: return true() M = skew_hadamard_matrix_spence_construction(n, check=False) - elif skew_hadamard_matrix_whiteman_construction(n, existence=True): + elif skew_hadamard_matrix_from_complementary_difference_sets(n, existence=True): + if existence: + return true() + M = skew_hadamard_matrix_from_complementary_difference_sets(n, check=False) + elif skew_hadamard_matrix_spence_1975(n, existence=True): if existence: return true() - M = skew_hadamard_matrix_whiteman_construction(n, check=False) + M = skew_hadamard_matrix_spence_1975(n, check=False) + elif skew_hadamard_matrix_from_orthogonal_design(n, existence=True): + if existence: + return true() + M = skew_hadamard_matrix_from_orthogonal_design(n, check=False) elif n % 8 == 0: if skew_hadamard_matrix(n//2, existence=True) is True: # (Lemma 14.1.6 in [Ha83]_) if existence: @@ -2968,12 +3227,10 @@ def symmetric_conference_matrix(n, check=True): INPUT: - - ``n`` (integer) -- dimension of the matrix - - - ``check`` (boolean) -- whether to check that output is correct before - returning it. As this is expected to be useless (but we are cautious - guys), you may want to disable it whenever you want speed. Set to ``True`` - by default. + - ``n`` -- integer; dimension of the matrix + - ``check`` -- boolean (default: ``True``); whether to check that output is + correct before returning it. As this is expected to be useless (but we are + cautious guys), you may want to disable it whenever you want speed EXAMPLES:: @@ -3021,9 +3278,9 @@ def szekeres_difference_set_pair(m, check=True): INPUT: - - ``m`` (integer) -- dimension of the matrix - - - ``check`` (default: ``True``) -- whether to check `A` and `B` for correctness + - ``m`` -- integer; dimension of the matrix + - ``check`` -- boolean (default: ``True``); whether to check `A` and `B` for + correctness EXAMPLES:: @@ -3152,3 +3409,233 @@ def rshcd_from_prime_power_and_conference_matrix(n): [ e_t_f.T, (e.T).tensor_product(W-II), A_t_W+JJ.tensor_product(II), H34], [-e_t_f.T, (e.T).tensor_product(W+II), H34.T, -A_t_W+JJ.tensor_product(II)]]) return H + + +def are_amicable_hadamard_matrices(M, N, verbose=False): + r""" + Check if ``M`` and ``N`` are amicable Hadamard matrices. + + Two matrices `M` and `N` of order `n` are called amicable if they + satisfy the following conditions (see [Seb2017]_): + + * `M` is a skew Hadamard matrix + * `N` is a symmetric Hadamard matrix + * `MN^T = NM^T` + + INPUT: + + - ``M`` -- a square matrix + - ``N`` -- a square matrix + - ``verbose`` -- boolean (default ``False``); whether to be verbose when the + matrices are not amicable Hadamard matrices + + EXAMPLES:: + + sage: from sage.combinat.matrices.hadamard_matrix import are_amicable_hadamard_matrices + sage: M = matrix([[1, 1], [-1, 1]]) + sage: N = matrix([[1, 1], [1, -1]]) + sage: are_amicable_hadamard_matrices(M, N) + True + + If ``verbose`` is true, the function will be verbose when returning False:: + + sage: N = matrix([[1, 1], [1, 1]]) + sage: are_amicable_hadamard_matrices(M, N, verbose=True) + The second matrix is not Hadamard + False + + TESTS:: + + sage: N = matrix.hadamard(12) + sage: are_amicable_hadamard_matrices(M, N) + False + """ + if not is_skew_hadamard_matrix(M): + if verbose: + print('The first matrix is not skew Hadamard') + return False + + if not is_hadamard_matrix(N): + if verbose: + print('The second matrix is not Hadamard') + return False + + if not N.is_symmetric(): + if verbose: + print('The second matrix is not symmetric') + return False + + if len(M[0]) != len(N[0]): + if verbose: + print('M*N.transpose() is not equal to N*M.transpose()') + return False + + return True + + +def amicable_hadamard_matrices_wallis(n, check=True): + r""" + Construct amicable Hadamard matrices of order `n = q + 1` where `q` is a prime power. + + If `q` is a prime power `\equiv 3 \mod 4`, then amicable Hadamard matrices + of order `q+1` can be constructed as described in [Wal1970b]_. + + INPUT: + + - ``n`` -- integer; the order of the matrices to be constructed + - ``check`` -- boolean (default: ``True``); if ``True``, check that the + resulting matrices are amicable Hadamard before returning them + + OUTPUT: + + The function returns two amicable Hadamard matrices, or raises an error if such + matrices cannot be created using this construction. + + EXAMPLES:: + + sage: from sage.combinat.matrices.hadamard_matrix import amicable_hadamard_matrices_wallis + sage: M, N = amicable_hadamard_matrices_wallis(28) + + TESTS:: + + sage: from sage.combinat.matrices.hadamard_matrix import are_amicable_hadamard_matrices + sage: M, N = amicable_hadamard_matrices_wallis(20, check=False) + sage: are_amicable_hadamard_matrices(M, N) + True + sage: amicable_hadamard_matrices_wallis(18) + Traceback (most recent call last): + ... + ValueError: n must be a positive multiple of 4 + sage: amicable_hadamard_matrices_wallis(16) + Traceback (most recent call last): + ... + ValueError: q = n-1 must be a prime power + """ + if n % 4 != 0 or n < 0: + raise ValueError('n must be a positive multiple of 4') + + q = n - 1 + if not is_prime_power(q): + raise ValueError('q = n-1 must be a prime power') + + from sage.rings.finite_rings.finite_field_constructor import GF + + G = GF(q) + + ls1, ls2 = [], [] + elements_added = set() + for el in G: + if el == 0 or el in elements_added: + continue + elements_added.add(el) + ls1.append(el) + elements_added.add(-el) + ls2 = [-el] + ls2 + Glist = [0] + ls1 + ls2 + + squares = [] + for el in Glist: + squares.append(el*el) + + def chi(el): + if el == 0: + return 0 + if el in squares: + return 1 + return -1 + + S = matrix([[chi(Glist[i] - Glist[j]) for j in range(q)] for i in range(q)]) + R = matrix([[1 if (i, j) == (0, 0) else 1 if j == q-i else 0 for j in range(q)] for i in range(q)]) + + P = S + I(q) + D = R + R*S + + e = matrix([1 for _ in range(q)]) + one = matrix([1]) + + M = block_matrix([[ one, e], + [-e.T, P]]) + N = block_matrix([[-one, -e], + [-e.T, D]]) + + if check: + assert are_amicable_hadamard_matrices(M, N) + return M, N + + +def amicable_hadamard_matrices(n, existence=False, check=True): + r""" + Construct amicable Hadamard matrices of order ``n`` using the available methods. + + INPUT: + + - ``n`` -- positive integer; the order of the amicable Hadamard matrices + - ``existence`` -- boolean (default: ``False``); if ``True``, only return + whether amicable Hadamard matrices of order `n` can be constructed + - ``check`` -- boolean (default: ``True``); if ``True``, check that the + matrices are amicable Hadamard matrices before returning them + + OUTPUT: + + If ``existence`` is true, the function returns a boolean representing whether + amicable Hadamard matrices of order `n` can be constructed. + If ``existence`` is false, returns two amicable Hadamard matrices, or raises + an error if the matrices cannot be constructed. + + EXAMPLES:: + + sage: from sage.combinat.matrices.hadamard_matrix import amicable_hadamard_matrices + sage: amicable_hadamard_matrices(2) + ( + [ 1 1] [ 1 1] + [-1 1], [ 1 -1] + ) + + If ``existence`` is true, the function returns a boolean:: + + sage: amicable_hadamard_matrices(16, existence=True) + False + + TESTS:: + + sage: M, N = amicable_hadamard_matrices(20) + sage: amicable_hadamard_matrices(18) + Traceback (most recent call last): + ... + ValueError: Hadamard matrices of order 18 do not exist + sage: amicable_hadamard_matrices(16) + Traceback (most recent call last): + ... + NotImplementedError: construction for amicable Hadamard matrices of order 16 not yet implemented + """ + if not ((n % 4 == 0 and n > 2) or n in [1, 2]): + if existence: + return False + raise ValueError(f"Hadamard matrices of order {n} do not exist") + + M = None + N = None + if n == 1: + if existence: + return True + M = matrix([1]) + N = matrix([1]) + elif n == 2: + if existence: + return True + M = matrix([[1, 1], [-1, 1]]) + N = matrix([[1, 1], [1, -1]]) + elif is_prime_power(n-1): + if existence: + return True + M, N = amicable_hadamard_matrices_wallis(n, check=False) + + if existence: + return False + + if M is None: + raise NotImplementedError(f'construction for amicable Hadamard matrices of order {n} not yet implemented') + + if check: + assert are_amicable_hadamard_matrices(M, N) + return M, N