#![allow(clippy::ptr_arg)]
mod diff;
#[cfg(feature = "ndarray")]
mod diff_ndarray;
mod hessian;
#[cfg(feature = "ndarray")]
mod hessian_ndarray;
mod jacobian;
#[cfg(feature = "ndarray")]
mod jacobian_ndarray;
mod pert;
mod utils;
use crate::diff::*;
#[cfg(feature = "ndarray")]
use crate::diff_ndarray::*;
use crate::hessian::*;
#[cfg(feature = "ndarray")]
use crate::hessian_ndarray::*;
use crate::jacobian::*;
#[cfg(feature = "ndarray")]
use crate::jacobian_ndarray::*;
pub use crate::pert::*;
#[cfg(feature = "ndarray")]
use ndarray;
const EPS_F64: f64 = std::f64::EPSILON;
pub trait FiniteDiff
where
Self: Sized,
{
type Jacobian;
type Hessian;
type OperatorOutput;
fn forward_diff(&self, f: &dyn Fn(&Self) -> f64) -> Self;
fn central_diff(&self, f: &dyn Fn(&Self) -> f64) -> Self;
fn forward_jacobian(&self, fs: &dyn Fn(&Self) -> Self::OperatorOutput) -> Self::Jacobian;
fn central_jacobian(&self, fs: &dyn Fn(&Self) -> Self::OperatorOutput) -> Self::Jacobian;
fn forward_jacobian_vec_prod(
&self,
fs: &dyn Fn(&Self) -> Self::OperatorOutput,
p: &Self,
) -> Self;
fn central_jacobian_vec_prod(
&self,
fs: &dyn Fn(&Self) -> Self::OperatorOutput,
p: &Self,
) -> Self;
fn forward_jacobian_pert(
&self,
fs: &dyn Fn(&Self) -> Self::OperatorOutput,
pert: &PerturbationVectors,
) -> Self::Jacobian;
fn central_jacobian_pert(
&self,
fs: &dyn Fn(&Self) -> Self::OperatorOutput,
pert: &PerturbationVectors,
) -> Self::Jacobian;
fn forward_hessian(&self, g: &dyn Fn(&Self) -> Self::OperatorOutput) -> Self::Hessian;
fn central_hessian(&self, g: &dyn Fn(&Self) -> Self::OperatorOutput) -> Self::Hessian;
fn forward_hessian_vec_prod(&self, g: &dyn Fn(&Self) -> Self::OperatorOutput, p: &Self)
-> Self;
fn central_hessian_vec_prod(&self, g: &dyn Fn(&Self) -> Self::OperatorOutput, p: &Self)
-> Self;
fn forward_hessian_nograd(&self, f: &dyn Fn(&Self) -> f64) -> Self::Hessian;
fn forward_hessian_nograd_sparse(
&self,
f: &dyn Fn(&Self) -> f64,
indices: Vec<[usize; 2]>,
) -> Self::Hessian;
}
impl FiniteDiff for Vec<f64>
where
Self: Sized,
{
type Jacobian = Vec<Vec<f64>>;
type Hessian = Vec<Vec<f64>>;
type OperatorOutput = Vec<f64>;
fn forward_diff(&self, f: &dyn Fn(&Self) -> f64) -> Self {
forward_diff_vec_f64(self, f)
}
fn central_diff(&self, f: &dyn Fn(&Self) -> f64) -> Self {
central_diff_vec_f64(self, f)
}
fn forward_jacobian(&self, fs: &dyn Fn(&Self) -> Self::OperatorOutput) -> Self::Jacobian {
forward_jacobian_vec_f64(self, fs)
}
fn central_jacobian(&self, fs: &dyn Fn(&Self) -> Self::OperatorOutput) -> Self::Jacobian {
central_jacobian_vec_f64(self, fs)
}
fn forward_jacobian_vec_prod(
&self,
fs: &dyn Fn(&Self) -> Self::OperatorOutput,
p: &Self,
) -> Self {
forward_jacobian_vec_prod_vec_f64(self, fs, p)
}
fn central_jacobian_vec_prod(
&self,
fs: &dyn Fn(&Self) -> Self::OperatorOutput,
p: &Self,
) -> Self {
central_jacobian_vec_prod_vec_f64(self, fs, p)
}
fn forward_jacobian_pert(
&self,
fs: &dyn Fn(&Self) -> Self::OperatorOutput,
pert: &PerturbationVectors,
) -> Self::Jacobian {
forward_jacobian_pert_vec_f64(self, fs, pert)
}
fn central_jacobian_pert(
&self,
fs: &dyn Fn(&Self) -> Self::OperatorOutput,
pert: &PerturbationVectors,
) -> Self::Jacobian {
central_jacobian_pert_vec_f64(self, fs, pert)
}
fn forward_hessian(&self, g: &dyn Fn(&Self) -> Self::OperatorOutput) -> Self::Hessian {
forward_hessian_vec_f64(self, g)
}
fn central_hessian(&self, g: &dyn Fn(&Self) -> Self::OperatorOutput) -> Self::Hessian {
central_hessian_vec_f64(self, g)
}
fn forward_hessian_vec_prod(
&self,
g: &dyn Fn(&Self) -> Self::OperatorOutput,
p: &Self,
) -> Self {
forward_hessian_vec_prod_vec_f64(self, g, p)
}
fn central_hessian_vec_prod(
&self,
g: &dyn Fn(&Self) -> Self::OperatorOutput,
p: &Self,
) -> Self {
central_hessian_vec_prod_vec_f64(self, g, p)
}
fn forward_hessian_nograd(&self, f: &dyn Fn(&Self) -> f64) -> Self::Hessian {
forward_hessian_nograd_vec_f64(self, f)
}
fn forward_hessian_nograd_sparse(
&self,
f: &dyn Fn(&Self) -> f64,
indices: Vec<[usize; 2]>,
) -> Self::Hessian {
forward_hessian_nograd_sparse_vec_f64(self, f, indices)
}
}
#[cfg(feature = "ndarray")]
impl FiniteDiff for ndarray::Array1<f64>
where
Self: Sized,
{
type Jacobian = ndarray::Array2<f64>;
type Hessian = ndarray::Array2<f64>;
type OperatorOutput = ndarray::Array1<f64>;
fn forward_diff(&self, f: &dyn Fn(&Self) -> f64) -> Self {
forward_diff_ndarray_f64(self, f)
}
fn central_diff(&self, f: &dyn Fn(&ndarray::Array1<f64>) -> f64) -> Self {
central_diff_ndarray_f64(self, f)
}
fn forward_jacobian(&self, fs: &dyn Fn(&Self) -> Self::OperatorOutput) -> Self::Jacobian {
forward_jacobian_ndarray_f64(self, fs)
}
fn central_jacobian(&self, fs: &dyn Fn(&Self) -> Self::OperatorOutput) -> Self::Jacobian {
central_jacobian_ndarray_f64(self, fs)
}
fn forward_jacobian_vec_prod(
&self,
fs: &dyn Fn(&Self) -> Self::OperatorOutput,
p: &Self,
) -> Self {
forward_jacobian_vec_prod_ndarray_f64(self, fs, p)
}
fn central_jacobian_vec_prod(
&self,
fs: &dyn Fn(&Self) -> Self::OperatorOutput,
p: &Self,
) -> Self {
central_jacobian_vec_prod_ndarray_f64(self, fs, p)
}
fn forward_jacobian_pert(
&self,
fs: &dyn Fn(&Self) -> Self::OperatorOutput,
pert: &PerturbationVectors,
) -> Self::Jacobian {
forward_jacobian_pert_ndarray_f64(self, fs, pert)
}
fn central_jacobian_pert(
&self,
fs: &dyn Fn(&Self) -> Self::OperatorOutput,
pert: &PerturbationVectors,
) -> Self::Jacobian {
central_jacobian_pert_ndarray_f64(self, fs, pert)
}
fn forward_hessian(&self, g: &dyn Fn(&Self) -> Self::OperatorOutput) -> Self::Jacobian {
forward_hessian_ndarray_f64(self, g)
}
fn central_hessian(&self, g: &dyn Fn(&Self) -> Self::OperatorOutput) -> Self::Jacobian {
central_hessian_ndarray_f64(self, g)
}
fn forward_hessian_vec_prod(
&self,
g: &dyn Fn(&Self) -> Self::OperatorOutput,
p: &Self,
) -> Self {
forward_hessian_vec_prod_ndarray_f64(self, g, p)
}
fn central_hessian_vec_prod(
&self,
g: &dyn Fn(&Self) -> Self::OperatorOutput,
p: &Self,
) -> Self {
central_hessian_vec_prod_ndarray_f64(self, g, p)
}
fn forward_hessian_nograd(&self, f: &dyn Fn(&Self) -> f64) -> Self::Hessian {
forward_hessian_nograd_ndarray_f64(self, f)
}
fn forward_hessian_nograd_sparse(
&self,
f: &dyn Fn(&Self) -> f64,
indices: Vec<[usize; 2]>,
) -> Self::Hessian {
forward_hessian_nograd_sparse_ndarray_f64(self, f, indices)
}
}
#[cfg(test)]
mod tests_vec {
use super::*;
const COMP_ACC: f64 = 1e-6;
fn f1(x: &Vec<f64>) -> f64 {
x[0] + x[1].powi(2)
}
fn f2(x: &Vec<f64>) -> Vec<f64> {
vec![
2.0 * (x[1].powi(3) - x[0].powi(2)),
3.0 * (x[1].powi(3) - x[0].powi(2)) + 2.0 * (x[2].powi(3) - x[1].powi(2)),
3.0 * (x[2].powi(3) - x[1].powi(2)) + 2.0 * (x[3].powi(3) - x[2].powi(2)),
3.0 * (x[3].powi(3) - x[2].powi(2)) + 2.0 * (x[4].powi(3) - x[3].powi(2)),
3.0 * (x[4].powi(3) - x[3].powi(2)) + 2.0 * (x[5].powi(3) - x[4].powi(2)),
3.0 * (x[5].powi(3) - x[4].powi(2)),
]
}
fn f3(x: &Vec<f64>) -> f64 {
x[0] + x[1].powi(2) + x[2] * x[3].powi(2)
}
fn g(x: &Vec<f64>) -> Vec<f64> {
vec![1.0, 2.0 * x[1], x[3].powi(2), 2.0 * x[3] * x[2]]
}
fn x1() -> Vec<f64> {
vec![1.0f64, 1.0f64]
}
fn x2() -> Vec<f64> {
vec![1.0f64, 1.0, 1.0, 1.0, 1.0, 1.0]
}
fn x3() -> Vec<f64> {
vec![1.0f64, 1.0, 1.0, 1.0]
}
fn res1() -> Vec<Vec<f64>> {
vec![
vec![-4.0, -6.0, 0.0, 0.0, 0.0, 0.0],
vec![6.0, 5.0, -6.0, 0.0, 0.0, 0.0],
vec![0.0, 6.0, 5.0, -6.0, 0.0, 0.0],
vec![0.0, 0.0, 6.0, 5.0, -6.0, 0.0],
vec![0.0, 0.0, 0.0, 6.0, 5.0, -6.0],
vec![0.0, 0.0, 0.0, 0.0, 6.0, 9.0],
]
}
fn res2() -> Vec<Vec<f64>> {
vec![
vec![0.0, 0.0, 0.0, 0.0],
vec![0.0, 2.0, 0.0, 0.0],
vec![0.0, 0.0, 0.0, 2.0],
vec![0.0, 0.0, 2.0, 2.0],
]
}
fn res3() -> Vec<f64> {
vec![8.0, 22.0, 27.0, 32.0, 37.0, 24.0]
}
fn pert() -> PerturbationVectors {
vec![
PerturbationVector::new()
.add(0, vec![0, 1])
.add(3, vec![2, 3, 4]),
PerturbationVector::new()
.add(1, vec![0, 1, 2])
.add(4, vec![3, 4, 5]),
PerturbationVector::new()
.add(2, vec![1, 2, 3])
.add(5, vec![4, 5]),
]
}
fn p1() -> Vec<f64> {
vec![1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0]
}
fn p2() -> Vec<f64> {
vec![2.0, 3.0, 4.0, 5.0]
}
#[test]
fn test_forward_diff_vec_f64_trait() {
let grad = x1().forward_diff(&f1);
let res = vec![1.0f64, 2.0];
for i in 0..2 {
assert!((res[i] - grad[i]).abs() < COMP_ACC)
}
let p = vec![1.0f64, 2.0f64];
let grad = p.forward_diff(&f1);
let res = vec![1.0f64, 4.0];
for i in 0..2 {
assert!((res[i] - grad[i]).abs() < COMP_ACC)
}
}
#[test]
fn test_central_diff_vec_f64_trait() {
let grad = x1().central_diff(&f1);
let res = vec![1.0f64, 2.0];
for i in 0..2 {
assert!((res[i] - grad[i]).abs() < COMP_ACC)
}
let p = vec![1.0f64, 2.0f64];
let grad = p.central_diff(&f1);
let res = vec![1.0f64, 4.0];
for i in 0..2 {
assert!((res[i] - grad[i]).abs() < COMP_ACC)
}
}
#[test]
fn test_forward_jacobian_vec_f64_trait() {
let jacobian = x2().forward_jacobian(&f2);
let res = res1();
for i in 0..6 {
for j in 0..6 {
assert!((res[i][j] - jacobian[i][j]).abs() < COMP_ACC)
}
}
}
#[test]
fn test_central_jacobian_vec_f64_trait() {
let jacobian = x2().central_jacobian(&f2);
let res = res1();
for i in 0..6 {
for j in 0..6 {
assert!((res[i][j] - jacobian[i][j]).abs() < COMP_ACC)
}
}
}
#[test]
fn test_forward_jacobian_vec_prod_vec_f64_trait() {
let jacobian = x2().forward_jacobian_vec_prod(&f2, &p1());
let res = res3();
for i in 0..6 {
assert!((res[i] - jacobian[i]).abs() < 5.5 * COMP_ACC)
}
}
#[test]
fn test_central_jacobian_vec_prod_vec_f64_trait() {
let jacobian = x2().central_jacobian_vec_prod(&f2, &p1());
let res = res3();
for i in 0..6 {
assert!((res[i] - jacobian[i]).abs() < COMP_ACC)
}
}
#[test]
fn test_forward_jacobian_pert_vec_f64_trait() {
let jacobian = x2().forward_jacobian_pert(&f2, &pert());
let res = res1();
for i in 0..6 {
for j in 0..6 {
assert!((res[i][j] - jacobian[i][j]).abs() < COMP_ACC)
}
}
}
#[test]
fn test_central_jacobian_pert_vec_f64_trait() {
let jacobian = x2().central_jacobian_pert(&f2, &pert());
let res = res1();
for i in 0..6 {
for j in 0..6 {
assert!((res[i][j] - jacobian[i][j]).abs() < COMP_ACC)
}
}
}
#[test]
fn test_forward_hessian_vec_f64_trait() {
let hessian = x3().forward_hessian(&g);
let res = res2();
for i in 0..4 {
for j in 0..4 {
assert!((res[i][j] - hessian[i][j]).abs() < COMP_ACC)
}
}
}
#[test]
fn test_central_hessian_vec_f64_trait() {
let hessian = x3().central_hessian(&g);
let res = res2();
for i in 0..4 {
for j in 0..4 {
assert!((res[i][j] - hessian[i][j]).abs() < COMP_ACC)
}
}
}
#[test]
fn test_forward_hessian_vec_prod_vec_f64_trait() {
let hessian = x3().forward_hessian_vec_prod(&g, &p2());
let res = vec![0.0, 6.0, 10.0, 18.0];
for i in 0..4 {
assert!((res[i] - hessian[i]).abs() < COMP_ACC)
}
}
#[test]
fn test_central_hessian_vec_prod_vec_f64_trait() {
let hessian = x3().central_hessian_vec_prod(&g, &p2());
let res = vec![0.0, 6.0, 10.0, 18.0];
for i in 0..4 {
assert!((res[i] - hessian[i]).abs() < COMP_ACC)
}
}
#[test]
fn test_forward_hessian_nograd_vec_f64_trait() {
let hessian = x3().forward_hessian_nograd(&f3);
let res = res2();
for i in 0..4 {
for j in 0..4 {
assert!((res[i][j] - hessian[i][j]).abs() < COMP_ACC)
}
}
}
#[test]
fn test_forward_hessian_nograd_sparse_vec_f64_trait() {
let indices = vec![[1, 1], [2, 3], [3, 3]];
let hessian = x3().forward_hessian_nograd_sparse(&f3, indices);
let res = res2();
for i in 0..4 {
for j in 0..4 {
assert!((res[i][j] - hessian[i][j]).abs() < COMP_ACC)
}
}
}
}
#[cfg(feature = "ndarray")]
#[cfg(test)]
mod tests_ndarray {
use super::*;
use ndarray;
use ndarray::{array, Array1};
const COMP_ACC: f64 = 1e-6;
fn f1(x: &Array1<f64>) -> f64 {
x[0] + x[1].powi(2)
}
fn f2(x: &Array1<f64>) -> Array1<f64> {
array![
2.0 * (x[1].powi(3) - x[0].powi(2)),
3.0 * (x[1].powi(3) - x[0].powi(2)) + 2.0 * (x[2].powi(3) - x[1].powi(2)),
3.0 * (x[2].powi(3) - x[1].powi(2)) + 2.0 * (x[3].powi(3) - x[2].powi(2)),
3.0 * (x[3].powi(3) - x[2].powi(2)) + 2.0 * (x[4].powi(3) - x[3].powi(2)),
3.0 * (x[4].powi(3) - x[3].powi(2)) + 2.0 * (x[5].powi(3) - x[4].powi(2)),
3.0 * (x[5].powi(3) - x[4].powi(2)),
]
}
fn f3(x: &Array1<f64>) -> f64 {
x[0] + x[1].powi(2) + x[2] * x[3].powi(2)
}
fn g(x: &Array1<f64>) -> Array1<f64> {
array![1.0, 2.0 * x[1], x[3].powi(2), 2.0 * x[3] * x[2]]
}
fn x1() -> Array1<f64> {
array![1.0f64, 1.0f64]
}
fn x2() -> Array1<f64> {
array![1.0f64, 1.0, 1.0, 1.0, 1.0, 1.0]
}
fn x3() -> Array1<f64> {
array![1.0f64, 1.0, 1.0, 1.0]
}
fn res1() -> Vec<Vec<f64>> {
vec![
vec![-4.0, -6.0, 0.0, 0.0, 0.0, 0.0],
vec![6.0, 5.0, -6.0, 0.0, 0.0, 0.0],
vec![0.0, 6.0, 5.0, -6.0, 0.0, 0.0],
vec![0.0, 0.0, 6.0, 5.0, -6.0, 0.0],
vec![0.0, 0.0, 0.0, 6.0, 5.0, -6.0],
vec![0.0, 0.0, 0.0, 0.0, 6.0, 9.0],
]
}
fn res2() -> Vec<Vec<f64>> {
vec![
vec![0.0, 0.0, 0.0, 0.0],
vec![0.0, 2.0, 0.0, 0.0],
vec![0.0, 0.0, 0.0, 2.0],
vec![0.0, 0.0, 2.0, 2.0],
]
}
fn res3() -> Vec<f64> {
vec![8.0, 22.0, 27.0, 32.0, 37.0, 24.0]
}
fn pert() -> PerturbationVectors {
vec![
PerturbationVector::new()
.add(0, vec![0, 1])
.add(3, vec![2, 3, 4]),
PerturbationVector::new()
.add(1, vec![0, 1, 2])
.add(4, vec![3, 4, 5]),
PerturbationVector::new()
.add(2, vec![1, 2, 3])
.add(5, vec![4, 5]),
]
}
fn p1() -> Array1<f64> {
array![1.0f64, 2.0, 3.0, 4.0, 5.0, 6.0]
}
fn p2() -> Array1<f64> {
array![2.0, 3.0, 4.0, 5.0]
}
#[test]
fn test_forward_diff_ndarray_f64_trait() {
let grad = x1().forward_diff(&f1);
let res = array![1.0f64, 2.0];
for i in 0..2 {
assert!((res[i] - grad[i]).abs() < COMP_ACC)
}
let x = array![1.0f64, 2.0f64];
let grad = x.forward_diff(&f1);
let res = vec![1.0f64, 4.0];
for i in 0..2 {
assert!((res[i] - grad[i]).abs() < COMP_ACC)
}
}
#[test]
fn test_central_diff_ndarray_f64_trait() {
let grad = x1().central_diff(&f1);
let res = vec![1.0f64, 2.0];
for i in 0..2 {
assert!((res[i] - grad[i]).abs() < COMP_ACC)
}
let x = array![1.0f64, 2.0f64];
let grad = x.central_diff(&f1);
let res = vec![1.0f64, 4.0];
for i in 0..2 {
assert!((res[i] - grad[i]).abs() < COMP_ACC)
}
}
#[test]
fn test_forward_jacobian_ndarray_f64_trait() {
let jacobian = x2().forward_jacobian(&f2);
let res = res1();
for i in 0..6 {
for j in 0..6 {
assert!((res[i][j] - jacobian[(i, j)]).abs() < COMP_ACC)
}
}
}
#[test]
fn test_central_jacobian_ndarray_f64_trait() {
let jacobian = x2().central_jacobian(&f2);
let res = res1();
for i in 0..6 {
for j in 0..6 {
assert!((res[i][j] - jacobian[(i, j)]).abs() < COMP_ACC)
}
}
}
#[test]
fn test_forward_jacobian_vec_prod_ndarray_f64_trait() {
let jacobian = x2().forward_jacobian_vec_prod(&f2, &p1());
let res = res3();
for i in 0..6 {
assert!((res[i] - jacobian[i]).abs() < 5.5 * COMP_ACC)
}
}
#[test]
fn test_central_jacobian_vec_prod_ndarray_f64_trait() {
let jacobian = x2().central_jacobian_vec_prod(&f2, &p1());
let res = res3();
for i in 0..6 {
assert!((res[i] - jacobian[i]).abs() < COMP_ACC)
}
}
#[test]
fn test_forward_jacobian_pert_ndarray_f64_trait() {
let jacobian = x2().forward_jacobian_pert(&f2, &pert());
let res = res1();
for i in 0..6 {
for j in 0..6 {
assert!((res[i][j] - jacobian[(i, j)]).abs() < COMP_ACC)
}
}
}
#[test]
fn test_central_jacobian_pert_ndarray_f64_trait() {
let jacobian = x2().central_jacobian_pert(&f2, &pert());
let res = res1();
for i in 0..6 {
for j in 0..6 {
assert!((res[i][j] - jacobian[(i, j)]).abs() < COMP_ACC)
}
}
}
#[test]
fn test_forward_hessian_ndarray_f64_trait() {
let hessian = x3().forward_hessian(&g);
let res = res2();
for i in 0..4 {
for j in 0..4 {
assert!((res[i][j] - hessian[(i, j)]).abs() < COMP_ACC)
}
}
}
#[test]
fn test_central_hessian_ndarray_f64_trait() {
let hessian = x3().central_hessian(&g);
let res = res2();
for i in 0..4 {
for j in 0..4 {
assert!((res[i][j] - hessian[(i, j)]).abs() < COMP_ACC)
}
}
}
#[test]
fn test_forward_hessian_vec_prod_ndarray_f64_trait() {
let hessian = x3().forward_hessian_vec_prod(&g, &p2());
let res = vec![0.0, 6.0, 10.0, 18.0];
for i in 0..4 {
assert!((res[i] - hessian[i]).abs() < COMP_ACC)
}
}
#[test]
fn test_central_hessian_vec_prod_ndarray_f64_trait() {
let hessian = x3().central_hessian_vec_prod(&g, &p2());
let res = vec![0.0, 6.0, 10.0, 18.0];
for i in 0..4 {
assert!((res[i] - hessian[i]).abs() < COMP_ACC)
}
}
#[test]
fn test_forward_hessian_nograd_ndarray_f64_trait() {
let hessian = x3().forward_hessian_nograd(&f3);
let res = res2();
for i in 0..4 {
for j in 0..4 {
assert!((res[i][j] - hessian[(i, j)]).abs() < COMP_ACC)
}
}
}
#[test]
fn test_forward_hessian_nograd_sparse_ndarray_f64_trait() {
let indices = vec![[1, 1], [2, 3], [3, 3]];
let hessian = x3().forward_hessian_nograd_sparse(&f3, indices);
let res = res2();
for i in 0..4 {
for j in 0..4 {
assert!((res[i][j] - hessian[(i, j)]).abs() < COMP_ACC)
}
}
}
}