Architecture & execution flow¶
This page traces how a declarative model description becomes a concrete result, and explains the role of each component.
The class hierarchy¶
Model: what the system is: initial state, state update blocks, parameters.Simulation: how long and how many times: a model plustimestepsandruns.Experiment: a collection of simulations run together.Engine: how to execute: backend, processes, deepcopy, exception handling.
Simulation and Experiment both inherit from Executable, which holds the shared engine, results, exceptions, and lifecycle hooks. This is why a lone Simulation can be run directly or grouped into an Experiment: both are things the Engine knows how to run.
What happens when you call run()¶
Executable.run()delegates toEngine._run(executable=self).- The engine fires the
before_experimenthook and builds a run generator (_run_stream). _run_streamwalks the nested loops (for each simulation, each run, and each parameter subset), firing thebefore_simulation/before_run/before_subsethooks. It expands parameter sweeps viagenerate_parameter_sweepand yields one deepcopiedSimulationExecutionper unit of work. Deepcopying each unit is what makes them safe to run concurrently.- The engine selects a backend executor based on the chosen
Backendand callsexecute_runs(). - The backend distributes each
SimulationExecutionto a worker, wheremultiprocess_wrapperruns it (catching exceptions ifraise_exceptions=False). - Results stream back, are flattened and split into
resultsandexceptions, theafter_experimenthook fires, and the flat results list is returned.
run() → Engine._run → _run_stream (yields SimulationExecutions)
│
▼
Backend.execute_runs
│ (per worker process)
▼
multiprocess_wrapper → SimulationExecution.execute
Inside a single execution¶
A SimulationExecution represents exactly one run of one parameter subset. Its loop is defined abstractly in SimulationExecutionSpecification as a sequence of hook methods:
execute → for each timestep: before_step → step → after_step
step → for each block (substep): substep
substep → execute_policies → update_state (per variable)
Separating the loop structure (SimulationExecutionSpecification) from the behaviour (SimulationExecution) is a deliberate design choice: it makes the execution semantics explicit and lets you override any step by subclassing, for example to change the deepcopy method or instrument the loop.
Why backends are pluggable¶
Each unit of work is independent and self-contained, so how the units are executed is orthogonal to what they compute. radCAD can therefore offer single-process, local-multiprocessing, and distributed (Ray) backends behind one interface (Executor.execute_runs). See Choose a backend.
See also¶
- State mutation & performance: why each unit is deepcopied.
- Engine settings: the settings the engine exposes.