#![allow(clippy::neg_cmp_op_on_partial_ord)]
use crate::core::math::Scalar;
use super::filter::{moderatef, savefilt, selectx};
use super::geometry::{assess_geo, geostep, setdrop_geo, setdrop_tr};
use super::init::initxfc;
use super::linalg::dot;
use super::model::{build_a, build_g};
use super::trstlp::{trrad, trstlp};
use super::update::{NO_DROP, updatepole, updatexfc};
pub(crate) type EvalFn<'a, F, E> = dyn FnMut(&[F]) -> Result<(F, Vec<F>), E> + 'a;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum Transition {
Continue,
RhoReduced,
Converged,
Failed,
}
pub(crate) struct CobylaWork<F = f64> {
n: usize,
m: usize,
sim: Vec<F>, simi: Vec<F>, fval: Vec<F>, conmat: Vec<F>, cval: Vec<F>, cpen: F,
delta: F,
rho: F,
rho_end: F,
eta1: F,
eta2: F,
gamma1: F,
gamma2: F,
gamma3: F,
factor_alpha: F,
factor_beta: F,
factor_gamma: F,
ctol: F,
cweight: F,
maxfilt: usize,
nfilt: usize,
xfilt: Vec<F>,
ffilt: Vec<F>,
cfilt: Vec<F>,
confilt: Vec<F>,
}
impl<F: Scalar> CobylaWork<F> {
#[allow(clippy::too_many_arguments)]
pub(crate) fn try_init<E>(
x0: Vec<F>,
m: usize,
rho_beg: F,
rho_end: F,
eval: &mut EvalFn<F, E>,
) -> Result<(Self, Vec<F>, F), E> {
let n = x0.len();
let zero = F::zero();
let (f0_raw, c0_raw) = eval(&x0)?;
let f0 = moderatef(f0_raw);
let constr0: Vec<F> = c0_raw.iter().map(|&v| moderatef(v)).collect();
let out = initxfc(n, m, &x0, f0, &constr0, rho_beg, eval)?;
let eta1 = F::from_f64(0.1).unwrap();
let eta2 = F::from_f64(0.7).unwrap();
let gamma1 = F::from_f64(0.5).unwrap();
let gamma2 = F::from_f64(2.0).unwrap();
let gamma3 =
F::one().max((F::from_f64(0.75).unwrap() * gamma2).min(F::from_f64(1.5).unwrap()));
let ctol = F::epsilon().sqrt();
let cweight = F::from_f64(1.0e8).unwrap();
let cpenmin = F::epsilon();
let cpen = cpenmin.max(F::from_f64(1.0e3).unwrap().min(fcratio(
&out.conmat,
&out.fval,
n,
m,
)));
let maxfilt = 2000usize;
let mut work = Self {
n,
m,
sim: out.sim,
simi: out.simi,
fval: out.fval,
conmat: out.conmat,
cval: out.cval,
cpen,
delta: rho_beg,
rho: rho_beg,
rho_end,
eta1,
eta2,
gamma1,
gamma2,
gamma3,
factor_alpha: F::from_f64(0.25).unwrap(),
factor_beta: F::from_f64(2.1).unwrap(),
factor_gamma: F::from_f64(0.5).unwrap(),
ctol,
cweight,
maxfilt,
nfilt: 0,
xfilt: vec![zero; n * maxfilt],
ffilt: vec![zero; maxfilt],
cfilt: vec![zero; maxfilt],
confilt: vec![zero; m * maxfilt],
};
for j in 0..=n {
let mut x = vec![zero; n];
for r in 0..n {
x[r] = if j < n {
work.sim[r + j * n] + work.sim[r + n * n]
} else {
work.sim[r + n * n]
};
}
let constr: Vec<F> = (0..m).map(|i| work.conmat[i + j * m]).collect();
work.save_to_filter(&x, work.fval[j], work.cval[j], &constr);
}
let (bx, bf) = work.best();
Ok((work, bx, bf))
}
pub(crate) fn rho(&self) -> F {
self.rho
}
fn pole(&self) -> Vec<F> {
(0..self.n).map(|r| self.sim[r + self.n * self.n]).collect()
}
fn save_to_filter(&mut self, x: &[F], f: F, cstrv: F, constr: &[F]) {
savefilt(
x,
f,
cstrv,
constr,
self.n,
self.m,
self.ctol,
self.cweight,
self.maxfilt,
&mut self.nfilt,
&mut self.xfilt,
&mut self.ffilt,
&mut self.cfilt,
&mut self.confilt,
);
}
pub(crate) fn best(&self) -> (Vec<F>, F) {
let kopt = selectx(
&self.ffilt[..self.nfilt],
&self.cfilt[..self.nfilt],
self.cpen.max(self.cweight),
self.ctol,
);
let x = (0..self.n).map(|r| self.xfilt[r + kopt * self.n]).collect();
(x, self.ffilt[kopt])
}
fn eval_moderated<E>(eval: &mut EvalFn<F, E>, x: &[F]) -> Result<(F, Vec<F>, F), E> {
let (f_raw, c_raw) = eval(x)?;
let f = moderatef(f_raw);
let constr: Vec<F> = c_raw.iter().map(|&v| moderatef(v)).collect();
let cstrv = constr
.iter()
.cloned()
.fold(F::zero(), F::max)
.max(F::zero());
Ok((f, constr, cstrv))
}
pub(crate) fn step<E>(&mut self, eval: &mut EvalFn<F, E>) -> Result<Transition, E> {
let n = self.n;
let m = self.m;
let zero = F::zero();
let one = F::one();
self.cpen = self.get_cpen();
if !updatepole(
self.cpen,
&mut self.conmat,
&mut self.cval,
&mut self.fval,
&mut self.sim,
&mut self.simi,
n,
m,
) {
return Ok(Transition::Failed);
}
let adequate_geo = assess_geo(
self.delta,
self.factor_alpha,
self.factor_beta,
&self.sim,
&self.simi,
n,
);
let g = build_g(&self.fval, &self.simi, n);
let a = build_a(&self.conmat, &self.simi, n, m);
let b: Vec<F> = (0..m).map(|i| -self.conmat[i + n * m]).collect();
let d = trstlp(&a, n, m, &b, self.delta, &g);
let dnorm = self.delta.min(dot(&d, &d).sqrt());
let shortd = dnorm < F::from_f64(0.1).unwrap() * self.rho;
let preref = -dot(&d, &g);
let mut lin_cv = zero;
for i in 0..m {
let adi: F = (0..n).map(|l| d[l] * a[l + i * n]).sum();
lin_cv = lin_cv.max(self.conmat[i + n * m] + adi);
}
lin_cv = lin_cv.max(zero);
let prerec = self.cval[n] - lin_cv;
let prerem = preref + self.cpen * prerec;
let trfail =
!(prerem > F::from_f64(1.0e-5).unwrap() * self.cpen.min(one) * self.rho * self.rho);
let mut ratio = -one;
let mut jdrop_tr = NO_DROP;
if shortd || trfail {
self.delta = F::from_f64(0.1).unwrap() * self.delta;
if self.delta <= self.gamma3 * self.rho {
self.delta = self.rho;
}
} else {
let pole = self.pole();
let x: Vec<F> = (0..n).map(|r| pole[r] + d[r]).collect();
let (f, constr, cstrv) = Self::eval_moderated(eval, &x)?;
self.save_to_filter(&x, f, cstrv, &constr);
let actrem = (self.fval[n] + self.cpen * self.cval[n]) - (f + self.cpen * cstrv);
ratio = redrat(actrem, prerem, self.eta1);
self.delta = trrad(
self.delta,
dnorm,
self.eta1,
self.eta2,
self.gamma1,
self.gamma2,
ratio,
);
if self.delta <= self.gamma3 * self.rho {
self.delta = self.rho;
}
let ximproved = actrem > zero;
jdrop_tr = setdrop_tr(
ximproved, &d, self.delta, self.rho, &self.sim, &self.simi, n,
);
if !updatexfc(
jdrop_tr,
&constr,
self.cpen,
cstrv,
&d,
f,
&mut self.conmat,
&mut self.cval,
&mut self.fval,
&mut self.sim,
&mut self.simi,
n,
m,
) {
return Ok(Transition::Failed);
}
}
let bad_trstep = shortd || trfail || ratio <= zero || jdrop_tr == NO_DROP;
let improve_geo = bad_trstep && !adequate_geo;
let reduce_rho = bad_trstep && adequate_geo && self.delta.max(dnorm) <= self.rho;
if improve_geo
&& !assess_geo(
self.delta,
self.factor_alpha,
self.factor_beta,
&self.sim,
&self.simi,
n,
)
{
let jdrop_geo = setdrop_geo(
self.delta,
self.factor_alpha,
self.factor_beta,
&self.sim,
&self.simi,
n,
);
if jdrop_geo == NO_DROP {
return Ok(Transition::Failed);
}
let d = geostep(
jdrop_geo,
&self.conmat,
self.cpen,
self.delta,
&self.fval,
self.factor_gamma,
&self.simi,
n,
m,
);
let pole = self.pole();
let x: Vec<F> = (0..n).map(|r| pole[r] + d[r]).collect();
let (f, constr, cstrv) = Self::eval_moderated(eval, &x)?;
self.save_to_filter(&x, f, cstrv, &constr);
if !updatexfc(
jdrop_geo,
&constr,
self.cpen,
cstrv,
&d,
f,
&mut self.conmat,
&mut self.cval,
&mut self.fval,
&mut self.sim,
&mut self.simi,
n,
m,
) {
return Ok(Transition::Failed);
}
}
if reduce_rho {
if self.rho <= self.rho_end {
if shortd {
let pole = self.pole();
let x: Vec<F> = (0..n).map(|r| pole[r] + d[r]).collect();
let (f, constr, cstrv) = Self::eval_moderated(eval, &x)?;
self.save_to_filter(&x, f, cstrv, &constr);
}
return Ok(Transition::Converged);
}
self.delta = (F::from_f64(0.5).unwrap() * self.rho).max(redrho(self.rho, self.rho_end));
self.rho = redrho(self.rho, self.rho_end);
self.cpen = F::epsilon().max(self.cpen.min(fcratio(&self.conmat, &self.fval, n, m)));
if !updatepole(
self.cpen,
&mut self.conmat,
&mut self.cval,
&mut self.fval,
&mut self.sim,
&mut self.simi,
n,
m,
) {
return Ok(Transition::Failed);
}
return Ok(Transition::RhoReduced);
}
Ok(Transition::Continue)
}
fn get_cpen(&self) -> F {
let n = self.n;
let m = self.m;
let zero = F::zero();
let realmax = F::max_value();
let two = F::from_f64(2.0).unwrap();
let mut cpen = self.cpen;
let mut conmat = self.conmat.clone();
let mut cval = self.cval.clone();
let mut fval = self.fval.clone();
let mut sim = self.sim.clone();
let mut simi = self.simi.clone();
for _ in 0..(n + 1) {
if !updatepole(
cpen,
&mut conmat,
&mut cval,
&mut fval,
&mut sim,
&mut simi,
n,
m,
) {
break;
}
let g = build_g(&fval, &simi, n);
let a = build_a(&conmat, &simi, n, m);
let b: Vec<F> = (0..m).map(|i| -conmat[i + n * m]).collect();
let d = trstlp(&a, n, m, &b, self.delta, &g);
let preref = -dot(&d, &g);
let mut lin_cv = zero;
for i in 0..m {
let adi: F = (0..n).map(|l| d[l] * a[l + i * n]).sum();
lin_cv = lin_cv.max(conmat[i + n * m] + adi);
}
lin_cv = lin_cv.max(zero);
let prerec = cval[n] - lin_cv;
if !(prerec > zero && preref < zero) {
break;
}
cpen = cpen.max((-two * (preref / prerec)).min(realmax));
if super::update::findpole(cpen, &cval, &fval, n) == n {
break;
}
}
cpen
}
}
fn fcratio<F: Scalar>(conmat: &[F], fval: &[F], n: usize, m: usize) -> F {
let zero = F::zero();
let half = F::from_f64(0.5).unwrap();
let np = n + 1;
let mut r = zero;
if m == 0 {
return r;
}
let cmin: Vec<F> = (0..m)
.map(|i| {
(0..np)
.map(|j| -conmat[i + j * m])
.fold(F::infinity(), F::min)
})
.collect();
let cmax: Vec<F> = (0..m)
.map(|i| {
(0..np)
.map(|j| -conmat[i + j * m])
.fold(F::neg_infinity(), F::max)
})
.collect();
let fmin = fval.iter().cloned().fold(F::infinity(), F::min);
let fmax = fval.iter().cloned().fold(F::neg_infinity(), F::max);
let any = (0..m).any(|i| cmin[i] < half * cmax[i]);
if any && fmin < fmax {
let denom = (0..m)
.filter(|&i| cmin[i] < half * cmax[i])
.map(|i| cmax[i].max(zero) - cmin[i])
.fold(F::infinity(), F::min);
r = (fmax - fmin) / denom;
}
r
}
fn redrat<F: Scalar>(ared: F, pred: F, rshrink: F) -> F {
let realmax = F::max_value();
let half = F::from_f64(0.5).unwrap();
if ared.is_nan() {
-realmax
} else if pred.is_nan() || pred <= F::zero() {
if ared > F::zero() {
half * rshrink
} else {
-realmax
}
} else if pred.is_infinite() && pred > F::zero() && ared.is_infinite() && ared > F::zero() {
F::one()
} else if pred.is_infinite() && pred > F::zero() && ared.is_infinite() && ared < F::zero() {
-realmax
} else {
ared / pred
}
}
fn redrho<F: Scalar>(rho_in: F, rhoend: F) -> F {
let tenth = F::from_f64(0.1).unwrap();
let ratio = rho_in / rhoend;
if ratio > F::from_f64(250.0).unwrap() {
tenth * rho_in
} else if ratio <= F::from_f64(16.0).unwrap() {
rhoend
} else {
ratio.sqrt() * rhoend
}
}