def guess(self, sequence, algorithm='sage'): """ Return the minimal CFiniteSequence that generates the sequence. Assume the first value has index 0. INPUT: - ``sequence`` -- list of integers - ``algorithm`` -- string - 'sage' - the default is to use Sage's matrix kernel function - 'pari' - use Pari's implementation of LLL - 'bm' - use Sage's Berlekamp-Massey algorithm OUTPUT: - a CFiniteSequence, or 0 if none could be found With the default kernel method, trailing zeroes are chopped off before a guessing attempt. This may reduce the data below the accepted length of six values. EXAMPLES:: sage: C.<x> = CFiniteSequences(QQ) sage: C.guess([1,2,4,8,16,32]) C-finite sequence, generated by 1/(-2*x + 1) sage: r = C.guess([1,2,3,4,5]) Traceback (most recent call last): ... ValueError: Sequence too short for guessing. With Berlekamp-Massey, if an odd number of values is given, the last one is dropped. So with an odd number of values the result may not generate the last value:: sage: r = C.guess([1,2,4,8,9], algorithm='bm'); r C-finite sequence, generated by 1/(-2*x + 1) sage: r[0:5] [1, 2, 4, 8, 16] """ S = self.polynomial_ring() if algorithm == 'bm': from sage.matrix.berlekamp_massey import berlekamp_massey if len(sequence) < 2: raise ValueError('Sequence too short for guessing.') R = PowerSeriesRing(QQ, 'x') if len(sequence) % 2 == 1: sequence = sequence[:-1] l = len(sequence) - 1 denominator = S(berlekamp_massey(sequence).list()[::-1]) numerator = R(S(sequence) * denominator, prec=l).truncate() return CFiniteSequence(numerator / denominator) elif algorithm == 'pari': global _gp if len(sequence) < 6: raise ValueError('Sequence too short for guessing.') if _gp is None: _gp = Gp() _gp("ggf(v)=local(l,m,p,q,B);l=length(v);B=floor(l/2);\ if(B<3,return(0));m=matrix(B,B,x,y,v[x-y+B+1]);\ q=qflll(m,4)[1];if(length(q)==0,return(0));\ p=sum(k=1,B,x^(k-1)*q[k,1]);\ q=Pol(Pol(vector(l,n,v[l-n+1]))*p+O(x^(B+1)));\ if(polcoeff(p,0)<0,q=-q;p=-p);q=q/p;p=Ser(q+O(x^(l+1)));\ for(m=1,l,if(polcoeff(p,m-1)!=v[m],return(0)));q") _gp.set('gf', sequence) _gp("gf=ggf(gf)") num = S(sage_eval(_gp.eval("Vec(numerator(gf))"))[::-1]) den = S(sage_eval(_gp.eval("Vec(denominator(gf))"))[::-1]) if num == 0: return 0 else: return CFiniteSequence(num / den) else: from sage.matrix.constructor import matrix from sage.functions.other import floor, ceil from numpy import trim_zeros l = len(sequence) while l > 0 and sequence[l - 1] == 0: l -= 1 sequence = sequence[:l] if l == 0: return 0 if l < 6: raise ValueError('Sequence too short for guessing.') hl = ceil(ZZ(l) / 2) A = matrix([sequence[k:k + hl] for k in range(hl)]) K = A.kernel() if K.dimension() == 0: return 0 R = PolynomialRing(QQ, 'x') den = R(trim_zeros(K.basis()[-1].list()[::-1])) if den == 1: return 0 offset = next((i for i, x in enumerate(sequence) if x != 0), None) S = PowerSeriesRing(QQ, 'x', default_prec=l - offset) num = S(R(sequence) * den).add_bigoh(floor(ZZ(l) / 2 + 1)).truncate() if num == 0 or sequence != S(num / den).list(): return 0 else: return CFiniteSequence(num / den)
def molien_series(self, chi=None, return_series=True, prec=20, variable='t'): r""" Compute the Molien series of this finite group with respect to the character ``chi``. It can be returned either as a rational function in one variable or a power series in one variable. The base field must be a finite field, the rationals, or a cyclotomic field. Note that the base field characteristic cannot divide the group order (i.e., the non-modular case). ALGORITHM: For a finite group `G` in characteristic zero we construct the Molien series as .. MATH:: \frac{1}{|G|}\sum_{g \in G} \frac{\chi(g)}{\text{det}(I-tg)}, where `I` is the identity matrix and `t` an indeterminate. For characteristic `p` not dividing the order of `G`, let `k` be the base field and `N` the order of `G`. Define `\lambda` as a primitive `N`-th root of unity over `k` and `\omega` as a primitive `N`-th root of unity over `\QQ`. For each `g \in G` define `k_i(g)` to be the positive integer such that `e_i = \lambda^{k_i(g)}` for each eigenvalue `e_i` of `g`. Then the Molien series is computed as .. MATH:: \frac{1}{|G|}\sum_{g \in G} \frac{\chi(g)}{\prod_{i=1}^n(1 - t\omega^{k_i(g)})}, where `t` is an indeterminant. [Dec1998]_ INPUT: - ``chi`` -- (default: trivial character) a linear group character of this group - ``return_series`` -- boolean (default: ``True``) if ``True``, then returns the Molien series as a power series, ``False`` as a rational function - ``prec`` -- integer (default: 20); power series default precision - ``variable`` -- string (default: ``'t'``); Variable name for the Molien series OUTPUT: single variable rational function or power series with integer coefficients EXAMPLES:: sage: MatrixGroup(matrix(QQ,2,2,[1,1,0,1])).molien_series() Traceback (most recent call last): ... NotImplementedError: only implemented for finite groups sage: MatrixGroup(matrix(GF(3),2,2,[1,1,0,1])).molien_series() Traceback (most recent call last): ... NotImplementedError: characteristic cannot divide group order Tetrahedral Group:: sage: K.<i> = CyclotomicField(4) sage: Tetra = MatrixGroup([(-1+i)/2,(-1+i)/2, (1+i)/2,(-1-i)/2], [0,i, -i,0]) sage: Tetra.molien_series(prec=30) 1 + t^8 + 2*t^12 + t^16 + 2*t^20 + 3*t^24 + 2*t^28 + O(t^30) sage: mol = Tetra.molien_series(return_series=False); mol (t^8 - t^4 + 1)/(t^16 - t^12 - t^4 + 1) sage: mol.parent() Fraction Field of Univariate Polynomial Ring in t over Integer Ring sage: chi = Tetra.character(Tetra.character_table()[1]) sage: Tetra.molien_series(chi, prec=30, variable='u') u^6 + u^14 + 2*u^18 + u^22 + 2*u^26 + 3*u^30 + 2*u^34 + O(u^36) sage: chi = Tetra.character(Tetra.character_table()[2]) sage: Tetra.molien_series(chi) t^10 + t^14 + t^18 + 2*t^22 + 2*t^26 + O(t^30) :: sage: S3 = MatrixGroup(SymmetricGroup(3)) sage: mol = S3.molien_series(prec=10); mol 1 + t + 2*t^2 + 3*t^3 + 4*t^4 + 5*t^5 + 7*t^6 + 8*t^7 + 10*t^8 + 12*t^9 + O(t^10) sage: mol.parent() Power Series Ring in t over Integer Ring Octahedral Group:: sage: K.<v> = CyclotomicField(8) sage: a = v-v^3 #sqrt(2) sage: i = v^2 sage: Octa = MatrixGroup([(-1+i)/2,(-1+i)/2, (1+i)/2,(-1-i)/2], [(1+i)/a,0, 0,(1-i)/a]) sage: Octa.molien_series(prec=30) 1 + t^8 + t^12 + t^16 + t^18 + t^20 + 2*t^24 + t^26 + t^28 + O(t^30) Icosahedral Group:: sage: K.<v> = CyclotomicField(10) sage: z5 = v^2 sage: i = z5^5 sage: a = 2*z5^3 + 2*z5^2 + 1 #sqrt(5) sage: Ico = MatrixGroup([[z5^3,0, 0,z5^2], [0,1, -1,0], [(z5^4-z5)/a, (z5^2-z5^3)/a, (z5^2-z5^3)/a, -(z5^4-z5)/a]]) sage: Ico.molien_series(prec=40) 1 + t^12 + t^20 + t^24 + t^30 + t^32 + t^36 + O(t^40) :: sage: G = MatrixGroup(CyclicPermutationGroup(3)) sage: chi = G.character(G.character_table()[1]) sage: G.molien_series(chi, prec=10) t + 2*t^2 + 3*t^3 + 5*t^4 + 7*t^5 + 9*t^6 + 12*t^7 + 15*t^8 + 18*t^9 + 22*t^10 + O(t^11) :: sage: K = GF(5) sage: S = MatrixGroup(SymmetricGroup(4)) sage: G = MatrixGroup([matrix(K,4,4,[K(y) for u in m.list() for y in u])for m in S.gens()]) sage: G.molien_series(return_series=False) 1/(t^10 - t^9 - t^8 + 2*t^5 - t^2 - t + 1) :: sage: i = GF(7)(3) sage: G = MatrixGroup([[i^3,0,0,-i^3],[i^2,0,0,-i^2]]) sage: chi = G.character(G.character_table()[4]) sage: G.molien_series(chi) 3*t^5 + 6*t^11 + 9*t^17 + 12*t^23 + O(t^25) """ if not self.is_finite(): raise NotImplementedError("only implemented for finite groups") if chi is None: chi = self.trivial_character() M = self.matrix_space() R = FractionField(self.base_ring()) N = self.order() if R.characteristic() == 0: P = PolynomialRing(R, variable) t = P.gen() #it is possible the character is over a larger cyclotomic field K = chi.values()[0].parent() if K.degree() != 1: if R.degree() != 1: L = K.composite_fields(R)[0] else: L = K else: L = R mol = P(0) for g in self: mol += L(chi(g)) / (M.identity_matrix() - t * g.matrix()).det().change_ring(L) elif R.characteristic().divides(N): raise NotImplementedError( "characteristic cannot divide group order") else: #char p>0 #find primitive Nth roots of unity over base ring and QQ F = cyclotomic_polynomial(N).change_ring(R) w = F.roots(ring=R.algebraic_closure(), multiplicities=False)[0] #don't need to extend further in this case since the order of #the roots of unity in the character divide the order of the group L = CyclotomicField(N, 'v') v = L.gen() #construct Molien series P = PolynomialRing(L, variable) t = P.gen() mol = P(0) for g in self: #construct Phi phi = L(chi(g)) for e in g.matrix().eigenvalues(): #find power such that w**n = e n = 1 while w**n != e and n < N + 1: n += 1 #raise v to that power phi *= (1 - t * v**n) mol += P(1) / phi #We know the coefficients will be integers mol = mol.numerator().change_ring(ZZ) / mol.denominator().change_ring( ZZ) #divide by group order mol /= N if return_series: PS = PowerSeriesRing(ZZ, variable, default_prec=prec) return PS(mol) return mol
def characteristic_polynomial_from_traces(traces, d, q, i, sign): r""" Given a sequence of traces `t_1, \dots, t_k`, return the corresponding characteristic polynomial with Weil numbers as roots. The characteristic polynomial is defined by the generating series .. MATH:: P(T) = \exp\left(- \sum_{k\geq 1} t_k \frac{T^k}{k}\right) and should have the property that reciprocals of all roots have absolute value `q^{i/2}`. INPUT: - ``traces`` -- a list of integers `t_1, \dots, t_k` - ``d`` -- the degree of the characteristic polynomial - ``q`` -- power of a prime number - ``i`` -- integer, the weight in the motivic sense - ``sign`` -- integer, the sign OUTPUT: a polynomial EXAMPLES:: sage: from sage.modular.hypergeometric_motive import characteristic_polynomial_from_traces sage: characteristic_polynomial_from_traces([1, 1], 1, 3, 0, -1) -T + 1 sage: characteristic_polynomial_from_traces([25], 1, 5, 4, -1) -25*T + 1 sage: characteristic_polynomial_from_traces([3], 2, 5, 1, 1) 5*T^2 - 3*T + 1 sage: characteristic_polynomial_from_traces([1], 2, 7, 1, 1) 7*T^2 - T + 1 sage: characteristic_polynomial_from_traces([20], 3, 29, 2, 1) 24389*T^3 - 580*T^2 - 20*T + 1 sage: characteristic_polynomial_from_traces([12], 3, 13, 2, -1) -2197*T^3 + 156*T^2 - 12*T + 1 sage: characteristic_polynomial_from_traces([36,7620], 4, 17, 3, 1) 24137569*T^4 - 176868*T^3 - 3162*T^2 - 36*T + 1 sage: characteristic_polynomial_from_traces([-4,276], 4, 5, 3, 1) 15625*T^4 + 500*T^3 - 130*T^2 + 4*T + 1 sage: characteristic_polynomial_from_traces([4,-276], 4, 5, 3, 1) 15625*T^4 - 500*T^3 + 146*T^2 - 4*T + 1 sage: characteristic_polynomial_from_traces([22, 484], 4, 31, 2, -1) -923521*T^4 + 21142*T^3 - 22*T + 1 TESTS:: sage: characteristic_polynomial_from_traces([-36], 4, 17, 3, 1) Traceback (most recent call last): ... ValueError: not enough traces were given """ if len(traces) < d // 2: raise ValueError('not enough traces were given') if i % 2 and d % 2: raise ValueError('i and d may not both be odd') t = PowerSeriesRing(QQ, 't').gen() ring = PolynomialRing(ZZ, 'T') series = sum(-api * t**(i + 1) / (i + 1) for i, api in enumerate(traces)) series = series.O(d // 2 + 1).exp() coeffs = list(series) coeffs += [0] * max(0, d // 2 + 1 - len(coeffs)) data = [0 for _ in range(d + 1)] for k in range(d // 2 + 1): data[k] = coeffs[k] for k in range(d // 2 + 1, d + 1): data[k] = sign * coeffs[d - k] * q**(i * (k - d / 2)) return ring(data)
def _rank(self, K) : """ Return the dimension of the level N space of given weight. """ if not K is QQ : raise NotImplementedError if not self.__level.is_prime() : raise NotImplementedError if self.__weight % 2 != 0 : raise NotImplementedError( "Only even weights available") N = self.__level if N == 1 : ## By Igusa's theorem on the generators of the graded ring P = PowerSeriesRing(ZZ, 't') t = P.gen(0) dims = 1 / ((1 - t**4) * (1 - t**6) * (1 - t**10) * (1 - t**12)).add_bigoh(self.__weight + 1) return dims[self.__weight] if N == 2 : ## As in Ibukiyama, Onodera - On the graded ring of modular forms of the Siegel ## paramodular group of level 2, Proposition 2 P = PowerSeriesRing(ZZ, 't') t = P.gen(0) dims = ((1 + t**10) * (1 + t ** 12) * (1 + t**11)) dims = dims / ((1 - t**4) * (1 - t**6) * (1 - t**8) * (1 - t**12)).add_bigoh(self.__weight + 1) return dims[self.__weight] if N == 3 : ## By Dern, Paramodular forms of degree 2 and level 3, Corollary 5.6 P = PowerSeriesRing(ZZ, 't') t = P.gen(0) dims = ((1 + t**12) * (1 + t**8 + t**9 + t**10 + t**11 + t**19)) dims = dims / ((1 - t**4) * (1 - t**6)**2 * (1 - t**12)).add_bigoh(self.__weight + 1) return dims[self.__weight] if N == 5 : ## By Marschner, Paramodular forms of degree 2 with particular emphasis on level t = 5, ## Corollary 7.3.4. PhD thesis electronically available via the library of ## RWTH University, Aachen, Germany P = PowerSeriesRing(ZZ, 't') t = P.gen(0) dims = t**30 + t**24 + t**23 + 2*t**22 + t**21 + 2*t**20 + t**19 + 2*t**18 \ + 2*t**16 + 2*t**14 + 2*t**12 + t**11 + 2*t**10 + t**9 + 2*t**8 + t**7 + t**6 + 1 dims = dims / ((1 - t**4) * (1 - t**5) * (1 - t**6) * (1 - t**12)).add_bigoh(self.__weight + 1) return dims[self.__weight] if self.__weight == 2 : ## There are only cuspforms, since there is no elliptic modular form ## of weight 2. if N < 277 : ## Poor, Yuen - Paramodular cusp forms tells us that all forms are ## Gritsenko lifts return JacobiFormD1NN_Gamma(self.__level, 2)._rank(QQ) raise NotImplementedError elif self.__weight == 4 : ## This is the formula cited by Poor and Yuen in Paramodular cusp forms cuspidal_dim = Integer( (N**2 - 143) / Integer(576) + N / Integer(8) + kronecker_symbol(-1, N) * (N - 12) / Integer(96) + kronecker_symbol(2, N) / Integer(8) + kronecker_symbol(3, N) / Integer(12) + kronecker_symbol(-3, N) * N / Integer(36) ) else : ## This is the formula given by Ibukiyama in ## Relations of dimension of automorphic forms of Sp(2,R) and its compact twist Sp(2), ## Theorem 4 p = N k = self.__weight ## This is the reversed Ibukiyama symbol [.., .., ..; ..] def ibukiyama_symbol(modulus, *args) : return args[k % modulus] ## if p == 2 this formula is wrong. If the weight is even it differs by ## -3/16 from the true dimension and if the weight is odd it differs by ## -1/16 from the true dimension. H1 = (p**2 + 1) * (2 * k - 2) * (2 * k - 3) * (2 * k - 4) / Integer(2**9 * 3**3 * 5) H2 = (-1)**k * (2 * k - 2) * (2 * k - 4) / Integer(2**8 * 3**2) \ + ( (-1)**k * (2 * k - 2) * (2 * k - 4) / Integer(2**7 * 3) if p !=2 else (-1)**k * (2 * k - 2) * (2 * k - 4) / Integer(2**9) ) H3 = ( ibukiyama_symbol(4, k - 2, -k + 1, -k + 2, k - 1) / Integer(2**4 * 3) if p != 3 else 5 * ibukiyama_symbol(4, k - 2, -k + 1, -k + 2, k - 1) / Integer(2**5 * 3) ) H4 = ( ibukiyama_symbol(3, 2 * k - 3, -k + 1, -k + 2) / Integer(2**2 * 3**3) if p != 2 else 5 * ibukiyama_symbol(3, 2 * k - 3, -k + 1, -k + 2) / Integer(2**2 * 3**3) ) H5 = ibukiyama_symbol(6, -1, -k + 1, -k + 2, 1, k - 1, k - 2) / Integer(2**2 * 3**2) if p % 4 == 1 : H6 = 5 * (2 * k - 3) * (p + 1) / Integer(2**7 * 3) + (-1)**k * (p + 1) / Integer(2**7) elif p % 4 == 3 : H6 = (2 * k - 3) * (p - 1) / Integer(2**7) + 5 * (-1)**k * (p - 1) / Integer(2**7 * 3) else : H6 = 3 * (2 * k - 3) / Integer(2**7) + 7 * (-1)**k / Integer(2**7 * 3) if p % 3 == 1 : H7 = (2 * k - 3) * (p + 1) / Integer(2 * 3**3) \ + (p + 1) * ibukiyama_symbol(3, 0, -1, 1) / Integer(2**2 * 3**3) elif p % 3 == 2 : H7 = (2 * k - 3) * (p - 1) / Integer(2**2 * 3**3) \ + (p - 1) * ibukiyama_symbol(3, 0, -1, 1) / Integer(2 * 3**3) else : H7 = 5 * (2 * k - 3) / Integer(2**2 * 3**3) \ + ibukiyama_symbol(3, 0, -1, 1) / Integer(3**3) H8 = ibukiyama_symbol(12, 1, 0, 0, -1, -1, -1, -1, 0, 0, 1, 1, 1) / Integer(2 * 3) H9 = ( 2 * ibukiyama_symbol(6, 1, 0, 0, -1, 0, 0) / Integer(3**2) if p != 2 else ibukiyama_symbol(6, 1, 0, 0, -1, 0, 0) / Integer(2 * 3**2) ) H10 = (1 + kronecker_symbol(5, p)) * ibukiyama_symbol(5, 1, 0, 0, -1, 0) / Integer(5) H11 = (1 + kronecker_symbol(2, p)) * ibukiyama_symbol(4, 1, 0, 0, -1) / Integer(2**3) if p % 12 == 1 : H12 = ibukiyama_symbol(3, 0, 1, -1) / Integer(2 * 3) elif p % 12 == 11 : H12 = (-1)**k / Integer(2 * 3) elif p == 2 or p == 3 : H12 = (-1)**k / Integer(2**2 * 3) else : H12 = 0 I1 = ibukiyama_symbol(6, 0, 1, 1, 0, -1, -1) / Integer(6) I2 = ibukiyama_symbol(3, -2, 1, 1) / Integer(2 * 3**2) if p == 3 : I3 = ibukiyama_symbol(3, -2, 1, 1)/ Integer(3**2) elif p % 3 == 1 : I3 = 2 * ibukiyama_symbol(3, -1, 1, 0) / Integer(3**2) else : I3 = 2 * ibukiyama_symbol(3, -1, 0, 1) / Integer(3**2) I4 = ibukiyama_symbol(4, -1, 1, 1, -1) / Integer(2**2) I5 = (-1)**k / Integer(2**3) I6 = (-1)**k * (2 - kronecker_symbol(-1, p)) / Integer(2**4) I7 = -(-1)**k * (2 * k - 3) / Integer(2**3 * 3) I8 = -p * (2 * k - 3) / Integer(2**4 * 3**2) I9 = -1 / Integer(2**3 * 3) I10 = (p + 1) / Integer(2**3 * 3) I11 = -(1 + kronecker_symbol(-1, p)) / Integer(8) I12 = -(1 + kronecker_symbol(-3, p)) / Integer(6) cuspidal_dim = H1 + H2 + H3 + H4 + H5 + H6 + H7 + H8 + H9 + H10 + H11 + H12 \ + I1 + I2 + I3 + I4 + I5 + I6 + I7 + I8 + I9 + I10 + I11 + I12 mfs = ModularForms(1, self.__weight) return cuspidal_dim + mfs.dimension() + mfs.cuspidal_subspace().dimension()
def __getitem__(self, arg): """ Extend this ring by one or several elements to create a polynomial ring, a power series ring, or an algebraic extension. This is a convenience method intended primarily for interactive use. .. SEEALSO:: :func:`~sage.rings.polynomial.polynomial_ring_constructor.PolynomialRing`, :func:`~sage.rings.power_series_ring.PowerSeriesRing`, :meth:`~sage.rings.ring.Ring.extension`, :meth:`sage.rings.integer_ring.IntegerRing_class.__getitem__`, :meth:`sage.rings.matrix_space.MatrixSpace.__getitem__`, :meth:`sage.structure.parent.Parent.__getitem__` EXAMPLES: We create several polynomial rings:: sage: ZZ['x'] Univariate Polynomial Ring in x over Integer Ring sage: QQ['x'] Univariate Polynomial Ring in x over Rational Field sage: GF(17)['abc'] Univariate Polynomial Ring in abc over Finite Field of size 17 sage: GF(17)['a,b,c'] Multivariate Polynomial Ring in a, b, c over Finite Field of size 17 sage: GF(17)['a']['b'] Univariate Polynomial Ring in b over Univariate Polynomial Ring in a over Finite Field of size 17 We can create skew polynomial rings:: sage: k.<t> = GF(5^3) sage: Frob = k.frobenius_endomorphism() sage: k['x',Frob] Skew Polynomial Ring in x over Finite Field in t of size 5^3 twisted by t |--> t^5 We can also create power series rings by using double brackets:: sage: QQ[['t']] Power Series Ring in t over Rational Field sage: ZZ[['W']] Power Series Ring in W over Integer Ring sage: ZZ[['x,y,z']] Multivariate Power Series Ring in x, y, z over Integer Ring sage: ZZ[['x','T']] Multivariate Power Series Ring in x, T over Integer Ring Use :func:`~sage.rings.fraction_field.Frac` or :meth:`~sage.rings.ring.CommutativeRing.fraction_field` to obtain the fields of rational functions and Laurent series:: sage: Frac(QQ['t']) Fraction Field of Univariate Polynomial Ring in t over Rational Field sage: Frac(QQ[['t']]) Laurent Series Ring in t over Rational Field sage: QQ[['t']].fraction_field() Laurent Series Ring in t over Rational Field Note that the same syntax can be used to create number fields:: sage: QQ[I] Number Field in I with defining polynomial x^2 + 1 with I = 1*I sage: QQ[I].coerce_embedding() Generic morphism: From: Number Field in I with defining polynomial x^2 + 1 with I = 1*I To: Complex Lazy Field Defn: I -> 1*I :: sage: QQ[sqrt(2)] Number Field in sqrt2 with defining polynomial x^2 - 2 with sqrt2 = 1.414213562373095? sage: QQ[sqrt(2)].coerce_embedding() Generic morphism: From: Number Field in sqrt2 with defining polynomial x^2 - 2 with sqrt2 = 1.414213562373095? To: Real Lazy Field Defn: sqrt2 -> 1.414213562373095? :: sage: QQ[sqrt(2),sqrt(3)] Number Field in sqrt2 with defining polynomial x^2 - 2 over its base field and orders in number fields:: sage: ZZ[I] Order in Number Field in I with defining polynomial x^2 + 1 with I = 1*I sage: ZZ[sqrt(5)] Order in Number Field in sqrt5 with defining polynomial x^2 - 5 with sqrt5 = 2.236067977499790? sage: ZZ[sqrt(2)+sqrt(3)] Order in Number Field in a with defining polynomial x^4 - 10*x^2 + 1 with a = 3.146264369941973? Embeddings are found for simple extensions (when that makes sense):: sage: QQi.<i> = QuadraticField(-1, 'i') sage: QQ[i].coerce_embedding() Generic morphism: From: Number Field in i with defining polynomial x^2 + 1 with i = 1*I To: Complex Lazy Field Defn: i -> 1*I TESTS: A few corner cases:: sage: QQ[()] Multivariate Polynomial Ring in no variables over Rational Field sage: QQ[[]] Traceback (most recent call last): ... TypeError: power series rings must have at least one variable These kind of expressions do not work:: sage: QQ['a,b','c'] Traceback (most recent call last): ... ValueError: variable name 'a,b' is not alphanumeric sage: QQ[['a,b','c']] Traceback (most recent call last): ... ValueError: variable name 'a,b' is not alphanumeric sage: QQ[[['x']]] Traceback (most recent call last): ... TypeError: expected R[...] or R[[...]], not R[[[...]]] Extension towers are built as follows and use distinct generator names:: sage: K = QQ[2^(1/3), 2^(1/2), 3^(1/3)] sage: K Number Field in a with defining polynomial x^3 - 2 over its base field sage: K.base_field() Number Field in sqrt2 with defining polynomial x^2 - 2 over its base field sage: K.base_field().base_field() Number Field in b with defining polynomial x^3 - 3 Embeddings:: sage: QQ[I](I.pyobject()) I sage: a = 10^100; expr = (2*a + sqrt(2))/(2*a^2-1) sage: QQ[expr].coerce_embedding() is None False sage: QQ[sqrt(5)].gen() > 0 True sage: expr = sqrt(2) + I*(cos(pi/4, hold=True) - sqrt(2)/2) sage: QQ[expr].coerce_embedding() Generic morphism: From: Number Field in a with defining polynomial x^2 - 2 with a = 1.414213562373095? To: Real Lazy Field Defn: a -> 1.414213562373095? """ def normalize_arg(arg): if isinstance(arg, (tuple, list)): # Allowing arbitrary iterables would create confusion, but we # may want to support a few more. return tuple(arg) elif isinstance(arg, str): return tuple(arg.split(',')) else: return (arg,) # 1. If arg is a list, try to return a power series ring. if isinstance(arg, list): if arg == []: raise TypeError("power series rings must have at least one variable") elif len(arg) == 1: # R[["a,b"]], R[[(a,b)]]... if isinstance(arg[0], list): raise TypeError("expected R[...] or R[[...]], not R[[[...]]]") elts = normalize_arg(arg[0]) else: elts = normalize_arg(arg) from sage.rings.power_series_ring import PowerSeriesRing return PowerSeriesRing(self, elts) if isinstance(arg, tuple): from sage.categories.morphism import Morphism if len(arg) == 2 and isinstance(arg[1], Morphism): from sage.rings.polynomial.skew_polynomial_ring_constructor import SkewPolynomialRing return SkewPolynomialRing(self, arg[1], names=arg[0]) # 2. Otherwise, if all specified elements are algebraic, try to # return an algebraic extension elts = normalize_arg(arg) try: minpolys = [a.minpoly() for a in elts] except (AttributeError, NotImplementedError, ValueError, TypeError): minpolys = None if minpolys: # how to pass in names? names = tuple(_gen_names(elts)) if len(elts) == 1: from sage.rings.all import CIF, CLF, RLF elt = elts[0] try: iv = CIF(elt) except (TypeError, ValueError): emb = None else: # First try creating an ANRoot manually, because # extension(..., embedding=CLF(expr)) (or # ...QQbar(expr)) would normalize the expression in # QQbar, which currently is VERY slow in many cases. # This may fail when minpoly has close roots or elt is # a complicated symbolic expression. # TODO: Rewrite using #19362 and/or #17886 and/or # #15600 once those issues are solved. from sage.rings.qqbar import AlgebraicNumber, ANRoot try: elt = AlgebraicNumber(ANRoot(minpolys[0], iv)) except ValueError: pass # Force a real embedding when possible, to get the # right ordered ring structure. if (iv.imag().is_zero() or iv.imag().contains_zero() and elt.imag().is_zero()): emb = RLF(elt) else: emb = CLF(elt) return self.extension(minpolys[0], names[0], embedding=emb) try: # Doing the extension all at once is best, if possible... return self.extension(minpolys, names) except (TypeError, ValueError): # ...but we can also construct it iteratively return reduce(lambda R, ext: R.extension(*ext), zip(minpolys, names), self) # 2. Otherwise, try to return a polynomial ring from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing return PolynomialRing(self, elts)
def __init__(self, base_ring, num_gens, name_list, order='negdeglex', default_prec=10, sparse=False): """ Initializes a multivariate power series ring. See PowerSeriesRing for complete documentation. INPUT - ``base_ring`` - a commutative ring - ``num_gens`` - number of generators - ``name_list`` - List of indeterminate names or a single name. If a single name is given, indeterminates will be this name followed by a number from 0 to num_gens - 1. If a list is given, these will be the indeterminate names and the length of the list must be equal to num_gens. - ``order`` - ordering of variables; default is negative degree lexicographic - ``default_prec`` - The default total-degree precision for elements. The default value of default_prec is 10. - ``sparse`` - whether or not power series are sparse EXAMPLES:: sage: R.<t,u,v> = PowerSeriesRing(QQ) sage: g = 1 + v + 3*u*t^2 - 2*v^2*t^2 sage: g = g.add_bigoh(5); g 1 + v + 3*t^2*u - 2*t^2*v^2 + O(t, u, v)^5 sage: g in R True """ order = TermOrder(order, num_gens) self._term_order = order if not base_ring.is_commutative(): raise TypeError("Base ring must be a commutative ring.") n = int(num_gens) if n < 0: raise ValueError( "Multivariate Polynomial Rings must have more than 0 variables." ) self._ngens = n self._has_singular = False #cannot convert to Singular by default ParentWithGens.__init__(self, base_ring, name_list) Nonexact.__init__(self, default_prec) # underlying polynomial ring in which to represent elements self._poly_ring_ = PolynomialRing(base_ring, self.variable_names(), sparse=sparse, order=order) # because sometimes PowerSeriesRing_generic calls self.__poly_ring self._PowerSeriesRing_generic__poly_ring = self._poly_ring() # background univariate power series ring self._bg_power_series_ring = PowerSeriesRing(self._poly_ring_, 'Tbg', sparse=sparse, default_prec=default_prec) self._bg_indeterminate = self._bg_power_series_ring.gen() ## use the following in PowerSeriesRing_generic.__call__ self._PowerSeriesRing_generic__power_series_class = MPowerSeries self._is_sparse = sparse self._params = (base_ring, num_gens, name_list, order, default_prec, sparse) self._populate_coercion_lists_()
def __init__(self, base_ring, num_gens, name_list, order='negdeglex', default_prec=10, sparse=False): """ Initializes a multivariate power series ring. See PowerSeriesRing for complete documentation. INPUT: - ``base_ring`` -- a commutative ring - ``num_gens`` -- number of generators - ``name_list`` -- List of indeterminate names or a single name. If a single name is given, indeterminates will be this name followed by a number from 0 to num_gens - 1. If a list is given, these will be the indeterminate names and the length of the list must be equal to num_gens. - ``order`` -- ordering of variables; default is negative degree lexicographic - ``default_prec`` -- The default total-degree precision for elements. The default value of default_prec is 10. - ``sparse`` -- whether or not the power series are sparse. The underlying polynomial ring is always sparse. EXAMPLES:: sage: R.<t,u,v> = PowerSeriesRing(QQ) sage: g = 1 + v + 3*u*t^2 - 2*v^2*t^2 sage: g = g.add_bigoh(5); g 1 + v + 3*t^2*u - 2*t^2*v^2 + O(t, u, v)^5 sage: g in R True TESTS: By :trac:`14084`, the multi-variate power series ring belongs to the category of integral domains, if the base ring does:: sage: P = ZZ[['x','y']] sage: P.category() Category of integral domains sage: TestSuite(P).run() Otherwise, it belongs to the category of commutative rings:: sage: P = Integers(15)[['x','y']] sage: P.category() Category of commutative rings sage: TestSuite(P).run() """ self._term_order = order if not base_ring.is_commutative(): raise TypeError("Base ring must be a commutative ring.") n = int(num_gens) if n < 0: raise ValueError("Multivariate Polynomial Rings must have more than 0 variables.") self._ngens = n self._has_singular = False #cannot convert to Singular by default # Multivariate power series rings inherit from power series rings. But # apparently we can not call their initialisation. Instead, initialise # CommutativeRing and Nonexact: CommutativeRing.__init__(self, base_ring, name_list, category = _IntegralDomains if base_ring in _IntegralDomains else _CommutativeRings) Nonexact.__init__(self, default_prec) # underlying polynomial ring in which to represent elements self._poly_ring_ = PolynomialRing(base_ring, self.variable_names(), order=order) # because sometimes PowerSeriesRing_generic calls self.__poly_ring self._PowerSeriesRing_generic__poly_ring = self._poly_ring() # background univariate power series ring self._bg_power_series_ring = PowerSeriesRing(self._poly_ring_, 'Tbg', sparse=sparse, default_prec=default_prec) self._bg_indeterminate = self._bg_power_series_ring.gen() self._is_sparse = sparse self._params = (base_ring, num_gens, name_list, order, default_prec, sparse) self._populate_coercion_lists_()
def compute_wp_fast(k, A, B, m): r""" Computes the Weierstrass function of an elliptic curve defined by short Weierstrass model: `y^2 = x^3 + Ax + B`. It does this with as fast as polynomial of degree `m` can be multiplied together in the base ring, i.e. `O(M(n))` in the notation of [BMSS]. Let `p` be the characteristic of the underlying field: Then we must have either `p=0`, or `p > m + 3`. INPUT: - ``k`` - the base field of the curve - ``A`` - and - ``B`` - as the coeffients of the short Weierstrass model `y^2 = x^3 +Ax +B`, and - ``m`` - the precision to which the function is computed to. OUTPUT: the Weierstrass `\wp` function as a Laurent series to precision `m`. ALGORITHM: This function uses the algorithm described in section 3.3 of [BMSS]. EXAMPLES:: sage: from sage.schemes.elliptic_curves.ell_wp import compute_wp_fast sage: compute_wp_fast(QQ, 1, 8, 7) z^-2 - 1/5*z^2 - 8/7*z^4 + 1/75*z^6 + O(z^7) sage: k = GF(37) sage: compute_wp_fast(k, k(1), k(8), 5) z^-2 + 22*z^2 + 20*z^4 + O(z^5) """ R = PowerSeriesRing(k, 'z', default_prec=m + 5) z = R.gen() s = 2 f1 = z.add_bigoh(m + 3) n = 2 * m + 4 # solve the nonlinear differential equation while (s < n): f1pr = f1.derivative() next_s = 2 * s - 1 a = 2 * f1pr b = -(6 * B * (f1**5) + 4 * A * (f1**3)) c = B * (f1**6) + A * f1**4 + 1 - (f1pr**2) # we should really be computing only mod z^next_s here. # but we loose only a factor 2 f2 = solve_linear_differential_system(a, b, c, 0) # sometimes we get to 0 quicker than s reaches n if f2 == 0: break f1 = f1 + f2 s = next_s R = f1 Q = R**2 pe = 1 / Q return pe
def e_phipsi(phi, psi, k, t=1, prec=5, mat=Matrix([[1, 0], [0, 1]]), base_ring=None): r""" Computes the Eisenstein series attached to the characters psi, phi as defined on p129 of Diamond--Shurman hit by mat \in \SL_2(\Z) INPUT: - psi, a primitive Dirichlet character - phi, a primitive Dirichlet character - k, int -- the weight - t, int -- the shift - prec, the desired absolute precision, can be fractional. The expansion will be up to O(q_w^(floor(w*prec))), where w is the width of the cusp. - mat, a matrix - typically taking $i\infty$ to some other cusp - base_ring, a ring - the ring in which the modular forms are defined OUTPUT: - an instance of QExpansion """ chi2 = phi chi1 = psi try: assert (QQ(chi1(-1)) * QQ(chi2(-1)) == (-1)**k) except AssertionError: print("Parity of characters must match parity of weight") return None N1 = chi1.level() N2 = chi2.level() N = t * N1 * N2 mat2, Tn = find_correct_matrix(mat, N) #By construction gamma = mat2 * Tn * mat**(-1) is in Gamma0(N) so if E is our Eisenstein series we can evaluate E|mat = chi(gamma) * E|mat2*Tn. #Since E|mat2 has a Fourier expansion in qN, the matrix Tn acts as a a twist. The value c_gamma = chi(gamma) is calculated below. #The point of swapping mat with mat2 is that mat2 satisfies C|N, C>0, (A,N)=1 and N|B and our formulas for the coefficients require this condition. A, B, C, D = mat2.list() gamma = mat2 * Tn * mat**(-1) if base_ring == None: Nbig = lcm(N, euler_phi(N1 * N2)) base_ring = CyclotomicField(Nbig, 'zeta' + str(Nbig)) zetaNbig = base_ring.gen() zetaN = zetaNbig**(Nbig / N) else: zetaN = base_ring.zeta(N) g = gcd(t, C) g1 = gcd(N1 * g, C) g2 = gcd(N2 * g, C) #Resulting Eisenstein series will have Fourier expansion in q_N**(g1g2)=q_(N/gcd(N,g1g2))**(g1g2/gcd(N,g1g2)) Q = PowerSeriesRing(base_ring, 'q' + str(N / gcd(N, g1 * g2))) qN = Q.gen() zeta_Cg = zetaN**(N / (C / g)) zeta_tmp = zeta_Cg**(inverse_mod(-A * ZZ(t / g), C / g)) #Calculating a few values that will be used repeatedly chi1bar_vals = [base_ring(chi1.bar()(i)) for i in range(N1)] cp_list1 = [i for i in range(N1) if gcd(i, N1) == 1] chi2bar_vals = [base_ring(chi2.bar()(i)) for i in range(N2)] cp_list2 = [i for i in range(N2) if gcd(i, N2) == 1] #Computation of the Fourier coefficients ser = O(qN**floor(prec * N / gcd(N, g1 * g2))) for n in range(1, ceil(prec * N / QQ(g1 * g2)) + 1): f = 0 for m in divisors(n) + list(map(lambda x: -x, divisors(n))): a = 0 for r1 in cp_list1: b = 0 if ((C / g1) * r1 - QQ(n) / QQ(m)) % ((N1 * g) / g1) == 0: for r2 in cp_list2: if ((C / g2) * r2 - m) % ((N2 * g) / g2) == 0: b += chi2bar_vals[r2] * zeta_tmp**( (n / m - (C / g1) * r1) / ((N1 * g) / g1) * (m - (C / g2) * r2) / ((N2 * g) / g2)) a += chi1bar_vals[r1] * b a *= sign(m) * m**(k - 1) f += a f *= zetaN**(inverse_mod(A, N) * (g1 * g2) / C * n) #The additional factor zetaN**(n*Tn[0][1]) comes from the twist by Tn ser += zetaN**(n * g1 * g2 * Tn[0][1]) * f * qN**(n * ( (g1 * g2) / gcd(N, g1 * g2))) #zk(chi1, chi2, c) gauss1 = base_ring(gauss_sum_corr(chi1.bar())) gauss2 = base_ring(gauss_sum_corr(chi2.bar())) zk = 2 * (N2 * t / QQ(g2))**(k - 1) * (t / g) * gauss1 * gauss2 #The following is a temporary fix for a bug in sage if base_ring == CC: G = DirichletGroup(N1 * N2, CC) G[0] #Otherwise chi1.bar().extend(N1*N2).base_extend(CC) or chi2.bar().extend(N1*N2).base_extend(CC) will produce an error #Constant term #c_gamma comes from replacing mat with mat2. c_gamma = chi1bar_vals[gamma[1][1] % N1] * chi2bar_vals[gamma[1][1] % N2] if N1.divides(C) and ZZ(C / N1).divides(t) and gcd(t / (C / N1), N1) == 1: ser += (-1)**(k - 1) * gauss2 / QQ( N2 * (g2 / g)**(k - 1)) * chi1bar_vals[(-A * t / g) % N1] * Sk( chi1.bar().extend(N1 * N2).base_extend(base_ring) * chi2.extend(N1 * N2).base_extend(base_ring), k) elif k == 1 and N2.divides(C) and ZZ(C / N2).divides(t) and gcd( t / (C / N2), N2) == 1: ser += gauss1 / QQ(N1) * chi2bar_vals[(-A * t / g) % N2] * Sk( chi1.extend(N1 * N2).base_extend(base_ring) * chi2.bar().extend(N1 * N2).base_extend(base_ring), k) return QExpansion( N, k, 2 / zk * c_gamma * ser + O(qN**floor(prec * N / gcd(N, g1 * g2))), N / gcd(N, g1 * g2))
def __init__(self, precision): self.__precision = precision self.__power_series_ring_ZZ = PowerSeriesRing(ZZ, 'q') self.__power_series_ring = PowerSeriesRing(QQ, 'q')
def _theta_factors(self, p=None): r""" Return the factor `W^\# (\theta_0, .., \theta_{2m - 1})^{\mathrm{T}}` as a list. The `q`-expansion is shifted by `-(m + 1)(2*m + 1) / 24` which will be compensated for by the eta factor. """ try: if p is None: return self.__theta_factors else: P = PowerSeriesRing(GF(p), 'q') return [list(map(P, facs)) for facs in self.__theta_factors] except AttributeError: qexp_prec = self._qexp_precision() if p is None: PS = self.integral_power_series_ring() else: PS = PowerSeriesRing(GF(p), 'q') m = self.__precision.jacobi_index() twom = 2 * m frmsq = twom**2 thetas = dict( ((i, j), dict()) for i in range(m + 1) for j in range(m + 1)) ## We want to calculate \hat \theta_{j,l} = sum_r (2 m r + j)**2l q**(m r**2 + j r). for r in range(0, isqrt((qexp_prec - 1 + m) // m) + 2): for j in [0, m]: fact = (twom * r + j)**2 coeff = 2 for l in range(0, m + 1): thetas[(j, l)][m * r**2 + r * j] = coeff coeff = coeff * fact thetas[(0, 0)][0] = 1 for r in range(0, isqrt((qexp_prec - 1 + m) // m) + 2): for j in range(1, m): fact_p = (twom * r + j)**2 fact_m = (twom * r - j)**2 coeff_p = 2 coeff_m = 2 for l in range(0, m + 1): thetas[(j, l)][m * r**2 + r * j] = coeff_p thetas[(j, l)][m * r**2 - r * j] = coeff_m coeff_p = coeff_p * fact_p coeff_m = coeff_m * fact_m thetas = dict( (k, PS(th).add_bigoh(qexp_prec)) for (k, th) in thetas.items()) W = matrix( PS, m + 1, [thetas[(j, l)] for j in range(m + 1) for l in range(m + 1)]) ## Since the adjoint of matrices with entries in a general ring ## is extremely slow for matrices of small size, we hard code the ## the cases `m = 2` and `m = 3`. The expressions are obtained by ## computing the adjoint of a matrix with entries `w_{i,j}` in a ## polynomial algebra. if m == 2 and qexp_prec > 10**5: adj00 = W[1, 1] * W[2, 2] - W[2, 1] * W[1, 2] adj01 = -W[1, 0] * W[2, 2] + W[2, 0] * W[1, 2] adj02 = W[1, 0] * W[2, 1] - W[2, 0] * W[1, 1] adj10 = -W[0, 1] * W[2, 2] + W[2, 1] * W[0, 2] adj11 = W[0, 0] * W[2, 2] - W[2, 0] * W[0, 2] adj12 = -W[0, 0] * W[2, 1] + W[2, 0] * W[0, 1] adj20 = W[0, 1] * W[1, 2] - W[1, 1] * W[0, 2] adj21 = -W[0, 0] * W[1, 2] + W[1, 0] * W[0, 2] adj22 = W[0, 0] * W[1, 1] - W[1, 0] * W[0, 1] Wadj = matrix(PS, [[adj00, adj01, adj02], [adj10, adj11, adj12], [adj20, adj21, adj22]]) elif m == 3 and qexp_prec > 10**5: adj00 = -W[0, 2] * W[1, 1] * W[2, 0] + W[0, 1] * W[1, 2] * W[ 2, 0] + W[0, 2] * W[1, 0] * W[2, 1] - W[0, 0] * W[ 1, 2] * W[2, 1] - W[0, 1] * W[1, 0] * W[2, 2] + W[ 0, 0] * W[1, 1] * W[2, 2] adj01 = -W[0, 3] * W[1, 1] * W[2, 0] + W[0, 1] * W[1, 3] * W[ 2, 0] + W[0, 3] * W[1, 0] * W[2, 1] - W[0, 0] * W[ 1, 3] * W[2, 1] - W[0, 1] * W[1, 0] * W[2, 3] + W[ 0, 0] * W[1, 1] * W[2, 3] adj02 = -W[0, 3] * W[1, 2] * W[2, 0] + W[0, 2] * W[1, 3] * W[ 2, 0] + W[0, 3] * W[1, 0] * W[2, 2] - W[0, 0] * W[ 1, 3] * W[2, 2] - W[0, 2] * W[1, 0] * W[2, 3] + W[ 0, 0] * W[1, 2] * W[2, 3] adj03 = -W[0, 3] * W[1, 2] * W[2, 1] + W[0, 2] * W[1, 3] * W[ 2, 1] + W[0, 3] * W[1, 1] * W[2, 2] - W[0, 1] * W[ 1, 3] * W[2, 2] - W[0, 2] * W[1, 1] * W[2, 3] + W[ 0, 1] * W[1, 2] * W[2, 3] adj10 = -W[0, 2] * W[1, 1] * W[3, 0] + W[0, 1] * W[1, 2] * W[ 3, 0] + W[0, 2] * W[1, 0] * W[3, 1] - W[0, 0] * W[ 1, 2] * W[3, 1] - W[0, 1] * W[1, 0] * W[3, 2] + W[ 0, 0] * W[1, 1] * W[3, 2] adj11 = -W[0, 3] * W[1, 1] * W[3, 0] + W[0, 1] * W[1, 3] * W[ 3, 0] + W[0, 3] * W[1, 0] * W[3, 1] - W[0, 0] * W[ 1, 3] * W[3, 1] - W[0, 1] * W[1, 0] * W[3, 3] + W[ 0, 0] * W[1, 1] * W[3, 3] adj12 = -W[0, 3] * W[1, 2] * W[3, 0] + W[0, 2] * W[1, 3] * W[ 3, 0] + W[0, 3] * W[1, 0] * W[3, 2] - W[0, 0] * W[ 1, 3] * W[3, 2] - W[0, 2] * W[1, 0] * W[3, 3] + W[ 0, 0] * W[1, 2] * W[3, 3] adj13 = -W[0, 3] * W[1, 2] * W[3, 1] + W[0, 2] * W[1, 3] * W[ 3, 1] + W[0, 3] * W[1, 1] * W[3, 2] - W[0, 1] * W[ 1, 3] * W[3, 2] - W[0, 2] * W[1, 1] * W[3, 3] + W[ 0, 1] * W[1, 2] * W[3, 3] adj20 = -W[0, 2] * W[2, 1] * W[3, 0] + W[0, 1] * W[2, 2] * W[ 3, 0] + W[0, 2] * W[2, 0] * W[3, 1] - W[0, 0] * W[ 2, 2] * W[3, 1] - W[0, 1] * W[2, 0] * W[3, 2] + W[ 0, 0] * W[2, 1] * W[3, 2] adj21 = -W[0, 3] * W[2, 1] * W[3, 0] + W[0, 1] * W[2, 3] * W[ 3, 0] + W[0, 3] * W[2, 0] * W[3, 1] - W[0, 0] * W[ 2, 3] * W[3, 1] - W[0, 1] * W[2, 0] * W[3, 3] + W[ 0, 0] * W[2, 1] * W[3, 3] adj22 = -W[0, 3] * W[2, 2] * W[3, 0] + W[0, 2] * W[2, 3] * W[ 3, 0] + W[0, 3] * W[2, 0] * W[3, 2] - W[0, 0] * W[ 2, 3] * W[3, 2] - W[0, 2] * W[2, 0] * W[3, 3] + W[ 0, 0] * W[2, 2] * W[3, 3] adj23 = -W[0, 3] * W[2, 2] * W[3, 1] + W[0, 2] * W[2, 3] * W[ 3, 1] + W[0, 3] * W[2, 1] * W[3, 2] - W[0, 1] * W[ 2, 3] * W[3, 2] - W[0, 2] * W[2, 1] * W[3, 3] + W[ 0, 1] * W[2, 2] * W[3, 3] adj30 = -W[1, 2] * W[2, 1] * W[3, 0] + W[1, 1] * W[2, 2] * W[ 3, 0] + W[1, 2] * W[2, 0] * W[3, 1] - W[1, 0] * W[ 2, 2] * W[3, 1] - W[1, 1] * W[2, 0] * W[3, 2] + W[ 1, 0] * W[2, 1] * W[3, 2] adj31 = -W[1, 3] * W[2, 1] * W[3, 0] + W[1, 1] * W[2, 3] * W[ 3, 0] + W[1, 3] * W[2, 0] * W[3, 1] - W[1, 0] * W[ 2, 3] * W[3, 1] - W[1, 1] * W[2, 0] * W[3, 3] + W[ 1, 0] * W[2, 1] * W[3, 3] adj32 = -W[1, 3] * W[2, 2] * W[3, 0] + W[1, 2] * W[2, 3] * W[ 3, 0] + W[1, 3] * W[2, 0] * W[3, 2] - W[1, 0] * W[ 2, 3] * W[3, 2] - W[1, 2] * W[2, 0] * W[3, 3] + W[ 1, 0] * W[2, 2] * W[3, 3] adj33 = -W[1, 3] * W[2, 2] * W[3, 1] + W[1, 2] * W[2, 3] * W[ 3, 1] + W[1, 3] * W[2, 1] * W[3, 2] - W[1, 1] * W[ 2, 3] * W[3, 2] - W[1, 2] * W[2, 1] * W[3, 3] + W[ 1, 1] * W[2, 2] * W[3, 3] Wadj = matrix(PS, [[adj00, adj01, adj02, adj03], [adj10, adj11, adj12, adj13], [adj20, adj21, adj22, adj23], [adj30, adj31, adj32, adj33]]) else: Wadj = W.adjoint() theta_factors = [[Wadj[i, r] for i in range(m + 1)] for r in range(m + 1)] if p is None: self.__theta_factors = theta_factors return theta_factors
def q_dimension(self, q=None, prec=None, use_product=False): r""" Return the `q`-dimension of ``self``. Let `B(\lambda)` denote a highest weight crystal. Recall that the degree of the `\mu`-weight space of `B(\lambda)` (under the principal gradation) is equal to `\langle \rho^{\vee}, \lambda - \mu \rangle` where `\langle \rho^{\vee}, \alpha_i \rangle = 1` for all `i \in I` (in particular, take `\rho^{\vee} = \sum_{i \in I} h_i`). The `q`-dimension of a highest weight crystal `B(\lambda)` is defined as .. MATH:: \dim_q B(\lambda) := \sum_{j \geq 0} \dim(B_j) q^j, where `B_j` denotes the degree `j` portion of `B(\lambda)`. This can be expressed as the product .. MATH:: \dim_q B(\lambda) = \prod_{\alpha^{\vee} \in \Delta_+^{\vee}} \left( \frac{1 - q^{\langle \lambda + \rho, \alpha^{\vee} \rangle}}{1 - q^{\langle \rho, \alpha^{\vee} \rangle}} \right)^{\mathrm{mult}\, \alpha}, where `\Delta_+^{\vee}` denotes the set of positive coroots. Taking the limit as `q \to 1` gives the dimension of `B(\lambda)`. For more information, see [Kac]_ Section 10.10. INPUT: - ``q`` -- the (generic) parameter `q` - ``prec`` -- (default: ``None``) The precision of the power series ring to use if the crystal is not known to be finite (i.e. the number of terms returned). If ``None``, then the result is returned as a lazy power series. - ``use_product`` -- (default: ``False``) if we have a finite crystal and ``True``, use the product formula EXAMPLES:: sage: C = crystals.Tableaux(['A',2], shape=[2,1]) sage: qdim = C.q_dimension(); qdim q^4 + 2*q^3 + 2*q^2 + 2*q + 1 sage: qdim(1) 8 sage: len(C) == qdim(1) True sage: C.q_dimension(use_product=True) == qdim True sage: C.q_dimension(prec=20) q^4 + 2*q^3 + 2*q^2 + 2*q + 1 sage: C.q_dimension(prec=2) 2*q + 1 sage: R.<t> = QQ[] sage: C.q_dimension(q=t^2) t^8 + 2*t^6 + 2*t^4 + 2*t^2 + 1 sage: C = crystals.Tableaux(['A',2], shape=[5,2]) sage: C.q_dimension() q^10 + 2*q^9 + 4*q^8 + 5*q^7 + 6*q^6 + 6*q^5 + 6*q^4 + 5*q^3 + 4*q^2 + 2*q + 1 sage: C = crystals.Tableaux(['B',2], shape=[2,1]) sage: qdim = C.q_dimension(); qdim q^10 + 2*q^9 + 3*q^8 + 4*q^7 + 5*q^6 + 5*q^5 + 5*q^4 + 4*q^3 + 3*q^2 + 2*q + 1 sage: qdim == C.q_dimension(use_product=True) True sage: C = crystals.Tableaux(['D',4], shape=[2,1]) sage: C.q_dimension() q^16 + 2*q^15 + 4*q^14 + 7*q^13 + 10*q^12 + 13*q^11 + 16*q^10 + 18*q^9 + 18*q^8 + 18*q^7 + 16*q^6 + 13*q^5 + 10*q^4 + 7*q^3 + 4*q^2 + 2*q + 1 We check with a finite tensor product:: sage: TP = crystals.TensorProduct(C, C) sage: TP.cardinality() 25600 sage: qdim = TP.q_dimension(use_product=True); qdim # long time q^32 + 2*q^31 + 8*q^30 + 15*q^29 + 34*q^28 + 63*q^27 + 110*q^26 + 175*q^25 + 276*q^24 + 389*q^23 + 550*q^22 + 725*q^21 + 930*q^20 + 1131*q^19 + 1362*q^18 + 1548*q^17 + 1736*q^16 + 1858*q^15 + 1947*q^14 + 1944*q^13 + 1918*q^12 + 1777*q^11 + 1628*q^10 + 1407*q^9 + 1186*q^8 + 928*q^7 + 720*q^6 + 498*q^5 + 342*q^4 + 201*q^3 + 117*q^2 + 48*q + 26 sage: qdim(1) # long time 25600 sage: TP.q_dimension() == qdim # long time True The `q`-dimensions of infinite crystals are returned as formal power series:: sage: C = crystals.LSPaths(['A',2,1], [1,0,0]) sage: C.q_dimension(prec=5) 1 + q + 2*q^2 + 2*q^3 + 4*q^4 + O(q^5) sage: C.q_dimension(prec=10) 1 + q + 2*q^2 + 2*q^3 + 4*q^4 + 5*q^5 + 7*q^6 + 9*q^7 + 13*q^8 + 16*q^9 + O(q^10) sage: qdim = C.q_dimension(); qdim 1 + q + 2*q^2 + 2*q^3 + 4*q^4 + 5*q^5 + 7*q^6 + 9*q^7 + 13*q^8 + 16*q^9 + 22*q^10 + O(x^11) sage: qdim.compute_coefficients(15) sage: qdim 1 + q + 2*q^2 + 2*q^3 + 4*q^4 + 5*q^5 + 7*q^6 + 9*q^7 + 13*q^8 + 16*q^9 + 22*q^10 + 27*q^11 + 36*q^12 + 44*q^13 + 57*q^14 + 70*q^15 + O(x^16) REFERENCES: .. [Kac] Victor G. Kac. *Infinite-dimensional Lie Algebras*. Third edition. Cambridge University Press, Cambridge, 1990. """ from sage.rings.all import ZZ WLR = self.weight_lattice_realization() I = self.index_set() mg = self.highest_weight_vectors() max_deg = float('inf') if prec is None else prec - 1 def iter_by_deg(gens): next = set(gens) deg = -1 while next and deg < max_deg: deg += 1 yield len(next) todo = next next = set([]) while todo: x = todo.pop() for i in I: y = x.f(i) if y is not None: next.add(y) # def iter_by_deg from sage.categories.finite_crystals import FiniteCrystals if self in FiniteCrystals(): if q is None: from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing q = PolynomialRing(ZZ, 'q').gen(0) if use_product: # Since we are in the classical case, all roots occur with multiplicity 1 pos_coroots = map(lambda x: x.associated_coroot(), WLR.positive_roots()) rho = WLR.rho() P = q.parent() ret = P.zero() for v in self.highest_weight_vectors(): hw = v.weight() ret += P.prod((1 - q**(rho + hw).scalar(ac)) / (1 - q**rho.scalar(ac)) for ac in pos_coroots) # We do a cast since the result would otherwise live in the fraction field return P(ret) elif prec is None: # If we're here, we may not be a finite crystal. # In fact, we're probably infinite. from sage.combinat.species.series import LazyPowerSeriesRing if q is None: P = LazyPowerSeriesRing(ZZ, names='q') else: P = q.parent() if not isinstance(P, LazyPowerSeriesRing): raise TypeError( "the parent of q must be a lazy power series ring") ret = P(iter_by_deg(mg)) ret.compute_coefficients(10) return ret from sage.rings.power_series_ring import PowerSeriesRing, PowerSeriesRing_generic if q is None: q = PowerSeriesRing(ZZ, 'q', default_prec=prec).gen(0) P = q.parent() ret = P.sum(c * q**deg for deg, c in enumerate(iter_by_deg(mg))) if ret.degree() == max_deg and isinstance(P, PowerSeriesRing_generic): ret = P(ret, prec) return ret
def product_space(chi, k, weights=False, base_ring=None, verbose=False): r""" Computes all eisenstein series, and products of pairs of eisenstein series of lower weight, lying in the space of modular forms of weight $k$ and nebentypus $\chi$. INPUT: - chi - Dirichlet character, the nebentypus of the target space - k - an integer, the weight of the target space OUTPUT: - a matrix of coefficients of q-expansions, which are the products of Eisenstein series in M_k(chi). WARNING: It is only for principal chi that we know that the resulting space is the whole space of modular forms. """ if weights == False: weights = srange(1, k / 2 + 1) weight_dict = {} weight_dict[-1] = [w for w in weights if w % 2] # Odd weights weight_dict[1] = [w for w in weights if not w % 2] # Even weights try: N = chi.modulus() except AttributeError: if chi.parent() == ZZ: N = chi chi = DirichletGroup(N)[0] Id = DirichletGroup(1)[0] if chi(-1) != (-1)**k: raise ValueError('chi(-1)!=(-1)^k') sturm = ModularForms(N, k).sturm_bound() + 1 if N > 1: target_dim = dimension_modular_forms(chi, k) else: target_dim = dimension_modular_forms(1, k) D = DirichletGroup(N) # product_space should ideally be called over number fields. Over complex # numbers the exact linear algebra solutions might not exist. if base_ring == None: base_ring = CyclotomicField(euler_phi(N)) Q = PowerSeriesRing(base_ring, 'q') q = Q.gen() d = len(D) prim_chars = [phi.primitive_character() for phi in D] divs = divisors(N) products = Matrix(base_ring, []) indexlist = [] rank = 0 if verbose: print(D) print('Sturm bound', sturm) #TODO: target_dim needs refinment in the case of weight 2. print('Target dimension', target_dim) for i in srange(0, d): # First character phi = prim_chars[i] M1 = phi.conductor() for j in srange(0, d): # Second character psi = prim_chars[j] M2 = psi.conductor() if not M1 * M2 in divs: continue parity = psi(-1) * phi(-1) for t1 in divs: if not M1 * M2 * t1 in divs: continue #TODO: THE NEXT CONDITION NEEDS TO BE CORRECTED. THIS IS JUST A TEST if phi.bar() == psi and not ( k == 2): #and i==0 and j==0 and t1==1): E = eisenstein_series_at_inf(phi, psi, k, sturm, t1, base_ring).padded_list() try: products.T.solve_right(vector(base_ring, E)) except ValueError: products = Matrix(products.rows() + [E]) indexlist.append([k, i, j, t1]) rank += 1 if verbose: print('Added ', [k, i, j, t1]) print('Rank is now', rank) if rank == target_dim: return products, indexlist for t in divs: if not M1 * M2 * t1 * t in divs: continue for t2 in divs: if not M1 * M2 * t1 * t2 * t in divs: continue for l in weight_dict[parity]: if l == 1 and phi.is_odd(): continue if i == 0 and j == 0 and (l == 2 or l == k - 2): continue #TODO: THE NEXT CONDITION NEEDS TO BE REMOVED. THIS IS JUST A TEST if l == 2 or l == k - 2: continue E1 = eisenstein_series_at_inf( phi, psi, l, sturm, t1 * t, base_ring) E2 = eisenstein_series_at_inf( phi**(-1), psi**(-1), k - l, sturm, t2 * t, base_ring) #If chi is non-principal this needs to be changed to be something like chi*phi^(-1) instead of phi^(-1) E = (E1 * E2 + O(q**sturm)).padded_list() try: products.T.solve_right(vector(base_ring, E)) except ValueError: products = Matrix(products.rows() + [E]) indexlist.append([l, k - l, i, j, t1, t2, t]) rank += 1 if verbose: print('Added ', [l, k - l, i, j, t1, t2, t]) print('Rank', rank) if rank == target_dim: return products, indexlist return products, indexlist
def _dimension_Gamma_2(wt_range, j, group='Gamma(2)'): """ Return the dict {(k-> partition -> [ d(k), e(k), c(k)] for k in wt_range]}, where d(k), e(k), c(k) are the dimensions of the $p$-canonical part of $M_{k,j}( \Gamma(2))$ and its subspaces of Non-cusp forms and Cusp forms. """ partitions = [ u'6', u'51', u'42', u'411', u'33', u'321', u'311', u'222', u'2211', u'21111', u'111111' ] if is_odd(j): dct = dict( (k, dict((h, [0, 0, 0]) for h in partitions)) for k in wt_range) for k in dct: dct[k]['All'] = [0, 0, 0] partitions.insert(0, 'All') return partitions, dct if 'Sp4(Z)' == group and 2 == j and wt_range[0] < 4: wt_range1 = [k for k in wt_range if k < 4] wt_range2 = [k for k in wt_range if k >= 4] # print wt_range1, wt_range2 if wt_range2 != []: headers, dct = _dimension_Gamma_2(wt_range2, j, group) else: headers, dct = ['Total', 'Non cusp', 'Cusp'], {} for k in wt_range1: dct[k] = dict([(h, 0) for h in headers]) return headers, dct if j >= 2 and wt_range[0] < 4: raise NotImplementedError( 'Dimensions of \(M_{k,j}\) for \(k<4\) and even \(j\ge 2\) not implemented' ) query = {'sym_power': str(j), 'group': 'Gamma(2)', 'space': 'total'} db_total = fetch(query) assert db_total, '%s: Data not available' % query query['space'] = 'cusp' db_cusp = fetch(query) assert db_cusp, '%s: Data not available' % query P = PowerSeriesRing(IntegerRing(), default_prec=wt_range[-1] + 1, names=('t', )) t = P.gen() total = dict() cusp = dict() for p in partitions: total[p] = eval(db_total[p]) cusp[p] = eval(db_cusp[p]) # total = dict( ( p, eval(db_total[p])) for p in partitions) # cusp = dict( ( p, eval(db_cusp[p])) for p in partitions) if 'Gamma(2)' == group: dct = dict( (k, dict((p, [total[p][k], total[p][k] - cusp[p][k], cusp[p][k]]) for p in partitions)) for k in wt_range) for k in dct: dct[k]['All'] = [ sum(dct[k][p][j] for p in dct[k]) for j in range(3) ] partitions.insert(0, 'All') headers = partitions elif 'Gamma1(2)' == group: ps = { '3': ['6', '42', '222'], '21': ['51', '42', '321'], '111': ['411', '33'] } dct = dict((k, dict((p, [ sum(total[q][k] for q in ps[p]), sum(total[q][k] - cusp[q][k] for q in ps[p]), sum(cusp[q][k] for q in ps[p]), ]) for p in ps)) for k in wt_range) for k in dct: dct[k]['All'] = [ sum(dct[k][p][j] for p in dct[k]) for j in range(3) ] headers = ps.keys() headers.sort(reverse=True) headers.insert(0, 'All') elif 'Gamma0(2)' == group: headers = ['Total', 'Non cusp', 'Cusp'] ps = ['6', '42', '222'] dct = dict((k, { 'Total': sum(total[p][k] for p in ps), 'Non cusp': sum(total[p][k] - cusp[p][k] for p in ps), 'Cusp': sum(cusp[p][k] for p in ps) }) for k in wt_range) elif 'Sp4(Z)' == group: headers = ['Total', 'Non cusp', 'Cusp'] p = '6' dct = dict((k, { 'Total': total[p][k], 'Non cusp': total[p][k] - cusp[p][k], 'Cusp': cusp[p][k] }) for k in wt_range) else: raise NotImplemetedError('Dimension for %s not implemented' % group) return headers, dct
def __getitem__(self, arg): """ Extend this ring by one or several elements to create a polynomial ring, a power series ring, or an algebraic extension. This is a convenience method intended primarily for interactive use. .. SEEALSO:: :func:`~sage.rings.polynomial.polynomial_ring_constructor.PolynomialRing`, :func:`~sage.rings.power_series_ring.PowerSeriesRing`, :meth:`~sage.rings.ring.Ring.extension`, :meth:`sage.rings.integer_ring.IntegerRing_class.__getitem__`, :meth:`sage.rings.matrix_space.MatrixSpace.__getitem__`, :meth:`sage.structure.parent.Parent.__getitem__` EXAMPLES: We create several polynomial rings:: sage: ZZ['x'] Univariate Polynomial Ring in x over Integer Ring sage: QQ['x'] Univariate Polynomial Ring in x over Rational Field sage: GF(17)['abc'] Univariate Polynomial Ring in abc over Finite Field of size 17 sage: GF(17)['a,b,c'] Multivariate Polynomial Ring in a, b, c over Finite Field of size 17 sage: GF(17)['a']['b'] Univariate Polynomial Ring in b over Univariate Polynomial Ring in a over Finite Field of size 17 We can create skew polynomial rings:: sage: k.<t> = GF(5^3) sage: Frob = k.frobenius_endomorphism() sage: k['x',Frob] Skew Polynomial Ring in x over Finite Field in t of size 5^3 twisted by t |--> t^5 We can also create power series rings by using double brackets:: sage: QQ[['t']] Power Series Ring in t over Rational Field sage: ZZ[['W']] Power Series Ring in W over Integer Ring sage: ZZ[['x,y,z']] Multivariate Power Series Ring in x, y, z over Integer Ring sage: ZZ[['x','T']] Multivariate Power Series Ring in x, T over Integer Ring Use :func:`~sage.rings.fraction_field.Frac` or :meth:`~sage.rings.ring.CommutativeRing.fraction_field` to obtain the fields of rational functions and Laurent series:: sage: Frac(QQ['t']) Fraction Field of Univariate Polynomial Ring in t over Rational Field sage: Frac(QQ[['t']]) Laurent Series Ring in t over Rational Field sage: QQ[['t']].fraction_field() Laurent Series Ring in t over Rational Field Note that the same syntax can be used to create number fields:: sage: QQ[I] Number Field in I with defining polynomial x^2 + 1 sage: QQ[sqrt(2)] Number Field in sqrt2 with defining polynomial x^2 - 2 sage: QQ[sqrt(2),sqrt(3)] Number Field in sqrt2 with defining polynomial x^2 - 2 over its base field and orders in number fields:: sage: ZZ[I] Order in Number Field in I with defining polynomial x^2 + 1 sage: ZZ[sqrt(5)] Order in Number Field in sqrt5 with defining polynomial x^2 - 5 sage: ZZ[sqrt(2)+sqrt(3)] Order in Number Field in a with defining polynomial x^4 - 10*x^2 + 1 TESTS: A few corner cases:: sage: QQ[()] Multivariate Polynomial Ring in no variables over Rational Field sage: QQ[[]] Traceback (most recent call last): ... TypeError: power series rings must have at least one variable Some flexibility is allowed when specifying variables:: sage: QQ["x", SR.var('y'), polygen(CC, 'z')] Multivariate Polynomial Ring in x, y, z over Rational Field sage: QQ[["x", SR.var('y'), polygen(CC, 'z')]] Multivariate Power Series Ring in x, y, z over Rational Field but more baroque expressions do not work:: sage: QQ['a,b','c'] Traceback (most recent call last): ... ValueError: variable name 'a,b' is not alphanumeric sage: QQ[['a,b','c']] Traceback (most recent call last): ... ValueError: variable name 'a,b' is not alphanumeric sage: QQ[[['x']]] Traceback (most recent call last): ... TypeError: expected R[...] or R[[...]], not R[[[...]]] Extension towers are built as follows and use distinct generator names:: sage: K = QQ[2^(1/3), 2^(1/2), 3^(1/3)] sage: K Number Field in a with defining polynomial x^3 - 2 over its base field sage: K.base_field() Number Field in sqrt2 with defining polynomial x^2 - 2 over its base field sage: K.base_field().base_field() Number Field in b with defining polynomial x^3 - 3 """ def normalize_arg(arg): if isinstance(arg, (tuple, list)): # Allowing arbitrary iterables would create confusion, but we # may want to support a few more. return tuple(arg) elif isinstance(arg, str): return tuple(arg.split(',')) else: return (arg, ) # 1. If arg is a list, try to return a power series ring. if isinstance(arg, list): if arg == []: raise TypeError( "power series rings must have at least one variable") elif len(arg) == 1: # R[["a,b"]], R[[(a,b)]]... if isinstance(arg[0], list): raise TypeError( "expected R[...] or R[[...]], not R[[[...]]]") elts = normalize_arg(arg[0]) else: elts = normalize_arg(arg) from sage.rings.power_series_ring import PowerSeriesRing return PowerSeriesRing(self, elts) if isinstance(arg, tuple): from sage.categories.morphism import Morphism if len(arg) == 2 and isinstance(arg[1], Morphism): from sage.rings.polynomial.skew_polynomial_ring_constructor import SkewPolynomialRing return SkewPolynomialRing(self, arg[1], names=arg[0]) # 2. Otherwise, if all specified elements are algebraic, try to # return an algebraic extension elts = normalize_arg(arg) try: minpolys = [a.minpoly() for a in elts] except (AttributeError, NotImplementedError, ValueError, TypeError): minpolys = None if minpolys: # how to pass in names? # TODO: set up embeddings names = tuple(_gen_names(elts)) try: # Doing the extension all at once is best, if possible... return self.extension(minpolys, names) except (TypeError, ValueError): # ...but we can also construct it iteratively return reduce(lambda R, ext: R.extension(*ext), zip(minpolys, names), self) # 2. Otherwise, try to return a polynomial ring from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing return PolynomialRing(self, elts)
def theta_by_cholesky(self, q_prec): r""" Uses the real Cholesky decomposition to compute (the `q`-expansion of) the theta function of the quadratic form as a power series in `q` with terms correct up to the power `q^{\text{q\_prec}}`. (So its error is `O(q^ {\text{q\_prec} + 1})`.) REFERENCE: From Cohen's "A Course in Computational Algebraic Number Theory" book, p 102. EXAMPLES:: ## Check the sum of 4 squares form against Jacobi's formula sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1]) sage: Theta = Q.theta_by_cholesky(10) sage: Theta 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 sage: Expected = [1] + [8*sum([d for d in divisors(n) if d%4 != 0]) for n in range(1,11)] sage: Expected [1, 8, 24, 32, 24, 48, 96, 64, 24, 104, 144] sage: Theta.list() == Expected True :: ## Check the form x^2 + 3y^2 + 5z^2 + 7w^2 represents everything except 2 and 22. sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Theta = Q.theta_by_cholesky(50) sage: Theta_list = Theta.list() sage: [m for m in range(len(Theta_list)) if Theta_list[m] == 0] [2, 22] """ ## RAISE AN ERROR -- This routine is deprecated! #raise NotImplementedError, "This routine is deprecated. Try theta_series(), which uses theta_by_pari()." n = self.dim() theta = [0 for i in range(q_prec + 1)] PS = PowerSeriesRing(ZZ, 'q') bit_prec = 53 ## TO DO: Set this precision to reflect the appropriate roundoff Cholesky = self.cholesky_decomposition( bit_prec ) ## error estimate, to be confident through our desired q-precision. Q = Cholesky ## <---- REDUNDANT!!! R = RealField(bit_prec) half = R(0.5) ## 1. Initialize i = n - 1 T = [R(0) for j in range(n)] U = [R(0) for j in range(n)] T[i] = R(q_prec) U[i] = 0 L = [0 for j in range(n)] x = [0 for j in range(n)] ## 2. Compute bounds #Z = sqrt(T[i] / Q[i,i]) ## IMPORTANT NOTE: sqrt preserves the precision of the real number it's given... which is not so good... =| #L[i] = floor(Z - U[i]) ## Note: This is a Sage Integer #x[i] = ceil(-Z - U[i]) - 1 ## Note: This is a Sage Integer too done_flag = False from_step4_flag = False from_step3_flag = True ## We start by pretending this, since then we get to run through 2 and 3a once. =) #double Q_val_double; #unsigned long Q_val; // WARNING: Still need a good way of checking overflow for this value... ## Big loop which runs through all vectors while (done_flag == False): ## Loop through until we get to i=1 (so we defined a vector x) while from_step3_flag or from_step4_flag: ## IMPORTANT WARNING: This replaces a do...while loop, so it may have to be adjusted! ## Go to directly to step 3 if we're coming from step 4, otherwise perform step 2. if from_step4_flag: from_step4_flag = False else: ## 2. Compute bounds from_step3_flag = False Z = sqrt(T[i] / Q[i, i]) L[i] = floor(Z - U[i]) x[i] = ceil(-Z - U[i]) - 1 ## 3a. Main loop ## DIAGNOSTIC #print #print " L = ", L #print " x = ", x x[i] += 1 while (x[i] > L[i]): ## DIAGNOSTIC #print " x = ", x i += 1 x[i] += 1 ## 3b. Main loop if (i > 0): from_step3_flag = True ## DIAGNOSTIC #print " i = " + str(i) #print " T[i] = " + str(T[i]) #print " Q[i,i] = " + str(Q[i,i]) #print " x[i] = " + str(x[i]) #print " U[i] = " + str(U[i]) #print " x[i] + U[i] = " + str(x[i] + U[i]) #print " T[i-1] = " + str(T[i-1]) T[i - 1] = T[i] - Q[i, i] * (x[i] + U[i]) * (x[i] + U[i]) # DIAGNOSTIC #print " T[i-1] = " + str(T[i-1]) #print i += -1 U[i] = 0 for j in range(i + 1, n): U[i] += Q[i, j] * x[j] ## 4. Solution found (This happens when i=0) from_step4_flag = True Q_val_double = q_prec - T[0] + Q[0, 0] * (x[0] + U[0]) * (x[0] + U[0]) Q_val = floor( Q_val_double + half ) ## Note: This rounds the value up, since the "round" function returns a float, but floor returns integer. ## DIAGNOSTIC #print " Q_val_double = ", Q_val_double #print " Q_val = ", Q_val #raise RuntimeError ## OPTIONAL SAFETY CHECK: eps = 0.000000001 if (abs(Q_val_double - Q_val) > eps): raise RuntimeError("Oh No! We have a problem with the floating point precision... \n" \ + " Q_val_double = " + str(Q_val_double) + "\n" \ + " Q_val = " + str(Q_val) + "\n" \ + " x = " + str(x) + "\n") ## DIAGNOSTIC #print " The float value is " + str(Q_val_double) #print " The associated long value is " + str(Q_val) #print if (Q_val <= q_prec): theta[Q_val] += 2 ## 5. Check if x = 0, for exit condition. =) done_flag = True for j in range(n): if (x[j] != 0): done_flag = False ## Set the value: theta[0] = 1 theta[0] = 1 ## DIAGNOSTIC #print "Leaving ComputeTheta \n" ## Return the series, truncated to the desired q-precision return PS(theta)
def eisenstein_series_qexp(k, prec=10, K=QQ, var='q', normalization='linear'): r""" Return the `q`-expansion of the normalized weight `k` Eisenstein series on `{\rm SL}_2(\ZZ)` to precision prec in the ring `K`. Three normalizations are available, depending on the parameter ``normalization``; the default normalization is the one for which the linear coefficient is 1. INPUT: - ``k`` - an even positive integer - ``prec`` - (default: 10) a nonnegative integer - ``K`` - (default: `\QQ`) a ring - ``var`` - (default: ``'q'``) variable name to use for q-expansion - ``normalization`` - (default: ``'linear'``) normalization to use. If this is ``'linear'``, then the series will be normalized so that the linear term is 1. If it is ``'constant'``, the series will be normalized to have constant term 1. If it is ``'integral'``, then the series will be normalized to have integer coefficients and no common factor, and linear term that is positive. Note that ``'integral'`` will work over arbitrary base rings, while ``'linear'`` or ``'constant'`` will fail if the denominator (resp. numerator) of `B_k / 2k` is invertible. ALGORITHM: We know `E_k = \text{constant} + \sum_n \sigma_{k-1}(n) q^n`. So we compute all the `\sigma_{k-1}(n)` simultaneously, using the fact that `\sigma` is multiplicative. EXAMPLES:: sage: eisenstein_series_qexp(2,5) -1/24 + q + 3*q^2 + 4*q^3 + 7*q^4 + O(q^5) sage: eisenstein_series_qexp(2,0) O(q^0) sage: eisenstein_series_qexp(2,5,GF(7)) 2 + q + 3*q^2 + 4*q^3 + O(q^5) sage: eisenstein_series_qexp(2,5,GF(7),var='T') 2 + T + 3*T^2 + 4*T^3 + O(T^5) We illustrate the use of the ``normalization`` parameter:: sage: eisenstein_series_qexp(12, 5, normalization='integral') 691 + 65520*q + 134250480*q^2 + 11606736960*q^3 + 274945048560*q^4 + O(q^5) sage: eisenstein_series_qexp(12, 5, normalization='constant') 1 + 65520/691*q + 134250480/691*q^2 + 11606736960/691*q^3 + 274945048560/691*q^4 + O(q^5) sage: eisenstein_series_qexp(12, 5, normalization='linear') 691/65520 + q + 2049*q^2 + 177148*q^3 + 4196353*q^4 + O(q^5) sage: eisenstein_series_qexp(12, 50, K=GF(13), normalization="constant") 1 + O(q^50) TESTS: Test that :trac:`5102` is fixed:: sage: eisenstein_series_qexp(10, 30, GF(17)) 15 + q + 3*q^2 + 15*q^3 + 7*q^4 + 13*q^5 + 11*q^6 + 11*q^7 + 15*q^8 + 7*q^9 + 5*q^10 + 7*q^11 + 3*q^12 + 14*q^13 + 16*q^14 + 8*q^15 + 14*q^16 + q^17 + 4*q^18 + 3*q^19 + 6*q^20 + 12*q^21 + 4*q^22 + 12*q^23 + 4*q^24 + 4*q^25 + 8*q^26 + 14*q^27 + 9*q^28 + 6*q^29 + O(q^30) This shows that the bug reported at :trac:`8291` is fixed:: sage: eisenstein_series_qexp(26, 10, GF(13)) 7 + q + 3*q^2 + 4*q^3 + 7*q^4 + 6*q^5 + 12*q^6 + 8*q^7 + 2*q^8 + O(q^10) We check that the function behaves properly over finite-characteristic base rings:: sage: eisenstein_series_qexp(12, 5, K = Zmod(691), normalization="integral") 566*q + 236*q^2 + 286*q^3 + 194*q^4 + O(q^5) sage: eisenstein_series_qexp(12, 5, K = Zmod(691), normalization="constant") Traceback (most recent call last): ... ValueError: The numerator of -B_k/(2*k) (=691) must be invertible in the ring Ring of integers modulo 691 sage: eisenstein_series_qexp(12, 5, K = Zmod(691), normalization="linear") q + 667*q^2 + 252*q^3 + 601*q^4 + O(q^5) sage: eisenstein_series_qexp(12, 5, K = Zmod(2), normalization="integral") 1 + O(q^5) sage: eisenstein_series_qexp(12, 5, K = Zmod(2), normalization="constant") 1 + O(q^5) sage: eisenstein_series_qexp(12, 5, K = Zmod(2), normalization="linear") Traceback (most recent call last): ... ValueError: The denominator of -B_k/(2*k) (=65520) must be invertible in the ring Ring of integers modulo 2 AUTHORS: - William Stein: original implementation - Craig Citro (2007-06-01): rewrote for massive speedup - Martin Raum (2009-08-02): port to cython for speedup - David Loeffler (2010-04-07): work around an integer overflow when `k` is large - David Loeffler (2012-03-15): add options for alternative normalizations (motivated by :trac:`12043`) """ ## we use this to prevent computation if it would fail anyway. if k <= 0 or k % 2 == 1: raise ValueError("k must be positive and even") a0 = -bernoulli(k) / (2 * k) if normalization == 'linear': a0den = a0.denominator() try: a0fac = K(1 / a0den) except ZeroDivisionError: raise ValueError( "The denominator of -B_k/(2*k) (=%s) must be invertible in the ring %s" % (a0den, K)) elif normalization == 'constant': a0num = a0.numerator() try: a0fac = K(1 / a0num) except ZeroDivisionError: raise ValueError( "The numerator of -B_k/(2*k) (=%s) must be invertible in the ring %s" % (a0num, K)) elif normalization == 'integral': a0fac = None else: raise ValueError( "Normalization (=%s) must be one of 'linear', 'constant', 'integral'" % normalization) R = PowerSeriesRing(K, var) if K == QQ and normalization == 'linear': ls = Ek_ZZ(k, prec) # The following is *dramatically* faster than doing the more natural # "R(ls)" would be: E = ZZ[var](ls, prec=prec, check=False).change_ring(QQ) if len(ls) > 0: E._unsafe_mutate(0, a0) return R(E, prec) # The following is an older slower alternative to the above three lines: #return a0fac*R(eisenstein_series_poly(k, prec).list(), prec=prec, check=False) else: # This used to work with check=False, but that can only be regarded as # an improbable lucky miracle. Enabling checking is a noticeable speed # regression; the morally right fix would be to expose FLINT's # fmpz_poly_to_nmod_poly command (at least for word-sized N). if a0fac is not None: return a0fac * R( eisenstein_series_poly(k, prec).list(), prec=prec, check=True) else: return R(eisenstein_series_poly(k, prec).list(), prec=prec, check=True)
def _wronskian_adjoint(self, weight_parity=0, p=None): r""" The matrix `W^\# \pmod{p}`, mentioned on page 142 of Nils Skoruppa's thesis. This matrix is represented by a list of lists of q-expansions. The q-expansion is shifted by `-(m + 1) (2*m + 1) / 24` in the case of even weights, and it is shifted by `-(m - 1) (2*m - 3) / 24` otherwise. This is compensated by the missing q-powers returned by _wronskian_invdeterminant. INPUT: - `p` -- A prime or ``None``. - ``weight_parity`` -- An integer (default: `0`). """ try: if weight_parity % 2 == 0: wronskian_adjoint = self.__wronskian_adjoint_even else: wronskian_adjoint = self.__wronskian_adjoint_ood if p is None: return wronskian_adjoint else: P = PowerSeriesRing(GF(p), 'q') return [map(P, row) for row in wronskian_adjoint] except AttributeError: qexp_prec = self._qexp_precision() if p is None: PS = self.integral_power_series_ring() else: PS = PowerSeriesRing(GF(p), 'q') m = self.jacobi_index() twom = 2 * m frmsq = twom**2 thetas = dict( ((i, j), dict()) for i in xrange(m + 1) for j in xrange(m + 1)) ## We want to calculate \hat \theta_{j,l} = sum_r (2 m r + j)**2l q**(m r**2 + j r) ## in the case of even weight, and \hat \theta_{j,l} = sum_r (2 m r + j)**(2l + 1) q**(m r**2 + j r), ## otherwise. for r in xrange(isqrt((qexp_prec - 1 + m) // m) + 2): for j in (xrange(m + 1) if weight_parity % 2 == 0 else range(1, m)): fact_p = (twom * r + j)**2 fact_m = (twom * r - j)**2 if weight_parity % 2 == 0: coeff_p = 2 coeff_m = 2 else: coeff_p = 2 * (twom * r + j) coeff_m = -2 * (twom * r - j) for l in (xrange(m + 1) if weight_parity % 2 == 0 else range(1, m)): thetas[(j, l)][m * r**2 + r * j] = coeff_p thetas[(j, l)][m * r**2 - r * j] = coeff_m coeff_p = coeff_p * fact_p coeff_m = coeff_m * fact_m if weight_parity % 2 == 0: thetas[(0, 0)][0] = 1 thetas = dict((k, PS(th).add_bigoh(qexp_prec)) for (k, th) in thetas.iteritems()) W = matrix(PS, m + 1 if weight_parity % 2 == 0 else (m - 1), [ thetas[(j, l)] for j in (xrange(m + 1) if weight_parity % 2 == 0 else range(1, m)) for l in (xrange(m + 1) if weight_parity % 2 == 0 else range(1, m)) ]) ## Since the adjoint of matrices with entries in a general ring ## is extremely slow for matrices of small size, we hard code the ## the cases `m = 2` and `m = 3`. The expressions are obtained by ## computing the adjoint of a matrix with entries `w_{i,j}` in a ## polynomial algebra. if W.nrows() == 1: Wadj = matrix(PS, [[1]]) elif W.nrows() == 2: Wadj = matrix(PS, [[W[1, 1], -W[0, 1]], [-W[1, 0], W[0, 0]]]) elif W.nrows() == 3 and qexp_prec > 10**5: adj00 = W[1, 1] * W[2, 2] - W[2, 1] * W[1, 2] adj01 = -W[1, 0] * W[2, 2] + W[2, 0] * W[1, 2] adj02 = W[1, 0] * W[2, 1] - W[2, 0] * W[1, 1] adj10 = -W[0, 1] * W[2, 2] + W[2, 1] * W[0, 2] adj11 = W[0, 0] * W[2, 2] - W[2, 0] * W[0, 2] adj12 = -W[0, 0] * W[2, 1] + W[2, 0] * W[0, 1] adj20 = W[0, 1] * W[1, 2] - W[1, 1] * W[0, 2] adj21 = -W[0, 0] * W[1, 2] + W[1, 0] * W[0, 2] adj22 = W[0, 0] * W[1, 1] - W[1, 0] * W[0, 1] Wadj = matrix(PS, [[adj00, adj01, adj02], [adj10, adj11, adj12], [adj20, adj21, adj22]]) elif W.nrows() == 4 and qexp_prec > 10**5: adj00 = -W[0, 2] * W[1, 1] * W[2, 0] + W[0, 1] * W[1, 2] * W[ 2, 0] + W[0, 2] * W[1, 0] * W[2, 1] - W[0, 0] * W[ 1, 2] * W[2, 1] - W[0, 1] * W[1, 0] * W[2, 2] + W[ 0, 0] * W[1, 1] * W[2, 2] adj01 = -W[0, 3] * W[1, 1] * W[2, 0] + W[0, 1] * W[1, 3] * W[ 2, 0] + W[0, 3] * W[1, 0] * W[2, 1] - W[0, 0] * W[ 1, 3] * W[2, 1] - W[0, 1] * W[1, 0] * W[2, 3] + W[ 0, 0] * W[1, 1] * W[2, 3] adj02 = -W[0, 3] * W[1, 2] * W[2, 0] + W[0, 2] * W[1, 3] * W[ 2, 0] + W[0, 3] * W[1, 0] * W[2, 2] - W[0, 0] * W[ 1, 3] * W[2, 2] - W[0, 2] * W[1, 0] * W[2, 3] + W[ 0, 0] * W[1, 2] * W[2, 3] adj03 = -W[0, 3] * W[1, 2] * W[2, 1] + W[0, 2] * W[1, 3] * W[ 2, 1] + W[0, 3] * W[1, 1] * W[2, 2] - W[0, 1] * W[ 1, 3] * W[2, 2] - W[0, 2] * W[1, 1] * W[2, 3] + W[ 0, 1] * W[1, 2] * W[2, 3] adj10 = -W[0, 2] * W[1, 1] * W[3, 0] + W[0, 1] * W[1, 2] * W[ 3, 0] + W[0, 2] * W[1, 0] * W[3, 1] - W[0, 0] * W[ 1, 2] * W[3, 1] - W[0, 1] * W[1, 0] * W[3, 2] + W[ 0, 0] * W[1, 1] * W[3, 2] adj11 = -W[0, 3] * W[1, 1] * W[3, 0] + W[0, 1] * W[1, 3] * W[ 3, 0] + W[0, 3] * W[1, 0] * W[3, 1] - W[0, 0] * W[ 1, 3] * W[3, 1] - W[0, 1] * W[1, 0] * W[3, 3] + W[ 0, 0] * W[1, 1] * W[3, 3] adj12 = -W[0, 3] * W[1, 2] * W[3, 0] + W[0, 2] * W[1, 3] * W[ 3, 0] + W[0, 3] * W[1, 0] * W[3, 2] - W[0, 0] * W[ 1, 3] * W[3, 2] - W[0, 2] * W[1, 0] * W[3, 3] + W[ 0, 0] * W[1, 2] * W[3, 3] adj13 = -W[0, 3] * W[1, 2] * W[3, 1] + W[0, 2] * W[1, 3] * W[ 3, 1] + W[0, 3] * W[1, 1] * W[3, 2] - W[0, 1] * W[ 1, 3] * W[3, 2] - W[0, 2] * W[1, 1] * W[3, 3] + W[ 0, 1] * W[1, 2] * W[3, 3] adj20 = -W[0, 2] * W[2, 1] * W[3, 0] + W[0, 1] * W[2, 2] * W[ 3, 0] + W[0, 2] * W[2, 0] * W[3, 1] - W[0, 0] * W[ 2, 2] * W[3, 1] - W[0, 1] * W[2, 0] * W[3, 2] + W[ 0, 0] * W[2, 1] * W[3, 2] adj21 = -W[0, 3] * W[2, 1] * W[3, 0] + W[0, 1] * W[2, 3] * W[ 3, 0] + W[0, 3] * W[2, 0] * W[3, 1] - W[0, 0] * W[ 2, 3] * W[3, 1] - W[0, 1] * W[2, 0] * W[3, 3] + W[ 0, 0] * W[2, 1] * W[3, 3] adj22 = -W[0, 3] * W[2, 2] * W[3, 0] + W[0, 2] * W[2, 3] * W[ 3, 0] + W[0, 3] * W[2, 0] * W[3, 2] - W[0, 0] * W[ 2, 3] * W[3, 2] - W[0, 2] * W[2, 0] * W[3, 3] + W[ 0, 0] * W[2, 2] * W[3, 3] adj23 = -W[0, 3] * W[2, 2] * W[3, 1] + W[0, 2] * W[2, 3] * W[ 3, 1] + W[0, 3] * W[2, 1] * W[3, 2] - W[0, 1] * W[ 2, 3] * W[3, 2] - W[0, 2] * W[2, 1] * W[3, 3] + W[ 0, 1] * W[2, 2] * W[3, 3] adj30 = -W[1, 2] * W[2, 1] * W[3, 0] + W[1, 1] * W[2, 2] * W[ 3, 0] + W[1, 2] * W[2, 0] * W[3, 1] - W[1, 0] * W[ 2, 2] * W[3, 1] - W[1, 1] * W[2, 0] * W[3, 2] + W[ 1, 0] * W[2, 1] * W[3, 2] adj31 = -W[1, 3] * W[2, 1] * W[3, 0] + W[1, 1] * W[2, 3] * W[ 3, 0] + W[1, 3] * W[2, 0] * W[3, 1] - W[1, 0] * W[ 2, 3] * W[3, 1] - W[1, 1] * W[2, 0] * W[3, 3] + W[ 1, 0] * W[2, 1] * W[3, 3] adj32 = -W[1, 3] * W[2, 2] * W[3, 0] + W[1, 2] * W[2, 3] * W[ 3, 0] + W[1, 3] * W[2, 0] * W[3, 2] - W[1, 0] * W[ 2, 3] * W[3, 2] - W[1, 2] * W[2, 0] * W[3, 3] + W[ 1, 0] * W[2, 2] * W[3, 3] adj33 = -W[1, 3] * W[2, 2] * W[3, 1] + W[1, 2] * W[2, 3] * W[ 3, 1] + W[1, 3] * W[2, 1] * W[3, 2] - W[1, 1] * W[ 2, 3] * W[3, 2] - W[1, 2] * W[2, 1] * W[3, 3] + W[ 1, 1] * W[2, 2] * W[3, 3] Wadj = matrix(PS, [[adj00, adj01, adj02, adj03], [adj10, adj11, adj12, adj13], [adj20, adj21, adj22, adj23], [adj30, adj31, adj32, adj33]]) else: Wadj = W.adjoint() if weight_parity % 2 == 0: wronskian_adjoint = [[Wadj[i, r] for i in xrange(m + 1)] for r in xrange(m + 1)] else: wronskian_adjoint = [[Wadj[i, r] for i in xrange(m - 1)] for r in xrange(m - 1)] if p is None: if weight_parity % 2 == 0: self.__wronskian_adjoint_even = wronskian_adjoint else: self.__wronskian_adjoint_odd = wronskian_adjoint return wronskian_adjoint