use crate::ad::{
adreal::{ADReal, IsReal},
tape::Tape,
};
use crate::utils::errors::{QSError, Result};
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 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,
});
}
x = x - self.step(&x, f, fval)?;
}
Ok(OptimizerSolution {
x,
f: fval,
status: SolutionStatus::NotConverged,
})
}
}
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>>;
}
pub trait ADJacobian: VectorFunc<ADReal, ADReal> {
fn jacobian_ad(&self, x: &[ADReal]) -> Result<Matrix<f64>> {
let started_locally = if Tape::is_active() {
false
} else {
Tape::start_recording();
true
};
let result = (|| {
Tape::set_mark();
let local_x = x
.iter()
.map(|value| ADReal::new(value.value()))
.collect::<Vec<_>>();
let residual = self.call(&local_x)?;
let n = x.len();
if residual.len() != n {
return Err(QSError::SolverErr(
"Vector function must return residual size equal to variable size".into(),
));
}
for item in residual.iter().take(n) {
if !item.is_on_tape() {
return Err(QSError::NodeNotIndexedInTapeErr);
}
}
let mut j = vec![vec![0.0; n]; n];
for (row, j_row) in j.iter_mut().enumerate().take(n) {
Tape::reset_adjoints();
residual[row].backward_to_mark()?;
for (col, j_cell) in j_row.iter_mut().enumerate().take(n) {
*j_cell = local_x[col].adjoint()?;
}
}
Tape::reset_adjoints();
Ok(j)
})();
if started_locally {
Tape::stop_recording();
Tape::rewind_to_init();
} else {
Tape::rewind_to_mark();
}
result
}
}
impl<T> JacobianFunc<ADReal, ADReal, f64> for T
where
T: ADJacobian,
{
fn jacobian(&self, x: &[ADReal]) -> Result<Matrix<f64>> {
self.jacobian_ad(x)
}
}