use crate::xasproc::mathutils::index_nearest;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RangeSide {
Above,
Below,
Between,
}
pub fn deglitch_point_mask(energy: &[f64], e: f64) -> Vec<bool> {
let mut mask = vec![true; energy.len()];
if !energy.is_empty() {
mask[index_nearest(energy, e)] = false;
}
mask
}
pub fn deglitch_range_mask(energy: &[f64], side: RangeSide, e1: f64, e2: f64) -> Vec<bool> {
let n = energy.len();
let mut mask = vec![true; n];
if n == 0 {
return mask;
}
let i1 = index_nearest(energy, e1);
match side {
RangeSide::Above => mask[i1..].fill(false),
RangeSide::Below => mask[..i1].fill(false),
RangeSide::Between => {
let i2 = index_nearest(energy, e2);
let (lo, hi) = if i1 <= i2 { (i1, i2) } else { (i2, i1) };
mask[lo..hi].fill(false);
}
}
mask
}
pub fn trim_mask(energy: &[f64], lo: f64, hi: f64) -> Vec<bool> {
let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
energy.iter().map(|&e| e >= lo && e <= hi).collect()
}
pub fn removed_count(mask: &[bool]) -> usize {
mask.iter().filter(|&&keep| !keep).count()
}
pub fn select(mask: &[bool], arr: &[f64]) -> Vec<f64> {
debug_assert_eq!(mask.len(), arr.len(), "mask and array must be co-indexed");
arr.iter()
.zip(mask)
.filter_map(|(&v, &keep)| keep.then_some(v))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn grid() -> Vec<f64> {
(0..10).map(|i| i as f64).collect()
}
#[test]
fn point_drops_only_nearest() {
let e = grid();
let mask = deglitch_point_mask(&e, 4.2);
assert_eq!(removed_count(&mask), 1);
assert!(!mask[4], "point nearest 4.2 (index 4) must be dropped");
for (i, &keep) in mask.iter().enumerate() {
if i != 4 {
assert!(keep, "index {i} must be kept");
}
}
}
#[test]
fn above_drops_i1_inclusive_to_end() {
let e = grid();
let mask = deglitch_range_mask(&e, RangeSide::Above, 6.0, 0.0);
for (i, &keep) in mask.iter().enumerate() {
assert_eq!(keep, i < 6, "above@6 index {i}");
}
}
#[test]
fn below_drops_up_to_i1_exclusive() {
let e = grid();
let mask = deglitch_range_mask(&e, RangeSide::Below, 3.0, 0.0);
for (i, &keep) in mask.iter().enumerate() {
assert_eq!(keep, i >= 3, "below@3 index {i}");
}
}
#[test]
fn between_drops_half_open_and_is_order_independent() {
let e = grid();
let fwd = deglitch_range_mask(&e, RangeSide::Between, 2.0, 5.0);
let rev = deglitch_range_mask(&e, RangeSide::Between, 5.0, 2.0);
assert_eq!(fwd, rev, "between must sort its bounds");
for (i, &keep) in fwd.iter().enumerate() {
assert_eq!(keep, !(2..5).contains(&i), "between@[2,5) index {i}");
}
}
#[test]
fn trim_keeps_inclusive_window() {
let e = grid();
let mask = trim_mask(&e, 3.0, 7.0);
for (i, &keep) in mask.iter().enumerate() {
assert_eq!(keep, (3..=7).contains(&i), "trim[3,7] index {i}");
}
assert_eq!(mask, trim_mask(&e, 7.0, 3.0));
}
#[test]
fn select_keeps_co_indexed_values() {
let e = grid();
let mask = deglitch_range_mask(&e, RangeSide::Between, 2.0, 5.0);
let kept = select(&mask, &e);
assert_eq!(kept, vec![0.0, 1.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
assert_eq!(kept.len(), e.len() - removed_count(&mask));
}
#[test]
fn empty_grid_is_noop() {
assert!(deglitch_point_mask(&[], 1.0).is_empty());
assert!(deglitch_range_mask(&[], RangeSide::Above, 1.0, 2.0).is_empty());
assert!(trim_mask(&[], 1.0, 2.0).is_empty());
}
}