Source code for pantr._parallel

"""Thread-count control for PaNTr's parallel kernels.

Provides a thin wrapper around Numba's thread-pool configuration so that
users can limit (or disable) the parallelism used by PaNTr without
touching environment variables or Numba internals directly.

Typical usage::

    import pantr

    # Query / set globally
    pantr.set_num_threads(4)
    print(pantr.get_num_threads())

    # Scoped override (restores previous value on exit)
    with pantr.num_threads(1):
        result = space.tabulate_basis(pts)  # runs serially

    # Also throttle BLAS threads
    with pantr.num_threads(4, limit_blas=True):
        result = space.tabulate_basis(pts)
"""

from __future__ import annotations

import contextlib
import os
import threading
from collections.abc import Generator
from typing import Any, Final

import numba as nb
from threadpoolctl import threadpool_limits

_user_configured: Final[threading.Event] = threading.Event()
"""Set by the public setters; queried by default policies (e.g. the per-rank MPI
default in :mod:`pantr.mpi`) so they never override an explicit user decision."""


[docs] def get_num_threads() -> int: """Return the current number of threads used by parallel kernels. Returns: int: Active Numba thread-pool size. """ return int(nb.get_num_threads())
def _set_num_threads_raw(n: int) -> None: """Set the Numba thread count without marking it as explicit configuration. Internal hook for default policies (e.g. the per-rank default applied by :mod:`pantr.mpi`): performs the same validation as :func:`set_num_threads` but leaves the explicit-configuration flag untouched, so a policy-applied value remains distinguishable from a user decision. Args: n (int): Desired thread count. Must be >= 1 and at most ``numba.config.NUMBA_NUM_THREADS``. Raises: ValueError: If *n* is less than 1 or exceeds the maximum. """ max_threads: int = nb.config.NUMBA_NUM_THREADS if n < 1: raise ValueError(f"n must be >= 1, got {n}") if n > max_threads: raise ValueError(f"n must be <= NUMBA_NUM_THREADS ({max_threads}), got {n}") nb.set_num_threads(n)
[docs] def set_num_threads(n: int) -> None: """Set the number of threads used by parallel kernels. Calling this marks the thread count as explicitly configured: default policies (e.g. the per-rank MPI default in :mod:`pantr.mpi`) will never override it afterwards. Args: n (int): Desired thread count. Must be >= 1 and at most ``numba.config.NUMBA_NUM_THREADS``. Raises: ValueError: If *n* is less than 1 or exceeds the maximum. """ _set_num_threads_raw(n) _user_configured.set()
def _threads_explicitly_configured() -> bool: """Report whether the user explicitly configured the thread count. ``True`` when :func:`set_num_threads` was called directly, when the ``NUMBA_NUM_THREADS`` environment variable is set, or when the :func:`num_threads` context manager was used. The flag is set on both entry *and* exit of :func:`num_threads` (exit restores the previous count via :func:`set_num_threads`), so any use of that context manager permanently marks the process as explicitly configured for the rest of its lifetime. Returns: bool: Whether explicit thread configuration is in effect. """ return _user_configured.is_set() or "NUMBA_NUM_THREADS" in os.environ
[docs] @contextlib.contextmanager def num_threads(n: int, *, limit_blas: bool = False) -> Generator[None, None, None]: """Context manager that temporarily sets the parallel thread count. On entry the Numba thread-pool size is changed to *n*; on exit the previous value is restored. When *limit_blas* is ``True``, BLAS/LAPACK thread pools (OpenBLAS, MKL, ...) are also limited to *n* threads for the duration of the block, via ``threadpoolctl``. Args: n (int): Desired thread count for the block. limit_blas (bool): If ``True``, also limit BLAS/LAPACK threads via ``threadpoolctl``. Defaults to ``False``. Yields: None Note: Using this context manager permanently marks the thread count as explicitly configured (via :func:`set_num_threads` on both entry and exit). Default policies -- e.g. the per-rank MPI default in :mod:`pantr.mpi` -- will not override the thread count for the rest of the process lifetime, even after the ``with`` block exits. Example: >>> with pantr.num_threads(1): ... # all pantr operations run serially here ... pass """ prev = get_num_threads() set_num_threads(n) blas_ctx: Any = None if limit_blas: blas_ctx = threadpool_limits(limits=n) blas_ctx.__enter__() try: yield finally: if blas_ctx is not None: blas_ctx.__exit__(None, None, None) set_num_threads(prev)
__all__ = [ "get_num_threads", "num_threads", "set_num_threads", ]