passagemath-symbolics 10.8.1a1__cp311-cp311-macosx_13_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. passagemath_symbolics/.dylibs/libgmp.10.dylib +0 -0
  2. passagemath_symbolics/__init__.py +3 -0
  3. passagemath_symbolics-10.8.1a1.dist-info/METADATA +186 -0
  4. passagemath_symbolics-10.8.1a1.dist-info/RECORD +182 -0
  5. passagemath_symbolics-10.8.1a1.dist-info/WHEEL +6 -0
  6. passagemath_symbolics-10.8.1a1.dist-info/top_level.txt +3 -0
  7. sage/all__sagemath_symbolics.py +17 -0
  8. sage/calculus/all.py +14 -0
  9. sage/calculus/calculus.py +2838 -0
  10. sage/calculus/desolvers.py +1864 -0
  11. sage/calculus/predefined.py +51 -0
  12. sage/calculus/tests.py +225 -0
  13. sage/calculus/var.cpython-311-darwin.so +0 -0
  14. sage/calculus/var.pyx +401 -0
  15. sage/dynamics/all__sagemath_symbolics.py +6 -0
  16. sage/dynamics/complex_dynamics/all.py +5 -0
  17. sage/dynamics/complex_dynamics/mandel_julia.py +765 -0
  18. sage/dynamics/complex_dynamics/mandel_julia_helper.cpython-311-darwin.so +0 -0
  19. sage/dynamics/complex_dynamics/mandel_julia_helper.pyx +1034 -0
  20. sage/ext/all__sagemath_symbolics.py +1 -0
  21. sage/ext_data/kenzo/CP2.txt +45 -0
  22. sage/ext_data/kenzo/CP3.txt +349 -0
  23. sage/ext_data/kenzo/CP4.txt +4774 -0
  24. sage/ext_data/kenzo/README.txt +49 -0
  25. sage/ext_data/kenzo/S4.txt +20 -0
  26. sage/ext_data/magma/latex/latex.m +1021 -0
  27. sage/ext_data/magma/latex/latex.spec +1 -0
  28. sage/ext_data/magma/sage/basic.m +356 -0
  29. sage/ext_data/magma/sage/sage.spec +1 -0
  30. sage/ext_data/magma/spec +9 -0
  31. sage/geometry/all__sagemath_symbolics.py +8 -0
  32. sage/geometry/hyperbolic_space/all.py +5 -0
  33. sage/geometry/hyperbolic_space/hyperbolic_coercion.py +755 -0
  34. sage/geometry/hyperbolic_space/hyperbolic_constants.py +5 -0
  35. sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +2419 -0
  36. sage/geometry/hyperbolic_space/hyperbolic_interface.py +206 -0
  37. sage/geometry/hyperbolic_space/hyperbolic_isometry.py +1083 -0
  38. sage/geometry/hyperbolic_space/hyperbolic_model.py +1502 -0
  39. sage/geometry/hyperbolic_space/hyperbolic_point.py +621 -0
  40. sage/geometry/riemannian_manifolds/all.py +7 -0
  41. sage/geometry/riemannian_manifolds/parametrized_surface3d.py +1632 -0
  42. sage/geometry/riemannian_manifolds/surface3d_generators.py +461 -0
  43. sage/interfaces/all__sagemath_symbolics.py +1 -0
  44. sage/interfaces/magma.py +2991 -0
  45. sage/interfaces/magma_free.py +90 -0
  46. sage/interfaces/maple.py +1402 -0
  47. sage/interfaces/mathematica.py +1345 -0
  48. sage/interfaces/mathics.py +1312 -0
  49. sage/interfaces/sympy.py +1398 -0
  50. sage/interfaces/sympy_wrapper.py +197 -0
  51. sage/interfaces/tides.py +938 -0
  52. sage/libs/all__sagemath_symbolics.py +6 -0
  53. sage/manifolds/all.py +7 -0
  54. sage/manifolds/calculus_method.py +553 -0
  55. sage/manifolds/catalog.py +437 -0
  56. sage/manifolds/chart.py +4010 -0
  57. sage/manifolds/chart_func.py +3416 -0
  58. sage/manifolds/continuous_map.py +2183 -0
  59. sage/manifolds/continuous_map_image.py +155 -0
  60. sage/manifolds/differentiable/affine_connection.py +2475 -0
  61. sage/manifolds/differentiable/all.py +1 -0
  62. sage/manifolds/differentiable/automorphismfield.py +1383 -0
  63. sage/manifolds/differentiable/automorphismfield_group.py +604 -0
  64. sage/manifolds/differentiable/bundle_connection.py +1445 -0
  65. sage/manifolds/differentiable/characteristic_cohomology_class.py +1840 -0
  66. sage/manifolds/differentiable/chart.py +1241 -0
  67. sage/manifolds/differentiable/curve.py +1028 -0
  68. sage/manifolds/differentiable/de_rham_cohomology.py +541 -0
  69. sage/manifolds/differentiable/degenerate.py +559 -0
  70. sage/manifolds/differentiable/degenerate_submanifold.py +1668 -0
  71. sage/manifolds/differentiable/diff_form.py +1660 -0
  72. sage/manifolds/differentiable/diff_form_module.py +1062 -0
  73. sage/manifolds/differentiable/diff_map.py +1315 -0
  74. sage/manifolds/differentiable/differentiable_submanifold.py +291 -0
  75. sage/manifolds/differentiable/examples/all.py +1 -0
  76. sage/manifolds/differentiable/examples/euclidean.py +2517 -0
  77. sage/manifolds/differentiable/examples/real_line.py +897 -0
  78. sage/manifolds/differentiable/examples/sphere.py +1186 -0
  79. sage/manifolds/differentiable/examples/symplectic_space.py +187 -0
  80. sage/manifolds/differentiable/examples/symplectic_space_test.py +40 -0
  81. sage/manifolds/differentiable/integrated_curve.py +4035 -0
  82. sage/manifolds/differentiable/levi_civita_connection.py +841 -0
  83. sage/manifolds/differentiable/manifold.py +4254 -0
  84. sage/manifolds/differentiable/manifold_homset.py +1826 -0
  85. sage/manifolds/differentiable/metric.py +3032 -0
  86. sage/manifolds/differentiable/mixed_form.py +1507 -0
  87. sage/manifolds/differentiable/mixed_form_algebra.py +559 -0
  88. sage/manifolds/differentiable/multivector_module.py +800 -0
  89. sage/manifolds/differentiable/multivectorfield.py +1522 -0
  90. sage/manifolds/differentiable/poisson_tensor.py +268 -0
  91. sage/manifolds/differentiable/pseudo_riemannian.py +755 -0
  92. sage/manifolds/differentiable/pseudo_riemannian_submanifold.py +1839 -0
  93. sage/manifolds/differentiable/scalarfield.py +1343 -0
  94. sage/manifolds/differentiable/scalarfield_algebra.py +472 -0
  95. sage/manifolds/differentiable/symplectic_form.py +912 -0
  96. sage/manifolds/differentiable/symplectic_form_test.py +220 -0
  97. sage/manifolds/differentiable/tangent_space.py +412 -0
  98. sage/manifolds/differentiable/tangent_vector.py +616 -0
  99. sage/manifolds/differentiable/tensorfield.py +4665 -0
  100. sage/manifolds/differentiable/tensorfield_module.py +963 -0
  101. sage/manifolds/differentiable/tensorfield_paral.py +2450 -0
  102. sage/manifolds/differentiable/tensorfield_paral_test.py +16 -0
  103. sage/manifolds/differentiable/vector_bundle.py +1725 -0
  104. sage/manifolds/differentiable/vectorfield.py +1717 -0
  105. sage/manifolds/differentiable/vectorfield_module.py +2445 -0
  106. sage/manifolds/differentiable/vectorframe.py +1832 -0
  107. sage/manifolds/family.py +270 -0
  108. sage/manifolds/local_frame.py +1490 -0
  109. sage/manifolds/manifold.py +3090 -0
  110. sage/manifolds/manifold_homset.py +452 -0
  111. sage/manifolds/operators.py +359 -0
  112. sage/manifolds/point.py +994 -0
  113. sage/manifolds/scalarfield.py +3718 -0
  114. sage/manifolds/scalarfield_algebra.py +629 -0
  115. sage/manifolds/section.py +3111 -0
  116. sage/manifolds/section_module.py +831 -0
  117. sage/manifolds/structure.py +229 -0
  118. sage/manifolds/subset.py +2721 -0
  119. sage/manifolds/subsets/all.py +1 -0
  120. sage/manifolds/subsets/closure.py +131 -0
  121. sage/manifolds/subsets/pullback.py +883 -0
  122. sage/manifolds/topological_submanifold.py +891 -0
  123. sage/manifolds/trivialization.py +733 -0
  124. sage/manifolds/utilities.py +1348 -0
  125. sage/manifolds/vector_bundle.py +1347 -0
  126. sage/manifolds/vector_bundle_fiber.py +332 -0
  127. sage/manifolds/vector_bundle_fiber_element.py +111 -0
  128. sage/matrix/all__sagemath_symbolics.py +1 -0
  129. sage/matrix/matrix_symbolic_dense.cpython-311-darwin.so +0 -0
  130. sage/matrix/matrix_symbolic_dense.pxd +6 -0
  131. sage/matrix/matrix_symbolic_dense.pyx +1030 -0
  132. sage/matrix/matrix_symbolic_sparse.cpython-311-darwin.so +0 -0
  133. sage/matrix/matrix_symbolic_sparse.pxd +6 -0
  134. sage/matrix/matrix_symbolic_sparse.pyx +1038 -0
  135. sage/modules/all__sagemath_symbolics.py +1 -0
  136. sage/modules/vector_callable_symbolic_dense.py +105 -0
  137. sage/modules/vector_symbolic_dense.py +116 -0
  138. sage/modules/vector_symbolic_sparse.py +118 -0
  139. sage/rings/all__sagemath_symbolics.py +4 -0
  140. sage/rings/asymptotic/all.py +6 -0
  141. sage/rings/asymptotic/asymptotic_expansion_generators.py +1485 -0
  142. sage/rings/asymptotic/asymptotic_ring.py +4858 -0
  143. sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py +4106 -0
  144. sage/rings/asymptotic/growth_group.py +5373 -0
  145. sage/rings/asymptotic/growth_group_cartesian.py +1400 -0
  146. sage/rings/asymptotic/term_monoid.py +5205 -0
  147. sage/rings/function_field/all__sagemath_symbolics.py +2 -0
  148. sage/rings/polynomial/all__sagemath_symbolics.py +1 -0
  149. sage/symbolic/all.py +15 -0
  150. sage/symbolic/assumptions.py +987 -0
  151. sage/symbolic/benchmark.py +93 -0
  152. sage/symbolic/callable.py +456 -0
  153. sage/symbolic/callable.pyi +66 -0
  154. sage/symbolic/comparison_impl.pyi +38 -0
  155. sage/symbolic/complexity_measures.py +35 -0
  156. sage/symbolic/constants.py +1286 -0
  157. sage/symbolic/constants_c_impl.pyi +10 -0
  158. sage/symbolic/expression_conversion_algebraic.py +310 -0
  159. sage/symbolic/expression_conversion_sympy.py +317 -0
  160. sage/symbolic/expression_conversions.py +1727 -0
  161. sage/symbolic/function_factory.py +355 -0
  162. sage/symbolic/function_factory.pyi +41 -0
  163. sage/symbolic/getitem_impl.pyi +24 -0
  164. sage/symbolic/integration/all.py +1 -0
  165. sage/symbolic/integration/external.py +271 -0
  166. sage/symbolic/integration/integral.py +1075 -0
  167. sage/symbolic/maxima_wrapper.py +162 -0
  168. sage/symbolic/operators.py +267 -0
  169. sage/symbolic/operators.pyi +61 -0
  170. sage/symbolic/pynac_constant_impl.pyi +13 -0
  171. sage/symbolic/pynac_function_impl.pyi +8 -0
  172. sage/symbolic/random_tests.py +461 -0
  173. sage/symbolic/relation.py +2062 -0
  174. sage/symbolic/ring.cpython-311-darwin.so +0 -0
  175. sage/symbolic/ring.pxd +5 -0
  176. sage/symbolic/ring.pyi +110 -0
  177. sage/symbolic/ring.pyx +1393 -0
  178. sage/symbolic/series_impl.pyi +10 -0
  179. sage/symbolic/subring.py +1025 -0
  180. sage/symbolic/symengine.py +19 -0
  181. sage/symbolic/tests.py +40 -0
  182. 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