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