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//--------------------------------------------------------------------------------------------------