use crate::core::{LinearAlgebra, Matrix, NalgebraProvider, RealScalar, Vector};
use std::convert::Infallible;
pub type ValueGradientHessian<T, B> = (T, Vector<T, B>, Matrix<T, B>);
pub type GradientHessian<T, B> = (Vector<T, B>, Matrix<T, B>);
pub trait ScalarCostFunction<
T: RealScalar = f64,
B: LinearAlgebra<T> = NalgebraProvider,
U = (),
E = Infallible,
>
{
fn evaluate(&self, x: &Vector<T, B>, args: &U) -> Result<T, E>;
}
pub trait ScalarGradient<
T: RealScalar = f64,
B: LinearAlgebra<T> = NalgebraProvider,
U = (),
E = Infallible,
>: ScalarCostFunction<T, B, U, E>
{
fn gradient(&self, x: &Vector<T, B>, args: &U) -> Result<Vector<T, B>, E> {
let mut work = x.clone();
let mut gradient = Vector::<T, B>::zeros(x.len());
let two = T::literal(2.0);
for i in 0..x.len() {
let xi = work.get(i);
let step = T::epsilon().cbrt() * (xi.abs() + T::one());
work.set(i, xi + step);
let plus = self.evaluate(&work, args)?;
work.set(i, xi - step);
let minus = self.evaluate(&work, args)?;
work.set(i, xi);
gradient.set(i, (plus - minus) / (two * step));
}
Ok(gradient)
}
fn hessian(&self, x: &Vector<T, B>, args: &U) -> Result<Matrix<T, B>, E> {
let n = x.len();
let mut work = x.clone();
let mut hessian = Matrix::<T, B>::zeros(n, n);
let two = T::literal(2.0);
for column in 0..n {
let xi = work.get(column);
let step = T::epsilon().cbrt() * (xi.abs() + T::one());
work.set(column, xi + step);
let plus = self.gradient(&work, args)?;
work.set(column, xi - step);
let minus = self.gradient(&work, args)?;
work.set(column, xi);
for row in 0..n {
hessian.set(row, column, (plus.get(row) - minus.get(row)) / (two * step));
}
}
for row in 0..n {
for column in 0..row {
let value = (hessian.get(row, column) + hessian.get(column, row)) / two;
hessian.set(row, column, value);
hessian.set(column, row, value);
}
}
Ok(hessian)
}
fn evaluate_with_gradient(&self, x: &Vector<T, B>, args: &U) -> Result<(T, Vector<T, B>), E> {
Ok((self.evaluate(x, args)?, self.gradient(x, args)?))
}
fn gradient_with_hessian(
&self,
x: &Vector<T, B>,
args: &U,
) -> Result<GradientHessian<T, B>, E> {
Ok((self.gradient(x, args)?, self.hessian(x, args)?))
}
fn evaluate_with_gradient_and_hessian(
&self,
x: &Vector<T, B>,
args: &U,
) -> Result<ValueGradientHessian<T, B>, E> {
let (value, gradient) = self.evaluate_with_gradient(x, args)?;
Ok((value, gradient, self.hessian(x, args)?))
}
}
pub trait ScalarLogDensity<
T: RealScalar = f64,
B: LinearAlgebra<T> = NalgebraProvider,
U = (),
E = Infallible,
>
{
fn log_density(&self, x: &Vector<T, B>, args: &U) -> Result<T, E>;
}
#[cfg(test)]
mod tests {
use super::*;
struct Quadratic;
impl<T, B> ScalarCostFunction<T, B> for Quadratic
where
T: RealScalar,
B: LinearAlgebra<T>,
{
fn evaluate(&self, x: &Vector<T, B>, _: &()) -> Result<T, Infallible> {
Ok(x.dot(x) + T::one())
}
}
impl<T, B> ScalarGradient<T, B> for Quadratic
where
T: RealScalar,
B: LinearAlgebra<T>,
{
}
fn check<T: RealScalar + nalgebra::RealField>() {
let x = Vector::<T>::from_vec(vec![<T as RealScalar>::one(), T::literal(2.0)]);
let gradient = Quadratic.gradient(&x, &()).unwrap();
let hessian = Quadratic.hessian(&x, &()).unwrap();
let tolerance = RealScalar::sqrt(<T as RealScalar>::epsilon()) * T::literal(20.0);
assert!(RealScalar::abs(gradient.get(0) - T::literal(2.0)) < tolerance);
assert!(RealScalar::abs(gradient.get(1) - T::literal(4.0)) < tolerance);
assert!(RealScalar::abs(hessian.get(0, 0) - T::literal(2.0)) < RealScalar::cbrt(tolerance));
}
#[test]
fn finite_differences_support_both_native_scalars() {
check::<f32>();
check::<f64>();
}
}