num_valid/functions/abs.rs
1#![deny(rustdoc::broken_intra_doc_links)]
2
3use crate::{
4 functions::FunctionErrors,
5 kernels::{RawComplexTrait, RawScalarTrait},
6 validation::StrictFinitePolicy,
7};
8use num::Complex;
9use thiserror::Error;
10use try_create::ValidationPolicy;
11
12//------------------------------------------------------------------------------------------------
13/// Errors that can occur during the input validation phase when attempting to compute
14/// the absolute value of a number.
15///
16/// This enum is used as a source for the `Input` variant of composed error types like
17/// [`AbsRealErrors`] or [`AbsComplexErrors`]. It is generic over `RawScalar`, which
18/// represents the specific scalar type being validated (e.g., [`f64`], [`Complex<f64>`], etc.).
19#[derive(Debug, Error)]
20pub enum AbsInputErrors<RawScalar: RawScalarTrait> {
21 /// The input value failed basic validation checks.
22 ///
23 /// This error occurs if the input value itself is considered invalid
24 /// according to the validation policy (e.g., [`StrictFinitePolicy`]),
25 /// such as being NaN or Infinity, before the absolute value calculation
26 /// is attempted.
27 #[error("the input value is invalid!")]
28 ValidationError {
29 /// The underlying validation error from the input type.
30 ///
31 /// This provides more specific details about why the input value
32 /// was considered invalid.
33 #[source]
34 #[backtrace]
35 source: <RawScalar as RawScalarTrait>::ValidationErrors,
36 },
37}
38
39/// A type alias for [`FunctionErrors`], specialized for errors that can occur during
40/// the computation of the absolute value of a real number.
41///
42/// It combines input validation errors specific to the `abs` operation on real numbers
43/// with potential validation errors of the resulting real number.
44///
45/// # Generic Parameters
46///
47/// - `RawReal`: A type that implements [`RawRealTrait`](crate::kernels::RawRealTrait). This defines:
48/// - The error type for the input: `AbsInputErrors<RawReal>`.
49/// - The raw error type for the output: `<RawReal as RawScalarTrait>::ValidationErrors`.
50///
51/// # Variants
52///
53/// This type alias wraps [`FunctionErrors`], which has the following variants:
54/// - `Input { source: AbsInputErrors<RawReal> }`: Indicates that the input real number
55/// provided for absolute value computation was invalid. The `source` field contains
56/// an [`AbsInputErrors`] detailing the specific input failure.
57/// - `Output { source: <RawReal as RawScalarTrait>::ValidationErrors }`: Indicates that the computed absolute value (a real number)
58/// itself failed validation. This typically means the result of the `abs` operation yielded
59/// a non-finite value (NaN or Infinity). The `source` field contains the raw validation error
60/// for the output.
61pub type AbsRealErrors<RawReal> =
62 FunctionErrors<AbsInputErrors<RawReal>, <RawReal as RawScalarTrait>::ValidationErrors>;
63
64/// A type alias for [`FunctionErrors`], specialized for errors that can occur during
65/// the computation of the absolute value (or modulus) of a complex number.
66///
67/// It combines input validation errors specific to the `abs` operation on complex numbers
68/// with potential validation errors of the resulting real number (the modulus).
69///
70/// # Generic Parameters
71///
72/// - `RawComplex`: A type that implements [`RawComplexTrait`]. This defines:
73/// - The error type for the input complex number: `AbsInputErrors<RawComplex>`.
74/// - The raw error type for the output real number: `<<RawComplex as RawComplexTrait>::RawReal as RawScalarTrait>::ValidationErrors`.
75///
76/// # Variants
77///
78/// This type alias wraps [`FunctionErrors`], which has the following variants:
79/// - `Input { source: AbsInputErrors<RawComplex> }`: Indicates that the input complex number
80/// provided for absolute value computation was invalid. The `source` field contains
81/// an [`AbsInputErrors`] detailing the specific input failure.
82/// - `Output { source: <<RawComplex as RawComplexTrait>::RawReal as RawScalarTrait>::ValidationErrors }`: Indicates that the computed
83/// absolute value (a real number) itself failed validation. This typically means the result of the
84/// `norm()` operation yielded a non-finite value. The `source` field contains the raw validation
85/// error for the output real number.
86pub type AbsComplexErrors<RawComplex> = FunctionErrors<
87 AbsInputErrors<RawComplex>,
88 <<RawComplex as RawComplexTrait>::RawReal as RawScalarTrait>::ValidationErrors,
89>;
90
91/// This trait provides the interface for the function used to compute the *absolute value*
92/// (also known as modulus or magnitude) of a number, which can be real or complex.
93///
94/// The absolute value of a real number `x` is `|x|`, which is `x` if `x` is non-negative,
95/// and `-x` if `x` is negative.
96/// The absolute value (or modulus) of a complex number `z = a + b*i` is `sqrt(a^2 + b^2)`.
97/// In both cases, the result is always a non-negative real number.
98///
99/// This trait provides two methods:
100/// - [`try_abs`](Abs::try_abs): A fallible version that performs validation on the input
101/// (and potentially the output) and returns a [`Result`]. This is the preferred method
102/// when robust error handling is required.
103/// - [`abs`](Abs::abs): A convenient infallible version that panics if `try_abs` would return
104/// an error in debug builds, or directly computes the value in release builds.
105/// Use this when the input is known to be valid or when panicking on error is acceptable.
106///
107/// # Associated types
108///
109/// - `Output`: The type of the result of the absolute value operation. This is constrained
110/// by [`RealScalar`](crate::RealScalar) to ensure it's a real number type.
111/// - `Error` : The error type that can be returned by the [`try_abs`](Abs::try_abs) method.
112pub trait Abs: Sized {
113 /// The output type of the *absolute value* function.
114 ///
115 /// This is always a real number type, even when the input `Self` is a complex number.
116 /// It must implement the [`RealScalar`](crate::RealScalar) trait.
117 ///
118 // TODO: the type Output must be bounded to be a RealScalar!
119 // type Output: RealScalar;
120 type Output: Sized;
121
122 /// The error type that can be returned by the [`try_abs`](Abs::try_abs) method.
123 ///
124 /// This type must implement [`std::error::Error`]. Specific implementations will
125 /// use types like [`AbsRealErrors`] or [`AbsComplexErrors`] to provide detailed
126 /// information about the failure.
127 type Error: std::error::Error;
128
129 /// Attempts to compute the absolute value of `self`.
130 fn try_abs(self) -> Result<Self::Output, Self::Error>;
131
132 /// Returns the *absolute value* of `self`.
133 fn abs(self) -> Self::Output;
134}
135
136impl Abs for f64 {
137 type Output = f64;
138 type Error = AbsRealErrors<f64>;
139
140 /// Attempts to compute the absolute value of `self`.
141 ///
142 /// This method performs validation on the input value according to the
143 /// [`StrictFinitePolicy`] (i.e., the input must not be NaN, Infinity, or subnormal).
144 /// If the input is valid, it computes the absolute value. The result is also
145 /// validated to ensure it is a finite real number.
146 ///
147 /// # Returns
148 ///
149 /// - `Ok(Self::Output)`: If the input is valid and the absolute value is successfully computed
150 /// and validated.
151 /// - `Err(Self::Error)`: If the input fails validation, or if the computed absolute value
152 /// fails validation. The specific error variant will indicate the cause of the failure.
153 #[inline(always)]
154 fn try_abs(self) -> Result<f64, Self::Error> {
155 StrictFinitePolicy::<f64, 53>::validate(self)
156 .map_err(|e| AbsInputErrors::ValidationError { source: e }.into())
157 .and_then(|v| {
158 StrictFinitePolicy::<f64, 53>::validate(f64::abs(v))
159 .map_err(|e| AbsRealErrors::Output { source: e })
160 })
161 }
162
163 /// Returns the *absolute value* of `self`.
164 ///
165 /// This method provides a convenient way to compute the absolute value.
166 ///
167 /// # Behavior
168 ///
169 /// - In **debug builds** (`#[cfg(debug_assertions)]`): This method calls
170 /// [`try_abs()`](Abs::try_abs) and unwraps the result. It will panic if `try_abs`
171 /// returns an error (e.g., if the input is NaN or Infinity).
172 /// - In **release builds** (`#[cfg(not(debug_assertions))]`): This method
173 /// calls `f64::abs()` directly for performance, bypassing the
174 /// validations performed by `try_abs`.
175 ///
176 /// # Panics
177 ///
178 /// This method will panic in debug builds if `try_abs()` would return an `Err`.
179 /// In release builds, the behavior for invalid inputs (like NaN or Infinity)
180 /// will match `f64::abs()` (e.g., `f64::NAN.abs()` is `NAN`).
181 #[inline(always)]
182 fn abs(self) -> f64 {
183 #[cfg(debug_assertions)]
184 {
185 self.try_abs()
186 .expect("Error in the computation of the absolute value in debug mode")
187 }
188 #[cfg(not(debug_assertions))]
189 {
190 f64::abs(self)
191 }
192 }
193}
194
195impl Abs for Complex<f64> {
196 type Output = f64;
197 type Error = AbsComplexErrors<Complex<f64>>;
198
199 /// Attempts to compute the absolute value (modulus) of `self`.
200 ///
201 /// This method performs validation on the input value according to the
202 /// [`StrictFinitePolicy`] (i.e., both real and imaginary parts must be finite).
203 /// If the input is valid, it computes the modulus. The result is also
204 /// validated to ensure it is a finite real number.
205 #[inline(always)]
206 fn try_abs(self) -> Result<f64, AbsComplexErrors<Complex<f64>>> {
207 StrictFinitePolicy::<Complex<f64>, 53>::validate(self)
208 .map_err(|e| AbsInputErrors::ValidationError { source: e }.into())
209 .and_then(|v| {
210 let norm = v.norm();
211 StrictFinitePolicy::<f64, 53>::validate(norm).map_err(|e| AbsComplexErrors::<
212 Complex<f64>,
213 >::Output {
214 source: e,
215 })
216 })
217 }
218
219 /// Returns the *absolute value* of `self`.
220 ///
221 /// # Behavior
222 ///
223 /// - In **debug builds** (`#[cfg(debug_assertions)]`): This method calls
224 /// [`try_abs()`](Abs::try_abs) and unwraps the result. It will panic if `try_abs`
225 /// returns an error.
226 /// - In **release builds** (`#[cfg(not(debug_assertions))]`): This method calls
227 /// `self.norm()` directly for performance.
228 ///
229 /// # Panics
230 ///
231 /// This method will panic in debug builds if `try_abs()` would return an `Err`.
232 #[inline(always)]
233 fn abs(self) -> f64 {
234 #[cfg(debug_assertions)]
235 {
236 self.try_abs()
237 .expect("Error in the computation of the absolute value in debug mode")
238 }
239 #[cfg(not(debug_assertions))]
240 {
241 self.norm()
242 }
243 }
244}
245
246//------------------------------------------------------------------------------------------------
247
248//------------------------------------------------------------------------------------------------
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use num::Complex;
253
254 mod abs {
255 use super::*;
256
257 mod native64 {
258 use super::*;
259
260 mod real {
261 use super::*;
262
263 #[test]
264 fn abs_valid() {
265 let value = 4.0;
266 assert_eq!(value.try_abs().unwrap(), 4.0);
267 assert_eq!(value.abs(), 4.0);
268 }
269
270 #[test]
271 fn abs_negative() {
272 let value = -4.0;
273 assert_eq!(value.try_abs().unwrap(), 4.0);
274 assert_eq!(value.abs(), 4.0);
275 }
276
277 #[test]
278 fn abs_zero() {
279 let value = 0.0;
280 assert_eq!(value.try_abs().unwrap(), 0.0);
281 assert_eq!(value.abs(), 0.0);
282 }
283
284 #[test]
285 fn abs_nan() {
286 let value = f64::NAN;
287 let result = value.try_abs().unwrap_err();
288 assert!(matches!(result, AbsRealErrors::Input { .. }));
289 }
290
291 #[test]
292 fn abs_infinity() {
293 let value = f64::INFINITY;
294 assert!(matches!(
295 value.try_abs().unwrap_err(),
296 AbsRealErrors::Input { .. }
297 ));
298 }
299
300 #[test]
301 fn abs_subnormal() {
302 let value = f64::MIN_POSITIVE / 2.0;
303 assert!(matches!(
304 value.try_abs().unwrap_err(),
305 AbsRealErrors::Input { .. }
306 ));
307 }
308 }
309
310 mod complex {
311 use super::*;
312
313 #[test]
314 fn abs_valid() {
315 let value = Complex::new(4.0, 0.0);
316 assert_eq!(value.try_abs().unwrap(), 4.0);
317 assert_eq!(value.abs(), 4.0);
318 }
319
320 #[test]
321 fn abs_negative() {
322 let value = Complex::new(-4.0, 0.0);
323 assert_eq!(value.try_abs().unwrap(), 4.0);
324 assert_eq!(value.abs(), 4.0);
325 }
326
327 #[test]
328 fn abs_zero() {
329 let value = Complex::new(0.0, 0.0);
330 assert_eq!(value.try_abs().unwrap(), 0.0);
331 assert_eq!(value.abs(), 0.0);
332 }
333
334 #[test]
335 fn abs_nan() {
336 let value = Complex::new(f64::NAN, 0.0);
337 assert!(matches!(
338 value.try_abs(),
339 Err(AbsComplexErrors::<Complex<f64>>::Input { .. })
340 ));
341
342 let value = Complex::new(0.0, f64::NAN);
343 assert!(matches!(
344 value.try_abs(),
345 Err(AbsComplexErrors::<Complex<f64>>::Input { .. })
346 ));
347 }
348
349 #[test]
350 fn abs_infinity() {
351 let value = Complex::new(f64::INFINITY, 0.0);
352 assert!(matches!(
353 value.try_abs(),
354 Err(AbsComplexErrors::<Complex<f64>>::Input { .. })
355 ));
356
357 let value = Complex::new(0.0, f64::INFINITY);
358 assert!(matches!(
359 value.try_abs(),
360 Err(AbsComplexErrors::<Complex<f64>>::Input { .. })
361 ));
362 }
363
364 #[test]
365 fn abs_subnormal() {
366 let value = Complex::new(f64::MIN_POSITIVE / 2.0, 0.0);
367 assert!(matches!(
368 value.try_abs(),
369 Err(AbsComplexErrors::<Complex<f64>>::Input { .. })
370 ));
371
372 let value = Complex::new(0.0, f64::MIN_POSITIVE / 2.0);
373 assert!(matches!(
374 value.try_abs(),
375 Err(AbsComplexErrors::<Complex<f64>>::Input { .. })
376 ));
377 }
378 }
379 }
380 }
381}
382//------------------------------------------------------------------------------------------------