pub mod defaults;
pub(crate) mod window;
pub(crate) mod ema;
pub(crate) mod extreme;
pub(crate) mod kernel;
pub(crate) use window::*;
pub(crate) use ema::*;
pub(crate) use extreme::*;
pub(crate) use kernel::*;
pub(crate) fn check_eq_len(lists: &[&[f64]], name: &str) -> Result<(), crate::error::TaError> {
let len = lists[0].len();
for l in lists.iter().skip(1) {
if l.len() != len {
return Err(crate::error::TaError::BadParam(format!(
"{name}: all input arrays must have equal length"
)));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rolling_extreme_matches_naive() {
let mut x: u64 = 0x1234_5678_9abc_def0;
let mut lcg = || {
x = x
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
((x >> 11) as f64 / (1u64 << 53) as f64) * 100.0 - 50.0
};
for &n in &[0usize, 1, 2, 5, 20, 137, 1000] {
for &p in &[1usize, 2, 3, 7, 20, 64] {
if n < p {
continue;
}
let mut v = Vec::with_capacity(n);
for _ in 0..n {
let r = lcg();
if (r as usize) % 8 == 0 {
v.push(42.0);
} else {
v.push(r);
}
}
for &take_max in &[true, false] {
let fast = rolling_extreme(&v, p, take_max);
let naive = rolling_extreme_naive(&v, p, take_max);
for i in 0..n {
assert!(
(fast[i].is_nan() && naive[i].is_nan())
|| (fast[i] - naive[i]).abs() < 1e-12,
"mismatch @ n={n} p={p} max={take_max} i={i}: {} vs {}",
fast[i],
naive[i]
);
}
}
}
}
}
#[test]
fn rolling_extreme_index_matches_naive_leftmost() {
let mut x: u64 = 0x1234_5678_9abc_def0;
let mut lcg = || {
x = x
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
((x >> 11) as f64 / (1u64 << 53) as f64) * 100.0 - 50.0
};
let naive_index = |v: &[f64], p: usize, take_max: bool| -> Vec<f64> {
let n = v.len();
let mut out = vec![f64::NAN; n];
if n < p {
return out;
}
for i in (p - 1)..n {
let mut best = v[i];
let mut best_idx = i;
for j in 1..p {
let val = v[i - j];
let better = if take_max { val >= best } else { val <= best };
if better {
best = val;
best_idx = i - j;
}
}
out[i] = best_idx as f64;
}
out
};
for &n in &[0usize, 1, 2, 5, 20, 137, 1000] {
for &p in &[1usize, 2, 3, 7, 20, 64] {
if n < p {
continue;
}
let mut v = Vec::with_capacity(n);
for _ in 0..n {
let r = lcg();
if (r as usize) % 8 == 0 {
v.push(42.0);
} else {
v.push(r);
}
}
for &take_max in &[true, false] {
let fast = rolling_extreme_index(&v, p, take_max);
let naive = naive_index(&v, p, take_max);
assert!(
fast.len() == naive.len()
&& fast
.iter()
.zip(&naive)
.all(|(a, b)| (a.is_nan() && b.is_nan()) || a == b),
"mismatch @ n={n} p={p} max={take_max}: index impl diverges from naïve"
);
}
}
}
}
#[test]
fn wma_matches_naive() {
let mut x: u64 = 0x9e37_79b9_7f4a_7c15;
let mut lcg = || {
x = x
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
((x >> 11) as f64 / (1u64 << 53) as f64) * 100.0 - 50.0
};
for &n in &[0usize, 1, 2, 5, 20, 137, 1000] {
for &p in &[1usize, 2, 3, 7, 20, 64, 200] {
if n < p {
continue;
}
let mut v = Vec::with_capacity(n);
for _ in 0..n {
v.push(lcg());
}
let fast = wma(&v, p);
let naive = wma_naive(&v, p);
for i in 0..n {
assert!(
(fast[i].is_nan() && naive[i].is_nan())
|| (fast[i] - naive[i]).abs() < 1e-9,
"mismatch @ n={n} p={p} i={i}: {} vs {}",
fast[i],
naive[i]
);
}
}
}
}
}