Source code for pantr.mpi

"""Optional MPI-parallel distribution layer for PaNTr.

This subpackage hosts the MPI-dependent and dolfinx-bridge code for distributing
B-spline and THB-spline spaces across ranks. It ships with every install of PaNTr but
is **backend-gated**: the serial core (:mod:`pantr.grid`, :mod:`pantr.bspline`, ...)
never imports it (enforced by an import-linter contract), and ``import pantr.mpi``
succeeds even when ``mpi4py`` is absent -- only helpers that genuinely need MPI raise at
call time, via :func:`require_mpi`.

``mpi4py`` is the optional *backend*, not extra PaNTr code: a plain ``pip install pantr``
is serial-only and MPI-free, while ``pip install "pantr[mpi]"`` simply installs ``mpi4py``
for you (equivalently ``pip install mpi4py``; an MPI library is also required).

Main exports:
- :data:`HAS_MPI`: whether ``mpi4py`` was importable at module load time.
- :func:`mpi_available`: live runtime check for ``mpi4py`` availability.
- :func:`require_mpi`: lazily import and return the ``mpi4py.MPI`` module, or raise.
- :func:`from_dolfinx`: build a :class:`pantr.grid.Partition` from a dolfinx mesh.
- :class:`DistributedSpace`: the per-rank handle to an MPI-distributed space.
- :func:`create_distributed_space`: build a :class:`DistributedSpace` from a space.
- :class:`DistributedFunction`: the per-rank handle to an MPI-distributed function.
- :func:`create_distributed_function`: build a :class:`DistributedFunction` from a function.
- :func:`configure_threads`: explicitly set this rank's Numba thread count.
- :func:`interpolate_bspline_distributed`: distributed B-spline collocation interpolation.
- :func:`fit_bspline_distributed`: distributed B-spline fit from pre-evaluated values.
- :func:`l2_project_bspline_distributed`: distributed B-spline L2 projection.
- :func:`quasi_interpolate_bspline_distributed`: distributed B-spline quasi-interpolation.
- :func:`quasi_interpolate_thb_spline_distributed`: distributed THB-spline quasi-interpolation.

A process-level thread policy coordinates MPI with PaNTr's Numba parallelism: the
first use of any MPI-engaging entry point limits this process to **one Numba thread
per rank**, unless threads were explicitly configured (``NUMBA_NUM_THREADS``,
``pantr.set_num_threads``, ``pantr.num_threads``, or :func:`configure_threads`).
Every new MPI entry point added to this package must call
``_ensure_default_thread_policy()`` from ``pantr.mpi._thread_policy`` before any
other work -- there is no structural chokepoint, since communicators are duck-typed.
"""

from __future__ import annotations

import importlib.util
from types import ModuleType
from typing import Final

from ._collocation import fit_bspline_distributed, interpolate_bspline_distributed
from ._create import create_distributed_space
from ._distributed_function import DistributedFunction, create_distributed_function
from ._distributed_space import DistributedSpace
from ._from_dolfinx import from_dolfinx
from ._l2 import l2_project_bspline_distributed
from ._qi import quasi_interpolate_bspline_distributed
from ._thb_qi import quasi_interpolate_thb_spline_distributed
from ._thread_policy import configure_threads


[docs] def mpi_available() -> bool: """Report whether ``mpi4py`` can be imported in this environment. Uses :func:`importlib.util.find_spec`, so it never triggers an actual import or MPI initialization. Returns: bool: ``True`` if ``mpi4py`` is installed and importable, ``False`` otherwise. """ return importlib.util.find_spec("mpi4py") is not None
HAS_MPI: Final[bool] = mpi_available() """bool: Whether ``mpi4py`` was importable at module load time. This value is frozen at import and will not reflect environment changes made afterwards. Use :func:`mpi_available` for a live probe. In code paths that actually need MPI, always call :func:`require_mpi` — it checks freshly and raises a clear error if MPI is unavailable or broken. """
[docs] def require_mpi() -> ModuleType: """Lazily import and return the ``mpi4py.MPI`` module, or raise a clear error. Call this from code paths that genuinely need MPI. Keeping the import lazy lets the serial core and a bare ``import pantr.mpi`` work without ``mpi4py``. Returns: ModuleType: The imported ``mpi4py.MPI`` module. Raises: ImportError: If ``mpi4py`` is not installed or fails to load (e.g. the underlying MPI runtime library is missing or ABI-incompatible). """ if not mpi_available(): raise ImportError( "pantr.mpi requires 'mpi4py', which is not installed. Install the optional " "MPI extra with 'pip install \"pantr[mpi]\"', or 'pip install mpi4py', in an " "environment with an MPI library." ) try: return importlib.import_module("mpi4py.MPI") except Exception as exc: err = ImportError("pantr.mpi found 'mpi4py' but failed to import 'mpi4py.MPI'.") err.add_note("The MPI runtime library (e.g. libmpi.so) may be missing or ABI-incompatible.") err.add_note("Fix: install an MPI library (e.g. 'conda install openmpi').") err.add_note( "Fix: reinstall mpi4py against it (e.g. 'pip install --no-binary mpi4py mpi4py')." ) raise err from exc
__all__ = [ "HAS_MPI", "DistributedFunction", "DistributedSpace", "configure_threads", "create_distributed_function", "create_distributed_space", "fit_bspline_distributed", "from_dolfinx", "interpolate_bspline_distributed", "l2_project_bspline_distributed", "mpi_available", "quasi_interpolate_bspline_distributed", "quasi_interpolate_thb_spline_distributed", "require_mpi", ]