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``. Let `G` be a finite Abelian group. For a given subset `D` of `G`, we define `\Delta D` to be the multi-set of differences `\Delta D = \{x - y; x \in D, y \in D, x \not= y\}`. A `(G,k,\lambda)`-*difference family* is a collection of `k`-subsets of `G`, `D = \{D_1, D_2, \ldots, D_b\}` such that the union of the difference sets `\Delta D_i` for `i=1,...b`, seen as a multi-set, contains each element of `G \backslash \{0\}` exactly `\lambda`-times. When there is only one block, i.e. `\lambda(v - 1) = k(k-1)`, then a `(G,k,\lambda)`-difference family is also called a *difference set*. See also :wikipedia:`Difference_set`. If there is no such difference family, an ``EmptySetError`` is raised and if there is no construction at the moment ``NotImplementedError`` is raised. INPUT: - ``v,k,l`` -- parameters of the difference family. If ``l`` is not provided 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.. - ``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 the computation is checked before being returned. This should not be 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. EXAMPLES:: sage: G,D = designs.difference_family(73,4) sage: G Finite Field of size 73 sage: D [[0, 1, 8, 64], [0, 2, 16, 55], [0, 3, 24, 46], [0, 25, 54, 67], [0, 35, 50, 61], [0, 36, 41, 69]] sage: print designs.difference_family(73, 4, explain_construction=True) Radical difference family on a finite field sage: G,D = designs.difference_family(15,7,3) sage: G The cartesian product of (Finite Field of size 3, Finite Field of size 5) sage: D [[(1, 1), (1, 4), (2, 2), (2, 3), (0, 0), (1, 0), (2, 0)]] sage: print designs.difference_family(15,7,3,explain_construction=True) Twin prime powers difference family sage: print designs.difference_family(91,10,1,explain_construction=True) Singer difference set For `k=6,7` we look at the set of small prime powers for which a construction is available:: sage: def prime_power_mod(r,m): ....: k = m+r ....: while True: ....: if is_prime_power(k): ....: yield k ....: k += m sage: from itertools import islice sage: l6 = {True:[], False: [], Unknown: []} sage: for q in islice(prime_power_mod(1,30), 60): ....: l6[designs.difference_family(q,6,existence=True)].append(q) sage: l6[True] [31, 121, 151, 181, 211, ..., 3061, 3121, 3181] sage: l6[Unknown] [61] sage: l6[False] [] sage: l7 = {True: [], False: [], Unknown: []} sage: for q in islice(prime_power_mod(1,42), 60): ....: l7[designs.difference_family(q,7,existence=True)].append(q) sage: l7[True] [337, 421, 463, 883, 1723, 3067, 3319, 3529, 3823, 3907, 4621, 4957, 5167] sage: l7[Unknown] [43, 127, 169, 211, ..., 4999, 5041, 5209] sage: l7[False] [] List available constructions:: sage: for v in xrange(2,100): ....: constructions = [] ....: for k in xrange(2,10): ....: for l in xrange(1,10): ....: if designs.difference_family(v,k,l,existence=True): ....: constructions.append((k,l)) ....: _ = designs.difference_family(v,k,l) ....: if constructions: ....: print "%2d: %s"%(v, ', '.join('(%d,%d)'%(k,l) for k,l in constructions)) 3: (2,1) 4: (3,2) 5: (2,1), (4,3) 6: (5,4) 7: (2,1), (3,1), (3,2), (4,2), (6,5) 8: (7,6) 9: (2,1), (4,3), (8,7) 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) 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) 21: (3,1), (4,3), (5,1), (6,3), (6,5) 22: (4,2), (6,5), (7,4), (8,8) 23: (2,1) 25: (2,1), (3,1), (3,2), (4,1), (4,3), (6,5), (7,7), (8,7) 27: (2,1), (3,1) 28: (3,2), (6,5) 29: (2,1), (4,3), (7,3), (7,6), (8,4), (8,6) 31: (2,1), (3,1), (3,2), (4,2), (5,2), (5,4), (6,1), (6,5) 33: (3,1), (5,5), (6,5) 34: (4,2) 35: (5,2) 37: (2,1), (3,1), (3,2), (4,1), (4,3), (6,5), (9,2), (9,8) 39: (3,1), (6,5) 40: (3,2), (4,1) 41: (2,1), (4,3), (5,1), (5,4), (6,3), (8,7) 43: (2,1), (3,1), (3,2), (4,2), (6,5), (7,2), (7,3), (7,6), (8,4) 45: (3,1), (5,1) 46: (4,2), (6,2) 47: (2,1) 49: (2,1), (3,1), (3,2), (4,1), (4,3), (6,5), (8,7), (9,3) 51: (3,1), (5,2), (6,3) 52: (4,1) 53: (2,1), (4,3) 55: (3,1), (9,4) 57: (3,1), (7,3), (8,1) 59: (2,1) 61: (2,1), (3,1), (3,2), (4,3), (5,1), (5,4), (6,2), (6,3), (6,5) 63: (3,1) 64: (3,2), (4,1), (7,2), (7,6), (9,8) 65: (5,1) 67: (2,1), (3,1), (3,2), (6,5) 69: (3,1) 71: (2,1), (5,2), (5,4), (7,3), (7,6), (8,4) 73: (2,1), (3,1), (3,2), (4,1), (4,3), (6,5), (8,7), (9,1), (9,8) 75: (3,1), (5,2) 76: (4,1) 79: (2,1), (3,1), (3,2), (6,5) 81: (2,1), (3,1), (4,3), (5,1), (5,4), (8,7) 83: (2,1) 85: (4,1), (7,2), (7,3), (8,2) 89: (2,1), (4,3), (8,7) 91: (6,1) 97: (2,1), (3,1), (3,2), (4,1), (4,3), (6,5), (8,7), (9,3) TESTS: Check more of the Wilson constructions from [Wi72]_:: sage: Q5 = [241, 281,421,601,641, 661, 701, 821,881] sage: Q9 = [73, 1153, 1873, 2017] sage: Q15 = [76231] sage: Q4 = [13, 73, 97, 109, 181, 229, 241, 277, 337, 409, 421, 457] sage: Q8 = [1009, 3137, 3697] sage: for Q,k in [(Q4,4),(Q5,5),(Q8,8),(Q9,9),(Q15,15)]: ....: for q in Q: ....: assert designs.difference_family(q,k,1,existence=True) is True ....: _ = designs.difference_family(q,k,1) Check Singer difference sets:: sage: sgp = lambda q,d: ((q**(d+1)-1)//(q-1), (q**d-1)//(q-1), (q**(d-1)-1)//(q-1)) sage: for q in range(2,10): ....: if is_prime_power(q): ....: for d in [2,3,4]: ....: v,k,l = sgp(q,d) ....: assert designs.difference_family(v,k,l,existence=True) is True ....: _ = designs.difference_family(v,k,l) Check twin primes difference sets:: sage: for p in [3,5,7,9,11]: ....: v = p*(p+2); k = (v-1)/2; lmbda = (k-1)/2 ....: G,D = designs.difference_family(v,k,lmbda) Check the database: sage: from sage.combinat.designs.database import DF sage: for v,k,l in DF: ....: df = designs.difference_family(v,k,l,check=True) Check a failing construction (:trac:`17528`): sage: designs.difference_family(9,3) Traceback (most recent call last): ... NotImplementedError: No construction available for (9,3,1)-difference family .. TODO:: Implement recursive constructions from Buratti "Recursive for difference matrices and relative difference families" (1998) and Jungnickel "Composition theorems for difference families and regular planes" (1978) """ from block_design import are_hyperplanes_in_projective_geometry_parameters from database import DF if (v,k,l) in DF: if existence: return True elif explain_construction: return "The database contains a ({},{},{})-difference family".format(v,k,l) vv, blocks = next(DF[v,k,l].iteritems()) # Build the group from sage.rings.finite_rings.integer_mod_ring import Zmod if len(vv) == 1: G = Zmod(vv[0]) else: from sage.categories.cartesian_product import cartesian_product G = cartesian_product([Zmod(i) for i in vv]) df = [[G(i) for i in b] for b in blocks] if check and not is_difference_family(G, df, v=v, k=k, l=l): raise RuntimeError("There is an invalid ({},{},{})-difference " "family in the database... Please contact " "*****@*****.**".format(v,k,l)) return G,df e = k*(k-1) if (l*(v-1)) % e: if existence: return Unknown raise NotImplementedError("No construction available for ({},{},{})-difference family".format(v,k,l)) t = l*(v-1) // e # number of blocks # trivial construction if k == (v-1) and l == (v-2): from sage.rings.finite_rings.integer_mod_ring import Zmod G = Zmod(v) return G, [range(1,v)] factorization = arith.factor(v) D = None if len(factorization) == 1: # i.e. is v a prime power from sage.rings.finite_rings.constructor import GF G = K = GF(v,'z') if radical_difference_family(K, k, l, existence=True): if existence: return True elif explain_construction: return "Radical difference family on a finite field" else: D = radical_difference_family(K,k,l) elif l == 1 and k == 6 and df_q_6_1(K,existence=True): if existence: return True elif explain_construction: return "Wilson 1972 difference family made from the union of two cyclotomic cosets" else: D = df_q_6_1(K) # Twin prime powers construction # i.e. v = p(p+2) where p and p+2 are prime powers # k = (v-1)/2 # lambda = (k-1)/2 (ie 2l+1 = k) elif (k == (v-1)//2 and l == (k-1)//2 and len(factorization) == 2 and abs(pow(*factorization[0]) - pow(*factorization[1])) == 2): if existence: return True elif explain_construction: return "Twin prime powers difference family" else: p = pow(*factorization[0]) q = pow(*factorization[1]) if p > q: p,q = q,p G,D = twin_prime_powers_difference_set(p,check=False) if D is None and are_hyperplanes_in_projective_geometry_parameters(v,k,l): _, (q,d) = are_hyperplanes_in_projective_geometry_parameters(v,k,l,True) if existence: return True elif explain_construction: return "Singer difference set" else: G,D = singer_difference_set(q,d) if D is None: if existence: return Unknown raise NotImplementedError("No constructions for these parameters") if check and not is_difference_family(G,D,v=v,k=k,l=l,verbose=False): raise RuntimeError("There is a problem. Sage built the following " "difference family on G='{}' with parameters ({},{},{}):\n " "{}\nwhich seems to not be a difference family... " "Please contact [email protected]".format(G,v,k,l,D)) return G, D
def difference_family(v, k, l=1, existence=False, check=True): r""" Return a (``k``, ``l``)-difference family on an Abelian group of size ``v``. Let `G` be a finite Abelian group. For a given subset `D` of `G`, we define `\Delta D` to be the multi-set of differences `\Delta D = \{x - y; x \in D, y \in D, x \not= y\}`. A `(G,k,\lambda)`-*difference family* is a collection of `k`-subsets of `G`, `D = \{D_1, D_2, \ldots, D_b\}` such that the union of the difference sets `\Delta D_i` for `i=1,...b`, seen as a multi-set, contains each element of `G \backslash \{0\}` exactly `\lambda`-times. When there is only one block, i.e. `\lambda(v - 1) = k(k-1)`, then a `(G,k,\lambda)`-difference family is also called a *difference set*. See also :wikipedia:`Difference_set`. If there is no such difference family, an ``EmptySetError`` is raised and if there is no construction at the moment ``NotImplementedError`` is raised. EXAMPLES:: sage: K,D = designs.difference_family(73,4) sage: D [[0, 1, 8, 64], [0, 25, 54, 67], [0, 41, 36, 69], [0, 3, 24, 46], [0, 2, 16, 55], [0, 50, 35, 61]] sage: K,D = designs.difference_family(337,7) sage: D [[1, 175, 295, 64, 79, 8, 52], [326, 97, 125, 307, 142, 249, 102], [121, 281, 310, 330, 123, 294, 226], [17, 279, 297, 77, 332, 136, 210], [150, 301, 103, 164, 55, 189, 49], [35, 59, 215, 218, 69, 280, 135], [289, 25, 331, 298, 252, 290, 200], [191, 62, 66, 92, 261, 180, 159]] For `k=6,7` we look at the set of small prime powers for which a construction is available:: sage: def prime_power_mod(r,m): ....: k = m+r ....: while True: ....: if is_prime_power(k): ....: yield k ....: k += m sage: from itertools import islice sage: l6 = {True:[], False: [], Unknown: []} sage: for q in islice(prime_power_mod(1,30), 60): ....: l6[designs.difference_family(q,6,existence=True)].append(q) sage: l6[True] [31, 151, 181, 211, ..., 3061, 3121, 3181] sage: l6[Unknown] [61, 121] sage: l6[False] [] sage: l7 = {True: [], False: [], Unknown: []} sage: for q in islice(prime_power_mod(1,42), 60): ....: l7[designs.difference_family(q,7,existence=True)].append(q) sage: l7[True] [337, 421, 463, 883, 1723, 3067, 3319, 3529, 3823, 3907, 4621, 4957, 5167] sage: l7[Unknown] [43, 127, 169, 211, ..., 4999, 5041, 5209] sage: l7[False] [] Other constructions for `\lambda > 1`:: sage: for v in xrange(2,100): ....: constructions = [] ....: for k in xrange(2,10): ....: for l in xrange(2,10): ....: if designs.difference_family(v,k,l,existence=True): ....: constructions.append((k,l)) ....: _ = designs.difference_family(v,k,l) ....: if constructions: ....: print "%2d: %s"%(v, ', '.join('(%d,%d)'%(k,l) for k,l in constructions)) 4: (3,2) 5: (4,3) 7: (3,2), (6,5) 8: (7,6) 9: (4,3), (8,7) 11: (5,2), (5,4) 13: (3,2), (4,3), (6,5) 15: (7,3) 16: (3,2), (5,4) 17: (4,3), (8,7) 19: (3,2), (6,5), (9,4), (9,8) 25: (3,2), (4,3), (6,5), (8,7) 29: (4,3), (7,6) 31: (3,2), (5,4), (6,5) 37: (3,2), (4,3), (6,5), (9,2), (9,8) 41: (4,3), (5,4), (8,7) 43: (3,2), (6,5), (7,6) 49: (3,2), (4,3), (6,5), (8,7) 53: (4,3) 61: (3,2), (4,3), (5,4), (6,5) 64: (3,2), (7,6), (9,8) 67: (3,2), (6,5) 71: (5,4), (7,6) 73: (3,2), (4,3), (6,5), (8,7), (9,8) 79: (3,2), (6,5) 81: (4,3), (5,4), (8,7) 89: (4,3), (8,7) 97: (3,2), (4,3), (6,5), (8,7) TESTS: Check more of the Wilson constructions from [Wi72]_:: sage: Q5 = [241, 281,421,601,641, 661, 701, 821,881] sage: Q9 = [73, 1153, 1873, 2017] sage: Q15 = [76231] sage: Q4 = [13, 73, 97, 109, 181, 229, 241, 277, 337, 409, 421, 457] sage: Q8 = [1009, 3137, 3697] sage: for Q,k in [(Q4,4),(Q5,5),(Q8,8),(Q9,9),(Q15,15)]: ....: for q in Q: ....: assert designs.difference_family(q,k,1,existence=True) is True ....: _ = designs.difference_family(q,k,1) Check Singer difference sets:: sage: sgp = lambda q,d: ((q**(d+1)-1)//(q-1), (q**d-1)//(q-1), (q**(d-1)-1)//(q-1)) sage: for q in range(2,10): ....: if is_prime_power(q): ....: for d in [2,3,4]: ....: v,k,l = sgp(q,d) ....: assert designs.difference_family(v,k,l,existence=True) is True ....: _ = designs.difference_family(v,k,l) .. TODO:: There is a slightly more general version of difference families where the stabilizers of the blocks are taken into account. A block is *short* if the stabilizer is not trivial. The more general version is called a *partial difference family*. It is still possible to construct BIBD from this more general version (see the chapter 16 in the Handbook [DesignHandbook]_). Implement recursive constructions from Buratti "Recursive for difference matrices and relative difference families" (1998) and Jungnickel "Composition theorems for difference families and regular planes" (1978) """ if (l * (v - 1)) % (k * (k - 1)) != 0: if existence: return False raise EmptySetError( "A (v,%d,%d)-difference family may exist only if %d*(v-1) = mod %d" % (k, l, l, k * (k - 1))) from block_design import are_hyperplanes_in_projective_geometry_parameters from database import DF_constructions if (v, k, l) in DF_constructions: if existence: return True return DF_constructions[(v, k, l)]() e = k * (k - 1) t = l * (v - 1) // e # number of blocks D = None if arith.is_prime_power(v): from sage.rings.finite_rings.constructor import GF G = K = GF(v, 'z') x = K.multiplicative_generator() if l == (k - 1): if existence: return True return K, K.cyclotomic_cosets(x**((v - 1) // k))[1:] if t == 1: # some of the difference set constructions VI.18.48 from the # Handbook of combinatorial designs # q = 3 mod 4 if v % 4 == 3 and k == (v - 1) // 2: if existence: return True D = K.cyclotomic_cosets(x**2, [1]) # q = 4t^2 + 1, t odd elif v % 8 == 5 and k == (v - 1) // 4 and arith.is_square( (v - 1) // 4): if existence: return True D = K.cyclotomic_cosets(x**4, [1]) # q = 4t^2 + 9, t odd elif v % 8 == 5 and k == (v + 3) // 4 and arith.is_square( (v - 9) // 4): if existence: return True D = K.cyclotomic_cosets(x**4, [1]) D[0].insert(0, K.zero()) if D is None and l == 1: one = K.one() # Wilson (1972), Theorem 9 if k % 2 == 1: m = (k - 1) // 2 xx = x**m to_coset = { x**i * xx**j: i for i in xrange(m) for j in xrange((v - 1) / m) } r = x**((v - 1) // k) # primitive k-th root of unity if len(set(to_coset[r**j - one] for j in xrange(1, m + 1))) == m: if existence: return True B = [r**j for j in xrange(k) ] # = H^((k-1)t) whose difference is # H^(mt) (r^i - 1, i=1,..,m) # Now pick representatives a translate of R for by a set of # representatives of H^m / H^(mt) D = [[x**(i * m) * b for b in B] for i in xrange(t)] # Wilson (1972), Theorem 10 else: m = k // 2 xx = x**m to_coset = { x**i * xx**j: i for i in xrange(m) for j in xrange((v - 1) / m) } r = x**((v - 1) // (k - 1)) # primitive (k-1)-th root of unity if (all(to_coset[r**j - one] != 0 for j in xrange(1, m)) and len(set(to_coset[r**j - one] for j in xrange(1, m))) == m - 1): if existence: return True B = [K.zero()] + [r**j for j in xrange(k - 1)] D = [[x**(i * m) * b for b in B] for i in xrange(t)] # Wilson (1972), Theorem 11 if D is None and k == 6: r = x**((v - 1) // 3) # primitive cube root of unity r2 = r * r xx = x**5 to_coset = { x**i * xx**j: i for i in xrange(5) for j in xrange((v - 1) / 5) } for c in to_coset: if c == 1 or c == r or c == r2: continue if len( set(to_coset[elt] for elt in (r - 1, c * (r - 1), c - 1, c - r, c - r**2))) == 5: if existence: return True B = [one, r, r**2, c, c * r, c * r**2] D = [[x**(i * 5) * b for b in B] for i in xrange(t)] break if D is None and are_hyperplanes_in_projective_geometry_parameters( v, k, l): _, (q, d) = are_hyperplanes_in_projective_geometry_parameters( v, k, l, True) if existence: return True else: G, D = singer_difference_set(q, d) if D is None: if existence: return Unknown raise NotImplementedError("No constructions for these parameters") if check and not is_difference_family(G, D, verbose=False): raise RuntimeError return G, D
def difference_family(v, k, l=1, existence=False, check=True): r""" Return a (``k``, ``l``)-difference family on an Abelian group of cardinality ``v``. Let `G` be a finite Abelian group. For a given subset `D` of `G`, we define `\Delta D` to be the multi-set of differences `\Delta D = \{x - y; x \in D, y \in D, x \not= y\}`. A `(G,k,\lambda)`-*difference family* is a collection of `k`-subsets of `G`, `D = \{D_1, D_2, \ldots, D_b\}` such that the union of the difference sets `\Delta D_i` for `i=1,...b`, seen as a multi-set, contains each element of `G \backslash \{0\}` exactly `\lambda`-times. When there is only one block, i.e. `\lambda(v - 1) = k(k-1)`, then a `(G,k,\lambda)`-difference family is also called a *difference set*. See also :wikipedia:`Difference_set`. If there is no such difference family, an ``EmptySetError`` is raised and if there is no construction at the moment ``NotImplementedError`` is raised. EXAMPLES:: sage: K,D = designs.difference_family(73,4) sage: D [[0, 1, 8, 64], [0, 25, 54, 67], [0, 41, 36, 69], [0, 3, 24, 46], [0, 2, 16, 55], [0, 50, 35, 61]] sage: K,D = designs.difference_family(337,7) sage: D [[1, 175, 295, 64, 79, 8, 52], [326, 97, 125, 307, 142, 249, 102], [121, 281, 310, 330, 123, 294, 226], [17, 279, 297, 77, 332, 136, 210], [150, 301, 103, 164, 55, 189, 49], [35, 59, 215, 218, 69, 280, 135], [289, 25, 331, 298, 252, 290, 200], [191, 62, 66, 92, 261, 180, 159]] For `k=6,7` we look at the set of small prime powers for which a construction is available:: sage: def prime_power_mod(r,m): ....: k = m+r ....: while True: ....: if is_prime_power(k): ....: yield k ....: k += m sage: from itertools import islice sage: l6 = {True:[], False: [], Unknown: []} sage: for q in islice(prime_power_mod(1,30), 60): ....: l6[designs.difference_family(q,6,existence=True)].append(q) sage: l6[True] [31, 121, 151, 181, 211, ..., 3061, 3121, 3181] sage: l6[Unknown] [61] sage: l6[False] [] sage: l7 = {True: [], False: [], Unknown: []} sage: for q in islice(prime_power_mod(1,42), 60): ....: l7[designs.difference_family(q,7,existence=True)].append(q) sage: l7[True] [337, 421, 463, 883, 1723, 3067, 3319, 3529, 3823, 3907, 4621, 4957, 5167] sage: l7[Unknown] [43, 127, 169, 211, ..., 4999, 5041, 5209] sage: l7[False] [] Other constructions for `\lambda > 1`:: sage: for v in xrange(2,100): ....: constructions = [] ....: for k in xrange(2,10): ....: for l in xrange(2,10): ....: if designs.difference_family(v,k,l,existence=True): ....: constructions.append((k,l)) ....: _ = designs.difference_family(v,k,l) ....: if constructions: ....: print "%2d: %s"%(v, ', '.join('(%d,%d)'%(k,l) for k,l in constructions)) 2: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 3: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 4: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 5: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 7: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 8: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 9: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 11: (3,2), (4,3), (4,6), (5,2), (5,3), (5,4), (6,5), (7,6), (8,7), (9,8) 13: (3,2), (4,3), (5,4), (5,5), (6,5), (7,6), (8,7), (9,8) 15: (4,6), (5,6), (7,3) 16: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 17: (3,2), (4,3), (5,4), (5,5), (6,5), (7,6), (8,7), (9,8) 19: (3,2), (4,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,4), (9,5), (9,6), (9,7), (9,8) 21: (4,3), (6,3), (6,5) 22: (4,2), (6,5), (7,4), (8,8) 23: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 25: (3,2), (4,3), (5,4), (6,5), (7,6), (7,7), (8,7), (9,8) 27: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 28: (3,2), (6,5) 29: (3,2), (4,3), (5,4), (6,5), (7,3), (7,6), (8,4), (8,6), (8,7), (9,8) 31: (3,2), (4,2), (4,3), (5,2), (5,4), (6,5), (7,6), (8,7), (9,8) 32: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 33: (5,5), (6,5) 34: (4,2) 35: (5,2), (8,4) 37: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,2), (9,3), (9,8) 39: (6,5) 40: (3,2) 41: (3,2), (4,3), (5,4), (6,3), (6,5), (7,6), (8,7), (9,8) 43: (3,2), (4,2), (4,3), (5,4), (6,5), (7,2), (7,3), (7,6), (8,4), (8,7), (9,8) 46: (4,2), (6,2) 47: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 49: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,3), (9,8) 51: (5,2), (6,3) 53: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 55: (9,4) 57: (7,3) 59: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 61: (3,2), (4,3), (5,4), (6,2), (6,3), (6,5), (7,6), (8,7), (9,8) 64: (3,2), (4,3), (5,4), (6,5), (7,2), (7,6), (8,7), (9,8) 67: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 71: (3,2), (4,3), (5,2), (5,4), (6,5), (7,3), (7,6), (8,4), (8,7), (9,8) 73: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 75: (5,2) 79: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 81: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 83: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 85: (7,2), (7,3), (8,2) 89: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,8) 97: (3,2), (4,3), (5,4), (6,5), (7,6), (8,7), (9,3), (9,8) TESTS: Check more of the Wilson constructions from [Wi72]_:: sage: Q5 = [241, 281,421,601,641, 661, 701, 821,881] sage: Q9 = [73, 1153, 1873, 2017] sage: Q15 = [76231] sage: Q4 = [13, 73, 97, 109, 181, 229, 241, 277, 337, 409, 421, 457] sage: Q8 = [1009, 3137, 3697] sage: for Q,k in [(Q4,4),(Q5,5),(Q8,8),(Q9,9),(Q15,15)]: ....: for q in Q: ....: assert designs.difference_family(q,k,1,existence=True) is True ....: _ = designs.difference_family(q,k,1) Check Singer difference sets:: sage: sgp = lambda q,d: ((q**(d+1)-1)//(q-1), (q**d-1)//(q-1), (q**(d-1)-1)//(q-1)) sage: for q in range(2,10): ....: if is_prime_power(q): ....: for d in [2,3,4]: ....: v,k,l = sgp(q,d) ....: assert designs.difference_family(v,k,l,existence=True) is True ....: _ = designs.difference_family(v,k,l) Check twin primes difference sets:: sage: for p in [3,5,7,9,11]: ....: v = p*(p+2); k = (v-1)/2; lmbda = (k-1)/2 ....: G,D = designs.difference_family(v,k,lmbda) Check the database: sage: from sage.combinat.designs.database import DF sage: for v,k,l in DF: ....: df = designs.difference_family(v,k,l,check=True) .. TODO:: Implement recursive constructions from Buratti "Recursive for difference matrices and relative difference families" (1998) and Jungnickel "Composition theorems for difference families and regular planes" (1978) """ from block_design import are_hyperplanes_in_projective_geometry_parameters from database import DF if (v, k, l) in DF: if existence: return True vv, blocks = DF[v, k, l].iteritems().next() # Build the group from sage.rings.finite_rings.integer_mod_ring import Zmod if len(vv) == 1: G = Zmod(vv[0]) else: from sage.categories.cartesian_product import cartesian_product G = cartesian_product([Zmod(i) for i in vv]) df = [[G(i) for i in b] for b in blocks] if check: assert is_difference_family( G, df, v=v, k=k, l=l), "Sage built an invalid ({},{},{})-DF!".format(v, k, l) return G, df e = k * (k - 1) t = l * (v - 1) // e # number of blocks D = None factorization = arith.factor(v) if len(factorization) == 1: # i.e. is v a prime power from sage.rings.finite_rings.constructor import GF G = K = GF(v, 'z') x = K.multiplicative_generator() if l == (k - 1): if existence: return True return K, K.cyclotomic_cosets(x**((v - 1) // k))[1:] if t == 1: # some of the difference set constructions VI.18.48 from the # Handbook of combinatorial designs # q = 3 mod 4 if v % 4 == 3 and k == (v - 1) // 2: if existence: return True D = K.cyclotomic_cosets(x**2, [1]) # q = 4t^2 + 1, t odd elif v % 8 == 5 and k == (v - 1) // 4 and arith.is_square( (v - 1) // 4): if existence: return True D = K.cyclotomic_cosets(x**4, [1]) # q = 4t^2 + 9, t odd elif v % 8 == 5 and k == (v + 3) // 4 and arith.is_square( (v - 9) // 4): if existence: return True D = K.cyclotomic_cosets(x**4, [1]) D[0].insert(0, K.zero()) if D is None and l == 1: one = K.one() # Wilson (1972), Theorem 9 if k % 2 == 1: m = (k - 1) // 2 xx = x**m to_coset = { x**i * xx**j: i for i in xrange(m) for j in xrange((v - 1) / m) } r = x**((v - 1) // k) # primitive k-th root of unity if len(set(to_coset[r**j - one] for j in xrange(1, m + 1))) == m: if existence: return True B = [r**j for j in xrange(k) ] # = H^((k-1)t) whose difference is # H^(mt) (r^i - 1, i=1,..,m) # Now pick representatives a translate of R for by a set of # representatives of H^m / H^(mt) D = [[x**(i * m) * b for b in B] for i in xrange(t)] # Wilson (1972), Theorem 10 else: m = k // 2 xx = x**m to_coset = { x**i * xx**j: i for i in xrange(m) for j in xrange((v - 1) / m) } r = x**((v - 1) // (k - 1)) # primitive (k-1)-th root of unity if (all(to_coset[r**j - one] != 0 for j in xrange(1, m)) and len(set(to_coset[r**j - one] for j in xrange(1, m))) == m - 1): if existence: return True B = [K.zero()] + [r**j for j in xrange(k - 1)] D = [[x**(i * m) * b for b in B] for i in xrange(t)] # Wilson (1972), Theorem 11 if D is None and k == 6: r = x**((v - 1) // 3) # primitive cube root of unity r2 = r * r xx = x**5 to_coset = { x**i * xx**j: i for i in xrange(5) for j in xrange((v - 1) / 5) } for c in to_coset: if c == 1 or c == r or c == r2: continue if len( set(to_coset[elt] for elt in (r - 1, c * (r - 1), c - 1, c - r, c - r**2))) == 5: if existence: return True B = [one, r, r**2, c, c * r, c * r**2] D = [[x**(i * 5) * b for b in B] for i in xrange(t)] break # Twin prime powers construction (see :wikipedia:`Difference_set`) # # i.e. v = p(p+2) where p and p+2 are prime powers # k = (v-1)/2 # lambda = (k-1)/2 elif (len(factorization) == 2 and abs(pow(*factorization[0]) - pow(*factorization[1])) == 2 and k == (v - 1) // 2 and (l is None or 2 * l == (v - 1) // 2 - 1)): # A difference set can be built from the set of elements # (x,y) in GF(p) x GF(p+2) such that: # # - either y=0 # - x and y with x and y squares # - x and y with x and y non-squares if existence: return True from sage.rings.finite_rings.constructor import FiniteField from sage.categories.cartesian_product import cartesian_product from itertools import product p, q = pow(*factorization[0]), pow(*factorization[1]) if p > q: p, q = q, p Fp = FiniteField(p, 'x') Fq = FiniteField(q, 'x') Fpset = set(Fp) Fqset = set(Fq) Fp_squares = set(x**2 for x in Fpset) Fq_squares = set(x**2 for x in Fqset) # Pairs of squares, pairs of non-squares d = [] d.extend( product(Fp_squares.difference([0]), Fq_squares.difference([0]))) d.extend( product(Fpset.difference(Fp_squares), Fqset.difference(Fq_squares))) # All (x,0) d.extend((x, 0) for x in Fpset) G = cartesian_product([Fp, Fq]) D = [d] if D is None and are_hyperplanes_in_projective_geometry_parameters( v, k, l): _, (q, d) = are_hyperplanes_in_projective_geometry_parameters( v, k, l, True) if existence: return True else: G, D = singer_difference_set(q, d) if D is None: if existence: return Unknown raise NotImplementedError("No constructions for these parameters") if check and not is_difference_family(G, D, verbose=False): raise RuntimeError return G, D
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``. Let `G` be a finite Abelian group. For a given subset `D` of `G`, we define `\Delta D` to be the multi-set of differences `\Delta D = \{x - y; x \in D, y \in D, x \not= y\}`. A `(G,k,\lambda)`-*difference family* is a collection of `k`-subsets of `G`, `D = \{D_1, D_2, \ldots, D_b\}` such that the union of the difference sets `\Delta D_i` for `i=1,...b`, seen as a multi-set, contains each element of `G \backslash \{0\}` exactly `\lambda`-times. When there is only one block, i.e. `\lambda(v - 1) = k(k-1)`, then a `(G,k,\lambda)`-difference family is also called a *difference set*. See also :wikipedia:`Difference_set`. If there is no such difference family, an ``EmptySetError`` is raised and if there is no construction at the moment ``NotImplementedError`` is raised. INPUT: - ``v,k,l`` -- parameters of the difference family. If ``l`` is not provided 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. - ``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 the computation is checked before being returned. This should not be 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. EXAMPLES:: sage: G,D = designs.difference_family(73,4) sage: G Finite Field of size 73 sage: D [[0, 1, 5, 18], [0, 3, 15, 54], [0, 9, 45, 16], [0, 27, 62, 48], [0, 8, 40, 71], [0, 24, 47, 67]] sage: print designs.difference_family(73, 4, explain_construction=True) The database contains a (73,4)-evenly distributed set sage: G,D = designs.difference_family(15,7,3) sage: G The cartesian product of (Finite Field of size 3, Finite Field of size 5) sage: D [[(1, 1), (1, 4), (2, 2), (2, 3), (0, 0), (1, 0), (2, 0)]] sage: print designs.difference_family(15,7,3,explain_construction=True) Twin prime powers difference family sage: print designs.difference_family(91,10,1,explain_construction=True) Singer difference set For `k=6,7` we look at the set of small prime powers for which a construction is available:: sage: def prime_power_mod(r,m): ....: k = m+r ....: while True: ....: if is_prime_power(k): ....: yield k ....: k += m sage: from itertools import islice sage: l6 = {True:[], False: [], Unknown: []} sage: for q in islice(prime_power_mod(1,30), 60): ....: l6[designs.difference_family(q,6,existence=True)].append(q) sage: l6[True] [31, 121, 151, 181, 211, ..., 3061, 3121, 3181] sage: l6[Unknown] [61] sage: l6[False] [] sage: l7 = {True: [], False: [], Unknown: []} sage: for q in islice(prime_power_mod(1,42), 60): ....: l7[designs.difference_family(q,7,existence=True)].append(q) sage: l7[True] [169, 337, 379, 421, 463, 547, 631, 673, 757, 841, 883, 967, ..., 4621, 4957, 5167] sage: l7[Unknown] [43, 127, 211, 2017, 2143, 2269, 2311, 2437, 2521, 2647, ..., 4999, 5041, 5209] sage: l7[False] [] List available constructions:: sage: for v in xrange(2,100): ....: constructions = [] ....: for k in xrange(2,10): ....: for l in xrange(1,10): ....: if designs.difference_family(v,k,l,existence=True): ....: constructions.append((k,l)) ....: _ = designs.difference_family(v,k,l) ....: if constructions: ....: print "%2d: %s"%(v, ', '.join('(%d,%d)'%(k,l) for k,l in constructions)) 3: (2,1) 4: (3,2) 5: (2,1), (4,3) 6: (5,4) 7: (2,1), (3,1), (3,2), (4,2), (6,5) 8: (7,6) 9: (2,1), (4,3), (8,7) 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) 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) 21: (3,1), (4,3), (5,1), (6,3), (6,5) 22: (4,2), (6,5), (7,4), (8,8) 23: (2,1) 25: (2,1), (3,1), (3,2), (4,1), (4,3), (6,5), (7,7), (8,7) 27: (2,1), (3,1) 28: (3,2), (6,5) 29: (2,1), (4,3), (7,3), (7,6), (8,4), (8,6) 31: (2,1), (3,1), (3,2), (4,2), (5,2), (5,4), (6,1), (6,5) 33: (3,1), (5,5), (6,5) 34: (4,2) 35: (5,2) 37: (2,1), (3,1), (3,2), (4,1), (4,3), (6,5), (9,2), (9,8) 39: (3,1), (6,5) 40: (3,2), (4,1) 41: (2,1), (4,3), (5,1), (5,4), (6,3), (8,7) 43: (2,1), (3,1), (3,2), (4,2), (6,5), (7,2), (7,3), (7,6), (8,4) 45: (3,1), (5,1) 46: (4,2), (6,2) 47: (2,1) 49: (2,1), (3,1), (3,2), (4,1), (4,3), (6,5), (8,7), (9,3) 51: (3,1), (5,2), (6,3) 52: (4,1) 53: (2,1), (4,3) 55: (3,1), (9,4) 57: (3,1), (7,3), (8,1) 59: (2,1) 61: (2,1), (3,1), (3,2), (4,1), (4,3), (5,1), (5,4), (6,2), (6,3), (6,5) 63: (3,1) 64: (3,2), (4,1), (7,2), (7,6), (9,8) 65: (5,1) 67: (2,1), (3,1), (3,2), (6,5) 69: (3,1) 71: (2,1), (5,2), (5,4), (7,3), (7,6), (8,4) 73: (2,1), (3,1), (3,2), (4,1), (4,3), (6,5), (8,7), (9,1), (9,8) 75: (3,1), (5,2) 76: (4,1) 79: (2,1), (3,1), (3,2), (6,5) 81: (2,1), (3,1), (4,3), (5,1), (5,4), (8,7) 83: (2,1) 85: (4,1), (7,2), (7,3), (8,2) 89: (2,1), (4,3), (8,7) 91: (6,1), (7,1) 97: (2,1), (3,1), (3,2), (4,1), (4,3), (6,5), (8,7), (9,3) TESTS: Check more of the Wilson constructions from [Wi72]_:: sage: Q5 = [241, 281,421,601,641, 661, 701, 821,881] sage: Q9 = [73, 1153, 1873, 2017] sage: Q15 = [76231] sage: Q4 = [13, 73, 97, 109, 181, 229, 241, 277, 337, 409, 421, 457] sage: Q8 = [1009, 3137, 3697] sage: for Q,k in [(Q4,4),(Q5,5),(Q8,8),(Q9,9),(Q15,15)]: ....: for q in Q: ....: assert designs.difference_family(q,k,1,existence=True) is True ....: _ = designs.difference_family(q,k,1) Check Singer difference sets:: sage: sgp = lambda q,d: ((q**(d+1)-1)//(q-1), (q**d-1)//(q-1), (q**(d-1)-1)//(q-1)) sage: for q in range(2,10): ....: if is_prime_power(q): ....: for d in [2,3,4]: ....: v,k,l = sgp(q,d) ....: assert designs.difference_family(v,k,l,existence=True) is True ....: _ = designs.difference_family(v,k,l) Check twin primes difference sets:: sage: for p in [3,5,7,9,11]: ....: v = p*(p+2); k = (v-1)/2; lmbda = (k-1)/2 ....: G,D = designs.difference_family(v,k,lmbda) Check the database:: sage: from sage.combinat.designs.database import DF,EDS sage: for v,k,l in DF: ....: assert designs.difference_family(v,k,l,existence=True) is True ....: df = designs.difference_family(v,k,l,check=True) sage: for k in EDS: ....: for v in EDS[k]: ....: assert designs.difference_family(v,k,1,existence=True) is True ....: df = designs.difference_family(v,k,1,check=True) Check a failing construction (:trac:`17528`):: sage: designs.difference_family(9,3) Traceback (most recent call last): ... NotImplementedError: No construction available for (9,3,1)-difference family .. TODO:: Implement recursive constructions from Buratti "Recursive for difference matrices and relative difference families" (1998) and Jungnickel "Composition theorems for difference families and regular planes" (1978) """ from block_design import are_hyperplanes_in_projective_geometry_parameters from database import DF, EDS if (v,k,l) in DF: if existence: return True elif explain_construction: return "The database contains a ({},{},{})-difference family".format(v,k,l) vv, blocks = next(DF[v,k,l].iteritems()) # Build the group from sage.rings.finite_rings.integer_mod_ring import Zmod if len(vv) == 1: G = Zmod(vv[0]) else: from sage.categories.cartesian_product import cartesian_product G = cartesian_product([Zmod(i) for i in vv]) df = [[G(i) for i in b] for b in blocks] if check and not is_difference_family(G, df, v=v, k=k, l=l): raise RuntimeError("There is an invalid ({},{},{})-difference " "family in the database... Please contact " "*****@*****.**".format(v,k,l)) return G,df elif l == 1 and k in EDS and v in EDS[k]: if existence: return True elif explain_construction: return "The database contains a ({},{})-evenly distributed set".format(v,k) from sage.rings.finite_rings.constructor import GF poly,B = EDS[k][v] if poly is None: # q is prime K = G = GF(v) else: K = G = GF(v,'a',modulus=poly) B = map(K,B) e = k*(k-1)/2 xe = G.multiplicative_generator()**e df = [[xe**j*b for b in B] for j in range((v-1)/(2*e))] if check and not is_difference_family(G, df, v=v, k=k, l=l): raise RuntimeError("There is an invalid ({},{})-evenly distributed " "set in the database... Please contact " "*****@*****.**".format(v,k,l)) return G,df e = k*(k-1) if (l*(v-1)) % e: if existence: return Unknown raise NotImplementedError("No construction available for ({},{},{})-difference family".format(v,k,l)) t = l*(v-1) // e # number of blocks # trivial construction if k == (v-1) and l == (v-2): from sage.rings.finite_rings.integer_mod_ring import Zmod G = Zmod(v) return G, [range(1,v)] factorization = arith.factor(v) D = None if len(factorization) == 1: # i.e. is v a prime power from sage.rings.finite_rings.constructor import GF G = K = GF(v,'z') if radical_difference_family(K, k, l, existence=True): if existence: return True elif explain_construction: return "Radical difference family on a finite field" else: D = radical_difference_family(K,k,l) elif l == 1 and k == 6 and df_q_6_1(K,existence=True): if existence: return True elif explain_construction: return "Wilson 1972 difference family made from the union of two cyclotomic cosets" else: D = df_q_6_1(K) # Twin prime powers construction # i.e. v = p(p+2) where p and p+2 are prime powers # k = (v-1)/2 # lambda = (k-1)/2 (ie 2l+1 = k) elif (k == (v-1)//2 and l == (k-1)//2 and len(factorization) == 2 and abs(pow(*factorization[0]) - pow(*factorization[1])) == 2): if existence: return True elif explain_construction: return "Twin prime powers difference family" else: p = pow(*factorization[0]) q = pow(*factorization[1]) if p > q: p,q = q,p G,D = twin_prime_powers_difference_set(p,check=False) if D is None and are_hyperplanes_in_projective_geometry_parameters(v,k,l): _, (q,d) = are_hyperplanes_in_projective_geometry_parameters(v,k,l,True) if existence: return True elif explain_construction: return "Singer difference set" else: G,D = singer_difference_set(q,d) if D is None: if existence: return Unknown raise NotImplementedError("No constructions for these parameters") if check and not is_difference_family(G,D,v=v,k=k,l=l,verbose=False): raise RuntimeError("There is a problem. Sage built the following " "difference family on G='{}' with parameters ({},{},{}):\n " "{}\nwhich seems to not be a difference family... " "Please contact [email protected]".format(G,v,k,l,D)) return G, D
def difference_family(v, k, l=1, existence=False, check=True): r""" Return a (``k``, ``l``)-difference family on an Abelian group of size ``v``. Let `G` be a finite Abelian group. For a given subset `D` of `G`, we define `\Delta D` to be the multi-set of differences `\Delta D = \{x - y; x \in D, y \in D, x \not= y\}`. A `(G,k,\lambda)`-*difference family* is a collection of `k`-subsets of `G`, `D = \{D_1, D_2, \ldots, D_b\}` such that the union of the difference sets `\Delta D_i` for `i=1,...b`, seen as a multi-set, contains each element of `G \backslash \{0\}` exactly `\lambda`-times. When there is only one block, i.e. `\lambda(v - 1) = k(k-1)`, then a `(G,k,\lambda)`-difference family is also called a *difference set*. See also :wikipedia:`Difference_set`. If there is no such difference family, an ``EmptySetError`` is raised and if there is no construction at the moment ``NotImplementedError`` is raised. EXAMPLES:: sage: K,D = designs.difference_family(73,4) sage: D [[0, 1, 8, 64], [0, 25, 54, 67], [0, 41, 36, 69], [0, 3, 24, 46], [0, 2, 16, 55], [0, 50, 35, 61]] sage: K,D = designs.difference_family(337,7) sage: D [[1, 175, 295, 64, 79, 8, 52], [326, 97, 125, 307, 142, 249, 102], [121, 281, 310, 330, 123, 294, 226], [17, 279, 297, 77, 332, 136, 210], [150, 301, 103, 164, 55, 189, 49], [35, 59, 215, 218, 69, 280, 135], [289, 25, 331, 298, 252, 290, 200], [191, 62, 66, 92, 261, 180, 159]] For `k=6,7` we look at the set of small prime powers for which a construction is available:: sage: def prime_power_mod(r,m): ....: k = m+r ....: while True: ....: if is_prime_power(k): ....: yield k ....: k += m sage: from itertools import islice sage: l6 = {True:[], False: [], Unknown: []} sage: for q in islice(prime_power_mod(1,30), 60): ....: l6[designs.difference_family(q,6,existence=True)].append(q) sage: l6[True] [31, 151, 181, 211, ..., 3061, 3121, 3181] sage: l6[Unknown] [61, 121] sage: l6[False] [] sage: l7 = {True: [], False: [], Unknown: []} sage: for q in islice(prime_power_mod(1,42), 60): ....: l7[designs.difference_family(q,7,existence=True)].append(q) sage: l7[True] [337, 421, 463, 883, 1723, 3067, 3319, 3529, 3823, 3907, 4621, 4957, 5167] sage: l7[Unknown] [43, 127, 169, 211, ..., 4999, 5041, 5209] sage: l7[False] [] Other constructions for `\lambda > 1`:: sage: for v in xrange(2,100): ....: constructions = [] ....: for k in xrange(2,10): ....: for l in xrange(2,10): ....: if designs.difference_family(v,k,l,existence=True): ....: constructions.append((k,l)) ....: _ = designs.difference_family(v,k,l) ....: if constructions: ....: print "%2d: %s"%(v, ', '.join('(%d,%d)'%(k,l) for k,l in constructions)) 4: (3,2) 5: (4,3) 7: (3,2), (6,5) 8: (7,6) 9: (4,3), (8,7) 11: (5,2), (5,4) 13: (3,2), (4,3), (6,5) 15: (7,3) 16: (3,2), (5,4) 17: (4,3), (8,7) 19: (3,2), (6,5), (9,4), (9,8) 25: (3,2), (4,3), (6,5), (8,7) 29: (4,3), (7,6) 31: (3,2), (5,4), (6,5) 37: (3,2), (4,3), (6,5), (9,2), (9,8) 41: (4,3), (5,4), (8,7) 43: (3,2), (6,5), (7,6) 49: (3,2), (4,3), (6,5), (8,7) 53: (4,3) 61: (3,2), (4,3), (5,4), (6,5) 64: (3,2), (7,6), (9,8) 67: (3,2), (6,5) 71: (5,4), (7,6) 73: (3,2), (4,3), (6,5), (8,7), (9,8) 79: (3,2), (6,5) 81: (4,3), (5,4), (8,7) 89: (4,3), (8,7) 97: (3,2), (4,3), (6,5), (8,7) TESTS: Check more of the Wilson constructions from [Wi72]_:: sage: Q5 = [241, 281,421,601,641, 661, 701, 821,881] sage: Q9 = [73, 1153, 1873, 2017] sage: Q15 = [76231] sage: Q4 = [13, 73, 97, 109, 181, 229, 241, 277, 337, 409, 421, 457] sage: Q8 = [1009, 3137, 3697] sage: for Q,k in [(Q4,4),(Q5,5),(Q8,8),(Q9,9),(Q15,15)]: ....: for q in Q: ....: assert designs.difference_family(q,k,1,existence=True) is True ....: _ = designs.difference_family(q,k,1) Check Singer difference sets:: sage: sgp = lambda q,d: ((q**(d+1)-1)//(q-1), (q**d-1)//(q-1), (q**(d-1)-1)//(q-1)) sage: for q in range(2,10): ....: if is_prime_power(q): ....: for d in [2,3,4]: ....: v,k,l = sgp(q,d) ....: assert designs.difference_family(v,k,l,existence=True) is True ....: _ = designs.difference_family(v,k,l) .. TODO:: There is a slightly more general version of difference families where the stabilizers of the blocks are taken into account. A block is *short* if the stabilizer is not trivial. The more general version is called a *partial difference family*. It is still possible to construct BIBD from this more general version (see the chapter 16 in the Handbook [DesignHandbook]_). Implement recursive constructions from Buratti "Recursive for difference matrices and relative difference families" (1998) and Jungnickel "Composition theorems for difference families and regular planes" (1978) """ if (l * (v - 1)) % (k * (k - 1)) != 0: if existence: return False raise EmptySetError( "A (v,%d,%d)-difference family may exist only if %d*(v-1) = mod %d" % (k, l, l, k * (k - 1)) ) from block_design import are_hyperplanes_in_projective_geometry_parameters from database import DF_constructions if (v, k, l) in DF_constructions: if existence: return True return DF_constructions[(v, k, l)]() e = k * (k - 1) t = l * (v - 1) // e # number of blocks D = None if arith.is_prime_power(v): from sage.rings.finite_rings.constructor import GF G = K = GF(v, "z") x = K.multiplicative_generator() if l == (k - 1): if existence: return True return K, K.cyclotomic_cosets(x ** ((v - 1) // k))[1:] if t == 1: # some of the difference set constructions VI.18.48 from the # Handbook of combinatorial designs # q = 3 mod 4 if v % 4 == 3 and k == (v - 1) // 2: if existence: return True D = K.cyclotomic_cosets(x ** 2, [1]) # q = 4t^2 + 1, t odd elif v % 8 == 5 and k == (v - 1) // 4 and arith.is_square((v - 1) // 4): if existence: return True D = K.cyclotomic_cosets(x ** 4, [1]) # q = 4t^2 + 9, t odd elif v % 8 == 5 and k == (v + 3) // 4 and arith.is_square((v - 9) // 4): if existence: return True D = K.cyclotomic_cosets(x ** 4, [1]) D[0].insert(0, K.zero()) if D is None and l == 1: one = K.one() # Wilson (1972), Theorem 9 if k % 2 == 1: m = (k - 1) // 2 xx = x ** m to_coset = {x ** i * xx ** j: i for i in xrange(m) for j in xrange((v - 1) / m)} r = x ** ((v - 1) // k) # primitive k-th root of unity if len(set(to_coset[r ** j - one] for j in xrange(1, m + 1))) == m: if existence: return True B = [r ** j for j in xrange(k)] # = H^((k-1)t) whose difference is # H^(mt) (r^i - 1, i=1,..,m) # Now pick representatives a translate of R for by a set of # representatives of H^m / H^(mt) D = [[x ** (i * m) * b for b in B] for i in xrange(t)] # Wilson (1972), Theorem 10 else: m = k // 2 xx = x ** m to_coset = {x ** i * xx ** j: i for i in xrange(m) for j in xrange((v - 1) / m)} r = x ** ((v - 1) // (k - 1)) # primitive (k-1)-th root of unity if ( all(to_coset[r ** j - one] != 0 for j in xrange(1, m)) and len(set(to_coset[r ** j - one] for j in xrange(1, m))) == m - 1 ): if existence: return True B = [K.zero()] + [r ** j for j in xrange(k - 1)] D = [[x ** (i * m) * b for b in B] for i in xrange(t)] # Wilson (1972), Theorem 11 if D is None and k == 6: r = x ** ((v - 1) // 3) # primitive cube root of unity r2 = r * r xx = x ** 5 to_coset = {x ** i * xx ** j: i for i in xrange(5) for j in xrange((v - 1) / 5)} for c in to_coset: if c == 1 or c == r or c == r2: continue if len(set(to_coset[elt] for elt in (r - 1, c * (r - 1), c - 1, c - r, c - r ** 2))) == 5: if existence: return True B = [one, r, r ** 2, c, c * r, c * r ** 2] D = [[x ** (i * 5) * b for b in B] for i in xrange(t)] break if D is None and are_hyperplanes_in_projective_geometry_parameters(v, k, l): _, (q, d) = are_hyperplanes_in_projective_geometry_parameters(v, k, l, True) if existence: return True else: G, D = singer_difference_set(q, d) if D is None: if existence: return Unknown raise NotImplementedError("No constructions for these parameters") if check and not is_difference_family(G, D, verbose=False): raise RuntimeError return G, D