use crate::foundation::{AlgoError, Lattice, Result};
use ndarray::Array2;
use super::min_curvature::grid_min_curvature;
pub struct ConvergentGridder {
lattice: Lattice,
coords: Vec<[f64; 3]>,
field: Array2<f64>,
}
impl ConvergentGridder {
pub fn new(coords: &[[f64; 3]], lattice: &Lattice) -> Result<ConvergentGridder> {
if coords.is_empty() {
return Err(AlgoError::EmptyInput(
"ConvergentGridder::new: no points to grid",
));
}
let field = grid_min_curvature(coords, lattice, None);
Ok(ConvergentGridder {
lattice: lattice.clone(),
coords: coords.to_vec(),
field,
})
}
pub fn add_control(&mut self, ip: usize, jp: usize, z: f64) -> &Array2<f64> {
self.add_controls(&[(ip, jp, z)])
}
pub fn add_controls(&mut self, controls: &[(usize, usize, f64)]) -> &Array2<f64> {
for &(ip, jp, z) in controls {
debug_assert!(
ip < self.lattice.ncol && jp < self.lattice.nrow,
"ConvergentGridder control ({ip}, {jp}) is off-lattice ({}x{})",
self.lattice.ncol,
self.lattice.nrow
);
let (x, y) = self.lattice.node_xy(ip, jp);
self.coords.push([x, y, z]);
}
self.field = grid_min_curvature(&self.coords, &self.lattice, Some(&self.field));
&self.field
}
pub fn field(&self) -> &Array2<f64> {
&self.field
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_coords() -> [[f64; 3]; 5] {
[
[1.0, 1.0, 10.0],
[9.0, 2.0, 25.0],
[3.0, 8.0, 5.0],
[10.0, 8.0, 40.0],
[5.0, 5.0, 18.0],
]
}
#[test]
fn new_empty_errors() {
let lattice = Lattice::regular(0.0, 0.0, 1.0, 1.0, 5, 5);
assert!(matches!(
ConvergentGridder::new(&[], &lattice),
Err(AlgoError::EmptyInput(_))
));
}
#[test]
fn new_matches_cold_grid() {
let lattice = Lattice::regular(0.0, 0.0, 1.0, 1.0, 12, 10);
let coords = sample_coords();
let g = ConvergentGridder::new(&coords, &lattice).unwrap();
let cold = grid_min_curvature(&coords, &lattice, None);
assert_eq!(g.field(), &cold);
}
#[test]
fn added_control_is_honoured_as_hard_constraint() {
let lattice = Lattice::regular(0.0, 0.0, 1.0, 1.0, 12, 10);
let mut g = ConvergentGridder::new(&sample_coords(), &lattice).unwrap();
let field = g.add_control(6, 7, 99.0);
assert!((field[[6, 7]] - 99.0).abs() < 1e-9, "got {}", field[[6, 7]]);
}
#[test]
fn incremental_matches_from_scratch_to_tolerance() {
let lattice = Lattice::regular(0.0, 0.0, 1.0, 1.0, 12, 10);
let coords = sample_coords();
let mut g = ConvergentGridder::new(&coords, &lattice).unwrap();
let warm = g.add_control(6, 7, 99.0).clone();
let (x, y) = lattice.node_xy(6, 7);
let mut from_scratch: Vec<[f64; 3]> = coords.to_vec();
from_scratch.push([x, y, 99.0]);
let cold = grid_min_curvature(&from_scratch, &lattice, None);
for (w, c) in warm.iter().zip(cold.iter()) {
assert!((w - c).abs() < 1e-3, "warm {w} vs cold {c}");
}
}
#[test]
fn deterministic() {
let lattice = Lattice::regular(0.0, 0.0, 1.0, 1.0, 12, 10);
let mut a = ConvergentGridder::new(&sample_coords(), &lattice).unwrap();
let mut b = ConvergentGridder::new(&sample_coords(), &lattice).unwrap();
let fa = a.add_control(4, 4, 50.0).clone();
let fb = b.add_control(4, 4, 50.0).clone();
assert_eq!(fa, fb); }
#[test]
fn batch_matches_sequential_to_tolerance() {
let lattice = Lattice::regular(0.0, 0.0, 1.0, 1.0, 12, 10);
let controls = [(6, 7, 99.0), (2, 2, -10.0), (9, 6, 33.0)];
let mut batch = ConvergentGridder::new(&sample_coords(), &lattice).unwrap();
let fb = batch.add_controls(&controls).clone();
let mut seq = ConvergentGridder::new(&sample_coords(), &lattice).unwrap();
for &(ip, jp, z) in &controls {
seq.add_control(ip, jp, z);
}
let fs = seq.field();
for (b, s) in fb.iter().zip(fs.iter()) {
assert!((b - s).abs() < 1e-3, "batch {b} vs seq {s}");
}
}
}