VFlood
English version
Implementation Status

10. What's implemented (and what isn't)

⚠ Known limitation as of this writing: a 264-hour regression against the real rokan15s_ Fortran run (VFLOOD_RRI_RUN_SLOW_TESTS=1 pytest tests/test_phase2_rokan.py::test_matches_reference_full_264_hours) originally showed discharge (qr) diverging substantially once the storm peak arrives. Two real, confirmed bugs in the adaptive integrator were found and fixed: (1) integrator.py's error control took abs() of the RKF45 error term where RRI's own Fortran uses a signed maxval (RRI.f90:718); (2) engine.py never passed on_floor_recompute=True to the river integrator, silently skipping an RRI-specific flux-recompute step. Fixing both brought hours 51-53's qr error from 4-8% down to ~0%.

A residual divergence from hour ~54 onward is now root-caused, not just observed: it traces to river reaches with an essentially flat bed (confirmed against real reference data — bed-slope-driven dh as low as ~0.00001-0.0002 over 460-650m spacing at the worst-affected reach). Since discharge scales with sqrt(dh), a sub-centimeter difference in simulated water depth at such a reach produces a large relative swing in qr — an inherent floating-point-precision sensitivity of the diffusive-wave scheme on flat terrain, not a further discrete bug to find.

Checked hour-by-hour across the complete 264-hour storm (full standalone comparison against every real reference hour, not just the test's 8 checkpoints). Depth error stays bounded for the entire run and never destabilizes: hs/hr max error across the entire grid peaks at ~10cm/~17cm respectively (hours 223/124), mean hr error in the storm's back half (hours 150-264) is only ~5cm — small relative to multi-meter flood depths, so flood extent/depth output is trustworthy for the whole event. Discharge (qr) relative error at the worst near-flat-bed cell, however, does not settle down: it fluctuates noisily throughout, trending higher in the back half (mean ~31% hours 54-264, ~38% hours 150-264, worst single point 69.7% at hour 224) as more of the deltaic/downstream terrain floods and more flat-bed cells become active — consistent with the root cause above, not a sign of numerical instability (nothing diverges to non-physical values; it's noisy-but-bounded throughout, not runaway). Bottom line: trust hs/hr for the full storm; treat qr at individual flat-reach cells as unreliable, particularly downstream/deltaic reaches, for the whole event, not just the peak.

Everything below is a faithful port of the corresponding RRI Fortran source file; "verified" means checked against a real reference simulation, not just read carefully.

ModuleStatusVerified against
Slope + river diffusive/kinematic-wave routing, RKF45 integrator⚠️ Partially verifiedChecked against every real reference hour across the full 264h storm — depth fields (hs/hr) match well throughout; qr diverges at specific flat-bed reaches for the whole event, not just the peak — root-caused, see warning above
River-slope exchange, Green-Ampt infiltration⚠️ Partially verifiedDepth fields checked precisely across the full 264h storm; same flat-bed qr caveat applies
Groundwater tank, evapotranspiration✅ DoneNot verified — no local project uses these
Multi-class land-use, explicit river-geometry files✅ DoneReal RRI flow network + synthetic parameters
Rating-curve cross-sections✅ DoneNot verified — hand-derived synthetic tests only
Boundary conditions (water-level / discharge)✅ DoneNot verified — synthetic tests only; water-level boundary is a once-per-timestep snap, not a per-substep hard pin (see river.py docstring)
Dam operation, diversion channels✅ DoneNot verified — synthetic tests only
GeoTIFF / PNG / KML export (vflood-export)✅ DoneNew capability — RRI itself never produced GeoTIFF; checked directly, not against reference output
GeoTIFF import from GEE/HEC-GeoHMS (vflood-import), incl. multi-band rainfall stacks (rain-geotiff-stack, geo_import.read_geotiff_stack)✅ DoneRound-trip + real-format-convention checks (rain.dat block timing confirmed against real rokan15s_ data); multi-band variant checked to produce numerically identical rain.dat output to the equivalent single-band-file-list input, incl. band-name recovery from GDAL band descriptions and the aligned (DEM-grid-resampled) variant project_scaffold.scaffold_project's rain_geotiff_stack= uses
GEE script generation (vflood-gee), incl. DEM source choice + editable DEM scale + opt-in --include-gee-flow-network/--include-evapotranspiration + IMERG multi-band rainfall stack✅ DoneDataset IDs/band names (incl. WWF/HydroSHEDS/03CONDEM's b1, WWF/HydroSHEDS/03DIR's existence but no matching 3-arc-sec accumulation product, NASA/GPM_L3/IMERG_V07's precipitation band unit (mm/hr, 30-min cadence) confirmed two independent ways against its correct daily-aggregation math, and MODIS/061/MOD16A2GF's PET/0.1 scale factor) verified against Earth Engine's public catalog; scripts themselves not run (no GEE auth here). Default topo script exports DEM only (1 export, no 15DIR/15ACC); default pipeline excludes evapotranspiration (3 files, not 4) — both checked directly, incl. that the opt-in flags restore the old behavior and that the DEM-source-mismatch consistency warning fires/doesn't fire correctly. Map.centerObject→Map.setCenter (a live Earth Engine centroid-bug workaround) and the client-loop→server-side-map rewrite (avoids a fragile .getInfo()-per-iteration pattern) both checked directly against a real, independently-validated reference script — confirmed the computed bbox center matches that script's own independently-hand-computed value exactly
VFloodPrep desktop GUI (vflood-prep)✅ DoneNew capability — a Tkinter front end over gee_codegen/project_scaffold, no new library logic of its own; checked end-to-end on a synthetic DEM + rainfall GeoTIFF stack (background-thread build -> load_model succeeds), same bar as project_scaffold.py's own row below
Pre-run project validation (validate.py)✅ DoneNew capability — checked directly by deliberately corrupting a real project (topo grid georeference mismatch, non-overlapping rain grid, a missing required file, an unparseable RRI_Input.txt) and confirming each is caught with the right message, plus a full-pass check against the real, uncorrupted rokan15s_ project
cli.run_project (shared run implementation, incl. mid-run cancellation)✅ DoneRefactored out of main() behavior-preserving — checked directly: known grid shape/river-cell-count on a real run, output byte-for-byte (within the .out format's own 5-decimal precision) matches calling engine.run directly, and a cancelled run keeps every already-completed step's output while stopping before the next
VFloodControl desktop GUI (vflood-control)✅ DoneNew capability — a Tkinter front end over validate.py/cli.run_project, no new library logic of its own; checked end-to-end with a real driven mainloop() (not just widget construction) against the real rokan15s_ project for all three flows — Check Inputs, Run to completion, and Cancel mid-run — see tests/test_phase13_control.py for the library-level half of that same coverage
VFloodControl pipeline orchestration (embedded VFloodPrep, chained Export/NetCDF/Analyze subprocesses)✅ DoneNew capability — checked end-to-end with a real driven mainloop() against rokan15s_: embedded-VFloodPrep callback wiring, Check auto-unlocking post-process from pre-existing output, Export, NetCDF bundling, and the NetCDF-then-Hydrograph auto-chain (confirmed real CSVs/PNGs written); one real bug (Export's default formats requested kml without the --start it needs) found and fixed by this same testing before shipping
Indonesian catchment lookup (basin_atlas.py, the "Pick an Indonesian Catchment" picker in VFloodPrep/VFloodControl)✅ DoneNew capability, reads a separate sibling project's (basin-atlas) own static JSON index directly, incl. a Wilayah Sungai (KemenPU/PUPR) join and named_only filtering — checked against that real data: ~17,000 real catchments load (~8,300 with named_only), search by name/code/agency/river-basin finds known real catchments (e.g. "Besitang"/DAS130030, correctly showing agency "WAMPU SEI ULAR" and river basin "ALAS-SINGKIL" with their source data's own "BPDAS "/"WS " prefixes stripped), sortable columns confirmed to actually reorder rows (ascending then descending) via a real driven mainloop(), and the km-buffer math is verified to widen more in longitude-degrees at high latitude than near the equator, as it should
Major flood event lookup (disaster_events.py, the "Pick a Major Flood Event" picker in VFloodPrep)✅ DoneNew capability, reads a third-party sibling project's (disasterLab) own compiled BNPB DIBI static JSON directly, bbox-filtered, casualty-only — checked against that real data: exact real count (463 of ~15,600 total) for the casualty+geocoded filter, a known real event (Somba Upu/Gowa, 2019-01-22, 56 dead) matches exactly, bbox containment correctly includes/excludes by real location, and the buffered date window (incl. its GEE-filterDate-exclusive +1-day adjustment) checked against hand-computed expected dates; one real data-quality bug found and fixed before shipping — disasterLab encodes "no coordinates" as a literal (0, 0) sentinel, not null, which an unfixed reading would have silently treated as ~920 real geocoded points in the Gulf of Guinea
NetCDF export + basin-atlas-style plotting (vflood-netcdf)✅ DoneNew capability — checked directly (dimensions, coordinate ordering, masking)
Local DEM terrain derivation (vflood-import terrain, pysheds)✅ DoneNew capability — synthetic-DEM mass-conservation check only (flow accumulation exactly totals the grid at a single outlet); not checked against a real DEMNAS/BATNAS tile
Satellite rainfall NetCDF import (vflood-import rain-netcdf)✅ DoneNew capability — round-trip + unit-conversion checks on synthetic NetCDF; not checked against a real CHIRPS/GSMaP/IMERG file
VRain CSV/manifest import, Thiessen weighting (vflood-import rain-vrain)✅ DoneNew capability — checked directly against VRain's own real demo file schemas; Thiessen geometry checked on synthetic station layouts only
KLHK land-cover scheme (vflood-import raster --scheme klhk)✅ DoneClass codes/names are a commonly-published convention, not verified against a real KLHK raster's attribute table — treat as a starting point more than usual, see §4c
Spatial/temporal post-processing (vflood-analyze: zonal stats, Moran's I, hydrograph/ACF)✅ DoneNew capability — Moran's I checked against exact textbook values (checkerboard/clustered patterns), zonal stats against hand-computed values, hydrograph extraction against a synthetic NetCDF with known per-cell values
Combined rain/Q/river-depth/slope-depth presentation chart, static + animated, 2 layouts (vflood-analyze combined)✅ DoneNew capability — basin_average_rainfall checked against a hand-computed spatial mean per rain.dat block; remap_rainfall_to_grid_series checked against an identical-grid identity remap and multi-block time selection; all four chart functions checked to produce a real PNG/GIF, incl. each GIF's frame count exactly matching the number of output timesteps; demoed end-to-end (both layouts) on the real Sesayap project's in-progress output
Hydrograph chart rendering (vflood-analyze --plot/plot)✅ DoneNew capability, scoped to the hydrograph only (zonal/moran/ACF are CSV-only by design) — checked that each chart function runs and produces a real (non-empty) PNG; not pixel-verified (matplotlib rendering isn't hand-checkable the way the statistics underneath are)
Catchment masking + automatic checkpoint placement (watershed.py, --catchment-boundary/--pour-point/--auto-mask-outlets/--auto-locations on vflood-import init, VFloodPrep Tab 2)✅ DoneNew capability — catchment delineation checked against a synthetic DEM where the whole grid provably drains to one outlet; a real, confirmed pysheds.grid.Grid.catchment() limitation found and worked around with an independent upstream flood-fill; outlet auto-detection, main-channel tracing (checked to follow the larger of two known-different-sized tributaries at a hand-built confluence), and fractional checkpoint placement all checked directly; demoed end-to-end on the real Sesayap DEM — masking to the single dominant outlet cut an already-aggregated 124,834-cell rectangle to 41,711 active cells
Real-world calendar dates (start_date.txt companion file) in the run log, NetCDF time coordinate, KML auto---start, hydrograph/combined chart axes✅ DoneNew capability — round-trip checked; NetCDF time units checked to use real CF "seconds since <date>" when known, plain relative fallback otherwise; never part of RRI_Input.txt itself
No-download areal-rainfall preview chart (00_rainfall_preview.js)✅ DoneNew capability — checked directly: widens the window, same half-hourly-rate correction and Map.setCenter fix as the real export, no Export.image.toDrive call at all
Antecedent rainfall spin-up (vflood-import init-with-spinup, VFloodPrep Tab 2)✅ DoneNew capability — checked end-to-end on a real spin-up run: spin-up's own lasth/outnum land exactly on spinup_days*24, its output exists after running, the event project's init switches reference that exact file, and the event's own lasth reflects only the event-band count, never extended
Basin-average rainfall awareness chart + Combined Chart button (VFloodPrep "View Rainfall Chart", VFloodControl)✅ DoneNew capability — checked directly; both are thin wrappers over already-tested library/subprocess calls
Interactive outlet picker ("Pick Outlet(s)..." in VFloodPrep Tab 2)✅ DoneNew capability — checked end-to-end with a real driven mainloop() against a real synthetic DEM: derives the flow network, lists real auto-detected candidates, confirms the selected row's lon/lat comes back correctly
VFloodControl "no gauge locations" auto-detect fallback (Analyze Hydrograph/Combined Chart)✅ DoneNew capability — checked end-to-end on a real scaffolded project: already-configured (no prompt), declined (untouched), and accepted (real location.txt written, hydro_switch flipped both in-memory and on disk); found and fixed a real inconsistency this uncovered — a project whose location.txt already existed but whose hydro_switch was still 0
vflood-netcdf --plot-max/--geotiff-max (peak-frame PNG/GeoTIFF + step/date report)✅ DoneNew capability — domain_max_series checked against a hand-computed per-frame spatial max on a synthetic NetCDF; demoed end-to-end on the real Sesayap project — correctly identified qr_max=405.42 at step 120 (2023-09-22 00:00), matching the storm-peak day independently identified from the raw rainfall data. --geotiff-max checked against a real run of the rokan15s_ reference project — the written GeoTIFF's embedded max matches exactly, real CRS confirmed, independent from --plot-max
VFloodControl NetCDF completeness check (_netcdf_missing_variables, Analyze Hydrograph/Combined Chart)✅ DoneReal bug found and fixed: write_simulation_netcdf opens in "w" (truncate, not merge) mode, so bundling with a narrower --variables list silently drops previously-bundled variables from an existing simulation.nc — a stale-but-present file then raised a raw KeyError instead of triggering a re-bundle. The two buttons now check which variables the existing file actually has, not just whether it exists, and auto-rebundle the full hs,hr,qr set when anything is missing. Checked with pytest cases including a direct reproduction of the bug, plus verified live end-to-end against the real Sesayap project by deliberately re-corrupting simulation.nc and confirming both buttons auto-repair it
DEM+hillshade animated flood GIF (vflood-export --formats gif)✅ DoneNew capability — checked directly (shape/range/compositing math) plus a real 96-frame animation rendered from the Rokan basin and visually inspected; hillshade vertical exaggeration tuned empirically against that same real DEM
Slope/groundwater directional flux output (qu/qv/gu/gv)✅ DoneDirect port of RRI.f90's own formula — checked against a real Rokan run (non-zero, finite, sensible values from hour 3 of a real storm)
Derived velocity + vector map (velocity.py, vflood-export --formats vector,vectorgif)✅ DoneNew capability, not a Fortran port — checked directly against hand-computed values (dry-cell zero-division guard, direction-vector correctness); a real 60-frame animation was rendered from the Rokan basin, visually inspected, and one real rendering bug (auto-scaled arrow length blown out by wetting-front division spikes) was found and fixed before shipping
Numba JIT acceleration❌ Not startedCurrently plain NumPy — correct at low flow, but see the storm-peak issue above before assuming "correct" generally
File-based initial conditions (resuming a run)✅ DoneDirect port of RRI.f90's own clamp+domain-mask logic — checked against real reference .out data (exact match after the documented clamp) and a rigorous resume-vs-continuous equivalence check (mm-level agreement with a correctly time-shifted rain.dat, see §2)
DEM-anchored grid alignment (grid_align.py)✅ DoneNew capability, built on rasterio.warp.reproject (not hand-rolled) — checked directly against known nearest/bilinear resample results on synthetic grids
One-call DEM-anchored project scaffolding (vflood-import init, project_scaffold.py), incl. --dem-aggregate-factor running/output resolution coarsening✅ DoneChecked end-to-end on a synthetic DEM + synthetic rainfall NetCDF: scaffolded project loads via load_model and actually runs to completion under VFlood-RRI; hydraulic parameters are documented generic placeholders, not calibrated, see §4d. --dem-aggregate-factor checked directly (coarsened grid shape/cellsize match the requested factor exactly, dir/acc re-derived from the coarsened DEM, non-dividing/≤1 factors rejected loudly) and against a real 2154×2088 GEE DEM export (Sesayap basin): factor 6 (the largest common divisor of that tile's own dimensions) cut 4.49M active cells to 124,834
Levee break, PET.txt reading❌ Not startedRRI itself doesn't call levee-break in its main loop either

"Not verified against real RRI output" means: no project available during development actually turns that feature on, so its correctness rests on reading the Fortran source carefully and testing the math by hand — not on matching a real simulation. Treat those modules with proportionally more scrutiny before trusting them for anything consequential (e.g. an actual flood-warning decision).