def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, impl=None, proof=None, **kwds): """ EXAMPLES:: sage: GF.create_key_and_extra_args(9, 'a') ((9, ('a',), 'conway', None, '{}', 3, 2, True), {}) sage: GF.create_key_and_extra_args(9, 'a', foo='value') ((9, ('a',), 'conway', None, "{'foo': 'value'}", 3, 2, True), {'foo': 'value'}) """ from sage.structure.proof.all import WithProof, arithmetic if proof is None: proof = arithmetic() with WithProof("arithmetic", proof): order = int(order) if order <= 1: raise ValueError("the order of a finite field must be > 1.") if arith.is_prime(order): name = None modulus = None p = integer.Integer(order) n = integer.Integer(1) elif arith.is_prime_power(order): if not names is None: name = names name = normalize_names(1, name) p, n = arith.factor(order)[0] if modulus is None or modulus == "default": if exists_conway_polynomial(p, n): modulus = "conway" else: if p == 2: modulus = "minimal_weight" else: modulus = "random" elif modulus == "random": modulus += str(random.randint(0, 1 << 128)) if isinstance(modulus, (list, tuple)): modulus = FiniteField(p)["x"](modulus) # some classes use 'random' as the modulus to # generate a random modulus, but we don't want # to cache it elif sage.rings.polynomial.polynomial_element.is_Polynomial(modulus): modulus = modulus.change_variable_name("x") elif not isinstance(modulus, str): raise ValueError("Modulus parameter not understood.") else: # Neither a prime, nor a prime power raise ValueError("the order of a finite field must be a prime power.") return (order, name, modulus, impl, str(kwds), p, n, proof), kwds
def find_q_x(k,n): r""" Find integers `q,x` such that the `q-x` construction yields an `OA(k,n)`. See the documentation of :func:`construction_q_x` to find out what hypotheses the integers `q,x` must satisfy. .. WARNING:: For efficiency reasons, this function checks that Sage can build an `OA(k+1,q-x-1)` and an `OA(k+1,q-x+1)`, which is stronger than checking that Sage can build a `OA(k,q-x-1)-(q-x-1).OA(k,1)` and a `OA(k,q-x+1)-(q-x+1).OA(k,1)`. The latter would trigger a lot of independent set computations in :func:`sage.combinat.designs.orthogonal_arrays.incomplete_orthogonal_array`. INPUT: - ``k,n`` (integers) .. SEEALSO:: :func:`construction_q_x` EXAMPLE:: sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_q_x sage: find_q_x(10,9) False sage: find_q_x(9,158)[1] (9, 16, 6) """ from sage.rings.arith import is_prime_power # n = (q-1)*(q-x) + x + 2 # = q^2 - q*x - q + 2*x + 2 for q in range(max(3,k+2),n): # n-q**2+q-2 = 2x-qx # = x(2-q) x = (n-q**2+q-2)//(2-q) if (x < q and 0 < x and n == (q-1)*(q-x)+x+2 and is_prime_power(q) and orthogonal_array(k+1,q-x-1,existence=True) and orthogonal_array(k+1,q-x+1,existence=True) and # The next is always True, because q is a prime power # orthogonal_array(k+1,q,existence=True) and orthogonal_array(k, x+2 ,existence=True)): return construction_q_x, (k,q,x) return False
def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, impl=None, proof=None, **kwds): """ EXAMPLES:: sage: GF.create_key_and_extra_args(9, 'a') ((9, ('a',), x^2 + 2*x + 2, None, '{}', 3, 2, True), {}) sage: GF.create_key_and_extra_args(9, 'a', foo='value') ((9, ('a',), x^2 + 2*x + 2, None, "{'foo': 'value'}", 3, 2, True), {'foo': 'value'}) """ from sage.structure.proof.all import WithProof, arithmetic if proof is None: proof = arithmetic() with WithProof('arithmetic', proof): order = int(order) if order <= 1: raise ValueError("the order of a finite field must be > 1.") if arith.is_prime(order): name = None modulus = None p = integer.Integer(order) n = integer.Integer(1) elif arith.is_prime_power(order): if not names is None: name = names name = normalize_names(1,name) p, n = arith.factor(order)[0] if modulus is None or isinstance(modulus, str): # A string specifies an algorithm to find a suitable modulus. if modulus == "default": # for backward compatibility modulus = None modulus = GF(p)['x'].irreducible_element(n, algorithm=modulus) elif isinstance(modulus, (list, tuple)): modulus = GF(p)['x'](modulus) elif sage.rings.polynomial.polynomial_element.is_Polynomial(modulus): modulus = modulus.change_variable_name('x') else: raise TypeError("wrong type for modulus parameter") else: raise ValueError("the order of a finite field must be a prime power.") return (order, name, modulus, impl, str(kwds), p, n, proof), kwds
def BIBD_5q_5_for_q_prime_power(q): r""" Return a `(5q,5,1)`-BIBD with `q\equiv 1\pmod 4` a prime power. See Theorem 24 [ClaytonSmith]_. INPUT: - ``q`` (integer) -- a prime power such that `q\equiv 1\pmod 4`. EXAMPLES:: sage: from sage.combinat.designs.bibd import BIBD_5q_5_for_q_prime_power sage: for q in [25, 45, 65, 85, 125, 145, 185, 205, 305, 405, 605]: # long time ....: _ = BIBD_5q_5_for_q_prime_power(q/5) # long time """ from sage.rings.arith import is_prime_power from sage.rings.finite_rings.constructor import FiniteField if q % 4 != 1 or not is_prime_power(q): raise ValueError("q is not a prime power or q%4!=1.") d = (q - 1) / 4 B = [] F = FiniteField(q, 'x') a = F.primitive_element() L = {b: i for i, b in enumerate(F)} for b in L: B.append([i * q + L[b] for i in range(5)]) for i in range(5): for j in range(d): B.append([ i * q + L[b], ((i + 1) % 5) * q + L[a**j + b], ((i + 1) % 5) * q + L[-a**j + b], ((i + 4) % 5) * q + L[a**(j + d) + b], ((i + 4) % 5) * q + L[-a**(j + d) + b], ]) return B
def find_construction_3_6(k,n): r""" Finds a decomposition for construction 3.6 from [AC07]_ INPUT: - ``k,n`` (integers) .. SEEALSO:: :func:`construction_3_6` OUTPUT: A pair ``f,args`` such that ``f(*args)`` returns the requested OA. EXAMPLES:: sage: from sage.combinat.designs.orthogonal_arrays_recursive import find_construction_3_6 sage: find_construction_3_6(8,95)[1] (8, 13, 7, 4) sage: find_construction_3_6(8,98) """ from sage.rings.arith import is_prime_power for mm in range(k-1,n//2+1): if (not orthogonal_array(k,mm+0,existence=True) or not orthogonal_array(k,mm+1,existence=True) or not orthogonal_array(k,mm+2,existence=True)): continue for nn in range(2,n//mm+1): i = n-nn*mm if i<=0: continue if (is_prime_power(nn) and orthogonal_array(k+i,nn,existence=True)): return construction_3_6, (k,nn,mm,i)
def BIBD_5q_5_for_q_prime_power(q): r""" Return a `(5q,5,1)`-BIBD with `q\equiv 1\pmod 4` a prime power. See Theorem 24 [ClaytonSmith]_. INPUT: - ``q`` (integer) -- a prime power such that `q\equiv 1\pmod 4`. EXAMPLES:: sage: from sage.combinat.designs.bibd import BIBD_5q_5_for_q_prime_power sage: for q in [25, 45, 65, 85, 125, 145, 185, 205, 305, 405, 605]: # long time ....: _ = BIBD_5q_5_for_q_prime_power(q/5) # long time """ from sage.rings.arith import is_prime_power from sage.rings.finite_rings.constructor import FiniteField if q%4 != 1 or not is_prime_power(q): raise ValueError("q is not a prime power or q%4!=1.") d = (q-1)/4 B = [] F = FiniteField(q,'x') a = F.primitive_element() L = {b:i for i,b in enumerate(F)} for b in L: B.append([i*q + L[b] for i in range(5)]) for i in range(5): for j in range(d): B.append([ i*q + L[b ], ((i+1)%5)*q + L[ a**j+b ], ((i+1)%5)*q + L[-a**j+b ], ((i+4)%5)*q + L[ a**(j+d)+b], ((i+4)%5)*q + L[-a**(j+d)+b], ]) return B
def projective_plane(n, check=True, existence=False): r""" Returns a projective plane of order ``n`` as a 2-design. A finite projective plane is a 2-design with `n^2+n+1` lines (or blocks) and `n^2+n+1` points. For more information on finite projective planes, see the :wikipedia:`Projective_plane#Finite_projective_planes`. If no construction is possible, then the function raises a ``EmptySetError`` whereas if no construction is available the function raises a ``NotImplementedError``. INPUT: - ``n`` -- the finite projective plane's order EXAMPLES:: sage: designs.projective_plane(2) Incidence structure with 7 points and 7 blocks sage: designs.projective_plane(3) Incidence structure with 13 points and 13 blocks sage: designs.projective_plane(4) Incidence structure with 21 points and 21 blocks sage: designs.projective_plane(5) Incidence structure with 31 points and 31 blocks sage: designs.projective_plane(6) Traceback (most recent call last): ... EmptySetError: By the Ryser-Chowla theorem, no projective plane of order 6 exists. sage: designs.projective_plane(10) Traceback (most recent call last): ... EmptySetError: No projective plane of order 10 exists by C. Lam, L. Thiel and S. Swiercz "The nonexistence of finite projective planes of order 10" (1989), Canad. J. Math. sage: designs.projective_plane(12) Traceback (most recent call last): ... NotImplementedError: If such a projective plane exists, we do not know how to build it. sage: designs.projective_plane(14) Traceback (most recent call last): ... EmptySetError: By the Ryser-Chowla theorem, no projective plane of order 14 exists. TESTS:: sage: designs.projective_plane(2197, existence=True) True sage: designs.projective_plane(6, existence=True) False sage: designs.projective_plane(10, existence=True) False sage: designs.projective_plane(12, existence=True) Unknown """ from sage.rings.arith import is_prime_power, two_squares if n <= 1: if existence: return False raise EmptySetError("There is no projective plane of order <= 1") if n == 10: if existence: return False ref = ("C. Lam, L. Thiel and S. Swiercz \"The nonexistence of finite " "projective planes of order 10\" (1989), Canad. J. Math.") raise EmptySetError("No projective plane of order 10 exists by %s"%ref) if (n%4) in [1,2]: try: two_squares(n) except ValueError: if existence: return False raise EmptySetError("By the Ryser-Chowla theorem, no projective" " plane of order "+str(n)+" exists.") if not is_prime_power(n): if existence: return Unknown raise NotImplementedError("If such a projective plane exists, we do " "not know how to build it.") if existence: return True else: return DesarguesianProjectivePlaneDesign(n, check=check)
def GDD_4_2(q,existence=False,check=True): r""" Return a `(2q,\{4\},\{2\})`-GDD for `q` a prime power with `q\equiv 1\pmod{6}`. This method implements Lemma VII.5.17 from [BJL99] (p.495). INPUT: - ``q`` (integer) - ``existence`` (boolean) -- instead of building the design, return: - ``True`` -- meaning that Sage knows how to build the design - ``Unknown`` -- meaning that Sage does not know how to build the design, but that the design may exist (see :mod:`sage.misc.unknown`). - ``False`` -- meaning that the design does not exist. - ``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. EXAMPLE:: sage: from sage.combinat.designs.group_divisible_designs import GDD_4_2 sage: GDD_4_2(7,existence=True) True sage: GDD_4_2(7) Group Divisible Design on 14 points of type 2^7 sage: GDD_4_2(8,existence=True) Unknown sage: GDD_4_2(8) Traceback (most recent call last): ... NotImplementedError """ if q <=1 or q%6 != 1 or not is_prime_power(q): if existence: return Unknown raise NotImplementedError if existence: return True from sage.rings.finite_rings.constructor import FiniteField as GF G = GF(q,'x') w = G.primitive_element() e = w**((q-1)/3) # A first parallel class is defined. G acts on it, which yields all others. first_class = [[(0,0),(1,w**i),(1,e*w**i),(1,e*e*w**i)] for i in range((q-1)/6)] label = {p:i for i,p in enumerate(G)} classes = [[[2*label[x[1]+g]+(x[0]+j)%2 for x in S] for S in first_class] for g in G for j in range(2)] return GroupDivisibleDesign(2*q, groups = [[i,i+1] for i in range(0,2*q,2)], blocks = sum(classes,[]), K = [4], G = [2], check = check, copy = False)
def NonisotropicUnitaryPolarGraph(m, q): r""" Returns the Graph `NU(m,q)`. Returns the graph on nonisotropic, with respect to a nondegenerate Hermitean form, points of the `(m-1)`-dimensional projective space over `F_q`, with points adjacent whenever they lie on a tangent (to the set of isotropic points) line. For more information, see Sect. 9.9 of [BH12]_ and series C14 in [Hu75]_. INPUT: - ``m,q`` (integers) -- `q` must be a prime power. EXAMPLES:: sage: g=graphs.NonisotropicUnitaryPolarGraph(5,2); g NU(5, 2): Graph on 176 vertices sage: g.is_strongly_regular(parameters=True) (176, 135, 102, 108) TESTS:: sage: graphs.NonisotropicUnitaryPolarGraph(4,2).is_strongly_regular(parameters=True) (40, 27, 18, 18) sage: graphs.NonisotropicUnitaryPolarGraph(4,3).is_strongly_regular(parameters=True) # long time (540, 224, 88, 96) sage: graphs.NonisotropicUnitaryPolarGraph(6,6) Traceback (most recent call last): ... ValueError: q must be a prime power REFERENCE: .. [Hu75] X. L. Hubaut. Strongly regular graphs. Disc. Math. 13(1975), pp 357--381. http://dx.doi.org/10.1016/0012-365X(75)90057-6 """ from sage.rings.arith import is_prime_power p, k = is_prime_power(q, get_data=True) if k == 0: raise ValueError('q must be a prime power') from sage.libs.gap.libgap import libgap from itertools import combinations F = libgap.GF(q**2) # F_{q^2} W = libgap.FullRowSpace(F, m) # F_{q^2}^m B = libgap.Elements(libgap.Basis(W)) # the standard basis of W if m % 2 != 0: point = B[(m - 1) / 2] else: if p == 2: point = B[m / 2] + F.PrimitiveRoot() * B[(m - 2) / 2] else: point = B[(m - 2) / 2] + B[m / 2] g = libgap.GeneralUnitaryGroup(m, q) V = libgap.Orbit(g, point, libgap.OnLines) # orbit on nonisotropic points gp = libgap.Action(g, V, libgap.OnLines) # make a permutation group s = libgap.Subspace(W, [point, point + B[0]]) # a tangent line on point # and the points there sp = [ libgap.Elements(libgap.Basis(x))[0] for x in libgap.Elements(s.Subspaces(1)) ] h = libgap.Set( map(lambda x: libgap.Position(V, x), libgap.Intersection(V, sp))) # indices L = libgap.Orbit(gp, h, libgap.OnSets) # orbit on the tangent lines G = Graph() for x in L: # every pair of points in the subspace is adjacent to each other in G G.add_edges(combinations(x, 2)) G.relabel() G.name("NU" + str((m, q))) return G
def AhrensSzekeresGeneralizedQuadrangleGraph(q, dual=False): r""" Return the collinearity graph of the generalized quadrangle `AS(q)`, or of its dual Let `q` be an odd prime power. `AS(q)` is a generalized quadrangle [GQwiki]_ of order `(q-1,q+1)`, see 3.1.5 in [PT09]_. Its points are elements of `F_q^3`, and lines are sets of size `q` of the form * `\{ (\sigma, a, b) \mid \sigma\in F_q \}` * `\{ (a, \sigma, b) \mid \sigma\in F_q \}` * `\{ (c \sigma^2 - b \sigma + a, -2 c \sigma + b, \sigma) \mid \sigma\in F_q \}`, where `a`, `b`, `c` are arbitrary elements of `F_q`. INPUT: - ``q`` -- a power of an odd prime number - ``dual`` -- if ``False`` (default), return the collinearity graph of `AS(q)`. Otherwise return the collinearity graph of the dual `AS(q)`. EXAMPLES:: sage: g=graphs.AhrensSzekeresGeneralizedQuadrangleGraph(5); g AS(5); GQ(4, 6): Graph on 125 vertices sage: g.is_strongly_regular(parameters=True) (125, 28, 3, 7) sage: g=graphs.AhrensSzekeresGeneralizedQuadrangleGraph(5,dual=True); g AS(5)*; GQ(6, 4): Graph on 175 vertices sage: g.is_strongly_regular(parameters=True) (175, 30, 5, 5) REFERENCE: .. [GQwiki] `Generalized quadrangle <http://en.wikipedia.org/wiki/Generalized_quadrangle>`__ .. [PT09] S. Payne, J. A. Thas. Finite generalized quadrangles. European Mathematical Society, 2nd edition, 2009. """ from sage.combinat.designs.incidence_structures import IncidenceStructure p, k = is_prime_power(q,get_data=True) if k==0 or p==2: raise ValueError('q must be an odd prime power') F = FiniteField(q, 'a') L = [] for a in F: for b in F: L.append(tuple(map(lambda s: (s, a, b), F))) L.append(tuple(map(lambda s: (a, s, b), F))) for c in F: L.append(tuple(map(lambda s: (c*s**2 - b*s + a, -2*c*s + b, s), F))) if dual: G = IncidenceStructure(L).intersection_graph() G.name('AS('+str(q)+')*; GQ'+str((q+1,q-1))) else: G = IncidenceStructure(L).dual().intersection_graph() G.name('AS('+str(q)+'); GQ'+str((q-1,q+1))) return G
def projective_plane(n, check=True, existence=False): r""" Return a projective plane of order ``n`` as a 2-design. A finite projective plane is a 2-design with `n^2+n+1` lines (or blocks) and `n^2+n+1` points. For more information on finite projective planes, see the :wikipedia:`Projective_plane#Finite_projective_planes`. If no construction is possible, then the function raises a ``EmptySetError`` whereas if no construction is available the function raises a ``NotImplementedError``. INPUT: - ``n`` -- the finite projective plane's order EXAMPLES:: sage: designs.projective_plane(2) (7,3,1)-Balanced Incomplete Block Design sage: designs.projective_plane(3) (13,4,1)-Balanced Incomplete Block Design sage: designs.projective_plane(4) (21,5,1)-Balanced Incomplete Block Design sage: designs.projective_plane(5) (31,6,1)-Balanced Incomplete Block Design sage: designs.projective_plane(6) Traceback (most recent call last): ... EmptySetError: By the Bruck-Ryser theorem, no projective plane of order 6 exists. sage: designs.projective_plane(10) Traceback (most recent call last): ... EmptySetError: No projective plane of order 10 exists by C. Lam, L. Thiel and S. Swiercz "The nonexistence of finite projective planes of order 10" (1989), Canad. J. Math. sage: designs.projective_plane(12) Traceback (most recent call last): ... NotImplementedError: If such a projective plane exists, we do not know how to build it. sage: designs.projective_plane(14) Traceback (most recent call last): ... EmptySetError: By the Bruck-Ryser theorem, no projective plane of order 14 exists. TESTS:: sage: designs.projective_plane(2197, existence=True) True sage: designs.projective_plane(6, existence=True) False sage: designs.projective_plane(10, existence=True) False sage: designs.projective_plane(12, existence=True) Unknown """ from sage.rings.arith import is_prime_power from sage.rings.sum_of_squares import is_sum_of_two_squares_pyx if n <= 1: if existence: return False raise EmptySetError("There is no projective plane of order <= 1") if n == 10: if existence: return False ref = ("C. Lam, L. Thiel and S. Swiercz \"The nonexistence of finite " "projective planes of order 10\" (1989), Canad. J. Math.") raise EmptySetError("No projective plane of order 10 exists by %s" % ref) if (n % 4) in [1, 2] and not is_sum_of_two_squares_pyx(n): if existence: return False raise EmptySetError("By the Bruck-Ryser theorem, no projective" " plane of order {} exists.".format(n)) if not is_prime_power(n): if existence: return Unknown raise NotImplementedError("If such a projective plane exists, we do " "not know how to build it.") if existence: return True else: return DesarguesianProjectivePlaneDesign(n, check=check)
def kirkman_triple_system(v,existence=False): r""" Return a Kirkman Triple System on `v` points. A Kirkman Triple System `KTS(v)` is a resolvable Steiner Triple System. It exists if and only if `v\equiv 3\pmod{6}`. INPUT: - `n` (integer) - ``existence`` (boolean; ``False`` by default) -- whether to build the `KTS(n)` or only answer whether it exists. .. SEEALSO:: :meth:`IncidenceStructure.is_resolvable` EXAMPLES: A solution to Kirkmman's original problem:: sage: kts = designs.kirkman_triple_system(15) sage: classes = kts.is_resolvable(1)[1] sage: names = '0123456789abcde' sage: to_name = lambda (r,s,t): ' '+names[r]+names[s]+names[t]+' ' sage: rows = [join(('Day {}'.format(i) for i in range(1,8)), ' ')] sage: rows.extend(join(map(to_name,row), ' ') for row in zip(*classes)) sage: print join(rows,'\n') Day 1 Day 2 Day 3 Day 4 Day 5 Day 6 Day 7 07e 18e 29e 3ae 4be 5ce 6de 139 24a 35b 46c 05d 167 028 26b 03c 14d 257 368 049 15a 458 569 06a 01b 12c 23d 347 acd 7bd 78c 89d 79a 8ab 9bc TESTS:: sage: for i in range(3,300,6): ....: _ = designs.kirkman_triple_system(i) """ if v%6 != 3: if existence: return False raise ValueError("There is no KTS({}) as v!=3 mod(6)".format(v)) if existence: return False elif v == 3: return BalancedIncompleteBlockDesign(3,[[0,1,2]],k=3,lambd=1) elif v == 9: classes = [[[0, 1, 5], [2, 6, 7], [3, 4, 8]], [[1, 6, 8], [3, 5, 7], [0, 2, 4]], [[1, 4, 7], [0, 3, 6], [2, 5, 8]], [[4, 5, 6], [0, 7, 8], [1, 2, 3]]] KTS = BalancedIncompleteBlockDesign(v,[tr for cl in classes for tr in cl],k=3,lambd=1,copy=False) KTS._classes = classes return KTS # Construction 1.1 from [Stinson91] (originally Theorem 6 from [RCW71]) # # For all prime powers q=1 mod 6, there exists a KTS(2q+1) elif ((v-1)//2)%6 == 1 and is_prime_power((v-1)//2): from sage.rings.finite_rings.constructor import FiniteField as GF q = (v-1)//2 K = GF(q,'x') a = K.primitive_element() t = (q-1)/6 # m is the solution of a^m=(a^t+1)/2 from sage.groups.generic import discrete_log m = discrete_log((a**t+1)/2, a) assert 2*a**m == a**t+1 # First parallel class first_class = [[(0,1),(0,2),'inf']] b0 = K.one(); b1 = a**t; b2 = a**m first_class.extend([(b0*a**i,1),(b1*a**i,1),(b2*a**i,2)] for i in range(t)+range(2*t,3*t)+range(4*t,5*t)) b0 = a**(m+t); b1=a**(m+3*t); b2=a**(m+5*t) first_class.extend([[(b0*a**i,2),(b1*a**i,2),(b2*a**i,2)] for i in range(t)]) # Action of K on the points action = lambda v,x : (v+x[0],x[1]) if len(x) == 2 else x # relabel to integer relabel = {(p,x): i+(x-1)*q for i,p in enumerate(K) for x in [1,2]} relabel['inf'] = 2*q classes = [[[relabel[action(p,x)] for x in tr] for tr in first_class] for p in K] KTS = BalancedIncompleteBlockDesign(v,[tr for cl in classes for tr in cl],k=3,lambd=1,copy=False) KTS._classes = classes return KTS # Construction 1.2 from [Stinson91] (originally Theorem 5 from [RCW71]) # # For all prime powers q=1 mod 6, there exists a KTS(3q) elif (v//3)%6 == 1 and is_prime_power(v//3): from sage.rings.finite_rings.constructor import FiniteField as GF q = v//3 K = GF(q,'x') a = K.primitive_element() t = (q-1)/6 A0 = [(0,0),(0,1),(0,2)] B = [[(a**i,j),(a**(i+2*t),j),(a**(i+4*t),j)] for j in range(3) for i in range(t)] A = [[(a**i,0),(a**(i+2*t),1),(a**(i+4*t),2)] for i in range(6*t)] # Action of K on the points action = lambda v,x: (v+x[0],x[1]) # relabel to integer relabel = {(p,j): i+j*q for i,p in enumerate(K) for j in range(3)} B0 = [A0] + B + A[t:2*t] + A[3*t:4*t] + A[5*t:6*t] # Classes classes = [[[relabel[action(p,x)] for x in tr] for tr in B0] for p in K] for i in range(t)+range(2*t,3*t)+range(4*t,5*t): classes.append([[relabel[action(p,x)] for x in A[i]] for p in K]) KTS = BalancedIncompleteBlockDesign(v,[tr for cl in classes for tr in cl],k=3,lambd=1,copy=False) KTS._classes = classes return KTS else: # This is Lemma IX.6.4 from [BJL99]. # # This construction takes a (v,{4,7})-PBD. All points are doubled (x has # a copy x'), and an infinite point \infty is added. # # On all blocks of 2*4 points we "paste" a KTS(2*4+1) using the infinite # point, in such a way that all {x,x',infty} are set of the design. We # do the same for blocks with 2*7 points using a KTS(2*7+1). # # Note that the triples of points equal to {x,x',\infty} will be added # several times. # # As all those subdesigns are resolvable, each class of the KTS(n) is # obtained by considering a set {x,x',\infty} and all sets of all # parallel classes of the subdesign which contain this set. # We create the small KTS(n') we need, and relabel them such that # 01(n'-1),23(n'-1),... are blocks of the design. gdd4 = kirkman_triple_system(9) gdd7 = kirkman_triple_system(15) X = [B for B in gdd4 if 8 in B] for b in X: b.remove(8) X = sum(X, []) + [8] gdd4.relabel({v:i for i,v in enumerate(X)}) gdd4 = gdd4.is_resolvable(True)[1] # the relabeled classes X = [B for B in gdd7 if 14 in B] for b in X: b.remove(14) X = sum(X, []) + [14] gdd7.relabel({v:i for i,v in enumerate(X)}) gdd7 = gdd7.is_resolvable(True)[1] # the relabeled classes # The first parallel class contains 01(n'-1), the second contains # 23(n'-1), etc.. # Then remove the blocks containing (n'-1) for B in gdd4: for i,b in enumerate(B): if 8 in b: j = min(b); del B[i]; B.insert(0,j); break gdd4.sort() for B in gdd4: B.pop(0) for B in gdd7: for i,b in enumerate(B): if 14 in b: j = min(b); del B[i]; B.insert(0,j); break gdd7.sort() for B in gdd7: B.pop(0) # Pasting the KTS(n') without {x,x',\infty} blocks classes = [[] for i in range((v-1)/2)] gdd = {4:gdd4, 7: gdd7} for B in PBD_4_7((v-1)//2,check=False): for i,classs in enumerate(gdd[len(B)]): classes[B[i]].extend([[2*B[x//2]+x%2 for x in BB] for BB in classs]) # The {x,x',\infty} blocks for i,classs in enumerate(classes): classs.append([2*i,2*i+1,v-1]) KTS = BalancedIncompleteBlockDesign(v, blocks = [tr for cl in classes for tr in cl], k=3, lambd=1, check=True, copy =False) KTS._classes = classes assert KTS.is_resolvable() return KTS
def skew_hadamard_matrix(n,existence=False, skew_normalize=True, check=True): r""" 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. 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: - ``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. EXAMPLES:: sage: from sage.combinat.matrices.hadamard_matrix import skew_hadamard_matrix sage: skew_hadamard_matrix(12).det() 2985984 sage: 12^6 2985984 sage: skew_hadamard_matrix(1) [1] sage: skew_hadamard_matrix(2) [ 1 1] [-1 1] TESTS:: sage: skew_hadamard_matrix(10,existence=True) False sage: skew_hadamard_matrix(12,existence=True) True sage: skew_hadamard_matrix(784,existence=True) True sage: skew_hadamard_matrix(10) Traceback (most recent call last): ... ValueError: A skew Hadamard matrix of order 10 does not exist sage: skew_hadamard_matrix(36) 36 x 36 dense matrix over Integer Ring... sage: skew_hadamard_matrix(36)==skew_hadamard_matrix(36,skew_normalize=False) False sage: skew_hadamard_matrix(52) 52 x 52 dense matrix over Integer Ring... sage: skew_hadamard_matrix(92) 92 x 92 dense matrix over Integer Ring... sage: skew_hadamard_matrix(816) # long time 816 x 816 dense matrix over Integer Ring... sage: skew_hadamard_matrix(100) Traceback (most recent call last): ... ValueError: A skew Hadamard matrix of order 100 is not yet implemented. sage: skew_hadamard_matrix(100,existence=True) Unknown REFERENCES: .. [Ha83] M. Hall, Combinatorial Theory, 2nd edition, Wiley, 1983 """ def true(): _skew_had_cache[n]=True return True M = None if existence and n in _skew_had_cache: return True if not(n % 4 == 0) and (n > 2): if existence: return False raise ValueError("A skew Hadamard matrix of order %s does not exist" % n) if n == 2: if existence: return true() M = matrix([[1, 1], [-1, 1]]) elif n == 1: if existence: return true() M = matrix([1]) elif is_prime_power(n - 1) and ((n - 1) % 4 == 3): if existence: return true() M = hadamard_matrix_paleyI(n, normalize=False) elif n % 8 == 0: if skew_hadamard_matrix(n//2,existence=True): # (Lemma 14.1.6 in [Ha83]_) if existence: return true() H = skew_hadamard_matrix(n//2,check=False) M = block_matrix([[H,H], [-H.T,H.T]]) else: # try Williamson construction (Lemma 14.1.5 in [Ha83]_) for d in divisors(n)[2:-2]: # skip 1, 2, n/2, and n n1 = n//d if is_prime_power(d - 1) and (d % 4 == 0) and (n1 % 4 == 0)\ and skew_hadamard_matrix(n1,existence=True): if existence: return true() H = skew_hadamard_matrix(n1, check=False)-I(n1) U = matrix(ZZ, d, lambda i, j: -1 if i==j==0 else\ 1 if i==j==1 or (i>1 and j-1==d-i)\ else 0) A = block_matrix([[matrix([0]), matrix(ZZ,1,d-1,[1]*(d-1))], [ matrix(ZZ,d-1,1,[-1]*(d-1)), _helper_payley_matrix(d-1,zero_position=0)]])+I(d) M = A.tensor_product(I(n1))+(U*A).tensor_product(H) break if M is None: # try Williamson-Goethals-Seidel construction if GS_skew_hadamard_smallcases(n, existence=True): if existence: return true() M = GS_skew_hadamard_smallcases(n) else: if existence: return Unknown raise ValueError("A skew Hadamard matrix of order %s is not yet implemented." % n) if skew_normalize: dd = diagonal_matrix(M[0]) M = dd*M*dd if check: assert is_hadamard_matrix(M, normalized=False, skew=True) if skew_normalize: from sage.modules.free_module_element import vector assert M[0]==vector([1]*n) _skew_had_cache[n]=True return M
def BalancedIncompleteBlockDesign(v,k,existence=False,use_LJCR=False): r""" Returns a BIBD of parameters `v,k`. A Balanced Incomplete Block Design of parameters `v,k` is a collection `\mathcal C` of `k`-subsets of `V=\{0,\dots,v-1\}` such that for any two distinct elements `x,y\in V` there is a unique element `S\in \mathcal C` such that `x,y\in S`. More general definitions sometimes involve a `\lambda` parameter, and we assume here that `\lambda=1`. For more information on BIBD, see the :wikipedia:`corresponding Wikipedia entry <Block_design#Definition_of_a_BIBD_.28or_2-design.29>`. INPUT: - ``v,k`` (integers) - ``existence`` (boolean) -- instead of building the design, returns: - ``True`` -- meaning that Sage knows how to build the design - ``Unknown`` -- meaning that Sage does not know how to build the design, but that the design may exist (see :mod:`sage.misc.unknown`). - ``False`` -- meaning that the design does not exist. - ``use_LJCR`` (boolean) -- whether to query the La Jolla Covering Repository for the design when Sage does not know how to build it (see :meth:`~sage.combinat.designs.covering_design.best_known_covering_design_www`). This requires internet. .. SEEALSO:: * :func:`steiner_triple_system` * :func:`v_4_1_BIBD` * :func:`v_5_1_BIBD` TODO: * Implement other constructions from the Handbook of Combinatorial Designs. EXAMPLES:: sage: designs.BalancedIncompleteBlockDesign(7,3).blocks() [[0, 1, 3], [0, 2, 4], [0, 5, 6], [1, 2, 6], [1, 4, 5], [2, 3, 5], [3, 4, 6]] sage: B = designs.BalancedIncompleteBlockDesign(21,5, use_LJCR=True) # optional - internet sage: B # optional - internet Incidence structure with 21 points and 21 blocks sage: B.blocks() # optional - internet [[0, 1, 2, 3, 20], [0, 4, 8, 12, 16], [0, 5, 10, 15, 19], [0, 6, 11, 13, 17], [0, 7, 9, 14, 18], [1, 4, 11, 14, 19], [1, 5, 9, 13, 16], [1, 6, 8, 15, 18], [1, 7, 10, 12, 17], [2, 4, 9, 15, 17], [2, 5, 11, 12, 18], [2, 6, 10, 14, 16], [2, 7, 8, 13, 19], [3, 4, 10, 13, 18], [3, 5, 8, 14, 17], [3, 6, 9, 12, 19], [3, 7, 11, 15, 16], [4, 5, 6, 7, 20], [8, 9, 10, 11, 20], [12, 13, 14, 15, 20], [16, 17, 18, 19, 20]] sage: designs.BalancedIncompleteBlockDesign(20,5, use_LJCR=True) # optional - internet Traceback (most recent call last): ... ValueError: No such design exists ! sage: designs.BalancedIncompleteBlockDesign(16,6) Traceback (most recent call last): ... NotImplementedError: I don't know how to build this design. TESTS:: sage: designs.BalancedIncompleteBlockDesign(85,5,existence=True) True sage: _ = designs.BalancedIncompleteBlockDesign(85,5) A BIBD from a Finite Projective Plane:: sage: _ = designs.BalancedIncompleteBlockDesign(21,5) Some trivial BIBD:: sage: designs.BalancedIncompleteBlockDesign(10,10) Incidence structure with 10 points and 1 blocks sage: designs.BalancedIncompleteBlockDesign(1,10) Incidence structure with 1 points and 0 blocks Existence of BIBD with `k=3,4,5`:: sage: [v for v in xrange(50) if designs.BalancedIncompleteBlockDesign(v,3,existence=True)] [1, 3, 7, 9, 13, 15, 19, 21, 25, 27, 31, 33, 37, 39, 43, 45, 49] sage: [v for v in xrange(100) if designs.BalancedIncompleteBlockDesign(v,4,existence=True)] [1, 4, 13, 16, 25, 28, 37, 40, 49, 52, 61, 64, 73, 76, 85, 88, 97] sage: [v for v in xrange(150) if designs.BalancedIncompleteBlockDesign(v,5,existence=True)] [1, 5, 21, 25, 41, 45, 61, 65, 81, 85, 101, 105, 121, 125, 141, 145] For `k > 5` there are currently very few constructions:: sage: [v for v in xrange(150) if designs.BalancedIncompleteBlockDesign(v,6,existence=True) is True] [1, 6, 31] sage: [v for v in xrange(150) if designs.BalancedIncompleteBlockDesign(v,6,existence=True) is Unknown] [16, 21, 36, 46, 51, 61, 66, 76, 81, 91, 96, 106, 111, 121, 126, 136, 141] """ if v == 1: if existence: return True return BlockDesign(v, [], test=False) if k == v: if existence: return True return BlockDesign(v, [range(v)], test=False) if v < k or k < 2 or (v-1) % (k-1) != 0 or (v*(v-1)) % (k*(k-1)) != 0: if existence: return False raise EmptySetError("No such design exists !") if k == 2: if existence: return True from itertools import combinations return BlockDesign(v, combinations(range(v),2), test = False) if k == 3: if existence: return v%6 == 1 or v%6 == 3 return steiner_triple_system(v) if k == 4: if existence: return v%12 == 1 or v%12 == 4 return BlockDesign(v, v_4_1_BIBD(v), test = False) if k == 5: if existence: return v%20 == 1 or v%20 == 5 return BlockDesign(v, v_5_1_BIBD(v), test = False) if BIBD_from_TD(v,k,existence=True): if existence: return True return BlockDesign(v, BIBD_from_TD(v,k)) if v == (k-1)**2+k and is_prime_power(k-1): if existence: return True from block_design import projective_plane return projective_plane(k-1) if use_LJCR: from covering_design import best_known_covering_design_www B = best_known_covering_design_www(v,k,2) # Is it a BIBD or just a good covering ? expected_n_of_blocks = binomial(v,2)/binomial(k,2) if B.low_bd() > expected_n_of_blocks: if existence: return False raise EmptySetError("No such design exists !") B = B.incidence_structure() if len(B.blcks) == expected_n_of_blocks: if existence: return True else: return B if existence: return Unknown else: raise NotImplementedError("I don't know how to build this design.")
def BIBD_from_arc_in_desarguesian_projective_plane(n,k,existence=False): r""" Returns a `(n,k,1)`-BIBD from a maximal arc in a projective plane. This function implements a construction from Denniston [Denniston69]_, who describes a maximal :meth:`arc <sage.combinat.designs.bibd.BalancedIncompleteBlockDesign.arc>` in a :func:`Desarguesian Projective Plane <sage.combinat.designs.block_design.DesarguesianProjectivePlaneDesign>` of order `2^k`. From two powers of two `n,q` with `n<q`, it produces a `((n-1)(q+1)+1,n,1)`-BIBD. INPUT: - ``n,k`` (integers) -- must be powers of two (among other restrictions). - ``existence`` (boolean) -- whether to return the BIBD obtained through this construction (default), or to merely indicate with a boolean return value whether this method *can* build the requested BIBD. EXAMPLES: A `(232,8,1)`-BIBD:: sage: from sage.combinat.designs.bibd import BIBD_from_arc_in_desarguesian_projective_plane sage: from sage.combinat.designs.bibd import BalancedIncompleteBlockDesign sage: D = BIBD_from_arc_in_desarguesian_projective_plane(232,8) sage: BalancedIncompleteBlockDesign(232,D) (232,8,1)-Balanced Incomplete Block Design A `(120,8,1)`-BIBD:: sage: D = BIBD_from_arc_in_desarguesian_projective_plane(120,8) sage: BalancedIncompleteBlockDesign(120,D) (120,8,1)-Balanced Incomplete Block Design Other parameters:: sage: all(BIBD_from_arc_in_desarguesian_projective_plane(n,k,existence=True) ....: for n,k in ....: [(120, 8), (232, 8), (456, 8), (904, 8), (496, 16), ....: (976, 16), (1936, 16), (2016, 32), (4000, 32), (8128, 64)]) True Of course, not all can be built this way:: sage: BIBD_from_arc_in_desarguesian_projective_plane(7,3,existence=True) False sage: BIBD_from_arc_in_desarguesian_projective_plane(7,3) Traceback (most recent call last): ... ValueError: This function cannot produce a (7,3,1)-BIBD REFERENCE: .. [Denniston69] R. H. F. Denniston, Some maximal arcs in finite projective planes. Journal of Combinatorial Theory 6, no. 3 (1969): 317-319. http://dx.doi.org/10.1016/S0021-9800(69)80095-5 """ q = (n-1)//(k-1)-1 if (k % 2 or q % 2 or q <= k or n != (k-1)*(q+1)+1 or not is_prime_power(k) or not is_prime_power(q)): if existence: return False raise ValueError("This function cannot produce a ({},{},1)-BIBD".format(n,k)) if existence: return True n = k # From now on, the code assumes the notations of [Denniston69] for n,q, so # that the BIBD returned by the method will have the requested parameters. from sage.rings.finite_rings.constructor import FiniteField as GF from sage.libs.gap.libgap import libgap from sage.matrix.constructor import Matrix K = GF(q,'a') one = K.one() # An irreducible quadratic form over K[X,Y] GO = libgap.GeneralOrthogonalGroup(-1,2,q) M = libgap.InvariantQuadraticForm(GO)['matrix'] M = Matrix(M) M = M.change_ring(K) Q = lambda xx,yy : M[0,0]*xx**2+(M[0,1]+M[1,0])*xx*yy+M[1,1]*yy**2 # Here, the additive subgroup H (of order n) of K mentioned in # [Denniston69] is the set of all elements of K of degree < log_n # (seeing elements of K as polynomials in 'a') K_iter = list(K) # faster iterations log_n = is_prime_power(n,get_data=True)[1] C = [(x,y,one) for x in K_iter for y in K_iter if Q(x,y).polynomial().degree() < log_n] from sage.combinat.designs.block_design import DesarguesianProjectivePlaneDesign return DesarguesianProjectivePlaneDesign(q).trace(C)._blocks
def v_5_1_BIBD(v, check=True): r""" Returns a `(v,5,1)`-BIBD. This method follows the constuction from [ClaytonSmith]_. INPUT: - ``v`` (integer) .. SEEALSO:: * :meth:`BalancedIncompleteBlockDesign` EXAMPLES:: sage: from sage.combinat.designs.bibd import v_5_1_BIBD sage: i = 0 sage: while i<200: ....: i += 20 ....: _ = v_5_1_BIBD(i+1) ....: _ = v_5_1_BIBD(i+5) """ v = int(v) assert (v > 1) assert (v % 20 == 5 or v % 20 == 1 ) # note: equivalent to (v-1)%4 == 0 and (v*(v-1))%20 == 0 # Lemma 27 if v % 5 == 0 and (v // 5) % 4 == 1 and is_prime_power(v // 5): bibd = BIBD_5q_5_for_q_prime_power(v // 5) # Lemma 28 elif v == 21: from sage.rings.finite_rings.integer_mod_ring import Zmod bibd = BIBD_from_difference_family(Zmod(21), [[0, 1, 4, 14, 16]], check=False) elif v == 41: from sage.rings.finite_rings.integer_mod_ring import Zmod bibd = BIBD_from_difference_family( Zmod(41), [[0, 1, 4, 11, 29], [0, 2, 8, 17, 22]], check=False) elif v == 61: from sage.rings.finite_rings.integer_mod_ring import Zmod bibd = BIBD_from_difference_family( Zmod(61), [[0, 1, 3, 13, 34], [0, 4, 9, 23, 45], [0, 6, 17, 24, 32]], check=False) elif v == 81: from sage.groups.additive_abelian.additive_abelian_group import AdditiveAbelianGroup D = [[(0, 0, 0, 1), (2, 0, 0, 1), (0, 0, 2, 1), (1, 2, 0, 2), (0, 1, 1, 1)], [(0, 0, 1, 0), (1, 1, 0, 2), (0, 2, 1, 0), (1, 2, 0, 1), (1, 1, 1, 0)], [(2, 2, 1, 1), (1, 2, 2, 2), (2, 0, 1, 2), (0, 1, 2, 1), (1, 1, 0, 0)], [(0, 2, 0, 2), (1, 1, 0, 1), (1, 2, 1, 2), (1, 2, 1, 0), (0, 2, 1, 1)]] bibd = BIBD_from_difference_family(AdditiveAbelianGroup([3] * 4), D, check=False) elif v == 161: # VI.16.16 of the Handbook of Combinatorial Designs, Second Edition D = [(0, 19, 34, 73, 80), (0, 16, 44, 71, 79), (0, 12, 33, 74, 78), (0, 13, 30, 72, 77), (0, 11, 36, 67, 76), (0, 18, 32, 69, 75), (0, 10, 48, 68, 70), (0, 3, 29, 52, 53)] from sage.rings.finite_rings.integer_mod_ring import Zmod bibd = BIBD_from_difference_family(Zmod(161), D, check=False) elif v == 281: from sage.rings.finite_rings.integer_mod_ring import Zmod D = [[3**(2 * a + 56 * b) for b in range(5)] for a in range(14)] bibd = BIBD_from_difference_family(Zmod(281), D, check=False) # Lemma 29 elif v == 165: bibd = BIBD_from_PBD(v_5_1_BIBD(41, check=False), 165, 5, check=False) elif v == 181: bibd = BIBD_from_PBD(v_5_1_BIBD(45, check=False), 181, 5, check=False) elif v in (201, 285, 301, 401, 421, 425): # Call directly the BIBD_from_TD function bibd = BIBD_from_TD(v, 5) # Lemma 30 elif v == 141: # VI.16.16 of the Handbook of Combinatorial Designs, Second Edition from sage.rings.finite_rings.integer_mod_ring import Zmod D = [(0, 33, 60, 92, 97), (0, 3, 45, 88, 110), (0, 18, 39, 68, 139), (0, 12, 67, 75, 113), (0, 1, 15, 84, 94), (0, 7, 11, 24, 30), (0, 36, 90, 116, 125)] bibd = BIBD_from_difference_family(Zmod(141), D, check=False) # Theorem 31.2 elif (v - 1) // 4 in [ 80, 81, 85, 86, 90, 91, 95, 96, 110, 111, 115, 116, 120, 121, 250, 251, 255, 256, 260, 261, 265, 266, 270, 271 ]: r = (v - 1) // 4 if r <= 96: k, t, u = 5, 16, r - 80 elif r <= 121: k, t, u = 10, 11, r - 110 else: k, t, u = 10, 25, r - 250 bibd = BIBD_from_PBD(PBD_from_TD(k, t, u), v, 5, check=False) else: r, s, t, u = _get_r_s_t_u(v) bibd = BIBD_from_PBD(PBD_from_TD(5, t, u), v, 5, check=False) if check: _check_pbd(bibd, v, [5]) return bibd
def BalancedIncompleteBlockDesign(v, k, existence=False, use_LJCR=False): r""" Returns a BIBD of parameters `v,k`. A Balanced Incomplete Block Design of parameters `v,k` is a collection `\mathcal C` of `k`-subsets of `V=\{0,\dots,v-1\}` such that for any two distinct elements `x,y\in V` there is a unique element `S\in \mathcal C` such that `x,y\in S`. More general definitions sometimes involve a `\lambda` parameter, and we assume here that `\lambda=1`. For more information on BIBD, see the :wikipedia:`corresponding Wikipedia entry <Block_design#Definition_of_a_BIBD_.28or_2-design.29>`. INPUT: - ``v,k`` (integers) - ``existence`` (boolean) -- instead of building the design, returns: - ``True`` -- meaning that Sage knows how to build the design - ``Unknown`` -- meaning that Sage does not know how to build the design, but that the design may exist (see :mod:`sage.misc.unknown`). - ``False`` -- meaning that the design does not exist. - ``use_LJCR`` (boolean) -- whether to query the La Jolla Covering Repository for the design when Sage does not know how to build it (see :meth:`~sage.combinat.designs.covering_design.best_known_covering_design_www`). This requires internet. .. SEEALSO:: * :func:`steiner_triple_system` * :func:`v_4_1_BIBD` * :func:`v_5_1_BIBD` TODO: * Implement other constructions from the Handbook of Combinatorial Designs. EXAMPLES:: sage: designs.BalancedIncompleteBlockDesign(7,3).blocks() [[0, 1, 3], [0, 2, 4], [0, 5, 6], [1, 2, 6], [1, 4, 5], [2, 3, 5], [3, 4, 6]] sage: B = designs.BalancedIncompleteBlockDesign(21,5, use_LJCR=True) # optional - internet sage: B # optional - internet Incidence structure with 21 points and 21 blocks sage: B.blocks() # optional - internet [[0, 1, 2, 3, 20], [0, 4, 8, 12, 16], [0, 5, 10, 15, 19], [0, 6, 11, 13, 17], [0, 7, 9, 14, 18], [1, 4, 11, 14, 19], [1, 5, 9, 13, 16], [1, 6, 8, 15, 18], [1, 7, 10, 12, 17], [2, 4, 9, 15, 17], [2, 5, 11, 12, 18], [2, 6, 10, 14, 16], [2, 7, 8, 13, 19], [3, 4, 10, 13, 18], [3, 5, 8, 14, 17], [3, 6, 9, 12, 19], [3, 7, 11, 15, 16], [4, 5, 6, 7, 20], [8, 9, 10, 11, 20], [12, 13, 14, 15, 20], [16, 17, 18, 19, 20]] sage: designs.BalancedIncompleteBlockDesign(20,5, use_LJCR=True) # optional - internet Traceback (most recent call last): ... ValueError: No such design exists ! sage: designs.BalancedIncompleteBlockDesign(16,6) Traceback (most recent call last): ... NotImplementedError: I don't know how to build this design. TESTS:: sage: designs.BalancedIncompleteBlockDesign(85,5,existence=True) True sage: _ = designs.BalancedIncompleteBlockDesign(85,5) A BIBD from a Finite Projective Plane:: sage: _ = designs.BalancedIncompleteBlockDesign(21,5) Some trivial BIBD:: sage: designs.BalancedIncompleteBlockDesign(10,10) Incidence structure with 10 points and 1 blocks sage: designs.BalancedIncompleteBlockDesign(1,10) Incidence structure with 1 points and 0 blocks Existence of BIBD with `k=3,4,5`:: sage: [v for v in xrange(50) if designs.BalancedIncompleteBlockDesign(v,3,existence=True)] [1, 3, 7, 9, 13, 15, 19, 21, 25, 27, 31, 33, 37, 39, 43, 45, 49] sage: [v for v in xrange(100) if designs.BalancedIncompleteBlockDesign(v,4,existence=True)] [1, 4, 13, 16, 25, 28, 37, 40, 49, 52, 61, 64, 73, 76, 85, 88, 97] sage: [v for v in xrange(150) if designs.BalancedIncompleteBlockDesign(v,5,existence=True)] [1, 5, 21, 25, 41, 45, 61, 65, 81, 85, 101, 105, 121, 125, 141, 145] For `k > 5` there are currently very few constructions:: sage: [v for v in xrange(150) if designs.BalancedIncompleteBlockDesign(v,6,existence=True) is True] [1, 6, 31] sage: [v for v in xrange(150) if designs.BalancedIncompleteBlockDesign(v,6,existence=True) is Unknown] [16, 21, 36, 46, 51, 61, 66, 76, 81, 91, 96, 106, 111, 121, 126, 136, 141] """ if v == 1: if existence: return True return BlockDesign(v, [], test=False) if k == v: if existence: return True return BlockDesign(v, [range(v)], test=False) if v < k or k < 2 or (v - 1) % (k - 1) != 0 or (v * (v - 1)) % (k * (k - 1)) != 0: if existence: return False raise EmptySetError("No such design exists !") if k == 2: if existence: return True from itertools import combinations return BlockDesign(v, combinations(range(v), 2), test=False) if k == 3: if existence: return v % 6 == 1 or v % 6 == 3 return steiner_triple_system(v) if k == 4: if existence: return v % 12 == 1 or v % 12 == 4 return BlockDesign(v, v_4_1_BIBD(v), test=False) if k == 5: if existence: return v % 20 == 1 or v % 20 == 5 return BlockDesign(v, v_5_1_BIBD(v), test=False) if BIBD_from_TD(v, k, existence=True): if existence: return True return BlockDesign(v, BIBD_from_TD(v, k)) if v == (k - 1)**2 + k and is_prime_power(k - 1): if existence: return True from block_design import projective_plane return projective_plane(k - 1) if use_LJCR: from covering_design import best_known_covering_design_www B = best_known_covering_design_www(v, k, 2) # Is it a BIBD or just a good covering ? expected_n_of_blocks = binomial(v, 2) / binomial(k, 2) if B.low_bd() > expected_n_of_blocks: if existence: return False raise EmptySetError("No such design exists !") B = B.incidence_structure() if len(B.blcks) == expected_n_of_blocks: if existence: return True else: return B if existence: return Unknown else: raise NotImplementedError("I don't know how to build this design.")
def projective_plane(n, check=True, existence=False): r""" Returns a projective plane of order ``n`` as a 2-design. A finite projective plane is a 2-design with `n^2+n+1` lines (or blocks) and `n^2+n+1` points. For more information on finite projective planes, see the :wikipedia:`Projective_plane#Finite_projective_planes`. If no construction is possible, then the function raises a ``EmptySetError`` whereas if no construction is available the function raises a ``NotImplementedError``. INPUT: - ``n`` -- the finite projective plane's order EXAMPLES:: sage: designs.projective_plane(2) Incidence structure with 7 points and 7 blocks sage: designs.projective_plane(3) Incidence structure with 13 points and 13 blocks sage: designs.projective_plane(4) Incidence structure with 21 points and 21 blocks sage: designs.projective_plane(5) Incidence structure with 31 points and 31 blocks sage: designs.projective_plane(6) Traceback (most recent call last): ... EmptySetError: By the Ryser-Chowla theorem, no projective plane of order 6 exists. sage: designs.projective_plane(10) Traceback (most recent call last): ... EmptySetError: No projective plane of order 10 exists by C. Lam, L. Thiel and S. Swiercz "The nonexistence of finite projective planes of order 10" (1989), Canad. J. Math. sage: designs.projective_plane(12) Traceback (most recent call last): ... NotImplementedError: If such a projective plane exists, we do not know how to build it. sage: designs.projective_plane(14) Traceback (most recent call last): ... EmptySetError: By the Ryser-Chowla theorem, no projective plane of order 14 exists. TESTS:: sage: designs.projective_plane(2197, existence=True) True sage: designs.projective_plane(6, existence=True) False sage: designs.projective_plane(10, existence=True) False sage: designs.projective_plane(12, existence=True) Unknown """ from sage.rings.arith import is_prime_power, two_squares if n <= 1: if existence: return False raise EmptySetError("There is no projective plane of order <= 1") if n == 10: if existence: return False ref = ("C. Lam, L. Thiel and S. Swiercz \"The nonexistence of finite " "projective planes of order 10\" (1989), Canad. J. Math.") raise EmptySetError("No projective plane of order 10 exists by %s" % ref) if (n % 4) in [1, 2]: try: two_squares(n) except ValueError: if existence: return False raise EmptySetError("By the Ryser-Chowla theorem, no projective" " plane of order " + str(n) + " exists.") if not is_prime_power(n): if existence: return Unknown raise NotImplementedError("If such a projective plane exists, we do " "not know how to build it.") if existence: return True else: return DesarguesianProjectivePlaneDesign(n, check=check)
def OA_and_oval(q): r""" Return a `OA(q+1,q)` whose blocks contains `\leq 2` zeroes in the last `q` columns. This `OA` is build from a projective plane of order `q`, in which there exists an oval `O` of size `q+1` (i.e. a set of `q+1` points no three of which are [colinear/contained in a common set of the projective plane]). Removing an element `x\in O` and all sets that contain it, we obtain a `TD(q+1,q)` in which `O` intersects all columns except one. As `O` is an oval, no block of the `TD` intersects it more than twice. INPUT: - ``q`` -- a prime power .. NOTE:: This function is called by :func:`construction_3_6`, an implementation of Construction 3.6 from [AC07]_. EXAMPLES:: sage: from sage.combinat.designs.orthogonal_arrays_recursive import OA_and_oval sage: _ = OA_and_oval """ from sage.rings.arith import is_prime_power from sage.combinat.designs.block_design import projective_plane from orthogonal_arrays import OA_relabel assert is_prime_power(q) B = projective_plane(q, check=False) # We compute the oval with a linear program from sage.numerical.mip import MixedIntegerLinearProgram p = MixedIntegerLinearProgram() b = p.new_variable(binary=True) V = B.ground_set() p.add_constraint(p.sum([b[i] for i in V]) == q+1) for bl in B: p.add_constraint(p.sum([b[i] for i in bl]) <= 2) p.solve() b = p.get_values(b) oval = [x for x,i in b.items() if i] assert len(oval) == q+1 # We remove one element from the oval x = oval.pop() oval.sort() # We build the TD by relabelling the point set, and removing those which # contain x. r = {} B = list(B) # (this is to make sure that the first set containing x in B is the one # which contains no other oval point) B.sort(key=lambda b:int(any([xx in oval for xx in b]))) BB = [] for b in B: if x in b: for xx in b: if xx == x: continue r[xx] = len(r) else: BB.append(b) assert len(r) == (q+1)*q # all points except x have an image assert len(set(r.values())) == len(r) # the images are different # Relabelling/sorting the blocks and the oval BB = [[r[xx] for xx in b] for b in BB] oval = [r[xx] for xx in oval] for b in BB: b.sort() oval.sort() # Turning the TD into an OA BB = [[xx%q for xx in b] for b in BB] oval = [xx%q for xx in oval] assert len(oval) == q # We relabel the "oval" as relabelled as [0,...,0] OA = OA_relabel(BB+([[0]+oval]),q+1,q,blocks=[[0]+oval]) OA = [[(x+1)%q for x in B] for B in OA] OA.remove([0]*(q+1)) assert all(sum([xx == 0 for xx in b[1:]]) <= 2 for b in OA) return OA
def T2starGeneralizedQuadrangleGraph(q, dual=False, hyperoval=None, field=None, check_hyperoval=True): r""" Return the collinearity graph of the generalized quadrangle `T_2^*(q)`, or of its dual Let `q=2^k` and `\Theta=PG(3,q)`. `T_2^*(q)` is a generalized quadrangle [GQwiki]_ of order `(q-1,q+1)`, see 3.1.3 in [PT09]_. Fix a plane `\Pi \subset \Theta` and a `hyperoval <http://en.wikipedia.org/wiki/Oval_(projective_plane)#Even_q>`__ `O \subset \Pi`. The points of `T_2^*(q):=T_2^*(O)` are the points of `\Theta` outside `\Pi`, and the lines are the lines of `\Theta` outside `\Pi` that meet `\Pi` in a point of `O`. INPUT: - ``q`` -- a power of two - ``dual`` -- if ``False`` (default), return the graph of `T_2^*(O)`. Otherwise return the graph of the dual `T_2^*(O)`. - ``hyperoval`` -- a hyperoval (i.e. a complete 2-arc; a set of points in the plane meeting every line in 0 or 2 points) in the plane of points with 0th coordinate 0 in `PG(3,q)` over the field ``field``. Each point of ``hyperoval`` must be a length 4 vector over ``field`` with 1st non-0 coordinate equal to 1. By default, ``hyperoval`` and ``field`` are not specified, and constructed on the fly. In particular, ``hyperoval`` we build is the classical one, i.e. a conic with the point of intersection of its tangent lines. - ``field`` -- an instance of a finite field of order `q`, must be provided if ``hyperoval`` is provided. - ``check_hyperoval`` -- (default: ``True``) if ``True``, check ``hyperoval`` for correctness. EXAMPLES: using the built-in construction:: sage: g=graphs.T2starGeneralizedQuadrangleGraph(4); g T2*(O,4); GQ(3, 5): Graph on 64 vertices sage: g.is_strongly_regular(parameters=True) (64, 18, 2, 6) sage: g=graphs.T2starGeneralizedQuadrangleGraph(4,dual=True); g T2*(O,4)*; GQ(5, 3): Graph on 96 vertices sage: g.is_strongly_regular(parameters=True) (96, 20, 4, 4) supplying your own hyperoval:: sage: F=GF(4,'b') sage: O=[vector(F,(0,0,0,1)),vector(F,(0,0,1,0))]+map(lambda x: vector(F, (0,1,x^2,x)),F) sage: g=graphs.T2starGeneralizedQuadrangleGraph(4, hyperoval=O, field=F); g T2*(O,4); GQ(3, 5): Graph on 64 vertices sage: g.is_strongly_regular(parameters=True) (64, 18, 2, 6) TESTS:: sage: F=GF(4,'b') # repeating a point... sage: O=[vector(F,(0,1,0,0)),vector(F,(0,0,1,0))]+map(lambda x: vector(F, (0,1,x^2,x)),F) sage: graphs.T2starGeneralizedQuadrangleGraph(4, hyperoval=O, field=F) Traceback (most recent call last): ... RuntimeError: incorrect hyperoval size sage: O=[vector(F,(0,1,1,0)),vector(F,(0,0,1,0))]+map(lambda x: vector(F, (0,1,x^2,x)),F) sage: graphs.T2starGeneralizedQuadrangleGraph(4, hyperoval=O, field=F) Traceback (most recent call last): ... RuntimeError: incorrect hyperoval """ from sage.combinat.designs.incidence_structures import IncidenceStructure from sage.combinat.designs.block_design import ProjectiveGeometryDesign as PG from sage.modules.free_module_element import free_module_element as vector p, k = is_prime_power(q,get_data=True) if k==0 or p!=2: raise ValueError('q must be a power of 2') if field is None: F = FiniteField(q, 'a') else: F = field Theta = PG(3, 1, F, point_coordinates=1) Pi = set(filter(lambda x: x[0]==F.zero(), Theta.ground_set())) if hyperoval is None: O = filter(lambda x: x[1]+x[2]*x[3]==0 or (x[1]==1 and x[2]==0 and x[3]==0), Pi) O = set(O) else: map(lambda x: x.set_immutable(), hyperoval) O = set(hyperoval) if check_hyperoval: if len(O) != q+2: raise RuntimeError("incorrect hyperoval size") for L in Theta.blocks(): if set(L).issubset(Pi): if not len(O.intersection(L)) in [0,2]: raise RuntimeError("incorrect hyperoval") L = map(lambda z: filter(lambda y: not y in O, z), filter(lambda x: len(O.intersection(x)) == 1, Theta.blocks())) if dual: G = IncidenceStructure(L).intersection_graph() G.name('T2*(O,'+str(q)+')*; GQ'+str((q+1,q-1))) else: G = IncidenceStructure(L).dual().intersection_graph() G.name('T2*(O,'+str(q)+'); GQ'+str((q-1,q+1))) return G
def BalancedIncompleteBlockDesign(v, k, use_LJCR=False): r""" Returns a BIBD of parameters `v,k`. A Balanced Incomplete Block Design of parameters `v,k` is a collection `\mathcal C` of `k`-subsets of `V=\{0,\dots,v-1\}` such that for any two distinct elements `x,y\in V` there is a unique element `S\in \mathcal C` such that `x,y\in S`. More general definitions sometimes involve a `\lambda` parameter, and we assume here that `\lambda=1`. For more information on BIBD, see the :wikipedia:`corresponding Wikipedia entry <Block_design#Definition_of_a_BIBD_.28or_2-design.29>`. INPUT: - ``v,k`` (integers) - ``use_LJCR`` (boolean) -- whether to query the La Jolla Covering Repository for the design when Sage does not know how to build it (see :meth:`~sage.combinat.designs.covering_design.best_known_covering_design_www`). This requires internet. .. SEEALSO:: * :meth:`steiner_triple_system` * :meth:`v_4_1_BIBD` TODO: * Implement `(v,5,1)`-BIBD using `this text <http://www.argilo.net/files/bibd.pdf>`_. * Implement other constructions from the Handbook of Combinatorial Designs. EXAMPLES:: sage: designs.BalancedIncompleteBlockDesign(7,3).blocks() [[0, 1, 3], [0, 2, 4], [0, 5, 6], [1, 2, 6], [1, 4, 5], [2, 3, 5], [3, 4, 6]] sage: B = designs.BalancedIncompleteBlockDesign(21,5, use_LJCR=True) # optional - internet sage: B # optional - internet Incidence structure with 21 points and 21 blocks sage: B.blocks() # optional - internet [[0, 1, 2, 3, 20], [0, 4, 8, 12, 16], [0, 5, 10, 15, 19], [0, 6, 11, 13, 17], [0, 7, 9, 14, 18], [1, 4, 11, 14, 19], [1, 5, 9, 13, 16], [1, 6, 8, 15, 18], [1, 7, 10, 12, 17], [2, 4, 9, 15, 17], [2, 5, 11, 12, 18], [2, 6, 10, 14, 16], [2, 7, 8, 13, 19], [3, 4, 10, 13, 18], [3, 5, 8, 14, 17], [3, 6, 9, 12, 19], [3, 7, 11, 15, 16], [4, 5, 6, 7, 20], [8, 9, 10, 11, 20], [12, 13, 14, 15, 20], [16, 17, 18, 19, 20]] sage: designs.BalancedIncompleteBlockDesign(20,5, use_LJCR=True) # optional - internet Traceback (most recent call last): ... ValueError: No such design exists ! TESTS: A BIBD from a Finite Projective Plane:: sage: _ = designs.BalancedIncompleteBlockDesign(21,5) """ if ((binomial(v, 2) % binomial(k, 2) != 0) or (v - 1) % (k - 1) != 0): raise ValueError("No such design exists !") if k == 2: from itertools import combinations return BlockDesign(v, combinations(range(v), 2), test=False) if k == 3: return steiner_triple_system(v) if k == 4: return BlockDesign(v, v_4_1_BIBD(v), test=False) if v == (k - 1)**2 + k and is_prime_power(k - 1): from block_design import ProjectivePlaneDesign return ProjectivePlaneDesign(k - 1) if use_LJCR: from covering_design import best_known_covering_design_www B = best_known_covering_design_www(v, k, 2) # Is it a BIBD or just a good covering ? expected_n_of_blocks = binomial(v, 2) / binomial(k, 2) if B.low_bd() > expected_n_of_blocks: raise ValueError("No such design exists !") B = B.incidence_structure() if len(B.blcks) == expected_n_of_blocks: return B raise ValueError("I don't know how to build this design.")
def v_5_1_BIBD(v, check=True): r""" Return a `(v,5,1)`-BIBD. This method follows the constuction from [ClaytonSmith]_. INPUT: - ``v`` (integer) .. SEEALSO:: * :func:`balanced_incomplete_block_design` EXAMPLES:: sage: from sage.combinat.designs.bibd import v_5_1_BIBD sage: i = 0 sage: while i<200: ....: i += 20 ....: _ = v_5_1_BIBD(i+1) ....: _ = v_5_1_BIBD(i+5) TESTS: Check that the needed difference families are there:: sage: for v in [21,41,61,81,141,161,281]: ....: assert designs.difference_family(v,5,existence=True) ....: _ = designs.difference_family(v,5) """ v = int(v) assert (v > 1) assert (v%20 == 5 or v%20 == 1) # note: equivalent to (v-1)%4 == 0 and (v*(v-1))%20 == 0 # Lemma 27 if v%5 == 0 and (v//5)%4 == 1 and is_prime_power(v//5): bibd = BIBD_5q_5_for_q_prime_power(v//5) # Lemma 28 elif v in [21,41,61,81,141,161,281]: from difference_family import difference_family G,D = difference_family(v,5) bibd = BIBD_from_difference_family(G, D, check=False) # Lemma 29 elif v == 165: bibd = BIBD_from_PBD(v_5_1_BIBD(41,check=False),165,5,check=False) elif v == 181: bibd = BIBD_from_PBD(v_5_1_BIBD(45,check=False),181,5,check=False) elif v in (201,285,301,401,421,425): # Call directly the BIBD_from_TD function # note: there are (201,5,1) and (421,5)-difference families that can be # obtained from the general constructor bibd = BIBD_from_TD(v,5) # Theorem 31.2 elif (v-1)//4 in [80, 81, 85, 86, 90, 91, 95, 96, 110, 111, 115, 116, 120, 121, 250, 251, 255, 256, 260, 261, 265, 266, 270, 271]: r = (v-1)//4 if r <= 96: k,t,u = 5, 16, r-80 elif r <= 121: k,t,u = 10, 11, r-110 else: k,t,u = 10, 25, r-250 bibd = BIBD_from_PBD(PBD_from_TD(k,t,u),v,5,check=False) else: r,s,t,u = _get_r_s_t_u(v) bibd = BIBD_from_PBD(PBD_from_TD(5,t,u),v,5,check=False) if check: assert is_pairwise_balanced_design(bibd,v,[5]) return bibd
def kirkman_triple_system(v, existence=False): r""" Return a Kirkman Triple System on `v` points. A Kirkman Triple System `KTS(v)` is a resolvable Steiner Triple System. It exists if and only if `v\equiv 3\pmod{6}`. INPUT: - `n` (integer) - ``existence`` (boolean; ``False`` by default) -- whether to build the `KTS(n)` or only answer whether it exists. .. SEEALSO:: :meth:`IncidenceStructure.is_resolvable` EXAMPLES: A solution to Kirkmman's original problem:: sage: kts = designs.kirkman_triple_system(15) sage: classes = kts.is_resolvable(1)[1] sage: names = '0123456789abcde' sage: to_name = lambda (r,s,t): ' '+names[r]+names[s]+names[t]+' ' sage: rows = [join(('Day {}'.format(i) for i in range(1,8)), ' ')] sage: rows.extend(join(map(to_name,row), ' ') for row in zip(*classes)) sage: print join(rows,'\n') Day 1 Day 2 Day 3 Day 4 Day 5 Day 6 Day 7 07e 18e 29e 3ae 4be 5ce 6de 139 24a 35b 46c 05d 167 028 26b 03c 14d 257 368 049 15a 458 569 06a 01b 12c 23d 347 acd 7bd 78c 89d 79a 8ab 9bc TESTS:: sage: for i in range(3,300,6): ....: _ = designs.kirkman_triple_system(i) """ if v % 6 != 3: if existence: return False raise ValueError("There is no KTS({}) as v!=3 mod(6)".format(v)) if existence: return False elif v == 3: return BalancedIncompleteBlockDesign(3, [[0, 1, 2]], k=3, lambd=1) elif v == 9: classes = [[[0, 1, 5], [2, 6, 7], [3, 4, 8]], [[1, 6, 8], [3, 5, 7], [0, 2, 4]], [[1, 4, 7], [0, 3, 6], [2, 5, 8]], [[4, 5, 6], [0, 7, 8], [1, 2, 3]]] KTS = BalancedIncompleteBlockDesign( v, [tr for cl in classes for tr in cl], k=3, lambd=1, copy=False) KTS._classes = classes return KTS # Construction 1.1 from [Stinson91] (originally Theorem 6 from [RCW71]) # # For all prime powers q=1 mod 6, there exists a KTS(2q+1) elif ((v - 1) // 2) % 6 == 1 and is_prime_power((v - 1) // 2): from sage.rings.finite_rings.constructor import FiniteField as GF q = (v - 1) // 2 K = GF(q, 'x') a = K.primitive_element() t = (q - 1) / 6 # m is the solution of a^m=(a^t+1)/2 from sage.groups.generic import discrete_log m = discrete_log((a**t + 1) / 2, a) assert 2 * a**m == a**t + 1 # First parallel class first_class = [[(0, 1), (0, 2), 'inf']] b0 = K.one() b1 = a**t b2 = a**m first_class.extend([(b0 * a**i, 1), (b1 * a**i, 1), (b2 * a**i, 2)] for i in range(t) + range(2 * t, 3 * t) + range(4 * t, 5 * t)) b0 = a**(m + t) b1 = a**(m + 3 * t) b2 = a**(m + 5 * t) first_class.extend([[(b0 * a**i, 2), (b1 * a**i, 2), (b2 * a**i, 2)] for i in range(t)]) # Action of K on the points action = lambda v, x: (v + x[0], x[1]) if len(x) == 2 else x # relabel to integer relabel = {(p, x): i + (x - 1) * q for i, p in enumerate(K) for x in [1, 2]} relabel['inf'] = 2 * q classes = [[[relabel[action(p, x)] for x in tr] for tr in first_class] for p in K] KTS = BalancedIncompleteBlockDesign( v, [tr for cl in classes for tr in cl], k=3, lambd=1, copy=False) KTS._classes = classes return KTS # Construction 1.2 from [Stinson91] (originally Theorem 5 from [RCW71]) # # For all prime powers q=1 mod 6, there exists a KTS(3q) elif (v // 3) % 6 == 1 and is_prime_power(v // 3): from sage.rings.finite_rings.constructor import FiniteField as GF q = v // 3 K = GF(q, 'x') a = K.primitive_element() t = (q - 1) / 6 A0 = [(0, 0), (0, 1), (0, 2)] B = [[(a**i, j), (a**(i + 2 * t), j), (a**(i + 4 * t), j)] for j in range(3) for i in range(t)] A = [[(a**i, 0), (a**(i + 2 * t), 1), (a**(i + 4 * t), 2)] for i in range(6 * t)] # Action of K on the points action = lambda v, x: (v + x[0], x[1]) # relabel to integer relabel = {(p, j): i + j * q for i, p in enumerate(K) for j in range(3)} B0 = [A0] + B + A[t:2 * t] + A[3 * t:4 * t] + A[5 * t:6 * t] # Classes classes = [[[relabel[action(p, x)] for x in tr] for tr in B0] for p in K] for i in range(t) + range(2 * t, 3 * t) + range(4 * t, 5 * t): classes.append([[relabel[action(p, x)] for x in A[i]] for p in K]) KTS = BalancedIncompleteBlockDesign( v, [tr for cl in classes for tr in cl], k=3, lambd=1, copy=False) KTS._classes = classes return KTS else: # This is Lemma IX.6.4 from [BJL99]. # # This construction takes a (v,{4,7})-PBD. All points are doubled (x has # a copy x'), and an infinite point \infty is added. # # On all blocks of 2*4 points we "paste" a KTS(2*4+1) using the infinite # point, in such a way that all {x,x',infty} are set of the design. We # do the same for blocks with 2*7 points using a KTS(2*7+1). # # Note that the triples of points equal to {x,x',\infty} will be added # several times. # # As all those subdesigns are resolvable, each class of the KTS(n) is # obtained by considering a set {x,x',\infty} and all sets of all # parallel classes of the subdesign which contain this set. # We create the small KTS(n') we need, and relabel them such that # 01(n'-1),23(n'-1),... are blocks of the design. gdd4 = kirkman_triple_system(9) gdd7 = kirkman_triple_system(15) X = [B for B in gdd4 if 8 in B] for b in X: b.remove(8) X = sum(X, []) + [8] gdd4.relabel({v: i for i, v in enumerate(X)}) gdd4 = gdd4.is_resolvable(True)[1] # the relabeled classes X = [B for B in gdd7 if 14 in B] for b in X: b.remove(14) X = sum(X, []) + [14] gdd7.relabel({v: i for i, v in enumerate(X)}) gdd7 = gdd7.is_resolvable(True)[1] # the relabeled classes # The first parallel class contains 01(n'-1), the second contains # 23(n'-1), etc.. # Then remove the blocks containing (n'-1) for B in gdd4: for i, b in enumerate(B): if 8 in b: j = min(b) del B[i] B.insert(0, j) break gdd4.sort() for B in gdd4: B.pop(0) for B in gdd7: for i, b in enumerate(B): if 14 in b: j = min(b) del B[i] B.insert(0, j) break gdd7.sort() for B in gdd7: B.pop(0) # Pasting the KTS(n') without {x,x',\infty} blocks classes = [[] for i in range((v - 1) / 2)] gdd = {4: gdd4, 7: gdd7} for B in PBD_4_7((v - 1) // 2, check=False): for i, classs in enumerate(gdd[len(B)]): classes[B[i]].extend([[2 * B[x // 2] + x % 2 for x in BB] for BB in classs]) # The {x,x',\infty} blocks for i, classs in enumerate(classes): classs.append([2 * i, 2 * i + 1, v - 1]) KTS = BalancedIncompleteBlockDesign( v, blocks=[tr for cl in classes for tr in cl], k=3, lambd=1, check=True, copy=False) KTS._classes = classes assert KTS.is_resolvable() return KTS
def mutually_orthogonal_latin_squares(n, k=None, partitions=False): r""" Returns `k` Mutually Orthogonal `n\times n` Latin Squares (MOLS). For more information on Latin Squares and MOLS, see :mod:`~sage.combinat.designs.latin_squares` or the :wikipedia:`Latin_square`, or even the :wikipedia:`Wikipedia entry on MOLS <Graeco-Latin_square#Mutually_orthogonal_Latin_squares>`. INPUT: - ``n`` (integer) -- size of the latin square. - ``k`` (integer) -- returns `k` MOLS. If set to ``None`` (default), returns the maximum number of MOLS that Sage can build. .. WARNING:: This has no reason to be the maximum number of `n\times n` MOLS, just the best Sage can do ! - ``partition`` (boolean) -- a Latin Square can be seen as 3 partitions of the `n^2` cells of the array into `n` sets of size `n`, respectively : * The partition of rows * The partition of columns * The partition of number (cells numbered with 0, cells numbered with 1, ...) These partitions have the additional property that any two sets from different partitions intersect on exactly one element. When ``partition`` is set to ``True``, this function returns a list of `k+2` partitions satisfying this intersection property instead of the `k+2` MOLS (though the data is exactly the same in both cases). EXAMPLES:: sage: designs.mutually_orthogonal_latin_squares(5) [ [0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4] [3 0 1 4 2] [4 3 0 2 1] [1 2 4 0 3] [2 4 3 1 0] [4 3 0 2 1] [1 2 4 0 3] [2 4 3 1 0] [3 0 1 4 2] [1 2 4 0 3] [2 4 3 1 0] [3 0 1 4 2] [4 3 0 2 1] [2 4 3 1 0], [3 0 1 4 2], [4 3 0 2 1], [1 2 4 0 3] ] sage: designs.mutually_orthogonal_latin_squares(7,3) [ [0 1 2 3 4 5 6] [0 1 2 3 4 5 6] [0 1 2 3 4 5 6] [4 0 3 1 6 2 5] [5 6 0 4 2 1 3] [6 4 1 0 5 3 2] [5 6 0 4 2 1 3] [6 4 1 0 5 3 2] [1 3 5 2 0 6 4] [6 4 1 0 5 3 2] [1 3 5 2 0 6 4] [2 5 4 6 3 0 1] [1 3 5 2 0 6 4] [2 5 4 6 3 0 1] [3 2 6 5 1 4 0] [2 5 4 6 3 0 1] [3 2 6 5 1 4 0] [4 0 3 1 6 2 5] [3 2 6 5 1 4 0], [4 0 3 1 6 2 5], [5 6 0 4 2 1 3] ] sage: designs.mutually_orthogonal_latin_squares(5,2,partitions=True) [[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19], [20, 21, 22, 23, 24]], [[0, 5, 10, 15, 20], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]], [[0, 6, 12, 18, 24], [1, 7, 14, 15, 23], [2, 9, 13, 16, 20], [3, 5, 11, 19, 22], [4, 8, 10, 17, 21]], [[0, 7, 13, 19, 21], [1, 9, 10, 18, 22], [2, 8, 11, 15, 24], [3, 6, 14, 17, 20], [4, 5, 12, 16, 23]]] TESTS:: sage: designs.mutually_orthogonal_latin_squares(5,5) Traceback (most recent call last): ... ValueError: There exist at most n-1 MOLS of size n. """ from sage.rings.finite_rings.constructor import FiniteField from sage.combinat.designs.block_design import AffineGeometryDesign from sage.rings.arith import is_prime_power from sage.matrix.constructor import Matrix from sage.rings.arith import factor if k is not None and k >= n: raise ValueError("There exist at most n-1 MOLS of size n.") if is_prime_power(n): if k is None: k = n - 1 # Section 6.4.1 of [Stinson2004] Fp = FiniteField(n, 'x') B = AffineGeometryDesign(2, 1, Fp).blocks() parallel_classes = [[] for _ in range(k + 2)] for b in B: for p in parallel_classes: if (not p) or all(i not in p[0] for i in b): p.append(b) break if partitions: return parallel_classes coord = {v: i for i, L in enumerate(parallel_classes[0]) for v in L} coord = { v: (coord[v], i) for i, L in enumerate(parallel_classes[1]) for v in L } matrices = [] for P in parallel_classes[2:]: matrices.append( Matrix({coord[v]: i for i, L in enumerate(P) for v in L})) return matrices else: # Theorem 6.33 of [Stinson2004], MacNeish's theorem. subcases = [p**i for p, i in factor(n)] s = min(subcases) - 1 if k is None: k = s elif k > s: raise NotImplementedError("I don't know how to build these MOLS.") subcalls = [mutually_orthogonal_latin_squares(p, k) for p in subcases] matrices = [ latin_square_product(*[sc[i] for sc in subcalls]) for i in range(k) ] if partitions: partitions = [[[i * n + j for j in range(n)] for i in range(n)], [[j * n + i for j in range(n)] for i in range(n)]] for m in matrices: partition = [[] for i in range(n)] for i in range(n): for j in range(n): partition[m[i, j]].append(i * n + j) partitions.append(partition) return partitions else: return matrices
def regular_symmetric_hadamard_matrix_with_constant_diagonal(n,e,existence=False): r""" Return a Regular Symmetric Hadamard Matrix with Constant Diagonal. A Hadamard matrix is said to be *regular* if its rows all sum to the same value. When `\epsilon\in\{-1,+1\}`, we say that `M` is a `(n,\epsilon)-RSHCD` if `M` is a regular symmetric Hadamard matrix with constant diagonal `\delta\in\{-1,+1\}` and row values all equal to `\delta \epsilon \sqrt(n)`. For more information, see [HX10]_ or 10.5.1 in [BH12]_. INPUT: - ``n`` (integer) -- side of the matrix - ``e`` -- one of `-1` or `+1`, equal to the value of `\epsilon` EXAMPLES:: sage: from sage.combinat.matrices.hadamard_matrix import regular_symmetric_hadamard_matrix_with_constant_diagonal sage: regular_symmetric_hadamard_matrix_with_constant_diagonal(4,1) [ 1 1 1 -1] [ 1 1 -1 1] [ 1 -1 1 1] [-1 1 1 1] sage: regular_symmetric_hadamard_matrix_with_constant_diagonal(4,-1) [ 1 -1 -1 -1] [-1 1 -1 -1] [-1 -1 1 -1] [-1 -1 -1 1] Other hardcoded values:: sage: for n,e in [(36,1),(36,-1),(100,1),(100,-1),(196, 1)]: ....: print regular_symmetric_hadamard_matrix_with_constant_diagonal(n,e) 36 x 36 dense matrix over Integer Ring 36 x 36 dense matrix over Integer Ring 100 x 100 dense matrix over Integer Ring 100 x 100 dense matrix over Integer Ring 196 x 196 dense matrix over Integer Ring From two close prime powers:: sage: print regular_symmetric_hadamard_matrix_with_constant_diagonal(64,-1) 64 x 64 dense matrix over Integer Ring Recursive construction:: sage: print regular_symmetric_hadamard_matrix_with_constant_diagonal(144,-1) 144 x 144 dense matrix over Integer Ring REFERENCE: .. [BH12] A. Brouwer and W. Haemers, Spectra of graphs, Springer, 2012, http://homepages.cwi.nl/~aeb/math/ipm/ipm.pdf .. [HX10] W. Haemers and Q. Xiang, Strongly regular graphs with parameters `(4m^4,2m^4+m^2,m^4+m^2,m^4+m^2)` exist for all `m>1`, European Journal of Combinatorics, Volume 31, Issue 6, August 2010, Pages 1553-1559, http://dx.doi.org/10.1016/j.ejc.2009.07.009. """ if existence and (n,e) in _rshcd_cache: return _rshcd_cache[n,e] from sage.graphs.strongly_regular_db import strongly_regular_graph def true(): _rshcd_cache[n,e] = True return True M = None if abs(e) != 1: raise ValueError if n<0: if existence: return False raise ValueError elif n == 4: if existence: return true() if e == 1: M = J(4)-2*matrix(4,[[int(i+j == 3) for i in range(4)] for j in range(4)]) else: M = -J(4)+2*I(4) elif n == 36: if existence: return true() if e == 1: M = strongly_regular_graph(36, 15, 6, 6).adjacency_matrix() M = J(36) - 2*M else: M = strongly_regular_graph(36,14,4,6).adjacency_matrix() M = -J(36) + 2*M + 2*I(36) elif n == 100: if existence: return true() if e == -1: M = strongly_regular_graph(100,44,18,20).adjacency_matrix() M = 2*M - J(100) + 2*I(100) else: M = strongly_regular_graph(100,45,20,20).adjacency_matrix() M = J(100) - 2*M elif n == 196 and e == 1: if existence: return true() M = strongly_regular_graph(196,91,42,42).adjacency_matrix() M = J(196) - 2*M elif ( e == 1 and n%16 == 0 and is_square(n) and is_prime_power(sqrt(n)-1) and is_prime_power(sqrt(n)+1)): if existence: return true() M = -rshcd_from_close_prime_powers(int(sqrt(n))) # Recursive construction: the kronecker product of two RSHCD is a RSHCD else: from itertools import product for n1,e1 in product(divisors(n)[1:-1],[-1,1]): e2 = e1*e n2 = n//n1 if (regular_symmetric_hadamard_matrix_with_constant_diagonal(n1,e1,existence=True) and regular_symmetric_hadamard_matrix_with_constant_diagonal(n2,e2,existence=True)): if existence: return true() M1 = regular_symmetric_hadamard_matrix_with_constant_diagonal(n1,e1) M2 = regular_symmetric_hadamard_matrix_with_constant_diagonal(n2,e2) M = M1.tensor_product(M2) break if M is None: from sage.misc.unknown import Unknown _rshcd_cache[n,e] = Unknown if existence: return Unknown raise ValueError("I do not know how to build a {}-RSHCD".format((n,e))) assert M*M.transpose() == n*I(n) assert set(map(sum,M)) == {e*sqrt(n)} return M
def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, impl=None, proof=None, **kwds): """ EXAMPLES:: sage: GF.create_key_and_extra_args(9, 'a') ((9, ('a',), x^2 + 2*x + 2, None, '{}', 3, 2, True), {}) sage: GF.create_key_and_extra_args(9, 'a', foo='value') ((9, ('a',), x^2 + 2*x + 2, None, "{'foo': 'value'}", 3, 2, True), {'foo': 'value'}) """ from sage.structure.proof.all import WithProof, arithmetic if proof is None: proof = arithmetic() with WithProof('arithmetic', proof): order = int(order) if order <= 1: raise ValueError("the order of a finite field must be > 1.") if arith.is_prime(order): name = None modulus = None p = integer.Integer(order) n = integer.Integer(1) elif arith.is_prime_power(order): if not names is None: name = names name = normalize_names(1, name) p, n = arith.factor(order)[0] # The following is a temporary solution that allows us # to construct compatible systems of finite fields # until algebraic closures of finite fields are # implemented in Sage. It requires the user to # specify two parameters: # # - `conway` -- boolean; if True, this field is # constructed to fit in a compatible system using # a Conway polynomial. # - `prefix` -- a string used to generate names for # automatically constructed finite fields # # See the docstring of FiniteFieldFactory for examples. # # Once algebraic closures of finite fields are # implemented, this syntax should be superseded by # something like the following: # # sage: Fpbar = GF(5).algebraic_closure('z') # sage: F, e = Fpbar.subfield(3) # e is the embedding into Fpbar # sage: F # Finite field in z3 of size 5^3 # # This temporary solution only uses actual Conway # polynomials (no pseudo-Conway polynomials), since # pseudo-Conway polynomials are not unique, and until # we have algebraic closures of finite fields, there # is no good place to store a specific choice of # pseudo-Conway polynomials. if name is None: if not (kwds.has_key('conway') and kwds['conway']): raise ValueError( "parameter 'conway' is required if no name given") if not kwds.has_key('prefix'): raise ValueError( "parameter 'prefix' is required if no name given") name = kwds['prefix'] + str(n) if kwds.has_key('conway') and kwds['conway']: from conway_polynomials import conway_polynomial if not kwds.has_key('prefix'): raise ValueError( "a prefix must be specified if conway=True") if modulus is not None: raise ValueError( "no modulus may be specified if conway=True") # The following raises a RuntimeError if no polynomial is found. modulus = conway_polynomial(p, n) if modulus is None or isinstance(modulus, str): # A string specifies an algorithm to find a suitable modulus. if modulus == "default": # for backward compatibility modulus = None modulus = GF(p)['x'].irreducible_element(n, algorithm=modulus) elif isinstance(modulus, (list, tuple)): modulus = GF(p)['x'](modulus) elif sage.rings.polynomial.polynomial_element.is_Polynomial( modulus): modulus = modulus.change_variable_name('x') else: raise TypeError("wrong type for modulus parameter") else: raise ValueError( "the order of a finite field must be a prime power.") return (order, name, modulus, impl, str(kwds), p, n, proof), kwds
def v_5_1_BIBD(v, check=True): r""" Returns a `(v,5,1)`-BIBD. This method follows the constuction from [ClaytonSmith]_. INPUT: - ``v`` (integer) .. SEEALSO:: * :meth:`BalancedIncompleteBlockDesign` EXAMPLES:: sage: from sage.combinat.designs.bibd import v_5_1_BIBD sage: i = 0 sage: while i<200: ....: i += 20 ....: _ = v_5_1_BIBD(i+1) ....: _ = v_5_1_BIBD(i+5) """ v = int(v) assert (v > 1) assert (v%20 == 5 or v%20 == 1) # note: equivalent to (v-1)%4 == 0 and (v*(v-1))%20 == 0 # Lemma 27 if v%5 == 0 and (v//5)%4 == 1 and is_prime_power(v//5): bibd = BIBD_5q_5_for_q_prime_power(v//5) # Lemma 28 elif v == 21: from sage.rings.finite_rings.integer_mod_ring import Zmod bibd = BIBD_from_difference_family(Zmod(21), [[0,1,4,14,16]], check=False) elif v == 41: from sage.rings.finite_rings.integer_mod_ring import Zmod bibd = BIBD_from_difference_family(Zmod(41), [[0,1,4,11,29],[0,2,8,17,22]], check=False) elif v == 61: from sage.rings.finite_rings.integer_mod_ring import Zmod bibd = BIBD_from_difference_family(Zmod(61), [[0,1,3,13,34],[0,4,9,23,45],[0,6,17,24,32]], check=False) elif v == 81: from sage.groups.additive_abelian.additive_abelian_group import AdditiveAbelianGroup D = [[(0, 0, 0, 1), (2, 0, 0, 1), (0, 0, 2, 1), (1, 2, 0, 2), (0, 1, 1, 1)], [(0, 0, 1, 0), (1, 1, 0, 2), (0, 2, 1, 0), (1, 2, 0, 1), (1, 1, 1, 0)], [(2, 2, 1, 1), (1, 2, 2, 2), (2, 0, 1, 2), (0, 1, 2, 1), (1, 1, 0, 0)], [(0, 2, 0, 2), (1, 1, 0, 1), (1, 2, 1, 2), (1, 2, 1, 0), (0, 2, 1, 1)]] bibd = BIBD_from_difference_family(AdditiveAbelianGroup([3]*4), D, check=False) elif v == 161: # VI.16.16 of the Handbook of Combinatorial Designs, Second Edition D = [(0, 19, 34, 73, 80), (0, 16, 44, 71, 79), (0, 12, 33, 74, 78), (0, 13, 30, 72, 77), (0, 11, 36, 67, 76), (0, 18, 32, 69, 75), (0, 10, 48, 68, 70), (0, 3, 29, 52, 53)] from sage.rings.finite_rings.integer_mod_ring import Zmod bibd = BIBD_from_difference_family(Zmod(161), D, check=False) elif v == 281: from sage.rings.finite_rings.integer_mod_ring import Zmod D = [[3**(2*a+56*b) for b in range(5)] for a in range(14)] bibd = BIBD_from_difference_family(Zmod(281), D, check=False) # Lemma 29 elif v == 165: bibd = BIBD_from_PBD(v_5_1_BIBD(41,check=False),165,5,check=False) elif v == 181: bibd = BIBD_from_PBD(v_5_1_BIBD(45,check=False),181,5,check=False) elif v in (201,285,301,401,421,425): # Call directly the BIBD_from_TD function bibd = BIBD_from_TD(v,5) # Lemma 30 elif v == 141: # VI.16.16 of the Handbook of Combinatorial Designs, Second Edition from sage.rings.finite_rings.integer_mod_ring import Zmod D = [(0, 33, 60, 92, 97), (0, 3, 45, 88, 110), (0, 18, 39, 68, 139), (0, 12, 67, 75, 113), (0, 1, 15, 84, 94), (0, 7, 11, 24, 30), (0, 36, 90, 116, 125)] bibd = BIBD_from_difference_family(Zmod(141), D, check=False) # Theorem 31.2 elif (v-1)//4 in [80, 81, 85, 86, 90, 91, 95, 96, 110, 111, 115, 116, 120, 121, 250, 251, 255, 256, 260, 261, 265, 266, 270, 271]: r = (v-1)//4 if r <= 96: k,t,u = 5, 16, r-80 elif r <= 121: k,t,u = 10, 11, r-110 else: k,t,u = 10, 25, r-250 bibd = BIBD_from_PBD(PBD_from_TD(k,t,u),v,5,check=False) else: r,s,t,u = _get_r_s_t_u(v) bibd = BIBD_from_PBD(PBD_from_TD(5,t,u),v,5,check=False) if check: _check_pbd(bibd,v,[5]) return bibd
def HaemersGraph(q, hyperoval=None, hyperoval_matching=None, field=None, check_hyperoval=True): r""" Return the Haemers graph obtained from `T_2^*(q)^*` Let `q` be a power of 2. In Sect. 8.A of [BvL84]_ one finds a construction of a strongly regular graph with parameters `(q^2(q+2),q^2+q-1,q-2,q)` from the graph of `T_2^*(q)^*`, constructed by :func:`~sage.graphs.graph_generators.GraphGenerators.T2starGeneralizedQuadrangleGraph`, by redefining adjacencies in the way specified by an arbitrary ``hyperoval_matching`` of the the points (i.e. partitioning into size two parts) of ``hyperoval`` defining `T_2^*(q)^*`. While [BvL84]_ gives the construction in geometric terms, it can be formulated, and is implemented, in graph-theoretic ones, of re-adjusting the edges. Namely, `G=T_2^*(q)^*` has a partition into `q+2` independent sets `I_k` of size `q^2` each. Each vertex in `I_j` is adajcent to `q` vertices from `I_k`. Each `I_k` is paired to some `I_{k'}`, according to ``hyperoval_matching``. One adds edges `(s,t)` for `s,t \in I_k` whenever `s` and `t` are adjacent to some `u \in I_{k'}`, and removes all the edges between `I_k` and `I_{k'}`. INPUT: - ``q`` -- a power of two - ``hyperoval_matching`` -- if ``None`` (default), pair each `i`-th point of ``hyperoval`` with `(i+1)`-th. Otherwise, specifies the pairing in the format `((i_1,i'_1),(i_2,i'_2),...)`. - ``hyperoval`` -- a hyperoval defining `T_2^*(q)^*`. If ``None`` (default), the classical hyperoval obtained from a conic is used. See the documentation of :func:`~sage.graphs.graph_generators.GraphGenerators.T2starGeneralizedQuadrangleGraph`, for more information. - ``field`` -- an instance of a finite field of order `q`, must be provided if ``hyperoval`` is provided. - ``check_hyperoval`` -- (default: ``True``) if ``True``, check ``hyperoval`` for correctness. EXAMPLES: using the built-in constructions:: sage: g=graphs.HaemersGraph(4); g Haemers(4): Graph on 96 vertices sage: g.is_strongly_regular(parameters=True) (96, 19, 2, 4) supplying your own hyperoval_matching:: sage: g=graphs.HaemersGraph(4,hyperoval_matching=((0,5),(1,4),(2,3))); g Haemers(4): Graph on 96 vertices sage: g.is_strongly_regular(parameters=True) (96, 19, 2, 4) TESTS:: sage: F=GF(4,'b') # repeating a point... sage: O=[vector(F,(0,1,0,0)),vector(F,(0,0,1,0))]+map(lambda x: vector(F, (0,1,x^2,x)),F) sage: graphs.HaemersGraph(4, hyperoval=O, field=F) Traceback (most recent call last): ... RuntimeError: incorrect hyperoval size sage: O=[vector(F,(0,1,1,0)),vector(F,(0,0,1,0))]+map(lambda x: vector(F, (0,1,x^2,x)),F) sage: graphs.HaemersGraph(4, hyperoval=O, field=F) Traceback (most recent call last): ... RuntimeError: incorrect hyperoval sage: g=graphs.HaemersGraph(8); g # not tested (long time) Haemers(8): Graph on 640 vertices sage: g.is_strongly_regular(parameters=True) # not tested (long time) (640, 71, 6, 8) """ from sage.modules.free_module_element import free_module_element as vector from sage.rings.finite_rings.constructor import GF from itertools import combinations p, k = is_prime_power(q,get_data=True) if k==0 or p!=2: raise ValueError('q must be a power of 2') if hyperoval_matching is None: hyperoval_matching = map(lambda k: (2*k+1,2*k), xrange(1+q/2)) if field is None: F = GF(q,'a') else: F = field # for q=8, 95% of CPU time taken by this function is spent in the follwing call G = T2starGeneralizedQuadrangleGraph(q, field=F, dual=True, hyperoval=hyperoval, check_hyperoval=check_hyperoval) def normalize(v): # make sure the 1st non-0 coordinate is 1. d=next(x for x in v if x!=F.zero()) return vector(map(lambda x: x/d, v)) # build the partition into independent sets P = map(lambda x: normalize(x[0]-x[1]), G.vertices()) O = list(set(map(tuple,P))) I_ks = {x:[] for x in range(q+2)} # the partition into I_k's for i in xrange(len(P)): I_ks[O.index(tuple(P[i]))].append(i) # perform the adjustment of the edges, as described. G.relabel() cliques = [] for i,j in hyperoval_matching: Pij = set(I_ks[i]+I_ks[j]) for v in Pij: cliques.append(Pij.intersection(G.neighbors(v))) G.delete_edges(G.edge_boundary(I_ks[i],I_ks[j])) # edges on (I_i,I_j) G.add_edges(e for c in cliques for e in combinations(c,2)) G.name('Haemers('+str(q)+')') return G
def balanced_incomplete_block_design(v, k, existence=False, use_LJCR=False): r""" Return a BIBD of parameters `v,k`. A Balanced Incomplete Block Design of parameters `v,k` is a collection `\mathcal C` of `k`-subsets of `V=\{0,\dots,v-1\}` such that for any two distinct elements `x,y\in V` there is a unique element `S\in \mathcal C` such that `x,y\in S`. More general definitions sometimes involve a `\lambda` parameter, and we assume here that `\lambda=1`. For more information on BIBD, see the :wikipedia:`corresponding Wikipedia entry <Block_design#Definition_of_a_BIBD_.28or_2-design.29>`. INPUT: - ``v,k`` (integers) - ``existence`` (boolean) -- instead of building the design, return: - ``True`` -- meaning that Sage knows how to build the design - ``Unknown`` -- meaning that Sage does not know how to build the design, but that the design may exist (see :mod:`sage.misc.unknown`). - ``False`` -- meaning that the design does not exist. - ``use_LJCR`` (boolean) -- whether to query the La Jolla Covering Repository for the design when Sage does not know how to build it (see :func:`~sage.combinat.designs.covering_design.best_known_covering_design_www`). This requires internet. .. SEEALSO:: * :func:`steiner_triple_system` * :func:`v_4_1_BIBD` * :func:`v_5_1_BIBD` TODO: * Implement other constructions from the Handbook of Combinatorial Designs. EXAMPLES:: sage: designs.balanced_incomplete_block_design(7, 3).blocks() [[0, 1, 3], [0, 2, 4], [0, 5, 6], [1, 2, 6], [1, 4, 5], [2, 3, 5], [3, 4, 6]] sage: B = designs.balanced_incomplete_block_design(66, 6, use_LJCR=True) # optional - internet sage: B # optional - internet Incidence structure with 66 points and 143 blocks sage: B.blocks() # optional - internet [[0, 1, 2, 3, 4, 65], [0, 5, 24, 25, 39, 57], [0, 6, 27, 38, 44, 55], ... sage: designs.balanced_incomplete_block_design(66, 6, use_LJCR=True) # optional - internet Incidence structure with 66 points and 143 blocks sage: designs.balanced_incomplete_block_design(216, 6) Traceback (most recent call last): ... NotImplementedError: I don't know how to build a (216,6,1)-BIBD! TESTS:: sage: designs.balanced_incomplete_block_design(85,5,existence=True) True sage: _ = designs.balanced_incomplete_block_design(85,5) A BIBD from a Finite Projective Plane:: sage: _ = designs.balanced_incomplete_block_design(21,5) Some trivial BIBD:: sage: designs.balanced_incomplete_block_design(10,10) (10,10,1)-Balanced Incomplete Block Design sage: designs.balanced_incomplete_block_design(1,10) (1,0,1)-Balanced Incomplete Block Design Existence of BIBD with `k=3,4,5`:: sage: [v for v in xrange(50) if designs.balanced_incomplete_block_design(v,3,existence=True)] [1, 3, 7, 9, 13, 15, 19, 21, 25, 27, 31, 33, 37, 39, 43, 45, 49] sage: [v for v in xrange(100) if designs.balanced_incomplete_block_design(v,4,existence=True)] [1, 4, 13, 16, 25, 28, 37, 40, 49, 52, 61, 64, 73, 76, 85, 88, 97] sage: [v for v in xrange(150) if designs.balanced_incomplete_block_design(v,5,existence=True)] [1, 5, 21, 25, 41, 45, 61, 65, 81, 85, 101, 105, 121, 125, 141, 145] For `k > 5` there are currently very few constructions:: sage: [v for v in xrange(300) if designs.balanced_incomplete_block_design(v,6,existence=True) is True] [1, 6, 31, 66, 76, 91, 96, 106, 111, 121, 126, 136, 141, 151, 156, 171, 181, 186, 196, 201, 211, 241, 271] sage: [v for v in xrange(300) if designs.balanced_incomplete_block_design(v,6,existence=True) is Unknown] [51, 61, 81, 166, 216, 226, 231, 246, 256, 261, 276, 286, 291] Here are some constructions with `k \geq 7` and `v` a prime power:: sage: designs.balanced_incomplete_block_design(169,7) (169,7,1)-Balanced Incomplete Block Design sage: designs.balanced_incomplete_block_design(617,8) (617,8,1)-Balanced Incomplete Block Design sage: designs.balanced_incomplete_block_design(433,9) (433,9,1)-Balanced Incomplete Block Design sage: designs.balanced_incomplete_block_design(1171,10) (1171,10,1)-Balanced Incomplete Block Design And we know some inexistence results:: sage: designs.balanced_incomplete_block_design(21,6,existence=True) False """ lmbd = 1 # Trivial BIBD if v == 1: if existence: return True return BalancedIncompleteBlockDesign(v, [], check=False) if k == v: if existence: return True return BalancedIncompleteBlockDesign(v, [range(v)], check=False, copy=False) # Non-existence of BIBD if (v < k or k < 2 or (v-1) % (k-1) != 0 or (v*(v-1)) % (k*(k-1)) != 0 or # From the Handbook of combinatorial designs: # # With lambda>1 other exceptions are # (15,5,2),(21,6,2),(22,7,2),(22,8,4). (k==6 and v in [36,46]) or (k==7 and v == 43) or # Fisher's inequality (v*(v-1))/(k*(k-1)) < v): if existence: return False raise EmptySetError("There exists no ({},{},{})-BIBD".format(v,k,lmbd)) if k == 2: if existence: return True from itertools import combinations return BalancedIncompleteBlockDesign(v, combinations(range(v),2), check=False, copy=True) if k == 3: if existence: return v%6 == 1 or v%6 == 3 return steiner_triple_system(v) if k == 4: if existence: return v%12 == 1 or v%12 == 4 return BalancedIncompleteBlockDesign(v, v_4_1_BIBD(v), copy=False) if k == 5: if existence: return v%20 == 1 or v%20 == 5 return BalancedIncompleteBlockDesign(v, v_5_1_BIBD(v), copy=False) from difference_family import difference_family from database import BIBD_constructions if (v,k,1) in BIBD_constructions: if existence: return True return BlockDesign(v,BIBD_constructions[(v,k,1)](), copy=False) if BIBD_from_arc_in_desarguesian_projective_plane(v,k,existence=True): if existence: return True B = BIBD_from_arc_in_desarguesian_projective_plane(v,k) return BalancedIncompleteBlockDesign(v, B, copy=False) if BIBD_from_TD(v,k,existence=True): if existence: return True return BalancedIncompleteBlockDesign(v, BIBD_from_TD(v,k), copy=False) if v == (k-1)**2+k and is_prime_power(k-1): if existence: return True from block_design import projective_plane return BalancedIncompleteBlockDesign(v, projective_plane(k-1),copy=False) if difference_family(v,k,existence=True): if existence: return True G,D = difference_family(v,k) return BalancedIncompleteBlockDesign(v, BIBD_from_difference_family(G,D,check=False), copy=False) if use_LJCR: from covering_design import best_known_covering_design_www B = best_known_covering_design_www(v,k,2) # Is it a BIBD or just a good covering ? expected_n_of_blocks = binomial(v,2)/binomial(k,2) if B.low_bd() > expected_n_of_blocks: if existence: return False raise EmptySetError("There exists no ({},{},{})-BIBD".format(v,k,lmbd)) B = B.incidence_structure() if B.num_blocks() == expected_n_of_blocks: if existence: return True else: return B if existence: return Unknown else: raise NotImplementedError("I don't know how to build a ({},{},1)-BIBD!".format(v,k))
def v_5_1_BIBD(v, check=True): r""" Return a `(v,5,1)`-BIBD. This method follows the constuction from [ClaytonSmith]_. INPUT: - ``v`` (integer) .. SEEALSO:: * :func:`balanced_incomplete_block_design` EXAMPLES:: sage: from sage.combinat.designs.bibd import v_5_1_BIBD sage: i = 0 sage: while i<200: ....: i += 20 ....: _ = v_5_1_BIBD(i+1) ....: _ = v_5_1_BIBD(i+5) TESTS: Check that the needed difference families are there:: sage: for v in [21,41,61,81,141,161,281]: ....: assert designs.difference_family(v,5,existence=True) ....: _ = designs.difference_family(v,5) """ v = int(v) assert (v > 1) assert (v % 20 == 5 or v % 20 == 1 ) # note: equivalent to (v-1)%4 == 0 and (v*(v-1))%20 == 0 # Lemma 27 if v % 5 == 0 and (v // 5) % 4 == 1 and is_prime_power(v // 5): bibd = BIBD_5q_5_for_q_prime_power(v // 5) # Lemma 28 elif v in [21, 41, 61, 81, 141, 161, 281]: from difference_family import difference_family G, D = difference_family(v, 5) bibd = BIBD_from_difference_family(G, D, check=False) # Lemma 29 elif v == 165: bibd = BIBD_from_PBD(v_5_1_BIBD(41, check=False), 165, 5, check=False) elif v == 181: bibd = BIBD_from_PBD(v_5_1_BIBD(45, check=False), 181, 5, check=False) elif v in (201, 285, 301, 401, 421, 425): # Call directly the BIBD_from_TD function # note: there are (201,5,1) and (421,5)-difference families that can be # obtained from the general constructor bibd = BIBD_from_TD(v, 5) # Theorem 31.2 elif (v - 1) // 4 in [ 80, 81, 85, 86, 90, 91, 95, 96, 110, 111, 115, 116, 120, 121, 250, 251, 255, 256, 260, 261, 265, 266, 270, 271 ]: r = (v - 1) // 4 if r <= 96: k, t, u = 5, 16, r - 80 elif r <= 121: k, t, u = 10, 11, r - 110 else: k, t, u = 10, 25, r - 250 bibd = BIBD_from_PBD(PBD_from_TD(k, t, u), v, 5, check=False) else: r, s, t, u = _get_r_s_t_u(v) bibd = BIBD_from_PBD(PBD_from_TD(5, t, u), v, 5, check=False) if check: assert is_pairwise_balanced_design(bibd, v, [5]) return bibd
def difference_matrix(g,k,lmbda=1,existence=False,check=True): r""" Return a `(g,k,\lambda)`-difference matrix A matrix `M` is a `(g,k,\lambda)`-difference matrix if it has size `\lambda g\times k`, its entries belong to the group `G` of cardinality `g`, and for any two rows `R,R'` of `M` and `x\in G` there are exactly `\lambda` values `i` such that `R_i-R'_i=x`. INPUT: - ``k`` -- (integer) number of columns. If ``k=None`` it is set to the largest value available. - ``g`` -- (integer) cardinality of the group `G` - ``lmbda`` -- (integer; default: 1) -- number of times each element of `G` appears as a difference. - ``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. - ``existence`` (boolean) -- instead of building the design, return: - ``True`` -- meaning that Sage knows how to build the design - ``Unknown`` -- meaning that Sage does not know how to build the design, but that the design may exist (see :mod:`sage.misc.unknown`). - ``False`` -- meaning that the design does not exist. .. NOTE:: When ``k=None`` and ``existence=True`` the function returns an integer, i.e. the largest `k` such that we can build a `(g,k,\lambda)`-DM. EXAMPLES:: sage: G,M = designs.difference_matrix(25,10); G Finite Field in x of size 5^2 sage: designs.difference_matrix(993,None,existence=1) 32 Here we print for each `g` the maximum possible `k` for which Sage knows how to build a `(g,k,1)`-difference matrix:: sage: for g in range(2,30): ....: k_max = designs.difference_matrix(g=g,k=None,existence=True) ....: print "{:2} {}".format(g, k_max) ....: _ = designs.difference_matrix(g,k_max) 2 2 3 3 4 4 5 5 6 2 7 7 8 8 9 9 10 2 11 11 12 6 13 13 14 2 15 3 16 16 17 17 18 2 19 19 20 4 21 6 22 2 23 23 24 8 25 25 26 2 27 27 28 6 29 29 TESTS:: sage: designs.difference_matrix(10,12,1,existence=True) False sage: designs.difference_matrix(10,12,1) Traceback (most recent call last): ... EmptySetError: No (10,12,1)-Difference Matrix exists as k(=12)>g(=10) sage: designs.difference_matrix(10,9,1,existence=True) Unknown sage: designs.difference_matrix(10,9,1) Traceback (most recent call last): ... NotImplementedError: I don't know how to build a (10,9,1)-Difference Matrix! """ if lmbda == 1 and k is not None and k>g: if existence: return False raise EmptySetError("No ({},{},{})-Difference Matrix exists as k(={})>g(={})".format(g,k,lmbda,k,g)) # Prime powers elif lmbda == 1 and is_prime_power(g): if k is None: if existence: return g else: k = g elif existence: return True F = FiniteField(g,'x') F_set = list(F) F_k_set = F_set[:k] G = F M = [[x*y for y in F_k_set] for x in F_set] # Treat the case k=None # (find the max k such that there exists a DM) elif k is None: i = 2 while difference_matrix(g=g,k=i,lmbda=lmbda,existence=True): i += 1 return i-1 # From the database elif (g,lmbda) in DM_constructions and DM_constructions[g,lmbda][0]>=k: if existence: return True _,f = DM_constructions[g,lmbda] G, M = f() M = [R[:k] for R in M] # Product construction elif find_product_decomposition(g,k,lmbda): if existence: return True (g1,lmbda1),(g2,lmbda2) = find_product_decomposition(g,k,lmbda) G1,M1 = difference_matrix(g1,k,lmbda1) G2,M2 = difference_matrix(g2,k,lmbda2) G,M = difference_matrix_product(k,M1,G1,lmbda1,M2,G2,lmbda2,check=False) else: if existence: return Unknown raise NotImplementedError("I don't know how to build a ({},{},{})-Difference Matrix!".format(g,k,lmbda)) if check and not is_difference_matrix(M,G,k,lmbda,1): raise RuntimeError("Sage built something which is not a ({},{},{})-DM!".format(g,k,lmbda)) return G,M
def balanced_incomplete_block_design(v, k, existence=False, use_LJCR=False): r""" Return a BIBD of parameters `v,k`. A Balanced Incomplete Block Design of parameters `v,k` is a collection `\mathcal C` of `k`-subsets of `V=\{0,\dots,v-1\}` such that for any two distinct elements `x,y\in V` there is a unique element `S\in \mathcal C` such that `x,y\in S`. More general definitions sometimes involve a `\lambda` parameter, and we assume here that `\lambda=1`. For more information on BIBD, see the :wikipedia:`corresponding Wikipedia entry <Block_design#Definition_of_a_BIBD_.28or_2-design.29>`. INPUT: - ``v,k`` (integers) - ``existence`` (boolean) -- instead of building the design, return: - ``True`` -- meaning that Sage knows how to build the design - ``Unknown`` -- meaning that Sage does not know how to build the design, but that the design may exist (see :mod:`sage.misc.unknown`). - ``False`` -- meaning that the design does not exist. - ``use_LJCR`` (boolean) -- whether to query the La Jolla Covering Repository for the design when Sage does not know how to build it (see :func:`~sage.combinat.designs.covering_design.best_known_covering_design_www`). This requires internet. .. SEEALSO:: * :func:`steiner_triple_system` * :func:`v_4_1_BIBD` * :func:`v_5_1_BIBD` TODO: * Implement other constructions from the Handbook of Combinatorial Designs. EXAMPLES:: sage: designs.balanced_incomplete_block_design(7, 3).blocks() [[0, 1, 3], [0, 2, 4], [0, 5, 6], [1, 2, 6], [1, 4, 5], [2, 3, 5], [3, 4, 6]] sage: B = designs.balanced_incomplete_block_design(66, 6, use_LJCR=True) # optional - internet sage: B # optional - internet Incidence structure with 66 points and 143 blocks sage: B.blocks() # optional - internet [[0, 1, 2, 3, 4, 65], [0, 5, 24, 25, 39, 57], [0, 6, 27, 38, 44, 55], ... sage: designs.balanced_incomplete_block_design(66, 6, use_LJCR=True) # optional - internet Incidence structure with 66 points and 143 blocks sage: designs.balanced_incomplete_block_design(141, 6) Traceback (most recent call last): ... NotImplementedError: I don't know how to build a (141,6,1)-BIBD! TESTS:: sage: designs.balanced_incomplete_block_design(85,5,existence=True) True sage: _ = designs.balanced_incomplete_block_design(85,5) A BIBD from a Finite Projective Plane:: sage: _ = designs.balanced_incomplete_block_design(21,5) Some trivial BIBD:: sage: designs.balanced_incomplete_block_design(10,10) (10,10,1)-Balanced Incomplete Block Design sage: designs.balanced_incomplete_block_design(1,10) (1,0,1)-Balanced Incomplete Block Design Existence of BIBD with `k=3,4,5`:: sage: [v for v in xrange(50) if designs.balanced_incomplete_block_design(v,3,existence=True)] [1, 3, 7, 9, 13, 15, 19, 21, 25, 27, 31, 33, 37, 39, 43, 45, 49] sage: [v for v in xrange(100) if designs.balanced_incomplete_block_design(v,4,existence=True)] [1, 4, 13, 16, 25, 28, 37, 40, 49, 52, 61, 64, 73, 76, 85, 88, 97] sage: [v for v in xrange(150) if designs.balanced_incomplete_block_design(v,5,existence=True)] [1, 5, 21, 25, 41, 45, 61, 65, 81, 85, 101, 105, 121, 125, 141, 145] For `k > 5` there are currently very few constructions:: sage: [v for v in xrange(150) if designs.balanced_incomplete_block_design(v,6,existence=True) is True] [1, 6, 31, 91, 121] sage: [v for v in xrange(150) if designs.balanced_incomplete_block_design(v,6,existence=True) is Unknown] [51, 61, 66, 76, 81, 96, 106, 111, 126, 136, 141] Here are some constructions with `k \geq 7` and `v` a prime power:: sage: designs.balanced_incomplete_block_design(169,7) (169,7,1)-Balanced Incomplete Block Design sage: designs.balanced_incomplete_block_design(617,8) (617,8,1)-Balanced Incomplete Block Design sage: designs.balanced_incomplete_block_design(433,9) (433,9,1)-Balanced Incomplete Block Design sage: designs.balanced_incomplete_block_design(1171,10) (1171,10,1)-Balanced Incomplete Block Design And we know some inexistence results:: sage: designs.balanced_incomplete_block_design(21,6,existence=True) False """ lmbd = 1 # Trivial BIBD if v == 1: if existence: return True return BalancedIncompleteBlockDesign(v, [], check=False) if k == v: if existence: return True return BalancedIncompleteBlockDesign(v, [range(v)], check=False, copy=False) # Non-existence of BIBD if (v < k or k < 2 or (v - 1) % (k - 1) != 0 or (v * (v - 1)) % (k * (k - 1)) != 0 or # From the Handbook of combinatorial designs: # # With lambda>1 other exceptions are # (15,5,2),(21,6,2),(22,7,2),(22,8,4). (k == 6 and v in [36, 46]) or (k == 7 and v == 43) or # Fisher's inequality (v * (v - 1)) / (k * (k - 1)) < v): if existence: return False raise EmptySetError("There exists no ({},{},{})-BIBD".format( v, k, lmbd)) if k == 2: if existence: return True from itertools import combinations return BalancedIncompleteBlockDesign(v, combinations(range(v), 2), check=False, copy=True) if k == 3: if existence: return v % 6 == 1 or v % 6 == 3 return steiner_triple_system(v) if k == 4: if existence: return v % 12 == 1 or v % 12 == 4 return BalancedIncompleteBlockDesign(v, v_4_1_BIBD(v), copy=False) if k == 5: if existence: return v % 20 == 1 or v % 20 == 5 return BalancedIncompleteBlockDesign(v, v_5_1_BIBD(v), copy=False) from difference_family import difference_family from database import BIBD_constructions if (v, k, 1) in BIBD_constructions: if existence: return True return BlockDesign(v, BIBD_constructions[(v, k, 1)](), copy=False) if BIBD_from_TD(v, k, existence=True): if existence: return True return BalancedIncompleteBlockDesign(v, BIBD_from_TD(v, k), copy=False) if v == (k - 1)**2 + k and is_prime_power(k - 1): if existence: return True from block_design import projective_plane return BalancedIncompleteBlockDesign(v, projective_plane(k - 1), copy=False) if difference_family(v, k, existence=True): if existence: return True G, D = difference_family(v, k) return BalancedIncompleteBlockDesign(v, BIBD_from_difference_family( G, D, check=False), copy=False) if use_LJCR: from covering_design import best_known_covering_design_www B = best_known_covering_design_www(v, k, 2) # Is it a BIBD or just a good covering ? expected_n_of_blocks = binomial(v, 2) / binomial(k, 2) if B.low_bd() > expected_n_of_blocks: if existence: return False raise EmptySetError("There exists no ({},{},{})-BIBD".format( v, k, lmbd)) B = B.incidence_structure() if B.num_blocks() == expected_n_of_blocks: if existence: return True else: return B if existence: return Unknown else: raise NotImplementedError( "I don't know how to build a ({},{},1)-BIBD!".format(v, k))
def projective_plane(n, check=True, existence=False): r""" Return a projective plane of order ``n`` as a 2-design. A finite projective plane is a 2-design with `n^2+n+1` lines (or blocks) and `n^2+n+1` points. For more information on finite projective planes, see the :wikipedia:`Projective_plane#Finite_projective_planes`. If no construction is possible, then the function raises a ``EmptySetError`` whereas if no construction is available the function raises a ``NotImplementedError``. INPUT: - ``n`` -- the finite projective plane's order EXAMPLES:: sage: designs.projective_plane(2) (7,3,1)-Balanced Incomplete Block Design sage: designs.projective_plane(3) (13,4,1)-Balanced Incomplete Block Design sage: designs.projective_plane(4) (21,5,1)-Balanced Incomplete Block Design sage: designs.projective_plane(5) (31,6,1)-Balanced Incomplete Block Design sage: designs.projective_plane(6) Traceback (most recent call last): ... EmptySetError: By the Bruck-Ryser theorem, no projective plane of order 6 exists. sage: designs.projective_plane(10) Traceback (most recent call last): ... EmptySetError: No projective plane of order 10 exists by C. Lam, L. Thiel and S. Swiercz "The nonexistence of finite projective planes of order 10" (1989), Canad. J. Math. sage: designs.projective_plane(12) Traceback (most recent call last): ... NotImplementedError: If such a projective plane exists, we do not know how to build it. sage: designs.projective_plane(14) Traceback (most recent call last): ... EmptySetError: By the Bruck-Ryser theorem, no projective plane of order 14 exists. TESTS:: sage: designs.projective_plane(2197, existence=True) True sage: designs.projective_plane(6, existence=True) False sage: designs.projective_plane(10, existence=True) False sage: designs.projective_plane(12, existence=True) Unknown """ from sage.rings.arith import is_prime_power from sage.rings.sum_of_squares import is_sum_of_two_squares_pyx if n <= 1: if existence: return False raise EmptySetError("There is no projective plane of order <= 1") if n == 10: if existence: return False ref = ("C. Lam, L. Thiel and S. Swiercz \"The nonexistence of finite " "projective planes of order 10\" (1989), Canad. J. Math.") raise EmptySetError("No projective plane of order 10 exists by %s"%ref) if (n%4) in [1,2] and not is_sum_of_two_squares_pyx(n): if existence: return False raise EmptySetError("By the Bruck-Ryser theorem, no projective" " plane of order {} exists.".format(n)) if not is_prime_power(n): if existence: return Unknown raise NotImplementedError("If such a projective plane exists, we do " "not know how to build it.") if existence: return True else: return DesarguesianProjectivePlaneDesign(n, point_coordinates=False, check=check)
def NonisotropicOrthogonalPolarGraph(m, q, sign="+", perp=None): r""" Returns the Graph `NO^{\epsilon,\perp}_{m}(q)` Let the vectorspace of dimension `m` over `F_q` be endowed with a nondegenerate quadratic form `F`, of type ``sign`` for `m` even. * `m` even: assume further that `q=2` or `3`. Returns the graph of the points (in the underlying projective space) `x` satisfying `F(x)=1`, with adjacency given by orthogonality w.r.t. `F`. Parameter ``perp`` is ignored. * `m` odd: if ``perp`` is not ``None``, then we assume that `q=5` and return the graph of the points `x` satisfying `F(x)=\pm 1` if ``sign="+"``, respectively `F(x) \in \{2,3\}` if ``sign="-"``, with adjacency given by orthogonality w.r.t. `F` (cf. Sect 7.D of [BvL84]_). Otherwise return the graph of nongenerate hyperplanes of type ``sign``, adjacent whenever the intersection is degenerate (cf. Sect. 7.C of [BvL84]_). Note that for `q=2` one will get a complete graph. For more information, see Sect. 9.9 of [BH12]_ and [BvL84]_. Note that the `page of Andries Brouwer's website <http://www.win.tue.nl/~aeb/graphs/srghub.html>`_ uses different notation. INPUT: - ``m`` - integer, half the dimension of the underlying vectorspace - ``q`` - a power of a prime number, the size of the underlying field - ``sign`` -- ``"+"`` (default) or ``"-"``. EXAMPLES: `NO^-(4,2)` is isomorphic to Petersen graph:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(4,2,'-'); g NO^-(4, 2): Graph on 10 vertices sage: g.is_strongly_regular(parameters=True) (10, 3, 0, 1) `NO^-(6,2)` and `NO^+(6,2)`:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,2,'-') sage: g.is_strongly_regular(parameters=True) (36, 15, 6, 6) sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,2,'+'); g NO^+(6, 2): Graph on 28 vertices sage: g.is_strongly_regular(parameters=True) (28, 15, 6, 10) `NO^+(8,2)`:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(8,2,'+') sage: g.is_strongly_regular(parameters=True) (120, 63, 30, 36) Wilbrink's graphs for `q=5`:: sage: graphs.NonisotropicOrthogonalPolarGraph(5,5,perp=1).is_strongly_regular(parameters=True) # long time (325, 60, 15, 10) sage: graphs.NonisotropicOrthogonalPolarGraph(5,5,'-',perp=1).is_strongly_regular(parameters=True) # long time (300, 65, 10, 15) Wilbrink's graphs:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,4,'+') sage: g.is_strongly_regular(parameters=True) (136, 75, 42, 40) sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,4,'-') sage: g.is_strongly_regular(parameters=True) (120, 51, 18, 24) sage: g=graphs.NonisotropicOrthogonalPolarGraph(7,4,'+'); g # not tested (long time) NO^+(7, 4): Graph on 2080 vertices sage: g.is_strongly_regular(parameters=True) # not tested (long time) (2080, 1071, 558, 544) TESTS:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(4,2); g NO^+(4, 2): Graph on 6 vertices sage: graphs.NonisotropicOrthogonalPolarGraph(4,3,'-').is_strongly_regular(parameters=True) (15, 6, 1, 3) sage: g=graphs.NonisotropicOrthogonalPolarGraph(3,5,'-',perp=1); g NO^-,perp(3, 5): Graph on 10 vertices sage: g.is_strongly_regular(parameters=True) (10, 3, 0, 1) sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,3,'+') # long time sage: g.is_strongly_regular(parameters=True) # long time (117, 36, 15, 9) sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,3,'-'); g # long time NO^-(6, 3): Graph on 126 vertices sage: g.is_strongly_regular(parameters=True) # long time (126, 45, 12, 18) sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,5,'-') # long time sage: g.is_strongly_regular(parameters=True) # long time (300, 104, 28, 40) sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,5,'+') # long time sage: g.is_strongly_regular(parameters=True) # long time (325, 144, 68, 60) sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,4,'+') Traceback (most recent call last): ... ValueError: for m even q must be 2 or 3 """ from sage.graphs.generators.classical_geometries import _orthogonal_polar_graph from sage.rings.arith import is_prime_power p, k = is_prime_power(q,get_data=True) if k==0: raise ValueError('q must be a prime power') dec = '' if m % 2 == 0: if q in [2,3]: G = _orthogonal_polar_graph(m, q, sign=sign, point_type=[1]) else: raise ValueError("for m even q must be 2 or 3") elif not perp is None: if q == 5: G = _orthogonal_polar_graph(m, q, point_type=\ [-1,1] if sign=='+' else [2,3] if sign=='-' else []) dec = ",perp" else: raise ValueError("for perp not None q must be 5") else: if not sign in ['+','-']: raise ValueError("sign must be '+' or '-'") from sage.libs.gap.libgap import libgap g0 = libgap.GeneralOrthogonalGroup(m,q) g = libgap.Group(libgap.List(g0.GeneratorsOfGroup(),libgap.TransposedMat)) F=libgap.GF(q) # F_q W=libgap.FullRowSpace(F, m) # F_q^m e = 1 if sign=='+' else -1 n = (m-1)/2 # we build (q^n(q^n+e)/2, (q^n-e)(q^(n-1)+e), 2(q^(2n-2)-1)+eq^(n-1)(q-1), # 2q^(n-1)(q^(n-1)+e))-srg # **use** v and k to select appropriate orbit and orbital nvert = (q**n)*(q**n+e)/2 # v deg = (q**n-e)*(q**(n-1)+e) # k S=map(lambda x: libgap.Elements(libgap.Basis(x))[0], \ libgap.Elements(libgap.Subspaces(W,1))) V = filter(lambda x: len(x)==nvert, libgap.Orbits(g,S,libgap.OnLines)) assert len(V)==1 V = V[0] gp = libgap.Action(g,V,libgap.OnLines) # make a permutation group h = libgap.Stabilizer(gp,1) Vh = filter(lambda x: len(x)==deg, libgap.Orbits(h,libgap.Orbit(gp,1))) assert len(Vh)==1 Vh = Vh[0][0] L = libgap.Orbit(gp, [1, Vh], libgap.OnSets) G = Graph() G.add_edges(L) G.name("NO^" + sign + dec + str((m, q))) return G
def difference_matrix(g, k, lmbda=1, existence=False, check=True): r""" Return a `(g,k,\lambda)`-difference matrix A matrix `M` is a `(g,k,\lambda)`-difference matrix if it has size `\lambda g\times k`, its entries belong to the group `G` of cardinality `g`, and for any two rows `R,R'` of `M` and `x\in G` there are exactly `\lambda` values `i` such that `R_i-R'_i=x`. INPUT: - ``k`` -- (integer) number of columns. If ``k=None`` it is set to the largest value available. - ``g`` -- (integer) cardinality of the group `G` - ``lmbda`` -- (integer; default: 1) -- number of times each element of `G` appears as a difference. - ``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. - ``existence`` (boolean) -- instead of building the design, return: - ``True`` -- meaning that Sage knows how to build the design - ``Unknown`` -- meaning that Sage does not know how to build the design, but that the design may exist (see :mod:`sage.misc.unknown`). - ``False`` -- meaning that the design does not exist. .. NOTE:: When ``k=None`` and ``existence=True`` the function returns an integer, i.e. the largest `k` such that we can build a `(g,k,\lambda)`-DM. EXAMPLES:: sage: G,M = designs.difference_matrix(25,10); G Finite Field in x of size 5^2 sage: designs.difference_matrix(993,None,existence=1) 32 Here we print for each `g` the maximum possible `k` for which Sage knows how to build a `(g,k,1)`-difference matrix:: sage: for g in range(2,30): ....: k_max = designs.difference_matrix(g=g,k=None,existence=True) ....: print "{:2} {}".format(g, k_max) ....: _ = designs.difference_matrix(g,k_max) 2 2 3 3 4 4 5 5 6 2 7 7 8 8 9 9 10 2 11 11 12 6 13 13 14 2 15 3 16 16 17 17 18 2 19 19 20 4 21 6 22 2 23 23 24 8 25 25 26 2 27 27 28 6 29 29 TESTS:: sage: designs.difference_matrix(10,12,1,existence=True) False sage: designs.difference_matrix(10,12,1) Traceback (most recent call last): ... EmptySetError: No (10,12,1)-Difference Matrix exists as k(=12)>g(=10) sage: designs.difference_matrix(10,9,1,existence=True) Unknown sage: designs.difference_matrix(10,9,1) Traceback (most recent call last): ... NotImplementedError: I don't know how to build a (10,9,1)-Difference Matrix! """ if lmbda == 1 and k is not None and k > g: if existence: return False raise EmptySetError( "No ({},{},{})-Difference Matrix exists as k(={})>g(={})".format( g, k, lmbda, k, g)) # Prime powers elif lmbda == 1 and is_prime_power(g): if k is None: if existence: return g else: k = g elif existence: return True F = FiniteField(g, 'x') F_set = list(F) F_k_set = F_set[:k] G = F M = [[x * y for y in F_k_set] for x in F_set] # Treat the case k=None # (find the max k such that there exists a DM) elif k is None: i = 2 while difference_matrix(g=g, k=i, lmbda=lmbda, existence=True): i += 1 return i - 1 # From the database elif (g, lmbda) in DM_constructions and DM_constructions[g, lmbda][0] >= k: if existence: return True _, f = DM_constructions[g, lmbda] G, M = f() M = [R[:k] for R in M] # Product construction elif find_product_decomposition(g, k, lmbda): if existence: return True (g1, lmbda1), (g2, lmbda2) = find_product_decomposition(g, k, lmbda) G1, M1 = difference_matrix(g1, k, lmbda1) G2, M2 = difference_matrix(g2, k, lmbda2) G, M = difference_matrix_product(k, M1, G1, lmbda1, M2, G2, lmbda2, check=False) else: if existence: return Unknown raise NotImplementedError( "I don't know how to build a ({},{},{})-Difference Matrix!".format( g, k, lmbda)) if check and not is_difference_matrix(M, G, k, lmbda, 1): raise RuntimeError( "Sage built something which is not a ({},{},{})-DM!".format( g, k, lmbda)) return G, M
def NonisotropicUnitaryPolarGraph(m, q): r""" Returns the Graph `NU(m,q)`. Returns the graph on nonisotropic, with respect to a nondegenerate Hermitean form, points of the `(m-1)`-dimensional projective space over `F_q`, with points adjacent whenever they lie on a tangent (to the set of isotropic points) line. For more information, see Sect. 9.9 of [BH12]_ and series C14 in [Hu75]_. INPUT: - ``m,q`` (integers) -- `q` must be a prime power. EXAMPLES:: sage: g=graphs.NonisotropicUnitaryPolarGraph(5,2); g NU(5, 2): Graph on 176 vertices sage: g.is_strongly_regular(parameters=True) (176, 135, 102, 108) TESTS:: sage: graphs.NonisotropicUnitaryPolarGraph(4,2).is_strongly_regular(parameters=True) (40, 27, 18, 18) sage: graphs.NonisotropicUnitaryPolarGraph(4,3).is_strongly_regular(parameters=True) # long time (540, 224, 88, 96) sage: graphs.NonisotropicUnitaryPolarGraph(6,6) Traceback (most recent call last): ... ValueError: q must be a prime power REFERENCE: .. [Hu75] X. L. Hubaut. Strongly regular graphs. Disc. Math. 13(1975), pp 357--381. http://dx.doi.org/10.1016/0012-365X(75)90057-6 """ from sage.rings.arith import is_prime_power p, k = is_prime_power(q,get_data=True) if k==0: raise ValueError('q must be a prime power') from sage.libs.gap.libgap import libgap from itertools import combinations F=libgap.GF(q**2) # F_{q^2} W=libgap.FullRowSpace(F, m) # F_{q^2}^m B=libgap.Elements(libgap.Basis(W)) # the standard basis of W if m % 2 != 0: point = B[(m-1)/2] else: if p==2: point = B[m/2] + F.PrimitiveRoot()*B[(m-2)/2] else: point = B[(m-2)/2] + B[m/2] g = libgap.GeneralUnitaryGroup(m,q) V = libgap.Orbit(g,point,libgap.OnLines) # orbit on nonisotropic points gp = libgap.Action(g,V,libgap.OnLines) # make a permutation group s = libgap.Subspace(W,[point, point+B[0]]) # a tangent line on point # and the points there sp = [libgap.Elements(libgap.Basis(x))[0] for x in libgap.Elements(s.Subspaces(1))] h = libgap.Set(map(lambda x: libgap.Position(V, x), libgap.Intersection(V,sp))) # indices L = libgap.Orbit(gp, h, libgap.OnSets) # orbit on the tangent lines G = Graph() for x in L: # every pair of points in the subspace is adjacent to each other in G G.add_edges(combinations(x, 2)) G.relabel() G.name("NU" + str((m, q))) return G
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 TaylorTwographDescendantSRG(q, clique_partition=None): r""" constructing the descendant graph of the Taylor's two-graph for `U_3(q)`, `q` odd This is a strongly regular graph with parameters `(v,k,\lambda,\mu)=(q^3, (q^2+1)(q-1)/2, (q-1)^3/4-1, (q^2+1)(q-1)/4)` obtained as a two-graph descendant of the :func:`Taylor's two-graph <sage.combinat.designs.twographs.taylor_twograph>` `T`. This graph admits a partition into cliques of size `q`, which are useful in :func:`TaylorTwographSRG <sage.graphs.generators.classical_geometries.TaylorTwographSRG>`, a strongly regular graph on `q^3+1` vertices in the Seidel switching class of `T`, for which we need `(q^2+1)/2` cliques. The cliques are the `q^2` lines on `v_0` of the projective plane containing the unital for `U_3(q)`, and intersecting the unital (i.e. the vertices of the graph and the point we remove) in `q+1` points. This is all taken from §7E of [BvL84]_. INPUT: - ``q`` -- a power of an odd prime number - ``clique_partition`` -- if ``True``, return `q^2-1` cliques of size `q` with empty pairwise intersection. (Removing all of them leaves a clique, too), and the point removed from the unital. EXAMPLES:: sage: g=graphs.TaylorTwographDescendantSRG(3); g Taylor two-graph descendant SRG: Graph on 27 vertices sage: g.is_strongly_regular(parameters=True) (27, 10, 1, 5) sage: from sage.combinat.designs.twographs import taylor_twograph sage: T = taylor_twograph(3) # long time sage: g.is_isomorphic(T.descendant(T.ground_set()[1])) # long time True sage: g=graphs.TaylorTwographDescendantSRG(5) # not tested (long time) sage: g.is_strongly_regular(parameters=True) # not tested (long time) (125, 52, 15, 26) TESTS:: sage: g,l,_=graphs.TaylorTwographDescendantSRG(3,clique_partition=True) sage: all(map(lambda x: g.is_clique(x), l)) True sage: graphs.TaylorTwographDescendantSRG(4) Traceback (most recent call last): ... ValueError: q must be an odd prime power sage: graphs.TaylorTwographDescendantSRG(6) Traceback (most recent call last): ... ValueError: q must be an odd prime power """ from sage.rings.arith import is_prime_power p, k = is_prime_power(q,get_data=True) if k==0 or p==2: raise ValueError('q must be an odd prime power') from sage.schemes.projective.projective_space import ProjectiveSpace from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module_element import free_module_element as vector from sage.rings.finite_rings.integer_mod import mod from __builtin__ import sum Fq = FiniteField(q**2, 'a') PG = map(tuple,ProjectiveSpace(2, Fq)) def S(x,y): return sum(map(lambda j: x[j]*y[2-j]**q, xrange(3))) V = filter(lambda x: S(x,x)==0, PG) # the points of the unital v0 = V[0] V.remove(v0) if mod(q,4)==1: G = Graph([V,lambda y,z: not (S(v0,y)*S(y,z)*S(z,v0)).is_square()], loops=False) else: G = Graph([V,lambda y,z: (S(v0,y)*S(y,z)*S(z,v0)).is_square()], loops=False) G.name("Taylor two-graph descendant SRG") if clique_partition: lines = map(lambda x: filter(lambda t: t[0]+x*t[1]==0, V), filter(lambda z: z != 0, Fq)) return (G, lines, v0) else: return G
def NonisotropicOrthogonalPolarGraph(m, q, sign="+", perp=None): r""" Returns the Graph `NO^{\epsilon,\perp}_{m}(q)` Let the vectorspace of dimension `m` over `F_q` be endowed with a nondegenerate quadratic form `F`, of type ``sign`` for `m` even. * `m` even: assume further that `q=2` or `3`. Returns the graph of the points (in the underlying projective space) `x` satisfying `F(x)=1`, with adjacency given by orthogonality w.r.t. `F`. Parameter ``perp`` is ignored. * `m` odd: if ``perp`` is not ``None``, then we assume that `q=5` and return the graph of the points `x` satisfying `F(x)=\pm 1` if ``sign="+"``, respectively `F(x) \in \{2,3\}` if ``sign="-"``, with adjacency given by orthogonality w.r.t. `F` (cf. Sect 7.D of [BvL84]_). Otherwise return the graph of nongenerate hyperplanes of type ``sign``, adjacent whenever the intersection is degenerate (cf. Sect. 7.C of [BvL84]_). Note that for `q=2` one will get a complete graph. For more information, see Sect. 9.9 of [BH12]_ and [BvL84]_. Note that the `page of Andries Brouwer's website <http://www.win.tue.nl/~aeb/graphs/srghub.html>`_ uses different notation. INPUT: - ``m`` - integer, half the dimension of the underlying vectorspace - ``q`` - a power of a prime number, the size of the underlying field - ``sign`` -- ``"+"`` (default) or ``"-"``. EXAMPLES: `NO^-(4,2)` is isomorphic to Petersen graph:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(4,2,'-'); g NO^-(4, 2): Graph on 10 vertices sage: g.is_strongly_regular(parameters=True) (10, 3, 0, 1) `NO^-(6,2)` and `NO^+(6,2)`:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,2,'-') sage: g.is_strongly_regular(parameters=True) (36, 15, 6, 6) sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,2,'+'); g NO^+(6, 2): Graph on 28 vertices sage: g.is_strongly_regular(parameters=True) (28, 15, 6, 10) `NO^+(8,2)`:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(8,2,'+') sage: g.is_strongly_regular(parameters=True) (120, 63, 30, 36) Wilbrink's graphs for `q=5`:: sage: graphs.NonisotropicOrthogonalPolarGraph(5,5,perp=1).is_strongly_regular(parameters=True) # long time (325, 60, 15, 10) sage: graphs.NonisotropicOrthogonalPolarGraph(5,5,'-',perp=1).is_strongly_regular(parameters=True) # long time (300, 65, 10, 15) Wilbrink's graphs:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,4,'+') sage: g.is_strongly_regular(parameters=True) (136, 75, 42, 40) sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,4,'-') sage: g.is_strongly_regular(parameters=True) (120, 51, 18, 24) sage: g=graphs.NonisotropicOrthogonalPolarGraph(7,4,'+'); g # not tested (long time) NO^+(7, 4): Graph on 2080 vertices sage: g.is_strongly_regular(parameters=True) # not tested (long time) (2080, 1071, 558, 544) TESTS:: sage: g=graphs.NonisotropicOrthogonalPolarGraph(4,2); g NO^+(4, 2): Graph on 6 vertices sage: graphs.NonisotropicOrthogonalPolarGraph(4,3,'-').is_strongly_regular(parameters=True) (15, 6, 1, 3) sage: g=graphs.NonisotropicOrthogonalPolarGraph(3,5,'-',perp=1); g NO^-,perp(3, 5): Graph on 10 vertices sage: g.is_strongly_regular(parameters=True) (10, 3, 0, 1) sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,3,'+') # long time sage: g.is_strongly_regular(parameters=True) # long time (117, 36, 15, 9) sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,3,'-'); g # long time NO^-(6, 3): Graph on 126 vertices sage: g.is_strongly_regular(parameters=True) # long time (126, 45, 12, 18) sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,5,'-') # long time sage: g.is_strongly_regular(parameters=True) # long time (300, 104, 28, 40) sage: g=graphs.NonisotropicOrthogonalPolarGraph(5,5,'+') # long time sage: g.is_strongly_regular(parameters=True) # long time (325, 144, 68, 60) sage: g=graphs.NonisotropicOrthogonalPolarGraph(6,4,'+') Traceback (most recent call last): ... ValueError: for m even q must be 2 or 3 """ from sage.graphs.generators.classical_geometries import _orthogonal_polar_graph from sage.rings.arith import is_prime_power p, k = is_prime_power(q, get_data=True) if k == 0: raise ValueError('q must be a prime power') dec = '' if m % 2 == 0: if q in [2, 3]: G = _orthogonal_polar_graph(m, q, sign=sign, point_type=[1]) else: raise ValueError("for m even q must be 2 or 3") elif not perp is None: if q == 5: G = _orthogonal_polar_graph(m, q, point_type=\ [-1,1] if sign=='+' else [2,3] if sign=='-' else []) dec = ",perp" else: raise ValueError("for perp not None q must be 5") else: if not sign in ['+', '-']: raise ValueError("sign must be '+' or '-'") from sage.libs.gap.libgap import libgap g0 = libgap.GeneralOrthogonalGroup(m, q) g = libgap.Group( libgap.List(g0.GeneratorsOfGroup(), libgap.TransposedMat)) F = libgap.GF(q) # F_q W = libgap.FullRowSpace(F, m) # F_q^m e = 1 if sign == '+' else -1 n = (m - 1) / 2 # we build (q^n(q^n+e)/2, (q^n-e)(q^(n-1)+e), 2(q^(2n-2)-1)+eq^(n-1)(q-1), # 2q^(n-1)(q^(n-1)+e))-srg # **use** v and k to select appropriate orbit and orbital nvert = (q**n) * (q**n + e) / 2 # v deg = (q**n - e) * (q**(n - 1) + e) # k S=map(lambda x: libgap.Elements(libgap.Basis(x))[0], \ libgap.Elements(libgap.Subspaces(W,1))) V = filter(lambda x: len(x) == nvert, libgap.Orbits(g, S, libgap.OnLines)) assert len(V) == 1 V = V[0] gp = libgap.Action(g, V, libgap.OnLines) # make a permutation group h = libgap.Stabilizer(gp, 1) Vh = filter(lambda x: len(x) == deg, libgap.Orbits(h, libgap.Orbit(gp, 1))) assert len(Vh) == 1 Vh = Vh[0][0] L = libgap.Orbit(gp, [1, Vh], libgap.OnSets) G = Graph() G.add_edges(L) G.name("NO^" + sign + dec + str((m, q))) return G
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 TaylorTwographDescendantSRG(q, clique_partition=None): r""" constructing the descendant graph of the Taylor's two-graph for `U_3(q)`, `q` odd This is a strongly regular graph with parameters `(v,k,\lambda,\mu)=(q^3, (q^2+1)(q-1)/2, (q-1)^3/4-1, (q^2+1)(q-1)/4)` obtained as a two-graph descendant of the :func:`Taylor's two-graph <sage.combinat.designs.twographs.taylor_twograph>` `T`. This graph admits a partition into cliques of size `q`, which are useful in :func:`TaylorTwographSRG <sage.graphs.generators.classical_geometries.TaylorTwographSRG>`, a strongly regular graph on `q^3+1` vertices in the Seidel switching class of `T`, for which we need `(q^2+1)/2` cliques. The cliques are the `q^2` lines on `v_0` of the projective plane containing the unital for `U_3(q)`, and intersecting the unital (i.e. the vertices of the graph and the point we remove) in `q+1` points. This is all taken from §7E of [BvL84]_. INPUT: - ``q`` -- a power of an odd prime number - ``clique_partition`` -- if ``True``, return `q^2-1` cliques of size `q` with empty pairwise intersection. (Removing all of them leaves a clique, too), and the point removed from the unital. EXAMPLES:: sage: g=graphs.TaylorTwographDescendantSRG(3); g Taylor two-graph descendant SRG: Graph on 27 vertices sage: g.is_strongly_regular(parameters=True) (27, 10, 1, 5) sage: from sage.combinat.designs.twographs import taylor_twograph sage: T = taylor_twograph(3) # long time sage: g.is_isomorphic(T.descendant(T.ground_set()[1])) # long time True sage: g=graphs.TaylorTwographDescendantSRG(5) # not tested (long time) sage: g.is_strongly_regular(parameters=True) # not tested (long time) (125, 52, 15, 26) TESTS:: sage: g,l,_=graphs.TaylorTwographDescendantSRG(3,clique_partition=True) sage: all(map(lambda x: g.is_clique(x), l)) True sage: graphs.TaylorTwographDescendantSRG(4) Traceback (most recent call last): ... ValueError: q must be an odd prime power sage: graphs.TaylorTwographDescendantSRG(6) Traceback (most recent call last): ... ValueError: q must be an odd prime power """ from sage.rings.arith import is_prime_power p, k = is_prime_power(q, get_data=True) if k == 0 or p == 2: raise ValueError('q must be an odd prime power') from sage.schemes.projective.projective_space import ProjectiveSpace from sage.rings.finite_rings.constructor import FiniteField from sage.modules.free_module_element import free_module_element as vector from sage.rings.finite_rings.integer_mod import mod from __builtin__ import sum Fq = FiniteField(q**2, 'a') PG = map(tuple, ProjectiveSpace(2, Fq)) def S(x, y): return sum(map(lambda j: x[j] * y[2 - j]**q, xrange(3))) V = filter(lambda x: S(x, x) == 0, PG) # the points of the unital v0 = V[0] V.remove(v0) if mod(q, 4) == 1: G = Graph( [V, lambda y, z: not (S(v0, y) * S(y, z) * S(z, v0)).is_square()], loops=False) else: G = Graph( [V, lambda y, z: (S(v0, y) * S(y, z) * S(z, v0)).is_square()], loops=False) G.name("Taylor two-graph descendant SRG") if clique_partition: lines = map(lambda x: filter(lambda t: t[0] + x * t[1] == 0, V), filter(lambda z: z != 0, Fq)) return (G, lines, v0) else: return G
def hadamard_matrix_paleyII(n): """ Implements the Paley type II construction. The Paley type II case corresponds to the case `p \cong 1 \mod{4}` for a prime `p` (see [Hora]_). EXAMPLES:: sage: sage.combinat.matrices.hadamard_matrix.hadamard_matrix_paleyII(12).det() 2985984 sage: 12^6 2985984 We note that the method returns a normalised Hadamard matrix :: sage: sage.combinat.matrices.hadamard_matrix.hadamard_matrix_paleyII(12) [ 1 1| 1 1| 1 1| 1 1| 1 1| 1 1] [ 1 -1|-1 1|-1 1|-1 1|-1 1|-1 1] [-----+-----+-----+-----+-----+-----] [ 1 -1| 1 -1| 1 1|-1 -1|-1 -1| 1 1] [ 1 1|-1 -1| 1 -1|-1 1|-1 1| 1 -1] [-----+-----+-----+-----+-----+-----] [ 1 -1| 1 1| 1 -1| 1 1|-1 -1|-1 -1] [ 1 1| 1 -1|-1 -1| 1 -1|-1 1|-1 1] [-----+-----+-----+-----+-----+-----] [ 1 -1|-1 -1| 1 1| 1 -1| 1 1|-1 -1] [ 1 1|-1 1| 1 -1|-1 -1| 1 -1|-1 1] [-----+-----+-----+-----+-----+-----] [ 1 -1|-1 -1|-1 -1| 1 1| 1 -1| 1 1] [ 1 1|-1 1|-1 1| 1 -1|-1 -1| 1 -1] [-----+-----+-----+-----+-----+-----] [ 1 -1| 1 1|-1 -1|-1 -1| 1 1| 1 -1] [ 1 1| 1 -1|-1 1|-1 1| 1 -1|-1 -1] TESTS:: sage: from sage.combinat.matrices.hadamard_matrix import (hadamard_matrix_paleyII, is_hadamard_matrix) sage: test_cases = [2*(x+1) for x in range(50) if is_prime_power(x) and x%4==1] sage: all(is_hadamard_matrix(hadamard_matrix_paleyII(n),normalized=True,verbose=True) ....: for n in test_cases) True """ q = n//2 - 1 if not(n%2==0 and is_prime_power(q) and (q % 4 == 1)): raise ValueError("The order %s is not covered by the Paley type II construction." % n) from sage.rings.finite_rings.constructor import FiniteField K = FiniteField(q,'x') K_list = list(K) K_list.insert(0,K.zero()) H = matrix(ZZ, [[(1 if (x-y).is_square() else -1) for x in K_list] for y in K_list]) for i in range(q+1): H[0,i] = 1 H[i,0] = 1 H[i,i] = 0 tr = { 0: matrix(2,2,[ 1,-1,-1,-1]), 1: matrix(2,2,[ 1, 1, 1,-1]), -1: matrix(2,2,[-1,-1,-1, 1])} H = block_matrix(q+1,q+1,[tr[v] for r in H for v in r]) return normalise_hadamard(H)
def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, impl=None, proof=None, **kwds): """ EXAMPLES:: sage: GF.create_key_and_extra_args(9, 'a') ((9, ('a',), x^2 + 2*x + 2, None, '{}', 3, 2, True), {}) sage: GF.create_key_and_extra_args(9, 'a', foo='value') ((9, ('a',), x^2 + 2*x + 2, None, "{'foo': 'value'}", 3, 2, True), {'foo': 'value'}) """ from sage.structure.proof.all import WithProof, arithmetic if proof is None: proof = arithmetic() with WithProof('arithmetic', proof): order = int(order) if order <= 1: raise ValueError("the order of a finite field must be > 1.") if arith.is_prime(order): name = None modulus = None p = integer.Integer(order) n = integer.Integer(1) elif arith.is_prime_power(order): if not names is None: name = names name = normalize_names(1,name) p, n = arith.factor(order)[0] # The following is a temporary solution that allows us # to construct compatible systems of finite fields # until algebraic closures of finite fields are # implemented in Sage. It requires the user to # specify two parameters: # # - `conway` -- boolean; if True, this field is # constructed to fit in a compatible system using # a Conway polynomial. # - `prefix` -- a string used to generate names for # automatically constructed finite fields # # See the docstring of FiniteFieldFactory for examples. # # Once algebraic closures of finite fields are # implemented, this syntax should be superseded by # something like the following: # # sage: Fpbar = GF(5).algebraic_closure('z') # sage: F, e = Fpbar.subfield(3) # e is the embedding into Fpbar # sage: F # Finite field in z3 of size 5^3 # # This temporary solution only uses actual Conway # polynomials (no pseudo-Conway polynomials), since # pseudo-Conway polynomials are not unique, and until # we have algebraic closures of finite fields, there # is no good place to store a specific choice of # pseudo-Conway polynomials. if name is None: if not (kwds.has_key('conway') and kwds['conway']): raise ValueError("parameter 'conway' is required if no name given") if not kwds.has_key('prefix'): raise ValueError("parameter 'prefix' is required if no name given") name = kwds['prefix'] + str(n) if kwds.has_key('conway') and kwds['conway']: from conway_polynomials import conway_polynomial if not kwds.has_key('prefix'): raise ValueError("a prefix must be specified if conway=True") if modulus is not None: raise ValueError("no modulus may be specified if conway=True") # The following raises a RuntimeError if no polynomial is found. modulus = conway_polynomial(p, n) if modulus is None or isinstance(modulus, str): # A string specifies an algorithm to find a suitable modulus. if modulus == "default": # for backward compatibility modulus = None modulus = GF(p)['x'].irreducible_element(n, algorithm=modulus) elif isinstance(modulus, (list, tuple)): modulus = GF(p)['x'](modulus) elif sage.rings.polynomial.polynomial_element.is_Polynomial(modulus): modulus = modulus.change_variable_name('x') else: raise TypeError("wrong type for modulus parameter") else: raise ValueError("the order of a finite field must be a prime power.") return (order, name, modulus, impl, str(kwds), p, n, proof), kwds
def hadamard_matrix(n,existence=False, check=True): r""" Tries to construct a Hadamard matrix using a combination of Paley and Sylvester constructions. 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: - ``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 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. EXAMPLES:: sage: hadamard_matrix(12).det() 2985984 sage: 12^6 2985984 sage: hadamard_matrix(1) [1] sage: hadamard_matrix(2) [ 1 1] [ 1 -1] sage: hadamard_matrix(8) # random [ 1 1 1 1 1 1 1 1] [ 1 -1 1 -1 1 -1 1 -1] [ 1 1 -1 -1 1 1 -1 -1] [ 1 -1 -1 1 1 -1 -1 1] [ 1 1 1 1 -1 -1 -1 -1] [ 1 -1 1 -1 -1 1 -1 1] [ 1 1 -1 -1 -1 -1 1 1] [ 1 -1 -1 1 -1 1 1 -1] sage: hadamard_matrix(8).det() == 8^4 True We note that the method `hadamard_matrix()` returns a normalised Hadamard matrix (the entries in the first row and column are all +1) :: sage: hadamard_matrix(12) # random [ 1 1| 1 1| 1 1| 1 1| 1 1| 1 1] [ 1 -1|-1 1|-1 1|-1 1|-1 1|-1 1] [-----+-----+-----+-----+-----+-----] [ 1 -1| 1 -1| 1 1|-1 -1|-1 -1| 1 1] [ 1 1|-1 -1| 1 -1|-1 1|-1 1| 1 -1] [-----+-----+-----+-----+-----+-----] [ 1 -1| 1 1| 1 -1| 1 1|-1 -1|-1 -1] [ 1 1| 1 -1|-1 -1| 1 -1|-1 1|-1 1] [-----+-----+-----+-----+-----+-----] [ 1 -1|-1 -1| 1 1| 1 -1| 1 1|-1 -1] [ 1 1|-1 1| 1 -1|-1 -1| 1 -1|-1 1] [-----+-----+-----+-----+-----+-----] [ 1 -1|-1 -1|-1 -1| 1 1| 1 -1| 1 1] [ 1 1|-1 1|-1 1| 1 -1|-1 -1| 1 -1] [-----+-----+-----+-----+-----+-----] [ 1 -1| 1 1|-1 -1|-1 -1| 1 1| 1 -1] [ 1 1| 1 -1|-1 1|-1 1| 1 -1|-1 -1] TESTS:: sage: matrix.hadamard(10,existence=True) False sage: matrix.hadamard(12,existence=True) True sage: matrix.hadamard(92,existence=True) Unknown sage: matrix.hadamard(10) Traceback (most recent call last): ... ValueError: The Hadamard matrix of order 10 does not exist """ if not(n % 4 == 0) and (n > 2): if existence: return False raise ValueError("The Hadamard matrix of order %s does not exist" % n) if n == 2: if existence: return True M = matrix([[1, 1], [1, -1]]) elif n == 1: if existence: return True M = matrix([1]) elif is_prime_power(n//2 - 1) and (n//2 - 1) % 4 == 1: if existence: return True M = hadamard_matrix_paleyII(n) elif n == 4 or n % 8 == 0: if existence: return hadamard_matrix(n//2,existence=True) had = hadamard_matrix(n//2,check=False) chad1 = matrix([list(r) + list(r) for r in had.rows()]) mhad = (-1) * had R = len(had.rows()) chad2 = matrix([list(had.rows()[i]) + list(mhad.rows()[i]) for i in range(R)]) M = chad1.stack(chad2) elif is_prime_power(n - 1) and (n - 1) % 4 == 3: if existence: return True M = hadamard_matrix_paleyI(n) else: if existence: return Unknown raise ValueError("The Hadamard matrix of order %s is not yet implemented." % n) if check: assert is_hadamard_matrix(M, normalized=True) return M
def GDD_4_2(q, existence=False, check=True): r""" Return a `(2q,\{4\},\{2\})`-GDD for `q` a prime power with `q\equiv 1\pmod{6}`. This method implements Lemma VII.5.17 from [BJL99] (p.495). INPUT: - ``q`` (integer) - ``existence`` (boolean) -- instead of building the design, return: - ``True`` -- meaning that Sage knows how to build the design - ``Unknown`` -- meaning that Sage does not know how to build the design, but that the design may exist (see :mod:`sage.misc.unknown`). - ``False`` -- meaning that the design does not exist. - ``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. EXAMPLE:: sage: from sage.combinat.designs.group_divisible_designs import GDD_4_2 sage: GDD_4_2(7,existence=True) True sage: GDD_4_2(7) Group Divisible Design on 14 points of type 2^7 sage: GDD_4_2(8,existence=True) Unknown sage: GDD_4_2(8) Traceback (most recent call last): ... NotImplementedError """ if q <= 1 or q % 6 != 1 or not is_prime_power(q): if existence: return Unknown raise NotImplementedError if existence: return True from sage.rings.finite_rings.constructor import FiniteField as GF G = GF(q, 'x') w = G.primitive_element() e = w**((q - 1) / 3) # A first parallel class is defined. G acts on it, which yields all others. first_class = [[(0, 0), (1, w**i), (1, e * w**i), (1, e * e * w**i)] for i in range((q - 1) / 6)] label = {p: i for i, p in enumerate(G)} classes = [[[2 * label[x[1] + g] + (x[0] + j) % 2 for x in S] for S in first_class] for g in G for j in range(2)] return GroupDivisibleDesign(2 * q, groups=[[i, i + 1] for i in range(0, 2 * q, 2)], blocks=sum(classes, []), K=[4], G=[2], check=check, copy=False)
def v_4_1_rbibd(v, existence=False): r""" Return a `(v,4,1)`-RBIBD. INPUT: - `n` (integer) - ``existence`` (boolean; ``False`` by default) -- whether to build the design or only answer whether it exists. .. SEEALSO:: - :meth:`IncidenceStructure.is_resolvable` - :func:`resolvable_balanced_incomplete_block_design` .. NOTE:: A resolvable `(v,4,1)`-BIBD exists whenever `1\equiv 4\pmod(12)`. This function, however, only implements a construction of `(v,4,1)`-BIBD such that `v=3q+1\equiv 1\pmod{3}` where `q` is a prime power (see VII.7.5.a from [BJL99]_). EXAMPLE:: sage: rBIBD = designs.resolvable_balanced_incomplete_block_design(28,4) sage: rBIBD.is_resolvable() True sage: rBIBD.is_t_design(return_parameters=True) (True, (2, 28, 4, 1)) TESTS:: sage: for q in prime_powers(2,30): ....: if (3*q+1)%12 == 4: ....: _ = designs.resolvable_balanced_incomplete_block_design(3*q+1,4) # indirect doctest """ # Volume 1, VII.7.5.a from [BJL99]_ if v % 3 != 1 or not is_prime_power((v - 1) // 3): if existence: return Unknown raise NotImplementedError( "I don't know how to build a ({},{},1)-RBIBD!".format(v, 4)) from sage.rings.finite_rings.constructor import FiniteField as GF q = (v - 1) // 3 nn = (q - 1) // 4 G = GF(q, 'x') w = G.primitive_element() e = w**(nn) assert e**2 == -1 first_class = [[(w**i, j), (-w**i, j), (e * w**i, j + 1), (-e * w**i, j + 1)] for i in range(nn) for j in range(3)] first_class.append([(0, 0), (0, 1), (0, 2), 'inf']) label = {p: i for i, p in enumerate(G)} classes = [[[ v - 1 if x == 'inf' else (x[1] % 3) * q + label[x[0] + g] for x in S ] for S in first_class] for g in G] BIBD = BalancedIncompleteBlockDesign(v, blocks=sum(classes, []), k=4, check=True, copy=False) BIBD._classes = classes assert BIBD.is_resolvable() return BIBD
def hadamard_matrix_paleyI(n, normalize=True): """ Implements the Paley type I construction. The Paley type I case corresponds to the case `p \cong 3 \mod{4}` for a prime `p` (see [Hora]_). INPUT: - ``n`` -- the matrix size - ``normalize`` (boolean) -- whether to normalize the result. EXAMPLES: We note that this method by default returns a normalised Hadamard matrix :: sage: from sage.combinat.matrices.hadamard_matrix import hadamard_matrix_paleyI sage: hadamard_matrix_paleyI(4) [ 1 1 1 1] [ 1 -1 1 -1] [ 1 -1 -1 1] [ 1 1 -1 -1] Otherwise, it returns a skew Hadamard matrix `H`, i.e. `H=S+I`, with `S=-S^\top` :: sage: M=hadamard_matrix_paleyI(4, normalize=False); M [ 1 1 1 1] [-1 1 1 -1] [-1 -1 1 1] [-1 1 -1 1] sage: S=M-identity_matrix(4); -S==S.T True TESTS:: sage: from sage.combinat.matrices.hadamard_matrix import is_hadamard_matrix sage: test_cases = [x+1 for x in range(100) if is_prime_power(x) and x%4==3] sage: all(is_hadamard_matrix(hadamard_matrix_paleyI(n),normalized=True,verbose=True) ....: for n in test_cases) True sage: all(is_hadamard_matrix(hadamard_matrix_paleyI(n,normalize=False),verbose=True) ....: for n in test_cases) True """ p = n - 1 if not(is_prime_power(p) and (p % 4 == 3)): raise ValueError("The order %s is not covered by the Paley type I construction." % n) from sage.rings.finite_rings.constructor import FiniteField K = FiniteField(p,'x') K_list = list(K) K_list.insert(0,K.zero()) H = matrix(ZZ, [[(1 if (x-y).is_square() else -1) for x in K_list] for y in K_list]) for i in range(n): H[i,0] = -1 H[0,i] = 1 if normalize: for i in range(n): H[i,i] = -1 H = normalise_hadamard(H) return H
def v_4_1_rbibd(v,existence=False): r""" Return a `(v,4,1)`-RBIBD. INPUT: - `n` (integer) - ``existence`` (boolean; ``False`` by default) -- whether to build the design or only answer whether it exists. .. SEEALSO:: - :meth:`IncidenceStructure.is_resolvable` - :func:`resolvable_balanced_incomplete_block_design` .. NOTE:: A resolvable `(v,4,1)`-BIBD exists whenever `1\equiv 4\pmod(12)`. This function, however, only implements a construction of `(v,4,1)`-BIBD such that `v=3q+1\equiv 1\pmod{3}` where `q` is a prime power (see VII.7.5.a from [BJL99]_). EXAMPLE:: sage: rBIBD = designs.resolvable_balanced_incomplete_block_design(28,4) sage: rBIBD.is_resolvable() True sage: rBIBD.is_t_design(return_parameters=True) (True, (2, 28, 4, 1)) TESTS:: sage: for q in prime_powers(2,30): ....: if (3*q+1)%12 == 4: ....: _ = designs.resolvable_balanced_incomplete_block_design(3*q+1,4) # indirect doctest """ # Volume 1, VII.7.5.a from [BJL99]_ if v%3 != 1 or not is_prime_power((v-1)//3): if existence: return Unknown raise NotImplementedError("I don't know how to build a ({},{},1)-RBIBD!".format(v,4)) from sage.rings.finite_rings.constructor import FiniteField as GF q = (v-1)//3 nn = (q-1)//4 G = GF(q,'x') w = G.primitive_element() e = w**(nn) assert e**2 == -1 first_class = [[(w**i,j),(-w**i,j),(e*w**i,j+1),(-e*w**i,j+1)] for i in range(nn) for j in range(3)] first_class.append([(0,0),(0,1),(0,2),'inf']) label = {p:i for i,p in enumerate(G)} classes = [[[v-1 if x=='inf' else (x[1]%3)*q+label[x[0]+g] for x in S] for S in first_class] for g in G] BIBD = BalancedIncompleteBlockDesign(v, blocks = sum(classes,[]), k=4, check=True, copy=False) BIBD._classes = classes assert BIBD.is_resolvable() return BIBD
def difference_matrix(g, k, lmbda=1, existence=False, check=True): r""" Return a `(g,k,\lambda)`-difference matrix A matrix `M` is a `(g,k,\lambda)`-difference matrix if its entries are element of a group `G` of cardinality `g`, and if for any two rows `R,R'` of `M` and `x\in G` there are exactly `\lambda` values `i` such that `R_i-R'_i=x`. INPUT: - ``k`` -- (integer) number of columns. If ``k=None`` it is set to the largest value available. - ``g`` -- (integer) cardinality of the group `G` - ``lmbda`` -- (integer; default: 1) -- number of times each element of `G` appears as a difference. - ``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. - ``existence`` (boolean) -- instead of building the design, return: - ``True`` -- meaning that Sage knows how to build the design - ``Unknown`` -- meaning that Sage does not know how to build the design, but that the design may exist (see :mod:`sage.misc.unknown`). - ``False`` -- meaning that the design does not exist. .. NOTE:: When ``k=None`` and ``existence=True`` the function returns an integer, i.e. the largest `k` such that we can build a `(g,k,\lambda)`-DM. EXAMPLES:: sage: G,M = designs.difference_matrix(25,10); G Finite Field in x of size 5^2 sage: designs.difference_matrix(993,None,existence=1) 32 TESTS:: sage: designs.difference_matrix(10,12,1,existence=True) False sage: designs.difference_matrix(10,12,1) Traceback (most recent call last): ... EmptySetError: No (10,12,1)-Difference Matrix exists as k(=12)>g(=10) sage: designs.difference_matrix(10,9,1,existence=True) Unknown sage: designs.difference_matrix(10,9,1) Traceback (most recent call last): ... NotImplementedError: I don't know how to build a (10,9,1)-Difference Matrix! """ if lmbda == 1 and k is not None and k > g: if existence: return False raise EmptySetError( "No ({},{},{})-Difference Matrix exists as k(={})>g(={})".format( g, k, lmbda, k, g)) # Prime powers elif lmbda == 1 and is_prime_power(g): if k is None: if existence: return g else: k = g elif existence: return True F = FiniteField(g, 'x') F_set = list(F) F_k_set = F_set[:k] G = F M = [[x * y for y in F_k_set] for x in F_set] # From the database elif (g, lmbda) in DM_constructions and ( k is None or DM_constructions[g, lmbda][0] >= k): if k is None: k = DM_constructions[g, lmbda][0] if existence: return k elif existence: return True _, f = DM_constructions[g, lmbda] G, M = f() M = [R[:k] for R in M] else: if existence: return Unknown raise NotImplementedError( "I don't know how to build a ({},{},{})-Difference Matrix!".format( g, k, lmbda)) if check: assert is_difference_matrix( M, G, k, lmbda, 1), "Sage built something which is not a ({},{},{})-DM!".format( g, k, lmbda) return G, M