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__': {}})¶
- __module__ = 'flatsurf.geometry.hyperbolic'¶
- 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 ofhalf spaces
.INPUT:
parent
– theHyperbolicPlane
of which this is a subsethalf_spaces
– theHyperbolicHalfSpace
of which this is an intersectionvertices
– 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()
andHyperbolicPlane.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()])}
- __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 thehalf_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 theHyperbolicPlane.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 toHyperbolicHalfSpaces._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/fall07/Lecture4.prn.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 currentbase_ring()
); the ring over which the polygon will be defined.geometry
– aHyperbolicGeometry
(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 beNone
orFalse
since polygons cannot have an explicit orientation. Seeis_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()
settingcheck=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
andgeodesics
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 polygonsedgecolor
– 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:
parent
– theHyperbolicPlane
containing the polygon
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
See also
- 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 outputOUTPUT:
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()
; seeHyperbolicVertices
: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, sayHyperbolicPlane.polygon()
take care of this by calling a setsHyperbolicConvexSet._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, aHyperbolicPointFromGeodesic
, and the point with coordinates (0, 0) in the upper half plane model, aHyperbolicPointFromCoordinates
, 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 aHyperbolicOrientedSegment
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})\), ifmodel
is"half_plane"
, or a 3×3 matrix giving a similitude that preserves a quadratic form of type \((1, 2)\), ifmodel
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 currentbase_ring()
); the ring over which the new set will be defined.geometry
– aHyperbolicGeometry
(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
andgeodesics
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()
andis_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()
andis_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 setHyperbolicHalfSpace.__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()
andis_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
- classmethod random_set(parent)[source]¶
Return a random convex set.
Concrete hyperbolic classes should override this method to provide random sets.
INPUT:
parent
– theHyperbolicPlane
the set should live in
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
See also
- 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 beforerhs
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:
parent
– theHyperbolicPlane
this is the empty set of
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 currentHyperbolicPlane.base_ring()
); the ring over which the empty set will be defined.geometry
– aHyperbolicGeometry
(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 beNone
orFalse
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
- classmethod random_set(parent)[source]¶
Return a random empty set, i.e., the empty set.
This implements
HyperbolicConvexSet.random_set()
.INPUT:
parent
– theHyperbolicPlane
this is the empty set of.
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
See also
- 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 theepsilon
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 thanepsilon
, see_equal()
for details.INPUT:
ring
– a ring, the ring in which coordinates in the hyperbolic plane will be representedepsilon
– 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))
See also
- __annotations__ = {}¶
- __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
andy
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:
p
– an element of thebase_ring()
q
– an element of thebase_ring()
point
– theHyperbolicPlane.point()
to create points
EXAMPLES:
sage: from flatsurf import HyperbolicPlane sage: H = HyperbolicPlane(RR)
The point
[p: q]
is the point at infinity ifq
is very small in comparison top
: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
andy
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 toring
.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 theHyperbolicGeometry
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
andHyperbolicOrientedGeodesic
.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:
parent
– theHyperbolicPlane
this geodesic lives ina
– an element ofHyperbolicPlane.base_ring()
b
– an element ofHyperbolicPlane.base_ring()
c
– an element ofHyperbolicPlane.base_ring()
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
- __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:
other
– anotherHyperbolicGeodesic
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 currentbase_ring()
); the ring over which the new geodesic will be defined.geometry
– aHyperbolicGeometry
(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 defaultNone
is not to normalize at all. Other options aregcd
, 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
See also
- 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 orNone
(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
- 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()
andHyperbolicOrientedGeodesic.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 andHyperbolicEpsilonGeometry
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__': {}})¶
- __module__ = 'flatsurf.geometry.hyperbolic'¶
- __weakref__¶
list of weak references to the object
- _equal(x, y)[source]¶
Return whether
x
andy
should be considered equal in thebase_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:
x
– an element of thebase_ring()
y
– an element of thebase_ring()
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 toring
.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 thebase_ring()
y
– an element of thebase_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 withradius_squared
in the upper half plane.INPUT:
center
– an element of thebase_ring()
, the center of the half circle on the real axisradius_squared
– a positive element of thebase_ring()
, the square of the radius of the half circlegeodesic
– theHyperbolicPlane.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
andg
.INPUT:
f
– a triple of elements(a, b, c)
ofbase_ring()
encoding the line \(a + bx + cy = 0\)g
– a triple of elements(a, b, c)
ofbase_ring()
encoding the line \(a + bx + cy = 0\)
OUTPUT: A pair of elements of
base_ring()
, the coordinates of the point of intersection, orNone
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:
p
– an element of thebase_ring()
q
– an element of thebase_ring()
point
– theHyperbolicPlane.point()
to create points
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:
real
– an element of thebase_ring()
geodesic
– theHyperbolicPlane.geodesic()
to create geodesics
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:
parent
– theHyperbolicPlane
containing this half space.geodesic
– theHyperbolicOrientedGeodesic
to whose left this half space lies.
EXAMPLES:
sage: from flatsurf import HyperbolicPlane sage: H = HyperbolicPlane() sage: H.half_circle(0, 1).left_half_space() {(x^2 + y^2) - 1 ≥ 0}
See also
HyperbolicPlane.half_space()
,HyperbolicOrientedGeodesic.left_half_space()
,HyperbolicOrientedGeodesic.right_half_space()
for the most common ways to create half spaces.- __annotations__ = {}¶
- __contains__(point)[source]¶
Return whether
point
is contained in this half space.INPUT:
point
– aHyperbolicPoint
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
- __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 currentbase_ring()
); the ring over which the new half space will be defined.geometry
– aHyperbolicGeometry
(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 beNone
orFalse
since half spaces cannot have an explicit orientation. Seeis_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:
model
– either"half_plane"
or"klein"
normalization
– how to normalize the coefficients, seeHyperbolicGeodesic.equation()
for details
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:
parent
– theHyperbolicPlane
containing the half plane
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
See also
- 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()
andHyperbolicOrientedGeodesic.end()
on theboundary()
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 thanrhs
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:
lhs
– aHyperbolicHalfSpace
rhs
– aHyperbolicHalfSpace
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:
vertices
– a sequence ofHyperbolicPoint
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}}
See also
- 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
See also
- __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:
parent
– theHyperbolicPlane
this geodesic lives ina
– an element ofHyperbolicPlane.base_ring()
b
– an element ofHyperbolicPlane.base_ring()
c
– an element ofHyperbolicPlane.base_ring()
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 andHyperbolicGeodesic
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, aValueError
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, aValueError
is raised.INPUT:
other
– another geodesic intersecting this geodesiceuclidean
– a boolean (default:False
); ifTrue
, 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 accw
of zero, and geodesics intersecting at an ultra-ideal point, are producing aValueError
.
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 sideHyperbolicPlane.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
– aHyperbolicPoint
on this geodesicmodel
– a string; currently only"euclidean"
is supportedcheck
– a boolean (default:True
); whether to ensure thatpoint
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:
parent
– theHyperbolicPlane
containing the geodesic
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
See also
- 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 sideHyperbolicPlane.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 ofHyperbolicPlane.base_ring()
model
– a string; currently only"euclidean"
is supportedcheck
– 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()
orHyperbolicPoint.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
- classmethod random_set(parent)[source]¶
Return a random oriented segment.
This implements
HyperbolicConvexSet.random_set()
.INPUT:
parent
– theHyperbolicPlane
containing the segment
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
See also
- 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
- 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 byHyperbolicExactGeometry
. If thebase_ring
is exact, this defaults toHyperbolicExactGeometry
over that base ring. If the base ring isRR
orRealField
, this defaults to theHyperbolicEpsilonGeometry
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__': {}})¶
- __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 fromremaining
.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
andfilter
.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()
andHyperbolicConvexSet.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 orNone
; ifNone
, uses the currentbase_ring()
.geometry
– a geometry orNone
; ifNone
, tries to convert the existing geometry toring
.
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, theHyperbolicExactGeometry
changes to theHyperbolicEpsilonGeometry
, seeHyperbolicExactGeometry.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 generallyHyperbolicConvexSet.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 implementationintersection()
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
andb
are given, return the geodesic going through the pointsa
and thenb
.If
c
is specified andmodel
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 andmodel
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 thebase_ring()
b
– a point in the hyperbolic plane or an element of thebase_ring()
c
–None
or an element of thebase_ring()
(default:None
)model
–None
,"half_plane"
, or"klein"
(default:None
); whena
,b
andc
are elements of thebase_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 withradius_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 thebase_ring()
, the center of the half circle on the real axisradius_squared
– a positive element of thebase_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 andgeodesic()
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:
a
– an element of thebase_ring()
b
– an element of thebase_ring()
c
– an element of thebase_ring()
model
– one of"half_plane"`
or"klein"
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 thesubsets
. 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 spacesHyperbolicPlane.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
toimage
.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 mapspreimage
toimage
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 thebase_ring()
, ifmodel
is"klein"
, returns a 3×3 matrix over the base ring. SeeHyperbolicConvexSet.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 inimage
, 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 thebase_ring()
y
– an element of thebase_ring()
model
– one of"half_plane"
or"klein"
check
– whether to validate the inputs (default:True
); set this toFalse
, 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()
andHyperbolicOrientedGeodesic.end()
to generate points that do not have coordinates over the base ring.infinity()
,real()
, andprojective()
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 ofHyperbolicHalfSpace
s of this hyperbolic plane.check
– boolean (default:True
), whether the arguments are validated.assume_sorted
– boolean (default:False
), whether to assume that thehalf_spaces
are already sorted with respect toHyperbolicHalfSpaces._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 thehalf_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 theHyperbolicConvexPolygon.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 settingassume_sorted
.sage: H.polygon(H.infinity().half_spaces(), assume_sorted=True) ∞
See also
intersection()
to intersect arbitrary convex setsconvex_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:
p
– an element of thebase_ring()
.q
– an element of thebase_ring()
.
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:
r
– an element of thebase_ring()
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 bystart
andend
.INPUT:
geodesic
– ageodesic()
in this space.start
–None
or apoint()
on thegeodesic
, e.g., obtained from theHyperbolicGeodesic._intersection()
ofgeodesic
with another geodesic. IfNone
, the segment starts at the infiniteHyperbolicOrientedGeodesic.start()
point of the geodesic.end
–None
or apoint()
on thegeodesic
, as forstart
; must be later ongeodesic
thanstart
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 iffgeodesic
is oriented or bothstart
andend
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, aHyperbolicGeodesic
is returned, if it is actually a point, aHyperbolicPoint
is returned.
EXAMPLES:
sage: from flatsurf import HyperbolicPlane sage: H = HyperbolicPlane()
When neither
start
norend
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
andend
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
See also
- 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 geodesiccheck
– whether to verify thatgeodesic
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 toHyperbolicOrientedGeodesic.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 fromreal
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:
real
– an element of thebase_ring()
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 andgeodesic()
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
andHyperbolicPointFromGeodesic
.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 onHyperbolicConvexSet.half_spaces()
which can not be computed for points without coordinates in theHyperbolicPlane.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"
, orNone
(default:None
); in which ring the coordinates should be returned. IfNone
, coordinates are returned in theHyperbolicPlane.base_ring()
. If"maybe"
, same asNone
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()
andHyperbolicPlane.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
- 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.
See also
- 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.
See also
- 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.
See also
- 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
- 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
- segment(end)[source]¶
Return the oriented segment from this point to
end
.INPUT:
end
– anotherHyperbolicPoint
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:
parent
– theHyperbolicPlane
containing this pointx
– an element ofHyperbolicPlane.base_ring()
y
– an element ofHyperbolicPlane.base_ring()
See also
Use :meth;`HyperbolicPlane.point` to create points from coordinates
- __annotations__ = {}¶
- __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 currentbase_ring()
); the ring over which the point will be defined.geometry
– aHyperbolicGeometry
(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 beNone
orFalse
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:
parent
– theHyperbolicPlane
containing the point
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
See also
- 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:
parent
– theHyperbolicPlane
containing this pointgeodesic
– toHyperbolicOrientedGeodesic
whoseHyperbolicOrientedGeodesic.start()
this point is
See also
Use
HyperbolicOrientedGeodesic.start()
andHyperbolicOrientedGeodesic.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
- __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 currentbase_ring()
); the ring over which the point will be defined.geometry
– aHyperbolicGeometry
(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 beNone
orFalse
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
andHyperbolicUnorientedSegment
.INPUT:
parent
– theHyperbolicPlane
containing this segmentgeodesic
– theHyperbolicGeodesic
of which this segment is a subsetstart
– aHyperbolicPoint
orNone
(default:None
); the finite endpoint of the segment. IfNone
, then the segment extends all the way to the ideal starting point of the geodesic.end
– aHyperbolicPoint
orNone
(default:None
); the finite endpoint of the segment. IfNone
, 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()
orHyperbolicPoint.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¶
- __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 currentbase_ring()
); the ring over which the new half space will be defined.geometry
– aHyperbolicGeometry
(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 beNone
orFalse
since half spaces cannot have an explicit orientation. Seeis_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()
settingcheck=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 orNone
(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
- 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()
andend()
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:
parent
– theHyperbolicPlane
containing the geodesic
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
See also
- 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()
orunoriented()
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:
parent
– theHyperbolicPlane
containing the segment
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
See also
- 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 ofHyperbolicPoint
, the vertices of this setassume_sorted
– a boolean or"rotated"
(default:True
); whether to assume that thevertices
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__ = {}¶
- __module__ = 'flatsurf.geometry.hyperbolic'¶
- _lt_(lhs, rhs)[source]¶
Return whether
lhs
should come beforerhs
in this set.INPUT:
lhs
– aHyperbolicPoint
rhs
– aHyperbolicPoint
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()
, andHyperbolicConvexSet.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
, andHyperbolicEdges
.INPUT:
entries
– an iterable, the elements of this setassume_sorted
– a boolean or"rotated"
(default:True
); whether to assume that theentries
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 andother
.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
- __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 beforerhs
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(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