.. _domains: Domains ======= Domains are mainly used when creating :ref:`nets `. There are different types that all inherit from the :py:class:`~ddg.datastructures.nets.domain.Domain` class, for example - :py:class:`~ddg.datastructures.nets.domain.EmptyDomain` - :py:class:`~ddg.datastructures.nets.domain.SmoothDomain` - :py:class:`~ddg.datastructures.nets.domain.DiscreteDomain` - :py:class:`~ddg.datastructures.nets.domain.SmoothInterval` - :py:class:`~ddg.datastructures.nets.domain.DiscreteInterval` or domains specified by their shape, for example - :py:class:`~ddg.datastructures.nets.domain.SmoothRectangularDomain` - :py:class:`~ddg.datastructures.nets.domain.DiscreteRectangularDomain` - :py:class:`~ddg.datastructures.nets.domain.DiscreteTriangularDomain` - :py:class:`~ddg.datastructures.nets.domain.DiscreteDiagonalDomain` These classes are located in :py:mod:`ddg.datastructures.nets.domain`. .. contents:: Table of contents :local: :backlinks: none Creating a Smooth Domain ------------------------ A smooth domain is given by an interval for each of its direction. .. doctest:: >>> import ddg >>> import numpy as np >>> domain = ddg.nets.SmoothDomain([[0, 4], [-np.pi, np.pi]]) >>> domain.bounded True >>> domain.intervals [[0.0, 4.0], [-3.141592653589793, 3.141592653589793]] >>> domain.periodicity set() Note that :py:class:`~ddg.datastructures.nets.domain.SmoothDomain` always expects a list of intervals to be passed to it, even if the domain is supposed to be 1-dimensional. To circumvent this, use :py:class:`~ddg.datastructures.nets.domain.SmoothInterval` instead: .. doctest:: >>> import ddg >>> domain = ddg.nets.SmoothInterval([0.0, 4.0]) >>> domain.interval [0.0, 4.0] You can also create both unbounded and periodic domains. .. doctest:: >>> import ddg >>> import numpy as np >>> domain = ddg.nets.SmoothDomain([[-np.inf, 0], [0, 2]]) >>> domain.bounded False >>> domain.unbounded_directions [0] To mark a direction as periodic, simply add ``True`` as the third entry of its interval: .. doctest:: >>> import ddg >>> domain = ddg.nets.SmoothDomain([[0, 3], [0, 2 * np.pi, True]]) >>> domain.periodicity {1} Creating a Discrete Domain -------------------------- A :py:class:`~ddg.datastructures.nets.domain.DiscreteDomain` behaves very much the same as a smooth one. They have all properties of a smooth domain, but additionally contain the combinatorial information of themselves, i.e. their edge and face data. These are used primarily in the conversion to a blender mesh. .. doctest:: >>> import ddg >>> domain = ddg.nets.DiscreteDomain([[0, 4], [-1, 10]]) >>> domain.intervals [[0, 4], [-1, 10]] Moreover we can iterate over all points inside of a discrete domain with the help of its traverser: .. doctest:: >>> import ddg >>> domain = ddg.nets.DiscreteDomain([[0, 1], [0, 1]]) >>> for i in domain.traverser: ... print(i) ... (0, 0) (0, 1) (1, 0) (1, 1) >>> for i in domain.traverser: ... print(domain.traverser.idx(*i)) ... 0 1 2 3 >>> for i in domain.edge_data: ... print(i) ... (0, 2) (0, 1) (1, 3) (2, 3) >>> for i in domain.face_data: ... print(i) ... (0, 2, 3, 1) Modifying a Domain ------------------ A domain can be modifyied by utility functions located at :py:mod:`ddg.datastructures.nets.utils`. Possibilities are - :py:func:`~ddg.datastructures.nets.utils.shrink_domain` - :py:func:`~ddg.datastructures.nets.utils.bound_domain` - :py:func:`~ddg.datastructures.nets.utils.create_subdomain` - :py:func:`~ddg.datastructures.nets.utils.delete_direction` - :py:func:`~ddg.datastructures.nets.utils.modify_direction` .. _sampling_a_smooth_domain: Sampling -------- Sampling a :py:class:`~ddg.datastructures.nets.domain.SmoothDomain` means choosing finitely (or countably) many of its points to obtain a discrete object. Pyddg provides the function `~ddg.datastructures.nets.conversion.sample_smooth_domain`. It takes a :py:class:`~ddg.datastructures.nets.domain.SmoothDomain` and returns a :py:class:`~ddg.datastructures.nets.net.DiscreteNet`. The resulting :py:class:`~ddg.datastructures.nets.net.DiscreteNet` corresponds to a function :math:`\phi: K \to U`, where :math:`K \subseteq \mathbb{Z}^n` is a discrete set of points, and :math:`U \subseteq \mathbb{R}^n` is the :py:class:`~ddg.datastructures.nets.domain.SmoothDomain`. We restrict ourselves to *rectangular* domains. With this we can think of :math:`\phi` as a map that places the grid :math:`K` on (a subset of) :math:`U` (see figures below). Sampling Syntax ^^^^^^^^^^^^^^^ See the docstring of `~ddg.datastructures.nets.conversion.sample_smooth_domain` for a full description of the syntax. The **general syntax** of a sampling is a `list` with entries ``[value, option]``, which specify the sampling in **each direction**. Here, ``value`` is an `int` or a `float` and ``option`` is the sampling option (see below). Exceptions are the option **compound sampling** ``'c'``, where the syntax is ``[float, int, option]`` and **stepsize** ``''``, where the sampling *can* be given as a single argument ``float``. .. note:: If we only specify one sampling option in `~ddg.datastructures.nets.conversion.sample_smooth_domain`, e.g. ``[0.5, '']`` or ``0.5``, it is applied to every direction of the domain. .. _sampling_options: Sampling Options ^^^^^^^^^^^^^^^^ The following table gives an overview of the available options for the `sampling` argument of `~ddg.datastructures.nets.conversion.sample_smooth_domain` or `~ddg.datastructures.nets.conversion.sample_smooth_net`. For a detailed explanation of the options, see below. +-----------+---------------+-----------+------------+--------------+ | options | '' | 't' | 'c' | 's' | +===========+===============+===========+============+==============+ | meaning | stepsize | total | compound | symmetric | +-----------+---------------+-----------+------------+--------------+ | bounded | yes | yes | yes | yes | +-----------+---------------+-----------+------------+--------------+ | unbounded | yes | no | yes | no | +-----------+---------------+-----------+------------+--------------+ | type | float | int | float, int | float | +-----------+---------------+-----------+------------+--------------+ | periodic | can preserve | preserves | preserves | can preserve | +-----------+---------------+-----------+------------+--------------+ | anchor | yes | no | no | no | +-----------+---------------+-----------+------------+--------------+ Consider the smooth domain :math:`[0, 0.8]\times[0, 1.3]`. .. figure:: images/domain.png :align: center :width: 400px :figwidth: image Smooth domain :math:`[0, 0.8]\times[0, 1.3]` total amount of samples """"""""""""""""""""""" One way to define a sampling is to divide each direction of the domain into :math:`n` even pieces. Let us divide :math:`[0, 0.8]` into 5 and :math:`[0, 1.3]` into 9 even pieces. This is done by using the sampling option `'t'` ("total number of samplings") in each case. .. doctest:: >>> import ddg >>> import ddg.datastructures.nets as net >>> smoothdomain = net.domain.SmoothDomain([[0, 0.8], [0, 1.3]]) >>> sampled = net.conversion.sample_smooth_domain( ... smoothdomain, [[5, "t"], [9, "t"]] ... ) .. figure:: images/samplingtotal.png :align: center :width: 400px :figwidth: image Sampled with sampling: `[[5, 't'], [9, 't']]` If the domain is **unbounded** in a direction, the option `'t'` will not work, since it divides the direction into even pieces. For unbounded directions, you can use the **step size** option. step size """"""""" Since this option works for all domains, it is the default that `~ddg.datastructures.nets.conversion.sample_smooth_domain` uses, if none is specified. In this case, we can - pass a float for each direction: `sample_smooth_domain(smoothdomain, [.1, .2])` - choose the option `''`: `sample_smooth_domain(smoothdomain, [[.1, ''], [.2,'']])` .. doctest:: >>> import ddg >>> import ddg.datastructures.nets as net >>> smoothdomain = net.domain.SmoothDomain([[0, 0.8], [0, 1.3]]) >>> sampled = net.conversion.sample_smooth_domain(smoothdomain, [0.1, ""]) .. figure:: images/samplingstepsize.png :align: center :width: 400px :figwidth: image Sampled with sampling: `[0.1, '']` If the step size is chosen so that it **does not divide the length** of the domain, the sampling will **not cover** the entire domain: .. doctest:: >>> import ddg >>> import ddg.datastructures.nets as net >>> smoothdomain = net.domain.SmoothDomain([[0, 0.8], [0, 1.3]]) >>> sampled = net.conversion.sample_smooth_domain(smoothdomain, 0.3) .. figure:: images/samplingstepsizenotfitting.png :align: center :width: 400px :figwidth: image Sampled with sampling: `0.3` Depending on the tolerances we have passed to `~ddg.datastructures.nets.conversion.sample_smooth_domain` the resulting discrete domain would be or would not be **periodic** if our smooth domain was. (more on periodicity :ref:`below`) symmetric sampling """""""""""""""""" Symmetric sampling can be chosen with option `'s'` and a given step size. `~ddg.datastructures.nets.conversion.sample_smooth_domain` then attempts to map the samples into the smooth domain symmetrically. .. doctest:: >>> import ddg >>> import ddg.datastructures.nets as net >>> smoothdomain = net.domain.SmoothDomain([[0, 0.8], [0, 1.3]]) >>> sampled = net.conversion.sample_smooth_domain(smoothdomain, [0.3, "s"]) .. figure:: images/samplingsymmetric.png :align: center :width: 400px :figwidth: image Sampled with sampling: `[0.3, 's']` anchor """""" Sometimes we want a specific point of our smooth domain to be a sample. For this we can use the *anchor* keyword argument. It is only compatible with step size and symmetric sampling. .. doctest:: >>> import ddg >>> import ddg.datastructures.nets as net >>> smoothdomain = net.domain.SmoothDomain([[0, 0.8], [0, 1.3]]) >>> sampled = net.conversion.sample_smooth_domain( ... smoothdomain, 0.2, anchor=(0.25, 0.35) ... ) .. figure:: images/samplinganchored.png :align: center :width: 400px :figwidth: image Sampled with sampling: `0.2, anchor=(0.25, 0.35)` compound sampling """"""""""""""""" Compound samping can be chosen with `'c'`. It **combines** the sampling forms 'step size' and 'total amount' and is given in the form `[stepsize, total, 'c']`. For **unbounded directions** it generates a `total` number of samples `stepsize` apart, starting from either the lower bound/upper bound or 0 (depending on whether the direction is bounded from below/above or not at all). In a **bounded direction**, it behaves as the option `'t'` would. .. doctest:: >>> import numpy as np >>> import ddg >>> import ddg.datastructures.nets as net >>> smoothdomain = net.domain.SmoothDomain([[0, np.inf], [0, 1.3]]) >>> sampled = net.conversion.sample_smooth_domain(smoothdomain, [0.1, 11, "c"]) >>> sampled.domain DiscreteRectangularDomain([0, 10], [0, 10]) .. _periodicity_sampling: periodicity """"""""""" Periodic smooth domains appear for example in closed curves or surfaces of revolution. Since the domain has to be bounded in the direction that it is periodic in, all sampling options can be applied to the direction. However: .. note:: Not all sampling options will preserve periodicity. (See :ref:`sampling_options`) That is, in some cases the sampling of a periodic smooth domain will be a non-periodic discrete domain: .. doctest:: >>> import numpy as np >>> import ddg >>> import ddg.datastructures.nets as net >>> smoothdomain = ddg.nets.SmoothDomain( ... [ ... [0, 1, True], ... ] ... ) >>> sampled = net.conversion.sample_smooth_domain(smoothdomain, 0.3) >>> sampled.domain.periodic False The periodicity breaks for two reasons: The **step size is too large**: it caused the last sample to not end up close enough to the first one (close in terms of `atol` passed to `~ddg.datastructures.nets.conversion.sample_smooth_domain`) The **step size does not divide the length** of the direction: if the stepsize divides the length of the direction, it will fit perfectly, no matter how large Fix either of these two points to preserve periodicity: .. doctest:: >>> import numpy as np >>> import ddg >>> import ddg.datastructures.nets as net >>> smoothdomain = ddg.nets.SmoothDomain( ... [ ... [0, 1, True], ... ] ... ) >>> sampled = net.conversion.sample_smooth_domain(smoothdomain, 0.10000000000000001) >>> sampled.domain.periodic True .. doctest:: >>> import numpy as np >>> import ddg >>> import ddg.datastructures.nets as net >>> smoothdomain = ddg.nets.SmoothDomain( ... [ ... [0, 1, True], ... ] ... ) >>> sampled = net.conversion.sample_smooth_domain(smoothdomain, 0.5) >>> sampled.domain.periodic True Conversion to Half-edge ----------------------- Bounded discrete domains of dimension 2 or less can be converted to a :ref:`half_edge` object using the function :py:func:`ddg.conversion.halfedge.nets.discrete_domain_to_halfedge`. The vertices of the resulting halfedge object will have an attribute `co` containing the value from domain.traverser and the order will be the same. .. doctest:: >>> from ddg.conversion.halfedge.nets import discrete_domain_to_halfedge >>> domain = ddg.nets.DiscreteDomain([[0, 1], [0, 1]]) >>> surface = discrete_domain_to_halfedge(domain) >>> for c in domain.traverser: ... print(c) ... (0, 0) (0, 1) (1, 0) (1, 1) >>> for v in surface.verts: ... print(v.co) ... (0, 0) (0, 1) (1, 0) (1, 1) The name of the coordinate attribute can be changed using the `co_attr` argument of the conversion function.