Skip to main content

nereids_fitting/
active_mask.rs

1//! Active-bin masking for fit-energy-range restriction.
2//!
3//! SAMMY EMIN/EMAX equivalent (INPut-file card set 2, manual Table VI A.1).
4//! When a user restricts the fit to `[E_min, E_max]`, the GUI extends
5//! the energy grid by ~5×FWHM beyond each boundary so resonances near
6//! the boundaries are correctly broadened, and the cost-function paths
7//! (LM transmission, joint-Poisson PBD) consult the mask returned here
8//! to skip residual contributions outside `[E_min, E_max]`.
9//!
10//! Returns `None` when no range is set; callers treat that as
11//! "all bins active" (default behaviour).
12
13/// Build a per-bin active mask from the energy grid and an optional
14/// user-specified `[E_min, E_max]` range.  Bin `i` is active iff
15/// `E_min ≤ energies[i] ≤ E_max`.
16///
17/// Returns `None` when `range` is `None` so the caller can short-circuit
18/// the all-active common case without allocating.
19pub fn build_active_mask(energies: &[f64], range: Option<(f64, f64)>) -> Option<Vec<bool>> {
20    let (lo, hi) = range?;
21    Some(energies.iter().map(|&e| e >= lo && e <= hi).collect())
22}
23
24/// Active-bin count for a mask.  `None` = all bins active.
25pub fn active_count(mask: Option<&[bool]>, n_total: usize) -> usize {
26    match mask {
27        Some(m) => m.iter().filter(|&&b| b).count(),
28        None => n_total,
29    }
30}
31
32#[cfg(test)]
33mod tests {
34    use super::*;
35
36    #[test]
37    fn build_returns_none_for_full_grid() {
38        let energies = [1.0, 2.0, 3.0];
39        assert!(build_active_mask(&energies, None).is_none());
40    }
41
42    #[test]
43    fn build_marks_bins_inside_range_inclusive() {
44        let energies = [1.0, 2.0, 3.0, 4.0, 5.0];
45        let mask = build_active_mask(&energies, Some((2.0, 4.0))).unwrap();
46        assert_eq!(mask, vec![false, true, true, true, false]);
47    }
48
49    #[test]
50    fn build_handles_empty_active_region() {
51        let energies = [1.0, 2.0, 3.0];
52        let mask = build_active_mask(&energies, Some((10.0, 20.0))).unwrap();
53        assert_eq!(mask, vec![false, false, false]);
54    }
55
56    #[test]
57    fn active_count_with_mask_counts_true_bins() {
58        let mask = [true, false, true, true, false];
59        assert_eq!(active_count(Some(&mask), 5), 3);
60    }
61
62    #[test]
63    fn active_count_without_mask_returns_total() {
64        assert_eq!(active_count(None, 42), 42);
65    }
66}