hyperbolic

Two dimensional hyperbolic geometry.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()

Points in the hyperbolic plane can be specified directly with coordinates in the upper (complex) half plane:

sage: H(1 + I)
1 + I

The hyperbolic plane is defined over a fixed base ring; the rationals if no base has been specified explicitly:

sage: H(sqrt(2) + I)
Traceback (most recent call last):
...
TypeError: unable to convert sqrt(2) to a rational

We can use a bigger field instead:

sage: H_algebraic = HyperbolicPlane(AA)
sage: H_algebraic(sqrt(2) + I)
1.414213562373095? + 1.000000000000000?*I

Given two points in the hyperbolic plane, we can form the geodesic they lay on:

sage: a = H(I)
sage: b = H(2*I)
sage: ab = H.geodesic(a, b)
sage: ab
{-x = 0}

Note that such a geodesic is oriented. The orientation is such that when we replace the = in the above representation with a , we obtain the half space on its left:

sage: H.geodesic(a, b).left_half_space()
{x ≤ 0}

We can pass explicitly to the unoriented geodesic. Note that the oriented and the unoriented version of a geodesic are not considered equal:

sage: ab.unoriented()
{x = 0}
sage: ab == ab.unoriented()
False
sage: ab.is_subset(ab.unoriented())
True
sage: ab.unoriented().is_subset(ab)
True

A vertical can also be specified directly:

sage: H.vertical(0)
{-x = 0}

We can also create ideal, i.e., infinite, points in the hyperbolic plane and construct the geodesic that connects them:

sage: H(1)
1

sage: H(oo)
∞

sage: H.geodesic(1, oo)
{-x + 1 = 0}

The geodesic that is given by a half circle in the upper half plane can be created directly by providing its midpoint and the square of its radius:

sage: H.half_circle(0, 1)
{(x^2 + y^2) - 1 = 0}

Geodesics can be intersected:

sage: H.half_circle(0, 1).intersection(H.vertical(0))
I

sage: H.half_circle(0, 1).intersection(H.half_circle(0, 2))
{}

The intersection of two geodesics might be an ideal point:

sage: H.vertical(-1).intersection(H.vertical(1))
∞

General convex subsets of the hyperbolic plane can be constructed by intersecting half spaces; this way we can construct (possibly unbounded) convex polygons:

sage: P = H.intersection(
....:   H.vertical(-1).right_half_space(),
....:   H.vertical(1).left_half_space(),
....:   H.half_circle(0, 2).left_half_space())

sage: P
{x - 1 ≤ 0} ∩ {x + 1 ≥ 0} ∩ {(x^2 + y^2) - 2 ≥ 0}

We can also intersect objects that are not half spaces:

sage: P.intersection(H.vertical(0))
{x = 0} ∩ {(x^2 + y^2) - 2 ≥ 0}

Warning

Our implementation was not conceived with inexact rings in mind. Due to popular demand, we do allow inexact base rings but many operations have not been tuned for numerical stability, yet.

To make our implementation work for a variety of (inexact) base rings, we delegate some numerically critical bits to a separate “geometry” class that can be specified when creating a hyperbolic plane. For exact base rings, this defaults to the HyperbolicExactGeometry which uses exact text book algorithms.

Over inexact rings, we implement a HyperbolicEpsilonGeometry which considers two numbers two be equal if they differ only by a small (relative) error. This should work reasonably well for inexact base rings that have no denormalized numbers, i.e., it will work well for RR and general RealField.

sage: HyperbolicPlane(RR) Hyperbolic Plane over Real Field with 53 bits of precision

There is currently no implementation that works well with RDF. It should be easy to adapt HyperbolicEpsilonGeometry for that purpose to take into account denormalized numbers:

sage: HyperbolicPlane(RDF)
Traceback (most recent call last):
...
ValueError: geometry must be specified for HyperbolicPlane over inexact rings

There is currently no implementation that works for ball arithmetic:

sage: HyperbolicPlane(RBF)
Traceback (most recent call last):
...
ValueError: geometry must be specified for HyperbolicPlane over inexact rings

Note

This module implements different kinds of convex subsets as different classes. The alternative would have been to represent all subsets as collections of (in)equalities in some hyperbolic model. There is for example a HyperbolicUnorientedSegment and a HyperbolicConvexPolygon even though the former could in principle be expressed as the latter. The advantage of this approach is that we can provide a more natural user interface, e.g., a segment has a single underlying geodesic whereas the corresponding convex polygon would have four (or three). Similarly, an oriented geodesic (which cannot really be expressed as a convex polygon due to the orientation) has a left and a right associated half spaces.

Sometimes it can, however, be beneficial to treat each subset as a convex polygon. In such a case, one can explicitly create polygons from subsets by intersecting their HyperbolicConvexSet.half_spaces():

sage: g = H.vertical(0)
sage: P = H.polygon(g.half_spaces(), check=False, assume_minimal=True)
sage: P
{x ≤ 0} ∩ {x ≥ 0}

Note that such an object might not be fully functional since some methods may assume that the object is an actual polygon:

sage: P.dimension()
2

Similarly, a geodesic can be treated as a segment without endpoints:

sage: H.segment(g, start=None, end=None, check=False, assume_normalized=True)
{-x = 0}

Note

This implementation is an alternative to the one that comes with SageMath. The one in SageMath has a number of issues, see e.g. https://trac.sagemath.org/ticket/32400. The implementation here tries very hard to perform all operations over the same base ring, have the best complexities possible, keep all objects in the same (Klein) model, is not using any symbolic expressions, and tries to produce better plots.

class flatsurf.geometry.hyperbolic.HyperbolicConvexFacade(parent, category=None)[source]

A convex subset of the hyperbolic plane that is itself a parent.

This is the base class for all hyperbolic convex sets that are not points. This class solves the problem that we want convex sets to be “elements” of the hyperbolic plane but at the same time, we want these sets to live as parents in the category framework of SageMath; so they have be a Parent with hyperbolic points as their Element class.

SageMath provides the (not very frequently used and somewhat flaky) facade mechanism for such parents. Such sets being a facade, their points can be both their elements and the elements of the hyperbolic plane.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: v = H.vertical(0)
sage: p = H(0)
sage: p in v
True
sage: p.parent() is H
True
sage: q = v.an_element()
sage: q
I
sage: q.parent() is H
True
__dict__ = mappingproxy({'__module__': 'flatsurf.geometry.hyperbolic', '__doc__': '\n    A convex subset of the hyperbolic plane that is itself a parent.\n\n    This is the base class for all hyperbolic convex sets that are not points.\n    This class solves the problem that we want convex sets to be "elements" of\n    the hyperbolic plane but at the same time, we want these sets to live as\n    parents in the category framework of SageMath; so they have be a Parent\n    with hyperbolic points as their Element class.\n\n    SageMath provides the (not very frequently used and somewhat flaky) facade\n    mechanism for such parents. Such sets being a facade, their points can be\n    both their elements and the elements of the hyperbolic plane.\n\n    EXAMPLES::\n\n        sage: from flatsurf import HyperbolicPlane\n        sage: H = HyperbolicPlane()\n        sage: v = H.vertical(0)\n        sage: p = H(0)\n        sage: p in v\n        True\n        sage: p.parent() is H\n        True\n        sage: q = v.an_element()\n        sage: q\n        I\n        sage: q.parent() is H\n        True\n\n    TESTS::\n\n        sage: from flatsurf.geometry.hyperbolic import HyperbolicConvexFacade\n        sage: isinstance(v, HyperbolicConvexFacade)\n        True\n\n    ', '__init__': <function HyperbolicConvexFacade.__init__>, 'parent': <function HyperbolicConvexFacade.parent>, '_element_constructor_': <function HyperbolicConvexFacade._element_constructor_>, 'base_ring': <function HyperbolicConvexFacade.base_ring>, '__dict__': <attribute '__dict__' of 'HyperbolicConvexFacade' objects>, '__annotations__': {}})
__init__(parent, category=None)[source]
__module__ = 'flatsurf.geometry.hyperbolic'
base_ring()[source]

Return the ring over which points of this set are defined.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: v = H.vertical(0)
sage: v.base_ring()
Rational Field
parent()[source]

Return the hyperbolic plane this is a subset of.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: v = H.vertical(0)
sage: v.parent()
Hyperbolic Plane over Rational Field
class flatsurf.geometry.hyperbolic.HyperbolicConvexPolygon(parent, half_spaces, vertices, category=None)[source]

A (possibly unbounded) closed polygon in the HyperbolicPlane, i.e., the intersection of a finite number of half spaces.

INPUT:

  • parent – the HyperbolicPlane of which this is a subset

  • half_spaces – the HyperbolicHalfSpace of which this is an intersection

  • vertices – marked vertices that should additionally be kept track of

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: P = H.polygon([
....:     H.vertical(0).right_half_space(),
....:     H.vertical(1).left_half_space(),
....:     H.half_circle(0, 2).left_half_space()])

See also

Use HyperbolicPlane.polygon() and HyperbolicPlane.intersection() to create polygons in the hyperbolic plane.

__annotations__ = {}
__eq__(other)[source]

Return whether this polygon is indistinguishable from other.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: P = H.polygon([H.vertical(1).left_half_space(), H.vertical(-1).right_half_space()])
sage: P == P
True

Marked vertices are taken into account to determine equality:

sage: Q = H.polygon([H.vertical(1).left_half_space(), H.vertical(-1).right_half_space()], marked_vertices=[I + 1])
sage: Q == Q
True
sage: P == Q
False
__hash__()[source]

Return a hash value for this polygon.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()

Since polygons are hashable, they can be put in a hash table, such as a Python set:

sage: S = {H.polygon([H.vertical(1).left_half_space(), H.vertical(-1).right_half_space()])}
__init__(parent, half_spaces, vertices, category=None)[source]
__module__ = 'flatsurf.geometry.hyperbolic'
_isometry_conditions(other)[source]

Return an iterable of primitive pairs that must map to each other in an isometry that maps this set to other.

Helper method for HyperbolicPlane._isometry_conditions().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: P = H.polygon([
....:     H.vertical(0).right_half_space(),
....:     H.half_circle(0, 4).left_half_space()],
....:     marked_vertices=[4*I])

sage: conditions = P._isometry_conditions(P)
sage: list(conditions)
[[({x ≥ 0}, {(x^2 + y^2) - 4 ≥ 0}),
  ({(x^2 + y^2) - 4 ≥ 0}, {x ≥ 0}),
  (4*I, 4*I)],
 [({x ≥ 0}, {x ≥ 0}),
  ({(x^2 + y^2) - 4 ≥ 0}, {(x^2 + y^2) - 4 ≥ 0}),
  (4*I, 4*I)],
 [({x ≥ 0}, {x ≥ 0}),
  ({(x^2 + y^2) - 4 ≥ 0}, {(x^2 + y^2) - 4 ≥ 0}),
  (4*I, 4*I)],
 [({x ≥ 0}, {(x^2 + y^2) - 4 ≥ 0}),
  ({(x^2 + y^2) - 4 ≥ 0}, {x ≥ 0}),
  (4*I, 4*I)]]

See also

HyperbolicConvexSet._isometry_conditions() for a general description.

r

_normalize(marked_vertices=False)[source]

Return a convex set describing the intersection of the half spaces underlying this polygon.

This implements HyperbolicConvexSet._normalize().

The half spaces are assumed to be already sorted respecting HyperbolicHalfSpaces._lt_().

ALGORITHM:

We compute the intersection of the half spaces in the Klein model in several steps:

  • Drop trivially redundant half spaces, e.g., repeated ones.

  • Handle the case that the intersection is empty or a single point, see _normalize_euclidean_boundary().

  • Compute the intersection of the corresponding half spaces in the Euclidean plane, see _normalize_drop_euclidean_redundant().

  • Remove redundant half spaces that make no contribution for the unit disk of the Klein model, see _normalize_drop_unit_disk_redundant().

  • Determine of which nature (point, segment, line, polygon) the intersection of half spaces is and return the resulting set.

INPUT:

  • marked_vertices – a boolean (default: False); whether to keep marked vertices when normalizing

Note

Over inexact rings, this is probably mostly useless.

_normalize_drop_euclidean_redundant(boundary)[source]

Return a minimal sublist of the half_spaces defining this polygon that describe their intersection as half spaces of the Euclidean plane.

Consider the half spaces in the Klein model. Ignoring the unit disk, they also describe half spaces in the Euclidean plane.

The half space boundary must be one of the half_spaces that defines a boundary edge of the intersection polygon in the Euclidean plane.

This is a helper method for _normalize().

ALGORITHM:

We use an approach similar to gift-wrapping (but from the inside) to remove redundant half spaces from the input list. We start from the boundary which is one of the minimal half spaces and extend to the full intersection by walking the sorted half spaces.

Since we visit each half space once, this reduction runs in linear time in the number of half spaces.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

A helper to create non-normalized polygons for testing:

sage: polygon = lambda *half_spaces: H.polygon(half_spaces, check=False, assume_sorted=False, assume_minimal=True)

An intersection which is a single point on the boundary of the unit disk:

sage: polygon(*H.infinity().half_spaces())._normalize_drop_euclidean_redundant(
....:     boundary=H.vertical(1).right_half_space())
{x ≤ 0} ∩ {x - 1 ≥ 0}

An intersection which is a segment outside of the unit disk:

sage: polygon(
....:     H.vertical(0).left_half_space(),
....:     H.vertical(0).right_half_space(),
....:     H.half_space(-2, -2, 1, model="klein"),
....:     H.half_space(17/8, 2, -1, model="klein"),
....: )._normalize_drop_euclidean_redundant(boundary=H.vertical(0).left_half_space())
{(x^2 + y^2) + 4*x + 3 ≤ 0} ∩ {x ≤ 0} ∩ {9*(x^2 + y^2) + 32*x + 25 ≥ 0} ∩ {x ≥ 0}

An intersection which is a polygon outside of the unit disk:

sage: polygon(
....:     H.half_space(0, 1, 0, model="klein"),
....:     H.half_space(1, -2, 0, model="klein"),
....:     H.half_space(-2, -2, 1, model="klein"),
....:     H.half_space(17/8, 2, -1, model="klein"),
....: )._normalize_drop_euclidean_redundant(boundary=H.half_space(17/8, 2, -1, model="klein"))
{(x^2 + y^2) + 4*x + 3 ≤ 0} ∩ {(x^2 + y^2) - 4*x + 1 ≥ 0} ∩ {9*(x^2 + y^2) + 32*x + 25 ≥ 0} ∩ {x ≥ 0}

An intersection which is an (unbounded) polygon touching the unit disk:

sage: polygon(
....:     H.vertical(-1).left_half_space(),
....:     H.vertical(1).right_half_space(),
....: )._normalize_drop_euclidean_redundant(boundary=H.vertical(1).right_half_space())
{x + 1 ≤ 0} ∩ {x - 1 ≥ 0}

An intersection which is a segment touching the unit disk:

sage: polygon(
....:     H.vertical(0).left_half_space(),
....:     H.vertical(0).right_half_space(),
....:     H.vertical(-1).left_half_space(),
....:     H.geodesic(-1, -2).right_half_space(),
....: )._normalize_drop_euclidean_redundant(boundary=H.vertical(0).left_half_space())
{x + 1 ≤ 0} ∩  {x ≤ 0} ∩ {(x^2 + y^2) + 3*x + 2 ≥ 0} ∩ {x ≥ 0}

An intersection which is a polygon inside the unit disk:

sage: polygon(
....:     H.vertical(1).left_half_space(),
....:     H.vertical(-1).right_half_space(),
....:     H.geodesic(0, -1).right_half_space(),
....:     H.geodesic(0, 1).left_half_space(),
....: )._normalize_drop_euclidean_redundant(boundary=H.geodesic(0, 1).left_half_space())
{(x^2 + y^2) - x ≥ 0} ∩ {x - 1 ≤ 0} ∩ {x + 1 ≥ 0} ∩ {(x^2 + y^2) + x ≥ 0}

A polygon which has no vertices inside the unit disk but intersects the unit disk:

sage: polygon(
....:     H.geodesic(2, 3).left_half_space(),
....:     H.geodesic(-3, -2).left_half_space(),
....:     H.geodesic(-1/2, -1/3).left_half_space(),
....:     H.geodesic(1/3, 1/2).left_half_space(),
....: )._normalize_drop_euclidean_redundant(boundary=H.geodesic(1/3, 1/2).left_half_space())
{6*(x^2 + y^2) - 5*x + 1 ≥ 0} ∩ {(x^2 + y^2) - 5*x + 6 ≥ 0} ∩ {(x^2 + y^2) + 5*x + 6 ≥ 0} ∩ {6*(x^2 + y^2) + 5*x + 1 ≥ 0}

A single half plane:

sage: polygon(
....:     H.vertical(0).left_half_space()
....: )._normalize_drop_euclidean_redundant(boundary=H.vertical(0).left_half_space())
{x ≤ 0}

A pair of anti-parallel half planes:

sage: polygon(
....:     H.geodesic(1/2, 2).left_half_space(),
....:     H.geodesic(-1/2, -2).right_half_space(),
....: )._normalize_drop_euclidean_redundant(boundary=H.geodesic(-1/2, -2).right_half_space())
{2*(x^2 + y^2) - 5*x + 2 ≥ 0} ∩ {2*(x^2 + y^2) + 5*x + 2 ≥ 0}

A pair of anti-parallel half planes in the upper half plane:

sage: polygon(
....:     H.vertical(1).left_half_space(),
....:     H.vertical(-1).right_half_space(),
....: )._normalize_drop_euclidean_redundant(boundary=H.vertical(1).left_half_space())
{x - 1 ≤ 0} ∩ {x + 1 ≥ 0}

sage: polygon(
....:     H.vertical(1).left_half_space(),
....:     H.vertical(-1).right_half_space(),
....: )._normalize_drop_euclidean_redundant(boundary=H.vertical(-1).right_half_space())
{x - 1 ≤ 0} ∩ {x + 1 ≥ 0}

A segment in the unit disk with several superfluous half planes at infinity:

sage: polygon(
....:     H.vertical(0).left_half_space(),
....:     H.vertical(0).right_half_space(),
....:     H.vertical(1).left_half_space(),
....:     H.vertical(1/2).left_half_space(),
....:     H.vertical(1/3).left_half_space(),
....:     H.vertical(1/4).left_half_space(),
....:     H.vertical(-1).right_half_space(),
....:     H.vertical(-1/2).right_half_space(),
....:     H.vertical(-1/3).right_half_space(),
....:     H.vertical(-1/4).right_half_space(),
....: )._normalize_drop_euclidean_redundant(boundary=H.vertical(0).left_half_space())
{x ≤ 0} ∩ {4*x + 1 ≥ 0} ∩ {x ≥ 0}

A polygon in the unit disk with several superfluous half planes:

sage: polygon(
....:     H.vertical(1).left_half_space(),
....:     H.vertical(-1).right_half_space(),
....:     H.geodesic(0, 1).left_half_space(),
....:     H.geodesic(0, -1).right_half_space(),
....:     H.vertical(2).left_half_space(),
....:     H.vertical(-2).right_half_space(),
....:     H.geodesic(0, 1/2).left_half_space(),
....:     H.geodesic(0, -1/2).right_half_space(),
....:     H.vertical(3).left_half_space(),
....:     H.vertical(-3).right_half_space(),
....:     H.geodesic(0, 1/3).left_half_space(),
....:     H.geodesic(0, -1/3).right_half_space(),
....: )._normalize_drop_euclidean_redundant(boundary=H.vertical(1).left_half_space())
{(x^2 + y^2) - x ≥ 0} ∩ {x - 1 ≤ 0} ∩ {x + 1 ≥ 0} ∩ {(x^2 + y^2) + x ≥ 0}

Note

There are some additional assumptions on the input than what is stated here. Please refer to the implementation.

_normalize_drop_unit_disk_redundant()[source]

Return the intersection of the Euclidean half_spaces defining this polygon with the unit disk.

The half_spaces must be minimal to describe their intersection in the Euclidean plane. If that intersection does not intersect the unit disk, then return the HyperbolicPlane.empty_set().

Otherwise, return a minimal sublist of half_spaces that describes the intersection inside the unit disk.

This is a helper method for _normalize().

ALGORITHM:

When passing to the Klein model, i.e., intersecting the polygon with the unit disk, some of the edges of the (possibly unbounded) polygon described by the half_spaces are unnecessary because they are not intersecting the unit disk.

If none of the edges intersect the unit disk, then the polygon has empty intersection with the unit disk.

Otherwise, we can drop the half spaces describing the edges that do not intersect the unit disk.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

A helper to create non-normalized polygons for testing:

sage: polygon = lambda *half_spaces: H.polygon(half_spaces, check=False, assume_sorted=False, assume_minimal=True)

An intersection which is a single point on the boundary of the unit disk:

sage: polygon(*H.infinity().half_spaces())._normalize_drop_unit_disk_redundant()
∞

An intersection which is a segment outside of the unit disk:

sage: polygon(
....:     H.vertical(0).left_half_space(),
....:     H.vertical(0).right_half_space(),
....:     H.half_space(-2, -2, 1, model="klein"),
....:     H.half_space(17/8, 2, -1, model="klein"),
....: )._normalize_drop_unit_disk_redundant()
{}

An intersection which is a polygon outside of the unit disk:

sage: polygon(
....:     H.half_space(0, 1, 0, model="klein"),
....:     H.half_space(1, -2, 0, model="klein"),
....:     H.half_space(-2, -2, 1, model="klein"),
....:     H.half_space(17/8, 2, -1, model="klein"),
....: )._normalize_drop_unit_disk_redundant()
{}

An intersection which is an (unbounded) polygon touching the unit disk:

sage: polygon(
....:     H.vertical(-1).left_half_space(),
....:     H.vertical(1).right_half_space())._normalize_drop_unit_disk_redundant()
∞

An intersection which is a segment touching the unit disk:

sage: polygon(
....:     H.vertical(0).left_half_space(),
....:     H.vertical(0).right_half_space(),
....:     H.vertical(-1).left_half_space(),
....:     H.geodesic(-1, -2).right_half_space())._normalize_drop_unit_disk_redundant()
∞

An intersection which is a polygon inside the unit disk:

sage: polygon(
....:     H.vertical(1).left_half_space(),
....:     H.vertical(-1).right_half_space(),
....:     H.geodesic(0, -1).right_half_space(),
....:     H.geodesic(0, 1).left_half_space())._normalize_drop_unit_disk_redundant()
{(x^2 + y^2) - x ≥ 0} ∩ {x - 1 ≤ 0} ∩ {x + 1 ≥ 0} ∩ {(x^2 + y^2) + x ≥ 0}

A polygon which has no vertices inside the unit disk but intersects the unit disk:

sage: polygon(
....:     H.geodesic(2, 3).left_half_space(),
....:     H.geodesic(-3, -2).left_half_space(),
....:     H.geodesic(-1/2, -1/3).left_half_space(),
....:     H.geodesic(1/3, 1/2).left_half_space())._normalize_drop_unit_disk_redundant()
{6*(x^2 + y^2) - 5*x + 1 ≥ 0} ∩ {(x^2 + y^2) - 5*x + 6 ≥ 0} ∩ {(x^2 + y^2) + 5*x + 6 ≥ 0} ∩ {6*(x^2 + y^2) + 5*x + 1 ≥ 0}

A single half plane:

sage: polygon(H.vertical(0).left_half_space())._normalize_drop_unit_disk_redundant()
{x ≤ 0}

A pair of anti-parallel half planes:

sage: polygon(
....:     H.geodesic(1/2, 2).left_half_space(),
....:     H.geodesic(-1/2, -2).right_half_space())._normalize_drop_unit_disk_redundant()
{2*(x^2 + y^2) - 5*x + 2 ≥ 0} ∩ {2*(x^2 + y^2) + 5*x + 2 ≥ 0}

A segment in the unit disk with a superfluous half plane at infinity:

sage: polygon(
....:     H.vertical(0).left_half_space(),
....:     H.vertical(0).right_half_space(),
....:     H.vertical(1).left_half_space())._normalize_drop_unit_disk_redundant()
{x = 0}

A polygon in the unit disk with several superfluous half planes:

sage: polygon(
....:     H.vertical(1).left_half_space(),
....:     H.vertical(-1).right_half_space(),
....:     H.geodesic(0, 1).left_half_space(),
....:     H.geodesic(0, -1).right_half_space(),
....:     H.vertical(2).left_half_space(),
....:     H.geodesic(0, 1/2).left_half_space())._normalize_drop_unit_disk_redundant()
{(x^2 + y^2) - x ≥ 0} ∩ {x - 1 ≤ 0} ∩ {x + 1 ≥ 0} ∩ {(x^2 + y^2) + x ≥ 0}

A segment touching the inside of the unit disk:

sage: polygon(
....:   H.vertical(1).left_half_space(),
....:   H.half_circle(0, 2).left_half_space(),
....:   H.vertical(0).left_half_space(),
....:   H.vertical(0).right_half_space(),
....: )._normalize_drop_unit_disk_redundant()
{x = 0} ∩ {(x^2 + y^2) - 2 ≥ 0}

An unbounded polygon touching the unit disk from the inside:

sage: polygon(
....:     H.vertical(1).left_half_space(),
....:     H.vertical(-1).right_half_space(),
....: )._normalize_drop_unit_disk_redundant()
{x - 1 ≤ 0} ∩ {x + 1 ≥ 0}

A segment inside the unit disk:

sage: polygon(
....:     H.vertical(0).right_half_space(),
....:     H.vertical(0).left_half_space(),
....:     H.geodesic(-2, 2).right_half_space(),
....:     H.geodesic(-1/2, 1/2).left_half_space(),
....: )._normalize_drop_unit_disk_redundant()
{x = 0} ∩ {(x^2 + y^2) - 4 ≤ 0} ∩ {4*(x^2 + y^2) - 1 ≥ 0}

Note

There are some additional assumptions on the input than what is stated here. Please refer to the implementation.

_normalize_euclidean_boundary()[source]

Return a half space whose (Euclidean) boundary intersects the boundary of the intersection of the half_spaces defining this polygon in more than a point.

