This notebook provides a short introduction to differentiable manifolds in SageMath. The tools described below have been implemented through the SageManifolds project.

This notebook is valid for version 9.2 or higher of SageMath:

In [1]:

```
version()
```

Out[1]:

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

In [2]:

```
%display latex
```

As an example let us define a differentiable manifold of dimension 3 over $\mathbb{R}$:

In [3]:

```
M = Manifold(3, 'M', latex_name=r'\mathcal{M}', start_index=1)
```

- The first argument,
`3`

, is the manifold dimension; it can be any positive integer. - The second argument,
`'M'`

, is a string defining the manifold's name; it may be different from the symbol set on the left-hand side of the = sign (here`M`

): the latter stands for the Python variable that refers to the manifold object in the computer memory, while the string`'M'`

is the mathematical symbol chosen for the manifold. - The optional argument
`latex_name=r'\mathcal{M}'`

sets the LaTeX symbol to display the manifold. Note the letter 'r' in front on the first quote: it indicates that the string is a*raw*one, so that the backslash character in`\mathcal`

is considered as an ordinary character (otherwise, the backslash is used to escape some special characters). If the argument`latex_name`

is not provided by the user, it is set to the string used as the second argument (here`'M'`

) - The optional argument
`start_index=1`

defines the range of indices to be used for tensor components on the manifold: setting it to 1 means that indices will range in $\{1,2,3\}$. The default value is`start_index=0`

.

Note that the default base field is $\mathbb{R}$. If we would have used the optional
argument `field='complex'`

, we would have defined a manifold over $\mathbb{C}$. See the
list of all options for more details.

If we ask for M, it is displayed via its LaTeX symbol:

In [4]:

```
M
```

Out[4]:

If we use the function `print()`

instead, we get a short description of the object:

In [5]:

```
print(M)
```

Via the function `type()`

, we get the type of the Python object corresponding to M (here the Python class `DifferentiableManifold_with_category`

):

In [6]:

```
print(type(M))
```

We can also ask for the category of M and see that it is the category of smooth manifolds over $\mathbb{R}$:

In [7]:

```
category(M)
```

Out[7]:

The indices on the manifold are generated by the method `irange()`

, to be used in loops:

In [8]:

```
[i for i in M.irange()]
```

Out[8]:

If the parameter `start_index`

had not been specified, the default range of the indices would have been $\{0,1,2\}$ instead:

In [9]:

```
M0 = Manifold(3, 'M', latex_name=r'\mathcal{M}')
[i for i in M0.irange()]
```

Out[9]:

Let us assume that the manifold $\mathcal{M}$ can be covered by a single chart (other cases are discussed below); the chart is declared as follows:

In [10]:

```
X.<x,y,z> = M.chart()
```

The writing `.<x,y,z>`

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

, `y`

and `z`

are set to the three coordinates of the chart. This allows one to refer subsequently to the coordinates by their names.

In this example, the function `chart()`

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

, `y`

and `z`

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

operator) and that each coordinate range is $(-\infty,+\infty)$. For other cases, an argument must be passed to `chart()`

to specify the coordinate symbols and range, as well as the LaTeX symbol of a coordinate if the latter is different from the coordinate name (an example will be provided below).

The chart is displayed as a pair formed by the open set covered by it (here the whole manifold) and the coordinates:

In [11]:

```
print(X)
```

In [12]:

```
X
```

Out[12]:

The coordinates can be accessed individually, by means of their indices, following the convention defined by `start_index=1`

in the manifold's definition:

In [13]:

```
X[1]
```

Out[13]:

In [14]:

```
X[2]
```

Out[14]:

In [15]:

```
X[3]
```

Out[15]:

The full set of coordinates is obtained by means of the operator `[:]`

:

In [16]:

```
X[:]
```

Out[16]:

Thanks to the operator `<x,y,z>`

used in the chart declaration, each coordinate can be accessed directly via its name:

In [17]:

```
z is X[3]
```

Out[17]:

Coordinates are SageMath symbolic expressions:

In [18]:

```
print(type(z))
```

Real-valued functions of the chart coordinates (mathematically speaking, *functions defined on the chart codomain*) are generated via the method `function()`

acting on the chart:

In [19]:

```
f = X.function(x+y^2+z^3)
f
```

Out[19]:

In [20]:

```
f.display()
```

Out[20]:

In [21]:

```
f(1,2,3)
```

Out[21]:

They belong to the class `ChartFunction`

(actually the subclass `𝙲𝚑𝚊𝚛𝚝𝙵𝚞𝚗𝚌𝚝𝚒𝚘𝚗𝚁𝚒𝚗𝚐_𝚠𝚒𝚝h_𝚌𝚊𝚝𝚎𝚐𝚘𝚛𝚢.𝚎𝚕𝚎𝚖𝚎𝚗t_𝚌𝚕𝚊𝚜𝚜`

):

In [22]:

```
print(type(f))
```

and differ from SageMath standard symbolic functions by automatic simplifications in all operations. For instance, adding the two symbolic functions

In [23]:

```
f0(x,y,z) = cos(x)^2; g0(x,y,z) = sin(x)^2
```

results in

In [24]:

```
f0 + g0
```

