Two Sage Visuals
27 Apr 2021 - Tags: sage , pretty-pictures , featured
I’m in a reading group with Elliott Vest and Jacob Garcia (supervised by Matt Durham) where we’re talking about CAT(0) Cube Complexes. We’re reading a set of lecture notes by sageev (pdf here, for the interested) and we came across a fairly simple problem that we wanted to draw. In a completely different vein, Russell Phelan asked a fun topological question in the UCR math discord, and to solve it I ended up needing to draw something else. I figured I would write up a quick post about both visualizations, since these things can be a bit tricky to get right.
Hyperbolic Circles
Let’s start with the cube complexes. One of the exercises in Sageev’s
notes1 asks a question which uses circles in the hyperbolic
plane
We know that in the disk model hyperbolic circles and euclidean circles agree (albeit the centers might not be where they appear). By this I mean a hyperbolic circle
looks like a euclidean circle when you draw it. I didn’t know of any such fact for the upper half plane model, though, and I asked if anyonne knew what circles look like there. We didn’t, so I went on a quest to just… draw a bunch of hyperbolic circles in sage.
According to wikipedia, the hyperbolic distance in the upper half plane is given by
So let’s go ahead and plot a circle in this metric! The relevant function
for this is implicit_plot
, which does what it says on the tin.
xxxxxxxxxx
x,y = var('x,y')
d(x1,y1,x2,y2) = 2 * arcsinh(1/2 * sqrt(((x2-x1)^2 + (y2-y1)^2) / (y1*y2)))
# plot a circle of radius 1/2 centered at (0,1)
implicit_plot(d(x,y,0,1) - 1/2, (-5,5), (0,5))
You can see this looks like a regular circle. Of course, we know that distances should be distorted – just look at the notion of distance! The distortion, it turns out, is in the apparent location of the center and the apparent size of the circle.
To see exactly what I mean by this, let’s plot a bunch of circles
(with their centers marked) each of radius
xxxxxxxxxx
x,y = var('x,y')
d(x1,y1,x2,y2) = 2 * arcsinh(1/2 * sqrt(((x2-x1)^2 + (y2-y1)^2) / (y1*y2)))
colors = ["blue", "red", "green", "maroon", "olive", "pink", "silver", "navy"]
def draw_circle(x0,y0,r, c):
"""
Draw a circle with center (x0,y0), radius r, and color c
"""
# draw the circle
p1 = implicit_plot(d(x,y,x0,y0) - r, (-5,5), (0,5), color=c)
# draw the center
p2 = point((x0,y0), color=c)
return p1 + p2
out = Graphics()
for i in range(1,8):
# draw a sequence of circles, all of radius 1,
# but with centers moving closer to the x axis
# (which we think of as a line at infinity)
out += draw_circle(-3 + i, 1/i, 1, colors[i])
out.show()
You can tell that the true center of the circle is not where one might
think. This is because distances near the
As an aside, it might seem magical that this wild distance formula makes circles look like euclidean circles. But like a good magic trick, there’s a shockingly simple explanation under the surface.
If we take for granted that circles in the disk model are euclidean circles, can you show that this must be true for the upper half plane model as well?
As a (possibly too helpful) hint, you might consider mobius transformations.
To really have fun experimenting, let’s make the above graphic interactive, and let’s throw in an interactive disk model graphic as well, just for fun.
Be a bit careful with the disk model – because I’m using the same sliders as the upper half plane model, you need to make sure that your center stays in the unit circle.
xxxxxxxxxx
x,y = var('x,y')
dUHP(x1,y1,x2,y2) = 2 * arcsinh(1/2 * sqrt(((x2-x1)^2 + (y2-y1)^2) / (y1*y2)))
dPD(x1,y1,x2,y2) = arccosh(1 + (2 * ((x2-x1)^2 + (y2-y1)^2))/((1 - (x1^2 + y1^2))*(1 - (x2^2 + y2^2))))
def _(model=selector(['upper half plane', 'poincare disk'], buttons=True),
x0=slider(-5,5,step_size=0.1, default=0),
y0=slider(0.1,5,step_size=0.1, default=1/2),
r=slider(0,3,step_size=0.1, default=1)):
if model == "upper half plane":
show("Upper Half Plane Circles")
# draw the circle
p1 = implicit_plot(dUHP(x,y,x0,y0) - r, (-5,5), (0,5))
# draw the center
p2 = point((x0,y0))
show(p1+p2)
else:
show("Poincare Disk Circles")
# draw the boundary circle of the poincare disk
p1 = implicit_plot(x^2 + y^2 - 1, (-1.5,1.5), (-1.5,1.5), color="black")
# draw the circle
p2 = implicit_plot(dPD(x,y,x0,y0) - r, (-1.5,1.5), (-1.5,1.5))
# draw the center
p3 = point((x0,y0))
show(p1+p2+p3)
A Topological Problem
In the second half of this post we’ll go over a fun problem that Russell put in the UCR discord. It’s Question 1 from Example 3.1.10 in Burago, Burago, and Ivanov’s A Course in Metric Geometry:
What topological space do you get when you quotient
I encourage you to give this a go by yourself before reading ahead! It took me a few days to be really confident in my answer, and it was a lot of fun to work through ^_^.
After a bit of looking for low hanging fruit (which, as far as I can tell,
wasn’t there), I decided to just hunker down and look for a fundamental domain.
It’s easy to see that this quotient space is the orbit space of the action
of
A little bit of experimentation lets you find a fundamental domain.
We know that we scale things by a factor of
xxxxxxxxxx
x,y = var('x,y')
xr = (x,-2,2)
yr = (y,-2,2)
out = Graphics()
# the first anulus
region = [1/4 < x^2 + y^2, x^2 + y^2 < 1, x > 0, y > 0]
out += region_plot(region, xr, yr, incol="purple")
# the second anulus
region = [1/16 < x^2 + y^2, x^2 + y^2 < 1/4, x > 0, y > 0]
out += region_plot(region, xr, yr, incol="cyan")
out.show()
We can convince ourselves that this really is a fundamental domain by plotting the orbit of all of these regions and checking that they cover the whole plane (barring the origin, of course. Do you see why we have to treat it specially?).
xxxxxxxxxx
x,y = var('x,y')
N = 5
T = matrix([[0,-1],[2,0]])
xr = (x,-2,2)
yr = (y,-2,2)
def drawRegion(n):
[v1,v2] = T^n * matrix([x,y]).transpose()
# janky hack mate
# I have no idea why we need to do this
v1 = eval(str(v1))
v2 = eval(str(v2))
out = Graphics()
# the first anulus
region = [1/4 < v1^2 + v2^2, v1^2 + v2^2 < 1, v2 > 0, v1 > 0]
out += region_plot(region, xr, yr, incol="purple")
# the second anulus
region = [1/16 < v1^2 + v2^2, v1^2 + v2^2 < 1/4, v2 > 0, v1 > 0]
out += region_plot(region, xr, yr, incol="cyan")
return out
out = Graphics()
for n in range(-N,N+1):
out += drawRegion(n)
out.show()
From this information we can piece together the quotient space! The pretty picture tells us that we have a (topological) hexagon from the two cyan sides, the two purple sides, the purple top and the cyan bottom.
As a quick exercise, write down the hexagon above and figure out which sides are identified. Why is this a torus?
So we understand
So our picture is of a torus, plus one really big “generic” point. Any neighborhood of this point contains the entire torus.
What a bizarre space, right? I had a lot of fun working this out! Before I typed this up I was feeling a bit insecure about the solution (I also had a much grosser fundamental domain at first), and asked about it on mse. It doesn’t have an answer yet, but I’m feeling more confident in my computation now, so I’m posting this anyways. I can always edit this post if someone leaves an answer that totally changes how I think about the problem, and you can always follow that link if you (in the future) want to see what people had to say.
Finally, if you want to think about some similar things, I have a fun problem for you ^_^
Let’s look at the action of
What are the quotient spaces of these actions? They generate subgroups of
-
Exercise 2.15, for the curious ↩