Consider the half spaces in the Klein model. Ignoring the unit disk, they also describe half spaces in the Euclidean plane.

If their intersection contains a segment it must be on the boundary of one of the half_spaces which is returned by this method.

If this is not the case, and the intersection is empty in the hyperbolic plane, return the HyperbolicPlane.empty_set(). Otherwise, if the intersection is a point in the hyperbolic plane, return that point.

The half_spaces must already be sorted with respect to HyperbolicHalfSpaces._lt_().

This is a helper method for _normalize().

ALGORITHM:

We initially ignore the hyperbolic structure and just consider the half spaces of the Klein model as Euclidean half spaces.

We use a relatively standard randomized optimization approach to find a point in the intersection: we randomly shuffle the half spaces and then optimize a segment on some boundary of the half spaces. The randomization makes this a linear time algorithm, see e.g., https://www2.cs.arizona.edu/classes/cs437/spring21/Lecture4.pdf.

If the only segment we can construct is a point, then the intersection is a single point in the Euclidean plane. The intersection in the hyperbolic plane might be a single point or empty.

If not even a point exists, the intersection is empty in the Euclidean plane and therefore empty in the hyperbolic plane.

Note that the segment returned might not be within the unit disk.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

A helper to create non-normalized polygons for testing:

sage: polygon = lambda *half_spaces: H.polygon(half_spaces, check=False, assume_sorted=False, assume_minimal=True)

Make the following randomized tests reproducible:

sage: set_random_seed(0)

An intersection that is already empty in the Euclidean plane:

sage: polygon(
....:     H.geodesic(2, 1/2).left_half_space(),
....:     H.geodesic(-1/2, -2).left_half_space()
....: )._normalize_euclidean_boundary()
{}

An intersection which in the Euclidean plane is a single point but outside the unit disk:

sage: polygon(
....:     H.half_space(0, 1, 0, model="klein"),
....:     H.half_space(0, -1, 0, model="klein"),
....:     H.half_space(2, 2, -1, model="klein"),
....:     H.half_space(-2, -2, 1, model="klein"),
....: )._normalize_euclidean_boundary()
{}

An intersection which is a single point inside the unit disk:

sage: polygon(*H(I).half_spaces())._normalize_euclidean_boundary()
I

An intersection which is a single point on the boundary of the unit disk:

sage: polygon(*H.infinity().half_spaces())._normalize_euclidean_boundary()
{x - 1 ≥ 0}

An intersection which is a segment outside of the unit disk:

sage: polygon(
....:     H.vertical(0).left_half_space(),
....:     H.vertical(0).right_half_space(),
....:     H.half_space(-2, -2, 1, model="klein"),
....:     H.half_space(17/8, 2, -1, model="klein"),
....: )._normalize_euclidean_boundary()
{x ≤ 0}

An intersection which is a polygon outside of the unit disk:

sage: polygon(
....:     H.half_space(0, 1, 0, model="klein"),
....:     H.half_space(1, -2, 0, model="klein"),
....:     H.half_space(-2, -2, 1, model="klein"),
....:     H.half_space(17/8, 2, -1, model="klein"),
....: )._normalize_euclidean_boundary()
{9*(x^2 + y^2) + 32*x + 25 ≥ 0}

An intersection which is an (unbounded) polygon touching the unit disk:

sage: polygon(
....:     H.vertical(-1).left_half_space(),
....:     H.vertical(1).right_half_space(),
....: )._normalize_euclidean_boundary()
{x - 1 ≥ 0}

An intersection which is a segment touching the unit disk:

sage: polygon(
....:     H.vertical(0).left_half_space(),
....:     H.vertical(0).right_half_space(),
....:     H.vertical(-1).left_half_space(),
....:     H.geodesic(-1, -2).right_half_space(),
....: )._normalize_euclidean_boundary()
{x ≥ 0}

An intersection which is a polygon inside the unit disk:

sage: polygon(
....:     H.vertical(1).left_half_space(),
....:     H.vertical(-1).right_half_space(),
....:     H.geodesic(0, -1).right_half_space(),
....:     H.geodesic(0, 1).left_half_space(),
....: )._normalize_euclidean_boundary()
{(x^2 + y^2) - x ≥ 0}

A polygon which has no vertices inside the unit disk but intersects the unit disk:

sage: polygon(
....:     H.geodesic(2, 3).left_half_space(),
....:     H.geodesic(-3, -2).left_half_space(),
....:     H.geodesic(-1/2, -1/3).left_half_space(),
....:     H.geodesic(1/3, 1/2).left_half_space(),
....: )._normalize_euclidean_boundary()
{6*(x^2 + y^2) - 5*x + 1 ≥ 0}

A single half plane:

sage: polygon(
....:     H.vertical(0).left_half_space()
....: )._normalize_euclidean_boundary()
{x ≤ 0}

A pair of anti-parallel half planes:

sage: polygon(
....:     H.geodesic(1/2, 2).left_half_space(),
....:     H.geodesic(-1/2, -2).right_half_space(),
....: )._normalize_euclidean_boundary()
{2*(x^2 + y^2) - 5*x + 2 ≥ 0}
change(ring=None, geometry=None, oriented=None)[source]

Return a modified copy of this polygon.

INPUT:

  • ring – a ring (default: None to keep the current base_ring()); the ring over which the polygon will be defined.

  • geometry – a HyperbolicGeometry (default: None to keep the current geometry); the geometry that will be used for the polygon.

  • oriented – a boolean (default: None to keep the current orientedness); must be None or False since polygons cannot have an explicit orientation. See is_oriented().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

We change the ring over which a polygon is defined:

sage: P = H.polygon([
....:     H.vertical(1).left_half_space(),
....:     H.vertical(-1).right_half_space(),
....:     H.half_circle(0, 1).left_half_space()],
....:     marked_vertices=[I])

sage: P.change(ring=AA)
{x - 1 ≤ 0} ∩ {x + 1 ≥ 0} ∩ {(x^2 + y^2) - 1 ≥ 0} ∪ {I}

We cannot give a polygon an explicit orientation:

sage: P.change(oriented=False) == P
True

sage: P.change(oriented=True)
Traceback (most recent call last):
...
NotImplementedError: polygons cannot have an explicit orientation
dimension()[source]

Return the dimension of this polygon, i.e., 2.

This implements HyperbolicConvexSet.dimension().

Note that this also returns 2 if the actual dimension of the polygon is smaller. This is, however, only possible for polygons created with HyperbolicPlane.polygon() setting check=False.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: P = H.polygon([
....:     H.vertical(-1).right_half_space(),
....:     H.vertical(1).left_half_space(),
....:     H.half_circle(0, 1).left_half_space(),
....:     H.half_circle(0, 4).right_half_space(),
....: ])
sage: P.dimension()
2
edges(as_segments=False, marked_vertices=True)[source]

Return the segments and geodesics defining this polygon.

This implements HyperbolicConvexSet.edges() for polygons.

INPUT:

  • as_segments – a boolean (default: False); whether to also return the geodesics as segments with ideal end points.

  • marked_vertices – a boolean (default: True); if set, edges with end points at a marked vertex are reported, otherwise, marked vertices are completely ignored.

OUTPUT:

A set of segments and geodesics. Iteration through this set is in counterclockwise order with respect to the points of the set.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

The edges of a polygon:

sage: P = H.intersection(
....:   H.vertical(-1).right_half_space(),
....:   H.vertical(1).left_half_space(),
....:   H.half_circle(0, 1).left_half_space(),
....:   H.half_circle(0, 4).right_half_space())

sage: P.edges()
{{-x + 1 = 0} ∩ {2*(x^2 + y^2) - 5*x - 3 ≤ 0}, {-(x^2 + y^2) + 4 = 0} ∩ {(x^2 + y^2) - 5*x + 1 ≥ 0} ∩ {(x^2 + y^2) + 5*x + 1 ≥ 0}, {x + 1 = 0} ∩ {2*(x^2 + y^2) + 5*x - 3 ≤ 0}, {(x^2 + y^2) - 1 = 0}}

sage: [type(e) for e in P.edges()]
[<class 'flatsurf.geometry.hyperbolic.HyperbolicOrientedSegment_with_category_with_category'>,
 <class 'flatsurf.geometry.hyperbolic.HyperbolicOrientedSegment_with_category_with_category'>,
 <class 'flatsurf.geometry.hyperbolic.HyperbolicOrientedSegment_with_category_with_category'>,
 <class 'flatsurf.geometry.hyperbolic.HyperbolicOrientedGeodesic_with_category_with_category'>]

sage: [type(e) for e in P.edges(as_segments=True)]
[<class 'flatsurf.geometry.hyperbolic.HyperbolicOrientedSegment_with_category_with_category'>,
 <class 'flatsurf.geometry.hyperbolic.HyperbolicOrientedSegment_with_category_with_category'>,
 <class 'flatsurf.geometry.hyperbolic.HyperbolicOrientedSegment_with_category_with_category'>,
 <class 'flatsurf.geometry.hyperbolic.HyperbolicOrientedSegment_with_category_with_category'>]

The edges of a polygon with marked vertices:

sage: P = H.convex_hull(-1, 1, I, 2*I, marked_vertices=True)
sage: P.edges()
{{-(x^2 + y^2) - 3*x + 4 = 0} ∩ {3*(x^2 + y^2) - 25*x - 12 ≤ 0}, {-(x^2 + y^2) + 3*x + 4 = 0} ∩ {3*(x^2 + y^2) + 25*x - 12 ≤ 0}, {(x^2 + y^2) - 1 = 0} ∩ {x ≤ 0}, {(x^2 + y^2) - 1 = 0} ∩ {x ≥ 0}}
sage: P.edges(marked_vertices=False)
{{-(x^2 + y^2) - 3*x + 4 = 0} ∩ {3*(x^2 + y^2) - 25*x - 12 ≤ 0}, {-(x^2 + y^2) + 3*x + 4 = 0} ∩ {3*(x^2 + y^2) + 25*x - 12 ≤ 0}, {(x^2 + y^2) - 1 = 0}}
half_spaces()[source]

Return a minimal set of half spaces whose intersection this polygon is.

This implements HyperbolicConvexSet.half_spaces().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

Marked vertices are not encoded in the half spaces in any way:

sage: P = H.polygon([
....:   H.vertical(1).left_half_space(),
....:   H.vertical(-1).right_half_space(),
....:   H.half_circle(0, 1).left_half_space(),
....:   H.half_circle(0, 4).right_half_space(),
....: ], marked_vertices=[I + 1])
sage: P
{x - 1 ≤ 0} ∩ {(x^2 + y^2) - 4 ≤ 0} ∩ {x + 1 ≥ 0} ∩ {(x^2 + y^2) - 1 ≥ 0} ∪ {1 + I}

sage: H.polygon(P.half_spaces())
{x - 1 ≤ 0} ∩ {(x^2 + y^2) - 4 ≤ 0} ∩ {x + 1 ≥ 0} ∩ {(x^2 + y^2) - 1 ≥ 0}
is_degenerate()[source]

Return whether this is considered to be a degenerate polygon.

EXAMPLES:

We consider polygons of area zero as degenerate:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: P = H.polygon([
....:     H.vertical(0).left_half_space(),
....:     H.half_circle(0, 1).left_half_space(),
....:     H.half_circle(0, 2).right_half_space(),
....:     H.vertical(0).right_half_space()
....: ], check=False, assume_minimal=True)
sage: P.is_degenerate()
True

We also consider polygons with marked points as degenerate:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: P = H.polygon([
....:     H.vertical(1).left_half_space(),
....:     H.half_circle(0, 2).left_half_space(),
....:     H.half_circle(0, 4).right_half_space(),
....:     H.vertical(-1).right_half_space()
....: ], marked_vertices=[2*I])
sage: P.is_degenerate()
True

sage: H.polygon(P.half_spaces()).is_degenerate()
False

Finally, we consider polygons with ideal points as degenerate:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: P = H.polygon([
....:     H.vertical(1).left_half_space(),
....:     H.vertical(-1).right_half_space()
....: ])
sage: P.is_degenerate()
True

Note

This is not a terribly meaningful notion. This exists mostly because degenerate polygons have a more obvious meaning in Euclidean geometry where this check is used when rendering a polygon as a string.

plot(model='half_plane', **kwds)[source]

Return a plot of this polygon in the hyperbolic model.

INPUT:

  • model – one of "half_plane" and "klein"

  • color – a string (default: "#efffff"); the fill color of polygons

  • edgecolor – a string (default: "blue"); the color of geodesics and segments

See flatsurf.graphical.hyperbolic.hyperbolic_path() for additional keyword arguments to customize the plot.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

A finite triangle:

sage: P = H.polygon([
....:     H.vertical(0).right_half_space(),
....:     H.geodesic(I, I + 1).left_half_space(),
....:     H.geodesic(I + 1, 2*I).left_half_space()
....: ])
sage: P
{(x^2 + y^2) - x - 1 ≥ 0} ∩ {(x^2 + y^2) + 2*x - 4 ≤ 0} ∩ {x ≥ 0}

In the upper half plane model, this plots as a polygon bounded by a segment and two arcs:

sage: P.plot("half_plane")[0]
CartesianPathPlot([CartesianPathPlotCommand(code='MOVETO', args=(0.000000000000000, 1.00000000000000)),
                   CartesianPathPlotCommand(code='RARCTO', args=((1.00000000000000, 1.00000000000000), (0.500000000000000, 0))),
                   CartesianPathPlotCommand(code='ARCTO', args=((0.000000000000000, 2.00000000000000), (-1.00000000000000, 0))),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 1.00000000000000))])

Technically, the plot consists of two parts, a filled layer with transparent stroke and a transparent layer with solid stroke. The latter shows the (finite) edges of the polygon:

sage: P.plot("half_plane")[1]
CartesianPathPlot([CartesianPathPlotCommand(code='MOVETO', args=(0.000000000000000, 1.00000000000000)),
                   CartesianPathPlotCommand(code='RARCTO', args=((1.00000000000000, 1.00000000000000), (0.500000000000000, 0))),
                   CartesianPathPlotCommand(code='ARCTO', args=((0.000000000000000, 2.00000000000000), (-1.00000000000000, 0))),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 1.00000000000000))])

In the Klein disk model, this plots as two identical Euclidean triangles (one for the background, one for the edges,) with an added circle representing the ideal points in that model:

sage: P.plot("klein")[0]
Circle defined by (0.0,0.0) with r=1.0
sage: P.plot("klein")[1]
CartesianPathPlot([CartesianPathPlotCommand(code='MOVETO', args=(0.000000000000000, 0.000000000000000)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.666666666666667, 0.333333333333333)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 0.600000000000000)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 0.000000000000000))])
sage: P.plot("klein")[2]
CartesianPathPlot([CartesianPathPlotCommand(code='MOVETO', args=(0.000000000000000, 0.000000000000000)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.666666666666667, 0.333333333333333)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 0.600000000000000)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 0.000000000000000))])

An ideal triangle plots the same way in the Klein model but now has two rays in the upper half plane model:

sage: P = H.polygon([
....:     H.vertical(0).right_half_space(),
....:     H.geodesic(0, 1).left_half_space(),
....:     H.vertical(1).left_half_space()
....: ])
sage: P
{(x^2 + y^2) - x ≥ 0} ∩ {x - 1 ≤ 0} ∩ {x ≥ 0}

sage: P.plot("half_plane")[0]
CartesianPathPlot([CartesianPathPlotCommand(code='MOVETO', args=(0.000000000000000, 0.000000000000000)),
                   CartesianPathPlotCommand(code='RARCTO', args=((1.00000000000000, 0.000000000000000), (0.500000000000000, 0))),
                   CartesianPathPlotCommand(code='RAYTO', args=(0, 1)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 0.000000000000000))])

sage: P.plot("klein")[1]
CartesianPathPlot([CartesianPathPlotCommand(code='MOVETO', args=(0.000000000000000, -1.00000000000000)),
                   CartesianPathPlotCommand(code='LINETO', args=(1.00000000000000, 0.000000000000000)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 1.00000000000000)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, -1.00000000000000))])

A polygon can contain infinitely many ideal points as is the case in this intersection of two half spaces:

sage: P = H.polygon([
....:     H.vertical(0).right_half_space(),
....:     H.vertical(1).left_half_space()
....: ])
sage: P
{x - 1 ≤ 0} ∩ {x ≥ 0}

sage: P.plot("half_plane")[0]
CartesianPathPlot([CartesianPathPlotCommand(code='MOVETO', args=(1.00000000000000, 0.000000000000000)),
                   CartesianPathPlotCommand(code='RAYTO', args=(0, 1)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 0.000000000000000)),
                   CartesianPathPlotCommand(code='LINETO', args=(1.00000000000000, 0.000000000000000))])

The last part, the line connecting 0 and 1, is missing from the stroke plot since we only stroke finite edges:

sage: P.plot("half_plane")[1]
CartesianPathPlot([CartesianPathPlotCommand(code='MOVETO', args=(1.00000000000000, 0.000000000000000)),
                   CartesianPathPlotCommand(code='RAYTO', args=(0, 1)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 0.000000000000000))])

Similarly in the Klein model picture, the arc of infinite points is only part of the fill, not of the stroke:

sage: P.plot("klein")[1]
CartesianPathPlot([CartesianPathPlotCommand(code='MOVETO', args=(1.00000000000000, 0.000000000000000)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 1.00000000000000)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, -1.00000000000000)),
                   CartesianPathPlotCommand(code='ARCTO', args=((1.00000000000000, 0.000000000000000), (0, 0)))])

sage: P.plot("klein")[2]
CartesianPathPlot([CartesianPathPlotCommand(code='MOVETO', args=(1.00000000000000, 0.000000000000000)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 1.00000000000000)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, -1.00000000000000))])

If the polygon contains unbounded set of reals, we get a horizontal ray in the half plane picture:

sage: P = H.polygon([
....:     H.vertical(0).right_half_space(),
....:     H.half_circle(2, 1).left_half_space(),
....: ])
sage: P
{(x^2 + y^2) - 4*x + 3 ≥ 0} ∩ {x ≥ 0}

sage: P.plot("half_plane")[0]
CartesianPathPlot([CartesianPathPlotCommand(code='MOVETO', args=(1.00000000000000, 0.000000000000000)),
                   CartesianPathPlotCommand(code='RARCTO', args=((3.00000000000000, 0.000000000000000), (2.00000000000000, 0))),
                   CartesianPathPlotCommand(code='RAYTO', args=(1, 0)),
                   CartesianPathPlotCommand(code='RAYTO', args=(0, 1)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 0.000000000000000)),
                   CartesianPathPlotCommand(code='LINETO', args=(1.00000000000000, 0.000000000000000))])

sage: P.plot("half_plane")[1]
CartesianPathPlot([CartesianPathPlotCommand(code='MOVETO', args=(1.00000000000000, 0.000000000000000)),
                   CartesianPathPlotCommand(code='RARCTO', args=((3.00000000000000, 0.000000000000000), (2.00000000000000, 0))),
                   CartesianPathPlotCommand(code='MOVETOINFINITY', args=(0, 1)),
                   CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 0.000000000000000))])
classmethod random_set(parent)[source]

Return a random hyperbolic convex polygon.

This implements HyperbolicConvexSet.random_set().

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: from flatsurf.geometry.hyperbolic import HyperbolicConvexPolygon
sage: x = HyperbolicConvexPolygon.random_set(H)

sage: x.dimension()
2
vertices(marked_vertices=True)[source]

Return the vertices of this polygon, i.e., the (possibly ideal) end points of the edges().

INPUT:

marked_vertices – a boolean (default: True) whether to include marked vertices in the output

OUTPUT:

Returns a set of points. Iteration over this set is in counterclockwise order.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

A finite polygon with a marked vertex:

sage: P = H.polygon([
....:   H.vertical(1).left_half_space(),
....:   H.vertical(-1).right_half_space(),
....:   H.half_circle(0, 1).left_half_space(),
....:   H.half_circle(0, 4).right_half_space(),
....: ], marked_vertices=[I])
sage: P.vertices()
{-1, I, 1, (2/5, 3/5), (-2/5, 3/5)}

sage: P.vertices(marked_vertices=False)
{-1, 1, (2/5, 3/5), (-2/5, 3/5)}

Currently, vertices cannot be computed if some of them have coordinates which do not live over the HyperbolicPlane.base_ring(); see HyperbolicVertices:

sage: P = H.polygon([
....:   H.half_circle(0, 1).left_half_space(),
....:   H.half_circle(0, 2).right_half_space(),
....: ])
sage: P.vertices()
Traceback (most recent call last):
...
ValueError: ...

See also

HyperbolicConvexSet.vertices() for more details.

class flatsurf.geometry.hyperbolic.HyperbolicConvexSet[source]

Base class for convex subsets of HyperbolicPlane.

Note

Concrete subclasses should apply the following rules.

There should only be a single type to describe a certain subset: normally, a certain subset, say a point, should only be described by a single class, namely HyperbolicPoint. Of course, one could describe a point as a polygon delimited by some edges that all intersect in that single point, such objects should be avoided. Namely, the methods that create a subset, say HyperbolicPlane.polygon() take care of this by calling a sets HyperbolicConvexSet._normalize() to rewrite a set in its most natural representation. To get the denormalized representation, we can always set \(check=False\) when creating the object. For this to work, the \(__init__\) should not take care of any such normalization and accept any input that can possibly be made sense of.

Comparison with == should mean “is essentially indistinguishable from”: Implementing == to mean anything else would get us into trouble in the long run. In particular we cannot implement <= to mean “is subset of” since then an oriented and an unoriented geodesic would be \(==\). So, objects of a different type should almost never be equal. A notable exception are objects that are indistinguishable to the end user but use different implementations: the starting point of the geodesic going from 0 to infinity, a HyperbolicPointFromGeodesic, and the point with coordinates (0, 0) in the upper half plane model, a HyperbolicPointFromCoordinates, are equal. Note that we also treat objects as equal that only differ in their exact representation such as the geodesic x = 1 and the geodesic 2x = 2.

__annotations__ = {}
__bool__()[source]

Return whether this set is non-empty.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: bool(H(0))
True

sage: bool(H.empty_set())
False
__contains__(point)[source]

Return whether point is contained in this set.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H(I) in H.empty_set()
False

sage: I in H.vertical(0)
True

sage: 2*I in H.half_circle(0, 1).left_half_space()
True

sage: I/2 in H.half_circle(0, 1).left_half_space()
False

Note

There is currently no way to check whether a point is in the interior of a set.

See also

HyperbolicConvexSet.is_subset() to check containment of arbitrary sets.

__dict__ = mappingproxy({'__module__': 'flatsurf.geometry.hyperbolic', '__doc__': '\n    Base class for convex subsets of :class:`HyperbolicPlane`.\n\n    .. NOTE::\n\n        Concrete subclasses should apply the following rules.\n\n        There should only be a single type to describe a certain subset:\n        normally, a certain subset, say a point, should only be described by a\n        single class, namely :class:`HyperbolicPoint`. Of course, one could\n        describe a point as a polygon delimited by some edges that all\n        intersect in that single point, such objects should be avoided. Namely,\n        the methods that create a subset, say :meth:`HyperbolicPlane.polygon`\n        take care of this by calling a sets\n        :meth:`HyperbolicConvexSet._normalize` to rewrite a set in its most\n        natural representation. To get the denormalized representation, we can\n        always set `check=False` when creating the object. For this to work,\n        the `__init__` should not take care of any such normalization and\n        accept any input that can possibly be made sense of.\n\n        Comparison with ``==`` should mean "is essentially indistinguishable\n        from": Implementing == to mean anything else would get us into trouble\n        in the long run. In particular we cannot implement <= to mean "is\n        subset of" since then an oriented and an unoriented geodesic would be\n        `==`. So, objects of a different type should almost never be equal. A\n        notable exception are objects that are indistinguishable to the end\n        user but use different implementations: the starting point of the\n        geodesic going from 0 to infinity, a\n        :class:`HyperbolicPointFromGeodesic`, and the point with coordinates\n        (0, 0) in the upper half plane model, a\n        :class:`HyperbolicPointFromCoordinates`, are equal. Note that we also\n        treat objects as equal that only differ in their exact representation\n        such as the geodesic x = 1 and the geodesic 2x = 2.\n\n    TESTS::\n\n        sage: from flatsurf import HyperbolicPlane\n        sage: from flatsurf.geometry.hyperbolic import HyperbolicConvexSet\n        sage: H = HyperbolicPlane()\n\n        sage: isinstance(H(0), HyperbolicConvexSet)\n        True\n\n    ', 'half_spaces': <function HyperbolicConvexSet.half_spaces>, '_test_half_spaces': <function HyperbolicConvexSet._test_half_spaces>, '_check': <function HyperbolicConvexSet._check>, '_normalize': <function HyperbolicConvexSet._normalize>, '_test_normalize': <function HyperbolicConvexSet._test_normalize>, 'unoriented': <function HyperbolicConvexSet.unoriented>, '_test_unoriented': <function HyperbolicConvexSet._test_unoriented>, 'intersection': <function HyperbolicConvexSet.intersection>, '__contains__': <function HyperbolicConvexSet.__contains__>, '_test_contains': <function HyperbolicConvexSet._test_contains>, 'vertices': <function HyperbolicConvexSet.vertices>, 'is_finite': <function HyperbolicConvexSet.is_finite>, 'is_ideal': <function HyperbolicConvexSet.is_ideal>, 'is_ultra_ideal': <function HyperbolicConvexSet.is_ultra_ideal>, '_test_is_finite': <function HyperbolicConvexSet._test_is_finite>, 'change_ring': <function HyperbolicConvexSet.change_ring>, '_test_change_ring': <function HyperbolicConvexSet._test_change_ring>, 'change': <function HyperbolicConvexSet.change>, '_test_change': <function HyperbolicConvexSet._test_change>, 'plot': <function HyperbolicConvexSet.plot>, '_test_plot': <function HyperbolicConvexSet._test_plot>, 'apply_isometry': <function HyperbolicConvexSet.apply_isometry>, '_apply_isometry_klein': <function HyperbolicConvexSet._apply_isometry_klein>, '_acted_upon_': <function HyperbolicConvexSet._acted_upon_>, 'is_subset': <function HyperbolicConvexSet.is_subset>, '_test_is_subset': <function HyperbolicConvexSet._test_is_subset>, '_an_element_': <function HyperbolicConvexSet._an_element_>, '_enhance_plot': <classmethod(<function HyperbolicConvexSet._enhance_plot>)>, 'is_empty': <function HyperbolicConvexSet.is_empty>, '__bool__': <function HyperbolicConvexSet.__bool__>, 'dimension': <function HyperbolicConvexSet.dimension>, '_test_dimension': <function HyperbolicConvexSet._test_dimension>, 'is_point': <function HyperbolicConvexSet.is_point>, 'is_oriented': <function HyperbolicConvexSet.is_oriented>, '_test_is_oriented': <function HyperbolicConvexSet._test_is_oriented>, 'edges': <function HyperbolicConvexSet.edges>, '_test_edges': <function HyperbolicConvexSet._test_edges>, 'area': <function HyperbolicConvexSet.area>, '__hash__': <function HyperbolicConvexSet.__hash__>, '_test_hash': <function HyperbolicConvexSet._test_hash>, '_isometry_conditions': <function HyperbolicConvexSet._isometry_conditions>, 'random_set': <classmethod(<function HyperbolicConvexSet.random_set>)>, '__dict__': <attribute '__dict__' of 'HyperbolicConvexSet' objects>, '__weakref__': <attribute '__weakref__' of 'HyperbolicConvexSet' objects>, '__annotations__': {}})
__hash__()[source]

