#![deny(rustdoc::broken_intra_doc_links)]
use crate::{
FpScalar, RealScalar,
core::{errors::capture_backtrace, policies::StrictFinitePolicy},
functions::FunctionErrors,
kernels::{RawComplexTrait, RawRealTrait, RawScalarTrait},
};
use num::{Complex, Zero};
use std::backtrace::Backtrace;
use thiserror::Error;
use try_create::{IntoInner, ValidationPolicy};
pub trait ComplexScalarConstructors: FpScalar<InnerType = Self::RawComplex> {
type RawComplex: RawComplexTrait<RawReal = <Self::RealType as RealScalar>::RawReal>;
#[must_use = "this `Result` may contain an error that should be handled"]
fn try_new_complex(
real: <Self::RawComplex as RawComplexTrait>::RawReal,
imag: <Self::RawComplex as RawComplexTrait>::RawReal,
) -> Result<Self, <Self::RawComplex as RawScalarTrait>::ValidationErrors>;
fn new_complex(real: Self::RealType, imag: Self::RealType) -> Self {
Self::try_new_complex(real.into_inner(), imag.into_inner()).unwrap()
}
#[must_use = "this `Result` may contain an error that should be handled"]
fn try_new_pure_real(
real_part: <Self::RawComplex as RawComplexTrait>::RawReal,
) -> Result<Self, <Self::RawComplex as RawScalarTrait>::ValidationErrors> {
let zero = <Self::RawComplex as RawComplexTrait>::RawReal::raw_zero(real_part.precision());
Self::try_new_complex(real_part, zero)
}
#[must_use = "this `Result` may contain an error that should be handled"]
fn try_new_pure_imaginary(
imag_part: <Self::RawComplex as RawComplexTrait>::RawReal,
) -> Result<Self, <Self::RawComplex as RawScalarTrait>::ValidationErrors> {
let zero = <Self::RawComplex as RawComplexTrait>::RawReal::raw_zero(imag_part.precision());
Self::try_new_complex(zero, imag_part)
}
fn new_pure_real(real_part: Self::RealType) -> Self {
Self::try_new_pure_real(real_part.into_inner()).unwrap()
}
fn new_pure_imaginary(imag_part: Self::RealType) -> Self {
Self::try_new_pure_imaginary(imag_part.into_inner()).unwrap()
}
}
pub trait ComplexScalarGetParts: ComplexScalarConstructors {
fn real_part(&self) -> Self::RealType;
fn imag_part(&self) -> Self::RealType;
fn raw_real_part(&self) -> &<Self::RealType as RealScalar>::RawReal;
fn raw_imag_part(&self) -> &<Self::RealType as RealScalar>::RawReal;
fn is_pure_real(&self) -> bool {
self.raw_imag_part().is_zero()
}
fn is_pure_imaginary(&self) -> bool {
self.raw_real_part().is_zero() && !self.raw_imag_part().is_zero()
}
}
pub trait ComplexScalarSetParts: ComplexScalarGetParts {
fn set_real_part(&mut self, real_part: Self::RealType);
fn set_imaginary_part(&mut self, imag_part: Self::RealType);
fn with_real_part(mut self, real_part: Self::RealType) -> Self {
self.set_real_part(real_part);
self
}
fn with_imaginary_part(mut self, imag_part: Self::RealType) -> Self {
self.set_imaginary_part(imag_part);
self
}
}
pub trait ComplexScalarMutateParts: ComplexScalarSetParts {
fn add_to_real_part(&mut self, c: &Self::RealType);
fn add_to_imaginary_part(&mut self, c: &Self::RealType);
fn multiply_real_part(&mut self, c: &Self::RealType);
fn multiply_imaginary_part(&mut self, c: &Self::RealType);
}
pub trait Conjugate: Sized {
fn conjugate(self) -> Self;
}
impl Conjugate for Complex<f64> {
#[inline(always)]
fn conjugate(self) -> Self {
self.conj()
}
}
#[derive(Debug, Error)]
pub enum ArgInputErrors<RawComplex: RawComplexTrait> {
#[error("the input complex number is invalid according to validation policy")]
ValidationError {
#[source]
#[backtrace]
source: <RawComplex as RawScalarTrait>::ValidationErrors,
},
#[error("the input value is zero!")]
Zero {
backtrace: Backtrace,
},
}
pub type ArgErrors<RawComplex> = FunctionErrors<
ArgInputErrors<RawComplex>,
<<RawComplex as RawComplexTrait>::RawReal as RawScalarTrait>::ValidationErrors,
>;
pub trait Arg: Sized {
type Output: Sized;
type Error: std::error::Error;
#[must_use = "this `Result` may contain an error that should be handled"]
fn try_arg(self) -> Result<Self::Output, Self::Error>;
fn arg(self) -> Self::Output;
}
impl Arg for Complex<f64> {
type Output = f64;
type Error = ArgErrors<Complex<f64>>;
#[inline(always)]
fn try_arg(self) -> Result<Self::Output, Self::Error> {
StrictFinitePolicy::<Complex<f64>, 53>::validate(self)
.map_err(|e| Self::Error::Input {
source: ArgInputErrors::ValidationError { source: e },
})
.and_then(|v: Complex<f64>| {
if <Complex<f64> as Zero>::is_zero(&v) {
Err(Self::Error::Input {
source: ArgInputErrors::Zero {
backtrace: capture_backtrace(),
},
})
} else {
StrictFinitePolicy::<f64, 53>::validate(Complex::<f64>::arg(v))
.map_err(|e| Self::Error::Output { source: e })
}
})
}
#[inline(always)]
fn arg(self) -> f64 {
#[cfg(debug_assertions)]
{
self.try_arg()
.expect("Error calling Arg::try_arg() inside Arg::arg() debug mode.")
}
#[cfg(not(debug_assertions))]
{
Complex::<f64>::arg(self)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ComplexScalar, core::errors::ErrorsValidationRawComplex};
use num::Complex;
use try_create::TryNew;
mod native64 {
use super::*;
use crate::backends::native64::validated::{
ComplexNative64StrictFinite, RealNative64StrictFinite,
};
use std::f64::consts::*;
#[test]
fn conjugate() {
let z = Complex::new(1.0, -2.0);
let expected_conjugate = Complex::new(1.0, 2.0);
assert_eq!(z.conjugate(), expected_conjugate);
let z = Complex::new(-3.0, 4.0);
let expected_conjugate = Complex::new(-3.0, -4.0);
assert_eq!(z.conjugate(), expected_conjugate);
let z_zero = Complex::new(0.0, 0.0);
assert_eq!(z_zero.conjugate(), Complex::new(0.0, 0.0));
let z_real = Complex::new(5.0, 0.0);
assert_eq!(z_real.conjugate(), Complex::new(5.0, 0.0));
let z_imag = Complex::new(0.0, 5.0);
assert_eq!(z_imag.conjugate(), Complex::new(0.0, -5.0));
let z_neg_imag = Complex::new(0.0, -5.0);
assert_eq!(z_neg_imag.conjugate(), Complex::new(0.0, 5.0));
}
#[test]
fn arg() {
let z = Complex::new(1.0, 1.0);
let expected_arg = std::f64::consts::FRAC_PI_4; assert_eq!(z.arg(), expected_arg);
let z_pos_real = Complex::new(1.0, 0.0);
assert_eq!(z_pos_real.arg(), 0.0);
let z_neg_real = Complex::new(-1.0, 0.0);
assert_eq!(z_neg_real.arg(), PI);
let z_pos_imag = Complex::new(0.0, 1.0);
assert_eq!(z_pos_imag.arg(), FRAC_PI_2);
let z_neg_imag = Complex::new(0.0, -1.0);
assert_eq!(z_neg_imag.arg(), -FRAC_PI_2);
let z2 = Complex::new(-1.0, 1.0); assert_eq!(z2.arg(), 3.0 * FRAC_PI_4);
let z3 = Complex::new(-1.0, -1.0); assert_eq!(z3.arg(), -3.0 * FRAC_PI_4);
let z4 = Complex::new(1.0, -1.0); assert_eq!(z4.arg(), -FRAC_PI_4);
let zero = Complex::new(0.0, 0.0);
assert!(matches!(
zero.try_arg(),
Err(ArgErrors::<Complex<f64>>::Input {
source: ArgInputErrors::Zero { .. }
})
));
}
#[test]
fn try_arg_invalid() {
let z_nan_re = Complex::new(f64::NAN, 1.0);
assert!(matches!(
z_nan_re.try_arg(),
Err(ArgErrors::<Complex<f64>>::Input {
source: ArgInputErrors::ValidationError {
source: ErrorsValidationRawComplex::InvalidRealPart { .. }
}
})
));
let z_nan_im = Complex::new(1.0, f64::NAN);
assert!(matches!(
z_nan_im.try_arg(),
Err(ArgErrors::<Complex<f64>>::Input {
source: ArgInputErrors::ValidationError {
source: ErrorsValidationRawComplex::InvalidImaginaryPart { .. }
}
})
));
let z_inf_re = Complex::new(f64::INFINITY, 1.0);
assert!(matches!(
z_inf_re.try_arg(),
Err(ArgErrors::<Complex<f64>>::Input {
source: ArgInputErrors::ValidationError {
source: ErrorsValidationRawComplex::InvalidRealPart { .. }
}
})
));
let z_inf_im = Complex::new(1.0, f64::INFINITY);
assert!(matches!(
z_inf_im.try_arg(),
Err(ArgErrors::<Complex<f64>>::Input {
source: ArgInputErrors::ValidationError {
source: ErrorsValidationRawComplex::InvalidImaginaryPart { .. }
}
})
));
}
#[test]
#[cfg(debug_assertions)]
#[should_panic]
fn arg_panics_on_zero_debug() {
let zero = Complex::new(0.0, 0.0);
let _ = Arg::arg(zero);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic]
fn arg_panics_on_nan_debug() {
let nan = Complex::new(f64::NAN, 1.0);
let _ = Arg::arg(nan);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic]
fn arg_panics_on_inf_debug() {
let inf = Complex::new(f64::INFINITY, 1.0);
let _ = Arg::arg(inf);
}
#[test]
#[cfg(not(debug_assertions))]
fn arg_behavior_release() {
let zero = Complex::new(0.0, 0.0);
assert_eq!(Arg::arg(zero), 0.0);
let nan_re = Complex::new(f64::NAN, 1.0);
assert!(Arg::arg(nan_re).is_nan());
let inf_re = Complex::new(f64::INFINITY, 0.0); assert_eq!(Arg::arg(inf_re), 0.0);
let inf_im = Complex::new(0.0, f64::INFINITY); assert_eq!(Arg::arg(inf_im), FRAC_PI_2);
let inf_both = Complex::new(f64::INFINITY, f64::INFINITY); assert_eq!(Arg::arg(inf_both), FRAC_PI_4);
}
#[test]
fn test_is_pure_real() {
let z_real = ComplexNative64StrictFinite::try_new_complex(5.0, 0.0).unwrap();
assert!(z_real.is_pure_real());
assert!(!z_real.is_pure_imaginary());
let z_zero = ComplexNative64StrictFinite::try_new_complex(0.0, 0.0).unwrap();
assert!(z_zero.is_pure_real());
assert!(!z_zero.is_pure_imaginary());
let z_complex = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
assert!(!z_complex.is_pure_real());
assert!(!z_complex.is_pure_imaginary());
let z_imag = ComplexNative64StrictFinite::try_new_complex(0.0, 5.0).unwrap();
assert!(!z_imag.is_pure_real());
assert!(z_imag.is_pure_imaginary());
}
#[test]
fn test_is_pure_imaginary() {
let z_imag = ComplexNative64StrictFinite::try_new_complex(0.0, 7.0).unwrap();
assert!(z_imag.is_pure_imaginary());
assert!(!z_imag.is_pure_real());
let z_neg_imag = ComplexNative64StrictFinite::try_new_complex(0.0, -3.0).unwrap();
assert!(z_neg_imag.is_pure_imaginary());
let z_zero = ComplexNative64StrictFinite::try_new_complex(0.0, 0.0).unwrap();
assert!(!z_zero.is_pure_imaginary());
assert!(z_zero.is_pure_real());
}
#[test]
fn test_with_real_part() {
let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
let new_real = RealNative64StrictFinite::try_new(10.0).unwrap();
let z_modified = z.with_real_part(new_real);
assert_eq!(*z_modified.raw_real_part(), 10.0);
assert_eq!(*z_modified.raw_imag_part(), 4.0);
}
#[test]
fn test_with_imaginary_part() {
let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
let new_imag = RealNative64StrictFinite::try_new(20.0).unwrap();
let z_modified = z.with_imaginary_part(new_imag);
assert_eq!(*z_modified.raw_real_part(), 3.0);
assert_eq!(*z_modified.raw_imag_part(), 20.0);
}
#[test]
fn test_new_pure_real() {
let real_part = RealNative64StrictFinite::try_new(7.0).unwrap();
let z = ComplexNative64StrictFinite::new_pure_real(real_part);
assert_eq!(*z.raw_real_part(), 7.0);
assert_eq!(*z.raw_imag_part(), 0.0);
assert!(z.is_pure_real());
}
#[test]
fn test_new_pure_imaginary() {
let imag_part = RealNative64StrictFinite::try_new(9.0).unwrap();
let z = ComplexNative64StrictFinite::new_pure_imaginary(imag_part);
assert_eq!(*z.raw_real_part(), 0.0);
assert_eq!(*z.raw_imag_part(), 9.0);
assert!(z.is_pure_imaginary());
}
#[test]
fn test_try_new_pure_real_valid() {
let z = ComplexNative64StrictFinite::try_new_pure_real(5.0).unwrap();
assert_eq!(*z.raw_real_part(), 5.0);
assert_eq!(*z.raw_imag_part(), 0.0);
}
#[test]
fn test_try_new_pure_real_invalid() {
let result = ComplexNative64StrictFinite::try_new_pure_real(f64::NAN);
assert!(result.is_err());
let result = ComplexNative64StrictFinite::try_new_pure_real(f64::INFINITY);
assert!(result.is_err());
}
#[test]
fn test_try_new_pure_imaginary_valid() {
let z = ComplexNative64StrictFinite::try_new_pure_imaginary(8.0).unwrap();
assert_eq!(*z.raw_real_part(), 0.0);
assert_eq!(*z.raw_imag_part(), 8.0);
}
#[test]
fn test_try_new_pure_imaginary_invalid() {
let result = ComplexNative64StrictFinite::try_new_pure_imaginary(f64::NAN);
assert!(result.is_err());
let result = ComplexNative64StrictFinite::try_new_pure_imaginary(f64::NEG_INFINITY);
assert!(result.is_err());
}
#[test]
fn test_into_parts_basic() {
let z = ComplexNative64StrictFinite::try_new_complex(3.0, 4.0).unwrap();
let (real, imag) = z.into_parts();
assert_eq!(*real.as_ref(), 3.0);
assert_eq!(*imag.as_ref(), 4.0);
}
#[test]
fn test_into_parts_zero() {
let z = ComplexNative64StrictFinite::try_new_complex(0.0, 0.0).unwrap();
let (real, imag) = z.into_parts();
assert_eq!(*real.as_ref(), 0.0);
assert_eq!(*imag.as_ref(), 0.0);
}
#[test]
fn test_into_parts_pure_real() {
let z = ComplexNative64StrictFinite::try_new_complex(7.5, 0.0).unwrap();
let (real, imag) = z.into_parts();
assert_eq!(*real.as_ref(), 7.5);
assert_eq!(*imag.as_ref(), 0.0);
}
#[test]
fn test_into_parts_pure_imaginary() {
let z = ComplexNative64StrictFinite::try_new_complex(0.0, -9.2).unwrap();
let (real, imag) = z.into_parts();
assert_eq!(*real.as_ref(), 0.0);
assert_eq!(*imag.as_ref(), -9.2);
}
#[test]
fn test_into_parts_negative() {
let z = ComplexNative64StrictFinite::try_new_complex(-5.5, -3.3).unwrap();
let (real, imag) = z.into_parts();
assert_eq!(*real.as_ref(), -5.5);
assert_eq!(*imag.as_ref(), -3.3);
}
#[test]
fn test_into_parts_parts_are_independent() {
let z = ComplexNative64StrictFinite::try_new_complex(10.0, 20.0).unwrap();
let (real, imag) = z.into_parts();
let two = RealNative64StrictFinite::try_new(2.0).unwrap();
let real_doubled = real * two;
let imag_halved = imag / two;
assert_eq!(*real_doubled.as_ref(), 20.0);
assert_eq!(*imag_halved.as_ref(), 10.0);
}
}
#[cfg(feature = "rug")]
mod rug53 {
use super::*;
use crate::backends::rug::validated::{ComplexRugStrictFinite, RealRugStrictFinite};
use rug::Float;
use std::f64::consts::*;
use try_create::TryNew;
const PRECISION: u32 = 53;
fn rug_complex(re: f64, im: f64) -> rug::Complex {
rug::Complex::with_val(
PRECISION,
(
Float::with_val(PRECISION, re),
Float::with_val(PRECISION, im),
),
)
}
fn new_complex_rug(re: f64, im: f64) -> ComplexRugStrictFinite<PRECISION> {
ComplexRugStrictFinite::<PRECISION>::try_new(rug_complex(re, im))
.expect("valid test value for complex rug")
}
#[allow(clippy::result_large_err)]
fn try_new_complex_rug(
re: f64,
im: f64,
) -> Result<
ComplexRugStrictFinite<PRECISION>,
<rug::Complex as RawScalarTrait>::ValidationErrors,
> {
ComplexRugStrictFinite::<PRECISION>::try_new(rug_complex(re, im))
}
fn real_rug(val: f64) -> RealRugStrictFinite<PRECISION> {
RealRugStrictFinite::<PRECISION>::try_new(Float::with_val(PRECISION, val))
.expect("valid test value for real rug")
}
#[test]
fn conjugate() {
let z = new_complex_rug(1.0, -2.0);
let expected_conjugate = new_complex_rug(1.0, 2.0);
assert_eq!(z.conjugate(), expected_conjugate);
let z = new_complex_rug(-3.0, 4.0);
let expected_conjugate = new_complex_rug(-3.0, -4.0);
assert_eq!(z.conjugate(), expected_conjugate);
let z_zero = new_complex_rug(0.0, 0.0);
assert_eq!(z_zero.conjugate(), new_complex_rug(0.0, 0.0));
let z_real = new_complex_rug(5.0, 0.0);
assert_eq!(z_real.conjugate(), new_complex_rug(5.0, 0.0));
let z_imag = new_complex_rug(0.0, 5.0);
assert_eq!(z_imag.conjugate(), new_complex_rug(0.0, -5.0));
let z_neg_imag = new_complex_rug(0.0, -5.0);
assert_eq!(z_neg_imag.conjugate(), new_complex_rug(0.0, 5.0));
}
#[test]
fn arg() {
let z1 = new_complex_rug(1.0, 1.0);
assert_eq!(z1.arg(), real_rug(FRAC_PI_4));
let z_pos_real = new_complex_rug(1.0, 0.0);
assert_eq!(z_pos_real.arg(), real_rug(0.0));
let z_neg_real = new_complex_rug(-1.0, 0.0);
assert_eq!(z_neg_real.arg(), real_rug(PI));
let z_pos_imag = new_complex_rug(0.0, 1.0);
assert_eq!(z_pos_imag.arg(), real_rug(FRAC_PI_2));
let z_neg_imag = new_complex_rug(0.0, -1.0);
assert_eq!(z_neg_imag.arg(), real_rug(-FRAC_PI_2));
let z2 = new_complex_rug(-1.0, 1.0); assert_eq!(z2.arg(), real_rug(3.0 * FRAC_PI_4));
let z3 = new_complex_rug(-1.0, -1.0); assert_eq!(z3.arg(), real_rug(-3.0 * FRAC_PI_4));
let z4 = new_complex_rug(1.0, -1.0); assert_eq!(z4.arg(), real_rug(-FRAC_PI_4));
let zero = new_complex_rug(0.0, 0.0);
assert!(matches!(
zero.try_arg(),
Err(ArgErrors::Input {
source: ArgInputErrors::Zero { .. }
})
));
}
#[test]
#[cfg(not(debug_assertions))]
fn try_arg_zero_invalid() {
let z_zero = new_complex_rug(0.0, 0.0);
assert!(matches!(
z_zero.try_arg(),
Err(ArgErrors::Input {
source: ArgInputErrors::Zero { .. }
})
));
}
#[test]
#[should_panic(
expected = "Error calling ComplexValidated::try_arg() inside ComplexValidated::arg()"
)]
fn arg_panics_on_zero() {
let zero = new_complex_rug(0.0, 0.0);
let _ = zero.arg();
}
#[test]
fn err_on_try_new_real_part_infinity_debug() {
let err = try_new_complex_rug(f64::INFINITY, 1.0);
assert!(matches!(
err,
Err(ErrorsValidationRawComplex::InvalidRealPart { .. })
));
}
#[test]
fn err_on_try_new_real_part_nan_debug() {
let err = try_new_complex_rug(f64::NAN, 1.0);
assert!(matches!(
err,
Err(ErrorsValidationRawComplex::InvalidRealPart { .. })
));
}
#[test]
fn err_on_try_new_imaginary_part_infinity_debug() {
let err = try_new_complex_rug(1.0, f64::INFINITY);
assert!(matches!(
err,
Err(ErrorsValidationRawComplex::InvalidImaginaryPart { .. })
));
}
#[test]
fn err_on_try_new_imaginary_part_nan_debug() {
let err = try_new_complex_rug(1.0, f64::NAN);
assert!(matches!(
err,
Err(ErrorsValidationRawComplex::InvalidImaginaryPart { .. })
));
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "valid test value for complex rug")]
fn panics_on_new_real_part_nan_debug() {
let _nan = new_complex_rug(f64::NAN, 1.0);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "valid test value for complex rug")]
fn panics_on_new_real_part_infinity_debug() {
let _inf = new_complex_rug(f64::INFINITY, 1.0);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "valid test value for complex rug")]
fn panics_on_new_imaginary_part_nan_debug() {
let _nan = new_complex_rug(1.0, f64::NAN);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "valid test value for complex rug")]
fn panics_on_new_imaginary_part_infinity_debug() {
let _inf = new_complex_rug(1.0, f64::INFINITY);
}
#[test]
fn test_into_parts_basic() {
use crate::ComplexScalar;
let z = new_complex_rug(3.0, 4.0);
let (real, imag) = z.into_parts();
assert_eq!(*real.as_ref(), 3.0);
assert_eq!(*imag.as_ref(), 4.0);
}
#[test]
fn test_into_parts_zero() {
use crate::ComplexScalar;
let z = new_complex_rug(0.0, 0.0);
let (real, imag) = z.into_parts();
assert_eq!(*real.as_ref(), 0.0);
assert_eq!(*imag.as_ref(), 0.0);
}
#[test]
fn test_into_parts_pure_real() {
use crate::ComplexScalar;
let z = new_complex_rug(7.5, 0.0);
let (real, imag) = z.into_parts();
assert_eq!(*real.as_ref(), 7.5);
assert_eq!(*imag.as_ref(), 0.0);
}
#[test]
fn test_into_parts_pure_imaginary() {
use crate::ComplexScalar;
let z = new_complex_rug(0.0, -9.2);
let (real, imag) = z.into_parts();
assert_eq!(*real.as_ref(), 0.0);
assert_eq!(*imag.as_ref(), -9.2);
}
#[test]
fn test_into_parts_negative() {
use crate::ComplexScalar;
let z = new_complex_rug(-5.5, -3.3);
let (real, imag) = z.into_parts();
assert_eq!(*real.as_ref(), -5.5);
assert_eq!(*imag.as_ref(), -3.3);
}
#[test]
fn test_into_parts_parts_are_independent() {
use crate::ComplexScalar;
let z = new_complex_rug(10.0, 20.0);
let (real, imag) = z.into_parts();
let two = real_rug(2.0);
let real_doubled = real * two;
let imag_halved = imag / real_rug(2.0);
assert_eq!(*real_doubled.as_ref(), 20.0);
assert_eq!(*imag_halved.as_ref(), 10.0);
}
}
}