use crate::linear_algebra::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 LinearApproximation<const NUM_VARS: usize, T = f64> {
point: [T; NUM_VARS],
value: T,
gradient: [T; NUM_VARS],
}
#[derive(Debug, Clone, Copy)]
pub struct LinearApproximationPredictionMetrics<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> LinearApproximation<NUM_VARS, T> {
#[inline]
pub fn predict(&self, x: &[T; NUM_VARS]) -> T {
let dx = Vector::from(*x) - Vector::from(self.point);
self.value + Vector::from(self.gradient).dot(dx)
}
pub fn point(&self) -> &[T; NUM_VARS] {
&self.point
}
pub fn coefficients(&self) -> &[T; NUM_VARS] {
&self.gradient
}
pub fn intercept(&self) -> T {
let mut intercept = self.value;
for i in 0..NUM_VARS {
intercept -= self.gradient[i] * self.point[i];
}
intercept
}
pub fn get_prediction_metrics<O: ScalarFnN<NUM_VARS>, const NUM_POINTS: usize>(
&self,
points: &[[T; NUM_VARS]; NUM_POINTS],
original_function: &O,
) -> LinearApproximationPredictionMetrics<T> {
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_VARS, );
LinearApproximationPredictionMetrics {
mean_absolute_error: mae,
mean_squared_error: mse,
root_mean_squared_error: rmse,
r_squared,
adjusted_r_squared,
}
}
}
pub struct LinearApproximator<D: DerivatorMultiVariable = AutoDiffMulti> {
derivator: D,
}
impl<D: DerivatorMultiVariable + Default> Default for LinearApproximator<D> {
fn default() -> Self {
LinearApproximator {
derivator: D::default(),
}
}
}
impl<D: DerivatorMultiVariable> LinearApproximator<D> {
pub fn from_derivator(derivator: D) -> Self {
LinearApproximator { derivator }
}
pub fn get<F: ScalarFnN<NUM_VARS>, const NUM_VARS: usize>(
&self,
function: &F,
point: &[D::Scalar; NUM_VARS],
) -> Result<LinearApproximation<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)?;
}
Ok(LinearApproximation {
point: *point,
value,
gradient,
})
}
}