This worksheet 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 (version 1.1, as included in SageMath 8.1).
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
NB: a version of SageMath at least equal to 8.0 is required to run this worksheet:
version()
'SageMath version 8.1, Release Date: 2017-12-07'
First we set up the notebook to display mathematical objects using LaTeX formatting:
%display latex
To increase the computational speed, we ask for demanding computations to be parallelly performed on 8 cores:
Parallelism().set(nproc=8)
We start by declaring $\mathbb{S}^3$ as a differentiable manifold of dimension 3 over $\mathbb{R}$:
S3 = Manifold(3, 'S^3', latex_name=r'\mathbb{S}^3', 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\}$.
print(S3)
3-dimensional differentiable manifold S^3
S3
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\} $$
U = S3.open_subset('U') ; print(U)
Open subset U of the 3-dimensional differentiable manifold S^3
V = S3.open_subset('V') ; print(V)
Open subset V of the 3-dimensional differentiable manifold S^3
We declare that $\mathbb{S}^3 = U \cup V$:
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:
stereoN.<x,y,z> = U.chart()
stereoN
stereoN.coord_range()
Similarly, we introduce on $V$ the coordinates $(x',y',z')$ corresponding to the stereographic projection from the South pole onto the equatorial plane:
stereoS.<xp,yp,zp> = V.chart("xp:x' yp:y' zp:z'")
stereoS
stereoS.coord_range()
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:
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()
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()
:
stereoS_to_N = stereoN_to_S.inverse()
stereoS_to_N.display()
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:
S3.atlas()
For future reference, we store $W=U\cap V$ into a Python variable:
W = U.intersection(V)
print(W)
Open subset W of the 3-dimensional differentiable manifold S^3
$N$ is the point of $V$ of stereographic coordinates $(x',y',z')=(0,0,0)$:
N = V((0,0,0), chart=stereoS, name='N')
print(N)
Point N on the 3-dimensional differentiable manifold S^3
while $S$ is the point of $U$ of stereographic coordinates $(x,y,z)=(0,0,0)$:
S = U((0,0,0), chart=stereoN, name='S')
print(S)
Point S on the 3-dimensional differentiable manifold S^3
We have of course
all([N not in U, N in V, S in U, S not in V])
Let us first declare $\mathbb{R}^4$ as a 4-dimensional manifold covered by a single chart (the so-called Cartesian coordinates):
R4 = Manifold(4, 'R^4', r'\mathbb{R}^4')
X4.<T,X,Y,Z> = R4.chart()
X4
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$:
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()
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:
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 differentiable manifold S^3
spher.<ch,th,ph> = A.chart(r'ch:(0,pi):\chi th:(0,pi):\theta ph:(0,2*pi):\phi')
spher
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()
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,
verbose=True)
Check of the inverse coordinate transformation: ch == 2*arctan(sqrt(-cos(ch) + 1)/sqrt(cos(ch) + 1)) th == arctan2(sqrt(-cos(ch) + 1)*sin(th)/sqrt(cos(ch) + 1), cos(th)*sin(ch)/(cos(ch) + 1)) ph == pi - arctan2(sin(ch)*sin(ph)*sin(th)/(cos(ch) + 1), -cos(ph)*sin(ch)*sin(th)/(cos(ch) + 1)) x == x y == y z == z
spher_to_stereoN.inverse().display()
spher_to_stereoS = stereoN_to_S.restrict(A) * spher_to_stereoN
spher_to_stereoS.display()
stereoS_to_spher = spher_to_stereoN.inverse() * stereoS_to_N.restrict(A)
stereoS_to_spher.display()
Phi.display(stereoN.restrict(A), X4)
Phi.display(spher, X4)
Phi.display()
The vector frames associated with the two stereographic charts are
frameN = stereoN.frame()
frameN
frameS = stereoS.frame()
frameS
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 worksheet 3-sphere: vector fields and left-invariant parallelization. We first declare this vector frame on all $\mathbb{S}^3$:
E = S3.vector_frame('E', latex_symbol=r'\varepsilon')
E
On $U$, we relate this frame to the stereographic coordinate frame, by means of the formulas obtained in the vector field worksheet mentionned above:
E_U = E.restrict(U); E_U
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:
E_V = E.restrict(V); E_V
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:
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)$:
P = U.automorphism_field()
for i in S3.irange():
for j in S3.irange():
P[j,i] = E_U[i][j]
all([E_U[i] == P(frameN[i]) for i in S3.irange()])
We add $P$ to the known changes of frame on $U$:
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')$:
P = V.automorphism_field()
for i in S3.irange():
for j in S3.irange():
P[j,i] = E_V[i][j]
all([E_V[i] == P(frameS[i]) for i in S3.irange()])
V.set_change_of_frame(frameS, E_V, P)
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 worksheet 3-sphere: charts, quaternions and Hopf fibration:
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 differentiable manifold S^3
Hcoord.<eta,alp,bet> = B.chart(r"eta:(0,pi/2):\eta alpha:(0,2*pi):\alpha beta:(0,2*pi):\beta")
Hcoord
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()
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),
verbose=True)
Check of the inverse coordinate transformation: eta == arcsin(sqrt(cos(eta) + 1)*sqrt(-cos(eta) + 1)) alpha == pi - arctan2(2*cos(eta)*sin(alpha)/(cos(eta)*sin(alpha) + 1), -2*cos(alpha)*cos(eta)/(cos(eta)*sin(alpha) + 1)) 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)) x == x y == y z == z
Hcoord_to_stereoN.inverse().display()
As explained in the worksheet 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:
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)
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)
On the contrary, the components of $\varepsilon_3$ are particularly simple:
E_U[3].display(Hcoord.frame(), Hcoord)
The standard metric on $\mathbb{S}^3$ is that induced by the Euclidean metric of $\mathbb{R}^4$. Let us start by defining the latter:
h = R4.metric('h')
h[0,0], h[1,1], h[2,2], h[3, 3] = 1, 1, 1, 1
h.display()
The metric $g$ on $\mathbb{S}^3$ is the pullback of $h$ by the embedding $\Phi$:
g = S3.metric('g')
g.set( Phi.pullback(h) )
print(g)
Riemannian metric g on the 3-dimensional differentiable 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))$:
g.display(frameN)
The components can be factored:
for i in S3.irange():
g[frameN, i,i].factor()
g.display(frameN)
g.display(frameS)
for i in S3.irange():
g[frameS, i,i].factor()
g.display(frameS)
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:
g.display(E_U)
g.display(E_V)
We may then set the components globally:
for i in S3.irange():
g.add_comp(E)[i, i] = 1
g.display(E)
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 worksheet 3-sphere: vector fields and left-invariant parallelization.
g.display(spher.frame(), spher)
g.display(Hcoord.frame(), Hcoord)
We note that the components of $g$ depend only on $\eta$. This implies that the tori $\eta=\mathrm{const}$ are flat tori.
This is necessary for what follows (should be fixed in a future version):
for chart in S3.top_charts():
g.restrict(chart.domain()).inverse()
g.christoffel_symbols_display(stereoN)
g.christoffel_symbols_display(spher)
g.christoffel_symbols_display(Hcoord)
Riem = g.riemann()
print(Riem)
Tensor field Riem(g) of type (1,3) on the 3-dimensional differentiable manifold S^3
Riem.display_comp(frameN, stereoN, only_nonredundant=True)
Riem.display_comp(spher.frame(), spher, only_nonredundant=True)
Riem.display_comp(Hcoord.frame(), Hcoord, only_nonredundant=True)
Riem.display_comp(E_U, stereoN, only_nonredundant=True)
Riem.display(E_U)
Ric = g.ricci()
print(Ric)
Field of symmetric bilinear forms Ric(g) on the 3-dimensional differentiable manifold S^3
Ric.display(frameN)
for i in S3.irange():
Ric[frameN, i,i].factor()
Ric.display(frameN)
We have $Ric(g) = 2 g$:
Ric.restrict(U) == 2 * g.restrict(U)
R_A = g.restrict(A).ricci_scalar()
print(R_A)
Scalar field r(g) on the Open subset A of the 3-dimensional differentiable manifold S^3
R_A.display()
We note that the Ricci scalar is constant, as for any maximally symmetric space.
We have
eps = g.volume_form()
print(eps)
eps.display()
3-form eps_g on the 3-dimensional differentiable manifold S^3
print(eps.parent())
Free module Omega^3(S^3) of 3-forms on the 3-dimensional differentiable manifold S^3
eps.display(frameN, stereoN)
The component $\epsilon_{xyz}$ can be factored:
eps_xyz = eps[frameN, 1,2,3, stereoN]
eps_xyz
eps_xyz.factor()
eps.display(frameN, stereoN)