pub fn sample_entropy(series: &[f64], m: usize, r: f64) -> f64 {
if series.len() < m + 2 {
return f64::NAN;
}
let count_m = count_matches(series, m, r);
let count_m1 = count_matches(series, m + 1, r);
if count_m == 0 || count_m1 == 0 {
return f64::NAN;
}
-((count_m1 as f64) / (count_m as f64)).ln()
}
pub fn approximate_entropy(series: &[f64], m: usize, r: f64) -> f64 {
if series.len() < m + 2 {
return f64::NAN;
}
let phi_m = phi(series, m, r);
let phi_m1 = phi(series, m + 1, r);
if phi_m.is_nan() || phi_m1.is_nan() {
return f64::NAN;
}
phi_m - phi_m1
}
fn phi(series: &[f64], m: usize, r: f64) -> f64 {
let n = series.len();
if n < m {
return f64::NAN;
}
let n_templates = n - m + 1;
let mut sum = 0.0;
for i in 0..n_templates {
let mut count = 0;
for j in 0..n_templates {
if templates_match(series, i, j, m, r) {
count += 1;
}
}
if count > 0 {
sum += (count as f64 / n_templates as f64).ln();
}
}
sum / n_templates as f64
}
fn count_matches(series: &[f64], m: usize, r: f64) -> usize {
let n = series.len();
if n < m {
return 0;
}
let n_templates = n - m;
let mut count = 0;
for i in 0..n_templates {
for j in (i + 1)..n_templates {
if templates_match(series, i, j, m, r) {
count += 2; }
}
}
count
}
fn templates_match(series: &[f64], i: usize, j: usize, m: usize, r: f64) -> bool {
for k in 0..m {
if (series[i + k] - series[j + k]).abs() > r {
return false;
}
}
true
}
pub fn permutation_entropy(series: &[f64], order: usize, delay: usize) -> f64 {
if order < 2 || series.len() < (order - 1) * delay + 1 {
return f64::NAN;
}
let n_patterns = series.len() - (order - 1) * delay;
let mut pattern_counts = std::collections::HashMap::new();
for i in 0..n_patterns {
let pattern = get_ordinal_pattern(series, i, order, delay);
*pattern_counts.entry(pattern).or_insert(0) += 1;
}
let mut entropy = 0.0;
for &count in pattern_counts.values() {
let p = count as f64 / n_patterns as f64;
if p > 0.0 {
entropy -= p * p.ln();
}
}
entropy
}
pub fn permutation_entropy_normalized(series: &[f64], order: usize, delay: usize) -> f64 {
let entropy = permutation_entropy(series, order, delay);
if entropy.is_nan() {
return f64::NAN;
}
let max_entropy = (factorial(order) as f64).ln();
if max_entropy > 0.0 {
entropy / max_entropy
} else {
entropy
}
}
fn get_ordinal_pattern(series: &[f64], start: usize, order: usize, delay: usize) -> Vec<usize> {
let values: Vec<f64> = (0..order).map(|k| series[start + k * delay]).collect();
let mut indices: Vec<usize> = (0..order).collect();
indices.sort_by(|&a, &b| {
values[a]
.partial_cmp(&values[b])
.unwrap_or(std::cmp::Ordering::Equal)
});
let mut ranks = vec![0; order];
for (rank, &idx) in indices.iter().enumerate() {
ranks[idx] = rank;
}
ranks
}
fn factorial(n: usize) -> usize {
(1..=n).product()
}
pub fn binned_entropy(series: &[f64], max_bins: usize) -> f64 {
if series.is_empty() || max_bins == 0 {
return f64::NAN;
}
let min_val = series.iter().copied().fold(f64::INFINITY, f64::min);
let max_val = series.iter().copied().fold(f64::NEG_INFINITY, f64::max);
if (max_val - min_val).abs() < 1e-10 {
return 0.0; }
let n_bins = max_bins.min(series.len());
let bin_width = (max_val - min_val) / n_bins as f64;
let mut counts = vec![0usize; n_bins];
for &x in series {
let bin = ((x - min_val) / bin_width).floor() as usize;
let bin = bin.min(n_bins - 1); counts[bin] += 1;
}
let n = series.len() as f64;
let mut entropy = 0.0;
for &count in &counts {
if count > 0 {
let p = count as f64 / n;
entropy -= p * p.ln();
}
}
entropy
}
pub fn fourier_entropy(series: &[f64]) -> f64 {
if series.len() < 4 {
return f64::NAN;
}
let psd = compute_psd(series);
if psd.is_empty() {
return f64::NAN;
}
let total: f64 = psd.iter().sum();
if total < 1e-10 {
return 0.0;
}
let mut entropy = 0.0;
for &p in &psd {
let prob = p / total;
if prob > 1e-10 {
entropy -= prob * prob.ln();
}
}
entropy
}
fn compute_psd(series: &[f64]) -> Vec<f64> {
let n = series.len();
let mut psd = Vec::with_capacity(n / 2);
for k in 0..n / 2 {
let mut real = 0.0;
let mut imag = 0.0;
for (t, &x) in series.iter().enumerate() {
let angle = -2.0 * std::f64::consts::PI * k as f64 * t as f64 / n as f64;
real += x * angle.cos();
imag += x * angle.sin();
}
psd.push((real * real + imag * imag) / n as f64);
}
psd
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn sample_entropy_regular() {
let series: Vec<f64> = (0..100)
.map(|i| ((i % 10) as f64 * std::f64::consts::PI / 5.0).sin())
.collect();
let se = sample_entropy(&series, 2, 0.2);
assert!(!se.is_nan());
}
#[test]
fn sample_entropy_random() {
let series: Vec<f64> = (0..100).map(|i| ((i * 7 + 3) % 13) as f64).collect();
let se = sample_entropy(&series, 2, 0.5);
assert!(!se.is_nan());
}
#[test]
fn sample_entropy_constant() {
let series = vec![5.0; 50];
let se = sample_entropy(&series, 2, 0.1);
assert!(se.is_nan() || se.abs() < 0.1);
}
#[test]
fn sample_entropy_short() {
assert!(sample_entropy(&[], 2, 0.2).is_nan());
assert!(sample_entropy(&[1.0, 2.0], 2, 0.2).is_nan());
}
#[test]
fn approximate_entropy_regular() {
let series: Vec<f64> = (0..50).map(|i| (i as f64 * 0.5).sin()).collect();
let ae = approximate_entropy(&series, 2, 0.2);
assert!(!ae.is_nan());
}
#[test]
fn approximate_entropy_constant() {
let series = vec![5.0; 50];
let ae = approximate_entropy(&series, 2, 0.1);
assert!(!ae.is_nan());
assert!(ae.abs() < 0.5);
}
#[test]
fn approximate_entropy_short() {
assert!(approximate_entropy(&[], 2, 0.2).is_nan());
assert!(approximate_entropy(&[1.0, 2.0], 2, 0.2).is_nan());
}
#[test]
fn permutation_entropy_monotonic() {
let series: Vec<f64> = (0..20).map(|i| i as f64).collect();
let pe = permutation_entropy(&series, 3, 1);
assert!(!pe.is_nan());
assert!(pe.abs() < 1e-10, "Monotonic should have 0 PE, got {}", pe);
}
#[test]
fn permutation_entropy_alternating() {
let series: Vec<f64> = (0..20)
.map(|i| if i % 2 == 0 { 0.0 } else { 1.0 })
.collect();
let pe = permutation_entropy(&series, 3, 1);
assert!(!pe.is_nan());
assert!(
pe > 0.6 && pe < 0.8,
"Expected ~ln(2)=0.693 for alternating, got {}",
pe
);
}
#[test]
fn permutation_entropy_random_like() {
let series: Vec<f64> = (0..50).map(|i| ((i * 7 + 3) % 13) as f64).collect();
let pe = permutation_entropy(&series, 3, 1);
assert!(!pe.is_nan());
assert!(pe > 0.5, "Random-like should have moderate PE, got {}", pe);
}
#[test]
fn permutation_entropy_short() {
assert!(permutation_entropy(&[1.0, 2.0], 3, 1).is_nan());
assert!(permutation_entropy(&[1.0, 2.0, 3.0], 5, 1).is_nan());
}
#[test]
fn permutation_entropy_invalid_order() {
let series = vec![1.0, 2.0, 3.0, 4.0, 5.0];
assert!(permutation_entropy(&series, 1, 1).is_nan()); }
#[test]
fn permutation_entropy_normalized_monotonic() {
let series: Vec<f64> = (0..20).map(|i| i as f64).collect();
let pe = permutation_entropy_normalized(&series, 3, 1);
assert!(!pe.is_nan());
assert!(pe.abs() < 1e-10, "Monotonic should have 0 normalized PE");
}
#[test]
fn permutation_entropy_normalized_random() {
let series: Vec<f64> = (0..50).map(|i| ((i * 7 + 3) % 13) as f64).collect();
let pe = permutation_entropy_normalized(&series, 3, 1);
assert!(!pe.is_nan());
assert!(
pe > 0.5 && pe <= 1.0,
"Normalized PE should be in (0.5, 1], got {}",
pe
);
}
#[test]
fn binned_entropy_uniform() {
let series: Vec<f64> = (0..100).map(|i| i as f64).collect();
let be = binned_entropy(&series, 10);
assert!(!be.is_nan());
assert!(be > 1.0, "Uniform should have high entropy, got {}", be);
}
#[test]
fn binned_entropy_constant() {
let series = vec![5.0; 100];
let be = binned_entropy(&series, 10);
assert_relative_eq!(be, 0.0, epsilon = 1e-10);
}
#[test]
fn binned_entropy_bimodal() {
let mut series = vec![0.0; 50];
series.extend(vec![100.0; 50]);
let be = binned_entropy(&series, 10);
assert!(!be.is_nan());
}
#[test]
fn binned_entropy_empty() {
assert!(binned_entropy(&[], 10).is_nan());
}
#[test]
fn binned_entropy_zero_bins() {
let series = vec![1.0, 2.0, 3.0];
assert!(binned_entropy(&series, 0).is_nan());
}
#[test]
fn fourier_entropy_sine() {
let series: Vec<f64> = (0..64)
.map(|i| (2.0 * std::f64::consts::PI * i as f64 / 16.0).sin())
.collect();
let fe = fourier_entropy(&series);
assert!(!fe.is_nan());
}
#[test]
fn fourier_entropy_white_noise_like() {
let series: Vec<f64> = (0..64).map(|i| ((i * 7 + 3) % 13) as f64 - 6.0).collect();
let fe = fourier_entropy(&series);
assert!(!fe.is_nan());
}
#[test]
fn fourier_entropy_constant() {
let series = vec![5.0; 64];
let fe = fourier_entropy(&series);
assert!(!fe.is_nan());
}
#[test]
fn fourier_entropy_short() {
assert!(fourier_entropy(&[]).is_nan());
assert!(fourier_entropy(&[1.0, 2.0]).is_nan());
}
}