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,1083 @@
1
+ # sage_setup: distribution = sagemath-symbolics
2
+ # sage.doctest: needs scipy
3
+ r"""
4
+ Hyperbolic Isometries
5
+
6
+ This module implements the abstract base class for isometries of
7
+ hyperbolic space of arbitrary dimension. It also contains the
8
+ implementations for specific models of hyperbolic geometry.
9
+
10
+ The isometry groups of all implemented models are either matrix Lie
11
+ groups or are doubly covered by matrix Lie groups. As such, the
12
+ isometry constructor takes a matrix as input. However, since the
13
+ isometries themselves may not be matrices, quantities like the trace
14
+ and determinant are not directly accessible from this class.
15
+
16
+ AUTHORS:
17
+
18
+ - Greg Laun (2013): initial version
19
+
20
+ EXAMPLES:
21
+
22
+ We can construct isometries in the upper half plane model, abbreviated
23
+ UHP for convenience::
24
+
25
+ sage: UHP = HyperbolicPlane().UHP()
26
+ sage: UHP.get_isometry(matrix(2,[1,2,3,4]))
27
+ Isometry in UHP
28
+ [1 2]
29
+ [3 4]
30
+ sage: A = UHP.get_isometry(matrix(2,[0,1,1,0]))
31
+ sage: A.inverse()
32
+ Isometry in UHP
33
+ [0 1]
34
+ [1 0]
35
+ """
36
+
37
+ # **********************************************************************
38
+ # Copyright (C) 2013 Greg Laun <glaun@math.umd.edu>
39
+ #
40
+ # Distributed under the terms of the GNU General Public License (GPL)
41
+ # as published by the Free Software Foundation; either version 2 of
42
+ # the License, or (at your option) any later version.
43
+ # https://www.gnu.org/licenses/
44
+ # **********************************************************************
45
+
46
+ from copy import copy
47
+ from sage.categories.homset import Hom
48
+ from sage.categories.morphism import Morphism
49
+ from sage.misc.lazy_attribute import lazy_attribute
50
+ from sage.matrix.constructor import matrix
51
+ from sage.modules.free_module_element import vector
52
+ from sage.rings.infinity import infinity
53
+ from sage.misc.latex import latex
54
+ from sage.rings.real_double import RDF
55
+ from sage.functions.other import imag
56
+ from sage.misc.functional import sqrt
57
+ from sage.functions.hyperbolic import acosh as arccosh
58
+ from sage.functions.generalized import sgn as sign
59
+
60
+ from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON
61
+ from sage.geometry.hyperbolic_space.hyperbolic_geodesic import HyperbolicGeodesic
62
+
63
+
64
+ class HyperbolicIsometry(Morphism):
65
+ r"""
66
+ Abstract base class for hyperbolic isometries. This class should
67
+ never be instantiated.
68
+
69
+ INPUT:
70
+
71
+ - ``A`` -- a matrix representing a hyperbolic isometry in the
72
+ appropriate model
73
+
74
+ EXAMPLES::
75
+
76
+ sage: HyperbolicPlane().HM().get_isometry(identity_matrix(3))
77
+ Isometry in HM
78
+ [1 0 0]
79
+ [0 1 0]
80
+ [0 0 1]
81
+ """
82
+
83
+ #####################
84
+ # "Private" Methods #
85
+ #####################
86
+
87
+ def __init__(self, model, A, check=True):
88
+ r"""
89
+ See :class:`HyperbolicIsometry` for full documentation.
90
+
91
+ EXAMPLES::
92
+
93
+ sage: A = HyperbolicPlane().UHP().get_isometry(matrix(2, [0,1,-1,0]))
94
+ sage: TestSuite(A).run(skip='_test_category')
95
+ """
96
+ if check:
97
+ model.isometry_test(A)
98
+ self._matrix = copy(A) # Make a copy of the potentially mutable matrix
99
+ self._matrix.set_immutable() # Make it immutable
100
+ Morphism.__init__(self, Hom(model, model))
101
+
102
+ @lazy_attribute
103
+ def _cached_isometry(self):
104
+ r"""
105
+ The representation of the current isometry used for
106
+ calculations. For example, if the current model uses the
107
+ upper half plane, then ``_cached_isometry`` will
108
+ hold the `SL(2,\RR)` representation of ``self.matrix()``.
109
+
110
+ EXAMPLES::
111
+
112
+ sage: A = HyperbolicPlane().HM().get_isometry(identity_matrix(3))
113
+ sage: A._cached_isometry
114
+ Isometry in UHP
115
+ [1 0]
116
+ [0 1]
117
+ """
118
+ R = self.domain().realization_of().a_realization()
119
+ return self.to_model(R)
120
+
121
+ def _repr_(self):
122
+ r"""
123
+ Return a string representation of ``self``.
124
+
125
+ OUTPUT: string
126
+
127
+ EXAMPLES::
128
+
129
+ sage: HyperbolicPlane().UHP().get_isometry(identity_matrix(2))
130
+ Isometry in UHP
131
+ [1 0]
132
+ [0 1]
133
+ """
134
+ return self._repr_type() + " in {0}\n{1}".format(self.domain().short_name(), self._matrix)
135
+
136
+ def _repr_type(self):
137
+ r"""
138
+ Return the type of morphism.
139
+
140
+ EXAMPLES::
141
+
142
+ sage: A = HyperbolicPlane().UHP().get_isometry(identity_matrix(2))
143
+ sage: A._repr_type()
144
+ 'Isometry'
145
+ """
146
+ return "Isometry"
147
+
148
+ def _latex_(self):
149
+ r"""
150
+ EXAMPLES::
151
+
152
+ sage: A = HyperbolicPlane().UHP().get_isometry(identity_matrix(2))
153
+ sage: latex(A)
154
+ \pm \left(\begin{array}{rr}
155
+ 1 & 0 \\
156
+ 0 & 1
157
+ \end{array}\right)
158
+
159
+ sage: B = HyperbolicPlane().HM().get_isometry(identity_matrix(3))
160
+ sage: latex(B)
161
+ \left(\begin{array}{rrr}
162
+ 1 & 0 & 0 \\
163
+ 0 & 1 & 0 \\
164
+ 0 & 0 & 1
165
+ \end{array}\right)
166
+ """
167
+ if self.domain().is_isometry_group_projective():
168
+ return r"\pm " + latex(self._matrix)
169
+ else:
170
+ return latex(self._matrix)
171
+
172
+ def __eq__(self, other):
173
+ r"""
174
+ Return ``True`` if the isometries are the same and ``False`` otherwise.
175
+
176
+ EXAMPLES::
177
+
178
+ sage: UHP = HyperbolicPlane().UHP()
179
+ sage: A = UHP.get_isometry(identity_matrix(2))
180
+ sage: B = UHP.get_isometry(-identity_matrix(2))
181
+ sage: A == B
182
+ True
183
+
184
+ sage: HM = HyperbolicPlane().HM()
185
+ sage: A = HM.random_isometry()
186
+ sage: A == A
187
+ True
188
+ """
189
+ if not isinstance(other, HyperbolicIsometry):
190
+ return False
191
+ test_matrix = bool((self.matrix() - other.matrix()).norm() < EPSILON)
192
+ if self.domain().is_isometry_group_projective():
193
+ A, B = self.matrix(), other.matrix() # Rename for simplicity
194
+ m = self.matrix().ncols()
195
+ A = A / sqrt(A.det(), m) # Normalized to have determinant 1
196
+ B = B / sqrt(B.det(), m)
197
+ test_matrix = ((A - B).norm() < EPSILON
198
+ or (A + B).norm() < EPSILON)
199
+ return self.domain() is other.domain() and test_matrix
200
+
201
+ def __hash__(self):
202
+ """
203
+ Return the hash of ``self``.
204
+
205
+ EXAMPLES::
206
+
207
+ sage: UHP = HyperbolicPlane().UHP()
208
+ sage: A = UHP.get_isometry(identity_matrix(2))
209
+ sage: B = UHP.get_isometry(-identity_matrix(2))
210
+ sage: hash(A) == hash(B)
211
+ True
212
+
213
+ sage: HM = HyperbolicPlane().HM()
214
+ sage: A = HM.random_isometry()
215
+ sage: hash(A) == hash(A)
216
+ True
217
+ """
218
+ if self.domain().is_isometry_group_projective():
219
+ # Special care must be taken for projective groups
220
+ m = matrix(self._matrix.nrows(),
221
+ [abs(x) for x in self._matrix.list()])
222
+ m.set_immutable()
223
+ else:
224
+ m = self._matrix
225
+ return hash((self.domain(), self.codomain(), m))
226
+
227
+ def __pow__(self, n):
228
+ r"""
229
+ EXAMPLES::
230
+
231
+ sage: A = HyperbolicPlane().UHP().get_isometry(matrix(2,[3,1,2,1]))
232
+ sage: A**3
233
+ Isometry in UHP
234
+ [41 15]
235
+ [30 11]
236
+ """
237
+ return self.__class__(self.domain(), self._matrix**n)
238
+
239
+ def __mul__(self, other):
240
+ r"""
241
+ EXAMPLES::
242
+
243
+ sage: UHP = HyperbolicPlane().UHP()
244
+ sage: A = UHP.get_isometry(Matrix(2,[5,2,1,2]))
245
+ sage: B = UHP.get_isometry(Matrix(2,[3,1,1,2]))
246
+ sage: B * A
247
+ Isometry in UHP
248
+ [16 8]
249
+ [ 7 6]
250
+ sage: A = UHP.get_isometry(Matrix(2,[5,2,1,2]))
251
+ sage: p = UHP.get_point(2 + I)
252
+ sage: A * p
253
+ Point in UHP 8/17*I + 53/17
254
+
255
+ sage: g = UHP.get_geodesic(2 + I, 4 + I)
256
+ sage: A * g
257
+ Geodesic in UHP from 8/17*I + 53/17 to 8/37*I + 137/37
258
+
259
+ sage: A = diagonal_matrix([1, -1, 1])
260
+ sage: A = HyperbolicPlane().HM().get_isometry(A)
261
+ sage: A.preserves_orientation()
262
+ False
263
+ sage: p = HyperbolicPlane().HM().get_point((0, 1, sqrt(2)))
264
+ sage: A * p
265
+ Point in HM (0, -1, sqrt(2))
266
+ """
267
+ if isinstance(other, HyperbolicIsometry):
268
+ other = other.to_model(self.codomain())
269
+ return self.__class__(self.codomain(), self._matrix*other._matrix)
270
+ from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint
271
+ if isinstance(other, HyperbolicPoint):
272
+ return self(other)
273
+ if isinstance(other, HyperbolicGeodesic):
274
+ return self.codomain().get_geodesic(self(other.start()), self(other.end()))
275
+
276
+ raise NotImplementedError("multiplication is not defined between a "
277
+ "hyperbolic isometry and {0}".format(other))
278
+
279
+ def _call_(self, p):
280
+ r"""
281
+ EXAMPLES::
282
+
283
+ sage: UHP = HyperbolicPlane().UHP()
284
+ sage: A = UHP.get_isometry(Matrix(2,[5,2,1,2]))
285
+ sage: p = UHP.get_point(2 + I)
286
+ sage: A(p)
287
+ Point in UHP 8/17*I + 53/17
288
+
289
+ sage: A = diagonal_matrix([1, -1, 1])
290
+ sage: A = HyperbolicPlane().HM().get_isometry(A)
291
+ sage: A.preserves_orientation()
292
+ False
293
+ sage: p = HyperbolicPlane().HM().get_point((0, 1, sqrt(2)))
294
+ sage: A(p)
295
+ Point in HM (0, -1, sqrt(2))
296
+
297
+ sage: I2 = UHP.get_isometry(identity_matrix(2))
298
+ sage: p = UHP.random_point()
299
+ sage: bool(UHP.dist(I2(p), p) < 10**-9)
300
+ True
301
+ """
302
+ return self.codomain().get_point(self._matrix * vector(p._coordinates))
303
+
304
+ #######################
305
+ # Setters and Getters #
306
+ #######################
307
+
308
+ def matrix(self):
309
+ r"""
310
+ Return the matrix of the isometry.
311
+
312
+ .. NOTE::
313
+
314
+ We do not allow the ``matrix`` constructor to work as these may
315
+ be elements of a projective group (ex. `PSL(n, \RR)`), so these
316
+ isometries aren't true matrices.
317
+
318
+ EXAMPLES::
319
+
320
+ sage: UHP = HyperbolicPlane().UHP()
321
+ sage: UHP.get_isometry(-identity_matrix(2)).matrix()
322
+ [-1 0]
323
+ [ 0 -1]
324
+ """
325
+ return self._matrix
326
+
327
+ def __invert__(self):
328
+ r"""
329
+ Return the inverse of the isometry ``self``.
330
+
331
+ EXAMPLES::
332
+
333
+ sage: UHP = HyperbolicPlane().UHP()
334
+ sage: A = UHP.get_isometry(matrix(2,[4,1,3,2]))
335
+ sage: B = A.inverse() # indirect doctest
336
+ sage: A*B == UHP.get_isometry(identity_matrix(2))
337
+ True
338
+ """
339
+ return self.__class__(self.domain(), self.matrix().__invert__())
340
+
341
+ def is_identity(self):
342
+ """
343
+ Return ``True`` if ``self`` is the identity isometry.
344
+
345
+ EXAMPLES::
346
+
347
+ sage: UHP = HyperbolicPlane().UHP()
348
+ sage: UHP.get_isometry(matrix(2,[4,1,3,2])).is_identity()
349
+ False
350
+ sage: UHP.get_isometry(identity_matrix(2)).is_identity()
351
+ True
352
+ """
353
+ return self._matrix.is_one()
354
+
355
+ def model(self):
356
+ r"""
357
+ Return the model to which ``self`` belongs.
358
+
359
+ EXAMPLES::
360
+
361
+ sage: HyperbolicPlane().UHP().get_isometry(identity_matrix(2)).model()
362
+ Hyperbolic plane in the Upper Half Plane Model
363
+
364
+ sage: HyperbolicPlane().PD().get_isometry(identity_matrix(2)).model()
365
+ Hyperbolic plane in the Poincare Disk Model
366
+
367
+ sage: HyperbolicPlane().KM().get_isometry(identity_matrix(3)).model()
368
+ Hyperbolic plane in the Klein Disk Model
369
+
370
+ sage: HyperbolicPlane().HM().get_isometry(identity_matrix(3)).model()
371
+ Hyperbolic plane in the Hyperboloid Model
372
+ """
373
+ return self.domain()
374
+
375
+ def to_model(self, other):
376
+ r"""
377
+ Convert the current object to image in another model.
378
+
379
+ INPUT:
380
+
381
+ - ``other`` -- (a string representing) the image model
382
+
383
+ EXAMPLES::
384
+
385
+ sage: H = HyperbolicPlane()
386
+ sage: UHP = H.UHP()
387
+ sage: PD = H.PD()
388
+ sage: KM = H.KM()
389
+ sage: HM = H.HM()
390
+
391
+ sage: A = UHP.get_isometry(identity_matrix(2))
392
+ sage: A.to_model(HM)
393
+ Isometry in HM
394
+ [1 0 0]
395
+ [0 1 0]
396
+ [0 0 1]
397
+ sage: A.to_model('HM')
398
+ Isometry in HM
399
+ [1 0 0]
400
+ [0 1 0]
401
+ [0 0 1]
402
+
403
+ sage: A = PD.get_isometry(matrix([[I, 0], [0, -I]]))
404
+ sage: A.to_model(UHP)
405
+ Isometry in UHP
406
+ [ 0 1]
407
+ [-1 0]
408
+ sage: A.to_model(HM)
409
+ Isometry in HM
410
+ [-1 0 0]
411
+ [ 0 -1 0]
412
+ [ 0 0 1]
413
+ sage: A.to_model(KM)
414
+ Isometry in KM
415
+ [-1 0 0]
416
+ [ 0 -1 0]
417
+ [ 0 0 1]
418
+
419
+ sage: A = HM.get_isometry(diagonal_matrix([-1, -1, 1]))
420
+ sage: A.to_model('UHP')
421
+ Isometry in UHP
422
+ [ 0 -1]
423
+ [ 1 0]
424
+ sage: A.to_model('PD')
425
+ Isometry in PD
426
+ [-I 0]
427
+ [ 0 I]
428
+ sage: A.to_model('KM')
429
+ Isometry in KM
430
+ [-1 0 0]
431
+ [ 0 -1 0]
432
+ [ 0 0 1]
433
+ """
434
+ if isinstance(other, str):
435
+ other = getattr(self.domain().realization_of(), other)()
436
+ if other is self.domain():
437
+ return self
438
+ phi = other.coerce_map_from(self.domain())
439
+ return phi.convert_isometry(self)
440
+
441
+ ###################
442
+ # Boolean Methods #
443
+ ###################
444
+
445
+ def preserves_orientation(self):
446
+ r"""
447
+ Return ``True`` if ``self`` is orientation-preserving and ``False``
448
+ otherwise.
449
+
450
+ EXAMPLES::
451
+
452
+ sage: UHP = HyperbolicPlane().UHP()
453
+ sage: A = UHP.get_isometry(identity_matrix(2))
454
+ sage: A.preserves_orientation()
455
+ True
456
+ sage: B = UHP.get_isometry(matrix(2,[0,1,1,0]))
457
+ sage: B.preserves_orientation()
458
+ False
459
+ """
460
+ return self._cached_isometry.preserves_orientation()
461
+
462
+ def classification(self):
463
+ r"""
464
+ Classify the hyperbolic isometry as elliptic, parabolic,
465
+ hyperbolic or a reflection.
466
+
467
+ A hyperbolic isometry fixes two points on the boundary of
468
+ hyperbolic space, a parabolic isometry fixes one point on the
469
+ boundary of hyperbolic space, and an elliptic isometry fixes no
470
+ points.
471
+
472
+ EXAMPLES::
473
+
474
+ sage: UHP = HyperbolicPlane().UHP()
475
+ sage: H = UHP.get_isometry(matrix(2,[2,0,0,1/2]))
476
+ sage: H.classification()
477
+ 'hyperbolic'
478
+
479
+ sage: P = UHP.get_isometry(matrix(2,[1,1,0,1]))
480
+ sage: P.classification()
481
+ 'parabolic'
482
+
483
+ sage: E = UHP.get_isometry(matrix(2,[-1,0,0,1]))
484
+ sage: E.classification()
485
+ 'reflection'
486
+ """
487
+ return self._cached_isometry.classification()
488
+
489
+ def translation_length(self):
490
+ r"""
491
+ For hyperbolic elements, return the translation length;
492
+ otherwise, raise a :exc:`ValueError`.
493
+
494
+ EXAMPLES::
495
+
496
+ sage: UHP = HyperbolicPlane().UHP()
497
+ sage: H = UHP.get_isometry(matrix(2,[2,0,0,1/2]))
498
+ sage: H.translation_length()
499
+ 2*arccosh(5/4)
500
+
501
+ ::
502
+
503
+ sage: f_1 = UHP.get_point(-1)
504
+ sage: f_2 = UHP.get_point(1)
505
+ sage: H = UHP.isometry_from_fixed_points(f_1, f_2)
506
+ sage: p = UHP.get_point(exp(i*7*pi/8))
507
+ sage: bool((p.dist(H*p) - H.translation_length()) < 10**-9)
508
+ True
509
+ """
510
+ return self._cached_isometry.translation_length()
511
+
512
+ def axis(self):
513
+ r"""
514
+ For a hyperbolic isometry, return the axis of the
515
+ transformation; otherwise raise a :exc:`ValueError`.
516
+
517
+ EXAMPLES::
518
+
519
+ sage: UHP = HyperbolicPlane().UHP()
520
+ sage: H = UHP.get_isometry(matrix(2,[2,0,0,1/2]))
521
+ sage: H.axis()
522
+ Geodesic in UHP from 0 to +Infinity
523
+
524
+ It is an error to call this function on an isometry that is
525
+ not hyperbolic::
526
+
527
+ sage: P = UHP.get_isometry(matrix(2,[1,4,0,1]))
528
+ sage: P.axis()
529
+ Traceback (most recent call last):
530
+ ...
531
+ ValueError: the isometry is not hyperbolic: axis is undefined
532
+ """
533
+ if self.classification() not in ['hyperbolic',
534
+ 'orientation-reversing hyperbolic']:
535
+ raise ValueError("the isometry is not hyperbolic: axis is undefined")
536
+ return self.fixed_point_set()
537
+
538
+ def fixed_point_set(self):
539
+ r"""
540
+ Return a list containing the fixed point set of
541
+ orientation-preserving isometries.
542
+
543
+ OUTPUT: list of hyperbolic points or a hyperbolic geodesic
544
+
545
+ EXAMPLES::
546
+
547
+ sage: KM = HyperbolicPlane().KM()
548
+ sage: H = KM.get_isometry(matrix([[5/3,0,4/3], [0,1,0], [4/3,0,5/3]]))
549
+ sage: g = H.fixed_point_set(); g
550
+ Geodesic in KM from (1, 0) to (-1, 0)
551
+ sage: H(g.start()) == g.start()
552
+ True
553
+ sage: H(g.end()) == g.end()
554
+ True
555
+ sage: A = KM.get_isometry(matrix([[1,0,0], [0,-1,0], [0,0,1]]))
556
+ sage: A.preserves_orientation()
557
+ False
558
+ sage: A.fixed_point_set()
559
+ Geodesic in KM from (1, 0) to (-1, 0)
560
+
561
+ ::
562
+
563
+ sage: B = KM.get_isometry(identity_matrix(3))
564
+ sage: B.fixed_point_set()
565
+ Traceback (most recent call last):
566
+ ...
567
+ ValueError: the identity transformation fixes the entire hyperbolic plane
568
+ """
569
+ M = self.domain()
570
+ pts = self._cached_isometry.fixed_point_set()
571
+ if isinstance(pts, HyperbolicGeodesic):
572
+ return pts.to_model(M)
573
+ return [M(k) for k in pts]
574
+
575
+ def fixed_geodesic(self):
576
+ r"""
577
+ If ``self`` is a reflection in a geodesic, return that geodesic.
578
+
579
+ EXAMPLES::
580
+
581
+ sage: A = HyperbolicPlane().PD().get_isometry(matrix([[0, 1], [1, 0]]))
582
+ sage: A.fixed_geodesic()
583
+ Geodesic in PD from -1 to 1
584
+ """
585
+ fps = self._cached_isometry.fixed_point_set()
586
+ if not isinstance(fps, HyperbolicGeodesic):
587
+ raise ValueError("isometries of type {0}".format(self.classification())
588
+ + " do not fix geodesics")
589
+ return fps.to_model(self.domain())
590
+
591
+ def repelling_fixed_point(self):
592
+ r"""
593
+ For a hyperbolic isometry, return the attracting fixed point;
594
+ otherwise raise a :exc:`ValueError`.
595
+
596
+ OUTPUT: a hyperbolic point
597
+
598
+ EXAMPLES::
599
+
600
+ sage: UHP = HyperbolicPlane().UHP()
601
+ sage: A = UHP.get_isometry(Matrix(2,[4,0,0,1/4]))
602
+ sage: A.repelling_fixed_point()
603
+ Boundary point in UHP 0
604
+ """
605
+ fp = self._cached_isometry.repelling_fixed_point()
606
+ return self.domain().get_point(fp)
607
+
608
+ def attracting_fixed_point(self):
609
+ r"""
610
+ For a hyperbolic isometry, return the attracting fixed point;
611
+ otherwise raise a :exc:`ValueError`.
612
+
613
+ OUTPUT: a hyperbolic point
614
+
615
+ EXAMPLES::
616
+
617
+ sage: UHP = HyperbolicPlane().UHP()
618
+ sage: A = UHP.get_isometry(Matrix(2,[4,0,0,1/4]))
619
+ sage: A.attracting_fixed_point()
620
+ Boundary point in UHP +Infinity
621
+ """
622
+ fp = self._cached_isometry.attracting_fixed_point()
623
+ return self.domain().get_point(fp)
624
+
625
+
626
+ class HyperbolicIsometryUHP(HyperbolicIsometry):
627
+ r"""
628
+ Create a hyperbolic isometry in the UHP model.
629
+
630
+ INPUT:
631
+
632
+ - a matrix in `GL(2, \RR)`
633
+
634
+ EXAMPLES::
635
+
636
+ sage: HyperbolicPlane().UHP().get_isometry(identity_matrix(2))
637
+ Isometry in UHP
638
+ [1 0]
639
+ [0 1]
640
+ """
641
+ def _call_(self, p): # UHP
642
+ r"""
643
+ Return image of ``p`` under the action of ``self``.
644
+
645
+ EXAMPLES::
646
+
647
+ sage: UHP = HyperbolicPlane().UHP()
648
+ sage: I2 = UHP.get_isometry(identity_matrix(2))
649
+ sage: p = UHP.random_point()
650
+ sage: bool(UHP.dist(I2(p), p) < 10**-9)
651
+ True
652
+ """
653
+ coords = p.coordinates()
654
+ # We apply complex conjugation to the point for negative determinants
655
+ # We check the coordinate is not equal to infinity. If we use !=, then
656
+ # it cannot determine it is not infinity, so it also returns False.
657
+ if not (coords == infinity) and bool(self._matrix.det() < 0):
658
+ coords = coords.conjugate()
659
+ return self.codomain().get_point(moebius_transform(self._matrix, coords))
660
+
661
+ def preserves_orientation(self): # UHP
662
+ r"""
663
+ Return ``True`` if ``self`` is orientation-preserving and ``False``
664
+ otherwise.
665
+
666
+ EXAMPLES::
667
+
668
+ sage: UHP = HyperbolicPlane().UHP()
669
+ sage: A = identity_matrix(2)
670
+ sage: UHP.get_isometry(A).preserves_orientation()
671
+ True
672
+ sage: B = matrix(2,[0,1,1,0])
673
+ sage: UHP.get_isometry(B).preserves_orientation()
674
+ False
675
+ """
676
+ return bool(self._matrix.det() > 0)
677
+
678
+ def classification(self): # UHP
679
+ r"""
680
+ Classify the hyperbolic isometry as elliptic, parabolic, or
681
+ hyperbolic.
682
+
683
+ A hyperbolic isometry fixes two points on the boundary of
684
+ hyperbolic space, a parabolic isometry fixes one point on the
685
+ boundary of hyperbolic space, and an elliptic isometry fixes
686
+ no points.
687
+
688
+ EXAMPLES::
689
+
690
+ sage: UHP = HyperbolicPlane().UHP()
691
+ sage: UHP.get_isometry(identity_matrix(2)).classification()
692
+ 'identity'
693
+
694
+ sage: UHP.get_isometry(4*identity_matrix(2)).classification()
695
+ 'identity'
696
+
697
+ sage: UHP.get_isometry(matrix(2,[2,0,0,1/2])).classification()
698
+ 'hyperbolic'
699
+
700
+ sage: UHP.get_isometry(matrix(2, [0, 3, -1/3, 6])).classification()
701
+ 'hyperbolic'
702
+
703
+ sage: UHP.get_isometry(matrix(2,[1,1,0,1])).classification()
704
+ 'parabolic'
705
+
706
+ sage: UHP.get_isometry(matrix(2,[-1,0,0,1])).classification()
707
+ 'reflection'
708
+ """
709
+ A = self._matrix.n()
710
+ A = A / (abs(A.det()).sqrt())
711
+ tau = abs(A.trace())
712
+ a = A.list()
713
+ if A.det() > 0:
714
+ tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < EPSILON)
715
+ if tf:
716
+ return 'identity'
717
+ if tau - 2 < -EPSILON:
718
+ return 'elliptic'
719
+ if tau - 2 > -EPSILON and tau - 2 < EPSILON:
720
+ return 'parabolic'
721
+ if tau - 2 > EPSILON:
722
+ return 'hyperbolic'
723
+ raise ValueError("something went wrong with classification:" +
724
+ " trace is {}".format(A.trace()))
725
+ # Otherwise The isometry reverses orientation
726
+ if tau < EPSILON:
727
+ return 'reflection'
728
+ return 'orientation-reversing hyperbolic'
729
+
730
+ def translation_length(self): # UHP
731
+ r"""
732
+ For hyperbolic elements, return the translation length;
733
+ otherwise, raise a :exc:`ValueError`.
734
+
735
+ EXAMPLES::
736
+
737
+ sage: UHP = HyperbolicPlane().UHP()
738
+ sage: UHP.get_isometry(matrix(2,[2,0,0,1/2])).translation_length()
739
+ 2*arccosh(5/4)
740
+
741
+ ::
742
+
743
+ sage: H = UHP.isometry_from_fixed_points(-1,1)
744
+ sage: p = UHP.get_point(exp(i*7*pi/8))
745
+ sage: Hp = H(p)
746
+ sage: bool((UHP.dist(p, Hp) - H.translation_length()) < 10**-9)
747
+ True
748
+ """
749
+ d = sqrt(self._matrix.det()**2)
750
+ tau = sqrt((self._matrix / sqrt(d)).trace()**2)
751
+ if self.classification() in ['hyperbolic', 'orientation-reversing hyperbolic']:
752
+ return 2 * arccosh(tau / 2)
753
+ raise TypeError("translation length is only defined for hyperbolic transformations")
754
+
755
+ def fixed_point_set(self): # UHP
756
+ r"""
757
+ Return a list or geodesic containing the fixed point set of
758
+ orientation-preserving isometries.
759
+
760
+ OUTPUT: list of hyperbolic points or a hyperbolic geodesic
761
+
762
+ EXAMPLES::
763
+
764
+ sage: UHP = HyperbolicPlane().UHP()
765
+ sage: H = UHP.get_isometry(matrix(2, [-2/3,-1/3,-1/3,-2/3]))
766
+ sage: g = H.fixed_point_set(); g
767
+ Geodesic in UHP from -1 to 1
768
+ sage: H(g.start()) == g.start()
769
+ True
770
+ sage: H(g.end()) == g.end()
771
+ True
772
+ sage: A = UHP.get_isometry(matrix(2,[0,1,1,0]))
773
+ sage: A.preserves_orientation()
774
+ False
775
+ sage: A.fixed_point_set()
776
+ Geodesic in UHP from 1 to -1
777
+
778
+ ::
779
+
780
+ sage: B = UHP.get_isometry(identity_matrix(2))
781
+ sage: B.fixed_point_set()
782
+ Traceback (most recent call last):
783
+ ...
784
+ ValueError: the identity transformation fixes the entire hyperbolic plane
785
+ """
786
+ d = sqrt(self._matrix.det() ** 2)
787
+ M = self._matrix / sqrt(d)
788
+ tau = M.trace() ** 2
789
+ M_cls = self.classification()
790
+ if M_cls == 'identity':
791
+ raise ValueError("the identity transformation fixes the entire "
792
+ "hyperbolic plane")
793
+
794
+ pt = self.domain().get_point
795
+ if M_cls == 'parabolic':
796
+ if abs(M[1, 0]) < EPSILON:
797
+ return [pt(infinity)]
798
+ else:
799
+ # boundary point
800
+ return [pt((M[0,0] - M[1,1]) / (2*M[1,0]))]
801
+ elif M_cls == 'elliptic':
802
+ d = sqrt(tau - 4)
803
+ return [pt((M[0,0] - M[1,1] + sign(M[1,0])*d) / (2*M[1,0]))]
804
+ elif M_cls == 'hyperbolic':
805
+ if M[1,0] != 0: # if the isometry does not fix infinity
806
+ d = sqrt(tau - 4)
807
+ p_1 = (M[0,0] - M[1,1]+d) / (2*M[1,0])
808
+ p_2 = (M[0,0] - M[1,1]-d) / (2*M[1,0])
809
+ return self.domain().get_geodesic(pt(p_1), pt(p_2))
810
+ #else, it fixes infinity.
811
+ p_1 = M[0,1] / (M[1,1] - M[0,0])
812
+ p_2 = infinity
813
+ return self.domain().get_geodesic(pt(p_1), pt(p_2))
814
+
815
+ try:
816
+ p, q = (M.eigenvectors_right()[k][1][0] for k in range(2))
817
+ except IndexError:
818
+ M = M.change_ring(RDF)
819
+ p, q = (M.eigenvectors_right()[k][1][0] for k in range(2))
820
+
821
+ pts = []
822
+ if p[1] == 0:
823
+ pts.append(infinity)
824
+ else:
825
+ p = p[0] / p[1]
826
+ if imag(p) >= 0:
827
+ pts.append(p)
828
+ if q[1] == 0:
829
+ pts.append(infinity)
830
+ else:
831
+ q = q[0] / q[1]
832
+ if imag(q) >= 0:
833
+ pts.append(q)
834
+ pts = [pt(k) for k in pts]
835
+ if len(pts) == 2:
836
+ return self.domain().get_geodesic(*pts)
837
+ return pts
838
+
839
+ def repelling_fixed_point(self): # UHP
840
+ r"""
841
+ Return the repelling fixed point.
842
+
843
+ Otherwise, this raises a :exc:`ValueError`.
844
+
845
+ OUTPUT: a hyperbolic point
846
+
847
+ EXAMPLES::
848
+
849
+ sage: UHP = HyperbolicPlane().UHP()
850
+ sage: A = matrix(2,[4,0,0,1/4])
851
+ sage: UHP.get_isometry(A).repelling_fixed_point()
852
+ Boundary point in UHP 0
853
+ """
854
+ if self.classification() not in ['hyperbolic',
855
+ 'orientation-reversing hyperbolic']:
856
+ raise ValueError("repelling fixed point is defined only" +
857
+ "for hyperbolic isometries")
858
+ v = self._matrix.eigenmatrix_right()[1].column(1)
859
+ if v[1] == 0:
860
+ return self.domain().get_point(infinity)
861
+ return self.domain().get_point(v[0] / v[1])
862
+
863
+ def attracting_fixed_point(self): # UHP
864
+ r"""
865
+ Return the attracting fixed point.
866
+
867
+ Otherwise, this raises a :exc:`ValueError`.
868
+
869
+ OUTPUT: a hyperbolic point
870
+
871
+ EXAMPLES::
872
+
873
+ sage: UHP = HyperbolicPlane().UHP()
874
+ sage: A = matrix(2,[4,0,0,1/4])
875
+ sage: UHP.get_isometry(A).attracting_fixed_point()
876
+ Boundary point in UHP +Infinity
877
+ """
878
+ if self.classification() not in \
879
+ ['hyperbolic', 'orientation-reversing hyperbolic']:
880
+ raise ValueError("Attracting fixed point is defined only" +
881
+ "for hyperbolic isometries.")
882
+ v = self._matrix.eigenmatrix_right()[1].column(0)
883
+ if v[1] == 0:
884
+ return self.domain().get_point(infinity)
885
+ return self.domain().get_point(v[0] / v[1])
886
+
887
+
888
+ class HyperbolicIsometryPD(HyperbolicIsometry):
889
+ r"""
890
+ Create a hyperbolic isometry in the PD model.
891
+
892
+ INPUT:
893
+
894
+ - a matrix in `PU(1,1)`
895
+
896
+ EXAMPLES::
897
+
898
+ sage: HyperbolicPlane().PD().get_isometry(identity_matrix(2))
899
+ Isometry in PD
900
+ [1 0]
901
+ [0 1]
902
+ """
903
+ def _call_(self, p): # PD
904
+ r"""
905
+ Return image of ``p`` under the action of ``self``.
906
+
907
+ EXAMPLES::
908
+
909
+ sage: PD = HyperbolicPlane().PD()
910
+ sage: I2 = PD.get_isometry(identity_matrix(2))
911
+ sage: q = PD.random_point()
912
+ sage: bool(PD.dist(I2(q), q) < 10**-9)
913
+ True
914
+ """
915
+ coords = p.coordinates()
916
+ # We apply complex conjugation to the point for negative determinants
917
+ if bool(self._matrix.det() < 0):
918
+ coords = coords.conjugate()
919
+ _image = moebius_transform(self._matrix, coords)
920
+ return self.codomain().get_point(_image)
921
+
922
+ def __mul__(self, other): # PD
923
+ r"""
924
+ Return image of ``p`` under the action of ``self``.
925
+
926
+ EXAMPLES::
927
+
928
+ sage: PD = HyperbolicPlane().PD()
929
+ sage: X = PD.get_isometry(matrix([[3/4, -I/4], [-I/4, -3/4]]))
930
+ sage: X*X
931
+ Isometry in PD
932
+ [ 5/8 3/8*I]
933
+ [-3/8*I 5/8]
934
+ """
935
+ if isinstance(other, HyperbolicIsometry):
936
+ M = self._cached_isometry * other._cached_isometry
937
+ return M.to_model('PD')
938
+ return super().__mul__(other)
939
+
940
+ def __pow__(self, n): # PD
941
+ r"""
942
+ EXAMPLES::
943
+
944
+ sage: PD = HyperbolicPlane().PD()
945
+ sage: X = PD.get_isometry(matrix([[3/4, -I/4], [-I/4, -3/4]]))
946
+ sage: X^2
947
+ Isometry in PD
948
+ [ 5/8 3/8*I]
949
+ [-3/8*I 5/8]
950
+ """
951
+ return (self._cached_isometry**n).to_model('PD')
952
+
953
+ def preserves_orientation(self): # PD
954
+ """
955
+ Return ``True`` if ``self`` preserves orientation and ``False``
956
+ otherwise.
957
+
958
+ EXAMPLES::
959
+
960
+ sage: PD = HyperbolicPlane().PD()
961
+ sage: PD.get_isometry(matrix([[-I, 0], [0, I]])).preserves_orientation()
962
+ True
963
+ sage: PD.get_isometry(matrix([[0, I], [I, 0]])).preserves_orientation()
964
+ False
965
+ """
966
+ return bool(self._matrix.det() > 0) and HyperbolicIsometryPD._orientation_preserving(self._matrix)
967
+
968
+ @staticmethod
969
+ def _orientation_preserving(A): # PD
970
+ r"""
971
+ For a matrix ``A`` of a PD isometry, determine if it preserves
972
+ orientation.
973
+
974
+ This test is more involved than just checking the sign of
975
+ the determinant.
976
+
977
+ EXAMPLES::
978
+
979
+ sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryPD
980
+ sage: orient = HyperbolicIsometryPD._orientation_preserving
981
+ sage: orient(matrix([[-I, 0], [0, I]]))
982
+ True
983
+ sage: orient(matrix([[0, I], [I, 0]]))
984
+ False
985
+ """
986
+ return bool(A[1][0] == A[0][1].conjugate() and A[1][1] == A[0][0].conjugate()
987
+ and abs(A[0][0]) - abs(A[0][1]) != 0)
988
+
989
+
990
+ class HyperbolicIsometryKM(HyperbolicIsometry):
991
+ r"""
992
+ Create a hyperbolic isometry in the KM model.
993
+
994
+ INPUT:
995
+
996
+ - a matrix in `SO(2,1)`
997
+
998
+ EXAMPLES::
999
+
1000
+ sage: HyperbolicPlane().KM().get_isometry(identity_matrix(3))
1001
+ Isometry in KM
1002
+ [1 0 0]
1003
+ [0 1 0]
1004
+ [0 0 1]
1005
+ """
1006
+ def _call_(self, p): # KM
1007
+ r"""
1008
+ Return image of ``p`` under the action of ``self``.
1009
+
1010
+ EXAMPLES::
1011
+
1012
+ sage: KM = HyperbolicPlane().KM()
1013
+ sage: I3 = KM.get_isometry(identity_matrix(3))
1014
+ sage: v = KM.random_point()
1015
+ sage: bool(KM.dist(I3(v), v) < 10**-9)
1016
+ True
1017
+ """
1018
+ v = self._matrix * vector(list(p.coordinates()) + [1])
1019
+ if v[2] == 0:
1020
+ return self.codomain().get_point(infinity)
1021
+ return self.codomain().get_point(v[0:2] / v[2])
1022
+
1023
+ #####################################################################
1024
+ # Helper functions
1025
+
1026
+
1027
+ def moebius_transform(A, z):
1028
+ r"""
1029
+ Given a matrix ``A`` in `GL(2, \CC)` and a point ``z`` in the complex
1030
+ plane return the Möbius transformation action of ``A`` on ``z``.
1031
+
1032
+ INPUT:
1033
+
1034
+ - ``A`` -- a `2 \times 2` invertible matrix over the complex numbers
1035
+ - ``z`` -- a complex number or infinity
1036
+
1037
+ OUTPUT: a complex number or infinity
1038
+
1039
+ EXAMPLES::
1040
+
1041
+ sage: from sage.geometry.hyperbolic_space.hyperbolic_model import moebius_transform
1042
+ sage: moebius_transform(matrix(2,[1,2,3,4]),2 + I)
1043
+ -2/109*I + 43/109
1044
+ sage: y = var('y')
1045
+ sage: moebius_transform(matrix(2,[1,0,0,1]),x + I*y)
1046
+ x + I*y
1047
+
1048
+ The matrix must be square and `2 \times 2`::
1049
+
1050
+ sage: moebius_transform(matrix([[3,1,2],[1,2,5]]),I)
1051
+ Traceback (most recent call last):
1052
+ ...
1053
+ TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring
1054
+
1055
+ sage: moebius_transform(identity_matrix(3),I)
1056
+ Traceback (most recent call last):
1057
+ ...
1058
+ TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring
1059
+
1060
+ The matrix can be symbolic or can be a matrix over the real
1061
+ or complex numbers, but must be provably invertible::
1062
+
1063
+ sage: a,b,c,d = var('a,b,c,d')
1064
+ sage: moebius_transform(matrix(2,[a,b,c,d]),I)
1065
+ (I*a + b)/(I*c + d)
1066
+ sage: moebius_transform(matrix(2,[1,b,c,b*c+1]),I)
1067
+ (b + I)/(b*c + I*c + 1)
1068
+ sage: moebius_transform(matrix(2,[0,0,0,0]),I)
1069
+ Traceback (most recent call last):
1070
+ ...
1071
+ TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring
1072
+ """
1073
+ if A.ncols() == 2 == A.nrows() and A.det() != 0:
1074
+ a, b, c, d = A.list()
1075
+ if z == infinity:
1076
+ if c == 0:
1077
+ return infinity
1078
+ return a / c
1079
+ if c * z + d == 0:
1080
+ return infinity
1081
+ return (a * z + b) / (c * z + d)
1082
+ raise TypeError("A must be an invertible 2x2 matrix over the"
1083
+ " complex numbers or a symbolic ring")