passagemath-symbolics 10.6.37__cp310-cp310-musllinux_1_2_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_symbolics/__init__.py +3 -0
- passagemath_symbolics-10.6.37.dist-info/METADATA +187 -0
- passagemath_symbolics-10.6.37.dist-info/RECORD +171 -0
- passagemath_symbolics-10.6.37.dist-info/WHEEL +5 -0
- passagemath_symbolics-10.6.37.dist-info/top_level.txt +3 -0
- sage/all__sagemath_symbolics.py +17 -0
- sage/calculus/all.py +14 -0
- sage/calculus/calculus.py +2826 -0
- sage/calculus/desolvers.py +1866 -0
- sage/calculus/predefined.py +51 -0
- sage/calculus/tests.py +225 -0
- sage/calculus/var.cpython-310-x86_64-linux-gnu.so +0 -0
- sage/calculus/var.pyx +401 -0
- sage/dynamics/all__sagemath_symbolics.py +6 -0
- sage/dynamics/complex_dynamics/all.py +5 -0
- sage/dynamics/complex_dynamics/mandel_julia.py +765 -0
- sage/dynamics/complex_dynamics/mandel_julia_helper.cpython-310-x86_64-linux-gnu.so +0 -0
- sage/dynamics/complex_dynamics/mandel_julia_helper.pyx +1035 -0
- sage/ext/all__sagemath_symbolics.py +1 -0
- sage/ext_data/kenzo/CP2.txt +45 -0
- sage/ext_data/kenzo/CP3.txt +349 -0
- sage/ext_data/kenzo/CP4.txt +4774 -0
- sage/ext_data/kenzo/README.txt +49 -0
- sage/ext_data/kenzo/S4.txt +20 -0
- sage/ext_data/magma/latex/latex.m +1021 -0
- sage/ext_data/magma/latex/latex.spec +1 -0
- sage/ext_data/magma/sage/basic.m +356 -0
- sage/ext_data/magma/sage/sage.spec +1 -0
- sage/ext_data/magma/spec +9 -0
- sage/geometry/all__sagemath_symbolics.py +8 -0
- sage/geometry/hyperbolic_space/all.py +5 -0
- sage/geometry/hyperbolic_space/hyperbolic_coercion.py +743 -0
- sage/geometry/hyperbolic_space/hyperbolic_constants.py +5 -0
- sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +2409 -0
- sage/geometry/hyperbolic_space/hyperbolic_interface.py +206 -0
- sage/geometry/hyperbolic_space/hyperbolic_isometry.py +1082 -0
- sage/geometry/hyperbolic_space/hyperbolic_model.py +1502 -0
- sage/geometry/hyperbolic_space/hyperbolic_point.py +621 -0
- sage/geometry/riemannian_manifolds/all.py +7 -0
- sage/geometry/riemannian_manifolds/parametrized_surface3d.py +1632 -0
- sage/geometry/riemannian_manifolds/surface3d_generators.py +461 -0
- sage/interfaces/all__sagemath_symbolics.py +1 -0
- sage/interfaces/magma.py +3017 -0
- sage/interfaces/magma_free.py +92 -0
- sage/interfaces/maple.py +1397 -0
- sage/interfaces/mathematica.py +1345 -0
- sage/interfaces/mathics.py +1312 -0
- sage/interfaces/sympy.py +1398 -0
- sage/interfaces/sympy_wrapper.py +197 -0
- sage/interfaces/tides.py +938 -0
- sage/libs/all__sagemath_symbolics.py +6 -0
- sage/manifolds/all.py +7 -0
- sage/manifolds/calculus_method.py +555 -0
- sage/manifolds/catalog.py +437 -0
- sage/manifolds/chart.py +4019 -0
- sage/manifolds/chart_func.py +3419 -0
- sage/manifolds/continuous_map.py +2183 -0
- sage/manifolds/continuous_map_image.py +155 -0
- sage/manifolds/differentiable/affine_connection.py +2475 -0
- sage/manifolds/differentiable/all.py +1 -0
- sage/manifolds/differentiable/automorphismfield.py +1383 -0
- sage/manifolds/differentiable/automorphismfield_group.py +604 -0
- sage/manifolds/differentiable/bundle_connection.py +1445 -0
- sage/manifolds/differentiable/characteristic_cohomology_class.py +1840 -0
- sage/manifolds/differentiable/chart.py +1241 -0
- sage/manifolds/differentiable/curve.py +1028 -0
- sage/manifolds/differentiable/de_rham_cohomology.py +541 -0
- sage/manifolds/differentiable/degenerate.py +559 -0
- sage/manifolds/differentiable/degenerate_submanifold.py +1671 -0
- sage/manifolds/differentiable/diff_form.py +1658 -0
- sage/manifolds/differentiable/diff_form_module.py +1062 -0
- sage/manifolds/differentiable/diff_map.py +1315 -0
- sage/manifolds/differentiable/differentiable_submanifold.py +291 -0
- sage/manifolds/differentiable/examples/all.py +1 -0
- sage/manifolds/differentiable/examples/euclidean.py +2517 -0
- sage/manifolds/differentiable/examples/real_line.py +897 -0
- sage/manifolds/differentiable/examples/sphere.py +1186 -0
- sage/manifolds/differentiable/examples/symplectic_space.py +187 -0
- sage/manifolds/differentiable/examples/symplectic_space_test.py +40 -0
- sage/manifolds/differentiable/integrated_curve.py +4035 -0
- sage/manifolds/differentiable/levi_civita_connection.py +841 -0
- sage/manifolds/differentiable/manifold.py +4254 -0
- sage/manifolds/differentiable/manifold_homset.py +1826 -0
- sage/manifolds/differentiable/metric.py +3032 -0
- sage/manifolds/differentiable/mixed_form.py +1507 -0
- sage/manifolds/differentiable/mixed_form_algebra.py +559 -0
- sage/manifolds/differentiable/multivector_module.py +800 -0
- sage/manifolds/differentiable/multivectorfield.py +1520 -0
- sage/manifolds/differentiable/poisson_tensor.py +268 -0
- sage/manifolds/differentiable/pseudo_riemannian.py +755 -0
- sage/manifolds/differentiable/pseudo_riemannian_submanifold.py +1839 -0
- sage/manifolds/differentiable/scalarfield.py +1343 -0
- sage/manifolds/differentiable/scalarfield_algebra.py +472 -0
- sage/manifolds/differentiable/symplectic_form.py +910 -0
- sage/manifolds/differentiable/symplectic_form_test.py +220 -0
- sage/manifolds/differentiable/tangent_space.py +412 -0
- sage/manifolds/differentiable/tangent_vector.py +616 -0
- sage/manifolds/differentiable/tensorfield.py +4665 -0
- sage/manifolds/differentiable/tensorfield_module.py +963 -0
- sage/manifolds/differentiable/tensorfield_paral.py +2450 -0
- sage/manifolds/differentiable/tensorfield_paral_test.py +16 -0
- sage/manifolds/differentiable/vector_bundle.py +1728 -0
- sage/manifolds/differentiable/vectorfield.py +1717 -0
- sage/manifolds/differentiable/vectorfield_module.py +2445 -0
- sage/manifolds/differentiable/vectorframe.py +1832 -0
- sage/manifolds/family.py +270 -0
- sage/manifolds/local_frame.py +1490 -0
- sage/manifolds/manifold.py +3090 -0
- sage/manifolds/manifold_homset.py +452 -0
- sage/manifolds/operators.py +359 -0
- sage/manifolds/point.py +994 -0
- sage/manifolds/scalarfield.py +3718 -0
- sage/manifolds/scalarfield_algebra.py +629 -0
- sage/manifolds/section.py +3111 -0
- sage/manifolds/section_module.py +831 -0
- sage/manifolds/structure.py +229 -0
- sage/manifolds/subset.py +2764 -0
- sage/manifolds/subsets/all.py +1 -0
- sage/manifolds/subsets/closure.py +131 -0
- sage/manifolds/subsets/pullback.py +885 -0
- sage/manifolds/topological_submanifold.py +891 -0
- sage/manifolds/trivialization.py +733 -0
- sage/manifolds/utilities.py +1348 -0
- sage/manifolds/vector_bundle.py +1342 -0
- sage/manifolds/vector_bundle_fiber.py +332 -0
- sage/manifolds/vector_bundle_fiber_element.py +111 -0
- sage/matrix/all__sagemath_symbolics.py +1 -0
- sage/matrix/matrix_symbolic_dense.cpython-310-x86_64-linux-gnu.so +0 -0
- sage/matrix/matrix_symbolic_dense.pxd +6 -0
- sage/matrix/matrix_symbolic_dense.pyx +1022 -0
- sage/matrix/matrix_symbolic_sparse.cpython-310-x86_64-linux-gnu.so +0 -0
- sage/matrix/matrix_symbolic_sparse.pxd +6 -0
- sage/matrix/matrix_symbolic_sparse.pyx +1029 -0
- sage/modules/all__sagemath_symbolics.py +1 -0
- sage/modules/vector_callable_symbolic_dense.py +105 -0
- sage/modules/vector_symbolic_dense.py +116 -0
- sage/modules/vector_symbolic_sparse.py +118 -0
- sage/rings/all__sagemath_symbolics.py +4 -0
- sage/rings/asymptotic/all.py +6 -0
- sage/rings/asymptotic/asymptotic_expansion_generators.py +1485 -0
- sage/rings/asymptotic/asymptotic_ring.py +4858 -0
- sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py +4153 -0
- sage/rings/asymptotic/growth_group.py +5373 -0
- sage/rings/asymptotic/growth_group_cartesian.py +1400 -0
- sage/rings/asymptotic/term_monoid.py +5237 -0
- sage/rings/function_field/all__sagemath_symbolics.py +2 -0
- sage/rings/polynomial/all__sagemath_symbolics.py +1 -0
- sage/symbolic/all.py +15 -0
- sage/symbolic/assumptions.py +985 -0
- sage/symbolic/benchmark.py +93 -0
- sage/symbolic/callable.py +459 -0
- sage/symbolic/complexity_measures.py +35 -0
- sage/symbolic/constants.py +1287 -0
- sage/symbolic/expression_conversion_algebraic.py +310 -0
- sage/symbolic/expression_conversion_sympy.py +317 -0
- sage/symbolic/expression_conversions.py +1713 -0
- sage/symbolic/function_factory.py +355 -0
- sage/symbolic/integration/all.py +1 -0
- sage/symbolic/integration/external.py +270 -0
- sage/symbolic/integration/integral.py +1115 -0
- sage/symbolic/maxima_wrapper.py +162 -0
- sage/symbolic/operators.py +267 -0
- sage/symbolic/random_tests.py +462 -0
- sage/symbolic/relation.py +1907 -0
- sage/symbolic/ring.cpython-310-x86_64-linux-gnu.so +0 -0
- sage/symbolic/ring.pxd +5 -0
- sage/symbolic/ring.pyx +1396 -0
- sage/symbolic/subring.py +1025 -0
- sage/symbolic/symengine.py +19 -0
- sage/symbolic/tests.py +40 -0
- sage/symbolic/units.py +1470 -0
sage/manifolds/chart.py
ADDED
|
@@ -0,0 +1,4019 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-symbolics
|
|
2
|
+
r"""
|
|
3
|
+
Coordinate Charts
|
|
4
|
+
|
|
5
|
+
The class :class:`Chart` implements coordinate charts on a topological
|
|
6
|
+
manifold over a topological field `K`. The subclass :class:`RealChart`
|
|
7
|
+
is devoted to the case `K=\RR`, for which the concept of coordinate
|
|
8
|
+
range is meaningful.
|
|
9
|
+
Moreover, :class:`RealChart` is endowed with some plotting
|
|
10
|
+
capabilities (cf. method :meth:`~sage.manifolds.chart.RealChart.plot`).
|
|
11
|
+
|
|
12
|
+
Transition maps between charts are implemented via the class
|
|
13
|
+
:class:`CoordChange`.
|
|
14
|
+
|
|
15
|
+
AUTHORS:
|
|
16
|
+
|
|
17
|
+
- Eric Gourgoulhon, Michal Bejger (2013-2015) : initial version
|
|
18
|
+
- Travis Scrimshaw (2015): review tweaks
|
|
19
|
+
- Eric Gourgoulhon (2019): periodic coordinates,
|
|
20
|
+
add :meth:`~Chart.calculus_method`
|
|
21
|
+
|
|
22
|
+
REFERENCES:
|
|
23
|
+
|
|
24
|
+
- Chap. 2 of [Lee2011]_
|
|
25
|
+
- Chap. 1 of [Lee2013]_
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# ****************************************************************************
|
|
29
|
+
# Copyright (C) 2015 Eric Gourgoulhon <eric.gourgoulhon@obspm.fr>
|
|
30
|
+
# Copyright (C) 2015 Michal Bejger <bejger@camk.edu.pl>
|
|
31
|
+
# Copyright (C) 2015 Travis Scrimshaw <tscrimsh@umn.edu>
|
|
32
|
+
#
|
|
33
|
+
# This program is free software: you can redistribute it and/or modify
|
|
34
|
+
# it under the terms of the GNU General Public License as published by
|
|
35
|
+
# the Free Software Foundation, either version 2 of the License, or
|
|
36
|
+
# (at your option) any later version.
|
|
37
|
+
# https://www.gnu.org/licenses/
|
|
38
|
+
# ****************************************************************************
|
|
39
|
+
|
|
40
|
+
from sage.ext.fast_callable import fast_callable
|
|
41
|
+
from sage.manifolds.calculus_method import CalculusMethod
|
|
42
|
+
from sage.manifolds.chart_func import ChartFunctionRing
|
|
43
|
+
from sage.misc.decorators import options
|
|
44
|
+
from sage.misc.latex import latex
|
|
45
|
+
from sage.rings.infinity import Infinity
|
|
46
|
+
from sage.structure.sage_object import SageObject
|
|
47
|
+
from sage.structure.unique_representation import UniqueRepresentation
|
|
48
|
+
from sage.symbolic.expression import Expression
|
|
49
|
+
from sage.symbolic.ring import SR
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Chart(UniqueRepresentation, SageObject):
|
|
53
|
+
r"""
|
|
54
|
+
Chart on a topological manifold.
|
|
55
|
+
|
|
56
|
+
Given a topological manifold `M` of dimension `n` over a topological
|
|
57
|
+
field `K`, a *chart* on `M` is a pair `(U, \varphi)`, where `U` is an
|
|
58
|
+
open subset of `M` and `\varphi : U \rightarrow V \subset K^n` is a
|
|
59
|
+
homeomorphism from `U` to an open subset `V` of `K^n`.
|
|
60
|
+
|
|
61
|
+
The components `(x^1, \ldots, x^n)` of `\varphi`, defined by
|
|
62
|
+
`\varphi(p) = (x^1(p), \ldots, x^n(p)) \in K^n` for any point
|
|
63
|
+
`p \in U`, are called the *coordinates* of the chart `(U, \varphi)`.
|
|
64
|
+
|
|
65
|
+
INPUT:
|
|
66
|
+
|
|
67
|
+
- ``domain`` -- open subset `U` on which the chart is defined (must be
|
|
68
|
+
an instance of :class:`~sage.manifolds.manifold.TopologicalManifold`)
|
|
69
|
+
- ``coordinates`` -- (default: '' (empty string)) single string defining
|
|
70
|
+
the coordinate symbols, with ``' '`` (whitespace) as a separator; each
|
|
71
|
+
item has at most three fields, separated by a colon (``:``):
|
|
72
|
+
|
|
73
|
+
1. the coordinate symbol (a letter or a few letters)
|
|
74
|
+
2. (optional) the period of the coordinate if the coordinate is
|
|
75
|
+
periodic; the period field must be written as ``period=T``, where
|
|
76
|
+
``T`` is the period (see examples below)
|
|
77
|
+
3. (optional) the LaTeX spelling of the coordinate; if not provided the
|
|
78
|
+
coordinate symbol given in the first field will be used
|
|
79
|
+
|
|
80
|
+
The order of fields 2 and 3 does not matter and each of them can be
|
|
81
|
+
omitted. If it contains any LaTeX expression, the string ``coordinates``
|
|
82
|
+
must be declared with the prefix 'r' (for "raw") to allow for a proper
|
|
83
|
+
treatment of LaTeX's backslash character (see examples below).
|
|
84
|
+
If no period and no LaTeX spelling are to be set for any coordinate, the
|
|
85
|
+
argument ``coordinates`` can be omitted when the shortcut operator
|
|
86
|
+
``<,>`` is used to declare the chart (see examples below).
|
|
87
|
+
- ``calc_method`` -- (default: ``None``) string defining the calculus
|
|
88
|
+
method for computations involving coordinates of the chart; must be
|
|
89
|
+
one of
|
|
90
|
+
|
|
91
|
+
- ``'SR'``: Sage's default symbolic engine (Symbolic Ring)
|
|
92
|
+
- ``'sympy'``: SymPy
|
|
93
|
+
- ``None``: the default of
|
|
94
|
+
:class:`~sage.manifolds.calculus_method.CalculusMethod` will be
|
|
95
|
+
used
|
|
96
|
+
- ``names`` -- (default: ``None``) unused argument, except if
|
|
97
|
+
``coordinates`` is not provided; it must then be a tuple containing
|
|
98
|
+
the coordinate symbols (this is guaranteed if the shortcut operator
|
|
99
|
+
``<,>`` is used)
|
|
100
|
+
- ``coord_restrictions`` -- additional restrictions on the coordinates.
|
|
101
|
+
A restriction can be any symbolic equality or inequality involving
|
|
102
|
+
the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items
|
|
103
|
+
of the list (or set or frozenset) ``coord_restrictions`` are combined
|
|
104
|
+
with the ``and`` operator; if some restrictions are to be combined with
|
|
105
|
+
the ``or`` operator instead, they have to be passed as a tuple in some
|
|
106
|
+
single item of the list (or set or frozenset) ``coord_restrictions``.
|
|
107
|
+
For example::
|
|
108
|
+
|
|
109
|
+
coord_restrictions=[x > y, (x != 0, y != 0), z^2 < x]
|
|
110
|
+
|
|
111
|
+
means ``(x > y) and ((x != 0) or (y != 0)) and (z^2 < x)``.
|
|
112
|
+
If the list ``coord_restrictions`` contains only one item, this
|
|
113
|
+
item can be passed as such, i.e. writing ``x > y`` instead
|
|
114
|
+
of the single element list ``[x > y]``. If the chart variables have
|
|
115
|
+
not been declared as variables yet, ``coord_restrictions`` must
|
|
116
|
+
be ``lambda``-quoted.
|
|
117
|
+
|
|
118
|
+
EXAMPLES:
|
|
119
|
+
|
|
120
|
+
A chart on a complex 2-dimensional topological manifold::
|
|
121
|
+
|
|
122
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
123
|
+
sage: X = M.chart('x y'); X
|
|
124
|
+
Chart (M, (x, y))
|
|
125
|
+
sage: latex(X)
|
|
126
|
+
\left(M,(x, y)\right)
|
|
127
|
+
sage: type(X)
|
|
128
|
+
<class 'sage.manifolds.chart.Chart'>
|
|
129
|
+
|
|
130
|
+
To manipulate the coordinates `(x,y)` as global variables,
|
|
131
|
+
one has to set::
|
|
132
|
+
|
|
133
|
+
sage: x,y = X[:]
|
|
134
|
+
|
|
135
|
+
However, a shortcut is to use the declarator ``<x,y>`` in the left-hand
|
|
136
|
+
side of the chart declaration (there is then no need to pass the string
|
|
137
|
+
``'x y'`` to ``chart()``)::
|
|
138
|
+
|
|
139
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
140
|
+
sage: X.<x,y> = M.chart(); X
|
|
141
|
+
Chart (M, (x, y))
|
|
142
|
+
|
|
143
|
+
The coordinates are then immediately accessible::
|
|
144
|
+
|
|
145
|
+
sage: y
|
|
146
|
+
y
|
|
147
|
+
sage: x is X[0] and y is X[1]
|
|
148
|
+
True
|
|
149
|
+
|
|
150
|
+
Note that ``x`` and ``y`` declared in ``<x,y>`` are mere Python variable
|
|
151
|
+
names and do not have to coincide with the coordinate symbols;
|
|
152
|
+
for instance, one may write::
|
|
153
|
+
|
|
154
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
155
|
+
sage: X.<x1,y1> = M.chart('x y'); X
|
|
156
|
+
Chart (M, (x, y))
|
|
157
|
+
|
|
158
|
+
Then ``y`` is not known as a global Python variable and the
|
|
159
|
+
coordinate `y` is accessible only through the global variable ``y1``::
|
|
160
|
+
|
|
161
|
+
sage: y1
|
|
162
|
+
y
|
|
163
|
+
sage: latex(y1)
|
|
164
|
+
y
|
|
165
|
+
sage: y1 is X[1]
|
|
166
|
+
True
|
|
167
|
+
|
|
168
|
+
However, having the name of the Python variable coincide with the
|
|
169
|
+
coordinate symbol is quite convenient; so it is recommended to declare::
|
|
170
|
+
|
|
171
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
172
|
+
sage: X.<x,y> = M.chart()
|
|
173
|
+
|
|
174
|
+
In the above example, the chart X covers entirely the manifold ``M``::
|
|
175
|
+
|
|
176
|
+
sage: X.domain()
|
|
177
|
+
Complex 2-dimensional topological manifold M
|
|
178
|
+
|
|
179
|
+
Of course, one may declare a chart only on an open subset of ``M``::
|
|
180
|
+
|
|
181
|
+
sage: U = M.open_subset('U')
|
|
182
|
+
sage: Y.<z1, z2> = U.chart(r'z1:\zeta_1 z2:\zeta_2'); Y
|
|
183
|
+
Chart (U, (z1, z2))
|
|
184
|
+
sage: Y.domain()
|
|
185
|
+
Open subset U of the Complex 2-dimensional topological manifold M
|
|
186
|
+
|
|
187
|
+
In the above declaration, we have also specified some LaTeX writing
|
|
188
|
+
of the coordinates different from the text one::
|
|
189
|
+
|
|
190
|
+
sage: latex(z1)
|
|
191
|
+
{\zeta_1}
|
|
192
|
+
|
|
193
|
+
Note the prefix ``r`` in front of the string ``r'z1:\zeta_1 z2:\zeta_2'``;
|
|
194
|
+
it makes sure that the backslash character is treated as an ordinary
|
|
195
|
+
character, to be passed to the LaTeX interpreter.
|
|
196
|
+
|
|
197
|
+
Periodic coordinates are declared through the keyword ``period=`` in the
|
|
198
|
+
coordinate field::
|
|
199
|
+
|
|
200
|
+
sage: N = Manifold(2, 'N', field='complex', structure='topological')
|
|
201
|
+
sage: XN.<Z1,Z2> = N.chart('Z1:period=1+2*I Z2')
|
|
202
|
+
sage: XN.periods()
|
|
203
|
+
(2*I + 1, None)
|
|
204
|
+
|
|
205
|
+
Coordinates are Sage symbolic variables (see
|
|
206
|
+
:mod:`sage.symbolic.expression`)::
|
|
207
|
+
|
|
208
|
+
sage: type(z1)
|
|
209
|
+
<class 'sage.symbolic.expression.Expression'>
|
|
210
|
+
|
|
211
|
+
In addition to the Python variable name provided in the operator ``<.,.>``,
|
|
212
|
+
the coordinates are accessible by their indices::
|
|
213
|
+
|
|
214
|
+
sage: Y[0], Y[1]
|
|
215
|
+
(z1, z2)
|
|
216
|
+
|
|
217
|
+
The index range is that declared during the creation of the manifold. By
|
|
218
|
+
default, it starts at 0, but this can be changed via the parameter
|
|
219
|
+
``start_index``::
|
|
220
|
+
|
|
221
|
+
sage: M1 = Manifold(2, 'M_1', field='complex', structure='topological',
|
|
222
|
+
....: start_index=1)
|
|
223
|
+
sage: Z.<u,v> = M1.chart()
|
|
224
|
+
sage: Z[1], Z[2]
|
|
225
|
+
(u, v)
|
|
226
|
+
|
|
227
|
+
The full set of coordinates is obtained by means of the slice
|
|
228
|
+
operator ``[:]``::
|
|
229
|
+
|
|
230
|
+
sage: Y[:]
|
|
231
|
+
(z1, z2)
|
|
232
|
+
|
|
233
|
+
Some partial sets of coordinates::
|
|
234
|
+
|
|
235
|
+
sage: Y[:1]
|
|
236
|
+
(z1,)
|
|
237
|
+
sage: Y[1:]
|
|
238
|
+
(z2,)
|
|
239
|
+
|
|
240
|
+
Each constructed chart is automatically added to the manifold's user
|
|
241
|
+
atlas::
|
|
242
|
+
|
|
243
|
+
sage: M.atlas()
|
|
244
|
+
[Chart (M, (x, y)), Chart (U, (z1, z2))]
|
|
245
|
+
|
|
246
|
+
and to the atlas of the chart's domain::
|
|
247
|
+
|
|
248
|
+
sage: U.atlas()
|
|
249
|
+
[Chart (U, (z1, z2))]
|
|
250
|
+
|
|
251
|
+
Manifold subsets have a *default chart*, which, unless changed via the
|
|
252
|
+
method
|
|
253
|
+
:meth:`~sage.manifolds.manifold.TopologicalManifold.set_default_chart`,
|
|
254
|
+
is the first defined chart on the subset (or on a open subset of it)::
|
|
255
|
+
|
|
256
|
+
sage: M.default_chart()
|
|
257
|
+
Chart (M, (x, y))
|
|
258
|
+
sage: U.default_chart()
|
|
259
|
+
Chart (U, (z1, z2))
|
|
260
|
+
|
|
261
|
+
The default charts are not privileged charts on the manifold, but rather
|
|
262
|
+
charts whose name can be skipped in the argument list of functions having
|
|
263
|
+
an optional ``chart=`` argument.
|
|
264
|
+
|
|
265
|
+
The chart map `\varphi` acting on a point is obtained by passing
|
|
266
|
+
it as an input to the map::
|
|
267
|
+
|
|
268
|
+
sage: p = M.point((1+i, 2), chart=X); p
|
|
269
|
+
Point on the Complex 2-dimensional topological manifold M
|
|
270
|
+
sage: X(p)
|
|
271
|
+
(I + 1, 2)
|
|
272
|
+
sage: X(p) == p.coord(X)
|
|
273
|
+
True
|
|
274
|
+
|
|
275
|
+
Setting additional coordinate restrictions::
|
|
276
|
+
|
|
277
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
278
|
+
sage: X.<x,y> = M.chart(coord_restrictions=lambda x,y: abs(x) > 1)
|
|
279
|
+
sage: X.valid_coordinates(2+i, 1)
|
|
280
|
+
True
|
|
281
|
+
sage: X.valid_coordinates(i, 1)
|
|
282
|
+
False
|
|
283
|
+
|
|
284
|
+
.. SEEALSO::
|
|
285
|
+
|
|
286
|
+
:class:`sage.manifolds.chart.RealChart` for charts on topological
|
|
287
|
+
manifolds over `\RR`.
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def __classcall__(
|
|
292
|
+
cls,
|
|
293
|
+
domain,
|
|
294
|
+
coordinates='',
|
|
295
|
+
calc_method=None,
|
|
296
|
+
names=None,
|
|
297
|
+
coord_restrictions=None,
|
|
298
|
+
**coordinate_options,
|
|
299
|
+
):
|
|
300
|
+
r"""
|
|
301
|
+
Normalize init args and implement unique representation behavior.
|
|
302
|
+
|
|
303
|
+
TESTS::
|
|
304
|
+
|
|
305
|
+
sage: from sage.manifolds.chart import Chart
|
|
306
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
307
|
+
sage: var("u v")
|
|
308
|
+
(u, v)
|
|
309
|
+
sage: Chart(M, (u, v)) is Chart(M, "u v")
|
|
310
|
+
True
|
|
311
|
+
"""
|
|
312
|
+
if isinstance(coordinates, str):
|
|
313
|
+
if coordinates == '':
|
|
314
|
+
for x in names:
|
|
315
|
+
coordinates += x + ' '
|
|
316
|
+
coordinates = coordinates[:-1]
|
|
317
|
+
coordinates, parsed_options = cls._parse_coordinates(domain, coordinates)
|
|
318
|
+
if not coordinate_options:
|
|
319
|
+
coordinate_options = parsed_options
|
|
320
|
+
|
|
321
|
+
coord_string = ' '.join(str(x) for x in coordinates)
|
|
322
|
+
|
|
323
|
+
try:
|
|
324
|
+
return domain._charts_by_coord[coord_string]
|
|
325
|
+
except KeyError:
|
|
326
|
+
# Make coord_restrictions hashable
|
|
327
|
+
coord_restrictions = cls._normalize_coord_restrictions(
|
|
328
|
+
coordinates, coord_restrictions
|
|
329
|
+
)
|
|
330
|
+
self = super().__classcall__(
|
|
331
|
+
cls,
|
|
332
|
+
domain,
|
|
333
|
+
coordinates,
|
|
334
|
+
calc_method,
|
|
335
|
+
coord_restrictions=coord_restrictions,
|
|
336
|
+
**coordinate_options,
|
|
337
|
+
)
|
|
338
|
+
domain._charts_by_coord[coord_string] = self
|
|
339
|
+
return self
|
|
340
|
+
|
|
341
|
+
def __init__(
|
|
342
|
+
self,
|
|
343
|
+
domain,
|
|
344
|
+
coordinates,
|
|
345
|
+
calc_method=None,
|
|
346
|
+
periods=None,
|
|
347
|
+
coord_restrictions=None,
|
|
348
|
+
):
|
|
349
|
+
r"""
|
|
350
|
+
Construct a chart.
|
|
351
|
+
|
|
352
|
+
TESTS::
|
|
353
|
+
|
|
354
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
355
|
+
sage: X.<x,y> = M.chart()
|
|
356
|
+
sage: X
|
|
357
|
+
Chart (M, (x, y))
|
|
358
|
+
sage: type(X)
|
|
359
|
+
<class 'sage.manifolds.chart.Chart'>
|
|
360
|
+
sage: assumptions() # no assumptions on x,y set
|
|
361
|
+
[]
|
|
362
|
+
sage: TestSuite(X).run()
|
|
363
|
+
|
|
364
|
+
Check that :issue:`32112` has been fixed::
|
|
365
|
+
|
|
366
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
367
|
+
sage: U = M.open_subset('U')
|
|
368
|
+
sage: V = M.open_subset('V')
|
|
369
|
+
sage: XU = U.chart('x y')
|
|
370
|
+
sage: XV = V.chart('x y')
|
|
371
|
+
sage: M.top_charts()
|
|
372
|
+
[Chart (U, (x, y)), Chart (V, (x, y))]
|
|
373
|
+
"""
|
|
374
|
+
from sage.manifolds.manifold import TopologicalManifold
|
|
375
|
+
|
|
376
|
+
if not isinstance(domain, TopologicalManifold):
|
|
377
|
+
raise TypeError(
|
|
378
|
+
"the first argument must be an open subset of "
|
|
379
|
+
+ "a topological manifold"
|
|
380
|
+
)
|
|
381
|
+
self._manifold = domain.manifold()
|
|
382
|
+
self._domain = domain
|
|
383
|
+
self._sindex = self._manifold.start_index()
|
|
384
|
+
# Handling of calculus methods available on this chart:
|
|
385
|
+
self._calc_method = CalculusMethod(
|
|
386
|
+
current=calc_method, base_field_type=self.manifold().base_field_type()
|
|
387
|
+
)
|
|
388
|
+
self.simplify = self._calc_method.simplify
|
|
389
|
+
|
|
390
|
+
# Treatment of the coordinates:
|
|
391
|
+
self._periods = periods
|
|
392
|
+
|
|
393
|
+
if len(coordinates) != self._manifold.dim():
|
|
394
|
+
raise ValueError(
|
|
395
|
+
"the list of coordinates must contain "
|
|
396
|
+
+ "{} elements".format(self._manifold.dim())
|
|
397
|
+
)
|
|
398
|
+
self._xx = coordinates
|
|
399
|
+
#
|
|
400
|
+
# Additional restrictions on the coordinates.
|
|
401
|
+
self._restrictions = sorted(coord_restrictions, key=str)
|
|
402
|
+
#
|
|
403
|
+
# The chart is added to the domain's atlas, as well as to all the
|
|
404
|
+
# atlases of the domain's supersets; moreover the first defined chart
|
|
405
|
+
# is considered as the default chart
|
|
406
|
+
for sd in domain.open_supersets():
|
|
407
|
+
# the chart is added in the top charts iff its coordinates have
|
|
408
|
+
# not been used on a domain including the chart's domain:
|
|
409
|
+
for chart in sd._atlas:
|
|
410
|
+
if domain.is_subset(chart._domain) and self._xx == chart._xx:
|
|
411
|
+
break
|
|
412
|
+
else:
|
|
413
|
+
sd._top_charts.append(self)
|
|
414
|
+
sd._atlas.append(self)
|
|
415
|
+
if sd._def_chart is None:
|
|
416
|
+
sd._def_chart = self
|
|
417
|
+
# The chart is added to the list of the domain's covering charts:
|
|
418
|
+
domain._covering_charts.append(self)
|
|
419
|
+
# Initialization of the set of charts that are restrictions of the
|
|
420
|
+
# current chart to subsets of the chart domain:
|
|
421
|
+
self._subcharts = set([self])
|
|
422
|
+
# Initialization of the set of charts which the current chart is a
|
|
423
|
+
# restriction of:
|
|
424
|
+
self._supercharts = set([self])
|
|
425
|
+
|
|
426
|
+
# dict. of the restrictions of self to
|
|
427
|
+
# subsets of self._domain, with the
|
|
428
|
+
# subsets as keys
|
|
429
|
+
self._dom_restrict = {}
|
|
430
|
+
# The null and one functions of the coordinates:
|
|
431
|
+
# Expression in self of the zero and one scalar fields of open sets
|
|
432
|
+
# containing the domain of self:
|
|
433
|
+
for dom in domain.open_supersets():
|
|
434
|
+
dom._zero_scalar_field._express[self] = self.function_ring().zero()
|
|
435
|
+
dom._one_scalar_field._express[self] = self.function_ring().one()
|
|
436
|
+
|
|
437
|
+
@classmethod
|
|
438
|
+
def _parse_coordinates(cls, domain, coordinates):
|
|
439
|
+
r"""
|
|
440
|
+
Initialization of the coordinates as symbolic variables.
|
|
441
|
+
|
|
442
|
+
INPUT:
|
|
443
|
+
|
|
444
|
+
- ``coord_list`` -- list (or space-separated concatenation) of
|
|
445
|
+
coordinate fields. Each field is a string of at most 3 items,
|
|
446
|
+
separated by ":". These items are: the coordinate symbol, the
|
|
447
|
+
(optional) indicator of the periodic character of the
|
|
448
|
+
coordinate, and the (optional) coordinate LaTeX symbol
|
|
449
|
+
|
|
450
|
+
OUTPUT:
|
|
451
|
+
|
|
452
|
+
- a tuple of variables (as elements of ``SR``)
|
|
453
|
+
- a dictionary with possible keys:
|
|
454
|
+
|
|
455
|
+
- ``'periods'`` -- a tuple of periods
|
|
456
|
+
|
|
457
|
+
TESTS::
|
|
458
|
+
|
|
459
|
+
sage: from sage.manifolds.chart import Chart
|
|
460
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
461
|
+
sage: Chart._parse_coordinates(M, ['z1', 'z2'])
|
|
462
|
+
((z1, z2), {'periods': (None, None)})
|
|
463
|
+
sage: Chart._parse_coordinates(M, 'z1 z2')
|
|
464
|
+
((z1, z2), {'periods': (None, None)})
|
|
465
|
+
sage: Chart._parse_coordinates(M, [r'z1:\zeta_1', r'z2:\zeta_2'])
|
|
466
|
+
((z1, z2), {'periods': (None, None)})
|
|
467
|
+
"""
|
|
468
|
+
if isinstance(coordinates, str):
|
|
469
|
+
coord_list = coordinates.split()
|
|
470
|
+
else:
|
|
471
|
+
coord_list = coordinates
|
|
472
|
+
xx_list = [] # will contain the coordinates as Sage symbolic variables
|
|
473
|
+
period_list = []
|
|
474
|
+
for coord_index, coord_field in enumerate(coord_list):
|
|
475
|
+
coord_properties = coord_field.split(':')
|
|
476
|
+
coord_symb = coord_properties[0].strip() # the coordinate symbol
|
|
477
|
+
coord_latex = None # possibly redefined below
|
|
478
|
+
period = None # possibly redefined below
|
|
479
|
+
# scan of the properties other than the symbol:
|
|
480
|
+
for prop in coord_properties[1:]:
|
|
481
|
+
prop1 = prop.strip()
|
|
482
|
+
if prop1[0:6] == 'period':
|
|
483
|
+
if domain.base_field_type() in ['real', 'complex']:
|
|
484
|
+
period = SR(prop1[7:])
|
|
485
|
+
else:
|
|
486
|
+
period = domain.base_field()(prop1[7:])
|
|
487
|
+
else:
|
|
488
|
+
# prop1 is the coordinate's LaTeX symbol
|
|
489
|
+
coord_latex = prop1
|
|
490
|
+
# Construction of the coordinate as a Sage symbolic variable:
|
|
491
|
+
coord_var = SR.var(coord_symb, latex_name=coord_latex)
|
|
492
|
+
xx_list.append(coord_var)
|
|
493
|
+
period_list.append(period)
|
|
494
|
+
return tuple(xx_list), dict(periods=tuple(period_list))
|
|
495
|
+
|
|
496
|
+
@staticmethod
|
|
497
|
+
def _normalize_coord_restrictions(coordinates, coord_restrictions):
|
|
498
|
+
r"""
|
|
499
|
+
Rewrite ``coord_restrictions`` as a ``frozenset``, representing a logical "and", of other clauses.
|
|
500
|
+
|
|
501
|
+
Also replace ``list`` by ``frozenset`` , making the result hashable.
|
|
502
|
+
|
|
503
|
+
EXAMPLES::
|
|
504
|
+
|
|
505
|
+
sage: from sage.manifolds.chart import Chart
|
|
506
|
+
sage: coordinates = var("x y z")
|
|
507
|
+
sage: Chart._normalize_coord_restrictions(coordinates, None)
|
|
508
|
+
frozenset()
|
|
509
|
+
sage: Chart._normalize_coord_restrictions(coordinates, x > y)
|
|
510
|
+
frozenset({x > y})
|
|
511
|
+
sage: Chart._normalize_coord_restrictions(coordinates, (x != 0, y != 0))
|
|
512
|
+
frozenset({(x != 0, y != 0)})
|
|
513
|
+
sage: Chart._normalize_coord_restrictions(coordinates, [x > y, (x != 0, y != 0), z^2 < x])
|
|
514
|
+
frozenset({(x != 0, y != 0), x > y, z^2 < x})
|
|
515
|
+
"""
|
|
516
|
+
|
|
517
|
+
def normalize(r):
|
|
518
|
+
if isinstance(r, tuple): # or
|
|
519
|
+
return tuple(normalize(x) for x in r)
|
|
520
|
+
elif isinstance(r, (list, set, frozenset)): # and
|
|
521
|
+
return frozenset(normalize(x) for x in r)
|
|
522
|
+
else:
|
|
523
|
+
return r
|
|
524
|
+
|
|
525
|
+
if coord_restrictions is None:
|
|
526
|
+
return frozenset()
|
|
527
|
+
|
|
528
|
+
if callable(coord_restrictions) and not isinstance(
|
|
529
|
+
coord_restrictions, Expression
|
|
530
|
+
):
|
|
531
|
+
# lambda-quoted
|
|
532
|
+
coord_restrictions = coord_restrictions(*coordinates)
|
|
533
|
+
|
|
534
|
+
if not isinstance(coord_restrictions, (list, set, frozenset)):
|
|
535
|
+
# case of a single condition or conditions to be combined by "or"
|
|
536
|
+
coord_restrictions = [coord_restrictions]
|
|
537
|
+
|
|
538
|
+
return normalize(coord_restrictions)
|
|
539
|
+
|
|
540
|
+
def _repr_(self):
|
|
541
|
+
r"""
|
|
542
|
+
String representation of the object.
|
|
543
|
+
|
|
544
|
+
TESTS::
|
|
545
|
+
|
|
546
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
547
|
+
sage: X.<x,y> = M.chart()
|
|
548
|
+
sage: X
|
|
549
|
+
Chart (M, (x, y))
|
|
550
|
+
"""
|
|
551
|
+
return 'Chart ({}, {})'.format(self.domain()._name, self._xx)
|
|
552
|
+
|
|
553
|
+
def _latex_(self):
|
|
554
|
+
r"""
|
|
555
|
+
LaTeX representation of the object.
|
|
556
|
+
|
|
557
|
+
TESTS::
|
|
558
|
+
|
|
559
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
560
|
+
sage: X.<x,y> = M.chart()
|
|
561
|
+
sage: X._latex_()
|
|
562
|
+
'\\left(M,(x, y)\\right)'
|
|
563
|
+
sage: Y.<z1, z2> = M.chart(r'z1:\zeta_1 z2:\zeta2')
|
|
564
|
+
sage: Y._latex_()
|
|
565
|
+
'\\left(M,({\\zeta_1}, {\\zeta2})\\right)'
|
|
566
|
+
sage: latex(Y)
|
|
567
|
+
\left(M,({\zeta_1}, {\zeta2})\right)
|
|
568
|
+
"""
|
|
569
|
+
description = r'\left(' + latex(self.domain()).strip() + ',('
|
|
570
|
+
n = len(self._xx)
|
|
571
|
+
for i in range(n - 1):
|
|
572
|
+
description += latex(self._xx[i]).strip() + ', '
|
|
573
|
+
description += latex(self._xx[n - 1]).strip() + r')\right)'
|
|
574
|
+
return description
|
|
575
|
+
|
|
576
|
+
def _first_ngens(self, n):
|
|
577
|
+
r"""
|
|
578
|
+
Return the list of coordinates.
|
|
579
|
+
|
|
580
|
+
This is useful only for the use of Sage preparser::
|
|
581
|
+
|
|
582
|
+
sage: preparse("c_cart.<x,y,z> = M.chart()")
|
|
583
|
+
"c_cart = M.chart(names=('x', 'y', 'z',)); (x, y, z,) = c_cart._first_ngens(3)"
|
|
584
|
+
"""
|
|
585
|
+
return self[:]
|
|
586
|
+
|
|
587
|
+
def __getitem__(self, i):
|
|
588
|
+
r"""
|
|
589
|
+
Access to the coordinates.
|
|
590
|
+
|
|
591
|
+
INPUT:
|
|
592
|
+
|
|
593
|
+
- ``i`` -- index of the coordinate; if the slice ``[:]``, then all
|
|
594
|
+
the coordinates are returned
|
|
595
|
+
|
|
596
|
+
OUTPUT:
|
|
597
|
+
|
|
598
|
+
- the coordinate of index ``i`` or all the coordinates (as a tuple)
|
|
599
|
+
if ``i`` is the slice ``[:]``
|
|
600
|
+
|
|
601
|
+
EXAMPLES::
|
|
602
|
+
|
|
603
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
604
|
+
sage: X.<x,y> = M.chart()
|
|
605
|
+
sage: X[0]
|
|
606
|
+
x
|
|
607
|
+
sage: X[1]
|
|
608
|
+
y
|
|
609
|
+
sage: X[:]
|
|
610
|
+
(x, y)
|
|
611
|
+
|
|
612
|
+
The index range is controlled by the parameter ``start_index``::
|
|
613
|
+
|
|
614
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological',
|
|
615
|
+
....: start_index=1)
|
|
616
|
+
sage: X.<x,y> = M.chart()
|
|
617
|
+
sage: X[1]
|
|
618
|
+
x
|
|
619
|
+
sage: X[2]
|
|
620
|
+
y
|
|
621
|
+
sage: X[:]
|
|
622
|
+
(x, y)
|
|
623
|
+
|
|
624
|
+
We check that slices are properly shifted as well::
|
|
625
|
+
|
|
626
|
+
sage: X[2:]
|
|
627
|
+
(y,)
|
|
628
|
+
sage: X[:2]
|
|
629
|
+
(x,)
|
|
630
|
+
"""
|
|
631
|
+
if isinstance(i, slice):
|
|
632
|
+
start, stop = i.start, i.stop
|
|
633
|
+
if start is not None:
|
|
634
|
+
start -= self._sindex
|
|
635
|
+
if stop is not None:
|
|
636
|
+
stop -= self._sindex
|
|
637
|
+
return self._xx[start : stop : i.step]
|
|
638
|
+
return self._xx[i - self._sindex]
|
|
639
|
+
|
|
640
|
+
def __call__(self, point):
|
|
641
|
+
r"""
|
|
642
|
+
Return the coordinates of a given point.
|
|
643
|
+
|
|
644
|
+
INPUT:
|
|
645
|
+
|
|
646
|
+
- ``point`` -- point in the domain of the chart
|
|
647
|
+
|
|
648
|
+
OUTPUT: tuple of the coordinates of the point
|
|
649
|
+
|
|
650
|
+
EXAMPLES::
|
|
651
|
+
|
|
652
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
653
|
+
sage: X.<x,y> = M.chart()
|
|
654
|
+
sage: p = M.point((1+i, 2-i), chart=X)
|
|
655
|
+
sage: X(p)
|
|
656
|
+
(I + 1, -I + 2)
|
|
657
|
+
sage: X(M.an_element())
|
|
658
|
+
(0, 0)
|
|
659
|
+
"""
|
|
660
|
+
return point.coord(self)
|
|
661
|
+
|
|
662
|
+
def domain(self):
|
|
663
|
+
r"""
|
|
664
|
+
Return the open subset on which the chart is defined.
|
|
665
|
+
|
|
666
|
+
EXAMPLES::
|
|
667
|
+
|
|
668
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
669
|
+
sage: X.<x,y> = M.chart()
|
|
670
|
+
sage: X.domain()
|
|
671
|
+
2-dimensional topological manifold M
|
|
672
|
+
sage: U = M.open_subset('U')
|
|
673
|
+
sage: Y.<u,v> = U.chart()
|
|
674
|
+
sage: Y.domain()
|
|
675
|
+
Open subset U of the 2-dimensional topological manifold M
|
|
676
|
+
"""
|
|
677
|
+
return self._domain
|
|
678
|
+
|
|
679
|
+
def manifold(self):
|
|
680
|
+
r"""
|
|
681
|
+
Return the manifold on which the chart is defined.
|
|
682
|
+
|
|
683
|
+
EXAMPLES::
|
|
684
|
+
|
|
685
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
686
|
+
sage: U = M.open_subset('U')
|
|
687
|
+
sage: X.<x,y> = U.chart()
|
|
688
|
+
sage: X.manifold()
|
|
689
|
+
2-dimensional topological manifold M
|
|
690
|
+
sage: X.domain()
|
|
691
|
+
Open subset U of the 2-dimensional topological manifold M
|
|
692
|
+
"""
|
|
693
|
+
return self._manifold
|
|
694
|
+
|
|
695
|
+
def periods(self):
|
|
696
|
+
r"""
|
|
697
|
+
Return the coordinate periods.
|
|
698
|
+
|
|
699
|
+
OUTPUT:
|
|
700
|
+
|
|
701
|
+
- a tuple containing the period of each coordinate, with the
|
|
702
|
+
value ``None`` if the coordinate is not periodic
|
|
703
|
+
|
|
704
|
+
EXAMPLES:
|
|
705
|
+
|
|
706
|
+
A chart without any periodic coordinate::
|
|
707
|
+
|
|
708
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
709
|
+
sage: X.<x,y> = M.chart()
|
|
710
|
+
sage: X.periods()
|
|
711
|
+
(None, None)
|
|
712
|
+
|
|
713
|
+
Charts with a periodic coordinate::
|
|
714
|
+
|
|
715
|
+
sage: Y.<u,v> = M.chart("u v:(0,2*pi):periodic")
|
|
716
|
+
sage: Y.periods()
|
|
717
|
+
(None, 2*pi)
|
|
718
|
+
sage: Z.<a,b> = M.chart(r"a:period=sqrt(2):\alpha b:\beta")
|
|
719
|
+
sage: Z.periods()
|
|
720
|
+
(sqrt(2), None)
|
|
721
|
+
|
|
722
|
+
Complex manifold with a periodic coordinate::
|
|
723
|
+
|
|
724
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological',
|
|
725
|
+
....: start_index=1)
|
|
726
|
+
sage: X.<x,y> = M.chart("x y:period=1+I")
|
|
727
|
+
sage: X.periods()
|
|
728
|
+
(None, I + 1)
|
|
729
|
+
|
|
730
|
+
TESTS::
|
|
731
|
+
|
|
732
|
+
sage: M = Manifold(2, 'M', field=QQ, structure='topological')
|
|
733
|
+
sage: X.<xq,yq> = M.chart(r"xq:period=3/2 yq:\zeta:period=2")
|
|
734
|
+
sage: X.periods()
|
|
735
|
+
(3/2, 2)
|
|
736
|
+
"""
|
|
737
|
+
return self._periods
|
|
738
|
+
|
|
739
|
+
def add_restrictions(self, restrictions):
|
|
740
|
+
r"""
|
|
741
|
+
Add some restrictions on the coordinates.
|
|
742
|
+
|
|
743
|
+
This is deprecated; provide the restrictions at the time of creating
|
|
744
|
+
the chart.
|
|
745
|
+
|
|
746
|
+
INPUT:
|
|
747
|
+
|
|
748
|
+
- ``restrictions`` -- list of restrictions on the
|
|
749
|
+
coordinates, in addition to the ranges declared by the intervals
|
|
750
|
+
specified in the chart constructor
|
|
751
|
+
|
|
752
|
+
A restriction can be any symbolic equality or inequality involving
|
|
753
|
+
the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items
|
|
754
|
+
of the list ``restrictions`` are combined with the ``and`` operator;
|
|
755
|
+
if some restrictions are to be combined with the ``or`` operator
|
|
756
|
+
instead, they have to be passed as a tuple in some single item
|
|
757
|
+
of the list ``restrictions``. For example::
|
|
758
|
+
|
|
759
|
+
restrictions = [x > y, (x != 0, y != 0), z^2 < x]
|
|
760
|
+
|
|
761
|
+
means ``(x > y) and ((x != 0) or (y != 0)) and (z^2 < x)``.
|
|
762
|
+
If the list ``restrictions`` contains only one item, this
|
|
763
|
+
item can be passed as such, i.e. writing ``x > y`` instead
|
|
764
|
+
of the single element list ``[x > y]``.
|
|
765
|
+
|
|
766
|
+
EXAMPLES::
|
|
767
|
+
|
|
768
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
769
|
+
sage: X.<x,y> = M.chart()
|
|
770
|
+
sage: X.add_restrictions(abs(x) > 1)
|
|
771
|
+
doctest:warning...
|
|
772
|
+
DeprecationWarning: Chart.add_restrictions is deprecated; provide the
|
|
773
|
+
restrictions at the time of creating the chart
|
|
774
|
+
See https://github.com/sagemath/sage/issues/32102 for details.
|
|
775
|
+
sage: X.valid_coordinates(2+i, 1)
|
|
776
|
+
True
|
|
777
|
+
sage: X.valid_coordinates(i, 1)
|
|
778
|
+
False
|
|
779
|
+
"""
|
|
780
|
+
from sage.misc.superseded import deprecation
|
|
781
|
+
|
|
782
|
+
deprecation(
|
|
783
|
+
32102,
|
|
784
|
+
"Chart.add_restrictions is deprecated; provide the restrictions at the time of creating the chart",
|
|
785
|
+
)
|
|
786
|
+
self._restrictions.extend(
|
|
787
|
+
self._normalize_coord_restrictions(self._xx, restrictions)
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
def restrict(self, subset, restrictions=None):
|
|
791
|
+
r"""
|
|
792
|
+
Return the restriction of ``self`` to some open subset of its domain.
|
|
793
|
+
|
|
794
|
+
If the current chart is `(U,\varphi)`, a *restriction* (or *subchart*)
|
|
795
|
+
is a chart `(V,\psi)` such that `V\subset U` and `\psi = \varphi |_V`.
|
|
796
|
+
|
|
797
|
+
If such subchart has not been defined yet, it is constructed here.
|
|
798
|
+
|
|
799
|
+
The coordinates of the subchart bare the same names as the coordinates
|
|
800
|
+
of the current chart.
|
|
801
|
+
|
|
802
|
+
INPUT:
|
|
803
|
+
|
|
804
|
+
- ``subset`` -- open subset `V` of the chart domain `U` (must be an
|
|
805
|
+
instance of :class:`~sage.manifolds.manifold.TopologicalManifold`)
|
|
806
|
+
- ``restrictions`` -- (default: ``None``) list of coordinate
|
|
807
|
+
restrictions defining the subset `V`
|
|
808
|
+
|
|
809
|
+
A restriction can be any symbolic equality or inequality involving
|
|
810
|
+
the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items
|
|
811
|
+
of the list ``restrictions`` are combined with the ``and`` operator;
|
|
812
|
+
if some restrictions are to be combined with the ``or`` operator
|
|
813
|
+
instead, they have to be passed as a tuple in some single item
|
|
814
|
+
of the list ``restrictions``. For example::
|
|
815
|
+
|
|
816
|
+
restrictions = [x > y, (x != 0, y != 0), z^2 < x]
|
|
817
|
+
|
|
818
|
+
means ``(x > y) and ((x != 0) or (y != 0)) and (z^2 < x)``.
|
|
819
|
+
If the list ``restrictions`` contains only one item, this
|
|
820
|
+
item can be passed as such, i.e. writing ``x > y`` instead
|
|
821
|
+
of the single element list ``[x > y]``.
|
|
822
|
+
|
|
823
|
+
OUTPUT:
|
|
824
|
+
|
|
825
|
+
- chart `(V, \psi)` as a :class:`Chart`
|
|
826
|
+
|
|
827
|
+
EXAMPLES:
|
|
828
|
+
|
|
829
|
+
Coordinates on the unit open ball of `\CC^2` as a subchart
|
|
830
|
+
of the global coordinates of `\CC^2`::
|
|
831
|
+
|
|
832
|
+
sage: M = Manifold(2, 'C^2', field='complex', structure='topological')
|
|
833
|
+
sage: X.<z1, z2> = M.chart()
|
|
834
|
+
sage: B = M.open_subset('B')
|
|
835
|
+
sage: X_B = X.restrict(B, abs(z1)^2 + abs(z2)^2 < 1); X_B
|
|
836
|
+
Chart (B, (z1, z2))
|
|
837
|
+
"""
|
|
838
|
+
if subset == self.domain():
|
|
839
|
+
return self
|
|
840
|
+
if subset not in self._dom_restrict:
|
|
841
|
+
if not subset.is_subset(self.domain()):
|
|
842
|
+
raise ValueError(
|
|
843
|
+
"the specified subset is not a subset "
|
|
844
|
+
+ "of the domain of definition of the chart"
|
|
845
|
+
)
|
|
846
|
+
coordinates = ""
|
|
847
|
+
for coord in self._xx:
|
|
848
|
+
coordinates += repr(coord) + ' '
|
|
849
|
+
res_coord_restrictions = set(self._restrictions)
|
|
850
|
+
res_coord_restrictions.update(
|
|
851
|
+
self._normalize_coord_restrictions(self._xx, restrictions)
|
|
852
|
+
)
|
|
853
|
+
res = type(self)(
|
|
854
|
+
subset,
|
|
855
|
+
coordinates,
|
|
856
|
+
calc_method=self._calc_method._current,
|
|
857
|
+
periods=self._periods,
|
|
858
|
+
# The coordinate restrictions are added
|
|
859
|
+
# to the result chart
|
|
860
|
+
coord_restrictions=res_coord_restrictions,
|
|
861
|
+
)
|
|
862
|
+
# Update of supercharts and subcharts:
|
|
863
|
+
res._supercharts.update(self._supercharts)
|
|
864
|
+
for schart in self._supercharts:
|
|
865
|
+
schart._subcharts.add(res)
|
|
866
|
+
schart._dom_restrict[subset] = res
|
|
867
|
+
# Update of domain restrictions:
|
|
868
|
+
self._dom_restrict[subset] = res
|
|
869
|
+
return self._dom_restrict[subset]
|
|
870
|
+
|
|
871
|
+
def valid_coordinates(self, *coordinates, **kwds):
|
|
872
|
+
r"""
|
|
873
|
+
Check whether a tuple of coordinates can be the coordinates of a
|
|
874
|
+
point in the chart domain.
|
|
875
|
+
|
|
876
|
+
INPUT:
|
|
877
|
+
|
|
878
|
+
- ``*coordinates`` -- coordinate values
|
|
879
|
+
- ``**kwds`` -- options:
|
|
880
|
+
|
|
881
|
+
- ``parameters=None``, dictionary to set numerical values to
|
|
882
|
+
some parameters (see example below)
|
|
883
|
+
|
|
884
|
+
OUTPUT:
|
|
885
|
+
|
|
886
|
+
- ``True`` if the coordinate values are admissible in the chart
|
|
887
|
+
image, ``False`` otherwise
|
|
888
|
+
|
|
889
|
+
EXAMPLES::
|
|
890
|
+
|
|
891
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
892
|
+
sage: X.<x,y> = M.chart(coord_restrictions=lambda x,y: [abs(x)<1, y!=0])
|
|
893
|
+
sage: X.valid_coordinates(0, i)
|
|
894
|
+
True
|
|
895
|
+
sage: X.valid_coordinates(i, 1)
|
|
896
|
+
False
|
|
897
|
+
sage: X.valid_coordinates(i/2, 1)
|
|
898
|
+
True
|
|
899
|
+
sage: X.valid_coordinates(i/2, 0)
|
|
900
|
+
False
|
|
901
|
+
sage: X.valid_coordinates(2, 0)
|
|
902
|
+
False
|
|
903
|
+
|
|
904
|
+
Example of use with the keyword ``parameters`` to set a specific value
|
|
905
|
+
to a parameter appearing in the coordinate restrictions::
|
|
906
|
+
|
|
907
|
+
sage: var('a') # the parameter is a symbolic variable
|
|
908
|
+
a
|
|
909
|
+
sage: Y.<u,v> = M.chart(coord_restrictions=lambda u,v: abs(v)<a)
|
|
910
|
+
sage: Y.valid_coordinates(1, i, parameters={a: 2}) # setting a=2
|
|
911
|
+
True
|
|
912
|
+
sage: Y.valid_coordinates(1, 2*i, parameters={a: 2})
|
|
913
|
+
False
|
|
914
|
+
"""
|
|
915
|
+
if len(coordinates) != self.domain()._dim:
|
|
916
|
+
return False
|
|
917
|
+
if 'parameters' in kwds:
|
|
918
|
+
parameters = kwds['parameters']
|
|
919
|
+
else:
|
|
920
|
+
parameters = None
|
|
921
|
+
# Check of restrictions:
|
|
922
|
+
if self._restrictions:
|
|
923
|
+
substitutions = dict(zip(self._xx, coordinates))
|
|
924
|
+
if parameters:
|
|
925
|
+
substitutions.update(parameters)
|
|
926
|
+
return self._check_restrictions(self._restrictions, substitutions)
|
|
927
|
+
return True
|
|
928
|
+
|
|
929
|
+
def _check_restrictions(self, restrict, substitutions):
|
|
930
|
+
r"""
|
|
931
|
+
Recursive helper function to check the validity of coordinates
|
|
932
|
+
given some restrictions
|
|
933
|
+
|
|
934
|
+
INPUT:
|
|
935
|
+
|
|
936
|
+
- ``restrict`` -- a tuple of conditions (combined with 'or'), a list of
|
|
937
|
+
conditions (combined with 'and') or a single coordinate condition
|
|
938
|
+
- ``substitutions`` -- dictionary (keys: coordinates of ``self``) giving the
|
|
939
|
+
value of each coordinate
|
|
940
|
+
|
|
941
|
+
OUTPUT: boolean stating whether the conditions are fulfilled by the
|
|
942
|
+
coordinate values
|
|
943
|
+
|
|
944
|
+
TESTS::
|
|
945
|
+
|
|
946
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
947
|
+
sage: X.<x,y> = M.chart()
|
|
948
|
+
sage: X._check_restrictions(x>0, {x: pi, y: 0})
|
|
949
|
+
True
|
|
950
|
+
sage: X._check_restrictions(x>0, {x: -sqrt(2), y: 0})
|
|
951
|
+
False
|
|
952
|
+
sage: X._check_restrictions((x>0, [x<y, y<0]), {x: 1, y: 2})
|
|
953
|
+
True
|
|
954
|
+
sage: X._check_restrictions((x>0, [x<y, y<0]), {x: -1, y: 2})
|
|
955
|
+
False
|
|
956
|
+
sage: X._check_restrictions((x>0, [x<y, y<0]), {x: -1, y: -1/2})
|
|
957
|
+
True
|
|
958
|
+
sage: X._check_restrictions([(x<y, y<0), x>0], {x: 1, y: 2})
|
|
959
|
+
True
|
|
960
|
+
sage: X._check_restrictions([(x<y, y<0), x>0], {x: -1, y: 2})
|
|
961
|
+
False
|
|
962
|
+
sage: X._check_restrictions([(x<y, y<0), x>0], {x: 1, y: -2})
|
|
963
|
+
True
|
|
964
|
+
sage: X._check_restrictions([(x<y, y<0), x>0], {x: 2, y: 1})
|
|
965
|
+
False
|
|
966
|
+
"""
|
|
967
|
+
if isinstance(restrict, tuple): # case of 'or' conditions
|
|
968
|
+
return any(
|
|
969
|
+
self._check_restrictions(cond, substitutions) for cond in restrict
|
|
970
|
+
)
|
|
971
|
+
elif isinstance(restrict, (list, set, frozenset)): # case of 'and' conditions
|
|
972
|
+
return all(
|
|
973
|
+
self._check_restrictions(cond, substitutions) for cond in restrict
|
|
974
|
+
)
|
|
975
|
+
# Case of a single condition:
|
|
976
|
+
return bool(restrict.subs(substitutions))
|
|
977
|
+
|
|
978
|
+
def codomain(self):
|
|
979
|
+
r"""
|
|
980
|
+
Return the codomain of ``self`` as a set.
|
|
981
|
+
|
|
982
|
+
EXAMPLES::
|
|
983
|
+
|
|
984
|
+
sage: M = Manifold(2, 'M', field='complex', structure='topological')
|
|
985
|
+
sage: X.<x,y> = M.chart()
|
|
986
|
+
sage: X.codomain()
|
|
987
|
+
Vector space of dimension 2 over Complex Field with 53 bits of precision
|
|
988
|
+
"""
|
|
989
|
+
from sage.modules.free_module import VectorSpace
|
|
990
|
+
|
|
991
|
+
ambient = VectorSpace(self.manifold().base_field(), self.manifold().dimension())
|
|
992
|
+
if self._restrictions:
|
|
993
|
+
return self._restrict_set(ambient, self._restrictions)
|
|
994
|
+
else:
|
|
995
|
+
return ambient
|
|
996
|
+
|
|
997
|
+
def _restrict_set(self, universe, coord_restrictions):
|
|
998
|
+
"""
|
|
999
|
+
Return a set corresponding to coordinate restrictions.
|
|
1000
|
+
|
|
1001
|
+
EXAMPLES::
|
|
1002
|
+
|
|
1003
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
1004
|
+
sage: X.<x,y> = M.chart()
|
|
1005
|
+
sage: universe = RR^2
|
|
1006
|
+
sage: X._restrict_set(universe, x>0)
|
|
1007
|
+
{ (x, y) ∈ Vector space of dimension 2 over Real Field with 53 bits of precision : x > 0 }
|
|
1008
|
+
sage: X._restrict_set(universe, x>0)
|
|
1009
|
+
{ (x, y) ∈ Vector space of dimension 2 over Real Field with 53 bits of precision : x > 0 }
|
|
1010
|
+
sage: X._restrict_set(universe, (x>0, [x<y, y<0]))
|
|
1011
|
+
Set-theoretic union of
|
|
1012
|
+
{ (x, y) ∈ Vector space of dimension 2 over Real Field with 53 bits of precision : x > 0 } and
|
|
1013
|
+
{ (x, y) ∈ Vector space of dimension 2 over Real Field with 53 bits of precision : x < y, y < 0 }
|
|
1014
|
+
sage: X._restrict_set(universe, [(x<y, y<0), x>0])
|
|
1015
|
+
Set-theoretic intersection of
|
|
1016
|
+
Set-theoretic union of
|
|
1017
|
+
{ (x, y) ∈ Vector space of dimension 2 over Real Field with 53 bits of precision : x < y } and
|
|
1018
|
+
{ (x, y) ∈ Vector space of dimension 2 over Real Field with 53 bits of precision : y < 0 } and
|
|
1019
|
+
{ (x, y) ∈ Vector space of dimension 2 over Real Field with 53 bits of precision : x > 0 }
|
|
1020
|
+
"""
|
|
1021
|
+
if isinstance(coord_restrictions, tuple): # case of 'or' conditions
|
|
1022
|
+
A = self._restrict_set(universe, coord_restrictions[0])
|
|
1023
|
+
if len(coord_restrictions) == 1:
|
|
1024
|
+
return A
|
|
1025
|
+
else:
|
|
1026
|
+
return A.union(self._restrict_set(universe, coord_restrictions[1:]))
|
|
1027
|
+
elif isinstance(
|
|
1028
|
+
coord_restrictions, (list, set, frozenset)
|
|
1029
|
+
): # case of 'and' conditions
|
|
1030
|
+
A = self._restrict_set(universe, coord_restrictions[0])
|
|
1031
|
+
if len(coord_restrictions) == 1:
|
|
1032
|
+
return A
|
|
1033
|
+
else:
|
|
1034
|
+
return A.intersection(
|
|
1035
|
+
self._restrict_set(universe, coord_restrictions[1:])
|
|
1036
|
+
)
|
|
1037
|
+
# Case of a single condition:
|
|
1038
|
+
from sage.sets.condition_set import ConditionSet
|
|
1039
|
+
|
|
1040
|
+
return ConditionSet(universe, coord_restrictions, vars=self._xx)
|
|
1041
|
+
|
|
1042
|
+
def transition_map(
|
|
1043
|
+
self,
|
|
1044
|
+
other,
|
|
1045
|
+
transformations,
|
|
1046
|
+
intersection_name=None,
|
|
1047
|
+
restrictions1=None,
|
|
1048
|
+
restrictions2=None,
|
|
1049
|
+
):
|
|
1050
|
+
r"""
|
|
1051
|
+
Construct the transition map between the current chart,
|
|
1052
|
+
`(U, \varphi)` say, and another one, `(V, \psi)` say.
|
|
1053
|
+
|
|
1054
|
+
If `n` is the manifold's dimension, the *transition map*
|
|
1055
|
+
is the map
|
|
1056
|
+
|
|
1057
|
+
.. MATH::
|
|
1058
|
+
|
|
1059
|
+
\psi\circ\varphi^{-1}: \varphi(U\cap V) \subset K^n
|
|
1060
|
+
\rightarrow \psi(U\cap V) \subset K^n,
|
|
1061
|
+
|
|
1062
|
+
where `K` is the manifold's base field. In other words, the
|
|
1063
|
+
transition map expresses the coordinates `(y^1, \ldots, y^n)` of
|
|
1064
|
+
`(V, \psi)` in terms of the coordinates `(x^1, \ldots, x^n)` of
|
|
1065
|
+
`(U, \varphi)` on the open subset where the two charts intersect,
|
|
1066
|
+
i.e. on `U \cap V`.
|
|
1067
|
+
|
|
1068
|
+
INPUT:
|
|
1069
|
+
|
|
1070
|
+
- ``other`` -- the chart `(V, \psi)`
|
|
1071
|
+
- ``transformations`` -- tuple (or list) `(Y_1, \ldots, Y_n)`, where
|
|
1072
|
+
`Y_i` is the symbolic expression of the coordinate `y^i` in terms
|
|
1073
|
+
of the coordinates `(x^1, \ldots, x^n)`
|
|
1074
|
+
- ``intersection_name`` -- (default: ``None``) name to be given to the
|
|
1075
|
+
subset `U \cap V` if the latter differs from `U` or `V`
|
|
1076
|
+
- ``restrictions1`` -- (default: ``None``) list of conditions on the
|
|
1077
|
+
coordinates of the current chart that define `U \cap V` if the
|
|
1078
|
+
latter differs from `U`
|
|
1079
|
+
- ``restrictions2`` -- (default: ``None``) list of conditions on the
|
|
1080
|
+
coordinates of the chart `(V,\psi)` that define `U \cap V` if the
|
|
1081
|
+
latter differs from `V`
|
|
1082
|
+
|
|
1083
|
+
A restriction can be any symbolic equality or inequality involving
|
|
1084
|
+
the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items
|
|
1085
|
+
of the list ``restrictions`` are combined with the ``and`` operator;
|
|
1086
|
+
if some restrictions are to be combined with the ``or`` operator
|
|
1087
|
+
instead, they have to be passed as a tuple in some single item
|
|
1088
|
+
of the list ``restrictions``. For example::
|
|
1089
|
+
|
|
1090
|
+
restrictions = [x > y, (x != 0, y != 0), z^2 < x]
|
|
1091
|
+
|
|
1092
|
+
means ``(x > y) and ((x != 0) or (y != 0)) and (z^2 < x)``.
|
|
1093
|
+
If the list ``restrictions`` contains only one item, this
|
|
1094
|
+
item can be passed as such, i.e. writing ``x > y`` instead
|
|
1095
|
+
of the single element list ``[x > y]``.
|
|
1096
|
+
|
|
1097
|
+
OUTPUT:
|
|
1098
|
+
|
|
1099
|
+
- the transition map `\psi \circ \varphi^{-1}` defined on
|
|
1100
|
+
`U \cap V` as a :class:`CoordChange`
|
|
1101
|
+
|
|
1102
|
+
EXAMPLES:
|
|
1103
|
+
|
|
1104
|
+
Transition map between two stereographic charts on the circle `S^1`::
|
|
1105
|
+
|
|
1106
|
+
sage: M = Manifold(1, 'S^1', structure='topological')
|
|
1107
|
+
sage: U = M.open_subset('U') # Complement of the North pole
|
|
1108
|
+
sage: cU.<x> = U.chart() # Stereographic chart from the North pole
|
|
1109
|
+
sage: V = M.open_subset('V') # Complement of the South pole
|
|
1110
|
+
sage: cV.<y> = V.chart() # Stereographic chart from the South pole
|
|
1111
|
+
sage: M.declare_union(U,V) # S^1 is the union of U and V
|
|
1112
|
+
sage: trans = cU.transition_map(cV, 1/x, intersection_name='W',
|
|
1113
|
+
....: restrictions1= x!=0, restrictions2 = y!=0)
|
|
1114
|
+
sage: trans
|
|
1115
|
+
Change of coordinates from Chart (W, (x,)) to Chart (W, (y,))
|
|
1116
|
+
sage: trans.display()
|
|
1117
|
+
y = 1/x
|
|
1118
|
+
|
|
1119
|
+
The subset `W`, intersection of `U` and `V`, has been created by
|
|
1120
|
+
``transition_map()``::
|
|
1121
|
+
|
|
1122
|
+
sage: F = M.subset_family(); F
|
|
1123
|
+
Set {S^1, U, V, W} of open subsets of the 1-dimensional topological manifold S^1
|
|
1124
|
+
sage: W = F['W']
|
|
1125
|
+
sage: W is U.intersection(V)
|
|
1126
|
+
True
|
|
1127
|
+
sage: M.atlas()
|
|
1128
|
+
[Chart (U, (x,)), Chart (V, (y,)), Chart (W, (x,)), Chart (W, (y,))]
|
|
1129
|
+
|
|
1130
|
+
Transition map between the spherical chart and the Cartesian
|
|
1131
|
+
one on `\RR^2`::
|
|
1132
|
+
|
|
1133
|
+
sage: M = Manifold(2, 'R^2', structure='topological')
|
|
1134
|
+
sage: c_cart.<x,y> = M.chart()
|
|
1135
|
+
sage: U = M.open_subset('U') # the complement of the half line {y=0, x >= 0}
|
|
1136
|
+
sage: c_spher.<r,phi> = U.chart(r'r:(0,+oo) phi:(0,2*pi):\phi')
|
|
1137
|
+
sage: trans = c_spher.transition_map(c_cart, (r*cos(phi), r*sin(phi)),
|
|
1138
|
+
....: restrictions2=(y!=0, x<0))
|
|
1139
|
+
sage: trans
|
|
1140
|
+
Change of coordinates from Chart (U, (r, phi)) to Chart (U, (x, y))
|
|
1141
|
+
sage: trans.display()
|
|
1142
|
+
x = r*cos(phi)
|
|
1143
|
+
y = r*sin(phi)
|
|
1144
|
+
|
|
1145
|
+
In this case, no new subset has been created since `U \cap M = U`::
|
|
1146
|
+
|
|
1147
|
+
sage: M.subset_family()
|
|
1148
|
+
Set {R^2, U} of open subsets of the 2-dimensional topological manifold R^2
|
|
1149
|
+
|
|
1150
|
+
but a new chart has been created: `(U, (x, y))`::
|
|
1151
|
+
|
|
1152
|
+
sage: M.atlas()
|
|
1153
|
+
[Chart (R^2, (x, y)), Chart (U, (r, phi)), Chart (U, (x, y))]
|
|
1154
|
+
"""
|
|
1155
|
+
dom1 = self.domain()
|
|
1156
|
+
dom2 = other.domain()
|
|
1157
|
+
dom = dom1.intersection(dom2, name=intersection_name)
|
|
1158
|
+
if dom is dom1:
|
|
1159
|
+
chart1 = self
|
|
1160
|
+
else:
|
|
1161
|
+
chart1 = self.restrict(dom, restrictions1)
|
|
1162
|
+
if dom is dom2:
|
|
1163
|
+
chart2 = other
|
|
1164
|
+
else:
|
|
1165
|
+
chart2 = other.restrict(dom, restrictions2)
|
|
1166
|
+
if not isinstance(transformations, (tuple, list)):
|
|
1167
|
+
transformations = [transformations]
|
|
1168
|
+
return CoordChange(chart1, chart2, *transformations)
|
|
1169
|
+
|
|
1170
|
+
def preimage(self, codomain_subset, name=None, latex_name=None):
|
|
1171
|
+
"""
|
|
1172
|
+
Return the preimage (pullback) of ``codomain_subset`` under ``self``.
|
|
1173
|
+
|
|
1174
|
+
It is the subset of the domain of ``self`` formed by the points
|
|
1175
|
+
whose coordinate vectors lie in ``codomain_subset``.
|
|
1176
|
+
|
|
1177
|
+
INPUT:
|
|
1178
|
+
|
|
1179
|
+
- ``codomain_subset`` -- an instance of
|
|
1180
|
+
:class:`~sage.geometry.convex_set.ConvexSet_base` or another
|
|
1181
|
+
object with a ``__contains__`` method that accepts coordinate
|
|
1182
|
+
vectors
|
|
1183
|
+
- ``name`` -- string; name (symbol) given to the subset
|
|
1184
|
+
- ``latex_name`` -- string (default: ``None``); LaTeX symbol to
|
|
1185
|
+
denote the subset; if none are provided, it is set to ``name``
|
|
1186
|
+
|
|
1187
|
+
OUTPUT:
|
|
1188
|
+
|
|
1189
|
+
- either a :class:`~sage.manifolds.manifold.TopologicalManifold` or
|
|
1190
|
+
a :class:`~sage.manifolds.subsets.pullback.ManifoldSubsetPullback`
|
|
1191
|
+
|
|
1192
|
+
EXAMPLES::
|
|
1193
|
+
|
|
1194
|
+
sage: M = Manifold(2, 'R^2', structure='topological')
|
|
1195
|
+
sage: c_cart.<x,y> = M.chart() # Cartesian coordinates on R^2
|
|
1196
|
+
|
|
1197
|
+
Pulling back a polytope under a chart::
|
|
1198
|
+
|
|
1199
|
+
sage: # needs sage.geometry.polyhedron
|
|
1200
|
+
sage: P = Polyhedron(vertices=[[0, 0], [1, 2], [2, 1]]); P
|
|
1201
|
+
A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices
|
|
1202
|
+
sage: McP = c_cart.preimage(P); McP
|
|
1203
|
+
Subset x_y_inv_P of the 2-dimensional topological manifold R^2
|
|
1204
|
+
sage: M((1, 2)) in McP
|
|
1205
|
+
True
|
|
1206
|
+
sage: M((2, 0)) in McP
|
|
1207
|
+
False
|
|
1208
|
+
|
|
1209
|
+
Pulling back the interior of a polytope under a chart::
|
|
1210
|
+
|
|
1211
|
+
sage: # needs sage.geometry.polyhedron
|
|
1212
|
+
sage: int_P = P.interior(); int_P
|
|
1213
|
+
Relative interior of
|
|
1214
|
+
a 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices
|
|
1215
|
+
sage: McInt_P = c_cart.preimage(int_P, name='McInt_P'); McInt_P
|
|
1216
|
+
Open subset McInt_P of the 2-dimensional topological manifold R^2
|
|
1217
|
+
sage: M((0, 0)) in McInt_P
|
|
1218
|
+
False
|
|
1219
|
+
sage: M((1, 1)) in McInt_P
|
|
1220
|
+
True
|
|
1221
|
+
|
|
1222
|
+
Pulling back a point lattice::
|
|
1223
|
+
|
|
1224
|
+
sage: W = span([[1, 0], [3, 5]], ZZ); W
|
|
1225
|
+
Free module of degree 2 and rank 2 over Integer Ring
|
|
1226
|
+
Echelon basis matrix:
|
|
1227
|
+
[1 0]
|
|
1228
|
+
[0 5]
|
|
1229
|
+
sage: McW = c_cart.pullback(W, name='McW'); McW
|
|
1230
|
+
Subset McW of the 2-dimensional topological manifold R^2
|
|
1231
|
+
sage: M((4, 5)) in McW
|
|
1232
|
+
True
|
|
1233
|
+
sage: M((4, 4)) in McW
|
|
1234
|
+
False
|
|
1235
|
+
|
|
1236
|
+
Pulling back a real vector subspaces::
|
|
1237
|
+
|
|
1238
|
+
sage: V = span([[1, 2]], RR); V
|
|
1239
|
+
Vector space of degree 2 and dimension 1 over Real Field with 53 bits of precision
|
|
1240
|
+
Basis matrix:
|
|
1241
|
+
[1.00000000000000 2.00000000000000]
|
|
1242
|
+
sage: McV = c_cart.pullback(V, name='McV'); McV
|
|
1243
|
+
Subset McV of the 2-dimensional topological manifold R^2
|
|
1244
|
+
sage: M((2, 4)) in McV
|
|
1245
|
+
True
|
|
1246
|
+
sage: M((1, 0)) in McV
|
|
1247
|
+
False
|
|
1248
|
+
|
|
1249
|
+
Pulling back a finite set of points::
|
|
1250
|
+
|
|
1251
|
+
sage: F = Family([vector(QQ, [1, 2], immutable=True),
|
|
1252
|
+
....: vector(QQ, [2, 3], immutable=True)])
|
|
1253
|
+
sage: McF = c_cart.pullback(F, name='McF'); McF
|
|
1254
|
+
Subset McF of the 2-dimensional topological manifold R^2
|
|
1255
|
+
sage: M((2, 3)) in McF
|
|
1256
|
+
True
|
|
1257
|
+
sage: M((0, 0)) in McF
|
|
1258
|
+
False
|
|
1259
|
+
|
|
1260
|
+
Pulling back the integers::
|
|
1261
|
+
|
|
1262
|
+
sage: R = manifolds.RealLine(); R
|
|
1263
|
+
Real number line ℝ
|
|
1264
|
+
sage: McZ = R.canonical_chart().pullback(ZZ, name='ℤ'); McZ
|
|
1265
|
+
Subset ℤ of the Real number line ℝ
|
|
1266
|
+
sage: R((3/2,)) in McZ
|
|
1267
|
+
False
|
|
1268
|
+
sage: R((-2,)) in McZ
|
|
1269
|
+
True
|
|
1270
|
+
"""
|
|
1271
|
+
from sage.manifolds.subsets.pullback import ManifoldSubsetPullback
|
|
1272
|
+
|
|
1273
|
+
return ManifoldSubsetPullback(
|
|
1274
|
+
self, codomain_subset, name=name, latex_name=latex_name
|
|
1275
|
+
)
|
|
1276
|
+
|
|
1277
|
+
pullback = preimage
|
|
1278
|
+
|
|
1279
|
+
def function_ring(self):
|
|
1280
|
+
"""
|
|
1281
|
+
Return the ring of coordinate functions on ``self``.
|
|
1282
|
+
|
|
1283
|
+
EXAMPLES::
|
|
1284
|
+
|
|
1285
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
1286
|
+
sage: X.<x,y> = M.chart()
|
|
1287
|
+
sage: X.function_ring()
|
|
1288
|
+
Ring of chart functions on Chart (M, (x, y))
|
|
1289
|
+
"""
|
|
1290
|
+
|
|
1291
|
+
return ChartFunctionRing(self)
|
|
1292
|
+
|
|
1293
|
+
def function(self, expression, calc_method=None, expansion_symbol=None, order=None):
|
|
1294
|
+
r"""
|
|
1295
|
+
Define a coordinate function to the base field.
|
|
1296
|
+
|
|
1297
|
+
If the current chart belongs to the atlas of a `n`-dimensional manifold
|
|
1298
|
+
over a topological field `K`, a *coordinate function* is a map
|
|
1299
|
+
|
|
1300
|
+
.. MATH::
|
|
1301
|
+
|
|
1302
|
+
\begin{array}{cccc}
|
|
1303
|
+
f:& V\subset K^n & \longrightarrow & K \\
|
|
1304
|
+
& (x^1,\ldots, x^n) & \longmapsto & f(x^1,\ldots, x^n),
|
|
1305
|
+
\end{array}
|
|
1306
|
+
|
|
1307
|
+
where `V` is the chart codomain and `(x^1, \ldots, x^n)` are the
|
|
1308
|
+
chart coordinates.
|
|
1309
|
+
|
|
1310
|
+
INPUT:
|
|
1311
|
+
|
|
1312
|
+
- ``expression`` -- a symbolic expression involving the chart
|
|
1313
|
+
coordinates, to represent `f(x^1,\ldots, x^n)`
|
|
1314
|
+
|
|
1315
|
+
- ``calc_method`` -- string (default: ``None``); the calculus method
|
|
1316
|
+
with respect to which the internal expression of the function must be
|
|
1317
|
+
initialized from ``expression``; one of
|
|
1318
|
+
|
|
1319
|
+
- ``'SR'``: Sage's default symbolic engine (Symbolic Ring)
|
|
1320
|
+
- ``'sympy'``: SymPy
|
|
1321
|
+
- ``None``: the chart current calculus method is assumed
|
|
1322
|
+
|
|
1323
|
+
- ``expansion_symbol`` -- (default: ``None``) symbolic variable (the
|
|
1324
|
+
"small parameter") with respect to which the coordinate expression is
|
|
1325
|
+
expanded in power series (around the zero value of this variable)
|
|
1326
|
+
|
|
1327
|
+
- ``order`` -- integer (default: ``None``); the order of the expansion
|
|
1328
|
+
if ``expansion_symbol`` is not ``None``; the *order* is defined as
|
|
1329
|
+
the degree of the polynomial representing the truncated power series
|
|
1330
|
+
in ``expansion_symbol``.
|
|
1331
|
+
|
|
1332
|
+
.. WARNING::
|
|
1333
|
+
|
|
1334
|
+
The value of ``order`` is `n-1`, where `n` is the order of the
|
|
1335
|
+
big `O` in the power series expansion
|
|
1336
|
+
|
|
1337
|
+
OUTPUT:
|
|
1338
|
+
|
|
1339
|
+
- instance of
|
|
1340
|
+
:class:`~sage.manifolds.chart_func.ChartFunction`
|
|
1341
|
+
representing the coordinate function `f`
|
|
1342
|
+
|
|
1343
|
+
EXAMPLES:
|
|
1344
|
+
|
|
1345
|
+
A symbolic coordinate function::
|
|
1346
|
+
|
|
1347
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
1348
|
+
sage: X.<x,y> = M.chart()
|
|
1349
|
+
sage: f = X.function(sin(x*y))
|
|
1350
|
+
sage: f
|
|
1351
|
+
sin(x*y)
|
|
1352
|
+
sage: type(f)
|
|
1353
|
+
<class 'sage.manifolds.chart_func.ChartFunctionRing_with_category.element_class'>
|
|
1354
|
+
sage: f.display()
|
|
1355
|
+
(x, y) ↦ sin(x*y)
|
|
1356
|
+
sage: f(2,3)
|
|
1357
|
+
sin(6)
|
|
1358
|
+
|
|
1359
|
+
Using SymPy for the internal representation of the function (dictionary
|
|
1360
|
+
``_express``)::
|
|
1361
|
+
|
|
1362
|
+
sage: g = X.function(x^2 + x*cos(y), calc_method='sympy')
|
|
1363
|
+
sage: g._express
|
|
1364
|
+
{'sympy': x**2 + x*cos(y)}
|
|
1365
|
+
|
|
1366
|
+
On the contrary, for ``f``, only the ``SR`` part has been initialized::
|
|
1367
|
+
|
|
1368
|
+
sage: f._express
|
|
1369
|
+
{'SR': sin(x*y)}
|
|
1370
|
+
|
|
1371
|
+
See :class:`~sage.manifolds.chart_func.ChartFunction` for more examples.
|
|
1372
|
+
"""
|
|
1373
|
+
parent = self.function_ring()
|
|
1374
|
+
return parent.element_class(
|
|
1375
|
+
parent,
|
|
1376
|
+
expression,
|
|
1377
|
+
calc_method=calc_method,
|
|
1378
|
+
expansion_symbol=expansion_symbol,
|
|
1379
|
+
order=order,
|
|
1380
|
+
)
|
|
1381
|
+
|
|
1382
|
+
def zero_function(self):
|
|
1383
|
+
r"""
|
|
1384
|
+
Return the zero function of the coordinates.
|
|
1385
|
+
|
|
1386
|
+
If the current chart belongs to the atlas of a `n`-dimensional manifold
|
|
1387
|
+
over a topological field `K`, the zero coordinate function is the map
|
|
1388
|
+
|
|
1389
|
+
.. MATH::
|
|
1390
|
+
|
|
1391
|
+
\begin{array}{cccc}
|
|
1392
|
+
f:& V\subset K^n & \longrightarrow & K \\
|
|
1393
|
+
& (x^1,\ldots, x^n) & \longmapsto & 0,
|
|
1394
|
+
\end{array}
|
|
1395
|
+
|
|
1396
|
+
where `V` is the chart codomain.
|
|
1397
|
+
|
|
1398
|
+
See class :class:`~sage.manifolds.chart_func.ChartFunction`
|
|
1399
|
+
for a complete documentation.
|
|
1400
|
+
|
|
1401
|
+
OUTPUT:
|
|
1402
|
+
|
|
1403
|
+
- a :class:`~sage.manifolds.chart_func.ChartFunction`
|
|
1404
|
+
representing the zero coordinate function `f`
|
|
1405
|
+
|
|
1406
|
+
EXAMPLES::
|
|
1407
|
+
|
|
1408
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
1409
|
+
sage: X.<x,y> = M.chart()
|
|
1410
|
+
sage: X.zero_function()
|
|
1411
|
+
0
|
|
1412
|
+
sage: X.zero_function().display()
|
|
1413
|
+
(x, y) ↦ 0
|
|
1414
|
+
sage: type(X.zero_function())
|
|
1415
|
+
<class 'sage.manifolds.chart_func.ChartFunctionRing_with_category.element_class'>
|
|
1416
|
+
|
|
1417
|
+
The result is cached::
|
|
1418
|
+
|
|
1419
|
+
sage: X.zero_function() is X.zero_function()
|
|
1420
|
+
True
|
|
1421
|
+
|
|
1422
|
+
Zero function on a `p`-adic manifold::
|
|
1423
|
+
|
|
1424
|
+
sage: # needs sage.rings.padics
|
|
1425
|
+
sage: M = Manifold(2, 'M', structure='topological', field=Qp(5)); M
|
|
1426
|
+
2-dimensional topological manifold M over the 5-adic Field with
|
|
1427
|
+
capped relative precision 20
|
|
1428
|
+
sage: X.<x,y> = M.chart()
|
|
1429
|
+
sage: X.zero_function()
|
|
1430
|
+
0
|
|
1431
|
+
sage: X.zero_function().display()
|
|
1432
|
+
(x, y) ↦ 0
|
|
1433
|
+
"""
|
|
1434
|
+
return self.function_ring().zero()
|
|
1435
|
+
|
|
1436
|
+
def one_function(self):
|
|
1437
|
+
r"""
|
|
1438
|
+
Return the constant function of the coordinates equal to one.
|
|
1439
|
+
|
|
1440
|
+
If the current chart belongs to the atlas of a `n`-dimensional manifold
|
|
1441
|
+
over a topological field `K`, the "one" coordinate function is the map
|
|
1442
|
+
|
|
1443
|
+
.. MATH::
|
|
1444
|
+
|
|
1445
|
+
\begin{array}{cccc}
|
|
1446
|
+
f:& V\subset K^n & \longrightarrow & K \\
|
|
1447
|
+
& (x^1,\ldots, x^n) & \longmapsto & 1,
|
|
1448
|
+
\end{array}
|
|
1449
|
+
|
|
1450
|
+
where `V` is the chart codomain.
|
|
1451
|
+
|
|
1452
|
+
See class :class:`~sage.manifolds.chart_func.ChartFunction`
|
|
1453
|
+
for a complete documentation.
|
|
1454
|
+
|
|
1455
|
+
OUTPUT:
|
|
1456
|
+
|
|
1457
|
+
- a :class:`~sage.manifolds.chart_func.ChartFunction`
|
|
1458
|
+
representing the one coordinate function `f`
|
|
1459
|
+
|
|
1460
|
+
EXAMPLES::
|
|
1461
|
+
|
|
1462
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
1463
|
+
sage: X.<x,y> = M.chart()
|
|
1464
|
+
sage: X.one_function()
|
|
1465
|
+
1
|
|
1466
|
+
sage: X.one_function().display()
|
|
1467
|
+
(x, y) ↦ 1
|
|
1468
|
+
sage: type(X.one_function())
|
|
1469
|
+
<class 'sage.manifolds.chart_func.ChartFunctionRing_with_category.element_class'>
|
|
1470
|
+
|
|
1471
|
+
The result is cached::
|
|
1472
|
+
|
|
1473
|
+
sage: X.one_function() is X.one_function()
|
|
1474
|
+
True
|
|
1475
|
+
|
|
1476
|
+
One function on a `p`-adic manifold::
|
|
1477
|
+
|
|
1478
|
+
sage: # needs sage.rings.padics
|
|
1479
|
+
sage: M = Manifold(2, 'M', structure='topological', field=Qp(5)); M
|
|
1480
|
+
2-dimensional topological manifold M over the 5-adic Field with
|
|
1481
|
+
capped relative precision 20
|
|
1482
|
+
sage: X.<x,y> = M.chart()
|
|
1483
|
+
sage: X.one_function()
|
|
1484
|
+
1 + O(5^20)
|
|
1485
|
+
sage: X.one_function().display()
|
|
1486
|
+
(x, y) ↦ 1 + O(5^20)
|
|
1487
|
+
"""
|
|
1488
|
+
return self.function_ring().one()
|
|
1489
|
+
|
|
1490
|
+
def calculus_method(self):
|
|
1491
|
+
r"""
|
|
1492
|
+
Return the interface governing the calculus engine for expressions
|
|
1493
|
+
involving coordinates of this chart.
|
|
1494
|
+
|
|
1495
|
+
The calculus engine can be one of the following:
|
|
1496
|
+
|
|
1497
|
+
- Sage's symbolic engine (Pynac + Maxima), implemented via the
|
|
1498
|
+
Symbolic Ring ``SR``
|
|
1499
|
+
- SymPy
|
|
1500
|
+
|
|
1501
|
+
.. SEEALSO::
|
|
1502
|
+
|
|
1503
|
+
:class:`~sage.manifolds.calculus_method.CalculusMethod` for a
|
|
1504
|
+
complete documentation.
|
|
1505
|
+
|
|
1506
|
+
OUTPUT: an instance of :class:`~sage.manifolds.calculus_method.CalculusMethod`
|
|
1507
|
+
|
|
1508
|
+
EXAMPLES:
|
|
1509
|
+
|
|
1510
|
+
The default calculus method relies on Sage's Symbolic Ring::
|
|
1511
|
+
|
|
1512
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
1513
|
+
sage: X.<x,y> = M.chart()
|
|
1514
|
+
sage: X.calculus_method()
|
|
1515
|
+
Available calculus methods (* = current):
|
|
1516
|
+
- SR (default) (*)
|
|
1517
|
+
- sympy
|
|
1518
|
+
|
|
1519
|
+
Accordingly the method
|
|
1520
|
+
:meth:`~sage.manifolds.chart_func.ChartFunction.expr` of a function
|
|
1521
|
+
``f`` defined on the chart ``X`` returns a Sage symbolic expression::
|
|
1522
|
+
|
|
1523
|
+
sage: f = X.function(x^2 + cos(y)*sin(x))
|
|
1524
|
+
sage: f.expr()
|
|
1525
|
+
x^2 + cos(y)*sin(x)
|
|
1526
|
+
sage: type(f.expr())
|
|
1527
|
+
<class 'sage.symbolic.expression.Expression'>
|
|
1528
|
+
sage: parent(f.expr())
|
|
1529
|
+
Symbolic Ring
|
|
1530
|
+
sage: f.display()
|
|
1531
|
+
(x, y) ↦ x^2 + cos(y)*sin(x)
|
|
1532
|
+
|
|
1533
|
+
Changing to SymPy::
|
|
1534
|
+
|
|
1535
|
+
sage: X.calculus_method().set('sympy')
|
|
1536
|
+
sage: f.expr()
|
|
1537
|
+
x**2 + sin(x)*cos(y)
|
|
1538
|
+
sage: type(f.expr())
|
|
1539
|
+
<class 'sympy.core.add.Add'>
|
|
1540
|
+
sage: parent(f.expr())
|
|
1541
|
+
<class 'sympy.core.add.Add'>
|
|
1542
|
+
sage: f.display()
|
|
1543
|
+
(x, y) ↦ x**2 + sin(x)*cos(y)
|
|
1544
|
+
|
|
1545
|
+
Back to the Symbolic Ring::
|
|
1546
|
+
|
|
1547
|
+
sage: X.calculus_method().set('SR')
|
|
1548
|
+
sage: f.display()
|
|
1549
|
+
(x, y) ↦ x^2 + cos(y)*sin(x)
|
|
1550
|
+
"""
|
|
1551
|
+
return self._calc_method
|
|
1552
|
+
|
|
1553
|
+
def multifunction(self, *expressions):
|
|
1554
|
+
r"""
|
|
1555
|
+
Define a coordinate function to some Cartesian power of the base field.
|
|
1556
|
+
|
|
1557
|
+
If `n` and `m` are two positive integers and `(U, \varphi)` is a
|
|
1558
|
+
chart on a topological manifold `M` of dimension `n` over a
|
|
1559
|
+
topological field `K`, a *multi-coordinate function* associated
|
|
1560
|
+
to `(U,\varphi)` is a map
|
|
1561
|
+
|
|
1562
|
+
.. MATH::
|
|
1563
|
+
|
|
1564
|
+
\begin{array}{llcl}
|
|
1565
|
+
f:& V \subset K^n & \longrightarrow & K^m \\
|
|
1566
|
+
& (x^1, \ldots, x^n) & \longmapsto & (f_1(x^1, \ldots, x^n),
|
|
1567
|
+
\ldots, f_m(x^1, \ldots, x^n)),
|
|
1568
|
+
\end{array}
|
|
1569
|
+
|
|
1570
|
+
where `V` is the codomain of `\varphi`. In other words, `f` is a
|
|
1571
|
+
`K^m`-valued function of the coordinates associated to the chart
|
|
1572
|
+
`(U, \varphi)`.
|
|
1573
|
+
|
|
1574
|
+
See :class:`~sage.manifolds.chart_func.MultiCoordFunction` for a
|
|
1575
|
+
complete documentation.
|
|
1576
|
+
|
|
1577
|
+
INPUT:
|
|
1578
|
+
|
|
1579
|
+
- ``expressions`` -- list (or tuple) of `m` elements to construct the
|
|
1580
|
+
coordinate functions `f_i` (`1\leq i \leq m`); for
|
|
1581
|
+
symbolic coordinate functions, this must be symbolic expressions
|
|
1582
|
+
involving the chart coordinates, while for numerical coordinate
|
|
1583
|
+
functions, this must be data file names
|
|
1584
|
+
|
|
1585
|
+
OUTPUT:
|
|
1586
|
+
|
|
1587
|
+
- a :class:`~sage.manifolds.chart_func.MultiCoordFunction`
|
|
1588
|
+
representing `f`
|
|
1589
|
+
|
|
1590
|
+
EXAMPLES:
|
|
1591
|
+
|
|
1592
|
+
Function of two coordinates with values in `\RR^3`::
|
|
1593
|
+
|
|
1594
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
1595
|
+
sage: X.<x,y> = M.chart()
|
|
1596
|
+
sage: f = X.multifunction(x+y, sin(x*y), x^2 + 3*y); f
|
|
1597
|
+
Coordinate functions (x + y, sin(x*y), x^2 + 3*y) on the Chart (M, (x, y))
|
|
1598
|
+
sage: f(2,3)
|
|
1599
|
+
(5, sin(6), 13)
|
|
1600
|
+
|
|
1601
|
+
TESTS::
|
|
1602
|
+
|
|
1603
|
+
sage: type(f)
|
|
1604
|
+
<class 'sage.manifolds.chart_func.MultiCoordFunction'>
|
|
1605
|
+
"""
|
|
1606
|
+
from sage.manifolds.chart_func import MultiCoordFunction
|
|
1607
|
+
|
|
1608
|
+
return MultiCoordFunction(self, expressions)
|
|
1609
|
+
|
|
1610
|
+
|
|
1611
|
+
# *****************************************************************************
|
|
1612
|
+
|
|
1613
|
+
|
|
1614
|
+
class RealChart(Chart):
|
|
1615
|
+
r"""
|
|
1616
|
+
Chart on a topological manifold over `\RR`.
|
|
1617
|
+
|
|
1618
|
+
Given a topological manifold `M` of dimension `n` over `\RR`, a *chart*
|
|
1619
|
+
on `M` is a pair `(U,\varphi)`, where `U` is an open subset of `M` and
|
|
1620
|
+
`\varphi : U \to V \subset \RR^n` is a homeomorphism from `U` to
|
|
1621
|
+
an open subset `V` of `\RR^n`.
|
|
1622
|
+
|
|
1623
|
+
The components `(x^1, \ldots, x^n)` of `\varphi`, defined by
|
|
1624
|
+
`\varphi(p) = (x^1(p), \ldots, x^n(p))\in \RR^n` for any point
|
|
1625
|
+
`p \in U`, are called the *coordinates* of the chart `(U, \varphi)`.
|
|
1626
|
+
|
|
1627
|
+
INPUT:
|
|
1628
|
+
|
|
1629
|
+
- ``domain`` -- open subset `U` on which the chart is defined
|
|
1630
|
+
- ``coordinates`` -- (default: '' (empty string)) single string defining
|
|
1631
|
+
the coordinate symbols, with ``' '`` (whitespace) as a separator; each
|
|
1632
|
+
item has at most four fields, separated by a colon (``:``):
|
|
1633
|
+
|
|
1634
|
+
1. the coordinate symbol (a letter or a few letters)
|
|
1635
|
+
2. (optional) the interval `I` defining the coordinate range: if not
|
|
1636
|
+
provided, the coordinate is assumed to span all `\RR`; otherwise
|
|
1637
|
+
`I` must be provided in the form ``(a,b)`` (or equivalently
|
|
1638
|
+
``]a,b[``); the bounds ``a`` and ``b`` can be ``+/-Infinity``,
|
|
1639
|
+
``Inf``, ``infinity``, ``inf`` or ``oo``; for *singular*
|
|
1640
|
+
coordinates, non-open intervals such as ``[a,b]`` and ``(a,b]``
|
|
1641
|
+
(or equivalently ``]a,b]``) are allowed; note that the interval
|
|
1642
|
+
declaration must not contain any whitespace
|
|
1643
|
+
3. (optional) indicator of the periodic character of the coordinate,
|
|
1644
|
+
either as ``period=T``, where ``T`` is the period, or as the keyword
|
|
1645
|
+
``periodic`` (the value of the period is then deduced from the
|
|
1646
|
+
interval `I` declared in field 2; see examples below)
|
|
1647
|
+
4. (optional) the LaTeX spelling of the coordinate; if not provided the
|
|
1648
|
+
coordinate symbol given in the first field will be used
|
|
1649
|
+
|
|
1650
|
+
The order of fields 2 to 4 does not matter and each of them can be
|
|
1651
|
+
omitted. If it contains any LaTeX expression, the string ``coordinates``
|
|
1652
|
+
must be declared with the prefix 'r' (for "raw") to allow for a proper
|
|
1653
|
+
treatment of LaTeX's backslash character (see examples below).
|
|
1654
|
+
If interval range, no period and no LaTeX spelling are to be set for any
|
|
1655
|
+
coordinate, the argument ``coordinates`` can be omitted when the shortcut
|
|
1656
|
+
operator ``<,>`` is used to declare the chart (see examples below).
|
|
1657
|
+
- ``calc_method`` -- (default: ``None``) string defining the calculus
|
|
1658
|
+
method for computations involving coordinates of the chart; must be
|
|
1659
|
+
one of
|
|
1660
|
+
|
|
1661
|
+
- ``'SR'``: Sage's default symbolic engine (Symbolic Ring)
|
|
1662
|
+
- ``'sympy'``: SymPy
|
|
1663
|
+
- ``None``: the default of
|
|
1664
|
+
:class:`~sage.manifolds.calculus_method.CalculusMethod` will be
|
|
1665
|
+
used
|
|
1666
|
+
- ``names`` -- (default: ``None``) unused argument, except if
|
|
1667
|
+
``coordinates`` is not provided; it must then be a tuple containing
|
|
1668
|
+
the coordinate symbols (this is guaranteed if the shortcut operator
|
|
1669
|
+
``<,>`` is used)
|
|
1670
|
+
- ``coord_restrictions`` -- additional restrictions on the coordinates.
|
|
1671
|
+
A restriction can be any symbolic equality or inequality involving
|
|
1672
|
+
the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items
|
|
1673
|
+
of the list (or set or frozenset) ``coord_restrictions`` are combined
|
|
1674
|
+
with the ``and`` operator; if some restrictions are to be combined with
|
|
1675
|
+
the ``or`` operator instead, they have to be passed as a tuple in some
|
|
1676
|
+
single item of the list (or set or frozenset) ``coord_restrictions``.
|
|
1677
|
+
For example::
|
|
1678
|
+
|
|
1679
|
+
coord_restrictions=[x > y, (x != 0, y != 0), z^2 < x]
|
|
1680
|
+
|
|
1681
|
+
means ``(x > y) and ((x != 0) or (y != 0)) and (z^2 < x)``.
|
|
1682
|
+
If the list ``coord_restrictions`` contains only one item, this
|
|
1683
|
+
item can be passed as such, i.e. writing ``x > y`` instead
|
|
1684
|
+
of the single element list ``[x > y]``. If the chart variables have
|
|
1685
|
+
not been declared as variables yet, ``coord_restrictions`` must
|
|
1686
|
+
be ``lambda``-quoted.
|
|
1687
|
+
|
|
1688
|
+
EXAMPLES:
|
|
1689
|
+
|
|
1690
|
+
Cartesian coordinates on `\RR^3`::
|
|
1691
|
+
|
|
1692
|
+
sage: M = Manifold(3, 'R^3', r'\RR^3', structure='topological',
|
|
1693
|
+
....: start_index=1)
|
|
1694
|
+
sage: c_cart = M.chart('x y z'); c_cart
|
|
1695
|
+
Chart (R^3, (x, y, z))
|
|
1696
|
+
sage: type(c_cart)
|
|
1697
|
+
<class 'sage.manifolds.chart.RealChart'>
|
|
1698
|
+
|
|
1699
|
+
To have the coordinates accessible as global variables, one has to set::
|
|
1700
|
+
|
|
1701
|
+
sage: (x,y,z) = c_cart[:]
|
|
1702
|
+
|
|
1703
|
+
However, a shortcut is to use the declarator ``<x,y,z>`` in the left-hand
|
|
1704
|
+
side of the chart declaration (there is then no need to pass the string
|
|
1705
|
+
``'x y z'`` to ``chart()``)::
|
|
1706
|
+
|
|
1707
|
+
sage: M = Manifold(3, 'R^3', r'\RR^3', structure='topological',
|
|
1708
|
+
....: start_index=1)
|
|
1709
|
+
sage: c_cart.<x,y,z> = M.chart(); c_cart
|
|
1710
|
+
Chart (R^3, (x, y, z))
|
|
1711
|
+
|
|
1712
|
+
The coordinates are then immediately accessible::
|
|
1713
|
+
|
|
1714
|
+
sage: y
|
|
1715
|
+
y
|
|
1716
|
+
sage: y is c_cart[2]
|
|
1717
|
+
True
|
|
1718
|
+
|
|
1719
|
+
Note that ``x, y, z`` declared in ``<x,y,z>`` are mere Python variable
|
|
1720
|
+
names and do not have to coincide with the coordinate symbols; for
|
|
1721
|
+
instance, one may write::
|
|
1722
|
+
|
|
1723
|
+
sage: M = Manifold(3, 'R^3', r'\RR^3', structure='topological',
|
|
1724
|
+
....: start_index=1)
|
|
1725
|
+
sage: c_cart.<x1,y1,z1> = M.chart('x y z'); c_cart
|
|
1726
|
+
Chart (R^3, (x, y, z))
|
|
1727
|
+
|
|
1728
|
+
Then ``y`` is not known as a global variable and the coordinate `y`
|
|
1729
|
+
is accessible only through the global variable ``y1``::
|
|
1730
|
+
|
|
1731
|
+
sage: y1
|
|
1732
|
+
y
|
|
1733
|
+
sage: y1 is c_cart[2]
|
|
1734
|
+
True
|
|
1735
|
+
|
|
1736
|
+
However, having the name of the Python variable coincide with the
|
|
1737
|
+
coordinate symbol is quite convenient; so it is recommended to declare::
|
|
1738
|
+
|
|
1739
|
+
sage: forget() # for doctests only
|
|
1740
|
+
sage: M = Manifold(3, 'R^3', r'\RR^3', structure='topological', start_index=1)
|
|
1741
|
+
sage: c_cart.<x,y,z> = M.chart()
|
|
1742
|
+
|
|
1743
|
+
Spherical coordinates on the subset `U` of `\RR^3` that is the
|
|
1744
|
+
complement of the half-plane `\{y=0, x \geq 0\}`::
|
|
1745
|
+
|
|
1746
|
+
sage: U = M.open_subset('U')
|
|
1747
|
+
sage: c_spher.<r,th,ph> = U.chart(r'r:(0,+oo) th:(0,pi):\theta ph:(0,2*pi):\phi')
|
|
1748
|
+
sage: c_spher
|
|
1749
|
+
Chart (U, (r, th, ph))
|
|
1750
|
+
|
|
1751
|
+
Note the prefix 'r' for the string defining the coordinates in the
|
|
1752
|
+
arguments of ``chart``.
|
|
1753
|
+
|
|
1754
|
+
Coordinates are Sage symbolic variables (see
|
|
1755
|
+
:mod:`sage.symbolic.expression`)::
|
|
1756
|
+
|
|
1757
|
+
sage: type(th)
|
|
1758
|
+
<class 'sage.symbolic.expression.Expression'>
|
|
1759
|
+
sage: latex(th)
|
|
1760
|
+
{\theta}
|
|
1761
|
+
sage: assumptions(th)
|
|
1762
|
+
[th is real, th > 0, th < pi]
|
|
1763
|
+
|
|
1764
|
+
Coordinate are also accessible by their indices::
|
|
1765
|
+
|
|
1766
|
+
sage: x1 = c_spher[1]; x2 = c_spher[2]; x3 = c_spher[3]
|
|
1767
|
+
sage: [x1, x2, x3]
|
|
1768
|
+
[r, th, ph]
|
|
1769
|
+
sage: (x1, x2, x3) == (r, th, ph)
|
|
1770
|
+
True
|
|
1771
|
+
|
|
1772
|
+
The full set of coordinates is obtained by means of the slice ``[:]``::
|
|
1773
|
+
|
|
1774
|
+
sage: c_cart[:]
|
|
1775
|
+
(x, y, z)
|
|
1776
|
+
sage: c_spher[:]
|
|
1777
|
+
(r, th, ph)
|
|
1778
|
+
|
|
1779
|
+
Let us check that the declared coordinate ranges have been taken into
|
|
1780
|
+
account::
|
|
1781
|
+
|
|
1782
|
+
sage: c_cart.coord_range()
|
|
1783
|
+
x: (-oo, +oo); y: (-oo, +oo); z: (-oo, +oo)
|
|
1784
|
+
sage: c_spher.coord_range()
|
|
1785
|
+
r: (0, +oo); th: (0, pi); ph: (0, 2*pi)
|
|
1786
|
+
sage: bool(th>0 and th<pi)
|
|
1787
|
+
True
|
|
1788
|
+
sage: assumptions() # list all current symbolic assumptions
|
|
1789
|
+
[x is real, y is real, z is real, r is real, r > 0, th is real,
|
|
1790
|
+
th > 0, th < pi, ph is real, ph > 0, ph < 2*pi]
|
|
1791
|
+
|
|
1792
|
+
The coordinate ranges are used for simplifications::
|
|
1793
|
+
|
|
1794
|
+
sage: simplify(abs(r)) # r has been declared to lie in the interval (0,+oo)
|
|
1795
|
+
r
|
|
1796
|
+
sage: simplify(abs(x)) # no positive range has been declared for x
|
|
1797
|
+
abs(x)
|
|
1798
|
+
|
|
1799
|
+
A coordinate can be declared periodic by adding the keyword ``periodic``
|
|
1800
|
+
to its range::
|
|
1801
|
+
|
|
1802
|
+
sage: V = M.open_subset('V')
|
|
1803
|
+
sage: c_spher1.<r,th,ph1> = \
|
|
1804
|
+
....: V.chart(r'r:(0,+oo) th:(0,pi):\theta ph1:(0,2*pi):periodic:\phi_1')
|
|
1805
|
+
sage: c_spher1.periods()
|
|
1806
|
+
(None, None, 2*pi)
|
|
1807
|
+
sage: c_spher1.coord_range()
|
|
1808
|
+
r: (0, +oo); th: (0, pi); ph1: [0, 2*pi] (periodic)
|
|
1809
|
+
|
|
1810
|
+
It is equivalent to give the period as ``period=2*pi``, skipping the
|
|
1811
|
+
coordinate range::
|
|
1812
|
+
|
|
1813
|
+
sage: c_spher2.<r,th,ph2> = \
|
|
1814
|
+
....: V.chart(r'r:(0,+oo) th:(0,pi):\theta ph2:period=2*pi:\phi_2')
|
|
1815
|
+
sage: c_spher2.periods()
|
|
1816
|
+
(None, None, 2*pi)
|
|
1817
|
+
sage: c_spher2.coord_range()
|
|
1818
|
+
r: (0, +oo); th: (0, pi); ph2: [0, 2*pi] (periodic)
|
|
1819
|
+
|
|
1820
|
+
Each constructed chart is automatically added to the manifold's
|
|
1821
|
+
user atlas::
|
|
1822
|
+
|
|
1823
|
+
sage: M.atlas()
|
|
1824
|
+
[Chart (R^3, (x, y, z)), Chart (U, (r, th, ph)),
|
|
1825
|
+
Chart (V, (r, th, ph1)), Chart (V, (r, th, ph2))]
|
|
1826
|
+
|
|
1827
|
+
and to the atlas of its domain::
|
|
1828
|
+
|
|
1829
|
+
sage: U.atlas()
|
|
1830
|
+
[Chart (U, (r, th, ph))]
|
|
1831
|
+
|
|
1832
|
+
Manifold subsets have a *default chart*, which, unless changed
|
|
1833
|
+
via the method
|
|
1834
|
+
:meth:`~sage.manifolds.manifold.TopologicalManifold.set_default_chart`,
|
|
1835
|
+
is the first defined chart on the subset (or on a open subset of it)::
|
|
1836
|
+
|
|
1837
|
+
sage: M.default_chart()
|
|
1838
|
+
Chart (R^3, (x, y, z))
|
|
1839
|
+
sage: U.default_chart()
|
|
1840
|
+
Chart (U, (r, th, ph))
|
|
1841
|
+
|
|
1842
|
+
The default charts are not privileged charts on the manifold, but rather
|
|
1843
|
+
charts whose name can be skipped in the argument list of functions having
|
|
1844
|
+
an optional ``chart=`` argument.
|
|
1845
|
+
|
|
1846
|
+
The chart map `\varphi` acting on a point is obtained by means of the
|
|
1847
|
+
call operator, i.e. the operator ``()``::
|
|
1848
|
+
|
|
1849
|
+
sage: p = M.point((1,0,-2)); p
|
|
1850
|
+
Point on the 3-dimensional topological manifold R^3
|
|
1851
|
+
sage: c_cart(p)
|
|
1852
|
+
(1, 0, -2)
|
|
1853
|
+
sage: c_cart(p) == p.coord(c_cart)
|
|
1854
|
+
True
|
|
1855
|
+
sage: q = M.point((2,pi/2,pi/3), chart=c_spher) # point defined by its spherical coordinates
|
|
1856
|
+
sage: c_spher(q)
|
|
1857
|
+
(2, 1/2*pi, 1/3*pi)
|
|
1858
|
+
sage: c_spher(q) == q.coord(c_spher)
|
|
1859
|
+
True
|
|
1860
|
+
sage: a = U.point((1,pi/2,pi)) # the default coordinates on U are the spherical ones
|
|
1861
|
+
sage: c_spher(a)
|
|
1862
|
+
(1, 1/2*pi, pi)
|
|
1863
|
+
sage: c_spher(a) == a.coord(c_spher)
|
|
1864
|
+
True
|
|
1865
|
+
|
|
1866
|
+
Cartesian coordinates on `U` as an example of chart construction with
|
|
1867
|
+
coordinate restrictions: since `U` is the complement of the half-plane
|
|
1868
|
+
`\{y = 0, x \geq 0\}`, we must have `y \neq 0` or `x < 0` on U.
|
|
1869
|
+
Accordingly, we set::
|
|
1870
|
+
|
|
1871
|
+
sage: c_cartU.<x,y,z> = U.chart(coord_restrictions=lambda x,y,z: (y!=0, x<0))
|
|
1872
|
+
sage: U.atlas()
|
|
1873
|
+
[Chart (U, (r, th, ph)), Chart (U, (x, y, z))]
|
|
1874
|
+
sage: M.atlas()
|
|
1875
|
+
[Chart (R^3, (x, y, z)), Chart (U, (r, th, ph)),
|
|
1876
|
+
Chart (V, (r, th, ph1)), Chart (V, (r, th, ph2)),
|
|
1877
|
+
Chart (U, (x, y, z))]
|
|
1878
|
+
sage: c_cartU.valid_coordinates(-1,0,2)
|
|
1879
|
+
True
|
|
1880
|
+
sage: c_cartU.valid_coordinates(1,0,2)
|
|
1881
|
+
False
|
|
1882
|
+
sage: c_cart.valid_coordinates(1,0,2)
|
|
1883
|
+
True
|
|
1884
|
+
|
|
1885
|
+
Note that, as an example, the following would have meant `y \neq 0`
|
|
1886
|
+
*and* `x < 0`::
|
|
1887
|
+
|
|
1888
|
+
c_cartU.<x,y,z> = U.chart(coord_restrictions=lambda x,y,z: [y!=0, x<0])
|
|
1889
|
+
|
|
1890
|
+
Chart grids can be drawn in 2D or 3D graphics thanks to the method
|
|
1891
|
+
:meth:`plot`.
|
|
1892
|
+
"""
|
|
1893
|
+
|
|
1894
|
+
def __init__(
|
|
1895
|
+
self,
|
|
1896
|
+
domain,
|
|
1897
|
+
coordinates,
|
|
1898
|
+
calc_method=None,
|
|
1899
|
+
bounds=None,
|
|
1900
|
+
periods=None,
|
|
1901
|
+
coord_restrictions=None,
|
|
1902
|
+
):
|
|
1903
|
+
r"""
|
|
1904
|
+
Construct a chart on a real topological manifold.
|
|
1905
|
+
|
|
1906
|
+
TESTS::
|
|
1907
|
+
|
|
1908
|
+
sage: forget() # for doctests only
|
|
1909
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
1910
|
+
sage: X.<x,y> = M.chart()
|
|
1911
|
+
sage: X
|
|
1912
|
+
Chart (M, (x, y))
|
|
1913
|
+
sage: type(X)
|
|
1914
|
+
<class 'sage.manifolds.chart.RealChart'>
|
|
1915
|
+
sage: assumptions() # assumptions set in X._init_coordinates
|
|
1916
|
+
[x is real, y is real]
|
|
1917
|
+
sage: TestSuite(X).run()
|
|
1918
|
+
"""
|
|
1919
|
+
super().__init__(
|
|
1920
|
+
domain,
|
|
1921
|
+
coordinates,
|
|
1922
|
+
calc_method=calc_method,
|
|
1923
|
+
periods=periods,
|
|
1924
|
+
coord_restrictions=coord_restrictions,
|
|
1925
|
+
)
|
|
1926
|
+
self._bounds = bounds
|
|
1927
|
+
self._tighten_bounds()
|
|
1928
|
+
self._fast_valid_coordinates = None
|
|
1929
|
+
|
|
1930
|
+
@classmethod
|
|
1931
|
+
def _parse_coordinates(cls, domain, coordinates):
|
|
1932
|
+
r"""
|
|
1933
|
+
Initialization of the coordinates as symbolic variables.
|
|
1934
|
+
|
|
1935
|
+
This method must be redefined by derived classes in order to take
|
|
1936
|
+
into account specificities (e.g. enforcing real coordinates).
|
|
1937
|
+
|
|
1938
|
+
INPUT:
|
|
1939
|
+
|
|
1940
|
+
- ``coord_list`` -- list (or space-separated concatenation) of
|
|
1941
|
+
coordinate fields. Each field is a string of at most 3 items,
|
|
1942
|
+
separated by ":". These items are: the coordinate symbol, the
|
|
1943
|
+
(optional) coordinate range or indicator of the periodic
|
|
1944
|
+
character of the coordinate, and the (optional) coordinate
|
|
1945
|
+
LaTeX symbol
|
|
1946
|
+
|
|
1947
|
+
OUTPUT:
|
|
1948
|
+
|
|
1949
|
+
- a tuple of variables (as elements of ``SR``)
|
|
1950
|
+
- a dictionary with possible keys:
|
|
1951
|
+
|
|
1952
|
+
- ``'periods'`` -- a tuple of periods
|
|
1953
|
+
- ``'bounds'`` -- a tuple of coordinate ranges
|
|
1954
|
+
|
|
1955
|
+
TESTS::
|
|
1956
|
+
|
|
1957
|
+
sage: from sage.manifolds.chart import RealChart
|
|
1958
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
1959
|
+
sage: RealChart._parse_coordinates(M, ['x', 'y'])
|
|
1960
|
+
((x, y),
|
|
1961
|
+
{'bounds': (((-Infinity, False), (+Infinity, False)),
|
|
1962
|
+
((-Infinity, False), (+Infinity, False))),
|
|
1963
|
+
'periods': (None, None)})
|
|
1964
|
+
sage: RealChart._parse_coordinates(M, [r'x1:\xi:(0,1)', r'y1:\eta'])
|
|
1965
|
+
((x1, y1),
|
|
1966
|
+
{'bounds': (((0, False), (1, False)),
|
|
1967
|
+
((-Infinity, False), (+Infinity, False))),
|
|
1968
|
+
'periods': (None, None)})
|
|
1969
|
+
"""
|
|
1970
|
+
from sage.symbolic.assumptions import assume
|
|
1971
|
+
|
|
1972
|
+
if isinstance(coordinates, str):
|
|
1973
|
+
coord_list = coordinates.split()
|
|
1974
|
+
else:
|
|
1975
|
+
coord_list = coordinates
|
|
1976
|
+
xx_list = [] # will contain the coordinates as Sage symbolic variables
|
|
1977
|
+
bounds_list = [] # will contain the coordinate bounds
|
|
1978
|
+
period_list = []
|
|
1979
|
+
for coord_index, coord_field in enumerate(coord_list):
|
|
1980
|
+
coord_properties = coord_field.split(':')
|
|
1981
|
+
coord_symb = coord_properties[0].strip() # the coordinate symbol
|
|
1982
|
+
# default values, possibly redefined below:
|
|
1983
|
+
coord_latex = None
|
|
1984
|
+
xmin = -Infinity
|
|
1985
|
+
xmin_included = False
|
|
1986
|
+
xmax = +Infinity
|
|
1987
|
+
xmax_included = False
|
|
1988
|
+
period = None
|
|
1989
|
+
# scan of the properties other than the symbol:
|
|
1990
|
+
is_periodic = False
|
|
1991
|
+
for prop in coord_properties[1:]:
|
|
1992
|
+
prop1 = prop.strip()
|
|
1993
|
+
delim_min = prop1[0]
|
|
1994
|
+
if delim_min in ['[', ']', '(']:
|
|
1995
|
+
# prop1 is the coordinate's range
|
|
1996
|
+
xmin_str, xmax_str = prop1[1 : len(prop1) - 1].split(',')
|
|
1997
|
+
if xmin_str not in [
|
|
1998
|
+
'-inf',
|
|
1999
|
+
'-Inf',
|
|
2000
|
+
'-infinity',
|
|
2001
|
+
'-Infinity',
|
|
2002
|
+
'-oo',
|
|
2003
|
+
]:
|
|
2004
|
+
xmin = SR(xmin_str)
|
|
2005
|
+
xmin_included = delim_min == '['
|
|
2006
|
+
if xmax_str not in [
|
|
2007
|
+
'inf',
|
|
2008
|
+
'+inf',
|
|
2009
|
+
'Inf',
|
|
2010
|
+
'+Inf',
|
|
2011
|
+
'infinity',
|
|
2012
|
+
'+infinity',
|
|
2013
|
+
'Infinity',
|
|
2014
|
+
'+Infinity',
|
|
2015
|
+
'oo',
|
|
2016
|
+
'+oo',
|
|
2017
|
+
]:
|
|
2018
|
+
xmax = SR(xmax_str)
|
|
2019
|
+
xmax_included = prop1[-1] == ']'
|
|
2020
|
+
elif prop1[0:6] == 'period':
|
|
2021
|
+
# prop1 indicates a periodic coordinate
|
|
2022
|
+
is_periodic = True
|
|
2023
|
+
if prop1[6:8] != 'ic':
|
|
2024
|
+
# case prop1 = 'period=value'
|
|
2025
|
+
xmin = 0
|
|
2026
|
+
xmax = SR(prop1[7:])
|
|
2027
|
+
else:
|
|
2028
|
+
# prop1 is the coordinate's LaTeX symbol
|
|
2029
|
+
coord_latex = prop1
|
|
2030
|
+
# Construction of the coordinate as a Sage symbolic variable:
|
|
2031
|
+
coord_var = SR.var(coord_symb, domain='real', latex_name=coord_latex)
|
|
2032
|
+
assume(coord_var, 'real')
|
|
2033
|
+
if is_periodic:
|
|
2034
|
+
period = xmax - xmin
|
|
2035
|
+
xmin_included = 'periodic'
|
|
2036
|
+
xmax_included = 'periodic'
|
|
2037
|
+
else:
|
|
2038
|
+
if not (xmin == -Infinity):
|
|
2039
|
+
if xmin_included:
|
|
2040
|
+
assume(coord_var >= xmin)
|
|
2041
|
+
else:
|
|
2042
|
+
assume(coord_var > xmin)
|
|
2043
|
+
if not (xmax == Infinity):
|
|
2044
|
+
if xmax_included:
|
|
2045
|
+
assume(coord_var <= xmax)
|
|
2046
|
+
else:
|
|
2047
|
+
assume(coord_var < xmax)
|
|
2048
|
+
xx_list.append(coord_var)
|
|
2049
|
+
bounds_list.append(((xmin, xmin_included), (xmax, xmax_included)))
|
|
2050
|
+
period_list.append(period)
|
|
2051
|
+
return tuple(xx_list), dict(
|
|
2052
|
+
bounds=tuple(bounds_list), periods=tuple(period_list)
|
|
2053
|
+
)
|
|
2054
|
+
|
|
2055
|
+
def coord_bounds(self, i=None):
|
|
2056
|
+
r"""
|
|
2057
|
+
Return the lower and upper bounds of the range of a coordinate.
|
|
2058
|
+
|
|
2059
|
+
For a nicely formatted output, use :meth:`coord_range` instead.
|
|
2060
|
+
|
|
2061
|
+
INPUT:
|
|
2062
|
+
|
|
2063
|
+
- ``i`` -- (default: ``None``) index of the coordinate; if ``None``,
|
|
2064
|
+
the bounds of all the coordinates are returned
|
|
2065
|
+
|
|
2066
|
+
OUTPUT:
|
|
2067
|
+
|
|
2068
|
+
- the coordinate bounds as the tuple
|
|
2069
|
+
``((xmin, min_included), (xmax, max_included))`` where
|
|
2070
|
+
|
|
2071
|
+
- ``xmin`` is the coordinate lower bound
|
|
2072
|
+
- ``min_included`` is a boolean, indicating whether the coordinate
|
|
2073
|
+
can take the value ``xmin``, i.e. ``xmin`` is a strict lower
|
|
2074
|
+
bound iff ``min_included`` is ``False``
|
|
2075
|
+
- ``xmin`` is the coordinate upper bound
|
|
2076
|
+
- ``max_included`` is a boolean, indicating whether the coordinate
|
|
2077
|
+
can take the value ``xmax``, i.e. ``xmax`` is a strict upper
|
|
2078
|
+
bound iff ``max_included`` is ``False``
|
|
2079
|
+
|
|
2080
|
+
EXAMPLES:
|
|
2081
|
+
|
|
2082
|
+
Some coordinate bounds on a 2-dimensional manifold::
|
|
2083
|
+
|
|
2084
|
+
sage: forget() # for doctests only
|
|
2085
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
2086
|
+
sage: c_xy.<x,y> = M.chart('x y:[0,1)')
|
|
2087
|
+
sage: c_xy.coord_bounds(0) # x in (-oo,+oo) (the default)
|
|
2088
|
+
((-Infinity, False), (+Infinity, False))
|
|
2089
|
+
sage: c_xy.coord_bounds(1) # y in [0,1)
|
|
2090
|
+
((0, True), (1, False))
|
|
2091
|
+
sage: c_xy.coord_bounds()
|
|
2092
|
+
(((-Infinity, False), (+Infinity, False)), ((0, True), (1, False)))
|
|
2093
|
+
sage: c_xy.coord_bounds() == (c_xy.coord_bounds(0), c_xy.coord_bounds(1))
|
|
2094
|
+
True
|
|
2095
|
+
|
|
2096
|
+
The coordinate bounds can also be recovered via the method
|
|
2097
|
+
:meth:`coord_range`::
|
|
2098
|
+
|
|
2099
|
+
sage: c_xy.coord_range()
|
|
2100
|
+
x: (-oo, +oo); y: [0, 1)
|
|
2101
|
+
sage: c_xy.coord_range(y)
|
|
2102
|
+
y: [0, 1)
|
|
2103
|
+
|
|
2104
|
+
or via Sage's function
|
|
2105
|
+
:func:`sage.symbolic.assumptions.assumptions`::
|
|
2106
|
+
|
|
2107
|
+
sage: assumptions(x)
|
|
2108
|
+
[x is real]
|
|
2109
|
+
sage: assumptions(y)
|
|
2110
|
+
[y is real, y >= 0, y < 1]
|
|
2111
|
+
"""
|
|
2112
|
+
if i is None:
|
|
2113
|
+
return self._bounds
|
|
2114
|
+
else:
|
|
2115
|
+
return self._bounds[i - self._sindex]
|
|
2116
|
+
|
|
2117
|
+
def codomain(self):
|
|
2118
|
+
r"""
|
|
2119
|
+
Return the codomain of ``self`` as a set.
|
|
2120
|
+
|
|
2121
|
+
EXAMPLES::
|
|
2122
|
+
|
|
2123
|
+
sage: M = Manifold(2, 'R^2', structure='topological')
|
|
2124
|
+
sage: U = M.open_subset('U') # the complement of the half line {y=0, x >= 0}
|
|
2125
|
+
sage: c_spher.<r,phi> = U.chart(r'r:(0,+oo) phi:(0,2*pi):\phi')
|
|
2126
|
+
sage: c_spher.codomain()
|
|
2127
|
+
The Cartesian product of ((0, +oo), (0, 2*pi))
|
|
2128
|
+
|
|
2129
|
+
sage: M = Manifold(3, 'R^3', r'\RR^3', structure='topological', start_index=1)
|
|
2130
|
+
sage: c_cart.<x,y,z> = M.chart()
|
|
2131
|
+
sage: c_cart.codomain()
|
|
2132
|
+
Vector space of dimension 3 over Real Field with 53 bits of precision
|
|
2133
|
+
|
|
2134
|
+
In the current implementation, the codomain of periodic coordinates are represented
|
|
2135
|
+
by a fundamental domain::
|
|
2136
|
+
|
|
2137
|
+
sage: V = M.open_subset('V')
|
|
2138
|
+
sage: c_spher1.<r,th,ph1> = \
|
|
2139
|
+
....: V.chart(r'r:(0,+oo) th:(0,pi):\theta ph1:(0,2*pi):periodic:\phi_1')
|
|
2140
|
+
sage: c_spher1.codomain()
|
|
2141
|
+
The Cartesian product of ((0, +oo), (0, pi), [0, 2*pi))
|
|
2142
|
+
"""
|
|
2143
|
+
from sage.categories.cartesian_product import cartesian_product
|
|
2144
|
+
from sage.modules.free_module import VectorSpace
|
|
2145
|
+
from sage.sets.real_set import RealSet
|
|
2146
|
+
|
|
2147
|
+
intervals = tuple(
|
|
2148
|
+
RealSet.interval(
|
|
2149
|
+
xmin,
|
|
2150
|
+
xmax,
|
|
2151
|
+
lower_closed=(min_included == 'periodic' or min_included),
|
|
2152
|
+
upper_closed=(max_included != 'periodic' and max_included),
|
|
2153
|
+
)
|
|
2154
|
+
for ((xmin, min_included), (xmax, max_included)) in self._bounds
|
|
2155
|
+
)
|
|
2156
|
+
if all(interval.is_universe() for interval in intervals):
|
|
2157
|
+
ambient = VectorSpace(
|
|
2158
|
+
self.manifold().base_field(), self.manifold().dimension()
|
|
2159
|
+
)
|
|
2160
|
+
else:
|
|
2161
|
+
ambient = cartesian_product(intervals)
|
|
2162
|
+
if self._restrictions:
|
|
2163
|
+
return self._restrict_set(ambient, self._restrictions)
|
|
2164
|
+
else:
|
|
2165
|
+
return ambient
|
|
2166
|
+
|
|
2167
|
+
def coord_range(self, xx=None):
|
|
2168
|
+
r"""
|
|
2169
|
+
Display the range of a coordinate (or all coordinates), as an
|
|
2170
|
+
interval.
|
|
2171
|
+
|
|
2172
|
+
INPUT:
|
|
2173
|
+
|
|
2174
|
+
- ``xx`` -- (default: ``None``) symbolic expression corresponding
|
|
2175
|
+
to a coordinate of the current chart; if ``None``, the ranges of
|
|
2176
|
+
all coordinates are displayed
|
|
2177
|
+
|
|
2178
|
+
EXAMPLES:
|
|
2179
|
+
|
|
2180
|
+
Ranges of coordinates on a 2-dimensional manifold::
|
|
2181
|
+
|
|
2182
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
2183
|
+
sage: X.<x,y> = M.chart()
|
|
2184
|
+
sage: X.coord_range()
|
|
2185
|
+
x: (-oo, +oo); y: (-oo, +oo)
|
|
2186
|
+
sage: X.coord_range(x)
|
|
2187
|
+
x: (-oo, +oo)
|
|
2188
|
+
sage: U = M.open_subset('U', coord_def={X: [x>1, y<pi]})
|
|
2189
|
+
sage: XU = X.restrict(U) # restriction of chart X to U
|
|
2190
|
+
sage: XU.coord_range()
|
|
2191
|
+
x: (1, +oo); y: (-oo, pi)
|
|
2192
|
+
sage: XU.coord_range(x)
|
|
2193
|
+
x: (1, +oo)
|
|
2194
|
+
sage: XU.coord_range(y)
|
|
2195
|
+
y: (-oo, pi)
|
|
2196
|
+
|
|
2197
|
+
The output is LaTeX-formatted for the notebook::
|
|
2198
|
+
|
|
2199
|
+
sage: latex(XU.coord_range(y))
|
|
2200
|
+
y :\ \left( -\infty, \pi \right)
|
|
2201
|
+
"""
|
|
2202
|
+
from sage.tensor.modules.format_utilities import FormattedExpansion
|
|
2203
|
+
|
|
2204
|
+
def _display_coord_range(self, xx, rtxt, rlatex):
|
|
2205
|
+
ind = self._xx.index(xx)
|
|
2206
|
+
bounds = self._bounds[ind]
|
|
2207
|
+
rtxt += "{}: ".format(xx)
|
|
2208
|
+
rlatex += latex(xx) + r":\ "
|
|
2209
|
+
if bounds[0][1]:
|
|
2210
|
+
rtxt += "["
|
|
2211
|
+
rlatex += r"\left["
|
|
2212
|
+
else:
|
|
2213
|
+
rtxt += "("
|
|
2214
|
+
rlatex += r"\left("
|
|
2215
|
+
xmin = bounds[0][0]
|
|
2216
|
+
if xmin == -Infinity:
|
|
2217
|
+
rtxt += "-oo, "
|
|
2218
|
+
rlatex += r"-\infty,"
|
|
2219
|
+
else:
|
|
2220
|
+
rtxt += "{}, ".format(xmin)
|
|
2221
|
+
rlatex += latex(xmin) + ","
|
|
2222
|
+
xmax = bounds[1][0]
|
|
2223
|
+
if xmax == Infinity:
|
|
2224
|
+
rtxt += "+oo"
|
|
2225
|
+
rlatex += r"+\infty"
|
|
2226
|
+
else:
|
|
2227
|
+
rtxt += "{}".format(xmax)
|
|
2228
|
+
rlatex += latex(xmax)
|
|
2229
|
+
if bounds[1][1]:
|
|
2230
|
+
rtxt += "]"
|
|
2231
|
+
rlatex += r"\right]"
|
|
2232
|
+
if bounds[1][1] == 'periodic':
|
|
2233
|
+
rtxt += " (periodic)"
|
|
2234
|
+
rlatex += r"\text{(periodic)}"
|
|
2235
|
+
else:
|
|
2236
|
+
rtxt += ")"
|
|
2237
|
+
rlatex += r"\right)"
|
|
2238
|
+
return rtxt, rlatex
|
|
2239
|
+
|
|
2240
|
+
resu_txt = ""
|
|
2241
|
+
resu_latex = ""
|
|
2242
|
+
if xx is None:
|
|
2243
|
+
for x in self._xx:
|
|
2244
|
+
if resu_txt != "":
|
|
2245
|
+
resu_txt += "; "
|
|
2246
|
+
resu_latex += r";\quad "
|
|
2247
|
+
resu_txt, resu_latex = _display_coord_range(
|
|
2248
|
+
self, x, resu_txt, resu_latex
|
|
2249
|
+
)
|
|
2250
|
+
else:
|
|
2251
|
+
resu_txt, resu_latex = _display_coord_range(self, xx, resu_txt, resu_latex)
|
|
2252
|
+
return FormattedExpansion(resu_txt, resu_latex)
|
|
2253
|
+
|
|
2254
|
+
def add_restrictions(self, restrictions):
|
|
2255
|
+
r"""
|
|
2256
|
+
Add some restrictions on the coordinates.
|
|
2257
|
+
|
|
2258
|
+
This is deprecated; provide the restrictions at the time of creating
|
|
2259
|
+
the chart.
|
|
2260
|
+
|
|
2261
|
+
INPUT:
|
|
2262
|
+
|
|
2263
|
+
- ``restrictions`` -- list of restrictions on the
|
|
2264
|
+
coordinates, in addition to the ranges declared by the intervals
|
|
2265
|
+
specified in the chart constructor
|
|
2266
|
+
|
|
2267
|
+
A restriction can be any symbolic equality or inequality involving
|
|
2268
|
+
the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items
|
|
2269
|
+
of the list ``restrictions`` are combined with the ``and`` operator;
|
|
2270
|
+
if some restrictions are to be combined with the ``or`` operator
|
|
2271
|
+
instead, they have to be passed as a tuple in some single item
|
|
2272
|
+
of the list ``restrictions``. For example::
|
|
2273
|
+
|
|
2274
|
+
restrictions = [x > y, (x != 0, y != 0), z^2 < x]
|
|
2275
|
+
|
|
2276
|
+
means ``(x > y) and ((x != 0) or (y != 0)) and (z^2 < x)``.
|
|
2277
|
+
If the list ``restrictions`` contains only one item, this
|
|
2278
|
+
item can be passed as such, i.e. writing ``x > y`` instead
|
|
2279
|
+
of the single element list ``[x > y]``.
|
|
2280
|
+
|
|
2281
|
+
EXAMPLES:
|
|
2282
|
+
|
|
2283
|
+
Cartesian coordinates on the open unit disc in `\RR^2`::
|
|
2284
|
+
|
|
2285
|
+
sage: M = Manifold(2, 'M', structure='topological') # the open unit disc
|
|
2286
|
+
sage: X.<x,y> = M.chart()
|
|
2287
|
+
sage: X.add_restrictions(x^2+y^2<1)
|
|
2288
|
+
doctest:warning...
|
|
2289
|
+
DeprecationWarning: Chart.add_restrictions is deprecated; provide the
|
|
2290
|
+
restrictions at the time of creating the chart
|
|
2291
|
+
See https://github.com/sagemath/sage/issues/32102 for details.
|
|
2292
|
+
sage: X.valid_coordinates(0,2)
|
|
2293
|
+
False
|
|
2294
|
+
sage: X.valid_coordinates(0,1/3)
|
|
2295
|
+
True
|
|
2296
|
+
|
|
2297
|
+
The restrictions are transmitted to subcharts::
|
|
2298
|
+
|
|
2299
|
+
sage: A = M.open_subset('A') # annulus 1/2 < r < 1
|
|
2300
|
+
sage: X_A = X.restrict(A, x^2+y^2 > 1/4)
|
|
2301
|
+
sage: X_A._restrictions
|
|
2302
|
+
[x^2 + y^2 < 1, x^2 + y^2 > (1/4)]
|
|
2303
|
+
sage: X_A.valid_coordinates(0,1/3)
|
|
2304
|
+
False
|
|
2305
|
+
sage: X_A.valid_coordinates(2/3,1/3)
|
|
2306
|
+
True
|
|
2307
|
+
|
|
2308
|
+
If appropriate, the restrictions are transformed into bounds on
|
|
2309
|
+
the coordinate ranges::
|
|
2310
|
+
|
|
2311
|
+
sage: U = M.open_subset('U')
|
|
2312
|
+
sage: X_U = X.restrict(U)
|
|
2313
|
+
sage: X_U.coord_range()
|
|
2314
|
+
x: (-oo, +oo); y: (-oo, +oo)
|
|
2315
|
+
sage: X_U.add_restrictions([x<0, y>1/2])
|
|
2316
|
+
sage: X_U.coord_range()
|
|
2317
|
+
x: (-oo, 0); y: (1/2, +oo)
|
|
2318
|
+
"""
|
|
2319
|
+
super().add_restrictions(restrictions)
|
|
2320
|
+
self._tighten_bounds()
|
|
2321
|
+
|
|
2322
|
+
def _tighten_bounds(self):
|
|
2323
|
+
"""
|
|
2324
|
+
Update coordinate bounds from the coordinate restrictions.
|
|
2325
|
+
|
|
2326
|
+
EXAMPLES::
|
|
2327
|
+
|
|
2328
|
+
sage: M = Manifold(2, 'M', structure='topological') # the open unit disc
|
|
2329
|
+
sage: X.<x,y> = M.chart()
|
|
2330
|
+
sage: U = M.open_subset('U')
|
|
2331
|
+
sage: X_U = X.restrict(U, restrictions=[x<0, y>1/2])
|
|
2332
|
+
sage: X_U.coord_range()
|
|
2333
|
+
x: (-oo, 0); y: (1/2, +oo)
|
|
2334
|
+
"""
|
|
2335
|
+
import operator
|
|
2336
|
+
|
|
2337
|
+
bounds = list(self._bounds) # convert to a list for modifications
|
|
2338
|
+
new_restrictions = []
|
|
2339
|
+
for restrict in self._restrictions:
|
|
2340
|
+
restrict_used = False # determines whether restrict is used to set some coordinate bound
|
|
2341
|
+
if not isinstance(
|
|
2342
|
+
restrict, (tuple, list, set, frozenset)
|
|
2343
|
+
):
|
|
2344
|
+
# case of combined
|
|
2345
|
+
# conditions excluded
|
|
2346
|
+
operands = restrict.operands()
|
|
2347
|
+
left = operands[0]
|
|
2348
|
+
right = operands[1]
|
|
2349
|
+
right_var = right.variables()
|
|
2350
|
+
if left in self._xx:
|
|
2351
|
+
# the l.h.s. of the restriction is a single
|
|
2352
|
+
# coordinate
|
|
2353
|
+
right_coord = [coord for coord in self._xx if coord in right_var]
|
|
2354
|
+
if not right_coord:
|
|
2355
|
+
# there is no other coordinate in the r.h.s.
|
|
2356
|
+
ind = self._xx.index(left)
|
|
2357
|
+
left_bounds = list(bounds[ind])
|
|
2358
|
+
oper = restrict.operator()
|
|
2359
|
+
oinf = left_bounds[0][0] # old coord inf
|
|
2360
|
+
osup = left_bounds[1][0] # old coord sup
|
|
2361
|
+
if oper == operator.lt:
|
|
2362
|
+
if osup == Infinity or right <= osup:
|
|
2363
|
+
left_bounds[1] = (right, False)
|
|
2364
|
+
restrict_used = True
|
|
2365
|
+
elif oper == operator.le:
|
|
2366
|
+
if osup == Infinity or right < osup:
|
|
2367
|
+
left_bounds[1] = (right, True)
|
|
2368
|
+
restrict_used = True
|
|
2369
|
+
elif oper == operator.gt:
|
|
2370
|
+
if oinf == -Infinity or right >= oinf:
|
|
2371
|
+
left_bounds[0] = (right, False)
|
|
2372
|
+
restrict_used = True
|
|
2373
|
+
elif oper == operator.ge:
|
|
2374
|
+
if oinf == -Infinity or right > oinf:
|
|
2375
|
+
left_bounds[0] = (right, True)
|
|
2376
|
+
restrict_used = True
|
|
2377
|
+
bounds[ind] = tuple(left_bounds)
|
|
2378
|
+
if not restrict_used:
|
|
2379
|
+
# if restrict has not been used to set a coordinate bound
|
|
2380
|
+
# it is maintained in the list of restrictions:
|
|
2381
|
+
new_restrictions.append(restrict)
|
|
2382
|
+
self._bounds = tuple(bounds)
|
|
2383
|
+
self._restrictions = new_restrictions
|
|
2384
|
+
self._fast_valid_coordinates = None
|
|
2385
|
+
|
|
2386
|
+
def restrict(self, subset, restrictions=None):
|
|
2387
|
+
r"""
|
|
2388
|
+
Return the restriction of the chart to some open subset of its domain.
|
|
2389
|
+
|
|
2390
|
+
If the current chart is `(U, \varphi)`, a *restriction* (or *subchart*)
|
|
2391
|
+
is a chart `(V, \psi)` such that `V \subset U` and `\psi = \varphi|_V`.
|
|
2392
|
+
|
|
2393
|
+
If such subchart has not been defined yet, it is constructed here.
|
|
2394
|
+
|
|
2395
|
+
The coordinates of the subchart bare the same names as the coordinates
|
|
2396
|
+
of the current chart.
|
|
2397
|
+
|
|
2398
|
+
INPUT:
|
|
2399
|
+
|
|
2400
|
+
- ``subset`` -- open subset `V` of the chart domain `U` (must be an
|
|
2401
|
+
instance of :class:`~sage.manifolds.manifold.TopologicalManifold`)
|
|
2402
|
+
- ``restrictions`` -- (default: ``None``) list of coordinate
|
|
2403
|
+
restrictions defining the subset `V`
|
|
2404
|
+
|
|
2405
|
+
A restriction can be any symbolic equality or inequality involving
|
|
2406
|
+
the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items
|
|
2407
|
+
of the list ``restrictions`` are combined with the ``and`` operator;
|
|
2408
|
+
if some restrictions are to be combined with the ``or`` operator
|
|
2409
|
+
instead, they have to be passed as a tuple in some single item
|
|
2410
|
+
of the list ``restrictions``. For example::
|
|
2411
|
+
|
|
2412
|
+
restrictions = [x > y, (x != 0, y != 0), z^2 < x]
|
|
2413
|
+
|
|
2414
|
+
means ``(x > y) and ((x != 0) or (y != 0)) and (z^2 < x)``.
|
|
2415
|
+
If the list ``restrictions`` contains only one item, this
|
|
2416
|
+
item can be passed as such, i.e. writing ``x > y`` instead
|
|
2417
|
+
of the single element list ``[x > y]``.
|
|
2418
|
+
|
|
2419
|
+
OUTPUT:
|
|
2420
|
+
|
|
2421
|
+
- the chart `(V, \psi)` as a :class:`RealChart`
|
|
2422
|
+
|
|
2423
|
+
EXAMPLES:
|
|
2424
|
+
|
|
2425
|
+
Cartesian coordinates on the unit open disc in `\RR^2` as a subchart
|
|
2426
|
+
of the global Cartesian coordinates::
|
|
2427
|
+
|
|
2428
|
+
sage: M = Manifold(2, 'R^2', structure='topological')
|
|
2429
|
+
sage: c_cart.<x,y> = M.chart() # Cartesian coordinates on R^2
|
|
2430
|
+
sage: D = M.open_subset('D') # the unit open disc
|
|
2431
|
+
sage: c_cart_D = c_cart.restrict(D, x^2+y^2<1)
|
|
2432
|
+
sage: p = M.point((1/2, 0))
|
|
2433
|
+
sage: p in D
|
|
2434
|
+
True
|
|
2435
|
+
sage: q = M.point((1, 2))
|
|
2436
|
+
sage: q in D
|
|
2437
|
+
False
|
|
2438
|
+
|
|
2439
|
+
Cartesian coordinates on the annulus `1 < \sqrt{x^2 + y^2} < 2`::
|
|
2440
|
+
|
|
2441
|
+
sage: A = M.open_subset('A')
|
|
2442
|
+
sage: c_cart_A = c_cart.restrict(A, [x^2+y^2>1, x^2+y^2<4])
|
|
2443
|
+
sage: p in A, q in A
|
|
2444
|
+
(False, False)
|
|
2445
|
+
sage: a = M.point((3/2,0))
|
|
2446
|
+
sage: a in A
|
|
2447
|
+
True
|
|
2448
|
+
|
|
2449
|
+
TESTS:
|
|
2450
|
+
|
|
2451
|
+
Check that :issue:`32929` is fixed::
|
|
2452
|
+
|
|
2453
|
+
sage: M = Manifold(2, 'M')
|
|
2454
|
+
sage: X.<x,y> = M.chart(r"x:(0,+oo) y:(0,2):periodic")
|
|
2455
|
+
sage: U = M.open_subset('U', coord_def={X: x<1})
|
|
2456
|
+
sage: XU = X.restrict(U)
|
|
2457
|
+
sage: XU.coord_range()
|
|
2458
|
+
x: (0, 1); y: [0, 2] (periodic)
|
|
2459
|
+
sage: XU.periods()
|
|
2460
|
+
(None, 2)
|
|
2461
|
+
"""
|
|
2462
|
+
if subset == self.domain():
|
|
2463
|
+
return self
|
|
2464
|
+
if subset not in self._dom_restrict:
|
|
2465
|
+
if not subset.is_subset(self.domain()):
|
|
2466
|
+
raise ValueError(
|
|
2467
|
+
"the specified subset is not a subset "
|
|
2468
|
+
+ "of the domain of definition of the chart"
|
|
2469
|
+
)
|
|
2470
|
+
coordinates = ""
|
|
2471
|
+
for coord in self._xx:
|
|
2472
|
+
coordinates += repr(coord) + ' '
|
|
2473
|
+
res_coord_restrictions = set(self._restrictions)
|
|
2474
|
+
res_coord_restrictions.update(
|
|
2475
|
+
self._normalize_coord_restrictions(self._xx, restrictions)
|
|
2476
|
+
)
|
|
2477
|
+
res = type(self)(
|
|
2478
|
+
subset,
|
|
2479
|
+
coordinates,
|
|
2480
|
+
calc_method=self._calc_method._current,
|
|
2481
|
+
bounds=self._bounds,
|
|
2482
|
+
periods=self._periods,
|
|
2483
|
+
# The coordinate restrictions are added
|
|
2484
|
+
# to the result chart and possibly
|
|
2485
|
+
# transformed into coordinate bounds:
|
|
2486
|
+
coord_restrictions=res_coord_restrictions,
|
|
2487
|
+
)
|
|
2488
|
+
# Update of supercharts and subcharts:
|
|
2489
|
+
res._supercharts.update(self._supercharts)
|
|
2490
|
+
for schart in self._supercharts:
|
|
2491
|
+
schart._subcharts.add(res)
|
|
2492
|
+
schart._dom_restrict[subset] = res
|
|
2493
|
+
# Update of domain restrictions:
|
|
2494
|
+
self._dom_restrict[subset] = res
|
|
2495
|
+
return self._dom_restrict[subset]
|
|
2496
|
+
|
|
2497
|
+
def valid_coordinates(self, *coordinates, **kwds):
|
|
2498
|
+
r"""
|
|
2499
|
+
Check whether a tuple of coordinates can be the coordinates of a
|
|
2500
|
+
point in the chart domain.
|
|
2501
|
+
|
|
2502
|
+
INPUT:
|
|
2503
|
+
|
|
2504
|
+
- ``*coordinates`` -- coordinate values
|
|
2505
|
+
- ``**kwds`` -- options:
|
|
2506
|
+
|
|
2507
|
+
- ``tolerance=0``, to set the absolute tolerance in the test of
|
|
2508
|
+
coordinate ranges
|
|
2509
|
+
- ``parameters=None``, to set some numerical values to parameters
|
|
2510
|
+
|
|
2511
|
+
OUTPUT:
|
|
2512
|
+
|
|
2513
|
+
- ``True`` if the coordinate values are admissible in the chart range
|
|
2514
|
+
and ``False`` otherwise
|
|
2515
|
+
|
|
2516
|
+
EXAMPLES:
|
|
2517
|
+
|
|
2518
|
+
Cartesian coordinates on a square interior::
|
|
2519
|
+
|
|
2520
|
+
sage: forget() # for doctest only
|
|
2521
|
+
sage: M = Manifold(2, 'M', structure='topological') # the square interior
|
|
2522
|
+
sage: X.<x,y> = M.chart('x:(-2,2) y:(-2,2)')
|
|
2523
|
+
sage: X.valid_coordinates(0,1)
|
|
2524
|
+
True
|
|
2525
|
+
sage: X.valid_coordinates(-3/2,5/4)
|
|
2526
|
+
True
|
|
2527
|
+
sage: X.valid_coordinates(0,3)
|
|
2528
|
+
False
|
|
2529
|
+
|
|
2530
|
+
The unit open disk inside the square::
|
|
2531
|
+
|
|
2532
|
+
sage: D = M.open_subset('D', coord_def={X: x^2+y^2<1})
|
|
2533
|
+
sage: XD = X.restrict(D)
|
|
2534
|
+
sage: XD.valid_coordinates(0,1)
|
|
2535
|
+
False
|
|
2536
|
+
sage: XD.valid_coordinates(-3/2,5/4)
|
|
2537
|
+
False
|
|
2538
|
+
sage: XD.valid_coordinates(-1/2,1/2)
|
|
2539
|
+
True
|
|
2540
|
+
sage: XD.valid_coordinates(0,0)
|
|
2541
|
+
True
|
|
2542
|
+
|
|
2543
|
+
Another open subset of the square, defined by `x^2+y^2<1` or
|
|
2544
|
+
(`x>0` and `|y|<1`)::
|
|
2545
|
+
|
|
2546
|
+
sage: B = M.open_subset('B',
|
|
2547
|
+
....: coord_def={X: (x^2+y^2<1,
|
|
2548
|
+
....: [x>0, abs(y)<1])})
|
|
2549
|
+
sage: XB = X.restrict(B)
|
|
2550
|
+
sage: XB.valid_coordinates(-1/2, 0)
|
|
2551
|
+
True
|
|
2552
|
+
sage: XB.valid_coordinates(-1/2, 3/2)
|
|
2553
|
+
False
|
|
2554
|
+
sage: XB.valid_coordinates(3/2, 1/2)
|
|
2555
|
+
True
|
|
2556
|
+
"""
|
|
2557
|
+
n = len(coordinates)
|
|
2558
|
+
if n != self._manifold._dim:
|
|
2559
|
+
return False
|
|
2560
|
+
if 'tolerance' in kwds:
|
|
2561
|
+
tolerance = kwds['tolerance']
|
|
2562
|
+
else:
|
|
2563
|
+
tolerance = 0
|
|
2564
|
+
if 'parameters' in kwds:
|
|
2565
|
+
parameters = kwds['parameters']
|
|
2566
|
+
else:
|
|
2567
|
+
parameters = None
|
|
2568
|
+
# Check of the coordinate ranges:
|
|
2569
|
+
for x, bounds in zip(coordinates, self._bounds):
|
|
2570
|
+
xmin = bounds[0][0] - tolerance
|
|
2571
|
+
min_included = bounds[0][1]
|
|
2572
|
+
if min_included == 'periodic':
|
|
2573
|
+
continue # no range to check for a periodic coordinate
|
|
2574
|
+
xmax = bounds[1][0] + tolerance
|
|
2575
|
+
max_included = bounds[1][1]
|
|
2576
|
+
if parameters:
|
|
2577
|
+
xmin = xmin.subs(parameters)
|
|
2578
|
+
xmax = xmax.subs(parameters)
|
|
2579
|
+
if min_included:
|
|
2580
|
+
if x < xmin:
|
|
2581
|
+
return False
|
|
2582
|
+
elif x <= xmin:
|
|
2583
|
+
return False
|
|
2584
|
+
if max_included:
|
|
2585
|
+
if x > xmax:
|
|
2586
|
+
return False
|
|
2587
|
+
elif x >= xmax:
|
|
2588
|
+
return False
|
|
2589
|
+
# Check of additional restrictions:
|
|
2590
|
+
if self._restrictions:
|
|
2591
|
+
substitutions = dict(zip(self._xx, coordinates))
|
|
2592
|
+
if parameters:
|
|
2593
|
+
substitutions.update(parameters)
|
|
2594
|
+
return self._check_restrictions(self._restrictions, substitutions)
|
|
2595
|
+
return True
|
|
2596
|
+
|
|
2597
|
+
def valid_coordinates_numerical(self, *coordinates):
|
|
2598
|
+
r"""
|
|
2599
|
+
Check whether a tuple of float coordinates can be the coordinates
|
|
2600
|
+
of a point in the chart domain.
|
|
2601
|
+
|
|
2602
|
+
This version is optimized for float numbers, and cannot accept
|
|
2603
|
+
parameters nor tolerance. The chart restriction must also be
|
|
2604
|
+
specified in CNF (i.e. a list of tuples).
|
|
2605
|
+
|
|
2606
|
+
INPUT:
|
|
2607
|
+
|
|
2608
|
+
- ``*coordinates`` -- coordinate values
|
|
2609
|
+
|
|
2610
|
+
OUTPUT:
|
|
2611
|
+
|
|
2612
|
+
- ``True`` if the coordinate values are admissible in the chart
|
|
2613
|
+
range and ``False`` otherwise
|
|
2614
|
+
|
|
2615
|
+
EXAMPLES:
|
|
2616
|
+
|
|
2617
|
+
Cartesian coordinates on a square interior::
|
|
2618
|
+
|
|
2619
|
+
sage: forget() # for doctest only
|
|
2620
|
+
sage: M = Manifold(2, 'M', structure='topological') # the square interior
|
|
2621
|
+
sage: X.<x,y> = M.chart('x:(-2,2) y:(-2,2)')
|
|
2622
|
+
sage: X.valid_coordinates_numerical(0,1)
|
|
2623
|
+
True
|
|
2624
|
+
sage: X.valid_coordinates_numerical(-3/2,5/4)
|
|
2625
|
+
True
|
|
2626
|
+
sage: X.valid_coordinates_numerical(0,3)
|
|
2627
|
+
False
|
|
2628
|
+
|
|
2629
|
+
The unit open disk inside the square::
|
|
2630
|
+
|
|
2631
|
+
sage: D = M.open_subset('D', coord_def={X: x^2+y^2<1})
|
|
2632
|
+
sage: XD = X.restrict(D)
|
|
2633
|
+
sage: XD.valid_coordinates_numerical(0,1)
|
|
2634
|
+
False
|
|
2635
|
+
sage: XD.valid_coordinates_numerical(-3/2,5/4)
|
|
2636
|
+
False
|
|
2637
|
+
sage: XD.valid_coordinates_numerical(-1/2,1/2)
|
|
2638
|
+
True
|
|
2639
|
+
sage: XD.valid_coordinates_numerical(0,0)
|
|
2640
|
+
True
|
|
2641
|
+
|
|
2642
|
+
Another open subset of the square, defined by `x^2 + y^2 < 1` or
|
|
2643
|
+
(`x > 0` and `|y| < 1`)::
|
|
2644
|
+
|
|
2645
|
+
sage: B = M.open_subset('B',coord_def={X: [(x^2+y^2<1, x>0),
|
|
2646
|
+
....: (x^2+y^2<1, abs(y)<1)]})
|
|
2647
|
+
sage: XB = X.restrict(B)
|
|
2648
|
+
sage: XB.valid_coordinates_numerical(-1/2, 0)
|
|
2649
|
+
True
|
|
2650
|
+
sage: XB.valid_coordinates_numerical(-1/2, 3/2)
|
|
2651
|
+
False
|
|
2652
|
+
sage: XB.valid_coordinates_numerical(3/2, 1/2)
|
|
2653
|
+
True
|
|
2654
|
+
"""
|
|
2655
|
+
# case fast callable already computed
|
|
2656
|
+
if self._fast_valid_coordinates is not None:
|
|
2657
|
+
return self._fast_valid_coordinates(*coordinates)
|
|
2658
|
+
|
|
2659
|
+
# case fast callable has to be computed
|
|
2660
|
+
from operator import gt, lt
|
|
2661
|
+
|
|
2662
|
+
if not isinstance(self._restrictions, (list, set, frozenset)):
|
|
2663
|
+
if isinstance(self._restrictions, tuple):
|
|
2664
|
+
self._restrictions = [self._restrictions]
|
|
2665
|
+
elif isinstance(self._restrictions, Expression):
|
|
2666
|
+
self._restrictions = [(self._restrictions,)]
|
|
2667
|
+
else:
|
|
2668
|
+
raise ValueError("restrictions must be in CNF (list of tuples)")
|
|
2669
|
+
|
|
2670
|
+
list_of_clause = []
|
|
2671
|
+
for clause in self._restrictions:
|
|
2672
|
+
if not isinstance(clause, tuple):
|
|
2673
|
+
if isinstance(clause, Expression):
|
|
2674
|
+
clause = (clause,)
|
|
2675
|
+
else:
|
|
2676
|
+
raise ValueError("restrictions must be in CNF (list of tuples)")
|
|
2677
|
+
list_of_fast_callable = []
|
|
2678
|
+
for literal in clause:
|
|
2679
|
+
if not isinstance(literal, Expression):
|
|
2680
|
+
raise ValueError("Restrictions must be in CNF (list of tuples)")
|
|
2681
|
+
# End of checks
|
|
2682
|
+
|
|
2683
|
+
fl = fast_callable(literal.lhs(), vars=self[:], domain=float)
|
|
2684
|
+
fr = fast_callable(literal.rhs(), vars=self[:], domain=float)
|
|
2685
|
+
op = literal.operator()
|
|
2686
|
+
list_of_fast_callable.append((fl, fr, op))
|
|
2687
|
+
list_of_clause.append(list_of_fast_callable)
|
|
2688
|
+
|
|
2689
|
+
# adding bounds as restrictions
|
|
2690
|
+
for x, bounds in zip(self[:], self._bounds):
|
|
2691
|
+
if bounds[0][1] == 'periodic':
|
|
2692
|
+
continue # no range to check for a periodic coordinate
|
|
2693
|
+
xmin = bounds[0][0]
|
|
2694
|
+
xmax = bounds[1][0]
|
|
2695
|
+
|
|
2696
|
+
if x <= xmin:
|
|
2697
|
+
return False
|
|
2698
|
+
if x >= xmax:
|
|
2699
|
+
return False
|
|
2700
|
+
|
|
2701
|
+
if xmin is not -Infinity:
|
|
2702
|
+
fl = fast_callable(x, vars=self[:], domain=float)
|
|
2703
|
+
fr = fast_callable(SR(xmin), vars=self[:], domain=float)
|
|
2704
|
+
list_of_clause.append(((fl, fr, gt),))
|
|
2705
|
+
if xmax is not Infinity:
|
|
2706
|
+
fl = fast_callable(x, vars=self[:], domain=float)
|
|
2707
|
+
fr = fast_callable(SR(xmax), vars=self[:], domain=float)
|
|
2708
|
+
list_of_clause.append(((fl, fr, lt),))
|
|
2709
|
+
|
|
2710
|
+
# final call
|
|
2711
|
+
def evaluate_fast_callable(*coordinates):
|
|
2712
|
+
for clause in list_of_clause:
|
|
2713
|
+
temp = False
|
|
2714
|
+
for fl, fr, op in clause:
|
|
2715
|
+
temp = temp or op(fl(*coordinates), fr(*coordinates))
|
|
2716
|
+
if not temp:
|
|
2717
|
+
return False
|
|
2718
|
+
return True
|
|
2719
|
+
|
|
2720
|
+
self._fast_valid_coordinates = evaluate_fast_callable
|
|
2721
|
+
return self._fast_valid_coordinates(*coordinates)
|
|
2722
|
+
|
|
2723
|
+
@options(
|
|
2724
|
+
max_range=8,
|
|
2725
|
+
color='red',
|
|
2726
|
+
style='-',
|
|
2727
|
+
thickness=1,
|
|
2728
|
+
plot_points=75,
|
|
2729
|
+
label_axes=True,
|
|
2730
|
+
)
|
|
2731
|
+
def plot(
|
|
2732
|
+
self,
|
|
2733
|
+
chart=None,
|
|
2734
|
+
ambient_coords=None,
|
|
2735
|
+
mapping=None,
|
|
2736
|
+
fixed_coords=None,
|
|
2737
|
+
ranges=None,
|
|
2738
|
+
number_values=None,
|
|
2739
|
+
steps=None,
|
|
2740
|
+
parameters=None,
|
|
2741
|
+
**kwds,
|
|
2742
|
+
):
|
|
2743
|
+
r"""
|
|
2744
|
+
Plot ``self`` as a grid in a Cartesian graph based on
|
|
2745
|
+
the coordinates of some ambient chart.
|
|
2746
|
+
|
|
2747
|
+
The grid is formed by curves along which a chart coordinate
|
|
2748
|
+
varies, the other coordinates being kept fixed. It is drawn in
|
|
2749
|
+
terms of two (2D graphics) or three (3D graphics) coordinates
|
|
2750
|
+
of another chart, called hereafter the *ambient chart*.
|
|
2751
|
+
|
|
2752
|
+
The ambient chart is related to the current chart either by
|
|
2753
|
+
a transition map if both charts are defined on the same manifold,
|
|
2754
|
+
or by the coordinate expression of some continuous map (typically an
|
|
2755
|
+
immersion). In the latter case, the two charts may be defined on two
|
|
2756
|
+
different manifolds.
|
|
2757
|
+
|
|
2758
|
+
INPUT:
|
|
2759
|
+
|
|
2760
|
+
- ``chart`` -- (default: ``None``) the ambient chart (see above); if
|
|
2761
|
+
``None``, the ambient chart is set to the current chart
|
|
2762
|
+
- ``ambient_coords`` -- (default: ``None``) tuple containing the 2
|
|
2763
|
+
or 3 coordinates of the ambient chart in terms of which the plot
|
|
2764
|
+
is performed; if ``None``, all the coordinates of the ambient
|
|
2765
|
+
chart are considered
|
|
2766
|
+
- ``mapping`` -- (default: ``None``)
|
|
2767
|
+
:class:`~sage.manifolds.continuous_map.ContinuousMap`; continuous
|
|
2768
|
+
manifold map providing the link between the current chart and the
|
|
2769
|
+
ambient chart (cf. above); if ``None``, both charts are supposed
|
|
2770
|
+
to be defined on the same manifold and related by some transition
|
|
2771
|
+
map (see :meth:`~sage.manifolds.chart.Chart.transition_map`)
|
|
2772
|
+
- ``fixed_coords`` -- (default: ``None``) dictionary with keys the
|
|
2773
|
+
chart coordinates that are not drawn and with values the fixed
|
|
2774
|
+
value of these coordinates; if ``None``, all the coordinates of the
|
|
2775
|
+
current chart are drawn
|
|
2776
|
+
- ``ranges`` -- (default: ``None``) dictionary with keys the
|
|
2777
|
+
coordinates to be drawn and values tuples ``(x_min, x_max)``
|
|
2778
|
+
specifying the coordinate range for the plot; if ``None``, the
|
|
2779
|
+
entire coordinate range declared during the chart construction
|
|
2780
|
+
is considered (with ``-Infinity`` replaced by ``-max_range``
|
|
2781
|
+
and ``+Infinity`` by ``max_range``)
|
|
2782
|
+
- ``number_values`` -- (default: ``None``) either an integer or a
|
|
2783
|
+
dictionary with keys the coordinates to be drawn and values the
|
|
2784
|
+
number of constant values of the coordinate to be considered; if
|
|
2785
|
+
``number_values`` is a single integer, it represents the number of
|
|
2786
|
+
constant values for all coordinates; if ``number_values`` is ``None``,
|
|
2787
|
+
it is set to 9 for a 2D plot and to 5 for a 3D plot
|
|
2788
|
+
- ``steps`` -- (default: ``None``) dictionary with keys the coordinates
|
|
2789
|
+
to be drawn and values the step between each constant value of
|
|
2790
|
+
the coordinate; if ``None``, the step is computed from the coordinate
|
|
2791
|
+
range (specified in ``ranges``) and ``number_values``. On the contrary
|
|
2792
|
+
if the step is provided for some coordinate, the corresponding
|
|
2793
|
+
number of constant values is deduced from it and the coordinate range.
|
|
2794
|
+
- ``parameters`` -- (default: ``None``) dictionary giving the numerical
|
|
2795
|
+
values of the parameters that may appear in the relation between
|
|
2796
|
+
the two coordinate systems
|
|
2797
|
+
- ``max_range`` -- (default: 8) numerical value substituted to
|
|
2798
|
+
+Infinity if the latter is the upper bound of the range of a
|
|
2799
|
+
coordinate for which the plot is performed over the entire coordinate
|
|
2800
|
+
range (i.e. for which no specific plot range has been set in
|
|
2801
|
+
``ranges``); similarly ``-max_range`` is the numerical valued
|
|
2802
|
+
substituted for ``-Infinity``
|
|
2803
|
+
- ``color`` -- (default: ``'red'``) either a single color or a
|
|
2804
|
+
dictionary of colors, with keys the coordinates to be drawn,
|
|
2805
|
+
representing the colors of the lines along which the coordinate
|
|
2806
|
+
varies, the other being kept constant; if ``color`` is a single
|
|
2807
|
+
color, it is used for all coordinate lines
|
|
2808
|
+
- ``style`` -- (default: ``'-'``) either a single line style or
|
|
2809
|
+
a dictionary of line styles, with keys the coordinates to be
|
|
2810
|
+
drawn, representing the style of the lines along which the
|
|
2811
|
+
coordinate varies, the other being kept constant; if ``style``
|
|
2812
|
+
is a single style, it is used for all coordinate lines;
|
|
2813
|
+
NB: ``style`` is effective only for 2D plots
|
|
2814
|
+
- ``thickness`` -- (default: 1) either a single line thickness or a
|
|
2815
|
+
dictionary of line thicknesses, with keys the coordinates to be drawn,
|
|
2816
|
+
representing the thickness of the lines along which the coordinate
|
|
2817
|
+
varies, the other being kept constant; if ``thickness`` is a single
|
|
2818
|
+
value, it is used for all coordinate lines
|
|
2819
|
+
- ``plot_points`` -- (default: 75) either a single number of points or
|
|
2820
|
+
a dictionary of integers, with keys the coordinates to be drawn,
|
|
2821
|
+
representing the number of points to plot the lines along which the
|
|
2822
|
+
coordinate varies, the other being kept constant; if ``plot_points``
|
|
2823
|
+
is a single integer, it is used for all coordinate lines
|
|
2824
|
+
- ``label_axes`` -- boolean (default: ``True``); determining whether the
|
|
2825
|
+
labels of the ambient coordinate axes shall be added to the graph;
|
|
2826
|
+
can be set to ``False`` if the graph is 3D and must be superposed
|
|
2827
|
+
with another graph
|
|
2828
|
+
|
|
2829
|
+
OUTPUT:
|
|
2830
|
+
|
|
2831
|
+
- a graphic object, either a :class:`~sage.plot.graphics.Graphics`
|
|
2832
|
+
for a 2D plot (i.e. based on 2 coordinates of the ambient chart)
|
|
2833
|
+
or a :class:`~sage.plot.plot3d.base.Graphics3d` for a 3D plot
|
|
2834
|
+
(i.e. based on 3 coordinates of the ambient chart)
|
|
2835
|
+
|
|
2836
|
+
EXAMPLES:
|
|
2837
|
+
|
|
2838
|
+
A 2-dimensional chart plotted in terms of itself results in a
|
|
2839
|
+
rectangular grid::
|
|
2840
|
+
|
|
2841
|
+
sage: R2 = Manifold(2, 'R^2', structure='topological') # the Euclidean plane
|
|
2842
|
+
sage: c_cart.<x,y> = R2.chart() # Cartesian coordinates
|
|
2843
|
+
sage: g = c_cart.plot(); g # equivalent to c_cart.plot(c_cart) # needs sage.plot
|
|
2844
|
+
Graphics object consisting of 18 graphics primitives
|
|
2845
|
+
|
|
2846
|
+
.. PLOT::
|
|
2847
|
+
|
|
2848
|
+
R2 = Manifold(2, 'R^2', structure='topological')
|
|
2849
|
+
c_cart = R2.chart('x y')
|
|
2850
|
+
g = c_cart.plot()
|
|
2851
|
+
sphinx_plot(g)
|
|
2852
|
+
|
|
2853
|
+
Grid of polar coordinates in terms of Cartesian coordinates in the
|
|
2854
|
+
Euclidean plane::
|
|
2855
|
+
|
|
2856
|
+
sage: U = R2.open_subset('U', coord_def={c_cart: (y!=0, x<0)}) # the complement of the segment y=0 and x>0
|
|
2857
|
+
sage: c_pol.<r,ph> = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi') # polar coordinates on U
|
|
2858
|
+
sage: pol_to_cart = c_pol.transition_map(c_cart, [r*cos(ph), r*sin(ph)])
|
|
2859
|
+
sage: g = c_pol.plot(c_cart); g # needs sage.plot
|
|
2860
|
+
Graphics object consisting of 18 graphics primitives
|
|
2861
|
+
|
|
2862
|
+
.. PLOT::
|
|
2863
|
+
|
|
2864
|
+
R2 = Manifold(2, 'R^2', structure='topological')
|
|
2865
|
+
c_cart = R2.chart('x y'); x, y = c_cart[:]
|
|
2866
|
+
U = R2.open_subset('U', coord_def={c_cart: (y!=0, x<0)})
|
|
2867
|
+
c_pol = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi'); r, ph = c_pol[:]
|
|
2868
|
+
pol_to_cart = c_pol.transition_map(c_cart, [r*cos(ph), r*sin(ph)])
|
|
2869
|
+
g = c_pol.plot(c_cart)
|
|
2870
|
+
sphinx_plot(g)
|
|
2871
|
+
|
|
2872
|
+
Call with non-default values::
|
|
2873
|
+
|
|
2874
|
+
sage: g = c_pol.plot(c_cart, ranges={ph:(pi/4,pi)}, # needs sage.plot
|
|
2875
|
+
....: number_values={r:7, ph:17},
|
|
2876
|
+
....: color={r:'red', ph:'green'},
|
|
2877
|
+
....: style={r:'-', ph:'--'})
|
|
2878
|
+
|
|
2879
|
+
.. PLOT::
|
|
2880
|
+
|
|
2881
|
+
R2 = Manifold(2, 'R^2', structure='topological')
|
|
2882
|
+
c_cart = R2.chart('x y'); x, y = c_cart[:]
|
|
2883
|
+
U = R2.open_subset('U', coord_def={c_cart: (y!=0, x<0)})
|
|
2884
|
+
c_pol = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi'); r, ph = c_pol[:]
|
|
2885
|
+
pol_to_cart = c_pol.transition_map(c_cart, [r*cos(ph), r*sin(ph)])
|
|
2886
|
+
g = c_pol.plot(c_cart, ranges={ph:(pi/4,pi)}, number_values={r:7, ph:17},
|
|
2887
|
+
color={r:'red', ph:'green'}, style={r:'-', ph:'--'})
|
|
2888
|
+
sphinx_plot(g)
|
|
2889
|
+
|
|
2890
|
+
A single coordinate line can be drawn::
|
|
2891
|
+
|
|
2892
|
+
sage: g = c_pol.plot(c_cart, # draw a circle of radius r=2 # needs sage.plot
|
|
2893
|
+
....: fixed_coords={r: 2})
|
|
2894
|
+
|
|
2895
|
+
.. PLOT::
|
|
2896
|
+
|
|
2897
|
+
R2 = Manifold(2, 'R^2', structure='topological')
|
|
2898
|
+
c_cart = R2.chart('x y'); x, y = c_cart[:]
|
|
2899
|
+
U = R2.open_subset('U', coord_def={c_cart: (y!=0, x<0)})
|
|
2900
|
+
c_pol = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi'); r, ph = c_pol[:]
|
|
2901
|
+
pol_to_cart = c_pol.transition_map(c_cart, [r*cos(ph), r*sin(ph)])
|
|
2902
|
+
g = c_pol.plot(c_cart, fixed_coords={r: 2})
|
|
2903
|
+
sphinx_plot(g)
|
|
2904
|
+
|
|
2905
|
+
::
|
|
2906
|
+
|
|
2907
|
+
sage: g = c_pol.plot(c_cart, # draw a segment at phi=pi/4 # needs sage.plot
|
|
2908
|
+
....: fixed_coords={ph: pi/4})
|
|
2909
|
+
|
|
2910
|
+
.. PLOT::
|
|
2911
|
+
|
|
2912
|
+
R2 = Manifold(2, 'R^2', structure='topological')
|
|
2913
|
+
c_cart = R2.chart('x y'); x, y = c_cart[:]
|
|
2914
|
+
U = R2.open_subset('U', coord_def={c_cart: (y!=0, x<0)})
|
|
2915
|
+
c_pol = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi'); r, ph = c_pol[:]
|
|
2916
|
+
pol_to_cart = c_pol.transition_map(c_cart, [r*cos(ph), r*sin(ph)])
|
|
2917
|
+
g = c_pol.plot(c_cart, fixed_coords={ph: pi/4})
|
|
2918
|
+
sphinx_plot(g)
|
|
2919
|
+
|
|
2920
|
+
An example with the ambient chart lying in an another manifold (the
|
|
2921
|
+
plot is then performed via some manifold map passed as the
|
|
2922
|
+
argument ``mapping``): 3D plot of the stereographic charts on the
|
|
2923
|
+
2-sphere::
|
|
2924
|
+
|
|
2925
|
+
sage: S2 = Manifold(2, 'S^2', structure='topological') # the 2-sphere
|
|
2926
|
+
sage: U = S2.open_subset('U'); V = S2.open_subset('V') # complement of the North and South pole, respectively
|
|
2927
|
+
sage: S2.declare_union(U,V)
|
|
2928
|
+
sage: c_xy.<x,y> = U.chart() # stereographic coordinates from the North pole
|
|
2929
|
+
sage: c_uv.<u,v> = V.chart() # stereographic coordinates from the South pole
|
|
2930
|
+
sage: xy_to_uv = c_xy.transition_map(c_uv, (x/(x^2+y^2), y/(x^2+y^2)),
|
|
2931
|
+
....: intersection_name='W', restrictions1= x^2+y^2!=0,
|
|
2932
|
+
....: restrictions2= u^2+v^2!=0)
|
|
2933
|
+
sage: uv_to_xy = xy_to_uv.inverse()
|
|
2934
|
+
sage: R3 = Manifold(3, 'R^3', structure='topological') # the Euclidean space R^3
|
|
2935
|
+
sage: c_cart.<X,Y,Z> = R3.chart() # Cartesian coordinates on R^3
|
|
2936
|
+
sage: Phi = S2.continuous_map(R3, {(c_xy, c_cart): [2*x/(1+x^2+y^2),
|
|
2937
|
+
....: 2*y/(1+x^2+y^2), (x^2+y^2-1)/(1+x^2+y^2)],
|
|
2938
|
+
....: (c_uv, c_cart): [2*u/(1+u^2+v^2),
|
|
2939
|
+
....: 2*v/(1+u^2+v^2), (1-u^2-v^2)/(1+u^2+v^2)]},
|
|
2940
|
+
....: name='Phi', latex_name=r'\Phi') # Embedding of S^2 in R^3
|
|
2941
|
+
sage: g = c_xy.plot(c_cart, mapping=Phi); g # needs sage.plot
|
|
2942
|
+
Graphics3d Object
|
|
2943
|
+
|
|
2944
|
+
.. PLOT::
|
|
2945
|
+
|
|
2946
|
+
S2 = Manifold(2, 'S^2', structure='topological')
|
|
2947
|
+
U = S2.open_subset('U') ; V = S2.open_subset('V')
|
|
2948
|
+
S2.declare_union(U,V)
|
|
2949
|
+
c_xy = U.chart('x y'); x, y = c_xy[:]
|
|
2950
|
+
c_uv = V.chart('u v'); u, v = c_uv[:]
|
|
2951
|
+
xy_to_uv = c_xy.transition_map(c_uv, (x/(x**2+y**2), y/(x**2+y**2)),
|
|
2952
|
+
intersection_name='W', restrictions1= x**2+y**2!=0,
|
|
2953
|
+
restrictions2= u**2+v**2!=0)
|
|
2954
|
+
uv_to_xy = xy_to_uv.inverse()
|
|
2955
|
+
R3 = Manifold(3, 'R^3', structure='topological')
|
|
2956
|
+
c_cart = R3.chart('X Y Z')
|
|
2957
|
+
Phi = S2.continuous_map(R3, {(c_xy, c_cart): [2*x/(1+x**2+y**2),
|
|
2958
|
+
2*y/(1+x**2+y**2), (x**2+y**2-1)/(1+x**2+y**2)],
|
|
2959
|
+
(c_uv, c_cart): [2*u/(1+u**2+v**2),
|
|
2960
|
+
2*v/(1+u**2+v**2), (1-u**2-v**2)/(1+u**2+v**2)]},
|
|
2961
|
+
name='Phi', latex_name=r'\Phi')
|
|
2962
|
+
sphinx_plot(c_xy.plot(c_cart, mapping=Phi))
|
|
2963
|
+
|
|
2964
|
+
NB: to get a better coverage of the whole sphere, one should increase
|
|
2965
|
+
the coordinate sampling via the argument ``number_values`` or the
|
|
2966
|
+
argument ``steps`` (only the default value, ``number_values = 5``, is
|
|
2967
|
+
used here, which is pretty low).
|
|
2968
|
+
|
|
2969
|
+
The same plot without the ``(X,Y,Z)`` axes labels::
|
|
2970
|
+
|
|
2971
|
+
sage: g = c_xy.plot(c_cart, mapping=Phi, label_axes=False) # needs sage.plot
|
|
2972
|
+
|
|
2973
|
+
The North and South stereographic charts on the same plot::
|
|
2974
|
+
|
|
2975
|
+
sage: g2 = c_uv.plot(c_cart, mapping=Phi, color='green') # needs sage.plot
|
|
2976
|
+
sage: g + g2 # needs sage.plot
|
|
2977
|
+
Graphics3d Object
|
|
2978
|
+
|
|
2979
|
+
.. PLOT::
|
|
2980
|
+
|
|
2981
|
+
S2 = Manifold(2, 'S^2', structure='topological')
|
|
2982
|
+
U = S2.open_subset('U') ; V = S2.open_subset('V')
|
|
2983
|
+
S2.declare_union(U,V)
|
|
2984
|
+
c_xy = U.chart('x y'); x, y = c_xy[:]
|
|
2985
|
+
c_uv = V.chart('u v'); u, v = c_uv[:]
|
|
2986
|
+
xy_to_uv = c_xy.transition_map(c_uv, (x/(x**2+y**2), y/(x**2+y**2)),
|
|
2987
|
+
intersection_name='W', restrictions1= x**2+y**2!=0,
|
|
2988
|
+
restrictions2= u**2+v**2!=0)
|
|
2989
|
+
uv_to_xy = xy_to_uv.inverse()
|
|
2990
|
+
R3 = Manifold(3, 'R^3', structure='topological')
|
|
2991
|
+
c_cart = R3.chart('X Y Z')
|
|
2992
|
+
Phi = S2.continuous_map(R3, {(c_xy, c_cart): [2*x/(1+x**2+y**2),
|
|
2993
|
+
2*y/(1+x**2+y**2), (x**2+y**2-1)/(1+x**2+y**2)],
|
|
2994
|
+
(c_uv, c_cart): [2*u/(1+u**2+v**2),
|
|
2995
|
+
2*v/(1+u**2+v**2), (1-u**2-v**2)/(1+u**2+v**2)]},
|
|
2996
|
+
name='Phi', latex_name=r'\Phi')
|
|
2997
|
+
g = c_xy.plot(c_cart, mapping=Phi, label_axes=False)
|
|
2998
|
+
g2 = c_uv.plot(c_cart, mapping=Phi, color='green')
|
|
2999
|
+
sphinx_plot(g+g2)
|
|
3000
|
+
|
|
3001
|
+
South stereographic chart drawn in terms of the North one (we split
|
|
3002
|
+
the plot in four parts to avoid the singularity at `(u,v)=(0,0)`)::
|
|
3003
|
+
|
|
3004
|
+
sage: # long time, needs sage.plot
|
|
3005
|
+
sage: W = U.intersection(V) # the subset common to both charts
|
|
3006
|
+
sage: c_uvW = c_uv.restrict(W) # chart (W,(u,v))
|
|
3007
|
+
sage: gSN1 = c_uvW.plot(c_xy, ranges={u:[-6.,-0.02], v:[-6.,-0.02]})
|
|
3008
|
+
sage: gSN2 = c_uvW.plot(c_xy, ranges={u:[-6.,-0.02], v:[0.02,6.]})
|
|
3009
|
+
sage: gSN3 = c_uvW.plot(c_xy, ranges={u:[0.02,6.], v:[-6.,-0.02]})
|
|
3010
|
+
sage: gSN4 = c_uvW.plot(c_xy, ranges={u:[0.02,6.], v:[0.02,6.]})
|
|
3011
|
+
sage: show(gSN1+gSN2+gSN3+gSN4, xmin=-1.5, xmax=1.5, ymin=-1.5, ymax=1.5)
|
|
3012
|
+
|
|
3013
|
+
.. PLOT::
|
|
3014
|
+
|
|
3015
|
+
S2 = Manifold(2, 'S^2', structure='topological')
|
|
3016
|
+
U = S2.open_subset('U'); V = S2.open_subset('V'); S2.declare_union(U,V)
|
|
3017
|
+
c_xy = U.chart('x y'); x, y = c_xy[:]
|
|
3018
|
+
c_uv = V.chart('u v'); u, v = c_uv[:]
|
|
3019
|
+
xy_to_uv = c_xy.transition_map(c_uv, (x/(x**2+y**2), y/(x**2+y**2)),
|
|
3020
|
+
intersection_name='W', restrictions1= x**2+y**2!=0,
|
|
3021
|
+
restrictions2= u**2+v**2!=0)
|
|
3022
|
+
uv_to_xy = xy_to_uv.inverse()
|
|
3023
|
+
c_uvW = c_uv.restrict(U.intersection(V))
|
|
3024
|
+
gSN1 = c_uvW.plot(c_xy, ranges={u:[-6.,-0.02], v:[-6.,-0.02]})
|
|
3025
|
+
gSN2 = c_uvW.plot(c_xy, ranges={u:[-6.,-0.02], v:[0.02,6.]})
|
|
3026
|
+
gSN3 = c_uvW.plot(c_xy, ranges={u:[0.02,6.], v:[-6.,-0.02]})
|
|
3027
|
+
gSN4 = c_uvW.plot(c_xy, ranges={u:[0.02,6.], v:[0.02,6.]})
|
|
3028
|
+
g = gSN1+gSN2+gSN3+gSN4; g.set_axes_range(-1.5, 1.5, -1.5, 1.5)
|
|
3029
|
+
sphinx_plot(g)
|
|
3030
|
+
|
|
3031
|
+
The coordinate line `u = 1` (red) and the coordinate line `v = 1`
|
|
3032
|
+
(green) on the same plot::
|
|
3033
|
+
|
|
3034
|
+
sage: # long time, needs sage.plot
|
|
3035
|
+
sage: gu1 = c_uvW.plot(c_xy, fixed_coords={u: 1}, max_range=20,
|
|
3036
|
+
....: plot_points=300)
|
|
3037
|
+
sage: gv1 = c_uvW.plot(c_xy, fixed_coords={v: 1}, max_range=20,
|
|
3038
|
+
....: plot_points=300, color='green')
|
|
3039
|
+
sage: gu1 + gv1
|
|
3040
|
+
Graphics object consisting of 2 graphics primitives
|
|
3041
|
+
|
|
3042
|
+
.. PLOT::
|
|
3043
|
+
|
|
3044
|
+
S2 = Manifold(2, 'S^2', structure='topological')
|
|
3045
|
+
U = S2.open_subset('U'); V = S2.open_subset('V'); S2.declare_union(U,V)
|
|
3046
|
+
c_xy = U.chart('x y'); x, y = c_xy[:]
|
|
3047
|
+
c_uv = V.chart('u v'); u, v = c_uv[:]
|
|
3048
|
+
xy_to_uv = c_xy.transition_map(c_uv, (x/(x**2+y**2), y/(x**2+y**2)),
|
|
3049
|
+
intersection_name='W', restrictions1= x**2+y**2!=0,
|
|
3050
|
+
restrictions2= u**2+v**2!=0)
|
|
3051
|
+
uv_to_xy = xy_to_uv.inverse()
|
|
3052
|
+
c_uvW = c_uv.restrict(U.intersection(V))
|
|
3053
|
+
gu1 = c_uvW.plot(c_xy, fixed_coords={u: 1}, max_range=20, plot_points=300)
|
|
3054
|
+
gv1 = c_uvW.plot(c_xy, fixed_coords={v: 1}, max_range=20, plot_points=300,
|
|
3055
|
+
color='green')
|
|
3056
|
+
sphinx_plot(gu1+gv1)
|
|
3057
|
+
|
|
3058
|
+
Note that we have set ``max_range=20`` to have a wider range for
|
|
3059
|
+
the coordinates `u` and `v`, i.e. to have `[-20, 20]` instead of
|
|
3060
|
+
the default `[-8, 8]`.
|
|
3061
|
+
|
|
3062
|
+
A 3-dimensional chart plotted in terms of itself results in a 3D
|
|
3063
|
+
rectangular grid::
|
|
3064
|
+
|
|
3065
|
+
sage: # long time, needs sage.plot
|
|
3066
|
+
sage: g = c_cart.plot() # equivalent to c_cart.plot(c_cart)
|
|
3067
|
+
sage: g
|
|
3068
|
+
Graphics3d Object
|
|
3069
|
+
|
|
3070
|
+
.. PLOT::
|
|
3071
|
+
|
|
3072
|
+
R3 = Manifold(3, 'R^3', structure='topological')
|
|
3073
|
+
c_cart = R3.chart('X Y Z')
|
|
3074
|
+
sphinx_plot(c_cart.plot())
|
|
3075
|
+
|
|
3076
|
+
A 4-dimensional chart plotted in terms of itself (the plot is
|
|
3077
|
+
performed for at most 3 coordinates, which must be specified via
|
|
3078
|
+
the argument ``ambient_coords``)::
|
|
3079
|
+
|
|
3080
|
+
sage: # needs sage.plot
|
|
3081
|
+
sage: M = Manifold(4, 'M', structure='topological')
|
|
3082
|
+
sage: X.<t,x,y,z> = M.chart()
|
|
3083
|
+
sage: g = X.plot(ambient_coords=(t,x,y)) # the coordinate z is not depicted # long time
|
|
3084
|
+
sage: g # long time
|
|
3085
|
+
Graphics3d Object
|
|
3086
|
+
|
|
3087
|
+
.. PLOT::
|
|
3088
|
+
|
|
3089
|
+
M = Manifold(4, 'M', structure='topological')
|
|
3090
|
+
X = M.chart('t x y z'); t,x,y,z = X[:]
|
|
3091
|
+
g = X.plot(ambient_coords=(t,x,y))
|
|
3092
|
+
sphinx_plot(g)
|
|
3093
|
+
|
|
3094
|
+
::
|
|
3095
|
+
|
|
3096
|
+
sage: # needs sage.plot
|
|
3097
|
+
sage: g = X.plot(ambient_coords=(t,y)) # the coordinates x and z are not depicted
|
|
3098
|
+
sage: g
|
|
3099
|
+
Graphics object consisting of 18 graphics primitives
|
|
3100
|
+
|
|
3101
|
+
.. PLOT::
|
|
3102
|
+
|
|
3103
|
+
M = Manifold(4, 'M', structure='topological')
|
|
3104
|
+
X = M.chart('t x y z'); t,x,y,z = X[:]
|
|
3105
|
+
g = X.plot(ambient_coords=(t,y))
|
|
3106
|
+
sphinx_plot(g)
|
|
3107
|
+
|
|
3108
|
+
Note that the default values of some arguments of the method ``plot``
|
|
3109
|
+
are stored in the dictionary ``plot.options``::
|
|
3110
|
+
|
|
3111
|
+
sage: X.plot.options # random (dictionary output) # needs sage.plot
|
|
3112
|
+
{'color': 'red', 'label_axes': True, 'max_range': 8,
|
|
3113
|
+
'plot_points': 75, 'style': '-', 'thickness': 1}
|
|
3114
|
+
|
|
3115
|
+
so that they can be adjusted by the user::
|
|
3116
|
+
|
|
3117
|
+
sage: X.plot.options['color'] = 'blue' # needs sage.plot
|
|
3118
|
+
|
|
3119
|
+
From now on, all chart plots will use blue as the default color.
|
|
3120
|
+
To restore the original default options, it suffices to type::
|
|
3121
|
+
|
|
3122
|
+
sage: X.plot.reset() # needs sage.plot
|
|
3123
|
+
"""
|
|
3124
|
+
from sage.manifolds.continuous_map import ContinuousMap
|
|
3125
|
+
from sage.manifolds.utilities import set_axes_labels
|
|
3126
|
+
from sage.misc.functional import numerical_approx
|
|
3127
|
+
from sage.plot.graphics import Graphics
|
|
3128
|
+
from sage.plot.line import line
|
|
3129
|
+
|
|
3130
|
+
# Extract the kwds options
|
|
3131
|
+
max_range = kwds['max_range']
|
|
3132
|
+
color = kwds['color']
|
|
3133
|
+
style = kwds['style']
|
|
3134
|
+
thickness = kwds['thickness']
|
|
3135
|
+
plot_points = kwds['plot_points']
|
|
3136
|
+
label_axes = kwds['label_axes']
|
|
3137
|
+
|
|
3138
|
+
def _plot_xx_list(xx_list, rem_coords, ranges, steps, number_values):
|
|
3139
|
+
r"""
|
|
3140
|
+
Helper function to plot the coordinate grid.
|
|
3141
|
+
"""
|
|
3142
|
+
coord = rem_coords[0]
|
|
3143
|
+
xmin = ranges[coord][0]
|
|
3144
|
+
sx = steps[coord]
|
|
3145
|
+
resu = []
|
|
3146
|
+
for xx in xx_list:
|
|
3147
|
+
xc = xmin
|
|
3148
|
+
for i in range(number_values[coord]):
|
|
3149
|
+
nxx = list(xx)
|
|
3150
|
+
nxx[self._xx.index(coord)] = xc
|
|
3151
|
+
resu.append(nxx)
|
|
3152
|
+
xc += sx
|
|
3153
|
+
if len(rem_coords) == 1:
|
|
3154
|
+
return resu
|
|
3155
|
+
else:
|
|
3156
|
+
rem_coords.remove(coord)
|
|
3157
|
+
return _plot_xx_list(resu, rem_coords, ranges, steps, number_values)
|
|
3158
|
+
|
|
3159
|
+
if chart is None:
|
|
3160
|
+
chart = self
|
|
3161
|
+
elif not isinstance(chart, Chart):
|
|
3162
|
+
raise TypeError("the argument 'chart' must be a coordinate chart")
|
|
3163
|
+
#
|
|
3164
|
+
# 1/ Determination of the relation between self and chart
|
|
3165
|
+
# ------------------------------------------------------------
|
|
3166
|
+
nc = self._manifold.dimension()
|
|
3167
|
+
if chart is self:
|
|
3168
|
+
transf = self.multifunction(*(self._xx))
|
|
3169
|
+
if nc > 3:
|
|
3170
|
+
if ambient_coords is None:
|
|
3171
|
+
raise TypeError("the argument 'ambient_coords' must be provided")
|
|
3172
|
+
if len(ambient_coords) > 3:
|
|
3173
|
+
raise ValueError("too many ambient coordinates")
|
|
3174
|
+
fixed_coords = {}
|
|
3175
|
+
for coord in self._xx:
|
|
3176
|
+
if coord not in ambient_coords:
|
|
3177
|
+
fixed_coords[coord] = 0
|
|
3178
|
+
else:
|
|
3179
|
+
transf = None # to be the MultiCoordFunction object relating self
|
|
3180
|
+
# to the ambient chart
|
|
3181
|
+
if mapping is None:
|
|
3182
|
+
if not self.domain().is_subset(chart.domain()):
|
|
3183
|
+
raise ValueError(
|
|
3184
|
+
"the domain of {} is not ".format(self)
|
|
3185
|
+
+ "included in that of {}".format(chart)
|
|
3186
|
+
)
|
|
3187
|
+
coord_changes = chart.domain()._coord_changes
|
|
3188
|
+
for chart_pair in coord_changes:
|
|
3189
|
+
if chart_pair == (self, chart):
|
|
3190
|
+
transf = coord_changes[chart_pair]._transf
|
|
3191
|
+
break
|
|
3192
|
+
else:
|
|
3193
|
+
# Search for a subchart
|
|
3194
|
+
for chart_pair in coord_changes:
|
|
3195
|
+
for schart in chart._subcharts:
|
|
3196
|
+
if chart_pair == (self, schart):
|
|
3197
|
+
transf = coord_changes[chart_pair]._transf
|
|
3198
|
+
else:
|
|
3199
|
+
if not isinstance(mapping, ContinuousMap):
|
|
3200
|
+
raise TypeError(
|
|
3201
|
+
"the argument 'mapping' must be a continuous manifold map"
|
|
3202
|
+
)
|
|
3203
|
+
if not self.domain().is_subset(mapping.domain()):
|
|
3204
|
+
raise ValueError(
|
|
3205
|
+
"the domain of {} is not ".format(self)
|
|
3206
|
+
+ "included in that of {}".format(mapping)
|
|
3207
|
+
)
|
|
3208
|
+
if not chart.domain().is_subset(mapping._codomain):
|
|
3209
|
+
raise ValueError(
|
|
3210
|
+
"the domain of {} is not ".format(chart)
|
|
3211
|
+
+ "included in the codomain of {}".format(mapping)
|
|
3212
|
+
)
|
|
3213
|
+
try:
|
|
3214
|
+
transf = mapping.coord_functions(chart1=self, chart2=chart)
|
|
3215
|
+
except ValueError:
|
|
3216
|
+
pass
|
|
3217
|
+
if transf is None:
|
|
3218
|
+
raise ValueError(
|
|
3219
|
+
"no relation has been found between "
|
|
3220
|
+
+ "{} and {}".format(self, chart)
|
|
3221
|
+
)
|
|
3222
|
+
#
|
|
3223
|
+
# 2/ Treatment of input parameters
|
|
3224
|
+
# -----------------------------
|
|
3225
|
+
if fixed_coords is None:
|
|
3226
|
+
coords = self._xx
|
|
3227
|
+
else:
|
|
3228
|
+
fixed_coord_list = fixed_coords.keys()
|
|
3229
|
+
coords = []
|
|
3230
|
+
for coord in self._xx:
|
|
3231
|
+
if coord not in fixed_coord_list:
|
|
3232
|
+
coords.append(coord)
|
|
3233
|
+
coords = tuple(coords)
|
|
3234
|
+
if ambient_coords is None:
|
|
3235
|
+
ambient_coords = chart._xx
|
|
3236
|
+
elif not isinstance(ambient_coords, tuple):
|
|
3237
|
+
ambient_coords = tuple(ambient_coords)
|
|
3238
|
+
nca = len(ambient_coords)
|
|
3239
|
+
if nca != 2 and nca != 3:
|
|
3240
|
+
raise ValueError("bad number of ambient coordinates: {}".format(nca))
|
|
3241
|
+
if ranges is None:
|
|
3242
|
+
ranges = {}
|
|
3243
|
+
ranges0 = {}
|
|
3244
|
+
for coord in coords:
|
|
3245
|
+
if coord in ranges:
|
|
3246
|
+
ranges0[coord] = (
|
|
3247
|
+
numerical_approx(ranges[coord][0]),
|
|
3248
|
+
numerical_approx(ranges[coord][1]),
|
|
3249
|
+
)
|
|
3250
|
+
else:
|
|
3251
|
+
bounds = self._bounds[self._xx.index(coord)]
|
|
3252
|
+
if bounds[0][0] == -Infinity:
|
|
3253
|
+
xmin = numerical_approx(-max_range)
|
|
3254
|
+
elif bounds[0][1]:
|
|
3255
|
+
xmin = numerical_approx(bounds[0][0])
|
|
3256
|
+
else:
|
|
3257
|
+
xmin = numerical_approx(bounds[0][0] + 1.0e-3)
|
|
3258
|
+
if bounds[1][0] == Infinity:
|
|
3259
|
+
xmax = numerical_approx(max_range)
|
|
3260
|
+
elif bounds[1][1]:
|
|
3261
|
+
xmax = numerical_approx(bounds[1][0])
|
|
3262
|
+
else:
|
|
3263
|
+
xmax = numerical_approx(bounds[1][0] - 1.0e-3)
|
|
3264
|
+
ranges0[coord] = (xmin, xmax)
|
|
3265
|
+
ranges = ranges0
|
|
3266
|
+
if number_values is None:
|
|
3267
|
+
if nca == 2: # 2D plot
|
|
3268
|
+
number_values = 9
|
|
3269
|
+
else: # 3D plot
|
|
3270
|
+
number_values = 5
|
|
3271
|
+
if not isinstance(number_values, dict):
|
|
3272
|
+
number_values0 = {}
|
|
3273
|
+
for coord in coords:
|
|
3274
|
+
number_values0[coord] = number_values
|
|
3275
|
+
number_values = number_values0
|
|
3276
|
+
if steps is None:
|
|
3277
|
+
steps = {}
|
|
3278
|
+
for coord in coords:
|
|
3279
|
+
if coord not in steps:
|
|
3280
|
+
steps[coord] = (ranges[coord][1] - ranges[coord][0]) / (
|
|
3281
|
+
number_values[coord] - 1
|
|
3282
|
+
)
|
|
3283
|
+
else:
|
|
3284
|
+
from sage.functions.other import floor
|
|
3285
|
+
|
|
3286
|
+
number_values[coord] = 1 + floor(
|
|
3287
|
+
(ranges[coord][1] - ranges[coord][0]) / steps[coord]
|
|
3288
|
+
)
|
|
3289
|
+
if not isinstance(color, dict):
|
|
3290
|
+
color0 = {}
|
|
3291
|
+
for coord in coords:
|
|
3292
|
+
color0[coord] = color
|
|
3293
|
+
color = color0
|
|
3294
|
+
if not isinstance(style, dict):
|
|
3295
|
+
style0 = {}
|
|
3296
|
+
for coord in coords:
|
|
3297
|
+
style0[coord] = style
|
|
3298
|
+
style = style0
|
|
3299
|
+
if not isinstance(thickness, dict):
|
|
3300
|
+
thickness0 = {}
|
|
3301
|
+
for coord in coords:
|
|
3302
|
+
thickness0[coord] = thickness
|
|
3303
|
+
thickness = thickness0
|
|
3304
|
+
if not isinstance(plot_points, dict):
|
|
3305
|
+
plot_points0 = {}
|
|
3306
|
+
for coord in coords:
|
|
3307
|
+
plot_points0[coord] = plot_points
|
|
3308
|
+
plot_points = plot_points0
|
|
3309
|
+
#
|
|
3310
|
+
# 3/ Plots
|
|
3311
|
+
# -----
|
|
3312
|
+
xx0 = [0] * nc
|
|
3313
|
+
if fixed_coords is not None:
|
|
3314
|
+
if len(fixed_coords) != nc - len(coords):
|
|
3315
|
+
raise ValueError("bad number of fixed coordinates")
|
|
3316
|
+
for fc, val in fixed_coords.items():
|
|
3317
|
+
xx0[self._xx.index(fc)] = val
|
|
3318
|
+
ind_a = [chart._xx.index(ac) for ac in ambient_coords]
|
|
3319
|
+
resu = Graphics()
|
|
3320
|
+
for coord in coords:
|
|
3321
|
+
color_c, style_c = color[coord], style[coord]
|
|
3322
|
+
thickness_c = thickness[coord]
|
|
3323
|
+
rem_coords = list(coords)
|
|
3324
|
+
rem_coords.remove(coord)
|
|
3325
|
+
xx_list = [xx0]
|
|
3326
|
+
if len(rem_coords) >= 1:
|
|
3327
|
+
xx_list = _plot_xx_list(
|
|
3328
|
+
xx_list, rem_coords, ranges, steps, number_values
|
|
3329
|
+
)
|
|
3330
|
+
xmin, xmax = ranges[coord]
|
|
3331
|
+
nbp = plot_points[coord]
|
|
3332
|
+
dx = (xmax - xmin) / (nbp - 1)
|
|
3333
|
+
ind_coord = self._xx.index(coord)
|
|
3334
|
+
for xx in xx_list:
|
|
3335
|
+
curve = []
|
|
3336
|
+
first_invalid = False # initialization
|
|
3337
|
+
xc = xmin
|
|
3338
|
+
xp = list(xx)
|
|
3339
|
+
if parameters is None:
|
|
3340
|
+
for i in range(nbp):
|
|
3341
|
+
xp[ind_coord] = xc
|
|
3342
|
+
if self.valid_coordinates(*xp, tolerance=1e-13):
|
|
3343
|
+
yp = transf(*xp, simplify=False)
|
|
3344
|
+
curve.append([numerical_approx(yp[j]) for j in ind_a])
|
|
3345
|
+
first_invalid = True # next invalid point will be
|
|
3346
|
+
# the first one
|
|
3347
|
+
else:
|
|
3348
|
+
if first_invalid:
|
|
3349
|
+
# the curve is stopped at previous point and
|
|
3350
|
+
# added to the graph:
|
|
3351
|
+
resu += line(
|
|
3352
|
+
curve,
|
|
3353
|
+
color=color_c,
|
|
3354
|
+
linestyle=style_c,
|
|
3355
|
+
thickness=thickness_c,
|
|
3356
|
+
)
|
|
3357
|
+
curve = [] # a new curve will start at the
|
|
3358
|
+
# next valid point
|
|
3359
|
+
first_invalid = False # next invalid point will not
|
|
3360
|
+
# be the first one
|
|
3361
|
+
xc += dx
|
|
3362
|
+
else:
|
|
3363
|
+
for i in range(nbp):
|
|
3364
|
+
xp[ind_coord] = xc
|
|
3365
|
+
if self.valid_coordinates(
|
|
3366
|
+
*xp, tolerance=1e-13, parameters=parameters
|
|
3367
|
+
):
|
|
3368
|
+
yp = transf(*xp, simplify=False)
|
|
3369
|
+
curve.append(
|
|
3370
|
+
[
|
|
3371
|
+
numerical_approx(yp[j].substitute(parameters))
|
|
3372
|
+
for j in ind_a
|
|
3373
|
+
]
|
|
3374
|
+
)
|
|
3375
|
+
first_invalid = True # next invalid point will be
|
|
3376
|
+
# the first one
|
|
3377
|
+
else:
|
|
3378
|
+
if first_invalid:
|
|
3379
|
+
# the curve is stopped at previous point and
|
|
3380
|
+
# added to the graph:
|
|
3381
|
+
resu += line(
|
|
3382
|
+
curve,
|
|
3383
|
+
color=color_c,
|
|
3384
|
+
linestyle=style_c,
|
|
3385
|
+
thickness=thickness_c,
|
|
3386
|
+
)
|
|
3387
|
+
curve = [] # a new curve will start at the
|
|
3388
|
+
# next valid point
|
|
3389
|
+
first_invalid = False # next invalid point will not
|
|
3390
|
+
# be the first one
|
|
3391
|
+
xc += dx
|
|
3392
|
+
if curve:
|
|
3393
|
+
resu += line(
|
|
3394
|
+
curve, color=color_c, linestyle=style_c, thickness=thickness_c
|
|
3395
|
+
)
|
|
3396
|
+
if nca == 2: # 2D graphic
|
|
3397
|
+
resu.set_aspect_ratio(1)
|
|
3398
|
+
if label_axes:
|
|
3399
|
+
# We update the dictionary _extra_kwds (options to be passed
|
|
3400
|
+
# to show()), instead of using the method
|
|
3401
|
+
# Graphics.axes_labels() since the latter is not robust w.r.t.
|
|
3402
|
+
# graph addition
|
|
3403
|
+
resu._extra_kwds['axes_labels'] = [
|
|
3404
|
+
r'$' + latex(ac) + r'$' for ac in ambient_coords
|
|
3405
|
+
]
|
|
3406
|
+
else: # 3D graphic
|
|
3407
|
+
resu.aspect_ratio(1)
|
|
3408
|
+
if label_axes:
|
|
3409
|
+
labels = [str(ac) for ac in ambient_coords]
|
|
3410
|
+
resu = set_axes_labels(resu, *labels)
|
|
3411
|
+
return resu
|
|
3412
|
+
|
|
3413
|
+
|
|
3414
|
+
# *****************************************************************************
|
|
3415
|
+
|
|
3416
|
+
|
|
3417
|
+
class CoordChange(SageObject):
|
|
3418
|
+
r"""
|
|
3419
|
+
Transition map between two charts of a topological manifold.
|
|
3420
|
+
|
|
3421
|
+
Giving two coordinate charts `(U, \varphi)` and `(V, \psi)` on a
|
|
3422
|
+
topological manifold `M` of dimension `n` over a topological field `K`,
|
|
3423
|
+
the *transition map from* `(U, \varphi)` *to* `(V, \psi)` is the map
|
|
3424
|
+
|
|
3425
|
+
.. MATH::
|
|
3426
|
+
|
|
3427
|
+
\psi\circ\varphi^{-1}: \varphi(U\cap V) \subset K^n
|
|
3428
|
+
\rightarrow \psi(U\cap V) \subset K^n.
|
|
3429
|
+
|
|
3430
|
+
In other words, the transition map `\psi \circ \varphi^{-1}` expresses
|
|
3431
|
+
the coordinates `(y^1, \ldots, y^n)` of `(V, \psi)` in terms of the
|
|
3432
|
+
coordinates `(x^1, \ldots, x^n)` of `(U, \varphi)` on the open subset
|
|
3433
|
+
where the two charts intersect, i.e. on `U \cap V`.
|
|
3434
|
+
|
|
3435
|
+
INPUT:
|
|
3436
|
+
|
|
3437
|
+
- ``chart1`` -- chart `(U, \varphi)`
|
|
3438
|
+
- ``chart2`` -- chart `(V, \psi)`
|
|
3439
|
+
- ``transformations`` -- tuple (or list) `(Y_1, \ldots, Y_2)`, where
|
|
3440
|
+
`Y_i` is the symbolic expression of the coordinate `y^i` in terms
|
|
3441
|
+
of the coordinates `(x^1, \ldots, x^n)`
|
|
3442
|
+
|
|
3443
|
+
EXAMPLES:
|
|
3444
|
+
|
|
3445
|
+
Transition map on a 2-dimensional topological manifold::
|
|
3446
|
+
|
|
3447
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
3448
|
+
sage: X.<x,y> = M.chart()
|
|
3449
|
+
sage: Y.<u,v> = M.chart()
|
|
3450
|
+
sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
|
|
3451
|
+
sage: X_to_Y
|
|
3452
|
+
Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))
|
|
3453
|
+
sage: type(X_to_Y)
|
|
3454
|
+
<class 'sage.manifolds.chart.CoordChange'>
|
|
3455
|
+
sage: X_to_Y.display()
|
|
3456
|
+
u = x + y
|
|
3457
|
+
v = x - y
|
|
3458
|
+
"""
|
|
3459
|
+
|
|
3460
|
+
def __init__(self, chart1, chart2, *transformations):
|
|
3461
|
+
r"""
|
|
3462
|
+
Construct a transition map.
|
|
3463
|
+
|
|
3464
|
+
TESTS::
|
|
3465
|
+
|
|
3466
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
3467
|
+
sage: X.<x,y> = M.chart()
|
|
3468
|
+
sage: Y.<u,v> = M.chart()
|
|
3469
|
+
sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
|
|
3470
|
+
sage: X_to_Y
|
|
3471
|
+
Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))
|
|
3472
|
+
sage: type(X_to_Y)
|
|
3473
|
+
<class 'sage.manifolds.chart.CoordChange'>
|
|
3474
|
+
sage: TestSuite(X_to_Y).run()
|
|
3475
|
+
"""
|
|
3476
|
+
self._n1 = len(chart1._xx)
|
|
3477
|
+
self._n2 = len(chart2._xx)
|
|
3478
|
+
if len(transformations) != self._n2:
|
|
3479
|
+
raise ValueError(
|
|
3480
|
+
"{} coordinate transformations ".format(self._n2) + "must be provided"
|
|
3481
|
+
)
|
|
3482
|
+
self._chart1 = chart1
|
|
3483
|
+
self._chart2 = chart2
|
|
3484
|
+
# The coordinate transformations are implemented via the class
|
|
3485
|
+
# MultiCoordFunction:
|
|
3486
|
+
self._transf = chart1.multifunction(*transformations)
|
|
3487
|
+
self._inverse = None
|
|
3488
|
+
# If the two charts are on the same open subset, the coordinate change
|
|
3489
|
+
# is added to the subset (and supersets) dictionary:
|
|
3490
|
+
if chart1.domain() == chart2.domain():
|
|
3491
|
+
domain = chart1.domain()
|
|
3492
|
+
for sdom in domain.open_supersets():
|
|
3493
|
+
sdom._coord_changes[(chart1, chart2)] = self
|
|
3494
|
+
|
|
3495
|
+
def _repr_(self):
|
|
3496
|
+
r"""
|
|
3497
|
+
String representation of the transition map.
|
|
3498
|
+
|
|
3499
|
+
TESTS::
|
|
3500
|
+
|
|
3501
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
3502
|
+
sage: X.<x,y> = M.chart()
|
|
3503
|
+
sage: Y.<u,v> = M.chart()
|
|
3504
|
+
sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
|
|
3505
|
+
sage: X_to_Y._repr_()
|
|
3506
|
+
'Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))'
|
|
3507
|
+
sage: repr(X_to_Y) # indirect doctest
|
|
3508
|
+
'Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))'
|
|
3509
|
+
sage: X_to_Y # indirect doctest
|
|
3510
|
+
Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))
|
|
3511
|
+
"""
|
|
3512
|
+
return "Change of coordinates from {} to {}".format(self._chart1, self._chart2)
|
|
3513
|
+
|
|
3514
|
+
def _latex_(self):
|
|
3515
|
+
r"""
|
|
3516
|
+
LaTeX representation of the transition map.
|
|
3517
|
+
|
|
3518
|
+
TESTS::
|
|
3519
|
+
|
|
3520
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
3521
|
+
sage: X.<x,y> = M.chart()
|
|
3522
|
+
sage: Y.<u,v> = M.chart()
|
|
3523
|
+
sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
|
|
3524
|
+
sage: X_to_Y._latex_()
|
|
3525
|
+
\left(M,(x, y)\right) \rightarrow \left(M,(u, v)\right)
|
|
3526
|
+
sage: latex(X_to_Y) # indirect doctest
|
|
3527
|
+
\left(M,(x, y)\right) \rightarrow \left(M,(u, v)\right)
|
|
3528
|
+
"""
|
|
3529
|
+
return latex(self._chart1) + r' \rightarrow ' + latex(self._chart2)
|
|
3530
|
+
|
|
3531
|
+
def __eq__(self, other):
|
|
3532
|
+
r"""
|
|
3533
|
+
Equality operator.
|
|
3534
|
+
|
|
3535
|
+
TESTS::
|
|
3536
|
+
|
|
3537
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
3538
|
+
sage: X.<x,y> = M.chart()
|
|
3539
|
+
sage: Y.<u,v> = M.chart()
|
|
3540
|
+
sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
|
|
3541
|
+
sage: X_to_Y == X_to_Y
|
|
3542
|
+
True
|
|
3543
|
+
sage: X_to_Y1 = X.transition_map(Y, [x+y, x-y])
|
|
3544
|
+
sage: X_to_Y == X_to_Y1
|
|
3545
|
+
True
|
|
3546
|
+
sage: X_to_Y2 = X.transition_map(Y, [2*y, -x])
|
|
3547
|
+
sage: X_to_Y == X_to_Y2
|
|
3548
|
+
False
|
|
3549
|
+
sage: Z.<w,z> = M.chart()
|
|
3550
|
+
sage: X_to_Z = X.transition_map(Z, [x+y, x-y])
|
|
3551
|
+
sage: X_to_Y == X_to_Z
|
|
3552
|
+
False
|
|
3553
|
+
"""
|
|
3554
|
+
if other is self:
|
|
3555
|
+
return True
|
|
3556
|
+
if not isinstance(other, CoordChange):
|
|
3557
|
+
return False
|
|
3558
|
+
return (
|
|
3559
|
+
(self._chart1 == other._chart1)
|
|
3560
|
+
and (self._chart2 == other._chart2)
|
|
3561
|
+
and (self._transf == other._transf)
|
|
3562
|
+
)
|
|
3563
|
+
|
|
3564
|
+
def __ne__(self, other):
|
|
3565
|
+
r"""
|
|
3566
|
+
Non-equality operator.
|
|
3567
|
+
|
|
3568
|
+
TESTS::
|
|
3569
|
+
|
|
3570
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
3571
|
+
sage: X.<x,y> = M.chart()
|
|
3572
|
+
sage: Y.<u,v> = M.chart()
|
|
3573
|
+
sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
|
|
3574
|
+
sage: X_to_Y2 = X.transition_map(Y, [2*y, -x])
|
|
3575
|
+
sage: X_to_Y != X_to_Y2
|
|
3576
|
+
True
|
|
3577
|
+
"""
|
|
3578
|
+
return not (self == other)
|
|
3579
|
+
|
|
3580
|
+
def __call__(self, *coords):
|
|
3581
|
+
r"""
|
|
3582
|
+
Compute the new coordinates from old ones.
|
|
3583
|
+
|
|
3584
|
+
INPUT:
|
|
3585
|
+
|
|
3586
|
+
- ``coords`` -- values of coordinates of ``chart1``
|
|
3587
|
+
|
|
3588
|
+
OUTPUT: tuple of values of coordinates of ``chart2``
|
|
3589
|
+
|
|
3590
|
+
EXAMPLES::
|
|
3591
|
+
|
|
3592
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
3593
|
+
sage: X.<x,y> = M.chart()
|
|
3594
|
+
sage: Y.<u,v> = M.chart()
|
|
3595
|
+
sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
|
|
3596
|
+
sage: X_to_Y(1,2)
|
|
3597
|
+
(3, -1)
|
|
3598
|
+
"""
|
|
3599
|
+
return self._transf(*coords)
|
|
3600
|
+
|
|
3601
|
+
def inverse(self):
|
|
3602
|
+
r"""
|
|
3603
|
+
Return the inverse coordinate transformation.
|
|
3604
|
+
|
|
3605
|
+
If the inverse is not already known, it is computed here. If the
|
|
3606
|
+
computation fails, the inverse can be set by hand via the method
|
|
3607
|
+
:meth:`set_inverse`.
|
|
3608
|
+
|
|
3609
|
+
OUTPUT:
|
|
3610
|
+
|
|
3611
|
+
- an instance of :class:`CoordChange` representing the inverse of
|
|
3612
|
+
the current coordinate transformation
|
|
3613
|
+
|
|
3614
|
+
EXAMPLES:
|
|
3615
|
+
|
|
3616
|
+
Inverse of a coordinate transformation corresponding to a rotation
|
|
3617
|
+
in the Cartesian plane::
|
|
3618
|
+
|
|
3619
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
3620
|
+
sage: c_xy.<x,y> = M.chart()
|
|
3621
|
+
sage: c_uv.<u,v> = M.chart()
|
|
3622
|
+
sage: phi = var('phi', domain='real')
|
|
3623
|
+
sage: xy_to_uv = c_xy.transition_map(c_uv,
|
|
3624
|
+
....: [cos(phi)*x + sin(phi)*y,
|
|
3625
|
+
....: -sin(phi)*x + cos(phi)*y])
|
|
3626
|
+
sage: M.coord_changes()
|
|
3627
|
+
{(Chart (M, (x, y)),
|
|
3628
|
+
Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))}
|
|
3629
|
+
sage: uv_to_xy = xy_to_uv.inverse(); uv_to_xy
|
|
3630
|
+
Change of coordinates from Chart (M, (u, v)) to Chart (M, (x, y))
|
|
3631
|
+
sage: uv_to_xy.display()
|
|
3632
|
+
x = u*cos(phi) - v*sin(phi)
|
|
3633
|
+
y = v*cos(phi) + u*sin(phi)
|
|
3634
|
+
sage: M.coord_changes() # random (dictionary output)
|
|
3635
|
+
{(Chart (M, (u, v)),
|
|
3636
|
+
Chart (M, (x, y))): Change of coordinates from Chart (M, (u, v)) to Chart (M, (x, y)),
|
|
3637
|
+
(Chart (M, (x, y)),
|
|
3638
|
+
Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))}
|
|
3639
|
+
|
|
3640
|
+
The result is cached::
|
|
3641
|
+
|
|
3642
|
+
sage: xy_to_uv.inverse() is uv_to_xy
|
|
3643
|
+
True
|
|
3644
|
+
|
|
3645
|
+
We have as well::
|
|
3646
|
+
|
|
3647
|
+
sage: uv_to_xy.inverse() is xy_to_uv
|
|
3648
|
+
True
|
|
3649
|
+
"""
|
|
3650
|
+
from sage.symbolic.relation import solve
|
|
3651
|
+
|
|
3652
|
+
if self._inverse is not None:
|
|
3653
|
+
return self._inverse
|
|
3654
|
+
# The computation is necessary:
|
|
3655
|
+
x1 = self._chart1._xx # list of coordinates in chart1
|
|
3656
|
+
x2 = self._chart2._xx # list of coordinates in chart2
|
|
3657
|
+
n1 = self._n1
|
|
3658
|
+
n2 = self._n2
|
|
3659
|
+
if n1 != n2:
|
|
3660
|
+
raise ValueError(
|
|
3661
|
+
"the change of coordinates is not invertible "
|
|
3662
|
+
+ "(different number of coordinates in the two "
|
|
3663
|
+
+ "charts)"
|
|
3664
|
+
)
|
|
3665
|
+
# New symbolic variables (different from x2 to allow for a
|
|
3666
|
+
# correct solution even when chart2 = chart1):
|
|
3667
|
+
base_field = self._chart1.domain().base_field_type()
|
|
3668
|
+
if base_field == 'real':
|
|
3669
|
+
coord_domain = ['real' for i in range(n2)]
|
|
3670
|
+
elif base_field == 'complex':
|
|
3671
|
+
coord_domain = ['complex' for i in range(n2)]
|
|
3672
|
+
else:
|
|
3673
|
+
coord_domain = [None for i in range(n2)]
|
|
3674
|
+
for i in range(n2):
|
|
3675
|
+
if x2[i].is_positive():
|
|
3676
|
+
coord_domain[i] = 'positive'
|
|
3677
|
+
xp2 = [SR.temp_var(domain=coord_domain[i]) for i in range(n2)]
|
|
3678
|
+
xx2 = self._transf.expr()
|
|
3679
|
+
equations = [xp2[i] == xx2[i] for i in range(n2)]
|
|
3680
|
+
try:
|
|
3681
|
+
solutions = solve(equations, *x1, solution_dict=True)
|
|
3682
|
+
except RuntimeError:
|
|
3683
|
+
raise RuntimeError(
|
|
3684
|
+
"the system could not be solved; use "
|
|
3685
|
+
+ "set_inverse() to set the inverse manually"
|
|
3686
|
+
)
|
|
3687
|
+
substitutions = dict(zip(xp2, x2))
|
|
3688
|
+
if len(solutions) == 1:
|
|
3689
|
+
x2_to_x1 = [solutions[0][x1[i]].subs(substitutions) for i in range(n1)]
|
|
3690
|
+
x2_to_x1_simpl = [] # to store simplified transformations
|
|
3691
|
+
for transf in x2_to_x1:
|
|
3692
|
+
try:
|
|
3693
|
+
transf = self._chart2.simplify(transf)
|
|
3694
|
+
except AttributeError:
|
|
3695
|
+
pass
|
|
3696
|
+
x2_to_x1_simpl.append(transf)
|
|
3697
|
+
x2_to_x1 = x2_to_x1_simpl
|
|
3698
|
+
else:
|
|
3699
|
+
list_x2_to_x1 = []
|
|
3700
|
+
for sol in solutions:
|
|
3701
|
+
if x2[0] in sol:
|
|
3702
|
+
raise ValueError(
|
|
3703
|
+
"the system could not be solved; use "
|
|
3704
|
+
+ "set_inverse() to set the inverse "
|
|
3705
|
+
+ "manually"
|
|
3706
|
+
)
|
|
3707
|
+
try:
|
|
3708
|
+
x2_to_x1 = [sol[x1[i]].subs(substitutions) for i in range(n1)]
|
|
3709
|
+
except KeyError: # sol is not a valid solution
|
|
3710
|
+
continue
|
|
3711
|
+
x2_to_x1_simpl = [] # to store simplified transformations
|
|
3712
|
+
for transf in x2_to_x1:
|
|
3713
|
+
try:
|
|
3714
|
+
transf = self._chart2.simplify(transf)
|
|
3715
|
+
except AttributeError:
|
|
3716
|
+
pass
|
|
3717
|
+
x2_to_x1_simpl.append(transf)
|
|
3718
|
+
x2_to_x1 = x2_to_x1_simpl
|
|
3719
|
+
if self._chart1.valid_coordinates(*x2_to_x1):
|
|
3720
|
+
list_x2_to_x1.append(x2_to_x1)
|
|
3721
|
+
if len(list_x2_to_x1) == 0:
|
|
3722
|
+
raise ValueError(
|
|
3723
|
+
"no solution found; use set_inverse() to "
|
|
3724
|
+
+ "set the inverse manually"
|
|
3725
|
+
)
|
|
3726
|
+
if len(list_x2_to_x1) > 1:
|
|
3727
|
+
print("Multiple solutions found: ")
|
|
3728
|
+
print(list_x2_to_x1)
|
|
3729
|
+
raise ValueError(
|
|
3730
|
+
"non-unique solution to the inverse coordinate "
|
|
3731
|
+
+ "transformation; use set_inverse() to set the inverse "
|
|
3732
|
+
+ "manually"
|
|
3733
|
+
)
|
|
3734
|
+
x2_to_x1 = list_x2_to_x1[0]
|
|
3735
|
+
self._inverse = type(self)(self._chart2, self._chart1, *x2_to_x1)
|
|
3736
|
+
self._inverse._inverse = self
|
|
3737
|
+
SR.cleanup_var(xp2)
|
|
3738
|
+
return self._inverse
|
|
3739
|
+
|
|
3740
|
+
def set_inverse(self, *transformations, **kwds):
|
|
3741
|
+
r"""
|
|
3742
|
+
Set the inverse of the coordinate transformation.
|
|
3743
|
+
|
|
3744
|
+
This is useful when the automatic computation via :meth:`inverse()`
|
|
3745
|
+
fails.
|
|
3746
|
+
|
|
3747
|
+
INPUT:
|
|
3748
|
+
|
|
3749
|
+
- ``transformations`` -- the inverse transformations expressed as a
|
|
3750
|
+
list of the expressions of the "old" coordinates in terms of the
|
|
3751
|
+
"new" ones
|
|
3752
|
+
- ``kwds`` -- optional arguments; valid keywords are
|
|
3753
|
+
|
|
3754
|
+
- ``check`` -- boolean (default: ``True``); whether the
|
|
3755
|
+
provided transformations are checked to be indeed the inverse
|
|
3756
|
+
coordinate transformations
|
|
3757
|
+
- ``verbose`` -- boolean (default: ``False``); whether
|
|
3758
|
+
some details of the check are printed out; if ``False``, no
|
|
3759
|
+
output is printed if the check is passed (see example below)
|
|
3760
|
+
|
|
3761
|
+
EXAMPLES:
|
|
3762
|
+
|
|
3763
|
+
From spherical coordinates to Cartesian ones in the plane::
|
|
3764
|
+
|
|
3765
|
+
sage: M = Manifold(2, 'R^2', structure='topological')
|
|
3766
|
+
sage: U = M.open_subset('U') # complement of the half line {y=0, x>= 0}
|
|
3767
|
+
sage: c_cart.<x,y> = U.chart()
|
|
3768
|
+
sage: c_spher.<r,ph> = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi')
|
|
3769
|
+
sage: spher_to_cart = c_spher.transition_map(c_cart,
|
|
3770
|
+
....: [r*cos(ph), r*sin(ph)])
|
|
3771
|
+
sage: spher_to_cart.set_inverse(sqrt(x^2+y^2), atan2(y,x))
|
|
3772
|
+
Check of the inverse coordinate transformation:
|
|
3773
|
+
r == r *passed*
|
|
3774
|
+
ph == arctan2(r*sin(ph), r*cos(ph)) **failed**
|
|
3775
|
+
x == x *passed*
|
|
3776
|
+
y == y *passed*
|
|
3777
|
+
NB: a failed report can reflect a mere lack of simplification.
|
|
3778
|
+
|
|
3779
|
+
As indicated, the failure for ``ph`` is due to a lack of simplification
|
|
3780
|
+
of the ``arctan2`` term, not to any error in the provided inverse
|
|
3781
|
+
formulas.
|
|
3782
|
+
|
|
3783
|
+
We have now::
|
|
3784
|
+
|
|
3785
|
+
sage: spher_to_cart.inverse()
|
|
3786
|
+
Change of coordinates from Chart (U, (x, y)) to Chart (U, (r, ph))
|
|
3787
|
+
sage: spher_to_cart.inverse().display()
|
|
3788
|
+
r = sqrt(x^2 + y^2)
|
|
3789
|
+
ph = arctan2(y, x)
|
|
3790
|
+
sage: M.coord_changes() # random (dictionary output)
|
|
3791
|
+
{(Chart (U, (r, ph)),
|
|
3792
|
+
Chart (U, (x, y))): Change of coordinates from Chart (U, (r, ph))
|
|
3793
|
+
to Chart (U, (x, y)),
|
|
3794
|
+
(Chart (U, (x, y)),
|
|
3795
|
+
Chart (U, (r, ph))): Change of coordinates from Chart (U, (x, y))
|
|
3796
|
+
to Chart (U, (r, ph))}
|
|
3797
|
+
|
|
3798
|
+
One can suppress the check of the provided formulas by means of the
|
|
3799
|
+
optional argument ``check=False``::
|
|
3800
|
+
|
|
3801
|
+
sage: spher_to_cart.set_inverse(sqrt(x^2+y^2), atan2(y,x),
|
|
3802
|
+
....: check=False)
|
|
3803
|
+
|
|
3804
|
+
However, it is not recommended to do so, the check being (obviously)
|
|
3805
|
+
useful to avoid some mistake. For instance, if the term
|
|
3806
|
+
``sqrt(x^2+y^2)`` contains a typo (``x^3`` instead of ``x^2``),
|
|
3807
|
+
we get::
|
|
3808
|
+
|
|
3809
|
+
sage: spher_to_cart.set_inverse(sqrt(x^3+y^2), atan2(y,x))
|
|
3810
|
+
Check of the inverse coordinate transformation:
|
|
3811
|
+
r == sqrt(r*cos(ph)^3 + sin(ph)^2)*r **failed**
|
|
3812
|
+
ph == arctan2(r*sin(ph), r*cos(ph)) **failed**
|
|
3813
|
+
x == sqrt(x^3 + y^2)*x/sqrt(x^2 + y^2) **failed**
|
|
3814
|
+
y == sqrt(x^3 + y^2)*y/sqrt(x^2 + y^2) **failed**
|
|
3815
|
+
NB: a failed report can reflect a mere lack of simplification.
|
|
3816
|
+
|
|
3817
|
+
If the check is passed, no output is printed out::
|
|
3818
|
+
|
|
3819
|
+
sage: M = Manifold(2, 'M')
|
|
3820
|
+
sage: X1.<x,y> = M.chart()
|
|
3821
|
+
sage: X2.<u,v> = M.chart()
|
|
3822
|
+
sage: X1_to_X2 = X1.transition_map(X2, [x+y, x-y])
|
|
3823
|
+
sage: X1_to_X2.set_inverse((u+v)/2, (u-v)/2)
|
|
3824
|
+
|
|
3825
|
+
unless the option ``verbose`` is set to ``True``::
|
|
3826
|
+
|
|
3827
|
+
sage: X1_to_X2.set_inverse((u+v)/2, (u-v)/2, verbose=True)
|
|
3828
|
+
Check of the inverse coordinate transformation:
|
|
3829
|
+
x == x *passed*
|
|
3830
|
+
y == y *passed*
|
|
3831
|
+
u == u *passed*
|
|
3832
|
+
v == v *passed*
|
|
3833
|
+
|
|
3834
|
+
TESTS:
|
|
3835
|
+
|
|
3836
|
+
Check that :issue:`31923` is fixed::
|
|
3837
|
+
|
|
3838
|
+
sage: X1_to_X2.inverse().inverse() is X1_to_X2
|
|
3839
|
+
True
|
|
3840
|
+
|
|
3841
|
+
Check of keyword arguments::
|
|
3842
|
+
|
|
3843
|
+
sage: X1_to_X2.set_inverse((u+v)/2, (u-v)/2, bla=3)
|
|
3844
|
+
Traceback (most recent call last):
|
|
3845
|
+
...
|
|
3846
|
+
TypeError: bla is not a valid keyword argument
|
|
3847
|
+
"""
|
|
3848
|
+
check = kwds.pop('check', True)
|
|
3849
|
+
verbose = kwds.pop('verbose', False)
|
|
3850
|
+
for unknown_key in kwds:
|
|
3851
|
+
raise TypeError("{} is not a valid keyword argument".format(unknown_key))
|
|
3852
|
+
self._inverse = type(self)(self._chart2, self._chart1, *transformations)
|
|
3853
|
+
self._inverse._inverse = self
|
|
3854
|
+
if check:
|
|
3855
|
+
infos = ["Check of the inverse coordinate transformation:"]
|
|
3856
|
+
x1 = self._chart1._xx
|
|
3857
|
+
x2 = self._chart2._xx
|
|
3858
|
+
x1_to_x1 = self._inverse(*(self(*x1)))
|
|
3859
|
+
x2_to_x2 = self(*(self._inverse(*x2)))
|
|
3860
|
+
any_failure = False # a priori
|
|
3861
|
+
for x, xc in zip(x1, x1_to_x1):
|
|
3862
|
+
eq = x == self._chart1.simplify(xc)
|
|
3863
|
+
if bool(eq):
|
|
3864
|
+
resu = '*passed*'
|
|
3865
|
+
else:
|
|
3866
|
+
resu = '**failed**'
|
|
3867
|
+
any_failure = True
|
|
3868
|
+
infos.append(" {} {}".format(eq, resu))
|
|
3869
|
+
for x, xc in zip(x2, x2_to_x2):
|
|
3870
|
+
eq = x == self._chart2.simplify(xc)
|
|
3871
|
+
if bool(eq):
|
|
3872
|
+
resu = '*passed*'
|
|
3873
|
+
else:
|
|
3874
|
+
resu = '**failed**'
|
|
3875
|
+
any_failure = True
|
|
3876
|
+
infos.append(" {} {}".format(eq, resu))
|
|
3877
|
+
if any_failure:
|
|
3878
|
+
infos.append(
|
|
3879
|
+
"NB: a failed report can reflect a mere lack of simplification."
|
|
3880
|
+
)
|
|
3881
|
+
if verbose or any_failure:
|
|
3882
|
+
for li in infos:
|
|
3883
|
+
print(li)
|
|
3884
|
+
|
|
3885
|
+
def __mul__(self, other):
|
|
3886
|
+
r"""
|
|
3887
|
+
Composition with another change of coordinates.
|
|
3888
|
+
|
|
3889
|
+
INPUT:
|
|
3890
|
+
|
|
3891
|
+
- ``other`` -- another change of coordinate, the final chart of
|
|
3892
|
+
it is the initial chart of ``self``
|
|
3893
|
+
|
|
3894
|
+
OUTPUT:
|
|
3895
|
+
|
|
3896
|
+
- the change of coordinates `X_1 \to X_3`, where `X_1` is the initial
|
|
3897
|
+
chart of ``other`` and `X_3` is the final chart of ``self``
|
|
3898
|
+
|
|
3899
|
+
EXAMPLES::
|
|
3900
|
+
|
|
3901
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
3902
|
+
sage: X.<x,y> = M.chart()
|
|
3903
|
+
sage: U.<u,v> = M.chart()
|
|
3904
|
+
sage: X_to_U = X.transition_map(U, (x+y, x-y))
|
|
3905
|
+
sage: W.<w,z> = M.chart()
|
|
3906
|
+
sage: U_to_W = U.transition_map(W, (u+cos(u)/2, v-sin(v)/2))
|
|
3907
|
+
sage: X_to_W = U_to_W * X_to_U; X_to_W
|
|
3908
|
+
Change of coordinates from Chart (M, (x, y)) to Chart (M, (w, z))
|
|
3909
|
+
sage: X_to_W.display()
|
|
3910
|
+
w = 1/2*cos(x)*cos(y) - 1/2*sin(x)*sin(y) + x + y
|
|
3911
|
+
z = -1/2*cos(y)*sin(x) + 1/2*cos(x)*sin(y) + x - y
|
|
3912
|
+
"""
|
|
3913
|
+
if not isinstance(other, CoordChange):
|
|
3914
|
+
raise TypeError("{} is not a change of coordinate".format(other))
|
|
3915
|
+
if other._chart2 != self._chart1:
|
|
3916
|
+
raise ValueError(
|
|
3917
|
+
"composition not possible: "
|
|
3918
|
+
+ "{} is different from {}".format(other._chart2, other._chart1)
|
|
3919
|
+
)
|
|
3920
|
+
transf = self._transf(*(other._transf.expr()))
|
|
3921
|
+
return type(self)(other._chart1, self._chart2, *transf)
|
|
3922
|
+
|
|
3923
|
+
def restrict(self, dom1, dom2=None):
|
|
3924
|
+
r"""
|
|
3925
|
+
Restriction to subsets.
|
|
3926
|
+
|
|
3927
|
+
INPUT:
|
|
3928
|
+
|
|
3929
|
+
- ``dom1`` -- open subset of the domain of ``chart1``
|
|
3930
|
+
- ``dom2`` -- (default: ``None``) open subset of the domain of
|
|
3931
|
+
``chart2``; if ``None``, ``dom1`` is assumed
|
|
3932
|
+
|
|
3933
|
+
OUTPUT:
|
|
3934
|
+
|
|
3935
|
+
- the transition map between the charts restricted to the
|
|
3936
|
+
specified subsets
|
|
3937
|
+
|
|
3938
|
+
EXAMPLES::
|
|
3939
|
+
|
|
3940
|
+
sage: M = Manifold(2, 'M', structure='topological')
|
|
3941
|
+
sage: X.<x,y> = M.chart()
|
|
3942
|
+
sage: Y.<u,v> = M.chart()
|
|
3943
|
+
sage: X_to_Y = X.transition_map(Y, [x+y, x-y])
|
|
3944
|
+
sage: U = M.open_subset('U', coord_def={X: x>0, Y: u+v>0})
|
|
3945
|
+
sage: X_to_Y_U = X_to_Y.restrict(U); X_to_Y_U
|
|
3946
|
+
Change of coordinates from Chart (U, (x, y)) to Chart (U, (u, v))
|
|
3947
|
+
sage: X_to_Y_U.display()
|
|
3948
|
+
u = x + y
|
|
3949
|
+
v = x - y
|
|
3950
|
+
|
|
3951
|
+
The result is cached::
|
|
3952
|
+
|
|
3953
|
+
sage: X_to_Y.restrict(U) is X_to_Y_U
|
|
3954
|
+
True
|
|
3955
|
+
"""
|
|
3956
|
+
if dom2 is None:
|
|
3957
|
+
dom2 = dom1
|
|
3958
|
+
ch1 = self._chart1.restrict(dom1)
|
|
3959
|
+
ch2 = self._chart2.restrict(dom2)
|
|
3960
|
+
if (ch1, ch2) in dom1.coord_changes():
|
|
3961
|
+
return dom1.coord_changes()[(ch1, ch2)]
|
|
3962
|
+
return type(self)(
|
|
3963
|
+
self._chart1.restrict(dom1),
|
|
3964
|
+
self._chart2.restrict(dom2),
|
|
3965
|
+
*(self._transf.expr()),
|
|
3966
|
+
)
|
|
3967
|
+
|
|
3968
|
+
def display(self):
|
|
3969
|
+
r"""
|
|
3970
|
+
Display of the coordinate transformation.
|
|
3971
|
+
|
|
3972
|
+
The output is either text-formatted (console mode) or LaTeX-formatted
|
|
3973
|
+
(notebook mode).
|
|
3974
|
+
|
|
3975
|
+
EXAMPLES:
|
|
3976
|
+
|
|
3977
|
+
From spherical coordinates to Cartesian ones in the plane::
|
|
3978
|
+
|
|
3979
|
+
sage: M = Manifold(2, 'R^2', structure='topological')
|
|
3980
|
+
sage: U = M.open_subset('U') # the complement of the half line {y=0, x>= 0}
|
|
3981
|
+
sage: c_cart.<x,y> = U.chart()
|
|
3982
|
+
sage: c_spher.<r,ph> = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi')
|
|
3983
|
+
sage: spher_to_cart = c_spher.transition_map(c_cart, [r*cos(ph), r*sin(ph)])
|
|
3984
|
+
sage: spher_to_cart.display()
|
|
3985
|
+
x = r*cos(ph)
|
|
3986
|
+
y = r*sin(ph)
|
|
3987
|
+
sage: latex(spher_to_cart.display())
|
|
3988
|
+
\left\{\begin{array}{lcl} x & = & r \cos\left({\phi}\right) \\
|
|
3989
|
+
y & = & r \sin\left({\phi}\right) \end{array}\right.
|
|
3990
|
+
|
|
3991
|
+
A shortcut is ``disp()``::
|
|
3992
|
+
|
|
3993
|
+
sage: spher_to_cart.disp()
|
|
3994
|
+
x = r*cos(ph)
|
|
3995
|
+
y = r*sin(ph)
|
|
3996
|
+
"""
|
|
3997
|
+
from sage.misc.latex import latex
|
|
3998
|
+
from sage.tensor.modules.format_utilities import FormattedExpansion
|
|
3999
|
+
|
|
4000
|
+
coords2 = self._chart2[:]
|
|
4001
|
+
n2 = len(coords2)
|
|
4002
|
+
expr = self._transf.expr('SR')
|
|
4003
|
+
rtxt = ""
|
|
4004
|
+
if n2 == 1:
|
|
4005
|
+
rlatex = r"\begin{array}{lcl}"
|
|
4006
|
+
else:
|
|
4007
|
+
rlatex = r"\left\{\begin{array}{lcl}"
|
|
4008
|
+
for i in range(n2):
|
|
4009
|
+
x2 = coords2[i]
|
|
4010
|
+
x2f = expr[i]
|
|
4011
|
+
rtxt += repr(x2) + " = " + repr(x2f) + "\n"
|
|
4012
|
+
rlatex += latex(x2) + r" & = & " + latex(x2f) + r"\\"
|
|
4013
|
+
rtxt = rtxt[:-1] # remove the last new line
|
|
4014
|
+
rlatex = rlatex[:-2] + r"\end{array}"
|
|
4015
|
+
if n2 > 1:
|
|
4016
|
+
rlatex += r"\right."
|
|
4017
|
+
return FormattedExpansion(rtxt, rlatex)
|
|
4018
|
+
|
|
4019
|
+
disp = display
|