passagemath-symbolics 10.6.37__cp310-cp310-musllinux_1_2_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_symbolics/__init__.py +3 -0
- passagemath_symbolics-10.6.37.dist-info/METADATA +187 -0
- passagemath_symbolics-10.6.37.dist-info/RECORD +171 -0
- passagemath_symbolics-10.6.37.dist-info/WHEEL +5 -0
- passagemath_symbolics-10.6.37.dist-info/top_level.txt +3 -0
- sage/all__sagemath_symbolics.py +17 -0
- sage/calculus/all.py +14 -0
- sage/calculus/calculus.py +2826 -0
- sage/calculus/desolvers.py +1866 -0
- sage/calculus/predefined.py +51 -0
- sage/calculus/tests.py +225 -0
- sage/calculus/var.cpython-310-x86_64-linux-gnu.so +0 -0
- sage/calculus/var.pyx +401 -0
- sage/dynamics/all__sagemath_symbolics.py +6 -0
- sage/dynamics/complex_dynamics/all.py +5 -0
- sage/dynamics/complex_dynamics/mandel_julia.py +765 -0
- sage/dynamics/complex_dynamics/mandel_julia_helper.cpython-310-x86_64-linux-gnu.so +0 -0
- sage/dynamics/complex_dynamics/mandel_julia_helper.pyx +1035 -0
- sage/ext/all__sagemath_symbolics.py +1 -0
- sage/ext_data/kenzo/CP2.txt +45 -0
- sage/ext_data/kenzo/CP3.txt +349 -0
- sage/ext_data/kenzo/CP4.txt +4774 -0
- sage/ext_data/kenzo/README.txt +49 -0
- sage/ext_data/kenzo/S4.txt +20 -0
- sage/ext_data/magma/latex/latex.m +1021 -0
- sage/ext_data/magma/latex/latex.spec +1 -0
- sage/ext_data/magma/sage/basic.m +356 -0
- sage/ext_data/magma/sage/sage.spec +1 -0
- sage/ext_data/magma/spec +9 -0
- sage/geometry/all__sagemath_symbolics.py +8 -0
- sage/geometry/hyperbolic_space/all.py +5 -0
- sage/geometry/hyperbolic_space/hyperbolic_coercion.py +743 -0
- sage/geometry/hyperbolic_space/hyperbolic_constants.py +5 -0
- sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +2409 -0
- sage/geometry/hyperbolic_space/hyperbolic_interface.py +206 -0
- sage/geometry/hyperbolic_space/hyperbolic_isometry.py +1082 -0
- sage/geometry/hyperbolic_space/hyperbolic_model.py +1502 -0
- sage/geometry/hyperbolic_space/hyperbolic_point.py +621 -0
- sage/geometry/riemannian_manifolds/all.py +7 -0
- sage/geometry/riemannian_manifolds/parametrized_surface3d.py +1632 -0
- sage/geometry/riemannian_manifolds/surface3d_generators.py +461 -0
- sage/interfaces/all__sagemath_symbolics.py +1 -0
- sage/interfaces/magma.py +3017 -0
- sage/interfaces/magma_free.py +92 -0
- sage/interfaces/maple.py +1397 -0
- sage/interfaces/mathematica.py +1345 -0
- sage/interfaces/mathics.py +1312 -0
- sage/interfaces/sympy.py +1398 -0
- sage/interfaces/sympy_wrapper.py +197 -0
- sage/interfaces/tides.py +938 -0
- sage/libs/all__sagemath_symbolics.py +6 -0
- sage/manifolds/all.py +7 -0
- sage/manifolds/calculus_method.py +555 -0
- sage/manifolds/catalog.py +437 -0
- sage/manifolds/chart.py +4019 -0
- sage/manifolds/chart_func.py +3419 -0
- sage/manifolds/continuous_map.py +2183 -0
- sage/manifolds/continuous_map_image.py +155 -0
- sage/manifolds/differentiable/affine_connection.py +2475 -0
- sage/manifolds/differentiable/all.py +1 -0
- sage/manifolds/differentiable/automorphismfield.py +1383 -0
- sage/manifolds/differentiable/automorphismfield_group.py +604 -0
- sage/manifolds/differentiable/bundle_connection.py +1445 -0
- sage/manifolds/differentiable/characteristic_cohomology_class.py +1840 -0
- sage/manifolds/differentiable/chart.py +1241 -0
- sage/manifolds/differentiable/curve.py +1028 -0
- sage/manifolds/differentiable/de_rham_cohomology.py +541 -0
- sage/manifolds/differentiable/degenerate.py +559 -0
- sage/manifolds/differentiable/degenerate_submanifold.py +1671 -0
- sage/manifolds/differentiable/diff_form.py +1658 -0
- sage/manifolds/differentiable/diff_form_module.py +1062 -0
- sage/manifolds/differentiable/diff_map.py +1315 -0
- sage/manifolds/differentiable/differentiable_submanifold.py +291 -0
- sage/manifolds/differentiable/examples/all.py +1 -0
- sage/manifolds/differentiable/examples/euclidean.py +2517 -0
- sage/manifolds/differentiable/examples/real_line.py +897 -0
- sage/manifolds/differentiable/examples/sphere.py +1186 -0
- sage/manifolds/differentiable/examples/symplectic_space.py +187 -0
- sage/manifolds/differentiable/examples/symplectic_space_test.py +40 -0
- sage/manifolds/differentiable/integrated_curve.py +4035 -0
- sage/manifolds/differentiable/levi_civita_connection.py +841 -0
- sage/manifolds/differentiable/manifold.py +4254 -0
- sage/manifolds/differentiable/manifold_homset.py +1826 -0
- sage/manifolds/differentiable/metric.py +3032 -0
- sage/manifolds/differentiable/mixed_form.py +1507 -0
- sage/manifolds/differentiable/mixed_form_algebra.py +559 -0
- sage/manifolds/differentiable/multivector_module.py +800 -0
- sage/manifolds/differentiable/multivectorfield.py +1520 -0
- sage/manifolds/differentiable/poisson_tensor.py +268 -0
- sage/manifolds/differentiable/pseudo_riemannian.py +755 -0
- sage/manifolds/differentiable/pseudo_riemannian_submanifold.py +1839 -0
- sage/manifolds/differentiable/scalarfield.py +1343 -0
- sage/manifolds/differentiable/scalarfield_algebra.py +472 -0
- sage/manifolds/differentiable/symplectic_form.py +910 -0
- sage/manifolds/differentiable/symplectic_form_test.py +220 -0
- sage/manifolds/differentiable/tangent_space.py +412 -0
- sage/manifolds/differentiable/tangent_vector.py +616 -0
- sage/manifolds/differentiable/tensorfield.py +4665 -0
- sage/manifolds/differentiable/tensorfield_module.py +963 -0
- sage/manifolds/differentiable/tensorfield_paral.py +2450 -0
- sage/manifolds/differentiable/tensorfield_paral_test.py +16 -0
- sage/manifolds/differentiable/vector_bundle.py +1728 -0
- sage/manifolds/differentiable/vectorfield.py +1717 -0
- sage/manifolds/differentiable/vectorfield_module.py +2445 -0
- sage/manifolds/differentiable/vectorframe.py +1832 -0
- sage/manifolds/family.py +270 -0
- sage/manifolds/local_frame.py +1490 -0
- sage/manifolds/manifold.py +3090 -0
- sage/manifolds/manifold_homset.py +452 -0
- sage/manifolds/operators.py +359 -0
- sage/manifolds/point.py +994 -0
- sage/manifolds/scalarfield.py +3718 -0
- sage/manifolds/scalarfield_algebra.py +629 -0
- sage/manifolds/section.py +3111 -0
- sage/manifolds/section_module.py +831 -0
- sage/manifolds/structure.py +229 -0
- sage/manifolds/subset.py +2764 -0
- sage/manifolds/subsets/all.py +1 -0
- sage/manifolds/subsets/closure.py +131 -0
- sage/manifolds/subsets/pullback.py +885 -0
- sage/manifolds/topological_submanifold.py +891 -0
- sage/manifolds/trivialization.py +733 -0
- sage/manifolds/utilities.py +1348 -0
- sage/manifolds/vector_bundle.py +1342 -0
- sage/manifolds/vector_bundle_fiber.py +332 -0
- sage/manifolds/vector_bundle_fiber_element.py +111 -0
- sage/matrix/all__sagemath_symbolics.py +1 -0
- sage/matrix/matrix_symbolic_dense.cpython-310-x86_64-linux-gnu.so +0 -0
- sage/matrix/matrix_symbolic_dense.pxd +6 -0
- sage/matrix/matrix_symbolic_dense.pyx +1022 -0
- sage/matrix/matrix_symbolic_sparse.cpython-310-x86_64-linux-gnu.so +0 -0
- sage/matrix/matrix_symbolic_sparse.pxd +6 -0
- sage/matrix/matrix_symbolic_sparse.pyx +1029 -0
- sage/modules/all__sagemath_symbolics.py +1 -0
- sage/modules/vector_callable_symbolic_dense.py +105 -0
- sage/modules/vector_symbolic_dense.py +116 -0
- sage/modules/vector_symbolic_sparse.py +118 -0
- sage/rings/all__sagemath_symbolics.py +4 -0
- sage/rings/asymptotic/all.py +6 -0
- sage/rings/asymptotic/asymptotic_expansion_generators.py +1485 -0
- sage/rings/asymptotic/asymptotic_ring.py +4858 -0
- sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py +4153 -0
- sage/rings/asymptotic/growth_group.py +5373 -0
- sage/rings/asymptotic/growth_group_cartesian.py +1400 -0
- sage/rings/asymptotic/term_monoid.py +5237 -0
- sage/rings/function_field/all__sagemath_symbolics.py +2 -0
- sage/rings/polynomial/all__sagemath_symbolics.py +1 -0
- sage/symbolic/all.py +15 -0
- sage/symbolic/assumptions.py +985 -0
- sage/symbolic/benchmark.py +93 -0
- sage/symbolic/callable.py +459 -0
- sage/symbolic/complexity_measures.py +35 -0
- sage/symbolic/constants.py +1287 -0
- sage/symbolic/expression_conversion_algebraic.py +310 -0
- sage/symbolic/expression_conversion_sympy.py +317 -0
- sage/symbolic/expression_conversions.py +1713 -0
- sage/symbolic/function_factory.py +355 -0
- sage/symbolic/integration/all.py +1 -0
- sage/symbolic/integration/external.py +270 -0
- sage/symbolic/integration/integral.py +1115 -0
- sage/symbolic/maxima_wrapper.py +162 -0
- sage/symbolic/operators.py +267 -0
- sage/symbolic/random_tests.py +462 -0
- sage/symbolic/relation.py +1907 -0
- sage/symbolic/ring.cpython-310-x86_64-linux-gnu.so +0 -0
- sage/symbolic/ring.pxd +5 -0
- sage/symbolic/ring.pyx +1396 -0
- sage/symbolic/subring.py +1025 -0
- sage/symbolic/symengine.py +19 -0
- sage/symbolic/tests.py +40 -0
- sage/symbolic/units.py +1470 -0
|
@@ -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")
|