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) from all import SL2Z # can't import at top as that would cause a circular import # 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 coset_reps(self): r""" Return representatives for the right cosets of this congruence subgroup in `{\rm SL}_2(\ZZ)` as a generator object. Use ``list(self.coset_reps())`` to obtain coset reps as a list. EXAMPLES:: sage: list(Gamma0(5).coset_reps()) [ [1 0] [ 0 -1] [1 0] [ 0 -1] [ 0 -1] [ 0 -1] [0 1], [ 1 0], [1 1], [ 1 2], [ 1 3], [ 1 4] ] sage: list(Gamma0(4).coset_reps()) [ [1 0] [ 0 -1] [1 0] [ 0 -1] [ 0 -1] [1 0] [0 1], [ 1 0], [1 1], [ 1 2], [ 1 3], [2 1] ] sage: list(Gamma0(1).coset_reps()) [ [1 0] [0 1] ] """ from all import SL2Z N = self.level() if N == 1: # P1List isn't very happy working modulo 1 yield SL2Z([1, 0, 0, 1]) else: for z in sage.modular.modsym.p1list.P1List(N): yield SL2Z(lift_to_sl2z(z[0], z[1], N))
def test_todd_coxeter(self): r""" Test representatives of Todd-Coxeter algorithm EXAMPLES:: sage: from sage.modular.arithgroup.tests import Test sage: Test().test_todd_coxeter() #random """ from all import SL2Z from arithgroup_perm import S2m, S3m, Lm, Rm G = random_even_arithgroup(self.index) reps, gens, l, s2 = G.todd_coxeter_l_s2() assert reps[0] == SL2Z([1, 0, 0, 1]) assert len(reps) == G.index() for i in xrange(1, len(reps)): assert reps[i] not in G assert reps[i] * S2m * ~reps[s2[i]] in G assert reps[i] * Lm * ~reps[l[i]] in G for j in xrange(i + 1, len(reps)): assert reps[i] * ~reps[j] not in G assert reps[j] * ~reps[i] not in G reps, gens, s2, s3 = G.todd_coxeter_s2_s3() assert reps[0] == SL2Z([1, 0, 0, 1]) assert len(reps) == G.index() for i in xrange(1, len(reps)): assert reps[i] not in G assert reps[i] * S2m * ~reps[s2[i]] in G assert reps[i] * S3m * ~reps[s3[i]] in G for j in xrange(i + 1, len(reps)): assert reps[i] * ~reps[j] not in G assert reps[j] * ~reps[i] not in G
def __call__(self, x, check=True): r""" Create an element of this congruence subgroup from x. If the optional flag check is True (default), check whether x actually gives an element of self. EXAMPLES:: sage: G = Gamma1(5) sage: G([1, 0, -10, 1]) [ 1 0] [-10 1] sage: G(matrix(ZZ, 2, [6, 1, 5, 1])) [6 1] [5 1] sage: G([1, 1, 6, 7]) Traceback (most recent call last): ... TypeError: matrix must have diagonal entries (=1, 7) congruent to 1 modulo 5, and lower left entry (=6) divisible by 5 """ from all import SL2Z x = SL2Z(x, check) if not check: return x a = x.a() c = x.c() d = x.d() N = self.level() if (a%N == 1) and (c%N == 0): return x # don't need to check d == 1 mod N as this is automatic from det else: raise TypeError, "matrix must have diagonal entries (=%s, %s) congruent to 1 modulo %s, and lower left entry (=%s) divisible by %s" %(a, d, N, c, N)
def gamma0_coset_reps(self): r""" Return a set of coset representatives for self \\ Gamma0(N), where N is the level of self. EXAMPLE:: sage: GammaH(108, [1,-1]).gamma0_coset_reps() [ [1 0] [-43 -45] [ 31 33] [-49 -54] [ 25 28] [-19 -22] [0 1], [108 113], [108 115], [108 119], [108 121], [108 125], <BLANKLINE> [-17 -20] [ 47 57] [ 13 16] [ 41 52] [ 7 9] [-37 -49] [108 127], [108 131], [108 133], [108 137], [108 139], [108 143], <BLANKLINE> [-35 -47] [ 29 40] [ -5 -7] [ 23 33] [-11 -16] [ 53 79] [108 145], [108 149], [108 151], [108 155], [108 157], [108 161] ] """ from all import SL2Z N = self.level() return [ SL2Z(lift_to_sl2z(0, d.lift(), N)) for d in _GammaH_coset_helper(N, self._list_of_elements_in_H()) ]
def __call__(self, x, check=True): r""" Create an element of this congruence subgroup from x. If the optional flag check is True (default), check whether x actually gives an element of self. EXAMPLES:: sage: G = Gamma0(12) sage: G([1, 0, 24, 1]) [ 1 0] [24 1] sage: G(matrix(ZZ, 2, [1, 1, -12, -11])) [ 1 1] [-12 -11] sage: G([1, 0, 23, 1]) Traceback (most recent call last): ... TypeError: matrix must have lower left entry (=23) divisible by 12 """ from all import SL2Z x = SL2Z(x, check) if not check: return x c = x.c() N = self.level() if c%N == 0: return x else: raise TypeError, "matrix must have lower left entry (=%s) divisible by %s" %(c, N)
def coset_reps(self): r""" Return a set of coset representatives for self \\ SL2Z. EXAMPLES:: sage: list(Gamma1(3).coset_reps()) [[1 0] [0 1], [-1 -2] [ 3 5], [ 0 -1] [ 1 0], [-2 1] [ 5 -3], [1 0] [1 1], [-3 -2] [ 8 5], [ 0 -1] [ 1 2], [-2 -3] [ 5 7]] sage: len(list(Gamma1(31).coset_reps())) == 31**2 - 1 True """ from all import Gamma0, SL2Z reps1 = Gamma0(self.level()).coset_reps() for r in reps1: reps2 = self.gamma0_coset_reps() for t in reps2: yield SL2Z(t) * r
def __call__(self, x, check=True): r""" Create an element of this congruence subgroup from x. If the optional flag check is True (default), check whether x actually gives an element of self. EXAMPLES:: sage: G = GammaH(10, [3]) sage: G([1, 0, -10, 1]) [ 1 0] [-10 1] sage: G(matrix(ZZ, 2, [7, 1, 20, 3])) [ 7 1] [20 3] sage: GammaH(10, [9])([7, 1, 20, 3]) Traceback (most recent call last): ... TypeError: matrix must have lower right entry (=3) congruent modulo 10 to some element of H """ from all import SL2Z x = SL2Z(x, check) if not check: return x c = x.c() d = x.d() N = self.level() if c%N != 0: raise TypeError, "matrix must have lower left entry (=%s) divisible by %s" %(c, N) elif d%N in self._list_of_elements_in_H(): return x else: raise TypeError, "matrix must have lower right entry (=%s) congruent modulo %s to some element of H" %(d, N)
def is_normal(self): r""" Return True precisely if this subgroup is a normal subgroup of SL2Z. EXAMPLES:: sage: Gamma(3).is_normal() True sage: Gamma1(3).is_normal() False """ from all import SL2Z for x in self.gens(): for y in SL2Z.gens(): if y * SL2Z(x) * (~y) not in self: return False return True
def test_spanning_trees(self): r""" Test coset representatives obtained from spanning trees for even subgroup (Kulkarni's method with generators ``S2``, ``S3`` and Verrill's method with generators ``L``, ``S2``). EXAMPLES:: sage: from sage.modular.arithgroup.tests import Test sage: Test().test_spanning_trees() #random """ from sage.all import prod from all import SL2Z from arithgroup_perm import S2m, S3m, Lm G = random_even_arithgroup(self.index) m = {'l': Lm, 's': S2m} tree, reps, wreps, gens = G._spanning_tree_verrill() assert reps[0] == SL2Z([1, 0, 0, 1]) assert wreps[0] == '' for i in xrange(1, self.index): assert prod(m[letter] for letter in wreps[i]) == reps[i] tree, reps, wreps, gens = G._spanning_tree_verrill(on_right=False) assert reps[0] == SL2Z([1, 0, 0, 1]) assert wreps[0] == '' for i in xrange(1, self.index): assert prod(m[letter] for letter in wreps[i]) == reps[i] m = {'s2': S2m, 's3': S3m} tree, reps, wreps, gens = G._spanning_tree_kulkarni() assert reps[0] == SL2Z([1, 0, 0, 1]) assert wreps[0] == [] for i in xrange(1, self.index): assert prod(m[letter] for letter in wreps[i]) == reps[i] tree, reps, wreps, gens = G._spanning_tree_kulkarni(on_right=False) assert reps[0] == SL2Z([1, 0, 0, 1]) assert wreps[0] == [] for i in xrange(1, self.index): assert prod(m[letter] for letter in wreps[i]) == reps[i]
def is_normal(self): r""" Return True precisely if this subgroup is a normal subgroup of SL2Z. EXAMPLES:: sage: Gamma(3).is_normal() True sage: Gamma1(3).is_normal() False """ from all import SL2Z for x in self.gens(): for y in SL2Z.gens(): if y*SL2Z(x)*(~y) not in self: return False return True
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 nu2(self): r""" Return the number of orbits of elliptic points of order 2 for this arithmetic subgroup. EXAMPLES:: sage: sage.modular.arithgroup.arithgroup_generic.ArithmeticSubgroup().nu2() Traceback (most recent call last): ... NotImplementedError sage: sage.modular.arithgroup.arithgroup_generic.ArithmeticSubgroup.nu2(Gamma0(1105)) == 8 True """ # Subgroups not containing -1 have no elliptic points of order 2. if not self.is_even(): return 0 # Cheap trick: if self is a subgroup of something with no elliptic points, # then self has no elliptic points either. from all import Gamma0, is_CongruenceSubgroup if is_CongruenceSubgroup(self): if self.is_subgroup(Gamma0(self.level())) and Gamma0( self.level()).nu2() == 0: return 0 # Otherwise, the number of elliptic points is the number of g in self \ # SL2Z such that the stabiliser of g * i in self is not trivial. (Note # that the points g*i for g in the coset reps are not distinct, but it # still works, since the failure of these points to be distinct happens # precisely when the preimages are not elliptic.) from all import SL2Z count = 0 for g in self.coset_reps(): if g * SL2Z([0, 1, -1, 0]) * (~g) in self: count += 1 return count
def nu3(self): r""" Return the number of orbits of elliptic points of order 3 for this arithmetic subgroup. EXAMPLES:: sage: sage.modular.arithgroup.arithgroup_generic.ArithmeticSubgroup().nu3() Traceback (most recent call last): ... NotImplementedError sage: sage.modular.arithgroup.arithgroup_generic.ArithmeticSubgroup.nu3(Gamma0(1729)) == 8 True We test that a bug in handling of subgroups not containing -1 is fixed: :: sage: sage.modular.arithgroup.arithgroup_generic.ArithmeticSubgroup.nu3(GammaH(7, [2])) 2 """ # Cheap trick: if self is a subgroup of something with no elliptic points, # then self has no elliptic points either. from all import Gamma0, is_CongruenceSubgroup if is_CongruenceSubgroup(self): if self.is_subgroup(Gamma0(self.level())) and Gamma0( self.level()).nu3() == 0: return 0 from all import SL2Z count = 0 for g in self.coset_reps(): if g * SL2Z([0, 1, -1, -1]) * (~g) in self: count += 1 if self.is_even(): return count else: return count / 2
def todd_coxeter(self, G=None, on_right=True): r""" Compute coset representatives for self \\ G and action of standard generators on them via Todd-Coxeter enumeration. If ``G`` is ``None``, default to ``SL2Z``. The method also computes generators of the subgroup at same time. INPUT: - ``G`` - intermediate subgroup (currently not implemented if diffferent from SL(2,Z)) - ``on_right`` - boolean (default: True) - if True return right coset enumeration, if False return left one. This is *extremely* slow in general. OUTPUT: - a list of coset representatives - a list of generators for the group - ``l`` - list of integers that correspond to the action of the standard parabolic element [[1,1],[0,1]] of `SL(2,\ZZ)` on the cosets of self. - ``s`` - list of integers that correspond to the action of the standard element of order `2` [[0,-1],[1,0]] on the cosets of self. EXAMPLES:: sage: L = SL2Z([1,1,0,1]) sage: S = SL2Z([0,-1,1,0]) sage: G = Gamma(2) sage: reps, gens, l, s = G.todd_coxeter() sage: len(reps) == G.index() True sage: all(reps[i] * L * ~reps[l[i]] in G for i in xrange(6)) True sage: all(reps[i] * S * ~reps[s[i]] in G for i in xrange(6)) True sage: G = Gamma0(7) sage: reps, gens, l, s = G.todd_coxeter() sage: len(reps) == G.index() True sage: all(reps[i] * L * ~reps[l[i]] in G for i in xrange(8)) True sage: all(reps[i] * S * ~reps[s[i]] in G for i in xrange(8)) True sage: G = Gamma1(3) sage: reps, gens, l, s = G.todd_coxeter(on_right=False) sage: len(reps) == G.index() True sage: all(~reps[l[i]] * L * reps[i] in G for i in xrange(8)) True sage: all(~reps[s[i]] * S * reps[i] in G for i in xrange(8)) True sage: G = Gamma0(5) sage: reps, gens, l, s = G.todd_coxeter(on_right=False) sage: len(reps) == G.index() True sage: all(~reps[l[i]] * L * reps[i] in G for i in xrange(6)) True sage: all(~reps[s[i]] * S * reps[i] in G for i in xrange(6)) True """ from all import SL2Z if G is None: G = SL2Z if G != SL2Z: raise NotImplementedError, "Don't know how to compute coset reps for subgroups yet" id = SL2Z([1, 0, 0, 1]) l = SL2Z([1, 1, 0, 1]) s = SL2Z([0, -1, 1, 0]) reps = [id] # coset representatives reps_inv = {id: 0} # coset representatives index l_wait_back = [id] # rep with no incoming s_edge s_wait_back = [id] # rep with no incoming l_edge l_wait = [id] # rep with no outgoing l_edge s_wait = [id] # rep with no outgoing s_edge l_edges = [None] # edges for l s_edges = [None] # edges for s gens = [] while l_wait or s_wait: if l_wait: x = l_wait.pop(0) y = x not_end = True while not_end: if on_right: y = y * l else: y = l * y for i in xrange(len(l_wait_back)): v = l_wait_back[i] if on_right: yy = y * ~v else: yy = ~v * y if yy in self: l_edges[reps_inv[x]] = reps_inv[v] del l_wait_back[i] if yy != id: gens.append(self(yy)) not_end = False break else: reps_inv[y] = len(reps) l_edges[reps_inv[x]] = len(reps) reps.append(y) l_edges.append(None) s_edges.append(None) s_wait_back.append(y) s_wait.append(y) x = y if s_wait: x = s_wait.pop(0) y = x not_end = True while not_end: if on_right: y = y * s else: y = s * y for i in xrange(len(s_wait_back)): v = s_wait_back[i] if on_right: yy = y * ~v else: yy = ~v * y if yy in self: s_edges[reps_inv[x]] = reps_inv[v] del s_wait_back[i] if yy != id: gens.append(self(yy)) not_end = False break else: reps_inv[y] = len(reps) s_edges[reps_inv[x]] = len(reps) reps.append(y) l_edges.append(None) s_edges.append(None) l_wait_back.append(y) l_wait.append(y) x = y return reps, gens, l_edges, s_edges