Return a hash value for this convex set.

Specific sets should override this method.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: hash(H.empty_set())
0

Note that has values of sets over different base rings might not be consistent:

sage: HyperbolicPlane(ZZ).half_circle(0, 2) == HyperbolicPlane(AA).half_circle(0, 2)
True

sage: hash(HyperbolicPlane(ZZ).half_circle(0, 2)) == hash(HyperbolicPlane(AA).half_circle(0, 2))
False

Sets over inexact base rings are not be hashable (since their hash would not be compatible with the notion of equality):

sage: hash(HyperbolicPlane(RR).vertical(0))
Traceback (most recent call last):
...
TypeError: cannot hash geodesic defined over inexact base ring
__module__ = 'flatsurf.geometry.hyperbolic'
__weakref__

list of weak references to the object

_isometry_conditions(other)[source]

Return an iterable of primitive pairs that must map to each other in an isometry that maps this set to other.

Helper method for HyperbolicPlane._isometry_conditions().

When determining an isometry that maps sets to each other, we reduce to an isometry that maps points or geodesics to each other. Here, we produce such more primitive objects that map to each other.

Sometimes, this mapping is not unique, e.g., when mapping polygons to each other, we may rotate the vertices of the polygon. Therefore, this returns an iterator that produces the possible mappings of primitive objects.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: P = H.polygon([
....:     H.vertical(0).right_half_space(),
....:     H.half_circle(0, 4).left_half_space()],
....:     marked_vertices=[4*I])

sage: conditions = P._isometry_conditions(P)
sage: list(conditions)
[[({x ≥ 0}, {(x^2 + y^2) - 4 ≥ 0}),
  ({(x^2 + y^2) - 4 ≥ 0}, {x ≥ 0}),
  (4*I, 4*I)],
 [({x ≥ 0}, {x ≥ 0}),
  ({(x^2 + y^2) - 4 ≥ 0}, {(x^2 + y^2) - 4 ≥ 0}),
  (4*I, 4*I)],
 [({x ≥ 0}, {x ≥ 0}),
  ({(x^2 + y^2) - 4 ≥ 0}, {(x^2 + y^2) - 4 ≥ 0}),
  (4*I, 4*I)],
 [({x ≥ 0}, {(x^2 + y^2) - 4 ≥ 0}),
  ({(x^2 + y^2) - 4 ≥ 0}, {x ≥ 0}),
  (4*I, 4*I)]]
_normalize()[source]

Return this set possibly rewritten in a simpler form.

This method is only relevant for sets created with check=False. Such sets might have been created in a non-canonical way, e.g., when creating a HyperbolicOrientedSegment whose start and end point are ideal, then this is actually a geodesic and it should be described as such.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: segment = H.segment(H.vertical(-1), start=H.infinity(), end=H.infinity(), check=False, assume_normalized=True)
sage: segment
{-x - 1 = 0} ∩ {x - 1 ≥ 0} ∩ {x - 1 ≤ 0}
sage: segment._normalize()
∞
apply_isometry(isometry, model='half_plane', on_right=False)[source]

Return the image of this set under the isometry.

INPUT:

  • isometry – a 2×2 matrix in \(GL(2,\mathbb{R})\), if model is "half_plane", or a 3×3 matrix giving a similitude that preserves a quadratic form of type \((1, 2)\), if model is "klein".

  • model – a string (default: "half_plane"); either "half_plane" or "klein"

  • on_right – a boolean (default: False) whether to apply the right action.

ALGORITHM:

If model is "half_plane", the 2×2 matrix with entries \(a, b, c, d\) encodes a fractional linear transformation sending

\[z \mapsto \frac{az + b}{cz + d}\]

if the determinant is positive, and

\[z \mapsto \frac{a\bar{z} + b}{a\bar{z} + d}\]

if the determinant is negative. Note that these maps are invariant under scaling the matrix with a non-zero real.

In any case, we convert the matrix to a corresponding 3×3 matrix, see HyperbolicPlane._isometry_gl2_to_sim12() and apply the isometry in the Klein model.

To apply an isometry in the Klein model, we lift objects to the hyperboloid model, apply the isometry given by the 3×3 matrix there, and then project to the Klein model again.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

The horizontal translation by 1 in the upper half plane model, a parabolic isometry:

sage: isometry = matrix([[1, 1], [0, 1]])
sage: H.vertical(0).apply_isometry(isometry)
{-x + 1 = 0}

The same isometry as an isometry of the hyperboloid model:

sage: isometry = matrix([[1, -1, 1], [1, 1/2, 1/2], [1, -1/2, 3/2]])
sage: H.vertical(0).apply_isometry(isometry, model="klein")
{-x + 1 = 0}

An elliptic isometry:

sage: isometry = matrix([[1, -1], [1, 1]])
sage: H.vertical(0).apply_isometry(isometry)
{(x^2 + y^2) - 1 = 0}

A hyperbolic isometry:

sage: isometry = matrix([[1, 0], [0, 1/2]])
sage: H.vertical(0).apply_isometry(isometry)
{-x = 0}
sage: H(I).apply_isometry(isometry)
2*I

A reflection:

sage: isometry = matrix([[-1, 0], [0, 1]])
sage: H.vertical(0).apply_isometry(isometry)
{x = 0}

A glide reflection:

sage: isometry = matrix([[-1, 0], [0, 1/2]])
sage: H.vertical(0).apply_isometry(isometry)
{x = 0}
sage: H.vertical(1).apply_isometry(isometry)
{x + 2 = 0}

An isometry of the upper half plane must have non-zero determinant:

sage: isometry = matrix([[1, 0], [1, 0]])
sage: H.vertical(0).apply_isometry(isometry)
Traceback (most recent call last):
...
ValueError: matrix does not define an isometry

An isometry of the Klein model, must preserve a quadratic form of type \((1, 2)\):

sage: isometry = matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
sage: H.vertical(0).apply_isometry(isometry, model="klein")
Traceback (most recent call last):
...
ValueError: matrix does not define an isometry

Isometries can be applied to half spaces:

sage: isometry = matrix([[1, 1], [0, 1]])
sage: H.vertical(0).left_half_space().apply_isometry(isometry)
{x - 1 ≤ 0}

Isometries can be applied to points:

sage: H(I).apply_isometry(isometry)
1 + I

Isometries can be applied to polygons:

sage: P = H.polygon([
....:   H.vertical(1).left_half_space(),
....:   H.vertical(-1).right_half_space(),
....:   H.half_circle(0, 1).left_half_space(),
....:   H.half_circle(0, 4).right_half_space(),
....: ], marked_vertices=[I])
sage: P.apply_isometry(isometry)
{(x^2 + y^2) - 2*x ≥ 0} ∩ {x - 2 ≤ 0} ∩ {(x^2 + y^2) - 2*x - 3 ≤ 0} ∩ {x ≥ 0} ∪ {1 + I}

Isometries can be applied to segments:

sage: segment = H(I).segment(2*I)
sage: segment.apply_isometry(isometry)
{-x + 1 = 0} ∩ {2*(x^2 + y^2) - 3*x - 1 ≥ 0} ∩ {(x^2 + y^2) - 3*x - 2 ≤ 0}

REFERENCES:

  • Svetlana Katok, “Fuchsian Groups”, Chicago University Press, Section 1.3; for the isometries of the upper half plane.

  • James W. Cannon, William J. Floyd, Richard Kenyon, and Walter R. Parry, “Hyperbolic Geometry”, Flavors of Geometry, MSRI Publications, Volume 31, 1997, Section 10; for the isometries as 3×3 matrices.

area(numerical=True)[source]

Return the area of this convex set divided by 2π.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane(QQ)
sage: p = H.polygon([H.geodesic(0, 1).left_half_space(),
....:                 H.geodesic(1, Infinity).left_half_space(),
....:                 H.geodesic(Infinity, 0).left_half_space()])
sage: p.area()
0.5
sage: p.area(numerical=False)
1/2
sage: a = H.point(0, 1, model='half_plane')
sage: b = H.point(1, 1, model='half_plane')
sage: c = H.point(1, 2, model='half_plane')
sage: d = H.point(0, 2, model='half_plane')
sage: p = H.polygon([H.geodesic(a, b).left_half_space(),
....:                H.geodesic(b, c).left_half_space(),
....:                H.geodesic(c, d).left_half_space(),
....:                H.geodesic(d, a).left_half_space()])
sage: p.area()
0.0696044872730639

Zero and one-dimensional objects have area zero:

sage: p = H(I)
sage: p.area()
0.0
sage: p.area(numerical=False)
0
sage: g = H.geodesic(0, 1)
sage: g.area()
0.0
sage: g.area(numerical=False)
0

A half space has infinite area:

sage: h = g.left_half_space()
sage: h.area()
inf
sage: h.area(numerical=False)
+Infinity
change(ring=None, geometry=None, oriented=None)[source]

Return a modified copy of this set.

INPUT:

  • ring – a ring (default: None to keep the current base_ring()); the ring over which the new set will be defined.

  • geometry – a HyperbolicGeometry (default: None to keep the current geometry); the geometry that will be used for the new set.

  • oriented – a boolean (default: None to keep the current orientedness) whether the new set will be explicitly oriented.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: geodesic = H.geodesic(0, 1)

We can change the base ring over which this set is defined:

sage: geodesic.change(ring=AA)
{(x^2 + y^2) - x = 0}

We can drop the explicit orientation of a set:

sage: unoriented = geodesic.change(oriented=False)
sage: unoriented.is_oriented()
False

We can also take an unoriented set and pick an orientation:

sage: oriented = geodesic.change(oriented=True)
sage: oriented.is_oriented()
True

See also

is_oriented() for oriented an unoriented sets.

change_ring(ring)[source]

Return this set as an element of the hyperbolic plane over ring.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: p = H(0)
sage: q = p.change_ring(AA)
sage: q.parent().base_ring()
Algebraic Real Field

Changing the base ring can provide coordinates for points:

sage: p = H.half_circle(0, 2).start()
sage: p.coordinates()
Traceback (most recent call last):
...
ValueError: ...

sage: q = p.change_ring(AA)
sage: q.coordinates()
(-1.414213562373095?, 0)

Note that changing the ring only works in relatively trivial cases:

sage: q = HyperbolicPlane(AA).point(sqrt(2), 0, model="half_plane")

sage: p = q.change_ring(QQ)
Traceback (most recent call last):
...
ValueError: ...

Most other sets also support changing the base ring:

sage: g = H.half_circle(0, 2)
sage: g.start().coordinates()
Traceback (most recent call last):
...
ValueError: ...

sage: g.change_ring(AA).start().coordinates()
(-1.414213562373095?, 0)

See also

change() for a more general interface to changing properties of hyperbolic sets.

HyperbolicPlane.change_ring() for the hyperbolic plane that the resulting objects lives in.

dimension()[source]

Return the dimension of this set.

OUTPUT:

An integer, one of -1, 0, 1, 2.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

We treat the empty set as -1-dimensional:

sage: H.empty_set().dimension()
-1

Points are zero-dimensional:

sage: H(0).dimension()
0

Geodesics and segments are one-dimensional:

sage: H.vertical(0).dimension()
1

sage: H(I).segment(2*I).dimension()
1

Polygons and half spaces are two dimensional:

sage: H.random_element("polygon").dimension()
2

sage: H.vertical(0).left_half_space().dimension()
2
edges(as_segments=False, marked_vertices=True)[source]

Return the segments and geodesics that bound this set.

INPUT:

  • as_segments – a boolean (default: False); whether to also return the geodesics as segments with ideal end points.

  • marked_vertices – a boolean (default: True); whether to report segments that start or end at redundant marked vertices or otherwise whether such marked vertices are completely ignored.

OUTPUT:

A set of segments and geodesics. Iteration through this set is in counterclockwise order with respect to the points of the set.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

The edges of a polygon:

sage: P = H.intersection(
....:   H.vertical(-1).right_half_space(),
....:   H.vertical(1).left_half_space(),
....:   H.half_circle(0, 2).left_half_space())

sage: P.edges()
{{-x + 1 = 0} ∩ {2*(x^2 + y^2) - 3*x - 1 ≥ 0}, {x + 1 = 0} ∩ {2*(x^2 + y^2) + 3*x - 1 ≥ 0}, {(x^2 + y^2) - 2 = 0} ∩ {(x^2 + y^2) + 3*x + 1 ≥ 0} ∩ {(x^2 + y^2) - 3*x + 1 ≥ 0}}

The single edge of a half space:

sage: H.vertical(0).left_half_space().edges() {{-x = 0},}

A geodesic and a segment are bounded by two edges:

sage: H.vertical(0).edges()
{{-x = 0}, {x = 0}}

sage: H(I).segment(2*I).edges()
{{-x = 0} ∩ {(x^2 + y^2) - 1 ≥ 0} ∩ {(x^2 + y^2) - 4 ≤ 0}, {x = 0} ∩ {(x^2 + y^2) - 4 ≤ 0} ∩ {(x^2 + y^2) - 1 ≥ 0}}

Lower dimensional objects have no edges:

sage: H(I).edges() {}

sage: H.empty_set().edges() {}

half_spaces()[source]

Return a minimal set of half spaces whose intersection is this convex set.

Iteration of the half spaces is in counterclockwise order, i.e., consistent with HyperbolicHalfSpaces._lt_().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).left_half_space().half_spaces()
{{x ≤ 0},}

sage: H.vertical(0).half_spaces()
{{x ≤ 0}, {x ≥ 0}}

sage: H(0).half_spaces()
{{(x^2 + y^2) + x ≤ 0}, {x ≥ 0}}
intersection(other)[source]

Return the intersection with the other convex set.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: H.vertical(0).intersection(H.vertical(1))
∞

..SEEALSO:

:meth:`HyperbolicPlane.intersection`
is_empty()[source]

Return whether this set is empty.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H(0).is_empty()
False

sage: H.empty_set().is_empty()
True
is_finite()[source]

Return whether all points in this set are finite.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.empty_set().is_finite()
True

sage: H.vertical(0).is_finite()
False

sage: H.vertical(0).left_half_space().is_finite()
False

sage: H(I).segment(2*I).is_finite()
True

sage: H(0).segment(I).is_finite()
False

sage: P = H.polygon([
....:     H.vertical(-1).right_half_space(),
....:     H.vertical(1).left_half_space(),
....:     H.half_circle(0, 1).left_half_space(),
....:     H.half_circle(0, 4).right_half_space(),
....: ])
sage: P.is_finite()
False

sage: P = H.polygon([
....:     H.vertical(-1).right_half_space(),
....:     H.vertical(1).left_half_space(),
....:     H.half_circle(0, 2).left_half_space(),
....:     H.half_circle(0, 4).right_half_space(),
....: ])
sage: P.is_finite()
True

See also

is_ideal() and is_ultra_ideal() for complementary notions

is_ideal()[source]

Return whether all points in this set are ideal.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.empty_set().is_ideal()
True

sage: H.vertical(0).is_ideal()
False

sage: H.vertical(0).start().is_ideal()
True

sage: H(I).is_ideal()
False

See also

is_finite() and is_ultra_ideal() for complementary notions

is_oriented()[source]

Return whether this is a set with an explicit orientation.

Some sets come in two flavors. There are oriented geodesics and unoriented geodesics. There are oriented segments and unoriented segments.

This method answers whether a set is of the oriented kind if there is a choice.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

Normally, geodesics are oriented:

sage: g = H.vertical(0)
sage: g.is_oriented()
True

sage: g.start()
0

sage: g.end()
∞

We can ask explicitly for an unoriented version:

sage: h = g.unoriented()
sage: h.is_oriented()
False

sage: h.start()
Traceback (most recent call last):
...
AttributeError: ... has no attribute 'start'...

Segments are oriented:

sage: s = H(I).segment(2*I)
sage: s.is_oriented()
True

sage: s.start()
I

sage: s.end()
2*I

We can ask explicitly for an unoriented segment:

sage: u = s.unoriented()
sage: u.is_oriented()
False

sage: u.start()
Traceback (most recent call last):
...
AttributeError: ... has no attribute 'start'...

Points are not oriented as there is no choice of orientation:

sage: H(0).is_oriented()
False

Half spaces are not oriented:

sage: H.vertical(0).left_half_space().is_oriented() False

See also

change() to pick an orientation on an unoriented set

HyperbolicHalfSpace.__neg__(), HyperbolicOrientedGeodesic.__neg__(), HyperbolicOrientedSegment.__neg__() i.e., the - operator, to invert the orientation of a set

is_point()[source]

Return whether this set is a single point.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane sage: H = HyperbolicPlane()

sage: H(0).is_point() True

sage: H(I).is_point() True

sage: H.empty_set().is_point() False

sage: H.vertical(0).is_point() False

See also

is_ideal() to check whether this is a finite or an infinite point

is_subset(other)[source]

Return whether this set is a subset of other.

INPUT:

  • other – another hyperbolic convex set

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H(I).is_subset(H.vertical(0))
True

sage: H.vertical(0).is_subset(H(I))
False
is_ultra_ideal()[source]

Return whether all points in this set are ultra-ideal, i.e., the correspond to points outside the Klein disk.

Note that it is normally not possible to create ultra ideal sets (except for the actual empty set). They only exist internally during geometric constructions in the Euclidean plane containing the Klein disk.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.empty_set().is_ultra_ideal()
True

sage: H.vertical(0).is_ultra_ideal()
False

Normally, ultra-ideal objects are not permitted. They can often be created with the check=False keyword:

sage: H.point(2, 0, check=False, model="klein").is_ultra_ideal()
True

sage: H.geodesic(2, 0, 1, check=False, model="klein").is_ultra_ideal()
True

See also

is_finite() and is_ultra_ideal() for complementary notions

plot(model='half_plane', **kwds)[source]

Return a plot of this subset.

Consult the implementation in the subclasses for a list supported keyword arguments, in particular HyperbolicConvexPolygon.plot().

INPUT:

  • model – one of "half_plane" and "klein" (default: "half_plane")

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).plot()  # long time (.5s)
...Graphics object consisting of 1 graphics primitive
../_images/hyperbolic_1_0.png
classmethod random_set(parent)[source]

Return a random convex set.

Concrete hyperbolic classes should override this method to provide random sets.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

Given a hyperbolic object, we can create another one of the same kind:

sage: p = H(I)
sage: type(p).random_set(H)  # random output
7
sage: from flatsurf.geometry.hyperbolic import HyperbolicConvexPolygon
sage: P = HyperbolicConvexPolygon.random_set(H)
sage: P.dimension()
2
unoriented()[source]

Return the non-oriented version of this set.

Some sets such as geodesics and segments can have an explicit orientation. This method returns the underlying set without any explicit orientation.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: H.vertical(0).unoriented()
{x = 0}
vertices(marked_vertices=True)[source]

Return the vertices bounding this hyperbolic set.

This returns both finite and ideal vertices.

INPUT:

  • marked_vertices – a boolean (default: True) whether to include marked vertices that are not actual cornerns of the convex set.

OUTPUT:

A set of points, namely HyperbolicVertices. Iteration of this set happens incounterclockwise order (as seen from the inside of the convex set).

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

The empty set has no vertices:

sage: H.empty_set().vertices()
{}

A point is its own vertex:

sage: H(0).vertices()
{0,}
sage: H(I).vertices()
{I,}
sage: H(oo).vertices()
{∞,}

The vertices of a geodesic are its ideal end points:

sage: H.vertical(0).vertices()
{0, ∞}

The vertices of a half space are the ideal end points of its boundary geodesic:

sage: H.vertical(0).left_half_space().vertices()
{0, ∞}

The vertices a polygon can be finite and ideal:

sage: P = H.polygon([H.vertical(0).left_half_space(), H.half_circle(0, 1).left_half_space()])
sage: P.vertices()
{-1, I, ∞}

If a polygon has marked vertices they are included:

sage: P = H.polygon([H.vertical(0).left_half_space(), H.half_circle(0, 1).left_half_space()], marked_vertices=[2*I])
sage: P.vertices()
{-1, I, 2*I, ∞}

sage: P.vertices(marked_vertices=False)
{-1, I, ∞}
class flatsurf.geometry.hyperbolic.HyperbolicEdges(entries, assume_sorted=None)[source]

A set of hyperbolic segments and geodesics ordered counterclockwise.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: edges = H.vertical(0).edges()
sage: edges
{{-x = 0}, {x = 0}}

See also

HyperbolicConvexSet.edges() to obtain such a set

__abstractmethods__ = frozenset({})
__module__ = 'flatsurf.geometry.hyperbolic'
classmethod _lt_(lhs, rhs)[source]

Return whether lhs should come before rhs in the ordering of this set.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: edges = H.vertical(0).edges()
sage: edges._lt_(edges[0], edges[1])
True

Segments on the same edge are ordered correctly:

sage: segments = [
....:   H(0).segment(H(I)),
....:   H(I).segment(H(2*I)),
....:   H(2*I).segment(H(oo))
....: ]

sage: edges._lt_(segments[0], segments[1])
True
sage: edges._lt_(segments[0], segments[2])
True
sage: edges._lt_(segments[1], segments[2])
True

sage: edges._lt_(segments[2], segments[1])
False
sage: edges._lt_(segments[2], segments[0])
False
sage: edges._lt_(segments[1], segments[0])
False
class flatsurf.geometry.hyperbolic.HyperbolicEmptySet(parent, category=None)[source]

The empty subset of the hyperbolic plane.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.empty_set()
{}

See also

Use HyperbolicPlane.empty_set() to construct the empty set.

__annotations__ = {}
__eq__(other)[source]

Return whether this empty set is indistinguishable from other.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: H.empty_set() == H.empty_set()
True
sage: H.empty_set() == HyperbolicPlane(AA).empty_set()
False
__hash__()[source]

Return a hash value for this set.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()

Since this set is hashable, it can be put in a hash table, such as a Python set:

sage: {H.empty_set()}
{{}}
__module__ = 'flatsurf.geometry.hyperbolic'
change(ring=None, geometry=None, oriented=None)[source]

Return a copy of the empty set.

INPUT:

  • ring – a ring (default: None to keep the current HyperbolicPlane.base_ring()); the ring over which the empty set will be defined.

  • geometry – a HyperbolicGeometry (default: None to keep the current geometry); the geometry that will be used for the empty set.

  • oriented – a boolean (default: None to keep the current orientedness); must be None or False since the empty set cannot have an explicit orientation.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane sage: H = HyperbolicPlane()

sage: H.empty_set().change(ring=AA) == HyperbolicPlane(AA).empty_set() True

sage: H.empty_set().change(oriented=True) Traceback (most recent call last): … NotImplementedError: cannot change orientation of empty set

dimension()[source]

Return the dimension of this set; returns -1 for the empty set.

This implements HyperbolicConvexSet.dimension().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.empty_set().dimension()
-1
half_spaces()[source]

Return a minimal set of half spaces whose intersection is empty.

This implements HyperbolicConvexSet.half_spaces().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.empty_set().half_spaces()
{{(x^2 + y^2) + 4*x + 3 ≤ 0}, {(x^2 + y^2) - 4*x + 3 ≤ 0}}
plot(model='half_plane', **kwds)[source]

Return a plot of the empty set.

INPUT:

  • model – one of "half_plane" and "klein" (default: "half_plane")

Any keyword arguments are ignored.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.empty_set().plot()
...Graphics object consisting of 0 graphics primitives
../_images/hyperbolic_2_0.png
classmethod random_set(parent)[source]

Return a random empty set, i.e., the empty set.

This implements HyperbolicConvexSet.random_set().

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: from flatsurf.geometry.hyperbolic import HyperbolicEmptySet
sage: x = HyperbolicEmptySet.random_set(H)

sage: x.dimension()
-1
some_elements()[source]

Return some representative points of this set for testing.

EXAMPLES:

Since this set is empty, there are no points to return:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: H.empty_set().some_elements()
[]
vertices(marked_vertices=True)[source]

Return the vertices of this empty, i.e., an empty set of points.

INPUT:

  • marked_vertices – a boolean (default: True), ignored

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.empty_set().vertices()
{}
class flatsurf.geometry.hyperbolic.HyperbolicEpsilonGeometry(ring, epsilon)[source]

Predicates and primitive geometric constructions over a base ring with “precision” epsilon.

This is an alternative to HyperbolicExactGeometry over inexact rings. The exact meaning of the epsilon parameter is a bit fuzzy, but the basic idea is that two numbers are considered equal in this geometry if their relative difference is less than epsilon, see _equal() for details.

INPUT:

  • ring – a ring, the ring in which coordinates in the hyperbolic plane will be represented

  • epsilon – an error bound

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicEpsilonGeometry
sage: H = HyperbolicPlane(RR, HyperbolicEpsilonGeometry(RR, 1/1024))

