Domains

Domains are mainly used when creating nets. There are different types that all inherit from the Domain class, for example

or domains specified by their shape, for example

These classes are located in ddg.datastructures.nets.domain.

Creating a Smooth Domain

A smooth domain is given by an interval for each of its direction.

>>> 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 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 SmoothInterval instead:

>>> 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.

>>> 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:

>>> import ddg

>>> domain = ddg.nets.SmoothDomain([[0, 3], [0, 2 * np.pi, True]])
>>> domain.periodicity
{1}

Creating a Discrete Domain

A 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.

>>> 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:

>>> 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 ddg.datastructures.nets.utils. Possibilities are

Sampling

Sampling a SmoothDomain means choosing finitely (or countably) many of its points to obtain a discrete object.

Pyddg provides the function sample_smooth_domain. It takes a SmoothDomain and returns a DiscreteNet. The resulting DiscreteNet corresponds to a function \(\phi: K \to U\), where \(K \subseteq \mathbb{Z}^n\) is a discrete set of points, and \(U \subseteq \mathbb{R}^n\) is the SmoothDomain.

We restrict ourselves to rectangular domains. With this we can think of \(\phi\) as a map that places the grid \(K\) on (a subset of) \(U\) (see figures below).

Sampling Syntax

See the docstring of 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 sample_smooth_domain, e.g. [0.5, ''] or 0.5, it is applied to every direction of the domain.

Sampling Options

The following table gives an overview of the available options for the sampling argument of sample_smooth_domain or 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 \([0, 0.8]\times[0, 1.3]\).

../../_images/domain.png

Smooth domain \([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 \(n\) even pieces.

Let us divide \([0, 0.8]\) into 5 and \([0, 1.3]\) into 9 even pieces. This is done by using the sampling option 't' (“total number of samplings”) in each case.

>>> 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"]]
... )
../../_images/samplingtotal.png

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 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,'']])

>>> 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, ""])
../../_images/samplingstepsize.png

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:

>>> 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)
../../_images/samplingstepsizenotfitting.png

Sampled with sampling: 0.3

Depending on the tolerances we have passed to sample_smooth_domain the resulting discrete domain would be or would not be periodic if our smooth domain was. (more on periodicity below)

symmetric sampling

Symmetric sampling can be chosen with option 's' and a given step size. sample_smooth_domain then attempts to map the samples into the smooth domain symmetrically.

>>> 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"])
../../_images/samplingsymmetric.png

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.

>>> 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)
... )
../../_images/samplinganchored.png

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.

>>> 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

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 Sampling Options)

That is, in some cases the sampling of a periodic smooth domain will be a non-periodic discrete domain:

>>> 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 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:

>>> 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
>>> 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 The half-edge data structure object using the function 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.

>>> 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.