Hyperbolic plane $\mathbb{H}^2$

This notebook illustrates some differential geometry capabilities of SageMath on the example of the hyperbolic plane. The corresponding tools have been developed within the SageManifolds project (version 1.3, as included in SageMath 8.3).

Click here to download the notebook file (ipynb format). To run it, you must start SageMath with the Jupyter notebook interface, via the command sage -n jupyter

NB: a version of SageMath at least equal to 7.5 is required to run this worksheet:

In [1]:
version()
Out[1]:
'SageMath version 8.3, Release Date: 2018-08-03'

First we set up the notebook to display mathematical objects using LaTeX formatting:

In [2]:
%display latex

We also define a viewer for 3D plots (use 'threejs' or 'jmol' for interactive 3D graphics):

In [3]:
viewer3D = 'threejs' # must be 'threejs', jmol', 'tachyon' or None (default)

Finally, we tell Maxima, which is used by SageMath for simplifications of symbolic expressions, that all computations involve real variables:

In [4]:
maxima_calculus.eval("domain: real;")
Out[4]:

We declare $\mathbb{H}^2$ as a 2-dimensional differentiable manifold:

In [5]:
H2 = Manifold(2, 'H2', latex_name=r'\mathbb{H}^2', start_index=1)
print(H2)
H2
2-dimensional differentiable manifold H2
Out[5]:

We shall introduce charts on $\mathbb{H}^2$ that are related to various models of the hyperbolic plane as submanifolds of $\mathbb{R}^3$. Therefore, we start by declaring $\mathbb{R}^3$ as a 3-dimensional manifold equiped with a global chart: the chart of Cartesian coordinates $(X,Y,Z)$:

In [6]:
R3 = Manifold(3, 'R3', latex_name=r'\mathbb{R}^3', start_index=1)
X3.<X,Y,Z> = R3.chart()
X3
Out[6]:

Hyperboloid model

The first chart we introduce is related to the hyperboloid model of $\mathbb{H}^2$, namely to the representation of $\mathbb{H}^2$ as the upper sheet ($Z>0$) of the hyperboloid of two sheets defined in $\mathbb{R}^3$ by the equation $X^2 + Y^2 - Z^2 = -1$:

In [7]:
X_hyp.<X,Y> = H2.chart()
X_hyp
Out[7]:

The corresponding embedding of $\mathbb{H}^2$ in $\mathbb{R}^3$ is

In [8]:
Phi1 = H2.diff_map(R3, [X, Y, sqrt(1+X^2+Y^2)], name='Phi_1', latex_name=r'\Phi_1')
Phi1.display()
Out[8]:

By plotting the chart $\left(\mathbb{H}^2,(X,Y)\right)$ in terms of the Cartesian coordinates of $\mathbb{R}^3$, we get a graphical view of $\Phi_1(\mathbb{H}^2)$:

In [9]:
show(X_hyp.plot(X3, mapping=Phi1, number_values=15, color='blue'), aspect_ratio=1, 
     viewer=viewer3D, online=True, figsize=7)

A second chart is obtained from the polar coordinates $(r,\varphi)$ associated with $(X,Y)$. Contrary to $(X,Y)$, the polar chart is not defined on the whole $\mathbb{H}^2$, but on the complement $U$ of the segment $\{Y=0, x\geq 0\}$:

In [10]:
U = H2.open_subset('U', coord_def={X_hyp: (Y!=0, X<0)})
print(U)
Open subset U of the 2-dimensional differentiable manifold H2

Note that (y!=0, x<0) stands for $y\not=0$ OR $x<0$; the condition $y\not=0$ AND $x<0$ would have been written [y!=0, x<0] instead.

In [11]:
X_pol.<r,ph> = U.chart(r'r:(0,+oo) ph:(0,2*pi):\varphi')
X_pol
Out[11]:
In [12]:
X_pol.coord_range()
Out[12]:

We specify the transition map between the charts $\left(U,(r,\varphi)\right)$ and $\left(\mathbb{H}^2,(X,Y)\right)$ as $X=r\cos\varphi$, $Y=r\sin\varphi$:

In [13]:
pol_to_hyp = X_pol.transition_map(X_hyp, [r*cos(ph), r*sin(ph)])
pol_to_hyp
Out[13]:
In [14]:
pol_to_hyp.display()
Out[14]:
In [15]:
pol_to_hyp.set_inverse(sqrt(X^2+Y^2), atan2(Y, X)) 
In [16]:
pol_to_hyp.inverse().display()
Out[16]:

