passagemath-symbolics 10.6.37__cp310-cp310-musllinux_1_2_x86_64.whl

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