use crate::numerical_derivative::derivator::DerivatorMultiVariable;
use crate::utils::error_codes::CalcError;
#[derive(Debug, Clone, Copy)]
pub struct QuadraticApproximation<const NUM_VARS: usize> {
point: [f64; NUM_VARS],
value: f64,
gradient: [f64; NUM_VARS],
hessian: [[f64; NUM_VARS]; NUM_VARS],
}
#[derive(Debug, Clone, Copy)]
pub struct QuadraticApproximationPredictionMetrics {
pub mean_absolute_error: f64,
pub mean_squared_error: f64,
pub root_mean_squared_error: f64,
pub r_squared: f64,
pub adjusted_r_squared: f64,
}
impl<const NUM_VARS: usize> QuadraticApproximation<NUM_VARS> {
pub fn predict(&self, x: &[f64; NUM_VARS]) -> f64 {
let mut result = self.value;
for (((&gi, &xi), &pi), hrow) in self
.gradient
.iter()
.zip(x)
.zip(&self.point)
.zip(&self.hessian)
{
let di = xi - pi;
result += gi * di;
for ((&hij, &xj), &pj) in hrow.iter().zip(x).zip(&self.point) {
result += 0.5 * hij * di * (xj - pj);
}
}
result
}
pub fn point(&self) -> &[f64; NUM_VARS] {
&self.point
}
pub fn gradient(&self) -> &[f64; NUM_VARS] {
&self.gradient
}
pub fn hessian(&self) -> &[[f64; NUM_VARS]; NUM_VARS] {
&self.hessian
}
pub fn get_prediction_metrics<O: Fn(&[f64; NUM_VARS]) -> f64, const NUM_POINTS: usize>(
&self,
points: &[[f64; NUM_VARS]; NUM_POINTS],
original_function: &O,
) -> QuadraticApproximationPredictionMetrics {
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,
original_function,
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> {
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: Fn(&[f64; NUM_VARS]) -> f64, const NUM_VARS: usize>(
&self,
function: &F,
point: &[f64; NUM_VARS],
) -> Result<QuadraticApproximation<NUM_VARS>, CalcError> {
let value = function(point);
let mut gradient = [0.0; NUM_VARS];
for (i, slot) in gradient.iter_mut().enumerate() {
*slot = self.derivator.get_single_partial(function, i, point)?;
}
let mut hessian = [[f64::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,
})
}
}