def is_supersingular(E): p = E.base_field().characteristic() j = E.j_invariant() if not j in GF(p**2): return False if p <= 3: return j == 0 F = ClassicalModularPolynomialDatabase()[2] x = PolynomialRing(GF(p**2), 'x').gen() f = F(x, j) roots = [i[0] for i in f.roots() for _ in range(i[1])] if len(roots) < 3: return False vertices = [j, j, j] m = floor(log(p, 2)) + 1 for k in range(1, m + 1): for i in range(3): f = F(x, roots[i]) g = x - vertices[i] a = f.quo_rem(g)[0] vertices[i] = roots[i] attempt = a.roots() if len(attempt) == 0: return False roots[i] = attempt[0][0] return True
def p_saturation(A, p, proof=True): """ INPUT: - A -- a matrix over ZZ - p -- a prime - proof -- bool (default: True) OUTPUT: The p-saturation of the matrix A, i.e., a new matrix in Hermite form whose row span a ZZ-module that is p-saturated. EXAMPLES:: sage: from sage.matrix.matrix_integer_dense_saturation import p_saturation sage: A = matrix(ZZ, 2, 2, [3,2,3,4]); B = matrix(ZZ, 2,3,[1,2,3,4,5,6]) sage: A.det() 6 sage: C = A*B; C [11 16 21] [19 26 33] sage: C2 = p_saturation(C, 2); C2 [ 1 8 15] [ 0 9 18] sage: C2.index_in_saturation() 9 sage: C3 = p_saturation(C, 3); C3 [ 1 0 -1] [ 0 2 4] sage: C3.index_in_saturation() 2 """ tm = verbose("%s-saturating a %sx%s matrix" % (p, A.nrows(), A.ncols())) H = A.hermite_form(include_zero_rows=False, proof=proof) while True: if p == 2: A = H.change_ring(GF(p)) else: try: # Faster than change_ring A = H._reduce(p) except OverflowError: # fall back to generic GF(p) matrices A = H.change_ring(GF(p)) assert A.nrows() <= A.ncols() K = A.kernel() if K.dimension() == 0: verbose("done saturating", tm) return H B = K.basis_matrix().lift() C = ((B * H) / p).change_ring(ZZ) H = H.stack(C).hermite_form(include_zero_rows=False, proof=proof) verbose("done saturating", tm)
def elkies_walk(E, l, lam, k): j_0 = E.j_invariant() if k == 0: return E lam = GF(l)(lam) q = E.base_field().order() mu = GF(l)(lam ** (-1) * q) if k < 0: return elkies_walk(E.quadratic_twist(), l, -mu, -k) j_1 = elkies_first_step(E, l, lam) for i in range(2, k + 1): j_0, j_1 = j_1, elkies_next_step(j_0, j_1, l, lam) return EllipticCurve_from_j(j_1)
def walk_the_crater(E,l,lam): k = E.base_field() q = k.order() lam = GF(l)(lam) r = lam.multiplicative_order() t = E.trace_of_frobenius() order = rec_order(q, t, r) cur = extend_field(E, r) crater = [E.j_invariant()] cur = velu_step(cur, l, lam, order, q) while k(cur.j_invariant())!=E.j_invariant(): crater.append(k(cur.j_invariant())) cur = velu_step(cur, l, lam, order, q) return crater
def orbits_lines_mod_p(self, p): r""" Let `(L, q)` be a lattice. This returns representatives of the orbits of lines in `L/pL` under the orthogonal group of `q`. INPUT: - ``p`` -- a prime number OUTPUT: - a list of vectors over ``GF(p)`` EXAMPLES:: sage: from sage.quadratic_forms.quadratic_form__neighbors import orbits_lines_mod_p sage: Q = QuadraticForm(ZZ, 3, [1, 0, 0, 2, 1, 3]) sage: Q.orbits_lines_mod_p(2) [(0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)] """ from sage.libs.gap.libgap import libgap # careful the self.automorphism_group() acts from the left # but in gap we act from the right!! --> transpose gens = self.automorphism_group().gens() gens = [g.matrix().transpose().change_ring(GF(p)) for g in gens] orbs = libgap.function_factory( """function(gens, p) local one, G, reps, V, n, orb; one:= One(GF(p)); G:=Group(List(gens, g -> g*one)); n:= Size(gens[1]); V:= GF(p)^n; orb:= OrbitsDomain(G, V, OnLines); reps:= List(orb, g->g[1]); return reps; end;""") # run this at startup if you need more memory... #from sage.interfaces.gap import get_gap_memory_pool_size, set_gap_memory_pool_size #memory_gap = get_gap_memory_pool_size() #set_gap_memory_pool_size(1028*memory_gap) orbs_reps = orbs(gens, p) #set_gap_memory_pool_size(memory_gap) M = GF(p)**self.dim() return [M(m.sage()) for m in orbs_reps if not m.IsZero()]
def count_points(self, n): r""" Count points over `\GF{q}, \ldots, \GF{q^n}` on a scheme over a finite field `\GF{q}`. .. note:: This is currently only implemented for schemes over prime order finite fields. EXAMPLES:: sage: P.<x> = PolynomialRing(GF(3)) sage: C = HyperellipticCurve(x^3+x^2+1) sage: C.count_points(4) [6, 12, 18, 96] sage: C.base_extend(GF(9,'a')).count_points(2) Traceback (most recent call last): ... NotImplementedError: Point counting only implemented for schemes over prime fields """ F = self.base_ring() if not F.is_finite(): raise TypeError, "Point counting only defined for schemes over finite fields" q = F.cardinality() if not q.is_prime(): raise NotImplementedError, "Point counting only implemented for schemes over prime fields" a = [] for i in range(1, n + 1): F1 = GF(q**i, name='z') S1 = self.base_extend(F1) a.append(len(S1.rational_points())) return (a)
def _min_nonsquare(p): r""" Return the minimal nonsquare modulo the prime `p`. INPUT: - ``p`` -- a prime number OUTPUT: - ``a`` -- the minimal nonsquare mod `p` EXAMPLES:: sage: from sage.quadratic_forms.genera.normal_form import _min_nonsquare sage: _min_nonsquare(2) sage: _min_nonsquare(3) 2 sage: _min_nonsquare(5) 2 sage: _min_nonsquare(7) 3 """ R = GF(p) for i in R: if not R(i).is_square(): return i
def nullspace_GF(n=300, p=16411, system='sage'): """ Given a n+1 x n matrix over GF(p) with random entries, compute the nullspace. INPUT: - ``n`` - matrix dimension (default: 300) - ``p`` - prime number (default: ``16411``) - ``system`` - either 'magma' or 'sage' (default: 'sage') EXAMPLES:: sage: import sage.matrix.benchmark as b sage: ts = b.nullspace_GF(300) sage: tm = b.nullspace_GF(300, system='magma') # optional - magma """ if system == 'sage': A = random_matrix(GF(p), n, n + 1) t = cputime() v = A.kernel() return cputime(t) elif system == 'magma': code = """ n := %s; A := Random(RMatrixSpace(GF(%s), n, n+1)); t := Cputime(); K := Kernel(A); s := Cputime(t); """ % (n, p) if verbose: print(code) magma.eval(code) return magma.eval('s') else: raise ValueError('unknown system "%s"' % system)
def rank2_GF(n=500, p=16411, system='sage'): """ Rank over GF(p): Given a (n + 10) x n matrix over GF(p) with random entries, compute the rank. INPUT: - ``n`` - matrix dimension (default: 300) - ``p`` - prime number (default: ``16411``) - ``system`` - either 'magma' or 'sage' (default: 'sage') EXAMPLES:: sage: import sage.matrix.benchmark as b sage: ts = b.rank2_GF(500) sage: tm = b.rank2_GF(500, system='magma') # optional - magma """ if system == 'sage': A = random_matrix(GF(p), n + 10, n) t = cputime() v = A.rank() return cputime(t) elif system == 'magma': code = """ n := %s; A := Random(MatrixAlgebra(GF(%s), n)); t := Cputime(); K := Rank(A); s := Cputime(t); """ % (n, p) if verbose: print(code) magma.eval(code) return float(magma.eval('s')) else: raise ValueError('unknown system "%s"' % system)
def charpoly_GF(n=100, p=16411, system='sage'): """ Given a n x n matrix over GF with random entries, compute the charpoly. INPUT: - ``n`` - matrix dimension (default: 100) - ``p`` - prime number (default: ``16411``) - ``system`` - either 'magma' or 'sage' (default: 'sage') EXAMPLES:: sage: import sage.matrix.benchmark as b sage: ts = b.charpoly_GF(100) sage: tm = b.charpoly_GF(100, system='magma') # optional - magma """ if system == 'sage': A = random_matrix(GF(p), n, n) t = cputime() v = A.charpoly() return cputime(t) elif system == 'magma': code = """ n := %s; A := Random(MatrixAlgebra(GF(%s), n)); t := Cputime(); K := CharacteristicPolynomial(A); s := Cputime(t); """ % (n, p) if verbose: print(code) magma.eval(code) return magma.eval('s') else: raise ValueError('unknown system "%s"' % system)
def SU(n, F, var='a'): """ Return the special unitary group of degree `n` over `F`. .. note:: This group is also available via ``groups.matrix.SU()``. EXAMPLES:: sage: SU(3,5) Special Unitary Group of degree 3 over Finite Field of size 5 sage: SU(3,QQ) Traceback (most recent call last): ... NotImplementedError: special unitary group only implemented over finite fields TESTS:: sage: groups.matrix.SU(2, 3) Special Unitary Group of degree 2 over Finite Field of size 3 """ if isinstance(F, (int, long, Integer)): F = GF(F, var) if is_FiniteField(F): return SpecialUnitaryGroup_finite_field(n, F, var=var) else: raise NotImplementedError, "special unitary group only implemented over finite fields"
def _UG(n, R, special, var='a', invariant_form=None): r""" This function is commonly used by the functions :func:`GU` and :func:`SU` to avoid duplicated code. For documentation and examples see the individual functions. TESTS:: sage: GU(3,25).order() # indirect doctest 3961191000000 """ prefix = 'General' latex_prefix = 'G' if special: prefix = 'Special' latex_prefix = 'S' degree, ring = normalize_args_vectorspace(n, R, var=var) if is_FiniteField(ring): q = ring.cardinality() ring = GF(q**2, name=var) if invariant_form is not None: raise NotImplementedError( "invariant_form for finite groups is fixed by GAP") if invariant_form is not None: invariant_form = normalize_args_invariant_form(ring, degree, invariant_form) if not invariant_form.is_hermitian(): raise ValueError("invariant_form must be hermitian") try: if invariant_form.is_positive_definite(): inserted_text = "with respect to positive definite hermitian form" else: inserted_text = "with respect to non positive definite hermitian form" except ValueError: inserted_text = "with respect to hermitian form" name = '{0} Unitary Group of degree {1} over {2} {3}\n{4}'.format( prefix, degree, ring, inserted_text, invariant_form) ltx = r'\text{{{0}U}}_{{{1}}}({2})\text{{ {3} }}{4}'.format( latex_prefix, degree, latex(ring), inserted_text, latex(invariant_form)) else: name = '{0} Unitary Group of degree {1} over {2}'.format( prefix, degree, ring) ltx = r'\text{{{0}U}}_{{{1}}}({2})'.format(latex_prefix, degree, latex(ring)) if is_FiniteField(ring): cmd = '{0}U({1}, {2})'.format(latex_prefix, degree, q) return UnitaryMatrixGroup_gap(degree, ring, special, name, ltx, cmd) else: return UnitaryMatrixGroup_generic(degree, ring, special, name, ltx, invariant_form=invariant_form)
def GU(n, F, var='a'): """ Return the general unitary group of degree n over the finite field F. INPUT: - ``n`` - a positive integer - ``F`` - finite field - ``var`` - variable used to represent generator of quadratic extension of F, if needed. .. note:: This group is also available via ``groups.matrix.GU()``. EXAMPLES:: sage: G = GU(3,GF(7)); G General Unitary Group of degree 3 over Finite Field of size 7 sage: G.gens() [ [ a 0 0] [ 0 1 0] [ 0 0 5*a], [6*a 6 1] [ 6 6 0] [ 1 0 0] ] sage: G = GU(2,QQ) Traceback (most recent call last): ... NotImplementedError: general unitary group only implemented over finite fields :: sage: G = GU(3,GF(5), var='beta') sage: G.gens() [ [ beta 0 0] [ 0 1 0] [ 0 0 3*beta], [4*beta 4 1] [ 4 4 0] [ 1 0 0] ] TESTS:: sage: groups.matrix.GU(2, 3) General Unitary Group of degree 2 over Finite Field of size 3 """ if isinstance(F, (int, long, Integer)): F = GF(F, var) if is_FiniteField(F): return GeneralUnitaryGroup_finite_field(n, F, var) else: raise NotImplementedError, "general unitary group only implemented over finite fields"
def _compute_q_expansion_basis(self, prec=None): """ Compute q-expansions for a basis of self to precision prec. EXAMPLES:: sage: M = ModularForms(23,2,base_ring=GF(7)) sage: M._compute_q_expansion_basis(10) [q + 6*q^3 + 6*q^4 + 5*q^6 + 2*q^7 + 6*q^8 + 2*q^9 + O(q^10), q^2 + 5*q^3 + 6*q^4 + 2*q^5 + q^6 + 2*q^7 + 5*q^8 + O(q^10), 1 + 5*q^3 + 5*q^4 + 5*q^6 + 3*q^8 + 5*q^9 + O(q^10)] TESTS: This checks that :trac:`13445` is fixed:: sage: M = ModularForms(Gamma1(29), base_ring=GF(29)) sage: S = M.cuspidal_subspace() sage: 0 in [f.valuation() for f in S.basis()] False sage: len(S.basis()) == dimension_cusp_forms(Gamma1(29), 2) True """ if prec is None: prec = self.prec() R = self._q_expansion_ring() c = self.base_ring().characteristic() if c == 0: B = self.__M.q_expansion_basis(prec) return [R(f) for f in B] elif c.is_prime_power(): K = self.base_ring() p = K.characteristic().prime_factors()[0] from sage.rings.all import GF Kp = GF(p) newB = [f.change_ring(K) for f in list(self.__M.cuspidal_subspace().q_integral_basis(prec))] A = Kp**prec gens = [f.padded_list(prec) for f in newB] V = A.span(gens) B = [f.change_ring(K) for f in self.__M.q_integral_basis(prec)] for f in B: fc = f.padded_list(prec) gens.append(fc) if not A.span(gens) == V: newB.append(f) V = A.span(gens) if len(newB) != self.dimension(): raise RuntimeError("The dimension of the space is %s but the basis we computed has %s elements"%(self.dimension(), len(newB))) lst = [R(f) for f in newB] return [f/f[f.valuation()] for f in lst] else: # this returns a basis of q-expansions, without guaranteeing that # the first vectors form a basis of the cuspidal subspace # TODO: bring this in line with the other cases # simply using the above code fails because free modules over # general rings do not have a .span() method B = self.__M.q_integral_basis(prec) return [R(f) for f in B]
def SU(n, R, var='a'): """ The special unitary group `SU( d, R )` consists of all `d \times d` matrices that preserve a nondegenerate sesquilinear form over the ring `R` and have determinant one. .. note:: For a finite field the matrices that preserve a sesquilinear form over `F_q` live over `F_{q^2}`. So ``SU(n,q)`` for integer ``q`` constructs the matrix group over the base ring ``GF(q^2)``. .. note:: This group is also available via ``groups.matrix.SU()``. INPUT: - ``n`` -- a positive integer. - ``R`` -- ring or an integer. If an integer is specified, the corresponding finite field is used. - ``var`` -- variable used to represent generator of the finite field, if needed. OUTPUT: Return the special unitary group. EXAMPLES:: sage: SU(3,5) Special Unitary Group of degree 3 over Finite Field in a of size 5^2 sage: SU(3, GF(5)) Special Unitary Group of degree 3 over Finite Field in a of size 5^2 sage: SU(3,QQ) Special Unitary Group of degree 3 over Rational Field TESTS:: sage: groups.matrix.SU(2, 3) Special Unitary Group of degree 2 over Finite Field in a of size 3^2 """ degree, ring = normalize_args_vectorspace(n, R, var=var) if is_FiniteField(ring): q = ring.cardinality() ring = GF(q**2, name=var) name = 'Special Unitary Group of degree {0} over {1}'.format(degree, ring) ltx = r'\text{{SU}}_{{{0}}}({1})'.format(degree, latex(ring)) if is_FiniteField(ring): cmd = 'SU({0}, {1})'.format(degree, q) return UnitaryMatrixGroup_gap(degree, ring, True, name, ltx, cmd) else: return UnitaryMatrixGroup_generic(degree, ring, True, name, ltx)
def matrix_add_GF(n=1000, p=16411, system='sage', times=100): """ Given two n x n matrix over GF(p) with random entries, add them. INPUT: - ``n`` - matrix dimension (default: 300) - ``p`` - prime number (default: ``16411``) - ``system`` - either 'magma' or 'sage' (default: 'sage') - ``times`` - number of experiments (default: ``100``) EXAMPLES:: sage: import sage.matrix.benchmark as b sage: ts = b.matrix_add_GF(500, p=19) sage: tm = b.matrix_add_GF(500, p=19, system='magma') # optional - magma """ if system == 'sage': A = random_matrix(GF(p), n, n) B = random_matrix(GF(p), n, n) t = cputime() for n in range(times): v = A + B return cputime(t) elif system == 'magma': code = """ n := %s; A := Random(MatrixAlgebra(GF(%s), n)); B := Random(MatrixAlgebra(GF(%s), n)); t := Cputime(); for z in [1..%s] do K := A + B; end for; s := Cputime(t); """ % (n, p, p, times) if verbose: print(code) magma.eval(code) return magma.eval('s') else: raise ValueError('unknown system "%s"' % system)
def residue_ring(self): """ Return the residue field of this valuation. EXAMPLES:: sage: v = ZZ.valuation(3) sage: v.residue_ring() Finite Field of size 3 """ from sage.rings.all import GF return GF(self.p())
def elkies_first_step(E, l, lam): q = E.base_field().order() lam = GF(l)(lam) Phi = ClassicalModularPolynomialDatabase()[l] x = PolynomialRing(E.base_field(), 'x').gen() f = Phi(x, E.j_invariant()) j_1, j_2 = f.roots()[0][0], f.roots()[1][0] E1 = elkies_mod_poly(E, j_1, l) try: I = EllipticCurveIsogeny(E, None, E1, l) except: I = l_isogeny(E, E1, l) r = lam.multiplicative_order() k = GF(q ** r) ext = extend_field(E, r) try: P = ext.lift_x(I.kernel_polynomial().any_root(k)) except: return j_2 if ext(P[0] ** q, P[1] ** q) == Integer(lam) * P: return j_1 else: return j_2
def residue_ring(self): """ Return the residue field of this valuation. EXAMPLES:: sage: sys.path.append(os.getcwd()); from mac_lane import * # optional: standalone sage: v = pAdicValuation(ZZ, 3) sage: v.residue_ring() Finite Field of size 3 """ from sage.rings.all import GF return GF(self.p())
def SU(n, F, var='a'): """ Return the special unitary group of degree `n` over `F`. EXAMPLES:: sage: SU(3,5) Special Unitary Group of degree 3 over Finite Field of size 5 sage: SU(3,QQ) Traceback (most recent call last): ... NotImplementedError: special unitary group only implemented over finite fields """ if isinstance(F, (int, long, Integer)): F = GF(F, var) if is_FiniteField(F): return SpecialUnitaryGroup_finite_field(n, F, var=var) else: raise NotImplementedError, "special unitary group only implemented over finite fields"
def matrix_multiply_GF(n=100, p=16411, system='sage', times=3): """ Given an n x n matrix A over GF(p) with random entries, compute A * (A+1). INPUT: - ``n`` - matrix dimension (default: 100) - ``p`` - prime number (default: ``16411``) - ``system`` - either 'magma' or 'sage' (default: 'sage') - ``times`` - number of experiments (default: ``3``) EXAMPLES:: sage: import sage.matrix.benchmark as b sage: ts = b.matrix_multiply_GF(100, p=19) sage: tm = b.matrix_multiply_GF(100, p=19, system='magma') # optional - magma """ if system == 'sage': A = random_matrix(GF(p), n) B = A + 1 t = cputime() for n in range(times): v = A * B return cputime(t) / times elif system == 'magma': code = """ n := %s; A := Random(MatrixAlgebra(GF(%s), n)); B := A + 1; t := Cputime(); for z in [1..%s] do K := A * B; end for; s := Cputime(t); """ % (n, p, times) if verbose: print code magma.eval(code) return float(magma.eval('s')) / times else: raise ValueError('unknown system "%s"' % system)
def field_of_definition(self): """ Return the field of definition of this general unity group. EXAMPLES:: sage: G = GU(3,GF(5)) sage: G.field_of_definition() Finite Field in a of size 5^2 sage: G.base_field() Finite Field of size 5 """ try: return self._field_of_definition except AttributeError: if self.base_ring().degree() % 2 == 0: k = self.base_ring() else: k = GF(self.base_ring().order()**2, names=self._var) self._field_of_definition = k return k
def velu_walk(E, l, lam, k): q = E.base_field().order() lam = GF(l)(lam) r = lam.multiplicative_order() mu = GF(l)((lam ** (-1)) * q) r_mu = mu.multiplicative_order() if k < 0: return velu_walk(E.quadratic_twist(), l, -mu, -k) if r_mu == r: print("Invalid parameters") return if r_mu < r: return velu_walk(E.quadratic_twist(), l, -lam, k) t = E.trace_of_frobenius() order = rec_order(q, t, r) cur = extend_field(E, r) for i in range(k): cur = velu_step(cur, l, lam, order, q) cur = EllipticCurve_from_j(GF(q)(cur.j_invariant())) return cur
def det_GF(n=400, p=16411, system='sage'): """ Dense determinant over GF(p). Given an n x n matrix A over GF with random entries compute det(A). INPUT: - ``n`` - matrix dimension (default: 300) - ``p`` - prime number (default: ``16411``) - ``system`` - either 'magma' or 'sage' (default: 'sage') EXAMPLES:: sage: import sage.matrix.benchmark as b sage: ts = b.det_GF(1000) sage: tm = b.det_GF(1000, system='magma') # optional - magma """ if system == 'sage': A = random_matrix(GF(p), n, n) t = cputime() d = A.determinant() return cputime(t) elif system == 'magma': code = """ n := %s; A := Random(MatrixAlgebra(GF(%s), n)); t := Cputime(); d := Determinant(A); s := Cputime(t); """ % (n, p) if verbose: print(code) magma.eval(code) return float(magma.eval('s')) else: raise ValueError('unknown system "%s"' % system)
def convert_to_milnor_matrix(n, basis, p=2, generic='auto'): r""" Change-of-basis matrix, 'basis' to Milnor, in dimension `n`, at the prime `p`. INPUT: - ``n`` - non-negative integer, the dimension - ``basis`` - string, the basis from which to convert - ``p`` - positive prime number (optional, default 2) OUTPUT: ``matrix`` - change-of-basis matrix, a square matrix over ``GF(p)`` EXAMPLES:: sage: from sage.algebras.steenrod.steenrod_algebra_bases import convert_to_milnor_matrix sage: convert_to_milnor_matrix(5, 'adem') # indirect doctest [0 1] [1 1] sage: convert_to_milnor_matrix(45, 'milnor') 111 x 111 dense matrix over Finite Field of size 2 (use the '.str()' method to see the entries) sage: convert_to_milnor_matrix(12,'wall') [1 0 0 1 0 0 0] [1 1 0 0 0 1 0] [0 1 0 1 0 0 0] [0 0 0 1 0 0 0] [1 1 0 0 1 0 0] [0 0 1 1 1 0 1] [0 0 0 0 1 0 1] The function takes an optional argument, the prime `p` over which to work:: sage: convert_to_milnor_matrix(17,'adem',3) [0 0 1 1] [0 0 0 1] [1 1 1 1] [0 1 0 1] sage: convert_to_milnor_matrix(48,'adem',5) [0 1] [1 1] sage: convert_to_milnor_matrix(36,'adem',3) [0 0 1] [0 1 0] [1 2 0] """ from sage.matrix.constructor import matrix from sage.rings.all import GF from .steenrod_algebra import SteenrodAlgebra if generic == 'auto': generic = False if p == 2 else True if n == 0: return matrix(GF(p), 1, 1, [[1]]) milnor_base = steenrod_algebra_basis(n, 'milnor', p, generic=generic) rows = [] A = SteenrodAlgebra(basis=basis, p=p, generic=generic) for poly in A.basis(n): d = poly.milnor().monomial_coefficients() for v in milnor_base: entry = d.get(v, 0) rows = rows + [entry] d = len(milnor_base) return matrix(GF(p), d, d, rows)
def lift_map(target): """ Create a lift map, to be used for lifting the cross ratios of a matroid representation. .. SEEALSO:: :meth:`lift_cross_ratios() <sage.matroids.utilities.lift_cross_ratios>` INPUT: - ``target`` -- a string describing the target (partial) field. OUTPUT: - a dictionary Depending on the value of ``target``, the following lift maps will be created: - "reg": a lift map from `\GF3` to the regular partial field `(\ZZ, <-1>)`. - "sru": a lift map from `\GF7` to the sixth-root-of-unity partial field `(\QQ(z), <z>)`, where `z` is a sixth root of unity. The map sends 3 to `z`. - "dyadic": a lift map from `\GF{11}` to the dyadic partial field `(\QQ, <-1, 2>)`. - "gm": a lift map from `\GF{19}` to the golden mean partial field `(\QQ(t), <-1,t>)`, where `t` is a root of `t^2-t-1`. The map sends `5` to `t`. The example below shows that the latter map satisfies three necessary conditions stated in :meth:`lift_cross_ratios() <sage.matroids.utilities.lift_cross_ratios>` EXAMPLES:: sage: from sage.matroids.utilities import lift_map sage: lm = lift_map('gm') sage: for x in lm: ....: if (x == 1) is not (lm[x] == 1): ....: print('not a proper lift map') ....: for y in lm: ....: if (x+y == 0) and not (lm[x]+lm[y] == 0): ....: print('not a proper lift map') ....: if (x+y == 1) and not (lm[x]+lm[y] == 1): ....: print('not a proper lift map') ....: for z in lm: ....: if (x*y==z) and not (lm[x]*lm[y]==lm[z]): ....: print('not a proper lift map') """ if target == "reg": R = GF(3) return {R(1): ZZ(1)} if target == "sru": R = GF(7) z = ZZ['z'].gen() S = NumberField(z * z - z + 1, 'z') return {R(1): S(1), R(3): S(z), R(3)**(-1): S(z)**5} if target == "dyadic": R = GF(11) return {R(1): QQ(1), R(-1): QQ(-1), R(2): QQ(2), R(6): QQ(1 / 2)} if target == "gm": R = GF(19) t = QQ['t'].gen() G = NumberField(t * t - t - 1, 't') return { R(1): G(1), R(5): G(t), R(1) / R(5): G(1) / G(t), R(-5): G(-t), R(-5)**(-1): G(-t)**(-1), R(5)**2: G(t)**2, R(5)**(-2): G(t)**(-2) } raise NotImplementedError(target)
def GU(n, R, var='a'): r""" Return the general unitary group. The general unitary group `GU( d, R )` consists of all `d \times d` matrices that preserve a nondegenerate sesquilinear form over the ring `R`. .. note:: For a finite field the matrices that preserve a sesquilinear form over `F_q` live over `F_{q^2}`. So ``GU(n,q)`` for integer ``q`` constructs the matrix group over the base ring ``GF(q^2)``. .. note:: This group is also available via ``groups.matrix.GU()``. INPUT: - ``n`` -- a positive integer. - ``R`` -- ring or an integer. If an integer is specified, the corresponding finite field is used. - ``var`` -- variable used to represent generator of the finite field, if needed. OUTPUT: Return the general unitary group. EXAMPLES:: sage: G = GU(3, 7); G General Unitary Group of degree 3 over Finite Field in a of size 7^2 sage: G.gens() ( [ a 0 0] [6*a 6 1] [ 0 1 0] [ 6 6 0] [ 0 0 5*a], [ 1 0 0] ) sage: GU(2,QQ) General Unitary Group of degree 2 over Rational Field sage: G = GU(3, 5, var='beta') sage: G.base_ring() Finite Field in beta of size 5^2 sage: G.gens() ( [ beta 0 0] [4*beta 4 1] [ 0 1 0] [ 4 4 0] [ 0 0 3*beta], [ 1 0 0] ) TESTS:: sage: groups.matrix.GU(2, 3) General Unitary Group of degree 2 over Finite Field in a of size 3^2 """ degree, ring = normalize_args_vectorspace(n, R, var=var) if is_FiniteField(ring): q = ring.cardinality() ring = GF(q**2, name=var) name = 'General Unitary Group of degree {0} over {1}'.format(degree, ring) ltx = r'\text{{GU}}_{{{0}}}({1})'.format(degree, latex(ring)) if is_FiniteField(ring): cmd = 'GU({0}, {1})'.format(degree, q) return UnitaryMatrixGroup_gap(degree, ring, False, name, ltx, cmd) else: return UnitaryMatrixGroup_generic(degree, ring, False, name, ltx)
def make_mono_admissible(mono, p=2, generic=None): r""" Given a tuple ``mono``, view it as a product of Steenrod operations, and return a dictionary giving data equivalent to writing that product as a linear combination of admissible monomials. When `p=2`, the sequence (and hence the corresponding monomial) `(i_1, i_2, ...)` is admissible if `i_j \geq 2 i_{j+1}` for all `j`. When `p` is odd, the sequence `(e_1, i_1, e_2, i_2, ...)` is admissible if `i_j \geq e_{j+1} + p i_{j+1}` for all `j`. INPUT: - ``mono`` - a tuple of non-negative integers - `p` - prime number, optional (default 2) - `generic` - whether to use the generic Steenrod algebra, (default: depends on prime) OUTPUT: Dictionary of terms of the form (tuple: coeff), where 'tuple' is an admissible tuple of non-negative integers and 'coeff' is its coefficient. This corresponds to a linear combination of admissible monomials. When `p` is odd, each tuple must have an odd length: it should be of the form `(e_1, i_1, e_2, i_2, ..., e_k)` where each `e_j` is either 0 or 1 and each `i_j` is a positive integer: this corresponds to the admissible monomial .. math:: \beta^{e_1} \mathcal{P}^{i_2} \beta^{e_2} \mathcal{P}^{i_2} ... \mathcal{P}^{i_k} \beta^{e_k} ALGORITHM: Given `(i_1, i_2, i_3, ...)`, apply the Adem relations to the first pair (or triple when `p` is odd) where the sequence is inadmissible, and then apply this function recursively to each of the resulting tuples `(i_1, ..., i_{j-1}, NEW, i_{j+2}, ...)`, keeping track of the coefficients. .. note:: Users should use :func:`make_mono_admissible` instead of this function (which has a trailing underscore in its name): :func:`make_mono_admissible` is the cached version of this one, and so will be faster. EXAMPLES:: sage: from sage.algebras.steenrod.steenrod_algebra_mult import make_mono_admissible sage: make_mono_admissible((12,)) # already admissible, indirect doctest {(12,): 1} sage: make_mono_admissible((2,1)) # already admissible {(2, 1): 1} sage: make_mono_admissible((2,2)) {(3, 1): 1} sage: make_mono_admissible((2, 2, 2)) {(5, 1): 1} sage: make_mono_admissible((0, 2, 0, 1, 0), p=7) {(0, 3, 0): 3} Test the fix from :trac:`13796`:: sage: SteenrodAlgebra(p=2, basis='adem').Q(2) * (Sq(6) * Sq(2)) # indirect doctest Sq^10 Sq^4 Sq^1 + Sq^10 Sq^5 + Sq^12 Sq^3 + Sq^13 Sq^2 """ from sage.rings.all import GF if generic is None: generic = False if p == 2 else True F = GF(p) if len(mono) == 1: return {mono: 1} if not generic and len(mono) == 2: return adem(*mono, p=p, generic=generic) if not generic: # check to see if admissible: admissible = True for j in range(len(mono) - 1): if mono[j] < 2 * mono[j + 1]: admissible = False break if admissible: return {mono: 1} # else j is the first index where admissibility fails ans = {} y = adem(mono[j], mono[j + 1]) for x in y: new = mono[:j] + x + mono[j + 2:] new = make_mono_admissible(new) for m in new: if m in ans: ans[m] = ans[m] + y[x] * new[m] if F(ans[m]) == 0: del ans[m] else: ans[m] = y[x] * new[m] return ans # p odd # check to see if admissible: admissible = True for j in range(1, len(mono) - 2, 2): if mono[j] < mono[j + 1] + p * mono[j + 2]: admissible = False break if admissible: return {mono: 1} # else j is the first index where admissibility fails ans = {} y = adem(*mono[j:j + 3], p=p, generic=True) for x in y: new_x = list(x) new_x[0] = mono[j - 1] + x[0] if len(mono) >= j + 3: new_x[-1] = mono[j + 3] + x[-1] if new_x[0] <= 1 and new_x[-1] <= 1: new = mono[:j - 1] + tuple(new_x) + mono[j + 4:] new = make_mono_admissible(new, p, generic=True) for m in new: if m in ans: ans[m] = ans[m] + y[x] * new[m] if F(ans[m]) == 0: del ans[m] else: ans[m] = y[x] * new[m] return ans
def multinomial_odd(list, p): r""" Multinomial coefficient of list, mod p. INPUT: - list - list of integers - p - a prime number OUTPUT: Associated multinomial coefficient, mod p Given the input $[n_1, n_2, n_3, ...]$, this computes the multinomial coefficient $(n_1 + n_2 + n_3 + ...)! / (n_1! n_2! n_3! ...)$, mod $p$. The method is this: expand each $n_i$ in base $p$: $n_i = \sum_j p^j n_{ij}$. Do the same for the sum of the $n_i$'s, which we call $m$: $m = \sum_j p^j m_j$. Then the multinomial coefficient is congruent, mod $p$, to the product of the multinomial coefficients $m_j! / (n_{1j}! n_{2j}! ...)$. Furthermore, any multinomial coefficient $m! / (n_1! n_2! ...)$ can be computed as a product of binomial coefficients: it equals .. math:: \binom{n_1}{n_1} \binom{n_1 + n_2}{n_2} \binom{n_1 + n_2 + n_3}{n_3} ... This is convenient because Sage's binomial function returns integers, not rational numbers (as would be produced just by dividing factorials). EXAMPLES:: sage: from sage.algebras.steenrod.steenrod_algebra_mult import multinomial_odd sage: multinomial_odd([1,2,4], 2) 1 sage: multinomial_odd([1,2,4], 7) 0 sage: multinomial_odd([1,2,4], 11) 6 sage: multinomial_odd([1,2,4], 101) 4 sage: multinomial_odd([1,2,4], 107) 105 """ from sage.rings.all import GF, Integer from sage.rings.arith import binomial n = sum(list) answer = 1 F = GF(p) n_expansion = Integer(n).digits(p) list_expansion = [Integer(k).digits(p) for k in list] index = 0 while answer != 0 and index < len(n_expansion): multi = F(1) partial_sum = 0 for exp in list_expansion: if index < len(exp): partial_sum = partial_sum + exp[index] multi = F(multi * binomial(partial_sum, exp[index])) answer = F(answer * multi) index += 1 return answer
def milnor_multiplication_odd(m1, m2, p): r""" Product of Milnor basis elements defined by m1 and m2 at the odd prime p. INPUT: - m1 - pair of tuples (e,r), where e is an increasing tuple of non-negative integers and r is a tuple of non-negative integers - m2 - pair of tuples (f,s), same format as m1 - p - odd prime number OUTPUT: Dictionary of terms of the form (tuple: coeff), where 'tuple' is a pair of tuples, as for r and s, and 'coeff' is an integer mod p. This computes the product of the Milnor basis elements $Q_{e_1} Q_{e_2} ... P(r_1, r_2, ...)$ and $Q_{f_1} Q_{f_2} ... P(s_1, s_2, ...)$. EXAMPLES:: sage: from sage.algebras.steenrod.steenrod_algebra_mult import milnor_multiplication_odd sage: milnor_multiplication_odd(((0,2),(5,)), ((1,),(1,)), 5) {((0, 1, 2), (0, 1)): 4, ((0, 1, 2), (6,)): 4} sage: milnor_multiplication_odd(((0,2,4),()), ((1,3),()), 7) {((0, 1, 2, 3, 4), ()): 6} sage: milnor_multiplication_odd(((0,2,4),()), ((1,5),()), 7) {((0, 1, 2, 4, 5), ()): 1} sage: milnor_multiplication_odd(((),(6,)), ((),(2,)), 3) {((), (4, 1)): 1, ((), (8,)): 1, ((), (0, 2)): 1} These examples correspond to the following product computations: .. math:: p=5: \quad Q_0 Q_2 \mathcal{P}(5) Q_1 \mathcal{P}(1) = 4 Q_0 Q_1 Q_2 \mathcal{P}(0,1) + 4 Q_0 Q_1 Q_2 \mathcal{P}(6) p=7: \quad (Q_0 Q_2 Q_4) (Q_1 Q_3) = 6 Q_0 Q_1 Q_2 Q_3 Q_4 p=7: \quad (Q_0 Q_2 Q_4) (Q_1 Q_5) = Q_0 Q_1 Q_2 Q_3 Q_5 p=3: \quad \mathcal{P}(6) \mathcal{P}(2) = \mathcal{P}(0,2) + \mathcal{P}(4,1) + \mathcal{P}(8) The following used to fail until the trailing zeroes were eliminated in p_mono:: sage: A = SteenrodAlgebra(3) sage: a = A.P(0,3); b = A.P(12); c = A.Q(1,2) sage: (a+b)*c == a*c + b*c True Test that the bug reported in #7212 has been fixed:: sage: A.P(36,6)*A.P(27,9,81) 2 P(13,21,83) + P(14,24,82) + P(17,20,83) + P(25,18,83) + P(26,21,82) + P(36,15,80,1) + P(49,12,83) + 2 P(50,15,82) + 2 P(53,11,83) + 2 P(63,15,81) Associativity once failed because of a sign error:: sage: a,b,c = A.Q_exp(0,1), A.P(3), A.Q_exp(1,1) sage: (a*b)*c == a*(b*c) True This uses the same algorithm Monks does in his Maple package to iterate through the possible matrices: see http://mathweb.scranton.edu/monks/software/Steenrod/steen.html. """ from sage.rings.all import GF F = GF(p) (f, s) = m2 # First compute Q_e0 Q_e1 ... P(r1, r2, ...) Q_f0 Q_f1 ... # Store results (as dictionary of pairs of tuples) in 'answer'. answer = {m1: F(1)} for k in f: old_answer = answer answer = {} for mono in old_answer: if k not in mono[0]: q_mono = set(mono[0]) if len(q_mono) > 0: ind = len(q_mono.intersection(range(k, 1 + max(q_mono)))) else: ind = 0 coeff = (-1)**ind * old_answer[mono] lst = list(mono[0]) if ind == 0: lst.append(k) else: lst.insert(-ind, k) q_mono = tuple(lst) p_mono = mono[1] answer[(q_mono, p_mono)] = F(coeff) for i in range(1, 1 + len(mono[1])): if (k + i not in mono[0]) and (p**k <= mono[1][i - 1]): q_mono = set(mono[0]) if len(q_mono) > 0: ind = len( q_mono.intersection(range(k + i, 1 + max(q_mono)))) else: ind = 0 coeff = (-1)**ind * old_answer[mono] lst = list(mono[0]) if ind == 0: lst.append(k + i) else: lst.insert(-ind, k + i) q_mono = tuple(lst) p_mono = list(mono[1]) p_mono[i - 1] = p_mono[i - 1] - p**k # The next two lines were added so that p_mono won't # have trailing zeros. This makes p_mono uniquely # determined by P(*p_mono). while len(p_mono) > 0 and p_mono[-1] == 0: p_mono.pop() answer[(q_mono, tuple(p_mono))] = F(coeff) # Now for the Milnor matrices. For each entry '(e,r): coeff' in answer, # multiply r with s. Record coefficient for matrix and multiply by coeff. # Store in 'result'. if len(s) == 0: result = answer else: result = {} for (e, r) in answer: old_coeff = answer[(e, r)] # Milnor multiplication for r and s rows = len(r) + 1 cols = len(s) + 1 diags = len(r) + len(s) # initialize matrix M = range(rows) for i in range(rows): M[i] = [0] * cols for j in range(1, cols): M[0][j] = s[j - 1] for i in range(1, rows): M[i][0] = r[i - 1] for j in range(1, cols): M[i][j] = 0 found = True while found: # check diagonals n = 1 coeff = old_coeff diagonal = [0] * diags while n <= diags and coeff != 0: nth_diagonal = [ M[i][n - i] for i in range(max(0, n - cols + 1), min(1 + n, rows)) ] coeff = coeff * multinomial_odd(nth_diagonal, p) diagonal[n - 1] = sum(nth_diagonal) n = n + 1 if F(coeff) != 0: i = diags - 1 while i >= 0 and diagonal[i] == 0: i = i - 1 t = tuple(diagonal[:i + 1]) if (e, t) in result: result[(e, t)] = F(coeff + result[(e, t)]) else: result[(e, t)] = F(coeff) # now look for new matrices: found = False i = 1 while not found and i < rows: temp_sum = M[i][0] j = 1 while not found and j < cols: # check to see if column index j is small enough if temp_sum >= p**j: # now check to see if there's anything above this entry # to add to it temp_col_sum = 0 for k in range(i): temp_col_sum += M[k][j] if temp_col_sum != 0: found = True for row in range(1, i): M[row][0] = r[row - 1] for col in range(1, cols): M[0][col] = M[0][col] + M[row][col] M[row][col] = 0 for col in range(1, j): M[0][col] = M[0][col] + M[i][col] M[i][col] = 0 M[0][j] = M[0][j] - 1 M[i][j] = M[i][j] + 1 M[i][0] = temp_sum - p**j else: temp_sum += M[i][j] * p**j else: temp_sum += M[i][j] * p**j j = j + 1 i = i + 1 return result