Module Reference¶
Complete API reference for all PyWMP modules. Each section covers the key classes, their parameters, method-selection guidance, and a runnable example.
pywmp.datasets — Automated Data Downloaders¶
Downloads and caches all geospatial inputs for a watershed model from open-access federal data sources.
DatasetManager¶
DatasetManager(aoi, output_dir, cache=True)
| Parameter | Type | Description |
|---|---|---|
aoi |
tuple[float,float,float,float] |
Bounding box (minx, miny, maxx, maxy) in WGS-84 decimal degrees |
output_dir |
str \| Path |
Directory where downloaded files are saved |
cache |
bool |
Reuse existing files rather than re-downloading (default True) |
Methods:
| Method | What it downloads | Output file |
|---|---|---|
download_dem(resolution_m=10) |
USGS 3DEP lidar DEM | dem.tif |
download_watershed(huc_level=12) |
USGS NHD WBD boundaries | huc12.gpkg |
download_nhd() |
NHDPlus flowlines + catchments | flowlines.gpkg, catchments.gpkg |
download_nlcd(year=2021) |
NLCD 30 m land cover | nlcd.tif |
download_soils() |
USDA SSURGO hydrologic soil groups | hsg.tif |
download_cn() |
SCS Curve Number raster (NLCD × HSG) | cn.tif |
download_atlas14(lat, lon) |
NOAA Atlas 14 IDF curves | atlas14.json |
download_floodzone() |
FEMA NFHL flood hazard areas | fema_sfha.gpkg |
download_all(lat, lon) |
All of the above in dependency order | — |
summary() |
Print download status table | — |
Example:
from pywmp.datasets import DatasetManager
dm = DatasetManager(
aoi=(-82.05, 28.02, -81.98, 28.07), # west-central Florida
output_dir="data/my_watershed",
)
dm.download_all(lat=28.044, lon=-82.013)
print(dm.summary())
Dependency order: DEM → watershed → NHD → NLCD + soils → CN → Atlas 14 + flood zones.
download_cn() requires both NLCD and soils to already be present.
download_for_aoi() — one-call shortcut¶
from pywmp.datasets import download_for_aoi
dm = download_for_aoi(
aoi=(-82.05, 28.02, -81.98, 28.07),
output_dir="data/my_watershed",
lat=28.044,
lon=-82.013,
)
pywmp.losses — Rainfall Loss Methods¶
All loss classes share the interface .compute(rainfall_ts) → (excess_ts, loss_ts).
Method comparison¶
| Method | Best for | Key parameters | Notes |
|---|---|---|---|
InitialConstantLoss |
Simple event models, minimal data | Ia, fc |
Fast; no soil-layer bookkeeping |
SCSCurveLoss |
NRCS-based design hydrology | CN, ia_ratio, amc |
Industry standard; CN from SSURGO or NLCD |
GreenAmptLoss |
Physically based soil infiltration | Ks, psi, theta_i, eta |
Best when soil texture data is available |
SMALoss |
Long continuous simulations | many (canopy, surface, soil, percolation layers) | Most complex; needed for multi-event continuous modeling |
InitialConstantLoss¶
InitialConstantLoss(Ia_in, fc_in_hr, units=UnitSystem.USC)
| Parameter | Type | Description |
|---|---|---|
Ia_in |
float |
Initial abstraction depth (inches) |
fc_in_hr |
float |
Constant infiltration rate after Ia is satisfied (in/hr) |
SCSCurveLoss¶
SCSCurveLoss(CN, ia_ratio=0.2, amc=2, units=UnitSystem.USC)
| Parameter | Type | Description |
|---|---|---|
CN |
float |
SCS Curve Number (0–100); higher = more runoff |
ia_ratio |
float |
Fraction of S used as initial abstraction (default 0.2) |
amc |
int |
Antecedent Moisture Condition: 1 (dry), 2 (normal), 3 (wet) |
Theory:
$$S = \frac{1000}{\text{CN}} - 10 \quad I_a = 0.2S \quad Q_{cum} = \frac{(P_{cum}-I_a)^2}{P_{cum}-I_a+S}$$
When to use: Standard design hydrology. CN = 78 is a typical composite for mixed residential/suburban catchments. Use AMC III for pre-wetted conditions or consecutive storms.
GreenAmptLoss¶
GreenAmptLoss(Ks, psi, theta_i, eta, units=UnitSystem.USC)
| Parameter | Type | Description |
|---|---|---|
Ks |
float |
Saturated hydraulic conductivity (in/hr) |
psi |
float |
Wetting-front suction head (in) |
theta_i |
float |
Initial volumetric moisture content (0–1) |
eta |
float |
Soil porosity (0–1) |
Theory: Green & Ampt equation (HEC-HMS TRM §3.4):
$$f(t) = K_s \left(1 + \frac{\psi \cdot \Delta\theta}{F(t)}\right)$$
Solved implicitly via Newton-Raphson per timestep.
Typical soil values (Rawls et al., 1983):
| Texture | Ks (in/hr) | ψ (in) | η |
|---|---|---|---|
| Sand | 4.74 | 1.95 | 0.437 |
| Sandy loam | 1.09 | 4.33 | 0.453 |
| Loam | 0.52 | 3.50 | 0.463 |
| Clay loam | 0.10 | 8.22 | 0.464 |
| Clay | 0.03 | 12.45 | 0.475 |
SMALoss¶
Soil Moisture Accounting (HEC-HMS TRM §3.7). Suitable for long-duration continuous simulations where soil-layer storage dynamics matter. Requires canopy, surface depression, soil tension zone, and percolation parameters. See HEC-HMS documentation for full parameter guidance.
Example:
Apply SCS Curve Number loss to a design storm hyetograph
from pywmp.losses import SCSCurveLoss
from pywmp.time_series import TimeSeries
import numpy as np
# 24-hour SCS Type II storm at 0.1-hr intervals
t = np.arange(0, 24.1, 0.1)
rain = np.zeros_like(t)
rain[60:180] = 0.15 # peak burst (in/hr)
rainfall_ts = TimeSeries(t, rain, dt=0.1, units="in/hr")
loss = SCSCurveLoss(CN=78, ia_ratio=0.2, amc=2)
excess_ts, loss_ts = loss.compute(rainfall_ts)
print(f"Total excess: {excess_ts.values.sum() * 0.1:.2f} in")
pywmp.transform — Unit Hydrograph Methods¶
All transform classes share the interface .ordinates() → np.ndarray and can convolve with an excess rainfall time series.
Method comparison¶
| Method | Best for | Key parameters | Calibration difficulty |
|---|---|---|---|
SCSUnitHydrograph |
Ungauged basins, NRCS design | lag_hr, area |
Low — lag estimated from geometry |
ClarkUH |
Gauged or well-characterized basins | Tc, R, area |
Medium — requires calibration of R |
SnyderUH |
Regional studies with historical coefficients | Ct, Cp, Lc, Lca |
Medium — regional coefficients required |
UserUH |
Directly observed unit hydrograph | ordinates |
N/A — used as-is |
SCSUnitHydrograph¶
SCSUnitHydrograph(lag_hr, area, dt=0.1, units=UnitSystem.USC)
| Parameter | Type | Description |
|---|---|---|
lag_hr |
float |
Basin lag time (hours); estimated as 0.6 × Tc |
area |
float |
Basin area (mi² for USC, km² for SI) |
dt |
float |
Computational timestep (hours) |
Lag estimation: lag_hr ≈ 0.6 × Tc, where Tc can be estimated from the TR-55 Velocity Method or estimate_scs_lag() in pywmp.workflow.
ClarkUH¶
ClarkUH(Tc, R, area, dt=0.1, time_area_curve=None, units=UnitSystem.USC)
| Parameter | Type | Description |
|---|---|---|
Tc |
float |
Time of concentration (hours) |
R |
float |
Linear reservoir storage coefficient (hours). Controls recession limb flatness. |
area |
float |
Basin area |
time_area_curve |
array-like \| None |
Custom time-area table [[t_ratio, area_ratio],...]; uses synthetic ellipse if None |
Theory: Translation hydrograph routed through a linear reservoir:
$$O(t+\Delta t) = C_1 \cdot \bar{I}(t,t+\Delta t) + C_2 \cdot O(t)$$
where $C_1 = \Delta t/(R + 0.5\Delta t)$ and $C_2 = 1 - C_1$.
Larger R → flatter, longer recession. Typical R ≈ 0.5–2.0 × Tc.
SnyderUH¶
SnyderUH(Ct, Cp, Lc, Lca, dt=0.1, units=UnitSystem.USC)
| Parameter | Type | Description |
|---|---|---|
Ct |
float |
Timing coefficient (0.4–2.2 for US basins) |
Cp |
float |
Peaking coefficient (0.4–0.8) |
Lc |
float |
Length of main channel (miles) |
Lca |
float |
Length from centroid to outlet along channel (miles) |
UserUH¶
UserUH(ordinates, dt, area, units=UnitSystem.USC)
Provide observed unit hydrograph ordinates directly (cfs/in or m³/s per mm). Use when you have a measured UH from a gauged storm.
Example:
from pywmp.transform import ClarkUH
uh = ClarkUH(Tc=2.0, R=1.5, area=3.2, dt=0.1)
# convolve with a 1-inch excess rainfall pulse
ordinates = uh.ordinates()
print(f"Peak: {ordinates.max():.1f} cfs/in at t={ordinates.argmax()*0.1:.1f} hr")
pywmp.routing — Channel Routing Methods¶
All routing classes share the interface .route(inflow_ts) → outflow_ts.
Method comparison¶
| Method | Best for | Key parameters | Notes |
|---|---|---|---|
MuskingumRouting |
Natural channels, simple reach | K, X, steps |
Standard; requires K·2X < Δt < 2K(1-X) |
MuskingumCungeRouting |
Variable discharge, kinematic wave | length, slope, n, width |
Automatically computes K and X from Manning's |
ModifiedPulsRouting |
Reservoirs, detention ponds | storage_curve |
Requires storage-discharge relationship |
LagRouting |
Pure translation (e.g., storm drains) | lag_hr |
No attenuation — flow shifted in time only |
MuskingumRouting¶
MuskingumRouting(K_hr, X, steps=1, units=UnitSystem.USC)
| Parameter | Type | Description |
|---|---|---|
K_hr |
float |
Travel time through reach (hours). Roughly equal to reach length ÷ wave celerity. |
X |
float |
Weighting factor (0 to 0.5). X=0 → pure storage (reservoir-like); X=0.5 → pure translation (no attenuation). Typical: 0.1–0.3 |
steps |
int |
Number of sub-reaches (increases numerical stability for short timesteps) |
Theory (HEC-HMS TRM §7.2):
$$O_{j+1} = C_0 I_{j+1} + C_1 I_j + C_2 O_j \quad \text{where} \quad C_0+C_1+C_2 = 1$$
Stability: Negative C₀ means parameters are unstable — increase steps or reduce K_hr.
MuskingumCungeRouting¶
MuskingumCungeRouting(length_ft, slope, n, width_ft, units=UnitSystem.USC)
Automatically derives K and X from the hydraulic properties at mean discharge. Best for reaches where channel geometry is known but routing parameters are not calibrated.
ModifiedPulsRouting¶
ModifiedPulsRouting(storage_curve, units=UnitSystem.USC)
storage_curve is a 2D array [[storage_af, discharge_cfs],...] (storage in acre-feet, discharge in cfs for USC). Use for detention basins with outlet structures.
LagRouting¶
LagRouting(lag_hr, units=UnitSystem.USC)
Shifts the inflow hydrograph forward by lag_hr with no attenuation. Use for short piped reaches or conveyance channels where travel time dominates.
Example:
Muskingum routing of a triangular inflow hydrograph
from pywmp.routing import MuskingumRouting
from pywmp.time_series import TimeSeries
import numpy as np
t = np.linspace(0, 24, 241)
# triangular inflow hydrograph peaking at 300 cfs at hour 10
inflow = np.interp(t, [0, 10, 24], [0, 300, 0])
inflow_ts = TimeSeries(t, inflow, dt=0.1, units="cfs")
router = MuskingumRouting(K_hr=1.5, X=0.2, steps=3)
outflow_ts = router.route(inflow_ts)
print(f"Peak attenuation: {inflow.max() - outflow_ts.values.max():.1f} cfs")
pywmp.meteorology — Precipitation & Design Storms¶
DesignStorm¶
DesignStorm(storm_type, total_depth_in, duration_hr, dt_hr=0.1)
storm_type |
Description |
|---|---|
"SCS_I" |
SCS Type I — Pacific coast, gentle slopes |
"SCS_IA" |
SCS Type IA — Pacific Northwest |
"SCS_II" |
SCS Type II — most of eastern/central US (default for humid regions) |
"SCS_III" |
SCS Type III — Gulf Coast, humid subtropics |
"SFWMD_24hr" |
South Florida Water Management District 24-hour |
"SFWMD_72hr" |
SFWMD 72-hour |
"SFWMD_120hr" |
SFWMD 120-hour (5-day) |
IDFCurve¶
IDFCurve(atlas14_json_path) # load from downloaded Atlas 14 file
| Method | Returns |
|---|---|
.intensity(duration_hr, return_period_yr) |
Rainfall intensity (in/hr) |
.depth(duration_hr, return_period_yr) |
Rainfall depth (in) |
.plot() |
Matplotlib figure of intensity-duration-frequency curves |
Return periods available: 1, 2, 5, 10, 25, 50, 100, 200, 500, 1000 years.
Example:
from pywmp.meteorology import IDFCurve
idf = IDFCurve("data/my_watershed/atlas14.json")
depth_100yr_24hr = idf.depth(24, 100)
print(f"100-year 24-hour depth: {depth_100yr_24hr:.2f} in")
pywmp.simulation — Basin Model Engine¶
BasinModel¶
BasinModel(name)
Directed-acyclic-graph engine that runs a network of hydrologic elements in topological order.
Methods:
| Method | Description |
|---|---|
add_element(el) |
Add a SubbasinElement, ReachElement, JunctionElement, ReservoirElement, or ROMElement |
connect(upstream, downstream) |
Wire two elements together by name |
run(rainfall_dict, dt=None) |
Execute the model; returns SimResults |
SimResults¶
| Attribute / Method | Description |
|---|---|
.outflows |
dict[name, TimeSeries] — outflow hydrograph at each element |
.peak_flow(name) |
Peak discharge (cfs or m³/s) at element name |
.total_volume(name) |
Total runoff volume at element name |
.summary() |
Print peak flow + volume table for all elements |
.to_dict() |
JSON-serializable representation |
.plot(names=None) |
Matplotlib figure of selected hydrographs |
pywmp.rom / pywmp.sim2d — 2D Rain-on-Mesh Solver¶
ROMGrid¶
ROMGrid.from_tif(path, resolution_m=None, crs=None)
ROMGrid.from_tif_bbox(path, bbox, resolution_m=None)
| Parameter | Description |
|---|---|
path |
Path to a GeoTIFF DEM |
resolution_m |
Resample to this cell size (meters). None preserves native resolution. |
bbox |
(minx, miny, maxx, maxy) in the DEM's CRS |
Attributes:
| Attribute | Description |
|---|---|
.dem |
Numpy array of elevations (m or ft) |
.nx, .ny |
Grid dimensions |
.dx, .dy |
Cell size |
.crs |
Coordinate reference system |
.summary() |
Print grid metadata |
ROMSimulation¶
ROMSimulation(
grid,
precipitation,
infiltration=None,
boundary_conditions=None,
roughness=None,
duration_hr=24,
dt_s=None,
backend="auto",
)
| Parameter | Description |
|---|---|
grid |
ROMGrid object |
precipitation |
UniformPrecipitation or GriddedPrecipitation |
infiltration |
Loss method object (optional; default = no infiltration) |
boundary_conditions |
List of BC objects (outlet, stage, inflow, etc.) |
roughness |
ManningField or scalar n value (default 0.035) |
duration_hr |
Total simulation time (hours) |
backend |
"auto", "cuda", "numba", or "numpy" |
Backends: auto selects the fastest available. Use pywmp.available_backends() to see what's installed.
Boundary conditions¶
| Class | When to use |
|---|---|
NormalDepthOutlet() |
Standard open outfall at domain edge |
FixedStageBC(ts) |
Prescribed tailwater stage (TimeSeries) |
InflowBC(cells, ts) |
Prescribed point inflow hydrograph |
InternalInflowBC(cells, ts) |
Distributed inflow across interior cells (used in hybrid coupling) |
Example:
Run a 2D rain-on-mesh simulation with design storm and normal-depth outlet
from pywmp.rom import ROMGrid, ROMSimulation, UniformPrecipitation, NormalDepthOutlet
from pywmp.meteorology import DesignStorm
grid = ROMGrid.from_tif("data/my_watershed/dem.tif", resolution_m=10)
storm = DesignStorm("SCS_II", total_depth_in=5.0, duration_hr=24, dt_hr=0.1)
precip = UniformPrecipitation(storm.hyetograph())
sim = ROMSimulation(
grid=grid,
precipitation=precip,
boundary_conditions=[NormalDepthOutlet()],
duration_hr=24,
)
results = sim.run()
print(results.summary())
print(f"Peak depth: {results.max_depth:.2f} m")
pywmp.hybrid — One-Way 1D→2D Coupling¶
Two coupling modes¶
Mode A — Outlet-to-inflow BC (HybridSimulation):
The 1D reach outlet hydrograph is injected as a boundary inflow into the 2D domain. Use when the watershed drains into a 2D floodplain at a known channel location.
Mode B — Excess-precip distribution (HybridExcessPrecipSimulation):
The 1D network computes per-subbasin losses; excess precipitation is spatially distributed onto the 2D mesh through subbasin masks. Use when rainfall-on-mesh with spatially varying losses is needed.
HybridSimulation¶
HybridSimulation(upstream_model, rom_grid, outlet_specs)
| Parameter | Description |
|---|---|
upstream_model |
A configured DesignStormSimulation or BasinModel |
rom_grid |
ROMGrid for the 2D domain |
outlet_specs |
List of OutletBCSpec objects mapping 1D element names to 2D mesh edge cells |
HybridExcessPrecipSimulation¶
HybridExcessPrecipSimulation(upstream_model, rom_grid, mask_spec)
| Parameter | Description |
|---|---|
mask_spec |
SubbasinMaskSpec — maps each 1D subbasin name to a set of 2D grid cells |
SubbasinMaskSpec¶
SubbasinMaskSpec.from_delineation(delineator, grid)
Automatically builds masks from a WatershedDelineator result and a ROMGrid. Each subbasin's catchment polygon is rasterized onto the 2D grid.
HybridResults¶
Extends ROMResults with:
| Attribute | Description |
|---|---|
.inflow_hydrograph |
Combined 1D inflow injected into 2D domain |
.flood_map |
FloodMap with peak depth raster |
.compare_1d_2d() |
Comparison metrics between 1D outlet and 2D outlet hydrographs |
pywmp.flood — HAND Terrain Analysis & Flood Mapping¶
HANDRaster¶
HANDRaster(dem_path, stream_threshold=500)
| Parameter | Description |
|---|---|
dem_path |
Path to a conditioned DEM (GeoTIFF) |
stream_threshold |
Flow accumulation cells needed to define a stream (default 500) |
Methods:
| Method | Description |
|---|---|
.compute() |
Fill sinks → D8 flow direction → flow accumulation → HAND |
.hand |
HAND raster array (height above nearest drainage) |
.streams |
Binary stream network raster |
.snap_to_stream(x, y) |
Snap a coordinate to the nearest stream cell |
WatershedDelineator¶
WatershedDelineator(dem_path, stream_threshold=500)
Methods:
| Method | Description |
|---|---|
.delineate() |
Run full delineation; returns reach network |
.reach_properties |
DataFrame: [reach_id, length_ft, slope, area_mi2, ...] |
.rating_curves(n=0.035) |
Per-reach Manning's normal-depth rating curves |
.catchment_polygons |
GeoDataFrame of delineated catchment polygons |
FloodMapper¶
FloodMapper(hand_raster, delineator)
Method:
flood_map = FloodMapper.map(sim_results, method="hand_uniform")
method |
Description |
|---|---|
"hand_uniform" |
HAND-uniform (one depth per reach from rating curve) — fastest |
"bathtub" |
Simple bathtub (all cells ≤ WSE flooded) |
"connected_bathtub" |
BFS-connected bathtub (flood must be contiguous from seed) |
"hand_spatial" |
Spatially varying HAND depth — highest accuracy |
FloodMap¶
| Attribute | Description |
|---|---|
.depth |
Depth raster array (ft or m) |
.stats.flooded_area_acres |
Total inundated area |
.stats.max_depth_ft |
Maximum water depth |
.stats.volume_acre_ft |
Total flood volume |
.plot() |
Matplotlib depth map |
.to_cog(path) |
Export as Cloud Optimized GeoTIFF |
pywmp.cascade — Cascade 2001 Detention Routing¶
CascadeSimulation¶
CascadeSimulation(basins, links, rainfall, dt_min=5)
Runs the Cascade 2001 iterative stage-routing algorithm over a network of detention basins and hydraulic structures.
parse_cascade_dat(path)¶
Parse an official Cascade 2001 .dat project file and return a configured CascadeSimulation.
from pywmp.cascade import parse_cascade_dat
sim = parse_cascade_dat("project.dat")
results = sim.run()
results.to_csv("output/")
Hydraulic structures¶
| Class | Models |
|---|---|
CascadeGravityStructure |
Weir + bleeder pipe + principal spillway composite |
CascadeGatedSpillway |
Gate-controlled spillway with 4 flow regimes |
CascadePumpStation |
On/off pump with hysteresis levels |
CascadeDropInlet |
Drop-inlet riser + barrel pipe |
CascadeSeawall |
Coastal seawall overtopping (new in v0.2.0) |
Runoff methods¶
| Class | Method |
|---|---|
SBUHRunoff |
Santa Barbara Urban Hydrograph (time-step Green-Ampt) |
GDCUHRunoff |
General Dimensionless Curvilinear Unit Hydrograph |
pywmp.api — REST API Server¶
Start the server¶
pip install "pywmp[api]"
uvicorn pywmp.api.app:app --host 0.0.0.0 --port 8000 --reload
Endpoints¶
| Method | Path | Description |
|---|---|---|
POST |
/simulate/hms |
Submit an HMS simulation job |
POST |
/simulate/cascade |
Submit a Cascade 2001 job |
GET |
/results/{run_id} |
Retrieve results (status + hydrographs) |
GET |
/runs |
List all in-memory runs |
POST |
/upload/dat |
Upload a .dat file to the server |
GET |
/health |
Server health check |
WS |
/ws/{run_id} |
WebSocket progress stream |
See the REST API Tutorial for full request/response schemas and a working Python client.
pywmp.time_series — Universal Data Carrier¶
TimeSeries¶
TimeSeries(times, values, dt=None, label="", units="")
| Parameter | Description |
|---|---|
times |
1-D array of time values (hours) |
values |
1-D array of data values |
dt |
Uniform timestep (hours); inferred if None |
label |
Human-readable label |
units |
Unit string (e.g. "cfs", "in/hr", "m") |
Arithmetic: +, -, *, /, ** all work element-wise with broadcasting.
Methods:
| Method | Description |
|---|---|
.interpolate(t) |
Linear interpolation at arbitrary time(s) |
.resample(new_dt) |
Change timestep (linear interpolation) |
.to_dict() |
JSON-serializable representation |
TimeSeries.from_dict(d) |
Reconstruct from dict |
pywmp.units — Unit System¶
from pywmp.units import UnitSystem, USC, SI
# USC — US customary (ft, in, mi, cfs, acres, ...)
# SI — metric (m, mm, km, m³/s, ha, ...)
Pass units=USC or units=SI to any class that accepts it. Most classes default to USC.