.. _subspaces: Subspaces --------- Instances of :py:class:`~ddg.geometry.subspaces.Subspace` are projective subspaces, e.g. points, lines, planes... . They are given in homogeneous coordinates. .. doctest:: >>> 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 :py:func:`~ddg.geometry.subspaces.subspace_from_columns` and :py:func:`~ddg.geometry.subspaces.subspace_from_rows`: .. doctest:: >>> 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 :py:class:`~ddg.geometry.subspaces.Point` that subspaces with a single point automatically get cast to: .. doctest:: >>> 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: .. doctest:: >>> 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 :py:mod:`~ddg.geometry.subspaces` module, or the general :py:class:`~ddg.geometry.intersection.Intersection` and :py:class:`~ddg.geometry.intersection.Join` classes in the intersection module. .. doctest:: >>> 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 :py:func:`~ddg.conversion.nets.geometry.core.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. .. doctest:: >>> 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 .. math:: &\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) where :math:`i` is `affine_component` of the subspace and :math:`(u_{ij})` is `subspace.matrix`, i. e. :math:`u_{ij}` is the :math:`i`-th entry of the basis vector :math:`u_j`. This parametrization always produces vectors with `affine_component` equal to 1. :math:`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. .. doctest:: >>> 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 :math:`u_{i0} = \dots = u_{ik} = 1`. In this case, the parametrization is simply .. math:: u_0 + \lambda_1 (u_1 - u_0) + \dots + \lambda_k (u_k - u_0), so in particular :math:`n(0,\dots,0) = u_0` and :math:`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 :math:`u_0` and :math:`u_1` (See `Restricting the domain`_) The other one is where :math:`u_{i0} = 1` and :math:`u_{i1} = \dots = u_{ik} = 0`. In this case, the parametrization is just .. math:: 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 :ref:`domains` for more information. .. doctest:: >>> 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: :py:func:`ddg.geometry.subspaces.dehomogenize_subspace` brings a subspace to the first above-mentioned special case :math:`u_{i0} = \dots = u_{ik} = 1` if none of the spanning vectors are at infinity. :py:func:`ddg.geometry.subspaces.orthonormalize_subspace` brings it to the second special case :math:`u_{i0} = 1,\ u_{i1} = \dots = u_{ik} = 0` with orthonormal :math:`u_1,\dots,u_k`. :py:func:`ddg.geometry.subspaces.center_subspace` Can set :math:`u_0` to be any point in the subspace. These functions are also available as methods :py:meth:`~ddg.geometry.subspaces.Subspace.orthonormalize`, :py:meth:`~ddg.geometry.subspaces.Subspace.center` and :py:meth:`~ddg.geometry.subspaces.Subspace.dehomogenize`. Visualization in Blender ======================== A guide on how to visualize ddg objects, including subspaces can be found :ref:`here `.