gam_problem/
log_strength.rs1pub const LOG_STRENGTH_MIN: f64 = -700.0;
15
16pub const LOG_STRENGTH_MAX: f64 = 700.0;
18
19#[derive(Debug, Clone, Copy, PartialEq)]
21pub struct LogStrengthDomainError {
22 pub value: f64,
23}
24
25impl std::fmt::Display for LogStrengthDomainError {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 write!(
28 f,
29 "log strength must be finite and in [{LOG_STRENGTH_MIN}, {LOG_STRENGTH_MAX}]; got {}",
30 self.value
31 )
32 }
33}
34
35impl std::error::Error for LogStrengthDomainError {}
36
37#[derive(Debug, Clone, Copy, PartialEq)]
38pub struct PhysicalStrengthDomainError {
39 pub value: f64,
40}
41
42impl std::fmt::Display for PhysicalStrengthDomainError {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 write!(
45 f,
46 "physical strength must be positive and finite with its logarithm in [{LOG_STRENGTH_MIN}, {LOG_STRENGTH_MAX}]; got {}",
47 self.value
48 )
49 }
50}
51
52impl std::error::Error for PhysicalStrengthDomainError {}
53
54#[derive(Debug, Clone, Copy, PartialEq)]
57pub struct IndexedLogStrengthDomainError {
58 pub coordinate: usize,
59 pub value: f64,
60}
61
62impl std::fmt::Display for IndexedLogStrengthDomainError {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 write!(
65 f,
66 "log strength coordinate {} must be finite and in [{LOG_STRENGTH_MIN}, {LOG_STRENGTH_MAX}]; got {}",
67 self.coordinate, self.value
68 )
69 }
70}
71
72impl std::error::Error for IndexedLogStrengthDomainError {}
73
74impl From<IndexedLogStrengthDomainError> for crate::EstimationError {
75 fn from(error: IndexedLogStrengthDomainError) -> Self {
76 Self::LogStrengthDomainViolation {
77 coordinate: error.coordinate,
78 value: error.value,
79 lower: LOG_STRENGTH_MIN,
80 upper: LOG_STRENGTH_MAX,
81 }
82 }
83}
84
85#[inline]
87pub fn validate_log_strength(log_strength: f64) -> Result<(), LogStrengthDomainError> {
88 if log_strength.is_finite() && (LOG_STRENGTH_MIN..=LOG_STRENGTH_MAX).contains(&log_strength) {
89 Ok(())
90 } else {
91 Err(LogStrengthDomainError {
92 value: log_strength,
93 })
94 }
95}
96
97pub fn validate_log_strengths(
100 values: impl IntoIterator<Item = f64>,
101) -> Result<(), IndexedLogStrengthDomainError> {
102 for (coordinate, value) in values.into_iter().enumerate() {
103 validate_log_strength(value)
104 .map_err(|_| IndexedLogStrengthDomainError { coordinate, value })?;
105 }
106 Ok(())
107}
108
109pub fn checked_exp_log_strengths(
111 values: impl IntoIterator<Item = f64>,
112) -> Result<Vec<f64>, IndexedLogStrengthDomainError> {
113 let mut strengths = Vec::new();
114 for (coordinate, value) in values.into_iter().enumerate() {
115 strengths.push(
116 checked_exp_log_strength(value)
117 .map_err(|_| IndexedLogStrengthDomainError { coordinate, value })?,
118 );
119 }
120 Ok(strengths)
121}
122
123#[inline]
129pub fn checked_exp_log_strength(log_strength: f64) -> Result<f64, LogStrengthDomainError> {
130 validate_log_strength(log_strength)?;
131 Ok(log_strength.exp())
132}
133
134pub fn checked_log_strength(strength: f64) -> Result<f64, PhysicalStrengthDomainError> {
137 if !(strength.is_finite() && strength > 0.0) {
138 return Err(PhysicalStrengthDomainError { value: strength });
139 }
140 let log_strength = strength.ln();
141 validate_log_strength(log_strength)
142 .map_err(|_| PhysicalStrengthDomainError { value: strength })?;
143 Ok(log_strength)
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn exact_closed_domain_accepts_both_endpoints_without_saturation() {
152 for endpoint in [LOG_STRENGTH_MIN, LOG_STRENGTH_MAX] {
153 let strength = checked_exp_log_strength(endpoint).expect("closed endpoint");
154 assert_eq!(strength.to_bits(), endpoint.exp().to_bits());
155 assert!(strength.is_finite() && strength > 0.0);
156 }
157 }
158
159 #[test]
160 fn exact_closed_domain_rejects_unsupported_and_nonfinite_values() {
161 for value in [
162 LOG_STRENGTH_MIN - 1.0,
163 LOG_STRENGTH_MAX + 1.0,
164 f64::NEG_INFINITY,
165 f64::INFINITY,
166 f64::NAN,
167 ] {
168 assert_eq!(
169 checked_exp_log_strength(value).unwrap_err().value.to_bits(),
170 value.to_bits()
171 );
172 }
173 }
174
175 #[test]
176 fn vector_validation_reports_the_smallest_bad_coordinate_atomically() {
177 let values = [0.0, LOG_STRENGTH_MAX + 1.0, f64::NAN];
178 let error = checked_exp_log_strengths(values).unwrap_err();
179 assert_eq!(error.coordinate, 1);
180 assert_eq!(error.value, LOG_STRENGTH_MAX + 1.0);
181 }
182
183 #[test]
184 fn physical_strength_conversion_refuses_floor_and_ceiling_cases() {
185 for value in [0.0, -1.0, f64::INFINITY, f64::NAN] {
186 assert!(checked_log_strength(value).is_err());
187 }
188 for endpoint in [LOG_STRENGTH_MIN, LOG_STRENGTH_MAX] {
189 assert!(checked_log_strength(endpoint.exp()).is_ok());
190 }
191 }
192}