Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/modular/arithgroup/congroup_gammaH.py
4084 views
1
r"""
2
Congruence Subgroup `\Gamma_H(N)`
3
4
AUTHORS:
5
6
- Jordi Quer
7
- David Loeffler
8
"""
9
10
################################################################################
11
#
12
# Copyright (C) 2009, The Sage Group -- http://www.sagemath.org/
13
#
14
# Distributed under the terms of the GNU General Public License (GPL)
15
#
16
# The full text of the GPL is available at:
17
#
18
# http://www.gnu.org/licenses/
19
#
20
################################################################################
21
22
from sage.rings.arith import euler_phi, lcm, gcd, divisors, get_inverse_mod, get_gcd, factor
23
from sage.modular.modsym.p1list import lift_to_sl2z
24
from congroup_generic import CongruenceSubgroup, is_CongruenceSubgroup
25
from arithgroup_element import ArithmeticSubgroupElement
26
from sage.modular.cusps import Cusp
27
from sage.misc.cachefunc import cached_method
28
29
# Just for now until we make an SL_2 group type.
30
from sage.rings.integer_ring import ZZ
31
from sage.rings.finite_rings.integer_mod_ring import Zmod
32
from sage.matrix.matrix_space import MatrixSpace
33
from sage.groups.matrix_gps.matrix_group import MatrixGroup
34
from sage.matrix.constructor import matrix
35
36
Mat2Z = MatrixSpace(ZZ,2)
37
38
39
_gammaH_cache = {}
40
def GammaH_constructor(level, H):
41
r"""
42
Return the congruence subgroup `\Gamma_H(N)`, which is the subgroup of
43
`SL_2(\ZZ)` consisting of matrices of the form `\begin{pmatrix} a & b \\
44
c & d \end{pmatrix}` with `N | c` and `a, b \in H`, for `H` a specified
45
subgroup of `(\ZZ/N\ZZ)^\times`.
46
47
INPUT:
48
49
- level -- an integer
50
- H -- either 0, 1, or a list
51
* If H is a list, return `\Gamma_H(N)`, where `H`
52
is the subgroup of `(\ZZ/N\ZZ)^*` **generated** by the
53
elements of the list.
54
* If H = 0, returns `\Gamma_0(N)`.
55
* If H = 1, returns `\Gamma_1(N)`.
56
57
EXAMPLES::
58
59
sage: GammaH(11,0) # indirect doctest
60
Congruence Subgroup Gamma0(11)
61
sage: GammaH(11,1)
62
Congruence Subgroup Gamma1(11)
63
sage: GammaH(11,[10])
64
Congruence Subgroup Gamma_H(11) with H generated by [10]
65
sage: GammaH(11,[10,1])
66
Congruence Subgroup Gamma_H(11) with H generated by [10]
67
sage: GammaH(14,[10])
68
Traceback (most recent call last):
69
...
70
ArithmeticError: The generators [10] must be units modulo 14
71
"""
72
from all import Gamma0, Gamma1, SL2Z
73
if level == 1:
74
return SL2Z
75
elif H == 0:
76
return Gamma0(level)
77
elif H == 1:
78
return Gamma1(level)
79
80
H = _normalize_H(H, level)
81
if H == []:
82
return Gamma1(level)
83
84
Hlist = _list_subgroup(level, H)
85
if len(Hlist) == euler_phi(level):
86
return Gamma0(level)
87
88
key = (level, tuple(H))
89
try:
90
return _gammaH_cache[key]
91
except KeyError:
92
_gammaH_cache[key] = GammaH_class(level, H, Hlist)
93
return _gammaH_cache[key]
94
95
def is_GammaH(x):
96
"""
97
Return True if x is a congruence subgroup of type GammaH.
98
99
EXAMPLES::
100
101
sage: from sage.modular.arithgroup.all import is_GammaH
102
sage: is_GammaH(GammaH(13, [2]))
103
True
104
sage: is_GammaH(Gamma0(6))
105
True
106
sage: is_GammaH(Gamma1(6))
107
True
108
sage: is_GammaH(sage.modular.arithgroup.congroup_generic.CongruenceSubgroup(5))
109
False
110
"""
111
return isinstance(x, GammaH_class)
112
113
def _normalize_H(H, level):
114
"""
115
Normalize representatives for a given subgroup H of the units
116
modulo level.
117
118
NOTE: This function does *not* make any attempt to find a minimal
119
set of generators for H. It simply normalizes the inputs for use
120
in hashing.
121
122
EXAMPLES::
123
124
sage: sage.modular.arithgroup.congroup_gammaH._normalize_H([23], 10)
125
[3]
126
sage: sage.modular.arithgroup.congroup_gammaH._normalize_H([1,5], 7)
127
[5]
128
sage: sage.modular.arithgroup.congroup_gammaH._normalize_H([4,18], 14)
129
Traceback (most recent call last):
130
...
131
ArithmeticError: The generators [4, 18] must be units modulo 14
132
sage: sage.modular.arithgroup.congroup_gammaH._normalize_H([3,17], 14)
133
[3]
134
sage: sage.modular.arithgroup.congroup_gammaH._normalize_H([-1,7,9], 10)
135
[7, 9]
136
"""
137
if not isinstance(H, list):
138
raise TypeError, "H must be a list."
139
H = [ZZ(h) for h in H]
140
for h in H:
141
if gcd(h, level) > 1:
142
raise ArithmeticError, 'The generators %s must be units modulo %s'%(H, level)
143
H = list(set([h%level for h in H]))
144
H.sort()
145
if 1 in H:
146
H.remove(1)
147
return H
148
149
class GammaH_class(CongruenceSubgroup):
150
151
r"""
152
The congruence subgroup `\Gamma_H(N)` for some subgroup `H \trianglelefteq
153
(\ZZ / N\ZZ)^\times`, which is the subgroup of `{\rm
154
SL}_2(\ZZ)` consisting of matrices of the form `\begin{pmatrix} a &
155
b \\ c & d \end{pmatrix}` with `N \mid c` and `a, b \in H`.
156
157
TESTS:
158
159
We test calculation of various invariants of the group: ::
160
161
sage: GammaH(33,[2]).projective_index()
162
96
163
sage: GammaH(33,[2]).genus()
164
5
165
sage: GammaH(7,[2]).genus()
166
0
167
sage: GammaH(23, [1..22]).genus()
168
2
169
sage: Gamma0(23).genus()
170
2
171
sage: GammaH(23, [1]).genus()
172
12
173
sage: Gamma1(23).genus()
174
12
175
176
We calculate the dimensions of some modular forms spaces: ::
177
178
sage: GammaH(33,[2]).dimension_cusp_forms(2)
179
5
180
sage: GammaH(33,[2]).dimension_cusp_forms(3)
181
0
182
sage: GammaH(33,[2,5]).dimension_cusp_forms(2)
183
3
184
sage: GammaH(32079, [21676]).dimension_cusp_forms(20)
185
180266112
186
187
We can sometimes show that there are no weight 1 cusp forms: ::
188
189
sage: GammaH(20, [9]).dimension_cusp_forms(1)
190
0
191
192
"""
193
194
def __init__(self, level, H, Hlist=None):
195
r"""
196
The congruence subgroup `\Gamma_H(N)`. The subgroup H
197
must be input as a list.
198
199
EXAMPLES::
200
201
sage: GammaH(117, [4])
202
Congruence Subgroup Gamma_H(117) with H generated by [4]
203
sage: G = GammaH(16, [7])
204
sage: G == loads(dumps(G))
205
True
206
sage: G is loads(dumps(G))
207
True
208
"""
209
CongruenceSubgroup.__init__(self, level)
210
self.__H = H
211
if Hlist is None: Hlist = _list_subgroup(level, H)
212
self.__Hlist = Hlist
213
214
def restrict(self, M):
215
r"""
216
Return the subgroup of `\Gamma_0(M)`, for `M` a divisor of `N`,
217
obtained by taking the image of this group under reduction modulo `N`.
218
219
EXAMPLES::
220
221
sage: G = GammaH(33,[2])
222
sage: G.restrict(11)
223
Congruence Subgroup Gamma0(11)
224
sage: G.restrict(1)
225
Modular Group SL(2,Z)
226
sage: G.restrict(15)
227
Traceback (most recent call last):
228
...
229
ValueError: M (=15) must be a divisor of the level (33) of self
230
"""
231
M = ZZ(M)
232
if self.level() % M:
233
raise ValueError, "M (=%s) must be a divisor of the level (%s) of self" % (M, self.level())
234
return self._new_group_from_level(M)
235
236
def extend(self, M):
237
r"""
238
Return the subgroup of `\Gamma_0(M)`, for `M` a multiple of `N`,
239
obtained by taking the preimage of this group under the reduction map;
240
in other words, the intersection of this group with `\Gamma_0(M)`.
241
242
EXAMPLES::
243
244
sage: G = GammaH(33, [2])
245
sage: G.extend(99)
246
Congruence Subgroup Gamma_H(99) with H generated by [2, 35, 68]
247
sage: G.extend(11)
248
Traceback (most recent call last):
249
...
250
ValueError: M (=11) must be a multiple of the level (33) of self
251
252
"""
253
M = ZZ(M)
254
if M % self.level():
255
raise ValueError, "M (=%s) must be a multiple of the level (%s) of self" % (M, self.level())
256
return self._new_group_from_level(M)
257
258
def __reduce__(self):
259
"""
260
Used for pickling self.
261
262
EXAMPLES::
263
264
sage: GammaH(92,[45,47]).__reduce__()
265
(<function GammaH_constructor at ...>, (92, [45, 47]))
266
"""
267
return GammaH_constructor, (self.level(), self.__H)
268
269
def divisor_subgroups(self):
270
r"""
271
Given this congruence subgroup `\Gamma_H(N)`, return all
272
subgroups `\Gamma_G(M)` for `M` a divisor of `N` and such that
273
`G` is equal to the image of `H` modulo `M`.
274
275
EXAMPLES::
276
277
sage: G = GammaH(33,[2]); G
278
Congruence Subgroup Gamma_H(33) with H generated by [2]
279
sage: G._list_of_elements_in_H()
280
[1, 2, 4, 8, 16, 17, 25, 29, 31, 32]
281
sage: G.divisor_subgroups()
282
[Modular Group SL(2,Z),
283
Congruence Subgroup Gamma0(3),
284
Congruence Subgroup Gamma0(11),
285
Congruence Subgroup Gamma_H(33) with H generated by [2]]
286
"""
287
v = self.__H
288
ans = []
289
for M in self.level().divisors():
290
w = [a % M for a in v if a%M]
291
ans.append(GammaH_constructor(M, w))
292
return ans
293
294
def to_even_subgroup(self):
295
r"""
296
Return the smallest even subgroup of `SL(2, \ZZ)` containing self.
297
298
EXAMPLE::
299
300
sage: GammaH(11, [4]).to_even_subgroup()
301
Congruence Subgroup Gamma0(11)
302
sage: Gamma1(11).to_even_subgroup()
303
Congruence Subgroup Gamma_H(11) with H generated by [10]
304
305
"""
306
if self.is_even(): return self
307
else:
308
return GammaH_constructor(self.level(), self._generators_for_H() + [-1])
309
310
def __cmp__(self, other):
311
"""
312
Compare self to other.
313
314
The ordering on congruence subgroups of the form GammaH(N) for
315
some H is first by level and then by the subgroup H. In
316
particular, this means that we have Gamma1(N) < GammaH(N) <
317
Gamma0(N) for every nontrivial subgroup H.
318
319
EXAMPLES::
320
321
sage: G = GammaH(86, [9])
322
sage: G.__cmp__(G)
323
0
324
sage: G.__cmp__(GammaH(86, [11])) is not 0
325
True
326
sage: Gamma1(11) < Gamma0(11)
327
True
328
sage: Gamma1(11) == GammaH(11, [])
329
True
330
sage: Gamma0(11) == GammaH(11, [2])
331
True
332
"""
333
if is_GammaH(other):
334
t = cmp(self.level(), other.level())
335
if t:
336
return t
337
else:
338
return cmp(self._list_of_elements_in_H(), other._list_of_elements_in_H())
339
else:
340
return CongruenceSubgroup.__cmp__(self, other)
341
342
def _generators_for_H(self):
343
"""
344
Return generators for the subgroup H of the units mod
345
self.level() that defines self.
346
347
EXAMPLES::
348
349
sage: GammaH(17,[4])._generators_for_H()
350
[4]
351
sage: GammaH(12,[-1])._generators_for_H()
352
[11]
353
"""
354
return self.__H
355
356
def _repr_(self):
357
"""
358
Return the string representation of self.
359
360
EXAMPLES::
361
362
sage: GammaH(123, [55])._repr_()
363
'Congruence Subgroup Gamma_H(123) with H generated by [55]'
364
"""
365
return "Congruence Subgroup Gamma_H(%s) with H generated by %s"%(self.level(), self.__H)
366
367
def _latex_(self):
368
r"""
369
Return the \LaTeX representation of self.
370
371
EXAMPLES::
372
373
sage: GammaH(5,[4])._latex_()
374
'\\Gamma_H(5)'
375
"""
376
return "\\Gamma_H(%s)"%self.level()
377
378
def _list_of_elements_in_H(self):
379
"""
380
Returns a sorted list of Python ints that are representatives
381
between 1 and N-1 of the elements of H.
382
383
WARNING: Do not change this returned list.
384
385
EXAMPLES::
386
387
sage: G = GammaH(11,[3]); G
388
Congruence Subgroup Gamma_H(11) with H generated by [3]
389
sage: G._list_of_elements_in_H()
390
[1, 3, 4, 5, 9]
391
"""
392
return self.__Hlist
393
394
def is_even(self):
395
"""
396
Return True precisely if this subgroup contains the matrix -1.
397
398
EXAMPLES::
399
400
sage: GammaH(10, [3]).is_even()
401
True
402
sage: GammaH(14, [1]).is_even()
403
False
404
"""
405
if self.level() == 1:
406
return True
407
v = self._list_of_elements_in_H()
408
return int(self.level() - 1) in v
409
410
@cached_method
411
def generators(self, algorithm="farey"):
412
r"""
413
Return generators for this congruence subgroup. The result is cached.
414
415
INPUT:
416
417
- ``algorithm`` (string): either ``farey`` (default) or
418
``todd-coxeter``.
419
420
If ``algorithm`` is set to ``"farey"``, then the generators will be
421
calculated using Farey symbols, which will always return a *minimal*
422
generating set. See :mod:`~sage.modular.arithgroup.farey_symbol` for
423
more information.
424
425
If ``algorithm`` is set to ``"todd-coxeter"``, a simpler algorithm
426
based on Todd-Coxeter enumeration will be used. This tends to return
427
far larger sets of generators.
428
429
EXAMPLE::
430
431
sage: GammaH(7, [2]).generators()
432
[
433
[1 1] [ 2 -1] [ 4 -3]
434
[0 1], [ 7 -3], [ 7 -5]
435
]
436
sage: GammaH(7, [2]).generators(algorithm="todd-coxeter")
437
[
438
[1 1] [-90 29] [ 15 4] [-10 -3] [ 1 -1] [1 0] [1 1] [-3 -1]
439
[0 1], [301 -97], [-49 -13], [ 7 2], [ 0 1], [7 1], [0 1], [ 7 2],
440
<BLANKLINE>
441
[-13 4] [-5 -1] [-5 -2] [-10 3] [ 1 0] [ 9 -1] [-20 7]
442
[ 42 -13], [21 4], [28 11], [ 63 -19], [-7 1], [28 -3], [-63 22],
443
<BLANKLINE>
444
[1 0] [-3 -1] [ 15 -4] [ 2 -1] [ 22 -7] [-5 1] [ 8 -3]
445
[7 1], [ 7 2], [ 49 -13], [ 7 -3], [ 63 -20], [14 -3], [-21 8],
446
<BLANKLINE>
447
[11 5] [-13 -4]
448
[35 16], [-42 -13]
449
]
450
"""
451
if algorithm=="farey":
452
return self.farey_symbol().generators()
453
elif algorithm=="todd-coxeter":
454
from sage.modular.modsym.ghlist import GHlist
455
from congroup_pyx import generators_helper
456
level = self.level()
457
gen_list = generators_helper(GHlist(self), level, Mat2Z)
458
return [self(g, check=False) for g in gen_list]
459
else:
460
raise ValueError, "Unknown algorithm '%s' (should be either 'farey' or 'todd-coxeter')" % algorithm
461
462
def _coset_reduction_data_first_coord(G):
463
"""
464
Compute data used for determining the canonical coset
465
representative of an element of SL_2(Z) modulo G. This
466
function specifically returns data needed for the first part
467
of the reduction step (the first coordinate).
468
469
INPUT:
470
G -- a congruence subgroup Gamma_0(N), Gamma_1(N), or Gamma_H(N).
471
472
OUTPUT:
473
A list v such that
474
v[u] = (min(u*h: h in H),
475
gcd(u,N) ,
476
an h such that h*u = min(u*h: h in H)).
477
478
EXAMPLES::
479
480
sage: G = Gamma0(12)
481
sage: sage.modular.arithgroup.congroup_gammaH.GammaH_class._coset_reduction_data_first_coord(G)
482
[(0, 12, 0), (1, 1, 1), (2, 2, 1), (3, 3, 1), (4, 4, 1), (1, 1, 5), (6, 6, 1),
483
(1, 1, 7), (4, 4, 5), (3, 3, 7), (2, 2, 5), (1, 1, 11)]
484
"""
485
H = [ int(x) for x in G._list_of_elements_in_H() ]
486
N = int(G.level())
487
488
# Get some useful fast functions for inverse and gcd
489
inverse_mod = get_inverse_mod(N) # optimal inverse function
490
gcd = get_gcd(N) # optimal gcd function
491
492
# We will be filling this list in below.
493
reduct_data = [0] * N
494
495
# We can fill in 0 and all elements of H immediately
496
reduct_data[0] = (0,N,0)
497
for u in H:
498
reduct_data[u] = (1, 1, inverse_mod(u, N))
499
500
# Make a table of the reduction of H (mod N/d), one for each
501
# divisor d.
502
repr_H_mod_N_over_d = {}
503
for d in divisors(N):
504
# We special-case N == d because in this case,
505
# 1 % N_over_d is 0
506
if N == d:
507
repr_H_mod_N_over_d[d] = [1]
508
break
509
N_over_d = N//d
510
# For each element of H, we look at its image mod
511
# N_over_d. If we haven't yet seen it, add it on to
512
# the end of z.
513
w = [0] * N_over_d
514
z = [1]
515
for x in H:
516
val = x%N_over_d
517
if not w[val]:
518
w[val] = 1
519
z.append(x)
520
repr_H_mod_N_over_d[d] = z
521
522
# Compute the rest of the tuples. The values left to process
523
# are those where reduct_data has a 0. Note that several of
524
# these values are processed on each loop below, so re-index
525
# each time.
526
while True:
527
try:
528
u = reduct_data.index(0)
529
except ValueError:
530
break
531
d = gcd(u, N)
532
for x in repr_H_mod_N_over_d[d]:
533
reduct_data[(u*x)%N] = (u, d, inverse_mod(x,N))
534
535
return reduct_data
536
537
def _coset_reduction_data_second_coord(G):
538
"""
539
Compute data used for determining the canonical coset
540
representative of an element of SL_2(Z) modulo G. This
541
function specifically returns data needed for the second part
542
of the reduction step (the second coordinate).
543
544
INPUT:
545
self
546
547
OUTPUT:
548
a dictionary v with keys the divisors of N such that v[d]
549
is the subgroup {h in H : h = 1 (mod N/d)}.
550
551
EXAMPLES::
552
553
sage: G = GammaH(240,[7,239])
554
sage: G._coset_reduction_data_second_coord()
555
{1: [1], 2: [1], 3: [1], 4: [1], 5: [1, 49], 6: [1], 48: [1, 191], 8: [1], 80: [1, 7, 49, 103], 10: [1, 49], 12: [1], 15: [1, 49], 240: [1, 7, 49, 103, 137, 191, 233, 239], 40: [1, 7, 49, 103], 20: [1, 49], 24: [1, 191], 120: [1, 7, 49, 103, 137, 191, 233, 239], 60: [1, 49, 137, 233], 30: [1, 49, 137, 233], 16: [1]}
556
sage: G = GammaH(1200,[-1,7]); G
557
Congruence Subgroup Gamma_H(1200) with H generated by [7, 1199]
558
sage: K = G._coset_reduction_data_second_coord().keys() ; K.sort()
559
sage: K == divisors(1200)
560
True
561
"""
562
H = G._list_of_elements_in_H()
563
N = G.level()
564
v = { 1: [1] , N: H }
565
for d in [x for x in divisors(N) if x > 1 and x < N ]:
566
N_over_d = N // d
567
v[d] = [x for x in H if x % N_over_d == 1]
568
return v
569
570
@cached_method
571
def _coset_reduction_data(self):
572
"""
573
Compute data used for determining the canonical coset
574
representative of an element of SL_2(Z) modulo G.
575
576
EXAMPLES::
577
578
sage: G = GammaH(13, [-1]); G
579
Congruence Subgroup Gamma_H(13) with H generated by [12]
580
sage: G._coset_reduction_data()
581
([(0, 13, 0), (1, 1, 1), (2, 1, 1), (3, 1, 1), (4, 1, 1), (5, 1, 1), (6, 1, 1), (6, 1, 12), (5, 1, 12), (4, 1, 12), (3, 1, 12), (2, 1, 12), (1, 1, 12)], {1: [1], 13: [1, 12]})
582
"""
583
return (self._coset_reduction_data_first_coord(),
584
self._coset_reduction_data_second_coord())
585
586
587
def _reduce_coset(self, uu, vv):
588
r"""
589
Compute a canonical form for a given Manin symbol.
590
591
INPUT:
592
Two integers (uu,vv) that define an element of `(Z/NZ)^2`.
593
uu -- an integer
594
vv -- an integer
595
596
OUTPUT:
597
pair of integers that are equivalent to (uu,vv).
598
599
NOTE: We do *not* require that gcd(uu,vv,N) = 1. If the gcd is
600
not 1, we return (0,0).
601
602
EXAMPLES:
603
604
An example at level 9.::
605
606
sage: G = GammaH(9,[7]); G
607
Congruence Subgroup Gamma_H(9) with H generated by [7]
608
sage: a = []
609
sage: for i in range(G.level()):
610
... for j in range(G.level()):
611
... a.append(G._reduce_coset(i,j))
612
sage: v = list(set(a))
613
sage: v.sort()
614
sage: v
615
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (3, 1), (3, 2), (6, 1), (6, 2)]
616
617
An example at level 100::
618
619
sage: G = GammaH(100,[3,7]); G
620
Congruence Subgroup Gamma_H(100) with H generated by [3, 7]
621
sage: a = []
622
sage: for i in range(G.level()):
623
... for j in range(G.level()):
624
... a.append(G._reduce_coset(i,j))
625
sage: v = list(set(a))
626
sage: v.sort()
627
sage: len(v)
628
361
629
630
This demonstrates the problem underlying trac \#1220::
631
632
sage: G = GammaH(99, [67])
633
sage: G._reduce_coset(11,-3)
634
(11, 96)
635
sage: G._reduce_coset(77, -3)
636
(11, 96)
637
"""
638
N = int(self.level())
639
u = uu % N
640
v = vv % N
641
first, second = self._coset_reduction_data()
642
643
if gcd(first[u][1], first[v][1]) != 1:
644
return (0,0)
645
if not u:
646
return (0, first[v][0])
647
if not v:
648
return (first[u][0], 0)
649
650
new_u = first[u][0]
651
d = first[u][1]
652
new_v = (first[u][2] * v) % N
653
H_ls = second[d]
654
if len(H_ls) > 1:
655
new_v = min([ (new_v * h)%N for h in H_ls ])
656
657
return (new_u, new_v)
658
659
def reduce_cusp(self, c):
660
r"""
661
Compute a minimal representative for the given cusp c. Returns
662
a cusp c' which is equivalent to the given cusp, and is in
663
lowest terms with minimal positive denominator, and minimal
664
positive numerator for that denominator.
665
666
Two cusps `u_1/v_1` and `u_2/v_2` are equivalent modulo `\Gamma_H(N)`
667
if and only if
668
669
.. math::
670
671
v_1 = h v_2 \bmod N\quad \text{and}\quad u_1 = h^{-1} u_2 \bmod {\rm gcd}(v_1,N)
672
673
or
674
675
.. math::
676
677
v_1 = -h v_2 \bmod N\quad \text{and}\quad u_1 = -h^{-1} u_2 \bmod {\rm gcd}(v_1,N)
678
679
for some `h \in H`.
680
681
EXAMPLES::
682
683
sage: GammaH(6,[5]).reduce_cusp(5/3)
684
1/3
685
sage: GammaH(12,[5]).reduce_cusp(Cusp(8,9))
686
1/3
687
sage: GammaH(12,[5]).reduce_cusp(5/12)
688
Infinity
689
sage: GammaH(12,[]).reduce_cusp(Cusp(5,12))
690
5/12
691
sage: GammaH(21,[5]).reduce_cusp(Cusp(-9/14))
692
1/7
693
sage: Gamma1(5).reduce_cusp(oo)
694
Infinity
695
sage: Gamma1(5).reduce_cusp(0)
696
0
697
"""
698
return self._reduce_cusp(c)[0]
699
700
def _reduce_cusp(self, c):
701
r"""
702
Compute a minimal representative for the given cusp c.
703
Returns a pair (c', t), where c' is the minimal representative
704
for the given cusp, and t is either 1 or -1, as explained
705
below. Largely for internal use.
706
707
The minimal representative for a cusp is the element in `P^1(Q)`
708
in lowest terms with minimal positive denominator, and minimal
709
positive numerator for that denominator.
710
711
Two cusps `u1/v1` and `u2/v2` are equivalent modulo `\Gamma_H(N)`
712
if and only if
713
`v1 = h*v2 (mod N)` and `u1 = h^(-1)*u2 (mod gcd(v1,N))`
714
or
715
`v1 = -h*v2 (mod N)` and `u1 = -h^(-1)*u2 (mod gcd(v1,N))`
716
for some `h \in H`. Then t is 1 or -1 as c and c' fall into
717
the first or second case, respectively.
718
719
EXAMPLES::
720
721
sage: GammaH(6,[5])._reduce_cusp(Cusp(5,3))
722
(1/3, -1)
723
sage: GammaH(12,[5])._reduce_cusp(Cusp(8,9))
724
(1/3, -1)
725
sage: GammaH(12,[5])._reduce_cusp(Cusp(5,12))
726
(Infinity, 1)
727
sage: GammaH(12,[])._reduce_cusp(Cusp(5,12))
728
(5/12, 1)
729
sage: GammaH(21,[5])._reduce_cusp(Cusp(-9/14))
730
(1/7, 1)
731
"""
732
c = Cusp(c)
733
N = int(self.level())
734
Cusps = c.parent()
735
v = int(c.denominator() % N)
736
H = self._list_of_elements_in_H()
737
738
# First, if N | v, take care of this case. If u is in \pm H,
739
# then we return Infinity. If not, let u_0 be the minimum
740
# of \{ h*u | h \in \pm H \}. Then return u_0/N.
741
if not v:
742
u = c.numerator() % N
743
if u in H:
744
return Cusps((1,0)), 1
745
if (N-u) in H:
746
return Cusps((1,0)), -1
747
ls = [ (u*h)%N for h in H ]
748
m1 = min(ls)
749
m2 = N-max(ls)
750
if m1 < m2:
751
return Cusps((m1,N)), 1
752
else:
753
return Cusps((m2,N)), -1
754
755
u = int(c.numerator() % v)
756
gcd = get_gcd(N)
757
d = gcd(v,N)
758
759
# If (N,v) == 1, let v_0 be the minimal element
760
# in \{ v * h | h \in \pm H \}. Then we either return
761
# Infinity or 1/v_0, as v is or is not in \pm H,
762
# respectively.
763
if d == 1:
764
if v in H:
765
return Cusps((0,1)), 1
766
if (N-v) in H:
767
return Cusps((0,1)), -1
768
ls = [ (v*h)%N for h in H ]
769
m1 = min(ls)
770
m2 = N-max(ls)
771
if m1 < m2:
772
return Cusps((1,m1)), 1
773
else:
774
return Cusps((1,m2)), -1
775
776
val_min = v
777
inv_mod = get_inverse_mod(N)
778
779
# Now we're in the case (N,v) > 1. So we have to do several
780
# steps: first, compute v_0 as above. While computing this
781
# minimum, keep track of *all* pairs of (h,s) which give this
782
# value of v_0.
783
hs_ls = [(1,1)]
784
for h in H:
785
tmp = (v*h)%N
786
787
if tmp < val_min:
788
val_min = tmp
789
hs_ls = [(inv_mod(h,N), 1)]
790
elif tmp == val_min:
791
hs_ls.append((inv_mod(h,N), 1))
792
793
if (N-tmp) < val_min:
794
val_min = N - tmp
795
hs_ls = [(inv_mod(h,N), -1)]
796
elif (N-tmp) == val_min:
797
hs_ls.append((inv_mod(h,N), -1))
798
799
# Finally, we find our minimal numerator. Let u_1 be the
800
# minimum of s*h^-1*u mod d as (h,s) ranges over the elements
801
# of hs_ls. We must find the smallest integer u_0 which is
802
# smaller than v_0, congruent to u_1 mod d, and coprime to
803
# v_0. Then u_0/v_0 is our minimal representative.
804
u_min = val_min
805
sign = None
806
for h_inv,s in hs_ls:
807
tmp = (h_inv * s * u)%d
808
while gcd(tmp, val_min) > 1 and tmp < u_min:
809
tmp += d
810
if tmp < u_min:
811
u_min = tmp
812
sign = s
813
814
return Cusps((u_min, val_min)), sign
815
816
def _find_cusps(self):
817
r"""
818
Return an ordered list of inequivalent cusps for self, i.e. a
819
set of representatives for the orbits of self on
820
`\mathbf{P}^1(\QQ)`. These are returned in a reduced
821
form; see self.reduce_cusp for the definition of reduced.
822
823
ALGORITHM:
824
Lemma 3.2 in Cremona's 1997 book shows that for the action
825
of Gamma1(N) on "signed projective space"
826
`\Q^2 / (\Q_{\geq 0}^+)`, we have `u_1/v_1 \sim u_2 / v_2`
827
if and only if `v_1 = v_2 \bmod N` and `u_1 = u_2 \bmod
828
gcd(v_1, N)`. It follows that every orbit has a
829
representative `u/v` with `v \le N` and `0 \le u \le
830
gcd(v, N)`. We iterate through all pairs `(u,v)`
831
satisfying this.
832
833
Having found a set containing at least one of every
834
equivalence class modulo Gamma1(N), we can be sure of
835
picking up every class modulo GammaH(N) since this
836
contains Gamma1(N); and the reduce_cusp call does the
837
checking to make sure we don't get any duplicates.
838
839
EXAMPLES::
840
841
sage: Gamma1(5)._find_cusps()
842
[0, 2/5, 1/2, Infinity]
843
sage: Gamma1(35)._find_cusps()
844
[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]
845
sage: Gamma1(24)._find_cusps() == Gamma1(24).cusps(algorithm='modsym')
846
True
847
sage: GammaH(24, [13,17])._find_cusps() == GammaH(24,[13,17]).cusps(algorithm='modsym')
848
True
849
"""
850
851
s = []
852
hashes = []
853
N = self.level()
854
855
for d in xrange(1, 1+N):
856
w = N.gcd(d)
857
M = int(w) if w > 1 else 2
858
for a in xrange(1,M):
859
if gcd(a, w) != 1:
860
continue
861
while gcd(a, d) != 1:
862
a += w
863
c = self.reduce_cusp(Cusp(a,d))
864
h = hash(c)
865
if not h in hashes:
866
hashes.append(h)
867
s.append(c)
868
return sorted(s)
869
870
def __call__(self, x, check=True):
871
r"""
872
Create an element of this congruence subgroup from x.
873
874
If the optional flag check is True (default), check whether
875
x actually gives an element of self.
876
877
EXAMPLES::
878
879
sage: G = GammaH(10, [3])
880
sage: G([1, 0, -10, 1])
881
[ 1 0]
882
[-10 1]
883
sage: G(matrix(ZZ, 2, [7, 1, 20, 3]))
884
[ 7 1]
885
[20 3]
886
sage: GammaH(10, [9])([7, 1, 20, 3])
887
Traceback (most recent call last):
888
...
889
TypeError: matrix must have lower right entry (=3) congruent modulo 10 to some element of H
890
"""
891
from all import SL2Z
892
x = SL2Z(x, check)
893
if not check:
894
return x
895
896
c = x.c()
897
d = x.d()
898
N = self.level()
899
if c%N != 0:
900
raise TypeError, "matrix must have lower left entry (=%s) divisible by %s" %(c, N)
901
elif d%N in self._list_of_elements_in_H():
902
return x
903
else:
904
raise TypeError, "matrix must have lower right entry (=%s) congruent modulo %s to some element of H" %(d, N)
905
906
def gamma0_coset_reps(self):
907
r"""
908
Return a set of coset representatives for self \\ Gamma0(N), where N is
909
the level of self.
910
911
EXAMPLE::
912
913
sage: GammaH(108, [1,-1]).gamma0_coset_reps()
914
[
915
[1 0] [-43 -45] [ 31 33] [-49 -54] [ 25 28] [-19 -22]
916
[0 1], [108 113], [108 115], [108 119], [108 121], [108 125],
917
<BLANKLINE>
918
[-17 -20] [ 47 57] [ 13 16] [ 41 52] [ 7 9] [-37 -49]
919
[108 127], [108 131], [108 133], [108 137], [108 139], [108 143],
920
<BLANKLINE>
921
[-35 -47] [ 29 40] [ -5 -7] [ 23 33] [-11 -16] [ 53 79]
922
[108 145], [108 149], [108 151], [108 155], [108 157], [108 161]
923
]
924
"""
925
from all import Gamma0
926
N = self.level()
927
G = Gamma0(N)
928
s = []
929
return [G(lift_to_sl2z(0, d.lift(), N)) for d in _GammaH_coset_helper(N, self._list_of_elements_in_H())]
930
931
def coset_reps(self):
932
r"""
933
Return a set of coset representatives for self \\ SL2Z.
934
935
EXAMPLES::
936
937
sage: list(Gamma1(3).coset_reps())
938
[
939
[1 0] [-1 -2] [ 0 -1] [-2 1] [1 0] [-3 -2] [ 0 -1] [-2 -3]
940
[0 1], [ 3 5], [ 1 0], [ 5 -3], [1 1], [ 8 5], [ 1 2], [ 5 7]
941
]
942
sage: len(list(Gamma1(31).coset_reps())) == 31**2 - 1
943
True
944
"""
945
from all import Gamma0, SL2Z
946
reps1 = Gamma0(self.level()).coset_reps()
947
for r in reps1:
948
reps2 = self.gamma0_coset_reps()
949
for t in reps2:
950
yield SL2Z(t)*r
951
952
953
def is_subgroup(self, other):
954
r"""
955
Return True if self is a subgroup of right, and False
956
otherwise.
957
958
EXAMPLES::
959
960
sage: GammaH(24,[7]).is_subgroup(SL2Z)
961
True
962
sage: GammaH(24,[7]).is_subgroup(Gamma0(8))
963
True
964
sage: GammaH(24, []).is_subgroup(GammaH(24, [7]))
965
True
966
sage: GammaH(24, []).is_subgroup(Gamma1(24))
967
True
968
sage: GammaH(24, [17]).is_subgroup(GammaH(24, [7]))
969
False
970
sage: GammaH(1371, [169]).is_subgroup(GammaH(457, [169]))
971
True
972
"""
973
974
from all import is_Gamma0, is_Gamma1
975
if not isinstance(other, GammaH_class):
976
raise NotImplementedError
977
978
# level of self should divide level of other
979
if self.level() % other.level():
980
return False
981
982
# easy cases
983
if is_Gamma0(other):
984
return True # recall self is a GammaH, so it's contained in Gamma0
985
986
if is_Gamma1(other) and len(self._generators_for_H()) > 0:
987
return False
988
989
else:
990
# difficult case
991
t = other._list_of_elements_in_H()
992
for x in self._generators_for_H():
993
if not (x in t):
994
return False
995
return True
996
997
998
def index(self):
999
r"""
1000
Return the index of self in SL2Z.
1001
1002
EXAMPLE::
1003
1004
sage: [G.index() for G in Gamma0(40).gamma_h_subgroups()]
1005
[72, 144, 144, 144, 144, 288, 288, 288, 288, 144, 288, 288, 576, 576, 144, 288, 288, 576, 576, 144, 288, 288, 576, 576, 288, 576, 1152]
1006
"""
1007
from all import Gamma1
1008
return Gamma1(self.level()).index() / len(self._list_of_elements_in_H())
1009
1010
def nu2(self):
1011
r"""
1012
Return the number of orbits of elliptic points of order 2 for this
1013
group.
1014
1015
EXAMPLE::
1016
1017
sage: [H.nu2() for n in [1..10] for H in Gamma0(n).gamma_h_subgroups()]
1018
[1, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0]
1019
sage: GammaH(33,[2]).nu2()
1020
0
1021
sage: GammaH(5,[2]).nu2()
1022
2
1023
1024
AUTHORS:
1025
1026
- Jordi Quer
1027
1028
"""
1029
N = self.level()
1030
H = self._list_of_elements_in_H()
1031
if N % 4 == 0: return ZZ(0)
1032
for p, r in N.factor():
1033
if p % 4 == 3: return ZZ(0)
1034
return (euler_phi(N) // len(H))*len([x for x in H if (x**2 + 1) % N == 0])
1035
1036
def nu3(self):
1037
r"""
1038
Return the number of orbits of elliptic points of order 3 for this
1039
group.
1040
1041
EXAMPLE::
1042
1043
sage: [H.nu3() for n in [1..10] for H in Gamma0(n).gamma_h_subgroups()]
1044
[1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
1045
sage: GammaH(33,[2]).nu3()
1046
0
1047
sage: GammaH(7,[2]).nu3()
1048
2
1049
1050
AUTHORS:
1051
1052
- Jordi Quer
1053
1054
"""
1055
N = self.level()
1056
H = self._list_of_elements_in_H()
1057
if N % 9 == 0: return ZZ(0)
1058
for p, r in N.factor():
1059
if p % 3 == 2: return ZZ(0)
1060
lenHpm = len(H)
1061
if N - ZZ(1) not in H: lenHpm*=2
1062
return (euler_phi(N)//lenHpm)*len([x for x in H if (x**2+x+1) % N == 0])
1063
1064
def ncusps(self):
1065
r"""
1066
Return the number of orbits of cusps (regular or otherwise) for this subgroup.
1067
1068
EXAMPLE::
1069
1070
sage: GammaH(33,[2]).ncusps()
1071
8
1072
sage: GammaH(32079, [21676]).ncusps()
1073
28800
1074
1075
AUTHORS:
1076
1077
- Jordi Quer
1078
1079
"""
1080
N = self.level()
1081
H = self._list_of_elements_in_H()
1082
c = ZZ(0)
1083
for d in [d for d in N.divisors() if d**2 <= N]:
1084
Nd = lcm(d,N//d)
1085
Hd = set([x % Nd for x in H])
1086
lenHd = len(Hd)
1087
if Nd-1 not in Hd: lenHd *= 2
1088
summand = euler_phi(d)*euler_phi(N//d)//lenHd
1089
if d**2 == N:
1090
c = c + summand
1091
else:
1092
c = c + 2*summand
1093
return c
1094
1095
def nregcusps(self):
1096
r"""
1097
Return the number of orbits of regular cusps for this subgroup. A cusp is regular
1098
if we may find a parabolic element generating the stabiliser of that
1099
cusp whose eigenvalues are both +1 rather than -1. If G contains -1,
1100
all cusps are regular.
1101
1102
EXAMPLES::
1103
1104
sage: GammaH(20, [17]).nregcusps()
1105
4
1106
sage: GammaH(20, [17]).nirregcusps()
1107
2
1108
sage: GammaH(3212, [2045, 2773]).nregcusps()
1109
1440
1110
sage: GammaH(3212, [2045, 2773]).nirregcusps()
1111
720
1112
1113
AUTHOR:
1114
1115
- Jordi Quer
1116
"""
1117
if self.is_even():
1118
return self.ncusps()
1119
1120
N = self.level()
1121
H = self._list_of_elements_in_H()
1122
1123
c = ZZ(0)
1124
for d in [d for d in divisors(N) if d**2 <= N]:
1125
Nd = lcm(d,N//d)
1126
Hd = set([x%Nd for x in H])
1127
if Nd - 1 not in Hd:
1128
summand = euler_phi(d)*euler_phi(N//d)//(2*len(Hd))
1129
if d**2==N:
1130
c = c + summand
1131
else:
1132
c = c + 2*summand
1133
return c
1134
1135
def nirregcusps(self):
1136
r"""
1137
Return the number of irregular cusps for this subgroup.
1138
1139
EXAMPLES::
1140
1141
sage: GammaH(3212, [2045, 2773]).nirregcusps()
1142
720
1143
"""
1144
1145
return self.ncusps() - self.nregcusps()
1146
1147
def dimension_new_cusp_forms(self, k=2, p=0):
1148
r"""
1149
Return the dimension of the space of new (or `p`-new)
1150
weight `k` cusp forms for this congruence subgroup.
1151
1152
INPUT:
1153
1154
- ``k`` - an integer (default: 2), the weight. Not fully implemented for k = 1.
1155
- ``p`` - integer (default: 0); if nonzero, compute the `p`-new subspace.
1156
1157
OUTPUT: Integer
1158
1159
EXAMPLES::
1160
1161
sage: GammaH(33,[2]).dimension_new_cusp_forms()
1162
3
1163
sage: Gamma1(4*25).dimension_new_cusp_forms(2, p=5)
1164
225
1165
sage: Gamma1(33).dimension_new_cusp_forms(2)
1166
19
1167
sage: Gamma1(33).dimension_new_cusp_forms(2,p=11)
1168
21
1169
1170
"""
1171
N = self.level()
1172
if p==0 or N % p != 0:
1173
return sum([H.dimension_cusp_forms(k) * mumu(N // H.level()) \
1174
for H in self.divisor_subgroups()])
1175
else:
1176
return self.dimension_cusp_forms(k) - \
1177
2*self.restrict(N//p).dimension_new_cusp_forms(k)
1178
1179
def image_mod_n(self):
1180
r"""
1181
Return the image of this group in `SL(2, \ZZ / N\ZZ)`.
1182
1183
EXAMPLE::
1184
1185
sage: Gamma0(3).image_mod_n()
1186
Matrix group over Ring of integers modulo 3 with 2 generators:
1187
[[[2, 0], [0, 2]], [[1, 1], [0, 1]]]
1188
1189
TEST::
1190
1191
sage: for n in [2..20]:
1192
... for g in Gamma0(n).gamma_h_subgroups():
1193
... G = g.image_mod_n()
1194
... assert G.order() == Gamma(n).index() / g.index()
1195
"""
1196
N = self.level()
1197
if N == 1:
1198
raise NotImplementedError, "Matrix groups over ring of integers modulo 1 not implemented"
1199
gens = [matrix(Zmod(N), 2, 2, [x, 0, 0, Zmod(N)(1)/x]) for x in self._generators_for_H()]
1200
gens += [matrix(Zmod(N),2,[1,1,0,1])]
1201
return MatrixGroup(gens)
1202
1203
def _list_subgroup(N, gens):
1204
r"""
1205
Given an integer ``N`` and a list of integers ``gens``, return a list of
1206
the elements of the subgroup of `(\ZZ / N\ZZ)^\times` generated by the
1207
elements of ``gens``.
1208
1209
EXAMPLE::
1210
1211
sage: sage.modular.arithgroup.congroup_gammaH._list_subgroup(11, [3])
1212
[1, 3, 4, 5, 9]
1213
"""
1214
H = set([1])
1215
N = int(N)
1216
for g in gens:
1217
if gcd(g, N) != 1:
1218
raise ValueError, "gen (=%s) is not in (Z/%sZ)^*"%(g,N)
1219
gk = int(g) % N
1220
sbgrp = [gk]
1221
while not (gk in H):
1222
gk = (gk * g)%N
1223
sbgrp.append(gk)
1224
H = set([(x*h)%N for x in sbgrp for h in H])
1225
H = list(H)
1226
H.sort()
1227
return H
1228
1229
def _GammaH_coset_helper(N, H):
1230
r"""
1231
Return a list of coset representatives for H in (Z / NZ)^*.
1232
1233
EXAMPLE::
1234
1235
sage: from sage.modular.arithgroup.congroup_gammaH import _GammaH_coset_helper
1236
sage: _GammaH_coset_helper(108, [1, 107])
1237
[1, 5, 7, 11, 13, 17, 19, 23, 25, 29, 31, 35, 37, 41, 43, 47, 49, 53]
1238
"""
1239
t = [Zmod(N)(1)]
1240
W = [Zmod(N)(h) for h in H]
1241
HH = [Zmod(N)(h) for h in H]
1242
k = euler_phi(N)
1243
1244
for i in xrange(1, N):
1245
if gcd(i, N) != 1: continue
1246
if not i in W:
1247
t.append(t[0]*i)
1248
W = W + [i*h for h in HH]
1249
if len(W) == k: break
1250
return t
1251
1252
def mumu(N):
1253
"""
1254
Return 0 if any cube divides `N`. Otherwise return
1255
`(-2)^v` where `v` is the number of primes that
1256
exactly divide `N`.
1257
1258
This is similar to the Moebius function.
1259
1260
INPUT:
1261
1262
1263
- ``N`` - an integer at least 1
1264
1265
1266
OUTPUT: Integer
1267
1268
EXAMPLES::
1269
1270
sage: from sage.modular.arithgroup.congroup_gammaH import mumu
1271
sage: mumu(27)
1272
0
1273
sage: mumu(6*25)
1274
4
1275
sage: mumu(7*9*25)
1276
-2
1277
sage: mumu(9*25)
1278
1
1279
"""
1280
if N < 1:
1281
raise ValueError, "N must be at least 1"
1282
p = 1
1283
for _,r in factor(N):
1284
if r > 2:
1285
return ZZ(0)
1286
elif r == 1:
1287
p *= -2
1288
return ZZ(p)
1289
1290