This worksheet illustrates some features of SageManifolds (v0.9.1) on computations regarding the hyperbolic plane.
Click here to download the worksheet file (ipynb format). To run it, you must start SageMath with the Jupyter notebook, via the command sage -n jupyter
First we set up the notebook to display mathematical objects using LaTeX formatting:
%display latex
We also define a viewer for 3D plots (use 'jmol'
for interactive 3D graphics):
viewer3D = 'tachyon' # must be 'jmol', 'tachyon' or None (default)
We declare $\mathbb{H}^2$ as a 2-dimensional differentiable manifold:
H2 = Manifold(2, 'H2', latex_name=r'\mathbb{H}^2', start_index=1)
print(H2)
H2
2-dimensional differentiable manifold H2
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)$:
R3 = Manifold(3, 'R3', latex_name=r'\mathbb{R}^3', start_index=1)
X3.<X,Y,Z> = R3.chart()
X3
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$:
X_hyp.<X,Y> = H2.chart()
X_hyp
The corresponding embedding of $\mathbb{H}^2$ in $\mathbb{R}^3$ is
Phi1 = H2.diff_map(R3, [X, Y, sqrt(1+X^2+Y^2)], name='Phi_1', latex_name=r'\Phi_1')
Phi1.display()
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)$:
show(X_hyp.plot(X3, mapping=Phi1, nb_values=15, color='blue'), aspect_ratio=1,
viewer=viewer3D, 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\}$:
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.
X_pol.<r,ph> = U.chart(r'r:(0,+oo) ph:(0,2*pi):\varphi')
X_pol
X_pol.coord_range()
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$:
pol_to_hyp = X_pol.transition_map(X_hyp, [r*cos(ph), r*sin(ph)])
pol_to_hyp
pol_to_hyp.display()
pol_to_hyp.set_inverse(sqrt(X^2+Y^2), atan2(Y, X))
pol_to_hyp.inverse().display()
The restriction of the embedding $\Phi_1$ to $U$ has then two coordinate expressions:
Phi1.restrict(U).display()
graph_hyp = X_pol.plot(X3, mapping=Phi1.restrict(U), nb_values=15, ranges={r: (0,3)},
color='blue')
show(graph_hyp, aspect_ratio=1, viewer=viewer3D, figsize=7)
Phi1._coord_expression
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
eta = R3.lorentzian_metric('eta', latex_name=r'\eta')
eta[1,1] = 1 ; eta[2,2] = 1 ; eta[3,3] = -1
eta.display()
g = H2.metric('g')
g.set( Phi1.pullback(eta) )
g.display()
The expression of the metric tensor in terms of the polar coordinates is
g.display(X_pol.frame(), X_pol)
The Riemann curvature tensor associated with $g$ is
Riem = g.riemann()
print(Riem)
Tensor field Riem(g) of type (1,3) on the 2-dimensional differentiable manifold H2
Riem.display(X_pol.frame(), X_pol)
The Ricci tensor and the Ricci scalar:
Ric = g.ricci()
print(Ric)
Field of symmetric bilinear forms Ric(g) on the 2-dimensional differentiable manifold H2
Ric.display(X_pol.frame(), X_pol)
Rscal = g.ricci_scalar()
print(Rscal)
Scalar field r(g) on the 2-dimensional differentiable manifold H2
Rscal.display()
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]}$:
delta = H2.tangent_identity_field()
Riem == - Rscal*(g*delta).antisymmetrize(2,3) # 2,3 = last positions of the type-(1,3) tensor g*delta
Similarly the relation $\mathrm{Ric} = (R/2)\; g$ must hold:
Ric == (Rscal/2)*g
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
X_Pdisk.<R,ph> = U.chart(r'R:(0,1) ph:(0,2*pi):\varphi')
X_Pdisk
X_Pdisk.coord_range()
and relate it to the hyperboloid polar chart by
pol_to_Pdisk = X_pol.transition_map(X_Pdisk, [r/(1+sqrt(1+r^2)), ph])
pol_to_Pdisk
pol_to_Pdisk.display()
pol_to_Pdisk.set_inverse(2*R/(1-R^2), ph)
pol_to_Pdisk.inverse().display()
A view of the Poincaré disk chart via the embedding $\Phi_1$:
show(X_Pdisk.plot(X3, mapping=Phi1.restrict(U), ranges={R: (0,0.9)}, color='blue',
nb_values=15), aspect_ratio=1, viewer=viewer3D, figsize=7)
The expression of the metric tensor in terms of coordinates $(R,\varphi)$:
g.display(X_Pdisk.frame(), X_Pdisk)
We may factorize each metric component:
for i in [1,2]:
g[X_Pdisk.frame(), i, i, X_Pdisk].factor()
g.display(X_Pdisk.frame(), X_Pdisk)
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$:
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
On $U$, the Cartesian coordinates $(u,v)$ are related to the polar coordinates $(R,\varphi)$ by the standard formulas:
Pdisk_to_Pdisk_cart = X_Pdisk.transition_map(X_Pdisk_cart, [R*cos(ph), R*sin(ph)])
Pdisk_to_Pdisk_cart
Pdisk_to_Pdisk_cart.display()
Pdisk_to_Pdisk_cart.set_inverse(sqrt(u^2+v^2), atan2(v, u))
Pdisk_to_Pdisk_cart.inverse().display()
The embedding of $\mathbb{H}^2$ in $\mathbb{R}^3$ associated with the Poincaré disk model is naturally defined as
Phi2 = H2.diff_map(R3, {(X_Pdisk_cart, X3): [u, v, 0]},
name='Phi_2', latex_name=r'\Phi_2')
Phi2.display()
Let us use it to draw the Poincaré disk in $\mathbb{R}^3$:
graph_disk_uv = X_Pdisk_cart.plot(X3, mapping=Phi2, nb_values=15)
show(graph_disk_uv, viewer=viewer3D, 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)$:
pol_to_Pdisk_cart = Pdisk_to_Pdisk_cart * pol_to_Pdisk
pol_to_Pdisk_cart
pol_to_Pdisk_cart.display()
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)$:
hyp_to_Pdisk_cart_U = pol_to_Pdisk_cart * pol_to_hyp.inverse()
hyp_to_Pdisk_cart_U
hyp_to_Pdisk_cart_U.display()
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$:
hyp_to_Pdisk_cart = X_hyp.transition_map(X_Pdisk_cart, hyp_to_Pdisk_cart_U(X,Y))
hyp_to_Pdisk_cart
hyp_to_Pdisk_cart.display()
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()
graph_Pdisk = X_pol.plot(X3, mapping=Phi2.restrict(U), ranges={r: (0, 20)}, nb_values=15,
label_axes=False)
show(graph_hyp + graph_Pdisk, aspect_ratio=1, viewer=viewer3D, figsize=7)
X_pol.plot(X_Pdisk_cart, ranges={r: (0, 20)}, nb_values=15)
From now on, we are using the Poincaré disk chart $(\mathbb{H}^2,(u,v))$ as the default one on $\mathbb{H}^2$:
H2.set_default_chart(X_Pdisk_cart)
H2.set_default_frame(X_Pdisk_cart.frame())
g.display(X_hyp.frame())
g.display()
g[1,1].factor() ; g[2,2].factor()
g.display()
The hemispherical model of $\mathbb{H}^2$ is obtained by the inverse stereographic projection from the point $S = (0,0,-1)$ of the Poincaré disk to the unit sphere $X^2+Y^2+Z^2=1$. This induces a spherical coordinate chart on $U$:
X_spher.<th,ph> = U.chart(r'th:(0,pi/2):\theta ph:(0,2*pi):\varphi')
X_spher
From the stereographic projection from $S$, we obtain that \begin{equation} \sin\theta = \frac{2R}{1+R^2} \end{equation} Hence the transition map:
Pdisk_to_spher = X_Pdisk.transition_map(X_spher, [arcsin(2*R/(1+R^2)), ph])
Pdisk_to_spher
Pdisk_to_spher.display()
Pdisk_to_spher.set_inverse(sin(th)/(1+cos(th)), ph)
Pdisk_to_spher.inverse().display()
In the spherical coordinates $(\theta,\varphi)$, the metric takes the following form:
g.display(X_spher.frame(), X_spher)
The embedding of $\mathbb{H}^2$ in $\mathbb{R}^3$ associated with the hemispherical model is naturally:
Phi3 = H2.diff_map(R3, {(X_spher, X3): [sin(th)*cos(ph), sin(th)*sin(ph), cos(th)]},
name='Phi_3', latex_name=r'\Phi_3')
Phi3.display()
graph_spher = X_pol.plot(X3, mapping=Phi3, ranges={r: (0, 20)}, nb_values=15, color='orange',
label_axes=False)
show(graph_hyp + graph_Pdisk + graph_spher, aspect_ratio=1, viewer=viewer3D, figsize=7)
The Poincaré half-plane model of $\mathbb{H}^2$ is obtained by stereographic projection from the point $W=(-1,0,0)$ of the hemispherical model to the plane $X=1$. This induces a new coordinate chart on $\mathbb{H}^2$ by setting $(x,y)=(Y,Z)$ in the plane $X=1$:
X_hplane.<x,y> = H2.chart('x y:(0,+oo)')
X_hplane
The coordinate transformation $(\theta,\varphi)\rightarrow (x,y)$ is easily deduced from the stereographic projection from the point $W$:
spher_to_hplane = X_spher.transition_map(X_hplane, [2*sin(th)*sin(ph)/(1+sin(th)*cos(ph)),
2*cos(th)/(1+sin(th)*cos(ph))])
spher_to_hplane
spher_to_hplane.display()
Pdisk_to_hplane = spher_to_hplane * Pdisk_to_spher
Pdisk_to_hplane
Pdisk_to_hplane.display()
Pdisk_cart_to_hplane_U = Pdisk_to_hplane * Pdisk_to_Pdisk_cart.inverse()
Pdisk_cart_to_hplane_U
Pdisk_cart_to_hplane_U.display()
Let us use the above formula to define the transition map $(u,v)\rightarrow (x,y)$ on the whole manifold $\mathbb{H}^2$ (and not only on $U$):
Pdisk_cart_to_hplane = X_Pdisk_cart.transition_map(X_hplane, Pdisk_cart_to_hplane_U(u,v))
Pdisk_cart_to_hplane
Pdisk_cart_to_hplane.display()
Pdisk_cart_to_hplane.set_inverse((4-x^2-y^2)/(x^2+(2+y)^2), 4*x/(x^2+(2+y)^2))
Pdisk_cart_to_hplane.inverse().display()
Since the coordinates $(x,y)$ correspond to $(Y,Z)$ in the plane $X=1$, the embedding of $\mathbb{H}^2$ in $\mathbb{R}^3$ naturally associated with the Poincaré half-plane model is
Phi4 = H2.diff_map(R3, {(X_hplane, X3): [1, x, y]}, name='Phi_4', latex_name=r'\Phi_4')
Phi4.display()
graph_hplane = X_pol.plot(X3, mapping=Phi4.restrict(U), ranges={r: (0, 1.5)}, nb_values=15,
color='brown', label_axes=False)
show(graph_hyp + graph_Pdisk + graph_spher + graph_hplane, aspect_ratio=1, viewer=viewer3D,
figsize=8)
Let us draw the grid of the hyperboloidal coordinates $(r,\varphi)$ in terms of the half-plane coordinates $(x,y)$:
pol_to_hplane = Pdisk_to_hplane * pol_to_Pdisk
show(X_pol.plot(X_hplane, ranges={r: (0,24)}, style={r: '-', ph: '--'}, nb_values=15,
plot_points=200, color='brown'), xmin=-5, xmax=5, ymin=0, ymax=5, aspect_ratio=1)
The solid curves are those along which $r$ varies while $\varphi$ is kept constant. Conversely, the dashed curves are those along which $\varphi$ varies, while $r$ is kept constant. We notice that the former curves are arcs of circles orthogonal to the half-plane boundary $y=0$, hence they are geodesics of $(\mathbb{H}^2,g)$. This is not surprising since they correspond to the intersections of the hyperboloid with planes through the origin (namely the plane $\varphi=\mathrm{const}$). The point $(x,y) = (0,2)$ corresponds to $r=0$.
We may also depict the Poincaré disk coordinates $(u,v)$ in terms of the half-plane coordinates $(x,y)$:
X_Pdisk_cart.plot(ranges={u: (-1, 0), v: (-1, 0)},
style={u: '-', v: '--'}) + \
X_Pdisk_cart.plot(ranges={u: (-1, 0), v: (0., 1)},
style={u: '-', v: '--'}, color='orange') + \
X_Pdisk_cart.plot(ranges={u: (0, 1), v: (-1, 0)},
style={u: '-', v: '--'}, color='pink') + \
X_Pdisk_cart.plot(ranges={u: (0, 1), v: (0, 1)},
style={u: '-', v: '--'}, color='violet')
show(X_Pdisk_cart.plot(X_hplane, ranges={u: (-1, 0), v: (-1, 0)},
style={u: '-', v: '--'}) + \
X_Pdisk_cart.plot(X_hplane, ranges={u: (-1, 0), v: (0, 1)},
style={u: '-', v: '--'}, color='orange') + \
X_Pdisk_cart.plot(X_hplane, ranges={u: (0, 1), v: (-1, 0)},
style={u: '-', v: '--'}, color='pink') + \
X_Pdisk_cart.plot(X_hplane, ranges={u: (0, 1), v: (0, 1)},
style={u: '-', v: '--'}, color='violet'),
xmin=-5, xmax=5, ymin=0, ymax=5, aspect_ratio=1)
The expression of the metric tensor in the half-plane coordinates $(x,y)$ is
g.display(X_hplane.frame(), X_hplane)
9 charts have been defined on $\mathbb{H}^2$:
H2.atlas()
There are actually 6 main charts, the other ones being subcharts:
H2.top_charts()
The expression of the metric tensor in each of these charts is
for chart in H2.top_charts():
show(g.display(chart.frame(), chart))
For each of these charts, the non-vanishing (and non-redundant w.r.t. the symmetry on the last 2 indices) Christoffel symbols of $g$ are
for chart in H2.top_charts():
show(chart)
show(g.christoffel_symbols_display(chart=chart))