2-D surface routing#

Note

Engine: OpenSWMM 6 — refactored. This page documents the openswmm.engine._2d.Surface2D view. It is only available when the C/C++ engine was built with OPENSWMM_BUILD_2D=ON and the loaded model defines a 2D mesh. Guard for it with openswmm.engine.HAS_2D (build-time) and Surface2D.is_active (per-model).

The 2-D surface-routing module overlays a triangular mesh on the 1-D drainage network and advances the shallow-water surface state with a CVODE solver, exchanging flow with 1-D nodes at coupling points. The Surface2D view hangs off the Solver as a lazy attribute and addresses the live mesh:

from openswmm.engine import Solver, HAS_2D

with Solver("mesh_model.inp") as s:
    if HAS_2D and s.surface2d.is_active:
        surf = s.surface2d          # → Surface2D
        surf.n_triangles            # mesh size
        surf.get_depth(0)           # state at triangle 0

Reference: openswmm_2d.h.


Quickstart#

from openswmm.engine import Solver, HAS_2D, SurfaceForcingMode, ForcingPersist

with Solver("mesh_model.inp") as s:
    if not (HAS_2D and s.surface2d.is_active):
        raise RuntimeError("model has no active 2D surface")
    surf = s.surface2d

    # Drive the whole mesh with steady rain that survives every step.
    surf.force_rainfall_uniform(
        30.0,                                  # mm/hr
        mode=SurfaceForcingMode.OVERRIDE,
        persist=ForcingPersist.PERSIST,
    )

    for _ in s.steps():
        pass

    depths = surf.get_depths()                 # numpy (n_triangles,)
    mb = surf.get_mass_balance()
    print("continuity error:", mb["continuity_error"])

Mesh geometry & topology#

The mesh is fixed for the run. Vertices carry (x, y, z) coordinates; triangles carry area, Manning’s n, centroid, and adjacency. Indices are zero-based.

Member

What it returns

n_vertices / n_triangles

Mesh size (properties).

get_vertex_coords()

(x, y, z) numpy arrays for all vertices (GIL released).

get_vertex_xyz(idx)

Scalar (x, y, z) for one vertex.

set_vertex_z(idx, z)

Set a vertex ground elevation.

get_triangle_vertices(idx)

(v0, v1, v2) vertex indices.

get_triangle_area(idx)

Triangle area.

get_triangle_centroid(idx)

(cx, cy, cz) centroid.

get_triangle_mannings(idx) / set_triangle_mannings(idx, n)

Manning’s n (the setter rejects non-positive values).

get_triangle_tag(idx) / set_triangle_tag(idx, tag)

[2D_TRIANGLES] TAG column; "" clears it.

get_vertex_tag(idx) / set_vertex_tag(idx, tag)

[2D_VERTICES] TAG column; "" clears it.

get_triangle_neighbours(idx)

(n0, n1, n2) neighbour triangle indices (-1 at a boundary).

Coupling to 1-D nodes#

Where the mesh exchanges flow with the 1-D network:

Member

What it returns

vertex_coupling_count / triangle_coupling_count

Number of coupling points (properties).

get_vertex_coupled_node(vertex_idx)

SWMM node index coupled to a vertex (-1 if uncoupled).

set_vertex_coupled_node(vertex_idx, node_name)

Couple a vertex to a 1-D node by id ("" clears the coupling).

get_vertex_coupling_cd(vertex_idx) / set_vertex_coupling_cd(vertex_idx, cd)

Coupling discharge coefficient ([2D_VERTEX_NODE_MAP] CD column; default 0.65, must be > 0). Persisted by the .inp writer.

get_vertex_coupling_area(vertex_idx) / set_vertex_coupling_area(vertex_idx, area)

Coupling exchange area in m² ([2D_VERTEX_NODE_MAP] AREA column; default 1.0, must be > 0). Persisted by the .inp writer.

get_triangle_coupled_node(tri_idx)

SWMM node index coupled to a triangle (-1 if uncoupled).


Runtime state#

Per-triangle state is read either one triangle at a time or as bulk float64 arrays of shape (n_triangles,). Bulk getters release the GIL.

Member

Scope

Meaning

get_depth(idx) / get_depths()

one / bulk

Water depth on the triangle.

get_head(idx) / get_heads()

one / bulk

Total head (water-surface elevation).

get_rainfall(idx)

one

Current rainfall source.

get_net_source(idx)

one

Net source term (rain − evap ± coupling).

get_coupling_flux(idx) / get_coupling_fluxes()

one / bulk

Exchange flux with the coupled 1-D node.

get_vertex_heads()

bulk

Heads reconstructed at vertices (n_vertices).

get_vertex_render_depths()

bulk

