changeset 1:c3d3ff442f2c

A lot of changes. Refactoring of code, bug fixes,...
author Marc Masdeu <masdeu@math.columbia.edu>
date Sun, 03 Jul 2011 20:20:59 +0200
parents a296fa27420d
children f557a75d18d1
files ocmodule.py pautomorphicform.py shimuracurve.py
diffstat 3 files changed, 722 insertions(+), 640 deletions(-) [+]
line wrap: on
line diff
--- a/ocmodule.py	Wed Jun 15 12:01:49 2011 +0200
+++ b/ocmodule.py	Sun Jul 03 20:20:59 2011 +0200
@@ -11,23 +11,26 @@
 from sage.modular.btquotients.utility import our_exp
 
 class OCVnElement(ModuleElement):
-    def __init__(self,_parent,val=0,prec=100):
-        ModuleElement.__init__(self, _parent)
+    def __init__(self,_parent,val=0,quick=False):
+        ModuleElement.__init__(self,_parent)
         self._parent=_parent
         self._n=self._parent._n
+        self._nhalf=IntegerRing()(self._n/2)
         self._depth=self._parent._depth
-        self._prec=prec
-        if(isinstance(val,OCVnElement)):
-            d=min([val._parent._depth,_parent._depth])
-            assert(val._parent.weight()==_parent.weight())
-            self._val=Matrix(self._parent._ZZp,self._depth,1,0)
-            for ii in range(d):
-                self._val[ii,0]=val._val[ii,0]
+        if(quick):
+            self._val=copy(val)
         else:
-            try:
-                self._val=MatrixSpace(self._parent._ZZp,self._depth,1)(val)
-            except:
-                self._val=Matrix(self._parent._ZZp,self._depth,1,[val]*self._depth)
+            if(isinstance(val,OCVnElement)):
+                d=min([val._parent._depth,_parent._depth])
+                assert(val._parent.weight()==_parent.weight())
+                self._val=Matrix(self._parent._ZZp,self._depth,1,0)
+                for ii in range(d):
+                    self._val[ii,0]=val._val[ii,0]
+            else:
+                try:
+                    self._val=MatrixSpace(self._parent._ZZp,self._depth,1)(val)
+                except:
+                    self._val=Matrix(self._parent._ZZp,self._depth,1,[val]*self._depth)
 
     def __getitem__(self,r):
         return self._val[r,0]
@@ -40,49 +43,47 @@
         return A.solve_right(self._val)
 
     def _add_(self,y):
-        prec=min(self._prec,y._prec)
         assert(self._depth==y._depth and (self._n==y._n or any[self._val==0,y._val==0]))
         val=self._val+y._val
-        return OCVnElement(self._parent,val,prec)
+        return OCVnElement(self._parent,val,quick=True)
 
     def _sub_(self,y):
-        prec=min(self._prec,y._prec)
         assert(self._depth==y._depth and (self._n==y._n or any[self._val==0,y._val==0]))
         val=self._val-y._val
-        return OCVnElement(self._parent,val,prec)
+        return OCVnElement(self._parent,val,quick=True)
 
     def l_act_by(self,x):
         assert(x.nrows()==2 and x.ncols()==2) #An element of GL2
         K=self._parent._ZZp
         assert(x!=0)
-        y=self._l_act_by(x[0,0],x[0,1],x[1,0],x[1,1])
-        return (K(x.determinant())**(-IntegerRing()(self._n/2)))*y
+        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):
         assert(x.nrows()==2 and x.ncols()==2) #An element of GL2
         K=self._parent._ZZp
         assert(x!=0)
-        y=self._l_act_by(x[1,1],-x[0,1],-x[1,0],x[0,0])
-        return (K(x.determinant())**(-IntegerRing()(self._n/2)))*y
+        extra=x.determinant()**(-self._nhalf)
+        return self._l_act_by(x[1,1],-x[0,1],-x[1,0],x[0,0],extrafactor=extra)
 
-    def _l_act_by(self,a,b,c,d):
+    def _l_act_by(self,a,b,c,d,extrafactor=1):
         R=self._parent._ZZp
-        t=min([R(a).valuation(),R(b).valuation(),R(c).valuation(),R(d).valuation()])
-        factor=R.prime()**(IntegerRing()(-t))
-        tmp=self._parent._get_powers_and_mult(factor*a,factor*b,factor*c,factor*d,factor**(-self._n),self._val)
-        return(OCVnElement(self._parent,tmp,self._prec))
+        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 OCVnElement(self._parent,extrafactor*factor**(-self._n)*x*self._val,quick=True)
+        except KeyError:
+            tmp=self._parent._get_powers_and_mult(factor*a,factor*b,factor*c,factor*d,extrafactor*factor**(-self._n),self._val)
+            return OCVnElement(self._parent,tmp,quick=True)
 
     def _rmul_(self,a):
     #assume that a is a scalar
-        if(a.parent().is_exact()):
-            prec=self._prec
-        else:
-            prec=min([self._prec,a.precision_relative()])
-        y=OCVnElement(self._parent,a*self._val,prec)
+        y=OCVnElement(self._parent,a*self._val,quick=True)
         return y
 
     def precision(self):
-        return self._prec
+        #This needs to be thought more carefully...
+        return self._parent._prec
 
     def _repr_(self):
         R=PowerSeriesRing(self._parent._ZZp,default_prec=self._depth,name='z')
@@ -90,8 +91,11 @@
         s=str(sum([R(self._val[ii,0]*z**ii) for ii in range(self._depth)]))
         return s
 
+    def __cmp__(self,other):
+        return cmp(self._val,other._val)
+
     def __nonzero__(self):
-        return(any([self._val[ii,0].__nonzero__() for ii in range(self._depth)]))
+        return self._val!=0
 
     def evaluate(self,P):
         p=self._parent._ZZp.prime()
@@ -111,23 +115,23 @@
         self._basis=copy(basis)
         self._n=n
         self._ZZp=ZZp
+        self._ZZmod=Zmod(self._ZZp.prime()**(self._ZZp.precision_cap()))
+
         if(depth==None):
             depth=n+1
         self._depth=depth
+        self._PowerSeries=PowerSeriesRing(self._ZZmod,default_prec=self._depth,name='z')
         self._powers=dict()
         self._populate_coercion_lists_()
 
     def _an_element_(self):
-        return OCVnElement(self,1)
+        return OCVnElement(self,Matrix(self._ZZp,self._depth,1,range(1,self._depth+1)),quick=True)
 
     def _coerce_map_from_(self, S):
        """
        Nothing coherces here, except OCVnElement
        """
-       if(isinstance(S,OCVnElement)):
-           return True
-       else:
-           return False
+       return False
 
     def _element_constructor_(self,x):
         #Code how to coherce x into the space
@@ -137,43 +141,39 @@
     def base(self):
         return self._ZZp
 
+
     def _get_powers_and_mult(self,a,b,c,d,lambd,vect):
         p=self._ZZp.prime()
         #print a,b,c,d
-        ZZmod=Zmod(p**(self._ZZp.precision_cap()))
-        R=PowerSeriesRing(ZZmod,default_prec=self._depth,name='z')
+        ZZmod=self._ZZmod
+        R=self._PowerSeries
         z=R.gen()
-        if(not self._powers.has_key((a,b,c,d))):
-            r=R([b,a])
-            s=R([d,c])
+        r=R([b,a])
+        s=R([d,c])
+        if(self._depth==self._n+1):
             rpows=[R(1)]
             spows=[R(1)]
-            for ii in range(self._depth-1):
+            for ii in range(self._n):
                 rpows.append(r*rpows[ii])
-            for ii in range(self._n):
                 spows.append(s*spows[ii])
-            if(self._depth>self._n+1):
-                assert(s[1].valuation(p)>s[0].valuation(p))
-                sinv=R(s**(-1))
-                sinvpows=[sinv]
-                for ii in range(self._n+1,self._depth):
-                    sinvpows.append(sinv*sinvpows[ii-self._n-1])
-
-            #x=Matrix(self._ZZp,self._depth,self._depth,0)
-            x=Matrix(ZZmod,self._depth,self._depth,0)
+            x=Matrix(ZZmod,self._n+1,self._n+1,0)
             for ii in range(self._n+1):
                 y=rpows[ii]*spows[self._n-ii]
                 for jj in range(self._depth):
-                    #x[ii,jj]=self._ZZp(y[jj])
                     x[ii,jj]=y[jj]
-            for ii in range(self._n+1,self._depth):
-                y=rpows[ii]*sinvpows[ii-self._n-1]
+        else:
+            ratio=r*(s**(-1))
+            y=s**self._n
+            x=Matrix(ZZmod,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]=self._ZZp(y[jj])
                     x[ii,jj]=y[jj]
-            self._powers[(a,b,c,d)]=x
-        x=self._powers[(a,b,c,d)]
-        return lambd*Matrix(self._ZZp,x.nrows(),x.ncols(),[self._ZZp(x[ii,jj]) for ii in range(x.nrows()) for jj in range(x.ncols())])*vect
+        xnew=x.change_ring(self._ZZp)
+        self._powers[(a,b,c,d)]=xnew
+        return self._ZZp(lambd)*xnew*vect
 
     def _repr_(self):
         s='Overconvergent coefficient module of weight n='+str(self._n)+' over the ring '+ str(self._ZZp)+' and depth '+str(self._depth)
@@ -182,7 +182,7 @@
     def basis(self):
         if(self._basis==[]):
             for jj in range(self._depth):
-                x=OCVnElement(self,Matrix(self._ZZp,self._depth,1,{(jj,0):1},sparse=False),self._ZZp.precision_cap())
+                x=OCVnElement(self,Matrix(self._ZZp,self._depth,1,{(jj,0):1},sparse=False),quick=True)
                 self._basis.append(x)
         return self._basis
 
--- a/pautomorphicform.py	Wed Jun 15 12:01:49 2011 +0200
+++ b/pautomorphicform.py	Sun Jul 03 20:20:59 2011 +0200
@@ -19,6 +19,7 @@
 from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
 from sage.rings.laurent_series_ring import LaurentSeriesRing
 from sage.modular.btquotients.utility import our_log
+from sage.modular.btquotients.utility import our_conj
 from sage.modular.btquotients.utility import our_exp
 from sage.modular.btquotients.utility import our_pow
 from sage.modular.btquotients.shimuracurve import EdgeDecomposition
@@ -28,75 +29,60 @@
 ###########################
 
 class HarmonicCocycle(ModuleElement):
-    def __init__(self,_parent,vec=None,prec=None):
+    def __init__(self,_parent,vec=None):
         ModuleElement.__init__(self,_parent)
 
+        self._parent=_parent
         if(isinstance(vec,HarmonicCocycle)):
             #The first argument is just a modular form that we copy as is
-            self._parent=vec._parent
-            self._prec=vec._prec
             self._ZZp=vec._ZZp
             self._nE=vec._nE
             self._wt=vec._wt
