Runner

Trial preparation, simulation specs, and the parallel runner.

ModelManager.SimulationProcessType
SimulationProcess

Holds the outcome of a single simulation run.

Fields

  • simulation::Simulation
  • monad_id::Int
  • process::Union{Nothing,Base.Process}: the local process, or nothing when the simulation ran as a SLURM job (there is no local process to hold) or no command could be built.
  • success::Bool
  • cmd::Union{Nothing,Cmd}: the command simulationCommand returned, or nothing if it could not build one.

process alone cannot tell a simulator hook what happened, because it is nothing for two unrelated reasons: a SLURM job (which ran, elsewhere) and a simulation that never had a command. cmd separates them — isnothing(cmd) means nothing was ever launched — and it is also the field to print when reporting a failure, since it is the simulator's own command on both paths rather than the sbatch wrapper.

source
ModelManager.SimulationSpecType
SimulationSpec

A pending simulation to be launched. Produced by pendingSimulationSpecs and consumed by run, which wraps each spec in a @task that calls runSimulation on the active simulator.

monad_id is always a real monad ID — prepareTrialHierarchy always runs before spec collection, so setup is guaranteed to have completed.

Fields

  • simulation::Simulation: The simulation to launch.
  • monad_id::Int: ID of the enclosing monad. setupMonad has already run for this monad before the spec was built.
source
Base.runMethod
run(T::AbstractTrial; quiet=false, kwargs...) -> MMOutput

Run all pending simulations in T and return an MMOutput.

Keyword arguments

  • quiet::Bool=false: when true, suppresses per-simulation and per-trial console output. Per-sim "Running simulation: N..." lines, the leading "Running ..." header, and the trailing "Finished ..." block are all gated by this flag. Used by ABC-SMC calibration to keep console output focused on per-generation progress.

  • on_progress::Union{Nothing,Function}=nothing: optional progress hook. When supplied, it is called as on_progress(:init, n_simulation_tasks) once after the pending simulation count is known, on_progress(:step, 1) after each simulation completes, and on_progress(:finish, n_success) once at the end. When nothing (default) the runner behaves exactly as before — this keeps the per-simulation completion loop framework- agnostic while letting callers (e.g. ABC-SMC calibration) render a live progress bar.

  • post_processor::Union{Nothing,Function}=nothing: optional user hook run once per successfully completed simulation, after the simulator's non-destructive postSimulationProcessing and before its destructive postSimulationCleanup — so the callback always sees the intact (but processed) output folder. It is called as post_processor(simulation::Simulation) — the same argument a QoI's compute receives, so one measurement function serves the sink, sensitivity analysis and calibration alike. A QoI or a vector of them may be passed instead of a function. Use simulationID and pathToOutputFolder(simulation) rather than reaching into fields; the hook only fires for simulations that succeeded, and the owning monad is only(monadIDs(simulation)) (which queries the database, and throws if the simulation is gone); reading the actual simulation output into usable data is the responsibility of the user or the simulator package (e.g. PhysiCellModelManager loaders keyed by simulationID). Its return value determines storage:

    • nothing → nothing is stored (pure side effects).
    • a NamedTuple or AbstractDict of name => scalar → one row keyed by simulation_id is upserted into the project's post-processing sink (data/outputs/postprocessing.db), readable via postProcessingTable. Each key becomes the column "<qoi name>.<key>", so two measurements that both report a tumor stay separate; a scalar return uses the name alone. Columns grow dynamically; sims lacking a given quantity have NULL.
    • any other type → an ArgumentError is thrown.

    Because every column is named after the QoI that wrote it, a bare anonymous function that stores anything is refused: its derived name is a gensym that changes between sessions, so the same script would write a fresh, half-empty set of columns each run. Wrap it — QoI("counts", sim -> …) — or pass a named function. A callback returning nothing is unaffected. The callback runs inside the per-simulation worker task (so heavy compute parallelizes), but all sink writes are serialized in the main completion loop; user code never touches the sink DB directly. post_processor is not forwarded to the simulator hooks. If the callback (or a simulator hook) throws, run fails fast: it rethrows a clear error naming the stage and simulation with the original stacktrace — it never hangs or swallows the exception.

  • All other kwargs flow through to prepareTrialHierarchy (which forwards them to the simulator's setupSampling / setupMonad hooks) and to both postSimulationProcessing and postSimulationCleanup (e.g. prune_options). Any simulator-specific flags flow through this channel. runSimulation takes no kwargs.

  • run_kwargs::NamedTuple=(;): simulator options as a bundle, equivalent to passing them loosely. Calibration must bundle — runABC and resumeCalibration spend their keyword splat on ABCSMC fields — so accepting the bundle here means one assembled once is portable to any entry point. Where a key appears both ways, the loose keyword wins.

source
ModelManager.monadIDMethod
monadID(simulation_process::SimulationProcess)

Return the ID of the monad enclosing this simulation.

source
ModelManager.prepareTrialHierarchyMethod
prepareTrialHierarchy(T::AbstractTrial; kwargs...) → Bool

Recurse down the trial hierarchy, creating output folders and calling the simulator's setupSampling and setupMonad hooks. Returns true on success, false if any hook fails (in which case the remaining hierarchy is skipped).

kwargs are forwarded to both hooks — any simulator-specific flags flow through this channel. This function has no knowledge of console output and does not touch simulation status codes.

Dispatch behaviour:

  • AbstractMonad (Simulation or Monad): mkpath + setupSampling on M (compile code, etc.) + setupMonad on M (prepare varied input folders).
  • Sampling: mkpath + setupSampling once for the whole sampling + mkpath and setupMonad for each constituent monad. setupSampling is called only once, not once-per-monad.
  • Trial: mkpath + recurse into each sampling.
source
ModelManager.runSimulationMethod
runSimulation(sim::AbstractSimulator, spec::SimulationSpec) → SimulationProcess

Run the simulation described by spec and report how it went. This default asks the backend for the command via simulationCommand and does everything else: it creates the simulation's output folder, sends stdout and stderr to output.log and output.err there, runs the command in simulatorDir (or the Cmd's own dir), and – when run_on_hpc is set – submits it as a SLURM job instead and waits for it. Called by run inside each worker task.

A backend only overrides this if its simulation is not an external process; for those, SimulationProcess.process may be nothing.

simulationCommand may return nothing to say no command could be built for this simulation; that is recorded as a failed simulation and the rest of the trial continues.

Otherwise the command must be a bare Cmd. Redirections and pipelines are added here, so a pipeline(...) is rejected; and it must not carry an environment (Cmd(...; env=...), setenv, addenv). Julia's Cmd.env replaces the environment, so locally the simulation would see only the variables listed; as a SLURM job the environment is not forwarded at all and the job inherits the submitting one. The same command would mean two different things, so it is refused rather than made silently path-dependent. Put what the simulation needs in the command's arguments or its working directory.

A local process that fails to start (missing executable, unwritable folder) is recorded as a failed simulation, not raised: one broken simulation should not abort a campaign of thousands. A process killed by a signal is also a failure – Julia reports exitcode == 0 for those, so the check is success(p), not the exit code.

source
ModelManager.simulationIDMethod
simulationID(simulation::Simulation)
simulationID(simulation_process::SimulationProcess)

Return a simulation's ID.

A post_processor (see run) is called with a Simulation, so that is the form to use in one — simulationID(sim) rather than reaching for sim.id. The SimulationProcess method serves the runner and the AbstractSimulator hooks (postSimulationProcessing, postSimulationCleanup), which still receive that type.

source