Source code for ddg.geometry.hyperbolic_models

"""Hyperbolic geometry module.

Contains model classes and functions for conversion between the models.
"""
from functools import singledispatch

import numpy as np

import ddg
from ddg.geometry import euclidean_models, moebius_models
from ddg.geometry._conversion import quadric_to_subspaces
from ddg.geometry._intersection import intersect, join, meet
from ddg.geometry._projections import (
    central_project,
    inverse_stereographic_project,
    lift_sphere_to_quadric,
    stereographic_project,
)
from ddg.geometry._quadrics import Quadric
from ddg.geometry._subspaces import (
    Point,
    Subspace,
    coordinate_hyperplane,
    subspace_from_columns,
)
from ddg.geometry.geometry_model_templates import (
    MetricCayleyKleinGeometry,
    MetricGeometry,
)
from ddg.geometry.spheres import SphereLike

__all__ = [
    "ProjectiveModel",
    "PoincareDiskModel",
    "HalfSpaceModel",
    "HemisphereModel",
    "projective_to_poincare",
    "poincare_to_projective",
    "projective_to_hemisphere",
    "hemisphere_to_projective",
    "hemisphere_to_poincare",
    "poincare_to_hemisphere",
    "hemisphere_to_half_space",
    "half_space_to_hemisphere",
]


