use crate::{SolverError, SolverResult, VectorType, DEFAULT_ITERMAX, DEFAULT_TOL};
use nalgebra::{allocator::Allocator, ComplexField, DefaultAllocator, Dim, UniformNorm, U1};
use num_traits::{Float, Signed};
use std::marker::PhantomData;
pub struct MultiVarNewtonFD<T, D, F> {
f: F,
h: T,
tolerance: T,
iter_max: usize,
d_phantom: PhantomData<D>,
}
impl<T, D, F> MultiVarNewtonFD<T, D, F>
where
T: Float + ComplexField<RealField = T> + Signed,
D: Dim,
F: Fn(VectorType<T, D>) -> VectorType<T, D>,
DefaultAllocator: Allocator<D> + Allocator<D, D> + Allocator<U1, D>,
{
pub fn new(f: F) -> Self {
Self {
f,
h: Float::sqrt(T::epsilon()),
tolerance: T::from(DEFAULT_TOL).unwrap(),
iter_max: DEFAULT_ITERMAX,
d_phantom: PhantomData,
}
}
pub fn with_tol(&mut self, tol: T) -> &mut Self {
self.tolerance = tol;
self
}
pub fn with_itermax(&mut self, max: usize) -> &mut Self {
self.iter_max = max;
self
}
pub fn with_fd_step_length(&mut self, h: T) -> &mut Self {
self.h = h;
self
}
pub fn solve(&self, mut x0: VectorType<T, D>) -> SolverResult<VectorType<T, D>> {
let mut dv = x0.clone().add_scalar(T::max_value()); let mut iter = 1;
let dim = x0.nrows();
let zero = (&x0 * x0.transpose()).scale(T::zero());
while dv.apply_norm(&UniformNorm) > self.tolerance && iter <= self.iter_max {
let mut j = zero.clone(); let fx = (self.f)(x0.clone());
for i in 0..dim {
let mut x_h = x0.clone();
x_h[i] += self.h; let df = ((self.f)(x_h) - &fx) / self.h; for k in 0..dim {
j[(k, i)] = df[k];
}
}
if let Some(j_inv) = j.try_inverse() {
dv = j_inv * fx;
x0 -= &dv;
iter += 1;
} else {
return Err(SolverError::BadJacobian);
}
}
if iter >= self.iter_max {
return Err(SolverError::MaxIterReached(iter));
}
Ok(x0)
}
}