-            self._F=[OCVnElement(_parent,vec._F[ii],self._prec) for ii in range(self._nE)]
+            self._F=[OCVnElement(_parent,vec._F[ii],quick=True) for ii in range(self._nE)]
         elif(isinstance(vec,pAutomorphicForm)):
-            self._parent=_parent
             #need to put some assertions
-            self._prec=vec._prec
-            self._ZZp=Qp(_parent._X._p,prec=prec)
+            self._ZZp=Qp(_parent._X._p,prec=_parent._prec)
             self._nE=IntegerRing()(len(_parent._E))
             self._wt=_parent._k
-            self._F=[OCVnElement(_parent._U,vec._F[ii],self._prec).l_act_by(self._parent._X._edge_list[ii].rep) for ii in range(self._nE)]
+            E=_parent._E
+            self._F=[OCVnElement(_parent._U,vec._F[ii]).l_act_by(E[ii].rep) for ii in range(self._nE)]
         elif(isinstance(vec,dict)):
-            self._parent=_parent
-            assert(vec['p'])==self._parent._X._p
-            assert(vec['t'])==self._parent._U._depth
-            assert(vec['level'])==self._parent._X._level
-            assert(vec['k']==self._parent._U.weight()+2)
-            self._prec=mydata['prec']
-            self._prec=prec
-            self._ZZp=Qp(_parent._X._p,prec=self._prec)
+            assert(vec['p'])==_parent._X._p
+            assert(vec['t'])==_parent._U._depth
+            assert(vec['level'])==_parent._X._level
+            assert(vec['k']==_parent._U.weight()+2)
+            self._ZZp=Qp(_parent._X._p,prec=_parent._prec)
             self._wt=_parent._k
             self._nE=len(_parent._E)
-            self._F=[self._parent._U(c) for c in mydata['value']]
+            self._F=[_parent._U(c) for c in mydata['value']]
         elif(isinstance(vec,list) and len(vec)==len(_parent._E) and vec[0].parent() is _parent._U):
-            self._ZZp=Qp(_parent._X._p,prec=prec)
-            self._prec=prec
-            self._parent=_parent
+            self._ZZp=Qp(_parent._X._p,prec=_parent._prec)
             self._wt=_parent._k
             self._nE=len(_parent._E)
             self._F=copy(vec)
         else:
             try:
                 veczp=_parent._U._ZZp(vec)
-                self._ZZp=Qp(_parent._X._p,prec=prec)
-                self._prec=prec
-                self._parent=_parent
+                self._ZZp=Qp(_parent._X._p,prec=_parent._prec)
                 self._wt=_parent._k
                 self._nE=len(_parent._E)
-                self._F=[self._parent._U(veczp) for ii in range(self._nE)]
+                self._F=[_parent._U(veczp) for ii in range(self._nE)]
             except:
                 assert(0)
 
 
     def _add_(self,g):
-        prec=min([self._prec,g._prec])
         #Should ensure that self and g are modular forms of the same weight and on the same curve
         vec=[self._F[e]+g._F[e] for e in range(self._nE)]
-        return HarmonicCocycle(self._parent,vec,prec)
+        return HarmonicCocycle(self._parent,vec)
     def _sub_(self,g):
-        prec=min([self._prec,g._prec])
         #Should ensure that self and g are modular forms of the same weight and on the same curve
         vec=[self._F[e]-g._F[e] for e in range(self._nE)]
-        return HarmonicCocycle(self._parent,vec,prec)
+        return HarmonicCocycle(self._parent,vec)
 
     def _rmul_(self,a):
-        if(a.parent().is_exact()):
-            prec=self._prec
-        else:
-            prec=min([self._prec,a.precision_relative()])
         #Should ensure that 'a' is a scalar
-        return HarmonicCocycle(self._parent,[a*self._F[e] for e in range(self._nE)],prec)
+        return HarmonicCocycle(self._parent,[a*self._F[e] for e in range(self._nE)])
 
     def _repr_(self):
         tmp='Harmonic cocycle on '+str(self._parent)+':\n'
@@ -106,11 +92,20 @@
             tmp+='  '+str(e)+' | '+str(self._F[e])+'\n'
         return tmp
 
-    def _nonzero_(self):
-        return(any([self._F[e]._nonzero_() for e in range(self._nE)]))
+    def __eq__(self,other):
+        return all([self._F[e].__eq__(other._F[e]) for e in range(self._nE)])
+
+    def __ne__(self,other):
+        return any([self._F[e].__ne__(other._F[e]) for e in range(self._nE)])
+
+    def __nonzero__(self):
+        return any([self._F[e].__nonzero__() for e in range(self._nE)])
 
     def valuation(self):
-        return min([self._F[e].valuation() for e in range(self._nE)])
+        if not self.__nonzero__():
+            return self._F[0].valuation()
+        else:
+            return min([self._F[e].valuation() for e in range(self._nE)])
 
     #In HarmonicCocycle
     def evaluate(self,e1):
@@ -133,19 +128,22 @@
         ii=0
         for e in E:
             ii+=1
-            print ii,"/",len(E)
+            #print ii,"/",len(E)
             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)
-            new2=self.evaluate(e).l_act_by(e.inverse()).evaluate(exp)
+            new=self.evaluate(e).l_act_by(e.inverse()).evaluate(exp)
             value+=new
         return value
+    def mod_form(self,z=None,level=0):
+        return self.derivative(z,level,order=0)
 
     # In HarmonicCocycle
-    def mod_form(self,z=None,level=0):
+    def derivative(self,z=None,level=0,order=1):
         def F(z):
             R=PolynomialRing(z.parent(),'x')
             x=R.gen()
             center=self._parent._X._BT.whereis(z)
-            return self.riemann_sum(1/(x-z),center,level)
+            f=R(1)/(x-z)
+            return self.riemann_sum(R(IntegerRing()(order).factorial())/(x-z)**(order+1)+order/(z-our_conj(z))*f,center,level)
         if(z==None):
             return F
         else:
@@ -170,7 +168,7 @@
         ii=0
         for e in EList:
             ii+=1
-            print ii,"/",len(EList)
+            #print ii,"/",len(EList)
             b=e[0,1]
             d=e[1,1]
             y=(b-d*t1)/(b-d*t2)
@@ -199,7 +197,7 @@
         ii=0
         for e in EList:
             ii+=1
-            print ii,"/",len(EList)
+            #print ii,"/",len(EList)
             b=e[0,1]
             d=e[1,1]
             if(d!=0):
@@ -218,8 +216,8 @@
         self._X=X
         self._ZZp=Qp(self._X._p,prec=prec)
         Module.__init__(self,base=self._ZZp)
-        self._V=self._X._vertex_list
-        self._E=self._X._edge_list
+        self._E=self._X.get_edge_list()
+        self._V=self._X.get_vertex_list()
         self._basis_matrix=None
         self._U=OCVn(self._k-2,self._ZZp,self._k-1)
         self._populate_coercion_lists_()
@@ -257,7 +255,7 @@
         #Code how to coherce x into the space
         #Admissible values of x?
         if(isinstance(x,HarmonicCocycle) or isinstance(x,pAutomorphicForm)):
-            return HarmonicCocycle(self,x,prec=x._prec)
+            return HarmonicCocycle(self,x)
 
     def iota(self,g):
         return self._X.iota(g,self._prec)
@@ -273,7 +271,7 @@
         basis=[]
         for ii in range(basis_matrix.nrows()):
             r=list(basis_matrix.row(ii))
-            basis.append(HarmonicCocycle(self,[OCVnElement(self._U,Matrix(self._ZZp,self._k-1,1,r[e*(self._k-1):(e+1)*(self._k-1)]),self._prec) for e in range(nE)],self._prec))
+            basis.append(HarmonicCocycle(self,[OCVnElement(self._U,Matrix(self._ZZp,self._k-1,1,r[e*(self._k-1):(e+1)*(self._k-1)]),quick=True) for e in range(nE)]))
         return basis
 
     def dimension(self):
@@ -285,7 +283,7 @@
         nV=len(self._V)
         nE=len(self._E)
         stab_conds=[]
-        tmp=self._X._get_edge_stabs()
+        tmp=self._X.get_edge_stabs()
         p=self._X._p
         for e in self._E:
             gg=tmp[e.label]
@@ -299,14 +297,14 @@
         self._M=Matrix(self._ZZp,(nV+len(stab_conds))*(self._k-1),nE*(self._k-1),0,sparse=True)
         for v in self._V:
             for e in v.leaving_edges:
-                if e.rep.determinant().valuation(p)%2!=0:
+                if e.parity!=0:
                     continue
                 C=sum(self._U.l_matrix_representation([self.iota(x[0]) for x in e.links]))
                 for ii in range(self._k-1):
                     for jj in range(self._k-1):
                         self._M[v.label*(self._k-1)+ii,e.label*(self._k-1)+jj]+=C[ii,jj]
             for e in v.entering_edges:
-                if e.rep.determinant().valuation(p)%2!=0:
+                if e.parity!=0:
                     continue
 
                 C=sum(self._U.l_matrix_representation([self.iota(x[0]) for x in e.opposite[0].links]))
@@ -328,25 +326,27 @@
         self._basis_matrix=Matrix(self._ZZp,len(K),nE*(self._k-1),K)
 
     def hecke_operator(self,l):
-        assert(IntegerRing()(l).is_prime())
+        l=IntegerRing()(l)
+        if(not l.is_prime()):
+            raise ValueError, "l must be prime"
 
         def T(f):
             R=Qp(f._ZZp.prime(),prec=f._ZZp.precision_cap())
-            HeckeData=self._X.hecke_coset_reps(l)
+            HeckeData=self._X.hecke_data(l)
             Tf=[]
             if(self._X.generic_fiber().level()%l==0):
                 factor=RationalField()(l**(IntegerRing()((self._k-2)/2))/(l+1))
             else:
                 factor=RationalField()(l**(IntegerRing()((self._k-2)/2)))
             for jj in range(len(self._E)):
-                tmp=OCVnElement(self._U,MatrixSpace(self._ZZp,self._k-1,1)(0),self._prec)
-                for ii in range(l+1):
-                    t=HeckeData[ii][1][jj]#.edge_list[jj]
-                    gg=HeckeData[ii][0]#.acter
+                tmp=OCVnElement(self._U,MatrixSpace(self._ZZp,self._k-1,1)(0),quick=True)
+                for d in HeckeData:
+                    t=d[1][jj] # edge_list[jj]
+                    gg=d[0] # acter
                     iota=self.iota
-                    tmp=tmp+(t.sign*f._F[t.label]).l_act_by((self._X._p**(-t.power))*(iota(gg)*iota(self._X._alpha1[l]))*(iota(t.gamma)))
+                    tmp+=(t.sign*f._F[t.label]).l_act_by((self._X._p**(-t.power))*(iota(gg)*iota(self._X._alpha1[l]))*(iota(t.gamma)))
                 Tf.append(factor*tmp)
