Keywords: Introduction 23 2, Tangent vectors 23 3, Vector fields 27

a SageMath implementation

Introduction

What is tensor calculus on manifolds?

We shall provide precise definitions in Chaps. 2 and 3. Here, let us state briefly that tensor calculus on manifolds stands for calculus on vector fields, and more generally tensor fields, on differentiable manifolds, involving the following operations [START_REF] Lee | Introduction to Smooth Manifolds[END_REF]:

• arithmetics of tensor fields;

• tensor product, contraction; • (anti)symmetrization; • Lie derivation along vector fields;

• pullback and pushforward associated with smooth manifold maps;

• exterior (Cartan) calculus on differential forms;

• covariant derivation with respect to a given affine connection;

• evaluating the torsion and the curvature of an affine connection.

Moreover, on pseudo-Riemannian manifolds, i.e. differentiable manifolds endowed with a metric tensor, we may add the following operations [START_REF] Lee | Riemannian Manifolds: An Introduction to Curvature[END_REF][START_REF] O'neill | Semi-Riemannian Geometry, with Applications to Relativity[END_REF]:

• musical isomorphisms (i.e. raising and lowering indices with the metric tensor);

• determining the Levi-Civita connection;

• evaluating the curvature tensor of the Levi-Civita connection (Riemann tensor); • Hodge duality; • computing geodesics.

A few words of history

Symbolic tensor calculus has a long history, which started almost as soon as computer algebra itself in the 1960s. Probably, the first tensor calculus program was GEOM, written by J.G. Fletcher in 1965 [START_REF] Fletcher | Computer Programs for Calculating General-Relativistic Curvature Tensors[END_REF]. Its main capability was to compute the Riemann tensor of a given metric. In 1969, R.A. d'Inverno developed ALAM (for Atlas Lisp Algebraic Manipulator) and used it to compute the Riemann and Ricci tensors of the Bondi metric. According to [START_REF] Skea | Applications of SHEEP[END_REF], the original calculations took Bondi and collaborators 6 months to finish, while the computation with ALAM took 4 minutes and yielded the discovery of 6 errors in the original paper by Bondi et al. Since then, numerous packages have been developed; the reader is referred to [START_REF] Maccallum | Computer algebra in gravity research[END_REF] for a recent review of computer algebra systems for general relativity (see also [START_REF] Maccallum | Computer Algebra in General Relativity[END_REF] for a review up to 2002), and to [START_REF] Korol'kova | yanov: Tensor computations in computer algebra systems[END_REF][START_REF] Birkandan | Symbolic and Numerical Analysis in General Relativity with Open Source Computer Algebra Systems[END_REF] for more recent reviews focused on tensor calculus. It is also worth to point out the extensive list of tensor calculus packages maintained by J. M. Martin-Garcia at http://www.xact.es/links.html.

Software for differential geometry

Software packages for differential geometry and tensor calculus can be classified in two categories:

(1) Applications atop some general purpose computer algebra system. Notable examples1 are the xAct suite [START_REF] Martin-Garcia | xPerm: fast index canonicalization for tensor computer algebra[END_REF] and Ricci [20], both running atop Mathematica, DifferentialGeometry [1] integrated into Maple, GRTensorIII [8] atop Maple, Atlas 2 [2] for Mathematica and Maple, ctensor and itensor for Maxima [START_REF] Toth | Tensor manipulation in GPL Maxima[END_REF] and SageManifolds [21] integrated in SageMath.

(2) Standalone applications. Recent examples are Cadabra [START_REF] Peeters | Symbolic field theory with Cadabra[END_REF] (field theory), SnapPy [START_REF] Culler | SnapPy, a computer program for studying the geometry and topology of 3-manifolds[END_REF] (topology and geometry of 3-manifolds) and Redberry [START_REF] Bolotin | Introduction to Redberry: the computer algebra system designed for tensor manipulation[END_REF] (tensors); older examples can be found in Refs. [START_REF] Maccallum | Computer Algebra in General Relativity[END_REF][START_REF] Maccallum | Computer algebra in gravity research[END_REF]. All applications listed in the second category are free software. In the first category, xAct and Ricci are also free software, but they require a proprietary product, the source code of which is closed (Mathematica).

As far as tensor calculus is concerned, the above packages can be distinguished by the type of computation that they perform: abstract calculus (xAct/xTensor, Ricci, itensor, Cadabra, Redberry), or component calculus (xAct/xCoba, DifferentialGeometry, GRTensorIII, Atlas 2, ctensor, SageManifolds). In the first category, tensor operations such as contraction or covariant differentiation are performed by manipulating the indices themselves rather than the components to which they correspond. In the second category, vector frames are explicitly introduced on the manifold and tensor operations are carried out on the components in a given frame.

A brief overview of SageMath

Since the tensor calculus method presented here is implemented in SageMath, we give first a brief overview of the latter.

SageMath2 is a free, open-source mathematics software system, which is based on the Python programming language. It makes use of over 90 open-source packages, among which are Maxima, Pynac and SymPy (symbolic calculations), GAP (group theory), PARI/GP (number theory), Singular (polynomial computations), matplotlib (high quality 2D figures), and Jupyter (graphical interface). SageMath provides a uniform Python interface to all these packages; however, SageMath is much more than a mere interface: it contains a large and increasing part of original code (more than 750,000 lines of Python and Cython, involving 5344 classes). SageMath was created in 2005 by William Stein [START_REF] Stein | SAGE: System for Algebra and Geometry Experimentation[END_REF] and since then its development has been sustained by more than a hundred researchers (mostly mathematicians). In particular, a strong impulse is currently being provided by the European Horizon 2020 project OpenDreamKit [18]. Very good introductory textbooks about SageMath are [START_REF] Joyner | Sage Tutorial[END_REF][START_REF] Zimmermann | Calcul mathématique avec Sage[END_REF][START_REF] Zimmermann | Computational Mathematics with SageMath[END_REF][START_REF] Bard | Sage for Undergraduates[END_REF].

Apart from the syntax, which is based on a popular programming language (Python) and not on some custom script language, a difference between SageMath and, e.g., Maple or Mathematica is the use of the parent/element pattern. This pattern closely reflects actual mathematics. For instance, in Mathematica, all objects are trees of symbols and the program is essentially a set of sophisticated rules to manipulate symbols. On the contrary, in SageMath each object has a given type (i.e. is an instance of a given Python class 3 ), and one distinguishes parent types, which model mathematical sets with some structure (e.g. algebraic structure), from element types, which model set elements. Moreover, each parent belongs to some dynamically generated class that encodes information about its category, in the mathematical sense of the word. 4 Automatic conversion rules, called coercions, prior to a binary operation, e.g. x + y with x and y having different parents, are implemented.

The purpose of this lecture

This lecture aims at presenting a symbolic tensor calculus method that

• runs on fully specified smooth manifolds (described by an atlas);

• is not limited to a single coordinate chart or vector frame;

• runs even on non-parallelizable manifolds (i.e. manifolds that cannot be covered by a single vector frame); • is independent of the symbolic backend (e.g., Pynac/Maxima, SymPy, ...) used to perform calculus at the level of coordinate expressions. The aim is to present not only the main ideas of the method, but also some details of its implementation in SageMath. This implementation has been performed via the SageManifolds project: https://sagemanifolds.obspm.fr, the contributors to which are listed at https://sagemanifolds.obspm.fr/authors.html.

CHAPTER 2

Differentiable manifolds

Introduction

Starting from basic mathematical definitions, we present the implementation of manifolds and coordinate charts in SageMath (Sec. 2). We then focus on the algebra of scalar fields on a manifold (3). As we shall see in Chap. 3, this algebra plays a central role in the implementation of vector fields, the latter being considered as forming a module over it.

Differentiable manifolds

2.1. Topological manifolds. Let K be a topological field. In most applications K = R or K = C. Given an integer n ≥ 1, a topological manifold of dimension n over K is a topological space M obeying the following properties:

(1) M is a separated space (also called Hausdorff space): any two distinct points of M admit disjoint open neighbourhoods.

(2) M has a countable base: 5 there exists a countable family (U k ) k∈N of open sets of M such that any open set of M can be written as the union (possibly infinite) of some members of this family. (3) Around each point of M , there exists a neighbourhood which is homeomorphic to an open subset of K n . Property 1 excludes manifolds with "forks". Property 2 excludes "too large" manifolds; in particular it permits setting up the theory of integration on manifolds. In the case K = R, it also allows for a smooth manifold of dimension n to be embedded smoothly into the Euclidean space R 2n (Whitney theorem). Property 3 expresses the essence of a manifold: it means that, locally, M "resembles" K n .

Let us start to discuss the implementation of manifolds in SageMath. We shall do it on a concrete example, exposed in a Jupyter notebook which can be downloaded from the page devoted to these lectures: https://sagemanifolds.obspm.fr/jncf2018/ As for all SageMath, the syntax used in this notebook is Python one. However, no a priori knowledge of Python is required, since we shall explain the main notations as they appear. The first cell of the Jupyter notebook is to have all outputs rendered with L A T E X:

