Note
Go to the end to download the full example code.
Register a new model¶
Declare a ModelSpec at module
import time, attach stage callbacks with decorators, and let a solver
detect it via the plugin system or load it directly.
Minimum spec¶
A model needs at least a @spec.load — it returns the per-instance
config the runtime will carry. That alone makes
turbulence.instantiate(case_dir, instance_id) work; the resulting
ModelRuntime exposes .config plus an empty operations
list.
from pathlib import Path
from typing import Any
from neofoam.framework.context import FieldUpdates
from neofoam.framework.initialization import (
ConfigContext,
InitializerBuilder,
InitStep,
)
from neofoam.framework.initialization.staged import LoadResult, StagedInitSpec
from neofoam.framework.model import Model
from neofoam.framework.solver import Solver
from neofoam.io import IOStrategy, YAML, BaseConfig
@IOStrategy(YAML("turbulence_config.yaml"))
class TurbulenceConfig(BaseConfig):
nut: float = 1e-3
turbulence = Model("Turbulence")
@turbulence.load
def _load(case_dir: Path, instance_id: str) -> TurbulenceConfig:
return TurbulenceConfig.load(case_dir=case_dir, validate=True)
Add operations¶
Use @spec.operation(...) to contribute to the solver’s DAG. The
first positional self is bound to the runtime; BaseConfig
parameters are injected from runtime.config by type; plain-typed
parameters (float, int, …) are looked up in ctx.fields
by name.
@turbulence.operation(operation_number="2.5", depends_on=["fields.U"])
def update_nut(self: Any, U: float, cfg: TurbulenceConfig) -> FieldUpdates:
return FieldUpdates({"nut": cfg.nut * abs(U)})
Optional stages¶
Three more decorators expand the spec when needed:
@spec.resolve(config, ctx)— adjustconfigafter every model has loaded but before any field is constructed. Use this to read cross-model decisions from theConfigContext.@spec.build(config)— returnlist[InitStep]to produce fields/operators during the BUILD stage. The runner adds them to the global init graph.@spec.detect()— returnTrueif the model should be loaded for this case. Used by plugin discovery; defaults toTrue.
@turbulence.resolve
def _resolve(config: TurbulenceConfig, ctx: ConfigContext) -> None:
# Read sibling configs from ctx, mutate this one if needed.
pass
@turbulence.build
def _build(config: TurbulenceConfig) -> list[InitStep]:
# Emit init steps for fields/operators owned by this model.
return []
Plug into a solver via StagedInit¶
Solvers don’t host the build stage directly — they delegate to a
StagedInitSpec that registers LOAD / RESOLVE / BUILD as
three independent callbacks. The build stage receives the core and
optional models the LOAD stage produced, hands them to an
InitializerBuilder, and returns the flat list of
InitStep objects the runner will execute.
add_core_models / add_optional_models accept anything
matching the BuildsInitSteps Protocol (one method:
run_build() -> list[InitStep]) — every ModelRuntime already
qualifies.
solver_spec = Solver("demo_solver")
init = StagedInitSpec.build("demo_solver")
@init.load
def _load() -> LoadResult:
return LoadResult(core_models=[], optional_models=[])
@init.build
def _solver_build(core_models: list[Any], optional_models: list[Any]) -> list[InitStep]:
builder = InitializerBuilder()
builder.add_core_models(core_models)
builder.add_optional_models(optional_models)
return builder.build()
See also¶
Model structure — why the
Spec/Runtimesplit exists.neofoam.framework.model — module API reference.
Total running time of the script: (0 minutes 0.002 seconds)