passagemath-symbolics 10.6.37__cp310-cp310-musllinux_1_2_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. passagemath_symbolics/__init__.py +3 -0
  2. passagemath_symbolics-10.6.37.dist-info/METADATA +187 -0
  3. passagemath_symbolics-10.6.37.dist-info/RECORD +171 -0
  4. passagemath_symbolics-10.6.37.dist-info/WHEEL +5 -0
  5. passagemath_symbolics-10.6.37.dist-info/top_level.txt +3 -0
  6. sage/all__sagemath_symbolics.py +17 -0
  7. sage/calculus/all.py +14 -0
  8. sage/calculus/calculus.py +2826 -0
  9. sage/calculus/desolvers.py +1866 -0
  10. sage/calculus/predefined.py +51 -0
  11. sage/calculus/tests.py +225 -0
  12. sage/calculus/var.cpython-310-x86_64-linux-gnu.so +0 -0
  13. sage/calculus/var.pyx +401 -0
  14. sage/dynamics/all__sagemath_symbolics.py +6 -0
  15. sage/dynamics/complex_dynamics/all.py +5 -0
  16. sage/dynamics/complex_dynamics/mandel_julia.py +765 -0
  17. sage/dynamics/complex_dynamics/mandel_julia_helper.cpython-310-x86_64-linux-gnu.so +0 -0
  18. sage/dynamics/complex_dynamics/mandel_julia_helper.pyx +1035 -0
  19. sage/ext/all__sagemath_symbolics.py +1 -0
  20. sage/ext_data/kenzo/CP2.txt +45 -0
  21. sage/ext_data/kenzo/CP3.txt +349 -0
  22. sage/ext_data/kenzo/CP4.txt +4774 -0
  23. sage/ext_data/kenzo/README.txt +49 -0
  24. sage/ext_data/kenzo/S4.txt +20 -0
  25. sage/ext_data/magma/latex/latex.m +1021 -0
  26. sage/ext_data/magma/latex/latex.spec +1 -0
  27. sage/ext_data/magma/sage/basic.m +356 -0
  28. sage/ext_data/magma/sage/sage.spec +1 -0
  29. sage/ext_data/magma/spec +9 -0
  30. sage/geometry/all__sagemath_symbolics.py +8 -0
  31. sage/geometry/hyperbolic_space/all.py +5 -0
  32. sage/geometry/hyperbolic_space/hyperbolic_coercion.py +743 -0
  33. sage/geometry/hyperbolic_space/hyperbolic_constants.py +5 -0
  34. sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +2409 -0
  35. sage/geometry/hyperbolic_space/hyperbolic_interface.py +206 -0
  36. sage/geometry/hyperbolic_space/hyperbolic_isometry.py +1082 -0
  37. sage/geometry/hyperbolic_space/hyperbolic_model.py +1502 -0
  38. sage/geometry/hyperbolic_space/hyperbolic_point.py +621 -0
  39. sage/geometry/riemannian_manifolds/all.py +7 -0
  40. sage/geometry/riemannian_manifolds/parametrized_surface3d.py +1632 -0
  41. sage/geometry/riemannian_manifolds/surface3d_generators.py +461 -0
  42. sage/interfaces/all__sagemath_symbolics.py +1 -0
  43. sage/interfaces/magma.py +3017 -0
  44. sage/interfaces/magma_free.py +92 -0
  45. sage/interfaces/maple.py +1397 -0
  46. sage/interfaces/mathematica.py +1345 -0
  47. sage/interfaces/mathics.py +1312 -0
  48. sage/interfaces/sympy.py +1398 -0
  49. sage/interfaces/sympy_wrapper.py +197 -0
  50. sage/interfaces/tides.py +938 -0
  51. sage/libs/all__sagemath_symbolics.py +6 -0
  52. sage/manifolds/all.py +7 -0
  53. sage/manifolds/calculus_method.py +555 -0
  54. sage/manifolds/catalog.py +437 -0
  55. sage/manifolds/chart.py +4019 -0
  56. sage/manifolds/chart_func.py +3419 -0
  57. sage/manifolds/continuous_map.py +2183 -0
  58. sage/manifolds/continuous_map_image.py +155 -0
  59. sage/manifolds/differentiable/affine_connection.py +2475 -0
  60. sage/manifolds/differentiable/all.py +1 -0
  61. sage/manifolds/differentiable/automorphismfield.py +1383 -0
  62. sage/manifolds/differentiable/automorphismfield_group.py +604 -0
  63. sage/manifolds/differentiable/bundle_connection.py +1445 -0
  64. sage/manifolds/differentiable/characteristic_cohomology_class.py +1840 -0
  65. sage/manifolds/differentiable/chart.py +1241 -0
  66. sage/manifolds/differentiable/curve.py +1028 -0
  67. sage/manifolds/differentiable/de_rham_cohomology.py +541 -0
  68. sage/manifolds/differentiable/degenerate.py +559 -0
  69. sage/manifolds/differentiable/degenerate_submanifold.py +1671 -0
  70. sage/manifolds/differentiable/diff_form.py +1658 -0
  71. sage/manifolds/differentiable/diff_form_module.py +1062 -0
  72. sage/manifolds/differentiable/diff_map.py +1315 -0
  73. sage/manifolds/differentiable/differentiable_submanifold.py +291 -0
  74. sage/manifolds/differentiable/examples/all.py +1 -0
  75. sage/manifolds/differentiable/examples/euclidean.py +2517 -0
  76. sage/manifolds/differentiable/examples/real_line.py +897 -0
  77. sage/manifolds/differentiable/examples/sphere.py +1186 -0
  78. sage/manifolds/differentiable/examples/symplectic_space.py +187 -0
  79. sage/manifolds/differentiable/examples/symplectic_space_test.py +40 -0
  80. sage/manifolds/differentiable/integrated_curve.py +4035 -0
  81. sage/manifolds/differentiable/levi_civita_connection.py +841 -0
  82. sage/manifolds/differentiable/manifold.py +4254 -0
  83. sage/manifolds/differentiable/manifold_homset.py +1826 -0
  84. sage/manifolds/differentiable/metric.py +3032 -0
  85. sage/manifolds/differentiable/mixed_form.py +1507 -0
  86. sage/manifolds/differentiable/mixed_form_algebra.py +559 -0
  87. sage/manifolds/differentiable/multivector_module.py +800 -0
  88. sage/manifolds/differentiable/multivectorfield.py +1520 -0
  89. sage/manifolds/differentiable/poisson_tensor.py +268 -0
  90. sage/manifolds/differentiable/pseudo_riemannian.py +755 -0
  91. sage/manifolds/differentiable/pseudo_riemannian_submanifold.py +1839 -0
  92. sage/manifolds/differentiable/scalarfield.py +1343 -0
  93. sage/manifolds/differentiable/scalarfield_algebra.py +472 -0
  94. sage/manifolds/differentiable/symplectic_form.py +910 -0
  95. sage/manifolds/differentiable/symplectic_form_test.py +220 -0
  96. sage/manifolds/differentiable/tangent_space.py +412 -0
  97. sage/manifolds/differentiable/tangent_vector.py +616 -0
  98. sage/manifolds/differentiable/tensorfield.py +4665 -0
  99. sage/manifolds/differentiable/tensorfield_module.py +963 -0
  100. sage/manifolds/differentiable/tensorfield_paral.py +2450 -0
  101. sage/manifolds/differentiable/tensorfield_paral_test.py +16 -0
  102. sage/manifolds/differentiable/vector_bundle.py +1728 -0
  103. sage/manifolds/differentiable/vectorfield.py +1717 -0
  104. sage/manifolds/differentiable/vectorfield_module.py +2445 -0
  105. sage/manifolds/differentiable/vectorframe.py +1832 -0
  106. sage/manifolds/family.py +270 -0
  107. sage/manifolds/local_frame.py +1490 -0
  108. sage/manifolds/manifold.py +3090 -0
  109. sage/manifolds/manifold_homset.py +452 -0
  110. sage/manifolds/operators.py +359 -0
  111. sage/manifolds/point.py +994 -0
  112. sage/manifolds/scalarfield.py +3718 -0
  113. sage/manifolds/scalarfield_algebra.py +629 -0
  114. sage/manifolds/section.py +3111 -0
  115. sage/manifolds/section_module.py +831 -0
  116. sage/manifolds/structure.py +229 -0
  117. sage/manifolds/subset.py +2764 -0
  118. sage/manifolds/subsets/all.py +1 -0
  119. sage/manifolds/subsets/closure.py +131 -0
  120. sage/manifolds/subsets/pullback.py +885 -0
  121. sage/manifolds/topological_submanifold.py +891 -0
  122. sage/manifolds/trivialization.py +733 -0
  123. sage/manifolds/utilities.py +1348 -0
  124. sage/manifolds/vector_bundle.py +1342 -0
  125. sage/manifolds/vector_bundle_fiber.py +332 -0
  126. sage/manifolds/vector_bundle_fiber_element.py +111 -0
  127. sage/matrix/all__sagemath_symbolics.py +1 -0
  128. sage/matrix/matrix_symbolic_dense.cpython-310-x86_64-linux-gnu.so +0 -0
  129. sage/matrix/matrix_symbolic_dense.pxd +6 -0
  130. sage/matrix/matrix_symbolic_dense.pyx +1022 -0
  131. sage/matrix/matrix_symbolic_sparse.cpython-310-x86_64-linux-gnu.so +0 -0
  132. sage/matrix/matrix_symbolic_sparse.pxd +6 -0
  133. sage/matrix/matrix_symbolic_sparse.pyx +1029 -0
  134. sage/modules/all__sagemath_symbolics.py +1 -0
  135. sage/modules/vector_callable_symbolic_dense.py +105 -0
  136. sage/modules/vector_symbolic_dense.py +116 -0
  137. sage/modules/vector_symbolic_sparse.py +118 -0
  138. sage/rings/all__sagemath_symbolics.py +4 -0
  139. sage/rings/asymptotic/all.py +6 -0
  140. sage/rings/asymptotic/asymptotic_expansion_generators.py +1485 -0
  141. sage/rings/asymptotic/asymptotic_ring.py +4858 -0
  142. sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py +4153 -0
  143. sage/rings/asymptotic/growth_group.py +5373 -0
  144. sage/rings/asymptotic/growth_group_cartesian.py +1400 -0
  145. sage/rings/asymptotic/term_monoid.py +5237 -0
  146. sage/rings/function_field/all__sagemath_symbolics.py +2 -0
  147. sage/rings/polynomial/all__sagemath_symbolics.py +1 -0
  148. sage/symbolic/all.py +15 -0
  149. sage/symbolic/assumptions.py +985 -0
  150. sage/symbolic/benchmark.py +93 -0
  151. sage/symbolic/callable.py +459 -0
  152. sage/symbolic/complexity_measures.py +35 -0
  153. sage/symbolic/constants.py +1287 -0
  154. sage/symbolic/expression_conversion_algebraic.py +310 -0
  155. sage/symbolic/expression_conversion_sympy.py +317 -0
  156. sage/symbolic/expression_conversions.py +1713 -0
  157. sage/symbolic/function_factory.py +355 -0
  158. sage/symbolic/integration/all.py +1 -0
  159. sage/symbolic/integration/external.py +270 -0
  160. sage/symbolic/integration/integral.py +1115 -0
  161. sage/symbolic/maxima_wrapper.py +162 -0
  162. sage/symbolic/operators.py +267 -0
  163. sage/symbolic/random_tests.py +462 -0
  164. sage/symbolic/relation.py +1907 -0
  165. sage/symbolic/ring.cpython-310-x86_64-linux-gnu.so +0 -0
  166. sage/symbolic/ring.pxd +5 -0
  167. sage/symbolic/ring.pyx +1396 -0
  168. sage/symbolic/subring.py +1025 -0
  169. sage/symbolic/symengine.py +19 -0
  170. sage/symbolic/tests.py +40 -0
  171. sage/symbolic/units.py +1470 -0
