num_valid/functions/
complex.rs

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