The epsilon affects the notion of equality in this geometry:

sage: H(0) == H(1/2048)
True

sage: H(1/2048) == H(2/2048)
False

This geometry is meant for inexact rings, however, it can also be used in exact rings:

sage: H = HyperbolicPlane(QQ, HyperbolicEpsilonGeometry(QQ, 1/1024))
__annotations__ = {}
__init__(ring, epsilon)[source]
__module__ = 'flatsurf.geometry.hyperbolic'
__repr__()[source]

Return a printable representation of this geometry.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane(RR)
sage: H.geometry
Epsilon geometry with ϵ=1.00000000000000e-6 over Real Field with 53 bits of precision
_equal(x, y)[source]

Return whether x and y should be considered equal numbers with respect to an ε error.

Note

This method has not been tested much. Since this underlies much of the inexact geometry, we should probably do something better here, see e.g., https://floating-point-gui.de/errors/comparison/

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane(RR)

sage: H.geometry._equal(1, 2)
False
sage: H.geometry._equal(1, 1 + 1e-32)
True
sage: H.geometry._equal(1e-32, 1e-32 + 1e-33)
False
sage: H.geometry._equal(1e-32, 1e-32 + 1e-64)
True
change_ring(ring)[source]

Return this geometry over ring.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane(RR)

sage: H.geometry.change_ring(RR) is H.geometry
True

sage: H.geometry.change_ring(RDF)
Epsilon geometry with ϵ=1e-06 over Real Double Field
projective(p, q, point)[source]

Return the ideal point with projective coordinates [p: q] in the upper half plane model.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane(RR)

The point [p: q] is the point at infinity if q is very small in comparison to p:

sage: H.geometry.projective(1, 0, H.point)
∞

sage: H.geometry.projective(1e-8, 1e-16, H.point)
∞

sage: H.geometry.projective(1e-8, -1e-16, H.point)
∞

Even though q might be small, [p: q] is not the point at infinity if both coordinates are of similar size:

sage: H.geometry.projective(1e-16, 1e-16, H.point)
1.00000000000000

sage: H.geometry.projective(-1e-16, 1e-16, H.point)
-1.00000000000000
class flatsurf.geometry.hyperbolic.HyperbolicExactGeometry(ring)[source]

Predicates and primitive geometric constructions over an exact base ring.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: H.geometry
Exact geometry over Rational Field

See also

HyperbolicEpsilonGeometry for an implementation over inexact rings

__annotations__ = {}
__module__ = 'flatsurf.geometry.hyperbolic'
__repr__()[source]

Return a printable representation of this geometry.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: H.geometry
Exact geometry over Rational Field
_equal(x, y)[source]

Return whether the numbers x and y should be considered equal in exact geometry.

Note

This predicate should not be used directly in geometric constructions since it does not specify the context in which this question is asked. This makes it very difficult to override a specific aspect in a custom geometry.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: H.geometry._equal(0, 1)
False
sage: H.geometry._equal(0, 1/2**64)
False
sage: H.geometry._equal(0, 0)
True
change_ring(ring)[source]

Return this geometry with the base_ring() changed to ring.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: H.geometry.change_ring(QQ) == H.geometry
True
sage: H.geometry.change_ring(AA)
Exact geometry over Algebraic Real Field

When presented with the reals, we guess the epsilon for the HyperbolicEpsilonGeometry to be consistent with the HyperbolicGeometry constructor. (And also, because we use this frequently when plotting.):

sage: H.geometry.change_ring(RR)
Epsilon geometry with ϵ=1.00000000000000e-6 over Real Field with 53 bits of precision
class flatsurf.geometry.hyperbolic.HyperbolicGeodesic(parent, a, b, c)[source]

A geodesic in the hyperbolic plane.

This is the abstract base class of HyperbolicUnorientedGeodesic and HyperbolicOrientedGeodesic.

ALGORITHM:

Internally, we represent geodesics as a triple \(a, b, c\) such that they satisfy the equation

\[a + bx + cy = 0\]

in the Klein disk model.

Note that due to this representation we can always compute intersection points of geodesics but we cannot always get the coordinates of the ideal end points of a geodesic (since we would have to take a square root to solve for the points on the unit circle).

It might be beneficial to store geodesics differently, see https://sagemath.zulipchat.com/#narrow/stream/271193-polygon/topic/hyperbolic.20geometry/near/284722650 for a discussion.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.geodesic(1, 2, 3, model="klein")
{2*(x^2 + y^2) + 2*x - 1 = 0}

See also

HyperbolicPlane.geodesic() for various ways of constructing geodesics

__annotations__ = {}
__contains__(point)[source]

Return whether point lies on this geodesic.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: g = H.geodesic(20, -479, 858, model="half_plane")
sage: g.start() in g
True

We can often decide containment for points coming from geodesics:

sage: g = H.geodesic(-1, -1/2)
sage: h = H.geodesic(1, 1/2)

sage: g.start() in g
True
sage: g.start() in h
False
sage: g.end() in g
True
sage: g.end() in h
False

Note

The implementation is currently not very robust over inexact rings.

__eq__(other)[source]

Return whether this geodesic is identical to other up to (orientation preserving) scaling of the defining equation.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: H.vertical(0) == H.vertical(0)
True

We distinguish oriented and unoriented geodesics:

sage: H.vertical(0).unoriented() == H.vertical(0)
False
sage: H.vertical(0).unoriented() != H.vertical(0)
True

We distinguish differently oriented geodesics:

sage: H.vertical(0) == -H.vertical(0)
False
sage: H.vertical(0) != -H.vertical(0)
True

We do, however, identify geodesics whose defining equations differ by some scaling:

sage: g = H.vertical(0)
sage: g.equation(model="half_plane")
(0, -2, 0)
sage: h = H.geodesic(0, -4, 0, model="half_plane")
sage: g.equation(model="half_plane") == h.equation(model="half_plane")
False
sage: g == h
True
sage: g != h
False

Note

Over inexact rings, this method is not very reliable. To some extent this is inherent to the problem but also the implementation uses generic predicates instead of relying on a specialized implementation in the HyperbolicGeometry.

__hash__()[source]

Return a hash value for this geodesic.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()

Since oriented geodesics are hashable, they can be put in a hash table, such as a Python set:

sage: S = {H.vertical(0), -H.vertical(0)}
sage: len(S)
2

Same for unoriented geodesics:

sage: {H.vertical(0).unoriented(), (-H.vertical(0)).unoriented()}
{{x = 0}}

Oriented and unoriented geodesics are distinct and so is their hash value (typically):

sage: hash(H.vertical(0)) != hash(H.vertical(0).unoriented())
True

We can also mix oriented and unoriented geodesics in hash tables:

sage: S = {H.vertical(0), -H.vertical(0), H.vertical(0).unoriented(), (-H.vertical(0)).unoriented()}
sage: len(S)
3
__init__(parent, a, b, c)[source]
__module__ = 'flatsurf.geometry.hyperbolic'
_intersection(other)[source]

Return the intersection of this geodesic and other.

Return None if they do not intersect in a point.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane(AA)

Geodesics can intersect in a finite point:

sage: H.vertical(0)._intersection(H.half_circle(0, 1))
I

Geodesics can intersect in an ideal point:

sage: H.vertical(1)._intersection(H.half_circle(0, 1))
1

Geodesics might intersect in an ultra ideal point:

sage: H.half_circle(0, 1)._intersection(H.half_circle(1, 8))
(-3, 0)

Or they are parallel in the Klein model:

sage: H.half_circle(0, 1)._intersection(H.half_circle(0, 4))

Note that geodesics that overlap do not intersect in a point:

sage: H.vertical(0)._intersection(H.vertical(0))

sage: H.vertical(0)._intersection(-H.vertical(0))

See also

HyperbolicConvexSet.intersection() for intersection with more general sets.

HyperbolicPlane.intersection() for the generic intersection of convex sets.

change(ring, geometry, oriented)[source]

Return a modified copy of this geodesic.

INPUT:

  • ring – a ring (default: None to keep the current base_ring()); the ring over which the new geodesic will be defined.

  • geometry – a HyperbolicGeometry (default: None to keep the current geometry); the geometry that will be used for the new geodesic.

  • oriented – a boolean (default: None to keep the current orientedness); whether the new geodesic should be oriented.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane(AA)

The base ring over which this geodesic is defined can be changed:

sage: H.vertical(1).change_ring(QQ)
{-x + 1 = 0}

But we cannot change the base ring if the geodesic cannot be expressed in the smaller ring:

sage: H.vertical(AA(2).sqrt()).change(ring=QQ)
Traceback (most recent call last):
...
ValueError: Cannot coerce irrational Algebraic Real ... to Rational

We can forget the orientation of a geodesic:

sage: v = H.vertical(0)
sage: v.is_oriented()
True

sage: v = v.change(oriented=False)
sage: v.is_oriented()
False

We can (somewhat randomly) pick the orientation of a geodesic:

sage: v = v.change(oriented=True)
sage: v.is_oriented()
True
dimension()[source]

Return the dimension of this set, i.e., 1.

This implements HyperbolicConvexSet.dimension().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).dimension()
1
equation(model, normalization=None)[source]

Return an equation for this geodesic as a triple a, b, c such that:

  • if model is "half_plane", a point \(x + iy\) of the upper half plane is on the geodesic if it satisfies \(a(x^2 + y^2) + bx + c = 0\).

  • if model is "klein", points \((x, y)\) in the unit disk satisfy are on the geodesic if \(a + bx + cy = 0\).

INPUT:

  • model – the model in which this equation holds, either "half_plane" or "klein"

  • normalization – how to normalize the coefficients; the default None is not to normalize at all. Other options are gcd, to divide the coefficients by their greatest common divisor, one, to normalize the first non-zero coefficient to ±1. This can also be a list of such values which are then tried in order and exceptions are silently ignored unless they happen at the last option.

If this geodesic :meth;`is_oriented`, then the sign of the coefficients is chosen to encode the orientation of this geodesic. The sign is such that the half plane obtained by replacing = with in above equationsis on the left of the geodesic.

Note that the output might not uniquely describe the geodesic since the coefficients are only unique up to scaling.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: v = H.vertical(0)
sage: v.equation(model="half_plane")
(0, -2, 0)

sage: v.equation(model="half_plane", normalization="gcd")
(0, -1, 0)

sage: v.equation(model="klein")
(0, -1, 0)

Sometimes, the desired normalization might not be possible (a more realistic example would be exact-real coefficients):

sage: H = HyperbolicPlane(ZZ)
sage: g = H.geodesic(2, 3, -4, model="half_plane")

sage: g.equation(model="half_plane", normalization="one")
Traceback (most recent call last):
...
TypeError: ...

In this case, we can ask for the best of several normalization:

sage: g.equation(model="half_plane", normalization=["one", "gcd", None])
(2, 3, -4)

For ultra-ideal geodesics, the equation in the half plane model is not very useful:

sage: g = H.geodesic(2, 0, 1, model="klein", check=False)
sage: g.equation(model="half_plane")  # i.e., 3*(x^2 + y^2) + 1 = 0
(3, 0, 1)

See also

HyperbolicPlane.geodesic() to create a geodesic from an equation

geodesic()[source]

Return the geodesic underlying this set, i.e., this geodesic itself.

This method exists to unify the interface between segments and geodesics, see HyperbolicSegment.geodesic().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).geodesic() == H.vertical(0)
True
half_spaces()[source]

Return the two half spaces whose intersection this geodesic is.

Implements HyperbolicConvexSet.half_spaces().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).half_spaces()
{{x ≤ 0}, {x ≥ 0}}
is_diameter()[source]

Return whether this geodesic is a diameter in the Klein model.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: H.vertical(0).is_diameter()
True
sage: H.vertical(1).is_diameter()
False
is_ideal()[source]

Return whether all hyperbolic points of this geodesic are ideal, i.e., the defining equation of this geodesic in the Klein model only touches the Klein disk but does not intersect it.

Note that it is normally not possible to create ideal geodesics. They only exist internally during constructions in the Euclidean plane.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).is_ideal()
False

sage: geodesic = H.geodesic(1, 0, 1, model="klein", check=False)
sage: geodesic.is_ideal()
True

sage: geodesic = H.geodesic(2, 0, 1, model="klein", check=False)
sage: geodesic.is_ideal()
False

Note

The implementation of this predicate is not numerically robust over inexact rings.

See also

is_ultra_ideal() to detect whether a geodesic does not even touch the Klein disk

is_ultra_ideal()[source]

Return whether the line given by the defining equation is completely outside the Klein disk, i.e., all “points” of this geodesic are ultra-ideal.

Note that it is normally not possible to create ultra-ideal geodesics. They only exist internally during constructions in the Euclidean plane.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).is_ultra_ideal()
False

sage: geodesic = H.geodesic(1, 0, 1, model="klein", check=False)
sage: geodesic.is_ultra_ideal()
False

sage: geodesic = H.geodesic(2, 0, 1, model="klein", check=False)
sage: geodesic.is_ultra_ideal()
True

Note

The implementation of this predicate is not numerically robust over inexact rings.

See also

is_ideal() to detect whether a geodesic touches the Klein disk

is_vertical()[source]

Return whether this geodesic is a vertical in the upper half plane.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: H.vertical(0).is_vertical()
True
sage: H.half_circle(0, 1).is_vertical()
False
midpoint()[source]

Return the fixed point of the (determinant one) Möbius transformation that interchanges the ideal endpoints of this geodesic.

ALGORITHM:

For the vertical connecting zero and infinity, the Möbius transformation sending z to \(-1/z\) has the imaginary unit as its fixed point. For a half circle centered at the origin its point on the imaginary axis must be the fixed point (due to symmetry or a direct computation). All other geodesics, are just translated versions of these so we can just conjugate with a translation to determine the fixed point, i.e., the fixed point is a translate of one of the above.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicSegment
sage: H = HyperbolicPlane()
sage: H.vertical(0).midpoint()
I
sage: H.vertical(1).midpoint()
1 + I
sage: H.half_circle(1, 1).midpoint()
1 + I
perpendicular(point_or_geodesic=None)[source]

Return a geodesic that is perpendicular to this geodesic.

If point_or_geodesic is a point, return a geodesic through that point.

If point_or_geodesic is another geodesic, return a geodesic that is also perpendicular to that geodesic.

ALGORITHM:

We use the construction as explained on \(Wikipedia <https://en.wikipedia.org/wiki/Beltrami%E2%80%93Klein_model#Compass_and_straightedge_constructions>\).

INPUT:

  • point_or_geodesic – a point or a geodesic in the hyperbolic plane or None (the default)

EXAMPLES:

Without parameters this method returns one of the many perpendicular geodesics:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: v = H.vertical(2)
sage: v.perpendicular()
{(x^2 + y^2) - 4*x - 1 = 0}

We can request a perpendicular geodesic through a specific point:

sage: v.perpendicular(2 + I)
{(x^2 + y^2) - 4*x + 3 = 0}
sage: v.perpendicular(I)
{(x^2 + y^2) - 4*x - 1 = 0}

In some cases, such a geodesic might not exist:

sage: v.perpendicular(oo)
Traceback (most recent call last):
...
ValueError: ... does not define a chord in the Klein model

We can request a geodesic that is also perpendicular to another geodesic:

sage: v.perpendicular(H.half_circle(4, 1))
{(x^2 + y^2) - 4*x + 1 = 0}

In some cases, such a geodesic might not exist:

sage: v.perpendicular(H.half_circle(2, 1))
Traceback (most recent call last):
...
ValueError: ... does not define a chord in the Klein model

Note

Currently, the orientation of the returned geodesic is somewhat random. It should probably be counterclockwise to this geodesic.

plot(model='half_plane', **kwds)[source]

Return a plot of this geodesic in the hyperbolic model.

See HyperbolicSegment.plot() for the supported keyword arguments.

INPUT:

  • model – one of "half_plane" and "klein" (default: "half_plane")

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).plot()
...Graphics object consisting of 1 graphics primitive
../_images/hyperbolic_3_0.png
pole()[source]

Return the pole of this geodesic.

ALGORITHM:

The pole is the intersection of tangents of the Klein disk at the ideal endpoints of this geodesic, see \(Wikipedia <https://en.wikipedia.org/wiki/Beltrami%E2%80%93Klein_model#Compass_and_straightedge_constructions>\).

EXAMPLES:

The pole of a geodesic is an ultra ideal point:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: p = H.vertical(2).pole(); p
(1/2, 1)
sage: p.is_ultra_ideal()
True

Computing the pole is only implemented if it is a finite point in the Euclidean plane:

sage: H.half_circle(0, 1).pole()
Traceback (most recent call last):
...
NotImplementedError: can only compute pole if geodesic is a not a diameter in the Klein model

The pole might not be defined without passing to a larger base ring:

sage: H.half_circle(2, 2).pole()
Traceback (most recent call last):
...
ValueError: square root of 32 not in Rational Field
vertices(marked_vertices=True)[source]

Return the ideal end points of this geodesic.

INPUT:

  • marked_vertices – a boolean (default: True), ignored since a geodesic cannot have marked vertices.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).vertices()
{0, ∞}

Note that iteration in the set is not consistent with the orientation of the geodesic (it is chosen such that the subset relation on vertices can be checked quickly):

sage: v = H.vertical(0)
sage: list(v.vertices())
[0, ∞]

sage: list((-v).vertices())
[0, ∞]

Use HyperbolicOrientedGeodesic.start() and HyperbolicOrientedGeodesic.end() to get the end points in an order that is consistent with orientation:

sage: v.start(), v.end()
(0, ∞)

sage: (-v).start(), (-v).end()
(∞, 0)

The vertices can also be determined for an unoriented geodesic:

sage: v.unoriented().vertices()
{0, ∞}

Vertices can be computed even if they do not have coordinates over the HyperbolicPlane.base_ring():

sage: H.half_circle(0, 2).vertices()
{-1.41421356237310, 1.41421356237310}

Note

The implementation of this method is not robust over inexact rings.

See also

HyperbolicConvexSet.vertices() for more details.

class flatsurf.geometry.hyperbolic.HyperbolicGeometry(ring)[source]

Predicates and primitive geometric constructions over a base ring.

This class and its subclasses implement the core underlying hyperbolic geometry that depends on the base ring. For example, when deciding whether two points in the hyperbolic plane are equal, we cannot just compare their coordinates if the base ring is inexact. Therefore, that predicate is implemented in this “geometry” class and is implemented differently by HyperbolicExactGeometry for exact and HyperbolicEpsilonGeometry for inexact rings.

INPUT:

  • ring – a ring, the ring in which coordinates in the hyperbolic plane will be represented

Note

Abstract methods are not marked with \(@abstractmethod\) since we cannot use the ABCMeta metaclass to enforce their implementation; otherwise, our subclasses could not use the unique representation metaclasses.

EXAMPLES:

The specific hyperbolic geometry implementation is picked automatically, depending on whether the base ring is exact or not:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: H.geometry
Exact geometry over Rational Field
sage: H(0) == H(1/1024)
False

However, we can explicitly use a different or custom geometry:

sage: from flatsurf.geometry.hyperbolic import HyperbolicEpsilonGeometry
sage: H = HyperbolicPlane(QQ, HyperbolicEpsilonGeometry(QQ, 1/1024))
sage: H.geometry
Epsilon geometry with ϵ=1/1024 over Rational Field
sage: H(0) == H(1/2048)
True
__annotations__ = {}
__dict__ = mappingproxy({'__module__': 'flatsurf.geometry.hyperbolic', '__doc__': '\n    Predicates and primitive geometric constructions over a base ``ring``.\n\n    This class and its subclasses implement the core underlying hyperbolic\n    geometry that depends on the base ring. For example, when deciding whether\n    two points in the hyperbolic plane are equal, we cannot just compare their\n    coordinates if the base ring is inexact. Therefore, that predicate is\n    implemented in this "geometry" class and is implemented differently by\n    :class:`HyperbolicExactGeometry` for exact and\n    :class:`HyperbolicEpsilonGeometry` for inexact rings.\n\n    INPUT:\n\n    - ``ring`` -- a ring, the ring in which coordinates in the hyperbolic plane\n      will be represented\n\n    .. NOTE::\n\n        Abstract methods are not marked with `@abstractmethod` since we cannot\n        use the ABCMeta metaclass to enforce their implementation; otherwise,\n        our subclasses could not use the unique representation metaclasses.\n\n    EXAMPLES:\n\n    The specific hyperbolic geometry implementation is picked automatically,\n    depending on whether the base ring is exact or not::\n\n        sage: from flatsurf import HyperbolicPlane\n        sage: H = HyperbolicPlane()\n        sage: H.geometry\n        Exact geometry over Rational Field\n        sage: H(0) == H(1/1024)\n        False\n\n    However, we can explicitly use a different or custom geometry::\n\n        sage: from flatsurf.geometry.hyperbolic import HyperbolicEpsilonGeometry\n        sage: H = HyperbolicPlane(QQ, HyperbolicEpsilonGeometry(QQ, 1/1024))\n        sage: H.geometry\n        Epsilon geometry with ϵ=1/1024 over Rational Field\n        sage: H(0) == H(1/2048)\n        True\n\n    .. SEEALSO::\n\n        :class:`HyperbolicExactGeometry`, :class:`HyperbolicEpsilonGeometry`\n\n    ', '__init__': <function HyperbolicGeometry.__init__>, 'base_ring': <function HyperbolicGeometry.base_ring>, '_zero': <function HyperbolicGeometry._zero>, '_cmp': <function HyperbolicGeometry._cmp>, '_sgn': <function HyperbolicGeometry._sgn>, '_equal': <function HyperbolicGeometry._equal>, '_determinant': <function HyperbolicGeometry._determinant>, 'change_ring': <function HyperbolicGeometry.change_ring>, 'projective': <function HyperbolicGeometry.projective>, 'half_circle': <function HyperbolicGeometry.half_circle>, 'vertical': <function HyperbolicGeometry.vertical>, 'classify_point': <function HyperbolicGeometry.classify_point>, 'intersection': <function HyperbolicGeometry.intersection>, '__dict__': <attribute '__dict__' of 'HyperbolicGeometry' objects>, '__weakref__': <attribute '__weakref__' of 'HyperbolicGeometry' objects>, '__annotations__': {}})
__init__(ring)[source]
__module__ = 'flatsurf.geometry.hyperbolic'
__weakref__

list of weak references to the object

_equal(x, y)[source]

Return whether x and y should be considered equal in the base_ring().

Note

This predicate should not be used directly in geometric constructions since it does not specify the context in which this question is asked. This makes it very difficult to override a specific aspect in a custom geometry.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane(RR)
sage: H.geometry._equal(0, 1)
False
sage: H.geometry._equal(0, 1e-10)
True
base_ring()[source]

Return the ring over which this geometry is implemented.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: H.geometry.base_ring()
Rational Field
change_ring(ring)[source]

Return this geometry with the base_ring() changed to ring.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: H.geometry
Exact geometry over Rational Field
sage: H.geometry.change_ring(AA)
Exact geometry over Algebraic Real Field
classify_point(x, y, model)[source]

Return whether the point (x, y) is finite, ideal, or ultra-ideal.

INPUT:

  • x – an element of the base_ring()

  • y – an element of the base_ring()

  • model – a supported model, either "half_plane" or "klein"

OUTPUT: 1 if the point is finite, 0 if the point is ideal, -1 if the point is neither of the two.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.geometry.classify_point(0, 1, model="half_plane")
1
sage: H.geometry.classify_point(0, 0, model="half_plane")
0
sage: H.geometry.classify_point(0, -1, model="half_plane")
-1

Unfortunately, over an inexact field, this detects points close to the real axis as being ultra-ideal:

sage: H = HyperbolicPlane(RR)
sage: H.geometry.classify_point(0, -1e32, model="half_plane")
-1
half_circle(center, radius_squared, geodesic)[source]

Return the geodesic around the real center and with radius_squared in the upper half plane.

INPUT:

  • center – an element of the base_ring(), the center of the half circle on the real axis

  • radius_squared – a positive element of the base_ring(), the square of the radius of the half circle

  • geodesic – the HyperbolicPlane.geodesic() to create geodesics

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.geometry.half_circle(0, 1, H.geodesic)
{(x^2 + y^2) - 1 = 0}

Unfortunately, this does not work correctly over inexact fields yet:

sage: H = HyperbolicPlane(RR)
sage: H.geometry.half_circle(0, 1e-32, H.geodesic)
Traceback (most recent call last):
...
ValueError: radius must be positive
intersection(f, g)[source]

Return the point of intersection between the Euclidean lines f and g.

INPUT:

  • f – a triple of elements (a, b, c) of base_ring() encoding the line \(a + bx + cy = 0\)

  • g – a triple of elements (a, b, c) of base_ring() encoding the line \(a + bx + cy = 0\)

OUTPUT: A pair of elements of base_ring(), the coordinates of the point of intersection, or None if the lines do not intersect.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.geometry.intersection((0, 1, 0), (0, 0, 1))
(0, 0)
projective(p, q, point)[source]

Return the ideal point with projective coordinates [p: q] in the upper half plane model.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.geometry.projective(1, 0, H.point)
∞
sage: H.geometry.projective(0, 1, H.point)
0
vertical(real, geodesic)[source]

Return the vertical geodesic at the real ideal point in the upper half plane model.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.geometry.vertical(0, H.geodesic)
{-x = 0}

Unfortunately, this does not allow creation of verticals at large reals over inexact fields yet:

sage: H = HyperbolicPlane(RR)
sage: H.geometry.vertical(1e32, H.geodesic)
Traceback (most recent call last):
...
ValueError: equation ... does not define a chord in the Klein model
class flatsurf.geometry.hyperbolic.HyperbolicHalfSpace(parent, geodesic)[source]

A closed half space of the hyperbolic plane.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.half_circle(0, 1).left_half_space()
{(x^2 + y^2) - 1 ≥ 0}
__annotations__ = {}
__contains__(point)[source]

Return whether point is contained in this half space.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: h = H.vertical(0).left_half_space()
sage: I in h
True

sage: I - 1 in h
True

sage: I + 1 in h
False

sage: oo in h
True

We can also check containment of ideal endpoints of geodesics:

