summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Gourgoulhon <eric.gourgoulhon@obspm.fr>2015-10-15 13:53:36 +0200
committerEric Gourgoulhon <eric.gourgoulhon@obspm.fr>2015-10-15 13:53:36 +0200
commit4de19a74c83ac6d4d0c4da74e1d1f2afce5c3045 (patch)
treed59ee40e264334e9f450b8d3becf6c117b34144b
parentUpdated Sage version to 6.9 (diff)
parentIntroduce open covers on top manifolds (diff)
Merge branch 'public/manifolds/top_manif_basics' of git://trac.sagemath.org/sage into sage 6.9
-rw-r--r--src/doc/en/reference/index.rst1
l---------src/doc/en/reference/manifolds/conf.py1
-rw-r--r--src/doc/en/reference/manifolds/index.rst18
-rw-r--r--src/doc/en/reference/manifolds/manifold.rst13
-rw-r--r--src/sage/all.py2
-rw-r--r--src/sage/manifolds/__init__.py1
-rw-r--r--src/sage/manifolds/all.py1
-rw-r--r--src/sage/manifolds/chart.py2028
-rw-r--r--src/sage/manifolds/manifold.py1332
-rw-r--r--src/sage/manifolds/point.py702
-rw-r--r--src/sage/manifolds/subset.py982
11 files changed, 5081 insertions, 0 deletions
diff --git a/src/doc/en/reference/index.rst b/src/doc/en/reference/index.rst
index 5972217..0eb98b5 100644
--- a/src/doc/en/reference/index.rst
+++ b/src/doc/en/reference/index.rst
@@ -85,6 +85,7 @@ Geometry and Topology
* :doc:`Combinatorial Geometry <geometry/index>`
* :doc:`Cell Complexes and their Homology <homology/index>`
* :doc:`Differential Forms <tensor/index>`
+* :doc:`Manifolds <manifolds/index>`
* :doc:`Parametrized Surfaces <riemannian_geometry/index>`
Number Theory, Algebraic Geometry
diff --git a/src/doc/en/reference/manifolds/conf.py b/src/doc/en/reference/manifolds/conf.py
new file mode 120000
index 0000000..2bdf7e6
--- /dev/null
+++ b/src/doc/en/reference/manifolds/conf.py
@@ -0,0 +1 @@
+../conf_sub.py \ No newline at end of file
diff --git a/src/doc/en/reference/manifolds/index.rst b/src/doc/en/reference/manifolds/index.rst
new file mode 100644
index 0000000..f0d5932
--- /dev/null
+++ b/src/doc/en/reference/manifolds/index.rst
@@ -0,0 +1,18 @@
+Manifolds
+=========
+
+This is the Sage implementation of manifolds resulting from the
+`SageManifolds project <http://sagemanifolds.obspm.fr/>`_.
+This section describes only the "manifold" part of SageManifolds;
+the pure algebraic part is described in the section
+:ref:`tensors-on-free-modules`.
+
+More documentation (in particular example worksheets) can be found
+`here <http://sagemanifolds.obspm.fr/documentation.html>`_.
+
+.. toctree::
+ :maxdepth: 2
+
+ manifold
+
+.. include:: ../footer.txt
diff --git a/src/doc/en/reference/manifolds/manifold.rst b/src/doc/en/reference/manifolds/manifold.rst
new file mode 100644
index 0000000..4180570
--- /dev/null
+++ b/src/doc/en/reference/manifolds/manifold.rst
@@ -0,0 +1,13 @@
+Topological manifolds
+=====================
+
+.. toctree::
+ :maxdepth: 2
+
+ sage/manifolds/manifold
+
+ sage/manifolds/point
+
+ sage/manifolds/subset
+
+ sage/manifolds/chart
diff --git a/src/sage/all.py b/src/sage/all.py
index 95e0c36..1cf10f4 100644
--- a/src/sage/all.py
+++ b/src/sage/all.py
@@ -172,6 +172,8 @@ from sage.matroids.all import *
from sage.game_theory.all import *
+from sage.manifolds.all import *
+
# Lazily import notebook functions and interacts (#15335)
lazy_import('sagenb.notebook.notebook_object', 'notebook')
lazy_import('sagenb.notebook.notebook_object', 'inotebook')
diff --git a/src/sage/manifolds/__init__.py b/src/sage/manifolds/__init__.py
new file mode 100644
index 0000000..932b798
--- /dev/null
+++ b/src/sage/manifolds/__init__.py
@@ -0,0 +1 @@
+# Empty file
diff --git a/src/sage/manifolds/all.py b/src/sage/manifolds/all.py
new file mode 100644
index 0000000..32e2be5
--- /dev/null
+++ b/src/sage/manifolds/all.py
@@ -0,0 +1 @@
+from manifold import TopManifold
diff --git a/src/sage/manifolds/chart.py b/src/sage/manifolds/chart.py
new file mode 100644
index 0000000..412e970
--- /dev/null
+++ b/src/sage/manifolds/chart.py
@@ -0,0 +1,2028 @@
+r"""
+Coordinate charts
+
+The class :class:`Chart` implements coordinate charts on a topological manifold
+over a topological field `K`. The subclass :class:`RealChart` is devoted
+to the case `K=\RR`, for which the concept of coordinate range is meaningful.
+
+Transition maps between charts are implemented via the class
+:class:`CoordChange`.
+
+AUTHORS:
+
+- Eric Gourgoulhon, Michal Bejger (2013-2015) : initial version
+
+REFERENCES:
+
+- Chap. 2 of J.M. Lee : *Introduction to Topological Manifolds*, 2nd ed.,
+ Springer (New York) (2011)
+
+- Chap. 1 of J.M. Lee : *Introduction to Smooth Manifolds*, 2nd ed., Springer
+ (New York) (2013)
+
+"""
+
+#*****************************************************************************
+# Copyright (C) 2015 Eric Gourgoulhon <eric.gourgoulhon@obspm.fr>
+# Copyright (C) 2015 Michal Bejger <bejger@camk.edu.pl>
+#
+# Distributed under the terms of the GNU General Public License (GPL)
+# as published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+# http://www.gnu.org/licenses/
+#*****************************************************************************
+
+from sage.structure.sage_object import SageObject
+from sage.structure.unique_representation import UniqueRepresentation
+from sage.symbolic.ring import SR
+from sage.rings.infinity import Infinity
+from sage.misc.latex import latex
+from sage.manifolds.manifold import TopManifold
+
+class Chart(UniqueRepresentation, SageObject):
+ r"""
+ Chart on a topological manifold.
+
+ Given a topological manifold `M` of dimension `n` over a topological field
+ `K`, a *chart* on `M` is a pair `(U,\varphi)`, where `U` is an open subset
+ of `M` and `\varphi: U \rightarrow V \subset K^n` is a homeomorphism from
+ `U` to an open subset `V` of `K^n`.
+
+ The components `(x^1,\ldots,x^n)` of `\varphi`, defined by
+ `\varphi(p) = (x^1(p),\ldots,x^n(p))\in K^n` for any point `p\in U`,
+ are called the *coordinates* of the chart `(U,\varphi)`.
+
+ INPUT:
+
+ - ``domain`` -- open subset `U` on which the chart is defined (must be
+ an instance of :class:`~sage.manifolds.manifold.TopManifold`)
+ - ``coordinates`` -- (default: '' (empty string)) single string defining
+ the coordinate symbols, with ' ' (whitespace) as a separator; each item
+ has at most two fields, separated by ':':
+
+ 1. The coordinate symbol (a letter or a few letters)
+ 2. (optional) The LaTeX spelling of the coordinate; if not provided the
+ coordinate symbol given in the first field will be used.
+
+ If it contains any LaTeX expression, the string ``coordinates`` must be
+ declared with the prefix 'r' (for "raw") to allow for a proper treatment
+ of LaTeX's backslash character (see examples below).
+ If no LaTeX spelling is to be set for any coordinate, the argument
+ ``coordinates`` can be omitted when the shortcut operator ``<,>`` is
+ used via Sage preparser (see examples below)
+ - ``names`` -- (default: ``None``) unused argument, except if
+ ``coordinates`` is not provided; it must then be a tuple containing
+ the coordinate symbols (this is guaranteed if the shortcut operator
+ ``<,>`` is used).
+
+ EXAMPLES:
+
+ A chart on a complex 2-dimensional topological manifold::
+
+ sage: M = TopManifold(2, 'M', field='complex')
+ sage: X = M.chart('x y'); X
+ Chart (M, (x, y))
+ sage: latex(X)
+ \left(M,(x, y)\right)
+ sage: type(X)
+ <class 'sage.manifolds.chart.Chart'>
+
+ To manipulate the coordinates `(x,y)` as global variables, one has to set::
+
+ sage: x,y = X[:]
+
+ However, a shortcut is to use the declarator ``<x,y>`` in the left-hand
+ side of the chart declaration (there is then no need to pass the string
+ ``'x y'`` to ``chart()``)::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M', field='complex')
+ sage: X.<x,y> = M.chart(); X
+ Chart (M, (x, y))
+
+ The coordinates are then immediately accessible::
+
+ sage: y
+ y
+ sage: x is X[0] and y is X[1]
+ True
+
+ The trick is performed by Sage preparser::
+
+ sage: preparse("X.<x,y> = M.chart()")
+ "X = M.chart(names=('x', 'y',)); (x, y,) = X._first_ngens(2)"
+
+ Note that ``x`` and ``y`` declared in ``<x,y>`` are mere Python variable
+ names and do not have to coincide with the coordinate symbols;
+ for instance, one may write::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M', field='complex')
+ sage: X.<x1,y1> = M.chart('x y'); X
+ Chart (M, (x, y))
+
+ Then ``y`` is not known as a global Python variable and the
+ coordinate `y` is accessible only through the global variable ``y1``::
+
+ sage: y1
+ y
+ sage: latex(y1)
+ y
+ sage: y1 is X[1]
+ True
+
+ However, having the name of the Python variable coincide with the
+ coordinate symbol is quite convenient; so it is recommended to declare::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M', field='complex')
+ sage: X.<x,y> = M.chart()
+
+ In the above example, the chart X covers entirely the manifold M::
+
+ sage: X.domain()
+ Complex 2-dimensional topological manifold M
+
+ Of course, one may declare a chart only on an open subset of M::
+
+ sage: U = M.open_subset('U')
+ sage: Y.<z1, z2> = U.chart(r'z1:\zeta_1 z2:\zeta_2'); Y
+ Chart (U, (z1, z2))
+ sage: Y.domain()
+ Open subset U of the Complex 2-dimensional topological manifold M
+
+ In the above declaration, we have also specified some LaTeX writing
+ of the coordinates different from the text one::
+
+ sage: latex(z1)
+ {\zeta_1}
+
+ Note the prefix ``r`` in front of the string ``r'z1:\zeta_1 z2:\zeta_2'``;
+ it makes sure that the backslash character is treated as an ordinary
+ character, to be passed to the LaTeX interpreter.
+
+ Coordinates are Sage symbolic variables (see
+ :mod:`sage.symbolic.expression`)::
+
+ sage: type(z1)
+ <type 'sage.symbolic.expression.Expression'>
+
+ In addition to the Python variable name provided in the operator ``<.,.>``,
+ the coordinates are accessible by their indices::
+
+ sage: Y[0], Y[1]
+ (z1, z2)
+
+ The index range is that declared during the creation of the manifold. By
+ default, it starts at 0, but this can be changed via the parameter
+ ``start_index``::
+
+ sage: M1 = TopManifold(2, 'M_1', field='complex', start_index=1)
+ sage: Z.<u,v> = M1.chart()
+ sage: Z[1], Z[2]
+ (u, v)
+
+ The full set of coordinates is obtained by means of the operator
+ ``[:]``::
+
+ sage: Y[:]
+ (z1, z2)
+
+ Each constructed chart is automatically added to the manifold's user
+ atlas::
+
+ sage: M.atlas()
+ [Chart (M, (x, y)), Chart (U, (z1, z2))]
+
+ and to the atlas of the chart's domain::
+
+ sage: U.atlas()
+ [Chart (U, (z1, z2))]
+
+ Manifold subsets have a *default chart*, which, unless changed via the
+ method
+ :meth:`~sage.manifolds.manifold.TopManifold.set_default_chart`,
+ is the first defined chart on the subset (or on a open subset of it)::
+
+ sage: M.default_chart()
+ Chart (M, (x, y))
+ sage: U.default_chart()
+ Chart (U, (z1, z2))
+
+ The default charts are not privileged charts on the manifold, but rather
+ charts whose name can be skipped in the argument list of functions having
+ an optional ``chart=`` argument.
+
+ The chart map `\varphi` acting on a point is obtained by means of the
+ call operator, i.e. the operator ``()``::
+
+ sage: p = M.point((1+i, 2), chart=X); p
+ Point on the Complex 2-dimensional topological manifold M
+ sage: X(p)
+ (I + 1, 2)
+ sage: X(p) == p.coord(X)
+ True
+
+ .. SEEALSO::
+
+ :class:`sage.manifolds.chart.RealChart` for charts on topological
+ manifolds over `\RR`.
+
+ """
+ def __init__(self, domain, coordinates='', names=None):
+ r"""
+ Construct a chart.
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M', field='complex')
+ sage: X.<x,y> = M.chart()
+ sage: X
+ Chart (M, (x, y))
+ sage: type(X)
+ <class 'sage.manifolds.chart.Chart'>
+ sage: assumptions() # no assumptions on x,y set by X._init_coordinates
+ []
+ sage: TestSuite(X).run()
+
+ """
+ if not isinstance(domain, TopManifold):
+ raise TypeError("the first argument must be an open subset of " +
+ "a topological manifold")
+ if coordinates == '':
+ for x in names:
+ coordinates += x + ' '
+ self._manifold = domain.manifold()
+ self._domain = domain
+ # Treatment of the coordinates:
+ if ' ' in coordinates:
+ coord_list = coordinates.split()
+ else:
+ coord_list = [coordinates]
+ if len(coord_list) != self._manifold.dim():
+ raise ValueError("the list of coordinates must contain " +
+ "{} elements".format(self._manifold.dim()))
+ # The treatment of coordinates is performed by a seperate method,
+ # _init_coordinates, which sets self._xx and
+ # which may be redefined for subclasses (for instance RealChart).
+ self._init_coordinates(coord_list)
+ #
+ # Additional restrictions on the coordinates
+ self._restrictions = [] # to be set with method add_restrictions()
+ #
+ # The chart is added to the domain's atlas, as well as to all the
+ # atlases of the domain's supersets; moreover the fist defined chart
+ # is considered as the default chart
+ for sd in self._domain._supersets:
+ # the chart is added in the top charts only if its coordinates have
+ # not been used:
+ for chart in sd._atlas:
+ if self._xx == chart._xx:
+ break
+ else:
+ sd._top_charts.append(self)
+ sd._atlas.append(self)
+ if sd._def_chart is None:
+ sd._def_chart = self
+ # The chart is added to the list of the domain's covering charts:
+ self._domain._covering_charts.append(self)
+ # Initialization of the set of charts that are restrictions of the
+ # current chart to subsets of the chart domain:
+ self._subcharts = set([self])
+ # Initialization of the set of charts which the current chart is a
+ # restriction of:
+ self._supercharts = set([self])
+ #
+ self._dom_restrict = {} # dict. of the restrictions of self to
+ # subsets of self._domain, with the
+ # subsets as keys
+
+ def _init_coordinates(self, coord_list):
+ r"""
+ Initialization of the coordinates as symbolic variables.
+
+ This method must be redefined by derived classes in order to take
+ into account specificities (e.g. enforcing real coordinates).
+
+ INPUT:
+
+ - ``coord_list`` -- list of coordinate fields, which items in each
+ field separated by ":"; there are at most 2 items per field:
+ the coordinate name and the coordinate LaTeX symbol
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M', field='complex')
+ sage: X.<z1, z2> = M.chart()
+ sage: X._init_coordinates(['z1', 'z2'])
+ sage: X
+ Chart (M, (z1, z2))
+ sage: X._init_coordinates([r'z1:\zeta_1', r'z2:\zeta_2'])
+ sage: X
+ Chart (M, (z1, z2))
+ sage: latex(X)
+ \left(M,({\zeta_1}, {\zeta_2})\right)
+
+ """
+ xx_list = [] # will contain the coordinates as Sage symbolic variables
+ for coord_field in coord_list:
+ coord_properties = coord_field.split(':')
+ coord_symb = coord_properties[0].strip() # the coordinate symbol
+ # LaTeX symbol:
+ coord_latex = None
+ for prop in coord_properties[1:]:
+ coord_latex = prop.strip()
+ # Construction of the coordinate as some Sage's symbolic variable:
+ coord_var = SR.var(coord_symb, latex_name=coord_latex)
+ xx_list.append(coord_var)
+ self._xx = tuple(xx_list)
+
+ def _repr_(self):
+ r"""
+ String representation of the object.
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M', field='complex')
+ sage: X.<x,y> = M.chart()
+ sage: X._repr_()
+ 'Chart (M, (x, y))'
+ sage: repr(X) # indirect doctest
+ 'Chart (M, (x, y))'
+ sage: X # indirect doctest
+ Chart (M, (x, y))
+
+ """
+ return 'Chart (' + self._domain._name + ', ' + str(self._xx) + ')'
+
+ def _latex_(self):
+ r"""
+ LaTeX representation of the object.
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M', field='complex')
+ sage: X.<x,y> = M.chart()
+ sage: X._latex_()
+ '\\left(M,(x, y)\\right)'
+ sage: Y.<z1, z2> = M.chart(r'z1:\zeta_1 z2:\zeta2')
+ sage: Y._latex_()
+ '\\left(M,({\\zeta_1}, {\\zeta2})\\right)'
+ sage: latex(Y) # indirect doctest
+ \left(M,({\zeta_1}, {\zeta2})\right)
+
+ """
+ description = r'\left(' + latex(self._domain).strip() + ',('
+ n = len(self._xx)
+ for i in range(n-1):
+ description += latex(self._xx[i]).strip() + ', '
+ description += latex(self._xx[n-1]).strip() + r')\right)'
+ return description
+
+ def _first_ngens(self, n):
+ r"""
+ Return the list of coordinates.
+
+ This is useful only for the use of Sage preparser::
+
+ sage: preparse("c_cart.<x,y,z> = M.chart()")
+ "c_cart = M.chart(names=('x', 'y', 'z',)); (x, y, z,) = c_cart._first_ngens(3)"
+
+ """
+ return self[:]
+
+ def __getitem__(self, i):
+ r"""
+ Access to the coordinates.
+
+ INPUT:
+
+ - ``i`` -- index of the coordinate; if [:] all the coordinates
+ are returned
+
+ OUTPUT:
+
+ - the coordinate of index ``i`` or all the coordinates (as a tuple) if
+ ``i`` is [:]
+
+ EXAMPLES::
+
+ sage: M = TopManifold(2, 'M', field='complex')
+ sage: X.<x,y> = M.chart()
+ sage: X[0]
+ x
+ sage: X[1]
+ y
+ sage: X[:]
+ (x, y)
+
+ The index range is controlled by the parameter ``start_index``::
+
+ sage: M = TopManifold(2, 'M', field='complex', start_index=1)
+ sage: X.<x,y> = M.chart()
+ sage: X[1]
+ x
+ sage: X[2]
+ y
+ sage: X[:]
+ (x, y)
+
+ """
+ if isinstance(i, slice):
+ return self._xx
+ else:
+ return self._xx[i-self._manifold._sindex]
+
+ def __call__(self, point):
+ r"""
+ Return the coordinates of a given point.
+
+ INPUT:
+
+ - ``point`` -- point in the domain of the chart
+
+ OUTPUT:
+
+ - tuple of the coordinates of the point
+
+ EXAMPLES::
+
+ sage: M = TopManifold(2, 'M', field='complex')
+ sage: X.<x,y> = M.chart()
+ sage: p = M.point((1+i, 2-i), chart=X)
+ sage: X(p)
+ (I + 1, -I + 2)
+ sage: X(M.an_element())
+ (0, 0)
+
+ """
+ return point.coord(self)
+
+ def domain(self):
+ r"""
+ Return the open subset on which the chart is defined.
+
+ EXAMPLES::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: X.domain()
+ 2-dimensional topological manifold M
+ sage: U = M.open_subset('U')
+ sage: Y.<u,v> = U.chart()
+ sage: Y.domain()
+ Open subset U of the 2-dimensional topological manifold M
+
+ """
+ return self._domain
+
+ def manifold(self):
+ r"""
+ Return the manifold on which the chart is defined.
+
+ EXAMPLES::
+
+ sage: M = TopManifold(2, 'M')
+ sage: U = M.open_subset('U')
+ sage: X.<x,y> = U.chart()
+ sage: X.manifold()
+ 2-dimensional topological manifold M
+ sage: X.domain()
+ Open subset U of the 2-dimensional topological manifold M
+
+ """
+ return self._manifold
+
+ def add_restrictions(self, restrictions):
+ r"""
+ Add some restrictions on the coordinates.
+
+ INPUT:
+
+ - ``restrictions`` -- list of restrictions on the
+ coordinates, in addition to the ranges declared by the intervals
+ specified in the chart constructor.
+ A restriction can be any symbolic equality or inequality involving the
+ coordinates, such as x>y or x^2+y^2 != 0. The items of the list
+ ``restrictions`` are combined with the ``and`` operator; if some
+ restrictions are to be combined with the ``or`` operator instead, they
+ have to be passed as a tuple in some single item of the list
+ ``restrictions``. For example, ``restrictions`` = [x>y, (x!=0, y!=0),
+ z^2<x] means (x>y) and ((x!=0) or (y!=0)) and (z^2<x). If the list
+ ``restrictions`` contains only one item, this item can be passed as
+ such, i.e. writing x>y instead of the single element list [x>y].
+
+ EXAMPLES::
+
+ sage: M = TopManifold(2, 'M', field='complex')
+ sage: X.<x,y> = M.chart()
+ sage: X.add_restrictions(abs(x) > 1)
+ sage: X.valid_coordinates(2+i, 1)
+ True
+ sage: X.valid_coordinates(i, 1)
+ False
+
+ """
+ if not isinstance(restrictions, list):
+ # case of a single condition or conditions to be combined by "or"
+ restrictions = [restrictions]
+ self._restrictions.extend(restrictions)
+
+ def restrict(self, subset, restrictions=None):
+ r"""
+ Return the restriction of the chart to some open subset of its domain.
+
+ If the current chart is `(U,\varphi)`, a *restriction* (or *subchart*)
+ is a chart `(V,\psi)` such that `V\subset U` and `\psi = \varphi |_V`.
+
+ If such subchart has not been defined yet, it is constructed here.
+
+ The coordinates of the subchart bare the same names as the coordinates
+ of the current chart.
+
+ INPUT:
+
+ - ``subset`` -- open subset `V` of the chart domain `U` (must be an
+ instance of :class:`~sage.manifolds.manifold.TopManifold`)
+ - ``restrictions`` -- (default: ``None``) list of coordinate
+ restrictions defining the subset `V`.
+ A restriction can be any symbolic equality or
+ inequality involving the coordinates, such as x>y or x^2+y^2 != 0.
+ The items of the list ``restrictions`` are combined with the ``and``
+ operator; if some restrictions are to be combined with the ``or``
+ operator instead, they have to be passed as a tuple in some single
+ item of the list ``restrictions``. For example, ``restrictions``
+ being [x>y, (x!=0, y!=0), z^2<x] means (x>y) and ((x!=0) or (y!=0))
+ and (z^2<x). If the list ``restrictions`` contains only one item,
+ this item can be passed as such, i.e. writing x>y instead of the
+ single element list [x>y]. Note that the argument ``restrictions``
+ can be omitted if the subchart has been already initialized by a
+ previous call.
+
+ OUTPUT:
+
+ - chart `(V,\psi)`, as an instance of :class:`Chart`.
+
+ EXAMPLES:
+
+ Coordinates on the unit open ball of `\CC^2` as a subchart
+ of the global coordinates of `\CC^2`::
+
+ sage: M = TopManifold(2, 'C^2', field='complex')
+ sage: X.<z1, z2> = M.chart()
+ sage: B = M.open_subset('B')
+ sage: X_B = X.restrict(B, abs(z1)^2 + abs(z2)^2 < 1); X_B
+ Chart (B, (z1, z2))
+
+ """
+ if subset == self._domain:
+ return self
+ if subset not in self._dom_restrict:
+ if not subset.is_subset(self._domain):
+ raise ValueError("the specified subset is not a subset " +
+ "of the domain of definition of the chart")
+ coordinates = ""
+ for coord in self._xx:
+ coordinates += repr(coord) + ' '
+ res = self.__class__(subset, coordinates)
+ res._restrictions.extend(self._restrictions)
+ # The coordinate restrictions are added to the result chart and
+ # possibly transformed into coordinate bounds:
+ if restrictions is not None:
+ res.add_restrictions(restrictions)
+ # Update of supercharts and subcharts:
+ res._supercharts.update(self._supercharts)
+ for schart in self._supercharts:
+ schart._subcharts.add(res)
+ schart._dom_restrict[subset] = res
+ # Update of domain restrictions:
+ self._dom_restrict[subset] = res
+ return self._dom_restrict[subset]
+
+ def valid_coordinates(self, *coordinates, **kwds):
+ r"""
+ Check whether a tuple of coordinates can be the coordinates of a
+ point in the chart domain.
+
+ INPUT:
+
+ - ``*coordinates`` -- coordinate values
+ - ``**kwds`` -- options:
+
+ - ``parameters=None``, dictionary to set numerical values to
+ some parameters (see example below)
+
+ OUTPUT:
+
+ - ``True`` if the coordinate values are admissible in the chart image,
+ ``False`` otherwise.
+
+ EXAMPLE::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M', field='complex')
+ sage: X.<x,y> = M.chart()
+ sage: X.add_restrictions([abs(x)<1, y!=0])
+ sage: X.valid_coordinates(0, i)
+ True
+ sage: X.valid_coordinates(i, 1)
+ False
+ sage: X.valid_coordinates(i/2, 1)
+ True
+ sage: X.valid_coordinates(i/2, 0)
+ False
+ sage: X.valid_coordinates(2, 0)
+ False
+
+ Example of use with the keyword ``parameters`` to set a specific value
+ to a parameter appearing in the coordinate restrictions::
+
+ sage: var('a') # the parameter is a symbolic variable
+ a
+ sage: Y.<u,v> = M.chart()
+ sage: Y.add_restrictions(abs(v)<a)
+ sage: Y.valid_coordinates(1, i, parameters={a: 2}) # setting a=2
+ True
+ sage: Y.valid_coordinates(1, 2*i, parameters={a: 2})
+ False
+
+ """
+ if len(coordinates) != self._domain._dim:
+ return False
+ if 'parameters' in kwds:
+ parameters = kwds['parameters']
+ else:
+ parameters = None
+ # Check of restrictions:
+ if self._restrictions != []:
+ substitutions = dict(zip(self._xx, coordinates))
+ if parameters:
+ substitutions.update(parameters)
+ for restrict in self._restrictions:
+ if isinstance(restrict, tuple): # case of or conditions
+ combine = False
+ for expr in restrict:
+ combine = combine or bool(expr.subs(substitutions))
+ if not combine:
+ return False
+ else:
+ if not bool(restrict.subs(substitutions)):
+ return False
+ # All tests have been passed:
+ return True
+
+ def transition_map(self, other, transformations, intersection_name=None,
+ restrictions1=None, restrictions2=None):
+ r"""
+ Construct the transition map between the current chart,
+ `(U,\varphi)` say, and another one, `(V,\psi)` say.
+
+ If `n` is the manifold's dimension, the *transition map* is the
+ map
+
+ .. MATH::
+
+ \psi\circ\varphi^{-1}: \varphi(U\cap V) \subset K^n
+ \rightarrow \psi(U\cap V) \subset K^n,
+
+ where `K` is the manifold's base field. In other words, the
+ transition map expresses the coordinates `(y^1,\ldots,y^n)` of
+ `(V,\psi)` in terms of the coordinates `(x^1,\ldots,x^n)` of
+ `(U,\varphi)` on the open subset where the two charts intersect, i.e.
+ on `U\cap V`.
+
+ INPUT:
+
+ - ``other`` -- the chart `(V,\psi)`
+ - ``transformations`` -- tuple (or list) `(Y_1,\ldots,Y_2)`, where
+ `Y_i` is the symbolic expression of the coordinate `y^i` in terms
+ of the coordinates `(x^1,\ldots,x^n)`
+ - ``intersection_name`` -- (default: ``None``) name to be given to the
+ subset `U\cap V` if the latter differs from `U` or `V`
+ - ``restrictions1`` -- (default: ``None``) list of conditions on the
+ coordinates of the current chart that define `U\cap V` if the
+ latter differs from `U`. ``restrictions1`` must be a list of
+ of symbolic equalities or inequalities involving the
+ coordinates, such as x>y or x^2+y^2 != 0. The items of the list
+ ``restrictions1`` are combined with the ``and`` operator; if some
+ restrictions are to be combined with the ``or`` operator instead,
+ they have to be passed as a tuple in some single item of the list
+ ``restrictions1``. For example, ``restrictions1`` = [x>y,
+ (x!=0, y!=0), z^2<x] means (x>y) and ((x!=0) or (y!=0)) and (z^2<x).
+ If the list ``restrictions1`` contains only one item, this item can
+ be passed as such, i.e. writing x>y instead of the single-element
+ list [x>y].
+ - ``restrictions2`` -- (default: ``None``) list of conditions on the
+ coordinates of the chart `(V,\psi)` that define `U\cap V` if the
+ latter differs from `V` (see ``restrictions1`` for the syntax)
+
+ OUTPUT:
+
+ - The transition map `\psi\circ\varphi^{-1}` defined on `U\cap V`, as an
+ instance of :class:`CoordChange`.
+
+ EXAMPLES:
+
+ Transition map between two stereographic charts on the circle `S^1`::
+
+ sage: M = TopManifold(1, 'S^1')
+ sage: U = M.open_subset('U') # Complement of the North pole
+ sage: cU.<x> = U.chart() # Stereographic chart from the North pole
+ sage: V = M.open_subset('V') # Complement of the South pole
+ sage: cV.<y> = V.chart() # Stereographic chart from the South pole
+ sage: M.declare_union(U,V) # S^1 is the union of U and V
+ sage: trans = cU.transition_map(cV, 1/x, intersection_name='W',
+ ....: restrictions1= x!=0, restrictions2 = y!=0)
+ sage: trans
+ Change of coordinates from Chart (W, (x,)) to Chart (W, (y,))
+ sage: trans.display()
+ y = 1/x
+
+ The subset `W`, intersection of `U` and `V`, has been created by
+ ``transition_map()``::
+
+ sage: M.list_of_subsets()
+ [1-dimensional topological manifold S^1,
+ Open subset U of the 1-dimensional topological manifold S^1,
+ Open subset V of the 1-dimensional topological manifold S^1,
+ Open subset W of the 1-dimensional topological manifold S^1]
+ sage: W = M.list_of_subsets()[3]
+ sage: W is U.intersection(V)
+ True
+ sage: M.atlas()
+ [Chart (U, (x,)), Chart (V, (y,)), Chart (W, (x,)), Chart (W, (y,))]
+
+ Transition map between the spherical chart and the Cartesian one on
+ `\RR^2`::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'R^2')
+ sage: c_cart.<x,y> = M.chart()
+ sage: U = M.open_subset('U') # the complement of the half line {y=0, x >= 0}
+ sage: c_spher.<r,phi> = U.chart(r'r:(0,+oo) phi:(0,2*pi):\phi')
+ sage: trans = c_spher.transition_map(c_cart, (r*cos(phi), r*sin(phi)),
+ ....: restrictions2=(y!=0, x<0))
+ sage: trans
+ Change of coordinates from Chart (U, (r, phi)) to Chart (U, (x, y))
+ sage: trans.display()
+ x = r*cos(phi)
+ y = r*sin(phi)
+
+ In this case, no new subset has been created since `U\cap M = U`::
+
+ sage: M.list_of_subsets()
+ [2-dimensional topological manifold R^2,
+ Open subset U of the 2-dimensional topological manifold R^2]
+
+ ... but a new chart has been created: `(U, (x, y))`::
+
+ sage: M.atlas()
+ [Chart (R^2, (x, y)), Chart (U, (r, phi)), Chart (U, (x, y))]
+
+ """
+ dom1 = self._domain
+ dom2 = other._domain
+ dom = dom1.intersection(dom2, name=intersection_name)
+ if dom is dom1:
+ chart1 = self
+ else:
+ chart1 = self.restrict(dom, restrictions1)
+ if dom is dom2:
+ chart2 = other
+ else:
+ chart2 = other.restrict(dom, restrictions2)
+ if not isinstance(transformations, (tuple, list)):
+ transformations = [transformations]
+ return CoordChange(chart1, chart2, *transformations)
+
+#*****************************************************************************
+
+class RealChart(Chart):
+ r"""
+ Chart on a topological manifold over `\RR`.
+
+ Given a topological manifold `M` of dimension `n` over `\RR`, a *chart*
+ on `M` is a pair `(U,\varphi)`, where `U` is an open subset of `M` and
+ `\varphi: U \rightarrow V \subset \RR^n` is a homeomorphism from `U` to
+ an open subset `V` of `\RR^n`.
+
+ The components `(x^1,\ldots,x^n)` of `\varphi`, defined by
+ `\varphi(p) = (x^1(p),\ldots,x^n(p))\in \RR^n` for any point `p\in U`,
+ are called the *coordinates* of the chart `(U,\varphi)`.
+
+ INPUT:
+
+ - ``domain`` -- open subset `U` on which the chart is defined
+ - ``coordinates`` -- (default: '' (empty string)) single string defining
+ the coordinate symbols and ranges, with ' ' (whitespace) as a separator;
+ each item has at most three fields, separated by ':':
+
+ 1. The coordinate symbol (a letter or a few letters)
+ 2. (optional) The interval `I` defining the coordinate range: if not
+ provided, the coordinate is assumed to span all `\RR`; otherwise
+ `I` must be provided in the form ``(a,b)`` (or equivalently
+ ``]a,b[``). The bounds ``a`` and ``b`` can be ``+/-Infinity``,
+ ``Inf``, ``infinity``, ``inf`` or ``oo``.
+ For *singular* coordinates, non-open intervals such as ``[a,b]`` and
+ ``(a,b]`` (or equivalently ``]a,b]``) are allowed.
+ Note that the interval declaration must not contain any whitespace.
+ 3. (optional) The LaTeX spelling of the coordinate; if not provided the
+ coordinate symbol given in the first field will be used.
+
+ The order of the fields 2 and 3 does not matter and each of them can be
+ omitted.
+ If it contains any LaTeX expression, the string ``coordinates`` must be
+ declared with the prefix 'r' (for "raw") to allow for a proper treatment
+ of LaTeX backslash characters (see examples below).
+ If no interval range and no LaTeX spelling is to be set for any
+ coordinate, the argument ``coordinates`` can be omitted when the
+ shortcut operator ``<,>`` is used via Sage preparser (see examples below)
+ - ``names`` -- (default: ``None``) unused argument, except if
+ ``coordinates`` is not provided; it must then be a tuple containing
+ the coordinate symbols (this is guaranteed if the shortcut operator
+ ``<,>`` is used).
+
+ EXAMPLES:
+
+ Cartesian coordinates on `\RR^3`::
+
+ sage: M = TopManifold(3, 'R^3', r'\RR^3', start_index=1)
+ sage: c_cart = M.chart('x y z'); c_cart
+ Chart (R^3, (x, y, z))
+ sage: type(c_cart)
+ <class 'sage.manifolds.chart.RealChart'>
+
+ To have the coordinates accessible as global variables, one has to set::
+
+ sage: (x,y,z) = c_cart[:]
+
+ However, a shortcut is to use the declarator ``<x,y,z>`` in the left-hand
+ side of the chart declaration (there is then no need to pass the string
+ ``'x y z'`` to ``chart()``)::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(3, 'R^3', r'\RR^3', start_index=1)
+ sage: c_cart.<x,y,z> = M.chart(); c_cart
+ Chart (R^3, (x, y, z))
+
+ The coordinates are then immediately accessible::
+
+ sage: y
+ y
+ sage: y is c_cart[2]
+ True
+
+ The trick is performed by Sage preparser::
+
+ sage: preparse("c_cart.<x,y,z> = M.chart()")
+ "c_cart = M.chart(names=('x', 'y', 'z',)); (x, y, z,) = c_cart._first_ngens(3)"
+
+ Note that ``x, y, z`` declared in ``<x,y,z>`` are mere Python variable
+ names and do not have to coincide with the coordinate symbols; for instance,
+ one may write::
+
+ sage: M = TopManifold(3, 'R^3', r'\RR^3', start_index=1)
+ sage: c_cart.<x1,y1,z1> = M.chart('x y z'); c_cart
+ Chart (R^3, (x, y, z))
+
+ Then ``y`` is not known as a global variable and the coordinate `y`
+ is accessible only through the global variable ``y1``::
+
+ sage: y1
+ y
+ sage: y1 is c_cart[2]
+ True
+
+ However, having the name of the Python variable coincide with the
+ coordinate symbol is quite convenient; so it is recommended to declare::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: forget() # for doctests only
+ sage: M = TopManifold(3, 'R^3', r'\RR^3', start_index=1)
+ sage: c_cart.<x,y,z> = M.chart()
+
+ Spherical coordinates on the subset `U` of `\RR^3` that is the
+ complement of the half-plane `\{y=0, x\geq 0\}`::
+
+ sage: U = M.open_subset('U')
+ sage: c_spher.<r,th,ph> = U.chart(r'r:(0,+oo) th:(0,pi):\theta ph:(0,2*pi):\phi')
+ sage: c_spher
+ Chart (U, (r, th, ph))
+
+ Note the prefix 'r' for the string defining the coordinates in the
+ arguments of ``chart``.
+
+ Coordinates are Sage symbolic variables (see
+ :mod:`sage.symbolic.expression`)::
+
+ sage: type(th)
+ <type 'sage.symbolic.expression.Expression'>
+ sage: latex(th)
+ {\theta}
+ sage: assumptions(th)
+ [th is real, th > 0, th < pi]
+
+ Coordinate are also accessible by their indices::
+
+ sage: x1 = c_spher[1]; x2 = c_spher[2]; x3 = c_spher[3]
+ sage: print x1, x2, x3
+ r th ph
+ sage: (x1, x2, x3) == (r, th, ph)
+ True
+
+ The full set of coordinates is obtained by means of the operator [:]::
+
+ sage: c_cart[:]
+ (x, y, z)
+ sage: c_spher[:]
+ (r, th, ph)
+
+ Let us check that the declared coordinate ranges have been taken into
+ account::
+
+ sage: c_cart.coord_range()
+ x: (-oo, +oo); y: (-oo, +oo); z: (-oo, +oo)
+ sage: c_spher.coord_range()
+ r: (0, +oo); th: (0, pi); ph: (0, 2*pi)
+ sage: bool(th>0 and th<pi)
+ True
+ sage: assumptions() # list all current symbolic assumptions
+ [x is real, y is real, z is real, r is real, r > 0, th is real,
+ th > 0, th < pi, ph is real, ph > 0, ph < 2*pi]
+
+ The coordinate ranges are used for simplifications::
+
+ sage: simplify(abs(r)) # r has been declared to lie in the interval (0,+oo)
+ r
+ sage: simplify(abs(x)) # no positive range has been declared for x
+ abs(x)
+
+ Each constructed chart is automatically added to the manifold's user atlas::
+
+ sage: M.atlas()
+ [Chart (R^3, (x, y, z)), Chart (U, (r, th, ph))]
+
+ and to the atlas of its domain::
+
+ sage: U.atlas()
+ [Chart (U, (r, th, ph))]
+
+
+ Manifold subsets have a *default chart*, which, unless changed via the
+ method
+ :meth:`~sage.manifolds.manifold.TopManifold.set_default_chart`,
+ is the first defined chart on the subset (or on a open subset of it)::
+
+ sage: M.default_chart()
+ Chart (R^3, (x, y, z))
+ sage: U.default_chart()
+ Chart (U, (r, th, ph))
+
+ The default charts are not privileged charts on the manifold, but rather
+ charts whose name can be skipped in the argument list of functions having
+ an optional ``chart=`` argument.
+
+ The chart map `\varphi` acting on a point is obtained by means of the
+ call operator, i.e. the operator ``()``::
+
+ sage: p = M.point((1,0,-2)); p
+ Point on the 3-dimensional topological manifold R^3
+ sage: c_cart(p)
+ (1, 0, -2)
+ sage: c_cart(p) == p.coord(c_cart)
+ True
+ sage: q = M.point((2,pi/2,pi/3), chart=c_spher) # point defined by its spherical coordinates
+ sage: c_spher(q)
+ (2, 1/2*pi, 1/3*pi)
+ sage: c_spher(q) == q.coord(c_spher)
+ True
+ sage: a = U.point((1,pi/2,pi)) # the default coordinates on U are the spherical ones
+ sage: c_spher(a)
+ (1, 1/2*pi, pi)
+ sage: c_spher(a) == a.coord(c_spher)
+ True
+
+ Cartesian coordinates on `U` as an example of chart construction with
+ coordinate restrictions: since `U` is the complement of the half-plane
+ `\{y=0, x\geq 0\}`, we must have `y\not=0` or `x<0` on U. Accordingly,
+ we set::
+
+ sage: c_cartU.<x,y,z> = U.chart()
+ sage: c_cartU.add_restrictions((y!=0, x<0)) # the tuple (y!=0, x<0) means y!=0 or x<0
+ sage: # c_cartU.add_restrictions([y!=0, x<0]) would have meant y!=0 AND x<0
+ sage: U.atlas()
+ [Chart (U, (r, th, ph)), Chart (U, (x, y, z))]
+ sage: M.atlas()
+ [Chart (R^3, (x, y, z)), Chart (U, (r, th, ph)), Chart (U, (x, y, z))]
+ sage: c_cartU.valid_coordinates(-1,0,2)
+ True
+ sage: c_cartU.valid_coordinates(1,0,2)
+ False
+ sage: c_cart.valid_coordinates(1,0,2)
+ True
+
+ """
+ def __init__(self, domain, coordinates='', names=None):
+ r"""
+ Construct a chart on a real topological manifold.
+
+ TESTS::
+
+ sage: forget() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: X
+ Chart (M, (x, y))
+ sage: type(X)
+ <class 'sage.manifolds.chart.RealChart'>
+ sage: assumptions() # assumptions set in X._init_coordinates
+ [x is real, y is real]
+ sage: TestSuite(X).run()
+
+ """
+ Chart.__init__(self, domain, coordinates=coordinates, names=names)
+
+ def _init_coordinates(self, coord_list):
+ r"""
+ Initialization of the coordinates as symbolic variables.
+
+ This method must be redefined by derived classes in order to take
+ into account specificities (e.g. enforcing real coordinates).
+
+ INPUT:
+
+ - ``coord_list`` -- list of coordinate fields, which items in each
+ field separated by ":"; there are at most 3 items per field:
+ the coordinate name, the coordinate LaTeX symbol and the coordinate
+ range
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: X._init_coordinates(['x', 'y'])
+ sage: X
+ Chart (M, (x, y))
+ sage: latex(X)
+ \left(M,(x, y)\right)
+ sage: X.coord_range()
+ x: (-oo, +oo); y: (-oo, +oo)
+ sage: X._init_coordinates([r'x1:\xi:(0,1)', r'y1:\eta'])
+ sage: X
+ Chart (M, (x1, y1))
+ sage: latex(X)
+ \left(M,({\xi}, {\eta})\right)
+ sage: X.coord_range()
+ x1: (0, 1); y1: (-oo, +oo)
+
+ """
+ from sage.symbolic.assumptions import assume
+ xx_list = [] # will contain the coordinates as Sage symbolic variables
+ bounds_list = [] # will contain the coordinate bounds
+ for coord_field in coord_list:
+ coord_properties = coord_field.split(':')
+ coord_symb = coord_properties[0].strip() # the coordinate symbol
+ # default values, possibly redefined below:
+ coord_latex = None
+ xmin = -Infinity; xmin_included = False
+ xmax = +Infinity; xmax_included = False
+ # scan of the properties other than the symbol:
+ for prop in coord_properties[1:]:
+ prop1 = prop.strip()
+ delim_min = prop1[0]
+ if delim_min in ['[', ']', '(']:
+ # prop1 is the coordinate's range
+ xmin_str, xmax_str = prop1[1:len(prop1)-1].split(',')
+ if xmin_str not in ['-inf', '-Inf', '-infinity',
+ '-Infinity', '-oo']:
+ xmin = SR(xmin_str)
+ xmin_included = ( delim_min == '[' )
+ if xmax_str not in ['inf', '+inf', 'Inf', '+Inf',
+ 'infinity', '+infinity', 'Infinity',
+ '+Infinity', 'oo', '+oo']:
+ xmax = SR(xmax_str)
+ xmax_included = ( prop1[-1] == ']' )
+ else:
+ # prop1 is the coordinate's LaTeX symbol
+ coord_latex = prop1
+ # Construction of the coordinate as some Sage's symbolic variable:
+ coord_var = SR.var(coord_symb, domain='real',
+ latex_name=coord_latex)
+ assume(coord_var, 'real')
+ if xmin != -Infinity:
+ if xmin_included:
+ assume(coord_var >= xmin)
+ else:
+ assume(coord_var > xmin)
+ if xmax != Infinity:
+ if xmax_included:
+ assume(coord_var <= xmax)
+ else:
+ assume(coord_var < xmax)
+ xx_list.append(coord_var)
+ bounds_list.append(((xmin, xmin_included), (xmax, xmax_included)))
+ self._xx = tuple(xx_list)
+ self._bounds = tuple(bounds_list)
+
+ def coord_bounds(self, i=None):
+ r"""
+ Return the lower and upper bounds of the range of a coordinate.
+
+ For a nicely formatted output, use :meth:`coord_range` instead.
+
+ INPUT:
+
+ - ``i`` -- (default: ``None``) index of the coordinate; if ``None``,
+ the bounds of all the coordinates are returned
+
+ OUTPUT:
+
+ - the coordinate bounds as the tuple
+ ((xmin, min_included), (xmax, max_included))
+ where
+
+ - xmin is the coordinate lower bound
+ - min_included is a Boolean, indicating whether the coordinate can
+ take the value xmin, i.e. xmin is a strict lower bound iff
+ min_included is False.
+ - xmin is the coordinate upper bound
+ - max_included is a Boolean, indicating whether the coordinate can
+ take the value xmax, i.e. xmax is a strict upper bound iff
+ max_included is False.
+
+ EXAMPLES:
+
+ Some coordinate bounds on a 2-dimensional manifold::
+
+ sage: forget() # for doctests only
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: c_xy.<x,y> = M.chart('x y:[0,1)')
+ sage: c_xy.coord_bounds(0) # x in (-oo,+oo) (the default)
+ ((-Infinity, False), (+Infinity, False))
+ sage: c_xy.coord_bounds(1) # y in [0,1)
+ ((0, True), (1, False))
+ sage: c_xy.coord_bounds()
+ (((-Infinity, False), (+Infinity, False)), ((0, True), (1, False)))
+ sage: c_xy.coord_bounds() == (c_xy.coord_bounds(0), c_xy.coord_bounds(1))
+ True
+
+ The coordinate bounds can also be recovered via the method
+ :meth:`coord_range`::
+
+ sage: c_xy.coord_range()
+ x: (-oo, +oo); y: [0, 1)
+ sage: c_xy.coord_range(y)
+ y: [0, 1)
+
+ or via Sage's function
+ :func:`sage.symbolic.assumptions.assumptions`::
+
+ sage: assumptions(x)
+ [x is real]
+ sage: assumptions(y)
+ [y is real, y >= 0, y < 1]
+
+ """
+ if i is None:
+ return self._bounds
+ else:
+ return self._bounds[i-self._manifold._sindex]
+
+ def coord_range(self, xx=None):
+ r"""
+ Display the range of a coordinate (or all coordinates), as an
+ interval.
+
+ INPUT:
+
+ - ``xx`` -- (default: ``None``) symbolic expression corresponding to a
+ coordinate of the current chart; if ``None``, the ranges of all
+ coordinates are displayed.
+
+ EXAMPLES:
+
+ Ranges of coordinates on a 2-dimensional manifold::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: X.coord_range()
+ x: (-oo, +oo); y: (-oo, +oo)
+ sage: X.coord_range(x)
+ x: (-oo, +oo)
+ sage: U = M.open_subset('U', coord_def={X: [x>1, y<pi]})
+ sage: XU = X.restrict(U) # restriction of chart X to U
+ sage: XU.coord_range()
+ x: (1, +oo); y: (-oo, pi)
+ sage: XU.coord_range(x)
+ x: (1, +oo)
+ sage: XU.coord_range(y)
+ y: (-oo, pi)
+
+ The output is LaTeX-formatted for the notebook::
+
+ sage: latex(XU.coord_range(y))
+ y :\ \left( -\infty, \pi \right)
+
+ """
+ from sage.tensor.modules.format_utilities import FormattedExpansion
+ def _display_coord_range(self, xx, rtxt, rlatex):
+ ind = self._xx.index(xx)
+ bounds = self._bounds[ind]
+ rtxt += "{}: ".format(xx)
+ rlatex += latex(xx) + r":\ "
+ if bounds[0][1]:
+ rtxt += "["
+ rlatex += r"\left["
+ else:
+ rtxt += "("
+ rlatex += r"\left("
+ xmin = bounds[0][0]
+ if xmin == -Infinity:
+ rtxt += "-oo, "
+ rlatex += r"-\infty,"
+ else:
+ rtxt += "{}, ".format(xmin)
+ rlatex += latex(xmin) + ","
+ xmax = bounds[1][0]
+ if xmax == Infinity:
+ rtxt += "+oo"
+ rlatex += r"+\infty"
+ else:
+ rtxt += "{}".format(xmax)
+ rlatex += latex(xmax)
+ if bounds[1][1]:
+ rtxt += "]"
+ rlatex += r"\right]"
+ else:
+ rtxt += ")"
+ rlatex += r"\right)"
+ return rtxt, rlatex
+ resu_txt = ""
+ resu_latex = ""
+ if xx is None:
+ for x in self._xx:
+ if resu_txt != "":
+ resu_txt += "; "
+ resu_latex += r";\quad "
+ resu_txt, resu_latex = _display_coord_range(self, x, resu_txt,
+ resu_latex)
+ else:
+ resu_txt, resu_latex = _display_coord_range(self, xx, resu_txt,
+ resu_latex)
+ return FormattedExpansion(resu_txt, resu_latex)
+
+
+ def add_restrictions(self, restrictions):
+ r"""
+ Add some restrictions on the coordinates.
+
+ INPUT:
+
+ - ``restrictions`` -- list of restrictions on the
+ coordinates, in addition to the ranges declared by the intervals
+ specified in the chart constructor.
+ A restriction can be any symbolic equality or inequality involving the
+ coordinates, such as x>y or x^2+y^2 != 0. The items of the list
+ ``restrictions`` are combined with the ``and`` operator; if some
+ restrictions are to be combined with the ``or`` operator instead, they
+ have to be passed as a tuple in some single item of the list
+ ``restrictions``. For example, ``restrictions`` = [x>y, (x!=0, y!=0),
+ z^2<x] means (x>y) and ((x!=0) or (y!=0)) and (z^2<x). If the list
+ ``restrictions`` contains only one item, this item can be passed as
+ such, i.e. writing x>y instead of the single element list [x>y].
+
+ EXAMPLES:
+
+ Cartesian coordinates on the open unit disc in $\RR^2$::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M') # the open unit disc
+ sage: X.<x,y> = M.chart()
+ sage: X.add_restrictions(x^2+y^2<1)
+ sage: X.valid_coordinates(0,2)
+ False
+ sage: X.valid_coordinates(0,1/3)
+ True
+
+ The restrictions are transmitted to subcharts::
+
+ sage: A = M.open_subset('A') # annulus 1/2 < r < 1
+ sage: X_A = X.restrict(A, x^2+y^2 > 1/4)
+ sage: X_A._restrictions
+ [x^2 + y^2 < 1, x^2 + y^2 > (1/4)]
+ sage: X_A.valid_coordinates(0,1/3)
+ False
+ sage: X_A.valid_coordinates(2/3,1/3)
+ True
+
+ If appropriate, the restrictions are transformed into bounds on
+ the coordinate ranges::
+
+ sage: U = M.open_subset('U')
+ sage: X_U = X.restrict(U)
+ sage: X_U.coord_range()
+ x: (-oo, +oo); y: (-oo, +oo)
+ sage: X_U.add_restrictions([x<0, y>1/2])
+ sage: X_U.coord_range()
+ x: (-oo, 0); y: (1/2, +oo)
+
+ """
+ import operator
+ if not isinstance(restrictions, list):
+ # case of a single condition or conditions to be combined by "or"
+ restrictions = [restrictions]
+ self._restrictions.extend(restrictions)
+ # Update of the coordinate bounds from the restrictions:
+ bounds = list(self._bounds) # convert to a list for modifications
+ new_restrictions = []
+ for restrict in self._restrictions:
+ restrict_used = False # determines whether restrict is used
+ # to set some coordinate bound
+ if not isinstance(restrict, tuple): # case of 'or' conditions
+ # excluded
+ operands = restrict.operands()
+ left = operands[0]
+ right = operands[1]
+ right_var = right.variables()
+ if left in self._xx:
+ # the l.h.s. of the restriction is a single
+ # coordinate
+ right_coord = [coord for coord in self._xx
+ if coord in right_var]
+ if not right_coord:
+ # there is no other coordinate in the r.h.s.
+ ind = self._xx.index(left)
+ left_bounds = list(bounds[ind])
+ oper = restrict.operator()
+ oinf = left_bounds[0][0] # old coord inf
+ osup = left_bounds[1][0] # old coord sup
+ if oper == operator.lt:
+ if osup == Infinity or right <= osup:
+ left_bounds[1] = (right, False)
+ restrict_used = True
+ elif oper == operator.le:
+ if osup == Infinity or right < osup:
+ left_bounds[1] = (right, True)
+ restrict_used = True
+ elif oper == operator.gt:
+ if oinf == -Infinity or right >= oinf:
+ left_bounds[0] = (right, False)
+ restrict_used = True
+ elif oper == operator.ge:
+ if oinf == -Infinity or right > oinf:
+ left_bounds[0] = (right, True)
+ restrict_used = True
+ bounds[ind] = tuple(left_bounds)
+ if not restrict_used:
+ # if restrict has not been used to set a coordinate bound
+ # it is maintained in the list of restrictions:
+ new_restrictions.append(restrict)
+ self._bounds = tuple(bounds)
+ self._restrictions = new_restrictions
+
+ def restrict(self, subset, restrictions=None):
+ r"""
+ Return the restriction of the chart to some open subset of its domain.
+
+ If the current chart is `(U,\varphi)`, a *restriction* (or *subchart*)
+ is a chart `(V,\psi)` such that `V\subset U` and `\psi = \varphi |_V`.
+
+ If such subchart has not been defined yet, it is constructed here.
+
+ The coordinates of the subchart bare the same names as the coordinates
+ of the current chart.
+
+ INPUT:
+
+ - ``subset`` -- open subset `V` of the chart domain `U` (must be an
+ instance of :class:`~sage.manifolds.manifold.TopManifold`)
+ - ``restrictions`` -- (default: ``None``) list of coordinate
+ restrictions defining the subset `V`.
+ A restriction can be any symbolic equality or
+ inequality involving the coordinates, such as x>y or x^2+y^2 != 0.
+ The items of the list ``restrictions`` are combined with the ``and``
+ operator; if some restrictions are to be combined with the ``or``
+ operator instead, they have to be passed as a tuple in some single
+ item of the list ``restrictions``. For example, ``restrictions``
+ being [x>y, (x!=0, y!=0), z^2<x] means (x>y) and ((x!=0) or (y!=0))
+ and (z^2<x). If the list ``restrictions`` contains only one item,
+ this item can be passed as such, i.e. writing x>y instead of the
+ single element list [x>y]. Note that the argument ``restrictions``
+ can be omitted if the subchart has been already initialized by a
+ previous call.
+
+ OUTPUT:
+
+ - chart `(V,\psi)`, as an instance of :class:`RealChart`.
+
+ EXAMPLES:
+
+ Cartesian coordinates on the unit open disc in `\RR^2` as a subchart
+ of the global Cartesian coordinates::
+
+ sage: M = TopManifold(2, 'R^2')
+ sage: c_cart.<x,y> = M.chart() # Cartesian coordinates on R^2
+ sage: D = M.open_subset('D') # the unit open disc
+ sage: c_cart_D = c_cart.restrict(D, x^2+y^2<1)
+ sage: p = M.point((1/2, 0))
+ sage: p in D
+ True
+ sage: q = M.point((1, 2))
+ sage: q in D
+ False
+
+ Cartesian coordinates on the annulus `1<\sqrt{x^2+y^2}<2`::
+
+ sage: A = M.open_subset('A')
+ sage: c_cart_A = c_cart.restrict(A, [x^2+y^2>1, x^2+y^2<4])
+ sage: p in A, q in A
+ (False, False)
+ sage: a = M.point((3/2,0))
+ sage: a in A
+ True
+
+ """
+ if subset == self._domain:
+ return self
+ if subset not in self._dom_restrict:
+ if not subset.is_subset(self._domain):
+ raise ValueError("the specified subset is not a subset " +
+ "of the domain of definition of the chart")
+ coordinates = ""
+ for coord in self._xx:
+ coordinates += repr(coord) + ' '
+ res = self.__class__(subset, coordinates)
+ res._bounds = self._bounds
+ res._restrictions.extend(self._restrictions)
+ # The coordinate restrictions are added to the result chart and
+ # possibly transformed into coordinate bounds:
+ if restrictions is not None:
+ res.add_restrictions(restrictions)
+ # Update of supercharts and subcharts:
+ res._supercharts.update(self._supercharts)
+ for schart in self._supercharts:
+ schart._subcharts.add(res)
+ schart._dom_restrict[subset] = res
+ # Update of domain restrictions:
+ self._dom_restrict[subset] = res
+ return self._dom_restrict[subset]
+
+ def valid_coordinates(self, *coordinates, **kwds):
+ r"""
+ Check whether a tuple of coordinates can be the coordinates of a
+ point in the chart domain.
+
+ INPUT:
+
+ - ``*coordinates`` -- coordinate values
+ - ``**kwds`` -- options:
+
+ - ``tolerance=0``, to set the absolute tolerance in the test of
+ coordinate ranges
+ - ``parameters=None``, to set some numerical values to parameters
+
+
+ OUTPUT:
+
+ - True if the coordinate values are admissible in the chart range.
+
+ EXAMPLES:
+
+ Cartesian coordinates on a square interior::
+
+ sage: forget() # for doctest only
+ sage: M = TopManifold(2, 'M') # the square interior
+ sage: X.<x,y> = M.chart('x:(-2,2) y:(-2,2)')
+ sage: X.valid_coordinates(0,1)
+ True
+ sage: X.valid_coordinates(-3/2,5/4)
+ True
+ sage: X.valid_coordinates(0,3)
+ False
+
+ The unit open disk inside the square::
+
+ sage: D = M.open_subset('D', coord_def={X: x^2+y^2<1})
+ sage: XD = X.restrict(D)
+ sage: XD.valid_coordinates(0,1)
+ False
+ sage: XD.valid_coordinates(-3/2,5/4)
+ False
+ sage: XD.valid_coordinates(-1/2,1/2)
+ True
+ sage: XD.valid_coordinates(0,0)
+ True
+
+ """
+ n = len(coordinates)
+ if n != self._manifold._dim:
+ return False
+ if 'tolerance' in kwds:
+ tolerance = kwds['tolerance']
+ else:
+ tolerance = 0
+ if 'parameters' in kwds:
+ parameters = kwds['parameters']
+ else:
+ parameters = None
+ # Check of the coordinate ranges:
+ for x, bounds in zip(coordinates, self._bounds):
+ xmin = bounds[0][0] - tolerance
+ min_included = bounds[0][1]
+ xmax = bounds[1][0] + tolerance
+ max_included = bounds[1][1]
+ if parameters:
+ xmin = xmin.subs(parameters)
+ xmax = xmax.subs(parameters)
+ if min_included:
+ if x < xmin:
+ return False
+ else:
+ if x <= xmin:
+ return False
+ if max_included:
+ if x > xmax:
+ return False
+ else:
+ if x >= xmax:
+ return False
+ # Check of additional restrictions:
+ if self._restrictions != []:
+ substitutions = dict(zip(self._xx, coordinates))
+ if parameters:
+ substitutions.update(parameters)
+ for restrict in self._restrictions:
+ if isinstance(restrict, tuple): # case of or conditions
+ combine = False
+ for expr in restrict:
+ combine = combine or bool(expr.subs(substitutions))
+ if not combine:
+ return False
+ else:
+ if not bool(restrict.subs(substitutions)):
+ return False
+ # All tests have been passed:
+ return True
+
+
+#*****************************************************************************
+
+class CoordChange(SageObject):
+ r"""
+ Transition map between two charts of a topological manifold.
+
+ Giving two coordinate charts `(U,\varphi)` and `(V,\psi)` on a topological
+ manifold `M` of dimension `n` over a topological field `K`, the
+ *transition map from* `(U,\varphi)` *to* `(V,\psi)` is the map
+
+ .. MATH::
+
+ \psi\circ\varphi^{-1}: \varphi(U\cap V) \subset K^n
+ \rightarrow \psi(U\cap V) \subset K^n,
+
+ In other words, the transition map `\psi\circ\varphi^{-1}` expresses the
+ coordinates `(y^1,\ldots,y^n)` of `(V,\psi)` in terms of the coordinates
+ `(x^1,\ldots,x^n)` of `(U,\varphi)` on the open subset where the two
+ charts intersect, i.e. on `U\cap V`.
+
+ INPUT:
+
+ - ``chart1`` -- chart `(U,\varphi)`
+ - ``chart2`` -- chart `(V,\psi)`
+ - ``transformations`` -- tuple (or list) `(Y_1,\ldots,Y_2)`, where
+ `Y_i` is the symbolic expression of the coordinate `y^i` in terms
+ of the coordinates `(x^1,\ldots,x^n)`
+
+ EXAMPLES:
+
+ Transition map on a 2-dimensional topological manifold::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: Y.<u,v> = M.chart()
+ sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
+ sage: X_to_Y
+ Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))
+ sage: type(X_to_Y)
+ <class 'sage.manifolds.chart.CoordChange'>
+ sage: X_to_Y.display()
+ u = x + y
+ v = x - y
+
+ """
+ def __init__(self, chart1, chart2, *transformations):
+ r"""
+ Construct a transition map.
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: Y.<u,v> = M.chart()
+ sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
+ sage: X_to_Y
+ Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))
+ sage: type(X_to_Y)
+ <class 'sage.manifolds.chart.CoordChange'>
+ sage: TestSuite(X_to_Y).run(skip='_test_pickling')
+
+ .. TODO::
+
+ fix _test_pickling
+
+ """
+ self._n1 = len(chart1._xx)
+ self._n2 = len(chart2._xx)
+ if len(transformations) != self._n2:
+ raise ValueError("{} coordinate transformations ".format(self._n2)
+ + "must be provided")
+ self._chart1 = chart1
+ self._chart2 = chart2
+ #*# when MultiCoordFunction will be implemented (trac #18640):
+ # self._transf = chart1.multifunction(*transformations)
+ #*# for now:
+ self._transf = transformations
+ self._inverse = None
+ # If the two charts are on the same open subset, the coordinate change
+ # is added to the subset (and supersets) dictionary:
+ if chart1._domain == chart2._domain:
+ domain = chart1._domain
+ for sdom in domain._supersets:
+ sdom._coord_changes[(chart1, chart2)] = self
+
+ def _repr_(self):
+ r"""
+ String representation of the transition map.
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: Y.<u,v> = M.chart()
+ sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
+ sage: X_to_Y._repr_()
+ 'Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))'
+ sage: repr(X_to_Y) # indirect doctest
+ 'Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))'
+ sage: X_to_Y # indirect doctest
+ Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))
+
+ """
+ return "Change of coordinates from {} to {}".format(self._chart1,
+ self._chart2)
+
+ def _latex_(self):
+ r"""
+ LaTeX representation of the transition map.
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: Y.<u,v> = M.chart()
+ sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
+ sage: X_to_Y._latex_()
+ \left(M,(x, y)\right) \rightarrow \left(M,(u, v)\right)
+ sage: latex(X_to_Y) # indirect doctest
+ \left(M,(x, y)\right) \rightarrow \left(M,(u, v)\right)
+
+ """
+ return latex(self._chart1) + r' \rightarrow ' + latex(self._chart2)
+
+ def __call__(self, *coords):
+ r"""
+ Compute the new coordinates from old ones.
+
+ INPUT:
+
+ - ``coords`` -- values of coordinates of ``chart1``
+
+ OUTPUT:
+
+ - tuple of values of coordinates of ``chart2``
+
+ EXAMPLE::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: Y.<u,v> = M.chart()
+ sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
+ sage: X_to_Y(1,2)
+ (3, -1)
+
+ """
+ #*# When MultiCoordFunction is implemented (trac #18640):
+ # return self._transf(*coords)
+ #*# for now:
+ substitutions = dict([(self._chart1._xx[j], coords[j]) for j in
+ range(self._n1)])
+ return tuple(self._transf[i].subs(substitutions).simplify_full()
+ for i in range(self._n2))
+
+ def inverse(self):
+ r"""
+ Compute the inverse coordinate transformation.
+
+ OUTPUT:
+
+ - an instance of :class:`CoordChange` representing the inverse of
+ the current coordinate transformation.
+
+ EXAMPLES:
+
+ Inverse of a coordinate transformation corresponding to a pi/3-rotation
+ in the plane::
+
+ sage: M = TopManifold(2, 'M')
+ sage: c_xy.<x,y> = M.chart()
+ sage: c_uv.<u,v> = M.chart()
+ sage: xy_to_uv = c_xy.transition_map(c_uv, ((x - sqrt(3)*y)/2, (sqrt(3)*x + y)/2))
+ sage: M.coord_changes()
+ {(Chart (M, (x, y)),
+ Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))}
+ sage: uv_to_xy = xy_to_uv.inverse(); uv_to_xy
+ Change of coordinates from Chart (M, (u, v)) to Chart (M, (x, y))
+ sage: uv_to_xy.display()
+ x = 1/2*sqrt(3)*v + 1/2*u
+ y = -1/2*sqrt(3)*u + 1/2*v
+ sage: M.coord_changes() # random (dictionary output)
+ {(Chart (M, (u, v)),
+ Chart (M, (x, y))): Change of coordinates from Chart (M, (u, v)) to Chart (M, (x, y)),
+ (Chart (M, (x, y)),
+ Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))}
+
+ """
+ from sage.symbolic.relation import solve
+ if self._inverse is not None:
+ return self._inverse
+ # The computation is necessary:
+ x1 = self._chart1._xx # list of coordinates in chart1
+ x2 = self._chart2._xx # list of coordinates in chart2
+ n1 = self._n1
+ n2 = self._n2
+ if n1 != n2:
+ raise ValueError("the change of coordinates is not invertible " +
+ "(different number of coordinates in the two " +
+ "charts)")
+ # New symbolic variables (different from x2 to allow for a
+ # correct solution even when chart2 = chart1):
+ if self._chart1.domain().base_field() == 'real':
+ coord_domain = ['real' for i in range(n2)]
+ elif self._chart1.domain().base_field() == 'complex':
+ coord_domain = ['complex' for i in range(n2)]
+ else:
+ coord_domain = [None for i in range(n2)]
+ for i in range(n2):
+ if x2[i].is_positive():
+ coord_domain[i] = 'positive'
+ xp2 = [ SR.var('xxxx' + str(i), domain=coord_domain[i])
+ for i in range(n2) ]
+ #*# when MultiCoordFunction will be implemented (trac #18640):
+ # xx2 = self._transf.expr()
+ #*# for now:
+ xx2 = self._transf
+ equations = [ xp2[i] == xx2[i] for i in range(n2) ]
+ try:
+ solutions = solve(equations, *x1, solution_dict=True)
+ except RuntimeError:
+ raise RuntimeError("the system could not be solved; use " +
+ "set_inverse() to set the inverse manually")
+ substitutions = dict(zip(xp2, x2))
+ if len(solutions) == 1:
+ x2_to_x1 = [solutions[0][x1[i]].subs(substitutions)
+ for i in range(n1)]
+ else:
+ list_x2_to_x1 = []
+ for sol in solutions:
+ if x2[0] in sol:
+ raise ValueError("the system could not be solved; use " +
+ "set_inverse() to set the inverse " +
+ "manually")
+ x2_to_x1 = [sol[x1[i]].subs(substitutions) for i in range(n1)]
+ if self._chart1.valid_coordinates(*x2_to_x1):
+ list_x2_to_x1.append(x2_to_x1)
+ if len(list_x2_to_x1) == 0:
+ raise ValueError("no solution found; use set_inverse() to " +
+ "set the inverse manually")
+ if len(list_x2_to_x1) > 1:
+ print "Multiple solutions found: "
+ print list_x2_to_x1
+ raise ValueError(
+ "non-unique solution to the inverse coordinate " +
+ "transformation; use set_inverse() to set the inverse " +
+ "manually")
+ x2_to_x1 = list_x2_to_x1[0]
+ self._inverse = self.__class__(self._chart2, self._chart1, *x2_to_x1)
+ return self._inverse
+
+
+ def set_inverse(self, *transformations, **kwds):
+ r"""
+ Sets the inverse of the coordinate transformation.
+
+ This is useful when the automatic computation via :meth:`inverse()`
+ fails.
+
+ INPUT:
+
+ - ``transformations`` -- the inverse transformations expressed as a
+ list of the expressions of the "old" coordinates in terms of the
+ "new" ones
+ - ``kwds`` -- keyword arguments: only ``check=True`` (default) or
+ ``check=False`` are meaningful; it determines whether the provided
+ transformations are checked to be indeed the inverse coordinate
+ transformations.
+
+ EXAMPLES:
+
+ From spherical coordinates to Cartesian ones in the plane::
+
+ sage: M = TopManifold(2, 'R^2')
+ sage: U = M.open_subset('U') # the complement of the half line {y=0, x>= 0}
+ sage: c_cart.<x,y> = U.chart()
+ sage: c_spher.<r,ph> = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi')
+ sage: spher_to_cart = c_spher.transition_map(c_cart, [r*cos(ph), r*sin(ph)])
+ sage: spher_to_cart.set_inverse(sqrt(x^2+y^2), atan2(y,x))
+ Check of the inverse coordinate transformation:
+ r == r
+ ph == arctan2(r*sin(ph), r*cos(ph))
+ x == x
+ y == y
+ sage: spher_to_cart.inverse()
+ Change of coordinates from Chart (U, (x, y)) to Chart (U, (r, ph))
+ sage: M.coord_changes() # random (dictionary output)
+ {(Chart (U, (r, ph)),
+ Chart (U, (x, y))): Change of coordinates from Chart (U, (r, ph)) to Chart (U, (x, y)),
+ (Chart (U, (x, y)),
+ Chart (U, (r, ph))): Change of coordinates from Chart (U, (x, y)) to Chart (U, (r, ph))}
+
+ Introducing a wrong inverse transformation is revealed by the check::
+
+ sage: spher_to_cart.set_inverse(sqrt(x^3+y^2), atan2(y,x)) # note the x^3 typo
+ Check of the inverse coordinate transformation:
+ r == sqrt(r^3*cos(ph)^3 + r^2*sin(ph)^2)
+ ph == arctan2(r*sin(ph), r*cos(ph))
+ x == sqrt(x^3 + y^2)*x/sqrt(x^2 + y^2)
+ y == sqrt(x^3 + y^2)*y/sqrt(x^2 + y^2)
+
+ """
+ if 'check' in kwds:
+ check = kwds['check']
+ else:
+ check = True
+ self._inverse = self.__class__(self._chart2, self._chart1,
+ *transformations)
+ if check:
+ print "Check of the inverse coordinate transformation:"
+ x1 = self._chart1._xx
+ x2 = self._chart2._xx
+ n1 = len(x1)
+ for i in range(n1):
+ print " ", x1[i], '==' , self._inverse(*(self(*x1)))[i]
+ for i in range(n1):
+ print " ", x2[i], '==', self(*(self._inverse(*x2)))[i]
+
+ def __mul__(self, other):
+ r"""
+ Composition with another change of coordinates
+
+ INPUT:
+
+ - ``other`` -- another change of coordinate, the final chart of
+ it is the initial chart of ``self``
+
+ OUTPUT:
+
+ - the change of coordinates X_1 --> X_3, where X_1 is the initial
+ chart of ``other`` and X_3 is the final chart of ``self``
+
+ EXAMPLE::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: U.<u,v> = M.chart()
+ sage: X_to_U = X.transition_map(U, (x+y, x-y))
+ sage: W.<w,z> = M.chart()
+ sage: U_to_W = U.transition_map(W, (u+cos(u)/2, v-sin(v)/2))
+ sage: X_to_W = U_to_W * X_to_U; X_to_W
+ Change of coordinates from Chart (M, (x, y)) to Chart (M, (w, z))
+ sage: X_to_W.display()
+ w = 1/2*cos(x)*cos(y) - 1/2*sin(x)*sin(y) + x + y
+ z = -1/2*cos(y)*sin(x) + 1/2*cos(x)*sin(y) + x - y
+
+ """
+ if not isinstance(other, CoordChange):
+ raise TypeError("{} is not a change of coordinate".format(other))
+ if other._chart2 != self._chart1:
+ raise ValueError("composition not possible: " +
+ "{} is different from {}".format(other._chart2,
+ other._chart1))
+ #*# when MultiCoordFunction will be implemented (trac #18640):
+ # transf = self._transf(*(other._transf.expr()))
+ #*# for now:
+ transf = self(*(other._transf))
+ return self.__class__(other._chart1, self._chart2, *transf)
+
+ def restrict(self, dom1, dom2=None):
+ r"""
+ Restriction to subsets.
+
+ INPUT:
+
+ - ``dom1`` -- open subset of the domain of ``chart1``
+ - ``dom2`` -- (default: ``None``) open subset of the domain of
+ ``chart2``; if ``None``, ``dom1`` is assumed.
+
+ OUTPUT:
+
+ - the transition map between the charts restricted to the specified
+ subsets.
+
+ EXAMPLE::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: Y.<u,v> = M.chart()
+ sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
+ sage: U = M.open_subset('U', coord_def={X: x>0, Y: u+v>0})
+ sage: X_to_Y_U = X_to_Y.restrict(U); X_to_Y_U
+ Change of coordinates from Chart (U, (x, y)) to Chart (U, (u, v))
+ sage: X_to_Y_U.display()
+ u = x + y
+ v = x - y
+
+ The result is cached::
+
+ sage: X_to_Y.restrict(U) is X_to_Y_U
+ True
+
+ """
+ if dom2 is None:
+ dom2 = dom1
+ ch1 = self._chart1.restrict(dom1)
+ ch2 = self._chart2.restrict(dom2)
+ if (ch1, ch2) in dom1.coord_changes():
+ return dom1.coord_changes()[(ch1,ch2)]
+ #*# when MultiCoordFunction will be implemented (trac #18640):
+ # return self.__class__(self._chart1.restrict(dom1),
+ # self._chart2.restrict(dom2), *(self._transf.expr()))
+ #*# for now:
+ return self.__class__(self._chart1.restrict(dom1),
+ self._chart2.restrict(dom2), *(self._transf))
+
+ def display(self):
+ r"""
+ Display of the coordinate transformation.
+
+ The output is either text-formatted (console mode) or LaTeX-formatted
+ (notebook mode).
+
+ EXAMPLE:
+
+ From spherical coordinates to Cartesian ones in the plane::
+
+ sage: M = TopManifold(2, 'R^2')
+ sage: U = M.open_subset('U') # the complement of the half line {y=0, x>= 0}
+ sage: c_cart.<x,y> = U.chart()
+ sage: c_spher.<r,ph> = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi')
+ sage: spher_to_cart = c_spher.transition_map(c_cart, [r*cos(ph), r*sin(ph)])
+ sage: spher_to_cart.display()
+ x = r*cos(ph)
+ y = r*sin(ph)
+ sage: latex(spher_to_cart.display())
+ \left\{\begin{array}{lcl} x & = & r \cos\left({\phi}\right) \\
+ y & = & r \sin\left({\phi}\right) \end{array}\right.
+
+ A shortcut is ``disp()``::
+
+ sage: spher_to_cart.disp()
+ x = r*cos(ph)
+ y = r*sin(ph)
+
+ """
+ from sage.misc.latex import latex
+ from sage.tensor.modules.format_utilities import FormattedExpansion
+ coords2 = self._chart2[:]
+ n2 = len(coords2)
+ #*# when MultiCoordFunction will be implemented (trac #18640):
+ # expr = self._transf.expr()
+ #*# for now:
+ expr = self._transf
+ rtxt = ""
+ if n2 == 1:
+ rlatex = r"\begin{array}{lcl}"
+ else:
+ rlatex = r"\left\{\begin{array}{lcl}"
+ for i in range(n2):
+ x2 = coords2[i]
+ x2f = expr[i]
+ rtxt += repr(x2) + " = " + repr(x2f) + "\n"
+ rlatex += latex(x2) + r" & = & " + latex(x2f) + r"\\"
+ rtxt = rtxt[:-1] # remove the last new line
+ rlatex = rlatex[:-2] + r"\end{array}"
+ if n2 > 1:
+ rlatex += r"\right."
+ return FormattedExpansion(rtxt, rlatex)
+
+ disp = display
diff --git a/src/sage/manifolds/manifold.py b/src/sage/manifolds/manifold.py
new file mode 100644
index 0000000..2538ce1
--- /dev/null
+++ b/src/sage/manifolds/manifold.py
@@ -0,0 +1,1332 @@
+r"""
+Topological manifolds
+
+Given a topological field `K` (in most applications, `K = \RR` or
+`K = \CC`) and a non-negative integer `n`, a *topological manifold of
+dimension* `n` *over K* is a topological space `M` such that
+
+- `M` is a Hausdorff space,
+- `M` is second countable,
+- every point in `M` has a neighborhood homeomorphic to `K^n`
+
+Topological manifolds are implemented via the class :class:`TopManifold`.
+Open subsets of topological manifolds are also implemented via
+:class:`TopManifold`, since they are topological manifolds by themselves.
+
+In the current setting, topological manifolds are mostly described by means of
+charts (see :class:`~sage.manifolds.chart.Chart`).
+
+:class:`TopManifold` serves as a base class for more specific manifold classes.
+
+.. RUBRIC:: Example 1: the 2-sphere as a topological manifold of dimension
+ 2 over `\RR`
+
+One starts by declaring `S^2` as a 2-dimensional topological manifold::
+
+ sage: M = TopManifold(2, 'S^2')
+ sage: M
+ 2-dimensional topological manifold S^2
+
+Since the base topological field has not been specified in the argument list
+of ``TopManifold``, `\RR` is assumed::
+
+ sage: M.base_field()
+ 'real'
+ sage: dim(M)
+ 2
+
+Let us consider the complement of a point, the "North pole" say; this is an
+open subset of `S^2`, which we call `U`::
+
+ sage: U = M.open_subset('U'); U
+ Open subset U of the 2-dimensional topological manifold S^2
+
+A standard chart on `U` is provided by the stereographic projection from the
+North pole to the equatorial plane::
+
+ sage: stereoN.<x,y> = U.chart(); stereoN
+ Chart (U, (x, y))
+
+Thanks to the operator ``<x,y>`` on the left-hand side, the coordinates
+declared in a chart (here `x` and `y`), are accessible by their names; they are
+Sage's symbolic variables::
+
+ sage: y
+ y
+ sage: type(y)
+ <type 'sage.symbolic.expression.Expression'>
+
+The South pole is the point of coordinates `(x,y)=(0,0)` in the above
+chart::
+
+ sage: S = U.point((0,0), chart=stereoN, name='S'); S
+ Point S on the 2-dimensional topological manifold S^2
+
+Let us call `V` the open subset that is the complement of the South pole and
+let us introduce on it the chart induced by the stereographic projection from
+the South pole to the equatorial plane::
+
+ sage: V = M.open_subset('V'); V
+ Open subset V of the 2-dimensional topological manifold S^2
+ sage: stereoS.<u,v> = V.chart(); stereoS
+ Chart (V, (u, v))
+
+The North pole is the point of coordinates `(u,v)=(0,0)` in this chart::
+
+ sage: N = V.point((0,0), chart=stereoS, name='N'); N
+ Point N on the 2-dimensional topological manifold S^2
+
+To fully construct the manifold, we declare that it is the union of `U`
+and `V`::
+
+ sage: M.declare_union(U,V)
+
+and we provide the transition map between the charts ``stereoN`` = `(U, (x, y))`
+and ``stereoS`` = `(V, (u, v))`, denoting by W the intersection of U and V
+(W is the subset of U defined by `x^2+y^2\not=0`, as well as the subset of V
+defined by`u^2+v^2\not=0`)::
+
+ sage: stereoN_to_S = stereoN.transition_map(stereoS, [x/(x^2+y^2), y/(x^2+y^2)],
+ ....: intersection_name='W', restrictions1= x^2+y^2!=0, restrictions2= u^2+v^2!=0)
+ sage: stereoN_to_S
+ Change of coordinates from Chart (W, (x, y)) to Chart (W, (u, v))
+ sage: stereoN_to_S.display()
+ u = x/(x^2 + y^2)
+ v = y/(x^2 + y^2)
+
+We give the name ``W`` to the Python variable representing `W=U\cap V`::
+
+ sage: W = U.intersection(V)
+
+The inverse of the transition map is computed by the method ``inverse()``::
+
+ sage: stereoN_to_S.inverse()
+ Change of coordinates from Chart (W, (u, v)) to Chart (W, (x, y))
+ sage: stereoN_to_S.inverse().display()
+ x = u/(u^2 + v^2)
+ y = v/(u^2 + v^2)
+
+At this stage, we have four open subsets on `S^2`::
+
+ sage: M.list_of_subsets()
+ [2-dimensional topological manifold S^2,
+ Open subset U of the 2-dimensional topological manifold S^2,
+ Open subset V of the 2-dimensional topological manifold S^2,
+ Open subset W of the 2-dimensional topological manifold S^2]
+
+`W` is the open subset that is the complement of the two poles::
+
+ sage: N in W or S in W
+ False
+
+
+The North pole lies in `V` and the South pole in `U`::
+
+ sage: N in V, N in U
+ (True, False)
+ sage: S in U, S in V
+ (True, False)
+
+The manifold's (user) atlas contains four charts, two of them
+being restrictions of charts to a smaller domain::
+
+ sage: M.atlas()
+ [Chart (U, (x, y)), Chart (V, (u, v)), Chart (W, (x, y)), Chart (W, (u, v))]
+
+Let us consider the point of coordinates (1,2) in the chart ``stereoN``::
+
+ sage: p = M.point((1,2), chart=stereoN, name='p'); p
+ Point p on the 2-dimensional topological manifold S^2
+ sage: p.parent()
+ 2-dimensional topological manifold S^2
+ sage: p in W
+ True
+
+The coordinates of `p` in the chart ``stereoS`` are::
+
+ sage: stereoS(p)
+ (1/5, 2/5)
+
+Given the definition of `p`, we have of course::
+
+ sage: stereoN(p)
+ (1, 2)
+
+Similarly::
+
+ sage: stereoS(N)
+ (0, 0)
+ sage: stereoN(S)
+ (0, 0)
+
+
+.. RUBRIC:: Example 2: the Riemann sphere as a topological manifold of
+ dimension 1 over `\CC`
+
+We declare the Riemann sphere `\CC^*` as a 1-dimensional topological manifold
+over `\CC`::
+
+ sage: M = TopManifold(1, 'C*', field='complex'); M
+ Complex 1-dimensional topological manifold C*
+
+We introduce a first open subset, which is actually
+`\CC = \CC^*\setminus\{\infty\}` if we interpret `\CC^*` as the Alexandroff
+one-point compactification of `\CC`::
+
+ sage: U = M.open_subset('U')
+
+A natural chart on `U` is then nothing but the identity map of `\CC`, hence
+we denote the associated coordinate by `z`::
+
+ sage: Z.<z> = U.chart()
+
+The origin of the complex plane is the point of coordinate `z=0`::
+
+ sage: O = U.point((0,), chart=Z, name='O'); O
+ Point O on the Complex 1-dimensional topological manifold C*
+
+Another open subset of `\CC^*` is `V = \CC^*\setminus\{O\}`::
+
+ sage: V = M.open_subset('V')
+
+We define a chart on `V` such that the point at infinity is the point of
+coordinate 0 in this chart::
+
+ sage: W.<w> = V.chart(); W
+ Chart (V, (w,))
+ sage: inf = M.point((0,), chart=W, name='inf', latex_name=r'\infty')
+ sage: inf
+ Point inf on the Complex 1-dimensional topological manifold C*
+
+To fully construct the Riemann sphere, we declare that it is the union of `U`
+and `V`::
+
+ sage: M.declare_union(U,V)
+
+and we provide the transition map between the two charts as `w=1/z` on
+on `A = U\cap V`::
+
+ sage: Z_to_W = Z.transition_map(W, 1/z, intersection_name='A',
+ ....: restrictions1= z!=0, restrictions2= w!=0)
+ sage: Z_to_W
+ Change of coordinates from Chart (A, (z,)) to Chart (A, (w,))
+ sage: Z_to_W.display()
+ w = 1/z
+ sage: Z_to_W.inverse()
+ Change of coordinates from Chart (A, (w,)) to Chart (A, (z,))
+ sage: Z_to_W.inverse().display()
+ z = 1/w
+
+Let consider the complex number `i` as a point of the Riemann sphere::
+
+ sage: i = M((I,), chart=Z, name='i'); i
+ Point i on the Complex 1-dimensional topological manifold C*
+
+Its coordinates w.r.t. the charts ``Z`` and ``W`` are::
+
+ sage: Z(i)
+ (I,)
+ sage: W(i)
+ (-I,)
+
+and we have::
+
+ sage: i in U
+ True
+ sage: i in V
+ True
+
+The following subsets and charts have been defined::
+
+ sage: M.list_of_subsets()
+ [Open subset A of the Complex 1-dimensional topological manifold C*,
+ Complex 1-dimensional topological manifold C*,
+ Open subset U of the Complex 1-dimensional topological manifold C*,
+ Open subset V of the Complex 1-dimensional topological manifold C*]
+ sage: M.atlas()
+ [Chart (U, (z,)), Chart (V, (w,)), Chart (A, (z,)), Chart (A, (w,))]
+
+
+AUTHORS:
+
+- Eric Gourgoulhon (2015): initial version
+
+REFERENCES:
+
+- J.M. Lee : *Introduction to Topological Manifolds*, 2nd ed., Springer (New
+ York) (2011)
+- S. Kobayashi & K. Nomizu : *Foundations of Differential Geometry*, vol. 1,
+ Interscience Publishers (New York) (1963)
+- D. Huybrechts : *Complex Geometry*, Springer (Berlin) (2005)
+
+"""
+
+#*****************************************************************************
+# Copyright (C) 2015 Eric Gourgoulhon <eric.gourgoulhon@obspm.fr>
+# Copyright (C) 2015 Michal Bejger <bejger@camk.edu.pl>
+#
+# Distributed under the terms of the GNU General Public License (GPL)
+# as published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+# http://www.gnu.org/licenses/
+#*****************************************************************************
+
+from sage.categories.fields import Fields
+#*# Before #18175:
+from sage.categories.sets_cat import Sets
+#*# After #18175, this should become
+# from sage.categories.manifolds import Manifolds
+from sage.manifolds.subset import TopManifoldSubset
+
+class TopManifold(TopManifoldSubset):
+ r"""
+ Topological manifold over a topological field `K`.
+
+ Given a topological field `K` (in most applications, `K = \RR` or
+ `K = \CC`) and a non-negative integer `n`, a *topological manifold of
+ dimension* `n` *over K* is a topological space `M` such that
+
+ - `M` is a Hausdorff space,
+ - `M` is second countable,
+ - every point in `M` has a neighborhood homeomorphic to `K^n`
+
+ This is a Sage *parent* class, the corresponding *element*
+ class being :class:`~sage.manifolds.point.TopManifoldPoint`.
+
+ INPUT:
+
+ - ``n`` -- positive integer; dimension of the manifold
+ - ``name`` -- string; name (symbol) given to the manifold
+ - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote the
+ manifold; if none is provided, it is set to ``name``
+ - ``field`` -- (default: 'real') field `K` on which the manifold is
+ defined; allowed values are
+
+ - 'real' for a manifold over `\RR`
+ - 'complex' for a manifold over `\CC`
+ - an object in the category of fields (see
+ :class:`~sage.categories.fields.Fields`) for more general manifolds
+
+ - ``start_index`` -- (default: 0) integer; lower value of the range of
+ indices used for "indexed objects" on the manifold, e.g. coordinates
+ in a chart
+ - ``category`` -- (default: ``None``) to specify the category; the
+ default being ``Sets()`` (``Manifolds()`` after :trac:`18175` is
+ implemented)
+ - ``ambient_manifold`` -- (default: ``None``) if not ``None``, the created
+ object is considered as an open subset of the topological manifold
+ ``ambient_manifold``
+
+ EXAMPLES:
+
+ A 4-dimensional topological manifold (over `\RR`)::
+
+ sage: M = TopManifold(4, 'M', latex_name=r'\mathcal{M}')
+ sage: M
+ 4-dimensional topological manifold M
+ sage: latex(M)
+ \mathcal{M}
+ sage: M.base_field()
+ 'real'
+ sage: dim(M)
+ 4
+
+ The input parameter ``start_index`` defines the range of indices on the
+ manifold::
+
+ sage: M = TopManifold(4, 'M')
+ sage: list(M.irange())
+ [0, 1, 2, 3]
+ sage: M = TopManifold(4, 'M', start_index=1)
+ sage: list(M.irange())
+ [1, 2, 3, 4]
+ sage: list(TopManifold(4, 'M', start_index=-2).irange())
+ [-2, -1, 0, 1]
+
+ A complex manifold::
+
+ sage: N = TopManifold(3, 'N', field='complex'); N
+ Complex 3-dimensional topological manifold N
+
+ A manifold over `\QQ`::
+
+ sage: N = TopManifold(6, 'N', field=QQ); N
+ 6-dimensional topological manifold N over the Rational Field
+
+ A manifold over `\QQ_5`, the field of 5-adic numbers::
+
+ sage: N = TopManifold(2, 'N', field=Qp(5)); N
+ 2-dimensional topological manifold N over the 5-adic Field with capped
+ relative precision 20
+
+ A manifold is a Sage *parent* object, in the category of sets::
+
+ sage: isinstance(M, Parent)
+ True
+ sage: M.category()
+ Category of sets
+ sage: M in Sets()
+ True
+
+ The corresponding Sage *elements* are points::
+
+ sage: X.<t, x, y, z> = M.chart()
+ sage: p = M.an_element(); p
+ Point on the 4-dimensional topological manifold M
+ sage: p.parent()
+ 4-dimensional topological manifold M
+ sage: M.is_parent_of(p)
+ True
+ sage: p in M
+ True
+
+ The manifold's points are instances of class
+ :class:`~sage.manifolds.point.TopManifoldPoint`::
+
+ sage: isinstance(p, sage.manifolds.point.TopManifoldPoint)
+ True
+
+ Manifolds are unique, as long as they are created with the same arguments::
+
+ sage: M is TopManifold(4, 'M', start_index=1)
+ True
+ sage: M is TopManifold(4, 'M')
+ False
+ sage: M is TopManifold(4, 'M', latex_name='M', start_index=1)
+ False
+
+ Since an open subset of a topological manifold `M` is itself a topological
+ manifold, open subsets of `M` are instances of the class
+ :class:`TopManifold`::
+
+ sage: U = M.open_subset('U'); U
+ Open subset U of the 4-dimensional topological manifold M
+ sage: isinstance(U, TopManifold)
+ True
+ sage: U.base_field() == M.base_field()
+ True
+ sage: dim(U) == dim(M)
+ True
+
+ The manifold passes all the tests of the test suite relative to the
+ category of Sets::
+
+ sage: TestSuite(M).run()
+
+ """
+ def __init__(self, n, name, latex_name=None, field='real', start_index=0,
+ category=None, ambient_manifold=None):
+ r"""
+ Construct a topological manifold.
+
+ TESTS::
+
+ sage: M = TopManifold(3, 'M', latex_name=r'\mathbb{M}', start_index=1)
+ sage: M
+ 3-dimensional topological manifold M
+ sage: latex(M)
+ \mathbb{M}
+ sage: dim(M)
+ 3
+ sage: X.<x,y,z> = M.chart()
+ sage: TestSuite(M).run()
+
+ """
+ # Initialization of the attributes _dim, _field and _start_index:
+ from sage.rings.integer import Integer
+ if not isinstance(n, (int, Integer)):
+ raise TypeError("the manifold dimension must be an integer")
+ if n<1:
+ raise ValueError("the manifold dimension must be strictly " +
+ "positive")
+ self._dim = n
+ if field not in ['real', 'complex']:
+ if field not in Fields():
+ raise TypeError("the argument 'field' must be a field")
+ self._field = field
+ if not isinstance(start_index, (int, Integer)):
+ raise TypeError("the starting index must be an integer")
+ self._sindex = start_index
+ if category is None:
+ category = Sets()
+ #*# After #18175, this should become
+ # category = Manifolds()
+ if ambient_manifold is None:
+ ambient_manifold = self
+ elif not isinstance(ambient_manifold, TopManifold):
+ raise TypeError("the argument 'ambient_manifold' must be " +
+ "a topological manifold")
+ # Initialization as a subset of the ambient manifold (possibly itself):
+ TopManifoldSubset.__init__(self, ambient_manifold, name,
+ latex_name=latex_name, category=category)
+ self._is_open = True
+ self._open_covers = [[self]] # list of open covers of self
+ self._atlas = [] # list of charts defined on subsets of self
+ self._top_charts = [] # list of charts defined on subsets of self
+ # that are not subcharts of charts on larger subsets
+ self._def_chart = None # default chart
+ self._coord_changes = {} # dictionary of transition maps
+ # List of charts that individually cover self, i.e. whose
+ # domains are self (if non-empty, self is a coordinate domain):
+ self._covering_charts = []
+
+ def _repr_(self):
+ r"""
+ String representation of the manifold.
+
+ TESTS::
+
+ sage: M = TopManifold(3, 'M')
+ sage: M._repr_()
+ '3-dimensional topological manifold M'
+ sage: repr(M) # indirect doctest
+ '3-dimensional topological manifold M'
+ sage: M # indirect doctest
+ 3-dimensional topological manifold M
+ sage: M = TopManifold(3, 'M', field='complex')
+ sage: M._repr_()
+ 'Complex 3-dimensional topological manifold M'
+ sage: M = TopManifold(3, 'M', field=QQ)
+ sage: M._repr_()
+ '3-dimensional topological manifold M over the Rational Field'
+
+ If the manifold is actually an open subset of a larger manifold, the
+ string representation is different::
+
+ sage: U = M.open_subset('U')
+ sage: U._repr_()
+ 'Open subset U of the 3-dimensional topological manifold M over the Rational Field'
+
+ """
+ if self._manifold is self:
+ if self._field == 'real':
+ return "{}-dimensional topological manifold {}".format(
+ self._dim, self._name)
+ elif self._field == 'complex':
+ return "Complex {}-dimensional topological manifold {}".format(
+ self._dim, self._name)
+ return "{}-dimensional topological manifold {} over the {}".format(
+ self._dim, self._name, self._field)
+ else:
+ return "Open subset {} of the {}".format(self._name,
+ self._manifold)
+
+ def _latex_(self):
+ r"""
+ LaTeX representation of the manifold.
+
+ TESTS::
+
+ sage: M = TopManifold(3, 'M')
+ sage: M._latex_()
+ 'M'
+ sage: latex(M)
+ M
+ sage: M = TopManifold(3, 'M', latex_name=r'\mathcal{M}')
+ sage: M._latex_()
+ '\\mathcal{M}'
+ sage: latex(M)
+ \mathcal{M}
+
+ """
+ return self._latex_name
+
+ def _an_element_(self):
+ r"""
+ Construct some point on the manifold.
+
+ EXAMPLES::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: p = M._an_element_(); p
+ Point on the 2-dimensional topological manifold M
+ sage: p.coord()
+ (0, 0)
+ sage: U = M.open_subset('U', coord_def={X: y>1}); U
+ Open subset U of the 2-dimensional topological manifold M
+ sage: p = U._an_element_(); p
+ Point on the 2-dimensional topological manifold M
+ sage: p in U
+ True
+ sage: p.coord()
+ (0, 2)
+ sage: V = U.open_subset('V', coord_def={X.restrict(U): x<-pi})
+ sage: p = V._an_element_(); p
+ Point on the 2-dimensional topological manifold M
+ sage: p in V
+ True
+ sage: p.coord()
+ (-pi - 1, 2)
+
+ """
+ from sage.rings.infinity import Infinity
+ if self._def_chart is None:
+ return self.element_class(self)
+ # Attempt to construct a point in the domain of the default chart
+ chart = self._def_chart
+ if self._field == 'real':
+ coords = []
+ for coord_range in chart._bounds:
+ xmin = coord_range[0][0]
+ xmax = coord_range[1][0]
+ if xmin == -Infinity:
+ if xmax == Infinity:
+ x = 0
+ else:
+ x = xmax - 1
+ else:
+ if xmax == Infinity:
+ x = xmin + 1
+ else:
+ x = (xmin + xmax)/2
+ coords.append(x)
+ else:
+ coords = self._dim*[0]
+ if not chart.valid_coordinates(*coords):
+ # Attempt to construct a point in the domain of other charts
+ if self._field == 'real':
+ for ch in self._atlas:
+ if ch is self._def_chart:
+ continue # since this case has already been attempted
+ coords = []
+ for coord_range in ch._bounds:
+ xmin = coord_range[0][0]
+ xmax = coord_range[1][0]
+ if xmin == -Infinity:
+ if xmax == Infinity:
+ x = 0
+ else:
+ x = xmax - 1
+ else:
+ if xmax == Infinity:
+ x = xmin + 1
+ else:
+ x = (xmin + xmax)/2
+ coords.append(x)
+ if ch.valid_coordinates(*coords):
+ chart = ch
+ break
+ else:
+ # A generic element with specific coordinates could not be
+ # automatically generated, due to too complex cooordinate
+ # conditions. An element without any coordinate set is
+ # returned instead:
+ return self.element_class(self)
+ else:
+ # Case of manifolds over a field different from R
+ for ch in self._atlas:
+ if ch is self._def_chart:
+ continue # since this case has already been attempted
+ if ch.valid_coordinates(*coords):
+ chart = ch
+ break
+ else:
+ return self.element_class(self)
+ # The point is constructed with check_coords=False since the check
+ # has just been performed above:
+ return self.element_class(self, coords=coords, chart=chart,
+ check_coords=False)
+
+ def __contains__(self, point):
+ r"""
+ Check whether a point is contained in the manifold.
+
+ EXAMPLES::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: p = M.point((1,2), chart=X)
+ sage: M.__contains__(p)
+ True
+ sage: p in M # indirect doctest
+ True
+ sage: U = M.open_subset('U', coord_def={X: x>0})
+ sage: U.__contains__(p)
+ True
+ sage: p in U # indirect doctest
+ True
+ sage: V = U.open_subset('V', coord_def={X.restrict(U): y<0})
+ sage: V.__contains__(p)
+ False
+ sage: p in V # indirect doctest
+ False
+
+ """
+ # for efficiency, a quick test first:
+ if point._subset is self:
+ return True
+ if point._subset.is_subset(self):
+ return True
+ for chart in self._atlas:
+ if chart in point._coordinates:
+ if chart.valid_coordinates( *(point._coordinates[chart]) ):
+ return True
+ for chart in point._coordinates:
+ for schart in chart._subcharts:
+ if schart in self._atlas and schart.valid_coordinates(
+ *(point._coordinates[chart]) ):
+ return True
+ return False
+
+ def dimension(self):
+ r"""
+ Return the dimension of the manifold over its base field.
+
+ EXAMPLE::
+
+ sage: M = TopManifold(2, 'M')
+ sage: M.dimension()
+ 2
+
+ A shortcut is ``dim()``::
+
+ sage: M.dim()
+ 2
+
+ The Sage global function ``dim`` can also be used::
+
+ sage: dim(M)
+ 2
+
+ """
+ return self._dim
+
+ dim = dimension
+
+ def base_field(self):
+ r"""
+ Return the field on which the manifolds is defined.
+
+ OUTPUT:
+
+ - a field or one of the strings 'real' and 'complex', since there
+ is no exact representation of the fields `\RR` and `\CC` in Sage
+
+ EXAMPLES::
+
+ sage: M = TopManifold(3, 'M')
+ sage: M.base_field()
+ 'real'
+ sage: M = TopManifold(3, 'M', field='complex')
+ sage: M.base_field()
+ 'complex'
+ sage: M = TopManifold(3, 'M', field=QQ)
+ sage: M.base_field()
+ Rational Field
+
+ """
+ return self._field
+
+ def start_index(self):
+ r"""
+ Return the first value of the index range used on the manifold.
+
+ This is the parameter ``start_index`` passed at the construction of
+ the manifold.
+
+ OUTPUT:
+
+ - the integer `i_0` such that all indices of indexed objects on the
+ manifold range from `i_0` to `i_0 + n - 1`, where `n` is the
+ manifold's dimension.
+
+ EXAMPLES::
+
+ sage: M = TopManifold(3, 'M')
+ sage: M.start_index()
+ 0
+ sage: M = TopManifold(3, 'M', start_index=1)
+ sage: M.start_index()
+ 1
+
+ """
+ return self._sindex
+
+ def irange(self, start=None):
+ r"""
+ Single index generator.
+
+ INPUT:
+
+ - ``start`` -- (default: ``None``) initial value `i_0` of the index; if
+ none is provided, the value returned by :meth:`start_index()` is
+ assumed.
+
+ OUTPUT:
+
+ - an iterable index, starting from `i_0` and ending at
+ `i_0 + n - 1`, where `n` is the manifold's dimension.
+
+ EXAMPLES:
+
+ Index range on a 4-dimensional manifold::
+
+ sage: M = TopManifold(4, 'M')
+ sage: for i in M.irange():
+ ... print i,
+ ...
+ 0 1 2 3
+ sage: for i in M.irange(2):
+ ... print i,
+ ...
+ 2 3
+ sage: list(M.irange())
+ [0, 1, 2, 3]
+
+ Index range on a 4-dimensional manifold with starting index=1::
+
+ sage: M = TopManifold(4, 'M', start_index=1)
+ sage: for i in M.irange():
+ ... print i,
+ ...
+ 1 2 3 4
+ sage: for i in M.irange(2):
+ ... print i,
+ ...
+ 2 3 4
+
+ In general, one has always::
+
+ sage: M.irange().next() == M.start_index()
+ True
+
+ """
+ si = self._sindex
+ imax = self._dim + si
+ if start is None:
+ i = si
+ else:
+ i = start
+ while i < imax:
+ yield i
+ i += 1
+
+ def index_generator(self, nb_indices):
+ r"""
+ Generator of index series.
+
+ INPUT:
+
+ - ``nb_indices`` -- number of indices in a series
+
+ OUTPUT:
+
+ - an iterable index series for a generic component with the specified
+ number of indices
+
+ EXAMPLES:
+
+ Indices on a 2-dimensional manifold::
+
+ sage: M = TopManifold(2, 'M', start_index=1)
+ sage: for ind in M.index_generator(2):
+ ... print ind
+ ...
+ (1, 1)
+ (1, 2)
+ (2, 1)
+ (2, 2)
+
+ Loops can be nested::
+
+ sage: for ind1 in M.index_generator(2):
+ ... print ind1, " : ",
+ ... for ind2 in M.index_generator(2):
+ ... print ind2,
+ ... print ""
+ ...
+ (1, 1) : (1, 1) (1, 2) (2, 1) (2, 2)
+ (1, 2) : (1, 1) (1, 2) (2, 1) (2, 2)
+ (2, 1) : (1, 1) (1, 2) (2, 1) (2, 2)
+ (2, 2) : (1, 1) (1, 2) (2, 1) (2, 2)
+
+ """
+ si = self._sindex
+ imax = self._dim - 1 + si
+ ind = [si for k in range(nb_indices)]
+ ind_end = [si for k in range(nb_indices)]
+ ind_end[0] = imax+1
+ while ind != ind_end:
+ yield tuple(ind)
+ ret = 1
+ for pos in range(nb_indices-1,-1,-1):
+ if ind[pos] != imax:
+ ind[pos] += ret
+ ret = 0
+ elif ret == 1:
+ if pos == 0:
+ ind[pos] = imax + 1 # end point reached
+ else:
+ ind[pos] = si
+ ret = 1
+
+ def atlas(self):
+ r"""
+ Return the list of charts that have been defined on the manifold.
+
+ EXAMPLES:
+
+ Charts on subsets of `\RR^2`::
+
+ sage: M = TopManifold(2, 'R^2')
+ sage: c_cart.<x,y> = M.chart() # Cartesian coordinates on R^2
+ sage: M.atlas()
+ [Chart (R^2, (x, y))]
+ sage: U = M.open_subset('U', coord_def={c_cart: (y!=0,x<0)}) # U = R^2 \ half line {y=0,x>=0}
+ sage: U.atlas()
+ [Chart (U, (x, y))]
+ sage: M.atlas()
+ [Chart (R^2, (x, y)), Chart (U, (x, y))]
+ sage: c_spher.<r, ph> = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi') # spherical (polar) coordinates on U
+ sage: U.atlas()
+ [Chart (U, (x, y)), Chart (U, (r, ph))]
+ sage: M.atlas()
+ [Chart (R^2, (x, y)), Chart (U, (x, y)), Chart (U, (r, ph))]
+
+ """
+ return self._atlas
+
+ def top_charts(self):
+ r"""
+ Return the list of charts defined on subsets of the current manifold
+ that are not subcharts of charts on larger subsets.
+
+ OUTPUT:
+
+ - list of charts defined on open subsets of the manifold but not on
+ larger subsets
+
+ EXAMPLES:
+
+ Charts on a 2-dimensional manifold::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: U = M.open_subset('U', coord_def={X: x>0})
+ sage: Y.<u,v> = U.chart()
+ sage: M.top_charts()
+ [Chart (M, (x, y)), Chart (U, (u, v))]
+
+ Note that the (user) atlas contains one more chart: (U, (x,y)), which
+ is not a "top" chart::
+
+ sage: M.atlas()
+ [Chart (M, (x, y)), Chart (U, (x, y)), Chart (U, (u, v))]
+
+ """
+ return self._top_charts
+
+ def default_chart(self):
+ r"""
+ Return the default chart defined on the manifold.
+
+ Unless changed via :meth:`set_default_chart`, the *default chart*
+ is the first one defined on a subset of the manifold (possibly itself).
+
+ OUTPUT:
+
+ - instance of :class:`~sage.manifolds.chart.Chart`
+ representing the default chart.
+
+ EXAMPLES:
+
+ Default chart on a 2-dimensional manifold and on some subsets::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: M.chart('x y')
+ Chart (M, (x, y))
+ sage: M.chart('u v')
+ Chart (M, (u, v))
+ sage: M.default_chart()
+ Chart (M, (x, y))
+ sage: A = M.open_subset('A')
+ sage: A.chart('t z')
+ Chart (A, (t, z))
+ sage: A.default_chart()
+ Chart (A, (t, z))
+
+ """
+ return self._def_chart
+
+ def set_default_chart(self, chart):
+ r"""
+ Changing the default chart on ``self``.
+
+ INPUT:
+
+ - ``chart`` -- a chart (must be defined on some subset ``self``)
+
+ EXAMPLES:
+
+ Charts on a 2-dimensional manifold::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: c_xy.<x,y> = M.chart()
+ sage: c_uv.<u,v> = M.chart()
+ sage: M.default_chart()
+ Chart (M, (x, y))
+ sage: M.set_default_chart(c_uv)
+ sage: M.default_chart()
+ Chart (M, (u, v))
+
+ """
+ from chart import Chart
+ if not isinstance(chart, Chart):
+ raise TypeError("{} is not a chart".format(chart))
+ if chart._domain is not self:
+ if self.is_manifestly_coordinate_domain():
+ raise TypeError("the chart domain must coincide with " +
+ "the {}".format(self))
+ if chart not in self._atlas:
+ raise ValueError("the chart must be defined on the " +
+ "{}".format(self))
+ self._def_chart = chart
+
+ def coord_change(self, chart1, chart2):
+ r"""
+ Return the change of coordinates (transition map) between two charts
+ defined on the manifold.
+
+ The change of coordinates must have been defined previously, for
+ instance by the method
+ :meth:`~sage.manifolds.chart.Chart.transition_map`.
+
+ INPUT:
+
+ - ``chart1`` -- chart 1
+ - ``chart2`` -- chart 2
+
+ OUTPUT:
+
+ - instance of :class:`~sage.manifolds.chart.CoordChange`
+ representing the transition map from chart 1 to chart 2
+
+ EXAMPLES:
+
+ Change of coordinates on a 2-dimensional manifold::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: c_xy.<x,y> = M.chart()
+ sage: c_uv.<u,v> = M.chart()
+ sage: c_xy.transition_map(c_uv, (x+y, x-y)) # defines the coordinate change
+ Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))
+ sage: M.coord_change(c_xy, c_uv) # returns the coordinate change defined above
+ Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))
+
+ """
+ if (chart1, chart2) not in self._coord_changes:
+ raise TypeError("the change of coordinates from " +
+ "{} to {}".format(chart1, chart2) + " has not " +
+ "been defined on the {}".format(self))
+ return self._coord_changes[(chart1, chart2)]
+
+ def coord_changes(self):
+ r"""
+ Return the changes of coordinates (transition maps) defined on
+ subsets of the manifold.
+
+ OUTPUT:
+
+ - dictionary of changes of coordinates, with pairs of charts as keys
+
+ EXAMPLES:
+
+ Various changes of coordinates on a 2-dimensional manifold::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: c_xy.<x,y> = M.chart()
+ sage: c_uv.<u,v> = M.chart()
+ sage: xy_to_uv = c_xy.transition_map(c_uv, [x+y, x-y])
+ sage: M.coord_changes()
+ {(Chart (M, (x, y)),
+ Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))}
+ sage: uv_to_xy = xy_to_uv.inverse()
+ sage: M.coord_changes() # random (dictionary output)
+ {(Chart (M, (u, v)),
+ Chart (M, (x, y))): Change of coordinates from Chart (M, (u, v)) to Chart (M, (x, y)),
+ (Chart (M, (x, y)),
+ Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))}
+ sage: c_rs.<r,s> = M.chart()
+ sage: uv_to_rs = c_uv.transition_map(c_rs, [-u+2*v, 3*u-v])
+ sage: M.coord_changes() # random (dictionary output)
+ {(Chart (M, (u, v)),
+ Chart (M, (r, s))): Change of coordinates from Chart (M, (u, v)) to Chart (M, (r, s)),
+ (Chart (M, (u, v)),
+ Chart (M, (x, y))): Change of coordinates from Chart (M, (u, v)) to Chart (M, (x, y)),
+ (Chart (M, (x, y)),
+ Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))}
+ sage: xy_to_rs = uv_to_rs * xy_to_uv
+ sage: M.coord_changes() # random (dictionary output)
+ {(Chart (M, (u, v)),
+ Chart (M, (r, s))): Change of coordinates from Chart (M, (u, v)) to Chart (M, (r, s)),
+ (Chart (M, (u, v)),
+ Chart (M, (x, y))): Change of coordinates from Chart (M, (u, v)) to Chart (M, (x, y)),
+ (Chart (M, (x, y)),
+ Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v)),
+ (Chart (M, (x, y)),
+ Chart (M, (r, s))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (r, s))}
+
+ """
+ return self._coord_changes
+
+ def is_manifestly_coordinate_domain(self):
+ r"""
+ Return ``True`` if the manifold is known to be the domain of some
+ coordinate chart and ``False`` otherwise.
+
+ If ``False`` is returned, either the manifold cannot be the domain of
+ some coordinate chart or no such chart has been declared yet.
+
+ EXAMPLES::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: U = M.open_subset('U')
+ sage: X.<x,y> = U.chart()
+ sage: U.is_manifestly_coordinate_domain()
+ True
+ sage: M.is_manifestly_coordinate_domain()
+ False
+ sage: Y.<u,v> = M.chart()
+ sage: M.is_manifestly_coordinate_domain()
+ True
+
+ """
+ return not self._covering_charts == []
+
+ def open_subset(self, name, latex_name=None, coord_def={}):
+ r"""
+ Create an open subset of the manifold.
+
+ An open subset is a set that is (i) included in the manifold and (ii)
+ open with respect to the manifold's topology. It is a topological
+ manifold by itself. Hence the returned object is an instance of
+ :class:`TopManifold`.
+
+ INPUT:
+
+ - ``name`` -- name given to the open subset
+ - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the
+ subset; if none is provided, it is set to ``name``
+ - ``coord_def`` -- (default: {}) definition of the subset in
+ terms of coordinates; ``coord_def`` must a be dictionary with keys
+ charts on the manifold and values the symbolic expressions formed by
+ the coordinates to define the subset.
+
+ OUTPUT:
+
+ - the open subset, as an instance of :class:`TopManifold`.
+
+ EXAMPLES:
+
+ Creating an open subset of a 2-dimensional manifold::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: A = M.open_subset('A'); A
+ Open subset A of the 2-dimensional topological manifold M
+
+ As an open subset of a topological manifold, ``A`` is itself a
+ topological manifold, on the same topological field and of the same
+ dimension as ``M``::
+
+ sage: isinstance(A, TopManifold)
+ True
+ sage: A.base_field() == M.base_field()
+ True
+ sage: dim(A) == dim(M)
+ True
+
+ Creating an open subset of ``A``::
+
+ sage: B = A.open_subset('B'); B
+ Open subset B of the 2-dimensional topological manifold M
+
+ We have then::
+
+ sage: A.subsets() # random (set output)
+ {Open subset B of the 2-dimensional topological manifold M,
+ Open subset A of the 2-dimensional topological manifold M}
+ sage: B.is_subset(A)
+ True
+ sage: B.is_subset(M)
+ True
+
+ Defining an open subset by some coordinate restrictions: the open
+ unit disk in `\RR^2`::
+
+ sage: M = TopManifold(2, 'R^2')
+ sage: c_cart.<x,y> = M.chart() # Cartesian coordinates on R^2
+ sage: U = M.open_subset('U', coord_def={c_cart: x^2+y^2<1}); U
+ Open subset U of the 2-dimensional topological manifold R^2
+
+ Since the argument ``coord_def`` has been set, ``U`` is automatically
+ provided with a chart, which is the restriction of the Cartesian one
+ to ``U``::
+
+ sage: U.atlas()
+ [Chart (U, (x, y))]
+
+ Therefore, one can immediately check whether a point belongs to U::
+
+ sage: M.point((0,0)) in U
+ True
+ sage: M.point((1/2,1/3)) in U
+ True
+ sage: M.point((1,2)) in U
+ False
+
+ """
+ resu = TopManifold(self._dim, name, latex_name=latex_name,
+ field=self._field, start_index=self._sindex,
+ category=self.category(),
+ ambient_manifold=self._manifold)
+ #!# NB: the above could have been
+ # resu = type(self).__base__(...) instead of resu = TopManifold(...)
+ # to allow for open_subset() of derived classes to call first this
+ # version,
+ # but, because of the category framework, it could NOT have been
+ # resu = self.__class__(...)
+ # cf. the discussion in
+ # https://groups.google.com/forum/#!topic/sage-devel/jHlFxhMDf3Y
+ resu._supersets.update(self._supersets)
+ for sd in self._supersets:
+ sd._subsets.add(resu)
+ self._top_subsets.add(resu)
+ # Charts on the result from the coordinate definition:
+ for chart, restrictions in coord_def.iteritems():
+ if chart not in self._atlas:
+ raise ValueError("the {} does not belong to ".format(chart) +
+ "the atlas of {}".format(self))
+ chart.restrict(resu, restrictions)
+ # Transition maps on the result inferred from those of self:
+ for chart1 in coord_def:
+ for chart2 in coord_def:
+ if chart2 != chart1:
+ if (chart1, chart2) in self._coord_changes:
+ self._coord_changes[(chart1, chart2)].restrict(resu)
+ return resu
+
+ def chart(self, coordinates='', names=None):
+ r"""
+ Define a chart, the domain of which is the manifold.
+
+ A *chart* is a pair `(U,\varphi)`, where `U` is the current manifold
+ and `\varphi: U \rightarrow V \subset K^n`
+ is a homeomorphism from `U` to an open subset `V` of `K^n`, `K` being
+ the field on which the manifold is defined.
+
+ The components `(x^1,\ldots,x^n)` of `\varphi`, defined by
+ `\varphi(p) = (x^1(p),\ldots,x^n(p))\in K^n` for any point `p\in U`,
+ are called the *coordinates* of the chart `(U,\varphi)`.
+
+ See :class:`~sage.manifolds.chart.Chart` for a complete
+ documentation.
+
+ INPUT:
+
+ - ``coordinates`` -- (default: '' (empty string)) single string
+ defining the coordinate symbols and ranges: the coordinates are
+ separated by ' ' (space) and each coordinate has at most three fields,
+ separated by ':':
+
+ 1. The coordinate symbol (a letter or a few letters)
+ 2. (optional, only for manifolds over `\RR`) The interval `I`
+ defining the coordinate range: if not
+ provided, the coordinate is assumed to span all `\RR`; otherwise
+ `I` must be provided in the form (a,b) (or equivalently ]a,b[)
+ The bounds a and b can be +/-Infinity, Inf, infinity, inf or oo.
+ For *singular* coordinates, non-open intervals such as [a,b] and
+ (a,b] (or equivalently ]a,b]) are allowed.
+ Note that the interval declaration must not contain any space
+ character.
+ 3. (optional) The LaTeX spelling of the coordinate; if not provided
+ the coordinate symbol given in the first field will be used.
+
+ The order of the fields 2 and 3 does not matter and each of them can
+ be omitted.
+ If it contains any LaTeX expression, the string ``coordinates`` must
+ be declared with the prefix 'r' (for "raw") to allow for a proper
+ treatment of the backslash character (see examples below).
+ If no interval range and no LaTeX spelling is to be provided for any
+ coordinate, the argument ``coordinates`` can be omitted when the
+ shortcut operator ``<,>`` is used via Sage preparser (see examples
+ below)
+ - ``names`` -- (default: ``None``) unused argument, except if
+ ``coordinates`` is not provided; it must then be a tuple containing
+ the coordinate symbols (this is guaranteed if the shortcut operator
+ ``<,>`` is used).
+
+ OUTPUT:
+
+ - the created chart, as an instance of
+ :class:`~sage.manifolds.chart.Chart` or of the subclass
+ :class:`~sage.manifolds.chart.RealChart` for manifolds over `\RR`.
+
+ EXAMPLES:
+
+ Chart on a 2-dimensional manifold::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: U = M.open_subset('U')
+ sage: X = U.chart('x y'); X
+ Chart (U, (x, y))
+ sage: X[0]
+ x
+ sage: X[1]
+ y
+ sage: X[:]
+ (x, y)
+
+ The declared coordinates are not known at the global level::
+
+ sage: y
+ Traceback (most recent call last):
+ ...
+ NameError: name 'y' is not defined
+
+ They can be recovered by the operator ``[:]`` applied to the chart::
+
+ sage: (x, y) = X[:]
+ sage: y
+ y
+ sage: type(y)
+ <type 'sage.symbolic.expression.Expression'>
+
+ But a shorter way to proceed is to use the operator ``<,>`` in the
+ left-hand side of the chart declaration (there is then no need to
+ pass the string 'x y' to chart())::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: U = M.open_subset('U')
+ sage: X.<x,y> = U.chart(); X
+ Chart (U, (x, y))
+
+ Indeed, the declared coordinates are then known at the global level::
+
+ sage: y
+ y
+ sage: (x,y) == X[:]
+ True
+
+ Actually the instruction ``X.<x,y> = U.chart()`` is
+ equivalent to the combination of the two instructions
+ ``X = U.chart('x y')`` and ``(x,y) = X[:]``.
+
+ See the documentation of class
+ :class:`~sage.manifolds.chart.Chart` for more examples,
+ especially regarding the coordinates ranges and restrictions.
+
+ """
+ from sage.manifolds.chart import Chart, RealChart
+ if self._field == 'real':
+ return RealChart(self, coordinates=coordinates, names=names)
+ return Chart(self, coordinates=coordinates, names=names)
diff --git a/src/sage/manifolds/point.py b/src/sage/manifolds/point.py
new file mode 100644
index 0000000..3e4ce31
--- /dev/null
+++ b/src/sage/manifolds/point.py
@@ -0,0 +1,702 @@
+r"""
+Points of topological manifolds
+
+The class :class:`TopManifoldPoint` implements points of a
+topological manifold.
+A :class:`TopManifoldPoint` object can have coordinates in
+various charts defined on the manifold. Two points are declared equal if they
+have the same coordinates in the same chart.
+
+AUTHORS:
+
+- Eric Gourgoulhon, Michal Bejger (2013-2015) : initial version
+
+REFERENCES:
+
+- J.M. Lee : *Introduction to Topological Manifolds*, 2nd ed., Springer (New
+ York) (2011)
+- J.M. Lee : *Introduction to Smooth Manifolds*, 2nd ed., Springer (New York,
+ 2013)
+
+EXAMPLES:
+
+Defining a point in `\RR^3` by its spherical coordinates::
+
+ sage: M = TopManifold(3, 'R^3')
+ sage: U = M.open_subset('U') # the complement of the half-plane (y=0, x>=0)
+ sage: c_spher.<r,th,ph> = U.chart(r'r:(0,+oo) th:(0,pi):\theta ph:(0,2*pi):\phi')
+ sage: p = U((1, pi/2, pi), name='P') # coordinates in U's default chart (c_spher)
+ sage: p
+ Point P on the 3-dimensional topological manifold R^3
+ sage: latex(p)
+ P
+ sage: p in U
+ True
+ sage: p.parent()
+ 3-dimensional topological manifold R^3
+ sage: c_spher(p)
+ (1, 1/2*pi, pi)
+ sage: p.coord(c_spher) # equivalent to above
+ (1, 1/2*pi, pi)
+
+Computing the point's coordinates in a new chart::
+
+ sage: c_cart.<x,y,z> = U.chart() # Cartesian coordinates on U
+ sage: spher_to_cart = c_spher.transition_map(c_cart,
+ ....: [r*sin(th)*cos(ph), r*sin(th)*sin(ph), r*cos(th)])
+ sage: c_cart(p) # evaluate P's Cartesian coordinates
+ (-1, 0, 0)
+
+Points can be compared::
+
+ sage: p1 = U((1, pi/2, pi))
+ sage: p == p1
+ True
+ sage: q = U((1,2,3), chart=c_cart, name='Q') # point defined by its Cartesian coordinates
+ sage: p == q
+ False
+
+"""
+
+#*****************************************************************************
+# Copyright (C) 2015 Eric Gourgoulhon <eric.gourgoulhon@obspm.fr>
+# Copyright (C) 2015 Michal Bejger <bejger@camk.edu.pl>
+#
+# Distributed under the terms of the GNU General Public License (GPL)
+# as published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+# http://www.gnu.org/licenses/
+#*****************************************************************************
+
+from sage.structure.element import Element
+
+class TopManifoldPoint(Element):
+ r"""
+ Point of a topological manifold.
+
+ This is a Sage *element* class, the corresponding *parent* class being
+ :class:`~sage.manifolds.manifold.TopManifold`.
+
+ INPUT:
+
+ - ``subset`` -- the manifold subset to which the point belongs (can be
+ the entire manifold)
+ - ``coords`` -- (default: ``None``) the point coordinates (as a tuple or a
+ list) in the chart ``chart``
+ - ``chart`` -- (default: ``None``) chart in which the coordinates are given;
+ if ``None``, the coordinates are assumed to refer to the subset's
+ default chart
+ - ``name`` -- (default: ``None``) name given to the point
+ - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the point;
+ if ``None``, the LaTeX symbol is set to ``name``
+ - ``check_coords`` -- (default: ``True``) determines whether ``coords`` are
+ valid coordinates for the chart ``chart``; for symbolic coordinates, it
+ is recommended to set ``check_coords`` to ``False``.
+
+ EXAMPLES:
+
+ A point on a 2-dimensional manifold::
+
+ sage: M = TopManifold(2, 'M')
+ sage: c_xy.<x,y> = M.chart()
+ sage: (a, b) = var('a b') # generic coordinates for the point
+ sage: p = M.point((a, b), name='P'); p
+ Point P on the 2-dimensional topological manifold M
+ sage: p.coord() # coordinates of P in the subset's default chart
+ (a, b)
+
+ Since points are Sage *elements*, the (facade) *parent* of which being the
+ subset on which they are defined, it is equivalent to write::
+
+ sage: p = M((a, b), name='P'); p
+ Point P on the 2-dimensional topological manifold M
+
+ A point is an element of the manifold subset in which it has been defined::
+
+ sage: p in M
+ True
+ sage: p.parent()
+ 2-dimensional topological manifold M
+ sage: U = M.open_subset('U', coord_def={c_xy: x>0})
+ sage: q = U.point((2,1), name='q')
+ sage: q in U
+ True
+ sage: q in M
+ True
+
+ Note that the parent of a point is always the manifold, not the subset
+ in which it has been defined (the latter being returned by the method
+ :meth:`containing_set`)::
+
+ sage: q.parent()
+ 2-dimensional topological manifold M
+ sage: q.containing_set()
+ Open subset U of the 2-dimensional topological manifold M
+
+ By default, the LaTeX symbol of the point is deduced from its name::
+
+ sage: latex(p)
+ P
+
+ But it can be set to any value::
+
+ sage: p = M.point((a, b), name='P', latex_name=r'\mathcal{P}')
+ sage: latex(p)
+ \mathcal{P}
+
+ Points can be drawn in 2D or 3D graphics thanks to the method :meth:`plot`.
+
+ """
+ def __init__(self, subset, coords=None, chart=None, name=None,
+ latex_name=None, check_coords=True):
+ r"""
+ Construct a manifold point.
+
+ TESTS::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: p = M((2,3), name='p'); p
+ Point p on the 2-dimensional topological manifold M
+ sage: TestSuite(p).run()
+ sage: U = M.open_subset('U', coord_def={X: x<0})
+ sage: q = U((-1,2), name='q'); q
+ Point q on the 2-dimensional topological manifold M
+ sage: TestSuite(q).run()
+
+ """
+ Element.__init__(self, subset._manifold)
+ self._manifold = subset._manifold
+ self._subset = subset
+ self._coordinates = {} # dictionary of the point coordinates in various
+ # charts, with the charts as keys
+ if coords is not None:
+ if len(coords) != self._manifold._dim:
+ raise ValueError("the number of coordinates must be equal " +
+ "to the manifold's dimension")
+ if chart is None:
+ chart = self._subset._def_chart
+ elif self._subset._is_open:
+ if chart not in self._subset._atlas:
+ raise ValueError("the {}".format(chart) + " has not " +
+ "been defined on the {}".format(self._subset))
+ if check_coords:
+ if not chart.valid_coordinates(*coords):
+ raise ValueError("the coordinates {}".format(coords) +
+ " are not valid on the {}".format(chart))
+ for schart in chart._supercharts:
+ self._coordinates[schart] = tuple(coords)
+ for schart in chart._subcharts:
+ if schart != chart:
+ if schart.valid_coordinates(*coords):
+ self._coordinates[schart] = tuple(coords)
+ self._name = name
+ if latex_name is None:
+ self._latex_name = self._name
+ else:
+ self._latex_name = latex_name
+
+ def _repr_(self):
+ r"""
+ Return a string representation of the point.
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: p = M((2,-3))
+ sage: p._repr_()
+ 'Point on the 2-dimensional topological manifold M'
+ sage: p = M((2,-3), name='p')
+ sage: p._repr_()
+ 'Point p on the 2-dimensional topological manifold M'
+ sage: repr(p) # indirect doctest
+ 'Point p on the 2-dimensional topological manifold M'
+
+ """
+ description = "Point"
+ if self._name is not None:
+ description += " " + self._name
+ description += " on the {}".format(self._manifold)
+ return description
+
+ def _latex_(self):
+ r"""
+ Return a LaTeX representation of the point.
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: p = M((2,-3))
+ sage: p._latex_()
+ '\\mbox{Point on the 2-dimensional topological manifold M}'
+ sage: p = M((2,-3), name='p')
+ sage: p._latex_()
+ 'p'
+ sage: p = M((2,-3), name='p', latex_name=r'\mathcal{P}')
+ sage: p._latex_()
+ '\\mathcal{P}'
+ sage: latex(p) # indirect doctest
+ \mathcal{P}
+
+ """
+ if self._latex_name is None:
+ return r'\mbox{' + str(self) + r'}'
+ else:
+ return self._latex_name
+
+ def containing_set(self):
+ r"""
+ Return a manifold subset that contains the point.
+
+ A priori, this method returns the manifold subset (possibly the
+ manifold itself) in which the point has been defined.
+
+ OUTPUT:
+
+ - an instance of
+ :class:`~sage.manifolds.subset.TopManifoldSubset`
+
+ EXAMPLES:
+
+ Points on a 2-dimensional manifold::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: p = M.point((1,3), name='p'); p
+ Point p on the 2-dimensional topological manifold M
+ sage: p.containing_set()
+ 2-dimensional topological manifold M
+ sage: U = M.open_subset('U', coord_def={X: x>0})
+ sage: q = U.point((2,1), name='q'); q
+ Point q on the 2-dimensional topological manifold M
+ sage: q.containing_set()
+ Open subset U of the 2-dimensional topological manifold M
+
+ Note that in the present case, the containing set is tighter than the
+ parent, which is always the manifold::
+
+ sage: q.parent()
+ 2-dimensional topological manifold M
+ sage: q.containing_set().is_subset(q.parent())
+ True
+ sage: q.containing_set() != q.parent()
+ True
+
+ """
+ return self._subset
+
+ def coord(self, chart=None, old_chart=None):
+ r"""
+ Return the point coordinates in the specified chart.
+
+ If these coordinates are not already known, they are computed from
+ known ones by means of change-of-chart formulas.
+
+ An equivalent way to get the coordinates of a point is to let the
+ chart acting of the point, i.e. if ``X`` is a chart and ``p`` a
+ point, one has ``p.coord(chart=X) == X(p)``.
+
+ INPUT:
+
+ - ``chart`` -- (default: ``None``) chart in which the coordinates are
+ given; if none is provided, the coordinates are assumed to refer to
+ the subset's default chart
+ - ``old_chart`` -- (default: ``None``) chart from which the coordinates
+ in ``chart`` are to be computed. If ``None``, a chart in which the
+ point's coordinates are already known will be picked, privileging the
+ subset's default chart.
+
+ EXAMPLES:
+
+ Spherical coordinates of a point on `\RR^3`::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(3, 'M') # the part of R^3 covered by spherical coordinates
+ sage: c_spher.<r,th,ph> = M.chart(r'r:(0,+oo) th:(0,pi):\theta ph:(0,2*pi):\phi') # spherical coordinates
+ sage: p = M.point((1, pi/2, pi))
+ sage: p.coord() # coordinates on the manifold's default chart
+ (1, 1/2*pi, pi)
+ sage: p.coord(c_spher) # with the chart c_spher specified (same result as above since this is the default chart)
+ (1, 1/2*pi, pi)
+
+ An alternative way to get the coordinates is to let the chart act
+ on the point (from the very definition of a chart)::
+
+ sage: c_spher(p)
+ (1, 1/2*pi, pi)
+
+ Computing the Cartesian coordinates from the spherical ones::
+
+ sage: c_cart.<x,y,z> = M.chart() # Cartesian coordinates
+ sage: c_spher.transition_map(c_cart, [r*sin(th)*cos(ph),
+ ....: r*sin(th)*sin(ph), r*cos(th)])
+ Change of coordinates from Chart (M, (r, th, ph)) to Chart (M, (x, y, z))
+ sage: p.coord(c_cart) # the computation is performed by means of the above change of coordinates
+ (-1, 0, 0)
+ sage: p.coord(c_cart) == c_cart(p)
+ True
+
+ Coordinates of a point on a 2-dimensional manifold::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: c_xy.<x,y> = M.chart()
+ sage: (a, b) = var('a b') # generic coordinates for the point
+ sage: p = M.point((a, b), name='P')
+ sage: p.coord() # coordinates of P in the manifold's default chart
+ (a, b)
+
+ Coordinates of P in a new chart::
+
+ sage: c_uv.<u,v> = M.chart()
+ sage: ch_xy_uv = c_xy.transition_map(c_uv, [x-y, x+y])
+ sage: p.coord(c_uv)
+ (a - b, a + b)
+
+ Coordinates of P in a third chart::
+
+ sage: c_wz.<w,z> = M.chart()
+ sage: ch_uv_wz = c_uv.transition_map(c_wz, [u^3, v^3])
+ sage: p.coord(c_wz, old_chart=c_uv)
+ (a^3 - 3*a^2*b + 3*a*b^2 - b^3, a^3 + 3*a^2*b + 3*a*b^2 + b^3)
+
+ Actually, in the present case, it is not necessary to specify
+ old_chart='uv'::
+
+ sage: p.set_coord((a-b, a+b), c_uv) # erases all the coordinates except those in the chart c_uv
+ sage: p._coordinates
+ {Chart (M, (u, v)): (a - b, a + b)}
+ sage: p.coord(c_wz)
+ (a^3 - 3*a^2*b + 3*a*b^2 - b^3, a^3 + 3*a^2*b + 3*a*b^2 + b^3)
+ sage: p._coordinates # random (dictionary output)
+ {Chart (M, (u, v)): (a - b, a + b),
+ Chart (M, (w, z)): (a^3 - 3*a^2*b + 3*a*b^2 - b^3,
+ a^3 + 3*a^2*b + 3*a*b^2 + b^3)}
+
+ """
+ if chart is None:
+ dom = self._subset
+ chart = dom._def_chart
+ def_chart = chart
+ else:
+ dom = chart._domain
+ def_chart = dom._def_chart
+ if self not in dom:
+ raise ValueError("the point does not belong to the domain " +
+ "of {}".format(chart))
+ if chart not in self._coordinates:
+ # Check whether chart corresponds to a superchart of a chart
+ # in which the coordinates are known:
+ for ochart in self._coordinates:
+ if chart in ochart._supercharts or chart in ochart._subcharts:
+ self._coordinates[chart] = self._coordinates[ochart]
+ return self._coordinates[chart]
+ # If this point is reached, some change of coordinates must be
+ # performed
+ if old_chart is not None:
+ s_old_chart = old_chart
+ s_chart = chart
+ else:
+ # A chart must be found as a starting point of the computation
+ # The domain's default chart is privileged:
+ if def_chart in self._coordinates \
+ and (def_chart, chart) in dom._coord_changes:
+ old_chart = def_chart
+ s_old_chart = def_chart
+ s_chart = chart
+ else:
+ for ochart in self._coordinates:
+ for subchart in ochart._subcharts:
+ if (subchart, chart) in dom._coord_changes:
+ old_chart = ochart
+ s_old_chart = subchart
+ s_chart = chart
+ break
+ if old_chart is not None:
+ break
+ if old_chart is None:
+ # Some search involving the subcharts of chart is
+ # performed:
+ for schart in chart._subcharts:
+ for ochart in self._coordinates:
+ for subchart in ochart._subcharts:
+ if (subchart, schart) in dom._coord_changes:
+ old_chart = ochart
+ s_old_chart = subchart
+ s_chart = schart
+ break
+ if old_chart is not None:
+ break
+ if old_chart is not None:
+ break
+ if old_chart is None:
+ raise ValueError("the coordinates of {}".format(self) +
+ " in the {}".format(chart) + " cannot be computed " +
+ "by means of known changes of charts.")
+ else:
+ chcoord = dom._coord_changes[(s_old_chart, s_chart)]
+ self._coordinates[chart] = \
+ chcoord(*self._coordinates[old_chart])
+ return self._coordinates[chart]
+
+ def set_coord(self, coords, chart=None):
+ r"""
+ Sets the point coordinates in the specified chart.
+
+ Coordinates with respect to other charts are deleted, in order to
+ avoid any inconsistency. To keep them, use the method :meth:`add_coord`
+ instead.
+
+ INPUT:
+
+ - ``coords`` -- the point coordinates (as a tuple or a list)
+ - ``chart`` -- (default: ``None``) chart in which the coordinates are
+ given; if none is provided, the coordinates are assumed to refer to
+ the subset's default chart
+
+ EXAMPLES:
+
+ Setting coordinates to a point on a 2-dimensional manifold::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: p = M.point()
+ sage: p.set_coord((2,-3)) # coordinates on the manifold's default chart
+ sage: p.coord()
+ (2, -3)
+ sage: X(p)
+ (2, -3)
+
+ Let us introduce a second chart on the manifold::
+
+ sage: Y.<u,v> = M.chart()
+ sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
+
+ If we set the coordinates of p in the chart Y, those in the chart X
+ are lost::
+
+ sage: Y(p)
+ (-1, 5)
+ sage: p.set_coord(Y(p), chart=Y)
+ sage: p._coordinates
+ {Chart (M, (u, v)): (-1, 5)}
+
+ """
+ self._coordinates.clear()
+ self.add_coord(coords, chart)
+
+ def add_coord(self, coords, chart=None):
+ r"""
+ Adds some coordinates in the specified chart.
+
+ The previous coordinates with respect to other charts are kept. To
+ clear them, use :meth:`set_coord` instead.
+
+ INPUT:
+
+ - ``coords`` -- the point coordinates (as a tuple or a list)
+ - ``chart`` -- (default: ``None``) chart in which the coordinates are
+ given; if none is provided, the coordinates are assumed to refer to
+ the subset's default chart
+
+ .. WARNING::
+
+ If the point has already coordinates in other charts, it
+ is the user's responsibility to make sure that the coordinates
+ to be added are consistent with them.
+
+ EXAMPLES:
+
+ Setting coordinates to a point on a 2-dimensional manifold::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: p = M.point()
+ sage: p.add_coord((2,-3)) # coordinates on the manifold's default chart
+ sage: p.coord()
+ (2, -3)
+ sage: X(p)
+ (2, -3)
+
+ Let us introduce a second chart on the manifold::
+
+ sage: Y.<u,v> = M.chart()
+ sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
+
+ If we add the coordinates of p in the chart Y, those in the chart X
+ are kept::
+
+ sage: p.add_coord((-1,5), chart=Y)
+ sage: p._coordinates # random (dictionary output)
+ {Chart (M, (u, v)): (-1, 5), Chart (M, (x, y)): (2, -3)}
+
+ On the contrary, with the method :meth:`set_coord`, the coordinates
+ in charts different from Y would be lost::
+
+ sage: p.set_coord((-1,5), chart=Y)
+ sage: p._coordinates
+ {Chart (M, (u, v)): (-1, 5)}
+
+ """
+ if len(coords) != self._manifold._dim:
+ raise ValueError("the number of coordinates must be equal to " +
+ "the manifold's dimension.")
+ if chart is None:
+ chart = self._subset._def_chart
+ else:
+ if chart not in self._subset._atlas:
+ raise ValueError("the {}".format(chart) + " has not been " +
+ "defined on the {}".format(self._subset))
+ self._coordinates[chart] = coords
+
+ def __eq__(self, other):
+ r"""
+ Compares the current point with another one.
+
+ EXAMPLES:
+
+ Comparison with coordinates in the same chart::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: p = M((2,-3), chart=X)
+ sage: q = M((2,-3), chart=X)
+ sage: p == q
+ True
+ sage: q = M((-2,-3), chart=X)
+ sage: p == q
+ False
+
+ Comparison with coordinates of other in a subchart::
+
+ sage: U = M.open_subset('U', coord_def={X: x>0})
+ sage: XU = X.restrict(U)
+ sage: q = U((2,-3), chart=XU)
+ sage: p == q and q == p
+ True
+ sage: q = U((1,-3), chart=XU)
+ sage: p == q or q == p
+ False
+
+ Comparison requiring a change of chart::
+
+ sage: Y.<u,v> = U.chart()
+ sage: XU_to_Y = XU.transition_map(Y, (ln(x), x+y))
+ sage: XU_to_Y.inverse()(u,v)
+ (e^u, v - e^u)
+ sage: q = U((ln(2),-1), chart=Y)
+ sage: p == q and q == p
+ True
+ sage: q = U((ln(3),1), chart=Y)
+ sage: p == q or q == p
+ False
+
+ """
+ if not isinstance(other, TopManifoldPoint):
+ return False
+ if other._manifold != self._manifold:
+ return False
+ # Search for a common chart to compare the coordinates
+ common_chart = None
+ # the subset's default chart is privileged:
+ def_chart = self._subset._def_chart
+ if def_chart in self._coordinates and def_chart in other._coordinates:
+ common_chart = def_chart
+ else:
+ for chart in self._coordinates:
+ if chart in other._coordinates:
+ common_chart = chart
+ break
+ if common_chart is None:
+ # At this stage, a commont chart is searched via a coordinate
+ # transformation:
+ for chart in self._coordinates:
+ try:
+ other.coord(chart)
+ common_chart = chart
+ break
+ except ValueError:
+ pass
+ else:
+ # Attempt a coordinate transformation in the reverse way:
+ for chart in other._coordinates:
+ try:
+ self.coord(chart)
+ common_chart = chart
+ break
+ except ValueError:
+ pass
+ if common_chart is None:
+ return False
+ #!# Another option would be:
+ # raise ValueError("no common chart has been found to compare " +
+ # "{} and {}".format(self, other))
+ return self._coordinates[common_chart] == \
+ other._coordinates[common_chart]
+
+ def __ne__(self, other):
+ r"""
+ Non-equality operator.
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: p = M((2,-3), chart=X)
+ sage: q = M((0,1), chart=X)
+ sage: p != q
+ True
+ sage: p != M((2,-3), chart=X)
+ False
+
+ """
+ return not self.__eq__(other)
+
+ def __cmp__(self, other):
+ r"""
+ Old-style (Python 2) comparison operator.
+
+ This is provisory, until migration to Python 3 is achieved.
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: p = M((2,-3), chart=X)
+ sage: q = M((2,-3), chart=X)
+ sage: p.__cmp__(q)
+ 0
+ sage: q = M((0,1), chart=X)
+ sage: p.__cmp__(q)
+ -1
+
+ """
+ if self.__eq__(other):
+ return 0
+ else:
+ return -1
+
+ def __hash__(self):
+ r"""
+ This hash function is set to constant on a given manifold, to fulfill
+ Python's credo:
+ p == q ==> hash(p) == hash(q)
+ This is necessary since p and q may be created in different coordinate
+ systems and nevertheless be equal
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: p = M((2,-3), chart=X)
+ sage: p.__hash__() # random
+ 8791657334475
+ sage: p.__hash__() == hash(M)
+ True
+
+ """
+ return self._manifold.__hash__()
diff --git a/src/sage/manifolds/subset.py b/src/sage/manifolds/subset.py
new file mode 100644
index 0000000..4fb66c8
--- /dev/null
+++ b/src/sage/manifolds/subset.py
@@ -0,0 +1,982 @@
+r"""
+Subsets of topological manifolds
+
+The class :class:`TopManifoldSubset` implements generic subsets of a
+topological manifold. Open subsets are implemented by the class
+:class:`~sage.manifolds.manifold.TopManifold` (since an open subset of a
+manifold is a manifold by itself), which inherits from
+:class:`TopManifoldSubset`.
+
+AUTHORS:
+
+- Eric Gourgoulhon, Michal Bejger (2013-2015): initial version
+
+REFERENCES:
+
+- J.M. Lee : *Introduction to Topological Manifolds*, 2nd ed., Springer (New
+ York) (2011)
+
+EXAMPLES:
+
+Two subsets on a manifold::
+
+ sage: M = TopManifold(2, 'M')
+ sage: a = M.subset('A'); a
+ Subset A of the 2-dimensional topological manifold M
+ sage: b = M.subset('B'); b
+ Subset B of the 2-dimensional topological manifold M
+ sage: M.list_of_subsets()
+ [Subset A of the 2-dimensional topological manifold M,
+ Subset B of the 2-dimensional topological manifold M,
+ 2-dimensional topological manifold M]
+
+The intersection of the two subsets::
+
+ sage: c = a.intersection(b); c
+ Subset A_inter_B of the 2-dimensional topological manifold M
+
+Their union::
+
+ sage: d = a.union(b); d
+ Subset A_union_B of the 2-dimensional topological manifold M
+
+Lists of subsets after the above operations::
+
+ sage: M.list_of_subsets()
+ [Subset A of the 2-dimensional topological manifold M,
+ Subset A_inter_B of the 2-dimensional topological manifold M,
+ Subset A_union_B of the 2-dimensional topological manifold M,
+ Subset B of the 2-dimensional topological manifold M,
+ 2-dimensional topological manifold M]
+ sage: a.list_of_subsets()
+ [Subset A of the 2-dimensional topological manifold M,
+ Subset A_inter_B of the 2-dimensional topological manifold M]
+ sage: c.list_of_subsets()
+ [Subset A_inter_B of the 2-dimensional topological manifold M]
+ sage: d.list_of_subsets()
+ [Subset A of the 2-dimensional topological manifold M,
+ Subset A_inter_B of the 2-dimensional topological manifold M,
+ Subset A_union_B of the 2-dimensional topological manifold M,
+ Subset B of the 2-dimensional topological manifold M]
+
+"""
+#*****************************************************************************
+# Copyright (C) 2015 Eric Gourgoulhon <eric.gourgoulhon@obspm.fr>
+# Copyright (C) 2015 Michal Bejger <bejger@camk.edu.pl>
+#
+# Distributed under the terms of the GNU General Public License (GPL)
+# as published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+# http://www.gnu.org/licenses/
+#*****************************************************************************
+
+from sage.structure.parent import Parent
+from sage.structure.unique_representation import UniqueRepresentation
+from sage.categories.sets_cat import Sets
+from sage.categories.homset import Hom
+from sage.rings.infinity import Infinity
+from sage.manifolds.point import TopManifoldPoint
+
+class TopManifoldSubset(UniqueRepresentation, Parent):
+ r"""
+ Subset of a topological manifold.
+
+ The class :class:`TopManifoldSubset` inherits from the generic Sage class
+ :class:`~sage.structure.parent.Parent` and is declared to belong to
+ the category of facade sets
+ (see :meth:`~sage.categories.sets_cat.Sets.SubcategoryMethods.Facade`).
+ The corresponding element class is
+ :class:`~sage.manifolds.point.TopManifoldPoint`. A subset acts
+ as a facade for the true parent of its points, which is the whole manifold
+ (see example below).
+
+ Note that open subsets are not implemented directly by this class, but
+ by the derived class :class:`~sage.manifolds.manifold.TopManifold` (an
+ open subset of a topological manifold being itself a topological manifold).
+
+ INPUT:
+
+ - ``manifold`` -- topological manifold on which the subset is defined
+ - ``name`` -- string; name (symbol) given to the subset
+ - ``latex_name`` -- (default: ``None``) string: LaTeX symbol to denote the
+ subset; if none is provided, it is set to ``name``
+ - ``category`` -- (default: ``None``) to specify the categeory; if ``None``,
+ the category of sets (:class:`~sage.categories.sets_cat.Sets`) is used
+
+ EXAMPLES:
+
+ A subset of a manifold::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: from sage.manifolds.subset import TopManifoldSubset
+ sage: A = TopManifoldSubset(M, 'A', latex_name=r'\mathcal{A}'); A
+ Subset A of the 2-dimensional topological manifold M
+ sage: latex(A)
+ \mathcal{A}
+ sage: A.is_subset(M)
+ True
+
+ Instead of importing :class:`TopManifoldSubset` in the global namespace,
+ it is recommended to use the method :meth:`subset` to create a new subset::
+
+ sage: B = M.subset('B', latex_name=r'\mathcal{B}'); B
+ Subset B of the 2-dimensional topological manifold M
+ sage: M.list_of_subsets()
+ [Subset A of the 2-dimensional topological manifold M,
+ Subset B of the 2-dimensional topological manifold M,
+ 2-dimensional topological manifold M]
+
+ The manifold is itself a subset::
+
+ sage: isinstance(M, TopManifoldSubset)
+ True
+
+ Instances of :class:`TopManifoldSubset` are Sage's facade sets
+ (see :meth:`~sage.categories.sets_cat.Sets.SubcategoryMethods.Facade`):
+ their elements are manifold points
+ (class :class:`~sage.manifolds.point.TopManifoldPoint`),
+ which have the manifold (and not the subset) as parent::
+
+ sage: isinstance(A, Parent)
+ True
+ sage: A.category()
+ Category of facade sets
+ sage: A.facade_for()
+ (2-dimensional topological manifold M,)
+ sage: p = A.an_element(); p
+ Point on the 2-dimensional topological manifold M
+ sage: p.parent()
+ 2-dimensional topological manifold M
+ sage: p in A
+ True
+ sage: p in M
+ True
+
+ """
+
+ Element = TopManifoldPoint
+
+ def __init__(self, manifold, name, latex_name=None, category=None):
+ r"""
+ Construct a manifold subset.
+
+ TESTS::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: A = M.subset('A'); A
+ Subset A of the 2-dimensional topological manifold M
+
+ """
+ if not isinstance(name, str):
+ raise TypeError("{} is not a string".format(name))
+ if category is None:
+ category = Sets()
+ # Except for the manifold itself, the subsets are facade sets:
+ if self is manifold:
+ Parent.__init__(self, category=category)
+ else:
+ Parent.__init__(self, category=category, facade=manifold)
+ for dom in manifold._subsets:
+ if name == dom._name:
+ raise ValueError("the name '" + name +
+ "' is already used for another " +
+ "subset of the {}".format(manifold))
+ manifold._subsets.add(self)
+ self._manifold = manifold
+ self._name = name
+ if latex_name is None:
+ self._latex_name = self._name
+ else:
+ if not isinstance(latex_name, str):
+ raise TypeError("{} is not a string".format(latex_name))
+ self._latex_name = latex_name
+ self._supersets = set([manifold, self]) # subsets containing self
+ self._subsets = set([self]) # subsets of self
+ self._top_subsets = set([self]) # subsets contained in self but not
+ # in another strict subset of self
+ self._intersections = {} # dict. of intersections with other subsets
+ # (key: subset name)
+ self._unions = {} # dict. of unions with other subsets (key: subset
+ # name)
+ self._open_covers = [] # list of open covers of self
+ self._is_open = False # a priori (may be redifined by subclasses)
+
+ #### Methods required for any Parent in the category of sets:
+
+ def _element_constructor_(self, coords=None, chart=None, name=None,
+ latex_name=None, check_coords=True):
+ r"""
+ Construct a point in the subset from its coordinates in some chart.
+
+ INPUT:
+
+ - ``coords`` -- (default: ``None``) either (i) the point coordinates
+ (as a tuple or a list) in the chart ``chart`` or (ii) another point
+ in the subset
+ - ``chart`` -- (default: ``None``) chart in which the coordinates are
+ given; if none is provided, the coordinates are assumed to refer to
+ the subset's default chart
+ - ``name`` -- (default: ``None``) name given to the point
+ - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the
+ point; if none is provided, the LaTeX symbol is set to ``name``
+ - ``check_coords`` -- (default: ``True``) determines whether ``coords``
+ are valid coordinates for the chart ``chart``; for symbolic
+ coordinates, it is recommended to set ``check_coords`` to ``False``.
+
+ OUTPUT:
+
+ - an instance of :class:`~sage.manifolds.point.TopManifoldPoint`
+ representing a point in the current subset.
+
+ EXAMPLES::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: p = M._element_constructor_(); p
+ Point on the 2-dimensional topological manifold M
+ sage: p = M._element_constructor_((-2,3)); p # coord in the default chart
+ Point on the 2-dimensional topological manifold M
+ sage: X(p)
+ (-2, 3)
+
+ A generic subset has no default chart, so the chart must be explicited::
+
+ sage: A = M.subset('A')
+ sage: p = A._element_constructor_((-2,3), chart=X); p
+ Point on the 2-dimensional topological manifold M
+ sage: X(p)
+ (-2, 3)
+ sage: p.containing_set()
+ Subset A of the 2-dimensional topological manifold M
+ sage: p in A
+ True
+
+ Coordinates in a chart with some coordinate restrictions::
+
+ sage: Y.<u,v> = M.chart('u:(-1,1) v:(-1,1)')
+ sage: p = A._element_constructor_((0,1/2), chart=Y); p
+ Point on the 2-dimensional topological manifold M
+ sage: Y(p)
+ (0, 1/2)
+ sage: p = A._element_constructor_((0,1/2), chart=Y,
+ ....: check_coords=False); p
+ Point on the 2-dimensional topological manifold M
+ sage: Y(p)
+ (0, 1/2)
+ sage: p = A._element_constructor_((3,1/2), chart=Y)
+ Traceback (most recent call last):
+ ...
+ ValueError: the coordinates (3, 1/2) are not valid on the Chart (M, (u, v))
+
+ Specifying the name of the point::
+
+ sage: p = A._element_constructor_((-2,3), chart=X, name='p'); p
+ Point p on the 2-dimensional topological manifold M
+
+ A point as entry::
+
+ sage: q = A._element_constructor_(p); q
+ Point p on the 2-dimensional topological manifold M
+ sage: X(q)
+ (-2, 3)
+
+ """
+ if isinstance(coords, TopManifoldPoint):
+ point = coords # for readability
+ if point._subset is self:
+ return point
+ if point in self:
+ resu = self.element_class(self, name=point._name,
+ latex_name=point._latex_name)
+ for chart, coords in point._coordinates.iteritems():
+ resu._coordinates[chart] = coords
+ return resu
+ else:
+ raise ValueError("the {}".format(point) +
+ " is not in {}".format(self))
+ return self.element_class(self, coords=coords, chart=chart, name=name,
+ latex_name=latex_name, check_coords=check_coords)
+
+ def _an_element_(self):
+ r"""
+ Construct some point in the subset.
+
+ EXAMPLES::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: A = M.subset('A')
+ sage: p = A._an_element_(); p
+ Point on the 2-dimensional topological manifold M
+ sage: p in A
+ True
+
+ """
+ #!# should be improved...
+ return self.element_class(self)
+
+ #### End of methods required for any Parent in the category of sets
+
+ def _repr_(self):
+ r"""
+ String representation of the object.
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M')
+ sage: A = M.subset('A')
+ sage: A._repr_()
+ 'Subset A of the 2-dimensional topological manifold M'
+ sage: repr(A) # indirect doctest
+ 'Subset A of the 2-dimensional topological manifold M'
+
+ """
+ return "Subset {} of the {}".format(self._name, self._manifold)
+
+ def _latex_(self):
+ r"""
+ LaTeX representation of the object.
+
+ TESTS::
+
+ sage: M = TopManifold(2, 'M')
+ sage: A = M.subset('A')
+ sage: A._latex_()
+ 'A'
+ sage: B = A.subset('B', latex_name=r'\mathcal{B}')
+ sage: B._latex_()
+ '\\mathcal{B}'
+ sage: latex(B) # indirect doctest
+ \mathcal{B}
+
+ """
+ return self._latex_name
+
+ def manifold(self):
+ r"""
+ Return the manifold of which the current object is a subset.
+
+ EXAMPLES::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: A = M.subset('A')
+ sage: A.manifold()
+ 2-dimensional topological manifold M
+ sage: B = A.subset('B')
+ sage: B.manifold()
+ 2-dimensional topological manifold M
+ sage: M.manifold() is M
+ True
+
+ """
+ return self._manifold
+
+ def open_covers(self):
+ r"""
+ Return the list of open covers of the current subset.
+
+ If the current subset, `A` say, is a subset of the manifold `M`, an
+ *open cover* of `A` is list (indexed set) `(U_i)_{i\in I}` of
+ open subsets of `M` such that
+
+ .. MATH::
+
+ A \subset \bigcup_{i \in I} U_i
+
+ If `A` is open, we ask that the above inclusion is actually an
+ identity:
+
+ .. MATH::
+
+ A = \bigcup_{i \in I} U_i
+
+ EXAMPLES::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: M.open_covers()
+ [[2-dimensional topological manifold M]]
+ sage: U = M.open_subset('U')
+ sage: U.open_covers()
+ [[Open subset U of the 2-dimensional topological manifold M]]
+ sage: A = U.open_subset('A')
+ sage: B = U.open_subset('B')
+ sage: U.declare_union(A,B)
+ sage: U.open_covers()
+ [[Open subset U of the 2-dimensional topological manifold M],
+ [Open subset A of the 2-dimensional topological manifold M,
+ Open subset B of the 2-dimensional topological manifold M]]
+ sage: V = M.open_subset('V')
+ sage: M.declare_union(U,V)
+ sage: M.open_covers()
+ [[2-dimensional topological manifold M],
+ [Open subset U of the 2-dimensional topological manifold M,
+ Open subset V of the 2-dimensional topological manifold M],
+ [Open subset A of the 2-dimensional topological manifold M,
+ Open subset B of the 2-dimensional topological manifold M,
+ Open subset V of the 2-dimensional topological manifold M]]
+
+ """
+ return self._open_covers
+
+ def subsets(self):
+ r"""
+ Return the set of subsets that have been defined on the current subset.
+
+ OUTPUT:
+
+ - A Python set containing all the subsets that have been defined on
+ the current subset.
+
+ .. NOTE::
+
+ To get the subsets as a list, used the method
+ :meth:`list_of_subsets` instead.
+
+ EXAMPLE:
+
+ Subsets of a 2-dimensional manifold::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: U = M.open_subset('U')
+ sage: V = M.subset('V')
+ sage: M.subsets() # random (set output)
+ {Subset V of the 2-dimensional topological manifold M,
+ 2-dimensional topological manifold M,
+ Open subset U of the 2-dimensional topological manifold M}
+ sage: type(M.subsets())
+ <type 'set'>
+ sage: U in M.subsets()
+ True
+
+ The method :meth:`list_of_subsets` returns a list (sorted
+ alphabetically by the subset names) instead of a set::
+
+ sage: M.list_of_subsets()
+ [2-dimensional topological manifold M,
+ Open subset U of the 2-dimensional topological manifold M,
+ Subset V of the 2-dimensional topological manifold M]
+
+ """
+ return self._subsets
+
+ def list_of_subsets(self):
+ r"""
+ Return the list of subsets that have been defined on the current
+ subset.
+
+ The list is sorted by the alphabetical names of the subsets.
+
+ OUTPUT:
+
+ - A list containing all the subsets that have been defined on
+ the current subset.
+
+ .. NOTE::
+
+ To get the subsets as a Python set, used the method
+ :meth:`subsets` instead.
+
+ EXAMPLE:
+
+ Subsets of a 2-dimensional manifold::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: U = M.open_subset('U')
+ sage: V = M.subset('V')
+ sage: M.list_of_subsets()
+ [2-dimensional topological manifold M,
+ Open subset U of the 2-dimensional topological manifold M,
+ Subset V of the 2-dimensional topological manifold M]
+
+ The method :meth:`subsets` returns a set instead of a list::
+
+ sage: M.subsets() # random (set output)
+ {Subset V of the 2-dimensional topological manifold M,
+ 2-dimensional topological manifold M,
+ Open subset U of the 2-dimensional topological manifold M}
+
+ """
+ return sorted(self._subsets, key = lambda x: x._name)
+
+ def subset(self, name, latex_name=None, is_open=False):
+ r"""
+ Create a subset of the current subset.
+
+ INPUT:
+
+ - ``name`` -- name given to the subset
+ - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the
+ subset; if none is provided, it is set to ``name``
+ - ``is_open`` -- (default: False) if ``True``, the created subset is
+ assumed to be open with respect to the manifold's topology
+
+ OUTPUT:
+
+ - the subset, as an instance of :class:`TopManifoldSubset`, or of
+ the derived class :class:`TopManifold` if ``is_open`` is ``True``.
+
+ EXAMPLES:
+
+ Creating a subset of a manifold::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: a = M.subset('A'); a
+ Subset A of the 2-dimensional topological manifold M
+
+ Creating a subset of A::
+
+ sage: b = a.subset('B', latex_name=r'\mathcal{B}'); b
+ Subset B of the 2-dimensional topological manifold M
+ sage: latex(b)
+ \mathcal{B}
+
+ We have then::
+
+ sage: b.is_subset(a)
+ True
+ sage: b in a.subsets()
+ True
+
+
+ """
+ if is_open:
+ return self.open_subset(name, latex_name=latex_name)
+ res = TopManifoldSubset(self._manifold, name, latex_name=latex_name)
+ res._supersets.update(self._supersets)
+ for sd in self._supersets:
+ sd._subsets.add(res)
+ self._top_subsets.add(res)
+ return res
+
+ def superset(self, name, latex_name=None, is_open=False):
+ r"""
+ Create a superset of the current subset.
+
+ A *superset* is a manifold subset in which the current subset is
+ included.
+
+ INPUT:
+
+ - ``name`` -- name given to the superset
+ - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the
+ superset; if none is provided, it is set to ``name``
+ - ``is_open`` -- (default: False) if ``True``, the created subset is
+ assumed to be open with respect to the manifold's topology
+
+ OUTPUT:
+
+ - the superset, as an instance of :class:`TopManifoldSubset` or of
+ the derived class :class:`TopManifold` if ``is_open`` is ``True``.
+
+ EXAMPLES:
+
+ Creating some superset of a given subset::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: a = M.subset('A')
+ sage: b = a.superset('B'); b
+ Subset B of the 2-dimensional topological manifold M
+ sage: b.list_of_subsets()
+ [Subset A of the 2-dimensional topological manifold M,
+ Subset B of the 2-dimensional topological manifold M]
+ sage: a._supersets # random (set output)
+ {Subset B of the 2-dimensional topological manifold M,
+ Subset A of the 2-dimensional topological manifold M,
+ 2-dimensional topological manifold M}
+
+ The superset of the whole manifold is itself::
+
+ sage: M.superset('SM') is M
+ True
+
+ Two supersets of a given subset are a priori different::
+
+ sage: c = a.superset('C')
+ sage: c == b
+ False
+
+ """
+ if self is self._manifold:
+ return self
+ if is_open:
+ res = self._manifold.open_subset(name, latex_name=latex_name)
+ else:
+ res = TopManifoldSubset(self._manifold, name,
+ latex_name=latex_name)
+ res._subsets.update(self._subsets)
+ for sd in self._subsets:
+ sd._supersets.add(res)
+ if is_open and self._is_open:
+ res._atlas = list(self._atlas)
+ res._top_charts = list(self._top_charts)
+ res._coord_changes = dict(self._coord_changes)
+ res._def_chart = self._def_chart
+ return res
+
+ def intersection(self, other, name=None, latex_name=None):
+ r"""
+ Return the intersection of the current subset with another subset.
+
+ INPUT:
+
+ - ``other`` -- another subset of the same manifold
+ - ``name`` -- (default: ``None``) name given to the intersection in the
+ case the latter has to be created; the default is
+ ``self._name`` inter ``other._name``
+ - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the
+ intersection in the case the latter has to be created; the default
+ is built upon the symbol `\cap`
+
+ OUTPUT:
+
+ - instance of :class:`TopManifoldSubset` representing the subset that
+ is the intersection of the current subset with ``other``
+
+ EXAMPLES:
+
+ Intersection of two subsets::
+
+ sage: TopManifold._clear_cache_() # for doctests only
+ sage: M = TopManifold(2, 'M')
+ sage: a = M.subset('A')
+ sage: b = M.subset('B')
+ sage: c = a.intersection(b); c
+ Subset A_inter_B of the 2-dimensional topological manifold M
+ sage: a.list_of_subsets()
+ [Subset A of the 2-dimensional topological manifold M,
+ Subset A_inter_B of the 2-dimensional topological manifold M]
+ sage: b.list_of_subsets()
+ [Subset A_inter_B of the 2-dimensional topological manifold M,
+ Subset B of the 2-dimensional topological manifold M]
+ sage: c._supersets # random (set output)
+ {Subset B of the 2-dimensional topological manifold M,
+ Subset A_inter_B of the 2-dimensional topological manifold M,
+ Subset A of the 2-dimensional topological manifold M,
+ 2-dimensional topological manifold M}
+
+ Some checks::
+
+ sage: (a.intersection(b)).is_subset(a)
+ True
+ sage: (a.intersection(b)).is_subset(a)
+ True
+ sage: a.intersection(b) is b.intersection(a)
+ True
+ sage: a.intersection(a.intersection(b)) is a.intersection(b)
+ True
+ sage: (a.intersection(b)).intersection(a) is a.intersection(b)
+ True
+ sage: M.intersection(a) is a
+ True
+ sage: a.intersection(M) is a
+ True
+
+ """
+ if other._manifold != self._manifold:
+ raise ValueError(
+ "the two subsets do not belong to the same manifold")
+ # Particular cases:
+ if self is self._manifold:
+ return other
+ if other is self._manifold:
+ return self
+ if self in other._subsets:
+ return self
+ if other in self._subsets:
+ return other
+ # Generic case:
+ if other._name in self._intersections:
+ # the intersection has already been created:
+ return self._intersections[other._name]
+ else:
+ # the intersection must be created:
+ if latex_name is None:
+ if name is None:
+ latex_name = self._latex_name + r'\cap ' + other._latex_name
+ else:
+ latex_name = name
+ if name is None:
+ name = self._name + "_inter_" + other._name
+ if self._is_open and other._is_open:
+ res = self.open_subset(name, latex_name=latex_name)
+ else:
+ res = self.subset(name, latex_name=latex_name)
+ res._supersets.update(other._supersets)
+ for sd in other._supersets:
+ sd._subsets.add(res)
+ other._top_subsets.add(res)
+ self._intersections[other._name] = res
+ other._intersections[self._name] = res
+ return res
+
+ def union(self, other, name=None, latex_name=None):
+ r"""
+ Return the union of the current subset with another subset.
+
+ INPUT:
+
+ - ``other`` -- another subset of the same manifold
+ - ``name`` -- (default: ``None``) name given to the union in the
+ case the latter has to be created; the default is
+ ``self._name`` union ``other._name``
+ - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the
+ union in the case the latter has to be created; the default
+ is built upon the symbol `\cup`
+
+ OUTPUT:
+
+ - instance of :class:`TopManifoldSubset` representing the subset that
+ is the union of the current subset with ``other``
+
+ EXAMPLES:
+
+ Union of two subsets::
+
+