Source code for ddg.geometry.moebius_models

"""Möbius geometry module.

Contains model classes and functions for conversion between the models.
"""
import numpy as np

import ddg
from ddg.geometry._intersection import meet
from ddg.geometry._projections import (
    inverse_stereographic_project,
    stereographic_project,
)
from ddg.geometry._quadrics import Quadric
from ddg.geometry.geometry_model_templates import CayleyKleinGeometry, MetricGeometry
from ddg.math.symmetric_matrices import symmetric_matrix_from_diagonal

__all__ = [
    "ProjectiveModel",
    "ParaboloidModel",
    "EuclideanModel",
    "euclidean_to_projective",
    "projective_to_euclidean",
    "paraboloid_to_projective_and_back",
]


[docs]class EuclideanModel: """Euclidean model of Möbius geometry. .. rubric:: Model space The model space is Euclidean space together with a single point at infinity, which we represent by ``float('inf')``. .. rubric:: Representation of objects Möbius Spheres in this model are Euclidean spheres or subspaces (spheres with center at infinity). """
class _Template(CayleyKleinGeometry): """ Common superclass for sphere model and paraboloid model, just to avoid duplicate code. Subclasses just need to have an attribute/property `absolute` and it will be used in the computations defined in the template methods of this auxiliary class. """ def angle(self, s1, s2): """Angle between two Möbius spheres. Parameters ---------- s1, s2 : ddg.geometry.Quadric Möbius spheres. Returns ------- float """ s1 = self.pole_of_sphere(s1) s2 = self.pole_of_sphere(s2) arg = np.sqrt(self.cayley_klein_distance(s1, s2)) if ddg.nonexact.isclose(arg, 1): return 0.0 return np.arccos(arg) def sphere_from_pole(self, subspace) -> Quadric: """Get a Möbius sphere from its pole. Returns intersection of `Q` and `Q.polarize(subspace)`, where `Q` is the Möbius quadric. Parameters ---------- subspace : ddg.geometry.Subspace Returns ------- ddg.geometry.Quadric """ Q = self.absolute return meet(Q, Q.polarize(subspace)) def pole_of_sphere(self, sphere): """Return pole corresponding to sphere. Parameters ---------- sphere : ddg.geometry.Quadric Returns ------- ddg.geometry.Subspace """ return self.absolute.polarize(sphere.subspace) def __contains__(self, point): return point in self.absolute
[docs]class ProjectiveModel(_Template): """Projective model of Möbius geometry. .. rubric:: Model space The Möbius quadric is the quadric with matrix ``diag([1,...,1, -1])``, which can be thought of as the unit sphere. .. rubric:: Representation of objects Spheres in this model are represented by quadrics which are intersections of the Möbius quadric with a subspace. Parameters ---------- dimension : int Attributes ---------- dimension : int """ @property def absolute(self): """The absolute quadric with matrix ``diag([1,...,1, -1])``. Returns ------- ddg.geometry.Quadric """ matrix = np.eye(self.dimension + 2) matrix[-1, -1] = -1 return Quadric(matrix)
[docs] def paraboloid(self): """Corresponding paraboloid model of Möbius geometry.""" return ParaboloidModel(self.dimension)
[docs] def from_paraboloid(self, object_): """Alias for :py:func:`.paraboloid_to_projective_and_back`""" return paraboloid_to_projective_and_back(object_, embedded=embedded)
@property def euclidean_point(self): p = np.zeros(self.ambient_dimension + 1) p[[-2, -1]] = 0.5 return ddg.geometry.Point(p) @property def euclidean_subspace(self): return ddg.geometry.coordinate_hyperplane(self.ambient_dimension) @property def elliptic_subspace(self): return self.absolute.polarize(self.hyperbolic_point)
[docs] def to_paraboloid(self, object_): """Alias for :py:func:`.paraboloid_to_projective_and_back`""" return paraboloid_to_projective_and_back(object_, embedded=embedded)
[docs] def euclidean(self): """Corresponding projective model of Euclidean geometry.""" return ddg.geometry.euclidean_models.ProjectiveModel(self.dimension)
[docs] def from_euclidean(self, object_, embedded=False): """Alias for :py:func:`.euclidean_models.projective_to_moebius`""" return ddg.geometry.euclidean_models.projective_to_moebius( object_, embedded=embedded )
[docs] def to_euclidean(self, object_, embedded=False): """Alias for :py:func:`.euclidean_models.moebius_to_projective`""" return ddg.geometry.euclidean_models.moebius_to_projective( object_, embedded=embedded )
@property def hyperbolic_point(self): p = np.zeros(self.ambient_dimension + 1) p[-2] = 1 return ddg.geometry.Point(p) @property def hyperbolic_subspace(self): return self.absolute.polarize(self.hyperbolic_point)
[docs] def hyperbolic(self): """Corresponding projective model of hyperbolic geometry.""" return ddg.geometry.hyperbolic_models.ProjectiveModel(self.dimension)
[docs] def to_hyperbolic(self, object_, embedded=False): """Alias for :py:func:`.hyperbolic_models.hemisphere_to_projective`""" return ddg.geometry.hyperbolic_models.hemisphere_to_projective( object_, embedded=embedded )
[docs] def from_hyperbolic(self, object_, embedded=False): """Alias for :py:func:`.hyperbolic_models.projective_to_hemisphere`""" return ddg.geometry.hyperbolic_models.projective_to_hemisphere( object_, embedded=embedded )
[docs] def hyperbolic_poincare(self): """Corresponding Poincare disk model of hyperbolic geometry.""" return ddg.geometry.hyperbolic_models.PoincareDiskModel(self.dimension)
[docs] def to_hyperbolic_poincare(self, object_, embedded=False): """Alias for :py:func:`.hyperbolic_models.projective_to_poincare`""" return ddg.geometry.hyperbolic_models.projective_to_poincare( object_, embedded=embedded )
[docs] def from_hyperbolic_poincare(self, object_, embedded=False): """Alias for :py:func:`.hyperbolic_models.poincare_to_projective`""" return ddg.geometry.hyperbolic_models.poincare_to_projective( object_, embedded=embedded )
[docs] def hyperbolic_half_space(self): """Corresponding Poincare disk model of hyperbolic geometry.""" return ddg.geometry.hyperbolic_models.PoincareDiskModel(self.dimension)
[docs] def to_hyperbolic_half_space(self, object_, embedded=False): """Alias for :py:func:`.hyperbolic_models.hemisphere_to_half_space`""" return ddg.geometry.hyperbolic_models.hemisphere_to_half_space( object_, embedded=embedded )
[docs] def from_hyperbolic_half_space(self, object_, embedded=False): """Alias for :py:func:`.hyperbolic_models.half_space_to_hemisphere`""" return ddg.geometry.hyperbolic_models.half_space_to_hemisphere( object_, embedded=embedded )
@property def elliptic_point(self): p = np.zeros(self.ambient_dimension + 1) p[-1] = 1 return ddg.geometry.Point(p) @property def elliptic_subspace(self): return self.absolute.polarize(self.elliptic_point)
[docs] def elliptic(self): """Corresponding projective model of elliptic geometry.""" return ddg.geometry.elliptic_models.ProjectiveModel(self.dimension)
[docs] def from_elliptic(self, object_, embedded=False): """Alias for :py:func:`.elliptic_models.projective_to_moebius`""" return ddg.geometry.elliptic_models.projective_to_moebius( object_, embedded=embedded )
[docs] def to_elliptic(self, object_, embedded=False): """Alias for :py:func:`.elliptic_models.moebius_to_projective`""" return ddg.geometry.elliptic_models.moebius_to_projective( object_, embedded=embedded )
[docs] def lie(self): """Corresponding projective model of Lie geometry.""" return ddg.geometry.lie_models.ProjectiveModel(self.dimension)
[docs] def from_lie(self, object_, embedded=False): raise NotImplementedError
[docs] def to_lie(self, object_, embedded=False): raise NotImplementedError
def __str__(self): return f"Projective model of {self.dimension}D Möbius geometry"
[docs]class ParaboloidModel(_Template): """Paraboloid model of Möbius geometry. .. rubric:: Model space The Möbius quadric is the quadric with matrix:: I | --+----- | 0 1 | 1 0 Which can be thought of as a paraboloid. .. rubric:: Representation of objects Spheres in this model are represented by quadrics which are intersections of the Möbius quadric with a subspace. Parameters ---------- dimension : int Attributes ---------- dimension : int """ @property def absolute(self): """The absolute quadric. Returns the quadric with matrix :: I | --+----- | 0 1 | 1 0 Returns ------- ddg.geometry.Quadric """ diagonal = np.ones(self.dimension + 2) diagonal[[-2, -1]] = 0 return Quadric(symmetric_matrix_from_diagonal(diagonal, parabolic=True)) def __str__(self): return f"Projective paraboloid model of {self.dimension}D Möbius geometry"
class _ProjectiveSubgeometry(ProjectiveModel, MetricGeometry): """Common superclass for submodels of projective Möbius geometry: hyperbolic_models.HemisphereModel, euclidean_models.MoebiusModel and spherical_models.ProjectiveModel. """ pass
[docs]def euclidean_to_projective(object_, embedded=False): r"""Convert from Euclidean model to projective model. This function works by embedding into the equatorial plane, then projecting stereographically. Parameters ---------- object_ : ddg.geometry.Subspace or ddg.geometry.spheres.SphereLike Object in n-dimensional ambient space. embedded : bool (default=False) If False, `object_` is embedded into the equatorial plane before projecting. Returns ------- ddg.geometry.Quadric An intersection of the Möbius quadric with a subspace in (n+1)-dimensonal ambient space. """ if not embedded: object_ = object_.embed() return inverse_stereographic_project(object_)
[docs]def projective_to_euclidean(object_, embedded=False): r"""Convert a Möbius sphere to a Euclidean sphere or subspace. This function works by projecting stereographically to the equatorial plane, then computing coordinates. Parameters ---------- object_ : ddg.geometry.Quadric Intersection of Möbius quadric with a subspace, all in (n+1)-dim. ambient space. embedded : bool (default=False) Whether to return the object in the equatorial plane or in n-dim. ambient space. Returns ------- ddg.geometry.spheres.SphereLike or ddg.geometry.Subspace """ mob = ddg.geometry.euclidean_models.MoebiusModel(object_.ambient_dimension - 1) img = stereographic_project(object_) if not embedded: img = img.unembed() if ( isinstance(object_, Quadric) and object_ <= mob.absolute and mob.fixed_point not in object_.subspace ): if mob.absolute.signature(object_.subspace).is_degenerate: # If the quadric only consists of a single point img = ddg.geometry.quadric_to_subspaces(img)[0] else: img = ddg.geometry.euclidean_models.ProjectiveModel.quadric_to_sphere(img) return img
[docs]def paraboloid_to_projective_and_back(object_): r"""Convert between projective and paraboloid models. Transform an object with the transformation that takes the projective model quadric to the paraboloid model quadric. This transformation is an involution, i.e. applying it again is the same as undoing the transformation. The transformation is :: I | ---+--------------------- | 1/sqrt(2) 1/sqrt(2) | 1/sqrt(2) -1/sqrt(2) Where I is the identity matrix of the appropriate size. Warnings -------- This WILL mutate `object_`. Parameters ---------- object_ : Transformable Returns ------- object_ : type(object\_) Transformed `object_`. """ trafo = np.eye(object_.ambient_dimension + 1) trafo[-2:, -2:] = np.sqrt(0.5) * np.array([[1, 1], [1, -1]]) object_ = object_.transform(trafo) return object_