Source code for modelly.calibrate.pool
"""A process executor that keeps its workers between batches.
ABC-SMC, simulated moments and Bayesian optimisation submit many small
batches. The experiments' ``processes`` executor starts a fresh pool for each
``run`` call (workers start with ``forkserver`` or ``spawn`` and import the
model: about a second each time); this one starts it once and reuses it until
``close``. Its outcomes are the same, task for task.
"""
from __future__ import annotations
from collections.abc import Iterator, Sequence
from concurrent.futures import ProcessPoolExecutor, as_completed
from modelly.batch.runs import default_workers, worker_context
from modelly.experiments.executors.local import ProcessExecutor
from modelly.experiments.tasks import RunOutcome, RunTask, run_task
[docs]
class PooledProcessExecutor(ProcessExecutor):
"""``processes``, with one pool for every ``run`` call until ``close``."""
def __init__(self, **options) -> None:
"""No pool until the first batch."""
super().__init__(**options)
self._pool: ProcessPoolExecutor | None = None
[docs]
def run(
self, tasks: Sequence[RunTask], max_parallel: int | None = None
) -> Iterator[RunOutcome]:
"""Run the tasks on the shared pool (sized by the first call's ``max_parallel``)."""
if not tasks:
return
self.check(tasks)
if self._pool is None:
workers = default_workers() if max_parallel is None else max_parallel
self._pool = ProcessPoolExecutor(
max_workers=max(1, workers), mp_context=worker_context()
)
futures = [self._pool.submit(run_task, task) for task in tasks]
for future in as_completed(futures):
yield future.result()
[docs]
def close(self) -> None:
"""Stop the workers."""
if self._pool is not None:
self._pool.shutdown(wait=True, cancel_futures=True)
self._pool = None