Out[24]:

while the sum of the corresponding functions in the class `ChartFunction`

is automatically simplified:

In [25]:

```
f1 = X.function(cos(x)^2); g1 = X.function(sin(x)^2)
f1 + g1
```

Out[25]:

To get the same output with symbolic functions, one has to invoke the method `simplify_trig()`

:

In [26]:

```
(f0 + g0).simplify_trig()
```

Out[26]:

Another difference regards the display; if we ask for the symbolic function `f0`

, we get

In [27]:

```
f0
```

Out[27]:

while if we ask for the chart function `f1`

, we get only the coordinate expression:

In [28]:

```
f1
```

Out[28]:

To get an output similar to that of `f0`

, one should call the method `display()`

:

In [29]:

```
f1.display()
```

Out[29]:

Note that the method `expr()`

returns the underlying symbolic expression:

In [30]:

```
f1.expr()
```

Out[30]:

In [31]:

```
print(type(f1.expr()))
```

Let us first consider an open subset of $\mathcal{M}$, for instance the complement $U$ of the region defined by $\{y=0, x\geq 0\}$ (note that `(y!=0, x<0)`

stands for $y\not=0$ OR $x<0$; the condition $y\not=0$ AND $x<0$ would have been written `[y!=0, x<0]`

instead):

In [32]:

```
U = M.open_subset('U', coord_def={X: (y!=0, x<0)})
```

Let us call `X_U`

the restriction of the chart `X`

to the open subset $U$:

In [33]:

```
X_U = X.restrict(U)
X_U
```

Out[33]:

We introduce another chart on $U$, with spherical-type coordinates $(r,\theta,\phi)$:

In [34]:

```
Y.<r,th,ph> = U.chart(r'r:(0,+oo) th:(0,pi):\theta ph:(0,2*pi):\phi')
Y
```

Out[34]:

The method `chart()`

is now used with an argument; it is a string, which contains specific LaTeX symbols, hence the prefix 'r' to it (for *raw* string). It also contains the coordinate ranges, since they are different from the default value, which is $(-\infty, +\infty)$. For a given coordinate, the various fields are separated by the character ':' and a space character separates the coordinates. Note that for the coordinate $r$, there are only two fields, since the LaTeX symbol has not to be specified. The LaTeX symbols are used for the outputs:

In [35]:

```
th, ph
```

Out[35]:

In [36]:

```
Y[2], Y[3]
```

Out[36]:

The declared coordinate ranges are now known to Sage, as we may check by means of the command `assumptions()`

:

In [37]:

```
assumptions()
```

Out[37]:

They are used in simplifications:

In [38]:

```
simplify(abs(r))
```

Out[38]:

In [39]:

```
simplify(abs(x)) # no simplification occurs since x can take any value in R
```

Out[39]:

After having been declared, the chart Y can be fully specified by its relation to the chart X_U, via a transition map:

In [40]:

```
transit_Y_to_X = Y.transition_map(X_U, [r*sin(th)*cos(ph), r*sin(th)*sin(ph), r*cos(th)])
transit_Y_to_X
```

Out[40]:

In [41]:

```
transit_Y_to_X.display()
```

Out[41]:

The inverse of the transition map can be specified by means of the method `set_inverse()`

:

In [42]:

```
transit_Y_to_X.set_inverse(sqrt(x^2+y^2+z^2), atan2(sqrt(x^2+y^2),z), atan2(y, x))
```

A check of the provided inverse is performed by composing it with the original transition map, on the left and on the right respectively. As indicated, the reported failure for `th`

and `ph`

is actually due to a lack of simplification of expressions involving `arctan2`

.

We have then

In [43]:

```
transit_Y_to_X.inverse().display()
```

Out[43]:

At this stage, the manifold's **atlas** (the "user atlas", not the maximal atlas!) contains three charts:

In [44]:

```
M.atlas()
```

Out[44]:

The first chart defined on the manifold is considered as the manifold's default chart (this can be changed by the method `set_default_chart()`

):

In [45]:

```
M.default_chart()
```

Out[45]:

Each open subset has its own atlas (since an open subset of a manifold is a manifold by itself):

In [46]:

```
U.atlas()
```

Out[46]:

In [47]:

```
U.default_chart()
```

Out[47]:

We can draw the chart $Y$ in terms of the chart $X$ via the command `Y.plot(X)`

, which shows the lines of constant coordinates from the $Y$ chart in a "Cartesian frame" based on the $X$ coordinates:

In [48]:

```
Y.plot(X)
```

Out[48]:

The method `plot()`

allows for many options, to control the number of coordinate lines to be drawn, their style and color, as well as the coordinate ranges (cf. the list of all options):

In [49]:

```
Y.plot(X, ranges={r:(1,2), th:(0,pi/2)}, number_values=4,
color={r:'blue', th:'green', ph:'red'}, aspect_ratio=1)
```

Out[49]:

Conversly, the chart $X|_{U}$ can be plotted in terms of the chart $Y$ (this is not possible for the whole chart $X$ since its domain is larger than that of chart $Y$):

In [50]:

```
graph = X_U.plot(Y)
show(graph, axes_labels=['r','theta','phi'])
```