pub fn validity_range(canonical_mnemonic: &str) -> Option<(f64, f64)> {
let key = canonical_mnemonic.trim().to_ascii_uppercase();
let range = match key.as_str() {
"PHIE" | "PHIT" | "NPHI" => (0.0, 1.0), "SW" => (0.0, 1.0), "VSH" => (0.0, 1.0), "NTG" => (0.0, 1.0), "GR" => (0.0, 250.0), "RHOB" => (1.0, 3.5), "DT" => (40.0, 200.0), "PERM" => (0.0, 1.0e6), "RT" => (0.0, 1.0e6), _ => return None,
};
Some(range)
}
pub fn in_range(canonical_mnemonic: &str, value: f64) -> bool {
if value.is_nan() {
return false;
}
match validity_range(canonical_mnemonic) {
Some((lo, hi)) => (lo..=hi).contains(&value),
None => value.is_finite(),
}
}
pub fn mask_out_of_range(canonical_mnemonic: &str, values: &mut [f64]) -> usize {
let Some((lo, hi)) = validity_range(canonical_mnemonic) else {
return 0;
};
let mut rejected = 0;
for v in values.iter_mut() {
if v.is_nan() {
continue;
}
if !(lo..=hi).contains(v) {
*v = f64::NAN;
rejected += 1;
}
}
rejected
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ranges_registered_and_unranged() {
assert_eq!(validity_range("PHIE"), Some((0.0, 1.0)));
assert_eq!(validity_range("sw"), Some((0.0, 1.0))); assert_eq!(validity_range("CUSTOM"), None);
}
#[test]
fn in_range_rejects_nan_and_out_of_bounds() {
assert!(in_range("PHIE", 0.25));
assert!(in_range("PHIE", 0.0)); assert!(!in_range("PHIE", 1.4));
assert!(!in_range("PHIE", f64::NAN));
assert!(in_range("CUSTOM", 999.0)); assert!(!in_range("CUSTOM", f64::INFINITY));
}
#[test]
fn mask_sets_out_of_range_to_nan() {
let mut v = vec![0.1, 1.4, -0.2, 0.3, f64::NAN];
let rejected = mask_out_of_range("PHIE", &mut v);
assert_eq!(rejected, 2); assert_eq!(v[0], 0.1);
assert!(v[1].is_nan());
assert!(v[2].is_nan());
assert_eq!(v[3], 0.3);
assert!(v[4].is_nan()); }
#[test]
fn mask_unranged_is_noop() {
let mut v = vec![999.0, -5.0];
assert_eq!(mask_out_of_range("CUSTOM", &mut v), 0);
assert_eq!(v, vec![999.0, -5.0]);
}
}