Skip to main content

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}