sage: g = H.half_circle(0, 2)
sage: g.start() in h
True

sage: g.end() in h
False
sage: g = H.half_circle(-3, 2)
sage: g.start() in h
True

sage: g.end() in h
True
sage: g = H.half_circle(3, 2)
sage: g.start() in h
False

sage: g.end() in h
False
sage: h = H.half_circle(0, 2).left_half_space()
sage: g = H.half_circle(0, 5)

sage: g.start() in h
True

sage: g.start() in -h
False

Note

The implementation is currently not very robust over inexact rings.

See also

HyperbolicConvexSet.is_subset() to check containment of arbitrary sets.

__eq__(other)[source]

Return whether this set is indistinguishable from other.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: h = H.vertical(0).left_half_space()

sage: h == H.vertical(0).left_half_space()
True
sage: h == H.vertical(0).right_half_space()
False
sage: h != H.vertical(0).left_half_space()
False
sage: h != H.vertical(0).right_half_space()
True
__hash__()[source]

Return a hash value for this half space

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

Since half spaces are hashable, they can be put in a hash table, such as a Python set:

sage: S = {H.vertical(0).left_half_space(), H.vertical(0).right_half_space()}
sage: len(S)
2
__init__(parent, geodesic)[source]
__module__ = 'flatsurf.geometry.hyperbolic'
__neg__()[source]

Return the closure of the complement of this half space.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: S = H.half_circle(0, 1).left_half_space()
sage: S
{(x^2 + y^2) - 1 ≥ 0}

sage: -S
{(x^2 + y^2) - 1 ≤ 0}
_isometry_conditions(other)[source]

Return an iterable of primitive pairs that must map to each other in an isometry that maps this set to other.

Helper method for HyperbolicPlane._isometry_conditions().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: v = H.vertical(0).left_half_space()
sage: w = H.vertical(1).right_half_space()

sage: conditions = v._isometry_conditions(w)
sage: list(conditions)
[[({-x = 0}, {x - 1 = 0})]]

See also

HyperbolicConvexSet._isometry_conditions() for a general description.

r

boundary()[source]

Return a geodesic on the boundary of this half space, oriented such that the half space is on its left.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: S = H.vertical(0).left_half_space()
sage: S.boundary()
{-x = 0}

See also

HyperbolicOrientedGeodesic.left_half_space() to recover the half space from the oriented geodesic

change(ring=None, geometry=None, oriented=None)[source]

Return a modified copy of this half space.

INPUT:

  • ring – a ring (default: None to keep the current base_ring()); the ring over which the new half space will be defined.

  • geometry – a HyperbolicGeometry (default: None to keep the current geometry); the geometry that will be used for the new half space.

  • oriented – a boolean (default: None to keep the current orientedness); must be None or False since half spaces cannot have an explicit orientation. See is_oriented().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: h = H.vertical(0).left_half_space()

We change the base ring over which this space is defined:

sage: h.change(ring=AA)
{x ≤ 0}

We cannot change the orientedness of a half space:

sage: h.change(oriented=True) Traceback (most recent call last): … NotImplementedError: half spaces cannot have an explicit orientation

sage: h.change(oriented=False) {x ≤ 0}

dimension()[source]

Return the dimension of this half space, i.e., 2.

This implements HyperbolicConvexSet.dimension().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).left_half_space().dimension()
2
equation(model, normalization=None)[source]

Return an inequality for this half space as a triple a, b, c such that:

  • if model is "half_plane", a point \(x + iy\) of the upper half plane is in the half space if it satisfies \(a(x^2 + y^2) + bx + c \ge 0\).

  • if model is "klein", points \((x, y)\) in the unit disk satisfy \(a + bx + cy \ge 0\).

Note that the output is not unique since the coefficients can be scaled by a positive scalar.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicHalfSpace
sage: H = HyperbolicPlane()

sage: h = H.half_circle(0, 1).left_half_space()

sage: h.equation(model="half_plane")
(2, 0, -2)

sage: H.half_space(2, 0, -2, model="half_plane") == h
True

sage: h.equation(model="klein")
(0, 0, 2)

sage: H.half_space(0, 0, 2, model="klein") == h
True

See also

HyperbolicPlane.half_space() to build a half space from the coefficients returned by this method.

half_spaces()[source]

Return the half spaces defining this half space, i.e., this half space itself.

Implements HyperbolicConvexSet.half_spaces().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: S = H.vertical(0).left_half_space()
sage: [S] == list(S.half_spaces())
True
plot(model='half_plane', **kwds)[source]

Return a plot of this half space in the hyperbolic model.

See HyperbolicConvexPolygon.plot() for the supported keyword arguments.

INPUT:

  • model – one of "half_plane" and "klein" (default: "half_plane")

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: G = H.vertical(0).left_half_space().plot()

In the half plane model, the half space is rendered as an infinite polygon:

sage: G = H.vertical(0).left_half_space().plot()
sage: G[0]
CartesianPathPlot([CartesianPathPlotCommand(code='MOVETO', args=(0.000000000000000, 0.000000000000000)),
    CartesianPathPlotCommand(code='RAYTO', args=(0, 1)),
    CartesianPathPlotCommand(code='RAYTO', args=(-1, 0)),
    CartesianPathPlotCommand(code='LINETO', args=(0.000000000000000, 0.000000000000000))])
classmethod random_set(parent)[source]

Return a random half space.

This implements HyperbolicConvexSet.random_set().

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: from flatsurf.geometry.hyperbolic import HyperbolicHalfSpace
sage: x = HyperbolicHalfSpace.random_set(H)

sage: x.dimension()
2
vertices(marked_vertices=True)[source]

Return the vertices bounding this half space.

INPUT:

  • marked_vertices – a boolean (default: True), ignored since a half space cannot have marked vertices.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

The vertices of a half space are the ideal end points of its boundary geodesic:

sage: H.vertical(0).left_half_space().vertices()
{0, ∞}

Note that iteration in the set is not consistent with the orientation of the half space (it is chosen such that the subset relation on vertices can be checked quickly):

sage: h = H.vertical(0).left_half_space()
sage: list(h.vertices())
[0, ∞]

sage: list((-h).vertices())
[0, ∞]

Use HyperbolicOrientedGeodesic.start() and HyperbolicOrientedGeodesic.end() on the boundary() to get the end points in an order consistent with the orientation:

sage: g = h.boundary()
sage: g.start(), g.end()
(0, ∞)

sage: g = (-h).boundary()
sage: g.start(), g.end()
(∞, 0)

Vertices can be computed even though they do not have coordinates over the HyperbolicPlane.base_ring():

sage: H.half_circle(0, 2).left_half_space().vertices()
{-1.41421356237310, 1.41421356237310}

See also

HyperbolicConvexSet.vertices() for more details.

class flatsurf.geometry.hyperbolic.HyperbolicHalfSpaces(entries, assume_sorted=None)[source]

A set of half spaces in the hyperbolic plane ordered counterclockwise.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: half_spaces = H.vertical(0).half_spaces()
sage: half_spaces
{{x ≤ 0}, {x ≥ 0}}

See also

HyperbolicConvexSet.half_spaces() to obtain such a set

__abstractmethods__ = frozenset({})
__annotations__ = {}
__module__ = 'flatsurf.geometry.hyperbolic'
classmethod _lt_(lhs, rhs)[source]

Return whether the half space lhs is smaller than rhs in a cyclic ordering of normal vectors, i.e., order half spaces by whether their normal points to the left/right, the slope of the geodesic, and finally by containment.

This ordering is such that HyperbolicPlane.intersection() can be computed in linear time for two hyperbolic convex sets.

INPUT:

Note

The implementation is not very robust over inexact rings and should be improved for that use case.

static convex_hull(vertices)[source]

Return the convex hull of vertices as a ordered set of half spaces.

INPUT:

ALGORITHM:

We use the classical Euclidean Graham scan algorithm in the Klein model.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicHalfSpaces
sage: H = HyperbolicPlane()

sage: HyperbolicHalfSpaces.convex_hull([H(0), H(1), H(oo)])
{{(x^2 + y^2) - x ≥ 0}, {x - 1 ≤ 0}, {x ≥ 0}}

sage: HyperbolicHalfSpaces.convex_hull([H(0), H(1), H(I), H(oo)])
{{(x^2 + y^2) - x ≥ 0}, {x - 1 ≤ 0}, {x ≥ 0}}

sage: HyperbolicHalfSpaces.convex_hull([H(0), H(1), H(I), H(I + 1), H(oo)])
{{(x^2 + y^2) - x ≥ 0}, {x - 1 ≤ 0}, {x ≥ 0}}

sage: HyperbolicHalfSpaces.convex_hull([H(1/2), H(-1/2), H(1), H(I), H(I + 1), H(oo)])
{{2*(x^2 + y^2) - 3*x + 1 ≥ 0}, {x - 1 ≤ 0}, {2*x + 1 ≥ 0}, {4*(x^2 + y^2) - 1 ≥ 0}}
class flatsurf.geometry.hyperbolic.HyperbolicOrientedConvexSet[source]

Base class for sets that have an explicit orientation.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicOrientedConvexSet
sage: H = HyperbolicPlane()

sage: isinstance(H(0), HyperbolicOrientedConvexSet)
False

sage: isinstance(H.vertical(0), HyperbolicOrientedConvexSet)
True

sage: isinstance(H.vertical(0).unoriented(), HyperbolicOrientedConvexSet)
False
__annotations__ = {}
__module__ = 'flatsurf.geometry.hyperbolic'
class flatsurf.geometry.hyperbolic.HyperbolicOrientedGeodesic(parent, a, b, c)[source]

An oriented geodesic in the hyperbolic plane.

Internally, we represent geodesics as the chords satisfying the equation

\[a + bx + cy = 0\]

in the unit disk of the Klein model.

The geodesic is oriented such that the half space

\[a + bx + cy ≥ 0\]

is on its left.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0)
{-x = 0}

sage: H.half_circle(0, 1)
{(x^2 + y^2) - 1 = 0}

sage: H.geodesic(H(I), 0)
{x = 0}

See also

HyperbolicPlane.geodesic() for the most common ways to construct geodesics.

HyperbolicUnorientedGeodesic for geodesics without an explicit orientation and HyperbolicGeodesic for shared functionality of all geodesics.

__annotations__ = {}
__module__ = 'flatsurf.geometry.hyperbolic'
__neg__()[source]

Return this geodesic with its orientation reversed.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: -H.vertical(0)
{x = 0}
angle(other, numerical=True)[source]

Compute the angle between this geodesic and other divided by 2π, i.e., as a number in \([0, 1)\).

If this geodesic and other do not intersect, a ValueError is raised.

INPUT:

  • other – a geodesic intersecting this geodesic in a finite or ideal point.

  • numerical – a boolean (default: True); whether a numerical approximation of the angle is returned or whether we attempt to render it as a rational number.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane(QQ)

sage: g0 = H.geodesic(-1, 1)
sage: g1 = H.geodesic(0, 2)
sage: g2 = H.geodesic(1, 2)

sage: g0.angle(g1, numerical=False)
1/6

sage: assert g0.angle(g1, numerical=False) == (-g0).angle(-g1, numerical=False)
sage: assert g1.angle(g2, numerical=False) == (-g2).angle(-g1, numerical=False)
sage: assert g0.angle(g1, numerical=False) + g1.angle(-g0, numerical=False) == 1/2
sage: assert g1.angle(g2, numerical=False) + g1.angle(-g2, numerical=False) == 1/2
sage: H.geodesic(0, 1).angle(H.geodesic(1, Infinity))
0.5

sage: H.geodesic(0, 1).angle(H.geodesic(Infinity, 1))
0.0
sage: m = matrix(2, [2, 1, 1, 1])

sage: g0.apply_isometry(m).angle(g1.apply_isometry(m), numerical=False)
1/6
sage: a = H.point(0, 1, model='half_plane')
sage: b = H.point(1, 1, model='half_plane')
sage: c = H.point(1, 2, model='half_plane')

sage: H.geodesic(a, b).angle(H.geodesic(b, c))
0.32379180882521663

sage: H.geodesic(b, a).angle(H.geodesic(c, b))
0.32379180882521663

sage: H.geodesic(a, b).angle(H.geodesic(b, c)) + H.geodesic(b, c).angle(H.geodesic(b, a))
0.5
sage: g3 = H.geodesic(2, 3)
sage: g0.angle(g3)
Traceback (most recent call last):
...
ValueError: geodesics do not intersect
ccw(other, euclidean=False)[source]

Return +1, -1, or zero if the turn from this geodesic to other is counterclockwise, clockwise, or if the geodesics are parallel at their point of intersection, respectively.

If this geodesic and other do not intersect, a ValueError is raised.

INPUT:

  • other – another geodesic intersecting this geodesic

  • euclidean – a boolean (default: False); if True, the geodesics are treated as lines in the Euclidean plane coming from their representations in the Klein model, i.e., geodesics intersecting at a non-finite point are reporting their Euclidean ccw. Otherwise, geodesics intersecting at an ideal point have a ccw of zero, and geodesics intersecting at an ultra-ideal point, are producing a ValueError.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane sage: H = HyperbolicPlane()

sage: v = H.vertical(0) sage: g = H.half_circle(0, 2)

sage: v.ccw(g) -1 sage: v.ccw(-g) 1

Two verticals meet intersect with an angle 0 at infinity:

sage: w = H.vertical(1)
sage: v.ccw(w)
0

However, we can also get the way the chords are turning in the Klein model:

sage: v.ccw(w, euclidean=True)
1

Geodesics that do not intersect produce an error:

sage: g = H.half_circle(2, 1)
sage: v.ccw(g)
Traceback (most recent call last):
...
ValueError: geodesics do not intersect

However, these geodesics do intersect at an ultra-ideal point and we can get their orientation at that point:

sage: v.ccw(g, euclidean=True)
1
end()[source]

Return the ideal end point of this geodesic.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).end()
∞

The coordinates of the end points of the half circle of radius \(\sqrt{2}\) around 0 can not be written down in the rationals:

sage: p = H.half_circle(0, 2).end()
sage: p
1.41421356237310

sage: p.coordinates()
Traceback (most recent call last):
...
ValueError: square root of 32 ...

Passing to a bigger field, the coordinates can be represented:

sage: K.<a> = QQ.extension(x^2 - 2, embedding=1.4)
sage: H.half_circle(0, 2).change_ring(K).end()
a
left_half_space()[source]

Return the closed half space to the left of this (oriented) geodesic.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane(AA)

sage: H.vertical(0).left_half_space()
{x ≤ 0}

See also

right_half_space() for the corresponding half space on the other side

HyperbolicPlane.half_space() for another method to create half spaces.

parametrize(point, model, check=True)[source]

Return the value of point in a linear parametrization of this geodesic.

INPUT:

  • point – a HyperbolicPoint on this geodesic

  • model – a string; currently only "euclidean" is supported

  • check – a boolean (default: True); whether to ensure that point is actually a point on the geodesic

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

We can parametrize points on a geodesic such that the order of the points corresponds to the parameters we get:

sage: g = H.vertical(0)

sage: g.parametrize(0, model="euclidean")
-1

sage: g.parametrize(I, model="euclidean")
0

sage: g.parametrize(2*I, model="euclidean")
3/5

sage: g.parametrize(oo, model="euclidean")
1

Note

This method is not robust for points over inexact rings and should be improved.

See also

unparametrize() for the recovers the point from the parameter

classmethod random_set(parent)[source]

Return a random oriented geodesic.

This implements HyperbolicConvexSet.random_set().

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: from flatsurf.geometry.hyperbolic import HyperbolicOrientedGeodesic
sage: x = HyperbolicOrientedGeodesic.random_set(H)

sage: x.dimension()
1
right_half_space()[source]

Return the closed half space to the right of this (oriented) geodesic.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane(AA)

sage: H.vertical(0).right_half_space()
{x ≥ 0}

See also

left_half_space() for the corresponding half space on the other side

HyperbolicPlane.half_space() for another method to create half spaces.

start()[source]

Return the ideal starting point of this geodesic.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).start()
0

The coordinates of the end points of the half circle of radius \(\sqrt{2}\) around 0 can not be written down in the rationals:

sage: p = H.half_circle(0, 2).start()
sage: p
-1.41421356237310

sage: p.coordinates()
Traceback (most recent call last):
...
ValueError: square root of 32 ...

Passing to a bigger field, the coordinates can be represented:

sage: K.<a> = QQ.extension(x^2 - 2, embedding=1.4)
sage: H.half_circle(0, 2).change_ring(K).start()
-a
unparametrize(λ, model, check=True)[source]

Return the point parametrized by λ on this geodesic.

INPUT:

  • λ – an element of HyperbolicPlane.base_ring()

  • model – a string; currently only "euclidean" is supported

  • check – a boolean (default: True); whether to ensure that the returned point is not ultra ideal

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

This method is the inverse of :meth;`parametrize`:

sage: g = H.vertical(0)

sage: g.unparametrize(g.parametrize(0, model="euclidean"), model="euclidean")
0

sage: g.unparametrize(g.parametrize(I, model="euclidean"), model="euclidean")
I

sage: g.unparametrize(g.parametrize(2*I, model="euclidean"), model="euclidean")
2*I

sage: g.unparametrize(g.parametrize(oo, model="euclidean"), model="euclidean")
∞
class flatsurf.geometry.hyperbolic.HyperbolicOrientedSegment(parent, geodesic, start=None, end=None)[source]

An oriented (possibly infinite) segment in the hyperbolic plane such as a boundary edge of a HyperbolicConvexPolygon.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: s = H(I).segment(2*I)

See also

Use HyperbolicPlane.segment() or HyperbolicPoint.segment() to construct oriented segments.

__annotations__ = {}
__hash__()[source]

Return a hash value for this set.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()
sage: s = H(I).segment(2*I)

Since this set is hashable, it can be put in a hash table, such as a Python set:

sage: {s}
{{-x = 0} ∩ {(x^2 + y^2) - 1 ≥ 0} ∩ {(x^2 + y^2) - 4 ≤ 0}}
__module__ = 'flatsurf.geometry.hyperbolic'
__neg__()[source]

Return this segment with its orientation reversed.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: s = H(I).segment(2*I)
sage: s
{-x = 0} ∩ {(x^2 + y^2) - 1 ≥ 0} ∩ {(x^2 + y^2) - 4 ≤ 0}

sage: -s
{x = 0} ∩ {(x^2 + y^2) - 4 ≤ 0} ∩ {(x^2 + y^2) - 1 ≥ 0}
_isometry_conditions(other)[source]

Return an iterable of primitive pairs that must map to each other in an isometry that maps this set to other.

Helper method for HyperbolicPlane._isometry_conditions().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: s = H(I).segment(2*I)

sage: conditions = s._isometry_conditions(s)
sage: list(conditions)
[[(I, I), (2*I, 2*I)]]

See also

HyperbolicConvexSet._isometry_conditions() for a general description.

r

end()[source]

Return the end point of this segment.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: s = H(I).segment(2*I)
sage: s.end()
2*I

sage: s.end().is_finite()
True

The end point can also be an ideal point:

sage: s = H(I).segment(oo)
sage: s.end()
∞

sage: s.end().is_ideal()
True

See also

start()

classmethod random_set(parent)[source]

Return a random oriented segment.

This implements HyperbolicConvexSet.random_set().

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: from flatsurf.geometry.hyperbolic import HyperbolicOrientedSegment
sage: x = HyperbolicOrientedSegment.random_set(H)

sage: x.dimension()
1
start()[source]

Return the start point of this segment.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: s = H(I).segment(2*I)
sage: s.start()
I

sage: s.start().is_finite()
True

The start point can also be an ideal point:

sage: s = H(0).segment(2*I)
sage: s.start()
0

sage: s.start().is_ideal()
True

See also

end()

class flatsurf.geometry.hyperbolic.HyperbolicPlane(base_ring, geometry, category)[source]

The hyperbolic plane.

All objects in the plane must be specified over the given base ring. Note that, in some representations, objects might appear to live in a larger ring. E.g., when specifying a line by giving a center and the square of its radius in the half plane model, then the ideal endpoints of this line might have coordinates in the ring after adjoining a square root.

The implemented elements of the plane are convex subsets such as (finite and infinite) points, geodesics, closed half planes, and closed convex polygons.

ALGORITHM:

We usually display objects as if they were defined in the upper half plane model. However, internally, we store most objects in a representation in the Klein model. In that model it tends to be easier to perform computations without having to extend the base ring and we can also rely on standard algorithms for geometry in the Euclidean plane.

For the Klein model, we use a unit disk centered at (0, 0). The map from the upper half plane sends the imaginary unit \(i\) to the center at the origin, and sends 0 to (0, -1), 1 to (1, 0), -1 to (-1, 0) and infinity to (0, 1). The Möbius transformation

\[z \mapsto \frac{z-i}{1 - iz}\]

maps from the half plane model to the Poincaré disk model. We then post-compose this with the map that goes from the Poincaré disk model to the Klein model, which in polar coordinates sends

\[(\phi, r)\mapsto \left(\phi, \frac{2r}{1 + r^2}\right).\]

When we write out the full map explicitly in Euclidean coordinates, we get

\[(x, y) \mapsto \frac{1}{1 + x^2 + y^2}\left(2x, -1 + x^2 + y^2\right)\]

and

\[(x, y) \mapsto \frac{1}{1 - y}\left(x, \sqrt{1 - x^2 - y^2}\right),\]

for its inverse.

A geodesic in the upper half plane is given by an equation of the form

\[a(x^2 + y^2) + bx + c = 0\]

which converts to an equation in the Klein model as

\[(a + c) + bx + (a - c)y = 0.\]

Conversely, a geodesic’s equation in the Klein model

\[a + bx + cy = 0\]

corresponds to the equation

\[(a + c)(x^2 + y^2) + 2bx + (a - c) = 0\]

in the upper half plane model.

Note that the intersection of two geodesics defined by coefficients in a field \(K\) in the Klein model has coordinates in \(K\) in the Klein model. The corresponding statement is not true for the upper half plane model.

INPUT:

  • base_ring – a base ring for the coefficients defining the equations of geodesics in the plane; defaults to the rational field if not specified.

  • geometry – an implementation of the geometric primitives specified by HyperbolicExactGeometry. If the base_ring is exact, this defaults to HyperbolicExactGeometry over that base ring. If the base ring is RR or RealField, this defaults to the HyperbolicEpsilonGeometry over that ring. For other rings, a geometry must be explicitly provided.

  • category – the category for this object; if not specified, defaults to sets. Note that we do not use metric spaces here since the elements are convex subsets of the hyperbolic plane and not just points and do therefore not satisfy the assumptions of a metric space.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: HyperbolicPlane()
Hyperbolic Plane over Rational Field
sage: HyperbolicPlane(AA)
Hyperbolic Plane over Algebraic Real Field
sage: HyperbolicPlane(RR)
Hyperbolic Plane over Real Field with 53 bits of precision
__annotations__ = {}
__call__(x)[source]

Return x as an element of the hyperbolic plane.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()

sage: H(1)
1

We need to override this method. The normal code path in SageMath requires the argument to be an Element but facade sets are not elements:

sage: v = H.vertical(0)

sage: Parent.__call__(H, v)
Traceback (most recent call last):
...
TypeError: Cannot convert HyperbolicOrientedGeodesic_with_category_with_category to sage.structure.element.Element

sage: H(v)
{-x = 0}
static __classcall__(base_ring=None, geometry=None, category=None)[source]

Create the hyperbolic plane with normalized arguments to make it a unique SageMath parent.

__contains__(x)[source]

Return whether the hyperbolic plane contains x.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()

We mostly rely on the standard implementation in SageMath, i.e., the interplay between the operator == and the coercion model:

sage: 0 in H
True

However, we override that logic for complex numbers. There cannot be a coercion from the complex number to the hyperbolic plane since that map would not be total but we want the following to work:

sage: I in H
True

We do not support such containment checks when conversion of the element could lead to a loss of precision:

sage: CC(I) in H
False

Note

There is currently no way to check whether a point is in the interior of a set.

See also

HyperbolicConvexSet.__contains__() to check containment of a point in subsets of the hyperbolic plane.

