Geometries
The geometry model modules like hyperbolic_models, euclidean_models etc. define geometries by adding additional structure to a projective space. This can include metrics, angle measurements, etc. Each geometry module has the following contents:
Classes representing models for the geometry. These classes define methods that deal specifically with objects represented in that model.
Functions to convert geometric objects between different models, if there is more than one.
For some geometry model classes, there are shortcuts, for example: ddg.geometry.euclidean for ddg.geometry.euclidean_models.ProjectiveModel. These are the “projective models” (and their duals, if applicable), which make the relation to projective geometry most obvious and are often nice to work with.
How exactly objects are represented in a model is documented in the docstrings of the geometry model classes.
Example: Euclidean geometry (and its lift to Moebius geometry)
The most familiar geometry is of course Euclidean geometry. We will create two points in the ProjectiveModel of Euclidean geometry, in which we compute distances as norms of differences in affine coordinates. We will then convert these points to the MoebiusModel, which is obtained by stereographically projecting to the 3-sphere in 4-space. In this other model, the distance is computed with a different formula, but should match the distance in the projective model.
As a geometry that comes with a metric, all models of Euclidean geometry implement the method d() that computes the Euclidean distance between objects. All geometries that have a metric work this way, see Metric geometries.
>>> import ddg
>>> euc = ddg.geometry.euclidean(3)
>>> print(euc)
Projective model of 3D Euclidean geometry
>>> euc.dimension
3
>>> euc.ambient_dimension
3
>>> p1 = ddg.geometry.subspace_from_affine_points([0, 0, 0])
>>> p2 = ddg.geometry.subspace_from_affine_points([1, 1, 1])
>>> euc.d(p1, p2)
1.732...
>>> mob = euc.moebius()
>>> mob.dimension
3
>>> mob.ambient_dimension
4
>>> print(mob.absolute)
quadric in 4D projective space
signature: (4, 1)
matrix:
[[ 1. 0. 0. 0. 0.]
[ 0. 1. 0. 0. 0.]
[ 0. 0. 1. 0. 0.]
[ 0. 0. 0. 1. 0.]
[ 0. 0. 0. 0. -1.]]
>>> print(mob.fixed_point)
Point in 4D projective space
Homogeneous coordinates: [0. 0. 0. 0.5 0.5]
>>> p1_mob = euc.to_moebius(p1)
>>> p2_mob = euc.to_moebius(p2)
>>> mob.d(p1_mob, p2_mob)
1.732...
Note that the attribute dimension always means the “intuitive” dimension of the geometry. ambient_dimension is the dimension of the projective space the model lives in.
The geometry models also provide a simple way to check whether a point in projective space is actually contained in the model of the geometry. For example, the projective model of Euclidean geometry consists of all points not at infinity:
>>> p1 in euc
True
>>> p_inf = ddg.geometry.Point([0, 0, 1, 0])
>>> p_inf.at_infinity()
True
>>> p_inf in euc
False
Spheres
Spheres are created using factory methods of the geometry model classes. Geometries for which spheres are implemented have methods sphere() that can be used to create them. Geometries defined by quadrics additionally have methods cayley_klein_sphere() and generalized_cayley_klein_sphere().
>>> s1 = euc.sphere(p1, 1.0)
>>> print(s1)
Sphere in:
Projective model of 3D Euclidean geometry
Center: [0. 0. 0. 1.]
Radius: 1.0
The lift to Moebius geometry is represented by a hyperplanar section of the absolute quadric, which is a quadric lying in a subspace.
>>> s1_mob = euc.to_moebius(s1)
>>> print(s1_mob)
quadric contained in 3D subspace in 4D projective space
signature: (3, 1)
matrix:
[[ 1. 0. 0. 0.]
[ 0. 1. 0. 0.]
[ 0. 0. 1. 0.]
[ 0. 0. 0. -1.]]
basis of containing subspace (columns):
...
>>> euc.from_moebius(s1_mob) == s1
True
Three spheres in Euclidean 3-space have a unique common orthogonal circle. Let us define two more spheres and compute this circle.
>>> s2 = euc.sphere(p2, 0.1)
>>> s3 = euc.sphere_from_affine_point_and_normals([1, 0, 0], 0.3)
>>> c = euc.orthogonal_sphere(s1, s2, s3)
>>> print(c)
Circle in:
Projective model of 3D Euclidean geometry
Center: [0.955 0.52 0.52 1. ]
Radius: 0.672...
Containing subspace basis (columns):
...
In the lift to Moebius geometry this circle may be obtained by polarity with respect to the absolute quadric. Let us check that we obtain the same circle this way.
>>> s2_mob = euc.to_moebius(s2)
>>> s3_mob = euc.to_moebius(s3)
>>> plane = mob.absolute.polarize(
... ddg.geometry.intersect(s1_mob.subspace, s2_mob.subspace, s3_mob.subspace)
... )
>>> c_mob = ddg.geometry.intersect(plane, mob.absolute)
>>> c == euc.from_moebius(c_mob)
True
Example: Hyperbolic geometry
>>> hyp = ddg.geometry.hyperbolic(2)
>>> print(hyp)
Projective model of 2D hyperbolic geometry
>>> p = ddg.geometry.subspace_from_affine_points([0, 0.5])
>>> p in hyp
True
>>> c = hyp.sphere(p, 0.2)
>>> print(c)
Circle in:
Projective model of 2D hyperbolic geometry
Center: [0. 0.5 1. ]
Radius: 0.2
Note that in the Poincare disk model hyperbolic circles look like Euclidean circles. The result after the transformation is thus treated like a Euclidean circle.
>>> print(hyp.to_poincare(c))
Circle in:
Projective model of 2D Euclidean geometry
...
The lift to Möbius geometry is non-unique and thus always gives two results
>>> p1_mob, p2_mob = hyp.to_moebius(p)
>>> p == hyp.from_moebius(p1_mob)
True
>>> p == hyp.from_moebius(p2_mob)
True
>>> c1_mob, c2_mob = hyp.to_moebius(c)
>>> c == hyp.from_moebius(c1_mob)
True
>>> c == hyp.from_moebius(c2_mob)
True
Example: Bisectors
Let us look at the creation of bisections.
Given two hyperplanes, the function angle_bisectors()
computes the two angle bisecting hyperplanes.
Alternatively one can also use
angle_bisector_orientation_preserving() or
angle_bisector_orientation_reversing()
for a specific one of the two bisectors.
>>> euc = ddg.geometry.euclidean(3)
>>> h1 = ddg.geometry.hyperplane_from_normal((1, 0, 0), level=0)
>>> h2 = ddg.geometry.hyperplane_from_normal((0, 1, 0), level=0)
>>> h_preserving, h_reversing = euc.angle_bisectors(h1, h2)
>>> ddg.geometry.normal(h_preserving)
array([ 0.70710678, 0.70710678, -0. ])
>>> ddg.geometry.normal(h_reversing)
array([ 0.70710678, -0.70710678, 0. ])
This will result in the following image.
Similarly, you can use perpendicular_bisector()
to obtain an orthogonal hyperplane intersecting the join of two points
in their (affine) midpoint.
>>> p1 = ddg.geometry.Point((0, 0, 1))
>>> p2 = ddg.geometry.Point((1, 1, 1))
>>> orthogonal_line = euc.perpendicular_bisector(p1, p2)
>>> ddg.geometry.normal(orthogonal_line)
array([-0.70710678, -0.70710678])
Which will result in the following image.
This works for arbitrary dimensions.
>>> p1 = ddg.geometry.Point((0, 0, 0, 1))
>>> p2 = ddg.geometry.Point((1, 1, 1, 1))
>>> orthogonal_hyperplane = euc.perpendicular_bisector(p1, p2)
>>> orthogonal_hyperplane.affine_points
[array([-2.1941919 , 4.36085856, -0.66666667]), array([-2.1941919 , -0.66666667, 4.36085856]), array([0.93303028, 0.28348486, 0.28348486])]
>>> ddg.geometry.normal(orthogonal_hyperplane)
array([0.57735027, 0.57735027, 0.57735027])
Example: Reflections in hyperplane
Similarly reflections in hyperplanes can be obtained in the following way.
Given a subspace and an hyperplane, the function reflect_in_hyperplane()
computes the reflection of the subspace in the hyperplane.
euc = ddg.geometry.euclidean(2)
point_2d = subspace_from_affine_points((1, 0))
line_2d = subspace_from_affine_points((0, 0), (-1, 1))
hyperplane_2d = subspace_from_affine_points((0, 0), (0, 1))
reflected_point_2d = euc.reflect_in_hyperplane(point_2d, hyperplane_2d)
reflected_line_2d = euc.reflect_in_hyperplane(line_2d, hyperplane_2d)
This will result in the following image.
This works for arbitrary dimensions.
The different types of geometries
There are three basic types of geometries, implemented as abstract base classes:
Metric geometries
MetricGeometry. Geometries of this type just have a dimension and define a metric d().
Cayley-Klein geometries
CayleyKleinGeometry. Geometries of this type have a quadric and its induced structure. For example, there is a Cayley-Klein distance, which is defined like this:
Let \(Q\) be a quadric in \(\RP^n\) called the absolute quadric or just absolute and let \(\langle\cdot,\cdot\rangle\) be its associated scalar product. We define the Cayley-Klein distance (not a metric!) on \(\RP^n \setminus Q\) as
The presence of a Cayley-Klein distance means that (generalized) Cayley-Klein spheres can be created in these geometries using the factory methods cayley_klein_sphere and generalized_cayley_klein_sphere.
Note
Sometimes in the literature (for example Wikipedia), Cayley-Klein geometries are required to have a metric. We do not require this, because the Cayley-Klein distance and Cayley-Klein spheres still make sense, even without a metric.
Metric Cayley-Klein geometries
MetricCayleyKleinGeometry. Geometries of this type are both Metric and Cayley-Klein geometries, as you would expect.
A Cayley-Klein metric is a metric \(d\) derived from such a Cayley-Klein distance, namely one that is a function of \(K_Q\).
In the projective model of hyperbolic geometry for example, the relation is given by the equation
where \(Q\) is a quadric of signature \((n,1)\) and the metric can be defined for two points which are on the same side of the quadric. You can see that the metric can always be converted to a Cayley-Klein distance, but not vice versa. Which Cayley-Klein distances correspond to metric distances depends on the geometry.
In these geometries, both metric spheres (using the method sphere) and Cayley-Klein spheres can be created.