use ad_trait::differentiable_function::{
DifferentiableFunctionTrait, FiniteDifferencing, ForwardAD, ForwardADMulti, Reparameterize,
ReverseAD,
};
use ad_trait::forward_ad::adfn::adfn;
use ad_trait::function_engine::FunctionEngine;
use ad_trait::AD;
use approx::assert_relative_eq;
#[derive(Clone)]
struct PolynomialTest;
impl<T: AD> DifferentiableFunctionTrait<T> for PolynomialTest {
const NAME: &'static str = "PolynomialTest";
fn call(&self, inputs: &[T], _freeze: bool) -> Vec<T> {
let x = inputs[0];
vec![x * x * T::constant(2.0) + x * T::constant(3.0) + T::constant(1.0)]
}
fn num_inputs(&self) -> usize {
1
}
fn num_outputs(&self) -> usize {
1
}
}
impl Reparameterize for PolynomialTest {
type SelfType<T2: AD> = PolynomialTest;
}
#[derive(Clone)]
struct MultiVariateTest;
impl<T: AD> DifferentiableFunctionTrait<T> for MultiVariateTest {
const NAME: &'static str = "MultiVariateTest";
fn call(&self, inputs: &[T], _freeze: bool) -> Vec<T> {
let x = inputs[0];
let y = inputs[1];
vec![x * x + y, y * y + x]
}
fn num_inputs(&self) -> usize {
2
}
fn num_outputs(&self) -> usize {
2
}
}
impl Reparameterize for MultiVariateTest {
type SelfType<T2: AD> = MultiVariateTest;
}
#[derive(Clone)]
struct MatrixMulTest;
impl<T: AD> DifferentiableFunctionTrait<T> for MatrixMulTest {
const NAME: &'static str = "MatrixMulTest";
fn call(&self, inputs: &[T], _freeze: bool) -> Vec<T> {
let x = inputs[0];
let y = inputs[1];
let m = ad_trait::nalgebra::Matrix2::new(x, y, T::constant(0.0), x * y);
let v = ad_trait::nalgebra::Vector2::new(T::constant(1.0), T::constant(2.0));
let res = m * v;
vec![res[0], res[1]]
}
fn num_inputs(&self) -> usize {
2
}
fn num_outputs(&self) -> usize {
2
}
}
impl Reparameterize for MatrixMulTest {
type SelfType<T2: AD> = MatrixMulTest;
}
#[test]
fn test_polynomial_forward_ad() {
let func = PolynomialTest;
let engine = FunctionEngine::new(func.clone(), func, ForwardAD::new());
let x = 2.0;
let (val, grad) = engine.derivative(&[x]);
assert_relative_eq!(val[0], 15.0);
assert_relative_eq!(grad[(0, 0)], 11.0);
}
#[test]
fn test_polynomial_reverse_ad() {
let func = PolynomialTest;
let engine = FunctionEngine::new(func.clone(), func, ReverseAD::new());
let x = 2.0;
let (val, grad) = engine.derivative(&[x]);
assert_relative_eq!(val[0], 15.0);
assert_relative_eq!(grad[(0, 0)], 11.0);
}
#[test]
fn test_polynomial_finite_differencing() {
let func = PolynomialTest;
let engine = FunctionEngine::new(func.clone(), func, FiniteDifferencing::new());
let x = 2.0;
let (val, grad) = engine.derivative(&[x]);
assert_relative_eq!(val[0], 15.0);
assert_relative_eq!(grad[(0, 0)], 11.0, epsilon = 1e-5);
}
#[test]
fn test_multivariate_forward_ad() {
let func = MultiVariateTest;
let engine = FunctionEngine::new(func.clone(), func, ForwardAD::new());
let inputs = [2.0, 3.0];
let (val, grad) = engine.derivative(&inputs);
assert_relative_eq!(val[0], 7.0);
assert_relative_eq!(val[1], 11.0);
assert_relative_eq!(grad[(0, 0)], 4.0);
assert_relative_eq!(grad[(0, 1)], 1.0);
assert_relative_eq!(grad[(1, 0)], 1.0);
assert_relative_eq!(grad[(1, 1)], 6.0);
}
#[test]
fn test_multivariate_reverse_ad() {
let func = MultiVariateTest;
let engine = FunctionEngine::new(func.clone(), func, ReverseAD::new());
let inputs = [2.0, 3.0];
let (val, grad) = engine.derivative(&inputs);
assert_relative_eq!(val[0], 7.0);
assert_relative_eq!(val[1], 11.0);
assert_relative_eq!(grad[(0, 0)], 4.0);
assert_relative_eq!(grad[(0, 1)], 1.0);
assert_relative_eq!(grad[(1, 0)], 1.0);
assert_relative_eq!(grad[(1, 1)], 6.0);
}
#[test]
fn test_multivariate_forward_ad_multi() {
let func = MultiVariateTest;
let engine = FunctionEngine::new(func.clone(), func, ForwardADMulti::<adfn<2>>::new());
let inputs = [2.0, 3.0];
let (val, grad) = engine.derivative(&inputs);
assert_relative_eq!(val[0], 7.0);
assert_relative_eq!(val[1], 11.0);
assert_relative_eq!(grad[(0, 0)], 4.0);
assert_relative_eq!(grad[(0, 1)], 1.0);
assert_relative_eq!(grad[(1, 0)], 1.0);
assert_relative_eq!(grad[(1, 1)], 6.0);
}
#[test]
fn test_matrix_mul_forward_ad() {
let func = MatrixMulTest;
let engine = FunctionEngine::new(func.clone(), func, ForwardAD::new());
let inputs = [2.0, 3.0];
let (val, grad) = engine.derivative(&inputs);
assert_relative_eq!(val[0], 8.0);
assert_relative_eq!(val[1], 12.0);
assert_relative_eq!(grad[(0, 0)], 1.0);
assert_relative_eq!(grad[(0, 1)], 2.0);
assert_relative_eq!(grad[(1, 0)], 6.0);
assert_relative_eq!(grad[(1, 1)], 4.0);
}
#[test]
fn test_matrix_mul_reverse_ad() {
let func = MatrixMulTest;
let engine = FunctionEngine::new(func.clone(), func, ReverseAD::new());
let inputs = [2.0, 3.0];
let (val, grad) = engine.derivative(&inputs);
assert_relative_eq!(val[0], 8.0);
assert_relative_eq!(val[1], 12.0);
assert_relative_eq!(grad[(0, 0)], 1.0);
assert_relative_eq!(grad[(0, 1)], 2.0);
assert_relative_eq!(grad[(1, 0)], 6.0);
assert_relative_eq!(grad[(1, 1)], 4.0);
}
#[cfg(feature = "hessian")]
#[test]
fn test_scalar_hessian() {
use ad_trait::AD;
use ad_trait::forward_ad::ForwardADTrait;
use ad_trait::forward_ad::adfn::adfn;
use ad_trait::hyper_ad::hyper::HyperAD_ADFN;
let mut x_inner = adfn::<1>::constant(2.0);
x_inner.set_tangent_value(0, 1.0);
let mut x_outer = HyperAD_ADFN::<1>::new_inner_constant(x_inner);
x_outer.set_tangent_value(0, 1.0);
let y = x_outer * x_outer * x_outer + HyperAD_ADFN::<1>::new_constant(2.0) * x_outer * x_outer + HyperAD_ADFN::<1>::new_constant(5.0) * x_outer + HyperAD_ADFN::<1>::new_constant(1.0);
let y_inner = y.inner_value(); let _f_val = y_inner.value();
let _f_prime = y_inner.tangent_as_vec()[0];
let y_outer_tangent = y.inner_tangent();
let _f_prime_from_outer = y_outer_tangent[0].value();
let f_double_prime = y_outer_tangent[0].tangent_as_vec()[0];
assert_eq!(f_double_prime, 16.0);
}
#[cfg(feature = "hessian")]
#[test]
fn test_function_engine_hessian() {
use ad_trait::differentiable_function::HessianAD;
let func = PolynomialTest;
let engine = FunctionEngine::new(func.clone(), func, HessianAD::<1>::new());
let x = 2.0;
let (val, grad, hess) = engine.hessian(&[x]);
assert_relative_eq!(val[0], 15.0);
assert_relative_eq!(grad[(0, 0)], 11.0);
assert_relative_eq!(hess[0][(0, 0)], 4.0);
}
#[cfg(feature = "hessian")]
#[test]
fn test_hessian_batching() {
use ad_trait::differentiable_function::{HessianAD, HessianAD_FOR};
let func = MultiVariateTest;
let engine_fof = FunctionEngine::new(func.clone(), func.clone(), HessianAD::<1>::new());
let inputs = [2.0, 3.0];
let (val, grad, hess) = engine_fof.hessian(&inputs);
assert_relative_eq!(val[0], 7.0);
assert_relative_eq!(grad[(0, 0)], 4.0);
assert_relative_eq!(grad[(0, 1)], 1.0);
assert_relative_eq!(hess[0][(0, 0)], 2.0);
assert_relative_eq!(hess[0][(1, 1)], 0.0);
assert_relative_eq!(hess[1][(1, 1)], 2.0);
let engine_for = FunctionEngine::new(func.clone(), func, HessianAD_FOR::<1>::new());
let (val_for, grad_for, hess_for) = engine_for.hessian(&inputs);
assert_relative_eq!(val_for[0], 7.0);
assert_relative_eq!(grad_for[(0, 0)], 4.0);
assert_relative_eq!(hess_for[0][(0, 0)], 2.0);
assert_relative_eq!(hess_for[1][(1, 1)], 2.0);
}