Advanced forcing#
Note
Engine: OpenSWMM 6 — refactored. Documents
openswmm.engine.Forcing.
The Forcing class is OpenSWMM 6’s purpose-built API for
runtime overrides. Per-step setters on the domain classes
(Nodes.set_lateral_inflow(), Gages.set_rainfall(), …) are
one-shot — overwritten by the engine on the next step. Forcing
overrides are persistent until cleared, and they support multiple
modes (replace, add, scale).
The Forcing API is the right tool for:
Driving a model from a Python source (ML controller, telemetry, user-supplied hyetograph) without re-applying the value every step.
Combining multiple forcings on the same target (e.g. baseline + perturbation).
Sweeping scenarios where a forcing differs only in one parameter.
Reference: openswmm_forcing.h.
Class signature#
class Forcing:
def __init__(self, solver: Solver) -> None: ...
Forcing modes#
Every setter takes a ForcingMode:
Mode |
Effect on the engine’s value at each step |
|---|---|
|
Overwrite the engine value with the forcing value. |
|
Add the forcing value to the engine value. |
|
Multiply the engine value by the forcing value. |
The persist argument is a boolean: True keeps the forcing
active across steps until you clear() it; False applies it
once and lets it lapse.
Key methods#
Per-target setters#
Method |
Targets |
|---|---|
|
Lateral inflow at a node. |
|
Head boundary at an outfall. |
|
Pollutant |
|
Force a flow on a link. |
|
Force a control setting on a link (orifice, weir, pump). |
|
Override rainfall on a subcatchment. |
|
Override evaporation on a subcatchment. |
|
Override rainfall at a gage. |
All methods accept either a string id or an integer index for idx.
Clearing#
Method |
Action |
|---|---|
|
Remove all forcings on the given (target type, index). |
|
Remove every active forcing. |
The target_type argument is a ForcingTarget enum value.
End-to-end example#
from openswmm.engine import (
Solver, Nodes, Forcing, ForcingMode, EngineState,
)
with Solver("model.inp", "model.rpt", "model.out") as s:
nodes = Nodes(s)
forcing = Forcing(s)
j1 = nodes.get_index("J1")
# Persistent baseline lateral inflow
forcing.node_lat_inflow(j1, 0.5, ForcingMode.REPLACE, persist=True)
# … plus a 2-hour pulse around the storm peak
applied_pulse = False
while s.state == EngineState.RUNNING:
if s.step() != 0:
break
h = s.elapsed * 24.0
if 5.5 <= h <= 7.5 and not applied_pulse:
forcing.node_lat_inflow(j1, 1.0, ForcingMode.ADD, persist=True)
applied_pulse = True
elif h > 7.5 and applied_pulse:
# release just the additive pulse, keep the baseline
forcing.clear(ForcingTarget.NODE_LAT_INFLOW, j1)
forcing.node_lat_inflow(j1, 0.5, ForcingMode.REPLACE, persist=True)
applied_pulse = False
forcing.clear_all()
Common recipes#
Replace mode (override the engine value)#
forcing.node_lat_inflow("J1", 1.5, ForcingMode.REPLACE, persist=True)
After this call, every step will see lateral inflow = 1.5 at J1
regardless of what’s in the .inp. Clear with clear() or
clear_all().
Add mode (perturb the engine value)#
# Add 0.3 cfs to whatever the engine already computes
forcing.node_lat_inflow("J1", 0.3, ForcingMode.ADD, persist=True)
Scale mode (proportional adjustment)#
# 80% of nominal rainfall on every gage during the run
for i in range(gages.count()):
forcing.gage_rainfall(i, 0.8, ForcingMode.SCALE, persist=True)
Time-varying ML controller driving lateral inflows#
while s.state == EngineState.RUNNING:
if s.step() != 0:
break
action = my_ml_controller.predict_inflow(observe(s))
# one-shot per step is fine here — we re-apply each step anyway
forcing.node_lat_inflow("J1", action, ForcingMode.REPLACE, persist=False)
Use persist=False when you intend to re-apply the forcing every
step — it’s slightly cheaper than installing and clearing.
Two stacked forcings on the same target#
# Engine value gets REPLACE'd to 0.0, then ADD'd to 1.5
# → final value 1.5
forcing.node_lat_inflow("J1", 0.0, ForcingMode.REPLACE, persist=True)
forcing.node_lat_inflow("J1", 1.5, ForcingMode.ADD, persist=True)
The order of evaluation is the order of registration. clear()
removes both at once; to remove only one, clear_all() and
re-register the survivor.
EngineState requirements & exceptions#
Method group |
Required state |
Notes |
|---|---|---|
per-target setters |
|
Persistence is honoured from registration onward. |
|
any state where the solver is alive |
Idempotent. |
Common EngineError codes:
INVALID_INDEX— index out of range for the given target.INVALID_TYPE— calling a node-only setter on a link, etc.
See also#
Nodes — one-shot per-step setters (
set_lateral_inflow()etc.) for callers that re-apply every step.Control rules — declarative rules that fire conditionally; use these instead of forcing when the trigger is internal to the model.
External inflows — static external-inflow registration in the model.