__dict__ = mappingproxy({'__module__': 'flatsurf.geometry.hyperbolic', '__doc__': "\n    The hyperbolic plane.\n\n    All objects in the plane must be specified over the given base ring. Note\n    that, in some representations, objects might appear to live in a larger\n    ring. E.g., when specifying a line by giving a center and the square of\n    its radius in the half plane model, then the ideal endpoints of this line\n    might have coordinates in the ring after adjoining a square root.\n\n    The implemented elements of the plane are convex subsets such as (finite\n    and infinite) points, geodesics, closed half planes, and closed convex\n    polygons.\n\n    ALGORITHM:\n\n    We usually display objects as if they were defined in the upper half plane\n    model. However, internally, we store most objects in a representation in\n    the Klein model. In that model it tends to be easier to perform\n    computations without having to extend the base ring and we can also rely on\n    standard algorithms for geometry in the Euclidean plane.\n\n    For the Klein model, we use a unit disk centered at (0, 0). The map from\n    the upper half plane sends the imaginary unit `i` to the center at the\n    origin, and sends 0 to (0, -1), 1 to (1, 0), -1 to (-1, 0) and infinity to\n    (0, 1). The Möbius transformation\n\n    .. MATH::\n\n        z \\mapsto \\frac{z-i}{1 - iz}\n\n    maps from the half plane model to the Poincaré disk model. We then\n    post-compose this with the map that goes from the Poincaré disk model to\n    the Klein model, which in polar coordinates sends\n\n    .. MATH::\n\n        (\\phi, r)\\mapsto \\left(\\phi, \\frac{2r}{1 + r^2}\\right).\n\n    When we write out the full map explicitly in Euclidean coordinates, we get\n\n    .. MATH::\n\n        (x, y) \\mapsto \\frac{1}{1 + x^2 + y^2}\\left(2x, -1 + x^2 + y^2\\right)\n\n    and\n\n    .. MATH::\n\n        (x, y) \\mapsto \\frac{1}{1 - y}\\left(x, \\sqrt{1 - x^2 - y^2}\\right),\n\n    for its inverse.\n\n    A geodesic in the upper half plane is given by an equation of the form\n\n    .. MATH::\n\n        a(x^2 + y^2) + bx + c = 0\n\n    which converts to an equation in the Klein model as\n\n    .. MATH::\n\n        (a + c) + bx + (a - c)y = 0.\n\n    Conversely, a geodesic's equation in the Klein model\n\n    .. MATH::\n\n        a + bx + cy = 0\n\n    corresponds to the equation\n\n    .. MATH::\n\n        (a + c)(x^2 + y^2) + 2bx + (a - c) = 0\n\n    in the upper half plane model.\n\n    Note that the intersection of two geodesics defined by coefficients in a\n    field `K` in the Klein model has coordinates in `K` in the Klein model.\n    The corresponding statement is not true for the upper half plane model.\n\n    INPUT:\n\n    - ``base_ring`` -- a base ring for the coefficients defining the equations\n      of geodesics in the plane; defaults to the rational field if not\n      specified.\n\n    - ``geometry`` -- an implementation of the geometric primitives specified\n      by :class:`HyperbolicExactGeometry`. If the ``base_ring`` is exact, this\n      defaults to :class:`HyperbolicExactGeometry` over that base ring. If the\n      base ring is ``RR`` or ``RealField``, this defaults to the\n      :class:`HyperbolicEpsilonGeometry` over that ring. For other rings, a\n      geometry must be explicitly provided.\n\n    - ``category`` -- the category for this object; if not specified, defaults\n      to sets. Note that we do not use metric spaces here since the elements\n      are convex subsets of the hyperbolic plane and not just points and do\n      therefore not satisfy the assumptions of a metric space.\n\n    EXAMPLES::\n\n        sage: from flatsurf import HyperbolicPlane\n\n        sage: HyperbolicPlane()\n        Hyperbolic Plane over Rational Field\n\n    ::\n\n        sage: HyperbolicPlane(AA)\n        Hyperbolic Plane over Algebraic Real Field\n\n    ::\n\n        sage: HyperbolicPlane(RR)\n        Hyperbolic Plane over Real Field with 53 bits of precision\n\n    ", '__classcall__': <staticmethod(<function HyperbolicPlane.__classcall__>)>, '__init__': <function HyperbolicPlane.__init__>, '_coerce_map_from_': <function HyperbolicPlane._coerce_map_from_>, '__contains__': <function HyperbolicPlane.__contains__>, 'change_ring': <function HyperbolicPlane.change_ring>, '_an_element_': <function HyperbolicPlane._an_element_>, 'some_subsets': <function HyperbolicPlane.some_subsets>, 'some_elements': <function HyperbolicPlane.some_elements>, '_test_some_subsets': <function HyperbolicPlane._test_some_subsets>, 'random_element': <function HyperbolicPlane.random_element>, '__call__': <function HyperbolicPlane.__call__>, '_element_constructor_': <function HyperbolicPlane._element_constructor_>, 'base_ring': <function HyperbolicPlane.base_ring>, 'is_exact': <function HyperbolicPlane.is_exact>, 'infinity': <function HyperbolicPlane.infinity>, 'real': <function HyperbolicPlane.real>, 'projective': <function HyperbolicPlane.projective>, 'start': <function HyperbolicPlane.start>, 'point': <function HyperbolicPlane.point>, 'half_circle': <function HyperbolicPlane.half_circle>, 'vertical': <function HyperbolicPlane.vertical>, 'geodesic': <function HyperbolicPlane.geodesic>, 'half_space': <function HyperbolicPlane.half_space>, 'segment': <function HyperbolicPlane.segment>, 'polygon': <function HyperbolicPlane.polygon>, 'convex_hull': <function HyperbolicPlane.convex_hull>, 'intersection': <function HyperbolicPlane.intersection>, 'empty_set': <function HyperbolicPlane.empty_set>, 'isometry': <function HyperbolicPlane.isometry>, '_isometry_gl2_to_sim12': <function HyperbolicPlane._isometry_gl2_to_sim12>, '_isometry_sim12_to_gl2': <function HyperbolicPlane._isometry_sim12_to_gl2>, '_isometry_from_pairs': <function HyperbolicPlane._isometry_from_pairs>, '_isometry_from_primitives': <function HyperbolicPlane._isometry_from_primitives>, '_isometry_untrivialize': <function HyperbolicPlane._isometry_untrivialize>, '_isometry_conditions': <function HyperbolicPlane._isometry_conditions>, '_isometry_from_single_points': <function HyperbolicPlane._isometry_from_single_points>, '_isometry_from_single_geodesics': <function HyperbolicPlane._isometry_from_single_geodesics>, '_isometry_from_equations': <function HyperbolicPlane._isometry_from_equations>, '_repr_': <function HyperbolicPlane._repr_>, '__dict__': <attribute '__dict__' of 'HyperbolicPlane' objects>, '__annotations__': {}})
__init__(base_ring, geometry, category)[source]

Create the hyperbolic plane over base_ring.

__module__ = 'flatsurf.geometry.hyperbolic'
_isometry_conditions(defining, remaining)[source]

Helper method for isometry().

Return sequences (typically triples) of pairs of hyperbolic primitive objects (geodesics and points) that (almost) uniquely define a hyperbolic mapping.

Build this sequence by extending defining with conditions extracted from remaining.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

Given four points, three points already uniquely determine the isometry:

sage: conditions = H._isometry_conditions(defining=[], remaining=[(H(0), H(0)), (H(1), H(1)), (H(2), H(2)), (H(3), H(3))])
sage: list(conditions)
[[(0, 0), (1, 1), (2, 2)]]

The data provided by remaining can contain redundancies:

sage: conditions = H._isometry_conditions(defining=[], remaining=[(H(0), H(0)), (H(1), H(1)), (H(1), H(1)), (H(3), H(3))])
sage: list(conditions)
[[(0, 0), (1, 1), (3, 3)]]

For more complex objects there might be lots of mappings possible (we could likely have a shorter list of possibilities here):

sage: P = H.polygon([H.vertical(1).left_half_space(), H.vertical(-1).right_half_space(), H.geodesic(-1, 1).left_half_space()], marked_vertices=[I + 1])
sage: Q = H.polygon(P.half_spaces(), marked_vertices=[I - 1])
sage: conditions = H._isometry_conditions([], [(P, Q)])
sage: list(conditions)
[[({-x + 1 = 0}, {x + 1 = 0}), ({x + 1 = 0}, {(x^2 + y^2) - 1 = 0})],
 [({-x + 1 = 0}, {(x^2 + y^2) - 1 = 0}), ({x + 1 = 0}, {-x + 1 = 0})],
 [({-x + 1 = 0}, {-x + 1 = 0}), ({x + 1 = 0}, {x + 1 = 0})],
 [({-x + 1 = 0}, {x + 1 = 0}), ({x + 1 = 0}, {-x + 1 = 0})],
 [({-x + 1 = 0}, {-x + 1 = 0}), ({x + 1 = 0}, {(x^2 + y^2) - 1 = 0})],
 [({-x + 1 = 0}, {(x^2 + y^2) - 1 = 0}), ({x + 1 = 0}, {x + 1 = 0})]]
_isometry_from_equations(conditions, filter)[source]

Helper method for isometry().

Return an isometry that satisfies conditions and filter.

INPUT:

  • conditions – a function that receives a (symbolic) isometry and some (symbolic) variables and creates polynomial equations that a concrete isometry must satisfy.

  • filter – a function that receives a concrete isometry and returns whether it maps objects correctly.

ALGORITHM:

We guess determine the entries of a 2×2 matrix with entries a, b, c, d by guessing for each term that it is non-zero and then building the symbolic relations that the isometry must satisfy by invoking conditions. These symbolic conditions contain free linear variables coming from the fact that points are encoded projectively and geodesics in the dual. We tune these variables so that all entries of the matrix are in the base ring. (Namely, so that we can take all the square roots that show up.)

The whole process is very ad-hoc and very slow since it computes lots of Gröbner bases. It is very likely that the approach is not mathematically sound but it worked for many random inputs that we presented it with.

_isometry_gl2_to_sim12(isometry)[source]

Return a lift of the isometry to a 3×3 matrix in the similitude group \(\mathrm{Sim}(1, 2)\) describing an isometry in hyperboloid model.

This is a helper method for isometry() and HyperbolicConvexSet.apply_isometry() since isometries in the hyperboloid model can be directly applied to our objects which we represent in the Klein model.

INPUT:

  • isometry – a 2×2 matrix with non-zero determinant

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H._isometry_gl2_to_sim12(matrix(2, [1,1,0,1]))
[   1   -1    1]
[   1  1/2  1/2]
[   1 -1/2  3/2]
sage: H._isometry_gl2_to_sim12(matrix(2, [1,0,1,1]))
[   1    1    1]
[  -1  1/2 -1/2]
[   1  1/2  3/2]
sage: H._isometry_gl2_to_sim12(matrix(2, [2,0,0,1/2]))
[   1    0    0]
[   0 17/8 15/8]
[   0 15/8 17/8]

See also

_isometry_sim12_to_gl2() for an inverse of this construction

_isometry_sim12_to_gl2(isometry)[source]

Return an invertible 2×2 matrix that encodes the same isometry as isometry.

INPUT:

  • isometry – a 3×3 matrix in \(\mathrm{Sim}(1, 2)\)

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H._isometry_sim12_to_gl2(matrix(3, [1, -1, 1, 1, 1/2, 1/2, 1, -1/2, 3/2]))
[1 1]
[0 1]

sage: H._isometry_sim12_to_gl2(matrix(3, [1, 1, 1, -1, 1/2, -1/2, 1, 1/2, 3/2]))
[1 0]
[1 1]

sage: H._isometry_sim12_to_gl2(matrix(3, [1, 0, 0, 0, 17/8, 15/8, 0, 15/8, 17/8]))
[  2   0]
[  0 1/2]

sage: H._isometry_sim12_to_gl2(H._isometry_gl2_to_sim12(matrix([[-1, 0], [0, 1]])))
[ 1  0]
[ 0 -1]

See also

_isometry_gl2_to_sim12() for an inverse of this construction

base_ring()[source]

Return the base ring over which objects in the plane are defined.

More specifically, all geodesics must have an equation \(a + bx + cy = 0\) in the Klein model with coefficients in this ring, and all points must have coordinates in this ring when written in the Klein model, or be the end point of a geodesic. All other objects are built from these primitives.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: HyperbolicPlane().base_ring()
Rational Field

See also

HyperbolicConvexSet.change_ring() to change the ring a set is defined over

change_ring(ring, geometry=None)[source]

Return the hyperbolic plane over a different base ring.

INPUT:

  • ring – a ring or None; if None, uses the current base_ring().

  • geometry – a geometry or None; if None, tries to convert the existing geometry to ring.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: HyperbolicPlane(QQ).change_ring(AA) is HyperbolicPlane(AA)
True

When changing to the ring RR and no geometry has been specified explicitly, the HyperbolicExactGeometry changes to the HyperbolicEpsilonGeometry, see HyperbolicExactGeometry.change_ring():

sage: HyperbolicPlane(QQ).change_ring(RR) is HyperbolicPlane(RR)
True

In the opposite direction, the geometry cannot be determined automatically:

sage: HyperbolicPlane(RR).change_ring(QQ)
Traceback (most recent call last):
...
ValueError: cannot change_ring() to an exact ring

So the geometry has to be specified explicitly:

sage: from flatsurf.geometry.hyperbolic import HyperbolicExactGeometry
sage: HyperbolicPlane(RR).change_ring(QQ, geometry=HyperbolicExactGeometry(QQ)) is HyperbolicPlane(QQ)
True

See also

HyperbolicConvexSet.change_ring() or more generally HyperbolicConvexSet.change() to change the ring and geometry a set is defined over.

convex_hull(marked_vertices, *subsets)[source]

Return the convex hull of the subsets.

INPUT:

  • subsets – a sequence of subsets of this hyperbolic space.

  • marked_vertices – a boolean (default: False), whether to keep redundant vertices on the boundary.

ALGORITHM:

We use the standard Graham scan algorithm which runs in O(nlogn), see HyperbolicHalfSpaces.convex_hull().

However, to get the unbounded bits of the convex hull right, we use a somewhat naive O(n²) algorithm which could probably be improved easily.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

A polygon can also be created as the convex hull of its vertices:

sage: H.convex_hull(I - 1, I + 1, 2*I - 1, 2*I + 1)
{x - 1 ≤ 0} ∩ {(x^2 + y^2) - 5 ≤ 0} ∩ {x + 1 ≥ 0} ∩ {(x^2 + y^2) - 2 ≥ 0}

The vertices can also be infinite:

sage: H.convex_hull(-1, 1, 2*I)
{(x^2 + y^2) + 3*x - 4 ≤ 0} ∩ {(x^2 + y^2) - 3*x - 4 ≤ 0} ∩ {(x^2 + y^2) - 1 ≥ 0}

Redundant vertices are removed. However, they can be kept by setting marked_vertices:

sage: H.convex_hull(-1, 1, I, 2*I)
{(x^2 + y^2) + 3*x - 4 ≤ 0} ∩ {(x^2 + y^2) - 3*x - 4 ≤ 0} ∩ {(x^2 + y^2) - 1 ≥ 0}

sage: polygon = H.convex_hull(-1, 1, I, 2*I, marked_vertices=True)
sage: polygon
{(x^2 + y^2) + 3*x - 4 ≤ 0} ∩ {(x^2 + y^2) - 3*x - 4 ≤ 0} ∩ {(x^2 + y^2) - 1 ≥ 0} ∪ {I}

sage: polygon.vertices()
{-1, I, 1, 2*I}

The convex hull of a half space and a point:

sage: H.convex_hull(H.half_circle(0, 1).right_half_space(), 0)
{(x^2 + y^2) - 1 ≤ 0}

To keep the additional vertices, again marked_vertices must be set:

sage: H.convex_hull(H.half_circle(0, 1).left_half_space(), I, marked_vertices=True)
{(x^2 + y^2) - 1 ≥ 0} ∪ {I}

sage: H.convex_hull(H.vertical(0).left_half_space(), 0, I, oo, marked_vertices=True)
{x ≤ 0} ∪ {I}

sage: H.convex_hull(H.vertical(0).right_half_space(), I, marked_vertices=True)
{x ≥ 0} ∪ {I}

Note that this cannot be used to produce marked points on a geodesic:

sage: H.convex_hull(-1, I, 1)
{(x^2 + y^2) - 1 = 0}

sage: H.convex_hull(-1, I, 1, marked_vertices=True)
Traceback (most recent call last):
...
NotImplementedError: cannot add marked vertices to low dimensional objects

Note that this cannot be used to produce marked points on a segment:

sage: H.convex_hull(I, 2*I, 3*I)
{x = 0}

sage: H.convex_hull(I, 2*I, 3*I, marked_vertices=True)
Traceback (most recent call last):
...
NotImplementedError: cannot add marked vertices to low dimensional objects

The convex point of two polygons which contain infinitely many ideal points:

sage: H.convex_hull(
....:     H.polygon([H.geodesic(-1, -1/4).left_half_space(), H.geodesic(0, -2).left_half_space()]),
....:     H.polygon([H.geodesic(4, 2).left_half_space(), H.geodesic(4, oo).left_half_space()]),
....:     H.polygon([H.geodesic(-1/2, 1/2).left_half_space(), H.geodesic(2, -2).left_half_space()])
....: )
{2*(x^2 + y^2) - x ≥ 0} ∩ {(x^2 + y^2) - 2*x - 8 ≤ 0} ∩ {8*(x^2 + y^2) + 6*x + 1 ≥ 0}

See also

HyperbolicHalfSpaces.convex_hull() for the underlying implementation intersection() to compute the intersection of convex sets

empty_set()[source]

Return an empty subset of this space.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: HyperbolicPlane().empty_set()
{}
geodesic(a, b, c=None, model=None, oriented=True, check=True)[source]

Return a geodesic in the hyperbolic plane.

If only a and b are given, return the geodesic going through the points a and then b.

If c is specified and model is "half_plane", return the geodesic given by the half circle

\[a(x^2 + y^2) + bx + c = 0\]

oriented such that the half plane

\[a(x^2 + y^2) + bx + c \ge 0\]

is to its left.

If c is specified and model is "klein", return the geodesic given by the chord with the equation

\[a + bx + cy = 0\]

oriented such that the half plane

\[a + bx + cy \ge 0\]

is to its left.

INPUT:

  • a – a point in the hyperbolic plane or an element of the base_ring()

  • b – a point in the hyperbolic plane or an element of the base_ring()

  • cNone or an element of the base_ring() (default: None)

  • modelNone, "half_plane", or "klein" (default: None); when a, b and c are elements of the base_ring(), in which model they should be interpreted.

  • oriented – whether the returned geodesic is oriented (default: True)

  • check – whether to verify that the arguments define a geodesic in the hyperbolic plane (default: True)

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.geodesic(-1, 1)
{(x^2 + y^2) - 1 = 0}

sage: H.geodesic(0, I)
{-x = 0}

sage: H.geodesic(-1, I + 1)
{2*(x^2 + y^2) - x - 3 = 0}

sage: H.geodesic(2, -1, -3, model="half_plane")
{2*(x^2 + y^2) - x - 3 = 0}

sage: H.geodesic(-1, -1, 5, model="klein")
{2*(x^2 + y^2) - x - 3 = 0}

Geodesics cannot be defined from points whose coordinates are over a quadratic field extension:

sage: H.geodesic(H.half_circle(0, 2).start(), H.half_circle(1, 2).end())
Traceback (most recent call last):
...
ValueError: square root of 32 not in Rational Field

Except for some special cases:

sage: H.geodesic(H.half_circle(0, 2).start(), H.half_circle(0, 2).end())
{(x^2 + y^2) - 2 = 0}

Disabling the check, lets us define geodesics in the Klein model that lie outside the unit circle:

sage: H.geodesic(2, 1, 0, model="klein")
Traceback (most recent call last):
...
ValueError: ...

sage: geodesic = H.geodesic(2, 1, 0, model="klein", check=False)
sage: geodesic
{2 + x = 0}

sage: geodesic.start()
Traceback (most recent call last):
...
ValueError: geodesic does not intersect the Klein disk

sage: geodesic.end()
Traceback (most recent call last):
...
ValueError: geodesic does not intersect the Klein disk
half_circle(center, radius_squared)[source]

Return the geodesic centered around the real center and with radius_squared in the upper half plane model. The geodesic is oriented such that the point at infinity is to its left.

Use the - operator to pass to the geodesic with opposite orientation.

INPUT:

  • center – an element of the base_ring(), the center of the half circle on the real axis

  • radius_squared – a positive element of the base_ring(), the square of the radius of the half circle

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.half_circle(0, 1)
{(x^2 + y^2) - 1 = 0}

sage: H.half_circle(1, 3)
{(x^2 + y^2) - 2*x - 2 = 0}

sage: H.half_circle(1/3, 1/2)
{18*(x^2 + y^2) - 12*x - 7 = 0}

See also

vertical() to get an oriented vertical in the half plane model and geodesic() for the general interface, producing a geodesic from an equation.

half_space(a, b, c, model, check=True)[source]

Return a closed half space from its equation in model.

If model is "half_plane", return the half space

\[a(x^2 + y^2) + bx + c \ge 0\]

in the upper half plane.

If model is "klein", return the half space

\[a + bx + cy \ge 0\]

in the Klein model.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.half_space(0, -1, 0, model="half_plane")
{x ≤ 0}

It is often easier to construct a half space as the space bounded by a geodesic:

sage: H.vertical(0).left_half_space()
{x ≤ 0}

The half space y ≥ 0 given by its equation in the Klein model:

sage: H.half_space(0, 0, 1, model="klein")
{(x^2 + y^2) - 1 ≥ 0}

..SEEALSO:

:meth:`HperbolicGeodesic.left_half_space`
:meth:`HperbolicGeodesic.right_half_space`
infinity()[source]

Return the point at infinity in the upper half plane model.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()
sage: p = H.infinity()
sage: p
∞

sage: p == H(oo)
True
sage: p.is_ideal()
True

See also

point() to create points in general.

intersection(*subsets)[source]

Return the intersection of convex subsets.

ALGORITHM:

We compute the intersection of the HyperbolicConvexSet.half_spaces() that make up the subsets. That intersection can be computed in the Klein model where we can essentially reduce this problem to the intersection of half spaces in the Euclidean plane.

The Euclidean intersection problem can be solved in time linear in the number of half spaces assuming that the half spaces are already sorted in a certain way. In particular, this is the case if there is only a constant number of subsets. Otherwise, the algorithm is quasi-linear in the number of half spaces due to the added complexity of sorting.

See HyperbolicConvexPolygon._normalize() for more algorithmic details.

INPUT:

  • subsets – a non-empty sequence of subsets of this hyperbolic space.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.intersection(H.vertical(0).left_half_space())
{x ≤ 0}

sage: H.intersection(H.vertical(0).left_half_space(), H.vertical(0).right_half_space())
{x = 0}

We cannot form the intersection of no spaces yet:

sage: H.intersection()
Traceback (most recent call last):
...
NotImplementedError: the full hyperbolic space cannot be created as an intersection

See also

HyperbolicPlane.polygon() for a specialized version for the intersection of half spaces HyperbolicPlane.convex_hull() to compute the convex hull of subspaces

is_exact()[source]

Return whether hyperbolic subsets have exact coordinates.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()
sage: H.is_exact()
True

sage: H = HyperbolicPlane(RR)
sage: H.is_exact()
False
isometry(preimage, image, model='half_plane', on_right=False, normalized=False)[source]

Return an isometry that maps preimage to image.

INPUT:

  • preimage – a convex set in the hyperbolic plane or a list of such convex sets.

  • image – a convex set in the hyperbolic plane or a list of such convex sets.

  • model – one of "half_plane" and "klein", the model in which this isometry applies.

  • on_right – a boolean (default: False); whether the returned isometry maps preimage to image when multiplied from the right; otherwise from the left.

  • normalized – a boolean (default: False); whether the returned matrix has determinant ±1.

OUTPUT:

If model is "half_plane", returns a 2×2 matrix over the base_ring(), if model is "klein", returns a 3×3 matrix over the base ring. See HyperbolicConvexSet.apply_isometry() for meaning of this matrix.

ALGORITHM:

We compute an isometry with a very inefficient Gröbner basis approach.

Essentially, we try to extract three points from the preimage with their prescribed images in image, see _isometry_conditions() and determine the unique isometry mapping the points by solving the corresponding polynomial system, see _isometry_from_equations() for the hacky Gröbner basis bit.

There are a lot of problems with this approach (apart from it being extremely slow).

Usually, we do not have access to meaningful points (the ideal end points of a geodesic do not typically live in the base_ring()) so we have to map around geodesics instead. However, given two pairs of geodesics, there is in general not a unique isometry mapping one pair to the other, since there might be one isometry with positive and one with negative determinant with this property. This adds to the inefficiency because we have to try for both determinants and then check which isometry maps the actual objects in question correctly.

Similarly, for more complex objects such as polygons, we do not know a-priori which edge of the preimage polygon can be mapped to which edge of the image polygon, so we have to try for all rotations of both polygons.

Finally, the approach cannot work in general for certain undeterdetermined systems. We do not know how to determine an isometry that maps one geodesic to another geodesic. We can of course try to write down an isometry that maps a geodesic to the vertical at 0 but in general no such isometry is defined over the base ring. We can make the system determined by requiring that the midpoints of the geodesics map to each other but (apart from the midpoint not having coordinates in the base ring) that isometry might not be defined over the base ring even though there exists some isometry that maps the geodesics over the base ring.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

An isometry mapping one point to another; naturally this is not the unique isometry with this property:

sage: m = H.isometry(0, 1)
sage: H(0).apply_isometry(m)
1
sage: m
[1 1]
[0 1]
sage: H.isometry(I, I+1)
[1 1]
[0 1]
sage: H.isometry(0, oo)
[ 0 -1]
[-1  0]

An isometry is uniquely determined by its image on three points:

sage: H.isometry([0, 1, oo], [1, oo, 0])
[ 0  1]
[-1  1]

It might be impossible to find an isometry with the prescribed mapping:

sage: H.isometry(0, I)
Traceback (most recent call last):
...
ValueError: no isometry can map these objects to each other

sage: H.isometry([0, 1, oo, I], [0, 1, oo, I + 1])  # long time (.4s)
Traceback (most recent call last):
...
ValueError: no isometry can map these objects to each other

We can determine isometries by mapping more complex objects than points, e.g., geodesics:

sage: H.isometry(H.geodesic(-1, 1), H.geodesic(1, -1))
[ 0  1]
[-1  0]

We can also determine an isometry mapping polygons:

sage: P = H.polygon([H.vertical(1).left_half_space(), H.vertical(-1).right_half_space(), H.geodesic(-1, 1).left_half_space()])
sage: Q = H.polygon([H.geodesic(-1, 0).left_half_space(), H.geodesic(0, 1).left_half_space(), H.geodesic(1, -1).left_half_space()])
sage: m = H.isometry(P, Q)
sage: P.apply_isometry(m) == Q
True

When determining an isometry of polygons, marked vertices are mapped to marked vertices:

sage: P = H.polygon(P.half_spaces(), marked_vertices=[1 + I])
sage: Q = H.polygon(P.half_spaces(), marked_vertices=[])
sage: H.isometry(P, Q)
Traceback (most recent call last):
...
ValueError: no isometry can map these objects to each other

sage: Q = H.polygon(P.half_spaces(), marked_vertices=[1 + 2*I])
sage: H.isometry(P, Q)  # long time (1s)
Traceback (most recent call last):
...
ValueError: no isometry can map these objects to each other

sage: Q = H.polygon(P.half_spaces(), marked_vertices=[-1 + I])
sage: H.isometry(P, Q)  # long time (1s)
[ 1  0]
[ 0 -1]

We can explicitly ask for an isometry in the Klein model, given by a 3×3 matrix:

sage: H.isometry(P, Q, model="klein")  # long time (1s)
[-1  0  0]
[ 0  1  0]
[ 0  0  1]

The isometries are not returned as matrices of unit determinant since such an isometry might not exist without extending the base ring, we can, however, ask for an isometry of determinant ±1:

sage: H.isometry(I, 2*I, normalized=True)
Traceback (most recent call last):
...
ValueError: not a perfect 2nd power

sage: H.change_ring(AA).isometry(I, 2*I, normalized=True)
[ 1.414213562373095?                   0]
[                  0 0.7071067811865475?]

sage: _.det()
1.000000000000000?

We can also explicitly ask for the isometry for the right action:

sage: isometry = H.isometry(H.vertical(0), H.vertical(1), on_right=True)
sage: isometry
[ 1 -1]
[ 0  1]
sage: H.vertical(0).apply_isometry(isometry)
{-x - 1 = 0}
sage: H.vertical(0).apply_isometry(isometry, on_right=True)
{-x + 1 = 0}

See also

HyperbolicConvexSet.apply_isometry() to apply the returned isometry to a convex set.

point(x, y, model, check=True)[source]

Return the point with coordinates (x, y) in the given model.

When model is "half_plane", return the point \(x + iy\) in the upper half plane.

When model is "klein", return the point (x, y) in the Klein model.

INPUT:

  • x – an element of the base_ring()

  • y – an element of the base_ring()

  • model – one of "half_plane" or "klein"

  • check – whether to validate the inputs (default: True); set this to False, to create an ultra-ideal point, i.e., a point outside the unit circle in the Klein model.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.point(0, 1, model="half_plane")
I

sage: H.point(1, 2, model="half_plane")
1 + 2*I

sage: H.point(0, 1, model="klein")
∞

An ultra-ideal point:

sage: H.point(2, 3, model="klein")
Traceback (most recent call last):
...
ValueError: point (2, 3) is not in the unit disk in the Klein model

sage: H.point(2, 3, model="klein", check=False)
(2, 3)

See also

HyperbolicOrientedGeodesic.start() and HyperbolicOrientedGeodesic.end() to generate points that do not have coordinates over the base ring. infinity(), real(), and projective() as shortcuts to generate ideal points.

polygon(half_spaces, check=True, assume_sorted=False, assume_minimal=False, marked_vertices=())[source]

Return the convex polygon obtained by intersecting half_spaces.

INPUT:

  • half_spaces – a non-empty iterable of HyperbolicHalfSpaces of this hyperbolic plane.

  • check – boolean (default: True), whether the arguments are validated.

  • assume_sorted – boolean (default: False), whether to assume that the half_spaces are already sorted with respect to HyperbolicHalfSpaces._lt_(). When set, we omit sorting the half spaces explicitly, which is asymptotically the most exponsive part of the process of creating a polygon.

  • assume_minimal – boolean (default: False), whether to assume that the half_spaces provide a minimal representation of the polygon, i.e., removing any of them describes a different polygon. When set, we omit searching for a minimal subset of half spaces to describe the polygon.

  • marked_vertices – an iterable of vertices (default: an empty tuple), the vertices are included in the HyperbolicConvexPolygon.vertices() even if they are not in the set of minimal vertices describing this polygon.

ALGORITHM:

See intersection() for algorithmic details.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

A finite convex polygon:

sage: H.polygon([
....:   H.vertical(1).left_half_space(),
....:   H.vertical(-1).right_half_space(),
....:   H.half_circle(0, 2).left_half_space(),
....:   H.half_circle(0, 4).right_half_space(),
....: ])
{x - 1 ≤ 0} ∩ {(x^2 + y^2) - 4 ≤ 0} ∩ {x + 1 ≥ 0} ∩ {(x^2 + y^2) - 2 ≥ 0}

Redundant half spaces are removed from the final representation:

sage: H.polygon([
....:   H.vertical(1).left_half_space(),
....:   H.vertical(-1).right_half_space(),
....:   H.half_circle(0, 2).left_half_space(),
....:   H.half_circle(0, 4).right_half_space(),
....:   H.half_circle(0, 6).right_half_space(),
....: ])
{x - 1 ≤ 0} ∩ {(x^2 + y^2) - 4 ≤ 0} ∩ {x + 1 ≥ 0} ∩ {(x^2 + y^2) - 2 ≥ 0}

The vertices of the polygon can be at ideal points; this polygon has vertices at -1 and 1:

sage: H.polygon([
....:   H.vertical(1).left_half_space(),
....:   H.vertical(-1).right_half_space(),
....:   H.half_circle(0, 1).left_half_space(),
....:   H.half_circle(0, 4).right_half_space(),
....: ])
{x - 1 ≤ 0} ∩ {(x^2 + y^2) - 4 ≤ 0} ∩ {x + 1 ≥ 0} ∩ {(x^2 + y^2) - 1 ≥ 0}

Any set of half spaces defines a polygon, even if the edges do not even meet at ideal points:

sage: H.polygon([
....:   H.half_circle(0, 1).left_half_space(),
....:   H.half_circle(0, 2).right_half_space(),
....: ])
{(x^2 + y^2) - 2 ≤ 0} ∩ {(x^2 + y^2) - 1 ≥ 0}

However, when the resulting set is point, the result is not represented as a polygon anymore:

sage: H.polygon([
....:   H.vertical(-1).left_half_space(),
....:   H.vertical(1).right_half_space(),
....: ])
∞

We can force the creation of this set as a polygon which might be beneficial in some algorithmic applications:

sage: H.polygon([
....:   H.vertical(-1).left_half_space(),
....:   H.vertical(1).right_half_space(),
....: ], check=False, assume_minimal=True)
{x + 1 ≤ 0} ∩ {x - 1 ≥ 0}

Note that forcing this mode does not remove redundant half spaces from the representation; we usually assume that the representation is minimal, so such a polygon might not behave correctly:

sage: H.polygon([
....:   H.vertical(-1).left_half_space(),
....:   H.vertical(1).right_half_space(),
....:   H.vertical(2).right_half_space(),
....: ], check=False, assume_minimal=True)
{x + 1 ≤ 0} ∩ {x - 1 ≥ 0} ∩ {x - 2 ≥ 0}

We could manually pass to a minimal representation by rewriting the point as half spaces again:

sage: minimal = H.polygon([
....:   H.vertical(-1).left_half_space(),
....:   H.vertical(1).right_half_space(),
....:   H.vertical(2).right_half_space(),
....: ])
sage: H.polygon(minimal.half_spaces(), check=False, assume_minimal=True)
{x ≤ 0} ∩ {x - 1 ≥ 0}

Note that this chose half spaces not in the original set; you might also want to have a look at HyperbolicConvexPolygon._normalize() for some ideas how to manually reduce the half spaces that are used in a polygon.

Note that the same applies if the intersection of half spaces is empty or just a single half space:

sage: empty = H.polygon([
....:   H.half_circle(0, 1).right_half_space(),
....:   H.half_circle(0, 2).left_half_space(),
....: ])
sage: type(empty)
<class 'flatsurf.geometry.hyperbolic.HyperbolicEmptySet_with_category_with_category'>
sage: half_space = H.polygon([
....:   H.half_circle(0, 1).right_half_space(),
....: ])
sage: type(half_space)
<class 'flatsurf.geometry.hyperbolic.HyperbolicHalfSpace_with_category_with_category'>

If we add a marked point to such a half space, the underlying type is a polygon again:

sage: half_space = H.polygon([
....:   H.half_circle(0, 1).right_half_space(),
....: ], marked_vertices=[I])
sage: half_space
{(x^2 + y^2) - 1 ≤ 0} ∪ {I}
sage: type(half_space)
<class 'flatsurf.geometry.hyperbolic.HyperbolicConvexPolygon_with_category_with_category'>

Marked points that coincide with vertices are ignored:

sage: half_space = H.polygon([
....:   H.half_circle(0, 1).right_half_space(),
....: ], marked_vertices=[-1])
sage: half_space
{(x^2 + y^2) - 1 ≤ 0}
sage: type(half_space)
<class 'flatsurf.geometry.hyperbolic.HyperbolicHalfSpace_with_category_with_category'>

Marked points must be on an edge of the polygon:

sage: H.polygon([
....:   H.half_circle(0, 1).right_half_space(),
....: ], marked_vertices=[-2])
Traceback (most recent call last):
...
ValueError: marked vertex must be on an edge of the polygon

sage: H.polygon([
....:   H.half_circle(0, 1).right_half_space(),
....: ], marked_vertices=[2*I])
Traceback (most recent call last):
...
ValueError: marked vertex must be on an edge of the polygon

The intersection of the half spaces is computed in time quasi-linear in the number of half spaces. The limiting factor is sorting the half spaces by HyperbolicHalfSpaces._lt_(). If we know that the half spaces are already sorted like that, we can make the process run in linear time by setting assume_sorted.

sage: H.polygon(H.infinity().half_spaces(), assume_sorted=True) ∞

See also

intersection() to intersect arbitrary convex sets convex_hull() to define a polygon by taking the convex hull of a union of convex sets

projective(p, q)[source]

Return the ideal point with projective coordinates [p: q] in the upper half plane model.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()
sage: H.projective(0, 1)
0

sage: H.projective(-1, 0)
∞

sage: H.projective(0, 0)
Traceback (most recent call last):
...
ValueError: one of p and q must not be zero

See also

point() to create points in general.

random_element(kind=None)[source]

Return a random convex subset of this hyperbolic plane.

