use crate::consts::{DAMAGING_ROUNDING, INFO_DFT, MAXFUN_REACHED};
use crate::linalg::{inprod, matprod12_into, matprod21_into, planerot};
use crate::mat::Mat;
use crate::math;
use crate::powalg::{hess_mul_into, setij_into};
use crate::util::{checkexit, evaluate, xinbd_into};
#[derive(Debug, Clone)]
struct UpdatehRscWs {
vlag: Vec<f64>, hcol: Vec<f64>, v1: Vec<f64>, v2: Vec<f64>, }
#[derive(Debug, Clone)]
pub(crate) struct RescueWs {
xopt: Vec<f64>, v: Vec<f64>, ptsaux1: Vec<f64>, ptsaux2: Vec<f64>, ptsid: Vec<f64>, ij: Vec<(usize, usize)>, score: Vec<f64>, vlag: Vec<f64>, wmv: Vec<f64>, wmv_z: Vec<f64>, z_wmv_z: Vec<f64>, bmat_wmv: Vec<f64>, t: Vec<f64>, den: Vec<f64>, xnew: Vec<f64>, x: Vec<f64>, xmod: Vec<f64>, xxpt: Vec<f64>, pq_xxpt: Vec<f64>, zrow: Vec<f64>, z_zrow: Vec<f64>, pqinc: Vec<f64>, xpt_col: Vec<f64>, shift: Vec<f64>, dxpt: Vec<f64>, rsc: UpdatehRscWs,
}
impl RescueWs {
pub(crate) fn new(n: usize, npt: usize) -> Self {
Self {
xopt: vec![0.0; n],
v: vec![0.0; n],
ptsaux1: vec![0.0; n],
ptsaux2: vec![0.0; n],
ptsid: vec![0.0; npt],
ij: Vec::with_capacity(npt.saturating_sub(2 * n + 1)),
score: vec![0.0; npt],
vlag: vec![0.0; npt + n],
wmv: vec![0.0; npt + n],
wmv_z: vec![0.0; npt - n - 1],
z_wmv_z: vec![0.0; npt],
bmat_wmv: vec![0.0; n],
t: vec![0.0; n],
den: vec![0.0; npt],
xnew: vec![0.0; n],
x: vec![0.0; n],
xmod: vec![0.0; n],
xxpt: vec![0.0; npt],
pq_xxpt: vec![0.0; npt],
zrow: vec![0.0; npt - n - 1],
z_zrow: vec![0.0; npt],
pqinc: vec![0.0; npt],
xpt_col: vec![0.0; n],
shift: vec![0.0; n],
dxpt: vec![0.0; npt],
rsc: UpdatehRscWs {
vlag: vec![0.0; npt + n],
hcol: vec![0.0; npt + n],
v1: vec![0.0; n],
v2: vec![0.0; n],
},
}
}
}
#[expect(clippy::too_many_arguments)] #[expect(clippy::too_many_lines)] #[expect(clippy::needless_range_loop)] #[expect(clippy::cast_possible_truncation)] #[expect(clippy::cast_sign_loss)] pub(crate) fn rescue<F: FnMut(&[f64]) -> f64>(
calfun: &mut F,
maxfun: usize,
delta: f64,
ftarget: f64,
xl: &[f64],
xu: &[f64],
kopt: &mut usize,
nf: &mut usize,
fval: &mut [f64],
gopt: &mut [f64],
hq: &mut Mat,
pq: &mut [f64],
sl: &mut [f64],
su: &mut [f64],
xbase: &mut [f64],
xpt: &mut Mat,
bmat: &mut Mat,
zmat: &mut Mat,
ws: &mut RescueWs,
) -> i32 {
let n = xpt.nrows();
let npt = xpt.ncols();
let RescueWs {
xopt,
v,
ptsaux1,
ptsaux2,
ptsid,
ij,
score,
vlag,
wmv,
wmv_z,
z_wmv_z,
bmat_wmv,
t,
den,
xnew,
x,
xmod,
xxpt,
pq_xxpt,
zrow,
z_zrow,
pqinc,
xpt_col,
shift,
dxpt,
rsc,
} = ws;
let mut info = INFO_DFT;
if *nf >= maxfun {
bmat.fill(0.0);
zmat.fill(0.0);
return MAXFUN_REACHED;
}
xopt.copy_from_slice(xpt.col(*kopt));
for i in 0..n {
sl[i] = (sl[i] - xopt[i]).min(0.0);
su[i] = (su[i] - xopt[i]).max(0.0);
xbase[i] = xl[i].max(xbase[i] + xopt[i]).min(xu[i]);
}
for k in 0..npt {
let col = xpt.col_mut(k);
for i in 0..n {
col[i] -= xopt[i];
}
}
xpt.col_mut(*kopt).fill(0.0);
let mut pqsum = 0.0;
for &p in pq.iter() {
pqsum += p;
}
let halfsum = 0.5 * pqsum;
matprod21_into(xpt, pq, v);
for i in 0..n {
v[i] += halfsum * xopt[i];
}
crate::linalg::r2update(hq, 1.0, xopt, v);
ptsaux1.fill(0.0);
ptsaux2.fill(0.0);
for i in 0..n {
ptsaux1[i] = delta.min(su[i]);
ptsaux2[i] = (-delta).max(sl[i]);
}
for i in 0..n {
if ptsaux1[i] + ptsaux2[i] < 0.0 {
std::mem::swap(&mut ptsaux1[i], &mut ptsaux2[i]);
}
}
for i in 0..n {
if math::abs(ptsaux2[i]) < 0.5 * math::abs(ptsaux1[i]) {
ptsaux2[i] = 0.5 * ptsaux1[i];
}
}
let sfrac = 0.5 / (n as f64 + 1.0);
ptsid.fill(0.0);
ptsid[0] = sfrac;
bmat.fill(0.0);
zmat.fill(0.0);
for k in 0..n {
ptsid[k + 1] = (k + 1) as f64 + sfrac;
#[expect(clippy::int_plus_one)] if k + 1 <= npt - n - 1 {
ptsid[k + n + 1] = (k + 1) as f64 / (n as f64 + 1.0) + sfrac;
let temp = 1.0 / (ptsaux1[k] - ptsaux2[k]);
bmat[[k, k + 1]] = -temp + 1.0 / ptsaux1[k];
bmat[[k, k + n + 1]] = temp + 1.0 / ptsaux2[k];
bmat[[k, 0]] = -bmat[[k, k + 1]] - bmat[[k, k + n + 1]];
zmat[[0, k]] = math::sqrt(2.0) / math::abs(ptsaux1[k] * ptsaux2[k]);
zmat[[k + 1, k]] = zmat[[0, k]] * ptsaux2[k] * temp;
zmat[[k + n + 1, k]] = -zmat[[0, k]] * ptsaux1[k] * temp;
} else {
bmat[[k, 0]] = -1.0 / ptsaux1[k];
bmat[[k, k + 1]] = 1.0 / ptsaux1[k];
bmat[[k, k + npt]] = -0.5 * ptsaux1[k] * ptsaux1[k];
}
}
setij_into(n, npt, ij);
for k in (2 * n + 1)..npt {
let (i0, j0) = ij[k - (2 * n + 1)];
let ip = i0 + 1; let iq = j0 + 1; ptsid[k] = ip as f64 + iq as f64 / (n as f64 + 1.0) + sfrac;
let temp = 1.0 / (ptsaux1[ip - 1] * ptsaux1[iq - 1]);
zmat[[0, k - n - 1]] = temp;
zmat[[k, k - n - 1]] = temp;
zmat[[ip, k - n - 1]] = -temp;
zmat[[iq, k - n - 1]] = -temp;
}
if *kopt != 0 {
for i in 0..n {
let tmp = bmat[[i, 0]];
bmat[[i, 0]] = bmat[[i, *kopt]];
bmat[[i, *kopt]] = tmp;
}
for j in 0..zmat.ncols() {
let tmp = zmat[[0, j]];
zmat[[0, j]] = zmat[[*kopt, j]];
zmat[[*kopt, j]] = tmp;
}
}
ptsid[0] = ptsid[*kopt];
ptsid[*kopt] = 0.0;
score.fill(0.0);
for k in 0..npt {
let mut s = 0.0;
for &v in xpt.col(k) {
s += v * v;
}
score[k] = math::sqrt(s);
}
score[*kopt] = 0.0;
let mut scoreinc = 0.0_f64;
for &s in score.iter() {
scoreinc = scoreinc.max(s);
}
let mut nprov = npt - 1;
let maxiter = npt * npt;
vlag.fill(0.0);
for _iter in 0..maxiter {
if score.iter().all(|&s| s <= 0.0) || nprov <= 1 {
break;
}
let mut korig = 0;
let mut found = false;
for k in 0..npt {
if score[k] > 0.0 && (!found || score[k] < score[korig]) {
korig = k;
found = true;
}
}
debug_assert!(found, "masked minloc found no positive score");
wmv.fill(0.0);
for k in 0..npt {
if k == *kopt {
wmv[k] = 0.0;
} else if ptsid[k] <= 0.0 {
wmv[k] = inprod(xpt.col(korig), xpt.col(k));
} else {
let ip = math::floor(ptsid[k]) as usize;
let iq = math::floor((n as f64 + 1.0) * ptsid[k] - ((n + 1) * ip) as f64) as usize;
if ip > 0 && iq > 0 {
wmv[k] = xpt[[ip - 1, korig]] * ptsaux1[ip - 1]
+ xpt[[iq - 1, korig]] * ptsaux1[iq - 1];
} else if ip > 0 {
wmv[k] = xpt[[ip - 1, korig]] * ptsaux1[ip - 1];
} else if iq > 0 {
wmv[k] = xpt[[iq - 1, korig]] * ptsaux2[iq - 1];
} else {
wmv[k] = 0.0;
}
}
wmv[k] = 0.5 * wmv[k] * wmv[k];
}
for i in 0..n {
wmv[npt + i] = xpt[[i, korig]];
}
matprod12_into(&wmv[..npt], zmat, wmv_z); matprod21_into(zmat, wmv_z, z_wmv_z); for k in 0..npt {
vlag[k] = z_wmv_z[k] + inprod(&wmv[npt..npt + n], bmat.col(k));
}
matprod21_into(bmat, wmv, bmat_wmv);
vlag[npt..npt + n].copy_from_slice(bmat_wmv);
t.fill(0.0);
for j in 0..npt {
let bj = bmat.col(j);
for i in 0..n {
t[i] += bj[i] * wmv[j];
}
}
for i in 0..n {
t[i] += bmat_wmv[i];
}
let bsum = inprod(&wmv[..n], t);
let mut s = 0.0;
for &x in xpt.col(korig) {
s += x * x;
}
let mut wmvz_sq = 0.0;
for &w in wmv_z.iter() {
wmvz_sq += w * w;
}
let beta = 0.5 * (s * s) - wmvz_sq - bsum;
vlag[*kopt] += 1.0;
den.fill(0.0);
for k in 0..npt {
if ptsid[k] > 0.0 {
let mut hdiag = 0.0;
for j in 0..zmat.ncols() {
let z = zmat[[k, j]];
hdiag += z * z;
}
den[k] = hdiag * beta + vlag[k] * vlag[k];
}
}
let vlag_abs_sum = vlag.iter().map(|v| math::abs(*v)).sum::<f64>();
let mut vmax = 0.0_f64;
for k in 0..npt {
vmax = vmax.max(vlag[k] * vlag[k]);
}
if !(vlag_abs_sum.is_finite() && den.iter().any(|&d| d > 5.0e-2 * vmax)) {
score[korig] = -score[korig] - scoreinc;
continue;
}
let mut kprov = 0;
let mut kfound = false;
for k in 0..npt {
if !den[k].is_nan() && (!kfound || den[k] > den[kprov]) {
kprov = k;
kfound = true;
}
}
if kprov != korig {
for i in 0..n {
let tmp = bmat[[i, kprov]];
bmat[[i, kprov]] = bmat[[i, korig]];
bmat[[i, korig]] = tmp;
}
for j in 0..zmat.ncols() {
let tmp = zmat[[kprov, j]];
zmat[[kprov, j]] = zmat[[korig, j]];
zmat[[korig, j]] = tmp;
}
vlag.swap(kprov, korig);
}
ptsid[kprov] = ptsid[korig];
ptsid[korig] = 0.0;
score[korig] = 0.0;
for s in score.iter_mut() {
*s = math::abs(*s);
}
let _ = updateh_rsc(korig, beta, vlag, bmat, zmat, rsc);
nprov -= 1;
}
let kbase = *kopt;
let fbase = fval[*kopt];
if nprov > 0 {
for kpt in 0..npt {
if ptsid[kpt] <= 0.0 {
continue;
}
xpt_col.copy_from_slice(xpt.col(kpt));
crate::linalg::r1update(hq, pq[kpt], xpt_col);
pq[kpt] = 0.0;
let ip = math::floor(ptsid[kpt]) as usize;
let iq = math::floor((n as f64 + 1.0) * ptsid[kpt] - ((n + 1) * ip) as f64) as usize;
let mut xp = 0.0;
let mut xq = 0.0;
xnew.fill(0.0);
if ip > 0 && iq > 0 {
xp = ptsaux1[ip - 1];
xnew[ip - 1] = xp;
xq = ptsaux1[iq - 1];
xnew[iq - 1] = xq;
} else if ip > 0 {
xp = ptsaux1[ip - 1];
xnew[ip - 1] = xp;
} else if iq > 0 {
xq = ptsaux2[iq - 1];
xnew[iq - 1] = xq;
}
let mut diff_abs_sum = 0.0;
let mut xnew_abs_sum = 0.0;
for i in 0..n {
diff_abs_sum += math::abs(xnew[i] - xpt[[i, kpt]]);
xnew_abs_sum += math::abs(xnew[i]);
}
if diff_abs_sum <= f64::from(1.0e-2_f32) * delta || !xnew_abs_sum.is_finite() {
continue;
}
xpt.col_mut(kpt).copy_from_slice(xnew);
xinbd_into(xbase, xpt.col(kpt), xl, xu, sl, su, x);
let f = evaluate(calfun, x, xmod);
*nf += 1;
fval[kpt] = f;
if f < fval[*kopt] {
*kopt = kpt;
}
let subinfo = checkexit(maxfun, *nf, f, ftarget, x);
if subinfo != INFO_DFT {
info = subinfo;
break;
}
let mut vquad = fbase;
xxpt.fill(0.0);
if ip > 0 && iq > 0 {
vquad += xp * (gopt[ip - 1] + 0.5 * xp * hq[[ip - 1, ip - 1]]);
vquad += xq * (gopt[iq - 1] + 0.5 * xq * hq[[iq - 1, iq - 1]]);
vquad += xp * xq * hq[[ip - 1, iq - 1]];
for k in 0..npt {
xxpt[k] = xp * xpt[[ip - 1, k]] + xq * xpt[[iq - 1, k]];
}
} else if ip > 0 {
vquad += xp * (gopt[ip - 1] + 0.5 * xp * hq[[ip - 1, ip - 1]]);
for k in 0..npt {
xxpt[k] = xp * xpt[[ip - 1, k]];
}
} else if iq > 0 {
vquad += xq * (gopt[iq - 1] + 0.5 * xq * hq[[iq - 1, iq - 1]]);
for k in 0..npt {
xxpt[k] = xq * xpt[[iq - 1, k]];
}
}
pq_xxpt.fill(0.0);
for k in 0..npt {
pq_xxpt[k] = pq[k] * xxpt[k];
}
vquad += 0.5 * inprod(xxpt, pq_xxpt);
let moderr = f - vquad;
for i in 0..n {
gopt[i] += moderr * bmat[[i, kpt]];
}
zrow.fill(0.0);
for j in 0..zmat.ncols() {
zrow[j] = zmat[[kpt, j]];
}
matprod21_into(zmat, zrow, z_zrow);
pqinc.fill(0.0);
for k in 0..npt {
pqinc[k] = moderr * z_zrow[k];
}
for k in 0..npt {
if ptsid[k] <= 0.0 {
pq[k] += pqinc[k];
}
}
for k in 0..npt {
if ptsid[k] <= 0.0 {
continue;
}
let ipk = math::floor(ptsid[k]) as usize;
let iqk =
math::floor((n as f64 + 1.0) * ptsid[k] - ((n + 1) * ipk) as f64) as usize;
if ipk > 0 && iqk > 0 {
hq[[ipk - 1, ipk - 1]] += pqinc[k] * (ptsaux1[ipk - 1] * ptsaux1[ipk - 1]);
hq[[iqk - 1, iqk - 1]] += pqinc[k] * (ptsaux1[iqk - 1] * ptsaux1[iqk - 1]);
hq[[ipk - 1, iqk - 1]] += pqinc[k] * ptsaux1[ipk - 1] * ptsaux1[iqk - 1];
hq[[iqk - 1, ipk - 1]] = hq[[ipk - 1, iqk - 1]];
} else if ipk > 0 {
hq[[ipk - 1, ipk - 1]] += pqinc[k] * (ptsaux1[ipk - 1] * ptsaux1[ipk - 1]);
} else if iqk > 0 {
hq[[iqk - 1, iqk - 1]] += pqinc[k] * (ptsaux2[iqk - 1] * ptsaux2[iqk - 1]);
}
}
ptsid[kpt] = 0.0;
}
}
if *kopt != kbase {
xpt_col.copy_from_slice(xpt.col(*kopt));
hess_mul_into(xpt_col, xpt, pq, Some(&*hq), dxpt, shift);
for i in 0..n {
gopt[i] += shift[i];
}
}
info
}
fn updateh_rsc(
knew: usize,
beta: f64,
vlag_in: &[f64],
bmat: &mut Mat,
zmat: &mut Mat,
ws: &mut UpdatehRscWs,
) -> i32 {
let n = bmat.nrows();
let npt = bmat.ncols() - n;
let UpdatehRscWs { vlag, hcol, v1, v2 } = ws;
vlag.copy_from_slice(vlag_in);
let tau = vlag[knew];
let mut zknew_sq = 0.0;
for j in 0..zmat.ncols() {
let z = zmat[[knew, j]];
zknew_sq += z * z;
}
let denom = zknew_sq * beta + tau * tau;
let vlag_abs_sum = vlag.iter().map(|v| math::abs(*v)).sum::<f64>();
if !((vlag_abs_sum + math::abs(beta)).is_finite() && denom > 0.0) {
return DAMAGING_ROUNDING;
}
vlag[knew] -= 1.0;
let nz = zmat.ncols();
for j in 1..nz {
let zmaxabs = zmat
.data()
.iter()
.map(|v| math::abs(*v))
.fold(0.0_f64, f64::max);
if math::abs(zmat[[knew, j]]) > f64::from(1.0e-20_f32) * zmaxabs {
let grot = planerot([zmat[[knew, 0]], zmat[[knew, j]]]);
for i in 0..zmat.nrows() {
let a = zmat[[i, 0]];
let b = zmat[[i, j]];
zmat[[i, 0]] = a * grot[0][0] + b * grot[0][1];
zmat[[i, j]] = a * grot[1][0] + b * grot[1][1];
}
}
zmat[[knew, j]] = 0.0;
}
hcol.fill(0.0);
let zknew0 = zmat[[knew, 0]];
for k in 0..npt {
hcol[k] = zknew0 * zmat[[k, 0]];
}
for i in 0..n {
hcol[npt + i] = bmat[[i, knew]];
}
let sqrtdn = math::sqrt(denom);
let zknew1 = zmat[[knew, 0]] / sqrtdn;
let tau_sqrtdn = tau / sqrtdn;
for k in 0..npt {
zmat[[k, 0]] = tau_sqrtdn * zmat[[k, 0]] - zknew1 * vlag[k];
}
zmat[[knew, 0]] = zknew1;
let alpha = hcol[knew];
v1.fill(0.0);
v2.fill(0.0);
for i in 0..n {
v1[i] = (alpha * vlag[npt + i] - tau * hcol[npt + i]) / denom;
v2[i] = (-beta * hcol[npt + i] - tau * vlag[npt + i]) / denom;
}
for j in 0..(npt + n) {
for i in 0..n {
bmat[[i, j]] = bmat[[i, j]] + v1[i] * vlag[j] + v2[i] * hcol[j];
}
}
for j in 0..n {
for i in 0..j {
bmat[[i, npt + j]] = bmat[[j, npt + i]];
}
}
INFO_DFT
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mat::Mat;
use crate::test_support::{self, DiffStats};
#[test]
#[expect(clippy::similar_names)] fn rescue_matches_prima_on_every_captured_state() {
let states = test_support::load_states("rescue");
assert!(!states.is_empty());
let mut stats = DiffStats::default();
for st in &states {
let (e, x) = (&st.entry, &st.exit);
let f = test_support::objective(&st.problem);
let mut kopt = e.usize("kopt") - 1;
let mut nf = e.usize("nf");
let (mut fval, mut gopt, mut pq) = (e.vec("fval"), e.vec("gopt"), e.vec("pq"));
let (mut sl, mut su, mut xbase) = (e.vec("sl"), e.vec("su"), e.vec("xbase"));
let (mut hq, mut xpt) = (e.mat("hq"), e.mat("xpt"));
let (mut bmat, mut zmat) = (e.mat("bmat"), e.mat("zmat"));
let mut ws = RescueWs::new(xpt.nrows(), xpt.ncols());
let info = rescue(
&mut |p: &[f64]| f(p),
e.usize("maxfun"),
e.f64("delta"),
e.f64("ftarget"),
&e.vec("xl"),
&e.vec("xu"),
&mut kopt,
&mut nf,
&mut fval,
&mut gopt,
&mut hq,
&mut pq,
&mut sl,
&mut su,
&mut xbase,
&mut xpt,
&mut bmat,
&mut zmat,
&mut ws,
);
assert_eq!(kopt + 1, x.usize("kopt"), "{}: kopt", st.problem);
assert_eq!(nf, x.usize("nf"), "{}: nf", st.problem);
assert_eq!(i64::from(info), x.i64("info"), "{}: info", st.problem);
stats.slice("fval", &fval, &x.vec("fval"));
stats.slice("gopt", &gopt, &x.vec("gopt"));
stats.mat("hq", &hq, &x.mat("hq"));
stats.slice("pq", &pq, &x.vec("pq"));
stats.slice("sl", &sl, &x.vec("sl"));
stats.slice("su", &su, &x.vec("su"));
stats.slice("xbase", &xbase, &x.vec("xbase"));
stats.mat("xpt", &xpt, &x.mat("xpt"));
stats.mat("bmat", &bmat, &x.mat("bmat"));
stats.mat("zmat", &zmat, &x.mat("zmat"));
}
stats.report("rescue");
}
#[test]
fn rescue_at_the_evaluation_budget_returns_maxfun_reached_with_zeroed_h() {
let (n, npt) = (2, 5);
let mut kopt = 0;
let mut nf = 10;
let mut fval = vec![1.0; npt];
let (mut gopt, mut sl, mut su, mut xbase) =
(vec![0.0; n], vec![-1.0; n], vec![1.0; n], vec![0.0; n]);
let (mut hq, mut xpt) = (Mat::zeros(n, n), Mat::zeros(n, npt));
let mut pq = vec![0.0; npt];
let mut bmat = Mat::from_col_major(n, npt + n, vec![1.0; n * (npt + n)]);
let mut zmat = Mat::from_col_major(npt, npt - n - 1, vec![1.0; npt * (npt - n - 1)]);
let mut ws = RescueWs::new(n, npt);
let info = rescue(
&mut |_: &[f64]| 0.0,
10,
0.5,
f64::NEG_INFINITY,
&[-1.0; 2],
&[1.0; 2],
&mut kopt,
&mut nf,
&mut fval,
&mut gopt,
&mut hq,
&mut pq,
&mut sl,
&mut su,
&mut xbase,
&mut xpt,
&mut bmat,
&mut zmat,
&mut ws,
);
assert_eq!(info, crate::consts::MAXFUN_REACHED);
assert!(bmat.data().iter().all(|&v| v == 0.0));
assert!(zmat.data().iter().all(|&v| v == 0.0));
assert_eq!(nf, 10);
}
}