-            return HarmonicCocycle(self,Tf,self._prec)
+            return HarmonicCocycle(self,Tf)
         return T
 
     def operator_matrix(self,T):
@@ -360,13 +360,16 @@
         B=Matrix(R,len(basis),len(self._E)*(self._k-1),tmp)
         return (A.solve_left(B)).transpose()
 
-    def hecke_operator_matrix(self,l):
+    def hecke_matrix(self,l):
         return self.operator_matrix(self.hecke_operator(l))
 
-    def rational_charpoly(self,l):
-        v1=self.hecke_operator_matrix(l).characteristic_polynomial().coeffs()
-        v=[x.rational_reconstruction() for x in v1]
-        return PolynomialRing(RationalField(),'x')(v)
+    def hecke_polynomial(self,l):
+        v1=self.hecke_matrix(l).characteristic_polynomial().coeffs()
+        try:
+            v=[x.rational_reconstruction() for x in v1]
+            return PolynomialRing(RationalField(),'x')(v)
+        except:
+            raise "Need to use more precision for this computation..."
 
 
 
@@ -375,68 +378,66 @@
 ######################
 
 class pAutomorphicForm(ModuleElement):
-    def __init__(self,_parent,vec,prec=None):
+    def __init__(self,_parent,vec,quick=False):
 
         ModuleElement.__init__(self,_parent)
         self._parent=_parent
         self._nE=2*len(_parent._E) # We record the values at the opposite edges
-        if(isinstance(vec,pAutomorphicForm)):
-            self._ZZp=Qp(_parent._X._p,prec=vec._prec)
-            self._prec=vec._prec
-
-            self._F=[self._parent._U(vec._F[ii]) for ii in range(self._nE)]
-
-            self._make_invariant()
+        if(quick):
+                self._ZZp=Qp(_parent._X._p,prec=_parent._prec)
+                self._F=[v for v in vec]
+        else:
+            if(isinstance(vec,pAutomorphicForm)):
+                self._ZZp=Qp(_parent._X._p,prec=_parent._prec)
+                self._F=[self._parent._U(vec._F[ii]) for ii in range(self._nE)]
+                self._make_invariant()
 
-        elif(isinstance(vec,HarmonicCocycle)):
-            assert(_parent._U.weight()==vec._wt-2)
-            self._prec=vec._prec
-            self._ZZp=Qp(_parent._X._p,prec=self._prec)
-            self._F=[]
-            assert(2*len(vec._F)==self._nE)
-            assert(isinstance(_parent._U,OCVn))
+            elif(isinstance(vec,HarmonicCocycle)):
+                assert(_parent._U.weight()==vec._wt-2)
+                self._ZZp=Qp(_parent._X._p,prec=_parent._prec)
+                self._F=[]
+                assert(2*len(vec._F)==self._nE)
+                assert(isinstance(_parent._U,OCVn))
+                E=self._parent._E
+                for ii in range(len(vec._F)):
+                    self._F.append(_parent._U(OCVnElement(vec._parent._U,vec._F[ii]).l_act_by(E[ii].rep.inverse())))
+                for ii in range(len(vec._F)):
 
-            for ii in range(len(vec._F)):
-                self._F.append(_parent._U(OCVnElement(vec._parent._U,vec._F[ii],self._prec).l_act_by(self._parent._X._edge_list[ii].rep.inverse())))
-            for ii in range(len(vec._F)):
-
-                self._F.append(_parent._U((-1*(OCVnElement(vec._parent._U,self._F[ii],self._prec)).r_act_by(Matrix(RationalField(),2,2,[0,-1/_parent._X._p,-1,0])))))
-            self._make_invariant()
+                    self._F.append(_parent._U((-1*(OCVnElement(vec._parent._U,self._F[ii])).r_act_by(Matrix(RationalField(),2,2,[0,-1/_parent._X._p,-1,0])))))
+                self._make_invariant()
 
-        elif(isinstance(vec,list) and len(vec)==self._nE):
-            self._ZZp=Qp(_parent._X._p,prec=prec)
-            try:
-                self._prec=prec
-                self._F=[self._parent._U(v) for v in vec]
-            except:
+            elif(isinstance(vec,list) and len(vec)==self._nE):
+                self._ZZp=Qp(_parent._X._p,prec=_parent._prec)
                 try:
+                    self._F=[self._parent._U(v) for v in vec]
+                except:
+                    try:
+                        veczp=_parent._U._ZZp(vec)
+                        self._parent=_parent
+                        self._F=[self._parent._U(veczp) for ii in range(self._nE)]
+                    except:
+                        print vec
+                        assert(0)
+            else:
+                try:
+                    self._ZZp=Qp(_parent._X._p,prec=_parent._prec)
                     veczp=_parent._U._ZZp(vec)
-                    self._prec=prec
                     self._parent=_parent
                     self._F=[self._parent._U(veczp) for ii in range(self._nE)]
                 except:
-                    print vec
-                    assert(0)
-        else:
-            try:
-                self._ZZp=Qp(_parent._X._p,prec=prec)
-                veczp=_parent._U._ZZp(vec)
-                self._prec=prec
-                self._parent=_parent
-                self._F=[self._parent._U(veczp) for ii in range(self._nE)]
-            except:
-                raise ValueError,"Cannot initialize a p-adic automorphic form with the given input="+str(vec)
+                    raise ValueError,"Cannot initialize a p-adic automorphic form with the given input="+str(vec)
 
     def _make_invariant(self):
-        S=self._parent._X._get_edge_stabs()
-        M=[e.rep for e in self._parent._X._edge_list]+[e.opposite[0].rep for e in self._parent._X._edge_list]
+        S=self._parent._X.get_edge_stabs()
+        E=self._parent._E
+        M=[e.rep for e in E]+[e.opposite[0].rep for e in E]
         lS=len(S)
         assert(2*len(S)==self._nE)
         for ii in range(self._nE):
             Si=S[ii%lS]
             if(Si!=1):
-                x=OCVnElement(self._F[ii].parent(),self._F[ii],self._prec)
-                self._F[ii]=OCVnElement(self._F[ii].parent(),0,self._prec)
+                x=OCVnElement(self._F[ii].parent(),self._F[ii]._val,quick=True)
+                self._F[ii]=OCVnElement(self._F[ii].parent(),0)
                 s=RationalField()(0)
                 m=M[ii]
                 for v in Si:
@@ -448,7 +449,6 @@
         mydata=dict([])
         mydata['p']=self._parent._X._p
         mydata['t']=self._parent._U._depth
-        mydata['prec']=self._prec
         mydata['level']=self._parent._X._level
         mydata['weight']=self._parent._U.weight()
         mydata['value']=[c._val for c in self._F]
@@ -460,20 +460,16 @@
         assert(mydata['t'])==self._parent._U._depth
         assert(mydata['level'])==self._parent._X._level
         assert(mydata['weight']==self._parent._U.weight())
-        self._prec=mydata['prec']
         self._F=[self._parent._U(c) for c in mydata['value']]
 
     def _add_(self,g):
-        prec=min([self._prec,g._prec])
         #Should ensure that self and g are of the same weight and on the same curve
         vec=[self._F[e]+g._F[e] for e in range(self._nE)]
-        return pAutomorphicForm(self._parent,vec,prec)
+        return pAutomorphicForm(self._parent,vec,quick=True)
     def _sub_(self,g):
-        prec=min([self._prec,g._prec])
         #Should ensure that self and g are of the same weight and on the same curve
         vec=[self._F[e]-g._F[e] for e in range(self._nE)]
-        return pAutomorphicForm(self._parent,vec,prec)
-
+        return pAutomorphicForm(self._parent,vec,quick=True)
 
     def _getitem_(self,e1):
         return self.evaluate(e1)
@@ -484,20 +480,16 @@
         return (self._F[u.label+IntegerRing()((1-u.sign)*self._nE/4)]).r_act_by((u.t)*X._p**(u.power))
 
     def _rmul_(self,a):
-        if(a.parent().is_exact()):
-            prec=self._prec
-        else:
-            prec=min([self._prec,a.precision_relative()])
         #Should ensure that 'a' is a scalar
-        return pAutomorphicForm(self._parent,[a*self._F[e] for e in range(self._nE)],prec)
+        return pAutomorphicForm(self._parent,[a*self._F[e] for e in range(self._nE)],quick=True)
 
 
     def l_act_by(self,alpha):
-        edge_list=self._parent._X._edge_list
+        E=self._parent._E
         gg=1#alpha.inverse()
         iota=self._parent.iota
         Tf=[]
-        for e in edge_list:
+        for e in E:
             u=EdgeDecomposition(self._parent._X,alpha*e.rep)
             r=u.t*gg*self._parent._X._p**(-(u.power))
             if(u.sign==1):
@@ -505,7 +497,7 @@
             else:
                 tmp=self._F[u.label+len(self._parent._E)].r_act_by(r)
             Tf.append(tmp)
-        for e in edge_list:
+        for e in E:
             u=EdgeDecomposition(self._parent._X,alpha*e.opposite[0].rep)
             r=u.t*gg*self._parent._X._p**(-(u.power))
             if(u.sign==1):
@@ -513,7 +505,7 @@
             else:
                 tmp=self._F[u.label+len(self._parent._E)].r_act_by(r)
             Tf.append(tmp)
-        return pAutomorphicForm(self._parent,Tf,self._prec)
+        return pAutomorphicForm(self._parent,Tf,quick=True)
 
 
     def _repr_(self):
@@ -525,25 +517,28 @@
         return tmp
 
     def valuation(self):
-        return(min([self._F[e].valuation() for e in range(self._nE)]))
+        if not self.__nonzero__():
+            return self._F[0].valuation() # Should be infinity
+        else:
+            return(min([self._F[e].valuation() for e in range(self._nE)]))
 
-    def _nonzero_(self):
-        return(any([self._F[e]._nonzero_() for e in range(self._nE)]))
+    def __nonzero__(self):
+        return(any([self._F[e].__nonzero__() for e in range(self._nE)]))
 
     def improve(self):
         MMM=self._parent
         h2=MMM.Up(self,True)
         ii=0
         current_val=0
-        while(current_val<self._prec and ii<self._prec+2):
+        old_val=-1
+        while(current_val<MMM._prec and current_val>old_val):
+            old_val=current_val
             ii+=1
-            print ii,"th iteration, val=",current_val
+            print ii,"th iteration, precision=",current_val
             self._F=[self._parent._U(c) for c in h2._F]
             h2=MMM.Up(self,True)
             current_val=(h2-self).valuation()
         self._F=[self._parent._U(c) for c in h2._F]
-        print "Done!"
-        return self
 
     def integrate(self,f,center=1,level=0,method='moments'):
         E=self._parent._X._BT.get_ball(center,level)
@@ -555,7 +550,7 @@
             R1.set_default_prec(self._parent._U.weight()+1)
             for e in E:
                 ii+=1
