Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
241818 views
1
# -*- coding: utf-8 -*-
2
r"""
3
A general class for subgroups of the modular group.
4
Extends the standard classes with methods needed for Maass waveforms.
5
6
7
AUTHORS:
8
9
- Fredrik Strömberg
10
11
12
EXAMPLES::
13
14
15
sage: P=Permutations(6)
16
sage: pS=P([2,1,4,3,6,5])
17
sage: pR=P([3,1,2,5,6,4])
18
sage: G
19
Arithmetic Subgroup of PSL2(Z) with index 6.
20
Given by
21
perm(S)=[2, 1, 4, 3, 6, 5]
22
perm(ST)=[3, 1, 2, 5, 6, 4]
23
Constructed from G=Arithmetic subgroup corresponding to permutations L=(2,3,5,4), R=(1,3,6,4)
24
sage: TestSuite.run()
25
26
"""
27
28
#*****************************************************************************
29
# Copyright (C) 2010 Fredrik Strömberg <[email protected]>,
30
#
31
# Distributed under the terms of the GNU General Public License (GPL)
32
#
33
# This code is distributed in the hope that it will be useful,
34
# but WITHOUT ANY WARRANTY; without even the implied warranty of
35
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
36
# General Public License for more details.
37
#
38
# The full text of the GPL is available at:
39
#
40
# http://www.gnu.org/licenses/
41
#*****************************************************************************
42
43
from sage.all_cmdline import * # import sage library
44
from sage.rings.all import (Integer, CC,ZZ,QQ,RR,RealNumber,I,infinity,Rational)
45
from sage.combinat.permutation import (Permutations,PermutationOptions)
46
from sage.modular.cusps import Cusp
47
from sage.modular.arithgroup.all import *
48
from sage.modular.modsym.p1list import lift_to_sl2z
49
from sage.functions.other import (floor,sqrt)
50
from sage.all import matrix #import constructor
51
from sage.modular.arithgroup import congroup_gamma0
52
from sage.groups.all import SymmetricGroup
53
from sage.rings.arith import lcm
54
from copy import deepcopy
55
from mysubgroups_alg import * #are_transitive_permutations
56
57
58
59
_sage_const_3 = Integer(3); _sage_const_2 = Integer(2);
60
_sage_const_1 = Integer(1); _sage_const_0 = Integer(0);
61
_sage_const_6 = Integer(6); _sage_const_4 = Integer(4);
62
_sage_const_100 = Integer(100);
63
_sage_const_1En12 = RealNumber('1E-12');
64
_sage_const_201 = Integer(201); _sage_const_1p0 = RealNumber('1.0');
65
_sage_const_1p5 = RealNumber('1.5'); _sage_const_0p0 = RealNumber('0.0');
66
_sage_const_2p0 = RealNumber('2.0'); _sage_const_0p2 = RealNumber('0.2');
67
_sage_const_0p5 = RealNumber('0.5'); _sage_const_1000 = Integer(1000);
68
_sage_const_10000 = Integer(10000); _sage_const_1En10 = RealNumber('1E-10');
69
_sage_const_20 = Integer(20); _sage_const_3p0 = RealNumber('3.0')
70
71
72
73
74
import sys,os
75
import matplotlib.patches as patches
76
import matplotlib.path as path
77
78
from sage.modular.arithgroup.arithgroup_perm import *
79
#from subgroups_alg import *
80
#load "/home/stromberg/sage/subgroups/subgroups_alg.spyx"
81
82
class MySubgroup (ArithmeticSubgroup):
83
r"""
84
A class for subgroups of the modular group SL(2,Z).
85
Extends the standard classes with methods needed for Maass waveforms.
86
87
EXAMPLES::
88
89
90
sage: G=MySubgroup(Gamma0(5));G
91
Arithmetic Subgroup of PSL2(Z) with index 6.
92
Given by
93
perm(S)=[2, 1, 4, 3, 5, 6]
94
perm(ST)=[3, 1, 2, 5, 6, 4]
95
Constructed from G=Congruence Subgroup Gamma0(5)
96
sage: G.is_subgroup(G5)
97
True
98
sage: G.cusps()
99
[Infinity, 0]
100
G.cusp_normalizer(G.cusps()[1])
101
[ 0 -1]
102
[ 1 0]
103
104
105
"""
106
def __init__(self,G=None,o2=None,o3=None,str=None):
107
r""" Init a subgroup in the following forms:
108
1. G = Arithmetic subgroup
109
2. (o2,o3) = pair of transitive permutations of order 2 and
110
order 3 respectively.
111
112
Attributes:
113
114
permS = permutation representating S
115
permT = permutation representating T
116
permR = permutation representating R=ST
117
permP = permutation representating P=STS
118
(permR,permT) gives the group as permutation group
119
Note:
120
The usual permutation group has two parabolic permutations L,R
121
L=permT, R=permP
122
123
EXAMPLES::
124
125
126
sage: G=SL2Z
127
sage: MG=MySubgroup(G);MG
128
Arithmetic Subgroup of PSL2(Z) with index 1.
129
Given by
130
perm(S)=[1]
131
perm(ST)=[1]
132
Constructed from G=Modular Group SL(2,Z)
133
sage: P=Permutatons(6)
134
sage: pS=[2,1,4,3,6,5]
135
sage: pR=[3,1,2,5,6,4]
136
sage: G=MySubgroup(o2=pS,o3=pR);G
137
Arithmetic Subgroup of PSL2(Z) with index 6.
138
Given by
139
perm(S)=[2, 1, 4, 3, 6, 5]
140
perm(ST)=[3, 1, 2, 5, 6, 4]
141
Constructed from G=Arithmetic subgroup corresponding to permutations L=(2,3,5,4), R=(1,3,6,4)
142
143
144
"""
145
# Get index also check consistency of indata
146
147
148
self._coset_reps = None
149
self._vertices = None # List of vertices corresponding to the coset reps.
150
self._cusps = None # Subset of representatives
151
self._cusp_representative = None # Associate cusp reps to vertices
152
self._vertex_reps=None # The coset rep associated to each vertex
153
if(str<>None):
154
try:
155
G=sage_eval(str)
156
except:
157
try:
158
[o2,o3]=self._get_perms_from_str(str)
159
o2.to_cycles()
160
except:
161
raise ValueError,"Incorrect string as input to constructor! Got s=%s" %(str)
162
163
self._index=self._get_index(G,o2,o3)
164
self._level=None
165
self._generalised_level=None
166
self._is_congruence=None
167
## The underlying permutation group
168
self._P=Permutations(self._index)
169
#self._S=SymmetricGroup(self._index)
170
## Check if the multiplication of permutations is set as left
171
## to right, which is what we expect. I.e. we need
172
## (f*g)(x)=g(f(x))
173
## With this conventions the map h:SL2Z->Permutations(N)
174
## is a homomorphis. I.e. h(AB)=h(A)h(B)
175
if(PermutationOptions()['mult']<>'l2r'):
176
raise ValueError, "Need permutations to multply from left to right! Got PermutationOptions()=" %PermutationOptions()
177
PermutationOptions(display='cycle')
178
if G <>None:
179
self._G=G
180
if hasattr(G,'permutation_action'):
181
# is a permutation subgroup
182
self.permS=self._P( (G.L*(~G.R)*G.L).list())
183
self.permT=self._P( G.L.list() )
184
self.permR=self.permS*self.permT
185
self.permP=self._P( G.R.list() )
186
self._coset_reps=self._get_coset_reps_from_perms(self.permS,self.permR,self.permT)
187
elif(hasattr(G,'is_congruence')):
188
self._coset_reps=self._get_coset_reps_from_G(G)
189
[self.permS,self.permR]=self._get_perms_from_coset_reps()
190
self.permT=self.permS*self.permR
191
self.permP=self.permT*self.permS*self.permT
192
else:
193
raise TypeError,"Error input G= %s" % G
194
ArithmeticSubgroup.__init__(G)
195
elif( (o2<>None and o3<>None) or (str<>None)):
196
if(o2 in self._P):
197
self.permS=o2
198
self.permR=o3
199
else:
200
self.permS=self._P(o2.list())
201
self.permR=self._P(o3.list())
202
# Check the consistency of the input permutations
203
self._test_consistency_perm(self.permS,self.permR)
204
self.permT=self.permS*self.permR
205
self.permP=self.permT*self.permS*self.permT
206
#print "self.permS=",self.permS
207
#print "self.permT=",self.permT
208
#print "self.permR=",self.permR
209
## Need to get rid of the necessity for this
210
S=SymmetricGroup(self._index)
211
L=S(list(self.permT))
212
R=S(list(self.permP))
213
G=ArithmeticSubgroup_Permutation(L,R)
214
ArithmeticSubgroup.__init__(G)
215
self._G=G
216
self._coset_reps=self._get_coset_reps_from_perms(self.permS,self.permR,self.permT)
217
## Recall that generalized level is just the greatest common divisor of cusp widths
218
## or alternatively gcd of all cycle lenghts of sigma_T
219
cycles=self.permT.to_cycles()
220
lens=list()
221
for c in cycles:
222
lens.append(len(c))
223
self._generalised_level=lcm(lens)
224
else:
225
raise TypeError,"Error incorrect input to constuctor!"
226
# Figure out if we have permutations in some form
227
## Just to make sure that the permutations are implemented correctly
228
self._nu2=len(self.permS.fixed_points())
229
self._nu3=len(self.permR.fixed_points())
230
#print "To get_vertices"
231
[self._vertices,self._vertex_reps]=self._get_vertices(self._coset_reps)
232
#print "GOT_vertices"
233
self._cusps=self._get_cusps(self._vertices)
234
self._ncusps=len(self._cusps)
235
self._genus=1 +1 /_sage_const_2 *(self._index/_sage_const_6 -self._ncusps-1 /_sage_const_2 *self._nu2-_sage_const_2 /_sage_const_3 *self._nu3)
236
# Recall that the true permutations are given by the coset reps.
237
#print "GOT_CUSPS"
238
# These are dictionaries with vertices/cusps as keys
239
self._vertex_map= None # Maps each vertex to the corr. cusp
240
self._cusp_normalizer=None # Cusp normalizers (unscaled)
241
self._cusp_stabilizer=None # Generators of the stabilizers
242
self._cusp_width=None # Cusp widths
243
if(self._generalised_level==None):
244
self._generalised_level=self._G.generalised_level()
245
# for some reason this doesn't work for SL2Z
246
if(len(self.permS)==1 and len(self.permR)==1):
247
self._is_congruence=True
248
else:
249
self._is_congruence=self._G.is_congruence()
250
if(self.is_congruence()):
251
self._level=self._generalised_level
252
253
#print "1"
254
[self._cusp_representative,self._vertex_map]=self._get_vertex_maps()
255
#print "2 GOT_vertex_DATA"
256
[self._cusp_normalizer,self._cusp_stabilizer,self._cusp_width]=self._get_cusp_data()
257
#print "3 GOT_CUSP_DATA"
258
# We need some manageble unique (string) identifier
259
# Which also contains info in clear text...
260
self._uid = self._get_uid()
261
262
def _repr_(self):
263
r"""
264
Return the string representation of self.
265
266
EXAMPLES::
267
268
269
sage: P=Permutatons(6)
270
sage: pS=[2,1,4,3,6,5]
271
sage: pR=[3,1,2,5,6,4]
272
sage: G=MySubgroup(o2=pS,o3=pR);G._repr_()
273
Arithmetic Subgroup of PSL2(Z) with index 6.
274
Given by
275
perm(S)=[2, 1, 4, 3, 6, 5]
276
perm(ST)=[3, 1, 2, 5, 6, 4]
277
Constructed from G=Arithmetic subgroup corresponding to permutations L=(2,3,5,4), R=(1,3,6,4)
278
sage: G=SL2Z
279
sage: MG=MySubgroup(G);MG._repr_()
280
Arithmetic Subgroup of PSL2(Z) with index 1.
281
Given by
282
perm(S)=[1]
283
perm(ST)=[1]
284
Constructed from G=Modular Group SL(2,Z)
285
286
"""
287
s ="Arithmetic Subgroup of PSL2(Z) with index "+str(self._index)+". "
288
s+="Given by: \n \t perm(S)="+str(self.permS)+"\n \t perm(ST)="+str(self.permR)
289
s+="\nConstructed from G="+self._G._repr_()
290
291
#self._name=s
292
#print "s=",s
293
return s
294
295
296
def _get_uid(self):
297
r""" Constructs a unique identifier for the group
298
299
OUTPUT::
300
301
A unique identifier as string.
302
The only truly unique identifier is the set of permutations
303
so we return those as strings.
304
Because of the extra stucture given by e.g. Gamma0(N) we also return
305
this information if available.
306
307
308
EXAMPLES::
309
310
311
sage: G=MySubgroup(Gamma0(5))
312
sage: G._get_uid()
313
[2_1_4_3_5_6]-[3_1_2_5_6_4]-Gamma0(5)'
314
sage: P=Permutations(6)
315
sage: pS=P([2,1,4,3,6,5])
316
sage: pR=P([3,1,2,5,6,4])
317
sage: G=MySubgroup(o2=pS,o3=pR)
318
sage: G._get_uid()
319
'[2_1_4_3_6_5]-[3_1_2_5_6_4]'
320
"""
321
# If we have a Congruence subgroup it is (more or less) easy
322
s=""
323
N=self.generalised_level()
324
domore=False
325
s=str(self.permS._list)+"-"
326
s=s+str(self.permR._list)
327
s=s.replace(", ","_")
328
if(self._is_congruence):
329
# test:
330
if(self._G == Gamma0(N) or self._G == Gamma0(N).as_permutation_group()):
331
s+="-Gamma0("+str(N)+")"
332
else:
333
if(self._G == Gamma(N)):
334
s+="-Gamma("+str(N)+")"
335
elif(Gamma(N).is_even()):
336
if(self._G == Gamma(N).as_permutation_group()):
337
s+="-Gamma("+str(N)+")"
338
if(self._G == Gamma1(N)):
339
s+="-Gamma1("+str(N)+")"
340
elif(Gamma1(N).is_even()):
341
if(self._G == Gamma1(N) or self._G == Gamma1(N).as_permutation_group()):
342
s+="-Gamma1("+str(N)+")"
343
return s
344
345
def __reduce__(self):
346
r"""
347
Used for pickling self.
348
349
EXAMPLES::
350
351
352
sage: G=MySubgroup(Gamma0(5))
353
354
Not implmented!
355
"""
356
#raise NotImplementedError, "Not (correctly) implemented!"
357
return (MySubgroup, (None,self.permS,self.permR))
358
359
def __cmp__(self,other):
360
r""" Compare self to other.
361
362
EXAMPLES::
363
364
sage: G=MySubgroup(Gamma0(5))
365
sage: G <> Gamma0(5)
366
False
367
sage: GG=MySubgroup(None,G.permS,G.permR)
368
sage: GG == G
369
370
371
"""
372
return self._G.__cmp__(other._G)
373
374
def __eq__(self,G):
375
r"""
376
Test if G is equal to self.
377
378
EXAMPLES::
379
380
381
sage: G=MySubgroup(Gamma0(5))
382
sage: G==Gamma0(5)
383
False
384
sage: GG=MySubgroup(None,G.permS,G.permR)
385
sage: GG == G
386
True
387
"""
388
try:
389
pR=G.permR
390
pS=G.permS
391
if(pR==self.permR and pS==self.permS):
392
return True
393
except:
394
pass
395
return False
396
397
def _get_index(self,G=None,o2=None,o3=None):
398
r""" Index of self.
399
400
INPUT:
401
402
- ``G`` -- subgroup
403
- ``o2``-- permutation
404
- ``o3``-- permutation
405
406
OUTPUT:
407
408
- integer -- the index of G in SL2Z or the size of the permutation group.
409
410
EXAMPLES::
411
412
413
sage: G=MySubgroup(Gamma0(5))
414
sage: G._get_index(G._G,None,None)
415
6
416
sage: G._get_index(Gamma0(8))
417
12
418
sage: pS=P([2,1,4,3,6,5])
419
sage: pR=P([3,1,2,5,6,4])
420
sage: G._get_index(o2=pS,o3=pR)
421
6
422
"""
423
if(G<>None):
424
try:
425
if(G.is_subgroup(SL2Z)):
426
ix=G.index()
427
except:
428
raise TypeError,"Wrong format for input G: Need subgroup of PSLZ! Got: \n %s" %(G)
429
else:
430
if(o2==None):
431
raise TypeError,"Need to supply one of G,o2,o3 got: \n G=%s,\n o2=%s,\o3=%s"%(G,o2,o3)
432
if(hasattr(o2,"to_cycles")):
433
ix=len(o2)
434
elif(hasattr(o2,"list")): # We had an element of the SymmetricGroup
435
ix=len(o2.list())
436
else:
437
raise TypeError,"Wrong format of input: o2=%s, \n o2.parent=%s"%(o2,o2.parent())
438
return ix
439
440
def _get_vertices(self,reps):
441
r""" Compute vertices of a fundamental domain corresponding
442
to coset reps. given in the list reps.
443
INPUT:
444
- ''reps'' -- a set of matrices in SL2Z (coset representatives)
445
OUTPUT:
446
- [vertices,vertex_reps]
447
''vertices'' = list of vertices represented as cusps of self
448
''vertex_reps'' = list of matrices corresponding to the vertices
449
(essentially a reordering of the elements of reps)
450
451
452
EXAMPLES::
453
454
455
sage: G=MySubgroup(Gamma0(5))
456
sage: l=G._coset_reps
457
sage: G._get_vertices(l)
458
sage: G._get_vertices(l)
459
[[Infiniy, 0], {0: [ 0 -1]
460
[ 1 -2], Infinity: [1 0]
461
[0 1]}]
462
sage: P=Permutations(6)
463
sage: pS=P([2,1,4,3,6,5])
464
sage: pR=P([3,1,2,5,6,4])
465
sage: G=MySubgroup(o2=pS,o3=pR)
466
sage: G._get_vertices(G._coset_reps)
467
[[Infinity, 0, -1/2], {0: [ 0 -1]
468
[ 1 2], Infinity: [1 0]
469
[0 1], -1/2: [-1 0]
470
[ 2 -1]}]
471
"""
472
v=list()
473
vr=dict()
474
for A in reps:
475
if(A[1 ,0 ]<>0 and v.count(Cusp(A[0 ,0 ],A[1 ,0 ]))==0 ):
476
c=Cusp(A[0 ,0 ],A[1 ,0 ])
477
v.append(c)
478
elif(v.count(Cusp(infinity))==0 ):
479
c=Cusp(infinity)
480
v.append(c)
481
vr[c]=A
482
# Reorder so that Infinity and 0 are first
483
if(v.count(Cusp(0 ))>0 ):
484
v.remove(Cusp(0 )); v.remove(Cusp(infinity))
485
v.append(Cusp(0 )); v.append(Cusp(infinity))
486
else:
487
v.remove(Cusp(infinity))
488
v.append(Cusp(infinity))
489
v.reverse()
490
return [v,vr]
491
492
def _test_consistency_perm(self,o2,o3):
493
r""" Check the consistency of input permutations.
494
495
INPUT:
496
- ''o2'' Permutation of N1 letters
497
- ''o3'' Permutation of N2 letters
498
499
OUTPUT:
500
- True : If N1=N2=N, o2**2=o3**3=Identity, <o2,o3>=Permutations(N)
501
(i.e. the pair o2,o3 is transitive) where N=index of self
502
- Otherwise an Exception is raised
503
504
505
EXAMPLES::
506
507
508
sage: P=Permutations(6)
509
sage: pS=P([2,1,4,3,6,5])
510
sage: pR=P2([3,1,2,5,6,4])
511
sage: G=MySubgroup(o2=pS,o3=pR)
512
sage: G._test_consistency_perm(pS,pR)
513
True
514
515
"""
516
SG1=self._P.identity()
517
o22=o2*o2; o33=o3*o3*o3
518
if(o22 <>SG1 or o33<>SG1):
519
s="Input permutations are not of order 2 and 3: \n perm(S)^2=%s \n perm(R)^3=%s "
520
raise ValueError, s % o2*o2,o3*o3*o3
521
if(not are_transitive_permutations(o2,o3)):
522
GS=S.subgroup([self.permS,self.permR])
523
s="Error in construction of permutation group! Permutations not transitive: <S,R>=%s"
524
raise ValueError, s % GS.list()
525
return True
526
527
528
def permutation_action(self,A):
529
r""" The permutation corresponding to the matrix A
530
INPUT:
531
- ''A'' Matrix in SL2Z
532
OUPUT:
533
element in self._P given by the presentation of the group self
534
535
536
EXAMPLES::
537
538
539
P=Permutations(6)
540
sage: pS=P([2,1,4,3,6,5])
541
sage: pR=P([3,1,2,5,6,4])
542
sage: G=MySubgroup(o2=pS,o3=pR)
543
sage: G.permutation_action(S*T).to_cycles()
544
[(1, 3, 2), (4, 5, 6)]
545
sage: G.permutation_action(S).to_cycles()
546
[(1, 2), (3, 4), (5, 6)]
547
sage: pR.to_cycles()
548
[(1, 3, 2), (4, 5, 6)]
549
sage: pS.to_cycles()
550
[(1, 2), (3, 4), (5, 6)]
551
sage: G=MySubgroup(Gamma0(5))
552
sage: G.permutation_action(S).to_cycles()
553
[(1, 2), (3, 4), (5,), (6,)]
554
sage: G.permutation_action(S*T).to_cycles()
555
[(1, 3, 2), (4, 5, 6)]
556
557
"""
558
[cf,sg]=factor_matrix_in_sl2z_in_S_and_T(A)
559
# The sign doesn't matter since we are working in paractice only with PSL(2,Z)
560
# We now have A=T^a0*S*T^a1*...*S*T^an
561
# Hence
562
# h(A)=h(T^an)*h(S)*...*h(T^a1)*h(S)*h(T^a0)
563
#print "cf=",cf
564
n=len(cf)
565
#print "n=",n
566
def ppow(perm,k):
567
if(k>=0 ):
568
pp=perm
569
for j in range(k-1 ):
570
pp=pp*perm
571
return pp
572
else:
573
permi=perm.inverse()
574
pp=permi
575
for j in range(abs(k)-1 ):
576
pp=pp*permi
577
return pp
578
if(cf[0 ]==0 ):
579
p=self._P.identity()
580
else:
581
p=ppow(self.permT,cf[0 ])
582
#print "p(",0,")=",p.to_cycles()
583
for j in range(1 ,n):
584
a=cf[j]
585
if(a<>0 ):
586
p=p*self.permS*ppow(self.permT,a)
587
else:
588
p=p*self.permS
589
#print "p(",j,")=",p.to_cycles()
590
return p
591
592
593
594
def _get_coset_reps_from_G(self,G):
595
r"""
596
Compute a better/nicer list of right coset representatives [V_j]
597
i.e. SL2Z = \cup G V_j
598
Use this routine for known congruence subgroups.
599
600
EXAMPLES::
601
602
603
sage: G=MySubgroup(Gamma0(5))
604
sage: G._get_coset_reps_from_G(Gamma0(5))
605
[[1 0]
606
[0 1], [ 0 -1]
607
[ 1 0], [ 0 -1]
608
[ 1 1], [ 0 -1]
609
[ 1 -1], [ 0 -1]
610
[ 1 2], [ 0 -1]
611
[ 1 -2]]
612
613
"""
614
cl=list()
615
S,T=SL2Z.gens()
616
lvl=G.generalised_level()
617
# Start with identity rep.
618
cl.append(SL2Z([1 ,0 ,0 ,1 ]))
619
if(not S in G):
620
cl.append(S)
621
# If the original group is given as a Gamma0 then
622
# the reps are not the one we want
623
# I.e. we like to have a fundamental domain in
624
# -1/2 <=x <= 1/2 for Gamma0, Gamma1, Gamma
625
for j in range(1 ,Integer(lvl)/_sage_const_2 +_sage_const_2 ):
626
for ep in [1 ,-1 ]:
627
if(len(cl)>=self._index):
628
break
629
# The ones about 0 are all of this form
630
A=SL2Z([0 ,-1 ,1 ,ep*j])
631
# just make sure they are inequivalent
632
try:
633
for V in cl:
634
if((A<>V and A*V**-1 in G) or cl.count(A)>0 ):
635
raise StopIteration()
636
cl.append(A)
637
except StopIteration:
638
pass
639
# We now addd the rest of the "flips" of these reps.
640
# So that we end up with a connected domain
641
i=1
642
while(True):
643
lold=len(cl)
644
for V in cl:
645
for A in [S,T,T**-1 ]:
646
B=V*A
647
try:
648
for W in cl:
649
if( (B*W**-1 in G) or cl.count(B)>0 ):
650
raise StopIteration()
651
cl.append(B)
652
except StopIteration:
653
pass
654
if(len(cl)>=self._index or lold>=len(cl)):
655
# If we either did not addd anything or if we addded enough
656
# we exit
657
break
658
# If we missed something (which is unlikely)
659
if(len(cl)<>self._index):
660
print "cl=",cl
661
raise ValueError,"Problem getting coset reps! Need %s and got %s" %(self._index,len(cl))
662
return cl
663
664
665
666
667
def _get_coset_reps_from_perms(self,pS,pR,pT):
668
r"""
669
Compute a better/nicer list of right coset representatives
670
i.e. SL2Z = \cup G V_j
671
Todo: Check consistency of input
672
673
EXAMPLES::
674
675
sage: pS=P([1,3,2,5,4,7,6]); pS
676
(2,3)(4,5)(6,7)
677
sage: pR=P([3,2,4,1,6,7,5]); pR
678
(1,3,4)(5,6,7)
679
sage: G=MySubgroup(o2=pS,o3=pR)
680
sage: G._get_coset_reps_from_perms(G.permS,G.permR,G.permT)
681
[[1 0]
682
[0 1], [1 2]
683
[0 1], [1 1]
684
[0 1], [1 3]
685
[0 1], [1 5]
686
[0 1], [1 4]
687
[0 1], [ 4 -1]
688
[ 1 0]]
689
690
# Testing that the reps really are correct coset reps
691
692
sage: l=G._get_coset_reps_from_perms(G.permS,G.permR,G.permT)
693
sage: for V in l:
694
....: print G.permutation_action(V)
695
....:
696
()
697
(1,2,6)(3,4,5)
698
(1,3,2,4,6,5)
699
(1,4)(2,5)(3,6)
700
(1,5,6,4,2,3)
701
(1,6,2)(3,5,4)
702
(1,7,6,3,4,2)
703
704
705
"""
706
ix=len(pR)
707
S,T=SL2Z.gens()
708
R=S*T
709
Id=SL2Z([1 ,0 ,0 ,1 ])
710
cl=list(range(ix))
711
pR2=pR*pR
712
for i in range(ix):
713
cl[i]=Id
714
deb=False# True #False
715
if(deb):
716
print "T=",T,pT.to_cycles()
717
print "S=",S,pS.to_cycles()
718
print "R=",R,pR.to_cycles()
719
cycT=pT.to_cycles()
720
for cy in cycT:
721
i=pT(cy[0 ])
722
if(i<>cy[0 ]):
723
if(cl[i-1 ]==Id and i-1 <>0 ):
724
cl[i-1 ]=cl[cy[0 ]-1 ]*T
725
if(deb):
726
print "VT(",i-1 ,")=cl[",cy[0 ]-1 ,"]*T"
727
print "VT(",i-1 ,")=",cl[cy[0 ]-1 ],"*",T
728
j=pT(i)
729
while(j<>cy[0 ]):
730
if(cl[j-1 ]==Id and j-1 <>0 ):
731
cl[j-1 ]=cl[i-1 ]*T
732
if(deb):
733
print "VT(",j-1 ,")=cl[",i-1 ,"]*T"
734
print "VT(",j-1 ,")=",cl[i-1 ],"*",T
735
i=j
736
j=pT(j)
737
#for A in cl:
738
# print A
739
if(pS(i)<>i):
740
if(cl[pS(i)-1 ]==Id and pS(i)-1 <>0 ):
741
cl[pS(i)-1 ]=cl[i-1 ]*S
742
if(deb):
743
print "VS(",pS(i)-1 ,")=",cl[i-1 ]*S
744
print "cl[",pS(i)-1 ,"]=cl[",i-1 ,"]*S"
745
print "=",cl[pS(i)-1 ]
746
if(pR(i)<>i):
747
i1=pR(i)-1
748
i2=pR(pR(i))-1
749
#print "i1=",i1
750
#print "i2=",i2
751
if(cl[i1]==Id and i1<>0 ):
752
cl[pR(i)-1 ]=cl[i-1 ]*R
753
if(deb):
754
print "VR(",i1,")=",cl[i-1 ]*R
755
if(cl[i2]==Id and i2<>0 ):
756
cl[i2]=cl[i-1 ]*R*R
757
if(deb):
758
print "VRR(",i2,")=",cl[i-1 ],"R^2=",cl[i-1 ]*R*R
759
print "p(V(i2))=",self.permutation_action(cl[i2])
760
print "p(V(i-1))*p(R^2)=",self.permutation_action(cl[i-1 ])*pR2
761
# If we missed something (which is unlikely)
762
if(cl.count(Id)>1 or len(cl)<>ix):
763
#print "cl=",cl
764
raise ValueError,"Problem getting coset reps! Need %s and got %s" %(self._index,len(cl))
765
## Test the reps
766
#print "cl=",cl
767
try:
768
for A in cl:
769
for B in cl:
770
if(A<>B):
771
C=A*(B**-1 )
772
p=self.permutation_action(C)
773
if(p(1 )==1 ):
774
#print "A=",A
775
#print "B=",B
776
#print "AB^-1=",C
777
#print "p(AB^-1)=",p
778
pA=self.permutation_action(A)
779
pB=self.permutation_action(B)
780
#print " pA=",pA
781
#print "pB=", pB
782
#print "~pB=", pB.inverse()
783
pBi=self.permutation_action(B**-1 )
784
#print " B^-1=",pBi
785
raise StopIteration()
786
except StopIteration:
787
raise ArithmeticError," Could not get coset reps! \n %s * %s ^-1 in G : p(AB^-1)=%s" % (A,B,p)
788
return cl
789
790
791
def are_equivalent_cusps(self,p,c):
792
r"""
793
Check whether two cusps are equivalent with respect to self
794
795
EXAMPLES::
796
797
798
sage: G=MySubgroup(Gamma0(5))
799
sage: G.are_equivalent_cusps(Cusp(1),Cusp(infinity))
800
False
801
sage: G.are_equivalent_cusps(Cusp(1),Cusp(0))
802
True
803
804
"""
805
if(p==c):
806
return True
807
if(p.denominator()<>0 and c.denominator()<>0 ):
808
# The builtin equivalent cusp function is
809
# slow for the cusp at infinty infinity
810
return self._G.are_equivalent_cusps(p,c)
811
elif(p.denominator()<>0 and c.denominator()==0 ):
812
w = lift_to_sl2z(p.denominator(), p.numerator(), 0 )
813
n = SL2Z([w[3 ], w[1 ], w[2 ],w[0 ]])
814
#print "n=",n
815
for i in range(len(self.permT.to_cycles()[0 ])):
816
test=n*SL2Z([1 ,i,0 ,1 ])
817
#print "test=",test
818
if test in self:
819
return True
820
return False
821
elif(p.denominator()==0 and c.denominator()<>0 ):
822
w = lift_to_sl2z(c.denominator(), c.numerator(), 0 )
823
n = SL2Z([w[3 ], w[1 ], w[2 ],w[0 ]])
824
for i in range(len(self.permT.to_cycles()[0 ])):
825
if n*SL2Z([1 ,i,0 ,1 ]) in self:
826
return True
827
return False
828
else:
829
return True
830
831
832
def _get_cusps(self,l):
833
r""" Compute a list of inequivalent cusps from the list of vertices
834
835
EXAMPLES::
836
837
838
sage: P=Permutations(6)
839
sage: pS=P([2,1,4,3,6,5])
840
sage: pR=P([3,1,2,5,6,4])
841
sage: G=MySubgroup(o2=pS,o3=pR)
842
sage: l=G._get_vertices(G._coset_reps)[0];l
843
[Infinity, 0, -1/2]
844
sage: G._get_cusps(l)
845
846
"""
847
lc=list()
848
for p in l:
849
are_eq=False
850
for c in lc:
851
if(self.are_equivalent_cusps(p,c)):
852
are_eq=True
853
continue
854
if(not are_eq):
855
lc.append(Cusp(p))
856
if(lc.count(Cusp(infinity))==1 and lc.count(0 )==1 ):
857
lc.remove(Cusp(infinity))
858
lc.remove(0 )
859
lc.append(Cusp(0 ))
860
lc.append(Cusp(infinity))
861
else:
862
lc.remove(Cusp(infinity))
863
lc.append(Cusp(infinity))
864
lc.reverse()
865
return lc
866
867
def _get_vertex_maps(self):
868
r"""
869
OUTPUT:
870
-- [vc,lu]
871
vc = dictionary of vertices and corresponding cusps
872
lu = dictionary of the maps mapping the vertices to the
873
corresponding cusp representatives
874
875
EXAMPLES::
876
877
878
sage: G=MySubgroup(Gamma0(5))
879
G._get_vertex_maps()
880
[{0: 0, Infinity: Infinity}, {0: [1 0]
881
[0 1], Infinity: [1 0]
882
[0 1]}]
883
sage: P=Permutatons(6)
884
sage: pS=[2,1,4,3,6,5]
885
sage: pR=[3,1,2,5,6,4]
886
sage: G=MySubgroup(o2=pS,o3=pR)
887
sage: G._get_vertex_maps()
888
[{0: 0, Infinity: Infinity, -1/2: -1/2}, {0: [1 0]
889
[0 1], Infinity: [1 0]
890
[0 1], -1/2: [1 0]
891
[0 1]}]
892
893
"""
894
lu=dict()
895
vc=dict()
896
Id=SL2Z([1 ,0 ,0 ,1 ])
897
for p in self._vertices:
898
if(self._cusps.count(p)>0 ):
899
lu[p]=Id; vc[p]=p
900
continue
901
Vp=self._vertex_reps[p]
902
try:
903
for c in self._cusps:
904
Vc=self._vertex_reps[c]**-1
905
for j in range(1 ,self._index):
906
Tj=SL2Z([1 ,j,0 ,1 ])
907
g=Vp*Tj*Vc
908
if(g in self):
909
lu[p]=g**-1
910
vc[p]=c
911
raise StopIteration()
912
except StopIteration:
913
pass
914
# We reorder the cusps so that the first in the list is Infinity and the second is 0 (if they exist)
915
# Recall that there is always a cusp at infinity here
916
return [vc,lu]
917
918
def _get_cusp_data(self):
919
r""" Return dictionaries of cusp normalizers, stabilisers and widths
920
921
OUTPUT:
922
-- [ns,ss,ws] - list of dictionaries with cusps p as keys.
923
ns : ns[p] = A in SL2Z with A(p)=infinity (cusp normalizer)
924
ss : ss[p] = A in SL2Z s.t. A(p)=p (cusp stabilizer)
925
ws : ws[p]= (w,s).
926
w = width of p and s = 1 if p is regular and -1 if not
927
928
EXAMPLES::
929
930
931
sage: P=Permutations(6)
932
sage: pS=P([2,1,4,3,6,5])
933
sage: pR=P([3,1,2,5,6,4])
934
sage: G=MySubgroup(o2=pS,o3=pR)
935
sage: l=G._get_cusp_data()
936
sage: l[0]
937
{0: [ 0 -1]
938
[ 1 0], Infinity: [1 1]
939
[0 1], -1/2: [-1 0]
940
[ 2 -1]}
941
sage: l[1]
942
{0: [ 1 0]
943
[-4 1], Infinity: [1 1]
944
[0 1], -1/2: [ 3 1]
945
[-4 -1]}
946
sage: l[2]
947
{0: (4, 1), Infinity: (1, 1), -1/2: (1, 1)}
948
949
"""
950
try:
951
p=self._cusps[0 ]
952
except:
953
self._get_cusps(self._vertices)
954
ns=dict()
955
ss=dict()
956
ws=dict()
957
lws=self.permT.to_cycles()
958
cusp_widths=map(len,lws)
959
cusp_widths.sort()
960
# Recall that we have permutations for all groups here
961
# and that the widths can be read from self.permT
962
for p in self._cusps:
963
# could use self._G.cusp_data but is more efficient to redo all
964
if(p.denominator()==0 ):
965
wi=len(lws[0 ])
966
ns[p]= SL2Z([1 , 0 , 0 ,1 ])
967
ss[p]= SL2Z([1 , wi,0 ,1 ])
968
ws[p]=(wi,1 )
969
cusp_widths.remove(wi)
970
else:
971
w = lift_to_sl2z(p.denominator(), p.numerator(), 0 )
972
n = SL2Z([w[3 ], w[1 ], w[2 ],w[0 ]])
973
stab=None
974
wi=0
975
try:
976
for d in cusp_widths:
977
if n * SL2Z([1 ,d,0 ,1 ]) * (~n) in self:
978
stab = n * SL2Z([1 ,d,0 ,1 ]) * (~n)
979
wi = (d,1 )
980
cusp_widths.remove(d)
981
raise StopIteration()
982
elif n * SL2Z([-1 ,-d,0 ,-1 ]) * (~n) in self:
983
stab = n * SL2Z([-1 ,-d,0 ,-1 ]) * (~n)
984
wi=(d,-1 )
985
cusp_widths.remove(d)
986
raise StopIteration()
987
except StopIteration:
988
pass
989
if(wi==0 ):
990
raise ArithmeticError, "Can not find cusp stabilizer for cusp:" %p
991
ss[p]=stab
992
ws[p]=wi
993
ns[p]=n
994
return [ns,ss,ws]
995
996
def coset_reps(self):
997
r""" Returns coset reps of self
998
999
1000
EXAMPLES::
1001
1002
1003
sage: P=Permutations(6)
1004
sage: pS=P([2,1,4,3,6,5]); pS
1005
(1,2)(3,4)(5,6)
1006
sage: pR=P([3,1,2,5,6,4]); pR
1007
(1,3,2)(4,5,6)
1008
sage: G=MySubgroup(o2=pS,o3=pR)
1009
sage: G.coset_reps()
1010
[[1 0]
1011
[0 1], [1 2]
1012
[0 1], [1 1]
1013
[0 1], [1 3]
1014
[0 1], [1 5]
1015
[0 1], [1 4]
1016
[0 1], [ 4 -1]
1017
[ 1 0]]
1018
1019
"""
1020
1021
return self._coset_reps
1022
1023
def _get_perms_from_coset_reps(self):
1024
r""" Get permutations of order 2 and 3 from the coset representatives
1025
1026
EXAMPLES::
1027
1028
1029
sage: P=Permutations(6)
1030
sage: pS=P([2,1,4,3,6,5]); pS
1031
(1,2)(3,4)(5,6)
1032
sage: pR=P([3,1,2,5,6,4]); pR
1033
(1,3,2)(4,5,6)
1034
sage: G=MySubgroup(o2=pS,o3=pR)
1035
sage: G._get_perms_from_coset_reps()
1036
[(1,2)(3,4)(5,6), (1,3,2)(4,5,6)]
1037
sage: G=MySubgroup(Gamma0(6))
1038
sage: p=G._get_perms_from_coset_reps(); p[0]; p[1]
1039
(1,2)(3,4)(5,8)(6,9)(7,10)(11,12)
1040
(1,3,2)(4,5,9)(6,11,10)(7,12,8)
1041
1042
1043
"""
1044
l=self._coset_reps
1045
li=list()
1046
n=len(l)
1047
G=self._G
1048
ps=range(1 ,self._index+1 )
1049
pr=range(1 ,self._index+1 )
1050
S,T=SL2Z.gens()
1051
R=S*T
1052
for i in range(n):
1053
li.append(l[i]**-1 )
1054
#used_inS=dict()
1055
#used_inR=dict()
1056
#for i in range(len(l)):
1057
## used_inS[i]=False
1058
# used_inR[i]=False
1059
ixr=range(n)
1060
ixs=range(n)
1061
for i in range(n):
1062
[a,b,c,d]=l[i]
1063
VS=SL2Z([b,-a,d,-c]) # Vi*S
1064
VR=SL2Z([b,b-a,d,d-c]) # Vi*R=Vi*S*T
1065
for j in ixr:
1066
Vji=li[j] # Vj^-1
1067
if(VR*Vji in G):
1068
pr[i]=j+1
1069
ixr.remove(j)
1070
break
1071
for j in ixs:
1072
Vji=li[j]
1073
if(VS*Vji in G):
1074
ps[i]=j+1
1075
ixs.remove(j)
1076
break
1077
return [self._P(ps),self._P(pr)]
1078
1079
# Now to public routines
1080
1081
def coset_rep(self,A):
1082
r"""
1083
Indata: A in PSL(2,Z)
1084
Returns the coset representative of A in
1085
PSL(2,Z)/self.G
1086
1087
EXAMPLES::
1088
1089
1090
sage: G=MySubgroup(Gamma0(4))
1091
sage: A=SL2Z([9,4,-16,-7])
1092
sage: G.coset_rep(A)
1093
[1 0]
1094
[0 1]
1095
sage: A=SL2Z([3,11,-26,-95])
1096
sage: G.coset_rep(A)
1097
[-1 0]
1098
[ 2 -1]
1099
sage: A=SL2Z([71,73,35,36])
1100
sage: G.coset_rep(A)
1101
[ 0 -1]
1102
[ 1 0]
1103
1104
1105
"""
1106
for V in (self._coset_reps):
1107
if( (A*V**-1 ) in self):
1108
return V
1109
raise ArithmeticError,"Did not find coset rep. for A=%s" %(A)
1110
1111
def pullback(self,x_in,y_in,prec=201 ):
1112
r""" Find the pullback of a point in H to the fundamental domain of self
1113
INPUT:
1114
1115
- ''x_in,y_in'' -- x_in+I*y_in is in the upper half-plane
1116
- ''prec'' -- (default 201) precision in bits
1117
1118
OUTPUT:
1119
1120
- [xpb,ypb,B] -- xpb+I*ypb=B(x_in+I*y_in) with B in self
1121
xpb and ypb are complex numbers with precision prec
1122
EXAMPLES::
1123
1124
1125
sage: P=Permutatons(6)
1126
sage: pS=[2,1,4,3,6,5]
1127
sage: pR=[3,1,2,5,6,4]
1128
sage: G=MySubgroup(o2=pS,o3=pR)
1129
sage: [x,y,B]=G.pullback(0.2,0.5,53); x,y;B
1130
(-0.237623762376238, 0.123762376237624)
1131
[-1 0]
1132
[ 4 -1]
1133
sage: (B**-1).acton(x+I*y)
1134
0.200000000000000 + 0.500000000000000*I
1135
1136
1137
"""
1138
x=deepcopy(x_in); y=deepcopy(y_in)
1139
A=pullback_to_psl2z_mat(RR(x),RR(y))
1140
A=SL2Z(A)
1141
try:
1142
for V in self._coset_reps:
1143
B=V*A
1144
if(B in self):
1145
raise StopIteration
1146
except StopIteration:
1147
pass
1148
else:
1149
raise ArithmeticError,"Did not find coset rep. for A=%s" % A
1150
#B=V*A
1151
z=CC.to_prec(prec)(x_in)+I*CC.to_prec(prec)(y_in)
1152
[a,b,c,d]=B
1153
zpb=CC.to_prec(prec)(a*z + b)/CC.to_prec(prec)(c*z + d)
1154
xpb=zpb.real();ypb=zpb.imag()
1155
#print "A=",A
1156
return [xpb,ypb,B]
1157
1158
def is_congruence(self):
1159
r""" Is self a congruence subgroup or not?
1160
1161
EXAMPLES::
1162
1163
1164
sage: P=Permutations(6)
1165
sage: pR=P([3,1,2,5,6,4]); pR
1166
(1,3,2)(4,5,6)
1167
sage: pS=P([2,1,4,3,6,5]); pS
1168
(1,2)(3,4)(5,6)
1169
sage: G=MySubgroup(o2=pS,o3=pR)
1170
sage: G.is_congruence()
1171
True
1172
sage: P=Permutations(7)
1173
sage: pS=P([1,3,2,5,4,7,6]); pS
1174
(2,3)(4,5)(6,7)
1175
sage: pR=P([3,2,4,1,6,7,5]); pR
1176
(1,3,4)(5,6,7)
1177
sage: G=MySubgroup(o2=pS,o3=pR)
1178
sage: G.is_congruence()
1179
False
1180
1181
"""
1182
try:
1183
return self._is_congruence
1184
except:
1185
self._is_congruence=self._G.is_congruence()
1186
return self._is_congruence
1187
1188
def generalised_level(self):
1189
r""" Generalized level of self
1190
1191
EXAMPLES::
1192
1193
1194
sage: P=Permutations(6)
1195
sage: pR=P([3,1,2,5,6,4]); pR
1196
(1,3,2)(4,5,6)
1197
sage: pS=P([2,1,4,3,6,5]); pS
1198
(1,2)(3,4)(5,6)
1199
sage: G=MySubgroup(o2=pS,o3=pR)
1200
sage: G.generalised_level()
1201
4
1202
sage: P=Permutations(7)
1203
sage: pS=P([1,3,2,5,4,7,6]); pS
1204
(2,3)(4,5)(6,7)
1205
sage: pR=P([3,2,4,1,6,7,5]); pR
1206
(1,3,4)(5,6,7)
1207
sage: G=MySubgroup(o2=pS,o3=pR)
1208
sage: G.generalised_level()
1209
6
1210
"""
1211
try:
1212
if(self._generalised_level<>None):
1213
return self._generalised_level
1214
else:
1215
raise ArithmeticError, "Could not compute generalised level of %s" %(self)
1216
except:
1217
self._generalised_level=self._G.generalised_level()
1218
return self._generalised_level
1219
1220
1221
def level(self):
1222
r""" Level of self
1223
1224
EXAMPLES::
1225
1226
1227
sage: G=MySubgroup(Gamma0(5));
1228
sage: G.level()
1229
5
1230
1231
"""
1232
if(self._is_congruence):
1233
return self._level
1234
else:
1235
raise TypeError,"Group is not a congruence group! Use G.generalised_level() instead!"
1236
1237
1238
def gens(self):
1239
r""" Generators of self
1240
1241
1242
EXAMPLES::
1243
1244
1245
sage: G=MySubgroup(Gamma0(5));
1246
sage: G.gens()
1247
([1 1]
1248
[0 1], [-1 0]
1249
[ 0 -1], [ 1 -1]
1250
[ 0 1], [1 0]
1251
[5 1], [1 1]
1252
[0 1], [-2 -1]
1253
[ 5 2], [-3 -1]
1254
[10 3], [-1 0]
1255
[ 5 -1], [ 1 0]
1256
[-5 1])
1257
sage: G3=MySubgroup(o2=Permutations(3)([1,2,3]),o3=Permutations(3)([2,3,1]))
1258
sage: G3.gens()
1259
([1 3]
1260
[0 1], [ 0 -1]
1261
[ 1 0], [ 1 -2]
1262
[ 1 -1], [ 2 -5]
1263
[ 1 -2])
1264
1265
"""
1266
return self._G.gens()
1267
1268
1269
def closest_vertex(self,x,y):
1270
r"""
1271
The closest vertex to the point z=x+iy in the following sense:
1272
Let sigma_j be the normalized cusp normalizer of the vertex p_j,
1273
i.e. sigma_j^-1(p_j)=Infinity and sigma_j*S_j*sigma_j^-1=T, where
1274
S_j is the generator of the stabiliser of p_j
1275
1276
The closest vertex is then the one for which Im(sigma_j^-1(p_j))
1277
is maximal.
1278
1279
INPUT:
1280
1281
- ''x,y'' -- x+iy in the upper half-plane
1282
1283
OUTPUT:
1284
1285
- ''v'' -- the closest vertex to x+iy
1286
1287
1288
EXAMPLES::
1289
1290
1291
sage: G=MySubgroup(Gamma0(5))
1292
sage: G.closest_vertex(-0.4,0.2)
1293
Infinity
1294
sage: G.closest_vertex(-0.1,0.1)
1295
0
1296
1297
"""
1298
ymax=0
1299
vmax=None
1300
for v in self._vertices:
1301
c=self._cusp_representative[v]
1302
U=self._vertex_map[v]
1303
N=self._cusp_normalizer[c]
1304
w=self._cusp_width[c][0 ]
1305
A=(N**-1 *U)
1306
[a,b,c,d]=A
1307
den=(c*x+d)**2 +(c*y)**2
1308
y2=y/den/w # We only want the y coordinate
1309
#print "v=",v
1310
#print "c=",c
1311
#print "A=",A
1312
#print "y=",y
1313
if(y2>ymax):
1314
ymax=y2
1315
vmax=v
1316
if(vmax==None):
1317
raise ArithmeticError," Did not find closest vertex to z=%s+i%s," %(x,y)
1318
return vmax
1319
1320
1321
1322
1323
1324
## def block_systems(self):
1325
## r"""
1326
## Return a list of possible Block systems and overgroups for the group G.
1327
## INDATA: G = subgroup of the modular group
1328
## OUTDATA res['blocksystems']= Block systems
1329
## res['overgroups'] = Overgroups corresponding to the blocks
1330
## in the block systems
1331
1332
1333
## EXAMPLES::
1334
1335
1336
## """
1337
## return get_block_systems(self.permS,self.permR,False)
1338
1339
def dimension_cuspforms(self,k):
1340
r"""
1341
Returns the dimension of the space of cuspforms on G of weight k
1342
where k is an even integer
1343
1344
EXAMPLES::
1345
1346
1347
sage: G=MySubgroup(Gamma0(4))
1348
sage: G.dimension_cuspforms(12)
1349
4
1350
sage: P=Permutations(7)
1351
sage: pR=P([3,2,4,1,6,7,5]); pR
1352
(1,3,4)(5,6,7)
1353
sage: pS=P([1,3,2,5,4,7,6]); pS
1354
(2,3)(4,5)(6,7)
1355
sage: G.dimension_cuspforms(4)
1356
1
1357
1358
1359
"""
1360
kk=Integer(k)
1361
if(is_odd(kk)):
1362
raise ValueError, "Use only for even weight k! not k=" %(kk)
1363
if(kk<2 ):
1364
dim=0
1365
elif(kk==2 ):
1366
dim=self._genus
1367
elif(kk>=_4 ):
1368
dim=Integer(kk-1 )*(self._genus-1 )+self._nu2*int(floor(kk/_sage_const_4 ))+self._nu3*int(floor(kk/_sage_const_3 ))+(kk/_sage_const_2 - _sage_const_1)*self._ncusps
1369
return dim
1370
1371
def dimension_modularforms(self,k):
1372
r"""
1373
Returns the dimension of the space of modular forms on G of weight k
1374
where k is an even integer
1375
1376
EXAMPLES::
1377
1378
1379
sage: P=Permutations(7)
1380
sage: pR=P([3,2,4,1,6,7,5]); pR
1381
(1,3,4)(5,6,7)
1382
sage: pS=P([1,3,2,5,4,7,6]); pS
1383
(2,3)(4,5)(6,7)
1384
sage: G.dimension_modularforms(4)
1385
3
1386
"""
1387
kk=Integer(k)
1388
if(is_odd(kk)):
1389
raise ValueError, "Use only for even weight k! not k=" %(kk)
1390
if(k==0 ):
1391
dim=1
1392
elif(k<2 ):
1393
dim=0
1394
else:
1395
dim=(kk-_sage_const_1 )*(self._genus-_sage_const_1 )+self._nu2()*int(floor(kk/_sage_const_4 ))+self._nu3*int(floor(kk/_sage_const_3 ))+kk/_sage_const_2 *self._ncusps()
1396
return dim
1397
1398
### Overloaded operators
1399
def __contains__(self,A):
1400
r"""
1401
Is A an element of self (this is an ineffective implementation if self._G is a permutation group)
1402
1403
EXAMPLES::
1404
1405
1406
sage: G=MySubgroup(Gamma0(5))
1407
sage: A=SL2Z([-69,-25,-80,-29])
1408
sage: G.__contains__(A)
1409
True
1410
sage: A in G
1411
True
1412
sage: P=Permutations(7)
1413
sage: pR=P([3,2,4,1,6,7,5]); pR
1414
(1,3,4)(5,6,7)
1415
sage: pS=P([1,3,2,5,4,7,6]); pS
1416
(2,3)(4,5)(6,7)
1417
sage: S,T=SL2Z.gens(); R=S*T
1418
sage: A=S*T^4*S
1419
sage: A in G
1420
False
1421
sage: A=S*T^6*S
1422
sage: A in G
1423
True
1424
1425
"""
1426
p=self.permutation_action(A)
1427
if(p(1 )==1 ):
1428
return True
1429
else:
1430
return False
1431
1432
def cusps(self):
1433
r"""
1434
Returns the cusps of self
1435
1436
EXAMPLES::
1437
1438
1439
sage: G=MySubgroup(Gamma0(5))
1440
sage: G.cusps()
1441
[Infinity, 0]
1442
1443
"""
1444
return self._cusps
1445
1446
def cusp_width(self,cusp):
1447
r"""
1448
Returns the cusp width of cusp
1449
1450
EXAMPLES::
1451
1452
1453
sage: G=MySubgroup(Gamma0(4))
1454
sage: G.cusp_data(Cusp(1/2))
1455
([-1 1]
1456
[-4 3], 1, 1)
1457
sage: G.cusp_width(Cusp(-1/2))
1458
1
1459
sage: G.cusp_width(Cusp(1/2))
1460
1
1461
sage: G.cusp_width(Cusp(0))
1462
4
1463
"""
1464
try:
1465
return self._cusp_width[cusp][0 ]
1466
except KeyError:
1467
(A,d,e)=self.cusp_data(cusp)
1468
return d
1469
1470
def cusp_data(self,c):
1471
r""":
1472
Returns cuspdata in the same format as for the generic Arithmetic subgroup
1473
1474
EXAMPLES::
1475
1476
1477
sage: G=MySubgroup(Gamma0(4))
1478
sage: G.cusp_data(Cusp(1/2))
1479
([-1 1]
1480
[-4 3], 1, 1)
1481
sage: G.cusp_data(Cusp(-1/2))
1482
([ 3 1]
1483
[-4 -1], 1, 1)
1484
sage: G.cusp_data(Cusp(0))
1485
([ 0 -1]
1486
[ 1 0], 4, 1)
1487
1488
"""
1489
try:
1490
# if we give a cusp already in the list we return stored values
1491
w=self.cusp_normalizer(c)
1492
d=self._cusp_width[c][0 ]
1493
e=self._cusp_width[c][1 ]
1494
g=w * SL2Z([e,d*e,0 ,e]) * (~w)
1495
return (self.cusp_normalizer(c),d*e,1)
1496
except:
1497
w = lift_to_sl2z(c.denominator(), c.numerator(), 0 )
1498
g = SL2Z([w[3 ], w[1 ], w[2 ],w[0 ]])
1499
for d in xrange(1 ,1 +self.index()):
1500
if g * SL2Z([1 ,d,0 ,1 ]) * (~g) in self:
1501
return (g * SL2Z([1 ,d,0 ,1 ]) * (~g), d, 1 )
1502
elif g * SL2Z([-1 ,-d,0 ,-1 ]) * (~g) in self:
1503
return (g * SL2Z([-1 ,-d,0 ,-1 ]) * (~g), d, -1 )
1504
raise ArithmeticError, "Can' t get here!"
1505
#
1506
1507
def cusp_normalizer(self,cusp):
1508
r"""
1509
Return the cuspnormalizer of cusp
1510
1511
EXAMPLES::
1512
1513
1514
sage: P=Permutations(6)
1515
sage: pR=P([3,1,2,5,6,4]); pR
1516
(1,3,2)(4,5,6)
1517
sage: pS=P([2,1,4,3,6,5]); pS
1518
(1,2)(3,4)(5,6)
1519
sage: G=MySubgroup(o2=pS,o3=pR)
1520
sage: G.cusps()
1521
[Infinity, 0, -1/2]
1522
sage: G.cusp_normalizer(Cusp(0))
1523
[ 0 -1]
1524
[ 1 0]
1525
sage: G.cusp_normalizer(Cusp(-1/2))
1526
[-1 0]
1527
[ 2 -1]
1528
sage: G.cusp_normalizer(Cusp(Infinity))
1529
[1 0]
1530
[0 1]
1531
1532
1533
"""
1534
1535
try:
1536
return self._cusp_normalizer[cusp]
1537
except KeyError:
1538
try:
1539
w = lift_to_sl2z(c.denominator(), c.numerator(), 0 )
1540
g = SL2Z([w[3 ], w[1 ], w[2 ],w[0 ]])
1541
self._cusp_normalizer[cusp]=g
1542
return g
1543
except:
1544
raise ValueError,"Supply a cusp as input! Got:%s" %cusp
1545
1546
def draw_fundamental_domain(self,model="H",axes=None,filename=None,**kwds):
1547
r""" Draw fundamental domain
1548
INPUT:
1549
- ''model'' -- (default ''H'')
1550
= ''H'' -- Upper halfplane
1551
= ''D'' -- Disk model
1552
- ''filename''-- filename to print to
1553
- ''**kwds''-- additional arguments to matplotlib
1554
- ''axes'' -- set geometry of output
1555
=[x0,x1,y0,y1] -- restrict figure to [x0,x1]x[y0,y1]
1556
1557
EXAMPLES::
1558
1559
sage: G=MySubgroup(Gamma0(3))
1560
sage: G.draw_fundamental_domain()
1561
1562
"""
1563
from matplotlib.backends.backend_agg import FigureCanvasAgg
1564
if(model=="D"):
1565
g=self._draw_funddom_d(format,I)
1566
else:
1567
g=self._draw_funddom(format)
1568
if(axes<>None):
1569
[x0,x1,y0,y1]=axes
1570
elif(model=="D"):
1571
x0=-1 ; x1=1 ; y0=-1 ; y1=1
1572
else:
1573
# find the width of the fundamental domain
1574
w=0 #self.cusp_width(Cusp(Infinity))
1575
wmin=0 ; wmax=1
1576
for V in self._coset_reps:
1577
if(V[1 ,0 ]==0 and V[0 ,0 ]==1 ):
1578
if(V[0 ,1 ]>wmax):
1579
wmax=V[0 ,1 ]
1580
if(V[0 ,1 ]<wmin):
1581
wmin=V[0 ,1 ]
1582
#print "wmin,wmax=",wmin,wmax
1583
#x0=-1; x1=1; y0=-0.2; y1=1.5
1584
x0=wmin-1 ; x1=wmax+1 ; y0=-_sage_const_0p2 ; y1=_sage_const_1p5
1585
g.set_aspect_ratio(1 )
1586
g.set_axes_range(x0,x1,y0,y1)
1587
if(filename<>None):
1588
fig = g.matplotlib()
1589
fig.set_canvas(FigureCanvasAgg(fig))
1590
axes = fig.get_axes()[0 ]
1591
axes.minorticks_off()
1592
axes.set_yticks([])
1593
fig.savefig(filename,**kwds)
1594
else:
1595
return g
1596
# g.show(figsize=[5,5])
1597
1598
def _draw_funddom(self,format="S"):
1599
r""" Draw a fundamental domain for G.
1600
1601
INPUT:
1602
1603
- ``format`` -- (default 'Disp') How to present the f.d.
1604
- ``S`` -- Display directly on the screen
1605
1606
EXAMPLES::
1607
1608
1609
sage: G=MySubgroup(Gamma0(3))
1610
sage: G._draw_funddom()
1611
1612
"""
1613
pi=RR.pi()
1614
from sage.plot.plot import (Graphics,line)
1615
from sage.functions.trig import (cos,sin)
1616
g=Graphics()
1617
x1=-_sage_const_0p5 ; y1=sqrt(_sage_const_3 )/_sage_const_2
1618
x2=_sage_const_0p5 ; y2=sqrt(_sage_const_3 )/_sage_const_2
1619
xmax=_sage_const_20
1620
l1 = line([[x1,y1],[x1,xmax]])
1621
l2 = line([[x2,y2],[x2,xmax]])
1622
l3 = line([[x2,xmax],[x1,xmax]]) # This is added to make a closed contour
1623
c0=_circ_arc(pi/_sage_const_3p0 ,_sage_const_2p0 *pi/_sage_const_3p0 ,0 ,1 ,_sage_const_100 )
1624
tri=c0+l1+l3+l2
1625
g+=tri
1626
for A in self._coset_reps:
1627
[a,b,c,d]=A
1628
if(a==1 and b==0 and c==0 and d==1 ):
1629
continue
1630
if(a<0 ):
1631
a=-a; b=-b; c=-c; d=-1
1632
if(c==0 ): # then this is easier
1633
L0 = [[cos(pi/_sage_const_3 *i/_sage_const_100 )+b,sin(pi/_sage_const_3 *i/_sage_const_100 )] for i in range(_sage_const_100 ,_sage_const_201 )]
1634
L1 = [[x1+b,y1],[x1+b,xmax]]
1635
L2 = [[x2+b,y2],[x2+b,xmax]]
1636
L3 = [[x2+b,xmax],[x1+b,xmax]]
1637
c0=line(L0); l1=line(L1); l2=line(L2); l3=line(L3)
1638
tri=c0+l1+l3+l2
1639
g+=tri
1640
else:
1641
den=(c*x1+d)**_sage_const_2 +c**_sage_const_2 *y1**_sage_const_2
1642
x1_t=(a*c*(x1**_sage_const_2 +y1**_sage_const_2 )+(a*d+b*c)*x1+b*d)/den
1643
y1_t=y1/den
1644
den=(c*x2+d)**_sage_const_2 +c**_sage_const_2 *y2**_sage_const_2
1645
x2_t=(a*c*(x2**_sage_const_2 +y2**_sage_const_2 )+(a*d+b*c)*x2+b*d)/den
1646
y2_t=y2/den
1647
inf_t=a/c
1648
#print "A=",A
1649
#print "arg1=",x1_t,y1_t,x2_t,y2_t
1650
c0=_geodesic_between_two_points(x1_t,y1_t,x2_t,y2_t)
1651
#print "arg1=",x1_t,y1_t,inf_t
1652
c1=_geodesic_between_two_points(x1_t,y1_t,inf_t,_sage_const_0p0 )
1653
#print "arg1=",x2_t,y2_t,inf_t
1654
c2=_geodesic_between_two_points(x2_t,y2_t,inf_t,_sage_const_0p0 )
1655
tri=c0+c1+c2
1656
g+=tri
1657
return g
1658
1659
1660
def _draw_funddom_d(self,format="MP",z0=I):
1661
r""" Draw a fundamental domain for self in the circle model
1662
INPUT:
1663
- ''format'' -- (default 'Disp') How to present the f.d.
1664
= 'S' -- Display directly on the screen
1665
- z0 -- (default I) the upper-half plane is mapped to the disk by z-->(z-z0)/(z-z0.conjugate())
1666
EXAMPLES::
1667
1668
1669
sage: G=MySubgroup(Gamma0(3))
1670
sage: G._draw_funddom_d()
1671
1672
"""
1673
# The fundamental domain consists of copies of the standard fundamental domain
1674
pi=RR.pi()
1675
from sage.plot.plot import (Graphics,line)
1676
g=Graphics()
1677
bdcirc=_circ_arc(0 ,_sage_const_2 *pi,0 ,1 ,1000 )
1678
g+=bdcirc
1679
# Corners
1680
x1=-_sage_const_0p5 ; y1=sqrt(_sage_const_3 )/_sage_const_2
1681
x2=_sage_const_0p5 ; y2=sqrt(_sage_const_3 )/_sage_const_2
1682
z_inf=1
1683
l1 = _geodesic_between_two_points_d(x1,y1,x1,infinity)
1684
l2 = _geodesic_between_two_points_d(x2,y2,x2,infinity)
1685
c0 = _geodesic_between_two_points_d(x1,y1,x2,y2)
1686
tri=c0+l1+l2
1687
g+=tri
1688
for A in self._coset_reps:
1689
[a,b,c,d]=A
1690
if(a==1 and b==0 and c==0 and d==1 ):
1691
continue
1692
if(a<0 ):
1693
a=-a; b=-b; c=-c; d=-1
1694
if(c==0 ): # then this is easier
1695
l1 = _geodesic_between_two_points_d(x1+b,y1,x1+b,infinity)
1696
l2 = _geodesic_between_two_points_d(x2+b,y2,x2+b,infinity)
1697
c0 = _geodesic_between_two_points_d(x1+b,y1,x2+b,y2)
1698
# c0=line(L0); l1=line(L1); l2=line(L2); l3=line(L3)
1699
tri=c0+l1+l2
1700
g+=tri
1701
else:
1702
den=(c*x1+d)**_sage_const_2 +c**_sage_const_2 *y1**_sage_const_2
1703
x1_t=(a*c*(x1**_sage_const_2 +y1**_sage_const_2 )+(a*d+b*c)*x1+b*d)/den
1704
y1_t=y1/den
1705
den=(c*x2+d)**_sage_const_2 +c**_sage_const_2 *y2**_sage_const_2
1706
x2_t=(a*c*(x2**_sage_const_2 +y2**_sage_const_2 )+(a*d+b*c)*x2+b*d)/den
1707
y2_t=y2/den
1708
inf_t=a/c
1709
c0=_geodesic_between_two_points_d(x1_t,y1_t,x2_t,y2_t)
1710
c1=_geodesic_between_two_points_d(x1_t,y1_t,inf_t,_sage_const_0p0 )
1711
c2=_geodesic_between_two_points_d(x2_t,y2_t,inf_t,_sage_const_0p0 )
1712
tri=c0+c1+c2
1713
g+=tri
1714
g.xmax(1 )
1715
g.ymax(1 )
1716
g.xmin(-1 )
1717
g.ymin(-1 )
1718
g.set_aspect_ratio(1 )
1719
return g
1720
1721
def minimal_height(self):
1722
r""" Computes the minimal (invariant) height of the fundamental region of self.
1723
1724
EXAMPLES::
1725
1726
1727
sage: G=MySubgroup(Gamma0(6))
1728
sage: G.minimal_height()
1729
0.144337567297406
1730
sage: P=Permutations(6)
1731
sage: pR=P([3,1,2,5,6,4])
1732
sage: pS=P([2,1,4,3,6,5])
1733
sage: G=MySubgroup(o2=pS,o3=pR)
1734
sage: G.minimal_height()
1735
0.216506350946110
1736
1737
1738
"""
1739
if self._is_congruence:
1740
l=self.level()
1741
if(self._G == Gamma0(l)):
1742
return RR(sqrt(3.0))/RR(2*l)
1743
# For all other groups we have have to locate the largest width
1744
maxw=0
1745
for c in self.cusps():
1746
l=self.cusp_width(Cusp(c))
1747
if(l>maxw):
1748
maxw=l
1749
return RR(sqrt(3.0))/RR(2*maxw)
1750
1751
1752
#### Methods not dependent explicitly on the group
1753
def _geodesic_between_two_points(x1,y1,x2,y2):
1754
r""" Geodesic path between two points hyperbolic upper half-plane
1755
1756
INPUTS:
1757
1758
- ''(x1,y1)'' -- starting point (0<y1<=infinity)
1759
- ''(x2,y2)'' -- ending point (0<y2<=infinity)
1760
- ''z0'' -- (default I) the point in the upper corresponding
1761
to the point 0 in the disc. I.e. the transform is
1762
w -> (z-I)/(z+I)
1763
OUTPUT:
1764
1765
- ''ca'' -- a polygonal approximation of a circular arc centered
1766
at c and radius r, starting at t0 and ending at t1
1767
1768
1769
EXAMPLES::
1770
1771
1772
sage: l=_geodesic_between_two_points(0.1,0.2,0.0,0.5)
1773
1774
"""
1775
pi=RR.pi()
1776
from sage.plot.plot import line
1777
from sage.functions.trig import arcsin
1778
if(x1==x2):
1779
# The line segment [x=x1, y0<= y <= y1]
1780
return line([[x1,y1],[x2,y2]]) #[0,0,x0,infinity]
1781
c=(y1**_sage_const_2 -y2**_sage_const_2 +x1**_sage_const_2 -x2**_sage_const_2 )/(_sage_const_2 *(x1-x2))
1782
r=sqrt(y1**_sage_const_2 +(x1-c)**_sage_const_2 )
1783
r1=y1/r; r2=y2/r
1784
if(abs(r1-1 )<_sage_const_1En12 ):
1785
r1=_sage_const_1p0
1786
elif(abs(r2+_sage_const_1 )<_sage_const_1En12 ):
1787
r2=-_sage_const_1p0
1788
if(abs(r2-_sage_const_1 )<_sage_const_1En12 ):
1789
r2=_sage_const_1p0
1790
elif(abs(r2+1 )<_sage_const_1En12 ):
1791
r2=-_sage_const_1p0
1792
if(x1>=c):
1793
t1 = arcsin(r1)
1794
else:
1795
t1 = pi-arcsin(r1)
1796
if(x2>=c):
1797
t2 = arcsin(r2)
1798
else:
1799
t2 = pi-arcsin(r2)
1800
tmid = (t1+t2)*_sage_const_0p5
1801
a0=min(t1,t2)
1802
a1=max(t1,t2)
1803
##print "c,r=",c,r
1804
#print "t1,t2=",t1,t2
1805
return _circ_arc(t1,t2,c,r)
1806
1807
def _geodesic_between_two_points_d(x1,y1,x2,y2,z0=I):
1808
r""" Geodesic path between two points represented in the unit disc
1809
by the map w = (z-I)/(z+I)
1810
INPUTS:
1811
- ''(x1,y1)'' -- starting point (0<y1<=infinity)
1812
- ''(x2,y2)'' -- ending point (0<y2<=infinity)
1813
- ''z0'' -- (default I) the point in the upper corresponding
1814
to the point 0 in the disc. I.e. the transform is
1815
w -> (z-I)/(z+I)
1816
OUTPUT:
1817
- ''ca'' -- a polygonal approximation of a circular arc centered
1818
at c and radius r, starting at t0 and ending at t1
1819
1820
1821
EXAMPLES::
1822
1823
sage: l=_geodesic_between_two_points_d(0.1,0.2,0.0,0.5)
1824
1825
"""
1826
pi=RR.pi()
1827
from sage.plot.plot import line
1828
from sage.functions.trig import (cos,sin)
1829
# First compute the points
1830
if(y1<0 or y2<0 ):
1831
raise ValueError,"Need points in the upper half-plane! Got y1=%s, y2=%s" %(y1,y2)
1832
if(y1==infinity):
1833
P1=CC(1 )
1834
else:
1835
P1=CC((x1+I*y1-z0)/(x1+I*y1-z0.conjugate()))
1836
if(y2==infinity):
1837
P2=CC(1 )
1838
else:
1839
P2=CC((x2+I*y2-z0)/(x2+I*y2-z0.conjugate()))
1840
# First find the endpoints of the completed geodesic in D
1841
if(x1==x2):
1842
a=CC((x1-z0)/(x1-z0.conjugate()))
1843
b=CC(1 )
1844
else:
1845
c=(y1**2 -y2**2 +x1**2 -x2**2 )/(2 *(x1-x2))
1846
r=sqrt(y1**2 +(x1-c)**2 )
1847
a=c-r
1848
b=c+r
1849
a=CC((a-z0)/(a-z0.conjugate()))
1850
b=CC((b-z0)/(b-z0.conjugate()))
1851
if( abs(a+b) < _sage_const_1En10 ): # On a diagonal
1852
return line([[P1.real(),P1.imag()],[P2.real(),P2.imag()]])
1853
th_a=a.argument()
1854
th_b=b.argument()
1855
# Compute the center of the circle in the disc model
1856
if( min(abs(b-1 ),abs(b+1 ))<_sage_const_1En10 and min(abs(a-1 ),abs(a+1 ))>_sage_const_1En10 ):
1857
c=b+I*(1 -b*cos(th_a))/sin(th_a)
1858
elif( min(abs(b-1 ),abs(b+1 ))>_sage_const_1En10 and min(abs(a-1 ),abs(a+1 ))<_sage_const_1En10 ):
1859
c=a+I*(1 -a*cos(th_b))/sin(th_b)
1860
else:
1861
cx=(sin(th_b)-sin(th_a))/sin(th_b-th_a)
1862
c=cx+I*(1 -cx*cos(th_b))/sin(th_b)
1863
# First find the endpoints of the completed geodesic
1864
r=abs(c-a)
1865
t1=CC(P1-c).argument()
1866
t2=CC(P2-c).argument()
1867
#print "t1,t2=",t1,t2
1868
return _circ_arc(t1,t2,c,r)
1869
1870
1871
def _circ_arc(t0,t1,c,r,num_pts=_sage_const_100 ):
1872
r""" Circular arc
1873
INPUTS:
1874
- ''t0'' -- starting parameter
1875
- ''t1'' -- ending parameter
1876
- ''c'' -- center point of the circle
1877
- ''r'' -- radius of circle
1878
- ''num_pts'' -- (default 100) number of points on polygon
1879
OUTPUT:
1880
- ''ca'' -- a polygonal approximation of a circular arc centered
1881
at c and radius r, starting at t0 and ending at t1
1882
1883
1884
EXAMPLES::
1885
1886
sage: ca=_circ_arc(0.1,0.2,0.0,1.0,100)
1887
1888
"""
1889
from sage.plot.plot import line
1890
from sage.functions.trig import (cos,sin)
1891
t00=t0; t11=t1
1892
## To make sure the line is correct we reduce all arguments to the same branch,
1893
## e.g. [0,2pi]
1894
pi=RR.pi()
1895
while(t00<0.0):
1896
t00=t00+2.0*pi
1897
while(t11<0):
1898
t11=t11+2.0*pi
1899
while(t00>2*pi):
1900
t00=t00-2.0*pi
1901
while(t11>2*pi):
1902
t11=t11-2.0*pi
1903
1904
xc=CC(c).real()
1905
yc=CC(c).imag()
1906
L0 = [[r*cos(t00+i*(t11-t00)/num_pts)+xc,r*sin(t00+i*(t11-t00)/num_pts)+yc] for i in range(0 ,num_pts)]
1907
ca=line(L0)
1908
return ca
1909
1910
1911
def factor_matrix_in_sl2z_in_S_and_T(A_in):
1912
r"""
1913
Factor A in SL2Z in generators S=[[0,-1],[1,0]] and T=[[1,1],[0,1]]
1914
INPUT:
1915
- ''A_in'' -- Matrix in SL2Z
1916
OUTPUT:
1917
- ''[[a0,a1,...,an],ep] with a0 in ZZ, ai in ZZ \ {0,\pm1}, ep=1,-1
1918
Determined by:
1919
A=ep*(T^a0*S*T^a1*S*...*S*T^an)
1920
The algorithm uses the Nearest integer Continued Fraction expansion. Note that there due to relations in SL2Z the sequence a_j is not unique
1921
(see example below)
1922
1923
EXAMPLES::
1924
sage: nearest_integer_continued_fraction(0.5);cf
1925
[1, 2]
1926
sage: A=ncf_to_matrix_in_SL2Z(cf); A
1927
[1 1]
1928
[1 2]
1929
sage: factor_matrix_in_sl2z_in_S_and_T(A)
1930
[[1, 2], 1]
1931
1932
An example where we do not get back the same sequence::
1933
1934
sage: cf=nearest_integer_continued_fraction(pi.n(100),nmax=10);cf
1935
[3, -7, 16, 294, 3, 4, 5, 15, -3, -2, 2]
1936
sage: A=ncf_to_matrix_in_SL2Z(cf); A
1937
[ -411557987 -1068966896]
1938
[ -131002976 -340262731]
1939
sage: factor_matrix_in_sl2z_in_S_and_T(A)
1940
[[3, -7, 16, 294, 3, 4, 5, 15, -2, 2, 3], -1]
1941
1942
"""
1943
if A_in not in SL2Z:
1944
raise TypeError, "%s must be an element of SL2Z" %A_in
1945
S,T=SL2Z.gens()
1946
# If A is not member of SL(2,Z) but a plain matrix
1947
# we cast it to a member of SL2Z
1948
A = SL2Z(A_in)
1949
if(A.matrix()[1 ,0 ] == 0 ):
1950
return [[A.matrix()[0 ,1 ]],1 ]
1951
x = Rational(A.matrix()[0 ,0 ] / A.matrix()[1 ,0 ])
1952
cf = nearest_integer_continued_fraction(x)
1953
B = ncf_to_matrix_in_SL2Z(cf)
1954
# we know that A(oo) = x = B*S (oo) and that A and BS differ at most by a translation T^j
1955
Tj = S**-1 * B**-1 * A
1956
sgn=1
1957
if(Tj.matrix()[0 ,0 ]<0 ):
1958
j=-Tj.matrix()[0 ,1 ]
1959
sgn=-1
1960
#Tj=SL2Z([1,j,0,1])
1961
else:
1962
j=Tj.matrix()[0 ,1 ]
1963
# if Tj = Id i.e. j=0 then A=BS otherwise A=BST^j
1964
cf.append(j)
1965
# To make sure we test
1966
C = B*S*Tj
1967
try:
1968
for ir in range(_sage_const_2 ):
1969
for ik in range(_sage_const_2 ):
1970
if(C[ir,ik]<>A[ir,ik] and C[ir,ik]<>-A[ir,ik]):
1971
raise StopIteration
1972
except StopIteration:
1973
print "ERROR: factorization failed %s <> %s " %(C,A)
1974
print "Cf(A)=",cf
1975
raise ArithmeticError," Could not factor matrix A=%s" % A_in
1976
if(C.matrix()[0 ,0 ]==-A.matrix()[0 ,0 ] and C.matrix()[0 ,1 ]==-A.matrix()[0 ,1 ]):
1977
sgn=-1
1978
return [cf,sgn]
1979
1980
def ncf_to_matrix_in_SL2Z(l):
1981
r""" Convert a nearest integer continued fraction of x to the
1982
matrix A where x=A(0)
1983
1984
EXAMPLES::
1985
1986
sage: nearest_integer_continued_fraction(0.5);cf
1987
[1, 2]
1988
sage: A=ncf_to_matrix_in_SL2Z(cf); A
1989
[1 1]
1990
[1 2]
1991
sage: factor_matrix_in_sl2z_in_S_and_T(A)
1992
[[1, 2], 1]
1993
sage: cf=nearest_integer_continued_fraction(pi.n(100),nmax=10);cf
1994
[3, -7, 16, 294, 3, 4, 5, 15, -3, -2, 2]
1995
sage: A=ncf_to_matrix_in_SL2Z(cf); A
1996
[ -411557987 -1068966896]
1997
[ -131002976 -340262731]
1998
sage: factor_matrix_in_sl2z_in_S_and_T(A)
1999
[[3, -7, 16, 294, 3, 4, 5, 15, -2, 2, 3], -1]
2000
2001
"""
2002
S,T=SL2Z.gens()
2003
A=SL2Z([1,l[0],0,1])
2004
for j in range(1,len(l)):
2005
A=A*S*T**l[j]
2006
return A
2007
2008
def nearest_integer_continued_fraction(x,nmax=None):
2009
r""" Nearest integer continued fraction of x
2010
where x is a rational number, and at n digits
2011
2012
EXAMPLES::
2013
2014
sage: nearest_integer_continued_fraction(0.5)
2015
[1, 2]
2016
nearest_integer_continued_fraction(pi.n(100),nmax=10)
2017
[3, -7, 16, 294, 3, 4, 5, 15, -3, -2, 2]
2018
2019
"""
2020
if(nmax == None):
2021
if(x in QQ):
2022
nmax=10000
2023
else:
2024
nmax=100 # For non-rational numbrs we don't want so many convergents
2025
jj=0
2026
cf=list()
2027
n=nearest_integer(x)
2028
cf.append(n)
2029
if(x in QQ):
2030
x1=Rational(x-n)
2031
while jj<nmax and x1<>0 :
2032
n=nearest_integer(-1 /x1)
2033
x1=Rational(-1 /x1-n)
2034
cf.append(n)
2035
jj=jj+1
2036
return cf
2037
else:
2038
try:
2039
RF=x.parent()
2040
x1=RF(x-n)
2041
while jj<nmax and x1<>0 :
2042
n=nearest_integer(RF(-1 )/x1)
2043
x1=RF(-1 )/x1-RF(n)
2044
cf.append(n)
2045
jj=jj+1
2046
return cf
2047
except AttributeError:
2048
raise ValueError,"Could not determine type of input x=%s" %s
2049
2050
def nearest_integer(x):
2051
r""" Returns the nearest integer to x: [x]
2052
using the convention that
2053
[1/2]=0 and [-1/2]=0
2054
2055
2056
EXAMPLES::
2057
2058
sage: nearest_integer(0)
2059
0
2060
sage: nearest_integer(0.5)
2061
1
2062
sage: nearest_integer(-0.5)
2063
0
2064
sage: nearest_integer(-0.500001)
2065
-1
2066
"""
2067
return floor(x+_sage_const_1 /_sage_const_2 )
2068
2069
2070
def _get_perms_from_str(st):
2071
r""" Construct permutations frm the string st of the form
2072
st='[a1_a2_..._an]-[b1_...bn]'
2073
2074
2075
EXAMPLES::
2076
2077
2078
sage: G=MySubgroup(Gamma0(5))
2079
sage: s=G._get_uid();s
2080
'[2_1_4_3_5_6]-[3_1_2_5_6_4]-Gamma0(5)'
2081
sage: _get_perms_from_str(s)
2082
[(1,2)(3,4), (1,3,2)(4,5,6)]
2083
2084
sage: P=Permutations(7)
2085
sage: pS=P([1,3,2,5,4,7,6]); pS
2086
(2,3)(4,5)(6,7)
2087
sage: pR=P([3,2,4,1,6,7,5]); pR
2088
(1,3,4)(5,6,7)
2089
sage: G=MySubgroup(Gamma0(4))
2090
sage: G=MySubgroup(o2=pS,o3=pR)
2091
sage: s=G._get_uid();s
2092
'[1_3_2_5_4_7_6]-[3_2_4_1_6_7_5]'
2093
sage: _get_perms_from_str(s)
2094
[(2,3)(4,5)(6,7), (1,3,4)(5,6,7)]
2095
2096
"""
2097
(s1,sep,s2)=s.partition("-")
2098
if(len(sep)==0 ):
2099
raise ValueError,"Need to supply string of correct format!"
2100
if(s2.count("-")>0 ):
2101
# split again
2102
(s21,sep,s22)=s2.partition("-")
2103
s2=s21
2104
ix=s1.count("_")+_sage_const_1
2105
pS=list(range(ix))
2106
pR=list(range(ix))
2107
s1=s1.strip("["); s1=s1.strip("]");
2108
s2=s2.strip("["); s2=s2.strip("]");
2109
vs1=s1.split("_")
2110
vs2=s2.split("_")
2111
P=Permutations(ix)
2112
for n in range(ix):
2113
pS[n]=int(vs1[n])
2114
pR[n]=int(vs2[n])
2115
return [P(pS),P(pR)]
2116
2117