use crate::numerical_derivative::hessian::Hessian;
use crate::numerical_derivative::jacobian::Jacobian;
use crate::numerical_derivative::mode::*;
use crate::numerical_derivative::derivator::*;
use crate::numerical_derivative::finite_difference::*;
use crate::utils::error_codes::*;
#[cfg(feature = "alloc")]
use alloc::{boxed::Box, vec::Vec};
#[test]
fn test_single_derivative_forward_difference() {
let func = |args: f64| -> f64 { args * args / 2.0 };
let mut derivator = FiniteDifferenceSingle::default();
derivator.config.method = FiniteDifferenceMode::Forward;
let val = derivator.get(1, &func, 2.0).unwrap();
assert!(f64::abs(val - 2.0) < 0.001);
}
#[test]
fn test_single_derivative_backward_difference() {
let func = |args: f64| -> f64 { args * args / 2.0 };
let mut derivator = FiniteDifferenceSingle::default();
derivator.config.method = FiniteDifferenceMode::Backward;
let val = derivator.get(1, &func, 2.0).unwrap();
assert!(f64::abs(val - 2.0) < 0.001);
}
#[test]
fn test_single_derivative_central_difference() {
let func = |args: f64| -> f64 { args * args / 2.0 };
let mut derivator = FiniteDifferenceSingle::default();
derivator.config.method = FiniteDifferenceMode::Central;
let val = derivator.get(1, &func, 2.0).unwrap();
assert!(f64::abs(val - 2.0) < 0.000001);
}
#[test]
fn test_single_derivative_partial_1() {
let func = |args: &[f64; 2]| -> f64 { 3.0 * args[0] * args[0] + 2.0 * args[0] * args[1] };
let point = [1.0, 3.0];
let mut derivator = FiniteDifferenceMulti::default();
derivator.config.method = FiniteDifferenceMode::Central;
let val = derivator.get_single_partial(&func, 0, &point).unwrap();
assert!(f64::abs(val - 12.0) < 0.000001);
let val = derivator.get_single_partial(&func, 1, &point).unwrap();
assert!(f64::abs(val - 2.0) < 0.000001);
}
#[test]
fn test_single_derivative_partial_2() {
let func = |args: &[f64; 3]| -> f64 {
args[1] * args[0].sin() + args[0] * args[1].cos() + args[0] * args[1] * args[2].exp()
};
let point = [1.0, 2.0, 3.0];
let mut derivator = FiniteDifferenceMulti::default();
derivator.config.method = FiniteDifferenceMode::Central;
let val = derivator.get_single_partial(&func, 0, &point).unwrap();
let expected_value = 2.0 * f64::cos(1.0) + f64::cos(2.0) + 2.0 * f64::exp(3.0);
assert!(f64::abs(val - expected_value) < 0.000001);
let val = derivator.get_single_partial(&func, 1, &point).unwrap();
let expected_value = f64::sin(1.0) - 1.0 * f64::sin(2.0) + 1.0 * f64::exp(3.0);
assert!(f64::abs(val - expected_value) < 0.000001);
let val = derivator.get_single_partial(&func, 2, &point).unwrap();
let expected_value = 1.0 * 2.0 * f64::exp(3.0);
assert!(f64::abs(val - expected_value) < 0.00001);
}
#[test]
fn test_single_derivative_error_1() {
let func = |args: &[f64; 3]| -> f64 {
args[1] * args[0].sin() + args[0] * args[1].cos() + args[0] * args[1] * args[2].exp()
};
let point = [1.0, 2.0, 3.0];
let derivator = FiniteDifferenceMulti::from_parameters(0.0, FiniteDifferenceMode::Central, 1.0);
let result = derivator.get(&func, &[0], &point);
assert!(result.is_err());
assert!(result.unwrap_err() == CalcError::StepSizeZero);
}
#[test]
fn test_single_derivative_error_2() {
let func = |args: &[f64; 3]| -> f64 {
args[1] * args[0].sin() + args[0] * args[1].cos() + args[0] * args[1] * args[2].exp()
};
let point = [1.0, 2.0, 3.0];
let derivator = FiniteDifferenceMulti::default();
let result = derivator.get(&func, &[5], &point);
assert!(result.is_err());
assert!(result.unwrap_err() == CalcError::IndexOutOfRange);
}
#[test]
fn test_single_derivative_error_3() {
let func = |args: &[f64; 3]| -> f64 {
args[1] * args[0].sin() + args[0] * args[1].cos() + args[0] * args[1] * args[2].exp()
};
let point = [1.0, 2.0, 3.0];
let derivator = FiniteDifferenceMulti::default();
let idx: [usize; 0] = [];
let result = derivator.get(&func, &idx, &point);
assert!(result.is_err());
assert!(result.unwrap_err() == CalcError::DerivativeOrderZero);
}
#[test]
fn test_single_derivative_error_4() {
let func = |args: &[f64; 3]| -> f64 {
args[1] * args[0].sin() + args[0] * args[1].cos() + args[0] * args[1] * args[2].exp()
};
let point = [1.0, 2.0, 3.0];
let derivator = FiniteDifferenceMulti::default();
let result = derivator.get(&func, &[0, 5], &point);
assert!(result.is_err());
assert!(result.unwrap_err() == CalcError::IndexOutOfRange);
}
#[test]
fn test_double_derivative_forward_difference() {
let func = |args: f64| -> f64 { args * args.sin() };
let mut derivator = FiniteDifferenceSingle::default();
derivator.config.method = FiniteDifferenceMode::Forward;
let val = derivator.get(2, &func, 1.0).unwrap();
let expected_val = 2.0 * f64::cos(1.0) - 1.0 * f64::sin(1.0);
assert!(f64::abs(val - expected_val) < 0.05);
}
#[test]
fn test_double_derivative_backward_difference() {
let func = |args: f64| -> f64 { args * args.sin() };
let mut derivator = FiniteDifferenceSingle::default();
derivator.config.method = FiniteDifferenceMode::Backward;
let val = derivator.get(2, &func, 1.0).unwrap();
let expected_val = 2.0 * f64::cos(1.0) - 1.0 * f64::sin(1.0);
assert!(f64::abs(val - expected_val) < 0.05);
}
#[test]
fn test_double_derivative_central_difference() {
let func = |args: f64| -> f64 { args * args.sin() };
let mut derivator = FiniteDifferenceSingle::default();
derivator.config.method = FiniteDifferenceMode::Central;
let val = derivator.get(2, &func, 1.0).unwrap();
let expected_val = 2.0 * f64::cos(1.0) - 1.0 * f64::sin(1.0);
assert!(f64::abs(val - expected_val) < 0.00001);
}
#[test]
fn test_double_derivative_partial_1() {
let func = |args: &[f64; 3]| -> f64 {
args[1] * args[0].sin() + args[0] * args[1].cos() + args[0] * args[1] * args[2].exp()
};
let point = [1.0, 2.0, 3.0];
let derivator = FiniteDifferenceMulti::default();
let idx: [usize; 2] = [0, 0];
let val = derivator.get(&func, &idx, &point).unwrap();
let expected_value = -2.0 * f64::sin(1.0);
assert!(f64::abs(val - expected_value) < 0.0001);
let idx: [usize; 2] = [1, 1];
let val = derivator.get(&func, &idx, &point).unwrap();
let expected_value = -f64::cos(2.0);
assert!(f64::abs(val - expected_value) < 0.0001);
let idx: [usize; 2] = [2, 2];
let val = derivator.get(&func, &idx, &point).unwrap();
let expected_value = 1.0 * 2.0 * f64::exp(3.0);
assert!(f64::abs(val - expected_value) < 0.0001);
}
#[test]
fn test_double_derivative_partial_2() {
let func = |args: &[f64; 3]| -> f64 {
args[1] * args[0].sin() + args[0] * args[1].cos() + args[0] * args[1] * args[2].exp()
};
let point = [1.0, 2.0, 3.0];
let derivator = FiniteDifferenceMulti::default();
let idx: [usize; 2] = [0, 1]; let val = derivator.get(&func, &idx, &point).unwrap();
let expected_value = f64::cos(1.0) - f64::sin(2.0) + f64::exp(3.0);
assert!(f64::abs(val - expected_value) < 0.001);
let idx: [usize; 2] = [1, 2]; let val = derivator.get(&func, &idx, &point).unwrap();
let expected_value = 1.0 * f64::exp(3.0);
assert!(f64::abs(val - expected_value) < 0.001);
let idx: [usize; 2] = [0, 2]; let val = derivator.get(&func, &idx, &point).unwrap();
let expected_value = 2.0 * f64::exp(3.0);
assert!(f64::abs(val - expected_value) < 0.001);
}
#[test]
fn test_triple_derivative_forward_difference() {
let func = |args: f64| -> f64 { args.powf(4.0) };
let mut derivator = FiniteDifferenceSingle::default();
derivator.config.method = FiniteDifferenceMode::Forward;
let val = derivator.get(4, &func, 1.0).unwrap();
assert!(f64::abs(val - 24.0) < 0.05);
}
#[test]
fn test_triple_derivative_backward_difference() {
let func = |args: f64| -> f64 { args.powf(4.0) };
let mut derivator = FiniteDifferenceSingle::default();
derivator.config.method = FiniteDifferenceMode::Backward;
let val = derivator.get(4, &func, 1.0).unwrap();
assert!(f64::abs(val - 24.0) < 0.05);
}
#[test]
fn test_triple_derivative_central_difference() {
let func = |args: f64| -> f64 { args.powf(4.0) };
let mut derivator = FiniteDifferenceSingle::default();
derivator.config.method = FiniteDifferenceMode::Central;
let val = derivator.get(4, &func, 1.0).unwrap();
assert!(f64::abs(val - 24.0) < 0.05);
}
#[test]
fn test_triple_derivative_partial_1() {
let func = |args: &[f64; 2]| -> f64 { args[1] * args[0].sin() + 2.0 * args[0] * args[1].exp() };
let point = [1.0, 3.0];
let derivator = FiniteDifferenceMulti::default();
let idx = [0, 0, 0];
let val = derivator.get(&func, &idx, &point).unwrap();
let expected_val = -3.0 * f64::cos(1.0);
assert!(f64::abs(val - expected_val) < 0.001);
let idx = [1, 1, 1];
let val = derivator.get(&func, &idx, &point).unwrap();
let expected_val = 2.0 * 1.0 * f64::exp(3.0);
assert!(f64::abs(val - expected_val) < 0.001);
}
#[test]
fn test_triple_derivative_partial_2() {
let func =
|args: &[f64; 3]| -> f64 { args[0].powf(3.0) * args[1].powf(3.0) * args[2].powf(3.0) };
let point = [1.0, 2.0, 3.0];
let derivator = FiniteDifferenceMulti::default();
let idx = [0, 1, 2]; let val = derivator.get(&func, &idx, &point).unwrap();
assert!(f64::abs(val - 972.0) < 0.01);
let idx = [0, 1, 1]; let val = derivator.get(&func, &idx, &point).unwrap();
assert!(f64::abs(val - 972.0) < 0.01);
}
#[test]
fn test_jacobian_1() {
let func1 = |args: &[f64; 3]| -> f64 { args[0] * args[1] * args[2] };
let func2 = |args: &[f64; 3]| -> f64 { args[0].powf(2.0) + args[1].powf(2.0) };
let function_matrix: [&dyn Fn(&[f64; 3]) -> f64; 2] = [&func1, &func2];
let points = [1.0, 2.0, 3.0];
let jacobian = Jacobian::<FiniteDifferenceMulti>::default();
let result = jacobian.get(&function_matrix, &points).unwrap();
assert!(result.len() == function_matrix.len()); assert!(result[0].len() == points.len());
let expected_result = [[6.0, 3.0, 2.0], [2.0, 4.0, 0.0]];
for i in 0..function_matrix.len() {
for j in 0..points.len() {
assert!(f64::abs(result[i][j] - expected_result[i][j]) < 0.000001);
}
}
}
#[test]
#[cfg(feature = "alloc")]
fn test_jacobian_2() {
let func1 = |args: &[f64; 3]| -> f64 { args[0] * args[1] * args[2] };
let func2 = |args: &[f64; 3]| -> f64 { args[0].powf(2.0) + args[1].powf(2.0) };
let function_matrix: Vec<Box<dyn Fn(&[f64; 3]) -> f64>> =
alloc::vec![Box::new(func1), Box::new(func2)];
let points = [1.0, 2.0, 3.0];
let jacobian = Jacobian::<FiniteDifferenceMulti>::default();
let result: Vec<Vec<f64>> = jacobian.get_on_heap(&function_matrix, &points).unwrap();
assert!(result.len() == function_matrix.len()); assert!(result[0].len() == points.len());
let expected_result = [[6.0, 3.0, 2.0], [2.0, 4.0, 0.0]];
for i in 0..function_matrix.len() {
for j in 0..points.len() {
assert!(f64::abs(result[i][j] - expected_result[i][j]) < 0.000001);
}
}
}
#[test]
fn test_jacobian_1_error() {
let function_matrix = [];
let points = [1.0, 2.0, 3.0];
let jacobian = Jacobian::<FiniteDifferenceMulti>::default();
let result = jacobian.get(&function_matrix, &points);
assert!(result.is_err());
assert!(result.unwrap_err() == CalcError::EmptyFunctionSet);
}
#[test]
fn test_hessian_1() {
let func = |args: &[f64; 2]| -> f64 { args[1] * args[0].sin() + 2.0 * args[0] * args[1].exp() };
let points = [1.0, 2.0];
let hessian = Hessian::<FiniteDifferenceMulti>::default();
let result = hessian.get(&func, &points).unwrap();
assert!(result.len() == points.len()); assert!(result[0].len() == points.len());
let expected_result = [
[-2.0 * f64::sin(1.0), f64::cos(1.0) + 2.0 * f64::exp(2.0)],
[f64::cos(1.0) + 2.0 * f64::exp(2.0), 2.0 * f64::exp(2.0)],
];
for i in 0..points.len() {
for j in 0..points.len() {
assert!(f64::abs(result[i][j] - expected_result[i][j]) < 1e-5);
}
}
}