Skip to content

Run a model

Use async iteration when the application needs progress, intermediate results, or runtime controls. The iterator starts an open or ended simulation lazily.

import { Simulation, type FileContents } from "@swmmrs/swmmrs";

export async function observeRun(input: FileContents) {
    const simulation = await Simulation.open(input, {}, { threads: 1 });
    try {
        for await (const time of simulation.steps({ seconds: 60, strict: true })) {
            const status = await simulation.status();
            console.log(time, status.percentComplete);
        }

        const statistics = await simulation.statistics();
        const files = await simulation.finish();
        return { statistics, ...files };
    } finally {
        await simulation.close();
    }
}

seconds must be an integer from 1 through 2,147,483,647. Omit it to observe every routing step. strict defaults to true; exact intervals can shorten a routing step. Use strict: false only when whole routing-step advancement is the required behavior. See units and time.

steps() also accepts { saveResults }, which defaults to true. Set saveResults: false when report-period binary results are not needed. The final RunResults.output is then empty, while live reads and snapshots still work.

Advance manually

Call start() before the first manual step() or stride():

import { Simulation, type FileContents } from "@swmmrs/swmmrs";

export async function manualRun(input: FileContents) {
    const simulation = await Simulation.open(input, {}, { threads: 1 });
    try {
        await simulation.start({ saveResults: true });
        while (true) {
            const time = await simulation.stride(60); // strict defaults to true
            if (time === null) break;
            console.log(time);
        }
        return await simulation.finish();
    } finally {
        await simulation.close();
    }
}

step() advances one routing step. stride(seconds, strict = true) advances an observation interval. Both return null when the solver completes. A null result is completion, not another timestamp.

Stop or resume an interactive run

Breaking from an iterator releases its advancement ownership. It does not call finish() or close(). After the break, you can iterate again, manually advance a running model, read statistics, finish partial output, or close the owner.

To request a cooperative stop from another controller, call terminate() while the iterator is active. It ends the iterator at its next observation boundary. It does not interrupt a native operation already in progress:

import { Simulation, type FileContents } from "@swmmrs/swmmrs";

export async function stopAfterOneSample(input: FileContents) {
    const simulation = await Simulation.open(input, {}, { threads: 1 });
    try {
        const iterator = simulation.steps({ seconds: 60, strict: true });
        const sample = await iterator.next();
        if (!sample.done) simulation.terminate();
        await iterator.next();
        return await simulation.finish();
    } finally {
        await simulation.close();
    }
}

Read results and apply controls inside the loop, but do not call step(), stride(), start(), run(), end(), finalizeReport(), report(), or finish() there. An active iterator owns advancement, so those operations reject with LifecycleError.

Finalize and release

Natural exhaustion leaves the model complete, with system and per-object statistics available. finish() ends the run, flushes binary output, writes the requested detailed report tables when results are enabled, finalizes report text, and returns { report, output }. Repeating finish() in ended is safe.

end(), finalizeReport(), and report() expose those native stages separately. end() advances to ended with summary output; finalizeReport() only appends the runtime footer, and report() adds requested detailed tables. resetSolver() returns an open owner while retaining declared configuration. sleepWorkers() can put active Dynamic Wave workers to sleep while running.

The runtime footer is normally written at project close. JavaScript exposes finalizeReport() so it can flush that text before close() removes the worker's in-memory files. Python's caller-owned report path remains available after close, so it does not need the same explicit operation.

For a new owner that continues or branches a run, see Hotstarts, checkpoints, and forks.

Always await close() in a finally block. runSwmm() is the batch convenience that opens, runs, finalizes, and closes for you. Batch execution has no progress callback, AbortSignal, or hard cancellation. See the full lifecycle contract.