"""Möbius geometry module.
Contains model classes and functions for conversion between the models.
"""
import numpy as np
from ddg.geometry import euclidean_models
from ddg.geometry.geometry_model_templates import CayleyKleinGeometry, MetricGeometry
from ddg.geometry.intersection import meet
from ddg.geometry.projections import (
inverse_stereographic_project,
stereographic_project,
)
from ddg.geometry.quadrics import Quadric
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 : Quadric
Möbius spheres.
Returns
-------
float
"""
s1 = self.pole_of_sphere(s1)
s2 = self.pole_of_sphere(s2)
return np.arccos(np.sqrt(self.cayley_klein_distance(s1, s2)))
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 : Subspace
Returns
-------
Quadric
"""
Q = self.absolute
return meet(Q, Q.polarize(subspace))
def pole_of_sphere(self, sphere):
"""Return pole corresponding to sphere.
Parameters
----------
sphere : Quadric
Returns
-------
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
-------
Quadric
"""
matrix = np.eye(self.dimension + 2)
matrix[-1, -1] = -1
return Quadric(matrix)
[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
-------
Quadric
"""
diagonal = np.ones(self.dimension + 2)
diagonal[[-2, -1]] = 0
return Quadric(symmetric_matrix_from_diagonal(diagonal, parabolic=True))
class _ProjectiveSubgeometry(CayleyKleinGeometry, MetricGeometry):
"""Common superclass for submodels of projective Möbius geometry:
hyperbolic_models.HemisphereModel, euclidean_models.MoebiusModel and
spherical_models.ProjectiveModel.
avoids duplicate code.
"""
def __init__(self, dimension):
super().__init__(dimension)
self._moebius = ProjectiveModel(self.dimension)
# Regular non-special methods can be forwarded like this
self.absolute = self._moebius.absolute
self.angle = self._moebius.angle
# Special methods should be defined on the class and not in the instance dictionary.
# See
# https://docs.python.org/3/reference/datamodel.html#special-method-lookup
def __contains__(self, point):
return self._moebius.__contains__(point)
[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_ : Subspace or SphereLike
Object in n-dimensional ambient space.
embedded : bool (default=False)
If False, `object_` is embedded into the equatorial plane before
projecting.
Returns
-------
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_ : 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
-------
SphereLike or Subspace
"""
img = stereographic_project(object_)
if not embedded:
img = img.unembed()
if isinstance(img, Quadric):
geo = euclidean_models.ProjectiveModel(img.ambient_dimension)
img = geo.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_