Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ increment that most needs a picture to check against
| 27 | The sampler reads a vertex on a DEM node from that node: `_core.sample` refused a cell with any NoData corner even at zero weight, so on the path without `--tolerance` a valid node next to a NoData node lost its height (increment 12's accepted one-cell trim, `12-dem-to-mesh.md` R2). Found again by increment 25's green step. A point that is a node bit for bit (`RasterGeometry::node_at`) now reads `value_at`; every other point keeps the four-corner rule; the `--tolerance` path is unchanged | **merged (#160, 2026-10-04)** (designed and approved 2026-10-03; Ola ruled nodes only, the rule in the C++ sampler; red `6605dfe`, pins `80990e0`, green `6fd4076`/`a0817ba`; 13 net production lines; no `@perf` run needed) | `docs/increments/27-node-sampling.md` |
| 23 | Basin scale: the domain cut into pieces along lattice lines, Nx × Ny ≈ `--pieces` from the domain's bounding box, cut only when the estimated memory at the tolerance exceeds `--memory-budget` (16 GB; BHO dropped as geometry; sub-catchments later from the DEM, never seams), each seam given its heights by a one-dimensional seam pass and then frozen, so pieces refine independently and in parallel; windowed DEM reads with no dense canvas; `rasputin fetch`, a resumable block cache of remote COGs (ANADEM, GLO-30), so meshing never touches the network; pieces plus an index, stitched into one file by default, with the seams removed and locally thinned. Absorbs 15d's window decoding; supersedes Q9's dense canvas at basin scale | **designed and ruled** (`@architect`, 2026-10-01; Ola ruled B1-B14 the same day, B15 (the refusals at half of physical memory: delete both) and B16 (licence notes in the mesh file) on 2026-10-02; 23a-1 merged as #136, 23a-2 merged as #138; 23b (frozen edges and the seam pass) merged as #162 (merge commit 4f56551), green 3c464ec with N18 in, 249 net lines, `@perf`'s acceptance recorded at 91c7cb5 and again at the merged head 4541e38, ACCEPTED; 23c split in two, 23c-1 (the partition rule, the piece vocabulary, the index model, a start mesh from arrays) built at 156 lines and merged as #164 (merge commit e56a2bf), 23c-2 (pieces, the run, the CLI) in progress on `worktree-23c`, not pushed, its state in that branch's copy of `23-basin-scale.md`): eight PRs, 23a-1, 23a-2, 23b, 23c-1, 23c-2, 23d, 23f, 23g (about 225, 373, 285, 215, 265, 235, 300 and 345 lines estimated); measured for it: the whole basin is 3,061 ANADEM blocks, 1.71 GiB, cut into 23 pieces at 1 m at the default budget | `docs/increments/23-basin-scale.md` |
| 24 | Release hardening, switched on: the bounds-checking standard library (libc++ FAST, libstdc++ assertions) on by default in every build, with a build option to turn it off for heavy runs (Ola, 2026-10-03: "So normal release, turn it on. For real heavy lifting, option to turn it off."); the loaded mode reported by `_core.hardening`, `rasputin version` and `--stats`; a guard test that fails when a toolchain ignores the macro; `bench.py --hardening on|off`, with hardened the baseline from here on | **designed and ruled** (`@architect`, 2026-10-03; Ola ruled Q1-Q4 the same day): the CMake option `RASPUTIN_HARDENING`, default ON, off by rebuilding into a second venv; an Ubuntu CI leg builds it OFF; one PR; **implemented on `worktree-agent-a08216a68d5534b18`** (red `21d5168`, green `23d4dad`, 53 net production lines; T6 reworked `57e1264`); `@reviewer` APPROVED (round 3); **`@perf` acceptance ACCEPTED** (2026-10-03, AC: built OFF it matches master `6cdc8cc`, meshes byte-identical; hardening costs refine +4.2 to +5.9 % at 1 thread and +3.2 to +11.1 % over 2 to 20 threads; first hardened baseline `docs/benchmarks/2026-10-03/24-on-r2/`, evidence `docs/benchmarks/2026-10-03/24-acceptance.md`). Two extensions in one wheel (option (b)) rejected. Measurement: `docs/benchmarks/2026-10-03/release-hardening/README.md` (#149) | `docs/increments/24-release-hardening.md` |
| 26 | Land cover for the São Francisco basin, MapBiomas (the land-cover half of "the basin's own inputs"): water bodies of at least a minimum area traced on cell edges and reduced with their area kept, as constraints (`rasputin water`); every other class as fractions per triangle from exact cell areas, small entries (under 5 % of the triangle and under 1 ha) dropped and their area carried along a Hilbert curve to the next triangles holding the class (Ola's "local out-of-balance ledger"); `rasputin fetch mapbiomas-c11 --year` | **designed and ruled** (`@architect`, 2026-10-03; Ola ruled all 11 questions on 2026-10-04: Collection 11, year 2025 by default; CC BY-SA 4.0 on the outputs; triangles inside a lake carry the raster's own mix, nothing forced to water; 26a-26c now alongside the basin work, the water PRs after the basin PRs, 26e with 23g). 26a ready for `@tester`. Measured for it on the Rio Corrente meshes at 20 to 2 m: renormalising inside triangles loses 8 to 45 % of the water, the ledger keeps every class within 0.05 %. Six PRs, 26a to 26e (about 170, 420, 300, 425, 355 and 210 lines) | `docs/increments/26-land-cover.md` |
| 26 | Land cover for the São Francisco basin, MapBiomas (the land-cover half of "the basin's own inputs"): water bodies of at least a minimum area traced on cell edges and reduced with their area kept, as constraints (`rasputin water`); every other class as fractions per triangle from exact cell areas, small entries (under 5 % of the triangle and under 1 ha) dropped and their area carried along a Hilbert curve to the next triangles holding the class (Ola's "local out-of-balance ledger"); `rasputin fetch mapbiomas-c11 --year` | **26a built** (branch `worktree-mapbiomas-26a`: red `cb5f254b`, green `8eec354b`, 163 net production lines; `@perf`'s quick check `38f86930` NO CHANGE, every mesh byte-identical; code review APPROVED in two rounds; next the push on Ola's yes); 26b onward designed and ruled (`@architect`, 2026-10-03; Ola ruled all 11 questions on 2026-10-04: Collection 11, year 2025 by default; CC BY-SA 4.0 on the outputs; triangles inside a lake carry the raster's own mix, nothing forced to water; 26a-26c now alongside the basin work, the water PRs after the basin PRs, 26e with 23g). Measured for it on the Rio Corrente meshes at 20 to 2 m: renormalising inside triangles loses 8 to 45 % of the water, the ledger keeps every class within 0.05 %. Six PRs, 26a to 26e (about 170, 420, 300, 425, 355 and 210 lines) | `docs/increments/26-land-cover.md` |
| 29 | NVE reference catchments: a catchment from the DEM for each of the 140 gauging stations in NVE's reference list of active, unregulated stations (Ola, 2026-10-04: "NVE should have a list of unregulated catchments, where they also record the discharge"), each compared with NVE's own catchment polygon (share of NVE's polygon in ours and of ours in NVE's, area ratio, mean distance between the divides). Adds flow accumulation from increment 22's flood and places each gauge where it physically is: on NVE's mapped river line, then on the DEM's flow path along it (burnt where they disagree), never by an area or flow-count objective (Ola's direction, 2026-10-04); a per-station sensitivity of the area along the river marks ill-posed stations `uncertain`. `rasputin fetch-stations nve-hrd` fetches the stations, rivers and NVE's polygons (NLOD), `rasputin station-catchments` runs them all. Residual inflow between gauges is later; the interfaces leave room for it. Discharge out of scope; station and series numbers kept for a later join. 138 of the 140 polygons lie inside DTM10's data; two cross into Finland and are expected to be refused | **PR 1, 2, 3 and 4 merged (#173, #179, #178, #182); the acceptance run over all 140 stations is in, at `docs/benchmarks/2026-10-05/nve-hrd/` (as fixed in `cd8b5be`): 74 match, 5 close, 6 miss, 39 uncertain, 16 refused (14 of them expected), 33 minutes and at most 7.7 GB; evidence review round 2 approved, the push waits for Ola; PR 5 (the nearest-stream fallback) and acceptance step 5 (the comparison with nearest stream) not started; questions 6 and 10 go back to Ola with the run's figures** | `docs/increments/29-nve-reference-catchments.md` |
| 30 | The bottlenecks of `rasputin mesh` on real catchments, in three pull requests, with every output byte-identical: 30a the `land cover` phase (point-in-polygon against prepared polygons, one sort fewer, no sort to pick each area's point), 30b the CORINE clip (point tests before the segment test, a feature whose lines miss the search region skipped whole, no clip of a line lying in the domain's interior), 30c reading the DEM (decode threads from the machine's cores instead of a fixed 4, the seam report's test against the domain's region run only on the nodes it can count). Measured by `@perf` on Numedalslågen and Skiensvassdraget (14.4 s and 21.2 s on battery; `docs/benchmarks/2026-10-06/bottlenecks/README.md`), projected to about 7.8 s and 11.7 s with all three. Approved by Ola 2026-10-06, in that order ("yes, go ahead in that order") | **30a merged (#199)** (`worktree-landcover-speed`: red `6cde0eb`, green `3066d60`, +12 net production lines; `@perf` ACCEPTED on AC power, median of 3: land cover 2.85 → 0.67 s on Numedalslågen and 6.54 → 1.74 s on Skiensvassdraget, whole run 13.66 → 11.41 s and 20.46 → 15.68 s, every `.vtk` byte-identical to the base; `docs/benchmarks/2026-10-06/30a-landcover/README.md`); **30b merged (#200)** (`worktree-clip-speed`: red `08269e77`, green `c5340459`, +23 net production lines; `@perf` ACCEPTED on AC power, median of 3: `features clip` 5.83 → 1.83 s on Numedalslågen and 5.66 → 1.35 s on Skiensvassdraget, whole run 11.39 → 7.52 s and 15.69 → 11.41 s, every `.vtk` byte-identical to the base and every base line of the probe unchanged; `docs/benchmarks/2026-10-06/30b-clip/README.md`); **30c approved by code review round 2; waits for Ola's yes to push** (`worktree-dem-read-speed`, 2026-10-06; Ola said "yes, build 30c"; red `bdf7b57d`, green `0663efc9`, +3 net production lines; `@perf` ACCEPTED on AC power, median of 3: `decode` 1.788 → 1.051 s on Numedalslågen and 1.323 → 0.842 s on Skiensvassdraget, whole run 7.48 → 6.72 s and 11.46 → 10.97 s, every `.vtk` byte-identical to the base and every base line of the probe unchanged; `docs/benchmarks/2026-10-06/30c-dem-read/README.md`; master merged in as `26a5d839`, where every base line of the probe is unchanged and the 16 new lines are all from the red suite) | `docs/increments/30a-landcover-speed.md`, `docs/increments/30b-clip-speed.md`, `docs/increments/30c-dem-read-speed.md` |
| 30d | Growing the catchment outline, made faster, with every mesh byte-identical: the same mitred buffer of the domain computed once instead of twice on the reprojected path; the CORINE search region grown from the domain's convex hull instead of taking the hull of the grown domain; and a long staircase outline (SMHI's raster-traced ones) grown in pieces of 1,000 edges and united, behind a gate that keeps smooth outlines on GEOS's single buffer. Measured by `@perf` on Lagan with the European CORINE GeoPackage: 103.4 s on master, about 17 s with all three (`docs/benchmarks/2026-10-07/30d-buffer/README.md`); from the performance review's finding F1 (PR #208). Ola, 2026-10-07: grow the outline in pieces; the gate is an open question, default yes | **merged (#215, 2026-10-07)** (branch `worktree-buffer-speed-2`; 29 net production lines; Python only, no refine or mesh code; Lagan 103.38 s → 16.97 s, meshes byte-identical; push waits on Ola) | `docs/increments/30d-outline-buffer-speed.md` |
Expand Down
86 changes: 86 additions & 0 deletions docs/benchmarks/2026-10-11/mapbiomas-26a/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Increment 26a quick check: MapBiomas in the catalogue, and class windows (@perf, 2026-10-11)

Branch `worktree-mapbiomas-26a`, head `f51f0a18`, base master `de9b6c03`
(the merge of increment 35). The rule: `docs/increments/perf-quick-check.md`,
section 3, the quick check only. 26a touches no refinement or mesh code, so
no `tools/bench.py` 1 m run and no thread sweep. It does touch the DEM
decode path: `8eec354b` moved the block-decoding loop of `decode_window`
(`src_python/tin_engine/io/cog.py`) into a shared `_decode_into`, which the
new `decode_class_window` also calls; `decode` is the largest phase of the
`geilo-al-ramp` case. So the check is: every case's mesh byte-identical to
the baseline's, every total within its band, and `decode` within its band
where the tool judges it (a phase at 5 % or more of the case's total).

## Verdict: ACCEPTED

`tools/bench_quick.py run` at `f51f0a18`: **`NO CHANGE`, exit 0**
(`run.log`). The tool prints a `MESH CHANGED:` line for any case whose mesh
SHA-256 differs from the baseline's, a `BROKEN:` line for a `max_error` over
the tolerance, and a `SLOWER:`/`FASTER:` line for any measure outside its
band (the case's own spread, at least 5 %, and more than 0.05 s), on the
total and on every phase at 5 % or more of the total; `decode` is judged on
every case. It printed none of them, so:

| gate | result |
|---|---|
| mesh SHA-256 unchanged, all 7 cases | met (no `MESH CHANGED` line) |
| `max_error` within tolerance, all 7 cases | met (no `BROKEN` line) |
| every case's total within its band | met (no `SLOWER`/`FASTER` line) |
| `decode` within its band, `geilo-al-ramp` and the tile cases | met (no `SLOWER`/`FASTER` line) |

Baseline: `docs/benchmarks/quick/baseline-ac.json`, commit `357808b1`, AC
power, Apple M1 Max (8 P + 2 E cores, 32 GB), macOS 27.0, Python 3.14.7,
numpy 2.5.4, shapely 2.2.0 / GEOS 3.14.1, pyproj 3.8.0 / PROJ 9.8.1; saved
by increment 35's acceptance (#229). Its per-case medians are the "head"
column of `docs/benchmarks/2026-10-11/cleanup-speed/README.md`: tile 0.416 s,
tile t=1 0.699 s, quarter 0.355 s, numedalslagen 8.829 s, lagan 14.542 s,
geilo-al-ramp 0.329 s (decode 0.138 s), romsdal-slope 3.578 s. The tool
compared this run's medians with those and found each inside its band; the
run's own medians are not on disk, see "Limits" below.

## Hotspots, as printed (`docs/benchmarks/quick/hotspots.toml` rules only Lagan `features clip`)

```
HOTSPOT: tile refine 44 % of the run (not in hotspots.toml)
HOTSPOT: tile t=1 refine 67 % of the run (not in hotspots.toml)
HOTSPOT: tile t=1 refine: scan (parallel) 50 % of the run (not in hotspots.toml)
HOTSPOT: quarter refine 47 % of the run (not in hotspots.toml)
HOTSPOT: numedalslagen features clip 68 % of the run (not in hotspots.toml)
HOTSPOT: numedalslagen features clip: clean-up 67 % of the run (not in hotspots.toml)
HOTSPOT: geilo-al-ramp decode 42 % of the run (not in hotspots.toml)
HOTSPOT: romsdal-slope refine 56 % of the run (not in hotspots.toml)
```

The same eight lines as the baseline run printed
(`docs/benchmarks/2026-10-11/cleanup-speed/quick/head-run.log`), six at the
same share; tile `refine` 46 -> 44 % and quarter `refine` 48 -> 47 % (both
within those cases' run-to-run noise on a 0.4 s case). geilo-al-ramp `decode`
is 42 % on both, 0.138 of 0.329 s on the baseline. None is new to 26a. Lagan
`features clip` (ruled at 40.7 %) did not print, so it is under 50.7 % of
Lagan's run, as after increment 35.

## Method

* Tree `/Users/skavhaug/projects/rasputin/.claude/worktrees/mapbiomas-26a`
at `f51f0a18`, clean. `tools/bench_quick.py run` builds Release into
`build-bench/` and runs the seven cases of `docs/benchmarks/quick/cases.toml`
from there (3 to 5 timed runs per case, the median judged), so no stale
extension is measured. `RASPUTIN_DATA=/Users/skavhaug/projects/rasputin_data`.
* Power: AC, battery 100 %, `caffeinate` running, before and after
(`power-state.txt`). Nothing else ran beside the timing run
(`pgrep` for cmake, clang, pytest, bench and `rasputin mesh` found nothing).
* Full tool output: `run.log` (Python's two `sys.prefix` RuntimeWarnings from
the Homebrew interpreter are noise, not from the tool).
* Meshes: written by the tool to its own temporary directories and removed;
regenerate by re-running the command above.

## Limits

* `bench_quick.py` writes a run's record (per-case medians, spreads and mesh
hashes) only with `--save-baseline`, which replaces
`docs/benchmarks/quick/baseline-ac.json`. A second run with that flag,
meant to be copied here and the baseline restored, was refused by the
session's permission classifier, so this directory holds the tool's
verdict and hotspot lines, not the medians. A `--record <path>` option that
saves the record without touching the baseline would close this gap; it
is tooling (`tools/`) and goes through the TDD loop.
2 changes: 2 additions & 0 deletions docs/benchmarks/2026-10-11/mapbiomas-26a/power-state.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Now drawing from 'AC Power'
Sun Oct 11 22:58:56 CEST 2026
10 changes: 10 additions & 0 deletions docs/benchmarks/2026-10-11/mapbiomas-26a/run.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HOTSPOT: tile refine 44 % of the run (not in hotspots.toml)
HOTSPOT: tile t=1 refine 67 % of the run (not in hotspots.toml)
HOTSPOT: tile t=1 refine: scan (parallel) 50 % of the run (not in hotspots.toml)
HOTSPOT: quarter refine 47 % of the run (not in hotspots.toml)
HOTSPOT: numedalslagen features clip 68 % of the run (not in hotspots.toml)
HOTSPOT: numedalslagen features clip: clean-up 67 % of the run (not in hotspots.toml)
HOTSPOT: geilo-al-ramp decode 42 % of the run (not in hotspots.toml)
HOTSPOT: romsdal-slope refine 56 % of the run (not in hotspots.toml)
NO CHANGE
EXIT 0
Loading
Loading