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