def _find_scaling_L_ratio(self): r""" This function is use to set ``_scaling``, the factor used to adjust the scalar multiple of the modular symbol. If `[0]`, the modular symbol evaluated at 0, is non-zero, we can just scale it with respect to the approximation of the L-value. It is known that the quotient is a rational number with small denominator. Otherwise we try to scale using quadratic twists. ``_scaling`` will be set to a rational non-zero multiple if we succeed and to 1 otherwise. Even if we fail we scale at least to make up the difference between the periods of the `X_0`-optimal curve and our given curve `E` in the isogeny class. EXAMPLES:: sage: m = EllipticCurve('11a1').modular_symbol(implementation="sage") sage: m._scaling 1/5 sage: m = EllipticCurve('11a2').modular_symbol(implementation="sage") sage: m._scaling 1 sage: m = EllipticCurve('11a3').modular_symbol(implementation="sage") sage: m._scaling 1/25 sage: m = EllipticCurve('37a1').modular_symbol(implementation="sage") sage: m._scaling -1 sage: m = EllipticCurve('37a1').modular_symbol() sage: m._scaling 1 sage: m = EllipticCurve('389a1').modular_symbol() sage: m._scaling 1 sage: m = EllipticCurve('389a1').modular_symbol(implementation="sage") sage: m._scaling 1 sage: m = EllipticCurve('196a1').modular_symbol(implementation="sage") sage: m._scaling 1 Some harder cases fail:: sage: m = EllipticCurve('121b1').modular_symbol(implementation="sage") Warning : Could not normalize the modular symbols, maybe all further results will be multiplied by -1 and a power of 2 sage: m._scaling 1 TESTS:: sage: rk0 = ['11a1', '11a2', '15a1', '27a1', '37b1'] sage: for la in rk0: # long time (3s on sage.math, 2011) ....: E = EllipticCurve(la) ....: me = E.modular_symbol(implementation="eclib") ....: ms = E.modular_symbol(implementation="sage") ....: print("{} {} {}".format(E.lseries().L_ratio()*E.real_components(), me(0), ms(0))) 1/5 1/5 1/5 1 1 1 1/4 1/4 1/4 1/3 1/3 1/3 2/3 2/3 2/3 sage: rk1 = ['37a1','43a1','53a1', '91b1','91b2','91b3'] sage: [EllipticCurve(la).modular_symbol()(0) for la in rk1] # long time (1s on sage.math, 2011) [0, 0, 0, 0, 0, 0] sage: for la in rk1: # long time (8s on sage.math, 2011) ....: E = EllipticCurve(la) ....: m = E.modular_symbol() ....: lp = E.padic_lseries(5) ....: for D in [5,17,12,8]: ....: ED = E.quadratic_twist(D) ....: md = sum([kronecker(D,u)*m(ZZ(u)/D) for u in range(D)]) ....: etaD = lp._quotient_of_periods_to_twist(D) ....: assert ED.lseries().L_ratio()*ED.real_components() * etaD == md """ E = self._E self._scaling = 1 # initial value, may be changed later. self._failed_to_scale = False if self._sign == 1: at0 = self(0) if at0 != 0: l1 = self.__lalg__(1) if at0 != l1: verbose('scale modular symbols by %s' % (l1 / at0)) self._scaling = l1 / at0 else: # if [0] = 0, we can still hope to scale it correctly by considering twists of E Dlist = [ 5, 8, 12, 13, 17, 21, 24, 28, 29, 33, 37, 40, 41, 44, 53, 56, 57, 60, 61, 65, 69, 73, 76, 77, 85, 88, 89, 92, 93, 97 ] # a list of positive fundamental discriminants j = 0 at0 = 0 # computes [0]+ for the twist of E by D until one value is non-zero while j < 30 and at0 == 0: D = Dlist[j] # the following line checks if the twist of the newform of E by D is a newform # this is to avoid that we 'twist back' if all( valuation(E.conductor(), ell) <= valuation(D, ell) for ell in prime_divisors(D)): at0 = sum([ kronecker_symbol(D, u) * self(ZZ(u) / D) for u in range(1, abs(D)) ]) j += 1 if j == 30 and at0 == 0: # curves like "121b1", "225a1", "225e1", "256a1", "256b1", "289a1", "361a1", "400a1", "400c1", "400h1", "441b1", "441c1", "441d1", "441f1 .. will arrive here print( "Warning : Could not normalize the modular symbols, maybe all further results will be multiplied by -1 and a power of 2" ) self._failed_to_scale = True else: l1 = self.__lalg__(D) if at0 != l1: verbose('scale modular symbols by %s found at D=%s ' % (l1 / at0, D), level=2) self._scaling = l1 / at0 else: # that is when sign = -1 Dlist = [ -3, -4, -7, -8, -11, -15, -19, -20, -23, -24, -31, -35, -39, -40, -43, -47, -51, -52, -55, -56, -59, -67, -68, -71, -79, -83, -84, -87, -88, -91 ] # a list of negative fundamental discriminants j = 0 at0 = 0 while j < 30 and at0 == 0: # computes [0]+ for the twist of E by D until one value is non-zero D = Dlist[j] if all( valuation(E.conductor(), ell) <= valuation(D, ell) for ell in prime_divisors(D)): at0 = -sum([ kronecker_symbol(D, u) * self(ZZ(u) / D) for u in range(1, abs(D)) ]) j += 1 if j == 30 and at0 == 0: # no more hope for a normalization print( "Warning : Could not normalize the modular symbols, maybe all further results will be multiplied by -1 and a power of 2" ) self._failed_to_scale = True else: l1 = self.__lalg__(D) if at0 != l1: verbose('scale modular symbols by %s' % (l1 / at0)) self._scaling = l1 / at0
def _tate(self, proof=None, globally=False): r""" Tate's algorithm for an elliptic curve over a number field. Computes both local reduction data at a prime ideal and a local minimal model. The model is not required to be integral on input. If `P` is principal, uses a generator as uniformizer, so it will not affect integrality or minimality at other primes. If `P` is not principal, the minimal model returned will preserve integrality at other primes, but not minimality. The optional argument globally, when set to True, tells the algorithm to use the generator of the prime ideal if it is principal. Otherwise just any uniformizer will be used. .. NOTE:: Called only by ``EllipticCurveLocalData.__init__()``. OUTPUT: (tuple) ``(Emin, p, val_disc, fp, KS, cp)`` where: - ``Emin`` (EllipticCurve) is a model (integral and) minimal at P - ``p`` (int) is the residue characteristic - ``val_disc`` (int) is the valuation of the local minimal discriminant - ``fp`` (int) is the valuation of the conductor - ``KS`` (string) is the Kodaira symbol - ``cp`` (int) is the Tamagawa number EXAMPLES (this raised a type error in sage prior to 4.4.4, see :trac:`7930`) :: sage: E = EllipticCurve('99d1') sage: R.<X> = QQ[] sage: K.<t> = NumberField(X^3 + X^2 - 2*X - 1) sage: L.<s> = NumberField(X^3 + X^2 - 36*X - 4) sage: EK = E.base_extend(K) sage: toK = EK.torsion_order() sage: da = EK.local_data() # indirect doctest sage: EL = E.base_extend(L) sage: da = EL.local_data() # indirect doctest EXAMPLES: The following example shows that the bug at :trac:`9324` is fixed:: sage: K.<a> = NumberField(x^2-x+6) sage: E = EllipticCurve([0,0,0,-53160*a-43995,-5067640*a+19402006]) sage: E.conductor() # indirect doctest Fractional ideal (18, 6*a) The following example shows that the bug at :trac:`9417` is fixed:: sage: K.<a> = NumberField(x^2+18*x+1) sage: E = EllipticCurve(K, [0, -36, 0, 320, 0]) sage: E.tamagawa_number(K.ideal(2)) 4 This is to show that the bug :trac:`11630` is fixed. (The computation of the class group would produce a warning):: sage: K.<t> = NumberField(x^7-2*x+177) sage: E = EllipticCurve([0,1,0,t,t]) sage: P = K.ideal(2,t^3 + t + 1) sage: E.local_data(P).kodaira_symbol() II """ E = self._curve P = self._prime K = E.base_ring() OK = K.maximal_order() t = verbose("Running Tate's algorithm with P = %s" % P, level=1) F = OK.residue_field(P) p = F.characteristic() # In case P is not principal we mostly use a uniformiser which # is globally integral (with positive valuation at some other # primes); for this to work, it is essential that we can # reduce (mod P) elements of K which are not integral (but are # P-integral). However, if the model is non-minimal and we # end up dividing a_i by pi^i then at that point we use a # uniformiser pi which has non-positive valuation at all other # primes, so that we can divide by it without losing # integrality at other primes. if globally: principal_flag = P.is_principal() else: principal_flag = False if (K is QQ) or principal_flag: pi = P.gens_reduced()[0] verbose("P is principal, generator pi = %s" % pi, t, 1) else: pi = K.uniformizer(P, 'positive') verbose("uniformizer pi = %s" % pi, t, 1) pi2 = pi * pi pi3 = pi * pi2 pi4 = pi * pi3 pi_neg = None prime = pi if K is QQ else P pval = lambda x: x.valuation(prime) pdiv = lambda x: x.is_zero() or pval(x) > 0 # Since ResidueField is cached in a way that # does not care much about embeddings of number # fields, it can happen that F.p.ring() is different # from K. This is a problem: If F.p.ring() has no # embedding but K has, then there is no coercion # from F.p.ring().maximal_order() to K. But it is # no problem to do an explicit conversion in that # case (Simon King, trac ticket #8800). from sage.categories.pushout import pushout, CoercionException try: if hasattr(F.p.ring(), 'maximal_order'): # it is not ZZ pushout(F.p.ring().maximal_order(), K) pinv = lambda x: F.lift(~F(x)) proot = lambda x, e: F.lift( F(x).nth_root(e, extend=False, all=True)[0]) preduce = lambda x: F.lift(F(x)) except CoercionException: # the pushout does not exist, we need conversion pinv = lambda x: K(F.lift(~F(x))) proot = lambda x, e: K( F.lift(F(x).nth_root(e, extend=False, all=True)[0])) preduce = lambda x: K(F.lift(F(x))) def _pquadroots(a, b, c): r""" Local function returning True iff `ax^2 + bx + c` has roots modulo `P` """ (a, b, c) = (F(a), F(b), F(c)) if a == 0: return (b != 0) or (c == 0) elif p == 2: return len(PolynomialRing(F, "x")([c, b, a]).roots()) > 0 else: return (b**2 - 4 * a * c).is_square() def _pcubicroots(b, c, d): r""" Local function returning the number of roots of `x^3 + b*x^2 + c*x + d` modulo `P`, counting multiplicities """ return sum([ rr[1] for rr in PolynomialRing(F, 'x') ([F(d), F(c), F(b), F(1)]).roots() ], 0) if p == 2: halfmodp = OK(Integer(0)) else: halfmodp = pinv(Integer(2)) A = E.a_invariants() A = [0, A[0], A[1], A[2], A[3], 0, A[4]] indices = [1, 2, 3, 4, 6] if min([pval(a) for a in A if a != 0]) < 0: verbose( "Non-integral model at P: valuations are %s; making integral" % ([pval(a) for a in A if a != 0]), t, 1) e = 0 for i in range(7): if A[i] != 0: e = max(e, (-pval(A[i]) / i).ceil()) pie = pi**e for i in range(7): if A[i] != 0: A[i] *= pie**i verbose( "P-integral model is %s, with valuations %s" % ([A[i] for i in indices], [pval(A[i]) for i in indices]), t, 1) split = None # only relevant for multiplicative reduction (a1, a2, a3, a4, a6) = (A[1], A[2], A[3], A[4], A[6]) while True: C = EllipticCurve([a1, a2, a3, a4, a6]) (b2, b4, b6, b8) = C.b_invariants() (c4, c6) = C.c_invariants() delta = C.discriminant() val_disc = pval(delta) if val_disc == 0: ## Good reduction already cp = 1 fp = 0 KS = KodairaSymbol("I0") break #return # Otherwise, we change coordinates so that p | a3, a4, a6 if p == 2: if pdiv(b2): r = proot(a4, 2) t = proot(((r + a2) * r + a4) * r + a6, 2) else: temp = pinv(a1) r = temp * a3 t = temp * (a4 + r * r) elif p == 3: if pdiv(b2): r = proot(-b6, 3) else: r = -pinv(b2) * b4 t = a1 * r + a3 else: if pdiv(c4): r = -pinv(12) * b2 else: r = -pinv(12 * c4) * (c6 + b2 * c4) t = -halfmodp * (a1 * r + a3) r = preduce(r) t = preduce(t) verbose("Before first transform C = %s" % C) verbose("[a1,a2,a3,a4,a6] = %s" % ([a1, a2, a3, a4, a6])) C = C.rst_transform(r, 0, t) (a1, a2, a3, a4, a6) = C.a_invariants() (b2, b4, b6, b8) = C.b_invariants() if min([pval(a) for a in (a1, a2, a3, a4, a6) if a != 0]) < 0: raise RuntimeError("Non-integral model after first transform!") verbose( "After first transform %s\n, [a1,a2,a3,a4,a6] = %s\n, valuations = %s" % ([r, 0, t], [a1, a2, a3, a4, a6], [pval(a1), pval(a2), pval(a3), pval(a4), pval(a6)]), t, 2) if pval(a3) == 0: raise RuntimeError( "p does not divide a3 after first transform!") if pval(a4) == 0: raise RuntimeError( "p does not divide a4 after first transform!") if pval(a6) == 0: raise RuntimeError( "p does not divide a6 after first transform!") # Now we test for Types In, II, III, IV # NB the c invariants never change. if not pdiv(c4): # Multiplicative reduction: Type In (n = val_disc) split = False if _pquadroots(1, a1, -a2): cp = val_disc split = True elif Integer(2).divides(val_disc): cp = 2 else: cp = 1 KS = KodairaSymbol("I%s" % val_disc) fp = 1 break #return # Additive reduction if pval(a6) < 2: ## Type II KS = KodairaSymbol("II") fp = val_disc cp = 1 break #return if pval(b8) < 3: ## Type III KS = KodairaSymbol("III") fp = val_disc - 1 cp = 2 break #return if pval(b6) < 3: ## Type IV cp = 1 a3t = preduce(a3 / pi) a6t = preduce(a6 / pi2) if _pquadroots(1, a3t, -a6t): cp = 3 KS = KodairaSymbol("IV") fp = val_disc - 2 break #return # If our curve is none of these types, we change coords so that # p | a1, a2; p^2 | a3, a4; p^3 | a6 if p == 2: s = proot(a2, 2) # so s^2=a2 (mod pi) t = pi * proot(a6 / pi2, 2) # so t^2=a6 (mod pi^3) elif p == 3: s = a1 # so a1'=2s+a1=3a1=0 (mod pi) t = a3 # so a3'=2t+a3=3a3=0 (mod pi^2) else: s = -a1 * halfmodp # so a1'=2s+a1=0 (mod pi) t = -a3 * halfmodp # so a3'=2t+a3=0 (mod pi^2) C = C.rst_transform(0, s, t) (a1, a2, a3, a4, a6) = C.a_invariants() verbose( "After second transform %s\n[a1, a2, a3, a4, a6] = %s\nValuations: %s" % ([0, s, t], [a1, a2, a3, a4, a6], [pval(a1), pval(a2), pval(a3), pval(a4), pval(a6)]), t, 2) if pval(a1) == 0: raise RuntimeError( "p does not divide a1 after second transform!") if pval(a2) == 0: raise RuntimeError( "p does not divide a2 after second transform!") if pval(a3) < 2: raise RuntimeError( "p^2 does not divide a3 after second transform!") if pval(a4) < 2: raise RuntimeError( "p^2 does not divide a4 after second transform!") if pval(a6) < 3: raise RuntimeError( "p^3 does not divide a6 after second transform!") if min(pval(a1), pval(a2), pval(a3), pval(a4), pval(a6)) < 0: raise RuntimeError( "Non-integral model after second transform!") # Analyze roots of the cubic T^3 + bT^2 + cT + d = 0 mod P, where # b = a2/p, c = a4/p^2, d = a6/p^3 b = preduce(a2 / pi) c = preduce(a4 / pi2) d = preduce(a6 / pi3) bb = b * b cc = c * c bc = b * c w = 27 * d * d - bb * cc + 4 * b * bb * d - 18 * bc * d + 4 * c * cc x = 3 * c - bb if pdiv(w): if pdiv(x): sw = 3 else: sw = 2 else: sw = 1 verbose( "Analyzing roots of cubic T^3 + %s*T^2 + %s*T + %s, case %s" % (b, c, d, sw), t, 1) if sw == 1: ## Three distinct roots - Type I*0 verbose("Distinct roots", t, 1) KS = KodairaSymbol("I0*") cp = 1 + _pcubicroots(b, c, d) fp = val_disc - 4 break #return elif sw == 2: ## One double root - Type I*m for some m verbose("One double root", t, 1) ## Change coords so that the double root is T = 0 mod p if p == 2: r = proot(c, 2) elif p == 3: r = c * pinv(b) else: r = (bc - 9 * d) * pinv(2 * x) r = pi * preduce(r) C = C.rst_transform(r, 0, 0) (a1, a2, a3, a4, a6) = C.a_invariants() # The rest of this branch is just to compute cp, fp, KS. # We use pi to keep transforms integral. ix = 3 iy = 3 mx = pi2 my = mx while True: a2t = preduce(a2 / pi) a3t = preduce(a3 / my) a4t = preduce(a4 / (pi * mx)) a6t = preduce(a6 / (mx * my)) if pdiv(a3t * a3t + 4 * a6t): if p == 2: t = my * proot(a6t, 2) else: t = my * preduce(-a3t * halfmodp) C = C.rst_transform(0, 0, t) (a1, a2, a3, a4, a6) = C.a_invariants() my *= pi iy += 1 a2t = preduce(a2 / pi) a3t = preduce(a3 / my) a4t = preduce(a4 / (pi * mx)) a6t = preduce(a6 / (mx * my)) if pdiv(a4t * a4t - 4 * a6t * a2t): if p == 2: r = mx * proot(a6t * pinv(a2t), 2) else: r = mx * preduce(-a4t * pinv(2 * a2t)) C = C.rst_transform(r, 0, 0) (a1, a2, a3, a4, a6) = C.a_invariants() mx *= pi ix += 1 # and stay in loop else: if _pquadroots(a2t, a4t, a6t): cp = 4 else: cp = 2 break # exit loop else: if _pquadroots(1, a3t, -a6t): cp = 4 else: cp = 2 break KS = KodairaSymbol("I%s*" % (ix + iy - 5)) fp = val_disc - ix - iy + 1 break #return else: # sw == 3 ## The cubic has a triple root verbose("Triple root", t, 1) ## First we change coordinates so that T = 0 mod p if p == 2: r = b elif p == 3: r = proot(-d, 3) else: r = -b * pinv(3) r = pi * preduce(r) C = C.rst_transform(r, 0, 0) (a1, a2, a3, a4, a6) = C.a_invariants() verbose( "After third transform %s\n[a1,a2,a3,a4,a6] = %s\nValuations: %s" % ([r, 0, 0], [a1, a2, a3, a4, a6], [pval(ai) for ai in [a1, a2, a3, a4, a6]]), t, 2) if min(pval(ai) for ai in [a1, a2, a3, a4, a6]) < 0: raise RuntimeError( "Non-integral model after third transform!") if pval(a2) < 2 or pval(a4) < 3 or pval(a6) < 4: raise RuntimeError( "Cubic after transform does not have a triple root at 0" ) a3t = preduce(a3 / pi2) a6t = preduce(a6 / pi4) # We test for Type IV* if not pdiv(a3t * a3t + 4 * a6t): cp = 3 if _pquadroots(1, a3t, -a6t) else 1 KS = KodairaSymbol("IV*") fp = val_disc - 6 break #return # Now change coordinates so that p^3|a3, p^5|a6 if p == 2: t = -pi2 * proot(a6t, 2) else: t = pi2 * preduce(-a3t * halfmodp) C = C.rst_transform(0, 0, t) (a1, a2, a3, a4, a6) = C.a_invariants() # We test for types III* and II* if pval(a4) < 4: ## Type III* KS = KodairaSymbol("III*") fp = val_disc - 7 cp = 2 break #return if pval(a6) < 6: ## Type II* KS = KodairaSymbol("II*") fp = val_disc - 8 cp = 1 break #return if pi_neg is None: if principal_flag: pi_neg = pi else: pi_neg = K.uniformizer(P, 'negative') pi_neg2 = pi_neg * pi_neg pi_neg3 = pi_neg * pi_neg2 pi_neg4 = pi_neg * pi_neg3 pi_neg6 = pi_neg4 * pi_neg2 a1 /= pi_neg a2 /= pi_neg2 a3 /= pi_neg3 a4 /= pi_neg4 a6 /= pi_neg6 verbose( "Non-minimal equation, dividing out...\nNew model is %s" % ([a1, a2, a3, a4, a6]), t, 1) return (C, p, val_disc, fp, KS, cp, split)
def complement(self, bound=None): """ Return the largest Hecke-stable complement of this space. EXAMPLES:: sage: M = ModularSymbols(15, 6).cuspidal_subspace() sage: M.complement() Modular Symbols subspace of dimension 4 of Modular Symbols space of dimension 20 for Gamma_0(15) of weight 6 with sign 0 over Rational Field sage: E = EllipticCurve("128a") sage: ME = E.modular_symbol_space() sage: ME.complement() Modular Symbols subspace of dimension 17 of Modular Symbols space of dimension 18 for Gamma_0(128) of weight 2 with sign 1 over Rational Field """ if self.dual_free_module.is_in_cache(): D = self.dual_free_module() V = D.basis_matrix().right_kernel() return self.submodule(V, check=False) if self.is_ambient(): return self.ambient_hecke_module().zero_submodule() if self.is_zero(): return self.ambient_hecke_module() if self.is_full_hecke_module(): anemic = False else: anemic = True # TODO: optimize in some cases by computing image of # complementary factor instead of kernel...? verbose("computing") N = self.level() A = self.ambient_hecke_module() V = A.free_module() p = 2 if bound is None: bound = A.hecke_bound() while True: if anemic: while N % p == 0: p = arith.next_prime(p) verbose("using T_%s" % p) f = self.hecke_polynomial(p) T = A.hecke_matrix(p) g = T.charpoly('x') V = T.kernel_on(V, poly=g // f, check=False) if V.rank() + self.rank() <= A.rank(): break p = arith.next_prime(p) if p > bound: # to avoid computing Hecke bound unless necessary break if V.rank() + self.rank() == A.rank(): C = A.submodule(V, check=False) return C # first attempt to compute the complement failed, we now try # the following naive approach: decompose the ambient space, # decompose self, and sum the pieces of ambient that are not # subspaces of self verbose("falling back on naive algorithm") D = A.decomposition() C = A.zero_submodule() for X in D: if self.intersection(X).dimension() == 0: C = C + X if C.rank() + self.rank() == A.rank(): return C # failed miserably raise RuntimeError( "Computation of complementary space failed (cut down to rank %s, but should have cut down to rank %s)." % (V.rank(), A.rank() - self.rank()))
def __init__(self, monic_polynomial, names='X', iterate=True, warning=True): r""" Python constructor. EXAMPLES:: sage: from sage.algebras.splitting_algebra import SplittingAlgebra sage: Lw.<w> = LaurentPolynomialRing(ZZ) sage: PuvLw.<u,v> = Lw[]; t = polygen(PuvLw) sage: S.<x, y> = SplittingAlgebra(t^3 - u*t^2 + v*t - w, warning=False) sage: TestSuite(S).run() """ # --------------------------------------------------------------------------------- # checking input parameters # --------------------------------------------------------------------------------- base_ring = monic_polynomial.base_ring() if not monic_polynomial.is_monic(): raise ValueError("given polynomial must be monic") deg = monic_polynomial.degree() from sage.structure.category_object import normalize_names self._root_names = normalize_names(deg - 1, names) root_names = list(self._root_names) verbose( "Create splitting algebra to base ring %s and polynomial %s (%s %s)" % (base_ring, monic_polynomial, iterate, warning)) self._defining_polynomial = monic_polynomial self._iterate = iterate try: if not base_ring.is_integral_domain(): raise TypeError("base_ring must be an integral domain") except NotImplementedError: from sage.rings.ring import Ring if not isinstance(base_ring, Ring): raise TypeError("base_ring must be an instance of ring") if warning: warn('Assuming %s to be an integral domain!' % (base_ring)) if deg < 1: raise ValueError("the degree of the polynomial must positive") self._splitting_roots = [] self._coefficients_list = [] self._invertible_elements = {} if isinstance(base_ring, SplittingAlgebra): self._invertible_elements = base_ring._invertible_elements # ------------------------------------------------------------------------------------ # taking next root_name # ------------------------------------------------------------------------------------ root_name = root_names[0] p = monic_polynomial.change_variable_name(root_name) P = p.parent() self._set_modulus_irreducible_ = False try: if not p.is_irreducible(): raise ValueError("monic_polynomial must be irreducible") except (NotImplementedError, AttributeError): # assuming this has been checked mathematically before self._set_modulus_irreducible_ = True if warning: warn('Asuming %s to have maximal Galois group!' % (monic_polynomial)) warning = False # one warning must be enough verbose("P %s defined:" % (P)) if deg > 2 and iterate: # ------------------------------------------------------------------------------------ # successive solution via recursion (on base_ring_step) # ------------------------------------------------------------------------------------ base_ring_step = SplittingAlgebra(monic_polynomial, tuple(root_names), iterate=False, warning=False) first_root = base_ring_step.gen() verbose("base_ring_step %s defined:" % (base_ring_step)) # ------------------------------------------------------------------------------------ # splitting first root off # ------------------------------------------------------------------------------------ from copy import copy root_names_reduces = copy(root_names) root_names_reduces.remove(root_name) P = base_ring_step[root_names_reduces[0]] p = P(monic_polynomial.dict()) q, r = p.quo_rem((P.gen() - first_root)) verbose("Invoking recursion with: %s" % (q, )) SplittingAlgebra.__init__(self, q, root_names_reduces, warning=False) splitting_roots = base_ring_step._splitting_roots + self._splitting_roots coefficients_list = base_ring_step._coefficients_list + self._coefficients_list verbose("Adding roots: %s" % (splitting_roots)) self._splitting_roots = splitting_roots self._coefficients_list = coefficients_list else: PolynomialQuotientRing_domain.__init__(self, P, p, root_name) first_root = self.gen() self._splitting_roots.append(first_root) self._coefficients_list = [ monic_polynomial.coefficients(sparse=False) ] if not iterate: verbose("pre ring defined splitting_roots: %s" % (self._splitting_roots)) return verbose("final ring defined splitting_roots: %s" % (self._splitting_roots)) if deg == 2: coefficients = monic_polynomial.coefficients(sparse=False) lin_coeff = coefficients[1] self._splitting_roots.append(-lin_coeff - first_root) self._root_names = names self._splitting_roots = [self(root) for root in self._splitting_roots] verbose("splitting_roots: %s embedded" % (self._splitting_roots)) # ------------------------------------------------------------------------------------------- # try to calculate inverses of the roots. This is possible if the original polynomial # has an invertible constant term. For example let cf = [-w, v,-u, 1] that is # p = h^3 -u*h^2 + v*h -w, than u = x + y + z, v = x*y + x*z + y*z, w = x*y*z. If # w is invertible then 1/x = (v -(u-x)*x)/w, 1/y = (v -(u-y)*y)/w, 1/z = (v -(u-z)*z)/w # ------------------------------------------------------------------------------------------- # first find the polynomial with invertible constant coefficient # ------------------------------------------------------------------------------------------- cf0_inv = None for cf in self._coefficients_list: cf0 = cf[0] try: cf0_inv = ~(cf[0]) cf0_inv = self(cf0_inv) verbose("invertible coefficient: %s found" % (cf0_inv)) break except NotImplementedError: verbose("constant coefficient: %s not invertibe" % (cf0)) # ---------------------------------------------------------------------------------- # assuming that cf splits into linear factors over self and the _splitting_roots # are its roots we can calculate inverses # ---------------------------------------------------------------------------------- if cf0_inv is not None: deg_cf = len(cf) - 1 pf = P(cf) for root in self._splitting_roots: check = self(pf) if not check.is_zero(): continue root_inv = self.one() for pos in range(deg_cf - 1): root_inv = (-1)**(pos + 1) * cf[deg_cf - pos - 1] - root_inv * root verbose("inverse %s of root %s" % (root_inv, root)) root_inv = (-1)**(deg_cf) * cf0_inv * root_inv self._invertible_elements.update({root: root_inv}) verbose("adding inverse %s of root %s" % (root_inv, root)) invert_items = [(k, v) for k, v in self._invertible_elements.items()] for k, v in invert_items: self._invertible_elements.update({v: k}) return
def solve_with_extension(monic_polynomial, root_names=None, var='x', flatten=False, warning=True): r""" Return all roots of a monic polynomial in its base ring or in an appropriate extension ring, as far as possible. INPUT: - ``monic_polynomial`` -- the monic polynomial whose roots should be created - ``root_names`` -- names for the indeterminates needed to define the splitting algebra of the ``monic_polynomial`` (if necessary and possible) - ``var`` -- (default: ``'x'``) for the indeterminate needed to define the splitting field of the ``monic_polynomial`` (if necessary and possible) - ``flatten`` -- (default: ``True``) if ``True`` the roots will not be given as a list of pairs ``(root, multiplicity)`` but as a list of roots repeated according to their multiplicity - ``warning`` -- (default: ``True``) can be used (by setting to ``False``) to suppress a warning which will be thrown whenever it cannot be checked that the Galois group of ``monic_polynomial`` is maximal OUTPUT: List of tuples ``(root, multiplicity)`` respectively list of roots repeated according to their multiplicity if option ``flatten`` is ``True``. EXAMPLES:: sage: from sage.algebras.splitting_algebra import solve_with_extension sage: t = polygen(ZZ) sage: p = t^2 -2*t +1 sage: solve_with_extension(p, flatten=True ) [1, 1] sage: solve_with_extension(p) [(1, 2)] sage: cp5 = cyclotomic_polynomial(5, var='T').change_ring(UniversalCyclotomicField()) sage: solve_with_extension(cp5) [(E(5), 1), (E(5)^4, 1), (E(5)^2, 1), (E(5)^3, 1)] sage: _[0][0].parent() Universal Cyclotomic Field """ def create_roots(monic_polynomial, warning=True): r""" This internal function creates all roots of a polynomial in an appropriate extension ring assuming that none of the roots is contained its base ring. It first tries to create the splitting field of the given polynomial. If this is not faithful the splitting algebra will be created. INPUT: - ``monic_polynomial`` -- the monic polynomial whose roots should be created - ``warning`` -- (default: ``True``) can be used (by setting to ``False``) to suppress a warning which will be thrown whenever it cannot be checked that the Galois group of ``monic_polynomial`` is maximal """ parent = monic_polynomial.parent() base_ring = parent.base_ring() try: ext_field, embed = monic_polynomial.splitting_field(var, map=True) if embed.domain() != base_ring: # in this case the SplittingAlgebra is preferred raise NotImplementedError # ------------------------------------------------------------------------------------- # in some cases the embedding of the base_ring in ext_field can not be obtained # as coercion # ------------------------------------------------------------------------------------- reset_coercion = False from sage.rings.number_field.number_field import NumberField_generic if isinstance(base_ring, NumberField_generic): reset_coercion = True elif base_ring.is_finite() and not base_ring.is_prime_field(): reset_coercion = True if reset_coercion: ext_field._unset_coercions_used() ext_field.register_coercion(embed) ext_field.register_conversion(embed) verbose("splitting field %s defined" % (ext_field)) pol_emb = monic_polynomial.change_ring(ext_field) roots = pol_emb.roots() except NotImplementedError: ext_ring = SplittingAlgebra(monic_polynomial, name_list, warning=warning) verbose("splitting algebra %s defined" % (ext_ring)) roots = [(r, 1) for r in ext_ring.splitting_roots()] return roots deg_pol = monic_polynomial.degree() if not root_names: from sage.structure.category_object import normalize_names root_names = normalize_names(deg_pol - 1, 'r') name_list = list(root_names) root_list = [] try: root_list = monic_polynomial.roots() except (TypeError, ValueError, NotImplementedError): pass if not root_list: # ------------------------------------------------------------------------------ # no roots found: find roots in an appropriate extension ring # ------------------------------------------------------------------------------ verbose("no roots in base_ring") if len(name_list) > deg_pol - 1: name_list = [name_list[i] for i in range(deg_pol - 1)] roots = create_roots(monic_polynomial, warning=warning) else: # ------------------------------------------------------------------------------ # root calculation was possible but maybe some more roots in an apropriate # extension ring can be constructed. # ------------------------------------------------------------------------------ num_roots = sum(m for r, m in root_list) if num_roots < deg_pol: h = monic_polynomial.variables()[0] divisor = monic_polynomial.base_ring().one() for r, m in root_list: divisor *= (h - r)**m q, r = monic_polynomial.quo_rem(divisor) if len(name_list) > deg_pol - num_roots - 1: name_list = [ name_list[i] for i in range(deg_pol - num_roots - 1) ] verbose("%d root found in base ring, now solving %s" % (num_roots, q)) missing_roots = create_roots(q, warning=True) roots = root_list + missing_roots else: roots = root_list verbose("all roots in base ring") if flatten: from sage.misc.flatten import flatten return flatten([[rt] * m for rt, m in roots]) return roots
def _find_generators(self, maxweight, start_gens, start_weight): r""" For internal use. This function is called by :meth:`generators` and :meth:`gen_forms`: it returns a list of triples `(k, f, F)` where `F` is a modular form of weight `k` and `f` is its `q`-expansion coerced into the base ring of self. INPUT: - maxweight: maximum weight to try - start_weight: minimum weight to try - start_gens: a sequence of tuples of the form `(k, f, F)`, where `F` is a modular form of weight `k` and `f` is its `q`-expansion coerced into ``self.base_ring()`. Either (but not both) of `f` and `F` may be None. OUTPUT: a list of tuples, formatted as with ``start_gens``. EXAMPLES:: sage: R = ModularFormsRing(Gamma1(4)) sage: R._find_generators(8, (), 2) [(2, 1 + 24*q^2 + 24*q^4 + 96*q^6 + 24*q^8 + O(q^9), 1 + 24*q^2 + 24*q^4 + O(q^6)), (2, q + 4*q^3 + 6*q^5 + 8*q^7 + O(q^9), q + 4*q^3 + 6*q^5 + O(q^6)), (3, 1 + 12*q^2 + 64*q^3 + 60*q^4 + 160*q^6 + 384*q^7 + 252*q^8 + O(q^9), 1 + 12*q^2 + 64*q^3 + 60*q^4 + O(q^6)), (3, q + 4*q^2 + 8*q^3 + 16*q^4 + 26*q^5 + 32*q^6 + 48*q^7 + 64*q^8 + O(q^9), q + 4*q^2 + 8*q^3 + 16*q^4 + 26*q^5 + O(q^6))] """ default_params = (start_gens == () and start_weight == 2) if default_params and self.__cached_maxweight != -1: verbose("Already know generators up to weight %s -- using those" % self.__cached_maxweight) if self.__cached_maxweight >= maxweight: return [(k, f, F) for k, f, F in self.__cached_gens if k <= maxweight] start_gens = self.__cached_gens start_weight = self.__cached_maxweight + 1 if self.group().is_even(): increment = 2 else: increment = 1 working_prec = self.modular_forms_of_weight(maxweight).sturm_bound() # parse the list of start gens G = [] for x in start_gens: k, f, F = x if F is None and f.prec() < working_prec: raise ValueError("Need start gens to precision at least %s" % working_prec) elif f is None or f.prec() < working_prec: f = F.qexp(working_prec).change_ring(self.base_ring()) G.append((k, f, F)) k = start_weight if increment == 2 and (k % 2) == 1: k += 1 while k <= maxweight: if self.modular_forms_of_weight(k).dimension() == 0: k += increment continue verbose('Looking at k = %s' % k) M = self.modular_forms_of_weight(k) # 1. Multiply together all forms in G that give an element # of M. if G: F = _span_of_forms_in_weight(G, k, M.sturm_bound(), None, False) else: F = (self.base_ring()**M.sturm_bound()).zero_submodule() # 2. If the dimension of the span of the result is equal # to the dimension of M, increment k. if F.rank() == M.dimension(): if self.base_ring().is_field() or F.index_in_saturation() == 1: # TODO: Do something clever if the submodule's of the right # rank but not saturated -- avoid triggering needless # modular symbol computations. verbose('Nothing new in weight %s' % k) k += increment continue # 3. If the dimension is less, compute a basis for G, and # try adding basis elements of M into G. verbose( "Known generators span a subspace of dimension %s of space of dimension %s" % (F.dimension(), M.dimension())) if self.base_ring() == ZZ: verbose("saturation index is %s" % F.index_in_saturation()) t = verbose("Computing more modular forms at weight %s" % k) kprec = M.sturm_bound() if self.base_ring() == QQ: B = M.q_echelon_basis(working_prec) else: B = M.q_integral_basis(working_prec) t = verbose("done computing forms", t) V = F.ambient_module().submodule_with_basis( [f.padded_list(kprec) for f in B]) Q = V / F for q in Q.gens(): try: qc = V.coordinates(Q.lift(q)) except AttributeError: # work around a silly free module bug qc = V.coordinates(q.lift()) qcZZ = [ZZ(_) for _ in qc] # lift to ZZ so we can define F f = sum([B[i] * qcZZ[i] for i in range(len(B))]) F = M(f) G.append((k, f.change_ring(self.base_ring()), F)) verbose('added %s new generators' % Q.ngens(), t) k += increment if default_params: self.__cached_maxweight = maxweight self.__cached_gens = G return G
def _compute_q_expansion_basis(self, prec=None): r""" Compute q-expansion basis using Schaeffer's algorithm. EXAMPLES:: sage: CuspForms(GammaH(31, [7]), 1).q_expansion_basis() # indirect doctest [ q - q^2 - q^5 + O(q^6) ] A more elaborate example (two Galois-conjugate characters each giving a 2-dimensional space):: sage: CuspForms(GammaH(124, [85]), 1).q_expansion_basis() [ q - q^4 - q^6 + O(q^7), q^2 + O(q^7), q^3 + O(q^7), q^5 - q^6 + O(q^7) ] """ if prec is None: prec = self.prec() else: prec = Integer(prec) chars=self.group().characters_mod_H(sign=-1, galois_orbits=True) B = [] dim = 0 for c in chars: chi = c.minimize_base_ring() Bchi = [weight1.modular_ratio_to_prec(chi, f, prec) for f in weight1.hecke_stable_subspace(chi) ] if Bchi == []: continue if chi.base_ring() == QQ: B += [f.padded_list(prec) for f in Bchi] dim += len(Bchi) else: d = chi.base_ring().degree() dim += d * len(Bchi) for f in Bchi: w = f.padded_list(prec) for i in range(d): B.append([x[i] for x in w]) basis_mat = Matrix(QQ, B) if basis_mat.is_zero(): return [] # Daft thing: "transformation=True" parameter to echelonize # is ignored for rational matrices! big_mat = basis_mat.augment(identity_matrix(dim)) big_mat.echelonize() c = big_mat.pivots()[-1] echelon_basis_mat = big_mat[:, :prec] R = self._q_expansion_ring() if c >= prec: verbose("Precision %s insufficient to determine basis" % prec, level=1) else: verbose("Minimal precision for basis: %s" % (c+1), level=1) t = big_mat[:, prec:] assert echelon_basis_mat == t * basis_mat self.__transformation_matrix = t self._char_basis = [R(f.list(), c+1) for f in basis_mat.rows()] return [R(f.list(), prec) for f in echelon_basis_mat.rows() if f != 0]
def saturation(A, proof=True, p=0, max_dets=5): """ Compute a saturation matrix of A. INPUT: - A -- a matrix over ZZ - proof -- bool (default: True) - p -- int (default: 0); if not 0 only guarantees that output is p-saturated - max_dets -- int (default: 4) max number of dets of submatrices to compute. OUTPUT: matrix -- saturation of the matrix A. EXAMPLES:: sage: from sage.matrix.matrix_integer_dense_saturation import saturation sage: A = matrix(ZZ, 2, 2, [3,2,3,4]); B = matrix(ZZ, 2,3,[1,2,3,4,5,6]); C = A*B sage: C [11 16 21] [19 26 33] sage: C.index_in_saturation() 18 sage: S = saturation(C); S [11 16 21] [-2 -3 -4] sage: S.index_in_saturation() 1 sage: saturation(C, proof=False) [11 16 21] [-2 -3 -4] sage: saturation(C, p=2) [11 16 21] [-2 -3 -4] sage: saturation(C, p=2, max_dets=1) [11 16 21] [-2 -3 -4] """ # Find a submatrix of full rank and instead saturate that matrix. r = A.rank() if A.is_square() and r == A.nrows(): return identity_matrix(ZZ, r) if A.nrows() > r: P = [] while len(P) < r: P = matrix_integer_dense_hnf.probable_pivot_rows(A) A = A.matrix_from_rows(P) # Factor out all common factors from all rows, just in case. A = copy(A) A._factor_out_common_factors_from_each_row() if A.nrows() <= 1: return A A, zero_cols = A._delete_zero_columns() if max_dets > 0: # Take the GCD of at most num_dets randomly chosen determinants. nr = A.nrows(); nc = A.ncols() d = 0 trials = min(binomial(nc, nr), max_dets) already_tried = [] while len(already_tried) < trials: v = random_sublist_of_size(nc, nr) tm = verbose('saturation -- checking det condition on submatrix') d = gcd(d, A.matrix_from_columns(v).determinant(proof=proof)) verbose('saturation -- got det down to %s'%d, tm) if gcd(d, p) == 1: return A._insert_zero_columns(zero_cols) already_tried.append(v) if gcd(d, p) == 1: # already p-saturated return A._insert_zero_columns(zero_cols) # Factor and p-saturate at each p. # This is not a good algorithm, because all the HNF's in it are really slow! # #tm = verbose('factoring gcd %s of determinants'%d) #limit = 2**31-1 #F = d.factor(limit = limit) #D = [p for p, e in F if p <= limit] #B = [n for n, e in F if n > limit] # all big factors -- there will only be at most one #assert len(B) <= 1 #C = B[0] #for p in D: # A = p_saturation(A, p=p, proof=proof) # This is a really simple but powerful algorithm. # FACT: If A is a matrix of full rank, then hnf(transpose(A))^(-1)*A is a saturation of A. # To make this practical we use solve_system_with_difficult_last_row, since the # last column of HNF's are typically the only really big ones. B = A.transpose().hermite_form(include_zero_rows=False, proof=proof) B = B.transpose() # Now compute B^(-1) * A C = solve_system_with_difficult_last_row(B, A) return C.change_ring(ZZ)._insert_zero_columns(zero_cols)
def det_given_divisor(A, d, proof=True, stabilize=2): """ Given a divisor d of the determinant of A, compute the determinant of A. INPUT: - ``A`` -- a square integer matrix - ``d`` -- a nonzero integer that is assumed to divide the determinant of A - ``proof`` -- bool (default: True) compute det modulo enough primes so that the determinant is computed provably correctly (via the Hadamard bound). It would be VERY hard for ``det()`` to fail even with proof=False. - ``stabilize`` -- int (default: 2) if proof = False, then compute the determinant modulo `p` until ``stabilize`` successive modulo determinant computations stabilize. OUTPUT: integer -- determinant EXAMPLES:: sage: import sage.matrix.matrix_integer_dense_hnf as matrix_integer_dense_hnf sage: a = matrix(ZZ,3,[-1, -1, -1, -20, 4, 1, -1, 1, 2]) sage: matrix_integer_dense_hnf.det_given_divisor(a, 3) -30 sage: matrix_integer_dense_hnf.det_given_divisor(a, 3, proof=False) -30 sage: matrix_integer_dense_hnf.det_given_divisor(a, 3, proof=False, stabilize=1) -30 sage: a.det() -30 Here we illustrate proof=False giving a wrong answer:: sage: p = matrix_integer_dense_hnf.max_det_prime(2) sage: q = previous_prime(p) sage: a = matrix(ZZ, 2, [p, 0, 0, q]) sage: p * q 70368442188091 sage: matrix_integer_dense_hnf.det_given_divisor(a, 1, proof=False, stabilize=2) 0 This still works, because we do not work modulo primes that divide the determinant bound, which is found using a p-adic algorithm:: sage: a.det(proof=False, stabilize=2) 70368442188091 3 primes is enough:: sage: matrix_integer_dense_hnf.det_given_divisor(a, 1, proof=False, stabilize=3) 70368442188091 sage: matrix_integer_dense_hnf.det_given_divisor(a, 1, proof=False, stabilize=5) 70368442188091 sage: matrix_integer_dense_hnf.det_given_divisor(a, 1, proof=True) 70368442188091 TESTS:: sage: m = diagonal_matrix(ZZ, 68, [2]*66 + [1,1]) sage: m.det() 73786976294838206464 """ p = max_det_prime(A.nrows()) z_mod = [] moduli = [] assert d != 0 z_so_far = 1 N_so_far = 1 if proof: N = 1 B = (2 * 10**A.hadamard_bound()) // d + 1 dd = d # bad verbose statement, since computing the log overflows! est = int(RR(B).log() / RR(p).log()) + 1 cnt = 1 verbose("Multimodular det -- need to use about %s primes." % est, level=1) while N < B: if d % p != 0: tm = cputime() dd, z_so_far, N_so_far = det_from_modp_and_divisor( A, d, p, z_mod, moduli, z_so_far, N_so_far) N *= p verbose( "computed det mod p=%s which is %s (of about %s)" % (p, cnt, est), tm) p = previous_prime(p) cnt += 1 return dd else: val = [] while True: if d % p: tm = cputime() dd, z_so_far, N_so_far = det_from_modp_and_divisor( A, d, p, z_mod, moduli, z_so_far, N_so_far) verbose("computed det mod %s" % p, tm) val.append(dd) if len(val) >= stabilize and len(set(val[-stabilize:])) == 1: return val[-1] p = previous_prime(p)
def theta_series_degree_2(Q, prec): r""" Compute the theta series of degree 2 for the quadratic form Q. INPUT: - ``prec`` -- an integer. OUTPUT: dictionary, where: - keys are `{\rm GL}_2(\ZZ)`-reduced binary quadratic forms (given as triples of coefficients) - values are coefficients EXAMPLES:: sage: Q2 = QuadraticForm(ZZ, 4, [1,1,1,1, 1,0,0, 1,0, 1]) sage: S = Q2.theta_series_degree_2(10) sage: S[(0,0,2)] 24 sage: S[(1,0,1)] 144 sage: S[(1,1,1)] 192 AUTHORS: - Gonzalo Tornaria (2010-03-23) REFERENCE: - Raum, Ryan, Skoruppa, Tornaria, 'On Formal Siegel Modular Forms' (preprint) """ from sage.misc.verbose import verbose if Q.base_ring() != ZZ: raise TypeError("The quadratic form must be integral") if not Q.is_positive_definite(): raise ValueError("The quadratic form must be positive definite") try: X = ZZ(prec - 1) # maximum discriminant except TypeError: raise TypeError("prec is not an integer") if X < -1: raise ValueError("prec must be >= 0") if X == -1: return {} V = ZZ**Q.dim() H = Q.Hessian_matrix() t = cputime() max = int(floor((X + 1) / 4)) v_list = (Q.vectors_by_length(max)) # assume a>0 v_list = [[V(_) for _ in vs] for vs in v_list] # coerce vectors into V verbose("Computed vectors_by_length", t) # Deal with the singular part coeffs = {(0, 0, 0): ZZ(1)} for i in range(1, max + 1): coeffs[(0, 0, i)] = ZZ(2) * len(v_list[i]) # Now deal with the non-singular part a_max = int(floor(sqrt(X / 3))) for a in range(1, a_max + 1): t = cputime() c_max = int(floor((a * a + X) / (4 * a))) for c in range(a, c_max + 1): for v1 in v_list[a]: v1_H = v1 * H def B_v1(v): return v1_H * v2 for v2 in v_list[c]: b = abs(B_v1(v2)) if b <= a and 4 * a * c - b * b <= X: qf = (a, b, c) count = ZZ(4) if b == 0 else ZZ(2) coeffs[qf] = coeffs.get(qf, ZZ(0)) + count verbose("done a = %d" % a, t) return coeffs
def solve_system_with_difficult_last_row(B, A): """ Solve the matrix equation B*Z = A when the last row of $B$ contains huge entries. INPUT: - B -- a square n x n nonsingular matrix with painful big bottom row. - A -- an n x k matrix. OUTPUT: the unique solution to B*Z = A. EXAMPLES:: sage: from sage.matrix.matrix_integer_dense_saturation import solve_system_with_difficult_last_row sage: B = matrix(ZZ, 3, [1,2,3, 3,-1,2,939239082,39202803080,2939028038402834]); A = matrix(ZZ,3,2,[1,2,4,3,-1,0]) sage: X = solve_system_with_difficult_last_row(B, A); X [ 290668794698843/226075992027744 468068726971/409557956572] [-226078357385539/1582531944194208 1228691305937/2866905696004] [ 2365357795/1582531944194208 -17436221/2866905696004] sage: B*X == A True """ # See the comments in the function of the same name in matrix_integer_dense_hnf.py. # This function is just a generalization of that one to A a matrix. C = copy(B) while True: C[C.nrows()-1] = random_matrix(ZZ,1,C.ncols()).row(0) try: X = C.solve_right(A) except ValueError: verbose("Try difficult solve again with different random vector") else: break D = B.matrix_from_rows(range(C.nrows()-1)) N = D._rational_kernel_flint() if N.ncols() != 1: verbose("Difficult solve quickly failed. Using direct approach.") return B.solve_right(A) tm = verbose("Recover correct linear combinations") k = N.matrix_from_columns([0]) # The sought for solution Z to B*Z = A is some linear combination # Z = X + alpha*k # Let w be the last row of B; then Z satisfies # w * Z = A' # where A' is the last row of A. Thus # w * (X + alpha*k) = A' # so w * X + alpha*w*k = A' # so alpha*w*k = A' - w*X. w = B[-1] # last row of B A_prime = A[-1] # last row of A lhs = w*k rhs = A_prime - w * X if lhs[0] == 0: verbose("Difficult solve quickly failed. Using direct approach.") return B.solve_right(A) for i in range(X.ncols()): alpha = rhs[i] / lhs[0] X.set_column(i, (X.matrix_from_columns([i]) + alpha*k).list()) verbose("Done getting linear combinations.", tm) return X
def points(self, **kwds): r""" Return some or all rational points of an affine scheme. For dimension 0 subschemes points are determined through a groebner basis calculation. For schemes or subschemes with dimension greater than 1 points are determined through enumeration up to the specified bound. Over a finite field, all points are returned. Over an infinite field, all points satisfying the bound are returned. For a zero-dimensional subscheme, all points are returned regardless of whether the field is infinite or not. For number fields, this uses the Doyle-Krumm algorithm 4 (algorithm 5 for imaginary quadratic) for computing algebraic numbers up to a given height [DK2013]_. The algorithm requires floating point arithmetic, so the user is allowed to specify the precision for such calculations. Additionally, due to floating point issues, points slightly larger than the bound may be returned. This can be controlled by lowering the tolerance. INPUT: kwds: - ``bound`` - real number (optional, default: 0). The bound for the height of the coordinates. Only used for subschemes with dimension at least 1. - ``zero_tolerance`` - positive real number (optional, default=10^(-10)). For numerically inexact fields, points are on the subscheme if they satisfy the equations to within tolerance. - ``tolerance`` - a rational number in (0,1] used in doyle-krumm algorithm-4 for enumeration over number fields. - ``precision`` - the precision to use for computing the elements of bounded height of number fields. OUTPUT: - a list of rational points of a affine scheme .. WARNING:: For numerically inexact fields such as ComplexField or RealField the list of points returned is very likely to be incomplete. It may also contain repeated points due to tolerance. EXAMPLES: The bug reported at #11526 is fixed:: sage: A2 = AffineSpace(ZZ, 2) sage: F = GF(3) sage: A2(F).points() [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] :: sage: A.<x,y> = ZZ[] sage: I = A.ideal(x^2-y^2-1) sage: V = AffineSpace(ZZ, 2) sage: X = V.subscheme(I) sage: M = X(ZZ) sage: M.points(bound=1) [(-1, 0), (1, 0)] :: sage: u = QQ['u'].0 sage: K.<v> = NumberField(u^2 + 3) sage: A.<x,y> = AffineSpace(K, 2) sage: len(A(K).points(bound=2)) 1849 :: sage: A.<x,y> = AffineSpace(QQ, 2) sage: E = A.subscheme([x^2 + y^2 - 1, y^2 - x^3 + x^2 + x - 1]) sage: E(A.base_ring()).points() [(-1, 0), (0, -1), (0, 1), (1, 0)] :: sage: A.<x,y> = AffineSpace(CC, 2) sage: E = A.subscheme([y^3 - x^3 - x^2, x*y]) sage: E(A.base_ring()).points() verbose 0 (...: affine_homset.py, points) Warning: computations in the numerical fields are inexact;points may be computed partially or incorrectly. [(-1.00000000000000, 0.000000000000000), (0.000000000000000, 0.000000000000000)] :: sage: A.<x1,x2> = AffineSpace(CDF, 2) sage: E = A.subscheme([x1^2 + x2^2 + x1*x2, x1 + x2]) sage: E(A.base_ring()).points() verbose 0 (...: affine_homset.py, points) Warning: computations in the numerical fields are inexact;points may be computed partially or incorrectly. [(0.0, 0.0)] """ from sage.schemes.affine.affine_space import is_AffineSpace X = self.codomain() if not is_AffineSpace(X) and X.base_ring() in Fields(): if hasattr(X.base_ring(), 'precision'): numerical = True verbose( "Warning: computations in the numerical fields are inexact;points may be computed partially or incorrectly.", level=0) zero_tol = RR(kwds.pop('zero_tolerance', 10**(-10))) if zero_tol <= 0: raise ValueError("tolerance must be positive") else: numerical = False # Then X must be a subscheme dim_ideal = X.defining_ideal().dimension() if dim_ideal < 0: # no points return [] if dim_ideal == 0: # if X zero-dimensional rat_points = [] AS = X.ambient_space() N = AS.dimension_relative() BR = X.base_ring() #need a lexicographic ordering for elimination R = PolynomialRing(BR, N, AS.gens(), order='lex') I = R.ideal(X.defining_polynomials()) I0 = R.ideal(0) #Determine the points through elimination #This is much faster than using the I.variety() function on each affine chart. G = I.groebner_basis() if G != [1]: P = {} points = [P] #work backwards from solving each equation for the possible #values of the next coordinate for i in range(len(G) - 1, -1, -1): new_points = [] good = 0 for P in points: #substitute in our dictionary entry that has the values #of coordinates known so far. This results in a single #variable polynomial (by elimination) L = G[i].substitute(P) if R(L).degree() > 0: if numerical: for pol in L.univariate_polynomial().roots( multiplicities=False): r = L.variables()[0] varindex = R.gens().index(r) P.update({R.gen(varindex): pol}) new_points.append(copy(P)) good = 1 else: L = L.factor() #the linear factors give the possible rational values of #this coordinate for pol, pow in L: if pol.degree() == 1 and len( pol.variables()) == 1: good = 1 r = pol.variables()[0] varindex = R.gens().index(r) #add this coordinates information to #each dictionary entry P.update({ R.gen(varindex): -pol.constant_coefficient() / pol.monomial_coefficient(r) }) new_points.append(copy(P)) else: new_points.append(P) good = 1 if good: points = new_points #the dictionary entries now have values for all coordinates #they are the rational solutions to the equations #make them into affine points for i in range(len(points)): if numerical: if len(points[i]) == N: S = AS([points[i][R.gen(j)] for j in range(N)]) if all( g(list(S)) < zero_tol for g in X.defining_polynomials()): rat_points.append(S) else: if len(points[i]) == N and I.subs(points[i]) == I0: S = X([points[i][R.gen(j)] for j in range(N)]) rat_points.append(S) rat_points = sorted(rat_points) return rat_points R = self.value_ring() B = kwds.pop('bound', 0) tol = kwds.pop('tolerance', 1e-2) prec = kwds.pop('precision', 53) if is_RationalField(R) or R == ZZ: if not B > 0: raise TypeError("a positive bound B (= %s) must be specified" % B) from sage.schemes.affine.affine_rational_point import enum_affine_rational_field return enum_affine_rational_field(self, B) if R in NumberFields(): if not B > 0: raise TypeError("a positive bound B (= %s) must be specified" % B) from sage.schemes.affine.affine_rational_point import enum_affine_number_field return enum_affine_number_field(self, bound=B, tolerance=tol, precision=prec) elif is_FiniteField(R): from sage.schemes.affine.affine_rational_point import enum_affine_finite_field return enum_affine_finite_field(self) else: raise TypeError("unable to enumerate points over %s" % R)
def mac_lane_approximants(self, G, assume_squarefree=False, require_final_EF=True, required_precision=-1, require_incomparability=False, require_maximal_degree=False, algorithm="serial"): r""" Return approximants on `K[x]` for the extensions of this valuation to `L=K[x]/(G)`. If `G` is an irreducible polynomial, then this corresponds to extensions of this valuation to the completion of `L`. INPUT: - ``G`` -- a monic squarefree integral polynomial in a univariate polynomial ring over the domain of this valuation - ``assume_squarefree`` -- a boolean (default: ``False``), whether to assume that ``G`` is squarefree. If ``True``, the squafreeness of ``G`` is not verified though it is necessary when ``require_final_EF`` is set for the algorithm to terminate. - ``require_final_EF`` -- a boolean (default: ``True``); whether to require the returned key polynomials to be in one-to-one correspondance to the extensions of this valuation to ``L`` and require them to have the ramification index and residue degree of the valuations they correspond to. - ``required_precision`` -- a number or infinity (default: -1); whether to require the last key polynomial of the returned valuations to have at least that valuation. - ``require_incomparability`` -- a boolean (default: ``False``); whether to require require the returned valuations to be incomparable (with respect to the partial order on valuations defined by comparing them pointwise.) - ``require_maximal_degree`` -- a boolean (default: ``False``); whether to require the last key polynomial of the returned valuation to have maximal degree. This is most relevant when using this algorithm to compute approximate factorizations of ``G``, when set to ``True``, the last key polynomial has the same degree as the corresponding factor. - ``algorithm`` -- one of ``"serial"`` or ``"parallel"`` (default: ``"serial"``); whether or not to parallelize the algorithm EXAMPLES:: sage: v = QQ.valuation(2) sage: R.<x> = QQ[] sage: v.mac_lane_approximants(x^2 + 1) [[ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/2 ]] sage: v.mac_lane_approximants(x^2 + 1, required_precision=infinity) [[ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/2, v(x^2 + 1) = +Infinity ]] sage: v.mac_lane_approximants(x^2 + x + 1) [[ Gauss valuation induced by 2-adic valuation, v(x^2 + x + 1) = +Infinity ]] Note that ``G`` does not need to be irreducible. Here, we detect a factor `x + 1` and an approximate factor `x + 1` (which is an approximation to `x - 1`):: sage: v.mac_lane_approximants(x^2 - 1) [[ Gauss valuation induced by 2-adic valuation, v(x + 1) = +Infinity ], [ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1 ]] However, it needs to be squarefree:: sage: v.mac_lane_approximants(x^2) Traceback (most recent call last): ... ValueError: G must be squarefree TESTS: Some difficult cases provided by Mark van Hoeij:: sage: k = GF(2) sage: K.<x> = FunctionField(k) sage: R.<y> = K[] sage: F = y^21 + x*y^20 + (x^3 + x + 1)*y^18 + (x^3 + 1)*y^17 + (x^4 + x)*y^16 + (x^7 + x^6 + x^3 + x + 1)*y^15 + x^7*y^14 + (x^8 + x^7 + x^6 + x^4 + x^3 + 1)*y^13 + (x^9 + x^8 + x^4 + 1)*y^12 + (x^11 + x^9 + x^8 + x^5 + x^4 + x^3 + x^2)*y^11 + (x^12 + x^9 + x^8 + x^7 + x^5 + x^3 + x + 1)*y^10 + (x^14 + x^13 + x^10 + x^9 + x^8 + x^7 + x^6 + x^3 + x^2 + 1)*y^9 + (x^13 + x^9 + x^8 + x^6 + x^4 + x^3 + x)*y^8 + (x^16 + x^15 + x^13 + x^12 + x^11 + x^7 + x^3 + x)*y^7 + (x^17 + x^16 + x^13 + x^9 + x^8 + x)*y^6 + (x^17 + x^16 + x^12 + x^7 + x^5 + x^2 + x + 1)*y^5 + (x^19 + x^16 + x^15 + x^12 + x^6 + x^5 + x^3 + 1)*y^4 + (x^18 + x^15 + x^12 + x^10 + x^9 + x^7 + x^4 + x)*y^3 + (x^22 + x^21 + x^20 + x^18 + x^13 + x^12 + x^9 + x^8 + x^7 + x^5 + x^4 + x^3)*y^2 + (x^23 + x^22 + x^20 + x^17 + x^15 + x^14 + x^12 + x^9)*y + x^25 + x^23 + x^19 + x^17 + x^15 + x^13 + x^11 + x^5 sage: x = K._ring.gen() sage: v0 = K.valuation(GaussValuation(K._ring, valuations.TrivialValuation(k)).augmentation(x,1)) sage: v0.mac_lane_approximants(F, assume_squarefree=True) # assumes squarefree for speed [[ Gauss valuation induced by (x)-adic valuation, v(y + x + 1) = 3/2 ], [ Gauss valuation induced by (x)-adic valuation, v(y) = 1 ], [ Gauss valuation induced by (x)-adic valuation, v(y) = 4/3 ], [ Gauss valuation induced by (x)-adic valuation, v(y^15 + y^13 + y^12 + y^10 + y^9 + y^8 + y^4 + y^3 + y^2 + y + 1) = 1 ]] sage: v0 = K.valuation(GaussValuation(K._ring, valuations.TrivialValuation(k)).augmentation(x+1,1)) sage: v0.mac_lane_approximants(F, assume_squarefree=True) # assumes squarefree for speed [[ Gauss valuation induced by (x + 1)-adic valuation, v(y + x^2 + 1) = 7/2 ], [ Gauss valuation induced by (x + 1)-adic valuation, v(y) = 3/4 ], [ Gauss valuation induced by (x + 1)-adic valuation, v(y) = 7/2 ], [ Gauss valuation induced by (x + 1)-adic valuation, v(y^13 + y^12 + y^10 + y^7 + y^6 + y^3 + 1) = 1 ]] sage: v0 = valuations.FunctionFieldValuation(K, GaussValuation(K._ring, valuations.TrivialValuation(k)).augmentation(x^3+x^2+1,1)) sage: v0.mac_lane_approximants(F, assume_squarefree=True) # assumes squarefree for speed [[ Gauss valuation induced by (x^3 + x^2 + 1)-adic valuation, v(y + x^3 + x^2 + x) = 2, v(y^2 + (x^6 + x^4 + 1)*y + x^14 + x^10 + x^9 + x^8 + x^5 + x^4 + x^3 + x^2 + x) = 5 ], [ Gauss valuation induced by (x^3 + x^2 + 1)-adic valuation, v(y^2 + (x^2 + x)*y + 1) = 1 ], [ Gauss valuation induced by (x^3 + x^2 + 1)-adic valuation, v(y^3 + (x + 1)*y^2 + (x + 1)*y + x^2 + x + 1) = 1 ], [ Gauss valuation induced by (x^3 + x^2 + 1)-adic valuation, v(y^3 + x^2*y + x) = 1 ], [ Gauss valuation induced by (x^3 + x^2 + 1)-adic valuation, v(y^4 + (x + 1)*y^3 + x^2*y^2 + (x^2 + x)*y + x) = 1 ], [ Gauss valuation induced by (x^3 + x^2 + 1)-adic valuation, v(y^7 + x^2*y^6 + (x + 1)*y^4 + x^2*y^3 + (x^2 + x + 1)*y^2 + x^2*y + x) = 1 ]] Cases with trivial residue field extensions:: sage: K.<x> = FunctionField(QQ) sage: S.<y> = K[] sage: F = y^2 - x^2 - x^3 - 3 sage: v0 = GaussValuation(K._ring, QQ.valuation(3)) sage: v1 = v0.augmentation(K._ring.gen(),1/3) sage: mu0 = valuations.FunctionFieldValuation(K, v1) sage: mu0.mac_lane_approximants(F) [[ Gauss valuation induced by Valuation on rational function field induced by [ Gauss valuation induced by 3-adic valuation, v(x) = 1/3 ], v(y + 2*x) = 2/3 ], [ Gauss valuation induced by Valuation on rational function field induced by [ Gauss valuation induced by 3-adic valuation, v(x) = 1/3 ], v(y + x) = 2/3 ]] Over a complete base field:: sage: k=Qp(2,10) sage: v = k.valuation() sage: R.<x>=k[] sage: G = x sage: v.mac_lane_approximants(G) [Gauss valuation induced by 2-adic valuation] sage: v.mac_lane_approximants(G, required_precision = infinity) [[ Gauss valuation induced by 2-adic valuation, v((1 + O(2^10))*x) = +Infinity ]] sage: G = x^2 + 1 sage: v.mac_lane_approximants(G) [[ Gauss valuation induced by 2-adic valuation, v((1 + O(2^10))*x + 1 + O(2^10)) = 1/2 ]] sage: v.mac_lane_approximants(G, required_precision = infinity) [[ Gauss valuation induced by 2-adic valuation, v((1 + O(2^10))*x + 1 + O(2^10)) = 1/2, v((1 + O(2^10))*x^2 + 1 + O(2^10)) = +Infinity ]] sage: G = x^4 + 2*x^3 + 2*x^2 - 2*x + 2 sage: v.mac_lane_approximants(G) [[ Gauss valuation induced by 2-adic valuation, v((1 + O(2^10))*x) = 1/4 ]] sage: v.mac_lane_approximants(G, required_precision=infinity) [[ Gauss valuation induced by 2-adic valuation, v((1 + O(2^10))*x) = 1/4, v((1 + O(2^10))*x^4 + (2 + O(2^11))*x^3 + (2 + O(2^11))*x^2 + (2 + 2^2 + 2^3 + 2^4 + 2^5 + 2^6 + 2^7 + 2^8 + 2^9 + 2^10 + O(2^11))*x + 2 + O(2^11)) = +Infinity ]] The factorization of primes in the Gaussian integers can be read off the Mac Lane approximants:: sage: v0 = QQ.valuation(2) sage: R.<x> = QQ[] sage: G = x^2 + 1 sage: v0.mac_lane_approximants(G) [[ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/2 ]] sage: v0 = QQ.valuation(3) sage: v0.mac_lane_approximants(G) [[ Gauss valuation induced by 3-adic valuation, v(x^2 + 1) = +Infinity ]] sage: v0 = QQ.valuation(5) sage: v0.mac_lane_approximants(G) [[ Gauss valuation induced by 5-adic valuation, v(x + 2) = 1 ], [ Gauss valuation induced by 5-adic valuation, v(x + 3) = 1 ]] sage: v0.mac_lane_approximants(G, required_precision = 10) [[ Gauss valuation induced by 5-adic valuation, v(x + 3116/237) = 10 ], [ Gauss valuation induced by 5-adic valuation, v(x - 3116/237) = 10 ]] The same example over the 5-adic numbers. In the quadratic extension `\QQ[x]/(x^2+1)`, 5 factors `-(x - 2)(x + 2)`, this behaviour can be read off the Mac Lane approximants:: sage: k=Qp(5,4) sage: v = k.valuation() sage: R.<x>=k[] sage: G = x^2 + 1 sage: v1,v2 = v.mac_lane_approximants(G); v1,v2 ([ Gauss valuation induced by 5-adic valuation, v((1 + O(5^4))*x + 2 + O(5^4)) = 1 ], [ Gauss valuation induced by 5-adic valuation, v((1 + O(5^4))*x + 3 + O(5^4)) = 1 ]) sage: w1, w2 = v.mac_lane_approximants(G, required_precision = 2); w1,w2 ([ Gauss valuation induced by 5-adic valuation, v((1 + O(5^4))*x + 2 + 5 + O(5^4)) = 2 ], [ Gauss valuation induced by 5-adic valuation, v((1 + O(5^4))*x + 3 + 3*5 + O(5^4)) = 2 ]) Note how the latter give a better approximation to the factors of `x^2 + 1`:: sage: v1.phi() * v2.phi() - G O(5^4)*x^2 + (5 + O(5^4))*x + 5 + O(5^4) sage: w1.phi() * w2.phi() - G O(5^4)*x^2 + (5^2 + O(5^4))*x + 5^3 + O(5^4) In this example, the process stops with a factorization of `x^2 + 1`:: sage: v.mac_lane_approximants(G, required_precision=infinity) [[ Gauss valuation induced by 5-adic valuation, v((1 + O(5^4))*x + 2 + 5 + 2*5^2 + 5^3 + O(5^4)) = +Infinity ], [ Gauss valuation induced by 5-adic valuation, v((1 + O(5^4))*x + 3 + 3*5 + 2*5^2 + 3*5^3 + O(5^4)) = +Infinity ]] This obviously cannot happen over the rationals where we only get an approximate factorization:: sage: v = QQ.valuation(5) sage: R.<x>=QQ[] sage: G = x^2 + 1 sage: v.mac_lane_approximants(G) [[ Gauss valuation induced by 5-adic valuation, v(x + 2) = 1 ], [ Gauss valuation induced by 5-adic valuation, v(x + 3) = 1 ]] sage: v.mac_lane_approximants(G, required_precision=5) [[ Gauss valuation induced by 5-adic valuation, v(x + 79/3) = 5 ], [ Gauss valuation induced by 5-adic valuation, v(x - 79/3) = 5 ]] Initial versions ran into problems with the trivial residue field extensions in this case:: sage: K = Qp(3, 20, print_mode='digits') sage: R.<T> = K[] sage: alpha = T^3/4 sage: G = 3^3*T^3*(alpha^4 - alpha)^2 - (4*alpha^3 - 1)^3 sage: G = G/G.leading_coefficient() sage: K.valuation().mac_lane_approximants(G) [[ Gauss valuation induced by 3-adic valuation, v(...1*T + ...2) = 1/9, v(...1*T^9 + ...20*T^8 + ...210*T^7 + ...20*T^6 + ...20*T^5 + ...10*T^4 + ...220*T^3 + ...20*T^2 + ...110*T + ...122) = 55/27 ]] A similar example:: sage: R.<x> = QQ[] sage: v = QQ.valuation(3) sage: G = (x^3 + 3)^3 - 81 sage: v.mac_lane_approximants(G) [[ Gauss valuation induced by 3-adic valuation, v(x) = 1/3, v(x^3 + 3*x + 3) = 13/9 ]] Another problematic case:: sage: R.<x> = QQ[] sage: Delta = x^12 + 20*x^11 + 154*x^10 + 664*x^9 + 1873*x^8 + 3808*x^7 + 5980*x^6 + 7560*x^5 + 7799*x^4 + 6508*x^3 + 4290*x^2 + 2224*x + 887 sage: K.<theta> = NumberField(x^6 + 108) sage: K.is_galois() True sage: vK = QQ.valuation(2).extension(K) sage: vK(2) 1 sage: vK(theta) 1/3 sage: G=Delta.change_ring(K) sage: vK.mac_lane_approximants(G) [[ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/4, v(x^4 + 1/2*theta^4 + 3*theta + 1) = 3/2 ], [ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/4, v(x^4 + 1/2*theta^4 + theta + 1) = 3/2 ], [ Gauss valuation induced by 2-adic valuation, v(x + 1) = 1/4, v(x^4 + 2*theta + 1) = 3/2 ]] An easy case that produced the wrong error at some point:: sage: R.<x> = QQ[] sage: v = QQ.valuation(2) sage: v.mac_lane_approximants(x^2 - 1/2) Traceback (most recent call last): ... ValueError: G must be integral Some examples that Sebastian Pauli used in a talk at Sage Days 87. :: sage: R = ZpFM(3, 7, print_mode='terse') sage: S.<x> = R[] sage: v = R.valuation() sage: f = x^4 + 234 sage: len(v.mac_lane_approximants(f, assume_squarefree=True)) # is_squarefree() is not properly implemented yet 2 :: sage: R = ZpFM(2, 50, print_mode='terse') sage: S.<x> = R[] sage: f = (x^32 + 16)*(x^32 + 16 + 2^16*x^2) + 2^34 sage: v = R.valuation() sage: len(v.mac_lane_approximants(f, assume_squarefree=True)) # is_squarefree() is not properly implemented yet 2 A case that triggered an assertion at some point:: sage: v = QQ.valuation(3) sage: R.<x> = QQ[] sage: f = x^36 + 60552000*x^33 + 268157412*x^30 + 173881701*x^27 + 266324841*x^24 + 83125683*x^21 + 111803814*x^18 + 31925826*x^15 + 205726716*x^12 +17990262*x^9 + 351459648*x^6 + 127014399*x^3 + 359254116 sage: v.mac_lane_approximants(f) [[ Gauss valuation induced by 3-adic valuation, v(x) = 1/3, v(x^3 - 3) = 3/2, v(x^12 - 3*x^9 + 54*x^6 + 27/2*x^3 + 405/2) = 13/2, v(x^36 + 60552000*x^33 + 268157412*x^30 + 173881701*x^27 + 266324841*x^24 + 83125683*x^21 + 111803814*x^18 + 31925826*x^15 + 205726716*x^12 + 17990262*x^9 + 351459648*x^6 + 127014399*x^3 + 359254116) = +Infinity ]] """ R = G.parent() if R.base_ring() is not self.domain(): raise ValueError( "G must be defined over the domain of this valuation") from sage.misc.verbose import verbose verbose("Approximants of %r on %r towards %r" % (self, self.domain(), G), level=3) from sage.rings.valuation.gauss_valuation import GaussValuation if not all(self(c) >= 0 for c in G.coefficients()): raise ValueError("G must be integral") if require_maximal_degree: # we can only assert maximality of degrees when E and F are final require_final_EF = True if not assume_squarefree: if require_final_EF and not G.is_squarefree(): raise ValueError("G must be squarefree") else: # if only required_precision is set, we do not need to check # whether G is squarefree. If G is not squarefree, we compute # valuations corresponding to approximants for all the # squarefree factors of G (up to required_precision.) pass def is_sufficient(leaf, others): if leaf.valuation.mu() < required_precision: return False if require_final_EF and not leaf.ef: return False if require_maximal_degree and leaf.valuation.phi().degree( ) != leaf.valuation.E() * leaf.valuation.F(): return False if require_incomparability: if any(leaf.valuation <= o.valuation for o in others): return False return True seed = MacLaneApproximantNode(GaussValuation(R, self), None, G.degree() == 1, G.degree(), None, None) seed.forced_leaf = is_sufficient(seed, []) def create_children(node): new_leafs = [] if node.forced_leaf: return new_leafs augmentations = node.valuation.mac_lane_step( G, report_degree_bounds_and_caches=True, coefficients=node.coefficients, valuations=node.valuations, check=False, # We do not want to see augmentations that are # already part of other branches of the tree of # valuations for obvious performance reasons and # also because the principal_part_bound would be # incorrect for these. allow_equivalent_key=node.valuation.is_gauss_valuation(), # The length of an edge in the Newton polygon in # one MacLane step bounds the length of the # principal part (i.e., the part with negative # slopes) of the Newton polygons in the next # MacLane step. Therefore, mac_lane_step does not # need to compute valuations for coefficients # beyond that bound as they do not contribute any # augmentations. principal_part_bound=node.principal_part_bound) for w, bound, principal_part_bound, coefficients, valuations in augmentations: ef = bound == w.E() * w.F() new_leafs.append( MacLaneApproximantNode(w, node, ef, principal_part_bound, coefficients, valuations)) for leaf in new_leafs: if is_sufficient(leaf, [l for l in new_leafs if l is not leaf]): leaf.forced_leaf = True return new_leafs def reduce_tree(v, w): return v + w from sage.all import RecursivelyEnumeratedSet tree = RecursivelyEnumeratedSet([seed], successors=create_children, structure='forest', enumeration='breadth') # this is a tad faster but annoying for profiling / debugging if algorithm == 'parallel': nodes = tree.map_reduce(map_function=lambda x: [x], reduce_init=[]) elif algorithm == 'serial': from sage.parallel.map_reduce import RESetMapReduce nodes = RESetMapReduce(forest=tree, map_function=lambda x: [x], reduce_init=[]).run_serial() else: raise NotImplementedError(algorithm) leafs = set([node.valuation for node in nodes]) for node in nodes: if node.parent is None: continue v = node.parent.valuation if v in leafs: leafs.remove(v) # The order of the leafs is not predictable in parallel mode and in # serial mode it depends on the hash functions and so on the underlying # architecture (32/64 bit). There is no natural ordering on these # valuations but it is very convenient for doctesting to return them in # some stable order, so we just order them by their string # representation which should be very fast. try: ret = sorted(leafs, key=str) except Exception: # if for some reason the valuation can not be printed, we leave them unsorted ret = list(leafs) return ret
def _find_scaling_period(self): r""" Uses the integral period map of the modular symbol implementation in sage in order to determine the scaling. The resulting modular symbol is correct only for the `X_0`-optimal curve, at least up to a possible factor +- a power of 2. EXAMPLES:: sage: E = EllipticCurve('11a1') sage: m = sage.schemes.elliptic_curves.ell_modular_symbols.ModularSymbolSage(E,+1,normalize='period') sage: m._e (1/5, 1/2) sage: E = EllipticCurve('11a2') sage: m = sage.schemes.elliptic_curves.ell_modular_symbols.ModularSymbolSage(E,+1,normalize='period') sage: m._e (1, 5/2) sage: E = EllipticCurve('121b2') sage: m = sage.schemes.elliptic_curves.ell_modular_symbols.ModularSymbolSage(E,+1,normalize='period') sage: m._e (0, 0, 0, 11/2, 11/2, 11/2, 11/2, -3, 3/2, 1/2, -1, 2) TESTS:: sage: E = EllipticCurve('19a1') sage: m = E.modular_symbol(sign=+1, implementation='sage', normalize='none') sage: m._find_scaling_period() sage: m._scaling 1 sage: E = EllipticCurve('19a2') sage: m = E.modular_symbol(sign=+1, implementation='sage', normalize='none') sage: m._scaling 1 sage: m._find_scaling_period() sage: m._scaling 3 """ P = self._modsym.integral_period_mapping() self._e = P.matrix().transpose().row(0) self._e /= 2 E = self._E try: crla = parse_cremona_label(E.label()) except RuntimeError: # raised when curve is outside of the table print( "Warning : Could not normalize the modular symbols, maybe all further results will be multiplied by a rational number." ) self._scaling = 1 else: cr0 = Integer(crla[0]).str() + crla[1] + '1' E0 = EllipticCurve(cr0) if self._sign == 1: q = E0.period_lattice().basis()[0] / E.period_lattice().basis( )[0] else: q = E0.period_lattice().basis()[1].imag() / E.period_lattice( ).basis()[1].imag() if E0.real_components() == 1: q *= 2 if E.real_components() == 1: q /= 2 q = QQ((q * 200).round()) / 200 verbose('scale modular symbols by %s' % q) self._scaling = q c = self(0) # required, to change base point from oo to 0 if c < 0: c *= -1 self._scaling *= -1 self._at_zero = c self._e *= self._scaling
def cuspidal_submodule_q_expansion_basis(self, weight, prec=None): r""" Calculate a basis of `q`-expansions for the space of cusp forms of weight ``weight`` for this group. INPUT: - ``weight`` (integer) -- the weight - ``prec`` (integer or None) -- precision of `q`-expansions to return ALGORITHM: Uses the method :meth:`cuspidal_ideal_generators` to calculate generators of the ideal of cusp forms inside this ring. Then multiply these up to weight ``weight`` using the generators of the whole modular form space returned by :meth:`q_expansion_basis`. EXAMPLES:: sage: R = ModularFormsRing(Gamma0(3)) sage: R.cuspidal_submodule_q_expansion_basis(20) [q - 8532*q^6 - 88442*q^7 + O(q^8), q^2 + 207*q^6 + 24516*q^7 + O(q^8), q^3 + 456*q^6 + O(q^8), q^4 - 135*q^6 - 926*q^7 + O(q^8), q^5 + 18*q^6 + 135*q^7 + O(q^8)] We compute a basis of a space of very large weight, quickly (using this module) and slowly (using modular symbols), and verify that the answers are the same. :: sage: A = R.cuspidal_submodule_q_expansion_basis(80, prec=30) # long time (1s on sage.math, 2013) sage: B = R.modular_forms_of_weight(80).cuspidal_submodule().q_expansion_basis(prec=30) # long time (19s on sage.math, 2013) sage: A == B # long time True """ d = self.modular_forms_of_weight( weight).cuspidal_submodule().dimension() if d == 0: return [] minprec = self.modular_forms_of_weight(weight).sturm_bound() if prec is None: prec = working_prec = minprec else: working_prec = max(prec, minprec) gen_weight = min(6, weight) while True: verbose( "Trying to generate the %s-dimensional cuspidal submodule at weight %s using generators of weight up to %s" % (d, weight, gen_weight)) G = self.cuspidal_ideal_generators(maxweight=gen_weight, prec=working_prec) flist = [] for (j, f, F) in G: for g in self.q_expansion_basis(weight - j, prec=working_prec): flist.append(g * f) A = self.base_ring()**working_prec W = A.span([A(f.padded_list(working_prec)) for f in flist]) if W.rank() == d and (self.base_ring().is_field() or W.index_in_saturation() == 1): break else: gen_weight += 1 verbose( "Need more generators: trying again with generators of weight up to %s" % gen_weight) R = G[0][1].parent() return [R(list(x), prec=prec) for x in W.gens()]
def hnf(A, include_zero_rows=True, proof=True): """ Return the Hermite Normal Form of a general integer matrix A, along with the pivot columns. INPUT: - A -- an n x m matrix A over the integers. - include_zero_rows -- bool (default: True) whether or not to include zero rows in the output matrix - proof -- whether or not to prove the result correct. OUTPUT: - matrix -- the Hermite normal form of A - pivots -- the pivot column positions of A EXAMPLES:: sage: import sage.matrix.matrix_integer_dense_hnf as matrix_integer_dense_hnf sage: a = matrix(ZZ,3,5,[-2, -6, -3, -17, -1, 2, -1, -1, -2, -1, -2, -2, -6, 9, 2]) sage: matrix_integer_dense_hnf.hnf(a) ( [ 2 0 26 -75 -10] [ 0 1 27 -73 -9] [ 0 0 37 -106 -13], [0, 1, 2] ) sage: matrix_integer_dense_hnf.hnf(a.transpose()) ( [1 0 0] [0 1 0] [0 0 1] [0 0 0] [0 0 0], [0, 1, 2] ) sage: matrix_integer_dense_hnf.hnf(a.transpose(), include_zero_rows=False) ( [1 0 0] [0 1 0] [0 0 1], [0, 1, 2] ) """ if A.nrows() <= 1: np = A.nonzero_positions() if not np: pivots = [] if not include_zero_rows: A = A.new_matrix(0) # 0 rows else: i, j = np[0] if A[i, j] < 0: A = -A pivots = [j] return A, pivots if not proof: H, pivots = probable_hnf(A, include_zero_rows=include_zero_rows, proof=False) if not include_zero_rows and len(pivots) > H.nrows(): return H.matrix_from_rows(range(len(pivots))), pivots while True: H, pivots = probable_hnf(A, include_zero_rows=include_zero_rows, proof=True) if is_in_hnf_form(H, pivots): if not include_zero_rows and len(pivots) > H.nrows(): H = H.matrix_from_rows(range(len(pivots))) return H, pivots verbose( "After attempt the return matrix is not in HNF form since pivots must have been wrong. We try again." )
def _span_of_forms_in_weight(forms, weight, prec, stop_dim=None, use_random=False): r""" Utility function. Given a nonempty list of pairs ``(k,f)``, where `k` is an integer and `f` is a power series, and a weight l, return all weight l forms obtained by multiplying together the given forms. INPUT: - ``forms`` -- list of pairs `(k, f)` with k an integer and f a power series (all over the same base ring) - ``weight`` -- an integer - ``prec`` -- an integer (less than or equal to the precision of all the forms in ``forms``) -- precision to use in power series computations. - ``stop_dim`` -- an integer: stop as soon as we have enough forms to span a submodule of this rank (a saturated one if the base ring is `\ZZ`). Ignored if ``use_random`` is False. - ``use_random`` -- which algorithm to use. If True, tries random products of the generators of the appropriate weight until a large enough submodule is found (determined by ``stop_dim``). If False, just tries everything. Note that if the given forms do generate the whole space, then ``use_random=True`` will often be quicker (particularly if the weight is large); but if the forms don't generate, the randomized algorithm is no help and will actually be substantially slower, because it needs to do repeated echelon form calls to check if vectors are in a submodule, while the non-randomized algorithm just echelonizes one enormous matrix at the end. EXAMPLES:: sage: import sage.modular.modform.ring as f sage: forms = [(4, 240*eisenstein_series_qexp(4,5)), (6,504*eisenstein_series_qexp(6,5))] sage: f._span_of_forms_in_weight(forms, 12, prec=5) Vector space of degree 5 and dimension 2 over Rational Field Basis matrix: [ 1 0 196560 16773120 398034000] [ 0 1 -24 252 -1472] sage: f._span_of_forms_in_weight(forms, 24, prec=5) Vector space of degree 5 and dimension 3 over Rational Field Basis matrix: [ 1 0 0 52416000 39007332000] [ 0 1 0 195660 12080128] [ 0 0 1 -48 1080] sage: ModularForms(1, 24).q_echelon_basis(prec=5) [ 1 + 52416000*q^3 + 39007332000*q^4 + O(q^5), q + 195660*q^3 + 12080128*q^4 + O(q^5), q^2 - 48*q^3 + 1080*q^4 + O(q^5) ] Test the alternative randomized algorithm:: sage: f._span_of_forms_in_weight(forms, 24, prec=5, use_random=True, stop_dim=3) Vector space of degree 5 and dimension 3 over Rational Field Basis matrix: [ 1 0 0 52416000 39007332000] [ 0 1 0 195660 12080128] [ 0 0 1 -48 1080] """ t = verbose('multiplying forms up to weight %s' % weight) # Algorithm: run through the monomials of the appropriate weight, and build # up the vector space they span. n = len(forms) R = forms[0][1].base_ring() V = R**prec W = V.zero_submodule() shortforms = [f[1].truncate_powerseries(prec) for f in forms] # List of weights from sage.combinat.integer_vector_weighted import WeightedIntegerVectors wts = list(WeightedIntegerVectors(weight, [f[0] for f in forms])) t = verbose("calculated weight list", t) N = len(wts) if use_random: if stop_dim is None: raise ValueError("stop_dim must be provided if use_random is True") shuffle(wts) for c in range(N): w = V( prod(shortforms[i]**wts[c][i] for i in range(n)).padded_list(prec)) if w in W: continue W = V.span(list(W.gens()) + [w]) if stop_dim and W.rank() == stop_dim: if R != ZZ or W.index_in_saturation() == 1: verbose("Succeeded after %s of %s" % (c, N), t) return W verbose("Nothing worked", t) return W else: G = [ V(prod(forms[i][1]**c[i] for i in range(n)).padded_list(prec)) for c in wts ] t = verbose('found %s candidates' % N, t) W = V.span(G) verbose('span has dimension %s' % W.rank(), t) return W
def solve_system_with_difficult_last_row(B, a): """ Solve B*x = a when the last row of `B` contains huge entries using a clever trick that reduces the problem to solve C*x = a where `C` is `B` but with the last row replaced by something small, along with one easy null space computation. The latter are both solved `p`-adically. INPUT: - B -- a square n x n nonsingular matrix with painful big bottom row. - a -- an n x 1 column matrix OUTPUT: - the unique solution to B*x = a. EXAMPLES:: sage: from sage.matrix.matrix_integer_dense_hnf import solve_system_with_difficult_last_row sage: B = matrix(ZZ, 3, [1,2,4, 3,-4,7, 939082,2930982,132902384098234]) sage: a = matrix(ZZ,3,1, [1,2,5]) sage: z = solve_system_with_difficult_last_row(B, a) sage: z [ 106321906985474/132902379815497] [132902385037291/1329023798154970] [ -5221794/664511899077485] sage: B*z [1] [2] [5] """ # Here's how: # 1. We make a copy of B but with the last *nasty* row of B replaced # by a random very nice row. C = copy(B) while True: C[C.nrows() - 1] = random_matrix(ZZ, 1, C.ncols()).row(0) # 2. Then we find the unique solution to C * x = a try: x = C.solve_right(a) except ValueError: verbose("Try difficult solve again with different random vector") else: break # 3. We next delete the last row of B and find a basis vector k # for the 1-dimensional kernel. D = B.matrix_from_rows(range(C.nrows() - 1)) N = D._rational_kernel_iml() if N.ncols() != 1: verbose("Try difficult solve again with different random vector") return solve_system_with_difficult_last_row(B, a) k = N.matrix_from_columns([0]) # 4. The sought for solution z to B*z = a is some linear combination # # z = x + alpha*k # # of x and k, where k is the above fixed basis for the kernel of D. # Setting w to be the last row of B, this column vector z satisfies # # w * z = a' # # where a' is the last entry of a. Thus # # w * (x + alpha*k) = a' # # so w * x + alpha*w*k = a' # so alpha*w*k = a' - w*x. w = B[-1] # last row of B a_prime = a[-1] lhs = w * k rhs = a_prime - w * x if lhs[0] == 0: verbose("Try difficult solve again with different random vector") return solve_system_with_difficult_last_row(B, a) alpha = rhs[0] / lhs[0] z = x + alpha * k return z
def q_expansion_basis(self, weight, prec=None, use_random=True): r""" Calculate a basis of q-expansions for the space of modular forms of the given weight for this group, calculated using the ring generators given by ``find_generators``. INPUT: - ``weight`` (integer) -- the weight - ``prec`` (integer or ``None``, default: ``None``) -- power series precision. If ``None``, the precision defaults to the Sturm bound for the requested level and weight. - ``use_random`` (boolean, default: True) -- whether or not to use a randomized algorithm when building up the space of forms at the given weight from known generators of small weight. EXAMPLES:: sage: m = ModularFormsRing(Gamma0(4)) sage: m.q_expansion_basis(2,10) [1 + 24*q^2 + 24*q^4 + 96*q^6 + 24*q^8 + O(q^10), q + 4*q^3 + 6*q^5 + 8*q^7 + 13*q^9 + O(q^10)] sage: m.q_expansion_basis(3,10) [] sage: X = ModularFormsRing(SL2Z) sage: X.q_expansion_basis(12, 10) [1 + 196560*q^2 + 16773120*q^3 + 398034000*q^4 + 4629381120*q^5 + 34417656000*q^6 + 187489935360*q^7 + 814879774800*q^8 + 2975551488000*q^9 + O(q^10), q - 24*q^2 + 252*q^3 - 1472*q^4 + 4830*q^5 - 6048*q^6 - 16744*q^7 + 84480*q^8 - 113643*q^9 + O(q^10)] We calculate a basis of a massive modular forms space, in two ways. Using this module is about twice as fast as Sage's generic code. :: sage: A = ModularFormsRing(11).q_expansion_basis(30, prec=40) # long time (5s) sage: B = ModularForms(Gamma0(11), 30).q_echelon_basis(prec=40) # long time (9s) sage: A == B # long time True Check that absurdly small values of ``prec`` don't mess things up:: sage: ModularFormsRing(11).q_expansion_basis(10, prec=5) [1 + O(q^5), q + O(q^5), q^2 + O(q^5), q^3 + O(q^5), q^4 + O(q^5), O(q^5), O(q^5), O(q^5), O(q^5), O(q^5)] """ d = self.modular_forms_of_weight(weight).dimension() if d == 0: return [] if prec is None: prec = self.modular_forms_of_weight(weight).sturm_bound() working_prec = max(prec, self.modular_forms_of_weight(weight).sturm_bound()) gen_weight = min(6, weight) while True: verbose( "Trying to generate the %s-dimensional space at weight %s using generators of weight up to %s" % (d, weight, gen_weight)) G = self.generators(maxweight=gen_weight, prec=working_prec) V = _span_of_forms_in_weight(G, weight, prec=working_prec, use_random=use_random, stop_dim=d) if V.rank() == d and (self.base_ring().is_field() or V.index_in_saturation() == 1): break else: gen_weight += 1 verbose( "Need more generators: trying again with generators of weight up to %s" % gen_weight) R = G[0][1].parent() return [R(list(x), prec=prec) for x in V.gens()]
def hnf_square(A, proof): """ INPUT: - a nonsingular n x n matrix A over the integers. OUTPUT: - the Hermite normal form of A. EXAMPLES:: sage: import sage.matrix.matrix_integer_dense_hnf as hnf sage: A = matrix(ZZ, 3, [-21, -7, 5, 1,20,-7, -1,1,-1]) sage: hnf.hnf_square(A, False) [ 1 6 29] [ 0 7 28] [ 0 0 46] sage: A.echelon_form() [ 1 6 29] [ 0 7 28] [ 0 0 46] """ n = A.nrows() m = A.ncols() if n != m: raise ValueError("A must be square.") # Small cases -- do not use this algorithm if n <= 3: return A.echelon_form(algorithm="pari") if A.rank() < A.nrows(): raise ValueError("matrix must have full rank") t = verbose("starting slicings") B = A.matrix_from_rows(range(m - 2)).matrix_from_columns(range(n - 1)) c = A.matrix_from_rows([m - 2]).matrix_from_columns(range(n - 1)) d = A.matrix_from_rows([m - 1]).matrix_from_columns(range(n - 1)) b = A.matrix_from_columns([n - 1]).matrix_from_rows(range(m - 2)) verbose("done slicing", t) try: d1, d2 = double_det(B, c, d, proof=proof) except (ValueError, ZeroDivisionError): d1 = B.stack(c).det(proof=proof) d2 = B.stack(d).det(proof=proof) g, k, l = d1._xgcd(d2, minimal=True) W = B.stack(k * c + l * d) verbose("submatrix det: g=%s" % g) CUTOFF = 2147483647 # 2^31-1 if g == 0: # Big trouble -- matrix is not invertible # Since we have no good conditioning code at present, # in this case we just fall back to using pari. H = W.echelon_form(algorithm='pari') elif 2 * g > CUTOFF: # Unlikely that g will be large on even slightly random input # if it is, we fallback to the traditional algorithm. # A nasty example is A = n*random_matrix(ZZ,m), where # this algorithm gets killed. This is not random input though. f = W.gcd() g = g / (f**W.nrows()) if 2 * g <= CUTOFF: verbose( "Found common factor of %s -- dividing out; get new g = %s" % (f, g)) W0 = (W / f).change_ring(ZZ) H = W0._hnf_mod(2 * g) H *= f else: verbose( "Falling back to PARI HNF since input matrix is ill conditioned for p-adic hnf algorithm." ) # We need more clever preconditioning? # It is important to *not* just do the submatrix, since # the whole rest of the algorithm will likely be very slow in # weird cases where the det is large. # E.g., matrix all of whose rows but 1 are multiplied by some # fixed scalar n. raise NotImplementedError("fallback to PARI!") # H = W.hermite_form(algorithm='pari') else: H = W._hnf_mod(2 * g) x = add_column( W, H, b.stack(matrix(1, 1, [k * A[m - 2, m - 1] + l * A[m - 1, m - 1]])), proof) Hprime = H.augment(x) pivots = pivots_of_hnf_matrix(Hprime) Hprime, pivots = add_row(Hprime, A.matrix_from_rows([m - 2]), pivots, include_zero_rows=False) Hprime, pivots = add_row(Hprime, A.matrix_from_rows([m - 1]), pivots, include_zero_rows=False) return Hprime.matrix_from_rows(range(m))
def siegel_product(self, u): """ Computes the infinite product of local densities of the quadratic form for the number `u`. EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1]) sage: Q.theta_series(11) 1 + 8*q + 24*q^2 + 32*q^3 + 24*q^4 + 48*q^5 + 96*q^6 + 64*q^7 + 24*q^8 + 104*q^9 + 144*q^10 + O(q^11) sage: Q.siegel_product(1) 8 sage: Q.siegel_product(2) ## This one is wrong -- expect 24, and the higher powers of 2 don't work... =( 24 sage: Q.siegel_product(3) 32 sage: Q.siegel_product(5) 48 sage: Q.siegel_product(6) 96 sage: Q.siegel_product(7) 64 sage: Q.siegel_product(9) 104 sage: Q.local_density(2,1) 1 sage: M = 4; len([v for v in mrange([M,M,M,M]) if Q(v) % M == 1]) / M^3 1 sage: M = 16; len([v for v in mrange([M,M,M,M]) if Q(v) % M == 1]) / M^3 # long time (2s on sage.math, 2014) 1 sage: Q.local_density(2,2) 3/2 sage: M = 4; len([v for v in mrange([M,M,M,M]) if Q(v) % M == 2]) / M^3 3/2 sage: M = 16; len([v for v in mrange([M,M,M,M]) if Q(v) % M == 2]) / M^3 # long time (2s on sage.math, 2014) 3/2 TESTS:: sage: [1] + [Q.siegel_product(ZZ(a)) for a in range(1,11)] == Q.theta_series(11).list() # long time (2s on sage.math, 2014) True """ ## Protect u (since it fails often if it's an just an int!) u = ZZ(u) n = self.dim() d = self.det( ) ## ??? Warning: This is a factor of 2^n larger than it should be! # DIAGNOSTIC verbose("n = " + str(n)) verbose("d = " + str(d)) verbose("In siegel_product: d = " + str(d) + "\n") ## Product of "bad" places to omit S = 2 * d * u ## DIAGNOSTIC verbose("siegel_product Break 1. \n") verbose(" u = " + str(u) + "\n") ## Make the odd generic factors if ((n % 2) == 1): m = (n - 1) // 2 d1 = fundamental_discriminant( ((-1)**m) * 2 * d * u) ## Replaced d by 2d here to compensate for the determinant f = abs( d1) ## gaining an odd power of 2 by using the matrix of 2Q instead ## of the matrix of Q. ## --> Old d1 = CoreDiscriminant((mpz_class(-1)^m) * d * u); ## Make the ratio of factorials factor: [(2m)! / m!] * prod_{i=1}^m (2*i-1) factor1 = 1 for i in range(1, m + 1): factor1 *= 2 * i - 1 for i in range(m + 1, 2 * m + 1): factor1 *= i genericfactor = factor1 * ((u / f) ** m) \ * QQ(sqrt((2 ** n) * f) / (u * d)) \ * abs(QuadraticBernoulliNumber(m, d1) / bernoulli(2*m)) ## DIAGNOSTIC verbose("siegel_product Break 2. \n") ## Make the even generic factor if ((n % 2) == 0): m = n // 2 d1 = fundamental_discriminant(((-1)**m) * d) f = abs(d1) ## DIAGNOSTIC #cout << " mpz_class(-1)^m = " << (mpz_class(-1)^m) << " and d = " << d << endl; #cout << " f = " << f << " and d1 = " << d1 << endl; genericfactor = m / QQ(sqrt(f*d)) \ * ((u/2) ** (m-1)) * (f ** m) \ / abs(QuadraticBernoulliNumber(m, d1)) \ * (2 ** m) ## This last factor compensates for using the matrix of 2*Q ##return genericfactor ## Omit the generic factors in S and compute them separately omit = 1 include = 1 S_divisors = prime_divisors(S) ## DIAGNOSTIC #cout << "\n S is " << S << endl; #cout << " The Prime divisors of S are :"; #PrintV(S_divisors); for p in S_divisors: Q_normal = self.local_normal_form(p) ## DIAGNOSTIC verbose(" p = " + str(p) + " and its Kronecker symbol (d1/p) = (" + str(d1) + "/" + str(p) + ") is " + str(kronecker_symbol(d1, p)) + "\n") omit *= 1 / (1 - (kronecker_symbol(d1, p) / (p**m))) ## DIAGNOSTIC verbose(" omit = " + str(omit) + "\n") verbose(" Q_normal is \n" + str(Q_normal) + "\n") verbose(" Q_normal = \n" + str(Q_normal)) verbose(" p = " + str(p) + "\n") verbose(" u = " + str(u) + "\n") verbose(" include = " + str(include) + "\n") include *= Q_normal.local_density(p, u) ## DIAGNOSTIC #cout << " Including the p = " << p << " factor: " << local_density(Q_normal, p, u) << endl; ## DIAGNOSTIC verbose(" --- Exiting loop \n") #// **************** Important ******************* #// Additional fix (only included for n=4) to deal #// with the power of 2 introduced at the real place #// by working with Q instead of 2*Q. This needs to #// be done for all other n as well... #/* #if (n==4) # genericfactor = 4 * genericfactor; #*/ ## DIAGNOSTIC #cout << endl; #cout << " generic factor = " << genericfactor << endl; #cout << " omit = " << omit << endl; #cout << " include = " << include << endl; #cout << endl; ## DIAGNOSTIC #// cout << "siegel_product Break 3. " << endl; ## Return the final factor (and divide by 2 if n=2) if n == 2: return genericfactor * omit * include / 2 else: return genericfactor * omit * include
def extract_ones_data(H, pivots): """ Compute ones data and corresponding submatrices of H. This is used to optimized the :func:`add_row` function. INPUT: - H -- a matrix in HNF - pivots -- list of all pivot column positions of H OUTPUT: C, D, E, onecol, onerow, non_onecol, non_onerow where onecol, onerow, non_onecol, non_onerow are as for the ones function, and C, D, E are matrices: - C -- submatrix of all non-onecol columns and onecol rows - D -- all non-onecol columns and other rows - E -- inverse of D If D is not invertible or there are 0 or more than 2 non onecols, then C, D, and E are set to None. EXAMPLES:: sage: H = matrix(ZZ, 3, 4, [1, 0, 0, 7, 0, 1, 5, 2, 0, 0, 6, 6]) sage: import sage.matrix.matrix_integer_dense_hnf as matrix_integer_dense_hnf sage: matrix_integer_dense_hnf.extract_ones_data(H, [0,1,2]) ( [0] [5], [6], [1/6], [0, 1], [0, 1], [2], [2] ) Here we get None's since the (2,2) position submatrix is not invertible. sage: H = matrix(ZZ, 3, 5, [1, 0, 0, 45, -36, 0, 1, 0, 131, -107, 0, 0, 0, 178, -145]); H [ 1 0 0 45 -36] [ 0 1 0 131 -107] [ 0 0 0 178 -145] sage: import sage.matrix.matrix_integer_dense_hnf as matrix_integer_dense_hnf sage: matrix_integer_dense_hnf.extract_ones_data(H, [0,1,3]) (None, None, None, [0, 1], [0, 1], [2], [2]) """ onecol, onerow, non_onecol, non_onerow = ones(H, pivots) verbose('extract_ones -- got submatrix of size %s' % len(non_onecol)) if len(non_onecol) in [1, 2]: # Extract submatrix of all non-onecol columns and onecol rows C = H.matrix_from_rows_and_columns(onerow, non_onecol) # Extract submatrix of all non-onecol columns and other rows D = H.matrix_from_rows_and_columns(non_onerow, non_onecol).transpose() tt = verbose("extract ones -- INVERT %s x %s" % (len(non_onerow), len(non_onecol)), level=1) try: E = D**(-1) except ZeroDivisionError: C = D = E = None verbose("done inverting", tt, level=1) return C, D, E, onecol, onerow, non_onecol, non_onerow else: return None, None, None, onecol, onerow, non_onecol, non_onerow
def hom(self, im_gens, codomain=None, check=True, base_map=None): r""" This version keeps track with the special recursive structure of :class:`SplittingAlgebra` Type ``Ring.hom?`` to see the general documentation of this method. Here you see just special examples for the current class. EXAMPLES:: sage: from sage.algebras.splitting_algebra import SplittingAlgebra sage: L.<u, v, w> = LaurentPolynomialRing(ZZ); x = polygen(L) sage: S = SplittingAlgebra(x^3 - u*x^2 + v*x - w, ('X', 'Y')) sage: P.<x, y, z> = PolynomialRing(ZZ) sage: F = FractionField(P) sage: im_gens = [F(g) for g in [y, x, x + y + z, x*y+x*z+y*z, x*y*z]] sage: f = S.hom(im_gens) sage: f(u), f(v), f(w) (x + y + z, x*y + x*z + y*z, x*y*z) sage: roots = S.splitting_roots(); roots [X, Y, -Y - X + u] sage: [f(r) for r in roots] [x, y, z] """ base_ring = self.base_ring() if not isinstance(im_gens, (list, tuple)): im_gens = [im_gens] all_gens = self.gens_dict_recursive() if len(im_gens) != len(all_gens): return super(SplittingAlgebra, self).hom(im_gens, codomain=codomain, check=check, base_map=base_map) num_gens = len(self.gens()) im_gens_start = [ img for img in im_gens if im_gens.index(img) < num_gens ] im_gens_end = [ img for img in im_gens if im_gens.index(img) >= num_gens ] if not im_gens_end: return super(SplittingAlgebra, self).hom(im_gens, codomain=codomain, check=check, base_map=base_map) verbose('base %s im_gens_end %s codomain %s check %s base_map %s' % (base_ring, im_gens_end, codomain, check, base_map)) hom_on_base_recurs = base_ring.hom(im_gens_end, codomain=codomain, check=check, base_map=base_map) verbose('hom_on_base_recurs %s' % (hom_on_base_recurs)) cover_ring = self.cover_ring() hom_from_cover = cover_ring.hom(im_gens_start, codomain=codomain, check=check, base_map=hom_on_base_recurs) lift = self.lifting_map() return hom_from_cover * lift
def probable_hnf(A, include_zero_rows, proof): """ Return the HNF of A or raise an exception if something involving the randomized nature of the algorithm goes wrong along the way. Calling this function again a few times should result it in it working, at least if proof=True. INPUT: - A -- a matrix - include_zero_rows -- bool - proof -- bool OUTPUT: the Hermite normal form of A. cols -- pivot columns EXAMPLES:: sage: a = matrix(ZZ,4,3,[-1, -1, -1, -20, 4, 1, -1, 1, 2,1,2,3]) sage: import sage.matrix.matrix_integer_dense_hnf as matrix_integer_dense_hnf sage: matrix_integer_dense_hnf.probable_hnf(a, True, True) ( [1 0 0] [0 1 0] [0 0 1] [0 0 0], [0, 1, 2] ) sage: matrix_integer_dense_hnf.probable_hnf(a, False, True) ( [1 0 0] [0 1 0] [0 0 1], [0, 1, 2] ) sage: matrix_integer_dense_hnf.probable_hnf(a, False, False) ( [1 0 0] [0 1 0] [0 0 1], [0, 1, 2] ) """ # Find left-most full rank submatrix by working modulo a prime rows = list(probable_pivot_rows(A)) B = A.matrix_from_rows(rows) cols = list(probable_pivot_columns(B)) C = B.matrix_from_columns(cols) # Now C is a submatrix of A that has full rank and is square. # We compute the HNF of C, which is a square nonsingular matrix. try: H = hnf_square(C, proof=proof) except NotImplementedError: # raise # this signals that we must fallback to PARI verbose( "generic random modular HNF algorithm failed -- we fall back to PARI" ) H = A.hermite_form(algorithm='pari', include_zero_rows=include_zero_rows, proof=proof) return H, H.pivots() # The transformation matrix to HNF is the unique # matrix U such that U * C = H, i.e., U = H*C^(-1). if len(cols) < B.ncols(): # We compute the HNF of B by multiplying the matrix D # got from the columns not in C by U: # We want to compute X = U*D. But U = H*C^(-1), # so X = U*D = H*C^(-1)*D. # So C*H^(-1)*X = D # find y s.t C*y = D # H^(-1)*X = y ===> X = H*y # cols_set = set(cols) cols2 = [i for i in range(B.ncols()) if i not in cols_set] D = B.matrix_from_columns(cols2) Y = C.solve_right(D) H2 = H * Y H2 = H2.change_ring(ZZ) # The HNF of B is got by assembling together # the matrices H and H2. H = interleave_matrices(H, H2, cols, cols2) pivots = pivots_of_hnf_matrix(H) # Now H is the HNF of the matrix B. # Finally we add all remaining rows of A to H using # the add_row function. C, D, E, onecol, onerow, non_onecol, non_onerow = extract_ones_data( H, cols) if not proof and len(non_onecol) == 0: # Identity matrix -- done verbose("hnf -- got identity matrix -- early abort (0)") if include_zero_rows: H = pad_zeros(H, A.nrows()) return H, pivots rows_set = set(rows) for i in range(A.nrows()): if i not in rows_set: v = A.matrix_from_rows([i]) if v == 0: continue if E is None: H, pivots = add_row(H, v, pivots, include_zero_rows=False) C, D, E, onecol, onerow, non_onecol, non_onerow = extract_ones_data( H, pivots) if not proof and len(non_onecol) == 0: # Identity matrix -- done verbose("hnf -- got identity matrix -- early abort (1)") if include_zero_rows: H = pad_zeros(H, A.nrows()) return H, pivots else: z = A.matrix_from_rows_and_columns([i], non_onecol) w = A.matrix_from_rows_and_columns([i], onecol) tt = verbose("checking denom (%s x %s)" % (D.nrows(), D.ncols())) Y = (z - w * C).transpose() k = E * Y verbose("done checking denom", tt) if k.denominator() != 1: H, pivots = add_row(H, v, pivots, include_zero_rows=False) D = H.matrix_from_rows_and_columns(non_onerow, non_onecol).transpose() nn = ones(H, pivots) if not proof and len(nn[2]) == 0: verbose("hnf -- got identity matrix -- early abort (2)") if include_zero_rows: H = pad_zeros(H, A.nrows()) return H, pivots if include_zero_rows: H = pad_zeros(H, A.nrows()) return H, pivots
def points(self, **kwds): """ Return some or all rational points of a projective scheme. For dimension 0 subschemes points are determined through a groebner basis calculation. For schemes or subschemes with dimension greater than 1 points are determined through enumeration up to the specified bound. INPUT: kwds: - ``bound`` - real number (optional, default=0). The bound for the coordinates for subschemes with dimension at least 1. - ``precision`` - integer (optional, default=53). The precision to use to compute the elements of bounded height for number fields. - ``point_tolerance`` - positive real number (optional, default=10^(-10)). For numerically inexact fields, two points are considered the same if their coordinates are within tolerance. - ``zero_tolerance`` - positive real number (optional, default=10^(-10)). For numerically inexact fields, points are on the subscheme if they satisfy the equations to within tolerance. - ``tolerance`` - a rational number in (0,1] used in doyle-krumm algorithm-4 for enumeration over number fields. OUTPUT: - a list of rational points of a projective scheme .. WARNING:: For numerically inexact fields such as ComplexField or RealField the list of points returned is very likely to be incomplete. It may also contain repeated points due to tolerances. EXAMPLES:: sage: P.<x,y> = ProjectiveSpace(QQ,1) sage: P(QQ).points(bound=4) [(-4 : 1), (-3 : 1), (-2 : 1), (-3/2 : 1), (-4/3 : 1), (-1 : 1), (-3/4 : 1), (-2/3 : 1), (-1/2 : 1), (-1/3 : 1), (-1/4 : 1), (0 : 1), (1/4 : 1), (1/3 : 1), (1/2 : 1), (2/3 : 1), (3/4 : 1), (1 : 0), (1 : 1), (4/3 : 1), (3/2 : 1), (2 : 1), (3 : 1), (4 : 1)] :: sage: u = QQ['u'].0 sage: K.<v> = NumberField(u^2 + 3) sage: P.<x,y,z> = ProjectiveSpace(K,2) sage: len(P(K).points(bound=1.8)) 381 :: sage: P1 = ProjectiveSpace(GF(2),1) sage: F.<a> = GF(4,'a') sage: P1(F).points() [(0 : 1), (1 : 0), (1 : 1), (a : 1), (a + 1 : 1)] :: sage: P.<x,y,z> = ProjectiveSpace(QQ,2) sage: E = P.subscheme([(y^3-y*z^2) - (x^3-x*z^2),(y^3-y*z^2) + (x^3-x*z^2)]) sage: E(P.base_ring()).points() [(-1 : -1 : 1), (-1 : 0 : 1), (-1 : 1 : 1), (0 : -1 : 1), (0 : 0 : 1), (0 : 1 : 1), (1 : -1 : 1), (1 : 0 : 1), (1 : 1 : 1)] :: sage: P.<x,y,z> = ProjectiveSpace(CC, 2) sage: E = P.subscheme([y^3 - x^3 - x*z^2, x*y*z]) sage: L=E(P.base_ring()).points(); sorted(L, key=str) verbose 0 (71: projective_homset.py, points) Warning: computations in the numerical fields are inexact;points may be computed partially or incorrectly. [(-0.500000000000000 + 0.866025403784439*I : 1.00000000000000 : 0.000000000000000), (-0.500000000000000 - 0.866025403784439*I : 1.00000000000000 : 0.000000000000000), (-1.00000000000000*I : 0.000000000000000 : 1.00000000000000), (0.000000000000000 : 0.000000000000000 : 1.00000000000000), (1.00000000000000 : 1.00000000000000 : 0.000000000000000), (1.00000000000000*I : 0.000000000000000 : 1.00000000000000)] sage: L[0].codomain() Projective Space of dimension 2 over Complex Field with 53 bits of precision :: sage: P.<x,y,z> = ProjectiveSpace(CDF, 2) sage: E = P.subscheme([y^2 + x^2 + z^2, x*y*z]) sage: len(E(P.base_ring()).points()) verbose 0 (71: projective_homset.py, points) Warning: computations in the numerical fields are inexact;points may be computed partially or incorrectly. 6 """ from sage.schemes.projective.projective_space import is_ProjectiveSpace X = self.codomain() if not is_ProjectiveSpace(X) and X.base_ring() in Fields(): if hasattr(X.base_ring(), 'precision'): numerical = True verbose( "Warning: computations in the numerical fields are inexact;points may be computed partially or incorrectly.", level=0) pt_tol = RR(kwds.pop('point_tolerance', 10**(-10))) zero_tol = RR(kwds.pop('zero_tolerance', 10**(-10))) if pt_tol <= 0 or zero_tol <= 0: raise ValueError("tolerance must be positive") else: numerical = False #Then it must be a subscheme dim_ideal = X.defining_ideal().dimension() if dim_ideal < 1: # no points return [] if dim_ideal == 1: # if X zero-dimensional rat_points = set() PS = X.ambient_space() N = PS.dimension_relative() BR = X.base_ring() #need a lexicographic ordering for elimination R = PolynomialRing(BR, N + 1, PS.variable_names(), order='lex') I = R.ideal(X.defining_polynomials()) I0 = R.ideal(0) #Determine the points through elimination #This is much faster than using the I.variety() function on each affine chart. for k in range(N + 1): #create the elimination ideal for the kth affine patch G = I.substitute({R.gen(k): 1}).groebner_basis() if G != [1]: P = {} #keep track that we know the kth coordinate is 1 P.update({R.gen(k): 1}) points = [P] #work backwards from solving each equation for the possible #values of the next coordinate for i in range(len(G) - 1, -1, -1): new_points = [] good = 0 for P in points: #substitute in our dictionary entry that has the values #of coordinates known so far. This results in a single #variable polynomial (by elimination) L = G[i].substitute(P) if R(L).degree() > 0: if numerical: for pol in L.univariate_polynomial( ).roots(multiplicities=False): good = 1 r = L.variables()[0] varindex = R.gens().index(r) P.update({R.gen(varindex): pol}) new_points.append(copy(P)) else: L = L.factor() #the linear factors give the possible rational values of #this coordinate for pol, pow in L: if pol.degree() == 1 and len( pol.variables()) == 1: good = 1 r = pol.variables()[0] varindex = R.gens().index(r) #add this coordinates information to #each dictionary entry P.update({ R.gen(varindex): -pol.constant_coefficient( ) / pol.monomial_coefficient(r) }) new_points.append(copy(P)) else: new_points.append(P) good = 1 if good: points = new_points #the dictionary entries now have values for all coordinates #they are the rational solutions to the equations #make them into projective points for i in range(len(points)): if numerical: if len(points[i]) == N + 1: S = PS([ points[i][R.gen(j)] for j in range(N + 1) ]) S.normalize_coordinates() if all( g(list(S)) < zero_tol for g in X.defining_polynomials()): rat_points.add(S) else: if len(points[i]) == N + 1 and I.subs( points[i]) == I0: S = X([ points[i][R.gen(j)] for j in range(N + 1) ]) S.normalize_coordinates() rat_points.add(S) # remove duplicate element using tolerance if numerical: dupl_points = list(rat_points) for i in range(len(dupl_points)): u = dupl_points[i] for j in range(i + 1, len(dupl_points)): v = dupl_points[j] if all((u[k] - v[k]).abs() < pt_tol for k in range(len(u))): rat_points.remove(u) break rat_points = sorted(rat_points) return rat_points R = self.value_ring() B = kwds.pop('bound', 0) tol = kwds.pop('tolerance', 1e-2) prec = kwds.pop('precision', 53) if is_RationalField(R): if not B > 0: raise TypeError("a positive bound B (= %s) must be specified" % B) if isinstance(X, AlgebraicScheme_subscheme ): # sieve should only be called for subschemes from sage.schemes.projective.projective_rational_point import sieve return sieve(X, B) else: from sage.schemes.projective.projective_rational_point import enum_projective_rational_field return enum_projective_rational_field(self, B) elif R in NumberFields(): if not B > 0: raise TypeError("a positive bound B (= %s) must be specified" % B) from sage.schemes.projective.projective_rational_point import enum_projective_number_field return enum_projective_number_field(self, bound=B, tolerance=tol, precision=prec) elif is_FiniteField(R): from sage.schemes.projective.projective_rational_point import enum_projective_finite_field return enum_projective_finite_field(self.extended_codomain()) else: raise TypeError("unable to enumerate points over %s" % R)
def L_ratio(self): r""" Return the ratio `L(E,1) / \Omega` as an exact rational number. The result is *provably* correct if the Manin constant of the associated optimal quotient is `\leq 2`. This hypothesis on the Manin constant is true for all semistable curves (i.e., squarefree conductor), by a theorem of Mazur from his *Rational Isogenies of Prime Degree* paper. EXAMPLES:: sage: E = EllipticCurve([0, -1, 1, -10, -20]) # 11A = X_0(11) sage: E.lseries().L_ratio() 1/5 sage: E = EllipticCurve([0, -1, 1, 0, 0]) # X_1(11) sage: E.lseries().L_ratio() 1/25 sage: E = EllipticCurve([0, 0, 1, -1, 0]) # 37A (rank 1) sage: E.lseries().L_ratio() 0 sage: E = EllipticCurve([0, 1, 1, -2, 0]) # 389A (rank 2) sage: E.lseries().L_ratio() 0 sage: E = EllipticCurve([0, 0, 1, -38, 90]) # 361A (CM curve)) sage: E.lseries().L_ratio() 0 sage: E = EllipticCurve([0,-1,1,-2,-1]) # 141C (13-isogeny) sage: E.lseries().L_ratio() 1 sage: E = EllipticCurve(RationalField(), [1, 0, 0, 1/24624, 1/886464]) sage: E.lseries().L_ratio() 2 See :trac:`3651` and :trac:`15299`:: sage: EllipticCurve([0,0,0,-193^2,0]).sha().an() 4 sage: EllipticCurve([1, 0, 1, -131, 558]).sha().an() # long time 1.00000000000000 ALGORITHM: Compute the root number. If it is -1 then `L(E,s)` vanishes to odd order at 1, hence vanishes. If it is +1, use a result about modular symbols and Mazur's *Rational Isogenies* paper to determine a provably correct bound (assuming Manin constant is <= 2) so that we can determine whether `L(E,1) = 0`. AUTHOR: William Stein, 2005-04-20. """ if not self.__E.is_minimal(): return self.__E.minimal_model().lseries().L_ratio() QQ = RationalField() if self.__E.root_number() == -1: return QQ.zero() # Even root number. Decide if L(E,1) = 0. If E is a modular # *OPTIMAL* quotient of J_0(N) elliptic curve, we know that T * # L(E,1)/omega is an integer n, where T is the order of the # image of the rational torsion point (0)-(oo) in E(Q), and # omega is the least real Neron period. (This is proved in my # Ph.D. thesis, but is probably well known.) We can easily # compute omega to very high precision using AGM. So to prove # that L(E,1) = 0 we compute T/omega * L(E,1) to sufficient # precision to determine it as an integer. If eps is the # error in computation of L(E,1), then the error in computing # the product is (2T/Omega_E) * eps, and we need this to be # less than 0.5, i.e., # (2T/Omega_E) * eps < 0.5, # so # eps < 0.5 * Omega_E / (2T) = Omega_E / (4*T). # # Since in general E need not be optimal, we have to choose # eps = Omega_E/(8*t*B), where t is the exponent of E(Q)_tor, # and is a multiple of the degree of an isogeny between E # and the optimal curve. # # NOTE: We *do* have to worry about the Manin constant, since # we are using the Neron model to compute omega, not the # newform. My theorem replaces the omega above by omega/c, # where c is the Manin constant, and the bound must be # correspondingly smaller. If the level is square free, then # the Manin constant is 1 or 2, so there's no problem (since # we took 8 instead of 4 in the denominator). If the level # is divisible by a square, then the Manin constant could # be a divisible by an arbitrary power of that prime, except # that Edixhoven claims the primes that appear are <= 7. t = self.__E.torsion_subgroup().order() omega = self.__E.period_lattice().basis()[0] d = self.__E._multiple_of_degree_of_isogeny_to_optimal_curve() C = 8 * d * t eps = omega / C sqrtN = 2 * self.__E.conductor().isqrt() k = sqrtN + 10 while True: L1, error_bound = self.at1(k) if error_bound < eps: n = (L1 * C / omega).round() quo = QQ((n, C)) return quo / self.__E.real_components() k += sqrtN verbose("Increasing precision to %s terms." % k)
def splitting_field(poly, name, map=False, degree_multiple=None, abort_degree=None, simplify=True, simplify_all=False): r""" Compute the splitting field of a given polynomial, defined over a number field. INPUT: - ``poly`` -- a monic polynomial over a number field - ``name`` -- a variable name for the number field - ``map`` -- (default: ``False``) also return an embedding of ``poly`` into the resulting field. Note that computing this embedding might be expensive. - ``degree_multiple`` -- a multiple of the absolute degree of the splitting field. If ``degree_multiple`` equals the actual degree, this can enormously speed up the computation. - ``abort_degree`` -- abort by raising a :class:`SplittingFieldAbort` if it can be determined that the absolute degree of the splitting field is strictly larger than ``abort_degree``. - ``simplify`` -- (default: ``True``) during the algorithm, try to find a simpler defining polynomial for the intermediate number fields using PARI's ``polred()``. This usually speeds up the computation but can also considerably slow it down. Try and see what works best in the given situation. - ``simplify_all`` -- (default: ``False``) If ``True``, simplify intermediate fields and also the resulting number field. OUTPUT: If ``map`` is ``False``, the splitting field as an absolute number field. If ``map`` is ``True``, a tuple ``(K, phi)`` where ``phi`` is an embedding of the base field in ``K``. EXAMPLES:: sage: R.<x> = PolynomialRing(QQ) sage: K.<a> = (x^3 + 2).splitting_field(); K Number Field in a with defining polynomial x^6 + 3*x^5 + 6*x^4 + 11*x^3 + 12*x^2 - 3*x + 1 sage: K.<a> = (x^3 - 3*x + 1).splitting_field(); K Number Field in a with defining polynomial x^3 - 3*x + 1 The ``simplify`` and ``simplify_all`` flags usually yield fields defined by polynomials with smaller coefficients. By default, ``simplify`` is True and ``simplify_all`` is False. :: sage: (x^4 - x + 1).splitting_field('a', simplify=False) Number Field in a with defining polynomial x^24 - 2780*x^22 + 2*x^21 + 3527512*x^20 - 2876*x^19 - 2701391985*x^18 + 945948*x^17 + 1390511639677*x^16 + 736757420*x^15 - 506816498313560*x^14 - 822702898220*x^13 + 134120588299548463*x^12 + 362240696528256*x^11 - 25964582366880639486*x^10 - 91743672243419990*x^9 + 3649429473447308439427*x^8 + 14310332927134072336*x^7 - 363192569823568746892571*x^6 - 1353403793640477725898*x^5 + 24293393281774560140427565*x^4 + 70673814899934142357628*x^3 - 980621447508959243128437933*x^2 - 1539841440617805445432660*x + 18065914012013502602456565991 sage: (x^4 - x + 1).splitting_field('a', simplify=True) Number Field in a with defining polynomial x^24 + 8*x^23 - 32*x^22 - 310*x^21 + 540*x^20 + 4688*x^19 - 6813*x^18 - 32380*x^17 + 49525*x^16 + 102460*x^15 - 129944*x^14 - 287884*x^13 + 372727*x^12 + 150624*x^11 - 110530*x^10 - 566926*x^9 + 1062759*x^8 - 779940*x^7 + 863493*x^6 - 1623578*x^5 + 1759513*x^4 - 955624*x^3 + 459975*x^2 - 141948*x + 53919 sage: (x^4 - x + 1).splitting_field('a', simplify_all=True) Number Field in a with defining polynomial x^24 - 3*x^23 + 2*x^22 - x^20 + 4*x^19 + 32*x^18 - 35*x^17 - 92*x^16 + 49*x^15 + 163*x^14 - 15*x^13 - 194*x^12 - 15*x^11 + 163*x^10 + 49*x^9 - 92*x^8 - 35*x^7 + 32*x^6 + 4*x^5 - x^4 + 2*x^2 - 3*x + 1 Reducible polynomials also work:: sage: pol = (x^4 - 1)*(x^2 + 1/2)*(x^2 + 1/3) sage: pol.splitting_field('a', simplify_all=True) Number Field in a with defining polynomial x^8 - x^4 + 1 Relative situation:: sage: R.<x> = PolynomialRing(QQ) sage: K.<a> = NumberField(x^3 + 2) sage: S.<t> = PolynomialRing(K) sage: L.<b> = (t^2 - a).splitting_field() sage: L Number Field in b with defining polynomial t^6 + 2 With ``map=True``, we also get the embedding of the base field into the splitting field:: sage: L.<b>, phi = (t^2 - a).splitting_field(map=True) sage: phi Ring morphism: From: Number Field in a with defining polynomial x^3 + 2 To: Number Field in b with defining polynomial t^6 + 2 Defn: a |--> b^2 sage: (x^4 - x + 1).splitting_field('a', simplify_all=True, map=True)[1] Ring morphism: From: Rational Field To: Number Field in a with defining polynomial x^24 - 3*x^23 + 2*x^22 - x^20 + 4*x^19 + 32*x^18 - 35*x^17 - 92*x^16 + 49*x^15 + 163*x^14 - 15*x^13 - 194*x^12 - 15*x^11 + 163*x^10 + 49*x^9 - 92*x^8 - 35*x^7 + 32*x^6 + 4*x^5 - x^4 + 2*x^2 - 3*x + 1 Defn: 1 |--> 1 We can enable verbose messages:: sage: from sage.misc.verbose import set_verbose sage: set_verbose(2) sage: K.<a> = (x^3 - x + 1).splitting_field() verbose 1 (...: splitting_field.py, splitting_field) Starting field: y verbose 1 (...: splitting_field.py, splitting_field) SplittingData to factor: [(3, 0)] verbose 2 (...: splitting_field.py, splitting_field) Done factoring (time = ...) verbose 1 (...: splitting_field.py, splitting_field) SplittingData to handle: [(2, 2), (3, 3)] verbose 1 (...: splitting_field.py, splitting_field) Bounds for absolute degree: [6, 6] verbose 2 (...: splitting_field.py, splitting_field) Handling polynomial x^2 + 23 verbose 1 (...: splitting_field.py, splitting_field) New field before simplifying: x^2 + 23 (time = ...) verbose 1 (...: splitting_field.py, splitting_field) New field: y^2 - y + 6 (time = ...) verbose 2 (...: splitting_field.py, splitting_field) Converted polynomials to new field (time = ...) verbose 1 (...: splitting_field.py, splitting_field) SplittingData to factor: [] verbose 2 (...: splitting_field.py, splitting_field) Done factoring (time = ...) verbose 1 (...: splitting_field.py, splitting_field) SplittingData to handle: [(3, 3)] verbose 1 (...: splitting_field.py, splitting_field) Bounds for absolute degree: [6, 6] verbose 2 (...: splitting_field.py, splitting_field) Handling polynomial x^3 - x + 1 verbose 1 (...: splitting_field.py, splitting_field) New field: y^6 + 3*y^5 + 19*y^4 + 35*y^3 + 127*y^2 + 73*y + 271 (time = ...) sage: set_verbose(0) Try all Galois groups in degree 4. We use a quadratic base field such that ``polgalois()`` cannot be used:: sage: R.<x> = PolynomialRing(QuadraticField(-11)) sage: C2C2pol = x^4 - 10*x^2 + 1 sage: C2C2pol.splitting_field('x') Number Field in x with defining polynomial x^8 + 24*x^6 + 608*x^4 + 9792*x^2 + 53824 sage: C4pol = x^4 + x^3 + x^2 + x + 1 sage: C4pol.splitting_field('x') Number Field in x with defining polynomial x^8 - x^7 - 2*x^6 + 5*x^5 + x^4 + 15*x^3 - 18*x^2 - 27*x + 81 sage: D8pol = x^4 - 2 sage: D8pol.splitting_field('x') Number Field in x with defining polynomial x^16 + 8*x^15 + 68*x^14 + 336*x^13 + 1514*x^12 + 5080*x^11 + 14912*x^10 + 35048*x^9 + 64959*x^8 + 93416*x^7 + 88216*x^6 + 41608*x^5 - 25586*x^4 - 60048*x^3 - 16628*x^2 + 12008*x + 34961 sage: A4pol = x^4 - 4*x^3 + 14*x^2 - 28*x + 21 sage: A4pol.splitting_field('x') Number Field in x with defining polynomial x^24 - 20*x^23 + 290*x^22 - 3048*x^21 + 26147*x^20 - 186132*x^19 + 1130626*x^18 - 5913784*x^17 + 26899345*x^16 - 106792132*x^15 + 371066538*x^14 - 1127792656*x^13 + 2991524876*x^12 - 6888328132*x^11 + 13655960064*x^10 - 23000783036*x^9 + 32244796382*x^8 - 36347834476*x^7 + 30850889884*x^6 - 16707053128*x^5 + 1896946429*x^4 + 4832907884*x^3 - 3038258802*x^2 - 200383596*x + 593179173 sage: S4pol = x^4 + x + 1 sage: S4pol.splitting_field('x') Number Field in x with defining polynomial x^48 ... Some bigger examples:: sage: R.<x> = PolynomialRing(QQ) sage: pol15 = chebyshev_T(31, x) - 1 # 2^30*(x-1)*minpoly(cos(2*pi/31))^2 sage: pol15.splitting_field('a') Number Field in a with defining polynomial x^15 - x^14 - 14*x^13 + 13*x^12 + 78*x^11 - 66*x^10 - 220*x^9 + 165*x^8 + 330*x^7 - 210*x^6 - 252*x^5 + 126*x^4 + 84*x^3 - 28*x^2 - 8*x + 1 sage: pol48 = x^6 - 4*x^4 + 12*x^2 - 12 sage: pol48.splitting_field('a') Number Field in a with defining polynomial x^48 ... If you somehow know the degree of the field in advance, you should add a ``degree_multiple`` argument. This can speed up the computation, in particular for polynomials of degree >= 12 or for relative extensions:: sage: pol15.splitting_field('a', degree_multiple=15) Number Field in a with defining polynomial x^15 + x^14 - 14*x^13 - 13*x^12 + 78*x^11 + 66*x^10 - 220*x^9 - 165*x^8 + 330*x^7 + 210*x^6 - 252*x^5 - 126*x^4 + 84*x^3 + 28*x^2 - 8*x - 1 A value for ``degree_multiple`` which isn't actually a multiple of the absolute degree of the splitting field can either result in a wrong answer or the following exception:: sage: pol48.splitting_field('a', degree_multiple=20) Traceback (most recent call last): ... ValueError: inconsistent degree_multiple in splitting_field() Compute the Galois closure as the splitting field of the defining polynomial:: sage: R.<x> = PolynomialRing(QQ) sage: pol48 = x^6 - 4*x^4 + 12*x^2 - 12 sage: K.<a> = NumberField(pol48) sage: L.<b> = pol48.change_ring(K).splitting_field() sage: L Number Field in b with defining polynomial x^48 ... Try all Galois groups over `\QQ` in degree 5 except for `S_5` (the latter is infeasible with the current implementation):: sage: C5pol = x^5 + x^4 - 4*x^3 - 3*x^2 + 3*x + 1 sage: C5pol.splitting_field('x') Number Field in x with defining polynomial x^5 + x^4 - 4*x^3 - 3*x^2 + 3*x + 1 sage: D10pol = x^5 - x^4 - 5*x^3 + 4*x^2 + 3*x - 1 sage: D10pol.splitting_field('x') Number Field in x with defining polynomial x^10 - 28*x^8 + 216*x^6 - 681*x^4 + 902*x^2 - 401 sage: AGL_1_5pol = x^5 - 2 sage: AGL_1_5pol.splitting_field('x') Number Field in x with defining polynomial x^20 + 10*x^19 + 55*x^18 + 210*x^17 + 595*x^16 + 1300*x^15 + 2250*x^14 + 3130*x^13 + 3585*x^12 + 3500*x^11 + 2965*x^10 + 2250*x^9 + 1625*x^8 + 1150*x^7 + 750*x^6 + 400*x^5 + 275*x^4 + 100*x^3 + 75*x^2 + 25 sage: A5pol = x^5 - x^4 + 2*x^2 - 2*x + 2 sage: A5pol.splitting_field('x') Number Field in x with defining polynomial x^60 ... We can use the ``abort_degree`` option if we don't want to compute fields of too large degree (this can be used to check whether the splitting field has small degree):: sage: (x^5+x+3).splitting_field('b', abort_degree=119) Traceback (most recent call last): ... SplittingFieldAbort: degree of splitting field equals 120 sage: (x^10+x+3).splitting_field('b', abort_degree=60) # long time (10s on sage.math, 2014) Traceback (most recent call last): ... SplittingFieldAbort: degree of splitting field is a multiple of 180 Use the ``degree_divisor`` attribute to recover the divisor of the degree of the splitting field or ``degree_multiple`` to recover a multiple:: sage: from sage.rings.number_field.splitting_field import SplittingFieldAbort sage: try: # long time (4s on sage.math, 2014) ....: (x^8+x+1).splitting_field('b', abort_degree=60, simplify=False) ....: except SplittingFieldAbort as e: ....: print(e.degree_divisor) ....: print(e.degree_multiple) 120 1440 TESTS:: sage: from sage.rings.number_field.splitting_field import splitting_field sage: splitting_field(polygen(QQ), name='x', map=True, simplify_all=True) (Number Field in x with defining polynomial x, Ring morphism: From: Rational Field To: Number Field in x with defining polynomial x Defn: 1 |--> 1) """ from sage.misc.all import cputime from sage.misc.verbose import verbose degree_multiple = Integer(degree_multiple or 0) abort_degree = Integer(abort_degree or 0) # Kpol = PARI polynomial in y defining the extension found so far F = poly.base_ring() if is_RationalField(F): Kpol = pari("'y") else: Kpol = F.pari_polynomial("y") # Fgen = the generator of F as element of Q[y]/Kpol # (only needed if map=True) if map: Fgen = F.gen().__pari__() verbose("Starting field: %s" % Kpol) # L and Lred are lists of SplittingData. # L contains polynomials which are irreducible over K, # Lred contains polynomials which need to be factored. L = [] Lred = [SplittingData(poly._pari_with_name(), degree_multiple)] # Main loop, handle polynomials one by one while True: # Absolute degree of current field K absolute_degree = Integer(Kpol.poldegree()) # Compute minimum relative degree of splitting field rel_degree_divisor = Integer(1) for splitting in L: rel_degree_divisor = rel_degree_divisor.lcm(splitting.poldegree()) # Check for early aborts abort_rel_degree = abort_degree // absolute_degree if abort_rel_degree and rel_degree_divisor > abort_rel_degree: raise SplittingFieldAbort(absolute_degree * rel_degree_divisor, degree_multiple) # First, factor polynomials in Lred and store the result in L verbose("SplittingData to factor: %s" % [s._repr_tuple() for s in Lred]) t = cputime() for splitting in Lred: m = splitting.dm.gcd(degree_multiple).gcd( factorial(splitting.poldegree())) if m == 1: continue factors = Kpol.nffactor(splitting.pol)[0] for q in factors: d = q.poldegree() fac = factorial(d) # Multiple of the degree of the splitting field of q, # note that the degree equals fac iff the Galois group is S_n. mq = m.gcd(fac) if mq == 1: continue # Multiple of the degree of the splitting field of q # over the field defined by adding square root of the # discriminant. # If the Galois group is contained in A_n, then mq_alt is # also the degree multiple over the current field K. # Here, we have equality if the Galois group is A_n. mq_alt = mq.gcd(fac // 2) # If we are over Q, then use PARI's polgalois() to compute # these degrees exactly. if absolute_degree == 1: try: G = q.polgalois() except PariError: pass else: mq = Integer(G[0]) mq_alt = mq // 2 if (G[1] == -1) else mq # In degree 4, use the cubic resolvent to refine the # degree bounds. if d == 4 and mq >= 12: # mq equals 12 or 24 # Compute cubic resolvent a0, a1, a2, a3, a4 = (q / q.pollead()).Vecrev() assert a4 == 1 cubicpol = pari([ 4 * a0 * a2 - a1 * a1 - a0 * a3 * a3, a1 * a3 - 4 * a0, -a2, 1 ]).Polrev() cubicfactors = Kpol.nffactor(cubicpol)[0] if len(cubicfactors) == 1: # A4 or S4 # After adding a root of the cubic resolvent, # the degree of the extension defined by q # is a factor 3 smaller. L.append(SplittingData(cubicpol, 3)) rel_degree_divisor = rel_degree_divisor.lcm(3) mq = mq // 3 # 4 or 8 mq_alt = 4 elif len(cubicfactors) == 2: # C4 or D8 # The irreducible degree 2 factor is # equivalent to x^2 - q.poldisc(). discpol = cubicfactors[1] L.append(SplittingData(discpol, 2)) mq = mq_alt = 4 else: # C2 x C2 mq = mq_alt = 4 if mq > mq_alt >= 3: # Add quadratic resolvent x^2 - D to decrease # the degree multiple by a factor 2. discpol = pari([-q.poldisc(), 0, 1]).Polrev() discfactors = Kpol.nffactor(discpol)[0] if len(discfactors) == 1: # Discriminant is not a square L.append(SplittingData(discpol, 2)) rel_degree_divisor = rel_degree_divisor.lcm(2) mq = mq_alt L.append(SplittingData(q, mq)) rel_degree_divisor = rel_degree_divisor.lcm(q.poldegree()) if abort_rel_degree and rel_degree_divisor > abort_rel_degree: raise SplittingFieldAbort( absolute_degree * rel_degree_divisor, degree_multiple) verbose("Done factoring", t, level=2) if len(L) == 0: # Nothing left to do break # Recompute absolute degree multiple new_degree_multiple = absolute_degree for splitting in L: new_degree_multiple *= splitting.dm degree_multiple = new_degree_multiple.gcd(degree_multiple) # Absolute degree divisor degree_divisor = rel_degree_divisor * absolute_degree # Sort according to degree to handle low degrees first L.sort(key=lambda x: x.key()) verbose("SplittingData to handle: %s" % [s._repr_tuple() for s in L]) verbose("Bounds for absolute degree: [%s, %s]" % (degree_divisor, degree_multiple)) # Check consistency if degree_multiple % degree_divisor != 0: raise ValueError( "inconsistent degree_multiple in splitting_field()") for splitting in L: # The degree of the splitting field must be a multiple of # the degree of the polynomial. Only do this check for # SplittingData with minimal dm, because the higher dm are # defined as relative degree over the splitting field of # the polynomials with lesser dm. if splitting.dm > L[0].dm: break if splitting.dm % splitting.poldegree() != 0: raise ValueError( "inconsistent degree_multiple in splitting_field()") # Add a root of f = L[0] to construct the field N = K[x]/f(x) splitting = L[0] f = splitting.pol verbose("Handling polynomial %s" % (f.lift()), level=2) t = cputime() Npol, KtoN, k = Kpol.rnfequation(f, flag=1) # Make Npol monic integral primitive, store in Mpol # (after this, we don't need Npol anymore, only Mpol) Mdiv = pari(1) Mpol = Npol while True: denom = Integer(Mpol.pollead()) if denom == 1: break denom = pari(denom.factor().radical_value()) Mpol = (Mpol * (denom**Mpol.poldegree())).subst( "x", pari([0, 1 / denom]).Polrev("x")) Mpol /= Mpol.content() Mdiv *= denom # We are finished for sure if we hit the degree bound finished = (Mpol.poldegree() >= degree_multiple) if simplify_all or (simplify and not finished): # Find a simpler defining polynomial Lpol for Mpol verbose("New field before simplifying: %s" % Mpol, t) t = cputime() M = Mpol.polred(flag=3) n = len(M[0]) - 1 Lpol = M[1][n].change_variable_name("y") LtoM = M[0][n].change_variable_name("y").Mod( Mpol.change_variable_name("y")) MtoL = LtoM.modreverse() else: # Lpol = Mpol Lpol = Mpol.change_variable_name("y") MtoL = pari("'y") NtoL = MtoL / Mdiv KtoL = KtoN.lift().subst("x", NtoL).Mod(Lpol) Kpol = Lpol # New Kpol (for next iteration) verbose("New field: %s" % Kpol, t) if map: t = cputime() Fgen = Fgen.lift().subst("y", KtoL) verbose("Computed generator of F in K", t, level=2) if finished: break t = cputime() # Convert f and elements of L from K to L and store in L # (if the polynomial is certain to remain irreducible) or Lred. Lold = L[1:] L = [] Lred = [] # First add f divided by the linear factor we obtained, # mg is the new degree multiple. mg = splitting.dm // f.poldegree() if mg > 1: g = [c.subst("y", KtoL).Mod(Lpol) for c in f.Vecrev().lift()] g = pari(g).Polrev() g /= pari([k * KtoL - NtoL, 1]).Polrev() # divide linear factor Lred.append(SplittingData(g, mg)) for splitting in Lold: g = [c.subst("y", KtoL) for c in splitting.pol.Vecrev().lift()] g = pari(g).Polrev() mg = splitting.dm if Integer(g.poldegree()).gcd( f.poldegree()) == 1: # linearly disjoint fields L.append(SplittingData(g, mg)) else: Lred.append(SplittingData(g, mg)) verbose("Converted polynomials to new field", t, level=2) # Convert Kpol to Sage and construct the absolute number field Kpol = PolynomialRing(RationalField(), name=poly.variable_name())(Kpol / Kpol.pollead()) K = NumberField(Kpol, name) if map: return K, F.hom(Fgen, K) else: return K
def cuspidal_ideal_generators(self, maxweight=8, prec=None): r""" Calculate generators for the ideal of cuspidal forms in this ring, as a module over the whole ring. EXAMPLES:: sage: ModularFormsRing(Gamma0(3)).cuspidal_ideal_generators(maxweight=12) [(6, q - 6*q^2 + 9*q^3 + 4*q^4 + O(q^5), q - 6*q^2 + 9*q^3 + 4*q^4 + 6*q^5 + O(q^6))] sage: [k for k,f,F in ModularFormsRing(13, base_ring=ZZ).cuspidal_ideal_generators(maxweight=14)] [4, 4, 4, 6, 6, 12] """ working_prec = self.modular_forms_of_weight(maxweight).sturm_bound() if self.__cached_cusp_maxweight > -1: k = self.__cached_cusp_maxweight + 1 verbose( "Already calculated cusp gens up to weight %s -- using those" % (k - 1)) # we may need to increase the precision of the cached cusp # generators G = [] for j, f, F in self.__cached_cusp_gens: if f.prec() >= working_prec: f = F.qexp(working_prec).change_ring(self.base_ring()) G.append((j, f, F)) else: k = 2 G = [] while k <= maxweight: t = verbose("Looking for cusp generators in weight %s" % k) kprec = self.modular_forms_of_weight(k).sturm_bound() flist = [] for (j, f, F) in G: for g in self.q_expansion_basis(k - j, prec=kprec): flist.append(g * f) A = self.base_ring()**kprec W = A.span([A(f.padded_list(kprec)) for f in flist]) S = self.modular_forms_of_weight(k).cuspidal_submodule() if (W.rank() == S.dimension() and (self.base_ring().is_field() or W.index_in_saturation() == 1)): verbose("Nothing new in weight %s" % k, t) k += 1 continue t = verbose( "Known cusp generators span a submodule of dimension %s of space of dimension %s" % (W.rank(), S.dimension()), t) B = S.q_integral_basis(prec=working_prec) V = A.span([ A(f.change_ring(self.base_ring()).padded_list(kprec)) for f in B ]) Q = V / W for q in Q.gens(): try: qc = V.coordinates(Q.lift(q)) except AttributeError: # work around a silly free module bug qc = V.coordinates(q.lift()) qcZZ = [ZZ(_) for _ in qc] # lift to ZZ so we can define F f = sum([B[i] * qcZZ[i] for i in range(len(B))]) F = S(f) G.append((k, f.change_ring(self.base_ring()), F)) verbose('added %s new generators' % Q.ngens(), t) k += 1 self.__cached_cusp_maxweight = maxweight self.__cached_cusp_gens = G if prec is None: return G elif prec <= working_prec: return [(k, f.truncate_powerseries(prec), F) for k, f, F in G] else: # user wants increased precision, so we may as well cache that Gnew = [(k, F.qexp(prec).change_ring(self.base_ring()), F) for k, f, F in G] self.__cached_cusp_gens = Gnew return Gnew
def dual_free_module(self, bound=None, anemic=True, use_star=True): r""" Compute embedded dual free module if possible. In general this won't be possible, e.g., if this space is not Hecke equivariant, possibly if it is not cuspidal, or if the characteristic is not 0. In all these cases we raise a RuntimeError exception. If use_star is True (which is the default), we also use the +/- eigenspaces for the star operator to find the dual free module of self. If self does not have a star involution, use_star will automatically be set to False. EXAMPLES:: sage: M = ModularSymbols(11, 2) sage: M.dual_free_module() Vector space of dimension 3 over Rational Field sage: Mpc = M.plus_submodule().cuspidal_submodule() sage: Mcp = M.cuspidal_submodule().plus_submodule() sage: Mcp.dual_free_module() == Mpc.dual_free_module() True sage: Mpc.dual_free_module() Vector space of degree 3 and dimension 1 over Rational Field Basis matrix: [ 1 5/2 5] sage: M = ModularSymbols(35,2).cuspidal_submodule() sage: M.dual_free_module(use_star=False) Vector space of degree 9 and dimension 6 over Rational Field Basis matrix: [ 1 0 0 0 -1 0 0 4 -2] [ 0 1 0 0 0 0 0 -1/2 1/2] [ 0 0 1 0 0 0 0 -1/2 1/2] [ 0 0 0 1 -1 0 0 1 0] [ 0 0 0 0 0 1 0 -2 1] [ 0 0 0 0 0 0 1 -2 1] sage: M = ModularSymbols(40,2) sage: Mmc = M.minus_submodule().cuspidal_submodule() sage: Mcm = M.cuspidal_submodule().minus_submodule() sage: Mcm.dual_free_module() == Mmc.dual_free_module() True sage: Mcm.dual_free_module() Vector space of degree 13 and dimension 3 over Rational Field Basis matrix: [ 0 1 0 0 0 0 1 0 -1 -1 1 -1 0] [ 0 0 1 0 -1 0 -1 0 1 0 0 0 0] [ 0 0 0 0 0 1 1 0 -1 0 0 0 0] sage: M = ModularSymbols(43).cuspidal_submodule() sage: S = M[0].plus_submodule() + M[1].minus_submodule() sage: S.dual_free_module(use_star=False) Traceback (most recent call last): ... RuntimeError: Computation of complementary space failed (cut down to rank 7, but should have cut down to rank 4). sage: S.dual_free_module().dimension() == S.dimension() True We test that :trac:`5080` is fixed:: sage: EllipticCurve('128a').congruence_number() 32 """ # if we know the complement we can read off the dual module if self.complement.is_in_cache(): verbose( 'This module knows its complement already -- cheating in dual_free_module' ) C = self.complement() V = C.basis_matrix().right_kernel() return V verbose("computing dual") A = self.ambient_hecke_module() if self.dimension() == 0: return A.zero_submodule() if A.dimension() == self.dimension(): return A.free_module() # ALGORITHM: Compute the char poly of each Hecke operator on # the submodule, then use it to cut out a submodule of the # dual. If the dimension cuts down to the dimension of self # terminate with success. If it stays larger beyond the Sturm # bound, raise a RuntimeError exception. # In the case that the sign of self is not 1, we need to use # the star involution as well as the Hecke operators in order # to find the dual of self. # # Note that one needs to comment out the line caching the # result of this computation below in order to get meaningful # timings. # If the star involution doesn't make sense for self, then we # can't use it. if not hasattr(self, 'star_eigenvalues'): use_star = False if use_star: # If the star involution has both + and - eigenspaces on self, # then we compute the dual on each eigenspace, then put them # together. if len(self.star_eigenvalues()) == 2: V = self.plus_submodule(compute_dual = False).dual_free_module() + \ self.minus_submodule(compute_dual = False).dual_free_module() return V # At this point, we know that self is an eigenspace for star. V = A.sign_submodule(self.sign()).dual_free_module() else: V = A.free_module() N = self.level() p = 2 if bound is None: bound = A.hecke_bound() while True: if anemic: while N % p == 0: p = arith.next_prime(p) verbose("using T_%s" % p) f = self.hecke_polynomial(p) T = A.dual_hecke_matrix(p) V = T.kernel_on(V, poly=f, check=False) if V.dimension() <= self.dimension(): break p = arith.next_prime(p) if p > bound: break if V.rank() == self.rank(): return V else: # Failed to reduce V to the appropriate dimension W = self.complement() V2 = W.basis_matrix().right_kernel() if V2.rank() == self.rank(): return V2 else: raise RuntimeError("Computation of embedded dual vector space failed " + \ "(cut down to rank %s, but should have cut down to rank %s)."%(V.rank(), self.rank()))
def _eigenvectors(self): r""" Find numerical approximations to simultaneous eigenvectors in self.modular_symbols() for all T_p in self._tp. EXAMPLES:: sage: n = numerical_eigenforms(61) sage: n._eigenvectors() # random order [ 1.0 0.289473640239 0.176788851952 0.336707726757 2.4182243084e-16] [ 0 -0.0702748344418 0.491416161212 0.155925712173 0.707106781187] [ 0 0.413171180356 0.141163094698 0.0923242547901 0.707106781187] [ 0 0.826342360711 0.282326189397 0.18464850958 6.79812569682e-16] [ 0 0.2402380858 0.792225196393 0.905370774276 4.70805946682e-16] TESTS: This tests if this routine selects only eigenvectors with multiplicity one. Two of the eigenvalues are (roughly) -92.21 and -90.30 so if we set ``eps = 2.0`` then they should compare as equal, causing both eigenvectors to be absent from the matrix returned. The remaining eigenvalues (ostensibly unique) are visible in the test, which should be independent of which eigenvectors are returned, but it does presume an ordering of these eigenvectors for the test to succeed. This exercises a correction in :trac:`8018`. :: sage: n = numerical_eigenforms(61, eps=2.0) sage: evectors = n._eigenvectors() sage: evalues = diagonal_matrix(CDF, [-283.0, 142.0, 108.522012456]) sage: diff = n._hecke_matrix*evectors - evectors*evalues sage: sum([abs(diff[i,j]) for i in range(5) for j in range(3)]) < 1.0e-9 True """ verbose('Finding eigenvector basis') M = self.modular_symbols() tp = self._tp p = tp[0] t = M.T(p).matrix() for p in tp[1:]: t += randint(-50, 50) * M.T(p).matrix() self._hecke_matrix = t global scipy if scipy is None: import scipy import scipy.linalg evals, eig = scipy.linalg.eig(self._hecke_matrix.numpy(), right=True, left=False) B = matrix(eig) v = [CDF(evals[i]) for i in range(len(evals))] # Determine the eigenvectors with eigenvalues of multiplicity # one, with equality controlled by the value of eps # Keep just these eigenvectors eps = self._eps w = [] for i in range(len(v)): e = v[i] uniq = True for j in range(len(v)): if uniq and i != j and abs(e - v[j]) < eps: uniq = False if uniq: w.append(i) return B.matrix_from_columns(w)