[docs]class ProjectiveModel(MetricCayleyKleinGeometry): """Projective model of hyperbolic geometry, aka Klein model. .. rubric:: Model space The model space can be thought of as the inside of the unit ball. More precisely, it is the set :: { [x] : b(x, x) < 0 } where `b` is the standard Lorentz scalar product represented by the matrix `diag([1,...,1, -1])`. .. rubric:: Representation of objects - Hyperbolic subspaces are represented by subspace objects - Spheres, distance curves and horospheres are sphere objects. Note that a hyperbolic subspace is a subspace intersected with the model space, but we use the whole subspace to represent it. Parameters ---------- dimension : int Attributes ---------- dimension : int Notes ----- This class supports comparison with `==`. Two geometries are equal if and only if they have the same type and `dimension`. This class also supports the `in` operator to check whether a point is in the model space. """ @property def absolute(self) -> Quadric: """The absolute quadric with matrix ``diag([1,...,1, -1])``. Returns ------- ddg.geometry.Quadric """ matrix = np.eye(self.dimension + 1) matrix[-1, -1] = -1 return Quadric(matrix)
[docs] @staticmethod def metric_to_cayley_klein_distance(d: float) -> float: """Return Metric distance converted to Cayley-Klein "distance":: K = cosh(d) ** 2 Parameters ---------- d : float Returns ------- K : float """ return np.cosh(d) ** 2
[docs] @staticmethod def cayley_klein_distance_to_metric(K: float) -> float: """Return Metric distance converted to Cayley-Klein "distance":: d = arccosh(sqrt(K)) Parameters ---------- K : float Returns ------- d : float Raises ------ ValueError If K < 1 """ if ddg.nonexact.isclose(K, 1): return 0.0 if K < 1: raise ValueError( "Cayley-Klein distance must be in [1, inf[ to correspond to a " "metric distance in hyperbolic geometry." ) return np.arccosh(np.sqrt(K))
[docs] def moebius(self): """Corresponding projective model of Moebius geometry (Hemisphere model).""" return HemisphereModel(self.dimension)
[docs] def to_moebius(self, object_, embedded=False): """Alias for :py:func:`.projective_to_hemisphere`""" return projective_to_hemisphere(object_, embedded=embedded)
[docs] def from_moebius(self, object_, embedded=False): """Alias for :py:func:`.hemisphere_to_projective`""" return hemisphere_to_projective(object_, embedded=embedded)
[docs] def poincare(self): """Corresponding Poincare disk model of hyperbolic geometry.""" return PoincareDiskModel(self.dimension)
[docs] def to_poincare(self, object_, embedded=False): """Alias for :py:func:`.projective_to_poincare`""" return projective_to_poincare(object_, embedded=embedded)
[docs] def from_poincare(self, object_, embedded=False): """Alias for :py:func:`.poincare_to_projective`""" return poincare_to_projective(object_, embedded=embedded)
[docs] def half_space(self): """Corresponding half space model of hyperbolic geometry.""" return PoincareDiskModel(self.dimension)
[docs] def to_half_space(self, object_, embedded=False): """Alias for :py:func:`.projective_to_half_space`""" return projective_to_half_space(object_, embedded=embedded)
[docs] def from_half_space(self, object_, embedded=False): """Alias for :py:func:`.half_space_to_projective`""" return half_space_to_projective(object_, embedded=embedded)
[docs] def geodesic(self, p1, p2): """Return geodesic connecting two points. Parameters ---------- p1, p2 : Point or array_like of shape (self.dimension + 1,) Returns ------- ddg.geometry.Subspace """ if isinstance(p1, Point): p1 = p1.point if isinstance(p2, Point): p2 = p2.point return Subspace(p1, p2).dehomogenize()
[docs] def perpendicular_subspace(self, subspace, contained_subspace): """Get a subspace that is perpendicular to another subspace. More precisely: The function returns the largest subspace S such that the orthogonal projection of S onto `subspace` is equal to the orthogonal projection of `contained_subspace` onto `subspace`. Common special case: If `subspace` is a hyperplane and `contained_subspace` is a point, this is the line that is orthogonal to the hyperplane and contains a specified point. This is computed as the join of `contained_subspace` and the polar subspace of `subspace`. Parameters ---------- subspace, contained_subspace : ddg.geometry.Subspace Returns ------- S : ddg.geometry.Subspace """ return join(self.absolute.polarize(subspace), contained_subspace)
[docs] def common_perpendicular(self, subspace1, subspace2) -> Subspace: """Subspace that is orthogonal to two given non-intersecting subspaces. Common special case: The line that is perpendicular to two parallel hyperplanes. This is computed as the join of the polarized subspaces. If the two subspaces intersect, this join will lie outside of the unit sphere. Parameters ---------- subspace1, subspace2 : ddg.geometry.Subspace Returns ------- S : ddg.geometry.Subspace """ return join( self.absolute.polarize(subspace1), self.absolute.polarize(subspace2) )
[docs] def d_point_hyperplane(self, point, subspace) -> float: """Perpendicular distance of a point to a hyperplane. Parameters ---------- point : ddg.geometry.Point subspace : ddg.geometry.Subspace Returns ------- float """ ptp = self.perpendicular_subspace(subspace, point) point_on_line = meet(subspace, ptp) return self.d(point, point_on_line)
[docs] def d_hyperplanes(self, subspace1, subspace2) -> float: """Perpendicular distance between two parallel hyperplanes. Parameters ---------- subspace1, subspace2 : ddg.geometry.Subspace Returns ------- float """ cp = self.common_perpendicular(subspace1, subspace2) p1 = meet(cp, subspace1) p2 = meet(cp, subspace2) return self.d(p1, p2)
[docs] def angle(self, subspace1, subspace2) -> float: """Angle between two hyperplanes. Parameters ---------- subspace1, subspace2 : ddg.geometry.Subspace Returns ------- float """ n1 = self.absolute.polarize(subspace1).point n2 = self.absolute.polarize(subspace2).point arg = np.sqrt(abs(self.cayley_klein_distance(n1, n2))) if ddg.nonexact.isclose(arg, 1): return 0.0 return np.arccos(arg)
[docs] def sphere_through_points(self, *points): """Find the unique k-sphere through given points. The sphere can be a MetricCayleyKleinSphere (actual sphere in the hyperbolic metric), a CayleyKleinSphere (distance curve), a GeneralizedCayleyKleinSphere (horosphere) or it degenerate to a subspace. Points on the absolute may also be given, in which case the result will be a distance curve or a horosphere. Generically, n points define an (n-2)-sphere. Parameters ---------- *points : iterable of type `ddg.geometry.Point` The points for which to find the sphere. Returns ------- ddg.geometry.spheres.MetricCayleyKleinSphere, ddg.geometry.spheres.CayleyKleinSphereLike, ddg.geometry.spheres.GeneralizedCayleyKleinSphere or ddg.geometry.Subspace Raises ------ ValueError - If the points are not contained in a sphere. - If a given point is not an element of the according geometry. See also -------- ddg.geometry.euclidean_models.ProjectiveModel.sphere_through_points, ddg.geometry.elliptic_models.ProjectiveModel.sphere_through_points """ hem = HemisphereModel(self.dimension) # Lift the points to the hemisphere model lifted_points = [] for point in points: if point in self.absolute: lifted_points.append(point.embed()) elif point in self: lifted_points.append(projective_to_hemisphere(point)[0]) else: raise ValueError(f"A given point is not an element of {self}.") # Create the subspace to get the plane of the sphere plane = join(*lifted_points) if plane.dimension > self.dimension: # The plane consists of the whole space. # Thus, there is no sphere through the given points raise ValueError("There is no sphere containing the given points.") # Intersect the plane with the (hemi)sphere and project back lifted_sphere = intersect(plane, hem.absolute) return hemisphere_to_projective(lifted_sphere)
[docs] def orthogonal_sphere(self, *spheres): """Find the intersection of all spheres orthogonal to the given spheres. If the output of this function is a non-degenerate sphere (i.e., sphere with positive radius), then this sphere possesses the following property: Any higher-dimensional sphere containing the former sphere is orthogonal to all spheres given in the input. Thus, it is the orthogonal sphere of minimal dimension, if it is unique. For example, m non-intersecting hyperspheres in general position in n-dimensional Euclidean space have a unique orthogonal (m-2)-sphere, which is returned by this function. Subspaces can also be inserted or returned. Returns None if the intersection is empty (including the case when there is no orthogonal spheres). Parameters ---------- *spheres: iterable of type ddg.geometry.spheres.CayleyKleinSphereLike, ddg.geometry.spheres.GeneralizedCayleyKleinSphere, ddg.geometry.spheres.MetricCayleyKleinSphere or ddg.geometry.Subspace Returns ------- ddg.geometry.spheres.MetricCayleyKleinSphere, ddg.geometry.spheres.CayleyKleinSphereLike, ddg.geometry.spheres.GeneralizedCayleyKleinSphere, ddg.geometry.Subspace or None See also -------- ddg.geometry.euclidean_models.ProjectiveModel.orthogonal_sphere, ddg.geometry.elliptic_models.ProjectiveModel.orthogonal_sphere """ hem = HemisphereModel(self.dimension) # Lift spheres to hem model hem_spheres = [] for sph in spheres: lifted_sph = projective_to_hemisphere(sph) if isinstance(lifted_sph, tuple): hem_spheres.append(lifted_sph[0]) else: # This is for correct lift of subspaces hem_spheres.append(lifted_sph) # Find the intersection of their subspaces hem_subspaces = [sph.subspace for sph in hem_spheres] polar_subspace = intersect(*hem_subspaces) if polar_subspace.dimension < 0: # There is no sphere orthogonal to the given spheres return None # Take the sphere in hem model polar to the intersection ort_sphere_subspace = hem.absolute.polarize(polar_subspace) ort_sphere_hem = intersect(hem.absolute, ort_sphere_subspace) if ort_sphere_hem.dimension < 0: # The intersection of the set of orthogonal spheres is empty. return None # Project the sphere back to Cayley-Klein model ort_sphere = hemisphere_to_projective(ort_sphere_hem) return ort_sphere
def __contains__(self, point): return self.inner_product(point, point) < 0 def __str__(self): return f"Projective model of {self.dimension}D hyperbolic geometry"
[docs]class HemisphereModel(moebius_models._ProjectiveSubgeometry): """Hemisphere model of hyperbolic space. Also known as the Möbius model because this is a subgeometry of Möbius geometry. It is also a transformed version of the hyperboloid model. .. rubric:: Model space The model space is the upper open hemisphere of the unit sphere. More precisely:: { [x] in Quadric(diag([1,...,1, -1])) : dehomogenize([x])[-1] > 0 }. .. rubric:: Representation of objects All geometric objects (except for points) are represented by Möbius spheres, i.e. Quadric objects contained in the Möbius quadric. Note that of course the actual objects are quadrics intersected with the upper hemisphere, but we use the whole quadric to represent them. Parameters ---------- dimension : int Attributes ---------- dimension : int Notes ----- This class supports comparison with `==`. Two geometries are equal if and only if they have the same type, and `dimension`. This class also supports the `in` operator to check whether a point is in the model space. """ @property def fixed_point(self): """Point outside the Möbius quadric used for computation of the metric. This is the point with representative [0,...,0, 1, 0]. Returns ------- ddg.geometry.Point """ return self.hyperbolic_point
[docs] def d(self, v, w): """Hyperbolic metric in Möbius model Given by the formula :: <x, y> / (<x, p> <y, p>) = -2 * sinh^2(d([x],[y]) / 2) Where `p` is `self.fixed_point.point`. Parameters ---------- v, w : ddg.geometry.Point or array_like of shape (n,) Returns ------- float """ if isinstance(v, Point): v = v.point if isinstance(w, Point): w = w.point p = self.fixed_point.point b = self.inner_product return 2 * np.arcsinh(np.sqrt(-0.5 * b(v, w) / (b(v, p) * b(w, p))))
def __contains__(self, point): return super().__contains__(point) and point.affine_point[-1] > 0 def __str__(self): return ( f"Hemisphere model of {self.dimension}D hyperbolic geometry\n" + f"in the projective model of {self.dimension}D Moebius geometry" )
[docs]class HalfSpaceModel(MetricGeometry): """Poincaré half-space model. .. rubric:: Model space The model space is the half-space {xn > 0} in R^n, where `n` is the `dimension`. .. rubric:: Representation of objects Objects are represented by their Euclidean counterparts: - Subspaces, including other objects like horospheres that look like subspaces, are represented by subspaces. - Objects that look like spheres are represented by Euclidean spheres. This means that the center of the spheres will not match the actual hyperbolic center. Note that we represent intersections of the upper half-space with an object `obj` by the whole `obj`. For example, geodesics are semicircles, but we represent them by the whole circle. Parameters ---------- dimension : int Attributes ---------- dimension : int Notes ----- This class supports comparison with `==`. Two geometries are equal if and only if they have the same type, and `dimension`. This class also supports the `in` operator to check whether a point is in the model space. """
[docs] def d(self, v, w): """Metric in Poincaré half-space model. Defined by:: arccosh(1 + |v-w|^2 / (2 * v[n] * w[n])), where `n` is the `dimension`. Parameters ---------- v, w : ddg.geometry.Point or array_like of shape(n,) Returns ------- float Raises ------ ValueError If the points are in different half-spaces. Notes ----- Source: Wikipedia """ if not isinstance(v, Point): v = Point(v) if not isinstance(w, Point): w = Point(w) v = v.affine_point w = w.affine_point # If this is not true, we can't apply Arcosh. if np.sign(v[-1]) != np.sign(w[-1]): raise ValueError("Points are in different half-spaces.") arg = 1 + np.dot(v - w, v - w) / (2 * v[-1] * w[-1]) if ddg.nonexact.isclose(arg, 1): return 0.0 return np.arccosh(arg)
@property def absolute(self): """The model embedded in (n+1)-space. Returns the hyperplane {x0 = 0}. The model is the upper half of this hyperplane. This plane is projected to the Möbius quadric to convert between the models. Returns ------- ddg.geometry.Subspace """ return coordinate_hyperplane(self.dimension + 1, zero_component=0) def __contains__(self, point): return point.ambient_dimension == self.dimension and point.affine_point[-1] > 0 def __str__(self): return f"Poincaré half-space model of {self.dimension}D hyperbolic geometry"
[docs]class PoincareDiskModel(MetricGeometry): """Poincaré disk model. .. rubric:: Model space The model space is the open unit ball in R^n. .. rubric:: Representation of objects Geometric objects are represented by their Euclidean equivalents, mainly spheres. Note: - If an object is the intersection of the unit ball with a larger object, we use the whole object to represent it. For example, geodesics are circular arcs, but we represent them by the whole circle. - Because we use Euclidean spheres to represent hyperbolic spheres which are the same viewed as sets, the center of a sphere will not actually match the center with respect to the hyperbolic metric. The hyperbolic center can be obtained by representing the sphere in a different model and converting the center to this model. Parameters ---------- dimension : int Attributes ---------- dimension : int Notes ----- This class supports comparison with `==`. Two geometries are equal if and only if they have the same type, and `dimension`. This class also supports the `in` operator to check whether a point is in the model space. """
[docs] def d(self, v, w): """Metric in the Poincaré disk model. Parameters ---------- v, w : ddg.geometry.Point or array_like of shape (n,) Returns ------- float """ if not isinstance(v, Point): v = Point(v) if not isinstance(w, Point): w = Point(w) o = np.zeros(self.dimension + 1) o[-1] = 1 o = Point(o) euc = euclidean_models.ProjectiveModel(self.dimension) numer = 2 * euc.d(v, w) ** 2 denom = (1 - euc.d(o, v) ** 2) * (1 - euc.d(o, w) ** 2) if ddg.nonexact.isclose(1 + numer / denom, 1): return 0.0 return np.arccosh(1 + numer / denom)
def __contains__(self, point): unit_ball = ProjectiveModel(self.dimension).absolute return unit_ball.inner_product(point, point) < 0 def __str__(self): return f"Poincaré disk model of {self.dimension}D hyperbolic geometry"
[docs]@singledispatch def projective_to_hemisphere(object_): """Projective to hemisphere model conversion.hyperconv This is a dispatcher, see type-specific functions for details. """ raise TypeError( f"Conversion of {type(object_)} objects from projective " f"to Möbius model is not supported." )
@projective_to_hemisphere.register def projective_to_hemisphere_subspace(subspace: Subspace, embedded=False): """Convert Subspace from Projective model to Möbius/hemisphere model. Works by optionally embedding `subspace` into the equatorial plane (the polar plane of [e_n+1]), then projecting to the Möbius quadric via [e_n+1]. The Möbius model is only one hemisphere, so the image is a semicircle, but this will return the whole circle. Parameters ---------- subspace : ddg.geometry.Subspace embedded : bool (default=False) If False, object will be embedded into the equatorial plane before projecting. Returns ------- ddg.geometry.Quadric """ n = subspace.ambient_dimension if not embedded: subspace = subspace.embed() geo = HemisphereModel(n) else: geo = HemisphereModel(n - 1) return central_project(subspace, geo.absolute, geo.fixed_point) @projective_to_hemisphere.register def projective_to_hemisphere_point(point: Point, embedded=False): """Convert Point from projective model to Möbius/hemisphere model. Parameters ---------- point : ddg.geometry.Point embedded : bool (default=False) If False, object will be embedded into the equatorial plane before projecting. Returns ------- Tuple (ddg.geometry.Point, ddg.geometry.Point) These are the central projection images in both hemispheres. The first object in the tuple will always be the point that lies in the half-space {x_n+1 > 0}. """ as_quadric = projective_to_hemisphere_subspace(point, embedded=embedded) p1, p2 = quadric_to_subspaces(as_quadric) if p1.affine_point[-1] > 0: return p1, p2 return p2, p1 @projective_to_hemisphere.register def projective_to_hemisphere_cayley_klein_sphere(sphere: SphereLike, embedded=False): """Convert Cayley-Klein sphere from Projective to Möbius/hemisphere model. Works by embedding `quadric` into the equatorial plane (the polar plane of [e_n+1]) and projecting to the Möbius quadric via [e_n+1]. Parameters ---------- sphere : Cayley-Klein sphere embedded : bool (default=False) If False, object will be embedded into the equatorial plane before projecting. Returns ------- Tuple (ddg.geometry.Quadric, ddg.geometry.Quadric) These are the central projection images in both hemispheres. The first object in the tuple will always be the quadric that lies (more) in the half-space {x_n+1 > 0}. """ n = sphere.ambient_dimension if not embedded: moeb = HemisphereModel(n) sphere = sphere.embed() else: moeb = HemisphereModel(n - 1) lift1, lift2 = lift_sphere_to_quadric(sphere, moeb.absolute, moeb.fixed_point) # Select the image in the upper hemisphere. The quadric is (more) in the # upper half-space if and only if its corresponding pole is in the upper # half-space. polar1 = meet( moeb.absolute.polarize(lift1.subspace), join(lift1.subspace, moeb.fixed_point) ) if polar1.affine_point[-1] > 0: return lift1, lift2 return lift2, lift1
[docs]def hemisphere_to_projective(object_, embedded=False): """Convert from Möbius/hemisphere model to projective model. Works by centrally projecting from the Möbius quadric to the equatorial plane (the polar plane of [e_n+1]) via [e_n+1] and computing coordinates. If a quadric is given which is contained in the Möbius quadric, the result will be converted to a corresponding Caley-Klein sphere. Parameters ---------- object_ : ddg.geometry.Quadric or ddg.geometry.Point embedded : bool (default=False) Whether to return the object in the equatorial plane or in n-dim. ambient space. returns ------- ddg.geometry.Quadric, ddg.geometry.Point, ddg.geometry.Subspace, ddg.geometry.spheres.MetricCayleyKleinSphere, ddg.geometry.spheres.CayleyKleinSphere or ddg.geometry.spheres.GeneralizedCayleyKleinSphere """ n = object_.ambient_dimension H = coordinate_hyperplane(n) hem = HemisphereModel(n - 1) projection_point = hem.fixed_point # Case where we can't central project because the join of projection_point # with the quadric would be a "solid cone". This is the case where the # quadric is a circle orthogonal to the equator, which means it represents # a geodesic. if isinstance(object_, Quadric) and projection_point in object_.subspace: img = meet(object_.subspace, H) elif ( isinstance(object_, Quadric) and object_ <= hem.absolute and projection_point not in object_.subspace ): hyp = ddg.geometry.hyperbolic(n - 1) polar_subspace = hem.absolute.polarize(object_.subspace) projected_subspace = central_project(object_.subspace, H, projection_point) projected_polar_subspace = central_project(polar_subspace, H, projection_point) center = ddg.geometry.intersect(projected_subspace, projected_polar_subspace) center_coords = center.unembed().point point_on_quadric = ddg.geometry.Point( ddg.math.inner_product.light_like_vectors_from_onb( ddg.math.inner_product.orthonormalize( np.asarray(object_.subspace.points).T, object_.inner_product ).T, object_.inner_product, )[0, :] ) point_on_sphere_coords = ( central_project(point_on_quadric, H, projection_point).unembed().point ) inn_prod_center = hyp.absolute.inner_product( center_coords, center_coords ) # The sign of this value tells whether the center point is # inside the absolute quadric or not if ddg.nonexact.isclose(inn_prod_center, 0): generalized_radius = hyp.absolute.inner_product( center_coords, point_on_sphere_coords ) ** 2 / hyp.absolute.inner_product( point_on_sphere_coords, point_on_sphere_coords ) else: cayley_klein_radius = hyp.absolute.inner_product( center_coords, point_on_sphere_coords ) ** 2 / ( hyp.absolute.inner_product(center_coords, center_coords) * hyp.absolute.inner_product( point_on_sphere_coords, point_on_sphere_coords ) ) if ddg.nonexact.isclose(inn_prod_center, 0): img_unembedded = hyp.generalized_cayley_klein_sphere( center_coords, generalized_radius, projected_subspace.unembed() ) elif inn_prod_center < 0: img_unembedded = hyp.sphere( center_coords, hyp.cayley_klein_distance_to_metric(cayley_klein_radius), projected_subspace.unembed(), ) else: img_unembedded = hyp.cayley_klein_sphere( center_coords, cayley_klein_radius, projected_subspace.unembed() ) img = img_unembedded.embed() else: img = central_project(object_, H, projection_point) if not embedded: img = img.unembed() return img
[docs]def projective_to_poincare(object_, embedded=False): """Convert from projective model to Poincaré disk model. Just the composition :: Projective -> Möbius -> Poincaré """ in_proj = projective_to_hemisphere(object_, embedded=embedded) if isinstance(in_proj, tuple): # Get the image in the lower hemisphere in_proj = in_proj[1] return hemisphere_to_poincare(in_proj, embedded=embedded)
[docs]def poincare_to_projective(object_, embedded=False): """Convert from Poincaré disk model to projective model. Just the composition :: Poincaré -> Möbius -> Projective """ return hemisphere_to_projective( poincare_to_hemisphere(object_, embedded=embedded), embedded=embedded )
[docs]def hemisphere_to_poincare(object_, embedded=False): """Hemisphere to Poincaré disk model conversion. Alias for :py:func:`.moebius_models.projective_to_euclidean` """ return moebius_models.projective_to_euclidean(object_, embedded=embedded)
[docs]def poincare_to_hemisphere(object_, embedded=False): """Poincaré disk to hemisphere model conversion. Alias for :py:func:`.moebius_models.euclidean_to_projective` """ return moebius_models.euclidean_to_projective(object_, embedded=embedded)
[docs]def hemisphere_to_half_space(object_, embedded=False): """Convert from Möbius/hemisphere model to Poincaré Half-plane model. This function stereographically projects from the point [1, 0,...,0, 1] on the Möbius quadric to the hyperplane {x1 = 0}, then computes coordinates. Warnings -------- No choice is made whether the upper or lower half-space is used. Make sure your object resides in the correct hemisphere. Parameters ---------- object_ : ddg.geometry.Point or ddg.geometry.Quadric embedded : bool (default=False) Whether to return the object in the equatorial plane or in n-dim. ambient space. Returns ------- ddg.geometry.Point or ddg.geometry.spheres.SphereLike """ n = object_.ambient_dimension projection_point = np.zeros(n + 1) projection_point[[0, -1]] = 1 projection_point = Point(projection_point) plane = subspace_from_columns(np.eye(n + 1)[:, 1:]) img = stereographic_project(object_, plane, projection_point) if isinstance(img, Quadric): euc = euclidean_models.ProjectiveModel(img.ambient_dimension) img = euc.quadric_to_sphere(img) if not embedded: img = img.unembed(plane) return img
[docs]def half_space_to_hemisphere(object_, embedded=False): """Convert from Poincaré-Half plane model to Möbius/hemisphere model. This function optionally embeds into the plane {x1 = 0}, then stereographically projects from that plane to the Möbius quadric via the point [1, 0,...,0, 1]. Warnings -------- No choice is made whether the upper or lower hemisphere is used. Make sure your object resides in the correct half-space. Parameters ---------- object_ : ddg.geometry.Subspace or ddg.geometry.spheres.SphereLike embedded : bool (default=False) If False, embeds into the the plane {x1 = 0} before projecting. Returns ------- ddg.geometry.Subspace or ddg.geometry.Quadric """ if not embedded: n = object_.ambient_dimension else: n = object_.ambient_dimension - 1 projection_point = np.zeros(n + 2) projection_point[[0, -1]] = 1 projection_point = Point(projection_point) Q = HemisphereModel(n).absolute plane = subspace_from_columns(np.eye(n + 2)[:, 1:]) if not embedded: object_ = object_.embed(plane) return inverse_stereographic_project(object_, Q, projection_point)