Skip to content

Add Manning normal-depth + rainfall-driven HAND flood (2/2)#16

Merged
rehsani merged 1 commit into
mainfrom
step-9-manning-and-rainfall-flood
May 4, 2026
Merged

Add Manning normal-depth + rainfall-driven HAND flood (2/2)#16
rehsani merged 1 commit into
mainfrom
step-9-manning-and-rainfall-flood

Conversation

@rehsani
Copy link
Copy Markdown
Owner

@rehsani rehsani commented May 4, 2026

Summary

PR 2 of 2 for the routing milestone. Closes the hydraulic half — turns the per-cell peak discharge from PR1 into per-stream-cell water levels via Manning's wide-channel normal-depth equation, then broadcasts those upstream along the flow direction grid to drive HAND-based inundation. Combined with PR1, the project now produces a true rainfall-driven flood depth raster from synthetic uniform rainfall (or any future `PrecipGrid`).

Pipeline closed

```
rainfall (uniform or any source)
↓ SCS-CN equation
runoff Q (mm)
↓ accumulate_runoff (PR1)
V_acc (m³)
↓ peak_discharge (PR1)
Q_peak (m³/s)
↓ compute_water_level — NEW
h at stream cells (m)
↓ compute_rainfall_inundation — NEW
flood depth (m) per cell
```

Module surface

Item What
`slope_along_flow(flow_grid, dem)` Per-cell slope (m/m) along D8 with metric distances; min_slope floor for Manning sqrt safety
`leopold_maddock_width(Q, a=2.5, b=0.5)` Global natural-channel hydraulic-geometry default
`manning_normal_depth(Q, w, S, n)` Closed-form wide-channel solution `h = ((Q·n)/(w·√S))^(3/5)`
`compute_water_level(discharge, roughness, flow_grid, streams, dem)` Convenience: ties everything together; output is `WaterLevelGrid` with h at stream cells, NaN elsewhere
`compute_rainfall_inundation(water_level, hand, flow_grid, streams)` Broadcasts per-stream h upstream via `pyflwdir.basins`, applies `depth = max(W - HAND, 0)`
`WaterLevelGrid`, `RainfallInundationDepth` Carry full provenance (precip_source, duration_s, method strings)

Test plan

  • `pytest -m "not integration"` — 323 passed (294 prior + 29 new; 16 deselected integration)
  • Slope helper: clamped to floor on flat DEM; positive on inclined; mountain-catchment range on Robit Bata (median ~5%)
  • Manning: pinned hand-computed h=1.150 m for Q=10, w=10, S=0.001, n=0.04; monotonic in n (rougher → deeper) and S (steeper → shallower); zero/negative inputs handled
  • End-to-end on Robit Bata fixtures: outlet stream h = 10.07 m (matches independent computation), flooded fraction = 9.5% under 100 mm × 6 hr — physically plausible for a small clay-rich Ethiopian catchment
  • All non-stream cells in `WaterLevelGrid` are NaN by construction; stream cells are finite
  • `compute_rainfall_inundation` raises `ValueError` on shape mismatch
  • Smoke test stage 16 produces a hillshade + log-flood-depth + stream-overlay map showing the dendritic flood pattern

Notes

  • Defaults follow Andreadis et al. 2013 (LM coefficients) and standard NEH min-depth/min-slope conventions; all overridable.
  • `WaterLevelGrid` masks non-stream cells to NaN to make the field semantically clean — only channels carry water level. `RainfallInundationDepth` then broadcasts those values upstream to every draining cell.
  • Out-of-basin cells (ridge cells in the square-cropped bbox that drain off the edge before hitting any stream) stay dry, which is the correct behaviour.
  • Time-resolved (kinematic-wave or unit-hydrograph) routing remains a v0.3 ambition; this PR uses the steady-state V/T peak-discharge approximation appropriate for small basins and intense storms.

Second of two routing PRs. Closes the hydraulic side of the rainfall->
flood pipeline by computing per-stream-cell water levels via Manning's
normal-depth equation, then broadcasting those water levels upstream
along the flow direction grid to drive HAND-based inundation. Combined
with PR1's runoff accumulation + peak discharge, the project now turns
synthetic uniform rainfall (or any future PrecipGrid) into a full
rainfall-driven flood depth raster with no remaining gaps.

Module additions (floodpath/routing/):
- utils.py: pixel_sizes_m() factored out of cell_areas_m2 for reuse;
  slope_along_flow(flow_grid, dem) computes per-cell slope (m/m) along
  the downstream D8 direction with a min_slope floor for numerical
  safety in Manning's sqrt(S).
- manning.py: leopold_maddock_width(Q, a=2.5, b=0.5) for global
  natural-channel width-discharge relation; manning_normal_depth(Q,
  w, S, n) returns the closed-form wide-channel solution
  h = ((Q*n)/(w*sqrt(S)))^(3/5); compute_water_level() is the
  high-level convenience producing a WaterLevelGrid with h at stream
  cells and NaN elsewhere.
- flood.py: compute_rainfall_inundation(water_level, hand, flow_grid,
  streams) uses pyflwdir.basins(idxs=stream_indices) to label every
  cell with its drainage stream index, then applies depth = max(W -
  HAND, 0) to every cell. Out-of-basin cells (square-cropped bbox
  ridges that flow off the edge) stay dry.

New dataclasses: WaterLevelGrid (h at stream cells, m) and
RainfallInundationDepth (per-cell flood depth driven by per-stream
water levels) — the latter has flooded_fraction(), stats(), and the
full provenance chain (precip_source, duration_s, method).

Tests: 29 new — pixel_sizes_m at equator vs 60 deg N, slope_along_flow
on flat / inclined / Robit Bata DEMs, Leopold-Maddock at canonical Q,
Manning at hand-computed (Q=10,w=10,S=0.001,n=0.04) -> h=1.150 m, plus
end-to-end pinned values on the Robit Bata fixtures (outlet h ~ 10.07 m,
flooded fraction ~ 9.5% under 100 mm/6 hr storm). Shape-mismatch errors
checked.

Smoke test gains stage 16 (rainfall-driven flood map): hillshade +
log-scale flood depth + stream overlay. The pipeline is now
end-to-end physical: rain -> CN -> SCS -> accumulation -> discharge ->
Manning -> water level -> HAND -> flood.
@rehsani rehsani merged commit 3a49f21 into main May 4, 2026
1 check passed
@rehsani rehsani deleted the step-9-manning-and-rainfall-flood branch May 4, 2026 19:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant