Subcatchments#

Note

Engine: OpenSWMM 6 — refactored. This page documents the openswmm.engine.Subcatchments class. Legacy SWMM 5 users access subcatchments through the enum-driven getValue / setValue API on openswmm.legacy.engine.Solver — see Legacy SWMM 5 Solver.

Sub-areas of urban land that produce runoff in response to rainfall. The Subcatchments class is the entry point for reading and writing subcatchment geometry, infiltration parameters, landuse coverage, and per-step hydrology state.

Reference: openswmm_subcatchments.h.


Class signature#

class Subcatchments:
    def __init__(self, solver: Solver) -> None: ...

A single Subcatchments instance covers every subcatchment in the model. Address by string id or integer index.


Key methods#

Identity#

Method

Returns

count()

Number of subcatchments.

get_index(id)()

Integer index for a string id.

get_id(idx)()

String id for an integer index.

add(id)()

Append a new subcatchment.

Geometry & connectivity#

Method

Action / returns

get_area() / set_area()

Total subcatchment area.

get_width() / set_width()

Characteristic flow width.

get_slope() / set_slope()

Average surface slope.

get_imperv_pct() / set_imperv_pct()

Impervious-area fraction (0–100%).

get_outlet() / set_outlet(node_idx)()

Receiving node index.

get_outlet_subcatch() / set_outlet_subcatch(sc_idx)()

Receiving subcatchment (chained routing).

get_gage() / set_gage(gage_idx)()

Rain gage index that drives this subcatchment.

Surface roughness & depression storage#

Method

Action / returns

get_n_imperv() / set_n_imperv()

Manning’s n over impervious area.

get_n_perv() / set_n_perv()

Manning’s n over pervious area.

get_ds_imperv() / set_ds_imperv()

Depression storage on impervious area.

get_ds_perv() / set_ds_perv()

Depression storage on pervious area.

Infiltration#

The infiltration model is selected globally in [OPTIONS] INFILTRATION. Setters write the parameters appropriate for the active model.

Method

Action / returns

get_infil_model()

Active InfilModel.

set_infil_horton(...)() / get_infil_horton()

Max + min infiltration rate (f0, fmin), decay constant (decay), drying time (dry_time).

set_infil_green_ampt(...)() / get_infil_green_ampt()

Suction head, conductivity, initial deficit.

set_infil_curve_number(cn)() / get_infil_curve_number()

SCS Curve Number.

Per-step hydrology state#

Method

Returns

get_rainfall()

Current rainfall intensity over the subcatchment.

get_runoff()

Current runoff flow leaving the subcatchment.

get_groundwater()

Groundwater contribution to the receiving node.

get_evap()

Surface evaporation flux.

get_infil()

Infiltration flux.

get_snow_depth()

Snowpack water-equivalent depth.

Cumulative statistics#

Method

Returns

get_stat_precip()

Total precipitation depth so far.

get_stat_runoff_vol()

Total runoff volume so far.

get_stat_max_runoff()

Peak runoff seen so far.

For a richer cumulative-statistics surface (peak rates, durations, quality components) see Statistics.

Forcing#

Method

Action

set_rainfall(idx, r)()

Override rainfall for this subcatchment this step (one-shot).

For sticky cross-step rainfall overrides, use Forcing.subcatch_rainfall() (see Advanced forcing).

Landuse coverage & water quality#

Method

Action / returns

get_coverage(idx, lu)() / set_coverage(idx, lu, frac)()

Fraction of subcatchment covered by landuse lu.

get_quality(idx, p)()

Pollutant p concentration in surface runoff.

get_ponded_quality(idx, p)()

Pollutant p concentration in ponded surface water.


End-to-end example#

from openswmm.engine import Solver, Subcatchments, EngineState

with Solver("site_drainage.inp", "site_drainage.rpt", "site_drainage.out") as s:
    sc = Subcatchments(s)
    print(f"{sc.count()} subcatchments")

    # describe each subcatchment
    for i in range(sc.count()):
        print(
            f"  {sc.get_id(i):<12}  "
            f"area={sc.get_area(i):7.3f}  "
            f"imperv={sc.get_imperv_pct(i):4.1f}%  "
            f"outlet={sc.get_outlet(i)}"
        )

    # accumulate runoff per subcatchment
    total = [0.0] * sc.count()
    dt = s.get_routing_step()
    while s.state == EngineState.RUNNING:
        if s.step() != 0:
            break
        for i in range(sc.count()):
            total[i] += sc.get_runoff(i) * dt
    for i, vol in enumerate(total):
        print(f"  {sc.get_id(i):<12}  runoff vol = {vol:.3f}")

Common recipes#

Override rainfall on one subcatchment for the run#

One-shot per step:

s1 = sc.get_index("S1")
while s.state == EngineState.RUNNING:
    if s.step() != 0:
        break
    sc.set_rainfall(s1, 0.5)        # in/hr or mm/hr per RAINFALL units

Sticky:

from openswmm.engine import Forcing, ForcingMode

forcing = Forcing(s)
forcing.subcatch_rainfall("S1", 0.5, ForcingMode.REPLACE, persist=True)
while s.state == EngineState.RUNNING:
    if s.step() != 0:
        break
forcing.clear_all()

Switch infiltration model parameters at runtime#

# Change Horton parameters before initialize()
s.open()
sc.set_infil_horton(
    sc.get_index("S1"),
    f0=3.0,         # initial rate
    fmin=0.05,      # final rate
    decay=4.0,      # /hr
    dry_time=7.0,   # /day
)
s.initialize()

Sweep multiple coverage fractions for sensitivity analysis#

# Snippet only — wrap in a loop over scenarios.
sc.set_coverage("S1", lu_idx=0, fraction=0.4)   # 40% landuse 0
sc.set_coverage("S1", lu_idx=1, fraction=0.6)   # 60% landuse 1
# remaining must sum to ≤ 1.0; the engine fills the residual with default

Per-subcatchment runoff coefficient (post-run summary)#

for i in range(sc.count()):
    precip = sc.get_stat_precip(i)
    runoff = sc.get_stat_runoff_vol(i) / max(sc.get_area(i), 1e-12)
    rc = (runoff / precip) if precip > 0 else 0.0
    print(f"  {sc.get_id(i):<12}  precip={precip:.3f}  runoff={runoff:.3f}  RC={rc:.3f}")

Bulk arrays#

The Subcatchments class does not (yet) expose bulk-array accessors. Vectorise across the population manually:

import numpy as np

runoff = np.zeros(sc.count())
while s.state == EngineState.RUNNING:
    if s.step() != 0:
        break
    for i in range(sc.count()):
        runoff[i] += sc.get_runoff(i)

If you need a strictly faster path, fall back to the OutputReader after the run and use the bulk subcatchment methods (Output reader (binary .out file)) — that’s typically fastest for post-processing.


EngineState requirements & exceptions#

Method group

Required state

Notes

identity / count

OPENED or later

Identity is fixed at parse time.

geometry / infiltration setters

OPENED

Modify before initialize() for predictable results.

hydrology accessors (get_runoff etc.)

RUNNING or ENDED

Need at least one runoff step.

cumulative statistics

RUNNING or ENDED

Accumulated since simulation start.

set_rainfall

RUNNING

One-shot.

quality accessors

RUNNING or ENDED

Require at least one pollutant in the model.

Common EngineError codes:

  • INVALID_INDEX — index out of range.

  • NOT_FOUND — string id not in the model.

  • INVALID_TYPE — landuse / infiltration mismatch.


See also#