use super::distance_correlation::distance_correlation;
use super::gcmi::gcmi;
use super::knn_mi::knn_mutual_information;
use super::transfer_entropy::transfer_entropy_curve;
use crate::features::entropy::permutation_entropy;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Scorer {
Mi,
Pearson,
Spearman,
Kendall,
Distance,
TransferEntropy,
Gcmi,
PermutationEntropy,
SpectralEntropy,
SpectralPredictability,
}
pub fn score(series: &[f64], scorer: Scorer) -> f64 {
let n = series.len();
match scorer {
Scorer::Mi => {
if n < 10 {
return 0.0;
}
knn_mutual_information(&series[..n - 1], &series[1..], 8)
}
Scorer::Pearson => super::lag_correlations::pearson_curve(series, 1)
.first()
.copied()
.unwrap_or(0.0),
Scorer::Spearman => super::lag_correlations::spearman_curve(series, 1)
.first()
.copied()
.unwrap_or(0.0),
Scorer::Kendall => super::lag_correlations::kendall_curve(series, 1)
.first()
.copied()
.unwrap_or(0.0),
Scorer::Distance => {
if n < 5 {
return 0.0;
}
distance_correlation(&series[..n - 1], &series[1..])
}
Scorer::TransferEntropy => transfer_entropy_curve(series, series, 1)
.first()
.copied()
.unwrap_or(0.0),
Scorer::Gcmi => {
if n < 4 {
return 0.0;
}
gcmi(&series[..n - 1], &series[1..])
}
Scorer::PermutationEntropy => {
let m = if n >= 120 {
5
} else if n >= 24 {
4
} else {
3
};
permutation_entropy(series, m, 1)
}
Scorer::SpectralEntropy => spectral_entropy(series),
Scorer::SpectralPredictability => 1.0 - spectral_entropy(series),
}
}
fn spectral_entropy(series: &[f64]) -> f64 {
use crate::detection::welch_periodogram;
let n = series.len();
if n < 16 {
return f64::NAN;
}
let window_size = (n / 4).max(8).next_power_of_two().min(n);
let psd = welch_periodogram(series, window_size, 0.5);
if psd.is_empty() {
return f64::NAN;
}
let total: f64 = psd.iter().map(|(_, p)| p).sum();
if total < 1e-30 {
return 0.0;
}
let n_bins = psd.len() as f64;
let mut h = 0.0;
for &(_, p) in &psd {
let prob = p / total;
if prob > 1e-30 {
h -= prob * prob.ln();
}
}
h / n_bins.ln() }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_scorers_return_finite_on_ar1() {
use rand::{Rng, SeedableRng};
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let n = 200;
let mut s = vec![0.0; n];
for i in 1..n {
s[i] = 0.7 * s[i - 1] + (rng.gen::<f64>() - 0.5) * 2.0;
}
for scorer in [
Scorer::Mi,
Scorer::Pearson,
Scorer::Spearman,
Scorer::Kendall,
Scorer::Distance,
Scorer::TransferEntropy,
Scorer::Gcmi,
Scorer::PermutationEntropy,
Scorer::SpectralEntropy,
Scorer::SpectralPredictability,
] {
let s_val = score(&s, scorer);
assert!(
s_val.is_finite(),
"{:?} returned non-finite: {}",
scorer,
s_val
);
}
}
}