This worksheet demonstrates a few capabilities of SageManifolds (version 1.0, as included in SageMath 7.5) on the example of the 2-dimensional sphere.

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:

In [1]:

```
version()
```

Out[1]:

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

In [2]:

```
%display latex
```

We also define a viewer for 3D plots (use `'threejs'`

or `'jmol'`

for interactive 3D graphics):

In [3]:

```
viewer3D = 'threejs' # must be 'threejs', jmol', 'tachyon' or None (default)
```

We start by declaring $\mathbb{S}^2$ as a differentiable manifold of dimension 2 over $\mathbb{R}$:

In [4]:

```
S2 = Manifold(2, 'S^2', latex_name=r'\mathbb{S}^2', start_index=1)
```

The first argument, 2, is the dimension of the manifold, while the second argument is the symbol used to label the manifold.

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\}$; the default value is start_index=0 and yields to $\{0,1\}$.

In [5]:

```
print(S2)
```

In [6]:

```
S2
```

Out[6]:

The manifold is a `Parent`

object:

In [7]:

```
isinstance(S2, Parent)
```

Out[7]:

in the category of smooth manifolds over $\mathbb{R}$:

In [8]:

```
S2.category()
```

Out[8]:

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

$$ V := \mathbb{S}^2\setminus\{S\}, $$
where $N$ is a point of $\mathbb{S}^2$, which we shall call the *North pole*, and $S$ is the point of $U$ of stereographic coordinates $(0,0)$, which we call the *South pole*:

In [9]:

```
U = S2.open_subset('U') ; print(U)
```

In [10]:

```
V = S2.open_subset('V') ; print(V)
```

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

In [11]:

```
S2.declare_union(U, V)
```

In [12]:

```
stereoN.<x,y> = U.chart()
```

`.<x,y>`

in the left-hand side means that the Python variables `x`

and `y`

are set to the two coordinates of the chart. This allows one to refer subsequently to the coordinates by their names. In the present case, the function `chart()`

has no argument, which implies that the coordinate symbols will be `x`

and `y`

(i.e. exactly the characters appearing in the `<...>`

operator) and that each coordinate range is $(-\infty,+\infty)$. As we will see below, for other cases, an argument must be passed to `chart()`

to specify each coordinate symbol and range, as well as some specific LaTeX symbol.

In [13]:

```
stereoN
```

Out[13]:

`start_index=1`

set in the manifold's definition) or by their names as Python variables:

In [14]:

```
stereoN[1]
```

Out[14]:

In [15]:

```
y is stereoN[2]
```

Out[15]:

In [16]:

```
stereoS.<xp,yp> = V.chart("xp:x' yp:y'")
```

`chart`

stipulates that the text-only names of the coordinates are xp and yp (same as the Python variables names defined within the `<...>`

operator in the left-hand side), while their LaTeX names are $x'$ and $y'$.

In [17]:

```
stereoS
```

Out[17]:

At this stage, the user's atlas on the manifold has two charts:

In [18]:

```
S2.atlas()
```

Out[18]:

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

In [19]:

```
stereoN_to_S = stereoN.transition_map(stereoS,
(x/(x^2+y^2), y/(x^2+y^2)),
intersection_name='W',
restrictions1= x^2+y^2!=0,
restrictions2= xp^2+xp^2!=0)
stereoN_to_S.display()
```

Out[19]:

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

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

:

In [20]:

```
stereoS_to_N = stereoN_to_S.inverse()
stereoS_to_N.display()
```

Out[20]:

In the present case, the situation is of course perfectly symmetric regarding the coordinates $(x,y)$ and $(x',y')$.

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

In [21]:

```
S2.atlas()
```

Out[21]:

Let us store $W = U\cap V$ into a Python variable for future use:

In [22]:

```
W = U.intersection(V)
```

In [23]:

```
stereoN_W = stereoN.restrict(W)
stereoN_W
```

Out[23]:

In [24]:

```
stereoS_W = stereoS.restrict(W)
stereoS_W
```

Out[24]:

We may plot the chart $(W, (x',y'))$ in terms of itself, as a grid:

In [25]:

```
stereoS_W.plot()
```

Out[25]:

In [26]:

```
graphSN1 = stereoS_W.plot(stereoN, ranges={xp:[-6,-0.02], yp:[-6,-0.02]})
graphSN2 = stereoS_W.plot(stereoN, ranges={xp:[-6,-0.02], yp:[0.02,6]})
graphSN3 = stereoS_W.plot(stereoN, ranges={xp:[0.02,6], yp:[-6,-0.02]})
graphSN4 = stereoS_W.plot(stereoN, ranges={xp:[0.02,6], yp:[0.02,6]})
show(graphSN1+graphSN2+graphSN3+graphSN4,
xmin=-1.5, xmax=1.5, ymin=-1.5, ymax=1.5)
```

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

In [27]:

```
A = W.open_subset('A', coord_def={stereoN_W: (y!=0, x<0),
stereoS_W: (yp!=0, xp<0)})
print(A)
```

The restriction of the stereographic chart from the North pole to $A$ is

In [28]:

```
stereoN_A = stereoN_W.restrict(A)
stereoN_A
```

Out[28]:

In [29]:

```
spher.<th,ph> = A.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') ; spher
```

Out[29]:

In [30]:

