Installs into .claude/skills of the current project.
Are you the author of Alpine3D?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/lzwei196-alpine3d)
> **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/` (4 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` (25 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` (35 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/convert_forcing_to_smet.py` | `KISSPATH_PYTHON_ENV/bin/python {KI}/tools/convert_forcing_to_smet.py --help` |
| `tools/generate_sno_files.py` | `KISSPATH_PYTHON_ENV/bin/python {KI}/tools/generate_sno_files.py --help` |
| `tools/parse_alpine3d_output.py` | `KISSPATH_PYTHON_ENV/bin/python {KI}/tools/parse_alpine3d_output.py --help` |
| `tools/run_alpine3d.py` | `KISSPATH_PYTHON_ENV/bin/python {KI}/tools/run_alpine3d.py --help` |
*4 public tools; `_`-prefixed helpers and packaging files excluded.*
<!-- KI-TOOL-INDEX:END -->
# Alpine3D Knowledge Infrastructure
- **Package**: hydrocraft-alpine3d-snow
- **Version**: 1.0.0
- **Model**: Alpine3D (with MeteoIO + SNOWPACK)
- **Domain**: Cryosphere — spatially distributed snow, energy balance, and runoff in mountainous terrain
- **Created**: 2026-03-26
- **Last updated**: 2026-04-30
- **Tools**: 4 | **Skill Documents**: 6 | **Diagnostic Triplets**: 22 | **Validation**: T3 (3 SNOTEL sites, 5 WY each)
## ⛔ READ FIRST — the SNOTEL precipitation column is NOT cumulative (2026-08-09)
The NRCS SNOTEL daily export column
Precipitation Accumulation (in) Start of Day Values
is the **DAILY INCREMENT, in inches**, despite the word "Accumulation" in its
name. It is *not* a water-year running total. Evidence at SNOTEL 590
(2005-10 → 2015-09): the raw column sums to **1069 mm/yr**, its largest single
value in ten years is **2.10 in** (a water-year cumulative would have to exceed
40 in), and only **70%** of day-to-day steps are non-negative.
Every Alpine3D run in this KI up to 2026-08-09 applied `.diff().clip(lower=0)`
to that column (`t3_runs/build_site.py` → `build_snotel_forcing`), which keeps
only the day-over-day *increases* and discards **~44% of the precipitation**:
| site | raw column | after `.diff()` | loss |
|---|---:|---:|---:|
| 590 Lone Mountain, MT | 1069 mm/yr | 604 mm/yr | −43% |
| 828 Trial Lake, UT | 1106 | 669 | −40% |
| 637 Mores Creek Summit, ID | 1200 | 718 | −40% |
| 679 Paradise, WA | 3832 | 1740 | −55% |
**This single data bug is the −40…−60% SWE PBIAS reported at every validated
site below, and the reason NSE stalled in the 0.31–0.39 band.** The physical
explanations previously given for that bias — "the gauge ~1660 mm/yr cannot
build the observed 2.3 m pillow" at Paradise, "pillow over-read", "orographic
undercatch" — attribute a parsing bug to physics. Note that the `dt_023`
maritime remedy factor (×1.9) is close to the reciprocal of the Paradise loss
(3832/1740 = 2.2): it was numerically compensating for the differencing, not
correcting the gauge.
**Correct handling: use the column AS IS**, converting inches → mm
(`convert_forcing_to_smet.py --precip-unit in`). At a fresh continental site
with *no* precip factor at all this yields NSE ≈ 0.83 / PBIAS ≈ −10% on daily
SWE. Before re-deriving any SWE bias as a physical effect, check the forcing
total against the raw gauge.
## Resolving a SNOTEL station (coordinates, elevation, timezone)
Do not hand-type site coordinates. The NRCS AWDB REST API answers directly and
is reachable from this host:
```bash
curl -s "https://wcc.sc.egov.usda.gov/awdbRestApi/services/v1/stations?stationTriplets=590:MT:SNTL&activeOnly=false"
# -> name, latitude, longitude, elevation (FEET — multiply by 0.3048),
# dataTimeZone, beginDate/endDate
```
The station triplet is `<id>:<STATE>:SNTL`; the id is the filename stem of
`KISSPATH_OBS/snotel/<id>_daily.csv` and the state is
in that file's `# SNOTEL <id>: <Name>, <ST>` comment line.
## Validated Test Cases (Tier-3, real DEM + real obs)
Located at `KISSPATH_BINARIES/Alpine3D/t3_runs/`:
⚠️ Every row in the table below was produced with the differenced (i.e. ~44%
too dry) precipitation described above. Treat their NSE/PBIAS as a **floor**,
not as Alpine3D's skill. The 2026-08-09 Lone Mountain row is the first run with
correct precipitation.
| Site | SNOTEL | Climate | r | NSE | KGE | PBIAS | Period | precip |
|---|---:|---|---:|---:|---:|---:|---|---|
| Lone Mountain, MT | 590 | continental | see below | see below | | | 2010-10 → 2020-09 (held out) | **raw (correct)** |
| Site | SNOTEL | Climate | r | NSE | KGE | Period |
|---|---:|---|---:|---:|---:|---|
| Trial Lake, UT | 828 | continental | 0.79 | +0.31 | +0.19 | 2014-10 → 2019-09 |
| Mores Creek Summit, ID | 637 | continental-W | 0.85 | +0.39 | +0.20 | 2014-10 → 2019-09 |
| Paradise, WA (Mt Rainier) | 679 | maritime | 0.62 | −0.22 | −0.11 | 2014-10 → 2019-09 (baseline, no remedy) |
| Paradise, WA (Mt Rainier) | 679 | maritime | 0.71 | **+0.33** | +0.46 | 2006-01 → 2015-12 (dt_023 remedy, 1×1) |
Continental sites have positive skill against daily SNOTEL SWE. Maritime
sites (Paradise) fail two ways under single-station daily disaggregation:
(1) orographic precip is undercaught and the SNOTEL gauge under-reads heavy
wet snow (gauge ~1660 mm/yr cannot build the observed 2.3 m pillow), and
(2) the synthetic ±6 K diurnal TA cycle + high roughness drive excessive
turbulent melt, so the pack peaks in Jan–Mar and melts out months early
(Apr–Jun sim/obs SWE ratio ≈ 0.09). The `undercatch_wmo` filter (dt_020)
alone is far too weak. **Maritime remedy (dt_023, added 2026-06-19):** apply
a multiplicative precip undercatch factor (~1.9) via
`convert_forcing_to_smet.py --precip-undercatch-factor`, damp the diurnal TA
amplitude to ~2.5 K, lower `ROUGHNESS_LENGTH` to ~0.003 m, and raise the
`PSUM_PH` snow threshold to ~275.35 K. `build_site.py` now carries these as
per-site `precip_factor`/`diurnal_amp`/`roughness`/`snow_thresh_K` overrides
on `Paradise_WA`. Driver: `t3_runs/build_site.py` + `validate_all.py`.
**Maritime remedy VERIFIED (2026-06-19, SNOTEL 679, WY2006–2015):** the dt_023
overrides were tested on the full 2006-01-01..2015-12-31 daily SWE window and
**PASS**: NSE **+0.33**, r **0.71**, KGE **+0.46**, PBIAS −40% (n=3651 days; cold
start 2005-10-01, scored from 2006-01-01). This overturns the −0.22 baseline.
The pack still under-accumulates (sim mean 511 vs obs 853 mm; the ×1.9 factor is
a floor, not a ceiling) but peak SWE (2442 vs 2764 mm) and timing (r 0.71) are now
good. Three new `build_site.py` knobs make this run reproducible and tractable:
- **Period is now configurable** per-site via `sim_start` / `sim_end` /
`eval_start` (was hardcoded WY15-19). Cold-start the autumn *before* the eval
window; `observed_swe.csv` is filtered to `eval_start` so the spinup autumn is
excluded from metrics.
- **Deep-maritime tractability:** a 10-yr Paradise run at 11×11/15-min is
compute-bound (the correct 2+ m pillow grows the finite-element count, so
per-step cost balloons — ~5 h+ and climbing). Single-station point-SWE only
scores the POI cell, so `grid_n=1` + `calc_step=60` + `light_output=True`
(1×1 column, 1 SNOWPACK sub-step/hr, grids/prof off, daily TS) cuts it to
**~15 min** with no change to the scored center-cell SWE. The validated 11×11
config remains the reference for any *spatial* fidelity claim.
- **POI centering fix:** the POI is now written at the centre of the centre cell
(`poi_e/poi_n`), not the cell corner. A corner POI is on the domain edge and
Alpine3D aborts with `InvalidArgument: Invalid POI` — fatal on a 1×1 grid.
Harmless (+50 m, same cell) on larger grids.
- `validate_all.py` POI glob generalized from the hardcoded `5_*` index to
`*_<exp>.smet`, so it finds the POI file for any grid size (1×1 → `0_0_…`).
Note: the `build_site.py` io.ini template has **no `[SnowpackAdvanced]` section**,
so `ALPINE3D` defaults FALSE (dt_016 warning at startup) — the run is effectively
point-mode per cell. For single-station single-POI SWE validation this is benign
(and consistent with the 1×1 reduction above), but a distributed/spatial run MUST
add `[SnowpackAdvanced]\nALPINE3D = TRUE`.
---
## 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 atmospheric forcing documentation.
See `data_ki/SNOTEL/SKILL.md` for snow observations.
See `data_ki/BedMachine/SKILL.md` for ice topography.
See `data_ki/MEaSUREs/SKILL.md` for ice velocity.
## Overview
Alpine3D is a spatially distributed, three-dimensional model for analyzing and predicting
snow-dominated surface processes in mountainous topography. It couples:
- **MeteoIO** — meteorological data I/O, quality filtering, temporal/spatial interpolation
- **SNOWPACK** — detailed multi-layer snow and soil physics (energy + mass balance)
- **EnergyBalance** — terrain-aware radiation transfer (shading, view factors, terrain reflection)
- **SnowDrift** — wind-driven snow erosion, saltation, suspension, and deposition (optional)
- **Runoff** — integrated surface/soil runoff from snowmelt and rainfall
The model reads meteorological station data (SMET format), a DEM (ARC grid), land-use grids,
and initial snow profiles (.sno), then produces gridded fields of SWE, snow depth, temperature,
radiation, and runoff at configurable time intervals.
Primary applications: snow water resources, avalanche forecasting, climate change impact
assessment, permafrost studies, and ski resort management.
---
## 6. Output Description
This section restates the KI's dag facts. If this section and `dag.yaml`
disagree, `dag.yaml` wins.
**Headline output** (`validation_rank: 1`):
> `SWE` — Snow water equivalent per pixel (bulk snowpack mass). (`kg/m2`)
| Output variable (dag `var`) | Rank | Unit | Dag description / scope |
|---|---:|---|---|
| SWE | 1 | kg/m2 | Snow water equivalent per pixel (bulk snowpack mass). |
| HS | listed dag output | see `dag.yaml` | other dag output |
| TSS | listed dag output | see `dag.yaml` | other dag output |
| MS_SNOWPACK_RUNOFF | listed dag output | see `dag.yaml` | other dag output |
| MS_SOIL_RUNOFF | listed dag output | see `dag.yaml` | other dag output |
| RSNO | listed dag output | see `dag.yaml` | other dag output |
| POI energy/mass-balance terms (.met) | listed dag output | see `dag.yaml` | point time-series energy/mass-balance terms |
The model also writes gridded ARC outputs and POI `.met` time series as
described in "Output File Formats" below. For scoring and obs-binding, treat
`SWE` as the headline variable because the dag ranks it first.
---
## 8. Unit Conversion Table
Exact I/O shapes live in `docs/format_spec.yaml`; this table summarizes the
unit conversions and traps already documented in this KI body. Alpine3D uses
SI/MKSA units internally.
| Variable | Source unit / common incoming unit | Model unit | Conversion | Trap ID / note |
|---|---|---|---|---|
| Air temperature (TA) | Celsius (degC) | Kelvin (K) | +273.15 | dt_001 |
| Relative humidity (RH) | Percent (0-100) | Fraction (0-1) | divide by 100 | dt_002 |
| Wind speed (VW) | km/h | m/s | divide by 3.6 | dt_003 |
| Wind direction (DW) | radians | degrees from N (0-360) | multiply by 180/pi | dt_004 |
| Precipitation (PSUM) | m | kg/m2, equivalent to mm water | multiply by 1000 | dt_005 |
| NRCS SNOTEL precipitation | inches, daily increment | mm water | convert inches to mm with `convert_forcing_to_smet.py --precip-unit in`; do not apply `.diff()` | 2026-08-09 SNOTEL warning |
| Pressure (P) | hPa/mbar | Pa | multiply by 100 | dt_006 |
| Incoming shortwave radiation (ISWR) | MJ/m2/day | W/m2 | multiply by 11.574 | dt_007 |
| Incoming longwave radiation (ILWR) | MJ/m2/day | W/m2 | multiply by 11.574 | dt_008 |
| Snow height (HS) | cm | m | divide by 100 | dt_009 |
| Layer thickness (.sno) | cm or mm | m | divide by 100 or 1000 | dt_010 |
| Grain radius (rg in .sno) | um | mm | divide by 1000 | dt_011 |
| Soil density | g/cm3 | kg/m3 | multiply by 1000 | dt_012 |
| Geothermal heat (GEO_HEAT) | mW/m2 | W/m2 | divide by 1000 | dt_013 |
| Roughness length | mm | m | divide by 1000 | dt_014 |
| Timestep (CALCULATION_STEP_LENGTH) | seconds | minutes | divide by 60 | dt_015 |
Output units explicitly documented in this KI body:
| Output | Unit |
|---|---|
| SWE | kg/m2 |
| HS | m |
| TSS | K |
| MS_SNOWPACK_RUNOFF | kg/m2 |
| MS_SOIL_RUNOFF | kg/m2 |
| RSNO | kg/m3 |
| POI energy/mass-balance terms (.met) | see `.met` fields and `dag.yaml` |
---
## 11. Validated Results
This section restates the KI's validation facts and the convention bar. If this
section and `docs/validation_convention.yaml` disagree, the convention file
wins.
### Performance Metrics — judged against the field's bar, not intuition
`SWE` is judged by `nrmse` with direction `minimize`.
| SWE convention band | Threshold | Citation |
|---|---|---|
| very_good | no cited threshold | esmsnowmip2018 |
| good | no cited threshold | esmsnowmip2018 |
| satisfactory | nrmse <= 1.0 | esmsnowmip2018 |
No `nrmse` achieved value is stated in the validated-run body below. Existing
validated SWE rows report `r`, `NSE`, `KGE`, and `PBIAS`; do not translate those
metrics into the convention bar without a sourced `nrmse` value.
### Documented Validated SWE Runs
| Site | SNOTEL | Climate | r | NSE | KGE | PBIAS | Period | Precipitation note |
|---|---:|---|---:|---:|---:|---:|---|---|
| Lone Mountain, MT | 590 | continental | see existing table below | see existing table below | | | 2010-10 to 2020-09 (held out) | raw (correct) |
| Trial Lake, UT | 828 | continental | 0.79 | +0.31 | +0.19 | | 2014-10 to 2019-09 | differenced precipitation; floor |
| Mores Creek Summit, ID | 637 | continental-W | 0.85 | +0.39 | +0.20 | | 2014-10 to 2019-09 | differenced precipitation; floor |
| Paradise, WA (Mt Rainier) | 679 | maritime | 0.62 | -0.22 | -0.11 | | 2014-10 to 2019-09 | baseline, no remedy; differenced precipitation; floor |
| Paradise, WA (Mt Rainier) | 679 | maritime | 0.71 | +0.33 | +0.46 | -40% | 2006-01 to 2015-12 | dt_023 remedy, 1x1 |
Rows marked "differenced precipitation" were produced before the 2026-08-09
SNOTEL precipitation correction documented above. Treat their NSE/PBIAS as a
floor, not as Alpine3D's skill.
---
## Installation
### Dependencies
| Component | Minimum Version | Purpose |
|-----------|----------------|---------|
| CMake | 3.1+ | Build system |
| C++ compiler | C++11 (g++/clang++) | Compilation |
| MeteoIO | 2.5.1+ | Data I/O and interpolation |
| SNOWPACK | 3.7+ | Snow physics library |
| OpenMP (optional) | — | Shared-memory parallelism |
| MPI (optional) | — | Distributed-memory parallelism |
| Doxygen (optional) | — | Documentation generation |
### Build from Source
The source tree must have MeteoIO, SNOWPACK, and Alpine3D as siblings:
```
Source/
├── meteoio/
├── snowpack/
└── alpine3d/
```
**Build sequence** (MeteoIO → SNOWPACK → Alpine3D):
```bash
# 1. Build MeteoIO
cd Source/meteoio && mkdir build && cd build
cmake .. && make -j$(nproc) && sudo make install
# 2. Build SNOWPACK
cd ../../snowpack && mkdir build && cd build
cmake .. && make -j$(nproc) && sudo make install
# 3. Build Alpine3D
cd ../../alpine3d && mkdir build && cd build
cmake .. -DOPENMP=ON # add -DMPI=ON for cluster support
make -j$(nproc) && sudo make install
```
### Binary Location
After build: `alpine3d/build/bin/alpine3d`
After install: `/usr/local/bin/alpine3d` or the GeoForge-managed location: `KISSPATH_BINARIES/Alpine3D/source/repo/Source/alpine3d/bin/alpine3d`
### Test
```bash
cd alpine3d/tests/simple
PROG_ROOTDIR=../../build ./run_simple.sh
# Runs 3-month Dischma simulation, compares with reference output
```
---
## Pipeline Stages
| # | Stage | Tool | Description |
|---|-------|------|-------------|
| s0 | Configuration | — | Create working directory structure and io.ini |
| s1 | DEM preparation | — | Prepare ARC-format DEM and land-use grids |
| s2 | Meteorological forcing | `convert_forcing_to_smet.py` | Convert global/reanalysis data to SMET format |
| s3 | Initial snow profiles | `generate_sno_files.py` | Generate .sno files for each grid cell |
| s4 | Spatial interpolation config | — | Configure [Interpolations2D] in io.ini |
| s5 | Execution | `run_alpine3d.py` | Run alpine3d binary with proper options |
| s6 | Output parsing | `parse_alpine3d_output.py` | Extract gridded results to CSV/analysis format |
| s7 | Validation | — | Compare SWE/HS against observations |
**Parallelism notes**: Stages s2 and s3 can run in parallel. Stage s5 supports OpenMP
(shared memory) and MPI (distributed memory) parallelism for the energy balance and
snowpack computations.
---
## Tools Reference
| Tool | Stage | Script | Lines | Purpose |
|------|-------|--------|-------|---------|
| convert_forcing_to_smet | s2 | `tools/convert_forcing_to_smet.py` | ~430 | Convert meteo data to SMET format with unit corrections |
| generate_sno_files | s3 | `tools/generate_sno_files.py` | ~250 | Create initial snow/soil profiles per pixel or per land-use class |
| run_alpine3d | s5 | `tools/run_alpine3d.py` | ~220 | Execution wrapper with config validation |
| parse_alpine3d_output | s6 | `tools/parse_alpine3d_output.py` | ~260 | Parse ARC grid outputs to CSV time series |
Options added 2026-08-09 (a full SNOTEL site can now be built with the KI tools
alone; `t3_runs/build_site.py` is no longer required):
| Tool | Option | Purpose |
|---|---|---|
| convert_forcing_to_smet | `--precip-unit in` | inches → mm (NRCS SNOTEL, NWS COOP) |
| convert_forcing_to_smet | `--daily-disaggregate` | daily records → 24 hourly SMET rows (Alpine3D cannot run on daily forcing) |
| convert_forcing_to_smet | `--diurnal-amp` | half-amplitude of the synthetic diurnal TA cycle; 6 K continental, 2.5 K maritime (dt_023) |
| convert_forcing_to_smet | `--precip-undercatch-factor` | multiplicative PSUM correction (dt_020/dt_023) — **re-derive it after dt_024; a value >1.5 on SNOTEL forcing means the differencing bug is still present** |
| generate_sno_files | `--experiment` / `--naming` / `--landuse-code` | emit the .sno file name Alpine3D actually searches for (dt_025) |
| generate_sno_files | `--epsg` | write `easting`/`northing`/`epsg` for a projected DEM instead of bogus lat/lon |
Reference end-to-end driver using only these tools:
`KISSPATH_KI_ROOT/Alpine3D/run_and_score.py`
(SNOTEL 590 Lone Mountain, MT; resumable at every stage).
---
## Unit Trap Table
**CRITICAL**: Alpine3D uses SI/MKSA units internally. The most common cause of silent model
failure is feeding data in wrong units. The table below documents every conversion trap:
| Variable | Alpine3D Unit | Common Source Unit | Conversion | Trap ID |
|----------|--------------|-------------------|------------|---------|
| Air temperature (TA) | Kelvin (K) | Celsius (°C) | +273.15 | dt_001 |
| Relative humidity (RH) | Fraction (0–1) | Percent (0–100) | ÷100 | dt_002 |
| Wind speed (VW) | m/s | km/h | ÷3.6 | dt_003 |
| Wind direction (DW) | Degrees from N (0–360) | Radians | ×(180/π) | dt_004 |
| Precipitation (PSUM) | kg/m² (mm water equiv.) | m | ×1000 | dt_005 |
| Pressure (P) | Pa | hPa/mbar | ×100 | dt_006 |
| Incoming SW radiation (ISWR) | W/m² | MJ/m²/day | ×11.574 (÷86400×1e6) | dt_007 |
| Incoming LW radiation (ILWR) | W/m² | MJ/m²/day | ×11.574 | dt_008 |
| Snow height (HS) | m | cm | ÷100 | dt_009 |
| Layer thickness (.sno) | m | cm or mm | ÷100 or ÷1000 | dt_010 |
| Grain radius (rg in .sno) | mm | µm | ÷1000 | dt_011 |
| Soil density | kg/m³ | g/cm³ | ×1000 | dt_012 |
| Geothermal heat (GEO_HEAT) | W/m² | mW/m² | ÷1000 | dt_013 |
| Roughness length | m | mm | ÷1000 | dt_014 |
| Timestep (CALCULATION_STEP_LENGTH) | minutes | seconds | ÷60 | dt_015 |
---
## Configuration Reference (io.ini)
### [General]
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| BUFFER_SIZE | int | 370 | Data buffer size in days |
| BUFF_BEFORE | float | 1.5 | Buffer before start date (days) |
### [Input]
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| COORDSYS | string | — | Coordinate system (CH1903, UTM, etc.) |
| TIME_ZONE | float | 0 | Time zone offset from UTC |
| METEO | string | SMET | Meteorological input format |
| METEOPATH | path | — | Path to meteo files |
| STATION1..N | string | — | Station identifiers |
| SNOW | string | SMET | Snow profile format |
| SNOWPATH | path | — | Path to initial .sno files |
| DEM | string | ARC | DEM format |
| DEMFILE | path | — | Path to DEM grid |
| LANDUSE | string | ARC | Land use format |
| LANDUSEFILE | path | — | Path to land use grid |
| POI | string | SMET | Points of interest format |
| POIFILE | path | — | Path to POI file |
### [Output]
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| GRIDS_WRITE | bool | FALSE | Write gridded output |
| GRIDS_DAYS_BETWEEN | float | 1.0 | Grid output interval (days) |
| GRIDS_PARAMETERS | string | — | Space-separated list (HS SWE TA ISWR...) |
| GRID2D | string | ARC | Grid output format |
| GRID2DPATH | path | — | Grid output directory |
| TS_WRITE | bool | FALSE | Write time series at POIs |
| TS_DAYS_BETWEEN | float | 1.0 | Time series interval (days) |
| PROF_WRITE | bool | FALSE | Write snow profiles at POIs |
| PROF_DAYS_BETWEEN | float | 1.0 | Profile interval (days) |
| SNOW_WRITE | bool | FALSE | Write full snow state (for restart) |
| SNOW_DAYS_BETWEEN | float | — | Snow state interval (days) |
### [Snowpack]
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| CALCULATION_STEP_LENGTH | float | 15 | Timestep in minutes |
| ROUGHNESS_LENGTH | float | 0.002 | Surface roughness (m) |
| HEIGHT_OF_METEO_VALUES | float | 2.0 | Measurement height (m) |
| HEIGHT_OF_WIND_VALUE | float | 10.0 | Wind measurement height (m) |
| ATMOSPHERIC_STABILITY | string | MO_MICHLMAYR | Stability scheme |
| CANOPY | bool | FALSE | Enable canopy model |
| SNP_SOIL | bool | FALSE | Enable soil model |
| SOIL_FLUX | bool | TRUE | Enable soil heat flux |
| GEO_HEAT | float | 0.06 | Geothermal heat flux (W/m²) |
| SW_MODE | string | INCOMING | Shortwave mode (INCOMING/BOTH/REFLECTED) |
### [SnowpackAdvanced]
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| ALPINE3D | bool | TRUE | **MUST be TRUE for Alpine3D** |
| SNOW_EROSION | bool | TRUE | Enable wind erosion |
| WATERTRANSPORTMODEL_SNOW | string | BUCKET | Water transport model |
| THRESH_RAIN | float | 1.2 | Rain/snow threshold (K above freezing) |
| LB_COND_WATERFLUX | string | FREEDRAINAGE | Lower boundary condition |
### [EBalance]
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| TERRAIN_RADIATION | bool | TRUE | Enable terrain radiation effects |
| TERRAIN_RADIATION_METHOD | string | SIMPLE | Algorithm (SIMPLE/COMPLEX/HELBIG) |
### [Interpolations2D] — Spatial Interpolation
Algorithms per variable: `VAR::algorithms = ALG1 ALG2 ...`
| Variable | Recommended Algorithms | Notes |
|----------|----------------------|-------|
| TA | ODKRIG_LAPSE, IDW_LAPSE, AVG_LAPSE | Lapse rate ~-0.006 K/m |
| RH | LISTON_RH, IDW_LAPSE | Liston preserves dewpoint |
| PSUM | IDW_LAPSE, AVG_LAPSE | Fractional lapse ~0.0005/m |
| PSUM_PH | PPHASE | Threshold-based phase separation |
| VW, DW | LISTON_WIND | Terrain-adjusted wind |
| ISWR | IDW, AVG | Direct interpolation |
| ILWR | AVG_LAPSE | Rate ~-0.03125 W/m²/m |
| P | STD_PRESS | Barometric formula |
### [Filters] — Input QC
Pattern: `VAR::filterN = TYPE` + `VAR::argN::param = value`
Common filters: `min_max`, `rate`, `mad`, `undercatch_wmo`
### [Generators] — Gap Filling
Pattern: `VAR::generatorN = TYPE`
Common generators: `ALLSKY_LW`, `CLEARSKY_LW`, `ISWR_ALBEDO`
---
## Command-Line Options
```
alpine3d [options]
Required:
-a, --startdate=YYYY-MM-DDTHH:MM Simulation start date
-z, --enddate=YYYY-MM-DDTHH:MM Simulation end date (or use --steps)
-i, --iofile=<file> Configuration file (default: io.ini)
Optional:
-n, --steps=<N> Number of timesteps (alternative to --enddate)
-b, --np-ebalance=<N> Workers for energy balance (default: 1)
-p, --np-snowpack=<N> Workers for snowpack (default: 1)
--enable-eb Enable energy balance module
--restart Restart from existing .sno files
-h, --help Display help
```
---
## Input File Formats
### SMET (Swiss Meteorological Exchange Text) — Meteo Data
```
SMET 1.1 ASCII
[HEADER]
station_id = WFJ2
station_name = Weissfluhjoch
latitude = 46.829897
longitude = 9.809315
altitude = 2540.0
nodata = -999
tz = 1
fields = timestamp TA RH VW DW ISWR ILWR PSUM HS
[DATA]
2014-10-01T00:00 277.25 0.85 3.2 270 0.0 285.0 0.0 0.0
2014-10-01T01:00 276.80 0.87 2.8 265 0.0 282.0 0.5 0.001
```
### SNO (Initial Snow/Soil Profile)
**The layer columns are parsed POSITIONALLY** by
`snowpack/plugins/SmetIO.cc::readSnowCover` (~L417–457), so the `fields` line
must contain exactly these 19 tokens, in this order — including `ne`, the
number of finite elements per layer (use 1). Omitting `ne` shifts every later
column one position left, so `CDot` is read as the element count and the
profile initialises with zero elements.
Header key names are looked up literally: it is `SoilAlbedo` (not `SoilAlb`)
and `CanopyLeafAreaIndex` (not `CanopyLAI`).
Layers are read **BOTTOM → TOP**: the first `[DATA]` row is the deepest soil
layer. Put the thick layers first and the thin near-surface layers last, and
make temperature decrease from the first row to the last.
For a projected DEM, use `easting` / `northing` / `epsg` — SMET accepts that
pair as an alternative to `latitude`/`longitude`. Writing projected metres into
`latitude`/`longitude` makes MeteoIO reject the profile.
```
SMET 1.1 ASCII
[HEADER]
station_id = 0_0_my_experiment
easting = 466550.00
northing = 5013550.00
epsg = 32612
altitude = 2500
ProfileDate = 2014-10-01T00:00
nSnowLayerData = 0
nSoilLayerData = 3
SoilAlbedo = 0.2
BareSoil_z0 = 0.02
CanopyHeight = 0.00
CanopyLeafAreaIndex = 0.00
CanopyDirectThroughfall = 1.00
WindScalingFactor = 1.00
ErosionLevel = 0
TimeCountDeltaHS = 0.000000
fields = timestamp Layer_Thick T Vol_Frac_I Vol_Frac_W Vol_Frac_V Vol_Frac_S Rho_S Conduc_S HeatCapac_S rg rb dd sp mk mass_hoar ne CDot metamo
[DATA]
2014-10-01T00:00 1.0 282.15 0.0 0.15 0.35 0.50 1800 1.5 900 0.3 0.2 0.0 1.0 0 0.0 1 0.0 0.0
2014-10-01T00:00 0.5 281.15 0.0 0.20 0.30 0.50 1800 1.5 900 0.3 0.2 0.0 1.0 0 0.0 1 0.0 0.0
2014-10-01T00:00 0.5 280.15 0.0 0.25 0.25 0.50 1800 1.5 900 0.3 0.2 0.0 1.0 0 0.0 1 0.0 0.0
```
`generate_sno_files.py` emits all of the above correctly as of 2026-08-09; use
`--naming landuse --experiment <EXPERIMENT> --landuse-code <code> --epsg <code>`
for a single per-class profile, or `--naming pixel` for one file per cell.
### ARC Grid (DEM, Land Use)
Standard ESRI ARC/INFO ASCII grid:
```
ncols 100
nrows 80
xllcorner 782000
yllcorner 180000
cellsize 25
NODATA_value -9999
1500 1502 1510 ...
```
### POI (Points of Interest)
```
SMET 1.1 ASCII
[HEADER]
fields = easting northing altitude
[DATA]
785360 182255 2520
786100 181900 2350
```
---
## Output File Formats
### Gridded Output (ARC ASCII)
Written to `GRID2DPATH` at `GRIDS_DAYS_BETWEEN` intervals.
Filename pattern: **`YYYY-MM-DDThh.mm.ss_PARAM.asc`**
(e.g. `2014-12-23T12.00.00_SWE.asc`) — MeteoIO's ARC2D plugin writes ISO-8601
dates with `.` substituted for the colons, which are illegal in many
filesystems. The compact `YYYYMMDDHHMI_PARAM.asc` form documented here before
2026-06-22 does **not** occur for SWE/HS/RSNO grids; a glob built on it matches
nothing. (`parse_alpine3d_output.py` accepts both.) Note that some
Alpine3D-internal grids — e.g. `TSOIL1` — *are* written with the compact stamp,
so a directory listing can show both conventions side by side.
Grid `NODATA_value` is **−999**, not −9999.
Key output parameters:
- **HS** — Snow depth (m)
- **SWE** — Snow water equivalent (kg/m²)
- **TA** — Air temperature (K)
- **TSS** — Snow surface temperature (K)
- **ISWR** — Incoming shortwave radiation (W/m²)
- **ILWR** — Incoming longwave radiation (W/m²)
- **MS_SNOWPACK_RUNOFF** — Surface runoff (kg/m²)
- **MS_SOIL_RUNOFF** — Bottom runoff (kg/m²)
- **RSNO** — Snow mean density (kg/m³)
- **ET** — Evapotranspiration (kg/m²)
### Time Series (.met SMET)
At each POI, written at `TS_DAYS_BETWEEN` intervals.
Contains full energy/mass balance terms.
### Profiles (.pro)
Full snow stratigraphy at POI locations.
---
## Critical Domain Knowledge
### 1. ALPINE3D = TRUE is mandatory (dt_016)
In [SnowpackAdvanced], `ALPINE3D` must be set to `TRUE`. If left at `FALSE` (Snowpack standalone
default), the model will run but produce incorrect spatial results because the Snowpack library
will not operate in distributed mode.
### 2. Temperature must be in Kelvin (dt_001)
All temperatures in SMET files must be in Kelvin. Feeding Celsius values creates temperatures
near 0 K internally, causing immediate crashes or physically impossible surface energy balance.
### 3. Precipitation accumulation period must match timestep (dt_015)
`PSUM::RESAMPLE1 = ACCUMULATE` with `PSUM::ARG1::PERIOD` must match `CALCULATION_STEP_LENGTH`.
Mismatches cause either double-counting or loss of precipitation.
### 4. DEM and land-use grids must be co-registered (dt_017)
The DEM and land-use grids must have identical ncols, nrows, xllcorner, yllcorner, and cellsize.
Mismatch causes a cryptic segmentation fault.
### 5. Coordinate system must be consistent (dt_018)
`COORDSYS` in [Input] and [Output] must be the same and must match the actual coordinate system
of the DEM file. Using WGS84 lat/lon with a projected DEM (or vice versa) causes all spatial
interpolation to fail silently.
### 6. SMET timestamps must be ISO 8601
Format: `YYYY-MM-DDTHH:MM` or `YYYY-MM-DDTHH:MM:SS`. Any deviation (e.g., spaces instead of T)
causes MeteoIO to fail to read the file.
### 7. Snow file naming convention (dt_024)
Alpine3D constructs the name it searches for in `SnowpackInterface.cc`
(~L1258–1260) and reads whichever exists, per pixel:
```
per-pixel (tried first) <ix>_<iy>_<EXPERIMENT>.sno e.g. 0_0_lone_mountain_mt.sno
per-class (fallback) <EXPERIMENT>_<landuse_code>.sno e.g. lone_mountain_mt_10851.sno
```
- `EXPERIMENT` is `[Output]::EXPERIMENT` from io.ini — **not** the station id.
- `ix` is the **column**, `iy` the **row counted from the BOTTOM** (MeteoIO
grids are south-up), so a 1×1 domain wants `0_0_<EXPERIMENT>.sno`.
- `landuse_code` is the rounded value in the land-use grid (e.g. PREVAH 10851).
Any other name means Alpine3D finds no initial profile and aborts. Use
`generate_sno_files.py --naming {pixel,landuse} --experiment <EXPERIMENT>`.
### 8. Energy balance workers vs grid size
Setting `--np-ebalance` higher than the number of grid rows causes a crash. The number of
workers must be ≤ number of DEM rows.
### 9. Relative humidity range trap (dt_002)
RH in SMET must be 0–1 (fraction). Values 0–100 (percent) will be clipped to 1.0 by the
min_max filter, making the entire domain saturated and producing unrealistic snowfall.
---
## Calibration Parameters
| Parameter | Section | Range | Controls | Sensitivity |
|-----------|---------|-------|----------|-------------|
| ROUGHNESS_LENGTH | Snowpack | 0.001–0.01 m | Turbulent exchange | High |
| GEO_HEAT | Snowpack | 0.0–0.1 W/m² | Basal melt rate | Medium |
| THRESH_RAIN | SnowpackAdvanced | 0.5–2.5 K | Rain/snow partitioning | High |
| HEIGHT_OF_METEO_VALUES | Snowpack | 1.5–10 m | Turbulent fluxes | Medium |
| ATMOSPHERIC_STABILITY | Snowpack | — | Stability correction | High |
| TA lapse rate | Interpolations2D | -0.003 to -0.009 K/m | Temperature field | High |
| PSUM lapse rate | Interpolations2D | 0.0001–0.001 /m | Precipitation gradient | High |
| TERRAIN_RADIATION_METHOD | EBalance | SIMPLE/COMPLEX/HELBIG | Radiation accuracy | Medium |
---
## Quick Start
```bash
# 1. Build Alpine3D (from source directory)
cd Source/alpine3d && mkdir build && cd build
cmake .. -DOPENMP=ON && make -j$(nproc)
# 2. Create simulation directory
mkdir -p ~/sim/Dischma/{input/{meteo,surface-grids,snowfiles},output/{grids,snowfiles},setup}
# 3. Prepare forcing data
python tools/convert_forcing_to_smet.py \
--input era5_data.nc --output ~/sim/Dischma/input/meteo/ \
--stations WFJ2 DAV --start 2014-10-01 --end 2015-09-30
# 4. Generate initial snow profiles
python tools/generate_sno_files.py \
--dem ~/sim/Dischma/input/surface-grids/dem.asc \
--output ~/sim/Dischma/input/snowfiles/ \
--date 2014-10-01T00:00 --n-soil-layers 3
# 5. Run simulation
python tools/run_alpine3d.py \
--iofile ~/sim/Dischma/setup/io.ini \
--startdate 2014-10-01T01:00 --enddate 2015-09-30T00:00 \
--np-ebalance 4 --np-snowpack 4
# 6. Parse output
python tools/parse_alpine3d_output.py \
--grid-dir ~/sim/Dischma/output/grids/ \
--params SWE HS TA --output ~/sim/Dischma/results.csv
```
---
## Diagnostic Triplets Summary
| ID | Severity | Domain | Summary |
|----|----------|--------|---------|
| dt_001 | silent | unit_conversion | Temperature in °C instead of K |
| dt_002 | silent | unit_conversion | RH in % instead of fraction |
| dt_003 | silent | unit_conversion | Wind speed in km/h instead of m/s |
| dt_004 | silent | unit_conversion | Wind direction in radians |
| dt_005 | silent | unit_conversion | Precipitation in m instead of mm |
| dt_006 | silent | unit_conversion | Pressure in hPa instead of Pa |
| dt_007 | silent | unit_conversion | Radiation in MJ/m²/day instead of W/m² |
| dt_008 | silent | unit_conversion | ILWR in MJ/m²/day instead of W/m² |
| dt_009 | silent | unit_conversion | Snow height in cm instead of m |
| dt_010 | silent | unit_conversion | Layer thickness wrong units in .sno |
| dt_011 | silent | unit_conversion | Grain radius in µm instead of mm |
| dt_012 | silent | unit_conversion | Soil density in g/cm³ instead of kg/m³ |
| dt_013 | silent | unit_conversion | Geothermal heat in mW/m² |
| dt_014 | silent | unit_conversion | Roughness length in mm instead of m |
| dt_015 | degraded | parameter_format | Timestep/accumulation mismatch |
| dt_016 | silent | parameter_format | ALPINE3D = FALSE in SnowpackAdvanced |
| dt_017 | fatal | parameter_format | DEM/landuse grid mismatch |
| dt_018 | silent | parameter_format | Coordinate system mismatch |
---
## File Structure
```
ki/
├── SKILL.md # This file
├── tools/
│ ├── convert_forcing_to_smet.py # Meteo data → SMET format
│ ├── generate_sno_files.py # Initial snow/soil profiles
│ ├── run_alpine3d.py # Execution wrapper
│ └── parse_alpine3d_output.py # Grid output → CSV
├── docs/
│ ├── s0_domain_setup.md # Domain and DEM preparation
│ ├── s2_meteorological_forcing.md # Forcing data preparation
│ ├── s3_initial_conditions.md # Initial snow/soil profiles
│ ├── s5_execution.md # Running Alpine3D
│ └── s6_output_analysis.md # Output parsing and analysis
└── diagnostics/
└── triplets.yaml # 18 symptom→diagnosis→remedy entries
```