This worksheet demonstrates a few capabilities of SageManifolds (version 1.0, as included in SageMath 7.5) in computations regarding the real projective 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
NB: a version of SageMath at least equal to 7.5 is required to run this worksheet:
version()
'SageMath version 7.5, Release Date: 2017-01-11'
First we set up the notebook to display mathematical objects using LaTeX rendering:
%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)
We start by declaring the real projective plane as a 2-dimensional differentiable manifold:
RP2 = Manifold(2, 'RP^2', r'\mathbb{RP}^2') ; RP2
Then we provide $\mathbb{RP}^2$ with some atlas. A minimal atlas on $\mathbb{RP}^2$ must have at least three charts. Such an atlas is easy to infer from the common interpretation of $\mathbb{RP}^2$ as the set of lines of $\mathbb{R}^3$ passing through the origin $(x,y,z)=(0,0,0)$. Let $U_1$ be the subset of lines that are not contained in the plane $z=0$; this is an open set of $\mathbb{RP}^2$, so that we declare it as:
U1 = RP2.open_subset('U_1') ; U1
Any line in $U_1$ is uniquely determined by its intersection with the plane $z=1$. The Cartesian coordinates $(x,y,1)$ of the intersection point lead to an obvious coordinate system $(x_1,y_1)$ on $U_1$ by setting $(x_1,y_1)=(x,y)$:
X1.<x1,y1> = U1.chart() ; X1
Note that since we have not specified any coordinate range in the arguments of chart(), the range of $(x_1,y_1)$ is $\mathbb{R}^2$.
Similarly, let $U_2$ be the set of lines through the origin of $\mathbb{R}^3$ that are not contained in the plane $x=0$. Any line in $U_2$ is uniquely determined by its intersection $(1,y,z)$ with the plane $x=1$, leading to coordinates $(x_2,y_2)=(y,z)$ on $U_2$:
U2 = RP2.open_subset('U_2')
X2.<x2,y2> = U2.chart() ; X2
Finally, let $U_3$ be the set of lines through the origin of $\mathbb{R}^3$ that are not contained in the plane $y=0$. Any line in $U_3$ is uniquely determined by its intersection $(x,1,z)$ with the plane $y=1$, leading to coordinates $(x_3,y_3)=(z,x)$ on $U_3$:
U3 = RP2.open_subset('U_3')
X3.<x3,y3> = U3.chart() ; X3
We declare that the union of the three (overlapping) open domains $U_1$, $U_2$ and $U_3$ is $\mathbb{RP}^2$:
RP2.declare_union(U1.union(U2), U3)
U1.union(U2).union(U3)
At this stage, three open covers of $\mathbb{RP}^2$ have been constructed:
RP2.open_covers()
Finally, to fully specify the manifold $\mathbb{RP}^2$, we give the transition maps between the various charts; the transition map between the charts X1=$(U_1,(x_1,y_1))$ and X2=$(U_2,(x_2,y_2))$ is defined on the set $U_{12} := U_1 \cap U_2$ of lines through the origin of $\mathbb{R}^3$ that are neither contained in the plane $x=0$ ($x_1=0$ in $U_1$) nor contained in the plane $z=0$ ($y_2=0$ in $U_2$):
X1_to_X2 = X1.transition_map(X2, (y1/x1, 1/x1), intersection_name='U_{12}',
restrictions1= x1!=0, restrictions2= y2!=0)
X1_to_X2.display()
The inverse of this transition map is easily computed by Sage:
X2_to_X1 = X1_to_X2.inverse()
X2_to_X1.display()
The transition map between the charts X1=$(U_1,(x_1,y_1))$ and X3=$(U_3,(x_3,y_3))$ is defined on the set $U_{13} := U_1 \cap U_3$ of lines through the origin of $\mathbb{R}^3$ that are neither contained in the plane $y=0$ ($y_1=0$ in $U_1$) nor contained in the plane $z=0$ ($x_3=0$ in $U_3$):
X1_to_X3 = X1.transition_map(X3, (1/y1, x1/y1), intersection_name='U_{13}',
restrictions1= y1!=0, restrictions2= x3!=0)
X1_to_X3.display()
X3_to_X1 = X1_to_X3.inverse()
X3_to_X1.display()
Finally, the transition map between the charts X2=$(U_2,(x_2,y_2))$ and X3=$(U_3,(x_3,y_3))$ is defined on the set $U_{23} := U_2 \cap U_3$ of lines through the origin of $\mathbb{R}^3$ that are neither contained in the plane $y=0$ ($x_2=0$ in $U_2$) nor contained in the plane $x=0$ ($y_3=0$ in $U_3$):
X2_to_X3 = X2.transition_map(X3, (y2/x2, 1/x2), intersection_name='U_{23}',
restrictions1= x2!=0, restrictions2= y3!=0)
X2_to_X3.display()
X3_to_X2 = X2_to_X3.inverse()
X3_to_X2.display()
At this stage, the manifold $\mathbb{RP}^2$ is fully constructed. It has been provided with the following atlas:
RP2.atlas()
Note that, in addition to the three chart we have defined, the atlas comprises subcharts on the intersection domains $U_{12}$, $U_{13}$ and $U_{23}$. These charts can be obtained by the method restrict():
U12 = U1.intersection(U2)
U13 = U1.intersection(U3)
U23 = U2.intersection(U3)
X1.restrict(U12)
X1.restrict(U12) is RP2.atlas()[3]
It is well known that $\mathbb{RP}^2$ is not an orientable manifold. To illustrate this, let us make an attempt to construct a global non-vanishing 2-form $\epsilon$ on $\mathbb{RP}^2$. If we succeed, this would provide a volume form and $\mathbb{RP}^2$ would be orientable. We start by declaring $\epsilon$ as a 2-form on $\mathbb{RP}^2$:
eps = RP2.diff_form(2, name='eps', latex_name=r'\epsilon')
print(eps)
2-form eps on the 2-dimensional differentiable manifold RP^2
We set the value of $\epsilon$ on domain $U_1$ to be $\mathrm{d}x_1 \wedge \mathrm{d}y_1$ by demanding that the component $\epsilon_{01}$ of $\epsilon$ with respect to coordinates $(x_1,y_1)$ is one, as follows:
e1 = X1.frame() ; e1
eps[e1,0,1] = 1
eps.display(e1)
If we ask for the expression of $\epsilon$ in terms of the coframe $(\mathrm{d}x_2, \mathrm{d}y_2)$ associated with the chart X2 on $U_{12} = U_1\cap U_2$, we get
eps.display(X2.frame().restrict(U12), chart=X2.restrict(U12))
Now, the complement of $U_{12}$ in $U_2$ is defined by $y_2=0$. The above expression shows that it is not possible to extend smoothly $\epsilon$ to the whole domain $U_2$. We conclude that starting from $\mathrm{d}x_1\wedge\mathrm{d}y_1$ on $U_1$, it is not possible to get a regular non-vanishing 2-form on $\mathbb{RP}^2$. This of course follows from the fact that $\mathbb{RP}^2$ is not orientable.
Let us first define $\mathbb{R}^3$ as a 3-dimensional manifold, with a single-chart atlas (Cartesian coordinates Y):
R3 = Manifold(3, 'R^3', r'\mathbb{R}^3')
Y.<x,y,z> = R3.chart()
The Steiner map is a map $\mathbb{RP}^2 \rightarrow \mathbb{R}^3$ defined as follows:
Phi = RP2.diff_map(R3, {(X1,Y): [y1/(1+x1^2+y1^2), x1/(1+x1^2+y1^2), x1*y1/(1+x1^2+y1^2)],
(X2,Y): [x2*y2/(1+x2^2+y2^2), y2/(1+x2^2+y2^2), x2/(1+x2^2+y2^2)],
(X3,Y): [x3/(1+x3^2+y3^2), x3*y3/(1+x3^2+y3^2), y3/(1+x3^2+y3^2)]},
name='Phi', latex_name=r'\Phi')
Phi.display()
$\Phi$ is a topological immersion of $\mathbb{RP}^2$ into $\mathbb{R}^3$, but it is not a smooth immersion (contrary to the Apéry map below): its differential is not injective at $(x_1,y_1)=(0,1)$ and $(x_1,y_1)=(1,0)$. The image of $\Phi$ is a self-intersecting surface of $\mathbb{R}^3$, called the Roman surface:
g1 = parametric_plot3d(Phi.expr(X1,Y), (x1,-10,10), (y1,-10,10), plot_points=[100,100])
g2 = parametric_plot3d(Phi.expr(X2,Y), (x2,-10,10), (y2,-10,10), plot_points=[100,100])
g3 = parametric_plot3d(Phi.expr(X3,Y), (x3,-10,10), (y3,-10,10), plot_points=[100,100])
show(g1+g2+g3, viewer=viewer3D)
gX1 = X1.plot(Y, mapping=Phi, max_range=16, number_values=24, plot_points=100,
label_axes=False)
gX2 = X2.plot(Y, mapping=Phi, max_range=16, number_values=24, plot_points=100,
label_axes=False, color='green')
gX3 = X3.plot(Y, mapping=Phi, max_range=16, number_values=24, plot_points=100,
label_axes=False, color='blue')
show(gX1, viewer=viewer3D)
show(gX2, viewer=viewer3D)
show(gX3, viewer=viewer3D)
show(gX1+gX2+gX3, viewer=viewer3D)
The Apéry map [Apéry, Adv. Math. 61, 185 (1986)] is a smooth immersion $\Psi: \mathbb{RP}^2 \rightarrow \mathbb{R}^3$. In terms of the charts X1, X2, X3 introduced above, it is defined as follows:
fx = ((2*x^2-y^2-z^2)*(x^2+y^2+z^2)+2*y*z*(y^2-z^2)+z*x*(x^2-z^2)+x*y*(y^2-z^2))/2 ; fx
fy = sqrt(3)/2*((y^2-z^2)*(x^2+y^2+z^2)+z*x*(z^2-x^2)+x*y*(y^2-x^2)) ; fy
fz = (x+y+z)*((x+y+z)^3/4+(y-x)*(z-y)*(x-z)) ; fz
a = sqrt(1+x1^2+y1^2)
fx1 = fx.subs(x=x1/a, y=y1/a, z=1/a).simplify_full()
fy1 = fy.subs(x=x1/a, y=y1/a, z=1/a).simplify_full()
fz1 = fz.subs(x=x1/a, y=y1/a, z=1/a).simplify_full()
a = sqrt(1+x2^2+y2^2)
fx2 = fx.subs(x=1/a, y=x2/a, z=y2/a).simplify_full()
fy2 = fy.subs(x=1/a, y=x2/a, z=y2/a).simplify_full()
fz2 = fz.subs(x=1/a, y=x2/a, z=y2/a).simplify_full()
a = sqrt(1+x3^2+y3^2)
fx3 = fx.subs(x=y3/a, y=1/a, z=x3/a).simplify_full()
fy3 = fy.subs(x=y3/a, y=1/a, z=x3/a).simplify_full()
fz3 = fz.subs(x=y3/a, y=1/a, z=x3/a).simplify_full()
Psi = RP2.diff_map(R3, {(X1,Y): [fx1, fy1, fz1], (X2,Y): [fx2, fy2, fz2],
(X3,Y): [fx3, fy3, fz3]}, name='Psi', latex_name=r'\Psi')
Psi.display()
The image of $\Psi$ is a self-intersecting surface of $\mathbb{R}^3$, called the Boy surface, after Werner Boy (1879-1914):
g1 = parametric_plot3d(Psi.expr(X1,Y), (x1,-10,10), (y1,-10,10), plot_points=[100,100])
g2 = parametric_plot3d(Psi.expr(X2,Y), (x2,-10,10), (y2,-10,10), plot_points=[100,100])
g3 = parametric_plot3d(Psi.expr(X3,Y), (x3,-10,10), (y3,-10,10), plot_points=[100,100])
show(g1+g2+g3, viewer=viewer3D)
gX1 = X1.plot(Y, mapping=Psi, number_values=40, plot_points=100, label_axes=False)
gX2 = X2.plot(Y, mapping=Psi, number_values=40, plot_points=100, label_axes=False,
color='green')
gX3 = X3.plot(Y, mapping=Psi, number_values=40, plot_points=100, label_axes=False,
color='blue')
show(gX1, viewer=viewer3D)
show(gX2, viewer=viewer3D)
show(gX3, viewer=viewer3D)
show(gX1+gX2+gX3, viewer=viewer3D)