```
spher_to_stereoN = spher.transition_map(stereoN_A,
(sin(th)*cos(ph)/(1-cos(th)),
sin(th)*sin(ph)/(1-cos(th))))
spher_to_stereoN.display()
```

Out[30]:

`verbose=True`

):

In [31]:

```
spher_to_stereoN.set_inverse(2*atan(1/sqrt(x^2+y^2)), atan2(-y,-x)+pi,
verbose=True)
```

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

In [32]:

```
spher_to_stereoN.inverse().display()
```

Out[32]:

In [33]:

```
stereoN_to_S_A = stereoN_to_S.restrict(A)
spher_to_stereoS = stereoN_to_S_A * spher_to_stereoN
spher_to_stereoS.display()
```

Out[33]:

In [34]:

```
stereoS_to_N_A = stereoN_to_S.inverse().restrict(A)
stereoS_to_spher = spher_to_stereoN.inverse() * stereoS_to_N_A
stereoS_to_spher.display()
```

Out[34]:

The user atlas of $\mathbb{S}^2$ is now

In [35]:

```
S2.atlas()
```

Out[35]:

In [36]:

```
spher.plot(stereoN, number_values=15, ranges={th: (pi/8,pi)})
```

Out[36]:

In [37]:

```
stereoN_A.plot(spher, ranges={x: (0.01,8), y: (0.01,8)}, number_values=20, plot_points=200)
```

Out[37]:

We declare the **North pole** (resp. the **South pole**) as the point of coordinates $(0,0)$ in the chart $(V,(x',y'))$ (resp. in the chart $(U,(x,y))$):

In [38]:

```
N = V.point((0,0), chart=stereoS, name='N') ; print(N)
S = U.point((0,0), chart=stereoN, name='S') ; print(S)
```

In [39]:

```
N = V((0,0), chart=stereoS, name='N') ; print(N)
S = U((0,0), chart=stereoN, name='S') ; print(S)
```

In [40]:

```
N = V((0,0), name='N') ; print(N)
S = U((0,0), name='S') ; print(S)
```

In [41]:

```
N.parent()
```

Out[41]:

In [42]:

```
S.parent()
```

Out[42]:

We have of course

In [43]:

```
N in V
```

Out[43]:

In [44]:

```
N in S2
```

Out[44]:

In [45]:

```
N in U
```

Out[45]:

In [46]:

```
N in A
```

Out[46]:

Let us introduce some point at the equator:

In [47]:

```
E = S2((0,1), chart=stereoN, name='E')
```

The point $E$ is in the open subset $A$:

In [48]:

```
E in A
```

Out[48]:

We may then ask for its spherical coordinates $(\theta,\phi)$:

In [49]:

```
E.coord(spher)
```

Out[49]:

which is not possible for the point $N$:

In [50]:

```
try:
N.coord(spher)
except ValueError as exc:
print('Error: ' + str(exc))
```

Let us first declare $\mathbb{R}^3$ as a 3-dimensional manifold covered by a single chart (the so-called** Cartesian coordinates**):

In [51]:

```
R3 = Manifold(3, 'R^3', r'\mathbb{R}^3', start_index=1)
cart.<X,Y,Z> = R3.chart() ; cart
```

Out[51]:

In [52]:

```
Phi = S2.diff_map(R3, {(stereoN, cart):
[2*x/(1+x^2+y^2), 2*y/(1+x^2+y^2),
(x^2+y^2-1)/(1+x^2+y^2)],
(stereoS, cart):
[2*xp/(1+xp^2+yp^2), 2*yp/(1+xp^2+yp^2),
(1-xp^2-yp^2)/(1+xp^2+yp^2)]},
name='Phi', latex_name=r'\Phi')
```

In [53]:

```
Phi.display()
```

Out[53]:

In [54]:

```
Phi.parent()
```

Out[54]:

In [55]:

```
print(Phi.parent())
```

In [56]:

```
Phi.parent() is Hom(S2, R3)
```

Out[56]:

$\Phi$ maps points of $\mathbb{S}^2$ to points of $\mathbb{R}^3$:

In [57]:

```
N1 = Phi(N) ; print(N1) ; N1 ; N1.coord()
```

Out[57]:

In [58]:

```
S1 = Phi(S) ; print(S1) ; S1 ; S1.coord()
```

Out[58]:

In [59]:

```
E1 = Phi(E) ; print(E1) ; E1 ; E1.coord()
```

Out[59]:

In [60]:

```
Phi.expr(stereoN_A, cart)
```

Out[60]:

In [61]:

```
Phi.expr(spher, cart)
```

Out[61]:

In [62]:

```
Phi.display(spher, cart)
```

Out[62]:

In [63]:

```
graph_spher = spher.plot(chart=cart, mapping=Phi, number_values=11,
color='blue', label_axes=False)
show(graph_spher, viewer=viewer3D)
```

In [64]:

```
graph_stereoN = stereoN.plot(chart=cart, mapping=Phi, number_values=25,
label_axes=False)
show(graph_stereoN, viewer=viewer3D)
```

and then have a view with the stereographic coordinates from the South pole superposed (in green):

In [65]:

```
graph_stereoS = stereoS.plot(chart=cart, mapping=Phi, number_values=25,
color='green', label_axes=False)
show(graph_stereoN + graph_stereoS, viewer=viewer3D)
```