Imported from lzwei196/KISS-Knowledge-Infrastructure-for-Scientific-Simulation (
models/SFINCS/SKILL.md). Install upstream withnpx skills add lzwei196/KISS-Knowledge-Infrastructure-for-Scientific-Simulation --skill SFINCS. Copyright stays with the author.
MANDATORY EXECUTION POLICY — READ BEFORE PROCEEDING
You MUST run the actual model binary or package described in this document. If the model fails to import, compile, or execute, you MUST:
- Check
diagnostics/triplets.yamlfor a matching error pattern- Try the fix suggested in the triplet's
remedysection- If still failing, report the error to the user with full details
You MUST NOT substitute a simplified Python formula, regression equation, or hand-coded approximation in place of the real model. Doing so produces scientifically invalid results and defeats the purpose of the KI.
Before starting, run:
python preflight_check.py(in this KI directory) to verify that the model binary/package and required data are available.DEBUGGING PROTOCOL — When something goes wrong, follow this order:
- Check triplets —
diagnostics/triplets.yamlmay already cover this error- Read official docs — The model's own documentation for expected formats/units
- Find working examples — Check
outputs/or the model's shipped test data- Fix the tool — With knowledge of what "correct" looks like
Do NOT write custom debug scripts. The answers are in the docs and examples.
KI map — what to read, and when
| when you need | read | why |
|---|---|---|
| FIRST, always | preflight_check.py |
run it (python preflight_check.py): proves env/binary/data are usable and emits a machine-readable PREFLIGHT_REPORT= line. Do not debug a run that never had a healthy environment. |
| to run the pipeline stages | tools/ (13 tools) |
the executable pipeline. Read each tool's argparse (--help) before composing a command; SKILL.md's stage table says which tool serves which stage. |
| before running a stage | docs/s*_*.md (8 stage docs) |
per-stage procedure, verification and traps — the how-to that SKILL.md's overview compresses. |
| on ANY error, before debugging | diagnostics/triplets.yaml (44 entries) |
symptom → diagnosis → remedy for this model's known failure modes. Check here FIRST; the answer usually exists. Never renumber or rewrite entries. |
| to know what an output IS | dag.yaml |
the model's identity: every output's medium, units, validation_rank (1 = the headline variable) and observability. Scoring and obs-binding read THIS — when asked 'what does this model predict', the dag is the answer, not a guess. |
| when building inputs / parsing outputs | docs/format_spec.yaml |
exact I/O shapes + known_issues, projected from dag + triplets. Regenerate with ki_tools_common/generate_format_spec.py after changing either — never hand-edit. |
| to judge a run's skill | docs/validation_convention.yaml |
how this model's field judges it validated: per-dag_variable metrics, directions and CITED pass-bands. A run is graded against these, not against intuition. |
| for claims and thresholds | docs/gathered_papers.json (20 papers) + docs/papers_index.md |
the literature this KI is judged by; each entry's text_path is fetched full text in the central paper cache. role: benchmark marks the model's own skill paper. |
| for a machine-readable summary | knowledge_infrastructure.yaml |
the manifest (package, pipeline, validation tier, counts) — projected by ki_tools_common/generate_ki_manifest.py; regenerate after structural changes, never hand-edit. |
| what past runs learned | .kdt_evolution.jsonl |
append-only memory of previous runs and fixes on this KI. |
Projected 2026-08-17 from the KI's actual contents — 10 components present. Refresh: python3 ki_tools_common/generate_skill_map.py --ki_dir <this KI>.
Executable tool index (projected — complete by construction)
Every public tool in this KI, by exact path. What each is FOR lives in the
human-written Tool Inventory above; --help on any of these prints its arguments.
| tool (exact path) | invocation |
|---|---|
tools/calib_run.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/calib_run.py --help |
tools/s1_domain/setup_sfincs_domain.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/s1_domain/setup_sfincs_domain.py --help |
tools/s2_topobathy/build_sfincs_topobathy.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/s2_topobathy/build_sfincs_topobathy.py --help |
tools/s3_roughness/build_sfincs_roughness.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/s3_roughness/build_sfincs_roughness.py --help |
tools/s4_forcing/cama_to_sfincs_boundary.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/s4_forcing/cama_to_sfincs_boundary.py --help |
tools/s4_forcing/hydat_to_sfincs_boundary.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/s4_forcing/hydat_to_sfincs_boundary.py --help |
tools/s4_forcing/prepare_sfincs_rainfall.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/s4_forcing/prepare_sfincs_rainfall.py --help |
tools/s4_forcing/register_vertical_datum.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/s4_forcing/register_vertical_datum.py --help |
tools/s6_config/generate_sfincs_inp.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/s6_config/generate_sfincs_inp.py --help |
tools/s7_execution/run_sfincs.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/s7_execution/run_sfincs.py --help |
tools/s8_postprocess/extract_sfincs_results.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/s8_postprocess/extract_sfincs_results.py --help |
tools/s8_postprocess/plot_sfincs_flood_map.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/s8_postprocess/plot_sfincs_flood_map.py --help |
tools/s9_validation/screen_obs_independence.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/s9_validation/screen_obs_independence.py --help |
13 public tools; _-prefixed helpers and packaging files excluded.
Data Preparation
Forcing data
Data Sources: Use from ki_tools_common.load_forcing import load_daily_forcing for CMFD/MSWX/NASA POWER.
SFINCS forcing tools are in tools/s4_forcing/ in this KI:
tools/s4_forcing/prepare_sfincs_rainfall.py— Converts CMFD/MSWX precipitation to SFINCS NetCDF rainfall (mm/3hr → mm/hr)tools/s4_forcing/cama_to_sfincs_boundary.py— Converts CaMa-Flood output to SFINCS boundary conditions (water level or discharge)
Data Validation Reference: See data_ki/CMFD/SKILL.md for CMFD unit documentation and known traps.
SFINCS v2.x (Super-Fast INundation of CoastS) — Knowledge Infrastructure
Package: hydrocraft-sfincs v1.0.0
Model: SFINCS v2.x (Deltares)
Created by: Jianyun Zhang Research Group, Hohai University
Last updated: 2026-03-22
Stats: 9 tools | 8 skill documents | 28 diagnostic triplets | 12 error log entries | ~2,379 lines of validated Python
Validation: Step 3 — Bengbu (Huai River) flood test (2026-03-22) — 384x455 cells, 100m, 397s, 6.99m max depth, CaMa-Flood cross-validated. Previous: Chaohe (2026-03-21) — 174x171 cells, 5.7s, 7.29m max depth.
Overview
This knowledge infrastructure enables fully autonomous 2D flood inundation simulation using SFINCS on any domain worldwide, integrated with HydroCraft's VIC + CaMa-Flood pipeline. The 8 validated tools handle domain setup, DEM processing, forcing conversion, configuration, execution, and post-processing.
What SFINCS does: Reduced-complexity 2D shallow water solver for rapid flood inundation modeling:
- Coastal flooding: Storm surge, tides, sea-level rise, wave setup
- Fluvial flooding: River overflow from CaMa-Flood discharge as boundary condition
- Pluvial flooding: Rainfall-driven urban/rural flooding from CMFD/MSWX precipitation
- Compound flooding: Combined coastal + fluvial + pluvial (primary use case)
- Subgrid: Coarse computational grid (50-200m) with fine bathymetry resolution (5-10m)
Key difference from CaMa-Flood: CaMa-Flood routes water through 15-arcmin (~28km) river networks. SFINCS resolves flood depth and extent at 10-200m within a local domain. They are complementary: CaMa-Flood provides river boundary conditions for SFINCS.
HydroMT-SFINCS: The Python model builder (hydromt_sfincs — NOT actually installed on this server as of 2026-08-05: import hydromt_sfincs -> ModuleNotFoundError; the binary formats in 'Critical Domain Knowledge' item 8 were therefore re-derived directly from the solver) provides automated grid generation, DEM processing, and Manning's n from land cover. The validated tools wrap HydroMT-SFINCS calls and add HydroCraft-specific validation, unit conversion, and coupling logic.
Installation
Binary
SFINCS solver: model/sfincs/bin/sfincs
Version: v2.3.2 mt. Faber+ (compiled 2026-03-21)
Source: github.com/Deltares/SFINCS main branch (GPL-3.0)
Build: gfortran 13.3 + libnetcdff 4.6.0
Dependencies: libnetcdff7, libgfortran5, libnetcdf19, libhdf5 (system packages)
Validated: Chaohe basin flood test (174x171, 100m, 5.7s, 7.29m max depth) + 50x50 flat test
To compile from source (if binary not yet built):
cd /tmp
curl -sL -o sfincs.tar.gz https://github.com/Deltares/SFINCS/archive/refs/heads/main.tar.gz
tar xzf sfincs.tar.gz
cd SFINCS-main/source
# Install autotools if needed: sudo apt install autoconf automake libtool
bash build_gfortran_cpu.sh
cp src/sfincs KISSPATH_BINARIES/sfincs/bin/sfincs
chmod +x KISSPATH_BINARIES/sfincs/bin/sfincs
Python dependencies (all in HydroCraft venv)
hydromt_sfincs==1.2.2, rasterio, xarray, geopandas, numpy, scipy, netCDF4, pyproj, matplotlib
CLI Usage
SFINCS reads sfincs.inp from the current working directory. No command-line arguments.
cd /path/to/run_dir # Must contain sfincs.inp and all referenced files
/path/to/sfincs # Reads sfincs.inp, writes output to same directory
For parallel: export OMP_NUM_THREADS=4; /path/to/sfincs
Pipeline (8 stages)
| # | Stage | Tool(s) | Description |
|---|---|---|---|
| 1 | Domain setup | setup_sfincs_domain |
Define grid from shapefile/bbox, auto-UTM, compute resolution |
| 2 | Topography | build_sfincs_topobathy |
Resample DEM to grid, build mask (active/outflow/inactive) |
| 3 | Roughness | build_sfincs_roughness |
Manning's n from AVHRR land cover or uniform value |
| 4 | Forcing & BC | prepare_sfincs_rainfall, cama_to_sfincs_boundary |
Precipitation (mm/hr) + river discharge/water level BC |
| 5 | Structures | (manual) | Optional: levees (sfincs.thd), weirs, drainage |
| 6 | Configuration | generate_sfincs_inp |
Assemble sfincs.inp with CFL-stable timestep |
| 7 | Execution | run_sfincs |
Preflight checks + run binary + output validation |
| 8 | Post-processing | extract_sfincs_results, plot_sfincs_flood_map |
Flood depth/extent maps, statistics, GeoTIFF export |
Parallelism
Stages 2, 3, 4 can run in parallel after stage 1. Stage 5 is optional. Stage 6 depends on 2+3+4+(5). Stage 7 depends on 6. Stage 8 depends on 7.
Tools Reference
| Tool | Stage | Script Path | Lines | Purpose |
|---|---|---|---|---|
setup_sfincs_domain |
s1 | tools/s1_domain/setup_sfincs_domain.py |
210 | Grid from shapefile/bbox, auto-UTM, CFL dt estimate |
build_sfincs_topobathy |
s2 | tools/s2_topobathy/build_sfincs_topobathy.py |
300 | DEM to sfincs.dep/msk/ind; auto-selects China DEM / BedMachine Greenland+Antarctica / GLO-30. --min_elev, --outflow_max_elev (dt_v019) |
build_sfincs_roughness |
s3 | tools/s3_roughness/build_sfincs_roughness.py |
200 | AVHRR land cover to Manning's n map |
prepare_sfincs_rainfall |
s4 | tools/s4_forcing/prepare_sfincs_rainfall.py |
270 | CMFD/MSWX to NetCDF precip (mm/3hr -> mm/hr) |
cama_to_sfincs_boundary |
s4 | tools/s4_forcing/cama_to_sfincs_boundary.py |
290 | CaMa-Flood outflw/sfcelv to sfincs.src/.dis or .bnd/.bzs |
hydat_to_sfincs_boundary |
s4 | tools/s4_forcing/hydat_to_sfincs_boundary.py |
300 | OBSERVED gauge BC: HYDAT daily Q to sfincs.src/.dis (snapped onto the lowest active channel cells), HYDAT stage stations to sfincs.obs + obs_levels_<id>.csv for scoring |
generate_sfincs_inp |
s6 | tools/s6_config/generate_sfincs_inp.py |
250 | sfincs.inp with auto CFL timestep |
run_sfincs |
s7 | tools/s7_execution/run_sfincs.py |
210 | Execute with preflight, log check, output validation |
extract_sfincs_results |
s8 | tools/s8_postprocess/extract_sfincs_results.py |
230 | Parse sfincs_map.nc, flood stats, GeoTIFF export |
plot_sfincs_flood_map |
s8 | tools/s8_postprocess/plot_sfincs_flood_map.py |
200 | Publication-quality flood depth map |
Total: 10 tools, ~2,680 lines of validated Python.
Skill Documents
| Stage | Document | Covers |
|---|---|---|
| s1 | docs/s1_domain_skill.md |
Grid resolution vs basin area, UTM selection, buffer sizing |
| s2 | docs/s2_topobathy_skill.md |
DEM sources, vertical datum, subgrid tables |
| s3 | docs/s3_roughness_skill.md |
Manning's n by land cover, urban roughness |
| s4 | docs/s4_forcing_skill.md |
Precipitation units, CaMa-Flood coupling, compound events |
| s5 | docs/s5_structures_skill.md |
Thin dams, weirs, drainage structures |
| s6 | docs/s6_config_skill.md |
CFL timestep, physics options, output settings |
| s7 | docs/s7_execution_skill.md |
Runtime estimation, GPU/CPU, convergence |
| s8 | docs/s8_postprocess_skill.md |
Flood maps, depth classes, validation |
6. Output Description
Source for this section is dag.yaml. If this section and dag.yaml ever disagree,
dag.yaml wins.
Headline output: flood extent (derived from hmax > threshold) is the dag's
rank-1 output and the variable this model is judged by. Dag description, verbatim:
Flood-water wet/dry extent classification derived from hmax exceeding a depth threshold.
Unit: category / area.
Output variable (dag var) |
Validation role | Unit | Description / scoring note |
|---|---|---|---|
flood extent (derived from hmax > threshold) |
rank 1 headline output | category / area |
Flood-water wet/dry extent classification derived from hmax exceeding a depth threshold. |
hmax |
other dag output | m |
Maximum water depth; use this, not zsmax, for flood-depth mapping. |
zsmax |
other dag output | m |
Maximum water-surface elevation. |
zs |
other dag output | m |
Water-surface elevation. |
point_zs / point_h |
other dag output | m |
His-point water-surface elevation / depth; requires shared datum before scoring. |
Do not report SFINCS skill from hmax, zsmax, zs, or point_zs / point_h
when the benchmark asks for the dag's headline variable. Convert hmax to the
wet/dry extent classification with the requested threshold, then score that rank-1
output against an observed inundation mask.
8. Unit Table and Conversion Table
Exact file shapes live in docs/format_spec.yaml; this table records the units that
agents must preserve when building inputs and interpreting outputs.
| Variable / file | Source unit | SFINCS / KI unit | Conversion | Notes |
|---|---|---|---|---|
| CMFD precipitation | kg m-2 s-1 |
mm/hr |
multiply by 3600 |
Current CMFD unit trap; if max precip is below 0.01 mm/hr, check units. |
| VIC forcing precipitation | mm/timestep for 3-hourly forcing |
mm/hr |
divide by 3 |
VIC ASCII precipitation is column 1, not column 0. |
SFINCS precipfile |
mm/hr |
mm/hr |
none | ASCII format: time_seconds precip_mmhr; prefer precipfile, not netprecipfile. |
| CaMa-Flood discharge boundary | m3/s |
m3/s |
none | Written as SFINCS source/discharge boundary forcing. |
| CaMa-Flood water-level boundary | m |
m |
none after datum check | DEM and boundary water levels must share a vertical datum. |
hmax |
model output | m |
none | Maximum water depth. Mask inactive cells and +9999 fill values before statistics. |
zsmax / zs |
model output | m |
none after datum check | Water-surface elevation; not flood depth. |
point_zs / point_h |
model output | m |
none after datum check | His-point output; score only against independent stage/depth observations. |
flood extent (derived from hmax > threshold) |
derived from hmax |
category / area |
apply the stated wet/dry depth threshold | Dag rank-1 output. Score with CSI against observed inundation masks. |
Validated Results
Performance Metrics - judged against the field's bar, not intuition
Source for this subsection is docs/validation_convention.yaml. If these bars and
the convention file ever disagree, the convention file wins. A run with only physical
sanity checks is not a field validation score unless it is compared to an appropriate
observation and the metric below is reported.
| Dag variable | Metric | Direction | Convention bar, with citation keys |
|---|---|---|---|
flood extent (derived from hmax > threshold) |
csi |
maximize | satisfactory >= 0.5 (wing2018, wing2021); good >= 0.7 (wing2018, wing2021); very_good >= 0.8 (wing2018, wing2021) |
point_zs / point_h |
rmse |
minimize | very_good <= 0.1 (garcia2022, muis2022, wing2021); good <= 0.2 (garcia2022, muis2022, wing2021); satisfactory <= 0.4 (garcia2022, muis2022, wing2021) |
point_zs / point_h |
bias |
zero_centered | very_good: no cited threshold; good within 10 of zero (muis2022); satisfactory within 18 of zero (muis2022) |
The headline bar is for flood extent (derived from hmax > threshold): CSI must be
reported against an observed inundation mask, with satisfactory >= 0.5
(wing2018, wing2021), good >= 0.7 (wing2018, wing2021), and very_good >=
0.8 (wing2018, wing2021). If no independent observation exists, report a null
metric with the reason rather than substituting a proxy score.
Chaohe Basin Flood Test (2026-03-21)
- Domain: 174x171 cells at 100m resolution, downstream Chaohe basin (40.50-40.65N, 116.60-116.80E)
- Event: Aug 2-12, 2008 monsoon rainfall (88.7mm total from CMFD via VIC forcing)
- DEM: China DEM 90m, elevation range 143-1387m
- Runtime: 5.7 seconds (OpenMP, 4 threads)
- Max flood depth: 7.29m (valley bottoms)
- Flood volume: 37.9 million m3
- Flooded area: 1,887 cells with depth >0.3m
- Physical validation: Water accumulates along NE-SW trending valleys matching terrain structure
- Bugs found: 6 (err_006 through err_010 + err_004), all fixed and promoted to triplets dt_v001-dt_v006
- Key lesson: First real-basin run exposed 3 FATAL bugs (ind format, precip column, time offset) and 1 SILENT bug (mask=2 draining inland terrain) that the 50x50 synthetic test did not catch
Bengbu (Huai River) Flood Test (2026-03-22) — Step 3 Validation
- Domain: 384x455 = 174,720 cells at 100m resolution, Huai River floodplain near Bengbu city (32.7-33.1N, 117.1-117.5E)
- Event: July 2003 monsoon flood (390mm total rainfall from CMFD via VIC forcing)
- DEM: China DEM 90m, elevation range 3.5-310.2m (river channel to hills)
- Runtime: 397 seconds (OpenMP, 4 threads)
- Max flood depth: 6.99m (river channel depressions)
- Flood volume: 701 million m3
- Flooded area: 44,933 cells with depth >0.3m; 27,107 cells >1.0m; 2,179 cells >3.0m
- Mass balance: rain volume 681M m3, stored volume 701M m3 (ratio 1.03 — excellent conservation)
- CaMa-Flood comparison: CaMa max flood depth at same location/period = 8.17m at 15-arcmin resolution, consistent with SFINCS 6.99m at 100m resolution. CaMa peak discharge = 6,790 m3/s (confirms major flood event)
- Validation criteria: All 5 criteria PASS — (1) non-zero flood depth, (2) max < 20m, (3) water in low areas, (4) no NaN, (5) positive flood volume
- Bugs found: 2 new bugs (err_011: extract tool used zsmax instead of hmax, err_012: generate_sfincs_inp used netprecipfile instead of precipfile)
- Key insight: The 2003 Huai River flood was one of the worst in the period — 390mm in July produced extensive floodplain inundation. SFINCS results are physically consistent with both CaMa-Flood coarse routing and historical records. This validates SFINCS with real HydroCraft data at a different basin, climate zone (humid subtropical), and terrain (lowland floodplain vs Chaohe mountainous).
What SFINCS can and cannot be VALIDATED against (read before scoring)
SFINCS emits hmax (max water depth), zsmax/zs (water surface elevation),
point_zs/point_h at his-points, and flood extent derived from hmax > threshold
(see dag.yaml → outputs). A valid comparison needs an observation of water:
| Obs type | Valid against | Metric family | Server dataset |
|---|---|---|---|
| Observed inundation mask (SAR) | flood extent | event_detection (CSI) |
gfd_v1_4_asia_rice_belt (monsoon Asia only) |
| Stage / depth gauge in-domain | point_zs / point_h |
temporal_pattern_match, magnitude_accuracy |
hydat (Canada), grdc_caravan (global, no China, no Greenland) |
How to score against a stage gauge WITHOUT making it circular (added 2026-08-05).
Forcing SFINCS with the discharge measured AT the gauge and then scoring the water
level at that same gauge is self-referential — the 2026-08-02 reviewer audit rejected
exactly that (r = 0.954 by construction). Use TWO stations on the same river:
hydat_to_sfincs_boundary.py --flow_station <UPSTREAM Q gauge> --obs_stations <DOWNSTREAM stage gauge>.
The model then has to turn an inflow hydrograph into a stage through its own
bathymetry, roughness and momentum — a real hydrodynamic test.
Two stations is NECESSARY but NOT SUFFICIENT (amended 2026-08-05, dt_v024). The
downstream gauge must add INFORMATION, not just a different station name. On the Fraser
Hope -> Agassiz reach the two-station rule was followed exactly and still produced a
degenerate score: with no unforced tributary between them and +0.5% drainage area,
r(Q_08MF005, h_08MF035) = 0.9939/0.9950, and a zero-hydraulics power-law rating
h = 0.0311*Q^0.577 + 10.314 fitted on 2021 scored NSE 0.9993 on 2022 — above
SFINCS's own 0.9744. The metric measured the rating curve, not the hydraulics.
Before scoring, run:
tools/s9_validation/screen_obs_independence.py --dis_file … --candidates <id:levels_csv> --cal_period … --val_period … --sim_his …
It exits NON-ZERO on a degenerate target. Reject any candidate with r(BC, obs) > 0.95
or NSE_model <= NSE_naive, and quote information_gain = NSE_model - NSE_naive
next to every headline number. Prefer a target the BC does not determine: a gauge below
an unforced tributary confluence (Fraser: add --flow_station 08MF005,08MG013 — Harrison
River, 7890 km², up to 1280 m³/s — and score in the Harrison system), a multi-gauge
longitudinal water-surface profile (08MF072/08MF074/08MF035), an off-channel floodplain
gauge, or the dag's flood-hazard target (hmax/extent vs a SAR mask, scored with CSI).
If nothing survives the screen, report a NULL metric with reason
no_independent_obs_target — never a degenerate number.
Also keep qinf BELOW the mean precipitation intensity: this run set qinf = 1.0 mm/hr
against a mean MSWX rainfall of 0.1495 mm/hr (6.7x), which deletes the pluvial term
entirely and leaves the prescribed hydrograph as the sole driver. The screen warns on this.
Datum registration is mandatory for zs (the dag caveat 'requires shared datum').
HYDAT levels are metres in the station's OWN vertical datum (STATIONS.DATUM_ID ->
DATUM_LIST; only names containing GEODETIC are comparable with a DEM at all), and a
global DEM over water reports the river SURFACE at acquisition, not the bed. Register the datum from METADATA, never from the obs: run
register_vertical_datum.py --station <id> --dem_vertical_datum <EGM2008 for GLO-30>, which
chains STATIONS.DATUM_ID -> DATUM_LIST -> STN_DATUM_CONVERSION (e.g. 08MF035: 35 GEODETIC
SURVEY OF CANADA DATUM -> 605 CGVD2013:EPOCH2010, +0.163 m) and then the CGVD2013 -> DEM-datum
geoid step via PROJ (+0.244 m to EGM2008 here), and FAILS if no chain exists.
The dag sets detrending_options: ['none'] for point_zs, so an obs-FITTED offset is a calibrated
parameter: it disqualifies the period it was fitted on from being reported as a metric. A fitted
offset far larger than the metadata conversion (Fraser 2026-08-05: 1.4035 m fitted vs 0.4073 m
metadata total — 3.4x, or 8.6x the 0.163 m HYDAT gauge-datum step alone) is absorbing GLO-30's
missing channel bathymetry, not registering a datum.
| Surveyed high-water marks | hmax / zsmax | magnitude_accuracy (PBIAS) | — |
A DEM is NOT a flood observation. bedmachine (bed_elevation_m) is a static
bedrock + seafloor altitude field — it is indexed under parameter_sourcing.dem, has
no time axis, and observes solid earth, not water. It cannot be paired with any SFINCS
output, so a time_series_comparison with nse on hmax vs bed_elevation_m is
uncomputable by any model, not a SFINCS failure. Use BedMachine as an input
(topobathy, §Data Requirements) and report scale_comparable: false if it is handed
to you as the observation.
Greenland/Antarctic domains currently have no scoreable flood obs on this server, and the KI has no ice/snow-melt source term (s4 offers rainfall and CaMa-Flood discharge only; CaMa-Flood has no Greenland domain), so melt-driven events such as the 11 July 2012 Watson River flood can only be run pluvial-only.
Critical Domain Knowledge (Non-Obvious Facts)
These rules prevent silent failures — the model runs without error but results are wrong.
1. Mask values: 1=active; 2=WATER-LEVEL boundary; 3=OUTFLOW (dt_v021)
The solver binary states the convention verbatim (strings sfincs):
inactive=0, active=1, normal_boundary=2, outflow_boundary=3, wavemaker=4
This KI had 2 and 3 swapped until 2026-08-05. msk=2 is the water-level boundary:
with no bnd+bzs pair SFINCS holds it at zs = 0.0 m and says so in the log
("Setting water level at 0.0 m at mask boundary cells"). msk=3 is the absorbing
outflow boundary and needs no extra file.
- Coastal domain, boundary at mean sea level →
msk=2is correct (zs=0 IS the datum). - Inland river reach (bed at 6–40 m) →
msk=3.msk=2there is a 40 m head gradient that drains the domain tohmax=0— the symptom recorded as dt_v005/dt_v019, whose old "close the boundary everywhere inland" remedy makes a fluvial run impossible.
build_sfincs_topobathy.py --outflow_value {2,3} (default 3) and
--outflow_edges n,s,e,w (open only the downstream side). Verify: np.unique(msk) shows
mostly 1 plus 3 along the outlet, and sfincs_map.nc's own msk reproduces those
counts exactly.
2. Precipitation: ASCII precipfile, NOT NetCDF netprecipfile
SFINCS has two precip input modes: precipfile (ASCII: time_seconds precip_mmhr) and netprecipfile (NetCDF). The NetCDF mode silently fails in some SFINCS versions. Always use ASCII precipfile. Format:
0.0 2.725272
10800.0 4.773856
21600.0 4.294822
3. CMFD precipitation is kg/m²/s, NOT mm/3hr
CMFD precip NetCDF attributes say kg m-2 s-1. To convert to SFINCS mm/hr: multiply by 3600 (not divide by 3). This is the #1 unit trap across all models (PREFLIGHT.md). If max precip < 0.01 mm/hr, the units are wrong.
4. CaMa-Flood boundary times must be ≥ 0
The cama_to_sfincs_boundary.py tool reads CaMa output which may span multiple years. Times in sfincs.dis must be seconds since tref (≥ 0). Filter CaMa data to the requested simulation period before writing.
5. generate_sfincs_inp must reference src/dis/precip files
The config generator checks if files exist before adding them to sfincs.inp. Files must be checked in BOTH the current working directory AND the --output_dir. Missing references = SFINCS ignores the forcing silently.
6. Date filtering for CMFD files
CMFD has hundreds of monthly files. The rainfall tool MUST filter by YYYYMM in filename to avoid reading all years. Without filtering: works but takes hours instead of seconds.
Critical Domain Knowledge
These non-obvious facts cause silent failures if violated.
1. Precipitation is mm/hr, NOT mm/3hr or mm/day (dt_001, dt_002)
SFINCS expects precipitation rate in mm/hr.
- CMFD/MSWX: mm/3hr -> divide by 3
- Some climate models: kg/m2/s (= m/s) -> multiply by 3,600,000
- VIC forcing ASCII: mm/timestep (3-hourly) -> divide by 3
Off by 3x produces flood depths 3x too high. Off by 3.6 million produces zero flooding. Both are silent.
2. Vertical datum consistency (dt_004)
DEM elevation and water level boundary conditions MUST use the same vertical datum:
- Copernicus GLO-30 and China DEM: EGM96 geoid heights
- CaMa-Flood sfcelv: relative to geoid (consistent with DEM)
- Tidal models (FES2014): usually MSL or chart datum
- EGM96 geoid vs WGS84 ellipsoid: 20-40m difference depending on location
For fluvial-only (CaMa-Flood BC), this is usually consistent. For coastal (tidal BC), verify datum.
3. SFINCS reads from CWD only (dt_017)
The binary has NO command-line arguments. It reads sfincs.inp from the current working directory. All file paths in sfincs.inp are relative to CWD. Always cd to the run directory before executing.
4. Mask value 2 = outflow (dt_007)
Edge cells of the active domain MUST have mask=2 (outflow) to allow water to exit. If set to 3 (active), water accumulates at boundaries unrealistically.
5. CFL stability (dt_009)
dt must satisfy: dt <= dx / sqrt(g * h_max)
For dx=100m, h_max=10m: dt_max = 10.1 seconds. Use 0.75 * dt_max for safety.
6. Double-counting precipitation (dt_014)
When coupling VIC/CaMa-Flood with SFINCS, the same rainfall must NOT be applied twice. Choose:
- (a) SFINCS has its own precipitation + CaMa river discharge at boundary only
- (b) SFINCS has VIC/CaMa runoff as source, NO precipitation
7. outputformat must be "net" (dt_018)
Add outputformat = net to sfincs.inp. Without it, output may be binary, and post-processing tools expect NetCDF (sfincs_map.nc).
8. The binary maps are COMPRESSED, Fortran-order, SOUTH-up (dt_v023) — THE #1 trap
sfincs.dep, sfincs.msk, sfincs.man and sfincs.ind are not full rasters.
Proven against the solver on 2026-08-05 by reading sfincs_map.nc's zb back and
matching it byte-for-byte to the file that produced it:
sfincs.ind : int32 n_active, then n_active 1-BASED flat indices in FORTRAN order
(n fastest) over an (nmax, mmax) grid whose row 0 is the SOUTH edge
sfincs.dep : n_active float32 — ONLY the active cells, in index order
sfincs.msk : n_active uint8 — ONLY the active cells, in index order
sfincs.man : n_active float32 — ONLY the active cells, in index order
File sizes MUST be dep = man = n_active*4, msk = n_active, ind = (n_active+1)*4.
Until 2026-08-05 the KI wrote full nmax*mmax grids in C order with row 0 = NORTH
for all four. SFINCS reads exactly n_active values from each file, so it consumed the
top strip of the raster as if it were the compressed array — the simulated domain was a
scrambled, upside-down fragment, with no error message. Measured on a 345×285 Fraser
domain: 25,067 active cells written, 5,406 read, and every msk=3 outflow cell lost;
observation points returned point_zb = -999 and point_zs all-NaN, and hmax was
uniformly +9999. This is the mechanism behind the Chaohe record's
flooded_cells == total_cells == 29754, flood_fraction = 1.0.
Write with build_sfincs_topobathy.py (fixed) and read back with its
read_binary_map(topobathy_dir, nmax, mmax) helper — never np.fromfile(...).reshape(nmax, mmax).
Post-run check: np.unique(sfincs_map.nc:msk) must reproduce
topobathy_summary.json's active/outflow counts.
9. Inland vs coastal mask strategy (dt_v005, SUPERSEDED by dt_v021)
Mask value 2 defaults to water level 0.0 m (sea level) — correct only where 0 m is
the datum, i.e. a coastal boundary. The old rule ("if min_elevation > 10 m close every
boundary") was a workaround for having 2 and 3 swapped; a closed domain has no outlet and
cannot run a fluvial reach at steady state. The rule now: coastal → msk=2
(+ bnd/bzs if you have them); inland → msk=3 on the downstream edge only
(--outflow_value 3 --outflow_edges w).
10. Use ASCII precipitation, not NetCDF (dt_v004)
The precipfile keyword (ASCII format: time_seconds precip_mmhr) is reliable across all SFINCS versions. The netprecipfile keyword (NetCDF) may silently fail to load, showing "Precipitation: no" in the log without crashing. Always prefer ASCII for precipitation.
11. VIC forcing column order (dt_v002)
VIC forcing ASCII files have columns: TEMP(0), PREC(1), PRESSURE(2), SW(3), LW(4), VP(5), WIND(6). Column 0 is temperature, NOT precipitation. This is a common confusion because many tools assume column 0 is the primary variable.
12. gfortran cleanup SIGABRT is not an error (dt_v006)
SFINCS compiled with gfortran 13.3 may exit with code -6 (SIGABRT) after printing "Simulation finished". The output is valid. Check success by: (1) "Simulation finished" in sfincs.log AND (2) sfincs_map.nc exists with non-zero size. Do not re-run on exit code alone.
13. Use hmax not zsmax for flood depth (dt_v007)
SFINCS output contains both hmax (maximum water DEPTH above bed) and zsmax (maximum water SURFACE elevation). For flood depth mapping, always use hmax. Using zsmax gives values equal to terrain elevation + water depth (e.g., 310m instead of 7m), which is meaningless for flood analysis. The extract_sfincs_results.py tool was fixed to prioritize hmax over zsmax.
14. generate_sfincs_inp must use precipfile not netprecipfile (dt_v008)
The config generator must write precipfile = sfincs.precip (ASCII format: time_seconds precip_mmhr) when the precipitation file is ASCII. Writing netprecipfile when the file is actually ASCII causes SFINCS to silently fall back to "Precipitation: no" — the simulation runs without rainfall, producing zero flooding. This compounds with dt_v004 (NetCDF precip itself is unreliable).
15. The venv's netCDF4 CANNOT read SFINCS output — use h5netcdf (dt_v016)
xr.open_dataset("sfincs_map.nc") raises OSError: [Errno -101] NetCDF: HDF error
in the HydroCraft venv (netCDF4 1.7.4 bundling HDF5 1.14.6) for every file SFINCS
writes with its own libnetcdff (HDF5 1.10). The file is fine: ncdump, system python
netCDF4, GDAL/rasterio, h5py and the h5netcdf engine all read it. Do not re-run
the model — it is a library defect, not a corrupt output. Open with a fallback:
try: ds = xr.open_dataset(path)
except OSError: ds = xr.open_dataset(path, engine="h5netcdf")
extract_sfincs_results.py does this via open_sfincs_nc(). The same defect blocks
BedMachine's NSIDC NetCDF — reach those through GDAL (NETCDF:"file.nc":bed).
16. SFINCS writes +9999 into hmax outside the mask (dt_v017)
Inactive cells get hmax = +9999 (and zb = -9999). Clipping only negative values
leaves the positive sentinel in, and the tool then reports max flood depth 9999 m
with a flood volume 10⁶ too large — silent and confidently wrong. Mask |v| >= 9998
and msk == 0 before computing any statistic.
17. Outflow must be elevation-aware, not domain-wide (dt_v019; read dt_v021 FIRST)
Item 9 / dt_v005 gives the inland-vs-coastal rule, but a mixed domain (shoreline at 0 m, ridge at 646 m) has no correct global setting: mask=2 cells are held at zs = 0 m, so an upland edge cell drains the whole domain. Use the new flags:
# coastal: cut the sea out of the active domain, outflow only on the shoreline
build_sfincs_topobathy.py --min_elev 0.0 --outflow_max_elev 5.0
# inland: closed boundary everywhere
build_sfincs_topobathy.py --outflow_max_elev -9999
Also note: with zsini = 0, any active cell below MSL starts full of standing
seawater, and s8 then reports that as flood depth (measured: 19.98 m of "flood" in a
zero-rainfall smoke run). --min_elev 0.0 removes it.
18. CFL h_max comes from the bathymetry, not from a constant (dt_v020)
generate_sfincs_inp.py used to assume h_max = 10 m for dt = 0.75·dx/√(g·h_max).
A domain whose bed reaches −135 m carries 135 m of standing water at t=0, where the
limit is 2.7 s, not 10.1 s. Pass --topobathy_dir (h_max is then derived from
topobathy_summary.json as max(10, zsini − elevation_min)) or set --h_max.
19. MSWX forcing must go through ki_tools_common (dt_v018)
MSWX ships annual, per-variable files (KISSPATH_FORCING/P/P_2012.nc). The CMFD
discovery logic (*prec* in the name + a YYYYMM stamp) matches none of them, so
--source mswx used to exit 2 with "No precipitation files found" — i.e. it never
worked on any non-China domain. prepare_sfincs_rainfall.py now calls
ki_tools_common.load_forcing.load_hourly_forcing. Budget ~5 min per variable per
year: MSWX annual files are one gzip slab per global timestep, so even a
single-point read decompresses the whole year.
20. --bbox needs the = form outside the eastern/northern hemisphere
--bbox "-51.25,66.94,-50.40,67.12" dies with argument --bbox: expected one argument — argparse reads the leading - as an option flag. Always write
--bbox="-51.25,66.94,-50.40,67.12". The Quick Start below only shows China examples,
where this never bites.
Coupling Points
| # | Source | Target | Variable | Tool |
|---|---|---|---|---|
| c1 | CaMa-Flood | SFINCS | Discharge (m3/s) at river entry | cama_to_sfincs_boundary |
| c2 | CaMa-Flood | SFINCS | Water level (m) at coastal boundary | cama_to_sfincs_boundary |
| c3 | CMFD/MSWX | SFINCS | Precipitation (mm/hr) | prepare_sfincs_rainfall |
| c4 | VIC | SFINCS | Surface runoff (mm/day -> m3/s) | (direct coupling, use with caution) |
Standard Workflow: VIC -> CaMa-Flood -> SFINCS
- VIC produces watershed runoff at 0.1-0.25 degree
- CaMa-Flood routes to river channels, produces daily discharge/stage
- SFINCS simulates local flood inundation at 10-200m using CaMa discharge as boundary + local rainfall
- No feedback needed (local flood does not significantly affect upstream hydrology)
Data Requirements
| Data | Source | Status | Path |
|---|---|---|---|
| SFINCS binary | Compiled from source | Installed | model/sfincs/bin/sfincs |
| China DEM 90m | Local | Available | data/dem/china_dem_90m/ |
| Copernicus GLO-30 | AWS (auto-download) | Available | Auto-downloaded by hydrobasin |
| BedMachine Greenland v6 | Local (NSIDC) | Available | data/obs/ice_sheets/bedmachine/BedMachineGreenland-v6.nc |
| BedMachine Antarctica v4.1 | Local (NSIDC) | Available | data/obs/ice_sheets/bedmachine/NSIDC-0756_BedMachineAntarctica_*.nc |
| CMFD forcing | Local | Available | data/forcing/Data_forcing_03hr_010deg/ |
| MSWX forcing | Local | Available | KISSPATH_FORCING/ |
| CaMa-Flood output | From pipeline | Available | model/cmf_v420_pkg/out/ |
| AVHRR land cover | Local | Available | data/forcing/AVHRR/ |
Quick Start
# Activate venv
source KISSPATH_PYTHON_ENV/bin/activate
# 1. Define domain (from existing basin shapefile)
python tools/s1_domain/setup_sfincs_domain.py \
--shp_path data/shp/chaohe_shp/chaohe.shp \
--resolution 100 \
--output_dir outputs/sfincs_test/
# 2. Build topography (auto-selects China DEM)
python tools/s2_topobathy/build_sfincs_topobathy.py \
--grid_info outputs/sfincs_test/grid_info.json \
--shp_path data/shp/chaohe_shp/chaohe.shp \
--output_dir outputs/sfincs_test/
# 3. Build roughness
python tools/s3_roughness/build_sfincs_roughness.py \
--grid_info outputs/sfincs_test/grid_info.json \
--uniform_n 0.04 \
--output_dir outputs/sfincs_test/
# 4. Prepare rainfall forcing
python tools/s4_forcing/prepare_sfincs_rainfall.py \
--forcing_dir outputs/chaohe_run/vic_temp/forcing/forcing_final \
--grid_info outputs/sfincs_test/grid_info.json \
--start_date 2003-07-01 --end_date 2003-09-30 \
--source vic_ascii \
--output_dir outputs/sfincs_test/
# 5. Generate configuration
python tools/s6_config/generate_sfincs_inp.py \
--grid_info outputs/sfincs_test/grid_info.json \
--start_date 20030701 --end_date 20030930 \
--precip_file outputs/sfincs_test/sfincs.precip \
--output_dir outputs/sfincs_test/
# 6. Run SFINCS
python tools/s7_execution/run_sfincs.py \
--run_dir outputs/sfincs_test/
# 7. Extract and plot results
python tools/s8_postprocess/extract_sfincs_results.py \
--map_nc outputs/sfincs_test/sfincs_map.nc \
--grid_info outputs/sfincs_test/grid_info.json \
--output_dir outputs/sfincs_test/results/
python tools/s8_postprocess/plot_sfincs_flood_map.py \
--flood_depth outputs/sfincs_test/results/flood_max_depth.npy \
--grid_info outputs/sfincs_test/grid_info.json \
--title "Chaohe Flood Inundation July-Sep 2003" \
--output outputs/sfincs_test/flood_map.png
Diagnostic Triplets
26 triplets covering 8 failure domains. See diagnostics/triplets.yaml for full details.
Original triplets (dt_001 to dt_020) — from dissection
| ID | Severity | Domain | Summary |
|---|---|---|---|
| dt_001 | silent | unit_conversion | Precip mm/3hr not divided by 3 (flood 3x too deep) |
| dt_002 | silent | unit_conversion | Precip in m/s instead of mm/hr (no flooding) |
| dt_003 | silent | unit_conversion | Discharge mm/day not converted to m3/s |
| dt_004 | silent | unit_conversion | Vertical datum EGM96 vs MSL offset 20-40m |
| dt_005 | fatal | spatial_error | CRS mismatch: geographic deg with metric dx/dy |
| dt_006 | silent | spatial_error | Grid origin mismatch after reprojection |
| dt_007 | silent | spatial_error | Edge cells mask=3 instead of mask=2 (no outflow) |
| dt_008 | degraded | spatial_error | Subgrid ratio > 30x causes checkerboard |
| dt_009 | fatal | timestep | CFL violation: dt > dx/sqrt(g*h) -> NaN crash |
| dt_010 | degraded | timestep | Advection + low alpha -> oscillations |
| dt_011 | degraded | timestep | Grid too fine -> 100x runtime |
| dt_012 | silent | boundary | Bnd point on inactive mask cell -> ignored |
| dt_013 | degraded | boundary | Source point on hilltop -> artificial pond |
| dt_014 | silent | coupling | Double-counted precipitation (VIC + SFINCS) |
| dt_015 | silent | coupling | CaMa discharge sign -> reverse flow |
| dt_016 | degraded | coupling | Daily CaMa BC -> artificial 24hr ramps |
| dt_017 | fatal | io_path | Binary not run from CWD containing sfincs.inp |
| dt_018 | degraded | io_path | outputformat missing -> binary not NetCDF |
| dt_019 | silent | io_path | Mask all zeros -> output all zeros |
| dt_020 | fatal | parameter | Manning's n < 0.01 -> CFL instability |
Validated triplets (dt_v001 to dt_v006) — from Chaohe basin test
| ID | Severity | Domain | Summary | Error Log |
|---|---|---|---|---|
| dt_v001 | fatal | file_format | sfincs.ind binary format wrong (full 2D grid vs header+indices) | err_006 |
| dt_v002 | fatal | unit_conversion | VIC forcing col 0 is temp, not precip — wrong column read | err_007 |
| dt_v003 | fatal | temporal_alignment | Multi-year VIC forcing time offset not computed | err_008 |
| dt_v004 | degraded | file_format | NetCDF precip via netprecipfile silently fails — use ASCII | err_009 |
| dt_v005 | silent | boundary_condition | mask=2 drains inland terrain at 0m water level | err_010 |
| dt_v006 | degraded | runtime | gfortran SIGABRT after "Simulation finished" — output valid | err_004 |
Validated triplets (dt_v007 to dt_v008) — from Bengbu flood test
| ID | Severity | Domain | Summary | Error Log |
|---|---|---|---|---|
| dt_v007 | silent | variable_selection | extract_sfincs_results used zsmax (water surface elevation) instead of hmax (water depth) — flood depths 310m instead of 7m |
err_011 |
| dt_v008 | silent | config_generation | generate_sfincs_inp wrote netprecipfile but ASCII precip needs precipfile — silently falls back to no precipitation |
err_012 |
Validated triplets (dt_v016 to dt_v020) — from the Kangerlussuaq / Watson River test (2026-08-02)
| ID | Severity | Domain | Summary |
|---|---|---|---|
| dt_v016 | fatal | environment | Venv netCDF4 1.7.4 / HDF5 1.14.6 cannot open ANY SFINCS output ("NetCDF: HDF error"); ncdump/GDAL/h5py/h5netcdf can — s8 was dead server-wide. Fallback to engine="h5netcdf" |
| dt_v017 | silent | postprocess | hmax = +9999 fill outside the mask survived the negative-only clip — reported max flood depth 9999 m |
| dt_v018 | fatal | io_path | --source mswx globbed *prec* + YYYYMM, which never matches MSWX's annual P/P_YYYY.nc — MSWX forcing could never load |
| dt_v019 | silent | boundary_condition | s2 set EVERY edge cell to mask=2; on a mixed coast+upland domain the upland edges (held at zs=0) drain everything. Added --min_elev / --outflow_max_elev |
| dt_v020 | degraded | timestep | s6 hard-coded h_max = 10 m for the CFL dt regardless of bathymetry; now derived from topobathy_summary.json |
triplets.yamlwas invalid YAML until 2026-08-02.dt_v009–dt_v015were indented 2 spaces, making them children ofdt_v008's mapping, soyaml.safe_loadraisedexpected <block end>, but found '-'and no triplet was programmatically loadable. De-indented; the file now parses to 40 entries.
Total: 28 triplets. Silent error count: 13/28 (46%). Validated from real runs: 8/28 (29%).
File Structure
models/SFINCS/knowledge_infrastructure/
DISSECTION_PLAN.md # Original dissection plan
SKILL.md # This file (agent entry point)
knowledge_infrastructure.yaml # Schema-compliant package definition
tools/
s1_domain/
setup_sfincs_domain.py # Grid from shapefile/bbox
s2_topobathy/
build_sfincs_topobathy.py # DEM to sfincs.dep/msk/ind
s3_roughness/
build_sfincs_roughness.py # Manning's n from land cover
s4_forcing/
prepare_sfincs_rainfall.py # CMFD/MSWX to sfincs.precip (mm/hr)
cama_to_sfincs_boundary.py # CaMa-Flood to sfincs.src/.dis or .bnd/.bzs
s5_structures/
(future: setup_sfincs_structures.py)
s6_config/
generate_sfincs_inp.py # Generate sfincs.inp with CFL dt
s7_execution/
run_sfincs.py # Execute with preflight checks
s8_postprocess/
extract_sfincs_results.py # Parse sfincs_map.nc, flood stats
plot_sfincs_flood_map.py # Flood depth map visualization
docs/
s1_domain_skill.md ... s8_postprocess_skill.md
diagnostics/
triplets.yaml # 26 diagnostic triplets (20 from dissection + 6 from Chaohe validation)
error_log.yaml # 10 error log entries (6 promoted to triplets)
model/sfincs/
bin/sfincs # SFINCS v2.3.2 binary (compiled from source, validated)
bin/VERSION # Build metadata
compile_sfincs.sh # Compilation script (for rebuilding)