Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/modular/arithgroup/congroup_gamma1.py
8820 views
1
r"""
2
Congruence Subgroup `\Gamma_1(N)`
3
"""
4
5
################################################################################
6
#
7
# Copyright (C) 2009, The Sage Group -- http://www.sagemath.org/
8
#
9
# Distributed under the terms of the GNU General Public License (GPL)
10
#
11
# The full text of the GPL is available at:
12
#
13
# http://www.gnu.org/licenses/
14
#
15
################################################################################
16
17
from sage.misc.cachefunc import cached_method
18
19
from sage.misc.misc import prod
20
from congroup_gammaH import GammaH_class, is_GammaH, GammaH_constructor
21
from sage.rings.all import ZZ, euler_phi as phi, moebius, divisors
22
from sage.modular.dirichlet import DirichletGroup
23
24
# Just for now until we make an SL_2 group type.
25
from sage.rings.finite_rings.integer_mod_ring import IntegerModRing
26
from sage.matrix.matrix_space import MatrixSpace
27
Mat2Z = MatrixSpace(IntegerModRing(0),2)
28
29
def is_Gamma1(x):
30
"""
31
Return True if x is a congruence subgroup of type Gamma1.
32
33
EXAMPLES::
34
35
sage: from sage.modular.arithgroup.all import is_Gamma1
36
sage: is_Gamma1(SL2Z)
37
False
38
sage: is_Gamma1(Gamma1(13))
39
True
40
sage: is_Gamma1(Gamma0(6))
41
False
42
sage: is_Gamma1(GammaH(12, [])) # trick question!
43
True
44
sage: is_Gamma1(GammaH(12, [5]))
45
False
46
"""
47
#from congroup_sl2z import is_SL2Z
48
#return (isinstance(x, Gamma1_class) or is_SL2Z(x))
49
return isinstance(x, Gamma1_class)
50
51
_gamma1_cache = {}
52
def Gamma1_constructor(N):
53
r"""
54
Return the congruence subgroup `\Gamma_1(N)`.
55
56
EXAMPLES::
57
58
sage: Gamma1(5) # indirect doctest
59
Congruence Subgroup Gamma1(5)
60
sage: G = Gamma1(23)
61
sage: G is Gamma1(23)
62
True
63
sage: G is GammaH(23, [1])
64
True
65
sage: TestSuite(G).run()
66
sage: G is loads(dumps(G))
67
True
68
"""
69
if N == 1 or N == 2:
70
from congroup_gamma0 import Gamma0_constructor
71
return Gamma0_constructor(N)
72
try:
73
return _gamma1_cache[N]
74
except KeyError:
75
_gamma1_cache[N] = Gamma1_class(N)
76
return _gamma1_cache[N]
77
78
class Gamma1_class(GammaH_class):
79
r"""
80
The congruence subgroup `\Gamma_1(N)`.
81
82
TESTS::
83
84
sage: [Gamma1(n).genus() for n in prime_range(2,100)]
85
[0, 0, 0, 0, 1, 2, 5, 7, 12, 22, 26, 40, 51, 57, 70, 92, 117, 126, 155, 176, 187, 222, 247, 287, 345]
86
sage: [Gamma1(n).index() for n in [1..10]]
87
[1, 3, 8, 12, 24, 24, 48, 48, 72, 72]
88
89
sage: [Gamma1(n).dimension_cusp_forms() for n in [1..20]]
90
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 1, 1, 2, 5, 2, 7, 3]
91
sage: [Gamma1(n).dimension_cusp_forms(1) for n in [1..20]]
92
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
93
sage: [Gamma1(4).dimension_cusp_forms(k) for k in [1..20]]
94
[0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8]
95
sage: Gamma1(23).dimension_cusp_forms(1)
96
Traceback (most recent call last):
97
...
98
NotImplementedError: Computation of dimensions of weight 1 cusp forms spaces not implemented in general
99
"""
100
101
def __init__(self, level):
102
r"""
103
The congruence subgroup `\Gamma_1(N)`.
104
105
EXAMPLES::
106
107
sage: G = Gamma1(11); G
108
Congruence Subgroup Gamma1(11)
109
sage: loads(G.dumps()) == G
110
True
111
"""
112
GammaH_class.__init__(self, level, [])
113
114
def _repr_(self):
115
"""
116
Return the string representation of self.
117
118
EXAMPLES::
119
120
sage: Gamma1(133)._repr_()
121
'Congruence Subgroup Gamma1(133)'
122
"""
123
return "Congruence Subgroup Gamma1(%s)"%self.level()
124
125
def __reduce__(self):
126
"""
127
Used for pickling self.
128
129
EXAMPLES::
130
131
sage: Gamma1(82).__reduce__()
132
(<function Gamma1_constructor at ...>, (82,))
133
"""
134
return Gamma1_constructor, (self.level(),)
135
136
def _latex_(self):
137
r"""
138
Return the \LaTeX representation of self.
139
140
EXAMPLES::
141
142
sage: Gamma1(3)._latex_()
143
'\\Gamma_1(3)'
144
sage: latex(Gamma1(3))
145
\Gamma_1(3)
146
"""
147
return "\\Gamma_1(%s)"%self.level()
148
149
def is_even(self):
150
"""
151
Return True precisely if this subgroup contains the matrix -1.
152
153
EXAMPLES::
154
155
sage: Gamma1(1).is_even()
156
True
157
sage: Gamma1(2).is_even()
158
True
159
sage: Gamma1(15).is_even()
160
False
161
"""
162
return self.level() in [1,2]
163
164
def is_subgroup(self, right):
165
"""
166
Return True if self is a subgroup of right.
167
168
EXAMPLES::
169
170
sage: Gamma1(3).is_subgroup(SL2Z)
171
True
172
sage: Gamma1(3).is_subgroup(Gamma1(5))
173
False
174
sage: Gamma1(3).is_subgroup(Gamma1(6))
175
False
176
sage: Gamma1(6).is_subgroup(Gamma1(3))
177
True
178
sage: Gamma1(6).is_subgroup(Gamma0(2))
179
True
180
sage: Gamma1(80).is_subgroup(GammaH(40, []))
181
True
182
sage: Gamma1(80).is_subgroup(GammaH(40, [21]))
183
True
184
"""
185
if right.level() == 1:
186
return True
187
if is_GammaH(right):
188
return self.level() % right.level() == 0
189
else:
190
raise NotImplementedError
191
192
@cached_method
193
def generators(self, algorithm="farey"):
194
r"""
195
Return generators for this congruence subgroup. The result is cached.
196
197
INPUT:
198
199
- ``algorithm`` (string): either ``farey`` (default) or
200
``todd-coxeter``.
201
202
If ``algorithm`` is set to ``"farey"``, then the generators will be
203
calculated using Farey symbols, which will always return a *minimal*
204
generating set. See :mod:`~sage.modular.arithgroup.farey_symbol` for
205
more information.
206
207
If ``algorithm`` is set to ``"todd-coxeter"``, a simpler algorithm
208
based on Todd-Coxeter enumeration will be used. This tends to return
209
far larger sets of generators.
210
211
EXAMPLE::
212
213
sage: Gamma1(3).generators()
214
[
215
[1 1] [ 1 -1]
216
[0 1], [ 3 -2]
217
]
218
sage: Gamma1(3).generators(algorithm="todd-coxeter")
219
[
220
[1 1] [-20 9] [ 4 1] [-5 -2] [ 1 -1] [1 0] [1 1] [-5 2]
221
[0 1], [ 51 -23], [-9 -2], [ 3 1], [ 0 1], [3 1], [0 1], [12 -5],
222
<BLANKLINE>
223
[ 1 0] [ 4 -1] [ -5 3] [ 1 -1] [ 7 -3] [ 4 -1] [ -5 3]
224
[-3 1], [ 9 -2], [-12 7], [ 3 -2], [12 -5], [ 9 -2], [-12 7]
225
]
226
"""
227
if algorithm=="farey":
228
return self.farey_symbol().generators()
229
elif algorithm=="todd-coxeter":
230
from sage.modular.modsym.g1list import G1list
231
from congroup_pyx import generators_helper
232
level = self.level()
233
gen_list = generators_helper(G1list(level), level, Mat2Z)
234
return [self(g, check=False) for g in gen_list]
235
else:
236
raise ValueError, "Unknown algorithm '%s' (should be either 'farey' or 'todd-coxeter')" % algorithm
237
238
def _contains_sl2(self, a,b,c,d):
239
r"""
240
Test whether x is an element of this group.
241
242
EXAMPLES::
243
244
sage: G = Gamma1(5)
245
sage: [1, 0, -10, 1] in G
246
True
247
sage: matrix(ZZ, 2, [6, 1, 5, 1]) in G
248
True
249
sage: SL2Z.0 in G
250
False
251
sage: G([1, 1, 6, 7]) # indirect doctest
252
Traceback (most recent call last):
253
...
254
TypeError: matrix [1 1]
255
[6 7] is not an element of Congruence Subgroup Gamma1(5)
256
"""
257
N = self.level()
258
# don't need to check d == 1 mod N as this is automatic from det
259
return ((a%N == 1) and (c%N == 0))
260
261
def nu2(self):
262
r"""
263
Calculate the number of orbits of elliptic points of order 2 for this
264
subgroup `\Gamma_1(N)`. This is known to be 0 if N > 2.
265
266
EXAMPLE::
267
268
sage: Gamma1(2).nu2()
269
1
270
sage: Gamma1(457).nu2()
271
0
272
sage: [Gamma1(n).nu2() for n in [1..16]]
273
[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
274
"""
275
276
N = self.level()
277
if N > 2: return 0
278
elif N == 2 or N == 1: return 1
279
280
def nu3(self):
281
r"""
282
Calculate the number of orbits of elliptic points of order 3 for this
283
subgroup `\Gamma_1(N)`. This is known to be 0 if N > 3.
284
285
EXAMPLE::
286
287
sage: Gamma1(2).nu3()
288
0
289
sage: Gamma1(3).nu3()
290
1
291
sage: Gamma1(457).nu3()
292
0
293
sage: [Gamma1(n).nu3() for n in [1..10]]
294
[1, 0, 1, 0, 0, 0, 0, 0, 0, 0]
295
"""
296
297
N = self.level()
298
if N > 3 or N == 2: return 0
299
else: return 1
300
301
def ncusps(self):
302
r"""
303
Return the number of cusps of this subgroup `\Gamma_1(N)`.
304
305
EXAMPLES::
306
307
sage: [Gamma1(n).ncusps() for n in [1..15]]
308
[1, 2, 2, 3, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 16]
309
sage: [Gamma1(n).ncusps() for n in prime_range(2, 100)]
310
[2, 2, 4, 6, 10, 12, 16, 18, 22, 28, 30, 36, 40, 42, 46, 52, 58, 60, 66, 70, 72, 78, 82, 88, 96]
311
"""
312
n = self.level()
313
if n <= 4:
314
return [None, 1, 2, 2, 3][n]
315
return ZZ(sum([phi(d)*phi(n/d)/ZZ(2) for d in n.divisors()]))
316
317
def index(self):
318
r"""
319
Return the index of self in the full modular group. This is given by the formula
320
321
.. math::
322
323
N^2 \prod_{\substack{p \mid N \\ \text{$p$ prime}}} \left( 1 - \frac{1}{p^2}\right).
324
325
EXAMPLE::
326
327
sage: Gamma1(180).index()
328
20736
329
sage: [Gamma1(n).projective_index() for n in [1..16]]
330
[1, 3, 4, 6, 12, 12, 24, 24, 36, 36, 60, 48, 84, 72, 96, 96]
331
"""
332
return prod([p**(2*e) - p**(2*e-2) for (p,e) in self.level().factor()])
333
334
##################################################################################
335
# Dimension formulas for Gamma1, accepting a Dirichlet character as an argument. #
336
##################################################################################
337
338
def dimension_modular_forms(self, k=2, eps=None, algorithm="CohenOesterle"):
339
r"""
340
Return the dimension of the space of modular forms for self, or the
341
dimension of the subspace corresponding to the given character if one
342
is supplied.
343
344
INPUT:
345
346
- ``k`` - an integer (default: 2), the weight.
347
348
- ``eps`` - either None or a Dirichlet character modulo N, where N is
349
the level of this group. If this is None, then the dimension of the
350
whole space is returned; otherwise, the dimension of the subspace of
351
forms of character eps.
352
353
- ``algorithm`` -- either "CohenOesterle" (the default) or "Quer". This
354
specifies the method to use in the case of nontrivial character:
355
either the Cohen--Oesterle formula as described in Stein's book, or
356
by Moebius inversion using the subgroups GammaH (a method due to
357
Jordi Quer).
358
359
EXAMPLES::
360
361
sage: K = CyclotomicField(3)
362
sage: eps = DirichletGroup(7*43,K).0^2
363
sage: G = Gamma1(7*43)
364
365
sage: G.dimension_modular_forms(2, eps)
366
32
367
sage: G.dimension_modular_forms(2, eps, algorithm="Quer")
368
32
369
"""
370
371
return self.dimension_cusp_forms(k, eps, algorithm) + self.dimension_eis(k, eps, algorithm)
372
373
def dimension_cusp_forms(self, k=2, eps=None, algorithm="CohenOesterle"):
374
r"""
375
Return the dimension of the space of cusp forms for self, or the
376
dimension of the subspace corresponding to the given character if one
377
is supplied.
378
379
INPUT:
380
381
- ``k`` - an integer (default: 2), the weight.
382
383
- ``eps`` - either None or a Dirichlet character modulo N, where N is
384
the level of this group. If this is None, then the dimension of the
385
whole space is returned; otherwise, the dimension of the subspace of
386
forms of character eps.
387
388
- ``algorithm`` -- either "CohenOesterle" (the default) or "Quer". This
389
specifies the method to use in the case of nontrivial character:
390
either the Cohen--Oesterle formula as described in Stein's book, or
391
by Moebius inversion using the subgroups GammaH (a method due to
392
Jordi Quer).
393
394
EXAMPLES:
395
396
We compute the same dimension in two different ways ::
397
398
sage: K = CyclotomicField(3)
399
sage: eps = DirichletGroup(7*43,K).0^2
400
sage: G = Gamma1(7*43)
401
402
Via Cohen--Oesterle: ::
403
404
sage: Gamma1(7*43).dimension_cusp_forms(2, eps)
405
28
406
407
Via Quer's method: ::
408
409
sage: Gamma1(7*43).dimension_cusp_forms(2, eps, algorithm="Quer")
410
28
411
412
Some more examples: ::
413
414
sage: G.<eps> = DirichletGroup(9)
415
sage: [Gamma1(9).dimension_cusp_forms(k, eps) for k in [1..10]]
416
[0, 0, 1, 0, 3, 0, 5, 0, 7, 0]
417
sage: [Gamma1(9).dimension_cusp_forms(k, eps^2) for k in [1..10]]
418
[0, 0, 0, 2, 0, 4, 0, 6, 0, 8]
419
"""
420
421
from all import Gamma0
422
423
# first deal with special cases
424
425
if eps is None:
426
return GammaH_class.dimension_cusp_forms(self, k)
427
428
N = self.level()
429
if eps.base_ring().characteristic() != 0:
430
raise ValueError
431
432
eps = DirichletGroup(N, eps.base_ring())(eps)
433
434
if eps.is_trivial():
435
return Gamma0(N).dimension_cusp_forms(k)
436
437
if (k <= 0) or ((k % 2) == 1 and eps.is_even()) or ((k%2) == 0 and eps.is_odd()):
438
return ZZ(0)
439
440
if k == 1:
441
try:
442
n = self.dimension_cusp_forms(1)
443
if n == 0:
444
return ZZ(0)
445
else: # never happens at present
446
raise NotImplementedError, "Computations of dimensions of spaces of weight 1 cusp forms not implemented at present"
447
except NotImplementedError:
448
raise
449
450
# now the main part
451
452
if algorithm == "Quer":
453
n = eps.order()
454
dim = ZZ(0)
455
for d in n.divisors():
456
G = GammaH_constructor(N,(eps**d).kernel())
457
dim = dim + moebius(d)*G.dimension_cusp_forms(k)
458
return dim//phi(n)
459
460
elif algorithm == "CohenOesterle":
461
K = eps.base_ring()
462
from sage.modular.dims import CohenOesterle
463
from all import Gamma0
464
return ZZ( K(Gamma0(N).index() * (k-1)/ZZ(12)) + CohenOesterle(eps,k) )
465
466
else: #algorithm not in ["CohenOesterle", "Quer"]:
467
raise ValueError, "Unrecognised algorithm in dimension_cusp_forms"
468
469
470
def dimension_eis(self, k=2, eps=None, algorithm="CohenOesterle"):
471
r"""
472
Return the dimension of the space of Eisenstein series forms for self,
473
or the dimension of the subspace corresponding to the given character
474
if one is supplied.
475
476
INPUT:
477
478
- ``k`` - an integer (default: 2), the weight.
479
480
- ``eps`` - either None or a Dirichlet character modulo N, where N is
481
the level of this group. If this is None, then the dimension of the
482
whole space is returned; otherwise, the dimension of the subspace of
483
Eisenstein series of character eps.
484
485
- ``algorithm`` -- either "CohenOesterle" (the default) or "Quer". This
486
specifies the method to use in the case of nontrivial character:
487
either the Cohen--Oesterle formula as described in Stein's book, or
488
by Moebius inversion using the subgroups GammaH (a method due to
489
Jordi Quer).
490
491
AUTHORS:
492
493
- William Stein - Cohen--Oesterle algorithm
494
495
- Jordi Quer - algorithm based on GammaH subgroups
496
497
- David Loeffler (2009) - code refactoring
498
499
EXAMPLES:
500
501
The following two computations use different algorithms: ::
502
503
sage: [Gamma1(36).dimension_eis(1,eps) for eps in DirichletGroup(36)]
504
[0, 4, 3, 0, 0, 2, 6, 0, 0, 2, 3, 0]
505
sage: [Gamma1(36).dimension_eis(1,eps,algorithm="Quer") for eps in DirichletGroup(36)]
506
[0, 4, 3, 0, 0, 2, 6, 0, 0, 2, 3, 0]
507
508
So do these: ::
509
510
sage: [Gamma1(48).dimension_eis(3,eps) for eps in DirichletGroup(48)]
511
[0, 12, 0, 4, 0, 8, 0, 4, 12, 0, 4, 0, 8, 0, 4, 0]
512
sage: [Gamma1(48).dimension_eis(3,eps,algorithm="Quer") for eps in DirichletGroup(48)]
513
[0, 12, 0, 4, 0, 8, 0, 4, 12, 0, 4, 0, 8, 0, 4, 0]
514
"""
515
from all import Gamma0
516
517
# first deal with special cases
518
519
if eps is None:
520
return GammaH_class.dimension_eis(self, k)
521
522
N = self.level()
523
eps = DirichletGroup(N)(eps)
524
525
if eps.is_trivial():
526
return Gamma0(N).dimension_eis(k)
527
528
# Note case of k = 0 and trivial character already dealt with separately, so k <= 0 here is valid:
529
if (k <= 0) or ((k % 2) == 1 and eps.is_even()) or ((k%2) == 0 and eps.is_odd()):
530
return ZZ(0)
531
532
if algorithm == "Quer":
533
n = eps.order()
534
dim = ZZ(0)
535
for d in n.divisors():
536
G = GammaH_constructor(N,(eps**d).kernel())
537
dim = dim + moebius(d)*G.dimension_eis(k)
538
return dim//phi(n)
539
540
elif algorithm == "CohenOesterle":
541
from sage.modular.dims import CohenOesterle
542
K = eps.base_ring()
543
j = 2-k
544
# We use the Cohen-Oesterle formula in a subtle way to
545
# compute dim M_k(N,eps) (see Ch. 6 of William Stein's book on
546
# computing with modular forms).
547
alpha = -ZZ( K(Gamma0(N).index()*(j-1)/ZZ(12)) + CohenOesterle(eps,j) )
548
if k == 1:
549
return alpha
550
else:
551
return alpha - self.dimension_cusp_forms(k, eps)
552
553
else: #algorithm not in ["CohenOesterle", "Quer"]:
554
raise ValueError, "Unrecognised algorithm in dimension_eis"
555
556
def dimension_new_cusp_forms(self, k=2, eps=None, p=0, algorithm="CohenOesterle"):
557
r"""
558
Dimension of the new subspace (or `p`-new subspace) of cusp forms of
559
weight `k` and character `\varepsilon`.
560
561
INPUT:
562
563
- ``k`` - an integer (default: 2)
564
565
- ``eps`` - a Dirichlet character
566
567
- ``p`` - a prime (default: 0); just the `p`-new subspace if given
568
569
- ``algorithm`` - either "CohenOesterle" (the default) or "Quer". This
570
specifies the method to use in the case of nontrivial character:
571
either the Cohen--Oesterle formula as described in Stein's book, or
572
by Moebius inversion using the subgroups GammaH (a method due to
573
Jordi Quer).
574
575
EXAMPLES::
576
577
sage: G = DirichletGroup(9)
578
sage: eps = G.0^3
579
sage: eps.conductor()
580
3
581
sage: [Gamma1(9).dimension_new_cusp_forms(k, eps) for k in [2..10]]
582
[0, 0, 0, 2, 0, 2, 0, 2, 0]
583
sage: [Gamma1(9).dimension_cusp_forms(k, eps) for k in [2..10]]
584
[0, 0, 0, 2, 0, 4, 0, 6, 0]
585
sage: [Gamma1(9).dimension_new_cusp_forms(k, eps, 3) for k in [2..10]]
586
[0, 0, 0, 2, 0, 2, 0, 2, 0]
587
588
Double check using modular symbols (independent calculation)::
589
590
sage: [ModularSymbols(eps,k,sign=1).cuspidal_subspace().new_subspace().dimension() for k in [2..10]]
591
[0, 0, 0, 2, 0, 2, 0, 2, 0]
592
sage: [ModularSymbols(eps,k,sign=1).cuspidal_subspace().new_subspace(3).dimension() for k in [2..10]]
593
[0, 0, 0, 2, 0, 2, 0, 2, 0]
594
595
Another example at level 33::
596
597
sage: G = DirichletGroup(33)
598
sage: eps = G.1
599
sage: eps.conductor()
600
11
601
sage: [Gamma1(33).dimension_new_cusp_forms(k, G.1) for k in [2..4]]
602
[0, 4, 0]
603
sage: [Gamma1(33).dimension_new_cusp_forms(k, G.1, algorithm="Quer") for k in [2..4]]
604
[0, 4, 0]
605
sage: [Gamma1(33).dimension_new_cusp_forms(k, G.1^2) for k in [2..4]]
606
[2, 0, 6]
607
sage: [Gamma1(33).dimension_new_cusp_forms(k, G.1^2, p=3) for k in [2..4]]
608
[2, 0, 6]
609
610
"""
611
612
if eps == None:
613
return GammaH_class.dimension_new_cusp_forms(self, k, p)
614
615
N = self.level()
616
eps = DirichletGroup(N)(eps)
617
618
from all import Gamma0
619
620
if eps.is_trivial():
621
return Gamma0(N).dimension_new_cusp_forms(k, p)
622
623
from congroup_gammaH import mumu
624
625
if p == 0 or N%p != 0 or eps.conductor().valuation(p) == N.valuation(p):
626
D = [eps.conductor()*d for d in divisors(N//eps.conductor())]
627
return sum([Gamma1_constructor(M).dimension_cusp_forms(k, eps.restrict(M), algorithm)*mumu(N//M) for M in D])
628
eps_p = eps.restrict(N//p)
629
old = Gamma1_constructor(N//p).dimension_cusp_forms(k, eps_p, algorithm)
630
return self.dimension_cusp_forms(k, eps, algorithm) - 2*old
631
632
633
634