use ndarray::{Array1, Array2};
use std::ops::{Add, Div, Mul, Sub};
#[derive(Clone, Debug, PartialEq)]
pub struct Dual2Vec {
pub value: f64,
pub grad: Array1<f64>,
pub hess: Array2<f64>,
}
impl Dual2Vec {
#[inline]
pub fn variable(value: f64, i: usize, n: usize) -> Self {
let mut grad = Array1::<f64>::zeros(n);
grad[i] = 1.0;
let hess = Array2::<f64>::zeros((n, n));
Self { value, grad, hess }
}
#[inline]
pub fn constant(value: f64, n: usize) -> Self {
Self {
value,
grad: Array1::<f64>::zeros(n),
hess: Array2::<f64>::zeros((n, n)),
}
}
#[inline]
pub fn value(&self) -> f64 {
self.value
}
#[inline]
pub fn gradient(&self) -> &Array1<f64> {
&self.grad
}
#[inline]
pub fn hessian(&self) -> &Array2<f64> {
&self.hess
}
#[inline]
pub fn len(&self) -> usize {
self.grad.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.grad.is_empty()
}
pub fn symmetrize(&mut self) {
let n = self.hess.nrows();
debug_assert_eq!(
n,
self.hess.ncols(),
"Dual2Vec::symmetrize on non-square hess"
);
for i in 0..n {
for j in (i + 1)..n {
let avg = 0.5 * (self.hess[[i, j]] + self.hess[[j, i]]);
self.hess[[i, j]] = avg;
self.hess[[j, i]] = avg;
}
}
}
}
#[allow(dead_code)]
#[inline]
pub(crate) fn mirror_upper_triangle(h: &mut Array2<f64>) {
let n = h.nrows();
debug_assert_eq!(n, h.ncols(), "mirror_upper_triangle on non-square matrix");
for i in 0..n {
for j in (i + 1)..n {
h[[j, i]] = h[[i, j]];
}
}
}
#[inline]
fn shape_check(a: &Dual2Vec, b: &Dual2Vec) {
debug_assert_eq!(
a.grad.len(),
b.grad.len(),
"Dual2Vec gradient length mismatch: {} vs {}",
a.grad.len(),
b.grad.len()
);
debug_assert_eq!(
a.hess.shape(),
b.hess.shape(),
"Dual2Vec hessian shape mismatch"
);
}
impl Add<&Dual2Vec> for &Dual2Vec {
type Output = Dual2Vec;
#[inline]
fn add(self, rhs: &Dual2Vec) -> Dual2Vec {
shape_check(self, rhs);
Dual2Vec {
value: self.value + rhs.value,
grad: &self.grad + &rhs.grad,
hess: &self.hess + &rhs.hess,
}
}
}
impl Add for Dual2Vec {
type Output = Dual2Vec;
#[inline]
fn add(self, rhs: Dual2Vec) -> Dual2Vec {
(&self).add(&rhs)
}
}
impl Sub<&Dual2Vec> for &Dual2Vec {
type Output = Dual2Vec;
#[inline]
fn sub(self, rhs: &Dual2Vec) -> Dual2Vec {
shape_check(self, rhs);
Dual2Vec {
value: self.value - rhs.value,
grad: &self.grad - &rhs.grad,
hess: &self.hess - &rhs.hess,
}
}
}
impl Sub for Dual2Vec {
type Output = Dual2Vec;
#[inline]
fn sub(self, rhs: Dual2Vec) -> Dual2Vec {
(&self).sub(&rhs)
}
}
impl Mul<&Dual2Vec> for &Dual2Vec {
type Output = Dual2Vec;
#[inline]
fn mul(self, rhs: &Dual2Vec) -> Dual2Vec {
shape_check(self, rhs);
let a_val = self.value;
let b_val = rhs.value;
let n = self.grad.len();
let value = a_val * b_val;
let grad = a_val * &rhs.grad + b_val * &self.grad;
let mut hess = Array2::<f64>::zeros((n, n));
for i in 0..n {
for j in i..n {
let cross = self.grad[i] * rhs.grad[j] + rhs.grad[i] * self.grad[j];
hess[[i, j]] = a_val * rhs.hess[[i, j]] + b_val * self.hess[[i, j]] + cross;
}
}
mirror_upper_triangle(&mut hess);
Dual2Vec { value, grad, hess }
}
}
impl Mul for Dual2Vec {
type Output = Dual2Vec;
#[inline]
fn mul(self, rhs: Dual2Vec) -> Dual2Vec {
(&self).mul(&rhs)
}
}
impl Div<&Dual2Vec> for &Dual2Vec {
type Output = Dual2Vec;
#[inline]
fn div(self, rhs: &Dual2Vec) -> Dual2Vec {
shape_check(self, rhs);
let b_val = rhs.value;
debug_assert!(b_val != 0.0, "Dual2Vec::div by zero value");
let inv_b = 1.0 / b_val;
let n = self.grad.len();
let value = self.value * inv_b;
let grad = (&self.grad - value * &rhs.grad) * inv_b;
let mut hess = Array2::<f64>::zeros((n, n));
for i in 0..n {
for j in i..n {
let cross = grad[i] * rhs.grad[j] + rhs.grad[i] * grad[j];
hess[[i, j]] = (self.hess[[i, j]] - value * rhs.hess[[i, j]] - cross) * inv_b;
}
}
mirror_upper_triangle(&mut hess);
Dual2Vec { value, grad, hess }
}
}
impl Div for Dual2Vec {
type Output = Dual2Vec;
#[inline]
fn div(self, rhs: Dual2Vec) -> Dual2Vec {
(&self).div(&rhs)
}
}
macro_rules! impl_unary_dual2vec {
($name:ident, $f:expr, $fp:expr, $fpp:expr) => {
impl Dual2Vec {
#[doc = concat!("Applies `", stringify!($name), "` elementwise via the multi-variable chain rule.")]
#[doc = ""]
#[doc = "Propagates value, gradient, and Hessian in one forward pass. See"]
#[doc = "`src/dual2vec.rs` module docs for the chain-rule derivation."]
#[inline]
pub fn $name(self) -> Self {
let u = self.value;
let fv = ($f)(u);
let fp = ($fp)(u);
let fpp = ($fpp)(u);
let n = self.grad.len();
let grad = fp * &self.grad;
let mut hess = Array2::<f64>::zeros((n, n));
for i in 0..n {
for j in i..n {
hess[[i, j]] =
fpp * self.grad[i] * self.grad[j] + fp * self.hess[[i, j]];
}
}
mirror_upper_triangle(&mut hess);
Dual2Vec {
value: fv,
grad,
hess,
}
}
}
};
}
#[allow(unused_imports)]
pub(crate) use impl_unary_dual2vec;
impl_unary_dual2vec!(sin, |u: f64| u.sin(), |u: f64| u.cos(), |u: f64| -u.sin());
impl_unary_dual2vec!(cos, |u: f64| u.cos(), |u: f64| -u.sin(), |u: f64| -u.cos());
impl_unary_dual2vec!(
tan,
|u: f64| u.tan(),
|u: f64| {
let t = u.tan();
1.0 + t * t
},
|u: f64| {
let t = u.tan();
2.0 * t * (1.0 + t * t)
}
);
impl_unary_dual2vec!(exp, |u: f64| u.exp(), |u: f64| u.exp(), |u: f64| u.exp());
impl_unary_dual2vec!(ln, |u: f64| u.ln(), |u: f64| 1.0 / u, |u: f64| -1.0
/ (u * u));
impl_unary_dual2vec!(
sqrt,
|u: f64| u.sqrt(),
|u: f64| 0.5 / u.sqrt(),
|u: f64| -0.25 / (u * u.sqrt())
);
impl_unary_dual2vec!(
tanh,
|u: f64| u.tanh(),
|u: f64| {
let t = u.tanh();
1.0 - t * t
},
|u: f64| {
let t = u.tanh();
-2.0 * t * (1.0 - t * t)
}
);
impl_unary_dual2vec!(
atan,
|u: f64| u.atan(),
|u: f64| 1.0 / (1.0 + u * u),
|u: f64| {
let d = 1.0 + u * u;
-2.0 * u / (d * d)
}
);
impl_unary_dual2vec!(
asinh,
|u: f64| u.asinh(),
|u: f64| 1.0 / (1.0 + u * u).sqrt(),
|u: f64| {
let d = 1.0 + u * u;
-u / (d * d.sqrt())
}
);
impl_unary_dual2vec!(
erf,
|u: f64| crate::math::erf(u),
|u: f64| std::f64::consts::FRAC_2_SQRT_PI * (-u * u).exp(),
|u: f64| {
let fp = std::f64::consts::FRAC_2_SQRT_PI * (-u * u).exp();
-2.0 * u * fp
}
);
impl_unary_dual2vec!(
norm_cdf,
|u: f64| crate::math::norm_cdf(u),
|u: f64| crate::math::norm_pdf(u),
|u: f64| -u * crate::math::norm_pdf(u)
);
impl_unary_dual2vec!(
inv_norm_cdf,
|u: f64| crate::math::inv_norm_cdf(u),
|u: f64| {
let r = crate::math::inv_norm_cdf(u);
1.0 / crate::math::norm_pdf(r)
},
|u: f64| {
let r = crate::math::inv_norm_cdf(u);
let gp = 1.0 / crate::math::norm_pdf(r);
r * gp * gp
}
);
impl Dual2Vec {
#[inline]
pub fn powf(self, k: f64) -> Self {
let u = self.value;
let fv = u.powf(k);
let fp = k * u.powf(k - 1.0);
let fpp = k * (k - 1.0) * u.powf(k - 2.0);
let n = self.grad.len();
let grad = fp * &self.grad;
let mut hess = Array2::<f64>::zeros((n, n));
for i in 0..n {
for j in i..n {
hess[[i, j]] = fpp * self.grad[i] * self.grad[j] + fp * self.hess[[i, j]];
}
}
mirror_upper_triangle(&mut hess);
Dual2Vec {
value: fv,
grad,
hess,
}
}
#[inline]
pub fn powd(self, y: Dual2Vec) -> Self {
let ln_x = self.ln();
let prod = &y * &ln_x;
prod.exp()
}
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::array;
#[test]
fn test_variable_constructor() {
let x = Dual2Vec::variable(3.0, 0, 2);
assert_eq!(x.value(), 3.0);
assert_eq!(x.gradient(), &array![1.0, 0.0]);
assert_eq!(x.hessian(), &Array2::<f64>::zeros((2, 2)));
}
#[test]
fn test_variable_constructor_index_1() {
let y = Dual2Vec::variable(5.0, 1, 3);
assert_eq!(y.value(), 5.0);
assert_eq!(y.gradient(), &array![0.0, 1.0, 0.0]);
assert_eq!(y.hessian(), &Array2::<f64>::zeros((3, 3)));
}
#[test]
fn test_constant_constructor() {
let c = Dual2Vec::constant(7.5, 4);
assert_eq!(c.value(), 7.5);
assert_eq!(c.gradient(), &Array1::<f64>::zeros(4));
assert_eq!(c.hessian(), &Array2::<f64>::zeros((4, 4)));
assert_eq!(c.len(), 4);
assert!(!c.is_empty());
}
#[test]
fn test_mirror_upper_triangle_3x3() {
let mut h = Array2::<f64>::zeros((3, 3));
h[[0, 0]] = 1.0;
h[[1, 1]] = 2.0;
h[[2, 2]] = 3.0;
h[[0, 1]] = 4.0;
h[[0, 2]] = 5.0;
h[[1, 2]] = 6.0;
mirror_upper_triangle(&mut h);
assert_eq!(h[[1, 0]], 4.0);
assert_eq!(h[[2, 0]], 5.0);
assert_eq!(h[[2, 1]], 6.0);
assert_eq!(h[[0, 0]], 1.0);
assert_eq!(h[[1, 1]], 2.0);
assert_eq!(h[[2, 2]], 3.0);
assert_eq!(h, h.t());
}
#[test]
fn test_symmetrize_averages_halves() {
let mut v = Dual2Vec::constant(0.0, 2);
v.hess[[0, 0]] = 1.0;
v.hess[[1, 1]] = 1.0;
v.hess[[0, 1]] = 2.0;
v.hess[[1, 0]] = 0.0;
v.symmetrize();
assert_eq!(v.hess[[0, 1]], 1.0);
assert_eq!(v.hess[[1, 0]], 1.0);
assert_eq!(v.hess, v.hess.t());
}
#[test]
fn test_add_by_value() {
let x = Dual2Vec::variable(3.0, 0, 2);
let y = Dual2Vec::variable(4.0, 1, 2);
let s = x + y;
assert_eq!(s.value(), 7.0);
assert_eq!(s.gradient(), &array![1.0, 1.0]);
assert_eq!(s.hessian(), &Array2::<f64>::zeros((2, 2)));
assert_eq!(s.hess, s.hess.t()); }
#[test]
fn test_sub_by_value() {
let x = Dual2Vec::variable(5.0, 0, 2);
let y = Dual2Vec::variable(2.0, 1, 2);
let d = x - y;
assert_eq!(d.value(), 3.0);
assert_eq!(d.gradient(), &array![1.0, -1.0]);
assert_eq!(d.hessian(), &Array2::<f64>::zeros((2, 2)));
assert_eq!(d.hess, d.hess.t());
}
#[test]
fn test_add_by_ref_matches_by_value() {
let x = Dual2Vec::variable(3.0, 0, 2);
let y = Dual2Vec::variable(4.0, 1, 2);
let s_ref = &x + &y;
let s_val = x.clone() + y.clone();
assert_eq!(s_ref, s_val);
}
#[test]
fn test_sub_by_ref_matches_by_value() {
let x = Dual2Vec::variable(5.0, 0, 2);
let y = Dual2Vec::variable(2.0, 1, 2);
let d_ref = &x - &y;
let d_val = x.clone() - y.clone();
assert_eq!(d_ref, d_val);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "gradient length mismatch")]
fn test_add_len_mismatch_panics() {
let a = Dual2Vec::variable(1.0, 0, 2);
let b = Dual2Vec::variable(1.0, 0, 3);
let _ = &a + &b;
}
#[test]
fn test_mul_cross_term_symmetric() {
let x = Dual2Vec::variable(3.0, 0, 2);
let y = Dual2Vec::variable(4.0, 1, 2);
let f = &x * &y; assert_eq!(f.value(), 12.0);
assert_eq!(f.gradient()[0], 4.0); assert_eq!(f.gradient()[1], 3.0); assert_eq!(f.hessian()[[0, 0]], 0.0);
assert_eq!(f.hessian()[[1, 1]], 0.0);
assert_eq!(f.hessian()[[0, 1]], 1.0); assert_eq!(f.hessian()[[1, 0]], 1.0); }
#[test]
fn test_mul_square_diagonal() {
let x = Dual2Vec::variable(5.0, 0, 2);
let f = &x * &x;
assert_eq!(f.value(), 25.0);
assert_eq!(f.gradient(), &array![10.0, 0.0]);
assert_eq!(f.hess, f.hess.t());
assert_eq!(f.hessian()[[0, 0]], 2.0);
assert_eq!(f.hessian()[[1, 1]], 0.0);
assert_eq!(f.hessian()[[0, 1]], 0.0);
}
#[test]
fn test_mul_symmetry_bit_exact_on_3_vars() {
let x = Dual2Vec::variable(1.0, 0, 3);
let y = Dual2Vec::variable(2.0, 1, 3);
let z = Dual2Vec::variable(3.0, 2, 3);
let a = &x + &y;
let b = &y + &z;
let f = &a * &b;
assert_eq!(f.value(), 15.0);
assert_eq!(f.gradient(), &array![5.0, 8.0, 3.0]);
assert_eq!(f.hess, f.hess.t());
}
#[test]
fn test_div_direct_form_at_b_near_one() {
let x = Dual2Vec::variable(7.5, 0, 2);
let y = Dual2Vec::variable(1.0, 1, 2);
let f = &x / &y;
assert_eq!(f.value(), 7.5);
assert_eq!(f.gradient()[0], 1.0);
assert_eq!(f.gradient()[1], -7.5);
assert_eq!(f.hess, f.hess.t());
assert_eq!(f.hessian()[[0, 0]], 0.0);
assert_eq!(f.hessian()[[1, 1]], 15.0);
assert_eq!(f.hessian()[[0, 1]], -1.0);
assert_eq!(f.hessian()[[1, 0]], -1.0);
}
#[test]
fn test_div_by_constant() {
let x = Dual2Vec::variable(6.0, 0, 1);
let two = Dual2Vec::constant(2.0, 1);
let f = &x / &two;
assert_eq!(f.value(), 3.0);
assert_eq!(f.gradient()[0], 0.5);
assert_eq!(f.hess, f.hess.t());
assert_eq!(f.hessian()[[0, 0]], 0.0);
}
use approx::assert_abs_diff_eq;
use std::f64::consts::PI;
#[test]
fn test_elementary_sin_at_xy() {
let x = Dual2Vec::variable(0.3, 0, 2);
let y = Dual2Vec::variable(0.4, 1, 2);
let f = (&x + &y).sin();
let u: f64 = 0.7;
assert_abs_diff_eq!(f.value(), u.sin(), epsilon = 1e-15);
assert_abs_diff_eq!(f.gradient()[0], u.cos(), epsilon = 1e-15);
assert_abs_diff_eq!(f.gradient()[1], u.cos(), epsilon = 1e-15);
let expected_hess = -u.sin();
assert_abs_diff_eq!(f.hessian()[[0, 0]], expected_hess, epsilon = 1e-15);
assert_abs_diff_eq!(f.hessian()[[1, 1]], expected_hess, epsilon = 1e-15);
assert_abs_diff_eq!(f.hessian()[[0, 1]], expected_hess, epsilon = 1e-15);
assert_eq!(f.hess, f.hess.t());
}
#[test]
fn test_elementary_cos_at_xy() {
let x = Dual2Vec::variable(0.3, 0, 2);
let y = Dual2Vec::variable(0.4, 1, 2);
let f = (&x + &y).cos();
let u: f64 = 0.7;
assert_abs_diff_eq!(f.value(), u.cos(), epsilon = 1e-15);
assert_abs_diff_eq!(f.gradient()[0], -u.sin(), epsilon = 1e-15);
let expected_hess = -u.cos();
assert_abs_diff_eq!(f.hessian()[[0, 1]], expected_hess, epsilon = 1e-15);
assert_eq!(f.hess, f.hess.t());
}
#[test]
fn test_elementary_tan_at_xy() {
let x = Dual2Vec::variable(0.3, 0, 2);
let y = Dual2Vec::variable(0.4, 1, 2);
let f = (&x + &y).tan();
let u: f64 = 0.7;
let t = u.tan();
assert_abs_diff_eq!(f.value(), t, epsilon = 1e-15);
assert_abs_diff_eq!(f.gradient()[0], 1.0 + t * t, epsilon = 1e-14);
let expected_hess = 2.0 * t * (1.0 + t * t);
assert_abs_diff_eq!(f.hessian()[[0, 1]], expected_hess, epsilon = 1e-14);
assert_eq!(f.hess, f.hess.t());
}
#[test]
fn test_elementary_exp_at_xy() {
let x = Dual2Vec::variable(0.3, 0, 2);
let y = Dual2Vec::variable(0.4, 1, 2);
let f = (&x + &y).exp();
let e = 0.7_f64.exp();
assert_abs_diff_eq!(f.value(), e, epsilon = 1e-15);
assert_abs_diff_eq!(f.gradient()[0], e, epsilon = 1e-15);
assert_abs_diff_eq!(f.hessian()[[0, 0]], e, epsilon = 1e-15);
assert_abs_diff_eq!(f.hessian()[[1, 1]], e, epsilon = 1e-15);
assert_abs_diff_eq!(f.hessian()[[0, 1]], e, epsilon = 1e-15);
assert_eq!(f.hess, f.hess.t());
}
#[test]
fn test_elementary_ln_at_xy() {
let x = Dual2Vec::variable(2.0, 0, 2);
let y = Dual2Vec::variable(3.0, 1, 2);
let f = (&x * &y).ln();
assert_abs_diff_eq!(f.value(), 6.0_f64.ln(), epsilon = 1e-15);
assert_abs_diff_eq!(f.gradient()[0], 0.5, epsilon = 1e-15);
assert_abs_diff_eq!(f.gradient()[1], 1.0 / 3.0, epsilon = 1e-15);
assert_abs_diff_eq!(f.hessian()[[0, 0]], -0.25, epsilon = 1e-15);
assert_abs_diff_eq!(f.hessian()[[1, 1]], -1.0 / 9.0, epsilon = 1e-15);
assert_abs_diff_eq!(f.hessian()[[0, 1]], 0.0, epsilon = 1e-14);
assert_eq!(f.hess, f.hess.t());
}
#[test]
fn test_elementary_sqrt_at_xy() {
let x = Dual2Vec::variable(1.0, 0, 2);
let y = Dual2Vec::variable(3.0, 1, 2);
let f = (&x + &y).sqrt();
assert_abs_diff_eq!(f.value(), 2.0, epsilon = 1e-15);
assert_abs_diff_eq!(f.gradient()[0], 0.25, epsilon = 1e-15);
assert_abs_diff_eq!(f.hessian()[[0, 0]], -1.0 / 32.0, epsilon = 1e-15);
assert_eq!(f.hess, f.hess.t());
}
#[test]
fn test_elementary_tanh_at_xy() {
let x = Dual2Vec::variable(0.3, 0, 2);
let y = Dual2Vec::variable(0.4, 1, 2);
let f = (&x + &y).tanh();
let u: f64 = 0.7;
let t = u.tanh();
assert_abs_diff_eq!(f.value(), t, epsilon = 1e-15);
assert_abs_diff_eq!(f.gradient()[0], 1.0 - t * t, epsilon = 1e-14);
let expected_hess = -2.0 * t * (1.0 - t * t);
assert_abs_diff_eq!(f.hessian()[[0, 1]], expected_hess, epsilon = 1e-14);
assert_eq!(f.hess, f.hess.t());
}
#[test]
fn test_elementary_atan_at_xy() {
let x = Dual2Vec::variable(0.3, 0, 2);
let y = Dual2Vec::variable(0.4, 1, 2);
let f = (&x + &y).atan();
let u: f64 = 0.7;
assert_abs_diff_eq!(f.value(), u.atan(), epsilon = 1e-15);
let fp = 1.0 / (1.0 + u * u);
assert_abs_diff_eq!(f.gradient()[0], fp, epsilon = 1e-15);
let fpp = -2.0 * u / ((1.0 + u * u) * (1.0 + u * u));
assert_abs_diff_eq!(f.hessian()[[0, 1]], fpp, epsilon = 1e-14);
assert_eq!(f.hess, f.hess.t());
}
#[test]
fn test_elementary_asinh_at_xy() {
let x = Dual2Vec::variable(0.3, 0, 2);
let y = Dual2Vec::variable(0.4, 1, 2);
let f = (&x + &y).asinh();
let u: f64 = 0.7;
assert_abs_diff_eq!(f.value(), u.asinh(), epsilon = 1e-15);
let fp = 1.0 / (1.0 + u * u).sqrt();
assert_abs_diff_eq!(f.gradient()[0], fp, epsilon = 1e-15);
let d = 1.0 + u * u;
let fpp = -u / (d * d.sqrt());
assert_abs_diff_eq!(f.hessian()[[0, 1]], fpp, epsilon = 1e-14);
assert_eq!(f.hess, f.hess.t());
}
#[test]
fn test_elementary_erf_at_xy() {
let x = Dual2Vec::variable(0.3, 0, 2);
let y = Dual2Vec::variable(0.4, 1, 2);
let f = (&x + &y).erf();
let u: f64 = 0.7;
let two_over_sqrt_pi = std::f64::consts::FRAC_2_SQRT_PI;
let expected_fp = two_over_sqrt_pi * (-u * u).exp();
let expected_fpp = -2.0 * u * expected_fp;
assert_abs_diff_eq!(f.value(), crate::math::erf(u), epsilon = 1e-15);
assert_abs_diff_eq!(f.gradient()[0], expected_fp, epsilon = 1e-15);
assert_abs_diff_eq!(f.gradient()[1], expected_fp, epsilon = 1e-15);
assert_abs_diff_eq!(f.hessian()[[0, 0]], expected_fpp, epsilon = 1e-14);
assert_abs_diff_eq!(f.hessian()[[1, 1]], expected_fpp, epsilon = 1e-14);
assert_abs_diff_eq!(f.hessian()[[0, 1]], expected_fpp, epsilon = 1e-14);
assert_eq!(f.hess, f.hess.t());
}
#[test]
fn test_powf_direct_form_at_xy() {
let x = Dual2Vec::variable(1.0, 0, 2);
let y = Dual2Vec::variable(2.0, 1, 2);
let f = (&x + &y).powf(3.0);
assert_abs_diff_eq!(f.value(), 27.0, epsilon = 1e-13);
assert_abs_diff_eq!(f.gradient()[0], 27.0, epsilon = 1e-13);
assert_abs_diff_eq!(f.gradient()[1], 27.0, epsilon = 1e-13);
assert_abs_diff_eq!(f.hessian()[[0, 0]], 18.0, epsilon = 1e-13);
assert_abs_diff_eq!(f.hessian()[[1, 1]], 18.0, epsilon = 1e-13);
assert_abs_diff_eq!(f.hessian()[[0, 1]], 18.0, epsilon = 1e-13);
assert_eq!(f.hess, f.hess.t());
}
#[test]
fn test_powd_via_exp_ln() {
let x = Dual2Vec::variable(2.0, 0, 2);
let y = Dual2Vec::variable(3.0, 1, 2);
let f = x.powd(y);
assert_abs_diff_eq!(f.value(), 8.0, epsilon = 1e-12);
assert_abs_diff_eq!(f.gradient()[0], 12.0, epsilon = 1e-12);
assert_abs_diff_eq!(f.gradient()[1], 8.0 * 2.0_f64.ln(), epsilon = 1e-12);
assert_eq!(f.hess, f.hess.t());
}
#[allow(dead_code)]
fn _use_pi() -> f64 {
PI
}
}