Fundamental domains and other pictures in the upper half plane, with Magma

by Helena Verrill

I spent May 2001 at the University of Sydney, developing the Magma package for working with congruence subgroups. If you have version 2.8 of Magma you will be able to use this package. Although this package does not allow the interaction of my java fundamental domains program, it is much more flexible in other ways, and can be used to produce postscript files, which you can then include in latex documents.

Below are some pictures created using the package, with the code used to create them. This page is really just for pretty pictures. For more information on how to use the package see the Magma manual. (Note, the package produces postscript files; I used another program to convert these to gifs). I shall probably add some more pictures at a later date.


Drawing Code
These show three different tilings of the upper half plane with a fundamental domain for Gamma0(2).

G := GammaUpper0(2);
H< i,r> := UpperHalfPlaneWithCusps();
generators := Generators(G);
M := generators cat [g^(-1) : g in generators] cat [G!1];
tri1 := [H|Infinity(),r-1,0,r,r+1];
tri2 := [H|Infinity(),i-1,0,i+1];
tri3 := FundamentalDomain(G);
X := [1..#M];
L := [G!m : m in Set([Matrix(M[i]*M[j]*M[k]*M[l]*M[m]) :
i in X, j in X, k in X, l in X, m in X])];

DisplayPolygons([g*tri1 : g in L],"/tmp/pic1.ps": Show := true,
Size:=[-2,2,1.5,100]);
DisplayPolygons([g*tri2 : g in L],"/tmp/pic2.ps": Show := true,
Size:=[-2,2,1.5,100]);
DisplayPolygons([g*tri3 : g in L],"/tmp/pic3.ps": Show := true,
Size:=[-2,2,1.5,100]);
This picture gives some explanation of the previous tilings. In this diagram, any choice of 6 triangles, one of each colour, gives a fundamental domain for Gamma0(2). Different ways of choosing these 6 triangles give the above different kinds of tilings.

G := GammaUpper0(2);
H< i,r> := UpperHalfPlaneWithCusps();
generators := Generators(G);
M := generators cat [g^(-1) : g in generators] cat [G!1];
X := [1..#M];
L := [G!m : m in Set([Matrix(M[i]*M[j]*M[k]*M[l]*M[m]) :
i in X, j in X, k in X, l in X, m in X])];

C := CosetRepresentatives(GammaUpper0(2));
tri1:=[H|Infinity(),i,r];
tri2:=[H|0,i,r];
Pols1:=[g*c*tri1 : c in C, g in L];
Pols2:=[g*c*tri2 : c in C, g in L];
pols := Pols1 cat Pols2;
Colours := &cat[[[0.5,0.5,1],[1,0,0],[0,0.8,0]] : i in L];
Colours2 := &cat[[[0,0,1],[1,0.6,0.2],[0.5,1,0.5]] : i in L];
cols:=Colours cat Colours2;

DisplayPolygons(pols,"/tmp/picture1.ps": Show := true,Size:=[-2,2,1.5,150],
Colours:=cols,Outline:=false);
Fundamental domain for Gamma0(400) in terms of domains for Gamma0(2), coloured according to at what stage they are added to the picture.

frac := func<a | a[1]/a[2]>;

function FareyValue(m)
mat := Matrix(m);
Denominators := [mat[2,1],mat[2,2],mat[2,1]+mat[2,2]];
values := [Abs(v) : v in Denominators];
return &+ContinuedFraction(frac(Sort(values)));
end function;

procedure drawDomain(cosets)
H< i,r> := UpperHalfPlaneWithCusps();
tri := [H|Infinity(),0,r];
cols := [[0.08*FareyValue(c),1-0.08*FareyValue(c),1-0.2*FareyValue(c)]
: c in cosets];
trans := [g*tri : g in cosets];
DisplayPolygons(trans,"/tmp/pic.ps":
Outline := false, Colours := cols, Show := true, Size:=[0,1,1.5,450]);
end procedure;

C := CosetRepresentatives(Gamma0(400));

drawDomain(C);

To complete the modular curves defined by quotienting by congruence subgroups, the cusps (rationals and infinity) are adjoined to the upper half plane. The topology is defined in such a way that neighborhoods of cusps include discs which touch the real line at the cusp, if the cusp is a rational. For infinity, sets of points with Imaginary part greater than some number give open sets about infinity. This picture shows some of these discs, which are drawn just by taking images of a neighborhood of infinity. The discs have a pattern in 3 colours, just to give a better feeling for how the transformation works.

P:=PSL2(Integers());

function find_matrix(x)
if x eq 0
then return P![0,-1,1,0];
end if;
a:=Numerator(x);
c:=Denominator(x);
g,d,b:=Xgcd(a,c);
return P![a,-b,c,d];
end function;

fractions:=[0] cat [a/b : a,b in [1..6] | a le 2*b and Gcd(a,b) eq 1];
matrices:=[P!1] cat [find_matrix(x) : x in fractions];

area1:=[H!Infinity()]
cat [H | i - (1+1/4)^n : n in Reverse([-5..16])]
cat [H|i]
cat [H|i+(1+1/4)^n : n in [-5..16]];
area2:=[H!Infinity()]
cat [H | 5/4*i - (1+1/4)^n : n in Reverse([-5..16])]
cat [H|5/4*i]
cat [H|5/4*i+(1+1/4)^n : n in [-5..16]];
area3:=[H|Infinity(),i+1/2,i+5/4,i+3/2];
polys:=[g*p : g in matrices, p in [area1,area2,area3]];

yellow:=[1,1,0];
orange:=[1,0.5,0];
green:=[0.5,0.9,0.2];
brown:=[0.4,0,0];
colours:=[c : i in matrices, c in [yellow,orange,green]];
pencolours:=[c : i in matrices, c in [brown,orange,green]];

DisplayPolygons(polys ,"/tmp/pic.ps":
Show:=true,Size:=[0,2,1.5,200],
Colours:=colours,PenColours:=pencolours);
In the above examples, we've used a well known fundamental domain for SL(2,Z) to draw fundamental domains for other groups. Of course, there are infinitely many possible fundamental domains for SL(2,Z). In this example, I've given code for creating more of them, with some random parameters. We're still just using examples associated to the choice of generators S=PSL(2,Z)![0,-1,1,0]; and R:=Gamma0(1)![0,-1,1,-1]; so these examples are still all very closely related.
The first picture shows one of these domains for SL(2,Z). The second shows a tiling of the upper half plane using images of this domain, and random colouring.
Here is another choice of domain for SL(2,Z). The union of 6 copies of this domain, in the second picture, is a domain for Gamma(2). In the third picture, a tiling of the upper half plane is created by translating these 6 triangles by elements of Gamma(2).
WARNING: Not all regions created by the method on the right give domains for SL(2,Z); you need to check that the edges of the polygon formed by this code do not intersect each other, apart from at their ends.



H< i,r>:=UpperHalfPlaneWithCusps();
S:=Gamma0(1)![0,-1,1,0];
R:=Gamma0(1)![0,-1,1,-1];
T:=Gamma0(1)![1,1,0,1];

function domain_for_SL()
edge1:=[H|Infinity()]
cat [H|((Random(1)+(8-r))/2)*i + (Random(2)-1)/2 : r in [1..5]]
cat [H|i];
edge2:=edge1;
edge2[7]:=H!r;
pol:=edge1 cat Reverse(S*edge1) cat (R*edge2) cat Reverse(edge2);
return pol;
end function;

// draw a domain found using the above function:

pol:=domain_for_SL();
size:=DisplayPolygons(pol,"/tmp/pic.ps":Show:=true);

// draw lots of translates of this domain, with random colours:

// lazy way to get a lot of matrices:
matrices:=
[T^j*g : g in CosetRepresentatives(CongruenceSubgroup(5)), j in [-1,0,1]]

cols:=[[Random(5)/5.0 : i in [1..3]] : g in matrices];

size:=DisplayPolygons([g*pol: g in matrices],"/tmp/pic.ps":
Show:=true,Colours:=cols,Size:=[-0.5,1.5,4,200]);

// draw another domain:

pol:=domain_for_SL();
size:=DisplayPolygons(pol,"/tmp/pic.ps":Show:=true);

// draw a nice fundamental domain for Gamma(2) in terms of this:

// define Gamma(2)
G2:=CongruenceSubgroup(2);

C2:=CosetRepresentatives(G2);
// we modify the list of coset representatives,
// to get an alternative list of representatives:
C3:=C2;
g:=Generators(G2)[1]^(-1)*T^(-2);
C3[3]:=g*C2[3];

// choose some nice colours!
yellow:=[1,0.8,0];
orange:=[1,0.5,0];
blue:=[0,0,1];
lightblue:=[0.4,0.4,1];
green:=[0.1,0.9,0.1];
darkgreen:=[0.1,0.7,0.1];
colours:=[blue,darkgreen,yellow,lightblue,orange,green];

DisplayPolygons([g*pol : g in C3],"/tmp/pic.ps":
Show:=true,Colours:=colours,Size:=[-0.5,2.5,4,200]);

// draw translates of the above domain for Gamma(2):

mats:=[G2!1,G2!(T^2)]
cat [(G2!(T^2)^j)*g^(i) : g in Generators(G2),
i in [-2,-1,1,2],j in [0,1]];
colours2:=&cat[colours : i in mats];

size:=DisplayPolygons([h*g*pol : g in C2,h in mats],"/tmp/pic.ps":
Show:=true,Colours:=colours2,Size:=[-0.5,2.5,4,200]);

// make the above example into a function:

procedure gamma2picture()
pol:=domain_for_SL();
size:=DisplayPolygons([h*g*pol : g in C2,h in mats],"/tmp/pic.ps":
Show:=true,Colours:=colours2,Size:=[-0.5,2.5,4,200]);
end procedure;

gamma2picture();
Pretty picture related to Gamma0(16). (no particular significance)

G:=Gamma0(16);
C:=CosetRepresentatives(G);

H< i,r>:=UpperHalfPlaneWithCusps();
Edge1:=[H|0,2];
Edge2:=[H|i,1/2*(i+1)];
point:=GeodesicsIntersection(Edge1,Edge2,H)[1];
triangle1:=[H|Infinity(),i,r];
triangle3:=[H|i,point,r];
triangle2:=[H|0,i,point];

Par:=Parent(triangle1);
t:=G![1,1,0,1];
Polygons:=[Par|g*tri : g in C,tri in [triangle1,triangle2,triangle3]];
Polygons2:=[Par|t*g*tri : g in C,tri in [triangle3]];
Polygons3:=[Par|t^(-1)*g*tri : g in C,tri in [triangle3]];
purple:=[0.5,0,0.8];
blue:=[0.6,0.6,0.9];
yellow:=[1,0.8,0];
Colours:=[blue : i in C] cat [purple : i in C] cat [yellow : i in C]
cat [purple : i in C] cat [yellow : i in C];
DisplayPolygons(Polygons cat Polygons2 cat
Polygons3,"/tmp/pic.ps":Show:=true,
Colours:=Colours,Size:=[-1,2,1.5,150]);


Created by Helena Verrill, September 2001. Comments to verrill@math.uni-hannover.de