use crate::Dual;
use numra_core::Scalar;
pub fn gradient<S, F>(f: F, x: &[S]) -> Vec<S>
where
S: Scalar,
F: Fn(&[Dual<S>]) -> Dual<S>,
{
let n = x.len();
let mut grad = Vec::with_capacity(n);
for i in 0..n {
let dual_x: Vec<Dual<S>> = (0..n)
.map(|j| {
if j == i {
Dual::variable(x[j])
} else {
Dual::constant(x[j])
}
})
.collect();
let result = f(&dual_x);
grad.push(result.deriv());
}
grad
}
pub fn jacobian<S, F>(f: F, x: &[S], m: usize) -> Vec<S>
where
S: Scalar,
F: Fn(&[Dual<S>], &mut [Dual<S>]),
{
let n = x.len();
let mut jac = vec![S::ZERO; m * n];
let mut out = vec![Dual::constant(S::ZERO); m];
for j in 0..n {
let dual_x: Vec<Dual<S>> = (0..n)
.map(|k| {
if k == j {
Dual::variable(x[k])
} else {
Dual::constant(x[k])
}
})
.collect();
for o in out.iter_mut() {
*o = Dual::constant(S::ZERO);
}
f(&dual_x, &mut out);
for i in 0..m {
jac[i * n + j] = out[i].deriv();
}
}
jac
}
#[cfg(test)]
mod tests {
use super::*;
const TOL: f64 = 1e-12;
#[test]
fn test_gradient_quadratic() {
let grad = gradient(
|x: &[Dual<f64>]| {
let two = Dual::constant(2.0);
x[0] * x[0] + two * x[1] * x[1] + x[0] * x[1]
},
&[1.0, 2.0],
);
assert!((grad[0] - 4.0).abs() < TOL);
assert!((grad[1] - 9.0).abs() < TOL);
}
#[test]
fn test_gradient_rosenbrock() {
let grad = gradient(
|x: &[Dual<f64>]| {
let one = Dual::constant(1.0);
let hundred = Dual::constant(100.0);
let a = one - x[0];
let b = x[1] - x[0] * x[0];
a * a + hundred * b * b
},
&[1.0, 1.0],
);
assert!((grad[0] - 0.0).abs() < 1e-10);
assert!((grad[1] - 0.0).abs() < 1e-10);
}
#[test]
fn test_jacobian() {
let jac = jacobian(
|x: &[Dual<f64>], out: &mut [Dual<f64>]| {
out[0] = x[0] * x[0] + x[1];
out[1] = x[0] - x[1] * x[1];
},
&[1.0, 2.0],
2,
);
assert!((jac[0] - 2.0).abs() < TOL); assert!((jac[1] - 1.0).abs() < TOL); assert!((jac[2] - 1.0).abs() < TOL); assert!((jac[3] - (-4.0)).abs() < TOL); }
#[test]
fn test_jacobian_3x2() {
let jac = jacobian(
|x: &[Dual<f64>], out: &mut [Dual<f64>]| {
out[0] = x[0] + x[1];
out[1] = x[0] * x[1];
out[2] = x[0] * x[0] - x[1];
},
&[3.0, 2.0],
3,
);
assert!((jac[0] - 1.0).abs() < TOL); assert!((jac[1] - 1.0).abs() < TOL); assert!((jac[2] - 2.0).abs() < TOL); assert!((jac[3] - 3.0).abs() < TOL); assert!((jac[4] - 6.0).abs() < TOL); assert!((jac[5] - (-1.0)).abs() < TOL); }
#[test]
fn test_gradient_single_variable() {
let grad = gradient(|x: &[Dual<f64>]| x[0] * x[0] * x[0], &[2.0]);
assert!((grad[0] - 12.0).abs() < TOL);
}
}