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