use crate::foundation::{AlgoError, Result};
#[derive(Debug, Clone, PartialEq)]
pub struct ExperimentalVariogram {
pub lags: Vec<f64>,
pub semivariances: Vec<f64>,
pub counts: Vec<usize>,
}
impl ExperimentalVariogram {
pub fn len(&self) -> usize {
self.lags.len()
}
pub fn is_empty(&self) -> bool {
self.lags.is_empty()
}
}
pub fn experimental_variogram(
coords: &[[f64; 3]],
lag: f64,
n_lags: usize,
) -> Result<ExperimentalVariogram> {
if coords.len() < 2 {
return Err(AlgoError::EmptyInput(
"experimental_variogram: need at least two data",
));
}
if !lag.is_finite() || lag <= 0.0 || n_lags == 0 {
return Err(AlgoError::InvalidArgument(
"experimental_variogram: need lag > 0 (finite) and n_lags >= 1".to_string(),
));
}
let mut sum_gamma = vec![0.0_f64; n_lags]; let mut sum_dist = vec![0.0_f64; n_lags]; let mut counts = vec![0_usize; n_lags];
let max_h = lag * n_lags as f64;
for i in 0..coords.len() {
let a = &coords[i];
for b in &coords[i + 1..] {
let dx = a[0] - b[0];
let dy = a[1] - b[1];
let h = (dx * dx + dy * dy).sqrt();
if h >= max_h {
continue;
}
let k = (h / lag) as usize; let dz = a[2] - b[2];
sum_gamma[k] += dz * dz;
sum_dist[k] += h;
counts[k] += 1;
}
}
let mut lags = Vec::new();
let mut semivariances = Vec::new();
let mut kept_counts = Vec::new();
for k in 0..n_lags {
let n = counts[k];
if n == 0 {
continue;
}
lags.push(sum_dist[k] / n as f64);
semivariances.push(sum_gamma[k] / (2.0 * n as f64));
kept_counts.push(n);
}
Ok(ExperimentalVariogram {
lags,
semivariances,
counts: kept_counts,
})
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn errors_on_too_few_data_or_bad_params() {
assert!(matches!(
experimental_variogram(&[[0.0, 0.0, 1.0]], 1.0, 5),
Err(AlgoError::EmptyInput(_))
));
let d = [[0.0, 0.0, 1.0], [1.0, 0.0, 2.0]];
assert!(experimental_variogram(&d, 0.0, 5).is_err());
assert!(experimental_variogram(&d, 1.0, 0).is_err());
}
#[test]
fn hand_checked_three_collinear_points() {
let d = [[0.0, 0.0, 0.0], [1.0, 0.0, 1.0], [2.0, 0.0, 2.0]];
let ev = experimental_variogram(&d, 1.0, 3).unwrap();
assert_eq!(ev.len(), 2); assert_relative_eq!(ev.lags[0], 1.0, epsilon = 1e-12);
assert_relative_eq!(ev.semivariances[0], 0.5, epsilon = 1e-12);
assert_eq!(ev.counts[0], 2);
assert_relative_eq!(ev.lags[1], 2.0, epsilon = 1e-12);
assert_relative_eq!(ev.semivariances[1], 2.0, epsilon = 1e-12);
assert_eq!(ev.counts[1], 1);
}
#[test]
fn pairs_beyond_reach_are_dropped() {
let d = [[0.0, 0.0, 1.0], [5.0, 0.0, 9.0]];
let ev = experimental_variogram(&d, 1.0, 2).unwrap();
assert!(ev.is_empty(), "the lone far pair should fall outside reach");
}
}