ConcurrentScenarioRunner

class ConcurrentScenarioRunner @JvmOverloads constructor(name: String, scenarioList: List<Scenario> = emptyList(), val pathToOutputDirectory: Path = KSL.createSubDirectory(sanitizeForFilesystem(name) + "_OutputDir"), val kslDb: KSLDatabase = KSLDatabase("${sanitizeForFilesystem(name)}.db", pathToOutputDirectory), useScenarioOutputDirs: Boolean = true) : Identity(source)

Executes a list of Scenario instances concurrently and writes all results to a shared KSLDatabase.

Execution model

simulate operates in two phases:

Phase 1 — concurrent simulation. Each scenario is dispatched as an independent coroutine on SimulationDispatcher.default, a CPU-bounded dispatcher limited to Runtime.availableProcessors() threads. Every coroutine builds a fresh Model via scenario.modelBuilder, attaches an InMemorySnapshotCollector to the model's lifecycle emitters, and runs the simulation. All database-bound data is captured in memory during this phase; no database writes occur.

Phase 2 — sequential database commit. After all coroutines complete (awaitAll), the collected snapshots are written to kslDb one scenario at a time via SnapshotBatchWriter. Serialising the writes avoids concurrent SQLite access while keeping simulation time fully parallel.

Model isolation requirement

Every scenario submitted to this runner must use a ModelBuilderIfc that constructs a fully independent Model instance on each ModelBuilderIfc.build call. Scenarios built with the backward-compatible model: Model constructors of Scenario wrap a single shared instance and must not be used here — concurrent coroutines operating on the same model produce incorrect results and data races.

Error handling

If a simulation throws a RuntimeException, the failing scenario's coroutine catches the exception, logs it, and continues without affecting other concurrent scenarios. The failing scenario's Scenario.simulationRun will contain the error message via SimulationRun.runErrorMsg; its partial snapshots are not committed to the database.

Parameters

name

Runner name; also used as the default database file stem and output directory name.

scenarioList

Initial list of scenarios to register.

pathToOutputDirectory

Root directory under which per-scenario output sub-directories are created.

kslDb

Shared database that receives all scenario results.

useScenarioOutputDirs

When true (the default, preserving the original behaviour), every scenario gets its own subdirectory under pathToOutputDirectory named <scenarioName>_OutputDir; each subdir contains that scenario's kslOutput.txt and any per- scenario CSV / plot artifacts the model writes. When false, every per-scenario model writes directly into pathToOutputDirectory and the diagnostic log uses a scenario- distinguished filename (kslOutput_<scenarioName>.txt) so concurrent writers don't clash and re-runs overwrite cleanly. Parallel to ParallelDesignedExperiment.useDesignPointOutputDirs — same substrate-level knob shape for the per-item-subdir question that the two runners share.

Constructors

Link copied to clipboard
constructor(name: String, scenarioList: List<Scenario> = emptyList(), pathToOutputDirectory: Path = KSL.createSubDirectory(sanitizeForFilesystem(name) + "_OutputDir"), kslDb: KSLDatabase = KSLDatabase("${sanitizeForFilesystem(name)}.db", pathToOutputDirectory), useScenarioOutputDirs: Boolean = true)

Properties

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

Read-only ordered list of registered scenarios.

Link copied to clipboard

Read-only map of scenario name to Scenario.

Functions

Link copied to clipboard
fun addScenario(scenario: Scenario)

Registers scenario with this runner. The scenario name must be unique.

fun addScenario(modelBuilder: ModelBuilderIfc, name: String, inputs: Map<String, Double> = emptyMap(), runParameters: ExperimentRunParameters, stringInputs: Map<String, String> = emptyMap(), jsonInputs: Map<String, String> = emptyMap()): Scenario

Creates a Scenario from a ModelBuilderIfc and a full ExperimentRunParameters snapshot, registers it with this runner, and returns it.

Link copied to clipboard
fun cancelScenario(scenarioName: String): Boolean

Cancel a single scenario by name without stopping the rest of the sweep. Looks up the scenario's coroutine job and issues a cooperative cancellation against it. Returns true when a running scenario was found; false when the name is unknown, has already finished, or hasn't started yet.

Link copied to clipboard

Sets the number of replications to numReps for every registered scenario.

Link copied to clipboard

Returns a map of scenario name → per-replication observations for responseName across all executed scenarios. Scenarios not yet executed or producing no observations for the response are silently omitted.

Link copied to clipboard
fun print()

Prints basic half-width summary reports for each scenario to the console.

Link copied to clipboard

Returns the scenario with the given name, or null if none is registered.

Link copied to clipboard
suspend fun simulate(scenarios: IntProgression = myScenarios.indices, clearAllData: Boolean = true, onScenarioComplete: (scenarioName: String, snapshot: SimulationSnapshot.ExperimentCompleted?) -> Unit? = null, executionMode: ExecutionMode = ksl.app.config.ExecutionMode.CONCURRENT, onScenarioStart: (scenarioName: String, scenarioIndex: Int, totalScenarios: Int) -> Unit? = null, onReplicationStart: suspend (scenarioName: String, repNumber: Int, totalReps: Int) -> Unit? = null, onReplicationEnd: suspend (scenarioName: String, repNumber: Int, totalReps: Int) -> Unit? = null, onScenarioReplications: (scenarioName: String, snapshots: List<SimulationSnapshot.ReplicationCompleted>) -> Unit? = null, onScenarioReplicationsCompleted: (scenarioName: String, scenarioIndex: Int, totalScenarios: Int) -> Unit? = null)

Runs the selected scenarios concurrently and writes results to kslDb.

Link copied to clipboard
fun useIndependentRandomStreams(scenarios: IntProgression = myScenarios.indices, startingStreamAdvance: Int = 0, streamAdvanceSpacing: Int? = null)

Assigns non-overlapping pre-run sub-stream advances to selected scenarios.

Link copied to clipboard
fun write(out: PrintWriter = KSL.out)

Writes basic half-width summary reports for each scenario to out.