Skip to main content
Ctrl+K

OpenSWMM

  • Installation
  • Quickstart
  • Concepts & engine lifecycle
  • OpenSWMM 6 User Guide (refactored engine)
  • Legacy SWMM 5 — Compatibility Layer
    • Legacy SWMM 5 Solver
    • Legacy SWMM 5 Output Reader
    • Migrating from SWMM 5 to v6
    • API Reference
    • License
    • C/C++ Engine Docs
  • GitHub
  • PyPI
  • Installation
  • Quickstart
  • Concepts & engine lifecycle
  • OpenSWMM 6 User Guide (refactored engine)
  • Legacy SWMM 5 — Compatibility Layer
  • Legacy SWMM 5 Solver
  • Legacy SWMM 5 Output Reader
  • Migrating from SWMM 5 to v6
  • API Reference
  • License
  • C/C++ Engine Docs
  • GitHub
  • PyPI

Section Navigation

  • Running a simulation — Solver
  • Error handling, edge cases & debugging
  • SWMM DateTime and Python datetime interop
  • Nodes
  • Links
  • Subcatchments
  • Rain gages
  • External inflows
  • Advanced forcing
  • Control rules
  • Tables (time series, curves, patterns)
  • Pollutants
  • Water quality (landuse, buildup, washoff, treatment)
  • Output reader (binary .out file)
  • Plotting
  • Hot start
  • Mass balance
  • Statistics
  • Programmatic model construction
  • Model editing (deletion + type conversion)
  • Infrastructure (transects, streets, inlets, LIDs)
  • Spatial (CRS, coordinates, geometry)
  • OpenSWMM 6 User Guide (refactored engine)
  • Links

Links#

Note

Engine: OpenSWMM 6 — refactored. This page documents the openswmm.engine.Links collection and the openswmm.engine._links.Link wrapper. Legacy SWMM 5 users access links through the enum-driven getValue / setValue API on openswmm.legacy.engine.Solver — see Legacy SWMM 5 Solver.

Conduits, pumps, orifices, weirs, and outlets — every connection between two nodes. The shape mirrors Nodes:

from openswmm.engine import Solver

with Solver("model.inp") as s:
    s.links             # → Links collection
    s.links["C1"]       # → Link wrapper
    s.links[0]          # → Link wrapper (by index)

Reference: openswmm_links.h.


Quickstart#

# Indexing.
c1 = s.links["C1"]

# Iteration.
for link in s.links:
    print(link.id, link.type.name, link.length)

# Per-object property access.
print(c1.flow, c1.depth, c1.velocity)
c1.control_setting = 0.5
c1.closed = True

# Topology — yields Node wrappers, not bare indices.
print(c1.from_node.id, "->", c1.to_node.id)

# Bulk numpy access.
flows = s.links.flows               # np.ndarray
s.links.flows = flows * 0.95

# Cross-section.
c1.xsect = (XSectShape.CIRCULAR, 1.0, 0.0, 0.0, 0.0)
print(c1.xsect.shape, c1.xsect.g1)

# Type-specific sub-views — raise AttributeError on wrong type.
s.links["P1"].pump.curve = 0
s.links["W1"].weir.type = WeirType.TRANSVERSE
s.links["OR1"].orifice.type = OrificeType.BOTTOM
s.links["OUT1"].outlet.rating_type = OutletRatingType.FUNCTIONAL_HEAD

# Stats.
print(s.links["C1"].stats.max_flow, s.links["C1"].stats.max_velocity)

Collection: Links#

Operation

What it does

len(s.links)

Current link count.

s.links[key]

Returns a Link. key is int or str.

for l in s.links:

Yields a fresh Link per index.

s.links.get_index(id)

String → int. Raises KeyError.

s.links.get_id(idx)

Int → string. Raises IndexError.

s.links.add(id, type)

Append a link. Returns the Link and bumps generation.

s.links.pop_last(id)

Remove the most recently added link.

s.links.rename(key, new_id)

Rename in place. Invalidates wrappers.

Bulk numpy properties#

Property

Mode

What it carries

flows

read/write

Per-link flow rate.

depths

read-only

Water depth.

velocities

read-only

capacities

read-only

Flow / full-flow ratio.

volumes

read-only

control_settings

read-only

target_settings

read-only

hyd_powers

read-only

Hydraulic power (for pumps).

ids

read-only

