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: HyperbolicPlane().UHP().get_geodesic(2, 3)
Geodesic in UHP from 2 to 3
sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3 + I)
sage: g.length()
arccosh(11/2)

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: HyperbolicPlane().UHP().get_geodesic(1,2) == HyperbolicPlane().UHP().get_geodesic(2,1)
False

Todo

Implement a parent for all geodesics of the hyperbolic plane?

class sage.geometry.hyperbolic_space.hyperbolic_geodesic.HyperbolicGeodesic(model, start, end, **graphics_options)

Bases: sage.structure.sage_object.SageObject

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:

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))
angle(other)

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, 9/13*I + 6/13)
sage: g.angle(h)
1/2*pi
common_perpendicula(other)

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

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
complete()

Return the geodesic with ideal endpoints in bounded models. Raise a NotImplementedError in models that are not bounded.

EXAMPLES:

sage: H = HyperbolicPlane()
sage: H.UHP().get_geodesic(1 + I, 1 + 3*I).complete()
Geodesic in UHP from 1 to +Infinity

sage: H.PD().get_geodesic(0, I/2).complete()
Geodesic in PD from -I to I

sage: H.KM().get_geodesic((0,0), (0, 1/2)).complete()
Geodesic in KM from (0, -1) to (0, 1)

sage: H.HM().get_geodesic((0,0,1), (1, 0, sqrt(2))).complete()
Geodesic in HM from (0, 0, 1) to (1, 0, sqrt(2))

sage: H.HM().get_geodesic((0,0,1), (1, 0, sqrt(2))).complete().is_complete()
True
dist(other)

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
end()

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
endpoints()

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]
graphics_options()

Return the graphics options of self.

EXAMPLES:

sage: g = HyperbolicPlane().UHP().get_geodesic(I, 2*I, color="red")
sage: g.graphics_options()
{'color': 'red'}
ideal_endpoints()

Return the ideal endpoints in bounded models. Raise a NotImplementedError in models that are not bounded.

EXAMPLES:

sage: H = HyperbolicPlane()
sage: H.UHP().get_geodesic(1 + I, 1 + 3*I).ideal_endpoints()
[Boundary point in UHP 1, Boundary point in UHP +Infinity]

sage: H.PD().get_geodesic(0, I/2).ideal_endpoints()
[Boundary point in PD -I, Boundary point in PD I]

sage: H.KM().get_geodesic((0,0), (0, 1/2)).ideal_endpoints()
[Boundary point in KM (0, -1), Boundary point in KM (0, 1)]

sage: H.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
intersection(other)

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()
is_asymptotically_parallel(other)

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

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

No hyperbolic geodesic is asymptotically parallel to itself:

sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5)
sage: g.is_asymptotically_parallel(g)
False
is_complete()

Return True if self is a complete geodesic (that is, both endpoints are on the ideal boundary) and False otherwise.

EXAMPLES:

sage: HyperbolicPlane().UHP().get_geodesic(I, 2*I).is_complete()
False

sage: HyperbolicPlane().UHP().get_geodesic(0, I).is_complete()
False

sage: HyperbolicPlane().UHP().get_geodesic(0, infinity).is_complete()
True
is_parallel(other)

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
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5)
sage: h = HyperbolicPlane().UHP().get_geodesic(-2,4)
sage: g.is_parallel(h)
True

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
is_ultra_parallel(other)

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,3)
sage: g.is_ultra_parallel(h)
True
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5)
sage: h = HyperbolicPlane().UHP().get_geodesic(2,6)
sage: g.is_ultra_parallel(h)
False
sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5)
sage: g.is_ultra_parallel(g)
False
length()

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)
midpoint()

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
model()

Return the model to which the HyperbolicGeodesic belongs.

EXAMPLES:

sage: HyperbolicPlane().UHP().get_geodesic(I, 2*I).model()
Hyperbolic plane in the Upper Half Plane Model model

sage: HyperbolicPlane().PD().get_geodesic(0, I/2).model()
Hyperbolic plane in the Poincare Disk Model model

sage: HyperbolicPlane().KM().get_geodesic((0, 0), (0, 1/2)).model()
Hyperbolic plane in the Klein Disk Model model

sage: HyperbolicPlane().HM().get_geodesic((0, 0, 1), (0, 1, sqrt(2))).model()
Hyperbolic plane in the Hyperboloid Model model
perpendicular_bisector()

Return the perpendicular bisector of self if self has finite length. Here distance is hyperbolic distance.

EXAMPLES:

