use std::ffi::c_int;
use ndarray::Array1;
use crate::_native::{len_c_int, lib, require_len, to_c_int};
use crate::common::exceptions::Result;
#[allow(clippy::too_many_arguments)]
pub(crate) fn run_fit_cross_variogram(
xa: &[f64],
ya: &[f64],
za: &[f64],
xb: &[f64],
yb: &[f64],
zb: &[f64],
max_colocation_dist: f64,
n_lags: i64,
max_pairs: i64,
max_distance: f64,
n_threads: i64,
) -> Result<(Array1<f64>, f64, i64)> {
const CONTEXT: &str = "error fitting cross-variogram";
require_len(ya.len(), xa.len(), "ya", CONTEXT)?;
require_len(za.len(), xa.len(), "za", CONTEXT)?;
require_len(yb.len(), xb.len(), "yb", CONTEXT)?;
require_len(zb.len(), xb.len(), "zb", CONTEXT)?;
let na = len_c_int(xa.len(), CONTEXT)?;
let nb = len_c_int(xb.len(), CONTEXT)?;
let n_lags = to_c_int(n_lags, CONTEXT)?;
let max_pairs = to_c_int(max_pairs, CONTEXT)?;
let thread_count = to_c_int(n_threads, CONTEXT)?;
let mut model_info = [0.0f64; 5];
let mut rho = 0.0f64;
let mut n_pairs: c_int = 0;
lib::load();
unsafe {
lib::fit_cross_variogram_(
xa.as_ptr(),
ya.as_ptr(),
za.as_ptr(),
xb.as_ptr(),
yb.as_ptr(),
zb.as_ptr(),
&max_colocation_dist,
&n_lags,
&max_pairs,
&max_distance,
&thread_count,
model_info.as_mut_ptr(),
&mut rho,
&mut n_pairs,
&na,
&nb,
);
}
Ok((Array1::from(model_info.to_vec()), rho, i64::from(n_pairs)))
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn run_screen_covariate(
model_00: &[f64],
model_uu: &[f64],
model_0u: &[f64],
rho: f64,
n_pairs: i64,
alpha: f64,
min_abs_rho: f64,
max_violation_fraction: f64,
) -> Result<(bool, f64, f64, i32)> {
const CONTEXT: &str = "error screening covariate";
require_len(model_00.len(), 5, "model_00", CONTEXT)?;
require_len(model_uu.len(), 5, "model_uu", CONTEXT)?;
require_len(model_0u.len(), 5, "model_0u", CONTEXT)?;
let n_pairs = to_c_int(n_pairs, CONTEXT)?;
let mut decision: c_int = 0;
let mut p_value = 0.0f64;
let mut admissible_fraction = 0.0f64;
let mut status: c_int = 0;
lib::load();
unsafe {
lib::screen_covariate_(
model_00.as_ptr(),
model_uu.as_ptr(),
model_0u.as_ptr(),
&rho,
&n_pairs,
&alpha,
&min_abs_rho,
&max_violation_fraction,
&mut decision,
&mut p_value,
&mut admissible_fraction,
&mut status,
);
}
Ok((decision != 0, p_value, admissible_fraction, status))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::_native::test_support::{assert_rejected, assert_status, grid_points};
use crate::common::exceptions::{CROSS_VARIOGRAM_STATUS_ERRORS, ErrorKind};
const MODEL_SPHERICAL: f64 = 1.0;
fn fisher_p_value_reference(rho: f64, n_pairs: i64) -> f64 {
let z_fisher = 0.5 * ((1.0 + rho) / (1.0 - rho)).ln();
let z_stat = z_fisher * ((n_pairs - 3) as f64).sqrt();
let normal_cdf = 0.5 * (1.0 + unsafe { lib::erf(z_stat.abs() / 2f64.sqrt()) });
2.0 * (1.0 - normal_cdf)
}
fn models(cross_partial_sill: f64) -> ([f64; 5], [f64; 5], [f64; 5]) {
(
[MODEL_SPHERICAL, 0.0, 1.0, 10.0, 0.0],
[MODEL_SPHERICAL, 0.0, 1.0, 10.0, 0.0],
[MODEL_SPHERICAL, 0.0, cross_partial_sill, 10.0, 0.0],
)
}
#[test]
fn test_high_rho_small_n_is_rejected_by_significance() {
let (model_00, model_uu, model_0u) = models(0.8);
let (rho, n_pairs) = (0.8, 6);
let (decision, p_value, admissible_fraction, status) =
run_screen_covariate(&model_00, &model_uu, &model_0u, rho, n_pairs, 0.05, 0.3, 0.1).unwrap();
let reference_p = fisher_p_value_reference(rho, n_pairs);
assert_eq!(status, 0);
assert!((p_value - reference_p).abs() <= 1e-9);
assert!(reference_p > 0.05);
assert!(!decision);
assert_eq!(admissible_fraction, 1.0);
}
#[test]
fn test_cauchy_schwarz_violation_is_rejected_even_when_significant() {
let (model_00, model_uu, model_0u) = models(5.0);
let (decision, p_value, admissible_fraction, status) =
run_screen_covariate(&model_00, &model_uu, &model_0u, 0.99, 100, 0.05, 0.3, 0.1).unwrap();
assert_eq!(status, 0);
assert!(p_value < 0.05);
assert!(admissible_fraction < 0.9);
assert!(!decision);
}
#[test]
fn test_insufficient_sample_reports_status_without_raising() {
let (model_00, model_uu, model_0u) = models(0.8);
let (decision, _, _, status) =
run_screen_covariate(&model_00, &model_uu, &model_0u, 0.9, 3, 0.05, 0.3, 0.1).unwrap();
assert_eq!(status, 1);
assert!(!decision);
}
#[test]
fn wrong_model_length_is_rejected_before_the_kernel() {
let short = [1.0, 0.0, 1.0];
let model = [MODEL_SPHERICAL, 0.0, 1.0, 10.0, 0.0];
let error = run_screen_covariate(&short, &model, &model, 0.5, 10, 0.05, 0.3, 0.1).unwrap_err();
assert_eq!(error.kind(), crate::common::ErrorKind::NativeExecutionError);
}
#[test]
fn fit_cross_variogram_on_perfectly_correlated_colocated_samples() {
let (x, y, za) = grid_points();
let zb: Vec<f64> = za.iter().map(|v| 2.0 * v + 1.0).collect();
let (model, rho, n_pairs) =
run_fit_cross_variogram(&x, &y, &za, &x, &y, &zb, 0.0, 10, 0, 0.0, 1).unwrap();
assert_eq!(n_pairs, 36);
assert!((rho - 1.0).abs() <= 1e-12, "{rho}");
assert!([1.0, 2.0, 3.0].contains(&model[0]), "{model}");
assert!(model[3].is_finite() && model[3] > 0.0, "{model}");
}
#[test]
fn fit_cross_variogram_mismatched_lengths_are_rejected_before_the_kernel() {
let (x, y, z) = grid_points();
let run =
|ya: &[f64], zb: &[f64]| run_fit_cross_variogram(&x, ya, &z, &x, &y, zb, 0.0, 10, 0, 0.0, 1);
assert_rejected(run(&y[1..], &z).unwrap_err(), "ya");
assert_rejected(run(&y, &z[1..]).unwrap_err(), "zb");
}
#[test]
fn screening_statuses_reach_the_documented_errors() {
let table = CROSS_VARIOGRAM_STATUS_ERRORS;
let (model_00, model_uu, model_0u) = models(0.8);
let status = |m00: &[f64], muu: &[f64], m0u: &[f64], rho: f64, n_pairs: i64| {
run_screen_covariate(m00, muu, m0u, rho, n_pairs, 0.05, 0.3, 0.1)
.unwrap()
.3
};
let sample = ErrorKind::CrossVariogramInsufficientSampleError;
assert_status(status(&model_00, &model_uu, &model_0u, 0.9, 3), 1, table, sample);
assert_status(
status(&model_00, &model_uu, &model_0u, f64::NAN, 50),
1,
table,
sample,
);
let bad_auto = [9.0, 0.0, 1.0, 10.0, 0.0];
let auto = ErrorKind::CrossVariogramInvalidAutoModelError;
assert_status(status(&bad_auto, &model_uu, &model_0u, 0.9, 50), 2, table, auto);
assert_status(status(&model_00, &bad_auto, &model_0u, 0.9, 50), 2, table, auto);
let cross = ErrorKind::CrossVariogramInvalidCrossModelError;
assert_status(
status(&model_00, &model_uu, &[0.0, 0.0, 0.8, 10.0, 0.0], 0.9, 50),
3,
table,
cross,
);
assert_status(
status(
&model_00,
&model_uu,
&[MODEL_SPHERICAL, 0.0, 0.8, 0.0, 0.0],
0.9,
50,
),
3,
table,
cross,
);
}
}