gam_problem/
finite_validation.rs1use crate::EstimationError;
4use ndarray::Array1;
5
6pub fn ensure_finite_scalar_estimation(name: &str, value: f64) -> Result<(), EstimationError> {
7 if value.is_finite() {
8 Ok(())
9 } else {
10 Err(EstimationError::InvalidInput(format!(
11 "{name} must be finite, got {value}"
12 )))
13 }
14}
15
16pub fn validate_all_finite_estimation<I>(label: &str, values: I) -> Result<(), EstimationError>
17where
18 I: IntoIterator<Item = f64>,
19{
20 for (idx, value) in values.into_iter().enumerate() {
21 if !value.is_finite() {
22 return Err(EstimationError::InvalidInput(format!(
23 "{label}[{idx}] must be finite, got {value}"
24 )));
25 }
26 }
27 Ok(())
28}
29
30pub fn validate_all_finite_trial_point<I>(label: &str, values: I) -> Result<(), EstimationError>
46where
47 I: IntoIterator<Item = f64>,
48{
49 for (idx, value) in values.into_iter().enumerate() {
50 if !value.is_finite() {
51 return Err(EstimationError::TrialPointRefused {
52 reason: format!("{label}[{idx}] must be finite, got {value}"),
53 });
54 }
55 }
56 Ok(())
57}
58
59#[inline]
60pub fn bail_if_cached_beta_non_finite(beta: &Array1<f64>) -> Result<(), EstimationError> {
61 if beta.iter().any(|v| !v.is_finite()) {
62 return Err(EstimationError::InvalidInput(
63 "cached inner beta contains non-finite entries".to_string(),
64 ));
65 }
66 Ok(())
67}
68
69pub fn ensure_finite_scalar(name: &str, value: f64) -> Result<(), String> {
71 ensure_finite_scalar_estimation(name, value).map_err(|err| err.to_string())
72}
73
74pub fn validate_all_finite<I: IntoIterator<Item = f64>>(
76 label: &str,
77 values: I,
78) -> Result<(), String> {
79 validate_all_finite_estimation(label, values).map_err(|err| err.to_string())
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn ensure_finite_scalar_ok_for_finite() {
88 assert!(ensure_finite_scalar("x", 0.0).is_ok());
89 assert!(ensure_finite_scalar("x", -1.5).is_ok());
90 assert!(ensure_finite_scalar("x", f64::MIN).is_ok());
91 }
92
93 #[test]
94 fn ensure_finite_scalar_err_for_nan() {
95 let e = ensure_finite_scalar("my_value", f64::NAN).unwrap_err();
96 assert!(e.contains("my_value"), "error should mention the name: {e}");
97 }
98
99 #[test]
100 fn ensure_finite_scalar_err_for_inf() {
101 assert!(ensure_finite_scalar("v", f64::INFINITY).is_err());
102 assert!(ensure_finite_scalar("v", f64::NEG_INFINITY).is_err());
103 }
104
105 #[test]
106 fn validate_all_finite_ok_for_finite_slice() {
107 assert!(validate_all_finite("vec", [1.0, 2.0, 3.0]).is_ok());
108 assert!(validate_all_finite("empty", std::iter::empty()).is_ok());
109 }
110
111 #[test]
112 fn validate_all_finite_err_reports_index() {
113 let e = validate_all_finite("arr", [1.0, f64::NAN, 3.0]).unwrap_err();
114 assert!(e.contains("arr[1]"), "error should mention arr[1]: {e}");
115 }
116
117 #[test]
118 fn validate_all_finite_err_reports_inf() {
119 let e = validate_all_finite("data", [0.0, f64::INFINITY]).unwrap_err();
120 assert!(e.contains("data[1]"), "error should mention data[1]: {e}");
121 }
122
123 #[test]
124 fn bail_if_cached_beta_ok_for_finite() {
125 let beta = ndarray::array![1.0, -2.0, 3.0];
126 assert!(bail_if_cached_beta_non_finite(&beta).is_ok());
127 }
128
129 #[test]
130 fn bail_if_cached_beta_err_for_nan() {
131 let beta = ndarray::array![1.0, f64::NAN];
132 assert!(bail_if_cached_beta_non_finite(&beta).is_err());
133 }
134}