Source code for solprop.driver.executors.batch

# SPDX-FileCopyrightText: 2026 John C. Travers and contributors
#
# SPDX-License-Identifier: GPL-3.0-or-later

"""Dispatch compatible scan members through their selected lockstep backend."""

from collections.abc import Callable, Iterable, Iterator
from dataclasses import dataclass, field
from typing import cast

from solprop.exceptions import DriverError

__all__ = ["Batch"]


[docs] @dataclass class Batch: """Run fixed-schedule scan batches in the calling process. The builder selects the backend. Compatible adjacent points share a fixed march, while each retains its input, material tables, noise and statistics. Smaller groups are used at layout boundaries and when memory requires them. Adaptive simulations must first discover and calibrate a replay schedule. Attributes ---------- size : int Positive maximum number of simultaneously advancing members. memory_limit : int or None Optional positive host-memory limit in bytes, further restricted by the physical or scheduler allocation. Twenty percent remains as headroom. budgets : list of dict Admission calculations recorded for this invocation, in dispatch order. Resolved backend policies and the actual batch size are stored per point. """ size: int memory_limit: int | None = None budgets: list[dict[str, int | None]] = field(default_factory=list, init=False) def __post_init__(self) -> None: """Reject invalid bounds before constructing any simulations.""" for name, value in (("size", self.size), ("memory_limit", self.memory_limit)): if value is None and name == "memory_limit": continue if isinstance(value, bool) or not isinstance(value, int) or value < 1: raise DriverError(f"Batch {name} must be a positive integer")
[docs] def run[T, R](self, points: Iterable[T], work: Callable[[T], R]) -> Iterator[R]: """Execute scan work through its backend-preserving batch operation. Parameters ---------- points : iterable Globally indexed scan jobs, consumed with bounded lookahead. work : callable Scan worker exposing the common driver's batch operation. Yields ------ object Independently validated results, including failures and skipped points. Raises ------ DriverError If work does not implement scan batching or a contract cannot execute. """ operation = getattr(work, "run_batch", None) if not callable(operation): raise DriverError("Batch requires run_scan work with a batch operation") self.budgets.clear() yield from cast(Iterator[R], operation(points, self))