Skip to content
Back to skills

MARRMoT

ASecurity

**Package**: hydrocraft-marrmot v1.0.0 **Model**: MARRMoT v2.1.2 (Modular Assessment of Rainfall-Runoff Models Toolbox) **Domain**: Conceptual rainfall-runoff hydrology **Language**: MATLAB / GNU Octave (with Python BMI interface) **License**: GNU GPL v3 **Created**: 2026-03-25 ---

  • 200 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 11, 2026
devopspythongobashspringdockerdebugginggitapidocumentation

Works with

  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 20 files and shows the line behind each finding

Scanned September 11, 2026

npx -y skills add lzwei196/KISS---Knowledge-Infrastructure-for-Scientific-Simulation --skill MARRMoT --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of MARRMoT?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for MARRMoT
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/lzwei196-marrmot/badge)](https://www.skillsdirectory.com/skills/lzwei196-marrmot)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
> **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:
> 1. Check `diagnostics/triplets.yaml` for a matching error pattern
> 2. Try the fix suggested in the triplet's `remedy` section
> 3. 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:
> 1. **Check triplets** — `diagnostics/triplets.yaml` may already cover this error
> 2. **Read official docs** — The model's own documentation for expected formats/units
> 3. **Find working examples** — Check `outputs/` or the model's shipped test data
> 4. **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:BEGIN (projected by generate_skill_map.py — edit the KI, not this table) -->
## 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/` (5 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` (20 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` (18 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>`.*
<!-- KI-MAP:END -->

<!-- KI-TOOL-INDEX:BEGIN (projected by generate_skill_map.py — the discoverability contract: every public tool, exact path; PURPOSE stays human-authored elsewhere) -->
### 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/convert_forcing.py` | `KISSPATH_PYTHON_ENV/bin/python {KI}/tools/convert_forcing.py --help` |
| `tools/convert_parameters.py` | `KISSPATH_PYTHON_ENV/bin/python {KI}/tools/convert_parameters.py --help` |
| `tools/parse_output.py` | `KISSPATH_PYTHON_ENV/bin/python {KI}/tools/parse_output.py --help` |
| `tools/run_marrmot.py` | `KISSPATH_PYTHON_ENV/bin/python {KI}/tools/run_marrmot.py --help` |

*5 public tools; `_`-prefixed helpers and packaging files excluded.*
<!-- KI-TOOL-INDEX:END -->

# MARRMoT Knowledge Infrastructure

**Package**: hydrocraft-marrmot v1.0.0
**Model**: MARRMoT v2.1.2 (Modular Assessment of Rainfall-Runoff Models Toolbox)
**Domain**: Conceptual rainfall-runoff hydrology
**Language**: MATLAB / GNU Octave (with Python BMI interface)
**License**: GNU GPL v3
**Created**: 2026-03-25

---

## 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 CMFD unit documentation and known traps.
See `data_ki/HWSD/SKILL.md` for soil property documentation.
See `data_ki/ObservedQ/SKILL.md` for observed discharge data.


## Overview

MARRMoT is a MATLAB/Octave toolbox containing **47 unique conceptual hydrological
model structures** with standardized parameter ranges and robust numerical solvers.
It enables objective comparison of lumped rainfall-runoff models under identical
numerical and data conditions.

Key characteristics:

- 47 model structures ranging from 1-parameter/1-store to 24-parameter/6-store
- 111 reusable flux functions (evaporation, baseflow, infiltration, percolation, etc.)
- 8 unit hydrograph routing methods (half/full triangle, gamma, uniform, delay)
- Object-oriented architecture: all models inherit from `MARRMoT_model` superclass
- Implicit Euler ODE solver with Newton-Raphson + fallback strategies
- Built-in calibration via CMA-ES optimiser
- Built-in objective functions: KGE, NSE, RMSE, inverse-KGE, log-NSE, etc.
- Python interface via BMI (Basic Model Interface) using oct2py bridge
- Docker support for containerised execution
- All fluxes computed in mm/d; storages in mm

---

## Installation

### Option A: MATLAB (native)

Requires MATLAB R2016b+ (R2021b+ recommended) with Optimization Toolbox.

```bash
git clone https://github.com/wknoben/MARRMoT
# In MATLAB: addpath(genpath('MARRMoT/MARRMoT'))
```

### Option B: GNU Octave

Requires Octave 6.4.0+ with `optim` package.

```bash
apt-get install octave octave-optim
git clone https://github.com/wknoben/MARRMoT
# In Octave: addpath(genpath('MARRMoT/MARRMoT'))
# pkg load optim  % auto-loaded by MARRMoT_model constructor
```

### Option C: Docker (Octave-based)

```bash
docker build -t marrmot .
docker run -v $PWD:/data --entrypoint octave marrmot /data/run_script.m
```

### Option D: Python BMI bridge

```bash
pip install oct2py numpy scipy
# Requires Octave installed on system
```

### Dependencies

| Component     | Required          | Purpose                          |
|---------------|-------------------|----------------------------------|
| MATLAB/Octave | Yes               | Core runtime                     |
| Optim Toolbox | Yes (calibration) | CMA-ES, fsolve, lsqnonlin       |
| oct2py        | Python BMI only   | MATLAB/Octave-Python bridge      |
| numpy/scipy   | Python BMI only   | Numerical support                |
| Docker        | Optional          | Containerised execution          |

### Test installation

```matlab
m = feval('m_01_collie1_1p_1s');
m.theta = 100;
m.input_climate = [ones(365,1)*5, ones(365,1)*2, ones(365,1)*15];
m.delta_t = 1;
m.S0 = 50;
m.get_output();
% Should print water balance summary
```

---

## Pipeline

The MARRMoT workflow converts global hydrometeorological data into
catchment-scale rainfall-runoff simulations using one of 47 model structures.

| # | Stage                  | Tool                       | Description                                           |
|---|------------------------|----------------------------|-------------------------------------------------------|
| 1 | Forcing preparation    | `convert_forcing.py`       | Convert global met data to [P, Ep, T] in mm/d and C   |
| 2 | Parameter setup        | `convert_parameters.py`    | Map soil/land properties to model parameter ranges     |
| 3 | Model selection        | (manual)                   | Choose from 47 model structures                       |
| 4 | Initial conditions     | (manual)                   | Set S0 vector (typically zeros or spin-up)             |
| 5 | Execution              | `run_marrmot.py`           | Run model via Octave subprocess                       |
| 6 | Output parsing         | `parse_output.py`          | Extract Q, Ea, storage to CSV                         |
| 7 | Validation             | `parse_output.py`          | Compute NSE, KGE, PBIAS against observations          |
| 8 | Calibration            | `run_marrmot.py` (loop)    | CMA-ES or Monte Carlo parameter optimisation          |
| 9 | Multi-model comparison | (manual)                   | Compare structures using consistent metrics           |

**Parallelism**: Stages 1-2 can run in parallel. Stages 3-4 are manual decisions.
Stage 5 depends on 1-4. Stages 6-7 depend on 5. Stage 8 is iterative on 5-7.

---

## Unit Trap Table

These are the critical unit conversions that cause **silent failures** if wrong.
Every value enters the model in specific units; no internal conversion is performed.

| Variable            | MARRMoT expects | Common source unit | Conversion                          | Trap ID |
|---------------------|-----------------|--------------------|-------------------------------------|---------|
| Precipitation       | mm/d            | mm/3h (CMFD)       | sum 8 values per day                | dt_001  |
| Precipitation       | mm/d            | kg/m2/s (ERA5)     | x 86400                             | dt_002  |
| Precipitation       | mm/d            | m/d (some GCMs)    | x 1000                              | dt_003  |
| PET                 | mm/d            | W/m2 (net rad)     | Penman-Monteith or Hargreaves       | dt_004  |
| PET                 | mm/d            | mm/month           | / days_in_month                     | dt_005  |
| PET (cold/high-alt) | mm/d            | Oudin/Hargreaves under-est | check Ep>=P-Q; use --pet-method priestley_taylor | dt_019  |
| Temperature         | deg C           | K (ERA5/CMFD)      | - 273.15                            | dt_006  |
| Temperature         | deg C           | deg F              | (F - 32) x 5/9                     | dt_007  |
| Storage (Smax)      | mm              | m                  | x 1000                              | dt_008  |
| Time step (delta_t) | days            | hours              | / 24                                | dt_009  |
| Streamflow (obs)    | mm/d            | m3/s               | x 86400 / (area_km2 x 1e6) x 1000  | dt_010  |

---

## Tools Reference

| Tool                  | Stage       | Script                           | Lines | Purpose                                         |
|-----------------------|-------------|----------------------------------|-------|--------------------------------------------------|
| `convert_forcing`     | s1_forcing  | `tools/convert_forcing.py`       | ~300  | Global met data to MARRMoT climate array         |
| `convert_parameters`  | s2_params   | `tools/convert_parameters.py`    | ~280  | Soil/land data to model parameter vector         |
| `run_marrmot`         | s5_execute  | `tools/run_marrmot.py`           | ~250  | Execute model via Octave subprocess              |
| `parse_output`        | s6_output   | `tools/parse_output.py`          | ~250  | Parse Octave output to CSV with metrics          |

---

## Critical Domain Knowledge

These non-obvious facts cause silent failures. Each is linked to a diagnostic triplet.

1. **dt_001 / dt_002 / dt_003**: Precipitation units must be mm/d. ERA5 provides
   kg/m2/s (multiply by 86400). CMFD provides mm/3h (sum 8 per day). GCMs may
   provide m/d (multiply by 1000). Wrong units produce runoff 1000x too high/low.

2. **dt_004 / dt_005 / dt_019**: PET must be pre-computed externally. MARRMoT does NOT compute
   PET from radiation. Common trap: passing radiation (W/m2) as PET. Use Hargreaves
   or Penman-Monteith to convert. Monthly PET must be divided by days_in_month.
   **PET-method choice (dt_019):** Oudin and Hargreaves are temperature-index formulae
   calibrated on temperate lowlands and structurally UNDER-estimate atmospheric demand on
   cold, high-altitude, high-radiation basins (e.g. the Tibetan Plateau / upper Yellow R).
   BEFORE calibrating, check `Ep_annual >= (P - Q_obs)_annual`. If it fails and downwelling
   radiation is available (CMFD ALWAYS supplies srad/lrad/pres), rebuild forcing with
   `convert_forcing.py --pet-method priestley_taylor --srad-col ... --lrad-col ... --pres-col ...`
   (always pass `--pres-col`; the 101325 Pa sea-level fallback inflates gamma and
   under-estimates Ep exactly at altitude). The trap tell: water_balance FAIL with GR4J x2
   (groundwater exchange) pinned near its bound while NSE/r still look respectable.

3. **dt_006**: Temperature in Kelvin silently produces wrong snow/rain partitioning
   in models with temperature thresholds (m_06, m_12, m_37, etc.). No error raised.

4. **dt_009**: delta_t must match the actual time step of forcing data. If forcing is
   daily but delta_t=24 (hours), all fluxes are scaled by 24x. Always use delta_t=1
   for daily data.

5. **dt_011**: Input climate array column order is [P, Ep, T], NOT [P, T, Ep].
   Swapping Ep and T is silent and produces nonsensical evaporation.

6. **dt_012**: Initial storage S0 vector length must equal number of stores. Too few
   elements cause MATLAB index error; too many are silently ignored. Check
   `m.numStores` before setting S0.

7. **dt_013**: Newton-Raphson solver may fail silently on stiff problems (high Smax
   with very low initial storage). Increase `resnorm_maxiter` to 12+ or check
   `solverSteps` output for solver fallback warnings.

8. **dt_014**: Water balance check is printed to console but NOT enforced. A water
   balance error > 1 mm indicates numerical problems, but the model does not stop.

9. **dt_015**: Unit hydrograph routing assumes delta_t matches the routing time base.
   Using sub-daily time steps with daily UH parameters shifts the hydrograph peak.

---

## Output Description

MARRMoT's `get_output()` method returns a structure containing three main arrays: (1) `Q` -- simulated streamflow in mm/d (same length as forcing time series), (2) `Ea` -- actual evapotranspiration in mm/d, and (3) `S` -- storage time series in mm for each model store (columns = stores). A water balance summary is printed to the console. The `parse_output.py` tool extracts these into a CSV with columns `date, Q_sim (mm/d), Ea (mm/d), S1, S2, ...` and computes validation metrics (NSE, KGE, PBIAS) against observed streamflow. To convert Q from mm/d to m^3/s, multiply by `catchment_area_km2 * 1e6 / 86400 / 1000`.

## 6. Output Description -- dag-sourced

This section restates `dag.yaml`. If this section and `dag.yaml` disagree, `dag.yaml` wins.

**Headline output**: `Q` -- Total simulated streamflow at the catchment outlet (sum of fluxes in FluxGroups.Q, after unit-hydrograph routing). (`mm/d`)

| Output variable (dag `var`) | validation rank | Unit | Emitted in | Description |
|-----------------------------|-----------------|------|------------|-------------|
| `Q` | 1 | mm/d | `fluxOutput.Q (get_output / get_streamflow)` | Total simulated streamflow at the catchment outlet (sum of fluxes in FluxGroups.Q, after unit-hydrograph routing). |
| `Ea` | 2 | mm/d | `fluxOutput.Ea (get_output)` | Actual evapotranspiration -- catchment water flux to the atmosphere, aggregated from the model's evaporation fluxes. |
| `S` | 3 | mm | `storeInternal (get_output)` | Storage time series per model store (e.g. soil moisture, fast reservoir, slow/baseflow reservoir, snow store); number of stores varies by structure (1 to 8). |
| `waterBalance` | 4 | mm | `waterBalance (check_waterbalance)` | Scalar water-balance closure residual over the run; should be near zero (<1 mm). Computed and printed but NOT enforced by the model. |

Other dag outputs are `Ea`, `S`, and `waterBalance`. Scoring and observation binding should treat `Q` as the rank-1 output.

## 8. Unit Conversion Table -- source-restated

This table restates the KI's existing unit traps and `docs/format_spec.yaml`. MARRMoT performs no internal unit conversion on the forcing array or parameter vector.

| Variable | Source unit | MARRMoT / comparison unit | Conversion | Source in KI |
|----------|-------------|---------------------------|------------|--------------|
| Precipitation | mm/3h (CMFD) | mm/d | sum 8 values per day | `Unit Trap Table`, `docs/format_spec.yaml` |
| Precipitation | kg/m2/s (ERA5) | mm/d | x 86400 | `Unit Trap Table`, `docs/format_spec.yaml` |
| Precipitation | m/d (some GCMs) | mm/d | x 1000 | `Unit Trap Table`, `docs/format_spec.yaml` |
| Potential evapotranspiration | W/m2 (net radiation) | mm/d | compute PET externally with Penman-Monteith or Hargreaves; do not pass radiation as PET | `Unit Trap Table`, `docs/format_spec.yaml` |
| Potential evapotranspiration | mm/month | mm/d | divide by `days_in_month` | `Unit Trap Table`, `docs/format_spec.yaml` |
| Temperature | K | deg C | subtract 273.15 | `Unit Trap Table`, `docs/format_spec.yaml` |
| Temperature | deg F | deg C | `(F - 32) x 5/9` | `Unit Trap Table`, `docs/format_spec.yaml` |
| Storage parameter (`Smax`, `x1`, `x3`) | m | mm | x 1000 | `Unit Trap Table`, `docs/format_spec.yaml` |
| Time step (`delta_t`) | hours | days | divide by 24 | `Unit Trap Table`, `docs/format_spec.yaml` |
| Observed streamflow | m3/s | mm/d | `Q_mm = Q_m3s * 86400 / (area_km2 * 1e6) * 1000` | `Unit Trap Table`, `dag.yaml`, `docs/format_spec.yaml` |
| Simulated streamflow `Q` | mm/d | m3/s | `Q_m3s = Q_mm_d * area_km2 * 1e6 / 86400 / 1000` | `Output Description`, `dag.yaml` |

## 11. Validated Results -- convention-sourced

**Status**: body campaign pending. This SKILL body does not claim achieved calibration, validation, or full-period scores until they are produced by the actual MARRMoT package and parsed by the KI tools.

**Rank-1 validation target**: `Q` (`mm/d`) -- Total simulated streamflow at the catchment outlet (sum of fluxes in FluxGroups.Q, after unit-hydrograph routing).

| Dag variable | Metric | Direction | Convention bands, with citation key on each band | Validation status in this body |
|--------------|--------|-----------|--------------------------------------------------|--------------------------------|
| `Q` | `nse` | maximize | very_good: 0.8 (`moriasi2015`, `moriasi2007`); good: 0.6 (`moriasi2015`, `moriasi2007`); satisfactory: 0.5 (`moriasi2015`, `moriasi2007`) | pending |
| `Q` | `pbias` | zero_centered | very_good: 3.0 (`moriasi2015`); good: 10.0 (`moriasi2015`); satisfactory: 15.0 (`moriasi2015`) | pending |
| `Q` | `pbias` | zero_centered | very_good: 3.0 (`moriasi2015`); good: 10.0 (`moriasi2015`); satisfactory: 15.0 (`moriasi2015`) | pending |
| `Ea` | `nse` | maximize | satisfactory: no cited threshold (`ershadi2014`) | pending |

For `Q` PBIAS, apply the bands to absolute PBIAS magnitude because the convention direction is zero-centered. For `Ea` NSE, do not substitute a numeric pass line: the convention records the satisfactory band as null, so this body states `no cited threshold`.

---

## Validation

### Test case: Buffalo River, Tennessee, USA

- **Model**: m_01_collie1_1p_1s (simplest, 1 parameter)
- **Period**: Included in MARRMoT_example_data.mat
- **Source**: BMI test case data

### HyMOD benchmark (m_29_hymod_5p_5s)

- **Parameters**: [Smax=200, b=0.5, a=0.7, kf=0.1, ks=0.01]
- **Typical KGE**: 0.6-0.85 after calibration on daily streamflow
- **Typical NSE**: 0.5-0.80

---

## Calibration Parameters

For HyMOD (m_29), the most commonly used benchmark model:

| Parameter | Symbol | Range       | Unit  | Sensitivity | Description                    |
|-----------|--------|-------------|-------|-------------|--------------------------------|
| Smax      | S_max  | [1, 2000]   | mm    | High        | Maximum soil moisture storage  |
| b         | b      | [0, 10]     | -     | Medium      | Distribution function shape    |
| a         | alpha  | [0, 1]      | -     | High        | Flow split fast/slow           |
| kf        | k_f    | [0, 1]      | 1/d   | Medium      | Fast reservoir rate constant   |
| ks        | k_s    | [0, 1]      | 1/d   | Low         | Slow reservoir rate constant   |

For other models, parameter ranges are defined in each model file's `parRanges`
property. Run `m = feval('m_XX_name'); m.parRanges` to retrieve bounds.

### Calibration optimisers (`run_marrmot.py --calibrate`)

Two optimisers are available via `--optimizer`:

- `mc` (default, legacy): uniform Monte-Carlo over `parRanges`. Fine for ≤3-4
  parameters; **hopeless beyond ~6** because the good region is an
  exponentially small fraction of the hypercube. Writes incremental best
  (timeout-safe).
- `cmaes` (**recommended**): MARRMoT's own CMA-ES (`my_cmaes`) via the
  `MARRMoT_model.calibrate` method — the optimiser the toolbox ships and
  documents (`User manual/Examples/workflow_example_4.m`). Scales to the
  6-15 parameter snow/soil/GW structures. Options:
  - `--of-name` : objective function (`of_NSE`, `of_KGE`, `of_log_NSE`, ...).
    KGE-family take a 3-weight vector (auto-handled); NSE-family take none.
    `check_and_select` natively drops NaN / negative obs, so **seasonal/gappy
    HYDAT records (e.g. prairie creeks gauged only Mar-Oct) need no masking**.
  - `--restarts N` : **IPOP restarts (doubled popsize each)**. A single
    mean-start CMA-ES run gets trapped in a local optimum on the rugged
    conceptual-hydrology objective surface — observed at 05CG004:
    mean-start cal_NSE **0.199**, with `--restarts 6` cal_NSE **0.329**.
    Always use ≥5 restarts for a real calibration.
  - `--max-fun-evals` : total eval budget across restarts. Each eval is one
    full simulation over `1:cal_end`. **Cost scales sharply with numStores**:
    m_12 alpine2 (2 stores) ≈ 1.6 s/eval (~550 evals ≈ 15 min); m_37 hbv
    (5 stores) ≈ **50 s/eval** under the implicit Euler solver — not
    "uncalibratable", but budget-limited: pair it with `--timeout` (below) and
    report how many evals it actually got.
  - `--lb/--ub` : optional tightened sampling bounds (JSON arrays).
  - `--timeout` : wall-clock cap, and it is **safe to rely on**. BOTH optimisers
    persist their incumbent to `best_theta.json` on every improvement (CMA-ES via
    the `kdt_of_persist` objective shim), and expiry is handled as a normal
    budget-limited outcome (`status: "partial"`, `timed_out: true`) rather than an
    exception. This is what makes a whole-toolbox sweep tractable: give every
    structure the same wall-clock cap, let the cheap ones converge and the
    expensive ≥5-store ones return their best-so-far. **Always report the eval
    budget next to a partial structure's metric** — a low NSE there means
    "under-searched", not "structurally unsuitable".

### Snowfall undercatch correction (`convert_forcing.py --cold-precip-scale`)

NASA POWER (MERRA-2) under-catches **solid** precip far more than rain. For
snowmelt-dominated basins the spring freshet — the NSE-dominant signal — is
driven by accumulated winter snow, so under-caught snowfall systematically
damps the simulated freshet. `--cold-precip-scale F --cold-precip-threshold T`
multiplies precip by `F` only on days with mean-T < `T` (default 0 °C),
boosting winter accumulation **without** inflating ET-dominated summer months
(which a global `--precip-scale` does, over-producing runoff). At 05CG004
(Bullpound Ck), `--cold-precip-scale 1.7` lifted held-out val NSE
0.152→0.209 and improved r at every period.

---

## Coupling Points

| Source              | Target           | Variable     | Unit    | Notes                          |
|---------------------|------------------|--------------|---------|--------------------------------|
| ERA5/CMFD/MSWX      | MARRMoT forcing  | Precip       | mm/d    | Aggregated to daily            |
| Hargreaves/PM        | MARRMoT forcing  | PET          | mm/d    | Pre-computed externally        |
| ERA5/CMFD            | MARRMoT forcing  | Temperature  | deg C   | Mean daily                     |
| MARRMoT output Q     | Routing model    | Streamflow   | mm/d    | Convert to m3/s for routing    |
| Observed discharge   | Validation       | Streamflow   | mm/d    | Convert from m3/s using area   |
| HWSD/SoilGrids       | Parameters       | Soil props   | various | Map to Smax, percolation rates |

---

## Data Requirements

| Data Type            | Source            | Status    | Path / Notes                             |
|----------------------|-------------------|-----------|------------------------------------------|
| Precipitation        | ERA5 / CMFD       | Required  | Must convert to mm/d                     |
| Temperature          | ERA5 / CMFD       | Required  | Must convert to deg C                    |
| PET                  | Pre-computed      | Required  | Hargreaves from Tmin/Tmax or PM          |
| Observed streamflow  | GRDC / local      | Optional  | For calibration/validation, convert mm/d |
| Catchment area       | Shapefile / known | Optional  | For m3/s <-> mm/d conversion             |
| Soil properties      | HWSD / SoilGrids  | Optional  | For parameter estimation                 |

---

## Quick Start

```bash
# 1. Prepare forcing
python tools/convert_forcing.py \
  --input /path/to/era5_daily.nc \
  --output forcing.csv \
  --lat 35.5 --lon 117.3 \
  --start 2000-01-01 --end 2010-12-31

# 2. Estimate parameters from soil data
python tools/convert_parameters.py \
  --model m_29_hymod_5p_5s \
  --soil-data /path/to/hwsd.csv \
  --output params.json

# 3. Run model
python tools/run_marrmot.py \
  --forcing forcing.csv \
  --model m_29_hymod_5p_5s \
  --params params.json \
  --output run_output.json

# 4. Parse and validate output
python tools/parse_output.py \
  --input run_output.json \
  --observed /path/to/observed_q.csv \
  --output results.csv \
  --figure validation.png
```

---

## Diagnostic Triplets Summary

| ID     | Stage      | Failure Domain    | Severity | Symptom (short)                           |
|--------|------------|-------------------|----------|-------------------------------------------|
| dt_001 | s1_forcing | unit_conversion   | silent   | Precip 1000x too high (mm/3h not mm/d)    |
| dt_002 | s1_forcing | unit_conversion   | silent   | Precip wrong from kg/m2/s                  |
| dt_003 | s1_forcing | unit_conversion   | silent   | Precip wrong from m/d                      |
| dt_004 | s1_forcing | unit_conversion   | silent   | PET given as radiation W/m2                |
| dt_005 | s1_forcing | unit_conversion   | silent   | Monthly PET not divided by days            |
| dt_006 | s1_forcing | unit_conversion   | silent   | Temperature in K not C                     |
| dt_007 | s1_forcing | unit_conversion   | silent   | Temperature in F not C                     |
| dt_008 | s2_params  | unit_conversion   | silent   | Storage parameter in m not mm              |
| dt_009 | s5_execute | parameter_format  | degraded | delta_t mismatch (hours vs days)           |
| dt_010 | s6_output  | unit_conversion   | silent   | Observed Q in m3/s not mm/d               |
| dt_011 | s1_forcing | parameter_format  | silent   | Climate columns swapped [P,T,Ep]           |
| dt_012 | s5_execute | parameter_format  | fatal    | S0 length != numStores                     |
| dt_013 | s5_execute | runtime           | degraded | Solver fails on stiff ODE                  |
| dt_014 | s5_execute | silent_error      | silent   | Water balance error not enforced            |
| dt_015 | s5_execute | parameter_format  | degraded | UH time base vs delta_t mismatch           |

---

## File Structure

```
ki/
  SKILL.md                           # This file (agent entry point)
  tools/
    convert_forcing.py               # Stage 1: Global met -> MARRMoT climate
    convert_parameters.py            # Stage 2: Soil data -> parameter vector
    run_marrmot.py                   # Stage 5: Execute model via Octave
    parse_output.py                  # Stage 6: Parse output + compute metrics
  docs/
    s1_forcing_preparation.md        # Forcing data conversion skill
    s2_parameter_estimation.md       # Parameter mapping skill
    s3_model_selection.md            # Model selection guide
    s5_execution.md                  # Model execution skill
    s6_output_analysis.md            # Output parsing and validation skill
  diagnostics/
    triplets.yaml                    # 15+ diagnostic triplets
```

---

## All 47 Model Structures

**These are the EXACT names shipped in `Models/Model files/`** — pass the full
name (the `Class / file name` column) to `run_marrmot.py --model`. Derive nothing:
the `Np`/`Ns` counts are part of the filename, and several differ from the
upstream paper's tables (e.g. `m_26` is `flexi`, not "flexis"; `m_47` is
capitalised `IHM19`). Verify at runtime with
`m = feval('<name>'); [m.numParams m.numStores]`.

