petekTools
Scattered-data gridding & geostatistics kernels for Rust — the numerics layer the ecosystem is missing.
Rust has excellent crates for linear algebra (faer), statistics and
distributions (statrs, rand_distr), FFT (rustfft), and spatial indexing
(kiddo, rstar). What it has lacked is a production-grade way to turn
scattered (x, y, z) observations into a regular grid — minimum-curvature
surfaces, inverse-distance weighting, nearest-neighbour fills. petekTools
fills exactly that gap, and curates the rest behind one small front-door.
If you have points and need a surface — a depth grid from well picks, a property map from samples, any scattered field on a regular lattice — this is the crate.
Why reach for it
- Real gridding methods, not toys. Briggs minimum-curvature (biharmonic SOR), inverse-distance weighting, and nearest-neighbour — the workhorses, with their defaults and tolerances stated and tested, not hand-waved.
- Warm-start / incremental re-gridding. Editing a surface point-by-point?
Re-solving from scratch each time is wasteful. Seed the solver from the prior
field and it converges in a fraction of the iterations — ~4–7× faster on a
typical structural edit, rising to ~114× in the near-converged incremental
limit (measured: 1.55 ms → 13.6 µs, 64 points → 40×40), converging to the same
field. A stateful [
ConvergentGridder] makes interactive, one-control-at-a-time refinement cheap. - Type-agnostic by design. Kernels speak a plain [
Lattice] +[[f64; 3]]rows and returnndarray::Array2<f64>— never a domain type. Any regular, rotatable areal lattice (the IRAP/RMS model) maps on field-for-field, so adoption is a conversion at the call site, not a rewrite. - Deterministic and honest. No RNG, no silent clamping; named, cited constants. Analytic cases are asserted as tests — a linear trend is the exact minimum-curvature solution, IDW is exact at coincident samples.
- A pure leaf. Depends only on general-purpose numeric crates. No I/O, no domain model, no heavy framework to adopt — drop it in.
- Binding-friendly. Owned inputs, no public lifetimes on kernels; PyO3 bindings are a planned thin layer over this same surface.
Install
Quick start
use ;
// A 100×80 grid, 25 m spacing, origin at (1000, 2000).
let lattice = regular;
// Scattered observations: [x, y, z] rows.
let points = ;
// Interpolate onto the grid → an (ncol × nrow) Array2<f64>;
// undefined nodes are NaN.
let surface = grid.unwrap;
Methods
GridMethod |
What it does |
|---|---|
Nearest |
Each node takes its areally-closest sample's z (blocky, exact at data). |
InverseDistance |
Global IDW, p = 2; exact at coincident samples. |
MinimumCurvature |
Briggs biharmonic SOR — smooth, honours the samples. |
Warm-start an incremental re-grid
use ;
let lattice = regular;
let points = ;
let cold = grid.unwrap;
// Later, after nudging the data: relax from the prior field instead of
// cold-starting. A None / wrong-shape seed falls back to a cold solve.
let warm = grid_min_curvature_seeded.unwrap;
Interactive refinement with ConvergentGridder
use ;
let lattice = regular;
let points = ;
let mut gridder = new.unwrap;
// Pin node (32, 40) to 25.0 as a hard constraint and re-solve incrementally.
let field = gridder.add_control; // warm — cheap
// … add more controls; each call returns the updated field.
Geometry: Lattice
A regular, rotatable areal lattice (the IRAP/RMS model): origin, spacing,
node counts, CCW rotation, and an optional y-flip. It carries the forward map
node_xy(i, j) and its inverse xy_to_ij(x, y), plus a bbox() — everything a
kernel needs to place a sample on the grid.
Design
- One job. Scattered-data gridding / geostatistics — the gap. Everything else (linear algebra, stats, neighbour search) is curated from mature crates, never reimplemented.
- Type-agnostic kernels.
Lattice+[[f64; 3]]in,ndarrayout. No domain types, no I/O. - Numerical honesty. Deterministic, documented to a stated tolerance, with analytic tests as the safety net.
See SPEC.md for the design constitution and API.md for
the locked public contract.
Status & roadmap
The public contract — Lattice, GridMethod, grid, the warm-start entries —
is locked and analytically tested. Also shipped: ordinary kriging behind a
Gridder trait, the curated stats / sampling front-doors over statrs /
rand_distr, and the units / container modules. On the roadmap: a PyO3
wheel (and RBF backends if a need appears).