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.
- passagemath_symbolics/__init__.py +3 -0
- passagemath_symbolics-10.8.1a1.dist-info/METADATA +186 -0
- passagemath_symbolics-10.8.1a1.dist-info/RECORD +181 -0
- passagemath_symbolics-10.8.1a1.dist-info/WHEEL +5 -0
- passagemath_symbolics-10.8.1a1.dist-info/top_level.txt +3 -0
- sage/all__sagemath_symbolics.py +17 -0
- sage/calculus/all.py +14 -0
- sage/calculus/calculus.py +2838 -0
- sage/calculus/desolvers.py +1864 -0
- sage/calculus/predefined.py +51 -0
- sage/calculus/tests.py +225 -0
- sage/calculus/var.cpython-314t-aarch64-linux-musl.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-314t-aarch64-linux-musl.so +0 -0
- sage/dynamics/complex_dynamics/mandel_julia_helper.pyx +1034 -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 +755 -0
- sage/geometry/hyperbolic_space/hyperbolic_constants.py +5 -0
- sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +2419 -0
- sage/geometry/hyperbolic_space/hyperbolic_interface.py +206 -0
- sage/geometry/hyperbolic_space/hyperbolic_isometry.py +1083 -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 +2991 -0
- sage/interfaces/magma_free.py +90 -0
- sage/interfaces/maple.py +1402 -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 +553 -0
- sage/manifolds/catalog.py +437 -0
- sage/manifolds/chart.py +4010 -0
- sage/manifolds/chart_func.py +3416 -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 +1668 -0
- sage/manifolds/differentiable/diff_form.py +1660 -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 +1522 -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 +912 -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 +1725 -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 +2721 -0
- sage/manifolds/subsets/all.py +1 -0
- sage/manifolds/subsets/closure.py +131 -0
- sage/manifolds/subsets/pullback.py +883 -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 +1347 -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-314t-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_symbolic_dense.pxd +6 -0
- sage/matrix/matrix_symbolic_dense.pyx +1030 -0
- sage/matrix/matrix_symbolic_sparse.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/matrix/matrix_symbolic_sparse.pxd +6 -0
- sage/matrix/matrix_symbolic_sparse.pyx +1038 -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 +4106 -0
- sage/rings/asymptotic/growth_group.py +5373 -0
- sage/rings/asymptotic/growth_group_cartesian.py +1400 -0
- sage/rings/asymptotic/term_monoid.py +5205 -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 +987 -0
- sage/symbolic/benchmark.py +93 -0
- sage/symbolic/callable.py +456 -0
- sage/symbolic/callable.pyi +66 -0
- sage/symbolic/comparison_impl.pyi +38 -0
- sage/symbolic/complexity_measures.py +35 -0
- sage/symbolic/constants.py +1286 -0
- sage/symbolic/constants_c_impl.pyi +10 -0
- sage/symbolic/expression_conversion_algebraic.py +310 -0
- sage/symbolic/expression_conversion_sympy.py +317 -0
- sage/symbolic/expression_conversions.py +1727 -0
- sage/symbolic/function_factory.py +355 -0
- sage/symbolic/function_factory.pyi +41 -0
- sage/symbolic/getitem_impl.pyi +24 -0
- sage/symbolic/integration/all.py +1 -0
- sage/symbolic/integration/external.py +271 -0
- sage/symbolic/integration/integral.py +1075 -0
- sage/symbolic/maxima_wrapper.py +162 -0
- sage/symbolic/operators.py +267 -0
- sage/symbolic/operators.pyi +61 -0
- sage/symbolic/pynac_constant_impl.pyi +13 -0
- sage/symbolic/pynac_function_impl.pyi +8 -0
- sage/symbolic/random_tests.py +461 -0
- sage/symbolic/relation.py +2062 -0
- sage/symbolic/ring.cpython-314t-aarch64-linux-musl.so +0 -0
- sage/symbolic/ring.pxd +5 -0
- sage/symbolic/ring.pyi +110 -0
- sage/symbolic/ring.pyx +1393 -0
- sage/symbolic/series_impl.pyi +10 -0
- sage/symbolic/subring.py +1025 -0
- sage/symbolic/symengine.py +19 -0
- sage/symbolic/tests.py +40 -0
- sage/symbolic/units.py +1468 -0
|
@@ -0,0 +1,1502 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-symbolics
|
|
2
|
+
r"""
|
|
3
|
+
Hyperbolic Models
|
|
4
|
+
|
|
5
|
+
In this module, a hyperbolic model is a collection of data that allow
|
|
6
|
+
the user to implement new models of hyperbolic space with minimal effort.
|
|
7
|
+
The data include facts about the underlying set (such as whether the
|
|
8
|
+
model is bounded), facts about the metric (such as whether the model is
|
|
9
|
+
conformal), facts about the isometry group (such as whether it is a
|
|
10
|
+
linear or projective group), and more. Generally speaking, any data
|
|
11
|
+
or method that pertains to the model itself -- rather than the points,
|
|
12
|
+
geodesics, or isometries of the model -- is implemented in this module.
|
|
13
|
+
|
|
14
|
+
Abstractly, a model of hyperbolic space is a connected, simply connected
|
|
15
|
+
manifold equipped with a complete Riemannian metric of constant curvature
|
|
16
|
+
`-1`. This module records information sufficient to enable computations
|
|
17
|
+
in hyperbolic space without explicitly specifying the underlying set or
|
|
18
|
+
its Riemannian metric. Although, see the
|
|
19
|
+
`SageManifolds <http://sagemanifolds.obspm.fr/>`_ project if
|
|
20
|
+
you would like to take this approach.
|
|
21
|
+
|
|
22
|
+
This module implements the abstract base class for a model of hyperbolic
|
|
23
|
+
space of arbitrary dimension. It also contains the implementations of
|
|
24
|
+
specific models of hyperbolic geometry.
|
|
25
|
+
|
|
26
|
+
AUTHORS:
|
|
27
|
+
|
|
28
|
+
- Greg Laun (2013): Initial version.
|
|
29
|
+
|
|
30
|
+
EXAMPLES:
|
|
31
|
+
|
|
32
|
+
We illustrate how the classes in this module encode data by comparing
|
|
33
|
+
the upper half plane (UHP), Poincaré disk (PD) and hyperboloid (HM)
|
|
34
|
+
models. First we create::
|
|
35
|
+
|
|
36
|
+
sage: U = HyperbolicPlane().UHP()
|
|
37
|
+
sage: P = HyperbolicPlane().PD()
|
|
38
|
+
sage: H = HyperbolicPlane().HM()
|
|
39
|
+
|
|
40
|
+
We note that the UHP and PD models are bounded while the HM model is
|
|
41
|
+
not::
|
|
42
|
+
|
|
43
|
+
sage: U.is_bounded() and P.is_bounded()
|
|
44
|
+
True
|
|
45
|
+
sage: H.is_bounded()
|
|
46
|
+
False
|
|
47
|
+
|
|
48
|
+
The isometry groups of UHP and PD are projective, while that of HM is
|
|
49
|
+
linear::
|
|
50
|
+
|
|
51
|
+
sage: U.is_isometry_group_projective()
|
|
52
|
+
True
|
|
53
|
+
sage: H.is_isometry_group_projective()
|
|
54
|
+
False
|
|
55
|
+
|
|
56
|
+
The models are responsible for determining if the coordinates of points
|
|
57
|
+
and the matrix of linear maps are appropriate for constructing points
|
|
58
|
+
and isometries in hyperbolic space::
|
|
59
|
+
|
|
60
|
+
sage: U.point_in_model(2 + I)
|
|
61
|
+
True
|
|
62
|
+
sage: U.point_in_model(2 - I)
|
|
63
|
+
False
|
|
64
|
+
sage: U.point_in_model(2)
|
|
65
|
+
False
|
|
66
|
+
sage: U.boundary_point_in_model(2)
|
|
67
|
+
True
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
# ***********************************************************************
|
|
71
|
+
#
|
|
72
|
+
# Copyright (C) 2013 Greg Laun <glaun@math.umd.edu>
|
|
73
|
+
#
|
|
74
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
|
75
|
+
# as published by the Free Software Foundation; either version 2 of
|
|
76
|
+
# the License, or (at your option) any later version.
|
|
77
|
+
# http://www.gnu.org/licenses/
|
|
78
|
+
# ***********************************************************************
|
|
79
|
+
|
|
80
|
+
from sage.structure.unique_representation import UniqueRepresentation
|
|
81
|
+
from sage.structure.parent import Parent
|
|
82
|
+
from sage.misc.bindable_class import BindableClass
|
|
83
|
+
from sage.misc.lazy_import import lazy_import
|
|
84
|
+
from sage.functions.other import imag, real
|
|
85
|
+
from sage.misc.functional import sqrt
|
|
86
|
+
from sage.functions.hyperbolic import acosh as arccosh
|
|
87
|
+
from sage.rings.cc import CC
|
|
88
|
+
from sage.rings.real_double import RDF
|
|
89
|
+
from sage.rings.real_mpfr import RR
|
|
90
|
+
from sage.rings.infinity import infinity
|
|
91
|
+
from sage.symbolic.constants import I
|
|
92
|
+
from sage.matrix.constructor import matrix
|
|
93
|
+
from sage.categories.homset import Hom
|
|
94
|
+
|
|
95
|
+
from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON, LORENTZ_GRAM
|
|
96
|
+
from sage.geometry.hyperbolic_space.hyperbolic_point import (
|
|
97
|
+
HyperbolicPoint, HyperbolicPointUHP)
|
|
98
|
+
from sage.geometry.hyperbolic_space.hyperbolic_isometry import (
|
|
99
|
+
HyperbolicIsometry, HyperbolicIsometryUHP,
|
|
100
|
+
HyperbolicIsometryPD, HyperbolicIsometryKM, moebius_transform)
|
|
101
|
+
from sage.geometry.hyperbolic_space.hyperbolic_geodesic import (
|
|
102
|
+
HyperbolicGeodesic, HyperbolicGeodesicUHP, HyperbolicGeodesicPD,
|
|
103
|
+
HyperbolicGeodesicKM, HyperbolicGeodesicHM)
|
|
104
|
+
from sage.geometry.hyperbolic_space.hyperbolic_coercion import (
|
|
105
|
+
CoercionUHPtoPD, CoercionUHPtoKM, CoercionUHPtoHM,
|
|
106
|
+
CoercionPDtoUHP, CoercionPDtoKM, CoercionPDtoHM,
|
|
107
|
+
CoercionKMtoUHP, CoercionKMtoPD, CoercionKMtoHM,
|
|
108
|
+
CoercionHMtoUHP, CoercionHMtoPD, CoercionHMtoKM)
|
|
109
|
+
|
|
110
|
+
lazy_import('sage.modules.free_module_element', 'vector')
|
|
111
|
+
|
|
112
|
+
#####################################################################
|
|
113
|
+
# Abstract model
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class HyperbolicModel(Parent, UniqueRepresentation, BindableClass):
|
|
117
|
+
r"""
|
|
118
|
+
Abstract base class for hyperbolic models.
|
|
119
|
+
"""
|
|
120
|
+
Element = HyperbolicPoint
|
|
121
|
+
_Geodesic = HyperbolicGeodesic
|
|
122
|
+
_Isometry = HyperbolicIsometry
|
|
123
|
+
|
|
124
|
+
def __init__(self, space, name, short_name, bounded, conformal,
|
|
125
|
+
dimension, isometry_group, isometry_group_is_projective):
|
|
126
|
+
"""
|
|
127
|
+
Initialize ``self``.
|
|
128
|
+
|
|
129
|
+
EXAMPLES::
|
|
130
|
+
|
|
131
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
132
|
+
sage: TestSuite(UHP).run()
|
|
133
|
+
sage: PD = HyperbolicPlane().PD()
|
|
134
|
+
sage: TestSuite(PD).run()
|
|
135
|
+
sage: KM = HyperbolicPlane().KM()
|
|
136
|
+
sage: TestSuite(KM).run()
|
|
137
|
+
sage: HM = HyperbolicPlane().HM()
|
|
138
|
+
sage: TestSuite(HM).run()
|
|
139
|
+
"""
|
|
140
|
+
self._name = name
|
|
141
|
+
self._short_name = short_name
|
|
142
|
+
self._bounded = bounded
|
|
143
|
+
self._conformal = conformal
|
|
144
|
+
self._dimension = dimension
|
|
145
|
+
self._isometry_group = isometry_group
|
|
146
|
+
self._isometry_group_is_projective = isometry_group_is_projective
|
|
147
|
+
from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicModels
|
|
148
|
+
Parent.__init__(self, category=HyperbolicModels(space))
|
|
149
|
+
|
|
150
|
+
def _repr_(self): # Abstract
|
|
151
|
+
"""
|
|
152
|
+
Return a string representation of ``self``.
|
|
153
|
+
|
|
154
|
+
EXAMPLES::
|
|
155
|
+
|
|
156
|
+
sage: HyperbolicPlane().UHP()
|
|
157
|
+
Hyperbolic plane in the Upper Half Plane Model
|
|
158
|
+
"""
|
|
159
|
+
return 'Hyperbolic plane in the {}'.format(self._name)
|
|
160
|
+
|
|
161
|
+
def _element_constructor_(self, x, is_boundary=None, **graphics_options): # Abstract
|
|
162
|
+
"""
|
|
163
|
+
Construct an element of ``self``.
|
|
164
|
+
|
|
165
|
+
EXAMPLES::
|
|
166
|
+
|
|
167
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
168
|
+
sage: UHP(2 + I)
|
|
169
|
+
Point in UHP I + 2
|
|
170
|
+
"""
|
|
171
|
+
return self.get_point(x, is_boundary, **graphics_options)
|
|
172
|
+
|
|
173
|
+
def name(self) -> str: # Abstract
|
|
174
|
+
"""
|
|
175
|
+
Return the name of this model.
|
|
176
|
+
|
|
177
|
+
EXAMPLES::
|
|
178
|
+
|
|
179
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
180
|
+
sage: UHP.name()
|
|
181
|
+
'Upper Half Plane Model'
|
|
182
|
+
"""
|
|
183
|
+
return self._name
|
|
184
|
+
|
|
185
|
+
def short_name(self) -> str:
|
|
186
|
+
"""
|
|
187
|
+
Return the short name of this model.
|
|
188
|
+
|
|
189
|
+
EXAMPLES::
|
|
190
|
+
|
|
191
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
192
|
+
sage: UHP.short_name()
|
|
193
|
+
'UHP'
|
|
194
|
+
"""
|
|
195
|
+
return self._short_name
|
|
196
|
+
|
|
197
|
+
def is_bounded(self) -> bool:
|
|
198
|
+
"""
|
|
199
|
+
Return ``True`` if ``self`` is a bounded model.
|
|
200
|
+
|
|
201
|
+
EXAMPLES::
|
|
202
|
+
|
|
203
|
+
sage: HyperbolicPlane().UHP().is_bounded()
|
|
204
|
+
True
|
|
205
|
+
sage: HyperbolicPlane().PD().is_bounded()
|
|
206
|
+
True
|
|
207
|
+
sage: HyperbolicPlane().KM().is_bounded()
|
|
208
|
+
True
|
|
209
|
+
sage: HyperbolicPlane().HM().is_bounded()
|
|
210
|
+
False
|
|
211
|
+
"""
|
|
212
|
+
return self._bounded
|
|
213
|
+
|
|
214
|
+
def is_conformal(self) -> bool:
|
|
215
|
+
"""
|
|
216
|
+
Return ``True`` if ``self`` is a conformal model.
|
|
217
|
+
|
|
218
|
+
EXAMPLES::
|
|
219
|
+
|
|
220
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
221
|
+
sage: UHP.is_conformal()
|
|
222
|
+
True
|
|
223
|
+
"""
|
|
224
|
+
return self._conformal
|
|
225
|
+
|
|
226
|
+
def is_isometry_group_projective(self) -> bool:
|
|
227
|
+
"""
|
|
228
|
+
Return ``True`` if the isometry group of ``self`` is projective.
|
|
229
|
+
|
|
230
|
+
EXAMPLES::
|
|
231
|
+
|
|
232
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
233
|
+
sage: UHP.is_isometry_group_projective()
|
|
234
|
+
True
|
|
235
|
+
"""
|
|
236
|
+
return self._isometry_group_is_projective
|
|
237
|
+
|
|
238
|
+
def point_in_model(self, p):
|
|
239
|
+
r"""
|
|
240
|
+
Return ``True`` if the point ``p`` is in the interior of the
|
|
241
|
+
given model and ``False`` otherwise.
|
|
242
|
+
|
|
243
|
+
INPUT:
|
|
244
|
+
|
|
245
|
+
- any object that can converted into a complex number
|
|
246
|
+
|
|
247
|
+
OUTPUT: boolean
|
|
248
|
+
|
|
249
|
+
EXAMPLES::
|
|
250
|
+
|
|
251
|
+
sage: HyperbolicPlane().UHP().point_in_model(I)
|
|
252
|
+
True
|
|
253
|
+
sage: HyperbolicPlane().UHP().point_in_model(-I)
|
|
254
|
+
False
|
|
255
|
+
"""
|
|
256
|
+
return True
|
|
257
|
+
|
|
258
|
+
def point_test(self, p): # Abstract
|
|
259
|
+
r"""
|
|
260
|
+
Test whether a point is in the model. If the point is in the
|
|
261
|
+
model, do nothing. Otherwise, raise a :exc:`ValueError`.
|
|
262
|
+
|
|
263
|
+
EXAMPLES::
|
|
264
|
+
|
|
265
|
+
sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP
|
|
266
|
+
sage: HyperbolicPlane().UHP().point_test(2 + I)
|
|
267
|
+
sage: HyperbolicPlane().UHP().point_test(2 - I)
|
|
268
|
+
Traceback (most recent call last):
|
|
269
|
+
...
|
|
270
|
+
ValueError: -I + 2 is not a valid point in the UHP model
|
|
271
|
+
"""
|
|
272
|
+
if not (self.point_in_model(p) or self.boundary_point_in_model(p)):
|
|
273
|
+
error_string = "{0} is not a valid point in the {1} model"
|
|
274
|
+
raise ValueError(error_string.format(p, self._short_name))
|
|
275
|
+
|
|
276
|
+
def boundary_point_in_model(self, p): # Abstract
|
|
277
|
+
r"""
|
|
278
|
+
Return ``True`` if the point is on the ideal boundary of hyperbolic
|
|
279
|
+
space and ``False`` otherwise.
|
|
280
|
+
|
|
281
|
+
INPUT:
|
|
282
|
+
|
|
283
|
+
- any object that can converted into a complex number
|
|
284
|
+
|
|
285
|
+
OUTPUT: boolean
|
|
286
|
+
|
|
287
|
+
EXAMPLES::
|
|
288
|
+
|
|
289
|
+
sage: HyperbolicPlane().UHP().boundary_point_in_model(I)
|
|
290
|
+
False
|
|
291
|
+
"""
|
|
292
|
+
return True
|
|
293
|
+
|
|
294
|
+
def bdry_point_test(self, p): # Abstract
|
|
295
|
+
r"""
|
|
296
|
+
Test whether a point is in the model. If the point is in the
|
|
297
|
+
model, do nothing; otherwise raise a :exc:`ValueError`.
|
|
298
|
+
|
|
299
|
+
EXAMPLES::
|
|
300
|
+
|
|
301
|
+
sage: HyperbolicPlane().UHP().bdry_point_test(2)
|
|
302
|
+
sage: HyperbolicPlane().UHP().bdry_point_test(1 + I)
|
|
303
|
+
Traceback (most recent call last):
|
|
304
|
+
...
|
|
305
|
+
ValueError: I + 1 is not a valid boundary point in the UHP model
|
|
306
|
+
"""
|
|
307
|
+
if not self._bounded or not self.boundary_point_in_model(p):
|
|
308
|
+
error_string = "{0} is not a valid boundary point in the {1} model"
|
|
309
|
+
raise ValueError(error_string.format(p, self._short_name))
|
|
310
|
+
|
|
311
|
+
def isometry_in_model(self, A): # Abstract
|
|
312
|
+
r"""
|
|
313
|
+
Return ``True`` if the input matrix represents an isometry of the
|
|
314
|
+
given model and ``False`` otherwise.
|
|
315
|
+
|
|
316
|
+
INPUT:
|
|
317
|
+
|
|
318
|
+
- ``A`` -- a matrix that represents an isometry in the appropriate model
|
|
319
|
+
|
|
320
|
+
OUTPUT: boolean
|
|
321
|
+
|
|
322
|
+
EXAMPLES::
|
|
323
|
+
|
|
324
|
+
sage: HyperbolicPlane().UHP().isometry_in_model(identity_matrix(2))
|
|
325
|
+
True
|
|
326
|
+
|
|
327
|
+
sage: HyperbolicPlane().UHP().isometry_in_model(identity_matrix(3))
|
|
328
|
+
False
|
|
329
|
+
"""
|
|
330
|
+
return True
|
|
331
|
+
|
|
332
|
+
def isometry_test(self, A): # Abstract
|
|
333
|
+
r"""
|
|
334
|
+
Test whether an isometry ``A`` is in the model.
|
|
335
|
+
|
|
336
|
+
If the isometry is in the model, do nothing. Otherwise, raise
|
|
337
|
+
a :exc:`ValueError`.
|
|
338
|
+
|
|
339
|
+
EXAMPLES::
|
|
340
|
+
|
|
341
|
+
sage: HyperbolicPlane().UHP().isometry_test(identity_matrix(2))
|
|
342
|
+
sage: HyperbolicPlane().UHP().isometry_test(matrix(2, [I,1,2,1]))
|
|
343
|
+
Traceback (most recent call last):
|
|
344
|
+
...
|
|
345
|
+
ValueError:
|
|
346
|
+
[I 1]
|
|
347
|
+
[2 1] is not a valid isometry in the UHP model
|
|
348
|
+
"""
|
|
349
|
+
if not self.isometry_in_model(A):
|
|
350
|
+
error_string = "\n{0} is not a valid isometry in the {1} model"
|
|
351
|
+
raise ValueError(error_string.format(A, self._short_name))
|
|
352
|
+
|
|
353
|
+
def get_point(self, coordinates, is_boundary=None, **graphics_options):
|
|
354
|
+
r"""
|
|
355
|
+
Return a point in ``self``.
|
|
356
|
+
|
|
357
|
+
Automatically determine the type of point to return given either:
|
|
358
|
+
|
|
359
|
+
#. the coordinates of a point in the interior or ideal boundary
|
|
360
|
+
of hyperbolic space, or
|
|
361
|
+
#. a :class:`~sage.geometry.hyperbolic_space.hyperbolic_point.HyperbolicPoint` object.
|
|
362
|
+
|
|
363
|
+
INPUT:
|
|
364
|
+
|
|
365
|
+
- a point in hyperbolic space or on the ideal boundary
|
|
366
|
+
|
|
367
|
+
OUTPUT: a :class:`~sage.geometry.hyperbolic_space.hyperbolic_point.HyperbolicPoint`
|
|
368
|
+
|
|
369
|
+
EXAMPLES:
|
|
370
|
+
|
|
371
|
+
We can create an interior point via the coordinates::
|
|
372
|
+
|
|
373
|
+
sage: HyperbolicPlane().UHP().get_point(2*I)
|
|
374
|
+
Point in UHP 2*I
|
|
375
|
+
|
|
376
|
+
Or we can create a boundary point via the coordinates::
|
|
377
|
+
|
|
378
|
+
sage: HyperbolicPlane().UHP().get_point(23)
|
|
379
|
+
Boundary point in UHP 23
|
|
380
|
+
|
|
381
|
+
However we cannot create points outside of our model::
|
|
382
|
+
|
|
383
|
+
sage: HyperbolicPlane().UHP().get_point(12 - I)
|
|
384
|
+
Traceback (most recent call last):
|
|
385
|
+
...
|
|
386
|
+
ValueError: -I + 12 is not a valid point in the UHP model
|
|
387
|
+
|
|
388
|
+
::
|
|
389
|
+
|
|
390
|
+
sage: HyperbolicPlane().UHP().get_point(2 + 3*I)
|
|
391
|
+
Point in UHP 3*I + 2
|
|
392
|
+
|
|
393
|
+
sage: HyperbolicPlane().PD().get_point(0)
|
|
394
|
+
Point in PD 0
|
|
395
|
+
|
|
396
|
+
sage: HyperbolicPlane().KM().get_point((0,0))
|
|
397
|
+
Point in KM (0, 0)
|
|
398
|
+
|
|
399
|
+
sage: HyperbolicPlane().HM().get_point((0,0,1))
|
|
400
|
+
Point in HM (0, 0, 1)
|
|
401
|
+
|
|
402
|
+
sage: p = HyperbolicPlane().UHP().get_point(I, color='red')
|
|
403
|
+
sage: p.graphics_options()
|
|
404
|
+
{'color': 'red'}
|
|
405
|
+
|
|
406
|
+
::
|
|
407
|
+
|
|
408
|
+
sage: HyperbolicPlane().UHP().get_point(12)
|
|
409
|
+
Boundary point in UHP 12
|
|
410
|
+
|
|
411
|
+
sage: HyperbolicPlane().UHP().get_point(infinity)
|
|
412
|
+
Boundary point in UHP +Infinity
|
|
413
|
+
|
|
414
|
+
sage: HyperbolicPlane().PD().get_point(I)
|
|
415
|
+
Boundary point in PD I
|
|
416
|
+
|
|
417
|
+
sage: HyperbolicPlane().KM().get_point((0,-1))
|
|
418
|
+
Boundary point in KM (0, -1)
|
|
419
|
+
"""
|
|
420
|
+
|
|
421
|
+
if isinstance(coordinates, HyperbolicPoint):
|
|
422
|
+
if coordinates.parent() is not self:
|
|
423
|
+
coordinates = self(coordinates)
|
|
424
|
+
coordinates.update_graphics(True, **graphics_options)
|
|
425
|
+
return coordinates # both Point and BdryPoint
|
|
426
|
+
|
|
427
|
+
if is_boundary is None:
|
|
428
|
+
is_boundary = self.boundary_point_in_model(coordinates)
|
|
429
|
+
return self.element_class(self, coordinates, is_boundary, **graphics_options)
|
|
430
|
+
|
|
431
|
+
def get_geodesic(self, start, end=None, **graphics_options): # Abstract
|
|
432
|
+
r"""
|
|
433
|
+
Return a geodesic in the appropriate model.
|
|
434
|
+
|
|
435
|
+
EXAMPLES::
|
|
436
|
+
|
|
437
|
+
sage: HyperbolicPlane().UHP().get_geodesic(I, 2*I)
|
|
438
|
+
Geodesic in UHP from I to 2*I
|
|
439
|
+
|
|
440
|
+
sage: HyperbolicPlane().PD().get_geodesic(0, I/2)
|
|
441
|
+
Geodesic in PD from 0 to 1/2*I
|
|
442
|
+
|
|
443
|
+
sage: HyperbolicPlane().KM().get_geodesic((1/2, 1/2), (0,0))
|
|
444
|
+
Geodesic in KM from (1/2, 1/2) to (0, 0)
|
|
445
|
+
|
|
446
|
+
sage: HyperbolicPlane().HM().get_geodesic((0,0,1), (1,0, sqrt(2)))
|
|
447
|
+
Geodesic in HM from (0, 0, 1) to (1, 0, sqrt(2))
|
|
448
|
+
|
|
449
|
+
TESTS::
|
|
450
|
+
|
|
451
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
452
|
+
sage: g = UHP.get_geodesic(UHP.get_point(I), UHP.get_point(2 + I))
|
|
453
|
+
sage: h = UHP.get_geodesic(I, 2 + I)
|
|
454
|
+
sage: g == h
|
|
455
|
+
True
|
|
456
|
+
"""
|
|
457
|
+
if end is None:
|
|
458
|
+
if isinstance(start, HyperbolicGeodesic):
|
|
459
|
+
G = start
|
|
460
|
+
if G.model() is not self:
|
|
461
|
+
G = G.to_model(self)
|
|
462
|
+
G.update_graphics(True, **graphics_options)
|
|
463
|
+
return G
|
|
464
|
+
raise ValueError("the start and end points must be specified")
|
|
465
|
+
return self._Geodesic(self, self(start), self(end), **graphics_options)
|
|
466
|
+
|
|
467
|
+
def get_isometry(self, A):
|
|
468
|
+
r"""
|
|
469
|
+
Return an isometry in ``self`` from the matrix ``A`` in the
|
|
470
|
+
isometry group of ``self``.
|
|
471
|
+
|
|
472
|
+
EXAMPLES::
|
|
473
|
+
|
|
474
|
+
sage: HyperbolicPlane().UHP().get_isometry(identity_matrix(2))
|
|
475
|
+
Isometry in UHP
|
|
476
|
+
[1 0]
|
|
477
|
+
[0 1]
|
|
478
|
+
|
|
479
|
+
sage: HyperbolicPlane().PD().get_isometry(identity_matrix(2))
|
|
480
|
+
Isometry in PD
|
|
481
|
+
[1 0]
|
|
482
|
+
[0 1]
|
|
483
|
+
|
|
484
|
+
sage: HyperbolicPlane().KM().get_isometry(identity_matrix(3)) # needs scipy
|
|
485
|
+
Isometry in KM
|
|
486
|
+
[1 0 0]
|
|
487
|
+
[0 1 0]
|
|
488
|
+
[0 0 1]
|
|
489
|
+
|
|
490
|
+
sage: HyperbolicPlane().HM().get_isometry(identity_matrix(3)) # needs scipy
|
|
491
|
+
Isometry in HM
|
|
492
|
+
[1 0 0]
|
|
493
|
+
[0 1 0]
|
|
494
|
+
[0 0 1]
|
|
495
|
+
"""
|
|
496
|
+
if isinstance(A, HyperbolicIsometry):
|
|
497
|
+
if A.model() is not self:
|
|
498
|
+
return A.to_model(self)
|
|
499
|
+
return A
|
|
500
|
+
return self._Isometry(self, A)
|
|
501
|
+
|
|
502
|
+
def random_element(self, **kwargs):
|
|
503
|
+
r"""
|
|
504
|
+
Return a random point in ``self``.
|
|
505
|
+
|
|
506
|
+
The points are uniformly distributed over the rectangle
|
|
507
|
+
`[-10, 10] \times [0, 10 i]` in the upper half plane model.
|
|
508
|
+
|
|
509
|
+
EXAMPLES::
|
|
510
|
+
|
|
511
|
+
sage: p = HyperbolicPlane().UHP().random_element()
|
|
512
|
+
sage: bool((p.coordinates().imag()) > 0)
|
|
513
|
+
True
|
|
514
|
+
|
|
515
|
+
sage: p = HyperbolicPlane().PD().random_element()
|
|
516
|
+
sage: HyperbolicPlane().PD().point_in_model(p.coordinates())
|
|
517
|
+
True
|
|
518
|
+
|
|
519
|
+
sage: p = HyperbolicPlane().KM().random_element()
|
|
520
|
+
sage: HyperbolicPlane().KM().point_in_model(p.coordinates())
|
|
521
|
+
True
|
|
522
|
+
|
|
523
|
+
sage: p = HyperbolicPlane().HM().random_element().coordinates()
|
|
524
|
+
sage: bool((p[0]**2 + p[1]**2 - p[2]**2 - 1) < 10**-8)
|
|
525
|
+
True
|
|
526
|
+
"""
|
|
527
|
+
return self.random_point(**kwargs)
|
|
528
|
+
|
|
529
|
+
def random_point(self, **kwargs):
|
|
530
|
+
r"""
|
|
531
|
+
Return a random point of ``self``.
|
|
532
|
+
|
|
533
|
+
The points are uniformly distributed over the rectangle
|
|
534
|
+
`[-10, 10] \times [0, 10 i]` in the upper half plane model.
|
|
535
|
+
|
|
536
|
+
EXAMPLES::
|
|
537
|
+
|
|
538
|
+
sage: p = HyperbolicPlane().UHP().random_point()
|
|
539
|
+
sage: bool((p.coordinates().imag()) > 0)
|
|
540
|
+
True
|
|
541
|
+
|
|
542
|
+
sage: PD = HyperbolicPlane().PD()
|
|
543
|
+
sage: p = PD.random_point()
|
|
544
|
+
sage: PD.point_in_model(p.coordinates())
|
|
545
|
+
True
|
|
546
|
+
"""
|
|
547
|
+
R = self.realization_of().a_realization()
|
|
548
|
+
return self(R.random_point(**kwargs))
|
|
549
|
+
|
|
550
|
+
def random_geodesic(self, **kwargs):
|
|
551
|
+
r"""
|
|
552
|
+
Return a random hyperbolic geodesic.
|
|
553
|
+
|
|
554
|
+
Return the geodesic between two random points.
|
|
555
|
+
|
|
556
|
+
EXAMPLES::
|
|
557
|
+
|
|
558
|
+
sage: h = HyperbolicPlane().PD().random_geodesic()
|
|
559
|
+
sage: all( e.coordinates().abs() <= 1 for e in h.endpoints() )
|
|
560
|
+
True
|
|
561
|
+
"""
|
|
562
|
+
R = self.realization_of().a_realization()
|
|
563
|
+
g_ends = [R.random_point(**kwargs) for k in range(2)]
|
|
564
|
+
return self.get_geodesic(self(g_ends[0]), self(g_ends[1]))
|
|
565
|
+
|
|
566
|
+
def random_isometry(self, preserve_orientation=True, **kwargs):
|
|
567
|
+
r"""
|
|
568
|
+
Return a random isometry in the model of ``self``.
|
|
569
|
+
|
|
570
|
+
INPUT:
|
|
571
|
+
|
|
572
|
+
- ``preserve_orientation`` -- if ``True`` return an
|
|
573
|
+
orientation-preserving isometry
|
|
574
|
+
|
|
575
|
+
OUTPUT: a hyperbolic isometry
|
|
576
|
+
|
|
577
|
+
EXAMPLES::
|
|
578
|
+
|
|
579
|
+
sage: # needs scipy
|
|
580
|
+
sage: A = HyperbolicPlane().PD().random_isometry()
|
|
581
|
+
sage: A.preserves_orientation()
|
|
582
|
+
True
|
|
583
|
+
sage: B = HyperbolicPlane().PD().random_isometry(preserve_orientation=False)
|
|
584
|
+
sage: B.preserves_orientation()
|
|
585
|
+
False
|
|
586
|
+
"""
|
|
587
|
+
R = self.realization_of().a_realization()
|
|
588
|
+
A = R.random_isometry(preserve_orientation, **kwargs)
|
|
589
|
+
return A.to_model(self)
|
|
590
|
+
|
|
591
|
+
################
|
|
592
|
+
# Dist methods #
|
|
593
|
+
################
|
|
594
|
+
|
|
595
|
+
def dist(self, a, b):
|
|
596
|
+
r"""
|
|
597
|
+
Calculate the hyperbolic distance between ``a`` and ``b``.
|
|
598
|
+
|
|
599
|
+
INPUT:
|
|
600
|
+
|
|
601
|
+
- ``a``, ``b`` -- a point or geodesic
|
|
602
|
+
|
|
603
|
+
OUTPUT: the hyperbolic distance
|
|
604
|
+
|
|
605
|
+
EXAMPLES::
|
|
606
|
+
|
|
607
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
608
|
+
sage: p1 = UHP.get_point(5 + 7*I)
|
|
609
|
+
sage: p2 = UHP.get_point(1.0 + I)
|
|
610
|
+
sage: UHP.dist(p1, p2)
|
|
611
|
+
2.23230104635820
|
|
612
|
+
|
|
613
|
+
sage: PD = HyperbolicPlane().PD()
|
|
614
|
+
sage: p1 = PD.get_point(0)
|
|
615
|
+
sage: p2 = PD.get_point(I/2)
|
|
616
|
+
sage: PD.dist(p1, p2)
|
|
617
|
+
arccosh(5/3)
|
|
618
|
+
|
|
619
|
+
sage: UHP(p1).dist(UHP(p2))
|
|
620
|
+
arccosh(5/3)
|
|
621
|
+
|
|
622
|
+
sage: KM = HyperbolicPlane().KM()
|
|
623
|
+
sage: p1 = KM.get_point((0, 0))
|
|
624
|
+
sage: p2 = KM.get_point((1/2, 1/2))
|
|
625
|
+
sage: numerical_approx(KM.dist(p1, p2))
|
|
626
|
+
0.881373587019543
|
|
627
|
+
|
|
628
|
+
sage: HM = HyperbolicPlane().HM()
|
|
629
|
+
sage: p1 = HM.get_point((0,0,1))
|
|
630
|
+
sage: p2 = HM.get_point((1,0,sqrt(2)))
|
|
631
|
+
sage: numerical_approx(HM.dist(p1, p2))
|
|
632
|
+
0.881373587019543
|
|
633
|
+
|
|
634
|
+
Distance between a point and itself is 0::
|
|
635
|
+
|
|
636
|
+
sage: p = UHP.get_point(47 + I)
|
|
637
|
+
sage: UHP.dist(p, p)
|
|
638
|
+
0
|
|
639
|
+
|
|
640
|
+
Points on the boundary are infinitely far from interior points::
|
|
641
|
+
|
|
642
|
+
sage: UHP.get_point(3).dist(UHP.get_point(I))
|
|
643
|
+
+Infinity
|
|
644
|
+
|
|
645
|
+
TESTS::
|
|
646
|
+
|
|
647
|
+
sage: UHP.dist(UHP.get_point(I), UHP.get_point(2*I))
|
|
648
|
+
arccosh(5/4)
|
|
649
|
+
sage: UHP.dist(I, 2*I)
|
|
650
|
+
arccosh(5/4)
|
|
651
|
+
"""
|
|
652
|
+
def coords(x):
|
|
653
|
+
return self(x).coordinates()
|
|
654
|
+
|
|
655
|
+
if isinstance(a, HyperbolicGeodesic):
|
|
656
|
+
if isinstance(b, HyperbolicGeodesic):
|
|
657
|
+
if not a.is_parallel(b):
|
|
658
|
+
return 0
|
|
659
|
+
|
|
660
|
+
if a.is_ultra_parallel(b):
|
|
661
|
+
perp = a.common_perpendicular(b)
|
|
662
|
+
# Find where a and b intersect the common perp...
|
|
663
|
+
p = a.intersection(perp)[0]
|
|
664
|
+
q = b.intersection(perp)[0]
|
|
665
|
+
# ...and return their distance
|
|
666
|
+
return self._dist_points(coords(p), coords(q))
|
|
667
|
+
|
|
668
|
+
raise NotImplementedError("can only compute distance between"
|
|
669
|
+
" ultra-parallel and intersecting geodesics")
|
|
670
|
+
|
|
671
|
+
# If only one is a geodesic, make sure it's b to make things easier
|
|
672
|
+
a, b = b, a
|
|
673
|
+
|
|
674
|
+
if isinstance(b, HyperbolicGeodesic):
|
|
675
|
+
(p, q) = b.ideal_endpoints()
|
|
676
|
+
return self._dist_geod_point(coords(p), coords(q), coords(a))
|
|
677
|
+
|
|
678
|
+
return self._dist_points(coords(a), coords(b))
|
|
679
|
+
|
|
680
|
+
def _dist_points(self, p1, p2):
|
|
681
|
+
r"""
|
|
682
|
+
Compute the distance between two points.
|
|
683
|
+
|
|
684
|
+
INPUT:
|
|
685
|
+
|
|
686
|
+
- ``p1``, ``p2`` -- the coordinates of the points
|
|
687
|
+
|
|
688
|
+
EXAMPLES::
|
|
689
|
+
|
|
690
|
+
sage: HyperbolicPlane().PD()._dist_points(3/5*I, 0)
|
|
691
|
+
arccosh(17/8)
|
|
692
|
+
"""
|
|
693
|
+
R = self.realization_of().a_realization()
|
|
694
|
+
phi = R.coerce_map_from(self)
|
|
695
|
+
return R._dist_points(phi.image_coordinates(p1), phi.image_coordinates(p2))
|
|
696
|
+
|
|
697
|
+
def _dist_geod_point(self, start, end, p):
|
|
698
|
+
r"""
|
|
699
|
+
Return the hyperbolic distance from a given hyperbolic geodesic
|
|
700
|
+
and a hyperbolic point.
|
|
701
|
+
|
|
702
|
+
INPUT:
|
|
703
|
+
|
|
704
|
+
- ``start`` -- the start ideal point coordinates of the geodesic
|
|
705
|
+
- ``end`` -- the end ideal point coordinates of the geodesic
|
|
706
|
+
- ``p`` -- the coordinates of the point
|
|
707
|
+
|
|
708
|
+
OUTPUT: the hyperbolic distance
|
|
709
|
+
|
|
710
|
+
EXAMPLES::
|
|
711
|
+
|
|
712
|
+
sage: HyperbolicPlane().PD()._dist_geod_point(3/5*I + 4/5, I, 0)
|
|
713
|
+
arccosh(1/10*sqrt(5)*((sqrt(5) - 1)^2 + 4) + 1)
|
|
714
|
+
|
|
715
|
+
If `p` is a boundary point, the distance is infinity::
|
|
716
|
+
|
|
717
|
+
sage: HyperbolicPlane().PD()._dist_geod_point(3/5*I + 4/5, I, 12/13*I + 5/13)
|
|
718
|
+
+Infinity
|
|
719
|
+
"""
|
|
720
|
+
R = self.realization_of().a_realization()
|
|
721
|
+
assert R is not self
|
|
722
|
+
|
|
723
|
+
def phi(c):
|
|
724
|
+
return R.coerce_map_from(self).image_coordinates(c)
|
|
725
|
+
return R._dist_geod_point(phi(start), phi(end), phi(p))
|
|
726
|
+
|
|
727
|
+
####################
|
|
728
|
+
# Isometry methods #
|
|
729
|
+
####################
|
|
730
|
+
|
|
731
|
+
def isometry_from_fixed_points(self, repel, attract):
|
|
732
|
+
r"""
|
|
733
|
+
Given two fixed points ``repel`` and ``attract`` as hyperbolic
|
|
734
|
+
points return a hyperbolic isometry with ``repel`` as repelling
|
|
735
|
+
fixed point and ``attract`` as attracting fixed point.
|
|
736
|
+
|
|
737
|
+
EXAMPLES::
|
|
738
|
+
|
|
739
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
740
|
+
sage: PD = HyperbolicPlane().PD()
|
|
741
|
+
sage: PD.isometry_from_fixed_points(-i, i)
|
|
742
|
+
Isometry in PD
|
|
743
|
+
[ 3/4 1/4*I]
|
|
744
|
+
[-1/4*I 3/4]
|
|
745
|
+
|
|
746
|
+
::
|
|
747
|
+
|
|
748
|
+
sage: p, q = PD.get_point(1/2 + I/2), PD.get_point(6/13 + 9/13*I)
|
|
749
|
+
sage: PD.isometry_from_fixed_points(p, q)
|
|
750
|
+
Traceback (most recent call last):
|
|
751
|
+
...
|
|
752
|
+
ValueError: fixed points of hyperbolic elements must be ideal
|
|
753
|
+
|
|
754
|
+
sage: p, q = PD.get_point(4/5 + 3/5*I), PD.get_point(-I)
|
|
755
|
+
sage: PD.isometry_from_fixed_points(p, q)
|
|
756
|
+
Isometry in PD
|
|
757
|
+
[ 1/6*I - 2/3 -1/3*I - 1/6]
|
|
758
|
+
[ 1/3*I - 1/6 -1/6*I - 2/3]
|
|
759
|
+
"""
|
|
760
|
+
R = self.realization_of().a_realization()
|
|
761
|
+
return R.isometry_from_fixed_points(R(self(repel)), R(self(attract))).to_model(self)
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
#####################################################################
|
|
765
|
+
# Upper half plane model
|
|
766
|
+
|
|
767
|
+
class HyperbolicModelUHP(HyperbolicModel):
|
|
768
|
+
r"""
|
|
769
|
+
Upper Half Plane model.
|
|
770
|
+
"""
|
|
771
|
+
Element = HyperbolicPointUHP
|
|
772
|
+
_Geodesic = HyperbolicGeodesicUHP
|
|
773
|
+
_Isometry = HyperbolicIsometryUHP
|
|
774
|
+
|
|
775
|
+
def __init__(self, space):
|
|
776
|
+
"""
|
|
777
|
+
Initialize ``self``.
|
|
778
|
+
|
|
779
|
+
EXAMPLES::
|
|
780
|
+
|
|
781
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
782
|
+
sage: TestSuite(UHP).run()
|
|
783
|
+
"""
|
|
784
|
+
HyperbolicModel.__init__(self, space,
|
|
785
|
+
name="Upper Half Plane Model", short_name='UHP',
|
|
786
|
+
bounded=True, conformal=True, dimension=2,
|
|
787
|
+
isometry_group="PSL(2, \\RR)", isometry_group_is_projective=True)
|
|
788
|
+
|
|
789
|
+
def _coerce_map_from_(self, X):
|
|
790
|
+
"""
|
|
791
|
+
Return if there is a coercion map from ``X`` to ``self``.
|
|
792
|
+
|
|
793
|
+
EXAMPLES::
|
|
794
|
+
|
|
795
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
796
|
+
sage: UHP.has_coerce_map_from(HyperbolicPlane().PD())
|
|
797
|
+
True
|
|
798
|
+
sage: UHP.has_coerce_map_from(HyperbolicPlane().KM())
|
|
799
|
+
True
|
|
800
|
+
sage: UHP.has_coerce_map_from(HyperbolicPlane().HM())
|
|
801
|
+
True
|
|
802
|
+
sage: UHP.has_coerce_map_from(QQ)
|
|
803
|
+
False
|
|
804
|
+
"""
|
|
805
|
+
if isinstance(X, HyperbolicModelPD):
|
|
806
|
+
return CoercionPDtoUHP(Hom(X, self))
|
|
807
|
+
if isinstance(X, HyperbolicModelKM):
|
|
808
|
+
return CoercionKMtoUHP(Hom(X, self))
|
|
809
|
+
if isinstance(X, HyperbolicModelHM):
|
|
810
|
+
return CoercionHMtoUHP(Hom(X, self))
|
|
811
|
+
return super()._coerce_map_from_(X)
|
|
812
|
+
|
|
813
|
+
def point_in_model(self, p):
|
|
814
|
+
r"""
|
|
815
|
+
Check whether a complex number lies in the open upper half plane.
|
|
816
|
+
|
|
817
|
+
EXAMPLES::
|
|
818
|
+
|
|
819
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
820
|
+
sage: UHP.point_in_model(1 + I)
|
|
821
|
+
True
|
|
822
|
+
sage: UHP.point_in_model(infinity)
|
|
823
|
+
False
|
|
824
|
+
sage: UHP.point_in_model(CC(infinity))
|
|
825
|
+
False
|
|
826
|
+
sage: UHP.point_in_model(RR(infinity))
|
|
827
|
+
False
|
|
828
|
+
sage: UHP.point_in_model(1)
|
|
829
|
+
False
|
|
830
|
+
sage: UHP.point_in_model(12)
|
|
831
|
+
False
|
|
832
|
+
sage: UHP.point_in_model(1 - I)
|
|
833
|
+
False
|
|
834
|
+
sage: UHP.point_in_model(-2*I)
|
|
835
|
+
False
|
|
836
|
+
sage: UHP.point_in_model(I)
|
|
837
|
+
True
|
|
838
|
+
sage: UHP.point_in_model(0) # Not interior point
|
|
839
|
+
False
|
|
840
|
+
"""
|
|
841
|
+
if isinstance(p, HyperbolicPoint):
|
|
842
|
+
return p.is_boundary()
|
|
843
|
+
if p in CC:
|
|
844
|
+
p = CC(p)
|
|
845
|
+
else:
|
|
846
|
+
p = CC(*p)
|
|
847
|
+
return bool(imag(p) > 0)
|
|
848
|
+
|
|
849
|
+
def boundary_point_in_model(self, p):
|
|
850
|
+
r"""
|
|
851
|
+
Check whether a complex number is a real number or ``\infty``.
|
|
852
|
+
In the ``UHP.model_name_name``, this is the ideal boundary of
|
|
853
|
+
hyperbolic space.
|
|
854
|
+
|
|
855
|
+
EXAMPLES::
|
|
856
|
+
|
|
857
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
858
|
+
sage: UHP.boundary_point_in_model(1 + I)
|
|
859
|
+
False
|
|
860
|
+
sage: UHP.boundary_point_in_model(infinity)
|
|
861
|
+
True
|
|
862
|
+
sage: UHP.boundary_point_in_model(CC(infinity))
|
|
863
|
+
True
|
|
864
|
+
sage: UHP.boundary_point_in_model(RR(infinity))
|
|
865
|
+
True
|
|
866
|
+
sage: UHP.boundary_point_in_model(1)
|
|
867
|
+
True
|
|
868
|
+
sage: UHP.boundary_point_in_model(12)
|
|
869
|
+
True
|
|
870
|
+
sage: UHP.boundary_point_in_model(1 - I)
|
|
871
|
+
False
|
|
872
|
+
sage: UHP.boundary_point_in_model(-2*I)
|
|
873
|
+
False
|
|
874
|
+
sage: UHP.boundary_point_in_model(0)
|
|
875
|
+
True
|
|
876
|
+
sage: UHP.boundary_point_in_model(I)
|
|
877
|
+
False
|
|
878
|
+
"""
|
|
879
|
+
if isinstance(p, HyperbolicPoint):
|
|
880
|
+
return p.is_boundary()
|
|
881
|
+
if p in CC:
|
|
882
|
+
p = CC(p)
|
|
883
|
+
else:
|
|
884
|
+
p = CC(*p)
|
|
885
|
+
im = abs(imag(p).n())
|
|
886
|
+
return (im < EPSILON) or bool(p == infinity)
|
|
887
|
+
|
|
888
|
+
def isometry_in_model(self, A):
|
|
889
|
+
r"""
|
|
890
|
+
Check that ``A`` acts as an isometry on the upper half plane.
|
|
891
|
+
That is, ``A`` must be an invertible `2 \times 2` matrix with real
|
|
892
|
+
entries.
|
|
893
|
+
|
|
894
|
+
EXAMPLES::
|
|
895
|
+
|
|
896
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
897
|
+
sage: A = matrix(2,[1,2,3,4])
|
|
898
|
+
sage: UHP.isometry_in_model(A)
|
|
899
|
+
True
|
|
900
|
+
sage: B = matrix(2,[I,2,4,1])
|
|
901
|
+
sage: UHP.isometry_in_model(B)
|
|
902
|
+
False
|
|
903
|
+
|
|
904
|
+
An example of a matrix `A` such that `\det(A) \neq 1`, but the `A`
|
|
905
|
+
acts isometrically::
|
|
906
|
+
|
|
907
|
+
sage: C = matrix(2,[10,0,0,10])
|
|
908
|
+
sage: UHP.isometry_in_model(C)
|
|
909
|
+
True
|
|
910
|
+
"""
|
|
911
|
+
if isinstance(A, HyperbolicIsometry):
|
|
912
|
+
return True
|
|
913
|
+
return bool(A.ncols() == 2 and A.nrows() == 2 and
|
|
914
|
+
sum([k in RR for k in A.list()]) == 4 and
|
|
915
|
+
abs(A.det()) > -EPSILON)
|
|
916
|
+
|
|
917
|
+
def get_background_graphic(self, **bdry_options):
|
|
918
|
+
r"""
|
|
919
|
+
Return a graphic object that makes the model easier to visualize.
|
|
920
|
+
For the upper half space, the background object is the ideal boundary.
|
|
921
|
+
|
|
922
|
+
EXAMPLES::
|
|
923
|
+
|
|
924
|
+
sage: hp = HyperbolicPlane().UHP().get_background_graphic() # needs sage.plot
|
|
925
|
+
"""
|
|
926
|
+
from sage.plot.line import line
|
|
927
|
+
bd_min = bdry_options.get('bd_min', -5)
|
|
928
|
+
bd_max = bdry_options.get('bd_max', 5)
|
|
929
|
+
return line(((bd_min, 0), (bd_max, 0)), color='black')
|
|
930
|
+
|
|
931
|
+
################
|
|
932
|
+
# Dist methods #
|
|
933
|
+
################
|
|
934
|
+
|
|
935
|
+
def _dist_points(self, p1, p2):
|
|
936
|
+
r"""
|
|
937
|
+
Compute the distance between two points in the Upper Half Plane
|
|
938
|
+
using the hyperbolic metric.
|
|
939
|
+
|
|
940
|
+
INPUT:
|
|
941
|
+
|
|
942
|
+
- ``p1``, ``p2`` -- the coordinates of the points
|
|
943
|
+
|
|
944
|
+
EXAMPLES::
|
|
945
|
+
|
|
946
|
+
sage: HyperbolicPlane().UHP()._dist_points(4.0*I, I)
|
|
947
|
+
1.38629436111989
|
|
948
|
+
"""
|
|
949
|
+
num = (real(p2) - real(p1))**2 + (imag(p2) - imag(p1))**2
|
|
950
|
+
denom = 2 * imag(p1) * imag(p2)
|
|
951
|
+
if denom == 0:
|
|
952
|
+
return infinity
|
|
953
|
+
return arccosh(1 + num/denom)
|
|
954
|
+
|
|
955
|
+
def _dist_geod_point(self, start, end, p):
|
|
956
|
+
r"""
|
|
957
|
+
Return the hyperbolic distance from a given hyperbolic geodesic
|
|
958
|
+
and a hyperbolic point.
|
|
959
|
+
|
|
960
|
+
INPUT:
|
|
961
|
+
|
|
962
|
+
- ``start`` -- the start ideal point coordinates of the geodesic
|
|
963
|
+
- ``end`` -- the end ideal point coordinates of the geodesic
|
|
964
|
+
- ``p`` -- the coordinates of the point
|
|
965
|
+
|
|
966
|
+
OUTPUT: the hyperbolic distance
|
|
967
|
+
|
|
968
|
+
EXAMPLES::
|
|
969
|
+
|
|
970
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
971
|
+
sage: UHP._dist_geod_point(2, infinity, I)
|
|
972
|
+
arccosh(1/10*sqrt(5)*((sqrt(5) - 1)^2 + 4) + 1)
|
|
973
|
+
|
|
974
|
+
If `p` is a boundary point, the distance is infinity::
|
|
975
|
+
|
|
976
|
+
sage: HyperbolicPlane().UHP()._dist_geod_point(2, infinity, 5)
|
|
977
|
+
+Infinity
|
|
978
|
+
"""
|
|
979
|
+
# Here is the trick for computing distance to a geodesic:
|
|
980
|
+
# find an isometry mapping the geodesic to the geodesic between
|
|
981
|
+
# 0 and infinity (so corresponding to the line imag(z) = 0.
|
|
982
|
+
# then any complex number is r exp(i*theta) in polar coordinates.
|
|
983
|
+
# the mutual perpendicular between this point and imag(z) = 0
|
|
984
|
+
# intersects imag(z) = 0 at ri. So we calculate the distance
|
|
985
|
+
# between r exp(i*theta) and ri after we transform the original
|
|
986
|
+
# point.
|
|
987
|
+
if start + end != infinity:
|
|
988
|
+
# Not a straight line:
|
|
989
|
+
# Map the endpoints to 0 and infinity and the midpoint to 1.
|
|
990
|
+
T = HyperbolicGeodesicUHP._crossratio_matrix(start, (start + end)/2, end)
|
|
991
|
+
else:
|
|
992
|
+
# Is a straight line:
|
|
993
|
+
# Map the endpoints to 0 and infinity and another endpoint to 1.
|
|
994
|
+
T = HyperbolicGeodesicUHP._crossratio_matrix(start, start + 1, end)
|
|
995
|
+
x = moebius_transform(T, p)
|
|
996
|
+
return self._dist_points(x, abs(x)*I)
|
|
997
|
+
|
|
998
|
+
#################
|
|
999
|
+
# Point Methods #
|
|
1000
|
+
#################
|
|
1001
|
+
|
|
1002
|
+
def random_point(self, **kwargs):
|
|
1003
|
+
r"""
|
|
1004
|
+
Return a random point in the upper half plane. The points are
|
|
1005
|
+
uniformly distributed over the rectangle `[-10, 10] \times [0, 10i]`.
|
|
1006
|
+
|
|
1007
|
+
EXAMPLES::
|
|
1008
|
+
|
|
1009
|
+
sage: p = HyperbolicPlane().UHP().random_point().coordinates()
|
|
1010
|
+
sage: bool((p.imag()) > 0)
|
|
1011
|
+
True
|
|
1012
|
+
"""
|
|
1013
|
+
# TODO: use **kwargs to allow these to be set
|
|
1014
|
+
real_min = -10
|
|
1015
|
+
real_max = 10
|
|
1016
|
+
imag_min = 0
|
|
1017
|
+
imag_max = 10
|
|
1018
|
+
p = RR.random_element(min=real_min, max=real_max) \
|
|
1019
|
+
+ I * RR.random_element(min=imag_min, max=imag_max)
|
|
1020
|
+
return self.get_point(p)
|
|
1021
|
+
|
|
1022
|
+
####################
|
|
1023
|
+
# Isometry Methods #
|
|
1024
|
+
####################
|
|
1025
|
+
|
|
1026
|
+
def isometry_from_fixed_points(self, repel, attract):
|
|
1027
|
+
r"""
|
|
1028
|
+
Given two fixed points ``repel`` and ``attract`` as complex
|
|
1029
|
+
numbers return a hyperbolic isometry with ``repel`` as repelling
|
|
1030
|
+
fixed point and ``attract`` as attracting fixed point.
|
|
1031
|
+
|
|
1032
|
+
EXAMPLES::
|
|
1033
|
+
|
|
1034
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1035
|
+
sage: UHP.isometry_from_fixed_points(2 + I, 3 + I)
|
|
1036
|
+
Traceback (most recent call last):
|
|
1037
|
+
...
|
|
1038
|
+
ValueError: fixed points of hyperbolic elements must be ideal
|
|
1039
|
+
|
|
1040
|
+
sage: UHP.isometry_from_fixed_points(2, 0)
|
|
1041
|
+
Isometry in UHP
|
|
1042
|
+
[ -1 0]
|
|
1043
|
+
[-1/3 -1/3]
|
|
1044
|
+
|
|
1045
|
+
TESTS::
|
|
1046
|
+
|
|
1047
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1048
|
+
sage: UHP.isometry_from_fixed_points(0, 4)
|
|
1049
|
+
Isometry in UHP
|
|
1050
|
+
[ -1 0]
|
|
1051
|
+
[-1/5 -1/5]
|
|
1052
|
+
sage: UHP.isometry_from_fixed_points(UHP.get_point(0), UHP.get_point(4))
|
|
1053
|
+
Isometry in UHP
|
|
1054
|
+
[ -1 0]
|
|
1055
|
+
[-1/5 -1/5]
|
|
1056
|
+
"""
|
|
1057
|
+
if isinstance(repel, HyperbolicPoint):
|
|
1058
|
+
repel = repel._coordinates
|
|
1059
|
+
if isinstance(attract, HyperbolicPoint):
|
|
1060
|
+
attract = attract._coordinates
|
|
1061
|
+
|
|
1062
|
+
if imag(repel) + imag(attract) > EPSILON:
|
|
1063
|
+
raise ValueError("fixed points of hyperbolic elements must be ideal")
|
|
1064
|
+
repel = real(repel)
|
|
1065
|
+
attract = real(attract)
|
|
1066
|
+
if repel == infinity:
|
|
1067
|
+
A = self._moebius_sending([infinity, attract, attract + 1],
|
|
1068
|
+
[infinity, attract, attract + 2])
|
|
1069
|
+
elif attract == infinity:
|
|
1070
|
+
A = self._moebius_sending([repel, infinity, repel + 1],
|
|
1071
|
+
[repel, infinity, repel + 2])
|
|
1072
|
+
else:
|
|
1073
|
+
A = self._moebius_sending([repel, attract, infinity],
|
|
1074
|
+
[repel, attract, max(repel, attract) + 1])
|
|
1075
|
+
return self.get_isometry(A)
|
|
1076
|
+
|
|
1077
|
+
def random_isometry(self, preserve_orientation=True, **kwargs):
|
|
1078
|
+
r"""
|
|
1079
|
+
Return a random isometry in the Upper Half Plane model.
|
|
1080
|
+
|
|
1081
|
+
INPUT:
|
|
1082
|
+
|
|
1083
|
+
- ``preserve_orientation`` -- if ``True`` return an
|
|
1084
|
+
orientation-preserving isometry
|
|
1085
|
+
|
|
1086
|
+
OUTPUT: a hyperbolic isometry
|
|
1087
|
+
|
|
1088
|
+
EXAMPLES::
|
|
1089
|
+
|
|
1090
|
+
sage: A = HyperbolicPlane().UHP().random_isometry() # needs scipy
|
|
1091
|
+
sage: B = HyperbolicPlane().UHP().random_isometry(preserve_orientation=False) # needs scipy
|
|
1092
|
+
sage: B.preserves_orientation() # needs scipy
|
|
1093
|
+
False
|
|
1094
|
+
"""
|
|
1095
|
+
a, b, c, d = (RR.random_element() for k in range(4))
|
|
1096
|
+
while abs(a * d - b * c) < EPSILON:
|
|
1097
|
+
a, b, c, d = (RR.random_element() for k in range(4))
|
|
1098
|
+
M = matrix(RDF, 2, [a, b, c, d])
|
|
1099
|
+
M = M / (M.det()).abs().sqrt()
|
|
1100
|
+
if M.det() > 0:
|
|
1101
|
+
if not preserve_orientation:
|
|
1102
|
+
M = M * matrix(2, [0, 1, 1, 0])
|
|
1103
|
+
elif preserve_orientation:
|
|
1104
|
+
M = M * matrix(2, [0, 1, 1, 0])
|
|
1105
|
+
return self._Isometry(self, M, check=False)
|
|
1106
|
+
|
|
1107
|
+
###################
|
|
1108
|
+
# Helping Methods #
|
|
1109
|
+
###################
|
|
1110
|
+
|
|
1111
|
+
@staticmethod
|
|
1112
|
+
def _moebius_sending(z, w): # UHP
|
|
1113
|
+
r"""
|
|
1114
|
+
Given two lists ``z`` and ``w`` of three points each in
|
|
1115
|
+
`\mathbb{CP}^1`, return the linear fractional transformation
|
|
1116
|
+
taking the points in ``z`` to the points in ``w``.
|
|
1117
|
+
|
|
1118
|
+
EXAMPLES::
|
|
1119
|
+
|
|
1120
|
+
sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP
|
|
1121
|
+
sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import moebius_transform
|
|
1122
|
+
sage: bool(abs(moebius_transform(HyperbolicModelUHP._moebius_sending([1,2,infinity],[3 - I, 5*I,-12]),1) - 3 + I) < 10^-4)
|
|
1123
|
+
True
|
|
1124
|
+
sage: bool(abs(moebius_transform(HyperbolicModelUHP._moebius_sending([1,2,infinity],[3 - I, 5*I,-12]),2) - 5*I) < 10^-4)
|
|
1125
|
+
True
|
|
1126
|
+
sage: bool(abs(moebius_transform(HyperbolicModelUHP._moebius_sending([1,2,infinity],[3 - I, 5*I,-12]),infinity) + 12) < 10^-4)
|
|
1127
|
+
True
|
|
1128
|
+
"""
|
|
1129
|
+
if len(z) != 3 or len(w) != 3:
|
|
1130
|
+
raise TypeError("moebius_sending requires each list to be three points long")
|
|
1131
|
+
A = HyperbolicGeodesicUHP._crossratio_matrix(z[0], z[1], z[2])
|
|
1132
|
+
B = HyperbolicGeodesicUHP._crossratio_matrix(w[0], w[1], w[2])
|
|
1133
|
+
return B.inverse() * A
|
|
1134
|
+
|
|
1135
|
+
#####################################################################
|
|
1136
|
+
# Poincaré disk model
|
|
1137
|
+
|
|
1138
|
+
|
|
1139
|
+
class HyperbolicModelPD(HyperbolicModel):
|
|
1140
|
+
r"""
|
|
1141
|
+
Poincaré Disk Model.
|
|
1142
|
+
"""
|
|
1143
|
+
_Geodesic = HyperbolicGeodesicPD
|
|
1144
|
+
_Isometry = HyperbolicIsometryPD
|
|
1145
|
+
|
|
1146
|
+
def __init__(self, space):
|
|
1147
|
+
"""
|
|
1148
|
+
Initialize ``self``.
|
|
1149
|
+
|
|
1150
|
+
EXAMPLES::
|
|
1151
|
+
|
|
1152
|
+
sage: PD = HyperbolicPlane().PD()
|
|
1153
|
+
sage: TestSuite(PD).run()
|
|
1154
|
+
"""
|
|
1155
|
+
# name should really be 'Poincaré Disk Model', but utf8 is not
|
|
1156
|
+
# accepted by repr
|
|
1157
|
+
HyperbolicModel.__init__(self, space,
|
|
1158
|
+
name='Poincare Disk Model', short_name='PD',
|
|
1159
|
+
bounded=True, conformal=True, dimension=2,
|
|
1160
|
+
isometry_group="PU(1, 1)",
|
|
1161
|
+
isometry_group_is_projective=True)
|
|
1162
|
+
|
|
1163
|
+
def _coerce_map_from_(self, X):
|
|
1164
|
+
"""
|
|
1165
|
+
Return if there is a coercion map from ``X`` to ``self``.
|
|
1166
|
+
|
|
1167
|
+
EXAMPLES::
|
|
1168
|
+
|
|
1169
|
+
sage: PD = HyperbolicPlane().PD()
|
|
1170
|
+
sage: PD.has_coerce_map_from(HyperbolicPlane().UHP())
|
|
1171
|
+
True
|
|
1172
|
+
sage: PD.has_coerce_map_from(HyperbolicPlane().KM())
|
|
1173
|
+
True
|
|
1174
|
+
sage: PD.has_coerce_map_from(HyperbolicPlane().HM())
|
|
1175
|
+
True
|
|
1176
|
+
sage: PD.has_coerce_map_from(QQ)
|
|
1177
|
+
False
|
|
1178
|
+
"""
|
|
1179
|
+
if isinstance(X, HyperbolicModelUHP):
|
|
1180
|
+
return CoercionUHPtoPD(Hom(X, self))
|
|
1181
|
+
if isinstance(X, HyperbolicModelKM):
|
|
1182
|
+
return CoercionKMtoPD(Hom(X, self))
|
|
1183
|
+
if isinstance(X, HyperbolicModelHM):
|
|
1184
|
+
return CoercionHMtoPD(Hom(X, self))
|
|
1185
|
+
return super()._coerce_map_from_(X)
|
|
1186
|
+
|
|
1187
|
+
def point_in_model(self, p):
|
|
1188
|
+
r"""
|
|
1189
|
+
Check whether a complex number lies in the open unit disk.
|
|
1190
|
+
|
|
1191
|
+
EXAMPLES::
|
|
1192
|
+
|
|
1193
|
+
sage: PD = HyperbolicPlane().PD()
|
|
1194
|
+
sage: PD.point_in_model(1.00)
|
|
1195
|
+
False
|
|
1196
|
+
sage: PD.point_in_model(1/2 + I/2)
|
|
1197
|
+
True
|
|
1198
|
+
sage: PD.point_in_model(1 + .2*I)
|
|
1199
|
+
False
|
|
1200
|
+
"""
|
|
1201
|
+
if isinstance(p, HyperbolicPoint):
|
|
1202
|
+
return p.is_boundary()
|
|
1203
|
+
if p in CC:
|
|
1204
|
+
p = CC(p)
|
|
1205
|
+
else:
|
|
1206
|
+
p = CC(*p)
|
|
1207
|
+
return bool(abs(p) < 1)
|
|
1208
|
+
|
|
1209
|
+
def boundary_point_in_model(self, p):
|
|
1210
|
+
r"""
|
|
1211
|
+
Check whether a complex number lies in the open unit disk.
|
|
1212
|
+
|
|
1213
|
+
EXAMPLES::
|
|
1214
|
+
|
|
1215
|
+
sage: PD = HyperbolicPlane().PD()
|
|
1216
|
+
sage: PD.boundary_point_in_model(1.00)
|
|
1217
|
+
True
|
|
1218
|
+
sage: PD.boundary_point_in_model(1/2 + I/2)
|
|
1219
|
+
False
|
|
1220
|
+
sage: PD.boundary_point_in_model(1 + .2*I)
|
|
1221
|
+
False
|
|
1222
|
+
"""
|
|
1223
|
+
if isinstance(p, HyperbolicPoint):
|
|
1224
|
+
return p.is_boundary()
|
|
1225
|
+
if p in CC:
|
|
1226
|
+
p = CC(p)
|
|
1227
|
+
else:
|
|
1228
|
+
p = CC(*p)
|
|
1229
|
+
return bool(abs(abs(p) - 1) < EPSILON)
|
|
1230
|
+
|
|
1231
|
+
def isometry_in_model(self, A):
|
|
1232
|
+
r"""
|
|
1233
|
+
Check if the given matrix ``A`` is in the group `U(1,1)`.
|
|
1234
|
+
|
|
1235
|
+
EXAMPLES::
|
|
1236
|
+
|
|
1237
|
+
sage: z = [CC.random_element() for k in range(2)]; z.sort(key=abs)
|
|
1238
|
+
sage: A = matrix(2,[z[1], z[0],z[0].conjugate(),z[1].conjugate()])
|
|
1239
|
+
sage: HyperbolicPlane().PD().isometry_in_model(A)
|
|
1240
|
+
True
|
|
1241
|
+
"""
|
|
1242
|
+
if isinstance(A, HyperbolicIsometry):
|
|
1243
|
+
return True
|
|
1244
|
+
# alpha = A[0][0]
|
|
1245
|
+
# beta = A[0][1]
|
|
1246
|
+
# Orientation preserving and reversing
|
|
1247
|
+
return (HyperbolicIsometryPD._orientation_preserving(A) or
|
|
1248
|
+
HyperbolicIsometryPD._orientation_preserving(I * A))
|
|
1249
|
+
|
|
1250
|
+
def get_background_graphic(self, **bdry_options):
|
|
1251
|
+
r"""
|
|
1252
|
+
Return a graphic object that makes the model easier to visualize.
|
|
1253
|
+
|
|
1254
|
+
For the Poincaré disk, the background object is the ideal boundary.
|
|
1255
|
+
|
|
1256
|
+
EXAMPLES::
|
|
1257
|
+
|
|
1258
|
+
sage: circ = HyperbolicPlane().PD().get_background_graphic() # needs sage.plot
|
|
1259
|
+
"""
|
|
1260
|
+
from sage.plot.circle import circle
|
|
1261
|
+
return circle((0, 0), 1, axes=False, color='black')
|
|
1262
|
+
|
|
1263
|
+
|
|
1264
|
+
#####################################################################
|
|
1265
|
+
# Klein disk model
|
|
1266
|
+
|
|
1267
|
+
class HyperbolicModelKM(HyperbolicModel):
|
|
1268
|
+
r"""
|
|
1269
|
+
Klein Model.
|
|
1270
|
+
"""
|
|
1271
|
+
_Geodesic = HyperbolicGeodesicKM
|
|
1272
|
+
_Isometry = HyperbolicIsometryKM
|
|
1273
|
+
|
|
1274
|
+
def __init__(self, space):
|
|
1275
|
+
"""
|
|
1276
|
+
Initialize ``self``.
|
|
1277
|
+
|
|
1278
|
+
EXAMPLES::
|
|
1279
|
+
|
|
1280
|
+
sage: KM = HyperbolicPlane().KM()
|
|
1281
|
+
sage: TestSuite(KM).run()
|
|
1282
|
+
"""
|
|
1283
|
+
HyperbolicModel.__init__(self, space,
|
|
1284
|
+
name="Klein Disk Model", short_name='KM',
|
|
1285
|
+
bounded=True, conformal=False, dimension=2,
|
|
1286
|
+
isometry_group="PSO(2, 1)", isometry_group_is_projective=True)
|
|
1287
|
+
|
|
1288
|
+
def _coerce_map_from_(self, X):
|
|
1289
|
+
"""
|
|
1290
|
+
Return if there is a coercion map from ``X`` to ``self``.
|
|
1291
|
+
|
|
1292
|
+
EXAMPLES::
|
|
1293
|
+
|
|
1294
|
+
sage: KM = HyperbolicPlane().UHP()
|
|
1295
|
+
sage: KM.has_coerce_map_from(HyperbolicPlane().UHP())
|
|
1296
|
+
True
|
|
1297
|
+
sage: KM.has_coerce_map_from(HyperbolicPlane().PD())
|
|
1298
|
+
True
|
|
1299
|
+
sage: KM.has_coerce_map_from(HyperbolicPlane().HM())
|
|
1300
|
+
True
|
|
1301
|
+
sage: KM.has_coerce_map_from(QQ)
|
|
1302
|
+
False
|
|
1303
|
+
"""
|
|
1304
|
+
if isinstance(X, HyperbolicModelUHP):
|
|
1305
|
+
return CoercionUHPtoKM(Hom(X, self))
|
|
1306
|
+
if isinstance(X, HyperbolicModelPD):
|
|
1307
|
+
return CoercionPDtoKM(Hom(X, self))
|
|
1308
|
+
if isinstance(X, HyperbolicModelHM):
|
|
1309
|
+
return CoercionHMtoKM(Hom(X, self))
|
|
1310
|
+
return super()._coerce_map_from_(X)
|
|
1311
|
+
|
|
1312
|
+
def point_in_model(self, p):
|
|
1313
|
+
r"""
|
|
1314
|
+
Check whether a point lies in the open unit disk.
|
|
1315
|
+
|
|
1316
|
+
EXAMPLES::
|
|
1317
|
+
|
|
1318
|
+
sage: KM = HyperbolicPlane().KM()
|
|
1319
|
+
sage: KM.point_in_model((1, 0))
|
|
1320
|
+
False
|
|
1321
|
+
sage: KM.point_in_model((1/2, 1/2))
|
|
1322
|
+
True
|
|
1323
|
+
sage: KM.point_in_model((1, .2))
|
|
1324
|
+
False
|
|
1325
|
+
"""
|
|
1326
|
+
if isinstance(p, HyperbolicPoint):
|
|
1327
|
+
return p.is_boundary()
|
|
1328
|
+
if p in CC:
|
|
1329
|
+
p = CC(p)
|
|
1330
|
+
else:
|
|
1331
|
+
p = CC(*p)
|
|
1332
|
+
# return len(p) == 2 and bool(p[0]**2 + p[1]**2 < 1)
|
|
1333
|
+
return bool(abs(p) < 1)
|
|
1334
|
+
|
|
1335
|
+
def boundary_point_in_model(self, p):
|
|
1336
|
+
r"""
|
|
1337
|
+
Check whether a point lies in the unit circle, which corresponds
|
|
1338
|
+
to the ideal boundary of the hyperbolic plane in the Klein model.
|
|
1339
|
+
|
|
1340
|
+
EXAMPLES::
|
|
1341
|
+
|
|
1342
|
+
sage: KM = HyperbolicPlane().KM()
|
|
1343
|
+
sage: KM.boundary_point_in_model((1, 0))
|
|
1344
|
+
True
|
|
1345
|
+
sage: KM.boundary_point_in_model((1/2, 1/2))
|
|
1346
|
+
False
|
|
1347
|
+
sage: KM.boundary_point_in_model((1, .2))
|
|
1348
|
+
False
|
|
1349
|
+
"""
|
|
1350
|
+
if isinstance(p, HyperbolicPoint):
|
|
1351
|
+
return p.is_boundary()
|
|
1352
|
+
if p in CC:
|
|
1353
|
+
p = CC(p)
|
|
1354
|
+
else:
|
|
1355
|
+
p = CC(*p)
|
|
1356
|
+
# return len(p) == 2 and bool(abs(p[0]**2 + p[1]**2 - 1) < EPSILON)
|
|
1357
|
+
return bool(abs(abs(p) - 1) < EPSILON)
|
|
1358
|
+
|
|
1359
|
+
def isometry_in_model(self, A):
|
|
1360
|
+
r"""
|
|
1361
|
+
Check if the given matrix ``A`` is in the group `SO(2,1)`.
|
|
1362
|
+
|
|
1363
|
+
EXAMPLES::
|
|
1364
|
+
|
|
1365
|
+
sage: A = matrix(3, [[1, 0, 0], [0, 17/8, 15/8], [0, 15/8, 17/8]])
|
|
1366
|
+
sage: HyperbolicPlane().KM().isometry_in_model(A) # needs scipy
|
|
1367
|
+
True
|
|
1368
|
+
"""
|
|
1369
|
+
if isinstance(A, HyperbolicIsometry):
|
|
1370
|
+
return True
|
|
1371
|
+
return bool((A * LORENTZ_GRAM * A.transpose() - LORENTZ_GRAM).norm()**2 <
|
|
1372
|
+
EPSILON)
|
|
1373
|
+
|
|
1374
|
+
def get_background_graphic(self, **bdry_options):
|
|
1375
|
+
r"""
|
|
1376
|
+
Return a graphic object that makes the model easier to visualize.
|
|
1377
|
+
|
|
1378
|
+
For the Klein model, the background object is the ideal boundary.
|
|
1379
|
+
|
|
1380
|
+
EXAMPLES::
|
|
1381
|
+
|
|
1382
|
+
sage: circ = HyperbolicPlane().KM().get_background_graphic() # needs sage.plot
|
|
1383
|
+
"""
|
|
1384
|
+
from sage.plot.circle import circle
|
|
1385
|
+
return circle((0, 0), 1, axes=False, color='black')
|
|
1386
|
+
|
|
1387
|
+
#####################################################################
|
|
1388
|
+
# Hyperboloid model
|
|
1389
|
+
|
|
1390
|
+
|
|
1391
|
+
class HyperbolicModelHM(HyperbolicModel):
|
|
1392
|
+
r"""
|
|
1393
|
+
Hyperboloid Model.
|
|
1394
|
+
"""
|
|
1395
|
+
_Geodesic = HyperbolicGeodesicHM
|
|
1396
|
+
|
|
1397
|
+
def __init__(self, space):
|
|
1398
|
+
"""
|
|
1399
|
+
Initialize ``self``.
|
|
1400
|
+
|
|
1401
|
+
EXAMPLES::
|
|
1402
|
+
|
|
1403
|
+
sage: HM = HyperbolicPlane().HM()
|
|
1404
|
+
sage: TestSuite(HM).run()
|
|
1405
|
+
"""
|
|
1406
|
+
HyperbolicModel.__init__(self, space,
|
|
1407
|
+
name="Hyperboloid Model", short_name='HM',
|
|
1408
|
+
bounded=False, conformal=True, dimension=2,
|
|
1409
|
+
isometry_group="SO(2, 1)", isometry_group_is_projective=False)
|
|
1410
|
+
|
|
1411
|
+
def _coerce_map_from_(self, X):
|
|
1412
|
+
"""
|
|
1413
|
+
Return if there is a coercion map from ``X`` to ``self``.
|
|
1414
|
+
|
|
1415
|
+
EXAMPLES::
|
|
1416
|
+
|
|
1417
|
+
sage: HM = HyperbolicPlane().UHP()
|
|
1418
|
+
sage: HM.has_coerce_map_from(HyperbolicPlane().UHP())
|
|
1419
|
+
True
|
|
1420
|
+
sage: HM.has_coerce_map_from(HyperbolicPlane().PD())
|
|
1421
|
+
True
|
|
1422
|
+
sage: HM.has_coerce_map_from(HyperbolicPlane().KM())
|
|
1423
|
+
True
|
|
1424
|
+
sage: HM.has_coerce_map_from(QQ)
|
|
1425
|
+
False
|
|
1426
|
+
"""
|
|
1427
|
+
if isinstance(X, HyperbolicModelUHP):
|
|
1428
|
+
return CoercionUHPtoHM(Hom(X, self))
|
|
1429
|
+
if isinstance(X, HyperbolicModelPD):
|
|
1430
|
+
return CoercionPDtoHM(Hom(X, self))
|
|
1431
|
+
if isinstance(X, HyperbolicModelKM):
|
|
1432
|
+
return CoercionKMtoHM(Hom(X, self))
|
|
1433
|
+
return super()._coerce_map_from_(X)
|
|
1434
|
+
|
|
1435
|
+
def point_in_model(self, p):
|
|
1436
|
+
r"""
|
|
1437
|
+
Check whether a complex number lies in the hyperboloid.
|
|
1438
|
+
|
|
1439
|
+
EXAMPLES::
|
|
1440
|
+
|
|
1441
|
+
sage: HM = HyperbolicPlane().HM()
|
|
1442
|
+
sage: HM.point_in_model((0,0,1))
|
|
1443
|
+
True
|
|
1444
|
+
sage: HM.point_in_model((1,0,sqrt(2)))
|
|
1445
|
+
True
|
|
1446
|
+
sage: HM.point_in_model((1,2,1))
|
|
1447
|
+
False
|
|
1448
|
+
"""
|
|
1449
|
+
if isinstance(p, HyperbolicPoint):
|
|
1450
|
+
return p.is_boundary()
|
|
1451
|
+
return len(p) == 3 and bool(abs(p[0]**2 + p[1]**2 - p[2]**2 + 1) < EPSILON)
|
|
1452
|
+
|
|
1453
|
+
def boundary_point_in_model(self, p):
|
|
1454
|
+
r"""
|
|
1455
|
+
Return ``False`` since the Hyperboloid model has no boundary points.
|
|
1456
|
+
|
|
1457
|
+
EXAMPLES::
|
|
1458
|
+
|
|
1459
|
+
sage: HM = HyperbolicPlane().HM()
|
|
1460
|
+
sage: HM.boundary_point_in_model((0,0,1))
|
|
1461
|
+
False
|
|
1462
|
+
sage: HM.boundary_point_in_model((1,0,sqrt(2)))
|
|
1463
|
+
False
|
|
1464
|
+
sage: HM.boundary_point_in_model((1,2,1))
|
|
1465
|
+
False
|
|
1466
|
+
"""
|
|
1467
|
+
return False
|
|
1468
|
+
|
|
1469
|
+
def isometry_in_model(self, A):
|
|
1470
|
+
r"""
|
|
1471
|
+
Test that the matrix ``A`` is in the group `SO(2,1)^+`.
|
|
1472
|
+
|
|
1473
|
+
EXAMPLES::
|
|
1474
|
+
|
|
1475
|
+
sage: A = diagonal_matrix([1,1,-1])
|
|
1476
|
+
sage: HyperbolicPlane().HM().isometry_in_model(A) # needs scipy
|
|
1477
|
+
True
|
|
1478
|
+
"""
|
|
1479
|
+
if isinstance(A, HyperbolicIsometry):
|
|
1480
|
+
return True
|
|
1481
|
+
return bool((A * LORENTZ_GRAM * A.transpose() - LORENTZ_GRAM).norm()**2 < EPSILON)
|
|
1482
|
+
|
|
1483
|
+
def get_background_graphic(self, **bdry_options):
|
|
1484
|
+
r"""
|
|
1485
|
+
Return a graphic object that makes the model easier to visualize.
|
|
1486
|
+
For the hyperboloid model, the background object is the hyperboloid
|
|
1487
|
+
itself.
|
|
1488
|
+
|
|
1489
|
+
EXAMPLES::
|
|
1490
|
+
|
|
1491
|
+
sage: H = HyperbolicPlane().HM().get_background_graphic() # needs sage.plot
|
|
1492
|
+
"""
|
|
1493
|
+
from sage.plot.plot3d.all import plot3d
|
|
1494
|
+
from sage.symbolic.ring import SR
|
|
1495
|
+
hyperboloid_opacity = bdry_options.get('hyperboloid_opacity', .1)
|
|
1496
|
+
z_height = bdry_options.get('z_height', 7.0)
|
|
1497
|
+
x_max = sqrt((z_height ** 2 - 1) / 2.0)
|
|
1498
|
+
x = SR.var('x')
|
|
1499
|
+
y = SR.var('y')
|
|
1500
|
+
return plot3d((1 + x ** 2 + y ** 2).sqrt(),
|
|
1501
|
+
(x, -x_max, x_max), (y, -x_max, x_max),
|
|
1502
|
+
opacity=hyperboloid_opacity, **bdry_options)
|