def cusp_data(self, c): r""" Return a triple (g, w, t) where g is an element of self generating the stabiliser of the given cusp, w is the width of the cusp, and t is 1 if the cusp is regular and -1 if not. EXAMPLES:: sage: Gamma1(4).cusp_data(Cusps(1/2)) ( [ 1 -1] [ 4 -3], 1, -1 ) """ c = Cusp(c) # first find an element of SL2Z sending infinity to the given cusp w = lift_to_sl2z(c.denominator(), c.numerator(), 0) g = SL2Z([w[3], w[1], w[2],w[0]]) for d in xrange(1,1+self.index()): if g * SL2Z([1,d,0,1]) * (~g) in self: return (g * SL2Z([1,d,0,1]) * (~g), d, 1) elif g * SL2Z([-1,-d,0,-1]) * (~g) in self: return (g * SL2Z([-1,-d,0,-1]) * (~g), d, -1) raise ArithmeticError("Can't get here!")
def reduce_cusp(self, c): r""" Calculate the unique reduced representative of the equivalence of the cusp `c` modulo this group. The reduced representative of an equivalence class is the unique cusp in the class of the form `u/v` with `u, v \ge 0` coprime, `v` minimal, and `u` minimal for that `v`. EXAMPLES:: sage: Gamma(5).reduce_cusp(1/5) Infinity sage: Gamma(5).reduce_cusp(7/8) 3/2 sage: Gamma(6).reduce_cusp(4/3) 2/3 TESTS:: sage: G = Gamma(50); all([c == G.reduce_cusp(c) for c in G.cusps()]) True """ N = self.level() c = Cusp(c) u,v = c.numerator() % N, c.denominator() % N if (v > N//2) or (2*v == N and u > N//2): u,v = -u,-v u,v = _lift_pair(u,v,N) return Cusp(u,v)
def _find_cusps(self): r""" Calculate a list of inequivalent cusps. EXAMPLES:: sage: sage.modular.arithgroup.congroup_generic.CongruenceSubgroup(5)._find_cusps() Traceback (most recent call last): ... NotImplementedError NOTE: There is a generic algorithm implemented at the top level that uses the coset representatives of self. This is *very slow* and for all the standard congruence subgroups there is a quicker way of doing it, so this should usually be overridden in subclasses; but it doesn't have to be. """ i = Cusp([1,0]) L = [i] for a in self.coset_reps(): ai = i.apply([a.a(), a.b(), a.c(), a.d()]) new = 1 for v in L: if self.are_equivalent(ai, v): new = 0 break if new == 1: L.append(ai) return L
def _find_cusps(self): r""" Calculate a list of inequivalent cusps. EXAMPLES:: sage: sage.modular.arithgroup.congroup_generic.CongruenceSubgroup(5)._find_cusps() Traceback (most recent call last): ... NotImplementedError NOTE: There is a generic algorithm implemented at the top level that uses the coset representatives of self. This is *very slow* and for all the standard congruence subgroups there is a quicker way of doing it, so this should usually be overridden in subclasses; but it doesn't have to be. """ i = Cusp([1, 0]) L = [i] for a in self.coset_reps(): ai = i.apply([a.a(), a.b(), a.c(), a.d()]) new = 1 for v in L: if self.are_equivalent(ai, v): new = 0 break if new == 1: L.append(ai) return L
def cusp_data(self, c): r""" Return a triple (g, w, t) where g is an element of self generating the stabiliser of the given cusp, w is the width of the cusp, and t is 1 if the cusp is regular and -1 if not. EXAMPLES:: sage: Gamma1(4).cusp_data(Cusps(1/2)) ( [ 1 -1] [ 4 -3], 1, -1 ) """ c = Cusp(c) # first find an element of SL2Z sending infinity to the given cusp w = lift_to_sl2z(c.denominator(), c.numerator(), 0) g = SL2Z([w[3], w[1], w[2], w[0]]) for d in xrange(1, 1 + self.index()): if g * SL2Z([1, d, 0, 1]) * (~g) in self: return (g * SL2Z([1, d, 0, 1]) * (~g), d, 1) elif g * SL2Z([-1, -d, 0, -1]) * (~g) in self: return (g * SL2Z([-1, -d, 0, -1]) * (~g), d, -1) raise ArithmeticError("Can't get here!")
def reduce_cusp(self, c): r""" Calculate the unique reduced representative of the equivalence of the cusp `c` modulo this group. The reduced representative of an equivalence class is the unique cusp in the class of the form `u/v` with `u, v \ge 0` coprime, `v` minimal, and `u` minimal for that `v`. EXAMPLES:: sage: Gamma(5).reduce_cusp(1/5) Infinity sage: Gamma(5).reduce_cusp(7/8) 3/2 sage: Gamma(6).reduce_cusp(4/3) 2/3 TESTS:: sage: G = Gamma(50); all([c == G.reduce_cusp(c) for c in G.cusps()]) True """ N = self.level() c = Cusp(c) u, v = c.numerator() % N, c.denominator() % N if (v > N // 2) or (2 * v == N and u > N // 2): u, v = -u, -v u, v = _lift_pair(u, v, N) return Cusp(u, v)
def _find_cusps(self): r""" Calculate the reduced representatives of the equivalence classes of cusps for this group. Adapted from code by Ron Evans. EXAMPLE:: sage: Gamma(8).cusps() # indirect doctest [0, 1/4, 1/3, 3/8, 1/2, 2/3, 3/4, 1, 4/3, 3/2, 5/3, 2, 7/3, 5/2, 8/3, 3, 7/2, 11/3, 4, 14/3, 5, 6, 7, Infinity] """ n = self.level() C = [QQ(x) for x in xrange(n)] n0 = n // 2 n1 = (n + 1) // 2 for r in xrange(1, n1): if r > 1 and gcd(r, n) == 1: C.append(ZZ(r) / ZZ(n)) if n0 == n / 2 and gcd(r, n0) == 1: C.append(ZZ(r) / ZZ(n0)) for s in xrange(2, n1): for r in xrange(1, 1 + n): if GCD_list([s, r, n]) == 1: # GCD_list is ~40x faster than gcd, since gcd wastes loads # of time initialising a Sequence type. u, v = _lift_pair(r, s, n) C.append(ZZ(u) / ZZ(v)) return [Cusp(x) for x in sorted(C)] + [Cusp(1, 0)]
def is_rational_cusp_gamma0(c, N, data): """ Return True if the rational number c is a rational cusp of level N. This uses remarks in Glenn Steven's Ph.D. thesis. INPUT: - ``c`` - a cusp - ``N`` - a positive integer - ``data`` - the list [n for n in range(2,N) if gcd(n,N) == 1], which is passed in as a parameter purely for efficiency reasons. EXAMPLES:: sage: from sage.modular.abvar.cuspidal_subgroup import is_rational_cusp_gamma0 sage: N = 27 sage: data = [n for n in range(2,N) if gcd(n,N) == 1] sage: is_rational_cusp_gamma0(Cusp(1/3), N, data) False sage: is_rational_cusp_gamma0(Cusp(1), N, data) True sage: is_rational_cusp_gamma0(Cusp(oo), N, data) True sage: is_rational_cusp_gamma0(Cusp(2/9), N, data) False """ num = c.numerator() den = c.denominator() return all(c.is_gamma0_equiv(Cusp(num, d * den), N) for d in data)
def find_correct_matrix(M, N): r""" Given a matrix M1 that maps infinity to a/c this returns a matrix M2 Matrix([[A,B],[C,D]]) that maps infinity to a Gamma0(N)-equivalent cusp A/C and satisfies, C|N, C>0, (A,N)=1 and N|B. It also returns T^n = Matrix([[1,n],[0,1]]) such that M2*T^n*M1**(-1) is in Gamma0(N). """ a, b, c, d = list(map(lambda x: ZZ(x), M.list())) if (c > 0 and c.divides(N) and gcd(a, N) == 1 and b % N == 0): return M, identity_matrix(2) if c == 0: return Matrix([[1, 0], [N, 1]]), identity_matrix(2) cusps = cusps_of_gamma0(N) for cusp in cusps: if Cusp(QQ(a) / QQ(c)).is_gamma0_equiv(cusp, N): break A, C = cusp.numerator(), cusp.denominator() _, D, b = xgcd(A, N * C) M2 = Matrix([[A, -N * b], [C, D]]) n = 0 T = Matrix([[1, 1], [0, 1]]) tmp = identity_matrix(2) while True: if N.divides((M2 * tmp * M**(-1))[1][0]): return M2, tmp elif N.divides((M2 * tmp**(-1) * M**(-1))[1][0]): return M2, tmp**(-1) tmp = tmp * T
def are_equivalent(self, x, y, trans=False): r""" Test whether or not cusps x and y are equivalent modulo self. If self has a reduce_cusp() method, use that; otherwise do a slow explicit test. If trans = False, returns True or False. If trans = True, then return either False or an element of self mapping x onto y. EXAMPLE:: sage: Gamma0(7).are_equivalent(Cusp(1/3), Cusp(0), trans=True) [ 3 -1] [-14 5] sage: Gamma0(7).are_equivalent(Cusp(1/3), Cusp(1/7)) False """ x = Cusp(x) y = Cusp(y) if not trans: try: xr = self.reduce_cusp(x) yr = self.reduce_cusp(y) if xr != yr: return False if xr == yr: return True except NotImplementedError: pass from all import SL2Z vx = lift_to_sl2z(x.numerator(), x.denominator(), 0) dx = SL2Z([vx[2], -vx[0], vx[3], -vx[1]]) vy = lift_to_sl2z(y.numerator(), y.denominator(), 0) dy = SL2Z([vy[2], -vy[0], vy[3], -vy[1]]) for i in xrange(self.index()): # Note that the width of any cusp is bounded above by the index of self. # If self is congruence, then the level of self is a much better bound, but # this method is written to work with non-congruence subgroups as well, if dy * SL2Z([1, i, 0, 1]) * (~dx) in self: if trans: return dy * SL2Z([1, i, 0, 1]) * ~dx else: return True elif (self.is_odd() and dy * SL2Z([-1, -i, 0, -1]) * ~dx in self): if trans: return dy * SL2Z([-1, -i, 0, -1]) * ~dx else: return True return False
def _find_cusps(self): r""" Return an ordered list of inequivalent cusps for self, i.e. a set of representatives for the orbits of self on `\mathbb{P}^1(\QQ)`. These are returned in a reduced form; see self.reduce_cusp for the definition of reduced. ALGORITHM: Uses explicit formulae specific to `\Gamma_0(N)`: a reduced cusp on `\Gamma_0(N)` is always of the form `a/d` where `d | N`, and `a_1/d \sim a_2/d` if and only if `a_1 \cong a_2 \bmod {\rm gcd}(d, N/d)`. EXAMPLES:: sage: Gamma0(90)._find_cusps() [0, 1/45, 1/30, 1/18, 1/15, 1/10, 1/9, 2/15, 1/6, 1/5, 1/3, 11/30, 1/2, 2/3, 5/6, Infinity] sage: Gamma0(1).cusps() [Infinity] sage: Gamma0(180).cusps() == Gamma0(180).cusps(algorithm='modsym') True """ N = self.level() s = [] for d in arith.divisors(N): w = arith.gcd(d, N // d) if w == 1: if d == 1: s.append(Cusp(1, 0)) elif d == N: s.append(Cusp(0, 1)) else: s.append(Cusp(1, d)) else: for a in xrange(1, w): if arith.gcd(a, w) == 1: while arith.gcd(a, d // w) != 1: a += w s.append(Cusp(a, d)) return sorted(s)
def reduce_cusp(self, c): r""" Return the unique reduced cusp equivalent to c under the action of self. Always returns Infinity, since there is only one equivalence class of cusps for $SL_2(Z)$. EXAMPLES:: sage: SL2Z.reduce_cusp(Cusps(-1/4)) Infinity """ return Cusp(1, 0)
def _find_cusps(self): r""" Return an ordered list of inequivalent cusps for self, i.e. a set of representatives for the orbits of self on `\mathbf{P}^1(\QQ)`. These are returned in a reduced form; see self.reduce_cusp for the definition of reduced. ALGORITHM: Lemma 3.2 in Cremona's 1997 book shows that for the action of Gamma1(N) on "signed projective space" `\Q^2 / (\Q_{\geq 0}^+)`, we have `u_1/v_1 \sim u_2 / v_2` if and only if `v_1 = v_2 \bmod N` and `u_1 = u_2 \bmod gcd(v_1, N)`. It follows that every orbit has a representative `u/v` with `v \le N` and `0 \le u \le gcd(v, N)`. We iterate through all pairs `(u,v)` satisfying this. Having found a set containing at least one of every equivalence class modulo Gamma1(N), we can be sure of picking up every class modulo GammaH(N) since this contains Gamma1(N); and the reduce_cusp call does the checking to make sure we don't get any duplicates. EXAMPLES:: sage: Gamma1(5)._find_cusps() [0, 2/5, 1/2, Infinity] sage: Gamma1(35)._find_cusps() [0, 2/35, 1/17, 1/16, 1/15, 1/14, 1/13, 1/12, 3/35, 1/11, 1/10, 1/9, 4/35, 1/8, 2/15, 1/7, 1/6, 6/35, 1/5, 3/14, 8/35, 1/4, 9/35, 4/15, 2/7, 3/10, 11/35, 1/3, 12/35, 5/14, 13/35, 2/5, 3/7, 16/35, 17/35, 1/2, 8/15, 4/7, 3/5, 9/14, 7/10, 5/7, 11/14, 4/5, 6/7, 9/10, 13/14, Infinity] sage: Gamma1(24)._find_cusps() == Gamma1(24).cusps(algorithm='modsym') True sage: GammaH(24, [13,17])._find_cusps() == GammaH(24,[13,17]).cusps(algorithm='modsym') True """ s = [] hashes = [] N = self.level() for d in xrange(1, 1 + N): w = N.gcd(d) M = int(w) if w > 1 else 2 for a in xrange(1, M): if gcd(a, w) != 1: continue while gcd(a, d) != 1: a += w c = self.reduce_cusp(Cusp(a, d)) h = hash(c) if not h in hashes: hashes.append(h) s.append(c) return sorted(s)
def cusps(self, algorithm='default'): r""" Return a sorted list of inequivalent cusps for self, i.e. a set of representatives for the orbits of self on `\mathbb{P}^1(\QQ)`. These should be returned in a reduced form where this makes sense. INPUT: - ``algorithm`` -- which algorithm to use to compute the cusps of self. ``'default'`` finds representatives for a known complete set of cusps. ``'modsym'`` computes the boundary map on the space of weight two modular symbols associated to self, which finds the cusps for self in the process. EXAMPLES:: sage: Gamma0(36).cusps() [0, 1/18, 1/12, 1/9, 1/6, 1/4, 1/3, 5/12, 1/2, 2/3, 5/6, Infinity] sage: Gamma0(36).cusps(algorithm='modsym') == Gamma0(36).cusps() True sage: GammaH(36, [19,29]).cusps() == Gamma0(36).cusps() True sage: Gamma0(1).cusps() [Infinity] """ try: return copy(self._cusp_list[algorithm]) except (AttributeError, KeyError): self._cusp_list = {} from .congroup_sl2z import is_SL2Z if algorithm == 'default': if is_SL2Z(self): s = [Cusp(1, 0)] else: s = self._find_cusps() elif algorithm == 'modsym': s = sorted(self.reduce_cusp(c) for c in self.modular_symbols().cusps()) else: raise ValueError("unknown algorithm: %s" % algorithm) self._cusp_list[algorithm] = s return copy(s)
def are_equivalent(self, x, y, trans = False): r""" Test whether or not cusps x and y are equivalent modulo self. If self has a reduce_cusp() method, use that; otherwise do a slow explicit test. If trans = False, returns True or False. If trans = True, then return either False or an element of self mapping x onto y. EXAMPLE:: sage: Gamma0(7).are_equivalent(Cusp(1/3), Cusp(0), trans=True) [ 3 -1] [-14 5] sage: Gamma0(7).are_equivalent(Cusp(1/3), Cusp(1/7)) False """ x = Cusp(x) y = Cusp(y) if not trans: try: xr = self.reduce_cusp(x) yr = self.reduce_cusp(y) if xr != yr: return False if xr == yr: return True except NotImplementedError: pass from all import SL2Z vx = lift_to_sl2z(x.numerator(),x.denominator(), 0) dx = SL2Z([vx[2], -vx[0], vx[3], -vx[1]]) vy = lift_to_sl2z(y.numerator(),y.denominator(), 0) dy = SL2Z([vy[2], -vy[0], vy[3], -vy[1]]) for i in xrange(self.index()): # Note that the width of any cusp is bounded above by the index of self. # If self is congruence, then the level of self is a much better bound, but # this method is written to work with non-congruence subgroups as well, if dy * SL2Z([1,i,0,1])*(~dx) in self: if trans: return dy * SL2Z([1,i,0,1]) * ~dx else: return True elif (self.is_odd() and dy * SL2Z([-1,-i,0,-1]) * ~dx in self): if trans: return dy * SL2Z([-1,-i,0,-1]) * ~dx else: return True return False
def _compute_lattice(self, rational_only=False, rational_subgroup=False): r""" Return a list of vectors that define elements of the rational homology that generate this finite subgroup. INPUT: - ``rational_only`` - bool (default: False); if ``True``, only use rational cusps. OUTPUT: - ``list`` - list of vectors EXAMPLES:: sage: J = J0(37) sage: C = sage.modular.abvar.cuspidal_subgroup.CuspidalSubgroup(J) sage: C._compute_lattice() Free module of degree 4 and rank 4 over Integer Ring Echelon basis matrix: [ 1 0 0 0] [ 0 1 0 0] [ 0 0 1 0] [ 0 0 0 1/3] sage: J = J0(43) sage: C = sage.modular.abvar.cuspidal_subgroup.CuspidalSubgroup(J) sage: C._compute_lattice() Free module of degree 6 and rank 6 over Integer Ring Echelon basis matrix: [ 1 0 0 0 0 0] [ 0 1/7 0 6/7 0 5/7] [ 0 0 1 0 0 0] [ 0 0 0 1 0 0] [ 0 0 0 0 1 0] [ 0 0 0 0 0 1] sage: J = J0(22) sage: C = sage.modular.abvar.cuspidal_subgroup.CuspidalSubgroup(J) sage: C._compute_lattice() Free module of degree 4 and rank 4 over Integer Ring Echelon basis matrix: [1/5 1/5 4/5 0] [ 0 1 0 0] [ 0 0 1 0] [ 0 0 0 1/5] sage: J = J1(13) sage: C = sage.modular.abvar.cuspidal_subgroup.CuspidalSubgroup(J) sage: C._compute_lattice() Free module of degree 4 and rank 4 over Integer Ring Echelon basis matrix: [ 1/19 0 0 9/19] [ 0 1/19 1/19 18/19] [ 0 0 1 0] [ 0 0 0 1] We compute with and without the optional ``rational_only`` option. :: sage: J = J0(27); G = sage.modular.abvar.cuspidal_subgroup.CuspidalSubgroup(J) sage: G._compute_lattice() Free module of degree 2 and rank 2 over Integer Ring Echelon basis matrix: [1/3 0] [ 0 1/3] sage: G._compute_lattice(rational_only=True) Free module of degree 2 and rank 2 over Integer Ring Echelon basis matrix: [1/3 0] [ 0 1] """ A = self.abelian_variety() Cusp = A.modular_symbols() Amb = Cusp.ambient_module() Eis = Amb.eisenstein_submodule() C = Amb.cusps() N = Amb.level() if rational_subgroup: # QQ-rational subgroup of cuspidal subgroup assert A.is_ambient() Q = Cusp.abvarquo_rational_cuspidal_subgroup() return Q.V() if rational_only: # subgroup generated by differences of rational cusps if not is_Gamma0(A.group()): raise NotImplementedError, 'computation of rational cusps only implemented in Gamma0 case.' if not N.is_squarefree(): data = [n for n in range(2,N) if gcd(n,N) == 1] C = [c for c in C if is_rational_cusp_gamma0(c, N, data)] v = [Amb([infinity, alpha]).element() for alpha in C] cusp_matrix = matrix(QQ, len(v), Amb.dimension(), v) # TODO -- refactor something out here # Now we project onto the cuspidal part. B = Cusp.free_module().basis_matrix().stack(Eis.free_module().basis_matrix()) X = B.solve_left(cusp_matrix) X = X.matrix_from_columns(range(Cusp.dimension())) lattice = X.row_module(ZZ) + A.lattice() return lattice
def _reduce_cusp(self, c): r""" Compute a minimal representative for the given cusp c. Returns a pair (c', t), where c' is the minimal representative for the given cusp, and t is either 1 or -1, as explained below. Largely for internal use. The minimal representative for a cusp is the element in `P^1(Q)` in lowest terms with minimal positive denominator, and minimal positive numerator for that denominator. Two cusps `u1/v1` and `u2/v2` are equivalent modulo `\Gamma_H(N)` if and only if - `v1 = h*v2 (mod N)` and `u1 = h^(-1)*u2 (mod gcd(v1,N))` or - `v1 = -h*v2 (mod N)` and `u1 = -h^(-1)*u2 (mod gcd(v1,N))` for some `h \in H`. Then t is 1 or -1 as c and c' fall into the first or second case, respectively. EXAMPLES:: sage: GammaH(6,[5])._reduce_cusp(Cusp(5,3)) (1/3, -1) sage: GammaH(12,[5])._reduce_cusp(Cusp(8,9)) (1/3, -1) sage: GammaH(12,[5])._reduce_cusp(Cusp(5,12)) (Infinity, 1) sage: GammaH(12,[])._reduce_cusp(Cusp(5,12)) (5/12, 1) sage: GammaH(21,[5])._reduce_cusp(Cusp(-9/14)) (1/7, 1) """ c = Cusp(c) N = int(self.level()) Cusps = c.parent() v = int(c.denominator() % N) H = self._list_of_elements_in_H() # First, if N | v, take care of this case. If u is in \pm H, # then we return Infinity. If not, let u_0 be the minimum # of \{ h*u | h \in \pm H \}. Then return u_0/N. if not v: u = c.numerator() % N if u in H: return Cusps((1,0)), 1 if (N-u) in H: return Cusps((1,0)), -1 ls = [ (u*h)%N for h in H ] m1 = min(ls) m2 = N-max(ls) if m1 < m2: return Cusps((m1,N)), 1 else: return Cusps((m2,N)), -1 u = int(c.numerator() % v) gcd = get_gcd(N) d = gcd(v,N) # If (N,v) == 1, let v_0 be the minimal element # in \{ v * h | h \in \pm H \}. Then we either return # Infinity or 1/v_0, as v is or is not in \pm H, # respectively. if d == 1: if v in H: return Cusps((0,1)), 1 if (N-v) in H: return Cusps((0,1)), -1 ls = [ (v*h)%N for h in H ] m1 = min(ls) m2 = N-max(ls) if m1 < m2: return Cusps((1,m1)), 1 else: return Cusps((1,m2)), -1 val_min = v inv_mod = get_inverse_mod(N) # Now we're in the case (N,v) > 1. So we have to do several # steps: first, compute v_0 as above. While computing this # minimum, keep track of *all* pairs of (h,s) which give this # value of v_0. hs_ls = [(1,1)] for h in H: tmp = (v*h)%N if tmp < val_min: val_min = tmp hs_ls = [(inv_mod(h,N), 1)] elif tmp == val_min: hs_ls.append((inv_mod(h,N), 1)) if (N-tmp) < val_min: val_min = N - tmp hs_ls = [(inv_mod(h,N), -1)] elif (N-tmp) == val_min: hs_ls.append((inv_mod(h,N), -1)) # Finally, we find our minimal numerator. Let u_1 be the # minimum of s*h^-1*u mod d as (h,s) ranges over the elements # of hs_ls. We must find the smallest integer u_0 which is # smaller than v_0, congruent to u_1 mod d, and coprime to # v_0. Then u_0/v_0 is our minimal representative. u_min = val_min sign = None for h_inv,s in hs_ls: tmp = (h_inv * s * u)%d while gcd(tmp, val_min) > 1 and tmp < u_min: tmp += d if tmp < u_min: u_min = tmp sign = s return Cusps((u_min, val_min)), sign
def _reduce_cusp(self, c): r""" Compute a minimal representative for the given cusp c. Returns a pair (c', t), where c' is the minimal representative for the given cusp, and t is either 1 or -1, as explained below. Largely for internal use. The minimal representative for a cusp is the element in `P^1(Q)` in lowest terms with minimal positive denominator, and minimal positive numerator for that denominator. Two cusps `u1/v1` and `u2/v2` are equivalent modulo `\Gamma_H(N)` if and only if `v1 = h*v2 (mod N)` and `u1 = h^(-1)*u2 (mod gcd(v1,N))` or `v1 = -h*v2 (mod N)` and `u1 = -h^(-1)*u2 (mod gcd(v1,N))` for some `h \in H`. Then t is 1 or -1 as c and c' fall into the first or second case, respectively. EXAMPLES:: sage: GammaH(6,[5])._reduce_cusp(Cusp(5,3)) (1/3, -1) sage: GammaH(12,[5])._reduce_cusp(Cusp(8,9)) (1/3, -1) sage: GammaH(12,[5])._reduce_cusp(Cusp(5,12)) (Infinity, 1) sage: GammaH(12,[])._reduce_cusp(Cusp(5,12)) (5/12, 1) sage: GammaH(21,[5])._reduce_cusp(Cusp(-9/14)) (1/7, 1) """ c = Cusp(c) N = int(self.level()) Cusps = c.parent() v = int(c.denominator() % N) H = self._list_of_elements_in_H() # First, if N | v, take care of this case. If u is in \pm H, # then we return Infinity. If not, let u_0 be the minimum # of \{ h*u | h \in \pm H \}. Then return u_0/N. if not v: u = c.numerator() % N if u in H: return Cusps((1, 0)), 1 if (N - u) in H: return Cusps((1, 0)), -1 ls = [(u * h) % N for h in H] m1 = min(ls) m2 = N - max(ls) if m1 < m2: return Cusps((m1, N)), 1 else: return Cusps((m2, N)), -1 u = int(c.numerator() % v) gcd = get_gcd(N) d = gcd(v, N) # If (N,v) == 1, let v_0 be the minimal element # in \{ v * h | h \in \pm H \}. Then we either return # Infinity or 1/v_0, as v is or is not in \pm H, # respectively. if d == 1: if v in H: return Cusps((0, 1)), 1 if (N - v) in H: return Cusps((0, 1)), -1 ls = [(v * h) % N for h in H] m1 = min(ls) m2 = N - max(ls) if m1 < m2: return Cusps((1, m1)), 1 else: return Cusps((1, m2)), -1 val_min = v inv_mod = get_inverse_mod(N) # Now we're in the case (N,v) > 1. So we have to do several # steps: first, compute v_0 as above. While computing this # minimum, keep track of *all* pairs of (h,s) which give this # value of v_0. hs_ls = [(1, 1)] for h in H: tmp = (v * h) % N if tmp < val_min: val_min = tmp hs_ls = [(inv_mod(h, N), 1)] elif tmp == val_min: hs_ls.append((inv_mod(h, N), 1)) if (N - tmp) < val_min: val_min = N - tmp hs_ls = [(inv_mod(h, N), -1)] elif (N - tmp) == val_min: hs_ls.append((inv_mod(h, N), -1)) # Finally, we find our minimal numerator. Let u_1 be the # minimum of s*h^-1*u mod d as (h,s) ranges over the elements # of hs_ls. We must find the smallest integer u_0 which is # smaller than v_0, congruent to u_1 mod d, and coprime to # v_0. Then u_0/v_0 is our minimal representative. u_min = val_min sign = None for h_inv, s in hs_ls: tmp = (h_inv * s * u) % d while gcd(tmp, val_min) > 1 and tmp < u_min: tmp += d if tmp < u_min: u_min = tmp sign = s return Cusps((u_min, val_min)), sign
def _compute_lattice(self, rational_only=False, rational_subgroup=False): r""" Return a list of vectors that define elements of the rational homology that generate this finite subgroup. INPUT: - ``rational_only`` - bool (default: False); if ``True``, only use rational cusps. OUTPUT: - ``list`` - list of vectors EXAMPLES:: sage: J = J0(37) sage: C = sage.modular.abvar.cuspidal_subgroup.CuspidalSubgroup(J) sage: C._compute_lattice() Free module of degree 4 and rank 4 over Integer Ring Echelon basis matrix: [ 1 0 0 0] [ 0 1 0 0] [ 0 0 1 0] [ 0 0 0 1/3] sage: J = J0(43) sage: C = sage.modular.abvar.cuspidal_subgroup.CuspidalSubgroup(J) sage: C._compute_lattice() Free module of degree 6 and rank 6 over Integer Ring Echelon basis matrix: [ 1 0 0 0 0 0] [ 0 1/7 0 6/7 0 5/7] [ 0 0 1 0 0 0] [ 0 0 0 1 0 0] [ 0 0 0 0 1 0] [ 0 0 0 0 0 1] sage: J = J0(22) sage: C = sage.modular.abvar.cuspidal_subgroup.CuspidalSubgroup(J) sage: C._compute_lattice() Free module of degree 4 and rank 4 over Integer Ring Echelon basis matrix: [1/5 1/5 4/5 0] [ 0 1 0 0] [ 0 0 1 0] [ 0 0 0 1/5] sage: J = J1(13) sage: C = sage.modular.abvar.cuspidal_subgroup.CuspidalSubgroup(J) sage: C._compute_lattice() Free module of degree 4 and rank 4 over Integer Ring Echelon basis matrix: [1/19 0 9/19 9/19] [ 0 1/19 0 9/19] [ 0 0 1 0] [ 0 0 0 1] We compute with and without the optional ``rational_only`` option. :: sage: J = J0(27); G = sage.modular.abvar.cuspidal_subgroup.CuspidalSubgroup(J) sage: G._compute_lattice() Free module of degree 2 and rank 2 over Integer Ring Echelon basis matrix: [1/3 0] [ 0 1/3] sage: G._compute_lattice(rational_only=True) Free module of degree 2 and rank 2 over Integer Ring Echelon basis matrix: [1/3 0] [ 0 1] """ A = self.abelian_variety() Cusp = A.modular_symbols() Amb = Cusp.ambient_module() Eis = Amb.eisenstein_submodule() C = Amb.cusps() N = Amb.level() if rational_subgroup: # QQ-rational subgroup of cuspidal subgroup assert A.is_ambient() Q = Cusp.abvarquo_rational_cuspidal_subgroup() return Q.V() if rational_only: # subgroup generated by differences of rational cusps if not is_Gamma0(A.group()): raise NotImplementedError( 'computation of rational cusps only implemented in Gamma0 case.' ) if not N.is_squarefree(): data = [n for n in N.coprime_integers(N) if n >= 2] C = [c for c in C if is_rational_cusp_gamma0(c, N, data)] v = [Amb([infinity, alpha]).element() for alpha in C] cusp_matrix = matrix(QQ, len(v), Amb.dimension(), v) # TODO -- refactor something out here # Now we project onto the cuspidal part. B = Cusp.free_module().basis_matrix().stack( Eis.free_module().basis_matrix()) X = B.solve_left(cusp_matrix) X = X.matrix_from_columns(range(Cusp.dimension())) lattice = X.row_module(ZZ) + A.lattice() return lattice