3-sphere: the round metric

This notebook demonstrates some differential geometry capabilities of SageMath on the example of the 3-dimensional sphere, $\mathbb{S}^3$. The corresponding tools have been developed within the SageManifolds project.

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

NB: a version of SageMath at least equal to 8.3 is required to run this notebook:

In [1]:
version()
Out[1]:
'SageMath version 9.0.beta1, Release Date: 2019-10-12'

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

In [2]:
%display latex

To increase the computational speed, we ask for demanding computations to be parallelly performed on 8 cores:

In [3]:
Parallelism().set(nproc=8)

$\mathbb{S}^3$ as a 3-dimensional Riemannian manifold

We start by declaring $\mathbb{S}^3$ as a Riemannian manifold of dimension 3:

In [4]:
S3 = Manifold(3, 'S^3', latex_name=r'\mathbb{S}^3', structure='Riemannian', 
              start_index=1)

The first argument, 3, is the dimension of the manifold, while the second argument is the symbol used to label the manifold, with the LaTeX output specified by the argument latex_name. The argument start_index sets the index range to be used on the manifold for labelling components w.r.t. a basis or a frame: start_index=1 corresponds to $\{1,2,3\}$; the default value is start_index=0, yielding to $\{0,1,2\}$.

In [5]:
print(S3)
3-dimensional Riemannian manifold S^3
In [6]:
S3
Out[6]:

Coordinate charts on $\mathbb{S}^3$

The 3-sphere cannot be covered by a single chart. At least two charts are necessary, for instance the charts associated with the stereographic projections from two distinct points, $N$ and $S$ say, which we may call the North pole and the South pole respectively. Let us introduce the open subsets covered by these two charts: $$ U := \mathbb{S}^3\setminus\{N\} $$
$$ V := \mathbb{S}^3\setminus\{S\} $$

In [7]:
U = S3.open_subset('U') ; print(U)
Open subset U of the 3-dimensional Riemannian manifold S^3
In [8]:
V = S3.open_subset('V') ; print(V)
Open subset V of the 3-dimensional Riemannian manifold S^3

We declare that $\mathbb{S}^3 = U \cup V$:

In [9]:
S3.declare_union(U, V)

Then we introduce the stereographic chart on $U$, denoting by $(x,y,z)$ the coordinates resulting from the stereographic projection from the North pole onto the equatorial plane:

In [10]:
stereoN.<x,y,z> = U.chart()
stereoN
Out[10]:
In [11]:
stereoN.coord_range()
Out[11]:

