Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/modular/arithgroup/congroup_gamma0.py
4079 views
1
r"""
2
Congruence Subgroup `\Gamma_0(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 congroup_gammaH import GammaH_class, is_GammaH
18
from congroup_gamma1 import is_Gamma1
19
from sage.modular.modsym.p1list import lift_to_sl2z
20
from congroup_generic import is_CongruenceSubgroup, CongruenceSubgroup
21
from arithgroup_element import ArithmeticSubgroupElement
22
23
from sage.modular.cusps import Cusp
24
from sage.misc.cachefunc import cached_method
25
from sage.rings.all import (IntegerModRing, kronecker_symbol, ZZ)
26
from sage.misc.misc import prod
27
import sage.modular.modsym.p1list
28
import sage.rings.arith as arith
29
30
# Just for now until we make an SL_2 group type.
31
from sage.matrix.matrix_space import MatrixSpace
32
Mat2Z = MatrixSpace(ZZ,2)
33
34
35
def is_Gamma0(x):
36
"""
37
Return True if x is a congruence subgroup of type Gamma0.
38
39
EXAMPLES::
40
41
sage: from sage.modular.arithgroup.all import is_Gamma0
42
sage: is_Gamma0(SL2Z)
43
True
44
sage: is_Gamma0(Gamma0(13))
45
True
46
sage: is_Gamma0(Gamma1(6))
47
False
48
"""
49
return isinstance(x, Gamma0_class)
50
51
_gamma0_cache = {}
52
def Gamma0_constructor(N):
53
"""
54
Return the congruence subgroup Gamma0(N).
55
56
EXAMPLES::
57
58
sage: G = Gamma0(51) ; G # indirect doctest
59
Congruence Subgroup Gamma0(51)
60
sage: G == Gamma0(51)
61
True
62
sage: G is Gamma0(51)
63
True
64
"""
65
from all import SL2Z
66
if N == 1: return SL2Z
67
try:
68
return _gamma0_cache[N]
69
except KeyError:
70
_gamma0_cache[N] = Gamma0_class(N)
71
return _gamma0_cache[N]
72
73
class Gamma0_class(GammaH_class):
74
r"""
75
The congruence subgroup `\Gamma_0(N)`.
76
77
TESTS::
78
79
sage: Gamma0(11).dimension_cusp_forms(2)
80
1
81
sage: a = Gamma0(1).dimension_cusp_forms(2); a
82
0
83
sage: type(a)
84
<type 'sage.rings.integer.Integer'>
85
sage: Gamma0(5).dimension_cusp_forms(0)
86
0
87
sage: Gamma0(20).dimension_cusp_forms(1)
88
0
89
sage: Gamma0(20).dimension_cusp_forms(4)
90
6
91
92
sage: Gamma0(23).dimension_cusp_forms(2)
93
2
94
sage: Gamma0(1).dimension_cusp_forms(24)
95
2
96
sage: Gamma0(3).dimension_cusp_forms(3)
97
0
98
sage: Gamma0(11).dimension_cusp_forms(-1)
99
0
100
101
sage: Gamma0(22).dimension_new_cusp_forms()
102
0
103
sage: Gamma0(100).dimension_new_cusp_forms(2, 5)
104
5
105
106
Independently compute the dimension 5 above::
107
108
sage: m = ModularSymbols(100, 2,sign=1).cuspidal_subspace()
109
sage: m.new_subspace(5)
110
Modular Symbols subspace of dimension 5 of Modular Symbols space of dimension 18 for Gamma_0(100) of weight 2 with sign 1 over Rational Field
111
112
"""
113
114
115
def __init__(self, level):
116
r"""
117
The congruence subgroup `\Gamma_0(N)`.
118
119
EXAMPLES::
120
121
sage: G = Gamma0(11); G
122
Congruence Subgroup Gamma0(11)
123
sage: loads(G.dumps()) == G
124
True
125
126
TESTS::
127
128
sage: g = Gamma0(5)([1,1,0,1])
129
sage: g in Gamma0(7)
130
True
131
sage: g = Gamma0(5)([1,0,5,1])
132
sage: g in Gamma0(7)
133
False
134
sage: g = Gamma0(2)([1,0,0,1])
135
sage: g in SL2Z
136
True
137
"""
138
CongruenceSubgroup.__init__(self, level)
139
140
# We *don't* call the GammaH init script, as this requires calculating
141
# generators for the units modulo N which is time-consuming; this will
142
# be done if needed by the _generators_for_H and _list_of_elements_in_H
143
# methods.
144
#
145
#GammaH_class.__init__(self, level, [int(x) for x in IntegerModRing(level).unit_gens()])
146
147
def __cmp__(self, other):
148
"""
149
Compare self to other.
150
151
The ordering on congruence subgroups of the form GammaH(N) for
152
some H is first by level and then by the subgroup H. In
153
particular, this means that we have Gamma1(N) < GammaH(N) <
154
Gamma0(N) for every nontrivial subgroup H.
155
156
EXAMPLES::
157
158
sage: G = Gamma0(86)
159
sage: G.__cmp__(G)
160
0
161
sage: G.__cmp__(GammaH(86, [11])) is not 0
162
True
163
sage: Gamma1(17) < Gamma0(17)
164
True
165
sage: Gamma0(1) == SL2Z
166
True
167
sage: Gamma0(11) == GammaH(11, [2])
168
True
169
sage: Gamma0(2) == Gamma1(2)
170
True
171
"""
172
if not is_CongruenceSubgroup(other):
173
return cmp(type(self), type(other))
174
175
c = cmp(self.level(), other.level())
176
if c: return c
177
178
# Since Gamma0(N) is GammaH(N) for H all of (Z/N)^\times,
179
# we know how to compare it to any other GammaH without having
180
# to look at self._list_of_elements_in_H().
181
from all import is_GammaH, is_Gamma0
182
if is_GammaH(other):
183
if is_Gamma0(other):
184
return 0
185
else:
186
H = other._list_of_elements_in_H()
187
return cmp(len(H), arith.euler_phi(self.level()))
188
return cmp(type(self), type(other))
189
190
def _repr_(self):
191
"""
192
Return the string representation of self.
193
194
EXAMPLES::
195
196
sage: Gamma0(98)._repr_()
197
'Congruence Subgroup Gamma0(98)'
198
"""
199
return "Congruence Subgroup Gamma0(%s)"%self.level()
200
201
def __reduce__(self):
202
"""
203
Used for pickling self.
204
205
EXAMPLES::
206
207
sage: Gamma0(22).__reduce__()
208
(<function Gamma0_constructor at ...>, (22,))
209
"""
210
return Gamma0_constructor, (self.level(),)
211
212
def _latex_(self):
213
r"""
214
Return the \LaTeX representation of self.
215
216
EXAMPLES::
217
218
sage: Gamma0(20)._latex_()
219
'\\Gamma_0(20)'
220
sage: latex(Gamma0(20))
221
\Gamma_0(20)
222
"""
223
return "\\Gamma_0(%s)"%self.level()
224
225
@cached_method
226
def _generators_for_H(self):
227
"""
228
Return generators for the subgroup H of the units mod
229
self.level() that defines self.
230
231
EXAMPLES::
232
233
sage: Gamma0(15)._generators_for_H()
234
[11, 7]
235
"""
236
if self.level() in [1, 2]:
237
return []
238
return [ZZ(x) for x in IntegerModRing(self.level()).unit_gens()]
239
240
@cached_method
241
def _list_of_elements_in_H(self):
242
"""
243
Returns a sorted list of Python ints that are representatives
244
between 0 and N-1 of the elements of H.
245
246
EXAMPLES::
247
248
sage: G = Gamma0(11)
249
sage: G._list_of_elements_in_H()
250
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
251
252
sage: G = Gamma0(6)
253
sage: G._list_of_elements_in_H()
254
[1, 5]
255
256
sage: G = Gamma0(1)
257
sage: G._list_of_elements_in_H()
258
[1]
259
"""
260
N = self.level()
261
if N != 1:
262
gcd = arith.gcd
263
H = [ x for x in range(1, N) if gcd(x, N) == 1 ]
264
else:
265
H = [1]
266
267
return H
268
269
def divisor_subgroups(self):
270
r"""
271
Return the subgroups of SL2Z of the form Gamma0(M) that contain this subgroup,
272
i.e. those for M a divisor of N.
273
274
EXAMPLE::
275
276
sage: Gamma0(24).divisor_subgroups()
277
[Modular Group SL(2,Z),
278
Congruence Subgroup Gamma0(2),
279
Congruence Subgroup Gamma0(3),
280
Congruence Subgroup Gamma0(4),
281
Congruence Subgroup Gamma0(6),
282
Congruence Subgroup Gamma0(8),
283
Congruence Subgroup Gamma0(12),
284
Congruence Subgroup Gamma0(24)]
285
"""
286
return [Gamma0_constructor(M) for M in self.level().divisors()]
287
288
def is_even(self):
289
r"""
290
Return True precisely if this subgroup contains the matrix -1.
291
292
Since `\Gamma0(N)` always contains the matrix -1, this always
293
returns True.
294
295
EXAMPLES::
296
297
sage: Gamma0(12).is_even()
298
True
299
sage: SL2Z.is_even()
300
True
301
"""
302
return True
303
304
def is_subgroup(self, right):
305
"""
306
Return True if self is a subgroup of right.
307
308
EXAMPLES::
309
310
sage: G = Gamma0(20)
311
sage: G.is_subgroup(SL2Z)
312
True
313
sage: G.is_subgroup(Gamma0(4))
314
True
315
sage: G.is_subgroup(Gamma0(20))
316
True
317
sage: G.is_subgroup(Gamma0(7))
318
False
319
sage: G.is_subgroup(Gamma1(20))
320
False
321
sage: G.is_subgroup(GammaH(40, []))
322
False
323
sage: Gamma0(80).is_subgroup(GammaH(40, [31, 21, 17]))
324
True
325
sage: Gamma0(2).is_subgroup(Gamma1(2))
326
True
327
"""
328
if right.level() == 1:
329
return True
330
if is_Gamma0(right):
331
return self.level() % right.level() == 0
332
if is_Gamma1(right):
333
if right.level() >= 3:
334
return False
335
elif right.level() == 2:
336
return self.level() == 2
337
# case level 1 dealt with above
338
else:
339
return GammaH_class.is_subgroup(self, right)
340
341
def coset_reps(self):
342
r"""
343
Return representatives for the right cosets of this congruence
344
subgroup in `{\rm SL}_2(\ZZ)` as a generator object.
345
346
Use ``list(self.coset_reps())`` to obtain coset reps as a
347
list.
348
349
EXAMPLES::
350
351
sage: list(Gamma0(5).coset_reps())
352
[
353
[1 0] [ 0 -1] [1 0] [ 0 -1] [ 0 -1] [ 0 -1]
354
[0 1], [ 1 0], [1 1], [ 1 2], [ 1 3], [ 1 4]
355
]
356
sage: list(Gamma0(4).coset_reps())
357
[
358
[1 0] [ 0 -1] [1 0] [ 0 -1] [ 0 -1] [1 0]
359
[0 1], [ 1 0], [1 1], [ 1 2], [ 1 3], [2 1]
360
]
361
sage: list(Gamma0(1).coset_reps())
362
[
363
[1 0]
364
[0 1]
365
]
366
"""
367
from all import SL2Z
368
N = self.level()
369
if N == 1: # P1List isn't very happy working modulo 1
370
yield SL2Z([1,0,0,1])
371
else:
372
for z in sage.modular.modsym.p1list.P1List(N):
373
yield SL2Z(lift_to_sl2z(z[0], z[1], N))
374
375
@cached_method
376
def generators(self, algorithm="farey"):
377
r"""
378
Return generators for this congruence subgroup.
379
380
INPUT:
381
382
- ``algorithm`` (string): either ``farey`` (default) or
383
``todd-coxeter``.
384
385
If ``algorithm`` is set to ``"farey"``, then the generators will be
386
calculated using Farey symbols, which will always return a *minimal*
387
generating set. See :mod:`~sage.modular.arithgroup.farey_symbol` for
388
more information.
389
390
If ``algorithm`` is set to ``"todd-coxeter"``, a simpler algorithm
391
based on Todd-Coxeter enumeration will be used. This tends to return
392
far larger sets of generators.
393
394
EXAMPLE::
395
396
sage: Gamma0(3).generators()
397
[
398
[1 1] [-1 1]
399
[0 1], [-3 2]
400
]
401
sage: Gamma0(3).generators(algorithm="todd-coxeter")
402
[
403
[1 1] [-1 0] [ 1 -1] [1 0] [1 1] [-1 0] [ 1 0]
404
[0 1], [ 0 -1], [ 0 1], [3 1], [0 1], [ 3 -1], [-3 1]
405
]
406
sage: SL2Z.gens()
407
(
408
[ 0 -1] [1 1]
409
[ 1 0], [0 1]
410
)
411
"""
412
if self.level() == 1:
413
# we return a fixed set of generators for SL2Z, for historical
414
# reasons, which aren't the ones the Farey symbol code gives
415
return [ self([0,-1,1,0]), self([1,1,0,1]) ]
416
417
elif algorithm=="farey":
418
return self.farey_symbol().generators()
419
420
elif algorithm=="todd-coxeter":
421
from sage.modular.modsym.p1list import P1List
422
from congroup_pyx import generators_helper
423
level = self.level()
424
if level == 1: # P1List isn't very happy working mod 1
425
return [ self([0,-1,1,0]), self([1,1,0,1]) ]
426
gen_list = generators_helper(P1List(level), level, Mat2Z)
427
return [self(g, check=False) for g in gen_list]
428
429
else:
430
raise ValueError, "Unknown algorithm '%s' (should be either 'farey' or 'todd-coxeter')" % algorithm
431
432
def gamma_h_subgroups(self):
433
r"""
434
Return the subgroups of the form `\Gamma_H(N)` contained
435
in self, where `N` is the level of self.
436
437
EXAMPLES::
438
439
sage: G = Gamma0(11)
440
sage: G.gamma_h_subgroups()
441
[Congruence Subgroup Gamma0(11), Congruence Subgroup Gamma_H(11) with H generated by [4], Congruence Subgroup Gamma_H(11) with H generated by [10], Congruence Subgroup Gamma1(11)]
442
sage: G = Gamma0(12)
443
sage: G.gamma_h_subgroups()
444
[Congruence Subgroup Gamma0(12), Congruence Subgroup Gamma_H(12) with H generated by [7], Congruence Subgroup Gamma_H(12) with H generated by [11], Congruence Subgroup Gamma_H(12) with H generated by [5], Congruence Subgroup Gamma1(12)]
445
"""
446
from all import GammaH
447
N = self.level()
448
R = IntegerModRing(N)
449
return [GammaH(N, H) for H in R.multiplicative_subgroups()]
450
451
def __call__(self, x, check=True):
452
r"""
453
Create an element of this congruence subgroup from x.
454
455
If the optional flag check is True (default), check whether
456
x actually gives an element of self.
457
458
EXAMPLES::
459
460
sage: G = Gamma0(12)
461
sage: G([1, 0, 24, 1])
462
[ 1 0]
463
[24 1]
464
sage: G(matrix(ZZ, 2, [1, 1, -12, -11]))
465
[ 1 1]
466
[-12 -11]
467
sage: G([1, 0, 23, 1])
468
Traceback (most recent call last):
469
...
470
TypeError: matrix must have lower left entry (=23) divisible by 12
471
"""
472
from all import SL2Z
473
x = SL2Z(x, check)
474
if not check:
475
return x
476
477
c = x.c()
478
N = self.level()
479
if c%N == 0:
480
return x
481
else:
482
raise TypeError, "matrix must have lower left entry (=%s) divisible by %s" %(c, N)
483
484
def _find_cusps(self):
485
r"""
486
Return an ordered list of inequivalent cusps for self, i.e. a
487
set of representatives for the orbits of self on
488
`\mathbb{P}^1(\QQ)`. These are returned in a reduced
489
form; see self.reduce_cusp for the definition of reduced.
490
491
ALGORITHM:
492
Uses explicit formulae specific to `\Gamma_0(N)`: a reduced cusp on
493
`\Gamma_0(N)` is always of the form `a/d` where `d | N`, and `a_1/d
494
\sim a_2/d` if and only if `a_1 \cong a_2 \bmod {\rm gcd}(d,
495
N/d)`.
496
497
EXAMPLES::
498
499
sage: Gamma0(90)._find_cusps()
500
[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]
501
sage: Gamma0(1).cusps()
502
[Infinity]
503
sage: Gamma0(180).cusps() == Gamma0(180).cusps(algorithm='modsym')
504
True
505
"""
506
N = self.level()
507
s = []
508
509
for d in arith.divisors(N):
510
w = arith.gcd(d, N//d)
511
if w == 1:
512
if d == 1:
513
s.append(Cusp(1,0))
514
elif d == N:
515
s.append(Cusp(0,1))
516
else:
517
s.append(Cusp(1,d))
518
else:
519
for a in xrange(1, w):
520
if arith.gcd(a, w) == 1:
521
while arith.gcd(a, d//w) != 1:
522
a += w
523
s.append(Cusp(a,d))
524
return sorted(s)
525
526
def ncusps(self):
527
r"""
528
Return the number of cusps of this subgroup `\Gamma_0(N)`.
529
530
EXAMPLES::
531
532
sage: [Gamma0(n).ncusps() for n in [1..19]]
533
[1, 2, 2, 3, 2, 4, 2, 4, 4, 4, 2, 6, 2, 4, 4, 6, 2, 8, 2]
534
sage: [Gamma0(n).ncusps() for n in prime_range(2,100)]
535
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
536
"""
537
n = self.level()
538
return sum([arith.euler_phi(arith.gcd(d,n//d)) for d in n.divisors()])
539
540
541
def nu2(self):
542
r"""
543
Return the number of elliptic points of order 2 for this congruence
544
subgroup `\Gamma_0(N)`. The number of these is given by a standard formula:
545
0 if `N` is divisible by 4 or any prime congruent to -1 mod 4, and
546
otherwise `2^d` where d is the number of odd primes dividing `N`.
547
548
EXAMPLE::
549
550
sage: Gamma0(2).nu2()
551
1
552
sage: Gamma0(4).nu2()
553
0
554
sage: Gamma0(21).nu2()
555
0
556
sage: Gamma0(1105).nu2()
557
8
558
sage: [Gamma0(n).nu2() for n in [1..19]]
559
[1, 1, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 2, 0, 0]
560
"""
561
n = self.level()
562
if n%4 == 0:
563
return ZZ(0)
564
return prod([ 1 + kronecker_symbol(-4, p) for p, _ in n.factor()])
565
566
def nu3(self):
567
r"""
568
Return the number of elliptic points of order 3 for this congruence
569
subgroup `\Gamma_0(N)`. The number of these is given by a standard formula:
570
0 if `N` is divisible by 9 or any prime congruent to -1 mod 3, and
571
otherwise `2^d` where d is the number of primes other than 3 dividing `N`.
572
573
EXAMPLE::
574
575
sage: Gamma0(2).nu3()
576
0
577
sage: Gamma0(3).nu3()
578
1
579
sage: Gamma0(9).nu3()
580
0
581
sage: Gamma0(7).nu3()
582
2
583
sage: Gamma0(21).nu3()
584
2
585
sage: Gamma0(1729).nu3()
586
8
587
"""
588
n = self.level()
589
if (n % 9 == 0):
590
return ZZ(0)
591
return prod([ 1 + kronecker_symbol(-3, p) for p, _ in n.factor()])
592
593
def index(self):
594
r"""
595
Return the index of self in the full modular group. This is given by
596
597
.. math::
598
599
N \prod_{\substack{p \mid N \\ \text{$p$ prime}}}\left(1 + \frac{1}{p}\right).
600
601
EXAMPLE::
602
sage: [Gamma0(n).index() for n in [1..19]]
603
[1, 3, 4, 6, 6, 12, 8, 12, 12, 18, 12, 24, 14, 24, 24, 24, 18, 36, 20]
604
sage: Gamma0(32041).index()
605
32220
606
"""
607
return prod([p**e + p**(e-1) for (p,e) in self.level().factor()])
608
609
610
611
612