{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Smooth manifolds, charts and scalar fields\n", "\n", "This notebook accompanies the lecture\n", "[Symbolic tensor calculus on manifolds](http://sagemanifolds.obspm.fr/jncf2018/) at JNCF 2018.\n", "\n", "Click [here](https://raw.githubusercontent.com/sagemanifolds/SageManifolds/master/Worksheets/JNCF2018/jncf18_scalar.ipynb) to download the notebook file (ipynb format). To run it, you must start SageMath within the Jupyter Notebook server, via the command `sage -n jupyter`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*NB:* a version of SageMath at least equal to 7.5 (8.2 for the SymPy part) is required to run this notebook." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First we set up the notebook to use LaTeX display:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "%display latex" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Starting with a manifold\n", "\n", "Manifolds are constructed via the global function `Manifold`:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2-dimensional differentiable manifold M\n" ] } ], "source": [ "M = Manifold(2, 'M')\n", "print(M)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By default, `Manifold` returns a manifold over $\\mathbb{R}$:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "Real Field with 53 bits of precision" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M.base_field()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Other base fields must be specified with the optional keyword `field`, like\n", "```python\n", "M = Manifold(2, 'M', field='complex')\n", "```\n", "We may check that $M$ is a topological space:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "True" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M in Sets().Topological()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Actually, $M$ belongs to the following categories:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "[Category of smooth manifolds over Real Field with 53 bits of precision,\n", " Category of differentiable manifolds over Real Field with 53 bits of precision,\n", " Category of manifolds over Real Field with 53 bits of precision,\n", " Category of topological spaces,\n", " Category of sets,\n", " Category of sets with partial maps,\n", " Category of objects]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M.categories()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see, by default, `Manifold` constructs a smooth manifold.\n", "If one would like to stick to the topological level, one should write\n", "```python\n", "M = Manifold(2, 'M', structure='topological')\n", "```\n", "Smooth manifolds are implemented by the Python class `DifferentiableManifold`:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(M)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The type of `M` appears as `DifferentiableManifold-with-category` because it is actually a subclass of `DifferentiableManifold`, which is dynamically generated by SageMath's [category mechanism](http://doc.sagemath.org/html/en/reference/categories/sage/categories/primer.html):" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "True" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(M, \n", " sage.manifolds.differentiable.manifold.DifferentiableManifold)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The class `DifferentiableManifold` inherits from `TopologicalManifold`:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "True" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(M, sage.manifolds.manifold.TopologicalManifold)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Coordinate charts\n", "\n", "We declare a chart, along with the symbols used to denote the coordinates (here $x=x^0$ and $y=x^1$) by" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "Chart (U, (x, y))" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "U = M.open_subset('U')\n", "XU. = U.chart()\n", "XU" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Open subsets are implemented by a (dynamically generated) subclass of\n", "`DifferentiableManifold`, since they are manifolds in their own:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "True" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(U, \n", " sage.manifolds.differentiable.manifold.DifferentiableManifold)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Points on $M$ are created by passing their coordinates in a given chart:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Point p on the 2-dimensional differentiable manifold M\n" ] } ], "source": [ "p = U((1,2), chart=XU, name='p')\n", "print(p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The syntax `U(...)` used to create $p$ as an element of $U$ reflects the \n", "**parent/element pattern** employed in SageMath; indeed $U$ is the parent of $p$:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "Open subset U of the 2-dimensional differentiable manifold M" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p.parent()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Points are implemented by the class `ManifoldPoint`.\n", "The principal attribute of this class is a Python dictionary storing the point's coordinates\n", "in various charts:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "{Chart (U, (x, y)): (1, 2)}" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p._coordinates" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Of course, we can recover the point's coordinates by letting the chart act on the point:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "(1, 2)" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "XU(p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let us introduce a second chart on $M$:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "Chart (V, (xp, yp))" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "V = M.open_subset('V')\n", "XV. = V.chart(\"xp:x' yp:y'\")\n", "XV" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and declare that $M$ is covered by only two charts, i.e. that $M=U\\cup V$:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "M.declare_union(U,V)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "[Chart (U, (x, y)), Chart (V, (xp, yp))]" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "M.atlas()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Transition map\n", "\n", "We define the transition map `XU` $\\to$ `XV` on $W = U\\cap V$ as follows:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "xp = x/(x^2 + y^2)\n", "yp = y/(x^2 + y^2)" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "XU_to_XV = XU.transition_map(XV, \n", " (x/(x^2+y^2), y/(x^2+y^2)), \n", " intersection_name='W',\n", " restrictions1= x^2+y^2!=0, \n", " restrictions2= xp^2+yp^2!=0)\n", "XU_to_XV.display()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The value of the argument `restrictions1` means that\n", "$W = U\\setminus \\{S\\}$, where $S$ is the point of coordinates $(x,y)=(0,0)$,\n", "while the value of `restrictions2` means that\n", "$W = V\\setminus \\{N\\}$, where $N$ is the point of coordinates $(x',y')=(0,0)$.\n", "Since $M=U\\cup V$, we have then\n", "$$\n", " U = M \\setminus \\{N\\},\\qquad\n", " V = M \\setminus \\{S\\},\\quad\\mbox{and}\\quad\n", " W = M \\setminus \\{N, S\\} .\n", "$$\n", "\n", "The transition map `XV` $\\to$ `XU` is obtained by computing the inverse of the one defined above:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "x = xp/(xp^2 + yp^2)\n", "y = yp/(xp^2 + yp^2)" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "XU_to_XV.inverse().display()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "At this stage, the smooth manifold $M$ is fully specified, being covered by\n", "one atlas with all transition maps specified. One may have recognized that\n", "$M$ is nothing but the **2-dimensional sphere**:\n", "$$\n", " M = \\mathbb{S}^2 ,\n", "$$\n", "with `XU` (resp. `XV`) being\n", "the chart of \\defin{stereographic coordinates}\\index{stereographic!coordinates}\n", "from the North pole $N$ (resp. the South pole $S$).\n", "\n", "Since the transition maps have been defined,\n", "we can ask for the coordinates $(x',y')$ of the point $p$:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "(1/5, 2/5)" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "XV(p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This operation has updated the dictionary `_coordinates`:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "{Chart (V, (xp, yp)): (1/5, 2/5), Chart (U, (x, y)): (1, 2)}" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p._coordinates" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Smooth maps\n", "\n", "As a example of smooth map, let us consider the canonical embedding of $M=\\mathbb{S}^2$ \n", "into $\\mathbb{R}^3$. We need first to introduce $\\mathbb{R}^3$ as a 3-dimensional smooth\n", "manifold, endowed with a single chart:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "Chart (R^3, (X, Y, Z))" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "R3 = Manifold(3, 'R^3', r'\\mathbb{R}^3')\n", "XR3. = R3.chart()\n", "XR3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The embedding $\\Phi: \\mathbb{S}^2 \\to \\mathbb{R}^3$ is then defined in terms of its coordinate expression in the two charts covering $M=\\mathbb{S}^2$:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "Phi: M --> R^3\n", "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))\n", "on V: (xp, yp) |--> (X, Y, Z) = (2*xp/(xp^2 + yp^2 + 1), 2*yp/(xp^2 + yp^2 + 1), -(xp^2 + yp^2 - 1)/(xp^2 + yp^2 + 1))" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Phi = M.diff_map(R3, {(XU, XR3): \n", " [2*x/(1+x^2+y^2), 2*y/(1+x^2+y^2),\n", " (x^2+y^2-1)/(1+x^2+y^2)],\n", " (XV, XR3): \n", " [2*xp/(1+xp^2+yp^2), 2*yp/(1+xp^2+yp^2),\n", " (1-xp^2-yp^2)/(1+xp^2+yp^2)]},\n", " name='Phi', latex_name=r'\\Phi')\n", "Phi.display()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We may use $\\Phi$ 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:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n" ], "text/plain": [ "Graphics3d Object" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "graph = XU.plot(chart=XR3, mapping=Phi, number_values=25, \n", " label_axes=False) + \\\n", " XV.plot(chart=XR3, mapping=Phi, number_values=25, \n", " color='green', label_axes=False) + \\\n", " p.plot(chart=XR3, mapping=Phi, label_offset=0.05)\n", "show(graph, viewer='threejs', online=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Scalar fields\n", "\n", "Let us define a scalar field, i.e. a smooth map $M\\to \\mathbb{R}$:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "f: M --> R\n", "on U: (x, y) |--> 1/(x^2 + y^2 + 1)\n", "on V: (xp, yp) |--> (xp^2 + yp^2)/(xp^2 + yp^2 + 1)" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f = M.scalar_field({XU: 1/(1+x^2+y^2), XV: (xp^2+yp^2)/(1+xp^2+yp^2)},\n", " name='f')\n", "f.display()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scalar fields are implemented by the class `DiffScalarField`\n", "and the function chart representations are\n", "stored in the attribute `_express` of this class, which is a\n", "Python dictionary whose keys are the various charts defined on $M$:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "{Chart (V, (xp, yp)): (xp^2 + yp^2)/(xp^2 + yp^2 + 1),\n", " Chart (U, (x, y)): 1/(x^2 + y^2 + 1)}" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f._express" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One may wonder about the compatibility of the two coordinate expressions\n", "provided in the definition of $f$. Actually, to enforce the compatibility, it is possible to declare the scalar field in a single chart, `XU` say,\n", "and then to obtain its expression in chart `XV` by analytic continuation\n", "from the expression in $W=U\\cap V$, where both expressions are known, thanks\n", "to the transition map `XV` $\\to$ `XU`:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "True" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f0 = M.scalar_field({XU: 1/(1+x^2+y^2)})\n", "f0.add_expr_by_continuation(XV, U.intersection(V))\n", "f == f0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The representation of the scalar field in a given chart, i.e. the public access to the directory `_express`, is obtained via the method `coord_function()`:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "(x, y) |--> 1/(x^2 + y^2 + 1)" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fU = f.coord_function(XU)\n", "fU.display()" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "(xp, yp) |--> (xp^2 + yp^2)/(xp^2 + yp^2 + 1)" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fV = f.coord_function(XV)\n", "fV.display()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Both `fU` and `fV` are instances of the class `ChartFunction`:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "True" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(fU, sage.manifolds.chart_func.ChartFunction)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Mathematically, **chart functions** are real-valued functions on the codomain of the considered chart. They map coordinates to elements of the base field (here $\\mathbb{R}$):" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "1/6" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fU(1,2)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "1/6" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fU(*XU(p))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "while scalar fields maps manifold points to $\\mathbb{R}$:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "1/6" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f(p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Internally, each chart function stores coordinate expressions with respect to various computational engines:\n", "\n", "- SageMath symbolic engine, based on the [Pynac](http://pynac.org) backend, \n", " with Maxima used for some simplifications or computation of integrals;\n", "- [SymPy](http://www.sympy.org}) (Python library for symbolic mathematics);\n", "- in the future, more symbolic engines (e.g. Giac) or numerical ones will be implemented\n", "\n", "The coordinate expressions are stored in the dictionary `_express`,\n", "whose keys are strings identifying the computational engines. By default\n", "only SageMath symbolic expressions, i.e. expressions pertaining\n", "to the so-called Symbolic Ring (`SR`), are stored:" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "{'SR': 1/(x^2 + y^2 + 1)}" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fU._express" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The public access to the dictionary `_express` is performed via the\n", "method `expr()`:" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "1/(x^2 + y^2 + 1)" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fU.expr()" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(fU.expr())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Actually, `fU.expr()` is a shortcut for `fU.expr('SR')` since\n", "`SR` is the default symbolic engine. Note that the class\n", "`Expression` is that devoted to SageMath symbolic expressions.\n", "The method `expr()` can also be invoked to get the expression in\n", "another symbolic engine, for instance SymPy:" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "1/(x**2 + y**2 + 1)" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fU.expr('sympy')" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(fU.expr('sympy'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This operation has updated the dictionary `_express`:" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "{'SR': 1/(x^2 + y^2 + 1), 'sympy': 1/(x**2 + y**2 + 1)}" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fU._express" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The default calculus engine for chart functions of chart `XU` can\n", "changed thanks to the method `set_calculus_method()`:" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "1/(x**2 + y**2 + 1)" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "XU.set_calculus_method('sympy')\n", "fU.expr()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can have better (i.e. LaTeX) rendering of SymPy objects with\n", "```python\n", "from sympy import init_printing\n", "init_printing()\n", "```\n", "We don't use it here to make clear the distinction between `SR` objects and SymPy ones." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Reverting to SageMath's symbolic engine:" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "1/(x^2 + y^2 + 1)" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "XU.set_calculus_method('SR')\n", "fU.expr()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Symbolic expressions can be accessed directly from the scalar field,\n", "`f.expr(XU)` being a shortcut for `f.coord_function(XU).expr()`:" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "1/(x^2 + y^2 + 1)" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f.expr(XU)" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "(xp^2 + yp^2)/(xp^2 + yp^2 + 1)" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f.expr(XV)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Algebra of scalar fields\n", "\n", "The commutative algebra of scalar fields on $M$ is" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "Algebra of differentiable scalar fields on the 2-dimensional differentiable manifold M" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "CM = M.scalar_field_algebra()\n", "CM" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "Category of commutative algebras over Symbolic Ring" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "CM.category()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As for the manifold classes, the actual Python class implementing\n", "$C^\\infty(M)$ is inherited from `DiffScalarFieldAlgebra` via the\n", "[category mechanism](http://doc.sagemath.org/html/en/reference/categories/sage/categories/primer.html), hence it bares the name `DiffScalarFieldAlgebra-with-category`:" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(CM)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The class `DiffScalarFieldAlgebra-with-category` is dynamically generated as a subclass of `DiffScalarFieldAlgebra` with extra functionalities, like\n", "for instance the method `is_commutative()`:" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "True" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "CM.is_commutative()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let us have a look at the code of this method; it suffices to use the double question mark:" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [], "source": [ "CM.is_commutative??" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see from the `File` field that the code belongs to the category part of SageMath, not to the manifold part, where the class `DiffScalarFieldAlgebra` is defined.\n", "\n", "Regarding the scalar field $f$ introduced above, we have of course" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "True" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f in CM" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Actually, in Sage language, `CM`=$C^\\infty(M)$ is the parent of `f`:" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "True" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f.parent() is CM" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The zero element of the algebra $C^\\infty(M)$ is" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "zero: M --> R\n", "on U: (x, y) |--> 0\n", "on V: (xp, yp) |--> 0" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "CM.zero().display()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "while its unit element is" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "1: M --> R\n", "on U: (x, y) |--> 1\n", "on V: (xp, yp) |--> 1" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "CM.one().display()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Implementation of algebraic operations on scalar fields\n", "\n", "Let us consider some operation in the algebra $C^\\infty(M)$:" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "M --> R\n", "on U: (x, y) |--> (2*x^2 + 2*y^2 + 3)/(x^2 + y^2 + 1)\n", "on V: (xp, yp) |--> (3*xp^2 + 3*yp^2 + 2)/(xp^2 + yp^2 + 1)" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "h = f + 2*CM.one()\n", "h.display()" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "13/6" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "h(p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We are going to investigate how the above addition is performed. For the Python interpreter\n", "`h = f + 2*CM.one()` is equivalent to `h = f.__add__(2*CM.one())`,\n", "i.e. the `+` operator amounts to calling the method `__add__()` on `f`.\n", "To have a look at the source code of this method, we use the double question mark:" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [], "source": [ "f.__add__??" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see that the method `__add__()` is implemented at the level\n", "of the class `Element` from which `DiffScalarField` inherits, via\n", "`CommutativeAlgebraElement`.\n", "In the present case, `left` = `f` and `right` = `2*CM.one()`\n", "have the same parent, namely the algebra `CM`, so that the actual\n", "result is computed in the line\n", "```python\n", "return (left)._add_(right)\n", "```\n", "This invokes the method `_add_()`\n", "(note the single underscore on each side of `add`). This operator is\n", "implemented at the level of `ScalarField`, as it can be checked from the source code:" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [], "source": [ "f._add_??" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This reflects a [general strategy](http://doc.sagemath.org/html/en/thematic_tutorials/coercion_and_categories.html) in SageMath: the arithmetic Python operators `__add__`, `__sub__`, etc. are implemented at the\n", "top-level class `Element`, while the specific element subclasses,\n", "like `ScalarField` here, implement single-underscore methods `_add_`, `_sub_`, etc., which perform the actual computation\n", "when both operands have the same parent.\n", "\n", "Looking at the code, we notice that the first step is to search\n", "for the charts in which both operands of the addition operator have a coordinate\n", "expression. This is performed by the method `common_charts()`;\n", "in the current case, we get the two stereographic charts defined on $M$:" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "[Chart (U, (x, y)), Chart (V, (xp, yp))]" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f.common_charts(2*CM.one())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In general, `common_charts()` returns the charts for which both operands\n", "have already a known coordinate expression or for which a coordinate\n", "expression can be computed by a known transition map, as we can see on the source code:" ] }, { "cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [], "source": [ "f.common_charts??" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once the list of charts in which both operands have a coordinate expression,\n", "the addition is performed at the chart function level.\n", "The code for the addition of chart functions defined on the same chart\n", "is (recall that `fU` is the chart function representing $f$ in chart `XU`):" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [], "source": [ "fU._add_??" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We notice that the addition is performed on the symbolic expression\n", "with respect to the symbolic engine currently at work (SageMath/Pynac, SymPy,...), as returned by\n", "the method `expr()`.\n", "Let us recall that the user can change the symbolic engine at any time\n", "by means of the method `set_calculus_method()`, applied either to\n", "a chart or to an open subset (possibly `M` itself).\n", "Besides, we notice in the code above that the result of the symbolic addition\n", "is automatically simplified, by means of the method `_simplify`.\n", "It invokes a chain of simplification functions, which depends on the\n", "symbolic engine (See [here](https://github.com/sagemath/sage/blob/develop/src/sage/manifolds/utilities.py)\n", "for details)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let us now discuss the second case in the `__add__` method of\n", "`Element`, namely the case for which the parents of both operands are\n", "different. This case is treated via SageMath **coercion model**, which allows one to deal with additions like" ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "M --> R\n", "on U: (x, y) |--> (2*x^2 + 2*y^2 + 3)/(x^2 + y^2 + 1)\n", "on V: (xp, yp) |--> (3*xp^2 + 3*yp^2 + 2)/(xp^2 + yp^2 + 1)" ] }, "execution_count": 60, "metadata": {}, "output_type": "execute_result" } ], "source": [ "h1 = f + 2\n", "h1.display()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A priori, `f + 2` is not a well defined operation, since the integer $2$ does not\n", "belong to the algebra $C^\\infty(M)$. However SageMath manages to treat it\n", "because $2$ can be coerced (i.e. automatically and unambigously converted) via `CM(2)`\n", "into a element of $C^\\infty(M)$, namely the constant scalar field whose value is $2$:" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "M --> R\n", "on U: (x, y) |--> 2\n", "on V: (xp, yp) |--> 2" ] }, "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ "CM(2).display()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This happens because there exists a coercion map from the parent of $2$, namely the ring of integers $\\mathbb{Z}$\n", "(denoted `ZZ` in SageMath), to $C^\\infty(M)$:" ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "Integer Ring" ] }, "execution_count": 62, "metadata": {}, "output_type": "execute_result" } ], "source": [ "2.parent()" ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "True" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "CM.has_coerce_map_from(ZZ)" ] } ], "metadata": { "kernelspec": { "display_name": "SageMath 8.2.rc1", "language": "", "name": "sagemath" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.14" } }, "nbformat": 4, "nbformat_minor": 2 }