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