Source code for pantr.grid._partition
"""Cell-ownership partition for distributing a structured grid.
A :class:`Partition` records, for every cell of a grid (or the knot-span grid of a
B-spline space), which rank owns it -- the serial, communication-free descriptor
consumed by the distributed-space machinery. It is produced either by consuming an
external partition (for example a dolfinx mesh) or by a native graph partitioner,
and is intentionally space-agnostic: it stores only an integer owner per cell.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
if TYPE_CHECKING:
import numpy.typing as npt
[docs]
class Partition:
"""A per-cell owner assignment over a grid's cells.
Records, for every cell, the rank that owns it -- or ``-1`` for an inactive cell
excluded from the partition (e.g. an exterior / trimmed cell). The owner array is
coerced to a read-only ``int32`` array on construction and the object is otherwise
immutable. Owners and counts are exposed through the :attr:`cell_owner`,
:attr:`n_parts`, :attr:`n_cells`, and :attr:`active_mask` properties.
"""
__slots__ = ("_cell_owner", "_n_parts")
[docs]
def __init__(self, cell_owner: npt.ArrayLike, n_parts: int) -> None:
"""Build a partition from a per-cell owner array.
Args:
cell_owner (npt.ArrayLike): Per-cell owner ranks (``-1`` for inactive
cells); coerced to a read-only 1D ``int32`` array.
n_parts (int): Number of parts (ranks); must be ``>= 1``.
Raises:
ValueError: If ``n_parts < 1``, ``cell_owner`` is not 1D integer, or any
owner is outside ``[-1, n_parts)``.
"""
if n_parts < 1:
raise ValueError(f"n_parts must be >= 1; got {n_parts}.")
owner = np.asarray(cell_owner)
if owner.ndim != 1 or not np.issubdtype(owner.dtype, np.integer):
raise ValueError("cell_owner must be a 1D integer array.")
owner = np.ascontiguousarray(owner, dtype=np.int32)
if owner.size and (int(owner.min()) < -1 or int(owner.max()) >= n_parts):
raise ValueError(
f"cell_owner values must lie in [-1, {n_parts}); "
f"got range [{int(owner.min())}, {int(owner.max())}]."
)
owner.flags.writeable = False
self._cell_owner = owner
self._n_parts = int(n_parts)
@property
def cell_owner(self) -> npt.NDArray[np.int32]:
"""Get the read-only per-cell owner array.
Returns:
npt.NDArray[np.int32]: ``(n_cells,)`` owners; ``-1`` for inactive cells.
"""
return self._cell_owner
@property
def n_parts(self) -> int:
"""Get the number of parts (ranks).
Returns:
int: The part count (``>= 1``).
"""
return self._n_parts
@property
def n_cells(self) -> int:
"""Get the total number of cells (active and inactive).
Returns:
int: Length of :attr:`cell_owner`.
"""
return int(self._cell_owner.shape[0])
@property
def active_mask(self) -> npt.NDArray[np.bool_]:
r"""Get a boolean mask of the active cells (owned by some rank).
Returns:
npt.NDArray[np.bool\_]: Fresh ``(n_cells,)`` mask; ``True`` where the cell
owner is not ``-1``.
"""
return self._cell_owner >= 0
[docs]
def owned_cells(self, rank: int) -> npt.NDArray[np.int64]:
"""Return the flat ids of the cells owned by ``rank``, ascending.
Args:
rank (int): Owner rank in ``[0, n_parts)``.
Returns:
npt.NDArray[np.int64]: Sorted cell ids with ``cell_owner == rank``.
Raises:
ValueError: If ``rank`` is outside ``[0, n_parts)``.
"""
if not 0 <= rank < self._n_parts:
raise ValueError(f"rank must be in [0, {self._n_parts}); got {rank}.")
return np.flatnonzero(self._cell_owner == rank).astype(np.int64)
__all__ = ["Partition"]