"""Euclidean geometry module.
Contains model classes and functions for conversion between the models.
"""
import numpy as np
import ddg
import ddg.geometry.signatures as sg
import ddg.math.linalg as la
import ddg.math.projective as pmath
from ddg import nonexact
from ddg.geometry import laguerre_models, moebius_models
from ddg.geometry._intersection import intersect, join
from ddg.geometry._projections import north_pole
from ddg.geometry._quadrics import Quadric, intersect_quadric_subspace
from ddg.geometry._subspaces import (
Point,
coordinate_hyperplane,
subspace_from_affine_points,
subspace_from_affine_points_and_directions,
)
from ddg.geometry.geometry_model_templates import CayleyKleinGeometry, MetricGeometry
from ddg.geometry.spheres import QuadricSphere, _QuadricSphereGeometryBridge
from ddg.math.euclidean import angle_bisector_orientation_preserving as euc_abop
from ddg.math.euclidean import angle_bisector_orientation_reversing as euc_abor
from ddg.math.euclidean import reflection_in_a_hyperplane
__all__ = [
"ProjectiveModel",
"DualProjectiveModel",
"MoebiusModel",
"projective_to_moebius",
"moebius_to_projective",
]
[docs]class DualProjectiveModel(CayleyKleinGeometry):
"""Dual projective model of Euclidean geometry.
.. rubric:: Model space
The model space is RP^n without [0,...,0, 1]. The points in this space can
be thought of as hyperplanes in R^n.
.. rubric:: Representation of objects
Objects are just projective points, subspaces, quadrics and Cayley-Klein
spheres that do not contain the origin.
Parameters
----------
dimension : int
Attributes
----------
dimension : int
"""
@property
def absolute(self):
"""The absolute quadric with matrix ``diag([1,...,1, 0])``.
Returns
-------
Quadric
"""
matrix = np.eye(self.dimension + 1)
matrix[-1, -1] = 0
return Quadric(matrix)
[docs] def angle(self, subspace1, subspace2):
"""Euclidean angle.
Computes the angle between the duals of the subspaces.
Parameters
----------
subspace1, subspace2 : ddg.geometry.Subspace
Returns
-------
float
Raises
------
NotImplementedError
If the subspaces have unsupported dimensions.
"""
if subspace1.dimension == 0 and subspace1.dimension == 0:
arg = np.sqrt(self.cayley_klein_distance(subspace1, subspace2))
if ddg.nonexact.isclose(arg, 1):
return 0.0
return np.arccos(arg)
if subspace1.codimension == 1 and subspace2.codimension == 1:
return ProjectiveModel(self.dimension).angle(
subspace1.dual(), subspace2.dual()
)
raise NotImplementedError(
"Angle can currently only be computed between two vectors or "
"between two hyperplanes."
)
[docs] def laguerre(self):
"""Corresponding projective model (Blaschke cylinder) of Laguerre geometry."""
return laguerre_models.ProjectiveModel(self.dimension)
[docs] def to_laguerre(self, object_, embedded=False):
raise NotImplementedError
[docs] def from_laguerre(self, object_, embedded=False):
raise NotImplementedError
def __contains__(self, point):
return self.inner_product(point, point) > 0
def __str__(self):
return f"Dual projective model of {self.dimension}D Euclidean geometry"
[docs]class ProjectiveModel(CayleyKleinGeometry, MetricGeometry):
"""Euclidean geometry.
.. rubric:: Model space
The model space is RP^n without the points at infinity.
.. rubric:: Representation of objects
Objects are just projective points, subspaces and quadrics not at infinity
and Euclidean spheres.
Parameters
----------
dimension : int
Attributes
----------
dimension : int
"""
@property
def absolute(self):
"""The absolute quadric.
This is the dual quadric of the quadric with matrix ``diag([1,...,1, 0])``.
As the dual of a degenerate quadric, it is contained in the hyperplane at
infinity.
Returns
-------
ddg.geometry.Quadric
"""
return DualProjectiveModel(self.dimension).absolute.dual()
[docs] @staticmethod
def d(x, y):
"""Euclidean distance.
Dehomogenizes and computes the norm of the difference of `x` and `y`.
Parameters
----------
x, y : Point or array_like of shape (dimension+1,)
Returns
-------
float
"""
if not isinstance(x, Point):
x = Point(x)
if not isinstance(y, Point):
y = Point(y)
return np.linalg.norm(x.affine_point - y.affine_point)
[docs] def angle(self, subspace1, subspace2):
"""Euclidean angle.
If both subspaces are points, the angle between them as vectors will be
returned, up to pi. Otherwise, the smaller angle between the subspaces
will be returned, up to pi/2. Currently only hyperplanes are supported
in the latter case.
Parameters
----------
subspace1, subspace2 : ddg.geometry.Subspace
Returns
-------
float
Raises
------
NotImplementedError
If the subspaces have unsupported dimensions.
"""
if subspace1.dimension == 0 and subspace2.dimension == 0:
p1 = subspace1.affine_point
p2 = subspace2.affine_point
p1 /= np.linalg.norm(p1)
p2 /= np.linalg.norm(p2)
inn_prod = np.dot(p1, p2)
if ddg.nonexact.isclose(inn_prod, 1):
return 0.0
if ddg.nonexact.isclose(inn_prod, -1):
return np.pi
return np.arccos(inn_prod)
if subspace1.codimension == 1 and subspace2.codimension == 1:
return DualProjectiveModel(self.dimension).angle(
subspace1.dual(), subspace2.dual()
)
raise NotImplementedError(
"Angle can currently only be computed between two vectors or "
"between two hyperplanes."
)
[docs] def moebius(self):
"""Corresponding projective model of Moebius geometry."""
return MoebiusModel(self.dimension)
[docs] def to_moebius(self, object_, embedded=False):
"""Alias for :py:func:`.projective_to_moebius`"""
return projective_to_moebius(object_, embedded=embedded)
[docs] def from_moebius(self, object_, embedded=False):
"""Alias for :py:func:`.moebius_to_projective`"""
return moebius_to_projective(object_, embedded=embedded)
[docs] def laguerre(self):
"""Corresponding projective model (Blaschke cylinder) of Laguerre geometry."""
return laguerre_models.ProjectiveModel(self.dimension)
[docs] def to_laguerre(self, object_):
"""Alias for :py:func:`.laguerre_models.euclidean_to_projective`"""
return laguerre_models.euclidean_to_projective(object_)
[docs] def from_laguerre(self, object_):
"""Alias for :py:func:`.laguerre_models.projective_to_euclidean`"""
return laguerre_models.projective_to_euclidean(object_)
[docs] @staticmethod
def angle_bisectors(hyperplane1, hyperplane2):
"""Create the two angle bisecting hyperplanes of two given hyperplanes.
The first entry in the output is the angle bisector that is
orientation preserving. The second one is orientation reversing.
Parameters
----------
hyperplane1 : ddg.geometry.Subspace
hyperplane2 : ddg.geometry.Subspace
Returns
-------
(ddg.geometry.Subspace, ddg.geometry.Subspace)
See Also
--------
ddg.geometry.angle_bisector_orientation_preserving
ddg.geometry.angle_bisector_orientation_reversing
"""
pre = ProjectiveModel.angle_bisector_orientation_preserving(
hyperplane1, hyperplane2
)
rev = ProjectiveModel.angle_bisector_orientation_reversing(
hyperplane1, hyperplane2
)
return (pre, rev)
[docs] @staticmethod
def angle_bisector_orientation_preserving(hyperplane1, hyperplane2):
"""Create the orientation preserving angle bisecting
hyperplane of two given hyperplanes.
It is orientation preserving in the sense, that
reflection in this angle bisector maps one hyperplane
to the other with matching orientation.
Parameters
----------
hyperplane1 : ddg.geometry.Subspace
hyperplane2 : ddg.geometry.Subspace
Returns
-------
ddg.geometry.Subspace
See Also
--------
ddg.math.euclidean.angle_bisector_orientation_preserving
"""
n1, d1 = (ddg.geometry.normal(hyperplane1), ddg.geometry.level(hyperplane1))
n2, d2 = (ddg.geometry.normal(hyperplane2), ddg.geometry.level(hyperplane2))
n_pre, d_pre = euc_abop(n1, d1, n2, d2)
return ddg.geometry.hyperplane_from_normal(n_pre, level=d_pre)
[docs] @staticmethod
def angle_bisector_orientation_reversing(hyperplane1, hyperplane2):
"""Create the orientation reversing angle bisecting
hyperplane of two given hyperplanes.
It is orientation reversing in the sense, that
reflection in this angle bisector maps one hyperplane
to the other with opposite orientation.
Parameters
----------
hyperplane1 : ddg.geometry.Subspace
hyperplane2 : ddg.geometry.Subspace
Returns
-------
ddg.geometry.Subspace
See Also
--------
ddg.math.euclidean.angle_bisector_orientation_reversing
"""
n1, d1 = (ddg.geometry.normal(hyperplane1), ddg.geometry.level(hyperplane1))
n2, d2 = (ddg.geometry.normal(hyperplane2), ddg.geometry.level(hyperplane2))
n_rev, d_rev = euc_abor(n1, d1, n2, d2)
return ddg.geometry.hyperplane_from_normal(n_rev, level=d_rev)
[docs] @staticmethod
def perpendicular_bisector(p1, p2):
"""Create a hyperplane orthogonal to the join of p1 and p2.
It intersects this line in the midpoint of p1.affine_point and p2.affine_point.
Parameters
----------
p1: ddg.geometry.Point
p2: ddg.geometry.Point
Returns
-------
ddg.geometry.Subspace
Hyperplane that is orthogonal to the input line.
"""
p1_affine = np.array(p1.affine_point)
p2_affine = np.array(p2.affine_point)
point = (p1_affine + p2_affine) / 2
direction = p1_affine - p2_affine
# We get a direction of our line and use that
# as the normal for the newly created hyperplane.
bisector_hyperplane = ddg.geometry.hyperplane_from_normal(
direction, point=point
)
return bisector_hyperplane
[docs] @staticmethod
def reflect_in_hyperplane(subspace, hyperplane):
"""Reflects a subspace in a hyperplane.
Parameters
----------
subspace : ddg.geometry.Subspace
Subspace to reflect in the hyperplane.
hyperplane : ddg.geometry.Subspace
Hyperplane in n-dimensional ambient space.
Raises
------
ValueError
if the reflecor is not a hyperplane,
if the hyperplane is at infinity,
or if the ambient dimensions don't match.
Returns
-------
reflected_hyperplane : ddg.geometry.Subspace
The reflected subspace.
"""
if hyperplane.codimension != 1:
raise ValueError(
"Reflector must be a hyperplane, "
f"but is of dimension {hyperplane.dimension}"
f"in {hyperplane.ambient_dimension}-dimensional space."
)
if hyperplane.at_infinity():
raise ValueError("Hyperplane is at infinity.")
if hyperplane.ambient_dimension != subspace.ambient_dimension:
raise ValueError(
"Subspace and hyperplane must have the same ambient dimension. "
f"The subspace has dimension {subspace.ambient_dimension}. "
f"The hyperplane has dimension {hyperplane.ambient_dimension}"
)
Nl = ddg.geometry.normal_with_level(hyperplane)
normal = Nl[:-1]
level = -Nl[-1]
reflection_matrix = reflection_in_a_hyperplane(normal, level)
return ddg.geometry.Subspace(*(reflection_matrix @ subspace.matrix).T)
[docs] def ellipse_from_foci(self, f1, f2, a):
"""Get ellipse in 2D from foci and distance parameter.
The returned ellipse will be the set::
{x : |x - f1| + |x - f2| = 2a}
Parameters
----------
f1, f2 : Point
a : float
2 * a must ge greater than the distance between the foci.
Returns
-------
ddg.geometry.Quadric
Raises
------
ValueError
If 2 * a is not greater than the distance between the foci.
"""
if self.dimension != 2:
raise NotImplementedError("Function ellipse_from_foci only works in 2D.")
f1 = f1.affine_point
f2 = f2.affine_point
c = np.linalg.norm(f1 - f2) / 2
if a <= c:
raise ValueError(
"2 * a must be greater than the distance between the foci."
)
# We first create the standard ellipse centered at the origin with the
# x-axis being the major axis. Then we rotate and translate it
# appropriately.
Q = Quadric(np.diag([1 / (a**2), 1 / (a**2 - c**2), -1]))
# It doesn't matter which way the difference goes because of symmetries of the
# ellipse.
angle = np.angle(complex(*(f2 - f1)))
A = ddg.math.euclidean2d.rotation(angle)
F = pmath.affine_transformation(A, (f1 + f2) / 2)
Q = Q.transform(F)
return Q
[docs] @classmethod
def sphere(cls, center, radius, subspace=None, atol=None, rtol=None):
if not isinstance(center, Point):
center = Point(center)
dim = center.ambient_dimension
geometry_bridge = _EuclideanQSGB(cls(dim))
return QuadricSphere(
center,
radius,
subspace=subspace,
geometry_bridge=geometry_bridge,
atol=atol,
rtol=rtol,
)
[docs] def sphere_through_points(self, *points):
"""Find the unique k-sphere through given points.
The sphere might degenerate to subspace.
Generically, n points define an (n-2)-sphere. If n given points are
contained in a lower-dimensional sphere, this sphere will be returned.
Parameters
----------
*points : iterable with types `ddg.geometry.Point` or `float`
The points for which to find the sphere.
The point at infinity can be passed as the floating point
representation of positive infinity, e.g., as `numpy.inf`.
Returns
-------
SphereLike or Subspace
Raises
------
ValueError
- If the points are not contained in a sphere.
- If a given point is not an element of the according geometry.
- If only the point at infinity is given.
See also
--------
ddg.geometry.hyperbolic_models.ProjectiveModel.orthogonal_sphere,
ddg.geometry.elliptic_models.ProjectiveModel.orthogonal_sphere
"""
mob = MoebiusModel(self.dimension)
# Lift the points to Möbius geometry
lifted_points = []
for point in points:
if point == np.inf:
lifted_points.append(north_pole(self.dimension + 1))
elif point in self:
lifted_points.append(projective_to_moebius(point))
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.")
if plane == north_pole(self.dimension + 1):
raise ValueError(
"Only the point at infinity was given which does "
"not define a Euclidean sphere."
)
# Intersect the plane with the Möbius quadric and project back
lifted_sphere = intersect(plane, mob.absolute)
return moebius_to_projective(lifted_sphere)
[docs] @classmethod
def sphere_from_affine_point_and_normals(
cls, point, radius, normals=None, atol=None, rtol=None
):
"""Create a Euclidean sphere from an affine point and normal directions.
Parameters
----------
point : array_like of shape (n,)
Affine coordinate vector in n-dimensional space.
radius : float
normals : array_like of shape (k, n) (default=None)
The rows/elements of `normals` are vectors in affine coordinates.
`sphere.subspace` will be the affine subspace ::
orthogonal_complement(span(normals)) + point.
The default is an empty array, i.e. `sphere.subspace` will be the whole
space.
atol, rtol : float (default=None)
Tolerances for the sphere.
Returns
-------
sphere : ddg.geometry.spheres.SphereLike
Let k'=dim(span(normals)). Returns a Euclidean (n-k'-1)-sphere contained
in the (n-k')-dimensional projective subspace described above.
"""
center = subspace_from_affine_points(point)
if normals is None:
normals = np.array([])
else:
normals = np.array(normals)
if normals.size == 0:
return cls.sphere(center, radius, atol=atol, rtol=rtol)
directions = list(la.nullspace(normals).T)
subspace = subspace_from_affine_points_and_directions(point, directions)
return cls.sphere(center, radius, subspace=subspace, atol=atol, rtol=rtol)
[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.SphereLike` or
`ddg.geometry.Subspace`
Returns
-------
`ddg.geometry.spheres.SphereLike` or `ddg.geometry.Subspace` or `None`
See also
--------
ddg.geometry.hyperbolic_models.ProjectiveModel.orthogonal_sphere,
ddg.geometry.elliptic_models.ProjectiveModel.orthogonal_sphere
"""
mob = MoebiusModel(self.dimension)
# Lift the spheres to Moebius model
mob_spheres = [projective_to_moebius(sph) for sph in spheres]
# Find the intersection of their subspaces
mob_subspaces = [sph.subspace for sph in mob_spheres]
polar_subspace = intersect(*mob_subspaces)
if polar_subspace.dimension < 0:
# There is no sphere orthogonal to the given spheres
return None
# Take the sphere in Moebius model polar to the intersection
ort_sphere_subspace = mob.absolute.polarize(polar_subspace)
ort_sphere_mob = intersect(mob.absolute, ort_sphere_subspace)
if ort_sphere_mob.dimension < 0:
# The intersection of the set of orthogonal spheres is empty.
return None
# Project the sphere back to Euclidean model
ort_sphere_euc = moebius_to_projective(ort_sphere_mob)
return ort_sphere_euc
[docs] @staticmethod
def radical_subspace(*spheres):
"""Find the subspace spanned by the centers of all hyperspheres
orthogonal to given spheres, or the radical center of given spheres.
For two circles in Euclidean plane the result is usually called the
radical line. More generally, for two hyperspheres in Euclidean space the
returned subspace is called the radical hyperplane.
Consider two spheres.
* If the spheres do not have common points, the radical subspace is the
subspace consisting of the centers of all hyperspheres orthogonal to two
given spheres.
* If the spheres touch, the radical subspace is the common touching subspace.
* If the spheres intersect, the radical subspace the subspace spanned by their
common sphere.
The radical subspace of more than two spheres is the intersection of their
pairs' radical subspaces.
A point on the radical subspace outside all given spheres represents the center
of a common orthogonal Euclidean hypersphere. If the dimension of the radical
subspace is non-zero, then such hyperspheres exist. If the dimension is zero,
then the point is called the radical center.
Parameters
----------
*spheres: iterable of type `ddg.geometry.spheres.SphereLike` or
`ddg.geometry.Subspace`
Returns
-------
`ddg.geometry.Subspace` or `None`
"""
# Lift the spheres to Moebius model
mob_spheres = [projective_to_moebius(sph) for sph in spheres]
# Find the intersection of their subspaces
mob_subspaces = [sph.subspace for sph in mob_spheres]
common_subspace = intersect(*mob_subspaces)
if common_subspace.dimension < 0:
# There is no sphere orthogonal to the given spheres
return None
# All the points in `common_subspace` are polar to the hyperspheres
# orthogonal to `mob_spheres`. The points are mapped to centers of
# corresponding Euclidean hyperspheres under stereographic projection.
return moebius_to_projective(common_subspace)
[docs] @staticmethod
def sphere_to_quadric(sphere):
"""Convert Euclidean sphere to quadric
Parameters
----------
sphere : ddg.geometry.spheres.SphereLike
Returns
-------
ddg.geometry.Quadric
"""
# This is necessary because quadric.transform actually transforms
# the quadric's subspace and can only handle invertible matrices. This
# should be fixed once transform returns a new object instead of
# mutating.
atol = sphere.atol
rtol = sphere.rtol
if nonexact.isclose(sphere.radius, 0, atol=atol, rtol=rtol):
return Quadric(np.array([[0]]), subspace=sphere.center)
r = sphere.radius
c = sphere.center.affine_point
k = sphere.ambient_dimension
Q = np.eye(k + 1)
Q[-1, -1] = -1
Q = Quadric(Q)
F = pmath.affine_transformation(r * np.eye(k), c)
Q = Q.transform(F)
if sphere.subspace.codimension > 0:
Q = intersect_quadric_subspace(Q, sphere.subspace)
Q.atol = atol
Q.rtol = rtol
return Q
[docs] @classmethod
def quadric_to_sphere(cls, quadric, atol=None, rtol=None):
"""Convert quadric to a Euclidean sphere, if it is one.
Parameters
----------
quadric : ddg.geometry.Quadric
atol, rtol : float (default=None)
If None is given, the global defaults are used. See ddg.nonexact for
details.
Returns
-------
ddg.geometry.Sphere
Raises
------
ValueError
If quadric is not a sphere.
"""
# Check signature
sgn = quadric.signature(affine=True)
k = quadric.subspace.dimension
is_point = sgn == sg.AffineSignature(k, 0, 1, last_entry=0)
is_regular_sphere = sgn == sg.AffineSignature(k, 1, last_entry=-1)
if not (is_point or is_regular_sphere):
raise ValueError(
f"Signature of quadric is {sgn}, but has to be one of:\n"
"- (k, 1) with -1 in the last position to be a regular sphere,\n"
"- or (k, 0, 1) with 0 in the last position to be a point."
)
Q = quadric.normalize(affine=True)
B = Q.subspace.matrix
try:
r, _, c = pmath.decompose_similarity(B, atol=atol, rtol=rtol)
except ValueError as exc:
raise ValueError("Quadric is not a Euclidean sphere.") from exc
c = subspace_from_affine_points(c)
if is_point:
r = 0
return cls.sphere(c, r, subspace=Q.subspace)
def __contains__(self, point):
return not point.at_infinity()
def __str__(self):
return f"Projective model of {self.dimension}D Euclidean geometry"
class _EuclideanQSGB(_QuadricSphereGeometryBridge):
"""Auxiliary object for spheres. See the spheres developer's guide for details."""
def __init__(self, geometry):
self.geometry = geometry
def validate_sphere(self, sphere):
if sphere.center.at_infinity():
raise ValueError("Center of Euclidean spheres can not be at infinity.")
@property
def model_dimension(self):
return self.geometry.dimension
def sphere_to_quadric(self, sphere):
return self.geometry.sphere_to_quadric(sphere)
def embed(self, sphere, subspace=None):
if subspace is None:
subspace = coordinate_hyperplane(sphere.ambient_dimension + 1)
try:
# The last basis vector must be a point not at infinity and the others
# must be a scaled orthonormal basis of the subspace. Determine the
# scaling s.
s, _, _ = pmath.decompose_similarity(
subspace.matrix, atol=sphere.atol, rtol=sphere.rtol
)
except ValueError as exc:
raise ValueError(
"This embedding would not result in a Euclidean sphere. If "
"this is intentional, convert the sphere to a Quadric before "
"embedding."
) from exc
return self.geometry.sphere(
sphere.center.embed(subspace),
s * sphere.radius,
sphere.subspace.embed(subspace),
)
def unembed(self, sphere, subspace=None):
if subspace is None:
subspace = coordinate_hyperplane(sphere.ambient_dimension)
if not sphere.subspace <= subspace:
raise ValueError("Given subspace must contain sphere.")
try:
# The last basis vector must be a point not at infinity and the others
# must be a scaled orthonormal basis of the subspace. Determine the
# scaling s.
s, _, _ = pmath.decompose_similarity(
subspace.matrix, atol=sphere.atol, rtol=sphere.rtol
)
except ValueError as exc:
raise ValueError(
"This coordinate computation would not result in a Euclidean "
"sphere. If this is intentional, convert the sphere to a "
" Quadric and compute its coordinates."
) from exc
return self.geometry.sphere(
sphere.center.unembed(subspace),
sphere.radius / s,
sphere.subspace.unembed(subspace),
)
def __repr__(self):
return "_EuclideanQSGB(" + repr(self.geometry) + ")"
[docs]class MoebiusModel(moebius_models._ProjectiveSubgeometry):
"""Euclidean geometry as a subgeometry of Möbius geometry.
.. rubric:: Model space
The model space is the standard Möbius quadric with matrix
`diag([1,...,1, -1])` without `fixed_point`.
.. rubric:: Representation of objects
Subspaces and spheres are represented as Möbius spheres, i.e. Quadric
objects contained in the Möbius quadric.
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, which checks whether a point is in the
model space of this geometry, i.e. the Möbius quadric.
"""
@property
def fixed_point(self):
"""Projection point on the Möbius quadric. Used to compute metric.
This is the north pole with representative [0,...,0, 0.5, 0.5].
Returns
-------
ddg.geometry.Point
"""
return self.euclidean_point
[docs] def d(self, v, w):
"""Euclidean distance in the Möbius model.
Given by ::
d([x], [y])^2 = (-1/2) * <x, y> / (<x, p>, <y, p>)
where `p` is `self.fixed_point` and `x` and `y` are points in the
Möbius quadric not equal to `p`.
Parameters
----------
x, y : ddg.geometry.Point or array_like of shape (dimension+2,)
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 np.sqrt(-0.5 * b(v, w) / (b(v, p) * b(w, p)))
def __contains__(self, point):
return point in self.absolute and point != self.fixed_point
def __str__(self):
return (
f"Spherical model of {self.dimension}D Euclidean geometry\n"
+ f"in the projective model of {self.dimension}D Moebius geometry"
)
[docs]def projective_to_moebius(object_, embedded=False):
"""Alias for :py:func:`.moebius_models.euclidean_to_projective`"""
return moebius_models.euclidean_to_projective(object_, embedded=embedded)
[docs]def moebius_to_projective(object_, embedded=False):
"""Alias for :py:func:`.moebius_models.projective_to_euclidean`"""
return moebius_models.projective_to_euclidean(object_, embedded=embedded)