#![cfg(feature = "stde")]
use echidna::stde::{estimate, estimate_weighted, Laplacian};
use echidna::{BReverse, BytecodeTape, Scalar};
fn record_fn(f: impl FnOnce(&[BReverse<f64>]) -> BReverse<f64>, x: &[f64]) -> BytecodeTape<f64> {
let (tape, _) = echidna::record(f, x);
tape
}
fn exp_prod<T: Scalar>(x: &[T]) -> T {
(x[0] * x[1]).exp()
}
fn quadratic<T: Scalar>(x: &[T]) -> T {
x[0] * x[0] + x[1] * x[1]
}
#[test]
fn estimate_skips_nonfinite_samples_and_counts_contributing() {
let x = [1.0_f64, 700.0];
let tape = record_fn(exp_prod, &x);
let live = vec![1.0_f64, 0.0]; let dead = vec![0.0_f64, 0.0]; let dirs: Vec<&[f64]> = vec![&live, &dead, &dead];
let r = estimate(&Laplacian, &tape, &x, &dirs);
assert_eq!(r.num_samples, 2, "one non-finite sample must be skipped");
assert_eq!(r.estimate, 0.0, "the finite samples are exactly zero");
assert!(r.standard_error.is_finite());
}
#[test]
fn estimate_all_samples_nonfinite_reports_nan_and_zero_count() {
let x = [1.0_f64, 700.0];
let tape = record_fn(exp_prod, &x);
let live = vec![1.0_f64, 0.0];
let dirs: Vec<&[f64]> = vec![&live, &live];
let r = estimate(&Laplacian, &tape, &x, &dirs);
assert_eq!(r.num_samples, 0);
assert!(
r.estimate.is_nan(),
"no contributing samples must surface NaN, got {}",
r.estimate
);
}
#[test]
fn estimate_weighted_skips_nonfinite_samples() {
let x = [1.0_f64, 700.0];
let tape = record_fn(exp_prod, &x);
let live = vec![1.0_f64, 0.0];
let dead = vec![0.0_f64, 0.0];
let dirs: Vec<&[f64]> = vec![&live, &dead];
let weights = vec![1.0_f64, 1.0];
let r = estimate_weighted(&Laplacian, &tape, &x, &dirs, &weights);
assert_eq!(r.num_samples, 1, "the non-finite sample must be skipped");
assert_eq!(r.estimate, 0.0);
}
#[test]
fn estimate_weighted_all_nonfinite_reports_nan() {
let x = [1.0_f64, 700.0];
let tape = record_fn(exp_prod, &x);
let live = vec![1.0_f64, 0.0];
let dirs: Vec<&[f64]> = vec![&live];
let weights = vec![1.0_f64];
let r = estimate_weighted(&Laplacian, &tape, &x, &dirs, &weights);
assert_eq!(r.num_samples, 0);
assert!(r.estimate.is_nan());
}
#[test]
fn estimate_weighted_zero_weights_still_count_as_samples() {
let x = [1.0_f64, 2.0];
let tape = record_fn(quadratic, &x);
let v1 = vec![1.0_f64, 0.0];
let v2 = vec![0.0_f64, 1.0];
let dirs: Vec<&[f64]> = vec![&v1, &v2];
let weights = vec![1.0_f64, 0.0];
let r = estimate_weighted(&Laplacian, &tape, &x, &dirs, &weights);
assert_eq!(r.num_samples, 2);
assert!(r.estimate.is_finite());
}
#[test]
#[should_panic(expected = "non-negative")]
fn estimate_weighted_negative_weight_panics() {
let x = [1.0_f64, 2.0];
let tape = record_fn(quadratic, &x);
let v1 = vec![1.0_f64, 0.0];
let dirs: Vec<&[f64]> = vec![&v1];
let weights = vec![-1.0_f64];
let _ = estimate_weighted(&Laplacian, &tape, &x, &dirs, &weights);
}
#[test]
#[should_panic(expected = "k must be <= 18")]
fn diagonal_kth_order_const_k19_panics() {
let x = [1.0_f64];
let tape = record_fn(|v| v[0] * v[0], &x);
let _ = echidna::stde::diagonal_kth_order_const::<f64, 20>(&tape, &x);
}
#[test]
fn diagonal_kth_order_constant_tape_recovers_primal() {
let (tape, _) = echidna::record(|_: &[BReverse<f64>]| BReverse::constant(3.5), &[]);
let (value, diag) = echidna::stde::diagonal_kth_order(&tape, &[], 3);
assert_eq!(value, 3.5);
assert!(diag.is_empty());
let (value_c, diag_c) = echidna::stde::diagonal_kth_order_const::<f64, 4>(&tape, &[]);
assert_eq!(value_c, 3.5);
assert!(diag_c.is_empty());
}