From 469c6e135dc62aa8f22457e448cd559a918398a0 Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Tue, 30 May 2023 21:55:24 +0200 Subject: [PATCH 1/6] Dedicated implementations for scalar dual numbers --- src/dual.rs | 328 +++------------ src/dual_vec.rs | 944 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 10 +- src/linalg.rs | 3 +- tests/nalgebra_api.rs | 8 +- 5 files changed, 1017 insertions(+), 276 deletions(-) create mode 100644 src/dual_vec.rs diff --git a/src/dual.rs b/src/dual.rs index 183f1e9..1fb2345 100644 --- a/src/dual.rs +++ b/src/dual.rs @@ -1,6 +1,5 @@ -use crate::{Derivative, DualNum, DualNumFloat}; +use crate::{DualNum, DualNumFloat}; use approx::{AbsDiffEq, RelativeEq, UlpsEq}; -use nalgebra::allocator::Allocator; use nalgebra::*; use num_traits::{Float, FloatConst, FromPrimitive, Inv, Num, One, Signed, Zero}; use std::convert::Infallible; @@ -12,37 +11,22 @@ use std::ops::{ }; /// A dual number for the calculations of gradients or Jacobians. -#[derive(Clone, Debug)] -pub struct DualVec, F, D: Dim> -where - DefaultAllocator: Allocator, -{ +#[derive(Copy, Clone, Debug)] +pub struct Dual, F> { /// Real part of the dual number pub re: T, /// Derivative part of the dual number - pub eps: Derivative, + pub eps: T, f: PhantomData, } -impl + Copy, F: Copy, const N: usize> Copy for DualVec> {} - -pub type DualVec32 = DualVec; -pub type DualVec64 = DualVec; -pub type DualSVec32 = DualVec>; -pub type DualSVec64 = DualVec>; -pub type DualDVec32 = DualVec; -pub type DualDVec64 = DualVec; -pub type Dual = DualVec; pub type Dual32 = Dual; pub type Dual64 = Dual; -impl, F, D: Dim> DualVec -where - DefaultAllocator: Allocator, -{ +impl, F> Dual { /// Create a new dual number from its fields. #[inline] - pub fn new(re: T, eps: Derivative) -> Self { + pub fn new(re: T, eps: T) -> Self { Self { re, eps, @@ -51,22 +35,11 @@ where } } -impl, F> Dual { - /// Create a new scalar dual number from its fields. - #[inline] - pub fn new_scalar(re: T, eps: T) -> Self { - Self::new(re, Derivative::some(SVector::from_element(eps))) - } -} - -impl + Zero, F, D: Dim> DualVec -where - DefaultAllocator: Allocator, -{ +impl + Zero, F> Dual { /// Create a new dual number from the real part. #[inline] pub fn from_re(re: T) -> Self { - Self::new(re, Derivative::none()) + Self::new(re, T::zero()) } } @@ -80,7 +53,7 @@ impl + One, F> Dual { /// ``` #[inline] pub fn derivative(mut self) -> Self { - self.eps = Derivative::derivative(); + self.eps = T::one(); self } } @@ -105,185 +78,53 @@ where G: FnOnce(Dual) -> Result, E>, { let x = Dual::from_re(x).derivative(); - g(x).map(|r| (r.re, r.eps.unwrap())) -} - -/// Calculate the gradient of a scalar function -/// ``` -/// # use approx::assert_relative_eq; -/// # use num_dual::{gradient, DualNum, DualSVec64}; -/// # use nalgebra::SVector; -/// let v = SVector::from([4.0, 3.0]); -/// let fun = |v: SVector, 2>| (v[0].powi(2) + v[1].powi(2)).sqrt(); -/// let (f, g) = gradient(fun, v); -/// assert_eq!(f, 5.0); -/// assert_relative_eq!(g[0], 0.8); -/// assert_relative_eq!(g[1], 0.6); -/// ``` -/// -/// The variable vector can also be dynamically sized -/// ``` -/// # use approx::assert_relative_eq; -/// # use num_dual::{gradient, DualNum, DualDVec64}; -/// # use nalgebra::DVector; -/// let v = DVector::repeat(4, 2.0); -/// let fun = |v: DVector| v.iter().map(|v| v * v).sum::().sqrt(); -/// let (f, g) = gradient(fun, v); -/// assert_eq!(f, 4.0); -/// assert_relative_eq!(g[0], 0.5); -/// assert_relative_eq!(g[1], 0.5); -/// assert_relative_eq!(g[2], 0.5); -/// assert_relative_eq!(g[3], 0.5); -/// ``` -pub fn gradient, F: DualNumFloat, D: Dim>( - g: G, - x: OVector, -) -> (T, OVector) -where - G: FnOnce(OVector, D>) -> DualVec, - DefaultAllocator: Allocator + Allocator, D>, -{ - try_gradient(|x| Ok::<_, Infallible>(g(x)), x).unwrap() -} - -/// Variant of [gradient] for fallible functions. -pub fn try_gradient, F: DualNumFloat, E, D: Dim>( - g: G, - x: OVector, -) -> Result<(T, OVector), E> -where - G: FnOnce(OVector, D>) -> Result, E>, - DefaultAllocator: Allocator + Allocator, D>, -{ - let mut x = x.map(DualVec::from_re); - let (r, c) = x.shape_generic(); - for (i, xi) in x.iter_mut().enumerate() { - xi.eps = Derivative::derivative_generic(r, c, i); - } - g(x).map(|res| (res.re, res.eps.unwrap_generic(r, c))) -} - -/// Calculate the Jacobian of a vector function. -/// ``` -/// # use num_dual::{jacobian, DualSVec64, DualNum}; -/// # use nalgebra::SVector; -/// let xy = SVector::from([5.0, 3.0, 2.0]); -/// let fun = |xy: SVector, 3>| SVector::from([ -/// xy[0] * xy[1].powi(3) * xy[2], -/// xy[0].powi(2) * xy[1] * xy[2].powi(2) -/// ]); -/// let (f, jac) = jacobian(fun, xy); -/// assert_eq!(f[0], 270.0); // xy³z -/// assert_eq!(f[1], 300.0); // x²yz² -/// assert_eq!(jac[(0,0)], 54.0); // y³z -/// assert_eq!(jac[(0,1)], 270.0); // 3xy²z -/// assert_eq!(jac[(0,2)], 135.0); // xy³ -/// assert_eq!(jac[(1,0)], 120.0); // 2xyz² -/// assert_eq!(jac[(1,1)], 100.0); // x²z² -/// assert_eq!(jac[(1,2)], 300.0); // 2x²yz -/// ``` -pub fn jacobian, F: DualNumFloat, M: Dim, N: Dim>( - g: G, - x: OVector, -) -> (OVector, OMatrix) -where - G: FnOnce(OVector, N>) -> OVector, M>, - DefaultAllocator: Allocator, M> - + Allocator - + Allocator - + Allocator - + Allocator, N> - + Allocator, N> - + Allocator, M>, -{ - try_jacobian(|x| Ok::<_, Infallible>(g(x)), x).unwrap() -} - -/// Variant of [jacobian] for fallible functions. -#[allow(clippy::type_complexity)] -pub fn try_jacobian, F: DualNumFloat, E, M: Dim, N: Dim>( - g: G, - x: OVector, -) -> Result<(OVector, OMatrix), E> -where - G: FnOnce(OVector, N>) -> Result, M>, E>, - DefaultAllocator: Allocator, M> - + Allocator - + Allocator - + Allocator - + Allocator, N> - + Allocator, N> - + Allocator, M>, -{ - let mut x = x.map(DualVec::from_re); - let (r, c) = x.shape_generic(); - for (i, xi) in x.iter_mut().enumerate() { - xi.eps = Derivative::derivative_generic(r, c, i); - } - g(x).map(|res| { - let eps = OMatrix::from_rows( - res.map(|res| res.eps.unwrap_generic(r, c).transpose()) - .as_slice(), - ); - (res.map(|r| r.re), eps) - }) + g(x).map(|r| (r.re, r.eps)) } /* chain rule */ -impl, F: Float, D: Dim> DualVec -where - DefaultAllocator: Allocator, -{ +impl, F: Float> Dual { #[inline] fn chain_rule(&self, f0: T, f1: T) -> Self { - Self::new(f0, &self.eps * f1) + Self::new(f0, self.eps.clone() * f1) } } /* product rule */ -impl<'a, 'b, T: DualNum, F: Float, D: Dim> Mul<&'a DualVec> for &'b DualVec -where - DefaultAllocator: Allocator, -{ - type Output = DualVec; +impl<'a, 'b, T: DualNum, F: Float> Mul<&'a Dual> for &'b Dual { + type Output = Dual; #[inline] - fn mul(self, other: &DualVec) -> Self::Output { - DualVec::new( + fn mul(self, other: &Dual) -> Self::Output { + Dual::new( self.re.clone() * other.re.clone(), - &self.eps * other.re.clone() + &other.eps * self.re.clone(), + self.eps.clone() * other.re.clone() + other.eps.clone() * self.re.clone(), ) } } /* quotient rule */ -impl<'a, 'b, T: DualNum, F: Float, D: Dim> Div<&'a DualVec> for &'b DualVec -where - DefaultAllocator: Allocator, -{ - type Output = DualVec; +impl<'a, 'b, T: DualNum, F: Float> Div<&'a Dual> for &'b Dual { + type Output = Dual; #[inline] - fn div(self, other: &DualVec) -> DualVec { + fn div(self, other: &Dual) -> Dual { let inv = other.re.recip(); - DualVec::new( + Dual::new( self.re.clone() * inv.clone(), - (&self.eps * other.re.clone() - &other.eps * self.re.clone()) * inv.clone() * inv, + (self.eps.clone() * other.re.clone() - other.eps.clone() * self.re.clone()) + * inv.clone() + * inv, ) } } /* string conversions */ -impl, F, D: Dim> fmt::Display for DualVec -where - DefaultAllocator: Allocator, -{ +impl, F> fmt::Display for Dual { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.re)?; - self.eps.fmt(f, "ε") + write!(f, "{} + {}ε", self.re, self.eps) } } -impl_first_derivatives!(DualVec, [eps], [D]); -impl_dual!(DualVec, [eps], [D]); +impl_first_derivatives!(Dual, [eps]); +impl_dual!(Dual, [eps]); /** * The SimdValue trait is for rearranging data into a form more suitable for Simd, @@ -291,11 +132,11 @@ impl_dual!(DualVec, [eps], [D]); * * The primary job of this SimdValue impl is to allow people to use `simba::simd::f32x4` etc, * instead of f32/f64. Those types implement nalgebra::SimdRealField/ComplexField, so they - * behave like scalars. When we use them, we would have `DualVec` etc, with our + * behave like scalars. When we use them, we would have `Dual` etc, with our * F parameter set to `::Element`. We will need to be able to split up that type - * into four of DualVec in order to get out of simd-land. That's what the SimdValue trait is for. + * into four of Dual in order to get out of simd-land. That's what the SimdValue trait is for. * - * Ultimately, someone will have to to implement SimdRealField on DualVec and call the + * Ultimately, someone will have to to implement SimdRealField on Dual and call the * simd_ functions of ``. That's future work for someone who finds * num_dual is not fast enough. * @@ -303,21 +144,20 @@ impl_dual!(DualVec, [eps], [D]); * . * */ -impl nalgebra::SimdValue for DualVec +impl nalgebra::SimdValue for Dual where - DefaultAllocator: Allocator + Allocator, T: DualNum + SimdValue + Scalar, T::Element: DualNum + Scalar, { // Say T = simba::f32x4. T::Element is f32. T::SimdBool is AutoSimd<[bool; 4]>. // AutoSimd<[f32; 4]> stores an actual [f32; 4], i.e. four floats in one slot. - // So our DualVec has 4 * (1+N) floats in it, stored in blocks of + // So our Dual has 4 * (1+N) floats in it, stored in blocks of // four. When we want to do any math on it but ignore its f32x4 storage mode, we need to break - // that type into FOUR of DualVec; then we do math on it, then we bring it back + // that type into FOUR of Dual; then we do math on it, then we bring it back // together. // // Hence this definition of Element: - type Element = DualVec; + type Element = Dual; type SimdBool = T::SimdBool; #[inline] @@ -331,7 +171,7 @@ where // - the real part // - each of the N epsilon parts let re = T::splat(val.re); - let eps = Derivative::splat(val.eps); + let eps = T::splat(val.eps); Self::new(re, eps) } @@ -379,10 +219,7 @@ where /// Comparisons are only made based on the real part. This allows the code to follow the /// same execution path as real-valued code would. -impl + PartialEq, F: Float, D: Dim> PartialEq for DualVec -where - DefaultAllocator: Allocator, -{ +impl + PartialEq, F: Float> PartialEq for Dual { #[inline] fn eq(&self, other: &Self) -> bool { self.re.eq(&other.re) @@ -390,10 +227,7 @@ where } /// Like PartialEq, comparisons are only made based on the real part. This allows the code to follow the /// same execution path as real-valued code would. -impl + PartialOrd, F: Float, D: Dim> PartialOrd for DualVec -where - DefaultAllocator: Allocator, -{ +impl + PartialOrd, F: Float> PartialOrd for Dual { #[inline] fn partial_cmp(&self, other: &Self) -> Option { self.re.partial_cmp(&other.re) @@ -401,11 +235,7 @@ where } /// Like PartialEq, comparisons are only made based on the real part. This allows the code to follow the /// same execution path as real-valued code would. -impl + approx::AbsDiffEq, F: Float, D: Dim> approx::AbsDiffEq - for DualVec -where - DefaultAllocator: Allocator, -{ +impl + approx::AbsDiffEq, F: Float> approx::AbsDiffEq for Dual { type Epsilon = Self; #[inline] fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { @@ -419,11 +249,7 @@ where } /// Like PartialEq, comparisons are only made based on the real part. This allows the code to follow the /// same execution path as real-valued code would. -impl + approx::RelativeEq, F: Float, D: Dim> approx::RelativeEq - for DualVec -where - DefaultAllocator: Allocator, -{ +impl + approx::RelativeEq, F: Float> approx::RelativeEq for Dual { #[inline] fn default_max_relative() -> Self::Epsilon { Self::from_re(T::default_max_relative()) @@ -439,10 +265,7 @@ where self.re.relative_eq(&other.re, epsilon.re, max_relative.re) } } -impl + UlpsEq, F: Float, D: Dim> UlpsEq for DualVec -where - DefaultAllocator: Allocator, -{ +impl + UlpsEq, F: Float> UlpsEq for Dual { #[inline] fn default_max_ulps() -> u32 { T::default_max_ulps() @@ -454,70 +277,51 @@ where } } -impl nalgebra::Field for DualVec +impl nalgebra::Field for Dual where T: DualNum + SimdValue, T::Element: DualNum + Scalar + Float, - DefaultAllocator: - Allocator + Allocator + Allocator + Allocator, - DefaultAllocator: Allocator - + Allocator - + Allocator - + Allocator, { } use simba::scalar::{SubsetOf, SupersetOf}; -impl SubsetOf> for DualVec +impl SubsetOf> for Dual where TSuper: DualNum + SupersetOf, T: DualNum, - DefaultAllocator: - Allocator + Allocator + Allocator + Allocator, - DefaultAllocator: Allocator - + Allocator - + Allocator - + Allocator, { #[inline(always)] - fn to_superset(&self) -> DualVec { + fn to_superset(&self) -> Dual { let re = TSuper::from_subset(&self.re); - let eps = Derivative::from_subset(&self.eps); - DualVec { + let eps = TSuper::from_subset(&self.eps); + Dual { re, eps, f: PhantomData, } } #[inline(always)] - fn from_superset(element: &DualVec) -> Option { + fn from_superset(element: &Dual) -> Option { let re = TSuper::to_subset(&element.re)?; - let eps = Derivative::to_subset(&element.eps)?; + let eps = TSuper::to_subset(&element.eps)?; Some(Self::new(re, eps)) } #[inline(always)] - fn from_superset_unchecked(element: &DualVec) -> Self { + fn from_superset_unchecked(element: &Dual) -> Self { let re = TSuper::to_subset_unchecked(&element.re); - let eps = Derivative::to_subset_unchecked(&element.eps); + let eps = TSuper::to_subset_unchecked(&element.eps); Self::new(re, eps) } #[inline(always)] - fn is_in_subset(element: &DualVec) -> bool { - TSuper::is_in_subset(&element.re) - && as SupersetOf>>::is_in_subset( - &element.eps, - ) + fn is_in_subset(element: &Dual) -> bool { + TSuper::is_in_subset(&element.re) && TSuper::is_in_subset(&element.eps) } } -impl SupersetOf for DualVec +impl SupersetOf for Dual where TSuper: DualNum + SupersetOf, - DefaultAllocator: Allocator - + Allocator - + Allocator - + Allocator, { #[inline(always)] fn is_in_subset(&self) -> bool { @@ -533,18 +337,14 @@ where fn from_subset(element: &f32) -> Self { // Interpret as a purely real number let re = TSuper::from_subset(element); - let eps = Derivative::none(); + let eps = TSuper::zero(); Self::new(re, eps) } } -impl SupersetOf for DualVec +impl SupersetOf for Dual where TSuper: DualNum + SupersetOf, - DefaultAllocator: Allocator - + Allocator - + Allocator - + Allocator, { #[inline(always)] fn is_in_subset(&self) -> bool { @@ -560,7 +360,7 @@ where fn from_subset(element: &f64) -> Self { // Interpret as a purely real number let re = TSuper::from_subset(element); - let eps = Derivative::none(); + let eps = TSuper::zero(); Self::new(re, eps) } } @@ -574,7 +374,7 @@ where use nalgebra::{ComplexField, RealField}; // This impl is modelled on `impl ComplexField for f32`. The imaginary part is nothing. -impl ComplexField for DualVec +impl ComplexField for Dual where T: DualNum + SupersetOf + AbsDiffEq + Sync + Send, T::Element: DualNum + Scalar + DualNumFloat + Sync + Send, @@ -583,9 +383,6 @@ where T: SimdPartialOrd + PartialOrd, T: SimdValue, T: RelativeEq + UlpsEq + AbsDiffEq, - DefaultAllocator: - Allocator + Allocator + Allocator + Allocator, - >::Buffer: Sync + Send, { type RealField = Self; @@ -611,7 +408,7 @@ where #[inline] fn modulus_squared(self) -> Self::RealField { - &self * &self + self * self } #[inline] @@ -832,7 +629,7 @@ where } } -impl RealField for DualVec +impl RealField for Dual where T: DualNum + SupersetOf + Sync + Send, T::Element: DualNum + Scalar + DualNumFloat, @@ -843,9 +640,6 @@ where T: SimdValue, T: UlpsEq, T: AbsDiffEq, - DefaultAllocator: - Allocator + Allocator + Allocator + Allocator, - >::Buffer: Sync + Send, { #[inline] fn copysign(self, sign: Self) -> Self { @@ -861,7 +655,7 @@ where let re = self.re.atan2(other.re); let eps = (self.eps * other.re - other.eps * self.re) / (self.re.powi(2) + other.re.powi(2)); - DualVec::new(re, eps) + Dual::new(re, eps) } #[inline] @@ -1003,6 +797,6 @@ mod test { let y = Dual64::from(-3.0); let z = x.atan2(y); let z2 = (x / y).atan(); - assert_relative_eq!(z.eps.unwrap(), z2.eps.unwrap(), epsilon = 1e-14); + assert_relative_eq!(z.eps, z2.eps, epsilon = 1e-14); } } diff --git a/src/dual_vec.rs b/src/dual_vec.rs new file mode 100644 index 0000000..441bdf4 --- /dev/null +++ b/src/dual_vec.rs @@ -0,0 +1,944 @@ +use crate::{Derivative, DualNum, DualNumFloat}; +use approx::{AbsDiffEq, RelativeEq, UlpsEq}; +use nalgebra::allocator::Allocator; +use nalgebra::*; +use num_traits::{Float, FloatConst, FromPrimitive, Inv, Num, One, Signed, Zero}; +use std::convert::Infallible; +use std::fmt; +use std::iter::{Product, Sum}; +use std::marker::PhantomData; +use std::ops::{ + Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, +}; + +/// A dual number for the calculations of gradients or Jacobians. +#[derive(Clone, Debug)] +pub struct DualVec, F, D: Dim> +where + DefaultAllocator: Allocator, +{ + /// Real part of the dual number + pub re: T, + /// Derivative part of the dual number + pub eps: Derivative, + f: PhantomData, +} + +impl + Copy, F: Copy, const N: usize> Copy for DualVec> {} + +pub type DualVec32 = DualVec; +pub type DualVec64 = DualVec; +pub type DualSVec32 = DualVec>; +pub type DualSVec64 = DualVec>; +pub type DualDVec32 = DualVec; +pub type DualDVec64 = DualVec; + +impl, F, D: Dim> DualVec +where + DefaultAllocator: Allocator, +{ + /// Create a new dual number from its fields. + #[inline] + pub fn new(re: T, eps: Derivative) -> Self { + Self { + re, + eps, + f: PhantomData, + } + } +} + +impl + Zero, F, D: Dim> DualVec +where + DefaultAllocator: Allocator, +{ + /// Create a new dual number from the real part. + #[inline] + pub fn from_re(re: T) -> Self { + Self::new(re, Derivative::none()) + } +} + +/// Calculate the gradient of a scalar function +/// ``` +/// # use approx::assert_relative_eq; +/// # use num_dual::{gradient, DualNum, DualSVec64}; +/// # use nalgebra::SVector; +/// let v = SVector::from([4.0, 3.0]); +/// let fun = |v: SVector, 2>| (v[0].powi(2) + v[1].powi(2)).sqrt(); +/// let (f, g) = gradient(fun, v); +/// assert_eq!(f, 5.0); +/// assert_relative_eq!(g[0], 0.8); +/// assert_relative_eq!(g[1], 0.6); +/// ``` +/// +/// The variable vector can also be dynamically sized +/// ``` +/// # use approx::assert_relative_eq; +/// # use num_dual::{gradient, DualNum, DualDVec64}; +/// # use nalgebra::DVector; +/// let v = DVector::repeat(4, 2.0); +/// let fun = |v: DVector| v.iter().map(|v| v * v).sum::().sqrt(); +/// let (f, g) = gradient(fun, v); +/// assert_eq!(f, 4.0); +/// assert_relative_eq!(g[0], 0.5); +/// assert_relative_eq!(g[1], 0.5); +/// assert_relative_eq!(g[2], 0.5); +/// assert_relative_eq!(g[3], 0.5); +/// ``` +pub fn gradient, F: DualNumFloat, D: Dim>( + g: G, + x: OVector, +) -> (T, OVector) +where + G: FnOnce(OVector, D>) -> DualVec, + DefaultAllocator: Allocator + Allocator, D>, +{ + try_gradient(|x| Ok::<_, Infallible>(g(x)), x).unwrap() +} + +/// Variant of [gradient] for fallible functions. +pub fn try_gradient, F: DualNumFloat, E, D: Dim>( + g: G, + x: OVector, +) -> Result<(T, OVector), E> +where + G: FnOnce(OVector, D>) -> Result, E>, + DefaultAllocator: Allocator + Allocator, D>, +{ + let mut x = x.map(DualVec::from_re); + let (r, c) = x.shape_generic(); + for (i, xi) in x.iter_mut().enumerate() { + xi.eps = Derivative::derivative_generic(r, c, i); + } + g(x).map(|res| (res.re, res.eps.unwrap_generic(r, c))) +} + +/// Calculate the Jacobian of a vector function. +/// ``` +/// # use num_dual::{jacobian, DualSVec64, DualNum}; +/// # use nalgebra::SVector; +/// let xy = SVector::from([5.0, 3.0, 2.0]); +/// let fun = |xy: SVector, 3>| SVector::from([ +/// xy[0] * xy[1].powi(3) * xy[2], +/// xy[0].powi(2) * xy[1] * xy[2].powi(2) +/// ]); +/// let (f, jac) = jacobian(fun, xy); +/// assert_eq!(f[0], 270.0); // xy³z +/// assert_eq!(f[1], 300.0); // x²yz² +/// assert_eq!(jac[(0,0)], 54.0); // y³z +/// assert_eq!(jac[(0,1)], 270.0); // 3xy²z +/// assert_eq!(jac[(0,2)], 135.0); // xy³ +/// assert_eq!(jac[(1,0)], 120.0); // 2xyz² +/// assert_eq!(jac[(1,1)], 100.0); // x²z² +/// assert_eq!(jac[(1,2)], 300.0); // 2x²yz +/// ``` +pub fn jacobian, F: DualNumFloat, M: Dim, N: Dim>( + g: G, + x: OVector, +) -> (OVector, OMatrix) +where + G: FnOnce(OVector, N>) -> OVector, M>, + DefaultAllocator: Allocator, M> + + Allocator + + Allocator + + Allocator + + Allocator, N> + + Allocator, N> + + Allocator, M>, +{ + try_jacobian(|x| Ok::<_, Infallible>(g(x)), x).unwrap() +} + +/// Variant of [jacobian] for fallible functions. +#[allow(clippy::type_complexity)] +pub fn try_jacobian, F: DualNumFloat, E, M: Dim, N: Dim>( + g: G, + x: OVector, +) -> Result<(OVector, OMatrix), E> +where + G: FnOnce(OVector, N>) -> Result, M>, E>, + DefaultAllocator: Allocator, M> + + Allocator + + Allocator + + Allocator + + Allocator, N> + + Allocator, N> + + Allocator, M>, +{ + let mut x = x.map(DualVec::from_re); + let (r, c) = x.shape_generic(); + for (i, xi) in x.iter_mut().enumerate() { + xi.eps = Derivative::derivative_generic(r, c, i); + } + g(x).map(|res| { + let eps = OMatrix::from_rows( + res.map(|res| res.eps.unwrap_generic(r, c).transpose()) + .as_slice(), + ); + (res.map(|r| r.re), eps) + }) +} + +/* chain rule */ +impl, F: Float, D: Dim> DualVec +where + DefaultAllocator: Allocator, +{ + #[inline] + fn chain_rule(&self, f0: T, f1: T) -> Self { + Self::new(f0, &self.eps * f1) + } +} + +/* product rule */ +impl<'a, 'b, T: DualNum, F: Float, D: Dim> Mul<&'a DualVec> for &'b DualVec +where + DefaultAllocator: Allocator, +{ + type Output = DualVec; + #[inline] + fn mul(self, other: &DualVec) -> Self::Output { + DualVec::new( + self.re.clone() * other.re.clone(), + &self.eps * other.re.clone() + &other.eps * self.re.clone(), + ) + } +} + +/* quotient rule */ +impl<'a, 'b, T: DualNum, F: Float, D: Dim> Div<&'a DualVec> for &'b DualVec +where + DefaultAllocator: Allocator, +{ + type Output = DualVec; + #[inline] + fn div(self, other: &DualVec) -> DualVec { + let inv = other.re.recip(); + DualVec::new( + self.re.clone() * inv.clone(), + (&self.eps * other.re.clone() - &other.eps * self.re.clone()) * inv.clone() * inv, + ) + } +} + +/* string conversions */ +impl, F, D: Dim> fmt::Display for DualVec +where + DefaultAllocator: Allocator, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.re)?; + self.eps.fmt(f, "ε") + } +} + +impl_first_derivatives!(DualVec, [eps], [D]); +impl_dual!(DualVec, [eps], [D]); + +/** + * The SimdValue trait is for rearranging data into a form more suitable for Simd, + * and rearranging it back into a usable form. It is not documented particularly well. + * + * The primary job of this SimdValue impl is to allow people to use `simba::simd::f32x4` etc, + * instead of f32/f64. Those types implement nalgebra::SimdRealField/ComplexField, so they + * behave like scalars. When we use them, we would have `DualVec` etc, with our + * F parameter set to `::Element`. We will need to be able to split up that type + * into four of DualVec in order to get out of simd-land. That's what the SimdValue trait is for. + * + * Ultimately, someone will have to to implement SimdRealField on DualVec and call the + * simd_ functions of ``. That's future work for someone who finds + * num_dual is not fast enough. + * + * Unfortunately, doing anything with SIMD is blocked on + * . + * + */ +impl nalgebra::SimdValue for DualVec +where + DefaultAllocator: Allocator + Allocator, + T: DualNum + SimdValue + Scalar, + T::Element: DualNum + Scalar, +{ + // Say T = simba::f32x4. T::Element is f32. T::SimdBool is AutoSimd<[bool; 4]>. + // AutoSimd<[f32; 4]> stores an actual [f32; 4], i.e. four floats in one slot. + // So our DualVec has 4 * (1+N) floats in it, stored in blocks of + // four. When we want to do any math on it but ignore its f32x4 storage mode, we need to break + // that type into FOUR of DualVec; then we do math on it, then we bring it back + // together. + // + // Hence this definition of Element: + type Element = DualVec; + type SimdBool = T::SimdBool; + + #[inline] + fn lanes() -> usize { + T::lanes() + } + + #[inline] + fn splat(val: Self::Element) -> Self { + // Need to make `lanes` copies of each of: + // - the real part + // - each of the N epsilon parts + let re = T::splat(val.re); + let eps = Derivative::splat(val.eps); + Self::new(re, eps) + } + + #[inline] + fn extract(&self, i: usize) -> Self::Element { + let re = self.re.extract(i); + let eps = self.eps.extract(i); + Self::Element { + re, + eps, + f: PhantomData, + } + } + + #[inline] + unsafe fn extract_unchecked(&self, i: usize) -> Self::Element { + let re = self.re.extract_unchecked(i); + let eps = self.eps.extract_unchecked(i); + Self::Element { + re, + eps, + f: PhantomData, + } + } + + #[inline] + fn replace(&mut self, i: usize, val: Self::Element) { + self.re.replace(i, val.re); + self.eps.replace(i, val.eps); + } + + #[inline] + unsafe fn replace_unchecked(&mut self, i: usize, val: Self::Element) { + self.re.replace_unchecked(i, val.re); + self.eps.replace_unchecked(i, val.eps); + } + + #[inline] + fn select(self, cond: Self::SimdBool, other: Self) -> Self { + let re = self.re.select(cond, other.re); + let eps = self.eps.select(cond, other.eps); + Self::new(re, eps) + } +} + +/// Comparisons are only made based on the real part. This allows the code to follow the +/// same execution path as real-valued code would. +impl + PartialEq, F: Float, D: Dim> PartialEq for DualVec +where + DefaultAllocator: Allocator, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + self.re.eq(&other.re) + } +} +/// Like PartialEq, comparisons are only made based on the real part. This allows the code to follow the +/// same execution path as real-valued code would. +impl + PartialOrd, F: Float, D: Dim> PartialOrd for DualVec +where + DefaultAllocator: Allocator, +{ + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + self.re.partial_cmp(&other.re) + } +} +/// Like PartialEq, comparisons are only made based on the real part. This allows the code to follow the +/// same execution path as real-valued code would. +impl + approx::AbsDiffEq, F: Float, D: Dim> approx::AbsDiffEq + for DualVec +where + DefaultAllocator: Allocator, +{ + type Epsilon = Self; + #[inline] + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + self.re.abs_diff_eq(&other.re, epsilon.re) + } + + #[inline] + fn default_epsilon() -> Self::Epsilon { + Self::from_re(T::default_epsilon()) + } +} +/// Like PartialEq, comparisons are only made based on the real part. This allows the code to follow the +/// same execution path as real-valued code would. +impl + approx::RelativeEq, F: Float, D: Dim> approx::RelativeEq + for DualVec +where + DefaultAllocator: Allocator, +{ + #[inline] + fn default_max_relative() -> Self::Epsilon { + Self::from_re(T::default_max_relative()) + } + + #[inline] + fn relative_eq( + &self, + other: &Self, + epsilon: Self::Epsilon, + max_relative: Self::Epsilon, + ) -> bool { + self.re.relative_eq(&other.re, epsilon.re, max_relative.re) + } +} +impl + UlpsEq, F: Float, D: Dim> UlpsEq for DualVec +where + DefaultAllocator: Allocator, +{ + #[inline] + fn default_max_ulps() -> u32 { + T::default_max_ulps() + } + + #[inline] + fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { + T::ulps_eq(&self.re, &other.re, epsilon.re, max_ulps) + } +} + +impl nalgebra::Field for DualVec +where + T: DualNum + SimdValue, + T::Element: DualNum + Scalar + Float, + DefaultAllocator: + Allocator + Allocator + Allocator + Allocator, + DefaultAllocator: Allocator + + Allocator + + Allocator + + Allocator, +{ +} + +use simba::scalar::{SubsetOf, SupersetOf}; + +impl SubsetOf> for DualVec +where + TSuper: DualNum + SupersetOf, + T: DualNum, + DefaultAllocator: + Allocator + Allocator + Allocator + Allocator, + DefaultAllocator: Allocator + + Allocator + + Allocator + + Allocator, +{ + #[inline(always)] + fn to_superset(&self) -> DualVec { + let re = TSuper::from_subset(&self.re); + let eps = Derivative::from_subset(&self.eps); + DualVec { + re, + eps, + f: PhantomData, + } + } + #[inline(always)] + fn from_superset(element: &DualVec) -> Option { + let re = TSuper::to_subset(&element.re)?; + let eps = Derivative::to_subset(&element.eps)?; + Some(Self::new(re, eps)) + } + #[inline(always)] + fn from_superset_unchecked(element: &DualVec) -> Self { + let re = TSuper::to_subset_unchecked(&element.re); + let eps = Derivative::to_subset_unchecked(&element.eps); + Self::new(re, eps) + } + #[inline(always)] + fn is_in_subset(element: &DualVec) -> bool { + TSuper::is_in_subset(&element.re) + && as SupersetOf>>::is_in_subset( + &element.eps, + ) + } +} + +impl SupersetOf for DualVec +where + TSuper: DualNum + SupersetOf, + DefaultAllocator: Allocator + + Allocator + + Allocator + + Allocator, +{ + #[inline(always)] + fn is_in_subset(&self) -> bool { + self.re.is_in_subset() + } + + #[inline(always)] + fn to_subset_unchecked(&self) -> f32 { + self.re.to_subset_unchecked() + } + + #[inline(always)] + fn from_subset(element: &f32) -> Self { + // Interpret as a purely real number + let re = TSuper::from_subset(element); + let eps = Derivative::none(); + Self::new(re, eps) + } +} + +impl SupersetOf for DualVec +where + TSuper: DualNum + SupersetOf, + DefaultAllocator: Allocator + + Allocator + + Allocator + + Allocator, +{ + #[inline(always)] + fn is_in_subset(&self) -> bool { + self.re.is_in_subset() + } + + #[inline(always)] + fn to_subset_unchecked(&self) -> f64 { + self.re.to_subset_unchecked() + } + + #[inline(always)] + fn from_subset(element: &f64) -> Self { + // Interpret as a purely real number + let re = TSuper::from_subset(element); + let eps = Derivative::none(); + Self::new(re, eps) + } +} + +// We can't do a simd implementation until simba lets us implement SimdPartialOrd +// using _T_'s SimdBool. The blanket impl gets in the way. So we must constrain +// T to SimdValue, which is basically the same as +// saying f32 or f64 only. +// +// Limitation of simba. See https://github.com/dimforge/simba/issues/44 + +use nalgebra::{ComplexField, RealField}; +// This impl is modelled on `impl ComplexField for f32`. The imaginary part is nothing. +impl ComplexField for DualVec +where + T: DualNum + SupersetOf + AbsDiffEq + Sync + Send, + T::Element: DualNum + Scalar + DualNumFloat + Sync + Send, + T: SupersetOf, + T: SupersetOf, + T: SimdPartialOrd + PartialOrd, + T: SimdValue, + T: RelativeEq + UlpsEq + AbsDiffEq, + DefaultAllocator: + Allocator + Allocator + Allocator + Allocator, + >::Buffer: Sync + Send, +{ + type RealField = Self; + + #[inline] + fn from_real(re: Self::RealField) -> Self { + re + } + + #[inline] + fn real(self) -> Self::RealField { + self + } + + #[inline] + fn imaginary(self) -> Self::RealField { + Self::zero() + } + + #[inline] + fn modulus(self) -> Self::RealField { + self.abs() + } + + #[inline] + fn modulus_squared(self) -> Self::RealField { + &self * &self + } + + #[inline] + fn argument(self) -> Self::RealField { + Self::zero() + } + + #[inline] + fn norm1(self) -> Self::RealField { + self.abs() + } + + #[inline] + fn scale(self, factor: Self::RealField) -> Self { + self * factor + } + + #[inline] + fn unscale(self, factor: Self::RealField) -> Self { + self / factor + } + + #[inline] + fn floor(self) -> Self { + panic!("called floor() on a dual number") + } + + #[inline] + fn ceil(self) -> Self { + panic!("called ceil() on a dual number") + } + + #[inline] + fn round(self) -> Self { + panic!("called round() on a dual number") + } + + #[inline] + fn trunc(self) -> Self { + panic!("called trunc() on a dual number") + } + + #[inline] + fn fract(self) -> Self { + panic!("called fract() on a dual number") + } + + #[inline] + fn mul_add(self, a: Self, b: Self) -> Self { + DualNum::mul_add(&self, a, b) + } + + #[inline] + fn abs(self) -> Self::RealField { + Signed::abs(&self) + } + + #[inline] + fn hypot(self, other: Self) -> Self::RealField { + let sum_sq = self.powi(2) + other.powi(2); + DualNum::sqrt(&sum_sq) + } + + #[inline] + fn recip(self) -> Self { + DualNum::recip(&self) + } + + #[inline] + fn conjugate(self) -> Self { + self + } + + #[inline] + fn sin(self) -> Self { + DualNum::sin(&self) + } + + #[inline] + fn cos(self) -> Self { + DualNum::cos(&self) + } + + #[inline] + fn sin_cos(self) -> (Self, Self) { + DualNum::sin_cos(&self) + } + + #[inline] + fn tan(self) -> Self { + DualNum::tan(&self) + } + + #[inline] + fn asin(self) -> Self { + DualNum::asin(&self) + } + + #[inline] + fn acos(self) -> Self { + DualNum::acos(&self) + } + + #[inline] + fn atan(self) -> Self { + DualNum::atan(&self) + } + + #[inline] + fn sinh(self) -> Self { + DualNum::sinh(&self) + } + + #[inline] + fn cosh(self) -> Self { + DualNum::cosh(&self) + } + + #[inline] + fn tanh(self) -> Self { + DualNum::tanh(&self) + } + + #[inline] + fn asinh(self) -> Self { + DualNum::asinh(&self) + } + + #[inline] + fn acosh(self) -> Self { + DualNum::acosh(&self) + } + + #[inline] + fn atanh(self) -> Self { + DualNum::atanh(&self) + } + + #[inline] + fn log(self, base: Self::RealField) -> Self { + DualNum::ln(&self) / DualNum::ln(&base) + } + + #[inline] + fn log2(self) -> Self { + DualNum::log2(&self) + } + + #[inline] + fn log10(self) -> Self { + DualNum::log10(&self) + } + + #[inline] + fn ln(self) -> Self { + DualNum::ln(&self) + } + + #[inline] + fn ln_1p(self) -> Self { + DualNum::ln_1p(&self) + } + + #[inline] + fn sqrt(self) -> Self { + DualNum::sqrt(&self) + } + + #[inline] + fn exp(self) -> Self { + DualNum::exp(&self) + } + + #[inline] + fn exp2(self) -> Self { + DualNum::exp2(&self) + } + + #[inline] + fn exp_m1(self) -> Self { + DualNum::exp_m1(&self) + } + + #[inline] + fn powi(self, n: i32) -> Self { + DualNum::powi(&self, n) + } + + #[inline] + fn powf(self, n: Self::RealField) -> Self { + // n could be a dual. + DualNum::powd(&self, n) + } + + #[inline] + fn powc(self, n: Self) -> Self { + // same as powf, Self isn't complex + self.powf(n) + } + + #[inline] + fn cbrt(self) -> Self { + DualNum::cbrt(&self) + } + + #[inline] + fn is_finite(&self) -> bool { + self.re.is_finite() + } + + #[inline] + fn try_sqrt(self) -> Option { + if self > Self::zero() { + Some(DualNum::sqrt(&self)) + } else { + None + } + } +} + +impl RealField for DualVec +where + T: DualNum + SupersetOf + Sync + Send, + T::Element: DualNum + Scalar + DualNumFloat, + T: SupersetOf, + T: SupersetOf, + T: SimdPartialOrd + PartialOrd, + T: RelativeEq + AbsDiffEq, + T: SimdValue, + T: UlpsEq, + T: AbsDiffEq, + DefaultAllocator: + Allocator + Allocator + Allocator + Allocator, + >::Buffer: Sync + Send, +{ + #[inline] + fn copysign(self, sign: Self) -> Self { + if sign.re.is_sign_positive() { + self.simd_abs() + } else { + -self.simd_abs() + } + } + + #[inline] + fn atan2(self, other: Self) -> Self { + let re = self.re.atan2(other.re); + let eps = + (self.eps * other.re - other.eps * self.re) / (self.re.powi(2) + other.re.powi(2)); + DualVec::new(re, eps) + } + + #[inline] + fn pi() -> Self { + Self::from_re(::PI()) + } + + #[inline] + fn two_pi() -> Self { + Self::from_re(::TAU()) + } + + #[inline] + fn frac_pi_2() -> Self { + Self::from_re(::FRAC_PI_4()) + } + + #[inline] + fn frac_pi_3() -> Self { + Self::from_re(::FRAC_PI_3()) + } + + #[inline] + fn frac_pi_4() -> Self { + Self::from_re(::FRAC_PI_4()) + } + + #[inline] + fn frac_pi_6() -> Self { + Self::from_re(::FRAC_PI_6()) + } + + #[inline] + fn frac_pi_8() -> Self { + Self::from_re(::FRAC_PI_8()) + } + + #[inline] + fn frac_1_pi() -> Self { + Self::from_re(::FRAC_1_PI()) + } + + #[inline] + fn frac_2_pi() -> Self { + Self::from_re(::FRAC_2_PI()) + } + + #[inline] + fn frac_2_sqrt_pi() -> Self { + Self::from_re(::FRAC_2_SQRT_PI()) + } + + #[inline] + fn e() -> Self { + Self::from_re(::E()) + } + + #[inline] + fn log2_e() -> Self { + Self::from_re(::LOG2_E()) + } + + #[inline] + fn log10_e() -> Self { + Self::from_re(::LOG10_E()) + } + + #[inline] + fn ln_2() -> Self { + Self::from_re(::LN_2()) + } + + #[inline] + fn ln_10() -> Self { + Self::from_re(::LN_10()) + } + + #[inline] + fn is_sign_positive(&self) -> bool { + self.re.is_sign_positive() + } + + #[inline] + fn is_sign_negative(&self) -> bool { + self.re.is_sign_negative() + } + + /// Got to be careful using this, because it throws away the derivatives of the one not chosen + #[inline] + fn max(self, other: Self) -> Self { + if other > self { + other + } else { + self + } + } + + /// Got to be careful using this, because it throws away the derivatives of the one not chosen + #[inline] + fn min(self, other: Self) -> Self { + if other < self { + other + } else { + self + } + } + + /// If the min/max values are constants and the clamping has an effect, you lose your gradients. + #[inline] + fn clamp(self, min: Self, max: Self) -> Self { + if self < min { + min + } else if self > max { + max + } else { + self + } + } + + #[inline] + fn min_value() -> Option { + Some(Self::from_re(T::min_value())) + } + + #[inline] + fn max_value() -> Option { + Some(Self::from_re(T::max_value())) + } +} diff --git a/src/lib.rs b/src/lib.rs index 46f8d4c..7ea40f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,19 +56,21 @@ mod derivative; mod dual; mod dual2; mod dual3; +mod dual_vec; mod hyperdual; mod hyperhyperdual; pub use bessel::BesselDual; pub use derivative::Derivative; -pub use dual::{ - first_derivative, gradient, jacobian, try_first_derivative, try_gradient, try_jacobian, Dual, - Dual32, Dual64, DualDVec32, DualDVec64, DualSVec32, DualSVec64, DualVec, DualVec32, DualVec64, -}; +pub use dual::{first_derivative, try_first_derivative, Dual, Dual32, Dual64}; pub use dual2::{ hessian, second_derivative, try_hessian, try_second_derivative, Dual2, Dual2DVec32, Dual2DVec64, Dual2SVec32, Dual2SVec64, Dual2Vec, Dual2Vec32, Dual2Vec64, Dual2_32, Dual2_64, }; pub use dual3::{third_derivative, try_third_derivative, Dual3, Dual3_32, Dual3_64}; +pub use dual_vec::{ + gradient, jacobian, try_gradient, try_jacobian, DualDVec32, DualDVec64, DualSVec32, DualSVec64, + DualVec, DualVec32, DualVec64, +}; pub use hyperdual::{ partial_hessian, second_partial_derivative, try_partial_hessian, try_second_partial_derivative, HyperDual, HyperDual32, HyperDual64, HyperDualDVec32, HyperDualDVec64, HyperDualSVec32, diff --git a/src/linalg.rs b/src/linalg.rs index 660647b..0ece436 100644 --- a/src/linalg.rs +++ b/src/linalg.rs @@ -1,5 +1,5 @@ #![allow(clippy::assign_op_pattern)] -use crate::{Dual2Vec, Dual3, DualNum, DualVec, HyperDualVec, HyperHyperDual}; +use crate::{Dual, Dual2Vec, Dual3, DualNum, DualVec, HyperDualVec, HyperHyperDual}; use nalgebra::allocator::Allocator; use nalgebra::{DefaultAllocator, Dim, U1}; use ndarray::{Array1, Array2, ScalarOperand}; @@ -8,6 +8,7 @@ use std::fmt; use std::iter::Product; use std::marker::PhantomData; +impl, F: Clone + 'static> ScalarOperand for Dual {} impl, F: Clone + 'static, N: Dim> ScalarOperand for DualVec where DefaultAllocator: Allocator { diff --git a/tests/nalgebra_api.rs b/tests/nalgebra_api.rs index 73a1481..935e8c1 100644 --- a/tests/nalgebra_api.rs +++ b/tests/nalgebra_api.rs @@ -18,7 +18,7 @@ fn use_nalgebra_2d() { let t = Dual32::from_re(0.25).derivative(); let point = unit_circle(t); let real = point.map(|x| x.re); - let grad = point.map(|x| x.eps.unwrap()); + let grad = point.map(|x| x.eps); println!("point: {}", point.coords); approx::assert_relative_eq!(real, Point2::new(0., 1.), epsilon = 1e-3); approx::assert_relative_eq!(grad, Point2::new(-TAU, 0.), epsilon = 1e-3); @@ -39,7 +39,7 @@ fn use_nalgebra_3d() { let function = |t: Dual32| rotation * lifted_3d_circle(t); let point = function(Dual32::from_re(0.25).derivative()); let real = point.map(|x| x.re); - let grad = point.map(|x| x.eps.unwrap()); + let grad = point.map(|x| x.eps); println!("rotated point: {}", point.coords); approx::assert_relative_eq!(real.coords, real.coords.normalize(), epsilon = 1e-3); approx::assert_relative_eq!(real, Point3::new(0.146, 0.924, 0.354), epsilon = 1e-3); @@ -55,7 +55,7 @@ fn eigenvalues() { let v = SymmetricEigen::new(m).eigenvalues; println!("{v}"); approx::assert_relative_eq!(v[0].re, 5.0); - approx::assert_relative_eq!(v[0].eps.unwrap(), 0.2); + approx::assert_relative_eq!(v[0].eps, 0.2); approx::assert_relative_eq!(v[1].re, 0.0); - approx::assert_relative_eq!(v[1].eps.unwrap(), 0.8); + approx::assert_relative_eq!(v[1].eps, 0.8); } From 388e8952f12227c9ca7a82105fc81af30616c5c5 Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Wed, 31 May 2023 11:41:19 +0200 Subject: [PATCH 2/6] added higher order dual numbers --- src/dual.rs | 2 +- src/dual2.rs | 183 ++++++----------------- src/dual2_vec.rs | 192 ++++++++++++++++++++++++ src/hyperdual.rs | 232 +++++++---------------------- src/hyperdual_vec.rs | 237 ++++++++++++++++++++++++++++++ src/lib.rs | 15 +- src/linalg.rs | 6 +- tests/test_dual.rs | 106 +++++++------- tests/test_dual2.rs | 212 +++++++++++++-------------- tests/test_hyperdual.rs | 318 ++++++++++++++++++++-------------------- write_tests.ipynb | 18 +-- 11 files changed, 863 insertions(+), 658 deletions(-) create mode 100644 src/dual2_vec.rs create mode 100644 src/hyperdual_vec.rs diff --git a/src/dual.rs b/src/dual.rs index 1fb2345..4b54438 100644 --- a/src/dual.rs +++ b/src/dual.rs @@ -10,7 +10,7 @@ use std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, }; -/// A dual number for the calculations of gradients or Jacobians. +/// A dual number for the calculations of first derivatives. #[derive(Copy, Clone, Debug)] pub struct Dual, F> { /// Real part of the dual number diff --git a/src/dual2.rs b/src/dual2.rs index 78649ac..04585bf 100644 --- a/src/dual2.rs +++ b/src/dual2.rs @@ -1,6 +1,4 @@ -use crate::{Derivative, DualNum, DualNumFloat}; -use nalgebra::allocator::Allocator; -use nalgebra::{Const, DefaultAllocator, Dim, Dyn, OMatrix, OVector, SMatrix, U1}; +use crate::{DualNum, DualNumFloat}; use num_traits::{Float, FloatConst, FromPrimitive, Inv, Num, One, Signed, Zero}; use std::convert::Infallible; use std::fmt; @@ -10,40 +8,25 @@ use std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, }; -/// A second order dual number for the calculation of Hessians. -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct Dual2Vec, F, D: Dim> -where - DefaultAllocator: Allocator + Allocator, -{ +/// A second order dual number for the calculation of second derivatives. +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub struct Dual2, F> { /// Real part of the second order dual number pub re: T, - /// Gradient part of the second order dual number - pub v1: Derivative, - /// Hessian part of the second order dual number - pub v2: Derivative, + /// First derivative part of the second order dual number + pub v1: T, + /// Second derivative part of the second order dual number + pub v2: T, f: PhantomData, } -impl + Copy, F: Copy, const N: usize> Copy for Dual2Vec> {} - -pub type Dual2Vec32 = Dual2Vec; -pub type Dual2Vec64 = Dual2Vec; -pub type Dual2SVec32 = Dual2Vec>; -pub type Dual2SVec64 = Dual2Vec>; -pub type Dual2DVec32 = Dual2Vec; -pub type Dual2DVec64 = Dual2Vec; -pub type Dual2 = Dual2Vec; pub type Dual2_32 = Dual2; pub type Dual2_64 = Dual2; -impl, F, D: Dim> Dual2Vec -where - DefaultAllocator: Allocator + Allocator, -{ +impl, F> Dual2 { /// Create a new second order dual number from its fields. #[inline] - pub fn new(re: T, v1: Derivative, v2: Derivative) -> Self { + pub fn new(re: T, v1: T, v2: T) -> Self { Self { re, v1, @@ -54,16 +37,6 @@ where } impl, F> Dual2 { - /// Create a new scalar second order dual number from its fields. - #[inline] - pub fn new_scalar(re: T, v1: T, v2: T) -> Self { - Self::new( - re, - Derivative::some(SMatrix::from_element(v1)), - Derivative::some(SMatrix::from_element(v2)), - ) - } - /// Set the derivative part to 1. /// ``` /// # use num_dual::{Dual2, DualNum}; @@ -87,19 +60,16 @@ impl, F> Dual2 { /// ``` #[inline] pub fn derivative(mut self) -> Self { - self.v1 = Derivative::derivative(); + self.v1 = T::one(); self } } -impl, F, D: Dim> Dual2Vec -where - DefaultAllocator: Allocator + Allocator, -{ +impl, F> Dual2 { /// Create a new second order dual number from the real part. #[inline] pub fn from_re(re: T) -> Self { - Self::new(re, Derivative::none(), Derivative::none()) + Self::new(re, T::zero(), T::zero()) } } @@ -137,135 +107,66 @@ where G: FnOnce(Dual2) -> Result, E>, { let x = Dual2::from_re(x).derivative(); - g(x).map(|r| (r.re, r.v1.unwrap(), r.v2.unwrap())) -} - -/// Calculate the Hessian of a scalar function. -/// ``` -/// # use approx::assert_relative_eq; -/// # use num_dual::{hessian, DualNum, Dual2SVec64}; -/// # use nalgebra::SVector; -/// let v = SVector::from([4.0, 3.0]); -/// let fun = |v: SVector, 2>| (v[0].powi(2) + v[1].powi(2)).sqrt(); -/// let (f, g, h) = hessian(fun, v); -/// assert_eq!(f, 5.0); -/// assert_relative_eq!(g[0], 0.8); -/// assert_relative_eq!(g[1], 0.6); -/// assert_relative_eq!(h[(0,0)], 0.072); -/// assert_relative_eq!(h[(0,1)], -0.096); -/// assert_relative_eq!(h[(1,0)], -0.096); -/// assert_relative_eq!(h[(1,1)], 0.128); -/// ``` -pub fn hessian, F: DualNumFloat, D: Dim>( - g: G, - x: OVector, -) -> (T, OVector, OMatrix) -where - G: FnOnce(OVector, D>) -> Dual2Vec, - DefaultAllocator: Allocator - + Allocator - + Allocator - + Allocator, D>, -{ - try_hessian(|x| Ok::<_, Infallible>(g(x)), x).unwrap() -} - -/// Variant of [hessian] for fallible functions. -#[allow(clippy::type_complexity)] -pub fn try_hessian, F: DualNumFloat, E, D: Dim>( - g: G, - x: OVector, -) -> Result<(T, OVector, OMatrix), E> -where - G: FnOnce(OVector, D>) -> Result, E>, - DefaultAllocator: Allocator - + Allocator - + Allocator - + Allocator, D>, -{ - let mut x = x.map(Dual2Vec::from_re); - let (r, c) = x.shape_generic(); - for (i, xi) in x.iter_mut().enumerate() { - xi.v1 = Derivative::derivative_generic(c, r, i) - } - g(x).map(|res| { - ( - res.re, - res.v1.unwrap_generic(c, r).transpose(), - res.v2.unwrap_generic(r, r), - ) - }) + g(x).map(|r| (r.re, r.v1, r.v2)) } /* chain rule */ -impl, F: Float, D: Dim> Dual2Vec -where - DefaultAllocator: Allocator + Allocator, -{ +impl, F: Float> Dual2 { #[inline] fn chain_rule(&self, f0: T, f1: T, f2: T) -> Self { Self::new( f0, - &self.v1 * f1.clone(), - &self.v2 * f1 + self.v1.tr_mul(&self.v1) * f2, + self.v1.clone() * f1.clone(), + self.v2.clone() * f1 + self.v1.clone() * self.v1.clone() * f2, ) } } /* product rule */ -impl<'a, 'b, T: DualNum, F: Float, D: Dim> Mul<&'a Dual2Vec> for &'b Dual2Vec -where - DefaultAllocator: Allocator + Allocator, -{ - type Output = Dual2Vec; +impl<'a, 'b, T: DualNum, F: Float> Mul<&'a Dual2> for &'b Dual2 { + type Output = Dual2; #[inline] - fn mul(self, other: &Dual2Vec) -> Dual2Vec { - Dual2Vec::new( + fn mul(self, other: &Dual2) -> Dual2 { + Dual2::new( self.re.clone() * other.re.clone(), - &other.v1 * self.re.clone() + &self.v1 * other.re.clone(), - &other.v2 * self.re.clone() - + self.v1.tr_mul(&other.v1) - + other.v1.tr_mul(&self.v1) - + &self.v2 * other.re.clone(), + other.v1.clone() * self.re.clone() + self.v1.clone() * other.re.clone(), + other.v2.clone() * self.re.clone() + + self.v1.clone() * other.v1.clone() + + other.v1.clone() * self.v1.clone() + + self.v2.clone() * other.re.clone(), ) } } /* quotient rule */ -impl<'a, 'b, T: DualNum, F: Float, D: Dim> Div<&'a Dual2Vec> for &'b Dual2Vec -where - DefaultAllocator: Allocator + Allocator, -{ - type Output = Dual2Vec; +impl<'a, 'b, T: DualNum, F: Float> Div<&'a Dual2> for &'b Dual2 { + type Output = Dual2; #[inline] - fn div(self, other: &Dual2Vec) -> Dual2Vec { + fn div(self, other: &Dual2) -> Dual2 { let inv = other.re.recip(); let inv2 = inv.clone() * inv.clone(); - Dual2Vec::new( + Dual2::new( self.re.clone() * inv.clone(), - (&self.v1 * other.re.clone() - &other.v1 * self.re.clone()) * inv2.clone(), - &self.v2 * inv.clone() - - (&other.v2 * self.re.clone() - + self.v1.tr_mul(&other.v1) - + other.v1.tr_mul(&self.v1)) + (self.v1.clone() * other.re.clone() - other.v1.clone() * self.re.clone()) + * inv2.clone(), + self.v2.clone() * inv.clone() + - (other.v2.clone() * self.re.clone() + + self.v1.clone() * other.v1.clone() + + other.v1.clone() * self.v1.clone()) * inv2.clone() - + other.v1.tr_mul(&other.v1) + + other.v1.clone() + * other.v1.clone() * ((T::one() + T::one()) * self.re.clone() * inv2 * inv), ) } } /* string conversions */ -impl, F: fmt::Display, D: Dim> fmt::Display for Dual2Vec -where - DefaultAllocator: Allocator + Allocator, -{ +impl, F: fmt::Display> fmt::Display for Dual2 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.re)?; - self.v1.fmt(f, "ε1")?; - self.v2.fmt(f, "ε1²") + write!(f, "{} + {}ε1 + {}ε1²", self.re, self.v1, self.v2) } } -impl_second_derivatives!(Dual2Vec, [v1, v2], [D]); -impl_dual!(Dual2Vec, [v1, v2], [D]); +impl_second_derivatives!(Dual2, [v1, v2]); +impl_dual!(Dual2, [v1, v2]); diff --git a/src/dual2_vec.rs b/src/dual2_vec.rs new file mode 100644 index 0000000..5fc1b11 --- /dev/null +++ b/src/dual2_vec.rs @@ -0,0 +1,192 @@ +use crate::{Derivative, DualNum, DualNumFloat}; +use nalgebra::allocator::Allocator; +use nalgebra::{Const, DefaultAllocator, Dim, Dyn, OMatrix, OVector, U1}; +use num_traits::{Float, FloatConst, FromPrimitive, Inv, Num, One, Signed, Zero}; +use std::convert::Infallible; +use std::fmt; +use std::iter::{Product, Sum}; +use std::marker::PhantomData; +use std::ops::{ + Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, +}; + +/// A second order dual number for the calculation of Hessians. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct Dual2Vec, F, D: Dim> +where + DefaultAllocator: Allocator + Allocator, +{ + /// Real part of the second order dual number + pub re: T, + /// Gradient part of the second order dual number + pub v1: Derivative, + /// Hessian part of the second order dual number + pub v2: Derivative, + f: PhantomData, +} + +impl + Copy, F: Copy, const N: usize> Copy for Dual2Vec> {} + +pub type Dual2Vec32 = Dual2Vec; +pub type Dual2Vec64 = Dual2Vec; +pub type Dual2SVec32 = Dual2Vec>; +pub type Dual2SVec64 = Dual2Vec>; +pub type Dual2DVec32 = Dual2Vec; +pub type Dual2DVec64 = Dual2Vec; + +impl, F, D: Dim> Dual2Vec +where + DefaultAllocator: Allocator + Allocator, +{ + /// Create a new second order dual number from its fields. + #[inline] + pub fn new(re: T, v1: Derivative, v2: Derivative) -> Self { + Self { + re, + v1, + v2, + f: PhantomData, + } + } +} + +impl, F, D: Dim> Dual2Vec +where + DefaultAllocator: Allocator + Allocator, +{ + /// Create a new second order dual number from the real part. + #[inline] + pub fn from_re(re: T) -> Self { + Self::new(re, Derivative::none(), Derivative::none()) + } +} + +/// Calculate the Hessian of a scalar function. +/// ``` +/// # use approx::assert_relative_eq; +/// # use num_dual::{hessian, DualNum, Dual2SVec64}; +/// # use nalgebra::SVector; +/// let v = SVector::from([4.0, 3.0]); +/// let fun = |v: SVector, 2>| (v[0].powi(2) + v[1].powi(2)).sqrt(); +/// let (f, g, h) = hessian(fun, v); +/// assert_eq!(f, 5.0); +/// assert_relative_eq!(g[0], 0.8); +/// assert_relative_eq!(g[1], 0.6); +/// assert_relative_eq!(h[(0,0)], 0.072); +/// assert_relative_eq!(h[(0,1)], -0.096); +/// assert_relative_eq!(h[(1,0)], -0.096); +/// assert_relative_eq!(h[(1,1)], 0.128); +/// ``` +pub fn hessian, F: DualNumFloat, D: Dim>( + g: G, + x: OVector, +) -> (T, OVector, OMatrix) +where + G: FnOnce(OVector, D>) -> Dual2Vec, + DefaultAllocator: Allocator + + Allocator + + Allocator + + Allocator, D>, +{ + try_hessian(|x| Ok::<_, Infallible>(g(x)), x).unwrap() +} + +/// Variant of [hessian] for fallible functions. +#[allow(clippy::type_complexity)] +pub fn try_hessian, F: DualNumFloat, E, D: Dim>( + g: G, + x: OVector, +) -> Result<(T, OVector, OMatrix), E> +where + G: FnOnce(OVector, D>) -> Result, E>, + DefaultAllocator: Allocator + + Allocator + + Allocator + + Allocator, D>, +{ + let mut x = x.map(Dual2Vec::from_re); + let (r, c) = x.shape_generic(); + for (i, xi) in x.iter_mut().enumerate() { + xi.v1 = Derivative::derivative_generic(c, r, i) + } + g(x).map(|res| { + ( + res.re, + res.v1.unwrap_generic(c, r).transpose(), + res.v2.unwrap_generic(r, r), + ) + }) +} + +/* chain rule */ +impl, F: Float, D: Dim> Dual2Vec +where + DefaultAllocator: Allocator + Allocator, +{ + #[inline] + fn chain_rule(&self, f0: T, f1: T, f2: T) -> Self { + Self::new( + f0, + &self.v1 * f1.clone(), + &self.v2 * f1 + self.v1.tr_mul(&self.v1) * f2, + ) + } +} + +/* product rule */ +impl<'a, 'b, T: DualNum, F: Float, D: Dim> Mul<&'a Dual2Vec> for &'b Dual2Vec +where + DefaultAllocator: Allocator + Allocator, +{ + type Output = Dual2Vec; + #[inline] + fn mul(self, other: &Dual2Vec) -> Dual2Vec { + Dual2Vec::new( + self.re.clone() * other.re.clone(), + &other.v1 * self.re.clone() + &self.v1 * other.re.clone(), + &other.v2 * self.re.clone() + + self.v1.tr_mul(&other.v1) + + other.v1.tr_mul(&self.v1) + + &self.v2 * other.re.clone(), + ) + } +} + +/* quotient rule */ +impl<'a, 'b, T: DualNum, F: Float, D: Dim> Div<&'a Dual2Vec> for &'b Dual2Vec +where + DefaultAllocator: Allocator + Allocator, +{ + type Output = Dual2Vec; + #[inline] + fn div(self, other: &Dual2Vec) -> Dual2Vec { + let inv = other.re.recip(); + let inv2 = inv.clone() * inv.clone(); + Dual2Vec::new( + self.re.clone() * inv.clone(), + (&self.v1 * other.re.clone() - &other.v1 * self.re.clone()) * inv2.clone(), + &self.v2 * inv.clone() + - (&other.v2 * self.re.clone() + + self.v1.tr_mul(&other.v1) + + other.v1.tr_mul(&self.v1)) + * inv2.clone() + + other.v1.tr_mul(&other.v1) + * ((T::one() + T::one()) * self.re.clone() * inv2 * inv), + ) + } +} + +/* string conversions */ +impl, F: fmt::Display, D: Dim> fmt::Display for Dual2Vec +where + DefaultAllocator: Allocator + Allocator, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.re)?; + self.v1.fmt(f, "ε1")?; + self.v2.fmt(f, "ε1²") + } +} + +impl_second_derivatives!(Dual2Vec, [v1, v2], [D]); +impl_dual!(Dual2Vec, [v1, v2], [D]); diff --git a/src/hyperdual.rs b/src/hyperdual.rs index 72021ef..683238f 100644 --- a/src/hyperdual.rs +++ b/src/hyperdual.rs @@ -1,6 +1,4 @@ -use crate::{Derivative, DualNum, DualNumFloat}; -use nalgebra::allocator::Allocator; -use nalgebra::{Const, DefaultAllocator, Dim, Dyn, OMatrix, OVector, SMatrix, U1}; +use crate::{DualNum, DualNumFloat}; use num_traits::{Float, FloatConst, FromPrimitive, Inv, Num, One, Signed, Zero}; use std::convert::Infallible; use std::fmt; @@ -11,51 +9,26 @@ use std::ops::{ }; /// A hyper dual number for the calculation of second partial derivatives. -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct HyperDualVec, F, M: Dim, N: Dim> -where - DefaultAllocator: Allocator + Allocator + Allocator, -{ +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub struct HyperDual, F> { /// Real part of the hyper dual number pub re: T, /// Partial derivative part of the hyper dual number - pub eps1: Derivative, + pub eps1: T, /// Partial derivative part of the hyper dual number - pub eps2: Derivative, + pub eps2: T, /// Second partial derivative part of the hyper dual number - pub eps1eps2: Derivative, + pub eps1eps2: T, f: PhantomData, } -impl + Copy, F: Copy, const M: usize, const N: usize> Copy - for HyperDualVec, Const> -{ -} - -pub type HyperDualVec32 = HyperDualVec; -pub type HyperDualVec64 = HyperDualVec; -pub type HyperDualSVec32 = - HyperDualVec, Const>; -pub type HyperDualSVec64 = - HyperDualVec, Const>; -pub type HyperDualDVec32 = HyperDualVec; -pub type HyperDualDVec64 = HyperDualVec; -pub type HyperDual = HyperDualVec; pub type HyperDual32 = HyperDual; pub type HyperDual64 = HyperDual; -impl, F, M: Dim, N: Dim> HyperDualVec -where - DefaultAllocator: Allocator + Allocator + Allocator, -{ +impl, F> HyperDual { /// Create a new hyper dual number from its fields. #[inline] - pub fn new( - re: T, - eps1: Derivative, - eps2: Derivative, - eps1eps2: Derivative, - ) -> Self { + pub fn new(re: T, eps1: T, eps2: T, eps1eps2: T) -> Self { Self { re, eps1, @@ -67,45 +40,26 @@ where } impl, F> HyperDual { - /// Create a new scalar hyper dual number from its fields. - #[inline] - pub fn new_scalar(re: T, eps1: T, eps2: T, eps1eps2: T) -> Self { - Self::new( - re, - Derivative::some(SMatrix::from_element(eps1)), - Derivative::some(SMatrix::from_element(eps2)), - Derivative::some(SMatrix::from_element(eps1eps2)), - ) - } - /// Set the partial derivative part w.r.t. the 1st variable to 1. #[inline] pub fn derivative1(mut self) -> Self { - self.eps1 = Derivative::derivative(); + self.eps1 = T::one(); self } /// Set the partial derivative part w.r.t. the 2nd variable to 1. #[inline] pub fn derivative2(mut self) -> Self { - self.eps2 = Derivative::derivative(); + self.eps2 = T::one(); self } } -impl, F, M: Dim, N: Dim> HyperDualVec -where - DefaultAllocator: Allocator + Allocator + Allocator, -{ +impl, F> HyperDual { /// Create a new hyper dual number from the real part. #[inline] pub fn from_re(re: T) -> Self { - Self::new( - re, - Derivative::none(), - Derivative::none(), - Derivative::none(), - ) + Self::new(re, T::zero(), T::zero(), T::zero()) } } @@ -139,162 +93,74 @@ where { let x = HyperDual::from_re(x).derivative1(); let y = HyperDual::from_re(y).derivative2(); - g(x, y).map(|r| (r.re, r.eps1.unwrap(), r.eps2.unwrap(), r.eps1eps2.unwrap())) -} - -/// Calculate second partial derivatives with respect to vectors. -/// ``` -/// # use approx::assert_relative_eq; -/// # use num_dual::{partial_hessian, DualNum, HyperDualSVec64}; -/// # use nalgebra::SVector; -/// let x = SVector::from([4.0, 3.0]); -/// let y = SVector::from([5.0]); -/// let fun = |x: SVector, 2>, y: SVector, 1>| -/// y[0] / (x[0].powi(2) + x[1].powi(2)).sqrt(); -/// let (f, dfdx, dfdy, d2fdxdy) = partial_hessian(fun, x, y); -/// assert_eq!(f, 1.0); -/// assert_relative_eq!(dfdx[0], -0.16); -/// assert_relative_eq!(dfdx[1], -0.12); -/// assert_relative_eq!(dfdy[0], 0.2); -/// assert_relative_eq!(d2fdxdy[0], -0.032); -/// assert_relative_eq!(d2fdxdy[1], -0.024); -/// ``` -#[allow(clippy::type_complexity)] -pub fn partial_hessian, F: DualNumFloat, M: Dim, N: Dim>( - g: G, - x: OVector, - y: OVector, -) -> (T, OVector, OVector, OMatrix) -where - G: FnOnce( - OVector, M>, - OVector, N>, - ) -> HyperDualVec, - DefaultAllocator: Allocator - + Allocator - + Allocator - + Allocator - + Allocator, M> - + Allocator, N>, -{ - try_partial_hessian(|x, y| Ok::<_, Infallible>(g(x, y)), x, y).unwrap() -} - -/// Variant of [partial_hessian] for fallible functions. -#[allow(clippy::type_complexity)] -pub fn try_partial_hessian, F: DualNumFloat, E, M: Dim, N: Dim>( - g: G, - x: OVector, - y: OVector, -) -> Result<(T, OVector, OVector, OMatrix), E> -where - G: FnOnce( - OVector, M>, - OVector, N>, - ) -> Result, E>, - DefaultAllocator: Allocator - + Allocator - + Allocator - + Allocator - + Allocator, M> - + Allocator, N>, -{ - let mut x = x.map(HyperDualVec::from_re); - let mut y = y.map(HyperDualVec::from_re); - let (m, _) = x.shape_generic(); - for (i, xi) in x.iter_mut().enumerate() { - xi.eps1 = Derivative::derivative_generic(m, U1, i) - } - let (n, _) = y.shape_generic(); - for (i, yi) in y.iter_mut().enumerate() { - yi.eps2 = Derivative::derivative_generic(U1, n, i) - } - g(x, y).map(|r| { - ( - r.re, - r.eps1.unwrap_generic(m, U1), - r.eps2.unwrap_generic(U1, n).transpose(), - r.eps1eps2.unwrap_generic(m, n), - ) - }) + g(x, y).map(|r| (r.re, r.eps1, r.eps2, r.eps1eps2)) } /* chain rule */ -impl, F: Float, M: Dim, N: Dim> HyperDualVec -where - DefaultAllocator: Allocator + Allocator + Allocator, -{ +impl, F: Float> HyperDual { #[inline] fn chain_rule(&self, f0: T, f1: T, f2: T) -> Self { Self::new( f0, - &self.eps1 * f1.clone(), - &self.eps2 * f1.clone(), - &self.eps1eps2 * f1 + &self.eps1 * &self.eps2 * f2, + self.eps1.clone() * f1.clone(), + self.eps2.clone() * f1.clone(), + self.eps1eps2.clone() * f1 + self.eps1.clone() * self.eps2.clone() * f2, ) } } /* product rule */ -impl<'a, 'b, T: DualNum, F: Float, M: Dim, N: Dim> Mul<&'a HyperDualVec> - for &'b HyperDualVec -where - DefaultAllocator: Allocator + Allocator + Allocator, -{ - type Output = HyperDualVec; +impl<'a, 'b, T: DualNum, F: Float> Mul<&'a HyperDual> for &'b HyperDual { + type Output = HyperDual; #[inline] - fn mul(self, other: &HyperDualVec) -> HyperDualVec { - HyperDualVec::new( + fn mul(self, other: &HyperDual) -> HyperDual { + HyperDual::new( self.re.clone() * other.re.clone(), - &other.eps1 * self.re.clone() + &self.eps1 * other.re.clone(), - &other.eps2 * self.re.clone() + &self.eps2 * other.re.clone(), - &other.eps1eps2 * self.re.clone() - + &self.eps1 * &other.eps2 - + &other.eps1 * &self.eps2 - + &self.eps1eps2 * other.re.clone(), + other.eps1.clone() * self.re.clone() + self.eps1.clone() * other.re.clone(), + other.eps2.clone() * self.re.clone() + self.eps2.clone() * other.re.clone(), + other.eps1eps2.clone() * self.re.clone() + + self.eps1.clone() * other.eps2.clone() + + other.eps1.clone() * self.eps2.clone() + + self.eps1eps2.clone() * other.re.clone(), ) } } /* quotient rule */ -impl<'a, 'b, T: DualNum, F: Float, M: Dim, N: Dim> Div<&'a HyperDualVec> - for &'b HyperDualVec -where - DefaultAllocator: Allocator + Allocator + Allocator, -{ - type Output = HyperDualVec; +impl<'a, 'b, T: DualNum, F: Float> Div<&'a HyperDual> for &'b HyperDual { + type Output = HyperDual; #[inline] - fn div(self, other: &HyperDualVec) -> HyperDualVec { + fn div(self, other: &HyperDual) -> HyperDual { let inv = other.re.recip(); let inv2 = inv.clone() * &inv; - HyperDualVec::new( + HyperDual::new( self.re.clone() * &inv, - (&self.eps1 * other.re.clone() - &other.eps1 * self.re.clone()) * inv2.clone(), - (&self.eps2 * other.re.clone() - &other.eps2 * self.re.clone()) * inv2.clone(), - &self.eps1eps2 * inv.clone() - - (&other.eps1eps2 * self.re.clone() - + &self.eps1 * &other.eps2 - + &other.eps1 * &self.eps2) + (self.eps1.clone() * other.re.clone() - other.eps1.clone() * self.re.clone()) + * inv2.clone(), + (self.eps2.clone() * other.re.clone() - other.eps2.clone() * self.re.clone()) + * inv2.clone(), + self.eps1eps2.clone() * inv.clone() + - (other.eps1eps2.clone() * self.re.clone() + + self.eps1.clone() * other.eps2.clone() + + other.eps1.clone() * self.eps2.clone()) * inv2.clone() - + &other.eps1 - * &other.eps2 + + other.eps1.clone() + * other.eps2.clone() * ((T::one() + T::one()) * self.re.clone() * inv2 * inv), ) } } /* string conversions */ -impl, F: fmt::Display, M: Dim, N: Dim> fmt::Display for HyperDualVec -where - DefaultAllocator: Allocator + Allocator + Allocator, -{ +impl, F: fmt::Display> fmt::Display for HyperDual { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.re)?; - self.eps1.fmt(f, "ε1")?; - self.eps2.fmt(f, "ε2")?; - self.eps1eps2.fmt(f, "ε1ε2") + write!( + f, + "{} + {}ε1 + {}ε2 + {}ε1ε2", + self.re, self.eps1, self.eps2, self.eps1eps2 + ) } } -impl_second_derivatives!(HyperDualVec, [eps1, eps2, eps1eps2], [M, N]); -impl_dual!(HyperDualVec, [eps1, eps2, eps1eps2], [M, N]); +impl_second_derivatives!(HyperDual, [eps1, eps2, eps1eps2]); +impl_dual!(HyperDual, [eps1, eps2, eps1eps2]); diff --git a/src/hyperdual_vec.rs b/src/hyperdual_vec.rs new file mode 100644 index 0000000..a7ccc3b --- /dev/null +++ b/src/hyperdual_vec.rs @@ -0,0 +1,237 @@ +use crate::{Derivative, DualNum, DualNumFloat}; +use nalgebra::allocator::Allocator; +use nalgebra::{Const, DefaultAllocator, Dim, Dyn, OMatrix, OVector, U1}; +use num_traits::{Float, FloatConst, FromPrimitive, Inv, Num, One, Signed, Zero}; +use std::convert::Infallible; +use std::fmt; +use std::iter::{Product, Sum}; +use std::marker::PhantomData; +use std::ops::{ + Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, +}; + +/// A hyper dual number for the calculation of partial Hessians. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct HyperDualVec, F, M: Dim, N: Dim> +where + DefaultAllocator: Allocator + Allocator + Allocator, +{ + /// Real part of the hyper dual number + pub re: T, + /// Gradient part of the hyper dual number + pub eps1: Derivative, + /// Gradient part of the hyper dual number + pub eps2: Derivative, + /// Partial Hessian part of the hyper dual number + pub eps1eps2: Derivative, + f: PhantomData, +} + +impl + Copy, F: Copy, const M: usize, const N: usize> Copy + for HyperDualVec, Const> +{ +} + +pub type HyperDualVec32 = HyperDualVec; +pub type HyperDualVec64 = HyperDualVec; +pub type HyperDualSVec32 = + HyperDualVec, Const>; +pub type HyperDualSVec64 = + HyperDualVec, Const>; +pub type HyperDualDVec32 = HyperDualVec; +pub type HyperDualDVec64 = HyperDualVec; + +impl, F, M: Dim, N: Dim> HyperDualVec +where + DefaultAllocator: Allocator + Allocator + Allocator, +{ + /// Create a new hyper dual number from its fields. + #[inline] + pub fn new( + re: T, + eps1: Derivative, + eps2: Derivative, + eps1eps2: Derivative, + ) -> Self { + Self { + re, + eps1, + eps2, + eps1eps2, + f: PhantomData, + } + } +} + +impl, F, M: Dim, N: Dim> HyperDualVec +where + DefaultAllocator: Allocator + Allocator + Allocator, +{ + /// Create a new hyper dual number from the real part. + #[inline] + pub fn from_re(re: T) -> Self { + Self::new( + re, + Derivative::none(), + Derivative::none(), + Derivative::none(), + ) + } +} + +/// Calculate second partial derivatives with respect to vectors. +/// ``` +/// # use approx::assert_relative_eq; +/// # use num_dual::{partial_hessian, DualNum, HyperDualSVec64}; +/// # use nalgebra::SVector; +/// let x = SVector::from([4.0, 3.0]); +/// let y = SVector::from([5.0]); +/// let fun = |x: SVector, 2>, y: SVector, 1>| +/// y[0] / (x[0].powi(2) + x[1].powi(2)).sqrt(); +/// let (f, dfdx, dfdy, d2fdxdy) = partial_hessian(fun, x, y); +/// assert_eq!(f, 1.0); +/// assert_relative_eq!(dfdx[0], -0.16); +/// assert_relative_eq!(dfdx[1], -0.12); +/// assert_relative_eq!(dfdy[0], 0.2); +/// assert_relative_eq!(d2fdxdy[0], -0.032); +/// assert_relative_eq!(d2fdxdy[1], -0.024); +/// ``` +#[allow(clippy::type_complexity)] +pub fn partial_hessian, F: DualNumFloat, M: Dim, N: Dim>( + g: G, + x: OVector, + y: OVector, +) -> (T, OVector, OVector, OMatrix) +where + G: FnOnce( + OVector, M>, + OVector, N>, + ) -> HyperDualVec, + DefaultAllocator: Allocator + + Allocator + + Allocator + + Allocator + + Allocator, M> + + Allocator, N>, +{ + try_partial_hessian(|x, y| Ok::<_, Infallible>(g(x, y)), x, y).unwrap() +} + +/// Variant of [partial_hessian] for fallible functions. +#[allow(clippy::type_complexity)] +pub fn try_partial_hessian, F: DualNumFloat, E, M: Dim, N: Dim>( + g: G, + x: OVector, + y: OVector, +) -> Result<(T, OVector, OVector, OMatrix), E> +where + G: FnOnce( + OVector, M>, + OVector, N>, + ) -> Result, E>, + DefaultAllocator: Allocator + + Allocator + + Allocator + + Allocator + + Allocator, M> + + Allocator, N>, +{ + let mut x = x.map(HyperDualVec::from_re); + let mut y = y.map(HyperDualVec::from_re); + let (m, _) = x.shape_generic(); + for (i, xi) in x.iter_mut().enumerate() { + xi.eps1 = Derivative::derivative_generic(m, U1, i) + } + let (n, _) = y.shape_generic(); + for (i, yi) in y.iter_mut().enumerate() { + yi.eps2 = Derivative::derivative_generic(U1, n, i) + } + g(x, y).map(|r| { + ( + r.re, + r.eps1.unwrap_generic(m, U1), + r.eps2.unwrap_generic(U1, n).transpose(), + r.eps1eps2.unwrap_generic(m, n), + ) + }) +} + +/* chain rule */ +impl, F: Float, M: Dim, N: Dim> HyperDualVec +where + DefaultAllocator: Allocator + Allocator + Allocator, +{ + #[inline] + fn chain_rule(&self, f0: T, f1: T, f2: T) -> Self { + Self::new( + f0, + &self.eps1 * f1.clone(), + &self.eps2 * f1.clone(), + &self.eps1eps2 * f1 + &self.eps1 * &self.eps2 * f2, + ) + } +} + +/* product rule */ +impl<'a, 'b, T: DualNum, F: Float, M: Dim, N: Dim> Mul<&'a HyperDualVec> + for &'b HyperDualVec +where + DefaultAllocator: Allocator + Allocator + Allocator, +{ + type Output = HyperDualVec; + #[inline] + fn mul(self, other: &HyperDualVec) -> HyperDualVec { + HyperDualVec::new( + self.re.clone() * other.re.clone(), + &other.eps1 * self.re.clone() + &self.eps1 * other.re.clone(), + &other.eps2 * self.re.clone() + &self.eps2 * other.re.clone(), + &other.eps1eps2 * self.re.clone() + + &self.eps1 * &other.eps2 + + &other.eps1 * &self.eps2 + + &self.eps1eps2 * other.re.clone(), + ) + } +} + +/* quotient rule */ +impl<'a, 'b, T: DualNum, F: Float, M: Dim, N: Dim> Div<&'a HyperDualVec> + for &'b HyperDualVec +where + DefaultAllocator: Allocator + Allocator + Allocator, +{ + type Output = HyperDualVec; + #[inline] + fn div(self, other: &HyperDualVec) -> HyperDualVec { + let inv = other.re.recip(); + let inv2 = inv.clone() * &inv; + HyperDualVec::new( + self.re.clone() * &inv, + (&self.eps1 * other.re.clone() - &other.eps1 * self.re.clone()) * inv2.clone(), + (&self.eps2 * other.re.clone() - &other.eps2 * self.re.clone()) * inv2.clone(), + &self.eps1eps2 * inv.clone() + - (&other.eps1eps2 * self.re.clone() + + &self.eps1 * &other.eps2 + + &other.eps1 * &self.eps2) + * inv2.clone() + + &other.eps1 + * &other.eps2 + * ((T::one() + T::one()) * self.re.clone() * inv2 * inv), + ) + } +} + +/* string conversions */ +impl, F: fmt::Display, M: Dim, N: Dim> fmt::Display for HyperDualVec +where + DefaultAllocator: Allocator + Allocator + Allocator, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.re)?; + self.eps1.fmt(f, "ε1")?; + self.eps2.fmt(f, "ε2")?; + self.eps1eps2.fmt(f, "ε1ε2") + } +} + +impl_second_derivatives!(HyperDualVec, [eps1, eps2, eps1eps2], [M, N]); +impl_dual!(HyperDualVec, [eps1, eps2, eps1eps2], [M, N]); diff --git a/src/lib.rs b/src/lib.rs index 7ea40f7..cb365ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,16 +55,19 @@ mod bessel; mod derivative; mod dual; mod dual2; +mod dual2_vec; mod dual3; mod dual_vec; mod hyperdual; +mod hyperdual_vec; mod hyperhyperdual; pub use bessel::BesselDual; pub use derivative::Derivative; pub use dual::{first_derivative, try_first_derivative, Dual, Dual32, Dual64}; -pub use dual2::{ - hessian, second_derivative, try_hessian, try_second_derivative, Dual2, Dual2DVec32, - Dual2DVec64, Dual2SVec32, Dual2SVec64, Dual2Vec, Dual2Vec32, Dual2Vec64, Dual2_32, Dual2_64, +pub use dual2::{second_derivative, try_second_derivative, Dual2, Dual2_32, Dual2_64}; +pub use dual2_vec::{ + hessian, try_hessian, Dual2DVec32, Dual2DVec64, Dual2SVec32, Dual2SVec64, Dual2Vec, Dual2Vec32, + Dual2Vec64, }; pub use dual3::{third_derivative, try_third_derivative, Dual3, Dual3_32, Dual3_64}; pub use dual_vec::{ @@ -72,8 +75,10 @@ pub use dual_vec::{ DualVec, DualVec32, DualVec64, }; pub use hyperdual::{ - partial_hessian, second_partial_derivative, try_partial_hessian, try_second_partial_derivative, - HyperDual, HyperDual32, HyperDual64, HyperDualDVec32, HyperDualDVec64, HyperDualSVec32, + second_partial_derivative, try_second_partial_derivative, HyperDual, HyperDual32, HyperDual64, +}; +pub use hyperdual_vec::{ + partial_hessian, try_partial_hessian, HyperDualDVec32, HyperDualDVec64, HyperDualSVec32, HyperDualSVec64, HyperDualVec, HyperDualVec32, HyperDualVec64, }; pub use hyperhyperdual::{ diff --git a/src/linalg.rs b/src/linalg.rs index 0ece436..d53226c 100644 --- a/src/linalg.rs +++ b/src/linalg.rs @@ -1,5 +1,7 @@ #![allow(clippy::assign_op_pattern)] -use crate::{Dual, Dual2Vec, Dual3, DualNum, DualVec, HyperDualVec, HyperHyperDual}; +use crate::{ + Dual, Dual2, Dual2Vec, Dual3, DualNum, DualVec, HyperDual, HyperDualVec, HyperHyperDual, +}; use nalgebra::allocator::Allocator; use nalgebra::{DefaultAllocator, Dim, U1}; use ndarray::{Array1, Array2, ScalarOperand}; @@ -13,12 +15,14 @@ impl, F: Clone + 'static, N: Dim> ScalarOperand for DualVec { } +impl, F: Clone + 'static> ScalarOperand for Dual2 {} impl, F: Clone + 'static, N: Dim> ScalarOperand for Dual2Vec where DefaultAllocator: Allocator + Allocator { } impl, F: Clone + 'static> ScalarOperand for Dual3 {} impl, F: Clone + 'static> ScalarOperand for HyperHyperDual {} +impl, F: Clone + 'static> ScalarOperand for HyperDual {} impl, F: Clone + 'static, M: Dim, N: Dim> ScalarOperand for HyperDualVec where DefaultAllocator: Allocator + Allocator + Allocator { diff --git a/tests/test_dual.rs b/tests/test_dual.rs index 49c1ef0..0dea84b 100644 --- a/tests/test_dual.rs +++ b/tests/test_dual.rs @@ -4,369 +4,369 @@ use num_dual::*; fn test_dual_recip() { let res = Dual64::from(1.2).derivative().recip(); assert!((res.re - 0.833333333333333).abs() < 1e-12); - assert!((res.eps.unwrap() - -0.694444444444445).abs() < 1e-12); + assert!((res.eps - -0.694444444444445).abs() < 1e-12); } #[test] fn test_dual_exp() { let res = Dual64::from(1.2).derivative().exp(); assert!((res.re - 3.32011692273655).abs() < 1e-12); - assert!((res.eps.unwrap() - 3.32011692273655).abs() < 1e-12); + assert!((res.eps - 3.32011692273655).abs() < 1e-12); } #[test] fn test_dual_exp_m1() { let res = Dual64::from(1.2).derivative().exp_m1(); assert!((res.re - 2.32011692273655).abs() < 1e-12); - assert!((res.eps.unwrap() - 3.32011692273655).abs() < 1e-12); + assert!((res.eps - 3.32011692273655).abs() < 1e-12); } #[test] fn test_dual_exp2() { let res = Dual64::from(1.2).derivative().exp2(); assert!((res.re - 2.29739670999407).abs() < 1e-12); - assert!((res.eps.unwrap() - 1.59243405216008).abs() < 1e-12); + assert!((res.eps - 1.59243405216008).abs() < 1e-12); } #[test] fn test_dual_ln() { let res = Dual64::from(1.2).derivative().ln(); assert!((res.re - 0.182321556793955).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.833333333333333).abs() < 1e-12); + assert!((res.eps - 0.833333333333333).abs() < 1e-12); } #[test] fn test_dual_log() { let res = Dual64::from(1.2).derivative().log(4.2); assert!((res.re - 0.127045866345188).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.580685888982970).abs() < 1e-12); + assert!((res.eps - 0.580685888982970).abs() < 1e-12); } #[test] fn test_dual_ln_1p() { let res = Dual64::from(1.2).derivative().ln_1p(); assert!((res.re - 0.788457360364270).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.454545454545455).abs() < 1e-12); + assert!((res.eps - 0.454545454545455).abs() < 1e-12); } #[test] fn test_dual_log2() { let res = Dual64::from(1.2).derivative().log2(); assert!((res.re - 0.263034405833794).abs() < 1e-12); - assert!((res.eps.unwrap() - 1.20224586740747).abs() < 1e-12); + assert!((res.eps - 1.20224586740747).abs() < 1e-12); } #[test] fn test_dual_log10() { let res = Dual64::from(1.2).derivative().log10(); assert!((res.re - 0.0791812460476248).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.361912068252710).abs() < 1e-12); + assert!((res.eps - 0.361912068252710).abs() < 1e-12); } #[test] fn test_dual_sqrt() { let res = Dual64::from(1.2).derivative().sqrt(); assert!((res.re - 1.09544511501033).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.456435464587638).abs() < 1e-12); + assert!((res.eps - 0.456435464587638).abs() < 1e-12); } #[test] fn test_dual_cbrt() { let res = Dual64::from(1.2).derivative().cbrt(); assert!((res.re - 1.06265856918261).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.295182935884059).abs() < 1e-12); + assert!((res.eps - 0.295182935884059).abs() < 1e-12); } #[test] fn test_dual_powf() { let res = Dual64::from(1.2).derivative().powf(4.2); assert!((res.re - 2.15060788316847).abs() < 1e-12); - assert!((res.eps.unwrap() - 7.52712759108966).abs() < 1e-12); + assert!((res.eps - 7.52712759108966).abs() < 1e-12); } #[test] fn test_dual_powf_0() { let res = Dual64::from(0.0).derivative().powf(0.0); assert!((res.re - 1.00000000000000).abs() < 1e-12); - assert!((res.eps.unwrap()).abs() < 1e-12); + assert!((res.eps).abs() < 1e-12); } #[test] fn test_dual_powf_1() { let res = Dual64::from(0.0).derivative().powf(1.0); assert!((res.re).abs() < 1e-12); - assert!((res.eps.unwrap() - 1.00000000000000).abs() < 1e-12); + assert!((res.eps - 1.00000000000000).abs() < 1e-12); } #[test] fn test_dual_powf_2() { let res = Dual64::from(0.0).derivative().powf(2.0); assert!((res.re).abs() < 1e-12); - assert!((res.eps.unwrap()).abs() < 1e-12); + assert!((res.eps).abs() < 1e-12); } #[test] fn test_dual_powf_3() { let res = Dual64::from(0.0).derivative().powf(3.0); assert!((res.re).abs() < 1e-12); - assert!((res.eps.unwrap()).abs() < 1e-12); + assert!((res.eps).abs() < 1e-12); } #[test] fn test_dual_powf_4() { let res = Dual64::from(0.0).derivative().powf(4.0); assert!((res.re).abs() < 1e-12); - assert!((res.eps.unwrap()).abs() < 1e-12); + assert!((res.eps).abs() < 1e-12); } #[test] fn test_dual_powi() { let res = Dual64::from(1.2).derivative().powi(6); assert!((res.re - 2.98598400000000).abs() < 1e-12); - assert!((res.eps.unwrap() - 14.9299200000000).abs() < 1e-12); + assert!((res.eps - 14.9299200000000).abs() < 1e-12); } #[test] fn test_dual_powi_0() { let res = Dual64::from(0.0).derivative().powi(0); assert!((res.re - 1.00000000000000).abs() < 1e-12); - assert!((res.eps.unwrap()).abs() < 1e-12); + assert!((res.eps).abs() < 1e-12); } #[test] fn test_dual_powi_1() { let res = Dual64::from(0.0).derivative().powi(1); assert!((res.re).abs() < 1e-12); - assert!((res.eps.unwrap() - 1.00000000000000).abs() < 1e-12); + assert!((res.eps - 1.00000000000000).abs() < 1e-12); } #[test] fn test_dual_powi_2() { let res = Dual64::from(0.0).derivative().powi(2); assert!((res.re).abs() < 1e-12); - assert!((res.eps.unwrap()).abs() < 1e-12); + assert!((res.eps).abs() < 1e-12); } #[test] fn test_dual_powi_3() { let res = Dual64::from(0.0).derivative().powi(3); assert!((res.re).abs() < 1e-12); - assert!((res.eps.unwrap()).abs() < 1e-12); + assert!((res.eps).abs() < 1e-12); } #[test] fn test_dual_powi_4() { let res = Dual64::from(0.0).derivative().powi(4); assert!((res.re).abs() < 1e-12); - assert!((res.eps.unwrap()).abs() < 1e-12); + assert!((res.eps).abs() < 1e-12); } #[test] fn test_dual_sin() { let res = Dual64::from(1.2).derivative().sin(); assert!((res.re - 0.932039085967226).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.362357754476674).abs() < 1e-12); + assert!((res.eps - 0.362357754476674).abs() < 1e-12); } #[test] fn test_dual_cos() { let res = Dual64::from(1.2).derivative().cos(); assert!((res.re - 0.362357754476674).abs() < 1e-12); - assert!((res.eps.unwrap() - -0.932039085967226).abs() < 1e-12); + assert!((res.eps - -0.932039085967226).abs() < 1e-12); } #[test] fn test_dual_tan() { let res = Dual64::from(1.2).derivative().tan(); assert!((res.re - 2.57215162212632).abs() < 1e-12); - assert!((res.eps.unwrap() - 7.61596396720705).abs() < 1e-12); + assert!((res.eps - 7.61596396720705).abs() < 1e-12); } #[test] fn test_dual_asin() { let res = Dual64::from(0.2).derivative().asin(); assert!((res.re - 0.201357920790331).abs() < 1e-12); - assert!((res.eps.unwrap() - 1.02062072615966).abs() < 1e-12); + assert!((res.eps - 1.02062072615966).abs() < 1e-12); } #[test] fn test_dual_acos() { let res = Dual64::from(0.2).derivative().acos(); assert!((res.re - 1.36943840600457).abs() < 1e-12); - assert!((res.eps.unwrap() - -1.02062072615966).abs() < 1e-12); + assert!((res.eps - -1.02062072615966).abs() < 1e-12); } #[test] fn test_dual_atan() { let res = Dual64::from(0.2).derivative().atan(); assert!((res.re - 0.197395559849881).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.961538461538462).abs() < 1e-12); + assert!((res.eps - 0.961538461538462).abs() < 1e-12); } #[test] fn test_dual_sinh() { let res = Dual64::from(1.2).derivative().sinh(); assert!((res.re - 1.50946135541217).abs() < 1e-12); - assert!((res.eps.unwrap() - 1.81065556732437).abs() < 1e-12); + assert!((res.eps - 1.81065556732437).abs() < 1e-12); } #[test] fn test_dual_cosh() { let res = Dual64::from(1.2).derivative().cosh(); assert!((res.re - 1.81065556732437).abs() < 1e-12); - assert!((res.eps.unwrap() - 1.50946135541217).abs() < 1e-12); + assert!((res.eps - 1.50946135541217).abs() < 1e-12); } #[test] fn test_dual_tanh() { let res = Dual64::from(1.2).derivative().tanh(); assert!((res.re - 0.833654607012155).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.305019996207409).abs() < 1e-12); + assert!((res.eps - 0.305019996207409).abs() < 1e-12); } #[test] fn test_dual_asinh() { let res = Dual64::from(1.2).derivative().asinh(); assert!((res.re - 1.01597313417969).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.640184399664480).abs() < 1e-12); + assert!((res.eps - 0.640184399664480).abs() < 1e-12); } #[test] fn test_dual_acosh() { let res = Dual64::from(1.2).derivative().acosh(); assert!((res.re - 0.622362503714779).abs() < 1e-12); - assert!((res.eps.unwrap() - 1.50755672288882).abs() < 1e-12); + assert!((res.eps - 1.50755672288882).abs() < 1e-12); } #[test] fn test_dual_atanh() { let res = Dual64::from(0.2).derivative().atanh(); assert!((res.re - 0.202732554054082).abs() < 1e-12); - assert!((res.eps.unwrap() - 1.04166666666667).abs() < 1e-12); + assert!((res.eps - 1.04166666666667).abs() < 1e-12); } #[test] fn test_dual_sph_j0() { let res = Dual64::from(1.2).derivative().sph_j0(); assert!((res.re - 0.776699238306022).abs() < 1e-12); - assert!((res.eps.unwrap() - -0.345284569857790).abs() < 1e-12); + assert!((res.eps - -0.345284569857790).abs() < 1e-12); } #[test] fn test_dual_sph_j1() { let res = Dual64::from(1.2).derivative().sph_j1(); assert!((res.re - 0.345284569857790).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.201224955209705).abs() < 1e-12); + assert!((res.eps - 0.201224955209705).abs() < 1e-12); } #[test] fn test_dual_sph_j2() { let res = Dual64::from(1.2).derivative().sph_j2(); assert!((res.re - 0.0865121863384538).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.129004104011656).abs() < 1e-12); + assert!((res.eps - 0.129004104011656).abs() < 1e-12); } #[test] fn test_dual_bessel_j0_0() { let res = Dual64::from(0.0).derivative().bessel_j0(); assert!((res.re - 1.00000000000000).abs() < 1e-12); - assert!((res.eps.unwrap()).abs() < 1e-12); + assert!((res.eps).abs() < 1e-12); } #[test] fn test_dual_bessel_j1_0() { let res = Dual64::from(0.0).derivative().bessel_j1(); assert!((res.re).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.500000000000000).abs() < 1e-12); + assert!((res.eps - 0.500000000000000).abs() < 1e-12); } #[test] fn test_dual_bessel_j2_0() { let res = Dual64::from(0.0).derivative().bessel_j2(); assert!((res.re).abs() < 1e-12); - assert!((res.eps.unwrap()).abs() < 1e-12); + assert!((res.eps).abs() < 1e-12); } #[test] fn test_dual_bessel_j0_1() { let res = Dual64::from(1.2).derivative().bessel_j0(); assert!((res.re - 0.671132744264363).abs() < 1e-12); - assert!((res.eps.unwrap() - -0.498289057567215).abs() < 1e-12); + assert!((res.eps - -0.498289057567215).abs() < 1e-12); } #[test] fn test_dual_bessel_j1_1() { let res = Dual64::from(1.2).derivative().bessel_j1(); assert!((res.re - 0.498289057567215).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.255891862958350).abs() < 1e-12); + assert!((res.eps - 0.255891862958350).abs() < 1e-12); } #[test] fn test_dual_bessel_j2_1() { let res = Dual64::from(1.2).derivative().bessel_j2(); assert!((res.re - 0.159349018347663).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.232707360321110).abs() < 1e-12); + assert!((res.eps - 0.232707360321110).abs() < 1e-12); } #[test] fn test_dual_bessel_j0_2() { let res = Dual64::from(7.2).derivative().bessel_j0(); assert!((res.re - 0.295070691400958).abs() < 1e-12); - assert!((res.eps.unwrap() - -0.0543274202223671).abs() < 1e-12); + assert!((res.eps - -0.0543274202223671).abs() < 1e-12); } #[test] fn test_dual_bessel_j1_2() { let res = Dual64::from(7.2).derivative().bessel_j1(); assert!((res.re - 0.0543274202223671).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.287525216370074).abs() < 1e-12); + assert!((res.eps - 0.287525216370074).abs() < 1e-12); } #[test] fn test_dual_bessel_j2_2() { let res = Dual64::from(7.2).derivative().bessel_j2(); assert!((res.re - -0.279979741339189).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.132099570594364).abs() < 1e-12); + assert!((res.eps - 0.132099570594364).abs() < 1e-12); } #[test] fn test_dual_bessel_j0_3() { let res = Dual64::from(-1.2).derivative().bessel_j0(); assert!((res.re - 0.671132744264363).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.498289057567215).abs() < 1e-12); + assert!((res.eps - 0.498289057567215).abs() < 1e-12); } #[test] fn test_dual_bessel_j1_3() { let res = Dual64::from(-1.2).derivative().bessel_j1(); assert!((res.re - -0.498289057567215).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.255891862958350).abs() < 1e-12); + assert!((res.eps - 0.255891862958350).abs() < 1e-12); } #[test] fn test_dual_bessel_j2_3() { let res = Dual64::from(-1.2).derivative().bessel_j2(); assert!((res.re - 0.159349018347663).abs() < 1e-12); - assert!((res.eps.unwrap() - -0.232707360321110).abs() < 1e-12); + assert!((res.eps - -0.232707360321110).abs() < 1e-12); } #[test] fn test_dual_bessel_j0_4() { let res = Dual64::from(-7.2).derivative().bessel_j0(); assert!((res.re - 0.295070691400958).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.0543274202223671).abs() < 1e-12); + assert!((res.eps - 0.0543274202223671).abs() < 1e-12); } #[test] fn test_dual_bessel_j1_4() { let res = Dual64::from(-7.2).derivative().bessel_j1(); assert!((res.re - -0.0543274202223671).abs() < 1e-12); - assert!((res.eps.unwrap() - 0.287525216370074).abs() < 1e-12); + assert!((res.eps - 0.287525216370074).abs() < 1e-12); } #[test] fn test_dual_bessel_j2_4() { let res = Dual64::from(-7.2).derivative().bessel_j2(); assert!((res.re - -0.279979741339189).abs() < 1e-12); - assert!((res.eps.unwrap() - -0.132099570594364).abs() < 1e-12); + assert!((res.eps - -0.132099570594364).abs() < 1e-12); } diff --git a/tests/test_dual2.rs b/tests/test_dual2.rs index 1a414d6..b7e8a26 100644 --- a/tests/test_dual2.rs +++ b/tests/test_dual2.rs @@ -4,422 +4,422 @@ use num_dual::*; fn test_dual2_recip() { let res = Dual2_64::from(1.2).derivative().recip(); assert!((res.re - 0.833333333333333).abs() < 1e-12); - assert!((res.v1.unwrap() - -0.694444444444445).abs() < 1e-12); - assert!((res.v2.unwrap() - 1.15740740740741).abs() < 1e-12); + assert!((res.v1 - -0.694444444444445).abs() < 1e-12); + assert!((res.v2 - 1.15740740740741).abs() < 1e-12); } #[test] fn test_dual2_exp() { let res = Dual2_64::from(1.2).derivative().exp(); assert!((res.re - 3.32011692273655).abs() < 1e-12); - assert!((res.v1.unwrap() - 3.32011692273655).abs() < 1e-12); - assert!((res.v2.unwrap() - 3.32011692273655).abs() < 1e-12); + assert!((res.v1 - 3.32011692273655).abs() < 1e-12); + assert!((res.v2 - 3.32011692273655).abs() < 1e-12); } #[test] fn test_dual2_exp_m1() { let res = Dual2_64::from(1.2).derivative().exp_m1(); assert!((res.re - 2.32011692273655).abs() < 1e-12); - assert!((res.v1.unwrap() - 3.32011692273655).abs() < 1e-12); - assert!((res.v2.unwrap() - 3.32011692273655).abs() < 1e-12); + assert!((res.v1 - 3.32011692273655).abs() < 1e-12); + assert!((res.v2 - 3.32011692273655).abs() < 1e-12); } #[test] fn test_dual2_exp2() { let res = Dual2_64::from(1.2).derivative().exp2(); assert!((res.re - 2.29739670999407).abs() < 1e-12); - assert!((res.v1.unwrap() - 1.59243405216008).abs() < 1e-12); - assert!((res.v2.unwrap() - 1.10379117348241).abs() < 1e-12); + assert!((res.v1 - 1.59243405216008).abs() < 1e-12); + assert!((res.v2 - 1.10379117348241).abs() < 1e-12); } #[test] fn test_dual2_ln() { let res = Dual2_64::from(1.2).derivative().ln(); assert!((res.re - 0.182321556793955).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.833333333333333).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.694444444444445).abs() < 1e-12); + assert!((res.v1 - 0.833333333333333).abs() < 1e-12); + assert!((res.v2 - -0.694444444444445).abs() < 1e-12); } #[test] fn test_dual2_log() { let res = Dual2_64::from(1.2).derivative().log(4.2); assert!((res.re - 0.127045866345188).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.580685888982970).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.483904907485808).abs() < 1e-12); + assert!((res.v1 - 0.580685888982970).abs() < 1e-12); + assert!((res.v2 - -0.483904907485808).abs() < 1e-12); } #[test] fn test_dual2_ln_1p() { let res = Dual2_64::from(1.2).derivative().ln_1p(); assert!((res.re - 0.788457360364270).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.454545454545455).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.206611570247934).abs() < 1e-12); + assert!((res.v1 - 0.454545454545455).abs() < 1e-12); + assert!((res.v2 - -0.206611570247934).abs() < 1e-12); } #[test] fn test_dual2_log2() { let res = Dual2_64::from(1.2).derivative().log2(); assert!((res.re - 0.263034405833794).abs() < 1e-12); - assert!((res.v1.unwrap() - 1.20224586740747).abs() < 1e-12); - assert!((res.v2.unwrap() - -1.00187155617289).abs() < 1e-12); + assert!((res.v1 - 1.20224586740747).abs() < 1e-12); + assert!((res.v2 - -1.00187155617289).abs() < 1e-12); } #[test] fn test_dual2_log10() { let res = Dual2_64::from(1.2).derivative().log10(); assert!((res.re - 0.0791812460476248).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.361912068252710).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.301593390210592).abs() < 1e-12); + assert!((res.v1 - 0.361912068252710).abs() < 1e-12); + assert!((res.v2 - -0.301593390210592).abs() < 1e-12); } #[test] fn test_dual2_sqrt() { let res = Dual2_64::from(1.2).derivative().sqrt(); assert!((res.re - 1.09544511501033).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.456435464587638).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.190181443578183).abs() < 1e-12); + assert!((res.v1 - 0.456435464587638).abs() < 1e-12); + assert!((res.v2 - -0.190181443578183).abs() < 1e-12); } #[test] fn test_dual2_cbrt() { let res = Dual2_64::from(1.2).derivative().cbrt(); assert!((res.re - 1.06265856918261).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.295182935884059).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.163990519935588).abs() < 1e-12); + assert!((res.v1 - 0.295182935884059).abs() < 1e-12); + assert!((res.v2 - -0.163990519935588).abs() < 1e-12); } #[test] fn test_dual2_powf() { let res = Dual2_64::from(1.2).derivative().powf(4.2); assert!((res.re - 2.15060788316847).abs() < 1e-12); - assert!((res.v1.unwrap() - 7.52712759108966).abs() < 1e-12); - assert!((res.v2.unwrap() - 20.0723402429058).abs() < 1e-12); + assert!((res.v1 - 7.52712759108966).abs() < 1e-12); + assert!((res.v2 - 20.0723402429058).abs() < 1e-12); } #[test] fn test_dual2_powf_0() { let res = Dual2_64::from(0.0).derivative().powf(0.0); assert!((res.re - 1.00000000000000).abs() < 1e-12); - assert!((res.v1.unwrap()).abs() < 1e-12); - assert!((res.v2.unwrap()).abs() < 1e-12); + assert!((res.v1).abs() < 1e-12); + assert!((res.v2).abs() < 1e-12); } #[test] fn test_dual2_powf_1() { let res = Dual2_64::from(0.0).derivative().powf(1.0); assert!((res.re).abs() < 1e-12); - assert!((res.v1.unwrap() - 1.00000000000000).abs() < 1e-12); - assert!((res.v2.unwrap()).abs() < 1e-12); + assert!((res.v1 - 1.00000000000000).abs() < 1e-12); + assert!((res.v2).abs() < 1e-12); } #[test] fn test_dual2_powf_2() { let res = Dual2_64::from(0.0).derivative().powf(2.0); assert!((res.re).abs() < 1e-12); - assert!((res.v1.unwrap()).abs() < 1e-12); - assert!((res.v2.unwrap() - 2.00000000000000).abs() < 1e-12); + assert!((res.v1).abs() < 1e-12); + assert!((res.v2 - 2.00000000000000).abs() < 1e-12); } #[test] fn test_dual2_powf_3() { let res = Dual2_64::from(0.0).derivative().powf(3.0); assert!((res.re).abs() < 1e-12); - assert!((res.v1.unwrap()).abs() < 1e-12); - assert!((res.v2.unwrap()).abs() < 1e-12); + assert!((res.v1).abs() < 1e-12); + assert!((res.v2).abs() < 1e-12); } #[test] fn test_dual2_powf_4() { let res = Dual2_64::from(0.0).derivative().powf(4.0); assert!((res.re).abs() < 1e-12); - assert!((res.v1.unwrap()).abs() < 1e-12); - assert!((res.v2.unwrap()).abs() < 1e-12); + assert!((res.v1).abs() < 1e-12); + assert!((res.v2).abs() < 1e-12); } #[test] fn test_dual2_powi() { let res = Dual2_64::from(1.2).derivative().powi(6); assert!((res.re - 2.98598400000000).abs() < 1e-12); - assert!((res.v1.unwrap() - 14.9299200000000).abs() < 1e-12); - assert!((res.v2.unwrap() - 62.2080000000000).abs() < 1e-12); + assert!((res.v1 - 14.9299200000000).abs() < 1e-12); + assert!((res.v2 - 62.2080000000000).abs() < 1e-12); } #[test] fn test_dual2_powi_0() { let res = Dual2_64::from(0.0).derivative().powi(0); assert!((res.re - 1.00000000000000).abs() < 1e-12); - assert!((res.v1.unwrap()).abs() < 1e-12); - assert!((res.v2.unwrap()).abs() < 1e-12); + assert!((res.v1).abs() < 1e-12); + assert!((res.v2).abs() < 1e-12); } #[test] fn test_dual2_powi_1() { let res = Dual2_64::from(0.0).derivative().powi(1); assert!((res.re).abs() < 1e-12); - assert!((res.v1.unwrap() - 1.00000000000000).abs() < 1e-12); - assert!((res.v2.unwrap()).abs() < 1e-12); + assert!((res.v1 - 1.00000000000000).abs() < 1e-12); + assert!((res.v2).abs() < 1e-12); } #[test] fn test_dual2_powi_2() { let res = Dual2_64::from(0.0).derivative().powi(2); assert!((res.re).abs() < 1e-12); - assert!((res.v1.unwrap()).abs() < 1e-12); - assert!((res.v2.unwrap() - 2.00000000000000).abs() < 1e-12); + assert!((res.v1).abs() < 1e-12); + assert!((res.v2 - 2.00000000000000).abs() < 1e-12); } #[test] fn test_dual2_powi_3() { let res = Dual2_64::from(0.0).derivative().powi(3); assert!((res.re).abs() < 1e-12); - assert!((res.v1.unwrap()).abs() < 1e-12); - assert!((res.v2.unwrap()).abs() < 1e-12); + assert!((res.v1).abs() < 1e-12); + assert!((res.v2).abs() < 1e-12); } #[test] fn test_dual2_powi_4() { let res = Dual2_64::from(0.0).derivative().powi(4); assert!((res.re).abs() < 1e-12); - assert!((res.v1.unwrap()).abs() < 1e-12); - assert!((res.v2.unwrap()).abs() < 1e-12); + assert!((res.v1).abs() < 1e-12); + assert!((res.v2).abs() < 1e-12); } #[test] fn test_dual2_sin() { let res = Dual2_64::from(1.2).derivative().sin(); assert!((res.re - 0.932039085967226).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.362357754476674).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.932039085967226).abs() < 1e-12); + assert!((res.v1 - 0.362357754476674).abs() < 1e-12); + assert!((res.v2 - -0.932039085967226).abs() < 1e-12); } #[test] fn test_dual2_cos() { let res = Dual2_64::from(1.2).derivative().cos(); assert!((res.re - 0.362357754476674).abs() < 1e-12); - assert!((res.v1.unwrap() - -0.932039085967226).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.362357754476674).abs() < 1e-12); + assert!((res.v1 - -0.932039085967226).abs() < 1e-12); + assert!((res.v2 - -0.362357754476674).abs() < 1e-12); } #[test] fn test_dual2_tan() { let res = Dual2_64::from(1.2).derivative().tan(); assert!((res.re - 2.57215162212632).abs() < 1e-12); - assert!((res.v1.unwrap() - 7.61596396720705).abs() < 1e-12); - assert!((res.v2.unwrap() - 39.1788281446144).abs() < 1e-12); + assert!((res.v1 - 7.61596396720705).abs() < 1e-12); + assert!((res.v2 - 39.1788281446144).abs() < 1e-12); } #[test] fn test_dual2_asin() { let res = Dual2_64::from(0.2).derivative().asin(); assert!((res.re - 0.201357920790331).abs() < 1e-12); - assert!((res.v1.unwrap() - 1.02062072615966).abs() < 1e-12); - assert!((res.v2.unwrap() - 0.212629317949929).abs() < 1e-12); + assert!((res.v1 - 1.02062072615966).abs() < 1e-12); + assert!((res.v2 - 0.212629317949929).abs() < 1e-12); } #[test] fn test_dual2_acos() { let res = Dual2_64::from(0.2).derivative().acos(); assert!((res.re - 1.36943840600457).abs() < 1e-12); - assert!((res.v1.unwrap() - -1.02062072615966).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.212629317949929).abs() < 1e-12); + assert!((res.v1 - -1.02062072615966).abs() < 1e-12); + assert!((res.v2 - -0.212629317949929).abs() < 1e-12); } #[test] fn test_dual2_atan() { let res = Dual2_64::from(0.2).derivative().atan(); assert!((res.re - 0.197395559849881).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.961538461538462).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.369822485207101).abs() < 1e-12); + assert!((res.v1 - 0.961538461538462).abs() < 1e-12); + assert!((res.v2 - -0.369822485207101).abs() < 1e-12); } #[test] fn test_dual2_sinh() { let res = Dual2_64::from(1.2).derivative().sinh(); assert!((res.re - 1.50946135541217).abs() < 1e-12); - assert!((res.v1.unwrap() - 1.81065556732437).abs() < 1e-12); - assert!((res.v2.unwrap() - 1.50946135541217).abs() < 1e-12); + assert!((res.v1 - 1.81065556732437).abs() < 1e-12); + assert!((res.v2 - 1.50946135541217).abs() < 1e-12); } #[test] fn test_dual2_cosh() { let res = Dual2_64::from(1.2).derivative().cosh(); assert!((res.re - 1.81065556732437).abs() < 1e-12); - assert!((res.v1.unwrap() - 1.50946135541217).abs() < 1e-12); - assert!((res.v2.unwrap() - 1.81065556732437).abs() < 1e-12); + assert!((res.v1 - 1.50946135541217).abs() < 1e-12); + assert!((res.v2 - 1.81065556732437).abs() < 1e-12); } #[test] fn test_dual2_tanh() { let res = Dual2_64::from(1.2).derivative().tanh(); assert!((res.re - 0.833654607012155).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.305019996207409).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.508562650138273).abs() < 1e-12); + assert!((res.v1 - 0.305019996207409).abs() < 1e-12); + assert!((res.v2 - -0.508562650138273).abs() < 1e-12); } #[test] fn test_dual2_asinh() { let res = Dual2_64::from(1.2).derivative().asinh(); assert!((res.re - 1.01597313417969).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.640184399664480).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.314844786720236).abs() < 1e-12); + assert!((res.v1 - 0.640184399664480).abs() < 1e-12); + assert!((res.v2 - -0.314844786720236).abs() < 1e-12); } #[test] fn test_dual2_acosh() { let res = Dual2_64::from(1.2).derivative().acosh(); assert!((res.re - 0.622362503714779).abs() < 1e-12); - assert!((res.v1.unwrap() - 1.50755672288882).abs() < 1e-12); - assert!((res.v2.unwrap() - -4.11151833515132).abs() < 1e-12); + assert!((res.v1 - 1.50755672288882).abs() < 1e-12); + assert!((res.v2 - -4.11151833515132).abs() < 1e-12); } #[test] fn test_dual2_atanh() { let res = Dual2_64::from(0.2).derivative().atanh(); assert!((res.re - 0.202732554054082).abs() < 1e-12); - assert!((res.v1.unwrap() - 1.04166666666667).abs() < 1e-12); - assert!((res.v2.unwrap() - 0.434027777777778).abs() < 1e-12); + assert!((res.v1 - 1.04166666666667).abs() < 1e-12); + assert!((res.v2 - 0.434027777777778).abs() < 1e-12); } #[test] fn test_dual2_sph_j0() { let res = Dual2_64::from(1.2).derivative().sph_j0(); assert!((res.re - 0.776699238306022).abs() < 1e-12); - assert!((res.v1.unwrap() - -0.345284569857790).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.201224955209705).abs() < 1e-12); + assert!((res.v1 - -0.345284569857790).abs() < 1e-12); + assert!((res.v2 - -0.201224955209705).abs() < 1e-12); } #[test] fn test_dual2_sph_j1() { let res = Dual2_64::from(1.2).derivative().sph_j1(); assert!((res.re - 0.345284569857790).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.201224955209705).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.201097592627034).abs() < 1e-12); + assert!((res.v1 - 0.201224955209705).abs() < 1e-12); + assert!((res.v2 - -0.201097592627034).abs() < 1e-12); } #[test] fn test_dual2_sph_j2() { let res = Dual2_64::from(1.2).derivative().sph_j2(); assert!((res.re - 0.0865121863384538).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.129004104011656).abs() < 1e-12); - assert!((res.v2.unwrap() - 0.0589484167190109).abs() < 1e-12); + assert!((res.v1 - 0.129004104011656).abs() < 1e-12); + assert!((res.v2 - 0.0589484167190109).abs() < 1e-12); } #[test] fn test_dual2_bessel_j0_0() { let res = Dual2_64::from(0.0).derivative().bessel_j0(); assert!((res.re - 1.00000000000000).abs() < 1e-12); - assert!((res.v1.unwrap()).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.500000000000000).abs() < 1e-12); + assert!((res.v1).abs() < 1e-12); + assert!((res.v2 - -0.500000000000000).abs() < 1e-12); } #[test] fn test_dual2_bessel_j1_0() { let res = Dual2_64::from(0.0).derivative().bessel_j1(); assert!((res.re).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.500000000000000).abs() < 1e-12); - assert!((res.v2.unwrap()).abs() < 1e-12); + assert!((res.v1 - 0.500000000000000).abs() < 1e-12); + assert!((res.v2).abs() < 1e-12); } #[test] fn test_dual2_bessel_j2_0() { let res = Dual2_64::from(0.0).derivative().bessel_j2(); assert!((res.re).abs() < 1e-12); - assert!((res.v1.unwrap()).abs() < 1e-12); - assert!((res.v2.unwrap() - 0.250000000000000).abs() < 1e-12); + assert!((res.v1).abs() < 1e-12); + assert!((res.v2 - 0.250000000000000).abs() < 1e-12); } #[test] fn test_dual2_bessel_j0_1() { let res = Dual2_64::from(1.2).derivative().bessel_j0(); assert!((res.re - 0.671132744264363).abs() < 1e-12); - assert!((res.v1.unwrap() - -0.498289057567215).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.255891862958350).abs() < 1e-12); + assert!((res.v1 - -0.498289057567215).abs() < 1e-12); + assert!((res.v2 - -0.255891862958350).abs() < 1e-12); } #[test] fn test_dual2_bessel_j1_1() { let res = Dual2_64::from(1.2).derivative().bessel_j1(); assert!((res.re - 0.498289057567215).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.255891862958350).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.365498208944163).abs() < 1e-12); + assert!((res.v1 - 0.255891862958350).abs() < 1e-12); + assert!((res.v2 - -0.365498208944163).abs() < 1e-12); } #[test] fn test_dual2_bessel_j2_1() { let res = Dual2_64::from(1.2).derivative().bessel_j2(); assert!((res.re - 0.159349018347663).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.232707360321110).abs() < 1e-12); - assert!((res.v2.unwrap() - 0.0893643434615870).abs() < 1e-12); + assert!((res.v1 - 0.232707360321110).abs() < 1e-12); + assert!((res.v2 - 0.0893643434615870).abs() < 1e-12); } #[test] fn test_dual2_bessel_j0_2() { let res = Dual2_64::from(7.2).derivative().bessel_j0(); assert!((res.re - 0.295070691400958).abs() < 1e-12); - assert!((res.v1.unwrap() - -0.0543274202223671).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.287525216370074).abs() < 1e-12); + assert!((res.v1 - -0.0543274202223671).abs() < 1e-12); + assert!((res.v2 - -0.287525216370074).abs() < 1e-12); } #[test] fn test_dual2_bessel_j1_2() { let res = Dual2_64::from(7.2).derivative().bessel_j1(); assert!((res.re - 0.0543274202223671).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.287525216370074).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.0932134954083656).abs() < 1e-12); + assert!((res.v1 - 0.287525216370074).abs() < 1e-12); + assert!((res.v2 - -0.0932134954083656).abs() < 1e-12); } #[test] fn test_dual2_bessel_j2_2() { let res = Dual2_64::from(7.2).derivative().bessel_j2(); assert!((res.re - -0.279979741339189).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.132099570594364).abs() < 1e-12); - assert!((res.v2.unwrap() - 0.240029203653306).abs() < 1e-12); + assert!((res.v1 - 0.132099570594364).abs() < 1e-12); + assert!((res.v2 - 0.240029203653306).abs() < 1e-12); } #[test] fn test_dual2_bessel_j0_3() { let res = Dual2_64::from(-1.2).derivative().bessel_j0(); assert!((res.re - 0.671132744264363).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.498289057567215).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.255891862958350).abs() < 1e-12); + assert!((res.v1 - 0.498289057567215).abs() < 1e-12); + assert!((res.v2 - -0.255891862958350).abs() < 1e-12); } #[test] fn test_dual2_bessel_j1_3() { let res = Dual2_64::from(-1.2).derivative().bessel_j1(); assert!((res.re - -0.498289057567215).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.255891862958350).abs() < 1e-12); - assert!((res.v2.unwrap() - 0.365498208944163).abs() < 1e-12); + assert!((res.v1 - 0.255891862958350).abs() < 1e-12); + assert!((res.v2 - 0.365498208944163).abs() < 1e-12); } #[test] fn test_dual2_bessel_j2_3() { let res = Dual2_64::from(-1.2).derivative().bessel_j2(); assert!((res.re - 0.159349018347663).abs() < 1e-12); - assert!((res.v1.unwrap() - -0.232707360321110).abs() < 1e-12); - assert!((res.v2.unwrap() - 0.0893643434615870).abs() < 1e-12); + assert!((res.v1 - -0.232707360321110).abs() < 1e-12); + assert!((res.v2 - 0.0893643434615870).abs() < 1e-12); } #[test] fn test_dual2_bessel_j0_4() { let res = Dual2_64::from(-7.2).derivative().bessel_j0(); assert!((res.re - 0.295070691400958).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.0543274202223671).abs() < 1e-12); - assert!((res.v2.unwrap() - -0.287525216370074).abs() < 1e-12); + assert!((res.v1 - 0.0543274202223671).abs() < 1e-12); + assert!((res.v2 - -0.287525216370074).abs() < 1e-12); } #[test] fn test_dual2_bessel_j1_4() { let res = Dual2_64::from(-7.2).derivative().bessel_j1(); assert!((res.re - -0.0543274202223671).abs() < 1e-12); - assert!((res.v1.unwrap() - 0.287525216370074).abs() < 1e-12); - assert!((res.v2.unwrap() - 0.0932134954083656).abs() < 1e-12); + assert!((res.v1 - 0.287525216370074).abs() < 1e-12); + assert!((res.v2 - 0.0932134954083656).abs() < 1e-12); } #[test] fn test_dual2_bessel_j2_4() { let res = Dual2_64::from(-7.2).derivative().bessel_j2(); assert!((res.re - -0.279979741339189).abs() < 1e-12); - assert!((res.v1.unwrap() - -0.132099570594364).abs() < 1e-12); - assert!((res.v2.unwrap() - 0.240029203653306).abs() < 1e-12); + assert!((res.v1 - -0.132099570594364).abs() < 1e-12); + assert!((res.v2 - 0.240029203653306).abs() < 1e-12); } diff --git a/tests/test_hyperdual.rs b/tests/test_hyperdual.rs index 7c38599..88fa9df 100644 --- a/tests/test_hyperdual.rs +++ b/tests/test_hyperdual.rs @@ -4,475 +4,475 @@ use num_dual::*; fn test_hyperdual_recip() { let res = HyperDual64::from(1.2).derivative1().derivative2().recip(); assert!((res.re - 0.833333333333333).abs() < 1e-12); - assert!((res.eps1.unwrap() - -0.694444444444445).abs() < 1e-12); - assert!((res.eps2.unwrap() - -0.694444444444445).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 1.15740740740741).abs() < 1e-12); + assert!((res.eps1 - -0.694444444444445).abs() < 1e-12); + assert!((res.eps2 - -0.694444444444445).abs() < 1e-12); + assert!((res.eps1eps2 - 1.15740740740741).abs() < 1e-12); } #[test] fn test_hyperdual_exp() { let res = HyperDual64::from(1.2).derivative1().derivative2().exp(); assert!((res.re - 3.32011692273655).abs() < 1e-12); - assert!((res.eps1.unwrap() - 3.32011692273655).abs() < 1e-12); - assert!((res.eps2.unwrap() - 3.32011692273655).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 3.32011692273655).abs() < 1e-12); + assert!((res.eps1 - 3.32011692273655).abs() < 1e-12); + assert!((res.eps2 - 3.32011692273655).abs() < 1e-12); + assert!((res.eps1eps2 - 3.32011692273655).abs() < 1e-12); } #[test] fn test_hyperdual_exp_m1() { let res = HyperDual64::from(1.2).derivative1().derivative2().exp_m1(); assert!((res.re - 2.32011692273655).abs() < 1e-12); - assert!((res.eps1.unwrap() - 3.32011692273655).abs() < 1e-12); - assert!((res.eps2.unwrap() - 3.32011692273655).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 3.32011692273655).abs() < 1e-12); + assert!((res.eps1 - 3.32011692273655).abs() < 1e-12); + assert!((res.eps2 - 3.32011692273655).abs() < 1e-12); + assert!((res.eps1eps2 - 3.32011692273655).abs() < 1e-12); } #[test] fn test_hyperdual_exp2() { let res = HyperDual64::from(1.2).derivative1().derivative2().exp2(); assert!((res.re - 2.29739670999407).abs() < 1e-12); - assert!((res.eps1.unwrap() - 1.59243405216008).abs() < 1e-12); - assert!((res.eps2.unwrap() - 1.59243405216008).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 1.10379117348241).abs() < 1e-12); + assert!((res.eps1 - 1.59243405216008).abs() < 1e-12); + assert!((res.eps2 - 1.59243405216008).abs() < 1e-12); + assert!((res.eps1eps2 - 1.10379117348241).abs() < 1e-12); } #[test] fn test_hyperdual_ln() { let res = HyperDual64::from(1.2).derivative1().derivative2().ln(); assert!((res.re - 0.182321556793955).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.833333333333333).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.833333333333333).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.694444444444445).abs() < 1e-12); + assert!((res.eps1 - 0.833333333333333).abs() < 1e-12); + assert!((res.eps2 - 0.833333333333333).abs() < 1e-12); + assert!((res.eps1eps2 - -0.694444444444445).abs() < 1e-12); } #[test] fn test_hyperdual_log() { let res = HyperDual64::from(1.2).derivative1().derivative2().log(4.2); assert!((res.re - 0.127045866345188).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.580685888982970).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.580685888982970).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.483904907485808).abs() < 1e-12); + assert!((res.eps1 - 0.580685888982970).abs() < 1e-12); + assert!((res.eps2 - 0.580685888982970).abs() < 1e-12); + assert!((res.eps1eps2 - -0.483904907485808).abs() < 1e-12); } #[test] fn test_hyperdual_ln_1p() { let res = HyperDual64::from(1.2).derivative1().derivative2().ln_1p(); assert!((res.re - 0.788457360364270).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.454545454545455).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.454545454545455).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.206611570247934).abs() < 1e-12); + assert!((res.eps1 - 0.454545454545455).abs() < 1e-12); + assert!((res.eps2 - 0.454545454545455).abs() < 1e-12); + assert!((res.eps1eps2 - -0.206611570247934).abs() < 1e-12); } #[test] fn test_hyperdual_log2() { let res = HyperDual64::from(1.2).derivative1().derivative2().log2(); assert!((res.re - 0.263034405833794).abs() < 1e-12); - assert!((res.eps1.unwrap() - 1.20224586740747).abs() < 1e-12); - assert!((res.eps2.unwrap() - 1.20224586740747).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -1.00187155617289).abs() < 1e-12); + assert!((res.eps1 - 1.20224586740747).abs() < 1e-12); + assert!((res.eps2 - 1.20224586740747).abs() < 1e-12); + assert!((res.eps1eps2 - -1.00187155617289).abs() < 1e-12); } #[test] fn test_hyperdual_log10() { let res = HyperDual64::from(1.2).derivative1().derivative2().log10(); assert!((res.re - 0.0791812460476248).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.361912068252710).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.361912068252710).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.301593390210592).abs() < 1e-12); + assert!((res.eps1 - 0.361912068252710).abs() < 1e-12); + assert!((res.eps2 - 0.361912068252710).abs() < 1e-12); + assert!((res.eps1eps2 - -0.301593390210592).abs() < 1e-12); } #[test] fn test_hyperdual_sqrt() { let res = HyperDual64::from(1.2).derivative1().derivative2().sqrt(); assert!((res.re - 1.09544511501033).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.456435464587638).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.456435464587638).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.190181443578183).abs() < 1e-12); + assert!((res.eps1 - 0.456435464587638).abs() < 1e-12); + assert!((res.eps2 - 0.456435464587638).abs() < 1e-12); + assert!((res.eps1eps2 - -0.190181443578183).abs() < 1e-12); } #[test] fn test_hyperdual_cbrt() { let res = HyperDual64::from(1.2).derivative1().derivative2().cbrt(); assert!((res.re - 1.06265856918261).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.295182935884059).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.295182935884059).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.163990519935588).abs() < 1e-12); + assert!((res.eps1 - 0.295182935884059).abs() < 1e-12); + assert!((res.eps2 - 0.295182935884059).abs() < 1e-12); + assert!((res.eps1eps2 - -0.163990519935588).abs() < 1e-12); } #[test] fn test_hyperdual_powf() { let res = HyperDual64::from(1.2).derivative1().derivative2().powf(4.2); assert!((res.re - 2.15060788316847).abs() < 1e-12); - assert!((res.eps1.unwrap() - 7.52712759108966).abs() < 1e-12); - assert!((res.eps2.unwrap() - 7.52712759108966).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 20.0723402429058).abs() < 1e-12); + assert!((res.eps1 - 7.52712759108966).abs() < 1e-12); + assert!((res.eps2 - 7.52712759108966).abs() < 1e-12); + assert!((res.eps1eps2 - 20.0723402429058).abs() < 1e-12); } #[test] fn test_hyperdual_powf_0() { let res = HyperDual64::from(0.0).derivative1().derivative2().powf(0.0); assert!((res.re - 1.00000000000000).abs() < 1e-12); - assert!((res.eps1.unwrap()).abs() < 1e-12); - assert!((res.eps2.unwrap()).abs() < 1e-12); - assert!((res.eps1eps2.unwrap()).abs() < 1e-12); + assert!((res.eps1).abs() < 1e-12); + assert!((res.eps2).abs() < 1e-12); + assert!((res.eps1eps2).abs() < 1e-12); } #[test] fn test_hyperdual_powf_1() { let res = HyperDual64::from(0.0).derivative1().derivative2().powf(1.0); assert!((res.re).abs() < 1e-12); - assert!((res.eps1.unwrap() - 1.00000000000000).abs() < 1e-12); - assert!((res.eps2.unwrap() - 1.00000000000000).abs() < 1e-12); - assert!((res.eps1eps2.unwrap()).abs() < 1e-12); + assert!((res.eps1 - 1.00000000000000).abs() < 1e-12); + assert!((res.eps2 - 1.00000000000000).abs() < 1e-12); + assert!((res.eps1eps2).abs() < 1e-12); } #[test] fn test_hyperdual_powf_2() { let res = HyperDual64::from(0.0).derivative1().derivative2().powf(2.0); assert!((res.re).abs() < 1e-12); - assert!((res.eps1.unwrap()).abs() < 1e-12); - assert!((res.eps2.unwrap()).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 2.00000000000000).abs() < 1e-12); + assert!((res.eps1).abs() < 1e-12); + assert!((res.eps2).abs() < 1e-12); + assert!((res.eps1eps2 - 2.00000000000000).abs() < 1e-12); } #[test] fn test_hyperdual_powf_3() { let res = HyperDual64::from(0.0).derivative1().derivative2().powf(3.0); assert!((res.re).abs() < 1e-12); - assert!((res.eps1.unwrap()).abs() < 1e-12); - assert!((res.eps2.unwrap()).abs() < 1e-12); - assert!((res.eps1eps2.unwrap()).abs() < 1e-12); + assert!((res.eps1).abs() < 1e-12); + assert!((res.eps2).abs() < 1e-12); + assert!((res.eps1eps2).abs() < 1e-12); } #[test] fn test_hyperdual_powf_4() { let res = HyperDual64::from(0.0).derivative1().derivative2().powf(4.0); assert!((res.re).abs() < 1e-12); - assert!((res.eps1.unwrap()).abs() < 1e-12); - assert!((res.eps2.unwrap()).abs() < 1e-12); - assert!((res.eps1eps2.unwrap()).abs() < 1e-12); + assert!((res.eps1).abs() < 1e-12); + assert!((res.eps2).abs() < 1e-12); + assert!((res.eps1eps2).abs() < 1e-12); } #[test] fn test_hyperdual_powi() { let res = HyperDual64::from(1.2).derivative1().derivative2().powi(6); assert!((res.re - 2.98598400000000).abs() < 1e-12); - assert!((res.eps1.unwrap() - 14.9299200000000).abs() < 1e-12); - assert!((res.eps2.unwrap() - 14.9299200000000).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 62.2080000000000).abs() < 1e-12); + assert!((res.eps1 - 14.9299200000000).abs() < 1e-12); + assert!((res.eps2 - 14.9299200000000).abs() < 1e-12); + assert!((res.eps1eps2 - 62.2080000000000).abs() < 1e-12); } #[test] fn test_hyperdual_powi_0() { let res = HyperDual64::from(0.0).derivative1().derivative2().powi(0); assert!((res.re - 1.00000000000000).abs() < 1e-12); - assert!((res.eps1.unwrap()).abs() < 1e-12); - assert!((res.eps2.unwrap()).abs() < 1e-12); - assert!((res.eps1eps2.unwrap()).abs() < 1e-12); + assert!((res.eps1).abs() < 1e-12); + assert!((res.eps2).abs() < 1e-12); + assert!((res.eps1eps2).abs() < 1e-12); } #[test] fn test_hyperdual_powi_1() { let res = HyperDual64::from(0.0).derivative1().derivative2().powi(1); assert!((res.re).abs() < 1e-12); - assert!((res.eps1.unwrap() - 1.00000000000000).abs() < 1e-12); - assert!((res.eps2.unwrap() - 1.00000000000000).abs() < 1e-12); - assert!((res.eps1eps2.unwrap()).abs() < 1e-12); + assert!((res.eps1 - 1.00000000000000).abs() < 1e-12); + assert!((res.eps2 - 1.00000000000000).abs() < 1e-12); + assert!((res.eps1eps2).abs() < 1e-12); } #[test] fn test_hyperdual_powi_2() { let res = HyperDual64::from(0.0).derivative1().derivative2().powi(2); assert!((res.re).abs() < 1e-12); - assert!((res.eps1.unwrap()).abs() < 1e-12); - assert!((res.eps2.unwrap()).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 2.00000000000000).abs() < 1e-12); + assert!((res.eps1).abs() < 1e-12); + assert!((res.eps2).abs() < 1e-12); + assert!((res.eps1eps2 - 2.00000000000000).abs() < 1e-12); } #[test] fn test_hyperdual_powi_3() { let res = HyperDual64::from(0.0).derivative1().derivative2().powi(3); assert!((res.re).abs() < 1e-12); - assert!((res.eps1.unwrap()).abs() < 1e-12); - assert!((res.eps2.unwrap()).abs() < 1e-12); - assert!((res.eps1eps2.unwrap()).abs() < 1e-12); + assert!((res.eps1).abs() < 1e-12); + assert!((res.eps2).abs() < 1e-12); + assert!((res.eps1eps2).abs() < 1e-12); } #[test] fn test_hyperdual_powi_4() { let res = HyperDual64::from(0.0).derivative1().derivative2().powi(4); assert!((res.re).abs() < 1e-12); - assert!((res.eps1.unwrap()).abs() < 1e-12); - assert!((res.eps2.unwrap()).abs() < 1e-12); - assert!((res.eps1eps2.unwrap()).abs() < 1e-12); + assert!((res.eps1).abs() < 1e-12); + assert!((res.eps2).abs() < 1e-12); + assert!((res.eps1eps2).abs() < 1e-12); } #[test] fn test_hyperdual_sin() { let res = HyperDual64::from(1.2).derivative1().derivative2().sin(); assert!((res.re - 0.932039085967226).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.362357754476674).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.362357754476674).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.932039085967226).abs() < 1e-12); + assert!((res.eps1 - 0.362357754476674).abs() < 1e-12); + assert!((res.eps2 - 0.362357754476674).abs() < 1e-12); + assert!((res.eps1eps2 - -0.932039085967226).abs() < 1e-12); } #[test] fn test_hyperdual_cos() { let res = HyperDual64::from(1.2).derivative1().derivative2().cos(); assert!((res.re - 0.362357754476674).abs() < 1e-12); - assert!((res.eps1.unwrap() - -0.932039085967226).abs() < 1e-12); - assert!((res.eps2.unwrap() - -0.932039085967226).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.362357754476674).abs() < 1e-12); + assert!((res.eps1 - -0.932039085967226).abs() < 1e-12); + assert!((res.eps2 - -0.932039085967226).abs() < 1e-12); + assert!((res.eps1eps2 - -0.362357754476674).abs() < 1e-12); } #[test] fn test_hyperdual_tan() { let res = HyperDual64::from(1.2).derivative1().derivative2().tan(); assert!((res.re - 2.57215162212632).abs() < 1e-12); - assert!((res.eps1.unwrap() - 7.61596396720705).abs() < 1e-12); - assert!((res.eps2.unwrap() - 7.61596396720705).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 39.1788281446144).abs() < 1e-12); + assert!((res.eps1 - 7.61596396720705).abs() < 1e-12); + assert!((res.eps2 - 7.61596396720705).abs() < 1e-12); + assert!((res.eps1eps2 - 39.1788281446144).abs() < 1e-12); } #[test] fn test_hyperdual_asin() { let res = HyperDual64::from(0.2).derivative1().derivative2().asin(); assert!((res.re - 0.201357920790331).abs() < 1e-12); - assert!((res.eps1.unwrap() - 1.02062072615966).abs() < 1e-12); - assert!((res.eps2.unwrap() - 1.02062072615966).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 0.212629317949929).abs() < 1e-12); + assert!((res.eps1 - 1.02062072615966).abs() < 1e-12); + assert!((res.eps2 - 1.02062072615966).abs() < 1e-12); + assert!((res.eps1eps2 - 0.212629317949929).abs() < 1e-12); } #[test] fn test_hyperdual_acos() { let res = HyperDual64::from(0.2).derivative1().derivative2().acos(); assert!((res.re - 1.36943840600457).abs() < 1e-12); - assert!((res.eps1.unwrap() - -1.02062072615966).abs() < 1e-12); - assert!((res.eps2.unwrap() - -1.02062072615966).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.212629317949929).abs() < 1e-12); + assert!((res.eps1 - -1.02062072615966).abs() < 1e-12); + assert!((res.eps2 - -1.02062072615966).abs() < 1e-12); + assert!((res.eps1eps2 - -0.212629317949929).abs() < 1e-12); } #[test] fn test_hyperdual_atan() { let res = HyperDual64::from(0.2).derivative1().derivative2().atan(); assert!((res.re - 0.197395559849881).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.961538461538462).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.961538461538462).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.369822485207101).abs() < 1e-12); + assert!((res.eps1 - 0.961538461538462).abs() < 1e-12); + assert!((res.eps2 - 0.961538461538462).abs() < 1e-12); + assert!((res.eps1eps2 - -0.369822485207101).abs() < 1e-12); } #[test] fn test_hyperdual_sinh() { let res = HyperDual64::from(1.2).derivative1().derivative2().sinh(); assert!((res.re - 1.50946135541217).abs() < 1e-12); - assert!((res.eps1.unwrap() - 1.81065556732437).abs() < 1e-12); - assert!((res.eps2.unwrap() - 1.81065556732437).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 1.50946135541217).abs() < 1e-12); + assert!((res.eps1 - 1.81065556732437).abs() < 1e-12); + assert!((res.eps2 - 1.81065556732437).abs() < 1e-12); + assert!((res.eps1eps2 - 1.50946135541217).abs() < 1e-12); } #[test] fn test_hyperdual_cosh() { let res = HyperDual64::from(1.2).derivative1().derivative2().cosh(); assert!((res.re - 1.81065556732437).abs() < 1e-12); - assert!((res.eps1.unwrap() - 1.50946135541217).abs() < 1e-12); - assert!((res.eps2.unwrap() - 1.50946135541217).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 1.81065556732437).abs() < 1e-12); + assert!((res.eps1 - 1.50946135541217).abs() < 1e-12); + assert!((res.eps2 - 1.50946135541217).abs() < 1e-12); + assert!((res.eps1eps2 - 1.81065556732437).abs() < 1e-12); } #[test] fn test_hyperdual_tanh() { let res = HyperDual64::from(1.2).derivative1().derivative2().tanh(); assert!((res.re - 0.833654607012155).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.305019996207409).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.305019996207409).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.508562650138273).abs() < 1e-12); + assert!((res.eps1 - 0.305019996207409).abs() < 1e-12); + assert!((res.eps2 - 0.305019996207409).abs() < 1e-12); + assert!((res.eps1eps2 - -0.508562650138273).abs() < 1e-12); } #[test] fn test_hyperdual_asinh() { let res = HyperDual64::from(1.2).derivative1().derivative2().asinh(); assert!((res.re - 1.01597313417969).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.640184399664480).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.640184399664480).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.314844786720236).abs() < 1e-12); + assert!((res.eps1 - 0.640184399664480).abs() < 1e-12); + assert!((res.eps2 - 0.640184399664480).abs() < 1e-12); + assert!((res.eps1eps2 - -0.314844786720236).abs() < 1e-12); } #[test] fn test_hyperdual_acosh() { let res = HyperDual64::from(1.2).derivative1().derivative2().acosh(); assert!((res.re - 0.622362503714779).abs() < 1e-12); - assert!((res.eps1.unwrap() - 1.50755672288882).abs() < 1e-12); - assert!((res.eps2.unwrap() - 1.50755672288882).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -4.11151833515132).abs() < 1e-12); + assert!((res.eps1 - 1.50755672288882).abs() < 1e-12); + assert!((res.eps2 - 1.50755672288882).abs() < 1e-12); + assert!((res.eps1eps2 - -4.11151833515132).abs() < 1e-12); } #[test] fn test_hyperdual_atanh() { let res = HyperDual64::from(0.2).derivative1().derivative2().atanh(); assert!((res.re - 0.202732554054082).abs() < 1e-12); - assert!((res.eps1.unwrap() - 1.04166666666667).abs() < 1e-12); - assert!((res.eps2.unwrap() - 1.04166666666667).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 0.434027777777778).abs() < 1e-12); + assert!((res.eps1 - 1.04166666666667).abs() < 1e-12); + assert!((res.eps2 - 1.04166666666667).abs() < 1e-12); + assert!((res.eps1eps2 - 0.434027777777778).abs() < 1e-12); } #[test] fn test_hyperdual_sph_j0() { let res = HyperDual64::from(1.2).derivative1().derivative2().sph_j0(); assert!((res.re - 0.776699238306022).abs() < 1e-12); - assert!((res.eps1.unwrap() - -0.345284569857790).abs() < 1e-12); - assert!((res.eps2.unwrap() - -0.345284569857790).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.201224955209705).abs() < 1e-12); + assert!((res.eps1 - -0.345284569857790).abs() < 1e-12); + assert!((res.eps2 - -0.345284569857790).abs() < 1e-12); + assert!((res.eps1eps2 - -0.201224955209705).abs() < 1e-12); } #[test] fn test_hyperdual_sph_j1() { let res = HyperDual64::from(1.2).derivative1().derivative2().sph_j1(); assert!((res.re - 0.345284569857790).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.201224955209705).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.201224955209705).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.201097592627034).abs() < 1e-12); + assert!((res.eps1 - 0.201224955209705).abs() < 1e-12); + assert!((res.eps2 - 0.201224955209705).abs() < 1e-12); + assert!((res.eps1eps2 - -0.201097592627034).abs() < 1e-12); } #[test] fn test_hyperdual_sph_j2() { let res = HyperDual64::from(1.2).derivative1().derivative2().sph_j2(); assert!((res.re - 0.0865121863384538).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.129004104011656).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.129004104011656).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 0.0589484167190109).abs() < 1e-12); + assert!((res.eps1 - 0.129004104011656).abs() < 1e-12); + assert!((res.eps2 - 0.129004104011656).abs() < 1e-12); + assert!((res.eps1eps2 - 0.0589484167190109).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j0_0() { let res = HyperDual64::from(0.0).derivative1().derivative2().bessel_j0(); assert!((res.re - 1.00000000000000).abs() < 1e-12); - assert!((res.eps1.unwrap()).abs() < 1e-12); - assert!((res.eps2.unwrap()).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.500000000000000).abs() < 1e-12); + assert!((res.eps1).abs() < 1e-12); + assert!((res.eps2).abs() < 1e-12); + assert!((res.eps1eps2 - -0.500000000000000).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j1_0() { let res = HyperDual64::from(0.0).derivative1().derivative2().bessel_j1(); assert!((res.re).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.500000000000000).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.500000000000000).abs() < 1e-12); - assert!((res.eps1eps2.unwrap()).abs() < 1e-12); + assert!((res.eps1 - 0.500000000000000).abs() < 1e-12); + assert!((res.eps2 - 0.500000000000000).abs() < 1e-12); + assert!((res.eps1eps2).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j2_0() { let res = HyperDual64::from(0.0).derivative1().derivative2().bessel_j2(); assert!((res.re).abs() < 1e-12); - assert!((res.eps1.unwrap()).abs() < 1e-12); - assert!((res.eps2.unwrap()).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 0.250000000000000).abs() < 1e-12); + assert!((res.eps1).abs() < 1e-12); + assert!((res.eps2).abs() < 1e-12); + assert!((res.eps1eps2 - 0.250000000000000).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j0_1() { let res = HyperDual64::from(1.2).derivative1().derivative2().bessel_j0(); assert!((res.re - 0.671132744264363).abs() < 1e-12); - assert!((res.eps1.unwrap() - -0.498289057567215).abs() < 1e-12); - assert!((res.eps2.unwrap() - -0.498289057567215).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.255891862958350).abs() < 1e-12); + assert!((res.eps1 - -0.498289057567215).abs() < 1e-12); + assert!((res.eps2 - -0.498289057567215).abs() < 1e-12); + assert!((res.eps1eps2 - -0.255891862958350).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j1_1() { let res = HyperDual64::from(1.2).derivative1().derivative2().bessel_j1(); assert!((res.re - 0.498289057567215).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.255891862958350).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.255891862958350).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.365498208944163).abs() < 1e-12); + assert!((res.eps1 - 0.255891862958350).abs() < 1e-12); + assert!((res.eps2 - 0.255891862958350).abs() < 1e-12); + assert!((res.eps1eps2 - -0.365498208944163).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j2_1() { let res = HyperDual64::from(1.2).derivative1().derivative2().bessel_j2(); assert!((res.re - 0.159349018347663).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.232707360321110).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.232707360321110).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 0.0893643434615870).abs() < 1e-12); + assert!((res.eps1 - 0.232707360321110).abs() < 1e-12); + assert!((res.eps2 - 0.232707360321110).abs() < 1e-12); + assert!((res.eps1eps2 - 0.0893643434615870).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j0_2() { let res = HyperDual64::from(7.2).derivative1().derivative2().bessel_j0(); assert!((res.re - 0.295070691400958).abs() < 1e-12); - assert!((res.eps1.unwrap() - -0.0543274202223671).abs() < 1e-12); - assert!((res.eps2.unwrap() - -0.0543274202223671).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.287525216370074).abs() < 1e-12); + assert!((res.eps1 - -0.0543274202223671).abs() < 1e-12); + assert!((res.eps2 - -0.0543274202223671).abs() < 1e-12); + assert!((res.eps1eps2 - -0.287525216370074).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j1_2() { let res = HyperDual64::from(7.2).derivative1().derivative2().bessel_j1(); assert!((res.re - 0.0543274202223671).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.287525216370074).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.287525216370074).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.0932134954083656).abs() < 1e-12); + assert!((res.eps1 - 0.287525216370074).abs() < 1e-12); + assert!((res.eps2 - 0.287525216370074).abs() < 1e-12); + assert!((res.eps1eps2 - -0.0932134954083656).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j2_2() { let res = HyperDual64::from(7.2).derivative1().derivative2().bessel_j2(); assert!((res.re - -0.279979741339189).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.132099570594364).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.132099570594364).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 0.240029203653306).abs() < 1e-12); + assert!((res.eps1 - 0.132099570594364).abs() < 1e-12); + assert!((res.eps2 - 0.132099570594364).abs() < 1e-12); + assert!((res.eps1eps2 - 0.240029203653306).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j0_3() { let res = HyperDual64::from(-1.2).derivative1().derivative2().bessel_j0(); assert!((res.re - 0.671132744264363).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.498289057567215).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.498289057567215).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.255891862958350).abs() < 1e-12); + assert!((res.eps1 - 0.498289057567215).abs() < 1e-12); + assert!((res.eps2 - 0.498289057567215).abs() < 1e-12); + assert!((res.eps1eps2 - -0.255891862958350).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j1_3() { let res = HyperDual64::from(-1.2).derivative1().derivative2().bessel_j1(); assert!((res.re - -0.498289057567215).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.255891862958350).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.255891862958350).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 0.365498208944163).abs() < 1e-12); + assert!((res.eps1 - 0.255891862958350).abs() < 1e-12); + assert!((res.eps2 - 0.255891862958350).abs() < 1e-12); + assert!((res.eps1eps2 - 0.365498208944163).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j2_3() { let res = HyperDual64::from(-1.2).derivative1().derivative2().bessel_j2(); assert!((res.re - 0.159349018347663).abs() < 1e-12); - assert!((res.eps1.unwrap() - -0.232707360321110).abs() < 1e-12); - assert!((res.eps2.unwrap() - -0.232707360321110).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 0.0893643434615870).abs() < 1e-12); + assert!((res.eps1 - -0.232707360321110).abs() < 1e-12); + assert!((res.eps2 - -0.232707360321110).abs() < 1e-12); + assert!((res.eps1eps2 - 0.0893643434615870).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j0_4() { let res = HyperDual64::from(-7.2).derivative1().derivative2().bessel_j0(); assert!((res.re - 0.295070691400958).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.0543274202223671).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.0543274202223671).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - -0.287525216370074).abs() < 1e-12); + assert!((res.eps1 - 0.0543274202223671).abs() < 1e-12); + assert!((res.eps2 - 0.0543274202223671).abs() < 1e-12); + assert!((res.eps1eps2 - -0.287525216370074).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j1_4() { let res = HyperDual64::from(-7.2).derivative1().derivative2().bessel_j1(); assert!((res.re - -0.0543274202223671).abs() < 1e-12); - assert!((res.eps1.unwrap() - 0.287525216370074).abs() < 1e-12); - assert!((res.eps2.unwrap() - 0.287525216370074).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 0.0932134954083656).abs() < 1e-12); + assert!((res.eps1 - 0.287525216370074).abs() < 1e-12); + assert!((res.eps2 - 0.287525216370074).abs() < 1e-12); + assert!((res.eps1eps2 - 0.0932134954083656).abs() < 1e-12); } #[test] fn test_hyperdual_bessel_j2_4() { let res = HyperDual64::from(-7.2).derivative1().derivative2().bessel_j2(); assert!((res.re - -0.279979741339189).abs() < 1e-12); - assert!((res.eps1.unwrap() - -0.132099570594364).abs() < 1e-12); - assert!((res.eps2.unwrap() - -0.132099570594364).abs() < 1e-12); - assert!((res.eps1eps2.unwrap() - 0.240029203653306).abs() < 1e-12); + assert!((res.eps1 - -0.132099570594364).abs() < 1e-12); + assert!((res.eps2 - -0.132099570594364).abs() < 1e-12); + assert!((res.eps1eps2 - 0.240029203653306).abs() < 1e-12); } diff --git a/write_tests.ipynb b/write_tests.ipynb index e1de2cf..a53c0cb 100644 --- a/write_tests.ipynb +++ b/write_tests.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 13, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -28,7 +28,7 @@ " test += f\"fn test_dual_{method}{'' if index is None else f'_{index}'}() {{\\n\"\n", " test += f\" let res = Dual64::from({x0}).derivative().{method}({additional_param});\\n\"\n", " test += f\" assert!((res.re{check_zero(f.evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", - " test += f\" assert!((res.eps.unwrap(){check_zero(f.diff().evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", + " test += f\" assert!((res.eps{check_zero(f.diff().evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", " test += \"}\\n\\n\"\n", " return test\n", "\n", @@ -48,9 +48,9 @@ " test += f\"fn test_hyperdual_{method}{'' if index is None else f'_{index}'}() {{\\n\"\n", " test += f\" let res = HyperDual64::from({x0}).derivative1().derivative2().{method}({additional_param});\\n\"\n", " test += f\" assert!((res.re{check_zero(f.evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", - " test += f\" assert!((res.eps1.unwrap(){check_zero(f.diff().evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", - " test += f\" assert!((res.eps2.unwrap(){check_zero(f.diff().evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", - " test += f\" assert!((res.eps1eps2.unwrap(){check_zero(f.diff().diff().evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", + " test += f\" assert!((res.eps1{check_zero(f.diff().evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", + " test += f\" assert!((res.eps2{check_zero(f.diff().evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", + " test += f\" assert!((res.eps1eps2{check_zero(f.diff().diff().evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", " test += \"}\\n\\n\"\n", " return test\n", "\n", @@ -105,8 +105,8 @@ " test += f\"fn test_dual2_{method}{'' if index is None else f'_{index}'}() {{\\n\"\n", " test += f\" let res = Dual2_64::from({x0}).derivative().{method}({additional_param});\\n\"\n", " test += f\" assert!((res.re{check_zero(f.evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", - " test += f\" assert!((res.v1.unwrap(){check_zero(f.diff().evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", - " test += f\" assert!((res.v2.unwrap(){check_zero(f.diff().diff().evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", + " test += f\" assert!((res.v1{check_zero(f.diff().evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", + " test += f\" assert!((res.v2{check_zero(f.diff().diff().evalf(subs={x: x0}))}).abs() < {tol});\\n\"\n", " test += \"}\\n\\n\"\n", " return test\n", "\n", @@ -211,7 +211,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ From b59696ea843be80014bdad32bd76491c5f49acdf Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Wed, 31 May 2023 12:26:00 +0200 Subject: [PATCH 3/6] fix tests --- src/dual.rs | 2 +- src/dual2.rs | 28 ++++++++++++++-------------- src/lib.rs | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/dual.rs b/src/dual.rs index 4b54438..90c299c 100644 --- a/src/dual.rs +++ b/src/dual.rs @@ -49,7 +49,7 @@ impl + One, F> Dual { /// # use num_dual::{Dual64, DualNum}; /// let x = Dual64::from_re(5.0).derivative().powi(2); /// assert_eq!(x.re, 25.0); - /// assert_eq!(x.eps.unwrap(), 10.0); + /// assert_eq!(x.eps, 10.0); /// ``` #[inline] pub fn derivative(mut self) -> Self { diff --git a/src/dual2.rs b/src/dual2.rs index 04585bf..757145f 100644 --- a/src/dual2.rs +++ b/src/dual2.rs @@ -42,8 +42,8 @@ impl, F> Dual2 { /// # use num_dual::{Dual2, DualNum}; /// let x = Dual2::from_re(5.0).derivative().powi(2); /// assert_eq!(x.re, 25.0); // x² - /// assert_eq!(x.v1.unwrap(), 10.0); // 2x - /// assert_eq!(x.v2.unwrap(), 2.0); // 2 + /// assert_eq!(x.v1, 10.0); // 2x + /// assert_eq!(x.v2, 2.0); // 2 /// ``` /// /// Can also be used for higher order derivatives. @@ -52,11 +52,11 @@ impl, F> Dual2 { /// let x = Dual2::from_re(Dual64::from_re(5.0).derivative()) /// .derivative() /// .powi(2); - /// assert_eq!(x.re.re, 25.0); // x² - /// assert_eq!(x.re.eps.unwrap(), 10.0); // 2x - /// assert_eq!(x.v1.unwrap().re, 10.0); // 2x - /// assert_eq!(x.v1.unwrap().eps.unwrap(), 2.0); // 2 - /// assert_eq!(x.v2.unwrap().re, 2.0); // 2 + /// assert_eq!(x.re.re, 25.0); // x² + /// assert_eq!(x.re.eps, 10.0); // 2x + /// assert_eq!(x.v1.re, 10.0); // 2x + /// assert_eq!(x.v1.eps, 2.0); // 2 + /// assert_eq!(x.v2.re, 2.0); // 2 /// ``` #[inline] pub fn derivative(mut self) -> Self { @@ -85,14 +85,14 @@ impl, F> Dual2 { /// The argument can also be a dual number. /// ``` /// # use num_dual::{second_derivative, Dual2, Dual64, DualNum}; -/// let x = Dual64::new_scalar(5.0, 1.0); +/// let x = Dual64::new(5.0, 1.0); /// let (f, df, d2f) = second_derivative(|x| x.powi(3), x); -/// assert_eq!(f.re(), 125.0); // x³ -/// assert_eq!(f.eps.unwrap(), 75.0); // 3x² -/// assert_eq!(df.re, 75.0); // 3x² -/// assert_eq!(df.eps.unwrap(), 30.0); // 6x -/// assert_eq!(d2f.re, 30.0); // 6x -/// assert_eq!(d2f.eps.unwrap(), 6.0); // 6 +/// assert_eq!(f.re, 125.0); // x³ +/// assert_eq!(f.eps, 75.0); // 3x² +/// assert_eq!(df.re, 75.0); // 3x² +/// assert_eq!(df.eps, 30.0); // 6x +/// assert_eq!(d2f.re, 30.0); // 6x +/// assert_eq!(d2f.eps, 6.0); // 6 /// ``` pub fn second_derivative, F>(g: G, x: T) -> (T, T, T) where diff --git a/src/lib.rs b/src/lib.rs index cb365ba..ce1d6c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ //! assert_eq!(df, 75.0); //! //! // Manually construct the dual number -//! let x = Dual64::new_scalar(5.0, 1.0); +//! let x = Dual64::new(5.0, 1.0); //! println!("{}", foo(x)); // 125 + [75]ε //! //! // Calculate a gradient From 85553d869df2dad1ee6e89325ee6f5f2c187693e Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Wed, 31 May 2023 12:30:53 +0200 Subject: [PATCH 4/6] fix Python --- src/python/dual.rs | 4 ++-- src/python/dual2.rs | 12 ++++++------ src/python/hyperdual.rs | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/python/dual.rs b/src/python/dual.rs index 2117b87..7580c39 100644 --- a/src/python/dual.rs +++ b/src/python/dual.rs @@ -36,12 +36,12 @@ pub struct PyDual64(Dual64); impl PyDual64 { #[new] pub fn new(re: f64, eps: f64) -> Self { - Self(Dual64::new_scalar(re, eps)) + Self(Dual64::new(re, eps)) } #[getter] pub fn get_first_derivative(&self) -> f64 { - self.0.eps.unwrap() + self.0.eps } } diff --git a/src/python/dual2.rs b/src/python/dual2.rs index 8e358f8..6880cc8 100644 --- a/src/python/dual2.rs +++ b/src/python/dual2.rs @@ -39,17 +39,17 @@ pub struct PyDual2_64(Dual2_64); impl PyDual2_64 { #[new] fn new(eps: f64, v1: f64, v2: f64) -> Self { - Dual2::new_scalar(eps, v1, v2).into() + Dual2::new(eps, v1, v2).into() } #[getter] fn get_first_derivative(&self) -> f64 { - self.0.v1.unwrap() + self.0.v1 } #[getter] fn get_second_derivative(&self) -> f64 { - self.0.v2.unwrap() + self.0.v2 } } @@ -64,17 +64,17 @@ pub struct PyDual2Dual64(Dual2); impl PyDual2Dual64 { #[new] pub fn new(v0: PyDual64, v1: PyDual64, v2: PyDual64) -> Self { - Dual2::new_scalar(v0.into(), v1.into(), v2.into()).into() + Dual2::new(v0.into(), v1.into(), v2.into()).into() } #[getter] fn get_first_derivative(&self) -> PyDual64 { - self.0.v1.unwrap().into() + self.0.v1.into() } #[getter] fn get_second_derivative(&self) -> PyDual64 { - self.0.v2.unwrap().into() + self.0.v2.into() } } diff --git a/src/python/hyperdual.rs b/src/python/hyperdual.rs index 544c487..794181c 100644 --- a/src/python/hyperdual.rs +++ b/src/python/hyperdual.rs @@ -38,17 +38,17 @@ pub struct PyHyperDual64(HyperDual64); impl PyHyperDual64 { #[new] pub fn new(re: f64, eps1: f64, eps2: f64, eps1eps2: f64) -> Self { - HyperDual::new_scalar(re, eps1, eps2, eps1eps2).into() + HyperDual::new(re, eps1, eps2, eps1eps2).into() } #[getter] fn get_first_derivative(&self) -> (f64, f64) { - (self.0.eps1.unwrap(), self.0.eps2.unwrap()) + (self.0.eps1, self.0.eps2) } #[getter] fn get_second_derivative(&self) -> f64 { - self.0.eps1eps2.unwrap() + self.0.eps1eps2 } } @@ -63,17 +63,17 @@ pub struct PyHyperDualDual64(HyperDual); impl PyHyperDualDual64 { #[new] pub fn new(re: PyDual64, eps1: PyDual64, eps2: PyDual64, eps1eps2: PyDual64) -> Self { - HyperDual::new_scalar(re.into(), eps1.into(), eps2.into(), eps1eps2.into()).into() + HyperDual::new(re.into(), eps1.into(), eps2.into(), eps1eps2.into()).into() } #[getter] fn get_first_derivative(&self) -> (PyDual64, PyDual64) { - (self.0.eps1.unwrap().into(), self.0.eps2.unwrap().into()) + (self.0.eps1.into(), self.0.eps2.into()) } #[getter] fn get_second_derivative(&self) -> PyDual64 { - self.0.eps1eps2.unwrap().into() + self.0.eps1eps2.into() } } From fa8c12bb9c0dcab8d5b258e15b4c1582a8223e46 Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Wed, 31 May 2023 12:37:15 +0200 Subject: [PATCH 5/6] fix benchmark --- benches/benchmark.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index e1560dc..b233ad1 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -67,7 +67,7 @@ fn criterion_benchmark(c: &mut Criterion) { { let mut group = c.benchmark_group("Hard sphere contribution"); group.bench_function("f64", |b| b.iter(|| bench(1.0))); - group.bench_function("Dual64", |b| b.iter(|| bench(Dual64::new_scalar(1.0, 1.0)))); + group.bench_function("Dual64", |b| b.iter(|| bench(Dual64::new(1.0, 1.0)))); group.bench_function("DualVec64<2>", |b| { b.iter(|| { bench(DualVec::new( From 81c50c311ccf1a64dc3da54a7502f67b3e343d07 Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Wed, 31 May 2023 13:08:53 +0200 Subject: [PATCH 6/6] Release 0.7.1 --- CHANGELOG.md | 4 ++++ Cargo.toml | 2 +- src/dual.rs | 2 +- src/dual2.rs | 2 +- src/dual2_vec.rs | 2 +- src/dual_vec.rs | 2 +- src/hyperdual.rs | 14 +++++++------- src/hyperdual_vec.rs | 14 +++++++------- src/hyperhyperdual.rs | 22 +++++++++++----------- 9 files changed, 34 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64f7adc..66873f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.7.1] - 2023-05-31 +### Fixed +- Added dedicated implementations for scalar dual numbers (`Dual`, `Dual2`, `HyperDual`) to avoid a performance regression introduced in `0.7.0`. [#68](https://github.com/itt-ustutt/num-dual/pull/68) + ## [0.7.0] - 2023-05-29 ### Added - Added new `HyerHyperDual` number for the calculation of third partial derivatives. [#51](https://github.com/itt-ustutt/num-dual/pull/51) diff --git a/Cargo.toml b/Cargo.toml index 6340562..e50f09d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "num-dual" -version = "0.7.0" +version = "0.7.1" authors = ["Gernot Bauer ", "Philipp Rehner "] edition = "2018" diff --git a/src/dual.rs b/src/dual.rs index 90c299c..83f6260 100644 --- a/src/dual.rs +++ b/src/dual.rs @@ -10,7 +10,7 @@ use std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, }; -/// A dual number for the calculations of first derivatives. +/// A scalar dual number for the calculations of first derivatives. #[derive(Copy, Clone, Debug)] pub struct Dual, F> { /// Real part of the dual number diff --git a/src/dual2.rs b/src/dual2.rs index 757145f..feefd00 100644 --- a/src/dual2.rs +++ b/src/dual2.rs @@ -8,7 +8,7 @@ use std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, }; -/// A second order dual number for the calculation of second derivatives. +/// A scalar second order dual number for the calculation of second derivatives. #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub struct Dual2, F> { /// Real part of the second order dual number diff --git a/src/dual2_vec.rs b/src/dual2_vec.rs index 5fc1b11..6880325 100644 --- a/src/dual2_vec.rs +++ b/src/dual2_vec.rs @@ -10,7 +10,7 @@ use std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, }; -/// A second order dual number for the calculation of Hessians. +/// A vector second order dual number for the calculation of Hessians. #[derive(PartialEq, Eq, Clone, Debug)] pub struct Dual2Vec, F, D: Dim> where diff --git a/src/dual_vec.rs b/src/dual_vec.rs index 441bdf4..8c92b57 100644 --- a/src/dual_vec.rs +++ b/src/dual_vec.rs @@ -11,7 +11,7 @@ use std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, }; -/// A dual number for the calculations of gradients or Jacobians. +/// A vector dual number for the calculations of gradients or Jacobians. #[derive(Clone, Debug)] pub struct DualVec, F, D: Dim> where diff --git a/src/hyperdual.rs b/src/hyperdual.rs index 683238f..03ada49 100644 --- a/src/hyperdual.rs +++ b/src/hyperdual.rs @@ -8,16 +8,16 @@ use std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, }; -/// A hyper dual number for the calculation of second partial derivatives. +/// A scalar hyper-dual number for the calculation of second partial derivatives. #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub struct HyperDual, F> { - /// Real part of the hyper dual number + /// Real part of the hyper-dual number pub re: T, - /// Partial derivative part of the hyper dual number + /// Partial derivative part of the hyper-dual number pub eps1: T, - /// Partial derivative part of the hyper dual number + /// Partial derivative part of the hyper-dual number pub eps2: T, - /// Second partial derivative part of the hyper dual number + /// Second partial derivative part of the hyper-dual number pub eps1eps2: T, f: PhantomData, } @@ -26,7 +26,7 @@ pub type HyperDual32 = HyperDual; pub type HyperDual64 = HyperDual; impl, F> HyperDual { - /// Create a new hyper dual number from its fields. + /// Create a new hyper-dual number from its fields. #[inline] pub fn new(re: T, eps1: T, eps2: T, eps1eps2: T) -> Self { Self { @@ -56,7 +56,7 @@ impl, F> HyperDual { } impl, F> HyperDual { - /// Create a new hyper dual number from the real part. + /// Create a new hyper-dual number from the real part. #[inline] pub fn from_re(re: T) -> Self { Self::new(re, T::zero(), T::zero(), T::zero()) diff --git a/src/hyperdual_vec.rs b/src/hyperdual_vec.rs index a7ccc3b..ae186da 100644 --- a/src/hyperdual_vec.rs +++ b/src/hyperdual_vec.rs @@ -10,19 +10,19 @@ use std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, }; -/// A hyper dual number for the calculation of partial Hessians. +/// A vector hyper-dual number for the calculation of partial Hessians. #[derive(PartialEq, Eq, Clone, Debug)] pub struct HyperDualVec, F, M: Dim, N: Dim> where DefaultAllocator: Allocator + Allocator + Allocator, { - /// Real part of the hyper dual number + /// Real part of the hyper-dual number pub re: T, - /// Gradient part of the hyper dual number + /// Gradient part of the hyper-dual number pub eps1: Derivative, - /// Gradient part of the hyper dual number + /// Gradient part of the hyper-dual number pub eps2: Derivative, - /// Partial Hessian part of the hyper dual number + /// Partial Hessian part of the hyper-dual number pub eps1eps2: Derivative, f: PhantomData, } @@ -45,7 +45,7 @@ impl, F, M: Dim, N: Dim> HyperDualVec where DefaultAllocator: Allocator + Allocator + Allocator, { - /// Create a new hyper dual number from its fields. + /// Create a new hyper-dual number from its fields. #[inline] pub fn new( re: T, @@ -67,7 +67,7 @@ impl, F, M: Dim, N: Dim> HyperDualVec where DefaultAllocator: Allocator + Allocator + Allocator, { - /// Create a new hyper dual number from the real part. + /// Create a new hyper-dual number from the real part. #[inline] pub fn from_re(re: T) -> Self { Self::new( diff --git a/src/hyperhyperdual.rs b/src/hyperhyperdual.rs index f6d1e62..999f451 100644 --- a/src/hyperhyperdual.rs +++ b/src/hyperhyperdual.rs @@ -6,24 +6,24 @@ use std::iter::{Product, Sum}; use std::marker::PhantomData; use std::ops::*; -/// A scalar hyper hyper dual number for the calculation of third partial derivatives. +/// A scalar hyper-hyper-dual number for the calculation of third partial derivatives. #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub struct HyperHyperDual { - /// Real part of the hyper hyper dual number + /// Real part of the hyper-hyper-dual number pub re: T, - /// First partial derivative part of the hyper hyper dual number + /// First partial derivative part of the hyper-hyper-dual number pub eps1: T, - /// First partial derivative part of the hyper hyper dual number + /// First partial derivative part of the hyper-hyper-dual number pub eps2: T, - /// First partial derivative part of the hyper hyper dual number + /// First partial derivative part of the hyper-hyper-dual number pub eps3: T, - /// Second partial derivative part of the hyper hyper dual number + /// Second partial derivative part of the hyper-hyper-dual number pub eps1eps2: T, - /// Second partial derivative part of the hyper hyper dual number + /// Second partial derivative part of the hyper-hyper-dual number pub eps1eps3: T, - /// Second partial derivative part of the hyper hyper dual number + /// Second partial derivative part of the hyper-hyper-dual number pub eps2eps3: T, - /// Third partial derivative part of the hyper hyper dual number + /// Third partial derivative part of the hyper-hyper-dual number pub eps1eps2eps3: T, f: PhantomData, } @@ -32,7 +32,7 @@ pub type HyperHyperDual32 = HyperHyperDual; pub type HyperHyperDual64 = HyperHyperDual; impl, F> HyperHyperDual { - /// Create a new hyper hyper dual number from its fields. + /// Create a new hyper-hyper-dual number from its fields. #[inline] #[allow(clippy::too_many_arguments)] pub fn new( @@ -79,7 +79,7 @@ impl, F> HyperHyperDual { self } - /// Create a new hyper hyper dual number from the real part. + /// Create a new hyper-hyper-dual number from the real part. #[inline] pub fn from_re(re: T) -> Self { Self::new(