-                print ii,"/",len(E)
+                #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=self.evaluate(e).evaluate(exp.truncate(self._parent._U.weight()+1))
@@ -575,18 +570,22 @@
         return value
 
     def mod_form(self,z=None,level=0,method='moments'):
+        return self.derivative(z,level,method,0)
+
+    def derivative(self,z=None,level=0,method='moments',order=1):
         def F(z,level=0,method='moments'):
             R=PolynomialRing(z.parent(),'x')
             x=R.gen()
             center=self._parent._X._BT.whereis(z)
-            return self.integrate(R(1)/(x-z),center,level,method)
+            f=R(1)/(x-z)
+            return self.integrate(R(IntegerRing()(order).factorial())/(x-z)**(order+1)+order/(z-our_conj(z))*f,center,level,method)
         if(z==None):
             return F
         else:
-            return F(z,level)
+            return F(z,level,method)
 
 
-    def _exp_factor(self,t1,t2,center=1,level=0,E=None):
+    def _exp_factor(self,t1,t2,center=1,level=0,E=None,delta=-1):
         K=t1.parent()
         if(center==None):
             center=self._parent._X._BT.whereis(t1)
@@ -598,20 +597,23 @@
         value=1
         ii=0
         p=self._parent._X._p
-        prec=self._prec
         for e in E:
             ii+=1
-            print ii,"/",len(E)
+            #print ii,"/",len(E)
             b=e[0,1]
             d=e[1,1]
-            y=(b-d*t1)/(b-d*t2)
+            num=b-d*t1
+            den=b-d*t2
+            y=num/den
             power=IntegerRing()(self.evaluate(e)._val[0,0].rational_reconstruction())
             new=K.teichmuller(y)**power
             value*=new
         return value
 
 
-    def coleman(self,t1,t2,center=None,level=0,E=None,method='moments',mult=False):
+    def coleman(self,t1,t2,center=None,level=0,E=None,method='moments',mult=False,delta=-1):
+        if(mult and delta>=0):
+            raise NotImplementedError, "Need to figure out how to implement the multiplicative part."
         p=self._parent._X._p
         K=t1.parent()
         R=PolynomialRing(K,'x')
@@ -629,7 +631,7 @@
             R1.set_default_prec(self._parent._U.weight()+1)
             for e in E:
                 ii+=1
-                print ii,"/",len(E)
+                #print ii,"/",len(E)
                 b=e[0,1]
                 d=e[1,1]
                 y=(b-d*t1)/(b-d*t2)
@@ -640,7 +642,7 @@
             R1.set_default_prec(self._parent._U.dimension())
             for e in E:
                 ii+=1
-                print ii,"/",len(E)
+                #print ii,"/",len(E)
                 f=(x-t1)/(x-t2)
                 y0=f(R1([e[0,1],e[0,0]])/R1([e[1,1],e[1,0]]))
                 y0=p**(-y0(0).valuation())*y0
@@ -651,19 +653,20 @@
                 for jj in range(1,R1.default_prec()+10):
                     exp+=(-1)**(jj+1)*ypow/jj
                     ypow*=y