The restriction of the embedding $\Phi_1$ to $U$ has then two coordinate expressions:

In [17]:
Phi1.restrict(U).display()
Out[17]:
In [18]:
graph_hyp = X_pol.plot(X3, mapping=Phi1.restrict(U), number_values=15, ranges={r: (0,3)}, 
                       color='blue')
show(graph_hyp, aspect_ratio=1, viewer=viewer3D, online=True, figsize=7)
In [19]:
Phi1._coord_expression
Out[19]:

Metric and curvature

The metric on $\mathbb{H}^2$ is that induced by the Minkowksy metric on $\mathbb{R}^3$: $$ \eta = \mathrm{d}X\otimes\mathrm{d}X + \mathrm{d}Y\otimes\mathrm{d}Y - \mathrm{d}Z\otimes\mathrm{d}Z $$

In [20]:
eta = R3.lorentzian_metric('eta', latex_name=r'\eta')
eta[1,1] = 1 ; eta[2,2] = 1 ; eta[3,3] = -1
eta.display()
Out[20]:
In [21]:
g = H2.metric('g')
g.set( Phi1.pullback(eta) )
g.display() 
Out[21]:

The expression of the metric tensor in terms of the polar coordinates is

In [22]:
g.display(X_pol.frame(), X_pol)
Out[22]:

The Riemann curvature tensor associated with $g$ is

In [23]:
Riem = g.riemann()
print(Riem)
Tensor field Riem(g) of type (1,3) on the 2-dimensional differentiable manifold H2
In [24]:
Riem.display(X_pol.frame(), X_pol)
Out[24]:

The Ricci tensor and the Ricci scalar:

In [25]:
Ric = g.ricci()
print(Ric)
Field of symmetric bilinear forms Ric(g) on the 2-dimensional differentiable manifold H2
In [26]:
Ric.display(X_pol.frame(), X_pol)
Out[26]:
In [27]:
Rscal = g.ricci_scalar()
print(Rscal)
Scalar field r(g) on the 2-dimensional differentiable manifold H2
In [28]:
Rscal.display()
Out[28]:

Hence we recover the fact that $(\mathbb{H}^2,g)$ is a space of constant negative curvature.

In dimension 2, the Riemann curvature tensor is entirely determined by the Ricci scalar $R$ according to

$$R^i_{\ \, jlk} = \frac{R}{2} \left( \delta^i_{\ \, k} g_{jl} - \delta^i_{\ \, l} g_{jk} \right)$$

Let us check this formula here, under the form $R^i_{\ \, jlk} = -R g_{j[k} \delta^i_{\ \, l]}$:

In [29]:
delta = H2.tangent_identity_field()
Riem == - Rscal*(g*delta).antisymmetrize(2,3)  # 2,3 = last positions of the type-(1,3) tensor g*delta 
Out[29]:

Similarly the relation $\mathrm{Ric} = (R/2)\; g$ must hold:

In [30]:
Ric == (Rscal/2)*g
Out[30]:

Poincaré disk model

The Poincaré disk model of $\mathbb{H}^2$ is obtained by stereographic projection from the point $S=(0,0,-1)$ of the hyperboloid model to the plane $Z=0$. The radial coordinate $R$ of the image of a point of polar coordinate $(r,\varphi)$ is $$ R = \frac{r}{1+\sqrt{1+r^2}}.$$ Hence we define the Poincaré disk chart on $\mathbb{H}^2$ by

In [31]:
X_Pdisk.<R,ph> = U.chart(r'R:(0,1) ph:(0,2*pi):\varphi')
X_Pdisk
Out[31]:
In [32]:
X_Pdisk.coord_range()
Out[32]:

and relate it to the hyperboloid polar chart by

In [33]:
pol_to_Pdisk = X_pol.transition_map(X_Pdisk, [r/(1+sqrt(1+r^2)), ph])
pol_to_Pdisk
Out[33]:
In [34]:
pol_to_Pdisk.display()
Out[34]:
In [35]:
pol_to_Pdisk.set_inverse(2*R/(1-R^2), ph)
pol_to_Pdisk.inverse().display()
Out[35]:

A view of the Poincaré disk chart via the embedding $\Phi_1$:

