Subspaces

Instances of Subspace are projective subspaces, e.g. points, lines, planes… . They are given in homogeneous coordinates.

>>> import ddg

>>> subspace = ddg.geometry.subspaces.Subspace([1,0,1,0], [0,1,1,1])
>>> subspace
Subspace(array([1., 0., 1., 0.]), array([0., 1., 1., 1.]))
>>> print(subspace)
Line in 3D projective space
Homogeneous coordinates (columns):
  [[1. 0.]
   [0. 1.]
   [1. 1.]
   [0. 1.]]

Note that we have passed the points as separate arguments. To create a subspace using the columns or rows of a matrix as points, we have the utility functions subspace_from_columns() and subspace_from_rows():

>>> import numpy as np

>>> points = np.array([[1,0],[0,1],[1,1],[0,1]])
>>> points
array([[1, 0],
       [0, 1],
       [1, 1],
       [0, 1]])
>>> subspace = ddg.geometry.subspaces.subspace_from_columns(points)
>>> subspace
Subspace(array([1., 0., 1., 0.]), array([0., 1., 1., 1.]))
>>> subspace.dimension
1
>>> subspace.ambient_dimension
3

There is a special subclass Point that subspaces with a single point automatically get cast to:

>>> p = ddg.geometry.subspaces.Subspace([1,0,1,0])
>>> print(p)
Point in 3D projective space
Homogeneous coordinates: [1. 0. 1. 0.]
>>> p.point
array([1., 0., 1., 0.])

Subspaces can be dualized:

>>> print(subspace.dualize())
Line in 3D projective space
Homogeneous coordinates (columns):
  [[...]
   [...]
   [...]
   [...]]

We can also intersect and join subspaces with either their respective functions in the subspaces module, or the general Intersection and Join classes in the intersection module.

>>> points1 = [[1,0,0,1],[0,1,0,1]]
>>> points2 = [[1,1,0,2],[0,0,1,2]]
>>> subspace1 = ddg.geometry.subspaces.Subspace(*points1)
>>> subspace2 = ddg.geometry.subspaces.Subspace(*points2)

>>> meet = ddg.geometry.subspaces.meet(subspace1, subspace2)
>>> meet
Point(array([...]))

>>> join = ddg.geometry.subspaces.join(subspace1, subspace2)
>>> join
Subspace(array([...]), array([...]), array([...]))

>>> meet.dimension
0

>>> join.dimension
2

Converting subspaces to nets

Subspaces of any dimension can simply be converted to nets using to_smooth_net(), accessible directly as ddg.to_smooth_net.

Available parametrizations

There are two different parametrizations available using the boolean keyword argument convex. You can also choose whether the net should output homogeneous or affine coordinates using the keyword affine. Both are False by default.

The convex=False parametrization takes k+1 parameters and simply returns a linear combination of the given basis vectors. It is not directly useful for visualization.

>>> subspace = ddg.geometry.subspaces.Subspace([0,0,1,0]).dualize()
>>> print(subspace)
Plane in 3D projective space
Homogeneous coordinates (columns):
  [[ 0.  1.  0.]
   [ 1.  0.  0.]
   [ 0.  0.  0.]
   [ 0.  0. -1.]]

>>> plane_net_homogeneous = ddg.to_smooth_net(subspace)
>>> plane_net_homogeneous(1, 0, 0)
array([0., 1., 0., 0.])
>>> plane_net_homogeneous(1, 1, 1)
array([ 1.,  1.,  0., -1.])

The convex=True parametrization is the one used for visualization. It takes k parameters and returns

\[\begin{split}&\frac{1 - \lambda_1 u_{i1} - \dots - \lambda_k u_{ik}}{u_{i0}} u_0 + \lambda_1 u_1 + \dots + \lambda_k u_k \\ = &\frac{1}{u_{i0}} u_0 + \lambda_1 \left(u_1 - \frac{u_{i1}}{u_{i0}} u_0\right) + \dots + \lambda_k \left(u_k - \frac{u_{ik}}{u_{i0}} u_0\right)\end{split}\]

where \(i\) is affine_component of the subspace and \((u_{ij})\) is subspace.matrix, i. e. \(u_{ij}\) is the \(i\)-th entry of the basis vector \(u_j\). This parametrization always produces vectors with affine_component equal to 1. \(u_0\) can not be at infinity, so the basis of the subspace gets searched for a vector not at infinity and it is then moved to the first position. This also means that this parametrization does not work for subspaces at infinity.

>>> plane_net = ddg.to_smooth_net(subspace, convex=True, affine=True)
>>> plane_net(0, 0)
array([0., 0., 0.])
>>> plane_net(1, 0)
array([0., 1., 0.])
>>> plane_net(0, 1)
array([1., 0., 0.])

Of particular interest are two cases: The first is where \(u_{i0} = \dots = u_{ik} = 1\). In this case, the parametrization is simply

\[u_0 + \lambda_1 (u_1 - u_0) + \dots + \lambda_k (u_k - u_0),\]

so in particular \(n(0,\dots,0) = u_0\) and \(n(e_j) = u_j\). This form is useful for drawing line segments for example: Simply restrict the domain of the curve to [0, 1] and you will get the line segment between \(u_0\) and \(u_1\) (See Restricting the domain)

The other one is where \(u_{i0} = 1\) and \(u_{i1} = \dots = u_{ik} = 0\). In this case, the parametrization is just

\[u_0 + \lambda_1 u_1 + \dots + \lambda_k u_k.\]

Restricting the domain

To restrict the domain, to_smooth_net has a keyword domain. It can take either a domain object or its representation as a list of lists, see Domains for more information.

>>> plane_net = ddg.to_smooth_net(subspace, convex=True, affine=True)
>>> plane_net.domain
SmoothRectangularDomain([[-inf, inf, False], [-inf, inf, False]])
>>> plane_net_restricted = ddg.to_smooth_net(
...     subspace,
...     convex=True,
...     affine=True,
...     domain=[[0, 1], [0, 1]]
... )
>>> plane_net_restricted.domain
SmoothRectangularDomain([[0.0, 1.0, False], [0.0, 1.0, False]])

Helpful utility functions

There are a few utility functions and methods available that help with creating better parametrizations:

ddg.geometry.subspaces.dehomogenize_subspace()

brings a subspace to the first above-mentioned special case \(u_{i0} = \dots = u_{ik} = 1\) if none of the spanning vectors are at infinity.

ddg.geometry.subspaces.orthonormalize_subspace()

brings it to the second special case \(u_{i0} = 1,\ u_{i1} = \dots = u_{ik} = 0\) with orthonormal \(u_1,\dots,u_k\).

ddg.geometry.subspaces.center_subspace()

Can set \(u_0\) to be any point in the subspace.

These functions are also available as methods orthonormalize(), center() and dehomogenize().

Visualization in Blender

A guide on how to visualize ddg objects, including subspaces can be found here.