Source code for pina.domain.union
"""Module for the Union operation."""
import random
from .base_operation import BaseOperation
from ..label_tensor import LabelTensor
from ..utils import check_consistency
[docs]
class Union(BaseOperation):
r"""
Implementation of the union operation defined on a list of domains.
Given multiple sets :math:`A_1, A_2, \ldots, A_n`, define their union as:
.. math::
\bigcup_{i=1}^{n} A_i = \{x \mid \exists i: x \in A_i \}
:Example:
>>> cartesian1 = CartesianDomain({'x': [0, 1], 'y': [0, 1]})
>>> cartesian2 = CartesianDomain({'x': [0, 1], 'y': [1, 2]})
>>> union = Union([cartesian1, cartesian2])
"""
[docs]
def is_inside(self, point, check_border=False):
"""
Check if a point is inside the union of the domains.
:param LabelTensor point: The point to check.
:param bool check_border: If ``True``, the boundary is considered inside
the domain. Default is ``False``.
:raises ValueError: If ``point`` is not a :class:`LabelTensor`.
:raises ValueError: If the labels of ``point`` differ from the variables
of the domain.
:return: Whether the point is inside the domain or not.
:rtype: bool
"""
# Checks on point
check_consistency(point, LabelTensor)
if set(self.variables) != set(point.labels):
raise ValueError(
"Point labels differ from domain's dictionary labels. "
f"Got {sorted(point.labels)}, expected {self.variables}."
)
return any(g.is_inside(point, check_border) for g in self.geometries)
[docs]
def sample(self, n, mode="random", variables="all"):
"""
The sampling routine.
:param int n: The number of samples to generate.
:param str mode: The sampling method. Default is ``random``.
:param variables: The list of variables to sample. If ``all``, all
variables are sampled. Default is ``all``.
:type variables: list[str] | str
:raises AssertionError: If ``n`` is not a positive integer.
:raises ValueError: If the sampling mode is invalid.
:raises ValueError: If ``variables`` is neither ``all``, a string, nor a
list/tuple of strings.
:raises ValueError: If any of the specified variables is unknown.
:return: The sampled points.
:rtype: LabelTensor
"""
# Validate sampling settings
variables = self._validate_sampling(n, mode, variables)
# Compute number of points per geometry and remainder
num_pts, remainder = divmod(n, len(self.geometries))
# Shuffle indices
shuffled_geometries = random.sample(
range(len(self.geometries)), len(self.geometries)
)
# Precompute per-geometry allocations following the shuffled order
alloc = [num_pts + (i < remainder) for i in range(len(self.geometries))]
samples = []
# Iterate over geometries in shuffled order
for idx, gi in enumerate(shuffled_geometries):
# If no points to allocate (possible if len(self.geometries) > n)
if alloc[idx] == 0:
continue
# Sample points
pts = self.geometries[gi].sample(alloc[idx], mode, variables)
samples.append(pts)
return LabelTensor.cat(samples, dim=0)
[docs]
def partial(self):
"""
Return the boundary of the domain resulting from the operation.
:raises NotImplementedError: The :meth:`partial` method is not
implemented for union domains. Please operate on the individual
domains instead.
"""
raise NotImplementedError(
"The partial method is not implemented for union domains. "
"Please operate on the individual domains instead."
)