Skip to main content

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    RealScalar,
10    core::traits::{raw::RawScalarValidate, validation::sealed::NativeReal},
11    functions::FunctionErrors,
12    kernels::{RawComplexTrait, RawScalarTrait},
13    scalars::NonNegativeRealScalar,
14};
15use duplicate::duplicate_item;
16use num::Complex;
17use num_traits::Float;
18use thiserror::Error;
19
20#[cfg(debug_assertions)]
21use try_create::TryNew;
22
23//------------------------------------------------------------------------------------------------
24/// Errors that can occur during the input validation phase when attempting to compute
25/// the absolute value of a number.
26///
27/// This enum is used as a source for the `Input` variant of composed error types like
28/// [`AbsRealErrors`] or [`AbsComplexErrors`]. It is generic over `RawScalar`, which
29/// represents the specific scalar type being validated (e.g., [`f64`], [`Complex<f64>`], etc.).
30#[derive(Debug, Error)]
31pub enum AbsInputErrors<RawScalar: RawScalarTrait> {
32    /// The input value failed basic validation checks.
33    ///
34    /// This error occurs if the input value itself is considered invalid
35    /// according to the validation policy (e.g., [`StrictFinitePolicy`](crate::core::policies::StrictFinitePolicy)),
36    /// such as being NaN or Infinity, before the absolute value calculation
37    /// is attempted.
38    #[error("the input value is invalid!")]
39    ValidationError {
40        /// The underlying validation error from the input type.
41        ///
42        /// This provides more specific details about why the input value
43        /// was considered invalid.
44        #[source]
45        #[backtrace]
46        source: <RawScalar as RawScalarValidate>::ValidationError,
47    },
48}
49
50/// A type alias for [`FunctionErrors`], specialized for errors that can occur during
51/// the computation of the absolute value of a real number.
52///
53/// It combines input validation errors specific to the `abs` operation on real numbers
54/// with potential validation errors of the resulting real number.
55///
56/// # Generic Parameters
57///
58/// - `RawReal`: A type that implements [`RawRealTrait`](crate::kernels::RawRealTrait). This defines:
59///   - The error type for the input: `AbsInputErrors<RawReal>`.
60///   - The raw error type for the output: `<RawReal as RawScalarValidate>::ValidationError`.
61///
62/// # Variants
63///
64/// This type alias wraps [`FunctionErrors`], which has the following variants:
65/// - `Input { source: AbsInputErrors<RawReal> }`: Indicates that the input real number
66///   provided for absolute value computation was invalid. The `source` field contains
67///   an [`AbsInputErrors`] detailing the specific input failure.
68/// - `Output { source: <RawReal as RawScalarValidate>::ValidationError }`: Indicates that the computed absolute value (a real number)
69///   itself failed validation. This typically means the result of the `abs` operation yielded
70///   a non-finite value (NaN or Infinity). The `source` field contains the raw validation error
71///   for the output.
72pub type AbsRealErrors<RawReal> =
73    FunctionErrors<AbsInputErrors<RawReal>, <RawReal as RawScalarValidate>::ValidationError>;
74
75/// A type alias for [`FunctionErrors`], specialized for errors that can occur during
76/// the computation of the absolute value (or modulus) of a complex number.
77///
78/// It combines input validation errors specific to the `abs` operation on complex numbers
79/// with potential validation errors of the resulting real number (the modulus).
80///
81/// # Generic Parameters
82///
83/// - `RawComplex`: A type that implements [`RawComplexTrait`]. This defines:
84///   - The error type for the input complex number: `AbsInputErrors<RawComplex>`.
85///   - The raw error type for the output real number: `<<RawComplex as RawComplexTrait>::RawReal as RawScalarValidate>::ValidationError`.
86///
87/// # Variants
88///
89/// This type alias wraps [`FunctionErrors`], which has the following variants:
90/// - `Input { source: AbsInputErrors<RawComplex> }`: Indicates that the input complex number
91///   provided for absolute value computation was invalid. The `source` field contains
92///   an [`AbsInputErrors`] detailing the specific input failure.
93/// - `Output { source: <<RawComplex as RawComplexTrait>::RawReal as RawScalarValidate>::ValidationError }`: Indicates that the computed
94///   absolute value (a real number) itself failed validation. This typically means the result of the
95///   `norm()` operation yielded a non-finite value. The `source` field contains the raw validation
96///   error for the output real number.
97pub type AbsComplexErrors<RawComplex> = FunctionErrors<
98    AbsInputErrors<RawComplex>,
99    <<RawComplex as RawComplexTrait>::RawReal as RawScalarValidate>::ValidationError,
100>;
101
102/// This trait provides the interface for the function used to compute the *absolute value*
103/// (also known as modulus or magnitude) of a number, which can be real or complex.
104///
105/// The absolute value of a real number `x` is `|x|`, which is `x` if `x` is non-negative,
106/// and `-x` if `x` is negative.
107/// The absolute value (or modulus) of a complex number `z = a + b*i` is `sqrt(a^2 + b^2)`.
108/// In both cases, the result is always a non-negative real number.
109///
110/// This trait provides four methods:
111/// - [`try_abs`](Abs::try_abs): A fallible version that performs validation on the input
112///   (and potentially the output) and returns a [`Result`]. This is the preferred method
113///   when robust error handling is required.
114/// - [`abs`](Abs::abs): A convenient infallible version that panics if `try_abs` would return
115///   an error in debug builds, or directly computes the value in release builds.
116///   Use this when the input is known to be valid or when panicking on error is acceptable.
117/// - [`try_abs_squared`](Abs::try_abs_squared): A fallible version that computes the squared
118///   absolute value (avoiding the square root). For complex numbers, this is `a^2 + b^2`.
119/// - [`abs_squared`](Abs::abs_squared): A convenient infallible version of `try_abs_squared`.
120///
121/// # Associated types
122///
123/// - `Output`: The type of the result of the absolute value operation. This is constrained
124///   by [`RealScalar`] to ensure it's a real number type.
125/// - `Error` : The error type that can be returned by the [`try_abs`](Abs::try_abs) method.
126pub trait Abs: Sized {
127    /// The output type of the *absolute value* function.
128    ///
129    /// This is always a real number type, even when the input `Self` is a complex number.
130    /// It must implement the [`RealScalar`] trait.
131    ///
132    type Output: Sized;
133
134    /// The error type that can be returned by the [`try_abs`](Abs::try_abs) method.
135    ///
136    /// This type must implement [`std::error::Error`]. Specific implementations will
137    /// use types like [`AbsRealErrors`] or [`AbsComplexErrors`] to provide detailed
138    /// information about the failure.
139    type Error: std::error::Error;
140
141    /// Attempts to compute the absolute value of `self`.
142    #[must_use = "this `Result` may contain an error that should be handled"]
143    fn try_abs(self) -> Result<Self::Output, Self::Error>;
144
145    /// Returns the *absolute value* of `self`.
146    fn abs(self) -> Self::Output;
147
148    /// Attempts to compute the squared absolute value of `self`.
149    ///
150    /// For real numbers, this computes `x^2`.
151    /// For complex numbers `z = a + b*i`, this computes `a^2 + b^2` (equivalent to `|z|^2`).
152    ///
153    /// This method is useful when you need the squared magnitude without the expense
154    /// of computing the square root, such as for distance comparisons or vector norms.
155    #[must_use = "this `Result` may contain an error that should be handled"]
156    fn try_abs_squared(self) -> Result<Self::Output, Self::Error>;
157
158    /// Returns the *squared absolute value* of `self`.
159    ///
160    /// For real numbers, this computes `x^2`.
161    /// For complex numbers `z = a + b*i`, this computes `a^2 + b^2`.
162    ///
163    /// # Behavior
164    ///
165    /// - In **debug builds** (`#[cfg(debug_assertions)]`): This method calls
166    ///   [`try_abs_squared()`](Abs::try_abs_squared) and unwraps the result. It will panic
167    ///   if `try_abs_squared` returns an error.
168    /// - In **release builds** (`#[cfg(not(debug_assertions))]`): This method directly
169    ///   computes the squared value for performance, bypassing the validations.
170    ///
171    /// # Panics
172    ///
173    /// This method will panic in debug builds if `try_abs_squared()` would return an `Err`.
174    fn abs_squared(self) -> Self::Output;
175}
176
177#[duplicate_item(
178    T;
179    [f64];
180    [f32];
181)]
182impl Abs for T
183//impl<T> Abs for T
184//where
185//    T: NativeReal<ValidationError = ErrorsValidationRawReal<Self>> + RealScalar<RawReal = Self>,
186{
187    type Output = NonNegativeRealScalar<T>;
188    type Error = AbsRealErrors<T>;
189
190    #[inline(always)]
191    fn try_abs(self) -> Result<NonNegativeRealScalar<T>, Self::Error> {
192        #[cfg(debug_assertions)]
193        {
194            self.validate_raw_scalar()
195                .map_err(|e| AbsInputErrors::ValidationError { source: e }.into())
196                .and_then(|v| {
197                    <T as Float>::abs(v)
198                        .validate_raw_scalar()
199                        .map(|abs_v| NonNegativeRealScalar::try_new(abs_v).unwrap())
200                        .map_err(|e| AbsRealErrors::Output { source: e })
201                })
202        }
203        #[cfg(not(debug_assertions))]
204        {
205            Ok(NonNegativeRealScalar::new(<T as Float>::abs(self)))
206        }
207    }
208
209    #[inline(always)]
210    fn abs(self) -> NonNegativeRealScalar<T> {
211        #[cfg(debug_assertions)]
212        {
213            self.try_abs()
214                .expect("Error in the computation of the absolute value in debug mode")
215        }
216        #[cfg(not(debug_assertions))]
217        {
218            NonNegativeRealScalar::new(<T as Float>::abs(self))
219        }
220    }
221
222    #[inline(always)]
223    fn try_abs_squared(self) -> Result<NonNegativeRealScalar<T>, Self::Error> {
224        #[cfg(debug_assertions)]
225        {
226            self.validate_raw_scalar()
227                .map_err(|e| AbsInputErrors::ValidationError { source: e }.into())
228                .and_then(|v| {
229                    let abs_v = <T as Float>::abs(v);
230                    (abs_v * abs_v)
231                        .validate_raw_scalar()
232                        .map(|abs_sq| NonNegativeRealScalar::try_new(abs_sq).unwrap())
233                        .map_err(|e| AbsRealErrors::Output { source: e })
234                })
235        }
236        #[cfg(not(debug_assertions))]
237        {
238            let abs_v = <T as Float>::abs(self);
239            Ok(NonNegativeRealScalar::new(abs_v * abs_v))
240        }
241    }
242
243    #[inline(always)]
244    fn abs_squared(self) -> NonNegativeRealScalar<T> {
245        #[cfg(debug_assertions)]
246        {
247            self.try_abs_squared()
248                .expect("Error in the computation of the squared absolute value in debug mode")
249        }
250        #[cfg(not(debug_assertions))]
251        {
252            let abs_v = <T as Float>::abs(self);
253            NonNegativeRealScalar::new(abs_v * abs_v)
254        }
255    }
256}
257
258impl<T> Abs for Complex<T>
259where
260    Self: RawComplexTrait<RawReal = T>,
261    T: NativeReal + RealScalar,
262{
263    type Output = NonNegativeRealScalar<T>;
264    type Error = AbsComplexErrors<Complex<T>>;
265
266    #[inline(always)]
267    fn try_abs(self) -> Result<NonNegativeRealScalar<T>, AbsComplexErrors<Complex<T>>> {
268        #[cfg(debug_assertions)]
269        {
270            self.validate_raw_scalar()
271                .map_err(|e| AbsInputErrors::ValidationError { source: e }.into())
272                .and_then(|v| {
273                    v.norm()
274                        .validate_raw_scalar()
275                        .map(|abs_v| NonNegativeRealScalar::try_new(abs_v).unwrap())
276                        .map_err(|e| AbsComplexErrors::<Complex<T>>::Output { source: e })
277                })
278        }
279        #[cfg(not(debug_assertions))]
280        {
281            Ok(NonNegativeRealScalar::new(self.norm()))
282        }
283    }
284
285    #[inline(always)]
286    fn abs(self) -> NonNegativeRealScalar<T> {
287        #[cfg(debug_assertions)]
288        {
289            self.try_abs()
290                .expect("Error in the computation of the absolute value in debug mode")
291        }
292        #[cfg(not(debug_assertions))]
293        {
294            NonNegativeRealScalar::new(self.norm())
295        }
296    }
297
298    #[inline(always)]
299    fn try_abs_squared(self) -> Result<NonNegativeRealScalar<T>, AbsComplexErrors<Complex<T>>> {
300        #[cfg(debug_assertions)]
301        {
302            self.validate_raw_scalar()
303                .map_err(|e| AbsInputErrors::ValidationError { source: e }.into())
304                .and_then(|v| {
305                    let norm_sq = v.norm_sqr();
306                    norm_sq
307                        .validate_raw_scalar()
308                        .map(|abs_sq| NonNegativeRealScalar::try_new(abs_sq).unwrap())
309                        .map_err(|e| AbsComplexErrors::<Complex<T>>::Output { source: e })
310                })
311        }
312        #[cfg(not(debug_assertions))]
313        {
314            Ok(NonNegativeRealScalar::new(self.norm_sqr()))
315        }
316    }
317
318    #[inline(always)]
319    fn abs_squared(self) -> NonNegativeRealScalar<T> {
320        #[cfg(debug_assertions)]
321        {
322            self.try_abs_squared()
323                .expect("Error in the computation of the squared absolute value in debug mode")
324        }
325        #[cfg(not(debug_assertions))]
326        {
327            NonNegativeRealScalar::new(self.norm_sqr())
328        }
329    }
330}
331
332//------------------------------------------------------------------------------------------------
333
334//------------------------------------------------------------------------------------------------
335#[cfg(test)]
336mod tests {
337    use super::*;
338    use num::Complex;
339    use num::Zero;
340
341    #[cfg(debug_assertions)]
342    use std::assert_matches;
343
344    mod abs {
345        use super::*;
346
347        mod native {
348            use super::*;
349
350            mod real {
351                use super::*;
352
353                #[test]
354                fn abs_valid() {
355                    let value = 4.0;
356                    assert_eq!(<f64 as Abs>::try_abs(value).unwrap().into_inner(), 4.0);
357                    assert_eq!(<f64 as Float>::abs(value), 4.0);
358                    assert_eq!(<f64 as Abs>::abs(value).into_inner(), 4.0);
359                }
360
361                #[test]
362                fn abs_negative() {
363                    let value = -4.0;
364                    assert_eq!(<f64 as Abs>::try_abs(value).unwrap().into_inner(), 4.0);
365                    assert_eq!(<f64 as Float>::abs(value), 4.0);
366                    assert_eq!(<f64 as Abs>::abs(value).into_inner(), 4.0);
367                }
368
369                #[test]
370                fn abs_zero() {
371                    let value = 0.0;
372                    assert_eq!(<f64 as Abs>::try_abs(value).unwrap().into_inner(), 0.0);
373                    assert_eq!(<f64 as Float>::abs(value), 0.0);
374                    assert_eq!(<f64 as Abs>::abs(value).into_inner(), 0.0);
375                }
376
377                #[test]
378                #[cfg(debug_assertions)]
379                fn abs_nan() {
380                    let value = f64::NAN;
381                    let result = value.try_abs().unwrap_err();
382                    assert_matches!(result, AbsRealErrors::Input(..));
383                }
384
385                #[test]
386                #[cfg(debug_assertions)]
387                fn abs_infinity() {
388                    let value = f64::INFINITY;
389                    assert_matches!(value.try_abs().unwrap_err(), AbsRealErrors::Input(..));
390                }
391
392                #[test]
393                #[cfg(debug_assertions)]
394                fn abs_subnormal() {
395                    let value = f64::MIN_POSITIVE / 2.0;
396                    assert_matches!(value.try_abs().unwrap_err(), AbsRealErrors::Input(..));
397                }
398
399                #[test]
400                fn abs_squared_positive() {
401                    let value = 4.0;
402                    assert_eq!(
403                        <f64 as Abs>::try_abs_squared(value).unwrap().into_inner(),
404                        16.0
405                    );
406                    assert_eq!(value.abs_squared().into_inner(), 16.0);
407                }
408
409                #[test]
410                fn abs_squared_negative() {
411                    let value = -4.0;
412                    assert_eq!(
413                        <f64 as Abs>::try_abs_squared(value).unwrap().into_inner(),
414                        16.0
415                    );
416                    assert_eq!(value.abs_squared().into_inner(), 16.0);
417                }
418
419                #[test]
420                fn abs_squared_zero() {
421                    let value = 0.0;
422                    assert_eq!(
423                        <f64 as Abs>::try_abs_squared(value).unwrap().into_inner(),
424                        0.0
425                    );
426                    assert_eq!(value.abs_squared().into_inner(), 0.0);
427                }
428
429                #[test]
430                #[cfg(debug_assertions)]
431                fn abs_squared_nan() {
432                    let value = f64::NAN;
433                    let result = value.try_abs_squared().unwrap_err();
434                    assert_matches!(result, AbsRealErrors::Input(..));
435                }
436
437                #[test]
438                #[cfg(debug_assertions)]
439                fn abs_squared_infinity() {
440                    let value = f64::INFINITY;
441                    assert_matches!(
442                        value.try_abs_squared().unwrap_err(),
443                        AbsRealErrors::Input(..)
444                    );
445                }
446            }
447
448            mod complex {
449                use super::*;
450
451                #[test]
452                fn abs_valid() {
453                    let value = Complex::new(4.0, 0.0);
454                    assert_eq!(value.try_abs().unwrap().into_inner(), 4.0);
455                    assert_eq!(value.abs().into_inner(), 4.0);
456                }
457
458                #[test]
459                fn abs_negative() {
460                    let value = Complex::new(-4.0, 0.0);
461                    assert_eq!(value.try_abs().unwrap().into_inner(), 4.0);
462                    assert_eq!(value.abs().into_inner(), 4.0);
463                }
464
465                #[test]
466                fn abs_zero() {
467                    let value = Complex::new(0.0, 0.0);
468                    assert_eq!(value.try_abs().unwrap(), NonNegativeRealScalar::zero());
469                    assert_eq!(value.abs(), NonNegativeRealScalar::zero());
470                }
471
472                #[test]
473                #[cfg(debug_assertions)]
474                fn abs_nan() {
475                    let value = Complex::new(f64::NAN, 0.0);
476                    assert!(matches!(
477                        value.try_abs(),
478                        Err(AbsComplexErrors::<Complex<f64>>::Input(..))
479                    ));
480
481                    let value = Complex::new(0.0, f64::NAN);
482                    assert!(matches!(
483                        value.try_abs(),
484                        Err(AbsComplexErrors::<Complex<f64>>::Input(..))
485                    ));
486                }
487
488                #[test]
489                #[cfg(debug_assertions)]
490                fn abs_infinity() {
491                    let value = Complex::new(f64::INFINITY, 0.0);
492                    assert!(matches!(
493                        value.try_abs(),
494                        Err(AbsComplexErrors::<Complex<f64>>::Input(..))
495                    ));
496
497                    let value = Complex::new(0.0, f64::INFINITY);
498                    assert!(matches!(
499                        value.try_abs(),
500                        Err(AbsComplexErrors::<Complex<f64>>::Input(..))
501                    ));
502                }
503
504                #[test]
505                #[cfg(debug_assertions)]
506                fn abs_subnormal() {
507                    let value = Complex::new(f64::MIN_POSITIVE / 2.0, 0.0);
508                    assert!(matches!(
509                        value.try_abs(),
510                        Err(AbsComplexErrors::<Complex<f64>>::Input(..))
511                    ));
512
513                    let value = Complex::new(0.0, f64::MIN_POSITIVE / 2.0);
514                    assert!(matches!(
515                        value.try_abs(),
516                        Err(AbsComplexErrors::<Complex<f64>>::Input(..))
517                    ));
518                }
519
520                #[test]
521                fn abs_squared_valid() {
522                    let value = Complex::new(3.0, 4.0);
523                    assert_eq!(value.try_abs_squared().unwrap().into_inner(), 25.0);
524                    assert_eq!(value.abs_squared().into_inner(), 25.0);
525                }
526
527                #[test]
528                fn abs_squared_zero() {
529                    let value = Complex::new(0.0, 0.0);
530                    assert_eq!(
531                        value.try_abs_squared().unwrap(),
532                        NonNegativeRealScalar::zero()
533                    );
534                    assert_eq!(value.abs_squared(), NonNegativeRealScalar::zero());
535                }
536
537                #[test]
538                #[cfg(debug_assertions)]
539                fn abs_squared_nan() {
540                    let value = Complex::new(f64::NAN, 0.0);
541                    assert!(matches!(
542                        value.try_abs_squared(),
543                        Err(AbsComplexErrors::<Complex<f64>>::Input(..))
544                    ));
545
546                    let value = Complex::new(0.0, f64::NAN);
547                    assert!(matches!(
548                        value.try_abs_squared(),
549                        Err(AbsComplexErrors::<Complex<f64>>::Input(..))
550                    ));
551                }
552
553                #[test]
554                #[cfg(debug_assertions)]
555                fn abs_squared_infinity() {
556                    let value = Complex::new(f64::INFINITY, 0.0);
557                    assert!(matches!(
558                        value.try_abs_squared(),
559                        Err(AbsComplexErrors::<Complex<f64>>::Input(..))
560                    ));
561
562                    let value = Complex::new(0.0, f64::INFINITY);
563                    assert!(matches!(
564                        value.try_abs_squared(),
565                        Err(AbsComplexErrors::<Complex<f64>>::Input(..))
566                    ));
567                }
568            }
569        }
570    }
571}
572//------------------------------------------------------------------------------------------------