num_valid/functions/
abs.rs

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