INPUT:

  • kind – one of "empty_set"`, "point"`, "oriented geodesic"`, "unoriented geodesic"`, "half_space"`, "oriented segment", "unoriented segment", "polygon"; the kind of set to produce. If not specified, the kind of set is chosen randomly.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

Make the following randomized tests reproducible:

sage: set_random_seed(0)
sage: H.random_element()
{}

Specific types of random subsets can be requested:

sage: H.random_element("point")
-1/2 + 1/95*I

sage: H.random_element("oriented geodesic")
{-12*(x^2 + y^2) + 1144*x + 1159 = 0}

sage: H.random_element("unoriented geodesic")
{648*(x^2 + y^2) + 1654*x + 85 = 0}

sage: H.random_element("half_space")
{3*(x^2 + y^2) - 5*x - 1 ≥ 0}

sage: H.random_element("oriented segment")
{-3*(x^2 + y^2) + x + 3 = 0} ∩ {9*(x^2 + y^2) - 114*x + 28 ≥ 0} ∩ {(x^2 + y^2) + 12*x - 1 ≥ 0}

sage: H.random_element("unoriented segment")
{16*(x^2 + y^2) - x - 16 = 0} ∩ {(x^2 + y^2) + 64*x - 1 ≥ 0} ∩ {496*(x^2 + y^2) - 1056*x + 529 ≥ 0}

sage: H.random_element("polygon")
{56766100*(x^2 + y^2) - 244977117*x + 57459343 ≥ 0} ∩ {822002048*(x^2 + y^2) - 3988505279*x + 2596487836 ≥ 0} ∩ {464*(x^2 + y^2) + 9760*x + 11359 ≥ 0} ∩ {4*(x^2 + y^2) + 45*x + 49 ≥ 0}

See also

some_elements() for a curated list of representative subsets.

real(r)[source]

Return the ideal point r on the real axis in the upper half plane model.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()
sage: p = H.real(-2)
sage: p
-2

sage: p == H(-2)
True
sage: p.is_ideal()
True

See also

point() to create points in general.

segment(geodesic, start=None, end=None, oriented=None, check=True, assume_normalized=False)[source]

Return the segment on the geodesic bounded by start and end.

INPUT:

  • geodesic – a geodesic() in this space.

  • startNone or a point() on the geodesic, e.g., obtained from the HyperbolicGeodesic._intersection() of geodesic with another geodesic. If None, the segment starts at the infinite HyperbolicOrientedGeodesic.start() point of the geodesic.

  • endNone or a point() on the geodesic, as for start; must be later on geodesic than start if the geodesic is oriented.

  • oriented – whether to produce an oriented segment or an unoriented segment. The default (None) is to produce an oriented segment iff geodesic is oriented or both start and end are provided so the orientation can be deduced from their order.

  • check – boolean (default: True), whether validation is performed on the arguments.

  • assume_normalized – boolean (default: False), if not set, the returned segment is normalized, i.e., if it is actually a geodesic, a HyperbolicGeodesic is returned, if it is actually a point, a HyperbolicPoint is returned.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

When neither start nor end are given, a geodesic is returned:

sage: H.segment(H.vertical(0), start=None, end=None)
{-x = 0}

When only one endpoint is provided, the segment is infinite on one end:

sage: H.segment(H.vertical(0), start=I, end=None)
{-x = 0} ∩ {(x^2 + y^2) - 1 ≥ 0}

When both endpoints are provided, a proper closed segment is returned:

sage: H.segment(H.vertical(0), start=I, end=2*I)
{-x = 0} ∩ {(x^2 + y^2) - 1 ≥ 0} ∩ {(x^2 + y^2) - 4 ≤ 0}

However, ideal endpoints on the geodesic are ignored:

sage: H.segment(H.vertical(0), start=0, end=oo)
{-x = 0}

A segment can be reduced to a single point:

sage: H.segment(H.vertical(0), start=I, end=I)
I

The endpoints must have coordinates over the base ring:

sage: H.segment(H.half_circle(0, 2), H.half_circle(0, 2).start(), H.half_circle(0, 2).end())
Traceback (most recent call last):
...
ValueError: square root of 32 ...

The produced segment is oriented if the geodesic is oriented:

sage: H.segment(H.vertical(0)).is_oriented()
True

sage: H.segment(H.vertical(0).unoriented()).is_oriented()
False

The segment is oriented if both start and end are provided:

sage: H.segment(H.vertical(0).unoriented(), start=0, end=oo).is_oriented()
True
sage: H.segment(H.vertical(0).unoriented(), start=2*I, end=I).is_oriented()
True
sage: H.segment(H.vertical(0), start=I) != H.segment(H.vertical(0), end=I)
True
some_elements()[source]

Return some representative elements, i.e., points of the hyperbolic plane for testing.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: HyperbolicPlane().some_elements()
[∞, 0, 1, -1, ...]
some_subsets()[source]

Return some subsets of the hyperbolic plane for testing.

Some of the returned sets are elements of the hyperbolic plane (i.e., points) some are parents themselves, e.g., polygons.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: HyperbolicPlane().some_elements()
[∞, 0, 1, -1, ...]
start(geodesic, check=True)[source]

Return the ideal starting point of geodesic.

INPUT:

  • geodesic – an oriented geodesic

  • check – whether to verify that geodesic is valid

Note

This method exists to keep all the methods that actually create hyperbolic sets on the lowest level in the HyperbolicPlane. It is otherwise identical to HyperbolicOrientedGeodesic.start().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: p = H.start(H.vertical(0))
sage: p
0

sage: H.vertical(0).start() == p
True

Points created this way might have coordinates that cannot be represented in the base ring:

sage: p = H.half_circle(0, 2).start()
sage: p.coordinates(model="klein")
Traceback (most recent call last):
...
ValueError: square root of 32 ...

sage: p.coordinates(model="half_plane")
Traceback (most recent call last):
...
ValueError: square root of 32 ...
vertical(real)[source]

Return the vertical geodesic at the real ideal point in the upper half plane model. The geodesic is oriented such that it goes from real to the point at infinity.

Use the - operator to pass to the geodesic with opposite orientation.

Use HyperbolicConvexSet.unoriented() to get the unoriented vertical.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0)
{-x = 0}

sage: H.vertical(1)
{-x + 1 = 0}

sage: H.vertical(-1)
{-x - 1 = 0}

We can also create an unoriented geodesic:

sage: v = H.vertical(0)
sage: v.unoriented() == v
False

See also

half_circle() to get an oriented geodesic that is not a vertical and geodesic() for the general interface, producing a geodesic from an equation.

class flatsurf.geometry.hyperbolic.HyperbolicPoint[source]

A (possibly infinite or even ultra-ideal) point in the HyperbolicPlane.

This is the abstract base class providing shared functionality for HyperbolicPointFromCoordinates and HyperbolicPointFromGeodesic.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

A point with coordinates in the upper half plane:

sage: p = H(0)

The same point, created as an endpoint of a geodesic:

sage: p = H.vertical(0).start()

Another point on the same geodesic, a finite point:

sage: p = H(I)

See also

HyperbolicPlane.point() for ways to create points

__annotations__ = {}
__contains__(point)[source]

Return whether the set comprised of this point contains point, i.e., whether the points are equal.

This implements HyperbolicConvexSet.__contains__() without relying on HyperbolicConvexSet.half_spaces() which can not be computed for points without coordinates in the HyperbolicPlane.base_ring().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: p = H(0)
sage: q = H.half_circle(0, 2).start()

sage: p in p
True

sage: p in q
False

sage: q in p
False

sage: q in q
True
__dict__ = mappingproxy({'__module__': 'flatsurf.geometry.hyperbolic', '__doc__': '\n    A (possibly infinite or even ultra-ideal) point in the\n    :class:`HyperbolicPlane`.\n\n    This is the abstract base class providing shared functionality for\n    :class:`HyperbolicPointFromCoordinates` and\n    :class:`HyperbolicPointFromGeodesic`.\n\n    EXAMPLES::\n\n        sage: from flatsurf import HyperbolicPlane\n        sage: H = HyperbolicPlane()\n\n    A point with coordinates in the upper half plane::\n\n        sage: p = H(0)\n\n    The same point, created as an endpoint of a geodesic::\n\n        sage: p = H.vertical(0).start()\n\n    Another point on the same geodesic, a finite point::\n\n        sage: p = H(I)\n\n    TESTS::\n\n        sage: from flatsurf.geometry.hyperbolic import HyperbolicPoint\n        sage: isinstance(p, HyperbolicPoint)\n        True\n\n        sage: TestSuite(p).run()\n\n    .. SEEALSO::\n\n        :meth:`HyperbolicPlane.point` for ways to create points\n\n    ', '_check': <function HyperbolicPoint._check>, 'is_ideal': <function HyperbolicPoint.is_ideal>, 'is_ultra_ideal': <function HyperbolicPoint.is_ultra_ideal>, 'is_finite': <function HyperbolicPoint.is_finite>, 'half_spaces': <function HyperbolicPoint.half_spaces>, 'coordinates': <function HyperbolicPoint.coordinates>, 'real': <function HyperbolicPoint.real>, 'imag': <function HyperbolicPoint.imag>, 'segment': <function HyperbolicPoint.segment>, 'plot': <function HyperbolicPoint.plot>, 'dimension': <function HyperbolicPoint.dimension>, 'vertices': <function HyperbolicPoint.vertices>, '__hash__': <function HyperbolicPoint.__hash__>, '_isometry_equations': <function HyperbolicPoint._isometry_equations>, '__contains__': <function HyperbolicPoint.__contains__>, '__dict__': <attribute '__dict__' of 'HyperbolicPoint' objects>, '__weakref__': <attribute '__weakref__' of 'HyperbolicPoint' objects>, '__annotations__': {}})
__hash__()[source]

Return a hash value for this point.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()

Since points are hashable, they can be put in a hash table, such as a Python set:

sage: S = {H(I), H(0)}
sage: len(S)
2
sage: {H.half_circle(0, 1).start(), H.half_circle(-2, 1).end()}
{-1}

Also, endpoints of geodesics that have no coordinates in the base ring can be hashed, see HyperbolicPointFromGeodesic.__hash__():

sage: S = {H.half_circle(0, 2).start()}
__module__ = 'flatsurf.geometry.hyperbolic'
__weakref__

list of weak references to the object

coordinates(model='half_plane', ring=None)[source]

Return coordinates of this point in ring.

If model is "half_plane", return coordinates in the upper half plane model.

If model is "klein", return Euclidean coordinates in the Klein model.

INPUT:

  • model – either "half_plane" or "klein" (default: "half_plane")

  • ring – a ring, "maybe", or None (default: None); in which ring the coordinates should be returned. If None, coordinates are returned in the HyperbolicPlane.base_ring(). If "maybe", same as None but instead of throwing an exception if the coordinates do not exist in the base ring, None is returned.

Note

It would be good to add a "extension" mode here to automatically take a ring extension where the coordinates live.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H(I).coordinates()
(0, 1)

sage: H(0).coordinates()
(0, 0)

The point at infinity has no coordinates in the upper half plane model:

sage: H(oo).coordinates()
Traceback (most recent call last):
...
ValueError: point has no coordinates in the upper half plane

Some points have coordinates in the Klein model but not in the upper half plane model:

sage: p = H.point(1/2, 0, model="klein")

sage: p.coordinates(model="klein")
(1/2, 0)

sage: p.coordinates()
Traceback (most recent call last):
...
ValueError: square root of 3/4 not in Rational Field

sage: K.<a> = NumberField(x^2 - 3/4)
sage: p.coordinates(ring=K)
(1/2, a)

Some points have no coordinates in either model unless we pass to a field extension:

sage: p = H.half_circle(0, 2).start()

sage: p.coordinates(model="half_plane")
Traceback (most recent call last):
...
ValueError: ...

sage: p.coordinates(model="klein")
Traceback (most recent call last):
...
ValueError: ...

sage: K.<a> = QuadraticField(2)
sage: p.coordinates(ring=K)
(-a, 0)

sage: p.coordinates(ring=K, model="klein")
(-2/3*a, 1/3)
dimension()[source]

Return the dimension of this point, i.e., 0.

This implements HyperbolicConvexSet.dimension().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H(0).dimension()
0
half_spaces()[source]

Return a set of half spaces whose intersection is this point.

Implements HyperbolicConvexSet.half_spaces().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H(I).half_spaces()
{{(x^2 + y^2) + 2*x - 1 ≤ 0}, {x ≥ 0}, {(x^2 + y^2) - 1 ≥ 0}}

sage: H(I + 1).half_spaces()
{{x - 1 ≤ 0}, {(x^2 + y^2) - 3*x + 1 ≤ 0}, {(x^2 + y^2) - 2 ≥ 0}}

sage: H.infinity().half_spaces()
{{x ≤ 0}, {x - 1 ≥ 0}}

sage: H(0).half_spaces()
{{(x^2 + y^2) + x ≤ 0}, {x ≥ 0}}

sage: H(-1).half_spaces()
{{x + 1 ≤ 0}, {(x^2 + y^2) - 1 ≤ 0}}

sage: H(1).half_spaces()
{{(x^2 + y^2) - x ≤ 0}, {(x^2 + y^2) - 1 ≥ 0}}

sage: H(2).half_spaces()
{{2*(x^2 + y^2) - 3*x - 2 ≥ 0}, {3*(x^2 + y^2) - 7*x + 2 ≤ 0}}

sage: H(-2).half_spaces()
{{(x^2 + y^2) - x - 6 ≥ 0}, {2*(x^2 + y^2) + 3*x - 2 ≤ 0}}

sage: H(1/2).half_spaces()
{{6*(x^2 + y^2) - x - 1 ≤ 0}, {2*(x^2 + y^2) + 3*x - 2 ≥ 0}}

sage: H(-1/2).half_spaces()
{{2*(x^2 + y^2) + 7*x + 3 ≤ 0}, {2*(x^2 + y^2) - 3*x - 2 ≤ 0}}

For ideal endpoints of geodesics that do not have coordinates over the base ring, we cannot produce defining half spaces since these would require equations over a quadratic extension as well:

sage: H.half_circle(0, 2).start().half_spaces()
Traceback (most recent call last):
...
ValueError: square root of 32 ...
sage: H = HyperbolicPlane(RR)

sage: H(I).half_spaces()
{{(x^2 + y^2) + 2.00000000000000*x - 1.00000000000000 ≤ 0},
 {x ≥ 0},
 {(x^2 + y^2) - 1.00000000000000 ≥ 0}}

sage: H(0).half_spaces()
{{(x^2 + y^2) + x ≤ 0}, {x ≥ 0}}

See also

HyperbolicPlane.intersection() and HyperbolicPlane.polygon() to intersect half spaces

imag()[source]

Return the imaginary part of this point in the upper half plane model.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()

sage: p = H(I + 2)
sage: p.imag()
1

See also

real()

is_finite()[source]

Return whether this point is finite.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H(0).is_finite()
False

sage: H(I).is_finite()
True

sage: H.half_circle(0, 2).start().is_finite()
False

Note

Currently, the implementation is not robust over inexact rings.

is_ideal()[source]

Return whether this point is at infinity.

This implements HyperbolicConvexSet.is_ideal().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicGeodesic
sage: H = HyperbolicPlane()

sage: H(0).is_ideal()
True

sage: H(I).is_ideal()
False

Note

This check is not very robust over inexact rings and should be improved.

is_ultra_ideal()[source]

Return whether this point is ultra-ideal, i.e., outside of the Klein disk.

This implements :meth;`HyperbolicConvexSet.is_ultra_ideal`.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicGeodesic
sage: H = HyperbolicPlane()

sage: H(0).is_ultra_ideal()
False

sage: H(I).is_ultra_ideal()
False

sage: H.point(2, 0, model="klein", check=False).is_ultra_ideal()
True

Note

This check is not very robust over inexact rings and should be improved.

plot(model='half_plane', **kwds)[source]

Return a plot of this subset.

See HyperbolicConvexPolygon.plot() for the supported keyword arguments.

INPUT:

  • model – one of "half_plane" and "klein" (default: "half_plane")

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H(I).plot()
...Graphics object consisting of 1 graphics primitive
../_images/hyperbolic_4_0.png
real()[source]

Return the real part of this point in the upper half plane model.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: p = H(I + 2)
sage: p.real()
2

See also

imag()

segment(end)[source]

Return the oriented segment from this point to end.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H(0).segment(I)
{-x = 0} ∩ {(x^2 + y^2) - 1 ≤ 0}

A geodesic is returned when both endpoints are ideal points:

sage: H(0).segment(1) == H.geodesic(0, 1)
True

See also

HyperbolicPlane.segment() for other ways to construct segments

vertices(marked_vertices=True)[source]

Return the vertices of this point, i.e., this point.

INPUT:

  • marked_vertices – a boolean (default: True), ignored since a point cannot have marked vertices.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H(oo).vertices()
{∞,}

Vertices can be computed even if they do not have coordinates over the HyperbolicPlane.base_ring():

sage: H.half_circle(0, 2).start().vertices()
{-1.41421356237310,}

See also

HyperbolicConvexSet.vertices() for more details.

class flatsurf.geometry.hyperbolic.HyperbolicPointFromCoordinates(parent, x, y)[source]

A HyperbolicPoint with explicit coordinates in the Klein model.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicPointFromCoordinates
sage: H = HyperbolicPlane()

sage: H.point(0, 0, model="klein")
I

Points with coordinates in the half plane model are also stored as points in the Klein model:

sage: p = H.point(0, 0, model="half_plane")

sage: isinstance(p, HyperbolicPointFromCoordinates)
True

INPUT:

See also

Use :meth;`HyperbolicPlane.point` to create points from coordinates

__annotations__ = {}
__init__(parent, x, y)[source]
__module__ = 'flatsurf.geometry.hyperbolic'
change(ring=None, geometry=None, oriented=None)[source]

Return a modified copy of this point.

INPUT:

  • ring – a ring (default: None to keep the current base_ring()); the ring over which the point will be defined.

  • geometry – a HyperbolicGeometry (default: None to keep the current geometry); the geometry that will be used for the point.

  • oriented – a boolean (default: None to keep the current orientedness); must be None or False since points cannot have an orientation.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

We change the base ring over which the point is defined:

sage: p = H(0)
sage: p.change(ring=AA)
0

We cannot make a point oriented:

sage: p.change(oriented=True)
Traceback (most recent call last):
...
NotImplementedError: cannot make a point oriented

sage: p.change(oriented=False) == p
True
classmethod random_set(parent)[source]

Return a random hyperbolic point.

This implements HyperbolicConvexSet.random_set().

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: from flatsurf.geometry.hyperbolic import HyperbolicPointFromCoordinates
sage: x = HyperbolicPointFromCoordinates.random_set(H)

sage: x.dimension()
0
class flatsurf.geometry.hyperbolic.HyperbolicPointFromGeodesic(parent, geodesic)[source]

An ideal HyperbolicPoint, the end point of a geodesic.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).start()
0

This class is necessary because not all points have coordinates in the HyperbolicPlane.base_ring():

sage: p = H.half_circle(0, 2).start()

sage: p.coordinates(model="klein")
Traceback (most recent call last):
...
ValueError: ...

sage: p.coordinates(model="half_plane")
Traceback (most recent call last):
...
ValueError: ...

INPUT:

See also

Use HyperbolicOrientedGeodesic.start() and HyperbolicOrientedGeodesic.end() to create endpoints of geodesics

__annotations__ = {}
__hash__()[source]

Return a hash value for this point.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()

Points that are given as the endpoints of a geodesic may or may not have coordinates over the base ring:

sage: H.half_circle(0, 1).start().coordinates(model="klein")
(-1, 0)
sage: H.half_circle(0, 2).start().coordinates(model="klein")
Traceback (most recent call last):
...
ValueError: square root of 32 not in Rational Field

While they always have coordinates in a quadratic extension, the hash of the coordinates in the extension might not be consistent with has values in the base ring, so we cannot simply hash the coordinates over some field extension:

sage: hash(QQ(1/2)) == hash(AA(1/2))
False

To obtain consistent hash values for sets over the same base ring, at least if that base ring is a field, we observe that a point whose coordinates are not in the base ring cannot be the starting point of two different geodesics with an equation in the base ring. Indeed, for it otherwise had coordinates in the base ring as it were the intersection of these two geodesics and whence a solution to a linear equation with coefficients in the base ring. So, for points that have no coordinates in the base ring, we can hash the equation of the oriented geodesic to obtain a hash value:

sage: hash(H.half_circle(0, 2).start()) != hash(H.half_circle(0, 2).end())
True
__init__(parent, geodesic)[source]
__module__ = 'flatsurf.geometry.hyperbolic'
change(ring=None, geometry=None, oriented=None)[source]

Return a modified copy of this point.

INPUT:

  • ring – a ring (default: None to keep the current base_ring()); the ring over which the point will be defined.

  • geometry – a HyperbolicGeometry (default: None to keep the current geometry); the geometry that will be used for the point.

  • oriented – a boolean (default: None to keep the current orientedness); must be None or False since points cannot have an orientation.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

We change the base ring over which the point is defined:

sage: p = H.half_circle(0, 2).start()
sage: p.change(ring=AA)
-1.414213562373095?

We cannot make a point oriented:

sage: p.change(oriented=True)
Traceback (most recent call last):
...
NotImplementedError: cannot change orientation of a point

sage: p.change(oriented=False) == p
True
is_finite()[source]

Return whether this is a finite point, i.e., return False since this end point of a geodesic is infinite.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.half_circle(0, 2).start().is_finite()
False
is_ideal()[source]

Return whether this is an infinite point, i.e., return True since this is an endpoint of a geodesic.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).start().is_ideal()
True
is_ultra_ideal()[source]

Return whether this is an ultra ideal point, i.e., return False since this end point of a geodesic is not outside of the unit disk in the Klein model.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).start().is_ultra_ideal()
False
class flatsurf.geometry.hyperbolic.HyperbolicSegment(parent, geodesic, start=None, end=None)[source]

A segment (possibly infinite) in the hyperbolic plane.

This is an abstract base class of HyperbolicOrientedSegment and HyperbolicUnorientedSegment.

INPUT:

  • parent – the HyperbolicPlane containing this segment

  • geodesic – the HyperbolicGeodesic of which this segment is a subset

  • start – a HyperbolicPoint or None (default: None); the finite endpoint of the segment. If None, then the segment extends all the way to the ideal starting point of the geodesic.

  • end – a HyperbolicPoint or None (default: None); the finite endpoint of the segment. If None, then the segment extends all the way to the ideal end point of the geodesic.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.segment(H.vertical(0), start=I)
{-x = 0} ∩ {(x^2 + y^2) - 1 ≥ 0}

sage: H(I).segment(oo)
{-x = 0} ∩ {(x^2 + y^2) - 1 ≥ 0}

See also

Use HyperbolicPlane.segment() or HyperbolicPoint.segment() to create segments.

__annotations__ = {}
__eq__(other)[source]

Return whether this segment is indistinguishable from other (except for scaling in the defining geodesic’s equation).

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicSegment
sage: H = HyperbolicPlane()

Oriented segments are equal if they have the same start and end points:

sage: H(I).segment(2*I) == H(2*I).segment(I)
False

For an unoriented segment the endpoints must be the same but order does not matter:

sage: H(I).segment(2*I).unoriented() == H(2*I).segment(I).unoriented()
True
__hash__ = None
__init__(parent, geodesic, start=None, end=None)[source]
__module__ = 'flatsurf.geometry.hyperbolic'
_normalize()[source]

Return this set possibly rewritten in a simpler form.

This implements HyperbolicConvexSet._normalize().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

We define a helper method for easier testing:

sage: segment = lambda *args, **kwds: H.segment(*args, **kwds, check=False, assume_normalized=True)

A segment that consists of an ideal point, is just that point:

sage: segment(H.vertical(-1), start=H.infinity(), end=H.infinity())._normalize()
∞

sage: segment(H.vertical(0), start=H.infinity(), end=None)._normalize()
∞

sage: segment(-H.vertical(0), start=None, end=H.infinity())._normalize()
∞

A segment that has two ideal end points is a geodesic:

sage: segment(H.vertical(0), start=None, end=H.infinity())._normalize()
{-x = 0}

sage: segment(-H.vertical(0), start=H.infinity(), end=None)._normalize()
{x = 0}

Segments that remain segments in normalization:

sage: segment(H.vertical(0), start=I, end=H.infinity())._normalize()
{-x = 0} ∩ {(x^2 + y^2) - 1 ≥ 0}

sage: segment(-H.vertical(0), start=H.infinity(), end=I)._normalize()
{x = 0} ∩ {(x^2 + y^2) - 1 ≥ 0}

Note

This method is not numerically robust and should be improve over inexact rings.

change(ring=None, geometry=None, oriented=None)[source]

Return a modified copy of this segment.

  • ring – a ring (default: None to keep the current base_ring()); the ring over which the new half space will be defined.

  • geometry – a HyperbolicGeometry (default: None to keep the current geometry); the geometry that will be used for the new half space.

  • oriented – a boolean (default: None to keep the current orientedness); must be None or False since half spaces cannot have an explicit orientation. See is_oriented().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

We change the ring over which the segment is defined:

sage: s = H(I).segment(2*I)

sage: s.change(ring=AA)
{-x = 0} ∩ {3/5*(x^2 + y^2) - 3/5 ≥ 0} ∩ {6/25*(x^2 + y^2) - 24/25 ≤ 0}

We make the segment unoriented:

sage: s.change(oriented=False).is_oriented()
False

We pick a (somewhat) random orientation of an unoriented segment:

sage: s.unoriented().change(oriented=True).is_oriented()
True
dimension()[source]

Return the dimension of this segment, i.e., 1.

This implements HyperbolicConvexSet.dimension().

Note that this also returns 1 if the actual dimension of the segment is smaller. This is, however, only possible for segments created with HyperbolicPlane.segment() setting check=False.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H(I).segment(2*I).dimension()
1
geodesic()[source]

Return the geodesic on which this segment lies.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: s = H(I).segment(2*I)
sage: s.geodesic()
{-x = 0}

Since the segment is oriented, the geodesic is also oriented:

sage: s.is_oriented()
True

sage: s.geodesic().is_oriented()
True

sage: s.unoriented().geodesic().is_oriented()
False

See also

geodesics also implement this method so that segments and geodesics can be treated uniformly, see HyperbolicGeodesic.geodesic()

half_spaces()[source]

Return a minimal set of half spaces whose intersection is this segment.

This implements HyperbolicConvexSet.half_spaces().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicSegment
sage: H = HyperbolicPlane()

sage: segment = H.segment(H.half_circle(0, 1), end=I)
sage: segment.half_spaces()
{{x ≤ 0}, {(x^2 + y^2) - 1 ≤ 0}, {(x^2 + y^2) - 1 ≥ 0}}
midpoint()[source]

Return the midpoint of this segment.

ALGORITHM:

We use the construction as explained on \(Wikipedia <https://en.wikipedia.org/wiki/Beltrami%E2%80%93Klein_model#Compass_and_straightedge_constructions>\).

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicSegment
sage: H = HyperbolicPlane()
sage: s = H(I).segment(4*I)
sage: s.midpoint()
2*I
sage: K.<a> = NumberField(x^2 - 2, embedding=1.414)
sage: H = HyperbolicPlane(K)
sage: s = H(I - 1).segment(I + 1)
sage: s.midpoint()
a*I

See also

HyperbolicSegment.perpendicular() for the perpendicular bisector

perpendicular(point=None)[source]

Return the geodesic through point that is perpendicular to this segment.

If no point is given, return the perpendicular bisector of this segment.

ALGORITHM:

See HyperbolicGeodesic.perpendicular().

INPUT:

  • point – a point on this segment or None (the default)

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicSegment
sage: H = HyperbolicPlane()
sage: s = H(I).segment(4*I)
sage: s.perpendicular()
{(x^2 + y^2) - 4 = 0}
sage: s.perpendicular(I)
{(x^2 + y^2) - 1 = 0}
sage: K.<a> = NumberField(x^2 - 2, embedding=1.414)
sage: H = HyperbolicPlane(K)
sage: s = H(I - 1).segment(I + 1)
sage: s.perpendicular()
{x = 0}
sage: s.perpendicular(I - 1)
{4/3*(x^2 + y^2) + 16/3*x + 8/3 = 0}
plot(model='half_plane', **kwds)[source]

Return a plot of this segment.

INPUT:

  • model – one of "half_plane" or "klein" (default: "half_plane"); in which model to produce the plot

See flatsurf.graphical.hyperbolic.hyperbolic_path() for additional keyword arguments to customize the plot.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicSegment
sage: H = HyperbolicPlane()

sage: segment = H.segment(H.half_circle(0, 1), end=I)
sage: segment.plot()  # long time (.25s)
...Graphics object consisting of 1 graphics primitive
../_images/hyperbolic_5_0.png
vertices(marked_vertices=True)[source]

Return the end poinst of this segment.

INPUT:

  • marked_vertices – a boolean (default: True), ignored since a segment cannot have marked vertices.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: s = H(I).segment(2*I)
sage: s.vertices()
{I, 2*I}

Note that iteration in the set is not consistent with the orientation of the segment (it is chosen such that the subset relation on vertices can be checked quickly):

sage: (-s).vertices()
{I, 2*I}

Use start() and end() to get the vertices in an order that is consistent with the orientation:

sage: s.start(), s.end()
(I, 2*I)
sage: (-s).start(), (-s).end()
(2*I, I)

Both finite and ideal end points of the segment are returned:

sage: s = H(-1).segment(I)
sage: s.vertices()
{-1, I}

See also

HyperbolicConvexSet.vertices() for more details.

class flatsurf.geometry.hyperbolic.HyperbolicUnorientedGeodesic(parent, a, b, c)[source]

An unoriented geodesic in the hyperbolic plane.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicUnorientedGeodesic
sage: H = HyperbolicPlane()

sage: H.vertical(0).unoriented()
{x = 0}

See also

HyperbolicPlane.geodesic() to create geodesics from points or equations

__annotations__ = {}
__module__ = 'flatsurf.geometry.hyperbolic'
_isometry_conditions(other)[source]

Return an iterable of primitive pairs that must map to each other in an isometry that maps this set to other.

Helper method for HyperbolicPlane._isometry_conditions().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: v = H.vertical(0).unoriented()
sage: w = H.vertical(1).unoriented()

sage: conditions = v._isometry_conditions(w)
sage: list(conditions)
[[({-x = 0}, {-x + 1 = 0})], [({-x = 0}, {x - 1 = 0})]]

See also

HyperbolicConvexSet._isometry_conditions() for a general description.

r

classmethod random_set(parent)[source]

Return a random unoriented geodesic.

This implements HyperbolicConvexSet.random_set().

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: from flatsurf.geometry.hyperbolic import HyperbolicUnorientedGeodesic
sage: x = HyperbolicUnorientedGeodesic.random_set(H)

sage: x.dimension()
1
class flatsurf.geometry.hyperbolic.HyperbolicUnorientedSegment(parent, geodesic, start=None, end=None)[source]

An unoriented (possibly infinity) segment in the hyperbolic plane.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: segment = H.segment(H.vertical(0), start=I).unoriented()

See also

Use HyperbolicPlane.segment() or unoriented() to construct unoriented segments.

__annotations__ = {}
__hash__()[source]

Return a hash value for this set.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane

sage: H = HyperbolicPlane()
sage: s = H(I).segment(2*I)

Since an oriented segment is hashable, it can be put in a hash table, such as a Python set:

sage: {s.unoriented(), (-s).unoriented()}
{{-x = 0} ∩ {(x^2 + y^2) - 1 ≥ 0} ∩ {(x^2 + y^2) - 4 ≤ 0}}
__module__ = 'flatsurf.geometry.hyperbolic'
_isometry_conditions(other)[source]

Return an iterable of primitive pairs that must map to each other in an isometry that maps this set to other.

Helper method for HyperbolicPlane._isometry_conditions().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: s = H(I).segment(2*I).unoriented()

sage: conditions = s._isometry_conditions(s)
sage: list(conditions)
[[({-x = 0} ∩ {(x^2 + y^2) - 1 ≥ 0} ∩ {(x^2 + y^2) - 4 ≤ 0},
   {-x = 0} ∩ {(x^2 + y^2) - 1 ≥ 0} ∩ {(x^2 + y^2) - 4 ≤ 0})],
 [({-x = 0} ∩ {(x^2 + y^2) - 1 ≥ 0} ∩ {(x^2 + y^2) - 4 ≤ 0},
   {x = 0} ∩ {(x^2 + y^2) - 4 ≤ 0} ∩ {(x^2 + y^2) - 1 ≥ 0})]]

See also

HyperbolicConvexSet._isometry_conditions() for a general description.

r

classmethod random_set(parent)[source]

Return a random unoriented segment.

This implements HyperbolicConvexSet.random_set().

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: from flatsurf.geometry.hyperbolic import HyperbolicUnorientedSegment
sage: x = HyperbolicUnorientedSegment.random_set(H)

sage: x.dimension()
1
class flatsurf.geometry.hyperbolic.HyperbolicVertices(vertices, assume_sorted=None)[source]

A set of vertices on the boundary of a convex set in the hyperbolic plane, sorted in counterclockwise order.

INPUT:

  • vertices – an iterable of HyperbolicPoint, the vertices of this set

  • assume_sorted – a boolean or "rotated" (default: True); whether to assume that the vertices are already sorted with respect to _lt_(). If "rotated", we assume that the vertices are sorted modulo a cyclic permutation.

ALGORITHM:

We keep vertices sorted in counterclockwise order relative to a fixed reference vertex (the leftmost and bottommost in the Klein model).

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()
sage: V = H.vertical(0).vertices()
sage: V
{0, ∞}

Note that in this example, 0 is chosen as the reference point:

sage: V._start
0

See also

HyperbolicConvexSet.vertices() to obtain such a set

__abstractmethods__ = frozenset({})
__annotations__ = {}
__init__(vertices, assume_sorted=None)[source]
__module__ = 'flatsurf.geometry.hyperbolic'
_lt_(lhs, rhs)[source]

Return whether lhs should come before rhs in this set.

INPUT:

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicHalfSpaces
sage: H = HyperbolicPlane()

sage: V = H.vertical(0).vertices()

We find that we go counterclockwise from 1 to ∞ when seen from 0 in the Klein model:

sage: V._lt_(H(oo), H(1))
False
_merge(*sets)[source]

Return the merge of sorted lists of sets.

Note that this set itself is not part of the merge (but its reference point is used).

INPUT:

  • sets – iterables that are sorted with respect to _lt_().

Warning

For this to work correctly, the result of the merge must eventually have the reference point of this set as its reference point.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicHalfSpaces
sage: H = HyperbolicPlane()

sage: V = H.vertical(0).vertices()

sage: V._merge([H(1)], [H(0)], [H(oo)])
[0, 1, ∞]
class flatsurf.geometry.hyperbolic.OrderedSet(entries, assume_sorted=None)[source]

A set of objects sorted by OrderedSet._lt_().

This is used to efficiently represent HyperbolicConvexSet.half_spaces(), HyperbolicConvexSet.vertices(), and HyperbolicConvexSet.edges(). In particular, it allows us to create and merge such sets in linear time.

This is an abstract base class for specialized sets such as HyperbolicHalfSpaces, HyperbolicVertices, and HyperbolicEdges.

INPUT:

  • entries – an iterable, the elements of this set

  • assume_sorted – a boolean or "rotated" (default: True); whether to assume that the entries are already sorted with respect to _lt_(). If "rotated", we assume that the entries are sorted modulo a cyclic permutation.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: segment = H(I).segment(2*I)
sage: segment.vertices()
{I, 2*I}
__abstractmethods__ = frozenset({})
__add__(other)[source]

Return the _merge() of this set and other.

INPUT:

  • other – another set of the same kind

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).half_spaces() + H.vertical(1).half_spaces()
{{x ≤ 0}, {x - 1 ≤ 0}, {x ≥ 0}, {x - 1 ≥ 0}}
__annotations__ = {}
__contains__(x)[source]

Return whether this set contains x.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: vertices = H.half_circle(0, 1).vertices()

sage: H(0) in vertices
False

sage: H(1) in vertices
True

Note

Presently, this method is not used. It only exists to satisfy the conditions of the Python abc for sets. It could be implemented more efficiently.

__dict__ = mappingproxy({'__module__': 'flatsurf.geometry.hyperbolic', '__doc__': '\n    A set of objects sorted by :meth:`OrderedSet._lt_`.\n\n    This is used to efficiently represent\n    :meth:`HyperbolicConvexSet.half_spaces`,\n    :meth:`HyperbolicConvexSet.vertices`, and\n    :meth:`HyperbolicConvexSet.edges`. In particular, it allows us to create\n    and merge such sets in linear time.\n\n    This is an abstract base class for specialized sets such as\n    :class:`HyperbolicHalfSpaces`, :class:`HyperbolicVertices`, and\n    :class:`HyperbolicEdges`.\n\n    INPUT:\n\n    - ``entries`` -- an iterable, the elements of this set\n\n    - ``assume_sorted`` -- a boolean or ``"rotated"`` (default: ``True``); whether to assume\n      that the ``entries`` are already sorted with respect to :meth:`_lt_`. If\n      ``"rotated"``, we assume that the entries are sorted modulo a cyclic permutation.\n\n    EXAMPLES::\n\n        sage: from flatsurf import HyperbolicPlane\n        sage: H = HyperbolicPlane()\n\n        sage: segment = H(I).segment(2*I)\n        sage: segment.vertices()\n        {I, 2*I}\n\n    ', '__init__': <function OrderedSet.__init__>, '_lt_': <function OrderedSet._lt_>, '_merge': <function OrderedSet._merge>, '__eq__': <function OrderedSet.__eq__>, '__ne__': <function OrderedSet.__ne__>, '__hash__': <function OrderedSet.__hash__>, '__add__': <function OrderedSet.__add__>, '__repr__': <function OrderedSet.__repr__>, '__iter__': <function OrderedSet.__iter__>, '__len__': <function OrderedSet.__len__>, 'pairs': <function OrderedSet.pairs>, 'triples': <function OrderedSet.triples>, '__getitem__': <function OrderedSet.__getitem__>, '__contains__': <function OrderedSet.__contains__>, '__dict__': <attribute '__dict__' of 'OrderedSet' objects>, '__weakref__': <attribute '__weakref__' of 'OrderedSet' objects>, '__abstractmethods__': frozenset(), '_abc_impl': <_abc._abc_data object>, '__annotations__': {}})
__eq__(other)[source]

Return whether this set is equal to other.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).vertices() == (-H.vertical(0)).vertices()
True
__getitem__(*args, **kwargs)[source]

Return items from this set by index.

INPUT:

Any arguments that can be used to access a tuple are accepted.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: vertices = H.half_circle(0, 1).vertices()
sage: vertices[0]
-1
__hash__()[source]

Return a hash value for this set that is consistent with __eq__() and __ne__().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: hash(H.vertical(0).vertices()) != hash(H.vertical(1).vertices())
True
__init__(entries, assume_sorted=None)[source]
__iter__()[source]

Return an iterator of this set.

Iteration happens in sorted order, consistent with _lt_().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: vertices = H.half_circle(0, 1).vertices()
sage: list(vertices)
[-1, 1]
__len__()[source]

Return the cardinality of this set.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: vertices = H.half_circle(0, 1).vertices()
sage: len(vertices)
2
__module__ = 'flatsurf.geometry.hyperbolic'
__ne__(other)[source]

Return whether this set is not equal to other.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.vertical(0).vertices() != H.vertical(1).vertices()
True
__repr__()[source]

Return a printable representation of this set.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: H.half_circle(0, 1).vertices()
{-1, 1}
__weakref__

list of weak references to the object

_lt_(lhs, rhs)[source]

Return whether lhs should come before rhs in this set.

Subclasses must implement this method.

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import OrderedSet
sage: H = HyperbolicPlane()

sage: vertices = H(I).segment(2*I).vertices()

sage: vertices._lt_(vertices[0], vertices[1])
True
_merge(*sets)[source]

Return the merge of sorted lists of sets using merge sort.

Note that this set itself is not part of the merge.

Naturally, when there are a lot of small sets, such a merge sort takes quasi-linear time. However, when there are only a few sets, this runs in linear time.

INPUT:

  • sets – iterables that are sorted with respect to _lt_().

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: from flatsurf.geometry.hyperbolic import HyperbolicHalfSpaces
sage: H = HyperbolicPlane()

sage: HyperbolicHalfSpaces([])._merge()
[]

sage: HyperbolicHalfSpaces([])._merge([H.vertical(0).left_half_space()], [H.vertical(0).left_half_space()])
[{x ≤ 0}]

sage: HyperbolicHalfSpaces([])._merge(*[[half_space] for half_space in H.real(0).half_spaces()])
[{(x^2 + y^2) + x ≤ 0}, {x ≥ 0}]

sage: HyperbolicHalfSpaces([])._merge(list(H.real(0).half_spaces()), list(H.real(0).half_spaces()))
[{(x^2 + y^2) + x ≤ 0}, {x ≥ 0}]

sage: HyperbolicHalfSpaces([])._merge(*[[half_space] for half_space in list(H.real(0).half_spaces()) * 2])
[{(x^2 + y^2) + x ≤ 0}, {x ≥ 0}]
pairs(repeat=False)[source]

Return an iterable that iterates over all consecutive pairs of elements in this set; including the pair formed by the last element and the first element.

INPUT:

  • repeat – a boolean (default: False); whether to produce pair consisting of the first element twice if there is only one element

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

sage: vertices = H.half_circle(0, 1).vertices()
sage: list(vertices.pairs())
[(1, -1), (-1, 1)]

See also

triples()

triples(repeat=False)[source]

Return an iterable that iterates over all consecutive triples of elements in this set; including the triples formed by wrapping around the end of the set.

INPUT:

  • repeat – a boolean (default: False); whether to produce triples by wrapping around even if there are fewer than three elements

EXAMPLES:

sage: from flatsurf import HyperbolicPlane
sage: H = HyperbolicPlane()

There must be at least three elements to form any triples:

sage: vertices = H.half_circle(0, 1).vertices()
sage: list(vertices.triples())
[]

sage: half_spaces = H(I).segment(2*I).half_spaces()
sage: list(half_spaces.triples())
[({(x^2 + y^2) - 1 ≥ 0}, {x ≤ 0}, {(x^2 + y^2) - 4 ≤ 0}),
 ({x ≤ 0}, {(x^2 + y^2) - 4 ≤ 0}, {x ≥ 0}),
 ({(x^2 + y^2) - 4 ≤ 0}, {x ≥ 0}, {(x^2 + y^2) - 1 ≥ 0}),
 ({x ≥ 0}, {(x^2 + y^2) - 1 ≥ 0}, {x ≤ 0})]

However, we can force triples to be produced by wrapping around with repeat:

sage: vertices = H.half_circle(0, 1).vertices()
sage: list(vertices.triples(repeat=True))
[(1, -1, 1), (-1, 1, -1)]

See also

pairs()