String ids as numpy.ndarray (dtype=object).

For per-pollutant concentrations:

s.links.qualities("TSS")     # str id or int index

For pump statistics in bulk:

cycles, on_time, volume = s.links.pump_stats()

Wrapper: Link#

Property

Type

Mode

Meaning

id

str

read-only

index

int

read-only

type

LinkType

read-only

CONDUIT / PUMP / ORIFICE / WEIR / OUTLET.

from_node

Node

read-only

Upstream node wrapper.

to_node

Node

read-only

Downstream node wrapper.

length

float

read/write

Conduit length (other types use slot for type-specific data).

roughness

float

read/write

slope

float

read-only

Computed from invert elevations.

offset_up / offset_dn

float

read/write

initial_flow / max_flow

float

read/write

xsect

XSection

read/write

Assign (shape, g1, g2, g3, g4) to write through.

flow

float

read/write

depth / velocity / capacity / volume

float

read-only

hyd_power

float

read-only

control_setting / target_setting

float

read/write

closed

bool

read/write

loss_coeff

(inlet, outlet, avg)

read/write

flap_gate / seep_rate / culvert_code / barrels

mixed

read/write

Conduit-flavoured knobs.

Methods:

Method

What it does

set_nodes(from_node, to_node)

Reconnect this link. Accepts indices, ids, or Node wrappers.

quality(pollutant)

Concentration of pollutant (id or int).


Cross-section: XSection#

link.xsect returns an XSection view. Access individual fields by attribute, the snapshot tuple via as_tuple(), or rewrite all five fields by assigning a tuple:

x = c1.xsect
print(x.shape, x.g1, x.g2, x.g3, x.g4)
shape, g1, g2, g3, g4 = x.as_tuple()

c1.xsect = (XSectShape.CIRCULAR, 1.0, 0.0, 0.0, 0.0)

The shape is a XSectShape enum; the four g parameters are shape-specific (diameter, width, height, depth, …).


Type-specific sub-views#

Each link type exposes a small sub-namespace; accessing the wrong one raises AttributeError:

# PUMP only.
p = s.links["P1"]
p.pump.curve = 0                       # curve index
p.pump.init_state = True               # starts on
p.pump.startup_depth = 1.0
p.pump.shutoff_depth = 0.1

# WEIR only.
w = s.links["W1"]
w.weir.type = WeirType.TRANSVERSE
w.weir.crest_height = 0.5
w.weir.discharge_coeff = 3.33
w.weir.end_contractions = 2

# ORIFICE only.
o = s.links["OR1"]
o.orifice.type = OrificeType.BOTTOM
o.orifice.open_close_rate = 0.5        # 1 / second

# OUTLET only.
out = s.links["OUT1"]
out.outlet.rating_type = OutletRatingType.FUNCTIONAL_HEAD
out.outlet.expon = 0.5

Statistics sub-view#

Every link carries a .stats view; pump-specific entries raise BadParamError (which is also a ValueError) on non-pump links:

c1 = s.links["C1"]
print(c1.stats.max_flow)
print(c1.stats.max_velocity)
print(c1.stats.max_filling)
print(c1.stats.vol_flow)
print(c1.stats.surcharge_time)

p1 = s.links["P1"]
print(p1.stats.pump_cycles)
print(p1.stats.pump_on_time)
print(p1.stats.pump_volume)

These values are only meaningful after Solver.end().


Staleness, equality, repr#

Same contract as Node:

  • Adding, removing, renaming, or converting links invalidates every Link minted before. Access raises StaleObjectError.

  • a == b is True when a and b wrap the same (solver, index) pair; hashing is consistent.

  • repr(link) includes the captured id and index.


See also#

  • Running a simulation — Solver — where s.links comes from.

  • Nodes — Link.from_node / Link.to_node return Node wrappers.

  • Control rules — runtime control of link control_setting and target_setting.

  • Error handling, edge cases & debugging — every exception type referenced on this page.

previous

Nodes

next

Subcatchments

On this page
  • Quickstart
  • Collection: Links
    • Bulk numpy properties
  • Wrapper: Link
  • Cross-section: XSection
  • Type-specific sub-views
  • Statistics sub-view
  • Staleness, equality, repr
  • See also
Show Source

© Copyright 2026 Caleb Buahin.

Created using Sphinx 9.1.0.

Built with the PyData Sphinx Theme 0.18.0.