use crate::utils::errors::{QSError, Result};
use nalgebra::{DMatrix, DVector};
use std::ops::Sub;
pub type Matrix<T> = Vec<Vec<T>>;
#[derive(Debug)]
pub enum SolutionStatus {
Converged,
NotConverged,
}
#[derive(Debug)]
pub struct OptimizerSolution<X, F = f64> {
pub x: X,
pub f: F,
pub status: SolutionStatus,
pub jacobian: Option<Matrix<f64>>,
}
pub trait ContFunc<X: ?Sized, Y = f64> {
fn call(&self, x: &X) -> Result<Y>;
}
pub trait C1Func<X>: ContFunc<X, f64> {
fn grad(&self, x: &X) -> Result<X>;
}
pub trait C2Func<X, H>: C1Func<X> {
fn inv_hess(&self, x: &X) -> Result<H>;
}
pub trait DescentMethod<P, X>
where
P: ContFunc<X, f64>,
X: Sub<X, Output = X> + Copy,
{
fn max_iter(&self) -> i64;
fn x0(&self) -> X;
fn ftol(&self) -> f64;
fn step(&self, x: &X, f: &P, fval: f64) -> Result<X>;
fn solve(&self, f: &P) -> Result<OptimizerSolution<X, f64>> {
let mut x = self.x0();
let mut fval = 0.0;
for _ in 0..self.max_iter() {
fval = f.call(&x)?;
if fval.abs() < self.ftol() {
return Ok(OptimizerSolution {
x,
f: fval,
status: SolutionStatus::Converged,
jacobian: None,
});
}
x = x - self.step(&x, f, fval)?;
}
Ok(OptimizerSolution {
x,
f: fval,
status: SolutionStatus::NotConverged,
jacobian: None,
})
}
}
pub trait VectorFunc<X, Y>: ContFunc<[X], Vec<Y>> {}
pub trait JacobianFunc<X, Y, J>: VectorFunc<X, Y> {
fn jacobian(&self, x: &[X]) -> Result<Matrix<J>>;
fn solve_ift(&self, x: &[X], g_diag: &[f64]) -> Result<Matrix<f64>>
where
J: Copy + Into<f64>,
{
let j = self.jacobian(x)?;
let n = g_diag.len();
if j.len() != n || j.iter().any(|row| row.len() != n) {
return Err(QSError::SolverErr(
"IFT requires a square Jacobian matching the quote sensitivity size".into(),
));
}
let jacobian = j
.iter()
.map(|row| row.iter().copied().map(Into::into).collect::<Vec<f64>>())
.collect::<Vec<_>>();
let data = jacobian
.iter()
.flat_map(|row| row.iter().copied())
.collect::<Vec<_>>();
let matrix = DMatrix::from_row_slice(n, n, &data);
let mut sensitivities = vec![vec![0.0; n]; n];
for j_col in 0..n {
let mut rhs = vec![0.0; n];
rhs[j_col] = -g_diag[j_col];
let rhs = DVector::from_row_slice(&rhs);
let column = matrix
.clone()
.lu()
.solve(&rhs)
.ok_or_else(|| QSError::SolverErr("Singular Jacobian in IFT".into()))?;
for i in 0..n {
sensitivities[i][j_col] = column[i];
}
}
Ok(sensitivities)
}
}