use std::ops::Deref;
use nalgebra::{
allocator::Allocator,
convert,
storage::{Storage, StorageMut},
ComplexField, DefaultAllocator, IsContiguous, OMatrix, RealField, Vector,
};
use num_traits::{One, Zero};
use thiserror::Error;
use crate::core::{Problem, ProblemError, System};
pub const EPSILON_SQRT: f64 = 0.000000014901161193847656;
#[derive(Debug, Error)]
pub enum JacobianError {
#[error("{0}")]
Problem(#[from] ProblemError),
}
#[derive(Debug)]
pub struct Jacobian<F: Problem>
where
DefaultAllocator: Allocator<F::Scalar, F::Dim, F::Dim>,
{
jac: OMatrix<F::Scalar, F::Dim, F::Dim>,
}
impl<F: Problem> Jacobian<F>
where
DefaultAllocator: Allocator<F::Scalar, F::Dim, F::Dim>,
{
pub fn zeros(f: &F) -> Self {
Self {
jac: OMatrix::zeros_generic(f.dim(), f.dim()),
}
}
}
impl<F: System> Jacobian<F>
where
DefaultAllocator: Allocator<F::Scalar, F::Dim, F::Dim>,
{
pub fn new<Sx, Sscale, Sfx>(
f: &F,
x: &mut Vector<F::Scalar, F::Dim, Sx>,
scale: &Vector<F::Scalar, F::Dim, Sscale>,
fx: &Vector<F::Scalar, F::Dim, Sfx>,
) -> Result<Self, JacobianError>
where
Sx: StorageMut<F::Scalar, F::Dim> + IsContiguous,
Sscale: Storage<F::Scalar, F::Dim>,
Sfx: Storage<F::Scalar, F::Dim>,
{
let mut jac = Self::zeros(f);
jac.compute(f, x, scale, fx)?;
Ok(jac)
}
pub fn compute<Sx, Sscale, Sfx>(
&mut self,
f: &F,
x: &mut Vector<F::Scalar, F::Dim, Sx>,
scale: &Vector<F::Scalar, F::Dim, Sscale>,
fx: &Vector<F::Scalar, F::Dim, Sfx>,
) -> Result<&mut Self, JacobianError>
where
Sx: StorageMut<F::Scalar, F::Dim> + IsContiguous,
Sscale: Storage<F::Scalar, F::Dim>,
Sfx: Storage<F::Scalar, F::Dim>,
{
let eps: F::Scalar = convert(EPSILON_SQRT);
for (j, mut col) in self.jac.column_iter_mut().enumerate() {
let xj = x[j];
let magnitude = F::Scalar::one() / scale[j];
let step = eps * xj.abs().max(magnitude) * xj.copysign(F::Scalar::one());
let step = if step == F::Scalar::zero() { eps } else { step };
x[j] = xj + step;
f.eval(x, &mut col)?;
col -= fx;
col /= step;
x[j] = xj;
}
Ok(self)
}
}
impl<F: Problem> Deref for Jacobian<F>
where
DefaultAllocator: Allocator<F::Scalar, F::Dim, F::Dim>,
{
type Target = OMatrix<F::Scalar, F::Dim, F::Dim>;
fn deref(&self) -> &Self::Target {
&self.jac
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::{ExtendedPowell, ExtendedRosenbrock};
use approx::assert_abs_diff_eq;
use nalgebra::{dmatrix, dvector};
#[test]
fn rosenbrock_jacobian() {
let mut x = dvector![2.0, 2.0];
let scale = dvector![1.0, 1.0];
let mut fx = dvector![0.0, 0.0];
let func = ExtendedRosenbrock::new(2);
func.eval(&x, &mut fx).unwrap();
let jac = Jacobian::new(&func, &mut x, &scale, &fx);
assert!(jac.is_ok());
let jac = jac.unwrap();
let expected = dmatrix![-40.0, 10.0; -1.0, 0.0];
assert_abs_diff_eq!(&*jac, &expected, epsilon = 10e-6);
}
#[test]
fn powell_jacobian_in_root() {
let mut x = dvector![0.0, 0.0, 0.0, 0.0];
let scale = dvector![1.0, 1.0, 1.0, 1.0];
let mut fx = dvector![0.0, 0.0, 0.0, 0.0];
let func = ExtendedPowell::new(4);
func.eval(&x, &mut fx).unwrap();
let jac = Jacobian::new(&func, &mut x, &scale, &fx);
assert!(jac.is_ok());
let jac = jac.unwrap();
let expected = dmatrix![
1.0, 10.0, 0.0, 0.0;
0.0, 0.0, 5f64.sqrt(), -(5f64.sqrt());
0.0, 0.0, 0.0, 0.0;
0.0, 0.0, 0.0, 0.0
];
assert_abs_diff_eq!(&*jac, &expected, epsilon = 10e-6);
}
}