use fenris_traits::Real;
use nalgebra::{DMatrix, DMatrixViewMut, DVector, DVectorView, DVectorViewMut, Dim, DimName, Dyn, Scalar, Vector, U1};
use nalgebra::base::storage::{Storage, StorageMut};
use numeric_literals::replace_float_literals;
use std::error::Error;
pub trait VectorFunction<T>
where
T: Scalar,
{
fn dimension(&self) -> usize;
fn eval_into(&mut self, f: &mut DVectorViewMut<T>, x: &DVectorView<T>);
}
impl<T, X> VectorFunction<T> for &mut X
where
T: Scalar,
X: VectorFunction<T>,
{
fn dimension(&self) -> usize {
X::dimension(self)
}
fn eval_into(&mut self, f: &mut DVectorViewMut<T>, x: &DVectorView<T>) {
X::eval_into(self, f, x)
}
}
pub trait DifferentiableVectorFunction<T>: VectorFunction<T>
where
T: Scalar,
{
fn solve_jacobian_system(
&mut self,
sol: &mut DVectorViewMut<T>,
x: &DVectorView<T>,
rhs: &DVectorView<T>,
) -> Result<(), Box<dyn Error>>;
}
impl<T, X> DifferentiableVectorFunction<T> for &mut X
where
T: Scalar,
X: DifferentiableVectorFunction<T>,
{
fn solve_jacobian_system(
&mut self,
sol: &mut DVectorViewMut<T>,
x: &DVectorView<T>,
rhs: &DVectorView<T>,
) -> Result<(), Box<dyn Error>> {
X::solve_jacobian_system(self, sol, x, rhs)
}
}
#[derive(Debug, Clone)]
pub struct VectorFunctionBuilder {
dimension: usize,
}
#[derive(Debug, Clone)]
pub struct ConcreteVectorFunction<F, J> {
dimension: usize,
function: F,
jacobian_solver: J,
}
impl VectorFunctionBuilder {
pub fn with_dimension(dimension: usize) -> Self {
Self { dimension }
}
pub fn with_function<F, T>(self, function: F) -> ConcreteVectorFunction<F, ()>
where
T: Scalar,
F: FnMut(&mut DVectorViewMut<T>, &DVectorView<T>),
{
ConcreteVectorFunction {
dimension: self.dimension,
function,
jacobian_solver: (),
}
}
}
impl<F> ConcreteVectorFunction<F, ()> {
pub fn with_jacobian_solver<J, T>(self, jacobian_solver: J) -> ConcreteVectorFunction<F, J>
where
T: Scalar,
J: FnMut(&mut DVectorViewMut<T>, &DVectorView<T>, &DVectorView<T>) -> Result<(), Box<dyn Error>>,
{
ConcreteVectorFunction {
dimension: self.dimension,
function: self.function,
jacobian_solver,
}
}
}
impl<F, J, T> VectorFunction<T> for ConcreteVectorFunction<F, J>
where
T: Scalar,
F: FnMut(&mut DVectorViewMut<T>, &DVectorView<T>),
{
fn dimension(&self) -> usize {
self.dimension
}
fn eval_into(&mut self, f: &mut DVectorViewMut<T>, x: &DVectorView<T>) {
let func = &mut self.function;
func(f, x)
}
}
impl<F, J, T> DifferentiableVectorFunction<T> for ConcreteVectorFunction<F, J>
where
T: Scalar,
F: FnMut(&mut DVectorViewMut<T>, &DVectorView<T>),
J: FnMut(&mut DVectorViewMut<T>, &DVectorView<T>, &DVectorView<T>) -> Result<(), Box<dyn Error>>,
{
fn solve_jacobian_system(
&mut self,
sol: &mut DVectorViewMut<T>,
x: &DVectorView<T>,
rhs: &DVectorView<T>,
) -> Result<(), Box<dyn Error>> {
let j = &mut self.jacobian_solver;
j(sol, x, rhs)
}
}
fn as_vector_slice<T, R, S>(vector: &Vector<T, R, S>) -> DVectorView<T>
where
T: Scalar,
S: Storage<T, R, U1, RStride = U1, CStride = Dyn>,
R: Dim,
{
vector.generic_view((0, 0), (Dyn(vector.nrows()), U1::name()))
}
fn as_vector_slice_mut<T, R, S>(vector: &mut Vector<T, R, S>) -> DVectorViewMut<T>
where
T: Scalar,
S: StorageMut<T, R, U1, RStride = U1, CStride = Dyn>,
R: Dim,
{
vector.generic_view_mut((0, 0), (Dyn(vector.nrows()), U1::name()))
}
#[replace_float_literals(T::from_f64(literal).expect("Literal must fit in T"))]
pub fn approximate_jacobian<T>(mut f: impl VectorFunction<T>, x: &DVector<T>, h: &T) -> DMatrix<T>
where
T: Real,
{
let out_dim = f.dimension();
let in_dim = x.len();
let mut result = DMatrix::zeros(out_dim, in_dim);
let mut x_plus = x.clone();
let mut x_minus = x.clone();
let mut f_plus = DVector::zeros(out_dim);
let mut f_minus = DVector::zeros(out_dim);
for j in 0..in_dim {
x_plus.copy_from(x);
x_plus[j] += *h;
x_minus.copy_from(x);
x_minus[j] -= *h;
f.eval_into(&mut as_vector_slice_mut(&mut f_plus), &as_vector_slice(&x_plus));
f.eval_into(&mut as_vector_slice_mut(&mut f_minus), &as_vector_slice(&x_minus));
let mut column_j = result.column_mut(j);
column_j += &f_plus;
column_j -= &f_minus;
column_j /= 2.0 * *h;
}
result
}
pub fn approximate_gradient_fd<'a, T>(
f: impl FnMut(DVectorView<T>) -> T,
x: impl Into<DVectorViewMut<'a, T>>,
h: T,
) -> DVector<T>
where
T: Real,
{
let x = x.into();
let mut df = DVector::zeros(x.len());
approximate_gradient_fd_into_(DVectorViewMut::from(&mut df), f, x, h);
df
}
pub fn approximate_gradient_fd_into<'a, T>(
mut df: DVectorViewMut<T>,
f: impl FnMut(DVectorView<T>) -> T,
x: impl Into<DVectorViewMut<'a, T>>,
h: T,
) where
T: Real,
{
approximate_gradient_fd_into_(DVectorViewMut::from(&mut df), f, x.into(), h);
}
#[replace_float_literals(T::from_f64(literal).unwrap())]
fn approximate_gradient_fd_into_<T>(
mut df: DVectorViewMut<T>,
mut f: impl FnMut(DVectorView<T>) -> T,
mut x: DVectorViewMut<T>,
h: T,
) where
T: Real,
{
let n = x.len();
for i in 0..n {
let x_i = x[i];
x[i] = x_i + h;
let f_plus = f(DVectorView::from(&x));
x[i] = x_i - h;
let f_minus = f(DVectorView::from(&x));
let df_i = (f_plus - f_minus) / (2.0 * h);
df[i] = df_i;
x[i] = x_i;
}
}
pub fn approximate_jacobian_fd<'a, T>(
m: usize,
f: impl FnMut(DVectorView<T>, DVectorViewMut<T>),
x: impl Into<DVectorViewMut<'a, T>>,
h: T,
) -> DMatrix<T>
where
T: Real,
{
let x = x.into();
let n = x.len();
let mut jacobian = DMatrix::zeros(m, n);
approximate_jacobian_fd_into_(DMatrixViewMut::from(&mut jacobian), f, x, h);
jacobian
}
pub fn approximate_jacobian_fd_into<'a, T>(
jacobian: impl Into<DMatrixViewMut<'a, T>>,
f: impl FnMut(DVectorView<T>, DVectorViewMut<T>),
x: impl Into<DVectorViewMut<'a, T>>,
h: T,
) where
T: Real,
{
approximate_jacobian_fd_into_(jacobian.into(), f, x.into(), h);
}
#[replace_float_literals(T::from_f64(literal).unwrap())]
fn approximate_jacobian_fd_into_<T>(
mut j: DMatrixViewMut<T>,
mut f: impl FnMut(DVectorView<T>, DVectorViewMut<T>),
mut x: DVectorViewMut<T>,
h: T,
) where
T: Real,
{
let m = j.nrows();
let n = x.len();
assert_eq!(n, j.ncols());
let mut f_plus = DVector::zeros(m);
let mut f_minus = DVector::zeros(m);
for i in 0..n {
let xi = x[i];
x[i] = xi + h;
f(DVectorView::from(&x), DVectorViewMut::from(&mut f_plus));
x[i] = xi - h;
f(DVectorView::from(&x), DVectorViewMut::from(&mut f_minus));
x[i] = xi;
let mut df_dxi = j.column_mut(i);
df_dxi.copy_from(&f_plus);
df_dxi -= &f_minus;
df_dxi /= 2.0 * h;
}
}