use ndarray::Array1;
pub fn regression_slope_per_octave_in_range(
freq: &Array1<f64>,
y: &Array1<f64>,
fmin: f64,
fmax: f64,
) -> Option<f64> {
assert_eq!(freq.len(), y.len(), "freq and y must have same length");
if fmax <= fmin {
return None;
}
let mut n: usize = 0;
let mut sum_x = 0.0;
let mut sum_y = 0.0;
let mut sum_xy = 0.0;
let mut sum_x2 = 0.0;
for i in 0..freq.len() {
let f = freq[i];
if f > 0.0 && f >= fmin && f <= fmax {
let xi = f.log2();
let yi = y[i];
n += 1;
sum_x += xi;
sum_y += yi;
sum_xy += xi * yi;
sum_x2 += xi * xi;
}
}
if n < 2 {
return None;
}
let n_f = n as f64;
let cov_xy = sum_xy - (sum_x * sum_y) / n_f;
let var_x = sum_x2 - (sum_x * sum_x) / n_f;
if var_x.abs() < 1e-10 {
return None;
}
Some(cov_xy / var_x)
}
pub fn curve_slope_per_octave_in_range(curve: &crate::Curve, fmin: f64, fmax: f64) -> Option<f64> {
regression_slope_per_octave_in_range(&curve.freq, &curve.spl, fmin, fmax)
}
pub fn calculate_standard_deviation_in_range(
freq: &Array1<f64>,
deviation: &Array1<f64>,
fmin: f64,
fmax: f64,
) -> f64 {
assert_eq!(
freq.len(),
deviation.len(),
"freq and deviation must have same length"
);
let mut values = Vec::new();
for i in 0..freq.len() {
let f = freq[i];
if f >= fmin && f <= fmax {
values.push(deviation[i]);
}
}
if values.is_empty() {
return 0.0;
}
let mean = values.iter().sum::<f64>() / values.len() as f64;
let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / values.len() as f64;
variance.sqrt()
}
pub(super) fn calculate_absolute_slope_in_range(
freq: &Array1<f64>,
deviation: &Array1<f64>,
fmin: f64,
fmax: f64,
) -> f64 {
match regression_slope_per_octave_in_range(freq, deviation, fmin, fmax) {
Some(slope) => slope.abs(),
None => 0.0,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn regression_slope_per_octave_linear_log_relation_full_range() {
let freq = Array1::from(vec![100.0, 200.0, 400.0, 800.0]);
let y = freq.mapv(|f: f64| 3.0 * f.log2() + 1.0);
let slope = regression_slope_per_octave_in_range(&freq, &y, 100.0, 800.0).unwrap();
assert!((slope - 3.0).abs() < 1e-12);
}
#[test]
fn regression_slope_per_octave_sub_range() {
let freq = Array1::from(vec![100.0, 200.0, 400.0, 800.0]);
let y = freq.mapv(|f: f64| -2.5 * f.log2() + 4.0);
let slope = regression_slope_per_octave_in_range(&freq, &y, 200.0, 800.0).unwrap();
assert!((slope + 2.5).abs() < 1e-12);
}
#[test]
fn test_calculate_standard_deviation_in_range() {
let freq = Array1::from(vec![50.0, 100.0, 1000.0, 5000.0, 10000.0]);
let deviation = Array1::from(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let sd = calculate_standard_deviation_in_range(&freq, &deviation, 50.0, 10000.0);
let expected_sd = 2.0_f64.sqrt();
assert!(
(sd - expected_sd).abs() < 1e-12,
"SD calculation incorrect: got {}, expected {}",
sd,
expected_sd
);
}
#[test]
fn test_calculate_standard_deviation_filtered_range() {
let freq = Array1::from(vec![20.0, 100.0, 1000.0, 5000.0, 15000.0]); let deviation = Array1::from(vec![10.0, 2.0, 4.0, 6.0, 20.0]);
let sd = calculate_standard_deviation_in_range(&freq, &deviation, 50.0, 10000.0);
let expected_sd = (8.0_f64 / 3.0_f64).sqrt();
assert!(
(sd - expected_sd).abs() < 1e-12,
"SD calculation with filtering incorrect: got {}, expected {}",
sd,
expected_sd
);
}
#[test]
fn test_calculate_absolute_slope_in_range() {
let freq = Array1::from(vec![
50.0, 100.0, 200.0, 400.0, 800.0, 1600.0, 3200.0, 6400.0, 10000.0,
]);
let deviation = freq.mapv(|f: f64| 2.0 * f.log2());
let as_value = calculate_absolute_slope_in_range(&freq, &deviation, 50.0, 10000.0);
assert!(
(as_value - 2.0).abs() < 1e-12,
"AS calculation incorrect: got {}, expected 2.0",
as_value
);
}
#[test]
fn test_calculate_absolute_slope_negative() {
let freq = Array1::from(vec![
50.0, 100.0, 200.0, 400.0, 800.0, 1600.0, 3200.0, 6400.0, 10000.0,
]);
let deviation = freq.mapv(|f: f64| -3.0 * f.log2());
let as_value = calculate_absolute_slope_in_range(&freq, &deviation, 50.0, 10000.0);
assert!(
(as_value - 3.0).abs() < 1e-12,
"AS calculation with negative slope incorrect: got {}, expected 3.0",
as_value
);
}
#[test]
fn test_regression_slope_identical_freqs() {
let freq = Array1::from_vec(vec![1000.0, 1000.0, 1000.0]);
let y = Array1::from_vec(vec![80.0, 85.0, 90.0]);
let result = regression_slope_per_octave_in_range(&freq, &y, 999.0, 1001.0);
assert!(
result.is_none(),
"identical frequencies should return None, got {:?}",
result
);
}
}