def local_coordinates_at_nonweierstrass(self, P, prec=20, name='t'): """ For a non-Weierstrass point `P = (a,b)` on the hyperelliptic curve `y^2 = f(x)`, return `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`, where `t = x - a` is the local parameter. INPUT: - ``P = (a, b)`` -- a non-Weierstrass point on self - ``prec`` -- desired precision of the local coordinates - ``name`` -- gen of the power series ring (default: ``t``) OUTPUT: `(x(t),y(t))` such that `y(t)^2 = f(x(t))` and `t = x - a` is the local parameter at `P` EXAMPLES:: sage: R.<x> = QQ['x'] sage: H = HyperellipticCurve(x^5-23*x^3+18*x^2+40*x) sage: P = H(1,6) sage: x,y = H.local_coordinates_at_nonweierstrass(P,prec=5) sage: x 1 + t + O(t^5) sage: y 6 + t - 7/2*t^2 - 1/2*t^3 - 25/48*t^4 + O(t^5) sage: Q = H(-2,12) sage: x,y = H.local_coordinates_at_nonweierstrass(Q,prec=5) sage: x -2 + t + O(t^5) sage: y 12 - 19/2*t - 19/32*t^2 + 61/256*t^3 - 5965/24576*t^4 + O(t^5) AUTHOR: - Jennifer Balakrishnan (2007-12) """ d = P[1] if d == 0: raise TypeError( "P = %s is a Weierstrass point. Use local_coordinates_at_weierstrass instead!" % P) pol = self.hyperelliptic_polynomials()[0] L = PowerSeriesRing(self.base_ring(), name) t = L.gen() L.set_default_prec(prec) K = PowerSeriesRing(L, 'x') pol = K(pol) x = K.gen() b = P[0] f = pol(t + b) for i in range((RR(log(prec) / log(2))).ceil()): d = (d + f / d) / 2 return t + b + O(t**(prec)), d + O(t**(prec))
def local_coordinates_at_nonweierstrass(self, P, prec=20, name='t'): """ For a non-Weierstrass point `P = (a,b)` on the hyperelliptic curve `y^2 = f(x)`, return `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`, where `t = x - a` is the local parameter. INPUT: - ``P = (a, b)`` -- a non-Weierstrass point on self - ``prec`` -- desired precision of the local coordinates - ``name`` -- gen of the power series ring (default: ``t``) OUTPUT: `(x(t),y(t))` such that `y(t)^2 = f(x(t))` and `t = x - a` is the local parameter at `P` EXAMPLES:: sage: R.<x> = QQ['x'] sage: H = HyperellipticCurve(x^5-23*x^3+18*x^2+40*x) sage: P = H(1,6) sage: x,y = H.local_coordinates_at_nonweierstrass(P,prec=5) sage: x 1 + t + O(t^5) sage: y 6 + t - 7/2*t^2 - 1/2*t^3 - 25/48*t^4 + O(t^5) sage: Q = H(-2,12) sage: x,y = H.local_coordinates_at_nonweierstrass(Q,prec=5) sage: x -2 + t + O(t^5) sage: y 12 - 19/2*t - 19/32*t^2 + 61/256*t^3 - 5965/24576*t^4 + O(t^5) AUTHOR: - Jennifer Balakrishnan (2007-12) """ d = P[1] if d == 0: raise TypeError("P = %s is a Weierstrass point. Use local_coordinates_at_weierstrass instead!"%P) pol = self.hyperelliptic_polynomials()[0] L = PowerSeriesRing(self.base_ring(), name) t = L.gen() L.set_default_prec(prec) K = PowerSeriesRing(L, 'x') pol = K(pol) x = K.gen() b = P[0] f = pol(t+b) for i in range((RR(log(prec)/log(2))).ceil()): d = (d + f/d)/2 return t+b+O(t**(prec)), d + O(t**(prec))
def get_action_data(self, g, K=None): a, b, c, d = g.list() prec = self._prec if K is None: if hasattr(a, 'lift'): a, b, c, d = a.lift(), b.lift(), c.lift(), d.lift() p = g.parent().base_ring().prime() K = ZpCA(p, prec) else: K = g.parent().base_ring() Ps = PowerSeriesRing(K, 't', default_prec=prec) z = Ps.gen() zz = (d * z - b) / (-c * z + a) zz_ps0 = Ps(zz).add_bigoh(prec) if self._dlog: zz_ps = ((a * d - b * c) * (-c * z + a)**-2).add_bigoh(prec) else: zz_ps = Ps(1).add_bigoh(prec) if self.is_additive(): M = Matrix(ZZ, prec, prec, 0) for j in range(prec): for i, aij in enumerate(zz_ps.list()): M[i, j] = aij if j < prec - 1: # Don't need the last multiplication zz_ps = (zz_ps0 * zz_ps).add_bigoh(prec) else: return M else: ans = [Ps(1), zz_ps] for _ in range(prec - 1): zz_ps = (zz_ps0 * zz_ps).add_bigoh(prec) ans.append(zz_ps) return ans
def eis_H(a, b, N, k, Q=None, t=1, prec=10): if Q == None: Q = PowerSeriesRing(CyclotomicField(N), 'q') R = Q.base_ring() zetaN = R.zeta(N) q = Q.gen() a = ZZ(a % N) b = ZZ(b % N) s = 0 if k == 1: if a == 0 and not b == 0: s = -QQ(1) / QQ(2) * (1 + zetaN**b) / (1 - zetaN**b) elif b == 0 and not a == 0: s = -QQ(1) / QQ(2) * (1 + zetaN**a) / (1 - zetaN**a) elif a != 0 and b != 0: s = -QQ(1) / QQ(2) * ((1 + zetaN**a) / (1 - zetaN**a) + (1 + zetaN**b) / (1 - zetaN**b)) elif k > 1: s = hurwitz_hat(-b, N, 1 - k, zetaN) for m in srange(1, prec / t): for n in srange(1, prec / t / m + 1): s += (zetaN**(-a * m - b * n) + (-1)**k * zetaN**(a * m + b * n)) * n**(k - 1) * q**(m * n) return s + O(q**floor(prec))
def _test__jacobi_predicted_taylor_coefficients(fs, q_precision) : r""" Given a list of power series, which are the corrected Taylor coefficients of a Jacobi form, return the renormalized uncorrected ones, assuming that all but one `f` vanish. INPUT: - ``fs`` -- A list of power series. - ``q_precision`` -- An integer. OUPUT: - A list of power series. TESTS: See jacobi_form_by_taylor_expansion. """ from sage.rings.arith import gcd R = PowerSeriesRing(ZZ, 'q'); q = R.gen(0) diff = lambda f: f.derivative().shift(1) normalize = lambda f: f / gcd(f.list()) if f != 0 else f diffnorm = lambda f,l: normalize(reduce(lambda a, g: g(a), l*[diff], f)) taylor_coefficients = list() allf = R(0) for f in fs : allf = f(q_precision) + diffnorm(allf, 1) taylor_coefficients.append(allf) return taylor_coefficients
def pad(self, target_param_level): """ When constructed the q-expansions are in the default parameter $q_N$, where $N$ is the level of the form. When taking products, the parameters should be in $q_{N'}$, where $N'$ is the target level. This helper function peforms that renormalization by padding with zeros. """ try: assert (target_param_level % self.param_level == 0) except AssertionError: print( "target parameter level should be a multiple of current parameter level" ) return shift = int(target_param_level / self.param_level) R = self.series.base_ring() Q = PowerSeriesRing(R, 'q' + str(target_param_level)) qN = Q.gen() s = 0 for i in range(self.series.prec() * shift): if i % shift == 0: s += self.series[int(i / shift)] * qN**i else: s += 0 * qN**i s += O(qN**(self.series.prec() * shift + shift - 1)) self.series = s self.param_level = target_param_level
def form_acting_matrix_on_dist(p,M,k,a,b,c,d): """forms a large M x M matrix say G such that if v is the vector of moments of a distribution mu, then v*G is the vector of moments of mu|[a,b;c,d]""" # print("Checking...") # print(a,b,c,d) # print(p) assert (a%p != 0) and (c%p == 0), "acting by bad matrix" R=PowerSeriesRing(QQ,'y',default_prec=M) y=R.gen() scale=(b+d*y)/(a+c*y) t=((a+c*y)**k).truncate(M) A = [] for i in range(0,M): temp1=t.list(); d=len(temp1) for j in range(d,M): temp1 = temp1 + [0] #while len(temp1)>M: # temp1.pop() A = A + [temp1] t=(t*scale).truncate(M) q=p**M B=Matrix(QQ,A).transpose() for r in range(0,M): for c in range(0,M): #B[r,c]=B[r,c]%(p**(M-c)) B[r,c]=B[r,c]%(q) return B
def eis_G(a, b, N, k, Q=None, t=1, prec=20): if Q == None: Q = PowerSeriesRing(QQ, 'q') R = Q.base_ring() q = Q.gen() a = ZZ(a % N) b = ZZ(b % N) s = 0 if k == 1: if a == 0 and not b == 0: s = QQ(1) / QQ(2) - QQ(b % N) / QQ(N) elif b == 0 and not a == 0: s = QQ(1) / QQ(2) - QQ(a % N) / QQ(N) elif k > 1: if b == 0: s = -N**(k - 1) * ber_pol(QQ(a % N) / QQ(N), k) / QQ(k) #If a == 0 or b ==0 the loop has to start at 1 starta, startb = 0, 0 if a == 0: starta = 1 if b == 0: startb = 1 for v in srange(starta, (prec / t + a) / N): for w in srange(startb, (prec / t / abs((-a + v * N)) + b) / N + 1): s += q**(t * (a + v * N) * (b + w * N)) * (a + v * N)**(k - 1) if (-a + v * N) > 0 and (-b + w * N) > 0: s += (-1)**k * q**(t * (-a + v * N) * (-b + w * N)) * (-a + v * N)**(k - 1) return s + O(q**floor(prec))
def _all_weak_taylor_coefficients(weight, index) : r""" A product basis of the echelon bases of - `M_k, M_{k + 2}, ..., M_{k + 2 m}` etc. if ``weight`` is even, - `M_{k + 1}, ..., M_{k + 2 m - 3}` if ``weight`` is odd. INPUT: - ``weight`` -- An integer. - ``index`` -- A non-negative integer. TESTS:: sage: from psage.modform.jacobiforms.jacobiformd1nn_fegenerators import _all_weak_taylor_coefficients sage: _all_weak_taylor_coefficients(12, 1) [[<bound method ModularFormElement.qexp of 1 + 196560*q^2 + 16773120*q^3 + 398034000*q^4 + 4629381120*q^5 + O(q^6)>, <function <lambda> at ...>], [<bound method ModularFormElement.qexp of q - 24*q^2 + 252*q^3 - 1472*q^4 + 4830*q^5 + O(q^6)>, <function <lambda> at ...>], [<function <lambda> at ...>, <bound method ModularFormElement.qexp of 1 - 24*q - 196632*q^2 - 38263776*q^3 - 1610809368*q^4 - 29296875024*q^5 + O(q^6)>]] """ R = PowerSeriesRing(ZZ, 'q'); q = R.gen() if weight % 2 == 0 : nmb_modular_forms = index + 1 start_weight = weight else : nmb_modular_forms = index - 1 start_weight = weight + 1 modular_forms = list() for (i,k) in enumerate(range(start_weight, start_weight + 2 * nmb_modular_forms, 2)) : modular_forms += [ [lambda p: big_oh.O(q**p) for _ in range(i)] + [b.qexp] + [lambda p: big_oh.O(q**p) for _ in range(nmb_modular_forms - 1 - i)] for b in ModularForms(1, k).echelon_basis() ] return modular_forms
def eis_phipsi(phi, psi, k, prec=10, t=1, cmplx=False): r""" Return Fourier expansion of Eisenstein series at the cusp oo. INPUT: - ``phi`` -- Dirichlet character. - ``psi`` -- Dirichlet character. - ``k`` -- integer, the weight of the Eistenstein series. - ``prec`` -- integer (default: 10). - ``t`` -- integer (default: 1). OUTPUT: The Fourier expansion of the Eisenstein series $E_k^{\phi,\psi, t}$ (as defined by [Diamond-Shurman]). EXAMPLES: sage: phi = DirichletGroup(3)[1] sage: psi = DirichletGroup(5)[1] sage: E = eisenstein_series_at_inf(phi, psi, 4) """ N1, N2 = phi.level(), psi.level() N = N1 * N2 #The Fourier expansion of the Eisenstein series at infinity is in the field Q(zeta_Ncyc) Ncyc = lcm([euler_phi(N1), euler_phi(N2)]) if cmplx == True: CC = ComplexField(53) pi = ComplexField().pi() I = ComplexField().gen() R = CC zeta = CC(exp(2 * pi * I / Ncyc)) else: R = CyclotomicField(Ncyc) zeta = R.zeta(Ncyc) phi, psi = phi.base_extend(R), psi.base_extend(R) Q = PowerSeriesRing(R, 'q') q = Q.gen() s = O(q**prec) #Weight 2 with trivial characters is calculated separately if k == 2 and phi.conductor() == 1 and psi.conductor() == 1: if t == 1: raise TypeError('E_2 is not a modular form.') s = 1 / 24 * (t - 1) for m in srange(1, prec): for n in srange(1, prec / m + 1): s += n * (q**(m * n) - t * q**(m * n * t)) return s + O(q**prec) if psi.level() == 1 and k == 1: s -= phi.bernoulli(k) / k elif phi.level() == 1: s -= psi.bernoulli(k) / k for m in srange(1, prec / t): for n in srange(1, prec / t / m + 1): s += 2 * phi(m) * psi(n) * n**(k - 1) * q**(m * n * t) return s + O(q**prec)
def _repr_(self): r""" Returns the representation of self as a string. """ R = PowerSeriesRing(self._parent._R,default_prec=self._depth,name='z') z = R.gen() s = str(sum([R(self._val[ii,0]*z**ii) for ii in range(self._depth)])) return s
def _sa_coefficients_lambda_(K, beta=0): r""" Return the coefficients `\lambda_{k, \ell}(\beta)` used in singularity analysis. INPUT: - ``K`` -- an integer. - ``beta`` -- (default: `0`) the order of the logarithmic singularity. OUTPUT: A dictionary mapping pairs of indices to rationals. .. SEEALSO:: :meth:`~AsymptoticExpansionGenerators.SingularityAnalysis` TESTS:: sage: from sage.rings.asymptotic.asymptotic_expansion_generators \ ....: import _sa_coefficients_lambda_ sage: _sa_coefficients_lambda_(3) {(0, 0): 1, (1, 1): -1, (1, 2): 1/2, (2, 2): 1, (2, 3): -5/6, (2, 4): 1/8, (3, 3): -1, (3, 4): 13/12, (4, 4): 1} sage: _sa_coefficients_lambda_(3, beta=1) {(0, 0): 1, (1, 1): -2, (1, 2): 1/2, (2, 2): 3, (2, 3): -4/3, (2, 4): 1/8, (3, 3): -4, (3, 4): 29/12, (4, 4): 5} """ from sage.rings.laurent_series_ring import LaurentSeriesRing from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.rational_field import QQ V = LaurentSeriesRing(QQ, names='v', default_prec=K) v = V.gen() T = PowerSeriesRing(V, names='t', default_prec=2*K-1) t = T.gen() S = (t - (1+1/v+beta) * (1+v*t).log()).exp() return dict(((k + L.valuation(), ell), c) for ell, L in enumerate(S.list()) for k, c in enumerate(L.list()))
def _compute_acting_matrix(self, g, M): r""" INPUT: - ``g`` -- an instance of :class:`sage.matrices.matrix_integer_2x2.Matrix_integer_2x2` or :class:`sage.matrix.matrix_generic_dense.Matrix_generic_dense` - ``M`` -- a positive integer giving the precision at which ``g`` should act. OUTPUT: - EXAMPLES:: sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk """ #tim = verbose("Starting") a, b, c, d = self._adjuster(g) # if g.parent().base_ring().is_exact(): # self._check_mat(a, b, c, d) k = self._k if g.parent().base_ring() is ZZ: if self._symk: base_ring = QQ else: base_ring = Zmod(self._p**M) else: base_ring = self.underlying_set().base_ring() #cdef Matrix B = matrix(base_ring,M,M) B = matrix(base_ring,M,M) # if M == 0: return B.change_ring(self.codomain().base_ring()) R = PowerSeriesRing(base_ring, 'y', default_prec = M) y = R.gen() #tim = verbose("Checked, made R",tim) # special case for small precision, large weight scale = (b+d*y)/(a+c*y) t = (a+c*y)**k # will already have precision M #cdef long row, col # #tim = verbose("Made matrix",tim) for col in range(M): for row in range(M): B.set_unsafe(row, col, t[row]) t *= scale #verbose("Finished loop",tim) # the changering here is annoying, but otherwise we have to change ring each time we multiply B = B.change_ring(self.codomain().base_ring()) if self._character is not None: B *= self._character(a) if self._dettwist is not None: B *= (a*d - b*c)**(self._dettwist) return B
def _sa_coefficients_lambda_(K, beta=0): r""" Return the coefficients `\lambda_{k, \ell}(\beta)` used in singularity analysis. INPUT: - ``K`` -- an integer. - ``beta`` -- (default: `0`) the order of the logarithmic singularity. OUTPUT: A dictionary mapping pairs of indices to rationals. .. SEEALSO:: :meth:`~AsymptoticExpansionGenerators.SingularityAnalysis` TESTS:: sage: from sage.rings.asymptotic.asymptotic_expansion_generators \ ....: import _sa_coefficients_lambda_ sage: _sa_coefficients_lambda_(3) {(0, 0): 1, (1, 1): -1, (1, 2): 1/2, (2, 2): 1, (2, 3): -5/6, (2, 4): 1/8, (3, 3): -1, (3, 4): 13/12, (4, 4): 1} sage: _sa_coefficients_lambda_(3, beta=1) {(0, 0): 1, (1, 1): -2, (1, 2): 1/2, (2, 2): 3, (2, 3): -4/3, (2, 4): 1/8, (3, 3): -4, (3, 4): 29/12, (4, 4): 5} """ from sage.rings.laurent_series_ring import LaurentSeriesRing from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.rational_field import QQ V = LaurentSeriesRing(QQ, names='v', default_prec=K) v = V.gen() T = PowerSeriesRing(V, names='t', default_prec=2 * K - 1) t = T.gen() S = (t - (1 + 1 / v + beta) * (1 + v * t).log()).exp() return dict(((k + L.valuation(), ell), c) for ell, L in enumerate(S.list()) for k, c in enumerate(L.list()))
def faithful_generating_function(normal_subgroups, character_dims): R = PowerSeriesRing(ZZ, 'x', 200) ## FIX ME! x = R.gen() f = R(0) for N, Ndata in normal_subgroups.items(): count, quotient, mobius = Ndata f += count * mobius * prod( [(1 - x**d).inverse_of_unit()**m for (d, m) in character_dims[quotient].items()]) return f
def local_coordinates_at_weierstrass(self, P, prec=20, name='t'): """ For a finite Weierstrass point on the hyperelliptic curve `y^2 = f(x)`, returns `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`, where `t = y` is the local parameter. INPUT: - ``P`` -- a finite Weierstrass point on self - ``prec`` -- desired precision of the local coordinates - ``name`` -- gen of the power series ring (default: `t`) OUTPUT: `(x(t),y(t))` such that `y(t)^2 = f(x(t))` and `t = y` is the local parameter at `P` EXAMPLES:: sage: R.<x> = QQ['x'] sage: H = HyperellipticCurve(x^5-23*x^3+18*x^2+40*x) sage: A = H(4, 0) sage: x, y = H.local_coordinates_at_weierstrass(A, prec=7) sage: x 4 + 1/360*t^2 - 191/23328000*t^4 + 7579/188956800000*t^6 + O(t^7) sage: y t + O(t^7) sage: B = H(-5, 0) sage: x, y = H.local_coordinates_at_weierstrass(B, prec=5) sage: x -5 + 1/1260*t^2 + 887/2000376000*t^4 + O(t^5) sage: y t + O(t^5) AUTHOR: - Jennifer Balakrishnan (2007-12) - Francis Clarke (2012-08-26) """ if P[1] != 0: raise TypeError( "P = %s is not a finite Weierstrass point. Use local_coordinates_at_nonweierstrass instead!" % P) L = PowerSeriesRing(self.base_ring(), name) t = L.gen() pol = self.hyperelliptic_polynomials()[0] pol_prime = pol.derivative() b = P[0] t2 = t**2 c = b + t2 / pol_prime(b) c = c.add_bigoh(prec) for _ in range(int(1 + log(prec, 2))): c -= (pol(c) - t2) / pol_prime(c) return (c, t.add_bigoh(prec))
def _repr_(self): r""" Returns the representation of self as a string. """ R = PowerSeriesRing(self._parent._R, default_prec=self._depth, name='z') z = R.gen() s = str(sum([R(self._val[ii, 0] * z**ii) for ii in range(self._depth)])) return s
def local_coordinates_at_weierstrass(self, P, prec=20, name='t'): """ For a finite Weierstrass point on the hyperelliptic curve `y^2 = f(x)`, returns `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`, where `t = y` is the local parameter. INPUT: - ``P`` -- a finite Weierstrass point on self - ``prec`` -- desired precision of the local coordinates - ``name`` -- gen of the power series ring (default: `t`) OUTPUT: `(x(t),y(t))` such that `y(t)^2 = f(x(t))` and `t = y` is the local parameter at `P` EXAMPLES:: sage: R.<x> = QQ['x'] sage: H = HyperellipticCurve(x^5-23*x^3+18*x^2+40*x) sage: A = H(4, 0) sage: x, y = H.local_coordinates_at_weierstrass(A, prec=7) sage: x 4 + 1/360*t^2 - 191/23328000*t^4 + 7579/188956800000*t^6 + O(t^7) sage: y t + O(t^7) sage: B = H(-5, 0) sage: x, y = H.local_coordinates_at_weierstrass(B, prec=5) sage: x -5 + 1/1260*t^2 + 887/2000376000*t^4 + O(t^5) sage: y t + O(t^5) AUTHOR: - Jennifer Balakrishnan (2007-12) - Francis Clarke (2012-08-26) """ if P[1] != 0: raise TypeError("P = %s is not a finite Weierstrass point. Use local_coordinates_at_nonweierstrass instead!"%P) L = PowerSeriesRing(self.base_ring(), name) t = L.gen() pol = self.hyperelliptic_polynomials()[0] pol_prime = pol.derivative() b = P[0] t2 = t**2 c = b + t2/pol_prime(b) c = c.add_bigoh(prec) for _ in range(1 + log(prec, 2)): c -= (pol(c) - t2)/pol_prime(c) return (c, t.add_bigoh(prec))
def eisenstein_series_at_inf(phi, psi, k, prec=10, t=1, base_ring=None): r""" Return Fourier expansion of Eistenstein series at a cusp. INPUT: - ``phi`` -- Dirichlet character. - ``psi`` -- Dirichlet character. - ``k`` -- integer, the weight of the Eistenstein series. - ``prec`` -- integer (default: 10). - ``t`` -- integer (default: 1). OUTPUT: The Fourier expansion of the Eisenstein series $E_k^{\phi,\psi, t}$ (as defined by [Diamond-Shurman]) at the specific cusp. EXAMPLES: sage: phi = DirichletGroup(3)[1] sage: psi = DirichletGroup(5)[1] sage: E = eisenstein_series_at_inf(phi, psi, 4) """ N1, N2 = phi.level(), psi.level() N = N1 * N2 #The Fourier expansion of the Eisenstein series at infinity is in the field Q(zeta_Ncyc) Ncyc = lcm([euler_phi(N1), euler_phi(N2)]) if base_ring == None: base_ring = CyclotomicField(Ncyc) Q = PowerSeriesRing(base_ring, 'q') q = Q.gen() s = O(q**prec) #Weight 2 with trivial characters is calculated separately if k == 2 and phi.conductor() == 1 and psi.conductor() == 1: if t == 1: raise TypeError('E_2 is not a modular form.') s = 1 / 24 * (t - 1) for m in srange(1, prec): for n in srange(1, prec / m): s += n * (q**(m * n) - t * q**(m * n * t)) return s + O(q**prec) if psi.level() == 1 and k == 1: s -= phi.bernoulli(k) / k elif phi.level() == 1: s -= psi.bernoulli(k) / k for m in srange(1, prec / t): for n in srange(1, prec / t / m + 1): s += 2 * base_ring(phi(m)) * base_ring( psi(n)) * n**(k - 1) * q**(m * n * t) return s + O(q**prec)
def _test__jacobi_taylor_coefficients(expansion, weight, prec=None): r""" Compute the renormalized Taylor coefficients of a Jacobi form. INPUT: - ``expansion`` -- A Fourier expansion or a dictionary with corresponding keys. - ``weight`` -- An integer. - ``prec`` -- A filter for Fourier expansions, of if ``expansion`` is a Fourier expansion possibly ``None``. OUTPUT: - A list of power series in `q`. """ from sage.rings.arith import gcd if prec is None: prec = expansion.precision() jacobi_index = prec.jacobi_index() q_precision = prec.index() R = PowerSeriesRing(ZZ, 'q') q = R.gen(0) weak_prec = JacobiFormD1NNFilter(prec, reduced=False, weak_forms=True) indices = JacobiFormD1NNIndices(jacobi_index) projs = list() for pw in (range(0, 2 * jacobi_index + 1, 2) if weight % 2 == 0 else range(1, 2 * jacobi_index - 1, 2)): proj = dict((n, 0) for n in range(q_precision)) for (n, r) in weak_prec: ((nred, rred), sign) = indices.reduce((n, r)) try: proj[n] += (sign * r)**pw * expansion[(nred, rred)] except (KeyError, ValueError): pass projs.append(proj) gcd_projs = [gcd(proj.values()) for proj in projs] gcd_projs = [g if g != 0 else 1 for g in gcd_projs] projs = [sorted(proj.iteritems()) for proj in projs] projs = [ R([c for (_, c) in proj]).add_bigoh(q_precision) / gcd_proj for (proj, gcd_proj) in zip(projs, gcd_projs) ] return projs
def eis_E(cv, dv, N, k, Q=None, param_level=1, prec=10): r""" Computes the coefficient of the Eisenstein series for $\Gamma(N)$. Not intended to be called by user. INPUT: - cv - int, the first coordinate of the vector determining the \Gamma(N) Eisenstein series - dv - int, the second coordinate of the vector determining the \Gamma(N) Eisenstein series - N - int, the level of the Eisenstein series to be computed - k - int, the weight of the Eisenstein seriess to be computed - Q - power series ring, the ring containing the q-expansion to be computed - param_level - int, the parameter of the returned series will be q_{param_level} - prec - int, the precision. The series in q_{param_level} will be truncated after prec coefficients OUTPUT: - an element of the ring Q, which is the Fourier expansion of the Eisenstein series """ if Q == None: Q = PowerSeriesRing(CyclotomicField(N), 'q') R = Q.base_ring() zetaN = R.zeta(N) q = Q.gen() cv = cv % N dv = dv % N #if dv == 0 and cv == 0 and k == 2: # raise ValueError("E_2 is not a modular form") if k == 1: if cv == 0 and dv == 0: raise ValueError("that shouldn't have happened...") elif cv == 0 and dv != 0: s = QQ(1) / QQ(2) * (1 + zetaN**dv) / (1 - zetaN**dv) elif cv != 0: s = QQ(1) / QQ(2) - QQ(cv) / QQ(N) + floor(QQ(cv) / QQ(N)) elif k > 1: if cv == 0: s = hurwitz_hat(QQ(dv), QQ(N), 1 - k, zetaN) else: s = 0 for n1 in xrange(1, prec): # this is n/m in DS for n2 in xrange(1, prec / n1 + 1): # this is m in DS if Mod(n1, N) == Mod(cv, N): s += n2**(k - 1) * zetaN**(dv * n2) * q**(n1 * n2) if Mod(n1, N) == Mod(-cv, N): s += (-1)**k * n2**(k - 1) * zetaN**(-dv * n2) * q**(n1 * n2) return s + O(q**floor(prec))
def _test__by_taylor_expansion(q_precision, weight, jacobi_index): r""" Run tests that validate by_taylor_expansions for various indices and weights. TESTS:: sage: from psage.modform.jacobiforms.jacobiformd1nn_fegenerators import * sage: JacobiFormD1NNFactory_class._test__by_taylor_expansion(100, 10, 2) sage: JacobiFormD1NNFactory_class._test__by_taylor_expansion(20, 11, 2) sage: JacobiFormD1NNFactory_class._test__by_taylor_expansion(50, 9, 3) # long time sage: JacobiFormD1NNFactory_class._test__by_taylor_expansion(50, 10, 10) # long time sage: JacobiFormD1NNFactory_class._test__by_taylor_expansion(30, 7, 15) # long time """ from sage.rings import big_oh prec = JacobiFormD1NNFilter(q_precision, jacobi_index) factory = JacobiFormD1NNFactory(prec) R = PowerSeriesRing(ZZ, 'q') q = R.gen(0) if weight % 2 == 0: nmb_modular_forms = jacobi_index + 1 start_weight = weight else: nmb_modular_forms = jacobi_index - 1 start_weight = weight + 1 modular_forms = list() for (i, k) in enumerate( range(start_weight, start_weight + 2 * nmb_modular_forms, 2)): modular_forms += [[lambda p: big_oh.O(q**p) for _ in range(i)] + [b.qexp] + [ lambda p: big_oh.O(q**p) for _ in range(nmb_modular_forms - 1 - i) ] for b in ModularForms(1, k).echelon_basis()] for (fs_index, fs) in enumerate(modular_forms): expansion = factory.by_taylor_expansion(fs, weight, True) taylor_coefficients = JacobiFormD1NNFactory_class._test__jacobi_taylor_coefficients( expansion, weight, prec) predicted_taylor_coefficients = JacobiFormD1NNFactory_class._test__jacobi_predicted_taylor_coefficients( fs, q_precision) for (i, (proj, f)) in enumerate( zip(taylor_coefficients, predicted_taylor_coefficients)): if f != proj: raise AssertionError( "{0}-th Taylor coefficient of the {1}-th Jacobi form is not correct. Expansions are\n {2}\nand\n {3}" .format(i, fs_index, proj, f))
def _compute_acting_matrix(self, g, M): r""" INPUT: - ``g`` -- an instance of :class:`sage.matrices.matrix_integer_2x2.Matrix_integer_2x2` or :class:`sage.matrix.matrix_generic_dense.Matrix_generic_dense` - ``M`` -- a positive integer giving the precision at which ``g`` should act. OUTPUT: - """ #tim = verbose("Starting") a, b, c, d = self._adjuster(g) # if g.parent().base_ring().is_exact(): # self._check_mat(a, b, c, d) #k = self._k base_ring = self.domain().base_ring() #cdef Matrix B = matrix(base_ring,M,M) B = matrix(base_ring, M, M) # if M == 0: return B.change_ring(self.codomain().base_ring()) R = PowerSeriesRing(base_ring, 'y', default_prec = M) y = R.gen() #tim = verbose("Checked, made R",tim) # special case for small precision, large weight scale = (b+d*y)/(a+c*y) #t = (a+c*y)**k # will already have precision M t = R.one() #cdef long row, col # #tim = verbose("Made matrix",tim) for col in range(M): for row in range(M): #B.set_unsafe(row, col, t[row]) B[row, col] = t[row] t *= scale #verbose("Finished loop",tim) # the change_ring here is annoying, but otherwise we have to change ring each time we multiply B = B.change_ring(self.codomain().base_ring()) #if self._character is not None: # B *= self._character(a) if self._dettwist is not None: B *= (a*d - b*c) ** (self._dettwist) return B
def _repr_(self): r""" This returns the representation of self as a string. EXAMPLES: This example illustrates ... :: """ R=PowerSeriesRing(self._parent._R,default_prec=self._depth,name='z') z=R.gen() s=str(sum([R(self._val[ii,0]*z**ii) for ii in range(self._depth)])) return s
def _test__jacobi_torsion_point(phi, weight, torsion_point): r""" Given a list of power series, which are the corrected Taylor coefficients of a Jacobi form, return the specialization to ``torsion_point``. INPUT: - ``phi`` -- A Fourier expansion of a Jacobi form. - ``weight`` -- An integer. - ``torsion_point`` -- A rational. OUPUT: - A power series. TESTS: See jacobi_form_by_taylor_expansion. sage: from psage.modform.jacobiforms.jacobiformd1nn_fegenerators import * sage: from psage.modform.jacobiforms.jacobiformd1nn_types import * sage: precision = 50 sage: weight = 10; index = 7 sage: phis = [jacobi_form_by_taylor_expansion(i, JacobiFormD1NNFilter(precision, index), weight) for i in range(JacobiFormD1NN_Gamma(weight, index)._rank(QQ))] sage: fs = [JacobiFormD1NNFactory_class._test__jacobi_torsion_point(phi, weight, 2/3) for phi in phis] sage: fs_vec = [vector(f.padded_list(precision)) for f in fs] sage: mf_span = span([vector(b.qexp(precision).padded_list(precision)) for b in ModularForms(GammaH(9, [4]), weight).basis()]) sage: all(f_vec in mf_span for f_vec in fs_vec) True FIXME: The case of torsion points of order 5, which should lead to forms for Gamma1(25) fails even in the simplest case. """ from sage.rings.all import CyclotomicField K = CyclotomicField(QQ(torsion_point).denominator()) zeta = K.gen() R = PowerSeriesRing(K, 'q') q = R.gen(0) ch = JacobiFormD1WeightCharacter(weight) coeffs = dict((n, QQ(0)) for n in range(phi.precision().index())) for (n, r) in phi.precision().monoid_filter(): coeffs[n] += zeta**r * phi[(ch, (n, r))] return PowerSeriesRing(K, 'q')(coeffs)
def _test__jacobi_taylor_coefficients(expansion, weight, prec = None) : r""" Compute the renormalized Taylor coefficients of a Jacobi form. INPUT: - ``expansion`` -- A Fourier expansion or a dictionary with corresponding keys. - ``weight`` -- An integer. - ``prec`` -- A filter for Fourier expansions, of if ``expansion`` is a Fourier expansion possibly ``None``. OUTPUT: - A list of power series in `q`. """ from sage.rings.arith import gcd if prec is None : prec = expansion.precision() jacobi_index = prec.jacobi_index() q_precision = prec.index() R = PowerSeriesRing(ZZ, 'q'); q = R.gen(0) weak_prec = JacobiFormD1NNFilter(prec, reduced = False, weak_forms = True) indices = JacobiFormD1NNIndices(jacobi_index) projs = list() for pw in (range(0, 2 * jacobi_index + 1, 2) if weight % 2 == 0 else range(1, 2 * jacobi_index - 1, 2)) : proj = dict( (n, 0) for n in range(q_precision) ) for (n, r) in weak_prec : ((nred, rred), sign) = indices.reduce((n,r)) try : proj[n] += (sign * r)**pw * expansion[(nred, rred)] except (KeyError, ValueError) : pass projs.append(proj) gcd_projs = [gcd(proj.values()) for proj in projs] gcd_projs = [g if g != 0 else 1 for g in gcd_projs] projs = [sorted(proj.iteritems()) for proj in projs] projs = [ R([c for (_, c) in proj]).add_bigoh(q_precision) / gcd_proj for (proj, gcd_proj) in zip(projs, gcd_projs) ] return projs
class MeromorphicFunctions(Parent, CachedRepresentation): Element = MeromorphicFunctionsElement def __init__(self, K, additive=False): Parent.__init__(self) self._additive = additive self._base_ring = K self._prec = K.precision_cap() self._Ps = PowerSeriesRing(self._base_ring, names='t', default_prec=self._prec) t = self._Ps.gen() p = K.prime() self._Ps_local_variable = lambda Q: 1 - t / Q self._unset_coercions_used() self.register_action(Scaling(ZZ, self)) self.register_action(MatrixAction(MatrixSpace(K, 2, 2), self)) @cached_method def get_action_data(self, g): a, b, c, d = g.list() K = g.parent().base_ring() R = PolynomialRing(K, 'z') Ps = PowerSeriesRing(K, 't', default_prec=self._prec) z = R.gen() zz = (d * z - b) / (-c * z + a) zz_ps = Ps(zz).add_bigoh(self._prec) ans = [Ps(1), zz_ps] for _ in range(self._prec - 1): ans.append((zz_ps * ans[-1]).add_bigoh(self._prec)) return ans def is_additive(self): return self._additive def base_ring(self): return self._base_ring def power_series_ring(self): return self._Ps def _element_constructor_(self, data): return self.element_class(self, data) def _repr_(self): return "Meromorphic %s Functions over %s" % ( 'Additive' if self._additive else 'Multiplicative', self._base_ring)
def _test__jacobi_torsion_point(phi, weight, torsion_point) : r""" Given a list of power series, which are the corrected Taylor coefficients of a Jacobi form, return the specialization to ``torsion_point``. INPUT: - ``phi`` -- A Fourier expansion of a Jacobi form. - ``weight`` -- An integer. - ``torsion_point`` -- A rational. OUPUT: - A power series. TESTS: See jacobi_form_by_taylor_expansion. sage: from psage.modform.jacobiforms.jacobiformd1nn_fegenerators import * sage: from psage.modform.jacobiforms.jacobiformd1nn_types import * sage: precision = 50 sage: weight = 10; index = 7 sage: phis = [jacobi_form_by_taylor_expansion(i, JacobiFormD1NNFilter(precision, index), weight) for i in range(JacobiFormD1NN_Gamma(weight, index)._rank(QQ))] sage: fs = [JacobiFormD1NNFactory_class._test__jacobi_torsion_point(phi, weight, 2/3) for phi in phis] sage: fs_vec = [vector(f.padded_list(precision)) for f in fs] sage: mf_span = span([vector(b.qexp(precision).padded_list(precision)) for b in ModularForms(GammaH(9, [4]), weight).basis()]) sage: all(f_vec in mf_span for f_vec in fs_vec) True FIXME: The case of torsion points of order 5, which should lead to forms for Gamma1(25) fails even in the simplest case. """ from sage.rings.all import CyclotomicField K = CyclotomicField(QQ(torsion_point).denominator()); zeta = K.gen() R = PowerSeriesRing(K, 'q'); q = R.gen(0) ch = JacobiFormD1WeightCharacter(weight) coeffs = dict( (n, QQ(0)) for n in range(phi.precision().index()) ) for (n, r) in phi.precision().monoid_filter() : coeffs[n] += zeta**r * phi[(ch, (n,r))] return PowerSeriesRing(K, 'q')(coeffs)
def _repr_(self): r""" This returns the representation of self as a string. EXAMPLES: This example illustrates ... :: """ R = PowerSeriesRing(self._parent._R, default_prec=self._depth, name='z') z = R.gen() s = str(sum([R(self._val[ii, 0] * z**ii) for ii in range(self._depth)])) return s
def eis_F(cv, dv, N, k, Q=None, prec=10, t=1): """ Computes the coefficient of the Eisenstein series for $\Gamma(N)$. Not indented to be called by user. INPUT: - cv - int, the first coordinate of the vector determining the \Gamma(N) Eisenstein series - dv - int, the second coordinate of the vector determining the \Gamma(N) Eisenstein series - N - int, the level of the Eisenstein series to be computed - k - int, the weight of the Eisenstein seriess to be computed - Q - power series ring, the ring containing the q-expansion to be computed - param_level - int, the parameter of the returned series will be q_{param_level} - prec - int, the precision. The series in q_{param_level} will be truncated after prec coefficients OUTPUT: - an element of the ring Q, which is the Fourier expansion of the Eisenstein series """ if Q == None: Q = PowerSeriesRing(CyclotomicField(N), 'q{}'.format(N)) R = Q.base_ring() zetaN = R.zeta(N) q = Q.gen() s = 0 if k == 1: if cv % N == 0 and dv % N != 0: s = QQ(1) / QQ(2) * (1 + zetaN**dv) / (1 - zetaN**dv) elif cv % N != 0: s = QQ(1) / QQ(2) - QQ(cv) / QQ(N) + floor(QQ(cv) / QQ(N)) elif k > 1: s = -ber_pol(QQ(cv) / QQ(N) - floor(QQ(cv) / QQ(N)), k) / QQ(k) for n1 in xrange(1, ceil(prec / QQ(t))): # this is n/m in DS for n2 in xrange(1, ceil(prec / QQ(t) / QQ(n1)) + 1): # this is m in DS if Mod(n1, N) == Mod(cv, N): s += N**(1 - k) * n1**(k - 1) * zetaN**(dv * n2) * q**(t * n1 * n2) if Mod(n1, N) == Mod(-cv, N): s += (-1)**k * N**(1 - k) * n1**(k - 1) * zetaN**( -dv * n2) * q**(t * n1 * n2) return s + O(q**floor(prec))
def herm_modform_space_dim(D, HermWeight): """ Calculates and returns the dimension of the vector space of Hermitian modular forms of weight `HermWeight` over \Gamma, where \Gamma = \Sp_2(\curlO) and \curlO is the maximal order of \QQ(\sqrt{D}). """ if D == -3: # dern2003graded, Thm 7 R = PowerSeriesRing(ZZ, name="t", default_prec = HermWeight + 1) t = R.gen() dims = (1 + t**45) / (1 - t**4 ) / ( 1 - t**6 ) / ( 1 - t**9 ) / ( 1 - t**10 ) / ( 1 - t**12 ) return dims[HermWeight] #elif D == -4: # dern2003graded, Corollary 9 and Lemma 3 # TODO... #R = PowerSeriesRing(ZZ, name="t", default_prec = HermWeight + 1) #t = R.an_element() else: raise NotImplementedError, "dimension calculation of Hermitian modular form with D = %i not implemented" % D
def _all_weak_taylor_coefficients(weight, index): r""" A product basis of the echelon bases of - `M_k, M_{k + 2}, ..., M_{k + 2 m}` etc. if ``weight`` is even, - `M_{k + 1}, ..., M_{k + 2 m - 3}` if ``weight`` is odd. INPUT: - ``weight`` -- An integer. - ``index`` -- A non-negative integer. TESTS:: sage: from psage.modform.jacobiforms.jacobiformd1nn_fegenerators import _all_weak_taylor_coefficients sage: _all_weak_taylor_coefficients(12, 1) [[<bound method ModularFormElement.qexp of 1 + 196560*q^2 + 16773120*q^3 + 398034000*q^4 + 4629381120*q^5 + O(q^6)>, <function <lambda> at ...>], [<bound method ModularFormElement.qexp of q - 24*q^2 + 252*q^3 - 1472*q^4 + 4830*q^5 + O(q^6)>, <function <lambda> at ...>], [<function <lambda> at ...>, <bound method ModularFormElement.qexp of 1 - 24*q - 196632*q^2 - 38263776*q^3 - 1610809368*q^4 - 29296875024*q^5 + O(q^6)>]] """ R = PowerSeriesRing(ZZ, 'q') q = R.gen() if weight % 2 == 0: nmb_modular_forms = index + 1 start_weight = weight else: nmb_modular_forms = index - 1 start_weight = weight + 1 modular_forms = list() for (i, k) in enumerate( range(start_weight, start_weight + 2 * nmb_modular_forms, 2)): modular_forms += [[lambda p: big_oh.O(q**p) for _ in range(i)] + [b.qexp] + [ lambda p: big_oh.O(q**p) for _ in range(nmb_modular_forms - 1 - i) ] for b in ModularForms(1, k).echelon_basis()] return modular_forms
def _test__by_taylor_expansion(q_precision, weight, jacobi_index) : r""" Run tests that validate by_taylor_expansions for various indices and weights. TESTS:: sage: from psage.modform.jacobiforms.jacobiformd1nn_fegenerators import * sage: JacobiFormD1NNFactory_class._test__by_taylor_expansion(100, 10, 2) sage: JacobiFormD1NNFactory_class._test__by_taylor_expansion(20, 11, 2) sage: JacobiFormD1NNFactory_class._test__by_taylor_expansion(50, 9, 3) # long time sage: JacobiFormD1NNFactory_class._test__by_taylor_expansion(50, 10, 10) # long time sage: JacobiFormD1NNFactory_class._test__by_taylor_expansion(30, 7, 15) # long time """ from sage.rings import big_oh prec = JacobiFormD1NNFilter(q_precision, jacobi_index) factory = JacobiFormD1NNFactory(prec) R = PowerSeriesRing(ZZ, 'q'); q = R.gen(0) if weight % 2 == 0 : nmb_modular_forms = jacobi_index + 1 start_weight = weight else : nmb_modular_forms = jacobi_index - 1 start_weight = weight + 1 modular_forms = list() for (i,k) in enumerate(range(start_weight, start_weight + 2 * nmb_modular_forms, 2)) : modular_forms += [ [lambda p: big_oh.O(q**p) for _ in range(i)] + [b.qexp] + [lambda p: big_oh.O(q**p) for _ in range(nmb_modular_forms - 1 - i)] for b in ModularForms(1, k).echelon_basis() ] for (fs_index, fs) in enumerate(modular_forms) : expansion = factory.by_taylor_expansion(fs, weight, True) taylor_coefficients = JacobiFormD1NNFactory_class._test__jacobi_taylor_coefficients(expansion, weight, prec) predicted_taylor_coefficients = JacobiFormD1NNFactory_class._test__jacobi_predicted_taylor_coefficients(fs, q_precision) for (i, (proj, f)) in enumerate(zip(taylor_coefficients, predicted_taylor_coefficients)) : if f != proj : raise AssertionError( "{0}-th Taylor coefficient of the {1}-th Jacobi form is not correct. Expansions are\n {2}\nand\n {3}".format(i, fs_index, proj, f) )
def _test__jacobi_predicted_taylor_coefficients(fs, q_precision): r""" Given a list of power series, which are the corrected Taylor coefficients of a Jacobi form, return the renormalized uncorrected ones, assuming that all but one `f` vanish. INPUT: - ``fs`` -- A list of power series. - ``q_precision`` -- An integer. OUPUT: - A list of power series. TESTS: See jacobi_form_by_taylor_expansion. """ from sage.rings.arith import gcd R = PowerSeriesRing(ZZ, 'q') q = R.gen(0) diff = lambda f: f.derivative().shift(1) normalize = lambda f: f / gcd(f.list()) if f != 0 else f diffnorm = lambda f, l: normalize( reduce(lambda a, g: g(a), l * [diff], f)) taylor_coefficients = list() allf = R(0) for f in fs: allf = f(q_precision) + diffnorm(allf, 1) taylor_coefficients.append(allf) return taylor_coefficients
def apply_triangular_matrix(self, delta): """ If self is a q-expansion and q = exp(2*pi*I*tau), then applying a triangular matrix [[a,b],[0,d]] to tau gives a well-defined q-expansion as long as the base ring contains zeta_d. Warning: The correct slash-action would also multiply with det(delta)^k/2 but we carefully avoid that in all applications of apply_triangular_matrix. """ R = self.series.base_ring() a, b, d = delta[0][0], delta[0][1], delta[1][1] """ Reduce the fraction a/d """ a2, d2 = (a / d).numerator(), (a / d).denominator() b2, d3 = (b / d).numerator(), (b / d).denominator() zetad = R.zeta(d3 * self.param_level) Q = PowerSeriesRing(R, 'q' + str(self.param_level * d2)) qNd = Q.gen() s = 0 for i in range(self.series.prec()): s += self.series[i] * zetad**(i * b2) * qNd**(i * a2) s = s + O(qNd**(self.series.prec())) s = s * d**(-self.weight) return QExpansion(self.level * d2, self.weight, s, self.param_level * d2)
def prune(self, tar_param_level): """ Prunes terms from the q-expansion to return a series in the desired parameter """ try: assert (self.param_level % tar_param_level == 0) except AssertionError: print( "target parameter level should be a divsor of current parameter level" ) return R = self.series.base_ring() Q = PowerSeriesRing(R, 'q' + str(tar_param_level)) q = Q.gen() s = 0 for i in range(self.series.prec() * tar_param_level // self.param_level): s += self.series[i * (self.param_level // tar_param_level)] * q**i s += O(q**(self.series.prec() * tar_param_level // self.param_level)) self.series = s self.param_level = tar_param_level
class MPowerSeriesRing_generic(PowerSeriesRing_generic, Nonexact): r""" A multivariate power series ring. This class is implemented as a single variable power series ring in the variable ``T`` over a multivariable polynomial ring in the specified generators. Each generator ``g`` of the multivariable polynomial ring (called the "foreground ring") is mapped to ``g*T`` in the single variable power series ring (called the "background ring"). The background power series ring is used to do arithmetic and track total-degree precision. The foreground polynomial ring is used to display elements. For usage and examples, see above, and :meth:`PowerSeriesRing`. """ ### methods from PowerSeriesRing_generic that we *don't* override: # # variable_names_recursive : works just fine # # __contains__ : works just fine # # base_extend : works just fine # # is_exact : works just fine # # random_element : works just fine # # is_field : works just fine # # is_finite : works just fine # # __setitem__ : works just fine # # #### notes # # sparse setting may not be implemented completely Element = MPowerSeries 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 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() """ 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 # 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(), 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() self._is_sparse = sparse self._params = (base_ring, num_gens, name_list, order, default_prec, sparse) self._populate_coercion_lists_() def _repr_(self): """ Prints out a multivariate power series ring. EXAMPLES:: sage: R.<x,y> = PowerSeriesRing(GF(17)) sage: R #indirect doctest Multivariate Power Series Ring in x, y over Finite Field of size 17 sage: R.rename('my multivariate power series ring') sage: R my multivariate power series ring """ if self.ngens() == 0: generators_rep = "no variables" else: generators_rep = ", ".join(self.variable_names()) s = "Multivariate Power Series Ring in %s over %s"%(generators_rep, self.base_ring()) if self.is_sparse(): s = 'Sparse ' + s return s def _latex_(self): """ Returns latex representation of power series ring EXAMPLES:: sage: M = PowerSeriesRing(QQ,4,'v'); M Multivariate Power Series Ring in v0, v1, v2, v3 over Rational Field sage: M._latex_() '\\Bold{Q}[[v_{0}, v_{1}, v_{2}, v_{3}]]' """ generators_latex = ", ".join(self.latex_variable_names()) return "%s[[%s]]"%(latex.latex(self.base_ring()), generators_latex) def is_integral_domain(self, proof=False): """ Return True if the base ring is an integral domain; otherwise return False. EXAMPLES:: sage: M = PowerSeriesRing(QQ,4,'v'); M Multivariate Power Series Ring in v0, v1, v2, v3 over Rational Field sage: M.is_integral_domain() True """ return self.base_ring().is_integral_domain() def is_noetherian(self, proof=False): """ Power series over a Noetherian ring are Noetherian. EXAMPLES:: sage: M = PowerSeriesRing(QQ,4,'v'); M Multivariate Power Series Ring in v0, v1, v2, v3 over Rational Field sage: M.is_noetherian() True sage: W = PowerSeriesRing(InfinitePolynomialRing(ZZ,'a'),2,'x,y') sage: W.is_noetherian() False """ return self.base_ring().is_noetherian() def term_order(self): """ Print term ordering of self. Term orderings are implemented by the TermOrder class. EXAMPLES:: sage: M.<x,y,z> = PowerSeriesRing(ZZ,3); sage: M.term_order() Negative degree lexicographic term order sage: m = y*z^12 - y^6*z^8 - x^7*y^5*z^2 + x*y^2*z + M.O(15); m x*y^2*z + y*z^12 - x^7*y^5*z^2 - y^6*z^8 + O(x, y, z)^15 sage: N = PowerSeriesRing(ZZ,3,'x,y,z', order="deglex"); sage: N.term_order() Degree lexicographic term order sage: N(m) -x^7*y^5*z^2 - y^6*z^8 + y*z^12 + x*y^2*z + O(x, y, z)^15 """ return self._term_order def characteristic(self): """ Return characteristic of base ring, which is characteristic of self. EXAMPLES:: sage: H = PowerSeriesRing(GF(65537),4,'f'); H Multivariate Power Series Ring in f0, f1, f2, f3 over Finite Field of size 65537 sage: H.characteristic() 65537 """ return self.base_ring().characteristic() def construction(self): """ Returns a functor F and base ring R such that F(R) == self. EXAMPLES:: sage: M = PowerSeriesRing(QQ,4,'f'); M Multivariate Power Series Ring in f0, f1, f2, f3 over Rational Field sage: (c,R) = M.construction(); (c,R) (Completion[('f0', 'f1', 'f2', 'f3')], Multivariate Polynomial Ring in f0, f1, f2, f3 over Rational Field) sage: c Completion[('f0', 'f1', 'f2', 'f3')] sage: c(R) Multivariate Power Series Ring in f0, f1, f2, f3 over Rational Field sage: c(R) == M True """ from sage.categories.pushout import CompletionFunctor return (CompletionFunctor(self._names, self.default_prec()), self._poly_ring()) def change_ring(self, R): """ Returns the power series ring over R in the same variable as self. This function ignores the question of whether the base ring of self is or can extend to the base ring of R; for the latter, use base_extend. EXAMPLES:: sage: R.<t,u,v> = PowerSeriesRing(QQ); R Multivariate Power Series Ring in t, u, v over Rational Field sage: R.base_extend(RR) Multivariate Power Series Ring in t, u, v over Real Field with 53 bits of precision sage: R.change_ring(IntegerModRing(10)) Multivariate Power Series Ring in t, u, v over Ring of integers modulo 10 sage: R.base_extend(IntegerModRing(10)) Traceback (most recent call last): ... TypeError: no base extension defined sage: S = PowerSeriesRing(GF(65537),2,'x,y'); S Multivariate Power Series Ring in x, y over Finite Field of size 65537 sage: S.change_ring(GF(5)) Multivariate Power Series Ring in x, y over Finite Field of size 5 """ return PowerSeriesRing(R, names = self.variable_names(), default_prec = self.default_prec()) def remove_var(self, *var): """ Remove given variable or sequence of variables from self. EXAMPLES:: sage: A.<s,t,u> = PowerSeriesRing(ZZ) sage: A.remove_var(t) Multivariate Power Series Ring in s, u over Integer Ring sage: A.remove_var(s,t) Power Series Ring in u over Integer Ring sage: M = PowerSeriesRing(GF(5),5,'t'); M Multivariate Power Series Ring in t0, t1, t2, t3, t4 over Finite Field of size 5 sage: M.remove_var(M.gens()[3]) Multivariate Power Series Ring in t0, t1, t2, t4 over Finite Field of size 5 Removing all variables results in the base ring:: sage: M.remove_var(*M.gens()) Finite Field of size 5 """ vars = list(self.variable_names()) for v in var: vars.remove(str(v)) if len(vars) == 0: return self.base_ring() return PowerSeriesRing(self.base_ring(), names=vars) ## this is defined in PowerSeriesRing_generic # def __call__(self, f, prec=infinity): # """ # Coerce object to this multivariate power series ring. # """ # return def _coerce_impl(self, f): """ Return the canonical coercion of ``f`` into this multivariate power series ring, if one is defined, or raise a TypeError. The rings that canonically coerce to this multivariate power series ring are: - this ring itself - a polynomial or power series ring in the same variables or a subset of these variables (possibly empty), over any base ring that canonically coerces into the base ring of this ring EXAMPLES:: sage: R.<t,u,v> = PowerSeriesRing(QQ); R Multivariate Power Series Ring in t, u, v over Rational Field sage: S1.<t,v> = PolynomialRing(ZZ); S1 Multivariate Polynomial Ring in t, v over Integer Ring sage: f1 = -t*v + 2*v^2 + v; f1 -t*v + 2*v^2 + v sage: R(f1) v - t*v + 2*v^2 sage: S2.<u,v> = PowerSeriesRing(ZZ); S2 Multivariate Power Series Ring in u, v over Integer Ring sage: f2 = -2*v^2 + 5*u*v^2 + S2.O(6); f2 -2*v^2 + 5*u*v^2 + O(u, v)^6 sage: R(f2) -2*v^2 + 5*u*v^2 + O(t, u, v)^6 sage: R2 = R.change_ring(GF(2)) sage: R2(f1) v + t*v sage: R2(f2) u*v^2 + O(t, u, v)^6 TESTS:: sage: R.<t,u,v> = PowerSeriesRing(QQ) sage: S1.<t,v> = PolynomialRing(ZZ) sage: f1 = S1.random_element() sage: g1 = R._coerce_impl(f1) sage: f1.parent() == R False sage: g1.parent() == R True """ P = f.parent() if is_MPolynomialRing(P) or is_MPowerSeriesRing(P) \ or is_PolynomialRing(P) or is_PowerSeriesRing(P): if set(P.variable_names()).issubset(set(self.variable_names())): if self.has_coerce_map_from(P.base_ring()): return self(f) else: return self._coerce_try(f,[self.base_ring()]) def _is_valid_homomorphism_(self, codomain, im_gens): """ Replacement for method of PowerSeriesRing_generic. To be valid, a homomorphism must send generators to elements of positive valuation or to nilpotent elements. Note that the method is_nilpotent doesn't (as of sage 4.4) seem to be defined for obvious examples (matrices, quotients of polynomial rings). EXAMPLES:: sage: R.<a,b,c> = PowerSeriesRing(Zmod(8)); R Multivariate Power Series Ring in a, b, c over Ring of integers modulo 8 sage: M = PowerSeriesRing(ZZ,3,'x,y,z'); sage: M._is_valid_homomorphism_(R,[a,c,b]) True sage: M._is_valid_homomorphism_(R,[0,c,b]) True 2 is nilpotent in `ZZ/8`, but 3 is not:: sage: M._is_valid_homomorphism_(R,[2,c,b]) True sage: M._is_valid_homomorphism_(R,[3,c,b]) False Over `ZZ`, 2 is not nilpotent:: sage: S = R.change_ring(ZZ); S Multivariate Power Series Ring in a, b, c over Integer Ring sage: M._is_valid_homomorphism_(S,[a,c,b]) True sage: M._is_valid_homomorphism_(S,[0,c,b]) True sage: M._is_valid_homomorphism_(S,[2,c,b]) False sage: g = [S.random_element(10)*v for v in S.gens()] sage: M._is_valid_homomorphism_(S,g) True """ try: im_gens = [codomain(v) for v in im_gens] except TypeError: raise TypeError("The given generator images do not coerce to codomain.") if len(im_gens) is not self.ngens(): raise ValueError("You must specify the image of each generator.") if all(v == 0 for v in im_gens): return True if is_MPowerSeriesRing(codomain) or is_PowerSeriesRing(codomain): try: B = all(v.valuation() > 0 or v.is_nilpotent() for v in im_gens) except NotImplementedError: B = all(v.valuation() > 0 for v in im_gens) return B if is_CommutativeRing(codomain): return all(v.is_nilpotent() for v in im_gens) def _coerce_map_from_(self, P): """ The rings that canonically coerce to this multivariate power series ring are: - this ring itself - a polynomial or power series ring in the same variables or a subset of these variables (possibly empty), over any base ring that canonically coerces into this ring - any ring that coerces into the foreground polynomial ring of this ring EXAMPLES:: sage: A = GF(17)[['x','y']] sage: A.has_coerce_map_from(ZZ) True sage: A.has_coerce_map_from(ZZ['x']) True sage: A.has_coerce_map_from(ZZ['y','x']) True sage: A.has_coerce_map_from(ZZ[['x']]) True sage: A.has_coerce_map_from(ZZ[['y','x']]) True sage: A.has_coerce_map_from(ZZ['x','z']) False sage: A.has_coerce_map_from(GF(3)['x','y']) False sage: A.has_coerce_map_from(Frac(ZZ['y','x'])) False TESTS:: sage: M = PowerSeriesRing(ZZ,3,'x,y,z'); sage: M._coerce_map_from_(M) True sage: M._coerce_map_from_(M.remove_var(x)) True sage: M._coerce_map_from_(PowerSeriesRing(ZZ,x)) True sage: M._coerce_map_from_(PolynomialRing(ZZ,'x,z')) True sage: M._coerce_map_from_(PolynomialRing(ZZ,0,'')) True sage: M._coerce_map_from_(ZZ) True sage: M._coerce_map_from_(Zmod(13)) False sage: M._coerce_map_from_(PolynomialRing(ZZ,2,'x,t')) False sage: M._coerce_map_from_(PolynomialRing(Zmod(11),2,'x,y')) False sage: P = PolynomialRing(ZZ,3,'z') sage: H = PowerSeriesRing(P,4,'f'); H Multivariate Power Series Ring in f0, f1, f2, f3 over Multivariate Polynomial Ring in z0, z1, z2 over Integer Ring sage: H._coerce_map_from_(P) True sage: H._coerce_map_from_(P.remove_var(P.gen(1))) True sage: H._coerce_map_from_(PolynomialRing(ZZ,'z2,f0')) True """ if is_MPolynomialRing(P) or is_MPowerSeriesRing(P) \ or is_PolynomialRing(P) or is_PowerSeriesRing(P): if set(P.variable_names()).issubset(set(self.variable_names())): if self.has_coerce_map_from(P.base_ring()): return True return self._poly_ring().has_coerce_map_from(P) def _element_constructor_(self,f,prec=None): """ TESTS:: sage: M = PowerSeriesRing(ZZ,5,'t'); sage: t = M.gens(); sage: m = -2*t[0]*t[3]^6*t[4] - 12*t[0]^2*t[3]*t[4]^6 + t[1]*t[2]*t[3]^4*t[4]^3 + M.O(10) sage: M._element_constructor_(m) -2*t0*t3^6*t4 - 12*t0^2*t3*t4^6 + t1*t2*t3^4*t4^3 + O(t0, t1, t2, t3, t4)^10 sage: R = PolynomialRing(ZZ,5,'t') sage: t = R.gens() sage: p = -4*t[0]*t[4] + t[1]^2 + t[1]*t[2] - 6*t[2]*t[4] - t[3]*t[4] sage: M._element_constructor_(p) -4*t0*t4 + t1^2 + t1*t2 - 6*t2*t4 - t3*t4 sage: p.parent() Multivariate Polynomial Ring in t0, t1, t2, t3, t4 over Integer Ring sage: M._element_constructor_(p).parent() Multivariate Power Series Ring in t0, t1, t2, t3, t4 over Integer Ring """ if prec is None: try: prec = f.prec() except AttributeError: prec = infinity return self.element_class(parent=self, x=f, prec=prec) def __cmp__(self, other): """ Compare this multivariate power series ring to something else. Power series rings are considered equal if the base ring, variable names, and default truncation precision are the same. Note that we don't compare term-ordering. First the base rings are compared, then the variable names, then the default precision. EXAMPLES:: sage: R.<t,u> = PowerSeriesRing(ZZ) sage: S.<t,u> = PowerSeriesRing(ZZ) sage: R is S True sage: R == S True sage: S.<t,u> = PowerSeriesRing(ZZ, default_prec=30) sage: R == S False sage: PowerSeriesRing(QQ,3,'t') == PowerSeriesRing(ZZ,3,'t') False sage: PowerSeriesRing(QQ,5,'t') == 5 False """ if not isinstance(other, MPowerSeriesRing_generic): return -1 c = cmp(self.base_ring(), other.base_ring()) if c: return c c = cmp(self.variable_names(), other.variable_names()) if c: return c c = cmp(self.default_prec(), other.default_prec()) if c: return c return 0 def laurent_series_ring(self): """ Laruent series not yet implemented for multivariate power series rings TESTS:: sage: M = PowerSeriesRing(ZZ,3,'x,y,z'); sage: M.laurent_series_ring() Traceback (most recent call last): ... NotImplementedError: Laurent series not implemented for multivariate power series. """ raise NotImplementedError("Laurent series not implemented for multivariate power series.") def _poly_ring(self, x=None): """ Return the underlying polynomial ring used to represent elements of this power series ring. If given an input x, returns x coerced into this polynomial ring. EXAMPLES:: sage: R.<t,u> = PowerSeriesRing(QQ) sage: R._poly_ring() Multivariate Polynomial Ring in t, u over Rational Field sage: R._poly_ring(2).parent() Multivariate Polynomial Ring in t, u over Rational Field """ if x is None: return self._poly_ring_ else: return self._poly_ring_(x) def _mpoly_ring(self, x=None): """ Same as _poly_ring TESTS:: sage: R.<t,u> = PowerSeriesRing(QQ) sage: R._mpoly_ring() Multivariate Polynomial Ring in t, u over Rational Field sage: R._mpoly_ring(2).parent() Multivariate Polynomial Ring in t, u over Rational Field """ return self._poly_ring(x) def _bg_ps_ring(self, x=None): """ Return the background univariate power series ring. If given an input x, returns x coerced into this power series ring. EXAMPLES:: sage: R.<t,u> = PowerSeriesRing(QQ) sage: R._bg_ps_ring() Power Series Ring in Tbg over Multivariate Polynomial Ring in t, u over Rational Field sage: R._bg_ps_ring(4).parent() == R False """ if x is None: return self._bg_power_series_ring else: return self._bg_power_series_ring(x) def is_sparse(self): """ Is self sparse? EXAMPLES:: sage: M = PowerSeriesRing(ZZ,3,'s,t,u'); M Multivariate Power Series Ring in s, t, u over Integer Ring sage: M.is_sparse() False sage: N = PowerSeriesRing(ZZ,3,'s,t,u',sparse=True); N Sparse Multivariate Power Series Ring in s, t, u over Integer Ring sage: N.is_sparse() True """ return self._is_sparse def is_dense(self): """ Is self dense? (opposite of sparse) EXAMPLES:: sage: M = PowerSeriesRing(ZZ,3,'s,t,u'); M Multivariate Power Series Ring in s, t, u over Integer Ring sage: M.is_dense() True sage: N = PowerSeriesRing(ZZ,3,'s,t,u',sparse=True); N Sparse Multivariate Power Series Ring in s, t, u over Integer Ring sage: N.is_dense() False """ return not self.is_sparse() def gen(self, n=0): """ Return the nth generator of self. EXAMPLES:: sage: M = PowerSeriesRing(ZZ,10,'v'); sage: M.gen(6) v6 """ if n < 0 or n >= self._ngens: raise ValueError("Generator not defined.") #return self(self._poly_ring().gens()[int(n)]) return self.element_class(parent=self,x=self._poly_ring().gens()[int(n)], is_gen=True) def ngens(self): """ Return number of generators of self. EXAMPLES:: sage: M = PowerSeriesRing(ZZ,10,'v'); sage: M.ngens() 10 """ return self._ngens def prec_ideal(self): """ Return the ideal which determines precision; this is the ideal generated by all of the generators of our background polynomial ring. EXAMPLES:: sage: A.<s,t,u> = PowerSeriesRing(ZZ) sage: A.prec_ideal() Ideal (s, t, u) of Multivariate Polynomial Ring in s, t, u over Integer Ring """ return self._poly_ring().ideal(self._poly_ring().gens()) def bigoh(self,prec): """ Return big oh with precision ``prec``. The function ``O`` does the same thing. EXAMPLES:: sage: T.<a,b> = PowerSeriesRing(ZZ,2); T Multivariate Power Series Ring in a, b over Integer Ring sage: T.bigoh(10) 0 + O(a, b)^10 sage: T.O(10) 0 + O(a, b)^10 """ return self(0).O(prec) def O(self,prec): """ Return big oh with precision ``prec``. This function is an alias for ``bigoh``. EXAMPLES:: sage: T.<a,b> = PowerSeriesRing(ZZ,2); T Multivariate Power Series Ring in a, b over Integer Ring sage: T.O(10) 0 + O(a, b)^10 sage: T.bigoh(10) 0 + O(a, b)^10 """ return self.bigoh(prec) def _send_to_bg(self,f): """ Send an element of the foreground polynomial ring to the background power series ring. EXAMPLES:: sage: M = PowerSeriesRing(QQ,4,'f') sage: f = M._poly_ring().gens() sage: bg = M._send_to_bg((f[0] + f[2] + 2)**2); bg 4 + (4*f0 + 4*f2)*Tbg + (f0^2 + 2*f0*f2 + f2^2)*Tbg^2 sage: M._send_to_bg(bg) Traceback (most recent call last): ... TypeError: Cannot coerce input to polynomial ring. """ try: f = self._poly_ring(f) except TypeError: raise TypeError("Cannot coerce input to polynomial ring.") fg_to_bg_dict = dict((v,v*self._bg_ps_ring().gen()) for v in self._poly_ring().gens()) return self._bg_ps_ring(f.subs(fg_to_bg_dict)) def _send_to_fg(self,f): """ Send an element of the background univariate power series ring to the foreground multivariate polynomial ring. EXAMPLES:: sage: M = PowerSeriesRing(QQ,4,'f') sage: f = M._poly_ring().gens() sage: bg = M._send_to_bg((f[0] + f[2] + 2)**2); bg 4 + (4*f0 + 4*f2)*Tbg + (f0^2 + 2*f0*f2 + f2^2)*Tbg^2 sage: bg.parent() Power Series Ring in Tbg over Multivariate Polynomial Ring in f0, f1, f2, f3 over Rational Field sage: fg = M._send_to_fg(bg); fg 4 + 4*f0 + 4*f2 + f0^2 + 2*f0*f2 + f2^2 sage: fg.parent() Multivariate Polynomial Ring in f0, f1, f2, f3 over Rational Field sage: fg = M._send_to_fg(bg.add_bigoh(3)); fg 4 + 4*f0 + 4*f2 + f0^2 + 2*f0*f2 + f2^2 sage: fg = M._send_to_fg(bg.add_bigoh(2)); fg 4 + 4*f0 + 4*f2 """ return self._poly_ring(f.polynomial().subs({self._bg_indeterminate:1}))
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 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 _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 _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 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
class MeromorphicFunctions(Parent, CachedRepresentation): Element = MeromorphicFunctionsElement def __init__(self, K, additive=True, dlog=True): Parent.__init__(self) self._additive = additive self._dlog = dlog self._base_ring = K self._prec = K.precision_cap() psprec = self._prec + 1 if dlog else self._prec self._Ps = PowerSeriesRing(self._base_ring, names='t', default_prec=psprec) if self._additive: self._V = FreeModule(K, self._prec) t = self._Ps.gen() self._Ps_local_variable = lambda Q: 1 - t / Q self._unset_coercions_used() self.register_action(Scaling(ZZ, self)) self.register_action(MatrixAction(MatrixSpace(K, 2, 2), self)) def acting_matrix(self, g, dim=None): try: g = g.matrix() except AttributeError: pass return self.get_action_data(g) @cached_method def get_action_data(self, g, K=None): a, b, c, d = g.list() prec = self._prec if K is None: if hasattr(a, 'lift'): a, b, c, d = a.lift(), b.lift(), c.lift(), d.lift() p = g.parent().base_ring().prime() K = ZpCA(p, prec) else: K = g.parent().base_ring() Ps = PowerSeriesRing(K, 't', default_prec=prec) z = Ps.gen() zz = (d * z - b) / (-c * z + a) zz_ps0 = Ps(zz).add_bigoh(prec) if self._dlog: zz_ps = ((a * d - b * c) * (-c * z + a)**-2).add_bigoh(prec) else: zz_ps = Ps(1).add_bigoh(prec) if self.is_additive(): M = Matrix(ZZ, prec, prec, 0) for j in range(prec): for i, aij in enumerate(zz_ps.list()): M[i, j] = aij if j < prec - 1: # Don't need the last multiplication zz_ps = (zz_ps0 * zz_ps).add_bigoh(prec) else: return M else: ans = [Ps(1), zz_ps] for _ in range(prec - 1): zz_ps = (zz_ps0 * zz_ps).add_bigoh(prec) ans.append(zz_ps) return ans def is_additive(self): return self._additive def base_ring(self): return self._base_ring def power_series_ring(self): return self._Ps def _element_constructor_(self, data): return self.element_class(self, data) def _repr_(self): return "Meromorphic %s Functions over %s" % ( 'Additive' if self._additive else 'Multiplicative', self._base_ring)
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 _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 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 _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()
class MPowerSeriesRing_generic(PowerSeriesRing_generic, Nonexact): r""" A multivariate power series ring. This class is implemented as a single variable power series ring in the variable ``T`` over a multivariable polynomial ring in the specified generators. Each generator ``g`` of the multivariable polynomial ring (called the "foreground ring") is mapped to ``g*T`` in the single variable power series ring (called the "background ring"). The background power series ring is used to do arithmetic and track total-degree precision. The foreground polynomial ring is used to display elements. For usage and examples, see above, and :meth:`PowerSeriesRing`. """ ### methods from PowerSeriesRing_generic that we *don't* override: # # variable_names_recursive : works just fine # # __contains__ : works just fine # # base_extend : works just fine # # is_exact : works just fine # # random_element : works just fine # # is_field : works just fine # # is_finite : works just fine # # __setitem__ : works just fine # # #### notes # # sparse setting may not be implemented completely Element = MPowerSeries 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 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() """ 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 # 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(), 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() self._is_sparse = sparse self._params = (base_ring, num_gens, name_list, order, default_prec, sparse) self._populate_coercion_lists_() def _repr_(self): """ Prints out a multivariate power series ring. EXAMPLES:: sage: R.<x,y> = PowerSeriesRing(GF(17)) sage: R #indirect doctest Multivariate Power Series Ring in x, y over Finite Field of size 17 sage: R.rename('my multivariate power series ring') sage: R my multivariate power series ring """ if self.ngens() == 0: generators_rep = "no variables" else: generators_rep = ", ".join(self.variable_names()) s = "Multivariate Power Series Ring in %s over %s" % (generators_rep, self.base_ring()) if self.is_sparse(): s = 'Sparse ' + s return s def _latex_(self): """ Returns latex representation of power series ring EXAMPLES:: sage: M = PowerSeriesRing(QQ,4,'v'); M Multivariate Power Series Ring in v0, v1, v2, v3 over Rational Field sage: M._latex_() '\\Bold{Q}[[v_{0}, v_{1}, v_{2}, v_{3}]]' """ generators_latex = ", ".join(self.latex_variable_names()) return "%s[[%s]]" % (latex.latex(self.base_ring()), generators_latex) def is_integral_domain(self, proof=False): """ Return True if the base ring is an integral domain; otherwise return False. EXAMPLES:: sage: M = PowerSeriesRing(QQ,4,'v'); M Multivariate Power Series Ring in v0, v1, v2, v3 over Rational Field sage: M.is_integral_domain() True """ return self.base_ring().is_integral_domain() def is_noetherian(self, proof=False): """ Power series over a Noetherian ring are Noetherian. EXAMPLES:: sage: M = PowerSeriesRing(QQ,4,'v'); M Multivariate Power Series Ring in v0, v1, v2, v3 over Rational Field sage: M.is_noetherian() True sage: W = PowerSeriesRing(InfinitePolynomialRing(ZZ,'a'),2,'x,y') sage: W.is_noetherian() False """ return self.base_ring().is_noetherian() def term_order(self): """ Print term ordering of self. Term orderings are implemented by the TermOrder class. EXAMPLES:: sage: M.<x,y,z> = PowerSeriesRing(ZZ,3); sage: M.term_order() Negative degree lexicographic term order sage: m = y*z^12 - y^6*z^8 - x^7*y^5*z^2 + x*y^2*z + M.O(15); m x*y^2*z + y*z^12 - x^7*y^5*z^2 - y^6*z^8 + O(x, y, z)^15 sage: N = PowerSeriesRing(ZZ,3,'x,y,z', order="deglex"); sage: N.term_order() Degree lexicographic term order sage: N(m) -x^7*y^5*z^2 - y^6*z^8 + y*z^12 + x*y^2*z + O(x, y, z)^15 """ return self._term_order def characteristic(self): """ Return characteristic of base ring, which is characteristic of self. EXAMPLES:: sage: H = PowerSeriesRing(GF(65537),4,'f'); H Multivariate Power Series Ring in f0, f1, f2, f3 over Finite Field of size 65537 sage: H.characteristic() 65537 """ return self.base_ring().characteristic() def construction(self): """ Returns a functor F and base ring R such that F(R) == self. EXAMPLES:: sage: M = PowerSeriesRing(QQ,4,'f'); M Multivariate Power Series Ring in f0, f1, f2, f3 over Rational Field sage: (c,R) = M.construction(); (c,R) (Completion[('f0', 'f1', 'f2', 'f3')], Multivariate Polynomial Ring in f0, f1, f2, f3 over Rational Field) sage: c Completion[('f0', 'f1', 'f2', 'f3')] sage: c(R) Multivariate Power Series Ring in f0, f1, f2, f3 over Rational Field sage: c(R) == M True """ from sage.categories.pushout import CompletionFunctor return (CompletionFunctor(self._names, self.default_prec()), self._poly_ring()) def change_ring(self, R): """ Returns the power series ring over R in the same variable as self. This function ignores the question of whether the base ring of self is or can extend to the base ring of R; for the latter, use base_extend. EXAMPLES:: sage: R.<t,u,v> = PowerSeriesRing(QQ); R Multivariate Power Series Ring in t, u, v over Rational Field sage: R.base_extend(RR) Multivariate Power Series Ring in t, u, v over Real Field with 53 bits of precision sage: R.change_ring(IntegerModRing(10)) Multivariate Power Series Ring in t, u, v over Ring of integers modulo 10 sage: R.base_extend(IntegerModRing(10)) Traceback (most recent call last): ... TypeError: no base extension defined sage: S = PowerSeriesRing(GF(65537),2,'x,y'); S Multivariate Power Series Ring in x, y over Finite Field of size 65537 sage: S.change_ring(GF(5)) Multivariate Power Series Ring in x, y over Finite Field of size 5 """ return PowerSeriesRing(R, names=self.variable_names(), default_prec=self.default_prec()) def remove_var(self, *var): """ Remove given variable or sequence of variables from self. EXAMPLES:: sage: A.<s,t,u> = PowerSeriesRing(ZZ) sage: A.remove_var(t) Multivariate Power Series Ring in s, u over Integer Ring sage: A.remove_var(s,t) Power Series Ring in u over Integer Ring sage: M = PowerSeriesRing(GF(5),5,'t'); M Multivariate Power Series Ring in t0, t1, t2, t3, t4 over Finite Field of size 5 sage: M.remove_var(M.gens()[3]) Multivariate Power Series Ring in t0, t1, t2, t4 over Finite Field of size 5 Removing all variables results in the base ring:: sage: M.remove_var(*M.gens()) Finite Field of size 5 """ vars = list(self.variable_names()) for v in var: vars.remove(str(v)) if len(vars) == 0: return self.base_ring() return PowerSeriesRing(self.base_ring(), names=vars) ## this is defined in PowerSeriesRing_generic # def __call__(self, f, prec=infinity): # """ # Coerce object to this multivariate power series ring. # """ # return def _coerce_impl(self, f): """ Return the canonical coercion of ``f`` into this multivariate power series ring, if one is defined, or raise a TypeError. The rings that canonically coerce to this multivariate power series ring are: - this ring itself - a polynomial or power series ring in the same variables or a subset of these variables (possibly empty), over any base ring that canonically coerces into the base ring of this ring EXAMPLES:: sage: R.<t,u,v> = PowerSeriesRing(QQ); R Multivariate Power Series Ring in t, u, v over Rational Field sage: S1.<t,v> = PolynomialRing(ZZ); S1 Multivariate Polynomial Ring in t, v over Integer Ring sage: f1 = -t*v + 2*v^2 + v; f1 -t*v + 2*v^2 + v sage: R(f1) v - t*v + 2*v^2 sage: S2.<u,v> = PowerSeriesRing(ZZ); S2 Multivariate Power Series Ring in u, v over Integer Ring sage: f2 = -2*v^2 + 5*u*v^2 + S2.O(6); f2 -2*v^2 + 5*u*v^2 + O(u, v)^6 sage: R(f2) -2*v^2 + 5*u*v^2 + O(t, u, v)^6 sage: R2 = R.change_ring(GF(2)) sage: R2(f1) v + t*v sage: R2(f2) u*v^2 + O(t, u, v)^6 TESTS:: sage: R.<t,u,v> = PowerSeriesRing(QQ) sage: S1.<t,v> = PolynomialRing(ZZ) sage: f1 = S1.random_element() sage: g1 = R._coerce_impl(f1) sage: f1.parent() == R False sage: g1.parent() == R True """ P = f.parent() if is_MPolynomialRing(P) or is_MPowerSeriesRing(P) \ or is_PolynomialRing(P) or is_PowerSeriesRing(P): if set(P.variable_names()).issubset(set(self.variable_names())): if self.has_coerce_map_from(P.base_ring()): return self(f) else: return self._coerce_try(f, [self.base_ring()]) def _is_valid_homomorphism_(self, codomain, im_gens): """ Replacement for method of PowerSeriesRing_generic. To be valid, a homomorphism must send generators to elements of positive valuation or to nilpotent elements. Note that the method is_nilpotent doesn't (as of sage 4.4) seem to be defined for obvious examples (matrices, quotients of polynomial rings). EXAMPLES:: sage: R.<a,b,c> = PowerSeriesRing(Zmod(8)); R Multivariate Power Series Ring in a, b, c over Ring of integers modulo 8 sage: M = PowerSeriesRing(ZZ,3,'x,y,z'); sage: M._is_valid_homomorphism_(R,[a,c,b]) True sage: M._is_valid_homomorphism_(R,[0,c,b]) True 2 is nilpotent in `ZZ/8`, but 3 is not:: sage: M._is_valid_homomorphism_(R,[2,c,b]) True sage: M._is_valid_homomorphism_(R,[3,c,b]) False Over `ZZ`, 2 is not nilpotent:: sage: S = R.change_ring(ZZ); S Multivariate Power Series Ring in a, b, c over Integer Ring sage: M._is_valid_homomorphism_(S,[a,c,b]) True sage: M._is_valid_homomorphism_(S,[0,c,b]) True sage: M._is_valid_homomorphism_(S,[2,c,b]) False sage: g = [S.random_element(10)*v for v in S.gens()] sage: M._is_valid_homomorphism_(S,g) True """ try: im_gens = [codomain(v) for v in im_gens] except TypeError: raise TypeError( "The given generator images do not coerce to codomain.") if len(im_gens) is not self.ngens(): raise ValueError("You must specify the image of each generator.") if all(v == 0 for v in im_gens): return True if is_MPowerSeriesRing(codomain) or is_PowerSeriesRing(codomain): try: B = all(v.valuation() > 0 or v.is_nilpotent() for v in im_gens) except NotImplementedError: B = all(v.valuation() > 0 for v in im_gens) return B if is_CommutativeRing(codomain): return all(v.is_nilpotent() for v in im_gens) def _coerce_map_from_(self, P): """ The rings that canonically coerce to this multivariate power series ring are: - this ring itself - a polynomial or power series ring in the same variables or a subset of these variables (possibly empty), over any base ring that canonically coerces into this ring - any ring that coerces into the foreground polynomial ring of this ring EXAMPLES:: sage: A = GF(17)[['x','y']] sage: A.has_coerce_map_from(ZZ) True sage: A.has_coerce_map_from(ZZ['x']) True sage: A.has_coerce_map_from(ZZ['y','x']) True sage: A.has_coerce_map_from(ZZ[['x']]) True sage: A.has_coerce_map_from(ZZ[['y','x']]) True sage: A.has_coerce_map_from(ZZ['x','z']) False sage: A.has_coerce_map_from(GF(3)['x','y']) False sage: A.has_coerce_map_from(Frac(ZZ['y','x'])) False TESTS:: sage: M = PowerSeriesRing(ZZ,3,'x,y,z'); sage: M._coerce_map_from_(M) True sage: M._coerce_map_from_(M.remove_var(x)) True sage: M._coerce_map_from_(PowerSeriesRing(ZZ,x)) True sage: M._coerce_map_from_(PolynomialRing(ZZ,'x,z')) True sage: M._coerce_map_from_(PolynomialRing(ZZ,0,'')) True sage: M._coerce_map_from_(ZZ) True sage: M._coerce_map_from_(Zmod(13)) False sage: M._coerce_map_from_(PolynomialRing(ZZ,2,'x,t')) False sage: M._coerce_map_from_(PolynomialRing(Zmod(11),2,'x,y')) False sage: P = PolynomialRing(ZZ,3,'z') sage: H = PowerSeriesRing(P,4,'f'); H Multivariate Power Series Ring in f0, f1, f2, f3 over Multivariate Polynomial Ring in z0, z1, z2 over Integer Ring sage: H._coerce_map_from_(P) True sage: H._coerce_map_from_(P.remove_var(P.gen(1))) True sage: H._coerce_map_from_(PolynomialRing(ZZ,'z2,f0')) True """ if is_MPolynomialRing(P) or is_MPowerSeriesRing(P) \ or is_PolynomialRing(P) or is_PowerSeriesRing(P): if set(P.variable_names()).issubset(set(self.variable_names())): if self.has_coerce_map_from(P.base_ring()): return True return self._poly_ring().has_coerce_map_from(P) def _element_constructor_(self, f, prec=None): """ TESTS:: sage: M = PowerSeriesRing(ZZ,5,'t'); sage: t = M.gens(); sage: m = -2*t[0]*t[3]^6*t[4] - 12*t[0]^2*t[3]*t[4]^6 + t[1]*t[2]*t[3]^4*t[4]^3 + M.O(10) sage: M._element_constructor_(m) -2*t0*t3^6*t4 - 12*t0^2*t3*t4^6 + t1*t2*t3^4*t4^3 + O(t0, t1, t2, t3, t4)^10 sage: R = PolynomialRing(ZZ,5,'t') sage: t = R.gens() sage: p = -4*t[0]*t[4] + t[1]^2 + t[1]*t[2] - 6*t[2]*t[4] - t[3]*t[4] sage: M._element_constructor_(p) -4*t0*t4 + t1^2 + t1*t2 - 6*t2*t4 - t3*t4 sage: p.parent() Multivariate Polynomial Ring in t0, t1, t2, t3, t4 over Integer Ring sage: M._element_constructor_(p).parent() Multivariate Power Series Ring in t0, t1, t2, t3, t4 over Integer Ring """ if prec is None: try: prec = f.prec() except AttributeError: prec = infinity return self.element_class(parent=self, x=f, prec=prec) def __cmp__(self, other): """ Compare this multivariate power series ring to something else. Power series rings are considered equal if the base ring, variable names, and default truncation precision are the same. Note that we don't compare term-ordering. First the base rings are compared, then the variable names, then the default precision. EXAMPLES:: sage: R.<t,u> = PowerSeriesRing(ZZ) sage: S.<t,u> = PowerSeriesRing(ZZ) sage: R is S True sage: R == S True sage: S.<t,u> = PowerSeriesRing(ZZ, default_prec=30) sage: R == S False sage: PowerSeriesRing(QQ,3,'t') == PowerSeriesRing(ZZ,3,'t') False sage: PowerSeriesRing(QQ,5,'t') == 5 False """ if not isinstance(other, MPowerSeriesRing_generic): return -1 c = cmp(self.base_ring(), other.base_ring()) if c: return c c = cmp(self.variable_names(), other.variable_names()) if c: return c c = cmp(self.default_prec(), other.default_prec()) if c: return c return 0 def laurent_series_ring(self): """ Laruent series not yet implemented for multivariate power series rings TESTS:: sage: M = PowerSeriesRing(ZZ,3,'x,y,z'); sage: M.laurent_series_ring() Traceback (most recent call last): ... NotImplementedError: Laurent series not implemented for multivariate power series. """ raise NotImplementedError( "Laurent series not implemented for multivariate power series.") def _poly_ring(self, x=None): """ Return the underlying polynomial ring used to represent elements of this power series ring. If given an input x, returns x coerced into this polynomial ring. EXAMPLES:: sage: R.<t,u> = PowerSeriesRing(QQ) sage: R._poly_ring() Multivariate Polynomial Ring in t, u over Rational Field sage: R._poly_ring(2).parent() Multivariate Polynomial Ring in t, u over Rational Field """ if x is None: return self._poly_ring_ else: return self._poly_ring_(x) def _mpoly_ring(self, x=None): """ Same as _poly_ring TESTS:: sage: R.<t,u> = PowerSeriesRing(QQ) sage: R._mpoly_ring() Multivariate Polynomial Ring in t, u over Rational Field sage: R._mpoly_ring(2).parent() Multivariate Polynomial Ring in t, u over Rational Field """ return self._poly_ring(x) def _bg_ps_ring(self, x=None): """ Return the background univariate power series ring. If given an input x, returns x coerced into this power series ring. EXAMPLES:: sage: R.<t,u> = PowerSeriesRing(QQ) sage: R._bg_ps_ring() Power Series Ring in Tbg over Multivariate Polynomial Ring in t, u over Rational Field sage: R._bg_ps_ring(4).parent() == R False """ if x is None: return self._bg_power_series_ring else: return self._bg_power_series_ring(x) def is_sparse(self): """ Is self sparse? EXAMPLES:: sage: M = PowerSeriesRing(ZZ,3,'s,t,u'); M Multivariate Power Series Ring in s, t, u over Integer Ring sage: M.is_sparse() False sage: N = PowerSeriesRing(ZZ,3,'s,t,u',sparse=True); N Sparse Multivariate Power Series Ring in s, t, u over Integer Ring sage: N.is_sparse() True """ return self._is_sparse def is_dense(self): """ Is self dense? (opposite of sparse) EXAMPLES:: sage: M = PowerSeriesRing(ZZ,3,'s,t,u'); M Multivariate Power Series Ring in s, t, u over Integer Ring sage: M.is_dense() True sage: N = PowerSeriesRing(ZZ,3,'s,t,u',sparse=True); N Sparse Multivariate Power Series Ring in s, t, u over Integer Ring sage: N.is_dense() False """ return not self.is_sparse() def gen(self, n=0): """ Return the nth generator of self. EXAMPLES:: sage: M = PowerSeriesRing(ZZ,10,'v'); sage: M.gen(6) v6 """ if n < 0 or n >= self._ngens: raise ValueError("Generator not defined.") #return self(self._poly_ring().gens()[int(n)]) return self.element_class(parent=self, x=self._poly_ring().gens()[int(n)], is_gen=True) def ngens(self): """ Return number of generators of self. EXAMPLES:: sage: M = PowerSeriesRing(ZZ,10,'v'); sage: M.ngens() 10 """ return self._ngens def prec_ideal(self): """ Return the ideal which determines precision; this is the ideal generated by all of the generators of our background polynomial ring. EXAMPLES:: sage: A.<s,t,u> = PowerSeriesRing(ZZ) sage: A.prec_ideal() Ideal (s, t, u) of Multivariate Polynomial Ring in s, t, u over Integer Ring """ return self._poly_ring().ideal(self._poly_ring().gens()) def bigoh(self, prec): """ Return big oh with precision ``prec``. The function ``O`` does the same thing. EXAMPLES:: sage: T.<a,b> = PowerSeriesRing(ZZ,2); T Multivariate Power Series Ring in a, b over Integer Ring sage: T.bigoh(10) 0 + O(a, b)^10 sage: T.O(10) 0 + O(a, b)^10 """ return self(0).O(prec) def O(self, prec): """ Return big oh with precision ``prec``. This function is an alias for ``bigoh``. EXAMPLES:: sage: T.<a,b> = PowerSeriesRing(ZZ,2); T Multivariate Power Series Ring in a, b over Integer Ring sage: T.O(10) 0 + O(a, b)^10 sage: T.bigoh(10) 0 + O(a, b)^10 """ return self.bigoh(prec) def _send_to_bg(self, f): """ Send an element of the foreground polynomial ring to the background power series ring. EXAMPLES:: sage: M = PowerSeriesRing(QQ,4,'f') sage: f = M._poly_ring().gens() sage: bg = M._send_to_bg((f[0] + f[2] + 2)**2); bg 4 + (4*f0 + 4*f2)*Tbg + (f0^2 + 2*f0*f2 + f2^2)*Tbg^2 sage: M._send_to_bg(bg) Traceback (most recent call last): ... TypeError: Cannot coerce input to polynomial ring. """ try: f = self._poly_ring(f) except TypeError: raise TypeError("Cannot coerce input to polynomial ring.") fg_to_bg_dict = dict((v, v * self._bg_ps_ring().gen()) for v in self._poly_ring().gens()) return self._bg_ps_ring(f.subs(fg_to_bg_dict)) def _send_to_fg(self, f): """ Send an element of the background univariate power series ring to the foreground multivariate polynomial ring. EXAMPLES:: sage: M = PowerSeriesRing(QQ,4,'f') sage: f = M._poly_ring().gens() sage: bg = M._send_to_bg((f[0] + f[2] + 2)**2); bg 4 + (4*f0 + 4*f2)*Tbg + (f0^2 + 2*f0*f2 + f2^2)*Tbg^2 sage: bg.parent() Power Series Ring in Tbg over Multivariate Polynomial Ring in f0, f1, f2, f3 over Rational Field sage: fg = M._send_to_fg(bg); fg 4 + 4*f0 + 4*f2 + f0^2 + 2*f0*f2 + f2^2 sage: fg.parent() Multivariate Polynomial Ring in f0, f1, f2, f3 over Rational Field sage: fg = M._send_to_fg(bg.add_bigoh(3)); fg 4 + 4*f0 + 4*f2 + f0^2 + 2*f0*f2 + f2^2 sage: fg = M._send_to_fg(bg.add_bigoh(2)); fg 4 + 4*f0 + 4*f2 """ return self._poly_ring(f.polynomial().subs({self._bg_indeterminate: 1}))
class BianchiDistributions(Module, UniqueRepresentation): r""" This class represents the overconvergent approximation modules used to describe p-adic overconvergent Bianchi modular symbols. INPUT: - ``p`` - integer Prime with which we work - ``depth`` - integer (Default: None) Precision to which we work; work with moments x^iy^j for i,j up to the depth - ``act_on_left`` - boolean, (Default: False) Encodes whether Sigma_0(p)^2 is acting on the left or right. - ``adjuster`` - Sigma0ActionAdjuster class (Default: _default_adjuster()) If using a different convention for matrix actions, tell the code where a,b,c,d w should be mapped to. AUTHORS: - Marc Masdeu (2018-08-14) - Chris Williams (2018-08-16) """ def __init__(self, p, depth, act_on_left=False, adjuster=None): self._dimension = 0 ## Hack!! Dimension was being called before it was intialised self._Rmod = ZpCA(p, depth - 1) ## create Zp Module.__init__(self, base=self._Rmod) self.Element = BianchiDistributionElement self._R = ZZ self._p = p self._depth = depth self._pN = self._p**(depth - 1) self._cache_powers = dict() self._unset_coercions_used() ## Initialise monoid Sigma_0(p) + action; use Pollack-Stevens modular symbol code ## our_adjuster() is set above to allow different conventions if adjuster is None: adjuster = _default_adjuster() self._adjuster = adjuster ## Power series ring for representing distributions as strings self._repr_R = PowerSeriesRing(self._R, num_gens=2, default_prec=self._depth, names='X,Y') self._Sigma0Squared = Sigma0Squared(self._p, self._Rmod, adjuster) self._act = Sigma0SquaredAction(self._Sigma0Squared, self, act_on_left=act_on_left) self.register_action(self._act) self._populate_coercion_lists_() ## Initialise dictionaries of indices to translate between pairs and index for moments self._index = dict() self._ij = [] m = 0 ## Populate dictionary/array giving index of the basis element corr. to tuple (i,j), 0 <= i,j <= depth = n ## These things are ordered by degree of y, then degree of x: [1, x, x^2, ..., y, xy, ... ] for j in range(depth): for i in range(depth): self._ij.append((i, j)) self._index[(i, j)] = m m += 1 self._dimension = m ## Number of moments we store ## Power series ring Zp[[x,y]]. We have to work with monomials up to x^depth * y^depth, so need prec = 2*depth self._PowerSeries_x = PowerSeriesRing(self._Rmod, default_prec=self._depth, names='x') self._PowerSeries_x_ZZ = PowerSeriesRing(ZZ, default_prec=self._depth, names='x') self._PowerSeries = PowerSeriesRing(self._PowerSeries_x, default_prec=self._depth, names='y') self._PowerSeries_ZZ = PowerSeriesRing(self._PowerSeries_x_ZZ, default_prec=self._depth, names='y') def index(self, ij): r""" Function to return index of a tuple (i,j). Input: - ij (tuple) : pair (i,j) Returns: Place in ordered basis corresponding to x^iy^j. """ return self._index[tuple(ij)] def ij_from_pos(self, n): r""" From position in the ordered basis, returns corr. tuple (n,i) Input: - n (int) : position in basis. Returns: pair (i,j) s.t. the nth basis vector is x^iy^j """ return self._ij[n] def monomial_from_index(self, n, R=None): """ Takes index and returns the corresponding monomial in the basis """ X, Y = self._repr_R.gens() if isinstance(n, tuple): (i, j) = n else: i, j = self._ij[n] return X**i * Y**j def basis_vector(self, ij): """ Returns the (i,j)th basis vector (in the dual basis), which takes the monomial x^iy^j to 1 and every other monomial to 0. EXAMPLES:: sage: from darmonpoints.ocbianchi import BianchiDistributions sage: D = BianchiDistributions(11,4) sage: D.basis_vector((2,3)) X^2*Y^3 sage: D.basis_vector(5) X*Y """ moments = vector(ZZ, [0 for i in range(self._dimension)]) if isinstance(ij, tuple): index = self.index(ij) moments[index] = 1 else: moments[ij] = 1 return self(moments) def analytic_functions(self): r""" Returns underlying power series of rigid analytic functions, that is, the space on which a distribution should take values. """ return self._PowerSeries def analytic_vars(self): r""" Returns x,y, the variables of the underlying ring of analytic functions. """ x = self.analytic_functions()(self._PowerSeries_x.gen()) y = self.analytic_functions().gen() return x, y def Sigma0Squared(self): r""" Returns underlying monoid Sigma_0(p)^2. """ return self._Sigma0Squared def Sigma0(self): r""" Returns underlying monoid Sigma_0(p)^2. """ return self._Sigma0Squared def approx_module(self, M=None): if M is None: M = self.dimension() return MatrixSpace(self._R, M, 1) def clear_cache(self): del self._cache_powers self._cache_powers = dict() def is_overconvergent(self): return True def _an_element_(self): r""" """ return BianchiDistributionElement(self, Matrix(self._R, self._dimension, 1, range(1, self._dimension + 1)), check=False) def _coerce_map_from_(self, S): # Nothing coherces here, except BianchiDistributionElement return False def _element_constructor_(self, x, check=True, normalize=False): #Code how to coherce x into the space #Admissible values of x? return BianchiDistributionElement(self, x) def acting_matrix(self, g, M): G = g.parent() qrep = G.quaternion_to_matrix(g) qrep_bar = qrep.apply_map(lambda x: x.trace() - x) first, second = qrep.apply_map(G._F_to_local), qrep_bar.apply_map( G._F_to_local) return self._get_powers(self.Sigma0Squared()(first, second)) def _get_powers(self, g, emb=None): r""" Auxiliary function to compute the Sigma_0(p)^2 action on moments. The action sends a monomial x^i to (gx)^i, where gx = (b+dx)/(a+cx). The action on two-variable functions is simply the product of two copies of the one variable action. Input: - g : Sigma0SquaredElement object (in the relevant Sigma_0(p)^2 group) Returns: matrix of (g_x,g_y) acting on distributions in the basis given by monomials EXAMPLES:: sage: from darmonpoints.ocbianchi import BianchiDistributions sage: D = BianchiDistributions(11,2) sage: h = D.Sigma0Squared()([1,1,0,1],[1,1,0,1]) sage: D._get_powers(h) [1 0 0 0] [1 1 0 0] [1 0 1 0] [1 1 1 1] sage: h = D.Sigma0Squared()([2,3,11,1],[12,1,22,1]) sage: D._get_powers(h) [1 0 0 0] [7 6 0 0] [1 0 1 0] [7 6 7 6] """ ## We want to ultimately compute actions on distributions. The matrix describing the (left) ## action of g on distributions is the transpose of the action of adjoint(g) acting on the (left) ## of analytic functions, so we start by taking adjoints. Then put the matrix entries into lists ## NOTE: First apply the adjuster defined above; permutes a,b,c,d to allow for different conventions. abcdx = g.first_element() abcdy = g.second_element() ## Adjust for action: change of convention is encoded in our_adjuster class above adjuster = self._adjuster abcdx = adjuster(abcdx.matrix()) abcdy = adjuster(abcdy.matrix()) ## We want better keys; keys in Zp are not great. Store them instead in ZZ abcdxZZ = tuple(ZZ(t) for t in abcdx) abcdyZZ = tuple(ZZ(t) for t in abcdy) ## check to see if the action of (g,h) has already been computed and cached try: return self._cache_powers[(abcdxZZ, abcdyZZ)] except KeyError: pass ## Sanity check output verbose('The element [{},{}] has not been stored. Computing:'.format( abcdxZZ, abcdyZZ), level=2) R = self._PowerSeries ## Zp[[x,y] y = R.gen() x = R.base_ring().gen() ## get values of a,b,c,d for x and y if emb is None: a, b, c, d = abcdx A, B, C, D = abcdy else: gg = emb(abcdy) a, b, c, d = gg[0].list() A, B, C, D = gg[1].list() ## Initialise terms num_x = b + d * x ## b + az + O(11^depth)R denom_x = a + c * x ## d + cz + O(11^depth)R num_y = B + D * x denom_y = A + C * x ## Ratios r = R.base_ring()(num_x / denom_x) ## after action on x s = num_y / denom_y ## after action on y r = r.change_ring(ZZ) s = s.change_ring(ZZ) RZ = self._PowerSeries_ZZ phi = s.parent().hom([RZ.gen()]) ## Constant term const = r.parent()(1) spows = [const] for n in range(self._depth): spows.append(s * spows[-1]) acted_monomials = {} for j in range(self._depth): acted_monomials[(0, j)] = phi(spows[j]) rpow = 1 for i in range(1, self._depth): rpow *= r rpow.add_bigoh(self._depth) for j in range(self._depth): acted_monomials[(i, j)] = rpow * phi(spows[j]) matrix_rows = [] for n in range(self._dimension): f = acted_monomials[tuple(self.ij_from_pos(n))] new_row = [] for polx in f.padded_list(self._depth): new_row.extend(polx.padded_list(self._depth)) matrix_rows.append(new_row) ## Build matrix . DO NOT TAKE TRANSPOSE, (built this in as a consequence of implementation) matrix_action = Matrix(ZZ, matrix_rows) #acted_monomials_list = Matrix(R.base_ring(),self._depth,self._depth,acted_monomials_list)#.apply_map(ZZ) self._cache_powers[(abcdxZZ, abcdyZZ)] = matrix_action return matrix_action def _repr_(self): r""" This returns the representation of self as a string. """ return "Space of %s-adic Bianchi distributions with k=0 action and precision cap %s" % ( self._p, self._dimension - 1) def prime(self): r""" Returns underlying prime. """ return self._p def basis(self): r""" A basis of the module. Returns all monomials in x,y of degree (in each variable) up to the specified depth-1. """ try: return self._basis except: pass self._basis = [ BianchiDistributionElement(self, Matrix(self._R, self._dimension, 1, {(jj, 0): 1}, sparse=False), check=False) for jj in range(self._dimension) ] return self._basis def base_ring(self): r""" This function returns the base ring of the overconvergent element. """ return self._Rmod def depth(self): r""" Returns the depth of the module. If the depth is d, then a basis for the approximation modules is x^iy^j with i,j in {0,...,d-1}. """ return self._depth def dimension(self): r""" Returns the dimension (rank) of the module. """ return self._dimension def precision_cap(self): r""" Returns the depth of the module. If the depth is d, then a basis for the approximation modules is x^iy^j with i,j in {0,...,d-1}. """ return self._depth def is_exact(self): r""" All distributions are finite approximations. They are only exact as elements of D/Fil^{d,d}D, where d is the depth. """ return False