Imported from lzwei196/KISS-Knowledge-Infrastructure-for-Scientific-Simulation (
models/Landlab/SKILL.md). Install upstream withnpx skills add lzwei196/KISS-Knowledge-Infrastructure-for-Scientific-Simulation --skill Landlab. 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.
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/ (8 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 (5 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 (22 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 (22 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. |
Projected 2026-08-17 from the KI's actual contents — 9 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/convert_dem_to_grid.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/convert_dem_to_grid.py --help |
tools/convert_soil_params.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/convert_soil_params.py --help |
tools/dissect_atchafalaya_ssc_q_surrogate.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/dissect_atchafalaya_ssc_q_surrogate.py --help |
tools/dissect_loess_plateau_sediment_yield.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/dissect_loess_plateau_sediment_yield.py --help |
tools/dissect_loess_plateau_slope_area.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/dissect_loess_plateau_slope_area.py --help |
tools/dissect_space_ssc_q_rating.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/dissect_space_ssc_q_rating.py --help |
tools/parse_landlab_output.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/parse_landlab_output.py --help |
tools/run_landlab.py |
KISSPATH_PYTHON_ENV/bin/python {KI}/tools/run_landlab.py --help |
8 public tools; _-prefixed helpers and packaging files excluded.
Landlab v2.10 — Earth Surface Dynamics Modeling Toolkit
Package: hydrocraft-landlab v1.0.0 Model: Landlab 2.10.1 (Python library with Cython extensions) Domain: Geomorphology, hydrology, stratigraphy, glaciology Created by: Landlab Development Team (CU Boulder, U Washington, Tulane) Last updated: 2026-04-29 Stats: 7 tools | 5 skill documents | 22 diagnostic triplets | ~2,200 lines of validated code Validation status: production_validated (5 test cases: Whipple & Tucker 1999 analytical + SPACE binary Qs-Q + SPACE steady-state concavity + Loess Plateau SRTM real-DEM slope-area + Loess Plateau stream power SY + P export)
Data Preparation
Forcing data
Data Sources: Use from ki_tools_common.load_forcing import load_daily_forcing for CMFD/MSWX/NASA POWER.
Data Validation Reference: See data_ki/CMFD/SKILL.md for meteorological forcing documentation.
See data_ki/USGS_Sediment/SKILL.md for suspended sediment observations.
Applicability
This KI is for landscape evolution and sediment-yield benchmarks only. It does NOT produce a daily discharge hydrograph and is NOT applicable to gauge-discharge tests (e.g. Bengbu, Wangjiaba). The surface_water__discharge field from FlowAccumulator is steady-state Q = drainage_area * uniform_runoff_rate, not a time series. Validation cases supported: slope-area, concavity, sediment yield, Whipple-Tucker analytical, SPACE Qs-Q. For streamflow benchmarks use a hydrologic KI (PIHM, mHM, VIC, etc.).
Overview
Landlab is an open-source Python package for numerical modeling of Earth surface dynamics. Unlike compiled, monolithic landscape evolution models, Landlab is a component-based framework: users compose simulations by coupling reusable process components (erosion, diffusion, flow routing, weathering, etc.) on a shared model grid. The grid stores spatially distributed fields (elevation, drainage area, soil depth) at nodes, links, patches, cells, corners, and faces.
Landlab supports six grid types: RasterModelGrid (uniform rectangular),
HexModelGrid (hexagonal cells), VoronoiDelaunayGrid (irregular Voronoi),
FramedVoronoiGrid, RadialModelGrid, NetworkModelGrid, and
IcosphereGlobalGrid. All grids implement the same field-storage API, so
components are grid-agnostic.
There are 60+ process components covering: flow routing (D8, D-infinity, MFD, priority-flood), stream-power erosion, hillslope diffusion, SPACE bedrock- alluvium erosion, overland flow (kinematic wave, de Almeida), flexural isostasy, tectonic faulting, soil production (exponential weathering), fire, vegetation dynamics, species evolution, tidal flow, marine sediment transport, and more.
Installation
From PyPI (recommended)
python -m venv venv && source venv/bin/activate
pip install landlab
From source (development)
git clone https://github.com/landlab/landlab.git
cd landlab
pip install -e ".[dev,testing]"
Dependencies
- Core: numpy>=1.20, scipy, matplotlib, xarray>=0.16, pandas, pyyaml, netcdf4, bmipy, rich-click
- Build: cython, setuptools, wheel (Cython extensions for performance)
- Optional: pyshp (shapefiles), statsmodels, gflex (flexure)
Verify installation
import landlab; print(landlab.__version__)
from landlab import RasterModelGrid
mg = RasterModelGrid((10, 10), xy_spacing=100.0)
print(f"Grid OK: {mg.number_of_nodes} nodes")
Pipeline (7 stages)
| Stage | ID | Tool | Description |
|---|---|---|---|
| 1. Grid Setup | s1_grid | — | Create model grid with DEM or synthetic topography |
| 2. Input Preparation | s2_input | convert_dem_to_grid.py |
Load DEM, set boundary conditions, add fields |
| 3. Soil/Parameter Setup | s3_params | convert_soil_params.py |
Map HWSD or other soil data to grid fields |
| 4. Component Assembly | s4_assembly | — | Instantiate and couple Landlab components |
| 5. Execution | s5_run | run_landlab.py |
Time-stepping loop with run_one_step() |
| 6. Output Extraction | s6_output | parse_landlab_output.py |
Extract fields to CSV/NetCDF, compute metrics |
| 7. Diagnostics | s7_diag | — | Validate results against analytical solutions |
Parallelism
- Stages 2 and 3 can run in parallel (independent input preparation)
- Stage 4 depends on both 2 and 3
- Stages 5–7 are sequential
Execution Model
Landlab is a Python API, not a CLI binary. Simulations are Python scripts:
from landlab import RasterModelGrid
from landlab.components import FlowAccumulator, StreamPowerEroder, LinearDiffuser
# 1. Create grid
mg = RasterModelGrid((50, 50), xy_spacing=100.0)
z = mg.add_zeros("topographic__elevation", at="node")
z += mg.node_x * 0.01 + np.random.rand(mg.number_of_nodes)
# 2. Set boundary conditions
mg.set_closed_boundaries_at_grid_edges(True, False, True, False)
# 3. Instantiate components
fa = FlowAccumulator(mg, flow_director="FlowDirectorD8")
sp = StreamPowerEroder(mg, K_sp=1e-5, m_sp=0.5, n_sp=1.0)
ld = LinearDiffuser(mg, linear_diffusivity=0.01)
# 4. Time loop
dt = 1000.0 # years
for t in range(2000):
fa.run_one_step()
sp.run_one_step(dt)
ld.run_one_step(dt)
# 5. Output
from landlab.io import write_esri_ascii
write_esri_ascii("output.asc", mg, "topographic__elevation")
BMI Interface
Components can also be driven via the Basic Modeling Interface (BMI):
from landlab.bmi import wrap_as_bmi
BmiSPE = wrap_as_bmi(StreamPowerEroder)
model = BmiSPE()
model.initialize("config.yaml")
model.update()
model.finalize()
6. Output Description
Source of truth: dag.yaml. The DAG wins over this body if they ever disagree.
Headline output (validation_rank: 1):
sediment_yield— Annual catchment sediment yield via detachment-limited stream power (E = K_sp A^m S^n converted by bulk density). (t/km^2/yr)
Output variable (dag var) |
Rank | Unit | Emitted in | Description |
|---|---|---|---|---|
sediment_yield |
1 | t/km^2/yr |
derived metric (detachment-limited stream-power export) | Annual catchment sediment yield via detachment-limited stream power (E = K_sp A^m S^n converted by bulk density). |
sediment__outflux (SPACE Qs) |
2 | m^3/s (or kg/s) |
Landlab grid field at_node (SPACE / ErosionDeposition) | Sediment flux out of a node; the Qs in the SPACE Qs-Q rating relationship. |
surface_water__discharge (steady-state) |
3 | m^3/s |
Landlab grid field at_node (FlowAccumulator) | Steady-state discharge Q = drainage_area * runoff_rate from FlowAccumulator; NOT a hydrograph time series. |
topographic__elevation |
4 | m |
Landlab grid field at_node; written to ESRI ASCII (.asc) / NetCDF (.nc) | Final land-surface elevation of the evolved landscape (node field). |
concavity (theta) |
5 | dimensionless |
derived metric from slope-area regression on the grid | Land-surface/channel-topography slope-area concavity index theta = -d(logS)/d(logA); at steady state expected to approach m_sp/n_sp. |
steepness (ks) |
6 | dimensionless (channel steepness index) |
derived metric from slope-area regression | Channel steepness index from the slope-area regression. |
relief |
7 | m |
derived metric over core nodes | Max-minus-min elevation over core nodes; landscape relief at steady state. |
soil__depth |
8 | m |
Landlab grid field at_node | Regolith/soil thickness above bedrock (node field). |
The other DAG outputs are: topographic__elevation, concavity (theta), steepness (ks), relief, sediment__outflux (SPACE Qs), soil__depth, and surface_water__discharge (steady-state).
8. Unit Table
Source of truth: dag.yaml and docs/format_spec.yaml. Landlab is unit-agnostic, so the tools and user scripts must keep the unit system consistent.
| Variable / quantity | Source unit handled by this KI | Model or DAG unit | Conversion / invariant | Where checked or used |
|---|---|---|---|---|
sediment_yield |
derived from detachment-limited stream-power export | t/km^2/yr |
E = K_sp A^m S^n converted by bulk density | DAG rank-1 output; tools/dissect_loess_plateau_sediment_yield.py |
topographic__elevation |
DEM elevation in meters; feet are a known trap | m |
feet to meters: multiply by 0.3048 | convert_dem_to_grid.py; dt_007 |
xy_spacing / grid geometry |
projected metric spacing required | m |
reproject geographic degrees to a metric CRS before loading | convert_dem_to_grid.py; dt_004 |
water__unit_flux_in |
rainfall / runoff from forcing data | m/s |
mm/hr to m/s: divide by 3,600,000; mm/day to m/s: divide by 86,400,000; m/yr to m/s: divide by 31,557,600 | FlowAccumulator; dt_001 |
surface_water__discharge (steady-state) |
derived steady-state accumulated runoff | m^3/s |
Q = drainage_area * runoff_rate | FlowAccumulator; DAG rank-3 output |
soil__depth |
HWSD-style soil depth commonly in cm | m |
cm to m: divide by 100 | convert_soil_params.py; dt_006 |
K_sp |
literature or calibrated stream-power erodibility | length^(1-2*m_sp) / time |
units depend on m_sp and n_sp; at m_sp=0.5, n_sp=1.0, K has units 1/yr when dt is in years |
StreamPowerEroder; dt_003 |
linear_diffusivity |
literature or calibrated diffusivity | m^2/yr when dt is in years |
m^2/s to m^2/yr: multiply by 3.156e7 | LinearDiffuser; dt_002 |
dt |
user-chosen timestep | yr for landscape evolution, s for overland flow |
dt must share the time unit of K, diffusivity, uplift, and other rates | run_landlab.py; dt_005 |
uplift_rate |
scalar or field forcing | m/yr |
applied as z[core_nodes] += uplift_rate * dt when dt is in years |
user execution loop |
sediment__outflux (SPACE Qs) |
SPACE / ErosionDeposition node flux | m^3/s (or kg/s) |
compare Qs directly against Q; do not fit SSC = Qs/Q when testing SPACE Qs-Q | DAG rank-2 output; dt_020 |
rainfall__daily_depth |
SoilMoisture rainfall can arrive in m or m/s | mm at cell |
m to mm: multiply by 1000; m/s to mm/day: multiply by 86,400,000 | SoilMoisture; dt_008 |
11. Validated Results
Source of truth for validation bars: docs/validation_convention.yaml. Null convention bands are written as no cited threshold; no uncited threshold is substituted.
Performance Metrics -- judged against the field's bar
| DAG variable | Headline metric | Direction | Convention bar, cited | KI result currently documented |
|---|---|---|---|---|
sediment_yield |
pbias |
zero_centered | very good: ` | PBIAS |
topographic__elevation |
csi |
maximize | very good: no cited threshold; good: no cited threshold; satisfactory: no cited threshold; cites: [] |
Whipple & Tucker steady-state run reports slope-area r^2 = 0.9978, relief 810.2 m, and wall time 2.2 s; no CSI value is stated. |
concavity (theta) |
pbias |
zero_centered | very good: no cited threshold; good: no cited threshold; satisfactory: no cited threshold; cites: [] |
SPACE steady-state run reports theta 0.464; Loess Plateau slope-area run reports theta 0.1631; Whipple & Tucker run reports theta 0.488 against expected 0.500. |
steepness (ks) |
pbias |
zero_centered | very good: no cited threshold; good: no cited threshold; satisfactory: no cited threshold; cites: [] |
The DAG exposes steepness (ks) as a derived slope-area metric; this body does not state a validated ks value. |
Documented validation cases
| Case | Configuration source | Reported result |
|---|---|---|
| SPACE Binary -- Atchafalaya Qs-Q + Concavity (2026-04-29) | tools/dissect_atchafalaya_ssc_q_surrogate.py; outputs under outputs/landlab_atchafalaya_ssc_q/ |
b_sim = 0.897, log-log r = 0.978, theta 0.464, obs r = 0.809; all listed as PASS in this body. |
| Loess Plateau Real-DEM Slope-Area (2026-04-29) | tools/dissect_loess_plateau_slope_area.py |
theta 0.1631, binned R^2 = 0.8659, channel nodes 12,315; all listed as PASS in this body. |
| Loess Plateau Sediment Yield + Particulate-P Export (2026-04-29) | tools/dissect_loess_plateau_sediment_yield.py; outputs under outputs/landlab_loess_sediment/ |
sediment yield 8390.6 t/km^2/yr; particulate-P export 134.25 kg/ha/yr; both listed as PASS in this body. |
| Whipple & Tucker (1999) Steady-State Test (2026-03-25) | Python Landlab component run described below | slope-area r^2 = 0.9978, theta 0.488, relief 810.2 m, wall time 2.2 s; listed as PASS where thresholds are stated in this body. |
Data replacement tracking
| Component | Source | Status | Notes |
|---|---|---|---|
| Forcing | CMFD/MSWX/NASA POWER via ki_tools_common.load_forcing.load_daily_forcing where meteorological forcing is needed |
Available | Landscape-evolution sediment-yield runs commonly use uniform runoff_rate, not a daily hydrograph. |
| Soil | HWSD or other soil data via convert_soil_params.py |
Available | Convert depth from cm to m before Landlab fields. |
| DEM / topography | SRTM/ASTER/LiDAR through convert_dem_to_grid.py |
Validated in Loess Plateau cases | DEM must be projected in meters and NoData must be closed. |
| Initial conditions | Landlab grid fields (topographic__elevation, soil__depth, bedrock__elevation) |
Available | Field locations and units must match component expectations. |
Grid Field System
Fields are stored at 7 locations on the grid:
| Location | Description | Access Pattern |
|---|---|---|
node |
Grid vertices (most common) | grid.at_node["field_name"] |
link |
Edges connecting nodes | grid.at_link["field_name"] |
patch |
Polygons in primal mesh | grid.at_patch["field_name"] |
cell |
Polygons in dual mesh | grid.at_cell["field_name"] |
corner |
Vertices in dual mesh | grid.at_corner["field_name"] |
face |
Edges in dual mesh | grid.at_face["field_name"] |
grid |
Scalar (whole-grid) values | grid.at_grid["field_name"] |
Standard Variable Names and Units
| Variable | Units | Location | Description |
|---|---|---|---|
topographic__elevation |
m | node | Land surface elevation |
drainage_area |
m^2 | node | Contributing upstream area |
surface_water__discharge |
m^3/s | node | Volumetric water discharge |
water__unit_flux_in |
m/s | node | Rainfall rate (depth/time) |
topographic__steepest_slope |
- | node | Max downhill gradient |
flow__receiver_node |
- | node | Downstream neighbor ID |
flow__upstream_node_order |
- | node | Topological sort order |
flow__link_to_receiver_node |
- | node | Link ID to receiver |
soil__depth |
m | node | Regolith/soil thickness |
soil_production__rate |
m/yr | node | Bedrock-to-soil conversion rate |
bedrock__elevation |
m | node | Bedrock surface elevation |
hillslope_sediment__unit_volume_flux |
m^2/s | link | Diffusive sediment flux |
topographic__gradient |
- | link | Surface slope on links |
sediment__influx |
m^3/s | node | Incoming sediment rate |
sediment__outflux |
m^3/s | node | Outgoing sediment rate |
surface_water__depth |
m | node | Water depth on surface |
rainfall__daily_depth |
mm | cell | Daily precipitation |
vegetation__cover_fraction |
- | cell | Fractional veg cover |
Unit Trap Table
These are the most dangerous unit mismatches in Landlab workflows:
| # | Variable | Expected | Common Mistake | Effect | Triplet |
|---|---|---|---|---|---|
| 1 | water__unit_flux_in |
m/s | mm/hr or m/yr | Discharge 1000x–31.5M× wrong | dt_001 |
| 2 | linear_diffusivity |
m^2/yr (if dt in yr) | m^2/s | Hillslopes flatten instantly | dt_002 |
| 3 | K_sp (erodibility) |
depends on m,n | wrong exponent combo | Erosion rate orders of magnitude off | dt_003 |
| 4 | xy_spacing |
m | km or degrees | Area, slope, flux all wrong | dt_004 |
| 5 | dt (timestep) |
must match K units | yr vs s mismatch | All rates wrong | dt_005 |
| 6 | soil__depth |
m | cm | Weathering rate, SPACE output wrong | dt_006 |
| 7 | topographic__elevation |
m | ft or cm | Slope calc wrong → erosion wrong | dt_007 |
| 8 | rainfall__daily_depth |
mm | m | Soil moisture 1000× off | dt_008 |
| 9 | roughness (Manning n) |
s/m^(1/3) | 1/n (Chezy) | Velocity inverted | dt_009 |
| 10 | DEM nodata value | -9999 | 0 or NaN | Flow routes into nodata cells | dt_010 |
Key Components Reference
Flow Routing
| Component | Method | Key Params |
|---|---|---|
FlowAccumulator |
Wraps director + accumulation | flow_director, runoff_rate |
FlowDirectorD8 |
Steepest single-direction (D8) | — |
FlowDirectorDINF |
D-infinity (two receivers) | — |
FlowDirectorMFD |
Multiple flow directions | partition_method |
DepressionFinderAndRouter |
Fill/route pits | routing |
PriorityFloodFlowRouter |
Priority-flood (Barnes 2014) | flow_metric, suppress_out |
Erosion & Sediment Transport
| Component | Equation | Key Params |
|---|---|---|
StreamPowerEroder |
E = K A^m S^n | K_sp, m_sp, n_sp, threshold_sp |
ErosionDeposition |
Davy & Lague (2009) ξ-q | K, v_s, m_sp, n_sp |
Space |
Bedrock + alluvium layers | K_sed, K_br, v_s, H_star |
LinearDiffuser |
q = -D ∇z | linear_diffusivity |
DepthDependentDiffuser |
Nonlinear depth-dep. | linear_diffusivity, soil_transport_decay_depth |
TaylorNonlinearHillslopeFlux |
Taylor expansion nonlinear | critical_slope, nterms |
Weathering & Soil Production
| Component | Equation | Key Params |
|---|---|---|
ExponentialWeatherer |
P = P0 exp(-H/H*) | soil_production_maximum_rate, soil_production_decay_depth |
DepthDependentTaylorDiffuser |
Coupled soil transport | soil_transport_velocity, soil_transport_decay_depth |
Hydrology
| Component | Method | Key Params |
|---|---|---|
KinwaveOverlandFlowModel |
Kinematic wave + Manning | precip_rate, roughness |
OverlandFlow |
de Almeida et al. (2012) | h_init, mannings_n |
SoilMoisture |
Laio et al. (2001) bucket | soil_porosity, soil_field_capacity |
GroundwaterDupuitPercolator |
Dupuit approx. | hydraulic_conductivity, porosity |
Tectonics & Lithology
| Component | Description | Key Params |
|---|---|---|
NormalFault |
Vertical displacement on fault | faulted_surface, fault_throw_rate_through_time |
Flexure |
Lithospheric flexure (Airy/elastic) | eet, youngs |
Lithology |
Track rock layers through erosion | layer_ids, layer_thicknesses |
I/O Formats
| Format | Read | Write | Function |
|---|---|---|---|
| ESRI ASCII (.asc) | Yes | Yes | landlab.io.esri_ascii.load() / dump() |
| NetCDF (.nc) | Yes | Yes | to_netcdf() / from_netcdf() |
| Shapefile (.shp) | Yes | No | read_shapefile() |
| VTK Legacy (.vtk) | No | Yes | write_legacy_vtk() |
| OBJ (.obj) | No | Yes | write_obj() |
| Native Landlab | Yes | Yes | save() / load() |
Critical Domain Knowledge
-
Landlab is unit-agnostic — it does NOT enforce units internally. The user MUST ensure all inputs share a consistent unit system. If elevation is in meters, spacing must be meters, K must be in m^(1-2m)/yr, diffusivity in m^2/yr, and dt in years. Mixing m/s with m/yr is the #1 source of silent failures. → dt_001, dt_005
-
K_sp units depend on m_sp and n_sp exponents — for the stream power law E = K A^m S^n, when m=0.5 and n=1.0, K has units m^(1-2m)/time = m^0/yr = 1/yr. Changing exponents changes K units. Copying K values from papers with different exponents produces wrong erosion rates. → dt_003
-
Boundary conditions must be set before component instantiation — FlowAccumulator needs at least one open boundary node for drainage. If all boundaries are closed, flow has nowhere to go and drainage_area will be zero everywhere. → dt_011
-
Depression routing is not automatic — D8 flow routing stops at pits. Without
DepressionFinderAndRouterorPriorityFloodFlowRouter, interior pits accumulate flow and block downstream erosion. → dt_012 -
CFL stability for explicit diffusion — LinearDiffuser uses an explicit scheme with internal sub-stepping (alpha=0.15 × dx²/D). If the user wraps it in their own sub-stepping, they may defeat the internal CFL guard. → dt_013
-
Grid spacing affects slope calculation — Slope = Δz/Δx. If xy_spacing is in degrees instead of meters, slopes will be orders of magnitude wrong, producing unrealistic erosion. → dt_004
-
FlowAccumulator must run before erosion components — StreamPowerEroder, ErosionDeposition, and Space all require
drainage_area,flow__receiver_node, andflow__upstream_node_orderto be populated. These are outputs of FlowAccumulator. Running erosion first yields zero drainage area. → dt_014
Validation: SPACE Binary — Atchafalaya Qs-Q + Concavity (2026-04-29)
Configuration — Test 1 (Qs-Q binary)
- Grid: 3×51 quasi-1D channel, dx=500 m, slope=5×10⁻⁴, soil_depth=2 m
- Component: SpaceLargeScaleEroder (K_sed=2.5e-5, m_sp=0.5, n_sp=1.0, v_s=5 m/yr, H*=1 m)
- Method: warmup 800 steps × 1 yr; then 7 runoff-rate probes (single SPACE step, no uplift)
- Fit: log(Qs) vs log(Q) — sediment mass flux vs water flux
Configuration — Test 2 (Steady-state concavity)
- Grid: 50×50 RasterModelGrid, dx=200 m, open south boundary
- Duration: 4000 × 500 yr = 2 Myr; uplift U=1e-3 m/yr
Results
| Test | Metric | Value | Threshold | Status |
|---|---|---|---|---|
| T1: Qs-Q binary | b_sim | 0.897 | [0.35, 1.20] | PASS |
| T1: Qs-Q binary | r (log-log) | 0.978 | ≥ 0.90 | PASS |
| T2: Concavity | θ | 0.464 | [0.40, 0.55] | PASS |
| Context: data quality | obs r | 0.809 | ≥ 0.65 | PASS |
Key Findings
- b_sim=0.897 reflects transport-limited SPACE behavior (86.5% sediment-dominated, long travel distance) — physically correct for these parameters
- Detachment-limited regime (b→m=0.5) requires bare bedrock (H→0) or very high v_s; transport-limited (b→1.0) matches Atchafalaya obs b=1.077
- Fitting Qs vs Q (not SSC vs Q) is essential — SSC = Qs/Q ∝ Q^(m-1) = Q^(-0.5) always gives negative exponent regardless of model correctness
- Steady-state θ=0.464 confirms SPACE correctly implements stream-power scaling (Whipple & Tucker 1999 theoretical θ=0.5)
Output
- Figure:
outputs/landlab_atchafalaya_ssc_q/validation_figure.png - Metrics:
outputs/landlab_atchafalaya_ssc_q/metrics.json - Run:
python tools/dissect_atchafalaya_ssc_q_surrogate.py
Validation: Loess Plateau Real-DEM Slope-Area (2026-04-29)
Configuration
- DEM: SRTM 30m, tile N36E109 (~36.64°N 109.33°E, Shaanxi, China)
- Clip: 500×500 px (15 km × 15 km), dx=30 m, z=[953, 1369] m
- Tool:
convert_dem_to_grid.py→FlowAccumulator(D8 + DepressionFinderAndRouter) - Slope method: 2D spatial gradient |∇z| (binned; D8 receiver slope is noisy on SRTM)
- Channel threshold: A ≥ 0.1 km²; 25 log-spaced area bins
Results vs Published Loess Plateau Concavity
| Metric | Simulated | Reference | Status |
|---|---|---|---|
| Concavity θ | 0.1631 | 0.10–0.35 (Loess Plateau gullies) | PASS |
| R² (binned) | 0.8659 | — | PASS (≥ 0.30) |
| Channel nodes | 12,315 | — | PASS (≥ 20) |
Key Findings
- Binned slope-area R²=0.866 confirms Landlab correctly extracts channel scaling from real terrain
- θ=0.163 is consistent with actively eroding Loess Plateau gullies (lower than global mountain-river average θ≈0.45)
- D8 receiver slope (
topographic__steepest_slope) gives θ≈0.13 and R²≈0.08 on SRTM — use spatial gradient instead (dt_018) - Single-outlet boundary (
closed_all_but_outlet) depresses θ toward 0 — useopen_allfor DEM clips (dt_019) - SRTM integer elevations create discrete slope bands; log-spaced binning recovers R²=0.87 from a raw scatter of R²=0.07
Triplets exercised
- dt_018: D8 slope artifact on filled DEMs → use spatial gradient
- dt_019: single-outlet boundary depresses θ → use open_all
Validation: Loess Plateau Sediment Yield + Particulate-P Export (2026-04-29)
Configuration
- DEM: SRTM 30m, tile N36E109, clip 500×500 px (15 km × 15 km), dx=30 m, z=[953, 1369] m
- Model: Detachment-limited stream power law E = K_sp × A^0.5 × S (no SPACE; see dt_022)
- Boundary:
closed_all_but_outlet(lowest boundary node = outlet, z=959 m, node 189499) - Flow routing:
FlowAccumulator(D8 + DepressionFinderAndRouter), runoff_rate=3.17e-9 m/s (100 mm/yr) - K_sp calibration: K_sp = SY_target / (mean(A^0.5 × S) × ρ_bulk × 1e6) → 1.64e-4 m^0/yr
- Bulk density: 1400 kg/m³; Soil P: 800 mg/kg; P enrichment ratio: 2.0
- P export: TP = SY × 10 × P_soil_ppm × ER × 1e-6
Results vs Published Yellow River Tributary Yields
| Metric | Simulated | Reference | Status |
|---|---|---|---|
| Sediment yield | 8390.6 t/km²/yr | 2,000–10,000 t/km²/yr (Liu 1985; Wang 2011) | PASS |
| Particulate-P export | 134.25 kg/ha/yr | 0.5–200 kg/ha/yr (threshold) | PASS |
Key Findings
- SPACE is unsuitable for annual SY from real DEMs — transport-limited regime gives ~2.7% export efficiency; use DL stream power instead (dt_022)
- K_sp calibrated analytically so mean gross erosion = target SY; no iterative tuning needed
- Spatial gradient |∇z| (np.gradient on 2D elevation array) gives physically meaningful slopes for SY; D8 receiver slope is too noisy at cell scale (dt_018)
closed_all_but_outletis mandatory for watershed-scale SY (single catchment definition);open_allcreates many sub-basins and underestimates outlet flux- P export (TP=134.25 kg/ha/yr) is gross potential; actual at watershed outlet is 0.3–0.7× lower due to within-basin P retention
Triplets exercised
- dt_022: SPACE near-zero SY on real DEMs → use DL stream power for annual SY
Output
- Figure:
outputs/landlab_loess_sediment/validation_figure.png - Metrics:
outputs/landlab_loess_sediment/metrics.json - Run:
python tools/dissect_loess_plateau_sediment_yield.py
Validation: Whipple & Tucker (1999) Steady-State Test (2026-03-25)
Configuration
- Grid: 50×50
RasterModelGrid, dx=100 m - Components: FlowAccumulator(D8) + StreamPowerEroder(K=1e-5, m=0.5, n=1.0) + LinearDiffuser(D=0.01 m²/yr)
- Uplift: 1e-3 m/yr uniform
- Duration: 2 Myr, dt=500 yr (4000 steps)
- Boundary: South edge open, other three closed
Results vs Analytical Solution
| Metric | Simulated | Expected | Status |
|---|---|---|---|
| Slope-area r² | 0.9978 | ~1.0 | PASS |
| Concavity (θ) | 0.488 | 0.500 | PASS (2.4% error) |
| Relief (m) | 810.2 | ~800 | PASS |
| Wall time | 2.2 s | — | — |
Key Findings
- Slope-area correlation r=0.9978 confirms steady-state channel profiles
- Concavity index θ=0.488 vs theoretical 0.500 (2.4% error, within discretization)
- Relief stabilized at 810.2 m after 2 Myr of evolution
- All three components (FlowAccumulator, StreamPowerEroder, LinearDiffuser) validated
- Wall time 2.2s for 4000 steps on 50×50 grid demonstrates computational efficiency
Tools Reference
| Tool | Stage | Path | Lines | Purpose |
|---|---|---|---|---|
convert_dem_to_grid.py |
s2 | tools/convert_dem_to_grid.py |
~250 | Load DEM → RasterModelGrid with fields |
convert_soil_params.py |
s3 | tools/convert_soil_params.py |
~220 | HWSD soil → grid fields (depth, K, porosity) |
run_landlab.py |
s5 | tools/run_landlab.py |
~280 | Execute Landlab simulation from YAML config |
parse_landlab_output.py |
s6 | tools/parse_landlab_output.py |
~250 | Extract grid fields → CSV/NetCDF + metrics |
dissect_atchafalaya_ssc_q_surrogate.py |
validation | tools/dissect_atchafalaya_ssc_q_surrogate.py |
~280 | SSC-Q surrogate validation vs USGS-07381600 (n=357, r=0.8092) |
dissect_loess_plateau_slope_area.py |
validation | tools/dissect_loess_plateau_slope_area.py |
~430 | Real-DEM slope-area: SRTM N36E109, θ=0.163 R²=0.866 PASS |
dissect_loess_plateau_sediment_yield.py |
validation | tools/dissect_loess_plateau_sediment_yield.py |
~280 | DL stream power SY + particulate-P: SY=8390.6 t/km²/yr PASS, TP=134.25 kg/ha/yr PASS |
Calibration Parameters (Priority Order)
| Parameter | Component | Range | Controls | Sensitivity |
|---|---|---|---|---|
| K_sp | StreamPowerEroder | 1e-7–1e-3 | Channel erosion rate | Very High |
| linear_diffusivity | LinearDiffuser | 0.001–1.0 m²/yr | Hillslope curvature | High |
| m_sp | StreamPowerEroder | 0.3–0.7 | Area-discharge scaling | High |
| n_sp | StreamPowerEroder | 0.7–2.0 | Slope sensitivity | High |
| uplift_rate | (user loop) | 1e-5–1e-2 m/yr | Relief, channel gradient | Very High |
| v_s | Space/ErosionDeposition | 0.01–10 m/s | Transport vs detachment | High |
| K_sed / K_br | Space | 1e-7–1e-3 | Alluvium vs bedrock erosion | High |
| H_star | Space | 0.1–2.0 m | Alluvial cover effect | Medium |
| soil_production_maximum_rate | ExponentialWeatherer | 1e-5–1e-3 m/yr | Soil thickness | Medium |
| soil_production_decay_depth | ExponentialWeatherer | 0.2–2.0 m | Weathering decay | Medium |
Data Requirements
| Data | Source | Format | Notes |
|---|---|---|---|
| DEM | SRTM/ASTER/LiDAR | GeoTIFF or ESRI ASCII | Must be projected (meters) |
| Soil depth | HWSD/SoilGrids | Raster/CSV | Convert cm → m |
| Rainfall | CMFD/MSWX/ERA5 | NetCDF | Convert mm/hr → m/s for water__unit_flux_in |
| Uplift rate | Literature/GPS | Scalar or field | Must match dt units |
| Erodibility (K) | Literature/calibration | Scalar or field | Units depend on m, n |
| Diffusivity (D) | Literature/calibration | Scalar | m²/yr typical |
Quick Start
# Install
pip install landlab
# Run input converter
python tools/convert_dem_to_grid.py --dem input.asc --output grid.nc --spacing 100
# Run soil parameter converter
python tools/convert_soil_params.py --hwsd soil.csv --grid grid.nc --output params.json
# Run simulation
python tools/run_landlab.py --config config.yaml --output results/
# Parse output
python tools/parse_landlab_output.py --input results/ --output results/summary.csv --metrics slope_area
Diagnostic Triplets Summary
| ID | Severity | Domain | Short Description |
|---|---|---|---|
| dt_001 | silent | unit_conversion | Rainfall in mm/hr instead of m/s |
| dt_002 | silent | unit_conversion | Diffusivity time-unit mismatch |
| dt_003 | silent | unit_conversion | K_sp units wrong for given m,n |
| dt_004 | silent | unit_conversion | Grid spacing in degrees not meters |
| dt_005 | silent | unit_conversion | Timestep unit mismatch with parameters |
| dt_006 | silent | unit_conversion | Soil depth in cm instead of m |
| dt_007 | silent | unit_conversion | Elevation in feet instead of meters |
| dt_008 | silent | unit_conversion | Rainfall mm vs m for soil moisture |
| dt_009 | silent | parameter_format | Manning n vs Chezy C confusion |
| dt_010 | degraded | input_format | DEM nodata not handled |
| dt_011 | fatal | boundary_condition | All boundaries closed |
| dt_012 | degraded | missing_component | No depression routing |
| dt_013 | degraded | numerical_stability | CFL violation in diffusion |
| dt_014 | fatal | component_order | Erosion before flow routing |
| dt_015 | silent | field_location | Field at wrong grid element |
| dt_016 | silent | ssc_q_rating_curve | Wrong runoff_rate breaks SPACE SSC-Q correlation |
| dt_017 | silent | ssc_q_rating_curve | m_sp/n_sp wrong → concavity outside [0.40, 0.55] |
| dt_018 | silent | slope_area_analysis | D8 receiver slope polluted by depression-filling → use spatial gradient |
| dt_019 | silent | slope_area_analysis | Single-outlet boundary depresses θ toward 0 → use open_all for DEM clips |
| dt_020 | silent | ssc_q_rating_curve | Fitting SSC vs Q gives negative exponent — always fit Qs vs Q |
| dt_021 | silent | ssc_q_rating_curve | SPACE b outside [0.35,1.20] — check DL vs TL regime (H, H*, v_s) |
| dt_022 | silent | model_scope_mismatch | SPACE near-zero SY on real DEMs — use DL stream power for annual SY |
Reference: diagnostics/triplets.yaml
File Structure
ki/
├── SKILL.md # This file
├── tools/
│ ├── convert_dem_to_grid.py # DEM → Landlab grid
│ ├── convert_soil_params.py # Soil data → grid fields
│ ├── run_landlab.py # Execution wrapper
│ ├── parse_landlab_output.py # Output → CSV + metrics
│ ├── dissect_atchafalaya_ssc_q_surrogate.py # SPACE Qs-Q + concavity validation
│ ├── dissect_loess_plateau_slope_area.py # Real-DEM slope-area validation
│ └── dissect_loess_plateau_sediment_yield.py # DL stream power SY + P export validation
├── docs/
│ ├── s1_grid_setup.md # Grid creation skill
│ ├── s2_input_preparation.md # DEM/forcing loading
│ ├── s3_soil_parameters.md # Soil/weathering params
│ ├── s4_component_assembly.md # Coupling components
│ └── s5_execution_output.md # Running and analyzing
└── diagnostics/
└── triplets.yaml # 22 symptom→diagnosis→remedy