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,2419 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-symbolics
|
|
2
|
+
r"""
|
|
3
|
+
Hyperbolic Geodesics
|
|
4
|
+
|
|
5
|
+
This module implements the abstract base class for geodesics in
|
|
6
|
+
hyperbolic space of arbitrary dimension. It also contains the
|
|
7
|
+
implementations for specific models of hyperbolic geometry.
|
|
8
|
+
|
|
9
|
+
AUTHORS:
|
|
10
|
+
|
|
11
|
+
- Greg Laun (2013): initial version
|
|
12
|
+
|
|
13
|
+
EXAMPLES:
|
|
14
|
+
|
|
15
|
+
We can construct geodesics in the upper half plane model, abbreviated
|
|
16
|
+
UHP for convenience::
|
|
17
|
+
|
|
18
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(2, 3)
|
|
19
|
+
sage: g
|
|
20
|
+
Geodesic in UHP from 2 to 3
|
|
21
|
+
|
|
22
|
+
This geodesic can be plotted using :meth:`plot`, in this example we will show
|
|
23
|
+
the axis.
|
|
24
|
+
|
|
25
|
+
::
|
|
26
|
+
|
|
27
|
+
sage: g.plot(axes=True) # needs sage.plot
|
|
28
|
+
Graphics object consisting of 2 graphics primitives
|
|
29
|
+
|
|
30
|
+
.. PLOT::
|
|
31
|
+
|
|
32
|
+
g = HyperbolicPlane().UHP().get_geodesic(2.0, 3.0)
|
|
33
|
+
sphinx_plot(g.plot(axes=True))
|
|
34
|
+
|
|
35
|
+
::
|
|
36
|
+
|
|
37
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3 + I)
|
|
38
|
+
sage: g.length()
|
|
39
|
+
arccosh(11/2)
|
|
40
|
+
sage: g.plot(axes=True) # needs sage.plot
|
|
41
|
+
Graphics object consisting of 2 graphics primitives
|
|
42
|
+
|
|
43
|
+
.. PLOT::
|
|
44
|
+
|
|
45
|
+
sphinx_plot(HyperbolicPlane().UHP().get_geodesic(I, 3 + I).plot(axes=True))
|
|
46
|
+
|
|
47
|
+
Geodesics of both types in UHP are supported::
|
|
48
|
+
|
|
49
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3*I)
|
|
50
|
+
sage: g
|
|
51
|
+
Geodesic in UHP from I to 3*I
|
|
52
|
+
sage: g.plot() # needs sage.plot
|
|
53
|
+
Graphics object consisting of 2 graphics primitives
|
|
54
|
+
|
|
55
|
+
.. PLOT::
|
|
56
|
+
|
|
57
|
+
sphinx_plot(HyperbolicPlane().UHP().get_geodesic(I, 3*I).plot())
|
|
58
|
+
|
|
59
|
+
Geodesics are oriented, which means that two geodesics with the same
|
|
60
|
+
graph will only be equal if their starting and ending points are
|
|
61
|
+
the same::
|
|
62
|
+
|
|
63
|
+
sage: g1 = HyperbolicPlane().UHP().get_geodesic(1,2)
|
|
64
|
+
sage: g2 = HyperbolicPlane().UHP().get_geodesic(2,1)
|
|
65
|
+
sage: g1 == g2
|
|
66
|
+
False
|
|
67
|
+
|
|
68
|
+
.. TODO::
|
|
69
|
+
|
|
70
|
+
Implement a parent for all geodesics of the hyperbolic plane?
|
|
71
|
+
Or implement geodesics as a parent in the subobjects category?
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# **********************************************************************
|
|
76
|
+
# Copyright (C) 2013 Greg Laun <glaun@math.umd.edu>
|
|
77
|
+
#
|
|
78
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
|
79
|
+
# as published by the Free Software Foundation; either version 2 of
|
|
80
|
+
# the License, or (at your option) any later version.
|
|
81
|
+
# https://www.gnu.org/licenses/
|
|
82
|
+
# **********************************************************************
|
|
83
|
+
|
|
84
|
+
from sage.functions.hyperbolic import sinh, cosh, arcsinh
|
|
85
|
+
from sage.functions.log import exp
|
|
86
|
+
from sage.functions.other import real, imag
|
|
87
|
+
from sage.functions.trig import arccos
|
|
88
|
+
from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON
|
|
89
|
+
from sage.matrix.constructor import matrix
|
|
90
|
+
from sage.misc.functional import sqrt
|
|
91
|
+
from sage.misc.lazy_attribute import lazy_attribute
|
|
92
|
+
from sage.misc.lazy_import import lazy_import
|
|
93
|
+
from sage.modules.free_module_element import vector
|
|
94
|
+
from sage.rings.cc import CC
|
|
95
|
+
from sage.rings.infinity import infinity
|
|
96
|
+
from sage.rings.real_mpfr import RR
|
|
97
|
+
from sage.structure.sage_object import SageObject
|
|
98
|
+
from sage.symbolic.constants import I
|
|
99
|
+
from sage.symbolic.constants import pi
|
|
100
|
+
from sage.symbolic.ring import SR
|
|
101
|
+
|
|
102
|
+
lazy_import("sage.plot.arc", "arc")
|
|
103
|
+
lazy_import("sage.plot.line", "line")
|
|
104
|
+
lazy_import("sage.plot.arc", "arc")
|
|
105
|
+
lazy_import("sage.plot.bezier_path", "bezier_path")
|
|
106
|
+
lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry',
|
|
107
|
+
'moebius_transform')
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class HyperbolicGeodesic(SageObject):
|
|
111
|
+
r"""
|
|
112
|
+
Abstract base class for oriented geodesics that are not necessarily
|
|
113
|
+
complete.
|
|
114
|
+
|
|
115
|
+
INPUT:
|
|
116
|
+
|
|
117
|
+
- ``start`` -- a HyperbolicPoint or coordinates of a point in
|
|
118
|
+
hyperbolic space representing the start of the geodesic
|
|
119
|
+
|
|
120
|
+
- ``end`` -- a HyperbolicPoint or coordinates of a point in
|
|
121
|
+
hyperbolic space representing the end of the geodesic
|
|
122
|
+
|
|
123
|
+
EXAMPLES:
|
|
124
|
+
|
|
125
|
+
We can construct a hyperbolic geodesic in any model::
|
|
126
|
+
|
|
127
|
+
sage: HyperbolicPlane().UHP().get_geodesic(1, 0)
|
|
128
|
+
Geodesic in UHP from 1 to 0
|
|
129
|
+
sage: HyperbolicPlane().PD().get_geodesic(1, 0)
|
|
130
|
+
Geodesic in PD from 1 to 0
|
|
131
|
+
sage: HyperbolicPlane().KM().get_geodesic((0,1/2), (1/2, 0))
|
|
132
|
+
Geodesic in KM from (0, 1/2) to (1/2, 0)
|
|
133
|
+
sage: HyperbolicPlane().HM().get_geodesic((0,0,1), (0,1, sqrt(2)))
|
|
134
|
+
Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2))
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
#####################
|
|
138
|
+
# "Private" Methods #
|
|
139
|
+
#####################
|
|
140
|
+
|
|
141
|
+
def __init__(self, model, start, end, **graphics_options):
|
|
142
|
+
r"""
|
|
143
|
+
See :class:`HyperbolicGeodesic` for full documentation.
|
|
144
|
+
|
|
145
|
+
EXAMPLES::
|
|
146
|
+
|
|
147
|
+
sage: HyperbolicPlane().UHP().get_geodesic(I, 2 + I)
|
|
148
|
+
Geodesic in UHP from I to I + 2
|
|
149
|
+
"""
|
|
150
|
+
self._model = model
|
|
151
|
+
self._start = start
|
|
152
|
+
self._end = end
|
|
153
|
+
self._graphics_options = graphics_options
|
|
154
|
+
|
|
155
|
+
@lazy_attribute
|
|
156
|
+
def _cached_geodesic(self):
|
|
157
|
+
r"""
|
|
158
|
+
The representation of the geodesic used for calculations.
|
|
159
|
+
|
|
160
|
+
EXAMPLES::
|
|
161
|
+
|
|
162
|
+
sage: A = HyperbolicPlane().PD().get_geodesic(0, 1/2)
|
|
163
|
+
sage: A._cached_geodesic
|
|
164
|
+
Geodesic in UHP from I to 3/5*I + 4/5
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
M = self._model.realization_of().a_realization()
|
|
168
|
+
return self.to_model(M)
|
|
169
|
+
|
|
170
|
+
@lazy_attribute
|
|
171
|
+
def _complete(self):
|
|
172
|
+
r"""
|
|
173
|
+
Return whether the geodesic is complete. This is used for
|
|
174
|
+
geodesics in non-bounded models. For these models,
|
|
175
|
+
``self.complete()`` simply sets ``_complete`` to ``True``.
|
|
176
|
+
|
|
177
|
+
EXAMPLES::
|
|
178
|
+
|
|
179
|
+
sage: HyperbolicPlane().UHP().get_geodesic(1, -12)._complete
|
|
180
|
+
True
|
|
181
|
+
sage: HyperbolicPlane().UHP().get_geodesic(I, 2 + I)._complete
|
|
182
|
+
False
|
|
183
|
+
sage: HM = HyperbolicPlane().HM()
|
|
184
|
+
sage: g = HM.get_geodesic((0,0,1), (0,1, sqrt(2)))
|
|
185
|
+
sage: g._complete
|
|
186
|
+
False
|
|
187
|
+
sage: g.complete()._complete
|
|
188
|
+
True
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
if self._model.is_bounded():
|
|
192
|
+
return (self._start.is_boundary() and self._end.is_boundary())
|
|
193
|
+
return False # All non-bounded geodesics start life incomplete.
|
|
194
|
+
|
|
195
|
+
def _repr_(self):
|
|
196
|
+
r"""
|
|
197
|
+
Return a string representation of ``self``.
|
|
198
|
+
|
|
199
|
+
EXAMPLES::
|
|
200
|
+
|
|
201
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
202
|
+
sage: UHP.get_geodesic(3 + 4*I, I)
|
|
203
|
+
Geodesic in UHP from 4*I + 3 to I
|
|
204
|
+
|
|
205
|
+
sage: PD = HyperbolicPlane().PD()
|
|
206
|
+
sage: PD.get_geodesic(1/2 + I/2, 0)
|
|
207
|
+
Geodesic in PD from 1/2*I + 1/2 to 0
|
|
208
|
+
|
|
209
|
+
sage: KM = HyperbolicPlane().KM()
|
|
210
|
+
sage: KM.get_geodesic((1/2, 1/2), (0, 0))
|
|
211
|
+
Geodesic in KM from (1/2, 1/2) to (0, 0)
|
|
212
|
+
|
|
213
|
+
sage: HM = HyperbolicPlane().HM()
|
|
214
|
+
sage: HM.get_geodesic((0,0,1), (0, 1, sqrt(Integer(2))))
|
|
215
|
+
Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2))
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
msg = "Geodesic in {0} from {1} to {2}"
|
|
219
|
+
return msg.format(self._model.short_name(),
|
|
220
|
+
self._start.coordinates(),
|
|
221
|
+
self._end.coordinates())
|
|
222
|
+
|
|
223
|
+
def __eq__(self, other):
|
|
224
|
+
r"""
|
|
225
|
+
Return ``True`` if ``self`` is equal to other as an oriented geodesic.
|
|
226
|
+
|
|
227
|
+
EXAMPLES::
|
|
228
|
+
|
|
229
|
+
sage: g1 = HyperbolicPlane().UHP().get_geodesic(I, 2*I)
|
|
230
|
+
sage: g2 = HyperbolicPlane().UHP().get_geodesic(2*I, I)
|
|
231
|
+
sage: g1 == g2
|
|
232
|
+
False
|
|
233
|
+
sage: g1 == g1
|
|
234
|
+
True
|
|
235
|
+
"""
|
|
236
|
+
if not isinstance(other, HyperbolicGeodesic):
|
|
237
|
+
return False
|
|
238
|
+
return (self._model is other._model and
|
|
239
|
+
self._start == other._start and
|
|
240
|
+
self._end == other._end)
|
|
241
|
+
|
|
242
|
+
def __ne__(self, other):
|
|
243
|
+
"""
|
|
244
|
+
Test unequality of ``self`` and ``other``.
|
|
245
|
+
|
|
246
|
+
EXAMPLES::
|
|
247
|
+
|
|
248
|
+
sage: g1 = HyperbolicPlane().UHP().get_geodesic(I, 2*I)
|
|
249
|
+
sage: g2 = HyperbolicPlane().UHP().get_geodesic(2*I, I)
|
|
250
|
+
sage: g1 != g2
|
|
251
|
+
True
|
|
252
|
+
sage: g1 != g1
|
|
253
|
+
False
|
|
254
|
+
"""
|
|
255
|
+
return not (self == other)
|
|
256
|
+
|
|
257
|
+
#######################
|
|
258
|
+
# Setters and Getters #
|
|
259
|
+
#######################
|
|
260
|
+
|
|
261
|
+
def start(self):
|
|
262
|
+
r"""
|
|
263
|
+
Return the starting point of the geodesic.
|
|
264
|
+
|
|
265
|
+
EXAMPLES::
|
|
266
|
+
|
|
267
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3*I)
|
|
268
|
+
sage: g.start()
|
|
269
|
+
Point in UHP I
|
|
270
|
+
"""
|
|
271
|
+
return self._start
|
|
272
|
+
|
|
273
|
+
def end(self):
|
|
274
|
+
r"""
|
|
275
|
+
Return the starting point of the geodesic.
|
|
276
|
+
|
|
277
|
+
EXAMPLES::
|
|
278
|
+
|
|
279
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3*I)
|
|
280
|
+
sage: g.end()
|
|
281
|
+
Point in UHP 3*I
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
return self._end
|
|
285
|
+
|
|
286
|
+
def endpoints(self):
|
|
287
|
+
r"""
|
|
288
|
+
Return a list containing the start and endpoints.
|
|
289
|
+
|
|
290
|
+
EXAMPLES::
|
|
291
|
+
|
|
292
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3*I)
|
|
293
|
+
sage: g.endpoints()
|
|
294
|
+
[Point in UHP I, Point in UHP 3*I]
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
return [self._start, self._end]
|
|
298
|
+
|
|
299
|
+
def model(self):
|
|
300
|
+
r"""
|
|
301
|
+
Return the model to which the :class:`HyperbolicGeodesic` belongs.
|
|
302
|
+
|
|
303
|
+
EXAMPLES::
|
|
304
|
+
|
|
305
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
306
|
+
sage: UHP.get_geodesic(I, 2*I).model()
|
|
307
|
+
Hyperbolic plane in the Upper Half Plane Model
|
|
308
|
+
|
|
309
|
+
sage: PD = HyperbolicPlane().PD()
|
|
310
|
+
sage: PD.get_geodesic(0, I/2).model()
|
|
311
|
+
Hyperbolic plane in the Poincare Disk Model
|
|
312
|
+
|
|
313
|
+
sage: KM = HyperbolicPlane().KM()
|
|
314
|
+
sage: KM.get_geodesic((0, 0), (0, 1/2)).model()
|
|
315
|
+
Hyperbolic plane in the Klein Disk Model
|
|
316
|
+
|
|
317
|
+
sage: HM = HyperbolicPlane().HM()
|
|
318
|
+
sage: HM.get_geodesic((0, 0, 1), (0, 1, sqrt(2))).model()
|
|
319
|
+
Hyperbolic plane in the Hyperboloid Model
|
|
320
|
+
"""
|
|
321
|
+
return self._model
|
|
322
|
+
|
|
323
|
+
def to_model(self, model):
|
|
324
|
+
r"""
|
|
325
|
+
Convert the current object to image in another model.
|
|
326
|
+
|
|
327
|
+
INPUT:
|
|
328
|
+
|
|
329
|
+
- ``model`` -- the image model
|
|
330
|
+
|
|
331
|
+
EXAMPLES::
|
|
332
|
+
|
|
333
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
334
|
+
sage: PD = HyperbolicPlane().PD()
|
|
335
|
+
sage: UHP.get_geodesic(I, 2*I).to_model(PD)
|
|
336
|
+
Geodesic in PD from 0 to 1/3*I
|
|
337
|
+
sage: UHP.get_geodesic(I, 2*I).to_model('PD')
|
|
338
|
+
Geodesic in PD from 0 to 1/3*I
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
if isinstance(model, str):
|
|
342
|
+
model = getattr(self._model.realization_of(), model)()
|
|
343
|
+
if not model.is_bounded() and self.length() == infinity:
|
|
344
|
+
raise NotImplementedError("cannot convert to an unbounded model")
|
|
345
|
+
start = model(self._start)
|
|
346
|
+
end = model(self._end)
|
|
347
|
+
g = model.get_geodesic(start, end)
|
|
348
|
+
return g
|
|
349
|
+
|
|
350
|
+
def graphics_options(self):
|
|
351
|
+
r"""
|
|
352
|
+
Return the graphics options of ``self``.
|
|
353
|
+
|
|
354
|
+
EXAMPLES::
|
|
355
|
+
|
|
356
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 2*I, color='red')
|
|
357
|
+
sage: g.graphics_options()
|
|
358
|
+
{'color': 'red'}
|
|
359
|
+
"""
|
|
360
|
+
return self._graphics_options
|
|
361
|
+
|
|
362
|
+
def update_graphics(self, update=False, **options):
|
|
363
|
+
r"""
|
|
364
|
+
Update the graphics options of ``self``.
|
|
365
|
+
|
|
366
|
+
INPUT:
|
|
367
|
+
|
|
368
|
+
- ``update`` -- if ``True``, the original option are updated
|
|
369
|
+
rather than overwritten
|
|
370
|
+
|
|
371
|
+
EXAMPLES::
|
|
372
|
+
|
|
373
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 2*I)
|
|
374
|
+
sage: g.graphics_options()
|
|
375
|
+
{}
|
|
376
|
+
|
|
377
|
+
sage: g.update_graphics(color = "red"); g.graphics_options()
|
|
378
|
+
{'color': 'red'}
|
|
379
|
+
|
|
380
|
+
sage: g.update_graphics(color = "blue"); g.graphics_options()
|
|
381
|
+
{'color': 'blue'}
|
|
382
|
+
|
|
383
|
+
sage: g.update_graphics(True, size = 20); g.graphics_options()
|
|
384
|
+
{'color': 'blue', 'size': 20}
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
if not update:
|
|
388
|
+
self._graphics_options = {}
|
|
389
|
+
self._graphics_options.update(**options)
|
|
390
|
+
|
|
391
|
+
###################
|
|
392
|
+
# Boolean Methods #
|
|
393
|
+
###################
|
|
394
|
+
|
|
395
|
+
def is_complete(self):
|
|
396
|
+
r"""
|
|
397
|
+
Return ``True`` if ``self`` is a complete geodesic (that is, both
|
|
398
|
+
endpoints are on the ideal boundary) and ``False`` otherwise.
|
|
399
|
+
|
|
400
|
+
If we represent complete geodesics using green color and incomplete
|
|
401
|
+
using red colors we have the following graphic:
|
|
402
|
+
|
|
403
|
+
.. PLOT::
|
|
404
|
+
|
|
405
|
+
UHP = HyperbolicPlane().UHP()
|
|
406
|
+
g = UHP.get_geodesic(1.5*I, 2.5*I)
|
|
407
|
+
h = UHP.get_geodesic(0, I)
|
|
408
|
+
l = UHP.get_geodesic(2, 4)
|
|
409
|
+
m = UHP.get_geodesic(3, infinity)
|
|
410
|
+
G = g.plot(color='red') +\
|
|
411
|
+
text('is_complete()=False',
|
|
412
|
+
(0, 2),
|
|
413
|
+
horizontal_alignement='left')
|
|
414
|
+
H = h.plot(color='red') +\
|
|
415
|
+
text('is_complete()=False',
|
|
416
|
+
(0, 0.5),
|
|
417
|
+
horizontal_alignement='left')
|
|
418
|
+
L = l.plot(color='green') +\
|
|
419
|
+
text('is_complete()=True',
|
|
420
|
+
(5, 1.5))
|
|
421
|
+
M = m.plot(color='green') + text('is complete()=True',
|
|
422
|
+
(5, 4),
|
|
423
|
+
horizontal_alignement='left')
|
|
424
|
+
sphinx_plot(G+H+L+M)
|
|
425
|
+
|
|
426
|
+
Notice, that there is no visual indication that the *vertical* geodesic
|
|
427
|
+
is complete
|
|
428
|
+
|
|
429
|
+
EXAMPLES::
|
|
430
|
+
|
|
431
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
432
|
+
sage: UHP.get_geodesic(1.5*I, 2.5*I).is_complete()
|
|
433
|
+
False
|
|
434
|
+
sage: UHP.get_geodesic(0, I).is_complete()
|
|
435
|
+
False
|
|
436
|
+
sage: UHP.get_geodesic(3, infinity).is_complete()
|
|
437
|
+
True
|
|
438
|
+
sage: UHP.get_geodesic(2,5).is_complete()
|
|
439
|
+
True
|
|
440
|
+
"""
|
|
441
|
+
|
|
442
|
+
return self._complete
|
|
443
|
+
|
|
444
|
+
def is_asymptotically_parallel(self, other):
|
|
445
|
+
r"""
|
|
446
|
+
Return ``True`` if ``self`` and ``other`` are asymptotically
|
|
447
|
+
parallel and ``False`` otherwise.
|
|
448
|
+
|
|
449
|
+
INPUT:
|
|
450
|
+
|
|
451
|
+
- ``other`` -- a hyperbolic geodesic
|
|
452
|
+
|
|
453
|
+
EXAMPLES::
|
|
454
|
+
|
|
455
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5)
|
|
456
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(-2,4)
|
|
457
|
+
sage: g.is_asymptotically_parallel(h)
|
|
458
|
+
True
|
|
459
|
+
|
|
460
|
+
.. PLOT::
|
|
461
|
+
|
|
462
|
+
g = HyperbolicPlane().UHP().get_geodesic(-2.0,5.0)
|
|
463
|
+
h = HyperbolicPlane().UHP().get_geodesic(-2.0,4.0)
|
|
464
|
+
sphinx_plot(g.plot(color='green')+h.plot(color='green'))
|
|
465
|
+
|
|
466
|
+
Ultraparallel geodesics are not asymptotically parallel::
|
|
467
|
+
|
|
468
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5)
|
|
469
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(-1,4)
|
|
470
|
+
sage: g.is_asymptotically_parallel(h)
|
|
471
|
+
False
|
|
472
|
+
|
|
473
|
+
.. PLOT::
|
|
474
|
+
|
|
475
|
+
g = HyperbolicPlane().UHP().get_geodesic(-2.0,5.0)
|
|
476
|
+
h = HyperbolicPlane().UHP().get_geodesic(-1.0,4.0)
|
|
477
|
+
sphinx_plot(g.plot(color='red')+h.plot(color='red'))
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
No hyperbolic geodesic is asymptotically parallel to itself::
|
|
481
|
+
|
|
482
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5)
|
|
483
|
+
sage: g.is_asymptotically_parallel(g)
|
|
484
|
+
False
|
|
485
|
+
"""
|
|
486
|
+
|
|
487
|
+
p1, p2 = self.complete().endpoints()
|
|
488
|
+
q1, q2 = other.complete().endpoints()
|
|
489
|
+
return ((self != other) and ((p1 in [q1, q2]) or (p2 in [q1, q2])) and
|
|
490
|
+
self.model() is other.model())
|
|
491
|
+
|
|
492
|
+
def is_ultra_parallel(self, other):
|
|
493
|
+
r"""
|
|
494
|
+
Return ``True`` if ``self`` and ``other`` are ultra parallel
|
|
495
|
+
and ``False`` otherwise.
|
|
496
|
+
|
|
497
|
+
INPUT:
|
|
498
|
+
|
|
499
|
+
- ``other`` -- a hyperbolic geodesic
|
|
500
|
+
|
|
501
|
+
EXAMPLES::
|
|
502
|
+
|
|
503
|
+
sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic \
|
|
504
|
+
....: import *
|
|
505
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(0,1)
|
|
506
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(-3,-1)
|
|
507
|
+
sage: g.is_ultra_parallel(h)
|
|
508
|
+
True
|
|
509
|
+
|
|
510
|
+
.. PLOT::
|
|
511
|
+
|
|
512
|
+
g = HyperbolicPlane().UHP().get_geodesic(0.0,1.1)
|
|
513
|
+
h = HyperbolicPlane().UHP().get_geodesic(-3.0,-1.0)
|
|
514
|
+
sphinx_plot(g.plot(color='green')+h.plot(color='green'))
|
|
515
|
+
|
|
516
|
+
::
|
|
517
|
+
|
|
518
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5)
|
|
519
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(2,6)
|
|
520
|
+
sage: g.is_ultra_parallel(h)
|
|
521
|
+
False
|
|
522
|
+
|
|
523
|
+
.. PLOT::
|
|
524
|
+
|
|
525
|
+
g = HyperbolicPlane().UHP().get_geodesic(-2,5)
|
|
526
|
+
h = HyperbolicPlane().UHP().get_geodesic(2,6)
|
|
527
|
+
sphinx_plot(g.plot(color='red')+h.plot(color='red'))
|
|
528
|
+
|
|
529
|
+
::
|
|
530
|
+
|
|
531
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5)
|
|
532
|
+
sage: g.is_ultra_parallel(g)
|
|
533
|
+
False
|
|
534
|
+
"""
|
|
535
|
+
|
|
536
|
+
A = self.reflection_involution()
|
|
537
|
+
B = other.reflection_involution()
|
|
538
|
+
return (A * B).classification() == 'hyperbolic'
|
|
539
|
+
|
|
540
|
+
def is_parallel(self, other):
|
|
541
|
+
r"""
|
|
542
|
+
Return ``True`` if the two given hyperbolic geodesics are either
|
|
543
|
+
ultra parallel or asymptotically parallel and ``False`` otherwise.
|
|
544
|
+
|
|
545
|
+
INPUT:
|
|
546
|
+
|
|
547
|
+
- ``other`` -- a hyperbolic geodesic in any model
|
|
548
|
+
|
|
549
|
+
OUTPUT:
|
|
550
|
+
|
|
551
|
+
``True`` if the given geodesics are either ultra parallel or
|
|
552
|
+
asymptotically parallel, ``False`` if not.
|
|
553
|
+
|
|
554
|
+
EXAMPLES::
|
|
555
|
+
|
|
556
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5)
|
|
557
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(5,12)
|
|
558
|
+
sage: g.is_parallel(h)
|
|
559
|
+
True
|
|
560
|
+
|
|
561
|
+
.. PLOT::
|
|
562
|
+
|
|
563
|
+
g = HyperbolicPlane().UHP().get_geodesic(-2,5)
|
|
564
|
+
h = HyperbolicPlane().UHP().get_geodesic(5,12)
|
|
565
|
+
sphinx_plot(g.plot(color='green')+h.plot(color='green'))
|
|
566
|
+
|
|
567
|
+
::
|
|
568
|
+
|
|
569
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5)
|
|
570
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(-2,4)
|
|
571
|
+
sage: g.is_parallel(h)
|
|
572
|
+
True
|
|
573
|
+
|
|
574
|
+
.. PLOT::
|
|
575
|
+
|
|
576
|
+
g = HyperbolicPlane().UHP().get_geodesic(-2.0,5.0)
|
|
577
|
+
h = HyperbolicPlane().UHP().get_geodesic(-2.0,4.0)
|
|
578
|
+
sphinx_plot(g.plot(color='green')+h.plot(color='green'))
|
|
579
|
+
|
|
580
|
+
::
|
|
581
|
+
|
|
582
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,2)
|
|
583
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(-1,4)
|
|
584
|
+
sage: g.is_parallel(h)
|
|
585
|
+
False
|
|
586
|
+
|
|
587
|
+
.. PLOT::
|
|
588
|
+
|
|
589
|
+
g = HyperbolicPlane().UHP().get_geodesic(-2,2)
|
|
590
|
+
h = HyperbolicPlane().UHP().get_geodesic(-1,4)
|
|
591
|
+
sphinx_plot(g.plot(color='red')+h.plot(color='red'))
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
No hyperbolic geodesic is either ultra parallel or
|
|
595
|
+
asymptotically parallel to itself::
|
|
596
|
+
|
|
597
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5)
|
|
598
|
+
sage: g.is_parallel(g)
|
|
599
|
+
False
|
|
600
|
+
"""
|
|
601
|
+
|
|
602
|
+
A = self.reflection_involution()
|
|
603
|
+
B = other.reflection_involution()
|
|
604
|
+
return (A * B).classification() in ['parabolic', 'hyperbolic']
|
|
605
|
+
|
|
606
|
+
def ideal_endpoints(self):
|
|
607
|
+
r"""
|
|
608
|
+
Return the ideal endpoints in bounded models. Raise a
|
|
609
|
+
:exc:`NotImplementedError` in models that are not bounded.
|
|
610
|
+
|
|
611
|
+
EXAMPLES::
|
|
612
|
+
|
|
613
|
+
sage: H = HyperbolicPlane()
|
|
614
|
+
sage: UHP = H.UHP()
|
|
615
|
+
sage: UHP.get_geodesic(1 + I, 1 + 3*I).ideal_endpoints()
|
|
616
|
+
[Boundary point in UHP 1, Boundary point in UHP +Infinity]
|
|
617
|
+
|
|
618
|
+
sage: PD = H.PD()
|
|
619
|
+
sage: PD.get_geodesic(0, I/2).ideal_endpoints()
|
|
620
|
+
[Boundary point in PD -I, Boundary point in PD I]
|
|
621
|
+
|
|
622
|
+
sage: KM = H.KM()
|
|
623
|
+
sage: KM.get_geodesic((0,0), (0, 1/2)).ideal_endpoints()
|
|
624
|
+
[Boundary point in KM (0, -1), Boundary point in KM (0, 1)]
|
|
625
|
+
|
|
626
|
+
sage: HM = H.HM()
|
|
627
|
+
sage: HM.get_geodesic((0,0,1), (1, 0, sqrt(2))).ideal_endpoints()
|
|
628
|
+
Traceback (most recent call last):
|
|
629
|
+
...
|
|
630
|
+
NotImplementedError: boundary points are not implemented in
|
|
631
|
+
the HM model
|
|
632
|
+
"""
|
|
633
|
+
|
|
634
|
+
if not self._model.is_bounded():
|
|
635
|
+
errtxt = "boundary points are not implemented in the " + \
|
|
636
|
+
"{0} model".format(self._model.short_name())
|
|
637
|
+
raise NotImplementedError(errtxt)
|
|
638
|
+
if self.is_complete():
|
|
639
|
+
return self.endpoints()
|
|
640
|
+
return [self._model(k)
|
|
641
|
+
for k in self._cached_geodesic.ideal_endpoints()]
|
|
642
|
+
|
|
643
|
+
def complete(self):
|
|
644
|
+
r"""
|
|
645
|
+
Return the geodesic with ideal endpoints in bounded models. Raise a
|
|
646
|
+
:exc:`NotImplementedError` in models that are not bounded.
|
|
647
|
+
In the following examples we represent complete geodesics by a dashed
|
|
648
|
+
line.
|
|
649
|
+
|
|
650
|
+
EXAMPLES::
|
|
651
|
+
|
|
652
|
+
sage: H = HyperbolicPlane()
|
|
653
|
+
sage: UHP = H.UHP()
|
|
654
|
+
sage: UHP.get_geodesic(1 + I, 1 + 3*I).complete()
|
|
655
|
+
Geodesic in UHP from 1 to +Infinity
|
|
656
|
+
|
|
657
|
+
.. PLOT::
|
|
658
|
+
|
|
659
|
+
g = HyperbolicPlane().UHP().get_geodesic(1 + I, 1 + 3*I)
|
|
660
|
+
h = g.complete()
|
|
661
|
+
sphinx_plot(g.plot()+h.plot(linestyle='dashed'))
|
|
662
|
+
|
|
663
|
+
::
|
|
664
|
+
|
|
665
|
+
sage: PD = H.PD()
|
|
666
|
+
sage: PD.get_geodesic(0, I/2).complete()
|
|
667
|
+
Geodesic in PD from -I to I
|
|
668
|
+
sage: PD.get_geodesic(0.25*(-1-I),0.25*(1-I)).complete()
|
|
669
|
+
Geodesic in PD from -0.895806416477617 - 0.444444444444444*I to 0.895806416477617 - 0.444444444444444*I
|
|
670
|
+
|
|
671
|
+
.. PLOT::
|
|
672
|
+
|
|
673
|
+
PD = HyperbolicPlane().PD()
|
|
674
|
+
g = PD.get_geodesic(0, I/2)
|
|
675
|
+
h = g. complete()
|
|
676
|
+
m = PD.get_geodesic(0.25*(-1-I),0.25*(1-I))
|
|
677
|
+
l = m.complete()
|
|
678
|
+
sphinx_plot(g.plot()+h.plot(linestyle='dashed') +
|
|
679
|
+
m.plot()+l.plot(linestyle='dashed'))
|
|
680
|
+
|
|
681
|
+
::
|
|
682
|
+
|
|
683
|
+
sage: KM = H.KM()
|
|
684
|
+
sage: KM.get_geodesic((0,0), (0,1/2)).complete()
|
|
685
|
+
Geodesic in KM from (0, -1) to (0, 1)
|
|
686
|
+
|
|
687
|
+
.. PLOT::
|
|
688
|
+
|
|
689
|
+
g = HyperbolicPlane().KM().get_geodesic(CC(0,0), CC(0, 0.5))
|
|
690
|
+
h = g.complete()
|
|
691
|
+
sphinx_plot(g.plot()+h.plot(linestyle='dashed'))
|
|
692
|
+
|
|
693
|
+
::
|
|
694
|
+
|
|
695
|
+
sage: KM.get_geodesic(-I, 1).complete()
|
|
696
|
+
Geodesic in KM from -I to 1
|
|
697
|
+
|
|
698
|
+
.. PLOT::
|
|
699
|
+
|
|
700
|
+
g = HyperbolicPlane().KM().get_geodesic(CC(0,-1), CC(1, 0))
|
|
701
|
+
h = g.complete()
|
|
702
|
+
sphinx_plot(g.plot()+h.plot(linestyle='dashed'))
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
::
|
|
706
|
+
|
|
707
|
+
sage: HM = H.HM()
|
|
708
|
+
sage: HM.get_geodesic((0,0,1), (1, 0, sqrt(2))).complete()
|
|
709
|
+
Geodesic in HM from (0, 0, 1) to (1, 0, sqrt(2))
|
|
710
|
+
|
|
711
|
+
.. PLOT::
|
|
712
|
+
|
|
713
|
+
g = HyperbolicPlane().HM().get_geodesic((0,0,1), (1, 0, sqrt(2)))
|
|
714
|
+
h = g.complete()
|
|
715
|
+
sphinx_plot(g.plot(color='black')+h.plot(linestyle='dashed',color='black'))
|
|
716
|
+
|
|
717
|
+
::
|
|
718
|
+
|
|
719
|
+
sage: g = HM.get_geodesic((0,0,1), (1, 0, sqrt(2))).complete()
|
|
720
|
+
sage: g.is_complete()
|
|
721
|
+
True
|
|
722
|
+
|
|
723
|
+
TESTS:
|
|
724
|
+
|
|
725
|
+
Check that floating points remain floating points through this method::
|
|
726
|
+
|
|
727
|
+
sage: H = HyperbolicPlane()
|
|
728
|
+
sage: g = H.UHP().get_geodesic(CC(0,1), CC(2,2))
|
|
729
|
+
sage: gc = g.complete()
|
|
730
|
+
sage: parent(gc.start().coordinates())
|
|
731
|
+
Real Field with 53 bits of precision
|
|
732
|
+
"""
|
|
733
|
+
|
|
734
|
+
if self._model.is_bounded():
|
|
735
|
+
return self._model.get_geodesic(*self.ideal_endpoints())
|
|
736
|
+
|
|
737
|
+
from copy import copy
|
|
738
|
+
g = copy(self)
|
|
739
|
+
g._complete = True
|
|
740
|
+
return g
|
|
741
|
+
|
|
742
|
+
def reflection_involution(self):
|
|
743
|
+
r"""
|
|
744
|
+
Return the involution fixing ``self``.
|
|
745
|
+
|
|
746
|
+
EXAMPLES::
|
|
747
|
+
|
|
748
|
+
sage: H = HyperbolicPlane()
|
|
749
|
+
sage: gU = H.UHP().get_geodesic(2,4)
|
|
750
|
+
sage: RU = gU.reflection_involution(); RU
|
|
751
|
+
Isometry in UHP
|
|
752
|
+
[ 3 -8]
|
|
753
|
+
[ 1 -3]
|
|
754
|
+
|
|
755
|
+
sage: RU*gU == gU
|
|
756
|
+
True
|
|
757
|
+
|
|
758
|
+
sage: gP = H.PD().get_geodesic(0, I)
|
|
759
|
+
sage: RP = gP.reflection_involution(); RP
|
|
760
|
+
Isometry in PD
|
|
761
|
+
[ 1 0]
|
|
762
|
+
[ 0 -1]
|
|
763
|
+
|
|
764
|
+
sage: RP*gP == gP
|
|
765
|
+
True
|
|
766
|
+
|
|
767
|
+
sage: gK = H.KM().get_geodesic((0,0), (0,1))
|
|
768
|
+
sage: RK = gK.reflection_involution(); RK
|
|
769
|
+
Isometry in KM
|
|
770
|
+
[-1 0 0]
|
|
771
|
+
[ 0 1 0]
|
|
772
|
+
[ 0 0 1]
|
|
773
|
+
|
|
774
|
+
sage: RK*gK == gK
|
|
775
|
+
True
|
|
776
|
+
|
|
777
|
+
sage: HM = H.HM()
|
|
778
|
+
sage: g = HM.get_geodesic((0,0,1), (1,0, n(sqrt(2))))
|
|
779
|
+
sage: A = g.reflection_involution()
|
|
780
|
+
sage: B = diagonal_matrix([1, -1, 1])
|
|
781
|
+
sage: bool((B - A.matrix()).norm() < 10**-9) # needs scipy
|
|
782
|
+
True
|
|
783
|
+
|
|
784
|
+
The above tests go through the Upper Half Plane. It remains to
|
|
785
|
+
test that the matrices in the models do what we intend. ::
|
|
786
|
+
|
|
787
|
+
sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry \
|
|
788
|
+
....: import moebius_transform
|
|
789
|
+
sage: R = H.PD().get_geodesic(-1,1).reflection_involution()
|
|
790
|
+
sage: bool(moebius_transform(R.matrix(), 0) == 0)
|
|
791
|
+
True
|
|
792
|
+
"""
|
|
793
|
+
|
|
794
|
+
ri = self._cached_geodesic.reflection_involution()
|
|
795
|
+
return ri.to_model(self._model)
|
|
796
|
+
|
|
797
|
+
def common_perpendicula(self, other):
|
|
798
|
+
r"""
|
|
799
|
+
Return the unique hyperbolic geodesic perpendicular to two given
|
|
800
|
+
geodesics, if such a geodesic exists. If none exists, raise a
|
|
801
|
+
:exc:`ValueError`.
|
|
802
|
+
|
|
803
|
+
INPUT:
|
|
804
|
+
|
|
805
|
+
- ``other`` -- a hyperbolic geodesic in the same model as ``self``
|
|
806
|
+
|
|
807
|
+
OUTPUT: a hyperbolic geodesic
|
|
808
|
+
|
|
809
|
+
EXAMPLES::
|
|
810
|
+
|
|
811
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(2,3)
|
|
812
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(4,5)
|
|
813
|
+
sage: g.common_perpendicular(h)
|
|
814
|
+
Geodesic in UHP from 1/2*sqrt(3) + 7/2 to -1/2*sqrt(3) + 7/2
|
|
815
|
+
|
|
816
|
+
.. PLOT::
|
|
817
|
+
|
|
818
|
+
g = HyperbolicPlane().UHP().get_geodesic(2.0, 3.0)
|
|
819
|
+
h = HyperbolicPlane().UHP().get_geodesic(4.0, 5.0)
|
|
820
|
+
l = g.common_perpendicular(h)
|
|
821
|
+
P = g.plot(color='blue') +\
|
|
822
|
+
h.plot(color='blue') +\
|
|
823
|
+
l.plot(color='orange')
|
|
824
|
+
sphinx_plot(P)
|
|
825
|
+
|
|
826
|
+
It is an error to ask for the common perpendicular of two
|
|
827
|
+
intersecting geodesics::
|
|
828
|
+
|
|
829
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(2,4)
|
|
830
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(3, infinity)
|
|
831
|
+
sage: g.common_perpendicular(h)
|
|
832
|
+
Traceback (most recent call last):
|
|
833
|
+
...
|
|
834
|
+
ValueError: geodesics intersect; no common perpendicular exists
|
|
835
|
+
"""
|
|
836
|
+
|
|
837
|
+
if not self.is_parallel(other):
|
|
838
|
+
raise ValueError('geodesics intersect; ' +
|
|
839
|
+
'no common perpendicular exists')
|
|
840
|
+
cp = self._cached_geodesic.common_perpendicular(other)
|
|
841
|
+
return cp.to_model(self._model)
|
|
842
|
+
|
|
843
|
+
def intersection(self, other):
|
|
844
|
+
r"""
|
|
845
|
+
Return the point of intersection of two geodesics (if such a
|
|
846
|
+
point exists).
|
|
847
|
+
|
|
848
|
+
INPUT:
|
|
849
|
+
|
|
850
|
+
- ``other`` -- a hyperbolic geodesic in the same model as ``self``
|
|
851
|
+
|
|
852
|
+
OUTPUT: a hyperbolic point or geodesic
|
|
853
|
+
|
|
854
|
+
EXAMPLES::
|
|
855
|
+
|
|
856
|
+
sage: PD = HyperbolicPlane().PD()
|
|
857
|
+
"""
|
|
858
|
+
|
|
859
|
+
if self == other:
|
|
860
|
+
return self
|
|
861
|
+
elif self.is_parallel(other):
|
|
862
|
+
raise ValueError("geodesics don't intersect")
|
|
863
|
+
inters = self._cached_geodesic.intersection(other)
|
|
864
|
+
if len(inters) == 2:
|
|
865
|
+
return self
|
|
866
|
+
elif len(inters) == 1:
|
|
867
|
+
return [self._model(inters[0])]
|
|
868
|
+
return []
|
|
869
|
+
|
|
870
|
+
def perpendicular_bisector(self):
|
|
871
|
+
r"""
|
|
872
|
+
Return the perpendicular bisector of ``self`` if ``self`` has
|
|
873
|
+
finite length. Here distance is hyperbolic distance.
|
|
874
|
+
|
|
875
|
+
EXAMPLES::
|
|
876
|
+
|
|
877
|
+
sage: # needs scipy sage.plot
|
|
878
|
+
sage: PD = HyperbolicPlane().PD()
|
|
879
|
+
sage: g = PD.get_geodesic(-0.3+0.4*I,+0.7-0.1*I)
|
|
880
|
+
sage: h = g.perpendicular_bisector().complete()
|
|
881
|
+
sage: P = g.plot(color='blue') + h.plot(color='orange'); P
|
|
882
|
+
Graphics object consisting of 4 graphics primitives
|
|
883
|
+
|
|
884
|
+
.. PLOT::
|
|
885
|
+
|
|
886
|
+
g = HyperbolicPlane().PD().get_geodesic(-0.3+0.4*I,+0.7-0.1*I)
|
|
887
|
+
h = g.perpendicular_bisector().complete()
|
|
888
|
+
sphinx_plot(g.plot(color='blue')+h.plot(color='orange'))
|
|
889
|
+
|
|
890
|
+
Complete geodesics cannot be bisected::
|
|
891
|
+
|
|
892
|
+
sage: # needs scipy
|
|
893
|
+
sage: g = HyperbolicPlane().PD().get_geodesic(0, 1)
|
|
894
|
+
sage: g.perpendicular_bisector()
|
|
895
|
+
Traceback (most recent call last):
|
|
896
|
+
...
|
|
897
|
+
ValueError: the length must be finite
|
|
898
|
+
|
|
899
|
+
TESTS::
|
|
900
|
+
|
|
901
|
+
sage: # needs scipy
|
|
902
|
+
sage: g = HyperbolicPlane().PD().random_geodesic()
|
|
903
|
+
sage: h = g.perpendicular_bisector().complete()
|
|
904
|
+
sage: bool(h.intersection(g)[0].coordinates() - g.midpoint().coordinates() < 10**-9)
|
|
905
|
+
True
|
|
906
|
+
|
|
907
|
+
sage: # needs scipy
|
|
908
|
+
sage: g = HyperbolicPlane().UHP().random_geodesic()
|
|
909
|
+
sage: h = g.perpendicular_bisector().complete()
|
|
910
|
+
sage: bool(h.intersection(g)[0].coordinates() - g.midpoint().coordinates() < 10**-9)
|
|
911
|
+
True
|
|
912
|
+
"""
|
|
913
|
+
|
|
914
|
+
P = self._cached_geodesic.perpendicular_bisector()
|
|
915
|
+
return P.to_model(self._model)
|
|
916
|
+
|
|
917
|
+
def midpoint(self):
|
|
918
|
+
r"""
|
|
919
|
+
Return the (hyperbolic) midpoint of a hyperbolic line segment.
|
|
920
|
+
|
|
921
|
+
EXAMPLES::
|
|
922
|
+
|
|
923
|
+
sage: g = HyperbolicPlane().UHP().random_geodesic()
|
|
924
|
+
sage: m = g.midpoint()
|
|
925
|
+
sage: end1, end2 = g.endpoints()
|
|
926
|
+
sage: bool(abs(m.dist(end1) - m.dist(end2)) < 10**-9)
|
|
927
|
+
True
|
|
928
|
+
|
|
929
|
+
Complete geodesics have no midpoint::
|
|
930
|
+
|
|
931
|
+
sage: HyperbolicPlane().UHP().get_geodesic(0,2).midpoint()
|
|
932
|
+
Traceback (most recent call last):
|
|
933
|
+
...
|
|
934
|
+
ValueError: the length must be finite
|
|
935
|
+
"""
|
|
936
|
+
|
|
937
|
+
UHP = self._model.realization_of().a_realization()
|
|
938
|
+
P = self.to_model(UHP).midpoint()
|
|
939
|
+
return self._model(P)
|
|
940
|
+
|
|
941
|
+
def dist(self, other):
|
|
942
|
+
r"""
|
|
943
|
+
Return the hyperbolic distance from a given hyperbolic geodesic
|
|
944
|
+
to another geodesic or point.
|
|
945
|
+
|
|
946
|
+
INPUT:
|
|
947
|
+
|
|
948
|
+
- ``other`` -- a hyperbolic geodesic or hyperbolic point in
|
|
949
|
+
the same model
|
|
950
|
+
|
|
951
|
+
OUTPUT: the hyperbolic distance
|
|
952
|
+
|
|
953
|
+
EXAMPLES::
|
|
954
|
+
|
|
955
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(2, 4.0)
|
|
956
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(5, 7.0)
|
|
957
|
+
sage: bool(abs(g.dist(h).n() - 1.92484730023841) < 10**-9)
|
|
958
|
+
True
|
|
959
|
+
|
|
960
|
+
If the second object is a geodesic ultraparallel to the first,
|
|
961
|
+
or if it is a point on the boundary that is not one of the
|
|
962
|
+
first object's endpoints, then return +infinity
|
|
963
|
+
|
|
964
|
+
::
|
|
965
|
+
|
|
966
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(2, 2+I)
|
|
967
|
+
sage: p = HyperbolicPlane().UHP().get_point(5)
|
|
968
|
+
sage: g.dist(p)
|
|
969
|
+
+Infinity
|
|
970
|
+
|
|
971
|
+
TESTS:
|
|
972
|
+
|
|
973
|
+
Check that floating points remain floating points in :meth:`dist` ::
|
|
974
|
+
|
|
975
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
976
|
+
sage: g = UHP.get_geodesic(CC(0,1), CC(2,2))
|
|
977
|
+
sage: UHP.dist(g.start(), g.end())
|
|
978
|
+
1.45057451382258
|
|
979
|
+
sage: parent(_)
|
|
980
|
+
Real Field with 53 bits of precision
|
|
981
|
+
"""
|
|
982
|
+
|
|
983
|
+
return self._model.dist(self, other)
|
|
984
|
+
|
|
985
|
+
def angle(self, other):
|
|
986
|
+
r"""
|
|
987
|
+
Return the angle between any two given geodesics if they
|
|
988
|
+
intersect.
|
|
989
|
+
|
|
990
|
+
INPUT:
|
|
991
|
+
|
|
992
|
+
- ``other`` -- a hyperbolic geodesic in the same model as ``self``
|
|
993
|
+
|
|
994
|
+
OUTPUT: the angle in radians between the two given geodesics
|
|
995
|
+
|
|
996
|
+
EXAMPLES::
|
|
997
|
+
|
|
998
|
+
sage: PD = HyperbolicPlane().PD()
|
|
999
|
+
sage: g = PD.get_geodesic(3/5*I + 4/5, 15/17*I + 8/17)
|
|
1000
|
+
sage: h = PD.get_geodesic(4/5*I + 3/5, I)
|
|
1001
|
+
sage: g.angle(h)
|
|
1002
|
+
1/2*pi
|
|
1003
|
+
|
|
1004
|
+
.. PLOT::
|
|
1005
|
+
|
|
1006
|
+
PD = HyperbolicPlane().PD()
|
|
1007
|
+
g = PD.get_geodesic(3.0/5.0*I + 4.0/5.0, 15.0/17.0*I + 8.0/17.0)
|
|
1008
|
+
h = PD.get_geodesic(4.0/5.0*I + 3.0/5.0, I)
|
|
1009
|
+
sphinx_plot(g.plot()+h.plot(color='orange'))
|
|
1010
|
+
"""
|
|
1011
|
+
|
|
1012
|
+
return self._cached_geodesic.angle(other)
|
|
1013
|
+
|
|
1014
|
+
def length(self):
|
|
1015
|
+
r"""
|
|
1016
|
+
Return the Hyperbolic length of the hyperbolic line segment.
|
|
1017
|
+
|
|
1018
|
+
EXAMPLES::
|
|
1019
|
+
|
|
1020
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(2 + I, 3 + I/2)
|
|
1021
|
+
sage: g.length()
|
|
1022
|
+
arccosh(9/4)
|
|
1023
|
+
"""
|
|
1024
|
+
|
|
1025
|
+
return self._model._dist_points(self._start.coordinates(),
|
|
1026
|
+
self._end.coordinates())
|
|
1027
|
+
|
|
1028
|
+
# ***********************************************************************
|
|
1029
|
+
# UHP geodesics
|
|
1030
|
+
# ***********************************************************************
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
class HyperbolicGeodesicUHP(HyperbolicGeodesic):
|
|
1034
|
+
r"""
|
|
1035
|
+
Create a geodesic in the upper half plane model.
|
|
1036
|
+
|
|
1037
|
+
The geodesics in this model are represented by circular arcs perpendicular
|
|
1038
|
+
to the real axis (half-circles whose origin is on the real axis) and
|
|
1039
|
+
straight vertical lines ending on the real axis.
|
|
1040
|
+
|
|
1041
|
+
INPUT:
|
|
1042
|
+
|
|
1043
|
+
- ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space
|
|
1044
|
+
representing the start of the geodesic
|
|
1045
|
+
|
|
1046
|
+
- ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space
|
|
1047
|
+
representing the end of the geodesic
|
|
1048
|
+
|
|
1049
|
+
EXAMPLES::
|
|
1050
|
+
|
|
1051
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1052
|
+
sage: g = UHP.get_geodesic(UHP.get_point(I), UHP.get_point(2 + I))
|
|
1053
|
+
sage: g = UHP.get_geodesic(I, 2 + I)
|
|
1054
|
+
sage: h = UHP.get_geodesic(-1, -1+2*I)
|
|
1055
|
+
|
|
1056
|
+
.. PLOT::
|
|
1057
|
+
|
|
1058
|
+
UHP = HyperbolicPlane().UHP()
|
|
1059
|
+
g = UHP.get_geodesic(I, 2 + I)
|
|
1060
|
+
h = UHP.get_geodesic(-1, -1+2*I)
|
|
1061
|
+
sphinx_plot(g.plot()+h.plot())
|
|
1062
|
+
"""
|
|
1063
|
+
|
|
1064
|
+
def reflection_involution(self):
|
|
1065
|
+
r"""
|
|
1066
|
+
Return the isometry of the involution fixing the geodesic ``self``.
|
|
1067
|
+
|
|
1068
|
+
EXAMPLES::
|
|
1069
|
+
|
|
1070
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1071
|
+
sage: g1 = UHP.get_geodesic(0, 1)
|
|
1072
|
+
sage: g1.reflection_involution()
|
|
1073
|
+
Isometry in UHP
|
|
1074
|
+
[ 1 0]
|
|
1075
|
+
[ 2 -1]
|
|
1076
|
+
sage: UHP.get_geodesic(I, 2*I).reflection_involution()
|
|
1077
|
+
Isometry in UHP
|
|
1078
|
+
[ 1 0]
|
|
1079
|
+
[ 0 -1]
|
|
1080
|
+
"""
|
|
1081
|
+
x, y = (real(k.coordinates()) for k in self.ideal_endpoints())
|
|
1082
|
+
if x == infinity:
|
|
1083
|
+
M = matrix([[1, -2*y], [0, -1]])
|
|
1084
|
+
elif y == infinity:
|
|
1085
|
+
M = matrix([[1, -2*x], [0, -1]])
|
|
1086
|
+
else:
|
|
1087
|
+
M = matrix([[(x+y)/(y-x), -2*x*y/(y-x)], [2/(y-x), -(x+y)/(y-x)]])
|
|
1088
|
+
return self._model.get_isometry(M)
|
|
1089
|
+
|
|
1090
|
+
def plot(self, boundary=True, **options):
|
|
1091
|
+
r"""
|
|
1092
|
+
Plot ``self``.
|
|
1093
|
+
|
|
1094
|
+
EXAMPLES::
|
|
1095
|
+
|
|
1096
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1097
|
+
sage: UHP.get_geodesic(0, 1).plot() # needs sage.plot
|
|
1098
|
+
Graphics object consisting of 2 graphics primitives
|
|
1099
|
+
|
|
1100
|
+
.. PLOT::
|
|
1101
|
+
|
|
1102
|
+
UHP = HyperbolicPlane().UHP()
|
|
1103
|
+
g = UHP.get_geodesic(0.0, 1.0).plot()
|
|
1104
|
+
sphinx_plot(g)
|
|
1105
|
+
|
|
1106
|
+
::
|
|
1107
|
+
|
|
1108
|
+
sage: UHP.get_geodesic(I, 3+4*I).plot(linestyle='dashed', color='brown') # needs sage.plot
|
|
1109
|
+
Graphics object consisting of 2 graphics primitives
|
|
1110
|
+
|
|
1111
|
+
.. PLOT::
|
|
1112
|
+
|
|
1113
|
+
UHP = HyperbolicPlane().UHP()
|
|
1114
|
+
g = UHP.get_geodesic(I, 3+4*I).plot(linestyle='dashed', color='brown')
|
|
1115
|
+
sphinx_plot(g)
|
|
1116
|
+
|
|
1117
|
+
::
|
|
1118
|
+
|
|
1119
|
+
sage: UHP.get_geodesic(1, infinity).plot(color='orange') # needs sage.plot
|
|
1120
|
+
Graphics object consisting of 2 graphics primitives
|
|
1121
|
+
|
|
1122
|
+
.. PLOT::
|
|
1123
|
+
|
|
1124
|
+
UHP = HyperbolicPlane().UHP()
|
|
1125
|
+
g = UHP.get_geodesic(1, infinity).plot(color='orange')
|
|
1126
|
+
sphinx_plot(g)
|
|
1127
|
+
|
|
1128
|
+
TESTS:
|
|
1129
|
+
|
|
1130
|
+
Plotting a line with ``boundary=True``. ::
|
|
1131
|
+
|
|
1132
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(0, I)
|
|
1133
|
+
sage: g.plot() # needs sage.plot
|
|
1134
|
+
Graphics object consisting of 2 graphics primitives
|
|
1135
|
+
|
|
1136
|
+
Plotting a line with ``boundary=False``. ::
|
|
1137
|
+
|
|
1138
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(0, I)
|
|
1139
|
+
sage: g.plot(boundary=False) # needs sage.plot
|
|
1140
|
+
Graphics object consisting of 1 graphics primitive
|
|
1141
|
+
|
|
1142
|
+
Plotting a circle with ``boundary=True``. ::
|
|
1143
|
+
|
|
1144
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-3, 19)
|
|
1145
|
+
sage: g.plot() # needs sage.plot
|
|
1146
|
+
Graphics object consisting of 2 graphics primitives
|
|
1147
|
+
|
|
1148
|
+
Plotting a circle with ``boundary=False``. ::
|
|
1149
|
+
|
|
1150
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(3, 4)
|
|
1151
|
+
sage: g.plot(boundary=False) # needs sage.plot
|
|
1152
|
+
Graphics object consisting of 1 graphics primitive
|
|
1153
|
+
"""
|
|
1154
|
+
|
|
1155
|
+
opts = {'axes': False, 'aspect_ratio': 1}
|
|
1156
|
+
opts.update(self.graphics_options())
|
|
1157
|
+
opts.update(options)
|
|
1158
|
+
end_1, end_2 = (CC(k.coordinates()) for k in self.endpoints())
|
|
1159
|
+
bd_1, bd_2 = (CC(k.coordinates()) for k in self.ideal_endpoints())
|
|
1160
|
+
if (abs(real(end_1) - real(end_2)) < EPSILON) \
|
|
1161
|
+
or CC(infinity) in [end_1, end_2]: # on same vertical line
|
|
1162
|
+
# If one of the endpoints is infinity, we replace it with a
|
|
1163
|
+
# large finite point
|
|
1164
|
+
if end_1 == CC(infinity):
|
|
1165
|
+
end_1 = (real(end_2), (imag(end_2) + 10))
|
|
1166
|
+
end_2 = (real(end_2), imag(end_2))
|
|
1167
|
+
elif end_2 == CC(infinity):
|
|
1168
|
+
end_2 = (real(end_1), (imag(end_1) + 10))
|
|
1169
|
+
end_1 = (real(end_1), imag(end_1))
|
|
1170
|
+
else:
|
|
1171
|
+
end_1 = (real(end_1), imag(end_1))
|
|
1172
|
+
end_2 = (real(end_2), imag(end_2))
|
|
1173
|
+
pic = bezier_path([[end_1, end_2]], **opts)
|
|
1174
|
+
if boundary:
|
|
1175
|
+
cent = min(bd_1, bd_2)
|
|
1176
|
+
bd_dict = {'bd_min': cent - 3, 'bd_max': cent + 3}
|
|
1177
|
+
bd_pic = self._model.get_background_graphic(**bd_dict)
|
|
1178
|
+
pic += bd_pic
|
|
1179
|
+
return pic
|
|
1180
|
+
else:
|
|
1181
|
+
center = (bd_1 + bd_2) / 2 # Circle center
|
|
1182
|
+
radius = abs(bd_1 - bd_2) / 2
|
|
1183
|
+
theta1 = CC(end_1 - center).arg()
|
|
1184
|
+
theta2 = CC(end_2 - center).arg()
|
|
1185
|
+
if abs(theta1 - theta2) < EPSILON:
|
|
1186
|
+
theta2 += pi
|
|
1187
|
+
pic = arc((real(center), imag(center)), radius,
|
|
1188
|
+
sector=(theta1, theta2), **opts)
|
|
1189
|
+
if boundary:
|
|
1190
|
+
# We want to draw a segment of the real line. The
|
|
1191
|
+
# computations below compute the projection of the
|
|
1192
|
+
# geodesic to the real line, and then draw a little
|
|
1193
|
+
# to the left and right of the projection.
|
|
1194
|
+
shadow_1, shadow_2 = (real(k) for k in [end_1, end_2])
|
|
1195
|
+
midpoint = (shadow_1 + shadow_2)/2
|
|
1196
|
+
length = abs(shadow_1 - shadow_2)
|
|
1197
|
+
bd_dict = {'bd_min': midpoint - length, 'bd_max': midpoint +
|
|
1198
|
+
length}
|
|
1199
|
+
bd_pic = self._model.get_background_graphic(**bd_dict)
|
|
1200
|
+
pic += bd_pic
|
|
1201
|
+
return pic
|
|
1202
|
+
|
|
1203
|
+
def ideal_endpoints(self):
|
|
1204
|
+
r"""
|
|
1205
|
+
Determine the ideal (boundary) endpoints of the complete
|
|
1206
|
+
hyperbolic geodesic corresponding to ``self``.
|
|
1207
|
+
|
|
1208
|
+
OUTPUT: list of 2 boundary points
|
|
1209
|
+
|
|
1210
|
+
EXAMPLES::
|
|
1211
|
+
|
|
1212
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1213
|
+
sage: UHP.get_geodesic(I, 2*I).ideal_endpoints()
|
|
1214
|
+
[Boundary point in UHP 0,
|
|
1215
|
+
Boundary point in UHP +Infinity]
|
|
1216
|
+
sage: UHP.get_geodesic(1 + I, 2 + 4*I).ideal_endpoints()
|
|
1217
|
+
[Boundary point in UHP -sqrt(65) + 9,
|
|
1218
|
+
Boundary point in UHP sqrt(65) + 9]
|
|
1219
|
+
|
|
1220
|
+
TESTS:
|
|
1221
|
+
|
|
1222
|
+
Check that :issue:`32362` is fixed::
|
|
1223
|
+
|
|
1224
|
+
sage: PD = HyperbolicPlane().PD()
|
|
1225
|
+
sage: z0 = CC(-0.0571909584179366 + 0.666666666666667*I)
|
|
1226
|
+
sage: z1 = CC(-1)
|
|
1227
|
+
sage: pts = PD.get_geodesic(z0, z1).ideal_endpoints()
|
|
1228
|
+
sage: pts[1]
|
|
1229
|
+
Boundary point in PD I
|
|
1230
|
+
"""
|
|
1231
|
+
start = self._start.coordinates()
|
|
1232
|
+
end = self._end.coordinates()
|
|
1233
|
+
x1, x2 = real(start), real(end)
|
|
1234
|
+
y1, y2 = imag(start), imag(end)
|
|
1235
|
+
M = self._model
|
|
1236
|
+
# infinity is the first endpoint, so the other ideal endpoint
|
|
1237
|
+
# is just the real part of the second coordinate
|
|
1238
|
+
if CC(start).is_infinity():
|
|
1239
|
+
return [M.get_point(start), M.get_point(x2)]
|
|
1240
|
+
# Same idea as above
|
|
1241
|
+
if CC(end).is_infinity():
|
|
1242
|
+
return [M.get_point(x1), M.get_point(end)]
|
|
1243
|
+
# We could also have a vertical line with two interior points
|
|
1244
|
+
if abs(x1 - x2) < EPSILON:
|
|
1245
|
+
return [M.get_point(x1), M.get_point(infinity)]
|
|
1246
|
+
# Otherwise, we have a semicircular arc in the UHP
|
|
1247
|
+
c = ((x1+x2)*(x2-x1) + (y1+y2)*(y2-y1)) / (2*(x2-x1))
|
|
1248
|
+
r = sqrt((c - x1)**2 + y1**2)
|
|
1249
|
+
return [M.get_point(c - r), M.get_point(c + r)]
|
|
1250
|
+
|
|
1251
|
+
def common_perpendicular(self, other):
|
|
1252
|
+
r"""
|
|
1253
|
+
Return the unique hyperbolic geodesic perpendicular to ``self``
|
|
1254
|
+
and ``other``, if such a geodesic exists; otherwise raise a
|
|
1255
|
+
:exc:`ValueError`.
|
|
1256
|
+
|
|
1257
|
+
INPUT:
|
|
1258
|
+
|
|
1259
|
+
- ``other`` -- a hyperbolic geodesic in current model
|
|
1260
|
+
|
|
1261
|
+
OUTPUT: a hyperbolic geodesic
|
|
1262
|
+
|
|
1263
|
+
EXAMPLES::
|
|
1264
|
+
|
|
1265
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1266
|
+
sage: g = UHP.get_geodesic(2, 3)
|
|
1267
|
+
sage: h = UHP.get_geodesic(4, 5)
|
|
1268
|
+
sage: g.common_perpendicular(h)
|
|
1269
|
+
Geodesic in UHP from 1/2*sqrt(3) + 7/2 to -1/2*sqrt(3) + 7/2
|
|
1270
|
+
|
|
1271
|
+
.. PLOT::
|
|
1272
|
+
|
|
1273
|
+
UHP = HyperbolicPlane().UHP()
|
|
1274
|
+
g = UHP.get_geodesic(2.0, 3.0)
|
|
1275
|
+
h = UHP.get_geodesic(4.0, 5.0)
|
|
1276
|
+
p = g.common_perpendicular(h)
|
|
1277
|
+
sphinx_plot(g.plot(color='blue')+h.plot(color='blue')+p.plot(color='orange'))
|
|
1278
|
+
|
|
1279
|
+
It is an error to ask for the common perpendicular of two
|
|
1280
|
+
intersecting geodesics::
|
|
1281
|
+
|
|
1282
|
+
sage: g = UHP.get_geodesic(2, 4)
|
|
1283
|
+
sage: h = UHP.get_geodesic(3, infinity)
|
|
1284
|
+
sage: g.common_perpendicular(h)
|
|
1285
|
+
Traceback (most recent call last):
|
|
1286
|
+
...
|
|
1287
|
+
ValueError: geodesics intersect; no common perpendicular exists
|
|
1288
|
+
"""
|
|
1289
|
+
|
|
1290
|
+
# Make sure both are in the same model
|
|
1291
|
+
if other._model is not self._model:
|
|
1292
|
+
other = other.to_model(self._model)
|
|
1293
|
+
|
|
1294
|
+
A = self.reflection_involution()
|
|
1295
|
+
B = other.reflection_involution()
|
|
1296
|
+
C = A * B
|
|
1297
|
+
if C.classification() != 'hyperbolic':
|
|
1298
|
+
raise ValueError("geodesics intersect; " +
|
|
1299
|
+
"no common perpendicular exists")
|
|
1300
|
+
return C.fixed_point_set()
|
|
1301
|
+
|
|
1302
|
+
def intersection(self, other):
|
|
1303
|
+
r"""
|
|
1304
|
+
Return the point of intersection of ``self`` and ``other``
|
|
1305
|
+
(if such a point exists).
|
|
1306
|
+
|
|
1307
|
+
INPUT:
|
|
1308
|
+
|
|
1309
|
+
- ``other`` -- a hyperbolic geodesic in the current model
|
|
1310
|
+
|
|
1311
|
+
OUTPUT: list of hyperbolic points or a hyperbolic geodesic
|
|
1312
|
+
|
|
1313
|
+
EXAMPLES::
|
|
1314
|
+
|
|
1315
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1316
|
+
sage: g = UHP.get_geodesic(3, 5)
|
|
1317
|
+
sage: h = UHP.get_geodesic(4, 7)
|
|
1318
|
+
sage: g.intersection(h)
|
|
1319
|
+
[Point in UHP 2/3*sqrt(-2) + 13/3]
|
|
1320
|
+
|
|
1321
|
+
.. PLOT::
|
|
1322
|
+
|
|
1323
|
+
UHP = HyperbolicPlane().UHP()
|
|
1324
|
+
g = UHP.get_geodesic(3, 5)
|
|
1325
|
+
h = UHP.get_geodesic(4, 7)
|
|
1326
|
+
P = g.intersection(h)
|
|
1327
|
+
pict = g.plot(color='red')+h.plot(color='red')
|
|
1328
|
+
sphinx_plot(pict)
|
|
1329
|
+
|
|
1330
|
+
If the given geodesics do not intersect, the function returns an
|
|
1331
|
+
empty list::
|
|
1332
|
+
|
|
1333
|
+
sage: g = UHP.get_geodesic(4, 5)
|
|
1334
|
+
sage: h = UHP.get_geodesic(6, 7)
|
|
1335
|
+
sage: g.intersection(h)
|
|
1336
|
+
[]
|
|
1337
|
+
|
|
1338
|
+
.. PLOT::
|
|
1339
|
+
|
|
1340
|
+
UHP = HyperbolicPlane().UHP()
|
|
1341
|
+
g = UHP.get_geodesic(4.0, 5.0)
|
|
1342
|
+
h = UHP.get_geodesic(6.0, 7.0)
|
|
1343
|
+
sphinx_plot(g.plot() + h.plot())
|
|
1344
|
+
|
|
1345
|
+
If the given geodesics are asymptotically parallel, the function returns the common boundary point::
|
|
1346
|
+
|
|
1347
|
+
sage: g = UHP.get_geodesic(4, 5)
|
|
1348
|
+
sage: h = UHP.get_geodesic(5, 7)
|
|
1349
|
+
sage: g.intersection(h)
|
|
1350
|
+
[Boundary point in UHP 5.00000000000000]
|
|
1351
|
+
|
|
1352
|
+
.. PLOT::
|
|
1353
|
+
|
|
1354
|
+
UHP = HyperbolicPlane().UHP()
|
|
1355
|
+
g = UHP.get_geodesic(4.0, 5.0)
|
|
1356
|
+
h = UHP.get_geodesic(6.0, 7.0)
|
|
1357
|
+
sphinx_plot(g.plot() + h.plot())
|
|
1358
|
+
|
|
1359
|
+
If the given geodesics are identical, return that
|
|
1360
|
+
geodesic::
|
|
1361
|
+
|
|
1362
|
+
sage: g = UHP.get_geodesic(4 + I, 18*I)
|
|
1363
|
+
sage: h = UHP.get_geodesic(4 + I, 18*I)
|
|
1364
|
+
sage: g.intersection(h)
|
|
1365
|
+
Geodesic in UHP from I + 4 to 18*I
|
|
1366
|
+
|
|
1367
|
+
TESTS:
|
|
1368
|
+
|
|
1369
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1370
|
+
sage: g1 = UHP.get_geodesic(2*QQbar.gen(), 5)
|
|
1371
|
+
sage: g2 = UHP.get_geodesic(-1/2, Infinity)
|
|
1372
|
+
sage: g1.intersection(g2)
|
|
1373
|
+
[]
|
|
1374
|
+
|
|
1375
|
+
sage: UHP = HyperbolicPlane().UHP() #case Ia
|
|
1376
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1377
|
+
sage: g1=UHP.get_geodesic(-1,I)
|
|
1378
|
+
sage: g2=UHP.get_geodesic(0,2)
|
|
1379
|
+
sage: g1.intersection(g2)
|
|
1380
|
+
[]
|
|
1381
|
+
|
|
1382
|
+
sage: UHP = HyperbolicPlane().UHP() #case Ib
|
|
1383
|
+
sage: g1=UHP.get_geodesic(-1,I)
|
|
1384
|
+
sage: g2=UHP.get_geodesic(1/2,1/2+2*I)
|
|
1385
|
+
sage: g1.intersection(g2)
|
|
1386
|
+
[]
|
|
1387
|
+
|
|
1388
|
+
sage: UHP = HyperbolicPlane().UHP() #case IIa
|
|
1389
|
+
sage: g1=UHP.get_geodesic(-1,+1)
|
|
1390
|
+
sage: g2=UHP.get_geodesic(-1,2)
|
|
1391
|
+
sage: g1.intersection(g2)
|
|
1392
|
+
[Boundary point in UHP -1.00000000000000]
|
|
1393
|
+
|
|
1394
|
+
sage: UHP = HyperbolicPlane().UHP() #case IIb
|
|
1395
|
+
sage: g1=UHP.get_geodesic(-1,+Infinity)
|
|
1396
|
+
sage: g2=UHP.get_geodesic(+1,+Infinity)
|
|
1397
|
+
sage: g1.intersection(g2)
|
|
1398
|
+
[Boundary point in UHP +infinity]
|
|
1399
|
+
|
|
1400
|
+
sage: UHP = HyperbolicPlane().UHP() #case IIc
|
|
1401
|
+
sage: g1=UHP.get_geodesic(-1,-1+I)
|
|
1402
|
+
sage: g2=UHP.get_geodesic(+1,+1+I)
|
|
1403
|
+
sage: g1.intersection(g2)
|
|
1404
|
+
[]
|
|
1405
|
+
|
|
1406
|
+
sage: UHP = HyperbolicPlane().UHP() #case IId
|
|
1407
|
+
sage: g1=UHP.get_geodesic(-1,+1)
|
|
1408
|
+
sage: g2=UHP.get_geodesic(-1,-1+2*I)
|
|
1409
|
+
sage: g1.intersection(g2)
|
|
1410
|
+
[Boundary point in UHP -1.00000000000000]
|
|
1411
|
+
|
|
1412
|
+
sage: UHP = HyperbolicPlane().UHP() #case IIIa
|
|
1413
|
+
sage: g1=UHP.get_geodesic(-1,I)
|
|
1414
|
+
sage: g2=UHP.get_geodesic(+1,(+cos(pi/3)+I*sin(pi/3)))
|
|
1415
|
+
sage: g1.intersection(g2)
|
|
1416
|
+
[]
|
|
1417
|
+
|
|
1418
|
+
sage: UHP = HyperbolicPlane().UHP() #case IIIb
|
|
1419
|
+
sage: g1=UHP.get_geodesic(I,2*I)
|
|
1420
|
+
sage: g2=UHP.get_geodesic(3*I,4*I)
|
|
1421
|
+
sage: g1.intersection(g2)
|
|
1422
|
+
[]
|
|
1423
|
+
|
|
1424
|
+
sage: UHP = HyperbolicPlane().UHP() #case IVa
|
|
1425
|
+
sage: g1=UHP.get_geodesic(3*I,Infinity)
|
|
1426
|
+
sage: g2=UHP.get_geodesic(2*I,4*I)
|
|
1427
|
+
sage: g1.intersection(g2)
|
|
1428
|
+
Geodesic in UHP from 3.00000000000000*I to 4.00000000000000*I
|
|
1429
|
+
|
|
1430
|
+
sage: UHP = HyperbolicPlane().UHP() #case IVb
|
|
1431
|
+
sage: g1=UHP.get_geodesic(I,3*I)
|
|
1432
|
+
sage: g2=UHP.get_geodesic(2*I,4*I)
|
|
1433
|
+
sage: g1.intersection(g2)
|
|
1434
|
+
Geodesic in UHP from 2.00000000000000*I to 3.00000000000000*I
|
|
1435
|
+
|
|
1436
|
+
sage: UHP = HyperbolicPlane().UHP() #case IVc
|
|
1437
|
+
sage: g1=UHP.get_geodesic(2*I,infinity)
|
|
1438
|
+
sage: g2=UHP.get_geodesic(3*I,infinity)
|
|
1439
|
+
sage: g1.intersection(g2)
|
|
1440
|
+
Geodesic in UHP from 3.00000000000000*I to +infinity
|
|
1441
|
+
"""
|
|
1442
|
+
|
|
1443
|
+
UHP = self.model()
|
|
1444
|
+
# Both geodesic need to be UHP geodesics for this to work
|
|
1445
|
+
if other.model() != UHP:
|
|
1446
|
+
other = other.to_model(UHP)
|
|
1447
|
+
# Get endpoints and ideal endpoints
|
|
1448
|
+
i_start_1, i_end_1 = sorted(self.ideal_endpoints(), key=str)
|
|
1449
|
+
i_start_2, i_end_2 = sorted(other.ideal_endpoints(), key=str)
|
|
1450
|
+
start_1, end_1 = (CC(x.coordinates()) for x in self.endpoints())
|
|
1451
|
+
start_2, end_2 = (CC(x.coordinates()) for x in other.endpoints())
|
|
1452
|
+
# sort the geodesic endpoints according to start_1.real() < end_1.real() and if start_1.real() == end_1.real()
|
|
1453
|
+
# then start_1.imag() < end_1.imag()
|
|
1454
|
+
if start_1.real() > end_1.real(): # enforce
|
|
1455
|
+
start_1, end_1 = end_1, start_1
|
|
1456
|
+
elif start_1.real() == end_1.real():
|
|
1457
|
+
if start_1.imag() > end_1.imag():
|
|
1458
|
+
start_1, end_1 = end_1, start_1
|
|
1459
|
+
# sort the geodesic endpoints according to start_2.real() < end_2.real() and if start_2.real() == end_2.real()
|
|
1460
|
+
# then start_2.imag() < end_2.imag()
|
|
1461
|
+
if start_2.real() > end_2.real():
|
|
1462
|
+
start_2, end_2 = end_2, start_2
|
|
1463
|
+
elif start_2.real() == end_2.real():
|
|
1464
|
+
if start_2.imag() > end_2.imag():
|
|
1465
|
+
start_2, end_2 = end_2, start_2
|
|
1466
|
+
if i_start_1 == i_start_2 and i_end_1 == i_end_2:
|
|
1467
|
+
# Unoriented segments lie on the same complete geodesic
|
|
1468
|
+
if start_1 == start_2 and end_1 == end_2:
|
|
1469
|
+
return self
|
|
1470
|
+
if start_1.real() == end_1.real() or end_1.real().is_infinity():
|
|
1471
|
+
# Both geodesics are vertical
|
|
1472
|
+
if start_2.imag() < start_1.imag():
|
|
1473
|
+
# make sure always start_1.imag() <= start_2.imag()
|
|
1474
|
+
start_1, start_2 = start_2, start_1
|
|
1475
|
+
end_1, end_2 = end_2, end_1
|
|
1476
|
+
if end_1 == start_2:
|
|
1477
|
+
return [UHP.get_point(end_1)]
|
|
1478
|
+
elif end_1.real().is_infinity() and end_2.real().is_infinity():
|
|
1479
|
+
return UHP.get_geodesic(start_2, end_2)
|
|
1480
|
+
elif end_1.imag() < start_2.imag():
|
|
1481
|
+
return []
|
|
1482
|
+
else:
|
|
1483
|
+
return UHP.get_geodesic(start_2, end_1)
|
|
1484
|
+
else:
|
|
1485
|
+
# Neither geodesic is vertical
|
|
1486
|
+
# make sure always start_1.real() <= start_2.real()
|
|
1487
|
+
if start_2.real() < start_1.real():
|
|
1488
|
+
start_1, start_2 = start_2, start_1
|
|
1489
|
+
end_1, end_2 = end_2, end_1
|
|
1490
|
+
if end_1 == start_2:
|
|
1491
|
+
return [UHP.get_point(end_1)]
|
|
1492
|
+
elif end_1.real() < start_2.real():
|
|
1493
|
+
return []
|
|
1494
|
+
else:
|
|
1495
|
+
return UHP.get_geodesic(start_2, end_1)
|
|
1496
|
+
else:
|
|
1497
|
+
# Both segments do not have the same complete geodesic
|
|
1498
|
+
# make sure always start_1.real() <= start_2.real()
|
|
1499
|
+
if start_2.real() < start_1.real():
|
|
1500
|
+
start_1, start_2 = start_2, start_1
|
|
1501
|
+
end_1, end_2 = end_2, end_1
|
|
1502
|
+
if self.is_asymptotically_parallel(other):
|
|
1503
|
+
# asymptotic parallel
|
|
1504
|
+
if start_1 == start_2:
|
|
1505
|
+
return [UHP.get_point(start_1)]
|
|
1506
|
+
elif end_1 == start_2 or end_1 == end_2:
|
|
1507
|
+
return [UHP.get_point(end_1)]
|
|
1508
|
+
else:
|
|
1509
|
+
return []
|
|
1510
|
+
else:
|
|
1511
|
+
A = self.reflection_involution()
|
|
1512
|
+
B = other.reflection_involution()
|
|
1513
|
+
C = A * B
|
|
1514
|
+
if C.classification() in ['hyperbolic', 'parabolic']:
|
|
1515
|
+
return []
|
|
1516
|
+
else:
|
|
1517
|
+
# the fixed point needs not to lie in both segments of geodesic
|
|
1518
|
+
if end_1 == start_2:
|
|
1519
|
+
return [UHP().get_point(end_1)]
|
|
1520
|
+
else:
|
|
1521
|
+
P = CC(C.fixed_point_set()[0].coordinates())
|
|
1522
|
+
if start_1.real() <= P.real() <= end_1.real() and start_2.real() <= P.real() <= end_2.real():
|
|
1523
|
+
return C.fixed_point_set()
|
|
1524
|
+
else:
|
|
1525
|
+
return []
|
|
1526
|
+
|
|
1527
|
+
def perpendicular_bisector(self): # UHP
|
|
1528
|
+
r"""
|
|
1529
|
+
Return the perpendicular bisector of the hyperbolic geodesic ``self``
|
|
1530
|
+
if that geodesic has finite length.
|
|
1531
|
+
|
|
1532
|
+
EXAMPLES::
|
|
1533
|
+
|
|
1534
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1535
|
+
|
|
1536
|
+
sage: # needs scipy
|
|
1537
|
+
sage: g = UHP.random_geodesic()
|
|
1538
|
+
sage: h = g.perpendicular_bisector().complete()
|
|
1539
|
+
sage: c = lambda x: x.coordinates()
|
|
1540
|
+
sage: bool(c(g.intersection(h)[0]) - c(g.midpoint()) < 10**-9)
|
|
1541
|
+
True
|
|
1542
|
+
|
|
1543
|
+
::
|
|
1544
|
+
|
|
1545
|
+
sage: # needs scipy
|
|
1546
|
+
sage: g = UHP.get_geodesic(1 + I, 2 + 0.5*I)
|
|
1547
|
+
sage: h = g.perpendicular_bisector().complete()
|
|
1548
|
+
sage: show(g.plot(color='blue') + h.plot(color='orange'))
|
|
1549
|
+
|
|
1550
|
+
.. PLOT::
|
|
1551
|
+
|
|
1552
|
+
UHP = HyperbolicPlane().UHP()
|
|
1553
|
+
g = UHP.get_geodesic(1+I,2+0.5*I)
|
|
1554
|
+
h = g.perpendicular_bisector().complete()
|
|
1555
|
+
sphinx_plot(g.plot(color='blue')+h.plot(color='orange'))
|
|
1556
|
+
|
|
1557
|
+
Infinite geodesics cannot be bisected::
|
|
1558
|
+
|
|
1559
|
+
sage: UHP.get_geodesic(0, 1).perpendicular_bisector()
|
|
1560
|
+
Traceback (most recent call last):
|
|
1561
|
+
...
|
|
1562
|
+
ValueError: the length must be finite
|
|
1563
|
+
|
|
1564
|
+
TESTS:
|
|
1565
|
+
|
|
1566
|
+
Check the result is independent of the order (:issue:`29936`)::
|
|
1567
|
+
|
|
1568
|
+
sage: # needs scipy
|
|
1569
|
+
sage: def bisector_gets_midpoint(a, b):
|
|
1570
|
+
....: UHP = HyperbolicPlane().UHP()
|
|
1571
|
+
....: g = UHP.get_geodesic(a, b)
|
|
1572
|
+
....: p = g.perpendicular_bisector()
|
|
1573
|
+
....: x = g.intersection(p)[0]
|
|
1574
|
+
....: m = g.midpoint()
|
|
1575
|
+
....: return bool(x.dist(m) < 1e-9)
|
|
1576
|
+
sage: c, d, e = CC(1, 1), CC(2, 1), CC(2, 0.5)
|
|
1577
|
+
sage: pairs = [(c, d), (d, c), (c, e), (e, c), (d, e), (e, d)]
|
|
1578
|
+
sage: all(bisector_gets_midpoint(a, b) for a, b in pairs)
|
|
1579
|
+
True
|
|
1580
|
+
"""
|
|
1581
|
+
if self.length() == infinity:
|
|
1582
|
+
raise ValueError("the length must be finite")
|
|
1583
|
+
start = self._start.coordinates()
|
|
1584
|
+
end = self._end.coordinates()
|
|
1585
|
+
# The complete geodesic p1 -> p2 always returns p1 < p2,
|
|
1586
|
+
# so we might need to swap start and end
|
|
1587
|
+
if ((real(start - end) > EPSILON) or
|
|
1588
|
+
(abs(real(start - end)) < EPSILON and
|
|
1589
|
+
imag(start - end) > 0)):
|
|
1590
|
+
start, end = end, start
|
|
1591
|
+
S = self.complete()._to_std_geod(start)
|
|
1592
|
+
d = self._model._dist_points(start, end) / 2
|
|
1593
|
+
T1 = matrix([[exp(d/2), 0], [0, exp(-d/2)]])
|
|
1594
|
+
s2 = sqrt(2) / 2
|
|
1595
|
+
T2 = matrix([[s2, -s2], [s2, s2]])
|
|
1596
|
+
isom_mtrx = S.inverse() * (T1 * T2) * S
|
|
1597
|
+
# We need to clean this matrix up.
|
|
1598
|
+
if (isom_mtrx - isom_mtrx.conjugate()).norm() < 5 * EPSILON:
|
|
1599
|
+
# Imaginary part is small.
|
|
1600
|
+
isom_mtrx = (isom_mtrx + isom_mtrx.conjugate()) / 2
|
|
1601
|
+
# Set it to its real part.
|
|
1602
|
+
H = self._model._Isometry(self._model, isom_mtrx, check=False)
|
|
1603
|
+
return self._model.get_geodesic(H(self._start), H(self._end))
|
|
1604
|
+
|
|
1605
|
+
def midpoint(self): # UHP
|
|
1606
|
+
r"""
|
|
1607
|
+
Return the (hyperbolic) midpoint of ``self`` if it exists.
|
|
1608
|
+
|
|
1609
|
+
EXAMPLES::
|
|
1610
|
+
|
|
1611
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1612
|
+
sage: g = UHP.random_geodesic()
|
|
1613
|
+
sage: m = g.midpoint()
|
|
1614
|
+
sage: d1 = UHP.dist(m, g.start())
|
|
1615
|
+
sage: d2 = UHP.dist(m, g.end())
|
|
1616
|
+
sage: bool(abs(d1 - d2) < 10**-9)
|
|
1617
|
+
True
|
|
1618
|
+
|
|
1619
|
+
Infinite geodesics have no midpoint::
|
|
1620
|
+
|
|
1621
|
+
sage: UHP.get_geodesic(0, 2).midpoint()
|
|
1622
|
+
Traceback (most recent call last):
|
|
1623
|
+
...
|
|
1624
|
+
ValueError: the length must be finite
|
|
1625
|
+
|
|
1626
|
+
TESTS:
|
|
1627
|
+
|
|
1628
|
+
This checks :issue:`20330` so that geodesics defined by symbolic
|
|
1629
|
+
expressions do not generate runtime errors. ::
|
|
1630
|
+
|
|
1631
|
+
sage: g=HyperbolicPlane().UHP().get_geodesic(-1+I,1+I)
|
|
1632
|
+
sage: point = g.midpoint(); point
|
|
1633
|
+
Point in UHP -1/2*(sqrt(2)*...
|
|
1634
|
+
sage: QQbar(point.coordinates()).radical_expression() # long time
|
|
1635
|
+
I*sqrt(2)
|
|
1636
|
+
|
|
1637
|
+
Check that floating points remain floating points
|
|
1638
|
+
in :meth:`midpoint`::
|
|
1639
|
+
|
|
1640
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1641
|
+
sage: g = UHP.get_geodesic(CC(0,1), CC(2,2))
|
|
1642
|
+
sage: g.midpoint()
|
|
1643
|
+
Point in UHP 0.666666666666667 + 1.69967317119760*I
|
|
1644
|
+
sage: parent(g.midpoint().coordinates())
|
|
1645
|
+
Complex Field with 53 bits of precision
|
|
1646
|
+
|
|
1647
|
+
Check that the midpoint is independent of the order (:issue:`29936`)::
|
|
1648
|
+
|
|
1649
|
+
sage: g = UHP.get_geodesic(1+I, 2+0.5*I)
|
|
1650
|
+
sage: h = UHP.get_geodesic(2+0.5*I, 1+I)
|
|
1651
|
+
sage: abs(g.midpoint().coordinates() - h.midpoint().coordinates()) < 1e-9
|
|
1652
|
+
True
|
|
1653
|
+
|
|
1654
|
+
sage: g = UHP.get_geodesic(2+I, 2+0.5*I)
|
|
1655
|
+
sage: h = UHP.get_geodesic(2+0.5*I, 2+I)
|
|
1656
|
+
sage: abs(g.midpoint().coordinates() - h.midpoint().coordinates()) < 1e-9
|
|
1657
|
+
True
|
|
1658
|
+
"""
|
|
1659
|
+
from sage.matrix.matrix_symbolic_dense import Matrix_symbolic_dense
|
|
1660
|
+
if self.length() == infinity:
|
|
1661
|
+
raise ValueError("the length must be finite")
|
|
1662
|
+
|
|
1663
|
+
start = self._start.coordinates()
|
|
1664
|
+
end = self._end.coordinates()
|
|
1665
|
+
d = self._model._dist_points(start, end) / 2
|
|
1666
|
+
# The complete geodesic p1 -> p2 always returns p1 < p2,
|
|
1667
|
+
# so we might need to swap start and end
|
|
1668
|
+
if ((real(start - end) > EPSILON) or
|
|
1669
|
+
(abs(real(start - end)) < EPSILON and
|
|
1670
|
+
imag(start - end) > 0)):
|
|
1671
|
+
start, end = end, start
|
|
1672
|
+
S = self.complete()._to_std_geod(start)
|
|
1673
|
+
|
|
1674
|
+
# If the matrix is symbolic then needs to be simplified in order to
|
|
1675
|
+
# make the calculations easier for the symbolic calculus module.
|
|
1676
|
+
if isinstance(S, Matrix_symbolic_dense):
|
|
1677
|
+
S = S.simplify_full().simplify_full()
|
|
1678
|
+
S_1 = S.inverse()
|
|
1679
|
+
T = matrix([[exp(d), 0], [0, 1]])
|
|
1680
|
+
M = S_1 * T * S
|
|
1681
|
+
P_3 = moebius_transform(M, start)
|
|
1682
|
+
return self._model.get_point(P_3)
|
|
1683
|
+
|
|
1684
|
+
def angle(self, other): # UHP
|
|
1685
|
+
r"""
|
|
1686
|
+
Return the angle between the completions of any two given
|
|
1687
|
+
geodesics if they intersect.
|
|
1688
|
+
|
|
1689
|
+
INPUT:
|
|
1690
|
+
|
|
1691
|
+
- ``other`` -- a hyperbolic geodesic in the UHP model
|
|
1692
|
+
|
|
1693
|
+
OUTPUT: the angle in radians between the two given geodesics
|
|
1694
|
+
|
|
1695
|
+
EXAMPLES::
|
|
1696
|
+
|
|
1697
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1698
|
+
sage: g = UHP.get_geodesic(2, 4)
|
|
1699
|
+
sage: h = UHP.get_geodesic(3, 3 + I)
|
|
1700
|
+
sage: g.angle(h)
|
|
1701
|
+
1/2*pi
|
|
1702
|
+
sage: numerical_approx(g.angle(h))
|
|
1703
|
+
1.57079632679490
|
|
1704
|
+
|
|
1705
|
+
.. PLOT::
|
|
1706
|
+
|
|
1707
|
+
UHP = HyperbolicPlane().UHP()
|
|
1708
|
+
g = UHP.get_geodesic(2, 4)
|
|
1709
|
+
h = UHP.get_geodesic(3, 3 + I)
|
|
1710
|
+
sphinx_plot(g.plot()+h.plot())
|
|
1711
|
+
|
|
1712
|
+
If the geodesics are identical, return angle 0::
|
|
1713
|
+
|
|
1714
|
+
sage: g.angle(g)
|
|
1715
|
+
0
|
|
1716
|
+
|
|
1717
|
+
It is an error to ask for the angle of two geodesics that do not
|
|
1718
|
+
intersect::
|
|
1719
|
+
|
|
1720
|
+
sage: g = UHP.get_geodesic(2, 4)
|
|
1721
|
+
sage: h = UHP.get_geodesic(5, 7)
|
|
1722
|
+
sage: g.angle(h)
|
|
1723
|
+
Traceback (most recent call last):
|
|
1724
|
+
...
|
|
1725
|
+
ValueError: geodesics do not intersect
|
|
1726
|
+
|
|
1727
|
+
TESTS:
|
|
1728
|
+
|
|
1729
|
+
Points as parameters raise an error. ::
|
|
1730
|
+
|
|
1731
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(I, I)
|
|
1732
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(-1, 1)
|
|
1733
|
+
sage: g.angle(h)
|
|
1734
|
+
Traceback (most recent call last):
|
|
1735
|
+
...
|
|
1736
|
+
ValueError: intersecting geodesic is a point
|
|
1737
|
+
sage: h.angle(g)
|
|
1738
|
+
Traceback (most recent call last):
|
|
1739
|
+
...
|
|
1740
|
+
ValueError: intersecting geodesic is a point
|
|
1741
|
+
|
|
1742
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(I, I)
|
|
1743
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(0, infinity)
|
|
1744
|
+
sage: g.angle(h)
|
|
1745
|
+
Traceback (most recent call last):
|
|
1746
|
+
...
|
|
1747
|
+
ValueError: intersecting geodesic is a point
|
|
1748
|
+
sage: h.angle(g)
|
|
1749
|
+
Traceback (most recent call last):
|
|
1750
|
+
...
|
|
1751
|
+
ValueError: intersecting geodesic is a point
|
|
1752
|
+
|
|
1753
|
+
Intersections in boundary points raise an error. ::
|
|
1754
|
+
|
|
1755
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(1, 3)
|
|
1756
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(1, 2)
|
|
1757
|
+
sage: g.angle(h)
|
|
1758
|
+
Traceback (most recent call last):
|
|
1759
|
+
...
|
|
1760
|
+
ValueError: geodesics do not intersect
|
|
1761
|
+
sage: h.angle(g)
|
|
1762
|
+
Traceback (most recent call last):
|
|
1763
|
+
...
|
|
1764
|
+
ValueError: geodesics do not intersect
|
|
1765
|
+
|
|
1766
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(1, 2)
|
|
1767
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(1, Infinity)
|
|
1768
|
+
sage: g.angle(h)
|
|
1769
|
+
Traceback (most recent call last):
|
|
1770
|
+
...
|
|
1771
|
+
ValueError: geodesics do not intersect
|
|
1772
|
+
sage: h.angle(g)
|
|
1773
|
+
Traceback (most recent call last):
|
|
1774
|
+
...
|
|
1775
|
+
ValueError: geodesics do not intersect
|
|
1776
|
+
|
|
1777
|
+
Parallel lines raise an error. ::
|
|
1778
|
+
|
|
1779
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-2, -2 + 4*I)
|
|
1780
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(9, Infinity)
|
|
1781
|
+
sage: g.angle(h)
|
|
1782
|
+
Traceback (most recent call last):
|
|
1783
|
+
...
|
|
1784
|
+
ValueError: geodesics do not intersect
|
|
1785
|
+
sage: h.angle(g)
|
|
1786
|
+
Traceback (most recent call last):
|
|
1787
|
+
...
|
|
1788
|
+
ValueError: geodesics do not intersect
|
|
1789
|
+
|
|
1790
|
+
Non-intersecting circles raise an error. ::
|
|
1791
|
+
|
|
1792
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-2, -1)
|
|
1793
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic( 2, 1)
|
|
1794
|
+
sage: g.angle(h)
|
|
1795
|
+
Traceback (most recent call last):
|
|
1796
|
+
...
|
|
1797
|
+
ValueError: geodesics do not intersect
|
|
1798
|
+
sage: h.angle(g)
|
|
1799
|
+
Traceback (most recent call last):
|
|
1800
|
+
...
|
|
1801
|
+
ValueError: geodesics do not intersect
|
|
1802
|
+
|
|
1803
|
+
Non-intersecting line and circle raise an error. ::
|
|
1804
|
+
|
|
1805
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-2, -2 + 4*I)
|
|
1806
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic( 7, 9)
|
|
1807
|
+
sage: g.angle(h)
|
|
1808
|
+
Traceback (most recent call last):
|
|
1809
|
+
...
|
|
1810
|
+
ValueError: geodesics do not intersect
|
|
1811
|
+
sage: h.angle(g)
|
|
1812
|
+
Traceback (most recent call last):
|
|
1813
|
+
...
|
|
1814
|
+
ValueError: geodesics do not intersect
|
|
1815
|
+
|
|
1816
|
+
Non-complete equal circles yield angle 0. ::
|
|
1817
|
+
|
|
1818
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-1, I)
|
|
1819
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(I, 1)
|
|
1820
|
+
sage: g.angle(h)
|
|
1821
|
+
0
|
|
1822
|
+
sage: h.angle(g)
|
|
1823
|
+
0
|
|
1824
|
+
|
|
1825
|
+
Complete equal lines yield angle 0. ::
|
|
1826
|
+
|
|
1827
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(4, Infinity)
|
|
1828
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(4, Infinity)
|
|
1829
|
+
sage: g.angle(h)
|
|
1830
|
+
0
|
|
1831
|
+
sage: h.angle(g)
|
|
1832
|
+
0
|
|
1833
|
+
|
|
1834
|
+
Non-complete equal lines yield angle 0. ::
|
|
1835
|
+
|
|
1836
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(1 + I, 1 + 2*I)
|
|
1837
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(1 + 3*I, 1 + 4*I)
|
|
1838
|
+
sage: g.angle(h)
|
|
1839
|
+
0
|
|
1840
|
+
sage: h.angle(g)
|
|
1841
|
+
0
|
|
1842
|
+
|
|
1843
|
+
Angle between two complete circles. ::
|
|
1844
|
+
|
|
1845
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(0, 2)
|
|
1846
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(1, 3)
|
|
1847
|
+
sage: g.angle(h)
|
|
1848
|
+
1/3*pi
|
|
1849
|
+
sage: h.angle(g)
|
|
1850
|
+
1/3*pi
|
|
1851
|
+
|
|
1852
|
+
Angle between two non-intersecting circles whose completion intersects. ::
|
|
1853
|
+
|
|
1854
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(-2, 2*I)
|
|
1855
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(-1, 1 + 2*I)
|
|
1856
|
+
sage: g.angle(h)
|
|
1857
|
+
arccos(7/8)
|
|
1858
|
+
sage: h.angle(g)
|
|
1859
|
+
arccos(7/8)
|
|
1860
|
+
|
|
1861
|
+
Angle between circle and line. Note that ``1/2*sqrt(2)`` equals
|
|
1862
|
+
``1/4*pi``. ::
|
|
1863
|
+
|
|
1864
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic( 0, Infinity)
|
|
1865
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(-1, 1)
|
|
1866
|
+
sage: g.angle(h)
|
|
1867
|
+
1/2*pi
|
|
1868
|
+
sage: h.angle(g)
|
|
1869
|
+
1/2*pi
|
|
1870
|
+
|
|
1871
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(1, 1 + I)
|
|
1872
|
+
sage: h = HyperbolicPlane().UHP().get_geodesic(-sqrt(2), sqrt(2))
|
|
1873
|
+
sage: g.angle(h)
|
|
1874
|
+
1/4*pi
|
|
1875
|
+
sage: h.angle(g)
|
|
1876
|
+
1/4*pi
|
|
1877
|
+
|
|
1878
|
+
Angle is unoriented, as opposed to oriented. ::
|
|
1879
|
+
|
|
1880
|
+
sage: g = HyperbolicPlane().UHP().get_geodesic(0, I)
|
|
1881
|
+
sage: h1 = HyperbolicPlane().UHP().get_geodesic(-1, 2)
|
|
1882
|
+
sage: h2 = HyperbolicPlane().UHP().get_geodesic(1, -2)
|
|
1883
|
+
sage: g.angle(h1)
|
|
1884
|
+
arccos(1/3)
|
|
1885
|
+
sage: h1.angle(g)
|
|
1886
|
+
arccos(1/3)
|
|
1887
|
+
sage: g.angle(h2)
|
|
1888
|
+
arccos(1/3)
|
|
1889
|
+
sage: h2.angle(g)
|
|
1890
|
+
arccos(1/3)
|
|
1891
|
+
"""
|
|
1892
|
+
|
|
1893
|
+
if self.is_parallel(other):
|
|
1894
|
+
raise ValueError("geodesics do not intersect")
|
|
1895
|
+
|
|
1896
|
+
if other._model is not self._model:
|
|
1897
|
+
other = other.to_model(self._model)
|
|
1898
|
+
|
|
1899
|
+
# Check if any of the geodesics is a point.
|
|
1900
|
+
a1, a2 = self.start().coordinates(), self.end().coordinates()
|
|
1901
|
+
b1, b2 = other.start().coordinates(), other.end().coordinates()
|
|
1902
|
+
if abs(a2 - a1) < EPSILON or abs(b2 - b1) < EPSILON:
|
|
1903
|
+
raise ValueError("intersecting geodesic is a point")
|
|
1904
|
+
|
|
1905
|
+
p1, p2 = (p.coordinates() for p in self.ideal_endpoints())
|
|
1906
|
+
q1, q2 = (p.coordinates() for p in other.ideal_endpoints())
|
|
1907
|
+
|
|
1908
|
+
# Check if both geodesics are lines. All lines intersect at
|
|
1909
|
+
# ``Infinity``, but the angle is always zero.
|
|
1910
|
+
if infinity in [p1, p2] and infinity in [q1, q2]:
|
|
1911
|
+
return 0
|
|
1912
|
+
|
|
1913
|
+
# Check if the geodesics are approximately equal. This must be
|
|
1914
|
+
# done to prevent addition of ``infinity`` and ``-infinity``.
|
|
1915
|
+
v = (abs(p1 - q1) < EPSILON and abs(p2 - q2) < EPSILON)
|
|
1916
|
+
w = (abs(p1 - q2) < EPSILON and abs(p2 - q1) < EPSILON)
|
|
1917
|
+
if v or w:
|
|
1918
|
+
return 0
|
|
1919
|
+
|
|
1920
|
+
# Next, check if exactly one geodesic is a line. If this is the
|
|
1921
|
+
# case, we will swap the values of the four points around until
|
|
1922
|
+
# ``p1`` is zero, ``p2`` is ``infinity``...
|
|
1923
|
+
#
|
|
1924
|
+
# First, swap ``p`` and ``q`` if any ideal endpoint of ``other``
|
|
1925
|
+
# is ``infinity``.
|
|
1926
|
+
if infinity in [q1, q2]:
|
|
1927
|
+
p1, p2, q1, q2 = q1, q2, p1, p2
|
|
1928
|
+
# Then, if ``p1`` is infinity, swap ``p1`` and ``p2``. This
|
|
1929
|
+
# ensures that if any element of ``{p1, p2}`` is ``infinity``,
|
|
1930
|
+
# then that element is now ``p2``.
|
|
1931
|
+
if p1 == infinity:
|
|
1932
|
+
p1, p2 = p2, p1
|
|
1933
|
+
|
|
1934
|
+
# If ``p2`` is ``infinity``, we need to apply a translation to
|
|
1935
|
+
# both geodesics that moves the first geodesic onto the
|
|
1936
|
+
# imaginary line. If ``p2`` is not ``infinity``, or,
|
|
1937
|
+
# equivalently, the first geodesic is not a line, then we need
|
|
1938
|
+
# to transform the hyperbolic plane so that the first geodesic
|
|
1939
|
+
# is the imaginary line.
|
|
1940
|
+
if p2 == infinity:
|
|
1941
|
+
q1 = q1 - p1
|
|
1942
|
+
q2 = q2 - p1
|
|
1943
|
+
p1 = 0
|
|
1944
|
+
if p2 != infinity:
|
|
1945
|
+
# From now on, we may assume that ``p1``, ``p2``, ``q1``,
|
|
1946
|
+
# ``q2`` are not ``infinity``...
|
|
1947
|
+
|
|
1948
|
+
# Transform into a line.
|
|
1949
|
+
t = HyperbolicGeodesicUHP._crossratio_matrix(p1, (p1 + p2) / 2, p2)
|
|
1950
|
+
q1, q2 = (moebius_transform(t, q) for q in [q1, q2])
|
|
1951
|
+
|
|
1952
|
+
# Calculate the angle.
|
|
1953
|
+
return arccos(abs(q1 + q2) / abs(q2 - q1))
|
|
1954
|
+
|
|
1955
|
+
##################
|
|
1956
|
+
# Helper methods #
|
|
1957
|
+
##################
|
|
1958
|
+
|
|
1959
|
+
@staticmethod
|
|
1960
|
+
def _get_B(a):
|
|
1961
|
+
r"""
|
|
1962
|
+
Helper function to get an appropriate matrix transforming
|
|
1963
|
+
(0,1,inf) -> (0,I,inf) based on the type of a
|
|
1964
|
+
|
|
1965
|
+
INPUT:
|
|
1966
|
+
|
|
1967
|
+
- ``a`` -- an element to identify the class of the resulting matrix
|
|
1968
|
+
|
|
1969
|
+
EXAMPLES::
|
|
1970
|
+
|
|
1971
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
1972
|
+
sage: g = UHP.random_geodesic()
|
|
1973
|
+
sage: B = g._get_B(CDF.an_element()); B
|
|
1974
|
+
[ 1.0 0.0]
|
|
1975
|
+
[ 0.0 -1.0*I]
|
|
1976
|
+
sage: type(B)
|
|
1977
|
+
<class 'sage.matrix.matrix_complex_double_dense.Matrix_complex_double_dense'>
|
|
1978
|
+
|
|
1979
|
+
::
|
|
1980
|
+
|
|
1981
|
+
sage: B = g._get_B(SR(1)); B
|
|
1982
|
+
[ 1 0]
|
|
1983
|
+
[ 0 -I]
|
|
1984
|
+
sage: type(B)
|
|
1985
|
+
<class 'sage.matrix.matrix_symbolic_dense.Matrix_symbolic_dense'>
|
|
1986
|
+
|
|
1987
|
+
::
|
|
1988
|
+
|
|
1989
|
+
sage: B = g._get_B(complex(1)); B
|
|
1990
|
+
[ 1.0 0.0]
|
|
1991
|
+
[ 0.0 -1.0*I]
|
|
1992
|
+
sage: type(B)
|
|
1993
|
+
<class 'sage.matrix.matrix_complex_double_dense.Matrix_complex_double_dense'>
|
|
1994
|
+
|
|
1995
|
+
::
|
|
1996
|
+
|
|
1997
|
+
sage: B = g._get_B(QQbar(1+I)); B
|
|
1998
|
+
[ 1 0]
|
|
1999
|
+
[ 0 -I]
|
|
2000
|
+
sage: type(B[1,1])
|
|
2001
|
+
<class 'sage.rings.qqbar.AlgebraicNumber'>
|
|
2002
|
+
sage: type(B)
|
|
2003
|
+
<class 'sage.matrix.matrix_generic_dense.Matrix_generic_dense'>
|
|
2004
|
+
"""
|
|
2005
|
+
from sage.structure.element import Element
|
|
2006
|
+
from sage.symbolic.expression import Expression
|
|
2007
|
+
from sage.rings.complex_double import CDF
|
|
2008
|
+
|
|
2009
|
+
if isinstance(a, (int, float, complex)): # Python number
|
|
2010
|
+
a = CDF(a)
|
|
2011
|
+
|
|
2012
|
+
if isinstance(a, Expression): # symbolic
|
|
2013
|
+
P = SR
|
|
2014
|
+
zero = SR.zero()
|
|
2015
|
+
one = SR.one()
|
|
2016
|
+
I = SR("I")
|
|
2017
|
+
elif isinstance(a, Element): # Sage number
|
|
2018
|
+
P = a.parent()
|
|
2019
|
+
zero = P.zero()
|
|
2020
|
+
one = P.one()
|
|
2021
|
+
I = P.gen()
|
|
2022
|
+
if I.is_one() or (I*I).is_one() or not (-I*I).is_one():
|
|
2023
|
+
raise ValueError("invalid number")
|
|
2024
|
+
else:
|
|
2025
|
+
raise ValueError("not a complex number")
|
|
2026
|
+
|
|
2027
|
+
return matrix(P, 2, [one, zero, zero, -I])
|
|
2028
|
+
|
|
2029
|
+
def _to_std_geod(self, p):
|
|
2030
|
+
r"""
|
|
2031
|
+
Given the coordinates of a geodesic in hyperbolic space, return the
|
|
2032
|
+
hyperbolic isometry that sends that geodesic to the geodesic
|
|
2033
|
+
through 0 and infinity that also sends the point ``p`` to `i`.
|
|
2034
|
+
|
|
2035
|
+
INPUT:
|
|
2036
|
+
|
|
2037
|
+
- ``p`` -- the coordinates of the
|
|
2038
|
+
|
|
2039
|
+
EXAMPLES::
|
|
2040
|
+
|
|
2041
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
2042
|
+
sage: (p1, p2) = [UHP.random_point() for k in range(2)]
|
|
2043
|
+
sage: g = UHP.get_geodesic(p1, p2)
|
|
2044
|
+
sage: m = g.midpoint()
|
|
2045
|
+
sage: A = g._to_std_geod(m.coordinates()) # Send midpoint to I.
|
|
2046
|
+
sage: A = UHP.get_isometry(A)
|
|
2047
|
+
sage: [s, e]= g.complete().endpoints()
|
|
2048
|
+
sage: bool(abs(A(s).coordinates()) < 10**-9)
|
|
2049
|
+
True
|
|
2050
|
+
sage: bool(abs(A(m).coordinates() - I) < 10**-9)
|
|
2051
|
+
True
|
|
2052
|
+
sage: bool(abs(A(e).coordinates()) > 10**9)
|
|
2053
|
+
True
|
|
2054
|
+
|
|
2055
|
+
TESTS:
|
|
2056
|
+
|
|
2057
|
+
Check that floating points remain floating points through this method::
|
|
2058
|
+
|
|
2059
|
+
sage: H = HyperbolicPlane()
|
|
2060
|
+
sage: g = H.UHP().get_geodesic(CC(0,1), CC(2,2))
|
|
2061
|
+
sage: gc = g.complete()
|
|
2062
|
+
sage: parent(gc._to_std_geod(g.start().coordinates()))
|
|
2063
|
+
Full MatrixSpace of 2 by 2 dense matrices over Complex Field
|
|
2064
|
+
with 53 bits of precision
|
|
2065
|
+
"""
|
|
2066
|
+
s, e = (k.coordinates() for k in self.complete().endpoints())
|
|
2067
|
+
B = HyperbolicGeodesicUHP._get_B(p)
|
|
2068
|
+
# outmat below will be returned after we normalize the determinant.
|
|
2069
|
+
outmat = B * HyperbolicGeodesicUHP._crossratio_matrix(s, p, e)
|
|
2070
|
+
outmat = outmat / outmat.det().sqrt()
|
|
2071
|
+
if (outmat - outmat.conjugate()).norm(1) < 10**-9:
|
|
2072
|
+
# Small imaginary part.
|
|
2073
|
+
outmat = (outmat + outmat.conjugate()) / 2
|
|
2074
|
+
# Set it equal to its real part.
|
|
2075
|
+
return outmat
|
|
2076
|
+
|
|
2077
|
+
@staticmethod
|
|
2078
|
+
def _crossratio_matrix(p0, p1, p2): # UHP
|
|
2079
|
+
r"""
|
|
2080
|
+
Given three points (the list `p`) in `\mathbb{CP}^{1}` in affine
|
|
2081
|
+
coordinates, return the linear fractional transformation taking
|
|
2082
|
+
the elements of `p` to `0`, `1`, and `\infty`.
|
|
2083
|
+
|
|
2084
|
+
INPUT:
|
|
2085
|
+
|
|
2086
|
+
- ``p0``, ``p1``, ``p2`` -- a list of three distinct elements
|
|
2087
|
+
of `\mathbb{CP}^1` in affine coordinates; that is, each element
|
|
2088
|
+
must be a complex number, `\infty`, or symbolic.
|
|
2089
|
+
|
|
2090
|
+
OUTPUT: an element of `\GL(2,\CC)`
|
|
2091
|
+
|
|
2092
|
+
EXAMPLES::
|
|
2093
|
+
|
|
2094
|
+
sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic \
|
|
2095
|
+
....: import HyperbolicGeodesicUHP
|
|
2096
|
+
sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry \
|
|
2097
|
+
....: import moebius_transform
|
|
2098
|
+
sage: UHP = HyperbolicPlane().UHP()
|
|
2099
|
+
sage: (p1, p2, p3) = [UHP.random_point().coordinates()
|
|
2100
|
+
....: for k in range(3)]
|
|
2101
|
+
sage: A = HyperbolicGeodesicUHP._crossratio_matrix(p1, p2, p3)
|
|
2102
|
+
sage: bool(abs(moebius_transform(A, p1)) < 10**-9)
|
|
2103
|
+
True
|
|
2104
|
+
sage: bool(abs(moebius_transform(A, p2) - 1) < 10**-9)
|
|
2105
|
+
True
|
|
2106
|
+
sage: bool(moebius_transform(A, p3) == infinity)
|
|
2107
|
+
True
|
|
2108
|
+
sage: (x,y,z) = var('x,y,z')
|
|
2109
|
+
sage: HyperbolicGeodesicUHP._crossratio_matrix(x,y,z)
|
|
2110
|
+
[ y - z -x*(y - z)]
|
|
2111
|
+
[ -x + y (x - y)*z]
|
|
2112
|
+
"""
|
|
2113
|
+
|
|
2114
|
+
if p0 == infinity:
|
|
2115
|
+
return matrix([[0, -(p1 - p2)], [-1, p2]])
|
|
2116
|
+
elif p1 == infinity:
|
|
2117
|
+
return matrix([[1, -p0], [1, -p2]])
|
|
2118
|
+
elif p2 == infinity:
|
|
2119
|
+
return matrix([[1, -p0], [0, p1 - p0]])
|
|
2120
|
+
return matrix([[p1 - p2, (p1 - p2)*(-p0)],
|
|
2121
|
+
[p1 - p0, (p1 - p0)*(-p2)]])
|
|
2122
|
+
|
|
2123
|
+
# ***********************************************************************
|
|
2124
|
+
# Other geodesics
|
|
2125
|
+
# ***********************************************************************
|
|
2126
|
+
|
|
2127
|
+
|
|
2128
|
+
class HyperbolicGeodesicPD(HyperbolicGeodesic):
|
|
2129
|
+
r"""
|
|
2130
|
+
A geodesic in the Poincaré disk model.
|
|
2131
|
+
|
|
2132
|
+
Geodesics in this model are represented by segments of circles contained
|
|
2133
|
+
within the unit disk that are orthogonal to the boundary of the disk,
|
|
2134
|
+
plus all diameters of the disk.
|
|
2135
|
+
|
|
2136
|
+
INPUT:
|
|
2137
|
+
|
|
2138
|
+
- ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space
|
|
2139
|
+
representing the start of the geodesic
|
|
2140
|
+
|
|
2141
|
+
- ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space
|
|
2142
|
+
representing the end of the geodesic
|
|
2143
|
+
|
|
2144
|
+
EXAMPLES::
|
|
2145
|
+
|
|
2146
|
+
sage: PD = HyperbolicPlane().PD()
|
|
2147
|
+
sage: g = PD.get_geodesic(PD.get_point(I), PD.get_point(-I/2))
|
|
2148
|
+
sage: g = PD.get_geodesic(I,-I/2)
|
|
2149
|
+
sage: h = PD.get_geodesic(-1/2+I/2,1/2+I/2)
|
|
2150
|
+
|
|
2151
|
+
.. PLOT::
|
|
2152
|
+
|
|
2153
|
+
PD = HyperbolicPlane().PD()
|
|
2154
|
+
g = PD.get_geodesic(I,-I/2)
|
|
2155
|
+
h = PD.get_geodesic(-0.5+I*0.5,0.5+I*0.5)
|
|
2156
|
+
sphinx_plot(g.plot()+h.plot(color='green'))
|
|
2157
|
+
"""
|
|
2158
|
+
|
|
2159
|
+
def plot(self, boundary=True, **options):
|
|
2160
|
+
r"""
|
|
2161
|
+
Plot ``self``.
|
|
2162
|
+
|
|
2163
|
+
EXAMPLES:
|
|
2164
|
+
|
|
2165
|
+
First some lines::
|
|
2166
|
+
|
|
2167
|
+
sage: PD = HyperbolicPlane().PD()
|
|
2168
|
+
sage: PD.get_geodesic(0, 1).plot() # needs sage.plot
|
|
2169
|
+
Graphics object consisting of 2 graphics primitives
|
|
2170
|
+
|
|
2171
|
+
.. PLOT::
|
|
2172
|
+
|
|
2173
|
+
sphinx_plot(HyperbolicPlane().PD().get_geodesic(0, 1).plot())
|
|
2174
|
+
|
|
2175
|
+
::
|
|
2176
|
+
|
|
2177
|
+
sage: PD.get_geodesic(0, 0.3+0.8*I).plot() # needs sage.plot
|
|
2178
|
+
Graphics object consisting of 2 graphics primitives
|
|
2179
|
+
|
|
2180
|
+
.. PLOT::
|
|
2181
|
+
|
|
2182
|
+
PD = HyperbolicPlane().PD()
|
|
2183
|
+
sphinx_plot(PD.get_geodesic(0, 0.3+0.8*I).plot())
|
|
2184
|
+
|
|
2185
|
+
Then some generic geodesics::
|
|
2186
|
+
|
|
2187
|
+
sage: PD.get_geodesic(-0.5, 0.3+0.4*I).plot() # needs sage.plot
|
|
2188
|
+
Graphics object consisting of 2 graphics primitives
|
|
2189
|
+
sage: g = PD.get_geodesic(-1, exp(3*I*pi/7))
|
|
2190
|
+
sage: G = g.plot(linestyle='dashed',color='red'); G # needs sage.plot
|
|
2191
|
+
Graphics object consisting of 2 graphics primitives
|
|
2192
|
+
sage: h = PD.get_geodesic(exp(2*I*pi/11), exp(1*I*pi/11))
|
|
2193
|
+
sage: H = h.plot(thickness=6, color='orange'); H # needs sage.plot
|
|
2194
|
+
Graphics object consisting of 2 graphics primitives
|
|
2195
|
+
sage: show(G+H) # needs sage.plot
|
|
2196
|
+
|
|
2197
|
+
.. PLOT::
|
|
2198
|
+
|
|
2199
|
+
PD = HyperbolicPlane().PD()
|
|
2200
|
+
PD.get_geodesic(-0.5, 0.3+0.4*I).plot()
|
|
2201
|
+
g = PD.get_geodesic(-1, exp(3*I*pi/7))
|
|
2202
|
+
G = g.plot(linestyle='dashed',color='red')
|
|
2203
|
+
h = PD.get_geodesic(exp(2*I*pi/11), exp(1*I*pi/11))
|
|
2204
|
+
H = h.plot(thickness=6, color='orange')
|
|
2205
|
+
sphinx_plot(G+H)
|
|
2206
|
+
"""
|
|
2207
|
+
|
|
2208
|
+
opts = {'axes': False, 'aspect_ratio': 1}
|
|
2209
|
+
opts.update(self.graphics_options())
|
|
2210
|
+
opts.update(options)
|
|
2211
|
+
end_1, end_2 = (CC(k.coordinates()) for k in self.endpoints())
|
|
2212
|
+
bd_1, bd_2 = (CC(k.coordinates()) for k in self.ideal_endpoints())
|
|
2213
|
+
# Check to see if it's a line
|
|
2214
|
+
if abs(bd_1 + bd_2) < EPSILON:
|
|
2215
|
+
pic = bezier_path([[(real(end_1), imag(end_1)), (real(end_2), imag(end_2))]], **opts)
|
|
2216
|
+
else:
|
|
2217
|
+
# If we are here, we know it's not a line
|
|
2218
|
+
# So we compute the center and radius of the circle
|
|
2219
|
+
invdet = RR.one() / (real(bd_1)*imag(bd_2) - real(bd_2)*imag(bd_1))
|
|
2220
|
+
centerx = (imag(bd_2) - imag(bd_1)) * invdet
|
|
2221
|
+
centery = (real(bd_1) - real(bd_2)) * invdet
|
|
2222
|
+
center = centerx + I * centery
|
|
2223
|
+
radius = RR(abs(bd_1 - center))
|
|
2224
|
+
# Now we calculate the angles for the arc
|
|
2225
|
+
theta1 = CC(end_1 - center).arg()
|
|
2226
|
+
theta2 = CC(end_2 - center).arg()
|
|
2227
|
+
theta1, theta2 = sorted([theta1, theta2])
|
|
2228
|
+
# Make sure the sector is inside the disk
|
|
2229
|
+
if theta2 - theta1 > pi:
|
|
2230
|
+
theta1 += 2 * pi
|
|
2231
|
+
pic = arc((centerx, centery), radius,
|
|
2232
|
+
sector=(theta1, theta2), **opts)
|
|
2233
|
+
if boundary:
|
|
2234
|
+
pic += self._model.get_background_graphic()
|
|
2235
|
+
return pic
|
|
2236
|
+
|
|
2237
|
+
|
|
2238
|
+
class HyperbolicGeodesicKM(HyperbolicGeodesic):
|
|
2239
|
+
r"""
|
|
2240
|
+
A geodesic in the Klein disk model.
|
|
2241
|
+
|
|
2242
|
+
Geodesics are represented by the chords, straight line segments with ideal
|
|
2243
|
+
endpoints on the boundary circle.
|
|
2244
|
+
|
|
2245
|
+
INPUT:
|
|
2246
|
+
|
|
2247
|
+
- ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space
|
|
2248
|
+
representing the start of the geodesic
|
|
2249
|
+
|
|
2250
|
+
- ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space
|
|
2251
|
+
representing the end of the geodesic
|
|
2252
|
+
|
|
2253
|
+
EXAMPLES::
|
|
2254
|
+
|
|
2255
|
+
sage: KM = HyperbolicPlane().KM()
|
|
2256
|
+
sage: g = KM.get_geodesic((0.1,0.9),(-0.1,-0.9))
|
|
2257
|
+
sage: h = KM.get_geodesic((-0.707106781,-0.707106781),(0.707106781,-0.707106781))
|
|
2258
|
+
sage: P = g.plot(color='orange')+h.plot(); P # needs sage.plot
|
|
2259
|
+
Graphics object consisting of 4 graphics primitives
|
|
2260
|
+
|
|
2261
|
+
.. PLOT::
|
|
2262
|
+
|
|
2263
|
+
KM = HyperbolicPlane().KM()
|
|
2264
|
+
g = KM.get_geodesic(CC(0.1,0.9),
|
|
2265
|
+
CC(-0.1,-0.9))
|
|
2266
|
+
h = KM.get_geodesic(CC(-0.707106781,-0.707106781),
|
|
2267
|
+
CC(0.707106781,-0.707106781))
|
|
2268
|
+
sphinx_plot(g.plot(color='orange')+h.plot())
|
|
2269
|
+
"""
|
|
2270
|
+
|
|
2271
|
+
def plot(self, boundary=True, **options):
|
|
2272
|
+
r"""
|
|
2273
|
+
Plot ``self``.
|
|
2274
|
+
|
|
2275
|
+
EXAMPLES::
|
|
2276
|
+
|
|
2277
|
+
sage: HyperbolicPlane().KM().get_geodesic(0, 1).plot() # needs sage.plot
|
|
2278
|
+
Graphics object consisting of 2 graphics primitives
|
|
2279
|
+
|
|
2280
|
+
.. PLOT::
|
|
2281
|
+
|
|
2282
|
+
KM = HyperbolicPlane().KM()
|
|
2283
|
+
sphinx_plot(KM.get_geodesic(CC(0,0), CC(1,0)).plot())
|
|
2284
|
+
"""
|
|
2285
|
+
opts = {'axes': False, 'aspect_ratio': 1}
|
|
2286
|
+
opts.update(self.graphics_options())
|
|
2287
|
+
opts.update(options)
|
|
2288
|
+
|
|
2289
|
+
def map_pt(pt):
|
|
2290
|
+
if pt in CC:
|
|
2291
|
+
return CC(pt)
|
|
2292
|
+
return CC(*pt)
|
|
2293
|
+
end_1, end_2 = (map_pt(k.coordinates()) for k in self.endpoints())
|
|
2294
|
+
pic = bezier_path([[(real(end_1), imag(end_1)),
|
|
2295
|
+
(real(end_2), imag(end_2))]], **opts)
|
|
2296
|
+
if boundary:
|
|
2297
|
+
pic += self._model.get_background_graphic()
|
|
2298
|
+
return pic
|
|
2299
|
+
|
|
2300
|
+
|
|
2301
|
+
class HyperbolicGeodesicHM(HyperbolicGeodesic):
|
|
2302
|
+
r"""
|
|
2303
|
+
A geodesic in the hyperboloid model.
|
|
2304
|
+
|
|
2305
|
+
Valid points in the hyperboloid model satisfy :math:`x^2 + y^2 - z^2 = -1`
|
|
2306
|
+
|
|
2307
|
+
INPUT:
|
|
2308
|
+
|
|
2309
|
+
- ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space
|
|
2310
|
+
representing the start of the geodesic
|
|
2311
|
+
|
|
2312
|
+
- ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space
|
|
2313
|
+
representing the end of the geodesic
|
|
2314
|
+
|
|
2315
|
+
EXAMPLES::
|
|
2316
|
+
|
|
2317
|
+
sage: HM = HyperbolicPlane().HM()
|
|
2318
|
+
sage: p1 = HM.get_point((4, -4, sqrt(33)))
|
|
2319
|
+
sage: p2 = HM.get_point((-3,-3,sqrt(19)))
|
|
2320
|
+
sage: g = HM.get_geodesic(p1, p2)
|
|
2321
|
+
sage: g = HM.get_geodesic((4, -4, sqrt(33)), (-3, -3, sqrt(19)))
|
|
2322
|
+
|
|
2323
|
+
.. PLOT::
|
|
2324
|
+
|
|
2325
|
+
HM = HyperbolicPlane().HM()
|
|
2326
|
+
p1 = HM.get_point((4, -4, sqrt(33)))
|
|
2327
|
+
p2 = HM.get_point((-3,-3,sqrt(19)))
|
|
2328
|
+
g = HM.get_geodesic(p1, p2)
|
|
2329
|
+
sphinx_plot(g.plot(color='blue'))
|
|
2330
|
+
"""
|
|
2331
|
+
def _plot_vertices(self, points=75):
|
|
2332
|
+
r"""
|
|
2333
|
+
Return ``self`` plotting vertices in `\RR^3`.
|
|
2334
|
+
|
|
2335
|
+
Auxiliary function needed to plot polygons.
|
|
2336
|
+
|
|
2337
|
+
EXAMPLES::
|
|
2338
|
+
|
|
2339
|
+
sage: HM = HyperbolicPlane().HM()
|
|
2340
|
+
sage: p1 = HM.get_point((4, -4, sqrt(33)))
|
|
2341
|
+
sage: p2 = HM.get_point((-3,-3,sqrt(19)))
|
|
2342
|
+
sage: g = HM.get_geodesic(p1, p2)
|
|
2343
|
+
sage: g._plot_vertices(5) # needs sage.plot
|
|
2344
|
+
[(4.0, -4.0, 5.744562...),
|
|
2345
|
+
(1.363213..., -1.637073..., 2.353372...),
|
|
2346
|
+
(0.138568..., -0.969980..., 1.400022...),
|
|
2347
|
+
(-0.942533..., -1.307681..., 1.896945...),
|
|
2348
|
+
(-3.0, -3.0, 4.358898...)]
|
|
2349
|
+
"""
|
|
2350
|
+
from sage.plot.misc import setup_for_eval_on_grid
|
|
2351
|
+
from sage.arith.srange import xsrange
|
|
2352
|
+
|
|
2353
|
+
x = SR.var('x')
|
|
2354
|
+
v1, u2 = (vector(k.coordinates()) for k in self.endpoints())
|
|
2355
|
+
# Lorentzian Gram Shmidt. The original vectors will be
|
|
2356
|
+
# u1, u2 and the orthogonal ones will be v1, v2. Except
|
|
2357
|
+
# v1 = u1, and I don't want to declare another variable,
|
|
2358
|
+
# hence the odd naming convention above.
|
|
2359
|
+
# We need the Lorentz dot product of v1 and u2.
|
|
2360
|
+
v1_ldot_u2 = u2[0]*v1[0] + u2[1]*v1[1] - u2[2]*v1[2]
|
|
2361
|
+
v2 = u2 + v1_ldot_u2 * v1
|
|
2362
|
+
v2_norm = sqrt(v2[0]**2 + v2[1]**2 - v2[2]**2)
|
|
2363
|
+
v2 = v2 / v2_norm
|
|
2364
|
+
v2_ldot_u2 = u2[0]*v2[0] + u2[1]*v2[1] - u2[2]*v2[2]
|
|
2365
|
+
# Now v1 and v2 are Lorentz orthogonal, and |v1| = -1, |v2|=1
|
|
2366
|
+
# That is, v1 is unit timelike and v2 is unit spacelike.
|
|
2367
|
+
# This means that cosh(x)*v1 + sinh(x)*v2 is unit timelike.
|
|
2368
|
+
hyperbola = tuple(cosh(x)*v1 + sinh(x)*v2)
|
|
2369
|
+
endtime = arcsinh(v2_ldot_u2)
|
|
2370
|
+
# mimic the function _parametric_plot3d_curve using a bezier3d
|
|
2371
|
+
# instead of a line3d
|
|
2372
|
+
# this is required in order to be able to plot hyperbolic
|
|
2373
|
+
# polygons within the plot library
|
|
2374
|
+
g, ranges = setup_for_eval_on_grid(hyperbola, [(x, 0, endtime)], points)
|
|
2375
|
+
f_x, f_y, f_z = g
|
|
2376
|
+
return [(f_x(u), f_y(u), f_z(u))
|
|
2377
|
+
for u in xsrange(*ranges[0], include_endpoint=True)]
|
|
2378
|
+
|
|
2379
|
+
def plot(self, show_hyperboloid=True, **graphics_options):
|
|
2380
|
+
r"""
|
|
2381
|
+
Plot ``self``.
|
|
2382
|
+
|
|
2383
|
+
EXAMPLES::
|
|
2384
|
+
|
|
2385
|
+
sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic \
|
|
2386
|
+
....: import *
|
|
2387
|
+
sage: g = HyperbolicPlane().HM().random_geodesic()
|
|
2388
|
+
sage: g.plot() # needs sage.plot
|
|
2389
|
+
Graphics3d Object
|
|
2390
|
+
|
|
2391
|
+
.. PLOT::
|
|
2392
|
+
|
|
2393
|
+
sphinx_plot(HyperbolicPlane().HM().random_geodesic().plot())
|
|
2394
|
+
"""
|
|
2395
|
+
|
|
2396
|
+
x = SR.var('x')
|
|
2397
|
+
opts = self.graphics_options()
|
|
2398
|
+
opts.update(graphics_options)
|
|
2399
|
+
v1, u2 = (vector(k.coordinates()) for k in self.endpoints())
|
|
2400
|
+
# Lorentzian Gram Shmidt. The original vectors will be
|
|
2401
|
+
# u1, u2 and the orthogonal ones will be v1, v2. Except
|
|
2402
|
+
# v1 = u1, and I don't want to declare another variable,
|
|
2403
|
+
# hence the odd naming convention above.
|
|
2404
|
+
# We need the Lorentz dot product of v1 and u2.
|
|
2405
|
+
v1_ldot_u2 = u2[0]*v1[0] + u2[1]*v1[1] - u2[2]*v1[2]
|
|
2406
|
+
v2 = u2 + v1_ldot_u2 * v1
|
|
2407
|
+
v2_norm = sqrt(v2[0]**2 + v2[1]**2 - v2[2]**2)
|
|
2408
|
+
v2 = v2 / v2_norm
|
|
2409
|
+
v2_ldot_u2 = u2[0]*v2[0] + u2[1]*v2[1] - u2[2]*v2[2]
|
|
2410
|
+
# Now v1 and v2 are Lorentz orthogonal, and |v1| = -1, |v2|=1
|
|
2411
|
+
# That is, v1 is unit timelike and v2 is unit spacelike.
|
|
2412
|
+
# This means that cosh(x)*v1 + sinh(x)*v2 is unit timelike.
|
|
2413
|
+
hyperbola = cosh(x)*v1 + sinh(x)*v2
|
|
2414
|
+
endtime = arcsinh(v2_ldot_u2)
|
|
2415
|
+
from sage.plot.plot3d.all import parametric_plot3d
|
|
2416
|
+
pic = parametric_plot3d(hyperbola, (x, 0, endtime), **graphics_options)
|
|
2417
|
+
if show_hyperboloid:
|
|
2418
|
+
pic += self._model.get_background_graphic()
|
|
2419
|
+
return pic
|