`Ns` (number of stores) is the cost driver: each store adds a dimension to the
implicit-Euler solve, so per-evaluation cost rises steeply (~1.6 s/eval at 2
stores to ~50 s/eval at 5). Budget calibration accordingly.

| ID | Name | Np | Ns | Class / file name |
|----|------|----|----|-------------------|
| m_01 | collie1 | 1 | 1 | `m_01_collie1_1p_1s` |
| m_02 | wetland | 4 | 1 | `m_02_wetland_4p_1s` |
| m_03 | collie2 | 4 | 1 | `m_03_collie2_4p_1s` |
| m_04 | newzealand1 | 6 | 1 | `m_04_newzealand1_6p_1s` |
| m_05 | ihacres | 7 | 1 | `m_05_ihacres_7p_1s` |
| m_06 | alpine1 | 4 | 2 | `m_06_alpine1_4p_2s` |
| m_07 | gr4j | 4 | 2 | `m_07_gr4j_4p_2s` |
| m_08 | us1 | 5 | 2 | `m_08_us1_5p_2s` |
| m_09 | susannah1 | 6 | 2 | `m_09_susannah1_6p_2s` |
| m_10 | susannah2 | 6 | 2 | `m_10_susannah2_6p_2s` |
| m_11 | collie3 | 6 | 2 | `m_11_collie3_6p_2s` |
| m_12 | alpine2 | 6 | 2 | `m_12_alpine2_6p_2s` |
| m_13 | hillslope | 7 | 2 | `m_13_hillslope_7p_2s` |
| m_14 | topmodel | 7 | 2 | `m_14_topmodel_7p_2s` |
| m_15 | plateau | 8 | 2 | `m_15_plateau_8p_2s` |
| m_16 | newzealand2 | 8 | 2 | `m_16_newzealand2_8p_2s` |
| m_17 | penman | 4 | 3 | `m_17_penman_4p_3s` |
| m_18 | simhyd | 7 | 3 | `m_18_simhyd_7p_3s` |
| m_19 | australia | 8 | 3 | `m_19_australia_8p_3s` |
| m_20 | gsfb | 8 | 3 | `m_20_gsfb_8p_3s` |
| m_21 | flexb | 9 | 3 | `m_21_flexb_9p_3s` |
| m_22 | vic | 10 | 3 | `m_22_vic_10p_3s` |
| m_23 | lascam | 24 | 3 | `m_23_lascam_24p_3s` |
| m_24 | mopex1 | 5 | 4 | `m_24_mopex1_5p_4s` |
| m_25 | tcm | 6 | 4 | `m_25_tcm_6p_4s` |
| m_26 | flexi | 10 | 4 | `m_26_flexi_10p_4s` |
| m_27 | tank | 12 | 4 | `m_27_tank_12p_4s` |
| m_28 | xinanjiang | 12 | 4 | `m_28_xinanjiang_12p_4s` |
| m_29 | hymod | 5 | 5 | `m_29_hymod_5p_5s` |
| m_30 | mopex2 | 7 | 5 | `m_30_mopex2_7p_5s` |
| m_31 | mopex3 | 8 | 5 | `m_31_mopex3_8p_5s` |
| m_32 | mopex4 | 10 | 5 | `m_32_mopex4_10p_5s` |
| m_33 | sacramento | 11 | 5 | `m_33_sacramento_11p_5s` |
| m_34 | flexis | 12 | 5 | `m_34_flexis_12p_5s` |
| m_35 | mopex5 | 12 | 5 | `m_35_mopex5_12p_5s` |
| m_36 | modhydrolog | 15 | 5 | `m_36_modhydrolog_15p_5s` |
| m_37 | hbv | 15 | 5 | `m_37_hbv_15p_5s` |
| m_38 | tank2 | 16 | 5 | `m_38_tank2_16p_5s` |
| m_39 | mcrm | 16 | 5 | `m_39_mcrm_16p_5s` |
| m_40 | smar | 8 | 6 | `m_40_smar_8p_6s` |
| m_41 | nam | 10 | 6 | `m_41_nam_10p_6s` |
| m_42 | hycymodel | 12 | 6 | `m_42_hycymodel_12p_6s` |
| m_43 | gsmsocont | 12 | 6 | `m_43_gsmsocont_12p_6s` |
| m_44 | echo | 16 | 6 | `m_44_echo_16p_6s` |
| m_45 | prms | 18 | 7 | `m_45_prms_18p_7s` |
| m_46 | classic | 12 | 8 | `m_46_classic_12p_8s` |
| m_47 | IHM19 | 16 | 4 | `m_47_IHM19_16p_4s` |

Files in this skill

  • SKILL.md31.5 KB
  • calibration.yaml4.8 KB
  • dag.yaml29.5 KB
  • diagnostics/triplets.yaml24.1 KB
  • docs/format_spec.yaml23.9 KB
  • docs/papers.json5.6 KB
  • docs/s1_forcing_preparation.md4.3 KB
  • docs/s2_parameter_estimation.md4.4 KB
  • docs/s3_model_selection.md4.5 KB
  • docs/s5_execution.md5.8 KB
  • docs/s6_output_analysis.md5.3 KB
  • docs/validation_convention.yaml10.3 KB
  • knowledge_infrastructure.yaml2.4 KB
  • marrmot_timeseries.csv44.7 KB
  • preflight_check.py6.6 KB
  • run_and_score.py5.1 KB
  • run_and_score_verify1.py8 KB
  • run_and_score_verify2.py9.9 KB
  • run_and_score_verify2_bengbu.py21.8 KB
  • run_and_score_verify2_xixian.py34 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…