In [ By default, the function Manifold returns a manifold over K = R:

In [3]: M.base_field()
Out [START_REF] Bard | Sage for Undergraduates[END_REF]: R Note the use of the standard object-oriented notation (ubiquitous in Python): the method base_field() is called on the object M; since this method does not require any extra argument (all the information lies in M), its argument list is empty, hence the final (). Base fields different from R must be specified with the optional keyword field, for instance M = Manifold(2, 'M', field='complex') to construct a complex manifold 6 . We may check that M is a topological space:

In [START_REF] Birkandan | Symbolic and Numerical Analysis in General Relativity with Open Source Computer Algebra Systems[END_REF]: M in Sets().Topological() Out [START_REF] Birkandan | Symbolic and Numerical Analysis in General Relativity with Open Source Computer Algebra Systems[END_REF]: True Actually, M belongs to the following categories:

In [START_REF] Bolotin | Introduction to Redberry: the computer algebra system designed for tensor manipulation[END_REF]: M.categories() Out [START_REF] Bolotin | Introduction to Redberry: the computer algebra system designed for tensor manipulation[END_REF]:

[Smooth R , Differentiable R , Manifolds R , TopologicalSpaces(Sets),

Sets, SetsWithPartialMaps, Objects]

As we can see from the first category in the above list, Manifold constructs a smooth manifold by default. (2.1)

X : U ⊂ M -→ X(U ) ⊂ K n p -→ (x 0 , . . . , x n-1 ).
We declare a chart, along with the symbols used to denote the coordinates (here x = x 0 and y = x 1 ) by

In [START_REF] Joyner | Sage Tutorial[END_REF]: U = M.open_subset('U') XU.<x,y> = U.chart() XU

Out [START_REF] Joyner | Sage Tutorial[END_REF]: (U, (x, y))

Open subsets of a differentiable manifold are implemented by a (dynamically generated) subclass of DifferentiableManifold, since they are differentiable manifolds in their own:

In [START_REF] Korol'kova | yanov: Tensor computations in computer algebra systems[END_REF]: isinstance(U, sage.manifolds.differentiable.manifold.DifferentiableManifold)

Out [START_REF] Korol'kova | yanov: Tensor computations in computer algebra systems[END_REF]: True

Points on M are created from their coordinates in a given chart:

In [11]: p = U((1,2), chart=XU, name='p') print(p)
Point p on the 2-dimensional differentiable manifold M

The syntax U(...) used to create p as an element of U reflects the parent/element pattern employed in SageMath; indeed U is the parent of p:

In [START_REF] Lee | Introduction to Smooth Manifolds[END_REF]: p.parent()

Out [START_REF] Lee | Introduction to Smooth Manifolds[END_REF]: U

Points are implemented by a dynamically generated subclass of ManifoldPoint (cf. Fig. 2.1).

The principal attribute of this class is the one storing the point's coordinates in various charts; it is implemented as a Python dictionary,9 whose keys are the charts:

In [START_REF] Maccallum | Computer Algebra in General Relativity[END_REF]: p._coordinates

Out [START_REF] Maccallum | Computer Algebra in General Relativity[END_REF]:

{(U, (x, y)) : (1, 2)}
The leading underscore in the name _coordinates is a notation convention to specify that this attribute is a private one: the dictionary _coordinates should not be manipulated by the end user or involved in some code outside of the class ManifoldPoint. It belongs to the internal implementation, which may be changed while the user interface of the class ManifoldPoint is kept fixed. We show this private attribute here because we are precisely interested in implementation features. The public way to recover the point's coordinates is to let the chart act on the point (reflecting thereby the definition (2.1) of a chart):

In [START_REF] Maccallum | Computer algebra in gravity research[END_REF]: XU(p)

Out [START_REF] Maccallum | Computer algebra in gravity research[END_REF]:

(1, 2)
Usually, one needs more than a single coordinate system to cover M . An atlas on M is a set of pairs (U i , X i ) i∈I , where I is a set, U i an open set of M and X i a chart on U i , such that the union of all U i 's covers M :

(2.2)

i∈I U i = M.
Here we introduce a second chart on M :

In [START_REF] Martin-Garcia | xPerm: fast index canonicalization for tensor computer algebra[END_REF]: V = M.open_subset('V')

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

Out [START_REF] Martin-Garcia | xPerm: fast index canonicalization for tensor computer algebra[END_REF]: (V, (x , y ))

and declare that M is covered by only two charts, i.e. that M = U ∪ V :

In [START_REF] Milnor | On manifolds homeomorphic to the 7-sphere[END_REF]: M.declare_union(U, V)

In [START_REF] O'neill | Semi-Riemannian Geometry, with Applications to Relativity[END_REF]: M.atlas()

Out [START_REF] O'neill | Semi-Riemannian Geometry, with Applications to Relativity[END_REF]: [(U, (x, y)) , (V, (x , y ))] 2.3. Smooth manifolds. For manifolds, the concept of differentiability is defined from the smooth structure of K n , via an atlas: a smooth manifold, is a topological manifold M equipped with an atlas (U i , X i ) i∈I such that for any non-empty intersection U i ∩ U j , the map (2.3)

X i • X -1 j : X j (U i ∩ U j ) ⊂ K n -→ X i (U i ∩ U j ) ⊂ K n
is smooth (i.e. C ∞ ). Note that the above map is from an open set of K n to an open set of K n , so that the invoked differentiability is nothing but that of K n . Such a map is called a change of coordinates or, in the mathematical literature, a transition map. The atlas (U i , X i ) i∈I is called a smooth atlas.

Remark 1: Strictly speaking a smooth manifold is a pair (M, A) where A is a (maximal) smooth atlas on M . Indeed a given topological manifold M can have non-equivalent differentiable structures, as shown by [START_REF] Milnor | On manifolds homeomorphic to the 7-sphere[END_REF] [START_REF] Milnor | On manifolds homeomorphic to the 7-sphere[END_REF] in the specific case of the unit sphere of dimension 7, S 7 : there exist smooth manifolds, the so-called exotic spheres, that are homeomorphic to S 7 but not diffeomorphic to S 7 . On the other side, for n ≤ 6, there is a unique smooth structure for the sphere S n . Moreover, any manifold of dimension n ≤ 3 admits a unique smooth structure. Amazingly, in the case of R n , there exists a unique smooth structure (the standard one) for any n = 4, but for n = 4 (the spacetime case!) there exist uncountably many non-equivalent smooth structures, the so-called exotic R 4 [START_REF] Taubes | Gauge theory on asymptotically periodic 4-manifolds[END_REF].

For the manifold M under consideration, we define the transition map XU → XV on W = U ∩ V as follows:

In [18]: XU_to_XV = XU.transition_map(XV, (x/(x^2+y^2), y/(x^2+y^2)), intersection_name='W', restrictions1= x^2+y^2!=0, restrictions2= xp^2+yp^2!=0) XU_to_XV.display() Out[18]: x = x x 2 +y 2 y = y x 2 +y 2
The argument restrictions1 means that W = U \ {S}, where S is the point of coordinates (x, y) = (0, 0), while the argument restrictions2 means that W = V \ {N }, where N is the point of coordinates (x , y ) = (0, 0). Since M = U ∪ V , we have then

(2.4) U = M \ {N }, V = M \ {S}, and W = M \ {N, S}.
The transition map XV → XU is obtained by computing the inverse of the one defined above:

In [START_REF] Peeters | Symbolic field theory with Cadabra[END_REF]: XU_to_XV.inverse().display()

Out [START_REF] Peeters | Symbolic field theory with Cadabra[END_REF]:

x = x x 2 +y 2 y = y x 2 +y 2
At this stage, the smooth manifold M is fully specified, being covered by one atlas with all transition maps specified. The reader may have recognized that M is nothing but the 2-dimensional sphere:

(2.5)

M = S 2 ,
with XU (resp. XV) being the chart of stereographic coordinates from the North pole N (resp. the South pole S).

Since the transition maps have been defined, we can ask for the coordinates (x , y ) of the point p, whose (x, y) coordinates were (1, 2):

In [20]: XV(p) Out[20]: 1 5 , 2 5
This operation has updated the internal dictionary _coordinates (compare with Out [START_REF] Maccallum | Computer Algebra in General Relativity[END_REF]):

In The map Φ is said to be a diffeomorphism iff it is bijective and both Φ and Φ -1 are smooth. This implies n = n . Back to our example manifold, a natural smooth map is the embedding of S 2 in R 3 . To define it, we start by declaring R 3 as a 3-dimensional smooth manifold, canonically endowed with a single chart, that of Cartesian coordinates (X, Y, Z):

In [START_REF] Skea | Applications of SHEEP[END_REF]: R3 = Manifold(3, 'R^3', r'\mathbb{R}^3')

XR3.<X,Y,Z> = R3.chart() XR3

Out [START_REF] Skea | Applications of SHEEP[END_REF]: R 3 , (X, Y, Z)

The embedding Φ : S 2 → R 3 is then defined in terms of its coordinate expression in the two charts covering M = S 2 :

In [START_REF] Steenrod | The Topology of Fibre Bundles[END_REF]:

Φ : M -→ R 3 on U : (x, y) -→ (X, Y, Z) = 2 x x 2 + y 2 + 1 , 2 y x 2 + y 2 + 1 , x 2 + y 2 -1 x 2 + y 2 + 1 on V : (x , y ) -→ (X, Y, Z) = 2 x x 2 + y 2 + 1 , 2 y x 2 + y 2 + 1 , - x 2 + y 2 -1 x 2 + y 2 + 1
We may use Φ for graphical purposes, for instance to display the grids of the stereographic charts XU (in red) and XV (in green), with the point p atop:

In [START_REF] Stein | SAGE: System for Algebra and Geometry Experimentation[END_REF]: graph = XU.plot(chart=XR3, mapping=Phi, number_values=25, label_axes=False) + \ XV.plot(chart=XR3, mapping=Phi, number_values=25, color='green', label_axes=False) + \ p.plot(chart=XR3, mapping=Phi, label_offset=0.05) show(graph, viewer='threejs', online=True) Out [START_REF] Stein | SAGE: System for Algebra and Geometry Experimentation[END_REF]: (see Figure 2.2) 3. Scalar fields and their algebra 3.1. Definition and implementation. Given a smooth manifold M over a topological field K, a scalar field (also called a scalar-valued function) on M is a smooth map A scalar field has different coordinate representations F , F , etc. in different charts X, X, etc. defined on M : In SageMath, scalar fields are implemented by the class DiffScalarField 10 and the various representations (3.2) are stored in the private attribute _express of this class, which is a Python dictionary whose keys are the various charts defined on M : (3.3) f._express = X : F, X : F , . . . .

(3.1) f : M -→ K p -→ f (p).

Each representation

F is an instance of the class ChartFunction, devoted to functions of coordinates, allowing for different internal representations: SageMath symbolic expression, SymPy expression, etc.

For instance, let us define a scalar field on our example manifold M = S 2 :

In [25]: f = M.scalar_field({XU: 1/(1+x^2+y^2), XV: (xp^2+yp^2)/(1+xp^2+yp^2)}, name='f') f.display()
Out [START_REF] Taubes | Gauge theory on asymptotically periodic 4-manifolds[END_REF]:

f : M -→ R on U : (x, y) -→ 1 x 2 +y 2 +1 on V : (x , y ) -→ x 2 +y 2 x 2 +y 2 +1
The internal dictionary _express is then

In [START_REF] Toth | Tensor manipulation in GPL Maxima[END_REF]: f._express 10 http://doc.sagemath.org/html/en/reference/manifolds/sage/manifolds/differentiable/scalarfield.html Out [START_REF] Toth | Tensor manipulation in GPL Maxima[END_REF]: (U, (x, y)) :

1

x 2 + y 2 + 1 , (V, (x , y )) : x 2 + y 2 x 2 + y 2 + 1
The reader may wonder about the compatibility of the two coordinate expressions provided in the definition of f . Actually, to ensure the compatibility, it is possible to declare the scalar field in a single chart, XU say, and then to obtain its expression in chart XV by analytic continuation from the expression in W = U ∩ V , where both expressions are known, thanks to the transition map XV → XU:

In [START_REF] Zimmermann | Calcul mathématique avec Sage[END_REF]: f0 = M.scalar_field({XU: 1/(1+x^2+y^2)}) f0.add_expr_by_continuation(XV, U.intersection(V)) f == f0

Out [START_REF] Zimmermann | Calcul mathématique avec Sage[END_REF]: True

The representation of the scalar field in a given chart, i.e. the public access to the private directory _express, is obtained via the method coord_function():

In [START_REF] Zimmermann | Computational Mathematics with SageMath[END_REF]: fU = f.coord_function(XU) fU.display() Out [START_REF] Zimmermann | Computational Mathematics with SageMath[END_REF]:

(x, y) → 1 x 2 + y 2 + 1 In [29]: fV = f.coord_function(XV) fV.display() Out[29]: (x , y ) → x 2 + y 2 x 2 + y 2 + 1
As mentioned above, each chart representation is an instance of the class ChartFunction:

In [30]: isinstance(fU, sage.manifolds.chart_func.ChartFunction)

Out[30]: True

Mathematically, chart functions are K-valued functions on the codomain of the considered chart. They map coordinates to elements of the base field K:

In [31]: fU(1,2) Out[31]: 1 6 In [32]: fU(*XU(p)) Out[32]: 1 6
Note the use of Python's star operator in *XU(p) to unpack the tuple of coordinates returned by XU(p) (in the present case: (1,2)) to positional arguments for the function fU (in the present case: 1, 2). On their side, scalar fields map manifold points, not coordinates, to K:

In [33]: f(p) Out[33]: 1 6
Note that the equality between Out[32] and Out[33] reflects the identity f = F • X, where F is the chart function (denoted fU above) representing the scalar field f on the chart X (cf. Eq. (3.2)).

I-14

Internally, each chart function stores coordinate expressions with respect to various computational backends:

• SageMath symbolic engine, based on the Pynac11 backend, with Maxima used for some simplifications or computation of integrals; • SymPy12 (Python library for symbolic mathematics); • in the future, more symbolic or numerical backends will be implemented. The coordinate expressions are stored in the private dictionary _express13 of the class ChartFunction, whose keys are strings identifying the computational backends. By default only SageMath symbolic expressions, i.e. expressions pertaining to the so-called SageMath's Symbolic Ring (SR), are stored:

In [34]: fU._express Out[34]: SR : 1 x 2 + y 2 + 1
The public access to the private dictionary _express is performed via the method expr():

In [35]: fU.expr() Out[35]: 1 x 2 + y 2 + 1
In [36]: type(fU.expr())

Out[36]: <type 'sage.symbolic.expression.Expression'> Actually, fU.expr() is a shortcut for fU.expr('SR') since SR is the default symbolic backend. Note that the class Expression is that devoted to SageMath symbolic expressions. The method expr() can also be invoked to get the expression in another symbolic backend, for instance SymPy: In [41]: XU.set_calculus_method('SR') fU.expr()

Out[41]: 1 x 2 + y 2 + 1
Symbolic expressions can be accessed directly from the scalar field, f.expr(XU) being a shortcut for f.coord_function(XU).expr():

In [42]: f.expr(XU) Out[42]: 1 x 2 + y 2 + 1 In [43]: f.expr(XV) Out[43]: x 2 + y 2 x 2 + y 2 + 1
3.2. Scalar field algebra. The set C ∞ (M ) of all scalar fields on M has naturally the structure of a commutative algebra over K: it is clearly a vector space over K and it is endowed with a commutative ring structure by pointwise multiplication: We see from the File field in line 11 that the code belongs to the category part of SageMath, not to the manifold part, where the class DiffScalarFieldAlgebra is defined. This shows that the method is_commutative() has indeed be added to the methods of the base class DiffScalarFieldAlgebra, while dynamically generating the class DiffScalarFieldAlgebra-with-category.

(3.4) ∀f, g ∈ C ∞ (M ), ∀p ∈ M, (f.g)(p) := f (p)g(p).
Regarding the scalar field f introduced in Sec.

0 : M -→ R on U : (x, y) -→ 0 on V : (x , y ) -→ 0
while its unit element is

In [52]: CM.one().display() Out[52]:

1 : M -→ R on U : (x, y) -→ 1 on V : (x , y ) -→ 1 3.3.
Implementation of algebra operations. Let us consider some operation in the algebra C ∞ (M ):

In [53]: h = f + 2*CM.one() h.display() Out[53]: M -→ R on U : (x, y) -→ 2 x 2 +2 y 2 +3 x 2 +y 2 +1 on V : (x , y ) -→ 3 x 2 +3 y 2 +2 x 2 +y 2 +1 In [54]: h(p) Out[54]: 13 6
Let us examine how the addition in In [53] is performed. For the Python interpreter h = f + 2*CM.one() is equivalent to h = f.__add__(2*CM.one()), i.e. the + operator amounts to calling the method __add__() on its left operand, with the right operand as argument. To have a look at the source code of this method, we use the double question mark: 15In [55]: f.__add__?? From lines 1 and 4, we see that the method __add__() is implemented at the level of the class Element from which DiffScalarField inherits, via CommutativeAlgebraElement (cf. Fig. 3.1).

In the present case, left = f and right = 2*CM.one() have the same parent, namely the algebra CM, so that the actual result is computed in line 12. The latter invokes the method _add_() (note the single underscore on each side of add). This operator is implemented at the level of ScalarField, as checked from the source code (see line 24 below):

In [56]: f._add_??

def _add_ ( self , other ):

""" Scalar field addition .

INPUT :

-'' other ' ' --a scalar field ( in the same algebra as '' self ' ')

OUTPUT :

-the scalar field resulting from the addition of '' self ' ' and This reflects a general strategy 16 in SageMath: the arithmetic Python operators __add__(), __sub__(), etc. are implemented at the top-level class Element, while specific element subclasses, like ScalarField here, implement single-underscore methods _add_(), _sub_(), etc., which perform the actual computation when both operands have the same parent. Looking at the code (lines 15 to 23), we notice that the first step is to search for the charts in which both operands of the addition operator have a coordinate expression (line 15). This is performed by the method common_charts(); in the current example, we get the two stereographic charts defined on M :

In [57]: f.common_charts(2*CM.one()) Out[57]: [(U, (x, y)) , (V, (x , y ))]
In general, common_charts() returns the charts for which both operands have already a known coordinate expression or for which a coordinate expression can be computed by a known transition map, as we can see on the source code:

In [58]: f.common_charts??

def common_charts ( self , other ):

""" Find common charts for the expressions of the scalar field and '' other ' '.

INPUT :

-'' other ' ' --a scalar field OUTPUT :

-list of common charts ; if no common chart is found , '' None ' ' is returned ( instead of an empty list ) ... We notice that the addition is performed in line 14 on the symbolic expression with respect to the symbolic backend currently at work (SageMath/Pynac, SymPy, ...), as returned by the method expr() (see Sec. 3.1). Let us recall that the user can change the symbolic backend at any time by means of the method set_calculus_method(), applied either to a chart or to an open subset (possibly M itself). Besides, we notice on line 14 above that the result of the symbolic addition is automatically simplified, by means of the method _simplify. The latter invokes a chain of simplifying functions, which depends on the symbolic backend. 17Let us now discuss the second case in the __add__() method of Element, namely the case for which the parents of both operands are different (lines 14-15 in the code listed as a result of In [55], on page 18). This case is treated via SageMath coercion model, which allows one to deal with additions like

-------------------------------------------------------------
---------------------------------------------------------- for chart1
In [60]: h1 = f + 2 h1.display() Out[60]: M -→ R on U : (x, y) -→ 2 x 2 +2 y 2 +3 x 2 +y 2 +1 on V : (x , y ) -→ 3 x 2 +3 y 2 +2
x 2 +y 2 +1

A priori, f + 2 is not a well defined operation, since the integer 2 does not belong to the algebra C ∞ (M ). However SageMath manages to treat it because 2 can be coerced (i.e. automatically and unambiguously converted) via CM(2) into a element of C ∞ (M ), namely the constant scalar field whose value is 2:

In [61]: CM(2).display() Out[61]: M -→ R on U : (x, y) -→ 2 on V : (x , y ) -→ 2
This happens because there exists a coercion map from the parent of 2, namely the ring of integers Z (denoted ZZ in SageMath), to C ∞ (M ):

In This chapter is devoted to the most basic objects of tensor calculus: vector fields. We start by defining tangent vectors and tangent spaces on a differentiable manifold (Sec. 2), and then move to vector fields (Sec. 3).

Tangent vectors

2.1. Definitions. Let M be a smooth manifold of dimension n over the topological field K and C ∞ (M ) the corresponding algebra of scalar fields introduced in Sec. 3.2. For p ∈ M , a tangent vector at p is a map

(2.1) v : C ∞ (M ) -→ K such that (i) v is K-linear and (ii) v obeys (2.2) ∀f, g ∈ C ∞ (M ), v(f g) = v(f )g(p) + f (p)v(g).
Because of property (2.2), one says that v is a derivation at p. The set T p M of all tangent vectors at p is a vector space of dimension n over K; it is called the tangent space to M at p.

SageMath implementation.

To illustrate the implementation of tangent vectors in Sage-Math, we shall consider the same example M = S 2 as in Chap. 2. First of all, we recreate the same objects as in Chap. 2, starting with the manifold M and its two stereographic charts X U = (U, (x, y)) and X V = (V, (x , y )), with M = U ∪ V (the full Jupyter notebook is available at https://sagemanifolds.obspm.fr/jncf2018/):

In [ Out [START_REF] Birkandan | Symbolic and Numerical Analysis in General Relativity with Open Source Computer Algebra Systems[END_REF]:

Φ : M -→ R 3 on U : (x, y) -→ (X, Y, Z) = 2 x x 2 +y 2 +1 , 2 y x 2 +y 2 +1 , x 2 +y 2 -1 x 2 +y 2 +1 on V : (x , y ) -→ (X, Y, Z) = 2 x x 2 +y 2 +1 , 2 y x 2 +y 2 +1 , -x 2 +y 2 -1
x 2 +y 2 +1

In [START_REF] Bolotin | Introduction to Redberry: the computer algebra system designed for tensor manipulation[END_REF]: graph = XU.plot(chart=XR3, mapping=Phi, number_values=25, label_axes=False) + \ XV.plot(chart=XR3, mapping=Phi, number_values=25, color='green', label_axes=False) + \ p.plot(chart=XR3, mapping=Phi, label_offset=0.05) show(graph, viewer='threejs', online=True)

Finally, the last objects defined in Chap. 2 are the scalar field f :

In [START_REF] Culler | SnapPy, a computer program for studying the geometry and topology of 3-manifolds[END_REF]: f = M.scalar_field({XU: 1/(1+x^2+y^2), XV: (xp^2+yp^2)/(1+xp^2+yp^2)}, name='f') f.display() Out [START_REF] Culler | SnapPy, a computer program for studying the geometry and topology of 3-manifolds[END_REF]:

f : M -→ R on U : (x, y) -→ 1 x 2 +y 2 +1 on V : (x , y ) -→ x 2 +y 2 x 2 +y 2 +1
and its parent, namely the commutative algebra C ∞ (M ) of smooth maps M → R:

In [START_REF] Fletcher | Computer Programs for Calculating General-Relativistic Curvature Tensors[END_REF]: CM = M.scalar_field_algebra() CM Out [START_REF] Fletcher | Computer Programs for Calculating General-Relativistic Curvature Tensors[END_REF]:

C ∞ (M )
The tangent space at the point p introduced in In [3] is generated by

In [8]: Tp = M.tangent_space(p) Tp Out[8]: T p M
It is a vector space over K (here K = R, which is represented by SageMath's Symbolic Ring SR):

In [START_REF] Joyner | Sage Tutorial[END_REF]: print(Tp.category())

Category of finite dimensional vector spaces over Symbolic Ring

The dimension of the vector space T p M equals that of the manifold M :

In [START_REF] Korol'kova | yanov: Tensor computations in computer algebra systems[END_REF]: dim(Tp)

Out [START_REF] Korol'kova | yanov: Tensor computations in computer algebra systems[END_REF]: 2

Tangent spaces are implemented as a class inherited from TangentSpace via the category framework:

In [START_REF] Lee | Riemannian Manifolds: An Introduction to Curvature[END_REF]: type(Tp)

Out [START_REF] Lee | Riemannian Manifolds: An Introduction to Curvature[END_REF]: <class 'sage.manifolds.differentiable.tangent_space.TangentSpace_with_category'> FiniteRankFreeModule,18 , which, in SageMath is devoted to free modules of finite rank without any distinguished basis:

In [START_REF] Lee | Introduction to Smooth Manifolds[END_REF]: isinstance(Tp, FiniteRankFreeModule)

Out [START_REF] Lee | Introduction to Smooth Manifolds[END_REF]: True Two bases of T p M are already available: those generated by the derivations at p along the coordinates of charts XU and XV respectively:

In [START_REF] Maccallum | Computer Algebra in General Relativity[END_REF]: Tp.bases() Out [START_REF] Maccallum | Computer Algebra in General Relativity[END_REF]:

∂ ∂x , ∂ ∂y , ∂ ∂x , ∂ ∂y
None of these bases is distinguished, but one if the default one, which simply means that it is the basis to be considered if the basis argument is skipped in some methods:

In [START_REF] Maccallum | Computer algebra in gravity research[END_REF]: Tp.default_basis()

Out [START_REF] Maccallum | Computer algebra in gravity research[END_REF]:

∂ ∂x , ∂ ∂y
A tangent vector is created as an element of the tangent space by the standard SageMath procedure new_element = parent(...), where ... stands for some material sufficient to construct the element:

In [START_REF] Martin-Garcia | xPerm: fast index canonicalization for tensor computer algebra[END_REF]: vp = Tp((-3, 2), name='v') print(vp)

Tangent vector v at Point p on the 2-dimensional differentiable manifold M Since the basis is not specified, the pair (-3, 2) refers to components with respect to the default basis:

In [START_REF] Milnor | On manifolds homeomorphic to the 7-sphere[END_REF]: vp.display()

Out[16]: v = -3 ∂ ∂x + 2 ∂ ∂y
We have of course

In [START_REF] O'neill | Semi-Riemannian Geometry, with Applications to Relativity[END_REF]: vp.parent() Out [START_REF] O'neill | Semi-Riemannian Geometry, with Applications to Relativity[END_REF]:

T p M
In [18]: vp in Tp

Out[18]: True

As other manifold objects, tangent vectors have some plotting capabilities:

In [START_REF] Peeters | Symbolic field theory with Cadabra[END_REF]: graph += vp.plot(chart=XR3, mapping=Phi, scale=0.5, color='gold') show(graph, viewer='threejs', online=True)

Out [START_REF] Peeters | Symbolic field theory with Cadabra[END_REF]: (see Figure 2.1)

The main attribute of the object vp representing the vector v is the private dictionary _components, which stores the components of v in various bases of T p M : In [START_REF] Skea | Applications of SHEEP[END_REF]: type(vpc)

Out [START_REF] Skea | Applications of SHEEP[END_REF]: <class 'sage.tensor.modules.comp.Components'>

The components themselves are stored in the private dictionary _comp of the Components object, with the indices as keys:

In [START_REF] Steenrod | The Topology of Fibre Bundles[END_REF]: vpc._comp

Out [START_REF] Steenrod | The Topology of Fibre Bundles[END_REF]: {(0) : -3, (1) : 2}

Hence the components are not stored via a sequence data type (list or tuple), as one might have expected, but via a mapping type (dictionary). This is a general feature of the class Components and all its subclasses, which permits to not store vanishing components and, in case of symmetries (for multi-index objects like tensors), to store only non-redundant components.

Vector fields

3.1. Definition. The tangent bundle of M is the disjoint union of the tangent spaces at all points of M :

(3.1) T M = p∈M T p M.
Elements of T M are usually denoted by (p, u), with u ∈ T p M . The tangent bundle is canonically endowed with the projection map:

(3.2) π : T M -→ M (p, u) -→ p.
The tangent bundle inherits some manifold structure from M : T M is a smooth manifold of dimension 2n over K (n being the dimension of M ).

A vector field on M is a continuous right-inverse of the projection map, i.e. it is a map

(3.3) v : M -→ T M p -→ v| p such that π • v = Id M , i.e. such that (3.4) ∀p ∈ M, v| p ∈ T p M.
3.2. Module of vector fields. The set X(M ) of all vector fields on M is naturally endowed with two algebraic structures:

(1) X(M ) is a (infinite dimensional) vector space over K -the base field of M -, the scalar multiplication K × X(M ) → X(M ), (λ, v) → λv being defined by

(3.5) ∀p ∈ M, (λv)| p = λv| p ,
where the right-hand side involves the scalar multiplication in the vector space T p M ; (2) X(M ) is a module over C ∞ (M ) -the commutative algebra of scalar fields -, the scalar multiplication

C ∞ (M ) × X(M ) → X(M ), (f, v) → f v being defined by (3.6) ∀p ∈ M, (f v)| p = f (p)v| p ,
where the right-hand side involves the scalar multiplication by f (p) ∈ K in the vector space T p M . An important subcase of 2 is when X(M ) is a free module over C ∞ (M ), i.e. when X(M ) admits a basis (a generating set consisting of linearly independent elements). If this occurs, then X(M ) is actually a free module of finite rank over C ∞ (M ) and its rank is n -the dimension of M over K, which means that all bases share the same cardinality, namely n. One says that M is a parallelizable manifold. A basis (e a ) 1≤a≤n of X(M ) is called a vector frame; for any p ∈ M , ( e a | p ) 1≤a≤n is then a basis of the tangent vector space T p M . Any vector field has a unique decomposition with respect to the vector frame20 (e a ) 1≤a≤n :

(3.7) ∀v ∈ X(M ), v = v a e a , with v a ∈ C ∞ (M ).
At each point p ∈ M , Eq. (3.7) gives birth to an identity in the tangent space T p M :

(3.8) v| p = v a (p) e a | p , with v a (p) ∈ K,
which is nothing but the expansion of the tangent vector v| p on the basis ( e a | p ) 1≤a≤n of the vector space T p M . Note that if M is covered by a chart X, i.e. M is the domain of the chart X, then M is parallelizable and a vector frame is (∂/∂x a ) 1≤a≤n , where the x a 's are the coordinates of chart X. Such a vector frame is called a coordinate frame or natural basis. More generally, examples of parallelizable manifolds are [START_REF] Lee | Introduction to Smooth Manifolds[END_REF] • the Cartesian space R n for n = 1, 2, . . .,

• the circle S 1 ,

• the torus

T 2 = S 1 × S 1 ,
• the sphere S 3 SU(2), as any Lie group,

• the sphere S 7 ,

• any orientable 3-manifold (Steenrod theorem [START_REF] Steenrod | The Topology of Fibre Bundles[END_REF]). • the sphere S 2 (as a consequence of the hairy ball theorem), as well as any sphere S n with n ∈ {1, 3, 7}, • the real projective plane RP 2 . Actually, "most" manifolds are non-parallelizable. As noticed above, if a manifold is covered by a single chart, it is parallelizable (the prototype being R n ). But the reverse is not true: S 1 and T 2 are parallelizable and require at least two charts to cover them.

On the other hand, examples of non-parallelizable manifolds are

3.3. SageMath implementation. Among the two algebraic structures for X(M ) discussed in Sec. 3.2, we select the second one, i.e. we consider X(M ) as a C ∞ (M )-module. With respect to the infinite-dimensional K-vector space point of view, the advantage for the implementation is the reduction to finite-dimensional structures: free modules of rank n on parallelizable open subsets of M . Indeed, if U is such an open subset, i.e. if X(U ) is a free C ∞ (U )-module of rank n, the generic class FiniteRankFreeModule discussed in Sec. 2.2 can be used to implement X(U ). The great benefit is that all calculus implemented on the free module elements, like the addition or the scalar multiplication, can be used as such for vector fields. This implies that vector fields will be described by their (scalar-field) components on vector frames, as defined by Eq. (3.7), on parallelizable open subsets of M .

If the manifold M is not parallelizable, we assume that it can be covered by a finite number m of parallelizable open subsets U i (1 ≤ i ≤ m):

(3.9) M = m i=1 U i , with U i parallelizable
In particular, this holds if M is compact, for any compact manifold admits a finite atlas.

For each i ∈ {1, . . . , m}, X(U i ) is a free module of rank n = dim M and is implemented in Sage-Math as an instance of VectorFieldFreeModule, which is a subclass of FiniteRankFreeModule. This inheritance is illustrated in Fig. 3.1. On that figure, we note that the class TangentSpace discussed in Sec. 2.2 inherits from FiniteRankFreeModule as well.

A vector field v ∈ X(M ) is then described by its restrictions v| Ui 1≤i≤m to each of the U i 's. Assuming that at least one vector frame is introduced in each of the U i 's, (e i,a ) 1≤a≤n say, the restriction v| Ui of v to U i is decribed by its components v a i in that frame: (3.10)

v| Ui = v a i e i,a , with v a i ∈ C ∞ (U i ).
Let us illustrate this strategy with the example of S 2 . We get X(M ) by21

In [START_REF] Stein | SAGE: System for Algebra and Geometry Experimentation[END_REF]: YM = M.vector_field_module() YM Out [START_REF] Stein | SAGE: System for Algebra and Geometry Experimentation[END_REF]: X (M )

As discussed above, X(M ) is considered as a module over C ∞ (M ):

In [START_REF] Taubes | Gauge theory on asymptotically periodic 4-manifolds[END_REF]: YM.category()

Out [START_REF] Taubes | Gauge theory on asymptotically periodic 4-manifolds[END_REF]:

Modules C ∞ (M )
Since the algebra C ∞ (M ) is denoted CM, we have

In [START_REF] Toth | Tensor manipulation in GPL Maxima[END_REF]: YM.base_ring() is CM Out [START_REF] Toth | Tensor manipulation in GPL Maxima[END_REF]: True X(M ) is not a free module; in particular, we can check that its SageMath implementation does not belong to the class FiniteRankFreeModule:

In [START_REF] Zimmermann | Calcul mathématique avec Sage[END_REF]: isinstance(YM, FiniteRankFreeModule)

Out [START_REF] Zimmermann | Calcul mathématique avec Sage[END_REF]: False This is because M = S 2 is not a parallelizable manifold:

In [START_REF] Zimmermann | Computational Mathematics with SageMath[END_REF]: M.is_manifestly_parallelizable() Out [START_REF] Zimmermann | Computational Mathematics with SageMath[END_REF]: False Via SageMath category framework, the module X(M ) is implemented by a dynamically-generated subclass of the class VectorFieldModule, which is devoted to modules of vector fields on nonparallelizable manifolds:

In [29]: type(YM)

Out[29]: <class 'sage.manifolds.differentiable.vectorfield_module. VectorFieldModule_with_category'> On the contrary, the set X(U ) of vector fields on U is a free module of finite rank over the algebra C ∞ (U ): Similarly, X(V ) is a free module, endowed with the coordinate frame associated to stereographic coordinates from the South pole, which we denote by eV:

In [30]: YU = U.
In [40]: YV = V.vector_field_module() YV.bases()

In [48]: v = M.vector_field(name='v') v[eU, 0] = f.restrict(U) v[eU, 1] = -2 v.display(eU) Out[48]: v = 1 x 2 + y 2 + 1 ∂ ∂x -2 ∂ ∂y
Notice that, at this stage, we have defined v only on U , by setting its components in the vector frame eU, either explicitly as scalar fields, like the component v 0 set to the restriction of f to U or implicitly, like the component v 1 : the integer -2 will be coerced to the constant scalar field of value -2 (cf. Sec. 3.3). We can ask for the scalar-field value of a component via the double-bracket operator; since eU is the default frame on M , we do not have to specify it:

In [49]: v[[0]] Out[49]: f In [50]: v[[0]].display() Out[50]: f : U -→ R (x, y) -→ 1 x 2 +y 2 +1 on W : (x , y ) -→ x 2 +y 2 x 2 +y 2 +1
Note that, for convenience, the single bracket operator returns a chart function of the component:

In [51]: v[0] Out[51]: 1 x 2 + y 2 + 1
The restriction of v to W is of course

In [52]: v.restrict(W).display(eUW) Out[52]: v = 1 x 2 + y 2 + 1 ∂ ∂x -2 ∂ ∂y
Since we have a second vector frame on W , namely eVW, and the change-of-frame automorphisms are known, we can ask for the components of v with respect to that frame:

In [53]: v.restrict(W).display(eVW) Out[53]: v = 4 xy 3 -x 2 + 4 x 3 + x y + y 2 x 6 + y 6 + (3 x 2 + 1)y 4 + x 4 + (3 x 4 + 2 x 2 )y 2 ∂ ∂x + - 2 x 4 -y 4 + x 2 + xy -y 2 x 6 + y 6 + (3 x 2 + 1)y 4 + x 4 + (3 x 4 + 2 x 2 )y 2

∂ ∂y

Notice that the components are expressed in terms of the coordinates (x, y) since they form the default chart on W . To have them expressed in terms of the coordinates (x , y ), we have to add the restriction of the chart (V, (x , y )) to W as the second argument of the method display():

In [54]: v.restrict(W).display(eVW, XV.restrict(W))

Out[54]: v =   - x 4 -4 x y 3 -y 4 -4 x 3 + x y x 2 + y 2 + 1   ∂ ∂x +   - 2 x 4 + x 3 y + x y 3 -y 4 + x 2 -y 2 x 2 + y 2 + 1   ∂ ∂y
We extend the expression of v to the full vector frame XV by continuation of this expression:

In [55]: v.add_comp_by_continuation(eV, W, chart=XV)
We have then

In [56]: v.display(eV) Out[56]: v =   - x 4 -4 x y 3 -y 4 -4 x 3 + x y x 2 + y 2 + 1   ∂ ∂x +   - 2 x 4 + x 3 y + x y 3 -y 4 + x 2 -y 2 x 2 + y 2 + 1

  ∂ ∂y

At this stage, the vector field v is defined in all M . According to the hairy ball theorem, it has to vanish somewhere. Let us show that this occurs at the North pole, by first introducing the latter, as the point of stereographic coordinates (x , y ) = (0, 0):

In [57]: N = M((0,0), chart=XV, name='N') print(N)
Point N on the 2-dimensional differentiable manifold M

As a check, we verify that the image of N by the canonical embedding Φ : S 2 → R 3 is the point of Cartesian coordinates (0, 0, 1):

In [58]: XR3(Phi(N))
Out[58]: (0, 0, 1)

The vanishing of v| N :

In [59]: v.at(N).display() Out[59]: v = 0
On the other hand, v does not vanish at the point p introduced above:

In [60]: v.at(p).display() Out[60]: v = 1 6 ∂ ∂x -2 ∂ ∂y
We may plot the vector field v in terms of the stereographic coordinates from the North pole:

In [61]: v.plot(chart=XU, chart_domain=XU, max_range=2, number_values=5, scale=0.4, aspect_ratio=1)
or in term of those from the South pole:

In [62]: v.plot(chart=XV, chart_domain=XV, max_range=2, number_values=9, scale=0.05, aspect_ratio=1)

Thanks to the embedding Φ, we may also have a 3D plot of the vector field v atop of the 3D plot already obtained:

In [63]: graph_v = v.plot(chart=XR3, mapping=Phi, chart_domain=XU, number_values=7, scale=0.2) + \ v.plot(chart=XR3, mapping=Phi, chart_domain=XV, number_values=7, scale=0.2) show(graph + graph_v, viewer='threejs', online=True) Note that the sampling, performed on the two charts XU and XV is not uniform on the sphere. A better sampling would be achieved by introducing spherical coordinates.

Implementation details regarding vector fields.

Let us now investigate some internals of the implementation of vector fields. Vector fields on M are implemented via the class VectorField22 (actually by a dynamically generated subclass of it, within SageMath category framework):

In [64]: isinstance(v, sage.manifolds.differentiable.vectorfield.VectorField)

Out[64]: True

Since M is not parallelizable, the defining data of a vector field v on M are its restrictions v| Ui 1≤i≤m to parallelizable open subsets U i , following the scheme presented in Sec. 3.3. These restrictions are stored in the private dictionary _restrictions, whose keys are the open subsets:

In [65]: v._restrictions Out[65]: {V : v, W : v, U : v}
Let us consider one of these restrictions, for instance the restriction v| U to U :

In [66]: vU = v._restrictions [U] vU is v.restrict(U)

Out[66]: True

Since U is a parallelizable open subset, the object vU belongs to the class VectorFieldParal, which is devoted to vector fields on parallelizable manifolds:

In [67]: isinstance(vU, sage.manifolds.differentiable.vectorfield.VectorFieldParal)

Out[67]: True

The class VectorFieldParal inherits both from FiniteRankFreeModuleElement (as TangentVector) and from VectorField (see Fig. 3.3). The defining data of v| U are its sets of components with respect to (possibly various) vector frames on U , according to Eq. (3.10). The sets of components are stored in the private dictionary _components, whose keys are the vector frames:

In The values of the dictionary _components belong to the same class Components as that discussed in Sec. 2.2 for the storage of components of tangent vectors:

In The difference with the tangent vector case is that the values of that dictionary are now scalar fields, i.e. elements of C ∞ (U ) in the present case. This is of course in agreement with the treatment of X(U ) as a free module over C ∞ (U ), as discussed in Sec. 3.3. Taking into account the storage of scalar fields presented in Sec. 3.1, the full storage structure of vector fields is presented in Fig. 3.4 (the latter actually regards tensor fields, of which vector fields constitute a subcase).

Let us perform some algebraic operation on vector fields:

In boxes are dictionary values, with the corresponding dictionary key located on the left of them.

The Python class of each dictionary value is indicated in typewriter font at the top of the yellow box. In the hierarchical tree, only the leftmost branch is indicated by grey connectors. In the special case of vector fields, the classes TensorField and TensorFieldParal are to be replaced by VectorField and VectorFieldParal respectively.

Out[73]: Vector field on the 2-dimensional differentiable manifold M

The code for the addition is accessible via

In [74]: v.__add__?? ... This is exactly the same method __add__() as that discussed in Sec. 3.3 for the addition of scalar fields (cf. page 18), namely the method __add__() of the top-level class Element, from which both VectorField and DiffScalarField inherit, cf. the inheritance diagrams of Figs. 3.3 and 3.1 (taking into account that CommutativeAlgebraElement is a subclass of Element). In the present case, left = v and right = f*v have the same parent, so that the actual result is computed in line 12, via the method _add_() (note the single underscore on each side of add). This operator is implemented at the level of TensorField, as it can be checked from the source code (see lines 3 and 29 below):

In [75]: v._add_??

def _add_ ( self , other ):

""" Tensor field addition .

INPUT :

-'' other ' ' --a tensor field , in the same tensor module as '' self ' ' OUTPUT :

-the tensor field resulting from the addition of '' self ' ' and '' other ' ' ... The first step in the addition of two vector fields is to search in the restrictions of both vector fields for common domains: this is performed in line 16, via the method _common_subdomains. Then the addition is performed at the level of the restrictions, in line 17. The rest of the code is simply the set up of the vector field object containing the result. Recursively, the addition performed in line 17 will reach a level at which the domains are parallelizable. Then a different method _add_(), will be involved, as we can check on vU:

In [76]: vU._add_??

def _add_ ( self , other ):

"""

Tensor addition .

INPUT :

-'' other ' ' --a tensor , of the same type as '' self ' ' OUTPUT :

-the tensor resulting from the addition of '' self ' ' and '' other ' ' ... """ Component addition .

INPUT :

-'' other ' ' --components of the same number of indices and defined on the same frame as '' self ' ' OUTPUT :

-components resulting from the addition of '' self ' ' and '' other ' ' ... First of all, we note from line 26 that this is not the method __add__() of class Element, as it was for VectorField and VectorFieldParal, but instead the method __add__() implemented in class Components. This is because Components is a technical class, as opposed to the mathematical classes VectorField and DiffScalarField; therefore it does not inherits from Element, but only from the base class SageObject, which does not implement any addition. We note from lines 17-19 that the computation of the components can be done in parallel on more that one CPU core if user has turned on parallelization. 24 Focusing on the sequential code (lines 23-24), we see that the addition is performed component by component. Note that this addition is that of scalar fields, as discussed in Sec. 3.3, since each component being an element of C ∞ (U ), the base ring of X(U ).

3.6. Action of vector fields on scalar fields. The action of v on f is defined pointwise by considering v at each point p ∈ M as a derivation (the very definition of a tangent vector, cf. Sec. 2.1); the result is then a scalar field v(f ) on M :

In [78]: vf = v(f) vf Out[78]: v (f ) In [79]: vf.display() Out[79]: v (f ) : M -→ R on U : (x, y) -→ 2 (2 y 3 +2 (x 2 +1)y-x) x 6 +y 6 +3 (x 2 +1)y 4 +3 x 4 +3 (x 4 +2 x 2 +1)y 2 +3 x 2 +1
on V : (x , y ) -→ -2 (x 5 +2 x 3 y 2 +x y 4 -2 y 5 -2 (2 x 2 +1)y 3 -2 (x 4 +x 2 )y )

x 6 +y 6 +3 (x 2 +1)y 4 +3 x 4 +3 (x 4 +2 x 2 +1)y 2 +3 x 2 +1 I-42
Since eU is the default frame on M , the default display of df is performed in terms of eU's coframe:

In [86]: df.display() Out[86]: df = - 2 x x 4 + y 4 + 2 (x 2 + 1)y 2 + 2 x 2 + 1 dx + - 2 y x 4 + y 4 + 2 (x 2 + 1)y 2 + 2 x 2 + 1 dy
We may check that in this basis, the components of df | U are nothing but the partial derivatives of the coordinate expression of f with respect to coordinates (x, y):

In In the coframe associated with eV = (∂/∂x , ∂/∂y ):

In [89]: df.display(eV) Out[89]: df = 2 x x 4 + y 4 + 2 x 2 + 1 y 2 + 2 x 2 + 1 dx + 2 y x 4 + y 4 + 2 x 2 + 1 y 2 + 2 x 2 + 1 dy
Since eV is not the default vector frame on M and XV = (V, (x , y )) is not the default chart on M , we get the individual components by specifying both eV and XV, in addition to the index, in the square-bracket operator:

In [90]: df[eV,0,XV] Out[90]: 2 x x 4 + y 4 + 2 x 2 + 1 y 2 + 2 x 2 + 1
We may then check that the components in the frame eV are the partial derivatives with respect to the coordinates xp = x and yp = y of the chart XV: We can use the method display_comp() for a display component by component: The parent of t is the set T (1,1) As for vector fields, since M is not parallelizable, the C ∞ (M )-module T (1,1) (M ) is not free and the tensor fields are described by their restrictions to parallelizable subdomains: The metric g on M is the pullback of h associated with the embedding Φ introduced in Sec. 2.2:

v ⊗ df = - 2 x x 6 + y 6 + 3 (x 2 + 1)y 4 + 3 x 4 + 3 (x 4 + 2 x 2 + 1)y 2 + 3 x 2 + 1 ∂ ∂x ⊗ dx + - 2 y x 6 + y 6 + 3 (x 2 + 1)y 4 + 3 x 4 + 3 (x 4 + 2 x 2 + 1)y 2 + 3 x 2 + 1 ∂ ∂x ⊗ dy + 4 x x 4 + y 4 + 2 (x 2 + 1)y 2 + 2 x 2 + 1 ∂ ∂y ⊗ dx + 4 y x 4 + y 4 + 2 (x 2 + 1)y 2 + 2 x 2 + 1 ∂ ∂y ⊗ dy In [100]: t.display(eV) Out[100]: v ⊗ df =   - 2 x 5 -4 x 2 y 3 -x y 4 -4 x 4 + x 2 y x 6 + y 6 + 3 x 2 + 1 y 4 + 3 x 4 + 3 x 4 + 2 x 2 + 1 y 2 + 3 x 2 + 1   ∂ ∂x ⊗ dx +   - 2
In [108]: g = M.metric('g') g.set( Phi.pullback(h) ) print(g)

Riemannian metric g on the 2-dimensional differentiable manifold M Note that we could have defined g intrinsically, i.e. by providing its components in the two vector frames eU and eV, as we did for the metric h on R 3 . Instead, we have chosen to get it as the pullback by Φ of h, as an example of pullback associated with some differential map.

The metric is a symmetric tensor field of type (0,2):

In x 8 +y 8 +4 (x 2 +1)y 6 +4 x 6 +6 (x 4 +2 x 2 +1)y 4 +6 x 4 +4 (x 6 +3 x 4 +3 x 2 +1)y 2 +4 x 2 +1 on V : (x , y ) -→ 4 (5 x 8 +5 y 8 +4 (5 x 2 +2)y 6 +8 x 6 +2 (15 x 4 +12 x 2 +2)y 4 +4 x 4 +4 (5 x 6 +6 x 4 +2 x 2 )y 2 )

x 8 +y 8 +4 (x 2 +1)y 6 +4 x 6 +6 (x 4 +2 x 2 +1)y 4 +6 x 4 +4 (x 6 +3 x 4 +3 x 2 +1)y 2 +4 x 2 +1 4.2. Levi-Civita connection. The Levi-Civita connection associated with the metric g is The Riemann tensor of the Euclidean metric h on R 3 is identically zero, i.e. h is a flat metric:

In [126]: h.riemann().display()

Figure 2 . 1 :

 21 Figure 2.1: Python classes for topological manifolds, differentiable manifolds, subsets of them and points on them (ManifoldPoint).

Figure 2 . 2 :

 22 Figure 2.2: Output corresponding to In [24].

( 3

 3 .2) f (p) = F ( x 1 , . . . , x n coord. of p in chart X ) = F ( x1 , . . . , xn coord. of p in chart X ) = . . .

1 ) 1 )Figure 3 . 1 :

 1131 Figure 3.1: SageMath classes for scalar fields on a manifold.

  The algebra C ∞ (M ) is implemented in SageMath via the parent class DiffScalarFieldAlgebra,14 in the category CommutativeAlgebras. The corresponding element class is of course DiffScalarField (cf. Fig. 3.1). The SageMath object representing C ∞ (M ) is obtained from M via the method scalar_field_algebra(): In [44]: CM = M.scalar_field_algebra() CM Out[44]: C ∞ (M ) In [45]: CM.category() Out[45]: CommutativeAlgebras SR As for the manifold classes, the actual Python class implementing C ∞ (M ) is inherited from DiffScalarFieldAlgebra via SageMath's category framework (cf. Sec. 2.1), hence it bares the name DiffScalarFieldAlgebra_with_category: In [46]: type(CM) Out[46]: <class 'sage.manifolds.differentiable.scalarfield_algebra. DiffScalarFieldAlgebra_with_category'> The class DiffScalarFieldAlgebra_with_category is dynamically generated as a subclass of DiffScalarFieldAlgebra with extra functionalities, like for instance the method is_commutative(): In [47]: CM.is_commutative() Out[47]: True To have a look at the corresponding code, we use the double question mark, owing to the fact that SageMath is open-source: In [48]: CM.is_commutative?? def is_commu tative ( self ): """ Return '' True ' ' , since commutative magmas are commutative . EXAMPLES :: sage : Parent ( QQ , category = C o m m u t a t i v e R i n g s ()). is_com mutative (../ local / lib / python2 .7/ site -packages / sage / categories / magmas . py

  3.1, we have of course In [49]: f in CM Out[49]: True Actually, in SageMath language, CM=C ∞ (M ) is the parent of f: In [50]: f.parent() is CM Out[50]: True The zero element of the algebra C ∞ (M ) is In [51]: CM.zero().display() Out[51]:

File

  : .../ src / sage / structure / element . pyx def __add__ ( left , right ): """ Top -level addition operator for : class : ' Element ' invoking the coercion model . See : ref : ' element_arithmetic '. ... """ cdef int cl = c l a s s i f y _ e l e m e n t s ( left , right ) if H A V E _ S A M E _ P A R E NT ( cl ): return ( < Element > left ). _add_ ( right ) # Left and right are Sage elements = > use coercion model if B O T H _ A R E _ E L E M E NT ( cl ): return coerc ion_model . bin_op ( left , right , add ) ...

  1]: %display latex In [2]: M = Manifold(2, 'M') U = M.open_subset('U') XU.<x,y> = U.chart() V = M.open_subset('V') XV.<xp,yp> = V.chart("xp:x' yp:y'") M.declare_union(U,V) XU_to_XV = XU.transition_map(XV, (x/(x^2+y^2), y/(x^2+y^2)), intersection_name='W', restrictions1= x^2+y^2!=0, restrictions2= xp^2+yp^2!=0) XV_to_XU = XU_to_XV.inverse() M.atlas() Out[2]: [(U, (x, y)) , (V, (x , y )) , (W, (x, y)) , (W, (x , y ))] Then we introduce the point p ∈ U of coordinates (x, y) = (1, 2): In [3]: p = U((1,2), chart=XU, name='p') print(p) Point p on the 2-dimensional differentiable manifold MThe canonical embedding of S 2 in R 3 is defined mostly for graphical purposes:In [4]: R3 = Manifold(3, 'R^3',r'\mathbb{R}^3') XR3.<X,Y,Z> = R3.chart() Phi = M.diff_map(R3, {(XU, XR3): [2*x/(1+x^2+y^2), 2*y/(1+x^2+y^2), (x^2+y^2-1)/(1+x^2+y^2)], (XV, XR3): [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') Phi.display()

Remark 1 :

 1 In SageMath, free modules with a distinguished basis are created with the command FreeModule or VectorSpace and belong to classes different from FiniteRankFreeModule. The differences are illustrated at http://doc.sagemath.org/html/en/reference/modules/sage/tensor/modules/finite_rank_free_module.html#diff-freemodule.

1 -

 1 index components w.r.t. Basis (d/dx,d/dy) on the Tangent space at Point p on the 2-dimensional differentiable manifold MThe keys of the dictionary _components are the bases of T p M , while the values belong to the class Components 19 devoted to store ring elements indexed by integers or tuples of integers:

Figure 2 . 1 :

 21 Figure 2.1: Output corresponding to In [19].

Figure 3 . 1 :

 31 Figure 3.1: SageMath classes for modules involved in differentiable manifolds.

Figure 3 . 2 : 3 . 4 .

 3234 Figure 3.2: Output corresponding to In [37].

Figure 3 . 3 :

 33 Figure 3.3: SageMath classes for tensor fields involved in differentiable manifolds. There are various multiple inheritances involving diamond diagrams; Python's method resolution order algorithm (MRO) relies on the ordering of the parents in the class declaration and this order can be read from the left to the right in this figure. For instance, the class VectorFieldParal is declared as class VectorFieldParal(FiniteRankFreeModuleElement, MultivectorFieldParal, VectorField).

1 TensorFieldParalT

 1 [73]: w = v + f*v w | U1 = T a b e a ⊗ e b = T âb ε â ⊗ ε b = . . . Basic x 1 cos x 2 . . .

Figure 3 . 4 :

 34 Figure 3.4: Internal storage of tensor fields. Red boxes represent Python dictionaries, yellow

File

  : .../ src / sage / structure / element . pyx def __add__ ( left , right ): """ Top -level addition operator for : class : ' Element ' invoking the coercion model . See : ref : ' element_arithmetic '. = c l a s s i f y _ e l e m e n t s ( left , right ) if H A V E _ S A M E _ P A R E NT ( cl ): return ( < Element > left ). _add_ ( right ) # Left and right are Sage elements = > use coercion model if B O T H _ A R E _ E L E M E NT ( cl ): return coerc ion_model . bin_op ( left , right , add )

  for dom in self . _ c o m m o n _ s u b d o m a i n s ( other ): resu_rst [ dom ] = self . _restrictions [ dom ] + other . _restrictions [ dom ] some_rst = next ( itervalues ( resu_rst )) resu_sym = some_rst . _sym resu_antisym = some_rst . _antisym resu = self . _vmodule . tensor ( self . _tensor_type , sym = resu_sym , antisym = resu_antisym ) resu . _restrictions = resu_rst if self . _name is not None and other . _name is not None : resu . _name = self . _name + '+ ' + other . _name if self . _latex_name is not None and other . _latex_name is not None : resu . _latex_name = self . _latex_name + '+ ' + other . _latex_name return resu File : .../ site -packages / sage / manifolds / diff erentiab le / tensorfield . py

  / site -packages / sage / tensor / modules / comp . py

  [87]: df[0] == diff(f.expr(), x) Out[87]: True In [88]: df[1] == diff(f.expr(), y) Out[88]: True

3 .

 3 In[91]: df[eV,0,XV] == diff(f.expr(XV), xp)Out[91]: TrueIn[92]: df[eV,1,XV] == diff(f.expr(XV), yp)Out[92]: TrueThe parent of df is the set Ω 1 (M ) of all 1-forms on M , considered as a C ∞ (M )-module:In [93]: print(df.parent()) df.parent() Module Omega^1(M) of 1-forms on the 2-dimensional differentiable manifold M Out[93]: Ω 1 (M )In [94]: df.parent().base_ring()Out[94]: C ∞ (M )This module is actually the dual of the vector-field module X(M ), which is represented by the Python object YM (cf. Sec. 3.3):In [95]: YM.dual()Out[95]: Ω 1 (M )Consequently, a 1-form acts on vector fields, yielding an element of C ∞ (M ), i.e. a scalar field:In [96]: print(df(v))Scalar field df(v) on the 2-dimensional differentiable manifold M This scalar field is nothing but the result of the action of v on f discussed in Sec. 3.6:In [97]: df(v) == v(f)Out[97]: True More general tensor fieldsWe construct a tensor of type (1, 1) by taking the tensor product v ⊗ df :In [98]: t = v * df t Out[98]: Tensor field of type (1,1) on the 2-dimensional differentiable manifold M In [99]: t.display() Out[99]:

6 4 x x 4 4 y x 4

 64444 +y 6 +3 (x 2 +1)y 4 +3 x 4 +3 (x 4 +2 x 2 +1)y 2 +3 x 2 +1 v ⊗ df x y = -2 y x 6 +y 6 +3 (x 2 +1)y 4 +3 x 4 +3 (x 4 +2 x 2 +1)y 2 +3 x 2 +1 v ⊗ df y x = +y 4 +2 (x 2 +1)y 2 +2 x 2 +1 v ⊗ df y y = +y 4 +2 (x 2 +1)y 2 +2 x 2 +1

  (M ) of all type-(1, 1) tensor fields on M , considered as a C ∞ (M )module: In [102]: print(t.parent()) t.parent() Module T^(1,1)(M) of type-(1,1) tensors fields on the 2-dimensional differentiable manifold M Out[102]: T (1,1) (M ) In [103]: t.parent().base_ring() Out[103]: C ∞ (M )

  In [104]: t._restrictions Out[104]: {V : v ⊗ df, U : v ⊗ df } These restrictions form free modules: In [105]: print(t._restrictions[U].parent()) Free module T^(1,1)(U) of type-(1,1) tensors fields on the Open subset U of the 2-dimensional differentiable manifold M In [106]: t._restrictions[U].parent().base_ring() Out[106]: C ∞ (U ) 4. Riemannian metric 4.1. Defining a metric. The standard metric on M = S 2 is that induced by the Euclidean metric of R 3 . Let us start by defining the latter:In [107]: h = R3.metric('h') h[0,0], h[1,1], h[2, 2] = 1,1, 1 h.display() Out[107]: h = dX ⊗ dX + dY ⊗ dY + dZ ⊗ dZ

4 (

 4 [109]: g.tensor_type() Out[109]: (0, 2)The expression of the metric in terms of the default frame on M (eU):In [110]: g.display() Out[110]: g = 4 x 4 + y 4 + 2 (x 2 + 1)y 2 + 2 x 2 + 1 dx ⊗ dx + 4 x 4 + y 4 + 2 (x 2 + 1)y 2 + 2 x 2 + 1 dy ⊗ dyWe may factorize the metric components to get a better display:In [111]: g[0,0].factor(); g[1,1].factor() Out[111]: 4 (x 2 + y 2 + 1) 2 In [112]: g.display() Out[112]: g = 4 (x 2 + y 2 + 1) 2 dx ⊗ dx + x 2 + y 2 + 1)2 dy ⊗ dy A matrix view of the components of g in the manifold's default frame:Display in terms of the vector frame (V, (∂ x , ∂ y )):In [114]: g.display(eV) Out[114]: g = 4 x 4 + y 4 + 2 x 2 + 1 y 2 + 2 x 2 + 1 dx ⊗ dx + 4 x 4 + y 4 + 2 x 2 + 1 y 2 + 2 x 2 + 1 dy ⊗ dyThe metric acts on vector field pairs, resulting in a scalar field:In [115]: print(g(v,v)) Scalar field g(v,v) on the 2-dimensional differentiable manifold M In [116]: g(v,v).parent() Out[116]: C ∞ (M ) In [117]: g(v,v).display() Out[117]: g (v, v) : M -→ R on U : (x, y) -→ 4 (4 x 4 +4 y 4 +8 (x 2 +1)y 2 +8 x 2 +5)

In [ 118 ]

 118 : nab = g.connection() print(nab) nab Levi-Civita connection nabla_g associated with the Riemannian metric g on the 2-dimensional differentiable manifold M Out[118]: ∇ g The nonzero Christoffel symbols of g (skipping those that can be deduced by symmetry on the last two indices) w.r.t. the chart XU: In [119]: g.christoffel_symbols_display(chart=XU) 2 +1 ∇ g acting on the vector field v: In [120]: Dv = nab(v) print(Dv)

4 Riem 4 Riem 4

 444 ∂ ∂y⊗ dx ⊗ dy ⊗ dxThe components of the Riemann tensor in the default frame on M areIn [123]: Riem.display_comp() +y 4 +2 (x 2 +1)y 2 +2 x 2 +1Riem (g)x y y x = -4 x 4 +y 4 +2 (x 2 +1)y 2 +2 x 2 +1 +y 4 +2 (x 2 +1)y 2 +2 x 2 +1 +y 4 +2 (x 2 +1)y 2 +2 x 2 +1The parent of the Riemann tensor is the C ∞ (M )-module of type-(1,3) tensor fields on M : In [124]: print(Riem.parent()) Module T^(1,3)(M) of type-(1,3) tensors fields on the 2-dimensional differentiable manifold M The Riemann tensor is antisymmetric on its two last indices (i.e. the indices at position 2 and 3, the first index being at position 0): In [125]: Riem.symmetries() Out[125]: no symmetry; antisymmetry: (2, 3)

  If one would like to stick to the topological level, one should add the keyword argument structure='topological' to Manifold, i.e. Let us check that the actual class of M, i.e. DifferentiableManifold-with-category, is a subclass of DifferentiableManifold:

	In [6]: type(M)
	Out[6]: <class 'sage.manifolds.differentiable.manifold.
	DifferentiableManifold_with_category'>
	In [7]: isinstance(M,
	sage.manifolds.differentiable.manifold.DifferentiableManifold)
	Out[7]: True
	and hence of TopologicalManifold according to the inheritance diagram of Fig. 2.1:
	In [8]: isinstance(M, sage.manifolds.manifold.TopologicalManifold)
	Out[8]: True
	M = Manifold(2, 'M', structure='topological')
	Then M would have been a topological manifold without any further structure.
	Manifolds are implemented by the Python classes TopologicalManifold and
	DifferentiableManifold (see Fig. 2.1), actually by dynamically generated subclasses of those,
	via SageMath category framework: 7

Notice from Fig. 2.1 that TopologicalManifold itself is a subclass of ManifoldSubset (the class for generic subsets of a manifold), which reflects the fact that M ⊂ M . 2.2. Coordinate charts. Property 3 in the definition of a topological manifold (Sec. 2.1) means that one can label the points of M in a continuous way by n numbers (x α ) α∈{0,...,n-1} ∈ K n , which are called coordinates. More precisely, given an open subset U ⊂ M , a coordinate chart (or simply a chart) on U is a homeomorphism 8

  Smooth maps. Given two smooth manifolds, M and M , of respective dimensions n and n , we say that a map Φ : M → M is smooth map if and only if in some (and hence all, thanks to the smoothness of (2.3)) coordinate systems of M and M belonging to the smooth atlases of M and M , the coordinates of the image Φ(p) of any point p ∈ M are smooth functions K n → K n of the coordinates of p.

	2.4.
	[21]: p._coordinates
	Out[21]: (U, (x, y)) : (1, 2), (V, (x , y )) :	1 5	,	2 5

  Once the list of charts in which both operands have a coordinate expression has been found, the addition is performed at the chart function level (cf. Sec. 3.1), via the loop on the charts in lines 19-21 of the code for _add_(). The code for the addition of chart functions defined on the same chart is (recall that fU is the chart function representing f in chart XU):

	"""
	curr = self . _calc_method . _current
	res = self . _simplify ( self . expr () + other . expr ())
	if curr == ' SR ' and res . is _t r iv ia l_ z er o ():
	# NB : " if res == 0" would be too expensive ( cf . #22859)
	return self . parent (). zero ()
	else :
	return type ( self )( self . parent () , res )
	File : .../ local / lib / python2 .7/ site -packages / sage / manifolds / chart_func . py
	in known_expr1 :
	if chart1 not in resu :
	for chart2 in known_expr2 :
	if chart2 not in resu :
	if ( chart1 , chart2 ) in coord_changes :
	self . coo rd_funct ion ( chart2 , from_chart = chart1 )
	resu . append ( chart2 )
	if ( chart2 , chart1 ) in coord_changes :
	other . coord _function ( chart1 , from_chart = chart2 )
	resu . append ( chart1 )
	if resu == []:
	return None
	else :
	return resu
	In [59]: fU._add_??
	def _add_ ( self , other ):
	"""
	Addition operator .
	INPUT :
	-'' other ' ' --a : class : ' ChartFunction ' or a value
	OUTPUT :
	-chart function resulting from the addition of '' self ' '
	and '' other ' '
	...

File : .../ local / lib / python2 .7/ site -packages / sage / manifolds / scalarfield . py

  This chart is XU = (U, (x, y)), i.e. the chart of stereographic coordinates from the North pole. The rank of X(U ) as a free C ∞ (U )-module is the manifold's dimension: The class VectorFieldFreeModule is itself a subclass of the generic class Since U is a chart domain, the free module X(U ) is automatically endowed with a basis, which is the coordinate frame associated to the chart:

	Out[32]: True
	being the domain of a coordinate chart:
	In [33]: U.is_manifestly_coordinate_domain()
	Out[33]: True
	We can check that in U 's atlas, at least one chart has U for domain:
	In [34]: U.atlas()
	Out[34]: [(U, (x, y)) , (W, (x, y)) , (W, (x , y ))]
	In [35]: rank(YU)
	Out[35]: 2
	FiniteRankFreeModule:
	In [37]: class_graph(
		sage.manifolds.differentiable.vectorfield_module.VectorFieldFreeModule
		).plot()
	Out[37]: (see Figure 3.2)
	In [38]: YU.bases()
	Out[38]:	vector_field_module() isinstance(YU, FiniteRankFreeModule) U, ∂ ∂x ∂ , ∂y
	Out[30]: True Let us denote by eU this frame. We can set eU = YU.bases()[0] or alternatively
	In [31]: YU.base_ring() In [39]: eU = YU.default_basis()
		eU
	Out[31]: C ∞ (U ) Out[39]: U, ∂ ∂x	,	∂ ∂y
	This is because the open subset U is a parallelizable manifold: Another equivalent instruction would have been eU = U.default_frame().
	In [32]: U.is_manifestly_parallelizable()

Via the category framework, the free module X(U ) is implemented by a dynamically-generated subclass of the class VectorFieldFreeModule, which is devoted to modules of vector fields on parallelizable manifolds:

In [36]: type(YU)

Out[36]: <class 'sage.manifolds.differentiable.vectorfield_module.

VectorFieldFreeModule_with_category'>

  From line 26, we see that this method _add_() is implemented at the level of tensors on free modules, i.e. in the class FreeModuleTensor,23 from which VectorFieldParal inherits (cf. the diagram in Fig.3.3). Here the free module is clearly X(U ). The addition amounts to adding the components in a basis of the free module in which both operands have known components. Such a basis is returned by the method common_basis invoked in line 16. If necessary, this method can use change-of-basis formulas to compute the components of self or other in a common basis. The addition of the components in the found basis is performed in line 19. It involves the method __add__() of class Components; we can examine the corresponding code via the object vUc since the latter has been defined above as vUc = vU._components[eU], i.e. vUc represents the set of components of the vector field v| U in the basis eU = (∂/∂x, ∂/∂y) of X(U ):

		raise ValueError ( " no ␣ common ␣ basis ␣ for ␣ the ␣ addition " )
	comp_result = self . _components [ basis ] + other . _components [ basis ]
	result = self . _fmodule . t e n s o r _ f r o m _ c o m p ( self . _tensor_type , comp_result )
	if self . _name is not None and other . _name is not None :
		result . _name = self . _name + '+ ' + other . _name
	if self . _latex_name is not None and other . _latex_name is not None :
		result . _latex_name = self . _latex_name + '+ ' + other . _latex_name
	return result
	File :	.../ site -packages / sage / tensor / modules / f r e e _ m o d u l e _ t e n s o r . py
	In [77]: vUc.__add__??
	"""
	# No need for consistency check since self and other are guaranted
	# to belong to the same tensor module
	basis = self . common_basis ( other )
	if basis is None :

def __add__ ( self , other ):

  + y 6 + 3 x 2 + 1 y 4 + 3 x 4 + 3 x 4 + 2 x 2 + 1 y 2 + 3 x 2 + 1 + y 6 + 3 x 2 + 1 y 4 + 3 x 4 + 3 x 4 + 2 x 2 + 1 y 2 + 3 x 2 + 1

	+	  -	4 x x 6  5 + x 4 y + x 2 y 3 -x y 4 + x 3 -x y 2  ∂ ∂y	⊗ dx
	+	  -	4 x x 6  3 y 2 + x y 4 -y 5 -y 3 + x 4 + x 2 y  ∂ ∂y	⊗ dy
				
				 ∂ ∂x	⊗ dy

x 4 y -4 x y 4 -y 5 -4 x 3 + x y 2 x 6 + y 6 + 3 x 2 + 1 y 4 + 3 x 4 + 3 x 4 + 2 x 2 + 1 y 2 + 3 x 2 + 1

  ∇ g v = 4 y 3 + x 2 + 1 y -x x 4 + y 4 + 2 (x 2 + 1)y 2 + 2 x 2 + 1 + xy 2 + x + y x 4 + y 4 + 2 (x 2 + 1)y 2 + 2 x 2 + 1 + 2 xy 2 + 2 x + y x 4 + y 4 + 2 (x 2 + 1)y 2 + 2 x 2 + 1 + 2 x 2 + 1 y -x x 4 + y 4 + 2 (x 2 + 1)y 2 + 2 x 2 + 1 Curvature.The Riemann curvature tensor of the metric g is + y 4 + 2 (x 2 + 1)y 2 + 2 x 2 + 1 + y 4 + 2 (x 2 + 1)y 2 + 2 x 2 + 1 + y 4 + 2 (x 2 + 1)y 2 + 2 x 2 + 1 + y 4 + 2 (x 2 + 1)y 2 + 2 x 2 + 1

	Out[121]: ∂ ∂x	⊗ dx
	+ -		4 x 3 ∂ ∂x	⊗ dy
	+		2 2 x 3 ∂ ∂y	⊗ dx
	+		2 2 y 3 ∂ ∂y	⊗ dy
	4.3. In [122]: Riem = g.riemann()
	print(Riem)
	Riem.display()
	Tensor field Riem(g) of type (1,3) on the 2-dimensional differentiable
	manifold M
	Out[122]: Riem (g) =	4 x 4 ∂ ∂x	⊗ dy ⊗ dx ⊗ dy
	+ -	4 x 4 ∂ ∂x	⊗ dy ⊗ dy ⊗ dx
	+ -	4 x 4 ∂ ∂y	⊗ dx ⊗ dx ⊗ dy
	+	x 4	4
	Tensor field nabla_g(v) of type (1,1) on the 2-dimensional differentiable
	manifold M
	In [121]: Dv.display()

See https://en.wikipedia.org/wiki/Tensor_software for more examples.

http://www.sagemath.org

Let us recall that within an object-oriented programming language (as Python), a class is a structure to declare and store the properties common to a set of objects. These properties are data (called attributes or state variables) and functions acting on the data (called methods). A specific realization of an object within a given class is called an instance of that class.

See http://doc.sagemath.org/html/en/reference/categories/sage/categories/primer.html for a discussion of SageMath's category framework

In the language of topology, one says that M is a second-countable space.

Note however that the functionalities regarding complex manifolds are pretty limited at the moment. Volunteers are welcome to implement them! See https://sagemanifolds.obspm.fr/contrib.html.

See http://doc.sagemath.org/html/en/reference/categories/sage/categories/primer.html for details.

Let us recall that a homeomorphism between two topological spaces (here U and X(U )) is a bijective map X such that both X and X -1 are continuous.

A dictionary, also known as associative array, is a data structure that generalizes the concept of array in the sense that the key to access to an element is not restricted to an integer or a tuple of integers.

http://pynac.org

https://www.sympy.org

not to be confused with the attribute _express of class DiffScalarField presented at In[START_REF] Toth | Tensor manipulation in GPL Maxima[END_REF]

http://doc.sagemath.org/html/en/reference/manifolds/sage/manifolds/differentiable/scalarfield_algebra.html

In this transcript of code and in those that follow, some parts have been skipped, being not relevant for the discussion; they are marked by "...".

See https://github.com/sagemath/sage/blob/develop/src/sage/manifolds/utilities.py for details; note that the simplifications regarding the SymPy engine are not fully implemented yet.

http://doc.sagemath.org/html/en/reference/tensor_free_modules/sage/tensor/modules/finite_rank_free_module. html

http://doc.sagemath.org/html/en/reference/tensor_free_modules/sage/tensor/modules/comp.html

Einstein's convention for summation on repeated indices is assumed.

We are using YM to denote X(M ) and not XM, because we reserve the symbol X to denote coordinate charts, as XU, XV or XR3.

http://doc.sagemath.org/html/en/reference/manifolds/sage/manifolds/differentiable/vectorfield.html

http://doc.sagemath.org/html/en/reference/tensor_free_modules/sage/tensor/modules/free_module_tensor.html

This is done with the command Parallelism().set(nproc=8) (for 8 threads); many examples of parallelized computations are presented at https://sagemanifolds.obspm.fr/examples.html.

Cf. the meta-ticket https://trac.sagemath.org/ticket/18528.

Cf. the list at https://sagemanifolds.obspm.fr/authors.html.

CHAPTER 4

Tensor fields

We recover the fact that (S 2 , g) is a Riemannian manifold of constant positive curvature.

In dimension 2, the Riemann curvature tensor is entirely determined by the Ricci scalar R according to

Let us check this formula here, under the form

In

The exterior derivative of g is a 3-form:

In [132]: print(eps.exterior_derivative())

3-form deps_g on the 2-dimensional differentiable manifold M

Of course, since the dimension of M is 2, all 3-forms vanish identically:

In [133]: eps.exterior_derivative().display()

Conclusion and perspectives

We have presented some aspects of symbolic tensor calculus as implemented in SageMath. The implementation is independent of the symbolic backend (i.e. the tool used to performed symbolic calculus on coordinate representations of scalar fields), the latter being involved only in the last stage of the diagram shown in Fig. 3.4.

The implementation has been performed via the SageManifolds project, the home page of which we refer to for details and material complementary to what has been shown here (in particular many more examples): https://sagemanifolds.obspm.fr/ This project resulted in approximately 85, 000 lines of Python code (including comments and doctests), which have been submitted to SageMath community as a sequence of ∼ 50 tickets 25 at the time of this writing (October 2018), the first ticket having been accepted in March 2015. These tickets have been written and reviewed by a dozen of contributors. 26 As a result, all code is fully included in SageMath 8.4 and does not require any separate installation. The following features have been already implemented:

• differentiable manifolds: tangent spaces, vector frames, tensor fields, curves, pullback and pushforward operators; • standard tensor calculus (tensor product, contraction, symmetrization, etc.), even on non-parallelizable manifolds; • all monoterm tensor symmetries taken into account;

• Lie derivatives of tensor fields;

• differential forms: exterior and interior products, exterior derivative, Hodge duality; • multivector fields: exterior and interior products, Schouten-Nijenhuis bracket;

• affine connections (curvature, torsion); • pseudo-Riemannian metrics;

• computation of geodesics (numerical integration via SageMath/GSL); • some plotting capabilities (charts, points, curves, vector fields); • extrinsic geometry of pseudo-Riemannian submanifolds;

• parallelization (on tensor components) of CPU demanding computations, via the Python library multiprocessing; • the possibility to use SymPy as the symbolic backend, instead of SageMath's default, which is Pynac (with Maxima for simplifications). Only a subset of the above functionalities have been presented in these lectures. In particular, the exterior calculus on differential forms and multivector fields has not been touched, nor the computation of geodesics.

The SageManifolds project is still ongoing and future prospects include

• adding more symbolic backends (Giac, FriCAS, ...);

• computing integrals on submanifolds;

• adding more plotting capabilities;

• introducing new functionalities: symplectic forms, fibre bundles, spinors, variational calculus, etc.; • connecting with numerical relativity: using SageMath to explore numerically-generated spacetimes; this will be done by introducing numerical backends, instead of symbolic ones, in the last stage of the Fig. 3.4 diagram.

In the spirit of open-source software, anybody interested is very welcome to join the project. Please visit https://sagemanifolds.obspm.fr/contact.html