use crate::StrError;
use crate::{deriv1_central5, Matrix, Vector};
pub fn num_jacobian<F, A>(
ndim: usize,
x_at: f64,
y_at: &Vector,
alpha: f64,
args: &mut A,
function: F,
) -> Result<Matrix, StrError>
where
F: Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>,
{
struct Extra {
x: f64, y: Vector, f: Vector, i: usize, j: usize, }
let mut extra = Extra {
x: x_at,
y: y_at.clone(),
f: Vector::new(ndim),
i: 0,
j: 0,
};
let mut jac = Matrix::new(ndim, ndim);
for i in 0..ndim {
extra.i = i;
for j in 0..ndim {
extra.j = j;
let res = deriv1_central5(y_at[j], &mut extra, |yj: f64, extra: &mut Extra| {
let original = extra.y[extra.j];
extra.y[extra.j] = yj;
function(&mut extra.f, extra.x, &extra.y, args)?;
extra.y[extra.j] = original;
Ok(extra.f[extra.i])
})?;
jac.set(i, j, res * alpha);
}
}
Ok(jac)
}
#[cfg(test)]
mod tests {
use super::num_jacobian;
use crate::{mat_approx_eq, Matrix, Vector};
#[test]
fn num_jacobian_works() {
struct Args {
count: usize,
}
let args = &mut Args { count: 0 };
let x = 1.5;
let y = Vector::from(&[1.0, 2.0, 3.0]);
let alpha = 2.0;
let jj_ana = Matrix::from(&[
[alpha * (1.0), alpha * (-1.0), alpha * (1.0)],
[0.0, alpha * (x * y[2] * y[2]), alpha * (2.0 * x * y[1] * y[2])],
[alpha * (-y[1] * y[2]), alpha * (-y[0] * y[2]), alpha * (-y[0] * y[1])],
]);
let jj_num = num_jacobian(y.dim(), x, &y, 2.0, args, |f, x, y, args| {
args.count += 1;
f[0] = x + y[0] - y[1] + y[2];
f[1] = x * y[1] * y[2] * y[2];
f[2] = x - y[0] * y[1] * y[2];
Ok(())
})
.unwrap();
mat_approx_eq(&jj_ana, &jj_num, 1e-10);
assert_eq!(args.count, 36);
}
#[test]
fn num_jacobian_captures_errors() {
let y = Vector::new(1);
let args = &mut 0;
assert_eq!(
num_jacobian(1, 0.0, &y, 2.0, args, |_, _, _, _| { Err("stop") }).err(),
Some("stop")
);
}
}