use num::Float as NumFloat;
use scirs2_core::ndarray::{Array1, Array2};
use std::fmt;
use std::ops::{Add, Div, Mul, Neg, Sub};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DualNumber<F: NumFloat + Copy + fmt::Debug> {
value: F,
tangent: F,
}
impl<F: NumFloat + Copy + fmt::Debug> DualNumber<F> {
#[inline]
pub fn new(value: F, tangent: F) -> Self {
Self { value, tangent }
}
#[inline]
pub fn constant(value: F) -> Self {
Self {
value,
tangent: F::zero(),
}
}
#[inline]
pub fn variable(value: F) -> Self {
Self {
value,
tangent: F::one(),
}
}
#[inline]
pub fn value(&self) -> F {
self.value
}
#[inline]
pub fn tangent(&self) -> F {
self.tangent
}
#[inline]
pub fn sin(self) -> Self {
Self {
value: self.value.sin(),
tangent: self.value.cos() * self.tangent,
}
}
#[inline]
pub fn cos(self) -> Self {
Self {
value: self.value.cos(),
tangent: -self.value.sin() * self.tangent,
}
}
#[inline]
pub fn exp(self) -> Self {
let ev = self.value.exp();
Self {
value: ev,
tangent: ev * self.tangent,
}
}
#[inline]
pub fn ln(self) -> Self {
if self.value <= F::zero() {
Self {
value: F::neg_infinity(),
tangent: F::zero(),
}
} else {
Self {
value: self.value.ln(),
tangent: self.tangent / self.value,
}
}
}
#[inline]
pub fn sqrt(self) -> Self {
if self.value < F::zero() {
Self {
value: F::nan(),
tangent: F::zero(),
}
} else if self.value == F::zero() {
Self {
value: F::zero(),
tangent: F::zero(),
}
} else {
let sv = self.value.sqrt();
Self {
value: sv,
tangent: self.tangent / (F::from(2.0).unwrap_or(F::one()) * sv),
}
}
}
#[inline]
pub fn tanh(self) -> Self {
let tv = self.value.tanh();
Self {
value: tv,
tangent: (F::one() - tv * tv) * self.tangent,
}
}
#[inline]
pub fn sigmoid(self) -> Self {
let sv = F::one() / (F::one() + (-self.value).exp());
Self {
value: sv,
tangent: sv * (F::one() - sv) * self.tangent,
}
}
#[inline]
pub fn relu(self) -> Self {
if self.value > F::zero() {
self
} else {
Self {
value: F::zero(),
tangent: F::zero(),
}
}
}
#[inline]
pub fn abs(self) -> Self {
if self.value > F::zero() {
Self {
value: self.value,
tangent: self.tangent,
}
} else if self.value < F::zero() {
Self {
value: -self.value,
tangent: -self.tangent,
}
} else {
Self {
value: F::zero(),
tangent: F::zero(),
}
}
}
#[inline]
pub fn powi(self, n: i32) -> Self {
let val = self.value.powi(n);
let deriv = if n == 0 {
F::zero()
} else {
F::from(n).unwrap_or(F::zero()) * self.value.powi(n - 1) * self.tangent
};
Self {
value: val,
tangent: deriv,
}
}
#[inline]
pub fn powf(self, p: F) -> Self {
let val = self.value.powf(p);
let deriv = if p == F::zero() {
F::zero()
} else {
p * self.value.powf(p - F::one()) * self.tangent
};
Self {
value: val,
tangent: deriv,
}
}
}
impl<F: NumFloat + Copy + fmt::Debug> Add for DualNumber<F> {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
Self {
value: self.value + rhs.value,
tangent: self.tangent + rhs.tangent,
}
}
}
impl<F: NumFloat + Copy + fmt::Debug> Add<F> for DualNumber<F> {
type Output = Self;
#[inline]
fn add(self, rhs: F) -> Self {
Self {
value: self.value + rhs,
tangent: self.tangent,
}
}
}
impl<F: NumFloat + Copy + fmt::Debug> Sub for DualNumber<F> {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
Self {
value: self.value - rhs.value,
tangent: self.tangent - rhs.tangent,
}
}
}
impl<F: NumFloat + Copy + fmt::Debug> Sub<F> for DualNumber<F> {
type Output = Self;
#[inline]
fn sub(self, rhs: F) -> Self {
Self {
value: self.value - rhs,
tangent: self.tangent,
}
}
}
impl<F: NumFloat + Copy + fmt::Debug> Mul for DualNumber<F> {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
Self {
value: self.value * rhs.value,
tangent: self.value * rhs.tangent + rhs.value * self.tangent,
}
}
}
impl<F: NumFloat + Copy + fmt::Debug> Mul<F> for DualNumber<F> {
type Output = Self;
#[inline]
fn mul(self, rhs: F) -> Self {
Self {
value: self.value * rhs,
tangent: self.tangent * rhs,
}
}
}
impl<F: NumFloat + Copy + fmt::Debug> Div for DualNumber<F> {
type Output = Self;
#[inline]
fn div(self, rhs: Self) -> Self {
let g2 = rhs.value * rhs.value;
Self {
value: self.value / rhs.value,
tangent: (rhs.value * self.tangent - self.value * rhs.tangent) / g2,
}
}
}
impl<F: NumFloat + Copy + fmt::Debug> Div<F> for DualNumber<F> {
type Output = Self;
#[inline]
fn div(self, rhs: F) -> Self {
Self {
value: self.value / rhs,
tangent: self.tangent / rhs,
}
}
}
impl<F: NumFloat + Copy + fmt::Debug> Neg for DualNumber<F> {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Self {
value: -self.value,
tangent: -self.tangent,
}
}
}
pub fn jvp<F, Func>(f: Func, x: &Array1<F>, v: &Array1<F>) -> Array1<F>
where
F: NumFloat + Copy + fmt::Debug,
Func: Fn(&[DualNumber<F>]) -> Vec<DualNumber<F>>,
{
assert_eq!(
x.len(),
v.len(),
"jvp: x and v must have the same length ({} vs {})",
x.len(),
v.len()
);
let duals: Vec<DualNumber<F>> = x
.iter()
.zip(v.iter())
.map(|(&xi, &vi)| DualNumber::new(xi, vi))
.collect();
let result = f(&duals);
Array1::from_iter(result.into_iter().map(|d| d.tangent))
}
pub fn jacobian_forward<F, Func>(f: Func, x: &Array1<F>) -> Array2<F>
where
F: NumFloat + Copy + fmt::Debug,
Func: Fn(&[DualNumber<F>]) -> Vec<DualNumber<F>> + Clone,
{
let n = x.len();
let primal: Vec<DualNumber<F>> = x.iter().map(|&xi| DualNumber::constant(xi)).collect();
let m = f.clone()(&primal).len();
if m == 0 || n == 0 {
return Array2::zeros((m, n));
}
let mut jac = Array2::<F>::zeros((m, n));
for j in 0..n {
let duals: Vec<DualNumber<F>> = x
.iter()
.enumerate()
.map(|(i, &xi)| {
if i == j {
DualNumber::new(xi, F::one())
} else {
DualNumber::new(xi, F::zero())
}
})
.collect();
let result = f.clone()(&duals);
for (i, d) in result.iter().enumerate() {
jac[[i, j]] = d.tangent();
}
}
jac
}
pub fn hessian<F, Func>(f: Func, x: &Array1<F>) -> Array2<F>
where
F: NumFloat + Copy + fmt::Debug,
Func: Fn(&[DualNumber<F>]) -> DualNumber<F> + Clone,
{
let n = x.len();
let mut h = Array2::<F>::zeros((n, n));
let step = F::from(1e-5_f64).unwrap_or(F::one());
let two = F::from(2.0_f64).unwrap_or(F::one());
for i in 0..n {
let duals_fwd: Vec<DualNumber<F>> = x
.iter()
.enumerate()
.map(|(k, &xk)| {
let xk_shifted = if k == i { xk + step } else { xk };
DualNumber::new(xk_shifted, if k == i { F::one() } else { F::zero() })
})
.collect();
let grad_fwd = f.clone()(&duals_fwd).tangent();
let duals_bwd: Vec<DualNumber<F>> = x
.iter()
.enumerate()
.map(|(k, &xk)| {
let xk_shifted = if k == i { xk - step } else { xk };
DualNumber::new(xk_shifted, if k == i { F::one() } else { F::zero() })
})
.collect();
let grad_bwd = f.clone()(&duals_bwd).tangent();
h[[i, i]] = (grad_fwd - grad_bwd) / (two * step);
}
for i in 0..n {
for j in (i + 1)..n {
let duals_pij: Vec<DualNumber<F>> = x
.iter()
.enumerate()
.map(|(k, &xk)| {
let val = if k == i { xk + step } else { xk };
let tan = if k == j { F::one() } else { F::zero() };
DualNumber::new(val, tan)
})
.collect();
let gj_fwd = f.clone()(&duals_pij).tangent();
let duals_nij: Vec<DualNumber<F>> = x
.iter()
.enumerate()
.map(|(k, &xk)| {
let val = if k == i { xk - step } else { xk };
let tan = if k == j { F::one() } else { F::zero() };
DualNumber::new(val, tan)
})
.collect();
let gj_bwd = f.clone()(&duals_nij).tangent();
let h_ij = (gj_fwd - gj_bwd) / (two * step);
h[[i, j]] = h_ij;
h[[j, i]] = h_ij; }
}
h
}
pub fn hessian_vector_product<F, Func>(f: Func, x: &Array1<F>, v: &Array1<F>) -> Array1<F>
where
F: NumFloat + Copy + fmt::Debug,
Func: Fn(&[DualNumber<F>]) -> DualNumber<F> + Clone,
{
assert_eq!(
x.len(),
v.len(),
"hessian_vector_product: x and v must have the same length"
);
let n = x.len();
let step = F::from(1e-5_f64).unwrap_or(F::one());
let two = F::from(2.0_f64).unwrap_or(F::one());
let jvp_at = |xp: &Array1<F>| -> F {
let duals: Vec<DualNumber<F>> = xp
.iter()
.zip(v.iter())
.map(|(&xi, &vi)| DualNumber::new(xi, vi))
.collect();
f.clone()(&duals).tangent()
};
let mut hvp = Array1::<F>::zeros(n);
for i in 0..n {
let mut x_fwd = x.clone();
let mut x_bwd = x.clone();
x_fwd[i] = x[i] + step;
x_bwd[i] = x[i] - step;
hvp[i] = (jvp_at(&x_fwd) - jvp_at(&x_bwd)) / (two * step);
}
hvp
}
pub fn gradient_forward<F, Func>(f: Func, x: &Array1<F>) -> Array1<F>
where
F: NumFloat + Copy + fmt::Debug,
Func: Fn(&[DualNumber<F>]) -> DualNumber<F> + Clone,
{
let n = x.len();
let mut grad = Array1::<F>::zeros(n);
for i in 0..n {
let duals: Vec<DualNumber<F>> = x
.iter()
.enumerate()
.map(|(k, &xk)| {
if k == i {
DualNumber::new(xk, F::one())
} else {
DualNumber::new(xk, F::zero())
}
})
.collect();
grad[i] = f.clone()(&duals).tangent();
}
grad
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array1;
#[test]
fn test_dual_constant() {
let c = DualNumber::constant(3.0_f64);
assert_eq!(c.value(), 3.0);
assert_eq!(c.tangent(), 0.0);
}
#[test]
fn test_dual_variable() {
let v = DualNumber::variable(5.0_f64);
assert_eq!(v.value(), 5.0);
assert_eq!(v.tangent(), 1.0);
}
#[test]
fn test_dual_add() {
let a = DualNumber::new(2.0_f64, 1.0);
let b = DualNumber::new(3.0_f64, 2.0);
let c = a + b;
assert_eq!(c.value(), 5.0);
assert_eq!(c.tangent(), 3.0);
}
#[test]
fn test_dual_sub() {
let a = DualNumber::new(5.0_f64, 3.0);
let b = DualNumber::new(2.0_f64, 1.0);
let c = a - b;
assert_eq!(c.value(), 3.0);
assert_eq!(c.tangent(), 2.0);
}
#[test]
fn test_dual_mul_product_rule() {
let f = DualNumber::new(3.0_f64, 1.0);
let g = DualNumber::new(4.0_f64, 2.0);
let h = f * g;
assert_eq!(h.value(), 12.0);
assert_eq!(h.tangent(), 10.0);
}
#[test]
fn test_dual_div_quotient_rule() {
let f = DualNumber::new(6.0_f64, 2.0);
let g = DualNumber::new(3.0_f64, 1.0);
let h = f / g;
assert!((h.value() - 2.0).abs() < 1e-14);
assert!((h.tangent() - 0.0).abs() < 1e-14);
}
#[test]
fn test_dual_neg() {
let a = DualNumber::new(3.0_f64, -1.0);
let b = -a;
assert_eq!(b.value(), -3.0);
assert_eq!(b.tangent(), 1.0);
}
#[test]
fn test_dual_sin_cos() {
use std::f64::consts::PI;
let x = DualNumber::variable(PI / 2.0);
let s = x.sin();
assert!((s.value() - 1.0).abs() < 1e-12);
assert!(s.tangent().abs() < 1e-12);
let x2 = DualNumber::variable(0.0_f64);
let c = x2.cos();
assert!((c.value() - 1.0).abs() < 1e-12);
assert!(c.tangent().abs() < 1e-12);
}
#[test]
fn test_dual_exp() {
let x = DualNumber::variable(0.0_f64);
let e = x.exp();
assert!((e.value() - 1.0).abs() < 1e-14);
assert!((e.tangent() - 1.0).abs() < 1e-14);
}
#[test]
fn test_dual_ln() {
let x = DualNumber::variable(1.0_f64);
let l = x.ln();
assert!((l.value() - 0.0).abs() < 1e-14);
assert!((l.tangent() - 1.0).abs() < 1e-14);
}
#[test]
fn test_dual_sqrt() {
let x = DualNumber::variable(4.0_f64);
let s = x.sqrt();
assert!((s.value() - 2.0).abs() < 1e-14);
assert!((s.tangent() - 0.25).abs() < 1e-14);
}
#[test]
fn test_dual_tanh() {
let x = DualNumber::variable(0.0_f64);
let t = x.tanh();
assert!((t.value() - 0.0).abs() < 1e-14);
assert!((t.tangent() - 1.0).abs() < 1e-14);
}
#[test]
fn test_dual_sigmoid() {
let x = DualNumber::variable(0.0_f64);
let s = x.sigmoid();
assert!((s.value() - 0.5).abs() < 1e-12);
assert!((s.tangent() - 0.25).abs() < 1e-12);
}
#[test]
fn test_dual_relu() {
let pos = DualNumber::variable(2.0_f64);
let r = pos.relu();
assert_eq!(r.value(), 2.0);
assert_eq!(r.tangent(), 1.0);
let neg = DualNumber::variable(-1.0_f64);
let r2 = neg.relu();
assert_eq!(r2.value(), 0.0);
assert_eq!(r2.tangent(), 0.0);
}
#[test]
fn test_dual_abs() {
let pos = DualNumber::new(3.0_f64, 2.0);
let a = pos.abs();
assert_eq!(a.value(), 3.0);
assert_eq!(a.tangent(), 2.0);
let neg = DualNumber::new(-3.0_f64, 2.0);
let b = neg.abs();
assert_eq!(b.value(), 3.0);
assert_eq!(b.tangent(), -2.0);
}
#[test]
fn test_dual_powi() {
let x = DualNumber::variable(2.0_f64);
let y = x.powi(3);
assert!((y.value() - 8.0).abs() < 1e-12);
assert!((y.tangent() - 12.0).abs() < 1e-12);
}
#[test]
fn test_dual_powf() {
let x = DualNumber::variable(4.0_f64);
let y = x.powf(1.5_f64);
assert!((y.value() - 8.0).abs() < 1e-10);
assert!((y.tangent() - 3.0).abs() < 1e-10);
}
#[test]
fn test_chain_rule() {
let x = DualNumber::variable(1.0_f64);
let y = (x * x).sin();
let expected = (1.0_f64).cos() * 2.0;
assert!((y.tangent() - expected).abs() < 1e-12);
}
#[test]
fn test_compose_exp_sin() {
let x = DualNumber::variable(0.0_f64);
let y = x.sin().exp();
assert!((y.value() - 1.0).abs() < 1e-12);
assert!((y.tangent() - 1.0).abs() < 1e-12);
}
#[test]
fn test_jvp_scalar() {
let f = |xs: &[DualNumber<f64>]| vec![xs[0].powi(3)];
let x = Array1::from(vec![3.0_f64]);
let v = Array1::from(vec![2.0_f64]);
let result = jvp(f, &x, &v);
assert!((result[0] - 54.0).abs() < 1e-10);
}
#[test]
fn test_jvp_vector_function() {
let f = |xs: &[DualNumber<f64>]| vec![xs[0] * xs[0], xs[0] * xs[1]];
let x = Array1::from(vec![2.0_f64, 3.0]);
let v = Array1::from(vec![1.0_f64, 0.0]);
let result = jvp(f, &x, &v);
assert!((result[0] - 4.0).abs() < 1e-12);
assert!((result[1] - 3.0).abs() < 1e-12);
}
#[test]
fn test_jvp_sum_direction() {
let f = |xs: &[DualNumber<f64>]| vec![xs[0] * xs[0], xs[1] * xs[1]];
let x = Array1::from(vec![3.0_f64, 4.0]);
let v = Array1::from(vec![1.0_f64, 1.0]);
let r = jvp(f, &x, &v);
assert!((r[0] - 6.0).abs() < 1e-12);
assert!((r[1] - 8.0).abs() < 1e-12);
}
#[test]
fn test_jacobian_2x2() {
let f = |xs: &[DualNumber<f64>]| vec![xs[0] * xs[0], xs[0] * xs[1]];
let x = Array1::from(vec![2.0_f64, 3.0]);
let jac = jacobian_forward(f, &x);
assert_eq!(jac.shape(), &[2, 2]);
assert!((jac[[0, 0]] - 4.0).abs() < 1e-12);
assert!((jac[[0, 1]] - 0.0).abs() < 1e-12);
assert!((jac[[1, 0]] - 3.0).abs() < 1e-12);
assert!((jac[[1, 1]] - 2.0).abs() < 1e-12);
}
#[test]
fn test_jacobian_1x1() {
let f = |xs: &[DualNumber<f64>]| vec![xs[0].powi(3)];
let x = Array1::from(vec![2.0_f64]);
let jac = jacobian_forward(f, &x);
assert_eq!(jac.shape(), &[1, 1]);
assert!((jac[[0, 0]] - 12.0).abs() < 1e-10);
}
#[test]
fn test_jacobian_scalar_to_vector() {
let f = |xs: &[DualNumber<f64>]| vec![xs[0], xs[0] * xs[0], xs[0] * xs[0] * xs[0]];
let x = Array1::from(vec![2.0_f64]);
let jac = jacobian_forward(f, &x);
assert_eq!(jac.shape(), &[3, 1]);
assert!((jac[[0, 0]] - 1.0).abs() < 1e-12);
assert!((jac[[1, 0]] - 4.0).abs() < 1e-12);
assert!((jac[[2, 0]] - 12.0).abs() < 1e-12);
}
#[test]
fn test_gradient_forward_quadratic() {
let f = |xs: &[DualNumber<f64>]| {
let two = DualNumber::constant(2.0_f64);
xs[0] * xs[0] + two * xs[1] * xs[1]
};
let x = Array1::from(vec![3.0_f64, 1.0]);
let g = gradient_forward(f, &x);
assert!((g[0] - 6.0).abs() < 1e-12);
assert!((g[1] - 4.0).abs() < 1e-12);
}
#[test]
fn test_hessian_quadratic_diagonal() {
let f = |xs: &[DualNumber<f64>]| {
let two = DualNumber::constant(2.0_f64);
xs[0] * xs[0] + two * xs[1] * xs[1]
};
let x = Array1::from(vec![1.0_f64, 1.0]);
let h = hessian(f, &x);
assert!((h[[0, 0]] - 2.0).abs() < 1e-5);
assert!((h[[1, 1]] - 4.0).abs() < 1e-5);
assert!(h[[0, 1]].abs() < 1e-5);
assert!(h[[1, 0]].abs() < 1e-5);
}
#[test]
fn test_hessian_mixed_terms() {
let f = |xs: &[DualNumber<f64>]| {
let two = DualNumber::constant(2.0_f64);
let three = DualNumber::constant(3.0_f64);
xs[0] * xs[0] + three * xs[0] * xs[1] + two * xs[1] * xs[1]
};
let x = Array1::from(vec![1.0_f64, 1.0]);
let h = hessian(f, &x);
assert!((h[[0, 0]] - 2.0).abs() < 1e-5);
assert!((h[[0, 1]] - 3.0).abs() < 1e-5);
assert!((h[[1, 0]] - 3.0).abs() < 1e-5);
assert!((h[[1, 1]] - 4.0).abs() < 1e-5);
}
#[test]
fn test_hessian_nonlinear() {
let f = |xs: &[DualNumber<f64>]| xs[0].sin() * xs[1].exp();
let x = Array1::from(vec![0.0_f64, 0.0]);
let h = hessian(f, &x);
assert!(h[[0, 0]].abs() < 1e-5);
assert!((h[[0, 1]] - 1.0).abs() < 1e-5);
assert!((h[[1, 0]] - 1.0).abs() < 1e-5);
assert!(h[[1, 1]].abs() < 1e-5);
}
#[test]
fn test_hessian_symmetry() {
let f = |xs: &[DualNumber<f64>]| {
let two = DualNumber::constant(2.0_f64);
xs[0].powi(3) + two * xs[0] * xs[1].powi(2) + xs[1].exp()
};
let x = Array1::from(vec![1.0_f64, 0.5]);
let h = hessian(f, &x);
assert!(
(h[[0, 1]] - h[[1, 0]]).abs() < 1e-6,
"Hessian should be symmetric"
);
}
#[test]
fn test_hvp_diagonal_hessian() {
let f = |xs: &[DualNumber<f64>]| xs[0] * xs[0] + xs[1] * xs[1];
let x = Array1::from(vec![1.0_f64, 2.0]);
let v = Array1::from(vec![3.0_f64, 4.0]);
let hvp = hessian_vector_product(f, &x, &v);
assert!((hvp[0] - 6.0).abs() < 1e-6);
assert!((hvp[1] - 8.0).abs() < 1e-6);
}
#[test]
fn test_hvp_mixed_hessian() {
let f = |xs: &[DualNumber<f64>]| {
let two = DualNumber::constant(2.0_f64);
let three = DualNumber::constant(3.0_f64);
xs[0] * xs[0] + three * xs[0] * xs[1] + two * xs[1] * xs[1]
};
let x = Array1::from(vec![1.0_f64, 1.0]);
let v = Array1::from(vec![1.0_f64, 0.0]);
let hvp = hessian_vector_product(f, &x, &v);
assert!((hvp[0] - 2.0).abs() < 1e-6);
assert!((hvp[1] - 3.0).abs() < 1e-6);
}
#[test]
fn test_hvp_nonlinear() {
let f = |xs: &[DualNumber<f64>]| {
let four = DualNumber::constant(4.0_f64);
xs[0].powi(4) / four
};
let x = Array1::from(vec![2.0_f64]);
let v = Array1::from(vec![1.0_f64]);
let hvp = hessian_vector_product(f, &x, &v);
assert!((hvp[0] - 12.0).abs() < 1e-4);
}
#[test]
fn test_hvp_consistency_with_hessian() {
let f = |xs: &[DualNumber<f64>]| {
let two = DualNumber::constant(2.0_f64);
let five = DualNumber::constant(5.0_f64);
xs[0] * xs[0] + five * xs[0] * xs[1] + two * xs[1] * xs[1]
};
let x = Array1::from(vec![1.0_f64, 2.0]);
let v = Array1::from(vec![1.0_f64, 2.0]);
let h_mat = hessian(f, &x);
let hvp = hessian_vector_product(f, &x, &v);
let h_times_v_0 = h_mat[[0, 0]] * v[0] + h_mat[[0, 1]] * v[1];
let h_times_v_1 = h_mat[[1, 0]] * v[0] + h_mat[[1, 1]] * v[1];
assert!(
(hvp[0] - h_times_v_0).abs() < 1e-4,
"hvp[0]={} h_mat*v[0]={}",
hvp[0],
h_times_v_0
);
assert!(
(hvp[1] - h_times_v_1).abs() < 1e-4,
"hvp[1]={} h_mat*v[1]={}",
hvp[1],
h_times_v_1
);
}
#[test]
fn test_hessian_scalar_function() {
let f = |xs: &[DualNumber<f64>]| xs[0] * xs[0];
let x = Array1::from(vec![3.0_f64]);
let h = hessian(f, &x);
assert_eq!(h.shape(), &[1, 1]);
assert!((h[[0, 0]] - 2.0).abs() < 1e-5);
}
#[test]
fn test_jacobian_sin_cos() {
use std::f64::consts::FRAC_PI_2;
let f = |xs: &[DualNumber<f64>]| vec![xs[0].sin(), xs[1].cos()];
let x = Array1::from(vec![FRAC_PI_2, 0.0_f64]);
let jac = jacobian_forward(f, &x);
assert!(jac[[0, 0]].abs() < 1e-12); assert!(jac[[0, 1]].abs() < 1e-12);
assert!(jac[[1, 0]].abs() < 1e-12);
assert!(jac[[1, 1]].abs() < 1e-12); }
#[test]
fn test_hessian_rosenbrock() {
let f = |xs: &[DualNumber<f64>]| {
let one = DualNumber::constant(1.0_f64);
let hundred = DualNumber::constant(100.0_f64);
let a = one - xs[0];
let b = xs[1] - xs[0] * xs[0];
a * a + hundred * b * b
};
let x = Array1::from(vec![1.0_f64, 1.0]);
let h = hessian(f, &x);
assert!((h[[0, 0]] - 802.0).abs() < 0.1, "H[0,0]={}", h[[0, 0]]);
assert!((h[[0, 1]] - (-400.0)).abs() < 0.1, "H[0,1]={}", h[[0, 1]]);
assert!((h[[1, 1]] - 200.0).abs() < 0.1, "H[1,1]={}", h[[1, 1]]);
}
}