In [36]:
show(X_Pdisk.plot(X3, mapping=Phi1.restrict(U), ranges={R: (0,0.9)}, color='blue',
                  number_values=15), 
     aspect_ratio=1, viewer=viewer3D, online=True, figsize=7)

The expression of the metric tensor in terms of coordinates $(R,\varphi)$:

In [37]:
g.display(X_Pdisk.frame(), X_Pdisk)
Out[37]:

We may factorize each metric component:

In [38]:
for i in [1,2]:
    g[X_Pdisk.frame(), i, i, X_Pdisk].factor()
g.display(X_Pdisk.frame(), X_Pdisk)
Out[38]:

Cartesian coordinates on the Poincaré disk

Let us introduce Cartesian coordinates $(u,v)$ on the Poincaré disk; since the latter has a unit radius, this amounts to define the following chart on $\mathbb{H}^2$:

In [39]:
X_Pdisk_cart.<u,v> = H2.chart('u:(-1,1) v:(-1,1)')
X_Pdisk_cart.add_restrictions(u^2+v^2 < 1)
X_Pdisk_cart
Out[39]:

On $U$, the Cartesian coordinates $(u,v)$ are related to the polar coordinates $(R,\varphi)$ by the standard formulas:

In [40]:
Pdisk_to_Pdisk_cart = X_Pdisk.transition_map(X_Pdisk_cart, [R*cos(ph), R*sin(ph)])
Pdisk_to_Pdisk_cart
Out[40]:
In [41]:
Pdisk_to_Pdisk_cart.display()
Out[41]:
In [42]:
Pdisk_to_Pdisk_cart.set_inverse(sqrt(u^2+v^2), atan2(v, u)) 
Pdisk_to_Pdisk_cart.inverse().display()
Out[42]:

The embedding of $\mathbb{H}^2$ in $\mathbb{R}^3$ associated with the Poincaré disk model is naturally defined as

In [43]:
Phi2 = H2.diff_map(R3, {(X_Pdisk_cart, X3): [u, v, 0]},
                   name='Phi_2', latex_name=r'\Phi_2')
Phi2.display()
Out[43]:

Let us use it to draw the Poincaré disk in $\mathbb{R}^3$:

In [44]:
graph_disk_uv = X_Pdisk_cart.plot(X3, mapping=Phi2, number_values=15)
show(graph_disk_uv, viewer=viewer3D, online=True, figsize=7)

On $U$, the change of coordinates $(r,\varphi) \rightarrow (u,v)$ is obtained by combining the changes $(r,\varphi) \rightarrow (R,\varphi)$ and $(R,\varphi) \rightarrow (u,v)$:

In [45]:
pol_to_Pdisk_cart = Pdisk_to_Pdisk_cart * pol_to_Pdisk
pol_to_Pdisk_cart
Out[45]:
In [46]:
pol_to_Pdisk_cart.display()
Out[46]:

Still on $U$, the change of coordinates $(X,Y) \rightarrow (u,v)$ is obtained by combining the changes $(X,Y) \rightarrow (r,\varphi)$ with $(r,\varphi) \rightarrow (u,v)$:

In [47]:
hyp_to_Pdisk_cart_U = pol_to_Pdisk_cart * pol_to_hyp.inverse()
hyp_to_Pdisk_cart_U
Out[47]:
In [48]:
hyp_to_Pdisk_cart_U.display()
Out[48]:

We use the above expression to extend the change of coordinates $(X,Y) \rightarrow (u,v)$ from $U$ to the whole manifold $\mathbb{H}^2$:

In [49]:
hyp_to_Pdisk_cart = X_hyp.transition_map(X_Pdisk_cart, hyp_to_Pdisk_cart_U(X,Y))
hyp_to_Pdisk_cart
Out[49]:
In [50]:
hyp_to_Pdisk_cart.display()
Out[50]:
In [51]:
hyp_to_Pdisk_cart.set_inverse(2*u/(1-u^2-v^2), 2*v/(1-u^2-v^2))
hyp_to_Pdisk_cart.inverse().display()
Out[51]:
In [52]:
graph_Pdisk = X_pol.plot(X3, mapping=Phi2.restrict(U), ranges={r: (0, 20)}, number_values=15, 
                         label_axes=False)
show(graph_hyp + graph_Pdisk, aspect_ratio=1, viewer=viewer3D, online=True, figsize=7)
In [53]:
X_pol.plot(X_Pdisk_cart, ranges={r: (0, 20)}, number_values=15)
Out[53]: