use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
use scirs2_core::numeric::{Float, One, Zero};
use std::fmt::Debug;
use crate::error::{LinalgError, LinalgResult};
pub mod enhanced;
pub mod matrix_derivatives;
pub mod optimization;
#[allow(dead_code)]
pub fn jacobian<F>(
f: impl Fn(&ArrayView1<F>) -> LinalgResult<Array1<F>>,
x: &ArrayView1<F>,
epsilon: Option<F>,
) -> LinalgResult<Array2<F>>
where
F: Float + Zero + One + Copy + Debug,
{
let n = x.len();
let f_x = f(x)?;
let m = f_x.len();
let eps = epsilon.unwrap_or_else(|| F::epsilon().sqrt());
let mut jac = Array2::zeros((m, n));
for j in 0..n {
let mut x_plus = x.to_owned();
x_plus[j] = x_plus[j] + eps;
let f_x_plus = f(&x_plus.view())?;
for i in 0..m {
jac[[i, j]] = (f_x_plus[i] - f_x[i]) / eps;
}
}
Ok(jac)
}
#[allow(dead_code)]
pub fn gradient<F>(
f: impl Fn(&ArrayView1<F>) -> LinalgResult<F>,
x: &ArrayView1<F>,
epsilon: Option<F>,
) -> LinalgResult<Array1<F>>
where
F: Float + Zero + One + Copy + Debug,
{
let n = x.len();
let f_x = f(x)?;
let eps = epsilon.unwrap_or_else(|| F::epsilon().sqrt());
let mut grad = Array1::zeros(n);
for i in 0..n {
let mut x_plus = x.to_owned();
x_plus[i] = x_plus[i] + eps;
let f_x_plus = f(&x_plus.view())?;
grad[i] = (f_x_plus - f_x) / eps;
}
Ok(grad)
}
#[allow(dead_code)]
pub fn hessian<F>(
f: impl Fn(&ArrayView1<F>) -> LinalgResult<F>,
x: &ArrayView1<F>,
epsilon: Option<F>,
) -> LinalgResult<Array2<F>>
where
F: Float + Zero + One + Copy + Debug,
{
let n = x.len();
let eps = epsilon.unwrap_or_else(|| F::epsilon().sqrt().sqrt());
let mut hess = Array2::zeros((n, n));
for i in 0..n {
for j in 0..n {
let mut x_pp = x.to_owned();
let mut x_pm = x.to_owned();
let mut x_mp = x.to_owned();
let mut x_mm = x.to_owned();
x_pp[i] = x_pp[i] + eps;
x_pp[j] = x_pp[j] + eps;
x_pm[i] = x_pm[i] + eps;
x_pm[j] = x_pm[j] - eps;
x_mp[i] = x_mp[i] - eps;
x_mp[j] = x_mp[j] + eps;
x_mm[i] = x_mm[i] - eps;
x_mm[j] = x_mm[j] - eps;
let f_pp = f(&x_pp.view())?;
let f_pm = f(&x_pm.view())?;
let f_mp = f(&x_mp.view())?;
let f_mm = f(&x_mm.view())?;
let h_ij =
(f_pp - f_pm - f_mp + f_mm) / (F::from(4.0).expect("Operation failed") * eps * eps);
hess[[i, j]] = h_ij;
}
}
Ok(hess)
}
#[allow(dead_code)]
pub fn directional_derivative<F>(
f: impl Fn(&ArrayView1<F>) -> LinalgResult<F>,
x: &ArrayView1<F>,
v: &ArrayView1<F>,
epsilon: Option<F>,
) -> LinalgResult<F>
where
F: Float + Zero + One + Copy + Debug,
{
if x.len() != v.len() {
return Err(LinalgError::ShapeError(format!(
"Direction vector must have the same dimension as input: {:?} vs {:?}",
v.shape(),
x.shape()
)));
}
let v_norm = v.iter().fold(F::zero(), |acc, &val| acc + val * val).sqrt();
if v_norm < F::epsilon() {
return Err(LinalgError::InvalidInputError(
"Direction vector must not be zero".to_string(),
));
}
let unit_v = Array1::from_iter(v.iter().map(|&val| val / v_norm));
let grad = gradient(f, x, epsilon)?;
let dir_deriv = grad
.iter()
.zip(unit_v.iter())
.fold(F::zero(), |acc, (&g, &v)| acc + g * v);
Ok(dir_deriv)
}
pub use matrix_derivatives::differential_operators;