num_valid/functions/
complex.rs

1#![deny(rustdoc::broken_intra_doc_links)]
2
3use crate::{
4    FpScalar, RealScalar,
5    functions::FunctionErrors,
6    kernels::RawComplexTrait,
7    kernels::{RawRealTrait, RawScalarTrait},
8    validation::StrictFinitePolicy,
9};
10use num::{Complex, Zero};
11use std::backtrace::Backtrace;
12use thiserror::Error;
13use try_create::{IntoInner, ValidationPolicy};
14
15/// Trait for constructing complex scalar types from their raw components.
16pub trait ComplexScalarConstructors: FpScalar<InnerType = Self::RawComplex> {
17    /// The raw underlying complex type (e.g., `num::Complex<f64>` or `rug::Complex`).
18    type RawComplex: RawComplexTrait<RawReal = <Self::RealType as RealScalar>::RawReal>;
19
20    /// Tries to create a new complex scalar from its raw real and imaginary parts.
21    fn try_new_complex(
22        real: <Self::RawComplex as RawComplexTrait>::RawReal,
23        imag: <Self::RawComplex as RawComplexTrait>::RawReal,
24    ) -> Result<Self, <Self::RawComplex as RawScalarTrait>::ValidationErrors>;
25
26    /// Creates a new complex scalar from its (validated) real and imaginary parts.
27    fn new_complex(real: Self::RealType, imag: Self::RealType) -> Self {
28        Self::try_new_complex(real.into_inner(), imag.into_inner()).unwrap()
29    }
30
31    /// Tries to create a new complex scalar from a raw real part, with a zero imaginary part.
32    fn try_new_pure_real(
33        real_part: <Self::RawComplex as RawComplexTrait>::RawReal,
34    ) -> Result<Self, <Self::RawComplex as RawScalarTrait>::ValidationErrors> {
35        let zero = <Self::RawComplex as RawComplexTrait>::RawReal::raw_zero(real_part.precision());
36        Self::try_new_complex(real_part, zero)
37    }
38
39    /// Tries to create a new complex scalar from a raw imaginary part, with a zero real part.
40    fn try_new_pure_imaginary(
41        imag_part: <Self::RawComplex as RawComplexTrait>::RawReal,
42    ) -> Result<Self, <Self::RawComplex as RawScalarTrait>::ValidationErrors> {
43        let zero = <Self::RawComplex as RawComplexTrait>::RawReal::raw_zero(imag_part.precision());
44        Self::try_new_complex(zero, imag_part)
45    }
46
47    fn new_pure_real(real_part: Self::RealType) -> Self {
48        Self::try_new_pure_real(real_part.into_inner()).unwrap()
49    }
50
51    fn new_pure_imaginary(imag_part: Self::RealType) -> Self {
52        Self::try_new_pure_imaginary(imag_part.into_inner()).unwrap()
53    }
54}
55
56/// Trait for accessing the real and imaginary components of a complex scalar.
57pub trait ComplexScalarGetParts: ComplexScalarConstructors {
58    /// Get the real part of the complex number.
59    fn real_part(&self) -> Self::RealType;
60
61    /// Get the imaginary part of the complex number.
62    fn imag_part(&self) -> Self::RealType;
63
64    /// Returns a reference to the raw real part of the complex number.
65    fn raw_real_part(&self) -> &<Self::RealType as RealScalar>::RawReal;
66
67    /// Returns a reference to the raw imaginary part of the complex number.
68    fn raw_imag_part(&self) -> &<Self::RealType as RealScalar>::RawReal;
69
70    /// Returns `true` if the complex number is purely real (i.e., has no imaginary part).
71    ///
72    /// Note: the complex number zero is considered purely real.
73    fn is_pure_real(&self) -> bool {
74        self.raw_imag_part().is_zero()
75    }
76
77    /// Returns `true` if the complex number is purely imaginary (i.e., has zero real part and non-zero imaginary part).
78    ///
79    /// Note: the complex number zero is considered purely real.
80    fn is_pure_imaginary(&self) -> bool {
81        self.raw_real_part().is_zero() && !self.raw_imag_part().is_zero()
82    }
83}
84
85/// Trait for setting the real and imaginary components of a complex scalar.
86pub trait ComplexScalarSetParts: ComplexScalarGetParts {
87    /// Set the real part of the complex number.
88    fn set_real_part(&mut self, real_part: Self::RealType);
89
90    /// Set the imaginary part of the complex number.
91    fn set_imaginary_part(&mut self, imag_part: Self::RealType);
92
93    /// Returns a new complex number with the real part set to the given value and the imaginary part from `self`.
94    fn with_real_part(mut self, real_part: Self::RealType) -> Self {
95        self.set_real_part(real_part);
96        self
97    }
98
99    /// Returns a new complex number with the imaginary part set to the given value and the real part from `self`.
100    fn with_imaginary_part(mut self, imag_part: Self::RealType) -> Self {
101        self.set_imaginary_part(imag_part);
102        self
103    }
104}
105
106/// Provides methods for in-place mutation of the components of a complex scalar.
107///
108/// This trait offers efficient, in-place operations to modify the real and imaginary
109/// parts of a complex number individually using a real scalar value. These methods
110/// are particularly useful in numerical algorithms that require adjusting specific
111/// components of a complex vector or matrix without allocating new complex numbers.
112///
113/// # Examples
114///
115/// ```
116/// use num_valid::{
117///     ComplexNative64StrictFinite, RealNative64StrictFinite,
118///     functions::{ComplexScalarGetParts, ComplexScalarMutateParts, ComplexScalarConstructors},
119/// };
120/// use try_create::TryNew;
121///
122/// let mut c = ComplexNative64StrictFinite::try_new_complex(3.0, 5.0).unwrap();
123/// let real_addend = RealNative64StrictFinite::try_new(2.0).unwrap();
124/// let real_multiplier = RealNative64StrictFinite::try_new(10.0).unwrap();
125///
126/// // Add 2.0 to the real part
127/// c.add_to_real_part(&real_addend);
128/// assert_eq!(c.real_part(), 5.0);
129///
130/// // Multiply the imaginary part by 10.0
131/// c.multiply_imaginary_part(&real_multiplier);
132/// assert_eq!(c.imag_part(), 50.0);
133///
134/// assert_eq!(c, ComplexNative64StrictFinite::try_new_complex(5.0, 50.0).unwrap());
135/// ```
136pub trait ComplexScalarMutateParts: ComplexScalarSetParts {
137    /// Add the value of the the real coefficient `c` to real part of `self`.
138    fn add_to_real_part(&mut self, c: &Self::RealType);
139
140    /// Add the value of the the real coefficient `c` to imaginary part of `self`.
141    fn add_to_imaginary_part(&mut self, c: &Self::RealType);
142
143    /// Multiply the value of the real part by the real coefficient `c`.
144    fn multiply_real_part(&mut self, c: &Self::RealType);
145
146    /// Multiply the value of the imaginary part by the real coefficient `c`.
147    fn multiply_imaginary_part(&mut self, c: &Self::RealType);
148}
149
150//--------------------------------------------------------------------------------------------------
151/// Trait for computing the complex conjugate of a number.
152///
153/// The complex conjugate of a complex number is obtained by changing the sign of its imaginary part.
154///
155/// # Example
156///
157/// ```rust
158/// use num_valid::functions::Conjugate;
159/// use num::Complex;
160/// use try_create::TryNew;
161///
162/// // Example with Complex<f64>
163/// let z = Complex::new(1.0, -2.0);
164/// let z_conjugate = z.conjugate();
165/// println!("Conjugate: {}", z_conjugate); // Output: Conjugate: 1+2i
166/// assert_eq!(z_conjugate, Complex::new(1.0, 2.0));
167///
168/// // Example with ComplexRugStrictFinite<53> (when the `rug` feature is enabled)
169/// #[cfg(feature = "rug")]
170/// {
171///     use rug::Float;
172///     use num_valid::ComplexRugStrictFinite;
173///
174///     const PRECISION: u32 = 53;
175///
176///     let z = ComplexRugStrictFinite::<PRECISION>::try_new(
177///         rug::Complex::with_val(PRECISION,
178///             (Float::with_val(PRECISION, 1.0),Float::with_val(PRECISION, -2.0)),
179///         )).unwrap();
180///     let z_conjugate = z.conjugate();
181///     println!("Conjugate: {}", z_conjugate);
182///     assert_eq!(
183///         z_conjugate,
184///         ComplexRugStrictFinite::<PRECISION>::try_new(
185///         rug::Complex::with_val(PRECISION,
186///             (Float::with_val(PRECISION, 1.0),Float::with_val(PRECISION, 2.0)),
187///         )).unwrap()
188///     );
189/// }
190/// ```
191pub trait Conjugate: Sized {
192    /// Returns the *complex conjugate* of `self`.
193    fn conjugate(self) -> Self;
194}
195
196impl Conjugate for Complex<f64> {
197    #[inline(always)]
198    fn conjugate(self) -> Self {
199        self.conj()
200    }
201}
202
203//--------------------------------------------------------------------------------------------------
204
205//--------------------------------------------------------------------------------------------------
206/// Errors that can occur specifically during the input validation phase or due to
207/// special input values when attempting to compute the argument (principal value)
208/// of a complex number.
209///
210/// This enum is used as a source for the [`ArgErrors::Input`] variant and helps categorize
211/// why an input was deemed unsuitable *before* or *during* the core argument calculation.
212///
213/// # Generic Parameters
214///
215/// - `RawComplex`: A type that implements [`RawComplexTrait`].
216///   This defines the raw error type for the input complex number via `<RawComplex as RawScalarTrait>::ValidationErrors`.
217#[derive(Debug, Error)]
218pub enum ArgInputErrors<RawComplex: RawComplexTrait> {
219    /// The input complex number failed basic validation checks.
220    ///
221    /// This error occurs if the input complex number itself is considered invalid
222    /// according to the validation policy (e.g., [`StrictFinitePolicy`]),
223    /// such as containing NaN or Infinity components, before the argument calculation
224    /// is attempted.
225    #[error("the input complex number is invalid according to validation policy")]
226    ValidationError {
227        /// The underlying validation error from the complex number type.
228        ///
229        /// This provides more specific details about why the complex number's
230        /// components (real or imaginary parts) were considered invalid.
231        #[source]
232        #[backtrace]
233        source: <RawComplex as RawScalarTrait>::ValidationErrors,
234    },
235
236    /// The input complex number is zero.
237    ///
238    /// The argument of zero is undefined. This error indicates that the input
239    /// value was `0 + 0i`.
240    #[error("the input value is zero!")]
241    Zero {
242        /// A captured backtrace for debugging purposes.
243        backtrace: Backtrace,
244    },
245}
246
247/// A type alias for [`FunctionErrors`], specialized for errors that can occur during
248/// the computation of the argument (principal value) of a complex number.
249///
250/// This type represents the possible failures when calling [`Arg::try_arg()`].
251///
252/// # Generic Parameters
253///
254/// - `RawComplex`: A type that implements [`RawComplexTrait`]. This defines:
255///   - The raw error type for the input complex number via `<RawComplex as RawScalarTrait>::ValidationErrors`.
256///   - The raw error type for the output real number (the argument) via
257///     `<<RawComplex as RawComplexTrait>::RawReal as RawScalarTrait>::ValidationErrors`.
258///
259/// # Variants
260///
261/// This type alias wraps [`FunctionErrors`], which has the following variants in this context:
262///
263/// - `Input { source: ArgInputErrors<RawComplex> }`:
264///   Indicates that the input complex number was invalid for argument computation.
265///   This could be due to failing initial validation (e.g., containing NaN or Infinity)
266///   or because the input was zero (for which the argument is undefined).
267///   The `source` field provides more specific details via [`ArgInputErrors`].
268///
269/// - `Output { source: <<RawComplex as RawComplexTrait>::RawReal as RawScalarTrait>::ValidationErrors }`:
270///   Indicates that the computed argument (a real number) failed validation.
271///   This typically means the result of the `atan2` or equivalent operation yielded
272///   a non-finite value (NaN or Infinity), which is unexpected if the input was valid
273///   and non-zero. The `source` field provides the raw validation error for the output real number.
274pub type ArgErrors<RawComplex> = FunctionErrors<
275    ArgInputErrors<RawComplex>,
276    <<RawComplex as RawComplexTrait>::RawReal as RawScalarTrait>::ValidationErrors,
277>;
278
279/// Trait for computing the argument (principal value) of a complex number.
280///
281/// The argument of a complex number `z = x + iy` is the angle `φ` (phi)
282/// in polar coordinates `z = r(cos φ + i sin φ)`. It is typically computed
283/// using `atan2(y, x)` and lies in the interval `(-π, π]`.
284///
285/// This trait provides both a fallible version (`try_arg`) that performs validation
286/// and an infallible version (`arg`) that may panic in debug builds if validation fails.
287pub trait Arg: Sized {
288    /// The return type of the *principal value* (or *argument*) function. It is always a real number.
289    // TODO: Consider using the trait bound `Output: RealScalar` instead of `Output: Sized`.
290    type Output: Sized;
291
292    /// The error type that can be returned by the [`Arg::try_arg()`] method.
293    ///
294    /// This is typically an instantiation of [`ArgErrors`].
295    type Error: std::error::Error;
296
297    /// Attempts to compute the argument of `self`, returning a `Result`.
298    fn try_arg(self) -> Result<Self::Output, Self::Error>;
299
300    /// Returns the argument of `self`.
301    fn arg(self) -> Self::Output;
302}
303
304impl Arg for Complex<f64> {
305    type Output = f64;
306
307    type Error = ArgErrors<Complex<f64>>;
308
309    /// Attempts to compute the argument of `self`, returning a `Result`.
310    ///
311    /// This method first validates the input `self` using [`StrictFinitePolicy`] and
312    /// also checks if it is zero. If the input is valid (finite components, non-zero),
313    /// it computes the argument and then validates the resulting real number
314    /// using the same policy.
315    ///
316    /// # Returns
317    ///
318    /// - `Ok(Self::Output)`: If the input complex number is valid (finite, non-zero)
319    ///   and the computed argument is a valid (finite) real number.
320    /// - `Err(Self::Error)`: If the input is invalid (e.g., NaN or Infinity components, zero)
321    ///   or if the computed argument is invalid (e.g., NaN, Infinity).
322    ///
323    /// # Examples
324    ///
325    /// ```rust
326    /// use num_valid::functions::Arg;
327    /// use num::Complex;
328    /// use std::f64::consts::PI;
329    ///
330    /// let z = Complex::new(1.0, 1.0); // Represents 1 + i
331    /// match z.try_arg() {
332    ///     Ok(angle) => println!("Arg(1+i) = {}", angle), // Arg(1+i) = 0.785... (π/4)
333    ///     Err(e) => println!("Error: {:?}", e),
334    /// }
335    ///
336    /// let zero = Complex::new(0.0, 0.0);
337    /// assert!(zero.try_arg().is_err());
338    ///
339    /// let nan_val = Complex::new(f64::NAN, 1.0);
340    /// assert!(nan_val.try_arg().is_err());
341    /// ```
342    #[inline(always)]
343    fn try_arg(self) -> Result<Self::Output, Self::Error> {
344        StrictFinitePolicy::<Complex<f64>, 53>::validate(self)
345            .map_err(|e| Self::Error::Input {
346                source: ArgInputErrors::ValidationError { source: e },
347            })
348            .and_then(|v: Complex<f64>| {
349                if <Complex<f64> as Zero>::is_zero(&v) {
350                    Err(Self::Error::Input {
351                        source: ArgInputErrors::Zero {
352                            backtrace: Backtrace::force_capture(),
353                        },
354                    })
355                } else {
356                    StrictFinitePolicy::<f64, 53>::validate(Complex::<f64>::arg(v))
357                        .map_err(|e| Self::Error::Output { source: e })
358                }
359            })
360    }
361
362    /// Returns the argument of `self`.
363    ///
364    /// # Behavior
365    ///
366    /// - **Debug Builds (`#[cfg(debug_assertions)]`)**: This method internally calls `try_arg().unwrap()`.
367    ///   It will panic if the input `self` is invalid (e.g., NaN/Infinity components, zero)
368    ///   or if the computed argument is invalid.
369    /// - **Release Builds (`#[cfg(not(debug_assertions))]`)**: This method calls the underlying
370    ///   argument function directly (e.g., `num::Complex::arg`).
371    ///   The behavior for non-finite inputs or zero (like NaN propagation or specific
372    ///   return values like `0.0` for `arg(0)`) depends on the underlying implementation.
373    ///
374    /// # Panics
375    ///
376    /// In debug builds, this method will panic if `try_arg()` would return an `Err`.
377    ///
378    /// # Examples
379    ///
380    /// ```rust
381    /// use num_valid::functions::Arg;
382    /// use num::Complex;
383    /// use std::f64::consts::PI;
384    ///
385    /// let z = Complex::new(-1.0, 0.0); // Represents -1
386    /// println!("Arg(-1) = {}", z.arg()); // Arg(-1) = 3.141... (π)
387    ///
388    /// let z_imag = Complex::new(0.0, 1.0); // Represents i
389    /// println!("Arg(i) = {}", z_imag.arg()); // Arg(i) = 1.570... (π/2)
390    /// ```
391    #[inline(always)]
392    fn arg(self) -> f64 {
393        #[cfg(debug_assertions)]
394        {
395            self.try_arg()
396                .expect("Error calling Arg::try_arg() inside Arg::arg() debug mode.")
397        }
398        #[cfg(not(debug_assertions))]
399        {
400            Complex::<f64>::arg(self)
401        }
402    }
403}
404
405//--------------------------------------------------------------------------------------------------
406
407//--------------------------------------------------------------------------------------------------
408#[cfg(test)]
409mod tests {
410    use super::*;
411    use crate::validation::ErrorsValidationRawComplex;
412    use num::Complex;
413
414    mod native64 {
415        use super::*;
416        use std::f64::consts::*;
417
418        #[test]
419        fn conjugate() {
420            let z = Complex::new(1.0, -2.0);
421            let expected_conjugate = Complex::new(1.0, 2.0);
422            assert_eq!(z.conjugate(), expected_conjugate);
423
424            let z = Complex::new(-3.0, 4.0);
425            let expected_conjugate = Complex::new(-3.0, -4.0);
426            assert_eq!(z.conjugate(), expected_conjugate);
427
428            // Special cases
429            // Zero
430            let z_zero = Complex::new(0.0, 0.0);
431            assert_eq!(z_zero.conjugate(), Complex::new(0.0, 0.0));
432            // Purely real
433            let z_real = Complex::new(5.0, 0.0);
434            assert_eq!(z_real.conjugate(), Complex::new(5.0, 0.0));
435            // Purely imaginary
436            let z_imag = Complex::new(0.0, 5.0);
437            assert_eq!(z_imag.conjugate(), Complex::new(0.0, -5.0));
438            let z_neg_imag = Complex::new(0.0, -5.0);
439            assert_eq!(z_neg_imag.conjugate(), Complex::new(0.0, 5.0));
440        }
441
442        #[test]
443        fn arg() {
444            let z = Complex::new(1.0, 1.0);
445            let expected_arg = std::f64::consts::FRAC_PI_4; //0.7853981633974483; // pi/4
446            assert_eq!(z.arg(), expected_arg);
447
448            // Axes
449            let z_pos_real = Complex::new(1.0, 0.0);
450            assert_eq!(z_pos_real.arg(), 0.0);
451            let z_neg_real = Complex::new(-1.0, 0.0);
452            assert_eq!(z_neg_real.arg(), PI);
453            let z_pos_imag = Complex::new(0.0, 1.0);
454            assert_eq!(z_pos_imag.arg(), FRAC_PI_2);
455            let z_neg_imag = Complex::new(0.0, -1.0);
456            assert_eq!(z_neg_imag.arg(), -FRAC_PI_2);
457
458            // Quadrants
459            let z2 = Complex::new(-1.0, 1.0); // Q2
460            assert_eq!(z2.arg(), 3.0 * FRAC_PI_4);
461            let z3 = Complex::new(-1.0, -1.0); // Q3
462            assert_eq!(z3.arg(), -3.0 * FRAC_PI_4);
463            let z4 = Complex::new(1.0, -1.0); // Q4
464            assert_eq!(z4.arg(), -FRAC_PI_4);
465
466            let zero = Complex::new(0.0, 0.0);
467            assert!(matches!(
468                zero.try_arg(),
469                Err(ArgErrors::<Complex<f64>>::Input {
470                    source: ArgInputErrors::Zero { .. }
471                })
472            ));
473        }
474
475        #[test]
476        fn try_arg_invalid() {
477            // NaN cases
478            let z_nan_re = Complex::new(f64::NAN, 1.0);
479            assert!(matches!(
480                z_nan_re.try_arg(),
481                Err(ArgErrors::<Complex<f64>>::Input {
482                    source: ArgInputErrors::ValidationError {
483                        source: ErrorsValidationRawComplex::InvalidRealPart { .. }
484                    }
485                })
486            ));
487            let z_nan_im = Complex::new(1.0, f64::NAN);
488            assert!(matches!(
489                z_nan_im.try_arg(),
490                Err(ArgErrors::<Complex<f64>>::Input {
491                    source: ArgInputErrors::ValidationError {
492                        source: ErrorsValidationRawComplex::InvalidImaginaryPart { .. }
493                    }
494                })
495            ));
496
497            // Infinity cases
498            let z_inf_re = Complex::new(f64::INFINITY, 1.0);
499            assert!(matches!(
500                z_inf_re.try_arg(),
501                Err(ArgErrors::<Complex<f64>>::Input {
502                    source: ArgInputErrors::ValidationError {
503                        source: ErrorsValidationRawComplex::InvalidRealPart { .. }
504                    }
505                })
506            ));
507            let z_inf_im = Complex::new(1.0, f64::INFINITY);
508            assert!(matches!(
509                z_inf_im.try_arg(),
510                Err(ArgErrors::<Complex<f64>>::Input {
511                    source: ArgInputErrors::ValidationError {
512                        source: ErrorsValidationRawComplex::InvalidImaginaryPart { .. }
513                    }
514                })
515            ));
516
517            // Case where num_complex::Complex::arg might produce NaN, caught by output validation
518            // e.g. if inputs were valid but atan2 produced NaN (though hard with f64 if inputs are finite)
519            // This test assumes that if Complex::arg itself returns NaN, f64::try_new catches it.
520            // Example: Complex::new(f64::NEG_INFINITY, f64::NEG_INFINITY).arg() is -3*PI/4
521            // Example: Complex::new(f64::NAN, f64::NEG_INFINITY).arg() is NaN
522            // The validate() step should catch component-wise NaN/Inf first.
523            // If validate() passed but Complex::arg() produced NaN, it would be an Output error.
524            // For instance, if we had a type where components are valid but arg calculation leads to NaN.
525            // For Complex<f64>, validate() is quite comprehensive.
526            // Let's consider a case that passes validate() but whose arg() is NaN (hypothetical for f64, more for other types)
527            // For f64, if re or im is NaN, validate() fails. If both are Inf, validate() fails.
528            // If one is Inf and other is finite, arg() is well-defined.
529            // So, for Complex<f64>, the Output error due to NaN is less likely if validate() passes.
530        }
531
532        #[test]
533        #[cfg(debug_assertions)]
534        #[should_panic]
535        fn arg_panics_on_zero_debug() {
536            let zero = Complex::new(0.0, 0.0);
537            let _ = Arg::arg(zero);
538        }
539
540        #[test]
541        #[cfg(debug_assertions)]
542        #[should_panic]
543        fn arg_panics_on_nan_debug() {
544            let nan = Complex::new(f64::NAN, 1.0);
545            let _ = Arg::arg(nan);
546        }
547
548        #[test]
549        #[cfg(debug_assertions)]
550        #[should_panic]
551        fn arg_panics_on_inf_debug() {
552            let inf = Complex::new(f64::INFINITY, 1.0);
553            let _ = Arg::arg(inf);
554        }
555
556        #[test]
557        #[cfg(not(debug_assertions))]
558        fn arg_behavior_release() {
559            // Zero input in release returns 0.0 from num_complex::Complex::arg
560            let zero = Complex::new(0.0, 0.0);
561            assert_eq!(Arg::arg(zero), 0.0);
562
563            // NaN/Inf inputs in release mode will lead to NaN from num_complex::Complex::arg
564            let nan_re = Complex::new(f64::NAN, 1.0);
565            assert!(Arg::arg(nan_re).is_nan());
566            let inf_re = Complex::new(f64::INFINITY, 0.0); // arg is 0
567            assert_eq!(Arg::arg(inf_re), 0.0);
568            let inf_im = Complex::new(0.0, f64::INFINITY); // arg is PI/2
569            assert_eq!(Arg::arg(inf_im), FRAC_PI_2);
570            let inf_both = Complex::new(f64::INFINITY, f64::INFINITY); // arg is PI/4
571            assert_eq!(Arg::arg(inf_both), FRAC_PI_4);
572        }
573    }
574
575    #[cfg(feature = "rug")]
576    mod rug53 {
577        use super::*;
578        use crate::kernels::rug::{ComplexRugStrictFinite, RealRugStrictFinite};
579        use rug::Float;
580        use std::f64::consts::*;
581        use try_create::{New, TryNew};
582
583        const PRECISION: u32 = 53;
584
585        fn rug_complex(re: f64, im: f64) -> rug::Complex {
586            rug::Complex::with_val(
587                PRECISION,
588                (
589                    Float::with_val(PRECISION, re),
590                    Float::with_val(PRECISION, im),
591                ),
592            )
593        }
594
595        fn new_complex_rug(re: f64, im: f64) -> ComplexRugStrictFinite<PRECISION> {
596            ComplexRugStrictFinite::<PRECISION>::new(rug_complex(re, im))
597        }
598
599        #[allow(clippy::result_large_err)]
600        fn try_new_complex_rug(
601            re: f64,
602            im: f64,
603        ) -> Result<
604            ComplexRugStrictFinite<PRECISION>,
605            <rug::Complex as RawScalarTrait>::ValidationErrors,
606        > {
607            ComplexRugStrictFinite::<PRECISION>::try_new(rug_complex(re, im))
608        }
609
610        fn real_rug(val: f64) -> RealRugStrictFinite<PRECISION> {
611            RealRugStrictFinite::<PRECISION>::new(Float::with_val(PRECISION, val))
612        }
613
614        #[test]
615        fn conjugate() {
616            let z = new_complex_rug(1.0, -2.0);
617            let expected_conjugate = new_complex_rug(1.0, 2.0);
618            assert_eq!(z.conjugate(), expected_conjugate);
619
620            let z = new_complex_rug(-3.0, 4.0);
621            let expected_conjugate = new_complex_rug(-3.0, -4.0);
622            assert_eq!(z.conjugate(), expected_conjugate);
623
624            // Special cases
625            // Zero
626            let z_zero = new_complex_rug(0.0, 0.0);
627            assert_eq!(z_zero.conjugate(), new_complex_rug(0.0, 0.0));
628            // Purely real
629            let z_real = new_complex_rug(5.0, 0.0);
630            assert_eq!(z_real.conjugate(), new_complex_rug(5.0, 0.0));
631            // Purely imaginary
632            let z_imag = new_complex_rug(0.0, 5.0);
633            assert_eq!(z_imag.conjugate(), new_complex_rug(0.0, -5.0));
634            let z_neg_imag = new_complex_rug(0.0, -5.0);
635            assert_eq!(z_neg_imag.conjugate(), new_complex_rug(0.0, 5.0));
636        }
637
638        #[test]
639        fn arg() {
640            // Existing test (Q1)
641            let z1 = new_complex_rug(1.0, 1.0);
642            assert_eq!(z1.arg(), real_rug(FRAC_PI_4));
643
644            // Axes
645            let z_pos_real = new_complex_rug(1.0, 0.0);
646            assert_eq!(z_pos_real.arg(), real_rug(0.0));
647            let z_neg_real = new_complex_rug(-1.0, 0.0);
648            assert_eq!(z_neg_real.arg(), real_rug(PI));
649            let z_pos_imag = new_complex_rug(0.0, 1.0);
650            assert_eq!(z_pos_imag.arg(), real_rug(FRAC_PI_2));
651            let z_neg_imag = new_complex_rug(0.0, -1.0);
652            assert_eq!(z_neg_imag.arg(), real_rug(-FRAC_PI_2));
653
654            // Quadrants
655            let z2 = new_complex_rug(-1.0, 1.0); // Q2
656            assert_eq!(z2.arg(), real_rug(3.0 * FRAC_PI_4));
657            let z3 = new_complex_rug(-1.0, -1.0); // Q3
658            assert_eq!(z3.arg(), real_rug(-3.0 * FRAC_PI_4));
659            let z4 = new_complex_rug(1.0, -1.0); // Q4
660            assert_eq!(z4.arg(), real_rug(-FRAC_PI_4));
661
662            // Test try_arg for zero (already in existing tests)
663            let zero = new_complex_rug(0.0, 0.0);
664            assert!(matches!(
665                zero.try_arg(),
666                Err(ArgErrors::Input {
667                    source: ArgInputErrors::Zero { .. }
668                })
669            ));
670        }
671
672        #[test]
673        #[cfg(not(debug_assertions))]
674        fn try_arg_zero_invalid() {
675            // zero
676            let z_zero = new_complex_rug(0.0, 0.0);
677            assert!(matches!(
678                z_zero.try_arg(),
679                Err(ArgErrors::Input {
680                    source: ArgInputErrors::Zero { .. }
681                })
682            ));
683        }
684
685        #[test]
686        #[should_panic(
687            expected = "Error calling ComplexValidated::try_arg() inside ComplexValidated::arg()"
688        )]
689        fn arg_panics_on_zero() {
690            let zero = new_complex_rug(0.0, 0.0);
691            let _ = zero.arg();
692        }
693
694        #[test]
695        fn err_on_try_new_real_part_infinity_debug() {
696            let err = try_new_complex_rug(f64::INFINITY, 1.0);
697            assert!(matches!(
698                err,
699                Err(ErrorsValidationRawComplex::InvalidRealPart { .. })
700            ));
701        }
702
703        #[test]
704        fn err_on_try_new_real_part_nan_debug() {
705            let err = try_new_complex_rug(f64::NAN, 1.0);
706            assert!(matches!(
707                err,
708                Err(ErrorsValidationRawComplex::InvalidRealPart { .. })
709            ));
710        }
711
712        #[test]
713        fn err_on_try_new_imaginary_part_infinity_debug() {
714            let err = try_new_complex_rug(1.0, f64::INFINITY);
715            assert!(matches!(
716                err,
717                Err(ErrorsValidationRawComplex::InvalidImaginaryPart { .. })
718            ));
719        }
720
721        #[test]
722        fn err_on_try_new_imaginary_part_nan_debug() {
723            let err = try_new_complex_rug(1.0, f64::NAN);
724            assert!(matches!(
725                err,
726                Err(ErrorsValidationRawComplex::InvalidImaginaryPart { .. })
727            ));
728        }
729
730        #[test]
731        #[cfg(debug_assertions)]
732        #[should_panic(expected = "Error calling try_new_validated() inside new() in debug mode")]
733        fn panics_on_new_real_part_nan_debug() {
734            let _nan = new_complex_rug(f64::NAN, 1.0);
735        }
736
737        #[test]
738        #[cfg(debug_assertions)]
739        #[should_panic(expected = "Error calling try_new_validated() inside new() in debug mode")]
740        fn panics_on_new_real_part_infinity_debug() {
741            let _inf = new_complex_rug(f64::INFINITY, 1.0);
742        }
743
744        #[test]
745        #[cfg(debug_assertions)]
746        #[should_panic(expected = "Error calling try_new_validated() inside new() in debug mode")]
747        fn panics_on_new_imaginary_part_nan_debug() {
748            let _nan = new_complex_rug(1.0, f64::NAN);
749        }
750
751        #[test]
752        #[cfg(debug_assertions)]
753        #[should_panic(expected = "Error calling try_new_validated() inside new() in debug mode")]
754        fn panics_on_new_imaginary_part_infinity_debug() {
755            let _inf = new_complex_rug(1.0, f64::INFINITY);
756        }
757
758        #[test]
759        #[cfg(not(debug_assertions))]
760        fn arg_behavior_release() {
761            // NaN/Inf inputs for rug
762            // rug::Float::NAN.arg() behavior might differ or how it's handled by rug::Complex::arg
763            // rug::Float from f64::NAN is NaN. rug::Complex with NaN component.
764            // rug::Complex::arg() on NaN components: real(NaN).arg() is NaN.
765            //let nan_re = new_complex_rug(f64::NAN, 1.0);
766            //assert!(nan_re.arg().is_nan()); // RealRugStrictFinite::is_nan()
767
768            // rug::Float from f64::INFINITY is Inf.
769            let inf_re = new_complex_rug(f64::INFINITY, 0.0); // arg is 0
770            assert_eq!(inf_re.arg(), real_rug(0.0));
771            let inf_im = new_complex_rug(0.0, f64::INFINITY); // arg is PI/2
772            assert_eq!(inf_im.arg(), real_rug(FRAC_PI_2));
773            let inf_both = new_complex_rug(f64::INFINITY, f64::INFINITY); // arg is PI/4
774            assert_eq!(inf_both.arg(), real_rug(FRAC_PI_4));
775        }
776    }
777}
778//--------------------------------------------------------------------------------------------------