Source code for neofoam.framework.solver.runtime

# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2025 NeoFOAM authors

"""
SolverRuntime — mutable per-instance state for a SolverSpec.

Created by SolverSpec.instantiate(). Each call produces an independent
runtime so multiple solver runs never share mutable state.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    from .spec import SolverSpec
    from neofoam.framework.context import Context
    from neofoam.framework.operations import OperationCollection, StepBuilder


[docs] @dataclass class SolverState: """Mutable state container for a single solver run.""" core_models: list[Any] = field(default_factory=list) optional_models: list[Any] = field(default_factory=list) configs: dict[str, Any] = field(default_factory=dict)
[docs] @dataclass class SolverRuntime: """ One instantiation of a SolverSpec with its own mutable state. Owns per-instance state (models, configs, argv). All decorator- registered callables live on the spec; this object provides the public execution API (initialize, execution_graph, operations). """ spec: "SolverSpec" name: str argv: list[Any] = field(default_factory=list) state: SolverState = field(default_factory=SolverState) _config_instance: Any = field(default=None, repr=False) # ------------------------------------------------------------------ # State proxy properties # ------------------------------------------------------------------ @property def core_models(self) -> list[Any]: """Access core_models from state.""" return self.state.core_models @core_models.setter def core_models(self, value: list[Any]) -> None: self.state.core_models = value @property def optional_models(self) -> list[Any]: """Access optional_models from state.""" return self.state.optional_models @optional_models.setter def optional_models(self, value: list[Any]) -> None: self.state.optional_models = value @property def configs(self) -> dict[str, Any]: """Access configs from state.""" return self.state.configs @configs.setter def configs(self, value: dict[str, Any]) -> None: self.state.configs = value # ------------------------------------------------------------------ # Execution API # ------------------------------------------------------------------
[docs] def initialize(self) -> "Context": """Execute the registered initialization step.""" return self.spec._run_initialize(self)
[docs] def execution_graph( self, domain_name: Optional[str] = None ) -> tuple["StepBuilder", "OperationCollection"]: """Execute the registered execution graph step.""" return self.spec._run_execution_graph(self, domain_name)
@property def operations(self) -> "OperationCollection": """Build operations with this runtime as the self binding.""" return self.spec._build_operations_for(self)
[docs] def get_config(self) -> Any: """ Get solver configuration instance (lazy initialization). Used as a dependency provider: config: Annotated[MyConfig, Depends(solver.get_config)] """ if self._config_instance is None: if self.spec._config_class is None: raise RuntimeError(f"No config class defined for solver '{self.name}'") self._config_instance = self.spec._config_class() return self._config_instance