Source code for openimpala.session
"""Context manager for deterministic AMReX / MPI lifecycle management."""
from __future__ import annotations
def _has_core() -> bool:
"""Return True if the compiled C++ backend is importable."""
try:
import openimpala._core # noqa: F401
return True
except ImportError:
return False
[docs]
class Session:
"""Ensures AMReX ``initialize()`` / ``finalize()`` are called exactly once
and in the correct order, even when *mpi4py* is also in use.
Usage::
import openimpala as oi
with oi.Session():
vf = oi.volume_fraction(data, phase=0)
The session is re-entrant: nested ``with Session()`` blocks share the same
underlying AMReX state; only the outermost block triggers init/finalize.
When the compiled C++ backend (``_core``) is not available, the session
activates the pure-Python solver backend (SciPy / CuPy) automatically.
"""
_depth: int = 0 # nesting counter (class-level)
_pure_python: bool = False # True when _core is unavailable
def __enter__(self) -> "Session":
if Session._depth == 0:
self._do_initialize()
Session._depth += 1
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> None: # noqa: ANN001
Session._depth -= 1
if Session._depth == 0:
self._do_finalize()
return None # do not suppress exceptions
# ------------------------------------------------------------------
@staticmethod
def _do_initialize() -> None:
if _has_core():
# Import mpi4py first so MPI_Init happens before AMReX touches MPI.
try:
from mpi4py import MPI # noqa: F401
except ImportError:
pass
# Initialise AMReX natively via our C++ bindings — no pyamrex needed.
from openimpala._core import init_amrex
init_amrex()
Session._pure_python = False
# NOTE: HYPRE initialisation is handled automatically by the C++
# solver constructors (TortuosityHypre, EffectiveDiffusivityHypre)
# via std::call_once, so no Python-side HYPRE_Init() is needed.
else:
# No compiled backend — use pure-Python solver path.
Session._pure_python = True
# Verify we have at least scipy (required for the pure-Python solver)
try:
import scipy # noqa: F401
except ImportError:
raise ImportError(
"Neither the compiled C++ backend nor SciPy is available. "
"Install scipy (pip install scipy) for the pure-Python solver, "
"or install the full package (pip install openimpala-cuda) for "
"the C++ backend."
)
from . import _solver
backend = _solver.backend_name()
import sys
print(f"OpenImpala: using pure-Python solver backend ({backend})",
file=sys.stderr)
@staticmethod
def _do_finalize() -> None:
if Session._pure_python:
return # Nothing to tear down for pure-Python backend
import gc
from openimpala._core import amrex_initialized, finalize_amrex
if amrex_initialized():
# Force Python to destroy all orphaned C++ pybind11 objects NOW
gc.collect()
# Now it is safe to shut down the C++ backend
finalize_amrex()