Source code for pantr.mpi._create
"""Convenience factory for building an MPI-distributed space directly from a space.
Provides :func:`create_distributed_space`, the one-call counterpart to the explicit
``partition -> DistributedSpace`` flow: it derives the grid, partitions it, and wraps
the result, so a caller need not assemble the grid and :class:`~pantr.grid.Partition`
by hand.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from ..bspline import BsplineSpace, THBSplineSpace, coupling_graph, partition_graph
from ..grid import partition_grid, tensor_product_grid
from ._distributed_space import DistributedSpace
from ._thread_policy import _ensure_default_thread_policy
if TYPE_CHECKING:
import numpy.typing as npt
from ..grid import Grid
[docs]
def create_distributed_space( # noqa: PLR0913 -- public factory mirrors the partitioner args
global_space: BsplineSpace | THBSplineSpace,
comm: Any, # noqa: ANN401 -- an mpi4py.MPI.Comm; mpi4py is an optional, untyped dep
*,
method: str = "grid",
backend: str | None = None,
cell_weights: npt.ArrayLike | None = None,
cell_active: npt.ArrayLike | None = None,
) -> DistributedSpace:
"""Build an MPI-distributed space directly from a global space.
Convenience wrapper over the explicit flow (derive grid -> partition ->
:class:`DistributedSpace`). It derives the space's cell grid
(:func:`~pantr.grid.tensor_product_grid` for a :class:`~pantr.bspline.BsplineSpace`,
or :attr:`THBSplineSpace.grid <pantr.bspline.THBSplineSpace.grid>` for a hierarchical
space), partitions it across ``comm.size`` ranks, and returns the per-rank handle.
The partition is deterministic, so every rank computes the same one without
communication.
The explicit three-step flow remains available for full control; this factory just
removes the boilerplate for the common case.
Args:
global_space (BsplineSpace | THBSplineSpace): The global space to distribute,
identical on every rank. A ``BsplineSpace`` must be non-periodic.
comm (Any): An MPI communicator (e.g. ``mpi4py.MPI.COMM_WORLD``). Only its
``rank`` and ``size`` are read; it is duck-typed.
method (str): Partitioning strategy. ``"grid"`` (default) splits the cell grid
geometrically via :func:`~pantr.grid.partition_grid`; ``"graph"`` partitions
the cell-coupling graph (:func:`~pantr.bspline.coupling_graph` +
:func:`~pantr.bspline.partition_graph`) to minimize cross-rank DOF coupling.
backend (str | None): Partitioner backend. ``None`` (default) selects each
method's default -- ``"auto"`` for ``"grid"`` (``"block"`` or ``"rcb"``),
``"spectral"`` for ``"graph"`` (or ``"metis"``, needing the ``metis`` extra).
cell_weights (npt.ArrayLike | None): Per-cell assembly-cost weights. For
``"grid"`` they balance the geometric split; for ``"graph"`` they become the
coupling graph's vertex weights. Defaults to ``None`` (uniform).
cell_active (npt.ArrayLike | None): Boolean per-cell activity mask; inactive
cells get owner ``-1`` and drop out of the partition. Defaults to ``None``
(all active).
Returns:
DistributedSpace: The per-rank distributed-space handle.
Raises:
TypeError: If ``method="grid"`` and ``global_space`` is neither a
``BsplineSpace`` nor a ``THBSplineSpace``.
ValueError: If ``method`` is not ``"grid"`` or ``"graph"``; if ``backend`` is
invalid for the chosen method; or if the derived partition is incompatible
with ``comm`` (e.g. ``comm.size`` mismatch), as raised downstream.
Example:
>>> from mpi4py import MPI # doctest: +SKIP
>>> from pantr.bspline import create_uniform_space # doctest: +SKIP
>>> from pantr.mpi import create_distributed_space # doctest: +SKIP
>>> space = create_uniform_space([2, 2], [8, 8]) # doctest: +SKIP
>>> ds = create_distributed_space(space, MPI.COMM_WORLD) # doctest: +SKIP
"""
_ensure_default_thread_policy()
n_parts = int(comm.size)
if method == "grid":
grid: Grid
if isinstance(global_space, THBSplineSpace):
grid = global_space.grid
elif isinstance(global_space, BsplineSpace):
grid = tensor_product_grid(global_space)
else:
raise TypeError(
f"global_space must be a BsplineSpace or THBSplineSpace; "
f"got {type(global_space).__name__!r}."
)
partition = partition_grid(
grid,
n_parts,
backend="auto" if backend is None else backend,
cell_weights=cell_weights,
cell_active=cell_active,
)
elif method == "graph":
graph = coupling_graph(global_space, cell_weights=cell_weights)
partition = partition_graph(
graph,
n_parts,
backend="spectral" if backend is None else backend,
cell_active=cell_active,
)
else:
raise ValueError(f"method must be 'grid' or 'graph'; got {method!r}.")
return DistributedSpace(global_space, partition, comm)