Skip to main content

num_valid/functions/
complex.rs

1#![deny(rustdoc::broken_intra_doc_links)]
2
3//! Complex number construction and part access.
4//!
5//! This module provides traits for creating complex numbers from polar coordinates
6//! and for accessing/mutating their real and imaginary parts.
7
8use crate::{
9    FpScalar, RealScalar,
10    core::traits::{raw::RawScalarValidate, validation::sealed::NativeReal},
11    functions::FunctionErrors,
12    kernels::{PrecisionBits, RawComplexTrait, RawScalarTrait},
13};
14use num::Complex;
15use std::backtrace::Backtrace;
16use thiserror::Error;
17use try_create::IntoInner;
18
19#[cfg(debug_assertions)]
20use crate::core::errors::capture_backtrace;
21
22#[cfg(debug_assertions)]
23use num::Zero;
24
25/// Trait for constructing complex scalar types from their raw components.
26///
27/// This trait provides methods for creating complex numbers from raw real and imaginary
28/// parts, as well as specialized constructors for purely real or purely imaginary numbers.
29///
30/// # Examples
31///
32/// ```rust
33/// use num_valid::ComplexNative64StrictFinite;
34/// use num_valid::functions::ComplexScalarConstructors;
35///
36/// // Create a complex number from raw f64 values
37/// let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
38///
39/// // Create a purely real number (imaginary part is zero)
40/// let real_only = ComplexNative64StrictFinite::try_new_pure_real(5.0).unwrap();
41///
42/// // Create a purely imaginary number (real part is zero)
43/// let imag_only = ComplexNative64StrictFinite::try_new_pure_imaginary(7.0).unwrap();
44/// ```
45pub trait ComplexScalarConstructors: FpScalar<InnerType = Self::RawComplex> {
46    /// The raw underlying complex type (e.g., `num::Complex<f64>` or `rug::Complex`).
47    type RawComplex: RawComplexTrait<RawReal = <Self::RealType as RealScalar>::RawReal>;
48
49    /// Tries to create a new complex scalar from its raw real and imaginary parts.
50    ///
51    /// Returns an error if either component fails validation (e.g., NaN, Infinity).
52    #[must_use = "this `Result` may contain an error that should be handled"]
53    fn try_new_complex(
54        real: <Self::RawComplex as RawComplexTrait>::RawReal,
55        imag: <Self::RawComplex as RawComplexTrait>::RawReal,
56    ) -> Result<Self, <Self::RawComplex as RawScalarValidate>::ValidationError>;
57
58    /// Creates a new complex scalar from its (validated) real and imaginary parts.
59    fn new_complex(real: Self::RealType, imag: Self::RealType) -> Self {
60        Self::try_new_complex(real.into_inner(), imag.into_inner()).unwrap()
61    }
62
63    /// Tries to create a new complex scalar from a raw real part, with a zero imaginary part.
64    #[must_use = "this `Result` may contain an error that should be handled"]
65    fn try_new_pure_real(
66        real_part: <Self::RawComplex as RawComplexTrait>::RawReal,
67    ) -> Result<Self, <Self::RawComplex as RawScalarValidate>::ValidationError> {
68        let zero =
69            <Self::RawComplex as RawComplexTrait>::RawReal::raw_zero(real_part.precision_bits());
70        Self::try_new_complex(real_part, zero)
71    }
72
73    /// Tries to create a new complex scalar from a raw imaginary part, with a zero real part.
74    #[must_use = "this `Result` may contain an error that should be handled"]
75    fn try_new_pure_imaginary(
76        imag_part: <Self::RawComplex as RawComplexTrait>::RawReal,
77    ) -> Result<Self, <Self::RawComplex as RawScalarValidate>::ValidationError> {
78        let zero =
79            <Self::RawComplex as RawComplexTrait>::RawReal::raw_zero(imag_part.precision_bits());
80        Self::try_new_complex(zero, imag_part)
81    }
82
83    /// Creates a new complex scalar from a validated real part, with a zero imaginary part.
84    ///
85    /// # Panics
86    ///
87    /// Panics if the underlying conversion fails. For a non-panicking version,
88    /// use [`try_new_pure_real`](Self::try_new_pure_real).
89    fn new_pure_real(real_part: Self::RealType) -> Self {
90        Self::try_new_pure_real(real_part.into_inner()).unwrap()
91    }
92
93    /// Creates a new complex scalar from a validated imaginary part, with a zero real part.
94    ///
95    /// # Panics
96    ///
97    /// Panics if the underlying conversion fails. For a non-panicking version,
98    /// use [`try_new_pure_imaginary`](Self::try_new_pure_imaginary).
99    fn new_pure_imaginary(imag_part: Self::RealType) -> Self {
100        Self::try_new_pure_imaginary(imag_part.into_inner()).unwrap()
101    }
102}
103
104/// Trait for accessing the real and imaginary components of a complex scalar.
105///
106/// This trait provides methods to extract and query the components of a complex number.
107///
108/// # Examples
109///
110/// ```rust
111/// use num_valid::ComplexNative64StrictFinite;
112/// use num_valid::functions::{ComplexScalarConstructors, ComplexScalarGetParts};
113///
114/// let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
115///
116/// // Access components
117/// assert_eq!(*z.raw_real_part(), 3.0);
118/// assert_eq!(*z.raw_imag_part(), 4.0);
119///
120/// // Check if purely real or imaginary
121/// assert!(!z.is_pure_real());
122/// assert!(!z.is_pure_imaginary());
123///
124/// let real_only = ComplexNative64StrictFinite::try_new_pure_real(5.0).unwrap();
125/// assert!(real_only.is_pure_real());
126/// ```
127pub trait ComplexScalarGetParts: ComplexScalarConstructors {
128    /// Get the real part of the complex number.
129    fn real_part(&self) -> Self::RealType;
130
131    /// Get the imaginary part of the complex number.
132    fn imag_part(&self) -> Self::RealType;
133
134    /// Returns a reference to the raw real part of the complex number.
135    fn raw_real_part(&self) -> &<Self::RealType as RealScalar>::RawReal;
136
137    /// Returns a reference to the raw imaginary part of the complex number.
138    fn raw_imag_part(&self) -> &<Self::RealType as RealScalar>::RawReal;
139
140    /// Returns `true` if the complex number is purely real (i.e., has no imaginary part).
141    ///
142    /// Note: the complex number zero is considered purely real.
143    fn is_pure_real(&self) -> bool {
144        self.raw_imag_part().is_zero()
145    }
146
147    /// Returns `true` if the complex number is purely imaginary (i.e., has zero real part and non-zero imaginary part).
148    ///
149    /// Note: the complex number zero is considered purely real.
150    fn is_pure_imaginary(&self) -> bool {
151        self.raw_real_part().is_zero() && !self.raw_imag_part().is_zero()
152    }
153}
154
155/// Trait for setting the real and imaginary components of a complex scalar.
156///
157/// This trait provides methods to modify the components of a complex number,
158/// either by mutation or by creating a new value with modified components.
159///
160/// # Examples
161///
162/// ```rust
163/// use num_valid::{ComplexNative64StrictFinite, RealNative64StrictFinite};
164/// use num_valid::functions::{ComplexScalarConstructors, ComplexScalarSetParts, ComplexScalarGetParts};
165/// use try_create::TryNew;
166///
167/// let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
168/// let new_real = RealNative64StrictFinite::try_new(10.0).unwrap();
169///
170/// // Create a new complex number with modified real part
171/// let z_modified = z.with_real_part(new_real);
172/// assert_eq!(*z_modified.raw_real_part(), 10.0);
173/// assert_eq!(*z_modified.raw_imag_part(), 4.0);
174/// ```
175pub trait ComplexScalarSetParts: ComplexScalarGetParts {
176    /// Set the real part of the complex number.
177    fn set_real_part(&mut self, real_part: Self::RealType);
178
179    /// Set the imaginary part of the complex number.
180    fn set_imaginary_part(&mut self, imag_part: Self::RealType);
181
182    /// Returns a new complex number with the real part set to the given value and the imaginary part from `self`.
183    fn with_real_part(mut self, real_part: Self::RealType) -> Self {
184        self.set_real_part(real_part);
185        self
186    }
187
188    /// Returns a new complex number with the imaginary part set to the given value and the real part from `self`.
189    fn with_imaginary_part(mut self, imag_part: Self::RealType) -> Self {
190        self.set_imaginary_part(imag_part);
191        self
192    }
193}
194
195/// Provides methods for in-place mutation of the components of a complex scalar.
196///
197/// This trait offers efficient, in-place operations to modify the real and imaginary
198/// parts of a complex number individually using a real scalar value. These methods
199/// are particularly useful in numerical algorithms that require adjusting specific
200/// components of a complex vector or matrix without allocating new complex numbers.
201///
202/// # Examples
203///
204/// ```
205/// use num_valid::{
206///     ComplexNative64StrictFinite, RealNative64StrictFinite,
207///     functions::{ComplexScalarGetParts, ComplexScalarMutateParts, ComplexScalarConstructors},
208/// };
209/// use try_create::TryNew;
210///
211/// let mut c = ComplexNative64StrictFinite::try_new_complex(3.0, 5.0).unwrap();
212/// let real_addend = RealNative64StrictFinite::try_new(2.0).unwrap();
213/// let real_multiplier = RealNative64StrictFinite::try_new(10.0).unwrap();
214///
215/// // Add 2.0 to the real part
216/// c.add_to_real_part(&real_addend);
217/// assert_eq!(c.real_part().as_ref(), &5.0);
218///
219/// // Multiply the imaginary part by 10.0
220/// c.multiply_imaginary_part(&real_multiplier);
221/// assert_eq!(c.imag_part().as_ref(), &50.0);
222///
223/// assert_eq!(c, ComplexNative64StrictFinite::try_new_complex(5.0, 50.0).unwrap());
224/// ```
225pub trait ComplexScalarMutateParts: ComplexScalarSetParts {
226    /// Add the value of the the real coefficient `c` to real part of `self`.
227    fn add_to_real_part(&mut self, c: &Self::RealType);
228
229    /// Add the value of the the real coefficient `c` to imaginary part of `self`.
230    fn add_to_imaginary_part(&mut self, c: &Self::RealType);
231
232    /// Multiply the value of the real part by the real coefficient `c`.
233    fn multiply_real_part(&mut self, c: &Self::RealType);
234
235    /// Multiply the value of the imaginary part by the real coefficient `c`.
236    fn multiply_imaginary_part(&mut self, c: &Self::RealType);
237}
238
239//--------------------------------------------------------------------------------------------------
240/// Trait for computing the complex conjugate of a number.
241///
242/// The complex conjugate of a complex number is obtained by changing the sign of its imaginary part.
243///
244/// # Example
245///
246/// ```rust
247/// use num_valid::functions::Conjugate;
248/// use num::Complex;
249/// use try_create::TryNew;
250///
251/// // Example with Complex<f64>
252/// let z = Complex::new(1.0, -2.0);
253/// let z_conjugate = z.conjugate();
254/// println!("Conjugate: {}", z_conjugate); // Output: Conjugate: 1+2i
255/// assert_eq!(z_conjugate, Complex::new(1.0, 2.0));
256///
257/// // Example with ComplexRugStrictFinite<53> (when the `rug` feature is enabled)
258/// #[cfg(feature = "rug")]
259/// {
260///     use rug::Float;
261///     use num_valid::ComplexRugStrictFinite;
262///
263///     const PRECISION: u32 = 53;
264///
265///     let z = ComplexRugStrictFinite::<PRECISION>::try_new(
266///         rug::Complex::with_val(PRECISION,
267///             (Float::with_val(PRECISION, 1.0),Float::with_val(PRECISION, -2.0)),
268///         )).unwrap();
269///     let z_conjugate = z.conjugate();
270///     println!("Conjugate: {}", z_conjugate);
271///     assert_eq!(
272///         z_conjugate,
273///         ComplexRugStrictFinite::<PRECISION>::try_new(
274///         rug::Complex::with_val(PRECISION,
275///             (Float::with_val(PRECISION, 1.0),Float::with_val(PRECISION, 2.0)),
276///         )).unwrap()
277///     );
278/// }
279/// ```
280pub trait Conjugate: Sized {
281    /// Returns the *complex conjugate* of `self`.
282    fn conjugate(self) -> Self;
283}
284
285impl<T: NativeReal> Conjugate for Complex<T> {
286    #[inline(always)]
287    fn conjugate(self) -> Self {
288        self.conj()
289    }
290}
291
292//--------------------------------------------------------------------------------------------------
293
294//--------------------------------------------------------------------------------------------------
295/// Errors that can occur specifically during the input validation phase or due to
296/// special input values when attempting to compute the argument (principal value)
297/// of a complex number.
298///
299/// This enum is used as a source for the [`ArgErrors::Input`] variant and helps categorize
300/// why an input was deemed unsuitable *before* or *during* the core argument calculation.
301///
302/// # Generic Parameters
303///
304/// - `RawComplex`: A type that implements [`RawComplexTrait`].
305///   This defines the raw error type for the input complex number via `<RawComplex as RawScalarValidate>::ValidationError`.
306#[derive(Debug, Error)]
307pub enum ArgInputErrors<RawComplex: RawComplexTrait> {
308    /// The input complex number failed basic validation checks.
309    ///
310    /// This error occurs if the input complex number itself is considered invalid
311    /// according to the validation policy (e.g., [`StrictFinitePolicy`](crate::core::policies::StrictFinitePolicy)),
312    /// such as containing NaN or Infinity components, before the argument calculation
313    /// is attempted.
314    #[error("the input complex number is invalid according to validation policy")]
315    ValidationError {
316        /// The underlying validation error from the complex number type.
317        ///
318        /// This provides more specific details about why the complex number's
319        /// components (real or imaginary parts) were considered invalid.
320        #[source]
321        #[backtrace]
322        source: <RawComplex as RawScalarValidate>::ValidationError,
323    },
324
325    /// The input complex number is zero.
326    ///
327    /// The argument of zero is undefined. This error indicates that the input
328    /// value was `0 + 0i`.
329    #[error("the input value is zero!")]
330    Zero {
331        /// A captured backtrace for debugging purposes.
332        backtrace: Backtrace,
333    },
334}
335
336/// A type alias for [`FunctionErrors`], specialized for errors that can occur during
337/// the computation of the argument (principal value) of a complex number.
338///
339/// This type represents the possible failures when calling [`Arg::try_arg()`].
340///
341/// # Generic Parameters
342///
343/// - `RawComplex`: A type that implements [`RawComplexTrait`]. This defines:
344///   - The raw error type for the input complex number via `<RawComplex as RawScalarValidate>::ValidationError`.
345///   - The raw error type for the output real number (the argument) via
346///     `<<RawComplex as RawComplexTrait>::RawReal as RawScalarValidate>::ValidationError`.
347///
348/// # Variants
349///
350/// This type alias wraps [`FunctionErrors`], which has the following variants in this context:
351///
352/// - `Input { source: ArgInputErrors<RawComplex> }`:
353///   Indicates that the input complex number was invalid for argument computation.
354///   This could be due to failing initial validation (e.g., containing NaN or Infinity)
355///   or because the input was zero (for which the argument is undefined).
356///   The `source` field provides more specific details via [`ArgInputErrors`].
357///
358/// - `Output { source: <<RawComplex as RawComplexTrait>::RawReal as RawScalarValidate>::ValidationError }`:
359///   Indicates that the computed argument (a real number) failed validation.
360///   This typically means the result of the `atan2` or equivalent operation yielded
361///   a non-finite value (NaN or Infinity), which is unexpected if the input was valid
362///   and non-zero. The `source` field provides the raw validation error for the output real number.
363pub type ArgErrors<RawComplex> = FunctionErrors<
364    ArgInputErrors<RawComplex>,
365    <<RawComplex as RawComplexTrait>::RawReal as RawScalarValidate>::ValidationError,
366>;
367
368/// Trait for computing the argument (principal value) of a complex number.
369///
370/// The argument of a complex number `z = x + iy` is the angle `φ` (phi)
371/// in polar coordinates `z = r(cos φ + i sin φ)`. It is typically computed
372/// using `atan2(y, x)` and lies in the interval `(-π, π]`.
373///
374/// This trait provides both a fallible version (`try_arg`) that performs validation in debug builds
375/// and an infallible version (`arg`) that may panic in debug builds if validation fails.
376pub trait Arg: Sized {
377    /// The return type of the *principal value* (or *argument*) function. It is always a real number.
378    // TODO: Consider using the trait bound `Output: RealScalar` instead of `Output: Sized`.
379    type Output: Sized;
380
381    /// The error type that can be returned by the [`Arg::try_arg()`] method.
382    ///
383    /// This is typically an instantiation of [`ArgErrors`].
384    type Error: std::error::Error;
385
386    /// Attempts to compute the argument of `self`, returning a `Result`.
387    #[must_use = "this `Result` may contain an error that should be handled"]
388    fn try_arg(self) -> Result<Self::Output, Self::Error>;
389
390    /// Returns the argument of `self`.
391    fn arg(self) -> Self::Output;
392}
393
394impl<T> Arg for Complex<T>
395where
396    Self: RawComplexTrait<RawReal = T>,
397    T: NativeReal,
398{
399    type Output = T;
400
401    type Error = ArgErrors<Complex<T>>;
402
403    /// Attempts to compute the argument of `self`, returning a `Result`.
404    ///
405    /// This method first validates the input `self` using [`StrictFinitePolicy`](crate::core::policies::StrictFinitePolicy) and
406    /// also checks if it is zero. If the input is valid (finite components, non-zero),
407    /// it computes the argument and then validates the resulting real number
408    /// using the same policy.
409    ///
410    /// # Returns
411    ///
412    /// - `Ok(Self::Output)`: If the input complex number is valid (finite, non-zero)
413    ///   and the computed argument is a valid (finite) real number.
414    /// - `Err(Self::Error)`: If the input is invalid (e.g., NaN or Infinity components, zero)
415    ///   or if the computed argument is invalid (e.g., NaN, Infinity).
416    ///
417    /// # Examples
418    ///
419    /// ```no_run
420    /// use num_valid::functions::Arg;
421    /// use num::Complex;
422    /// use std::f64::consts::PI;
423    ///
424    /// let z = Complex::new(1.0, 1.0); // Represents 1 + i
425    /// match z.try_arg() {
426    ///     Ok(angle) => println!("Arg(1+i) = {}", angle), // Arg(1+i) = 0.785... (π/4)
427    ///     Err(e) => println!("Error: {:?}", e),
428    /// }
429    ///
430    /// let zero = Complex::new(0.0, 0.0);
431    /// #[cfg(debug_assertions)]
432    /// assert!(zero.try_arg().is_err());
433    ///
434    /// let nan_val = Complex::new(f64::NAN, 1.0);
435    /// #[cfg(debug_assertions)]
436    /// assert!(nan_val.try_arg().is_err());
437    /// ```
438    #[inline(always)]
439    fn try_arg(self) -> Result<Self::Output, Self::Error> {
440        #[cfg(debug_assertions)]
441        {
442            self.validate_raw_scalar()
443                .map_err(|e| Self::Error::Input(ArgInputErrors::ValidationError { source: e }))
444                .and_then(|v: Complex<T>| {
445                    if <Complex<T> as Zero>::is_zero(&v) {
446                        Err(Self::Error::Input(ArgInputErrors::Zero {
447                            backtrace: capture_backtrace(),
448                        }))
449                    } else {
450                        Complex::<T>::arg(v)
451                            .validate_raw_scalar()
452                            .map_err(|e| Self::Error::Output { source: e })
453                    }
454                })
455        }
456        #[cfg(not(debug_assertions))]
457        {
458            Ok(Complex::<T>::arg(self))
459        }
460    }
461
462    /// Returns the argument of `self`.
463    ///
464    /// # Behavior
465    ///
466    /// - **Debug Builds (`#[cfg(debug_assertions)]`)**: This method internally calls `try_arg().unwrap()`.
467    ///   It will panic if the input `self` is invalid (e.g., NaN/Infinity components, zero)
468    ///   or if the computed argument is invalid.
469    /// - **Release Builds (`#[cfg(not(debug_assertions))]`)**: This method calls the underlying
470    ///   argument function directly (e.g., `num::Complex::arg`).
471    ///   The behavior for non-finite inputs or zero (like NaN propagation or specific
472    ///   return values like `0.0` for `arg(0)`) depends on the underlying implementation.
473    ///
474    /// # Panics
475    ///
476    /// In debug builds, this method will panic if `try_arg()` would return an `Err`.
477    ///
478    /// # Examples
479    ///
480    /// ```rust
481    /// use num_valid::functions::Arg;
482    /// use num::Complex;
483    /// use std::f64::consts::PI;
484    ///
485    /// let z = Complex::new(-1.0, 0.0); // Represents -1
486    /// println!("Arg(-1) = {}", z.arg()); // Arg(-1) = 3.141... (π)
487    ///
488    /// let z_imag = Complex::new(0.0, 1.0); // Represents i
489    /// println!("Arg(i) = {}", z_imag.arg()); // Arg(i) = 1.570... (π/2)
490    /// ```
491    #[inline(always)]
492    fn arg(self) -> T {
493        #[cfg(debug_assertions)]
494        {
495            self.try_arg()
496                .expect("Error calling Arg::try_arg() inside Arg::arg() debug mode.")
497        }
498        #[cfg(not(debug_assertions))]
499        {
500            Complex::<T>::arg(self)
501        }
502    }
503}
504
505//--------------------------------------------------------------------------------------------------
506
507//--------------------------------------------------------------------------------------------------
508#[cfg(test)]
509mod tests {
510    use super::*;
511    use crate::{ComplexScalar, core::errors::ErrorsValidationRawComplex};
512    use num::Complex;
513    use try_create::TryNew;
514
515    mod native_f64 {
516        use super::*;
517        use crate::backends::native::validated::{
518            ComplexNative64StrictFinite, RealNative64StrictFinite,
519        };
520        use std::f64::consts::*;
521
522        #[test]
523        fn conjugate() {
524            let z = Complex::new(1.0, -2.0);
525            let expected_conjugate = Complex::new(1.0, 2.0);
526            assert_eq!(z.conjugate(), expected_conjugate);
527
528            let z = Complex::new(-3.0, 4.0);
529            let expected_conjugate = Complex::new(-3.0, -4.0);
530            assert_eq!(z.conjugate(), expected_conjugate);
531
532            // Special cases
533            // Zero
534            let z_zero = Complex::new(0.0, 0.0);
535            assert_eq!(z_zero.conjugate(), Complex::new(0.0, 0.0));
536            // Purely real
537            let z_real = Complex::new(5.0, 0.0);
538            assert_eq!(z_real.conjugate(), Complex::new(5.0, 0.0));
539            // Purely imaginary
540            let z_imag = Complex::new(0.0, 5.0);
541            assert_eq!(z_imag.conjugate(), Complex::new(0.0, -5.0));
542            let z_neg_imag = Complex::new(0.0, -5.0);
543            assert_eq!(z_neg_imag.conjugate(), Complex::new(0.0, 5.0));
544        }
545
546        #[test]
547        fn arg() {
548            let z = Complex::new(1.0, 1.0);
549            let expected_arg = std::f64::consts::FRAC_PI_4; //0.7853981633974483; // pi/4
550            assert_eq!(z.arg(), expected_arg);
551
552            // Axes
553            let z_pos_real = Complex::new(1.0, 0.0);
554            assert_eq!(z_pos_real.arg(), 0.0);
555            let z_neg_real = Complex::new(-1.0, 0.0);
556            assert_eq!(z_neg_real.arg(), PI);
557            let z_pos_imag = Complex::new(0.0, 1.0);
558            assert_eq!(z_pos_imag.arg(), FRAC_PI_2);
559            let z_neg_imag = Complex::new(0.0, -1.0);
560            assert_eq!(z_neg_imag.arg(), -FRAC_PI_2);
561
562            // Quadrants
563            let z2 = Complex::new(-1.0, 1.0); // Q2
564            assert_eq!(z2.arg(), 3.0 * FRAC_PI_4);
565            let z3 = Complex::new(-1.0, -1.0); // Q3
566            assert_eq!(z3.arg(), -3.0 * FRAC_PI_4);
567            let z4 = Complex::new(1.0, -1.0); // Q4
568            assert_eq!(z4.arg(), -FRAC_PI_4);
569        }
570
571        #[test]
572        #[cfg(debug_assertions)]
573        fn try_arg_invalid() {
574            let zero = Complex::new(0.0, 0.0);
575            assert!(matches!(
576                zero.try_arg(),
577                Err(ArgErrors::<Complex<f64>>::Input(
578                    ArgInputErrors::Zero { .. }
579                ))
580            ));
581
582            // NaN cases
583            let z_nan_re = Complex::new(f64::NAN, 1.0);
584            assert!(matches!(
585                z_nan_re.try_arg(),
586                Err(ArgErrors::<Complex<f64>>::Input(
587                    ArgInputErrors::ValidationError {
588                        source: ErrorsValidationRawComplex::InvalidRealPart { .. }
589                    }
590                ))
591            ));
592            let z_nan_im = Complex::new(1.0, f64::NAN);
593            assert!(matches!(
594                z_nan_im.try_arg(),
595                Err(ArgErrors::<Complex<f64>>::Input(
596                    ArgInputErrors::ValidationError {
597                        source: ErrorsValidationRawComplex::InvalidImaginaryPart { .. }
598                    }
599                ))
600            ));
601
602            // Infinity cases
603            let z_inf_re = Complex::new(f64::INFINITY, 1.0);
604            assert!(matches!(
605                z_inf_re.try_arg(),
606                Err(ArgErrors::<Complex<f64>>::Input(
607                    ArgInputErrors::ValidationError {
608                        source: ErrorsValidationRawComplex::InvalidRealPart { .. }
609                    }
610                ))
611            ));
612            let z_inf_im = Complex::new(1.0, f64::INFINITY);
613            assert!(matches!(
614                z_inf_im.try_arg(),
615                Err(ArgErrors::<Complex<f64>>::Input(
616                    ArgInputErrors::ValidationError {
617                        source: ErrorsValidationRawComplex::InvalidImaginaryPart { .. }
618                    }
619                ))
620            ));
621
622            // Case where num::Complex::arg might produce NaN, caught by output validation
623            // e.g. if inputs were valid but atan2 produced NaN (though hard with f64 if inputs are finite)
624            // This test assumes that if Complex::arg itself returns NaN, f64::try_new catches it.
625            // Example: Complex::new(f64::NEG_INFINITY, f64::NEG_INFINITY).arg() is -3*PI/4
626            // Example: Complex::new(f64::NAN, f64::NEG_INFINITY).arg() is NaN
627            // The validate() step should catch component-wise NaN/Inf first.
628            // If validate() passed but Complex::arg() produced NaN, it would be an Output error.
629            // For instance, if we had a type where components are valid but arg calculation leads to NaN.
630            // For Complex<f64>, validate() is quite comprehensive.
631            // Let's consider a case that passes validate() but whose arg() is NaN (hypothetical for f64, more for other types)
632            // For f64, if re or im is NaN, validate() fails. If both are Inf, validate() fails.
633            // If one is Inf and other is finite, arg() is well-defined.
634            // So, for Complex<f64>, the Output error due to NaN is less likely if validate() passes.
635        }
636
637        #[test]
638        #[cfg(debug_assertions)]
639        #[should_panic]
640        fn arg_panics_on_zero_debug() {
641            let zero = Complex::new(0.0, 0.0);
642            let _ = Arg::arg(zero);
643        }
644
645        #[test]
646        #[cfg(debug_assertions)]
647        #[should_panic]
648        fn arg_panics_on_nan_debug() {
649            let nan = Complex::new(f64::NAN, 1.0);
650            let _ = Arg::arg(nan);
651        }
652
653        #[test]
654        #[cfg(debug_assertions)]
655        #[should_panic]
656        fn arg_panics_on_inf_debug() {
657            let inf = Complex::new(f64::INFINITY, 1.0);
658            let _ = Arg::arg(inf);
659        }
660
661        #[test]
662        #[cfg(not(debug_assertions))]
663        fn arg_behavior_release() {
664            // Zero input in release returns 0.0 from num::Complex::arg
665            let zero = Complex::new(0.0, 0.0);
666            assert_eq!(Arg::arg(zero), 0.0);
667
668            // NaN/Inf inputs in release mode will lead to NaN from num::Complex::arg
669            let nan_re = Complex::new(f64::NAN, 1.0);
670            assert!(Arg::arg(nan_re).is_nan());
671            let inf_re = Complex::new(f64::INFINITY, 0.0); // arg is 0
672            assert_eq!(Arg::arg(inf_re), 0.0);
673            let inf_im = Complex::new(0.0, f64::INFINITY); // arg is PI/2
674            assert_eq!(Arg::arg(inf_im), FRAC_PI_2);
675            let inf_both = Complex::new(f64::INFINITY, f64::INFINITY); // arg is PI/4
676            assert_eq!(Arg::arg(inf_both), FRAC_PI_4);
677        }
678
679        #[test]
680        fn test_is_pure_real() {
681            // Purely real number
682            let z_real = ComplexNative64StrictFinite::try_new_complex(5.0, 0.0).unwrap();
683            assert!(z_real.is_pure_real());
684            assert!(!z_real.is_pure_imaginary());
685
686            // Zero is considered purely real
687            let z_zero = ComplexNative64StrictFinite::try_new_complex(0.0, 0.0).unwrap();
688            assert!(z_zero.is_pure_real());
689            assert!(!z_zero.is_pure_imaginary());
690
691            // Complex with both parts non-zero
692            let z_complex = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
693            assert!(!z_complex.is_pure_real());
694            assert!(!z_complex.is_pure_imaginary());
695
696            // Purely imaginary
697            let z_imag = ComplexNative64StrictFinite::try_new_complex(0.0, 5.0).unwrap();
698            assert!(!z_imag.is_pure_real());
699            assert!(z_imag.is_pure_imaginary());
700        }
701
702        #[test]
703        fn test_is_pure_imaginary() {
704            // Purely imaginary number
705            let z_imag = ComplexNative64StrictFinite::try_new_complex(0.0, 7.0).unwrap();
706            assert!(z_imag.is_pure_imaginary());
707            assert!(!z_imag.is_pure_real());
708
709            // Negative imaginary
710            let z_neg_imag = ComplexNative64StrictFinite::try_new_complex(0.0, -3.0).unwrap();
711            assert!(z_neg_imag.is_pure_imaginary());
712
713            // Zero is NOT considered purely imaginary
714            let z_zero = ComplexNative64StrictFinite::try_new_complex(0.0, 0.0).unwrap();
715            assert!(!z_zero.is_pure_imaginary());
716            assert!(z_zero.is_pure_real());
717        }
718
719        #[test]
720        fn test_with_real_part() {
721            let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
722            let new_real = RealNative64StrictFinite::try_new(10.0).unwrap();
723
724            let z_modified = z.with_real_part(new_real);
725            assert_eq!(*z_modified.raw_real_part(), 10.0);
726            assert_eq!(*z_modified.raw_imag_part(), 4.0);
727        }
728
729        #[test]
730        fn test_with_imaginary_part() {
731            let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
732            let new_imag = RealNative64StrictFinite::try_new(20.0).unwrap();
733
734            let z_modified = z.with_imaginary_part(new_imag);
735            assert_eq!(*z_modified.raw_real_part(), 3.0);
736            assert_eq!(*z_modified.raw_imag_part(), 20.0);
737        }
738
739        #[test]
740        fn test_new_pure_real() {
741            let real_part = RealNative64StrictFinite::try_new(7.0).unwrap();
742            let z = ComplexNative64StrictFinite::new_pure_real(real_part);
743
744            assert_eq!(*z.raw_real_part(), 7.0);
745            assert_eq!(*z.raw_imag_part(), 0.0);
746            assert!(z.is_pure_real());
747        }
748
749        #[test]
750        fn test_new_pure_imaginary() {
751            let imag_part = RealNative64StrictFinite::try_new(9.0).unwrap();
752            let z = ComplexNative64StrictFinite::new_pure_imaginary(imag_part);
753
754            assert_eq!(*z.raw_real_part(), 0.0);
755            assert_eq!(*z.raw_imag_part(), 9.0);
756            assert!(z.is_pure_imaginary());
757        }
758
759        #[test]
760        fn test_try_new_pure_real_valid() {
761            let z = ComplexNative64StrictFinite::try_new_pure_real(5.0).unwrap();
762            assert_eq!(*z.raw_real_part(), 5.0);
763            assert_eq!(*z.raw_imag_part(), 0.0);
764        }
765
766        #[test]
767        fn test_try_new_pure_real_invalid() {
768            // NaN should fail
769            let result = ComplexNative64StrictFinite::try_new_pure_real(f64::NAN);
770            assert!(result.is_err());
771
772            // Infinity should fail
773            let result = ComplexNative64StrictFinite::try_new_pure_real(f64::INFINITY);
774            assert!(result.is_err());
775        }
776
777        #[test]
778        fn test_try_new_pure_imaginary_valid() {
779            let z = ComplexNative64StrictFinite::try_new_pure_imaginary(8.0).unwrap();
780            assert_eq!(*z.raw_real_part(), 0.0);
781            assert_eq!(*z.raw_imag_part(), 8.0);
782        }
783
784        #[test]
785        fn test_try_new_pure_imaginary_invalid() {
786            // NaN should fail
787            let result = ComplexNative64StrictFinite::try_new_pure_imaginary(f64::NAN);
788            assert!(result.is_err());
789
790            // Infinity should fail
791            let result = ComplexNative64StrictFinite::try_new_pure_imaginary(f64::NEG_INFINITY);
792            assert!(result.is_err());
793        }
794
795        #[test]
796        fn test_into_parts_basic() {
797            let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
798
799            // Consume z and extract its parts
800            let (real, imag) = z.into_parts();
801
802            assert_eq!(*real.as_ref(), 3.0);
803            assert_eq!(*imag.as_ref(), 4.0);
804        }
805
806        #[test]
807        fn test_into_parts_zero() {
808            let z = ComplexNative64StrictFinite::try_new_complex(0.0, 0.0).unwrap();
809            let (real, imag) = z.into_parts();
810
811            assert_eq!(*real.as_ref(), 0.0);
812            assert_eq!(*imag.as_ref(), 0.0);
813        }
814
815        #[test]
816        fn test_into_parts_pure_real() {
817            let z = ComplexNative64StrictFinite::try_new_complex(7.5, 0.0).unwrap();
818            let (real, imag) = z.into_parts();
819
820            assert_eq!(*real.as_ref(), 7.5);
821            assert_eq!(*imag.as_ref(), 0.0);
822        }
823
824        #[test]
825        fn test_into_parts_pure_imaginary() {
826            let z = ComplexNative64StrictFinite::try_new_complex(0.0, -9.2).unwrap();
827            let (real, imag) = z.into_parts();
828
829            assert_eq!(*real.as_ref(), 0.0);
830            assert_eq!(*imag.as_ref(), -9.2);
831        }
832
833        #[test]
834        fn test_into_parts_negative() {
835            let z = ComplexNative64StrictFinite::try_new_complex(-5.5, -3.3).unwrap();
836            let (real, imag) = z.into_parts();
837
838            assert_eq!(*real.as_ref(), -5.5);
839            assert_eq!(*imag.as_ref(), -3.3);
840        }
841
842        #[test]
843        fn test_into_parts_parts_are_independent() {
844            let z = ComplexNative64StrictFinite::try_new_complex(10.0, 20.0).unwrap();
845            let (real, imag) = z.into_parts();
846
847            // Parts should be usable independently
848            let two = RealNative64StrictFinite::try_new(2.0).unwrap();
849            let real_doubled = real * two;
850            let imag_halved = imag / two;
851
852            assert_eq!(*real_doubled.as_ref(), 20.0);
853            assert_eq!(*imag_halved.as_ref(), 10.0);
854        }
855    }
856
857    mod native_f32 {
858        use super::*;
859        use crate::backends::native::validated::{
860            ComplexNative32StrictFinite, RealNative32StrictFinite,
861        };
862        use std::f32::consts::*;
863
864        #[test]
865        fn conjugate() {
866            let z = Complex::new(1.0_f32, -2.0_f32);
867            let expected_conjugate = Complex::new(1.0_f32, 2.0_f32);
868            assert_eq!(z.conjugate(), expected_conjugate);
869
870            let z = Complex::new(-3.0_f32, 4.0_f32);
871            let expected_conjugate = Complex::new(-3.0_f32, -4.0_f32);
872            assert_eq!(z.conjugate(), expected_conjugate);
873
874            // Special cases
875            // Zero
876            let z_zero = Complex::new(0.0_f32, 0.0_f32);
877            assert_eq!(z_zero.conjugate(), Complex::new(0.0_f32, 0.0_f32));
878            // Purely real
879            let z_real = Complex::new(5.0_f32, 0.0_f32);
880            assert_eq!(z_real.conjugate(), Complex::new(5.0_f32, 0.0_f32));
881            // Purely imaginary
882            let z_imag = Complex::new(0.0_f32, 5.0_f32);
883            assert_eq!(z_imag.conjugate(), Complex::new(0.0_f32, -5.0_f32));
884            let z_neg_imag = Complex::new(0.0_f32, -5.0_f32);
885            assert_eq!(z_neg_imag.conjugate(), Complex::new(0.0_f32, 5.0_f32));
886        }
887
888        #[test]
889        fn arg() {
890            let z = Complex::new(1.0_f32, 1.0_f32);
891            let expected_arg = std::f32::consts::FRAC_PI_4; //0.7853981633974483; // pi/4
892            assert_eq!(z.arg(), expected_arg);
893
894            // Axes
895            let z_pos_real = Complex::new(1.0_f32, 0.0_f32);
896            assert_eq!(z_pos_real.arg(), 0.0_f32);
897            let z_neg_real = Complex::new(-1.0_f32, 0.0_f32);
898            assert_eq!(z_neg_real.arg(), PI);
899            let z_pos_imag = Complex::new(0.0_f32, 1.0_f32);
900            assert_eq!(z_pos_imag.arg(), FRAC_PI_2);
901            let z_neg_imag = Complex::new(0.0_f32, -1.0_f32);
902            assert_eq!(z_neg_imag.arg(), -FRAC_PI_2);
903
904            // Quadrants
905            let z2 = Complex::new(-1.0_f32, 1.0_f32); // Q2
906            assert_eq!(z2.arg(), 3.0 * FRAC_PI_4);
907            let z3 = Complex::new(-1.0_f32, -1.0_f32); // Q3
908            assert_eq!(z3.arg(), -3.0 * FRAC_PI_4);
909            let z4 = Complex::new(1.0_f32, -1.0_f32); // Q4
910            assert_eq!(z4.arg(), -FRAC_PI_4);
911        }
912
913        #[test]
914        #[cfg(debug_assertions)]
915        fn try_arg_invalid() {
916            let zero = Complex::new(0.0_f32, 0.0_f32);
917            assert!(matches!(
918                zero.try_arg(),
919                Err(ArgErrors::<Complex<f32>>::Input(
920                    ArgInputErrors::Zero { .. }
921                ))
922            ));
923
924            // NaN cases
925            let z_nan_re = Complex::new(f32::NAN, 1.0_f32);
926            assert!(matches!(
927                z_nan_re.try_arg(),
928                Err(ArgErrors::<Complex<f32>>::Input(
929                    ArgInputErrors::ValidationError {
930                        source: ErrorsValidationRawComplex::InvalidRealPart { .. }
931                    }
932                ))
933            ));
934            let z_nan_im = Complex::new(1.0_f32, f32::NAN);
935            assert!(matches!(
936                z_nan_im.try_arg(),
937                Err(ArgErrors::<Complex<f32>>::Input(
938                    ArgInputErrors::ValidationError {
939                        source: ErrorsValidationRawComplex::InvalidImaginaryPart { .. }
940                    }
941                ))
942            ));
943
944            // Infinity cases
945            let z_inf_re = Complex::new(f32::INFINITY, 1.0_f32);
946            assert!(matches!(
947                z_inf_re.try_arg(),
948                Err(ArgErrors::<Complex<f32>>::Input(
949                    ArgInputErrors::ValidationError {
950                        source: ErrorsValidationRawComplex::InvalidRealPart { .. }
951                    }
952                ))
953            ));
954            let z_inf_im = Complex::new(1.0_f32, f32::INFINITY);
955            assert!(matches!(
956                z_inf_im.try_arg(),
957                Err(ArgErrors::<Complex<f32>>::Input(
958                    ArgInputErrors::ValidationError {
959                        source: ErrorsValidationRawComplex::InvalidImaginaryPart { .. }
960                    }
961                ))
962            ));
963
964            // Case where num::Complex::arg might produce NaN, caught by output validation
965            // e.g. if inputs were valid but atan2 produced NaN (though hard with f64 if inputs are finite)
966            // This test assumes that if Complex::arg itself returns NaN, f64::try_new catches it.
967            // Example: Complex::new(f32::NEG_INFINITY, f32::NEG_INFINITY).arg() is -3*PI/4
968            // Example: Complex::new(f32::NAN, f32::NEG_INFINITY).arg() is NaN
969            // The validate() step should catch component-wise NaN/Inf first.
970            // If validate() passed but Complex::arg() produced NaN, it would be an Output error.
971            // For instance, if we had a type where components are valid but arg calculation leads to NaN.
972            // For Complex<f64>, validate() is quite comprehensive.
973            // Let's consider a case that passes validate() but whose arg() is NaN (hypothetical for f64, more for other types)
974            // For f64, if re or im is NaN, validate() fails. If both are Inf, validate() fails.
975            // If one is Inf and other is finite, arg() is well-defined.
976            // So, for Complex<f64>, the Output error due to NaN is less likely if validate() passes.
977        }
978
979        #[test]
980        #[cfg(debug_assertions)]
981        #[should_panic]
982        fn arg_panics_on_zero_debug() {
983            let zero = Complex::new(0.0, 0.0);
984            let _ = Arg::arg(zero);
985        }
986
987        #[test]
988        #[cfg(debug_assertions)]
989        #[should_panic]
990        fn arg_panics_on_nan_debug() {
991            let nan = Complex::new(f32::NAN, 1.0_f32);
992            let _ = Arg::arg(nan);
993        }
994
995        #[test]
996        #[cfg(debug_assertions)]
997        #[should_panic]
998        fn arg_panics_on_inf_debug() {
999            let inf = Complex::new(f32::INFINITY, 1.0_f32);
1000            let _ = Arg::arg(inf);
1001        }
1002
1003        #[test]
1004        #[cfg(not(debug_assertions))]
1005        fn arg_behavior_release() {
1006            // Zero input in release returns 0.0 from num::Complex::arg
1007            let zero = Complex::new(0.0, 0.0);
1008            assert_eq!(Arg::arg(zero), 0.0);
1009
1010            // NaN/Inf inputs in release mode will lead to NaN from num::Complex::arg
1011            let nan_re = Complex::new(f32::NAN, 1.0_f32);
1012            assert!(Arg::arg(nan_re).is_nan());
1013            let inf_re = Complex::new(f32::INFINITY, 0.0_f32); // arg is 0
1014            assert_eq!(Arg::arg(inf_re), 0.0_f32);
1015            let inf_im = Complex::new(0.0_f32, f32::INFINITY); // arg is PI/2
1016            assert_eq!(Arg::arg(inf_im), FRAC_PI_2);
1017            let inf_both = Complex::new(f32::INFINITY, f32::INFINITY); // arg is PI/4
1018            assert_eq!(Arg::arg(inf_both), FRAC_PI_4);
1019        }
1020
1021        #[test]
1022        fn test_is_pure_real() {
1023            // Purely real number
1024            let z_real = ComplexNative32StrictFinite::try_new_complex(5.0, 0.0).unwrap();
1025            assert!(z_real.is_pure_real());
1026            assert!(!z_real.is_pure_imaginary());
1027
1028            // Zero is considered purely real
1029            let z_zero = ComplexNative32StrictFinite::try_new_complex(0.0, 0.0).unwrap();
1030            assert!(z_zero.is_pure_real());
1031            assert!(!z_zero.is_pure_imaginary());
1032
1033            // Complex with both parts non-zero
1034            let z_complex = ComplexNative32StrictFinite::try_new_complex(3.0, 4.0).unwrap();
1035            assert!(!z_complex.is_pure_real());
1036            assert!(!z_complex.is_pure_imaginary());
1037
1038            // Purely imaginary
1039            let z_imag = ComplexNative32StrictFinite::try_new_complex(0.0, 5.0).unwrap();
1040            assert!(!z_imag.is_pure_real());
1041            assert!(z_imag.is_pure_imaginary());
1042        }
1043
1044        #[test]
1045        fn test_is_pure_imaginary() {
1046            // Purely imaginary number
1047            let z_imag = ComplexNative32StrictFinite::try_new_complex(0.0, 7.0).unwrap();
1048            assert!(z_imag.is_pure_imaginary());
1049            assert!(!z_imag.is_pure_real());
1050
1051            // Negative imaginary
1052            let z_neg_imag = ComplexNative32StrictFinite::try_new_complex(0.0, -3.0).unwrap();
1053            assert!(z_neg_imag.is_pure_imaginary());
1054
1055            // Zero is NOT considered purely imaginary
1056            let z_zero = ComplexNative32StrictFinite::try_new_complex(0.0, 0.0).unwrap();
1057            assert!(!z_zero.is_pure_imaginary());
1058            assert!(z_zero.is_pure_real());
1059        }
1060
1061        #[test]
1062        fn test_with_real_part() {
1063            let z = ComplexNative32StrictFinite::try_new_complex(3.0, 4.0).unwrap();
1064            let new_real = RealNative32StrictFinite::try_new(10.0).unwrap();
1065
1066            let z_modified = z.with_real_part(new_real);
1067            assert_eq!(*z_modified.raw_real_part(), 10.0);
1068            assert_eq!(*z_modified.raw_imag_part(), 4.0);
1069        }
1070
1071        #[test]
1072        fn test_with_imaginary_part() {
1073            let z = ComplexNative32StrictFinite::try_new_complex(3.0, 4.0).unwrap();
1074            let new_imag = RealNative32StrictFinite::try_new(20.0).unwrap();
1075
1076            let z_modified = z.with_imaginary_part(new_imag);
1077            assert_eq!(*z_modified.raw_real_part(), 3.0);
1078            assert_eq!(*z_modified.raw_imag_part(), 20.0);
1079        }
1080
1081        #[test]
1082        fn test_new_pure_real() {
1083            let real_part = RealNative32StrictFinite::try_new(7.0).unwrap();
1084            let z = ComplexNative32StrictFinite::new_pure_real(real_part);
1085
1086            assert_eq!(*z.raw_real_part(), 7.0);
1087            assert_eq!(*z.raw_imag_part(), 0.0);
1088            assert!(z.is_pure_real());
1089        }
1090
1091        #[test]
1092        fn test_new_pure_imaginary() {
1093            let imag_part = RealNative32StrictFinite::try_new(9.0).unwrap();
1094            let z = ComplexNative32StrictFinite::new_pure_imaginary(imag_part);
1095
1096            assert_eq!(*z.raw_real_part(), 0.0);
1097            assert_eq!(*z.raw_imag_part(), 9.0);
1098            assert!(z.is_pure_imaginary());
1099        }
1100
1101        #[test]
1102        fn test_try_new_pure_real_valid() {
1103            let z = ComplexNative32StrictFinite::try_new_pure_real(5.0).unwrap();
1104            assert_eq!(*z.raw_real_part(), 5.0);
1105            assert_eq!(*z.raw_imag_part(), 0.0);
1106        }
1107
1108        #[test]
1109        fn test_try_new_pure_real_invalid() {
1110            // NaN should fail
1111            let result = ComplexNative32StrictFinite::try_new_pure_real(f32::NAN);
1112            assert!(result.is_err());
1113
1114            // Infinity should fail
1115            let result = ComplexNative32StrictFinite::try_new_pure_real(f32::INFINITY);
1116            assert!(result.is_err());
1117        }
1118
1119        #[test]
1120        fn test_try_new_pure_imaginary_valid() {
1121            let z = ComplexNative32StrictFinite::try_new_pure_imaginary(8.0).unwrap();
1122            assert_eq!(*z.raw_real_part(), 0.0);
1123            assert_eq!(*z.raw_imag_part(), 8.0);
1124        }
1125
1126        #[test]
1127        fn test_try_new_pure_imaginary_invalid() {
1128            // NaN should fail
1129            let result = ComplexNative32StrictFinite::try_new_pure_imaginary(f32::NAN);
1130            assert!(result.is_err());
1131
1132            // Infinity should fail
1133            let result = ComplexNative32StrictFinite::try_new_pure_imaginary(f32::NEG_INFINITY);
1134            assert!(result.is_err());
1135        }
1136
1137        #[test]
1138        fn test_into_parts_basic() {
1139            let z = ComplexNative32StrictFinite::try_new_complex(3.0, 4.0).unwrap();
1140
1141            // Consume z and extract its parts
1142            let (real, imag) = z.into_parts();
1143
1144            assert_eq!(*real.as_ref(), 3.0);
1145            assert_eq!(*imag.as_ref(), 4.0);
1146        }
1147
1148        #[test]
1149        fn test_into_parts_zero() {
1150            let z = ComplexNative32StrictFinite::try_new_complex(0.0, 0.0).unwrap();
1151            let (real, imag) = z.into_parts();
1152
1153            assert_eq!(*real.as_ref(), 0.0);
1154            assert_eq!(*imag.as_ref(), 0.0);
1155        }
1156
1157        #[test]
1158        fn test_into_parts_pure_real() {
1159            let z = ComplexNative32StrictFinite::try_new_complex(7.5, 0.0).unwrap();
1160            let (real, imag) = z.into_parts();
1161
1162            assert_eq!(*real.as_ref(), 7.5);
1163            assert_eq!(*imag.as_ref(), 0.0);
1164        }
1165
1166        #[test]
1167        fn test_into_parts_pure_imaginary() {
1168            let z = ComplexNative32StrictFinite::try_new_complex(0.0, -9.2).unwrap();
1169            let (real, imag) = z.into_parts();
1170
1171            assert_eq!(*real.as_ref(), 0.0);
1172            assert_eq!(*imag.as_ref(), -9.2);
1173        }
1174
1175        #[test]
1176        fn test_into_parts_negative() {
1177            let z = ComplexNative32StrictFinite::try_new_complex(-5.5, -3.3).unwrap();
1178            let (real, imag) = z.into_parts();
1179
1180            assert_eq!(*real.as_ref(), -5.5);
1181            assert_eq!(*imag.as_ref(), -3.3);
1182        }
1183
1184        #[test]
1185        fn test_into_parts_parts_are_independent() {
1186            let z = ComplexNative32StrictFinite::try_new_complex(10.0, 20.0).unwrap();
1187            let (real, imag) = z.into_parts();
1188
1189            // Parts should be usable independently
1190            let two = RealNative32StrictFinite::try_new(2.0).unwrap();
1191            let real_doubled = real * two;
1192            let imag_halved = imag / two;
1193
1194            assert_eq!(*real_doubled.as_ref(), 20.0);
1195            assert_eq!(*imag_halved.as_ref(), 10.0);
1196        }
1197    }
1198
1199    #[cfg(feature = "rug")]
1200    mod rug53 {
1201        use super::*;
1202        use crate::backends::rug::validated::{ComplexRugStrictFinite, RealRugStrictFinite};
1203        use rug::Float;
1204        use std::f64::consts::*;
1205        use try_create::TryNew;
1206
1207        const PRECISION: u32 = 53;
1208
1209        fn rug_complex(re: f64, im: f64) -> rug::Complex {
1210            rug::Complex::with_val(
1211                PRECISION,
1212                (
1213                    Float::with_val(PRECISION, re),
1214                    Float::with_val(PRECISION, im),
1215                ),
1216            )
1217        }
1218
1219        fn new_complex_rug(re: f64, im: f64) -> ComplexRugStrictFinite<PRECISION> {
1220            ComplexRugStrictFinite::<PRECISION>::try_new(rug_complex(re, im))
1221                .expect("valid test value for complex rug")
1222        }
1223
1224        #[allow(clippy::result_large_err)]
1225        fn try_new_complex_rug(
1226            re: f64,
1227            im: f64,
1228        ) -> Result<
1229            ComplexRugStrictFinite<PRECISION>,
1230            <rug::Complex as RawScalarValidate>::ValidationError,
1231        > {
1232            ComplexRugStrictFinite::<PRECISION>::try_new(rug_complex(re, im))
1233        }
1234
1235        fn real_rug(val: f64) -> RealRugStrictFinite<PRECISION> {
1236            RealRugStrictFinite::<PRECISION>::try_new(Float::with_val(PRECISION, val))
1237                .expect("valid test value for real rug")
1238        }
1239
1240        #[test]
1241        fn conjugate() {
1242            let z = new_complex_rug(1.0, -2.0);
1243            let expected_conjugate = new_complex_rug(1.0, 2.0);
1244            assert_eq!(z.conjugate(), expected_conjugate);
1245
1246            let z = new_complex_rug(-3.0, 4.0);
1247            let expected_conjugate = new_complex_rug(-3.0, -4.0);
1248            assert_eq!(z.conjugate(), expected_conjugate);
1249
1250            // Special cases
1251            // Zero
1252            let z_zero = new_complex_rug(0.0, 0.0);
1253            assert_eq!(z_zero.conjugate(), new_complex_rug(0.0, 0.0));
1254            // Purely real
1255            let z_real = new_complex_rug(5.0, 0.0);
1256            assert_eq!(z_real.conjugate(), new_complex_rug(5.0, 0.0));
1257            // Purely imaginary
1258            let z_imag = new_complex_rug(0.0, 5.0);
1259            assert_eq!(z_imag.conjugate(), new_complex_rug(0.0, -5.0));
1260            let z_neg_imag = new_complex_rug(0.0, -5.0);
1261            assert_eq!(z_neg_imag.conjugate(), new_complex_rug(0.0, 5.0));
1262        }
1263
1264        #[test]
1265        fn arg() {
1266            // Existing test (Q1)
1267            let z1 = new_complex_rug(1.0, 1.0);
1268            assert_eq!(z1.arg(), real_rug(FRAC_PI_4));
1269
1270            // Axes
1271            let z_pos_real = new_complex_rug(1.0, 0.0);
1272            assert_eq!(z_pos_real.arg(), real_rug(0.0));
1273            let z_neg_real = new_complex_rug(-1.0, 0.0);
1274            assert_eq!(z_neg_real.arg(), real_rug(PI));
1275            let z_pos_imag = new_complex_rug(0.0, 1.0);
1276            assert_eq!(z_pos_imag.arg(), real_rug(FRAC_PI_2));
1277            let z_neg_imag = new_complex_rug(0.0, -1.0);
1278            assert_eq!(z_neg_imag.arg(), real_rug(-FRAC_PI_2));
1279
1280            // Quadrants
1281            let z2 = new_complex_rug(-1.0, 1.0); // Q2
1282            assert_eq!(z2.arg(), real_rug(3.0 * FRAC_PI_4));
1283            let z3 = new_complex_rug(-1.0, -1.0); // Q3
1284            assert_eq!(z3.arg(), real_rug(-3.0 * FRAC_PI_4));
1285            let z4 = new_complex_rug(1.0, -1.0); // Q4
1286            assert_eq!(z4.arg(), real_rug(-FRAC_PI_4));
1287
1288            // Test try_arg for zero (already in existing tests)
1289            let zero = new_complex_rug(0.0, 0.0);
1290            assert!(matches!(
1291                zero.try_arg(),
1292                Err(ArgErrors::Input(ArgInputErrors::Zero { .. }))
1293            ));
1294        }
1295
1296        #[test]
1297        #[cfg(not(debug_assertions))]
1298        fn try_arg_zero_invalid() {
1299            // zero
1300            let z_zero = new_complex_rug(0.0, 0.0);
1301            assert!(matches!(
1302                z_zero.try_arg(),
1303                Err(ArgErrors::Input(ArgInputErrors::Zero { .. }))
1304            ));
1305        }
1306
1307        #[test]
1308        #[should_panic(
1309            expected = "Error calling ComplexValidated::try_arg() inside ComplexValidated::arg()"
1310        )]
1311        fn arg_panics_on_zero() {
1312            let zero = new_complex_rug(0.0, 0.0);
1313            let _ = zero.arg();
1314        }
1315
1316        #[test]
1317        fn err_on_try_new_real_part_infinity_debug() {
1318            let err = try_new_complex_rug(f64::INFINITY, 1.0);
1319            assert!(matches!(
1320                err,
1321                Err(ErrorsValidationRawComplex::InvalidRealPart { .. })
1322            ));
1323        }
1324
1325        #[test]
1326        fn err_on_try_new_real_part_nan_debug() {
1327            let err = try_new_complex_rug(f64::NAN, 1.0);
1328            assert!(matches!(
1329                err,
1330                Err(ErrorsValidationRawComplex::InvalidRealPart { .. })
1331            ));
1332        }
1333
1334        #[test]
1335        fn err_on_try_new_imaginary_part_infinity_debug() {
1336            let err = try_new_complex_rug(1.0, f64::INFINITY);
1337            assert!(matches!(
1338                err,
1339                Err(ErrorsValidationRawComplex::InvalidImaginaryPart { .. })
1340            ));
1341        }
1342
1343        #[test]
1344        fn err_on_try_new_imaginary_part_nan_debug() {
1345            let err = try_new_complex_rug(1.0, f64::NAN);
1346            assert!(matches!(
1347                err,
1348                Err(ErrorsValidationRawComplex::InvalidImaginaryPart { .. })
1349            ));
1350        }
1351
1352        #[test]
1353        #[cfg(debug_assertions)]
1354        #[should_panic(expected = "valid test value for complex rug")]
1355        fn panics_on_new_real_part_nan_debug() {
1356            let _nan = new_complex_rug(f64::NAN, 1.0);
1357        }
1358
1359        #[test]
1360        #[cfg(debug_assertions)]
1361        #[should_panic(expected = "valid test value for complex rug")]
1362        fn panics_on_new_real_part_infinity_debug() {
1363            let _inf = new_complex_rug(f64::INFINITY, 1.0);
1364        }
1365
1366        #[test]
1367        #[cfg(debug_assertions)]
1368        #[should_panic(expected = "valid test value for complex rug")]
1369        fn panics_on_new_imaginary_part_nan_debug() {
1370            let _nan = new_complex_rug(1.0, f64::NAN);
1371        }
1372
1373        #[test]
1374        #[cfg(debug_assertions)]
1375        #[should_panic(expected = "valid test value for complex rug")]
1376        fn panics_on_new_imaginary_part_infinity_debug() {
1377            let _inf = new_complex_rug(1.0, f64::INFINITY);
1378        }
1379
1380        // NOTE: arg_behavior_release test removed because StrictFinite types
1381        // reject infinity values by design, even in release mode. The validation
1382        // happens in try_new(), not in new_unchecked().
1383
1384        #[test]
1385        fn test_into_parts_basic() {
1386            use crate::ComplexScalar;
1387
1388            let z = new_complex_rug(3.0, 4.0);
1389            let (real, imag) = z.into_parts();
1390
1391            assert_eq!(*real.as_ref(), 3.0);
1392            assert_eq!(*imag.as_ref(), 4.0);
1393        }
1394
1395        #[test]
1396        fn test_into_parts_zero() {
1397            use crate::ComplexScalar;
1398
1399            let z = new_complex_rug(0.0, 0.0);
1400            let (real, imag) = z.into_parts();
1401
1402            assert_eq!(*real.as_ref(), 0.0);
1403            assert_eq!(*imag.as_ref(), 0.0);
1404        }
1405
1406        #[test]
1407        fn test_into_parts_pure_real() {
1408            use crate::ComplexScalar;
1409
1410            let z = new_complex_rug(7.5, 0.0);
1411            let (real, imag) = z.into_parts();
1412
1413            assert_eq!(*real.as_ref(), 7.5);
1414            assert_eq!(*imag.as_ref(), 0.0);
1415        }
1416
1417        #[test]
1418        fn test_into_parts_pure_imaginary() {
1419            use crate::ComplexScalar;
1420
1421            let z = new_complex_rug(0.0, -9.2);
1422            let (real, imag) = z.into_parts();
1423
1424            assert_eq!(*real.as_ref(), 0.0);
1425            assert_eq!(*imag.as_ref(), -9.2);
1426        }
1427
1428        #[test]
1429        fn test_into_parts_negative() {
1430            use crate::ComplexScalar;
1431
1432            let z = new_complex_rug(-5.5, -3.3);
1433            let (real, imag) = z.into_parts();
1434
1435            assert_eq!(*real.as_ref(), -5.5);
1436            assert_eq!(*imag.as_ref(), -3.3);
1437        }
1438
1439        #[test]
1440        fn test_into_parts_parts_are_independent() {
1441            use crate::ComplexScalar;
1442
1443            let z = new_complex_rug(10.0, 20.0);
1444            let (real, imag) = z.into_parts();
1445
1446            // Parts should be usable independently
1447            let two = real_rug(2.0);
1448            let real_doubled = real * two;
1449            let imag_halved = imag / real_rug(2.0);
1450
1451            assert_eq!(*real_doubled.as_ref(), 20.0);
1452            assert_eq!(*imag_halved.as_ref(), 10.0);
1453        }
1454    }
1455}
1456//--------------------------------------------------------------------------------------------------