+                if(delta>=0):
+                    exp*=((r1-t1)**delta*(r1-t2)**(self._parent._n-delta))
                 new=self.evaluate(e).evaluate(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
-
         if(mult):
             value=our_exp(value)*self._exp_factor(t1,t2)
         return value
 
 class pAutomorphicForms(Module):
     Element=pAutomorphicForm
-    def __init__(self,X,U,prec=None,t=None,R=None):
+    def __init__(self,X,U,prec=None,t=None,R=None,overconvergent=False):
         if(R==None):
             if(not isinstance(U,Integer)):
                 self._ZZp=U.base_ring()
@@ -674,22 +677,27 @@
         else:
             self._ZZp=R
         #U is a CoefficientModuleSpace
-
-
         if(isinstance(U,Integer)):
             if(t==None):
-                t=0
+                if(overconvergent):
+                    t=prec-U+1
+                else:
+                    t=0
             self._U=OCVn(U-2,self._ZZp,U-1+t)
         else:
             self._U=U
         self._X=X
-        self._V=self._X._vertex_list
-        self._E=self._X._edge_list
+        self._V=self._X.get_vertex_list()
+        self._E=self._X.get_edge_list()
         self._prec=self._ZZp.precision_cap()
         self._n=self._U.weight()
         Module.__init__(self,base=self._ZZp)
         self._populate_coercion_lists_()
 
+    def _repr_(self):
+        s='Space of automorphic forms on '+str(self._X)+' with values in '+str(self._U)
+        return s
+
     def _coerce_map_from_(self, S):
        """
        Can coerce from other HarmonicCocycles or from pAutomorphicForms
@@ -712,7 +720,7 @@
         #Code how to coherce x into the space
         #Admissible values of x?
         if(isinstance(x,(HarmonicCocycle,pAutomorphicForm))):
-            return pAutomorphicForm(self,x,prec=x._prec)
+            return pAutomorphicForm(self,x)
 
     def _an_element_(self):
         return pAutomorphicForm(self,1)
@@ -722,7 +730,6 @@
 
     def Up(self,v=None,scale=False):
         def T(f):
-            iota=self.iota
             HeckeData=self._X.Up_data()
             #Should find a way to replace this n/2 with something more intrinsinc to U
             if(scale==False):
@@ -733,17 +740,14 @@
             Tf=[]
             for jj in range(2*len(self._E)):
                 tmp=self._U(0)
-                for ii in range(self._X._p):
-                    u=HeckeData[ii][1][jj]#.edge_list[jj]
-                    gg=HeckeData[ii][0]#.acter
-                    iota=self.iota
-                    r=u.t*gg*self._X._p**(-(u.power))
-                    tmp=tmp+f._F[u.label+IntegerRing()((1-u.sign)/2)*len(self._E)].r_act_by(r)
+                for d in HeckeData:
+                    u=d[1][jj] # edge_list[jj]
+                    gg=d[0] # acter
+                    r=self._X._p**(-(u.power))*(u.t*gg)
+                    tmp+=f._F[u.label+IntegerRing()((1-u.sign)/2)*len(self._E)].r_act_by(r)
                 Tf.append(factor*tmp)
-            return pAutomorphicForm(self,Tf,self._prec)
+            return pAutomorphicForm(self,Tf,quick=True)
         if(v==None):
             return T
         else:
             return T(v)
-
-
--- a/shimuracurve.py	Wed Jun 15 12:01:49 2011 +0200
+++ b/shimuracurve.py	Sun Jul 03 20:20:59 2011 +0200
@@ -15,10 +15,12 @@
 from sage.structure.element import Element
 from sage.groups.matrix_gps.general_linear import GeneralLinearGroup_generic
 from sage.matrix.constructor import Matrix
+from sage.matrix.all import matrix
 from sage.matrix.matrix_space import MatrixSpace
 from sage.structure.sage_object import SageObject
-from sage.rings.all import RationalField
-from sage.rings.all import IntegerRing
+from sage.rings.all import QQ
+from sage.rings.all import ZZ
+from sage.misc.latex import latex
 from sage.plot import plot
 from sage.rings.arith import *
 from sage.rings.integer import Integer
@@ -36,6 +38,7 @@
 from sage.modular.btquotients.utility import our_sqrt
 from copy import copy
 from sage.structure.unique_representation import UniqueRepresentation
+from sage.modules.free_module import FreeModule_submodule_pid
 
 r"""
   Initialized with an element x0 of GL2(ZZ), finds elements
@@ -47,7 +50,7 @@
      if sign==-1:
         gamma*edge_list[label].opposite[0].rep*t==x0
 
-  It also returns power, so that:
+  It also stores a member called power, so that:
   p**(2*power)=gamma.reduced_norm()
 
   If one wants the usual decomposition, then it would be:
@@ -65,27 +68,27 @@
 """
 class EdgeDecomposition(SageObject):
     def __init__(self,Y,x0,extrapow=0):
-        x=MatrixSpace(IntegerRing(),2,2)(x0)
+        x=MatrixSpace(ZZ,2,2)(x0)
         assert(x.determinant()!=0)
         e1=Y._BT.edge(x)
         e1opp=Y._BT.opposite(e1)
         sign=0
-        E=Y._edge_list
-        for ii in range(len(E)):
-            e2=E[ii]
-            g=Y._are_equivalent(e2.rep,e1,Y._Xe)
+        E=Y.get_edge_list()
+        Xe=Y._Xe
+        for e in E:
+            g=Y._are_equivalent(e.rep,e1,-1,Xe)
             if(g!=0):
                 sign=1
-                t=(Y.iota(g[0])*e2.rep).inverse()*x
+                t=(Y.iota(g[0])*e.rep).inverse()*x
                 break
-            e3=e2.opposite[0]
-            g=Y._are_equivalent(e3.rep,e1,Y._Xe)
+            e3=e.opposite[0]
+            g=Y._are_equivalent(e3.rep,e1,-1,Xe)
             if(g!=0):
                 sign=-1
                 t=(Y.iota(g[0])*e3.rep).inverse()*x
                 break
         self.sign=sign
-        self.label=e2.label
+        self.label=e.label
         self.gamma=g[0]
         self.power=g[1]+extrapow
         self.t=t
@@ -96,10 +99,13 @@
    information as it is known
 """
 class Vertex():
-    def __init__(self,owner,label,rep,leaving_edges,entering_edges):
+    def __init__(self,owner,label,rep,leaving_edges,entering_edges,determinant,valuation):
         self.owner=owner
         self.label=label
         self.rep=rep
+        self.determinant=determinant
+        self.valuation=valuation
+        self.parity=valuation%2
         self.leaving_edges=leaving_edges
         self.entering_edges=entering_edges
 
@@ -115,7 +121,7 @@
    that are equivalent to this one in the quotient.
 """
 class Edge():
-    def __init__(self,owner,label,rep,origin,target,links,opposite):
+    def __init__(self,owner,label,rep,origin,target,links,opposite,determinant,valuation):
         self.owner=owner
         self.label=label
         self.rep=rep
@@ -123,6 +129,9 @@
         self.target=target
         self.links=links
         self.opposite=opposite
+        self.determinant=determinant
+        self.valuation=valuation
+        self.parity=valuation%2
 
 class BruhatTitsTree(SageObject):
     def __init__(self,p):
@@ -149,7 +158,7 @@
         else:
             tmp=(B.determinant()).valuation()-B[0,1].valuation()
             B=Matrix(ZZ,2,2,[0,p**(B[0,1].valuation()),p**(tmp),(B[1,1]/B[0,1].unit_part()).residue(tmp)])
-        v=min([valuation(B[i,j],p) for i in range(2) for j in range(2)])
+        v=min([B[i,j].valuation(p) for i in range(2) for j in range(2)])
         B=p**(-v)*B
         return(B)
 
@@ -163,42 +172,46 @@
             B.swap_columns(0,1)
         tmp=(B.determinant()).valuation()-B[0,0].valuation()
         B=Matrix(ZZ,2,2,[p**(B[0,0].valuation()),0,(B[1,0]/B[0,0].unit_part()).residue(tmp),p**(tmp)])
-        v=min([valuation(B[i,j],p) for i in range(2) for j in range(2)])
+        v=min([B[i,j].valuation(p) for i in range(2) for j in range(2)])
         B=p**(-v)*B
         return(B)
 
+    #Construct the edges leaving v0
+    def edges_leaving_origin(self):
+        try: return self._edges_leaving_origin
+        except:
+            p=self._p
+            self._edges_leaving_origin=[[matrix(ZZ,2,2,[0,-1,p,0]),self.target([0,-1,p,0])]]
+            self._edges_leaving_origin.extend([[matrix(ZZ,2,2,[p,i,0,1]),self.target([p,i,0,1])] for i in range(p)])
+            return self._edges_leaving_origin
+
+
     def leaving_edges(self,M):
-        R=MatrixSpace(ZZ,2,2)
-        p=self._p
-        #Construct the edges leaving v0
-        v=[[R([0,-1,p,0]),self.target(R([0,-1,p,0]))]]
-        v.extend([[R([p,i,0,1]),self.target(R([p,i,0,1]))] for i in range(p)])
-
-        MM=R(M)
-        return [[self.edge(MM*A[0]),self.vertex(MM*A[1])] for A in v]
+        return [[self.edge(M*A[0]),self.vertex(M*A[1])] for A in self.edges_leaving_origin()]
 
     def opposite(self,M):
-        return self.edge(Matrix(ZZ,2,2,M)*Matrix(ZZ,2,2,[0,1,self._p,0]))
+        return self.edge(M*Matrix(ZZ,2,2,[0,1,self._p,0]))
 
     def entering_edges(self,M):
          v=[[self.opposite(e[0]),M] for e in self.leaving_edges(M)]
          return v
 
-
-    # Given a list of edges (which we think of finite union of open balls)
-    # return a list of lists of edges
-    # corresponding to the level-th subdivision of that open.
-    # That is, if level==0, return the given list, unchanged.
+    r"""
+    Given a list of edges (which we think of finite union of open balls)
+    return a list of lists of edges
+    corresponding to the level-th subdivision of that open.
+    That is, if level==0, return the given list, unchanged.
+    """
     def subdivide(self,edgelist,level):
         all_edges=[]
         if(level<0):
             return []
         if(level==0):
-            return [MatrixSpace(ZZ,2,2)(edge) for edge in edgelist]
+            return [Matrix(ZZ,2,2,edge) for edge in edgelist]
         else:
             newEgood=[]
             for edge in edgelist:
-                edge=MatrixSpace(ZZ,2,2)(edge)
+                edge=Matrix(ZZ,2,2,edge)
                 origin=self.origin(edge)
                 newE=self.leaving_edges(self.target(edge))
                 newEgood.extend([e[0] for e in newE if e[1]!=origin])
@@ -230,17 +243,25 @@
                 if(len(L[n])>0):
                     a+=pn*L[n][0]
             pn*=p
-        #return self._normalize_vertex(Matrix(ZZ,2,2,[1,0,a,pn]))
         if(flag==True):
-            return (self.vertex(Matrix(ZZ,2,2,[0,1,p,0])*Matrix(ZZ,2,2,[pn,a,0,1])))
+            return self.vertex(Matrix(ZZ,2,2,[0,1,p,0])*Matrix(ZZ,2,2,[pn,a,0,1]))
         else:
-            return (self.vertex(Matrix(ZZ,2,2,[pn,a,0,1])))
+            return self.vertex(Matrix(ZZ,2,2,[pn,a,0,1]))
 
 
-class ShimuraCurveFiber(SageObject):
-    def __init__(self,X,p,source='sage',magma_proc=None,compute=True):
-        if(X.level()%p!=0):
-            raise ValueError, "p must divide the level of X"
+class ShimuraCurveFiber(SageObject,UniqueRepresentation):
+    @staticmethod
+    def __classcall__(cls,X,p,source='sage'):
+        return super(ShimuraCurveFiber,cls).__classcall__(cls,X,p,source)
+
+    def __init__(self,X,p,source):
+        #global magma
+        p=ZZ(p)
+        if(not p.is_prime()):
+            raise ValueError, "must be a prime"
+        if(X._level%p!= 0):
+            raise ValueError, "p must divide the level X"
+
         self._generic_fiber=X
         self._pN=p
         self._p=p
@@ -251,45 +272,28 @@
         if(self._Nplus!=1 and source=='sage'):
             raise NotImplementedError,'Sage does not know yet how to deal with non-maximal orders, so this feature is disabled for now. Try with Nplus=1 or, if you have Magma installed, with source="sage"'
 
-        self._S=Graph(0,multiedges=True,weighted=True)
+        if(self._p==2 and source=='sage'):
+            raise NotImplementedError,'Sage does not know how to split orders at the prime 2 yet...'
+
         self._BT=BruhatTitsTree(p)
-        self._edge_list=[]
-        self._vertex_list=[]
-        self._preci=-1
-        self._hecke_coset_reps=dict([])
+        self._prec=-1
+        self._hecke_data=dict([])
         self._alpha1=dict([])
-        self._cached=False
         self._CM_points=dict()
-        self._edge_stabs=[]
         self._source=source
         if(source=='magma'):
-            from sage.interfaces.magma import Magma
-            if(magma_proc!=None):
-                self._magma=magma_proc
-            else:
-                self._magma=Magma()
+            self._magma=magma
 
-        self._V=RationalField()**4
-        XX=MatrixSpace(ZZ,2,2)
-        self._Xv=[XX([1,0,0,0]),XX([0,1,0,0]),XX([0,0,1,0]),XX([0,0,0,1])]
-        self._Xe=[XX([1,0,0,0]),XX([0,1,0,0]),XX([0,0,self._p,0]),XX([0,0,0,1])]
-        self._num_edges=0
-
-        self._O=None
-        self._A=None
-        self._B=None
-        self._OQuadForm=None
-
-        if(compute):
-            self._compute()
+        self._V=(QQ**4).ambient_module().change_ring(ZZ)
+        self._Xv=[Matrix(ZZ,2,2,[1,0,0,0]),Matrix(ZZ,2,2,[0,1,0,0]),Matrix(ZZ,2,2,[0,0,1,0]),Matrix(ZZ,2,2,[0,0,0,1])]
+        self._Xe=[Matrix(ZZ,2,2,[1,0,0,0]),Matrix(ZZ,2,2,[0,1,0,0]),Matrix(ZZ,2,2,[0,0,self._p,0]),Matrix(ZZ,2,2,[0,0,0,1])]
+        self._R4x4=MatrixSpace(ZZ,4,4)
 
     def __getstate__(self):
         from sage.all import dumps
         odict=self.__dict__.copy()
         odict['_S']=self._S.sparse6_string()
         del odict['_generic_fiber']
-        if(self._source=='magma'):
-            del odict['_magma']
         return odict
 
     def __setstate__(self,ndict):
@@ -297,52 +301,54 @@
         self.__dict__.update(ndict)
         self._generic_fiber=ShimuraCurve(ndict['_p']*ndict['_Nminus'],ndict['_Nplus'])
         self._S=Graph(ndict['_S'],multiedges=True,weighted=True)
-        if(self._source=='magma'):
-            from sage.interfaces.magma import Magma
-            self._magma=Magma()
 
     def _repr_(self):
-        return "Fiber of Shimura curve of level ("+str(factor(self._generic_fiber.level()))+") and conductor ("+str(factor(self._Nplus))+") at the prime "+str(self._p)
+        return "Fiber of Shimura curve of level %s and conductor %s at the prime %s"%(self._generic_fiber.level().factor(),self._Nplus.factor(),self._p)
 
     def _latex_(self):
-        return "\\text{Fiber of Shimura curve of level (}"+latex(factor(self._generic_fiber.level()))+"\\text{) and conductor (}"+latex(factor(self._Nplus))+"\\text{) at the prime }"+latex(self._p)
+        return "X(%s,%s)\\otimes_{\\mathbb{Z}} \\mathbb{F}_{%s}"%(latex(self._generic_fiber.level().factor()),latex(self._Nplus.factor()),latex(self._p))
 
     def generic_fiber(self):
         return self._generic_fiber
-    def _is_cached(self):
-        return (self._cached)
+
+    def get_vertex_list(self):
+        try: return self._vertex_list
+        except AttributeError:
+            self._compute()
+            return self._vertex_list
+
+    def get_edge_list(self):
+        try: return self._edge_list
+        except AttributeError:
+            return self._compute()
+
     def genus(self):
-        if (not self._is_cached()):
+        try: return self._genus
+        except AttributeError:
             self._compute()
-        return ZZ(1- len(self._vertex_list)+self._num_edges)
+            return self._genus
+
     def Nplus(self):
         return(self._Nplus)
+
     def Nminus(self):
         return(self._Nminus)
+
     def get_graph(self):
-        return self._S
-    def plot(self):
-        A=self._S.adjacency_matrix()
-        return Graph(A).plot(edge_labels=False)
+        try: return self._S
+        except AttributeError:
+            self._compute()
+            return self._S
 
-    def is_admissible(self,disc):
-        disc=ZZ(disc)
-        if(disc.squarefree_part()!=disc):
-            return False
-        R = RationalField()['x']
-        K=NumberField(R([-disc,0,1]), 'sq', check=False)
-        ff=factor(self._p*self._Nminus)
-        for f in ff:
-            if kronecker_symbol(K.discriminant(),f[0])!=-1:
-                return False
-        ff=factor(self._Nplus)
-        for f in ff:
-            if kronecker_symbol(K.discriminant(),f[0])!=1:
-                return False
-        return True
+    def plot(self):
+        S=self.get_graph()
+        return S.plot(vertex_labels=True,edge_labels=True)
 
-    def _local_splitting_map(self,preci):
-        I,J,K=self._local_splitting(preci)
+    def is_admissible(self,D):
+        return self.generic_fiber().is_admissible(D)
+
+    def _local_splitting_map(self,prec):
+        I,J,K=self._local_splitting(prec)
         def phi(q):
             R=parent(I)
             return R(q[0] + I*q[1] + J*q[2] + K*q[3])
@@ -350,17 +356,20 @@
 
     def _local_splitting(self,prec):
         assert(self._source=='sage')
-        preci=max([5,prec])
-        i=self._A.gens()[0]
-        j=self._A.gens()[1]
-        if(self._ZZp.precision_cap()<preci):
-            self._ZZp=Zp(self._p,preci)
-        ZZp=self._ZZp
+        A=self.get_quaternion_algebra()
+        Agens=A.gens()
+        i=Agens[0]
+        j=Agens[1]
+
+        # if(self._ZZp.precision_cap()<prec):
+        #     self._ZZp=Zp(self._p,prec)
+
+        ZZp=Zp(self._p,prec)
         a =ZZp(i*i)
         b = ZZp(j*j)
-        if (self._A.base_ring() != RationalField()):
+        if (A.base_ring() != QQ):
             raise ValueError, "must be rational quaternion algebra"
-        if (self._A.discriminant() % self._p == 0):
+        if (A.discriminant() % self._p == 0):
             raise ValueError, "p (=%s) must be an unramified prime"%self._p
         M = MatrixSpace(ZZp, 2)
 
@@ -399,343 +408,102 @@
         # assert I^2==a*M([1,0,0,1])
         # assert J^2==b*M([1,0,0,1])
         # assert K == -J*I
-
         return I, J, K
 
-
-    # Need to understand why pMatrixRing sometimes returns low precision
-    def _get_Iota0(self,preci):
+    def _get_Iota0(self,prec):
         if(self._source=='magma'):
-            M,f,rho=self._magma.function_call('pMatrixRing',args=[self._O,self._p],params={'Precision':100},nvals=3)
-            OBasis=self._O.Basis()
+            Ord=self.get_eichler_order()
+            M,f,rho=self._magma.function_call('pMatrixRing',args=[Ord,self._p],params={'Precision':str(prec+100)},nvals=3)
+            OBasis=Ord.Basis()
             v=[f.Image(OBasis[i]) for i in [1,2,3,4]]
-            A=Matrix(Zmod(self._pN),4,4,[v[kk][ii,jj].sage() for ii in range(1,3) for jj in range(1,3) for kk in range(4)])
-            return A
+            #return Matrix(self._ZZp,4,4,[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,[v[kk][ii,jj].sage() for ii in range(1,3) for jj in range(1,3) for kk in range(4)])
         else:
             assert(self._source=='sage')
-            phi=self._local_splitting_map(preci)
-            return Matrix(Zmod(self._pN),4,4,[phi(self._B[kk,0])[ii,jj] for ii in range(2) for jj in range(2) for kk in range(4)])
+            phi=self._local_splitting_map(prec)
+            B=self.get_eichler_order_basis()
+            #return Matrix(self._ZZp,4,4,[phi(B[kk,0])[ii,jj] for ii in range(2) for jj in range(2) for kk in range(4)])
+            return Matrix(Zmod(self._pN),4,4,[phi(B[kk,0])[ii,jj] for ii in range(2) for jj in range(2) for kk in range(4)])
 
-    def get_Iota(self,preci=None):
-        if(preci==None):
-            preci=self._preci
-        self._pN=self._p**preci
-
-        self._ZZp=Qp(self._p,prec=preci)
+    def get_Iota(self,prec=None):
+        if(prec==None):
+            prec=self._prec
+        self._pN=self._p**prec
+        self._ZZp=Qp(self._p,prec=prec)
         self._BT.update_prec(self._ZZp)
 
-        if(preci>self._preci):
-            Iotamod=self._get_Iota0(preci)
+        if(prec>self._prec):
+            Iotamod=self._get_Iota0(prec)
             self._Iotainv_lift=Iotamod.inverse().lift()
-            self._Iota=MatrixSpace(self._ZZp,4,4)(Iotamod)
+            self._Iota=Matrix(self._ZZp,4,4,[Iotamod[ii,jj] for ii in range(4) for jj in range(4)])
 
-        self._preci=preci
-
-        self._Iotainv=Matrix(ZZ,4,4,[self._Iotainv_lift[ii,jj]%self._pN for ii in range(4) for jj in range(4)])
+        self._prec=prec
+        self._Iotainv=self._R4x4([self._Iotainv_lift[ii,jj]%self._pN for ii in range(4) for jj in range(4)])
         return self._Iota
 
     def iota(self,g,prec=None):
         if(prec==None):
-            prec=self._preci
+            prec=self._prec
         x=self.get_Iota(prec)
         gm=Matrix(self._ZZp,4,1,g)
         return Matrix(self._ZZp,2,2,x*gm)
 
-
-    def _get_edge_stabs(self):
-        if(len(self._edge_stabs)>0):
+    def get_edge_stabs(self):
+        try: return self._edge_stabs
+        except AttributeError:
+            self._edge_stabs=[self._edge_stabilizer(e.rep) for e in self.get_edge_list()]
             return self._edge_stabs
-        for e in self._edge_list:
-            tmp=self._edge_stabilizer(e.rep)
-            self._edge_stabs.append(tmp)
-        return self._edge_stabs
-
-    def _edge_stabilizer(self,e):
-        p=self._p
-        m=valuation(e.determinant(),p)
-        if((2*m+1)>self._preci):
-            self.get_Iota(2*m+1)
-        vecM=[e*self._Xe[ii]*e.adjoint() for ii in range(4)]
-        M=Matrix(ZZ,4,4,[[vecM[ii][jj,kk] for ii in range(4) ] for jj in range(2) for kk in range(2)])
-        M=self._Iotainv*M
-        vec=[[ZZ(M[ii,kk]%(self._pN)) for ii in range(4)] for kk in range(4) ]
-        vec+=permutations([self._pN,0,0,0])
-        L=self._V.span(vec,ZZ)
-        E=Matrix(ZZ,4,4,L.basis())
-        OGram=self._OQuadForm.matrix()
-        A=E*OGram*E.transpose()
-        n_units=len(self.get_units_of_order())
-        ## Using PARI to get the shortest vector in the lattice (via LLL)
-        v=pari('qfminim('+str(A._pari_())+','+str(0)+','+str(n_units)+',flag=0)')
-        n_vecs=ZZ(v[0].python()/2)
-        if(n_vecs>1):
-            tmp=[]
-            for jj in range(n_vecs):
-                vec=Matrix(ZZ,1,4,[v[2][ii,jj].python() for ii in range(4)])
-                nrd=ZZ((vec*A*vec.transpose())[0,0]/2)
-                w=vec*E
-                x=(w*self._B)[0,0]
-                if(nrd.is_power_of(p**2) and valuation(nrd,p)==2*m):
-                    tmp.append([w.transpose(),m,x!=p**m])
-            return tmp
-        else:
-            return 1
-        return 1
 
-    def _are_equivalent(self,v1,v2,X):
-        p=self._p
-        m=valuation(v1.determinant(),p)+valuation(v2.determinant(),p)
-        if (m%2==1):
-            return 0
-        if((m+1)>self._preci):
-            self.get_Iota(m+1)
-        m=ZZ(m/2)
-        vecM=[v2*X[ii]*v1.adjoint() for ii in range(4)]
-        M=Matrix(RationalField(),4,4,[[vecM[ii][jj,kk] for ii in range(4) ] for jj in range(2) for kk in range(2)])
-        M=self._Iotainv*M
-
-        vec=[[M[ii,kk]%(self._pN) for ii in range(4)] for kk in range(4) ]
-        vec+=permutations([self._pN,0,0,0])
-        L=self._V.span(vec,ZZ)
-        E=Matrix(ZZ,4,4,L.basis())
-        A=E*self._OQuadForm.matrix()*E.transpose()
-
-        ## Using PARI to get the shortest vector in the lattice (via LLL)
-        v=pari('qfminim('+str(A._pari_())+','+str(0)+',1,flag=0)')
-        vec=Matrix(ZZ,1,4,[v[2][ii,0].python() for ii in range(4)])
-        nrd=ZZ((vec*A*vec.transpose())[0,0]/2)
-        if(nrd.is_power_of(p**2) and valuation(nrd,p)==2*m):
-                return [(vec*E).transpose(),m]
-        else:
-               return 0
+    def get_quaternion_algebra(self):
+        try: return self._A
+        except AttributeError: pass
+        self._init_order()
+        return self._A
 
-    def _get_Eichler_order(self):
-        if(self._source=='magma'):
-            #global magma
-            A=self._magma.QuaternionAlgebra(self._Nminus)
-            g=A.gens()
-            #We store the order because we need to split it
-            self._O=A.QuaternionOrder(self._Nplus)
-            OBasis=self._O.Basis()
-            self._A=QuaternionAlgebra((g[0]**2).sage(),(g[1]**2).sage())
-            v=[1]+self._A.gens()
-            self._B=Matrix(self._A,4,1,[sum([OBasis[tt+1][rr+1].sage()*v[rr] for rr in range(4)]) for tt in range(4)])
-
-        else:
-            assert(self._source=='sage')
-            # Note that we can't work with non-maximal orders in sage
-            assert(self._Nplus==1)
-            self._A=QuaternionAlgebra(self._Nminus)
-            v=[1]+self._A.gens()
-            self._O=self._A.maximal_order()
-            OBasis=self._O.basis()
-
-            # Added to test example from Darmon's Book
-            if(self._p==7 and self._Nminus==2):
-                OBasis=[self._A(1),OBasis[1],OBasis[2],OBasis[0]]
-            # End of addition
-
-            self._B=Matrix(self._A,4,1,[OBasis[tt] for tt in range(4)])
-        self._OQuadForm=QuadraticForm(Matrix(ZZ,4,4,[(self._B[ii,0]*self._B[jj,0].conjugate()).reduced_trace() for ii in range(4) for jj in range(4)]))
-
+    def get_eichler_order(self):
+        try: return self._O
+        except AttributeError: pass
+        self._init_order()
+        return self._O
 
-    def _compute(self):
-        self._get_Eichler_order()
-        self.get_Iota(1)
-        num_verts=0
-        V=[Vertex(self,num_verts,Matrix(ZZ,2,2,[1,0,0,1]),[],[])]
-        E=[]
-        v0=V[0]
-        self._vertex_list.append(V[0])
-        num_verts+=1
-        while len(V)>0:
-            #print '#E=',self._S.num_edges(),'#V=',self._S.num_verts()
-            v=V.pop(0)
-            E=self._BT.leaving_edges(v.rep)
-            for e in E:
-                new_edge=True
-                for e1 in v.leaving_edges:
-                    g=self._are_equivalent(e1.rep,e[0],self._Xe)
-                    if(g):
-                        e1.links.append(g)
-                        new_edge=False
-                        break
-                if(new_edge):
-                    #Now we found a new edge. Decide whether its target vertex is new
-                    target=e[1]
-                    new_vertex=True
-                    for v1 in self._vertex_list:
-                        g1=self._are_equivalent(target,v1.rep,self._Xv)
-                        if(g1):
-                            target=v1.rep
-                            #The edge is new, but the vertices are not
-                            new_vertex=False
-                            break
-                    if(new_vertex):
-                        #The vertex is also new
-                        v1=Vertex(self,num_verts,target,[],[])
-                        self._vertex_list.append(v1)
-                        num_verts+=1
-                        self._S.add_vertex(v1.label)
-                        #Add the vertex to the list of pending vertices
-                        V.append(v1)
-
-                    #Find the opposite edge
-                    opp=self._BT.opposite(e[0])
-                    #We add the edge
-                    new_e=Edge(self,self._num_edges,e[0],v,v1,[],[])
-
-                    new_e.links.append(self._are_equivalent(new_e.rep,e[0],self._Xe))
-
-                    #Add the edge to the graph
-                    self._S.add_edge(v.label,v1.label,self._num_edges)
-
-                    new_e_opp=Edge(self,self._num_edges,opp,v1,v,[],[new_e])
-                    assert(len(new_e.opposite)==0)
-                    new_e.opposite.append(new_e_opp)
-
-
-                    if(valuation(new_e.rep.determinant(),self._p)%2==0):
-                        self._edge_list.append(new_e)
-                    else:
-                        self._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)
-
-                    self._num_edges += 1
-        self._cached=True
-
+    def get_eichler_order_basis(self):
+        try: return self._B
+        except AttributeError: pass
+        self._init_order()
+        return self._B
 
-    #Returns (computes if necessary) Up data. This 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
-    # EdgeDecomposition
-    def Up_data(self):
-        try: return self._Up_data
+    def get_eichler_order_quadform(self):
+        try: return self._OQuadForm
         except AttributeError: pass
-        self._Up_data=[]
-        vec_a=self._BT.subdivide([1],1)
-        for ii in range(self._p):
-            alpha=vec_a[ii]
-            tmp=[alpha.inverse(),[]]#GraphActionData(alpha.inverse(),[])
-            for e in self._edge_list:
-                tmp[1].append(EdgeDecomposition(self,e.rep*alpha)) #edge_list
-            for e in self._edge_list:
-                tmp[1].append(EdgeDecomposition(self,e.opposite[0].rep*alpha)) #edge_list
-            self._Up_data.append(tmp)
-        return self._Up_data
-
-    def hecke_coset_reps(self,l):
-        try: return self._hecke_coset_reps[l]
-        except KeyError: pass
-
-        if((self._p*self.Nminus()*self.Nplus())%l==0):
-            Sset=[]
-        else:
-            Sset=[self._p]
-        B=self._B
-        BB=Matrix(RationalField(),4,4,[[B[ii,0][jj] for ii in range(4)] for jj in range(4)]).inverse()
-        T=[]
-        T0=[]
-        nn=0
-        alpha1=self._find_elements_in_order(l)[0]
-        print "Got a vector of length l=",l
-        alpha0=sum([alpha1[ii]*B[ii,0] for ii in range(4)])
-        self._alpha1[l]=Matrix(RationalField(),4,1,alpha1)
+        self._init_order()
+        return self._OQuadForm
 
-        if(l==self._p):
-            nninc=1
-        else:
-            nninc=0
-
-        while(len(T)<l+1):
-            print '#T=',len(T),' and bound=',l+1
-            print 'Iteration number: ',1+ZZ(nn)
-            tmp=self._find_elements_in_order(self._p**(2*nn))
-            vec0=[sum([v[ii]*B[ii,0] for ii in range(4)])*alpha0 for v in tmp]
-            vec1=[Matrix(RationalField(),4,1,v) for v in tmp]
-            print 'Analyzing ',len(vec0),' vectors.'
-            while(len(T)<l+1 and len(vec0)>0):
-                print '#T=',len(T),' and bound=',l+1
-                v0=vec0.pop(0)
-                v1=vec1.pop(0)
-                vinv=(v0)**(-1)
-                new=True
-                for tt in range(len(T)):
-                    r=self._A(vinv*T0[tt])
-                    x=BB*Matrix(RationalField(),4,1,[r[ii] for ii in range(4)])
-                    if(all([x[jj,0].is_S_integral(Sset) for jj in range(4)])):
-                        new=False
-                        break
-                if(new):
-                    tmp=[v1,[]]#GraphActionData(v1,[])
-                    x=self.iota(v1)*self.iota(self._alpha1[l])
-                    for e in self._edge_list:
-                        tmp[1].append(EdgeDecomposition(self,x.adjoint()*e.rep,extrapow=nn+nninc))
-                    T.append(tmp)
-                    T0.append(v0)
-            nn+=1
-        self._hecke_coset_reps[l]=T
-        return T
-
-    def _fixed_points(self,x,K):
-        a=x[0,0]
-        b=x[0,1]
-        c=x[1,0]
-        d=x[1,1]
-        t=x.trace()
-        n=x.determinant()
-        A=K(a-d)
-        D=our_sqrt(t**2-4*n,K)
-        return [(A+D)/(2*c),(A-D)/(2*c)]
+    def get_eichler_order_quadmatrix(self):
+        try: return self._OM
+        except AttributeError: pass
+        self._init_order()
+        return self._OM
 
     def get_units_of_order(self):
         try: return self._O_units
         except AttributeError: pass
-        OGram=self._OQuadForm.matrix()
-        v=pari('qfminim('+str(OGram._pari_())+',2,0,flag=0)')
+        OM=self.get_eichler_order_quadmatrix()
+        v=pari('qfminim(%s,2,0)'%(OM._pari_()))
         n_units=ZZ(v[0].python()/2)
-        v=pari('qfminim('+str(OGram._pari_())+','+str(0)+','+str(n_units)+',flag=0)')
+        v=pari('qfminim(%s,0,%s)'%((OM._pari_()),n_units))
         self._O_units=[]
         for jj in range(n_units):
             vec=Matrix(ZZ,1,4,[v[2][ii,jj].python() for ii in range(4)])
             self._O_units.append(vec)
         return self._O_units
 
-    def _conv(self,v):
-        return (Matrix(ZZ,1,4,v)*self._B)[0,0]
-
-    def _find_elements_in_order(self,n,t=None,primitive=False):
-        p=self._ZZp.prime()
-        V=self._OQuadForm.vectors_by_length(n)[n]
-        if(primitive):
-            W=[]
-            for v in V:
-                if not all([v[ii]%p==0 for ii in range(4)]):
-                    W.append(v)
-        else:
-            W=V
-        if(t==None):
-            return W
-        WW=[]
-        for v in W:
-            x=self._conv(v)
-            assert(x.reduced_norm()==n)
-            if(x.reduced_trace()==t):
-                WW.append(v)
-        return WW
-
     def get_CM_points(self,O,prec=None,ignore_units=False):
-        p=self._ZZp.prime()
+        p=self._p
         if(prec==None):
-            prec=self._preci
+            prec=self._prec
         if(not isinstance(O,AbsoluteOrder)):
-            disc=IntegerRing()(O)
-            R = RationalField()['x']
+            disc=ZZ(O)
+            R = QQ['x']
             f = R([-disc, 0, 1])
             K=NumberField(f, 'sq', check=False)
             O=K.maximal_order()
@@ -746,8 +514,6 @@
         except KeyError:
             if(not self.is_admissible(disc)):
                 return []
-            #var('x')
-
             w=O.ring_generators()[0]
             all_elts=[]
             nn=-1
@@ -803,8 +569,311 @@
             W.append(v[0])
         return W
 
+    r"""
+     Returns (computes if necessary) Up data. This 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
+     EdgeDecomposition
+     """
+    def Up_data(self):
+        try: return self._Up_data
+        except AttributeError: pass
+        E=self.get_edge_list()
+        Up_data=[]
+        vec_a=self._BT.subdivide([1],1)
+        for ii in range(self._p):
+            alpha=vec_a[ii]
+            tmp=[alpha.inverse(),[]]#GraphActionData(alpha.inverse(),[])
+            for e in E:
+                tmp[1].append(EdgeDecomposition(self,e.rep*alpha)) #edge_list
+            for e in E:
+                tmp[1].append(EdgeDecomposition(self,e.opposite[0].rep*alpha)) #edge_list
+            Up_data.append(tmp)
+        self._Up_data=Up_data
+        return self._Up_data
+
+    def hecke_data(self,l):
+        try: return self._hecke_data[l]
+        except KeyError: pass
+        E=self.get_edge_list()
+        if((self._p*self.Nminus()*self.Nplus())%l==0):
+            Sset=[]
+        else:
+            Sset=[self._p]
+        B=self.get_eichler_order_basis()
+        BB=Matrix(QQ,4,4,[[B[ii,0][jj] for ii in range(4)] for jj in range(4)]).inverse()
+        T=[]
+        T0=[]
+        nn=0
+        alpha1=self._find_elements_in_order(l)[0]
+        print "Got a vector of length l=",l
+        alpha0=sum([alpha1[ii]*B[ii,0] for ii in range(4)])
+        self._alpha1[l]=Matrix(QQ,4,1,alpha1)
+
+        if(l==self._p):
+            nninc=1
+        else:
+            nninc=0
+        A=self.get_quaternion_algebra()
+        while(len(T)<l+1):
+            print '#T=',len(T),' and bound=',l+1
+            print 'Iteration number: ',1+ZZ(nn)
+            tmp=self._find_elements_in_order(self._p**(2*nn))
+            vec0=[sum([v[ii]*B[ii,0] for ii in range(4)])*alpha0 for v in tmp]
+            vec1=[Matrix(QQ,4,1,v) for v in tmp]
+            print 'Analyzing ',len(vec0),' vectors.'
+            while(len(T)<l+1 and len(vec0)>0):
+                print '#T=',len(T),' and bound=',l+1
+                v0=vec0.pop(0)
+                v1=vec1.pop(0)
+                vinv=(v0)**(-1)
+                new=True
+                for tt in range(len(T)):
+                    r=A(vinv*T0[tt])
+                    x=BB*Matrix(QQ,4,1,[r[ii] for ii in range(4)])
+                    if(all([x[jj,0].is_S_integral(Sset) for jj in range(4)])):
+                        new=False
+                        break
+                if(new):
+                    tmp=[v1,[]]#GraphActionData(v1,[])
+                    x=self.iota(v1)*self.iota(self._alpha1[l])
+                    for e in E:
+                        tmp[1].append(EdgeDecomposition(self,x.adjoint()*e.rep,extrapow=nn+nninc))
+                    T.append(tmp)
+                    T0.append(v0)
+            nn+=1
+        self._hecke_data[l]=T
+        return T
+
+    def _find_lattice(self,v1,v2,m,X):
+        p=self._p
+        if(m+1>self._prec):
+            self.get_Iota(m+1)
+        v1adj=v1.adjoint()
+        R=self._R4x4
+        vecM=[v2*X[ii]*v1adj for ii in range(4)]
+        M=(self._Iotainv*R([[vecM[ii][jj,kk] for ii in range(4) ] for jj in range(2) for kk in range(2)])).augment(R(self._pN)).transpose()
+        M.echelonize()
+        E = M.submatrix(0,0,4,4)
+        A=E*self._OM*E.transpose()
+        return E,A
+
+    def _edge_stabilizer(self,e):
+        p=self._p
+        m=valuation(e.determinant(),p)
+        twom=2*m
+        E,A = self._find_lattice(e,e,twom,self._Xe)
+        n_units=len(self.get_units_of_order())
+        ## Using PARI to get the shortest vector in the lattice (via LLL)
+        v=pari('qfminim(%s,0,%s)'%(A._pari_(),n_units))
+        n_vecs=ZZ(v[0].python()/2)
+        B=self.get_eichler_order_basis()
+        if(n_vecs>1):
+            tmp=[]
+            for jj in range(n_vecs):
+                vec=Matrix(ZZ,1,4,[v[2][ii,jj].python() for ii in range(4)])
+                nrd=ZZ((vec*A*vec.transpose())[0,0]/2)
+                if(nrd.is_power_of(p**2) and nrd.valuation(p)==twom):
+                    w=vec*E
+                    x=self._conv(w)
+                    tmp.append([w.transpose(),m,x!=p**m])
+            return tmp
+        else:
+            return 1
+        return 1
+
+    def _are_equivalent(self,v1,v2,twom,X):
+        p=self._p
+        if(twom==-1):
+            twom=(v1*v2).determinant().valuation(p)
+        if (twom%2!=0):
+            return 0
+        E,A=self._find_lattice(v1,v2,twom,X)
+        m=ZZ(twom/2)
+        ## Using PARI to get the shortest vector in the lattice (via LLL)
+        try:
+            v=pari('qfminim(%s,0,1)'%(A._pari_()))
+        except:
+            print A
+            assert(0)
+        # vec=Matrix(ZZ,1,4,[v[2][ii,0].python() for ii in range(4)])
+        # try: nrd=ZZ((vec*A*vec.transpose())[0,0]/2)
+        # except TypeError:
+        #     return 0
+        try: nrd=ZZ(v[1].python()/2)
+        except TypeError:
+            return 0
+        if(nrd.is_power_of(p**2) and nrd.valuation(p)==twom):
+                return [(Matrix(ZZ,1,4,[v[2][ii,0].python() for ii in range(4)])*E).transpose(),m]
+        else:
+               return 0
+
+    def _init_order(self):
+        if(self._source=='magma'):
+            A=self._magma.QuaternionAlgebra(self._Nminus)
+            g=A.gens()
+            #We store the order because we need to split it
+            self._O=A.QuaternionOrder(self._Nplus)
+            OBasis=self._O.Basis()
+            self._A=QuaternionAlgebra((g[0]**2).sage(),(g[1]**2).sage())
+            v=[1]+self._A.gens()
+            self._B=Matrix(self._A,4,1,[sum([OBasis[tt+1][rr+1].sage()*v[rr] for rr in range(4)]) for tt in range(4)])
+        else:
+            assert(self._source=='sage')
+            # Note that we can't work with non-maximal orders in sage
+            assert(self._Nplus==1)
+            self._A=QuaternionAlgebra(self._Nminus)
+            v=[1]+self._A.gens()
+            self._O=self._A.maximal_order()
+            OBasis=self._O.basis()
+
+            # Added to test example from Darmon's Book
+            if(self._p==7 and self._Nminus==2):
+                OBasis=[self._A(1),OBasis[1],OBasis[2],OBasis[0]]
+            # End of addition
+
+            self._B=Matrix(self._A,4,1,[OBasis[tt] for tt in range(4)])
+        self._OQuadForm=QuadraticForm(Matrix(ZZ,4,4,[(self._B[ii,0]*self._B[jj,0].conjugate()).reduced_trace() for ii in range(4) for jj in range(4)]))
+        self._OM=self._OQuadForm.matrix()
+
+    def _compute(self):
+        self.get_Iota(1)
+        p=self._p
+        num_verts=0
+        V=[Vertex(self,num_verts,Matrix(ZZ,2,2,[1,0,0,1]),[],[],1,0)]
+        E=[]
+        v0=V[0]
+        S=Graph(0,multiedges=True,weighted=True)
+        edge_list=[]
+        vertex_list=[[V[0]],[]]
+        vertex_list_done=[]
+        self._num_edges=0
+        num_verts+=1
+        B_one=self._are_equivalent(V[0].rep,V[0].rep,0,self._Xe)
+        while len(V)>0:
+            v=V.pop(0)
+            E=self._BT.leaving_edges(v.rep)
+            for e in E:
+                new_edge=True
+                edge_det=e[0].determinant()
+                edge_valuation=edge_det.valuation(p)
+                for e1 in v.leaving_edges:
+                    g=self._are_equivalent(e1.rep,e[0],e1.valuation+edge_valuation,self._Xe)
+                    if(g):
+                        e1.links.append(g)
+                        new_edge=False
+                        break
+                if(new_edge):
+                    # Now we found a new edge. Decide whether its target vertex is new
+                    target=e[1]
+                    new_vertex=True
+                    new_det=target.determinant()
+                    new_valuation=new_det.valuation(p)
+                    new_parity=new_valuation%2
+                    #for v1 in vertex_list[new_parity]:
+                    for v1 in V:
+                        if(v1.parity!=new_parity):
+                            continue
+                        g1=self._are_equivalent(target,v1.rep,v1.valuation+new_valuation,self._Xv)
+                        if(g1):
+                            target=v1.rep
+                            #The edge is new, but the vertices are not
+                            new_vertex=False
+                            break
+                    if(new_vertex):
+                        #The vertex is also new
+                        v1=Vertex(self,num_verts,target,[],[],new_det,new_valuation)
+                        vertex_list[new_valuation%2].append(v1)
+                        num_verts+=1
+                        S.add_vertex(v1.label)
+                        #Add the vertex to the list of pending vertices
+                        V.append(v1)
+
+                    # Add the edge to the list
+                    new_e=Edge(self,self._num_edges,e[0],v,v1,[],[],edge_det,edge_valuation)
+                    new_e.links.append(B_one)
+
+                    # Find the opposite edge
+                    opp=self._BT.opposite(e[0])
+
+                    # Add the edge to the graph
+                    S.add_edge(v.label,v1.label,self._num_edges)
+                    if(S.degree(v.label) == self._p+1):
+                        vertex_list_done.append(v)
+                        vertex_list[v.parity].remove(v)
+                    if(S.degree(v1.label) == self._p+1):
+                        vertex_list_done.append(v1)
+                        vertex_list[v1.parity].remove(v1)
+
+                    opp_det=opp.determinant()
+                    new_e_opp=Edge(self,self._num_edges,opp,v1,v,[],[new_e],opp_det,opp_det.valuation(p))
+                    assert(len(new_e.opposite)==0)
+                    new_e.opposite.append(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)
+
+                    self._num_edges += 1
+        genus=ZZ(1- len(vertex_list[0])-len(vertex_list[1])-len(vertex_list_done)+self._num_edges)
+        if(genus!=self.generic_fiber().genus()):
+            print 'You found a bug!'
+            print 'Computed genus =',genus
+            print 'Theoretical genus =',self.generic_fiber().genus()
+        self._genus=genus
+        self._edge_list=edge_list
+        self._vertex_list=vertex_list[0]+vertex_list[1]+vertex_list_done
+        self._S=S
+        return self._edge_list
+
+    def _fixed_points(self,x,K):
+        a=x[0,0]
+        b=x[0,1]
+        c=x[1,0]
+        d=x[1,1]
+        t=x.trace()
+        n=x.determinant()
+        A=K(a-d)
+        D=our_sqrt(t**2-4*n,K)
+        return [(A+D)/(2*c),(A-D)/(2*c)]
+
+    def _conv(self,v):
+        return (Matrix(QQ,1,4,v)*self.get_eichler_order_basis())[0,0]
+
+    def _find_elements_in_order(self,n,t=None,primitive=False):
+        p=self._p
+        OQuadForm=self.get_eichler_order_quadform()
+        V=OQuadForm.vectors_by_length(n)[n]
+        if(primitive):
+            W=[]
+            for v in V:
+                if not all([v[ii]%p==0 for ii in range(4)]):
+                    W.append(v)
+        else:
+            W=V
+        if(t==None):
+            return W
+        WW=[]
+        for v in W:
+            x=self._conv(v)
+            assert(x.reduced_norm()==n)
+            if(x.reduced_trace()==t):
+                WW.append(v)
+        return WW
+
+
 class ShimuraCurve(SageObject,UniqueRepresentation):
     def __init__(self,lev,Nplus=1):
+        lev=ZZ(lev)
+        Nplus=ZZ(Nplus)
         if (not is_squarefree(lev)):
             raise NotImplementedError, "level must be squarefree"
         if(gcd(lev,Nplus)>1):
@@ -812,7 +881,7 @@
         self._level=lev
         self._Nplus=Nplus
 
-        if(len(factor(lev))%2!=0):
+        if(len(lev.factor())%2!=0):
             raise ValueError, "level should be divisible by an even number of primes"
         self._fibers=dict([])
 
@@ -820,11 +889,11 @@
         e4=1
         e3=1
         mu=Nplus
-        for f in factor(lev):
+        for f in lev.factor():
             e4=e4*(1-kronecker_symbol(-4,ZZ(f[0])))
             e3=e3*(1-kronecker_symbol(-3,ZZ(f[0])))
             mu=mu*(ZZ(f[0])-1)
-        for f in factor(Nplus):
+        for f in Nplus.factor():
             if (f[1]==1):
                 e4=e4*(1+kronecker_symbol(-4,ZZ(f[0])))
                 e3=e3*(1+kronecker_symbol(-3,ZZ(f[0])))
@@ -845,28 +914,37 @@
         self._genus=1+ZZ(mu/12-e3/3-e4/4-einf/2)
 
     def _repr_(self):
-        return "Shimura Curve of level "+str(self._level)+" and conductor "+str(self._Nplus)
+        return "Shimura Curve of level %s and conductor %s"%(self._level.factor(),self._Nplus.factor())
+
+    def _latex_(self):
+        return "X(%s,%s)"%(latex(self._level.factor()),latex(self._Nplus.factor()))
+
     def level(self):
         return self._level
+
     def genus(self):
         return self._genus
 
+    def is_admissible(self,D):
+        D=ZZ(D).squarefree_part()
+        disc=D
+        if(D%4!=1):
+            disc*=4
+        ff=self._level.factor()
+        for f in ff:
+            if kronecker_symbol(disc,f[0])!=-1:
+                return False
+        ff=self._Nplus.factor()
+        for f in ff:
+            if kronecker_symbol(disc,f[0])!=1:
+                return False
+        return True
+
     def __getitem__(self,key):
         return self.special_fiber(key)
 
-    def special_fiber(self,p,source='sage',magma_proc=None):
-        if(not Integer(p).is_prime()):
-            raise ValueError, "must be a prime"
-        if(self._level%Integer(p)!= 0):
-            raise ValueError, "must divide the level"
-
-        if(not self._fibers.has_key(p)):
-            self._fibers[p]=ShimuraCurveFiber(self,p,source=source,magma_proc=magma_proc,compute=False)
-
-        if(not self._fibers[p]._is_cached()):
-            self._fibers[p]._compute()
-            if(self._fibers[p].genus()!=self.genus()):
-                print 'You found a bug!'
-                print 'Computed genus=',self._fibers[p].genus()
-                print 'Theoretical genus=',self._genus
+    def special_fiber(self,p,source='sage'):
+        try: return self._fibers[p]
+        except KeyError: pass
+        self._fibers[p]=ShimuraCurveFiber(self,p,source=source)
         return self._fibers[p]