sage: g = HyperbolicPlane().PD().random_geodesic()
sage: h = g.perpendicular_bisector()
sage: bool(h.intersection(g)[0].coordinates() - g.midpoint().coordinates() < 10**-9)
True

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
reflection_involution()

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: A = H.HM().get_geodesic((0,0,1), (1,0, n(sqrt(2)))).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 mobius_transform
sage: R = H.PD().get_geodesic(-1,1).reflection_involution()
sage: bool(mobius_transform(R.matrix(), 0) == 0)
True
start()

Return the starting point of the geodesic.

EXAMPLES:

sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3*I)
sage: g.start()
Point in UHP I
to_model(model)

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
update_graphics(update=False, **options)

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}
class sage.geometry.hyperbolic_space.hyperbolic_geodesic.HyperbolicGeodesicHM(model, start, end, **graphics_options)

Bases: sage.geometry.hyperbolic_space.hyperbolic_geodesic.HyperbolicGeodesic

A geodesic in the hyperboloid model.

INPUT:

  • start – a HyperbolicPoint in hyperbolic space representing the start of the geodesic
  • end – a 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: g = HM.get_geodesic(HM.get_point((0, 0, 1)), HM.get_point((0,1,sqrt(2))))
sage: g = HM.get_geodesic((0, 0, 1), (0, 1, sqrt(2)))
show(show_hyperboloid=True, **graphics_options)

Plot self.

EXAMPLES:

sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import *
sage: g = HyperbolicPlane().HM().random_geodesic()
sage: g.show()
Graphics3d Object
class sage.geometry.hyperbolic_space.hyperbolic_geodesic.HyperbolicGeodesicKM(model, start, end, **graphics_options)

Bases: sage.geometry.hyperbolic_space.hyperbolic_geodesic.HyperbolicGeodesic

A geodesic in the Klein disk model.

INPUT:

  • start – a HyperbolicPoint in hyperbolic space representing the start of the geodesic
  • end – a 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)), KM.get_point((0,1/2)))
sage: g = KM.get_geodesic((0,1), (0,1/2))
show(boundary=True, **options)

Plot self.

EXAMPLES:

sage: HyperbolicPlane().KM().get_geodesic((0,0), (1,0)).show()
Graphics object consisting of 2 graphics primitives
class sage.geometry.hyperbolic_space.hyperbolic_geodesic.HyperbolicGeodesicPD(model, start, end, **graphics_options)

Bases: sage.geometry.hyperbolic_space.hyperbolic_geodesic.HyperbolicGeodesic

A geodesic in the Poincaré disk model.

INPUT:

  • start – a HyperbolicPoint in hyperbolic space representing the start of the geodesic
  • end – a 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)
show(boundary=True, **options)

Plot self.

EXAMPLES:

First some lines:

sage: PD = HyperbolicPlane().PD()
sage: PD.get_geodesic(0, 1).show()
Graphics object consisting of 2 graphics primitives
sage: PD.get_geodesic(0, 0.3+0.8*I).show()
Graphics object consisting of 2 graphics primitives

Then some generic geodesics:

sage: PD.get_geodesic(-0.5, 0.3+0.4*I).show()
Graphics object consisting of 2 graphics primitives
sage: PD.get_geodesic(-1, exp(3*I*pi/7)).show(linestyle="dashed", color="red")
Graphics object consisting of 2 graphics primitives
sage: PD.get_geodesic(exp(2*I*pi/11), exp(1*I*pi/11)).show(thickness=6, color="orange")
Graphics object consisting of 2 graphics primitives
class sage.geometry.hyperbolic_space.hyperbolic_geodesic.HyperbolicGeodesicUHP(model, start, end, **graphics_options)

Bases: sage.geometry.hyperbolic_space.hyperbolic_geodesic.HyperbolicGeodesic

Create a geodesic in the upper half plane model.

INPUT:

  • start – a HyperbolicPoint in hyperbolic space representing the start of the geodesic
  • end – a 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)
angle(other)

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

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
common_perpendicular(other)

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

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
ideal_endpoints()

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]
intersection(other)

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]
midpoint()

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
perpendicular_bisector()

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

Infinite geodesics cannot be bisected:

sage: UHP.get_geodesic(0, 1).perpendicular_bisector()
Traceback (most recent call last):
...
ValueError: the length must be finite
reflection_involution()

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]
show(boundary=True, **options)

Plot self.

EXAMPLES:

sage: UHP = HyperbolicPlane().UHP()
sage: UHP.get_geodesic(0, 1).show()
Graphics object consisting of 2 graphics primitives
sage: UHP.get_geodesic(I, 3+4*I).show(linestyle="dashed", color="red")
Graphics object consisting of 2 graphics primitives