From 1b85377ded1713a21b9329f2a00fbd9fb729940b Mon Sep 17 00:00:00 2001 From: Marc Masdeu Date: Tue, 25 Feb 2014 17:06:57 +0000 Subject: [PATCH] Initial commit. Cloned from experimental branch of roed314's github. --- src/sage/modular/btquotients/__init__.py | 0 src/sage/modular/btquotients/all.py | 3 + src/sage/modular/btquotients/btquotient.py | 3603 +++++++++++++++++ src/sage/modular/btquotients/ocmodule.py | 557 +++ .../modular/btquotients/pautomorphicform.py | 2540 ++++++++++++ src/sage/modular/btquotients/utility.py | 295 ++ src/sage/modular/pollack_stevens/__init__.py | 0 src/sage/modular/pollack_stevens/all.py | 5 + src/sage/modular/pollack_stevens/dist.pxd | 66 + src/sage/modular/pollack_stevens/dist.pyx | 1872 +++++++++ .../modular/pollack_stevens/distributions.py | 766 ++++ .../modular/pollack_stevens/fund_domain.py | 1581 ++++++++ src/sage/modular/pollack_stevens/manin_map.py | 924 +++++ src/sage/modular/pollack_stevens/modsym.py | 1546 +++++++ .../modular/pollack_stevens/padic_lseries.py | 465 +++ src/sage/modular/pollack_stevens/sigma0.py | 492 +++ src/sage/modular/pollack_stevens/space.py | 1016 +++++ 17 files changed, 15731 insertions(+) create mode 100644 src/sage/modular/btquotients/__init__.py create mode 100644 src/sage/modular/btquotients/all.py create mode 100644 src/sage/modular/btquotients/btquotient.py create mode 100644 src/sage/modular/btquotients/ocmodule.py create mode 100644 src/sage/modular/btquotients/pautomorphicform.py create mode 100644 src/sage/modular/btquotients/utility.py create mode 100644 src/sage/modular/pollack_stevens/__init__.py create mode 100644 src/sage/modular/pollack_stevens/all.py create mode 100644 src/sage/modular/pollack_stevens/dist.pxd create mode 100644 src/sage/modular/pollack_stevens/dist.pyx create mode 100644 src/sage/modular/pollack_stevens/distributions.py create mode 100644 src/sage/modular/pollack_stevens/fund_domain.py create mode 100644 src/sage/modular/pollack_stevens/manin_map.py create mode 100644 src/sage/modular/pollack_stevens/modsym.py create mode 100644 src/sage/modular/pollack_stevens/padic_lseries.py create mode 100644 src/sage/modular/pollack_stevens/sigma0.py create mode 100644 src/sage/modular/pollack_stevens/space.py diff --git a/src/sage/modular/btquotients/__init__.py b/src/sage/modular/btquotients/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/sage/modular/btquotients/all.py b/src/sage/modular/btquotients/all.py new file mode 100644 index 00000000000..4ed8cf8a741 --- /dev/null +++ b/src/sage/modular/btquotients/all.py @@ -0,0 +1,3 @@ +from btquotient import BTQuotient, DoubleCosetReduction +from pautomorphicform import HarmonicCocycleElement, HarmonicCocycles, pAutomorphicFormElement, pAutomorphicForms +from ocmodule import OCVn,OCVnElement diff --git a/src/sage/modular/btquotients/btquotient.py b/src/sage/modular/btquotients/btquotient.py new file mode 100644 index 00000000000..be1c1aee7b5 --- /dev/null +++ b/src/sage/modular/btquotients/btquotient.py @@ -0,0 +1,3603 @@ +######################################################################### +# Copyright (C) 2011 Cameron Franc and Marc Masdeu +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# http://www.gnu.org/licenses/ +######################################################################### +r""" +Compute arithmetic quotients of the Bruhat-Tits tree + +""" +from sage.rings.integer import Integer +from sage.structure.element import Element +from sage.matrix.constructor import Matrix +from sage.matrix.matrix_space import MatrixSpace +from sage.structure.sage_object import SageObject +from sage.rings.all import ZZ,Zmod,QQ +from sage.misc.latex import latex +from sage.plot import plot +from sage.rings.padics.precision_error import PrecisionError +from itertools import islice +import collections +from sage.misc.misc_c import prod +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.cachefunc import cached_method +from sage.rings.arith import gcd,xgcd,kronecker_symbol +from sage.rings.padics.all import Qp,Zp +from sage.algebras.quatalg.all import QuaternionAlgebra +from sage.quadratic_forms.all import QuadraticForm +from sage.graphs.all import Graph +from sage.libs.all import pari +from sage.interfaces.all import magma +from copy import copy +from sage.plot.colors import rainbow +from sage.rings.number_field.all import NumberField +from sage.modular.arithgroup.all import Gamma0 +from sage.misc.lazy_attribute import lazy_attribute +from sage.modular.dirichlet import DirichletGroup +from sage.modular.arithgroup.congroup_gammaH import GammaH_class +from sage.rings.arith import fundamental_discriminant +from sage.misc.misc import verbose, cputime + +class DoubleCosetReduction(SageObject): + r""" + Edges in the Bruhat-tits tree are represented by cosets of + matrices in `\GL_2`. Given a matrix `x` in `\GL_2`, this + class computes and stores the data corresponding to the + double coset representation of `x` in terms of a fundamental + domain of edges for the action of the arithmetic group `\Gamma'. + + More precisely: + Initialized with an element `x` of `\GL_2(\ZZ)`, finds elements + `\gamma` in `\Gamma`, `t` and an edge `e` such that `get=x`. It + stores these values as members ``gamma``, ``label`` and functions + ``self.sign()``, ``self.t()`` and ``self.igamma()``, satisfying: + if ``self.sign()==+1``: + ``igamma()*edge_list[label].rep*t()==x`` + if ``self.sign()==-1``: + ``igamma()*edge_list[label].opposite.rep*t()==x`` + + It also stores a member called power so that: + ``p**(2*power)=gamma.reduced_norm()`` + + The usual decomposition ``get=x`` would be: + g=gamma/(p**power) + e=edge_list[label] + t'=t*p**power + Here usual denotes that we've rescaled gamma to have unit + determinant, and so that the result is honestly an element + of the arithmetic quarternion group under consideration. In + practice we store integral multiples and keep track of the + powers of `p`. + + INPUT: + + - ``Y`` - BTQuotient object in which to work + - ``x`` - Something coercible into a matrix in `\GL_2(\ZZ)`. In + principle we should allow elements in `\GL_2(\QQ_p)`, but it is + enough to work with integral entries + - ``extrapow`` - gets added to the power attribute, and it is + used for the Hecke action. + + EXAMPLES:: + + sage: from sage.modular.btquotients.btquotient import DoubleCosetReduction + sage: Y = BTQuotient(5,13) + sage: x = Matrix(ZZ,2,2,[123,153,1231,1231]) + sage: d = DoubleCosetReduction(Y,x) + sage: d.sign() + -1 + sage: d.igamma()*Y._edge_list[d.label - len(Y.get_edge_list())].opposite.rep*d.t()==x + True + sage: x = Matrix(ZZ,2,2,[1423,113553,11231,12313]) + sage: d = DoubleCosetReduction(Y,x) + sage: d.sign() + 1 + sage: d.igamma()*Y._edge_list[d.label].rep*d.t()==x + True + + AUTHORS: + + - Cameron Franc (2012-02-20) + - Marc Masdeu + + """ + def __init__(self,Y,x,extrapow=0): + r""" + Initializes and computes the reduction as a double coset. + + EXAMPLES:: + + sage: Y = BTQuotient(5,13) + sage: x = Matrix(ZZ,2,2,[123,153,1231,1231]) + sage: d = DoubleCosetReduction(Y,x) + sage: TestSuite(d).run() + """ + e1=Y._BT.edge(x) + try: + g,label,parity=Y._cached_decomps[e1] + except KeyError: + valuation=e1.determinant().valuation(Y._p) + parity=valuation%2 + v1=Y._BT.target(e1) + v=Y.fundom_rep(v1) + g,e=Y._find_equivalent_edge(e1,v.entering_edges,valuation=valuation) + label=e.label + Y._cached_decomps[e1]=(g,label,parity) + + self._parent=Y + self.parity=parity + self._num_edges = len(Y.get_edge_list()) + self.label=label + parity * self._num_edges # The label will encode whether it is an edge or its opposite ! + self.gamma=g[0] + self.x=x + self.power=g[1]+extrapow + self._t_prec=-1 + self._igamma_prec=-1 + + def _repr_(self): + r""" + Returns the representation of self as a string. + + EXAMPLES:: + + sage: Y = BTQuotient(5,13) + sage: x = Matrix(ZZ,2,2,[123,153,1231,1231]) + sage: DoubleCosetReduction(Y,x) + DoubleCosetReduction + """ + return "DoubleCosetReduction" + + def __cmp__(self,other): + """ + Return self == other + + TESTS:: + + sage: Y = BTQuotient(5,13) + sage: x = Matrix(ZZ,2,2,[123,153,1231,1231]) + sage: d1 = DoubleCosetReduction(Y,x) + sage: d1 == d1 + True + """ + c = cmp(self._parent,other._parent) + if c: return c + c = cmp(self.parity,other.parity) + if c: return c + c = cmp(self._num_edges,other._num_edges) + if c: return c + c = cmp(self.label,other.label) + if c: return c + c = cmp(self.gamma,other.gamma) + if c: return c + c = cmp(self.x,other.x) + if c: return c + c = cmp(self.power,other.power) + if c: return c + c = cmp(self._t_prec,other._t_prec) + if c: return c + c = cmp(self._igamma_prec,other._igamma_prec) + if c: return c + return 0 + + def sign(self): + r""" + The direction of the edge. + + The BT quotients are directed graphs but we only store + half the edges (we treat them more like unordered graphs). + The sign tells whether the matrix self.x is equivalent to the + representative in the quotient (sign = +1), or to the + opposite of one of the representatives (sign = -1). + + OUTPUT : + + - an int that is +1 or -1 according to the sign of self + + EXAMPLES:: + + sage: Y = BTQuotient(3,11) + sage: x = Matrix(ZZ,2,2,[123,153,1231,1231]) + sage: d = DoubleCosetReduction(Y,x) + sage: d.sign() + -1 + sage: d.igamma()*Y._edge_list[d.label - len(Y.get_edge_list())].opposite.rep*d.t()==x + True + sage: x = Matrix(ZZ,2,2,[1423,113553,11231,12313]) + sage: d = DoubleCosetReduction(Y,x) + sage: d.sign() + 1 + sage: d.igamma()*Y._edge_list[d.label].rep*d.t()==x + True + """ + if self.parity == 0: + return 1 + else: + return -1 + + def igamma(self,embedding = None, scale = 1): + r""" + Image under gamma. + + Elements of the arithmetic group can be regarded as elements + of the global quarterion order, and hence may be represented + exactly. This function computes the image of such an element + under the local splitting and returns the corresponding p-adic + approximation. + + INPUT: + + - ``embedding`` - an integer, or a function (Default: + none). If ``embedding`` is None, then the image of + ``self.gamma`` under the local splitting associated to + ``self.Y`` is used. If ``embedding`` is an integer, then + the precision of the local splitting of self.Y is raised + (if necessary) to be larger than this integer, and this + new local splitting is used. If a function is passed, then + map ``self.gamma`` under ``embedding``. + + OUTPUT: + + - ``cached_igamma`` - a 2x2 matrix with p-adic entries + encoding the image of self under the local splitting + + EXAMPLES:: + + sage: from sage.modular.btquotients.btquotient import DoubleCosetReduction + sage: Y = BTQuotient(7,11) + sage: d = DoubleCosetReduction(Y,Matrix(ZZ,2,2,[123,45,88,1])) + sage: d.igamma() + [6 + 6*7 + 6*7^2 + 6*7^3 + 6*7^4 + O(7^5) O(7^5)] + [ O(7^5) 6 + 6*7 + 6*7^2 + 6*7^3 + 6*7^4 + O(7^5)] + sage: d.igamma(embedding = 7) + [6 + 6*7 + 6*7^2 + 6*7^3 + 6*7^4 + 6*7^5 + 6*7^6 + O(7^7) O(7^7)] + [ O(7^7) 6 + 6*7 + 6*7^2 + 6*7^3 + 6*7^4 + 6*7^5 + 6*7^6 + O(7^7)] + """ + Y = self._parent + if embedding is None: + prec = Y._prec + else: + try: + # The user wants higher precision + prec = ZZ(embedding) + except TypeError: + # The user knows what she is doing, so let it go + return embedding(self.gamma,scale = scale) + if prec > self._igamma_prec: + self._igamma_prec = prec + self._cached_igamma = Y.embed_quaternion(self.gamma,exact = False, prec = prec) + return scale * self._cached_igamma + + def t(self, prec = None): + r""" + Return the 't part' of the decomposition using the rest of the data. + + INPUT: + + - ``prec`` - a p-adic precision that t will be computed + to. Default is the default working precision of self + + OUTPUT: + + - ``cached_t`` - a 2x2 p-adic matrix with entries of + precision 'prec' that is the 't-part' of the decomposition of + self + + EXAMPLES:: + + sage: from sage.modular.btquotients.btquotient import DoubleCosetReduction + sage: Y = BTQuotient(5,13) + sage: x = Matrix(ZZ,2,2,[123,153,1231,1232]) + sage: d = DoubleCosetReduction(Y,x) + sage: t = d.t(20) + sage: t[1,0].valuation() > 0 + True + """ + Y = self._parent + if prec is None: + prec = max([5,Y._prec]) + if self._t_prec >= prec: + return self._cached_t + e = Y._edge_list[self.label % self._num_edges] + tmp_prec = prec + while self._t_prec < prec: + if self.parity == 0: + self._cached_t = (self.igamma(tmp_prec)*e.rep).inverse()*self.x + # assert self._cached_t[1,0].valuation()>self._cached_t[1,1].valuation() + else: + self._cached_t = (self.igamma(tmp_prec)*e.opposite.rep).inverse()*self.x + # assert self._cached_t[1,0].valuation()>self._cached_t[1,1].valuation() + tmp_prec += 1 + self._t_prec = min([xx.precision_absolute() for xx in self._cached_t.list()]) + return self._cached_t + +class BruhatTitsTree(SageObject, UniqueRepresentation): + r""" + An implementation of the Bruhat-Tits tree for `\GL_2(\QQ_p)`. + + INPUT: + + - ``p`` - a prime number. The corresponding tree is then p+1 regular + + EXAMPLES: + + We create the tree for `\GL_2(\QQ_5)`:: + + sage: from sage.modular.btquotients.btquotient import BruhatTitsTree + sage: p = 5 + sage: T = BruhatTitsTree(p) + sage: m = Matrix(ZZ,2,2,[p**5,p**2,p**3,1+p+p*3]) + sage: e = T.edge(m); e + [ 0 25] + [625 21] + sage: v0 = T.origin(e); v0 + [ 25 0] + [ 21 125] + sage: v1 = T.target(e); v1 + [ 25 0] + [ 21 625] + sage: T.origin(T.opposite(e)) == v1 + True + sage: T.target(T.opposite(e)) == v0 + True + + A value error is raised if a prime is not passed:: + + sage: T = BruhatTitsTree(4) + Traceback (most recent call last): + ... + ValueError: Input (4) must be prime + + AUTHORS: + + - Marc Masdeu (2012-02-20) + """ + def __init__(self,p): + """ + Initializes a BruhatTitsTree object for a given prime p + + EXAMPLES:: + + sage: from sage.modular.btquotients.btquotient import BruhatTitsTree + sage: T = BruhatTitsTree(17) + sage: TestSuite(T).run() + """ + if not(ZZ(p).is_prime()): + raise ValueError, 'Input (%s) must be prime'%p + self._p=ZZ(p) + self._Mat_22=MatrixSpace(ZZ,2,2) + self._mat_p001=self._Mat_22([self._p,0,0,1]) + + def target(self,e,normalized = False): + r""" + Returns the target vertex of the edge represented by the + input matrix e. + + INPUT: + + - ``e`` - a 2x2 matrix with integer entries + + - ``normalized`` - boolean (default: false). If true + then the input matrix is assumed to be normalized. + + OUPUT: + + - ``e`` - 2x2 integer matrix representing the target of + the input edge + + EXAMPLES:: + + sage: from sage.modular.btquotients.btquotient import BruhatTitsTree + sage: T = BruhatTitsTree(7) + sage: T.target(Matrix(ZZ,2,2,[1,5,8,9])) + [1 0] + [0 1] + """ + if normalized: + #then the normalized target vertex is also M and we save some + #row reductions with a simple return + return e + else: + #must normalize the target vertex representative + return self.vertex(e) + + def origin(self, e ,normalized = False): + r""" + Returns the origin vertex of the edge represented by the + input matrix e. + + INPUT: + + - ``e`` - a 2x2 matrix with integer entries + + - ``normalized`` - boolean (default: false). If true + then the input matrix M is assumed to be normalized + + OUTPUT: + + - ``e`` - A 2x2 integer matrix + + EXAMPLES:: + + sage: from sage.modular.btquotients.btquotient import BruhatTitsTree + sage: T = BruhatTitsTree(7) + sage: T.origin(Matrix(ZZ,2,2,[1,5,8,9])) + [1 0] + [1 7] + """ + if not normalized: + #then normalize + x=copy(self.edge(e)) + else: + x=copy(M) + x.swap_columns(0,1) + x.rescale_col(0,self._p) + return self.vertex(x) + + def edge(self,M): + r""" + Normalizes a matrix to the correct normalized edge + representative. + + INPUT: + + - ``M`` - a 2x2 integer matrix + + OUTPUT: + + - ``newM`` - a 2x2 integer matrix + + EXAMPLES:: + + sage: from sage.modular.btquotients.btquotient import BruhatTitsTree + sage: T = BruhatTitsTree(3) + sage: T.edge( Matrix(ZZ,2,2,[0,-1,3,0]) ) + [0 1] + [3 0] + """ + p=self._p + M_orig = M + + def lift(a): + """ + Naively approximates a p-adic integer by a positive integer. + + INPUT: + + - ``a`` - a p-adic integer. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: x = Zp(3)(-17) + sage: lift(x) + 3486784384 + """ + try: return ZZ(a.lift()) + except AttributeError: return ZZ(a) + + if M.base_ring() is not ZZ: + M = M.apply_map(lift,R = ZZ) + + v=min([M[i,j].valuation(p) for i in range(2) for j in range(2)]) + + if v != 0: + M=p**(-v)*M + + m00=M[0,0].valuation(p) + m01=M[0,1].valuation(p) + + if m00 <= m01: + tmp=M.determinant().valuation(p)-m00 + bigpower=p**(1+tmp) + r=M[0,0] + if r != 0: + r/=p**m00 + g,s,_=xgcd(r,bigpower) + r=(M[1,0]*s)%bigpower + newM=self._Mat_22([p**m00,0,r,bigpower/p]) + else: + tmp=M.determinant().valuation(p)-m01 + bigpower=p**tmp + r = M[0,1] + if r!=0: + r/=p**m01 + g,s,_ = xgcd(r,bigpower) + r=(ZZ(M[1,1])*s)%bigpower + newM=self._Mat_22([0,p**m01,bigpower,r]) + newM.set_immutable() + # assert self.is_in_group(M_orig.inverse()*newM, as_edge = True) + return newM + + # This function tests if a given matrix in Gamma0(p) + # + # def is_in_group(self,t,as_edge = True): + # """ + # INPUT: + # - ``t`` - + # - ``as_edge`` - a boolean + + # OUTPUT: + # - `` ``- + + # EXAMPLES:: + # sage: from btquotients.btquotient import BruhatTitsTree + # """ + # v = t.determinant().valuation(self._p) + # t = self._p**(-v)*t + # if any([x.valuation(self._p)<0 for x in t.list()]): + # return False + # if as_edge: + # if t[1,0].valuation(self._p)==0: + # return False + # return True + + def vertex(self,M): + r""" + Normalizes a matrix to the corresponding normalized + vertex representative + + INPUT: + + - ``M`` - 2x2 integer matrix + + OUTPUT: + + - ``newM`` - 2x2 integer matrix + + EXAMPLES:: + sage: from sage.modular.btquotients.btquotient import BruhatTitsTree + sage: p = 5 + sage: T = BruhatTitsTree(p) + sage: m = Matrix(ZZ,2,2,[p**5,p**2,p**3,1+p+p*3]) + sage: e = T.edge(m) + sage: t = m.inverse()*e + sage: scaling = Qp(p,20)(t.determinant()).sqrt() + sage: t = 1/scaling * t + sage: min([t[ii,jj].valuation(p) for ii in range(2) for jj in range(2)]) >= 0 + True + sage: t[1,0].valuation(p) > 0 + True + """ + p=self._p + M_orig = M + def lift(a): + try: return ZZ(a.lift()) + except AttributeError: return ZZ(a) + + if M.base_ring() is not ZZ: + M = M.apply_map(lift,R = ZZ) + + v=min([M[i,j].valuation(p) for i in range(2) for j in range(2)]) + + if v != 0: + M=p**(-v)*M + m00=M[0,0].valuation(p) + m01=M[0,1].valuation(p) + if m01 = Qq(5^2,20) + sage: T.find_containing_affinoid(a) + [1 0] + [0 1] + sage: z = 5*a+3 + sage: v = T.find_containing_affinoid(z).inverse(); v + [ 1 0] + [-2/5 1/5] + + Note that the translate of ``z`` belongs to the standard + affinoid. That is, it is a `p`-adic unit and its reduction + modulo `p` is not in `\FF_p`:: + + sage: gz = (v[0,0]*z+v[0,1])/(v[1,0]*z+v[1,1]); gz + (a + 1) + O(5^19) + sage: gz.valuation() == 0 + True + """ + #Assume z belongs to some extension of QQp. + p=self._p + if(z.valuation()<0): + return self.vertex(self._Mat_22([0,1,p,0])*self.find_containing_affinoid(1/(p*z))) + a=0 + pn=1 + val=z.valuation() + L=[] + for ii in range(val): + L.append(0) + L.extend(z.list()) + for n in range(len(L)): + if(L[n]!=0): + if(len(L[n])>1): + break + if(len(L[n])>0): + a+=pn*L[n][0] + pn*=p + return self.vertex(self._Mat_22([pn,a,0,1])) + + def find_geodesic(self,v1,v2,normalized = True): + r""" + This function computes the geodesic between two vertices + + INPUT: + + - ``v1`` - 2x2 integer matrix representing a vertex + + - ``v2`` - 2x2 integer matrix representing a vertex + + - ``normalized`` - boolean (Default: True) + + OUTPUT: + + ordered list of 2x2 integer matrices representing edges + + EXAMPLES:: + + sage: from sage.modular.btquotients.btquotient import BruhatTitsTree + sage: p = 3 + sage: T = BruhatTitsTree(p) + sage: v1 = T.vertex( Matrix(ZZ,2,2,[p^3, 0, 1, p^1]) ); v1 + [27 0] + [ 1 3] + sage: v2 = T.vertex( Matrix(ZZ,2,2,[p,2,0,p]) ); v2 + [1 0] + [6 9] + sage: T.find_geodesic(v1,v2) + [ + [27 0] [27 0] [9 0] [3 0] [1 0] [1 0] [1 0] + [ 1 3], [ 0 1], [0 1], [0 1], [0 1], [0 3], [6 9] + ] + """ + if not normalized: + v1,v2=self.vertex(v1),self.vertex(v2) + gamma=v2 + vv=self.vertex(gamma.adjoint()*v1) + chain,v0=self.find_path(vv) + return [self.vertex(gamma*x) for x in chain+[v0]] + + def find_covering(self,z1,z2,level = 0): + r""" + Computes a covering of P1(Qp) adapted to a certain + geodesic in self. + + More precisely, the `p`-adic upper half plane points ``z1`` + and ``z2`` reduce to vertices `v_1`, `v_2`. + The returned covering consists of all the edges leaving the + geodesic from `v_1` to `v_2`. + + INPUT: + + - ``z1``, ``z2`` - unramified algebraic points of h_p + + OUTPUT: + + a list of 2x2 integer matrices representing edges of self + + EXAMPLES:: + + sage: from sage.modular.btquotients.btquotient import BruhatTitsTree + sage: p = 3 + sage: K. = Qq(p^2) + sage: T = BruhatTitsTree(p) + sage: z1 = a + a*p + sage: z2 = 1 + a*p + a*p^2 - p^6 + sage: T.find_covering(z1,z2) + [ + [0 1] [3 0] [0 1] [0 1] [0 1] [0 1] + [3 0], [0 1], [3 2], [9 1], [9 4], [9 7] + ] + + NOTES: + + This function is used to compute certain Coleman integrals + on `\PP^1`. That's why the input consists of two points of + the `p`-adic upper half plane, but decomposes + `\PP^1(\QQ_p)`. This decomposition is what allows us to + represent the relevant integrand as a locally analytic + function. The ``z1`` and ``z2`` appear in the integrand. + """ + v1=self.find_containing_affinoid(z1) + v2=self.find_containing_affinoid(z2) + vertex_set=[self._Mat_22(0)]+self.find_geodesic(v1,v2)+[self._Mat_22(0)] + total_dist = len(vertex_set) - 3 + E=[] + for ii in range(1,len(vertex_set)-1): + vv=vertex_set[ii] + m = vv.determinant().valuation(self._p) + newE=self.leaving_edges(vv) + for e in newE: + targ = self.target(e) + if targ!=vertex_set[ii-1] and targ != vertex_set[ii+1]: + E.extend(self.subdivide([e],level)) + return E + + +class Vertex(SageObject): + r""" + This is a structure to represent vertices of quotients of the + Bruhat-Tits tree. It is useful to enrich the representation of + the vertex as a matrix with extra data. + + INPUT: + + - ``p`` - a prime integer. + + - ``label`` - An integer which uniquely identifies this vertex. + + - ``rep`` - A 2x2 matrix in reduced form representing this + vertex. + + - ``leaving_edges`` - (Default: empty list) A list of edges + leaving this vertex. + + - ``entering_edges`` - (Default: empty list) A list of edges + entering this vertex. + + - ``determinant`` - (Default: None) The determinant of ``rep``, + if known. + + - ``valuation`` - (Default: None) The valuation of the + determinant of ``rep``, if known. + + EXAMPLES:: + + sage: from sage.modular.btquotients.btquotient import Vertex + sage: v1 = Vertex(5,0,Matrix(ZZ,2,2,[1,2,3,18])) + sage: v1.rep + [ 1 2] + [ 3 18] + sage: v1.entering_edges + [] + + AUTHORS: + + - Marc Masdeu (2012-02-20) + """ + def __init__(self,p,label,rep,leaving_edges=None,entering_edges=None,determinant=None,valuation=None): + """ + This initializes a structure to represent vertices of + quotients of the Bruhat-Tits tree. It is useful to enrich the + representation of the vertex as a matrix with extra data. + + EXAMPLES:: + + sage: from sage.modular.btquotients.btquotient import Vertex + sage: Y = BTQuotient(5,13) + sage: v1 = Vertex(5,0,Matrix(ZZ,2,2,[1,2,3,18])) + sage: TestSuite(v1).run() + """ + if leaving_edges is None: + leaving_edges = [] + if entering_edges is None: + entering_edges = [] + if determinant is None: + determinant = rep.determinant() + if valuation is None: + valuation = determinant.valuation(p) + self.p = p + self.label=label + self.rep=rep + self.rep.set_immutable() + self.determinant=determinant + self.valuation=valuation + self.parity=valuation%2 + self.leaving_edges=leaving_edges + self.entering_edges=entering_edges + + def _repr_(self): + r""" + Returns the representation of self as a string. + + EXAMPLES:: + + sage: X = BTQuotient(3,5) + sage: X.get_vertex_list()[0] + Vertex of BT-tree for p = 3 + """ + return "Vertex of BT-tree for p = %s"%(self.p) + + def __cmp__(self,other): + """ + Returns self == other + + TESTS:: + + sage: from sage.modular.btquotients.btquotient import Vertex + sage: v1 = Vertex(7,0,Matrix(ZZ,2,2,[1,2,3,18])) + sage: v1 == v1 + True + + """ + c = cmp(self.p,other.p) + if c: return c + c = cmp(self.label,other.label) + if c: return c + c = cmp(self.rep,other.rep) + if c: return c + c = cmp(self.determinant,other.determinant) + if c: return c + c = cmp(self.valuation,other.valuation) + if c: return c + c = cmp(self.parity,other.parity) + if c: return c + return 0 + +class Edge(SageObject): + r""" + This is a structure to represent edges of quotients of the + Bruhat-Tits tree. It is useful to enrich the representation of an + edge as a matrix with extra data. + + INPUT: + + - ``p`` - a prime integer. + + - ``label`` - An integer which uniquely identifies this edge. + + - ``rep`` - A 2x2 matrix in reduced form representing this edge. + + - ``origin`` - The origin vertex of ``self``. + + - ``target`` - The target vertex of ``self``. + + - ``links`` - (Default: empty list) A list of elements of + `\Gamma` which identify different edges in the Bruhat-Tits tree + which are equivalent to ``self``. + + - ``opposite`` - (Default: None) The edge opposite to ``self`` + + - ``determinant`` - (Default: None) The determinant of ``rep``, + if known. + + - ``valuation`` - (Default: None) The valuation of the + determinant of ``rep``, if known. + + EXAMPLES:: + + sage: from sage.modular.btquotients.btquotient import Edge, Vertex + sage: v1 = Vertex(7,0,Matrix(ZZ,2,2,[1,2,3,18])) + sage: v2 = Vertex(7,0,Matrix(ZZ,2,2,[3,2,1,18])) + sage: e1 = Edge(7,0,Matrix(ZZ,2,2,[1,2,3,18]),v1,v2) + sage: e1.rep + [ 1 2] + [ 3 18] + + AUTHORS: + + - Marc Masdeu (2012-02-20) + """ + def __init__(self,p,label,rep,origin,target,links = None,opposite = None,determinant = None,valuation = None): + """ + Representation for edges of quotients of the Bruhat-Tits + tree. It is useful to enrich the representation of an edge as + a matrix with extra data. + + EXAMPLES:: + + sage: from sage.modular.btquotients.btquotient import Edge + sage: Y = BTQuotient(5,11) + sage: el = Y.get_edge_list() + sage: e1 = el.pop() + sage: e2 = Edge(5,e1.label,e1.rep,e1.origin,e1.target) + sage: TestSuite(e2).run() + """ + if links is None: + links = [] + if determinant is None: + determinant=rep.determinant() + if valuation is None: + valuation = determinant.valuation(p) + self.p = p + self.label=label + self.rep=rep + self.rep.set_immutable() + self.origin=origin + self.target=target + self.links=links + self.opposite=opposite + self.determinant=determinant + self.valuation=valuation + self.parity=valuation%2 + + def _repr_(self): + r""" + Returns the representation of self as a string. + + EXAMPLES:: + + sage: X = BTQuotient(3,5) + sage: X.get_edge_list()[0] + Edge of BT-tree for p = 3 + """ + return "Edge of BT-tree for p = %s"%(self.p) + + def __cmp__(self,other): + """ + Returns self == other + + TESTS:: + + sage: from sage.modular.btquotients.btquotient import Edge,Vertex + sage: v1 = Vertex(7,0,Matrix(ZZ,2,2,[1,2,3,18])) + sage: v2 = Vertex(7,0,Matrix(ZZ,2,2,[3,2,1,18])) + sage: e1 = Edge(7,0,Matrix(ZZ,2,2,[1,2,3,18]),v1,v2) + sage: e1 == e1 + True + + """ + c = cmp(self.p,other.p) + if c: return c + c = cmp(self.label,other.label) + if c: return c + c = cmp(self.rep,other.rep) + if c: return c + c = cmp(self.origin,other.origin) + if c: return c + c = cmp(self.target,other.target) + if c: return c + c = cmp(self.links,other.links) + if c: return c + c = cmp(self.opposite,other.opposite) + if c: return c + c = cmp(self.determinant,other.determinant) + if c: return c + c = cmp(self.valuation,other.valuation) + if c: return c + c = cmp(self.parity,other.parity) + if c: return c + return 0 + +class BTQuotient(SageObject, UniqueRepresentation): + @staticmethod + def __classcall__(cls,p,Nminus,Nplus=1, character = None, use_magma = False, seed = None): + """ + Ensures that a canonical BTQuotient is created. + + EXAMPLES: + + sage: BTQuotient(3,17) is BTQuotient(3,17,1) + True + """ + return super(BTQuotient,cls).__classcall__(cls,p,Nminus,Nplus,character,use_magma,seed) + + r""" + This function computes the quotient of the Bruhat-Tits tree + by an arithmetic quaternionic group. The group in question is the + group of norm 1 elements in an eichler Z[1/p]-order of some (tame) + level inside of a definite quaternion algebra that is unramified + at the prime p. Note that this routine relies in Magma in the case + `p = 2` or when `Nplus > 1`. + + INPUT: + + - ``p`` - a prime number + + - ``Nminus`` - squarefree integer divisible by an odd number of + distinct primes and relatively prime to p. This is the + discriminant of the definite quaternion algebra that one is + quotienting by. + + - ``Nplus`` - an integer corpime to pNminus (Default: 1). This is + the tame level. It need not be squarefree! If Nplus is not 1 + then the user currently needs magma installed due to sage's + inability to compute well with nonmaximal Eichler orders in + rational (definite) quaternion algebras. + + - ``character`` - a Dirichlet character (Default: None) of modulus + `pN^-N^+`. + + - ``use_magma`` - boolean (default: False). If True, uses magma + for quaternion arithmetic. + + EXAMPLES: + + Here is an example without a Dirichlet character:: + + sage: X = BTQuotient(13,19) + sage: X.genus() + 19 + sage: G = X.get_graph(); G + Multi-graph on 4 vertices + + And an example with a Dirichlet character:: + + sage: f = DirichletGroup(6)[1] + sage: X = BTQuotient(3,2*5*7,character = f) + sage: X.genus() + 5 + + NOTES:: + + A sage implementation of Eichler orders in rational quaternions + algebras would remove the dependency on magma. + + AUTHORS:: + + - Marc Masdeu (2012-02-20) + """ + def __init__(self,p,Nminus,Nplus=1,character = None, use_magma = False, seed = None): + """ + Computes the quotient of the Bruhat-Tits tree by an arithmetic + quaternionic group. + + EXAMPLES:: + + sage: Y = BTQuotient(19,11) + sage: TestSuite(Y).run() + """ + Nminus=Integer(Nminus) + Nplus=Integer(Nplus) + p=Integer(p) + lev=p*Nminus + + if character is not None: + extra_level = character.conductor() + if not extra_level.is_squarefree(): + raise ValueError, "character must be of squarefree conductor" + else: + G = DirichletGroup(lev*Nplus) + character = G([1]*G.ngens()) + extra_level = 1 + + if not p.is_prime(): + raise ValueError, "p must be a prime" + if not lev.is_squarefree(): + raise ValueError, "level must be squarefree" + if(gcd(lev,Nplus)>1): + raise ValueError, "level and conductor must be coprime" + + # if len(Nminus.factor())%2 != 1: + # raise ValueError, "Nminus should be divisible by an odd number of primes" + + self._pN=p + self._p=p + self._Nminus=Nminus + self._Nplus=Nplus + if use_magma == True or self._Nplus != 1 or self._p == 2: + try: + self._magma=magma + magmap=self._magma(p) + # print "Warning: this input needs magma to work..." + except RuntimeError: + raise NotImplementedError,'Sage does not know yet how to work with the kind of orders that you are trying to use. Try installing Magma first and set it up so that Sage can use it.' + + ## This is added for debugging, in order to have reproducible results + if seed is not None: + self._magma.function_call('SetSeed',seed,nvals=0) + self._use_magma = True + else: + self._use_magma = False + + self._BT=BruhatTitsTree(p) + + # This value for self._prec was chosen to agree with a hardcoded + # value in _compute_quotient (the line: + # self.get_embedding_matrix(prec = 3)) + # It was previously -1 and caused the program to default to + # exact splittings (hence magma) in many situations + self._prec = -1 + + self._cached_vertices=dict() + self._cached_edges=dict() + self._cached_paths=dict() + self._cached_decomps=dict() + self._cached_equivalent=dict() + self._CM_points=dict() + + self._V=(QQ**4).ambient_module().change_ring(ZZ) + self._Mat_44=MatrixSpace(ZZ,4,4) + self._Mat_22=MatrixSpace(ZZ,2,2) + self._Mat_41=MatrixSpace(ZZ,4,1) + if extra_level == 1: + self._extra_level = [] + else: + self._extra_level = [ff[0] for ff in extra_level.factor()] + self._character = character + self._Xv=[self._Mat_22([1,0,0,0]),self._Mat_22([0,1,0,0]),self._Mat_22([0,0,1,0]),self._Mat_22([0,0,0,1])] + self._Xe=[self._Mat_22([1,0,0,0]),self._Mat_22([0,1,0,0]),self._Mat_22([0,0,self._p,0]),self._Mat_22([0,0,0,1])] + + def _repr_(self): + r""" + Returns the representation of self as a string. + + EXAMPLES:: + + sage: X = BTQuotient(5,13); X + Quotient of the Bruhat Tits tree of GL_2(QQ_5) with discriminant 13 and level 1 + """ + return "Quotient of the Bruhat Tits tree of GL_2(QQ_%s) with discriminant %s and level %s"%(self.prime(),self.Nminus().factor(),self.Nplus().factor()) + + def __eq__(self,other): + r""" + Compares self with other. + + EXAMPLES:: + + sage: X = BTQuotient(5,13) + sage: Y = BTQuotient(p = 5, Nminus = 13, Nplus = 1,seed = 1231) + sage: X == Y + True + """ + if self._p != other._p: + return False + elif self._Nminus != other._Nminus: + return False + elif self._Nplus != other._Nplus: + return False + elif self._character != other._character: + return False + else: + return True + + def _latex_(self): + r""" + Returns the LaTeX representation of self. + + EXAMPLES:: + + sage: X = BTQuotient(5,13); latex(X) + X(5 \cdot 13,1)\otimes_{\mathbb{Z}} \mathbb{F}_{5} + """ + return "X(%s,%s)\\otimes_{\\mathbb{Z}} \\mathbb{F}_{%s}"%(latex(self.level().factor()),latex(self.Nplus().factor()),latex(self.prime())) + + def get_vertex_dict(self): + r""" + This function returns the vertices of the quotient viewed as + a dict. + + OUTPUT: + + A python dict with the vertices of the quotient. + + EXAMPLES:: + + sage: X = BTQuotient(37,3) + sage: X.get_vertex_dict() + {[1 0] + [0 1]: Vertex of BT-tree for p = 37, [ 1 0] + [ 0 37]: Vertex of BT-tree for p = 37} + """ + try: return self._boundary + except AttributeError: + self._compute_quotient() + return self._boundary + + def get_vertex_list(self): + r""" + Returns a list of the vertices of the quotient. + + OUTPUT: + + - A list with the vertices of the quotient. + + EXAMPLES:: + + sage: X = BTQuotient(37,3) + sage: X.get_vertex_list() + [Vertex of BT-tree for p = 37, Vertex of BT-tree for p = 37] + """ + try: return self._vertex_list + except AttributeError: + self._compute_quotient() + return self._vertex_list + + def get_edge_list(self): + r""" + Returns a list of ``Edge``s which represent a fundamental + domain inside the Bruhat-Tits tree for the quotient. + + OUTPUT: + + A list of ``Edge``s. + + EXAMPLES:: + + sage: X = BTQuotient(37,3) + sage: len(X.get_edge_list()) + 8 + """ + try: return self._edge_list + except AttributeError: + self._compute_quotient() + return self._edge_list + + def get_list(self): + r""" + Returns a list of ``Edge``s which represent a fundamental + domain inside the Bruhat-Tits tree for the quotient, + together with a list of the opposite edges. This is used + to work with automorphic forms. + + OUTPUT: + + A list of ``Edge``s. + + EXAMPLES:: + + sage: X = BTQuotient(37,3) + sage: len(X.get_list()) + 16 + """ + E = self.get_edge_list() + return E + [e.opposite for e in E] + + def get_generators(self): + r""" + Uses a fundamental domain in the Bruhat-Tits tree, and + certain gluing data for boundary vertices, in order to compute + a collection of generators for the arithmetic quaternionic + group that one is quotienting by. This is analogous to using a + polygonal rep. of a compact real surface to present its + fundamental domain. + + OUTPUT: + + - A generating list of elements of an arithmetic + quaternionic group. + + EXAMPLES:: + + sage: X = BTQuotient(3,13) + sage: X.get_generators() + [ + [ 2] [-5] [ 4] + [-5] [ 3] [ 1] + [ 1] [ 1] [-3] + [ 0], [ 2], [-2] + ] + """ + try: return list(self._generators) + except AttributeError: + self._compute_quotient() + return list(self._generators) + + def _compute_invariants(self): + """ + Compute certain invariants from the level data of the quotient + which allow one to compute the genus of the curve. + + ## Reference: Theorem 9 of our paper "Computing fundamental domains for the Bruhat-Tits tree for GL2 (Qp ), p-adic automorphic forms, and the canonical embedding of Shimura curves". + + EXAMPLES:: + + sage: X = BTQuotient(23,11) + sage: X._compute_invariants() + """ + Nplus=self._Nplus + lev=self._Nminus + e4=1 + e3=1 + mu=Nplus + for f in lev.factor(): + e4*=(1-kronecker_symbol(-4,Integer(f[0]))) + e3*=(1-kronecker_symbol(-3,Integer(f[0]))) + mu*=Integer(f[0])-1 + for f in Nplus.factor(): + if (f[1]==1): + e4*=(1+kronecker_symbol(-4,Integer(f[0]))) + e3*=(1+kronecker_symbol(-3,Integer(f[0]))) + else: + if(kronecker_symbol(-4,Integer(f[0]))==1): + e4*=2 + else: + e4=0 + if(kronecker_symbol(-3,Integer(f[0]))==1): + e3*=2 + else: + e3=0 + mu*=1+1/Integer(f[0]) + self.e3 = e3 + self.e4 = e4 + self.mu = mu + + @lazy_attribute + def e3(self): + """ + Compute the `e_3` invariant defined by the formula + + .. math:: + + e_k =\prod_{\ell\mid pN^-}\left(1-\left(\frac{-3}{\ell}\right)\right)\prod_{\ell \| N^+}\left(1+\left(\frac{-3}{\ell}\right)\right)\prod_{\ell^2\mid N^+} \nu_\ell(3) + + OUTPUT: + + - an integer + + EXAMPLES:: + + sage: X = BTQuotient(31,3) + sage: X.e3 + 1 + """ + self._compute_invariants() + return self.e3 + @lazy_attribute + def e4(self): + """ + Compute the `e_4` invariant defined by the formula + + .. math:: + + e_k =\prod_{\ell\mid pN^-}\left(1-\left(\frac{-k}{\ell}\right)\right)\prod_{\ell \| N^+}\left(1+\left(\frac{-k}{\ell}\right)\right)\prod_{\ell^2\mid N^+} \nu_\ell(k) + + OUTPUT: + + - an integer + + EXAMPLES:: + + sage: X = BTQuotient(31,3) + sage: X.e4 + 2 + """ + self._compute_invariants() + return self.e4 + + @lazy_attribute + def mu(self): + """ + Computes the mu invariant of self. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: X = BTQuotient(29,3) + sage: X.mu + 2 + """ + self._compute_invariants() + return self.mu + + @cached_method + def get_num_verts(self): + """ + Returns the number of vertices in the quotient using a + formula. + + ##Add me: reference for the formula being used + + OUTPUT: + + - An integer (the number of vertices) + + EXAMPLES:: + + sage: X = BTQuotient(29,11) + sage: X.get_num_verts() + 4 + """ + Nplus=self._Nplus + lev=self._Nminus + return 2*Integer(self.mu/12+self.e3/3+self.e4/4) + + @cached_method + def get_num_ordered_edges(self): + """ + Returns the number of ordered edges in the quotient. + + OUTPUT: + + - An integer + + EXAMPLES:: + + sage: X = BTQuotient(3,2) + sage: X.get_num_ordered_edges() + 2 + """ + return 2*(self.genus() + self.get_num_verts()-1) + + def genus_no_formula(self): + """ + Computes the genus of the quotient from the data of the + quotient graph. This should agree with self.genus(). + + OUTPUT: + + - An integer + + EXAMPLES:: + + sage: X = BTQuotient(5,2*3*29) + sage: X.genus_no_formula() + 17 + sage: X.genus_no_formula() == X.genus() + True + """ + return ZZ(1 - len(self.get_vertex_list()) + len(self.get_edge_list())) + + @cached_method + def genus(self): + r""" + Computes the genus of the quotient graph using a formula + This should agree with self.genus_no_formula(). + + Computes the genus of the Shimura curve + corresponding to this quotient via Cerednik-Drinfeld. It is + computed via a formula and not in terms of the quotient graph. + + INPUT: + + - level: Integer (default: None) a level. By default, use that + of ``self``. + + - Nplus: Integer (default: None) a conductor. By default, use + that of ``self``. + + OUTPUT: + + An integer equal to the genus + + EXAMPLES:: + + sage: X = BTQuotient(3,2*5*31) + sage: X.genus() + 21 + sage: X.genus() == X.genus_no_formula() + True + """ + return self.dimension_harmonic_cocycles(2) + + @cached_method + def dimension_harmonic_cocycles(self,k,lev = None,Nplus = None,character = None): + r""" + Computes the dimension of the space of harmonic cocycles + of weight `k` on ``self``. + + OUTPUT: + + An integer equal to the dimension + + EXAMPLES:: + + sage: X = BTQuotient(3,7) + sage: print [X.dimension_harmonic_cocycles(k) for k in range(2,20,2)] + [1, 4, 4, 8, 8, 12, 12, 16, 16] + + sage: X = BTQuotient(2,5) # optional - magma + sage: print [X.dimension_harmonic_cocycles(k) for k in range(2,40,2)] # optional - magma + [0, 1, 3, 1, 3, 5, 3, 5, 7, 5, 7, 9, 7, 9, 11, 9, 11, 13, 11] + """ + + k = ZZ(k) + if lev is None: + lev = self._p * self._Nminus + else: + lev = ZZ(lev) + if Nplus is None: + Nplus = self._Nplus + else: + Nplus = ZZ(Nplus) + + if character is None: + character = self._character + kernel = filter(lambda r: gcd(r,lev*Nplus) == 1 and character(r) == 1,range(lev*Nplus)) + + if k == 0: + return 0 + + if lev == 1: + return Gamma0(Nplus).dimension_cusp_forms(k = k) + + f = lev.factor() + if any([l[1] != 1 for l in f]): + raise NotImplementedError, 'The level should be squarefree for this function to work... Sorry!' + + divs = lev.divisors() + + return GammaH_class(lev*Nplus,kernel).dimension_cusp_forms(k = k) - sum([len(ZZ(lev/d).divisors())*self.dimension_harmonic_cocycles(k,d,Nplus,character) for d in divs[:-1]]) + + def Nplus(self): + r""" + Returns the tame level `N^+`. + + OUTPUT: + + An integer equal to `N^+`. + + EXAMPLES:: + + sage: X = BTQuotient(5,7,1) + sage: X.Nplus() + 1 + """ + return self._Nplus + + + def Nminus(self): + r""" + Returns the discriminant of the relevant definite + quaternion algebra. + + OUTPUT: + + An integer equal to `N^-`. + + EXAMPLES:: + + sage: X = BTQuotient(5,7) + sage: X.Nminus() + 7 + """ + return self._Nminus + + @cached_method + def level(self): + r""" + Returns `p N^-`, which is the discriminant of the + indefinite quaternion algebra that is uniformed by + Cerednik-Drinfeld. + + OUTPUT: + + An integer equal to `p N^-`. + + EXAMPLES:: + + sage: X = BTQuotient(5,7) + sage: X.level() + 35 + """ + return self._Nminus*self._p + + def prime(self): + r""" + Returns the prime one is working with. + + OUTPUT: + + An integer equal to the fixed prime p + + EXAMPLES:: + + sage: X = BTQuotient(5,7) + sage: X.prime() + 5 + """ + return self._p + + + def get_graph(self): + r""" + Returns the quotient graph (and computes it if needed). + + OUTPUT: + + A graph representing the quotient of the Bruhat-Tits tree. + + EXAMPLES:: + + sage: X = BTQuotient(11,5) + sage: X.get_graph() + Multi-graph on 2 vertices + """ + try: return self._S + except AttributeError: + self._compute_quotient() + return self._S + + def get_fundom_graph(self): + r""" + Returns the fundamental domain (and computes it if needed). + + OUTPUT: + + A fundamental domain for the action of `\Gamma`. + + EXAMPLES:: + + sage: X = BTQuotient(11,5) + sage: X.get_fundom_graph() + Graph on 24 vertices + """ + try: return self._Sfun + except AttributeError: + self._compute_quotient() + return self._Sfun + + def plot(self,*args,**kwargs): + r""" + Plots the quotient graph. + + OUTPUT: + + A plot of the quotient graph + + EXAMPLES:: + + sage: X = BTQuotient(7,23) + sage: X.plot() + """ + S=self.get_graph() + vertex_colors = {} + v0 = Matrix(ZZ,2,2,[1,0,0,1]) + v0.set_immutable() + rainbow_color = rainbow(len(self._vertex_list)) + for v in S.vertex_iterator(): + key =rainbow_color[S.get_vertex(v).label] + if vertex_colors.has_key(key): + vertex_colors[key].append(v) + else: + vertex_colors[key]=[v] + + my_args = dict() + my_args['vertex_colors'] = vertex_colors + my_args['color_by_label'] = True + my_args['vertex_labels'] = False + my_args.update(kwargs) + return S.plot(*args,**my_args) + return S.plot(*args,**kwargs) + + def plot_fundom(self,*args,**kwargs): + r""" + Plots a fundamental domain. + + OUTPUT: + + A plot of the fundamental domain. + + EXAMPLES:: + + sage: X = BTQuotient(7,23) + sage: X.plot_fundom() + """ + S=self.get_fundom_graph() + vertex_colors = {} + rainbow_color = rainbow(len(self._vertex_list)) + for v in S.vertex_iterator(): + key =rainbow_color[S.get_vertex(v).label] + if vertex_colors.has_key(key): + vertex_colors[key].append(v) + else: + vertex_colors[key]=[v] + + my_args = dict() + my_args['vertex_colors'] = vertex_colors + my_args['color_by_label'] = True + my_args['vertex_labels'] = True + my_args.update(kwargs) + return S.plot(*args,**my_args) + + def is_admissible(self,D): + r""" + Tests whether the imaginary quadratic field of + discriminant `D` embeds in the quaternion algebra. It + furthermore tests the Heegner hypothesis in this setting + (e.g., is `p` inert in the field, etc). + + INPUT: + + - ``D`` - an integer whose squarefree part will define the + quadratic field + + OUTPUT: + + A boolean describing whether the quadratic field is admissible + + EXAMPLES:: + + sage: X = BTQuotient(5,7) + sage: print [X.is_admissible(D) for D in range(-1,-20,-1)] + [False, True, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, True, False] + """ + disc = fundamental_discriminant(D) + for f in self.level().factor(): + if kronecker_symbol(disc,f[0]) != -1: + return False + for f in self._Nplus.factor(): + if kronecker_symbol(disc,f[0]) != 1: + return False + return True + + def _local_splitting_map(self,prec): + r""" + Returns an embedding of the definite quaternion algebra + into the algebra of 2x2 matrices with coefficients in `\QQ_p`. + + INPUT: + + prec -- Integer. The precision of the splitting. + + OUTPUT: + + A function giving the splitting. + + EXAMPLES:: + + sage: X = BTQuotient(11,3) + sage: phi = X._local_splitting_map(10) + sage: B. = QuaternionAlgebra(3) + sage: phi(i)**2 == QQ(i**2)*phi(B(1)) + True + """ + I,J,K=self._local_splitting(prec) + def phi(q): + R=I.parent() + v=q.coefficient_tuple() + return R(v[0] + I*v[1] + J*v[2] + K*v[3]) + return phi + + def _local_splitting(self,prec): + r""" + Finds an embedding of the definite quaternion algebra + into the algebra of 2x2 matrices with coefficients in `\QQ_p`. + + INPUT: + + - prec - Integer. The precision of the splitting. + + OUTPUT: + + - Matrices I, J, K giving the splitting. + + EXAMPLES:: + + sage: X = BTQuotient(11,3) + sage: phi = X._local_splitting_map(10) + sage: B. = QuaternionAlgebra(3) + sage: phi(i)**2 == QQ(i**2)*phi(B(1)) + True + """ + assert self._use_magma == False + if prec <= self._prec: + return self._II,self._JJ,self._KK + + A=self.get_quaternion_algebra() + + ZZp=Zp(self._p,prec) + v=A.invariants() + a =ZZp(v[0]) + b = ZZp(v[1]) + if (A.base_ring() != QQ): + raise ValueError, "must be rational quaternion algebra" + if (A.discriminant() % self._p == 0): + raise ValueError, "p (=%s) must be an unramified prime"%self._p + M = MatrixSpace(ZZp, 2) + + if a.is_square(): + alpha=a.sqrt() + self._II=M([alpha,0,2*alpha,-alpha]) + self._JJ=M([b,-b,b-1,-b]) + else: + self._II = M([0,a,1,0]) + z=0 + self._JJ=0 + while(self._JJ==0): + c=a*z*z+b + if c.is_square(): + x=c.sqrt() + self._JJ=M([x,-a*z,z,-x]) + else: + z+=1 + self._KK = self._II*self._JJ + return self._II, self._JJ, self._KK + + def _compute_embedding_matrix(self,prec, force_computation = False): + r""" + Returns a matrix representing the embedding with the + given precision. + + INPUT: + + - ``prec`` - Integer. The precision of the embedding matrix. + + EXAMPLES: + + Note that the entries of the matrix are elements of Zmod:: + + sage: X = BTQuotient(3,7) + sage: A = X._compute_embedding_matrix(10); A + [26830 29524 53659 59048] + [29525 26829 1 53659] + [29525 26830 1 53659] + [32220 29525 5390 1] + sage: R = A.base_ring() + sage: B = X.get_eichler_order_basis() + sage: R(B[0].reduced_trace()) == A[0,0]+A[3,0] + True + """ + if self._use_magma == True: + if force_computation == False: + try: return Matrix(Zmod(self._pN),4,4,self._cached_Iota0_matrix) + except AttributeError: pass + + Ord = self.get_eichler_order(magma = True, force_computation = force_computation) + OrdMax = self.get_maximal_order(magma = True) + + OBasis = Ord.Basis() + M,f,rho=self._magma.function_call('pMatrixRing',args=[OrdMax,self._p],params={'Precision':2000},nvals=3) + v=[f.Image(OBasis[i]) for i in [1,2,3,4]] + + self._cached_Iota0_matrix=[v[kk][ii,jj].sage() for ii in range(1,3) for jj in range(1,3) for kk in range(4)] + return Matrix(Zmod(self._pN),4,4,self._cached_Iota0_matrix) + else: + phi=self._local_splitting_map(prec) + B=self.get_eichler_order_basis() + return Matrix(Zmod(self._p**prec),4,4,[phi(B[kk])[ii,jj] for ii in range(2) for jj in range(2) for kk in range(4)]) + + def get_extra_embedding_matrices(self): + r""" + Returns a list of matrices representing the different embeddings. + + NOTE: The precision is very low (currently set to 5 digits), + since these embeddings are only used to apply a character. + + EXAMPLES: + + This portion of the code is only relevant when working with a + nontrivial Dirichlet character. If there is no such character + then the code returns an empty list. Even if the character is + not trivial it might return an empty list:: + + sage: f = DirichletGroup(6)[1] + sage: X = BTQuotient(3,2*5*7,character = f) + sage: X.get_extra_embedding_matrices() + [] + """ + try: return self._extra_embedding_matrices + except AttributeError: pass + if self._use_magma == False or len(self._extra_level) == 0: + self._extra_embedding_matrices = [] + else: + n_iters = 0 + Ord=self.get_eichler_order(magma = True) + OrdMax=self.get_maximal_order(magma = True) + OBasis=Ord.Basis() + extra_embeddings = [] + success = False + while not success: + success = True + for l in self._extra_level: + success = False + found = False + while not found: + M,f,rho = self._magma.function_call('pMatrixRing',args=[OrdMax,l],params={'Precision':20},nvals=3) + v=[f.Image(OBasis[i]) for i in [1,2,3,4]] + if all([Qp(l,5)(v[kk][2,1].sage()).valuation() >= 1 for kk in range(4)]) and not all([Qp(l,5)(v[kk][2,1].sage()).valuation() >= 2 for kk in range(4)]): + found = True + success = True + else: + n_iters += 1 + self._magma.quit() + self._magma = magma + self._magma.function_call('SetSeed',n_iters,nvals=0) + self._compute_embedding_matrix(self._prec, force_computation = True) + Ord = self.get_eichler_order(magma = True) + OrdMax = self.get_maximal_order(magma = True) + OBasis = Ord.Basis() + extra_embeddings = [] + success = False + break + if not success: + break + extra_embeddings.append(Matrix(GF(l),4,4,[v[kk][ii,jj].sage() for ii in range(1,3) for jj in range(1,3) for kk in range(4)])) + self._extra_embedding_matrices = extra_embeddings + return self._extra_embedding_matrices + + def _increase_precision(self,amount=1): + r""" + Increase the working precision. + + INPUT: + + - ``amount`` Integer (Default: 1). The amount by which to + increase the precision. + + EXAMPLES: + + sage: X = BTQuotient(3,101) + sage: X.get_embedding_matrix() + [ O(3) 1 + O(3) 1 + O(3) 1 + O(3)] + [2 + O(3) O(3) 2 + O(3) 2 + O(3)] + [1 + O(3) 1 + O(3) O(3) 2 + O(3)] + [1 + O(3) 2 + O(3) 2 + O(3) 2 + O(3)] + sage: X._increase_precision(5) + sage: X.get_embedding_matrix()[0,0] + 2*3^3 + 2*3^5 + O(3^6) + """ + if amount >= 1: + self.get_embedding_matrix(prec = self._prec+amount) + return + else: + return + + def get_embedding_matrix(self, prec = None, exact = False): + r""" + Returns the matrix of the embedding. + + INPUT: + + - ``exact`` boolean (Default: False). If True, return an + embedding into a matrix algebra with coefficients in a + number field. Otherwise, embed into matrices over `p`-adic + numbers. + + - ``prec`` Integer (Default: None). If specified, return the + matrix with precision ``prec``. Otherwise, return the the + cached matrix (with the current working precision). + + OUTPUT: + + - A 4x4 matrix representing the embedding. + + EXAMPLES:: + sage: X = BTQuotient(7,2*3*5) + sage: X.get_embedding_matrix(4) + [ 1 + O(7^4) 5 + 2*7 + 3*7^3 + O(7^4) 4 + 5*7 + 6*7^2 + 6*7^3 + O(7^4) 6 + 3*7^2 + 4*7^3 + O(7^4)] + [ O(7^4) O(7^4) 3 + 7 + O(7^4) 1 + 6*7 + 3*7^2 + 2*7^3 + O(7^4)] + [ O(7^4) 2 + 5*7 + 6*7^3 + O(7^4) 3 + 5*7 + 6*7^2 + 6*7^3 + O(7^4) 3 + 3*7 + 3*7^2 + O(7^4)] + [ 1 + O(7^4) 3 + 4*7 + 6*7^2 + 3*7^3 + O(7^4) 3 + 7 + O(7^4) 1 + 6*7 + 3*7^2 + 2*7^3 + O(7^4)] + sage: X.get_embedding_matrix(3) + [ 1 + O(7^4) 5 + 2*7 + 3*7^3 + O(7^4) 4 + 5*7 + 6*7^2 + 6*7^3 + O(7^4) 6 + 3*7^2 + 4*7^3 + O(7^4)] + [ O(7^4) O(7^4) 3 + 7 + O(7^4) 1 + 6*7 + 3*7^2 + 2*7^3 + O(7^4)] + [ O(7^4) 2 + 5*7 + 6*7^3 + O(7^4) 3 + 5*7 + 6*7^2 + 6*7^3 + O(7^4) 3 + 3*7 + 3*7^2 + O(7^4)] + [ 1 + O(7^4) 3 + 4*7 + 6*7^2 + 3*7^3 + O(7^4) 3 + 7 + O(7^4) 1 + 6*7 + 3*7^2 + 2*7^3 + O(7^4)] + sage: X.get_embedding_matrix(5) + [ 1 + O(7^5) 5 + 2*7 + 3*7^3 + 6*7^4 + O(7^5) 4 + 5*7 + 6*7^2 + 6*7^3 + 6*7^4 + O(7^5) 6 + 3*7^2 + 4*7^3 + 5*7^4 + O(7^5)] + [ O(7^5) O(7^5) 3 + 7 + O(7^5) 1 + 6*7 + 3*7^2 + 2*7^3 + 7^4 + O(7^5)] + [ O(7^5) 2 + 5*7 + 6*7^3 + 5*7^4 + O(7^5) 3 + 5*7 + 6*7^2 + 6*7^3 + 6*7^4 + O(7^5) 3 + 3*7 + 3*7^2 + 5*7^4 + O(7^5)] + [ 1 + O(7^5) 3 + 4*7 + 6*7^2 + 3*7^3 + O(7^5) 3 + 7 + O(7^5) 1 + 6*7 + 3*7^2 + 2*7^3 + 7^4 + O(7^5)] + """ + if exact is True: + try: + return self._Iota_exact + except: + raise RuntimeError, 'Exact splitting not available.' + else: + if prec is None: + prec = self._prec + + if prec < 0: + prec = 1 + + if prec == self._prec: + try: + return self._Iota + except AttributeError: pass + + self._pN=self._p**prec + self._R=Qp(self._p,prec = prec) + + if prec > self._prec: + verbose('self._prec = %s, prec = %s'%(self._prec,prec)) + Iotamod = self._compute_embedding_matrix(prec) + self._Iotainv_lift = Iotamod.inverse().lift() + self._Iota = Matrix(self._R,4,4,[Iotamod[ii,jj] for ii in range(4) for jj in range(4)]) + + self._prec = prec + self._Iotainv = self._Mat_44([self._Iotainv_lift[ii,jj]%self._pN for ii in range(4) for jj in range(4)]) + return self._Iota + + def embed_quaternion(self, g, exact = False, prec=None): + r""" + Embeds the quaternion element ``g`` into a matrix algebra. + + INPUT: + + - ``g`` a row vector of size `4` whose entries represent a + quaternion in our basis. + + - ``exact`` boolean (Default: False) - If True, tries to embed + ``g`` into a matrix algebra over a number field. If False, + the target is the matrix algebra over `\QQ_p`. + + OUTPUT: + + A 2x2 matrix with coefficients in `\QQ_p` if ``exact`` is + False, or a number field if ``exact`` is True. + + EXAMPLES:: + sage: X = BTQuotient(7,2) + sage: l = X.get_units_of_order() + sage: len(l) + 12 + sage: l[3] + [-1] + [ 0] + [ 1] + [ 1] + sage: X.embed_quaternion(l[3]) + [ O(7) 3 + O(7)] + [2 + O(7) 6 + O(7)] + sage: X._increase_precision(5) + sage: X.embed_quaternion(l[3]) + [ 7 + 3*7^2 + 7^3 + 4*7^4 + O(7^6) 3 + 7 + 3*7^2 + 7^3 + 4*7^4 + O(7^6)] + [ 2 + 7 + 3*7^2 + 7^3 + 4*7^4 + O(7^6) 6 + 5*7 + 3*7^2 + 5*7^3 + 2*7^4 + 6*7^5 + O(7^6)] + """ + if exact == True: + return Matrix(self.get_splitting_field(),2,2,(self.get_embedding_matrix(exact = True)*g).list()) + else: + A = self.get_embedding_matrix(prec = prec) * g + return Matrix(self._R,2,2,A.list()) + + def get_embedding(self,prec=None): + r""" + Returns a function which embeds quaternions into a matrix + algebra. + + EXAMPLES:: + + sage: X = BTQuotient(5,3) + sage: f = X.get_embedding(prec = 4) + sage: b = Matrix(ZZ,4,1,[1,2,3,4]) + sage: f(b) + [2 + 3*5 + 2*5^2 + 4*5^3 + O(5^4) 3 + 2*5^2 + 4*5^3 + O(5^4)] + [ 5 + 5^2 + 3*5^3 + O(5^4) 4 + 5 + 2*5^2 + O(5^4)] + """ + A = self.get_embedding_matrix(prec = prec) + return lambda g: Matrix(self._R,2,2,(A*g).list()) + + def get_edge_stabs(self): + r""" + Computes the stabilizers in the arithmetic group of all + edges in the Bruhat-Tits tree within a fundamental domain for + the quotient graph. The stabilizers of an edge and its + opposite are equal, and so we only store half the data. + + OUTPUT: + + A list of lists encoding edge stabilizers. It contains one + entry for each edge. Each entry is a list of data + corresponding to the group elements in the stabilizer of the + edge. The data consists of: (0) a column matrix representing + a quaternion, (1) the power of `p` that one needs to divide + by in order to obtain a quaternion of norm 1, and hence an + element of the arithmetic group `\Gamma`, (2) a boolean that + is only used to compute spaces of modular forms. + + EXAMPLES:: + + sage: X=BTQuotient(3,2) + sage: s = X.get_edge_stabs() + sage: len(s) == X.get_num_ordered_edges()/2 + True + sage: s[0] + [[[ 2] + [-1] + [-1] + [-1], 0, False], [[ 1] + [-1] + [-1] + [-1], 0, True], [[1] + [0] + [0] + [0], 0, True]] + + The second element of `s` should stabilize the first edge of + X, which corresponds to the identity matrix:: + + sage: X.embed_quaternion(s[0][1][0]) + [2 + 2*3 + 3^2 + O(3^3) 1 + 2*3 + 3^2 + O(3^3)] + [ 2*3 + 3^2 + O(3^3) 2 + 3^2 + O(3^3)] + sage: newe = X.embed_quaternion(s[0][1][0]) + sage: newe.set_immutable() + sage: X._find_equivalent_edge(newe) + (([ 2] + [-1] + [-1] + [-1], 0), Edge of BT-tree for p = 3) + + The first entry above encodes an element that maps the edge + corresponding to newe to something in the fundamental domain + of X. Note that this quaternion is in fact in the + stabilizer. We check the representative matrix of the edge and + ensure that it's the identity, which is the edge we started + with:: + + sage: X._find_equivalent_edge(newe)[1].rep + [1 0] + [0 1] + """ + try: return self._edge_stabs + except AttributeError: + self._edge_stabs=[self._stabilizer(e.rep,as_edge=True) for e in self.get_edge_list()] + return self._edge_stabs + + def get_stabilizers(self): + r""" + Computes the stabilizers in the arithmetic group of all + edges in the Bruhat-Tits tree within a fundamental domain for + the quotient graph. This is similar to get_edge_stabs, except + that here we also store the stabilizers of the opposites. + + OUTPUT: + + A list of lists encoding edge stabilizers. It contains one + entry for each edge. Each entry is a list of data + corresponding to the group elements in the stabilizer of the + edge. The data consists of: (0) a column matrix representing + a quaternion, (1) the power of `p` that one needs to divide + by in order to obtain a quaternion of norm 1, and hence an + element of the arithmetic group `\Gamma`, (2) a boolean that + is only used to compute spaces of modular forms. + + EXAMPLES:: + + sage: X=BTQuotient(3,5) + sage: s = X.get_stabilizers() + sage: len(s) == X.get_num_ordered_edges() + True + sage: gamma = X.embed_quaternion(s[1][0][0][0],prec = 20) + sage: v = X.get_edge_list()[0].rep + sage: X._BT.edge(gamma*v) == v + True + """ + S = self.get_edge_stabs() + return S + S + + def get_vertex_stabs(self): + r""" + This function computes the stabilizers in the arithmetic + group of all vertices in the Bruhat-Tits tree within a + fundamental domain for the quotient graph. + + OUTPUT: + + A list of vertex stabilizers. Each vertex stabilizer is a + finite cyclic subgroup, so we return generators for these + subgroups. + + EXAMPLES:: + + sage: X = BTQuotient(13,2) + sage: S = X.get_vertex_stabs() + sage: gamma = X.embed_quaternion(S[0][0][0],prec = 20) + sage: v = X.get_vertex_list()[0].rep + sage: X._BT.vertex(gamma*v) == v + True + """ + try: return self._vertex_stabs + except AttributeError: + self._vertex_stabs=[self._stabilizer(e.rep,as_edge=False) for e in self.get_vertex_list()] + return self._vertex_stabs + + def get_quaternion_algebra(self): + r""" + Returns the underlying quaternion algebra. + + OUTPUT: + + The underlying definite quaternion algebra + + EXAMPLES:: + + sage: X = BTQuotient(5,7) + sage: X.get_quaternion_algebra() + Quaternion Algebra (-1, -7) with base ring Rational Field + """ + try: return self._A + except AttributeError: pass + self._init_order() + return self._A + + def get_eichler_order(self, magma = False, force_computation = False): + r""" + Returns the underlying Eichler order of level `N^+`. + + OUTPUT: + + Underlying Eichler order. + + EXAMPLES:: + + sage: X = BTQuotient(5,7) + sage: X.get_eichler_order() + Order of Quaternion Algebra (-1, -7) with base ring Rational Field with basis (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k) + """ + if magma == True: + if force_computation == False: + try: return self._Omagma + except AttributeError: pass + self._init_order() + return self._Omagma + else: + try: return self._O + except AttributeError: pass + self._init_order() + return self._O + + def get_maximal_order(self, magma = False, force_computation = False): + r""" + Returns the underlying maximal order containing the + Eichler order. + + OUTPUT: + + Underlying maximal order. + + EXAMPLES:: + + sage: X = BTQuotient(5,7) + sage: X.get_maximal_order() + Order of Quaternion Algebra (-1, -7) with base ring Rational Field with basis (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k) + """ + if magma == True: + if force_computation == False: + try: return self._OMaxmagma + except AttributeError: pass + self._init_order() + return self._OMaxmagma + else: + try: return self._OMax + except AttributeError: pass + self._init_order() + return self._OMax + + def get_splitting_field(self): + r""" + Returns a quadratic field that splits the quaternion + algebra attached to ``self``. Currently requires Magma. + + EXAMPLES:: + + sage: X = BTQuotient(5,11) + sage: X.get_splitting_field() + Traceback (most recent call last): + ... + NotImplementedError: Sage does not know yet how to work with the kind of orders that you are trying to use. Try installing Magma first and set it up so that Sage can use it. + + If we do have Magma installed, then it works:: + + sage: X = BTQuotient(5,11,use_magma = True) # optional - magma + sage: X.get_splitting_field() # optional - magma + Number Field in a with defining polynomial X1^2 + 11 + """ + if self._use_magma == False: + raise NotImplementedError,'Sage does not know yet how to work with the kind of orders that you are trying to use. Try installing Magma first and set it up so that Sage can use it.' + try: return self._FF + except AttributeError: pass + self._compute_exact_splitting() + return self._FF + + def get_eichler_order_basis(self): + r""" + Returns a basis for the global Eichler order. + + OUTPUT: + + Basis for the underlying Eichler order of level Nplus. + + EXAMPLES:: + + sage: X = BTQuotient(7,11) + sage: X.get_eichler_order_basis() + [1/2 + 1/2*j, 1/2*i + 1/2*k, j, k] + """ + try: return self._B + except AttributeError: pass + self._init_order() + return self._B + + def get_eichler_order_quadform(self): + r""" + This function returns the norm form for the underlying + Eichler order of level Nplus. Required for finding elements in + the arithmetic subgroup Gamma. + + OUTPUT: + + The norm form of the underlying Eichler order + + EXAMPLES:: + + sage: X = BTQuotient(7,11) + sage: X.get_eichler_order_quadform() + Quadratic form in 4 variables over Integer Ring with coefficients: + [ 3 0 11 0 ] + [ * 3 0 11 ] + [ * * 11 0 ] + [ * * * 11 ] + """ + try: return self._OQuadForm + except AttributeError: pass + self._init_order() + return self._OQuadForm + + def get_eichler_order_quadmatrix(self): + r""" + This function returns the matrix of the quadratic form of + the underlying Eichler order in the fixed basis. + + OUTPUT: + + A 4x4 integral matrix describing the norm form. + + EXAMPLES:: + + sage: X = BTQuotient(7,11) + sage: X.get_eichler_order_quadmatrix() + [ 6 0 11 0] + [ 0 6 0 11] + [11 0 22 0] + [ 0 11 0 22] + """ + try: return self._OM + except AttributeError: pass + self._init_order() + return self._OM + + @cached_method + def get_units_of_order(self): + r""" + Returns the units of the underlying Eichler + `\ZZ`-order. This is a finite group since the order lives in a + definite quaternion algebra over `\QQ`. + + OUTPUT: + + A list of elements of the global Eichler `\ZZ`-order of + level `N^+`. + + EXAMPLES:: + + sage: X = BTQuotient(7,11) + sage: X.get_units_of_order() + [ + [ 0] [-2] + [-2] [ 0] + [ 0] [ 1] + [ 1], [ 0] + ] + """ + OM=self.get_eichler_order_quadmatrix() + v=pari('qfminim(%s,2,0, flag = 0)'%(OM._pari_())) + n_units=Integer(v[0].python()/2) + v=pari('qfminim(%s,2,%s, flag = 2)'%((OM._pari_()),n_units)) + O_units=[] + for jj in range(n_units): + vec=Matrix(ZZ,4,1,[v[2][ii,jj].python() for ii in range(4)]) + O_units.append(vec) + return O_units + +# def _is_new_element(self,x,old_list,unit_list): +# for tt in old_list: +# for u in unit_list: +# if tt*u == u*x: +# return False +# return True + + #def get_CM_points(self,disc,prec, twist = None): + # p=self._p + # R = self.get_eichler_order() + # D = fundamental_discriminant(disc) + # if disc%D != 0: + # raise ValueError,'disc (= %s) should be a fundamental discriminant times a square'%disc + # c = ZZ(sqrt(disc/D)) + + # if c > 1: + # raise NotImplementedError,'For now we only accept maximal orders (trivial conductor)' + + # K = QuadraticField(D) #, 'sq', check=False) + # h = K.class_number() + # Omax = K.maximal_order() + # O = K.order(c*Omax.ring_generators()[0]) + # w = O.ring_generators()[0] + # pol = w.minpoly() + # try: + # all_elts_purged=self._CM_points[disc] + # except KeyError: + # if not self.is_admissible(disc): + # return [] + # + # all_elts=[] + + # all_elts_purged0=[] + # all_elts_purged=[] + + # all_elts = self._find_elements_in_order(w.norm(),w.trace()) + # if len(all_elts) == 0: + # all_elts = self._find_elements_in_order(w.norm()*p**2,w.trace()*p) + # all_elts = [[xx/p for xx in x] for x in all_elts] + + # Now we take into account the action of units + # units=self._find_elements_in_order(1) + # units0=[self._conv(u) for u in units] + + # all_elts0=[self._conv(v) for v in all_elts] + # for v1 in all_elts: + # v0=self._conv(v1) + # if self._is_new_element(v0,all_elts_purged0,units0): + # all_elts_purged0.append(v0) + # all_elts_purged.append(v1) + + # self._CM_points[disc]=all_elts_purged + # if c == 1 and 4*h != len(self._CM_points[disc])*K.unit_group().order(): + # print 'K.class_number()=',K.class_number() + # print 'Found ',len(self._CM_points[disc]), 'points...' + + # all_elts_split=[self.embed_quaternion(matrix(4,1,y),prec=prec) for y in all_elts_purged] + # assert not Qp(p,prec)(pol.discriminant()).is_square() + # Kp=Qp(p,prec = prec).extension(pol,names='g') + # g = Kp.gen() + # W=[] + # for m1 in all_elts_split: + # if twist is not None: + # m = twist.inverse()*m1*twist + # else: + # m = m1 + # a,b,c,d = m.list() + # Compute the fixed points of the matrix [a,b,c,d] acting on the Kp points of Hp. + # A=Kp(a-d) + # trace = a+d + # norm = a*d-b*c + + # D2=Kp(trace**2-4*norm) + # if D2==0: + # D=D2 + # else: + # Compute the square root of D in a naive way + # for a0,b0 in product(range(p),repeat = 2): + # y0=a0+b0*g + # if (y0**2-D2).valuation() > 0: + # break + # y1=y0 + # D=0 + # while(D!=y1): + # D=y1 + # y1=(D**2+D2)/(2*D) + # z1 = (A+D)/(2*c) + # assert a*z1+b ==z1*(c*z1+d) + # if c*z1+d != g: + # z1 = (A-D)/(2*c) + # assert a*z1+b == g*z1 + # assert c*z1+d == g + # W.append(z1) + # return W + + @cached_method + def _get_Up_data(self): + r""" + Returns (computes if necessary) Up data. + + The Up data is a vector of length p, and each entry consists + of the corresponding data for the matrix [p,a,0,1] where a + varies from 0 to p-1. The data is a tuple (acter,edge_images), + with edge images being of type ``DoubleCosetReduction``. + + EXAMPLES:: + + sage: X = BTQuotient(3,7) + sage: X._get_Up_data() + [[[1/3 0] + [ 0 1], [DoubleCosetReduction, DoubleCosetReduction, DoubleCosetReduction, DoubleCosetReduction]], [[-1/3 1/3] + [ 1 0], [DoubleCosetReduction, DoubleCosetReduction, DoubleCosetReduction, DoubleCosetReduction]], [[-2/3 1/3] + [ 1 0], [DoubleCosetReduction, DoubleCosetReduction, DoubleCosetReduction, DoubleCosetReduction]]] + """ + E=self.get_edge_list() + vec_a=self._BT.subdivide([1],1) + return [[alpha.inverse(),[DoubleCosetReduction(self,e.rep*alpha) for e in E]+[DoubleCosetReduction(self,e.opposite.rep*alpha) for e in E]] for alpha in vec_a] + + @cached_method + def _get_atkin_lehner_data(self,q): + r""" + Returns (computes if necessary) data to compute the + Atkin-Lehner involution. + + INPUT: + + - ``q`` - integer dividing p*Nminus*Nplus + + EXAMPLES:: + + sage: X = BTQuotient(3,5) + sage: X._get_atkin_lehner_data(3) + [ + [ 2] + [ 4] + [-3] + [-2], [DoubleCosetReduction, DoubleCosetReduction] + ] + """ + E=self.get_edge_list() + # self._increase_precision(20) + + nninc=-2 + V = [] + while len(V) == 0: + nninc+=2 + #print 'Searching for norm', q*self._p**nninc + V = filter(lambda g:prod([self._character(ZZ((v*Matrix(ZZ,4,1,g))[0,0]))/self._character((p**ZZ(nninc/2))) for v in self.get_extra_embedding_matrices()]) == 1, self._find_elements_in_order(q*self._p**nninc)) + + beta1=Matrix(QQ,4,1,V[0]) + + success=False + while not success: + try: + x=self.embed_quaternion(beta1) + nn=x.determinant().valuation() + T=[beta1,[DoubleCosetReduction(self,x.adjoint()*e.rep,extrapow=nn) for e in E]] + success=True + except (PrecisionError,NotImplementedError): + self._increase_precision(10) + return T + + @cached_method + def _get_hecke_data(self,l): + r""" + Returns (computes if necessary) data to compute the + Hecke operator at a prime. + + INPUT: + + - ``l`` - a prime l. + + EXAMPLES:: + sage: X = BTQuotient(3,17) + sage: len(X._get_hecke_data(5)) + 2 + """ + # print 'Getting hecke data for prime ',l,'...' + def enumerate_words(v): + n=[] + while True: + add_new = True + for jj in range(len(n)): + n[jj] += 1 + if n[jj] != len(v): + add_new = False + break + else: + n[jj] = 0 + if add_new: + n.append(0) + yield prod([v[x] for x in n]) + + E=self.get_edge_list() + # self._increase_precision(20) + if (self.level()*self.Nplus())%l == 0: + Sset=[] + else: + Sset=[self._p] + BB=self._BB + p = self._p + T=[] + T0=[] + V=[] + nninc=-2 + while len(V) == 0: + nninc+=2 + V = filter(lambda g:prod([self._character(ZZ((v*Matrix(ZZ,4,1,g))[0,0]))/self._character((p**ZZ(nninc/2))) for v in self.get_extra_embedding_matrices()]) == 1, self._find_elements_in_order(l*p**nninc)) + + alpha1 = V[0] + alpha0 = self._conv(alpha1) + + alpha = Matrix(QQ,4,1,alpha1) + alphamat = self.embed_quaternion(alpha) + letters = self.get_generators() + filter(lambda g:prod([self._character(ZZ((v*Matrix(ZZ,4,1,g))[0,0]))/self._character((p**ZZ(nninc/2))) for v in self.get_extra_embedding_matrices()]) == 1, self._find_elements_in_order(1)) + I=enumerate_words([self._conv(x) for x in letters]) + n_iters = 0 + while len(T) 10^3: + verbose('Warning: norm (= %s) is quite large, this may take some time!'%norm) + V=OQuadForm.vectors_by_length(norm)[norm] + W=V if not primitive else filter(lambda v: any((vi%self._p != 0 for vi in v)),V) + return W if trace is None else filter(lambda v:self._conv(v).reduced_trace() == trace,W) + + def _compute_quotient(self, check = True): + r""" + Computes the quotient graph. + + INPUT: + + - ``check`` - Boolean (Default = True). + + EXAMPLES:: + + sage: X = BTQuotient(11,2) + sage: X.get_graph() # indirect doctest + Multi-graph on 2 vertices + + sage: X = BTQuotient(17,19) + sage: X.get_graph() # indirect doctest + Multi-graph on 4 vertices + + The following examples require magma:: + + sage: X = BTQuotient(5,7,12) # optional - magma + sage: X.get_graph() # optional - magma + Multi-graph on 24 vertices + sage: len(X._edge_list) # optional - magma + 72 + + sage: X = BTQuotient(2,3,5) # optional - magma + sage: X.get_graph() # optional - magma + Multi-graph on 4 vertices + + sage: X = BTQuotient(2,3,35) # optional - magma + sage: X.get_graph() # optional - magma + Multi-graph on 16 vertices + + sage: X = BTQuotient(53,11,2) # optional - magma + sage: X.get_graph() # optional - magma + Multi-graph on 6 vertices + + sage: X = BTQuotient(2,13,9) # optional - magma + sage: X.get_graph() # optional - magma + Multi-graph on 24 vertices + + AUTHORS: + + - Cameron Franc (2012-02-20) + - Marc Masdeu + """ + generators=set([]) + genus=self.genus() + num_verts=0 + num_edges=0 + self.get_extra_embedding_matrices() + self.get_embedding_matrix(prec = 3) + p=self._p + v0=Vertex(p,num_verts,self._Mat_22([1,0,0,1]),determinant = 1,valuation = 0) + V=collections.deque([v0]) + S=Graph(0,multiedges=True,weighted=True) + Sfun = Graph(0) + edge_list=[] + vertex_list=[v0] + num_edges = 0 + num_verts+=1 + total_verts = self.get_num_verts() + total_edges = genus + total_verts -1 + while len(V)>0: + v=V.popleft() + E=self._BT.leaving_edges(v.rep) + + # print 'V = %s, E = %s, G = %s (target = %s), lenV = %s'%(num_verts,num_edges,1+num_edges-num_verts,genus,len(V)) + for e in E: + edge_det=e.determinant() + edge_valuation=edge_det.valuation(p) + + g,e1=self._find_equivalent_edge(e,v.leaving_edges,valuation=edge_valuation) + + if e1 is not None: # The edge is old. We just update the links + e1.links.append(g) + target = self._BT.target(e) + if e1.parity == 0: + Sfun.add_edge(v.rep,target,label = e1.label) + else: + Sfun.add_edge(v.rep,target,label = e1.opposite.label) + + Sfun.set_vertex(target,e1.target) + else: # The edge is new. + target=self._BT.target(e) + target.set_immutable() + new_det=target.determinant() + new_valuation=new_det.valuation(p) + new_parity=new_valuation%2 + g1,v1=self._find_equivalent_vertex(target,V,valuation=new_valuation) + if v1 is None: + #The vertex is also new + v1=Vertex(p,num_verts,target,determinant = new_det,valuation = new_valuation) + vertex_list.append(v1) + num_verts+=1 + #Add the vertex to the list of pending vertices + V.append(v1) + else: + generators.add(g1[0]) + + # Add the edge to the list + new_e=Edge(p,num_edges,e,v,v1,determinant = edge_det,valuation = edge_valuation) + new_e.links.append(self.B_one()) + Sfun.add_edge(v.rep,target,label = num_edges) + Sfun.set_vertex(target,v1) + + # Add the edge to the graph + S.add_edge(v.rep,v1.rep,num_edges) + S.set_vertex(v.rep,v) + S.set_vertex(v1.rep,v1) + + # Find the opposite edge + opp=self._BT.opposite(e) + opp_det=opp.determinant() + new_e_opp=Edge(p,num_edges,opp,v1,v,opposite = new_e) + new_e.opposite=new_e_opp + + if new_e.parity == 0: + edge_list.append(new_e) + else: + edge_list.append(new_e_opp) + + v.leaving_edges.append(new_e) + v.entering_edges.append(new_e_opp) + v1.entering_edges.append(new_e) + v1.leaving_edges.append(new_e_opp) + num_edges += 1 + computed_genus=Integer(1- len(vertex_list)+num_edges) + if check == True: + if computed_genus != genus: + print 'You found a bug! Please report!' + print 'Computed genus =',computed_genus + print 'Theoretical genus =', genus + raise RuntimeError + if self.get_num_verts() != len(vertex_list): + raise RuntimeError, 'Number of vertices different from expected.' + + self._generators = generators + self._boundary = dict([(v.rep,v) for v in vertex_list]) + self._edge_list = edge_list + self._vertex_list = vertex_list + self._num_edges = num_edges + self._S = S + self._Sfun = Sfun diff --git a/src/sage/modular/btquotients/ocmodule.py b/src/sage/modular/btquotients/ocmodule.py new file mode 100644 index 00000000000..bd96a4631f7 --- /dev/null +++ b/src/sage/modular/btquotients/ocmodule.py @@ -0,0 +1,557 @@ +######################################################################### +# Copyright (C) 2011 Cameron Franc and Marc Masdeu +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# http://www.gnu.org/licenses/ +######################################################################### + +from sage.structure.element import ModuleElement +from sage.modules.module import Module +from sage.matrix.constructor import Matrix +from sage.matrix.matrix_space import MatrixSpace +from copy import copy +from sage.rings.finite_rings.integer_mod_ring import Zmod +from sage.rings.all import Integer +from sage.rings.power_series_ring import PowerSeriesRing +from sage.structure.unique_representation import UniqueRepresentation +from sage.rings.rational_field import QQ +from sage.rings.integer_ring import ZZ +from sage.rings.padics.padic_generic import pAdicGeneric +from sage.categories.pushout import pushout + +class OCVnElement(ModuleElement): + r""" + This class represents elements in an overconvergent coefficient module. + + INPUT: + + - ``parent`` - An overconvergent coefficient module. + + - ``val`` - The value that it needs to store (default: 0). It can be another OCVnElement, + in which case the values are copied. It can also be a column vector (or something + coercible to a column vector) which represents the values of the element applied to + the polynomials `1`, `x`, `x^2`, ... ,`x^n`. + + - ``check`` - boolean (default: True). If set to False, no checks are done and ``val`` is + assumed to be the a column vector. + + AUTHORS: + + - Cameron Franc (2012-02-20) + - Marc Masdeu (2012-02-20) + """ + def __init__(self,parent,val = 0,check = False): + ModuleElement.__init__(self,parent) + self._parent=parent + self._n=self._parent._n + self._nhalf=Integer(self._n/2) + self._depth=self._parent._depth + if check: + if isinstance(val,self.__class__): + d=min([val._parent._depth,parent._depth]) + assert(val._parent.weight()==parent.weight()) + self._val=Matrix(self._parent._R,self._depth,1,0) + for ii in range(d): + self._val[ii,0]=val._val[ii,0] + else: + try: + self._val = Matrix(self._parent._R,self._depth,1,val) + except: + self._val= self._parent._R(val) * MatrixSpace(self._parent._R,self._depth,1)(1) + else: + self._val= MatrixSpace(self._parent._R,self._depth,1)(val) + self.moments = self._val + + def moment(self, i): + return self.moments[i,0] + + def __getitem__(self,r): + r""" + Returns the value of ``self`` on the polynomial `x^r`. + + INPUT: + - ``r`` - an integer. The power of `x`. + + EXAMPLES: + + """ + return self._val[r,0] + + def __setitem__(self,r, val): + r""" + Sets the value of ``self`` on the polynomial `x^r` to ``val``. + + INPUT: + - ``r`` - an integer. The power of `x`. + - ``val`` - a value. + + EXAMPLES: + + """ + self._val[r,0] = val + + def element(self): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + """ + tmp=self.matrix_rep() + return [tmp[ii,0] for ii in range(tmp.nrows())] + + def list(self): + r""" + EXAMPLES: + + This example illustrates ... + + :: + """ + return self.element() + + def matrix_rep(self,B=None): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + + """ + #Express the element in terms of the basis B + if(B is None): + B=self._parent.basis() + A=Matrix(self._parent._R,self._parent.dimension(),self._parent.dimension(),[[b._val[ii,0] for b in B] for ii in range(self._depth)]) + tmp=A.solve_right(self._val) + return tmp + + def _add_(self,y): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + """ + val=self._val+y._val + return self.__class__(self._parent,val, check = False) + + def _sub_(self,y): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + """ + val=self._val-y._val + return self.__class__(self._parent,val, check = False) + + def l_act_by(self,x): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + """ + #assert(x.nrows()==2 and x.ncols()==2) #An element of GL2 + return self._l_act_by(x[0,0],x[0,1],x[1,0],x[1,1],extrafactor=x.determinant()**(-self._nhalf)) + + def r_act_by(self,x): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + """ + #assert(x.nrows()==2 and x.ncols()==2) #An element of GL2 + return self._l_act_by(x[1,1],-x[0,1],-x[1,0],x[0,0],extrafactor=x.determinant()**(-self._nhalf)) + + def _l_act_by(self,a,b,c,d,extrafactor=1): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + + """ + R=self._parent._R + if(self._parent.base_ring().is_exact()): + factor=1 + else: + t=min([R(x).valuation() for x in [a,b,c,d] if x!=0]) + factor=R.prime()**(-t) + try: + x=self._parent._powers[(factor*a,factor*b,factor*c,factor*d)] + return self.__class__(self._parent,(extrafactor*factor**(-self._n))*(x*self._val), check = False) + except KeyError: + tmp = self._parent._get_powers_and_mult(factor*a,factor*b,factor*c,factor*d,extrafactor*factor**(-self._n),self._val) + + return self.__class__(self._parent,tmp) + + def _rmul_(self,a): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + + """ + #assume that a is a scalar + return self.__class__(self._parent,a*self._val, check = False) + + def precision_absolute(self): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + + """ + #This needs to be thought more carefully... + if not self._parent.base_ring().is_exact(): + return [self._val[ii,0].precision_absolute() for ii in range(self._depth)] + else: + return Infinity + + def precision(self): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + + """ + #This needs to be thought more carefully... + if not self._parent.base_ring().is_exact(): + return min([self._val[ii,0].precision_absolute() for ii in range(self._depth)]) + else: + return Infinity + + def precision_relative(self): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + """ + #This needs to be thought more carefully... + if not self._parent.base_ring().is_exact(): + return min([self._val[ii,0].precision_relative() for ii in range(self._depth)]) + else: + return Infinity + + 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 __cmp__(self,other): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + + """ + return cmp(self._val,other._val) + + def __nonzero__(self): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + """ + return self._val!=0 + + def evaluate_at_poly(self,P): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + + """ + p = self._parent._R.prime() + try: + R = pushout(P.parent().base_ring(),self.parent().base_ring()) + except AttributeError: + R = self.parent().base_ring() + + if hasattr(P,'degree'): + try: + r = min([P.degree()+1,self._depth]) + return sum([R(self._val[ii,0])*P[ii] for ii in range(r)]) + except NotImplementedError: pass + return R(self._val[0,0])*P + + def valuation(self,l=None): + r""" + + EXAMPLES: + + This example illustrates ... + + :: + + """ + if not self._parent.base_ring().is_exact(): + if(not l is None and l!=self._parent._R.prime()): + raise ValueError, "This function can only be called with the base prime" + return min([self._val[ii,0].valuation() for ii in range(self._depth)]) + else: + return min([self._val[ii,0].valuation(l) for ii in range(self._depth)]) + + +class OCVn(Module,UniqueRepresentation): + Element=OCVnElement + r""" + This class represents objects in the overconvergent approximation modules used to + describe overconvergent p-adic automorphic forms. + + INPUT: + + - ``n`` - integer + + - ``R`` - ring + + - ``depth`` - integer (Default: None) + + - ``basis`` - (Default: None) + + + AUTHORS: + + - Cameron Franc (2012-02-20) + - Marc Masdeu (2012-02-20) + """ + def __init__(self,n,R,depth=None,basis=None): + Module.__init__(self,base=R) + if basis is not None: + self._basis=copy(basis) + self._n=n + self._R=R + if R.is_exact(): + self._Rmod=self._R + else: + self._Rmod=Zmod(self._R.prime()**(self._R.precision_cap())) + + if depth is None: + depth=n+1 + if depth != n+1: + if R.is_exact(): raise ValueError, "Trying to construct an over-convergent module with exact coefficients, how do you store p-adics ??" + self._depth=depth + self._PowerSeries=PowerSeriesRing(self._Rmod,default_prec=self._depth,name='z') + self._powers=dict() + self._populate_coercion_lists_() + + def is_overconvergent(self): + return self._depth != self._n+1 + + def _an_element_(self): + r""" + """ + return OCVnElement(self,Matrix(self._R,self._depth,1,range(1,self._depth+1)), check = False) + + def _coerce_map_from_(self, S): + r""" + + EXAMPLES: + + :: + + """ + # Nothing coherces here, except OCVnElement + return False + + def _element_constructor_(self,x,check = True): + r""" + + EXAMPLES: + + """ + #Code how to coherce x into the space + #Admissible values of x? + return OCVnElement(self,x,check) + + def _get_powers_and_mult(self,a,b,c,d,lambd,vect): + r""" + Compute the action of a matrix on the basis elements. + + EXAMPLES: + + :: + + """ + R=self._PowerSeries + r=R([b,a]) + s=R([d,c]) + n=self._n + if(self._depth==n+1): + rpows=[R(1)] + spows=[R(1)] + for ii in range(n): + rpows.append(r*rpows[ii]) + spows.append(s*spows[ii]) + x=Matrix(self._Rmod,n+1,n+1,0) + for ii in range(n+1): + y=rpows[ii]*spows[n-ii] + for jj in range(self._depth): + x[ii,jj]=y[jj] + else: + ratio=r*(s**(-1)) + y=s**n + x=Matrix(self._Rmod,self._depth,self._depth,0) + for jj in range(self._depth): + x[0,jj]=y[jj] + for ii in range(1,self._depth): + y*=ratio + for jj in range(self._depth): + x[ii,jj]=y[jj] + if self._Rmod is self._R: + xnew=x + else: + xnew=x.change_ring(self._R.base_ring()) + xnew=xnew.change_ring(self._R) + self._powers[(a,b,c,d)]=xnew + return self._R(lambd) * xnew * vect + + def _repr_(self): + r""" + This returns the representation of self as a string. + + EXAMPLES: + + """ + if self.is_overconvergent(): + return "Space of %s-adic distributions with k=%s action and precision cap %s"%(self._R.prime(), self._n, self._depth - 1) + else: + if self.base_ring() is QQ: + V = 'Q^2' + elif self.base_ring() is ZZ: + V = 'Z^2' + elif isinstance(self.base_ring(), pAdicGeneric) and self.base_ring().degree() == 1: + if self.base_ring().is_field(): + V = 'Q_%s^2'%(self._R.prime()) + else: + V = 'Z_%s^2'%(self._R.prime()) + else: + V = '(%s)^2'%(self.base_ring()) + return "Sym^%s %s"%(self._n, V) + # s='Overconvergent coefficient module of weight n = %s over the ring %s and depth %s'%(self._n,self._R,self._depth) + return s + + def basis(self): + r""" + A basis of the module. + + INPUT: + + - ``x`` - integer (default: 1) the description of the + argument x goes here. If it contains multiple lines, all + the lines after the first need to be indented. + + - ``y`` - integer (default: 2) the ... + + OUTPUT: + + integer -- the ... + + EXAMPLES: + + + """ + try: return self._basis + except: pass + self._basis=[OCVnElement(self,Matrix(self._R,self._depth,1,{(jj,0):1},sparse=False),check = False) for jj in range(self._depth)] + return self._basis + + def base_ring(self): + r""" + This function returns the base ring of the overconvergent element. + + EXAMPLES:: + + This example illustrates ... + + :: + + """ + return self._R + + def depth(self): + r""" + Returns the depth of the module. + """ + return self._depth + + def dimension(self): + r""" + Returns the dimension (rank) of the module. + """ + return self._depth + + def precision_cap(self): + r""" + Returns the dimension (rank) of the module. + """ + return self._depth + + def weight(self): + r""" + Returns the cohomological weight of the automorphic form. + """ + return self._n + + def acting_matrix(self,g,d,B=None): + r""" + Matrix representation of ``g`` in a given basis. + + """ + if d is None: + d = self.dimension() + if B is None: + B=self.basis() + A=[(b.l_act_by(g)).matrix_rep(B) for b in B] + return Matrix(self._R,d,d,[A[jj][ii,0] for ii in range(d) for jj in range(d)]).transpose() + + diff --git a/src/sage/modular/btquotients/pautomorphicform.py b/src/sage/modular/btquotients/pautomorphicform.py new file mode 100644 index 00000000000..25b349bd9b2 --- /dev/null +++ b/src/sage/modular/btquotients/pautomorphicform.py @@ -0,0 +1,2540 @@ +######################################################################### +# Copyright (C) 2011 Cameron Franc and Marc Masdeu +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# http://www.gnu.org/licenses/ +######################################################################### +from sage.modular.btquotients.btquotient import * +from collections import namedtuple +from sage.structure.element import Element, ModuleElement +from sage.structure.parent import Parent +from sage.modules.module import Module +from sage.rings.all import Integer +from sage.structure.element import Element +from sage.matrix.constructor import Matrix, zero_matrix +from sage.rings.all import Qp +from sage.rings.all import RationalField +from sage.rings.number_field.all import NumberField +from copy import copy +from sage.quadratic_forms.quadratic_form import QuadraticForm +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.laurent_series_ring import LaurentSeriesRing +from sage.modular.hecke.all import (AmbientHeckeModule, HeckeSubmodule, HeckeModuleElement) +from sage.rings.infinity import Infinity +import sage.rings.arith as arith +import sage.modular.hecke.hecke_operator +from sage.misc.misc import verbose, cputime +from sage.structure.parent import Parent +from itertools import imap,starmap,izip +from operator import mul + +use_ps_dists = False + +if use_ps_dists: + from sage.modular.pollack_stevens.distributions import Distributions, Symk + from sage.modular.pollack_stevens.sigma0 import Sigma0,Sigma0ActionAdjuster +else: + from sage.modular.btquotients.ocmodule import * + +def eval_dist_at_powseries(phi,f): + """ + Evaluate a distribution on a powerseries. + + A distribution is an element in the dual of the Tate ring. The + elements of coefficient modules of overconvergent modular symbols + and overconvergent p-automorphic forms give examples of + distributions in Sage. + + INPUT: + + - ``phi`` - a distribution + + - ``f`` - a power series over a ring coercible into a p-adic field + + OUTPUT: + + The value of phi evaluated at f, which will be an element in the + ring of definition of f + + EXAMPLES: + + First we construct an overconvergent automorphic form so that + we can get our hands on its coefficient module of + distributions:: + + sage: from sage.modular.btquotients.pautomorphicform import eval_dist_at_powseries + sage: X = BTQuotient(3,7) + sage: H = HarmonicCocycles(X,6,prec=10) + sage: B = H.basis() + sage: c = B[0]+3*B[1] + sage: HH = pAutomorphicForms(X,6,overconvergent = True) + sage: oc = HH.lift(c) + + Next we evaluate this form on a matrix in GL_2(Qp) to extract + an element of the coefficient module of distributions:: + + sage: phi = oc.evaluate(Matrix(ZZ,2,2,[1,77,23,4])) + + Finally we define a power series in the Tate ring and evaluate + phi on it:: + + sage: R. = PowerSeriesRing(ZZ,1) + sage: f = (1 - 3*X)^(-1) + sage: eval_dist_at_powseries(phi,f) + 2*3^2 + 3^3 + 3^6 + O(3^8) + + Even though it only makes sense to evaluate a distribution on + a Tate series, this function will output a (possibly + nonsensical) value for any power series:: + + sage: g = (1-X)^(-1) + sage: eval_dist_at_powseries(phi,g) + 2*3^2 + 3^3 + 3^6 + O(3^8) + """ + if use_ps_dists: + nmoments = len(phi._moments) + return sum(a*phi._moments[i] for a,i in izip(f.coefficients(),f.exponents()) if i >= 0 and i < nmoments) + else: + return phi.evaluate_at_poly(f) + +# Need this to be pickleable +if use_ps_dists: + class _btquot_adjuster(Sigma0ActionAdjuster): + """ + Callable object that turns matrices into 4-tuples. + + Since the modular symbol and harmonic cocycle code use different + conventions for group actions, this function is used to make sure + that actions are correct for harmonic cocycle computations. + + EXAMPLES:: + + sage: from sage.modular.btquotients.pautomorphicform import _btquot_adjuster + sage: adj = _btquot_adjuster() + sage: adj(matrix(ZZ,2,2,[1..4])) + (4, 2, 3, 1) + """ + def __call__(self, g): + """ + Turns matrices into 4-tuples. + + INPUT: + + - ``g`` - a 2x2 matrix + + OUTPUT: + + A 4-tuple encoding the entries of ``g``. + + EXAMPLES:: + + sage: from sage.modular.btquotients.pautomorphicform import _btquot_adjuster + sage: adj = _btquot_adjuster() + sage: adj(matrix(ZZ,2,2,[1..4])) + (4, 2, 3, 1) + """ + a,b,c,d = g.list() + return tuple([d, b, c, a]) + +class HarmonicCocycleElement(HeckeModuleElement): + r""" + Gamma-invariant harmonic cocycles on the Bruhat-Tits + tree. Gamma-invariance is necessary so that the cocycle can be + stored in terms of a finite amount of data. + + More precisely, given a BTQuotient T, harmonic cocycles are stored as + a list of values in some coefficient module (e.g. for weight 2 forms + can take Cp) indexed by edges of a fundamental domain for T in the + Bruhat-Tits tree. Evaluate the cocycle at other edges using Gamma + invariance (although the values may not be equal over an orbit of + edges as the coefficient module action may be nontrivial). + + INPUT: + + - ``vec`` - (default: None) + + - ``from_values`` - (default: False) + + EXAMPLES: + + Harmonic cocycles form a vector space, so they can be added and/or + subtracted from each other:: + + sage: X = BTQuotient(5,23) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: v1 = H.basis()[0]; v2 = H.basis()[1] # indirect doctest + sage: v3 = v1+v2 + sage: v1 == v3-v2 + True + + and rescaled:: + + sage: v4 = 2*v1 + sage: v1 == v4 - v1 + True + + AUTHORS: + + - Cameron Franc (2012-02-20) + - Marc Masdeu + """ + def __init__(self,_parent,vec): + """ + Create a harmonic cocycle element. + + INPUT:: + + _parent : the parent + vec : Defining data, as a list of coefficient module elements + + EXAMPLES:: + + sage: X = BTQuotient(31,7) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: v = H.basis()[0] # indirect doctest + sage: TestSuite(v).run() + """ + HeckeModuleElement.__init__(self,_parent,None) + self._parent = _parent + assert type(vec) is list + assert all([v.parent() is _parent._U for v in vec]) + self._R = _parent._U.base_ring() + self._wt = _parent._k + self._nE = len(_parent._E) + self._F = copy(vec) + return + + def _add_(self,g): + r""" + Add two cocycles componentwise. + + INPUT: + + - `g` - a harmonic cocycle + + OUTPUT: + + A harmonic cocycle + + EXAMPLES:: + + sage: X = BTQuotient(5,23) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: v1 = H.basis()[0]; v2 = H.basis()[1] + sage: v3 = v1+v2 # indirect doctest + sage: v1 == v3-v2 + True + """ + return self.parent()(self.element()+g.element()) + + def _sub_(self,g): + r""" + Computes the difference of two cocycles. + + INPUT: + + - `g` - a harmonic cocycle + + OUTPUT: + + A harmonic cocycle + + EXAMPLES:: + + sage: X = BTQuotient(5,23) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: v1 = H.basis()[0]; v2 = H.basis()[1] + sage: v3 = v1-v2 # indirect doctest + sage: v1 == v3+v2 + True + """ + #Should ensure that self and g are modular forms of the same weight and on the same curve + return self.parent()(self.element()-g.element()) + + def _rmul_(self,a): + r""" + Multiplies a cocycle by a scalar. + + INPUT: + + - `a` - a ring element + + OUTPUT: + + A harmonic cocycle + + EXAMPLES:: + + sage: X = BTQuotient(5,23) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: v1 = H.basis()[0] + sage: v2 = 2*v1 # indirect doctest + sage: v1 == v2-v1 + True + """ + #Should ensure that 'a' is a scalar + return self.parent()(a*self.element()) + + + def __cmp__(self,other): + r""" + General comparison method for Harmonic Cocycles + + INPUT: + + - `other` - Another harmonic cocycle + + EXAMPLES:: + + sage: X = BTQuotient(5,23) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: v1 = H.basis()[0] + sage: v2 = 3*v1 # indirect doctest + sage: 2*v1 == v2-v1 + True + """ + for e in range(self._nE): + c = cmp(self._F[e],other._F[e]) + if c: return c + return 0 + + def _repr_(self): + r""" + Returns a string describing the cocycle. + + EXAMPLES:: + + sage: X = BTQuotient(5,23) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: print H.basis()[0] # indirect doctest + Harmonic cocycle with values in Sym^0 Q_5^2 + """ + return 'Harmonic cocycle with values in %s'%(self.parent()._U) + + def print_values(self): + r""" + Prints the values of the cocycle on all of the edges. + + EXAMPLES:: + + sage: X = BTQuotient(5,23) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: H.basis()[0].print_values() + 0 |1 + O(5^10) + 1 |0 + 2 |0 + 3 |4 + 4*5 + 4*5^2 + 4*5^3 + 4*5^4 + 4*5^5 + 4*5^6 + 4*5^7 + 4*5^8 + 4*5^9 + O(5^10) + 4 |0 + 5 |0 + 6 |0 + 7 |0 + 8 |0 + 9 |0 + 10 |0 + 11 |0 + """ + tmp = '' + for e in range(self._nE): + tmp += '%s\t|%s\n'%(str(e),str(self._F[e])) + print tmp[:-1] + return + + def valuation(self): + r""" + Returns the valuation of the cocycle, defined as the + minimum of the values it takes on a set of representatives. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: X = BTQuotient(3,17) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: b1 = H.basis()[0] + sage: b2 = 3*b1 + sage: b1.valuation() + 0 + sage: b2.valuation() + 1 + sage: H(0).valuation() + +Infinity + """ + if self == 0: + return Infinity + else: + return min([self._F[e].valuation() for e in range(self._nE)]) + + def _compute_element(self): + r""" + Express a harmonic cocycle in a coordinate vector. + + OUTPUT: + + A coordinate vector encoding self in terms of the ambient + basis in self.parent + + EXAMPLES:: + + sage: X = BTQuotient(3,17) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: H.basis()[0]._compute_element() + (1 + O(3^9), O(3^9), 0) + sage: H.basis()[1]._compute_element() + (0, 1 + O(3^9), 0) + sage: H.basis()[2]._compute_element() + (0, O(3^9), 1 + O(3^10)) + """ + R = self._R + A = self.parent().basis_matrix().transpose() + B = Matrix(R,self._nE*(self.parent()._k-1),1,[self._F[e].moment(ii) for e in range(self._nE) for ii in range(self.parent()._k-1) ]) + res = (A.solve_right(B)).transpose() + return self.parent().free_module()(res.row(0)) + + #In HarmonicCocycle + def evaluate(self,e1): + r""" + Evaluates a harmonic cocycle on an edge of the Bruhat-Tits tree. + + INPUT: + + - ``e1`` - a matrix corresponding to an edge of the + Bruhat-Tits tree + + OUTPUT: + + - An element of the coefficient module of the cocycle which + describes the value of the cocycle on e1 + + EXAMPLES:: + + sage: X = BTQuotient(5,17) + sage: e0 = X.get_edge_list()[0] + sage: e1 = X.get_edge_list()[1] + sage: H = HarmonicCocycles(X,2,prec=10) + sage: b = H.basis()[0] + sage: b.evaluate(e0.rep) + 1 + O(5^10) + sage: b.evaluate(e1.rep) + 4 + 4*5 + 4*5^2 + 4*5^3 + 4*5^4 + 4*5^5 + 4*5^6 + 4*5^7 + 4*5^8 + 4*5^9 + O(5^10) + """ + X = self.parent()._X + p = X._p + u = DoubleCosetReduction(X,e1) + if u.label < self._nE: + val = self._F[u.label] + else: + val = -self._F[u.label-self._nE] + + if use_ps_dists: + return u.igamma(self.parent().embed_quaternion, scale= p**-u.power) * val + else: + return val.l_act_by(u.igamma(self.parent().embed_quaternion) * (p**(-u.power))) + + #In HarmonicCocycle + def riemann_sum(self,f,center = 1,level = 0,E = None): + r""" + Evaluates the integral of the function ``f`` with respect + to the measure determined by ``self`` over `\mathbf{P}_1(\Qp)`. + + INPUT: + + - `f` - a function on `\PP^1(\QQ_p)`. + + - `center` - An integer (Default = 1). Center of integration. + + - `level` - An integer (Default = 0). Determines the size of + the covering when computing the Riemann sum. Runtime is + exponential in the level. + + - `E` - A list of edges (Default = None). They should describe + a covering of `\mathbf{P}_1(\Qp)`. + + OUTPUT: + + A p-adic number. + + EXAMPLES:: + + sage: X = BTQuotient(5,7) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: b = H.basis()[0] + sage: R. = PolynomialRing(QQ,1) + sage: f = z^2 + + Note that `f` has a pole at infinity, so that the result will be meaningless:: + + sage: b.riemann_sum(f,level=0) + 1 + 5 + 2*5^3 + 4*5^4 + 2*5^5 + 3*5^6 + 3*5^7 + 2*5^8 + 4*5^9 + O(5^10) + """ + R1 = LaurentSeriesRing(f.base_ring(),'r1') + R1.set_default_prec(self.parent()._k-1) + R2 = PolynomialRing(f.base_ring(),'r2') + + if E is None: + E = self.parent()._X._BT.get_balls(center,level) + else: + E = self.parent()._X._BT.subdivide(E,level) + value = 0 + ii = 0 + for e in E: + ii += 1 + exp = ((R1([e[1,1],e[1,0]])**(self.parent()._k-2)*e.determinant()**(-(self.parent()._k-2)/2))*f(R1([e[0 +,1],e[0,0]])/R1([e[1,1],e[1,0]]))).truncate(self.parent()._k-1) + if use_ps_dists: + new = eval_dist_at_powseries((self.parent()._Sigma0(e.inverse(),check = False) * self.evaluate(e)),exp) + else: + new = eval_dist_at_powseries(self.evaluate(e).l_act_by(e.inverse()),exp) + value += new + return value + + def modular_form(self,z = None,level = 0): + r""" + Integrates Teitelbaum's `p`-adic Poisson kernel against + the measure corresponding to self to evaluate the associated + modular form at z. + + If z = None, a function is returned that encodes the modular form. + + NOTE: This function uses the integration method of Riemann + summation and is incredibly slow! It should only be used for + testing and bug-finding. Overconvergent methods are quicker. + + INPUT: + + - `z` - an element in the quadratic unramified extension of + `\Qp` that is not contained in `\Qp` (Default = None). + + - `level` - an integer. How fine of a mesh should the Riemann + sum use. + + OUTPUT: + + An element of the quadratic unramified extension of `\Qp`. + + EXAMPLES:: + + sage: X = BTQuotient(3,23) + sage: H = HarmonicCocycles(X,2,prec = 8) + sage: b = H.basis()[0] + sage: R. = Qq(9,prec=10) + sage: x1 = b.modular_form(a,level = 0); x1 + a + (2*a + 1)*3 + (a + 1)*3^2 + (a + 1)*3^3 + 3^4 + (a + 2)*3^5 + O(3^7) + sage: x2 = b.modular_form(a,level = 1); x2 + a + (a + 2)*3 + (2*a + 1)*3^3 + (2*a + 1)*3^4 + 3^5 + (a + 2)*3^6 + O(3^7) + sage: x3 = b.modular_form(a,level = 2); x3 + a + (a + 2)*3 + (2*a + 2)*3^2 + 2*a*3^4 + (a + 1)*3^5 + 3^6 + O(3^7) + sage: x4 = b.modular_form(a,level = 3);x4 + a + (a + 2)*3 + (2*a + 2)*3^2 + (2*a + 2)*3^3 + 2*a*3^5 + a*3^6 + O(3^7) + sage: (x4-x3).valuation() + 3 + """ + return self.derivative(z,level,order = 0) + + # In HarmonicCocycle + def derivative(self,z = None,level = 0,order = 1): + r""" + Integrates Teitelbaum's `p`-adic Poisson kernel against + the measure corresponding to self to evaluate the rigid + analytic Shimura-Maass derivatives of the associated modular + form at z. + + If z = None, a function is returned that encodes the + derivative of the modular form. + + NOTE: This function uses the integration method of Riemann + summation and is incredibly slow! It should only be used for + testing and bug-finding. Overconvergent methods are quicker. + + INPUT: + + - `z` - an element in the quadratic unramified extension of + `\Qp` that is not contained in `\Qp` (Default = None). If `z + = None` then a function encoding the derivative is returned. + + - `level` - an integer. How fine of a mesh should the Riemann + sum use. + + - `order` - an integer. How many derivatives to take. + + OUTPUT: + + An element of the quadratic unramified extension of `\Qp`, or + a function encoding the derivative. + + EXAMPLES:: + + sage: X = BTQuotient(3,23) + sage: H = HarmonicCocycles(X,2,prec=5) + sage: b = H.basis()[0] + sage: R. = Qq(9,prec=10) + sage: b.modular_form(a,level=0) == b.derivative(a,level=0,order=0) + True + sage: b.derivative(a,level=1,order=1) + (2*a + 2)*3 + (a + 2)*3^2 + 2*a*3^3 + O(3^4) + sage: b.derivative(a,level=2,order=1) + (2*a + 2)*3 + 2*a*3^2 + 3^3 + O(3^4) + + REFERENCES: + + For a discussion of nearly rigid analytic modular forms and + the rigid analytic Shimura-Maass operator, see the thesis of + C. Franc [2011]. + """ + def F(z): + R = PolynomialRing(z.parent(),'x,y').fraction_field() + Rx = PolynomialRing(z.parent(),'x1').fraction_field() + x1 = Rx.gen() + subst = R.hom([x1,z],codomain = Rx) + x,y = R.gens() + center = self.parent()._X._BT.find_containing_affinoid(z) + zbar = z.trace()-z + f = R(1)/(x-y) + k = self.parent()._k + V = [f] + for ii in range(order): + V = [v.derivative(y) for v in V]+[k/(y-zbar)*v for v in V] + k += 2 + return sum([self.riemann_sum(subst(v),center,level) for v in V]) + if(z is None): + return F + else: + return F(z) + + +class HarmonicCocycles(AmbientHeckeModule,UniqueRepresentation): + Element = HarmonicCocycleElement + r""" + Ensures unique representation + + EXAMPLES:: + + sage: X = BTQuotient(3,5) + sage: M1 = HarmonicCocycles(X,2,prec = 10) + sage: M2 = HarmonicCocycles(X,2,10) + sage: M1 is M2 + True + + """ + @staticmethod + def __classcall__(cls,X,k,prec = None,basis_matrix = None,base_field = None): + r""" + Represents a space of Gamma invariant harmonic + cocycles valued in a cofficient module. + + INPUT: + + - ``X`` - A BTQuotient object + + - ``k`` - integer - The weight. It must be even. + + - ``prec`` - integer (Default: None). If specified, the + precision for the coefficient module + + - ``basis_matrix`` - a matrix (Default: None). + + - ``base_field`` - a ring (Default: None) + + EXAMPLES:: + + sage: X = BTQuotient(3,23) + sage: H = HarmonicCocycles(X,2,prec = 5) + sage: H.dimension() + 3 + sage: X.genus() + 3 + + Higher even weights are implemented:: + + sage: H = HarmonicCocycles(X,8, prec = 10) + sage: H.dimension() + 26 + + AUTHORS: + + - Cameron Franc (2012-02-20) + - Marc Masdeu + """ + return super(HarmonicCocycles,cls).__classcall__(cls,X,k,prec,basis_matrix,base_field) + + def __init__(self,X,k,prec = None,basis_matrix = None,base_field = None): + """ + Compute the space of harmonic cocycles. + + EXAMPLES:: + + sage: X = BTQuotient(3,37) + sage: H = HarmonicCocycles(X,4,prec=10) + sage: TestSuite(H).run() + """ + self._k = k + self._X = X + self._E = self._X.get_edge_list() + self._V = self._X.get_vertex_list() + + if base_field is not None and not base_field.is_exact(): + prec = base_field.precision_cap() + + if prec is None: + if base_field is None: + try: + self._R = X.get_splitting_field() + except AttributeError: + raise ValueError, "It looks like you are not using Magma as backend...and still we don't know how to compute splittings in that case!" + else: + pol = X.get_splitting_field().defining_polynomial().factor()[0][0] + self._R = base_field.extension(pol,pol.variable_name()).absolute_field(name = 'r') + if use_ps_dists: + self._U = Symk(self._k-2,base = self._R,act_on_left = True,adjuster = _btquot_adjuster(),dettwist = -ZZ((self._k-2)/2)) #monoid = MatrixSpace(self._R,2,2)) + else: + self._U = OCVn(self._k-2,self._R) + else: + self._prec = prec + if base_field is None: + self._R = Qp(self._X._p,prec = prec) + else: + self._R = base_field + if use_ps_dists: + self._U = Symk(self._k-2,base = self._R,act_on_left = True,adjuster = _btquot_adjuster(),dettwist = -ZZ((self._k-2)/2)) + else: + self._U = OCVn(self._k-2,self._R,self._k-1) + if basis_matrix is None: + self.__rank = self._X.dimension_harmonic_cocycles(self._k) + else: + self.__rank = basis_matrix.nrows() + if basis_matrix is not None: + self.__matrix = basis_matrix + self.__matrix.set_immutable() + assert self.__rank == self.__matrix.nrows() + + if use_ps_dists: + # self._Sigma0 = Sigma0(1, base_ring = self._U.base_ring(),adjuster = _btquot_adjuster()) + self._Sigma0 = self._U._act._Sigma0 + else: + def _Sigma0(x,check = False): return x + self._Sigma0 = _Sigma0 + + AmbientHeckeModule.__init__(self, self._R, self.__rank, self._X.prime()*self._X.Nplus()*self._X.Nminus(), weight = self._k) + self._populate_coercion_lists_() + + def base_extend(self,base_ring): + r""" + Extends the base ring of the coefficient module. + + INPUT: + + - ``base_ring`` - a ring that has a coerce map from the + current base ring + + OUTPUT: + + A new space of HarmonicCocycles with the base extended. + + EXAMPLES:: + + sage: X = BTQuotient(3,19) + sage: H = HarmonicCocycles(X,2,10) + sage: H.base_ring() + 3-adic Field with capped relative precision 10 + sage: H1 = H.base_extend(Qp(3,prec=15)) + sage: H1.base_ring() + 3-adic Field with capped relative precision 15 + """ + if not base_ring.has_coerce_map_from(self.base_ring()): + raise ValueError, "No coercion defined" + else: + return self.change_ring(base_ring) + + def change_ring(self, new_base_ring): + r""" + Changes the base ring of the coefficient module. + + INPUT: + + - ``new_base_ring'' - a ring that has a coerce map from the + current base ring + + OUTPUT: + + New space of HarmonicCocycles with different base ring + + EXAMPLES:: + + sage: X = BTQuotient(5,17) + sage: H = HarmonicCocycles(X,2,10) + sage: H.base_ring() + 5-adic Field with capped relative precision 10 + sage: H1 = H.base_extend(Qp(5,prec=15)) # indirect doctest + sage: H1.base_ring() + 5-adic Field with capped relative precision 15 + """ + if not new_base_ring.has_coerce_map_from(self.base_ring()): + raise ValueError, "No coercion defined" + + else: + basis_matrix = self.basis_matrix().change_ring(new_base_ring) + basis_matrix.set_immutable() + return self.__class__(self._X,self._k,prec = None,basis_matrix = basis_matrix,base_field = new_base_ring) + + def rank(self): + r""" + Returns the rank (dimension) of ``self``. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: X = BTQuotient(7,11) + sage: H = HarmonicCocycles(X,2,prec = 10) + sage: X.genus() == H.rank() + True + sage: H1 = HarmonicCocycles(X,4,prec = 10) + sage: H1.rank() + 16 + """ + return self.__rank + + def submodule(self,v,check = False): + r""" + Return the submodule of ``self`` spanned by ``v``. + + INPUT: + + - ``v`` - Submodule of self.free_module(). + + - ``check`` - Boolean (Default = False). + + OUTPUT: + + Subspace of harmonic cocycles. + + EXAMPLES:: + + sage: X = BTQuotient(3,17) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: H.rank() + 3 + sage: v = H.gen(0) + sage: N = H.free_module().span([v.element()]) + sage: H1 = H.submodule(N) + Traceback (most recent call last): + ... + NotImplementedError + """ + # return HarmonicCocyclesSubmodule(self,v) + raise NotImplementedError + + def is_simple(self): + r""" + Whether ``self`` is irreducible. + + OUTPUT: + + Boolean. True iff self is irreducible. + + EXAMPLES:: + + sage: X = BTQuotient(3,29) + sage: H = HarmonicCocycles(X,4,prec =10) + sage: H.rank() + 14 + sage: H.is_simple() + False + sage: X = BTQuotient(7,2) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: H.rank() + 1 + sage: H.is_simple() + True + """ + return self.rank() == 1 + + def _repr_(self): + r""" + This returns the representation of self as a string. + + EXAMPLES:: + + sage: X = BTQuotient(5,23) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: print H + Space of harmonic cocycles of weight 2 on Quotient of the Bruhat Tits tree of GL_2(QQ_5) with discriminant 23 and level 1 + """ + return 'Space of harmonic cocycles of weight %s on %s'%(self._k,self._X) + + def _latex_(self): + r""" + A LaTeX representation of ``self``. + + EXAMPLES:: + + sage: X = BTQuotient(5,23) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: latex(H) # indirect doctest + \text{Space of harmonic cocycles of weight } 2 \text{ on } X(5 \cdot 23,1)\otimes_{\mathbb{Z}} \mathbb{F}_{5} + """ + s = '\\text{Space of harmonic cocycles of weight }'+latex(self._k)+'\\text{ on }'+latex(self._X) + return s + + def _an_element_(self): + r""" + Returns an element of the ambient space + + OUTPUT: + + A harmonic cocycle in self. + + EXAMPLES: + + sage: X = BTQuotient(5,23) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: H.an_element() # indirect doctest + Harmonic cocycle with values in Sym^0 Q_5^2 + """ + return self.basis()[0] + + + def _coerce_map_from_(self, S): + r""" + Can coerce from other HarmonicCocycles or from pAutomorphicForms, also from 0 + + OUTPUT: + + Boolean. True iff self is a space of HarmonicCocycles or + pAutomorphicForms. + + EXAMPLES:: + + sage: X = BTQuotient(3,17) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: A = pAutomorphicForms(X,2,prec=10) + sage: A(H.basis()[0]) # indirect doctest + p-adic automorphic form of cohomological weight 0 + """ + if isinstance(S,(HarmonicCocycles,pAutomorphicForms)): + if S._k != self._k: + return False + if S._X != self._X: + return False + return True + return False + + def __cmp__(self,other): + r""" + Tests whether two HarmonicCocycle spaces are equal. + + INPUT: + + - `other` - a HarmonicCocycles class. + + OUTPUT: + + A boolean value + + EXAMPLES:: + + sage: X = BTQuotient(5,7) + sage: H1 = HarmonicCocycles(X,2,prec=10) + sage: H2 = HarmonicCocycles(X,2,prec=10) + sage: H1 == H2 + True + """ + res = cmp(self.base_ring(),other.base_ring()) + if res: return res + res = cmp(self._X,other._X) + if res: return res + res = cmp(self._k,other._k) + if res: return res + return 0 + + def _element_constructor_(self,x): + r""" + Constructor for harmonic cocycles. + + INPUT: + + - `x` - an object coercible into a harmonic cocycle. + + OUTPUT: + + A harmonic cocycle. + + EXAMPLES:: + + sage: X = BTQuotient(3,17) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: H(H.an_element()) # indirect doctest + Harmonic cocycle with values in Sym^0 Q_3^2 + sage: H(0) + Harmonic cocycle with values in Sym^0 Q_3^2 + """ + #Code how to coherce x into the space + #Admissible values of x? + if type(x) is sage.modules.free_module_element.FreeModuleElement_generic_dense: + vmat = MatrixSpace(self._R,1,self.dimension())(x) + tmp = (vmat*self.ambient_module().basis_matrix()).row(0) + if use_ps_dists: + vec = [self._U(tmp[e*(self._k-1):(e+1)*(self._k-1)]) for e in range(len(self._E))] + else: + vec = [self._U(Matrix(self._R,self._k-1,1,tmp[e*(self._k-1):(e+1)*(self._k-1)])) for e in range(len(self._E))] + return self.element_class(self,vec) + + if type(x) is list: + return self.element_class(self,[self._U(o) for o in x]) + + if hasattr(x,'parent'): + parent = x.parent() + if isinstance(parent,HarmonicCocycles): + return self.element_class(self,[self._U(o) for o in x._F]) + elif isinstance(parent,pAutomorphicForms): + tmp = [self._U(x._F[ii]).l_act_by(self._E[ii].rep) for ii in range(self._nE)] + # tmp = [self._E[ii].rep * self._U(x._F[ii]) for ii in range(self._nE)] + return self.element_class(self,tmp) + if x == 0: + tmp = [self._U([0 for jj in range(self.weight()-1)]) for ii in range(self._X._num_edges)] + return self.element_class(self,tmp) + else: + raise TypeError + + + def free_module(self): + r""" + Returns the underlying free module + + OUTPUT: + + A free module. + + EXAPLES:: + + sage: X = BTQuotient(3,7) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: H.free_module() + Vector space of dimension 1 over 3-adic Field with capped relative precision 10 + """ + try: return self.__free_module + except AttributeError: pass + V = self.base_ring()**self.dimension() + self.__free_module = V + return V + + def character(self): + r""" + The trivial character. + + OUTPUT: + + The identity map. + + EXAMPLES:: + + sage: X = BTQuotient(3,7) + sage: H = HarmonicCocycles(X,2,prec = 10) + sage: f = H.character() + sage: f(1) + 1 + sage: f(2) + 2 + """ + return lambda x:x + + def embed_quaternion(self,g,scale = 1): + r""" + Embed the quaternion element ``g`` into the matrix algebra. + + INPUT: + + - `g` - A quaternion, expressed as a 4x1 matrix. + + OUTPUT: + + A 2x2 matrix with p-adic entries. + + EXAMPLES:: + + sage: X = BTQuotient(7,2) + sage: q = X.get_stabilizers()[0][1][0] + sage: H = HarmonicCocycles(X,2,prec = 5) + sage: H.embed_quaternion(q) + [4 + 5*7 + 3*7^2 + 5*7^3 + 2*7^4 + O(7^5) 1 + 7 + 3*7^2 + 7^3 + 4*7^4 + O(7^5)] + [ 7 + 3*7^2 + 7^3 + 4*7^4 + O(7^5) 2 + 7 + 3*7^2 + 7^3 + 4*7^4 + O(7^5)] + """ + if use_ps_dists: + return self._Sigma0(scale * self._X.embed_quaternion(g,exact = self._R.is_exact(), prec = self._prec), check = False) + else: + return scale * self._X.embed_quaternion(g,exact = self._R.is_exact(), prec = self._prec) + + def basis_matrix(self): + r""" + Returns a basis of ``self`` in matrix form. + + If the coefficient module `M` is of finite rank then the space + of Gamma invariant `M` valued harmonic cocycles can be + represented as a subspace of the finite rank space of all + functions from the finitely many edges in the corresponding + BTQuotient into `M`. This function computes this + representation of the space of cocycles. + + OUTPUT: + + - A basis matrix describing the cocycles in the spaced of all + `M` valued Gamma invariant functions on the tree. + + EXAMPLES:: + + sage: X = BTQuotient(5,3) + sage: M = HarmonicCocycles(X,4,prec = 20) + sage: B = M.basis() # indirect doctest + sage: len(B) == X.dimension_harmonic_cocycles(4) + True + + AUTHORS: + + - Cameron Franc (2012-02-20) + - Marc Masdeu (2012-02-20) + """ + try: return self.__matrix + except AttributeError: pass + nV = len(self._V) + nE = len(self._E) + stab_conds = [] + S = self._X.get_edge_stabs() + p = self._X._p + d = self._k-1 + for e in self._E: + try: + g = filter(lambda g:g[2],S[e.label])[0] + if use_ps_dists: + C = self._U.acting_matrix(self._Sigma0(self.embed_quaternion(g[0])),d).transpose() #Warning - Need to allow the check = True + C -= self._U.acting_matrix(self._Sigma0(Matrix(QQ,2,2,p**g[1])),d).transpose() #Warning - Need to allow the check = True + else: + C = self._U.acting_matrix(self.embed_quaternion(g[0]),d).transpose() + C -= self._U.acting_matrix(Matrix(QQ,2,2,p**g[1]),d).transpose() + stab_conds.append([e.label,C]) + except IndexError: pass + + n_stab_conds = len(stab_conds) + self._M = Matrix(self._R,(nV+n_stab_conds)*d,nE*d,0,sparse = True) + for v in self._V: + for e in filter(lambda e:e.parity == 0,v.leaving_edges): + C = sum([self._U.acting_matrix(self.embed_quaternion(x[0]),d) for x in e.links],Matrix(self._R,d,d,0)).transpose() + self._M.set_block(v.label*d,e.label*d,C) + for e in filter(lambda e:e.parity == 0,v.entering_edges): + C = sum([self._U.acting_matrix(self.embed_quaternion(x[0]),d) for x in e.opposite.links],Matrix(self._R,d,d,0)).transpose() + self._M.set_block(v.label*d,e.opposite.label*d,C) + + for kk in range(n_stab_conds): + v = stab_conds[kk] + self._M.set_block((nV+kk)*d,v[0]*d,v[1]) + + x1 = self._M.right_kernel().matrix() + + if x1.nrows() != self.rank(): + raise RuntimeError, 'The computed dimension does not agree with the expectation. Consider increasing precision!' + + K = [c for c in x1.rows()] + + if not self._R.is_exact(): + for ii in range(len(K)): + s = min([t.valuation() for t in K[ii]]) + for jj in range(len(K[ii])): + K[ii][jj] = (p**(-s))*K[ii][jj] + + self.__matrix = Matrix(self._R,len(K),nE*d,K) + self.__matrix.set_immutable() + return self.__matrix + + def __apply_atkin_lehner(self,q,f): + r""" + Applies an Atkin-Lehner involution to a harmonic cocycle + + INPUT: + + - ``q`` - an integer dividing the full level p*Nminus*Nplus + + - ``f`` - a harmonic cocycle + + OUTPUT: + + - The harmonic cocycle obtained by hitting f with the + Atkin-Lehner at q + + EXAMPLES:: + + sage: X = BTQuotient(5,17) + sage: H = HarmonicCocycles(X,2,prec = 10) + sage: A = H.atkin_lehner_operator(5).matrix() # indirect doctest + sage: A**2 == 1 + True + + """ + R = self._R + Data = self._X._get_atkin_lehner_data(q) + p = self._X._p + tmp = [self._U(0) for jj in range(len(self._E))] + d1 = Data[1] + mga = self.embed_quaternion(Data[0]) + nE = len(self._E) + for jj in range(nE): + t = d1[jj] + if t.label < nE: + if use_ps_dists: + tmp[jj] += mga * t.igamma(self.embed_quaternion, scale = p**-t.power) * f._F[t.label] + else: + tmp[jj] += (f._F[t.label]).l_act_by(p**(-t.power)*mga*t.igamma(self.embed_quaternion)) + else: + if use_ps_dists: + tmp[jj] += mga * t.igamma(self.embed_quaternion, scale = p**-t.power) * (-f._F[t.label-nE]) + else: + tmp[jj] += (-f._F[t.label-nE]).l_act_by(p**(-t.power)*mga*t.igamma(self.embed_quaternion)) + + return self(tmp) + + def __apply_hecke_operator(self,l,f): + r""" + This function applies a Hecke operator to a harmonic cocycle. + + INPUT: + + - ``l`` - an integer + + - ``f`` - a harmonic cocycle + + OUTPUT: + + - A harmonic cocycle which is the result of applying the lth + Hecke operator to f + + EXAMPLES:: + + sage: X = BTQuotient(5,17) + sage: H = HarmonicCocycles(X,2,prec=50) + sage: A = H.hecke_operator(7).matrix() # indirect doctest + sage: print [o.rational_reconstruction() for o in A.charpoly().coefficients()] + [-8, -12, 12, 20, 8, 1] + + """ + R = self._R + HeckeData,alpha = self._X._get_hecke_data(l) + if(self.level()%l == 0): + factor = QQ(l**(Integer((self._k-2)/2))/(l+1)) + else: + factor = QQ(l**(Integer((self._k-2)/2))) + p = self._X._p + alphamat = self.embed_quaternion(alpha) + tmp = [self._U(0) for jj in range(len(self._E))] + for ii in range(len(HeckeData)): + d1 = HeckeData[ii][1] + mga = self.embed_quaternion(HeckeData[ii][0])*alphamat + nE = len(self._E) + for jj in range(nE): + t = d1[jj] + if t.label < nE: + if use_ps_dists: + tmp[jj] += mga * t.igamma(self.embed_quaternion,scale = p**-t.power) * f._F[t.label] + else: + tmp[jj] += f._F[t.label].l_act_by(p**(-t.power)*mga*t.igamma(self.embed_quaternion)) + else: + if use_ps_dists: + tmp[jj] += mga * t.igamma(self.embed_quaternion,scale = p**-t.power) * (-f._F[t.label-nE]) + else: + tmp[jj] += (-f._F[t.label-nE]).l_act_by(p**(-t.power)*mga*t.igamma(self.embed_quaternion)) + return self([factor*x for x in tmp]) + + def _compute_atkin_lehner_matrix(self,d): + r""" + When the underlying coefficient module is finite, this + function computes the matrix of an Atkin-Lehner involution in + the basis provided by the function basis_matrix + + INPUT: + + - ``d`` - an integer dividing p*Nminus*Nplus, where these + quantities are associated to the BTQuotient self._X + + OUTPUT: + + - The matrix of the AL-involution at d in the basis given by + self.basis_matrix + + EXAMPLES:: + + sage: X = BTQuotient(5,13) + sage: H = HarmonicCocycles(X,2,prec=5) + sage: A = H.atkin_lehner_operator(5).matrix() # indirect doctest + sage: A**2 == 1 + True + """ + res = self.__compute_operator_matrix(lambda f:self.__apply_atkin_lehner(d,f)) + return res + + def _compute_hecke_matrix_prime(self,l): + r""" + When the underlying coefficient module is finite, this + function computes the matrix of a (prime) Hecke operator in + the basis provided by the function basis_matrix + + INPUT: + + - ``l`` - a prime integer + + OUTPUT: + + - The matrix of `T_l` acting on the cocycles in the basis given by + self.basis_matrix + + EXAMPLES:: + + sage: X = BTQuotient(3,11) + sage: H = HarmonicCocycles(X,4,prec=60) + sage: A = H.hecke_operator(7).matrix() # long time indirect doctest + sage: print [o.rational_reconstruction() for o in A.charpoly().coefficients()] # long time + [6496256, 1497856, -109040, -33600, -904, 32, 1] + """ + res = self.__compute_operator_matrix(lambda f:self.__apply_hecke_operator(l,f)) + return res + + def __compute_operator_matrix(self,T): + r""" + Compute the matrix of the operator `T`. + + Used primarily to compute matrices of Hecke operators + in a streamlined way. + + INPUT: + + - ``T`` - A linear function on the space of harmonic cocycles. + + OUTPUT: + + The matrix of `T` acting on the space of harmonic cocycles. + + EXAMPLES:: + + sage: X = BTQuotient(3,17) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: A = H.hecke_operator(11).matrix() # indirect doctest + sage: print [o.rational_reconstruction() for o in A.charpoly().coefficients()] + [-12, -1, 4, 1] + """ + R = self._R + A = self.basis_matrix().transpose() + basis = self.basis() + B = zero_matrix(R,len(self._E) * (self._k-1),self.dimension()) + for rr in range(len(basis)): + g = T(basis[rr]) + B.set_block(0,rr,Matrix(R,len(self._E) * (self._k-1),1,[g._F[e].moment(ii) for e in range(len(self._E)) for ii in range(self._k-1) ])) + + res = (A.solve_right(B)).transpose() + res.set_immutable() + return res + +# class HarmonicCocyclesSubmodule(HarmonicCocycles,sage.modular.hecke.submodule.HeckeSubmodule): +# r""" +# Submodule of a space of HarmonicCocycles. +# +# INPUT: +# +# - ``x`` - integer (default: 1) the description of the +# argument x goes here. If it contains multiple lines, all +# the lines after the first need to be indented. +# +# - ``y`` - integer (default: 2) the ... +# +# EXAMPLES:: +# +# sage: X = BTQuotient(3,17) +# sage: H = HarmonicCocycles(X,2,prec=10) +# sage: N = H.free_module().span([H.an_element().element()]) +# sage: H1 = H.submodule(N) # indirect doctest +# sage: H1 +# Subspace of Space of harmonic cocycles of weight 2 on Quotient of the Bruhat Tits tree of GL_2(QQ_3) with discriminant 17 and level 1 of dimension 1 +# +# AUTHOR: +# +# - Marc Masdeu (2012-02-20) +# """ +# def __init__(self, ambient_module, submodule, check): +# """ +# Submodule of harmonic cocycles. +# +# INPUT: +# +# - ``ambient_module`` - HarmonicCocycles +# +# - ``submodule`` - submodule of the ambient space. +# +# - ``check`` - (default: False) whether to check that the +# submodule is Hecke equivariant +# +# EXAMPLES:: +# +# sage: X = BTQuotient(3,17) +# sage: H = HarmonicCocycles(X,2,prec=10) +# sage: N = H.free_module().span([H.an_element().element()]) +# sage: H1 = H.submodule(N) +# sage: TestSuite(H1).run() +# """ +# A = ambient_module +# self.__rank = submodule.dimension() +# basis_matrix = submodule.basis_matrix()*A.basis_matrix() +# basis_matrix.set_immutable() +# HarmonicCocycles.__init__(self,A._X,A._k,A._prec,basis_matrix,A.base_ring()) +# +# def rank(self): +# r""" +# Returns the rank (dimension) of the submodule. +# +# OUTPUT: +# +# Integer - The rank of ``self``. +# +# EXAMPLES:: +# +# sage: X = BTQuotient(3,17) +# sage: H = HarmonicCocycles(X,2,prec=10) +# sage: N = H.free_module().span([H.an_element().element()]) +# sage: H1 = H.submodule(basis = [H.an_element()]) +# sage: H1.rank() +# 1 +# """ +# return self.__rank +# +# def _repr_(self): +# r""" +# Returns the representation of self as a string. +# +# OUTPUT: +# +# String representation of self. +# +# EXAMPLES:: +# +# sage: X = BTQuotient(3,17) +# sage: H = HarmonicCocycles(X,2,prec=10) +# sage: N = H.free_module().span([H.an_element().element()]) +# sage: H1=H.submodule(N) +# sage: print H1 +# Subspace of Space of harmonic cocycles of weight 2 on Quotient of the Bruhat Tits tree of GL_2(QQ_3) with discriminant 17 and level 1 of dimension 1 +# """ +# return "Subspace of %s of dimension %s"%(self.ambient(),self.dimension()) + + +class pAutomorphicFormElement(ModuleElement): + r""" + Rudimentary implementation of a class for a p-adic + automorphic form on a definite quaternion algebra over Q. These + are required in order to compute moments of measures associated to + harmonic cocycles on the BT-tree using the overconvergent modules + of Darmon-Pollack and Matt Greenberg. See Greenberg's thesis for + more details. + + INPUT: + + - ``vec`` - A preformatted list of data + + EXAMPLES:: + + sage: X = BTQuotient(17,3) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: h = H.an_element() + sage: HH = pAutomorphicForms(X,2,10) + sage: a = HH(h) + sage: print a + p-adic automorphic form of cohomological weight 0 + + REFERENCES: + + Matthew Greenberg's thesis (available on his webpage as of 02/12). + + AUTHORS: + + - Cameron Franc (2012-02-20) + - Marc Masdeu + + """ + def __init__(self,parent,vec): + """ + Create a pAutomorphicFormElement + + EXAMPLES:: + + sage: X = BTQuotient(17,3) + sage: A = pAutomorphicForms(X,2,prec=10) + sage: TestSuite(A.an_element()).run() + """ + self._num_generators = len(parent._list) + self._cached_values = dict() + self._R = Qp(parent.prime(),prec = parent._prec) + self._value = [ parent._U(v) for v in vec] + ModuleElement.__init__(self,parent) + return + + def _add_(self,g): + r""" + This function adds two p-adic automorphic forms. + + INPUT: + + - ``g`` - a p-adic automorphic form + + OUTPUT: + + - the result of adding g to self + + EXAMPLES:: + + sage: X = BTQuotient(17,3) + sage: A = pAutomorphicForms(X,2,prec=10) + sage: a = A.an_element() + sage: b = a + a # indirect doctest + """ + #Should ensure that self and g are of the same weight and on the same curve + vec = [self._value[e]+g._value[e] for e in range(self._num_generators)] + return self.parent()(vec) + + def _sub_(self,g): + r""" + This function subtracts a p-adic automorphic form from another. + + INPUT: + + - ``g`` - a p-adic automorphic form + + OUTPUT: + + - the result of subtracting g from self + + EXAMPLES:: + + sage: X = BTQuotient(17,3) + sage: A = pAutomorphicForms(X,2,prec=10) + sage: a = A.an_element() + sage: b = a - a # indirect doctest + sage: b == 0 + True + """ + #Should ensure that self and g are of the same weight and on the same curve + vec = [self._value[e]-g._value[e] for e in range(self._num_generators)] + return self.parent()(vec) + + def __cmp__(self,other): + r""" + Test for equality of pAutomorphicForm elements + + INPUT: + + - `other` - Another p-automorphic form + + EXAMPLES:: + + sage: X = BTQuotient(5,23) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: A = pAutomorphicForms(X,2,prec=10) + sage: v1 = A(H.basis()[0]) + sage: v2 = 3*v1 + sage: 2*v1 == v2-v1 # indirect doctest + True + """ + for e in range(self._num_generators): + c = cmp(self._value[e],other._value[e]) + if c: return c + return 0 + + def __nonzero__(self): + """ + Tells whether the form is zero or not. + + OUTPUT: + + Boolean. True if self is zero, false otherwise. + + EXAMPLES:: + + sage: X = BTQuotient(5,23) + sage: H = HarmonicCocycles(X,4,prec=10) + sage: A = pAutomorphicForms(X,4,prec=10) + sage: v1 = A(H.basis()[1]) + sage: v1.__nonzero__() + True + sage: v2 = v1-v1 + sage: v2.__nonzero__() + False + """ + return any([not o.is_zero() for o in self._value]) + + def __getitem__(self,e1): + r""" + Evaluates a p-adic automorphic form on a matrix in `\GL_2(\Qp)`. + + INPUT: + + - ``e1`` - a matrix in `\GL_2(\Qp)` + + OUTPUT: + + - the value of self evaluated on e1 + + EXAMPLES:: + + sage: X = BTQuotient(17,3) + sage: M = HarmonicCocycles(X,2,prec=5) + sage: A = pAutomorphicForms(X,2,prec=5) + sage: a = A(M.gen(0)) + sage: a[Matrix(ZZ,2,2,[1,2,3,4])] + 8 + 8*17 + 8*17^2 + 8*17^3 + 8*17^4 + O(17^5) + """ + return self.evaluate(e1) + + def evaluate(self,e1): + r""" + Evaluates a p-adic automorphic form on a matrix in `\GL_2(\Qp)`. + + INPUT: + + - ``e1`` - a matrix in `\GL_2(\Qp)` + + OUTPUT: + + - the value of self evaluated on e1 + + EXAMPLES:: + + sage: X = BTQuotient(7,5) + sage: M = HarmonicCocycles(X,2,prec=5) + sage: A = pAutomorphicForms(X,2,prec=5) + sage: a = A(M.basis()[0]) + sage: a.evaluate(Matrix(ZZ,2,2,[1,2,3,1])) + 4 + 6*7 + 6*7^2 + 6*7^3 + 6*7^4 + O(7^5) + sage: a.evaluate(Matrix(ZZ,2,2,[17,0,0,1])) + 1 + O(7^5) + """ + X = self.parent()._source + p = self.parent().prime() + u = DoubleCosetReduction(X,e1) + if use_ps_dists: + tmp = ((u.t(self.parent()._U.base_ring().precision_cap()+1))*p**(u.power)).adjoint() + return self.parent()._Sigma0(tmp,check = False) * self._value[u.label] # Warning! Should remove check=False... + else: + return (self._value[u.label].r_act_by((u.t(prec = self.parent().precision_cap()))*p**(u.power))) + + def _rmul_(self,a): + r""" + Multiplies the automorphic form by a scalar. + + EXAMPLES:: + + sage: X = BTQuotient(17,3) + sage: M = HarmonicCocycles(X,2,prec=5) + sage: A = pAutomorphicForms(X,2,prec=5) + sage: a = A(M.basis()[0]) + sage: a.evaluate(Matrix(ZZ,2,2,[1,2,3,4])) + 8 + 8*17 + 8*17^2 + 8*17^3 + 8*17^4 + O(17^5) + sage: b = 2*a # indirect doctest + sage: b.evaluate(Matrix(ZZ,2,2,[1,2,3,4])) + 16 + 16*17 + 16*17^2 + 16*17^3 + 16*17^4 + O(17^5) + """ + #Should ensure that 'a' is a scalar + return self.parent()([a*self._value[e] for e in range(self._num_generators)]) + + def _repr_(self): + r""" + This returns the representation of self as a string. + + If self corresponds to a modular form of weight k, then the + cohomological weight is k-2. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: X = BTQuotient(17,3) + sage: A = pAutomorphicForms(X,2,prec=10) + sage: a = A.an_element() + sage: print a # indirect doctest + p-adic automorphic form of cohomological weight 0 + """ + return 'p-adic automorphic form of cohomological weight %s'%self.parent()._U.weight() + + def valuation(self): + r""" + The valuation of ``self``, defined as the minimum of the + valuations of the values that it takes on a set of edge + representatives. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: X = BTQuotient(17,3) + sage: M = HarmonicCocycles(X,2,prec=10) + sage: A = pAutomorphicForms(X,2,prec=10) + sage: a = A(M.gen(0)) + sage: a.valuation() + 0 + sage: (17*a).valuation() + 1 + """ + return min([self._value[e].valuation() for e in range(self._num_generators)]) + + + def _improve(self): + r""" + Repeatedly applies the `U_p` operator to a p-adic + automorphic form. This is used to compute moments of a measure + associated to a rigid modular form in the following way: lift + a rigid modular form to an ``overconvergent'' `p`-adic + automorphic form in any way, and then repeatedly apply `U_p` + to project to the ordinary part. The resulting form encodes + the moments of the measure of the original rigid modular form + (assuming it is ordinary). + + + EXAMPLES:: + + sage: X = BTQuotient(7,2) + sage: H = HarmonicCocycles(X,2,prec = 10) + sage: h = H.gen(0) + sage: A = pAutomorphicForms(X,2,prec = 10,overconvergent=True) + sage: a = A.lift(h) # indirect doctest + + REFERENCES: + + For details see Matthew Greenberg's thesis (available on his + webpage as of 02/12). Alternatively check out Darmon-Pollack + for the analogous algorithm in the case of modular symbols. + + AUTHORS: + + - Cameron Franc (2012-02-20) + - Marc Masdeu + + """ + MMM = self.parent() + if use_ps_dists: + if MMM._U.is_symk(): + return + U = MMM._U + h1 = MMM(self) + if use_ps_dists: + h1._value = [o.lift(M = MMM.precision_cap()) for o in h1._value] + h2 = MMM._apply_Up_operator(h1,True) + verbose("Applied Up once") + ii = 0 + current_val = 0 + old_val = -Infinity + init_val = self.valuation() + while ii < MMM.precision_cap(): #current_val > old_val: + old_val = current_val + ii += 1 + self._value = [U(c) for c in h2._value] + h2 = MMM._apply_Up_operator(self,scale = True) + current_val = (h2-self).valuation()-init_val + verbose('val = %s'%current_val) + if current_val is Infinity: + break + verbose('Applied Up %s times'%(ii+1)) + self._value = [U(c) for c in h2._value] + + def integrate(self,f,center = 1,level = 0,method = 'moments'): + r""" + Calculate + .. MATH:: + + \int_{\PP^1(\QQ_p)} f(x)d\mu(x) + + were `\mu` is the measure associated to ``self``. + + INPUT: + + - ``f`` - An analytic function. + + - ``center`` - 2x2 matrix over Qp (default: 1) + + - ``level`` - integer (default: 0) + + - ``method`` - string (default: 'moments'). Which method of + integration to use. Either 'moments' or 'riemann_sum'. + + EXAMPLES: + + Integrating the Poisson kernel against a measure yields a + value of the associated modular form. Such values can be + computed efficiently using the overconvergent method, as long + as one starts with an ordinary form:: + + sage: X = BTQuotient(7,2) + sage: X.genus() + 1 + + Since the genus is 1, the space of weight 2 forms is 1 + dimensional. Hence any nonzero form will be a `U_7` + eigenvector. By Jacquet-Langlands and Cerednik-Drinfeld, in + this case the Hecke eigenvalues correspond to that of any + nonzero form on `\Gamma_0(14)` of weight `2`. Such a form is + ordinary at `7`, and so we can apply the overconvergent method + directly to this form without `p`-stabilizing:: + + sage: H = HarmonicCocycles(X,2,prec = 5) + sage: h = H.gen(0) + sage: A = pAutomorphicForms(X,2,prec = 5,overconvergent=True) + sage: a = A.lift(h) + sage: a._value[0].moment(2) + 2 + 6*7 + 4*7^2 + 4*7^3 + 6*7^4 + O(7^5) + + Now that we've lifted our harmonic cocycle to an + overconvergent automorphic form we simply need to define the + Teitelbaum-Poisson Kernel, and then integrate:: + + sage: T. = Qq(49,prec = 5) + sage: R. = PolynomialRing(T) + sage: PK = 1/(z-x) + sage: a.integrate(PK) + (5*x + 5) + (4*x + 4)*7 + (5*x + 5)*7^2 + (5*x + 6)*7^3 + O(7^5) + + AUTHORS: + + - Marc Masdeu (2012-02-20) + - Cameron Franc (2012-02-20) + + """ + E = self.parent()._source._BT.get_balls(center,level) + R1 = LaurentSeriesRing(f.base_ring(),'r1') + R2 = PolynomialRing(f.base_ring(),'x') + x = R2.gen() + value = 0 + ii = 0 + if(method == 'riemann_sum'): + R1.set_default_prec(self.parent()._U.weight()+1) + for e in E: + ii += 1 + #print ii,"/",len(E) + exp = ((R1([e[1,1],e[1,0]]))**(self.parent()._U.weight())*e.determinant()**(-(self.parent()._U.weight())/2))*f(R1([e[0,1],e[0,0]])/R1([e[1,1],e[1,0]])) + #exp = R2([tmp[jj] for jj in range(self.parent()._k-1)]) + new = eval_dist_at_powseries(self.evaluate(e),exp.truncate(self.parent()._U.weight()+1)) + value += new + elif(method == 'moments'): + R1.set_default_prec(self.parent()._U.base_ring().precision_cap()) + n = self.parent()._U.weight() + for e in E: + ii += 1 + #print ii,"/",len(E) + a,b,c,d = e.list() + delta = e.determinant() + verbose('%s'%(R2([e[0,1],e[0,0]])/R2([e[1,1],e[1,0]]))) + tmp = ( (c*x+d)**n * delta**-ZZ(n/2) ) * f( (a*x+b) / (c*x+d) ) + exp = R1(tmp.numerator())/R1(tmp.denominator()) + new = eval_dist_at_powseries(self.evaluate(e),exp) + + + value += new + else: + print 'The available methods are either "moments" or "riemann_sum". The latter is only provided for consistency check, and should never be used.' + return False + return value + + def modular_form(self,z = None,level = 0,method = 'moments'): + r""" + Returns the modular form corresponding to ``self``. + + INPUT: + + - ``z`` - (default: None). If specified, returns the value of + the form at the point ``zz`` in the `p`-adic upper half + plane. + + - ``level`` - integer (default: 0). If ``method`` is + 'riemann_sum', will use a covering of `\PP^1(\QQ_p)` with + balls of size `p^-\mbox{level]`. + + - ``method`` - string (default: ``moments``). It must be + either ``moments`` or ``riemann_sum``. + + OUTPUT: + + - A function from the `p`-adic upper half plane to `\CC_p`. If + an argument ``z`` was passed, returns instead the value at + that point. + + EXAMPLES:: + + Integrating the Poisson kernel against a measure yields a + value of the associated modular form. Such values can be + computed efficiently using the overconvergent method, as long + as one starts with an ordinary form:: + + sage: X=BTQuotient(7,2) + sage: X.genus() + 1 + + Since the genus is 1, the space of weight 2 forms is 1 + dimensional. Hence any nonzero form will be a `U_7` + eigenvector. By Jacquet-Langlands and Cerednik-Drinfeld, in + this case the Hecke eigenvalues correspond to that of any + nonzero form on `\Gamma_0(14)` of weight `2`. Such a form is + ordinary at `7`, and so we can apply the overconvergent method + directly to this form without `p`-stabilizing:: + + sage: H = HarmonicCocycles(X,2,prec = 5) + sage: A = pAutomorphicForms(X,2,prec = 5,overconvergent=True) + sage: f0 = A.lift(H.basis()[0]) + + Now that we've lifted our harmonic cocycle to an + overconvergent automorphic form, we extract the associated + modular form as a function and test the modular property:: + + sage: T. = Qq(7^2,prec = 5) + sage: f = f0.modular_form(method = 'moments') + sage: a,b,c,d = X.embed_quaternion(X.get_units_of_order()[1]).change_ring(T.base_ring()).list() + sage: ((c*x + d)^2*f(x)-f((a*x + b)/(c*x + d))).valuation() + 5 + """ + return self.derivative(z,level,method,order = 0) + + def derivative(self,z = None,level = 0,method = 'moments',order = 1): + r""" + Returns the derivative of the modular form corresponding to + ``self``. + + INPUT: + + - ``z`` - (Default: None). If specified, evaluates the derivative + at the point ``z`` in the `p`-adic upper half plane. + + - ``level`` - integer (default: 0). If ``method`` is + 'riemann_sum', will use a covering of `\PP^1(\QQ_p)` with + balls of size `p^-\mbox{level]`. + + - ``method`` - string (default: ``moments``). It must be + either ``moments`` or ``riemann_sum``. + + - ``order`` - integer (Default: 1). The order of the + derivative to be computed. + + OUTPUT: + + - A function from the `p`-adic upper half plane to `\CC_p`. If + an argument ``z`` was passed, returns instead the value of + the derivative at that point. + + EXAMPLES: + + Integrating the Poisson kernel against a measure yields a + value of the associated modular form. Such values can be + computed efficiently using the overconvergent method, as long + as one starts with an ordinary form:: + + sage: X=BTQuotient(7,2) + sage: X.genus() + 1 + + Since the genus is 1, the space of weight 2 forms is 1 + dimensional. Hence any nonzero form will be a `U_7` + eigenvector. By Jacquet-Langlands and Cerednik-Drinfeld, in + this case the Hecke eigenvalues correspond to that of any + nonzero form on `\Gamma_0(14)` of weight `2`. Such a form is + ordinary at `7`, and so we can apply the overconvergent method + directly to this form without `p`-stabilizing:: + + sage: H = HarmonicCocycles(X,2,prec=5) + sage: h = H.gen(0) + sage: A = pAutomorphicForms(X,2,prec=5,overconvergent=True) + sage: f0 = A.lift(h) + + Now that we've lifted our harmonic cocycle to an + overconvergent automorphic form, we extract the associated + modular form as a function and test the modular property:: + + sage: T. = Qq(49,prec=10) + sage: f = f0.modular_form() + sage: g = X.get_embedding_matrix()*X.get_units_of_order()[1] + sage: a,b,c,d = g.change_ring(T).list() + sage: (c*x +d)^2*f(x)-f((a*x + b)/(c*x + d)) + O(7^5) + + We can also compute the Shimura-Maass derivative, which is a + nearly rigid analytic modular forms of weight 4:: + + sage: f = f0.derivative() + sage: (c*x + d)^4*f(x)-f((a*x + b)/(c*x + d)) + O(7^5) + + REFERENCES: + + For a discussion of nearly rigid analytic modular forms and + the rigid analytic Shimura-Maass operator, see the thesis of + C. Franc [2011]. + """ + def F(z,level = level,method = method): + R = PolynomialRing(z.parent(),'x,y').fraction_field() + Rx = PolynomialRing(z.parent(),'x1').fraction_field() + x1 = Rx.gen() + subst = R.hom([x1,z],codomain = Rx) + x,y = R.gens() + center = self.parent()._source._BT.find_containing_affinoid(z) + zbar = z.trace()-z + f = R(1)/(x-y) + k = self.parent()._n+2 + V = [f] + for ii in range(order): + V = [v.derivative(y) for v in V]+[k/(y-zbar)*v for v in V] + k += 2 + return sum([self.integrate(subst(v),center,level,method) for v in V]) + if z is None: + return F + + return F(z,level,method) + + + # So far we can't break it into two integrals because of the pole at infinity. + def coleman(self,t1,t2,E = None,method = 'moments',mult = False,delta = -1,level = 0): + r""" + If ``self`` is a `p`-adic automorphic form that + corresponds to a rigid modular form, then this computes the + coleman integral of this form between two points on the + boundary `\PP^1(\QQ_p)` of the `p`-adic upper half plane. + + INPUT: + + - ``t1``, ``t2`` - elements of `\PP^1(\QQ_p)` (the endpoints + of integration) + + - ``E`` - (Default: None). If specified, will not compute the + covering adapted to ``t1`` and ``t2`` and instead use the + given one. In that case, ``E`` should be a list of matrices + corresponding to edges describing the open balls to be + considered. + + - ``method`` - string (Default: 'moments'). Tells which + algorithm to use (alternative is 'riemann_sum', which is + unsuitable for computations requiring high precision) + + - ``mult`` - boolean (Default: False). Whether to use the + multiplicative version. + + - ``delta`` - integer (Default: -1) + + - ``level`` - integer (Default: 0) + + OUTPUT: + + The result of the coleman integral + + EXAMPLES:: + + sage: p = 7 + sage: lev = 2 + sage: prec = 10 + sage: X = BTQuotient(p,lev, use_magma = True) # optional - magma + sage: k = 2 # optional - magma + sage: M = HarmonicCocycles(X,k,prec) # optional - magma + sage: B = M.basis() # optional - magma + sage: f = 3*B[0] # optional - magma + sage: MM = pAutomorphicForms(X,k,prec,overconvergent = True) # optional - magma + sage: D = -11 # optional - magma + sage: X.is_admissible(D) # optional - magma + True + sage: K. = QuadraticField(D) # optional - magma + sage: Kp. = Qq(p**2,prec) # optional - magma + sage: P = Kp.gen() # optional - magma + sage: Q = 2+Kp.gen()+ p*(Kp.gen() +1) # optional - magma + sage: F = MM.lift(f) # long time optional - magma + sage: J0 = F.coleman(P,Q,mult = True) # long time optional - magma + sage: print J0 # optional - magma + 1 + (4*g + 3)*7 + (g + 5)*7^2 + (3*g + 4)*7^3 + (4*g + 3)*7^4 + (3*g + 4)*7^5 + (2*g + 1)*7^6 + 5*g*7^7 + (4*g + 6)*7^8 + (4*g + 1)*7^9 + O(7^10) + + AUTHORS: + + - Cameron Franc (2012-02-20) + - Marc Masdeu (2012-02-20) + """ + if(mult and delta >= 0): + raise NotImplementedError, "Need to figure out how to implement the multiplicative part." + p = self.parent().prime() + K = t1.parent() + R = PolynomialRing(K,'x') + x = R.gen() + R1 = LaurentSeriesRing(K,'r1') + r1 = R1.gen() + if(E is None): + E = self.parent()._source._BT.find_covering(t1,t2) + # print 'Got %s open balls.'%len(E) + value = 0 + ii = 0 + value_exp = K(1) + if(method == 'riemann_sum'): + R1.set_default_prec(self.parent()._U.weight()+1) + for e in E: + ii += 1 + b = e[0,1] + d = e[1,1] + y = (b-d*t1)/(b-d*t2) + poly = R1(y.log()) #R1(our_log(y)) + c_e = self.evaluate(e) + new = eval_dist_at_powseries(c_e,poly) + value += new + if mult: + value_exp *= K.teichmuller(y)**Integer(c_e.moment(0).rational_reconstruction()) + + elif(method == 'moments'): + R1.set_default_prec(self.parent()._U.base_ring().precision_cap()) + for e in E: + ii += 1 + f = (x-t1)/(x-t2) + a,b,c,d = e.list() + y0 = f(R1([b,a])/R1([d,c])) #f( (ax+b)/(cx+d) ) + y0 = p**(-y0(ZZ(0)).valuation())*y0 + mu = K.teichmuller(y0(ZZ(0))) + y = y0/mu-1 + poly = R1(0) + ypow = y + for jj in range(1,R1.default_prec()+10): + poly += (-1)**(jj+1)*ypow/jj + ypow *= y + if(delta >= 0): + poly *= ((r1-t1)**delta*(r1-t2)**(self.parent()._n-delta)) + c_e = self.evaluate(e) + new = eval_dist_at_powseries(c_e,poly) + if hasattr(new,'degree'): + assert 0 + value += new + if mult: + value_exp *= K.teichmuller(((b-d*t1)/(b-d*t2)))**Integer(c_e.moment(0).rational_reconstruction()) + + else: + print 'The available methods are either "moments" or "riemann_sum". The latter is only provided for consistency check, and should not be used in practice.' + return False + if mult: + return K.teichmuller(value_exp) * value.exp() + return value + + +class pAutomorphicForms(Module,UniqueRepresentation): + Element = pAutomorphicFormElement + + @staticmethod + def __classcall__(cls,domain,U,prec = None,t = None,R = None,overconvergent = False): + r""" + The module of (quaternionic) `p`-adic automorphic forms. + + INPUT: + + - `domain` - A BTQuotient. + + - `U` - A coefficient module or an integer. If U is a + coefficient module then this creates the relevant space of + automorphic forms. If U is an integer then the coefficients + are the (`U-2`)nd power of the symmetric representation of + `\GL_2(\Qp)`. + + - `prec` - A precision (Default = None). If not None should + be a positive integer + + - `t` - (Default = None). + + - `R` - (Default = None). + + - `overconvergent` - Boolean (Default = False). + + EXAMPLES: + + The space of weight 2 p-automorphic forms is isomorphic with + the space of scalar valued invariant harmonic cocycles:: + + sage: X = BTQuotient(11,5) + sage: H0 = pAutomorphicForms(X,2,10) + sage: H1 = pAutomorphicForms(X,2,prec = 10) + sage: H0 == H1 + True + + AUTHORS: + + - Cameron Franc (2012-02-20) + - Marc Masdeu (2012-02-20) + """ + return super(pAutomorphicForms,cls).__classcall__(cls,domain,U,prec,t,R,overconvergent) + + def __init__(self,domain,U,prec = None,t = None,R = None,overconvergent = False): + """ + Create a space of p-automorphic forms + + EXAMPLES:: + + sage: X = BTQuotient(11,5) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: A = pAutomorphicForms(X,2,prec=10) + sage: TestSuite(A).run() + """ + if(R is None): + if not isinstance(U,Integer): + self._R = U.base_ring() + else: + if(prec is None): + prec = 100 + self._R = Qp(domain._p,prec) + else: + self._R = R + #U is a CoefficientModuleSpace + if isinstance(U,Integer): + if t is None: + if overconvergent: + t = prec-U+1 + else: + t = 0 + if use_ps_dists: + if overconvergent: + self._U = Distributions(U-2,base = self._R,prec_cap = U - 1 + t ,act_on_left = True,adjuster = _btquot_adjuster(), dettwist = -ZZ((U-2)/2)) #monoid = MatrixSpace(self._R,2,2)) + else: + self._U = Symk(U-2,base = self._R,act_on_left = True,adjuster = _btquot_adjuster(), dettwist = -ZZ((U-2)/2)) #monoid = MatrixSpace(self._R,2,2)) + else: + self._U = OCVn(U-2,self._R,U-1+t) + else: + self._U = U + self._source = domain + self._list = self._source.get_list() # Contains also the opposite edges + self._prec = self._R.precision_cap() + self._n = self._U.weight() + self._p = self._source._p + + if use_ps_dists: + # self._Sigma0 = Sigma0(1, base_ring = self._U.base_ring(),adjuster = _btquot_adjuster()) + self._Sigma0 = self._U._act._Sigma0 + + Module.__init__(self,base = self._R) + self._populate_coercion_lists_() + + def prime(self): + """ + Return the underlying prime. + + OUTPUT: + + - `p` - a prime integer + + EXAMPLES:: + + sage: X = BTQuotient(11,5) + sage: H = HarmonicCocycles(X,2,prec = 10) + sage: A = pAutomorphicForms(X,2,prec = 10) + sage: A.prime() + 11 + """ + return self._p + + def zero_element(self): + r""" + Returns the zero element of self. + + EXAMPLES:: + + sage: X = BTQuotient(5,7) + sage: H1 = pAutomorphicForms(X,2,prec = 10) + sage: H1.zero_element() == 0 + True + """ + + return self.element_class(self,[self._U(0) for o in self._list]) + + def __cmp__(self,other): + r""" + Tests whether two pAutomorphicForm spaces are equal. + + INPUT: + + - `other` - another space of p-automorhic forms. + + OUTPUT: + + A boolean value + + EXAMPLES:: + + sage: X = BTQuotient(5,7) + sage: H1 = pAutomorphicForms(X,2,prec = 10) + sage: H2 = pAutomorphicForms(X,2,prec = 10) + sage: H1 == H2 + True + """ + res = cmp(self.base_ring(),other.base_ring()) + if res: return res + res = cmp(self._source,other._source) + if res: return res + res = cmp(self._U,other._U) + if res: return res + return 0 + + def _repr_(self): + r""" + Returns the representation of self as a string. + + EXAMPLES:: + + sage: X = BTQuotient(3,7) + sage: A = pAutomorphicForms(X,2,prec = 10) + sage: print A # indirect doctest + Space of automorphic forms on Quotient of the Bruhat Tits tree of GL_2(QQ_3) with discriminant 7 and level 1 with values in Sym^0 Q_3^2 + """ + s = 'Space of automorphic forms on '+str(self._source)+' with values in '+str(self._U) + return s + + def _coerce_map_from_(self, S): + r""" + Can coerce from other HarmonicCocycles or from pAutomorphicForms + + INPUT: + + - ``S`` - a HarmonicCocycle or pAutomorphicForm + + OUTPUT: + + A boolean value. True iff S is coercible into self. + + EXAMPLES:: + + sage: X = BTQuotient(3,7) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: A = pAutomorphicForms(X,2,prec=10) + sage: A._coerce_map_from_(H) + True + """ + if isinstance(S,HarmonicCocycles): + if S.weight()-2 != self._n: + return False + if S._X != self._source: + return False + return True + if isinstance(S,pAutomorphicForms): + if S._n != self._n: + return False + if S._source != self._source: + return False + return True + return False + + def _element_constructor_(self,x): + r""" + Constructs a p-automorphic form. + + INPUT: + + - ``x`` - + + OUTPUT: + + A p-automorphic form. + + EXAMPLES:: + + sage: X = BTQuotient(13,5) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: h=H.an_element() # indirect doctest + sage: A = pAutomorphicForms(X,2,prec=10) + sage: A(h) + p-adic automorphic form of cohomological weight 0 + """ + #Code how to coherce x into the space + #Admissible values of x? + if type(x) is list: + return self.element_class(self,[self._U(o) for o in x]) + + if isinstance(x,pAutomorphicFormElement): + return self.element_class(self,[self._U(o) for o in x._value]) + + if isinstance(x,HarmonicCocycleElement): + E = self._list + tmp = [] + F = [] + Uold = x.parent()._U + for ii in range(len(x._F)): + if use_ps_dists: + newtmp = x.parent()._Sigma0(E[ii].rep.inverse(),check = False) * x.parent()._U(x._F[ii]) ## Warning, should remove check=False! + else: + newtmp = Uold(x._F[ii]).l_act_by(E[ii].rep.inverse()) + tmp.append(newtmp) + F.append(newtmp) + A = Matrix(QQ,2,2,[0,-1/self.prime(),-1,0]) + for ii in range(len(x._F)): + if use_ps_dists: + F.append(-(x.parent()._Sigma0(A.adjoint(),check = False) * tmp[ii])) + else: + F.append(Uold(-1*tmp[ii]).r_act_by(A)) + vals = self._make_invariant([self._U(o) for o in F]) + return self.element_class(self,vals) + if x == 0: + return self.zero_element() + + def _an_element_(self): + r""" + Returns an element of the module. + + OUTPUT: + + A harmonic cocycle. + + EXAMPLES:: + + sage: X = BTQuotient(13,5) + sage: A = pAutomorphicForms(X,2,prec=10) + sage: A.an_element() # indirect doctest + p-adic automorphic form of cohomological weight 0 + """ + return self(0) + + def precision_cap(self): + """ + Return the precision of self. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: X = BTQuotient(13,11) + sage: A = pAutomorphicForms(X,2,prec=10) + sage: A.precision_cap() + 10 + """ + return self._prec + + def lift(self,f): + r""" + Lifts the harmonic cocycle ``f`` to a p-automorphic form. + + If one is using overconvergent coefficients, then this will + compute all of the moments of the measure associated to ``f``. + + INPUT: + + - ``f`` - a harmonic cocycle + + OUTPUT: + + A p-automorphic form + + EXAMPLES: + + If one does not work with an overconvergent form then lift + does nothing:: + + sage: X = BTQuotient(13,5) + sage: H = HarmonicCocycles(X,2,prec=10) + sage: h = H.gen(0) + sage: A = pAutomorphicForms(X,2,prec=10) + sage: A.lift(h) + p-adic automorphic form of cohomological weight 0 + + With overconvergent forms, the input is lifted naively and its + moments are computed:: + + sage: X = BTQuotient(13,11) + sage: H = HarmonicCocycles(X,2,prec=5) + sage: A2 = pAutomorphicForms(X,2,prec=5,overconvergent=True) + sage: a = H.gen(0) + sage: A2.lift(a) + p-adic automorphic form of cohomological weight 0 + """ + F = self(f) + F._improve() + return F + + def _make_invariant(self, F): + r""" + Naively lifts a ``classical`` automorphic form to an + overconvergent form. + + INPUT: + + - ``F`` - a classical (nonoverconvergent) pAutomorphicForm or + HarmonicCocycle. + + OUTPUT: + + An overconvergent pAutomorphicForm + + EXAMPLES:: + + sage: X = BTQuotient(13,11) + sage: H = HarmonicCocycles(X,2,prec = 5) + sage: A = pAutomorphicForms(X,2,prec = 5) + sage: h = H.basis()[0] + sage: A.lift(h) # indirect doctest + p-adic automorphic form of cohomological weight 0 + + """ + S = self._source.get_stabilizers() + M = [e.rep for e in self._list] + newF = [] + for ii in range(len(S)): + Si = S[ii] + if use_ps_dists: + x = self._U(F[ii]) + else: + x = self._U(F[ii]) + + if(any([v[2] for v in Si])): + newFi = self._U(0) + s = QQ(0) + m = M[ii] + for v in Si: + s += 1 + if use_ps_dists: + newFi += self._Sigma0((m.adjoint() * self._source.embed_quaternion(v[0],prec = self._prec)*m).adjoint(),check = False) * self._U(x) + else: + newFi += x.r_act_by(m.adjoint()*self._source.embed_quaternion(v[0],prec = self._prec)*m) + newF.append((1/s)*newFi) + else: + newF.append(self._U(x)) + return newF + + def _apply_Up_operator(self,f,scale = False, fix_lowdeg_terms = True): + r""" + Apply the Up operator to ``f``. + + EXAMPLES:: + + sage: X = BTQuotient(3,11) + sage: M = HarmonicCocycles(X,4,10) + sage: A = pAutomorphicForms(X,4,10, overconvergent = True) + sage: F = A.lift(M.basis()[0]); F # indirect doctest + p-adic automorphic form of cohomological weight 2 + """ + HeckeData = self._source._get_Up_data() + if scale == False: + factor = self._p**(self._U.weight()/2) + else: + factor = 1 + + # Save original moments + if use_ps_dists: + orig_moments = [ [fval._moments[ii] for ii in range(self._n+1)] for fval in f._value] + + + Tf = [] + for jj in range(len(self._list)): + tmp = self._U(0) + for d in HeckeData: + gg = d[0] # acter + u = d[1][jj] # edge_list[jj] + r = (self._p**(-(u.power)) * (u.t(self._U.base_ring().precision_cap() + 2*u.power + 1)*gg)) + if use_ps_dists: + tmp += self._Sigma0(r.adjoint(),check = False) * f._value[u.label] # Warning: should activate check... + else: + tmp += f._value[u.label].r_act_by(r) + + tmp *= factor + for ii in range(self._n+1): + if use_ps_dists: + tmp._moments[ii] = orig_moments[jj][ii] + else: + tmp.moments[ii,0] = f._value[jj].moments[ii,0] + Tf.append(tmp) + return self(Tf) diff --git a/src/sage/modular/btquotients/utility.py b/src/sage/modular/btquotients/utility.py new file mode 100644 index 00000000000..000d0487bfb --- /dev/null +++ b/src/sage/modular/btquotients/utility.py @@ -0,0 +1,295 @@ +######################################################################### +# Copyright (C) 2011 Cameron Franc and Marc Masdeu +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# http://www.gnu.org/licenses/ +######################################################################### + + +from itertools import product,chain +from sage.rings.all import Qp + +def getcoords(E,u,prec=20): + q = E.parameter(prec=prec) + un = u * q**(-(u.valuation()/q.valuation()).floor()) + precn = (prec/q.valuation()).floor() + 4 + + # formulas in Silverman II (Advanced Topics in the Arithmetic of Elliptic curves, p. 425) + + xx = un/(1-un)**2 + sum( [q**n*un/(1-q**n*un)**2 + q**n/un/(1-q**n/un)**2-2*q**n/(1-q**n)**2 for n in range(1,precn) ]) + + yy = un**2/(1-un)**3 + sum( [q**(2*n)*un**2/(1-q**n*un)**3 - q**n/un/(1-q**n/un)**3+q**n/(1-q**n)**2 for n in range(1,precn) ]) + + C,r,s,t = E._inverse_isomorphism(prec=prec) + C2 = C**2 + return ( r + C2 * xx, t + s * C2 * xx + C * C2 * yy ) + + +def our_sqrt(x,K): + if(x==0): + return x + x=K(x) + p=K.base_ring().prime() + z=K.gen() + found=False + for a,b in product(range(p),repeat=2): + y0=a+b*z + if((y0**2-x).valuation()>0): + found=True + break + y1=y0 + y=0 + while(y!=y1): + y=y1 + y1=(y**2+x)/(2*y) + return y + +def our_log(x,prec=None): + K=x.parent() + if prec is None: + prec=K.precision_cap()+10 + x0=x.unit_part() + y=x0/K.teichmuller(x0)-1 + tmp=K(0) + ypow=y + for ii in range(1,prec+1): + tmp+=(-1)**(ii+1)*ypow/ii + ypow*=y + return tmp + +def our_exp(x,prec=None): + K=x.parent() + if prec is None: + prec=K.precision_cap()+10 + tmp=K(1+x) + xpow=x**2 + iifact=2 + for ii in range(3,prec): + tmp+=xpow/iifact + xpow*=x + iifact*=ii + return tmp + + +def fix_deg_monomials(v,n): + return [reduce(lambda x,y:x*y,[v[ii]**(part[ii]-1) for ii in range(len(v))]) for part in OrderedPartitions(len(v)+n,len(v))] + + +#The list of elements elts must be in the form [a1,a1^-1,a2,a2^{-1}, etc] +def free_group_words(elts,op=None,init=[1]): + if op is None: + op=lambda x,y:x*y + allwords=[] + + ii=0 + n=1 + # Generate words of length 1 + for i in range(len(elts)): + wd=[i,op(elts[i],init),[i]] + ii+=1 + if ii%10000==0: + print ii + yield wd[1] + #yield wd[1],n,wd[2] + allwords.append(wd) + + # Generate longer words + while True: + n+=1 + newwords = [] + for pairs in allwords: + leftind = pairs[0] + if leftind % 2 == 0: + omit = leftind+1 + else: + omit = leftind-1 + for i in range(omit)+range(omit+1,len(elts)): + wd=[i,op(elts[i],pairs[1]),[i]+pairs[2]] + ii+=1 + if ii%10000==0: + print ii + yield wd[1] + #yield wd[1],n,wd[2] + newwords.append(wd) + allwords=newwords + + +#Act by a fractional linear transformation on an element of the p-adic upper half plane +# The parameter twist corresponds to applying a change of variables given by the +# matrix [1,0,twist,1] +def act_by_flt(g,Z,twist = 0): + bb=g[0,1] + btwist=bb*twist + aa, dd=g[0,0]+btwist,g[1,1]-btwist + cc=g[1,0]+(g[1,1]-aa)*twist + try: + return [(aa*z + bb)/(cc*z + dd) for z in Z] + except TypeError: + return (aa*Z + bb)/(cc*Z + dd) + + +def get_action_flt(twist): + return lambda g,Z:act_by_flt(g,Z,twist) + +def find_good_monomial(f): + d=max(f.degrees()) + for x in f.parent().gens(): + x2d=x**d + print 'Trying monomial ',x + print 'Appears in degree',f.degree(x) + print 'and the other deg is',(f-f.coefficient(x2d)*x2d).degree(x) + + if f.degree(x)>0 and (f-f.coefficient(x2d)*x2d).degree(x)==0: + return x2d + return None + +# Finds relations among the modular forms in X +# Up to a given degree +def find_relations(X,dmax,prec,generators,h=0): + genus=len(X) + p=X[0].parent()._X.prime() + K=Qq(p^2,prec = prec, names = 'g') + g=K.gen() + max_num_monomials=binomial(genus+dmax-1,dmax) + + sys.stdout.flush() + CEP=[] + for ii in range(max_num_monomials+h): + Pt=g+p*ii + sys.stdout.write("#") + sys.stdout.flush() + CEP.append([f.modular_form(Pt) for f in X]) + + V=[] + for d in range(2,dmax+1): + num_monomials=binomial(genus+d-1,d) + A=Matrix(K,num_monomials+h,num_monomials,[fix_deg_monomials(CEP[ii][:num_monomials],d) for ii in range(num_monomials+h)]) + for v in V: + # Find a suitable monomial to cancel higher degrees + d0=v[0] + f0=sum([x[0] for x in v[1]]) + xi2d=find_good_monomial(f0) + assert not xi2d is None + tmons=fix_deg_monomials(generators,d-d0) + degdmons=fix_deg_monomials(generators,d) + pos=[(xi2d*t,degdmons.index(xi2d*t)) for t in tmons] + A=A.stack(Matrix(K,len(pos),num_monomials,dict([((ii,pos[ii][1]),1) for ii in range(len(pos))]))) + B=A.right_kernel().matrix() + assert(B.nrows()==1) + mons=fix_deg_monomials(generators,d) + tmp=B.row(0) + newV=filter(lambda x:x[1]!=0,zip(mons,tmp)) + print 'newV=',newV + V.append((d,newV)) + return V + + +def find_invariants(genus,V,P): + generators=P.gens() + goodMons=list(chain.from_iterable([v[1] for v in V])) + assert all([x[1]!=0 for x in goodMons]) + + A=copy(Matrix(ZZ,len(goodMons),genus,[tuple(x[0].degrees()) for x in goodMons]).kernel().matrix()) + + n_invariants=A.nrows() + goodcols=[] + + # Try to select columns to become dependent variables + for ii in range(A.nrows()): + found=False + for jj in range(A.ncols()): + if ZZ(A[ii,jj]).abs()==1 and all([all([A[i1,jj]*A[i1,j1]==0 for j1 in goodcols]) for i1 in range(ii+1,A.nrows())]): + goodcols.append(jj) + found=True + break + if not found: raise RuntimeError + A.rescale_row(ii,A[ii,jj]) + assert(A[ii,jj]==1) + for i0 in range(ii)+range(ii+1,A.nrows()): + A.add_multiple_of_row(i0,ii,-A[i0,jj]) + + badcols=range(A.ncols()) + for x in goodcols: + badcols.remove(x) + + ################ + # Just to gather more information + print 'goodcols=',goodcols + print 'badcols=',badcols + for ii in range(A.nrows()): + r=A.row(ii) + tmp=1 + for jj in range(A.ncols()): + if(A[ii,jj]!=0): + tmp*=goodMons[jj][1]**ZZ(A[ii,jj]) + if jj<5: + print 'a%s^(%s)'%(jj,ZZ(A[ii,jj])), + else: + print 'b%s^(%s)'%(jj-5,ZZ(A[ii,jj])), + print '' + rat=algdep(tmp,1).roots(RationalField())[0][0] + print 'rat=',rat + ################ + + S0=PolynomialRing(QQ,genus,names='a') + S=S0.fraction_field() + lst=[] + for j0 in range(A.ncols()): + try: lst.append(S.gen(badcols.index(j0))) + except ValueError: + ii=goodcols.index(j0) + r=A.row(ii) + tmp=1 + mon=1 + for jj in range(A.ncols()): + if(A[ii,jj]!=0): + tmp*=goodMons[jj][1]**ZZ(A[ii,jj]) + if jj!=j0: + mon*=S.gen(badcols.index(jj))**(-ZZ(A[ii,jj])) + rat=algdep(tmp,1).roots(RationalField())[0][0] + lst.append(S(rat*mon)) + PolyS=P.change_ring(S) + F=[] + ii=0 + for d,v in V: + f=PolyS(0) + for x in filter(lambda x:x[1]!=0,v): + f+=PolyS(lst[ii])*PolyS(x[0]) + ii+=1 + F.append(f*f.denominator()) + PolyS0=P.change_ring(S0) + return [PolyS0(f) for f in F] + +def substitute(F,**args): + R=F[0].parent() + tmp=[R(f.subs(**args)) for f in F] + return [lcm([x.denominator() for x in f.coefficients()])*f for f in tmp] + +def find_divisor(F,x): + R=F[0].parent() + gens=R.gens() + y=gens[(gens.index(x)+1)%len(gens)] + F1=[f.subs(dict([(x,0),(y,1)])) for f in F] + S = PolynomialRing(RationalField(), 'y') + y = S.gen() + others=[] + for f in F1: + if list(f.degrees()).count(0)==len(gens)-1: + # It means that it is really a single variable polynomial + ii=list(f.degrees()).index(f.degree()) + xi=gens[ii] + lst=[] + for jj in range(len(gens)): + if jj==ii: + lst.append(S.gen(0)) + else: + lst.append(0) + phi=R.hom(lst,codomain=S,check=False) + fone=phi(f) + S0=S.base_extend((fone/fone.leading_coefficient()).root_field('a')) + a=S0(fone).roots()[0][0] + else: + others.append(f) + others=[f.subs(dict([(f.parent().gen(ii),a)])) for f in others] + return others diff --git a/src/sage/modular/pollack_stevens/__init__.py b/src/sage/modular/pollack_stevens/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/sage/modular/pollack_stevens/all.py b/src/sage/modular/pollack_stevens/all.py new file mode 100644 index 00000000000..1173cd84ba2 --- /dev/null +++ b/src/sage/modular/pollack_stevens/all.py @@ -0,0 +1,5 @@ +from space import PSModularSymbols +from distributions import Distributions, Symk +from fund_domain import ManinRelations +from padic_lseries import pAdicLseries + diff --git a/src/sage/modular/pollack_stevens/dist.pxd b/src/sage/modular/pollack_stevens/dist.pxd new file mode 100644 index 00000000000..77a077d790f --- /dev/null +++ b/src/sage/modular/pollack_stevens/dist.pxd @@ -0,0 +1,66 @@ +from sage.structure.sage_object cimport SageObject +from sage.structure.element cimport ModuleElement +from sage.categories.action cimport Action +from sage.rings.padics.pow_computer cimport PowComputer_class +from sage.libs.flint.ulong_extras cimport * + +#cdef extern from "../../../ext/multi_modular.h": +# ctypedef unsigned long mod_int +# mod_int MOD_INT_MAX + + + +cdef class Dist(ModuleElement): + cpdef normalize(self) + cdef long ordp + cdef long _relprec(self) + cdef _unscaled_moment(self, long i) + +cdef class Dist_vector(Dist): + cdef public _moments + cdef Dist_vector _new_c(self) + cdef Dist_vector _addsub(self, Dist_vector right, bint negate) + +#cdef class Dist2(Dist): # only works on 64-bit.... +# cdef long[60] moments +# cdef int prec +# cdef public PowComputer_long prime_pow +# cdef Dist2 _new_c(self) + +cdef class Dist_long(Dist): + cdef long[60] _moments # 38 once 2 is special-cased + cdef int relprec + cdef public PowComputer_class prime_pow + cdef int quasi_normalize(self) except -1 + cdef Dist_long _new_c(self) + cdef Dist_long _addsub(self, Dist_long right, bint negate) + +cdef class WeightKAction(Action): + cdef public _k + cdef public _character + cdef public _adjuster + cdef public _p + cdef public _Np + cdef public _actmat + cdef public _maxprecs + cdef public _symk + cdef public _dettwist + cdef public _Sigma0 + + + cpdef acting_matrix(self, g, M) + cpdef _compute_acting_matrix(self, g, M) + +cdef class WeightKAction_vector(WeightKAction): + pass + +cdef class SimpleMat(SageObject): + cdef long* _mat + cdef long M + cdef bint _inited + +cdef class WeightKAction_long(WeightKAction): + pass + +cdef class iScale(Action): + pass diff --git a/src/sage/modular/pollack_stevens/dist.pyx b/src/sage/modular/pollack_stevens/dist.pyx new file mode 100644 index 00000000000..5133775c716 --- /dev/null +++ b/src/sage/modular/pollack_stevens/dist.pyx @@ -0,0 +1,1872 @@ +# cython: profile=True + +#***************************************************************************** +# Copyright (C) 2012 Robert Pollack +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.structure.sage_object import SageObject +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.rings.polynomial.all import PolynomialRing +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.finite_rings.integer_mod_ring import Zmod +from sage.rings.arith import binomial, bernoulli +from sage.modules.free_module_element import vector, zero_vector +from sage.matrix.matrix cimport Matrix +from sage.matrix.matrix_space import MatrixSpace +from sage.matrix.all import matrix +from sage.misc.prandom import random +from sage.functions.other import floor +from sage.structure.element cimport RingElement, Element +import operator +from sage.rings.padics.padic_generic import pAdicGeneric +from sage.rings.padics.padic_capped_absolute_element cimport pAdicCappedAbsoluteElement +from sage.rings.padics.padic_capped_relative_element cimport pAdicCappedRelativeElement +from sage.rings.padics.padic_fixed_mod_element cimport pAdicFixedModElement +from sage.rings.integer cimport Integer +from sage.rings.rational cimport Rational +from sage.misc.misc import verbose, cputime +from sage.rings.infinity import Infinity + +include "sage/ext/cdefs.pxi" +include "sage/ext/interrupt.pxi" +include "sage/libs/flint/fmpz_poly.pxi" +include "sage/ext/stdsage.pxi" + +from sage.libs.flint.nmod_poly cimport nmod_poly_init2_preinv,nmod_poly_set_coeff_ui,nmod_poly_inv_series,nmod_poly_mullow,nmod_poly_pow_trunc,nmod_poly_get_coeff_ui,nmod_poly_t + +from sage.libs.flint.ulong_extras cimport * + +from sigma0 import Sigma0 + +cdef long overflow = 1 << (4*sizeof(long)-1) +cdef long underflow = -overflow +cdef long maxordp = (1L << (sizeof(long) * 8 - 2)) - 1 + +def get_dist_classes(p, prec_cap, base, symk): + r""" + Determines the element and action classes to be used for given inputs. + + INPUT: + + - ``p`` -- prime + + - ``prec_cap`` -- The p-adic precision cap + + - ``base`` -- The base ring + + - ``symk`` -- An element of Symk + + OUTPUT: + + - Either a Dist_vector and WeightKAction_vector, or a Dist_vector_long + and WeightKAction_vector_long + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.dist import get_dist_classes + sage: pass + """ + if symk or p is None or base.is_field() or (isinstance(base, pAdicGeneric) and base.degree() > 1): + return Dist_vector, WeightKAction_vector + if 7*p**(prec_cap) < ZZ(2)**(4*sizeof(long)-1): + return Dist_long, WeightKAction_long + else: + return Dist_vector, WeightKAction_vector + +cdef class Dist(ModuleElement): + r""" + The main p-adic distribution class, implemented as per the paper + 'Overconvergent Modular Symbols and p-adic L-functions' by Pollack + & Stevens + """ + def moment(self, n): + r""" + Returns the `n`-th moment. + + INPUT: + + - ``n`` -- an integer or slice, to be passed on to moments. + + OUTPUT: + + - the `n`-th moment, or a list of moments in the case that `n` + is a slice. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + if self.ordp == 0: + return self._unscaled_moment(n) + else: + return self.parent().prime()**(self.ordp) * self._unscaled_moment(n) + + cpdef normalize(self): + r""" + Normalize so that the precision of the `i`-th moment is `n-i`, + where `n` is the number of moments. + + OUTPUT: + + - Normalized entries of the distribution + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + sage: D = Distributions(5, 7, 15) + sage: D + Space of 7-adic distributions with k=5 action and precision cap 15 + sage: v = D([1,2,3,4,5]); v + (1 + O(7^5), 2 + O(7^4), 3 + O(7^3), 4 + O(7^2), 5 + O(7)) + sage: v.normalize() + (1 + O(7^5), 2 + O(7^4), 3 + O(7^3), 4 + O(7^2), 5 + O(7)) + """ + raise NotImplementedError + + cdef long _relprec(self): + raise NotImplementedError + + cdef _unscaled_moment(self, long i): + raise NotImplementedError + + def scale(self,left): + r""" + Scales the moments of the distribution by `left` + + INPUT: + + - ``left`` -- scalar + + OUTPUT: + + - Scales the moments by `left` + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + sage: D = Distributions(5, 7, 15) + sage: v = D([1,2,3,4,5]); v + (1 + O(7^5), 2 + O(7^4), 3 + O(7^3), 4 + O(7^2), 5 + O(7)) + sage: v.scale(2) + (2 + O(7^5), 4 + O(7^4), 6 + O(7^3), 1 + 7 + O(7^2), 3 + O(7)) + """ + if isinstance(self, Dist_long) and isinstance(left, (Integer, pAdicCappedRelativeElement, pAdicCappedAbsoluteElement, pAdicFixedModElement)): + return self._lmul_(left) + R = left.parent() + base = self.parent().base_ring() + if base is R: + return self._lmul_(left) + elif base.has_coerce_map_from(R): + return self._lmul_(base(left)) + else: + from sage.categories.pushout import pushout + new_base = pushout(base, R) + V = self.parent().change_ring(new_base) + scalar = new_base(left) + return V([scalar * new_base(self.moment(i)) for i in range(self.precision_absolute())]) + + def is_zero(self, p=None, M=None): + r""" + Returns True if the `i`th moment is zero for all `i` (case M is None) + or zero modulo p^(M-i) for all `i` (M is not None). + + Note that some moments are not known to precision M, in which + case they are only checked to be equal to zero modulo the + precision to which they are defined. + + INPUT: + + - ``p`` -- prime + + - ``M`` -- precision + + OUTPUT: + + - True/False + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + sage: D = Distributions(5, 7, 15) + sage: v = D([1,2,3,4,5]); v + (1 + O(7^5), 2 + O(7^4), 3 + O(7^3), 4 + O(7^2), 5 + O(7)) + sage: v.is_zero() + False + sage: v = D(5*[0]) + sage: v.is_zero() + True + """ + n = self.precision_relative() + aprec = self.precision_absolute() + if M is None: + M = n + elif M > aprec: + return False + elif M < aprec: + n -= (aprec - M) + M -= self.ordp + if p is None: + p = self.parent().prime() + cdef bint usearg = True + if n == 0: + return True + else: + try: + z = self.moment(0).is_zero(M) + except TypeError: + z = self.moment(0).is_zero() + use_arg = False + if not z: return False + for a in xrange(1, n): + if usearg: + z = self._unscaled_moment(a).is_zero(M-a) + else: + z = self._unscaled_moment(a).is_zero() + if not z: return False + return True + + def find_scalar(self, _other, p, M = None, check=True): + r""" + Returns an ``alpha`` with ``other = self * alpha``, or raises a ValueError. + + It will also raise a ValueError if this distribution is zero. + + INPUT: + + - ``other`` -- another distribution + + - ``p`` -- an integral prime (only used if the parent is not a Symk) + + - ``M`` -- (default: None) an integer, the relative precision + to which the scalar must be determined + + - ``check`` -- (default: True) boolean, whether to validate + that ``other`` is actually a multiple of this element. + + OUTPUT: + + - A scalar ``alpha`` with ``other = self * alpha``. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + sage: D = Distributions(5, 7, 15) + sage: v = D([1,2,3,4,5]) + sage: w = D([3,6,9,12,15]) + sage: v.find_scalar(w,p=7) + 3 + O(7^5) + + sage: u = D([1,4,9,16,25]) + sage: v.find_scalar(u,p=7) + Traceback (most recent call last): + ... + ValueError: not a scalar multiple + + """ + cdef Dist other = _other + i = 0 + n = self.precision_relative() + other_pr = other.precision_relative() + if n == 0: + raise ValueError("self is zero") +## RP: This code doesn't seem right. For instance, if the eigenvalue has positive valuation +## then the relative precision will go down. +## if n != other.precision_relative(): +## raise ValueError("other should have the same number of moments") + verbose("n = %s"%n) + verbose("moment 0") + a = self._unscaled_moment(i) + verbose("a = %s"%(a)) + padic = isinstance(a.parent(), pAdicGeneric) + if self.parent().is_symk(): + while a == 0: + if other._unscaled_moment(i) != 0: + raise ValueError("not a scalar multiple") + i += 1 + verbose("moment %s"%i) + try: + a = self._unscaled_moment(i) + except IndexError: + raise ValueError("self is zero") + alpha = other._unscaled_moment(i) / a + if check: + i += 1 + while i < n: + verbose("comparing moment %s"%i) + if alpha * self._unscaled_moment(i) != other._unscaled_moment(i): + raise ValueError("not a scalar multiple") + i += 1 + else: + p = self.parent().prime() + v = a.valuation(p) + while v >= n - i: + i += 1 + verbose("p moment %s"%i) + try: + a = self._unscaled_moment(i) + except IndexError: + raise ValueError("self is zero") + v = a.valuation(p) + relprec = n - i - v +# verbose("p=%s, n-i=%s\nself.moment=%s, other.moment=%s"%(p, n-i, a, other._unscaled_moment(i)),level=2) +## RP: This code was crashing because other may have too few moments -- so I added this bound with other's relative precision + if padic: + if i < other_pr: + alpha = (other._unscaled_moment(i) / a).add_bigoh(n-i) + else: + alpha = (0*a).add_bigoh(other_pr-i) + else: + if i < other_pr: + alpha = (other._unscaled_moment(i) / a) % p**(n-i) + else: + alpha = 0 + verbose("alpha = %s"%(alpha)) +## RP: This code was crashing because other may have too few moments -- so I added this bound with other's relative precision + while i < other_pr-1: + i += 1 + verbose("comparing p moment %s"%i) + a = self._unscaled_moment(i) + if check: +# verbose("self.moment=%s, other.moment=%s"%(a, other._unscaled_moment(i))) + if (padic and other._unscaled_moment(i) != alpha * a) or \ + (not padic and other._unscaled_moment(i) % p**(n-i) != alpha * a % p**(n-i)): + raise ValueError("not a scalar multiple") + v = a.valuation(p) + if n - i - v > relprec: + verbose("Reseting alpha: relprec=%s, n-i=%s, v=%s"%(relprec, n-i, v)) + relprec = n - i - v + if padic: + alpha = (other._unscaled_moment(i) / a).add_bigoh(n-i) + else: + alpha = (other._unscaled_moment(i) / a) % p**(n-i) + verbose("alpha=%s"%(alpha)) + if relprec < M: + raise ValueError("result not determined to high enough precision") + alpha = alpha * self.parent().prime()**(other.ordp - self.ordp) + verbose("alpha=%s"%(alpha)) + try: + return self.parent().base_ring()(alpha) + except ValueError: + return alpha + + cpdef ModuleElement _rmul_(self, RingElement _left): + """ + Scalar multiplication. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + """ + return self._lmul_(_left) + + cdef int _cmp_c_impl(_left, Element _right) except -2: + r""" + Comparison. + + EXAMPLES: + + Equality of two :class:`Dist_long`:: + + sage: D = Distributions(0, 5, 10) + sage: D([1, 2]) == D([1]) + True + sage: D([1]) == D([1, 2]) + True + + Equality of two :class:`Dist_vector`:: + + # XXX FIXME + + Equality of a :class:`Dist_vector` and a :class:`Dist_long`:: + + # XXX FIXME + """ + cdef Dist left = _left + cdef Dist right = _right + left.normalize() + right.normalize() + cdef long rprec = min(left._relprec(), right._relprec()) + cdef long i + p = left.parent().prime() + if left.ordp > right.ordp: + shift = p ** (left.ordp - right.ordp) + for i in range(rprec): + c = cmp(shift * left._unscaled_moment(i), right._unscaled_moment(i)) + if c: return c + elif left.ordp < right.ordp: + shift = p ** (right.ordp - left.ordp) + for i in range(rprec): + c = cmp(left._unscaled_moment(i), shift * right._unscaled_moment(i)) + if c: return c + else: + for i in range(rprec): + c = cmp(left._unscaled_moment(i), right._unscaled_moment(i)) + if c: return c + return 0 + + def diagonal_valuation(self, p=None): + """ + Returns the largest `m` so that this distribution lies in `Fil^m`. + + INPUT: + + - ``p`` -- (default: None) a positive integral prime + + OUTPUT: + + - the largest integer `m` so that `p^m` divides the `0`-th + moment, `p^{m-1}` divides the first moment, etc. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + sage: D = Distributions(8, 7, 15) + sage: v = D([7^(5-i) for i in range(1,5)]) + sage: v + (O(7^4), O(7^3), O(7^2), O(7)) + sage: v.diagonal_valuation(7) + 4 + """ + if p is None: + p = self.parent()._p + n = self.precision_relative() + return self.ordp + min([n] + [a + self._unscaled_moment(a).valuation(p) for a in range(n)]) + + def valuation(self, p=None): + """ + Returns the minimum valuation of any moment. + + INPUT: + + - ``p`` -- (default: None) a positive integral prime + + OUTPUT: + + - + + .. WARNING:: + + Since only finitely many moments are computed, this valuation may + be larger than the actual valuation of this distribution. + Moreover, since distributions are normalized so that the top moment + has precision 1, this valuation may be smaller than the actual + valuation (for example, if the actual valuation is 2) + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + sage: D = Distributions(8, 7, 15) + sage: v = D([7^(5-i) for i in range(1,5)]) + sage: v + (O(7^4), O(7^3), O(7^2), O(7)) + sage: v.valuation(7) + 1 + """ + if p is None: + p = self.parent()._p + n = self.precision_relative() + if self.parent().is_symk(): + return self.ordp + min([self._unscaled_moment(a).valuation(p) for a in range(n)]) + else: + return self.ordp + min([n] + [self._unscaled_moment(a).valuation(p) for a in range(n) if not self._unscaled_moment(a).is_zero()]) + + + def specialize(self, new_base_ring=None): + """ + Returns the image of this overconvergent distribution under + the canonical projection from distributions of weight k to + Sym^k. + + INPUT: + + - ``new_base_ring`` -- (default: None) a ring giving the + desired base ring of the result. + + OUTPUT: + + - An element of Sym^k(K), where K is the specified base ring. + + EXAMPLES:: + + sage: D = Distributions(4, 13) + sage: d = D([0,2,4,6,8,10,12]) + sage: d.specialize() + (O(13^7), 2 + O(13^6), 4 + O(13^5), 6 + O(13^4), 8 + O(13^3)) + + """ + self.normalize() + k=self.parent()._k + if k < 0: + raise ValueError("negative weight") + if self.precision_absolute() < k+1: + raise ValueError("not enough moments") + V = self.parent().specialize(new_base_ring) + new_base_ring = V.base_ring() + if self.precision_relative() == 0: + return V.zero_element() + else: + return V([new_base_ring.coerce(self.moment(j)) for j in range(k+1)]) + + def lift(self, p=None, M=None, new_base_ring=None): + r""" + Lifts a distribution or element of Sym^k to an overconvergent distribution. + + INPUT: + + - ``p`` -- (default: None) a positive integral prime. If None + then p must be available in the parent. + + - ``M`` -- (default: None) a positive integer giving the + desired number of moments. If None, returns a distribution having one + more moment than this one. + + - ``new_base_ring`` -- (default: None) a ring giving the desired base + ring of the result. If None, a base ring is chosen automatically. + + OUTPUT: + + - An overconvergent distribution with `M` moments whose image + under the specialization map is this element. + + EXAMPLES:: + + sage: V = Symk(0) + sage: x = V(1/4) + sage: y = x.lift(17, 5) + sage: y + (13 + 12*17 + 12*17^2 + 12*17^3 + 12*17^4 + O(17^5), O(17^4), O(17^3), O(17^2), O(17)) + sage: y.specialize()._moments == x._moments + True + """ + V = self.parent().lift(p, M, new_base_ring) + k = V._k + p = V.prime() + M = V.precision_cap() + R = V.base_ring() + moments = [R.coerce(self.moment(j)) for j in range(k+1)] + zero = R(0) + moments.extend([zero] * (M - k - 1)) + mu = V(moments) + #val = mu.valuation() + #if val < 0: + # # This seems unnatural + # print "scaling by %s^%s to keep things integral"%(p, -val) + # mu *= p**(-val) + return mu + + def _is_malformed(self): + r""" + Check that the precision of self is sensible. + + EXAMPLE:: + + sage: D = sage.modular.pollack_stevens.distributions.Symk(2, base=Qp(5)) + sage: v = D([1, 2, 3]) + sage: v._is_malformed() + False + sage: v = D([1 + O(5), 2, 3]) + sage: v._is_malformed() + True + """ + n = self.precision_absolute() + for i in range(n): + if self.moment(i).precision_absolute() < n - i: + return True + return False + + def act_right(self,gamma): + r""" + The image of this element under the right action by a + `2 \times 2` matrix. + + INPUT: + + - ``gamma`` -- the matrix by which to act + + OUTPUT: + + - ``self | gamma`` + + .. NOTE:: + + You may also just use multiplication ``self * gamma``. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + """ + return self.parent()._act(self, gamma) + +cdef class Dist_vector(Dist): + r""" + A distribution is stored as a vector whose `j`-th entry is the `j`-th moment of the distribution. + + The `j`-th entry is stored modulo `p^(N-j)` where `N` is the total number of moments. + (This is the accuracy that is maintained after acting by `\Gamma_0(p)`.) + + INPUTS: + + - ``moments`` -- the list of moments. If ``check == False`` it + must be a vector in the appropriate approximation module. + + - ``parent`` -- a :class:`distributions.Distributions_class` or + :class:`distributions.Symk_class` instance + + - ``ordp`` -- an integer. This MUST be zero in the case of Symk + of an exact ring. + + - ``check`` -- (default: True) boolean, whether to validate input + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + """ + def __init__(self, moments, parent, ordp=0, check=True): + """ + Initialization. + + TESTS:: + + sage: from sage.modular.pollack_stevens.distributions import Symk + sage: Symk(4)(0) + (0, 0, 0, 0, 0) + + """ + Dist.__init__(self, parent) + if check: + # case 1: input is a distribution already + if PY_TYPE_CHECK(moments, Dist): + moments = moments._moments.change_ring(parent.base_ring()) + # case 2: input is a vector, or something with a len + elif hasattr(moments, '__len__'): + M = len(moments) + moments = parent.approx_module(M)(moments) + # case 3: input is zero + elif moments == 0: + moments = parent.approx_module(parent.precision_cap())(moments) + # case 4: everything else + else: + moments = parent.approx_module(1)([moments]) + # TODO: This is not quite right if the input is an inexact zero. + if ordp != 0 and parent.prime() == 0: + raise ValueError("can not specify a valuation shift for an exact ring") + + ## RP: if the input has negative valuations everything was crashing so I added + ## this code, but I don't feel good about it. DOESN'T WORK!!!! +# if self.parent().prime() != 0: +# p = self.parent().prime() +# ordp = min([m.valuation(p) for m in moments]) +# moments = [p**(-ordp) * moments[a] for a in range(len(moments))] + + self._moments = moments + self.ordp = ordp + + def __reduce__(self): + r""" + Used for pickling. + + EXAMPLE:: + + sage: D = sage.modular.pollack_stevens.distributions.Symk(2) + sage: x = D([2,3,4]) + sage: x.__reduce__() + (, ((2, 3, 4), Sym^2 Q^2, False)) + """ + return (self.__class__,(self._moments,self.parent(),False)) + + cdef Dist_vector _new_c(self): + r""" + Creates an empty distribution. + + Note that you MUST fill in the ordp attribute on the resulting distribution. + + OUTPUT: + + - A distribution with no moments. The moments are then filled + in by the calling function. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + """ + cdef Dist_vector ans = PY_NEW(Dist_vector) + ans._parent = self._parent + return ans + + def _repr_(self): + r""" + String representation. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + """ + r""" + Displays the moments of the distribution + """ + self.normalize() + valstr = "" + if self.ordp == 1: + valstr = "%s * "%(self.parent().prime()) + elif self.ordp != 0: + valstr = "%s^%s * "%(self.parent().prime(), self.ordp) + if len(self._moments) == 1: + return valstr + repr(self._moments[0]) + else: + return valstr + repr(self._moments) + + def _rational_(self): + """ + Convert to a rational number. + + EXAMPLES:: + + sage: D = Symk(0); d = D(4/3); d + 4/3 + sage: QQ(d) + 4/3 + + We get a TypeError if there is more than 1 moment:: + + sage: D = Symk(1); d = D([1,2]); d + (1, 2) + sage: QQ(d) + Traceback (most recent call last): + ... + TypeError: k must be 0 + """ + if len(self._moments) == 1: + return QQ(self.moment(0)) + raise TypeError, "k must be 0" + + cdef long _relprec(self): + return len(self._moments) + + cdef _unscaled_moment(self, long n): + r""" + Returns the `n`-th moment, unscaled by the overall power of p stored in self.ordp. + """ + return self._moments[n] + + + cdef Dist_vector _addsub(self, Dist_vector right, bint negate): + r""" + Common code for the sum and the difference of two distributions + """ + cdef Dist_vector ans = self._new_c() + cdef long aprec = min(self.ordp + len(self._moments), right.ordp + len(right._moments)) + ans.ordp = min(self.ordp, right.ordp) + cdef long rprec = aprec - ans.ordp + # In the case of symk, rprec will always be k + V = ans.parent().approx_module(rprec) + R = V.base_ring() + smoments = self._moments + rmoments = right._moments + # we truncate if the moments are too long; extend by zero if too short + if smoments.parent() is not V: + #vv = smoments.list(copy=False) + #print len(vv), len(vv[:rprec]), rprec + #xx = [R(0)] * (rprec - len(smoments)) if rprec > len(smoments) else [] + #print len(xx) + #ww = vv[:rprec] + xx + #print len(ww) + #smoments = V(ww) + smoments = V(smoments.list(copy=False)[:rprec] + ([R(0)] * (rprec - len(smoments)) if rprec > len(smoments) else [])) + if rmoments.parent() is not V: + #vv = rmoments.list(copy=False) + #xx = [R(0)] * (rprec - len(rmoments)) if rprec > len(rmoments) else [] + #ww = vv[:rprec] + xx + #rmoments = V(ww) + rmoments = V(rmoments.list(copy=False)[:rprec] + ([R(0)] * (rprec - len(rmoments)) if rprec > len(rmoments) else [])) + # We multiply by the relative power of p + if self.ordp > right.ordp: + smoments *= self.parent().prime()**(self.ordp - right.ordp) + elif self.ordp < right.ordp: + rmoments *= self.parent().prime()**(right.ordp - self.ordp) + if negate: + rmoments = -rmoments + ans._moments = smoments + rmoments + return ans + + cpdef ModuleElement _add_(self, ModuleElement _right): + r""" + Sum of two distributions. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + return self._addsub(_right, False) + + cpdef ModuleElement _sub_(self, ModuleElement _right): + r""" + Difference of two distributions. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + return self._addsub(_right, True) + + cpdef ModuleElement _lmul_(self, RingElement right): + r""" + Scalar product of a distribution with a ring element that coerces into the base ring. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + cdef Dist_vector ans = self._new_c() + p = self.parent().prime() + if p == 0: + ans._moments = self._moments * right + ans.ordp = self.ordp + elif right.valuation(p) == Infinity: + ans._moments = self.parent().approx_module(0)([]) + ans.ordp += self.precision_relative() +## RP: I don't understand this is_exact_zero command +## This changes makes the function work when scaling by 0 -- it might +## cause other problems... +# elif right.is_zero(): +# ans._moments = self.parent().approx_module(0)([]) +# if right.is_exact_zero(): +# ans.ordp = maxordp +# else: +# ans.ordp = self.ordp + right.valuation(p) + else: + #print right, right.parent() + try: + v, u = right.val_unit(p) + except TypeError: # bug in p-adics: they should accept p here + v, u = right.val_unit() + ans._moments = self._moments * u + ans.ordp = self.ordp + v + # if the relative precision of u is less than that of self, ans may not be normalized. + return ans + + def precision_relative(self): + r""" + The relative precision of this distribution. + + The precision is just the number of moments stored, which is + also k+1 in the case of Sym^k(R). For overconvergent + distributions, the precision is the integer `m` so that the + sequence of moments is known modulo `Fil^m`. + + OUTPUT: + + - An integer giving the number of moments. + """ + return Integer(len(self._moments)) + + def precision_absolute(self): + r""" + Returns the absolute precision of this distribution. + + The absolute precision is the sum of the relative precision + (number of moments) and the valuation. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + return Integer(len(self._moments) + self.ordp) + + cpdef normalize(self): + r""" + Normalize by reducing modulo `Fil^N`, where `N` is the number of moments. + + If the parent is Symk, then normalize has no effect. If the + parent is a space of distributions, then normalize reduces the + `i`-th moment modulo `p^{N-i}`. + + OUTPUT: + + - this distribtion, after normalizing. + + .. WARNING:: + + This function modifies the distribution in place as well as returning it. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + return self # DEBUG + if not self.parent().is_symk(): # non-classical + V = self._moments.parent() + R = V.base_ring() + n = self.precision_relative() + p = self.parent()._p + if isinstance(R, pAdicGeneric): + self._moments = V([self._moments[i].add_bigoh(n-i) for i in range(n)]) + else: + self._moments = V([self._moments[i]%(p**(n-i)) for i in range(n)]) + shift = self.valuation() - self.ordp + if shift != 0: + V = self.parent().approx_module(n-shift) + self.ordp += shift + self._moments = V([self._moments[i] // p**shift for i in range(n-shift)]) + return self + + def reduce_precision(self, M): + r""" + Only hold on to `M` moments. + + INPUT: + + - ``M`` -- a positive integer less than the precision of this + distribution. + + OUTPUT: + + - a new distribution with `M` moments equal to the first `M` + moments of this distribution. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + assert M<=self.precision_relative(),"not enough moments" + + cdef Dist_vector ans = self._new_c() + ans._moments = self._moments[:M] + ans.ordp = self.ordp + return ans + + def solve_diff_eqn(self): + r""" + Solves the difference equation. + + See Theorem 4.5 and Lemma 4.4 of [PS]. + + OUTPUT: + + - a distribution v so that self = v | Delta, where Delta = [1, 1; 0, 1] - 1. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + # assert self._moments[0][0]==0, "not total measure zero" + # print "result accurate modulo p^",self.moment(0).valuation(self.p) + #v=[0 for j in range(0,i)]+[binomial(j,i)*bernoulli(j-i) for j in range(i,M)] + M = self.precision_relative() + R = self.parent().base_ring() + K = R.fraction_field() + V = self._moments.parent() + v = [K(0) for i in range(M)] + bern = [bernoulli(i) for i in range(0,M,2)] + minhalf = ~K(-2) + for m in range(1,M): + scalar = K(self.moment(m) / m) + # bernoulli(1) = -1/2; the only nonzero odd bernoulli number + v[m] += m * minhalf * scalar + for j in range(m-1,M,2): + v[j] += binomial(j,m-1) * bern[(j-m+1)//2] * scalar + p = self.parent().prime() + cdef Dist_vector ans + if p == 0: + if R.is_field(): + ans = self._new_c() + ans.ordp = 0 + ans._moments = V(v) + else: + newparent = self.parent().change_ring(K) + ans = newparent(v) + else: + ans = self._new_c() + ans.ordp = min(a.valuation(p) for a in v) + if ans.ordp < 0: + scalar = K(p) ** (-ans.ordp) + ans._moments = V([R(a * scalar) for a in v]) + elif ans.ordp > 0: + scalar = K(p) ** ans.ordp + ans._moments = V([R(a // scalar) for a in v]) + else: + ans._moments = V([R(a) for a in v]) + v = ans._moments + N = len(ans._moments) + prec_loss = max([N-j-v[j].precision_absolute() for j in range(N)]) + # print "precision loss = ",prec_loss + if prec_loss > 0: + ans._moments = ans._moments[:(N-prec_loss)] + return ans + + #def lift(self): + # r""" + # Increases the number of moments by `1`. + # """ + # n = len(self._moments) + # if n >= self.parent()._prec_cap: + # raise ValueError("Cannot lift above precision cap") + # cdef Dist_vector ans = self._new_c() + # R = self.parent().base_ring() + # ## Need to increse the precision of individual moments if they're p-adic + # ans._moments = self.parent().approx_module(n+1)(list(self._moments) + [R(0)]) + # ans.ordp = self.ordp + # return ans + +cdef class Dist_long(Dist): + r""" + A class for distributions implemented using a C array of longs. + + INPUT: + + - ``moments`` -- the list of moments. If ``check == False`` it + must be a vector in the appropriate approximation module. + + - ``parent`` -- a :class:`distributions.Distributions_class` or + :class:`distributions.Symk_class` instance + + - ``check`` -- (default: True) boolean, whether to validate input + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + def __init__(self, moments, parent, ordp = 0, check = True): + """ + Initialization. + + TESTS:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + Dist.__init__(self, parent) + p = parent._p + cdef int i + if check: + + # case 1: input is a distribution already + if PY_TYPE_CHECK(moments, Dist): + M = len(moments) + moments = [ZZ(moments.moment(i)) for i in range(M)] + # case 2: input is a vector, or something with a len + elif hasattr(moments, '__len__'): + M = len(moments) + moments = [ZZ(a) for a in parent.approx_module(M)(moments)] + # case 3: input is zero + elif moments == 0: + M = parent.precision_cap() + moments = [ZZ(0)] * M + else: + M = 1 + moments = [ZZ(moments)] + if M > 100 or 7*p**M > ZZ(2)**(4*sizeof(long) - 1): # 6 is so that we don't overflow on gathers + raise ValueError("moments too long") + else: + M = len(moments) + + for i in range(len(moments)): + self._moments[i] = moments[i] + self.relprec = M + self.prime_pow = parent.prime_pow + #gather = 2**(4*sizeof(long)-1) // p**len(moments) + #if gather >= len(moments): + # gather = 0 + #self._gather = gather + + cdef Dist_long _new_c(self): + r""" + + + OUTPUT: + + - + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + cdef Dist_long ans = PY_NEW(Dist_long) + ans._parent = self._parent + ans.prime_pow = self.prime_pow + return ans + + def _repr_(self): + r""" + + + OUTPUT: + + - + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + self.normalize() + valstr = "" + if self.ordp == 1: + valstr = "%s * "%(self.prime_pow.prime) + elif self.ordp != 0: + valstr = "%s^%s * "%(self.prime_pow.prime, self.ordp) + if self.relprec == 1: + return valstr + repr(self._moments[0]) + else: + return valstr + "(" + ", ".join([repr(self._moments[i]) for i in range(self.relprec)]) + ")" + + cdef int quasi_normalize(self) except -1: + r""" + + + OUTPUT: + + - + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + cdef int i + for i in range(self.relprec): + if self._moments[i] > overflow: + self._moments[i] = self._moments[i] % self.prime_pow.small_powers[self.relprec-i] + elif self._moments[i] < underflow: + self._moments[i] = self._moments[i] % self.prime_pow.small_powers[self.relprec-i] + self._moments[i] += self.prime_pow.small_powers[self.relprec-i] + + cpdef normalize(self): + r""" + + + OUTPUT: + + - + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + cdef int i + for i in range(self.relprec): + if self._moments[i] < 0: + self._moments[i] = self._moments[i] % self.prime_pow.small_powers[self.relprec-i] + self._moments[i] += self.prime_pow.small_powers[self.relprec-i] + elif self._moments[i] >= self.prime_pow.small_powers[self.relprec-i]: + self._moments[i] = self._moments[i] % self.prime_pow.small_powers[self.relprec-i] + return self + + cdef long _relprec(self): + return self.relprec + + cdef _unscaled_moment(self, long _n): + r""" + + + INPUT: + + - ``_n`` -- an integer or slice giving an index into the + moments. + + OUTPUT: + + - + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + if isinstance(_n, slice): + a, b, c = _n.indices(self.relprec) + return [self.moment(i) for i in range(a, b, c)] + cdef int n = _n + if n < 0: + n += self.relprec + if n < 0 or n >= self.relprec: + raise IndexError("list index out of range") + return self._moments[n] + + cdef Dist_long _addsub(self, Dist_long right, bint negate): + r""" + Common code for the sum and the difference of two distributions + """ + cdef Dist_long ans = self._new_c() + cdef long aprec = min(self.ordp + self.relprec, right.ordp + right.relprec) + ans.ordp = min(self.ordp, right.ordp) + ans.relprec = aprec - ans.ordp + # In the case of symk, rprec will always be k + cdef int i, n + cdef long diff, cutoff + # The following COULD overflow, but it would require 2^32 + # additions (on a 64-bit machine), since we restrict p^k to be + # less than 2^31/7. + if self.ordp == right.ordp: + n = min(self.relprec, right.relprec) + for i in range(n): + ans._moments[i] = self._moments[i] - right._moments[i] if negate else self._moments[i] + right._moments[i] + if self.relprec < ans.relprec: + for i in range(n, ans.relprec): + ans._moments[i] = -right._moments[i] if negate else right._moments[i] + elif ans.relprec < self.relprec: + for i in range(n, ans.relprec): + ans._moments[i] = self._moments[i] + elif self.ordp < right.ordp: + diff = right.ordp - self.ordp + n = min(right.relprec, ans.relprec - diff) + for i in range(n): + ans._moments[i] = self.prime_pow.small_powers[diff] * (right._moments[i] % self.prime_pow.small_powers[ans.relprec - diff - i]) + ans._moments[i] = self._moments[i] - ans._moments[i] if negate else self._moments[i] + ans._moments[i] + if n < ans.relprec: + for i in range(n, ans.relprec): + ans._moments[i] = self._moments[i] + else: # self.ordp > right.ordp + diff = self.ordp - right.ordp + n = min(self.relprec, ans.relprec - diff) + for i in range(n): + ans._moments[i] = self.prime_pow.small_powers[diff] * (self._moments[i] % self.prime_pow.small_powers[ans.relprec - diff - i]) + ans._moments[i] += -right._moments[i] if negate else right._moments[i] + if n < ans.relprec: + for i in range(n, ans.relprec): + ans._moments[i] = -right._moments[i] if negate else right._moments[i] + return ans + + cpdef ModuleElement _add_(self, ModuleElement right): + r""" + + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + return self._addsub( right, False) + + cpdef ModuleElement _sub_(self, ModuleElement right): + r""" + + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + return self._addsub( right, True) + + cpdef ModuleElement _lmul_(self, RingElement _right): + r""" + + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + cdef Dist_long ans = self._new_c() + ans.relprec = self.relprec + self.quasi_normalize() + cdef long scalar, absprec, ordp + cdef Integer iright, unit, ppow, p = self.prime_pow.prime + cdef Rational qright, qunit + cdef pAdicCappedAbsoluteElement pcaright + cdef pAdicCappedRelativeElement pcrright + cdef pAdicFixedModElement pfmright + if PY_TYPE_CHECK(_right, Integer): + iright = _right + if mpz_sgn(iright.value) == 0: + ans.ordp = maxordp + ans.relprec = 0 + return ans + unit = PY_NEW(Integer) + ordp = mpz_remove(unit.value, iright.value, p.value) + if mpz_fits_slong_p(unit.value): + scalar = mpz_get_si(iright.value) % self.prime_pow.small_powers[self.relprec] + else: + scalar = mpz_fdiv_ui(iright.value, self.prime_pow.small_powers[self.relprec]) + elif PY_TYPE_CHECK(_right, Rational): + qright = _right + if mpq_sgn(qright.value) == 0: + ans.ordp = maxordp + ans.relprec = 0 + return ans + qunit = PY_NEW(Rational) + ordp = mpz_remove(mpq_numref(qunit.value), mpq_numref(qright.value), p.value) + if ordp == 0: + ordp = -mpz_remove(mpq_denref(qunit.value), mpq_denref(qright.value), p.value) + else: + mpz_set(mpq_denref(qunit.value), mpq_denref(qright.value)) + ppow = PY_NEW(Integer) + mpz_set_ui(ppow.value, self.prime_pow.small_powers[self.relprec]) + # We reuse the pointers inside qunit, since we're going to discard it. + mpz_invert(mpq_denref(qunit.value), mpq_denref(qunit.value), ppow.value) + mpz_mul(mpq_numref(qunit.value), mpq_numref(qunit.value), mpq_denref(qunit.value)) + scalar = mpz_fdiv_ui(mpq_numref(qunit.value), self.prime_pow.small_powers[self.relprec]) + # qunit should not be used now (it's unnormalized) + elif PY_TYPE_CHECK(_right, pAdicCappedAbsoluteElement): + pcaright = _right + unit = PY_NEW(Integer) + ordp = mpz_remove(unit.value, pcaright.value, p.value) + if pcaright.absprec - ordp <= self.relprec: + ans.relprec = pcaright.absprec - ordp + scalar = mpz_get_si(unit.value) + else: + scalar = mpz_fdiv_ui(unit.value, self.prime_pow.small_powers[self.relprec]) + elif PY_TYPE_CHECK(_right, pAdicCappedRelativeElement): + pcrright = _right + ordp = pcrright.ordp + if pcrright.relprec <= self.relprec: + ans.relprec = pcrright.relprec + scalar = mpz_get_si(pcrright.unit) + else: + scalar = mpz_fdiv_ui(pcrright.unit, self.prime_pow.small_powers[self.relprec]) + elif PY_TYPE_CHECK(_right, pAdicFixedModElement): + pfmright = _right + scalar = mpz_get_si(pfmright.value) + ordp = 0 + cdef int i + for i in range(self.relprec): + ans._moments[i] = self._moments[i] * scalar + ans.ordp = self.ordp + ordp + ans.quasi_normalize() + return ans + + def precision_relative(self): + return Integer(self.relprec) + + def precision_absolute(self): + r""" + + + OUTPUT: + + - + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + return Integer(self.relprec + self.ordp) + + def reduce_precision(self, M): + r""" + + + INPUT: + + - ``M`` -- a positive integer less than the precision of this + distribution. + + OUTPUT: + + - + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + if M > self.relprec: raise ValueError("not enough moments") + if M < 0: raise ValueError("precision must be non-negative") + cdef Dist_long ans = self._new_c() + ans.relprec = M + cdef int i + for i in range(ans.relprec): + ans._moments[i] = self._moments[i] + ans.ordp = self.ordp + return ans + + def solve_diff_eqn(self): + r""" + + + OUTPUT: + + - + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + raise NotImplementedError + + #def lift(self): + # if self.relprec >= self.parent()._prec_cap: + # raise ValueError("Cannot lift above precision cap") + # cdef Dist_long ans = self._new_c() + # ans.relprec = self.relprec + 1 + # cdef int i + # for i in range(self.relprec): + # ans._moments[i] = self._moments[i] + # ans._moments[self.relprec] = 0 + # ans.ordp = self.ordp + # return ans + + def __reduce__(self): + r""" + Used in pickling. + + EXAMPLE:: + + sage: D = Distributions(0, 5, 10) + sage: D([1,2,3,4]).__reduce__() + (, ([1, 2, 3, 4], Space of 5-adic distributions with k=0 action and precision cap 10, 0, False)) + """ + return (self.__class__,([self._moments[i] for i in xrange(self.relprec)], self.parent(), self.ordp, False)) + +cdef class WeightKAction(Action): + r""" + + INPUT: + + - ``Dk`` -- a space of distributions + - ``character`` -- data specifying a Dirichlet character to apply to the + top right corner, and a power of the determinant by which to scale. See + the documentation of + :class:`sage.modular.pollack_stevens.distributions.Distributions_factory` + for more details. + - ``adjuster`` -- a callable object that turns matrices into 4-tuples. + - ``on_left`` -- whether this action should be on the left. + - ``dettwist`` -- a power of the determinant to twist by + - ``padic`` -- if True, define an action of p-adic matrices (not just integer ones) + + OUTPUT: + + - + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + def __init__(self, Dk, character, adjuster, on_left, dettwist, padic=False): + r""" + Initialization. + + TESTS:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + self._k = Dk._k +# if self._k < 0: raise ValueError("k must not be negative") + self._adjuster = adjuster + self._character = character + self._dettwist = dettwist + self._p = Dk._p + self._symk = Dk.is_symk() + self._actmat = {} + self._maxprecs = {} + if character is None: + self._Np = ZZ(1) # all of M2Z acts + else: + self._Np = character.modulus() + if not self._symk: + self._Np = self._Np.lcm(self._p) + + if padic: + self._Sigma0 = Sigma0(self._Np, base_ring=Dk.base_ring(), adjuster=self._adjuster) + else: + self._Sigma0 = Sigma0(self._Np, base_ring=ZZ, adjuster=self._adjuster) + Action.__init__(self, self._Sigma0, Dk, on_left, operator.mul) + + def clear_cache(self): + r""" + + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + self._actmat = {} + self._maxprecs = {} + + cpdef acting_matrix(self, g, M): + r""" + + + INPUT: + + - ``g`` -- an instance of + :class:`sage.matrices.matrix_integer_2x2.Matrix_integer_2x2` + or of :class:`sage.matrix.matrix_generic_dense.Matrix_generic_dense` + + - ``M`` -- a positive integer giving the precision at which + ``g`` should act. + + OUTPUT: + + - An `M \times M` matrix so that the action of `g` on a + distribution with `M` moments is given by a vector-matrix + multiplication. + + .. NOTE:: + + This function caches its results. To clear the cache use + :meth:`clear_cache`. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + g = g.matrix() + if not self._maxprecs.has_key(g): + A = self._compute_acting_matrix(g, M) + self._actmat[g] = {M:A} + self._maxprecs[g] = M + return A + else: + mats = self._actmat[g] + if mats.has_key(M): + return mats[M] + maxprec = self._maxprecs[g] + if M < maxprec: + A = mats[maxprec][:M,:M] # submatrix; might want to reduce precisions + mats[M] = A + return A + if M < 30: # This should not be hard-coded + maxprec = max([M,2*maxprec]) # This may be wasting memory + else: + maxprec = M + self._maxprecs[g] = maxprec + mats[maxprec] = self._compute_acting_matrix(g, maxprec) # could lift from current maxprec + if M == maxprec: + return mats[maxprec] + A = mats[maxprec][:M,:M] # submatrix; might want to reduce precisions + mats[M] = A + return A + +# cpdef _check_mat(self, a, b, c, d): +# r""" +# +# +# INPUT: +# +# - ``a``, ``b``, ``c``, ``d`` -- integers, playing the role of +# the corresponding entries of the `2 \times 2` matrix that is +# acting. +# +# EXAMPLES:: +# +# sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk +# """ +# if a*d == b*c: +# raise ValueError("zero determinant") +# if not self._symk: +# if self._p.divides(a): +# raise ValueError("p divides a") +# if not self._Np.divides(c): +# raise ValueError("Np does not divide c") + + cpdef _compute_acting_matrix(self, g, M): + r""" + + + INPUT: + + - ``g`` -- an instance of + :class:`sage.matrices.matrix_integer_2x2.Matrix_integer_2x2` + + - ``M`` -- a positive integer giving the precision at which + ``g`` should act. + + OUTPUT: + + - + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + """ + 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] + """ + raise NotImplementedError + +cdef class WeightKAction_vector(WeightKAction): + cpdef _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) + 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) + try: + B = B.apply_map(operator.methodcaller('lift')) + except AttributeError: pass + return B + + cpdef _call_(self, _v, g): + r""" + + + INPUT: + + - ``_v`` -- a :class:`Dist_vector` instance, the distribution + on which to act. + + - ``g`` -- a + :class:`sage.matrix.matrix_integer_2x2.Matrix_integer_2x2` + instance, the `2 \times 2` matrix that is acting. + + OUTPUT: + + - + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + # if g is a matrix it needs to be immutable + # hashing on arithmetic_subgroup_elements is by str + if self.is_left(): + _v,g = g,_v + if g == 1: + return _v + cdef Dist_vector v = _v + cdef Dist_vector ans = v._new_c() + #try: + # g.set_immutable() + #except AttributeError: + # pass + try: + v_moments = v._moments.apply_map(operator.methodcaller('lift')) + except AttributeError: + v_moments = v._moments + ans._moments = v_moments * self.acting_matrix(g, len(v_moments)) + ans.ordp = v.ordp + return ans + +cdef inline long mymod(long a, unsigned long pM): + """ + Returns the remainder ``a % pM``. + + INPUT: + + - ``a`` -- a long + + - ``pM`` -- an unsigned long + + OUPUT: + + - ``a % pM`` as a positive integer. + """ + a = a % pM + if a < 0: + a += pM + return a + +cdef class SimpleMat(SageObject): + r""" + A simple class emulating a square matrix that holds its values as + a C array of longs. + + INPUT: + + - ``M`` -- a positive integer, the dimension of the matrix + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + def __cinit__(self, unsigned long M): + r""" + Memory initialization. + + TESTS:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + self._inited = False + self.M = M + self._mat = sage_malloc(M*M*sizeof(long)) + if self._mat == NULL: + raise MemoryError + self._inited = True + + def __getitem__(self, i): + r""" + + + INPUT: + + - ``i`` -- a tuple containing two slices, each from `0` to `M'` for some `M' < M` + + OUTPUT: + + - A new SimpleMat of size `M'` with the top left `M' \times + M'` block of values copied over. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + cdef Py_ssize_t r, c, Mnew, Morig = self.M + cdef SimpleMat ans + if PyTuple_Check(i) and PyTuple_Size(i) == 2: + a, b = i + if PySlice_Check(a) and PySlice_Check(b): + r0, r1, rs = a.indices(Morig) + c0, c1, cs = b.indices(Morig) + if r0 != 0 or c0 != 0 or rs != 1 or cs != 1: raise NotImplementedError + Mr = r1 + Mc = c1 + if Mr != Mc: raise ValueError("result not square") + Mnew = Mr + if Mnew > Morig: raise IndexError("index out of range") + ans = SimpleMat(Mnew) + for r in range(Mnew): + for c in range(Mnew): + ans._mat[Mnew*c + r] = self._mat[Morig*c + r] + return ans + raise NotImplementedError + + def __dealloc__(self): + r""" + Deallocation. + + TESTS:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + sage_free(self._mat) + +cdef class WeightKAction_long(WeightKAction): + cpdef _compute_acting_matrix(self, g, _M): + r""" + + + INPUT: + + - ``g`` -- an instance of + :class:`sage.matrices.matrix_integer_2x2.Matrix_integer_2x2` + + - ``_M`` -- a positive integer giving the precision at which + ``g`` should act. + + OUTPUT: + + - A :class:`SimpleMat` that gives the action of ``g`` at + precision ``_M`` in the sense that the moments of the result + are obtained from the moments of the input by a + vector-matrix multiplication. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + _a, _b, _c, _d = self._adjuster(g) + #if self._character is not None: raise NotImplementedError + # self._check_mat(_a, _b, _c, _d) + cdef long k = self._k + cdef Py_ssize_t row, col, M = _M + cdef nmod_poly_t t, scale, xM, bdy + cdef mp_limb_t pM = self._p**M #unsigned long + cdef long a, b, c, d + a = mymod(ZZ(_a), pM) + b = mymod(ZZ(_b), pM) + c = mymod(ZZ(_c), pM) + d = mymod(ZZ(_d), pM) + cdef mp_limb_t pMinv = pM #n_preinvert_limb(pM) DEBUG!!! + nmod_poly_init2_preinv(t, pM, pMinv, M) + nmod_poly_init2_preinv(scale, pM, pMinv, M) + nmod_poly_init2_preinv(xM, pM, pMinv, M+1) + nmod_poly_init2_preinv(bdy, pM, pMinv, 2) + nmod_poly_set_coeff_ui(xM, M, 1) + nmod_poly_set_coeff_ui(t, 0, a) + nmod_poly_set_coeff_ui(t, 1, c) + nmod_poly_inv_series(scale, t, M) + nmod_poly_set_coeff_ui(bdy, 0, b) + nmod_poly_set_coeff_ui(bdy, 1, d) + nmod_poly_mullow(scale, scale, bdy, M) # scale = (b+dy)/(a+cy) + nmod_poly_pow_trunc(t, t, k, M) # t = (a+cy)^k + cdef SimpleMat B = SimpleMat(M) + for col in range(M): + for row in range(M): + B._mat[M*col + row] = nmod_poly_get_coeff_ui(t, row) + if col < M - 1: + nmod_poly_mullow(t, t, scale, M) + if self._character is not None: + B = B * self._character(_a,_b,_c,_d) + return B + + cpdef _call_(self, _v, g): + r""" + Application of the action. + + INPUT: + + - ``_v`` -- a :class:`Dist_long` instance, the distribution on + which to act. + + - ``g`` -- a + :class:`sage.matrix.matrix_integer_2x2.Matrix_integer_2x2` + instance, the `2 \times 2` matrix that is acting. + + OUTPUT: + + - The image of ``_v`` under the action of ``g``. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + """ + if self.is_left(): + _v,g = g,_v + + cdef Dist_long v = _v + cdef Dist_long ans = v._new_c() + ans.relprec = v.relprec + ans.ordp = v.ordp + cdef long pM = self._p**ans.relprec + cdef SimpleMat B = self.acting_matrix(g, ans.relprec) + cdef long row, col, entry = 0 + for col in range(ans.relprec): + ans._moments[col] = 0 + for row in range(ans.relprec): + try: + ans._moments[col] += mymod(B._mat[entry] * v._moments[row].apply_map(operator.methodcaller('lift')), pM) + except AttributeError: + ans._moments[col] += mymod(B._mat[entry] * v._moments[row], pM) + entry += 1 + return ans diff --git a/src/sage/modular/pollack_stevens/distributions.py b/src/sage/modular/pollack_stevens/distributions.py new file mode 100644 index 00000000000..a7b91ab01c5 --- /dev/null +++ b/src/sage/modular/pollack_stevens/distributions.py @@ -0,0 +1,766 @@ +""" +Spaces of Distributions + +""" +#***************************************************************************** +# Copyright (C) 2012 Robert Pollack +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.modules.module import Module +from sage.structure.parent import Parent +from sage.rings.padics.factory import ZpCA, QpCR +from sage.rings.padics.padic_generic import pAdicGeneric +from sage.rings.rational_field import QQ +from sage.rings.integer_ring import ZZ +from sage.misc.cachefunc import cached_method +from sage.categories.action import PrecomposedAction +from sage.categories.modules import Modules +from sage.structure.coerce_actions import LeftModuleAction, RightModuleAction +from sage.matrix.all import MatrixSpace +from sage.rings.fast_arith import prime_range +from sage.modular.pollack_stevens.dist import get_dist_classes, Dist_long +from sage.structure.factory import UniqueFactory +from sage.structure.unique_representation import UniqueRepresentation +import operator +import sage.rings.ring as ring + +from sigma0 import _default_adjuster #sage.modular.pollack_stevens. + +class Distributions_factory(UniqueFactory): + """ + Create a space of distributions. + + INPUT: + + - `k` -- nonnegative integer + - `p` -- prime number or None + - ``prec_cap`` -- positive integer or None + - ``base`` -- ring or None + - ``character`` -- a dirichlet character or None + - ``adjuster`` -- None or callable that turns 2x2 matrices into a 4-tuple + - ``act_on_left`` -- bool (default: False) + - ``dettwist`` -- integer or None (interpreted as 0) + + EXAMPLES:: + + sage: D = Distributions(3, 11, 20) + sage: D + Space of 11-adic distributions with k=3 action and precision cap 20 + sage: v = D([1,0,0,0,0]) + sage: v.act_right([2,1,0,1]) + (8 + O(11^5), 4 + O(11^4), 2 + O(11^3), 1 + O(11^2), 6 + O(11)) + sage: D = Distributions(3, 11, 20, dettwist=1) + sage: v = D([1,0,0,0,0]) + sage: v.act_right([2,1,0,1]) + (5 + 11 + O(11^5), 8 + O(11^4), 4 + O(11^3), 2 + O(11^2), 1 + O(11)) + """ + def create_key(self, k, p=None, prec_cap=None, base=None, character=None, adjuster=None, act_on_left=False, dettwist=None): + """ + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + sage: Distributions(20, 3, 10) # indirect doctest + Space of 3-adic distributions with k=20 action and precision cap 10 + sage: TestSuite(Distributions).run() + """ + k = ZZ(k) + + if p is None: + try: + p = base.prime() + except AttributeError: + raise ValueError("You must specify a prime") + else: + p = ZZ(p) + + if base is None: + if prec_cap is None: + base = ZpCA(p) + else: + base = ZpCA(p, prec_cap) + + if prec_cap is None: + try: + prec_cap = base.precision_cap() + except AttributeError: + raise ValueError("You must specify a base or precision cap") + + if adjuster is None: + adjuster = _default_adjuster() + + if dettwist is not None: + dettwist = ZZ(dettwist) + if dettwist == 0: + dettwist = None + + return (k, p, prec_cap, base, character, adjuster, act_on_left, dettwist) + + def create_object(self, version, key): + """ + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + sage: Distributions(0, 7, 5) # indirect doctest + Space of 7-adic distributions with k=0 action and precision cap 5 + """ + return Distributions_class(*key) + +class Symk_factory(UniqueFactory): + r""" + Create the space of polynomial distributions of degree k (stored as a sequence of k + 1 moments). + + INPUT: + + - ``k`` (integer): the degree (degree `k` corresponds to weight `k + 2` modular forms) + - ``base`` (ring, default None): the base ring (None is interpreted as `\QQ`) + - ``character`` (Dirichlet character or None, default None) the character + - ``adjuster`` (None or a callable that turns 2x2 matrices into a 4-tuple, default None) + - ``act_on_left`` (boolean, default False) whether to have the group acting + on the left rather than the right. + - ``dettwist`` (integer or None) -- power of determinant to twist by + + EXAMPLE:: + + sage: D = Symk(4) + sage: loads(dumps(D)) is D + True + sage: loads(dumps(D)) == D + True + sage: from sage.modular.pollack_stevens.distributions import Symk + sage: Symk(5) + Sym^5 Q^2 + sage: Symk(5, RR) + Sym^5 (Real Field with 53 bits of precision)^2 + sage: Symk(5, oo.parent()) # don't do this + Sym^5 (The Infinity Ring)^2 + sage: Symk(5, act_on_left = True) + Sym^5 Q^2 + + The ``dettwist`` attribute:: + + sage: V = Symk(6) + sage: v = V([1,0,0,0,0,0,0]) + sage: v.act_right([2,1,0,1]) + (64, 32, 16, 8, 4, 2, 1) + sage: V = Symk(6, dettwist=-1) + sage: v = V([1,0,0,0,0,0,0]) + sage: v.act_right([2,1,0,1]) + (32, 16, 8, 4, 2, 1, 1/2) + """ + def create_key(self, k, base=None, character=None, adjuster=None, act_on_left=False, dettwist=None): + r""" + Sanitize input. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.distributions import Symk + sage: Symk(6) # indirect doctest + Sym^6 Q^2 + + sage: V = Symk(6, Qp(7)) + sage: TestSuite(V).run() + """ + k = ZZ(k) + if adjuster is None: + adjuster = _default_adjuster() + prec_cap = k+1 + if base is None: + base = QQ + return (k, base, character, adjuster, act_on_left, dettwist) + + def create_object(self, version, key): + r""" + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.distributions import Symk + sage: Symk(6) # indirect doctest + Sym^6 Q^2 + """ + return Symk_class(*key) + +Distributions = Distributions_factory('Distributions') +Symk = Symk_factory('Symk') + +class Distributions_abstract(Module): + """ + Parent object for distributions. Not to be used directly, see derived + classes :class:`Symk_class` and :class:`Distributions_class`. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + sage: Distributions(2, 17, 100) + Space of 17-adic distributions with k=2 action and precision cap 100 + """ + def __init__(self, k, p=None, prec_cap=None, base=None, character=None, \ + adjuster=None, act_on_left=False, dettwist=None): + """ + INPUT: + + - `k` -- integer; k is the usual modular forms weight minus 2 + - `p` -- None or prime + - ``prec_cap`` -- None or positive integer + - ``base`` -- None or TODO + - ``character`` -- None or Dirichlet character + - ``adjuster`` -- None or TODO + - ``act_on_left`` -- bool (default: False) + - ``dettwist`` -- None or integer (twist by determinant). Ignored for Symk spaces + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + sage: D = Distributions(2, 3, 5); D + Space of 3-adic distributions with k=2 action and precision cap 5 + sage: type(D) + + + p must be a prime, but p=6 below, which is not prime:: + + sage: Distributions(k=0, p=6, prec_cap=10) + Traceback (most recent call last): + ... + ValueError: p must be prime + """ + if not isinstance(base, ring.Ring): + raise TypeError("base must be a ring") + from sage.rings.padics.pow_computer import PowComputer_long + # should eventually be the PowComputer on ZpCA once that uses longs. + Dist, WeightKAction = get_dist_classes(p, prec_cap, base, self.is_symk()) + self.Element = Dist + if Dist is Dist_long: + self.prime_pow = PowComputer_long(p, prec_cap, prec_cap, prec_cap, 0) + Parent.__init__(self, base, category=Modules(base)) + self._k = k + self._p = p + self._prec_cap = prec_cap + self._character = character + self._adjuster=adjuster + self._dettwist=dettwist + + if self.is_symk() or character is not None: + self._act = WeightKAction(self, character, adjuster, act_on_left, dettwist) + else: + self._act = WeightKAction(self, character, adjuster, act_on_left, dettwist, padic=True) + + self._populate_coercion_lists_(action_list=[self._act]) + + def _coerce_map_from_(self, other): + """ + Determine if self has a coerce map from other. + + EXAMPLES:: + + sage: V = Symk(4) + sage: W = V.base_extend(QQ[i]) + sage: W.has_coerce_map_from(V) # indirect doctest + True + + Test some coercions:: + + sage: v = V.an_element() + sage: w = W.an_element() + sage: v + w + (0, 2, 4, 6, 8) + sage: v == w + True + """ + if isinstance(other, Distributions_abstract) \ + and other._k == self._k \ + and self._character == other._character \ + and self.base_ring().has_coerce_map_from(other.base_ring()) \ + and (self.is_symk() or not other.is_symk()): + return True + else: + return False + + + def acting_matrix(self, g, M): + r""" + Return the matrix for the action of g on self, truncated to the first M moments. + + EXAMPLE:: + + sage: V = Symk(3) + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: V.acting_matrix(Sigma0(1)([3,4,0,1]), 4) + [27 36 48 64] + [ 0 9 24 48] + [ 0 0 3 12] + [ 0 0 0 1] + """ + # TODO: Add examples with a non-default action adjuster + return self._act.acting_matrix(g,M) + + def prime(self): + """ + Return prime `p` such that this is a space of `p`-adic distributions. + + In case this space is Symk of a non-padic field, we return 0. + + OUTPUT: + + - a prime + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + sage: D = Distributions(0, 7); D + Space of 7-adic distributions with k=0 action and precision cap 20 + sage: D.prime() + 7 + sage: D = Symk(4, base=GF(7)); D + Sym^4 (Finite Field of size 7)^2 + sage: D.prime() + 0 + + But Symk of a `p`-adic field does work:: + + sage: D = Symk(4, base=Qp(7)); D + Sym^4 Q_7^2 + sage: D.prime() + 7 + sage: D.is_symk() + True + """ + return self._p + + def weight(self): + """ + Return the weight of this distribution space. The standard + caveat applies, namely that the weight of `Sym^k` is + defined to be `k`, not `k+2`. + + OUTPUT: + + - nonnegative integer + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + sage: D = Distributions(0, 7); D + Space of 7-adic distributions with k=0 action and precision cap 20 + sage: D.weight() + 0 + sage: Distributions(389, 7).weight() + 389 + """ + return self._k + + def precision_cap(self): + """ + Return the precision cap on distributions. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + sage: D = Distributions(0, 7, 10); D + Space of 7-adic distributions with k=0 action and precision cap 10 + sage: D.precision_cap() + 10 + sage: D = Symk(389, base=QQ); D + Sym^389 Q^2 + sage: D.precision_cap() + 390 + """ + return self._prec_cap + + def lift(self, p=None, M=None, new_base_ring=None): + """ + Return distribution space that contains lifts with given p, + precision cap M, and base ring new_base_ring. + + INPUT: + + - `p` -- prime or None + - `M` -- nonnegative integer or None + - ``new_base_ring`` -- ring or None + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + sage: D = Symk(0, Qp(7)); D + Sym^0 Q_7^2 + sage: D.lift(M=20) + Space of 7-adic distributions with k=0 action and precision cap 20 + sage: D.lift(p=7, M=10) + Space of 7-adic distributions with k=0 action and precision cap 10 + sage: D.lift(p=7, M=10, new_base_ring=QpCR(7,15)).base_ring() + 7-adic Field with capped relative precision 15 + """ + if self._character is not None: + if self._character.base_ring() != QQ: + # need to change coefficient ring for character + raise NotImplementedError + if M is None: + M = self._prec_cap + 1 + + # sanitize new_base_ring. Don't want it to end up being QQ! + if new_base_ring is None: + new_base_ring = self.base_ring() + try: + pp = new_base_ring.prime() + except AttributeError: + pp = None + + if p is None and pp is None: + raise ValueError("You must specify a prime") + elif pp is None: + new_base_ring = QpCR(p, M) + elif p is None: + p = pp + elif p != pp: + raise ValueError("Inconsistent primes") + return Distributions(k=self._k, p=p, prec_cap=M, base=new_base_ring, character=self._character, adjuster=self._adjuster, act_on_left=self._act.is_left()) + + @cached_method + def approx_module(self, M=None): + """ + Return the M-th approximation module, or if M is not specified, + return the largest approximation module. + + INPUT:: + + - `M` -- None or nonnegative integer that is at most the precision cap + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + sage: D = Distributions(0, 5, 10) + sage: D.approx_module() + Ambient free module of rank 10 over the principal ideal domain 5-adic Ring with capped absolute precision 10 + sage: D.approx_module(1) + Ambient free module of rank 1 over the principal ideal domain 5-adic Ring with capped absolute precision 10 + sage: D.approx_module(0) + Ambient free module of rank 0 over the principal ideal domain 5-adic Ring with capped absolute precision 10 + + Note that M must be at most the precision cap, and must be nonnegative:: + + sage: D.approx_module(11) + Traceback (most recent call last): + ... + ValueError: M must be less than or equal to the precision cap + sage: D.approx_module(-1) + Traceback (most recent call last): + ... + ValueError: rank (=-1) must be nonnegative + """ + +# print "Calling approx_module with self = ",self," and M = ",M + if M is None: + M = self._prec_cap + elif M > self._prec_cap: + raise ValueError("M(=%s) must be less than or equal to the precision cap (=%s)"%(M,self._prec_cap)) + elif M < self._prec_cap and self.is_symk(): + raise ValueError("Sym^k objects do not support approximation modules") + return self.base_ring()**M + + def random_element(self, M=None): + """ + Return a random element of the M-th approximation module with non-negative valuation. + + INPUT: + + - `M` -- None or a nonnegative integer + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + sage: D = Distributions(0, 5, 10) + sage: D.random_element() + (..., ..., ..., ..., ..., ..., ..., ..., ..., ...) + sage: D.random_element(0) + () + sage: D.random_element(5) + (..., ..., ..., ..., ...) + sage: D.random_element(-1) + Traceback (most recent call last): + ... + ValueError: rank (=-1) must be nonnegative + sage: D.random_element(11) + Traceback (most recent call last): + ... + ValueError: M must be less than or equal to the precision cap + """ + if M == None: + M = self.precision_cap() + R = self.base_ring().integer_ring() + return self((R**M).random_element()) +## return self(self.approx_module(M).random_element()) + + def clear_cache(self): + """ + Clear some caches that are created only for speed purposes. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + sage: D = Distributions(0, 7, 10) + sage: D.clear_cache() + """ + self.approx_module.clear_cache() + self._act.clear_cache() + + @cached_method + def basis(self, M=None): + """ + Return a basis for this space of distributions. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + sage: D = Distributions(0, 7, 4); D + Space of 7-adic distributions with k=0 action and precision cap 4 + sage: D.basis() + [(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)] + + sage: D = Symk(3, base=QQ); D + Sym^3 Q^2 + sage: D.basis() + [(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)] + """ + V = self.approx_module(M) + return [self(v) for v in V.basis()] + + def _an_element_(self): + """ + Return a typical element of self. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + sage: D = Distributions(0, 7, 4); D + Space of 7-adic distributions with k=0 action and precision cap 4 + sage: D.an_element() # indirect doctest + (2, 1) + """ + if self._prec_cap > 1: + return self([2,1]) + else: + return self([1]) + +class Symk_class(Distributions_abstract): + + def __init__(self, k, base, character, adjuster, act_on_left, dettwist): + r""" + EXAMPLE:: + + sage: D = sage.modular.pollack_stevens.distributions.Symk(4); D + Sym^4 Q^2 + sage: TestSuite(D).run() # indirect doctest + """ + if hasattr(base, 'prime'): + p = base.prime() + else: + p = ZZ(0) + Distributions_abstract.__init__(self, k, p, k+1, base, character, adjuster, act_on_left, dettwist) + + def _an_element_(self): + r""" + Return a representative element of self. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.distributions import Symk + sage: D = Symk(3, base=QQ); D + Sym^3 Q^2 + sage: D.an_element() # indirect doctest + (0, 1, 2, 3) + """ + return self(range(self.weight() + 1)) + + def _repr_(self): + """ + EXAMPLES:: + + sage: Symk(6) + Sym^6 Q^2 + sage: Symk(6,dettwist=3) + Sym^6 Q^2 * det^3 + sage: Symk(6,character=DirichletGroup(7,QQ).0) + Sym^6 Q^2 twisted by Dirichlet character modulo 7 of conductor 7 mapping 3 |--> -1 + sage: Symk(6,character=DirichletGroup(7,QQ).0,dettwist=3) + Sym^6 Q^2 * det^3 twisted by Dirichlet character modulo 7 of conductor 7 mapping 3 |--> -1 + + """ + if self.base_ring() is QQ: + V = 'Q^2' + elif self.base_ring() is ZZ: + V = 'Z^2' + elif isinstance(self.base_ring(), pAdicGeneric) and self.base_ring().degree() == 1: + if self.base_ring().is_field(): + V = 'Q_%s^2'%(self._p) + else: + V = 'Z_%s^2'%(self._p) + else: + V = '(%s)^2'%(self.base_ring()) + s = "Sym^%s %s" % (self._k, V) + if self._dettwist is not None and self._dettwist != 0: + s += " * det^%s" % self._dettwist + if self._character is not None: + s += " twisted by %s" % self._character + return s + + def is_symk(self): + """ + Whether or not this distributions space is Sym^k (ring). + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + sage: D = Distributions(4, 17, 10); D + Space of 17-adic distributions with k=4 action and precision cap 10 + sage: D.is_symk() + False + sage: D = Symk(4); D + Sym^4 Q^2 + sage: D.is_symk() + True + sage: D = Symk(4, base=GF(7)); D + Sym^4 (Finite Field of size 7)^2 + sage: D.is_symk() + True + """ + return True + + def change_ring(self, new_base_ring): + """ + Return a Symk with the same k but a different base ring. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + sage: D = Distributions(0, 7, 4); D + Space of 7-adic distributions with k=0 action and precision cap 4 + sage: D.base_ring() + 7-adic Ring with capped absolute precision 4 + sage: D2 = D.change_ring(QpCR(7)); D2 + Space of 7-adic distributions with k=0 action and precision cap 4 + sage: D2.base_ring() + 7-adic Field with capped relative precision 20 + """ + return Symk(k=self._k, base=new_base_ring, character=self._character, adjuster=self._adjuster, act_on_left=self._act.is_left()) + + def base_extend(self, new_base_ring): + r""" + Extend scalars to a new base ring. + + EXAMPLE:: + + sage: Symk(3).base_extend(Qp(3)) + Sym^3 Q_3^2 + """ + if not new_base_ring.has_coerce_map_from(self.base_ring()): + raise ValueError("New base ring (%s) does not have a coercion from %s" % (new_base_ring, self.base_ring())) + return self.change_ring(new_base_ring) + + +class Distributions_class(Distributions_abstract): + r""" + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions + sage: D = Distributions(0, 5, 10) + sage: TestSuite(D).run() + """ + + def _repr_(self): + """ + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + sage: Distributions(0, 5, 10)._repr_() + 'Space of 5-adic distributions with k=0 action and precision cap 10' + sage: Distributions(0, 5, 10) + Space of 5-adic distributions with k=0 action and precision cap 10 + + Examples with twists:: + + sage: Distributions(0,3,4) + Space of 3-adic distributions with k=0 action and precision cap 4 + sage: Distributions(0,3,4,dettwist=-1) + Space of 3-adic distributions with k=0 action and precision cap 4 twistted by det^-1 + sage: Distributions(0,3,4,character=DirichletGroup(3).0) + Space of 3-adic distributions with k=0 action and precision cap 4 twistted by (Dirichlet character modulo 3 of conductor 3 mapping 2 |--> -1) + sage: Distributions(0,3,4,character=DirichletGroup(3).0,dettwist=-1) + Space of 3-adic distributions with k=0 action and precision cap 4 twistted by det^-1 * (Dirichlet character modulo 3 of conductor 3 mapping 2 |--> -1) + """ + s = "Space of %s-adic distributions with k=%s action and precision cap %s"%(self._p, self._k, self._prec_cap) + twiststuff = [] + if self._dettwist is not None: + twiststuff.append("det^%s" % self._dettwist) + if self._character is not None: + twiststuff.append("(%s)" % self._character) + if twiststuff: + s += " twistted by " + " * ".join(twiststuff) + return s + + def is_symk(self): + """ + Whether or not this distributions space is Sym^k (ring). + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + sage: D = Distributions(4, 17, 10); D + Space of 17-adic distributions with k=4 action and precision cap 10 + sage: D.is_symk() + False + sage: D = Symk(4); D + Sym^4 Q^2 + sage: D.is_symk() + True + sage: D = Symk(4, base=GF(7)); D + Sym^4 (Finite Field of size 7)^2 + sage: D.is_symk() + True + """ + return False + + def change_ring(self, new_base_ring): + """ + Return space of distributions like this one, but with the base ring changed. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + sage: D = Distributions(0, 7, 4); D + Space of 7-adic distributions with k=0 action and precision cap 4 + sage: D.base_ring() + 7-adic Ring with capped absolute precision 4 + sage: D2 = D.change_ring(QpCR(7)); D2 + Space of 7-adic distributions with k=0 action and precision cap 4 + sage: D2.base_ring() + 7-adic Field with capped relative precision 20 + """ + return Distributions(k=self._k, p=self._p, prec_cap=self._prec_cap, base=new_base_ring, character=self._character, adjuster=self._adjuster, act_on_left=self._act.is_left()) + + def specialize(self, new_base_ring=None): + """ + Return distribution space got by specializing to Sym^k, over + the new_base_ring. If new_base_ring is not given, use current + base_ring. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + sage: D = Distributions(0, 7, 4); D + Space of 7-adic distributions with k=0 action and precision cap 4 + sage: D.is_symk() + False + sage: D2 = D.specialize(); D2 + Sym^0 Z_7^2 + sage: D2.is_symk() + True + sage: D2 = D.specialize(QQ); D2 + Sym^0 Q^2 + """ + if self._character is not None: + raise NotImplementedError + if new_base_ring is None: + new_base_ring = self.base_ring() + return Symk(k=self._k, base=new_base_ring, adjuster=self._adjuster, act_on_left=self._act.is_left()) diff --git a/src/sage/modular/pollack_stevens/fund_domain.py b/src/sage/modular/pollack_stevens/fund_domain.py new file mode 100644 index 00000000000..cd3d86e4fb4 --- /dev/null +++ b/src/sage/modular/pollack_stevens/fund_domain.py @@ -0,0 +1,1581 @@ +r""" +Manin Relations + +Code to create the Manin Relations class, which solves the "Manin relations". +That is, a description of `Div^0(P^1(\QQ))` as a `\ZZ[\Gamma_0(N)]`-module in +terms of generators and relations is found. The method used is geometric, +constructing a nice fundamental domain for `\Gamma_0(N)` and reading the +relevant Manin relations off of that picture. The algorithm follows [PS2011]. + +REFERENCES: + +.. [PS2011] R. Pollack, and G. Stevens. "Overconvergent modular symobals and +p-adic L-functions." Annales scientifiques de l'Ecole normale superieure. Vol. +44. No. 1. Elsevier, 2011. + +AUTHORS: + + - Robert Pollack, Jonathan Hanke (2012): initial version + +""" +#***************************************************************************** +# Copyright (C) 2012 Robert Pollack +# Jonathan Hanke +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.matrix.matrix_integer_2x2 import MatrixSpace_ZZ_2x2 +from sage.modular.modsym.all import P1List +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.finite_rings.integer_mod_ring import Zmod +from sage.rings.rational_field import QQ +from sage.structure.sage_object import SageObject +from sage.modules.free_module_element import zero_vector +from copy import deepcopy +from sage.misc.cachefunc import cached_method +from sage.rings.arith import convergents,xgcd,gcd + +from sigma0 import Sigma0, Sigma0Element + +M2ZSpace = MatrixSpace_ZZ_2x2() + +def M2Z(x): + r""" + Create an immutable 2x2 integer matrix from x. + """ + x = M2ZSpace(x) + x.set_immutable() + return x + +Id = M2Z([1,0,0,1]) +sig = M2Z([0,1,-1,0]) +tau = M2Z([0,-1,1,-1]) +minone_inf_path = M2Z([1,1,-1,0]) + +# We store these so that we don't have to constantly create them. +t00 = (0,0) +t10 = (1,0) +t01 = (0,1) +t11 = (1,1) + +class PSModularSymbolsDomain(SageObject): + r""" + The domain of a modular symbol. + + INPUT: + + - ``N`` -- a positive integer, the level of the congruence subgroup + `\Gamma_0(N)` + + - ``reps`` -- a list of 2x2 matrices, the coset representatives of + `Div^0(P^1(\QQ))` + + - ``indices`` -- a list of integers; indices of elements in ``reps`` + which are generators + + - ``rels`` -- a list of list of triples ``(d, A, i)``, one for each + coset representative of ``reps`` which describes how to express the + elements of ``reps`` in terms of generators specified by ``indices``. + See :meth:`relations` for a detailed explanations of these triples. + + - ``equiv_ind`` -- a dictionary which maps normalized coordinates on + `P^1(\ZZ/N\ZZ)` to an integer such that a matrix whose bottom row is + equivalent to `[a:b]` in `P^1(\ZZ/N\ZZ)` is in the coset of + ``reps[equiv_ind[(a,b)]]`` + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.fund_domain import PSModularSymbolsDomain, M2Z + sage: PSModularSymbolsDomain(2 , [M2Z([1,0,0,1]), M2Z([1,1,-1,0]), M2Z([0,-1,1,1])], [0,2], [[(1, M2Z([1,0,0,1]), 0)], [(-1,M2Z([-1,-1,0,-1]),0)], [(1, M2Z([1,0,0,1]), 2)]], {(0,1): 0, (1,0): 1, (1,1): 2}) + Modular Symbol domain of level 2 + + TESTS: + + The level ``N`` must be an integer:: + + sage: PSModularSymbolsDomain(1/2, None, None, None, None) + Traceback (most recent call last): + ... + TypeError: no conversion of this rational to integer + sage: PSModularSymbolsDomain(Gamma0(11), None, None, None, None) + Traceback (most recent call last): + ... + TypeError: unable to coerce to an integer + + """ + def __init__(self, N, reps, indices, rels, equiv_ind): + r""" + INPUT: + + See :class:`PSModularSymbolsDomain`. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.fund_domain import PSModularSymbolsDomain + sage: isinstance(ManinRelations(11), PSModularSymbolsDomain) # indirect doctest + True + + """ + self._N = ZZ(N) + self._reps = reps + + self._indices = sorted(indices) + self._gens = [M2Z(reps[i]) for i in self._indices] + self._ngens = len(indices) + + if len(rels) != len(reps): + raise ValueError("length of reps and length of rels must be equal") + self._rels = rels + self._rel_dict = {} + for j, L in enumerate(rels): + self._rel_dict[reps[j]] = L + + self._equiv_ind = equiv_ind + self._equiv_rep = {} + for ky in equiv_ind: + self._equiv_rep[ky] = reps[equiv_ind[ky]] + + def _repr_(self): + r""" + A string representation of this domain. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.fund_domain import PSModularSymbolsDomain, M2Z + sage: PSModularSymbolsDomain(2 , [M2Z([1,0,0,1]), M2Z([1,1,-1,0]), M2Z([0,-1,1,1])], [0,2], [[(1, M2Z([1,0,0,1]), 0)], [(-1,M2Z([-1,-1,0,-1]),0)], [(1, M2Z([1,0,0,1]), 2)]], {(0,1): 0, (1,0): 1, (1,1): 2})._repr_() + 'Modular Symbol domain of level 2' + + """ + return "Modular Symbol domain of level %s"%self._N + + def __len__(self): + r""" + Returns the number of coset representatives. + + EXAMPLES:: + + sage: A = ManinRelations(11) + sage: len(A) + 12 + + """ + return len(self._reps) + + def __getitem__(self, i): + r""" + Returns the ``i``-th coset representative. + + EXAMPLES:: + + sage: A = ManinRelations(11) + sage: A[4] + [-1 -2] + [ 2 3] + + """ + return self._reps[i] + + def __iter__(self): + r""" + Returns an iterator over all coset representatives. + + EXAMPLES:: + + sage: A = ManinRelations(11) + sage: for rep in A: + ... if rep[1,0] == 1: + ... print rep + [ 0 -1] + [ 1 3] + [ 0 -1] + [ 1 2] + [ 0 -1] + [ 1 1] + + """ + return iter(self._reps) + + def gens(self): + r""" + Returns the list of coset representatives chosen as generators. + + EXAMPLES:: + + sage: A = ManinRelations(11) + sage: A.gens() + [ + [1 0] [ 0 -1] [-1 -1] + [0 1], [ 1 3], [ 3 2] + ] + + """ + return self._gens + + def gen(self, n=0): + r""" + Returns the ``n``-th generator. + + INPUT: + + - ``n`` -- integer (default: 0), which generator is desired + + EXAMPLES:: + + sage: A = ManinRelations(137) + sage: A.gen(17) + [-4 -1] + [ 9 2] + + """ + return self._gens[n] + + def ngens(self): + r""" + Returns the number of generators. + + OUTPUT: + + The number of coset representatives from which a modular symbol's value + on any coset can be derived. + + EXAMPLES:: + + sage: A = ManinRelations(1137) + sage: A.ngens() + 255 + + """ + return len(self._gens) + + def level(self): + r""" + Returns the level `N` of `\Gamma_0(N)` that we work with. + + OUTPUT: + + The integer `N` of the group `\Gamma_0(N)` for which the Manin + Relations are being computed. + + EXAMPLES:: + + sage: A = ManinRelations(11) + sage: A.level() + 11 + + """ + return self._N + + def indices(self, n=None): + r""" + Returns the ``n``-th index of the coset representatives which were + chosen as our generators. + + In particular, the divisors associated to these coset representatives + generate all divisors over `\ZZ[\Gamma_0(N)]`, and thus a modular + symbol is uniquely determined by its values on these divisors. + + INPUT: + + - ``n`` -- integer (default: None) + + OUTPUT: + + The ``n``-th index of the generating set in ``self.reps()`` or all + indices if ``n`` is ``None``. + + EXAMPLES:: + + sage: A = ManinRelations(11) + sage: A.indices() + [0, 2, 3] + + sage: A.indices(2) + 3 + + sage: A = ManinRelations(13) + sage: A.indices() + [0, 2, 3, 4, 5] + + sage: A = ManinRelations(101) + sage: A.indices() + [0, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14, 16, 17, 19, 20, 23, 24, 26, 28] + + """ + if n is None: + return self._indices + else: + return self._indices[n] + + def reps(self, n=None): + r""" + Returns the ``n``-th coset representative associated with our + fundamental domain. + + INPUT: + + - ``n`` -- integer (default: None) + + OUTPUT: + + The ``n``-th coset representative or all coset representatives if ``n`` + is ``None``. + + EXAMPLES:: + + sage: A = ManinRelations(11) + sage: A.reps(0) + [1 0] + [0 1] + sage: A.reps(1) + [ 1 1] + [-1 0] + sage: A.reps(2) + [ 0 -1] + [ 1 3] + sage: A.reps() + [ + [1 0] [ 1 1] [ 0 -1] [-1 -1] [-1 -2] [-2 -1] [ 0 -1] [ 1 0] + [0 1], [-1 0], [ 1 3], [ 3 2], [ 2 3], [ 3 1], [ 1 2], [-2 1], + + [ 0 -1] [ 1 0] [-1 -1] [ 1 -1] + [ 1 1], [-1 1], [ 2 1], [-1 2] + ] + + """ + if n is None: + return self._reps + else: + return self._reps[n] + + def relations(self, A=None): + r""" + Expresses the divisor attached to the coset representative of ``A`` in + terms of our chosen generators. + + INPUT: + + - ``A`` -- ``None``, an integer, or a coset representative (default: + ``None``) + + OUTPUT: + + A `\ZZ[\Gamma_0(N)]`-relation expressing the divisor attached to ``A`` + in terms of the generating set. The relation is given as a list of + triples ``(d, B, i)`` such that the divisor attached to `A`` is the sum + of ``d`` times the divisor attached to ``B^{-1} * self.reps(i)``. + + If ``A`` is an integer, then return this data for the ``A``-th + coset representative. + + If ``A`` is ``None``, then return this data in a list for all coset + representatives. + + .. NOTE:: + + These relations allow us to recover the value of a modular symbol + on any coset representative in terms of its values on our + generating set. + + EXAMPLES:: + + sage: MR = ManinRelations(11) + sage: MR.indices() + [0, 2, 3] + sage: MR.relations(0) + [(1, [1 0] + [0 1], 0)] + sage: MR.relations(2) + [(1, [1 0] + [0 1], 2)] + sage: MR.relations(3) + [(1, [1 0] + [0 1], 3)] + + The fourth coset representative can be expressed through the second coset representative:: + + sage: MR.reps(4) + [-1 -2] + [ 2 3] + sage: d, B, i = MR.relations(4)[0] + sage: P = B.inverse()*MR.reps(i); P + [ 2 -1] + [-3 2] + sage: d # the above corresponds to minus the divisor of A.reps(4) since d is -1 + -1 + + The sixth coset representative can be expressed as the sum of the second and the third:: + + sage: MR.reps(6) + [ 0 -1] + [ 1 2] + sage: MR.relations(6) + [(1, [1 0] + [0 1], 2), (1, [1 0] + [0 1], 3)] + sage: MR.reps(2), MR.reps(3) # MR.reps(6) is the sum of these divisors + ( + [ 0 -1] [-1 -1] + [ 1 3], [ 3 2] + ) + + TESTS: + + Test that the other ways of calling this method work:: + + sage: MR.relations(MR.reps(6)) + [(1, [1 0] + [0 1], 2), (1, [1 0] + [0 1], 3)] + sage: MR.relations(None) + [[(1, [1 0] + [0 1], 0)], [(-1, [-1 -1] + [ 0 -1], 0)], [(1, [1 0] + [0 1], 2)], [(1, [1 0] + [0 1], 3)], [(-1, [-3 -2] + [11 7], 2)], [(-1, [-4 -3] + [11 8], 3)], [(1, [1 0] + [0 1], 2), (1, [1 0] + [0 1], 3)], [(-1, [1 0] + [0 1], 2), (-1, [1 0] + [0 1], 3)], [(1, [1 0] + [0 1], 2), (1, [1 0] + [0 1], 3), (-1, [-3 -2] + [11 7], 2), (-1, [-4 -3] + [11 8], 3)], [(-1, [1 0] + [0 1], 2), (-1, [1 0] + [0 1], 3), (1, [-3 -2] + [11 7], 2), (1, [-4 -3] + [11 8], 3)], [(-1, [-3 -2] + [11 7], 2), (-1, [-4 -3] + [11 8], 3)], [(1, [-3 -2] + [11 7], 2), (1, [-4 -3] + [11 8], 3)]] + + """ + if A is None: + return self._rels + elif isinstance(A, (int, Integer, slice)): + return self._rels[A] + else: + return self._rel_dict[A] + + def equivalent_index(self, A): + r""" + Returns the index of the coset representative equivalent to ``A``. + + Here by equivalent we mean the unique coset representative whose bottom + row is equivalent to the bottom row of ``A`` in `P^1(\ZZ/N\ZZ)`. + + INPUT: + + - ``A`` -- an element of `SL_2(\ZZ)` + + OUTPUT: + + The unique integer ``j`` satisfying that the bottom row of + ``self.reps(j)`` is equivalent to the bottom row of ``A``. + + EXAMPLES:: + + sage: MR = ManinRelations(11) + sage: A = matrix(ZZ,2,2,[1,5,3,16]) + sage: j = MR.equivalent_index(A); j + 11 + sage: MR.reps(11) + [ 1 -1] + [-1 2] + sage: MR.equivalent_rep(A) + [ 1 -1] + [-1 2] + sage: MR.P1().normalize(3,16) + (1, 9) + + """ + return self._equiv_ind[self._P.normalize(A[t10],A[t11])] + + def equivalent_rep(self, A): + r""" + Returns a coset representative that is equivalent to ``A`` modulo + `\Gamma_0(N)`. + + INPUT: + + - ``A`` -- a matrix in `SL_2(\ZZ)` + + OUTPUT: + + The unique generator congruent to ``A`` modulo `\Gamma_0(N)`. + + EXAMPLES:: + + sage: from sage.matrix.matrix_integer_2x2 import MatrixSpace_ZZ_2x2 + sage: M2Z = MatrixSpace_ZZ_2x2() + sage: A = M2Z([5,3,38,23]) + sage: ManinRelations(60).equivalent_rep(A) + [-7 -3] + [26 11] + + """ + return self._reps[self.equivalent_index(A)] + + def P1(self): + r""" + Returns the Sage representation of `P^1(\ZZ/N\ZZ)`. + + EXAMPLES:: + + sage: A = ManinRelations(11) + sage: A.P1() + The projective line over the integers modulo 11 + + """ + return self._P + +class ManinRelations(PSModularSymbolsDomain): + r""" + This class gives a description of `Div^0(P^1(\QQ))` as a + `\ZZ[\Gamma_0(N)]`-module. + + INPUT: + + - ``N`` -- a positive integer, the level of `\Gamma_0(N)` to work with + + EXAMPLES:: + + sage: ManinRelations(1) + Manin Relations of level 1 + sage: ManinRelations(11) + Manin Relations of level 11 + + Large values of ``N`` are not supported:: + + sage: ManinRelations(2^20) + Traceback (most recent call last): + ... + OverflowError: Modulus is too large (must be < 46340) + + TESTS: + + ``N`` has to be a positive integer:: + + sage: ManinRelations(0) + Traceback (most recent call last): + ... + ValueError: N must be a positive integer + sage: ManinRelations(-5) + Traceback (most recent call last): + ... + ValueError: N must be a positive integer + + """ + def __init__(self, N): + r""" + Create an instance of this class. + + INPUT: + + - ``N`` -- a positive integer, the level of `\Gamma_0(N)` to work with + + EXAMPLES:: + + sage: type(ManinRelations(30)) + + + """ + N = ZZ(N) + if N <= 0: + raise ValueError, "N must be a positive integer" + self._N = N + SN = Sigma0(N) + + ## Creates and stores the Sage representation of P^1(Z/NZ) + P = P1List(N) + self._P = P + IdN = SN([1,0,0,1]) + + ## Creates a fundamental domain for Gamma_0(N) whose boundary is a union + ## of unimodular paths (except in the case of 3-torsion). + ## We will call the intersection of this domain with the real axis the + ## collection of cusps (even if some are Gamma_0(N) equivalent to one another). + cusps = self.form_list_of_cusps() + + ## Takes the boundary of this fundamental domain and finds SL_2(Z) matrices whose + ## associated unimodular path gives this boundary. These matrices form the + ## beginning of our collection of coset reps for Gamma_0(N) / SL_2(Z). + coset_reps = self.fd_boundary(cusps) + + ## Takes the bottom row of each of our current coset reps, + ## thinking of them as distinct elements of P^1(Z/NZ) + p1s = [(coset_reps[j])[1] for j in range(len(coset_reps))] + + ## Initializes relevant Manin data + gens_index = [] + twotor_index = [] + twotorrels = [] + threetor_index = [] + threetorrels = [] + rels = [0] * len(coset_reps) + gammas = {} + + ## the list rels (above) will give Z[Gamma_0(N)] relations between + ## the associated divisor of each coset representatives in terms + ## of our chosen set of generators. + ## entries of rels will be lists of elements of the form (c,A,r) + ## with c a constant, A a Gamma_0(N) matrix, and r the index of a + ## generator. The meaning is that the divisor associated to the + ## j-th coset rep will equal the sum of: + ## + ## c * A^(-1) * (divisor associated to r-th coset rep) + ## + ## as one varies over all (c,A,r) in rels[j]. + ## (Here r must be in self.generator_indices().) + ## + ## This will be used for modular symbols as then the value of a + ## modular symbol phi on the (associated divisor) of the j-th + ## element of coset_reps will be the sum of c * phi (r-th generator) | A + ## as one varies over the tuples in rels[j] + + boundary_checked = [False] * len(coset_reps) + + ## The list boundary_checked keeps track of which boundary pieces of the + ## fundamental domain have been already used as we are picking + ## our generators + + ## The following loop will choose our generators by picking one edge + ## out of each pair of edges that are glued to each other and picking + ## each edge glued to itself (arising from two-torsion) + ## ------------------------------------------------------------------ + for r in range(len(coset_reps)): + if boundary_checked[r] == False: + + ## We now check if this boundary edge is glued to itself by + ## Gamma_0(N) + + if P.normalize(p1s[r][0],p1s[r][1]) == P.normalize(-p1s[r][1],p1s[r][0]): + ## This edge is glued to itself and so coset_reps[r] + ## needs to be added to our generator list. + + ## this relation expresses the fact that + ## coset_reps[r] is one of our basic generators + rels[r] = [(1,IdN,r)] + + ## the index r is adding to our list + ## of indexes of generators + gens_index.append(r) + + ## the index r is adding to our list of indexes of + ## generators which satisfy a 2-torsion relation + twotor_index.append(r) + + gam = SN(coset_reps[r] * sig * coset_reps[r].inverse()) + ## gam is 2-torsion matrix and in Gamma_0(N). + ## if D is the divisor associated to coset_reps[r] + ## then gam * D = - D and so (1+gam)D=0. + + ## This gives a restriction to the possible values of + ## modular symbols on D + + ## The 2-torsion matrix gam is recorded in our list of + ## 2-torsion relations. + twotorrels.append(gam) + + ## We have now finished with this edge. + boundary_checked[r] = True + + else: + c = coset_reps[r][t10] + d = coset_reps[r][t11] + + ## In the following case the ideal triangle below + ## the unimodular path described by coset_reps[r] + ## contains a point fixed by a 3-torsion element. + if (c**2+d**2+c*d)%N == 0: + + ## the index r is adding to our list of indexes + ## of generators + gens_index.append(r) + + ## this relation expresses the fact that coset_reps[r] + ## is one of our basic generators + rels[r] = [(1,IdN,r)] + + ## the index r is adding to our list of indexes of + ## generators which satisfy a 3-torsion relation + threetor_index.append(r) + + gam = SN(coset_reps[r] * tau * coset_reps[r].inverse()) + ## gam is 3-torsion matrix and in Gamma_0(N). + ## if D is the divisor associated to coset_reps[r] + ## then (1+gam+gam^2)D=0. + ## This gives a restriction to the possible values of + ## modular symbols on D + + ## The 3-torsion matrix gam is recorded in our list of + ## 3-torsion relations. + threetorrels.append(gam) + + ## The reverse of the unimodular path associated to + ## coset_reps[r] is not Gamma_0(N) equivalent to it, so + ## we need to include it in our list of coset + ## representatives and record the relevant relations. + + a = coset_reps[r][t00] + b = coset_reps[r][t01] + + A = M2Z([-b,a,-d,c]) + coset_reps.append(A) + ## A (representing the reversed edge) is included in + ## our list of coset reps + + rels.append([(-1,IdN,r)]) + ## This relation means that phi on the reversed edge + ## equals -phi on original edge + + boundary_checked[r] = True + ## We have now finished with this edge. + + else: + ## This is the generic case where neither 2 or + ## 3-torsion intervenes. + ## The below loop searches through the remaining edges + ## and finds which one is equivalent to the reverse of + ## coset_reps[r] + ## --------------------------------------------------- + for s in range(r+1, len(coset_reps)): + if boundary_checked[s]: + continue + if P.normalize(p1s[s][0],p1s[s][1]) == P.normalize(-p1s[r][1],p1s[r][0]): + ## the reverse of coset_reps[r] is + ## Gamma_0(N)-equivalent to coset_reps[s] + ## coset_reps[r] will now be made a generator + ## and we need to express phi(coset_reps[s]) + ## in terms of phi(coset_reps[r]) + + gens_index.append(r) + ## the index r is adding to our list of + ## indexes of generators + + rels[r] = [(1,IdN,r)] + ## this relation expresses the fact that + ## coset_reps[r] is one of our basic generators + + A = coset_reps[s] * sig + ## A corresponds to reversing the orientation + ## of the edge corr. to coset_reps[r] + + gam = SN(coset_reps[r] * A.inverse()) + ## gam is in Gamma_0(N) (by assumption of + ## ending up here in this if statement) + + rels[s] = [(-1,gam,r)] + ## this relation means that phi evaluated on + ## coset_reps[s] equals -phi(coset_reps[r])|gam + ## To see this, let D_r be the divisor + ## associated to coset_reps[r] and D_s to + ## coset_reps[s]. Then gam D_s = -D_r and so + ## phi(gam D_s) = - phi(D_r) and thus + ## phi(D_s) = -phi(D_r)|gam + ## since gam is in Gamma_0(N) + + gammas[coset_reps[r]] = gam + ## this is a dictionary whose keys are the + ## non-torsion generators and whose values + ## are the corresponding gamma_i. It is + ## eventually stored as self.gammas. + + boundary_checked[r] = True + boundary_checked[s] = True + break + + ## We now need to complete our list of coset representatives by + ## finding all unimodular paths in the interior of the fundamental + ## domain, as well as express these paths in terms of our chosen set + ## of generators. + ## ------------------------------------------------------------------- + + for r in range(len(cusps)-2): + ## r is the index of the cusp on the left of the path. We only run + ## thru to the number of cusps - 2 since you can't start an + ## interior path on either of the last two cusps + + for s in range(r+2,len(cusps)): + ## s is in the index of the cusp on the the right of the path + cusp1 = cusps[r] + cusp2 = cusps[s] + if self.is_unimodular_path(cusp1,cusp2): + A,B = self.unimod_to_matrices(cusp1,cusp2) + ## A and B are the matrices whose associated paths + ## connect cusp1 to cusp2 and cusp2 to cusp1 (respectively) + coset_reps.extend([A,B]) + ## A and B are added to our coset reps + vA = [] + vB = [] + + ## This loop now encodes the relation between the + ## unimodular path A and our generators. This is done + ## simply by accounting for all of the edges that lie + ## below the path attached to A (as they form a triangle) + ## Similarly, this is also done for B. + + ## Running between the cusps between cusp1 and cusp2 + for rel in rels[r+2:s+2]: + ## Add edge relation + vA.append(rel[0]) + ## Add negative of edge relation + vB.append((-rel[0][0], rel[0][1], rel[0][2])) + ## Add relations for A and B to relations list + rels.extend([vA,vB]) + + ## Make the translation table between the Sage and Geometric + ## descriptions of P^1 + equiv_ind = {} + for i, rep in enumerate(coset_reps): + ky = P.normalize(rep[t10],rep[t11]) + equiv_ind[ky] = i + + self.gammas = gammas + PSModularSymbolsDomain.__init__(self, N, coset_reps, gens_index, rels, equiv_ind) + + ## A list of indices of the (geometric) coset representatives whose + ## paths are identified by some 2-torsion element (which switches the + ## path orientation) + self._indices_with_two_torsion = twotor_index + self._reps_with_two_torsion = [coset_reps[i] for i in twotor_index] + + ## A dictionary of (2-torsion in PSL_2(Z)) matrices in Gamma_0(N) that give + ## the orientation identification in the paths listed in twotor_index above! + self._two_torsion = {} + for j, tor_elt in zip(twotor_index, twotorrels): + self._two_torsion[coset_reps[j]] = tor_elt + + ## A list of indices of the (geometric) coset representatives that + ## form one side of an ideal triangle with an interior fixed point of + ## a 3-torsion element of Gamma_0(N) + self._indices_with_three_torsion = threetor_index + self._reps_with_three_torsion = [coset_reps[i] for i in threetor_index] + + ## A dictionary of (3-torsion in PSL_2(Z)) matrices in Gamma_0(N) that give + ## the interior fixed point described in threetor_index above! + self._three_torsion = {} + for j, tor_elt in zip(threetor_index, threetorrels): + self._three_torsion[coset_reps[j]] = tor_elt + + def _repr_(self): + r""" + A printable representation of this domain. + + EXAMPLES:: + + sage: ManinRelations(11)._repr_() + 'Manin Relations of level 11' + + """ + return "Manin Relations of level %s"%self._N + + def indices_with_two_torsion(self): + r""" + The indices of coset representatives whose associated unimodular path + contains a point fixed by a `\Gamma_0(N)` element of order 2 (where the + order is computed in `PSL_2(\ZZ)`). + + OUTPUT: + + A list of integers. + + EXAMPLES:: + + sage: MR = ManinRelations(11) + sage: MR.indices_with_two_torsion() + [] + sage: MR = ManinRelations(13) + sage: MR.indices_with_two_torsion() + [3, 4] + sage: MR.reps(3), MR.reps(4) + ( + [-1 -1] [-1 -2] + [ 3 2], [ 2 3] + ) + + The coresponding matrix of order 2:: + + sage: A = MR.two_torsion_matrix(MR.reps(3)); A + [ 5 2] + [-13 -5] + sage: A^2 + [-1 0] + [ 0 -1] + + You can see that multiplication by ``A`` just interchanges the rational + cusps determined by the columns of the matrix ``MR.reps(3)``:: + + sage: MR.reps(3), A*MR.reps(3) + ( + [-1 -1] [ 1 -1] + [ 3 2], [-2 3] + ) + + """ + return self._indices_with_two_torsion + + def reps_with_two_torsion(self): + r""" + The coset representatives whose associated unimodular path contains a + point fixed by a `\Gamma_0(N)` element of order 2 (where the order is + computed in `PSL_2(\ZZ)`). + + OUTPUT: + + A list of matrices. + + EXAMPLES:: + + sage: MR = ManinRelations(11) + sage: MR.reps_with_two_torsion() + [] + sage: MR = ManinRelations(13) + sage: MR.reps_with_two_torsion() + [ + [-1 -1] [-1 -2] + [ 3 2], [ 2 3] + ] + sage: B = MR.reps_with_two_torsion()[0] + + The coresponding matrix of order 2:: + + sage: A = MR.two_torsion_matrix(B); A + [ 5 2] + [-13 -5] + sage: A^2 + [-1 0] + [ 0 -1] + + You can see that multiplication by ``A`` just interchanges the rational + cusps determined by the columns of the matrix ``MR.reps(3)``:: + + sage: B, A*B + ( + [-1 -1] [ 1 -1] + [ 3 2], [-2 3] + ) + + """ + return self._reps_with_two_torsion + + def two_torsion_matrix(self, A): + r""" + Return the matrix of order two in `\Gamma_0(N)` which corresponds to an + ``A`` in ``self.reps_with_two_torsion()``. + + INPUT: + + - ``A`` -- a matrix in ``self.reps_with_two_torsion()`` + + EXAMPLES:: + + sage: MR = ManinRelations(25) + sage: B = MR.reps_with_two_torsion()[0] + + The coresponding matrix of order 2:: + + sage: A = MR.two_torsion_matrix(B); A + [ 7 2] + [-25 -7] + sage: A^2 + [-1 0] + [ 0 -1] + + """ + return self._two_torsion[A] + + def indices_with_three_torsion(self): + r""" + A list of indices of coset representatives whose associated unimodular + path contains a point fixed by a `\Gamma_0(N)` element of order 3 in + the ideal triangle directly below that path (the order is computed in + `PSL_2(\ZZ)`). + + EXAMPLES:: + + sage: MR = ManinRelations(11) + sage: MR.indices_with_three_torsion() + [] + sage: MR = ManinRelations(13) + sage: MR.indices_with_three_torsion() + [2, 5] + sage: B = MR.reps(2); B + [ 0 -1] + [ 1 3] + + The corresponding matrix of order three:: + + sage: A = MR.three_torsion_matrix(B); A + [-4 -1] + [13 3] + sage: A^3 + [1 0] + [0 1] + + The columns of ``B`` and the columns of ``A*B`` and ``A^2*B`` give the + same rational cusps:: + + sage: B + [ 0 -1] + [ 1 3] + sage: A*B, A^2*B + ( + [-1 1] [ 1 0] + [ 3 -4], [-4 1] + ) + + """ + return self._indices_with_three_torsion + + def reps_with_three_torsion(self): + r""" + A list of coset representatives whose associated unimodular path + contains a point fixed by a `\Gamma_0(N)` element of order 3 in the + ideal triangle directly below that path (the order is computed in + `PSL_2(\ZZ)`). + + EXAMPLES:: + + sage: MR = ManinRelations(13) + sage: B = MR.reps_with_three_torsion()[0]; B + [ 0 -1] + [ 1 3] + + The corresponding matrix of order three:: + + sage: A = MR.three_torsion_matrix(B); A + [-4 -1] + [13 3] + sage: A^3 + [1 0] + [0 1] + + The columns of ``B`` and the columns of ``A*B`` and ``A^2*B`` give the + same rational cusps:: + + sage: B + [ 0 -1] + [ 1 3] + sage: A*B, A^2*B + ( + [-1 1] [ 1 0] + [ 3 -4], [-4 1] + ) + + """ + return self._reps_with_three_torsion + + def three_torsion_matrix(self, A): + """ + Return the matrix of order two in `\Gamma_0(N)` which corresponds to an + ``A`` in ``self.reps_with_two_torsion()``. + + INPUT: + + - ``A`` -- a matrix in ``self.reps_with_two_torsion()`` + + EXAMPLES:: + + sage: MR = ManinRelations(37) + sage: B = MR.reps_with_three_torsion()[0] + + The coresponding matrix of order 3:: + + sage: A = MR.three_torsion_matrix(B); A + [-11 -3] + [ 37 10] + sage: A^3 + [1 0] + [0 1] + + """ + return self._three_torsion[A] + + def form_list_of_cusps(self): + r""" + Returns the intersection of a fundamental domain for `\Gamma_0(N)` with + the real axis. + + The construction of this fundamental domain follows the arguments of + [PS2011] Section 2. The boundary of this fundamental domain consists + entirely of unimodular paths when `\Gamma_0(N)` has no elements of + order 3. (See [PS2011] Section 2.5 for the case when there are + elements of order 3.) + + OUTPUT: + + A sorted list of rational numbers marking the intersection of a + fundamental domain for `\Gamma_0(N)` with the real axis. + + EXAMPLES:: + + sage: A = ManinRelations(11) + sage: A.form_list_of_cusps() + [-1, -2/3, -1/2, -1/3, 0] + sage: A = ManinRelations(13) + sage: A.form_list_of_cusps() + [-1, -2/3, -1/2, -1/3, 0] + sage: A = ManinRelations(101) + sage: A.form_list_of_cusps() + [-1, -6/7, -5/6, -4/5, -7/9, -3/4, -11/15, -8/11, -5/7, -7/10, -9/13, -2/3, -5/8, -13/21, -8/13, -3/5, -7/12, -11/19, -4/7, -1/2, -4/9, -3/7, -5/12, -7/17, -2/5, -3/8, -4/11, -1/3, -2/7, -3/11, -1/4, -2/9, -1/5, -1/6, 0] + + """ + ## Get the level + N = self.level() + + ## Checks that the level N is > 1 + # TODO: I'm commenting this out; I see no reason not to allow level 1, except + # possibly the bug here that I fixed: http://trac.sagemath.org/sage_trac/ticket/12772 + #if not (N > 1): + # raise TypeError, "Error in form_list_of_cusps: level should be > 1" + + ## Some convenient shortcuts + P = self.P1() + sP = len(P.list()) ## Size of P^1(Z/NZ) + + ## Initialize some lists + + C = [QQ(-1),"?",QQ(0)] + + ## Initialize the list of cusps at the bottom of the fund. domain. + ## The ? denotes that it has not yet been checked if more cusps need + ## to be added between the surrounding cusps. + + full_domain = False ## Says that we're not done yet! + + v = [False for r in range(sP)] + ## This initializes a list indexed by P^1(Z/NZ) which keeps track of + ## which right coset representatives we've found for Gamma_0(N)/SL_2(Z) + ## thru the construction of a fundamental domain + + ## Includeds the coset repns formed by the original ideal triangle + ## (with corners at -1, 0, infty) + + v[P.index(0,1)] = True + v[P.index(1,-1)] = True + v[P.index(-1,0)] = True + + + ## Main Loop -- Ideal Triangle Flipping + ## ==================================== + while (not full_domain): + full_domain = True + + ## This loop runs through the current set of cusps + ## and checks to see if more cusps should be added + ## ----------------------------------------------- + for s in range(1, len(C), 2): ## range over odd indices in the + ## final list C + if C[s] == "?": + + ## Single out our two cusps (path from cusp2 to cusp1) + cusp1 = C[s-1] + cusp2 = C[s+1] + + ## Makes the unimodular transform for the path from cusp2 + ## to cusp1 + + b1 = cusp1.denominator() + b2 = cusp2.denominator() + + ## This is the point where it is determined whether + ## or not the adjacent triangle should be added + ## ------------------------------------------------ + pos = P.index(b2,b1) ## The Sage index of the bottom + ## row of our unimodular + ## transformation gam + + ## Check if we need to flip (since this P1 element has not + ## yet been accounted for!) + if v[pos] == False: + v[pos] = True ## Say this P1 element now occurs + v[P.index(b1,-(b1+b2))] = True ## Say that the other + ## two ideal triangle + ## edges also occur! + v[P.index(-(b1+b2),b2)] = True + + ## Check to see if this triangle contains a fixed + ## point by an element of Gamma_0(N). If such an + ## element is present, the fundamental domain can be + ## extended no further. + + if (b1**2 + b2**2 + b1*b2)%N != 0: + + ## this congruence is exactly equivalent to + ## gam * [0 -1; 1 -1] * gam^(-1) is in Gamma_0(N) + ## where gam is the matrix corresponding to the + ## unimodular path connecting cusp1 to cusp2 + + C[s] = "i" ## The '?' is changed to an 'i' + ## indicating that a new cusp needs to + ## be inserted here + full_domain = False + else: + C[s] = "x" ## The '?' is changed to an 'x' and no + ## more checking below is needed! =) + else: + C[s] = "x" ## The '?' is changed to an 'x' and no more + ## checking below is needed! =) + + + ## Now insert the missing cusps (where there is an 'i' in the + ## final list) + ## This will keep the fundamental domain as flat as possible! + ## --------------------------------------------------------------- + + s=1 + while s < len(C): ## range over odd indices in the final list C + if C[s] == "i": + C[s]="?" + + ## Single out our two cusps (path from cusp2 to cusp1) + cusp1 = C[s-1] + cusp2 = C[s+1] + + ## Makes the unimodular transform for the path from cusp2 + ## to cusp1 + a1 = cusp1.numerator() + b1 = cusp1.denominator() + a2 = cusp2.numerator() + b2 = cusp2.denominator() + + ## Inserts the Farey center of these two cusps! + a = a1 + a2 + b = b1 + b2 + C.insert(s+1, a/b) + C.insert(s+2, "?") + s = s+2 + s = s+2 + + ## Remove the (now superfluous) extra string characters that appear + ## in the odd list entries + C = [QQ(C[s]) for s in range(0,len(C),2)] + return C + + def is_unimodular_path(self, r1, r2): + r""" + Determines whether two (non-infinite) cusps are connected by a + unimodular path. + + INPUT: + + - ``r1, r2`` -- rational numbers + + OUTPUT: + + A boolean expressing whether or not a unimodular path connects ``r1`` + to ``r2``. + + EXAMPLES:: + + sage: A = ManinRelations(11) + sage: A.is_unimodular_path(0,1/3) + True + sage: A.is_unimodular_path(1/3,0) + True + sage: A.is_unimodular_path(0,2/3) + False + sage: A.is_unimodular_path(2/3,0) + False + + """ + a = r1.numerator() + b = r2.numerator() + c = r1.denominator() + d = r2.denominator() + return (a*d - b*c)**2 == 1 + + def unimod_to_matrices(self, r1, r2): + r""" + Returns the two matrices whose associated unimodular paths connect + ``r1`` and ``r2`` and ``r2`` and ``r1``, respectively. + + INPUT: + + - ``r1, r2`` -- rational numbers (that are assumed to be connected by a + unimodular path) + + OUTPUT: + + A pair of 2x2 matrices of determinant 1 + + EXAMPLES:: + + sage: A = ManinRelations(11) + sage: A.unimod_to_matrices(0,1/3) + ( + [ 0 1] [1 0] + [-1 3], [3 1] + ) + + """ + a = r1.numerator() + b = r2.numerator() + c = r1.denominator() + d = r2.denominator() + if (a*d-b*c)==1: + ans = M2Z([a,b,c,d]), M2Z([-b,a,-d,c]) + else: + ans = M2Z([-a,b,-c,d]), M2Z([b,a,d,c]) + return ans + + def fd_boundary(self,C): + r""" + Finds matrices whose associated unimodular paths give the + boundary of a fundamental domain. + + Here the fundamental domain is for `\Gamma_0(N)`. (In the + case when `\Gamma_0(N)` has elements of order three the shape + cut out by these unimodular matrices is a little smaller than + a fundamental domain. See Section 2.5 of [PS2011].) + + INPUT: + + - ``C`` -- a list of rational numbers coming from + ``self.form_list_of_cusps()`` + + OUTPUT: + + A list of 2x2 integer matrices of determinant 1 whose associated + unimodular paths give the boundary of a fundamental domain for + `Gamma_0(N)` (or nearly so in the case of 3-torsion). + + EXAMPLES:: + + sage: A = ManinRelations(11) + sage: C = A.form_list_of_cusps(); C + [-1, -2/3, -1/2, -1/3, 0] + sage: A.fd_boundary(C) + [ + [1 0] [ 1 1] [ 0 -1] [-1 -1] [-1 -2] [-2 -1] + [0 1], [-1 0], [ 1 3], [ 3 2], [ 2 3], [ 3 1] + ] + sage: A = ManinRelations(13) + sage: C = A.form_list_of_cusps(); C + [-1, -2/3, -1/2, -1/3, 0] + sage: A.fd_boundary(C) + [ + [1 0] [ 1 1] [ 0 -1] [-1 -1] [-1 -2] [-2 -1] + [0 1], [-1 0], [ 1 3], [ 3 2], [ 2 3], [ 3 1] + ] + sage: A = ManinRelations(101) + sage: C = A.form_list_of_cusps(); C + [-1, -6/7, -5/6, -4/5, -7/9, -3/4, -11/15, -8/11, -5/7, -7/10, -9/13, -2/3, -5/8, -13/21, -8/13, -3/5, -7/12, -11/19, -4/7, -1/2, -4/9, -3/7, -5/12, -7/17, -2/5, -3/8, -4/11, -1/3, -2/7, -3/11, -1/4, -2/9, -1/5, -1/6, 0] + sage: A.fd_boundary(C) + [ + [1 0] [ 1 1] [ 0 -1] [-1 -1] [-1 -2] [-2 -1] [-1 -3] [-3 -2] + [0 1], [-1 0], [ 1 6], [ 6 5], [ 5 9], [ 9 4], [ 4 11], [11 7], + + [-2 -1] [-1 -4] [-4 -3] [-3 -2] [-2 -7] [-7 -5] [-5 -3] [-3 -4] + [ 7 3], [ 3 11], [11 8], [ 8 5], [ 5 17], [17 12], [12 7], [ 7 9], + + [-4 -1] [-1 -4] [ -4 -11] [-11 -7] [-7 -3] [-3 -8] [ -8 -13] + [ 9 2], [ 2 7], [ 7 19], [ 19 12], [12 5], [ 5 13], [ 13 21], + + [-13 -5] [-5 -2] [-2 -9] [-9 -7] [-7 -5] [-5 -8] [ -8 -11] + [ 21 8], [ 8 3], [ 3 13], [13 10], [10 7], [ 7 11], [ 11 15], + + [-11 -3] [-3 -7] [-7 -4] [-4 -5] [-5 -6] [-6 -1] + [ 15 4], [ 4 9], [ 9 5], [ 5 6], [ 6 7], [ 7 1] + ] + + """ + C.reverse() ## Reverse here to get clockwise orientation of boundary + + ## These matrices correspond to the paths from infty to 0 and -1 to infty + mats = [Id, minone_inf_path] + + ## Now find SL_2(Z) matrices whose associated unimodular paths connect + ## the cusps listed in C. + ## -------------------------------------------------------- + for j in range(len(C)-1): + a = C[j].numerator() + b = C[j+1].numerator() + c = C[j].denominator() + d = C[j+1].denominator() + new_mat = M2Z([a,b,c,d]) + mats.append(new_mat) + + return mats + + @cached_method + def prep_hecke_on_gen(self, l, gen, modulus = None): + r""" + This function does some precomputations needed to compute `T_l`. + + In particular, if `phi` is a modular symbol and `D_m` is the divisor + associated to the generator ``gen``, to compute `(\phi|T_{l})(D_m)` one + needs to compute `\phi(\gamma_a D_m)|\gamma_a` where `\gamma_a` runs + through the `l+1` matrices defining `T_l`. One + then takes `\gamma_a D_m` and writes it as a sum of unimodular + divisors. For each such unimodular divisor, say `[M]` where `M` is a + `SL_2` matrix, we then write `M=\gamma*h` where `\gamma` is in + `\Gamma_0(N)` and `h` is one of our chosen coset representatives. Then + `\phi([M]) = \phi([h]) | `\gamma^{-1}`. Thus, one has + + .. MATH:: + + (\phi | \gamma_a)(D_m) = \sum_h \sum_j \phi([h]) | \gamma_{hj}^(-1) * \gamma_a + + as `h` runs over all coset representatives and `j` simply runs over + however many times `M_h` appears in the above computation. + + Finally, the output of this function is a dictionary ``D`` whose keys are + the coset representatives in ``self.reps()`` where each value is a list + of matrices, and the entries of ``D`` satisfy: + + .. MATH:: + + D[h][j] = \gamma_{hj} * \gamma_a + + INPUT: + + - ``l`` -- a prime + - ``gen`` -- a generator + + OUTPUT: + + A list of lists (see above). + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi.values() + [-1/5, 3/2, -1/2] + sage: M = phi.parent().source() + sage: M.prep_hecke_on_gen(2, M.gens()[0]) + {[ 1 0] + [-1 1]: [], [1 0] + [0 1]: [[1 0] + [0 2], [1 1] + [0 2], [2 0] + [0 1]], [ 1 -1] + [-1 2]: [[ 1 -1] + [ 0 2]], [ 1 0] + [-2 1]: [], [ 0 -1] + [ 1 1]: [], [-1 -2] + [ 2 3]: [], [ 0 -1] + [ 1 3]: [], [-1 -1] + [ 2 1]: [], [ 0 -1] + [ 1 2]: [], [-2 -1] + [ 3 1]: [], [ 1 1] + [-1 0]: [], [-1 -1] + [ 3 2]: []} + + """ + N = self.level() + SN = Sigma0(N) + + ans = {} + # this will be the dictionary D above enumerated by coset reps + + # This loop will run thru the l+1 (or l) matrices + # defining T_l of the form [1, a, 0, l] and carry out the + # computation described above. + # ------------------------------------- + for a in range(l + 1): + if ((a < l) or (N % l != 0)) and (modulus is None or a%l == modulus%l): + # if the level is not prime to l the matrix [l, 0, 0, 1] is avoided. + gamma = basic_hecke_matrix(a, l) + t = gamma * gen + # In the notation above this is gam_a * D_m + from manin_map import unimod_matrices_to_infty, unimod_matrices_from_infty + v = unimod_matrices_from_infty(t[0, 0], t[1, 0]) + unimod_matrices_to_infty(t[0, 1], t[1, 1]) + # This expresses t as a sum of unimodular divisors + + # This loop runs over each such unimodular divisor + # ------------------------------------------------ + for A in v: + # B is the coset rep equivalent to A + B = self.equivalent_rep(A) + # gaminv = B*A^(-1) + gaminv = B * A.inverse() + # The matrix gaminv * gamma is added to our list in the j-th slot + # (as described above) + tmp = SN(gaminv * gamma) + try: + ans[B].append(tmp) + except KeyError: + ans[B] = [tmp] + + return ans + + @cached_method + def prep_hecke_on_gen_list(self, l, gen, modulus = None): + r""" + Returns the precomputation to compute `T_l` in a way that speeds up the hecke calculation. + + Namely, returns a list of the form [h,A]. + + INPUT: + + - ``l`` -- a prime + - ``gen`` -- a generator + + OUTPUT: + + A list of lists (see above). + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi.values() + [-1/5, 3/2, -1/2] + sage: M = phi.parent().source() + sage: len(M.prep_hecke_on_gen_list(2, M.gens()[0])) + 4 + """ + ans = [] + for h,vh in self.prep_hecke_on_gen(l,gen,modulus = modulus).iteritems(): + ans.extend([(h,v) for v in vh]) + return ans + +def basic_hecke_matrix(a, l): + r""" + Returns the 2x2 matrix with entries ``[1, a, 0, l]`` if ``a=l``. + + INPUT: + + - `a` -- an integer or Infinity + - ``l`` -- a prime + + OUTPUT: + + A 2x2 matrix of determinant l + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.fund_domain import basic_hecke_matrix + sage: basic_hecke_matrix(0, 7) + [1 0] + [0 7] + sage: basic_hecke_matrix(5, 7) + [1 5] + [0 7] + sage: basic_hecke_matrix(7, 7) + [7 0] + [0 1] + sage: basic_hecke_matrix(19, 7) + [7 0] + [0 1] + sage: basic_hecke_matrix(infinity, 7) + [7 0] + [0 1] + + """ + if a < l: + return M2Z([1, a, 0, l]) + else: + return M2Z([l, 0, 0, 1]) diff --git a/src/sage/modular/pollack_stevens/manin_map.py b/src/sage/modular/pollack_stevens/manin_map.py new file mode 100644 index 00000000000..955181a6c5d --- /dev/null +++ b/src/sage/modular/pollack_stevens/manin_map.py @@ -0,0 +1,924 @@ +r""" +Represents maps from a set of right coset representatives to a coefficient module. + +This is a basic building block for implementing modular symbols, and provides basic arithmetic +and right action of matrices. + +EXAMPLES:: + +sage: E = EllipticCurve('11a') +sage: phi = E.PS_modular_symbol() +sage: phi +Modular symbol of level 11 with values in Sym^0 Q^2 +sage: phi.values() +[-1/5, 3/2, -1/2] + +sage: from sage.modular.pollack_stevens.manin_map import ManinMap, M2Z +sage: D = Distributions(0, 11, 10) +sage: MR = ManinRelations(11) +sage: data = {M2Z([1,0,0,1]):D([1,2]), M2Z([0,-1,1,3]):D([3,5]), M2Z([-1,-1,3,2]):D([1,1])} +sage: f = ManinMap(D, MR, data) +sage: f(M2Z([1,0,0,1])) +(1 + O(11^2), 2 + O(11)) + +sage: S = Symk(0,QQ) +sage: MR = ManinRelations(37) +sage: data = {M2Z([-2,-3,5,7]): S(0), M2Z([1,0,0,1]): S(0), M2Z([-1,-2,3,5]): S(0), M2Z([-1,-4,2,7]): S(1), M2Z([0,-1,1,4]): S(1), M2Z([-3,-1,7,2]): S(-1), M2Z([-2,-3,3,4]): S(0), M2Z([-4,-3,7,5]): S(0), M2Z([-1,-1,4,3]): S(0)} +sage: f = ManinMap(S,MR,data) +sage: f(M2Z([2,3,4,5])) +1 + +""" + +#***************************************************************************** +# Copyright (C) 2012 Robert Pollack +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.rings.arith import convergents +from sage.misc.misc import verbose +from sage.matrix.matrix_integer_2x2 import MatrixSpace_ZZ_2x2, Matrix_integer_2x2 +from sigma0 import Sigma0,Sigma0Element +from fund_domain import t00, t10, t01, t11, Id, basic_hecke_matrix, M2Z +from sage.matrix.matrix_space import MatrixSpace +from sage.rings.integer_ring import ZZ +from sage.parallel.decorate import fork,parallel +from sage.modular.pollack_stevens.distributions import Distributions +from sys import stdout +from operator import methodcaller +from sage.structure.sage_object import load + +def fast_dist_act(v,g,acting_matrix = None): + if g is not None and g == 1: + ans = v._moments + try: + if acting_matrix is None: + ans = v._moments.apply_map(methodcaller('lift')) * v.parent().acting_matrix(g,len(v._moments)) + else: + ans = v._moments.apply_map(methodcaller('lift')) * acting_matrix + except AttributeError, TypeError: + ans = (v * g)._moments + assert len(ans) > 0 + return ans + +@parallel +def f_par(mmap,v,g): + try: + return sum((fast_dist_act(mmap[h],A) for h,A in v)) + except TypeError: + return sum((mmap[h] * A for h,A in v)) + + +def unimod_matrices_to_infty(r, s): + r""" + Return a list of matrices whose associated unimodular paths connect `0` to ``r/s``. + + INPUT: + + - ``r``, ``s`` -- rational numbers + + OUTPUT: + + - a list of matrices in `SL_2(\ZZ)` + + EXAMPLES:: + + sage: v = sage.modular.pollack_stevens.manin_map.unimod_matrices_to_infty(19,23); v + [ + [1 0] [ 0 1] [1 4] [-4 5] [ 5 19] + [0 1], [-1 1], [1 5], [-5 6], [ 6 23] + ] + sage: [a.det() for a in v] + [1, 1, 1, 1, 1] + + sage: sage.modular.pollack_stevens.manin_map.unimod_matrices_to_infty(11,25) + [ + [1 0] [ 0 1] [1 3] [-3 4] [ 4 11] + [0 1], [-1 2], [2 7], [-7 9], [ 9 25] + ] + + + ALGORITHM: + + This is Manin's continued fraction trick, which gives an expression + `{0,r/s} = {0,\infty} + ... + {a,b} + ... + {*,r/s}`, where each `{a,b}` is + the image of `{0,\infty}` under a matrix in `SL_2(\ZZ)`. + + """ + if s == 0: + return [] + # the function contfrac_q in + # https://github.com/williamstein/psage/blob/master/psage/modform/rational/modular_symbol_map.pyx + # is very, very relevant to massively optimizing this. + L = convergents(r / s) + # Computes the continued fraction convergents of r/s + v = [M2Z([1, L[0].numerator(), 0, L[0].denominator()])] + # Initializes the list of matrices + for j in range(0, len(L)-1): + a = L[j].numerator() + c = L[j].denominator() + b = L[j + 1].numerator() + d = L[j + 1].denominator() + v.append(M2Z([(-1)**(j + 1) * a, b, (-1)**(j + 1) * c, d])) + # The matrix connecting two consecutive convergents is added on + return v + + +def unimod_matrices_from_infty(r, s): + r""" + Return a list of matrices whose associated unimodular paths connect `\infty` to ``r/s``. + + INPUT: + + - ``r``, ``s`` -- rational numbers + + OUTPUT: + + - a list of `SL_2(\ZZ)` matrices + + EXAMPLES:: + + sage: v = sage.modular.pollack_stevens.manin_map.unimod_matrices_from_infty(19,23); v + [ + [ 0 1] [-1 0] [-4 1] [-5 -4] [-19 5] + [-1 0], [-1 -1], [-5 1], [-6 -5], [-23 6] + ] + sage: [a.det() for a in v] + [1, 1, 1, 1, 1] + + sage: sage.modular.pollack_stevens.manin_map.unimod_matrices_from_infty(11,25) + [ + [ 0 1] [-1 0] [-3 1] [-4 -3] [-11 4] + [-1 0], [-2 -1], [-7 2], [-9 -7], [-25 9] + ] + + + ALGORITHM: + + This is Manin's continued fraction trick, which gives an expression + `{\infty,r/s} = {\infty,0} + ... + {a,b} + ... + {*,r/s}`, where each + `{a,b}` is the image of `{0,\infty}` under a matrix in `SL_2(\ZZ)`. + + """ + if s != 0: + L = convergents(r / s) + # Computes the continued fraction convergents of r/s + v = [M2Z([-L[0].numerator(), 1, -L[0].denominator(), 0])] + # Initializes the list of matrices + # the function contfrac_q in https://github.com/williamstein/psage/blob/master/psage/modform/rational/modular_symbol_map.pyx + # is very, very relevant to massively optimizing this. + for j in range(0, len(L) - 1): + a = L[j].numerator() + c = L[j].denominator() + b = L[j + 1].numerator() + d = L[j + 1].denominator() + v.append(M2Z([-b, (-1)**(j + 1) * a, -d, (-1)**(j + 1) * c])) + # The matrix connecting two consecutive convergents is added on + return v + else: + return [] + +class ManinMap(object): + r""" + Map from a set of right coset representatives of `\Gamma_0(N)` in + `SL_2(\ZZ)` to a coefficient module that satisfies the Manin + relations. + """ + def __init__(self, codomain, manin_relations, defining_data, check=True): + """ + INPUT: + + - ``codomain`` -- coefficient module + - ``manin_relations`` -- a ManinRelations object + - ``defining_data`` -- a dictionary whose keys are a superset of + manin_relations.gens() and a subset of manin_relations.reps(), + and whose values are in the codomain. + - ``check`` -- do numerous (slow) checks and transformations to + ensure that the input data is perfect. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: D = Distributions(0, 11, 10) + sage: manin = sage.modular.pollack_stevens.fund_domain.ManinRelations(11) + sage: data = {M2Z([1,0,0,1]):D([1,2]), M2Z([0,-1,1,3]):D([3,5]), M2Z([-1,-1,3,2]):D([1,1])} + sage: f = ManinMap(D, manin, data); f # indirect doctest + Map from the set of right cosets of Gamma0(11) in SL_2(Z) to Space of 11-adic distributions with k=0 action and precision cap 10 + sage: f(M2Z([1,0,0,1])) + (1 + O(11^2), 2 + O(11)) + + TESTS: + + Test that it fails gracefully on some bogus inputs:: + + sage: rels = ManinRelations(37) + sage: ManinMap(ZZ, rels, {}) + Traceback (most recent call last): + ... + ValueError: Codomain must have an action of Sigma0(N) + sage: ManinMap(Symk(0), rels, []) + Traceback (most recent call last): + ... + ValueError: length of defining data must be the same as number of Manin generators + """ + self._codomain = codomain + self._manin = manin_relations + if check: + if not codomain.get_action(Sigma0(manin_relations._N)): + raise ValueError("Codomain must have an action of Sigma0(N)") + self._dict = {} + if isinstance(defining_data, (list, tuple)): + if len(defining_data) != manin_relations.ngens(): + raise ValueError("length of defining data must be the same as number of Manin generators") + for i in xrange(len(defining_data)): + self._dict[manin_relations.gen(i)] = codomain(defining_data[i]) + elif isinstance(defining_data, dict): + for g in manin_relations.gens(): + self._dict[g] = codomain(defining_data[g]) + else: + # constant function + try: + c = codomain(defining_data) + except TypeError: + raise TypeError("unrecognized type %s for defining_data" % type(defining_data)) + g = manin_relations.gens() + self._dict = dict(zip(g, [c]*len(g))) + else: + self._dict = defining_data + + def extend_codomain(self, new_codomain, check=True): + r""" + Extend the codomain of self to new_codomain. There must be a valid conversion operation from the old to the new codomain. This is most often used for extension of scalars from `\QQ` to `\QQ_p`. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.manin_map import ManinMap, M2Z + sage: S = Symk(0,QQ) + sage: MR = ManinRelations(37) + sage: data = {M2Z([-2,-3,5,7]): S(0), M2Z([1,0,0,1]): S(0), M2Z([-1,-2,3,5]): S(0), M2Z([-1,-4,2,7]): S(1), M2Z([0,-1,1,4]): S(1), M2Z([-3,-1,7,2]): S(-1), M2Z([-2,-3,3,4]): S(0), M2Z([-4,-3,7,5]): S(0), M2Z([-1,-1,4,3]): S(0)} + sage: m = ManinMap(S, MR, data); m + Map from the set of right cosets of Gamma0(37) in SL_2(Z) to Sym^0 Q^2 + sage: m.extend_codomain(Symk(0, Qp(11))) + Map from the set of right cosets of Gamma0(37) in SL_2(Z) to Sym^0 Q_11^2 + """ + new_dict = {} + for g in self._manin.gens(): + new_dict[g] = new_codomain(self._dict[g]) + return ManinMap(new_codomain, self._manin, new_dict, check) + + def _compute_image_from_gens(self, B): + r""" + Compute image of ``B`` under ``self``. + + INPUT: + + - ``B`` -- generator of Manin relations. + + OUTPUT: + + - an element in the codomain of self (e.g. a distribution), the image of ``B`` under ``self``. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: D = Distributions(0, 11, 10) + sage: MR = ManinRelations(11) + sage: data = {M2Z([1,0,0,1]):D([1,2]), M2Z([0,-1,1,3]):D([3,5]), M2Z([-1,-1,3,2]):D([1,1])} + sage: f = ManinMap(D, MR, data) + sage: f._compute_image_from_gens(MR.reps()[1]) + (10 + 10*11 + O(11^2), 8 + O(11)) + """ + L = self._manin.relations(B) + # could raise KeyError if B is not a generator + if len(L) == 0: + t = self._codomain(0) + else: + c, A, g = L[0] + try: + mrep = self._manin.reps(g) + val = self._dict[mrep] + try: + g1 = self._codomain(fast_dist_act(val),A) + except TypeError: + g1 = val * A + + except ValueError: + print "%s is not in Sigma0" % A + t = g1 * c + for c, A, g in L[1:]: + try: + g1 = self._codomain(fast_dist_act(self._dict[self._manin.reps(g)],A)) + except TypeError: + g1 = self._dict[self._manin.reps(g)] * A + t += g1 * c + return t + + def __getitem__(self, B): + r""" + + Compute image of ``B`` under ``self``. + + INPUT: + + - ``B`` -- coset representative of Manin relations. + + OUTPUT: + + - an element in the codomain of self (e.g. a distribution), the image of ``B`` under ``self``. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: S = Symk(0,QQ) + sage: MR = ManinRelations(37); MR.gens() + [ + [1 0] [ 0 -1] [-1 -1] [-1 -2] [-2 -3] [-3 -1] [-1 -4] [-4 -3] + [0 1], [ 1 4], [ 4 3], [ 3 5], [ 5 7], [ 7 2], [ 2 7], [ 7 5], + + [-2 -3] + [ 3 4] + ] + + sage: data = {M2Z([-2,-3,5,7]): S(0), M2Z([1,0,0,1]): S(0), M2Z([-1,-2,3,5]): S(0), M2Z([-1,-4,2,7]): S(1), M2Z([0,-1,1,4]): S(1), M2Z([-3,-1,7,2]): S(-1), M2Z([-2,-3,3,4]): S(0), M2Z([-4,-3,7,5]): S(0), M2Z([-1,-1,4,3]): S(0)} + sage: D = Distributions(2, 37, 40) + sage: f = ManinMap(D, MR, data) + sage: f.__getitem__(MR.gens()[1]) + 1 + O(37) + sage: f.__getitem__(MR.gens()[3]) + 0 + sage: f.__getitem__(MR.gens()[5]) + 36 + O(37) + sage: f[MR.gens()[5]] + 36 + O(37) + + """ + try: + return self._dict[B] + except KeyError: + # To prevent memory overflow + return self._compute_image_from_gens(B) + # self._dict[B] = self._compute_image_from_gens(B) + # return self._dict[B] + + def compute_full_data(self): + r""" + Compute the values of self on all coset reps from its values on our generating set. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: S = Symk(0,QQ) + sage: MR = ManinRelations(37); MR.gens() + [ + [1 0] [ 0 -1] [-1 -1] [-1 -2] [-2 -3] [-3 -1] [-1 -4] [-4 -3] + [0 1], [ 1 4], [ 4 3], [ 3 5], [ 5 7], [ 7 2], [ 2 7], [ 7 5], + + [-2 -3] + [ 3 4] + ] + + sage: data = {M2Z([-2,-3,5,7]): S(0), M2Z([1,0,0,1]): S(0), M2Z([-1,-2,3,5]): S(0), M2Z([-1,-4,2,7]): S(1), M2Z([0,-1,1,4]): S(1), M2Z([-3,-1,7,2]): S(-1), M2Z([-2,-3,3,4]): S(0), M2Z([-4,-3,7,5]): S(0), M2Z([-1,-1,4,3]): S(0)} + sage: f = ManinMap(S,MR,data) + sage: len(f._dict) + 9 + sage: f.compute_full_data() + sage: len(f._dict) + 38 + """ + verbose('Computing full data...') + for B in self._manin.reps(): + if not self._dict.has_key(B): + self._dict[B] = self._compute_image_from_gens(B) + verbose('Done') + + def __add__(self, right): + r""" + Return sum self + right, where self and right are + assumed to have identical codomains and Manin relations. + + INPUT: + + - ``self`` and ``right`` -- two Manin maps with the same codomain and Manin relations. + + OUTPUT: + + - the sum of ``self`` and ``right`` -- a Manin map + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: D = Distributions(0, 11, 10); D + Space of 11-adic distributions with k=0 action and precision cap 10 + sage: manin = sage.modular.pollack_stevens.fund_domain.ManinRelations(11) + sage: data = {M2Z([1,0,0,1]):D([1,2]), M2Z([0,-1,1,3]):D([3,5]), M2Z([-1,-1,3,2]):D([1,1])} + sage: f = ManinMap(D, manin, data); f + Map from the set of right cosets of Gamma0(11) in SL_2(Z) to Space of 11-adic distributions with k=0 action and precision cap 10 + sage: f(M2Z([1,0,0,1])) + (1 + O(11^2), 2 + O(11)) + sage: f+f # indirect doctest + Map from the set of right cosets of Gamma0(11) in SL_2(Z) to Space of 11-adic distributions with k=0 action and precision cap 10 + sage: (f+f)(M2Z([1,0,0,1])) + (2 + O(11^2), 4 + O(11)) + """ + D = {} + sd = self._dict + rd = right._dict + for ky, val in sd.iteritems(): + if ky in rd: + D[ky] = val + rd[ky] + return self.__class__(self._codomain, self._manin, D, check=False) + + def __sub__(self, right): + """ + Return difference self - right, where self and right are + assumed to have identical codomains and Manin relations. + + INPUT: + + - ``self`` and ``right`` -- two Manin maps with the same codomain and Manin relations. + + OUTPUT: + + - the difference of ``self`` and ``right`` -- a Manin map + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: D = Distributions(0, 11, 10); D + Space of 11-adic distributions with k=0 action and precision cap 10 + sage: manin = sage.modular.pollack_stevens.fund_domain.ManinRelations(11) + sage: data = {M2Z([1,0,0,1]):D([1,2]), M2Z([0,-1,1,3]):D([3,5]), M2Z([-1,-1,3,2]):D([1,1])} + sage: f = ManinMap(D, manin, data); f + Map from the set of right cosets of Gamma0(11) in SL_2(Z) to Space of 11-adic distributions with k=0 action and precision cap 10 + sage: f(M2Z([1,0,0,1])) + (1 + O(11^2), 2 + O(11)) + sage: f-f + Map from the set of right cosets of Gamma0(11) in SL_2(Z) to Space of 11-adic distributions with k=0 action and precision cap 10 + sage: (f-f)(M2Z([1,0,0,1])) + (0, 0) + + """ + D = {} + sd = self._dict + rd = right._dict + for ky, val in sd.iteritems(): + if ky in rd: + D[ky] = val - rd[ky] + return self.__class__(self._codomain, self._manin, D, check=False) + + def __mul__(self, right): + """ + Return scalar multiplication self * right, where right is in the + base ring of the codomain. + + INPUT: + + - ``self`` -- a Manin map. + - ``right`` -- an element of the base ring of the codomain of self. + + OUTPUT: + + - the sum ``self`` and ``right`` -- a Manin map + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: D = Distributions(0, 11, 10) + sage: manin = sage.modular.pollack_stevens.fund_domain.ManinRelations(11) + sage: data = {M2Z([1,0,0,1]):D([1,2]), M2Z([0,-1,1,3]):D([3,5]), M2Z([-1,-1,3,2]):D([1,1])} + sage: f = ManinMap(D, manin, data) + sage: f(M2Z([1,0,0,1])) + (1 + O(11^2), 2 + O(11)) + sage: f*2 + Map from the set of right cosets of Gamma0(11) in SL_2(Z) to Space of 11-adic distributions with k=0 action and precision cap 10 + sage: (f*2)(M2Z([1,0,0,1])) + (2 + O(11^2), 4 + O(11)) + """ +# if isinstance(right, Matrix_integer_2x2): + if isinstance(right, type(Sigma0(self._manin.level())(MatrixSpace(ZZ,2,2)([1,0,0,1])))): + return self._right_action(right) + + D = {} + sd = self._dict + for ky, val in sd.iteritems(): + D[ky] = val * right + return self.__class__(self._codomain, self._manin, D, check=False) + + def __repr__(self): + """ + Return print representation of self. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: D = Distributions(0, 11, 10) + sage: manin = sage.modular.pollack_stevens.fund_domain.ManinRelations(11) + sage: data = {M2Z([1,0,0,1]):D([1,2]), M2Z([0,-1,1,3]):D([3,5]), M2Z([-1,-1,3,2]):D([1,1])} + sage: f = ManinMap(D, manin, data) + sage: f.__repr__() + 'Map from the set of right cosets of Gamma0(11) in SL_2(Z) to Space of 11-adic distributions with k=0 action and precision cap 10' + + """ + return "Map from the set of right cosets of Gamma0(%s) in SL_2(Z) to %s"%( + self._manin.level(), self._codomain) + + def _eval_sl2(self, A): + r""" + Return the value of self on the unimodular divisor corresponding to `A`. + + Note that `A` must be in `SL_2(Z)` for this to work. + + INPUT: + + - ``A`` -- an element of `SL_2(Z)` + + OUTPUT: + + The value of self on the divisor corresponding to `A` -- i.e. on the divisor `{A(0)} - {A(\infty)}`. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: D = Distributions(0, 11, 10) + sage: MR = ManinRelations(11) + sage: data = {M2Z([1,0,0,1]):D([1,2]), M2Z([0,-1,1,3]):D([3,5]), M2Z([-1,-1,3,2]):D([1,1])} + sage: f = ManinMap(D, MR, data) + sage: A = MR.reps()[1] + sage: f._eval_sl2(A) + (10 + 10*11 + O(11^2), 8 + O(11)) + + """ + SN = Sigma0(self._manin._N) + A = M2Z(A) + B = self._manin.equivalent_rep(A) + gaminv = SN(B * M2Z(A).inverse()) + return self[B] * gaminv + + def __call__(self, A): + """ + Evaluate self at A. + + INPUT: + + - ``A`` -- a 2x2 matrix + + OUTPUT: + + The value of self on the divisor corresponding to A -- an element of the codomain of self. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: D = Distributions(0, 11, 10); D + Space of 11-adic distributions with k=0 action and precision cap 10 + sage: manin = sage.modular.pollack_stevens.fund_domain.ManinRelations(11) + sage: data = {M2Z([1,0,0,1]):D([1,2]), M2Z([0,-1,1,3]):D([3,5]), M2Z([-1,-1,3,2]):D([1,1])} + sage: f = ManinMap(D, manin, data); f + Map from the set of right cosets of Gamma0(11) in SL_2(Z) to Space of 11-adic distributions with k=0 action and precision cap 10 + sage: f(M2Z([1,0,0,1])) + (1 + O(11^2), 2 + O(11)) + + sage: S = Symk(0,QQ) + sage: MR = ManinRelations(37) + sage: data = {M2Z([-2,-3,5,7]): S(0), M2Z([1,0,0,1]): S(0), M2Z([-1,-2,3,5]): S(0), M2Z([-1,-4,2,7]): S(1), M2Z([0,-1,1,4]): S(1), M2Z([-3,-1,7,2]): S(-1), M2Z([-2,-3,3,4]): S(0), M2Z([-4,-3,7,5]): S(0), M2Z([-1,-1,4,3]): S(0)} + sage: f = ManinMap(S,MR,data) + sage: f(M2Z([2,3,4,5])) + 1 + + """ + a = A[t00] + b = A[t01] + c = A[t10] + d = A[t11] + # v1: a list of unimodular matrices whose divisors add up to {b/d} - {infty} + v1 = unimod_matrices_to_infty(b,d) + # v2: a list of unimodular matrices whose divisors add up to {a/c} - {infty} + v2 = unimod_matrices_to_infty(a,c) + # ans: the value of self on A + ans = self._codomain(0) + # This loop computes self({b/d}-{infty}) by adding up the values of self on elements of v1 + for B in v1: + ans = ans + self._eval_sl2(B) + + # This loops subtracts away the value self({a/c}-{infty}) from ans by subtracting away the values of self on elements of v2 + # and so in the end ans becomes self({b/d}-{a/c}) = self({A(0)} - {A(infty)} + for B in v2: + ans = ans - self._eval_sl2(B) + return ans + + def apply(self, f, codomain=None, to_moments=False): + r""" + Return Manin map given by `x \mapsto f(self(x))`, where `f` is + anything that can be called with elements of the coefficient + module. + + This might be used to normalize, reduce modulo a prime, change + base ring, etc. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: S = Symk(0,QQ) + sage: MR = ManinRelations(37) + sage: data = {M2Z([-2,-3,5,7]): S(0), M2Z([1,0,0,1]): S(0), M2Z([-1,-2,3,5]): S(0), M2Z([-1,-4,2,7]): S(1), M2Z([0,-1,1,4]): S(1), M2Z([-3,-1,7,2]): S(-1), M2Z([-2,-3,3,4]): S(0), M2Z([-4,-3,7,5]): S(0), M2Z([-1,-1,4,3]): S(0)} + sage: f = ManinMap(S,MR,data) + sage: list(f.apply(lambda t:2*t)) + [0, 2, 0, 0, 0, -2, 2, 0, 0] + + """ + D = {} + sd = self._dict + if codomain is None: + codomain = self._codomain + for ky, val in sd.iteritems(): + if to_moments: + D[ky] = codomain([f(val.moment(a)) for a in range(val.precision_absolute())]) + else: + D[ky] = f(val) + return self.__class__(codomain, self._manin, D, check=False) + + def __iter__(self): + r""" + Return iterator over the values of this map on the reduced + representatives. + + This might be used to compute the valuation. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: S = Symk(0,QQ) + sage: MR = ManinRelations(37) + sage: data = {M2Z([-2,-3,5,7]): S(0), M2Z([1,0,0,1]): S(0), M2Z([-1,-2,3,5]): S(0), M2Z([-1,-4,2,7]): S(1), M2Z([0,-1,1,4]): S(1), M2Z([-3,-1,7,2]): S(-1), M2Z([-2,-3,3,4]): S(0), M2Z([-4,-3,7,5]): S(0), M2Z([-1,-1,4,3]): S(0)} + sage: f = ManinMap(S,MR,data) + sage: [a for a in f] + [0, 1, 0, 0, 0, -1, 1, 0, 0] + + """ + for A in self._manin.gens(): + yield self._dict[A] + + def _right_action(self, gamma): + r""" + Return self | gamma, where gamma is a 2x2 integer matrix. + + The action is defined by `(self | gamma)(D) = self(gamma D)|gamma` + + For the action by a single element gamma to be a modular symbol, gamma + must normalize `\Gamma_0(N)`. However, this right action + can also be used to define Hecke operators, in which case each + individual self | gamma is not a modular symbol on `\Gamma_0(N)`, but + the sum over acting by the appropriate double coset representatives is. + + INPUT: + + - ``gamma`` - 2x2 integer matrix of nonzero determinant, with a + well-defined action on the coefficient module + + OUTPUT: + + - the image of self under the action of gamma -- a Manin map. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import ManinMap, M2Z, Sigma0 + sage: S01 = Sigma0(1) + sage: f = Newforms(7, 4)[0] + sage: f.modular_symbols(1) + Modular Symbols subspace of dimension 1 of Modular Symbols space of dimension 3 for Gamma_0(7) of weight 4 with sign 1 over Rational Field + sage: phi = f.PS_modular_symbol()._map + sage: psi = phi._right_action(S01([2,3,4,5])); psi + Map from the set of right cosets of Gamma0(7) in SL_2(Z) to Sym^2 Q^2 + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_simple_modsym_space + sage: M = ModularSymbols(17,4,1).cuspidal_subspace() + sage: A = M.decomposition() + sage: f = ps_modsym_from_simple_modsym_space(A[0])._map + sage: g = f._right_action(S01([1,2,0,1])) + sage: g + Map from the set of right cosets of Gamma0(17) in SL_2(Z) to Sym^2 Q^2 + + sage: x = sage.modular.pollack_stevens.fund_domain.M2Z([2,3,1,0]) + sage: g(x) + (17, -34, 69) + + """ + D = {} + sd = self._dict + # we should eventually replace the for loop with a call to apply_many + keys = [ky for ky in sd.iterkeys()] + for ky in keys: + try: + D[ky] = self._codomain(fast_dist_act(self(gamma*ky),gamma)) + except TypeError: + D[ky] = self(gamma*ky) * gamma + return self.__class__(self._codomain, self._manin, D, check=False) + + def normalize(self): + r""" + Normalize every value of self -- e.g., reduces each value's + `j`-th moment modulo `p^(N-j)` + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: D = Distributions(0, 11, 10) + sage: manin = sage.modular.pollack_stevens.fund_domain.ManinRelations(11) + sage: data = {M2Z([1,0,0,1]):D([1,2]), M2Z([0,-1,1,3]):D([3,5]), M2Z([-1,-1,3,2]):D([1,1])} + sage: f = ManinMap(D, manin, data) + sage: f._dict[M2Z([1,0,0,1])] + (1 + O(11^2), 2 + O(11)) + sage: g = f.normalize() + sage: g._dict[M2Z([1,0,0,1])] + (1 + O(11^2), 2 + O(11)) + + """ + sd = self._dict + for val in sd.itervalues(): + val.normalize() + return self + + def reduce_precision(self, M): + r""" + Reduce the precision of all the values of the Manin map. + + INPUT: + + - ``M`` -- an integer, the new precision. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: D = Distributions(0, 11, 10) + sage: manin = sage.modular.pollack_stevens.fund_domain.ManinRelations(11) + sage: data = {M2Z([1,0,0,1]):D([1,2]), M2Z([0,-1,1,3]):D([3,5]), M2Z([-1,-1,3,2]):D([1,1])} + sage: f = ManinMap(D, manin, data) + sage: f._dict[M2Z([1,0,0,1])] + (1 + O(11^2), 2 + O(11)) + sage: g = f.reduce_precision(1) + sage: g._dict[M2Z([1,0,0,1])] + 1 + O(11) + """ + D = {} + sd = self._dict + for ky, val in sd.iteritems(): + D[ky] = val.reduce_precision(M) + return self.__class__(self._codomain, self._manin, D, check=False) + + def specialize(self, *args): + r""" + Specializes all the values of the Manin map to a new coefficient + module. Assumes that the codomain has a ``specialize`` method, and + passes all its arguments to that method. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.manin_map import M2Z, ManinMap + sage: D = Distributions(0, 11, 10) + sage: manin = sage.modular.pollack_stevens.fund_domain.ManinRelations(11) + sage: data = {M2Z([1,0,0,1]):D([1,2]), M2Z([0,-1,1,3]):D([3,5]), M2Z([-1,-1,3,2]):D([1,1])} + sage: f = ManinMap(D, manin, data) + sage: g = f.specialize() + sage: g._codomain + Sym^0 Z_11^2 + """ + D = {} + sd = self._dict + for ky, val in sd.iteritems(): + D[ky] = val.specialize(*args) + return self.__class__(self._codomain.specialize(*args), self._manin, D, check=False) + + def hecke(self, ell, algorithm = 'prep', _parallel = False, fname = None): + r""" + Return the image of this Manin map under the Hecke operator `T_{\ell}`. + + INPUT: + + - ``ell`` -- a prime + + - ``algorithm`` -- a string, either 'prep' (default) or + 'naive' + + OUTPUT: + + - The image of this ManinMap under the Hecke operator + `T_{\ell}` + + EXAMPLES: + + :: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi.values() + [-1/5, 3/2, -1/2] + sage: phi.is_Tq_eigensymbol(7,7,10) + True + sage: phi.hecke(7).values() + [2/5, -3, 1] + sage: phi.Tq_eigenvalue(7,7,10) + -2 + """ + verbose('parallel = %s'%_parallel) + self.compute_full_data() # Why? + self.normalize() # Why? + M = self._manin + + if algorithm == 'prep': + ## psi will denote self | T_ell + psi = {} + if _parallel: + input_vector = [(self,list(M.prep_hecke_on_gen_list(ell,g)),g) for g in M.gens()] + par_vector = f_par(input_vector) + for inp,outp in par_vector: + psi[inp[0][2]] = self._codomain(outp) + psi[inp[0][2]].normalize() + elif fname is not None: + import cPickle as pickle + for i in range(ell): + try: + print 'Loading %s/%s'%(i,ell) + data = pickle.load( open(fname+'_%s.sobj'%i) ) + #data load(fname + '_%s.sobj'%i) + print 'Done!!' + except MemoryError: + verbose('Memory error while loading file!') + raise MemoryError + for g in M.gens(): + mprep = data[g] #M.prep_hecke_on_gen_list(ell,g) + h,actmat = mprep[0] + psi_g = fast_dist_act( self[h],None,actmat ) + for h,actmat in mprep[1:]: + psi_g += fast_dist_act( self[h], None,actmat ) + psi_g = self._codomain(psi_g) + #psi_g = self._codomain(sum((fast_dist_act(self[h], A,actmat) for h,A,actmat in mprep),self._codomain(0)._moments)) + try: + psi[g] += psi_g + except KeyError: + psi[g] = psi_g + psi[g].normalize() + else: # The default, which should be used for most settings which do not strain memory. + for g in M.gens(): + try: + psi_g = self._codomain(sum((fast_dist_act(self[h], A) for h,A in M.prep_hecke_on_gen_list(ell,g)),self._codomain(0)._moments)) + except TypeError: + psi_g = sum((self[h] * A for h,A in M.prep_hecke_on_gen_list(ell,g)),self._codomain(0)) + psi_g.normalize() + psi[g] = psi_g + return self.__class__(self._codomain, self._manin, psi, check=False) + elif algorithm == 'naive': + S0N = Sigma0(self._manin.level()) + psi = self._right_action(S0N([1,0,0,ell])) + for a in range(1, ell): + psi += self._right_action(S0N([1,a,0,ell])) + if self._manin.level() % ell != 0: + psi += self._right_action(S0N([ell,0,0,1])) + return psi.normalize() + else: + raise ValueError,'Algorithm must be either "naive" or "prep"' + + def p_stabilize(self, p, alpha, V): + r""" + Return the `p`-stablization of self to level `N*p` on which `U_p` acts by `alpha`. + + INPUT: + + - ``p`` -- a prime. + + - ``alpha`` -- a `U_p`-eigenvalue. + + - ``V`` -- a space of modular symbols. + + OUTPUT: + + - The image of this ManinMap under the Hecke operator `T_{\ell}` + + EXAMPLES: + + :: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: f = phi._map + sage: V = phi.parent() + sage: f.p_stabilize(5,1,V) + Map from the set of right cosets of Gamma0(11) in SL_2(Z) to Sym^0 Q^2 + """ + manin = V.source() + S0 = Sigma0(self._codomain._act._Np) + pmat = S0([p,0,0,1]) + D = {} + scalar = 1/alpha + one = scalar.parent()(1) + for g in map(M2Z, manin.gens()): + # we use scale here so that we don't need to define a + # construction functor in order to scale by something + # outside the base ring. + D[g] = self._eval_sl2(g).scale(one) - (self(pmat * g) * pmat).scale(1/alpha) + return self.__class__(self._codomain.change_ring(scalar.parent()), manin, D, check=False) diff --git a/src/sage/modular/pollack_stevens/modsym.py b/src/sage/modular/pollack_stevens/modsym.py new file mode 100644 index 00000000000..9601ad1dc39 --- /dev/null +++ b/src/sage/modular/pollack_stevens/modsym.py @@ -0,0 +1,1546 @@ + +# Copyright (C) 2012 Robert Pollack +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +import operator + +from sage.structure.element import ModuleElement +from sage.matrix.matrix_integer_2x2 import MatrixSpace_ZZ_2x2 +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.misc.cachefunc import cached_method +from sage.rings.padics.factory import Qp +from sage.rings.polynomial.all import PolynomialRing +from sage.rings.padics.padic_generic import pAdicGeneric +from sage.rings.arith import next_prime +from sage.misc.misc import verbose +from sage.rings.padics.precision_error import PrecisionError + +from sage.categories.action import Action +from fund_domain import Id +from manin_map import ManinMap, M2Z +from padic_lseries import pAdicLseries +from sigma0 import Sigma0 +from sage.modular.pollack_stevens.distributions import Distributions +from sage.misc.misc import walltime +from sage.parallel.decorate import fork + +minusproj = [1,0,0,-1] + + +class PSModSymAction(Action): + def __init__(self, actor, MSspace): + Action.__init__(self, actor, MSspace, False, operator.mul) + + def _call_(self, sym, g): + return sym.__class__(sym._map * g, sym.parent(), construct=True) + +class PSModularSymbolElement(ModuleElement): + def __init__(self, map_data, parent, construct=False): + ModuleElement.__init__(self, parent) + if construct: + self._map = map_data + else: + self._map = ManinMap(parent._coefficients, parent._source, map_data) + + def _repr_(self): + r""" + Returns the print representation of the symbol. + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi._repr_() + 'Modular symbol of level 11 with values in Sym^0 Q^2' + """ + return "Modular symbol of level %s with values in %s"%(self.parent().level(),self.parent().coefficient_module()) + + def dict(self): + r""" + Returns dictionary on the modular symbol self, where keys are generators and values are the corresponding values of self on generators + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi.dict() + {[1 0] + [0 1]: -1/5, [ 0 -1] + [ 1 3]: 3/2, [-1 -1] + [ 3 2]: -1/2} + """ + D = {} + for g in self.parent().source().gens(): + D[g] = self._map[g] + return D + + def weight(self): + r""" + Returns the weight of this Pollack-Stevens modular symbol. + + This is `k-2`, where `k` is the usual notion of weight for modular + forms! + + EXAMPLES:: + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi.weight() + 0 + + """ + return self.parent().weight() + + def values(self): + r""" + Returns the values of the symbol self on our chosen generators (generators are listed in self.dict().keys()) + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi.values() + [-1/5, 3/2, -1/2] + sage: phi.dict().keys() + [ + [1 0] [ 0 -1] [-1 -1] + [0 1], [ 1 3], [ 3 2] + ] + sage: phi.values() == phi.dict().values() + True + """ + return [self._map[g] for g in self.parent().source().gens()] + + def _normalize(self): + """ + Normalizes all of the values of the symbol self + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi._normalize() + Modular symbol of level 11 with values in Sym^0 Q^2 + sage: phi._normalize().values() + [-1/5, 3/2, -1/2] + """ + for val in self._map: + val.normalize() + return self + + def __cmp__(self, other): + """ + Checks if self == other. Here self and other have the same parent. + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi == phi + True + sage: phi == 2*phi + False + sage: psi = ps_modsym_from_elliptic_curve(EllipticCurve('37a')) + sage: psi == phi + False + """ + gens = self.parent().source().gens() + for g in gens: + c = cmp(self._map[g], other._map[g]) + if c: return c + return 0 + + def _add_(self, right): + """ + Returns self + right + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E); phi.values() + [-1/5, 3/2, -1/2] + sage: phi + phi + Modular symbol of level 11 with values in Sym^0 Q^2 + sage: (phi + phi).values() + [-2/5, 3, -1] + """ + return self.__class__(self._map + right._map, self.parent(), construct=True) + + def _lmul_(self, right): + """ + Returns self * right + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E); phi.values() + [-1/5, 3/2, -1/2] + sage: 2*phi + Modular symbol of level 11 with values in Sym^0 Q^2 + sage: (2*phi).values() + [-2/5, 3, -1] + """ + return self.__class__(self._map * right, self.parent(), construct=True) + + def _rmul_(self, right): + """ + Returns self * right + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E); phi.values() + [-1/5, 3/2, -1/2] + sage: phi*2 + Modular symbol of level 11 with values in Sym^0 Q^2 + sage: (phi*2).values() + [-2/5, 3, -1] + """ + return self.__class__(self._map * right, self.parent(), construct=True) + + def _sub_(self, right): + """ + Returns self - right + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E); phi.values() + [-1/5, 3/2, -1/2] + sage: phi - phi + Modular symbol of level 11 with values in Sym^0 Q^2 + sage: (phi - phi).values() + [0, 0, 0] + """ + return self.__class__(self._map - right._map, self.parent(), construct=True) + + def _get_prime(self, p=None, alpha = None, allow_none=False): + """ + Combines a prime specified by the user with the prime from the parent. + + INPUT: + + - ``p`` -- an integer or None (default None); if specified + needs to match the prime of the parent. + + - ``alpha`` -- an element or None (default None); if p-adic + can contribute a prime. + + - ``allow_none`` -- boolean (default False); whether to allow + no prime to be specified. + + OUTPUT: + + - a prime or None. If ``allow_none`` is False then a + ValueError will be raised rather than returning None if no + prime can be determined. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Distributions, Symk + sage: D = Distributions(0, 5, 10); M = PSModularSymbols(Gamma0(5), coefficients=D) + sage: f = M(1); f._get_prime() + 5 + sage: f._get_prime(5) + 5 + sage: f._get_prime(7) + Traceback (most recent call last): + ... + ValueError: inconsistent prime + sage: f._get_prime(alpha=Qp(5)(1)) + 5 + sage: D = Symk(0); M = PSModularSymbols(Gamma0(2), coefficients=D) + sage: f = M(1); f._get_prime(allow_none=True) is None + True + sage: f._get_prime(alpha=Qp(7)(1)) + 7 + sage: f._get_prime(7,alpha=Qp(7)(1)) + 7 + sage: f._get_prime() + Traceback (most recent call last): + ... + ValueError: you must specify a prime + """ + pp = self.parent().prime() + ppp = ((alpha is not None) and hasattr(alpha.parent(),'prime') and alpha.parent().prime()) or None + p = ZZ(p) or pp or ppp + if not p: + if not allow_none: + raise ValueError("you must specify a prime") + elif (pp and p != pp) or (ppp and p != ppp): + raise ValueError("inconsistent prime") + return p + + def plus_part(self): + r""" + Returns the plus part of self -- i.e. self + self | [1,0,0,-1]. + + Note that we haven't divided by 2. Is this a problem? + + OUTPUT: + + - self + self | [1,0,0,-1] + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E); phi.values() + [-1/5, 3/2, -1/2] + sage: (phi.plus_part()+phi.minus_part()) == 2 * phi + True + """ + S0N = Sigma0(self.parent().level()) + return self + self * S0N(minusproj) + + def minus_part(self): + r""" + Returns the minus part of self -- i.e. self - self | [1,0,0,-1] + + Note that we haven't divided by 2. Is this a problem? + + OUTPUT: + + - self - self | [1,0,0,-1] + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E); phi.values() + [-1/5, 3/2, -1/2] + sage: (phi.plus_part()+phi.minus_part()) == phi * 2 + True + """ + S0N = Sigma0(self.parent().level()) + return self - self * S0N(minusproj) + + def hecke(self, ell, algorithm="prep", parallel = False,precomp_data = None): + r""" + Returns self | `T_{\ell}` by making use of the precomputations in + self.prep_hecke() + + INPUT: + + - ``ell`` -- a prime + + - ``algorithm`` -- a string, either 'prep' (default) or + 'naive' + + OUTPUT: + + - The image of this element under the hecke operator + `T_{\ell}` + + ALGORITHMS: + + - If ``algorithm == 'prep'``, precomputes a list of matrices + that only depend on the level, then uses them to speed up + the action. + + - If ``algorithm == 'naive'``, just acts by the matrices + defining the Hecke operator. That is, it computes + sum_a self | [1,a,0,ell] + self | [ell,0,0,1], + the last term occurring only if the level is prime to ell. + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E); phi.values() + [-1/5, 3/2, -1/2] + sage: phi.hecke(2) == phi * E.ap(2) + True + sage: phi.hecke(3) == phi * E.ap(3) + True + sage: phi.hecke(5) == phi * E.ap(5) + True + sage: phi.hecke(101) == phi * E.ap(101) + True + + sage: all([phi.hecke(p, algorithm='naive') == phi * E.ap(p) for p in [2,3,5,101]]) + True + """ + if precomp_data is not None: + return self.__class__(fork(self._map.hecke)(ell, algorithm, _parallel = parallel,fname = precomp_data), self.parent(), construct=True) + else: + return self.__class__(self._map.hecke(ell, algorithm, _parallel = parallel), self.parent(), construct=True) + + def valuation(self, p=None): + r""" + Returns the valuation of self at `p`. + + Here the valuation is the minimum of the valuations of the values of self. + + INPUT: + + - ``p`` - prime + + OUTPUT: + + - The valuation of self at `p` + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi.values() + [-1/5, 3/2, -1/2] + sage: phi.valuation(2) + -1 + sage: phi.valuation(3) + 0 + sage: phi.valuation(5) + -1 + sage: phi.valuation(7) + 0 + sage: phi.valuation() + Traceback (most recent call last): + ... + ValueError: you must specify a prime + + sage: phi2 = phi.lift(11, M=2) + sage: phi2.valuation() + 0 + sage: phi2.valuation(3) + Traceback (most recent call last): + ... + ValueError: inconsistent prime + sage: phi2.valuation(11) + 0 + """ + q = self._get_prime(p) + return min([val.valuation(q) for val in self._map]) + + def diagonal_valuation(self, p): + """ + Retuns the minimum of the diagonal valuation on the values of self + + INPUT: + + - ``p`` -- a positive integral prime + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi.values() + [-1/5, 3/2, -1/2] + sage: phi.diagonal_valuation(2) + -1 + sage: phi.diagonal_valuation(3) + 0 + sage: phi.diagonal_valuation(5) + -1 + sage: phi.diagonal_valuation(7) + 0 + """ + return min([val.diagonal_valuation(p) for val in self._map]) + + @cached_method + def is_Tq_eigensymbol(self,q,p=None,M=None): + r""" + Determines if self is an eigenvector for `T_q` modulo `p^M` + + INPUT: + + - ``q`` -- prime of the Hecke operator + + - ``p`` -- prime we are working modulo + + - ``M`` -- degree of accuracy of approximation + + OUTPUT: + + - True/False + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi.values() + [-1/5, 3/2, -1/2] + sage: phi_ord = phi.p_stabilize(p = 3, ap = E.ap(3), M = 10, ordinary = True) + sage: phi_ord.is_Tq_eigensymbol(2,3,10) + True + sage: phi_ord.is_Tq_eigensymbol(2,3,100) + False + sage: phi_ord.is_Tq_eigensymbol(2,3,1000) + False + sage: phi_ord.is_Tq_eigensymbol(3,3,10) + True + sage: phi_ord.is_Tq_eigensymbol(3,3,100) + False + """ + try: + aq = self.Tq_eigenvalue(q, p, M) + return True + except ValueError: + return False + + # what happens if a cached method raises an error? Is it recomputed each time? + @cached_method + def Tq_eigenvalue(self, q, p=None, M=None, check=True): + r""" + Eigenvalue of `T_q` modulo `p^M` + + INPUT: + + - ``q`` -- prime of the Hecke operator + + - ``p`` -- prime we are working modulo (default: None) + + - ``M`` -- degree of accuracy of approximation (default: None) + + - ``check`` -- check that `self` is an eigensymbol + + OUTPUT: + + - Constant `c` such that `self|T_q - c * self` has valuation greater than + or equal to `M` (if it exists), otherwise raises ValueError + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi.values() + [-1/5, 3/2, -1/2] + sage: phi_ord = phi.p_stabilize(p = 3, ap = E.ap(3), M = 10, ordinary = True) + sage: phi_ord.Tq_eigenvalue(2,3,10) + 2 + O(3^10) + + sage: phi_ord.Tq_eigenvalue(3,3,10) + 2 + 3^2 + 2*3^3 + 2*3^4 + 2*3^6 + 3^8 + 2*3^9 + O(3^10) + sage: phi_ord.Tq_eigenvalue(3,3,100) + Traceback (most recent call last): + ... + ValueError: not a scalar multiple + """ + qhecke = self.hecke(q) + gens = self.parent().source().gens() + if p is None: + p = self.parent().prime() + i = 0 + g = gens[i] + verbose("Computing eigenvalue") + while self._map[g].is_zero(p, M): + if not qhecke._map[g].is_zero(p, M): + raise ValueError("not a scalar multiple") + i += 1 + try: + g = gens[i] + except IndexError: + raise ValueError("self is zero") + aq = self._map[g].find_scalar(qhecke._map[g], p, M, check) + verbose("Found eigenvalues of %s"%(aq)) + if check: + verbose("Checking that this is actually an eigensymbol") + if p is None or M is None: + for g in gens[1:]: + if qhecke._map[g] != aq * self._map[g]: + raise ValueError("not a scalar multiple") + elif (qhecke - aq * self).valuation(p) < M: + raise ValueError("not a scalar multiple") + return aq + + def is_ordinary(self,p=None,P=None): + r""" + Returns true if the p-th eigenvalue is a p-adic unit. + + INPUT: + + - ``p`` - a positive integral prime, or None (default None) + - ``P`` - a prime of the base ring above `p`, or None. This is ignored + unless the base ring is a number field. + + OUTPUT: + + - True/False + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('11a1') + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi.is_ordinary(2) + False + sage: E.ap(2) + -2 + sage: phi.is_ordinary(3) + True + sage: E.ap(3) + -1 + sage: phip = phi.p_stabilize(3,20) + sage: phip.is_ordinary() + True + + A number field example. Here there are multiple primes above `p`, and + `\phi` is ordinary at one but not the other.:: + + sage: f = Newforms(32, 8, names='a')[1] + sage: K = f.hecke_eigenvalue_field() + sage: a = f[3] + sage: phi = f.PS_modular_symbol() + sage: phi.is_ordinary(K.ideal(3, 1/16*a + 3/2)) + False + sage: phi.is_ordinary(K.ideal(3, 1/16*a + 5/2)) + True + sage: phi.is_ordinary(3) + Traceback (most recent call last): + ... + TypeError: P must be an ideal + + """ + # q is the prime below p, if base is a number field; q = p otherwise + if p == None: + if self.parent().prime() == 0: + raise ValueError("need to specify a prime") + q = p = self.parent().prime() + elif p in ZZ: + q = p + else: + q = p.smallest_integer() + if not q.is_prime(): + raise ValueError("p is not prime") + if (self.parent().prime() != q) and (self.parent().prime() != 0): + raise ValueError("prime does not match coefficient module's prime") + aq = self.Tq_eigenvalue(q) + return aq.valuation(p) == 0 + + def _consistency_check(self): + """ + Check that the map really does satisfy the Manin relations loop (for debugging). + The two and three torsion relations are checked and it is checked that the symbol + adds up correctly around the fundamental domain + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('37a1') + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi._consistency_check() + This modular symbol satisfies the manin relations + + """ + + f = self._map + MR = self._map._manin + ## Test two torsion relations + for g in MR.reps_with_two_torsion(): + gamg = MR.two_torsion_matrix(g) + if not (f[g]*gamg + f[g]).is_zero(): + raise ValueError("Two torsion relation failed with",g) + + ## Test three torsion relations + for g in MR.reps_with_three_torsion(): + gamg = MR.three_torsion_matrix(g) + if not (f[g]*(gamg**2) + f[g]*gamg + f[g]).is_zero(): + raise ValueError("Three torsion relation failed with",g) + + ## Test that the symbol adds to 0 around the boundary of the fundamental domain + t = self.parent().coefficient_module().zero_element() + for g in MR.gens()[1:]: + if (not g in MR.reps_with_two_torsion()) and (not g in MR.reps_with_three_torsion()): + t += f[g] * MR.gammas[g] - f[g] + else: + if g in MR.reps_with_two_torsion(): + t -= f[g] + else: + t -= f[g] + + id = MR.gens()[0] + if f[id]*MR.gammas[id] - f[id] != -t: + print t + print f[id]*MR.gammas[id] - f[id] + raise ValueError("Does not add up correctly around loop") + + print "This modular symbol satisfies the manin relations" + +class PSModularSymbolElement_symk(PSModularSymbolElement): + def _find_M(self, M): + """ + Determines `M` from user input. ????? + + INPUT: + + - ``M`` -- an integer at least 2 or None. If None, sets `M` to + be one more than the precision cap of the parent (the + minimum amount of lifting). + + OUTPUT: + + - An updated ``M``. + + EXAMPLES:: + + sage: pass + """ + if M is None: + M = ZZ(20) + elif M <= 1: + raise ValueError("M must be at least 2") + else: + M = ZZ(M) + return M + + def _find_alpha(self, p, k, M=None, ap=None, new_base_ring=None, ordinary=True, check=True, find_extraprec=True): + r""" + Finds `alpha`, a `U_p` eigenvalue, which is found as a root of + the polynomial `x^2 - ap * x + p^(k+1)*chi(p)`. + + INPUT: + + - ``p`` -- prime + + - ``k`` -- Pollack-Stevens weight + + - ``M`` -- precision (default: None) of `Q_p` + + - ``ap`` -- Hecke eigenvalue at p (default: None) + + - ``new_base_ring`` -- field of definition of `alpha` (default: None) + + - ``ordinary`` -- True if the prime is ordinary (default: True) + + - ``check`` -- check to see if the prime is ordinary (default: True) + + - ``find_extraprec`` -- setting this to True finds extra precision (default: True) + + OUTPUT: + + The output is a tuple (`alpha`, `new_base_ring`, `newM`, `eisenloss`,`q`,`aq`), with + + - ``alpha`` -- `U_p` eigenvalue + + - ``new_base_ring`` -- field of definition of `alpha` with precision at least `newM` + + - ``newM`` -- new precision + + - ``eisenloss`` -- loss of precision + + - ``q`` -- a prime not equal to p which was used to find extra precision + + - ``aq`` -- the Hecke eigenvalue `aq` corresponding to `q` + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('11a') + sage: p = 5 + sage: M = 10 + sage: k = 0 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi._find_alpha(p,k,M) + (1 + 4*5 + 3*5^2 + 2*5^3 + 4*5^4 + 4*5^5 + 4*5^6 + 3*5^7 + 2*5^8 + 3*5^9 + 3*5^10 + 3*5^12 + O(5^13), 5-adic Field with capped relative precision 13, 12, 1, 2, -2) + """ + if ap is None: + ap = self.Tq_eigenvalue(p, check=check) + if check and ap.valuation(p) > 0: + raise ValueError("p is not ordinary") + + chi = self._map._codomain._character + if chi is not None: + eps = chi(p) + else: + eps = 1 + poly = PolynomialRing(ap.parent(), 'x')([p**(k+1) * eps, -ap, 1]) + if new_base_ring is None: + # These should actually be completions of disc.parent() + if p == 2: + # is this the right precision adjustment for p=2? + new_base_ring = Qp(2, M+1) + else: + new_base_ring = Qp(p, M) + set_padicbase = True + else: + set_padicbase = False + try: + verbose("finding alpha: rooting %s in %s"%(poly, new_base_ring)) + (v0,e0),(v1,e1) = poly.roots(new_base_ring) + except (TypeError, ValueError): + raise ValueError("new base ring must contain a root of x^2 - ap * x + p^(k+1)") + if v0.valuation(p) > 0: + v0, v1 = v1, v0 + if ordinary: + alpha = v0 + else: + alpha = v1 + if find_extraprec: + newM, eisenloss, q, aq = self._find_extraprec(p, M, alpha, check) + else: + newM, eisenloss, q, aq = M, None, None, None + if set_padicbase: + # We want to ensure that the relative precision of alpha and (alpha-1) are both at least *newM*, + # where newM is obtained from self._find_extraprec + prec_cap = None + verbose("testing prec_rel: newM = %s, alpha = %s"%(newM, alpha), level=2) + if alpha.precision_relative() < newM: + prec_cap = newM + alpha.valuation(p) + (1 if p == 2 else 0) + if ordinary: + a1val = (alpha - 1).valuation(p) + verbose("a1val = %s"%a1val, level=2) + if a1val > 0 and ap != 1 + p**(k+1): # if ap = 1 + p**(k+1) then alpha = 1 and we need to give up. + if prec_cap is None: + prec_cap = newM + a1val + (1 if p == 2 else 0) + else: + prec_cap = max(prec_cap, newM + a1val + (1 if p == 2 else 0)) + verbose("prec_cap = %s"%(prec_cap), level=2) + if prec_cap is not None: + new_base_ring = Qp(p, prec_cap) + return self._find_alpha(p=p, k=k, M=M, ap=ap, new_base_ring=new_base_ring, ordinary=ordinary, check=False, find_extraprec=find_extraprec) + return alpha, new_base_ring, newM, eisenloss, q, aq + + def p_stabilize(self, p=None, M=None, alpha=None, ap=None, new_base_ring=None, ordinary=True, check=True): + r""" + + Returns the `p`-stablization of self to level `N*p` on which `U_p` acts by `alpha`. + + Note that since `alpha` is `p`-adic, the resulting symbol + is just an approximation to the true `p`-stabilization + (depending on how well `alpha` is approximated). + + INPUT: + + - ``p`` -- prime not dividing the level of self + + - ``M`` -- precision of `Q_p` + + - ``alpha`` -- `U_p` eigenvalue + + - ``ap`` -- Hecke eigenvalue + + - ``new_base_ring`` -- change of base ring + + OUTPUT: + + A modular symbol with the same Hecke eigenvalues as + self away from `p` and eigenvalue `alpha` at `p`. + The eigenvalue `alpha` depends on the parameter `ordinary`. + + If ordinary == True: the unique modular symbol of level + `N*p` with the same Hecke eigenvalues as self away from + `p` and unit eigenvalue at `p`; else the unique modular + symbol of level `N*p` with the same Hecke eigenvalues as + self away from `p` and non-unit eigenvalue at `p`. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('11a') + sage: p = 5 + sage: prec = 4 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phis = phi.p_stabilize(p,M = prec) + sage: phis + Modular symbol of level 55 with values in Sym^0 Q_5^2 + sage: phis.hecke(7) == phis*E.ap(7) + True + sage: phis.hecke(5) == phis*E.ap(5) + False + sage: phis.hecke(3) == phis*E.ap(3) + True + sage: phis.Tq_eigenvalue(5) + 1 + 4*5 + 3*5^2 + 2*5^3 + O(5^4) + sage: phis = phi.p_stabilize(p,M = prec,ordinary=False) + sage: phis.Tq_eigenvalue(5) + 5 + 5^2 + 2*5^3 + O(5^4) + + A complicated example (with nontrivial character):: + + sage: chi = DirichletGroup(24)([-1, -1, -1]) + sage: f = Newforms(chi,names='a')[0] + sage: phi = f.PS_modular_symbol() + sage: phi11, h11 = phi.completions(11,5)[0] + sage: phi11s = phi11.p_stabilize() + sage: phi11s.is_Tq_eigensymbol(11) + True + """ + if check: + p = self._get_prime(p, alpha) + k = self.parent().weight() + M = self._find_M(M) + verbose("p stabilizing: M = %s"%M, level=2) + if alpha is None: + alpha, new_base_ring, newM, eisenloss, q, aq = self._find_alpha(p, k, M, ap, new_base_ring, ordinary, check, False) + else: + if new_base_ring is None: + new_base_ring = alpha.parent() + if check: + if ap is None: + ap = self.base_ring()(alpha + p**(k+1)/alpha) + elif alpha**2 - ap * alpha + p**(k+1) != 0: + raise ValueError("alpha must be a root of x^2 - a_p*x + p^(k+1)") + if self.hecke(p) != ap * self: + raise ValueError("alpha must be a root of x^2 - a_p*x + p^(k+1)") + verbose("found alpha = %s"%(alpha)) + V = self.parent()._p_stabilize_parent_space(p, new_base_ring) + return self.__class__(self._map.p_stabilize(p, alpha, V), V, construct=True) + + def completions(self, p, M): + r""" + If `K` is the base_ring of self, this function takes all maps + `K-->Q_p` and applies them to self return a list of + (modular symbol,map: `K-->Q_p`) as map varies over all such maps. + + .. NOTE:: + + This only returns all completions when `p` splits completely in `K` + + INPUT: + + - ``p`` -- prime + + - ``M`` -- precision + + OUTPUT: + + - A list of tuples (modular symbol,map: `K-->Q_p`) as map varies over all such maps + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_simple_modsym_space + sage: D = ModularSymbols(67,2,1).cuspidal_submodule().new_subspace().decomposition()[1] + sage: f = ps_modsym_from_simple_modsym_space(D) + sage: S = f.completions(41,10); S + [(Modular symbol of level 67 with values in Sym^0 Q_41^2, Ring morphism: + From: Number Field in alpha with defining polynomial x^2 + 3*x + 1 + To: 41-adic Field with capped relative precision 10 + Defn: alpha |--> 5 + 22*41 + 19*41^2 + 10*41^3 + 28*41^4 + 22*41^5 + 9*41^6 + 25*41^7 + 40*41^8 + 8*41^9 + O(41^10)), (Modular symbol of level 67 with values in Sym^0 Q_41^2, Ring morphism: + From: Number Field in alpha with defining polynomial x^2 + 3*x + 1 + To: 41-adic Field with capped relative precision 10 + Defn: alpha |--> 33 + 18*41 + 21*41^2 + 30*41^3 + 12*41^4 + 18*41^5 + 31*41^6 + 15*41^7 + 32*41^9 + O(41^10))] + sage: TestSuite(S[0][0]).run() + """ + K = self.base_ring() + R = Qp(p,M+10)['x'] + x = R.gen() + if K == QQ: + f = x-1 + else: + f = K.defining_polynomial() + v = R(f).roots() + if len(v) == 0: + L = Qp(p,M).extension(f,names='a') + a = L.gen() + V = self.parent().change_ring(L) + Dist = V.coefficient_module() + psi = K.hom([K.gen()],L) + embedded_sym = self.parent().element_class(self._map.apply(psi,codomain=Dist, to_moments=True),V, construct=True) + ans = [embedded_sym,psi] + return ans + else: + roots = [r[0] for r in v] + ans = [] + V = self.parent().change_ring(Qp(p, M)) + Dist = V.coefficient_module() + for r in roots: + psi = K.hom([r],Qp(p,M)) + embedded_sym = self.parent().element_class(self._map.apply(psi, codomain=Dist, to_moments=True), V, construct=True) + ans.append((embedded_sym,psi)) + return ans + + def lift(self, p=None, M=None, alpha=None, new_base_ring=None, algorithm='stevens', eigensymbol=False, check=True, parallel = False,precomp_data = None): + r""" + Returns a (`p`-adic) overconvergent modular symbol with + `M` moments which lifts self up to an Eisenstein error + + Here the Eisenstein error is a symbol whose system of Hecke + eigenvalues equals `ell+1` for `T_ell` when `ell` + does not divide `Np` and 1 for `U_q` when `q` divides `Np`. + + INPUT: + + - ``p`` -- prime + + - ``M`` -- integer equal to the number of moments + + - ``alpha`` -- `U_p` eigenvalue + + - ``new_base_ring`` -- change of base ring + + - ``algorithm`` -- 'stevens' or 'greenberg' (default 'stevens') + + - ``eigensymbol`` -- if True, lifts to Hecke eigensymbol (self must be a `p`-ordinary eigensymbol) + + (Note: ``eigensymbol = True`` does *not* just indicate to the code that + self is an eigensymbol; it solves a wholly different problem, lifting + an eigensymbol to an eigensymbol.) + + OUTPUT: + + An overconvergent modular symbol whose specialization equals self, up + to some Eisenstein error if ``eigensymbol`` is False. If ``eigensymbol + = True`` then the output will be an overconvergent Hecke eigensymbol + (and it will lift the input exactly, the Eisenstein error disappears). + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('11a') + sage: f = ps_modsym_from_elliptic_curve(E) + sage: g = f.lift(11,4,algorithm='stevens',eigensymbol=True) + sage: g.is_Tq_eigensymbol(2) + True + sage: g.Tq_eigenvalue(3) + 10 + 10*11 + 10*11^2 + 10*11^3 + O(11^4) + sage: g.Tq_eigenvalue(11) + 1 + O(11^4) + + We check that lifting and then specializing gives back the original symbol:: + + sage: g.specialize() == f + True + """ + if p is None: + p = self.parent().prime() + if p == 0: + raise ValueError("must specify a prime") + elif (self.parent().prime() != 0) and p != self.parent().prime(): + raise ValueError("inconsistent prime") + if M is None: + M = self.parent().precision_cap() + 1 +### I don't understand this. This might only make sense in weight 2. Probably need a bound +### on M related to the weight. + elif M <= 1: + raise ValueError("M must be at least 2") + else: + M = ZZ(M) + if new_base_ring is None: + if isinstance(self.parent().base_ring(), pAdicGeneric): + new_base_ring = self.parent().base_ring() + else: + # We may need extra precision in solving the difference equation + extraprec = (M-1).exact_log(p) + # should eventually be a completion + new_base_ring = Qp(p, M+extraprec) + if algorithm is None: + raise NotImplementedError + if algorithm == 'stevens': + if eigensymbol: + # We need some extra precision due to the fact that solving + # the difference equation can give denominators. + if alpha is None: + alpha = self.Tq_eigenvalue(p, check=check) + newM, eisenloss, q, aq = self._find_extraprec(p, M, alpha, check) + return self._lift_to_OMS_eigen(p, M, new_base_ring, alpha, newM, eisenloss, q, aq, check, parallel = parallel,precomp_data = precomp_data) + else: + return self._lift_to_OMS(p, M, new_base_ring, check) + elif algorithm == 'greenberg': + return self._lift_greenberg(p, M, new_base_ring, check) + else: + raise ValueError("algorithm %s not recognized" % algorithm) + + + def _lift_greenberg(self, p, M, new_base_ring=None, check=False): + """ + This is the Greenberg algorithm for lifting a modular eigensymbol to + an overconvergent modular symbol. One first lifts to any set of numbers + (not necessarily satifying the Manin relations). Then one applies the U_p, + and normalizes this result to get a lift satisfying the manin relations. + + + INPUT: + + - ``p`` -- prime + + - ``M`` -- integer equal to the number of moments + + - ``new_base_ring`` -- new base ring + + - ``check`` -- THIS IS CURRENTLY NOT USED IN THE CODE! + + OUTPUT: + + - an overconvergent modular symbol lifting the symbol that was input + + EXAMPLES:: + + sage: E = EllipticCurve('11a') + sage: phi = E.PS_modular_symbol() + sage: Phi = phi.lift(11,5,algorithm='greenberg') + sage: Phi2 = phi.lift(11,5,algorithm='stevens',eigensymbol=True) + sage: Phi == Phi2 + True + sage: set_verbose(1) + sage: E = EllipticCurve('105a1') + sage: phi = E.PS_modular_symbol().minus_part() + sage: Phi = phi.lift(7,8,algorithm='greenberg') + sage: Phi2 = phi.lift(7,8,algorithm='stevens',eigensymbol=True) + sage: Phi == Phi2 + True + + An example in higher weight:: + + sage: f = Newforms(7, 4)[0].PS_modular_symbol() + sage: fs = f.p_stabilize(5) + sage: FsG = fs.lift(M=6, eigensymbol=True,algorithm='greenberg') + sage: FsG.values()[0] + (2 + 5 + 3*5^2 + 4*5^3 + O(5^6), O(5^5), 2*5 + 3*5^2 + O(5^4), O(5^3), 5 + O(5^2), O(5)) + sage: FsS = fs.lift(M=6, eigensymbol=True,algorithm='stevens') + sage: FsS == FsG + True + """ + p = self._get_prime(p) + aqinv = ~self.Tq_eigenvalue(p) + #get a lift that is not a modular symbol + MS = self.parent() + gens = MS.source().gens() + if new_base_ring == None: + new_base_ring = MS.base_ring() + MSnew = MS._lift_parent_space(p, M, new_base_ring) + CMnew = MSnew.coefficient_module() + D = {} + gens = MS.source().gens() + for j in range(len(gens)): + D[gens[j]] = CMnew( self.values()[j]._moments.list() + [0] ).lift(M=2) + Phi1bad = MSnew(D) + + #fix the lift by applying a hecke operator + Phi1 = aqinv * Phi1bad.hecke(p, parallel = parallel) + #if you don't want to compute with good accuracy, stop + if M<=2: + return Phi1 + + #otherwise, keep lifting + padic_prec=M + 1 + R = Qp(p,padic_prec) + + for r in range(self.weight() + 2, M+2): + newvalues = [] + for j,adist in enumerate(Phi1.values()): + newdist = [R(moment).lift_to_precision(moment.precision_absolute()+1) for moment in adist._moments] + if r <= M: + newdist.append([0]) + for s in xrange(self.weight()+1): + newdist[s] = R(self.values()[j].moment(s), r+2) + newvalues.append(newdist) + D2 = {} + for j in range(len(gens)): + D2[ gens[j]] = CMnew( newvalues[j] ).lift(M = min([M,r])) + Phi2 = MSnew(D2) + Phi2 = aqinv * Phi2.hecke(p, parallel = parallel) + verbose('Error = O(p^%s)'%(Phi1-Phi2).valuation()) + Phi1 = Phi2 + for j,adist in enumerate(Phi1.values()): + for s in xrange(self.weight() + 1): + Phi1.values()[j]._moments[s] = self.values()[j].moment(s) + return Phi1 #.reduce_precision(M) # Fix this!! + + def _lift_greenberg2(self, p, M, new_base_ring=None, check=False): + #this is a slower version of the _lift_greenberg that tries not to + #instantiate a bunch of parents. It turns out to be actually slower. + #This code actually only works for weight 2 too. + MS = self.parent() + gens=MS.source().gens() + num_gens=len(gens) + K=Qp(p,M) + zero_moms=self.values() + ap = self.Tq_eigenvalue(p) + + if new_base_ring == None: + new_base_ring = MS.base_ring() + MS1 = MS._lift_parent_space(p,M,new_base_ring) + CM1=MS1.coefficient_module() + D0 = {} + for j in range(num_gens): + D0[gens[j]] = CM1( [zero_moms[j]] + (M-1)*[0]) + + #hecke and divide by eigenvalue + Phi=MS1(D0) + Phi=Phi.hecke(p)/ap + + #keep fixing first moments, hecke and divide by eigenvalues + for k in range(M-1): + D1 = {} + for j in range(num_gens): + vals = Phi.values()[j] + newvals=[vals.moment(n) for n in range(M)] + newvals[0] = K(zero_moms[j]) + D1[gens[j]] = CM1(vals) + Phi = MS1(D1) + Phi = Phi.hecke(p)/ap + + return Phi + + + + def _lift_to_OMS(self, p, M, new_base_ring, check): + r""" + Returns a (`p`-adic) overconvergent modular symbol with + `M` moments which lifts self up to an Eisenstein error + + Here the Eisenstein error is a symbol whose system of Hecke + eigenvalues equals `ell+1` for `T_ell` when `ell` + does not divide `Np` and 1 for `U_q` when `q` divides `Np`. + + INPUT: + + - ``p`` -- prime + + - ``M`` -- integer equal to the number of moments + + - ``new_base_ring`` -- new base ring + + - ``check`` -- THIS IS CURRENTLY NOT USED IN THE CODE! + + OUTPUT: + + - An overconvergent modular symbol whose specialization + equals self up to some Eisenstein error. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('11a') + sage: f = ps_modsym_from_elliptic_curve(E) + sage: f._lift_to_OMS(11,4,Qp(11,4),True) + Modular symbol of level 11 with values in Space of 11-adic distributions with k=0 action and precision cap 4 + + """ + D = {} + manin = self.parent().source() + MSS = self.parent()._lift_parent_space(p, M, new_base_ring) + verbose("Naive lifting: newM=%s, new_base_ring=%s"%(M, MSS.base_ring())) + half = ZZ(1) / ZZ(2) + for g in manin.gens()[1:]: + twotor = g in manin.reps_with_two_torsion() + threetor = g in manin.reps_with_three_torsion() + if twotor: + # See [PS] section 4.1 + gam = manin.two_torsion_matrix(g) + mu = self._map[g].lift(p, M, new_base_ring) + D[g] = (mu - mu * gam) * half + elif threetor: + # See [PS] section 4.1 + gam = manin.three_torsion_matrix(g) + mu = self._map[g].lift(p, M, new_base_ring) + D[g] = (2 * mu - mu * gam - mu * (gam**2)) * half + else: + # no two or three torsion + D[g] = self._map[g].lift(p, M, new_base_ring) + + t = self.parent().coefficient_module().lift(p, M, new_base_ring).zero_element() + ## This loops adds up around the boundary of fundamental domain except the two vertical lines + for g in manin.gens()[1:]: + twotor = g in manin.reps_with_two_torsion() + threetor = g in manin.reps_with_three_torsion() + if twotor or threetor: + t = t - D[g] + else: + t = t + D[g] * manin.gammas[g] - D[g] + ## t now should be sum Phi(D_i) | (gamma_i - 1) - sum Phi(D'_i) - sum Phi(D''_i) + ## (Here I'm using the opposite sign convention of [PS1] regarding D'_i and D''_i) + + D[manin.gen(0)] = -t.solve_diff_eqn() ###### Check this! + + return MSS(D) + + def _find_aq(self, p, M, check): + r""" + Helper function for finding Hecke eigenvalue `aq` for a prime `q` + not equal to `p`. This is called in the case when `alpha = 1 (mod p^M)` + (with `alpha` a `U_p`-eigenvalue), which creates the need to use + other Hecke eigenvalues (and `alpha`s), because of division by `(alpha - 1)`. + + INPUT: + + - ``p`` -- working prime + + - ``M`` -- precision + + - ``check`` -- checks that `self` is a `Tq` eigensymbol + + OUTPUT: + + Tuple `(q, aq, eisenloss)`, with + + - ``q`` -- a prime not equal to `p` + + - ``aq`` -- Hecke eigenvalue at `q` + + - ``eisenloss`` -- the `p`-adic valuation of `aq - q^(k+1) - 1` + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('11a') + sage: f = ps_modsym_from_elliptic_curve(E) + sage: f._find_aq(5,10,True) + (2, -2, 1) + """ + N = self.parent().level() + q = ZZ(2) + k = self.parent().weight() + aq = self.Tq_eigenvalue(q, check=check) + eisenloss = (aq - q**(k+1) - 1).valuation(p) + while ((q == p) or (N % q == 0) or (eisenloss >= M)) and (q<50): + q = next_prime(q) + aq = self.Tq_eigenvalue(q, check=check) + if q != p: + eisenloss = (aq - q**(k+1) - 1).valuation(p) + else: + eisenloss = (aq - 1).valuation(p) + if q >= 50: + raise ValueError("The symbol appears to be eisenstein -- not implemented yet") + return q, aq, eisenloss + + def _find_extraprec(self, p, M, alpha, check): + q, aq, eisenloss = self._find_aq(p, M, check) + newM = M + eisenloss + # We also need to add precision to account for denominators appearing while solving the difference equation. + eplog = (newM -1).exact_log(p) + while eplog < (newM + eplog).exact_log(p): + eplog = (newM + eplog).exact_log(p) + verbose("M = %s, newM = %s, eplog=%s"%(M, newM, eplog), level=2) + newM += eplog + + # We also need to add precision to account for denominators that might be present in self + s = self.valuation(p) + if s < 0: + newM += -s + return newM, eisenloss, q, aq + + def _lift_to_OMS_eigen(self, p, M, new_base_ring, ap, newM, eisenloss, q, aq, check, parallel = False,precomp_data = None): + r""" + Returns Hecke-eigensymbol OMS lifting self -- self must be a + `p`-ordinary eigensymbol + + INPUT: + + - ``p`` -- prime + + - ``M`` -- integer equal to the number of moments + + - ``new_base_ring`` -- new base ring + + - ``ap`` -- Hecke eigenvalue at `p` + + - ``newM`` -- + + - ``eisenloss`` -- + + - ``q`` -- prime + + - ``aq`` -- Hecke eigenvalue at `q` + + - ``check`` -- + + OUTPUT: + + - Hecke-eigenvalue OMS lifting self. + + EXAMPLES:: + + + + """ + if new_base_ring(ap).valuation() > 0: + raise ValueError("Lifting non-ordinary eigensymbols not implemented (issue #20)") + + verbose("computing naive lift: M=%s, newM=%s, new_base_ring=%s"%(M, newM, new_base_ring)) + Phi = self._lift_to_OMS(p, newM, new_base_ring, check) + + ## Scale by a large enough power of p to clear denominators from solving difference equation +# s = newM.exact_log(p)+1 +# Phi = Phi * p**s + + ## Act by Hecke to ensure values are in D and not D^dag after sovling difference equation + # verbose("Calculating input vector") + # input_vector = [] + # for g in self._map._manin.gens(): + # input_vector.append(([(se,A) for h,A in M.prep_hecke_on_gen_list(p,g)],g)) + + # verbose("Computing acting matrices") + # acting_matrices = {} + # for g in Phi._map._manin.gens(): + # acting_matrices[g] = Phi._map._codomain.acting_matrix(g,Phi._map._codomain.precision_cap()) + + verbose("Applying Hecke") + + apinv = ~ap + t_start = walltime() + Phi = apinv * Phi.hecke(p, parallel = parallel,precomp_data = precomp_data) + t_end = walltime(t_start) + # Estimate the total time to complete + eta = (t_end * (newM + 1))/(60*60) + verbose("Estimated time to complete: %s hours"%eta) + + ## Killing eisenstein part + verbose("Killing eisenstein part with q = %s"%(q)) + + k = self.parent().weight() + Phi = ((q**(k+1) + 1) * Phi - Phi.hecke(q, parallel = parallel)) + #verbose(Phi._show_malformed_dist("Eisenstein killed"), level=2) + + ## Iterating U_p + verbose("Iterating U_p") + t_start = walltime() + Psi = apinv * Phi.hecke(p, parallel = parallel,precomp_data = precomp_data) + t_end = walltime(t_start) + # Estimate the total time to complete + eta = (t_end * (newM + 1))/(60*60) + verbose("Estimated time to complete (second estimate): %s hours"%eta) + + + attempts = 0 + while (Phi != Psi) and (attempts < 2*newM): + verbose("%s attempt"%(attempts+1)) + Phi = Psi + Psi = Phi.hecke(p, parallel = parallel,precomp_data = precomp_data) * apinv + attempts += 1 + if attempts >= 2*newM: + raise RuntimeError("Precision problem in lifting -- applied U_p many times without success") + Phi = ~(q**(k+1) + 1 - aq) * Phi + + return Phi.reduce_precision(M) + + def p_stabilize_and_lift(self, p=None, M=None, alpha=None, ap=None, new_base_ring=None, \ + ordinary=True, algorithm=None, eigensymbol=False, check=True, parallel = False): + """ + `p`-stabilizes and lifts self + + INPUT: + + - ``p`` -- (default: None) + + - ``M`` -- (default: None) + + - ``alpha`` -- (default: None) + + - ``ap`` -- (default: None) + + - ``new_base_ring`` -- (default: None) + + - ``ordinary`` -- (default: True) + + - ``algorithm`` -- (default: None) + + - ``eigensymbol`` -- (default: False) + + - ``check`` -- (default: True) + + OUTPUT: + + `p`-stabilized and lifted version of self. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('11a') + sage: f = ps_modsym_from_elliptic_curve(E) + sage: g = f.p_stabilize_and_lift(3,10) + sage: g.Tq_eigenvalue(5) + 1 + O(3^10) + sage: g.Tq_eigenvalue(7) + 1 + 2*3 + 2*3^2 + 2*3^3 + 2*3^4 + 2*3^5 + 2*3^6 + 2*3^7 + 2*3^8 + 2*3^9 + O(3^10) + sage: g.Tq_eigenvalue(3) + 2 + 3^2 + 2*3^3 + 2*3^4 + 2*3^6 + 3^8 + 2*3^9 + O(3^10) + """ + if check: + p = self._get_prime(p, alpha) + k = self.parent().weight() + M = self._find_M(M) + # alpha will be the eigenvalue of Up + if alpha is None: + alpha, new_base_ring, newM, eisenloss, q, aq = self._find_alpha(p, k, M, ap, new_base_ring, ordinary, check) + else: + if new_base_ring is None: + new_base_ring = alpha.parent() + newM, eisenloss, q, aq = self._find_extraprec(p, M, alpha, check) + if hasattr(new_base_ring, 'precision_cap') and newM > new_base_ring.precision_cap(): + raise ValueError("Not enough precision in new base ring") + + # Now we can stabilize + self = self.p_stabilize(p=p, alpha=alpha,ap=ap, M=newM, new_base_ring = new_base_ring, check=check) + # And use the standard lifting function for eigensymbols + return self._lift_to_OMS_eigen(p=p, M=M, new_base_ring=new_base_ring, ap=alpha, newM=newM, eisenloss=eisenloss, q=q, aq=aq, check=check, parallel = parallel) + +class PSModularSymbolElement_dist(PSModularSymbolElement): + + def _show_malformed_dist(self, location_str): + malformed = [] + gens = self.parent().source().gens() + for j, g in enumerate(gens): + val = self._map[g] + if val._is_malformed(): + malformed.append((j, val)) + return location_str + ": (%s/%s malformed)%s"%(len(malformed), len(gens), ", %s -- %s"%(malformed[0][0], str(malformed[0][1])) if len(malformed) > 0 else "") + + def reduce_precision(self, M): + r""" + Only holds on to `M` moments of each value of self + """ + return self.__class__(self._map.reduce_precision(M), self.parent(), construct=True) + + def precision_absolute(self): + r""" + Returns the number of moments of each value of self + """ + return min([a.precision_absolute() for a in self._map]) + + def specialize(self, new_base_ring=None): + r""" + Returns the underlying classical symbol of weight `k` -- i.e., + applies the canonical map `D_k --> Sym^k` to all values of + self. + + EXAMPLES:: + + sage: D = Distributions(0, 5, 10); M = PSModularSymbols(Gamma0(5), coefficients=D); M + Space of overconvergent modular symbols for Congruence Subgroup Gamma0(5) with sign 0 and values in Space of 5-adic distributions with k=0 action and precision cap 10 + sage: f = M(1) + sage: f.specialize() + Modular symbol of level 5 with values in Sym^0 Z_5^2 + sage: f.specialize().values() + [1 + O(5^10), 1 + O(5^10), 1 + O(5^10)] + sage: f.values() + [1, 1, 1] + sage: f.specialize().parent() + Space of modular symbols for Congruence Subgroup Gamma0(5) with sign 0 and values in Sym^0 Z_5^2 + sage: f.specialize().parent().coefficient_module() + Sym^0 Z_5^2 + sage: f.specialize().parent().coefficient_module().is_symk() + True + + sage: f.specialize(QQ) + Modular symbol of level 5 with values in Sym^0 Q^2 + sage: f.specialize(QQ).values() + [1, 1, 1] + sage: f.specialize(QQ).parent().coefficient_module() + Sym^0 Q^2 + """ + if new_base_ring is None: + new_base_ring = self.base_ring() + return self.__class__(self._map.specialize(new_base_ring), + self.parent()._specialize_parent_space(new_base_ring), construct=True) + + def padic_lseries(self,*args, **kwds): + r""" + Return the p-adic L-series of this modular symbol. + + EXAMPLE:: + + sage: f = Newform("37a") + sage: f.PS_modular_symbol().lift(37, M=6, algorithm="stevens").padic_lseries() + 37-adic L-series of Modular symbol of level 37 with values in Space of 37-adic distributions with k=0 action and precision cap 6 + """ + return pAdicLseries(self, *args, **kwds) diff --git a/src/sage/modular/pollack_stevens/padic_lseries.py b/src/sage/modular/pollack_stevens/padic_lseries.py new file mode 100644 index 00000000000..58123ad4256 --- /dev/null +++ b/src/sage/modular/pollack_stevens/padic_lseries.py @@ -0,0 +1,465 @@ +r""" +P-adic L-series attached to overconvergent eigensymbols +""" +#***************************************************************************** +# Copyright (C) 2012 Robert Pollack +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.rings.padics.all import pAdicField +from sage.rings.all import ZZ, QQ +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.big_oh import O +from sage.rings.arith import binomial, gcd, kronecker + +from sage.structure.sage_object import SageObject +from sigma0 import Sigma0 +from fund_domain import M2Z + +class pAdicLseries(SageObject): + r""" + The `p`-adic `L`-series associated to an overconvergent eigensymbol. + + INPUT: + + - ``symb`` -- overconvergent eigensymbol + - ``gamma`` -- topological generator of `1 + pZ_p` + - ``quadratic_twist`` -- conductor of quadratic twist `\chi`, default 1 + - ``precision`` -- if None is specified, the correct precision bound is + computed and the answer is returned modulo that accuracy + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('37a') + sage: p = 5 + sage: prec = 4 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi_stabilized = phi.p_stabilize(p,20) + sage: Phi = phi_stabilized.lift(p,prec,algorithm='stevens',eigensymbol=True) + sage: L = pAdicLseries(Phi) + sage: L[1] + 2 + 3*5 + O(5^3) + sage: L[0] + O(5^3) + + Using the existing algorithm in Sage, it seems we're off by a factor of 2: + + sage: L = E.padic_lseries(5) + sage: L.series(4)[1] + 1 + 4*5 + 2*5^2 + O(5^3) + + But here, we're correct without the factor of 2: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('57a') + sage: p = 5 + sage: prec = 4 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi_stabilized = phi.p_stabilize(p,M = prec+3) + sage: Phi = phi_stabilized.lift(p=5, M=prec, alpha=None, algorithm='stevens', eigensymbol=True) + sage: L = pAdicLseries(Phi) + sage: L[1] + 3*5 + 5^2 + O(5^3) + + sage: L1 = E.padic_lseries(5) + sage: L1.series(4)[1] + 3*5 + 5^2 + O(5^3) + + An example of a `p`-adic `L`-series associated to a modular abelian surface: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_simple_modsym_space + sage: A = ModularSymbols(103,2,1).cuspidal_submodule().new_subspace().decomposition()[0] + sage: p = 19 + sage: prec = 4 + sage: phi = ps_modsym_from_simple_modsym_space(A) + sage: ap = phi.Tq_eigenvalue(p,prec) + sage: c1,c2 = phi.completions(p,prec) + sage: phi1,psi1 = c1 + sage: phi2,psi2 = c2 + sage: phi1p = phi1.p_stabilize_and_lift(p,ap = psi1(ap), M = prec, algorithm='stevens') # long time + sage: L1 = pAdicLseries(phi1p) # long time + sage: phi2p = phi2.p_stabilize_and_lift(p,ap = psi2(ap), M = prec, algorithm='stevens') # long time + sage: L2 = pAdicLseries(phi2p) # long time + sage: L1[1]*L2[1] # long time + 13 + 9*19 + O(19^2) + """ + def __init__(self, symb, gamma=None, quadratic_twist=1, precision=None): + r""" + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('37a') + sage: p = 37 + sage: prec = 3 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: Phi = phi.lift(p,prec,algorithm='stevens',eigensymbol=True) + sage: L = pAdicLseries(Phi) + sage: L[1] + 4 + 37 + O(37^2) + + sage: TestSuite(L).run() + """ + self._coefficients = {} + + if symb.parent().prime() == None: + raise ValueError ("Not a p-adic overconvergent modular symbol.") + + self._symb = symb + + if gamma == None: + gamma = 1 + self._symb.parent().prime() + + self._gamma = gamma + self._quadratic_twist = quadratic_twist + self._precision = precision + + def __getitem__(self, n): + """ + Returns the `n`-th coefficient of the `p`-adic `L`-series + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('57a') + sage: p = 5 + sage: prec = 4 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi_stabilized = phi.p_stabilize(p,M = prec+3) + sage: Phi = phi_stabilized.lift(p=p,M=prec,alpha=None,algorithm='stevens',eigensymbol=True) + sage: L = pAdicLseries(Phi) + sage: L[1] + 3*5 + 5^2 + O(5^3) + + sage: L1 = E.padic_lseries(5) + sage: L1.series(4)[1] + 3*5 + 5^2 + O(5^3) + + """ + if self._coefficients.has_key(n): + return self._coefficients[n] + else: + p = self.prime() + symb = self.symb() + ap = symb.Tq_eigenvalue(p) + gamma = self._gamma + precision = self._precision + + S = QQ[['z']] + z = S.gen() + M = symb.precision_absolute() + K = pAdicField(p, M) + dn = 0 + if n == 0: + precision = M + lb = [1] + [0 for a in range(M-1)] + else: + lb = log_gamma_binomial(p, gamma, z, n, 2*M) + if precision == None: + precision = min([j + lb[j].valuation(p) for j in range(M, len(lb))]) + lb = [lb[a] for a in range(M)] + + for j in range(len(lb)): + cjn = lb[j] + temp = sum((ZZ(K.teichmuller(a))**(-j)) * self._basic_integral(a, j) for a in range(1, p)) + dn = dn + cjn*temp + self._coefficients[n] = dn + O(p**precision) + return self._coefficients[n] + + def __cmp__(self, other): + r""" + Compare self and other. + + EXAMPLE:: + + sage: E = EllipticCurve('11a') + sage: S = sage.modular.pollack_stevens.space.ps_modsym_from_elliptic_curve(E) + sage: SS = S.lift(11, M=10, algorithm='stevens') + sage: L = pAdicLseries(SS) + sage: L == loads(dumps(L)) # indirect doctest + True + """ + return cmp(type(self), type(other)) \ + or cmp(self._symb, other._symb) \ + or cmp(self._quadratic_twist, other._quadratic_twist) \ + or cmp(self._gamma, other._gamma) \ + or cmp(self._precision, other._precision) + + def symb(self): + r""" + Returns the overconvergent modular symbol + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('37a') + sage: p = 5 + sage: prec = 6 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi_stabilized = phi.p_stabilize(p,M = prec) + sage: Phi = phi_stabilized.lift(p=p,M=prec,alpha=None,algorithm='stevens',eigensymbol=True) + sage: L = pAdicLseries(Phi) + sage: L.symb() is Phi + True + """ + return self._symb + + def prime(self): + r""" + Returns the prime associatd to the OMS + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('37a') + sage: p = 5 + sage: prec = 6 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi_stabilized = phi.p_stabilize(p,M = prec) + sage: Phi = phi_stabilized.lift(p,prec,None,algorithm='stevens',eigensymbol=True) + sage: L = pAdicLseries(Phi) + sage: L.prime() + 5 + """ + return self._symb.parent().prime() + + def quadratic_twist(self): + r""" + Returns the discriminant of the quadratic twist + + EXAMPLES:: + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('37a') + sage: p = 5 + sage: prec = 6 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi_stabilized = phi.p_stabilize(p,M = prec) + sage: Phi = phi_stabilized.lift(p,prec,None,algorithm='stevens',eigensymbol=True) + sage: L = pAdicLseries(Phi) + sage: L.quadratic_twist() + 1 + """ + return self._quadratic_twist + + def _repr_(self): + r""" + Returns the print representation + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('37a') + sage: p = 5 + sage: prec = 6 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi_stabilized = phi.p_stabilize(p,M = prec) + sage: Phi = phi_stabilized.lift(p,prec,None,algorithm='stevens',eigensymbol=True) + sage: L = pAdicLseries(Phi) + sage: L._repr_() + '5-adic L-series of Modular symbol of level 37 with values in Space of 5-adic distributions with k=0 action and precision cap 7' + """ + s = "%s-adic L-series of %s"%(self.prime(), self.symb()) + return s + + def series(self, n, prec): + r""" + Returns the `n`-th approximation to the `p`-adic `L`-series + associated to self, as a power series in `T` (corresponding to + `\gamma-1` with `\gamma= 1 + p` as a generator of `1+p\ZZ_p`). + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('57a') + sage: p = 5 + sage: prec = 4 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi_stabilized = phi.p_stabilize(p,M = prec+3) + sage: Phi = phi_stabilized.lift(p,prec,None,algorithm='stevens',eigensymbol=True) + sage: L = pAdicLseries(Phi) + sage: L.series(3,4) + O(5^3) + (3*5 + 5^2 + O(5^3))*T + (5 + O(5^2))*T^2 + + sage: L1 = E.padic_lseries(5) + sage: L1.series(4) + O(5^6) + (3*5 + 5^2 + O(5^3))*T + (5 + 4*5^2 + O(5^3))*T^2 + (4*5^2 + O(5^3))*T^3 + (2*5 + 4*5^2 + O(5^3))*T^4 + O(T^5) + + """ + p = self.prime() + M = self.symb().precision_absolute() + K = pAdicField(p, M) + R = PowerSeriesRing(K, names = 'T') + T = R.gens()[0] + R.set_default_prec(prec) + return sum(self[i] * T**i for i in range(n)) + + def interpolation_factor(self, ap,chip=1, psi = None): + r""" + Returns the interpolation factor associated to self + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('57a') + sage: p = 5 + sage: prec = 4 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi_stabilized = phi.p_stabilize(p,M = prec) + sage: Phi = phi_stabilized.lift(p,prec,None,algorithm='stevens') + sage: L = pAdicLseries(Phi) + sage: ap = phi.Tq_eigenvalue(p) + sage: L.interpolation_factor(ap) + 4 + 2*5 + 4*5^3 + O(5^4) + + Comparing against a different implementation: + + sage: L = E.padic_lseries(5) + sage: (1-1/L.alpha(prec=4))^2 + 4 + 2*5 + 4*5^3 + O(5^4) + + """ + M = self.symb().precision_absolute() + p = self.prime() + if p == 2: + R = pAdicField(2, M + 1) + else: + R = pAdicField(p, M) + if psi != None: + ap = psi(ap) + ap = ap*chip + sdisc = R(ap**2 - 4*p).sqrt() + v0 = (R(ap) + sdisc) / 2 + v1 = (R(ap) - sdisc) / 2 + if v0.valuation() > 0: + v0, v1 = v1, v0 + alpha = v0 + return (1 - 1/alpha)**2 + + def eval_twisted_symbol_on_Da(self, a): # rename! should this be in modsym? + """ + Returns `\Phi_{\chi}(\{a/p}-{\infty})` where `Phi` is the OMS and + `\chi` is a the quadratic character corresponding to self + + INPUT: + - ``a`` -- integer in range(p) + + OUTPUT: + + The distribution `\Phi_{\chi}(\{a/p\}-\{\infty\})`. + + EXAMPLES: + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('57a') + sage: p = 5 + sage: prec = 4 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: ap = phi.Tq_eigenvalue(p,prec) + sage: Phi = phi.p_stabilize_and_lift(p,ap = ap, M = prec, algorithm='stevens') + sage: L = pAdicLseries(Phi) + sage: L.eval_twisted_symbol_on_Da(1) + (2 + 2*5 + 2*5^2 + 2*5^3 + O(5^4), 2 + 3*5 + 2*5^2 + O(5^3), 4*5 + O(5^2), 3 + O(5)) + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('40a4') + sage: p = 7 + sage: prec = 4 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: ap = phi.Tq_eigenvalue(p,prec) + sage: Phi = phi.p_stabilize_and_lift(p,ap = ap, M = prec, algorithm='stevens') + sage: L = pAdicLseries(Phi) + sage: L.eval_twisted_symbol_on_Da(1) + (4 + 6*7 + 3*7^2 + O(7^4), 2 + 7 + O(7^3), 4 + 6*7 + O(7^2), 6 + O(7)) + + """ + symb = self.symb() + p = symb.parent().prime() + S0p = Sigma0(p) + Dists = symb.parent().coefficient_module() + M = Dists.precision_cap() + p = Dists.prime() + twisted_dist = Dists.zero_element() + m_map = symb._map + D = self._quadratic_twist + for b in range(1, abs(D) + 1): + if gcd(b, D) == 1: + M1 = S0p([1, (b / abs(D)) % p**M, 0, 1]) + new_dist = m_map(M1 * M2Z([a, 1, p, 0]))*M1 + new_dist = new_dist.scale(kronecker(D, b)).normalize() + twisted_dist = twisted_dist + new_dist + #ans = ans + self.eval(M1 * M2Z[a, 1, p, 0])._right_action(M1)._lmul_(kronecker(D, b)).normalize() + return twisted_dist.normalize() + + def _basic_integral(self, a, j): + r""" + Returns `\int_{a+pZ_p} (z-{a})^j d\Phi(0-infty)` + -- see formula [Pollack-Stevens, sec 9.2] + + INPUT: + + - ``a`` -- integer in range(p) + - ``j`` -- integer in range(self.symb().precision_absolute()) + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('57a') + sage: p = 5 + sage: prec = 4 + sage: phi = ps_modsym_from_elliptic_curve(E) + sage: phi_stabilized = phi.p_stabilize(p,M = prec+3) + sage: Phi = phi_stabilized.lift(p,prec,None,algorithm = 'stevens',eigensymbol = True) + sage: L = pAdicLseries(Phi) + sage: L.eval_twisted_symbol_on_Da(1) + (2 + 2*5 + 2*5^2 + 2*5^3 + O(5^4), 2 + 3*5 + 2*5^2 + O(5^3), 4*5 + O(5^2), 3 + O(5)) + sage: L._basic_integral(1,2) + 2*5^3 + O(5^4) + + """ + symb = self.symb() + M = symb.precision_absolute() + if j > M: + raise PrecisionError ("Too many moments requested") + p = self.prime() + ap = symb.Tq_eigenvalue(p) + D = self._quadratic_twist + ap = ap * kronecker(D, p) + K = pAdicField(p, M) + symb_twisted = self.eval_twisted_symbol_on_Da(a) + return sum(binomial(j, r) * ((a - ZZ(K.teichmuller(a)))**(j - r)) * + (p**r) * symb_twisted.moment(r) for r in range(j + 1)) / ap + +def log_gamma_binomial(p,gamma,z,n,M): + r""" + Returns the list of coefficients in the power series + expansion (up to precision `M`) of `{\log_p(z)/\log_p(\gamma) \choose n}` + + INPUT: + + - ``p`` -- prime + - ``gamma`` -- topological generator e.g., `1+p` + - ``z`` -- variable + - ``n`` -- nonnegative integer + - ``M`` -- precision + + OUTPUT: + + The list of coefficients in the power series expansion of + `{\log_p(z)/\log_p(\gamma) \choose n}` + + EXAMPLES: + + sage: R. = QQ['z'] + sage: from sage.modular.pollack_stevens.padic_lseries import log_gamma_binomial + sage: log_gamma_binomial(5,1+5,z,2,4) + [0, -3/205, 651/84050, -223/42025] + sage: log_gamma_binomial(5,1+5,z,3,4) + [0, 2/205, -223/42025, 95228/25845375] + """ + L = sum([ZZ(-1)**j / j*z**j for j in range (1,M)]) #log_p(1+z) + loggam = L / (L(gamma - 1)) #log_{gamma}(1+z)= log_p(1+z)/log_p(gamma) + return z.parent()(binomial(loggam,n)).truncate(M).list() diff --git a/src/sage/modular/pollack_stevens/sigma0.py b/src/sage/modular/pollack_stevens/sigma0.py new file mode 100644 index 00000000000..71f467c90ca --- /dev/null +++ b/src/sage/modular/pollack_stevens/sigma0.py @@ -0,0 +1,492 @@ +r""" +The monoid `\Sigma_0(N)`. + +This stands for a monoid of matrices over `\ZZ`, `\QQ`, `\ZZ_p`, or `\QQ_p`, +depending on an integer `N \ge 1`. This class exists in order to act on p-adic +distribution spaces. + +Over `\QQ` or `\ZZ`, it is the monoid of matrices `2\times2` matrices `\begin{pmatrix} a & b \\ c & d \end{pmatrix}` +such that +- `ad - bc \ne 0`, +- `a` is integral and invertible at the primes dividing `N`, +- `c` has valuation at least `v_p(N)` for each `p` dividing `N` (but may be + non-integral at other places). + +The value `N=1` is allowed, in which case the second and third conditions are vacuous. + +EXAMPLES:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: S1 = Sigma0(1); S3 = Sigma0(3) + sage: S1([3, 0, 0, 1]) + [3 0] + [0 1] + sage: S3([3, 0, 0, 1]) # boom + Traceback (most recent call last): + ... + TypeError: 3 is not a unit at 3 + sage: S3([5,0,0,1]) + [5 0] + [0 1] + sage: S3([1, 0, 0, 3]) + [1 0] + [0 3] + sage: matrix(ZZ, 2, [1,0,0,1]) in S1 + True +""" + +# Warning to developers: when working with Sigma0 elements it is generally a +# good idea to avoid using the entries of x.matrix() directly; rather, use the +# "adjuster" mechanism. The purpose of this is to allow us to seamlessly change +# conventions for matrix actions (since there are several in use in the +# literature and no natural "best" choice). + +from sage.matrix.matrix_integer_2x2 import MatrixSpace_ZZ_2x2 +from sage.matrix.matrix_space import MatrixSpace +from sage.misc.abstract_method import abstract_method +from sage.structure.factory import UniqueFactory +from sage.structure.element import MonoidElement +from sage.categories.monoids import Monoids +from sage.categories.morphism import Morphism +from sage.structure.parent import Parent +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.rings.infinity import Infinity +from sage.structure.unique_representation import UniqueRepresentation + +class Sigma0ActionAdjuster(UniqueRepresentation): + + # Can one make an abstract class in Sage? + + @abstract_method + def __call__(self, x): + r""" + Given a Sigma0 element x, return four integers. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import _default_adjuster + sage: A = _default_adjuster() + sage: A(matrix(ZZ, 2, [1,2,3,4])) # indirect doctest + (1, 2, 3, 4) + """ + pass + +class _default_adjuster(Sigma0ActionAdjuster): + """ + A callable object that does nothing to a matrix, returning its entries in the natural order. + + INPUT: + + - ``g`` -- a 2x2 matrix + + OUTPUT: + + - a 4-tuple consisting of the entries of the matrix + + EXAMPLES:: + + sage: A = sage.modular.pollack_stevens.sigma0._default_adjuster(); A + + sage: TestSuite(A).run() + """ + def __call__(self, g): + """ + EXAMPLES:: + + sage: T = sage.modular.pollack_stevens.sigma0._default_adjuster() + sage: T(matrix(ZZ,2,[1..4])) # indirect doctest + (1, 2, 3, 4) + """ + return tuple(g.list()) + +class Sigma0_factory(UniqueFactory): + r""" + Create the monoid of non-singular matrices, upper triangular mod `N`. + + INPUT: + + - ``N`` (integer) -- the level (should be strictly positive) + - ``base_ring`` (commutative ring, default `\ZZ`) -- the base ring (normally `\ZZ` or a `p`-adic ring) + - ``adjuster`` -- None, or a callable which takes a 2x2 matrix and returns + a 4-tuple of integers. This is supplied in order to support differing + conventions for the action of 2x2 matrices on distributions. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: Sigma0(3) + Monoid Sigma0(3) with coefficients in Integer Ring + """ + + def create_key(self, N, base_ring=ZZ, adjuster=None): + r""" + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: Sigma0.create_key(3) + (3, Integer Ring, ) + sage: TestSuite(Sigma0).run() + """ + N = ZZ(N) + if N <= 0: + raise ValueError("Modulus should be > 0") + if adjuster is None: + adjuster = _default_adjuster() + + if base_ring not in (QQ, ZZ): + try: + if not N.is_power_of(base_ring.prime()): + raise ValueError("Modulus must equal base ring prime") + except AttributeError: + raise ValueError("Base ring must be QQ, ZZ or a p-adic field") + return (N, base_ring, adjuster) + + def create_object(self, version, key): + r""" + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: Sigma0(3) # indirect doctest + Monoid Sigma0(3) with coefficients in Integer Ring + """ + return Sigma0_class(*key) + +Sigma0 = Sigma0_factory('sage.modular.pollack_stevens.sigma0.Sigma0') + +class Sigma0Element(MonoidElement): + r""" + An element of the monoid Sigma0. This is a wrapper around a 2x2 matrix. + """ + def __init__(self, parent, mat): + r""" + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: s = Sigma0(3)([1,4,3,3]) # indirect doctest + sage: TestSuite(s).run() + """ + self._mat = mat + MonoidElement.__init__(self, parent) + + def __hash__(self): + r""" + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: s = Sigma0(3)([1,4,3,3]) + sage: hash(s) # indirect doctest + 11 + + # TODO: the doctest is probably wrong on 32-bit machines + """ + return hash(self.matrix()) + + def det(self): + r""" + Return the determinant of this matrix, which is (by assumption) non-zero. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: s = Sigma0(3)([1,4,3,3]) + sage: s.det() + -9 + """ + return self.matrix().det() + + def _mul_(self, other): + r""" + Return the product of two Sigma0 elements. + + EXAMPLE:: + + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: s = Sigma0(3)([1,4,3,3]) + sage: t = Sigma0(15)([4,0,0,1]) + sage: u = s*t; u # indirect doctest + [ 4 4] + [12 3] + sage: type(u) + + sage: u.parent() + Monoid Sigma0(3) with coefficients in Integer Ring + """ + return self.parent()(self._mat * other._mat, check=False) + + def __cmp__(self, other): + r""" + Compare two elements (of a common Sigma0 object). + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: s = Sigma0(3)([1,4,3,3]) + sage: t = Sigma0(3)([4,0,0,1]) + sage: s == t + False + sage: s == Sigma0(1)([1,4,3,3]) + True + + This uses the coercion model to find a common parent, with occasionally surprising results: + + sage: t == Sigma0(5)([4, 0, 0, 1]) # should be True + False + """ + return cmp(self._mat, other._mat) + + def _repr_(self): + r""" + String representation of self. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: s = Sigma0(3)([1,4,3,3]) + sage: s._repr_() + '[1 4]\n[3 3]' + """ + return self.matrix().__repr__() + + def matrix(self): + r""" + Return self as a matrix (forgetting the additional data that it is in Sigma0(N)). + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: s = Sigma0(3)([1,4,3,3]) + sage: sm = s.matrix() + sage: type(s) + + sage: type(sm) + + sage: s == sm + True + """ + return self._mat + + def inverse(self): + r""" + Return the inverse of self. This will raise an error if the result is not in the monoid. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: s = Sigma0(3)([1,4,3,13]) + sage: s.inverse() + [13 -4] + [-3 1] + sage: Sigma0(3)([1, 0, 0, 3]).inverse() + Traceback (most recent call last): + ... + TypeError: no conversion of this rational to integer + + .. todo:: + + In an ideal world this would silently extend scalars to `\QQ` if + the inverse has non-integer entries but is still in `\Sigma_0(N)` + locally at `N`. But we do not use such functionality, anyway. + """ + return self.parent()(~self._mat) + +class _Sigma0Embedding(Morphism): + r""" + A Morphism object giving the natural inclusion of `\Sigma_0` into the + appropriate matrix space. This snippet of code is fed to the coercion + framework so that "x * y" will work if x is a matrix and y is a Sigma0 + element (returning a matrix, *not* a Sigma0 element). + """ + def __init__(self, domain): + r""" + TESTS:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0, _Sigma0Embedding + sage: x = _Sigma0Embedding(Sigma0(3)) + sage: TestSuite(x).run(skip=['_test_category']) + + # TODO: The category test breaks because _Sigma0Embedding is not an instance of + # the element class of its parent (a homset in the category of + # monoids). I have no idea how to fix this. + """ + Morphism.__init__(self, domain.Hom(domain._matrix_space, category=Monoids())) + + def _call_(self, x): + r""" + Return a matrix. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0, _Sigma0Embedding + sage: S = Sigma0(3) + sage: x = _Sigma0Embedding(S) + sage: x(S([1,0,0,3])).parent() # indirect doctest + Space of 2x2 integer matrices + """ + return x.matrix() + + def __cmp__(self, other): + r""" + Required for pickling. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0, _Sigma0Embedding + sage: S = Sigma0(3) + sage: x = _Sigma0Embedding(S) + sage: x == loads(dumps(x)) + True + """ + return cmp(type(self), type(other)) or cmp(self.domain(), other.domain()) + +class Sigma0_class(Parent): + + Element = Sigma0Element + + def __init__(self, N, base_ring,adjuster): + r""" + Standard init function. For args documentation see the factory + function. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: S = Sigma0(3) # indirect doctest + sage: TestSuite(S).run() + """ + self._N = N + self._primes = list(N.factor()) + self._base_ring = base_ring + self._adjuster = adjuster + if base_ring == ZZ: + self._matrix_space = MatrixSpace_ZZ_2x2() + else: + self._matrix_space = MatrixSpace(base_ring, 2) + Parent.__init__(self, category=Monoids()) + self.register_embedding(_Sigma0Embedding(self)) + + def _an_element_(self): + r""" + Return an element of self. This is implemented in a rather dumb way. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: S = Sigma0(3) + sage: S.an_element() # indirect doctest + [1 0] + [0 1] + """ + return self([1,0,0,1]) + +# I removed __cmp__ because this class has unique representation anyway + + def level(self): + r""" + If this monoid is `\Sigma_0(N)`, return `N`. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: S = Sigma0(3) + sage: S.level() + 3 + """ + return self._N + + def base_ring(self): + r""" + Return the base ring. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: S = Sigma0(3) + sage: S.base_ring() + Integer Ring + """ + return self._base_ring + + def _coerce_map_from_(self, other): + r""" + Find out wheter other coerces into self. + + The *only* thing that coerces canonically into `\Sigma_0` is another + `\Sigma_0`. It is *very bad* if integers are allowed to coerce in, as + this leads to a noncommutative coercion diagram whenever we let + `\Sigma_0` act on anything.. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: Sigma0(1, QQ).has_coerce_map_from(Sigma0(3, ZZ)) # indirect doctest + True + sage: Sigma0(1, ZZ).has_coerce_map_from(ZZ) + False + + (If something changes that causes the last doctest above to return + True, then the entire purpose of this class is violated, and all sorts + of nasty things will go wrong with scalar multiplication of + distributions. Do not let this happen!) + """ + if isinstance(other, Sigma0_class) \ + and self.level().divides(other.level()) \ + and self.base_ring().has_coerce_map_from(other.base_ring()): + return True + else: + return False + + def _element_constructor_(self, x, check=True): + r""" + Construct an element of self from x. + + INPUT: + + - ``x`` -- something that one can make into a matrix over the + appropriate base ring + - ``check`` (boolean, default True) -- if True, then check that this + matrix actually satisfies the conditions. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: S = Sigma0(3) + sage: S([1,0,0,3]) # indirect doctest + [1 0] + [0 3] + sage: S([3,0,0,1]) # boom + Traceback (most recent call last): + ... + TypeError: 3 is not a unit at 3 + sage: S(Sigma0(1)([3,0,0,1]), check=False) # don't do this + [3 0] + [0 1] + """ + if isinstance(x, Sigma0Element): + x = x.matrix() + if check: + x = self._matrix_space(x) + a,b,c,d = self._adjuster(x) + for (p, e) in self._primes: + if c.valuation(p) < e: + raise TypeError("level %s^%s does not divide %s" % (p, e, c)) + if a.valuation(p) != 0: + raise TypeError("%s is not a unit at %s" % (a, p)) + if x.det() == 0: + raise TypeError("matrix must be nonsingular") + x.set_immutable() + return self.element_class(self, x) + + def _repr_(self): + r""" + String representation of self. + + EXAMPLE:: + + sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 + sage: S = Sigma0(3) + sage: S._repr_() + 'Monoid Sigma0(3) with coefficients in Integer Ring' + """ + return 'Monoid Sigma0(%s) with coefficients in %s' % (self.level(), self.base_ring()) diff --git a/src/sage/modular/pollack_stevens/space.py b/src/sage/modular/pollack_stevens/space.py new file mode 100644 index 00000000000..bf24291c69c --- /dev/null +++ b/src/sage/modular/pollack_stevens/space.py @@ -0,0 +1,1016 @@ +r""" +Pollack-Stevens Modular Symbols Spaces + +This module contains a class for spaces of modular symbols that use Glenn +Stevens' conventions. + +There are two main differences between the modular symbols in this directory +and the ones in :mod:`sage.modular.modsym`: + +- There is a shift in the weight: weight `k=0` here corresponds to weight `k=2` + there. + +- There is a duality: these modular symbols are functions from + `Div^0(P^1(\QQ))` (cohomological objects), the others are formal linear + combinations of `Div^0(P^1(\QQ))` (homological objects). +""" +#***************************************************************************** +# Copyright (C) 2012 Robert Pollack +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +import types + +from sage.modules.module import Module +from sage.modular.dirichlet import DirichletCharacter +from sage.modular.arithgroup.all import Gamma0 +from sage.modular.arithgroup.arithgroup_element import ArithmeticSubgroupElement +from sage.rings.arith import binomial +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.rings.arith import valuation +from modsym import PSModularSymbolElement_symk, PSModularSymbolElement_dist, PSModSymAction +from fund_domain import ManinRelations +from sage.rings.padics.precision_error import PrecisionError +from sage.rings.infinity import infinity as oo +from sage.structure.factory import UniqueFactory + +from distributions import Distributions, Symk +from modsym import PSModularSymbolElement, PSModularSymbolElement_symk, PSModularSymbolElement_dist, PSModSymAction +from fund_domain import ManinRelations +from manin_map import ManinMap +from sigma0 import Sigma0, Sigma0Element + +class PSModularSymbols_factory(UniqueFactory): + r""" + Create a space of Pollack-Stevens modular symbols. + + INPUT: + + - ``group`` -- integer or congruence subgroup + + - ``weight`` -- integer `\ge 0`, or ``None`` + + - ``sign`` -- integer; -1, 0, 1 + + - ``base_ring`` -- ring or ``None`` + + - ``p`` -- prime or ``None`` + + - ``prec_cap`` -- positive integer or None + + - ``coefficients`` -- the coefficient module (a special type of module, + typically distributions), or ``None`` + + If an explicit coefficient module is given, then the arguments ``weight``, + ``base_ring``, ``prec_cap``, and ``p`` are redundant and must be ``None``. + They are only relevant if ``coefficients`` is ``None``, in which case the + coefficient module is inferred from the other data. + + .. WARNING:: + + We emphasize that in the Pollack-Stevens notation, the ``weight`` is + the usual weight minus 2, so a classical weight 2 modular form + corresponds to a modular symbol of "weight 0". + + EXAMPLES:: + + sage: M = PSModularSymbols(Gamma0(7), weight=0, prec_cap = None); M + Space of modular symbols for Congruence Subgroup Gamma0(7) with sign 0 and values in Sym^0 Q^2 + + An example with an explict coefficient module:: + + sage: D = Distributions(3, 7, prec_cap=10) + sage: M = PSModularSymbols(Gamma0(7), coefficients=D); M + Space of overconvergent modular symbols for Congruence Subgroup Gamma0(7) with sign 0 and values in Space of 7-adic distributions with k=3 action and precision cap 10 + + TESTS:: + + sage: TestSuite(PSModularSymbols).run() + + """ + def create_key(self, group, weight=None, sign=0, base_ring=None, p=None, prec_cap=None, coefficients=None): + r""" + Sanitize input. + + EXAMPLES:: + + sage: D = Distributions(3, 7, prec_cap=10) + sage: M = PSModularSymbols(Gamma0(7), coefficients=D) # indirect doctest + + """ + if sign not in (-1,0,1): + raise ValueError("sign must be -1, 0, 1") + + if isinstance(group, (int, Integer)): + group = Gamma0(group) + + if coefficients is None: + if isinstance(group, DirichletCharacter): + character = group.minimize_base_ring() + group = Gamma0(character.modulus()) + if character.is_trivial(): + character = None + else: + character = None + + if weight is None: raise ValueError("you must specify a weight or coefficient module") + + if prec_cap is None: + coefficients = Symk(weight, base_ring, character) + else: + coefficients = Distributions(weight, p, prec_cap, base_ring, character) + else: + if weight is not None or base_ring is not None or p is not None or prec_cap is not None: + raise ValueError("if coefficients are specified, then weight, base_ring, p, and prec_cap must take their default value None") + + return (group, coefficients, sign) + + def create_object(self, version, key): + r""" + Create a space of modular symbols from ``key``. + + INPUT: + + - ``version`` -- the version of the object to create + + - ``key`` -- a tuple of parameters, as created by :meth:`create_key` + + EXAMPLES:: + + sage: D = Distributions(5, 7, 15) + sage: M = PSModularSymbols(Gamma0(7), coefficients=D) # indirect doctest + sage: M2 = PSModularSymbols(Gamma0(7), coefficients=D) # indirect doctest + sage: M is M2 + True + + """ + return PSModularSymbolSpace(*key) + +PSModularSymbols = PSModularSymbols_factory('PSModularSymbols') + +class PSModularSymbolSpace(Module): + r""" + A class for spaces of modular symbols that use Glenn Stevens' conventions. + This class should not be instantiated directly by the user: this is handled + by the factory object ``PSModularSymbols``. + + INPUT: + + - ``group`` -- congruence subgroup + + - ``coefficients`` -- a coefficient module + + - ``sign`` -- (default: 0); 0, -1, or 1 + + EXAMPLES:: + + sage: D = Distributions(2, 11) + sage: M = PSModularSymbols(Gamma0(2), coefficients=D); M.sign() + 0 + sage: M = PSModularSymbols(Gamma0(2), coefficients=D, sign=-1); M.sign() + -1 + sage: M = PSModularSymbols(Gamma0(2), coefficients=D, sign=1); M.sign() + 1 + + """ + def __init__(self, group, coefficients, sign=0): + r""" + INPUT: + + See :class:`PSModularSymbolSpace` + + EXAMPLES:: + + sage: D = Distributions(2, 11) + sage: M = PSModularSymbols(Gamma0(11), coefficients=D) + sage: type(M) + + sage: TestSuite(M).run() + + """ + Module.__init__(self, coefficients.base_ring()) + if sign not in [0,-1,1]: + # sign must be be 0, -1 or 1 + raise ValueError, "sign must be 0, -1, or 1" + self._group = group + self._coefficients = coefficients + if coefficients.is_symk(): + self.Element = PSModularSymbolElement_symk + else: + self.Element = PSModularSymbolElement_dist + self._sign = sign + # should distingish between Gamma0 and Gamma1... + self._source = ManinRelations(group.level()) + + # Register the action of 2x2 matrices on self. + + if coefficients.is_symk(): + action = PSModSymAction(Sigma0(1), self) + else: + action = PSModSymAction(Sigma0(self.prime()), self) + + self._populate_coercion_lists_(action_list=[action]) + + def _element_constructor_(self, data): + r""" + Construct an element of self from data. + """ + if isinstance(data, PSModularSymbolElement): + data = data._map + elif isinstance(data, ManinMap): + pass + else: + # a dict, or a single distribution specifying a constant symbol, etc + data = ManinMap(self._coefficients, self._source, data) + + if data._codomain != self._coefficients: + data = data.extend_codomain(self._coefficients) + + return self.element_class(data, self, construct=True) + + def _coerce_map_from_(self, other): + r""" + Used for comparison and coercion. + + EXAMPLE:: + + sage: M1 = PSModularSymbols(Gamma0(11), coefficients=Symk(3)) + sage: M2 = PSModularSymbols(Gamma0(11), coefficients=Symk(3,Qp(11))) + sage: M3 = PSModularSymbols(Gamma0(11), coefficients=Symk(4)) + sage: M4 = PSModularSymbols(Gamma0(11), coefficients=Distributions(3, 11, 10)) + sage: M1.has_coerce_map_from(M2) + False + sage: M2.has_coerce_map_from(M1) + True + sage: M1.has_coerce_map_from(M3) + False + sage: M1.has_coerce_map_from(M4) + False + sage: M2.has_coerce_map_from(M4) + True + """ + if isinstance(other, PSModularSymbolSpace): + if other.group() == self.group() \ + and self.coefficient_module().has_coerce_map_from(other.coefficient_module()): + return True + else: + return False + + def _repr_(self): + r""" + Returns print representation. + + EXAMPLES:: + + sage: D = Distributions(2, 11) + sage: M = PSModularSymbols(Gamma0(2), coefficients=D) + sage: M._repr_() + 'Space of overconvergent modular symbols for Congruence Subgroup Gamma0(2) with sign 0 and values in Space of 11-adic distributions with k=2 action and precision cap 20' + + """ + if self.coefficient_module().is_symk(): + s = "Space of modular symbols for " + else: + s = "Space of overconvergent modular symbols for " + s += "%s with sign %s and values in %s"%(self.group(), self.sign(), self.coefficient_module()) + return s + + def source(self): + r""" + Return the domain of the modular symbols in this space. + + OUTPUT: + + A :class:`sage.modular.pollack_stevens.fund_domain.PSModularSymbolsDomain` + + EXAMPLES:: + + sage: D = Distributions(2, 11); M = PSModularSymbols(Gamma0(2), coefficients=D) + sage: M.source() + Manin Relations of level 2 + + """ + return self._source + + def coefficient_module(self): + r""" + Return the coefficient module of this space. + + EXAMPLES:: + + sage: D = Distributions(2, 11); M = PSModularSymbols(Gamma0(2), coefficients=D) + sage: M.coefficient_module() + Space of 11-adic distributions with k=2 action and precision cap 20 + sage: M.coefficient_module() is D + True + + """ + return self._coefficients + + def group(self): + r""" + Return the congruence subgroup of this space. + + EXAMPLES:: + + sage: D = Distributions(2, 5) + sage: G = Gamma0(23) + sage: M = PSModularSymbols(G, coefficients=D) + sage: M.group() + Congruence Subgroup Gamma0(23) + sage: D = Symk(4) + sage: G = Gamma1(11) + sage: M = PSModularSymbols(G, coefficients=D) + sage: M.group() + Congruence Subgroup Gamma1(11) + + """ + return self._group + + def sign(self): + r""" + Return the sign of this space. + + EXAMPLES:: + + sage: D = Distributions(3, 17) + sage: M = PSModularSymbols(Gamma(5), coefficients=D) + sage: M.sign() + 0 + sage: D = Symk(4) + sage: M = PSModularSymbols(Gamma1(8), coefficients=D, sign=-1) + sage: M.sign() + -1 + + """ + return self._sign + + def ngens(self): + r""" + Returns the number of generators defining this space. + + EXAMPLES:: + + sage: D = Distributions(4, 29) + sage: M = PSModularSymbols(Gamma1(12), coefficients=D) + sage: M.ngens() + 5 + sage: D = Symk(2) + sage: M = PSModularSymbols(Gamma0(2), coefficients=D) + sage: M.ngens() + 2 + """ + return len(self._source.indices()) + + def ncoset_reps(self): + r""" + Returns the number of coset representatives defining the domain of the + modular symbols in this space. + + OUTPUT: + + The number of coset representatives stored in the manin relations. + (Just the size of P^1(Z/NZ)) + + EXAMPLES:: + + sage: D = Symk(2) + sage: M = PSModularSymbols(Gamma0(2), coefficients=D) + sage: M.ncoset_reps() + 3 + + """ + return len(self._source.reps()) + + def level(self): + r""" + Returns the level `N`, where this space is of level `\Gamma_0(N)`. + + EXAMPLES:: + + sage: D = Distributions(7, 11) + sage: M = PSModularSymbols(Gamma1(14), coefficients=D) + sage: M.level() + 14 + + """ + return self._source.level() + + def _grab_relations(self): + r""" + This is used internally as part of a consistency check. + + EXAMPLES:: + + sage: D = Distributions(4, 3) + sage: M = PSModularSymbols(Gamma1(13), coefficients=D) + sage: M._grab_relations() + [[(1, [1 0] + [0 1], 0)], [(-1, [-1 -1] + [ 0 -1], 0)], [(1, [1 0] + [0 1], 2)], [(1, [1 0] + [0 1], 3)], [(1, [1 0] + [0 1], 4)], [(1, [1 0] + [0 1], 5)]] + """ + S0N = Sigma0(self._source._N) + v = [] + for r in range(len(self._source.gens())): + for j in range(len(self._source.reps())): + R = self._source.relations(j) + if len(R) == 1 and R[0][2] == self._source.indices(r): + if R[0][0] != -1 or R[0][1] != S0N(1): + v = v + [R] + return v + + def precision_cap(self): + r""" + Returns the number of moments of each element of this space. + + EXAMPLES:: + + sage: D = Distributions(2, 5) + sage: M = PSModularSymbols(Gamma1(13), coefficients=D) + sage: M.precision_cap() + 20 + sage: D = Distributions(3, 7, prec_cap=10) + sage: M = PSModularSymbols(Gamma0(7), coefficients=D) + sage: M.precision_cap() + 10 + + """ +### WARNING -- IF YOU ARE WORKING IN SYM^K(Q^2) THIS WILL JUST RETURN K-1. NOT GOOD + + return self.coefficient_module()._prec_cap + + def weight(self): + r""" + Returns the weight of this space. + + .. WARNING:: + + We emphasize that in the Pollack-Stevens notation, this is the usual + weight minus 2, so a classical weight 2 modular form corresponds to a + modular symbol of "weight 0". + + EXAMPLES:: + + sage: D = Symk(5) + sage: M = PSModularSymbols(Gamma1(7), coefficients=D) + sage: M.weight() + 5 + + """ + return self.coefficient_module()._k + + def prime(self): + r""" + Returns the prime of this space. + + EXAMPLES: + sage: D = Distributions(2, 11) + sage: M = PSModularSymbols(Gamma(2), coefficients=D) + sage: M.prime() + 11 + + """ + return self.coefficient_module()._p + + def _p_stabilize_parent_space(self, p, new_base_ring): + r""" + Returns the space of Pollack-Stevens modular symbols of level + ``p * N``, with changed base ring. This is used internally when + constructing the p-stabilization of a modular symbol. + + INPUT: + + - ``p`` -- prime number + - ``new_base_ring`` -- the base ring of the result + + OUTPUT: + + The space of modular symbols of level ``p * N``, where N is the level + of this space. + + EXAMPLES:: + + sage: D = Distributions(2, 7); M = PSModularSymbols(Gamma(13), coefficients=D) + sage: M._p_stabilize_parent_space(7, M.base_ring()) + Space of overconvergent modular symbols for Congruence Subgroup + Gamma(91) with sign 0 and values in Space of 7-adic distributions + with k=2 action and precision cap 20 + + sage: D = Distributions(4, 17); M = PSModularSymbols(Gamma1(3), coefficients=D) + sage: M._p_stabilize_parent_space(17, Qp(17)) + Space of overconvergent modular symbols for Congruence + Subgroup Gamma1(51) with sign 0 and values in Space of + 17-adic distributions with k=4 action and precision cap 20 + + """ + N = self.level() + if N % p == 0: + raise ValueError("the level isn't prime to p") + from sage.modular.arithgroup.all import Gamma, is_Gamma, Gamma0, is_Gamma0, Gamma1, is_Gamma1 + G = self.group() + if is_Gamma0(G): + G = Gamma0(N*p) + elif is_Gamma1(G): + G = Gamma1(N*p) + elif is_Gamma(G): + G = Gamma(N*p) + else: + raise NotImplementedError + return PSModularSymbols(G, coefficients=self.coefficient_module().change_ring(new_base_ring), sign=self.sign()) + + def _specialize_parent_space(self, new_base_ring): + r""" + Internal function that is used by the specialize method on + elements. It returns a space with same parameters as this + one, but over ``new_base_ring``. + + INPUT: + + - ``new_base_ring`` -- a ring + + OUTPUT: + + A space of modular symbols to which our space specializes. + + EXAMPLES:: + + sage: D = Distributions(7, 5); M = PSModularSymbols(Gamma0(2), coefficients=D); M + Space of overconvergent modular symbols for Congruence Subgroup Gamma0(2) with sign 0 and values in Space of 5-adic distributions with k=7 action and precision cap 20 + sage: M._specialize_parent_space(QQ) + Space of modular symbols for Congruence Subgroup Gamma0(2) with sign 0 and values in Sym^7 Q^2 + sage: M.base_ring() + 5-adic Ring with capped absolute precision 20 + sage: M._specialize_parent_space(QQ).base_ring() + Rational Field + + """ + return PSModularSymbols(self.group(), coefficients=self.coefficient_module().specialize(new_base_ring), sign=self.sign()) + + def _lift_parent_space(self, p, M, new_base_ring): + r""" + Used internally to lift a space of modular symbols to space of + overconvergent modular symbols. + + INPUT: + + - ``p`` -- prime + - ``M`` -- precision cap + - ``new_base_ring`` -- ring + + OUTPUT: + + A space of distribution valued modular symbols. + + EXAMPLES:: + + sage: D = Distributions(4, 17, 2); M = PSModularSymbols(Gamma1(3), coefficients=D) + sage: D.is_symk() + False + sage: M._lift_parent_space(17, 10, Qp(17)) + Traceback (most recent call last): + ... + TypeError: Coefficient module must be a Symk + sage: PSModularSymbols(Gamma1(3), weight=1)._lift_parent_space(17,10,Qp(17)) + Space of overconvergent modular symbols for Congruence Subgroup Gamma1(3) with sign 0 and values in Space of 17-adic distributions with k=1 action and precision cap 10 + + """ + if self.coefficient_module().is_symk(): + return PSModularSymbols(self.group(), coefficients=self.coefficient_module().lift(p, M, new_base_ring), sign=self.sign()) + else: + raise TypeError("Coefficient module must be a Symk") + + def change_ring(self, new_base_ring): + r""" + Changes the base ring of this space to ``new_base_ring``. + + INPUT: + + - ``new_base_ring`` -- a ring + + OUTPUT: + + A space of modular symbols over the specified base. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.distributions import Symk + sage: D = Symk(4) + sage: M = PSModularSymbols(Gamma(6), coefficients=D); M + Space of modular symbols for Congruence Subgroup Gamma(6) with sign 0 and values in Sym^4 Q^2 + sage: M.change_ring(Qp(5,8)) + Space of modular symbols for Congruence Subgroup Gamma(6) with sign 0 and values in Sym^4 Q_5^2 + + """ + return PSModularSymbols(self.group(), coefficients=self.coefficient_module().change_ring(new_base_ring), sign=self.sign()) + + def _an_element_(self): +# WARNING -- THIS ISN'T REALLY AN ELEMENT OF THE SPACE BECAUSE IT DOESN'T +# SATISFY THE MANIN RELATIONS + + r""" + Returns the cusps associated to an element of a congruence subgroup. + + OUTPUT: + + An element of the modular symbol space. + + Returns a "typical" element of this space; in this case the constant + map sending every element to an element of the coefficient module. + + EXAMPLES:: + + sage: D = Symk(4) + sage: M = PSModularSymbols(Gamma(6), coefficients=D) + sage: x = M.an_element(); x # indirect doctest + Modular symbol of level 6 with values in Sym^4 Q^2 + sage: x.values() + [(0, 1, 2, 3, 4), (0, 1, 2, 3, 4), (0, 1, 2, 3, 4)] + sage: D = Symk(2, Qp(11)); M = PSModularSymbols(Gamma0(2), coefficients=D) + sage: x = M.an_element(); x.values() + [(0, 1 + O(11^20), 2 + O(11^20)), (0, 1 + O(11^20), 2 + O(11^20))] + sage: x in M + True + + """ + return self(self.coefficient_module().an_element()) + + def random_element(self,M=None): + r""" + Returns a random OMS in this space with M moments + + INPUT: + + - ``M`` -- positive integer + + OUTPUT: + + An element of the modular symbol space with ``M`` moments + + Returns a random element in this space by randomly choosing values of distributions + on all but one divisor, and solves the difference equation to determine the value + on the last divisor. + + """ + if (M == None) and (not self.coefficient_module().is_symk()): + M = self.coefficient_module().precision_cap() + + k = self.coefficient_module()._k + p = self.prime() + manin = self.source() + +# ## There must be a problem here with that +1 -- should be variable depending on a c of some matrix +# ## We'll need to divide by some power of p and so we add extra accuracy here. +# if k != 0: +# MM = M + valuation(k,p) + 1 + M.exact_log(p) +# else: +# MM = M + M.exact_log(p) + 1 + + ## this loop runs thru all of the generators (except (0)-(infty)) and randomly chooses a distribution + ## to assign to this generator (in the 2,3-torsion cases care is taken to satisfy the relevant relation) + D = {} + for g in manin.gens(): + D[g] = self.coefficient_module().random_element(M) +# print "pre:",D[g] + if g in manin.reps_with_two_torsion() and g in manin.reps_with_three_torsion: + raise ValueError("Level 1 not implemented") + if g in manin.reps_with_two_torsion(): + gamg = manin.two_torsion_matrix(g) + D[g] = D[g] - D[g] * gamg + else: + if g in manin.reps_with_three_torsion(): + gamg = manin.three_torsion_matrix(g) + D[g] = 2*D[g] - D[g] * gamg - D[g] * gamg**2 +# print "post:",D[g] + + ## now we compute nu_infty of Prop 5.1 of [PS1] + t = self.coefficient_module().zero_element() + for g in manin.gens()[1:]: + if (not g in manin.reps_with_two_torsion()) and (not g in manin.reps_with_three_torsion()): +# print "g:", g + # print "D[g]:",D[g] + # print "manin",manin.gammas[g] + # print "D*m:",D[g] * manin.gammas[g] + # print "-------" + t += D[g] * manin.gammas[g] - D[g] + else: + if g in MR.reps_with_two_torsion(): + t -= D[g] + else: + t -= D[g] + + ## If k = 0, then t has total measure zero. However, this is not true when k != 0 + ## (unlike Prop 5.1 of [PS1] this is not a lift of classical symbol). + ## So instead we simply add (const)*mu_1 to some (non-torsion) v[j] to fix this + ## here since (mu_1 |_k ([a,b,c,d]-1))(trival char) = chi(a) k a^{k-1} c , + ## we take the constant to be minus the total measure of t divided by (chi(a) k a^{k-1} c) + + if k != 0: + j = 1 + g = manin.gens()[j] + while (g in manin.reps_with_two_torsion()) or (g in manin.reps_with_three_torsion()) and (j < len(manin.gens())): + j = j + 1 + g = manin.gens()[j] + if j == len(manin.gens()): + raise ValueError("everything is 2 or 3 torsion! NOT YET IMPLEMENTED IN THIS CASE") + + gam = manin.gammas[g] + a = gam.matrix()[0,0] + c = gam.matrix()[1,0] + + if self.coefficient_module()._character != None: + chara = self.coefficient_module()._character(a) + else: + chara = 1 + err = -t.moment(0)/(chara*k*a**(k-1)*c) + v = [0 for j in range(M)] + v[1] = 1 + mu_1 = err * self.coefficient_module()(v) + D[g] += mu_1 +# print "Modifying: ",D[g] + t = t + mu_1 * gam - mu_1 + + Id = manin.gens()[0] + if not self.coefficient_module().is_symk(): + mu = t.solve_diff_eqn() + D[Id] = -mu + # print "Last:",D[Id] + else: + if self.coefficient_module()._k == 0: + D[Id] = self.coefficient_module().random_element() + else: + raise ValueError("Not implemented for symk with k>0 yet") + + return self(D) + +def cusps_from_mat(g): + r""" + Returns the cusps associated to an element of a congruence subgroup. + + INPUT: + + - ``g`` -- an element of a congruence subgroup or a matrix + + OUTPUT: + + A tuple of cusps associated to ``g``. + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import cusps_from_mat + sage: g = SL2Z.one() + sage: cusps_from_mat(g) + (+Infinity, 0) + + You can also just give the matrix of g:: + + sage: type(g) + + sage: cusps_from_mat(g.matrix()) + (+Infinity, 0) + + Another example:: + + sage: from sage.modular.pollack_stevens.space import cusps_from_mat + sage: g = GammaH(3, [2]).generators()[1].matrix(); g + [-1 1] + [-3 2] + sage: cusps_from_mat(g) + (1/3, 1/2) + + """ + if isinstance(g, ArithmeticSubgroupElement) or isinstance(g, Sigma0Element): + g = g.matrix() + a, b, c, d = g.list() + if c: ac = a/c + else: ac = oo + if d: bd = b/d + else: bd = oo + return ac, bd + +def ps_modsym_from_elliptic_curve(E): + r""" + Returns the PS modular symbol associated to an elliptic curve + defined over the rationals. + + INPUT: + + - ``E`` -- an elliptic curve defined over the rationals + + OUTPUT: + + The Pollack-Stevens modular symbol associated to ``E`` + + EXAMPLES:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve('113a1') + sage: symb = ps_modsym_from_elliptic_curve(E) + sage: symb + Modular symbol of level 113 with values in Sym^0 Q^2 + sage: symb.values() + [-1/2, 3/2, -2, 1/2, 0, 1, 2, -3/2, 0, -3/2, 0, -1/2, 0, 1, -2, 1/2, 0, + 0, 2, 0, 0] + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_elliptic_curve + sage: E = EllipticCurve([0,1]) + sage: symb = ps_modsym_from_elliptic_curve(E) + sage: symb.values() + [-1/6, 7/12, 1, 1/6, -5/12, 1/3, -7/12, -1, -1/6, 5/12, 1/4, -1/6, -5/12] + + """ + if not (E.base_ring() is QQ): + raise ValueError("The elliptic curve must be defined over the rationals.") + N = E.conductor() + V = PSModularSymbols(Gamma0(N), 0) + D = V.coefficient_module() + manin = V.source() + plus_sym = E.modular_symbol(sign = 1) + minus_sym = E.modular_symbol(sign = -1) + val = {} + for g in manin.gens(): + ac, bd = cusps_from_mat(g) + val[g] = D([plus_sym(ac) + minus_sym(ac) - plus_sym(bd) - minus_sym(bd)]) + return V(val) + +def ps_modsym_from_simple_modsym_space(A, name="alpha"): + r""" + Returns some choice -- only well defined up a nonzero scalar (!) -- of a + Pollack-Stevens modular symbol that corresponds to ``A``. + + INPUT: + + - ``A`` -- nonzero simple Hecke equivariant new space of modular symbols, + which need not be cuspidal. + + OUTPUT: + + A choice of corresponding Pollack-Stevens modular symbols; when dim(A)>1, + we make an arbitrary choice of defining polynomial for the codomain field. + + EXAMPLES: + + The level 11 example:: + + sage: from sage.modular.pollack_stevens.space import ps_modsym_from_simple_modsym_space + sage: A = ModularSymbols(11, sign=1, weight=2).decomposition()[0] + sage: A.is_cuspidal() + True + sage: f = ps_modsym_from_simple_modsym_space(A); f + Modular symbol of level 11 with values in Sym^0 Q^2 + sage: f.values() + [1, -5/2, -5/2] + sage: f.weight() # this is A.weight()-2 !!!!!! + 0 + + And the -1 sign for the level 11 example:: + + sage: A = ModularSymbols(11, sign=-1, weight=2).decomposition()[0] + sage: f = ps_modsym_from_simple_modsym_space(A); f.values() + [0, 1, -1] + + A does not have to be cuspidal; it can be Eisenstein:: + + sage: A = ModularSymbols(11, sign=1, weight=2).decomposition()[1] + sage: A.is_cuspidal() + False + sage: f = ps_modsym_from_simple_modsym_space(A); f + Modular symbol of level 11 with values in Sym^0 Q^2 + sage: f.values() + [1, 0, 0] + + We create the simplest weight 2 example in which ``A`` has dimension + bigger than 1:: + + sage: A = ModularSymbols(23, sign=1, weight=2).decomposition()[0] + sage: f = ps_modsym_from_simple_modsym_space(A); f.values() + [1, 0, 0, 0, 0] + sage: A = ModularSymbols(23, sign=-1, weight=2).decomposition()[0] + sage: f = ps_modsym_from_simple_modsym_space(A); f.values() + [0, 1, -alpha, alpha, -1] + sage: f.base_ring() + Number Field in alpha with defining polynomial x^2 + x - 1 + + We create the +1 modular symbol attached to the weight 12 modular form ``Delta``:: + + sage: A = ModularSymbols(1, sign=+1, weight=12).decomposition()[0] + sage: f = ps_modsym_from_simple_modsym_space(A); f + Modular symbol of level 1 with values in Sym^10 Q^2 + sage: f.values() + [(-1620/691, 0, 1, 0, -9/14, 0, 9/14, 0, -1, 0, 1620/691), (1620/691, 1620/691, 929/691, -453/691, -29145/9674, -42965/9674, -2526/691, -453/691, 1620/691, 1620/691, 0), (0, -1620/691, -1620/691, 453/691, 2526/691, 42965/9674, 29145/9674, 453/691, -929/691, -1620/691, -1620/691)] + + And, the -1 modular symbol attached to ``Delta``:: + + sage: A = ModularSymbols(1, sign=-1, weight=12).decomposition()[0] + sage: f = ps_modsym_from_simple_modsym_space(A); f + Modular symbol of level 1 with values in Sym^10 Q^2 + sage: f.values() + [(0, 1, 0, -25/48, 0, 5/12, 0, -25/48, 0, 1, 0), (0, -1, -2, -119/48, -23/12, -5/24, 23/12, 3, 2, 0, 0), (0, 0, 2, 3, 23/12, -5/24, -23/12, -119/48, -2, -1, 0)] + + A consistency check with :meth:`sage.modular.pollack_stevens.space.ps_modsym_from_simple_modsym_space`:: + + sage: from sage.modular.pollack_stevens.space import (ps_modsym_from_elliptic_curve, ps_modsym_from_simple_modsym_space) + sage: E = EllipticCurve('11a') + sage: f_E = ps_modsym_from_elliptic_curve(E); f_E.values() + [-1/5, 3/2, -1/2] + sage: A = ModularSymbols(11, sign=1, weight=2).decomposition()[0] + sage: f_plus = ps_modsym_from_simple_modsym_space(A); f_plus.values() + [1, -5/2, -5/2] + sage: A = ModularSymbols(11, sign=-1, weight=2).decomposition()[0] + sage: f_minus = ps_modsym_from_simple_modsym_space(A); f_minus.values() + [0, 1, -1] + + We find that a linear combination of the plus and minus parts equals the + Pollack-Stevens symbol attached to ``E``. This illustrates how + ``ps_modsym_from_simple_modsym_space`` is only well-defined up to a nonzero + scalar:: + + sage: (-1/5)*vector(QQ, f_plus.values()) + vector(QQ, f_minus.values()) + (-1/5, 3/2, -1/2) + sage: vector(QQ, f_E.values()) + (-1/5, 3/2, -1/2) + + The next few examples all illustrate the ways in which exceptions are + raised if A does not satisfy various constraints. + + First, ``A`` must be new:: + + sage: A = ModularSymbols(33,sign=1).cuspidal_subspace().old_subspace() + sage: ps_modsym_from_simple_modsym_space(A) + Traceback (most recent call last): + ... + ValueError: A must be new + + ``A`` must be simple:: + + sage: A = ModularSymbols(43,sign=1).cuspidal_subspace() + sage: ps_modsym_from_simple_modsym_space(A) + Traceback (most recent call last): + ... + ValueError: A must be simple + + ``A`` must have sign -1 or +1 in order to be simple:: + + sage: A = ModularSymbols(11).cuspidal_subspace() + sage: ps_modsym_from_simple_modsym_space(A) + Traceback (most recent call last): + ... + ValueError: A must have sign +1 or -1 (otherwise it is not simple) + + The dimension must be positive:: + + sage: A = ModularSymbols(10).cuspidal_subspace(); A + Modular Symbols subspace of dimension 0 of Modular Symbols space of dimension 3 for Gamma_0(10) of weight 2 with sign 0 over Rational Field + sage: ps_modsym_from_simple_modsym_space(A) + Traceback (most recent call last): + ... + ValueError: A must positive dimension + + We check that forms of nontrivial character are getting handled correctly:: + + sage: f = Newforms(Gamma1(13), names='a')[0] + sage: phi = f.PS_modular_symbol() + sage: phi.hecke(7) + Modular symbol of level 13 with values in Sym^0 (Number Field in alpha with defining polynomial x^2 + 3*x + 3)^2 + sage: phi.hecke(7).values() + [0, 0, 0, 0, 0] + """ + if A.dimension() == 0: + raise ValueError, "A must positive dimension" + + if A.sign() == 0: + raise ValueError, "A must have sign +1 or -1 (otherwise it is not simple)" + + if not A.is_new(): + raise ValueError, "A must be new" + + if not A.is_simple(): + raise ValueError, "A must be simple" + + M = A.ambient_module() + w = A.dual_eigenvector(name) + K = w.base_ring() + chi = A.q_eigenform_character(name) + V = PSModularSymbols(chi, A.weight()-2, base_ring=K, sign=A.sign()) + D = V.coefficient_module() + N = V.level() + k = V.weight() # = A.weight() - 2 + manin = V.source() + val = {} + for g in manin.gens(): + ac, bd = cusps_from_mat(g) + v = [] + for j in range(k+1): + # TODO: The following might be backward: it should be the coefficient of X^j Y^(k-j) + v.append(w.dot_product(M.modular_symbol([j, ac, bd]).element()) * (-1)**(k-j)) + val[g] = D(v) + return V(val)