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