@@ -0,0 +1,2409 @@
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.structure.sage_object import SageObject
85
+ from sage.symbolic.constants import I
86
+ from sage.misc.lazy_attribute import lazy_attribute
87
+ from sage.rings.infinity import infinity
88
+ from sage.rings.cc import CC
89
+ from sage.rings.real_mpfr import RR
90
+ from sage.misc.lazy_import import lazy_import
91
+ lazy_import("sage.plot.arc", "arc")
92
+ lazy_import("sage.plot.line", "line")
93
+ lazy_import("sage.plot.arc", "arc")
94
+ lazy_import("sage.plot.bezier_path", "bezier_path")
95
+ from sage.symbolic.constants import pi
96
+ from sage.modules.free_module_element import vector
97
+ from sage.matrix.constructor import matrix
98
+ from sage.functions.other import real, imag
99
+ from sage.misc.functional import sqrt
100
+ from sage.functions.trig import arccos
101
+ from sage.functions.log import exp
102
+ from sage.functions.hyperbolic import sinh, cosh, arcsinh
103
+ from sage.symbolic.ring import SR
104
+ from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON
105
+
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
+
1082
+ x, y = (real(k.coordinates()) for k in self.ideal_endpoints())
1083
+ if x == infinity:
1084
+ M = matrix([[1, -2*y], [0, -1]])
1085
+ elif y == infinity:
1086
+ M = matrix([[1, -2*x], [0, -1]])
1087
+ else:
1088
+ M = matrix([[(x+y)/(y-x), -2*x*y/(y-x)], [2/(y-x), -(x+y)/(y-x)]])
1089
+ return self._model.get_isometry(M)
1090
+
1091
+ def plot(self, boundary=True, **options):
1092
+ r"""
1093
+ Plot ``self``.
1094
+
1095
+ EXAMPLES::
1096
+
1097
+ sage: UHP = HyperbolicPlane().UHP()
1098
+ sage: UHP.get_geodesic(0, 1).plot() # needs sage.plot
1099
+ Graphics object consisting of 2 graphics primitives
1100
+
1101
+ .. PLOT::
1102
+
1103
+ UHP = HyperbolicPlane().UHP()
1104
+ g = UHP.get_geodesic(0.0, 1.0).plot()
1105
+ sphinx_plot(g)
1106
+
1107
+ ::
1108
+
1109
+ sage: UHP.get_geodesic(I, 3+4*I).plot(linestyle='dashed', color='brown') # needs sage.plot
1110
+ Graphics object consisting of 2 graphics primitives
1111
+
1112
+ .. PLOT::
1113
+
1114
+ UHP = HyperbolicPlane().UHP()
1115
+ g = UHP.get_geodesic(I, 3+4*I).plot(linestyle='dashed', color='brown')
1116
+ sphinx_plot(g)
1117
+
1118
+ ::
1119
+
1120
+ sage: UHP.get_geodesic(1, infinity).plot(color='orange') # needs sage.plot
1121
+ Graphics object consisting of 2 graphics primitives
1122
+
1123
+ .. PLOT::
1124
+
1125
+ UHP = HyperbolicPlane().UHP()
1126
+ g = UHP.get_geodesic(1, infinity).plot(color='orange')
1127
+ sphinx_plot(g)
1128
+
1129
+ TESTS:
1130
+
1131
+ Plotting a line with ``boundary=True``. ::
1132
+
1133
+ sage: g = HyperbolicPlane().UHP().get_geodesic(0, I)
1134
+ sage: g.plot() # needs sage.plot
1135
+ Graphics object consisting of 2 graphics primitives
1136
+
1137
+ Plotting a line with ``boundary=False``. ::
1138
+
1139
+ sage: g = HyperbolicPlane().UHP().get_geodesic(0, I)
1140
+ sage: g.plot(boundary=False) # needs sage.plot
1141
+ Graphics object consisting of 1 graphics primitive
1142
+
1143
+ Plotting a circle with ``boundary=True``. ::
1144
+
1145
+ sage: g = HyperbolicPlane().UHP().get_geodesic(-3, 19)
1146
+ sage: g.plot() # needs sage.plot
1147
+ Graphics object consisting of 2 graphics primitives
1148
+
1149
+ Plotting a circle with ``boundary=False``. ::
1150
+
1151
+ sage: g = HyperbolicPlane().UHP().get_geodesic(3, 4)
1152
+ sage: g.plot(boundary=False) # needs sage.plot
1153
+ Graphics object consisting of 1 graphics primitive
1154
+ """
1155
+
1156
+ opts = {'axes': False, 'aspect_ratio': 1}
1157
+ opts.update(self.graphics_options())
1158
+ opts.update(options)
1159
+ end_1, end_2 = (CC(k.coordinates()) for k in self.endpoints())
1160
+ bd_1, bd_2 = (CC(k.coordinates()) for k in self.ideal_endpoints())
1161
+ if (abs(real(end_1) - real(end_2)) < EPSILON) \
1162
+ or CC(infinity) in [end_1, end_2]: # on same vertical line
1163
+ # If one of the endpoints is infinity, we replace it with a
1164
+ # large finite point
1165
+ if end_1 == CC(infinity):
1166
+ end_1 = (real(end_2), (imag(end_2) + 10))
1167
+ end_2 = (real(end_2), imag(end_2))
1168
+ elif end_2 == CC(infinity):
1169
+ end_2 = (real(end_1), (imag(end_1) + 10))
1170
+ end_1 = (real(end_1), imag(end_1))
1171
+ else:
1172
+ end_1 = (real(end_1), imag(end_1))
1173
+ end_2 = (real(end_2), imag(end_2))
1174
+ pic = bezier_path([[end_1, end_2]], **opts)
1175
+ if boundary:
1176
+ cent = min(bd_1, bd_2)
1177
+ bd_dict = {'bd_min': cent - 3, 'bd_max': cent + 3}
1178
+ bd_pic = self._model.get_background_graphic(**bd_dict)
1179
+ pic += bd_pic
1180
+ return pic
1181
+ else:
1182
+ center = (bd_1 + bd_2) / 2 # Circle center
1183
+ radius = abs(bd_1 - bd_2) / 2
1184
+ theta1 = CC(end_1 - center).arg()
1185
+ theta2 = CC(end_2 - center).arg()
1186
+ if abs(theta1 - theta2) < EPSILON:
1187
+ theta2 += pi
1188
+ pic = arc((real(center), imag(center)), radius,
1189
+ sector=(theta1, theta2), **opts)
1190
+ if boundary:
1191
+ # We want to draw a segment of the real line. The
1192
+ # computations below compute the projection of the
1193
+ # geodesic to the real line, and then draw a little
1194
+ # to the left and right of the projection.
1195
+ shadow_1, shadow_2 = (real(k) for k in [end_1, end_2])
1196
+ midpoint = (shadow_1 + shadow_2)/2
1197
+ length = abs(shadow_1 - shadow_2)
1198
+ bd_dict = {'bd_min': midpoint - length, 'bd_max': midpoint +
1199
+ length}
1200
+ bd_pic = self._model.get_background_graphic(**bd_dict)
1201
+ pic += bd_pic
1202
+ return pic
1203
+
1204
+ def ideal_endpoints(self):
1205
+ r"""
1206
+ Determine the ideal (boundary) endpoints of the complete
1207
+ hyperbolic geodesic corresponding to ``self``.
1208
+
1209
+ OUTPUT: list of 2 boundary points
1210
+
1211
+ EXAMPLES::
1212
+
1213
+ sage: UHP = HyperbolicPlane().UHP()
1214
+ sage: UHP.get_geodesic(I, 2*I).ideal_endpoints()
1215
+ [Boundary point in UHP 0,
1216
+ Boundary point in UHP +Infinity]
1217
+ sage: UHP.get_geodesic(1 + I, 2 + 4*I).ideal_endpoints()
1218
+ [Boundary point in UHP -sqrt(65) + 9,
1219
+ Boundary point in UHP sqrt(65) + 9]
1220
+ """
1221
+ start = self._start.coordinates()
1222
+ end = self._end.coordinates()
1223
+ x1, x2 = real(start), real(end)
1224
+ y1, y2 = imag(start), imag(end)
1225
+ M = self._model
1226
+ # infinity is the first endpoint, so the other ideal endpoint
1227
+ # is just the real part of the second coordinate
1228
+ if CC(start).is_infinity():
1229
+ return [M.get_point(start), M.get_point(x2)]
1230
+ # Same idea as above
1231
+ if CC(end).is_infinity():
1232
+ return [M.get_point(x1), M.get_point(end)]
1233
+ # We could also have a vertical line with two interior points
1234
+ if x1 == x2:
1235
+ return [M.get_point(x1), M.get_point(infinity)]
1236
+ # Otherwise, we have a semicircular arc in the UHP
1237
+ c = ((x1+x2)*(x2-x1) + (y1+y2)*(y2-y1)) / (2*(x2-x1))
1238
+ r = sqrt((c - x1)**2 + y1**2)
1239
+ return [M.get_point(c - r), M.get_point(c + r)]
1240
+
1241
+ def common_perpendicular(self, other):
1242
+ r"""
1243
+ Return the unique hyperbolic geodesic perpendicular to ``self``
1244
+ and ``other``, if such a geodesic exists; otherwise raise a
1245
+ :exc:`ValueError`.
1246
+
1247
+ INPUT:
1248
+
1249
+ - ``other`` -- a hyperbolic geodesic in current model
1250
+
1251
+ OUTPUT: a hyperbolic geodesic
1252
+
1253
+ EXAMPLES::
1254
+
1255
+ sage: UHP = HyperbolicPlane().UHP()
1256
+ sage: g = UHP.get_geodesic(2, 3)
1257
+ sage: h = UHP.get_geodesic(4, 5)
1258
+ sage: g.common_perpendicular(h)
1259
+ Geodesic in UHP from 1/2*sqrt(3) + 7/2 to -1/2*sqrt(3) + 7/2
1260
+
1261
+ .. PLOT::
1262
+
1263
+ UHP = HyperbolicPlane().UHP()
1264
+ g = UHP.get_geodesic(2.0, 3.0)
1265
+ h = UHP.get_geodesic(4.0, 5.0)
1266
+ p = g.common_perpendicular(h)
1267
+ sphinx_plot(g.plot(color='blue')+h.plot(color='blue')+p.plot(color='orange'))
1268
+
1269
+ It is an error to ask for the common perpendicular of two
1270
+ intersecting geodesics::
1271
+
1272
+ sage: g = UHP.get_geodesic(2, 4)
1273
+ sage: h = UHP.get_geodesic(3, infinity)
1274
+ sage: g.common_perpendicular(h)
1275
+ Traceback (most recent call last):
1276
+ ...
1277
+ ValueError: geodesics intersect; no common perpendicular exists
1278
+ """
1279
+
1280
+ # Make sure both are in the same model
1281
+ if other._model is not self._model:
1282
+ other = other.to_model(self._model)
1283
+
1284
+ A = self.reflection_involution()
1285
+ B = other.reflection_involution()
1286
+ C = A * B
1287
+ if C.classification() != 'hyperbolic':
1288
+ raise ValueError("geodesics intersect; " +
1289
+ "no common perpendicular exists")
1290
+ return C.fixed_point_set()
1291
+
1292
+ def intersection(self, other):
1293
+ r"""
1294
+ Return the point of intersection of ``self`` and ``other``
1295
+ (if such a point exists).
1296
+
1297
+ INPUT:
1298
+
1299
+ - ``other`` -- a hyperbolic geodesic in the current model
1300
+
1301
+ OUTPUT: list of hyperbolic points or a hyperbolic geodesic
1302
+
1303
+ EXAMPLES::
1304
+
1305
+ sage: UHP = HyperbolicPlane().UHP()
1306
+ sage: g = UHP.get_geodesic(3, 5)
1307
+ sage: h = UHP.get_geodesic(4, 7)
1308
+ sage: g.intersection(h)
1309
+ [Point in UHP 2/3*sqrt(-2) + 13/3]
1310
+
1311
+ .. PLOT::
1312
+
1313
+ UHP = HyperbolicPlane().UHP()
1314
+ g = UHP.get_geodesic(3, 5)
1315
+ h = UHP.get_geodesic(4, 7)
1316
+ P = g.intersection(h)
1317
+ pict = g.plot(color='red')+h.plot(color='red')
1318
+ sphinx_plot(pict)
1319
+
1320
+ If the given geodesics do not intersect, the function returns an
1321
+ empty list::
1322
+
1323
+ sage: g = UHP.get_geodesic(4, 5)
1324
+ sage: h = UHP.get_geodesic(6, 7)
1325
+ sage: g.intersection(h)
1326
+ []
1327
+
1328
+ .. PLOT::
1329
+
1330
+ UHP = HyperbolicPlane().UHP()
1331
+ g = UHP.get_geodesic(4.0, 5.0)
1332
+ h = UHP.get_geodesic(6.0, 7.0)
1333
+ sphinx_plot(g.plot() + h.plot())
1334
+
1335
+ If the given geodesics are asymptotically parallel, the function returns the common boundary point::
1336
+
1337
+ sage: g = UHP.get_geodesic(4, 5)
1338
+ sage: h = UHP.get_geodesic(5, 7)
1339
+ sage: g.intersection(h)
1340
+ [Boundary point in UHP 5.00000000000000]
1341
+
1342
+ .. PLOT::
1343
+
1344
+ UHP = HyperbolicPlane().UHP()
1345
+ g = UHP.get_geodesic(4.0, 5.0)
1346
+ h = UHP.get_geodesic(6.0, 7.0)
1347
+ sphinx_plot(g.plot() + h.plot())
1348
+
1349
+ If the given geodesics are identical, return that
1350
+ geodesic::
1351
+
1352
+ sage: g = UHP.get_geodesic(4 + I, 18*I)
1353
+ sage: h = UHP.get_geodesic(4 + I, 18*I)
1354
+ sage: g.intersection(h)
1355
+ Geodesic in UHP from I + 4 to 18*I
1356
+
1357
+ TESTS:
1358
+
1359
+ sage: UHP = HyperbolicPlane().UHP()
1360
+ sage: g1 = UHP.get_geodesic(2*QQbar.gen(), 5)
1361
+ sage: g2 = UHP.get_geodesic(-1/2, Infinity)
1362
+ sage: g1.intersection(g2)
1363
+ []
1364
+
1365
+ sage: UHP = HyperbolicPlane().UHP() #case Ia
1366
+ sage: UHP = HyperbolicPlane().UHP()
1367
+ sage: g1=UHP.get_geodesic(-1,I)
1368
+ sage: g2=UHP.get_geodesic(0,2)
1369
+ sage: g1.intersection(g2)
1370
+ []
1371
+
1372
+ sage: UHP = HyperbolicPlane().UHP() #case Ib
1373
+ sage: g1=UHP.get_geodesic(-1,I)
1374
+ sage: g2=UHP.get_geodesic(1/2,1/2+2*I)
1375
+ sage: g1.intersection(g2)
1376
+ []
1377
+
1378
+ sage: UHP = HyperbolicPlane().UHP() #case IIa
1379
+ sage: g1=UHP.get_geodesic(-1,+1)
1380
+ sage: g2=UHP.get_geodesic(-1,2)
1381
+ sage: g1.intersection(g2)
1382
+ [Boundary point in UHP -1.00000000000000]
1383
+
1384
+ sage: UHP = HyperbolicPlane().UHP() #case IIb
1385
+ sage: g1=UHP.get_geodesic(-1,+Infinity)
1386
+ sage: g2=UHP.get_geodesic(+1,+Infinity)
1387
+ sage: g1.intersection(g2)
1388
+ [Boundary point in UHP +infinity]
1389
+
1390
+ sage: UHP = HyperbolicPlane().UHP() #case IIc
1391
+ sage: g1=UHP.get_geodesic(-1,-1+I)
1392
+ sage: g2=UHP.get_geodesic(+1,+1+I)
1393
+ sage: g1.intersection(g2)
1394
+ []
1395
+
1396
+ sage: UHP = HyperbolicPlane().UHP() #case IId
1397
+ sage: g1=UHP.get_geodesic(-1,+1)
1398
+ sage: g2=UHP.get_geodesic(-1,-1+2*I)
1399
+ sage: g1.intersection(g2)
1400
+ [Boundary point in UHP -1.00000000000000]
1401
+
1402
+ sage: UHP = HyperbolicPlane().UHP() #case IIIa
1403
+ sage: g1=UHP.get_geodesic(-1,I)
1404
+ sage: g2=UHP.get_geodesic(+1,(+cos(pi/3)+I*sin(pi/3)))
1405
+ sage: g1.intersection(g2)
1406
+ []
1407
+
1408
+ sage: UHP = HyperbolicPlane().UHP() #case IIIb
1409
+ sage: g1=UHP.get_geodesic(I,2*I)
1410
+ sage: g2=UHP.get_geodesic(3*I,4*I)
1411
+ sage: g1.intersection(g2)
1412
+ []
1413
+
1414
+ sage: UHP = HyperbolicPlane().UHP() #case IVa
1415
+ sage: g1=UHP.get_geodesic(3*I,Infinity)
1416
+ sage: g2=UHP.get_geodesic(2*I,4*I)
1417
+ sage: g1.intersection(g2)
1418
+ Geodesic in UHP from 3.00000000000000*I to 4.00000000000000*I
1419
+
1420
+ sage: UHP = HyperbolicPlane().UHP() #case IVb
1421
+ sage: g1=UHP.get_geodesic(I,3*I)
1422
+ sage: g2=UHP.get_geodesic(2*I,4*I)
1423
+ sage: g1.intersection(g2)
1424
+ Geodesic in UHP from 2.00000000000000*I to 3.00000000000000*I
1425
+
1426
+ sage: UHP = HyperbolicPlane().UHP() #case IVc
1427
+ sage: g1=UHP.get_geodesic(2*I,infinity)
1428
+ sage: g2=UHP.get_geodesic(3*I,infinity)
1429
+ sage: g1.intersection(g2)
1430
+ Geodesic in UHP from 3.00000000000000*I to +infinity
1431
+ """
1432
+
1433
+ UHP = self.model()
1434
+ # Both geodesic need to be UHP geodesics for this to work
1435
+ if other.model() != UHP:
1436
+ other = other.to_model(UHP)
1437
+ # Get endpoints and ideal endpoints
1438
+ i_start_1, i_end_1 = sorted(self.ideal_endpoints(), key=str)
1439
+ i_start_2, i_end_2 = sorted(other.ideal_endpoints(), key=str)
1440
+ start_1, end_1 = (CC(x.coordinates()) for x in self.endpoints())
1441
+ start_2, end_2 = (CC(x.coordinates()) for x in other.endpoints())
1442
+ # sort the geodesic endpoints according to start_1.real() < end_1.real() and if start_1.real() == end_1.real()
1443
+ # then start_1.imag() < end_1.imag()
1444
+ if start_1.real() > end_1.real(): # enforce
1445
+ start_1, end_1 = end_1, start_1
1446
+ elif start_1.real() == end_1.real():
1447
+ if start_1.imag() > end_1.imag():
1448
+ start_1, end_1 = end_1, start_1
1449
+ # sort the geodesic endpoints according to start_2.real() < end_2.real() and if start_2.real() == end_2.real()
1450
+ # then start_2.imag() < end_2.imag()
1451
+ if start_2.real() > end_2.real():
1452
+ start_2, end_2 = end_2, start_2
1453
+ elif start_2.real() == end_2.real():
1454
+ if start_2.imag() > end_2.imag():
1455
+ start_2, end_2 = end_2, start_2
1456
+ if i_start_1 == i_start_2 and i_end_1 == i_end_2:
1457
+ # Unoriented segments lie on the same complete geodesic
1458
+ if start_1 == start_2 and end_1 == end_2:
1459
+ return self
1460
+ if start_1.real() == end_1.real() or end_1.real().is_infinity():
1461
+ # Both geodesics are vertical
1462
+ if start_2.imag() < start_1.imag():
1463
+ # make sure always start_1.imag() <= start_2.imag()
1464
+ start_1, start_2 = start_2, start_1
1465
+ end_1, end_2 = end_2, end_1
1466
+ if end_1 == start_2:
1467
+ return [UHP.get_point(end_1)]
1468
+ elif end_1.real().is_infinity() and end_2.real().is_infinity():
1469
+ return UHP.get_geodesic(start_2, end_2)
1470
+ elif end_1.imag() < start_2.imag():
1471
+ return []
1472
+ else:
1473
+ return UHP.get_geodesic(start_2, end_1)
1474
+ else:
1475
+ # Neither geodesic is vertical
1476
+ # make sure always start_1.real() <= start_2.real()
1477
+ if start_2.real() < start_1.real():
1478
+ start_1, start_2 = start_2, start_1
1479
+ end_1, end_2 = end_2, end_1
1480
+ if end_1 == start_2:
1481
+ return [UHP.get_point(end_1)]
1482
+ elif end_1.real() < start_2.real():
1483
+ return []
1484
+ else:
1485
+ return UHP.get_geodesic(start_2, end_1)
1486
+ else:
1487
+ # Both segments do not have the same complete geodesic
1488
+ # make sure always start_1.real() <= start_2.real()
1489
+ if start_2.real() < start_1.real():
1490
+ start_1, start_2 = start_2, start_1
1491
+ end_1, end_2 = end_2, end_1
1492
+ if self.is_asymptotically_parallel(other):
1493
+ # asymptotic parallel
1494
+ if start_1 == start_2:
1495
+ return [UHP.get_point(start_1)]
1496
+ elif end_1 == start_2 or end_1 == end_2:
1497
+ return [UHP.get_point(end_1)]
1498
+ else:
1499
+ return []
1500
+ else:
1501
+ A = self.reflection_involution()
1502
+ B = other.reflection_involution()
1503
+ C = A * B
1504
+ if C.classification() in ['hyperbolic', 'parabolic']:
1505
+ return []
1506
+ else:
1507
+ # the fixed point needs not to lie in both segments of geodesic
1508
+ if end_1 == start_2:
1509
+ return [UHP().get_point(end_1)]
1510
+ else:
1511
+ P = CC(C.fixed_point_set()[0].coordinates())
1512
+ if start_1.real() <= P.real() <= end_1.real() and start_2.real() <= P.real() <= end_2.real():
1513
+ return C.fixed_point_set()
1514
+ else:
1515
+ return []
1516
+
1517
+ def perpendicular_bisector(self): # UHP
1518
+ r"""
1519
+ Return the perpendicular bisector of the hyperbolic geodesic ``self``
1520
+ if that geodesic has finite length.
1521
+
1522
+ EXAMPLES::
1523
+
1524
+ sage: UHP = HyperbolicPlane().UHP()
1525
+
1526
+ sage: # needs scipy
1527
+ sage: g = UHP.random_geodesic()
1528
+ sage: h = g.perpendicular_bisector().complete()
1529
+ sage: c = lambda x: x.coordinates()
1530
+ sage: bool(c(g.intersection(h)[0]) - c(g.midpoint()) < 10**-9)
1531
+ True
1532
+
1533
+ ::
1534
+
1535
+ sage: # needs scipy
1536
+ sage: g = UHP.get_geodesic(1 + I, 2 + 0.5*I)
1537
+ sage: h = g.perpendicular_bisector().complete()
1538
+ sage: show(g.plot(color='blue') + h.plot(color='orange'))
1539
+
1540
+ .. PLOT::
1541
+
1542
+ UHP = HyperbolicPlane().UHP()
1543
+ g = UHP.get_geodesic(1+I,2+0.5*I)
1544
+ h = g.perpendicular_bisector().complete()
1545
+ sphinx_plot(g.plot(color='blue')+h.plot(color='orange'))
1546
+
1547
+ Infinite geodesics cannot be bisected::
1548
+
1549
+ sage: UHP.get_geodesic(0, 1).perpendicular_bisector()
1550
+ Traceback (most recent call last):
1551
+ ...
1552
+ ValueError: the length must be finite
1553
+
1554
+ TESTS:
1555
+
1556
+ Check the result is independent of the order (:issue:`29936`)::
1557
+
1558
+ sage: # needs scipy
1559
+ sage: def bisector_gets_midpoint(a, b):
1560
+ ....: UHP = HyperbolicPlane().UHP()
1561
+ ....: g = UHP.get_geodesic(a, b)
1562
+ ....: p = g.perpendicular_bisector()
1563
+ ....: x = g.intersection(p)[0]
1564
+ ....: m = g.midpoint()
1565
+ ....: return bool(x.dist(m) < 1e-9)
1566
+ sage: c, d, e = CC(1, 1), CC(2, 1), CC(2, 0.5)
1567
+ sage: pairs = [(c, d), (d, c), (c, e), (e, c), (d, e), (e, d)]
1568
+ sage: all(bisector_gets_midpoint(a, b) for a, b in pairs)
1569
+ True
1570
+ """
1571
+ if self.length() == infinity:
1572
+ raise ValueError("the length must be finite")
1573
+ start = self._start.coordinates()
1574
+ end = self._end.coordinates()
1575
+ # The complete geodesic p1 -> p2 always returns p1 < p2,
1576
+ # so we might need to swap start and end
1577
+ if ((real(start - end) > EPSILON) or
1578
+ (abs(real(start - end)) < EPSILON and
1579
+ imag(start - end) > 0)):
1580
+ start, end = end, start
1581
+ S = self.complete()._to_std_geod(start)
1582
+ d = self._model._dist_points(start, end) / 2
1583
+ T1 = matrix([[exp(d/2), 0], [0, exp(-d/2)]])
1584
+ s2 = sqrt(2) / 2
1585
+ T2 = matrix([[s2, -s2], [s2, s2]])
1586
+ isom_mtrx = S.inverse() * (T1 * T2) * S
1587
+ # We need to clean this matrix up.
1588
+ if (isom_mtrx - isom_mtrx.conjugate()).norm() < 5 * EPSILON:
1589
+ # Imaginary part is small.
1590
+ isom_mtrx = (isom_mtrx + isom_mtrx.conjugate()) / 2
1591
+ # Set it to its real part.
1592
+ H = self._model._Isometry(self._model, isom_mtrx, check=False)
1593
+ return self._model.get_geodesic(H(self._start), H(self._end))
1594
+
1595
+ def midpoint(self): # UHP
1596
+ r"""
1597
+ Return the (hyperbolic) midpoint of ``self`` if it exists.
1598
+
1599
+ EXAMPLES::
1600
+
1601
+ sage: UHP = HyperbolicPlane().UHP()
1602
+ sage: g = UHP.random_geodesic()
1603
+ sage: m = g.midpoint()
1604
+ sage: d1 = UHP.dist(m, g.start())
1605
+ sage: d2 = UHP.dist(m, g.end())
1606
+ sage: bool(abs(d1 - d2) < 10**-9)
1607
+ True
1608
+
1609
+ Infinite geodesics have no midpoint::
1610
+
1611
+ sage: UHP.get_geodesic(0, 2).midpoint()
1612
+ Traceback (most recent call last):
1613
+ ...
1614
+ ValueError: the length must be finite
1615
+
1616
+ TESTS:
1617
+
1618
+ This checks :issue:`20330` so that geodesics defined by symbolic
1619
+ expressions do not generate runtime errors. ::
1620
+
1621
+ sage: g=HyperbolicPlane().UHP().get_geodesic(-1+I,1+I)
1622
+ sage: point = g.midpoint(); point
1623
+ Point in UHP -1/2*(sqrt(2)*...
1624
+ sage: QQbar(point.coordinates()).radical_expression() # long time
1625
+ I*sqrt(2)
1626
+
1627
+ Check that floating points remain floating points
1628
+ in :meth:`midpoint`::
1629
+
1630
+ sage: UHP = HyperbolicPlane().UHP()
1631
+ sage: g = UHP.get_geodesic(CC(0,1), CC(2,2))
1632
+ sage: g.midpoint()
1633
+ Point in UHP 0.666666666666667 + 1.69967317119760*I
1634
+ sage: parent(g.midpoint().coordinates())
1635
+ Complex Field with 53 bits of precision
1636
+
1637
+ Check that the midpoint is independent of the order (:issue:`29936`)::
1638
+
1639
+ sage: g = UHP.get_geodesic(1+I, 2+0.5*I)
1640
+ sage: h = UHP.get_geodesic(2+0.5*I, 1+I)
1641
+ sage: abs(g.midpoint().coordinates() - h.midpoint().coordinates()) < 1e-9
1642
+ True
1643
+
1644
+ sage: g = UHP.get_geodesic(2+I, 2+0.5*I)
1645
+ sage: h = UHP.get_geodesic(2+0.5*I, 2+I)
1646
+ sage: abs(g.midpoint().coordinates() - h.midpoint().coordinates()) < 1e-9
1647
+ True
1648
+ """
1649
+ from sage.matrix.matrix_symbolic_dense import Matrix_symbolic_dense
1650
+ if self.length() == infinity:
1651
+ raise ValueError("the length must be finite")
1652
+
1653
+ start = self._start.coordinates()
1654
+ end = self._end.coordinates()
1655
+ d = self._model._dist_points(start, end) / 2
1656
+ # The complete geodesic p1 -> p2 always returns p1 < p2,
1657
+ # so we might need to swap start and end
1658
+ if ((real(start - end) > EPSILON) or
1659
+ (abs(real(start - end)) < EPSILON and
1660
+ imag(start - end) > 0)):
1661
+ start, end = end, start
1662
+ S = self.complete()._to_std_geod(start)
1663
+
1664
+ # If the matrix is symbolic then needs to be simplified in order to
1665
+ # make the calculations easier for the symbolic calculus module.
1666
+ if isinstance(S, Matrix_symbolic_dense):
1667
+ S = S.simplify_full().simplify_full()
1668
+ S_1 = S.inverse()
1669
+ T = matrix([[exp(d), 0], [0, 1]])
1670
+ M = S_1 * T * S
1671
+ P_3 = moebius_transform(M, start)
1672
+ return self._model.get_point(P_3)
1673
+
1674
+ def angle(self, other): # UHP
1675
+ r"""
1676
+ Return the angle between the completions of any two given
1677
+ geodesics if they intersect.
1678
+
1679
+ INPUT:
1680
+
1681
+ - ``other`` -- a hyperbolic geodesic in the UHP model
1682
+
1683
+ OUTPUT: the angle in radians between the two given geodesics
1684
+
1685
+ EXAMPLES::
1686
+
1687
+ sage: UHP = HyperbolicPlane().UHP()
1688
+ sage: g = UHP.get_geodesic(2, 4)
1689
+ sage: h = UHP.get_geodesic(3, 3 + I)
1690
+ sage: g.angle(h)
1691
+ 1/2*pi
1692
+ sage: numerical_approx(g.angle(h))
1693
+ 1.57079632679490
1694
+
1695
+ .. PLOT::
1696
+
1697
+ UHP = HyperbolicPlane().UHP()
1698
+ g = UHP.get_geodesic(2, 4)
1699
+ h = UHP.get_geodesic(3, 3 + I)
1700
+ sphinx_plot(g.plot()+h.plot())
1701
+
1702
+ If the geodesics are identical, return angle 0::
1703
+
1704
+ sage: g.angle(g)
1705
+ 0
1706
+
1707
+ It is an error to ask for the angle of two geodesics that do not
1708
+ intersect::
1709
+
1710
+ sage: g = UHP.get_geodesic(2, 4)
1711
+ sage: h = UHP.get_geodesic(5, 7)
1712
+ sage: g.angle(h)
1713
+ Traceback (most recent call last):
1714
+ ...
1715
+ ValueError: geodesics do not intersect
1716
+
1717
+ TESTS:
1718
+
1719
+ Points as parameters raise an error. ::
1720
+
1721
+ sage: g = HyperbolicPlane().UHP().get_geodesic(I, I)
1722
+ sage: h = HyperbolicPlane().UHP().get_geodesic(-1, 1)
1723
+ sage: g.angle(h)
1724
+ Traceback (most recent call last):
1725
+ ...
1726
+ ValueError: intersecting geodesic is a point
1727
+ sage: h.angle(g)
1728
+ Traceback (most recent call last):
1729
+ ...
1730
+ ValueError: intersecting geodesic is a point
1731
+
1732
+ sage: g = HyperbolicPlane().UHP().get_geodesic(I, I)
1733
+ sage: h = HyperbolicPlane().UHP().get_geodesic(0, infinity)
1734
+ sage: g.angle(h)
1735
+ Traceback (most recent call last):
1736
+ ...
1737
+ ValueError: intersecting geodesic is a point
1738
+ sage: h.angle(g)
1739
+ Traceback (most recent call last):
1740
+ ...
1741
+ ValueError: intersecting geodesic is a point
1742
+
1743
+ Intersections in boundary points raise an error. ::
1744
+
1745
+ sage: g = HyperbolicPlane().UHP().get_geodesic(1, 3)
1746
+ sage: h = HyperbolicPlane().UHP().get_geodesic(1, 2)
1747
+ sage: g.angle(h)
1748
+ Traceback (most recent call last):
1749
+ ...
1750
+ ValueError: geodesics do not intersect
1751
+ sage: h.angle(g)
1752
+ Traceback (most recent call last):
1753
+ ...
1754
+ ValueError: geodesics do not intersect
1755
+
1756
+ sage: g = HyperbolicPlane().UHP().get_geodesic(1, 2)
1757
+ sage: h = HyperbolicPlane().UHP().get_geodesic(1, Infinity)
1758
+ sage: g.angle(h)
1759
+ Traceback (most recent call last):
1760
+ ...
1761
+ ValueError: geodesics do not intersect
1762
+ sage: h.angle(g)
1763
+ Traceback (most recent call last):
1764
+ ...
1765
+ ValueError: geodesics do not intersect
1766
+
1767
+ Parallel lines raise an error. ::
1768
+
1769
+ sage: g = HyperbolicPlane().UHP().get_geodesic(-2, -2 + 4*I)
1770
+ sage: h = HyperbolicPlane().UHP().get_geodesic(9, Infinity)
1771
+ sage: g.angle(h)
1772
+ Traceback (most recent call last):
1773
+ ...
1774
+ ValueError: geodesics do not intersect
1775
+ sage: h.angle(g)
1776
+ Traceback (most recent call last):
1777
+ ...
1778
+ ValueError: geodesics do not intersect
1779
+
1780
+ Non-intersecting circles raise an error. ::
1781
+
1782
+ sage: g = HyperbolicPlane().UHP().get_geodesic(-2, -1)
1783
+ sage: h = HyperbolicPlane().UHP().get_geodesic( 2, 1)
1784
+ sage: g.angle(h)
1785
+ Traceback (most recent call last):
1786
+ ...
1787
+ ValueError: geodesics do not intersect
1788
+ sage: h.angle(g)
1789
+ Traceback (most recent call last):
1790
+ ...
1791
+ ValueError: geodesics do not intersect
1792
+
1793
+ Non-intersecting line and circle raise an error. ::
1794
+
1795
+ sage: g = HyperbolicPlane().UHP().get_geodesic(-2, -2 + 4*I)
1796
+ sage: h = HyperbolicPlane().UHP().get_geodesic( 7, 9)
1797
+ sage: g.angle(h)
1798
+ Traceback (most recent call last):
1799
+ ...
1800
+ ValueError: geodesics do not intersect
1801
+ sage: h.angle(g)
1802
+ Traceback (most recent call last):
1803
+ ...
1804
+ ValueError: geodesics do not intersect
1805
+
1806
+ Non-complete equal circles yield angle 0. ::
1807
+
1808
+ sage: g = HyperbolicPlane().UHP().get_geodesic(-1, I)
1809
+ sage: h = HyperbolicPlane().UHP().get_geodesic(I, 1)
1810
+ sage: g.angle(h)
1811
+ 0
1812
+ sage: h.angle(g)
1813
+ 0
1814
+
1815
+ Complete equal lines yield angle 0. ::
1816
+
1817
+ sage: g = HyperbolicPlane().UHP().get_geodesic(4, Infinity)
1818
+ sage: h = HyperbolicPlane().UHP().get_geodesic(4, Infinity)
1819
+ sage: g.angle(h)
1820
+ 0
1821
+ sage: h.angle(g)
1822
+ 0
1823
+
1824
+ Non-complete equal lines yield angle 0. ::
1825
+
1826
+ sage: g = HyperbolicPlane().UHP().get_geodesic(1 + I, 1 + 2*I)
1827
+ sage: h = HyperbolicPlane().UHP().get_geodesic(1 + 3*I, 1 + 4*I)
1828
+ sage: g.angle(h)
1829
+ 0
1830
+ sage: h.angle(g)
1831
+ 0
1832
+
1833
+ Angle between two complete circles. ::
1834
+
1835
+ sage: g = HyperbolicPlane().UHP().get_geodesic(0, 2)
1836
+ sage: h = HyperbolicPlane().UHP().get_geodesic(1, 3)
1837
+ sage: g.angle(h)
1838
+ 1/3*pi
1839
+ sage: h.angle(g)
1840
+ 1/3*pi
1841
+
1842
+ Angle between two non-intersecting circles whose completion intersects. ::
1843
+
1844
+ sage: g = HyperbolicPlane().UHP().get_geodesic(-2, 2*I)
1845
+ sage: h = HyperbolicPlane().UHP().get_geodesic(-1, 1 + 2*I)
1846
+ sage: g.angle(h)
1847
+ arccos(7/8)
1848
+ sage: h.angle(g)
1849
+ arccos(7/8)
1850
+
1851
+ Angle between circle and line. Note that ``1/2*sqrt(2)`` equals
1852
+ ``1/4*pi``. ::
1853
+
1854
+ sage: g = HyperbolicPlane().UHP().get_geodesic( 0, Infinity)
1855
+ sage: h = HyperbolicPlane().UHP().get_geodesic(-1, 1)
1856
+ sage: g.angle(h)
1857
+ 1/2*pi
1858
+ sage: h.angle(g)
1859
+ 1/2*pi
1860
+
1861
+ sage: g = HyperbolicPlane().UHP().get_geodesic(1, 1 + I)
1862
+ sage: h = HyperbolicPlane().UHP().get_geodesic(-sqrt(2), sqrt(2))
1863
+ sage: g.angle(h)
1864
+ 1/4*pi
1865
+ sage: h.angle(g)
1866
+ 1/4*pi
1867
+
1868
+ Angle is unoriented, as opposed to oriented. ::
1869
+
1870
+ sage: g = HyperbolicPlane().UHP().get_geodesic(0, I)
1871
+ sage: h1 = HyperbolicPlane().UHP().get_geodesic(-1, 2)
1872
+ sage: h2 = HyperbolicPlane().UHP().get_geodesic(1, -2)
1873
+ sage: g.angle(h1)
1874
+ arccos(1/3)
1875
+ sage: h1.angle(g)
1876
+ arccos(1/3)
1877
+ sage: g.angle(h2)
1878
+ arccos(1/3)
1879
+ sage: h2.angle(g)
1880
+ arccos(1/3)
1881
+ """
1882
+
1883
+ if self.is_parallel(other):
1884
+ raise ValueError("geodesics do not intersect")
1885
+
1886
+ if other._model is not self._model:
1887
+ other = other.to_model(self._model)
1888
+
1889
+ # Check if any of the geodesics is a point.
1890
+ a1, a2 = self.start().coordinates(), self.end().coordinates()
1891
+ b1, b2 = other.start().coordinates(), other.end().coordinates()
1892
+ if abs(a2 - a1) < EPSILON or abs(b2 - b1) < EPSILON:
1893
+ raise ValueError("intersecting geodesic is a point")
1894
+
1895
+ p1, p2 = (p.coordinates() for p in self.ideal_endpoints())
1896
+ q1, q2 = (p.coordinates() for p in other.ideal_endpoints())
1897
+
1898
+ # Check if both geodesics are lines. All lines intersect at
1899
+ # ``Infinity``, but the angle is always zero.
1900
+ if infinity in [p1, p2] and infinity in [q1, q2]:
1901
+ return 0
1902
+
1903
+ # Check if the geodesics are approximately equal. This must be
1904
+ # done to prevent addition of ``infinity`` and ``-infinity``.
1905
+ v = (abs(p1 - q1) < EPSILON and abs(p2 - q2) < EPSILON)
1906
+ w = (abs(p1 - q2) < EPSILON and abs(p2 - q1) < EPSILON)
1907
+ if v or w:
1908
+ return 0
1909
+
1910
+ # Next, check if exactly one geodesic is a line. If this is the
1911
+ # case, we will swap the values of the four points around until
1912
+ # ``p1`` is zero, ``p2`` is ``infinity``...
1913
+ #
1914
+ # First, swap ``p`` and ``q`` if any ideal endpoint of ``other``
1915
+ # is ``infinity``.
1916
+ if infinity in [q1, q2]:
1917
+ p1, p2, q1, q2 = q1, q2, p1, p2
1918
+ # Then, if ``p1`` is infinity, swap ``p1`` and ``p2``. This
1919
+ # ensures that if any element of ``{p1, p2}`` is ``infinity``,
1920
+ # then that element is now ``p2``.
1921
+ if p1 == infinity:
1922
+ p1, p2 = p2, p1
1923
+
1924
+ # If ``p2`` is ``infinity``, we need to apply a translation to
1925
+ # both geodesics that moves the first geodesic onto the
1926
+ # imaginary line. If ``p2`` is not ``infinity``, or,
1927
+ # equivalently, the first geodesic is not a line, then we need
1928
+ # to transform the hyperbolic plane so that the first geodesic
1929
+ # is the imaginary line.
1930
+ if p2 == infinity:
1931
+ q1 = q1 - p1
1932
+ q2 = q2 - p1
1933
+ p1 = 0
1934
+ if p2 != infinity:
1935
+ # From now on, we may assume that ``p1``, ``p2``, ``q1``,
1936
+ # ``q2`` are not ``infinity``...
1937
+
1938
+ # Transform into a line.
1939
+ t = HyperbolicGeodesicUHP._crossratio_matrix(p1, (p1 + p2) / 2, p2)
1940
+ q1, q2 = (moebius_transform(t, q) for q in [q1, q2])
1941
+
1942
+ # Calculate the angle.
1943
+ return arccos(abs(q1 + q2) / abs(q2 - q1))
1944
+
1945
+ ##################
1946
+ # Helper methods #
1947
+ ##################
1948
+
1949
+ @staticmethod
1950
+ def _get_B(a):
1951
+ r"""
1952
+ Helper function to get an appropriate matrix transforming
1953
+ (0,1,inf) -> (0,I,inf) based on the type of a
1954
+
1955
+ INPUT:
1956
+
1957
+ - ``a`` -- an element to identify the class of the resulting matrix
1958
+
1959
+ EXAMPLES::
1960
+
1961
+ sage: UHP = HyperbolicPlane().UHP()
1962
+ sage: g = UHP.random_geodesic()
1963
+ sage: B = g._get_B(CDF.an_element()); B
1964
+ [ 1.0 0.0]
1965
+ [ 0.0 -1.0*I]
1966
+ sage: type(B)
1967
+ <class 'sage.matrix.matrix_complex_double_dense.Matrix_complex_double_dense'>
1968
+
1969
+ ::
1970
+
1971
+ sage: B = g._get_B(SR(1)); B
1972
+ [ 1 0]
1973
+ [ 0 -I]
1974
+ sage: type(B)
1975
+ <class 'sage.matrix.matrix_symbolic_dense.Matrix_symbolic_dense'>
1976
+
1977
+ ::
1978
+
1979
+ sage: B = g._get_B(complex(1)); B
1980
+ [ 1.0 0.0]
1981
+ [ 0.0 -1.0*I]
1982
+ sage: type(B)
1983
+ <class 'sage.matrix.matrix_complex_double_dense.Matrix_complex_double_dense'>
1984
+
1985
+ ::
1986
+
1987
+ sage: B = g._get_B(QQbar(1+I)); B
1988
+ [ 1 0]
1989
+ [ 0 -I]
1990
+ sage: type(B[1,1])
1991
+ <class 'sage.rings.qqbar.AlgebraicNumber'>
1992
+ sage: type(B)
1993
+ <class 'sage.matrix.matrix_generic_dense.Matrix_generic_dense'>
1994
+ """
1995
+ from sage.structure.element import Element
1996
+ from sage.symbolic.expression import Expression
1997
+ from sage.rings.complex_double import CDF
1998
+
1999
+ if isinstance(a, (int, float, complex)): # Python number
2000
+ a = CDF(a)
2001
+
2002
+ if isinstance(a, Expression): # symbolic
2003
+ P = SR
2004
+ zero = SR.zero()
2005
+ one = SR.one()
2006
+ I = SR("I")
2007
+ elif isinstance(a, Element): # Sage number
2008
+ P = a.parent()
2009
+ zero = P.zero()
2010
+ one = P.one()
2011
+ I = P.gen()
2012
+ if I.is_one() or (I*I).is_one() or not (-I*I).is_one():
2013
+ raise ValueError("invalid number")
2014
+ else:
2015
+ raise ValueError("not a complex number")
2016
+
2017
+ return matrix(P, 2, [one, zero, zero, -I])
2018
+
2019
+ def _to_std_geod(self, p):
2020
+ r"""
2021
+ Given the coordinates of a geodesic in hyperbolic space, return the
2022
+ hyperbolic isometry that sends that geodesic to the geodesic
2023
+ through 0 and infinity that also sends the point ``p`` to `i`.
2024
+
2025
+ INPUT:
2026
+
2027
+ - ``p`` -- the coordinates of the
2028
+
2029
+ EXAMPLES::
2030
+
2031
+ sage: UHP = HyperbolicPlane().UHP()
2032
+ sage: (p1, p2) = [UHP.random_point() for k in range(2)]
2033
+ sage: g = UHP.get_geodesic(p1, p2)
2034
+ sage: m = g.midpoint()
2035
+ sage: A = g._to_std_geod(m.coordinates()) # Send midpoint to I.
2036
+ sage: A = UHP.get_isometry(A)
2037
+ sage: [s, e]= g.complete().endpoints()
2038
+ sage: bool(abs(A(s).coordinates()) < 10**-9)
2039
+ True
2040
+ sage: bool(abs(A(m).coordinates() - I) < 10**-9)
2041
+ True
2042
+ sage: bool(abs(A(e).coordinates()) > 10**9)
2043
+ True
2044
+
2045
+ TESTS:
2046
+
2047
+ Check that floating points remain floating points through this method::
2048
+
2049
+ sage: H = HyperbolicPlane()
2050
+ sage: g = H.UHP().get_geodesic(CC(0,1), CC(2,2))
2051
+ sage: gc = g.complete()
2052
+ sage: parent(gc._to_std_geod(g.start().coordinates()))
2053
+ Full MatrixSpace of 2 by 2 dense matrices over Complex Field
2054
+ with 53 bits of precision
2055
+ """
2056
+ s, e = [k.coordinates() for k in self.complete().endpoints()]
2057
+ B = HyperbolicGeodesicUHP._get_B(p)
2058
+ # outmat below will be returned after we normalize the determinant.
2059
+ outmat = B * HyperbolicGeodesicUHP._crossratio_matrix(s, p, e)
2060
+ outmat = outmat / outmat.det().sqrt()
2061
+ if (outmat - outmat.conjugate()).norm(1) < 10**-9:
2062
+ # Small imaginary part.
2063
+ outmat = (outmat + outmat.conjugate()) / 2
2064
+ # Set it equal to its real part.
2065
+ return outmat
2066
+
2067
+ @staticmethod
2068
+ def _crossratio_matrix(p0, p1, p2): # UHP
2069
+ r"""
2070
+ Given three points (the list `p`) in `\mathbb{CP}^{1}` in affine
2071
+ coordinates, return the linear fractional transformation taking
2072
+ the elements of `p` to `0`, `1`, and `\infty`.
2073
+
2074
+ INPUT:
2075
+
2076
+ - ``p0``, ``p1``, ``p2`` -- a list of three distinct elements
2077
+ of `\mathbb{CP}^1` in affine coordinates; that is, each element
2078
+ must be a complex number, `\infty`, or symbolic.
2079
+
2080
+ OUTPUT: an element of `\GL(2,\CC)`
2081
+
2082
+ EXAMPLES::
2083
+
2084
+ sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic \
2085
+ ....: import HyperbolicGeodesicUHP
2086
+ sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry \
2087
+ ....: import moebius_transform
2088
+ sage: UHP = HyperbolicPlane().UHP()
2089
+ sage: (p1, p2, p3) = [UHP.random_point().coordinates()
2090
+ ....: for k in range(3)]
2091
+ sage: A = HyperbolicGeodesicUHP._crossratio_matrix(p1, p2, p3)
2092
+ sage: bool(abs(moebius_transform(A, p1)) < 10**-9)
2093
+ True
2094
+ sage: bool(abs(moebius_transform(A, p2) - 1) < 10**-9)
2095
+ True
2096
+ sage: bool(moebius_transform(A, p3) == infinity)
2097
+ True
2098
+ sage: (x,y,z) = var('x,y,z')
2099
+ sage: HyperbolicGeodesicUHP._crossratio_matrix(x,y,z)
2100
+ [ y - z -x*(y - z)]
2101
+ [ -x + y (x - y)*z]
2102
+ """
2103
+
2104
+ if p0 == infinity:
2105
+ return matrix([[0, -(p1 - p2)], [-1, p2]])
2106
+ elif p1 == infinity:
2107
+ return matrix([[1, -p0], [1, -p2]])
2108
+ elif p2 == infinity:
2109
+ return matrix([[1, -p0], [0, p1 - p0]])
2110
+ return matrix([[p1 - p2, (p1 - p2)*(-p0)],
2111
+ [p1 - p0, (p1 - p0)*(-p2)]])
2112
+
2113
+ # ***********************************************************************
2114
+ # Other geodesics
2115
+ # ***********************************************************************
2116
+
2117
+
2118
+ class HyperbolicGeodesicPD(HyperbolicGeodesic):
2119
+ r"""
2120
+ A geodesic in the Poincaré disk model.
2121
+
2122
+ Geodesics in this model are represented by segments of circles contained
2123
+ within the unit disk that are orthogonal to the boundary of the disk,
2124
+ plus all diameters of the disk.
2125
+
2126
+ INPUT:
2127
+
2128
+ - ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space
2129
+ representing the start of the geodesic
2130
+
2131
+ - ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space
2132
+ representing the end of the geodesic
2133
+
2134
+ EXAMPLES::
2135
+
2136
+ sage: PD = HyperbolicPlane().PD()
2137
+ sage: g = PD.get_geodesic(PD.get_point(I), PD.get_point(-I/2))
2138
+ sage: g = PD.get_geodesic(I,-I/2)
2139
+ sage: h = PD.get_geodesic(-1/2+I/2,1/2+I/2)
2140
+
2141
+ .. PLOT::
2142
+
2143
+ PD = HyperbolicPlane().PD()
2144
+ g = PD.get_geodesic(I,-I/2)
2145
+ h = PD.get_geodesic(-0.5+I*0.5,0.5+I*0.5)
2146
+ sphinx_plot(g.plot()+h.plot(color='green'))
2147
+ """
2148
+
2149
+ def plot(self, boundary=True, **options):
2150
+ r"""
2151
+ Plot ``self``.
2152
+
2153
+ EXAMPLES:
2154
+
2155
+ First some lines::
2156
+
2157
+ sage: PD = HyperbolicPlane().PD()
2158
+ sage: PD.get_geodesic(0, 1).plot() # needs sage.plot
2159
+ Graphics object consisting of 2 graphics primitives
2160
+
2161
+ .. PLOT::
2162
+
2163
+ sphinx_plot(HyperbolicPlane().PD().get_geodesic(0, 1).plot())
2164
+
2165
+ ::
2166
+
2167
+ sage: PD.get_geodesic(0, 0.3+0.8*I).plot() # needs sage.plot
2168
+ Graphics object consisting of 2 graphics primitives
2169
+
2170
+ .. PLOT::
2171
+
2172
+ PD = HyperbolicPlane().PD()
2173
+ sphinx_plot(PD.get_geodesic(0, 0.3+0.8*I).plot())
2174
+
2175
+ Then some generic geodesics::
2176
+
2177
+ sage: PD.get_geodesic(-0.5, 0.3+0.4*I).plot() # needs sage.plot
2178
+ Graphics object consisting of 2 graphics primitives
2179
+ sage: g = PD.get_geodesic(-1, exp(3*I*pi/7))
2180
+ sage: G = g.plot(linestyle='dashed',color='red'); G # needs sage.plot
2181
+ Graphics object consisting of 2 graphics primitives
2182
+ sage: h = PD.get_geodesic(exp(2*I*pi/11), exp(1*I*pi/11))
2183
+ sage: H = h.plot(thickness=6, color='orange'); H # needs sage.plot
2184
+ Graphics object consisting of 2 graphics primitives
2185
+ sage: show(G+H) # needs sage.plot
2186
+
2187
+ .. PLOT::
2188
+
2189
+ PD = HyperbolicPlane().PD()
2190
+ PD.get_geodesic(-0.5, 0.3+0.4*I).plot()
2191
+ g = PD.get_geodesic(-1, exp(3*I*pi/7))
2192
+ G = g.plot(linestyle='dashed',color='red')
2193
+ h = PD.get_geodesic(exp(2*I*pi/11), exp(1*I*pi/11))
2194
+ H = h.plot(thickness=6, color='orange')
2195
+ sphinx_plot(G+H)
2196
+ """
2197
+
2198
+ opts = {'axes': False, 'aspect_ratio': 1}
2199
+ opts.update(self.graphics_options())
2200
+ opts.update(options)
2201
+ end_1, end_2 = (CC(k.coordinates()) for k in self.endpoints())
2202
+ bd_1, bd_2 = (CC(k.coordinates()) for k in self.ideal_endpoints())
2203
+ # Check to see if it's a line
2204
+ if abs(bd_1 + bd_2) < EPSILON:
2205
+ pic = bezier_path([[(real(end_1), imag(end_1)), (real(end_2), imag(end_2))]], **opts)
2206
+ else:
2207
+ # If we are here, we know it's not a line
2208
+ # So we compute the center and radius of the circle
2209
+ invdet = RR.one() / (real(bd_1)*imag(bd_2) - real(bd_2)*imag(bd_1))
2210
+ centerx = (imag(bd_2) - imag(bd_1)) * invdet
2211
+ centery = (real(bd_1) - real(bd_2)) * invdet
2212
+ center = centerx + I * centery
2213
+ radius = RR(abs(bd_1 - center))
2214
+ # Now we calculate the angles for the arc
2215
+ theta1 = CC(end_1 - center).arg()
2216
+ theta2 = CC(end_2 - center).arg()
2217
+ theta1, theta2 = sorted([theta1, theta2])
2218
+ # Make sure the sector is inside the disk
2219
+ if theta2 - theta1 > pi:
2220
+ theta1 += 2 * pi
2221
+ pic = arc((centerx, centery), radius,
2222
+ sector=(theta1, theta2), **opts)
2223
+ if boundary:
2224
+ pic += self._model.get_background_graphic()
2225
+ return pic
2226
+
2227
+
2228
+ class HyperbolicGeodesicKM(HyperbolicGeodesic):
2229
+ r"""
2230
+ A geodesic in the Klein disk model.
2231
+
2232
+ Geodesics are represented by the chords, straight line segments with ideal
2233
+ endpoints on the boundary circle.
2234
+
2235
+ INPUT:
2236
+
2237
+ - ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space
2238
+ representing the start of the geodesic
2239
+
2240
+ - ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space
2241
+ representing the end of the geodesic
2242
+
2243
+ EXAMPLES::
2244
+
2245
+ sage: KM = HyperbolicPlane().KM()
2246
+ sage: g = KM.get_geodesic((0.1,0.9),(-0.1,-0.9))
2247
+ sage: h = KM.get_geodesic((-0.707106781,-0.707106781),(0.707106781,-0.707106781))
2248
+ sage: P = g.plot(color='orange')+h.plot(); P # needs sage.plot
2249
+ Graphics object consisting of 4 graphics primitives
2250
+
2251
+ .. PLOT::
2252
+
2253
+ KM = HyperbolicPlane().KM()
2254
+ g = KM.get_geodesic(CC(0.1,0.9),
2255
+ CC(-0.1,-0.9))
2256
+ h = KM.get_geodesic(CC(-0.707106781,-0.707106781),
2257
+ CC(0.707106781,-0.707106781))
2258
+ sphinx_plot(g.plot(color='orange')+h.plot())
2259
+ """
2260
+
2261
+ def plot(self, boundary=True, **options):
2262
+ r"""
2263
+ Plot ``self``.
2264
+
2265
+ EXAMPLES::
2266
+
2267
+ sage: HyperbolicPlane().KM().get_geodesic(0, 1).plot() # needs sage.plot
2268
+ Graphics object consisting of 2 graphics primitives
2269
+
2270
+ .. PLOT::
2271
+
2272
+ KM = HyperbolicPlane().KM()
2273
+ sphinx_plot(KM.get_geodesic(CC(0,0), CC(1,0)).plot())
2274
+ """
2275
+ opts = {'axes': False, 'aspect_ratio': 1}
2276
+ opts.update(self.graphics_options())
2277
+ opts.update(options)
2278
+
2279
+ def map_pt(pt):
2280
+ if pt in CC:
2281
+ return CC(pt)
2282
+ return CC(*pt)
2283
+ end_1, end_2 = (map_pt(k.coordinates()) for k in self.endpoints())
2284
+ pic = bezier_path([[(real(end_1), imag(end_1)),
2285
+ (real(end_2), imag(end_2))]], **opts)
2286
+ if boundary:
2287
+ pic += self._model.get_background_graphic()
2288
+ return pic
2289
+
2290
+
2291
+ class HyperbolicGeodesicHM(HyperbolicGeodesic):
2292
+ r"""
2293
+ A geodesic in the hyperboloid model.
2294
+
2295
+ Valid points in the hyperboloid model satisfy :math:`x^2 + y^2 - z^2 = -1`
2296
+
2297
+ INPUT:
2298
+
2299
+ - ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space
2300
+ representing the start of the geodesic
2301
+
2302
+ - ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space
2303
+ representing the end of the geodesic
2304
+
2305
+ EXAMPLES::
2306
+
2307
+ sage: HM = HyperbolicPlane().HM()
2308
+ sage: p1 = HM.get_point((4, -4, sqrt(33)))
2309
+ sage: p2 = HM.get_point((-3,-3,sqrt(19)))
2310
+ sage: g = HM.get_geodesic(p1, p2)
2311
+ sage: g = HM.get_geodesic((4, -4, sqrt(33)), (-3, -3, sqrt(19)))
2312
+
2313
+ .. PLOT::
2314
+
2315
+ HM = HyperbolicPlane().HM()
2316
+ p1 = HM.get_point((4, -4, sqrt(33)))
2317
+ p2 = HM.get_point((-3,-3,sqrt(19)))
2318
+ g = HM.get_geodesic(p1, p2)
2319
+ sphinx_plot(g.plot(color='blue'))
2320
+ """
2321
+ def _plot_vertices(self, points=75):
2322
+ r"""
2323
+ Return ``self`` plotting vertices in `\RR^3`.
2324
+
2325
+ Auxiliary function needed to plot polygons.
2326
+
2327
+ EXAMPLES::
2328
+
2329
+ sage: HM = HyperbolicPlane().HM()
2330
+ sage: p1 = HM.get_point((4, -4, sqrt(33)))
2331
+ sage: p2 = HM.get_point((-3,-3,sqrt(19)))
2332
+ sage: g = HM.get_geodesic(p1, p2)
2333
+ sage: g._plot_vertices(5) # needs sage.plot
2334
+ [(4.0, -4.0, 5.744562...),
2335
+ (1.363213..., -1.637073..., 2.353372...),
2336
+ (0.138568..., -0.969980..., 1.400022...),
2337
+ (-0.942533..., -1.307681..., 1.896945...),
2338
+ (-3.0, -3.0, 4.358898...)]
2339
+ """
2340
+ from sage.plot.misc import setup_for_eval_on_grid
2341
+ from sage.arith.srange import xsrange
2342
+
2343
+ x = SR.var('x')
2344
+ v1, u2 = (vector(k.coordinates()) for k in self.endpoints())
2345
+ # Lorentzian Gram Shmidt. The original vectors will be
2346
+ # u1, u2 and the orthogonal ones will be v1, v2. Except
2347
+ # v1 = u1, and I don't want to declare another variable,
2348
+ # hence the odd naming convention above.
2349
+ # We need the Lorentz dot product of v1 and u2.
2350
+ v1_ldot_u2 = u2[0]*v1[0] + u2[1]*v1[1] - u2[2]*v1[2]
2351
+ v2 = u2 + v1_ldot_u2 * v1
2352
+ v2_norm = sqrt(v2[0]**2 + v2[1]**2 - v2[2]**2)
2353
+ v2 = v2 / v2_norm
2354
+ v2_ldot_u2 = u2[0]*v2[0] + u2[1]*v2[1] - u2[2]*v2[2]
2355
+ # Now v1 and v2 are Lorentz orthogonal, and |v1| = -1, |v2|=1
2356
+ # That is, v1 is unit timelike and v2 is unit spacelike.
2357
+ # This means that cosh(x)*v1 + sinh(x)*v2 is unit timelike.
2358
+ hyperbola = tuple(cosh(x)*v1 + sinh(x)*v2)
2359
+ endtime = arcsinh(v2_ldot_u2)
2360
+ # mimic the function _parametric_plot3d_curve using a bezier3d
2361
+ # instead of a line3d
2362
+ # this is required in order to be able to plot hyperbolic
2363
+ # polygons within the plot library
2364
+ g, ranges = setup_for_eval_on_grid(hyperbola, [(x, 0, endtime)], points)
2365
+ f_x, f_y, f_z = g
2366
+ return [(f_x(u), f_y(u), f_z(u))
2367
+ for u in xsrange(*ranges[0], include_endpoint=True)]
2368
+
2369
+ def plot(self, show_hyperboloid=True, **graphics_options):
2370
+ r"""
2371
+ Plot ``self``.
2372
+
2373
+ EXAMPLES::
2374
+
2375
+ sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic \
2376
+ ....: import *
2377
+ sage: g = HyperbolicPlane().HM().random_geodesic()
2378
+ sage: g.plot() # needs sage.plot
2379
+ Graphics3d Object
2380
+
2381
+ .. PLOT::
2382
+
2383
+ sphinx_plot(HyperbolicPlane().HM().random_geodesic().plot())
2384
+ """
2385
+
2386
+ x = SR.var('x')
2387
+ opts = self.graphics_options()
2388
+ opts.update(graphics_options)
2389
+ v1, u2 = (vector(k.coordinates()) for k in self.endpoints())
2390
+ # Lorentzian Gram Shmidt. The original vectors will be
2391
+ # u1, u2 and the orthogonal ones will be v1, v2. Except
2392
+ # v1 = u1, and I don't want to declare another variable,
2393
+ # hence the odd naming convention above.
2394
+ # We need the Lorentz dot product of v1 and u2.
2395
+ v1_ldot_u2 = u2[0]*v1[0] + u2[1]*v1[1] - u2[2]*v1[2]
2396
+ v2 = u2 + v1_ldot_u2 * v1
2397
+ v2_norm = sqrt(v2[0]**2 + v2[1]**2 - v2[2]**2)
2398
+ v2 = v2 / v2_norm
2399
+ v2_ldot_u2 = u2[0]*v2[0] + u2[1]*v2[1] - u2[2]*v2[2]
2400
+ # Now v1 and v2 are Lorentz orthogonal, and |v1| = -1, |v2|=1
2401
+ # That is, v1 is unit timelike and v2 is unit spacelike.
2402
+ # This means that cosh(x)*v1 + sinh(x)*v2 is unit timelike.
2403
+ hyperbola = cosh(x)*v1 + sinh(x)*v2
2404
+ endtime = arcsinh(v2_ldot_u2)
2405
+ from sage.plot.plot3d.all import parametric_plot3d
2406
+ pic = parametric_plot3d(hyperbola, (x, 0, endtime), **graphics_options)
2407
+ if show_hyperboloid:
2408
+ pic += self._model.get_background_graphic()
2409
+ return pic