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 (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, via the command sage -n jupyter
NB: a version of SageMath at least equal to 7.5 is required to run this worksheet:
version()
'SageMath version 8.3, Release Date: 2018-08-03'
First we set up the notebook to display mathematical objects using LaTeX formatting:
%display latex
We also define a viewer for 3D plots (use 'threejs'
or 'jmol'
for interactive 3D graphics):
viewer3D = 'threejs' # must be 'threejs', jmol', 'tachyon' or None (default)
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()
From this choice of stereographic projection, the "North" pole is actually the point of coordinates $(-1,0,0,0)$ in $\mathbb{R}^4$:
X4(Phi(N))
while the "South" pole is the point of coordinates $(1,0,0,0)$:
X4(Phi(S))
We may use the embedding $\Phi$ to plot the stereographic coordinate grid in terms of the $\mathbb{R}^4$'s Cartesian coordinates:
graph_stereoN = stereoN.plot(chart=X4, mapping=Phi,
ambient_coords=(X,Y,Z),
number_values=9,
color={x: 'red', y: 'green', z: 'gold'},
label_axes=False)
show(graph_stereoN, viewer=viewer3D, online=True, axes_labels=['X', 'Y', 'Z'])
graph_stereoN = stereoN.plot(chart=X4, mapping=Phi,
ambient_coords=(X,Y,T),
number_values=13, plot_points=150,
color={x: 'red', y: 'green', z: 'gold'},
label_axes=False)
pointN = N.plot(chart=X4, mapping=Phi, ambient_coords=(X,Y,T),
color='maroon', label_offset=0.05)
pointS = S.plot(chart=X4, mapping=Phi, ambient_coords=(X,Y,T),
color='maroon', label_offset=0.05)
show(graph_stereoN + pointN + pointS, viewer=viewer3D, online=True,
axes_labels=['X', 'Y', 'T'])
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
We then declare the chart $(A,(\chi,\theta,\phi))$ by specifying the intervals spanned by the various coordinates:
spher.<ch,th,ph> = A.chart(r'ch:(0,pi):\chi th:(0,pi):\theta ph:(0,2*pi):\phi')
spher
spher.coord_range()
The specification of the hyperspherical coordinates is completed by providing the transition map to the stereographic chart $(A,(x,y,z))$:
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()
We also provide the inverse transition map, asking to check that the provided formulas are indeed correct (argument verbose=True
):
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
The check is passed, modulo some lack of trigonometric simplifications in the first three lines.
spher_to_stereoN.inverse().display()
The transition map $(A,(\chi,\theta,\phi))\rightarrow (A,(x',y',z'))$ is obtained by combining the transition maps $(A,(\chi,\theta,\phi))\rightarrow (A,(x,y,z))$ and $(A,(x,y,z))\rightarrow (A,(x',y',z'))$:
spher_to_stereoS = stereoN_to_S.restrict(A) * spher_to_stereoN
spher_to_stereoS.display()
Similarly, the transition map $(A,(x',y',z'))\rightarrow (A,(\chi,\theta,\phi))$ is obtained by combining the transition maps $(A,(x',y',z'))\rightarrow (A,(x,y,z))$ and $(A,(x,y,z))\rightarrow (A,(\chi,\theta,\phi))$:
stereoS_to_spher = spher_to_stereoN.inverse() * stereoS_to_N.restrict(A)
stereoS_to_spher.display()
At this stage, the user atlas of $\mathbb{S}^3$ is
S3.atlas()
Let us get the coordinate expression of the restriction of the embedding $\Phi$ to $A$:
Phi.display(stereoN.restrict(A), X4)
Phi.display(spher, X4)
Phi.display()
First let us plot the chart $(A,(\chi,\theta,\phi))$ in terms of the stereographic chart $(U,(x,y,z))$ (notice that the "point at infinity" corresponds to $\chi\rightarrow \pi$, hence the $0.9$ truncation in the $\chi$ range)::
graph = spher.plot(stereoN,
number_values=7,
ranges={ch: (0, 0.9*pi)},
color={ch: 'green', th: 'blue', ph: 'red'},
label_axes=False)
show(graph, viewer=viewer3D, online=True, axes_labels=['x', 'y', 'z'])
In terms of the stereographic coordinates $(V, (x',y',z'))$ (notice that the "point at infinity" corresponds then to $\chi\rightarrow 0$):
graph = spher.plot(stereoS,
number_values=7,
ranges={ch: (0.1, pi)},
color={ch: 'green', th: 'blue', ph: 'red'},
label_axes=False)
show(graph, viewer=viewer3D, online=True, axes_labels=["x'", "y'", "z'"])
Of course we may use the embeddding $\Phi$ to get views of the hyperspherical coordinates in terms of the Cartesian coordinates $(T,X,Y,Z)$ of $\mathbb{R}^4$:
graph = spher.plot(X4, mapping=Phi,
ambient_coords=(X,Y,T),
number_values=7,
color={ch: 'green', th: 'blue', ph: 'red'},
label_axes=False)
show(graph, viewer=viewer3D, online=True, axes_labels=['X', 'Y', 'T'])
graph = spher.plot(X4, mapping=Phi,
ambient_coords=(X,Y,Z),
number_values=7,
color={ch: 'green', th: 'blue', ph: 'red'},
label_axes=False)
show(graph, viewer=viewer3D, online=True, axes_labels=['X', 'Y', 'Z'])
We will need some projection operator from (a subset of) $\mathbb{R}^4$ to $\mathbb{S}^3$. Let $\mathbb{R}^4_N$ be $\mathbb{R}^4$ minus the hyperplane $T=-1$:
R4N = R4.open_subset('R4N', latex_name=r'\mathbb{R}^4_N',
coord_def={X4: T!=-1})
X4N = X4.restrict(R4N)
ProjN = R4N.diff_map(U, {(X4N, stereoN):
[X/(1+T), Y/(1+T), Z/(1+T)]},
name='P_N', latex_name=r'\Pi_N')
ProjN.display()
Let us check that once applied to an embedded point of $U\subset \mathbb{S}^3$, this projection reduces to the identity:
var('a b c', domain='real')
p = S3((a,b,c), chart=stereoN)
ProjN(Phi(p)) == p
q = R4((sqrt(3)/2, sin(a)*cos(b)/2, sin(a)*sin(b)/2, cos(a)/2))
X4(q)
all([q in R4N, Phi(ProjN(q)) == q])
We consider the (division) algebra of quaternions $\mathbb{H}$ as $\mathbb{R}^4$ endowed with the following (non-commutative) product:
def qprod(p,q):
if p in R4 and q in R4:
T1, X1, Y1, Z1 = X4(p)
T2, X2, Y2, Z2 = X4(q)
return R4(((T1*T2-X1*X2-Y1*Y2-Z1*Z2).simplify_full(),
(T1*X2+X1*T2+Y1*Z2-Z1*Y2).simplify_full(),
(T1*Y2-X1*Z2+Y1*T2+Z1*X2).simplify_full(),
(T1*Z2+X1*Y2-Y1*X2+Z1*T2).simplify_full()))
if p in S3 and q in S3:
a = qprod(Phi(p),Phi(q))
if X4(a) == (-1,0,0,0):
return N
return ProjN(R4N(a))
raise ValueError("Cannot evaluate qprod of {} and {}".format(p,q))
Note that we have extended the definition of the quaternionic product to $\mathbb{S}^3$ via the embedding $\Phi$.
Let us introduce two special points on $\mathbb{S}^3$: $\mathbf{1}$ and $-\mathbf{1}$.
One = S3((0,0,0), chart=stereoN, name='1', latex_name=r'\mathbf{1}')
X4(Phi(One))
As we can see from the Cartesian coordinates of $\Phi(\mathbf{1})$, the point $\mathbf{1}$ is actually nothing but the "South" pole used to define the stereographic chart $(V,(x',y',z'))$:
One == S
minusOne = S3((0,0,0), chart=stereoS, name='-1', latex_name=r'-\mathbf{1}')
X4(Phi(minusOne))
The point $\mathbf{-1}$ is thus nothing but the "North" pole used to define the stereographic chart $(U,(x,y,z))$:
minusOne == N
Next we introduce points $\mathbf{i}$, $\mathbf{j}$ and $\mathbf{k}$ on $\mathbb{S}^3$:
I = S3((1,0,0), chart=stereoN, name='i', latex_name=r'\mathbf{i}')
X4(Phi(I))
stereoS(I)
J = S3((0,1,0), chart=stereoN, name='j', latex_name=r'\mathbf{j}')
X4(Phi(J))
stereoS(J)
Since $\mathbf{j}$ lies in $A$, contrary to $\mathbf{i}$, we may ask for its hyperspherical coordinates:
spher(J)
K = S3((0,0,1), chart=stereoN, name='k', latex_name=r'\mathbf{k}')
X4(Phi(K))
stereoS(K)
Hamilton's fundamental relations $$ \mathbf{i} \mathbf{j} \mathbf{k} = \mathbf{-1} $$ $$ \mathbf{i} \mathbf{j} = \mathbf{k},\quad \mathbf{j} \mathbf{k} = \mathbf{i}, \quad \mathbf{k} \mathbf{i} = \mathbf{j}$$ are satisfied:
qprod(I, qprod(J,K)) == minusOne
all([qprod(I,J) == K, qprod(J,K) == I,
qprod(K,I) == J])
These relations imply $\mathbf{i}^2 = \mathbf{-1}$, $\mathbf{j}^2 = \mathbf{-1}$ and $\mathbf{k}^2 = \mathbf{-1}$:
all([qprod(One,One) == One, qprod(I,I) == minusOne,
qprod(J,J) == minusOne, qprod(K,K) == minusOne])
Let us introduce $\mathbf{-i}$, $\mathbf{-j}$ and $\mathbf{-k}$, as points of $\mathbb{S}^3$:
minusI = qprod(minusOne, I)
X4(Phi(minusI))
minusJ = qprod(minusOne, J)
X4(Phi(minusJ))
minusK = qprod(minusOne, K)
X4(Phi(minusK))
In the comments below (but not in the SageMath code), we shall identify $\mathbf{1}\in \mathbb{S}^3$ with $\Phi(\mathbf{1})\in \mathbb{R}^4$, $\mathbf{i}\in \mathbb{S}^3$ with $\Phi(\mathbf{i})\in \mathbb{R}^4$, etc. In particular, we consider $(\mathbf{1}, \mathbf{i}, \mathbf{j},\mathbf{k})$ as a basis of the quaternion algebra $\mathbb{H}$.
The conjugate of a quaternion $q = T + X\mathbf{i} + Y\mathbf{j} + Z\mathbf{k}$ is $\bar{q} = T - X\mathbf{i} - Y\mathbf{j} - Z\mathbf{k}$; hence we define:
def qconj(p):
if p in R4:
T, X, Y, Z = X4(p)
return R4((T, -X, -Y, -Z))
if p in S3:
a = qconj(Phi(p))
if X4(a) == (-1,0,0,0):
return N
return ProjN(a)
raise ValueError("Cannot evaluate qconf of {}".format(p))
In particular, we have $\bar{\mathbf{1}} = \mathbf{1}$, $\bar{\mathbf{i}} = -\mathbf{i}$, $\bar{\mathbf{j}} = -\mathbf{j}$ and $\bar{\mathbf{k}} = -\mathbf{k}$:
all([qconj(One) == One,
qconj(I) == minusI,
qconj(J) == minusJ,
qconj(K) == minusK])
The conjugate of an element of $\mathbb{S}^3$
assume(a != 0) # to ensure that qconj(p) is not N
p = S3((a,b,c), chart=stereoN)
stereoN(qconj(p))
p = S3((a,b,c), chart=stereoS)
stereoS(qconj(p))
forget(a!=0)
The quaternionic norm $\| q\| = \sqrt{q\bar{q}}$ coincide with the Euclidean norm in $\mathbb{R}^4$, so that $\mathbb{S}^3$ can be viewed as the set of unit quaternions; hence we define:
def qnorm(p):
if p in R4:
T, X, Y, Z = X4(p)
return (sqrt(T^2 + X^2 + Y^2 + Z^2)).simplify_full()
if p in S3:
return 1
raise ValueError("Cannot evaluate qnorm of {}".format(p))
var('d', domain='real')
q = R4((a,b,c,d))
qnorm(q)
Let us check that $\| q\|^2 = q\bar{q}$:
R4((qnorm(q)^2,0,0,0)) == qprod(q, qconj(q))
As elements of $\mathbb{S}^3$, $\mathbf{1}$, $\mathbf{i}$, $\mathbf{j}$ and $\mathbf{k}$ have all unit norm:
(qnorm(One), qnorm(I), qnorm(J), qnorm(K)) == (1, 1, 1, 1)
We shall define the Hopf map by considering first the map $$ \begin{array}{cccc} C: & \mathbb{R}^4 & \to & \mathbb{R}^4\\ & p & \mapsto & p \mathbf{k} \bar{p} \end{array} $$ The coordinate expression of $C$ is obtained as follows:
p = R4((T,X,Y,Z)) # a generic point of R^4
coord_Cp = X4( qprod(p, qprod(Phi(K), qconj(p))) )
coord_Cp
Therefore we define $C$ as
C = R4.diff_map(R4, coord_Cp, name='C')
C.display()
The restriction of $C$ to $\Phi(\mathbb{S}^3)\subset \mathbb{R}^4$ can be viewed as the map $C\circ \Phi: \mathbb{S}^3 \to \mathbb{R}^4$ :
CS = C * Phi
CS.display()
On the above coordinate expressions, we note that the codomain of $C\circ \Phi$ lies in the hyperplane $T=0$, i.e. in the set $\operatorname{Im}\mathbb{H}$ of pure imaginary quaternions. Moreover, if we consider a generic point $p\in U\subset\mathbb{S}^3$:
p = S3((a,b,c), chart=stereoN)
we have $\| C\circ\Phi(p) \| = 1$:
qnorm(CS(p))
For the only point of $\mathbb{S}^3$ not lying in $U$, i.e. $N = -\mathbf{1}$, we have as well
qnorm(CS(N))
Hence the codomain of $C \circ\Phi$ lies in $\Phi(\mathbb{S}^3)$. From the previous result, we conclude that it actually lies in $\Phi(\mathbb{S}^3)\cap \operatorname{Im}\mathbb{H}$, which is a 2-sphere: the 2-sphere of unit imaginary quaternions.
In particular, we have:
all([CS(K) == Phi(K), CS(One) == Phi(K), CS(minusOne) == Phi(K)])
all([CS(I) == Phi(minusK), CS(J) == Phi(minusK),
CS(minusI) == Phi(minusK), CS(minusJ) == Phi(minusK)])
On $\Phi(\mathbb{S}^3)\cap \operatorname{Im}\mathbb{H}$, the inverse embedding $\Phi^{-1}$ coincides with the projector $\Pi_N$ introduced above since $(\Phi(\mathbb{S}^3)\cap \operatorname{Im}\mathbb{H})\subset \mathbb{R}^4_N$. Hence the map $H = \Phi^{-1}\circ C \circ \Phi: \mathbb{S}^3 \to \mathbb{S}^3$ can be obtained as $\Pi_N\circ C \circ \Phi$:
H = ProjN * CS.restrict(S3, subcodomain=R4N)
Note that we have used the method restrict
with the argument subcodomain=R4N
to declare that the codomain of $C\circ\Phi$ actually lies in $\mathbb{R}^4_N$, so that the composition with $\Pi_N$ is well defined.
We have
H.display()
Actually since neither $N$ (which has $T=-1$) nor $S$ (which has $T=1$) lie in the codomain of $C\circ\Phi$, we may safely declare that the codomain of $H$ is $W = U\cap V$:
H = H.restrict(S3, subcodomain=W)
H.display()
We have $H(\mathbf{k})=H(\mathbf{1})=H(-\mathbf{k})=H(-\mathbf{1})=\mathbf{k}$:
all([H(K) == K, H(One) == K, H(minusK) == K,
H(minusOne) == K])
and $H(\mathbf{i})=H(\mathbf{j})=-\mathbf{k}$:
all([H(I) == minusK, H(J) == minusK])
Let us consider the expression of $H$ in stereographic coordinates:
Hx, Hy, Hz = H.expr(stereoN, stereoN)
(Hx.factor(), Hy.factor(), Hz.factor())
We have
(Hx^2 + Hy^2 + Hz^2).simplify_full()
which shows that the codomain of $H$ lies in the 2-sphere of equation $x^2+y^2+z^2=1$ in stereographic coordinates. This is not surprising since (i) the equation of the 2-sphere of unit imaginary quaternions, $\Phi(\mathbb{S}^3)\cap \operatorname{Im}\mathbb{H}$, is $T=0$ and $X^2+Y^2+Z^2=1$ and (ii) for $T=0$, we have $x=X$, $y=Y$ and $z=Z$. Let us construct this 2-sphere as a manifold by itself, which we call the base 2-sphere. This will be the codomain of the Hopf map.
S2 = Manifold(2, 'S^2', latex_name=r'\mathbb{S}^2')
print(S2)
2-dimensional differentiable manifold S^2
As for $\mathbb{S}^3$, we introduce on $\mathbb{S}^2$ two complementary stereographic coordinate systems:
U2 = S2.open_subset('U_2')
V2 = S2.open_subset('V_2')
S2.declare_union(U2, V2)
stereoN2.<x2,y2> = U2.chart("x2:x_2 y2:y_2")
stereoN2
stereoS2.<xp2,yp2> = V2.chart(r"xp2:{x'}_2 yp2:{y'}_2")
stereoS2
stereoN_to_S2 = stereoN2.transition_map(stereoS2,
(x2/(x2^2+y2^2), y2/(x2^2+y2^2)),
intersection_name='W_2',
restrictions1= x2^2+y2^2!=0,
restrictions2= xp2^2+xp2^2!=0)
stereoN_to_S2.display()
stereoS_to_N2 = stereoN_to_S2.inverse()
stereoS_to_N2.display()
W2 = U2.intersection(V2)
We embed the base 2-sphere $\mathbb{S}^2$ in $\mathbb{S}^3$ by considering that the North pole defining the above stereographic coordinates is $\mathbf{k}$, i.e. the point $(x,y,z)=(0,0,1)$ in $\mathbb{S}^3$:
Phi2 = S2.diff_map(S3, {(stereoN2, stereoN):
[2*x2/(1+x2^2+y2^2),
2*y2/(1+x2^2+y2^2),
(x2^2+y2^2-1)/(1+x2^2+y2^2)],
(stereoS2, stereoN):
[2*xp2/(1+xp2^2+yp2^2),
2*yp2/(1+xp2^2+yp2^2),
(1-xp2^2-yp2^2)/(1+xp2^2+yp2^2)]},
name='Phi2', latex_name=r'\Phi_2')
Phi2.display()
The unit imaginary quaternions $\mathbf{i}$, $\mathbf{j}$, $\mathbf{k}$ and $-\mathbf{k}$ as elements of the base 2-sphere:
I2 = S2((1,0), chart=stereoN2)
J2 = S2((0,1), chart=stereoN2)
K2 = S2((0,0), chart=stereoS2)
minusK2 = S2((0,0), chart=stereoN2)
all([Phi2(I2) == I, Phi2(J2) == J,
Phi2(K2) == K, Phi2(minusK2) == minusK])
We introduce spherical coordinates $(\theta_2,\phi_2)$ on the base 2-sphere in the standard way (cf. the 2-sphere worksheet):
A2 = W2.open_subset('A_2',
coord_def={stereoN2.restrict(W2): (y2!=0, x2<0),
stereoS2.restrict(W2): (yp2!=0, xp2<0)})
spher2.<th2,ph2> = A2.chart(r'th2:(0,pi):\theta_2 ph2:(0,2*pi):\phi_2')
spher2_to_stereoN2 = spher2.transition_map(stereoN2.restrict(A2),
(sin(th2)*cos(ph2)/(1-cos(th2)),
sin(th2)*sin(ph2)/(1-cos(th2))))
spher2_to_stereoN2.set_inverse(2*atan(1/sqrt(x2^2+y2^2)),
atan2(-y2,-x2)+pi)
spher2_to_stereoS2 = stereoN_to_S2.restrict(A2) * spher2_to_stereoN2
stereoS2_to_spher2 = spher2_to_stereoN2.inverse() * \
stereoN_to_S2.inverse().restrict(A2)
A2.atlas()
Let $W_{z\not=1}$ denote the subset of $W\subset\mathbb{S}^3$ defined by $z\not=1$:
Wz1 = W.open_subset('Wz1', latex_name=r'W_{z\not=1}',
coord_def={stereoN.restrict(W): z!=1})
all([I in Wz1, J in Wz1, minusK in Wz1,
K not in Wz1, One not in Wz1, minusOne not in Wz1])
Proj2N = Wz1.diff_map(U2, {(stereoN.restrict(Wz1), stereoN2):
[x/(1-z), y/(1-z)]},
name='P_2^N', latex_name=r'\Pi_2^N')
Proj2N.display()
p = U2((a,b), chart=stereoN2)
Proj2N(Phi2(p)) == p
assume(cos(a)!=1, cos(a)!=0)
p = U((sin(a)*cos(b), sin(a)*sin(b), cos(a)), chart=stereoN)
Phi2(Proj2N(p)) == p
forget(cos(a)!=1, cos(a)!=0)
Let $W_{z\not=-1}$ denote the subset of $W\subset\mathbb{S}^3$ defined by $z\not=-1$:
Wzm1 = W.open_subset('Wzm1', latex_name=r'W_{z\not=-1}',
coord_def={stereoN.restrict(W): z!=-1})
all([I in Wzm1, J in Wzm1, K in Wzm1,
minusK not in Wzm1, One not in Wzm1,
minusOne not in Wzm1])
Proj2S = Wzm1.diff_map(V2, {(stereoN.restrict(Wzm1), stereoS2):
[x/(1+z), y/(1+z)]},
name='P_2^S', latex_name=r'\Pi_2^S')
Proj2S.display()
p = V2((a,b), chart=stereoS2)
Proj2S(Phi2(p)) == p
assume(cos(a)!=-1, cos(a)!=0)
p = U((sin(a)*cos(b), sin(a)*sin(b), cos(a)), chart=stereoN)
Phi2(Proj2S(p)) == p
forget(cos(a)!=-1, cos(a)!=0)
We are now in position to define the Hopf map as a map $\mathbb{S}^3 \to \mathbb{S}^2$.
To give its coordinate expressions, we have to consider that $\mathbb{S}^3$ is covered by
two charts, stereoN
= $(U,(x,y,z))$ and stereoS
= $(V,(x',y',z'))$, and $\mathbb{S}^2$ is covered by two charts:
stereoN2
= $(U_2,(x_2,y_2))$, the domain of which contains
all points of $\mathbb{S}^2$ but $\mathbf{k}$stereoS2
= $(V_2,(x'_2,y'_2))$, the domain of which contains
all points of $\mathbb{S}^2$ but $-\mathbf{k}$.First we search for all the points $p\in U$ such that $H(p)\in U_2$, i.e. such that $H(p)\not=\mathbf{k}$, or equivalently, $z(H(p))\not= 1$, or again $H(p)\in W_{z\not=1}$. On the chart $(U,(x,y,z))$, the expression of $z(H(p))$ is
Hx, Hy, Hz = H.expr(stereoN, stereoN)
Hz
The condition $z(H(p))\not=1$ is
Hz.numerator() - Hz.denominator() != 0
which is equivalent to $x^2+y^2 \not= 0$. We define thus the subdomain $D_1 = U \setminus (H^{-1}(\mathbf{k})\cap U)$ as
D1 = U.open_subset('D_1', coord_def=({stereoN: x^2+y^2!=0}))
stereoN_D1 = stereoN.restrict(D1)
By construction, the restriction of $H$ to $D_1$ has $W_{z\not=1}$ as codomain and we declare the Hopf map on $D_1$ by considering the image points as points of $\mathbb{S}^2$ via $\Pi_2^N$:
hD1 = Proj2N * H.restrict(D1, subcodomain=Wz1)
hD1.display()
We have $\mathbf{i}\in D_1$ and $\mathbf{j}\in D_1$; we can check that $h(\mathbf{i}) = h(\mathbf{j}) = -\mathbf{k}$:
all([hD1(I) == minusK2, hD1(J) == minusK2])
Let us now consider the points $p\in U$ such that $H(p)\in V_2$, i.e. such that $H(p)\not=-\mathbf{k}$, or equivalently, $z(H(p))\not= -1$, or again $H(p)\in W_{z\not=-1}$. The condition $z(H(p))\not= -1$ is equivalent to $s\not=0$ with
s = ((Hz.numerator() + Hz.denominator())/2).simplify_full()
s
Since
(s-(x^2+y^2+z^2-1)^2).simplify_full()
the condition $s\not =0$ is equivalent to
(x^2+y^2+z^2-1)^2 + 4*z^2 != 0
i.e. to ($x^2+y^2\not= 1$ or $z\not= 0$). Hence we introduce the subset $D_2 = U \setminus (H^{-1}(-\mathbf{k})\cap U)$ by
D2 = U.open_subset('D_2', coord_def=({stereoN: (x^2+y^2!=1, z!=0)}))
stereoN_D2 = stereoN.restrict(D2)
By construction, the restriction of $H$ to $D_2$ has $W_{z\not=-1}$ as codomain and we declare the Hopf map on $D_2$ by considering the image points as points of $\mathbb{S}^2$ via $\Pi_2^S$:
hD2 = Proj2S * H.restrict(D2, subcodomain=Wzm1)
hD2.display()
We have $\mathbf{k}\in D_2$, $-\mathbf{k}\in D_2$ and $\mathbf{1}\in D_2$; we can check that $h(\mathbf{k}) = h(-\mathbf{k}) = h(\mathbf{1}) = \mathbf{k}$:
all([hD2(K) == K2, hD2(minusK) == K2, hD2(One) == K2])
Since $H^{-1}(\mathbf{k})\cap H^{-1}(-\mathbf{k})=\emptyset$, we have $U=D_1\cup D_2$:
U.declare_union(D1, D2)
Similarly let us consider the points $p\in V$ such that $H(p)\in U_2$, i.e. such that $H(p)\not=\mathbf{k}$, or equivalently, $z(H(p))\not= 1$, or again $H(p)\in W_{z\not=1}$. On the chart $(V,(x',y',z'))$, the expression of $z(H(p))$ is
Hx, Hy, Hz = H.expr(stereoS, stereoN)
Hz
The condition $z(H(p))\not=1$ is
Hz.numerator() - Hz.denominator() != 0
which is equivalent to ${x'}^2+{y'}^2 \not= 0$. We define thus the subset $D_3 = V \setminus (H^{-1}(\mathbf{k})\cap V)$ as
D3 = V.open_subset('D_3', coord_def=({stereoS: xp^2+yp^2!=0}))
stereoS_D3 = stereoS.restrict(D3)
By construction, the restriction of $H$ to $D_2$ has $W_{z\not=1}$ as codomain and we declare the Hopf map on $D_3$ by considering the image points as points of $\mathbb{S}^2$ via $\Pi_2^N$:
hD3 = Proj2N * H.restrict(D3, subcodomain=Wz1)
hD3.display()
We have $\mathbf{i}\in D_3$ and $\mathbf{j}\in D_3$; we can check that $h(\mathbf{i}) = h(\mathbf{j}) = -\mathbf{k}$:
all([hD3(I) == minusK2, hD3(J) == minusK2])
Finally, let us consider the points $p\in V$ such that $H(p)\in V_2$, i.e. such that $H(p)\not=-\mathbf{k}$, or equivalently, $z(H(p))\not= -1$, or again $H(p)\in W_{z\not=-1}$. The condition $z(H(p))\not= -1$ is equivalent to $s\not=0$ with
s = ((Hz.numerator() + Hz.denominator())/2).simplify_full()
s
Since
(s-(xp^2+yp^2+zp^2-1)^2).simplify_full()
the condition $s\not=0$ is equivalent to
(xp^2+yp^2+zp^2-1)^2 + 4*zp^2 == 0
i.e. to (${x'}^2+{y'}^2\not= 1$ or $z'\not= 0$). Hence we introduce the subset $D_4 = V \setminus (H^{-1}(-\mathbf{k})\cap V)$ by
D4 = V.open_subset('D_4', coord_def=({stereoS: (xp^2+yp^2!=1, zp!=0)}))
stereoS_D4 = stereoS.restrict(D4)
By construction, the restriction of $H$ to $D_4$ has $W_{z\not=-1}$ as codomain and we declare the Hopf map on $D_4$ by considering the image points as points of $\mathbb{S}^2$ via $\Pi_2^S$:
hD4 = Proj2S * H.restrict(D4, subcodomain=Wzm1)
hD4.display()
We have $-\mathbf{1}\in D_4$ and we can check that $h(-\mathbf{1}) = \mathbf{k}$:
hD4(minusOne) == K2
Since $H^{-1}(\mathbf{k})\cap H^{-1}(-\mathbf{k})=\emptyset$, we have $V=D_3\cup D_4$:
V.declare_union(D3, D4)
We construct the Hopf map $h:\mathbb{S}^3\to \mathbb{S}^2$ from the coordinate expressions of its restriction to $D_1$, $D_2$, $D_3$ and $D_4$, as obtained above:
h = S3.diff_map(S2, name='h')
h.add_expression(stereoN_D1, stereoN2, hD1.expr(stereoN_D1, stereoN2))
h.add_expression(stereoN_D2, stereoS2, hD2.expr(stereoN_D2, stereoS2))
h.add_expression(stereoS_D3, stereoN2, hD3.expr(stereoS_D3, stereoN2))
h.add_expression(stereoS_D4, stereoS2, hD4.expr(stereoS_D4, stereoS2))
h.display(stereoN_D1, stereoN2)
h.display(stereoN_D2, stereoS2)
h.display(stereoS_D3, stereoN2)
h.display(stereoS_D4, stereoS2)
Let us check that $h(\mathbf{1})=h(-\mathbf{1})=h(\mathbf{k})=h(-\mathbf{k})=\mathbf{k}$ and $h(\mathbf{i})=h(-\mathbf{i})=h(\mathbf{j})=h(-\mathbf{j})=-\mathbf{k}$:
all([h(One)==K2, h(minusOne)==K2,
h(K)==K2, h(minusK)==K2,
h(I)==minusK2, h(minusI)==minusK2,
h(J)==minusK2, h(minusJ)==minusK2])
D1A = A.intersection(D1)
spherD1A = spher.restrict(D1A)
spherD1A
stereoND1A = stereoN_D1.restrict(D1A)
stereoND1A.add_restrictions((y!=0, x<0))
D1A.atlas()
spher_to_stereoND1A = spher_to_stereoN.restrict(D1A)
stereoN_to_spherD1A = spher_to_stereoN.inverse().restrict(D1A)
spher_to_stereoSD1A = spher_to_stereoS.restrict(D1A)
stereoS_to_spherD1A = stereoS_to_spher.restrict(D1A)
h.expr(stereoND1A, stereoN2) # necessary
h.display(spherD1A, stereoN2)
hA = h.restrict(D1A, subcodomain=A2)
hA.display(spherD1A, spher2)
The Hopf coordinates are coordinates $(\eta,\alpha,\beta)$ on $\mathbb{S}^3$ which are related to the Cartesian coordinates on $\mathbb{R}^4$ (via the embedding $\Phi$) by $$ \begin{equation} \tag{1} \left\{ \begin{array}{lcl} T & = &\cos\eta \sin\alpha \\ X & = &\sin\eta \cos(\alpha+\beta) \\ Y & = &\sin\eta \sin(\alpha+\beta) \\ Z & = &\cos\eta \cos\alpha \end{array} \right . \end{equation} $$ and whose range is $\eta\in(0,\pi/2)$, $\alpha\in (0, 2\pi)$, $\beta\in (0, 2\pi)$. They are defined in $D_1$ minus the points for which $X^2+Y^2+T^2=1$ (limit $\alpha\rightarrow 0$ or $2\pi$) or $TX-YZ=0$ (limit $\beta\rightarrow 0$ or $2\pi$). In terms of the stereograĥic coordinates, this corresponds to $x^2+y^2+z^2=1$ or to $x(1-x^2-y^2-z^2)-2yz=0$. Hence we declare the domain $B$ of Hopf coordinates as
B = D1.open_subset('B', coord_def={stereoN_D1:
[x^2+y^2+z^2!=1,
x*(1-x^2-y^2-z^2)-2*y*z!=0]})
print(B)
Open subset B of the 3-dimensional differentiable manifold S^3
The limiting surface $x(1-x^2-y^2-z^2)-2yz=0$, where $\beta\rightarrow 0$ or $2\pi$:
beta_zero = implicit_plot3d(x*(1-x^2-y^2-z^2)-2*y*z==0,
(x,-3,3), (y,-3,3), (z,-3,3))
show(beta_zero, viewer=viewer3D, online=True)
We define the Hopf coordinates and provide the transition map to the stereographic coordinates $(x,y,z)$:
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(B),
(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((cos(eta)*sin(alpha) + 1)*sqrt(cos(eta) + 1)*sqrt(-cos(eta) + 1)/abs(cos(eta)*sin(alpha) + 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
Note that the test of the inverse coordinate transformation is passed, modulo some lack of trigonometric simplifications.
Hcoord_to_stereoN.inverse().display()
PhiB = Phi.restrict(B)
PhiB.display()
The expression of $\Phi$ in terms of the Hopf coordinates can be simplified by means of the method trig_reduce
:
PhiB.expression(Hcoord, X4)[1].factor().trig_reduce()
PhiB.expression(Hcoord, X4)[2].factor().trig_reduce()
Hence the recover the expression (1) above.
The expression of $h$ in terms of stereographic coordinates on $B$ is
h.display(stereoN.restrict(B), stereoN2)
Let us ask for the expression in terms of Hoopf coordinates:
h.display(Hcoord, stereoN2)
We notice that the image point in $\mathbb{S}^2$ is independent of the value of $\alpha$.
The expression of $h$ is even simpler in terms of spherical coordinates on $\mathbb{S}^2$:
hB = h.restrict(B, subcodomain=A2)
hB.display(Hcoord, spher2)
We are facing some lack of simplification:
atan2
function should simplify to $\operatorname{atan2}(\sin\beta,-\cos\beta) = \pi-\beta$, so that $\phi_2=\beta$Hence the right-hand side of the above formula simplifies to $(\theta_2,\phi_2)=(2\eta,\beta)$. We enforce it by the method add_expression
:
hB.add_expression(Hcoord, spher2, (2*eta, bet))
hB.display(Hcoord, spher2)
The expression of the Hopf map in terms of the Hopf coordinates is thus very simple, which justifies the name given to these coordinates. We also recover a very simple expression when asking to express $C\circ \Phi$ (from which $h$ has been constructed) in terms of the Hopf coordinates:
CS.restrict(B).display(Hcoord, X4)
The right-hand side should simplify to
$$(T,X,Y,Z) = (0, \sin(2\eta)\cos\beta, \sin(2\eta)\sin\beta, \cos(2\eta))$$
We can (partially) obtain this by means of trig_reduce()
:
for cp in CS.restrict(B).expr(Hcoord, X4):
show(cp.trig_reduce().simplify_full())
The above results show that the image by $h$ of a point of Hopf coordinates $(\eta,\alpha,\beta)$ is independent of $\alpha$. Since $h$ is surjective, this means that for any point $p\in\mathbb{S}^2$, the preimage set $h^{-1}(p)$ corresponds to a fixed value of $(\eta,\beta)$, with $\alpha$ taking all values in the range $(0,2\pi)$. From Eq. (1), the projection of $h^{-1}(p)$ in the $(T,X)$-plane is a circle of radius $\cos\eta$ centered on $(0,0)$, while its projection in the $(X,Y)$-plane is a circle of radius $\sin\eta$ centered on $(0,0)$. We conclude that $h^{-1}(p)$ is a great circle of $\mathbb{S}^3$, sometimes called a Hopf circle.
It follows that $\mathbb{S}^3$ has the structure of a fiber bundle over $\mathbb{S}^2$ with $\mathbb{S}^1$ fibers. The Hopf map $h:\mathbb{S}^3\to\mathbb{S}^2$ is then nothing but the projection map of this bundle.
We can get a first view of the fibers by plotting the Hopf coordinates in terms of the stereographic ones, for a fixed value of $\eta$ ($\eta=\pi/4$): the lines along which $\alpha$ varies while $\beta$ is kept fixed, hence the fibers $h^{-1}(p)$, are plotted in green. They are indeed circles (remember that stereographic projection preserves circles):
graph = Hcoord.plot(stereoN,
fixed_coords={eta: pi/4},
color={alp: 'green', bet: 'orange'},
number_values=9, label_axes=False)
show(graph, viewer=viewer3D, online=True, axes_labels=['x','y','z'])
Note that all the green circles are linked. The same plot, but in terms of the coordinates $(X,Y,T)$ of $\mathbb{R}^4$ via the embedding $\Phi$:
graph = Hcoord.plot(X4, mapping=PhiB, ambient_coords=(X,Y,T),
fixed_coords={eta: pi/4},
color={alp: 'green', bet: 'orange'},
number_values=9, label_axes=False)
show(graph, viewer=viewer3D, online=True, axes_labels=['X','Y','T'])
We may fix $\beta$ instead of $\eta$, in plotting the Hopf coordinates in terms of the stereographic ones. The fibers are still the green circles, the red lines being lines along which $\eta$ varies at fixed $\alpha$. Again, note that all the green circles are linked.
graph = Hcoord.plot(stereoN,
fixed_coords={bet: pi/2},
ranges={eta: (0.25, 1.5)},
color={eta: 'red', alp: 'green'},
number_values=9, plot_points=150,
label_axes=False)
show(graph, viewer=viewer3D, online=True, axes_labels=['x','y','z'])
If we vary the three coordinates $(\eta,\alpha,\beta)$, we get the following plots, where $\mathbb{S}^3$ appears as filled by the grid of Hoopf coordinates:
graph = Hcoord.plot(X4, mapping=PhiB, ambient_coords=(X,Y,Z),
color={eta: 'red', alp: 'green', bet: 'orange'},
number_values=7, label_axes=False)
show(graph, viewer=viewer3D, online=True, axes_labels=['X','Y','Z'])
#graph = Hcoord.plot(X4, mapping=PhiB, ambient_coords=(X,Y,T),
# color={eta: 'red', alp: 'green', bet: 'orange'},
# number_values=7, label_axes=False)
# show(graph, viewer=viewer3D, online=True, axes_labels=['X','Y','T'])
#graph = Hcoord.plot(X4, mapping=PhiB, ambient_coords=(X,Z,T),
# color={eta: 'red', alp: 'green', bet: 'orange'},
# number_values=7, label_axes=False)
#show(graph, viewer=viewer3D, online=True, axes_labels=['X','Z','T'])
For a point $p\in\mathbb{S}^2$, identified by its spherical coordinates $(\theta_2,\phi_2)=(2\eta,\beta)$, the fiber $h^{-1}(p)$ can be seen as a curve in $\mathbb{S}^3$:
R.<t> = RealLine()
def fiber(eta, bet):
return S3.curve({Hcoord: (eta, t, bet)}, (t, 0, 2*pi))
For instance, the fiber above the point $(\theta_2,\phi_2)=(\pi/3,\pi/4)$ is
F = fiber(pi/6, pi/4)
F
F.display()
graph_F = F.plot(chart=stereoN, color='green', plot_points=100)
show(graph_F, viewer=viewer3D, online=True)
Let us plot the fibers for three values of $\eta$: $\eta=\pi/6$ (turquoise), $\eta=\pi/4$ (gold) and $\eta=5\pi/12$ (red). For each value of $\eta$, we note that the fibers fill a torus.
graph= Graphics()
etas = {pi/6: ['turquoise', (0, 2*pi), 30],
pi/4: ['gold', (0,2*pi), 30],
5*pi/12: ['red', (0, 2*pi), 30]}
for eta_v, param in etas.items():
color = param[0]
beta_min, beta_max = param[1]
nb = param[2]
db = (beta_max - beta_min)/(nb-1)
betas = [beta_min + db*k for k in range(nb)]
for beta_v in betas:
F = fiber(eta_v, beta_v)
F.coord_expr(stereoN.restrict(B))
graph += F.plot(chart=stereoN, color=color, plot_points=150,
label_axes=None)
show(graph, viewer=viewer3D, online=True)
#show(graph, viewer='tachyon', figsize=24, aspect_ratio=1, frame=False)
A "top" view of the fibers, obtained by projection to the $(x,y)$-plane (note that for clarity, we have reduced the number of fibers from 30 to 12):
graph= Graphics()
etas = {pi/6: ['turquoise', (0, 2*pi), 12],
pi/4: ['gold', (0,2*pi), 12],
5*pi/12: ['red', (0, 2*pi), 12]}
for eta_v, param in etas.items():
color = param[0]
beta_min, beta_max = param[1]
nb = param[2]
db = (beta_max - beta_min)/(nb-1)
betas = [beta_min + db*k for k in range(nb)]
for beta_v in betas:
F = fiber(eta_v, beta_v)
F.coord_expr(stereoN.restrict(B))
graph += F.plot(chart=stereoN, ambient_coords=(x,y),
color=color, plot_points=150)
show(graph, aspect_ratio=1)
The same fibers, but viewed in $\mathbb{R}^4$ (via the embedding $\Phi$), in terms of the coordinates $(T,X,Z)$:
graph = Graphics()
for eta_v, param in etas.items():
color = param[0]
beta_min, beta_max = param[1]
nb = param[2]
db = (beta_max - beta_min)/(nb-1)
betas = [beta_min + db*k for k in range(nb)]
for beta_v in betas:
F = fiber(eta_v, beta_v)
F.coord_expr(stereoN.restrict(B))
graph += F.plot(chart=X4, ambient_coords=(X,Z,T),
mapping=Phi, color=color,
plot_points=200, label_axes=None)
show(graph, viewer=viewer3D, online=True, axes_labels=['X', 'Z', 'T'])
or in terms of the coordinates $(X,Y,Z)$:
graph = Graphics()
for eta_v, param in etas.items():
color = param[0]
beta_min, beta_max = param[1]
nb = param[2]
db = (beta_max - beta_min)/(nb-1)
betas = [beta_min + db*k for k in range(nb)]
for beta_v in betas:
F = fiber(eta_v, beta_v)
F.coord_expr(stereoN.restrict(B))
graph += F.plot(chart=X4, ambient_coords=(X,Y,Z),
mapping=Phi, color=color,
plot_points=100, label_axes=None)
show(graph, viewer=viewer3D, online=True, axes_labels=['X', 'Y', 'Z'])
In this worksheet, we have used 18 charts on $\mathbb{S}^3$:
S3.atlas()
len(S3.atlas())
corresponding actually to 4 different coordinate systems:
S3.top_charts()
For a follow-up, see the worksheet devoted to vector fields on $\mathbb{S}^3$.
You may also visit Niles Johnson's page for a very nice animated 3D visualization of the Hopf fibration, also constructed with SageMath.