use std::{convert::TryFrom, error::Error};
use nalgebra::DMatrix;
use num_traits::Float;
use crate::funcs::opt::{fminfn::fminfn, fmingr::fmingr, Optim, OptimOptions, OptimResponse};
pub struct BFGS {
pub nREPORT: i32,
pub grad_func: Option<Box<dyn Fn(Vec<f64>) -> Vec<f64>>>,
pub optim_options: OptimOptions,
}
impl Default for BFGS {
fn default() -> Self {
Self {
nREPORT: 10,
grad_func: None,
optim_options: OptimOptions {
parscale: None,
fnscale: 1.0,
abstol: f64::neg_infinity(),
reltol: f64::epsilon().sqrt(),
maxit: 100,
},
}
}
}
impl Optim for BFGS {
type ParameterType = Vec<f64>;
fn optim(
&mut self,
par: Self::ParameterType,
min_func: &dyn Fn(Self::ParameterType) -> f64,
) -> Result<OptimResponse<Self::ParameterType>, Box<dyn Error>> {
let npar = par.len();
let parscale = if let Some(parscale) = &self.optim_options.parscale {
parscale.clone()
} else {
vec![1.0; npar]
};
let mut bvec = par
.iter()
.zip(parscale.iter())
.map(|(par, scale)| par / scale)
.collect::<Vec<_>>();
let ndeps = vec![1e-3; npar];
let bounds = None;
let mut grcount = 0;
let mut fncount = 0;
let mut min = f64::nan();
let mut par_out = Vec::new();
let mut fail = true;
let mask = vec![true; par.len()];
(par_out, min, fncount, grcount, fail) = vmmin(
npar,
&bvec,
min_func,
self.grad_func.as_deref(),
self.optim_options.maxit,
&mask,
&ndeps,
&parscale,
self.optim_options.fnscale,
bounds,
self.optim_options.abstol,
self.optim_options.reltol,
self.nREPORT,
)
.expect("Error calculating vmmin");
for i in 0..npar {
par_out[i] *= parscale[i];
}
Ok(OptimResponse {
parameters: par_out,
value: min,
function_count: fncount,
gradient_count: grcount,
convergence: !fail,
})
}
}
const RELTEST: f64 = 10.0;
const ACCTOL: f64 = 0.0001;
const STEPREDN: f64 = 0.2;
pub fn vmmin(
n0: usize,
b: &[f64],
min_func: &dyn Fn(Vec<f64>) -> f64,
grad_func: Option<&dyn Fn(Vec<f64>) -> Vec<f64>>,
maxit: usize,
mask: &[bool],
ndeps: &[f64],
parscale: &[f64],
fnscale: f64,
bounds: Option<(Vec<f64>, Vec<f64>)>,
abstol: f64,
reltol: f64,
nREPORT: i32,
) -> Result<(Vec<f64>, f64, usize, usize, bool), Box<dyn Error>> {
let mut fail = false;
if maxit == 0 {
let mut b = b.to_vec();
let min = fminfn(n0, &b, parscale, fnscale, min_func);
Ok((b, min, 0, 0, fail))
} else {
if nREPORT <= 0 {
return Err(Box::try_from("nREPORT must be > 0").unwrap());
}
let mut l = vec![0; n0];
let mut n = 0;
for (i, &mask_i) in mask.iter().take(n0).enumerate() {
if mask_i {
l[n] = i;
n += 1;
}
}
let mut t = vec![0.0; n];
let mut x = vec![0.0; n];
let mut c = vec![0.0; n];
let mut B = DMatrix::<f64>::zeros(n, n);
let mut f: f64 = fminfn(n0, b, parscale, fnscale, min_func);
if !f.is_finite() {
return Err(Box::try_from("initial value in vmmin is not finite").unwrap());
}
let mut fmin = f;
let mut funcount = 1;
let mut gradcount = 1;
let (mut b, mut g) = fmingr(
n0,
b,
ndeps,
parscale,
fnscale,
bounds.clone(),
min_func,
grad_func,
);
let mut iter = 1;
let mut ilast = gradcount;
let mut count = 0;
loop {
if ilast == gradcount {
for i in 0..n {
for j in 0..i {
B[(i, j)] = 0.0;
}
B[(i, i)] = 1.0;
}
}
for i in 0..n {
x[i] = b[l[i]];
c[i] = g[l[i]];
}
let mut gradproj = 0.0;
for i in 0..n {
let mut s = 0.0;
for j in 0..=i {
s -= B[(i, j)] * g[l[j]];
}
for j in (i + 1)..n {
s -= B[(j, i)] * g[l[j]];
}
t[i] = s;
gradproj += s * g[l[i]];
}
if gradproj < 0.0 {
let mut steplength = 1.0;
let mut accpoint = false;
loop {
count = 0;
for i in 0..n {
b[l[i]] = x[i] + steplength * t[i];
if RELTEST + x[i] == RELTEST + b[l[i]] {
count += 1;
}
}
if count < n {
f = fminfn(n0, &b, parscale, fnscale, min_func);
funcount += 1;
accpoint = f.is_finite() && f <= fmin + gradproj * steplength * ACCTOL;
if !accpoint {
steplength *= STEPREDN;
}
}
if count == n || accpoint {
break;
}
}
let enough = f > abstol && (f - fmin).abs() > reltol * (fmin.abs() + reltol);
if !enough {
count = n;
fmin = f;
}
if count < n {
fmin = f;
(b, g) = fmingr(
n0,
&b,
ndeps,
parscale,
fnscale,
bounds.clone(),
min_func,
grad_func,
);
gradcount += 1;
iter += 1;
let mut D1 = 0.0;
for i in 0..n {
t[i] *= steplength;
c[i] = g[l[i]] - c[i];
D1 += t[i] * c[i];
}
if D1 > 0.0 {
let mut D2 = 0.0;
for i in 0..n {
let mut s = 0.0;
for j in 0..=i {
s += B[(i, j)] * c[j];
}
for j in (i + 1)..n {
s += B[(j, i)] * c[j];
}
x[i] = s;
D2 += s * c[i];
}
D2 = 1.0 + D2 / D1;
for i in 0..n {
for j in 0..=i {
B[(i, j)] += (D2 * t[i] * t[j] - x[i] * t[j] - t[i] * x[j]) / D1;
}
}
} else {
ilast = gradcount;
}
} else {
if ilast < gradcount {
count = 0;
ilast = gradcount;
}
}
} else {
count = 0;
if ilast == gradcount {
count = n;
} else {
ilast = gradcount;
}
}
if iter >= maxit {
fail = true;
break;
}
if gradcount - ilast > 2 * n {
ilast = gradcount;
}
if count == n && ilast == gradcount {
break;
}
}
Ok((b, fmin, funcount, gradcount, fail))
}
}