use crate::{Contains as _, Error, F32AbsValAtMost1, F32Bw0and1, ThresholdState};
pub fn threshold_and_mean(mod_list: &[u8]) -> Result<F32Bw0and1, Error> {
let win_size: usize = match mod_list.len() {
0 => Err(Error::EmptyWindow("in `threshold_and_mean`".to_owned())),
v => Ok(v),
}?;
let count_mod: usize = mod_list
.iter()
.filter(|x| ThresholdState::GtEq(128).contains(x))
.count();
#[expect(
clippy::cast_precision_loss,
reason = "if two nums below have >6 digits, we suffer precision loss. \
may fix in future. this means we are using ~Mbp windows, which is unlikely..."
)]
F32Bw0and1::new(count_mod as f32 / win_size as f32)
}
#[expect(
clippy::cast_precision_loss,
reason = "if win_size or mod_list length has too many digits (>6), we suffer precision loss. \
may fix in future. this means we are using ~Mbp windows, which is unlikely..."
)]
pub fn threshold_and_gradient(mod_list: &[u8]) -> Result<F32AbsValAtMost1, Error> {
let win_size = match mod_list.len() {
0 => Err(Error::EmptyWindow(
"threshold and gradient needs > 1 data point".to_owned(),
)),
1 => Err(Error::InsufficientDataSize(
"threshold and gradient needs > 1 data point".to_owned(),
)),
v => Ok(v),
}?;
let x_mean = f32::midpoint(win_size as f32, 1.0);
let numerator: f32 = mod_list
.iter()
.enumerate()
.map(|(i, x)| {
if ThresholdState::GtEq(128).contains(x) {
i as f32 + 1.0 - x_mean
} else {
0.0
}
})
.sum();
let denominator: f32 = (1..=win_size)
.map(|x| (x as f32 - x_mean) * (x as f32 - x_mean))
.sum();
F32AbsValAtMost1::new(numerator / denominator)
}
pub fn threshold_and_mean_and_thres_win(
mod_list: &[u8],
threshold: F32Bw0and1,
) -> Result<F32Bw0and1, Error> {
let density = threshold_and_mean(mod_list)?;
if density.val() < threshold.val() {
Err(Error::WindowDensBelowThres { density, threshold })
} else {
Ok(density)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[expect(
clippy::float_cmp,
reason = "divide-by-four unlikely to give floating point errors"
)]
fn threshold_and_mean_no_modified() {
let mod_data = [0, 50, 100, 127];
let result = threshold_and_mean(&mod_data).unwrap();
assert_eq!(result.val(), 0.0);
}
#[test]
#[expect(
clippy::float_cmp,
reason = "divide-by-four unlikely to give floating point errors"
)]
fn threshold_and_mean_half_modified() {
let mod_data = [100, 128, 50, 200];
let result = threshold_and_mean(&mod_data).unwrap();
assert_eq!(result.val(), 0.5);
}
#[test]
#[expect(
clippy::float_cmp,
reason = "divide-by-five unlikely to give floating point errors"
)]
fn threshold_and_gradient_decreasing() {
let mod_data = [200, 128, 0, 0];
let result = threshold_and_gradient(&mod_data).unwrap();
assert_eq!(result.val(), -0.4);
}
#[test]
#[expect(
clippy::float_cmp,
reason = "divide-by-five unlikely to give floating point errors"
)]
fn threshold_and_gradient_alternating() {
let mod_data = [128, 0, 128, 0];
let result = threshold_and_gradient(&mod_data).unwrap();
assert_eq!(result.val(), -0.2);
}
#[test]
#[expect(
clippy::float_cmp,
reason = "divide-by-four unlikely to give floating point errors"
)]
fn threshold_and_mean_and_thres_win_above_threshold() {
let mod_data = [0, 128, 200, 255];
let threshold = F32Bw0and1::new(0.5).unwrap();
let result = threshold_and_mean_and_thres_win(&mod_data, threshold).unwrap();
assert_eq!(result.val(), 0.75);
}
#[test]
#[expect(
clippy::float_cmp,
reason = "divide-by-four unlikely to give floating point errors"
)]
fn threshold_and_mean_and_thres_win_equal_threshold() {
let mod_data = [0, 0, 128, 128];
let threshold = F32Bw0and1::new(0.5).unwrap();
let result = threshold_and_mean_and_thres_win(&mod_data, threshold).unwrap();
assert_eq!(result.val(), 0.5);
}
#[test]
#[should_panic(expected = "WindowDensBelowThres")]
fn threshold_and_mean_and_thres_win_below_threshold() {
let mod_data = [0, 0, 0, 128];
let threshold = F32Bw0and1::new(0.5).unwrap();
let _: F32Bw0and1 = threshold_and_mean_and_thres_win(&mod_data, threshold).unwrap();
}
#[test]
#[should_panic(expected = "EmptyWindow")]
fn threshold_and_mean_empty_array() {
let mod_data: [u8; 0] = [];
let _: F32Bw0and1 = threshold_and_mean(&mod_data).unwrap();
}
#[test]
#[should_panic(expected = "EmptyWindow")]
fn threshold_and_gradient_empty_array() {
let mod_data: [u8; 0] = [];
let _: F32AbsValAtMost1 = threshold_and_gradient(&mod_data).unwrap();
}
#[test]
#[should_panic(expected = "InsufficientDataSize")]
fn threshold_and_gradient_single_element() {
let mod_data = [128];
let _: F32AbsValAtMost1 = threshold_and_gradient(&mod_data).unwrap();
}
}