diff --git a/ROADMAP.md b/ROADMAP.md index 77f0fd4e..9998226b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -42,7 +42,7 @@ increment that most needs a picture to check against | 20 | Quality start: before DEM refinement, Steiner points at the DEM node nearest each bad triangle's circumcentre until the start mesh has a 25° minimum angle (`--start-min-angle`, 0 is off); geometry only, input segments not split, serial and deterministic. Removes increment 16's boundary fans | landed with 20b as interim (C1-C3 open, to 20c; C4 -> 20b) | `docs/increments/20-start-quality.md` | | 20b | Minimum insertion distance from constraints: when refinement's worst node lies within ε = clamp(tol / slope, cell/100, cell/2) of a constraint segment, insert the foot on the segment (off-node, bilinear z) instead; a footed node still above tolerance is inserted after all, so the tolerance guarantee is unchanged. Removes the 0.0117° needle at 1 m (increment 20's C4) | shipped with 20 (#97) | `docs/increments/20b-min-insertion-distance.md` | | — | `tools/bench.py`: the 1 m benchmark and the thread-scaling sweep from one checked-in command, with power state, quality and commit recorded per run; the one-off scripts in `docs/benchmarks/2026-09-26/` are its specification. Rule 2's acceptance run needs it | shipped with branch `tools-bench`'s PR | `docs/benchmarks/bench-py.md` | -| — | The serial phase: profile refine's serial insert-and-flip phase, then parallelise what the profile blames. Scaling tops out at about 2.0-2.2×. Profiled 2026-09-27: serial part about a third of 1-thread refine, mostly Lawson legalisation; the scan stops near 5× from load imbalance | profiled; designed as increment 21 (Ola's rulings 2026-09-27: L1 determinism, one path, at most 2 % more triangles). **21a shipped with branch `increment21a-quick-wins`'s PR**: dynamic scan blocks, active merge, reused flip stack; mesh bit-identical; on AC refine -10 to -15 % at 8 threads, ceiling 2.1x -> 2.3x (`docs/benchmarks/2026-09-27/21a-acceptance.md`). 21b next, then 21c measurements, then 21d | `docs/increments/21-parallel-refine.md`, `docs/benchmarks/2026-09-27/serial-profile/README.md` | +| — | The serial phase: profile refine's serial insert-and-flip phase, then parallelise what the profile blames. Scaling tops out at about 2.0-2.2×. Profiled 2026-09-27: serial part about a third of 1-thread refine, mostly Lawson legalisation; the scan stops near 5× from load imbalance | profiled; designed as increment 21 (Ola's rulings 2026-09-27: L1 determinism, one path, at most 2 % more triangles). **21a shipped with branch `increment21a-quick-wins`'s PR**: dynamic scan blocks, active merge, reused flip stack; mesh bit-identical; on AC refine -10 to -15 % at 8 threads, ceiling 2.1x -> 2.3x (`docs/benchmarks/2026-09-27/21a-acceptance.md`). **21b shipped with branch `increment21b-lattice-incircle`'s PR**: an int64 lattice incircle answers 99.97-100 % of refine's incircle tests; mesh bit-identical; on battery refine -15 % at 8 threads, ceiling 2.3x -> 2.5x (`docs/benchmarks/2026-09-27/21b-acceptance.md`). 21c measurements next, then 21d | `docs/increments/21-parallel-refine.md`, `docs/benchmarks/2026-09-27/serial-profile/README.md` | | 16b | Interior polygons and polylines as constraints ("terrain polygons": lakes, land cover, roads, rivers): `--features PATH`, a GeoJSON `FeatureCollection`, each feature naming a vocabulary property; closed or open `Breakline`s, crossings noded, off-node vertices with bilinear z. **Its working example is real data** (Ola, 2026-09-27): CORINE Land Cover 2018 over the benchmark tile `7908_3_10m_z33.tif`. The source is Ola's local copy, `rasputin_data/corine_sql/.../U2018_CLC2018_V2020_20u1.gpkg` (8.2 GB, EPSG:3035, a sibling of this repository, which also holds the 254-tile DTM10 archive for gap 6). It reads without GDAL: sqlite3 over its R-tree, the GeoPackage blob header stripped, `shapely.wkb`, then `pyproj` to 25833, so CRS stops in Python as before. Probed 2026-09-27 in 0.2 s: 60 polygons in 8 classes (heath, bare rock, sparse vegetation, bogs, intertidal flats, water, sea, urban), 11 068 vertices clipped to the tile, median segment 54 m against 10 m cells. The EEA's public ArcGIS service (`image.discomap.eea.europa.eu`, `Corine/CLC2018_WM`) returns the same 11 068 clipped vertices and is the route for anyone without the file. What it forces on 16b's design: neighbouring polygons share their boundaries, so each shared edge arrives twice; the polygons run past the domain and must be clipped; the extract is committed as a fixture with the Copernicus attribution. Placed before 20c because 20c may split constraint segments and should be designed and measured on inputs that have interior ones | designed in 16's R6, no increment file yet; after the serial phase, before 20c | `docs/increments/16-domain-polygon.md` (R6) | | 20c | Soft quality criterion: a penalty that each Steiner node or constraint split must pay for in angle gained, instead of 20's hard 25°; applied at the start and during DEM refinement; may split constraint segments when that improves the mesh. Ola's rulings on 20's C1-C3 | to design after 16b (`@architect` measures cost against 20 first) | `docs/increments/20-start-quality.md` (Ola's rulings) | | — | Auto-catchment: the watershed upstream of a coordinate, computed from the DEM and handed to `--domain`, so a catchment no longer has to be supplied as a file (Ola, 2026-09-27: "not far into the future"). The textbook route is depression handling (Priority-Flood, Barnes, Lehman and Mulla 2014), D8 flow directions (O'Callaghan and Mark 1984) and accumulation, the pour point snapped to the strongest flow nearby, the upstream cells traced and their outline turned into a polygon; the literature check is `@architect`'s. Open for its design: whether it runs in the C++ core (a 10 m tile is 25 M cells); how a stair-stepped cell outline becomes a domain polygon, which meets input coarsening; and that a real catchment crosses tile edges, so it needs gap 6 (a DEM in several tiles) first. Legacy has nothing on it (`grep -rliE "watershed|flow.?acc|flow.?dir|pour.?point|catchment" legacy` returns no files) | to design; after gap 6, which it needs; placed after 20c, can move ahead of it on Ola's word | none yet | diff --git a/docs/benchmarks/2026-09-27/21b-acceptance.md b/docs/benchmarks/2026-09-27/21b-acceptance.md new file mode 100644 index 00000000..5d99fa23 --- /dev/null +++ b/docs/benchmarks/2026-09-27/21b-acceptance.md @@ -0,0 +1,228 @@ +# Increment 21b acceptance run (@perf, 2026-09-27) + +**Verdict: ACCEPTED.** On battery, measured back to back against the previous +merge (`071e4f3`, 21a): refine at 8 threads is 14.5-14.8 % faster on both +domains, in both pairs. The mesh sha256 is identical on both domains. No +measure regressed. + +One thing to act on: the head `a03ae65` has a split phase 4-6 % slower than +21b's green commit `f707322`. The only production change between them is the +non-finite refusal in `lattice_incircle` (see "Green against head"). Fixed in +`4be156c`; see the Addendum. + +## Method + +- **Why a re-measured base.** The Mac was on battery. The stored `bench.py` + runs of 21a (`21a*/`) are AC. The only battery `bench.py` run, + `serial-profile-bench/`, predates 21a. So there was no comparable baseline. + Master `071e4f3` (the 21a merge, #102) was checked out in a temporary + `git worktree` and measured with `--tree`, back to back with 21b's head + `a03ae65`. The worktree was removed afterwards. +- **`--baseline` is explicit.** `071e4f3` is not an ancestor of `a03ae65`: the + branch forked from 21a's head, not from the merge + (`git merge-base --is-ancestor 071e4f3 a03ae65` exits 1). `find_baseline` + would therefore skip it. +- **Two pairs**, run in the order base, 21b, base, 21b between 18:56 and + 19:05 CEST. The verdicts can be reproduced from the stored evidence: + + ``` + .venv/bin/python tools/bench.py compare docs/benchmarks/2026-09-27/21b \ + --baseline docs/benchmarks/2026-09-27/21b-base-071e4f3 # ACCEPTED + .venv/bin/python tools/bench.py compare docs/benchmarks/2026-09-27/21b-r2 \ + --baseline docs/benchmarks/2026-09-27/21b-base-071e4f3-r2 # ACCEPTED + .venv/bin/python tools/bench.py compare docs/benchmarks/2026-09-27/21b-base-071e4f3-r2 \ + --baseline docs/benchmarks/2026-09-27/21b-base-071e4f3 # REGRESSION (same build; see Spread) + .venv/bin/python tools/bench.py compare docs/benchmarks/2026-09-27/21b-r2 \ + --baseline docs/benchmarks/2026-09-27/21b # ACCEPTED + ``` + + The two cross pairs (`21b-r2` against `21b-base-071e4f3`, and `21b` against + `21b-base-071e4f3-r2`) are also ACCEPTED. +- **Command.** All four runs used the main tree's `tools/bench.py`, blob + `77765b1`, the same as 21a's. Each run did its own Release build into + `/build-bench`. + + ``` + .venv/bin/python tools/bench.py run --label