Render-oriented signed water depths at all vertices (n_vertices): the wet-masked, depth-weighted free-surface reconstruction eta_v - z_v (m). Unlike get_vertex_heads(), dry-cell bed elevations never contribute; the value is negative over the dry side of a partially wet cell and 0 where no incident cell is wet. This is the field GUIs should interpolate for water-surface rendering and profiles.

get_edge_flux_bulk()

bulk

Normal flux on every triangle edge.

get_edge_geometry_bulk()

bulk

Time-invariant edge lengths and outward unit normals.

Aggregate scalars (properties): max_depth, total_volume (Σ depth × area), total_exchange_flow, and the last-advance solver counters cvode_steps and cvode_last_step.


Forcing#

Inject rainfall, evaporation, or a coupling flux onto the mesh. Each forcing setter is keyword-compatible with the 1-D Advanced forcing view but uses the 2-D-specific enums:

  • mode (SurfaceForcingMode) — OVERRIDE (default) replaces the computed source; ADD adds to it.

  • persist (ForcingPersist) — RESET (default) applies for the next step only; PERSIST keeps the forcing active every step until cleared.

Method

Forces …

force_rainfall(idx, value, *, mode, persist)

Rainfall on one triangle.

force_rainfall_uniform(value, *, mode, persist)

Rainfall on every triangle.

force_evap(idx, value, *, mode, persist)

Evaporation on one triangle.

force_evap_uniform(value, *, mode, persist)

Evaporation on every triangle.

force_coupling_flux(idx, value, *, mode, persist)

Coupling flux on one triangle.

force_clear_all()

Remove every 2-D forcing.


Mass balance & continuity#

get_mass_balance() returns a dict[str, float] of cumulative volumetric terms (all m³) plus the dimensionless continuity_error:

Key

Term

init_storage / final_storage

Surface storage at the start / end.

rainfall_in

Cumulative rainfall input.

coupling_1d_to_2d_in / coupling_2d_to_1d_out

Flow exchanged with the 1-D network, each direction.

outfall_in / outfall_out

Flow across outfall boundaries.

boundary_in / boundary_out

Flow across open mesh-edge boundaries.

evap_out

Cumulative evaporation loss.

continuity_error

Global closure error as a fraction.

The scalar Surface2D.continuity_error is the same closure error. Cumulative per-triangle envelopes are available after the run:

Method

Envelope (per triangle)

get_stat_max_depths()

Maximum depth reached.

get_stat_max_velocities()

Maximum velocity magnitude.

get_stat_max_continuity_err()

Maximum absolute continuity residual (m³/s).


Solver tolerances#

The CVODE integrator is tuned through three read/write properties:

  • dry_depth — depth (m) below which a triangle is treated as dry.

  • rel_tolerance — CVODE relative tolerance.

  • abs_tolerance — CVODE absolute tolerance.

surf.dry_depth = 1e-4
surf.rel_tolerance = 1e-6
surf.abs_tolerance = 1e-8

Boundary conditions#

Open mesh edges carry a boundary condition selected per (triangle, edge) pair, where edge is 0, 1, or 2. The type is a SurfaceBoundaryType: WALL (closed), NORMAL_FLOW, SPECIFIED_STAGE, SPECIFIED_FLOW, or RATING_CURVE.

Member

What it does

boundary_edge_count

Number of boundary edges (property).

get_edge_bc_type / set_edge_bc_type

Read / set the BC type.

get_edge_bc_head / set_edge_bc_head

Boundary head (SPECIFIED_STAGE).

get_edge_bc_slope / set_edge_bc_slope

Boundary slope (NORMAL_FLOW).

get_edge_bc_flow / set_edge_bc_flow

Prescribed flow per metre (SPECIFIED_FLOW).

get_edge_bc_cum_flux

Cumulative flux across the edge.

get_edge_bc_flow (runtime)

Instantaneous flow across the edge.

set_edge_bc_tseries_name

Name the timeseries driving a SPECIFIED_STAGE edge.

set_edge_bc_flow_tseries_name

Name the timeseries driving a SPECIFIED_FLOW edge.

set_edge_bc_rating_curve_name

Name the rating curve driving a RATING_CURVE edge.

Edge conveyance#

A per-edge conveyance factor in [0, 1] throttles flow across internal edges (1.0 = unrestricted, 0.0 = closed):

Member

What it does

get_edge_conveyance(tri, edge) / set_edge_conveyance(tri, edge, c)

Read / set one edge’s factor.

get_edge_conveyance_bulk()

All factors as a numpy array.

reset_edge_conveyance()

Reset every edge to 1.0.


EngineState & exceptions#

State queries (get_depth, get_mass_balance, the *_bulk getters) are only meaningful while the simulation is running or after it ends; calling them before Solver.start() or when the 2-D module did not run raises a RuntimeError. Forcing setters require an active 2-D module. Always gate access with Surface2D.is_active, and guard the import itself with openswmm.engine.HAS_2D so code degrades gracefully on builds without 2-D support.


See also#