petektools/gridding/gridder.rs
1//! The [`Gridder`] trait — one interface over the scattered-data → grid backends.
2//!
3//! The built-in [`GridMethod`] set (nearest / IDW / minimum-curvature) and the
4//! new [`OrdinaryKriging`](crate::OrdinaryKriging) backend all produce a dense
5//! `(ncol × nrow)` field from the same `(coords, lattice)` inputs, so they share
6//! this trait — letting a caller hold a `Box<dyn Gridder>` and swap methods
7//! (including kriging) behind one call.
8//!
9//! This unifies the **stateless, cold-grid** entry points. The two warm-start
10//! entry points are deliberately *not* `Gridder`s, because they are not pure
11//! `(coords, lattice) → field` functions:
12//! - [`grid_min_curvature_seeded`](crate::grid_min_curvature_seeded) takes an
13//! extra seed field (a warm start), and
14//! - [`ConvergentGridder`](crate::ConvergentGridder) is *stateful* — it holds a
15//! solved field and an accumulating control set, exposed through its own
16//! `add_control` / `field` surface.
17//!
18//! Both remain the fast incremental path over the same minimum-curvature kernel
19//! that `GridMethod::MinimumCurvature` reaches through this trait.
20
21use crate::foundation::{Lattice, Result};
22use ndarray::Array2;
23
24/// A scattered-data gridding backend: interpolate `[x, y, z]` rows onto
25/// `lattice`, returning the `(ncol × nrow)` node array (`NaN` where undefined).
26///
27/// Implemented by [`GridMethod`](crate::GridMethod) (the built-in enum) and by
28/// [`OrdinaryKriging`](crate::OrdinaryKriging). Errors follow the built-ins:
29/// empty input errors; per-node undefined values are `NaN`.
30pub trait Gridder {
31 /// Grid `coords` onto `lattice`, producing the node-value field.
32 fn grid(&self, coords: &[[f64; 3]], lattice: &Lattice) -> Result<Array2<f64>>;
33}
34
35impl Gridder for super::GridMethod {
36 /// Dispatch to the free [`grid`](super::grid) function — so
37 /// `method.grid(coords, lattice)` equals `grid(coords, lattice, method)`.
38 fn grid(&self, coords: &[[f64; 3]], lattice: &Lattice) -> Result<Array2<f64>> {
39 super::grid(coords, lattice, *self)
40 }
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46 use crate::gridding::GridMethod;
47
48 #[test]
49 fn gridmethod_trait_matches_free_function() {
50 let lattice = Lattice::regular(0.0, 0.0, 1.0, 1.0, 5, 5);
51 let coords = [[0.0, 0.0, 1.0], [4.0, 4.0, 9.0], [2.0, 2.0, 5.0]];
52 for method in [
53 GridMethod::Nearest,
54 GridMethod::InverseDistance,
55 GridMethod::MinimumCurvature,
56 ] {
57 let via_trait = Gridder::grid(&method, &coords, &lattice).unwrap();
58 let via_free = crate::gridding::grid(&coords, &lattice, method).unwrap();
59 assert_eq!(via_trait, via_free, "{method:?}");
60 }
61 }
62
63 #[test]
64 fn usable_as_trait_object() {
65 let lattice = Lattice::regular(0.0, 0.0, 1.0, 1.0, 4, 4);
66 let coords = [[0.0, 0.0, 1.0], [3.0, 3.0, 4.0]];
67 let g: Box<dyn Gridder> = Box::new(GridMethod::InverseDistance);
68 let out = g.grid(&coords, &lattice).unwrap();
69 assert_eq!(out.dim(), (4, 4));
70 }
71}