def test_tensprod_121_chi(): C121=[1,2,-1,2,1,-2,2,0,-2,2,0,-2,-4,4,-1,-4,2,-4,0,2,-2,0,\ -1,0,-4,-8,5,4,0,-2,7,-8,0,4,2,-4,3,0,4,0,8,-4,6,0,-2,-2,\ 8,4,-3,-8,-2,-8,-6,10,0,0,0,0,5,-2,-12,14,-4,-8,-4,0,-7,4,\ 1,4,-3,0,-4,6,4,0,0,8,10,-4,1,16,6,-4,2,12,0,0,15,-4,-8,\ -2,-7,16,0,8,-7,-6,0,-8,-2,-4,-16,0,-2,-12,-18,10,-10,0,-3,\ -8,9,0,-1,0,8,10,4,0,0,-24,-8,14,-9,-8,-8,0,-6,-8,18,0,0,\ -14,5,0,-7,2,-10,4,-8,-6,0,8,0,-8,3,6,10,8,-2,0,-4,0,7,8,\ -7,20,6,-8,-2,2,4,16,0,12,12,0,3,4,0,12,6,0,-8,0,-5,30,\ -15,-4,7,-16,12,0,3,-14,0,16,10,0,17,8,-4,-14,4,-6,2,0,0,0] chi=[1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,\ 1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,\ -1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,\ 1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,\ 1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,\ 1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,\ -1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,\ -1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,\ -1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1] ANS=[1,-2,-1,2,1,2,-2,0,-2,-2,1,-2,4,4,-1,-4,-2,4,0,2,2,-2,\ -1,0,-4,-8,5,-4,0,2,7,8,-1,4,-2,-4,3,0,-4,0,-8,-4,-6,2,-2,\ 2,8,4,-3,8,2,8,-6,-10,1,0,0,0,5,-2,12,-14,4,-8,4,2,-7,-4,\ 1,4,-3,0,4,-6,4,0,-2,8,-10,-4,1,16,-6,4,-2,12,0,0,15,4,-8,\ -2,-7,-16,0,-8,-7,6,-2,-8,2,-4,-16,0,2,12,18,10,10,-2,-3,8,\ 9,0,-1,0,-8,-10,4,0,1,-24,8,14,-9,-8,8,0,6,-8,-18,-2,0,14,\ 5,0,-7,-2,10,-4,-8,6,4,8,0,-8,3,6,-10,-8,2,0,4,4,7,-8,-7,\ 20,6,8,2,-2,4,-16,-1,12,-12,0,3,4,0,-12,-6,0,8,-4,-5,-30,\ -15,-4,7,16,-12,0,3,14,-2,16,-10,0,17,8,4,14,-4,-6,-2,4,0,0] R = PowerSeriesRing(ZZ, "T") T = R.gens()[0] assert ANS==tensor_get_an_deg1(C121,chi,[[11,1-T]]) assert ANS==tensor_get_an(C121,chi,2,1,[[11,1-T,1-T]]) assert get_euler_factor(ANS,2)==(1+2*T+2*T**2+O(T**8)) assert get_euler_factor(ANS,3)==(1+T+3*T**2+O(T**5)) assert get_euler_factor(ANS,5)==(1-T+5*T**2+O(T**4))
def test_tensprod_11a_17a(): C11=[1,-2,-1,2,1,2,-2,0,-2,-2,1,-2,4,4,-1,-4,-2,4,0,2,2,-2,\ -1,0,-4,-8,5,-4,0,2,7,8,-1,4,-2,-4,3,0,-4,0,-8,-4,-6,2,-2,\ 2,8,4,-3,8,2,8,-6,-10,1,0,0,0,5,-2,12,-14,4,-8,4,2,-7,-4,\ 1,4,-3,0,4,-6,4,0,-2,8,-10,-4,1,16,-6,4,-2,12,0,0,15,4,-8,\ -2,-7,-16,0,-8,-7,6,-2,-8,2,-4,-16,0,2,12,18,10,10,-2,-3,8,\ 9,0,-1,0,-8,-10,4,0,1,-24,8,14,-9,-8,8,0,6,-8,-18,-2,0,14,\ 5,0,-7,-2,10,-4,-8,6,4,8,0,-8,3,6,-10,-8,2,0,4,4,7,-8,-7,\ 20,6,8,2,-2,4,-16,-1,12,-12,0,3,4,0,-12,-6,0,8,-4,-5,-30,\ -15,-4,7,16,-12,0,3,14,-2,16,-10,0,17,8,4,14,-4,-6,-2,4,0,0] C17=[1,-1,0,-1,-2,0,4,3,-3,2,0,0,-2,-4,0,-1,1,3,-4,2,0,0,4,\ 0,-1,2,0,-4,6,0,4,-5,0,-1,-8,3,-2,4,0,-6,-6,0,4,0,6,-4,0,\ 0,9,1,0,2,6,0,0,12,0,-6,-12,0,-10,-4,-12,7,4,0,4,-1,0,8,\ -4,-9,-6,2,0,4,0,0,12,2,9,6,-4,0,-2,-4,0,0,10,-6,-8,-4,0,\ 0,8,0,2,-9,0,1,-10,0,8,-6,0,-6,8,0,6,0,0,-4,-14,0,-8,-6,\ 6,12,4,0,-11,10,0,-4,12,12,8,3,0,-4,16,0,-16,-4,0,3,-6,0,\ -8,8,0,4,0,3,-12,6,0,2,-10,0,-16,-12,-3,0,-8,0,-2,-12,0,10,\ 16,-9,24,6,0,4,-4,0,-9,2,12,-4,22,0,-4,0,0,-10,12,-6,-2,8,\ 0,12,4,0,0,0,0,-8,-16,0,2,-2,0,-9,-18,0,-20,-3] ANS=[1,2,0,2,-2,0,-8,8,15,-4,0,0,-8,-16,0,12,-2,30,0,-4,0,0,\ -4,0,29,-16,0,-16,0,0,28,-8,0,-4,16,30,-6,0,0,-16,48,0,-24,\ 0,-30,-8,0,0,22,58,0,-16,-36,0,0,-64,0,0,-60,0,-120,56,-120,\ -8,16,0,-28,-4,0,32,12,120,-24,-12,0,0,0,0,-120,-24,144,96,\ 24,0,4,-48,0,0,150,-60,64,-8,0,0,0,0,-14,44,0,58,-20,0,-128,\ -64,0,-72,144,0,60,0,0,-96,-126,0,8,0,-120,-120,16,0,-11,-240,\ 0,56,-158,-240,64,-32,0,32,-288,0,0,-56,0,-16,42,0,-80,32,0,\ 24,0,180,0,-48,0,-12,100,0,-32,0,-30,0,-56,0,14,-240,0,16,32,\ 288,96,96,0,48,48,0,142,8,0,-48,-132,0,-232,0,0,300,-180,-60,\ -14,128,0,-32,12,0,0,0,0,0,-272,0,8,-28,0,44,36,0,0,232] R = PowerSeriesRing(ZZ, "T") T = R.gens()[0] B11=[11,1-T,1+11*T**2] B17=[17,1+2*T+17*T**2,1-T] assert ANS==tensor_get_an_no_deg1(C11,C17,2,2,[B11,B17])
def series(self, prec=5): r""" Return the ``prec``-th approximation to the `p`-adic `L`-series associated to self, as a power series in `T` (corresponding to `\gamma-1` with `\gamma` the chosen generator of `1+p\ZZ_p`). INPUT: - ``prec`` -- (default 5) is the precision of the power series EXAMPLES:: sage: E = EllipticCurve('14a2') sage: p = 3 sage: prec = 6 sage: L = E.padic_lseries(p,implementation="pollackstevens",precision=prec) # long time sage: L.series(4) # long time 2*3 + 3^4 + 3^5 + O(3^6) + (2*3 + 3^2 + O(3^4))*T + (2*3 + O(3^2))*T^2 + (3 + O(3^2))*T^3 + O(T^4) sage: E = EllipticCurve("15a3") sage: L = E.padic_lseries(5,implementation="pollackstevens",precision=15) # long time sage: L.series(3) # long time O(5^15) + (2 + 4*5^2 + 3*5^3 + 5^5 + 2*5^6 + 3*5^7 + 3*5^8 + 2*5^9 + 2*5^10 + 3*5^11 + 5^12 + O(5^13))*T + (4*5 + 4*5^3 + 3*5^4 + 4*5^5 + 3*5^6 + 2*5^7 + 5^8 + 4*5^9 + 3*5^10 + O(5^11))*T^2 + O(T^3) sage: E = EllipticCurve("79a1") sage: L = E.padic_lseries(2,implementation="pollackstevens",precision=10) # not tested sage: L.series(4) # not tested O(2^9) + (2^3 + O(2^4))*T + O(2^0)*T^2 + (O(2^-3))*T^3 + O(T^4) """ p = self.prime() M = self.symbol().precision_relative() K = pAdicField(p, M) R = PowerSeriesRing(K, names='T') T = R.gens()[0] return R([self[i] for i in range(prec)]).add_bigoh(prec)
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 series(self, prec=5): r""" Return the ``prec``-th approximation to the `p`-adic `L`-series associated to self, as a power series in `T` (corresponding to `\gamma-1` with `\gamma` the chosen generator of `1+p\ZZ_p`). INPUT: - ``prec`` -- (default 5) is the precision of the power series EXAMPLES:: sage: E = EllipticCurve('14a2') sage: p = 3 sage: prec = 6 sage: L = E.padic_lseries(p,implementation="pollackstevens",precision=prec) # long time sage: L.series(4) # long time 2*3 + 3^4 + 3^5 + O(3^6) + (2*3 + 3^2 + O(3^4))*T + (2*3 + O(3^2))*T^2 + (3 + O(3^2))*T^3 + O(T^4) sage: E = EllipticCurve("15a3") sage: L = E.padic_lseries(5,implementation="pollackstevens",precision=15) # long time sage: L.series(3) # long time O(5^15) + (2 + 4*5^2 + 3*5^3 + 5^5 + 2*5^6 + 3*5^7 + 3*5^8 + 2*5^9 + 2*5^10 + 3*5^11 + 5^12 + O(5^13))*T + (4*5 + 4*5^3 + 3*5^4 + 4*5^5 + 3*5^6 + 2*5^7 + 5^8 + 4*5^9 + 3*5^10 + O(5^11))*T^2 + O(T^3) sage: E = EllipticCurve("79a1") sage: L = E.padic_lseries(2,implementation="pollackstevens",precision=10) # not tested sage: L.series(4) # not tested O(2^9) + (2^3 + O(2^4))*T + O(2^0)*T^2 + (O(2^-3))*T^3 + O(T^4) """ p = self.prime() M = self.symbol().precision_relative() K = pAdicField(p, M) R = PowerSeriesRing(K, names="T") T = R.gens()[0] return R([self[i] for i in range(prec)]).add_bigoh(prec)
def test_tensprod_11a_17a(): C11=[1,-2,-1,2,1,2,-2,0,-2,-2,1,-2,4,4,-1,-4,-2,4,0,2,2,-2,\ -1,0,-4,-8,5,-4,0,2,7,8,-1,4,-2,-4,3,0,-4,0,-8,-4,-6,2,-2,\ 2,8,4,-3,8,2,8,-6,-10,1,0,0,0,5,-2,12,-14,4,-8,4,2,-7,-4,\ 1,4,-3,0,4,-6,4,0,-2,8,-10,-4,1,16,-6,4,-2,12,0,0,15,4,-8,\ -2,-7,-16,0,-8,-7,6,-2,-8,2,-4,-16,0,2,12,18,10,10,-2,-3,8,\ 9,0,-1,0,-8,-10,4,0,1,-24,8,14,-9,-8,8,0,6,-8,-18,-2,0,14,\ 5,0,-7,-2,10,-4,-8,6,4,8,0,-8,3,6,-10,-8,2,0,4,4,7,-8,-7,\ 20,6,8,2,-2,4,-16,-1,12,-12,0,3,4,0,-12,-6,0,8,-4,-5,-30,\ -15,-4,7,16,-12,0,3,14,-2,16,-10,0,17,8,4,14,-4,-6,-2,4,0,0] C17=[1,-1,0,-1,-2,0,4,3,-3,2,0,0,-2,-4,0,-1,1,3,-4,2,0,0,4,\ 0,-1,2,0,-4,6,0,4,-5,0,-1,-8,3,-2,4,0,-6,-6,0,4,0,6,-4,0,\ 0,9,1,0,2,6,0,0,12,0,-6,-12,0,-10,-4,-12,7,4,0,4,-1,0,8,\ -4,-9,-6,2,0,4,0,0,12,2,9,6,-4,0,-2,-4,0,0,10,-6,-8,-4,0,\ 0,8,0,2,-9,0,1,-10,0,8,-6,0,-6,8,0,6,0,0,-4,-14,0,-8,-6,\ 6,12,4,0,-11,10,0,-4,12,12,8,3,0,-4,16,0,-16,-4,0,3,-6,0,\ -8,8,0,4,0,3,-12,6,0,2,-10,0,-16,-12,-3,0,-8,0,-2,-12,0,10,\ 16,-9,24,6,0,4,-4,0,-9,2,12,-4,22,0,-4,0,0,-10,12,-6,-2,8,\ 0,12,4,0,0,0,0,-8,-16,0,2,-2,0,-9,-18,0,-20,-3] ANS=[1,2,0,2,-2,0,-8,8,15,-4,0,0,-8,-16,0,12,-2,30,0,-4,0,0,\ -4,0,29,-16,0,-16,0,0,28,-8,0,-4,16,30,-6,0,0,-16,48,0,-24,\ 0,-30,-8,0,0,22,58,0,-16,-36,0,0,-64,0,0,-60,0,-120,56,-120,\ -8,16,0,-28,-4,0,32,12,120,-24,-12,0,0,0,0,-120,-24,144,96,\ 24,0,4,-48,0,0,150,-60,64,-8,0,0,0,0,-14,44,0,58,-20,0,-128,\ -64,0,-72,144,0,60,0,0,-96,-126,0,8,0,-120,-120,16,0,-11,-240,\ 0,56,-158,-240,64,-32,0,32,-288,0,0,-56,0,-16,42,0,-80,32,0,\ 24,0,180,0,-48,0,-12,100,0,-32,0,-30,0,-56,0,14,-240,0,16,32,\ 288,96,96,0,48,48,0,142,8,0,-48,-132,0,-232,0,0,300,-180,-60,\ -14,128,0,-32,12,0,0,0,0,0,-272,0,8,-28,0,44,36,0,0,232] R = PowerSeriesRing(ZZ, "T") T = R.gens()[0] B11 = [11, 1 - T, 1 + 11 * T**2] B17 = [17, 1 + 2 * T + 17 * T**2, 1 - T] assert ANS == tensor_get_an_no_deg1(C11, C17, 2, 2, [B11, B17])
def test_tensprod_121_chi(): C121=[1,2,-1,2,1,-2,2,0,-2,2,0,-2,-4,4,-1,-4,2,-4,0,2,-2,0,\ -1,0,-4,-8,5,4,0,-2,7,-8,0,4,2,-4,3,0,4,0,8,-4,6,0,-2,-2,\ 8,4,-3,-8,-2,-8,-6,10,0,0,0,0,5,-2,-12,14,-4,-8,-4,0,-7,4,\ 1,4,-3,0,-4,6,4,0,0,8,10,-4,1,16,6,-4,2,12,0,0,15,-4,-8,\ -2,-7,16,0,8,-7,-6,0,-8,-2,-4,-16,0,-2,-12,-18,10,-10,0,-3,\ -8,9,0,-1,0,8,10,4,0,0,-24,-8,14,-9,-8,-8,0,-6,-8,18,0,0,\ -14,5,0,-7,2,-10,4,-8,-6,0,8,0,-8,3,6,10,8,-2,0,-4,0,7,8,\ -7,20,6,-8,-2,2,4,16,0,12,12,0,3,4,0,12,6,0,-8,0,-5,30,\ -15,-4,7,-16,12,0,3,-14,0,16,10,0,17,8,-4,-14,4,-6,2,0,0,0] chi=[1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,\ 1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,\ -1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,\ 1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,\ 1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,\ 1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,\ -1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,\ -1,-1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1,1,1,1,-1,-1,\ -1,1,-1,0,1,-1,1,1,1,-1,-1,-1,1,-1,0,1,-1] ANS=[1,-2,-1,2,1,2,-2,0,-2,-2,1,-2,4,4,-1,-4,-2,4,0,2,2,-2,\ -1,0,-4,-8,5,-4,0,2,7,8,-1,4,-2,-4,3,0,-4,0,-8,-4,-6,2,-2,\ 2,8,4,-3,8,2,8,-6,-10,1,0,0,0,5,-2,12,-14,4,-8,4,2,-7,-4,\ 1,4,-3,0,4,-6,4,0,-2,8,-10,-4,1,16,-6,4,-2,12,0,0,15,4,-8,\ -2,-7,-16,0,-8,-7,6,-2,-8,2,-4,-16,0,2,12,18,10,10,-2,-3,8,\ 9,0,-1,0,-8,-10,4,0,1,-24,8,14,-9,-8,8,0,6,-8,-18,-2,0,14,\ 5,0,-7,-2,10,-4,-8,6,4,8,0,-8,3,6,-10,-8,2,0,4,4,7,-8,-7,\ 20,6,8,2,-2,4,-16,-1,12,-12,0,3,4,0,-12,-6,0,8,-4,-5,-30,\ -15,-4,7,16,-12,0,3,14,-2,16,-10,0,17,8,4,14,-4,-6,-2,4,0,0] R = PowerSeriesRing(ZZ, "T") T = R.gens()[0] assert ANS == tensor_get_an_deg1(C121, chi, [[11, 1 - T]]) assert ANS == tensor_get_an(C121, chi, 2, 1, [[11, 1 - T, 1 - T]]) assert get_euler_factor(ANS, 2) == (1 + 2 * T + 2 * T**2 + O(T**8)) assert get_euler_factor(ANS, 3) == (1 + T + 3 * T**2 + O(T**5)) assert get_euler_factor(ANS, 5) == (1 - T + 5 * T**2 + O(T**4))
def list_to_euler_factor(L,prec): """ takes a list [a_p, a_p^2,... and returns the euler factor """ if isinstance(L[0], int): K = QQ else: K = L[0].parent() R = PowerSeriesRing(K, "T") T = R.gens()[0] f = 1/ R([1]+L) f = f.add_bigoh(prec+1) return f
def list_to_euler_factor(L, d): """ takes a list [a_p, a_p^2,... and returns the euler factor """ if isinstance(L[0], int): K = QQ else: K = L[0].parent() R = PowerSeriesRing(K, "T") T = R.gens()[0] f = 1 / R([1] + L) f = f.add_bigoh(d + 1) return f
class padic_Lfunction_two_variable(padic_Lfunction): def __init__(self, Phis, var='T', prec=None): #TODO: prec: Default it to None would make us compute it. self._Phis = Phis #should we create a copy of Phis, in case Phis changes? probably self._coefficient_ring = Phis.base_ring() self._base_ring = PowerSeriesRing(self._coefficient_ring, var) #What precision? self._prec = prec def base_ring(self): return self._base_ring def coefficient_ring(self): return self._coefficient_ring def variables(self): #returns (T, w) return (self._base_ring.gens()[0], self._coefficient_ring.gens()[0]) def _max_coeff(self): Phis = self._Phis p = Phis.parent().prime() p_prec, var_prec = Phis.precision_absolute() max_j = Phis.parent().coefficient_module().length_of_moments(p_prec) n = 0 while True: if max_j - (n / (p-1)).floor() - min(max_j, n) - (max_j / p).floor() <= 0: return n - 1 n += 1 def _on_Da(self, a, twist): r""" An internal method used by ``self._basic_integral``. The parameter ``twist`` is assumed to be either ``None`` or a primitive quaratic Dirichlet character. """ p = self._Phis.parent().prime() if twist is None: return self._Phis(M2Z([1,a,0,p])) D = twist.level() DD = self._Phis.parent().coefficient_module() S0 = DD.action().actor() ans = DD.zero() for b in range(1, D + 1): if D.gcd(b) == 1: ans += twist(b) * (self._Phis(M2Z([1, D * a + b * p, 0, D * p])) * S0([1, b / D, 0, 1])) return ans @cached_method def _basic_integral(self, a, j, twist=None): r""" Computes the integral .. MATH:: \int_{a+p\ZZ_p}(z-\omega(a))^jd\mu_\chi. If ``twist`` is ``None``, `\\chi` is the trivial character. Otherwise, ``twist`` can be a primitive quadratic character of conductor prime to `p`. """ #is this the negative of what we want? #if Phis is fixed for this p-adic L-function, we should make this method cached p = self._Phis.parent().prime() if twist is None: pass elif twist in ZZ: twist = kronecker_character(twist) if twist.is_trivial(): twist = None else: D = twist.level() assert(D.gcd(p) == 1) else: if twist.is_trivial(): twist = None else: assert((twist**2).is_trivial()) twist = twist.primitive_character() D = twist.level() assert(D.gcd(p) == 1) onDa = self._on_Da(a, twist)#self._Phis(Da) aminusat = a - self._Phis.parent().base_ring().base_ring().teichmuller(a) #aminusat = a - self._coefficient_ring.base_ring().teichmuller(a) try: ap = self._ap except AttributeError: self._ap = self._Phis.Tq_eigenvalue(p) #catch exception if not eigensymbol ap = self._ap if not twist is None: ap *= twist(p) if j == 0: return (~ap) * onDa.moment(0) if a == 1: #aminusat is 0, so only the j=r term is non-zero return (~ap) * (p ** j) * onDa.moment(j) #print "j =", j, "a = ", a ans = onDa.moment(0) * (aminusat ** j) #ans = onDa.moment(0) #print "\tr =", 0, " ans =", ans for r in range(1, j+1): if r == j: ans += binomial(j, r) * (p ** r) * onDa.moment(r) else: ans += binomial(j, r) * (aminusat ** (j - r)) * (p ** r) * onDa.moment(r) #print "\tr =", r, " ans =", ans #print " " return (~ap) * ans @cached_method def _compute_nth_coeff(self, n, twist=None): r""" Computes the coefficient of T^n. """ #TODO: Check that n is not too big #TODO implement twist Phis = self._Phis p = Phis.parent().prime() if n == 0: return sum([self._basic_integral(a, 0, twist) for a in range(1, p)]) p_prec, var_prec = Phis.precision_absolute() max_j = Phis.parent().coefficient_module().length_of_moments(p_prec) ans_prec = max_j - (n / (p-1)).floor() - min(max_j, n) - (max_j / p).floor() if ans_prec == 0: return self._coefficient_ring(0) #prec = self._Phis.parent()#precision_absolute()[0] #Not quite right, probably #print "@@@@n =", n, "prec =", prec cjns = list(logp_binom(n, p, max_j+1)) #print cjns teich = Phis.parent().base_ring().base_ring().teichmuller #Next line should work but loses precision!!! ans = sum([cjns[j] * sum([((~teich(a)) ** j) * self._basic_integral(a, j, twist) for a in range(1,p)]) for j in range(1, min(max_j, len(cjns)))]) #Instead do this messed up thing w = ans.parent().gen() #ans = 0*w #for j in range(1,min(max_j, len(cjns))): # ans_term = [0*w] * var_prec # for a in range(1,p): # term = (((~teich(a)) ** j) * self._basic_integral(a, j, twist)).list() # for i in range(min(var_prec, len(term))): # ans_term[i] += term[i] # ans += cjns[j] * sum([ans_term[i] * w**i for i in range(var_prec)]) #print ans_prec ans_prec = O(p**ans_prec) #print ans_prec #print ans for i in range(ans.degree() + 1): ans += ans_prec * w**i return ans def coefficient(self, index, twist=None): r""" index should be some sort of pair (i,j) corresponding to T^i w^j. Maybe if index is just one number, i, it can return the coefficient of T^i to biggest possible precision in w. Should also make it so that one can pass some monomial in the variables. """ pass def power_series(self, twist=None, prec=None): r""" returns a power series in base_ring, up to given precision, or max prec if prec is None """ p = self._Phis.parent().prime() if prec is None: prec = self._max_coeff()#Phis.precision_absolute()[0] #Not quite right, probably else: pass #do some checks on inputted prec return self._base_ring([self._compute_nth_coeff(n, twist) for n in range(prec + 1)])
class BianchiDistributions(Module, UniqueRepresentation): r""" This class represents the overconvergent approximation modules used to describe p-adic overconvergent Bianchi modular symbols. INPUT: - ``p`` - integer Prime with which we work - ``depth`` - integer (Default: None) Precision to which we work; work with moments x^iy^j for i,j up to the depth - ``act_on_left`` - boolean, (Default: False) Encodes whether Sigma_0(p)^2 is acting on the left or right. - ``adjuster`` - Sigma0ActionAdjuster class (Default: _default_adjuster()) If using a different convention for matrix actions, tell the code where a,b,c,d w should be mapped to. AUTHORS: - Marc Masdeu (2018-08-14) - Chris Williams (2018-08-16) """ def __init__(self, p, depth, act_on_left=False, adjuster=None): self._dimension = 0 ## Hack!! Dimension was being called before it was intialised self._Rmod = ZpCA(p, depth - 1) ## create Zp Module.__init__(self, base=self._Rmod) self.Element = BianchiDistributionElement self._R = ZZ self._p = p self._depth = depth self._pN = self._p**(depth - 1) self._cache_powers = dict() self._unset_coercions_used() ## Initialise monoid Sigma_0(p) + action; use Pollack-Stevens modular symbol code ## our_adjuster() is set above to allow different conventions if adjuster is None: adjuster = _default_adjuster() self._adjuster = adjuster ## Power series ring for representing distributions as strings self._repr_R = PowerSeriesRing(self._R, num_gens=2, default_prec=self._depth, names='X,Y') self._Sigma0Squared = Sigma0Squared(self._p, self._Rmod, adjuster) self._act = Sigma0SquaredAction(self._Sigma0Squared, self, act_on_left=act_on_left) self.register_action(self._act) self._populate_coercion_lists_() ## Initialise dictionaries of indices to translate between pairs and index for moments self._index = dict() self._ij = [] m = 0 ## Populate dictionary/array giving index of the basis element corr. to tuple (i,j), 0 <= i,j <= depth = n ## These things are ordered by degree of y, then degree of x: [1, x, x^2, ..., y, xy, ... ] for j in range(depth): for i in range(depth): self._ij.append((i, j)) self._index[(i, j)] = m m += 1 self._dimension = m ## Number of moments we store ## Power series ring Zp[[x,y]]. We have to work with monomials up to x^depth * y^depth, so need prec = 2*depth self._PowerSeries_x = PowerSeriesRing(self._Rmod, default_prec=self._depth, names='x') self._PowerSeries_x_ZZ = PowerSeriesRing(ZZ, default_prec=self._depth, names='x') self._PowerSeries = PowerSeriesRing(self._PowerSeries_x, default_prec=self._depth, names='y') self._PowerSeries_ZZ = PowerSeriesRing(self._PowerSeries_x_ZZ, default_prec=self._depth, names='y') def index(self, ij): r""" Function to return index of a tuple (i,j). Input: - ij (tuple) : pair (i,j) Returns: Place in ordered basis corresponding to x^iy^j. """ return self._index[tuple(ij)] def ij_from_pos(self, n): r""" From position in the ordered basis, returns corr. tuple (n,i) Input: - n (int) : position in basis. Returns: pair (i,j) s.t. the nth basis vector is x^iy^j """ return self._ij[n] def monomial_from_index(self, n, R=None): """ Takes index and returns the corresponding monomial in the basis """ X, Y = self._repr_R.gens() if isinstance(n, tuple): (i, j) = n else: i, j = self._ij[n] return X**i * Y**j def basis_vector(self, ij): """ Returns the (i,j)th basis vector (in the dual basis), which takes the monomial x^iy^j to 1 and every other monomial to 0. EXAMPLES:: sage: from darmonpoints.ocbianchi import BianchiDistributions sage: D = BianchiDistributions(11,4) sage: D.basis_vector((2,3)) X^2*Y^3 sage: D.basis_vector(5) X*Y """ moments = vector(ZZ, [0 for i in range(self._dimension)]) if isinstance(ij, tuple): index = self.index(ij) moments[index] = 1 else: moments[ij] = 1 return self(moments) def analytic_functions(self): r""" Returns underlying power series of rigid analytic functions, that is, the space on which a distribution should take values. """ return self._PowerSeries def analytic_vars(self): r""" Returns x,y, the variables of the underlying ring of analytic functions. """ x = self.analytic_functions()(self._PowerSeries_x.gen()) y = self.analytic_functions().gen() return x, y def Sigma0Squared(self): r""" Returns underlying monoid Sigma_0(p)^2. """ return self._Sigma0Squared def Sigma0(self): r""" Returns underlying monoid Sigma_0(p)^2. """ return self._Sigma0Squared def approx_module(self, M=None): if M is None: M = self.dimension() return MatrixSpace(self._R, M, 1) def clear_cache(self): del self._cache_powers self._cache_powers = dict() def is_overconvergent(self): return True def _an_element_(self): r""" """ return BianchiDistributionElement(self, Matrix(self._R, self._dimension, 1, range(1, self._dimension + 1)), check=False) def _coerce_map_from_(self, S): # Nothing coherces here, except BianchiDistributionElement return False def _element_constructor_(self, x, check=True, normalize=False): #Code how to coherce x into the space #Admissible values of x? return BianchiDistributionElement(self, x) def acting_matrix(self, g, M): G = g.parent() qrep = G.quaternion_to_matrix(g) qrep_bar = qrep.apply_map(lambda x: x.trace() - x) first, second = qrep.apply_map(G._F_to_local), qrep_bar.apply_map( G._F_to_local) return self._get_powers(self.Sigma0Squared()(first, second)) def _get_powers(self, g, emb=None): r""" Auxiliary function to compute the Sigma_0(p)^2 action on moments. The action sends a monomial x^i to (gx)^i, where gx = (b+dx)/(a+cx). The action on two-variable functions is simply the product of two copies of the one variable action. Input: - g : Sigma0SquaredElement object (in the relevant Sigma_0(p)^2 group) Returns: matrix of (g_x,g_y) acting on distributions in the basis given by monomials EXAMPLES:: sage: from darmonpoints.ocbianchi import BianchiDistributions sage: D = BianchiDistributions(11,2) sage: h = D.Sigma0Squared()([1,1,0,1],[1,1,0,1]) sage: D._get_powers(h) [1 0 0 0] [1 1 0 0] [1 0 1 0] [1 1 1 1] sage: h = D.Sigma0Squared()([2,3,11,1],[12,1,22,1]) sage: D._get_powers(h) [1 0 0 0] [7 6 0 0] [1 0 1 0] [7 6 7 6] """ ## We want to ultimately compute actions on distributions. The matrix describing the (left) ## action of g on distributions is the transpose of the action of adjoint(g) acting on the (left) ## of analytic functions, so we start by taking adjoints. Then put the matrix entries into lists ## NOTE: First apply the adjuster defined above; permutes a,b,c,d to allow for different conventions. abcdx = g.first_element() abcdy = g.second_element() ## Adjust for action: change of convention is encoded in our_adjuster class above adjuster = self._adjuster abcdx = adjuster(abcdx.matrix()) abcdy = adjuster(abcdy.matrix()) ## We want better keys; keys in Zp are not great. Store them instead in ZZ abcdxZZ = tuple(ZZ(t) for t in abcdx) abcdyZZ = tuple(ZZ(t) for t in abcdy) ## check to see if the action of (g,h) has already been computed and cached try: return self._cache_powers[(abcdxZZ, abcdyZZ)] except KeyError: pass ## Sanity check output verbose('The element [{},{}] has not been stored. Computing:'.format( abcdxZZ, abcdyZZ), level=2) R = self._PowerSeries ## Zp[[x,y] y = R.gen() x = R.base_ring().gen() ## get values of a,b,c,d for x and y if emb is None: a, b, c, d = abcdx A, B, C, D = abcdy else: gg = emb(abcdy) a, b, c, d = gg[0].list() A, B, C, D = gg[1].list() ## Initialise terms num_x = b + d * x ## b + az + O(11^depth)R denom_x = a + c * x ## d + cz + O(11^depth)R num_y = B + D * x denom_y = A + C * x ## Ratios r = R.base_ring()(num_x / denom_x) ## after action on x s = num_y / denom_y ## after action on y r = r.change_ring(ZZ) s = s.change_ring(ZZ) RZ = self._PowerSeries_ZZ phi = s.parent().hom([RZ.gen()]) ## Constant term const = r.parent()(1) spows = [const] for n in range(self._depth): spows.append(s * spows[-1]) acted_monomials = {} for j in range(self._depth): acted_monomials[(0, j)] = phi(spows[j]) rpow = 1 for i in range(1, self._depth): rpow *= r rpow.add_bigoh(self._depth) for j in range(self._depth): acted_monomials[(i, j)] = rpow * phi(spows[j]) matrix_rows = [] for n in range(self._dimension): f = acted_monomials[tuple(self.ij_from_pos(n))] new_row = [] for polx in f.padded_list(self._depth): new_row.extend(polx.padded_list(self._depth)) matrix_rows.append(new_row) ## Build matrix . DO NOT TAKE TRANSPOSE, (built this in as a consequence of implementation) matrix_action = Matrix(ZZ, matrix_rows) #acted_monomials_list = Matrix(R.base_ring(),self._depth,self._depth,acted_monomials_list)#.apply_map(ZZ) self._cache_powers[(abcdxZZ, abcdyZZ)] = matrix_action return matrix_action def _repr_(self): r""" This returns the representation of self as a string. """ return "Space of %s-adic Bianchi distributions with k=0 action and precision cap %s" % ( self._p, self._dimension - 1) def prime(self): r""" Returns underlying prime. """ return self._p def basis(self): r""" A basis of the module. Returns all monomials in x,y of degree (in each variable) up to the specified depth-1. """ try: return self._basis except: pass self._basis = [ BianchiDistributionElement(self, Matrix(self._R, self._dimension, 1, {(jj, 0): 1}, sparse=False), check=False) for jj in range(self._dimension) ] return self._basis def base_ring(self): r""" This function returns the base ring of the overconvergent element. """ return self._Rmod def depth(self): r""" Returns the depth of the module. If the depth is d, then a basis for the approximation modules is x^iy^j with i,j in {0,...,d-1}. """ return self._depth def dimension(self): r""" Returns the dimension (rank) of the module. """ return self._dimension def precision_cap(self): r""" Returns the depth of the module. If the depth is d, then a basis for the approximation modules is x^iy^j with i,j in {0,...,d-1}. """ return self._depth def is_exact(self): r""" All distributions are finite approximations. They are only exact as elements of D/Fil^{d,d}D, where d is the depth. """ return False
class padic_Lfunction_two_variable(padic_Lfunction): def __init__(self, Phis, var='T', prec=None): #TODO: prec: Default it to None would make us compute it. self._Phis = Phis #should we create a copy of Phis, in case Phis changes? probably self._coefficient_ring = Phis.base_ring() self._base_ring = PowerSeriesRing(self._coefficient_ring, var) #What precision? self._prec = prec def base_ring(self): return self._base_ring def coefficient_ring(self): return self._coefficient_ring def variables(self): #returns (T, w) return (self._base_ring.gens()[0], self._coefficient_ring.gens()[0]) def _max_coeff(self): Phis = self._Phis p = Phis.parent().prime() p_prec, var_prec = Phis.precision_absolute() max_j = Phis.parent().coefficient_module().length_of_moments(p_prec) n = 0 while True: if max_j - (n / (p - 1)).floor() - min( max_j, n) - (max_j / p).floor() <= 0: return n - 1 n += 1 def _on_Da(self, a, twist): r""" An internal method used by ``self._basic_integral``. The parameter ``twist`` is assumed to be either ``None`` or a primitive quaratic Dirichlet character. """ p = self._Phis.parent().prime() if twist is None: return self._Phis(M2Z([1, a, 0, p])) D = twist.level() DD = self._Phis.parent().coefficient_module() S0 = DD.action().actor() ans = DD.zero() for b in range(1, D + 1): if D.gcd(b) == 1: ans += twist(b) * (self._Phis(M2Z([1, D * a + b * p, 0, D * p ])) * S0([1, b / D, 0, 1])) return ans @cached_method def _basic_integral(self, a, j, twist=None): r""" Computes the integral .. MATH:: \int_{a+p\ZZ_p}(z-\omega(a))^jd\mu_\chi. If ``twist`` is ``None``, `\\chi` is the trivial character. Otherwise, ``twist`` can be a primitive quadratic character of conductor prime to `p`. """ #is this the negative of what we want? #if Phis is fixed for this p-adic L-function, we should make this method cached p = self._Phis.parent().prime() if twist is None: pass elif twist in ZZ: twist = kronecker_character(twist) if twist.is_trivial(): twist = None else: D = twist.level() assert (D.gcd(p) == 1) else: if twist.is_trivial(): twist = None else: assert ((twist**2).is_trivial()) twist = twist.primitive_character() D = twist.level() assert (D.gcd(p) == 1) onDa = self._on_Da(a, twist) #self._Phis(Da) aminusat = a - self._Phis.parent().base_ring().base_ring().teichmuller( a) #aminusat = a - self._coefficient_ring.base_ring().teichmuller(a) try: ap = self._ap except AttributeError: self._ap = self._Phis.Tq_eigenvalue( p) #catch exception if not eigensymbol ap = self._ap if not twist is None: ap *= twist(p) if j == 0: return (~ap) * onDa.moment(0) if a == 1: #aminusat is 0, so only the j=r term is non-zero return (~ap) * (p**j) * onDa.moment(j) #print "j =", j, "a = ", a ans = onDa.moment(0) * (aminusat**j) #ans = onDa.moment(0) #print "\tr =", 0, " ans =", ans for r in range(1, j + 1): if r == j: ans += binomial(j, r) * (p**r) * onDa.moment(r) else: ans += binomial(j, r) * (aminusat **(j - r)) * (p**r) * onDa.moment(r) #print "\tr =", r, " ans =", ans #print " " return (~ap) * ans @cached_method def _compute_nth_coeff(self, n, twist=None): r""" Computes the coefficient of T^n. """ #TODO: Check that n is not too big #TODO implement twist Phis = self._Phis p = Phis.parent().prime() if n == 0: return sum( [self._basic_integral(a, 0, twist) for a in range(1, p)]) p_prec, var_prec = Phis.precision_absolute() max_j = Phis.parent().coefficient_module().length_of_moments(p_prec) ans_prec = max_j - (n / (p - 1)).floor() - min( max_j, n) - (max_j / p).floor() if ans_prec == 0: return self._coefficient_ring(0) #prec = self._Phis.parent()#precision_absolute()[0] #Not quite right, probably #print "@@@@n =", n, "prec =", prec cjns = list(logp_binom(n, p, max_j + 1)) #print cjns teich = Phis.parent().base_ring().base_ring().teichmuller #Next line should work but loses precision!!! ans = sum([ cjns[j] * sum([((~teich(a))**j) * self._basic_integral(a, j, twist) for a in range(1, p)]) for j in range(1, min(max_j, len(cjns))) ]) #Instead do this messed up thing w = ans.parent().gen() #ans = 0*w #for j in range(1,min(max_j, len(cjns))): # ans_term = [0*w] * var_prec # for a in range(1,p): # term = (((~teich(a)) ** j) * self._basic_integral(a, j, twist)).list() # for i in range(min(var_prec, len(term))): # ans_term[i] += term[i] # ans += cjns[j] * sum([ans_term[i] * w**i for i in range(var_prec)]) #print ans_prec ans_prec = O(p**ans_prec) #print ans_prec #print ans for i in range(ans.degree() + 1): ans += ans_prec * w**i return ans def coefficient(self, index, twist=None): r""" index should be some sort of pair (i,j) corresponding to T^i w^j. Maybe if index is just one number, i, it can return the coefficient of T^i to biggest possible precision in w. Should also make it so that one can pass some monomial in the variables. """ pass def power_series(self, twist=None, prec=None): r""" returns a power series in base_ring, up to given precision, or max prec if prec is None """ p = self._Phis.parent().prime() if prec is None: prec = self._max_coeff( ) #Phis.precision_absolute()[0] #Not quite right, probably else: pass #do some checks on inputted prec return self._base_ring( [self._compute_nth_coeff(n, twist) for n in range(prec + 1)])