use super::ami::linear_residualize;
use super::knn_mi::knn_mutual_information;
pub fn transfer_entropy_curve(source: &[f64], target: &[f64], max_lag: usize) -> Vec<f64> {
let n = source.len();
assert_eq!(
n,
target.len(),
"source and target must have the same length"
);
let k = 8;
let mut result = Vec::with_capacity(max_lag);
for h in 1..=max_lag {
if n <= h + k {
result.push(0.0);
continue;
}
let usable = n - h;
let y_future = &target[h..h + usable];
let x_past = &source[..usable];
if h == 1 {
result.push(knn_mutual_information(y_future, x_past, k));
continue;
}
let z_cols: Vec<Vec<f64>> = (1..h)
.map(|j| target[h - j..h - j + usable].to_vec())
.collect();
let y_resid = linear_residualize(y_future, &z_cols);
let x_resid = linear_residualize(x_past, &z_cols);
if y_resid.len() < k + 1 {
result.push(0.0);
continue;
}
result.push(knn_mutual_information(&y_resid, &x_resid, k));
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use rand::{Rng, SeedableRng};
#[test]
fn te_from_driver_to_follower_is_positive() {
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let n = 500;
let x: Vec<f64> = (0..n).map(|_| (rng.gen::<f64>() - 0.5) * 2.0).collect();
let mut y = vec![0.0; n];
for t in 1..n {
y[t] = 0.7 * x[t - 1] + (rng.gen::<f64>() - 0.5) * 0.5;
}
let te = transfer_entropy_curve(&x, &y, 3);
assert!(te[0] > 0.2, "TE(1) should be large: {:.4}", te[0]);
}
#[test]
fn te_from_independent_is_near_zero() {
let mut rng = rand::rngs::StdRng::seed_from_u64(99);
let x: Vec<f64> = (0..400).map(|_| (rng.gen::<f64>() - 0.5) * 2.0).collect();
let y: Vec<f64> = (0..400).map(|_| (rng.gen::<f64>() - 0.5) * 2.0).collect();
let te = transfer_entropy_curve(&x, &y, 3);
for &v in &te {
assert!(v < 0.4, "TE from independent should be near 0: {:.4}", v);
}
}
}