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//------------------------------------------------------------------------------------------------