diff --git a/ROADMAP.md b/ROADMAP.md index 12277b1d..c78037e8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -56,7 +56,7 @@ increment that most needs a picture to check against | 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` | | 31 | Binary output by default: `rasputin mesh` writes binary `.vtk` (and `.ply`) unless `--ascii` is given, because the text writer formats one number at a time and costs minutes at basin scale (the performance review's finding F6 and question 3, PR #208). Ola, 2026-10-07: "change 3 to default binary". The flag pair `--binary/--ascii` stays; the help says binary is the default; README updated. Python only; no `@perf` run (not refine or mesh code) | **merged (#212, 2026-10-07)** (designed 2026-10-07; red `e3fe391a`/`9008811c`, green `29bc5ad3`; 4 net production lines against about 1, the overrun is `ruff format` wrapping the option); Ola ruled `.ply` binary too, as built; next the push | `docs/increments/31-binary-default.md` | | 32 | Land-cover borders simplified within a band, each class keeping its area exactly (Ola, 2026-10-08: "we need to implement is a CORINE simplification, similar to what we do on the outline of the auto-catchments"): the land-cover coverage cut into borders between junctions, each border reduced by the area-preserving segment collapse of the catchment outline (`reduce_ring`), with junctions and the domain outline fixed, a crossing test and a side test across all borders, and a band no border moves past (two-sided, proved by anchors on the source border). `--features-tolerance` becomes that band, default 50 m (half CORINE's positional accuracy), replacing GEOS's coverage simplifier; start angle 15° when land cover is simplified (band above 0), 25° otherwise. Prototype on the German fused catchment: 5.7 times fewer triangles at 50 m vertical tolerance | **merged (#222, 2026-10-11)** (green `82c0a7d4`, 519 net production lines, one PR; mutation round `30f939b6`, one mutant recorded as not killed); Ola ruled all four questions (2026-10-08); code review round 1 asked for changes (comments, docstrings and `project_structure.md` the change made false), fixed by `4d4f0411` and the docs commit after it; code review rounds 1-3 done (round 3 approved); `@perf`'s short timing check found the runs about 70 % slower (the outline rule on borders clipped to the catchment) and the worst angle far lower (the simplifier placing points millimetres from junctions); fix built (section 15: outline rule first, a 1 m clearance in the simplifier; green `05c8f73a`, 573 counted lines in all; mutation round M7 to M13 killed); its quick check `b192a2c6`: Numedalslagen −11 %, Lagan −1.3 %, outline rule and moved area as before, triangles −42 %; questions 5 and 6 decided on default while Ola was away, reversible; fix code review round 1 asked for prose fixes and the test file's red-step staging removed; fix code review round 3 approved; next the push on Ola's yes | `docs/increments/32-landcover-simplify.md` | -| — | GeoPackage output, so QGIS opens a mesh as it opens any vector layer: `rasputin mesh --out X.gpkg`, chosen by the suffix of `--out` as `.vtk` and `.ply` are (Ola, 2026-10-05: "Is the vtk_to_phkg.py the way to go, or as a direct output from rasputin? I think the latter, and it should go into the roadmap and eventually into the codebase."). Proposed content: a `triangles` layer, one PolygonZ per triangle in the run's CRS (z at each of the three corners, as the stopgap writes), with the attributes the run already knows: land-cover code and class name (the code system's names, as `rasputin palette` has them), mean z, plan area; a layer of the constraint polygons (the `--features` polygons clipped to the domain, with their code); optionally a QGIS style for land cover stored in the file (the `layer_styles` table), so the triangles open coloured. Land-cover colour: when the mesh carries CORINE classes, each triangle and each constraint polygon also gets its class's colour as a `#rrggbb` text column, so a reader can colour by the column without the stored style (in QGIS, a data-defined fill colour). The Graz file of 2026-10-05 carries `clc_color` on its `triangles` and `corine` layers; its stored styles categorise on `clc_code` and do not read the column. The colours are those of `rasputin palette` (`src_python/tin_engine/palettes.py`), the same 26 the Graz file carries: natural colours, as Ola chose over the official CLC legend in 16c's ruling 2 (that legend paints peat bogs blue and the sea almost white). **One attribute is not in the mesh yet**: the ID of the source polygon each triangle lies in (a sub-catchment cell, a parcel). Today only a class code labels triangles, one code system per mesh (16c, 16e's D1); the stopgap finds the cell by a centroid lookup outside rasputin. It needs a per-triangle array from a polygon source's ID property: its own small step, done just before this writer, because the `.vtk` and `.ply` outputs gain the attribute too (Ola, 2026-10-05, accepting the default: "defaults on the cell ID"). Written with the standard library's `sqlite3` beside the existing reader `src_python/tin_engine/io/geopackage.py`, which can read it back in the tests; no GDAL, OGR or Fiona (CLAUDE.md §2). Python only: the C++ core is unchanged and never sees the file (the I/O boundary). Prior art and working spec: Ola's stopgap `vtk_to_gpkg.py`, outside the repository (`rasputin_data/lands/scripts/`), which converts a mesh `.vtk` with `sqlite3`, NumPy, Shapely and PyProj, no GDAL, and its first use, the Mur above Graz (2 451 734 triangles, 10 m DEM, tolerance 10 m, 2026-10-05): **the GeoPackage was 677.9 MB against 110.6 MB for the binary `.vtk`**; the whole stopgap run took 39 s and 4.1 GB peak, and that includes the centroid cell lookup and the CORINE clipping, so it is not the cost of the GeoPackage encoding alone: the design measures the writer on its own. A GeoPackage row holds the triangle's whole ring (four corners, the first repeated) and a bounding box, and the file adds a spatial index, where `.vtk` stores each vertex once. Questions for the design: file size (keep the spatial index or not; at most an opt-in variant of 2D polygons with mean z as an attribute, proposed default: full PolygonZ; writing in chunks to bound memory); the CRS entry for a CRS with no EPSG code; where the run record (increment 25) goes; whether the constraint lines get a layer of their own | proposed by Ola 2026-10-05 ("yes, roadmap row now, build after PR D"); not designed. Built after the Python audit's PR D (branch `audit-encoders`: the file encoders move out of `cli.py` into `io/`, and the mesh writers get one shared check; `docs/increments/python-audit.md` on branch `worktree-python-audit`, `8241e14`), so the new writer joins that shared check instead of a third copy | - | +| 36 | GeoPackage output, so QGIS opens a mesh as it opens any vector layer: `rasputin mesh --out X.gpkg`, chosen by the suffix of `--out` as `.vtk` and `.ply` are (Ola, 2026-10-05: "Is the vtk_to_phkg.py the way to go, or as a direct output from rasputin? I think the latter, and it should go into the roadmap and eventually into the codebase."). Proposed content: a `triangles` layer, one PolygonZ per triangle in the run's CRS (z at each of the three corners, as the stopgap writes), with the attributes the run already knows: land-cover code and class name (the code system's names, as `rasputin palette` has them), mean z, plan area; a layer of the constraint polygons (the `--features` polygons clipped to the domain, with their code); optionally a QGIS style for land cover stored in the file (the `layer_styles` table), so the triangles open coloured. Land-cover colour: when the mesh carries CORINE classes, each triangle and each constraint polygon also gets its class's colour as a `#rrggbb` text column, so a reader can colour by the column without the stored style (in QGIS, a data-defined fill colour). The Graz file of 2026-10-05 carries `clc_color` on its `triangles` and `corine` layers; its stored styles categorise on `clc_code` and do not read the column. The colours are those of `rasputin palette` (`src_python/tin_engine/palettes.py`), the same 26 the Graz file carries: natural colours, as Ola chose over the official CLC legend in 16c's ruling 2 (that legend paints peat bogs blue and the sea almost white). **One attribute is not in the mesh yet**: the ID of the source polygon each triangle lies in (a sub-catchment cell, a parcel). Today only a class code labels triangles, one code system per mesh (16c, 16e's D1); the stopgap finds the cell by a centroid lookup outside rasputin. It needs a per-triangle array from a polygon source's ID property: its own small step, done just before this writer, because the `.vtk` and `.ply` outputs gain the attribute too (Ola, 2026-10-05, accepting the default: "defaults on the cell ID"). Written with the standard library's `sqlite3` beside the existing reader `src_python/tin_engine/io/geopackage.py`, which can read it back in the tests; no GDAL, OGR or Fiona (CLAUDE.md §2). Python only: the C++ core is unchanged and never sees the file (the I/O boundary). Prior art and working spec: Ola's stopgap `vtk_to_gpkg.py`, outside the repository (`rasputin_data/lands/scripts/`), which converts a mesh `.vtk` with `sqlite3`, NumPy, Shapely and PyProj, no GDAL, and its first use, the Mur above Graz (2 451 734 triangles, 10 m DEM, tolerance 10 m, 2026-10-05): **the GeoPackage was 677.9 MB against 110.6 MB for the binary `.vtk`**; the whole stopgap run took 39 s and 4.1 GB peak, and that includes the centroid cell lookup and the CORINE clipping, so it is not the cost of the GeoPackage encoding alone: the design measures the writer on its own. A GeoPackage row holds the triangle's whole ring (four corners, the first repeated) and a bounding box, and the file adds a spatial index, where `.vtk` stores each vertex once. Questions for the design: file size (keep the spatial index or not; at most an opt-in variant of 2D polygons with mean z as an attribute, proposed default: full PolygonZ; writing in chunks to bound memory); the CRS entry for a CRS with no EPSG code; where the run record (increment 25) goes; whether the constraint lines get a layer of their own | **built** (branch `worktree-gpkg-out`: red `87f41c3d`, green `c59246e5`, fix-up `7085dddf`, 377 net production lines, mutation round `dddce8e8` ten of ten killed; `@perf`'s acceptance `57c25074` ACCEPTED: Numedalslagen 3.93 s / 203 MB, Romsdalen 19.4 s / 810 MB, every mesh byte-identical; code review round 1's one blocker, an empty land-cover layer crashing the write, fixed by `55cc3c3e` + `18442b27`, 378 net lines; code review round 2 APPROVED; next the push on Ola's yes). Designed by `@architect`, 2026-10-11, on `81a551b3`; proposed by Ola 2026-10-05, "yes, roadmap row now, build after PR D"; PR D merged as #205, so the writer joins the encoders under `io/`). Measured on this design's probe: the quick check's Numedalslagen mesh (653,039 triangles) written in 4.0 s to 203 MB against 0.28 s and 29 MB for the binary `.vtk`, the stopgap 5.1 s; the Romsdalen slope mesh (3.1 million) in 21.5 s to 922 MB at 2.1 GB peak, the stopgap 28.7 s at 4.1 GB; the R-tree index is 60 % of the time; the same run writes the same bytes. Layers `triangles` (PolygonZ with code, name, colour, z_min, z_max, z_mean, area_m2, slope_deg), `lines`, `land_cover`, `land_cover_classes`, a `run` table with the run record, and the stored QGIS style; about 305 net lines, one PR; questions 1-5 answered with their defaults while Ola is away | `docs/increments/36-geopackage-output.md` | | — | A missing station number refused in plain words. In a reference-polygon file (`rasputin station-catchments --reference`), a feature whose `station` is null becomes the text "None" (`src_python/tin_engine/io/station_set.py@ed12512:89`). No station can match it, since a station number must look like `2.11.0` (`src_python/tin_engine/hydrography.py@ed12512:40`), so the polygon is dropped without a word and the station it belonged to is written with no comparison (`src_python/tin_engine/catchment_batch.py@ed12512:220`); two such polygons are refused as "station None appears twice". In a stations file a null number is refused, but in Pydantic's validation text rather than a sentence naming the file (`src_python/tin_engine/io/station_set.py@ed12512:79`). NVE's files have a number on every station, so only hand-made or third-party files are hit. The fix: both readers refuse a null `station`, naming the file; one or two lines. Plainer GeoJSON error messages in general are not planned until other people feed rasputin their own files | approved by Ola 2026-10-06; not designed (a small fix) | - | | — | A GeoTIFF whose coordinate-system GeoKeys hold garbage refused in plain words. The reader turns each code key into a whole number with `int()`, and a key stored as a double or as text can hold NaN, infinity or letters. Probed 2026-10-06 on hand-made files: NaN or text in `ProjectedCSTypeGeoKey` (3072) stops `rasputin mesh --dem` with Python's conversion message (for NaN, "Invalid value for --dem: cannot convert float NaN to integer"), which names neither the key nor the file, and infinity ends in a Python traceback (`OverflowError`) (`src_python/tin_engine/io/geotiff.py@ed12512:171`). The same holds for every other code key the reader reads: 1025, 4099, 3076, 3074, 3075, 2048 and 2054 (`src_python/tin_engine/io/geotiff.py@ed12512:310`, `src_python/tin_engine/io/geotiff.py@ed12512:176`, `src_python/tin_engine/io/geotiff.py@ed12512:339`, `src_python/tin_engine/io/geotiff.py@ed12512:352`, `src_python/tin_engine/io/geotiff.py@ed12512:362`, `src_python/tin_engine/io/geotiff.py@ed12512:499`, `src_python/tin_engine/io/geotiff.py@ed12512:423`). Worse, a fractional value is cut to a whole number first: 2.5 in 4099 is refused as 2, and 32633.5 in 3072 is read as EPSG:32633 without a word (`src_python/tin_engine/io/geotiff.py@ed12512:321`); 1.5 in 1025, 9001.7 in 4099 and 9001.5 in 3076 are likewise accepted as 1 and 9001 (probed by code review round 1 on this row's branch, exit 0). The parameter keys are already checked (`_one_number`, `src_python/tin_engine/io/geotiff.py@ed12512:454`). The fix: one check for every code key, a whole number or a refusal naming the key and its value. No real file is known to do this | approved by Ola 2026-10-06; not designed (a small fix) | - | | — | tifffile's warning about the NoData tag silenced. On a float32 DEM whose `GDAL_NODATA` (42113) is the lowest float32 written in 17 digits (`-3.4028234663852886e+38`), tifffile prints "parsing GDAL_NODATA tag raised ValueError: ... is not castable to float32" to stderr when the file is opened (`src_python/tin_engine/io/geotiff.py@ed12512:146`). It is noise: rasputin reads the tag itself and accepts the value, since it is that float32 exactly (`src_python/tin_engine/io/geotiff.py@ed12512:535`). The fix: silence that one tifffile message while the reader opens the file, leaving every other tifffile warning visible | approved by Ola 2026-10-06; not designed (a small PR) | - | diff --git a/docs/benchmarks/2026-10-11/gpkg-out/README.md b/docs/benchmarks/2026-10-11/gpkg-out/README.md new file mode 100644 index 00000000..f91295e1 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/README.md @@ -0,0 +1,144 @@ +# Increment 36 acceptance: GeoPackage output (@perf, 2026-10-11) + +Branch `worktree-gpkg-out`, head `e2470ae7`, against its base, master +`81a551b3` (`git merge-base master HEAD`; the merge of #227, before increment +35's #229). The rule: `docs/increments/36-geopackage-output.md`, section 9: +(a) the quick check (`tools/bench_quick.py run`) unchanged with every +`mesh_sha256` equal; (b) the GeoPackage of the quick check's Numedalslagen +case written in at most 5 s on AC and of the Romsdalen slope mesh in at most +25 s, peak RSS under 1 GB and 3 GB respectively, measured with `/usr/bin/time +-l` on the built command. (c) is Ola's QGIS check and (d) is `@tester`'s T5; +neither is measured here. The branch touches no C++ (`git diff --stat +81a551b3..HEAD -- include src CMakeLists.txt tests/cpp` is empty), so no +`bench.py` 1 m run and no thread sweep. + +## Verdict: ACCEPTED + +| gate | measured (medians of 3, AC) | limit | result | +|---|---|---|---| +| (a) mesh SHA-256 unchanged, all 7 cases | equal on every case, against the base and against master's refreshed baseline (`quick/delta.md`, `quick/delta-vs-master-357808b1.md`) | equal | met | +| (a) quick check totals | `NO CHANGE`, exit 0, head against base back to back (`quick/head-run.log`); -4.3 % to +1.6 % | within the band | met | +| (b) Numedalslagen `write: geopackage` | **3.932 s** (3.892, 3.932, 3.988) | at most 5 s | met | +| (b) Numedalslagen peak RSS | whole command **2.77 GB** against 2.73 GB with `--out .vtk`; the writer's own share **+32 MB** (RSS growth inside its phase; +0.04 GB on the command's peak) | writer's share under 1 GB | met | +| (b) Romsdalen `write: geopackage` | **19.368 s** (19.051, 19.368, 20.154) | at most 25 s | met | +| (b) Romsdalen peak RSS | whole command **2.91 GB** against 2.78 GB with `--out .vtk`; the writer's own share **+188 MB** (+0.13 GB on the command's peak) | writer's share under 3 GB | met | + +`max_error` is within tolerance on every quick-check case and the same on both +sides (the records). Both GeoPackage runs exited 0; `--stats` and the +written file sizes agree with section 3 (Numedalslagen 203.3 MB, 653,039 +triangles; Romsdalen 810.0 MB, 3,096,202 triangles — section 3's probe wrote +922 MB for the same mesh; the difference was not investigated). + +## (a) The quick check, base `81a551b3` / head `e2470ae7` + +`scripts/quick_delta.py`, full output `quick/delta.md`: + +| case | base total s | head total s | change | mesh same | features clip | decode | refine | +|---|---:|---:|---:|---|---|---|---| +| tile | 0.443 | 0.426 | -3.8 % | yes | - | 0.064 / 0.060 | 0.196 / 0.195 | +| tile t=1 | 0.801 | 0.766 | -4.3 % | yes | - | 0.064 / 0.062 | 0.555 / 0.531 | +| quarter | 0.383 | 0.369 | -3.6 % | yes | - | 0.050 / 0.048 | 0.179 / 0.173 | +| numedalslagen | 9.169 | 9.320 | +1.6 % | yes | 6.226 / 6.381 (+2.5 %) | 0.967 / 0.964 | 0.708 / 0.688 | +| lagan | 20.166 | 20.029 | -0.7 % | yes | 11.584 / 11.521 (-0.5 %) | 4.731 / 4.673 | 0.251 / 0.245 | +| geilo-al-ramp | 0.349 | 0.335 | -3.9 % | yes | - | 0.148 / 0.141 | 0.109 / 0.101 | +| romsdal-slope | 3.844 | 3.748 | -2.5 % | yes | - | 0.070 / 0.070 | 2.200 / 2.094 | + +Spreads (max - min) / median of the totals: 0.2 % to 3.4 % on both sides +(`quick/delta.md`). The `.vtk` write phases are unchanged (`write: encode` +within 5 % on every case; `write: disk` is 1 to 62 ms and noisy). + +**Against master's refreshed baseline** (`357808b1`, increment 35's, +`quick/master-357808b1-baseline.json`, table `quick/delta-vs-master-357808b1.md`): +every mesh SHA-256 equal; Lagan total 14.542 -> 20.029 s (+37.7 %) with +`features clip` 6.342 -> 11.521 s, and Numedalslagen +5.6 %. Lagan's gap is +increment 35's saving, which this branch does not contain (the base +`81a551b3`, run back to back, has the same 11.58 s clip); it is the base +difference, not a regression of 36. The remaining +2 % to +10 % on every +case, including the pure-C++ ones, is tonight's machine against this +afternoon's: the base run of unchanged code is already 3 % to 15 % slower +than master's record (`tile t=1` 0.801 against 0.699). The back-to-back +pair is the like-for-like measure. + +Hotspot lines (`quick/head-run.log`): the same unruled lines as the last +checks (`tile` and `quarter` refine, `tile t=1` scan, `geilo-al-ramp` decode, +`romsdal-slope` refine, Numedalslagen `features clip`), plus Lagan `features +clip` raised at 58 % (ruled at 41 %), which is the pre-35 clip; master's +check after #229 is quiet on it (`2026-10-11/cleanup-speed/README.md`). + +## (b) The writer on the built command + +`scripts/writer_time.py`, output `writer/writer-run.log`, records +`writer/writer-results.json`, per-run logs `writer/writer---.log` +(the command's `--stats -` report and the `/usr/bin/time -l` block; run 0 is +the warm-up) and RSS traces `writer/writer---.rss`. Each +command is `/usr/bin/time -l .venv/bin/rasputin mesh --out +mesh.gpkg --stats -`, and the subtraction baseline is the same command with +`--out mesh.vtk --binary`. The case arguments are the quick check's +`numedalslagen` and `romsdal-slope` lines of `docs/benchmarks/quick/cases.toml` +(the Romsdalen mesh is tile `6901_3`, `--tolerance 10 --tolerance-slope 2 25 +35`; its binary `.vtk` is 111.5 MB, as section 3's). + +| command | real s | `write:` phase s | whole-command peak RSS | writer's own share | file | +|---|---:|---:|---:|---:|---:| +| Numedalslagen `.gpkg` | 13.53 | **3.932** (`write: geopackage`) | **2.77 GB** (2.78, 2.77, 2.73) | 2.75 -> 2.77 GB, **+32 MB** (32, 18, 35) | 203.3 MB | +| Numedalslagen `.vtk` | 9.58 | 0.014 (`encode` + `disk`) | 2.73 GB (2.73, 2.72, 2.73) | - | 29.2 MB | +| Romsdalen `.gpkg` | 24.15 | **19.368** (`write: geopackage`) | **2.91 GB** (2.95, 2.90, 2.91) | 2.72 -> 2.91 GB, **+188 MB** (227, 181, 188) | 810.0 MB | +| Romsdalen `.vtk` | 4.82 | 0.058 (`encode` + `disk`) | 2.78 GB (2.78, 2.78, 2.79) | - | 111.5 MB | + +How the writer's share is separated: `/usr/bin/time -l`'s `maximum resident +set size` is the whole command's peak, and the meshing sets it (the `.vtk` +runs peak at 2.73 and 2.78 GB with no GeoPackage at all; `@developer`'s 2.79 GB +in section 11 is this figure). The script samples the rasputin child's RSS +with `ps` every 0.1 s; the writer's window is the last `write: geopackage` +seconds of the process (it is the run's last phase), and the writer's own +share is the RSS growth inside that window: what it adds on top of the mesh +the process already holds. Freed meshing memory the allocator reuses is not +visible to this measure, so it is a lower bound on the writer's allocations +and an exact figure for what the writer costs the command; the whole-command +peak, 2.77 and 2.91 GB, is the upper bound on anything the writer holds. Both +bounds are under section 9's 1 GB and 3 GB. Section 3's standalone probe +figures (0.56 GB and 2.1 GB, the probe's VTK read included) sit between them. + +Per triangle the writer takes 6.0 µs (Numedalslagen) and 6.3 µs +(Romsdalen); section 3 measured 2.2 µs per row plus 4.6 µs per R-tree entry. +The `.gpkg` command's extra wall time over the `.vtk` one (3.95 s and +19.33 s) equals the `write: geopackage` phase, so the GeoPackage path adds +nothing outside that phase. + +## Method + +- `RASPUTIN_DATA=/Users/skavhaug/projects/rasputin_data`, this worktree's + `.venv` (Python 3.14.7, numpy 2.5.4, shapely 2.2.0, GEOS 3.14.1, pyproj + 3.8.0, PROJ 9.8.1, SQLite as bundled with that Python), Apple M1 Max (8 P + + 2 E cores, 32 GB), macOS 27.0, AC power before and after every run + (`power-before.txt`, `quick/power-*.txt`, `writer/power-*.txt`), caffeinated; + nothing else ran. Bounds checks on (`bounds checks: on (libc++ fast)` in + every `--stats` report), as the stored baselines. +- **Quick check, base first** (`quick/base-run.log`): `bench_quick.py run + --tree --save-baseline`, judged + against the branch's stored baseline `39e3ae34` (increment 34's, numpy + 2.5.3): `NO BASELINE: machine numpy: 2.5.4 vs 2.5.3`, as + `docs/increments/README.md` ("Acceptance") foresees; its record + `quick/base-81a551b3.json` became the stored baseline for the head run. Its + meshes equal master's refreshed baseline's on every case, so the base and + master `de9b6c03` produce the same meshes (increment 35 changed none). + **Then head** (`quick/head-run.log`): `bench_quick.py run --save-baseline` + at `e2470ae7`, judged against the base record: `NO CHANGE`, exit 0; record + `quick/head-e2470ae7.json`. Both Release `_core` builds came from + `bench.build` into each tree's `build-bench`; the Python package ran from + each tree. `docs/benchmarks/quick/baseline-ac.json` was then restored to + the branch's committed file (`git checkout --`): the refreshed baseline is + master's (#229), and a second one on this branch would only conflict at + the merge. The scratch worktree was removed afterwards. +- **Writer**: the head run's Release `_core` (`build-bench`, hardening on) + copied into the venv's `site-packages` and touched before the timed runs + (sha256 `51872f0ab6f1d780...` on both sides), per the stale-artifacts + rule. No C++ changed, so no sanitized run. One warm-up and 3 timed runs per + command, medians; meshes written to the session scratchpad and deleted. +- Wall clock: 21:49 to 22:01; two quick checks and 16 writer commands. + +## To regenerate + + cd ; RASPUTIN_DATA=... .venv/bin/python tools/bench_quick.py run + RASPUTIN_DATA=... .venv/bin/python docs/benchmarks/2026-10-11/gpkg-out/scripts/writer_time.py + python3 docs/benchmarks/2026-10-11/gpkg-out/scripts/quick_delta.py diff --git a/docs/benchmarks/2026-10-11/gpkg-out/power-before.txt b/docs/benchmarks/2026-10-11/gpkg-out/power-before.txt new file mode 100644 index 00000000..2b930cf7 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/power-before.txt @@ -0,0 +1,2 @@ +Now drawing from 'AC Power' + -InternalBattery-0 (id=24117347) 100%; charged; 0:00 remaining present: true diff --git a/docs/benchmarks/2026-10-11/gpkg-out/quick/base-81a551b3.json b/docs/benchmarks/2026-10-11/gpkg-out/quick/base-81a551b3.json new file mode 100644 index 00000000..fc5842a4 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/quick/base-81a551b3.json @@ -0,0 +1,785 @@ +{ + "commit": "81a551b33d8a54d948385fabff6008b8d2550edd", + "machine": { + "cpu_brand": "Apple M1 Max", + "p_cores": 8, + "e_cores": 2, + "memory_bytes": 34359738368, + "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" + }, + "power": "ac", + "inputs": { + "$TREE/tests/fixtures/dem_archive/7908_3_10m_z33.tif": "file:4426661:aabd0cbc28471ce8593e4c278381811bec3fbf4e1194058b3b4888d00c3af575", + "$TREE/docs/benchmarks/2026-09-26/quarter.geojson": "file:16426:b7f7bbe96848383c77863ce29ee28c45bce087601a1738ee82ddb4ffb8279d1e", + "$RASPUTIN_DATA/DTM10_UTM33_20260925": "dir:3eef3cfed8b6d7db435791da65386cc1dcbbbadc2a9b145567765eb67b1a3dac", + "$RASPUTIN_DATA/numedalslagen_outline_nve.geojson": "file:554438:3069b1dbd2c22c87401fd57c3189f5a67d319f9aea0a95e90cc29886fa0dabca", + "$RASPUTIN_DATA/corine2018_dtm10_utm33.gpkg": "file:440332288:1790574665364371436", + "$RASPUTIN_DATA/sweden_glo30_cache": "dir:3deaa80f96e39f4ae4b4d3df54646b17e47c70847fe450d9c3e377fe7d758e0f", + "$RASPUTIN_DATA/sweden_smhi_svar/lagan_mouth_svar2022_3006.geojson": "file:1294446:a93a524fc198bd3e516ea36db55734c3e5de3994be05e19c807ce5546454123e", + "$RASPUTIN_DATA/corine_sql/u2018_clc2018_v2020_20u1_geoPackage/DATA/U2018_CLC2018_V2020_20u1.gpkg": "file:8855322624:1587553228000000000", + "$RASPUTIN_DATA/banenor_banenettverk/section_domain.geojson": "file:2337:d3af5872ac6da011cc95eaade2328cfff965116a5756f68786cd1b1907d5e571", + "$RASPUTIN_DATA/banenor_banenettverk/bergensbanen.geojson": "file:2195883:75e725295b777190f1a1be72fc99eeb2689bff59c1e23e99eaa3339bbcfea2af" + }, + "cases": { + "tile": { + "tolerance": 1.0, + "max_error": 0.9999759197235107, + "mesh_sha256": "746bf5c68bb9464fbb0746a9de6e0a5dbc7a15d1bfa1006cd9000f95d870ee0e", + "measures": { + "total": { + "median": 0.44295375, + "min": 0.435423375, + "max": 0.446950292 + }, + "decode": { + "median": 0.063743167, + "min": 0.062562084, + "max": 0.067922333 + }, + "start mesh: build": { + "median": 0.000188625, + "min": 0.0001815, + "max": 0.000217625 + }, + "start mesh: node": { + "median": 0.002698708, + "min": 0.002647292, + "max": 0.0027535 + }, + "start mesh: triangulate": { + "median": 0.029915042, + "min": 0.02981125, + "max": 0.030244958 + }, + "start mesh: constraint edges": { + "median": 0.006820708, + "min": 0.006740833, + "max": 0.006961 + }, + "refine": { + "median": 0.196378084, + "min": 0.192004458, + "max": 0.20466675 + }, + "refine: legalise start": { + "median": 0.00046525, + "min": 0.000461875, + "max": 0.000473166 + }, + "refine: start quality": { + "median": 0.000836458, + "min": 0.000824083, + "max": 0.000987958 + }, + "refine: scan (parallel)": { + "median": 0.061432789, + "min": 0.060428125000000006, + "max": 0.06586416400000004 + }, + "refine: split + flip (serial)": { + "median": 0.10802337699999995, + "min": 0.10361437200000002, + "max": 0.11179000300000003 + }, + "refine: setup + output": { + "median": 0.02578445100000004, + "min": 0.025551458999999943, + "max": 0.02596671000000006 + }, + "edge strip: generate": { + "median": 0.000787458, + "min": 0.000721542, + "max": 0.0009105 + }, + "edge strip: scan (parallel)": { + "median": 0.001047208, + "min": 0.001002917, + "max": 0.001279542 + }, + "edge strip: split + flip (serial)": { + "median": 0.001126125, + "min": 0.001054708, + "max": 0.00124675 + }, + "trim": { + "median": 0.017460041, + "min": 0.017318791, + "max": 0.018701208 + }, + "write: encode": { + "median": 0.010002417, + "min": 0.009656958, + "max": 0.012608042 + }, + "write: disk": { + "median": 0.008613583, + "min": 0.00304375, + "max": 0.009172833 + } + } + }, + "tile t=1": { + "tolerance": 1.0, + "max_error": 0.9999759197235107, + "mesh_sha256": "746bf5c68bb9464fbb0746a9de6e0a5dbc7a15d1bfa1006cd9000f95d870ee0e", + "measures": { + "total": { + "median": 0.800682833, + "min": 0.788788375, + "max": 0.816318458 + }, + "decode": { + "median": 0.063633666, + "min": 0.062429375, + "max": 0.064174625 + }, + "start mesh: build": { + "median": 0.000187667, + "min": 0.00017425, + "max": 0.000195917 + }, + "start mesh: node": { + "median": 0.002785417, + "min": 0.002667417, + "max": 0.00291925 + }, + "start mesh: triangulate": { + "median": 0.030173958, + "min": 0.029716291, + "max": 0.03123275 + }, + "start mesh: constraint edges": { + "median": 0.007020625, + "min": 0.006684833, + "max": 0.007296083 + }, + "refine": { + "median": 0.554770416, + "min": 0.549585041, + "max": 0.5701835 + }, + "refine: legalise start": { + "median": 0.000473125, + "min": 0.00045425, + "max": 0.000487625 + }, + "refine: start quality": { + "median": 0.000878875, + "min": 0.00080225, + "max": 0.0009 + }, + "refine: scan (parallel)": { + "median": 0.41457596199999985, + "min": 0.40758808499999993, + "max": 0.422637544 + }, + "refine: split + flip (serial)": { + "median": 0.11473466799999996, + "min": 0.11215175, + "max": 0.12153670300000002 + }, + "refine: setup + output": { + "median": 0.0260997460000002, + "min": 0.02590803800000019, + "max": 0.026563086999999763 + }, + "edge strip: generate": { + "median": 0.000781667, + "min": 0.000725208, + "max": 0.0010135 + }, + "edge strip: scan (parallel)": { + "median": 0.001047083, + "min": 0.001007833, + "max": 0.001081583 + }, + "edge strip: split + flip (serial)": { + "median": 0.001097583, + "min": 0.001041042, + "max": 0.001149542 + }, + "trim": { + "median": 0.017940958, + "min": 0.017302792, + "max": 0.01840925 + }, + "write: encode": { + "median": 0.009850208, + "min": 0.009685291, + "max": 0.01023825 + }, + "write: disk": { + "median": 0.007897209, + "min": 0.006395958, + "max": 0.010773125 + } + } + }, + "quarter": { + "tolerance": 1.0, + "max_error": 0.9999796549479072, + "mesh_sha256": "35a2b7bfda09e5b2f2912d33709889b11bb65af2d8e644ecfb28d24191d57fa4", + "measures": { + "total": { + "median": 0.382935875, + "min": 0.376925541, + "max": 0.387280292 + }, + "domain read": { + "median": 0.001714792, + "min": 0.001676, + "max": 0.001840458 + }, + "decode": { + "median": 0.05042, + "min": 0.048027291, + "max": 0.051249833 + }, + "start mesh: build": { + "median": 0.000146666, + "min": 0.000131625, + "max": 0.000169042 + }, + "start mesh: node": { + "median": 0.000487417, + "min": 0.000481042, + "max": 0.000488791 + }, + "start mesh: triangulate": { + "median": 0.000449625, + "min": 0.000444291, + "max": 0.000452875 + }, + "start mesh: constraint edges": { + "median": 0.000605667, + "min": 0.000589417, + "max": 0.000645 + }, + "refine": { + "median": 0.179326833, + "min": 0.174290416, + "max": 0.181828209 + }, + "refine: legalise start": { + "median": 0.000013792, + "min": 0.000013708, + "max": 0.000017583 + }, + "refine: start quality": { + "median": 0.001655667, + "min": 0.001629667, + "max": 0.001738042 + }, + "refine: scan (parallel)": { + "median": 0.05095395700000002, + "min": 0.04890250100000001, + "max": 0.052223295999999995 + }, + "refine: split + flip (serial)": { + "median": 0.10898196000000003, + "min": 0.105855163, + "max": 0.11029536899999999 + }, + "refine: setup + output": { + "median": 0.017881918999999996, + "min": 0.017636293999999997, + "max": 0.018156042999999983 + }, + "edge strip: generate": { + "median": 0.00081775, + "min": 0.000797125, + "max": 0.000855375 + }, + "edge strip: scan (parallel)": { + "median": 0.001081375, + "min": 0.001057209, + "max": 0.001191042 + }, + "edge strip: split + flip (serial)": { + "median": 0.002586, + "min": 0.0024592499999999996, + "max": 0.002651834 + }, + "trim": { + "median": 0.016494291, + "min": 0.016305083, + "max": 0.016761125 + }, + "write: encode": { + "median": 0.01001575, + "min": 0.009815417, + "max": 0.010147584 + }, + "write: disk": { + "median": 0.008114084, + "min": 0.006907709, + "max": 0.0094575 + } + } + }, + "numedalslagen": { + "tolerance": 10.0, + "max_error": 10.0, + "mesh_sha256": "7101ea0017bbf046b83d0a3a68bc2414207e1f82fd2f5dc785dae570451fc5f7", + "measures": { + "total": { + "median": 9.169014875, + "min": 9.143983083, + "max": 9.397428083 + }, + "domain read": { + "median": 0.014401709, + "min": 0.014392917, + "max": 0.014402792 + }, + "decode": { + "median": 0.966683209, + "min": 0.963877792, + "max": 0.967764792 + }, + "features read": { + "median": 0.2088687588693574, + "min": 0.20722492213826627, + "max": 0.20894190622493625 + }, + "features clip": { + "median": 6.226058449130505, + "min": 6.2109118278604, + "max": 6.461027177749202 + }, + "features clip: clean-up": { + "median": 6.158592708059587, + "min": 6.144984040991403, + "max": 6.394496166030876 + }, + "start mesh: build": { + "median": 0.001779834, + "min": 0.001772542, + "max": 0.00193975 + }, + "start mesh: node": { + "median": 0.147905625, + "min": 0.147896666, + "max": 0.152728417 + }, + "start mesh: triangulate": { + "median": 0.039931167, + "min": 0.039681458, + "max": 0.041406583 + }, + "start mesh: constraint edges": { + "median": 0.093580333, + "min": 0.093432875, + "max": 0.099093583 + }, + "refine": { + "median": 0.707757042, + "min": 0.703211458, + "max": 0.713311042 + }, + "refine: legalise start": { + "median": 0.002127959, + "min": 0.00212675, + "max": 0.002135667 + }, + "refine: start quality": { + "median": 0.034594708, + "min": 0.034292458, + "max": 0.039478708 + }, + "refine: scan (parallel)": { + "median": 0.4371284189999999, + "min": 0.4327861269999999, + "max": 0.4432698319999999 + }, + "refine: split + flip (serial)": { + "median": 0.180448459, + "min": 0.17789158200000002, + "max": 0.180634166 + }, + "refine: setup + output": { + "median": 0.05272237400000013, + "min": 0.0517722490000001, + "max": 0.05287008400000004 + }, + "edge strip: generate": { + "median": 0.10203175, + "min": 0.101111625, + "max": 0.105356333 + }, + "edge strip: scan (parallel)": { + "median": 0.009071501, + "min": 0.008698875000000002, + "max": 0.009401626000000001 + }, + "edge strip: split + flip (serial)": { + "median": 0.007819917000000001, + "min": 0.007317708, + "max": 0.00903229 + }, + "trim": { + "median": 0.026601416, + "min": 0.026447166, + "max": 0.028459459 + }, + "land cover": { + "median": 0.301729709, + "min": 0.298247458, + "max": 0.301982875 + }, + "write: encode": { + "median": 0.009923709, + "min": 0.009907458, + "max": 0.010136083 + }, + "write: disk": { + "median": 0.00614675, + "min": 0.004573458, + "max": 0.006301375 + } + } + }, + "lagan": { + "tolerance": 10.0, + "max_error": 9.999904677002576, + "mesh_sha256": "c0f3b555ddaffac4d38332b7f11a149e11e2039a66db6ff98695bf297a043d28", + "measures": { + "total": { + "median": 20.165750333, + "min": 20.0483955, + "max": 20.252794792 + }, + "domain read": { + "median": 0.044594209, + "min": 0.044218375, + "max": 0.044861375 + }, + "decode": { + "median": 4.731076292, + "min": 4.71986025, + "max": 4.811327167 + }, + "features read": { + "median": 0.9992148112505674, + "min": 0.9938796550268307, + "max": 1.0079902739962563 + }, + "features clip": { + "median": 11.583618678967468, + "min": 11.40784301806707, + "max": 11.661718646762893 + }, + "features clip: clean-up": { + "median": 7.425850374973379, + "min": 7.1974741669837385, + "max": 7.481557832914405 + }, + "start mesh: build": { + "median": 0.003144667, + "min": 0.00309, + "max": 0.003350875 + }, + "start mesh: node": { + "median": 0.321597333, + "min": 0.310555417, + "max": 0.322366458 + }, + "start mesh: triangulate": { + "median": 0.090991208, + "min": 0.0881235, + "max": 0.093619 + }, + "start mesh: constraint edges": { + "median": 0.193215916, + "min": 0.182163333, + "max": 0.196235917 + }, + "refine": { + "median": 0.251281792, + "min": 0.248316459, + "max": 0.257865792 + }, + "refine: legalise start": { + "median": 0.004438292, + "min": 0.004296875, + "max": 0.004464583 + }, + "refine: start quality": { + "median": 0.067369416, + "min": 0.063077625, + "max": 0.073155791 + }, + "refine: scan (parallel)": { + "median": 0.06399091700000002, + "min": 0.063359211, + "max": 0.064184705 + }, + "refine: split + flip (serial)": { + "median": 0.05869175299999999, + "min": 0.057677916999999995, + "max": 0.059332379 + }, + "refine: setup + output": { + "median": 0.05761866299999996, + "min": 0.057611462, + "max": 0.05819445400000001 + }, + "edge strip: generate": { + "median": 0.043101833, + "min": 0.042501625, + "max": 0.044022541 + }, + "check points: project": { + "median": 0.6770286259707063, + "min": 0.6741807459620759, + "max": 0.6852470012381673 + }, + "check points: store": { + "median": 0.27521387403323505, + "min": 0.274806878128636, + "max": 0.286407998824078 + }, + "final check: scan (parallel)": { + "median": 0.310477458, + "min": 0.30987154200000006, + "max": 0.3159169579999999 + }, + "final check: split + flip (serial)": { + "median": 0.042363084, + "min": 0.041876208000000005, + "max": 0.04302975 + }, + "trim": { + "median": 0.01962175, + "min": 0.01930775, + "max": 0.019773542 + }, + "land cover": { + "median": 0.215243833, + "min": 0.21460775, + "max": 0.216975416 + }, + "write: encode": { + "median": 0.008287, + "min": 0.0080685, + "max": 0.008302416 + }, + "write: disk": { + "median": 0.004228375, + "min": 0.004150208, + "max": 0.004942917 + } + } + }, + "geilo-al-ramp": { + "tolerance": 20.0, + "max_error": 19.984358261826856, + "mesh_sha256": "9f01b9ac27e2d2d5d1970e69a174995ebf99e6582adc17e05b257c8bd3f4348e", + "measures": { + "total": { + "median": 0.349105333, + "min": 0.346328625, + "max": 0.357195917 + }, + "domain read": { + "median": 0.001302167, + "min": 0.001275916, + "max": 0.001453166 + }, + "decode": { + "median": 0.147747667, + "min": 0.146144583, + "max": 0.152639833 + }, + "start mesh: build": { + "median": 0.0001295, + "min": 0.00012425, + "max": 0.000130417 + }, + "start mesh: node": { + "median": 0.000074834, + "min": 0.000071125, + "max": 0.000078417 + }, + "start mesh: triangulate": { + "median": 0.000030292, + "min": 0.000029334, + "max": 0.000032958 + }, + "start mesh: constraint edges": { + "median": 0.000091417, + "min": 0.000086792, + "max": 0.000143084 + }, + "refine": { + "median": 0.108747333, + "min": 0.104142042, + "max": 0.110062666 + }, + "refine: legalise start": { + "median": 3.167e-6, + "min": 3.125e-6, + "max": 3.333e-6 + }, + "refine: start quality": { + "median": 0.00016225, + "min": 0.0001565, + "max": 0.000167209 + }, + "refine: scan (parallel)": { + "median": 0.09152029199999999, + "min": 0.08692629299999999, + "max": 0.09219233199999997 + }, + "refine: split + flip (serial)": { + "median": 0.014212207999999999, + "min": 0.014194499999999997, + "max": 0.014755875 + }, + "refine: setup + output": { + "median": 0.0028669580000000167, + "min": 0.002843874000000024, + "max": 0.0029441250000000335 + }, + "edge strip: generate": { + "median": 0.000612, + "min": 0.000595875, + "max": 0.000670459 + }, + "edge strip: scan (parallel)": { + "median": 0.000749542, + "min": 0.000741875, + "max": 0.000797584 + }, + "edge strip: split + flip (serial)": { + "median": 0.000388792, + "min": 0.00030891599999999995, + "max": 0.000397125 + }, + "trim": { + "median": 0.0023825, + "min": 0.002296084, + "max": 0.002427 + }, + "write: encode": { + "median": 0.004263958, + "min": 0.004100541, + "max": 0.004856125 + }, + "write: disk": { + "median": 0.000630417, + "min": 0.000492584, + "max": 0.001973583 + } + } + }, + "romsdal-slope": { + "tolerance": 10.0, + "max_error": 9.999908093780277, + "mesh_sha256": "992e49cbcbe4a781ff03783a820946b163869b2b4a1f02bb70927d94224c4197", + "measures": { + "total": { + "median": 3.8444295, + "min": 3.831631875, + "max": 3.9217825 + }, + "decode": { + "median": 0.070037709, + "min": 0.068077959, + "max": 0.071510458 + }, + "start mesh: build": { + "median": 0.000177792, + "min": 0.000169792, + "max": 0.000185125 + }, + "start mesh: node": { + "median": 0.002625042, + "min": 0.002621459, + "max": 0.002814541 + }, + "start mesh: triangulate": { + "median": 0.029931834, + "min": 0.0298445, + "max": 0.0299565 + }, + "start mesh: constraint edges": { + "median": 0.006637833, + "min": 0.006590708, + "max": 0.006779 + }, + "slope": { + "median": 0.109908791, + "min": 0.109824125, + "max": 0.111281375 + }, + "refine": { + "median": 2.200274875, + "min": 2.160508125, + "max": 2.239031417 + }, + "refine: legalise start": { + "median": 0.000468875, + "min": 0.000468459, + "max": 0.000473542 + }, + "refine: start quality": { + "median": 0.000770125, + "min": 0.000752167, + "max": 0.000852916 + }, + "refine: scan (parallel)": { + "median": 0.6214294150000002, + "min": 0.607051414, + "max": 0.6317321229999999 + }, + "refine: split + flip (serial)": { + "median": 1.3112586689999997, + "min": 1.2881972920000004, + "max": 1.3275665040000002 + }, + "refine: setup + output": { + "median": 0.266366165, + "min": 0.2640204189999993, + "max": 0.27840633199999987 + }, + "edge strip: generate": { + "median": 0.001208083, + "min": 0.001086417, + "max": 0.001211334 + }, + "edge strip: scan (parallel)": { + "median": 0.010862457, + "min": 0.009744666999999999, + "max": 0.012122959000000003 + }, + "edge strip: split + flip (serial)": { + "median": 0.013748834000000003, + "min": 0.013093167000000003, + "max": 0.013927708999999998 + }, + "trim": { + "median": 0.120292333, + "min": 0.11961575, + "max": 0.1212495 + }, + "write: encode": { + "median": 0.045995416, + "min": 0.045504375, + "max": 0.046463834 + }, + "write: disk": { + "median": 0.0404895, + "min": 0.021991666, + "max": 0.067467959 + } + } + } + } +} diff --git a/docs/benchmarks/2026-10-11/gpkg-out/quick/base-run.log b/docs/benchmarks/2026-10-11/gpkg-out/quick/base-run.log new file mode 100644 index 00000000..32c7f755 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/quick/base-run.log @@ -0,0 +1,12 @@ +HOTSPOT: tile refine 44 % of the run (not in hotspots.toml) +HOTSPOT: tile t=1 refine 69 % of the run (not in hotspots.toml) +HOTSPOT: tile t=1 refine: scan (parallel) 52 % 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: lagan features clip 57 % of the run (ruled at 41 %, grown 10 points or more) +HOTSPOT: geilo-al-ramp decode 42 % of the run (not in hotspots.toml) +HOTSPOT: romsdal-slope refine 57 % of the run (not in hotspots.toml) +baseline saved: /Users/skavhaug/projects/rasputin/.claude/worktrees/gpkg-out/docs/benchmarks/quick/baseline-ac.json +NO BASELINE: machine numpy: 2.5.4 vs 2.5.3 +exit 2 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/quick/delta-vs-master-357808b1.md b/docs/benchmarks/2026-10-11/gpkg-out/quick/delta-vs-master-357808b1.md new file mode 100644 index 00000000..fa7ff97b --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/quick/delta-vs-master-357808b1.md @@ -0,0 +1,20 @@ +base 357808b1 (numpy 2.5.4, ac) -> head e2470ae7 (numpy 2.5.4, ac) + +| case | base total s | head total s | change | mesh same | max_error base / head | features clip | decode | refine | write: encode | write: disk | +|---|---:|---:|---:|---|---|---|---|---|---|---| +| tile | 0.416 | 0.426 | +2.5 % | True | 1.000 / 1.000 | - | 0.060 / 0.060 (-0.8 %) | 0.190 / 0.195 (+2.6 %) | 0.009 / 0.010 (+1.0 %) | 0.003 / 0.003 (-4.2 %) | +| tile t=1 | 0.699 | 0.766 | +9.6 % | True | 1.000 / 1.000 | - | 0.060 / 0.062 (+1.7 %) | 0.472 / 0.531 (+12.7 %) | 0.009 / 0.010 (+3.2 %) | 0.004 / 0.003 (-11.2 %) | +| quarter | 0.355 | 0.369 | +3.9 % | True | 1.000 / 1.000 | - | 0.044 / 0.048 (+9.9 %) | 0.172 / 0.173 (+0.8 %) | 0.009 / 0.010 (+5.6 %) | 0.004 / 0.003 (-8.3 %) | +| numedalslagen | 8.829 | 9.320 | +5.6 % | True | 10.000 / 10.000 | 6.000 / 6.381 (+6.3 %) | 0.921 / 0.964 (+4.7 %) | 0.659 / 0.688 (+4.4 %) | 0.010 / 0.010 (+0.2 %) | 0.006 / 0.006 (+5.0 %) | +| lagan | 14.542 | 20.029 | +37.7 % | True | 10.000 / 10.000 | 6.342 / 11.521 (+81.7 %) | 4.578 / 4.673 (+2.1 %) | 0.238 / 0.245 (+2.9 %) | 0.008 / 0.008 (+0.0 %) | 0.004 / 0.004 (+1.3 %) | +| geilo-al-ramp | 0.329 | 0.335 | +2.1 % | True | 19.984 / 19.984 | - | 0.138 / 0.141 (+2.1 %) | 0.099 / 0.101 (+2.1 %) | 0.004 / 0.004 (+4.0 %) | 0.001 / 0.001 (+1.4 %) | +| romsdal-slope | 3.578 | 3.748 | +4.8 % | True | 10.000 / 10.000 | - | 0.068 / 0.070 (+2.6 %) | 2.009 / 2.094 (+4.3 %) | 0.045 / 0.048 (+6.1 %) | 0.027 / 0.062 (+134.0 %) | + +Spreads (max - min) / median of the total, base / head: + tile: 2.2 % / 2.5 % + tile t=1: 0.4 % / 2.6 % + quarter: 3.0 % / 2.5 % + numedalslagen: 0.1 % / 3.2 % + lagan: 0.1 % / 2.5 % + geilo-al-ramp: 1.0 % / 1.1 % + romsdal-slope: 1.6 % / 0.2 % diff --git a/docs/benchmarks/2026-10-11/gpkg-out/quick/delta.md b/docs/benchmarks/2026-10-11/gpkg-out/quick/delta.md new file mode 100644 index 00000000..85c0eba8 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/quick/delta.md @@ -0,0 +1,20 @@ +base 81a551b3 (numpy 2.5.4, ac) -> head e2470ae7 (numpy 2.5.4, ac) + +| case | base total s | head total s | change | mesh same | max_error base / head | features clip | decode | refine | write: encode | write: disk | +|---|---:|---:|---:|---|---|---|---|---|---|---| +| tile | 0.443 | 0.426 | -3.8 % | True | 1.000 / 1.000 | - | 0.064 / 0.060 (-6.4 %) | 0.196 / 0.195 (-0.9 %) | 0.010 / 0.010 (-4.6 %) | 0.009 / 0.003 (-63.8 %) | +| tile t=1 | 0.801 | 0.766 | -4.3 % | True | 1.000 / 1.000 | - | 0.064 / 0.062 (-3.3 %) | 0.555 / 0.531 (-4.2 %) | 0.010 / 0.010 (-1.0 %) | 0.008 / 0.003 (-58.7 %) | +| quarter | 0.383 | 0.369 | -3.6 % | True | 1.000 / 1.000 | - | 0.050 / 0.048 (-4.7 %) | 0.179 / 0.173 (-3.3 %) | 0.010 / 0.010 (+0.1 %) | 0.008 / 0.003 (-59.7 %) | +| numedalslagen | 9.169 | 9.320 | +1.6 % | True | 10.000 / 10.000 | 6.226 / 6.381 (+2.5 %) | 0.967 / 0.964 (-0.3 %) | 0.708 / 0.688 (-2.8 %) | 0.010 / 0.010 (-0.2 %) | 0.006 / 0.006 (+5.0 %) | +| lagan | 20.166 | 20.029 | -0.7 % | True | 10.000 / 10.000 | 11.584 / 11.521 (-0.5 %) | 4.731 / 4.673 (-1.2 %) | 0.251 / 0.245 (-2.5 %) | 0.008 / 0.008 (-0.7 %) | 0.004 / 0.004 (-2.1 %) | +| geilo-al-ramp | 0.349 | 0.335 | -3.9 % | True | 19.984 / 19.984 | - | 0.148 / 0.141 (-4.3 %) | 0.109 / 0.101 (-6.9 %) | 0.004 / 0.004 (+1.0 %) | 0.001 / 0.001 (+7.4 %) | +| romsdal-slope | 3.844 | 3.748 | -2.5 % | True | 10.000 / 10.000 | - | 0.070 / 0.070 (-0.7 %) | 2.200 / 2.094 (-4.8 %) | 0.046 / 0.048 (+3.6 %) | 0.040 / 0.062 (+54.2 %) | + +Spreads (max - min) / median of the total, base / head: + tile: 2.6 % / 2.5 % + tile t=1: 3.4 % / 2.6 % + quarter: 2.7 % / 2.5 % + numedalslagen: 2.8 % / 3.2 % + lagan: 1.0 % / 2.5 % + geilo-al-ramp: 3.1 % / 1.1 % + romsdal-slope: 2.3 % / 0.2 % diff --git a/docs/benchmarks/2026-10-11/gpkg-out/quick/head-e2470ae7.json b/docs/benchmarks/2026-10-11/gpkg-out/quick/head-e2470ae7.json new file mode 100644 index 00000000..68edb840 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/quick/head-e2470ae7.json @@ -0,0 +1,785 @@ +{ + "commit": "e2470ae7d549f7f0f8c364e69f5bf60790e7deac", + "machine": { + "cpu_brand": "Apple M1 Max", + "p_cores": 8, + "e_cores": 2, + "memory_bytes": 34359738368, + "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" + }, + "power": "ac", + "inputs": { + "$TREE/tests/fixtures/dem_archive/7908_3_10m_z33.tif": "file:4426661:aabd0cbc28471ce8593e4c278381811bec3fbf4e1194058b3b4888d00c3af575", + "$TREE/docs/benchmarks/2026-09-26/quarter.geojson": "file:16426:b7f7bbe96848383c77863ce29ee28c45bce087601a1738ee82ddb4ffb8279d1e", + "$RASPUTIN_DATA/DTM10_UTM33_20260925": "dir:3eef3cfed8b6d7db435791da65386cc1dcbbbadc2a9b145567765eb67b1a3dac", + "$RASPUTIN_DATA/numedalslagen_outline_nve.geojson": "file:554438:3069b1dbd2c22c87401fd57c3189f5a67d319f9aea0a95e90cc29886fa0dabca", + "$RASPUTIN_DATA/corine2018_dtm10_utm33.gpkg": "file:440332288:1790574665364371436", + "$RASPUTIN_DATA/sweden_glo30_cache": "dir:3deaa80f96e39f4ae4b4d3df54646b17e47c70847fe450d9c3e377fe7d758e0f", + "$RASPUTIN_DATA/sweden_smhi_svar/lagan_mouth_svar2022_3006.geojson": "file:1294446:a93a524fc198bd3e516ea36db55734c3e5de3994be05e19c807ce5546454123e", + "$RASPUTIN_DATA/corine_sql/u2018_clc2018_v2020_20u1_geoPackage/DATA/U2018_CLC2018_V2020_20u1.gpkg": "file:8855322624:1587553228000000000", + "$RASPUTIN_DATA/banenor_banenettverk/section_domain.geojson": "file:2337:d3af5872ac6da011cc95eaade2328cfff965116a5756f68786cd1b1907d5e571", + "$RASPUTIN_DATA/banenor_banenettverk/bergensbanen.geojson": "file:2195883:75e725295b777190f1a1be72fc99eeb2689bff59c1e23e99eaa3339bbcfea2af" + }, + "cases": { + "tile": { + "tolerance": 1.0, + "max_error": 0.9999759197235107, + "mesh_sha256": "746bf5c68bb9464fbb0746a9de6e0a5dbc7a15d1bfa1006cd9000f95d870ee0e", + "measures": { + "total": { + "median": 0.426248417, + "min": 0.422540292, + "max": 0.433158584 + }, + "decode": { + "median": 0.05966025, + "min": 0.057878042, + "max": 0.060832292 + }, + "start mesh: build": { + "median": 0.000183084, + "min": 0.000171833, + "max": 0.000214875 + }, + "start mesh: node": { + "median": 0.002701458, + "min": 0.002641792, + "max": 0.002822166 + }, + "start mesh: triangulate": { + "median": 0.029711208, + "min": 0.029615208, + "max": 0.029747167 + }, + "start mesh: constraint edges": { + "median": 0.006761542, + "min": 0.006693958, + "max": 0.0071395 + }, + "refine": { + "median": 0.194554125, + "min": 0.191826458, + "max": 0.201449375 + }, + "refine: legalise start": { + "median": 0.000468041, + "min": 0.00045375, + "max": 0.000481917 + }, + "refine: start quality": { + "median": 0.000755, + "min": 0.00072225, + "max": 0.000762917 + }, + "refine: scan (parallel)": { + "median": 0.060304711999999996, + "min": 0.05898374699999998, + "max": 0.06622083100000004 + }, + "refine: split + flip (serial)": { + "median": 0.10658016900000003, + "min": 0.10522587399999997, + "max": 0.10835129300000002 + }, + "refine: setup + output": { + "median": 0.025642708999999958, + "min": 0.02543711899999998, + "max": 0.025923125000000047 + }, + "edge strip: generate": { + "median": 0.000767625, + "min": 0.000732542, + "max": 0.000779125 + }, + "edge strip: scan (parallel)": { + "median": 0.001071959, + "min": 0.000983542, + "max": 0.001100666 + }, + "edge strip: split + flip (serial)": { + "median": 0.001055834, + "min": 0.001049375, + "max": 0.001213833 + }, + "trim": { + "median": 0.01740075, + "min": 0.017225209, + "max": 0.017737541 + }, + "write: encode": { + "median": 0.009541541, + "min": 0.009430542, + "max": 0.009792625 + }, + "write: disk": { + "median": 0.003118666, + "min": 0.002838583, + "max": 0.003476833 + } + } + }, + "tile t=1": { + "tolerance": 1.0, + "max_error": 0.9999759197235107, + "mesh_sha256": "746bf5c68bb9464fbb0746a9de6e0a5dbc7a15d1bfa1006cd9000f95d870ee0e", + "measures": { + "total": { + "median": 0.766163417, + "min": 0.754440958, + "max": 0.77470325 + }, + "decode": { + "median": 0.06151975, + "min": 0.060308375, + "max": 0.063273 + }, + "start mesh: build": { + "median": 0.000172416, + "min": 0.000166, + "max": 0.0001855 + }, + "start mesh: node": { + "median": 0.002642625, + "min": 0.002634542, + "max": 0.002770292 + }, + "start mesh: triangulate": { + "median": 0.029689458, + "min": 0.029610459, + "max": 0.029954458 + }, + "start mesh: constraint edges": { + "median": 0.00674875, + "min": 0.00665425, + "max": 0.007293458 + }, + "refine": { + "median": 0.531490584, + "min": 0.519743333, + "max": 0.540774583 + }, + "refine: legalise start": { + "median": 0.000457791, + "min": 0.000456, + "max": 0.000483208 + }, + "refine: start quality": { + "median": 0.000743917, + "min": 0.000734208, + "max": 0.000772084 + }, + "refine: scan (parallel)": { + "median": 0.39491675099999984, + "min": 0.38593720399999987, + "max": 0.40330233199999993 + }, + "refine: split + flip (serial)": { + "median": 0.10981804099999998, + "min": 0.10696266200000004, + "max": 0.11104166500000001 + }, + "refine: setup + output": { + "median": 0.025269741000000123, + "min": 0.025162204999999993, + "max": 0.02564355000000007 + }, + "edge strip: generate": { + "median": 0.00075725, + "min": 0.000698959, + "max": 0.000816625 + }, + "edge strip: scan (parallel)": { + "median": 0.0010255, + "min": 0.000983917, + "max": 0.001127708 + }, + "edge strip: split + flip (serial)": { + "median": 0.001072875, + "min": 0.001024167, + "max": 0.001136083 + }, + "trim": { + "median": 0.0177295, + "min": 0.017570875, + "max": 0.018028458 + }, + "write: encode": { + "median": 0.009751292, + "min": 0.009563, + "max": 0.009902709 + }, + "write: disk": { + "median": 0.003262, + "min": 0.003129959, + "max": 0.003448042 + } + } + }, + "quarter": { + "tolerance": 1.0, + "max_error": 0.9999796549479072, + "mesh_sha256": "35a2b7bfda09e5b2f2912d33709889b11bb65af2d8e644ecfb28d24191d57fa4", + "measures": { + "total": { + "median": 0.369215875, + "min": 0.365587917, + "max": 0.374968833 + }, + "domain read": { + "median": 0.001789792, + "min": 0.001676125, + "max": 0.001863959 + }, + "decode": { + "median": 0.048041875, + "min": 0.046741583, + "max": 0.048493208 + }, + "start mesh: build": { + "median": 0.000127333, + "min": 0.000123416, + "max": 0.000130708 + }, + "start mesh: node": { + "median": 0.000479042, + "min": 0.000469167, + "max": 0.000490833 + }, + "start mesh: triangulate": { + "median": 0.000449625, + "min": 0.00044175, + "max": 0.000450458 + }, + "start mesh: constraint edges": { + "median": 0.000584042, + "min": 0.00057975, + "max": 0.000589083 + }, + "refine": { + "median": 0.17341, + "min": 0.172948583, + "max": 0.175918875 + }, + "refine: legalise start": { + "median": 0.000013584, + "min": 0.000013375, + "max": 0.000014208 + }, + "refine: start quality": { + "median": 0.001615667, + "min": 0.001611041, + "max": 0.001659458 + }, + "refine: scan (parallel)": { + "median": 0.04892133100000001, + "min": 0.048453836, + "max": 0.051456996 + }, + "refine: split + flip (serial)": { + "median": 0.10499953800000002, + "min": 0.10304691599999996, + "max": 0.10835187700000004 + }, + "refine: setup + output": { + "median": 0.017403088999999955, + "min": 0.017232422000000053, + "max": 0.017489540000000026 + }, + "edge strip: generate": { + "median": 0.000795417, + "min": 0.000774833, + "max": 0.000811958 + }, + "edge strip: scan (parallel)": { + "median": 0.0017274999999999999, + "min": 0.000925749, + "max": 0.002018959 + }, + "edge strip: split + flip (serial)": { + "median": 0.002377834, + "min": 0.0022243329999999998, + "max": 0.002433625 + }, + "trim": { + "median": 0.0162075, + "min": 0.016153417, + "max": 0.016484417 + }, + "write: encode": { + "median": 0.010022292, + "min": 0.00958075, + "max": 0.010436166 + }, + "write: disk": { + "median": 0.003267916, + "min": 0.0031585, + "max": 0.008186042 + } + } + }, + "numedalslagen": { + "tolerance": 10.0, + "max_error": 10.0, + "mesh_sha256": "7101ea0017bbf046b83d0a3a68bc2414207e1f82fd2f5dc785dae570451fc5f7", + "measures": { + "total": { + "median": 9.319828125, + "min": 9.08900075, + "max": 9.390277 + }, + "domain read": { + "median": 0.014418375, + "min": 0.013902833, + "max": 0.015087 + }, + "decode": { + "median": 0.963524625, + "min": 0.951329542, + "max": 0.968175083 + }, + "features read": { + "median": 0.20673449069727212, + "min": 0.20268483343534172, + "max": 0.24182136228773743 + }, + "features clip": { + "median": 6.381064217304811, + "min": 6.226515041547827, + "max": 6.422088137711398 + }, + "features clip: clean-up": { + "median": 6.3155588750960305, + "min": 6.161075167008676, + "max": 6.3574698329903185 + }, + "start mesh: build": { + "median": 0.001826917, + "min": 0.001761792, + "max": 0.001890625 + }, + "start mesh: node": { + "median": 0.152376625, + "min": 0.146443167, + "max": 0.154151333 + }, + "start mesh: triangulate": { + "median": 0.040957916, + "min": 0.039655875, + "max": 0.041027209 + }, + "start mesh: constraint edges": { + "median": 0.095955125, + "min": 0.092260709, + "max": 0.097355584 + }, + "refine": { + "median": 0.687798667, + "min": 0.685782541, + "max": 0.706340916 + }, + "refine: legalise start": { + "median": 0.002131208, + "min": 0.002125291, + "max": 0.00213325 + }, + "refine: start quality": { + "median": 0.035854167, + "min": 0.0347155, + "max": 0.03702375 + }, + "refine: scan (parallel)": { + "median": 0.42000687600000003, + "min": 0.41982974899999986, + "max": 0.43269704200000003 + }, + "refine: split + flip (serial)": { + "median": 0.176764292, + "min": 0.17642449899999998, + "max": 0.18176062500000004 + }, + "refine: setup + output": { + "median": 0.052726249, + "min": 0.05250445800000003, + "max": 0.05322516800000021 + }, + "edge strip: generate": { + "median": 0.101228292, + "min": 0.100354708, + "max": 0.106941334 + }, + "edge strip: scan (parallel)": { + "median": 0.008402541, + "min": 0.007941959, + "max": 0.008894083999999998 + }, + "edge strip: split + flip (serial)": { + "median": 0.007575293, + "min": 0.007481750999999999, + "max": 0.007796458000000001 + }, + "trim": { + "median": 0.026461958, + "min": 0.026431375, + "max": 0.027123334 + }, + "land cover": { + "median": 0.298948083, + "min": 0.298137583, + "max": 0.300605958 + }, + "write: encode": { + "median": 0.0099045, + "min": 0.009900708, + "max": 0.009927875 + }, + "write: disk": { + "median": 0.006453917, + "min": 0.006255375, + "max": 0.006639917 + } + } + }, + "lagan": { + "tolerance": 10.0, + "max_error": 9.999904677002576, + "mesh_sha256": "c0f3b555ddaffac4d38332b7f11a149e11e2039a66db6ff98695bf297a043d28", + "measures": { + "total": { + "median": 20.029154708, + "min": 19.544096708, + "max": 20.053208667 + }, + "domain read": { + "median": 0.044091958, + "min": 0.042828958, + "max": 0.044938584 + }, + "decode": { + "median": 4.672758167, + "min": 4.58275375, + "max": 4.761679208 + }, + "features read": { + "median": 0.988476540427655, + "min": 0.9600067179417238, + "max": 1.0180120061850175 + }, + "features clip": { + "median": 11.521122410777025, + "min": 11.260383532033302, + "max": 11.563800501520745 + }, + "features clip: clean-up": { + "median": 7.3749810410663486, + "min": 7.168419042020105, + "max": 7.419166082981974 + }, + "start mesh: build": { + "median": 0.00308475, + "min": 0.003062875, + "max": 0.003136875 + }, + "start mesh: node": { + "median": 0.308431458, + "min": 0.30796725, + "max": 0.310221375 + }, + "start mesh: triangulate": { + "median": 0.087955416, + "min": 0.08777275, + "max": 0.088827042 + }, + "start mesh: constraint edges": { + "median": 0.183405334, + "min": 0.179390875, + "max": 0.187215708 + }, + "refine": { + "median": 0.244875625, + "min": 0.241119625, + "max": 0.249743667 + }, + "refine: legalise start": { + "median": 0.004397, + "min": 0.004369291, + "max": 0.0044485 + }, + "refine: start quality": { + "median": 0.063105375, + "min": 0.062320208, + "max": 0.064115292 + }, + "refine: scan (parallel)": { + "median": 0.06207233399999999, + "min": 0.06014587199999998, + "max": 0.063716962 + }, + "refine: split + flip (serial)": { + "median": 0.05794458399999999, + "min": 0.05706845499999999, + "max": 0.060485624999999994 + }, + "refine: setup + output": { + "median": 0.05713659000000004, + "min": 0.05705649700000001, + "max": 0.05735633200000001 + }, + "edge strip: generate": { + "median": 0.0426905, + "min": 0.041255041, + "max": 0.042844084 + }, + "check points: project": { + "median": 0.6719232480973005, + "min": 0.6691897064447403, + "max": 0.7032833365956321 + }, + "check points: store": { + "median": 0.27395683037376406, + "min": 0.2739567945492683, + "max": 0.28620025088038614 + }, + "final check: scan (parallel)": { + "median": 0.295717082, + "min": 0.276256333, + "max": 0.29946641399999996 + }, + "final check: split + flip (serial)": { + "median": 0.041169209, + "min": 0.040370665000000014, + "max": 0.041375831999999994 + }, + "trim": { + "median": 0.0193365, + "min": 0.019061458, + "max": 0.019765083 + }, + "land cover": { + "median": 0.212721625, + "min": 0.212272958, + "max": 0.213426583 + }, + "write: encode": { + "median": 0.008230125, + "min": 0.008147125, + "max": 0.0083425 + }, + "write: disk": { + "median": 0.004138417, + "min": 0.003757, + "max": 0.004159166 + } + } + }, + "geilo-al-ramp": { + "tolerance": 20.0, + "max_error": 19.984358261826856, + "mesh_sha256": "9f01b9ac27e2d2d5d1970e69a174995ebf99e6582adc17e05b257c8bd3f4348e", + "measures": { + "total": { + "median": 0.33549, + "min": 0.332769708, + "max": 0.336389542 + }, + "domain read": { + "median": 0.001267417, + "min": 0.001257875, + "max": 0.00131675 + }, + "decode": { + "median": 0.141405167, + "min": 0.1401925, + "max": 0.142964458 + }, + "start mesh: build": { + "median": 0.000133791, + "min": 0.00012525, + "max": 0.000134166 + }, + "start mesh: node": { + "median": 0.000074625, + "min": 0.000070917, + "max": 0.000080333 + }, + "start mesh: triangulate": { + "median": 0.000035334, + "min": 0.000032458, + "max": 0.000041292 + }, + "start mesh: constraint edges": { + "median": 0.000092666, + "min": 0.000088167, + "max": 0.000098208 + }, + "refine": { + "median": 0.101289167, + "min": 0.1012, + "max": 0.101476208 + }, + "refine: legalise start": { + "median": 3.125e-6, + "min": 3.083e-6, + "max": 3.166e-6 + }, + "refine: start quality": { + "median": 0.000160959, + "min": 0.000156167, + "max": 0.000163208 + }, + "refine: scan (parallel)": { + "median": 0.083470917, + "min": 0.08344346000000002, + "max": 0.08387866699999999 + }, + "refine: split + flip (serial)": { + "median": 0.014618003, + "min": 0.014584709999999997, + "max": 0.014793706 + }, + "refine: setup + output": { + "median": 0.0028879589999999677, + "min": 0.002820205000000006, + "max": 0.0029780400000000012 + }, + "edge strip: generate": { + "median": 0.000569083, + "min": 0.0005655, + "max": 0.000574167 + }, + "edge strip: scan (parallel)": { + "median": 0.000678749, + "min": 0.000678042, + "max": 0.000706417 + }, + "edge strip: split + flip (serial)": { + "median": 0.000311083, + "min": 0.000283875, + "max": 0.000367 + }, + "trim": { + "median": 0.002268167, + "min": 0.002242417, + "max": 0.002375041 + }, + "write: encode": { + "median": 0.004307917, + "min": 0.004207333, + "max": 0.004519 + }, + "write: disk": { + "median": 0.000676875, + "min": 0.000650291, + "max": 0.002070166 + } + } + }, + "romsdal-slope": { + "tolerance": 10.0, + "max_error": 9.999908093780277, + "mesh_sha256": "992e49cbcbe4a781ff03783a820946b163869b2b4a1f02bb70927d94224c4197", + "measures": { + "total": { + "median": 3.748047042, + "min": 3.744094625, + "max": 3.750099458 + }, + "decode": { + "median": 0.069542083, + "min": 0.067977792, + "max": 0.069829875 + }, + "start mesh: build": { + "median": 0.000177, + "min": 0.000173917, + "max": 0.000182041 + }, + "start mesh: node": { + "median": 0.002635417, + "min": 0.002621875, + "max": 0.002652875 + }, + "start mesh: triangulate": { + "median": 0.029662667, + "min": 0.029283542, + "max": 0.02979 + }, + "start mesh: constraint edges": { + "median": 0.006619541, + "min": 0.006530917, + "max": 0.006811625 + }, + "slope": { + "median": 0.106585417, + "min": 0.106283833, + "max": 0.106588167 + }, + "refine": { + "median": 2.094340209, + "min": 2.088409625, + "max": 2.110542 + }, + "refine: legalise start": { + "median": 0.000473875, + "min": 0.000465459, + "max": 0.000478708 + }, + "refine: start quality": { + "median": 0.000742166, + "min": 0.000741875, + "max": 0.000745458 + }, + "refine: scan (parallel)": { + "median": 0.5889751630000001, + "min": 0.5851259120000002, + "max": 0.5930273319999999 + }, + "refine: split + flip (serial)": { + "median": 1.246296999, + "min": 1.2381513369999997, + "max": 1.2518195399999998 + }, + "refine: setup + output": { + "median": 0.26169796499999975, + "min": 0.26007550000000035, + "max": 0.26447454500000034 + }, + "edge strip: generate": { + "median": 0.001103084, + "min": 0.001088708, + "max": 0.00111325 + }, + "edge strip: scan (parallel)": { + "median": 0.009732792, + "min": 0.009629292999999997, + "max": 0.009817666 + }, + "edge strip: split + flip (serial)": { + "median": 0.014966249000000001, + "min": 0.012987249000000001, + "max": 0.015902999 + }, + "trim": { + "median": 0.120276917, + "min": 0.119419334, + "max": 0.120587958 + }, + "write: encode": { + "median": 0.047643209, + "min": 0.044522458, + "max": 0.049097916 + }, + "write: disk": { + "median": 0.062439083, + "min": 0.039847042, + "max": 0.067411208 + } + } + } + } +} diff --git a/docs/benchmarks/2026-10-11/gpkg-out/quick/head-run.log b/docs/benchmarks/2026-10-11/gpkg-out/quick/head-run.log new file mode 100644 index 00000000..f450b387 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/quick/head-run.log @@ -0,0 +1,12 @@ +HOTSPOT: tile refine 46 % of the run (not in hotspots.toml) +HOTSPOT: tile t=1 refine 69 % of the run (not in hotspots.toml) +HOTSPOT: tile t=1 refine: scan (parallel) 52 % 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 68 % of the run (not in hotspots.toml) +HOTSPOT: lagan features clip 58 % of the run (ruled at 41 %, grown 10 points or more) +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) +baseline saved: /Users/skavhaug/projects/rasputin/.claude/worktrees/gpkg-out/docs/benchmarks/quick/baseline-ac.json +NO CHANGE +exit 0 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/quick/master-357808b1-baseline.json b/docs/benchmarks/2026-10-11/gpkg-out/quick/master-357808b1-baseline.json new file mode 100644 index 00000000..c4e09b53 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/quick/master-357808b1-baseline.json @@ -0,0 +1,785 @@ +{ + "commit": "357808b1d23462b1ff5c71760a0a9943fff76dd4", + "machine": { + "cpu_brand": "Apple M1 Max", + "p_cores": 8, + "e_cores": 2, + "memory_bytes": 34359738368, + "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" + }, + "power": "ac", + "inputs": { + "$TREE/tests/fixtures/dem_archive/7908_3_10m_z33.tif": "file:4426661:aabd0cbc28471ce8593e4c278381811bec3fbf4e1194058b3b4888d00c3af575", + "$TREE/docs/benchmarks/2026-09-26/quarter.geojson": "file:16426:b7f7bbe96848383c77863ce29ee28c45bce087601a1738ee82ddb4ffb8279d1e", + "$RASPUTIN_DATA/DTM10_UTM33_20260925": "dir:3eef3cfed8b6d7db435791da65386cc1dcbbbadc2a9b145567765eb67b1a3dac", + "$RASPUTIN_DATA/numedalslagen_outline_nve.geojson": "file:554438:3069b1dbd2c22c87401fd57c3189f5a67d319f9aea0a95e90cc29886fa0dabca", + "$RASPUTIN_DATA/corine2018_dtm10_utm33.gpkg": "file:440332288:1790574665364371436", + "$RASPUTIN_DATA/sweden_glo30_cache": "dir:3deaa80f96e39f4ae4b4d3df54646b17e47c70847fe450d9c3e377fe7d758e0f", + "$RASPUTIN_DATA/sweden_smhi_svar/lagan_mouth_svar2022_3006.geojson": "file:1294446:a93a524fc198bd3e516ea36db55734c3e5de3994be05e19c807ce5546454123e", + "$RASPUTIN_DATA/corine_sql/u2018_clc2018_v2020_20u1_geoPackage/DATA/U2018_CLC2018_V2020_20u1.gpkg": "file:8855322624:1587553228000000000", + "$RASPUTIN_DATA/banenor_banenettverk/section_domain.geojson": "file:2337:d3af5872ac6da011cc95eaade2328cfff965116a5756f68786cd1b1907d5e571", + "$RASPUTIN_DATA/banenor_banenettverk/bergensbanen.geojson": "file:2195883:75e725295b777190f1a1be72fc99eeb2689bff59c1e23e99eaa3339bbcfea2af" + }, + "cases": { + "tile": { + "tolerance": 1.0, + "max_error": 0.9999759197235107, + "mesh_sha256": "746bf5c68bb9464fbb0746a9de6e0a5dbc7a15d1bfa1006cd9000f95d870ee0e", + "measures": { + "total": { + "median": 0.415963875, + "min": 0.409482542, + "max": 0.418569459 + }, + "decode": { + "median": 0.060134583, + "min": 0.058657334, + "max": 0.061936625 + }, + "start mesh: build": { + "median": 0.00017525, + "min": 0.000171709, + "max": 0.0002175 + }, + "start mesh: node": { + "median": 0.002629333, + "min": 0.002613791, + "max": 0.002672584 + }, + "start mesh: triangulate": { + "median": 0.029249291, + "min": 0.029212459, + "max": 0.029342375 + }, + "start mesh: constraint edges": { + "median": 0.006628292, + "min": 0.006577917, + "max": 0.006648291 + }, + "refine": { + "median": 0.18963575, + "min": 0.1840335, + "max": 0.191952709 + }, + "refine: legalise start": { + "median": 0.000456459, + "min": 0.000451666, + "max": 0.000465125 + }, + "refine: start quality": { + "median": 0.000719125, + "min": 0.000708791, + "max": 0.000743458 + }, + "refine: scan (parallel)": { + "median": 0.05605558699999999, + "min": 0.05550579000000001, + "max": 0.056878839 + }, + "refine: split + flip (serial)": { + "median": 0.10617537000000003, + "min": 0.10221883000000001, + "max": 0.10878087500000003 + }, + "refine: setup + output": { + "median": 0.025143629999999972, + "min": 0.025011369000000006, + "max": 0.025580412999999996 + }, + "edge strip: generate": { + "median": 0.000753459, + "min": 0.000740833, + "max": 0.00078125 + }, + "edge strip: scan (parallel)": { + "median": 0.001003834, + "min": 0.000992792, + "max": 0.00103425 + }, + "edge strip: split + flip (serial)": { + "median": 0.001039083, + "min": 0.001022792, + "max": 0.001111 + }, + "trim": { + "median": 0.017144458, + "min": 0.017099417, + "max": 0.017305166 + }, + "write: encode": { + "median": 0.009446042, + "min": 0.009401416, + "max": 0.009483459 + }, + "write: disk": { + "median": 0.003253791, + "min": 0.00285225, + "max": 0.003490208 + } + } + }, + "tile t=1": { + "tolerance": 1.0, + "max_error": 0.9999759197235107, + "mesh_sha256": "746bf5c68bb9464fbb0746a9de6e0a5dbc7a15d1bfa1006cd9000f95d870ee0e", + "measures": { + "total": { + "median": 0.699345042, + "min": 0.698522291, + "max": 0.701520208 + }, + "decode": { + "median": 0.060461875, + "min": 0.059979042, + "max": 0.063467542 + }, + "start mesh: build": { + "median": 0.000171958, + "min": 0.000166083, + "max": 0.00019175 + }, + "start mesh: node": { + "median": 0.002638208, + "min": 0.002625833, + "max": 0.0026535 + }, + "start mesh: triangulate": { + "median": 0.029259584, + "min": 0.029204292, + "max": 0.029281666 + }, + "start mesh: constraint edges": { + "median": 0.006600167, + "min": 0.0065755, + "max": 0.006606167 + }, + "refine": { + "median": 0.471695084, + "min": 0.471505292, + "max": 0.472082 + }, + "refine: legalise start": { + "median": 0.000455292, + "min": 0.000454583, + "max": 0.00045675 + }, + "refine: start quality": { + "median": 0.000742, + "min": 0.000711083, + "max": 0.000778792 + }, + "refine: scan (parallel)": { + "median": 0.3518835419999999, + "min": 0.3518694170000001, + "max": 0.35251199799999994 + }, + "refine: split + flip (serial)": { + "median": 0.09404675200000004, + "min": 0.09354608300000002, + "max": 0.094215045 + }, + "refine: setup + output": { + "median": 0.024436711000000055, + "min": 0.024414089000000083, + "max": 0.024481247000000095 + }, + "edge strip: generate": { + "median": 0.000700625, + "min": 0.000687833, + "max": 0.000704458 + }, + "edge strip: scan (parallel)": { + "median": 0.001016375, + "min": 0.000999375, + "max": 0.001025667 + }, + "edge strip: split + flip (serial)": { + "median": 0.001055708, + "min": 0.001019875, + "max": 0.00107525 + }, + "trim": { + "median": 0.017234708, + "min": 0.017102458, + "max": 0.017310542 + }, + "write: encode": { + "median": 0.009445458, + "min": 0.009403834, + "max": 0.00954925 + }, + "write: disk": { + "median": 0.003674875, + "min": 0.003261875, + "max": 0.00372575 + } + } + }, + "quarter": { + "tolerance": 1.0, + "max_error": 0.9999796549479072, + "mesh_sha256": "35a2b7bfda09e5b2f2912d33709889b11bb65af2d8e644ecfb28d24191d57fa4", + "measures": { + "total": { + "median": 0.355239917, + "min": 0.346618459, + "max": 0.35728825 + }, + "domain read": { + "median": 0.001567958, + "min": 0.001559916, + "max": 0.001584708 + }, + "decode": { + "median": 0.0437335, + "min": 0.043059334, + "max": 0.045552 + }, + "start mesh: build": { + "median": 0.000126667, + "min": 0.00012575, + "max": 0.000133417 + }, + "start mesh: node": { + "median": 0.00048, + "min": 0.00047375, + "max": 0.000529166 + }, + "start mesh: triangulate": { + "median": 0.000447916, + "min": 0.000446041, + "max": 0.000451416 + }, + "start mesh: constraint edges": { + "median": 0.000590333, + "min": 0.000585917, + "max": 0.000596 + }, + "refine": { + "median": 0.171971959, + "min": 0.163373125, + "max": 0.173065167 + }, + "refine: legalise start": { + "median": 0.000013334, + "min": 0.000013167, + "max": 0.000013584 + }, + "refine: start quality": { + "median": 0.001615333, + "min": 0.001604708, + "max": 0.001626708 + }, + "refine: scan (parallel)": { + "median": 0.045102876999999986, + "min": 0.04491041599999999, + "max": 0.046273589999999996 + }, + "refine: split + flip (serial)": { + "median": 0.10733411799999998, + "min": 0.100114457, + "max": 0.10843924999999997 + }, + "refine: setup + output": { + "median": 0.01693670700000005, + "min": 0.01653312400000001, + "max": 0.017424415 + }, + "edge strip: generate": { + "median": 0.000807083, + "min": 0.000757959, + "max": 0.000810708 + }, + "edge strip: scan (parallel)": { + "median": 0.001058167, + "min": 0.000933, + "max": 0.001106125 + }, + "edge strip: split + flip (serial)": { + "median": 0.0024928330000000003, + "min": 0.0022938340000000002, + "max": 0.002553334 + }, + "trim": { + "median": 0.015983791, + "min": 0.015893708, + "max": 0.01600575 + }, + "write: encode": { + "median": 0.009495291, + "min": 0.009368959, + "max": 0.00968125 + }, + "write: disk": { + "median": 0.003561917, + "min": 0.003426458, + "max": 0.003709792 + } + } + }, + "numedalslagen": { + "tolerance": 10.0, + "max_error": 10.0, + "mesh_sha256": "7101ea0017bbf046b83d0a3a68bc2414207e1f82fd2f5dc785dae570451fc5f7", + "measures": { + "total": { + "median": 8.829228584, + "min": 8.828564375, + "max": 8.835614083 + }, + "domain read": { + "median": 0.013599625, + "min": 0.013553, + "max": 0.013614917 + }, + "decode": { + "median": 0.920596416, + "min": 0.918978959, + "max": 0.92552275 + }, + "features read": { + "median": 0.1973755065118894, + "min": 0.19701432588044554, + "max": 0.200384576106444 + }, + "features clip": { + "median": 6.000404131948017, + "min": 5.996730951475911, + "max": 6.009083757176995 + }, + "features clip: clean-up": { + "median": 5.936553500010632, + "min": 5.932732084067538, + "max": 5.9448600829346105 + }, + "start mesh: build": { + "median": 0.001760542, + "min": 0.001760292, + "max": 0.001769292 + }, + "start mesh: node": { + "median": 0.147264375, + "min": 0.14577075, + "max": 0.14733075 + }, + "start mesh: triangulate": { + "median": 0.039687042, + "min": 0.039634416, + "max": 0.039709917 + }, + "start mesh: constraint edges": { + "median": 0.093702042, + "min": 0.09296875, + "max": 0.094190375 + }, + "refine": { + "median": 0.659033375, + "min": 0.655672125, + "max": 0.662185 + }, + "refine: legalise start": { + "median": 0.002126833, + "min": 0.002125083, + "max": 0.002181334 + }, + "refine: start quality": { + "median": 0.035552959, + "min": 0.034938, + "max": 0.035729083 + }, + "refine: scan (parallel)": { + "median": 0.39490754, + "min": 0.3946743339999999, + "max": 0.395629583 + }, + "refine: split + flip (serial)": { + "median": 0.17402166800000002, + "min": 0.170890834, + "max": 0.176348416 + }, + "refine: setup + output": { + "median": 0.052426956000000136, + "min": 0.05208687500000009, + "max": 0.05325100199999999 + }, + "edge strip: generate": { + "median": 0.098305666, + "min": 0.098302167, + "max": 0.098961 + }, + "edge strip: scan (parallel)": { + "median": 0.008010208999999999, + "min": 0.007995292, + "max": 0.008590707999999999 + }, + "edge strip: split + flip (serial)": { + "median": 0.007330208999999999, + "min": 0.007145334, + "max": 0.007409542000000001 + }, + "trim": { + "median": 0.025855083, + "min": 0.025761208, + "max": 0.025908042 + }, + "land cover": { + "median": 0.295597333, + "min": 0.295216375, + "max": 0.295728834 + }, + "write: encode": { + "median": 0.009886292, + "min": 0.009834709, + "max": 0.009888417 + }, + "write: disk": { + "median": 0.006146209, + "min": 0.006043, + "max": 0.012273958 + } + } + }, + "lagan": { + "tolerance": 10.0, + "max_error": 9.999904677002576, + "mesh_sha256": "c0f3b555ddaffac4d38332b7f11a149e11e2039a66db6ff98695bf297a043d28", + "measures": { + "total": { + "median": 14.542084583, + "min": 14.535847917, + "max": 14.551780625 + }, + "domain read": { + "median": 0.042737541, + "min": 0.042447125, + "max": 0.043427792 + }, + "decode": { + "median": 4.578057291, + "min": 4.565632417, + "max": 4.584270167 + }, + "features read": { + "median": 0.921586470445618, + "min": 0.9120247770333663, + "max": 0.963109087315388 + }, + "features clip": { + "median": 6.342146404553205, + "min": 6.338787996675819, + "max": 6.34631439007353 + }, + "features clip: clean-up": { + "median": 5.392329290974885, + "min": 5.391268541919999, + "max": 5.398513959022239 + }, + "start mesh: build": { + "median": 0.003107875, + "min": 0.003080167, + "max": 0.003132375 + }, + "start mesh: node": { + "median": 0.306133667, + "min": 0.305732583, + "max": 0.306136959 + }, + "start mesh: triangulate": { + "median": 0.087295375, + "min": 0.087039208, + "max": 0.087431208 + }, + "start mesh: constraint edges": { + "median": 0.190978042, + "min": 0.190633125, + "max": 0.191376125 + }, + "refine": { + "median": 0.237921625, + "min": 0.237089709, + "max": 0.240623083 + }, + "refine: legalise start": { + "median": 0.004321209, + "min": 0.00430625, + "max": 0.004351125 + }, + "refine: start quality": { + "median": 0.063313916, + "min": 0.062647959, + "max": 0.063466458 + }, + "refine: scan (parallel)": { + "median": 0.05935803999999999, + "min": 0.059079792, + "max": 0.059541455 + }, + "refine: split + flip (serial)": { + "median": 0.054421622, + "min": 0.05414283399999999, + "max": 0.057798166000000005 + }, + "refine: setup + output": { + "median": 0.05646462800000002, + "min": 0.055953169999999997, + "max": 0.05646779299999999 + }, + "edge strip: generate": { + "median": 0.040602, + "min": 0.040267959, + "max": 0.040666834 + }, + "check points: project": { + "median": 0.6664140454959124, + "min": 0.6445603384636343, + "max": 0.6678974126698449 + }, + "check points: store": { + "median": 0.2739846293001653, + "min": 0.272586036511269, + "max": 0.27819728855660675 + }, + "final check: scan (parallel)": { + "median": 0.264481997, + "min": 0.262131541, + "max": 0.2656331669999999 + }, + "final check: split + flip (serial)": { + "median": 0.038177456, + "min": 0.037784956999999994, + "max": 0.03839912400000001 + }, + "trim": { + "median": 0.018914791, + "min": 0.018853458, + "max": 0.018928833 + }, + "land cover": { + "median": 0.211289208, + "min": 0.21093725, + "max": 0.211618625 + }, + "write: encode": { + "median": 0.008228792, + "min": 0.008190625, + "max": 0.008303584 + }, + "write: disk": { + "median": 0.004085541, + "min": 0.003913208, + "max": 0.0040945 + } + } + }, + "geilo-al-ramp": { + "tolerance": 20.0, + "max_error": 19.984358261826856, + "mesh_sha256": "9f01b9ac27e2d2d5d1970e69a174995ebf99e6582adc17e05b257c8bd3f4348e", + "measures": { + "total": { + "median": 0.328736709, + "min": 0.327646541, + "max": 0.331091417 + }, + "domain read": { + "median": 0.001223958, + "min": 0.001193125, + "max": 0.001237209 + }, + "decode": { + "median": 0.13842975, + "min": 0.138245292, + "max": 0.138948709 + }, + "start mesh: build": { + "median": 0.0001265, + "min": 0.000120417, + "max": 0.000132583 + }, + "start mesh: node": { + "median": 0.000077, + "min": 0.000072459, + "max": 0.000078542 + }, + "start mesh: triangulate": { + "median": 0.000032459, + "min": 0.000030375, + "max": 0.00003275 + }, + "start mesh: constraint edges": { + "median": 0.000096292, + "min": 0.000084584, + "max": 0.000099375 + }, + "refine": { + "median": 0.099222708, + "min": 0.099139917, + "max": 0.099541917 + }, + "refine: legalise start": { + "median": 3.084e-6, + "min": 3e-6, + "max": 3.125e-6 + }, + "refine: start quality": { + "median": 0.000154041, + "min": 0.000151959, + "max": 0.000155 + }, + "refine: scan (parallel)": { + "median": 0.08256295899999999, + "min": 0.082561748, + "max": 0.08276754300000001 + }, + "refine: split + flip (serial)": { + "median": 0.013793952, + "min": 0.013646123000000001, + "max": 0.013868874000000002 + }, + "refine: setup + output": { + "median": 0.0027748799999999935, + "min": 0.0026358320000000185, + "max": 0.0028224219999999772 + }, + "edge strip: generate": { + "median": 0.000566792, + "min": 0.000533875, + "max": 0.000601084 + }, + "edge strip: scan (parallel)": { + "median": 0.0006741659999999999, + "min": 0.000641333, + "max": 0.00072825 + }, + "edge strip: split + flip (serial)": { + "median": 0.000327834, + "min": 0.000243999, + "max": 0.000373084 + }, + "trim": { + "median": 0.002361208, + "min": 0.002238458, + "max": 0.002452459 + }, + "write: encode": { + "median": 0.004143583, + "min": 0.004069917, + "max": 0.004341125 + }, + "write: disk": { + "median": 0.000667291, + "min": 0.000614709, + "max": 0.00070875 + } + } + }, + "romsdal-slope": { + "tolerance": 10.0, + "max_error": 9.999908093780277, + "mesh_sha256": "992e49cbcbe4a781ff03783a820946b163869b2b4a1f02bb70927d94224c4197", + "measures": { + "total": { + "median": 3.578062333, + "min": 3.560890084, + "max": 3.61971725 + }, + "decode": { + "median": 0.067789542, + "min": 0.066407958, + "max": 0.070348 + }, + "start mesh: build": { + "median": 0.000172417, + "min": 0.000166208, + "max": 0.000173292 + }, + "start mesh: node": { + "median": 0.002643709, + "min": 0.002630709, + "max": 0.002646917 + }, + "start mesh: triangulate": { + "median": 0.029220125, + "min": 0.029195625, + "max": 0.029241125 + }, + "start mesh: constraint edges": { + "median": 0.006545333, + "min": 0.00654325, + "max": 0.006562417 + }, + "slope": { + "median": 0.105743292, + "min": 0.105629208, + "max": 0.106262166 + }, + "refine": { + "median": 2.008883625, + "min": 2.002274709, + "max": 2.010968792 + }, + "refine: legalise start": { + "median": 0.000462125, + "min": 0.000460083, + "max": 0.000474375 + }, + "refine: start quality": { + "median": 0.000755292, + "min": 0.0007465, + "max": 0.0007765 + }, + "refine: scan (parallel)": { + "median": 0.5638042910000002, + "min": 0.5638000879999999, + "max": 0.5670207089999998 + }, + "refine: split + flip (serial)": { + "median": 1.1840706219999997, + "min": 1.17977575, + "max": 1.1896711659999994 + }, + "refine: setup + output": { + "median": 0.25655571100000074, + "min": 0.2562801210000005, + "max": 0.25747379299999995 + }, + "edge strip: generate": { + "median": 0.001067875, + "min": 0.001059792, + "max": 0.001085375 + }, + "edge strip: scan (parallel)": { + "median": 0.009431124, + "min": 0.009402165999999998, + "max": 0.009484958 + }, + "edge strip: split + flip (serial)": { + "median": 0.013071624, + "min": 0.012965457999999997, + "max": 0.013226416000000001 + }, + "trim": { + "median": 0.117721459, + "min": 0.117681, + "max": 0.117933542 + }, + "write: encode": { + "median": 0.044901708, + "min": 0.044475291, + "max": 0.045232375 + }, + "write: disk": { + "median": 0.026688542, + "min": 0.02042975, + "max": 0.069523875 + } + } + } + } +} diff --git a/docs/benchmarks/2026-10-11/gpkg-out/quick/power-after.txt b/docs/benchmarks/2026-10-11/gpkg-out/quick/power-after.txt new file mode 100644 index 00000000..2b930cf7 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/quick/power-after.txt @@ -0,0 +1,2 @@ +Now drawing from 'AC Power' + -InternalBattery-0 (id=24117347) 100%; charged; 0:00 remaining present: true diff --git a/docs/benchmarks/2026-10-11/gpkg-out/quick/power-before-base.txt b/docs/benchmarks/2026-10-11/gpkg-out/quick/power-before-base.txt new file mode 100644 index 00000000..2b930cf7 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/quick/power-before-base.txt @@ -0,0 +1,2 @@ +Now drawing from 'AC Power' + -InternalBattery-0 (id=24117347) 100%; charged; 0:00 remaining present: true diff --git a/docs/benchmarks/2026-10-11/gpkg-out/quick/power-before-head.txt b/docs/benchmarks/2026-10-11/gpkg-out/quick/power-before-head.txt new file mode 100644 index 00000000..2b930cf7 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/quick/power-before-head.txt @@ -0,0 +1,2 @@ +Now drawing from 'AC Power' + -InternalBattery-0 (id=24117347) 100%; charged; 0:00 remaining present: true diff --git a/docs/benchmarks/2026-10-11/gpkg-out/scripts/quick_delta.py b/docs/benchmarks/2026-10-11/gpkg-out/scripts/quick_delta.py new file mode 100644 index 00000000..aadd8cbc --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/scripts/quick_delta.py @@ -0,0 +1,41 @@ +"""Quick check: a head record against a base record, both saved by +bench_quick.py --save-baseline and copied to quick/, per case: total, mesh +SHA-256 equality, max_error, and the features clip, decode, refine and write +phases. Adapted from 2026-10-11/cleanup-speed/scripts/quick_delta.py. +Usage: python quick_delta.py [base.json head.json]; default: the back-to-back +pair base-81a551b3.json and head-e2470ae7.json. Run from anywhere.""" +import json +import sys +from pathlib import Path + +here = Path(__file__).resolve().parent.parent +names = sys.argv[1:3] or ["base-81a551b3.json", "head-e2470ae7.json"] +b = json.loads((here / "quick" / names[0]).read_text()) +h = json.loads((here / "quick" / names[1]).read_text()) +PHASES = ["features clip", "decode", "refine", "write: encode", "write: disk"] + + +def cell(c: dict, hc: dict, k: str) -> str: + if k not in c["measures"]: + return "-" + pb, ph = c["measures"][k]["median"], hc["measures"][k]["median"] + return f"{pb:.3f} / {ph:.3f} ({100 * (ph / pb - 1):+.1f} %)" + + +print(f"base {b['commit'][:8]} (numpy {b['machine']['numpy']}, {b['power']}) -> " + f"head {h['commit'][:8]} (numpy {h['machine']['numpy']}, {h['power']})\n") # fmt: skip +print("| case | base total s | head total s | change | mesh same | max_error base / head | " + + " | ".join(PHASES) + " |") # fmt: skip +print("|---|---:|---:|---:|---|---|" + "---|" * len(PHASES)) +for n, c in b["cases"].items(): + hc = h["cases"][n] + tb, th = c["measures"]["total"]["median"], hc["measures"]["total"]["median"] + print(f"| {n} | {tb:.3f} | {th:.3f} | {100 * (th / tb - 1):+.1f} % | " + f"{c['mesh_sha256'] == hc['mesh_sha256']} | {c['max_error']:.3f} / {hc['max_error']:.3f} | " + + " | ".join(cell(c, hc, k) for k in PHASES) + " |") # fmt: skip + +print("\nSpreads (max - min) / median of the total, base / head:") +for n, c in b["cases"].items(): + sb, sh = c["measures"]["total"], h["cases"][n]["measures"]["total"] + print(f" {n}: {100 * (sb['max'] - sb['min']) / sb['median']:.1f} % / " + f"{100 * (sh['max'] - sh['min']) / sh['median']:.1f} %") # fmt: skip diff --git a/docs/benchmarks/2026-10-11/gpkg-out/scripts/writer_time.py b/docs/benchmarks/2026-10-11/gpkg-out/scripts/writer_time.py new file mode 100644 index 00000000..71496e9a --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/scripts/writer_time.py @@ -0,0 +1,127 @@ +"""Increment 36 acceptance (b): the GeoPackage writer's time and memory on the +built command, `docs/increments/36-geopackage-output.md` section 9. + +For each case, `rasputin mesh ... --out X.gpkg --stats -` and the same command +with `--out X.vtk --binary` (the subtraction baseline), each under +`/usr/bin/time -l`, one warm-up then `RUNS` timed runs. While a command runs, +the rasputin child's RSS is sampled with `ps` every 0.1 s, so the writer's own +share is the RSS growth inside the `write: geopackage` window (the last +`write: geopackage` seconds of the process; it is the last phase of the run), +not the whole command's peak, which the meshing sets. Medians are reported. + +Usage, from the worktree root: + RASPUTIN_DATA=... .venv/bin/python docs/benchmarks/2026-10-11/gpkg-out/scripts/writer_time.py \ + [case ...] +Writes /writer---.log (the command's stderr+stdout and the +/usr/bin/time block), /writer---.rss (t_s rss_kB samples), +/writer-results.json and prints the table. +""" + +from __future__ import annotations + +import json +import os +import re +import statistics +import subprocess +import sys +import threading +import time +from pathlib import Path + +RUNS = 3 +TREE = Path(__file__).resolve().parents[5] # scripts/gpkg-out/2026-10-11/benchmarks/docs/ +DATA = os.environ.get("RASPUTIN_DATA", str(TREE.parent / "rasputin_data")) +RASPUTIN = TREE / ".venv" / "bin" / "rasputin" +CASES = { + "numedalslagen": [ + "mesh", "--dem", f"{DATA}/DTM10_UTM33_20260925", + "--domain", f"{DATA}/numedalslagen_outline_nve.geojson", + "--features", f"{DATA}/corine2018_dtm10_utm33.gpkg", "--features-layer", "corine2018", + "--features-map", "corine", "--tolerance", "10", + ], + "romsdal-slope": [ + "mesh", "--dem", f"{DATA}/DTM10_UTM33_20260925/6901_3_10m_z33.tif", + "--tolerance", "10", "--tolerance-slope", "2", "25", "35", + ], +} # fmt: skip +FORMATS = {"gpkg": ["--out", "mesh.gpkg"], "vtk": ["--out", "mesh.vtk", "--binary"]} + + +def sample(proc: subprocess.Popen[bytes], out: list[tuple[float, int]], t0: float) -> None: + child: str | None = None + while proc.poll() is None: + if child is None: + kids = subprocess.run(["pgrep", "-P", str(proc.pid)], capture_output=True, text=True) + child = (kids.stdout.split() or [None])[0] + if child is not None: + ps = subprocess.run(["ps", "-o", "rss=", "-p", child], capture_output=True, text=True) + if ps.stdout.strip(): + out.append((time.perf_counter() - t0, int(ps.stdout.split()[0]))) + time.sleep(0.1) + + +def one(case: str, fmt: str, n: int, out_dir: Path, work: Path) -> dict[str, float]: + target = work / FORMATS[fmt][1] + argv = ["/usr/bin/time", "-l", str(RASPUTIN), *CASES[case], "--out", str(target), + *FORMATS[fmt][2:], "--stats", "-"] # fmt: skip + for stale in work.glob("mesh.*"): + stale.unlink() + samples: list[tuple[float, int]] = [] + t0 = time.perf_counter() + proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + thread = threading.Thread(target=sample, args=(proc, samples, t0)) + thread.start() + stdout, stderr = proc.communicate() + t_end = time.perf_counter() - t0 + thread.join() + text = stdout.decode() + "\n--- stderr ---\n" + stderr.decode() + (out_dir / f"writer-{case}-{fmt}-{n}.log").write_text(text) + (out_dir / f"writer-{case}-{fmt}-{n}.rss").write_text( + "".join(f"{t:.2f} {kb}\n" for t, kb in samples) + ) + if proc.returncode != 0: + raise SystemExit(f"{case} {fmt} run {n}: exited {proc.returncode}\n{text[-3000:]}") + real = float(re.search(r"([\d.]+) real", text).group(1)) + peak = int(re.search(r"(\d+)\s+maximum resident set size", text).group(1)) + # the --stats Markdown table: `| write: geopackage | 3.980 | 40.0 % |` + phases = {m.group(1): float(m.group(2)) + for m in re.finditer(r"\|\s*(write: [a-z]+(?: [a-z]+)*)\s*\|\s*([\d.]+)\s*\|", text)} # fmt: skip + write_s = phases.get("write: geopackage", phases.get("write: encode", 0.0) + phases.get("write: disk", 0.0)) + window = [kb for t, kb in samples if t >= t_end - write_s - 0.05] + result = { + "real_s": real, "peak_rss_bytes": peak, "write_s": write_s, + "size_bytes": target.stat().st_size, + "writer_rss_start_bytes": (window[0] if window else 0) * 1024, + "writer_rss_peak_bytes": (max(window) if window else 0) * 1024, + "samples": len(samples), + } # fmt: skip + result["writer_share_bytes"] = result["writer_rss_peak_bytes"] - result["writer_rss_start_bytes"] + for phase, seconds in phases.items(): + result[phase] = seconds + return result + + +def main() -> None: + out_dir = Path(sys.argv[1]).resolve() + out_dir.mkdir(parents=True, exist_ok=True) + cases = sys.argv[2:] or list(CASES) + work = Path(os.environ.get("TMPDIR", "/tmp")) / "rasputin-writer-time" + work.mkdir(exist_ok=True) + results: dict[str, dict[str, object]] = {} + for case in cases: + for fmt in FORMATS: + one(case, fmt, 0, out_dir, work) # warm-up + runs = [one(case, fmt, n, out_dir, work) for n in range(1, RUNS + 1)] + med = {k: statistics.median(r[k] for r in runs) for k in runs[0]} + results[f"{case}/{fmt}"] = {"runs": runs, "median": med} + print(f"{case} {fmt}: real {med['real_s']:.2f} s, write {med['write_s']:.3f} s, " + f"peak RSS {med['peak_rss_bytes'] / 1e9:.2f} GB, writer window " + f"{med['writer_rss_start_bytes'] / 1e9:.2f} -> {med['writer_rss_peak_bytes'] / 1e9:.2f} GB " + f"(share {med['writer_share_bytes'] / 1e6:.0f} MB), file {med['size_bytes'] / 1e6:.1f} MB", + flush=True) # fmt: skip + (out_dir / "writer-results.json").write_text(json.dumps(results, indent=1) + "\n") + + +if __name__ == "__main__": + main() diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/power-after.txt b/docs/benchmarks/2026-10-11/gpkg-out/writer/power-after.txt new file mode 100644 index 00000000..2b930cf7 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/power-after.txt @@ -0,0 +1,2 @@ +Now drawing from 'AC Power' + -InternalBattery-0 (id=24117347) 100%; charged; 0:00 remaining present: true diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/power-before.txt b/docs/benchmarks/2026-10-11/gpkg-out/writer/power-before.txt new file mode 100644 index 00000000..2b930cf7 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/power-before.txt @@ -0,0 +1,2 @@ +Now drawing from 'AC Power' + -InternalBattery-0 (id=24117347) 100%; charged; 0:00 remaining present: true diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-0.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-0.log new file mode 100644 index 00000000..ba67d2ea --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-0.log @@ -0,0 +1,164 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925 --domain /Users/skavhaug/projects/rasputin_data/numedalslagen_outline_nve.geojson --features /Users/skavhaug/projects/rasputin_data/corine2018_dtm10_utm33.gpkg --features-layer corine2018 --features-map corine --tolerance 10 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 17882 × 15667 (10 m) | +| domain vertices | 14092 (1 ring, 0 holes) | +| start vertices | 49618 | +| start triangles | 84399 | +| output vertices | 334190 | +| output triangles | 653039 | +| constraint edges | 67240 | +| mesh.gpkg | 203.3 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 15667 columns x 17882 rows, 10 m apart | `dem_grid` | +| The tiles or downloaded blocks used | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_tiles` | +| Overlapping tiles that disagree, and by how much | 6601_1_10m_z33.tif and 6602_3_10m_z33.tif disagree at 1804 nodes, by up to 0.043335 m (median 0.0050354 m); 6601_1_10m_z33.tif and 6602_4_10m_z33.tif disagree at 8379 nodes, by up to 1.18677 m (median 0.0448608 m); 6601_2_10m_z33.tif and 6602_3_10m_z33.tif disagree at 56278 nodes, by up to 0.207977 m (median 0.00482178 m); 6601_2_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2026 nodes, by up to 1.18677 m (median 0.0470276 m); 6602_3_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2029 nodes, by up to 1.19373 m (median 0.0465088 m); 6700_2_10m_z33.tif and 6701_3_10m_z33.tif disagree at 31004 nodes, by up to 0.267212 m (median 0.0020752 m) | `dem_seams` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| The domain file and shape | numedalslagen_outline_nve.geojson: 1 outline, 0 holes, 14092 vertices | `domain` | +| The domain's coordinate system | EPSG:25833 | `domain_crs` | +| Conversion from the domain's coordinate system | none | `domain_transform` | +| Each features file: layer, class map, features, lines, vertices | corine2018_dtm10_utm33.gpkg layer corine2018, class map corine: 22 features, 2650 lines, 73414 vertices | `features` | +| Each features file's coordinate system | EPSG:25833 | `features_crs` | +| Conversion from each features file's coordinate system | none | `features_transform` | +| Land-cover borders closer than this made one, narrower gaps filled | 1 | `features_repair_m` | +| Borders between land-cover polygons of one class dropped | on | `features_merge_same_class` | +| Land-cover borders simplified, each at most this far from its border after the repair and the outline rule, each class's area kept (0 = off) | 50 | `features_tolerance_m` | +| Land-cover borders this close to the outline moved onto it | 5 | `features_outline_snap_m` | +| What refinement started from | the domain outline and the feature lines | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 15 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## DEM seams + +| tile | tile | nodes | max | median | +|---|---|---|---|---| +| 6601_1_10m_z33.tif | 6602_3_10m_z33.tif | 1804 | 0.043335 | 0.0050354 | +| 6601_1_10m_z33.tif | 6602_4_10m_z33.tif | 8379 | 1.18677 | 0.0448608 | +| 6601_2_10m_z33.tif | 6602_3_10m_z33.tif | 56278 | 0.207977 | 0.00482178 | +| 6601_2_10m_z33.tif | 6602_4_10m_z33.tif | 2026 | 1.18677 | 0.0470276 | +| 6602_3_10m_z33.tif | 6602_4_10m_z33.tif | 2029 | 1.19373 | 0.0465088 | +| 6700_2_10m_z33.tif | 6701_3_10m_z33.tif | 31004 | 0.267212 | 0.0020752 | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 33.47° | 0.01 % | 3.19 % | 0.311° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 10 | 17 | 635 | 0 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 10 | `max_error_m` | +| DEM | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| Credit for the features data | Contains modified CORINE Land Cover 2018 data (version 2020_20u1), (c) European Union, Copernicus Land Monitoring Service 2018, European Environment Agency (EEA): clipped and re-encoded, produced with funding by the European Union, not endorsed by the EU | `features_notice` | +| What land_cover_code holds | CORINE Land Cover level-3 code, attribute Code_18, map corine; 0 = in no polygon, and every constraint line | `land_cover_codes` | +| Land-cover vertices before and after clean-up | 603977 after the clip, 75014 after clean-up | `land_cover_vertices` | +| Land-cover area inside the outline that the outline rule gave to another polygon, m2 | 13238.0 | `land_cover_area_moved_m2` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 2773195 | `line_points_checked` | +| Their largest difference from the mesh | 9.999720974715501 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 1066 | `line_points_inserted` | +| DEM nodes the line check added | 294 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 24 | `refinement_rounds` | +| Points added | 264125 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 651486 | `edge_flips` | +| Points added to improve the starting mesh | 14308 | `start_quality_points_inserted` | +| Tries skipped while improving it | 2960 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 2402 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 201 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 4563 | `start_quality_lines_split` | +| Points refinement moved onto lines | 10338 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 15 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 49618 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| domain read | 0.014 | 0.1 % | +| decode | 0.954 | 7.2 % | +| features read | 0.288 | 2.2 % | +| features clip | 6.317 | 47.8 % | +| features clip: clean-up | 6.250 | 47.2 % | +| start mesh: build | 0.002 | 0.0 % | +| start mesh: node | 0.149 | 1.1 % | +| start mesh: triangulate | 0.041 | 0.3 % | +| start mesh: constraint edges | 0.101 | 0.8 % | +| refine | 0.687 | 5.2 % | +| refine: legalise start | 0.002 | 0.0 % | +| refine: start quality | 0.036 | 0.3 % | +| refine: scan (parallel) | 0.418 | 3.2 % | +| refine: split + flip (serial) | 0.177 | 1.3 % | +| refine: setup + output | 0.053 | 0.4 % | +| edge strip: generate | 0.102 | 0.8 % | +| edge strip: scan (parallel) | 0.009 | 0.1 % | +| edge strip: split + flip (serial) | 0.008 | 0.1 % | +| trim | 0.029 | 0.2 % | +| land cover | 0.298 | 2.3 % | +| write: geopackage | 3.941 | 29.8 % | +| other | 0.290 | 2.2 % | +| **total** | **13.229** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.143 s, not included above. + +--- stderr --- +features: 22 kept (15 cut at the domain outline), 4638 outside the domain, 0 empty +lines: 87506 vertices read, 49618 after joining shared edges and adding crossings +DEM: 16 files, 15667 columns x 17882 rows +land cover: 1840 areas between lines; 0 in no polygon, 0 in more than one (the smallest wins), 0 too narrow to label with certainty +653039 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 10 m). + 13.69 real 18.24 user 1.21 sys + 2787180544 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 250503 page reclaims + 0 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 1063 voluntary context switches + 7852 involuntary context switches + 208202527470 instructions retired + 60222425420 cycles elapsed + 2703411648 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-0.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-0.rss new file mode 100644 index 00000000..4836643c --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-0.rss @@ -0,0 +1,120 @@ +0.03 18544 +0.14 48432 +0.25 73648 +0.37 80704 +0.48 360128 +0.60 1454512 +0.71 1786064 +0.83 1914960 +0.94 2002752 +1.05 2015744 +1.17 2028896 +1.28 2032592 +1.39 2056688 +1.50 2175280 +1.62 2299968 +1.73 2303328 +1.85 2307872 +1.96 2316848 +2.08 2326768 +2.19 2344272 +2.31 2363008 +2.42 2395584 +2.54 2422304 +2.65 2446016 +2.77 2472064 +2.88 2647728 +3.00 1891776 +3.11 1922240 +3.22 1943200 +3.33 1943424 +3.45 1955328 +3.56 1961632 +3.67 1967744 +3.79 1969776 +3.90 1985824 +4.02 1989328 +4.13 1989728 +4.24 1991104 +4.35 1994448 +4.47 1995120 +4.58 1998032 +4.70 1998320 +4.81 1996304 +4.93 1996880 +5.04 1997024 +5.15 1998544 +5.27 1999152 +5.38 1999216 +5.50 1999504 +5.61 2001520 +5.72 2004800 +5.83 2007520 +5.95 2008048 +6.06 2008256 +6.17 2009024 +6.29 2009024 +6.40 2021328 +6.52 2021536 +6.63 2021568 +6.75 2027520 +6.86 2064384 +6.97 2072432 +7.08 2076768 +7.20 2081488 +7.31 2086192 +7.43 2087440 +7.54 2087680 +7.65 2087824 +7.77 2087824 +7.88 2085456 +8.00 2092080 +8.11 2099696 +8.25 2094192 +8.40 2094192 +8.53 2109936 +8.65 2115200 +8.77 2136032 +8.89 2289552 +9.00 2334064 +9.11 2356960 +9.22 2563744 +9.33 2594656 +9.45 2655184 +9.56 2661456 +9.67 2685600 +9.78 2689984 +9.90 2684864 +10.01 2689984 +10.12 2690016 +10.24 2690016 +10.34 2690096 +10.46 2688480 +10.57 2690144 +10.69 2678384 +10.80 2681152 +10.91 2683600 +11.03 2685520 +11.14 2687536 +11.26 2689824 +11.37 2691984 +11.49 2694160 +11.60 2696288 +11.72 2698304 +11.83 2700336 +11.94 2702384 +12.06 2704400 +12.17 2706224 +12.29 2708272 +12.40 2710368 +12.52 2712240 +12.63 2714192 +12.75 2716032 +12.86 2717728 +12.98 2719456 +13.09 2721312 +13.20 2707520 +13.31 2707520 +13.43 2707536 +13.54 2707584 +13.66 2450048 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-1.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-1.log new file mode 100644 index 00000000..c3445809 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-1.log @@ -0,0 +1,164 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925 --domain /Users/skavhaug/projects/rasputin_data/numedalslagen_outline_nve.geojson --features /Users/skavhaug/projects/rasputin_data/corine2018_dtm10_utm33.gpkg --features-layer corine2018 --features-map corine --tolerance 10 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 17882 × 15667 (10 m) | +| domain vertices | 14092 (1 ring, 0 holes) | +| start vertices | 49618 | +| start triangles | 84399 | +| output vertices | 334190 | +| output triangles | 653039 | +| constraint edges | 67240 | +| mesh.gpkg | 203.3 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 15667 columns x 17882 rows, 10 m apart | `dem_grid` | +| The tiles or downloaded blocks used | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_tiles` | +| Overlapping tiles that disagree, and by how much | 6601_1_10m_z33.tif and 6602_3_10m_z33.tif disagree at 1804 nodes, by up to 0.043335 m (median 0.0050354 m); 6601_1_10m_z33.tif and 6602_4_10m_z33.tif disagree at 8379 nodes, by up to 1.18677 m (median 0.0448608 m); 6601_2_10m_z33.tif and 6602_3_10m_z33.tif disagree at 56278 nodes, by up to 0.207977 m (median 0.00482178 m); 6601_2_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2026 nodes, by up to 1.18677 m (median 0.0470276 m); 6602_3_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2029 nodes, by up to 1.19373 m (median 0.0465088 m); 6700_2_10m_z33.tif and 6701_3_10m_z33.tif disagree at 31004 nodes, by up to 0.267212 m (median 0.0020752 m) | `dem_seams` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| The domain file and shape | numedalslagen_outline_nve.geojson: 1 outline, 0 holes, 14092 vertices | `domain` | +| The domain's coordinate system | EPSG:25833 | `domain_crs` | +| Conversion from the domain's coordinate system | none | `domain_transform` | +| Each features file: layer, class map, features, lines, vertices | corine2018_dtm10_utm33.gpkg layer corine2018, class map corine: 22 features, 2650 lines, 73414 vertices | `features` | +| Each features file's coordinate system | EPSG:25833 | `features_crs` | +| Conversion from each features file's coordinate system | none | `features_transform` | +| Land-cover borders closer than this made one, narrower gaps filled | 1 | `features_repair_m` | +| Borders between land-cover polygons of one class dropped | on | `features_merge_same_class` | +| Land-cover borders simplified, each at most this far from its border after the repair and the outline rule, each class's area kept (0 = off) | 50 | `features_tolerance_m` | +| Land-cover borders this close to the outline moved onto it | 5 | `features_outline_snap_m` | +| What refinement started from | the domain outline and the feature lines | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 15 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## DEM seams + +| tile | tile | nodes | max | median | +|---|---|---|---|---| +| 6601_1_10m_z33.tif | 6602_3_10m_z33.tif | 1804 | 0.043335 | 0.0050354 | +| 6601_1_10m_z33.tif | 6602_4_10m_z33.tif | 8379 | 1.18677 | 0.0448608 | +| 6601_2_10m_z33.tif | 6602_3_10m_z33.tif | 56278 | 0.207977 | 0.00482178 | +| 6601_2_10m_z33.tif | 6602_4_10m_z33.tif | 2026 | 1.18677 | 0.0470276 | +| 6602_3_10m_z33.tif | 6602_4_10m_z33.tif | 2029 | 1.19373 | 0.0465088 | +| 6700_2_10m_z33.tif | 6701_3_10m_z33.tif | 31004 | 0.267212 | 0.0020752 | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 33.47° | 0.01 % | 3.19 % | 0.311° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 10 | 17 | 635 | 0 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 10 | `max_error_m` | +| DEM | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| Credit for the features data | Contains modified CORINE Land Cover 2018 data (version 2020_20u1), (c) European Union, Copernicus Land Monitoring Service 2018, European Environment Agency (EEA): clipped and re-encoded, produced with funding by the European Union, not endorsed by the EU | `features_notice` | +| What land_cover_code holds | CORINE Land Cover level-3 code, attribute Code_18, map corine; 0 = in no polygon, and every constraint line | `land_cover_codes` | +| Land-cover vertices before and after clean-up | 603977 after the clip, 75014 after clean-up | `land_cover_vertices` | +| Land-cover area inside the outline that the outline rule gave to another polygon, m2 | 13238.0 | `land_cover_area_moved_m2` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 2773195 | `line_points_checked` | +| Their largest difference from the mesh | 9.999720974715501 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 1066 | `line_points_inserted` | +| DEM nodes the line check added | 294 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 24 | `refinement_rounds` | +| Points added | 264125 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 651486 | `edge_flips` | +| Points added to improve the starting mesh | 14308 | `start_quality_points_inserted` | +| Tries skipped while improving it | 2960 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 2402 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 201 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 4563 | `start_quality_lines_split` | +| Points refinement moved onto lines | 10338 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 15 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 49618 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| domain read | 0.013 | 0.1 % | +| decode | 0.957 | 7.3 % | +| features read | 0.230 | 1.8 % | +| features clip | 6.323 | 48.2 % | +| features clip: clean-up | 6.253 | 47.7 % | +| start mesh: build | 0.002 | 0.0 % | +| start mesh: node | 0.150 | 1.1 % | +| start mesh: triangulate | 0.042 | 0.3 % | +| start mesh: constraint edges | 0.098 | 0.7 % | +| refine | 0.696 | 5.3 % | +| refine: legalise start | 0.002 | 0.0 % | +| refine: start quality | 0.035 | 0.3 % | +| refine: scan (parallel) | 0.422 | 3.2 % | +| refine: split + flip (serial) | 0.184 | 1.4 % | +| refine: setup + output | 0.052 | 0.4 % | +| edge strip: generate | 0.104 | 0.8 % | +| edge strip: scan (parallel) | 0.009 | 0.1 % | +| edge strip: split + flip (serial) | 0.007 | 0.1 % | +| trim | 0.026 | 0.2 % | +| land cover | 0.295 | 2.2 % | +| write: geopackage | 3.892 | 29.7 % | +| other | 0.272 | 2.1 % | +| **total** | **13.116** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.143 s, not included above. + +--- stderr --- +features: 22 kept (15 cut at the domain outline), 4638 outside the domain, 0 empty +lines: 87506 vertices read, 49618 after joining shared edges and adding crossings +DEM: 16 files, 15667 columns x 17882 rows +land cover: 1840 areas between lines; 0 in no polygon, 0 in more than one (the smallest wins), 0 too narrow to label with certainty +653039 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 10 m). + 13.56 real 18.20 user 1.16 sys + 2783264768 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 248539 page reclaims + 35 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 159 voluntary context switches + 6937 involuntary context switches + 208148386908 instructions retired + 59900449156 cycles elapsed + 2688321960 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-1.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-1.rss new file mode 100644 index 00000000..131fb1d1 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-1.rss @@ -0,0 +1,119 @@ +0.03 18080 +0.15 52304 +0.26 79504 +0.38 126432 +0.49 361088 +0.61 1566096 +0.72 1783440 +0.83 1996112 +0.95 2009568 +1.06 2021856 +1.18 2030864 +1.29 2045936 +1.40 2154528 +1.52 2282992 +1.63 2286448 +1.74 2288896 +1.86 2295856 +1.97 2307408 +2.09 2319936 +2.20 2339136 +2.31 2356720 +2.43 2403520 +2.54 2421360 +2.66 2448624 +2.77 2545472 +2.89 1862720 +3.00 1900320 +3.11 1928800 +3.23 1929008 +3.34 1941280 +3.46 1946512 +3.57 1951888 +3.69 1954144 +3.80 1969264 +3.92 1972320 +4.03 1972752 +4.14 1978960 +4.26 1977216 +4.37 1977952 +4.49 1980864 +4.60 1979952 +4.72 1979024 +4.83 1979152 +4.94 1981216 +5.06 1981744 +5.17 1981904 +5.29 1982032 +5.40 1984160 +5.52 1985040 +5.63 1987712 +5.75 1990512 +5.86 1990848 +5.97 1991680 +6.08 1991680 +6.19 1991696 +6.30 2003952 +6.42 2004128 +6.53 2004128 +6.65 2005584 +6.76 2057216 +6.87 2065408 +6.99 2070864 +7.10 2076592 +7.22 2082512 +7.33 2085248 +7.44 2085424 +7.55 2085600 +7.67 2085600 +7.78 2085024 +7.89 2086336 +8.01 2090672 +8.16 2090208 +8.27 2090080 +8.41 2099280 +8.54 2108592 +8.67 2129808 +8.79 2206640 +8.90 2329168 +9.01 2352064 +9.13 2549968 +9.24 2589760 +9.36 2648736 +9.47 2653168 +9.58 2680784 +9.70 2685168 +9.81 2680048 +9.93 2685168 +10.04 2685200 +10.16 2685200 +10.27 2685280 +10.38 2683648 +10.49 2685328 +10.61 2674432 +10.72 2677248 +10.84 2679760 +10.95 2681680 +11.07 2683712 +11.18 2686000 +11.29 2687968 +11.40 2690080 +11.52 2692272 +11.62 2694272 +11.74 2696352 +11.85 2698480 +11.96 2700560 +12.08 2702480 +12.19 2704592 +12.31 2706672 +12.42 2708480 +12.54 2710320 +12.65 2712320 +12.77 2714256 +12.88 2716096 +13.00 2706768 +13.11 2702352 +13.23 2702352 +13.34 2702368 +13.45 2702416 +13.56 0 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-2.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-2.log new file mode 100644 index 00000000..370b739b --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-2.log @@ -0,0 +1,164 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925 --domain /Users/skavhaug/projects/rasputin_data/numedalslagen_outline_nve.geojson --features /Users/skavhaug/projects/rasputin_data/corine2018_dtm10_utm33.gpkg --features-layer corine2018 --features-map corine --tolerance 10 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 17882 × 15667 (10 m) | +| domain vertices | 14092 (1 ring, 0 holes) | +| start vertices | 49618 | +| start triangles | 84399 | +| output vertices | 334190 | +| output triangles | 653039 | +| constraint edges | 67240 | +| mesh.gpkg | 203.3 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 15667 columns x 17882 rows, 10 m apart | `dem_grid` | +| The tiles or downloaded blocks used | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_tiles` | +| Overlapping tiles that disagree, and by how much | 6601_1_10m_z33.tif and 6602_3_10m_z33.tif disagree at 1804 nodes, by up to 0.043335 m (median 0.0050354 m); 6601_1_10m_z33.tif and 6602_4_10m_z33.tif disagree at 8379 nodes, by up to 1.18677 m (median 0.0448608 m); 6601_2_10m_z33.tif and 6602_3_10m_z33.tif disagree at 56278 nodes, by up to 0.207977 m (median 0.00482178 m); 6601_2_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2026 nodes, by up to 1.18677 m (median 0.0470276 m); 6602_3_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2029 nodes, by up to 1.19373 m (median 0.0465088 m); 6700_2_10m_z33.tif and 6701_3_10m_z33.tif disagree at 31004 nodes, by up to 0.267212 m (median 0.0020752 m) | `dem_seams` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| The domain file and shape | numedalslagen_outline_nve.geojson: 1 outline, 0 holes, 14092 vertices | `domain` | +| The domain's coordinate system | EPSG:25833 | `domain_crs` | +| Conversion from the domain's coordinate system | none | `domain_transform` | +| Each features file: layer, class map, features, lines, vertices | corine2018_dtm10_utm33.gpkg layer corine2018, class map corine: 22 features, 2650 lines, 73414 vertices | `features` | +| Each features file's coordinate system | EPSG:25833 | `features_crs` | +| Conversion from each features file's coordinate system | none | `features_transform` | +| Land-cover borders closer than this made one, narrower gaps filled | 1 | `features_repair_m` | +| Borders between land-cover polygons of one class dropped | on | `features_merge_same_class` | +| Land-cover borders simplified, each at most this far from its border after the repair and the outline rule, each class's area kept (0 = off) | 50 | `features_tolerance_m` | +| Land-cover borders this close to the outline moved onto it | 5 | `features_outline_snap_m` | +| What refinement started from | the domain outline and the feature lines | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 15 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## DEM seams + +| tile | tile | nodes | max | median | +|---|---|---|---|---| +| 6601_1_10m_z33.tif | 6602_3_10m_z33.tif | 1804 | 0.043335 | 0.0050354 | +| 6601_1_10m_z33.tif | 6602_4_10m_z33.tif | 8379 | 1.18677 | 0.0448608 | +| 6601_2_10m_z33.tif | 6602_3_10m_z33.tif | 56278 | 0.207977 | 0.00482178 | +| 6601_2_10m_z33.tif | 6602_4_10m_z33.tif | 2026 | 1.18677 | 0.0470276 | +| 6602_3_10m_z33.tif | 6602_4_10m_z33.tif | 2029 | 1.19373 | 0.0465088 | +| 6700_2_10m_z33.tif | 6701_3_10m_z33.tif | 31004 | 0.267212 | 0.0020752 | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 33.47° | 0.01 % | 3.19 % | 0.311° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 10 | 17 | 635 | 0 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 10 | `max_error_m` | +| DEM | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| Credit for the features data | Contains modified CORINE Land Cover 2018 data (version 2020_20u1), (c) European Union, Copernicus Land Monitoring Service 2018, European Environment Agency (EEA): clipped and re-encoded, produced with funding by the European Union, not endorsed by the EU | `features_notice` | +| What land_cover_code holds | CORINE Land Cover level-3 code, attribute Code_18, map corine; 0 = in no polygon, and every constraint line | `land_cover_codes` | +| Land-cover vertices before and after clean-up | 603977 after the clip, 75014 after clean-up | `land_cover_vertices` | +| Land-cover area inside the outline that the outline rule gave to another polygon, m2 | 13238.0 | `land_cover_area_moved_m2` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 2773195 | `line_points_checked` | +| Their largest difference from the mesh | 9.999720974715501 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 1066 | `line_points_inserted` | +| DEM nodes the line check added | 294 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 24 | `refinement_rounds` | +| Points added | 264125 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 651486 | `edge_flips` | +| Points added to improve the starting mesh | 14308 | `start_quality_points_inserted` | +| Tries skipped while improving it | 2960 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 2402 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 201 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 4563 | `start_quality_lines_split` | +| Points refinement moved onto lines | 10338 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 15 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 49618 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| domain read | 0.013 | 0.1 % | +| decode | 0.960 | 7.3 % | +| features read | 0.204 | 1.6 % | +| features clip | 6.251 | 47.8 % | +| features clip: clean-up | 6.186 | 47.3 % | +| start mesh: build | 0.002 | 0.0 % | +| start mesh: node | 0.148 | 1.1 % | +| start mesh: triangulate | 0.043 | 0.3 % | +| start mesh: constraint edges | 0.101 | 0.8 % | +| refine | 0.685 | 5.2 % | +| refine: legalise start | 0.002 | 0.0 % | +| refine: start quality | 0.038 | 0.3 % | +| refine: scan (parallel) | 0.408 | 3.1 % | +| refine: split + flip (serial) | 0.184 | 1.4 % | +| refine: setup + output | 0.053 | 0.4 % | +| edge strip: generate | 0.109 | 0.8 % | +| edge strip: scan (parallel) | 0.008 | 0.1 % | +| edge strip: split + flip (serial) | 0.007 | 0.1 % | +| trim | 0.028 | 0.2 % | +| land cover | 0.306 | 2.3 % | +| write: geopackage | 3.932 | 30.1 % | +| other | 0.284 | 2.2 % | +| **total** | **13.080** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.145 s, not included above. + +--- stderr --- +features: 22 kept (15 cut at the domain outline), 4638 outside the domain, 0 empty +lines: 87506 vertices read, 49618 after joining shared edges and adding crossings +DEM: 16 files, 15667 columns x 17882 rows +land cover: 1840 areas between lines; 0 in no polygon, 0 in more than one (the smallest wins), 0 too narrow to label with certainty +653039 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 10 m). + 13.53 real 18.20 user 1.12 sys + 2765635584 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 244051 page reclaims + 0 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 31 voluntary context switches + 6512 involuntary context switches + 208039921329 instructions retired + 59670143168 cycles elapsed + 2688584104 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-2.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-2.rss new file mode 100644 index 00000000..0a02a7b4 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-2.rss @@ -0,0 +1,119 @@ +0.04 18160 +0.15 52448 +0.27 79680 +0.38 235984 +0.50 438784 +0.61 1596624 +0.73 1789664 +0.83 1994368 +0.94 2004304 +1.05 2015456 +1.17 2030448 +1.28 2042400 +1.39 2152464 +1.51 2283104 +1.62 2286112 +1.74 2289600 +1.85 2296832 +1.96 2309440 +2.07 2322032 +2.19 2342592 +2.30 2356944 +2.41 2403744 +2.52 2426448 +2.64 2454304 +2.75 2584448 +2.87 1856784 +2.98 1910240 +3.10 1942272 +3.21 1940224 +3.32 1953888 +3.44 1959744 +3.55 1964096 +3.66 1966176 +3.77 1981312 +3.89 1984672 +4.00 1985168 +4.12 1991232 +4.23 1989456 +4.34 1990304 +4.46 1993184 +4.57 1992304 +4.69 1991280 +4.80 1991344 +4.92 1992688 +5.03 1993136 +5.15 1993216 +5.26 1993392 +5.37 1995296 +5.49 1996016 +5.60 1998704 +5.71 2001088 +5.83 2001344 +5.94 2002208 +6.05 2002272 +6.17 2002368 +6.28 2014704 +6.40 2015008 +6.51 2015056 +6.62 2045040 +6.74 2060128 +6.85 2067488 +6.96 2071712 +7.07 2076704 +7.19 2080976 +7.30 2081632 +7.42 2081840 +7.53 2081968 +7.65 2080960 +7.76 2083296 +7.87 2086992 +7.99 2088000 +8.11 2088208 +8.24 2088048 +8.38 2103680 +8.49 2108960 +8.61 2129904 +8.72 2240768 +8.84 2327936 +8.95 2350832 +9.07 2557632 +9.18 2588528 +9.30 2649056 +9.41 2654256 +9.52 2678400 +9.64 2682784 +9.75 2677664 +9.87 2682784 +9.97 2682816 +10.09 2682816 +10.20 2682896 +10.31 2682944 +10.43 2678848 +10.54 2671120 +10.66 2671120 +10.77 2671120 +10.89 2671120 +11.00 2671120 +11.11 2671120 +11.22 2671120 +11.34 2673008 +11.45 2675136 +11.56 2677200 +11.68 2679376 +11.79 2681424 +11.91 2683456 +12.02 2685344 +12.13 2687360 +12.24 2689392 +12.36 2691280 +12.47 2693216 +12.59 2695152 +12.70 2696976 +12.81 2698720 +12.92 2700592 +13.04 2687264 +13.15 2687264 +13.27 2687280 +13.38 2687328 +13.50 934656 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-3.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-3.log new file mode 100644 index 00000000..af04b31f --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-3.log @@ -0,0 +1,164 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925 --domain /Users/skavhaug/projects/rasputin_data/numedalslagen_outline_nve.geojson --features /Users/skavhaug/projects/rasputin_data/corine2018_dtm10_utm33.gpkg --features-layer corine2018 --features-map corine --tolerance 10 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 17882 × 15667 (10 m) | +| domain vertices | 14092 (1 ring, 0 holes) | +| start vertices | 49618 | +| start triangles | 84399 | +| output vertices | 334190 | +| output triangles | 653039 | +| constraint edges | 67240 | +| mesh.gpkg | 203.3 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 15667 columns x 17882 rows, 10 m apart | `dem_grid` | +| The tiles or downloaded blocks used | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_tiles` | +| Overlapping tiles that disagree, and by how much | 6601_1_10m_z33.tif and 6602_3_10m_z33.tif disagree at 1804 nodes, by up to 0.043335 m (median 0.0050354 m); 6601_1_10m_z33.tif and 6602_4_10m_z33.tif disagree at 8379 nodes, by up to 1.18677 m (median 0.0448608 m); 6601_2_10m_z33.tif and 6602_3_10m_z33.tif disagree at 56278 nodes, by up to 0.207977 m (median 0.00482178 m); 6601_2_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2026 nodes, by up to 1.18677 m (median 0.0470276 m); 6602_3_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2029 nodes, by up to 1.19373 m (median 0.0465088 m); 6700_2_10m_z33.tif and 6701_3_10m_z33.tif disagree at 31004 nodes, by up to 0.267212 m (median 0.0020752 m) | `dem_seams` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| The domain file and shape | numedalslagen_outline_nve.geojson: 1 outline, 0 holes, 14092 vertices | `domain` | +| The domain's coordinate system | EPSG:25833 | `domain_crs` | +| Conversion from the domain's coordinate system | none | `domain_transform` | +| Each features file: layer, class map, features, lines, vertices | corine2018_dtm10_utm33.gpkg layer corine2018, class map corine: 22 features, 2650 lines, 73414 vertices | `features` | +| Each features file's coordinate system | EPSG:25833 | `features_crs` | +| Conversion from each features file's coordinate system | none | `features_transform` | +| Land-cover borders closer than this made one, narrower gaps filled | 1 | `features_repair_m` | +| Borders between land-cover polygons of one class dropped | on | `features_merge_same_class` | +| Land-cover borders simplified, each at most this far from its border after the repair and the outline rule, each class's area kept (0 = off) | 50 | `features_tolerance_m` | +| Land-cover borders this close to the outline moved onto it | 5 | `features_outline_snap_m` | +| What refinement started from | the domain outline and the feature lines | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 15 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## DEM seams + +| tile | tile | nodes | max | median | +|---|---|---|---|---| +| 6601_1_10m_z33.tif | 6602_3_10m_z33.tif | 1804 | 0.043335 | 0.0050354 | +| 6601_1_10m_z33.tif | 6602_4_10m_z33.tif | 8379 | 1.18677 | 0.0448608 | +| 6601_2_10m_z33.tif | 6602_3_10m_z33.tif | 56278 | 0.207977 | 0.00482178 | +| 6601_2_10m_z33.tif | 6602_4_10m_z33.tif | 2026 | 1.18677 | 0.0470276 | +| 6602_3_10m_z33.tif | 6602_4_10m_z33.tif | 2029 | 1.19373 | 0.0465088 | +| 6700_2_10m_z33.tif | 6701_3_10m_z33.tif | 31004 | 0.267212 | 0.0020752 | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 33.47° | 0.01 % | 3.19 % | 0.311° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 10 | 17 | 635 | 0 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 10 | `max_error_m` | +| DEM | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| Credit for the features data | Contains modified CORINE Land Cover 2018 data (version 2020_20u1), (c) European Union, Copernicus Land Monitoring Service 2018, European Environment Agency (EEA): clipped and re-encoded, produced with funding by the European Union, not endorsed by the EU | `features_notice` | +| What land_cover_code holds | CORINE Land Cover level-3 code, attribute Code_18, map corine; 0 = in no polygon, and every constraint line | `land_cover_codes` | +| Land-cover vertices before and after clean-up | 603977 after the clip, 75014 after clean-up | `land_cover_vertices` | +| Land-cover area inside the outline that the outline rule gave to another polygon, m2 | 13238.0 | `land_cover_area_moved_m2` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 2773195 | `line_points_checked` | +| Their largest difference from the mesh | 9.999720974715501 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 1066 | `line_points_inserted` | +| DEM nodes the line check added | 294 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 24 | `refinement_rounds` | +| Points added | 264125 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 651486 | `edge_flips` | +| Points added to improve the starting mesh | 14308 | `start_quality_points_inserted` | +| Tries skipped while improving it | 2960 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 2402 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 201 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 4563 | `start_quality_lines_split` | +| Points refinement moved onto lines | 10338 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 15 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 49618 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| domain read | 0.012 | 0.1 % | +| decode | 0.961 | 7.3 % | +| features read | 0.205 | 1.6 % | +| features clip | 6.239 | 47.5 % | +| features clip: clean-up | 6.170 | 47.0 % | +| start mesh: build | 0.002 | 0.0 % | +| start mesh: node | 0.148 | 1.1 % | +| start mesh: triangulate | 0.040 | 0.3 % | +| start mesh: constraint edges | 0.094 | 0.7 % | +| refine | 0.681 | 5.2 % | +| refine: legalise start | 0.002 | 0.0 % | +| refine: start quality | 0.035 | 0.3 % | +| refine: scan (parallel) | 0.413 | 3.1 % | +| refine: split + flip (serial) | 0.178 | 1.4 % | +| refine: setup + output | 0.053 | 0.4 % | +| edge strip: generate | 0.100 | 0.8 % | +| edge strip: scan (parallel) | 0.008 | 0.1 % | +| edge strip: split + flip (serial) | 0.008 | 0.1 % | +| trim | 0.026 | 0.2 % | +| land cover | 0.339 | 2.6 % | +| write: geopackage | 3.988 | 30.4 % | +| other | 0.270 | 2.1 % | +| **total** | **13.123** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.144 s, not included above. + +--- stderr --- +features: 22 kept (15 cut at the domain outline), 4638 outside the domain, 0 empty +lines: 87506 vertices read, 49618 after joining shared edges and adding crossings +DEM: 16 files, 15667 columns x 17882 rows +land cover: 1840 areas between lines; 0 in no polygon, 0 in more than one (the smallest wins), 0 too narrow to label with certainty +653039 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 10 m). + 13.53 real 18.23 user 1.18 sys + 2726019072 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 262826 page reclaims + 0 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 154 voluntary context switches + 6175 involuntary context switches + 208063265591 instructions retired + 59820693169 cycles elapsed + 2685766056 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-3.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-3.rss new file mode 100644 index 00000000..22fdd146 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-gpkg-3.rss @@ -0,0 +1,118 @@ +0.03 18624 +0.15 52400 +0.26 79712 +0.37 244080 +0.49 428032 +0.60 1561296 +0.71 1785184 +0.84 1999184 +0.95 2009408 +1.07 2023024 +1.18 2031440 +1.29 2056608 +1.41 2168560 +1.52 2287696 +1.64 2290800 +1.74 2293840 +1.86 2297104 +1.97 2306976 +2.09 2319408 +2.20 2335456 +2.31 2352208 +2.43 2401984 +2.54 2426160 +2.66 2454064 +2.77 2641920 +2.88 1878736 +3.00 1901088 +3.11 1920512 +3.23 1922096 +3.34 1939824 +3.46 1951712 +3.57 1952544 +3.68 1964576 +3.79 1969856 +3.91 1975408 +4.02 1975680 +4.13 1981904 +4.25 1980432 +4.36 1981248 +4.48 1984032 +4.59 1983408 +4.71 1983216 +4.82 1983744 +4.94 1984544 +5.05 1986544 +5.17 1987200 +5.28 1988000 +5.39 1988384 +5.51 1990976 +5.62 1996400 +5.74 1997328 +5.85 1997360 +5.96 1998192 +6.08 1998208 +6.19 1998688 +6.30 2010848 +6.42 2010864 +6.53 2010912 +6.65 2063392 +6.76 2072128 +6.88 2077712 +6.99 2083648 +7.11 2089680 +7.21 2092816 +7.33 2093392 +7.44 2094384 +7.56 2094416 +7.67 2094032 +7.79 2099008 +7.90 2104224 +8.05 2103904 +8.20 2103920 +8.35 2119408 +8.46 2124752 +8.58 2145616 +8.69 2318800 +8.81 2340416 +8.93 2462480 +9.04 2600928 +9.15 2600928 +9.26 2662128 +9.37 1083616 +9.48 1136000 +9.60 1152960 +9.71 1148864 +9.83 1152960 +9.94 1147344 +10.06 1152992 +10.17 1148816 +10.29 1153072 +10.40 1153120 +10.52 1142224 +10.63 1143808 +10.74 1146416 +10.86 1148784 +10.97 1150400 +11.09 1152576 +11.20 1154800 +11.32 1156832 +11.43 1159008 +11.54 1161120 +11.66 1163120 +11.77 1165296 +11.89 1167296 +12.00 1169184 +12.12 1171120 +12.23 1173200 +12.34 1175200 +12.46 1177040 +12.57 1178992 +12.68 1180672 +12.79 1182496 +12.91 1184448 +13.02 1174416 +13.14 1171984 +13.25 1171984 +13.37 1172016 +13.48 1187360 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-0.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-0.log new file mode 100644 index 00000000..ba85d5e2 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-0.log @@ -0,0 +1,165 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925 --domain /Users/skavhaug/projects/rasputin_data/numedalslagen_outline_nve.geojson --features /Users/skavhaug/projects/rasputin_data/corine2018_dtm10_utm33.gpkg --features-layer corine2018 --features-map corine --tolerance 10 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk --binary --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 17882 × 15667 (10 m) | +| domain vertices | 14092 (1 ring, 0 holes) | +| start vertices | 49618 | +| start triangles | 84399 | +| output vertices | 334190 | +| output triangles | 653039 | +| constraint edges | 67240 | +| mesh.vtk | 29.2 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 15667 columns x 17882 rows, 10 m apart | `dem_grid` | +| The tiles or downloaded blocks used | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_tiles` | +| Overlapping tiles that disagree, and by how much | 6601_1_10m_z33.tif and 6602_3_10m_z33.tif disagree at 1804 nodes, by up to 0.043335 m (median 0.0050354 m); 6601_1_10m_z33.tif and 6602_4_10m_z33.tif disagree at 8379 nodes, by up to 1.18677 m (median 0.0448608 m); 6601_2_10m_z33.tif and 6602_3_10m_z33.tif disagree at 56278 nodes, by up to 0.207977 m (median 0.00482178 m); 6601_2_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2026 nodes, by up to 1.18677 m (median 0.0470276 m); 6602_3_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2029 nodes, by up to 1.19373 m (median 0.0465088 m); 6700_2_10m_z33.tif and 6701_3_10m_z33.tif disagree at 31004 nodes, by up to 0.267212 m (median 0.0020752 m) | `dem_seams` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| The domain file and shape | numedalslagen_outline_nve.geojson: 1 outline, 0 holes, 14092 vertices | `domain` | +| The domain's coordinate system | EPSG:25833 | `domain_crs` | +| Conversion from the domain's coordinate system | none | `domain_transform` | +| Each features file: layer, class map, features, lines, vertices | corine2018_dtm10_utm33.gpkg layer corine2018, class map corine: 22 features, 2650 lines, 73414 vertices | `features` | +| Each features file's coordinate system | EPSG:25833 | `features_crs` | +| Conversion from each features file's coordinate system | none | `features_transform` | +| Land-cover borders closer than this made one, narrower gaps filled | 1 | `features_repair_m` | +| Borders between land-cover polygons of one class dropped | on | `features_merge_same_class` | +| Land-cover borders simplified, each at most this far from its border after the repair and the outline rule, each class's area kept (0 = off) | 50 | `features_tolerance_m` | +| Land-cover borders this close to the outline moved onto it | 5 | `features_outline_snap_m` | +| What refinement started from | the domain outline and the feature lines | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 15 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## DEM seams + +| tile | tile | nodes | max | median | +|---|---|---|---|---| +| 6601_1_10m_z33.tif | 6602_3_10m_z33.tif | 1804 | 0.043335 | 0.0050354 | +| 6601_1_10m_z33.tif | 6602_4_10m_z33.tif | 8379 | 1.18677 | 0.0448608 | +| 6601_2_10m_z33.tif | 6602_3_10m_z33.tif | 56278 | 0.207977 | 0.00482178 | +| 6601_2_10m_z33.tif | 6602_4_10m_z33.tif | 2026 | 1.18677 | 0.0470276 | +| 6602_3_10m_z33.tif | 6602_4_10m_z33.tif | 2029 | 1.19373 | 0.0465088 | +| 6700_2_10m_z33.tif | 6701_3_10m_z33.tif | 31004 | 0.267212 | 0.0020752 | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 33.47° | 0.01 % | 3.19 % | 0.311° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 10 | 17 | 635 | 0 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 10 | `max_error_m` | +| DEM | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| Credit for the features data | Contains modified CORINE Land Cover 2018 data (version 2020_20u1), (c) European Union, Copernicus Land Monitoring Service 2018, European Environment Agency (EEA): clipped and re-encoded, produced with funding by the European Union, not endorsed by the EU | `features_notice` | +| What land_cover_code holds | CORINE Land Cover level-3 code, attribute Code_18, map corine; 0 = in no polygon, and every constraint line | `land_cover_codes` | +| Land-cover vertices before and after clean-up | 603977 after the clip, 75014 after clean-up | `land_cover_vertices` | +| Land-cover area inside the outline that the outline rule gave to another polygon, m2 | 13238.0 | `land_cover_area_moved_m2` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 2773195 | `line_points_checked` | +| Their largest difference from the mesh | 9.999720974715501 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 1066 | `line_points_inserted` | +| DEM nodes the line check added | 294 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 24 | `refinement_rounds` | +| Points added | 264125 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 651486 | `edge_flips` | +| Points added to improve the starting mesh | 14308 | `start_quality_points_inserted` | +| Tries skipped while improving it | 2960 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 2402 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 201 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 4563 | `start_quality_lines_split` | +| Points refinement moved onto lines | 10338 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 15 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 49618 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| domain read | 0.013 | 0.1 % | +| decode | 0.945 | 10.5 % | +| features read | 0.207 | 2.3 % | +| features clip | 6.161 | 68.4 % | +| features clip: clean-up | 6.095 | 67.6 % | +| start mesh: build | 0.002 | 0.0 % | +| start mesh: node | 0.147 | 1.6 % | +| start mesh: triangulate | 0.040 | 0.4 % | +| start mesh: constraint edges | 0.093 | 1.0 % | +| refine | 0.676 | 7.5 % | +| refine: legalise start | 0.002 | 0.0 % | +| refine: start quality | 0.035 | 0.4 % | +| refine: scan (parallel) | 0.409 | 4.5 % | +| refine: split + flip (serial) | 0.176 | 2.0 % | +| refine: setup + output | 0.053 | 0.6 % | +| edge strip: generate | 0.100 | 1.1 % | +| edge strip: scan (parallel) | 0.008 | 0.1 % | +| edge strip: split + flip (serial) | 0.008 | 0.1 % | +| trim | 0.027 | 0.3 % | +| land cover | 0.300 | 3.3 % | +| write: encode | 0.010 | 0.1 % | +| write: disk | 0.005 | 0.1 % | +| other | 0.272 | 3.0 % | +| **total** | **9.011** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.141 s, not included above. + +--- stderr --- +features: 22 kept (15 cut at the domain outline), 4638 outside the domain, 0 empty +lines: 87506 vertices read, 49618 after joining shared edges and adding crossings +DEM: 16 files, 15667 columns x 17882 rows +land cover: 1840 areas between lines; 0 in no polygon, 0 in more than one (the smallest wins), 0 too narrow to label with certainty +653039 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 10 m). + 9.45 real 14.47 user 0.84 sys + 2747777024 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 242933 page reclaims + 0 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 6 voluntary context switches + 6328 involuntary context switches + 158237390959 instructions retired + 47251580763 cycles elapsed + 2688092584 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-0.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-0.rss new file mode 100644 index 00000000..23e7b4dc --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-0.rss @@ -0,0 +1,83 @@ +0.03 18432 +0.15 52560 +0.26 79568 +0.37 256304 +0.49 455168 +0.60 1587600 +0.71 1785456 +0.82 1995968 +0.94 2009568 +1.05 2022400 +1.17 2035088 +1.28 2062544 +1.40 2174816 +1.51 2288560 +1.62 2290480 +1.74 2293568 +1.85 2296880 +1.96 2307072 +2.07 2319792 +2.18 2335152 +2.29 2348400 +2.40 2403248 +2.52 2427264 +2.63 2455584 +2.74 2624784 +2.86 1878224 +2.97 1902880 +3.08 1921024 +3.19 1921904 +3.30 1937040 +3.41 1958064 +3.52 1960512 +3.63 1972976 +3.75 1979072 +3.86 1985600 +3.98 1986048 +4.08 1998976 +4.19 1996752 +4.30 1997456 +4.41 2000416 +4.53 2000560 +4.63 1999616 +4.75 1999792 +4.86 2003184 +4.97 2003696 +5.09 2003840 +5.20 2004000 +5.32 2006336 +5.43 2008016 +5.54 2010848 +5.66 2012992 +5.77 2013024 +5.88 2013936 +5.99 2014080 +6.11 2014464 +6.22 2026400 +6.33 2026464 +6.45 2026496 +6.56 2074192 +6.68 2088720 +6.78 2097104 +6.89 2102864 +7.00 2109120 +7.11 2113328 +7.23 2113536 +7.34 2113728 +7.46 2113728 +7.56 2113184 +7.68 2114528 +7.79 2118320 +7.96 2119488 +8.11 2119392 +8.25 2135040 +8.36 2140416 +8.48 2161856 +8.60 2339760 +8.71 2358432 +8.83 2480592 +8.94 2619008 +9.05 2619008 +9.17 2680176 +9.28 2683344 +9.40 2391360 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-1.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-1.log new file mode 100644 index 00000000..3638b17d --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-1.log @@ -0,0 +1,165 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925 --domain /Users/skavhaug/projects/rasputin_data/numedalslagen_outline_nve.geojson --features /Users/skavhaug/projects/rasputin_data/corine2018_dtm10_utm33.gpkg --features-layer corine2018 --features-map corine --tolerance 10 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk --binary --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 17882 × 15667 (10 m) | +| domain vertices | 14092 (1 ring, 0 holes) | +| start vertices | 49618 | +| start triangles | 84399 | +| output vertices | 334190 | +| output triangles | 653039 | +| constraint edges | 67240 | +| mesh.vtk | 29.2 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 15667 columns x 17882 rows, 10 m apart | `dem_grid` | +| The tiles or downloaded blocks used | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_tiles` | +| Overlapping tiles that disagree, and by how much | 6601_1_10m_z33.tif and 6602_3_10m_z33.tif disagree at 1804 nodes, by up to 0.043335 m (median 0.0050354 m); 6601_1_10m_z33.tif and 6602_4_10m_z33.tif disagree at 8379 nodes, by up to 1.18677 m (median 0.0448608 m); 6601_2_10m_z33.tif and 6602_3_10m_z33.tif disagree at 56278 nodes, by up to 0.207977 m (median 0.00482178 m); 6601_2_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2026 nodes, by up to 1.18677 m (median 0.0470276 m); 6602_3_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2029 nodes, by up to 1.19373 m (median 0.0465088 m); 6700_2_10m_z33.tif and 6701_3_10m_z33.tif disagree at 31004 nodes, by up to 0.267212 m (median 0.0020752 m) | `dem_seams` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| The domain file and shape | numedalslagen_outline_nve.geojson: 1 outline, 0 holes, 14092 vertices | `domain` | +| The domain's coordinate system | EPSG:25833 | `domain_crs` | +| Conversion from the domain's coordinate system | none | `domain_transform` | +| Each features file: layer, class map, features, lines, vertices | corine2018_dtm10_utm33.gpkg layer corine2018, class map corine: 22 features, 2650 lines, 73414 vertices | `features` | +| Each features file's coordinate system | EPSG:25833 | `features_crs` | +| Conversion from each features file's coordinate system | none | `features_transform` | +| Land-cover borders closer than this made one, narrower gaps filled | 1 | `features_repair_m` | +| Borders between land-cover polygons of one class dropped | on | `features_merge_same_class` | +| Land-cover borders simplified, each at most this far from its border after the repair and the outline rule, each class's area kept (0 = off) | 50 | `features_tolerance_m` | +| Land-cover borders this close to the outline moved onto it | 5 | `features_outline_snap_m` | +| What refinement started from | the domain outline and the feature lines | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 15 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## DEM seams + +| tile | tile | nodes | max | median | +|---|---|---|---|---| +| 6601_1_10m_z33.tif | 6602_3_10m_z33.tif | 1804 | 0.043335 | 0.0050354 | +| 6601_1_10m_z33.tif | 6602_4_10m_z33.tif | 8379 | 1.18677 | 0.0448608 | +| 6601_2_10m_z33.tif | 6602_3_10m_z33.tif | 56278 | 0.207977 | 0.00482178 | +| 6601_2_10m_z33.tif | 6602_4_10m_z33.tif | 2026 | 1.18677 | 0.0470276 | +| 6602_3_10m_z33.tif | 6602_4_10m_z33.tif | 2029 | 1.19373 | 0.0465088 | +| 6700_2_10m_z33.tif | 6701_3_10m_z33.tif | 31004 | 0.267212 | 0.0020752 | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 33.47° | 0.01 % | 3.19 % | 0.311° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 10 | 17 | 635 | 0 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 10 | `max_error_m` | +| DEM | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| Credit for the features data | Contains modified CORINE Land Cover 2018 data (version 2020_20u1), (c) European Union, Copernicus Land Monitoring Service 2018, European Environment Agency (EEA): clipped and re-encoded, produced with funding by the European Union, not endorsed by the EU | `features_notice` | +| What land_cover_code holds | CORINE Land Cover level-3 code, attribute Code_18, map corine; 0 = in no polygon, and every constraint line | `land_cover_codes` | +| Land-cover vertices before and after clean-up | 603977 after the clip, 75014 after clean-up | `land_cover_vertices` | +| Land-cover area inside the outline that the outline rule gave to another polygon, m2 | 13238.0 | `land_cover_area_moved_m2` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 2773195 | `line_points_checked` | +| Their largest difference from the mesh | 9.999720974715501 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 1066 | `line_points_inserted` | +| DEM nodes the line check added | 294 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 24 | `refinement_rounds` | +| Points added | 264125 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 651486 | `edge_flips` | +| Points added to improve the starting mesh | 14308 | `start_quality_points_inserted` | +| Tries skipped while improving it | 2960 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 2402 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 201 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 4563 | `start_quality_lines_split` | +| Points refinement moved onto lines | 10338 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 15 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 49618 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| domain read | 0.013 | 0.1 % | +| decode | 0.943 | 10.3 % | +| features read | 0.228 | 2.5 % | +| features clip | 6.221 | 68.1 % | +| features clip: clean-up | 6.156 | 67.4 % | +| start mesh: build | 0.002 | 0.0 % | +| start mesh: node | 0.149 | 1.6 % | +| start mesh: triangulate | 0.040 | 0.4 % | +| start mesh: constraint edges | 0.095 | 1.0 % | +| refine | 0.693 | 7.6 % | +| refine: legalise start | 0.002 | 0.0 % | +| refine: start quality | 0.039 | 0.4 % | +| refine: scan (parallel) | 0.418 | 4.6 % | +| refine: split + flip (serial) | 0.180 | 2.0 % | +| refine: setup + output | 0.054 | 0.6 % | +| edge strip: generate | 0.105 | 1.1 % | +| edge strip: scan (parallel) | 0.009 | 0.1 % | +| edge strip: split + flip (serial) | 0.008 | 0.1 % | +| trim | 0.027 | 0.3 % | +| land cover | 0.301 | 3.3 % | +| write: encode | 0.010 | 0.1 % | +| write: disk | 0.004 | 0.0 % | +| other | 0.282 | 3.1 % | +| **total** | **9.129** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.143 s, not included above. + +--- stderr --- +features: 22 kept (15 cut at the domain outline), 4638 outside the domain, 0 empty +lines: 87506 vertices read, 49618 after joining shared edges and adding crossings +DEM: 16 files, 15667 columns x 17882 rows +land cover: 1840 areas between lines; 0 in no polygon, 0 in more than one (the smallest wins), 0 too narrow to label with certainty +653039 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 10 m). + 9.58 real 14.59 user 0.83 sys + 2729295872 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 241485 page reclaims + 19 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 6 voluntary context switches + 6478 involuntary context switches + 158161257249 instructions retired + 47375296980 cycles elapsed + 2688158120 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-1.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-1.rss new file mode 100644 index 00000000..af3cae6a --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-1.rss @@ -0,0 +1,84 @@ +0.03 17232 +0.15 50400 +0.26 78976 +0.38 160528 +0.49 367296 +0.61 1586704 +0.71 1781152 +0.83 1994768 +0.94 2006480 +1.06 2018512 +1.17 2031232 +1.29 2048960 +1.40 2153616 +1.52 2283680 +1.63 2286784 +1.74 2290320 +1.86 2297520 +1.97 2310256 +2.09 2323952 +2.19 2340144 +2.31 2354384 +2.42 2402496 +2.53 2425008 +2.65 2452160 +2.76 2575952 +2.88 1861200 +2.99 1910576 +3.10 1941888 +3.22 1939824 +3.33 1953408 +3.45 1961456 +3.56 1963728 +3.67 1975136 +3.78 1981200 +3.90 1984624 +4.01 1984976 +4.13 1992384 +4.24 1989632 +4.36 1990400 +4.47 1993312 +4.59 1993408 +4.70 1992432 +4.80 1992464 +4.92 1993888 +5.03 1994320 +5.15 1994416 +5.26 1994624 +5.38 1996512 +5.49 1998000 +5.60 2001600 +5.71 2002400 +5.83 2002432 +5.94 2003296 +6.06 2003344 +6.17 2003792 +6.29 2016048 +6.40 2016048 +6.52 2016096 +6.63 2068272 +6.74 2075488 +6.86 2079184 +6.97 2083696 +7.09 2089104 +7.20 2092128 +7.32 2092320 +7.43 2092560 +7.54 2092576 +7.66 2093120 +7.77 2097792 +7.89 2101488 +8.07 2101680 +8.20 2101776 +8.32 2117520 +8.44 2120768 +8.55 2141664 +8.66 2208800 +8.78 2340992 +8.89 2363872 +9.00 2539760 +9.11 2601584 +9.23 2632272 +9.34 2664656 +9.46 2665296 +9.57 913216 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-2.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-2.log new file mode 100644 index 00000000..f1c5acf4 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-2.log @@ -0,0 +1,165 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925 --domain /Users/skavhaug/projects/rasputin_data/numedalslagen_outline_nve.geojson --features /Users/skavhaug/projects/rasputin_data/corine2018_dtm10_utm33.gpkg --features-layer corine2018 --features-map corine --tolerance 10 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk --binary --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 17882 × 15667 (10 m) | +| domain vertices | 14092 (1 ring, 0 holes) | +| start vertices | 49618 | +| start triangles | 84399 | +| output vertices | 334190 | +| output triangles | 653039 | +| constraint edges | 67240 | +| mesh.vtk | 29.2 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 15667 columns x 17882 rows, 10 m apart | `dem_grid` | +| The tiles or downloaded blocks used | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_tiles` | +| Overlapping tiles that disagree, and by how much | 6601_1_10m_z33.tif and 6602_3_10m_z33.tif disagree at 1804 nodes, by up to 0.043335 m (median 0.0050354 m); 6601_1_10m_z33.tif and 6602_4_10m_z33.tif disagree at 8379 nodes, by up to 1.18677 m (median 0.0448608 m); 6601_2_10m_z33.tif and 6602_3_10m_z33.tif disagree at 56278 nodes, by up to 0.207977 m (median 0.00482178 m); 6601_2_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2026 nodes, by up to 1.18677 m (median 0.0470276 m); 6602_3_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2029 nodes, by up to 1.19373 m (median 0.0465088 m); 6700_2_10m_z33.tif and 6701_3_10m_z33.tif disagree at 31004 nodes, by up to 0.267212 m (median 0.0020752 m) | `dem_seams` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| The domain file and shape | numedalslagen_outline_nve.geojson: 1 outline, 0 holes, 14092 vertices | `domain` | +| The domain's coordinate system | EPSG:25833 | `domain_crs` | +| Conversion from the domain's coordinate system | none | `domain_transform` | +| Each features file: layer, class map, features, lines, vertices | corine2018_dtm10_utm33.gpkg layer corine2018, class map corine: 22 features, 2650 lines, 73414 vertices | `features` | +| Each features file's coordinate system | EPSG:25833 | `features_crs` | +| Conversion from each features file's coordinate system | none | `features_transform` | +| Land-cover borders closer than this made one, narrower gaps filled | 1 | `features_repair_m` | +| Borders between land-cover polygons of one class dropped | on | `features_merge_same_class` | +| Land-cover borders simplified, each at most this far from its border after the repair and the outline rule, each class's area kept (0 = off) | 50 | `features_tolerance_m` | +| Land-cover borders this close to the outline moved onto it | 5 | `features_outline_snap_m` | +| What refinement started from | the domain outline and the feature lines | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 15 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## DEM seams + +| tile | tile | nodes | max | median | +|---|---|---|---|---| +| 6601_1_10m_z33.tif | 6602_3_10m_z33.tif | 1804 | 0.043335 | 0.0050354 | +| 6601_1_10m_z33.tif | 6602_4_10m_z33.tif | 8379 | 1.18677 | 0.0448608 | +| 6601_2_10m_z33.tif | 6602_3_10m_z33.tif | 56278 | 0.207977 | 0.00482178 | +| 6601_2_10m_z33.tif | 6602_4_10m_z33.tif | 2026 | 1.18677 | 0.0470276 | +| 6602_3_10m_z33.tif | 6602_4_10m_z33.tif | 2029 | 1.19373 | 0.0465088 | +| 6700_2_10m_z33.tif | 6701_3_10m_z33.tif | 31004 | 0.267212 | 0.0020752 | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 33.47° | 0.01 % | 3.19 % | 0.311° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 10 | 17 | 635 | 0 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 10 | `max_error_m` | +| DEM | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| Credit for the features data | Contains modified CORINE Land Cover 2018 data (version 2020_20u1), (c) European Union, Copernicus Land Monitoring Service 2018, European Environment Agency (EEA): clipped and re-encoded, produced with funding by the European Union, not endorsed by the EU | `features_notice` | +| What land_cover_code holds | CORINE Land Cover level-3 code, attribute Code_18, map corine; 0 = in no polygon, and every constraint line | `land_cover_codes` | +| Land-cover vertices before and after clean-up | 603977 after the clip, 75014 after clean-up | `land_cover_vertices` | +| Land-cover area inside the outline that the outline rule gave to another polygon, m2 | 13238.0 | `land_cover_area_moved_m2` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 2773195 | `line_points_checked` | +| Their largest difference from the mesh | 9.999720974715501 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 1066 | `line_points_inserted` | +| DEM nodes the line check added | 294 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 24 | `refinement_rounds` | +| Points added | 264125 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 651486 | `edge_flips` | +| Points added to improve the starting mesh | 14308 | `start_quality_points_inserted` | +| Tries skipped while improving it | 2960 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 2402 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 201 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 4563 | `start_quality_lines_split` | +| Points refinement moved onto lines | 10338 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 15 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 49618 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| domain read | 0.013 | 0.1 % | +| decode | 0.978 | 10.7 % | +| features read | 0.209 | 2.3 % | +| features clip | 6.187 | 67.9 % | +| features clip: clean-up | 6.119 | 67.1 % | +| start mesh: build | 0.002 | 0.0 % | +| start mesh: node | 0.147 | 1.6 % | +| start mesh: triangulate | 0.040 | 0.4 % | +| start mesh: constraint edges | 0.092 | 1.0 % | +| refine | 0.697 | 7.7 % | +| refine: legalise start | 0.002 | 0.0 % | +| refine: start quality | 0.035 | 0.4 % | +| refine: scan (parallel) | 0.426 | 4.7 % | +| refine: split + flip (serial) | 0.181 | 2.0 % | +| refine: setup + output | 0.053 | 0.6 % | +| edge strip: generate | 0.106 | 1.2 % | +| edge strip: scan (parallel) | 0.009 | 0.1 % | +| edge strip: split + flip (serial) | 0.008 | 0.1 % | +| trim | 0.026 | 0.3 % | +| land cover | 0.309 | 3.4 % | +| write: encode | 0.010 | 0.1 % | +| write: disk | 0.004 | 0.0 % | +| other | 0.276 | 3.0 % | +| **total** | **9.113** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.142 s, not included above. + +--- stderr --- +features: 22 kept (15 cut at the domain outline), 4638 outside the domain, 0 empty +lines: 87506 vertices read, 49618 after joining shared edges and adding crossings +DEM: 16 files, 15667 columns x 17882 rows +land cover: 1840 areas between lines; 0 in no polygon, 0 in more than one (the smallest wins), 0 too narrow to label with certainty +653039 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 10 m). + 9.56 real 14.70 user 0.80 sys + 2717728768 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 239802 page reclaims + 0 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 4 voluntary context switches + 6141 involuntary context switches + 158080254863 instructions retired + 47499422233 cycles elapsed + 2683947456 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-2.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-2.rss new file mode 100644 index 00000000..2dda9e59 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-2.rss @@ -0,0 +1,84 @@ +0.03 18544 +0.15 52704 +0.26 79616 +0.38 181696 +0.49 359376 +0.61 1516144 +0.72 1785600 +0.84 2001728 +0.94 2007088 +1.06 2021216 +1.17 2031936 +1.29 2043984 +1.40 2155904 +1.51 2282336 +1.62 2285440 +1.74 2288016 +1.85 2291328 +1.97 2299760 +2.08 2310640 +2.20 2328208 +2.30 2344624 +2.42 2394032 +2.53 2415120 +2.65 2442160 +2.76 2531600 +2.88 1854816 +2.99 1891520 +3.11 1918176 +3.22 1918224 +3.34 1933680 +3.45 1945392 +3.57 1949984 +3.68 1953328 +3.80 1966240 +3.91 1972144 +4.03 1972416 +4.13 1979424 +4.25 1977776 +4.36 1978544 +4.48 1981344 +4.59 1981728 +4.71 1981408 +4.82 1982768 +4.93 1986608 +5.05 1988512 +5.15 1988944 +5.27 1989728 +5.38 1990160 +5.49 1992240 +5.60 1996960 +5.72 1999392 +5.83 1999520 +5.94 2000352 +6.05 2000352 +6.15 2000496 +6.27 2012704 +6.38 2012848 +6.50 2012880 +6.61 2052544 +6.72 2066848 +6.83 2072944 +6.94 2076080 +7.05 2079776 +7.16 2082912 +7.28 2083248 +7.39 2084064 +7.50 2084176 +7.62 2084384 +7.73 2086720 +7.85 2090448 +7.96 2088432 +8.09 2088448 +8.25 2097648 +8.38 2106976 +8.49 2126768 +8.60 2139328 +8.70 2323488 +8.82 2328320 +8.93 2503440 +9.04 2588912 +9.16 2596544 +9.27 2650864 +9.39 2654000 +9.50 2649936 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-3.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-3.log new file mode 100644 index 00000000..b037f230 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-3.log @@ -0,0 +1,165 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925 --domain /Users/skavhaug/projects/rasputin_data/numedalslagen_outline_nve.geojson --features /Users/skavhaug/projects/rasputin_data/corine2018_dtm10_utm33.gpkg --features-layer corine2018 --features-map corine --tolerance 10 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk --binary --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 17882 × 15667 (10 m) | +| domain vertices | 14092 (1 ring, 0 holes) | +| start vertices | 49618 | +| start triangles | 84399 | +| output vertices | 334190 | +| output triangles | 653039 | +| constraint edges | 67240 | +| mesh.vtk | 29.2 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 15667 columns x 17882 rows, 10 m apart | `dem_grid` | +| The tiles or downloaded blocks used | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_tiles` | +| Overlapping tiles that disagree, and by how much | 6601_1_10m_z33.tif and 6602_3_10m_z33.tif disagree at 1804 nodes, by up to 0.043335 m (median 0.0050354 m); 6601_1_10m_z33.tif and 6602_4_10m_z33.tif disagree at 8379 nodes, by up to 1.18677 m (median 0.0448608 m); 6601_2_10m_z33.tif and 6602_3_10m_z33.tif disagree at 56278 nodes, by up to 0.207977 m (median 0.00482178 m); 6601_2_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2026 nodes, by up to 1.18677 m (median 0.0470276 m); 6602_3_10m_z33.tif and 6602_4_10m_z33.tif disagree at 2029 nodes, by up to 1.19373 m (median 0.0465088 m); 6700_2_10m_z33.tif and 6701_3_10m_z33.tif disagree at 31004 nodes, by up to 0.267212 m (median 0.0020752 m) | `dem_seams` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| The domain file and shape | numedalslagen_outline_nve.geojson: 1 outline, 0 holes, 14092 vertices | `domain` | +| The domain's coordinate system | EPSG:25833 | `domain_crs` | +| Conversion from the domain's coordinate system | none | `domain_transform` | +| Each features file: layer, class map, features, lines, vertices | corine2018_dtm10_utm33.gpkg layer corine2018, class map corine: 22 features, 2650 lines, 73414 vertices | `features` | +| Each features file's coordinate system | EPSG:25833 | `features_crs` | +| Conversion from each features file's coordinate system | none | `features_transform` | +| Land-cover borders closer than this made one, narrower gaps filled | 1 | `features_repair_m` | +| Borders between land-cover polygons of one class dropped | on | `features_merge_same_class` | +| Land-cover borders simplified, each at most this far from its border after the repair and the outline rule, each class's area kept (0 = off) | 50 | `features_tolerance_m` | +| Land-cover borders this close to the outline moved onto it | 5 | `features_outline_snap_m` | +| What refinement started from | the domain outline and the feature lines | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 15 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## DEM seams + +| tile | tile | nodes | max | median | +|---|---|---|---|---| +| 6601_1_10m_z33.tif | 6602_3_10m_z33.tif | 1804 | 0.043335 | 0.0050354 | +| 6601_1_10m_z33.tif | 6602_4_10m_z33.tif | 8379 | 1.18677 | 0.0448608 | +| 6601_2_10m_z33.tif | 6602_3_10m_z33.tif | 56278 | 0.207977 | 0.00482178 | +| 6601_2_10m_z33.tif | 6602_4_10m_z33.tif | 2026 | 1.18677 | 0.0470276 | +| 6602_3_10m_z33.tif | 6602_4_10m_z33.tif | 2029 | 1.19373 | 0.0465088 | +| 6700_2_10m_z33.tif | 6701_3_10m_z33.tif | 31004 | 0.267212 | 0.0020752 | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 33.47° | 0.01 % | 3.19 % | 0.311° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 10 | 17 | 635 | 0 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 10 | `max_error_m` | +| DEM | 6500_1_10m_z33.tif; 6501_1_10m_z33.tif; 6501_4_10m_z33.tif; 6502_4_10m_z33.tif; 6600_1_10m_z33.tif; 6600_2_10m_z33.tif; 6601_1_10m_z33.tif; 6601_2_10m_z33.tif; 6601_3_10m_z33.tif; 6601_4_10m_z33.tif; 6602_3_10m_z33.tif; 6602_4_10m_z33.tif; 6700_2_10m_z33.tif; 6701_2_10m_z33.tif; 6701_3_10m_z33.tif; 6702_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| Credit for the features data | Contains modified CORINE Land Cover 2018 data (version 2020_20u1), (c) European Union, Copernicus Land Monitoring Service 2018, European Environment Agency (EEA): clipped and re-encoded, produced with funding by the European Union, not endorsed by the EU | `features_notice` | +| What land_cover_code holds | CORINE Land Cover level-3 code, attribute Code_18, map corine; 0 = in no polygon, and every constraint line | `land_cover_codes` | +| Land-cover vertices before and after clean-up | 603977 after the clip, 75014 after clean-up | `land_cover_vertices` | +| Land-cover area inside the outline that the outline rule gave to another polygon, m2 | 13238.0 | `land_cover_area_moved_m2` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 2773195 | `line_points_checked` | +| Their largest difference from the mesh | 9.999720974715501 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 1066 | `line_points_inserted` | +| DEM nodes the line check added | 294 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 24 | `refinement_rounds` | +| Points added | 264125 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 651486 | `edge_flips` | +| Points added to improve the starting mesh | 14308 | `start_quality_points_inserted` | +| Tries skipped while improving it | 2960 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 2402 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 201 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 4563 | `start_quality_lines_split` | +| Points refinement moved onto lines | 10338 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 15 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 49618 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| domain read | 0.013 | 0.1 % | +| decode | 0.983 | 10.6 % | +| features read | 0.233 | 2.5 % | +| features clip | 6.305 | 68.3 % | +| features clip: clean-up | 6.239 | 67.6 % | +| start mesh: build | 0.002 | 0.0 % | +| start mesh: node | 0.147 | 1.6 % | +| start mesh: triangulate | 0.040 | 0.4 % | +| start mesh: constraint edges | 0.094 | 1.0 % | +| refine | 0.687 | 7.4 % | +| refine: legalise start | 0.002 | 0.0 % | +| refine: start quality | 0.038 | 0.4 % | +| refine: scan (parallel) | 0.418 | 4.5 % | +| refine: split + flip (serial) | 0.178 | 1.9 % | +| refine: setup + output | 0.052 | 0.6 % | +| edge strip: generate | 0.103 | 1.1 % | +| edge strip: scan (parallel) | 0.009 | 0.1 % | +| edge strip: split + flip (serial) | 0.008 | 0.1 % | +| trim | 0.027 | 0.3 % | +| land cover | 0.297 | 3.2 % | +| write: encode | 0.010 | 0.1 % | +| write: disk | 0.004 | 0.0 % | +| other | 0.273 | 3.0 % | +| **total** | **9.233** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.143 s, not included above. + +--- stderr --- +features: 22 kept (15 cut at the domain outline), 4638 outside the domain, 0 empty +lines: 87506 vertices read, 49618 after joining shared edges and adding crossings +DEM: 16 files, 15667 columns x 17882 rows +land cover: 1840 areas between lines; 0 in no polygon, 0 in more than one (the smallest wins), 0 too narrow to label with certainty +653039 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 10 m). + 9.68 real 14.76 user 0.80 sys + 2731622400 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 236545 page reclaims + 35 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 4 voluntary context switches + 6520 involuntary context switches + 158229777412 instructions retired + 47542779723 cycles elapsed + 2694515136 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-3.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-3.rss new file mode 100644 index 00000000..b1764a04 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-numedalslagen-vtk-3.rss @@ -0,0 +1,84 @@ +0.03 18112 +0.14 51648 +0.26 79296 +0.37 139664 +0.49 359120 +0.60 1508928 +0.72 1787968 +0.83 1892544 +0.95 2007024 +1.06 2019456 +1.18 2028800 +1.29 2035776 +1.41 2129040 +1.52 2290496 +1.63 2293936 +1.73 2296368 +1.84 2303280 +1.95 2313328 +2.06 2319664 +2.18 2337200 +2.29 2355792 +2.41 2400624 +2.52 2417520 +2.63 2442640 +2.75 2468480 +2.87 2380944 +2.98 1892048 +3.09 1911856 +3.21 1928752 +3.32 1933248 +3.44 1949904 +3.55 1960016 +3.66 1961744 +3.77 1977280 +3.88 1978544 +3.99 1982064 +4.11 1982512 +4.22 1990528 +4.34 1987632 +4.45 1988304 +4.57 1991280 +4.68 1991424 +4.79 1990432 +4.90 1990592 +5.02 1992304 +5.13 1992784 +5.25 1992960 +5.36 1993072 +5.48 1996000 +5.59 1996832 +5.70 1999424 +5.81 2002256 +5.93 2002608 +6.04 2003504 +6.16 2003616 +6.27 2003648 +6.39 2015936 +6.50 2016112 +6.62 2016176 +6.72 2049328 +6.84 2072880 +6.95 2081440 +7.07 2087152 +7.18 2093328 +7.29 2098304 +7.41 2098784 +7.52 2098992 +7.64 2099056 +7.75 2098080 +7.87 2099824 +7.98 2103520 +8.10 2103584 +8.25 2103600 +8.40 2112800 +8.53 2122032 +8.66 2143328 +8.78 2186608 +8.89 2342640 +9.01 2365536 +9.12 2568032 +9.23 2603232 +9.35 2662864 +9.46 2667520 +9.57 2667568 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-results.json b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-results.json new file mode 100644 index 00000000..0a6b482d --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-results.json @@ -0,0 +1,202 @@ +{ + "numedalslagen/gpkg": { + "runs": [ + { + "real_s": 13.56, + "peak_rss_bytes": 2783264768, + "write_s": 3.892, + "size_bytes": 203296768, + "writer_rss_start_bytes": 2749612032, + "writer_rss_peak_bytes": 2781282304, + "samples": 119, + "writer_share_bytes": 31670272, + "write: geopackage": 3.892 + }, + { + "real_s": 13.53, + "peak_rss_bytes": 2765635584, + "write_s": 3.932, + "size_bytes": 203296768, + "writer_rss_start_bytes": 2747170816, + "writer_rss_peak_bytes": 2765406208, + "samples": 119, + "writer_share_bytes": 18235392, + "write: geopackage": 3.932 + }, + { + "real_s": 13.53, + "peak_rss_bytes": 2726019072, + "write_s": 3.988, + "size_bytes": 203296768, + "writer_rss_start_bytes": 1180631040, + "writer_rss_peak_bytes": 1215856640, + "samples": 118, + "writer_share_bytes": 35225600, + "write: geopackage": 3.988 + } + ], + "median": { + "real_s": 13.53, + "peak_rss_bytes": 2765635584, + "write_s": 3.932, + "size_bytes": 203296768, + "writer_rss_start_bytes": 2747170816, + "writer_rss_peak_bytes": 2765406208, + "samples": 119, + "writer_share_bytes": 31670272, + "write: geopackage": 3.932 + } + }, + "numedalslagen/vtk": { + "runs": [ + { + "real_s": 9.58, + "peak_rss_bytes": 2729295872, + "write_s": 0.014, + "size_bytes": 29153879, + "writer_rss_start_bytes": 935133184, + "writer_rss_peak_bytes": 935133184, + "samples": 84, + "writer_share_bytes": 0, + "write: encode": 0.01, + "write: disk": 0.004 + }, + { + "real_s": 9.56, + "peak_rss_bytes": 2717728768, + "write_s": 0.014, + "size_bytes": 29153879, + "writer_rss_start_bytes": 0, + "writer_rss_peak_bytes": 0, + "samples": 84, + "writer_share_bytes": 0, + "write: encode": 0.01, + "write: disk": 0.004 + }, + { + "real_s": 9.68, + "peak_rss_bytes": 2731622400, + "write_s": 0.014, + "size_bytes": 29153879, + "writer_rss_start_bytes": 0, + "writer_rss_peak_bytes": 0, + "samples": 84, + "writer_share_bytes": 0, + "write: encode": 0.01, + "write: disk": 0.004 + } + ], + "median": { + "real_s": 9.58, + "peak_rss_bytes": 2729295872, + "write_s": 0.014, + "size_bytes": 29153879, + "writer_rss_start_bytes": 0, + "writer_rss_peak_bytes": 0, + "samples": 84, + "writer_share_bytes": 0, + "write: encode": 0.01, + "write: disk": 0.004 + } + }, + "romsdal-slope/gpkg": { + "runs": [ + { + "real_s": 23.72, + "peak_rss_bytes": 2948382720, + "write_s": 19.051, + "size_bytes": 810037248, + "writer_rss_start_bytes": 2721251328, + "writer_rss_peak_bytes": 2947907584, + "samples": 206, + "writer_share_bytes": 226656256, + "write: geopackage": 19.051 + }, + { + "real_s": 25.0, + "peak_rss_bytes": 2903326720, + "write_s": 20.154, + "size_bytes": 810037248, + "writer_rss_start_bytes": 2721103872, + "writer_rss_peak_bytes": 2901950464, + "samples": 219, + "writer_share_bytes": 180846592, + "write: geopackage": 20.154 + }, + { + "real_s": 24.15, + "peak_rss_bytes": 2912583680, + "write_s": 19.368, + "size_bytes": 810037248, + "writer_rss_start_bytes": 2723053568, + "writer_rss_peak_bytes": 2911420416, + "samples": 211, + "writer_share_bytes": 188366848, + "write: geopackage": 19.368 + } + ], + "median": { + "real_s": 24.15, + "peak_rss_bytes": 2912583680, + "write_s": 19.368, + "size_bytes": 810037248, + "writer_rss_start_bytes": 2721251328, + "writer_rss_peak_bytes": 2911420416, + "samples": 211, + "writer_share_bytes": 188366848, + "write: geopackage": 19.368 + } + }, + "romsdal-slope/vtk": { + "runs": [ + { + "real_s": 4.67, + "peak_rss_bytes": 2777055232, + "write_s": 0.055, + "size_bytes": 111542331, + "writer_rss_start_bytes": 2776989696, + "writer_rss_peak_bytes": 2776989696, + "samples": 40, + "writer_share_bytes": 0, + "write: encode": 0.039, + "write: disk": 0.016 + }, + { + "real_s": 4.82, + "peak_rss_bytes": 2775777280, + "write_s": 0.058, + "size_bytes": 111542331, + "writer_rss_start_bytes": 2775613440, + "writer_rss_peak_bytes": 2775613440, + "samples": 41, + "writer_share_bytes": 0, + "write: encode": 0.042, + "write: disk": 0.016 + }, + { + "real_s": 4.84, + "peak_rss_bytes": 2785820672, + "write_s": 0.062, + "size_bytes": 111542331, + "writer_rss_start_bytes": 2785787904, + "writer_rss_peak_bytes": 2785787904, + "samples": 42, + "writer_share_bytes": 0, + "write: encode": 0.04, + "write: disk": 0.022 + } + ], + "median": { + "real_s": 4.82, + "peak_rss_bytes": 2777055232, + "write_s": 0.058, + "size_bytes": 111542331, + "writer_rss_start_bytes": 2776989696, + "writer_rss_peak_bytes": 2776989696, + "samples": 41, + "writer_share_bytes": 0, + "write: encode": 0.04, + "write: disk": 0.016 + } + } +} diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-0.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-0.log new file mode 100644 index 00000000..bd633f96 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-0.log @@ -0,0 +1,132 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925/6901_3_10m_z33.tif --tolerance 10 --tolerance-slope 2 25 35 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 5051 × 5051 (10 m) | +| start vertices | 16384 | +| start triangles | 32258 | +| output vertices | 1549323 | +| output triangles | 3096202 | +| constraint edges | 2442 | +| mesh.gpkg | 810.0 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 5051 columns x 5051 rows, 10 m apart | `dem_grid` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| What refinement started from | every 40th DEM node | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 25 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 45.00° | 0.00 % | 1.80 % | 0.935° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 9 | 21 | 1083 | 1 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 9.999908093780277 | `max_error_m` | +| Tolerance on steep ground | 2 | `tolerance_slope_m` | +| Slope where the tolerance starts to tighten, and where it reaches the steep value | 25 to 35 | `tolerance_slope_deg` | +| DEM nodes held to less than the general tolerance because of their slope | 7917630 of 25512601 DEM nodes (31.0 %) | `slope_nodes_tightened` | +| Largest error as a share of the tolerance its slope allows | 1 | `max_error_slope_share_of_tolerance` | +| DEM | 6901_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 38132 | `line_points_checked` | +| Their largest difference from the mesh | 9.955078125 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 174 | `line_points_inserted` | +| DEM nodes the line check added | 28 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 30 | `refinement_rounds` | +| Points added | 1532234 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 3323985 | `edge_flips` | +| Points added to improve the starting mesh | 503 | `start_quality_points_inserted` | +| Tries skipped while improving it | 252 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 0 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 0 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 0 | `start_quality_lines_split` | +| Points refinement moved onto lines | 0 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 0 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 0 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| decode | 0.073 | 0.3 % | +| start mesh: build | 0.000 | 0.0 % | +| start mesh: node | 0.003 | 0.0 % | +| start mesh: triangulate | 0.029 | 0.1 % | +| start mesh: constraint edges | 0.007 | 0.0 % | +| slope | 0.108 | 0.5 % | +| refine | 2.167 | 9.5 % | +| refine: legalise start | 0.000 | 0.0 % | +| refine: start quality | 0.001 | 0.0 % | +| refine: scan (parallel) | 0.608 | 2.7 % | +| refine: split + flip (serial) | 1.294 | 5.7 % | +| refine: setup + output | 0.263 | 1.2 % | +| edge strip: generate | 0.001 | 0.0 % | +| edge strip: scan (parallel) | 0.010 | 0.0 % | +| edge strip: split + flip (serial) | 0.014 | 0.1 % | +| trim | 0.121 | 0.5 % | +| write: geopackage | 19.124 | 83.6 % | +| other | 1.231 | 5.4 % | +| **total** | **22.886** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.739 s, not included above. + +--- stderr --- +3096202 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 9.9999 m). Nodes on steep ground are within the tolerance their slope allows (largest error 100 % of it). + 23.89 real 30.46 user 1.24 sys + 2839871488 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 240095 page reclaims + 0 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 626 voluntary context switches + 3741 involuntary context switches + 297349144479 instructions retired + 98692703878 cycles elapsed + 2817100416 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-0.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-0.rss new file mode 100644 index 00000000..930be2b2 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-0.rss @@ -0,0 +1,208 @@ +0.04 18256 +0.15 52400 +0.26 128928 +0.43 337920 +0.56 347488 +0.67 383632 +0.79 439824 +0.95 502608 +1.06 511296 +1.18 571392 +1.31 631552 +1.43 641008 +1.54 659520 +1.66 670288 +1.77 760960 +1.91 929280 +2.03 937056 +2.14 960080 +2.26 979472 +2.37 1001232 +2.54 1013552 +2.64 1050336 +2.76 1050336 +2.87 1050336 +2.99 1086640 +3.10 1086640 +3.22 1086640 +3.33 1088256 +3.45 1088256 +3.56 1850240 +3.68 2575968 +3.82 2575968 +3.93 2612368 +4.05 2651904 +4.16 2643152 +4.28 2652400 +4.39 2652384 +4.51 2652384 +4.62 2652416 +4.74 2651408 +4.85 2652400 +4.96 2652432 +5.08 2650592 +5.19 2652464 +5.31 2652496 +5.42 2652896 +5.53 2651872 +5.65 2652512 +5.76 2653920 +5.88 2652528 +5.99 2650480 +6.11 2654944 +6.22 2652544 +6.33 2651872 +6.44 2655968 +6.55 2652896 +6.67 2655968 +6.78 2655968 +6.89 2654944 +7.00 2655968 +7.11 2655968 +7.22 2655968 +7.33 2655968 +7.45 2655968 +7.55 2655968 +7.67 2655968 +7.78 2655968 +7.90 2655968 +8.01 2655968 +8.13 2655968 +8.24 2655968 +8.36 2655968 +8.47 2655968 +8.59 2655968 +8.70 2655968 +8.82 2655968 +8.93 2655968 +9.05 2656096 +9.16 2658832 +9.27 2661152 +9.39 2663408 +9.50 2665680 +9.62 2667952 +9.73 2670128 +9.84 2672224 +9.96 2674416 +10.07 2676448 +10.18 2678480 +10.29 2680576 +10.40 2682512 +10.52 2684672 +10.63 2686672 +10.74 2688752 +10.85 2690592 +10.97 2692464 +11.08 2694544 +11.19 2696480 +11.30 2698416 +11.41 2700208 +11.52 2702160 +11.64 2704000 +11.75 2705968 +11.87 2707968 +11.98 2709792 +12.09 2711792 +12.21 2713696 +12.32 2715680 +12.44 2717568 +12.55 2719456 +12.67 2721408 +12.78 2723136 +12.89 2724880 +13.01 2726656 +13.12 2728624 +13.24 2732112 +13.35 2734544 +13.46 2736384 +13.57 2738256 +13.69 2740016 +13.80 2741792 +13.92 2743696 +14.03 2745312 +14.15 2747120 +14.26 2748912 +14.38 2750720 +14.49 2752560 +14.61 2754384 +14.72 2756160 +14.84 2757952 +14.95 2759536 +15.07 2761328 +15.18 2763088 +15.29 2764768 +15.40 2766400 +15.52 2768144 +15.63 2769856 +15.75 2771568 +15.86 2773312 +15.98 2354944 +16.09 2356576 +16.21 2358336 +16.32 2360032 +16.43 2361616 +16.54 2363296 +16.66 2365088 +16.77 2366816 +16.89 2368400 +17.00 2370112 +17.12 2371840 +17.23 2373600 +17.34 2375184 +17.46 2376912 +17.57 2378608 +17.69 2380368 +17.80 2381904 +17.92 2383520 +18.03 2385264 +18.15 2386912 +18.26 2388640 +18.38 2390160 +18.49 2391792 +18.61 2393488 +18.72 2395072 +18.84 2396560 +18.95 2398144 +19.07 2399808 +19.18 2401472 +19.29 2403056 +19.41 2404768 +19.52 2406368 +19.63 2408064 +19.75 2409680 +19.86 2411248 +19.98 2412752 +20.09 2414416 +20.20 2415936 +20.31 2417456 +20.42 2418928 +20.53 2420528 +20.64 2422096 +20.75 2423744 +20.86 2425248 +20.97 2426640 +21.09 2428352 +21.20 2429936 +21.31 2431584 +21.43 2433120 +21.54 2434736 +21.66 2436352 +21.77 2437920 +21.89 2439536 +22.00 2441088 +22.11 2442784 +22.23 2444432 +22.34 2446080 +22.46 2447680 +22.57 2449376 +22.69 2451136 +22.80 2452848 +22.92 2451392 +23.03 2424560 +23.15 508528 +23.26 600432 +23.38 706720 +23.49 826240 +23.61 900864 +23.72 918512 +23.84 1024912 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-1.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-1.log new file mode 100644 index 00000000..2b210281 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-1.log @@ -0,0 +1,132 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925/6901_3_10m_z33.tif --tolerance 10 --tolerance-slope 2 25 35 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 5051 × 5051 (10 m) | +| start vertices | 16384 | +| start triangles | 32258 | +| output vertices | 1549323 | +| output triangles | 3096202 | +| constraint edges | 2442 | +| mesh.gpkg | 810.0 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 5051 columns x 5051 rows, 10 m apart | `dem_grid` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| What refinement started from | every 40th DEM node | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 25 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 45.00° | 0.00 % | 1.80 % | 0.935° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 9 | 21 | 1083 | 1 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 9.999908093780277 | `max_error_m` | +| Tolerance on steep ground | 2 | `tolerance_slope_m` | +| Slope where the tolerance starts to tighten, and where it reaches the steep value | 25 to 35 | `tolerance_slope_deg` | +| DEM nodes held to less than the general tolerance because of their slope | 7917630 of 25512601 DEM nodes (31.0 %) | `slope_nodes_tightened` | +| Largest error as a share of the tolerance its slope allows | 1 | `max_error_slope_share_of_tolerance` | +| DEM | 6901_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 38132 | `line_points_checked` | +| Their largest difference from the mesh | 9.955078125 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 174 | `line_points_inserted` | +| DEM nodes the line check added | 28 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 30 | `refinement_rounds` | +| Points added | 1532234 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 3323985 | `edge_flips` | +| Points added to improve the starting mesh | 503 | `start_quality_points_inserted` | +| Tries skipped while improving it | 252 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 0 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 0 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 0 | `start_quality_lines_split` | +| Points refinement moved onto lines | 0 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 0 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 0 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| decode | 0.071 | 0.3 % | +| start mesh: build | 0.000 | 0.0 % | +| start mesh: node | 0.003 | 0.0 % | +| start mesh: triangulate | 0.029 | 0.1 % | +| start mesh: constraint edges | 0.007 | 0.0 % | +| slope | 0.107 | 0.5 % | +| refine | 2.120 | 9.3 % | +| refine: legalise start | 0.000 | 0.0 % | +| refine: start quality | 0.001 | 0.0 % | +| refine: scan (parallel) | 0.594 | 2.6 % | +| refine: split + flip (serial) | 1.263 | 5.6 % | +| refine: setup + output | 0.261 | 1.1 % | +| edge strip: generate | 0.001 | 0.0 % | +| edge strip: scan (parallel) | 0.010 | 0.0 % | +| edge strip: split + flip (serial) | 0.014 | 0.1 % | +| trim | 0.119 | 0.5 % | +| write: geopackage | 19.051 | 83.9 % | +| other | 1.182 | 5.2 % | +| **total** | **22.713** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.748 s, not included above. + +--- stderr --- +3096202 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 9.9999 m). Nodes on steep ground are within the tolerance their slope allows (largest error 100 % of it). + 23.72 real 30.31 user 1.20 sys + 2948382720 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 240119 page reclaims + 19 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 682 voluntary context switches + 3591 involuntary context switches + 297141373055 instructions retired + 98120161509 cycles elapsed + 2925923016 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-1.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-1.rss new file mode 100644 index 00000000..e7614d05 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-1.rss @@ -0,0 +1,206 @@ +0.03 18176 +0.15 52992 +0.26 175552 +0.42 343392 +0.55 352944 +0.67 389296 +0.78 445536 +0.91 505424 +1.02 514880 +1.14 576480 +1.27 636960 +1.39 647824 +1.49 666208 +1.61 676416 +1.72 767104 +1.86 934560 +1.97 942048 +2.08 964976 +2.20 984368 +2.31 1006032 +2.49 1018784 +2.61 1055424 +2.72 1055424 +2.84 1055424 +2.95 1091728 +3.07 1091728 +3.19 1091728 +3.30 1093312 +3.41 1093312 +3.53 2142048 +3.68 2581168 +3.79 2617392 +3.90 2620704 +4.02 2657520 +4.13 2657472 +4.24 2649280 +4.36 2657456 +4.47 2657488 +4.59 2650544 +4.70 2657472 +4.82 2657632 +4.93 2651488 +5.04 2657616 +5.16 2657648 +5.27 2658320 +5.39 2658048 +5.50 2657664 +5.62 2659072 +5.73 2659072 +5.84 2657680 +5.96 2660096 +6.07 2656848 +6.19 2657696 +6.30 2661120 +6.42 2658048 +6.53 2658048 +6.65 2661120 +6.76 2660096 +6.88 2661120 +6.99 2661120 +7.11 2661120 +7.22 2661120 +7.34 2661120 +7.45 2661120 +7.57 2661120 +7.68 2661120 +7.80 2661120 +7.91 2661120 +8.02 2661120 +8.14 2661120 +8.25 2661120 +8.37 2661120 +8.48 2661120 +8.60 2661120 +8.71 2661120 +8.83 2661120 +8.94 2662032 +9.06 2664624 +9.17 2667008 +9.29 2669264 +9.40 2671456 +9.51 2673776 +9.63 2675904 +9.74 2678064 +9.86 2680240 +9.97 2682224 +10.08 2684400 +10.20 2686480 +10.31 2688512 +10.43 2690672 +10.54 2692736 +10.65 2694688 +10.76 2696656 +10.88 2698592 +10.99 2700640 +11.10 2702624 +11.22 2704656 +11.33 2706624 +11.44 2708432 +11.55 2710272 +11.67 2712224 +11.78 2714192 +11.90 2716032 +12.01 2717888 +12.12 2719808 +12.23 2721728 +12.35 2723696 +12.46 2725568 +12.58 2727424 +12.69 2729200 +12.80 2731008 +12.92 2732848 +13.03 2735040 +13.14 2738432 +13.26 2740496 +13.37 2742416 +13.48 2744048 +13.59 2745888 +13.71 2747712 +13.82 2749472 +13.94 2751088 +14.05 2752896 +14.17 2754640 +14.28 2756512 +14.39 2758304 +14.50 2759984 +14.62 2761760 +14.73 2763632 +14.85 2765296 +14.96 2767008 +15.08 2768800 +15.19 2770576 +15.31 2772288 +15.42 2773984 +15.53 2775696 +15.65 2777424 +15.76 2779120 +15.88 2780880 +15.99 2782656 +16.10 2784144 +16.22 2785840 +16.33 2787488 +16.44 2789152 +16.55 2790832 +16.66 2792464 +16.78 2794256 +16.89 2795936 +17.01 2797568 +17.12 2799232 +17.24 2800832 +17.35 2802560 +17.47 2804224 +17.58 2806000 +17.70 2807520 +17.81 2809264 +17.92 2810992 +18.04 2812496 +18.16 2814064 +18.27 2815696 +18.38 2817296 +18.50 2818960 +18.61 2820640 +18.73 2822080 +18.84 2823760 +18.96 2825328 +19.07 2826864 +19.18 2828432 +19.30 2830144 +19.41 2831856 +19.53 2833536 +19.64 2835136 +19.76 2836688 +19.87 2838320 +19.99 2839952 +20.10 2841632 +20.21 2843136 +20.33 2844896 +20.44 2846528 +20.55 2848096 +20.67 2849728 +20.78 2851312 +20.90 2853056 +21.01 2854528 +21.12 2856000 +21.23 2857568 +21.34 2859136 +21.45 2860592 +21.57 2862240 +21.68 2863840 +21.79 2865376 +21.91 2867056 +22.02 2868624 +22.13 2870272 +22.25 2871904 +22.36 2873568 +22.48 2875312 +22.59 2877072 +22.71 2878816 +22.82 2849264 +22.94 420928 +23.05 575312 +23.17 668224 +23.28 808032 +23.40 856416 +23.51 904800 +23.63 1001568 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-2.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-2.log new file mode 100644 index 00000000..4630b339 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-2.log @@ -0,0 +1,132 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925/6901_3_10m_z33.tif --tolerance 10 --tolerance-slope 2 25 35 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 5051 × 5051 (10 m) | +| start vertices | 16384 | +| start triangles | 32258 | +| output vertices | 1549323 | +| output triangles | 3096202 | +| constraint edges | 2442 | +| mesh.gpkg | 810.0 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 5051 columns x 5051 rows, 10 m apart | `dem_grid` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| What refinement started from | every 40th DEM node | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 25 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 45.00° | 0.00 % | 1.80 % | 0.935° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 9 | 21 | 1083 | 1 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 9.999908093780277 | `max_error_m` | +| Tolerance on steep ground | 2 | `tolerance_slope_m` | +| Slope where the tolerance starts to tighten, and where it reaches the steep value | 25 to 35 | `tolerance_slope_deg` | +| DEM nodes held to less than the general tolerance because of their slope | 7917630 of 25512601 DEM nodes (31.0 %) | `slope_nodes_tightened` | +| Largest error as a share of the tolerance its slope allows | 1 | `max_error_slope_share_of_tolerance` | +| DEM | 6901_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 38132 | `line_points_checked` | +| Their largest difference from the mesh | 9.955078125 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 174 | `line_points_inserted` | +| DEM nodes the line check added | 28 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 30 | `refinement_rounds` | +| Points added | 1532234 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 3323985 | `edge_flips` | +| Points added to improve the starting mesh | 503 | `start_quality_points_inserted` | +| Tries skipped while improving it | 252 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 0 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 0 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 0 | `start_quality_lines_split` | +| Points refinement moved onto lines | 0 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 0 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 0 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| decode | 0.074 | 0.3 % | +| start mesh: build | 0.000 | 0.0 % | +| start mesh: node | 0.003 | 0.0 % | +| start mesh: triangulate | 0.030 | 0.1 % | +| start mesh: constraint edges | 0.007 | 0.0 % | +| slope | 0.114 | 0.5 % | +| refine | 2.217 | 9.2 % | +| refine: legalise start | 0.000 | 0.0 % | +| refine: start quality | 0.001 | 0.0 % | +| refine: scan (parallel) | 0.622 | 2.6 % | +| refine: split + flip (serial) | 1.326 | 5.5 % | +| refine: setup + output | 0.267 | 1.1 % | +| edge strip: generate | 0.001 | 0.0 % | +| edge strip: scan (parallel) | 0.010 | 0.0 % | +| edge strip: split + flip (serial) | 0.013 | 0.1 % | +| trim | 0.118 | 0.5 % | +| write: geopackage | 20.154 | 84.1 % | +| other | 1.232 | 5.1 % | +| **total** | **23.971** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.756 s, not included above. + +--- stderr --- +3096202 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 9.9999 m). Nodes on steep ground are within the tolerance their slope allows (largest error 100 % of it). + 25.00 real 31.31 user 1.34 sys + 2903326720 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 240133 page reclaims + 19 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 673 voluntary context switches + 9234 involuntary context switches + 297733466250 instructions retired + 101881937239 cycles elapsed + 2880604872 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-2.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-2.rss new file mode 100644 index 00000000..6831852d --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-2.rss @@ -0,0 +1,219 @@ +0.04 20064 +0.15 54288 +0.27 150416 +0.40 342432 +0.53 345344 +0.65 373072 +0.78 420400 +0.89 486544 +1.00 508864 +1.14 573056 +1.26 582992 +1.37 639872 +1.48 649424 +1.60 668224 +1.74 688992 +1.85 769584 +1.96 935120 +2.07 942176 +2.19 964720 +2.30 983872 +2.42 1005200 +2.58 1018624 +2.70 1055232 +2.81 1055232 +2.93 1055232 +3.04 1091536 +3.16 1091536 +3.27 1091536 +3.39 1093088 +3.50 1093168 +3.62 1439424 +3.73 2580896 +3.88 2580912 +3.99 2617296 +4.11 2657344 +4.22 2648128 +4.34 2657296 +4.44 2649360 +4.56 2657296 +4.67 2657328 +4.78 2650608 +4.90 2657328 +5.01 2657360 +5.13 2659920 +5.24 2657344 +5.35 2657376 +5.46 2659696 +5.57 2657776 +5.69 2657392 +5.80 2658624 +5.91 2658800 +6.03 2657408 +6.14 2659824 +6.25 2659824 +6.37 2657424 +6.48 2660848 +6.60 2660848 +6.71 2657776 +6.82 2660848 +6.94 2660848 +7.05 2659824 +7.17 2660848 +7.28 2660848 +7.39 2660848 +7.51 2660848 +7.62 2660848 +7.73 2660848 +7.84 2660848 +7.95 2660848 +8.07 2660848 +8.18 2660848 +8.30 2660848 +8.41 2660848 +8.53 2660848 +8.64 2660848 +8.76 2660848 +8.87 2660848 +8.98 2660848 +9.10 2660848 +9.21 2661568 +9.32 2664032 +9.44 2666336 +9.55 2668480 +9.66 2670672 +9.77 2672800 +9.89 2674864 +10.00 2676992 +10.12 2678976 +10.23 2680992 +10.34 2683072 +10.46 2685136 +10.57 2686960 +10.68 2688896 +10.79 2690944 +10.91 2692864 +11.02 2694736 +11.13 2696512 +11.25 2698384 +11.36 2700416 +11.48 2702256 +11.59 2704160 +11.70 2706000 +11.82 2707744 +11.92 2709520 +12.03 2711232 +12.15 2713040 +12.26 2714800 +12.38 2716656 +12.49 2718416 +12.61 2720320 +12.72 2722096 +12.82 2723808 +12.94 2725568 +13.05 2727248 +13.17 2728784 +13.28 2730448 +13.39 2732208 +13.51 2734384 +13.62 2737696 +13.74 2739760 +13.85 2741568 +13.97 2743296 +14.08 2744944 +14.19 2746576 +14.31 2748352 +14.42 2749888 +14.54 2751440 +14.65 2753088 +14.76 2754688 +14.88 2756416 +14.99 2758064 +15.10 2759696 +15.21 2761328 +15.33 2762992 +15.44 2764448 +15.55 2766096 +15.67 2767776 +15.78 2769360 +15.89 2770928 +16.01 2772480 +16.12 2774000 +16.23 2775472 +16.35 2777024 +16.46 2778576 +16.57 2780224 +16.69 2781792 +16.80 2783376 +16.92 2784992 +17.03 2786544 +17.14 2788032 +17.25 2789648 +17.37 2791216 +17.48 2792768 +17.60 2794352 +17.71 2795904 +17.82 2797472 +17.94 2799024 +18.05 2800496 +18.16 2802112 +18.28 2803648 +18.39 2805328 +18.51 2806800 +18.62 2808368 +18.74 2809952 +18.85 2811488 +18.97 2813056 +19.08 2814496 +19.19 2815984 +19.30 2817376 +19.41 2818928 +19.52 2820368 +19.64 2821792 +19.75 2823328 +19.86 2824944 +19.98 2826448 +20.09 2827840 +20.21 2829456 +20.32 2830896 +20.43 2832448 +20.54 2833936 +20.65 2387600 +20.76 1718224 +20.87 1719648 +20.99 1721184 +21.10 1722704 +21.22 1724112 +21.33 1725584 +21.44 1727120 +21.56 1728656 +21.67 1730160 +21.79 1731568 +21.90 1733104 +22.02 1734672 +22.13 1736160 +22.24 1737648 +22.36 1739104 +22.46 1740512 +22.58 1741984 +22.69 1743488 +22.80 1744944 +22.92 1746304 +23.03 1747792 +23.15 1749328 +23.26 1750848 +23.38 1752336 +23.49 1753776 +23.60 1755280 +23.71 1756784 +23.82 1758352 +23.94 1759936 +24.05 1730928 +24.17 1131344 +24.28 545040 +24.40 624384 +24.51 736512 +24.62 856800 +24.73 905760 +24.84 929952 +24.95 1029904 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-3.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-3.log new file mode 100644 index 00000000..a656dc8d --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-3.log @@ -0,0 +1,132 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925/6901_3_10m_z33.tif --tolerance 10 --tolerance-slope 2 25 35 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.gpkg --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 5051 × 5051 (10 m) | +| start vertices | 16384 | +| start triangles | 32258 | +| output vertices | 1549323 | +| output triangles | 3096202 | +| constraint edges | 2442 | +| mesh.gpkg | 810.0 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 5051 columns x 5051 rows, 10 m apart | `dem_grid` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| What refinement started from | every 40th DEM node | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 25 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 45.00° | 0.00 % | 1.80 % | 0.935° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 9 | 21 | 1083 | 1 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 9.999908093780277 | `max_error_m` | +| Tolerance on steep ground | 2 | `tolerance_slope_m` | +| Slope where the tolerance starts to tighten, and where it reaches the steep value | 25 to 35 | `tolerance_slope_deg` | +| DEM nodes held to less than the general tolerance because of their slope | 7917630 of 25512601 DEM nodes (31.0 %) | `slope_nodes_tightened` | +| Largest error as a share of the tolerance its slope allows | 1 | `max_error_slope_share_of_tolerance` | +| DEM | 6901_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 38132 | `line_points_checked` | +| Their largest difference from the mesh | 9.955078125 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 174 | `line_points_inserted` | +| DEM nodes the line check added | 28 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 30 | `refinement_rounds` | +| Points added | 1532234 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 3323985 | `edge_flips` | +| Points added to improve the starting mesh | 503 | `start_quality_points_inserted` | +| Tries skipped while improving it | 252 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 0 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 0 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 0 | `start_quality_lines_split` | +| Points refinement moved onto lines | 0 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 0 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 0 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| decode | 0.072 | 0.3 % | +| start mesh: build | 0.000 | 0.0 % | +| start mesh: node | 0.003 | 0.0 % | +| start mesh: triangulate | 0.030 | 0.1 % | +| start mesh: constraint edges | 0.007 | 0.0 % | +| slope | 0.111 | 0.5 % | +| refine | 2.199 | 9.5 % | +| refine: legalise start | 0.000 | 0.0 % | +| refine: start quality | 0.001 | 0.0 % | +| refine: scan (parallel) | 0.618 | 2.7 % | +| refine: split + flip (serial) | 1.310 | 5.7 % | +| refine: setup + output | 0.270 | 1.2 % | +| edge strip: generate | 0.001 | 0.0 % | +| edge strip: scan (parallel) | 0.010 | 0.0 % | +| edge strip: split + flip (serial) | 0.013 | 0.1 % | +| trim | 0.119 | 0.5 % | +| write: geopackage | 19.368 | 83.7 % | +| other | 1.212 | 5.2 % | +| **total** | **23.145** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.734 s, not included above. + +--- stderr --- +3096202 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 9.9999 m). Nodes on steep ground are within the tolerance their slope allows (largest error 100 % of it). + 24.15 real 30.47 user 1.26 sys + 2912583680 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 239481 page reclaims + 0 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 610 voluntary context switches + 7029 involuntary context switches + 297349347173 instructions retired + 99156106175 cycles elapsed + 2889845424 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-3.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-3.rss new file mode 100644 index 00000000..c087592b --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-gpkg-3.rss @@ -0,0 +1,211 @@ +0.04 19104 +0.15 51920 +0.27 75440 +0.41 344416 +0.53 346816 +0.65 373984 +0.78 421552 +0.89 488384 +1.01 511312 +1.14 574208 +1.25 584336 +1.37 642128 +1.52 663632 +1.63 674080 +1.75 692160 +1.86 772704 +1.97 938336 +2.10 961744 +2.23 981904 +2.35 997520 +2.46 1015968 +2.62 1019568 +2.73 1057296 +2.85 1057296 +2.96 1057296 +3.07 1093600 +3.19 1093600 +3.30 1093600 +3.42 1095216 +3.53 1095216 +3.64 1935344 +3.81 2583088 +3.92 2619328 +4.04 2622464 +4.15 2659264 +4.27 2659216 +4.38 2651024 +4.49 2659216 +4.61 2659248 +4.72 2652080 +4.83 2659232 +4.94 2654096 +5.05 2659264 +5.16 2659248 +5.28 2654384 +5.39 2659280 +5.50 2659680 +5.62 2655328 +5.73 2659296 +5.85 2660704 +5.96 2659312 +6.07 2657264 +6.18 2661728 +6.29 2659328 +6.41 2658304 +6.52 2662752 +6.63 2659680 +6.75 2662576 +6.85 2662752 +6.97 2661728 +7.08 2662752 +7.19 2662752 +7.30 2662752 +7.42 2662752 +7.52 2662752 +7.64 2662752 +7.75 2662752 +7.87 2662752 +7.98 2662752 +8.09 2662752 +8.20 2662752 +8.32 2662752 +8.43 2662752 +8.55 2662752 +8.65 2662752 +8.76 2662752 +8.87 2662752 +8.99 2662752 +9.10 2662752 +9.22 2664816 +9.33 2667184 +9.44 2669488 +9.56 2671696 +9.67 2673904 +9.78 2676016 +9.90 2678096 +10.01 2680272 +10.13 2682336 +10.24 2684352 +10.36 2686512 +10.47 2688464 +10.59 2690368 +10.70 2692448 +10.81 2694432 +10.93 2696320 +11.04 2698272 +11.16 2700128 +11.27 2702128 +11.39 2704064 +11.50 2706000 +11.62 2707920 +11.73 2709680 +11.84 2711552 +11.96 2713408 +12.07 2715296 +12.18 2716960 +12.30 2718848 +12.41 2720608 +12.53 2722480 +12.64 2724240 +12.75 2726000 +12.87 2727856 +12.98 2729568 +13.10 2731376 +13.21 2733232 +13.33 2735152 +13.44 2738528 +13.56 2741184 +13.66 2743024 +13.78 2744928 +13.89 2746656 +14.01 2748480 +14.12 2750256 +14.23 2751840 +14.35 2753536 +14.46 2755328 +14.58 2757120 +14.69 2758976 +14.81 2760768 +14.92 2762528 +15.04 2764384 +15.15 2766128 +15.26 2767808 +15.38 2769664 +15.49 2771408 +15.61 2773168 +15.72 2774720 +15.84 2776320 +15.95 2777936 +16.07 2779520 +16.17 2781024 +16.29 2782608 +16.40 2784368 +16.52 2785968 +16.63 2787680 +16.75 2789376 +16.86 2791120 +16.98 2792864 +17.09 2794528 +17.20 2796224 +17.32 2797952 +17.43 2799712 +17.54 2801360 +17.66 2803024 +17.77 2804736 +17.89 2806464 +18.00 2808080 +18.12 2809616 +18.23 2811248 +18.35 2812896 +18.46 2814560 +18.57 2816112 +18.68 2817696 +18.80 2819312 +18.91 2820976 +19.03 2822560 +19.14 2824048 +19.26 2825744 +19.37 2827456 +19.48 2829008 +19.59 2830544 +19.70 2832160 +19.81 2833776 +19.93 2835424 +20.04 2836928 +20.16 2838448 +20.27 2839888 +20.39 2841568 +20.50 2843184 +20.62 1818912 +20.73 1820576 +20.84 1822256 +20.96 1823888 +21.07 1825440 +21.19 1826896 +21.30 1828592 +21.42 1830208 +21.53 1831856 +21.65 1833440 +21.76 1835088 +21.88 1836704 +21.99 1838336 +22.11 1839936 +22.22 1841344 +22.34 1842896 +22.45 1844544 +22.56 1846192 +22.67 1847776 +22.79 1849296 +22.90 1851040 +23.02 1852736 +23.13 1854464 +23.25 1825584 +23.36 784096 +23.48 562048 +23.59 656336 +23.71 810864 +23.82 859248 +23.94 907632 +24.05 949904 +24.16 0 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-0.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-0.log new file mode 100644 index 00000000..78ebd1d2 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-0.log @@ -0,0 +1,133 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925/6901_3_10m_z33.tif --tolerance 10 --tolerance-slope 2 25 35 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk --binary --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 5051 × 5051 (10 m) | +| start vertices | 16384 | +| start triangles | 32258 | +| output vertices | 1549323 | +| output triangles | 3096202 | +| constraint edges | 2442 | +| mesh.vtk | 111.5 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 5051 columns x 5051 rows, 10 m apart | `dem_grid` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| What refinement started from | every 40th DEM node | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 25 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 45.00° | 0.00 % | 1.80 % | 0.935° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 9 | 21 | 1083 | 1 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 9.999908093780277 | `max_error_m` | +| Tolerance on steep ground | 2 | `tolerance_slope_m` | +| Slope where the tolerance starts to tighten, and where it reaches the steep value | 25 to 35 | `tolerance_slope_deg` | +| DEM nodes held to less than the general tolerance because of their slope | 7917630 of 25512601 DEM nodes (31.0 %) | `slope_nodes_tightened` | +| Largest error as a share of the tolerance its slope allows | 1 | `max_error_slope_share_of_tolerance` | +| DEM | 6901_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 38132 | `line_points_checked` | +| Their largest difference from the mesh | 9.955078125 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 174 | `line_points_inserted` | +| DEM nodes the line check added | 28 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 30 | `refinement_rounds` | +| Points added | 1532234 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 3323985 | `edge_flips` | +| Points added to improve the starting mesh | 503 | `start_quality_points_inserted` | +| Tries skipped while improving it | 252 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 0 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 0 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 0 | `start_quality_lines_split` | +| Points refinement moved onto lines | 0 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 0 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 0 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| decode | 0.071 | 1.9 % | +| start mesh: build | 0.000 | 0.0 % | +| start mesh: node | 0.003 | 0.1 % | +| start mesh: triangulate | 0.030 | 0.8 % | +| start mesh: constraint edges | 0.007 | 0.2 % | +| slope | 0.106 | 2.9 % | +| refine | 2.102 | 56.9 % | +| refine: legalise start | 0.000 | 0.0 % | +| refine: start quality | 0.001 | 0.0 % | +| refine: scan (parallel) | 0.591 | 16.0 % | +| refine: split + flip (serial) | 1.248 | 33.8 % | +| refine: setup + output | 0.261 | 7.1 % | +| edge strip: generate | 0.001 | 0.0 % | +| edge strip: scan (parallel) | 0.010 | 0.3 % | +| edge strip: split + flip (serial) | 0.014 | 0.4 % | +| trim | 0.120 | 3.2 % | +| write: encode | 0.040 | 1.1 % | +| write: disk | 0.015 | 0.4 % | +| other | 1.180 | 31.9 % | +| **total** | **3.696** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.697 s, not included above. + +--- stderr --- +3096202 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 9.9999 m). Nodes on steep ground are within the tolerance their slope allows (largest error 100 % of it). + 4.67 real 12.14 user 0.40 sys + 2776596480 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 182126 page reclaims + 0 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 17 voluntary context switches + 3172 involuntary context switches + 75989360503 instructions retired + 37236411576 cycles elapsed + 2755807848 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-0.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-0.rss new file mode 100644 index 00000000..f053bb69 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-0.rss @@ -0,0 +1,39 @@ +0.03 18240 +0.15 52640 +0.26 159072 +0.43 338880 +0.55 348432 +0.67 384672 +0.78 440816 +0.90 487664 +1.01 510784 +1.13 572624 +1.25 632480 +1.37 643072 +1.48 661984 +1.61 684624 +1.72 763920 +1.84 930032 +1.95 937712 +2.07 960592 +2.18 979936 +2.30 1001744 +2.46 1014208 +2.57 1050832 +2.69 1050832 +2.80 1050832 +2.92 1087136 +3.03 1087136 +3.15 1087136 +3.26 1088752 +3.38 1088752 +3.49 1935104 +3.69 2576608 +3.80 2612832 +3.92 2638592 +4.03 2638608 +4.15 2662800 +4.26 2679504 +4.37 2711248 +4.48 2711248 +4.59 2711360 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-1.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-1.log new file mode 100644 index 00000000..6b0eb7e9 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-1.log @@ -0,0 +1,133 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925/6901_3_10m_z33.tif --tolerance 10 --tolerance-slope 2 25 35 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk --binary --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 5051 × 5051 (10 m) | +| start vertices | 16384 | +| start triangles | 32258 | +| output vertices | 1549323 | +| output triangles | 3096202 | +| constraint edges | 2442 | +| mesh.vtk | 111.5 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 5051 columns x 5051 rows, 10 m apart | `dem_grid` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| What refinement started from | every 40th DEM node | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 25 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 45.00° | 0.00 % | 1.80 % | 0.935° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 9 | 21 | 1083 | 1 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 9.999908093780277 | `max_error_m` | +| Tolerance on steep ground | 2 | `tolerance_slope_m` | +| Slope where the tolerance starts to tighten, and where it reaches the steep value | 25 to 35 | `tolerance_slope_deg` | +| DEM nodes held to less than the general tolerance because of their slope | 7917630 of 25512601 DEM nodes (31.0 %) | `slope_nodes_tightened` | +| Largest error as a share of the tolerance its slope allows | 1 | `max_error_slope_share_of_tolerance` | +| DEM | 6901_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 38132 | `line_points_checked` | +| Their largest difference from the mesh | 9.955078125 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 174 | `line_points_inserted` | +| DEM nodes the line check added | 28 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 30 | `refinement_rounds` | +| Points added | 1532234 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 3323985 | `edge_flips` | +| Points added to improve the starting mesh | 503 | `start_quality_points_inserted` | +| Tries skipped while improving it | 252 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 0 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 0 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 0 | `start_quality_lines_split` | +| Points refinement moved onto lines | 0 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 0 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 0 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| decode | 0.073 | 2.0 % | +| start mesh: build | 0.000 | 0.0 % | +| start mesh: node | 0.003 | 0.1 % | +| start mesh: triangulate | 0.029 | 0.8 % | +| start mesh: constraint edges | 0.007 | 0.2 % | +| slope | 0.106 | 2.8 % | +| refine | 2.104 | 56.8 % | +| refine: legalise start | 0.000 | 0.0 % | +| refine: start quality | 0.001 | 0.0 % | +| refine: scan (parallel) | 0.595 | 16.1 % | +| refine: split + flip (serial) | 1.246 | 33.6 % | +| refine: setup + output | 0.262 | 7.1 % | +| edge strip: generate | 0.001 | 0.0 % | +| edge strip: scan (parallel) | 0.010 | 0.3 % | +| edge strip: split + flip (serial) | 0.013 | 0.4 % | +| trim | 0.119 | 3.2 % | +| write: encode | 0.039 | 1.1 % | +| write: disk | 0.016 | 0.4 % | +| other | 1.184 | 32.0 % | +| **total** | **3.705** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.694 s, not included above. + +--- stderr --- +3096202 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 9.9999 m). Nodes on steep ground are within the tolerance their slope allows (largest error 100 % of it). + 4.67 real 12.13 user 0.42 sys + 2777055232 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 183063 page reclaims + 0 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 14 voluntary context switches + 3276 involuntary context switches + 75920373945 instructions retired + 37320913915 cycles elapsed + 2756266600 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-1.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-1.rss new file mode 100644 index 00000000..7f5ce9a4 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-1.rss @@ -0,0 +1,40 @@ +0.03 18288 +0.15 52928 +0.26 173744 +0.44 338544 +0.56 354064 +0.67 385504 +0.78 442000 +0.93 504000 +1.08 569040 +1.20 579456 +1.31 637056 +1.45 658544 +1.56 668352 +1.67 686384 +1.78 767392 +1.90 933648 +2.01 956816 +2.13 976784 +2.25 992880 +2.36 1012816 +2.50 1014496 +2.62 1051312 +2.72 1051312 +2.83 1051312 +2.94 1087616 +3.05 1087616 +3.16 1087616 +3.28 1089248 +3.39 1089248 +3.50 1963328 +3.62 2577120 +3.73 2576960 +3.84 2613616 +3.96 2639024 +4.07 2639024 +4.18 2663216 +4.30 2709296 +4.41 2711664 +4.53 2711664 +4.64 2711904 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-2.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-2.log new file mode 100644 index 00000000..6343eabf --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-2.log @@ -0,0 +1,133 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925/6901_3_10m_z33.tif --tolerance 10 --tolerance-slope 2 25 35 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk --binary --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 5051 × 5051 (10 m) | +| start vertices | 16384 | +| start triangles | 32258 | +| output vertices | 1549323 | +| output triangles | 3096202 | +| constraint edges | 2442 | +| mesh.vtk | 111.5 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 5051 columns x 5051 rows, 10 m apart | `dem_grid` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| What refinement started from | every 40th DEM node | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 25 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 45.00° | 0.00 % | 1.80 % | 0.935° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 9 | 21 | 1083 | 1 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 9.999908093780277 | `max_error_m` | +| Tolerance on steep ground | 2 | `tolerance_slope_m` | +| Slope where the tolerance starts to tighten, and where it reaches the steep value | 25 to 35 | `tolerance_slope_deg` | +| DEM nodes held to less than the general tolerance because of their slope | 7917630 of 25512601 DEM nodes (31.0 %) | `slope_nodes_tightened` | +| Largest error as a share of the tolerance its slope allows | 1 | `max_error_slope_share_of_tolerance` | +| DEM | 6901_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 38132 | `line_points_checked` | +| Their largest difference from the mesh | 9.955078125 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 174 | `line_points_inserted` | +| DEM nodes the line check added | 28 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 30 | `refinement_rounds` | +| Points added | 1532234 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 3323985 | `edge_flips` | +| Points added to improve the starting mesh | 503 | `start_quality_points_inserted` | +| Tries skipped while improving it | 252 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 0 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 0 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 0 | `start_quality_lines_split` | +| Points refinement moved onto lines | 0 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 0 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 0 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| decode | 0.071 | 1.8 % | +| start mesh: build | 0.000 | 0.0 % | +| start mesh: node | 0.003 | 0.1 % | +| start mesh: triangulate | 0.030 | 0.8 % | +| start mesh: constraint edges | 0.007 | 0.2 % | +| slope | 0.115 | 3.0 % | +| refine | 2.178 | 56.8 % | +| refine: legalise start | 0.000 | 0.0 % | +| refine: start quality | 0.001 | 0.0 % | +| refine: scan (parallel) | 0.598 | 15.6 % | +| refine: split + flip (serial) | 1.301 | 33.9 % | +| refine: setup + output | 0.278 | 7.2 % | +| edge strip: generate | 0.001 | 0.0 % | +| edge strip: scan (parallel) | 0.010 | 0.2 % | +| edge strip: split + flip (serial) | 0.013 | 0.3 % | +| trim | 0.121 | 3.2 % | +| write: encode | 0.042 | 1.1 % | +| write: disk | 0.016 | 0.4 % | +| other | 1.228 | 32.0 % | +| **total** | **3.833** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.703 s, not included above. + +--- stderr --- +3096202 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 9.9999 m). Nodes on steep ground are within the tolerance their slope allows (largest error 100 % of it). + 4.82 real 12.13 user 0.43 sys + 2775777280 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 182043 page reclaims + 0 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 17 voluntary context switches + 4456 involuntary context switches + 75956319370 instructions retired + 37477369620 cycles elapsed + 2754972240 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-2.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-2.rss new file mode 100644 index 00000000..5f741685 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-2.rss @@ -0,0 +1,41 @@ +0.03 18272 +0.15 54032 +0.27 180880 +0.39 337424 +0.52 339872 +0.63 367296 +0.75 414720 +0.87 481920 +0.98 505472 +1.10 567408 +1.21 593184 +1.33 636912 +1.45 656592 +1.56 666496 +1.68 684464 +1.78 764176 +1.90 929760 +2.01 937056 +2.13 959840 +2.24 978416 +2.35 999504 +2.51 1012736 +2.62 1015376 +2.73 1049392 +2.85 1049392 +2.96 1049392 +3.07 1085696 +3.19 1085696 +3.30 1085696 +3.42 1087312 +3.53 1087312 +3.65 2281648 +3.82 2575024 +3.94 2612064 +4.05 2637792 +4.17 2637808 +4.28 2662000 +4.39 2663408 +4.50 2710448 +4.62 2710448 +4.73 2710560 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-3.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-3.log new file mode 100644 index 00000000..89f8adea --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-3.log @@ -0,0 +1,133 @@ +/private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk +# rasputin mesh — statistics + +`rasputin mesh --dem /Users/skavhaug/projects/rasputin_data/DTM10_UTM33_20260925/6901_3_10m_z33.tif --tolerance 10 --tolerance-slope 2 25 35 --out /private/tmp/claude-501/-Users-skavhaug-projects-rasputin/6a989a29-4492-430f-99b0-9fe3058f931a/scratchpad/rasputin-writer-time/mesh.vtk --binary --stats -` + +bounds checks: on (libc++ fast) + +## Sizes + +| item | count | +|---|---| +| DEM nodes | 5051 × 5051 (10 m) | +| start vertices | 16384 | +| start triangles | 32258 | +| output vertices | 1549323 | +| output triangles | 3096202 | +| constraint edges | 2442 | +| mesh.vtk | 111.5 MB | + +## Inputs + +| item | value | name | +|---|---|---| +| DEM grid size and spacing | 5051 columns x 5051 rows, 10 m apart | `dem_grid` | +| Unit of z | metres (assumed: the DEM file does not say) | `dem_vertical_unit` | +| What refinement started from | every 40th DEM node | `start_mesh` | +| Starting mesh improved to this smallest angle (0 = off) | 25 | `start_min_angle_deg` | +| Starting mesh: a point is added only if it raises the smallest angle around it by at least this many degrees (negative = always added) | 0 | `start_quality_gain_deg` | +| Points very close to a line were moved onto it | on | `snap_to_lines` | + +## Quality (plan view, x/y) + +| metric | median | < 1° | < 10° | worst | +|---|---|---|---|---| +| minimum angle | 45.00° | 0.00 % | 1.80 % | 0.935° | + +| metric | median | p99 | max | ≥ 12 | ≥ 20 | +|---|---|---|---|---|---| +| vertex degree (triangles) | 6 | 9 | 21 | 1083 | 1 | + +## Result + +| item | value | name | +|---|---|---| +| Coordinate system | EPSG:25833 | `crs` | +| Tolerance | 10 | `tolerance_m` | +| Largest height error | 9.999908093780277 | `max_error_m` | +| Tolerance on steep ground | 2 | `tolerance_slope_m` | +| Slope where the tolerance starts to tighten, and where it reaches the steep value | 25 to 35 | `tolerance_slope_deg` | +| DEM nodes held to less than the general tolerance because of their slope | 7917630 of 25512601 DEM nodes (31.0 %) | `slope_nodes_tightened` | +| Largest error as a share of the tolerance its slope allows | 1 | `max_error_slope_share_of_tolerance` | +| DEM | 6901_3_10m_z33.tif | `dem_source` | +| Vertices removed on NoData | 0 | `nodata_vertices_removed` | +| DEM nodes on a vertex (to rounding), not compared | 0 | `dem_nodes_at_vertices` | +| Their largest difference | 0 | `dem_nodes_at_vertices_max_error_m` | +| Points along lines (grid-line crossings and halfway between) compared | 38132 | `line_points_checked` | +| Their largest difference from the mesh | 9.955078125 | `line_max_error_m` | +| Points along lines on NoData cells, not compared | 0 | `line_points_on_nodata` | +| Points along lines that could not be added | 0 | `line_points_refused` | +| Their largest difference from the mesh | 0 | `line_points_refused_max_error_m` | +| Points along lines added | 174 | `line_points_inserted` | +| DEM nodes the line check added | 28 | `line_check_dem_nodes_inserted` | +| Points along lines dropped as duplicates | 0 | `line_points_duplicate` | +| Self-check: DEM nodes with data left outside the mesh | 0 | `dem_nodes_outside_mesh` | +| Refinement passes | 30 | `refinement_rounds` | +| Points added | 1532234 | `points_inserted` | +| Of them, into triangles with a NoData corner | 0 | `points_inserted_on_nodata` | +| Edge swaps | 3323985 | `edge_flips` | +| Points added to improve the starting mesh | 503 | `start_quality_points_inserted` | +| Tries skipped while improving it | 252 | `start_quality_points_skipped` | +| Tries that would not have improved the angles | 0 | `start_quality_points_without_gain` | +| Points moved onto lines while improving the starting mesh | 0 | `start_quality_points_snapped_to_lines` | +| Land-cover and outline lines split to improve angles | 0 | `start_quality_lines_split` | +| Points refinement moved onto lines | 0 | `points_snapped_to_lines` | +| Moves onto lines refused | 0 | `snaps_refused` | +| Points the final check against the DEM moved onto lines | 0 | `final_check_points_snapped_to_lines` | +| Of them, also added where they were | 0 | `final_check_snapped_points_added_anyway` | +| Starting-mesh vertices not on a DEM node | 0 | `start_vertices_between_dem_nodes` | + +## Timings + +Wall clock, `time.perf_counter_ns` (Python) and `std::chrono::steady_clock` +(inside `refine`), one run, no warm-up. Total is the `mesh` command body, from +argument checks to the last file written; interpreter start-up and imports are +not in it. Threads: 10 (hardware concurrency). + +| phase | seconds | share | +|---|---|---| +| decode | 0.070 | 1.8 % | +| start mesh: build | 0.000 | 0.0 % | +| start mesh: node | 0.003 | 0.1 % | +| start mesh: triangulate | 0.030 | 0.8 % | +| start mesh: constraint edges | 0.007 | 0.2 % | +| slope | 0.111 | 2.9 % | +| refine | 2.219 | 57.7 % | +| refine: legalise start | 0.000 | 0.0 % | +| refine: start quality | 0.001 | 0.0 % | +| refine: scan (parallel) | 0.616 | 16.0 % | +| refine: split + flip (serial) | 1.337 | 34.8 % | +| refine: setup + output | 0.265 | 6.9 % | +| edge strip: generate | 0.001 | 0.0 % | +| edge strip: scan (parallel) | 0.011 | 0.3 % | +| edge strip: split + flip (serial) | 0.014 | 0.4 % | +| trim | 0.121 | 3.2 % | +| write: encode | 0.040 | 1.0 % | +| write: disk | 0.022 | 0.6 % | +| other | 1.198 | 31.2 % | +| **total** | **3.847** | **100 %** | + +Sub-rows sum to their parent and are not added to the total. + +Statistics computed in 0.706 s, not included above. + +--- stderr --- +3096202 triangles. Every DEM node inside the mesh is within 10 m of it (largest difference 9.9999 m). Nodes on steep ground are within the tolerance their slope allows (largest error 100 % of it). + 4.84 real 12.08 user 0.41 sys + 2785820672 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 179428 page reclaims + 0 page faults + 0 swaps + 0 block input operations + 0 block output operations + 0 messages sent + 0 messages received + 0 signals received + 14 voluntary context switches + 5985 involuntary context switches + 75929103940 instructions retired + 37460251444 cycles elapsed + 2764999224 peak memory footprint diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-3.rss b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-3.rss new file mode 100644 index 00000000..4c6e80c8 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-romsdal-slope-vtk-3.rss @@ -0,0 +1,42 @@ +0.03 18480 +0.15 52416 +0.26 132512 +0.41 347712 +0.52 350128 +0.64 377472 +0.76 424896 +0.88 491568 +0.99 514352 +1.14 577632 +1.25 587520 +1.36 645120 +1.52 666976 +1.63 676976 +1.75 694832 +1.86 775232 +1.96 940432 +2.07 947200 +2.18 969360 +2.29 988224 +2.41 1009696 +2.52 1023024 +2.63 1023056 +2.74 1059968 +2.86 1059968 +2.97 1059968 +3.09 1096272 +3.20 1096272 +3.32 1096272 +3.43 1097888 +3.54 1097888 +3.65 2175184 +3.80 2585744 +3.91 2621920 +4.02 2622304 +4.14 2647616 +4.25 2658080 +4.36 2671840 +4.48 2720256 +4.59 2720256 +4.70 2720256 +4.82 2720496 diff --git a/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-run.log b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-run.log new file mode 100644 index 00000000..832edb82 --- /dev/null +++ b/docs/benchmarks/2026-10-11/gpkg-out/writer/writer-run.log @@ -0,0 +1,5 @@ +numedalslagen gpkg: real 13.53 s, write 3.932 s, peak RSS 2.77 GB, writer window 2.75 -> 2.77 GB (share 32 MB), file 203.3 MB +numedalslagen vtk: real 9.58 s, write 0.014 s, peak RSS 2.73 GB, writer window 0.00 -> 0.00 GB (share 0 MB), file 29.2 MB +romsdal-slope gpkg: real 24.15 s, write 19.368 s, peak RSS 2.91 GB, writer window 2.72 -> 2.91 GB (share 188 MB), file 810.0 MB +romsdal-slope vtk: real 4.82 s, write 0.058 s, peak RSS 2.78 GB, writer window 2.78 -> 2.78 GB (share 0 MB), file 111.5 MB +exit 0 diff --git a/docs/increments/36-geopackage-output.md b/docs/increments/36-geopackage-output.md new file mode 100644 index 00000000..a88e1127 --- /dev/null +++ b/docs/increments/36-geopackage-output.md @@ -0,0 +1,469 @@ +# Increment 36: GeoPackage output + +**Status: **built** on branch `worktree-gpkg-out` (design `01118660`, fixes +`0233d1e3`, design review rounds 1 and 2 recorded; red `87f41c3d` (`@tester`, +42 tests), green `c59246e5` + `0943a7a5` (`@developer`), layering rows +`4e7901ee`, mutation round `dddce8e8` (ten mutants, all killed, record in its +commit message), fix-up `7085dddf` (the `.part` housekeeping moved to the +CLI); 377 net production lines by `python3 tools/count_loc.py 81a551b3 HEAD` (the merge base; `3e6f87eb` gives the same count) +against the design's about 305; section 11 is the as-built record; `@perf`'s +acceptance `57c25074` ACCEPTED). Code review round 1 CHANGES REQUESTED (one blocker, Review section), fixed: red test `55cc3c3e` (`@tester`), guard `18442b27` (`@developer`), 378 net lines; code review round 2 APPROVED. Next: the push on Ola's yes, then a merge of master (#229, increment 35) before the queue. + +## 1. What Ola asked + +`rasputin mesh --out X.gpkg` writes a GeoPackage, chosen by the suffix of +`--out` as `.vtk` and `.ply` are, so QGIS opens a mesh as any vector layer +(Ola, 2026-10-05, ROADMAP row 36). The file is also for agents that cannot +read VTK (Ola, 2026-10-11, "intent-driven triangulation"): a script with +`sqlite3` and shapely, or an agent, checks a mesh from the file alone — +triangle count, per-triangle heights, land-cover code, plan area, and the +run record (tolerance, DEM source). Specification by example: Ola's stopgap +converter `../rasputin_data/lands/scripts/vtk_to_gpkg.py` (outside git), +which this increment replaces. + +## 2. Prior art: legacy and literature + +*Literature.* The file format is OGC GeoPackage 1.3 (OGC 12-128r18): the +geometry blob header (section 2.1.3, which `io/geopackage.py` already +decodes), the R-tree spatial index extension (Annex F.3), `gpkg_contents`, +`gpkg_geometry_columns`, `gpkg_spatial_ref_sys` and attribute tables +(`data_type = 'attributes'`, section 2.3). The spatial index is SQLite's +R*Tree module (sqlite.org/rtree.html), fed row by row; the GeoPackage +extension's triggers are left out, since the file is written once and the +extension's `scope` column says `write-only`, as the stopgap does. The stored +QGIS style is QGIS's own `layer_styles` table (QGIS Desktop User Guide, +"Save style to the database"), as the stopgap writes it and QGIS 3.28+ reads +it. Bulk-loading an SQLite R-tree is known and faster: Even Rouault's +`sqlite_rtree_bulk_load` builds the tree in memory and writes the `_node`, +`_parent` and `_rowid` shadow tables directly (SQLite forum, +sqlite.org/forum/forumpost/af937a36b3, 2023-10-22: 3.2 million rows, 26 s +to 6 s; shipped in GDAL 3.8), packing after Leutenegger, Lopez and Edgington, +"STR: a simple and efficient algorithm for R-tree packing", ICDE 1997. Not +built here (section 7: 36b), because the row-by-row index costs 2.5 s on the +quick check's mesh, and the shadow-table format is SQLite's private layout. +No novelty is claimed: this is an encoder for a published format. + +*Legacy.* Nothing. The archived tree has no GeoPackage, SQLite or QGIS code: + + $ git grep -l -i -E "gpkg|geopackage|sqlite|qgis" legacy-archive -- legacy + (no files; exit 1) + +*In this tree.* `src_python/tin_engine/io/geopackage.py` (16b R2, R3) reads +the blob header, `gpkg_contents`, the SRS table and the R-tree; the writer is +its mirror and the reader reads the writer's files back in the tests. +`tests/python/gpkg_fixtures.py` (`write_gpkg`) already writes small test +GeoPackages with `sqlite3` and is kept as a test helper, not promoted. + +## 3. Measured on this worktree (`docs/increments/36-probes/gpkg_write_probe.py`) + +AC power, Python 3.14, SQLite 3.53.1, shapely 2.2.0; single runs. The probe +reads a binary `.vtk` and writes the `triangles` and `lines` layers with +section 4's attributes; `--stopgap` runs Ola's converter on the same mesh. + +Every "candidate" figure is one command, from the worktree root: + + /usr/bin/time -l .venv/bin/python docs/increments/36-probes/gpkg_write_probe.py \ + .vtk out.gpkg --rtree executemany --cache-mb 256 --stream --per-batch --vtk-time + +`--rtree executemany` feeds the index rows straight into the virtual table, +`--cache-mb 256` sets the page cache for that build, `--stream --per-batch` +builds rows and blobs per batch of 100,000 (`--batch`'s default), `--vtk-time` +re-times the binary `.vtk` write beside it, and `/usr/bin/time -l`'s +`maximum resident set size` is the peak RSS. The stopgap column adds +`--stopgap ../rasputin_data/lands/scripts/vtk_to_gpkg.py`; the "same bytes" +claim below is `--twice`. **The probe's own defaults (`--rtree select`, no +`--cache-mb`, no `--stream`) are the rejected variants**, so a bare run does +not reproduce the table. The meshes, all binary `.vtk` from `rasputin mesh`: +row 1 `rasputin mesh --dem tests/fixtures/dem_archive/7908_3_10m_z33.tif +--tolerance 10 --out .vtk --binary`; row 2 the `numedalslagen` case of +`docs/benchmarks/quick/cases.toml` with `--out .vtk --binary` appended; +row 3 `../rasputin_scratch/norway/romsdalen/romsdalen_slope_2m_25_35.vtk` +(the `romsdal-slope` case, kept outside git). Row 1 re-run with that line +when the fix was written, 2026-10-11: total 0.40 s, R-tree 0.24 s, 20.0 MB, +160 MB peak RSS. + +| mesh | triangles | `.vtk` | `.vtk` write (`--stats`) | GeoPackage, candidate | of which R-tree | size | peak RSS | stopgap | +|---|---|---|---|---|---|---|---|---| +| test tile `7908_3_10m_z33.tif`, `--tolerance 10` | 69,571 | 2.6 MB | 0.005 s | 0.40 s | 0.23 s | 20.0 MB | — | — | +| quick check's Numedalslagen (`--tolerance 10`, CORINE) | 653,039 | 29.2 MB | 0.28 s | 4.0 s | 2.5 s | 203 MB | 0.56 GB | 5.1 s, 163 MB | +| Romsdalen slope mesh (`romsdalen_slope_2m_25_35.vtk`) | 3,096,202 | 111.5 MB | about 1 s | 21.5 s | 14.2 s | 922 MB | 2.1 GB | 28.7 s, 779 MB, 4.1 GB | + +Per triangle: a row costs about 2.2 µs and 242 bytes, the R-tree entry about +4.6 µs and 56 bytes. The stopgap's file is smaller only because it carries +fewer attributes. The file is 7 to 8 times the binary `.vtk`, as the ROADMAP +row foresaw (Graz: 678 MB against 111 MB): a row stores the triangle's whole +ring (four corners) and a bounding box, where `.vtk` stores each vertex once. + +What the writer must do, each measured as the difference it made: + +- **One transaction, `executemany` in batches of 100,000 rows** (`BEGIN` once, + `COMMIT` once; `journal_mode=OFF`, `synchronous=OFF`, as the stopgap). The + batch size is not sensitive: `--batch 10000` gave the same time on + Numedalslagen. +- **The R-tree after the rows, into the virtual table directly** (14.2 s on + Romsdalen) — not through a temporary table (`--rtree select`, 16.6 s). + Feeding it in Morton order (`--rtree-order morton`) was slower (4.5 s + against 3.8 s on Numedalslagen): the kernel's triangle order is already + local. `PRAGMA cache_size` of 256 MB during the index build: 3.8 s to 2.9 s + on Numedalslagen (without `--cache-mb`, the default cache is 2 MB). +- **Blobs and rows built per batch from array slices**, no whole-mesh list of + tuples and no whole-mesh blob array: peak RSS on Romsdalen 5.8 GB with a + list of all rows (neither flag), 3.2 GB streaming rows only (`--stream`), + 2.1 GB streaming blobs too (`--stream --per-batch`; 0.3 GB of that is the + probe's own VTK read). Checked at 3.1 million + triangles; at the São Francisco scale (about 10⁸ triangles) the rows would + take about 4 minutes and the index 8, for a 30 GB file (section 7). +- **Same run, same bytes**: the second write of each mesh was SHA-256 + identical to the first. Nothing calls `now()`: `gpkg_contents.last_change` + and `layer_styles.update_time` are written as the fixed text + `1970-01-01T00:00:00.000Z` (question 3). + +The blob encoding is one numpy structured array per batch (`GP`, version 0, +flags `0b0011` = little-endian with a 32-byte XY envelope, `srs_id`, then ISO +WKB: byte order 1, type 1003 PolygonZ, one ring of four points), as the +stopgap's `tri_blobs`; `io/geopackage.py`'s `decode_geometry` reads it. + +## 4. The file + +All layers in the run's CRS. Row `fid` = index + 1, so `fid - 1` is the +triangle's or edge's index in the `.vtk` of the same run; rows are inserted +in that order and never reordered. + +**`triangles`** (PolygonZ, `gpkg_geometry_columns.z = 1`), one row per +triangle, in triangle order. Columns: `code INTEGER` (the `.vtk`'s +`land_cover_code`: 0 in no polygon; NULL on every row when the run had no +coded land cover), `land_cover TEXT` (the code's name from the palette, NULL +when the palette lacks the code), `colour TEXT` (`#rrggbb` from the palette; +`#ff00ff` for a code the palette lacks, the preset's NaN colour), `z_min`, +`z_max`, `z_mean REAL` (the three corner heights), `area_m2 REAL` (plan +area), `slope_deg REAL` (the triangle plane's slope, Horn's quantity on a +flat triangle: `atan2(|n_xy|, |n_z|)`). An R-tree index. + +**`lines`** (LineStringZ), one row per constraint edge, in edge order, with +`length_m REAL` (plan length) and one `INTEGER` 0/1 column per vocabulary +property set on at least one edge, named as `write_vtk` names its cell +arrays (ruling 6 of increment 13). Written only when the mesh has edges. +No R-tree (67,240 rows on Numedalslagen draw in one scan). + +**`land_cover`** (MultiPolygon, 2D), one row per coded polygon of the +features after the clip and the clean-up (`FeatureSet.features` with a +`polygon` and a `code`, the polygons `_land_cover` labels from): `code`, +`land_cover`, `colour`, `area_m2`. Written only when a coded source ran and +at least one polygon lies in the domain; otherwise absent (as built, review +round 1). +22 rows on Numedalslagen; cheap, so kept (the ROADMAP's constraint-polygons +layer). Constraint lines from non-coded sources are already in `lines`. + +**`land_cover_classes`** (an attributes table), one row per code present in +`triangles`: `fid INTEGER PRIMARY KEY` (OGC 12-128 Requirement 119: every +attributes table has an integer primary key, the rowid alias), `code`, +`land_cover`, `colour`, `triangles INTEGER`, `area_m2 REAL`. The legend, and the class shares an agent reads with one +query. Written only with coded land cover. + +**`run`** (an attributes table), the run record (increment 25) as rows: +`fid INTEGER PRIMARY KEY` (Requirement 119 again), `name TEXT UNIQUE`, +`wording TEXT`, `value TEXT`, `number REAL` (NULL for text entries), in +record order, straight from `RunRecord.entries` +(`name`, `wording`, `value`, `number`); first `rasputin_version` and +`command` as `--record` writes them, then every entry `as_json` writes. So the tolerance +(`tolerance_m`), the largest error (`max_error_m`), the DEM source +(`dem_source`) and the CRS are in the file. `--record` and `--stats` are +unchanged and still work beside a `.gpkg`. + +**`layer_styles`** (QGIS), one row each for `triangles` and, when that layer +exists, `land_cover`, +`useAsDefault = 1`: a categorised style on `code` with the palette's colours +and labels, the stopgap's QML with its three ``/`