Source code for sdom.parametric.worker

"""Multiprocessing worker function for SDOM parametric analysis.

This module contains the top-level worker function ``_run_single_case``.
It **must** be defined at module level (not nested or as a lambda) so that
``pickle`` can serialise it for ``ProcessPoolExecutor``.
"""

import copy
import logging

from ..optimization_main import initialize_model, run_solver
from .mutations import (
    _apply_scalar_mutation,
    _apply_storage_factor_mutation,
    _apply_ts_mutation,
)

logger = logging.getLogger(__name__)


[docs] def _run_single_case(case_dict: dict): """Evaluate one parameter combination by building and solving a fresh model. This is the worker function submitted to :class:`ProcessPoolExecutor`. Each invocation receives a fully self-contained description of the case; no shared state is required between processes. Parameters ---------- case_dict : dict Serialisable description of the case with the following keys: ``"data"`` The shared SDOM base data dict. A deep copy is made inside the worker so that mutations are isolated to this case and the original is not modified. This avoids creating all copies up-front in the parent process. ``"solver_config"`` Solver configuration dict from :func:`sdom.optimization_main.get_default_solver_config_dict`. ``"n_hours"`` Number of simulation hours. ``"case_name"`` Human-readable identifier for this combination, used as the ``case_name`` argument to :func:`sdom.optimization_main.run_solver`. ``"scalar_mutations"`` List of ``(data_key, param_name, value)`` triples to apply. ``"storage_factor_mutations"`` List of ``(param_name, factor)`` pairs to apply. ``"ts_mutations"`` List of ``(ts_key, factor)`` pairs to apply. Returns ------- sdom.results.OptimizationResults Results dataclass. ``is_optimal`` is ``False`` when the solver did not find a feasible solution or an exception was raised. """ from ..results import OptimizationResults case_name: str = case_dict["case_name"] data: dict = copy.deepcopy(case_dict["data"]) solver_config: dict = case_dict["solver_config"] n_hours: int = case_dict["n_hours"] # Apply mutations on the deep-copied data dict try: for data_key, param_name, value in case_dict.get("scalar_mutations", []): _apply_scalar_mutation(data, data_key, param_name, value) for param_name, factor in case_dict.get("storage_factor_mutations", []): _apply_storage_factor_mutation(data, param_name, factor) for ts_key, factor in case_dict.get("ts_mutations", []): _apply_ts_mutation(data, ts_key, factor) logger.info("Worker: initialising model for case '%s'", case_name) model = initialize_model(data, n_hours=n_hours, model_name=f"SDOM_{case_name}") logger.info("Worker: solving model for case '%s'", case_name) results = run_solver(model, solver_config, case_name=case_name) except Exception as exc: # noqa: BLE001 logger.error("Worker: case '%s' raised an exception: %s", case_name, exc, exc_info=True) results = OptimizationResults( termination_condition="exception", solver_status="error", gen_mix_target=float("nan"), total_cost=float("nan"), ) return results