Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# -*- coding: utf-8 -*- Hyperbolic Geodesics
This module implements the abstract base class for geodesics in hyperbolic space of arbitrary dimension. It also contains the implementations for specific models of hyperbolic geometry.
AUTHORS:
- Greg Laun (2013): initial version
EXAMPLES:
We can construct geodesics in the upper half plane model, abbreviated UHP for convenience::
sage: g = HyperbolicPlane().UHP().get_geodesic(2, 3) sage: g Geodesic in UHP from 2 to 3
This geodesic can be plotted using :meth:`plot`, in this example we will show the axis.
::
sage: g.plot(axes=True) Graphics object consisting of 2 graphics primitives
.. PLOT::
g = HyperbolicPlane().UHP().get_geodesic(2.0, 3.0) sphinx_plot(g.plot(axes=True))
::
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3 + I) sage: g.length() arccosh(11/2) sage: g.plot(axes=True) Graphics object consisting of 2 graphics primitives
.. PLOT::
sphinx_plot(HyperbolicPlane().UHP().get_geodesic(I, 3 + I).plot(axes=True))
Geodesics of both types in UHP are supported::
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3*I) sage: g Geodesic in UHP from I to 3*I sage: g.plot() Graphics object consisting of 2 graphics primitives
.. PLOT::
sphinx_plot(HyperbolicPlane().UHP().get_geodesic(I, 3*I).plot())
Geodesics are oriented, which means that two geodesics with the same graph will only be equal if their starting and ending points are the same::
sage: g1 = HyperbolicPlane().UHP().get_geodesic(1,2) sage: g2 = HyperbolicPlane().UHP().get_geodesic(2,1) sage: g1 == g2 False
.. TODO::
Implement a parent for all geodesics of the hyperbolic plane? Or implement geodesics as a parent in the subobjects category?
"""
#*********************************************************************** # Copyright (C) 2013 Greg Laun <glaun@math.umd.edu> # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # http://www.gnu.org/licenses/ #***********************************************************************
'moebius_transform')
r""" Abstract base class for oriented geodesics that are not necessarily complete.
INPUT:
- ``start`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the start of the geodesic
- ``end`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the end of the geodesic
EXAMPLES:
We can construct a hyperbolic geodesic in any model::
sage: HyperbolicPlane().UHP().get_geodesic(1, 0) Geodesic in UHP from 1 to 0 sage: HyperbolicPlane().PD().get_geodesic(1, 0) Geodesic in PD from 1 to 0 sage: HyperbolicPlane().KM().get_geodesic((0,1/2), (1/2, 0)) Geodesic in KM from (0, 1/2) to (1/2, 0) sage: HyperbolicPlane().HM().get_geodesic((0,0,1), (0,1, sqrt(2))) Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2))
"""
##################### # "Private" Methods # #####################
r""" See :class:`HyperbolicGeodesic` for full documentation.
EXAMPLES ::
sage: HyperbolicPlane().UHP().get_geodesic(I, 2 + I) Geodesic in UHP from I to I + 2
"""
def _cached_geodesic(self): r""" The representation of the geodesic used for calculations.
EXAMPLES::
sage: A = HyperbolicPlane().PD().get_geodesic(0, 1/2) sage: A._cached_geodesic Geodesic in UHP from I to 3/5*I + 4/5
"""
def _complete(self): r""" Return whether the geodesic is complete. This is used for geodesics in non-bounded models. For these models, ``self.complete()`` simply sets ``_complete`` to ``True``.
EXAMPLES::
sage: HyperbolicPlane().UHP().get_geodesic(1, -12)._complete True sage: HyperbolicPlane().UHP().get_geodesic(I, 2 + I)._complete False sage: HM = HyperbolicPlane().HM() sage: g = HM.get_geodesic((0,0,1), (0,1, sqrt(2))) sage: g._complete False sage: g.complete()._complete True
"""
r""" Return a string representation of ``self``.
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: UHP.get_geodesic(3 + 4*I, I) Geodesic in UHP from 4*I + 3 to I
sage: PD = HyperbolicPlane().PD() sage: PD.get_geodesic(1/2 + I/2, 0) Geodesic in PD from 1/2*I + 1/2 to 0
sage: KM = HyperbolicPlane().KM() sage: KM.get_geodesic((1/2, 1/2), (0, 0)) Geodesic in KM from (1/2, 1/2) to (0, 0)
sage: HM = HyperbolicPlane().HM() sage: HM.get_geodesic((0,0,1), (0, 1, sqrt(Integer(2)))) Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2))
"""
self._start.coordinates(), self._end.coordinates())
r""" Return ``True`` if ``self`` is equal to other as an oriented geodesic.
EXAMPLES::
sage: g1 = HyperbolicPlane().UHP().get_geodesic(I, 2*I) sage: g2 = HyperbolicPlane().UHP().get_geodesic(2*I, I) sage: g1 == g2 False sage: g1 == g1 True """ return False self._start == other._start and self._end == other._end)
""" Test unequality of self and other.
EXAMPLES::
sage: g1 = HyperbolicPlane().UHP().get_geodesic(I, 2*I) sage: g2 = HyperbolicPlane().UHP().get_geodesic(2*I, I) sage: g1 != g2 True sage: g1 != g1 False """
####################### # Setters and Getters # #######################
r""" Return the starting point of the geodesic.
EXAMPLES::
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3*I) sage: g.start() Point in UHP I
"""
r""" Return the starting point of the geodesic.
EXAMPLES::
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3*I) sage: g.end() Point in UHP 3*I
"""
r""" Return a list containing the start and endpoints.
EXAMPLES::
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3*I) sage: g.endpoints() [Point in UHP I, Point in UHP 3*I]
"""
r""" Return the model to which the :class:`HyperbolicGeodesic` belongs.
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: UHP.get_geodesic(I, 2*I).model() Hyperbolic plane in the Upper Half Plane Model
sage: PD = HyperbolicPlane().PD() sage: PD.get_geodesic(0, I/2).model() Hyperbolic plane in the Poincare Disk Model
sage: KM = HyperbolicPlane().KM() sage: KM.get_geodesic((0, 0), (0, 1/2)).model() Hyperbolic plane in the Klein Disk Model
sage: HM = HyperbolicPlane().HM() sage: HM.get_geodesic((0, 0, 1), (0, 1, sqrt(2))).model() Hyperbolic plane in the Hyperboloid Model """
r""" Convert the current object to image in another model.
INPUT:
- ``model`` -- the image model
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: PD = HyperbolicPlane().PD() sage: UHP.get_geodesic(I, 2*I).to_model(PD) Geodesic in PD from 0 to 1/3*I sage: UHP.get_geodesic(I, 2*I).to_model('PD') Geodesic in PD from 0 to 1/3*I
"""
raise NotImplementedError("cannot convert to an unbounded model")
r""" Return the graphics options of ``self``.
EXAMPLES::
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 2*I, color="red") sage: g.graphics_options() {'color': 'red'}
"""
r""" Update the graphics options of ``self``.
INPUT:
- ``update`` -- if ``True``, the original option are updated rather than overwritten
EXAMPLES::
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 2*I) sage: g.graphics_options() {}
sage: g.update_graphics(color = "red"); g.graphics_options() {'color': 'red'}
sage: g.update_graphics(color = "blue"); g.graphics_options() {'color': 'blue'}
sage: g.update_graphics(True, size = 20); g.graphics_options() {'color': 'blue', 'size': 20}
"""
################### # Boolean Methods # ###################
r""" Return ``True`` if ``self`` is a complete geodesic (that is, both endpoints are on the ideal boundary) and ``False`` otherwise.
If we represent complete geodesics using green color and incomplete using red colors we have the following graphic:
.. PLOT::
UHP = HyperbolicPlane().UHP() g = UHP.get_geodesic(1.5*I, 2.5*I) h = UHP.get_geodesic(0, I) l = UHP.get_geodesic(2, 4) m = UHP.get_geodesic(3, infinity) G = g.plot(color='red') +\ text('is_complete()=False', (0, 2), horizontal_alignement='left') H = h.plot(color='red') +\ text('is_complete()=False', (0, 0.5), horizontal_alignement='left') L = l.plot(color='green') +\ text('is_complete()=True', (5, 1.5)) M = m.plot(color='green') + text('is complete()=True', (5, 4), horizontal_alignement='left') sphinx_plot(G+H+L+M)
Notice, that there is no visual indication that the *vertical* geodesic is complete
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: UHP.get_geodesic(1.5*I, 2.5*I).is_complete() False sage: UHP.get_geodesic(0, I).is_complete() False sage: UHP.get_geodesic(3, infinity).is_complete() True sage: UHP.get_geodesic(2,5).is_complete() True
"""
r""" Return ``True`` if ``self`` and ``other`` are asymptotically parallel and ``False`` otherwise.
INPUT:
- ``other`` -- a hyperbolic geodesic
EXAMPLES::
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) sage: h = HyperbolicPlane().UHP().get_geodesic(-2,4) sage: g.is_asymptotically_parallel(h) True
.. PLOT::
g = HyperbolicPlane().UHP().get_geodesic(-2.0,5.0) h = HyperbolicPlane().UHP().get_geodesic(-2.0,4.0) sphinx_plot(g.plot(color='green')+h.plot(color='green'))
Ultraparallel geodesics are not asymptotically parallel::
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) sage: h = HyperbolicPlane().UHP().get_geodesic(-1,4) sage: g.is_asymptotically_parallel(h) False
.. PLOT::
g = HyperbolicPlane().UHP().get_geodesic(-2.0,5.0) h = HyperbolicPlane().UHP().get_geodesic(-1.0,4.0) sphinx_plot(g.plot(color='red')+h.plot(color='red'))
No hyperbolic geodesic is asymptotically parallel to itself::
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) sage: g.is_asymptotically_parallel(g) False
"""
self.model() is other.model())
r""" Return ``True`` if ``self`` and ``other`` are ultra parallel and ``False`` otherwise.
INPUT:
- ``other`` -- a hyperbolic geodesic
EXAMPLES::
sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic \ ....: import * sage: g = HyperbolicPlane().UHP().get_geodesic(0,1) sage: h = HyperbolicPlane().UHP().get_geodesic(-3,-1) sage: g.is_ultra_parallel(h) True
.. PLOT::
g = HyperbolicPlane().UHP().get_geodesic(0.0,1.1) h = HyperbolicPlane().UHP().get_geodesic(-3.0,-1.0) sphinx_plot(g.plot(color='green')+h.plot(color='green'))
::
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) sage: h = HyperbolicPlane().UHP().get_geodesic(2,6) sage: g.is_ultra_parallel(h) False
.. PLOT::
g = HyperbolicPlane().UHP().get_geodesic(-2,5) h = HyperbolicPlane().UHP().get_geodesic(2,6) sphinx_plot(g.plot(color='red')+h.plot(color='red'))
::
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) sage: g.is_ultra_parallel(g) False
"""
r""" Return ``True`` if the two given hyperbolic geodesics are either ultra parallel or asymptotically parallel and``False`` otherwise.
INPUT:
- ``other`` -- a hyperbolic geodesic in any model
OUTPUT:
``True`` if the given geodesics are either ultra parallel or asymptotically parallel, ``False`` if not.
EXAMPLES::
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) sage: h = HyperbolicPlane().UHP().get_geodesic(5,12) sage: g.is_parallel(h) True
.. PLOT::
g = HyperbolicPlane().UHP().get_geodesic(-2,5) h = HyperbolicPlane().UHP().get_geodesic(5,12) sphinx_plot(g.plot(color='green')+h.plot(color='green'))
::
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) sage: h = HyperbolicPlane().UHP().get_geodesic(-2,4) sage: g.is_parallel(h) True
.. PLOT::
g = HyperbolicPlane().UHP().get_geodesic(-2.0,5.0) h = HyperbolicPlane().UHP().get_geodesic(-2.0,4.0) sphinx_plot(g.plot(color='green')+h.plot(color='green'))
::
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,2) sage: h = HyperbolicPlane().UHP().get_geodesic(-1,4) sage: g.is_parallel(h) False
.. PLOT::
g = HyperbolicPlane().UHP().get_geodesic(-2,2) h = HyperbolicPlane().UHP().get_geodesic(-1,4) sphinx_plot(g.plot(color='red')+h.plot(color='red'))
No hyperbolic geodesic is either ultra parallel or asymptotically parallel to itself::
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) sage: g.is_parallel(g) False
"""
r""" Return the ideal endpoints in bounded models. Raise a ``NotImplementedError`` in models that are not bounded.
EXAMPLES::
sage: H = HyperbolicPlane() sage: UHP = H.UHP() sage: UHP.get_geodesic(1 + I, 1 + 3*I).ideal_endpoints() [Boundary point in UHP 1, Boundary point in UHP +Infinity]
sage: PD = H.PD() sage: PD.get_geodesic(0, I/2).ideal_endpoints() [Boundary point in PD -I, Boundary point in PD I]
sage: KM = H.KM() sage: KM.get_geodesic((0,0), (0, 1/2)).ideal_endpoints() [Boundary point in KM (0, -1), Boundary point in KM (0, 1)]
sage: HM = H.HM() sage: HM.get_geodesic((0,0,1), (1, 0, sqrt(2))).ideal_endpoints() Traceback (most recent call last): ... NotImplementedError: boundary points are not implemented in the HM model
"""
"{0} model".format(self._model.short_name()) for k in self._cached_geodesic.ideal_endpoints()]
r""" Return the geodesic with ideal endpoints in bounded models. Raise a ``NotImplementedError`` in models that are not bounded. In the following examples we represent complete geodesics by a dashed line.
EXAMPLES::
sage: H = HyperbolicPlane() sage: UHP = H.UHP() sage: UHP.get_geodesic(1 + I, 1 + 3*I).complete() Geodesic in UHP from 1 to +Infinity
.. PLOT::
g = HyperbolicPlane().UHP().get_geodesic(1 + I, 1 + 3*I) h = g.complete() sphinx_plot(g.plot()+h.plot(linestyle='dashed'))
::
sage: PD = H.PD() sage: PD.get_geodesic(0, I/2).complete() Geodesic in PD from -I to I sage: PD.get_geodesic(0.25*(-1-I),0.25*(1-I)).complete() Geodesic in PD from -0.895806416477617 - 0.444444444444444*I to 0.895806416477617 - 0.444444444444444*I
.. PLOT::
PD = HyperbolicPlane().PD() g = PD.get_geodesic(0, I/2) h = g. complete() m = PD.get_geodesic(0.25*(-1-I),0.25*(1-I)) l = m.complete() sphinx_plot(g.plot()+h.plot(linestyle='dashed') + m.plot()+l.plot(linestyle='dashed'))
::
sage: KM = H.KM() sage: KM.get_geodesic((0,0), (0, 1/2)).complete() Geodesic in KM from (0, -1) to (0, 1)
.. PLOT::
g = HyperbolicPlane().KM().get_geodesic((0.0,0.0), (0.0, 0.5)) h = g.complete() sphinx_plot(g.plot()+h.plot(linestyle='dashed'))
::
sage: HM = H.HM() sage: HM.get_geodesic((0,0,1), (1, 0, sqrt(2))).complete() Geodesic in HM from (0, 0, 1) to (1, 0, sqrt(2))
.. PLOT::
g = HyperbolicPlane().HM().get_geodesic((0,0,1), (1, 0, sqrt(2))) h = g.complete() sphinx_plot(g.plot(color='black')+h.plot(linestyle='dashed',color='black'))
::
sage: g = HM.get_geodesic((0,0,1), (1, 0, sqrt(2))).complete() sage: g.is_complete() True
TESTS:
Check that floating points remain floating points through this method::
sage: H = HyperbolicPlane() sage: g = H.UHP().get_geodesic(CC(0,1), CC(2,2)) sage: gc = g.complete() sage: parent(gc.start().coordinates()) Real Field with 53 bits of precision
"""
r""" Return the involution fixing ``self``.
EXAMPLES::
sage: H = HyperbolicPlane() sage: gU = H.UHP().get_geodesic(2,4) sage: RU = gU.reflection_involution(); RU Isometry in UHP [ 3 -8] [ 1 -3]
sage: RU*gU == gU True
sage: gP = H.PD().get_geodesic(0, I) sage: RP = gP.reflection_involution(); RP Isometry in PD [ 1 0] [ 0 -1]
sage: RP*gP == gP True
sage: gK = H.KM().get_geodesic((0,0), (0,1)) sage: RK = gK.reflection_involution(); RK Isometry in KM [-1 0 0] [ 0 1 0] [ 0 0 1]
sage: RK*gK == gK True
sage: HM = H.HM() sage: g = HM.get_geodesic((0,0,1), (1,0, n(sqrt(2)))) sage: A = g.reflection_involution() sage: B = diagonal_matrix([1, -1, 1]) sage: bool((B - A.matrix()).norm() < 10**-9) True
The above tests go through the Upper Half Plane. It remains to test that the matrices in the models do what we intend. ::
sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry \ ....: import moebius_transform sage: R = H.PD().get_geodesic(-1,1).reflection_involution() sage: bool(moebius_transform(R.matrix(), 0) == 0) True
"""
r""" Return the unique hyperbolic geodesic perpendicular to two given geodesics, if such a geodesic exists. If none exists, raise a ``ValueError``.
INPUT:
- ``other`` -- a hyperbolic geodesic in the same model as ``self``
OUTPUT:
- a hyperbolic geodesic
EXAMPLES::
sage: g = HyperbolicPlane().UHP().get_geodesic(2,3) sage: h = HyperbolicPlane().UHP().get_geodesic(4,5) sage: g.common_perpendicular(h) Geodesic in UHP from 1/2*sqrt(3) + 7/2 to -1/2*sqrt(3) + 7/2
.. PLOT::
g = HyperbolicPlane().UHP().get_geodesic(2.0, 3.0) h = HyperbolicPlane().UHP().get_geodesic(4.0, 5.0) l = g.common_perpendicular(h) P = g.plot(color='blue') +\ h.plot(color='blue') +\ l.plot(color='orange') sphinx_plot(P)
It is an error to ask for the common perpendicular of two intersecting geodesics::
sage: g = HyperbolicPlane().UHP().get_geodesic(2,4) sage: h = HyperbolicPlane().UHP().get_geodesic(3, infinity) sage: g.common_perpendicular(h) Traceback (most recent call last): ... ValueError: geodesics intersect; no common perpendicular exists
"""
if not self.is_parallel(other): raise ValueError('geodesics intersect; ' + 'no common perpendicular exists') cp = self._cached_geodesic.common_perpendicular(other) return cp.to_model(self._model)
r""" Return the point of intersection of two geodesics (if such a point exists).
INPUT:
- ``other`` -- a hyperbolic geodesic in the same model as ``self``
OUTPUT:
- a hyperbolic point or geodesic
EXAMPLES::
sage: PD = HyperbolicPlane().PD()
"""
return self raise ValueError("geodesics don't intersect") return self return []
r""" Return the perpendicular bisector of ``self`` if ``self`` has finite length. Here distance is hyperbolic distance.
EXAMPLES::
sage: PD = HyperbolicPlane().PD() sage: g = PD.get_geodesic(-0.3+0.4*I,+0.7-0.1*I) sage: h = g.perpendicular_bisector() sage: P = g.plot(color='blue')+h.plot(color='orange');P Graphics object consisting of 4 graphics primitives
.. PLOT::
g = HyperbolicPlane().PD().get_geodesic(-0.3+0.4*I,+0.7-0.1*I) h = g.perpendicular_bisector() sphinx_plot(g.plot(color='blue')+h.plot(color='orange'))
Complete geodesics cannot be bisected::
sage: g = HyperbolicPlane().PD().get_geodesic(0, 1) sage: g.perpendicular_bisector() Traceback (most recent call last): ... ValueError: the length must be finite
TESTS::
sage: g = HyperbolicPlane().PD().random_geodesic() sage: h = g.perpendicular_bisector() sage: bool(h.intersection(g)[0].coordinates() - g.midpoint().coordinates() < 10**-9) True
"""
r""" Return the (hyperbolic) midpoint of a hyperbolic line segment.
EXAMPLES::
sage: g = HyperbolicPlane().UHP().random_geodesic() sage: m = g.midpoint() sage: end1, end2 = g.endpoints() sage: bool(abs(m.dist(end1) - m.dist(end2)) < 10**-9) True
Complete geodesics have no midpoint::
sage: HyperbolicPlane().UHP().get_geodesic(0,2).midpoint() Traceback (most recent call last): ... ValueError: the length must be finite
"""
r""" Return the hyperbolic distance from a given hyperbolic geodesic to another geodesic or point.
INPUT:
- ``other`` -- a hyperbolic geodesic or hyperbolic point in the same model
OUTPUT:
- the hyperbolic distance
EXAMPLES::
sage: g = HyperbolicPlane().UHP().get_geodesic(2, 4.0) sage: h = HyperbolicPlane().UHP().get_geodesic(5, 7.0) sage: bool(abs(g.dist(h).n() - 1.92484730023841) < 10**-9) True
If the second object is a geodesic ultraparallel to the first, or if it is a point on the boundary that is not one of the first object's endpoints, then return +infinity
::
sage: g = HyperbolicPlane().UHP().get_geodesic(2, 2+I) sage: p = HyperbolicPlane().UHP().get_point(5) sage: g.dist(p) +Infinity
TESTS:
Check that floating points remain floating points in :meth:`dist` ::
sage: UHP = HyperbolicPlane().UHP() sage: g = UHP.get_geodesic(CC(0,1), CC(2,2)) sage: UHP.dist(g.start(), g.end()) 1.45057451382258 sage: parent(_) Real Field with 53 bits of precision
"""
r""" Return the angle between any two given geodesics if they intersect.
INPUT:
- ``other`` -- a hyperbolic geodesic in the same model as ``self``
OUTPUT:
- the angle in radians between the two given geodesics
EXAMPLES::
sage: PD = HyperbolicPlane().PD() sage: g = PD.get_geodesic(3/5*I + 4/5, 15/17*I + 8/17) sage: h = PD.get_geodesic(4/5*I + 3/5, I) sage: g.angle(h) 1/2*pi
.. PLOT::
PD = HyperbolicPlane().PD() g = PD.get_geodesic(3.0/5.0*I + 4.0/5.0, 15.0/17.0*I + 8.0/17.0) h = PD.get_geodesic(4.0/5.0*I + 3.0/5.0, I) sphinx_plot(g.plot()+h.plot(color='orange'))
"""
raise ValueError("geodesics do not intersect")
r""" Return the Hyperbolic length of the hyperbolic line segment.
EXAMPLES::
sage: g = HyperbolicPlane().UHP().get_geodesic(2 + I, 3 + I/2) sage: g.length() arccosh(9/4)
"""
self._end.coordinates())
#*********************************************************************** # UHP geodesics #***********************************************************************
r""" Create a geodesic in the upper half plane model.
The geodesics in this model are represented by circular arcs perpendicular to the real axis (half-circles whose origin is on the real axis) and straight vertical lines ending on the real axis.
INPUT:
- ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space representing the start of the geodesic
- ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space representing the end of the geodesic
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: g = UHP.get_geodesic(UHP.get_point(I), UHP.get_point(2 + I)) sage: g = UHP.get_geodesic(I, 2 + I) sage: h = UHP.get_geodesic(-1, -1+2*I)
.. PLOT::
UHP = HyperbolicPlane().UHP() g = UHP.get_geodesic(I, 2 + I) h = UHP.get_geodesic(-1, -1+2*I) sphinx_plot(g.plot()+h.plot())
"""
r""" Return the isometry of the involution fixing the geodesic ``self``.
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: g1 = UHP.get_geodesic(0, 1) sage: g1.reflection_involution() Isometry in UHP [ 1 0] [ 2 -1] sage: UHP.get_geodesic(I, 2*I).reflection_involution() Isometry in UHP [ 1 0] [ 0 -1]
"""
M = matrix([[1, -2*y], [0, -1]]) else:
r""" Plot ``self``.
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: UHP.get_geodesic(0, 1).plot() Graphics object consisting of 2 graphics primitives
.. PLOT::
UHP = HyperbolicPlane().UHP() g = UHP.get_geodesic(0.0, 1.0).plot() sphinx_plot(g)
::
sage: UHP.get_geodesic(I, 3+4*I).plot(linestyle="dashed", color="brown") Graphics object consisting of 2 graphics primitives
.. PLOT::
UHP = HyperbolicPlane().UHP() g = UHP.get_geodesic(I, 3+4*I).plot(linestyle="dashed", color="brown") sphinx_plot(g)
::
sage: UHP.get_geodesic(1, infinity).plot(color='orange') Graphics object consisting of 2 graphics primitives
.. PLOT::
UHP = HyperbolicPlane().UHP() g = UHP.get_geodesic(1, infinity).plot(color='orange') sphinx_plot(g)
"""
or CC(infinity) in [end_1, end_2]: # on same vertical line # If one of the endpoints is infinity, we replace it with a # large finite point end_1 = (real(end_2), (imag(end_2) + 10)) end_2 = (real(end_2), imag(end_2)) else: theta2 += pi sector=(theta1, theta2), **opts) # We want to draw a segment of the real line. The # computations below compute the projection of the # geodesic to the real line, and then draw a little # to the left and right of the projection. length}
r""" Determine the ideal (boundary) endpoints of the complete hyperbolic geodesic corresponding to ``self``.
OUTPUT:
- a list of 2 boundary points
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: UHP.get_geodesic(I, 2*I).ideal_endpoints() [Boundary point in UHP 0, Boundary point in UHP +Infinity] sage: UHP.get_geodesic(1 + I, 2 + 4*I).ideal_endpoints() [Boundary point in UHP -sqrt(65) + 9, Boundary point in UHP sqrt(65) + 9]
"""
# infinity is the first endpoint, so the other ideal endpoint # is just the real part of the second coordinate return [M.get_point(start), M.get_point(x2)] # Same idea as above # We could also have a vertical line with two interior points # Otherwise, we have a semicircular arc in the UHP
r""" Return the unique hyperbolic geodesic perpendicular to ``self`` and ``other``, if such a geodesic exists; otherwise raise a ``ValueError``.
INPUT:
- ``other`` -- a hyperbolic geodesic in current model
OUTPUT:
- a hyperbolic geodesic
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: g = UHP.get_geodesic(2, 3) sage: h = UHP.get_geodesic(4, 5) sage: g.common_perpendicular(h) Geodesic in UHP from 1/2*sqrt(3) + 7/2 to -1/2*sqrt(3) + 7/2
.. PLOT::
UHP = HyperbolicPlane().UHP() g = UHP.get_geodesic(2.0, 3.0) h = UHP.get_geodesic(4.0, 5.0) p = g.common_perpendicular(h) sphinx_plot(g.plot(color='blue')+h.plot(color='blue')+p.plot(color='orange'))
It is an error to ask for the common perpendicular of two intersecting geodesics::
sage: g = UHP.get_geodesic(2, 4) sage: h = UHP.get_geodesic(3, infinity) sage: g.common_perpendicular(h) Traceback (most recent call last): ... ValueError: geodesics intersect; no common perpendicular exists
"""
# Make sure both are in the same model other = other.to_model(self._model)
"no common perpendicular exists")
r""" Return the point of intersection of ``self`` and ``other`` (if such a point exists).
INPUT:
- ``other`` -- a hyperbolic geodesic in the current model
OUTPUT:
- a list of hyperbolic points or a hyperbolic geodesic
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: g = UHP.get_geodesic(3, 5) sage: h = UHP.get_geodesic(4, 7) sage: g.intersection(h) [Point in UHP 2/3*sqrt(-2) + 13/3]
If the given geodesics do not intersect, the function returns an empty list::
sage: g = UHP.get_geodesic(4, 5) sage: h = UHP.get_geodesic(5, 7) sage: g.intersection(h) []
If the given geodesics are identical, return that geodesic::
sage: g = UHP.get_geodesic(4 + I, 18*I) sage: h = UHP.get_geodesic(4 + I, 18*I) sage: g.intersection(h) [Boundary point in UHP -1/8*sqrt(114985) - 307/8, Boundary point in UHP 1/8*sqrt(114985) - 307/8]
"""
return start_1 return end_1
r""" Return the perpendicular bisector of the hyperbolic geodesic ``self`` if that geodesic has finite length.
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: g = UHP.random_geodesic() sage: h = g.perpendicular_bisector() sage: c = lambda x: x.coordinates() sage: bool(c(g.intersection(h)[0]) - c(g.midpoint()) < 10**-9) True
::
sage: UHP = HyperbolicPlane().UHP() sage: g = UHP.get_geodesic(1+I,2+0.5*I) sage: h = g.perpendicular_bisector() sage: show(g.plot(color='blue')+h.plot(color='orange'))
.. PLOT::
UHP = HyperbolicPlane().UHP() g = UHP.get_geodesic(1+I,2+0.5*I) h = g.perpendicular_bisector() sphinx_plot(g.plot(color='blue')+h.plot(color='orange'))
Infinite geodesics cannot be bisected::
sage: UHP.get_geodesic(0, 1).perpendicular_bisector() Traceback (most recent call last): ... ValueError: the length must be finite
"""
# We need to clean this matrix up. # Imaginary part is small. # Set it to its real part.
r""" Return the (hyperbolic) midpoint of ``self`` if it exists.
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: g = UHP.random_geodesic() sage: m = g.midpoint() sage: d1 = UHP.dist(m, g.start()) sage: d2 = UHP.dist(m, g.end()) sage: bool(abs(d1 - d2) < 10**-9) True
Infinite geodesics have no midpoint::
sage: UHP.get_geodesic(0, 2).midpoint() Traceback (most recent call last): ... ValueError: the length must be finite
TESTS:
This checks :trac:`20330` so that geodesics defined by symbolic expressions do not generate runtime errors. ::
sage: g=HyperbolicPlane().UHP().get_geodesic(-1+I,1+I) sage: g.midpoint() Point in UHP 1/2*(sqrt(2)*e^(1/2*arccosh(3)) - sqrt(2) + (I - 1)*e^(1/2*arccosh(3)) + I - 1)/((1/4*I - 1/4)*sqrt(2)*e^(1/2*arccosh(3)) - (1/4*I - 1/4)*sqrt(2) + 1/2*e^(1/2*arccosh(3)) + 1/2)
Check that floating points remain floating points in :meth:`midpoint` ::
sage: UHP = HyperbolicPlane().UHP() sage: g = UHP.get_geodesic(CC(0,1), CC(2,2)) sage: g.midpoint() Point in UHP 0.666666666666667 + 1.69967317119760*I sage: parent(g.midpoint().coordinates()) Complex Field with 53 bits of precision
"""
# If the matrix is symbolic then needs to be simplified in order to # make the calculations easier for the symbolic calculus module. (abs(real(start - end)) < EPSILON and imag(start - end) < EPSILON)): else: end_p = end
r""" Return the angle between any two given completed geodesics if they intersect.
INPUT:
- ``other`` -- a hyperbolic geodesic in the UHP model
OUTPUT:
- the angle in radians between the two given geodesics
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: g = UHP.get_geodesic(2, 4) sage: h = UHP.get_geodesic(3, 3 + I) sage: g.angle(h) 1/2*pi sage: numerical_approx(g.angle(h)) 1.57079632679490
.. PLOT::
UHP = HyperbolicPlane().UHP() g = UHP.get_geodesic(2, 4) h = UHP.get_geodesic(3, 3 + I) sphinx_plot(g.plot()+h.plot())
If the geodesics are identical, return angle 0::
sage: g.angle(g) 0
It is an error to ask for the angle of two geodesics that do not intersect::
sage: g = UHP.get_geodesic(2, 4) sage: h = UHP.get_geodesic(5, 7) sage: g.angle(h) Traceback (most recent call last): ... ValueError: geodesics do not intersect
"""
# Make sure the segments are complete or intersect not self.intersection(other)): print("Warning: Geodesic segments do not intersect. " "The angle between them is not defined.\n" "Returning the angle between their completions.")
# Make sure both are in the same model
for k in self.ideal_endpoints()], key=str) for k in other.ideal_endpoints()], key=str) # if the geodesics are equal, the angle between them is 0 abs(p2 - q2) < EPSILON): # So we send it to the geodesic with endpoints [0, oo] else: # geodesic is a straight line, so we send it to the geodesic # with endpoints [0,oo] T = HyperbolicGeodesicUHP._crossratio_matrix(p1, p1 + 1, p2) # b1 and b2 are the endpoints of the image of other # If other is now a straight line... # then since they intersect, they are equal return 0
################## # Helper methods # ##################
def _get_B(a): r""" Helper function to get an appropriate matrix transforming (0,1,inf) -> (0,I,inf) based on the type of a
INPUT:
- ``a`` -- an element to identify the class of the resulting matrix.
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: g = UHP.random_geodesic() sage: B = g._get_B(CDF.an_element()); B [ 1.0 0.0] [ 0.0 -1.0*I] sage: type(B) <type 'sage.matrix.matrix_complex_double_dense.Matrix_complex_double_dense'>
::
sage: B = g._get_B(SR(1)); B [ 1 0] [ 0 -I] sage: type(B) <type 'sage.matrix.matrix_symbolic_dense.Matrix_symbolic_dense'>
::
sage: B = g._get_B(complex(1)); B [ 1.0 0.0] [ 0.0 -1.0*I] sage: type(B) <type 'sage.matrix.matrix_complex_double_dense.Matrix_complex_double_dense'>
::
sage: B = g._get_B(QQbar(1+I)); B [ 1 0] [ 0 -I] sage: type(B[1,1]) <class 'sage.rings.qqbar.AlgebraicNumber'> sage: type(B) <type 'sage.matrix.matrix_generic_dense.Matrix_generic_dense'>
"""
raise ValueError("invalid number") else: raise ValueError("not a complex number")
r""" Given the coordinates of a geodesic in hyperbolic space, return the hyperbolic isometry that sends that geodesic to the geodesic through 0 and infinity that also sends the point ``p`` to `i`.
INPUT:
- ``p`` -- the coordinates of the
EXAMPLES::
sage: UHP = HyperbolicPlane().UHP() sage: (p1, p2) = [UHP.random_point() for k in range(2)] sage: g = UHP.get_geodesic(p1, p2) sage: m = g.midpoint() sage: A = g._to_std_geod(m.coordinates()) # Send midpoint to I. sage: A = UHP.get_isometry(A) sage: [s, e]= g.complete().endpoints() sage: bool(abs(A(s).coordinates()) < 10**-9) True sage: bool(abs(A(m).coordinates() - I) < 10**-9) True sage: bool(abs(A(e).coordinates()) > 10**9) True
TESTS:
Check that floating points remain floating points through this method::
sage: H = HyperbolicPlane() sage: g = H.UHP().get_geodesic(CC(0,1), CC(2,2)) sage: gc = g.complete() sage: parent(gc._to_std_geod(g.start().coordinates())) Full MatrixSpace of 2 by 2 dense matrices over Complex Field with 53 bits of precision
"""
# outmat below will be returned after we normalize the determinant. # Small imaginary part. # Set it equal to its real part.
def _crossratio_matrix(p0, p1, p2): # UHP r""" Given three points (the list `p`) in `\mathbb{CP}^{1}` in affine coordinates, return the linear fractional transformation taking the elements of `p` to `0`, `1`, and `\infty`.
INPUT:
- a list of three distinct elements of `\mathbb{CP}^1` in affine coordinates; that is, each element must be a complex number, `\infty`, or symbolic.
OUTPUT:
- an element of `\GL(2,\CC)`
EXAMPLES::
sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic \ ....: import HyperbolicGeodesicUHP sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry \ ....: import moebius_transform sage: UHP = HyperbolicPlane().UHP() sage: (p1, p2, p3) = [UHP.random_point().coordinates() ....: for k in range(3)] sage: A = HyperbolicGeodesicUHP._crossratio_matrix(p1, p2, p3) sage: bool(abs(moebius_transform(A, p1)) < 10**-9) True sage: bool(abs(moebius_transform(A, p2) - 1) < 10**-9) True sage: bool(moebius_transform(A, p3) == infinity) True sage: (x,y,z) = var('x,y,z'); sage: HyperbolicGeodesicUHP._crossratio_matrix(x,y,z) [ y - z -x*(y - z)] [ -x + y (x - y)*z]
"""
return matrix([[0, -(p1 - p2)], [-1, p2]]) [p1 - p0, (p1 - p0)*(-p2)]])
#*********************************************************************** # Other geodesics #***********************************************************************
r""" A geodesic in the Poincaré disk model.
Geodesics in this model are represented by segments of circles contained within the unit disk that are orthogonal to the boundary of the disk, plus all diameters of the disk.
INPUT:
- ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space representing the start of the geodesic
- ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space representing the end of the geodesic
EXAMPLES::
sage: PD = HyperbolicPlane().PD() sage: g = PD.get_geodesic(PD.get_point(I), PD.get_point(-I/2)) sage: g = PD.get_geodesic(I,-I/2) sage: h = PD.get_geodesic(-1/2+I/2,1/2+I/2)
.. PLOT::
PD = HyperbolicPlane().PD() g = PD.get_geodesic(I,-I/2) h = PD.get_geodesic(-0.5+I*0.5,0.5+I*0.5) sphinx_plot(g.plot()+h.plot(color='green'))
"""
r""" Plot ``self``.
EXAMPLES:
First some lines::
sage: PD = HyperbolicPlane().PD() sage: PD.get_geodesic(0, 1).plot() Graphics object consisting of 2 graphics primitives
.. PLOT::
sphinx_plot(HyperbolicPlane().PD().get_geodesic(0, 1).plot())
::
sage: PD.get_geodesic(0, 0.3+0.8*I).plot() Graphics object consisting of 2 graphics primitives
.. PLOT::
PD = HyperbolicPlane().PD() sphinx_plot(PD.get_geodesic(0, 0.3+0.8*I).plot())
Then some generic geodesics::
sage: PD.get_geodesic(-0.5, 0.3+0.4*I).plot() Graphics object consisting of 2 graphics primitives sage: g = PD.get_geodesic(-1, exp(3*I*pi/7)) sage: G = g.plot(linestyle="dashed",color="red"); G Graphics object consisting of 2 graphics primitives sage: h = PD.get_geodesic(exp(2*I*pi/11), exp(1*I*pi/11)) sage: H = h.plot(thickness=6, color="orange"); H Graphics object consisting of 2 graphics primitives sage: show(G+H)
.. PLOT::
PD = HyperbolicPlane().PD() PD.get_geodesic(-0.5, 0.3+0.4*I).plot() g = PD.get_geodesic(-1, exp(3*I*pi/7)) G = g.plot(linestyle="dashed",color="red") h = PD.get_geodesic(exp(2*I*pi/11), exp(1*I*pi/11)) H = h.plot(thickness=6, color="orange") sphinx_plot(G+H)
"""
# Check to see if it's a line else: # If we are here, we know it's not a line # So we compute the center and radius of the circle # Now we calculate the angles for the arc # Make sure the sector is inside the disk sector=(theta1, theta2), **opts)
r""" A geodesic in the Klein disk model.
Geodesics are represented by the chords, straight line segments with ideal endpoints on the boundary circle.
INPUT:
- ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space representing the start of the geodesic
- ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space representing the end of the geodesic
EXAMPLES::
sage: KM = HyperbolicPlane().KM() sage: g = KM.get_geodesic(KM.get_point((0.1,0.9)), KM.get_point((-0.1,-0.9))) sage: g = KM.get_geodesic((0.1,0.9),(-0.1,-0.9)) sage: h = KM.get_geodesic((-0.707106781,-0.707106781),(0.707106781,-0.707106781)) sage: P = g.plot(color='orange')+h.plot(); P Graphics object consisting of 4 graphics primitives
.. PLOT::
KM = HyperbolicPlane().KM() g = KM.get_geodesic((0.1,0.9), (-0.1,-0.9)) h = KM.get_geodesic((-0.707106781,-0.707106781), (0.707106781,-0.707106781)) sphinx_plot(g.plot(color='orange')+h.plot())
"""
r""" Plot ``self``.
EXAMPLES::
sage: HyperbolicPlane().KM().get_geodesic((0,0), (1,0)).plot() Graphics object consisting of 2 graphics primitives
.. PLOT::
KM = HyperbolicPlane().KM() sphinx_plot(KM.get_geodesic((0,0), (1,0)).plot())
"""
r""" A geodesic in the hyperboloid model.
Valid points in the hyperboloid model satisfy :math:`x^2 + y^2 - z^2 = -1`
INPUT:
- ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space representing the start of the geodesic
- ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space representing the end of the geodesic
EXAMPLES::
sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: HM = HyperbolicPlane().HM() sage: p1 = HM.get_point((4, -4, sqrt(33))) sage: p2 = HM.get_point((-3,-3,sqrt(19))) sage: g = HM.get_geodesic(p1, p2) sage: g = HM.get_geodesic((4, -4, sqrt(33)), (-3, -3, sqrt(19)))
.. PLOT::
HM = HyperbolicPlane().HM() p1 = HM.get_point((4, -4, sqrt(33))) p2 = HM.get_point((-3,-3,sqrt(19))) g = HM.get_geodesic(p1, p2) sphinx_plot(g.plot(color='blue'))
"""
r""" Plot ``self``.
EXAMPLES::
sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic \ ....: import * sage: g = HyperbolicPlane().HM().random_geodesic() sage: g.plot() Graphics3d Object
.. PLOT::
sphinx_plot(HyperbolicPlane().HM().random_geodesic().plot())
"""
# Lorentzian Gram Shmidt. The original vectors will be # u1, u2 and the orthogonal ones will be v1, v2. Except # v1 = u1, and I don't want to declare another variable, # hence the odd naming convention above. # We need the Lorentz dot product of v1 and u2. # Now v1 and v2 are Lorentz orthogonal, and |v1| = -1, |v2|=1 # That is, v1 is unit timelike and v2 is unit spacelike. # This means that cosh(x)*v1 + sinh(x)*v2 is unit timelike.
|