Similarly, we introduce on $V$ the coordinates $(x',y',z')$ corresponding to the stereographic projection from the South pole onto the equatorial plane:

In [12]:
stereoS.<xp,yp,zp> = V.chart("xp:x' yp:y' zp:z'")
stereoS
Out[12]:
In [13]:
stereoS.coord_range()
Out[13]:

We have to specify the transition map between the charts stereoN = $(U,(x,y,z))$ and stereoS = $(V,(x',y',z'))$; it is given by the standard inversion formulas:

In [14]:
r2 = x^2+y^2+z^2
stereoN_to_S = stereoN.transition_map(stereoS, 
                                      (x/r2, y/r2, z/r2), 
                                      intersection_name='W',
                                      restrictions1= x^2+y^2+z^2!=0, 
                                      restrictions2= xp^2+yp^2+zp^2!=0)
stereoN_to_S.display()
Out[14]:

In the above declaration, 'W' is the name given to the open subset where the two charts overlap: $W := U\cap V$, the condition $x^2+y^2+z^2\not=0$ defines $W$ as a subset of $U$, and the condition $x'^2+y'^2+z'^2\not=0$ defines $W$ as a subset of $V$.

The inverse coordinate transformation is computed by means of the method inverse():

In [15]:
stereoS_to_N = stereoN_to_S.inverse()
stereoS_to_N.display()
Out[15]:

Note that the situation is of course perfectly symmetric regarding the coordinates $(x,y,z)$ and $(x',y',z')$.

At this stage, the user's atlas has four charts:

In [16]:
S3.atlas()
Out[16]:

For future reference, we store $W=U\cap V$ into a Python variable:

In [17]:
W = U.intersection(V)
print(W)
Open subset W of the 3-dimensional Riemannian manifold S^3

The North and South poles

$N$ is the point of $V$ of stereographic coordinates $(x',y',z')=(0,0,0)$:

In [18]:
N = V((0,0,0), chart=stereoS, name='N')
print(N)
Point N on the 3-dimensional Riemannian manifold S^3

while $S$ is the point of $U$ of stereographic coordinates $(x,y,z)=(0,0,0)$:

In [19]:
S = U((0,0,0), chart=stereoN, name='S')
print(S)
Point S on the 3-dimensional Riemannian manifold S^3

We have of course

In [20]:
all([N not in U, N in V, S in U, S not in V])
Out[20]:

Embedding of $\mathbb{S}^3$ into $\mathbb{R}^4$

Let us first declare $\mathbb{R}^4$ as the 4-dimensional Euclidean space, denoting the Cartesian coordinates by $(T,X,Y,Z)$:

In [21]:
R4.<T,X,Y,Z> = EuclideanSpace(name='R^4', latex_name=r'\mathbb{R}^4', metric_name='h')
X4 = R4.cartesian_coordinates()
X4
Out[21]:

The embedding of $\mathbb{S}^3$ into $\mathbb{R}^4$ is then defined by the standard formulas relating the stereographic coordinates to the ambient Cartesian ones when considering a stereographic projection from the point $(-1,0,0,0)$ to the equatorial plane $T=0$:

In [22]:
rp2 = xp^2 + yp^2 + zp^2
Phi = S3.diff_map(R4, {(stereoN, X4): 
                       [(1-r2)/(r2+1), 2*x/(r2+1), 
                        2*y/(r2+1), 2*z/(r2+1)],
                       (stereoS, X4):
                       [(rp2-1)/(rp2+1), 2*xp/(rp2+1), 
                        2*yp/(rp2+1), 2*zp/(rp2+1)]},
                  name='Phi', latex_name=r'\Phi')
Phi.display()
Out[22]:

Hyperspherical coordinates

The hyperspherical coordinates $(\chi, \theta, \phi)$ generalize the standard spherical coordinates $(\theta, \phi)$ on $\mathbb{S}^2$. They are defined on the open domain $A\subset W \subset \mathbb{S}^3$ that is the complement of the "origin meridian"; since the latter is defined by $y=0$ and $x\geq 0$, we declare:

In [23]:
A = W.open_subset('A', coord_def={stereoN.restrict(W): (y!=0, x<0), 
                                  stereoS.restrict(W): (yp!=0, xp<0)})
print(A)
Open subset A of the 3-dimensional Riemannian manifold S^3
In [24]:
spher.<ch,th,ph> = A.chart(r'ch:(0,pi):\chi th:(0,pi):\theta ph:(0,2*pi):\phi')
spher
Out[24]:
In [25]:
den = 1 + cos(ch)
spher_to_stereoN = spher.transition_map(stereoN.restrict(A), 
                                        (sin(ch)*sin(th)*cos(ph)/den,
                                         sin(ch)*sin(th)*sin(ph)/den,
                                         sin(ch)*cos(th)/den))
spher_to_stereoN.display()
Out[25]:

We also provide the inverse transition map by means of the method set_inverse, which (by default) performs a check that the provided formulas are indeed correct:

In [26]:
spher_to_stereoN.set_inverse(2*atan(sqrt(x^2+y^2+z^2)),
                             atan2(sqrt(x^2+y^2), z),
                             atan2(-y, -x) + pi)
Check of the inverse coordinate transformation:
  ch == 2*arctan(sqrt(-cos(ch) + 1)/sqrt(cos(ch) + 1))  **failed**
  th == arctan2(sqrt(-cos(ch) + 1)*sin(th)/sqrt(cos(ch) + 1), cos(th)*sin(ch)/(cos(ch) + 1))  **failed**
  ph == pi - arctan2(sin(ch)*sin(ph)*sin(th)/(cos(ch) + 1), -cos(ph)*sin(ch)*sin(th)/(cos(ch) + 1))  **failed**
  x == x  *passed*
  y == y  *passed*
  z == z  *passed*
NB: a failed report can reflect a mere lack of simplification.

The check is passed, modulo some lack of trigonometric simplifications in the first three lines.

In [27]:
spher_to_stereoN.inverse().display()
Out[27]:
In [28]:
spher_to_stereoS = stereoN_to_S.restrict(A) * spher_to_stereoN
spher_to_stereoS.display()
Out[28]:
In [29]:
stereoS_to_spher = spher_to_stereoN.inverse() * stereoS_to_N.restrict(A)
stereoS_to_spher.display()
Out[29]:
In [30]:
Phi.display(stereoN.restrict(A), X4)
Out[30]:
In [31]:
Phi.display(spher, X4)
Out[31]:
In [32]:
Phi.display()
Out[32]:

Global left-invariant vector frame on $\mathbb{S}^3$

The vector frames associated with the two stereographic charts are

In [33]:
frameN = stereoN.frame()
frameN
Out[33]:
In [34]:
frameS = stereoS.frame()
frameS
Out[34]:

None of these two frames cover entirely the 3-sphere, since $U$ and $V$ are strict subsets of $\mathbb{S}^3$. Now, as it is well known, $\mathbb{S}^3$ admits global vector frames, i.e. $\mathbb{S}^3$ is a parallelizable manifold. Among all the spheres, it shares this remarkable property with $\mathbb{S}^1$ and $\mathbb{S}^7$. We shall use a global vector frame $(\mathbb{S}^3, (\epsilon_1, \epsilon_2, \epsilon_3))$ associated with the Lie group structure of $\mathbb{S}^3$, namely the left-invariant vector frame constructed in the notebook 3-sphere: vector fields and left-invariant parallelization. We first declare this vector frame on all $\mathbb{S}^3$:

In [35]:
E = S3.vector_frame('E', latex_symbol=r'\varepsilon')
E
Out[35]:

On $U$, we relate this frame to the stereographic coordinate frame, by means of the formulas obtained in the vector field notebook mentioned above:

In [36]:
E_U = E.restrict(U); E_U
Out[36]:
In [37]:
E_U[1][frameN,:,stereoN] = \
    [(x^2-y^2-z^2+1)/2, x*y+z, x*z-y]
E_U[2][frameN,:,stereoN] = \
    [x*y-z, (1-x^2+y^2-z^2)/2, x+y*z]  
E_U[3][frameN,:,stereoN] = \
    [x*z+y, y*z-x, (1-x^2-y^2+z^2)/2]

Similarly, on $V$, we relate the global frame with the stereographic coordinate frame from the South pole:

In [38]:
E_V = E.restrict(V); E_V
Out[38]:
In [39]:
E_V[1][frameS,:, stereoS] = \
    [(yp^2+zp^2-xp^2-1)/2, zp-xp*yp, -yp-xp*zp]
E_V[2][frameS,:, stereoS] = \
    [-zp-xp*yp, (xp^2-yp^2+zp^2-1)/2, xp-yp*zp]
E_V[3][frameS,:, stereoS] = \
    [yp-xp*zp, -xp-yp*zp, (xp^2+yp^2-zp^2-1)/2]

Let us display the links between the various frames:

In [40]:
for i in S3.irange():
    show(E[i].display(frameN))
print(" ")
for i in S3.irange():
    show(E[i].display(frameS))
 

To complete the links, we introduce the change-of-frame operators: first the operator $P$ such that, on $U$, $\epsilon_i = P(\partial/\partial{x^i})$, with $x^i=(x,y,z)$:

In [41]:
P = U.automorphism_field()
for i in S3.irange():
    for j in S3.irange():
        P[j,i] = E_U[i][j]
In [42]:
all([E_U[i] == P(frameN[i]) for i in S3.irange()])
Out[42]:

We add $P$ to the known changes of frame on $U$:

In [43]:
U.set_change_of_frame(frameN, E_U, P)

Similarly, on $V$, we introduce the operator $P$ such that $\epsilon_i = P(\partial/\partial {x'}^i)$, with ${x'}^i=(x',y',z')$:

In [44]:
P = V.automorphism_field()
for i in S3.irange():
    for j in S3.irange():
        P[j,i] = E_V[i][j]
In [45]:
all([E_V[i] == P(frameS[i]) for i in S3.irange()])
Out[45]:
In [46]:
V.set_change_of_frame(frameS, E_V, P)

Hopf coordinates

Despite there does not exist any coordinate chart associated to the global vector frame $(\mathbb{S}^3, (\epsilon_1, \epsilon_2, \epsilon_3))$, there exist a chart, called the Hopf chart, which has some link to it in the sense that some coordinate lines are integral curves of $\epsilon_3$. The Hopf coordinates have been introduced in the notebook 3-sphere: charts, quaternions and Hopf fibration:

In [47]:
B = U.open_subset('B', coord_def={stereoN.restrict(U): 
                                   [x^2+y^2!=0, x^2+y^2+z^2!=1, 
                                    (1-x^2-y^2-z^2)*x-2*y*z!=0]})
print(B)
Open subset B of the 3-dimensional Riemannian manifold S^3
In [48]:
Hcoord.<eta,alp,bet> = B.chart(r"eta:(0,pi/2):\eta alpha:(0,2*pi):\alpha beta:(0,2*pi):\beta")
Hcoord
Out[48]:
In [49]:
Hcoord_to_stereoN = Hcoord.transition_map(
                        stereoN.restrict(U),
                        (sin(eta)*cos(alp+bet)/(1+cos(eta)*sin(alp)),
                         sin(eta)*sin(alp+bet)/(1+cos(eta)*sin(alp)),
                         cos(eta)*cos(alp)/(1+cos(eta)*sin(alp))))
Hcoord_to_stereoN.display()
Out[49]:
In [50]:
Hcoord_to_stereoN.set_inverse(asin(2*sqrt(x^2+y^2)/(1+x^2+y^2+z^2)),
                              atan2(x^2+y^2+z^2-1, -2*z) + pi,
                              atan2(-y,-x) - atan2(x^2+y^2+z^2-1, -2*z))
Check of the inverse coordinate transformation:
  eta == arcsin((cos(eta)*sin(alpha) + 1)*sqrt(cos(eta) + 1)*sqrt(-cos(eta) + 1)/abs(cos(eta)*sin(alpha) + 1))  **failed**
  alpha == pi - arctan2(2*cos(eta)*sin(alpha)/(cos(eta)*sin(alpha) + 1), -2*cos(alpha)*cos(eta)/(cos(eta)*sin(alpha) + 1))  **failed**
  beta == arctan2(2*cos(eta)*sin(alpha)/(cos(eta)*sin(alpha) + 1), -2*cos(alpha)*cos(eta)/(cos(eta)*sin(alpha) + 1)) - arctan2((cos(beta)*sin(alpha) + cos(alpha)*sin(beta))*sin(eta)/(cos(eta)*sin(alpha) + 1), -(cos(alpha)*cos(beta) - sin(alpha)*sin(beta))*sin(eta)/(cos(eta)*sin(alpha) + 1))  **failed**
  x == x  *passed*
  y == y  *passed*
  z == z  *passed*
NB: a failed report can reflect a mere lack of simplification.
In [51]:
Hcoord_to_stereoN.inverse().display()
Out[51]:

As explained in the notebook 3-sphere: vector fields and left-invariant parallelization, due to some lack of simplification, it's better to set the components of $\varepsilon_1$ and $\varepsilon_2$ by hand:

In [52]:
E_U[1].add_comp(Hcoord.frame())[1, Hcoord] = sin(2*alp+bet)
E_U[1].add_comp(Hcoord.frame())[2, Hcoord] = -cos(2*alp+bet) * tan(eta)
E_U[1].add_comp(Hcoord.frame())[3, Hcoord] = cos(2*alp+bet) / (cos(eta)*sin(eta))
E_U[1].display(Hcoord.frame(), Hcoord)
Out[52]:
In [53]:
E_U[2].add_comp(Hcoord.frame())[1, Hcoord] = -cos(2*alp+bet)
E_U[2].add_comp(Hcoord.frame())[2, Hcoord] = -sin(2*alp+bet) * tan(eta)
E_U[2].add_comp(Hcoord.frame())[3, Hcoord] = sin(2*alp+bet) / (cos(eta)*sin(eta))
E_U[2].display(Hcoord.frame(), Hcoord)
Out[53]:

On the contrary, the components of $\varepsilon_3$ are particularly simple:

In [54]:
E_U[3].display(Hcoord.frame(), Hcoord)
Out[54]:

Round metric on $\mathbb{S}^3$

The standard metric on $\mathbb{S}^3$ is that induced by the Euclidean metric of $\mathbb{R}^4$. The latter is obtained by:

In [55]:
h = R4.metric()
h.display()
Out[55]:

The metric $g$ on $\mathbb{S}^3$ is computed as being the pullback of $h$ by the embedding $\Phi$:

In [56]:
g = S3.metric()
g.set( Phi.pullback(h) )
print(g)
Riemannian metric g on the 3-dimensional Riemannian manifold S^3

Let us display $g$ in terms of the coordinate frame associated with the stereographic chart from the North pole $(U,(x,y,z))$:

In [57]:
g.display(frameN)
Out[57]:

The components can be factored:

In [58]:
for i in S3.irange():
    g[frameN, i,i].factor()
g.display(frameN)
Out[58]:
In [59]:
g.display(frameS)
Out[59]:
In [60]:
for i in S3.irange():
    g[frameS, i,i].factor()
g.display(frameS)
Out[60]:

Expression of the round metric in the global frame

The expression of $g$ in terms of the global frame $(\varepsilon_1, \varepsilon_2, \varepsilon_3)$ is deduced from that w.r.t. the stereographic coordinates on the two domains $U$ and $V$, where the change-of-frame formulas are known:

In [61]:
g.display(E_U)
Out[61]:
In [62]:
g.display(E_V)
Out[62]:

We may then set the components globally:

In [63]:
for i in S3.irange():
    g.add_comp(E)[i, i] = 1
g.display(E)
Out[63]:

The above shows that the global frame $(\varepsilon_1, \varepsilon_2, \varepsilon_3)$ is orthonormal (w.r.t. to $g$), which is not surprising since this frame is induced by three vector fields of $\mathbb{R}^4$ which are clearly orthonormal for the Euclidean metric $h$, namely the three vector fields $E_{\mathbf{i}}$, $E_{\mathbf{j}}$ and $E_{\mathbf{k}}$ in the notebook 3-sphere: vector fields and left-invariant parallelization.

Expression of the round metric in terms of the hyperspherical coordinates

In [64]:
g.display(spher.frame(), spher)
Out[64]:

Expression of the round metric in terms of the Hopf coordinates

In [65]:
g.display(Hcoord.frame(), Hcoord)
Out[65]:

We note that the components of $g$ depend only on $\eta$. This implies that the tori $\eta=\mathrm{const}$ are flat tori.

The Christoffel symbols

This is necessary for what follows (should be fixed in a future version):

In [66]:
for chart in S3.top_charts():
    g.restrict(chart.domain()).inverse()
In [67]:
g.christoffel_symbols_display(stereoN)
Out[67]:
In [68]:
g.christoffel_symbols_display(spher)
Out[68]:
In [69]:
g.christoffel_symbols_display(Hcoord)
Out[69]:

The Riemann tensor

In [70]:
Riem = g.riemann()
print(Riem)
Tensor field Riem(g) of type (1,3) on the 3-dimensional Riemannian manifold S^3
In [71]:
Riem.display_comp(frameN, stereoN, only_nonredundant=True)
Out[71]:
In [72]:
Riem.display_comp(spher.frame(), spher, only_nonredundant=True)
Out[72]:
In [73]:
Riem.display_comp(Hcoord.frame(), Hcoord, only_nonredundant=True)
Out[73]:
In [74]:
Riem.display_comp(E_U, stereoN, only_nonredundant=True)
Out[74]:
In [75]:
Riem.display(E_U)
Out[75]:

The Ricci tensor

In [76]:
Ric = g.ricci()
print(Ric)
Field of symmetric bilinear forms Ric(g) on the 3-dimensional Riemannian manifold S^3
In [77]:
Ric.display(frameN)
Out[77]:
In [78]:
for i in S3.irange():
    Ric[frameN, i,i].factor()
Ric.display(frameN)
Out[78]:

We have $Ric(g) = 2 g$:

In [79]:
Ric.restrict(U) == 2 * g.restrict(U)
Out[79]:

The Ricci scalar

In [80]:
R_A = g.restrict(A).ricci_scalar()
print(R_A)
Scalar field r(g) on the Open subset A of the 3-dimensional Riemannian manifold S^3
In [81]:
R_A.display()
Out[81]:

We note that the Ricci scalar is constant, as for any maximally symmetric space.

The volume 3-form (Levi-Civita tensor)

We have

In [82]:
eps = S3.volume_form()
print(eps)
eps.display()
3-form eps_g on the 3-dimensional Riemannian manifold S^3
Out[82]:
In [83]:
print(eps.parent())
Free module Omega^3(S^3) of 3-forms on the 3-dimensional Riemannian manifold S^3

Expression of the volume 3-form in terms of the stereographic coordinates

In [84]:
eps.display(frameN, stereoN)
Out[84]:

The component $\epsilon_{xyz}$ can be factored:

In [85]:
eps_xyz = eps[frameN, 1,2,3, stereoN]
eps_xyz
Out[85]:
In [86]:
eps_xyz.factor()
Out[86]:
In [87]:
eps.display(frameN, stereoN)
Out[87]: