use crate::linear_algebra::{Matrix, Vector};
use crate::numerical_derivative::autodiff::AutoDiffMulti;
use crate::numerical_derivative::derivator::DerivatorMultiVariable;
use crate::scalar::{Numeric, ScalarFnN};
use crate::utils::error_codes::CalcError;
#[derive(Debug, Clone, Copy)]
pub struct QuadraticApproximation<const NUM_VARS: usize, T = f64> {
point: [T; NUM_VARS],
value: T,
gradient: [T; NUM_VARS],
hessian: [[T; NUM_VARS]; NUM_VARS],
}
#[derive(Debug, Clone, Copy)]
pub struct QuadraticApproximationPredictionMetrics<T = f64> {
pub mean_absolute_error: T,
pub mean_squared_error: T,
pub root_mean_squared_error: T,
pub r_squared: T,
pub adjusted_r_squared: T,
}
impl<const NUM_VARS: usize, T: Numeric> QuadraticApproximation<NUM_VARS, T> {
#[inline]
pub fn predict(&self, x: &[T; NUM_VARS]) -> T {
let dx = Vector::from(*x) - Vector::from(self.point);
let hessian = Matrix::from(self.hessian);
self.value + Vector::from(self.gradient).dot(dx) + T::HALF * (hessian * dx).dot(dx)
}
pub fn point(&self) -> &[T; NUM_VARS] {
&self.point
}
pub fn gradient(&self) -> &[T; NUM_VARS] {
&self.gradient
}
pub fn hessian(&self) -> &[[T; NUM_VARS]; NUM_VARS] {
&self.hessian
}
pub fn get_prediction_metrics<O: ScalarFnN<NUM_VARS>, const NUM_POINTS: usize>(
&self,
points: &[[T; NUM_VARS]; NUM_POINTS],
original_function: &O,
) -> QuadraticApproximationPredictionMetrics<T> {
let num_predictors = NUM_VARS + NUM_VARS * (NUM_VARS + 1) / 2;
let (mae, mse, rmse, r_squared, adjusted_r_squared) = crate::approximation::compute_metrics(
|x| self.predict(x),
points,
&|x: &[T; NUM_VARS]| original_function.eval(x),
num_predictors,
);
QuadraticApproximationPredictionMetrics {
mean_absolute_error: mae,
mean_squared_error: mse,
root_mean_squared_error: rmse,
r_squared,
adjusted_r_squared,
}
}
}
pub struct QuadraticApproximator<D: DerivatorMultiVariable = AutoDiffMulti> {
derivator: D,
}
impl<D: DerivatorMultiVariable + Default> Default for QuadraticApproximator<D> {
fn default() -> Self {
QuadraticApproximator {
derivator: D::default(),
}
}
}
impl<D: DerivatorMultiVariable> QuadraticApproximator<D> {
pub fn from_derivator(derivator: D) -> Self {
QuadraticApproximator { derivator }
}
pub fn get<F: ScalarFnN<NUM_VARS>, const NUM_VARS: usize>(
&self,
function: &F,
point: &[D::Scalar; NUM_VARS],
) -> Result<QuadraticApproximation<NUM_VARS, D::Scalar>, CalcError> {
let value = function.eval(point);
let mut gradient = [<D::Scalar as Numeric>::ZERO; NUM_VARS];
for (i, slot) in gradient.iter_mut().enumerate() {
*slot = self.derivator.get_single_partial(function, i, point)?;
}
let mut hessian = [[<D::Scalar as Numeric>::NAN; NUM_VARS]; NUM_VARS];
#[allow(clippy::needless_range_loop)]
for row in 0..NUM_VARS {
for col in 0..NUM_VARS {
if hessian[row][col].is_nan() {
hessian[row][col] =
self.derivator
.get_double_partial(function, &[row, col], point)?;
hessian[col][row] = hessian[row][col];
}
}
}
Ok(QuadraticApproximation {
point: *point,
value,
gradient,
hessian,
})
}
}