use crate::consts::{
DAMAGING_ROUNDING, INFO_DFT, MAXTR_REACHED, NAN_INF_MODEL, REALMAX, SMALL_TR_RADIUS,
};
use crate::geometry::{GeostepWs, geostep, setdrop_tr};
use crate::initialize::{InitWs, inith, initq, initxf};
use crate::linalg::{inprod, matprod12_into, matprod21_into, matprod22_into, norm, outprod_into};
use crate::mat::Mat;
use crate::math;
use crate::powalg::{CalWs, calvlag_and_den_into, hess_mul_into, quadinc};
use crate::rescue::{RescueWs, rescue};
use crate::trustregion::{TrsboxWs, trrad, trsbox};
use crate::update::{UpdateWs, tryqalt, updateh, updateq, updatexf};
use crate::util::{checkexit, evaluate, xinbd_into};
const TRTOL: f64 = 1.0e-2;
#[derive(Debug, Clone)]
struct ShiftbaseWs {
xopt: Vec<f64>, xptxav: Mat, sxpt: Vec<f64>, ymat: Mat, bmat_npt: Mat, ymat_t: Mat, bymat: Mat, yzmat: Mat, yzmat_c: Mat, yzmat_c_t: Mat, zmat_t: Mat, yzyzmat: Mat, yz_zt: Mat, v: Vec<f64>, vxopt: Mat, }
impl ShiftbaseWs {
fn new(n: usize, npt: usize) -> Self {
Self {
xopt: vec![0.0; n],
xptxav: Mat::zeros(n, npt),
sxpt: vec![0.0; npt],
ymat: Mat::zeros(n, npt),
bmat_npt: Mat::zeros(n, npt),
ymat_t: Mat::zeros(npt, n),
bymat: Mat::zeros(n, n),
yzmat: Mat::zeros(n, npt - n - 1),
yzmat_c: Mat::zeros(n, npt - n - 1),
yzmat_c_t: Mat::zeros(npt - n - 1, n),
zmat_t: Mat::zeros(npt - n - 1, npt),
yzyzmat: Mat::zeros(n, n),
yz_zt: Mat::zeros(n, npt),
v: vec![0.0; n],
vxopt: Mat::zeros(n, n),
}
}
}
#[derive(Debug, Clone)]
struct ErrbdWs {
xnew: Vec<f64>, hm: Vec<f64>, gnew: Vec<f64>, bfirst: Vec<f64>, v: Vec<f64>, bsecond: Vec<f64>, dxpt: Vec<f64>, }
impl ErrbdWs {
fn new(n: usize, npt: usize) -> Self {
Self {
xnew: vec![0.0; n],
hm: vec![0.0; n],
gnew: vec![0.0; n],
bfirst: vec![0.0; n],
v: vec![0.0; n],
bsecond: vec![0.0; n],
dxpt: vec![0.0; npt],
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct BobyqbWs {
pub(crate) xl: Vec<f64>, pub(crate) xu: Vec<f64>, bmat: Mat, zmat: Mat, xpt: Mat, hq: Mat, fval: Vec<f64>, pq: Vec<f64>, den: Vec<f64>, distsq: Vec<f64>, gopt: Vec<f64>, sl: Vec<f64>, su: Vec<f64>, xbase: Vec<f64>, d: Vec<f64>, xdrop: Vec<f64>, xosav: Vec<f64>, vlag: Vec<f64>, ij: Vec<(usize, usize)>, dxpt_q: Vec<f64>, pqdxpt: Vec<f64>, hqd: Vec<f64>, xmod: Vec<f64>, xnew: Vec<f64>, xnew_clamped: Vec<f64>, fval_shift: Vec<f64>, xopt_copy: Vec<f64>, cal: CalWs,
shiftbase: ShiftbaseWs,
errbd: ErrbdWs,
}
#[derive(Debug, Clone)]
pub(crate) struct SolverWs {
pub(crate) bobyqb: BobyqbWs,
trsbox: TrsboxWs,
geostep: GeostepWs,
rescue: RescueWs,
update: UpdateWs,
init: InitWs,
}
impl SolverWs {
pub(crate) fn new(n: usize, npt: usize) -> Self {
Self {
bobyqb: BobyqbWs {
xl: vec![0.0; n],
xu: vec![0.0; n],
bmat: Mat::zeros(n, npt + n),
zmat: Mat::zeros(npt, npt - n - 1),
xpt: Mat::zeros(n, npt),
hq: Mat::zeros(n, n),
fval: vec![0.0; npt],
pq: vec![0.0; npt],
den: vec![0.0; npt],
distsq: vec![0.0; npt],
gopt: vec![0.0; n],
sl: vec![0.0; n],
su: vec![0.0; n],
xbase: vec![0.0; n],
d: vec![0.0; n],
xdrop: vec![0.0; n],
xosav: vec![0.0; n],
vlag: vec![0.0; npt + n],
ij: Vec::with_capacity(npt.saturating_sub(2 * n + 1)),
dxpt_q: vec![0.0; npt],
pqdxpt: vec![0.0; npt],
hqd: vec![0.0; n],
xmod: vec![0.0; n],
xnew: vec![0.0; n],
xnew_clamped: vec![0.0; n],
fval_shift: vec![0.0; npt],
xopt_copy: vec![0.0; n],
cal: CalWs::new(n, npt),
shiftbase: ShiftbaseWs::new(n, npt),
errbd: ErrbdWs::new(n, npt),
},
trsbox: TrsboxWs::new(n, npt),
geostep: GeostepWs::new(n, npt),
rescue: RescueWs::new(n, npt),
update: UpdateWs::new(n, npt),
init: InitWs::new(n, npt),
}
}
}
fn redrat(ared: f64, pred: f64, rshrink: f64) -> f64 {
if ared.is_nan() {
-REALMAX
} else if pred.is_nan() || pred <= 0.0 {
if ared > 0.0 { 0.5 * rshrink } else { -REALMAX }
} else if pred.is_infinite() && pred.is_sign_positive() && ared.is_infinite() {
if ared.is_sign_positive() {
1.0
} else {
-REALMAX
}
} else {
ared / pred
}
}
fn redrho(rho_in: f64, rhoend: f64) -> f64 {
let rho_ratio = rho_in / rhoend;
if rho_ratio > 250.0 {
0.1 * rho_in
} else if rho_ratio <= 16.0 {
rhoend
} else {
math::sqrt(rho_ratio) * rhoend
}
}
#[expect(clippy::too_many_arguments)] #[expect(clippy::too_many_lines)] fn shiftbase(
kopt: usize,
xbase: &mut [f64],
xpt: &mut Mat,
zmat: &Mat,
bmat: &mut Mat,
pq: &[f64],
hq: &mut Mat,
ws: &mut ShiftbaseWs,
) {
let n = xpt.nrows();
let npt = xpt.ncols();
let ShiftbaseWs {
xopt,
xptxav,
sxpt,
ymat,
bmat_npt,
ymat_t,
bymat,
yzmat,
yzmat_c,
yzmat_c_t,
zmat_t,
yzyzmat,
yz_zt,
v,
vxopt,
} = ws;
xopt.copy_from_slice(xpt.col(kopt));
let xoptsq = inprod(xopt, xopt);
xptxav.fill(0.0);
for k in 0..npt {
for i in 0..n {
xptxav[[i, k]] = xpt[[i, k]] - 0.5 * xopt[i];
}
}
matprod12_into(xopt, xpt, sxpt);
for s in sxpt.iter_mut() {
*s -= 0.5 * xoptsq;
}
let qxoptq = 0.25 * xoptsq;
ymat.fill(0.0);
for k in 0..npt {
for i in 0..n {
ymat[[i, k]] = sxpt[k] * xptxav[[i, k]] + qxoptq * xopt[i];
}
}
bmat_npt.fill(0.0);
for k in 0..npt {
for i in 0..n {
bmat_npt[[i, k]] = bmat[[i, k]];
}
}
ymat_t.fill(0.0);
for k in 0..npt {
for i in 0..n {
ymat_t[[k, i]] = ymat[[i, k]];
}
}
matprod22_into(bmat_npt, ymat_t, bymat);
for i in 0..n {
for j in 0..n {
bmat[[i, npt + j]] += bymat[[i, j]] + bymat[[j, i]];
}
}
matprod22_into(ymat, zmat, yzmat);
yzmat_c.copy_from(yzmat);
yzmat_c_t.fill(0.0);
for j in 0..yzmat_c.ncols() {
for i in 0..n {
yzmat_c_t[[j, i]] = yzmat_c[[i, j]];
}
}
matprod22_into(yzmat, yzmat_c_t, yzyzmat); for i in 0..n {
for j in 0..n {
bmat[[i, npt + j]] += yzyzmat[[i, j]];
}
}
zmat_t.fill(0.0);
for j in 0..zmat.ncols() {
for k in 0..npt {
zmat_t[[j, k]] = zmat[[k, j]];
}
}
matprod22_into(yzmat_c, zmat_t, yz_zt); for k in 0..npt {
for i in 0..n {
bmat[[i, k]] += yz_zt[[i, k]];
}
}
matprod21_into(xpt, pq, v);
let pq_sum: f64 = pq.iter().sum();
for i in 0..n {
v[i] -= (0.5 * pq_sum) * xopt[i];
}
outprod_into(v, xopt, vxopt); #[expect(clippy::assign_op_pattern)] for i in 0..n {
for j in 0..n {
hq[[i, j]] = (vxopt[[i, j]] + vxopt[[j, i]]) + hq[[i, j]];
}
}
for i in 0..n {
xbase[i] += xopt[i]; }
for k in 0..npt {
for i in 0..n {
xpt[[i, k]] -= xopt[i]; }
}
xpt.col_mut(kopt).fill(0.0); }
#[expect(clippy::too_many_arguments)] #[expect(clippy::similar_names)] fn errbd(
crvmin: f64,
d: &[f64],
gopt: &[f64],
hq: &Mat,
moderr_rec: &[f64],
pq: &[f64],
rho: f64,
sl: &[f64],
su: &[f64],
xopt: &[f64],
xpt: &Mat,
ws: &mut ErrbdWs,
) -> f64 {
let n = xpt.nrows();
let npt = xpt.ncols();
let ErrbdWs {
xnew,
hm,
gnew,
bfirst,
v,
bsecond,
dxpt,
} = ws;
xnew.fill(0.0);
for i in 0..n {
xnew[i] = xopt[i] + d[i];
}
hess_mul_into(d, xpt, pq, Some(hq), dxpt, hm);
gnew.fill(0.0);
for i in 0..n {
gnew[i] = gopt[i] + hm[i];
}
let max_abs_moderr = moderr_rec.iter().fold(0.0_f64, |acc, &v| acc.max(v.abs()));
bfirst.fill(max_abs_moderr);
for i in 0..n {
if xnew[i] <= sl[i] {
bfirst[i] = gnew[i] * rho;
}
}
for i in 0..n {
if xnew[i] >= su[i] {
bfirst[i] = -gnew[i] * rho;
}
}
let rhosq = rho * rho;
v.fill(0.0);
for k in 0..npt {
for i in 0..n {
v[i] += (xpt[[i, k]] * xpt[[i, k]]) * pq[k];
}
}
bsecond.fill(0.0);
for i in 0..n {
bsecond[i] = 0.5 * (hq[[i, i]] + v[i]) * rhosq;
}
let mut ebound = f64::INFINITY;
for i in 0..n {
let val = bfirst[i].max(bfirst[i] + bsecond[i]);
if val < ebound {
ebound = val;
}
}
if crvmin > 0.0 {
ebound = ebound.min(0.125 * crvmin * rhosq);
}
ebound
}
#[expect(clippy::too_many_arguments)] #[expect(clippy::too_many_lines)] #[expect(clippy::neg_cmp_op_on_partial_ord)] #[expect(clippy::needless_range_loop)] pub(crate) fn bobyqb<F: FnMut(&[f64]) -> f64>(
calfun: &mut F,
maxfun: usize,
npt: usize,
eta1: f64,
eta2: f64,
ftarget: f64,
gamma1: f64,
gamma2: f64,
rhobeg: f64,
rhoend: f64,
x: &mut [f64],
ws: &mut SolverWs,
) -> (f64, usize, i32) {
let n = x.len();
let SolverWs {
bobyqb: bws,
trsbox: tws,
geostep: gws,
rescue: rws,
update: uws,
init: iws,
} = ws;
let BobyqbWs {
xl,
xu,
bmat,
zmat,
xpt,
hq,
fval,
pq,
den,
distsq,
gopt,
sl,
su,
xbase,
d,
xdrop,
xosav,
vlag,
ij,
dxpt_q,
pqdxpt,
hqd,
xmod,
xnew,
xnew_clamped,
fval_shift,
xopt_copy,
cal,
shiftbase: sbws,
errbd: ebws,
} = bws;
let (xl, xu): (&[f64], &[f64]) = (xl, xu);
bmat.fill(0.0);
zmat.fill(0.0);
xpt.fill(0.0);
hq.fill(0.0);
fval.fill(0.0);
pq.fill(0.0);
den.fill(0.0);
distsq.fill(0.0);
gopt.fill(0.0);
sl.fill(0.0);
su.fill(0.0);
xbase.fill(0.0);
d.fill(0.0);
xdrop.fill(0.0);
xosav.fill(0.0);
vlag.fill(0.0);
ij.clear();
dxpt_q.fill(0.0);
pqdxpt.fill(0.0);
hqd.fill(0.0);
xmod.fill(0.0);
let mut dnorm_rec = [REALMAX; 2];
let mut moderr_rec = [REALMAX; 2];
let (mut kopt, mut nf, mut subinfo) = initxf(
calfun, maxfun, ftarget, rhobeg, xl, xu, x, ij, fval, sl, su, xbase, xpt, iws,
);
xinbd_into(xbase, xpt.col(kopt), xl, xu, sl, su, x);
let mut f = fval[kopt];
if subinfo == INFO_DFT {
let _ = inith(ij, xpt, bmat, zmat, iws);
let _ = initq(ij, fval, xpt, gopt, hq, pq, iws);
if !(gopt.iter().all(|v| v.is_finite())
&& hq.data().iter().all(|v| v.is_finite())
&& pq.iter().all(|v| v.is_finite()))
{
subinfo = NAN_INF_MODEL;
}
}
if subinfo != INFO_DFT {
return (f, nf, subinfo);
}
let mut rho = rhobeg;
let mut delta = rho;
let mut ebound = 0.0;
let mut rescued = false;
let mut shortd = false;
let mut ratio = -1.0;
let mut knew_tr: Option<usize> = None;
let mut itest: i32 = 0;
let gamma3 = 1.0_f64.max((0.75 * gamma2).min(1.5));
let maxtr = maxfun.saturating_mul(2);
let mut info = MAXTR_REACHED;
let mut dnorm = 0.0;
for _tr in 1..=maxtr {
let crvmin = trsbox(
delta,
gopt,
hq,
pq,
sl,
su,
TRTOL,
xpt.col(kopt),
xpt,
d,
tws,
);
dnorm = delta.min(norm(d));
shortd = dnorm <= 0.5 * rho;
let qred = -quadinc(d, xpt, gopt, pq, hq, dxpt_q, pqdxpt, hqd);
let trfail = !(qred > f64::from(1.0e-6_f32) * (rho * rho));
if shortd || trfail {
#[expect(clippy::assign_op_pattern)]
{
delta = 0.1 * delta;
}
if delta <= gamma3 * rho {
delta = rho;
}
ebound = errbd(
crvmin,
d,
gopt,
hq,
&moderr_rec,
pq,
rho,
sl,
su,
xpt.col(kopt),
xpt,
ebws,
);
} else {
for i in 0..n {
xnew[i] = xpt[[i, kopt]] + d[i];
}
xinbd_into(xbase, xnew, xl, xu, sl, su, x);
f = evaluate(calfun, x, xmod);
nf += 1;
rescued = false;
subinfo = checkexit(maxfun, nf, f, ftarget, x);
if subinfo != INFO_DFT {
info = subinfo;
break;
}
dnorm_rec = [dnorm_rec[1], dnorm];
let mut moderr = f - fval[kopt] + qred;
moderr_rec = [moderr_rec[1], moderr];
ratio = redrat(fval[kopt] - f, qred, eta1);
delta = trrad(delta, dnorm, eta1, eta2, gamma1, gamma2, ratio);
if delta <= gamma3 * rho {
delta = rho;
}
let mut ximproved = f < fval[kopt];
let ref_beta = calvlag_and_den_into(kopt, bmat, d, xpt, zmat, cal, vlag, den);
let mut model_unchanged = true;
let vlag_abs_sum: f64 = vlag.iter().map(|v| math::abs(*v)).sum();
let mut vmax = 0.0_f64;
for k in 0..npt {
let sq = vlag[k] * vlag[k];
if k == 0 || sq > vmax {
vmax = sq;
}
}
let to_rescue =
ximproved && !(vlag_abs_sum.is_finite() && den.iter().any(|&v| v > vmax));
if to_rescue {
if rescued {
info = DAMAGING_ROUNDING; break;
}
subinfo = rescue(
calfun, maxfun, delta, ftarget, xl, xu, &mut kopt, &mut nf, fval, gopt, hq, pq,
sl, su, xbase, xpt, bmat, zmat, rws,
);
if subinfo != INFO_DFT {
info = subinfo;
break;
}
rescued = true;
model_unchanged = false; dnorm_rec = [REALMAX; 2];
moderr_rec = [REALMAX; 2];
for i in 0..n {
d[i] = sl[i].max(su[i].min(d[i])) - xpt[[i, kopt]];
}
moderr = f - fval[kopt] - quadinc(d, xpt, gopt, pq, hq, dxpt_q, pqdxpt, hqd);
ximproved = f < fval[kopt];
}
knew_tr = setdrop_tr(
kopt,
ximproved,
bmat,
d,
delta,
rho,
xpt,
zmat,
gws,
model_unchanged.then_some(den.as_slice()),
);
if let Some(knew) = knew_tr {
xdrop.copy_from_slice(xpt.col(knew));
xosav.copy_from_slice(xpt.col(kopt));
let _ = updateh(
Some(knew),
kopt,
d,
xpt,
bmat,
zmat,
uws,
model_unchanged.then_some((vlag.as_slice(), ref_beta)),
);
for i in 0..n {
xnew_clamped[i] = sl[i].max(su[i].min(xosav[i] + d[i]));
}
updatexf(Some(knew), ximproved, f, xnew_clamped, &mut kopt, fval, xpt);
updateq(
Some(knew),
ximproved,
bmat,
d,
moderr,
xdrop,
xosav,
xpt,
zmat,
gopt,
hq,
pq,
uws,
);
for k in 0..npt {
fval_shift[k] = fval[k] - fval[kopt];
}
xopt_copy.copy_from_slice(xpt.col(kopt));
tryqalt(
bmat, fval_shift, ratio, sl, su, xopt_copy, xpt, zmat, &mut itest, gopt, hq,
pq, uws,
);
if !(gopt.iter().all(|v| v.is_finite())
&& hq.data().iter().all(|v| v.is_finite())
&& pq.iter().all(|v| v.is_finite()))
{
info = NAN_INF_MODEL;
break;
}
}
}
let accurate_mod = moderr_rec.iter().all(|v| math::abs(*v) <= ebound)
&& dnorm_rec.iter().all(|&v| v <= rho);
for k in 0..npt {
let mut sq = 0.0;
for i in 0..n {
let diff = xpt[[i, k]] - xpt[[i, kopt]];
sq += diff * diff;
}
distsq[k] = sq;
}
let close_itpset = distsq
.iter()
.all(|&v| v <= (delta * delta).max((10.0 * rho) * (10.0 * rho)));
let adequate_geo = (shortd && accurate_mod) || close_itpset;
let small_trrad = delta.max(dnorm) <= rho;
let bad_trstep = shortd || trfail || ratio <= eta1 || knew_tr.is_none();
let improve_geo = bad_trstep && !adequate_geo;
let bad_trstep = shortd || trfail || ratio <= 0.0 || knew_tr.is_none();
let reduce_rho = bad_trstep && adequate_geo && small_trrad;
if improve_geo {
let mut knew_geo = 0usize;
for k in 0..npt {
if k == 0 || distsq[k] > distsq[knew_geo] {
knew_geo = k;
}
}
let mut max_distsq = 0.0_f64;
for k in 0..npt {
if k == 0 || distsq[k] > max_distsq {
max_distsq = distsq[k];
}
}
let delbar = (0.1 * math::sqrt(max_distsq)).min(delta).max(rho);
geostep(knew_geo, kopt, bmat, delbar, sl, su, xpt, zmat, d, gws);
calvlag_and_den_into(kopt, bmat, d, xpt, zmat, cal, vlag, den);
let vlag_abs_sum: f64 = vlag.iter().map(|v| math::abs(*v)).sum();
let to_rescue = !(vlag_abs_sum.is_finite()
&& den[knew_geo] > 0.5 * (vlag[knew_geo] * vlag[knew_geo]));
if to_rescue {
if rescued {
info = DAMAGING_ROUNDING; break;
}
subinfo = rescue(
calfun, maxfun, delta, ftarget, xl, xu, &mut kopt, &mut nf, fval, gopt, hq, pq,
sl, su, xbase, xpt, bmat, zmat, rws,
);
if subinfo != INFO_DFT {
info = subinfo;
break;
}
rescued = true;
dnorm_rec = [REALMAX; 2];
moderr_rec = [REALMAX; 2];
} else {
for i in 0..n {
xnew[i] = xpt[[i, kopt]] + d[i];
}
xinbd_into(xbase, xnew, xl, xu, sl, su, x);
f = evaluate(calfun, x, xmod);
nf += 1;
rescued = false;
subinfo = checkexit(maxfun, nf, f, ftarget, x);
if subinfo != INFO_DFT {
info = subinfo;
break;
}
dnorm = delbar.min(norm(d));
dnorm_rec = [dnorm_rec[1], dnorm];
let moderr = f - fval[kopt] - quadinc(d, xpt, gopt, pq, hq, dxpt_q, pqdxpt, hqd);
moderr_rec = [moderr_rec[1], moderr];
let ximproved = f < fval[kopt];
xdrop.copy_from_slice(xpt.col(knew_geo));
xosav.copy_from_slice(xpt.col(kopt));
let _ = updateh(Some(knew_geo), kopt, d, xpt, bmat, zmat, uws, None);
for i in 0..n {
xnew_clamped[i] = sl[i].max(su[i].min(xosav[i] + d[i]));
}
updatexf(
Some(knew_geo),
ximproved,
f,
xnew_clamped,
&mut kopt,
fval,
xpt,
);
updateq(
Some(knew_geo),
ximproved,
bmat,
d,
moderr,
xdrop,
xosav,
xpt,
zmat,
gopt,
hq,
pq,
uws,
);
if !(gopt.iter().all(|v| v.is_finite())
&& hq.data().iter().all(|v| v.is_finite())
&& pq.iter().all(|v| v.is_finite()))
{
info = NAN_INF_MODEL;
break;
}
}
}
if reduce_rho {
if rho <= rhoend {
info = SMALL_TR_RADIUS;
break;
}
delta = (0.5 * rho).max(redrho(rho, rhoend));
rho = redrho(rho, rhoend);
dnorm_rec = [REALMAX; 2];
moderr_rec = [REALMAX; 2];
}
let mut xopt_sq = 0.0_f64;
for i in 0..n {
xopt_sq += xpt[[i, kopt]] * xpt[[i, kopt]];
}
if xopt_sq >= 1.0e3 * (delta * delta) {
xopt_copy.copy_from_slice(xpt.col(kopt));
for i in 0..n {
sl[i] = (sl[i] - xopt_copy[i]).min(0.0);
su[i] = (su[i] - xopt_copy[i]).max(0.0);
}
shiftbase(kopt, xbase, xpt, zmat, bmat, pq, hq, sbws);
for i in 0..n {
xbase[i] = xl[i].max(xu[i].min(xbase[i]));
}
}
}
if info == SMALL_TR_RADIUS && shortd && dnorm > 0.1 * rhoend && nf < maxfun {
for i in 0..n {
xnew[i] = xpt[[i, kopt]] + d[i];
}
xinbd_into(xbase, xnew, xl, xu, sl, su, x);
f = evaluate(calfun, x, xmod);
nf += 1;
}
if fval[kopt] < f || f.is_nan() {
xinbd_into(xbase, xpt.col(kopt), xl, xu, sl, su, x);
f = fval[kopt];
}
(f, nf, info)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mat::Mat;
#[test]
fn bobyqb_converges_on_the_sphere_to_small_tr_radius() {
let mut sphere = |x: &[f64]| x.iter().map(|v| v * v).sum::<f64>();
let mut x = vec![1.0, 2.0];
let mut ws = SolverWs::new(2, 5);
ws.bobyqb.xl.copy_from_slice(&[-5.0, -5.0]);
ws.bobyqb.xu.copy_from_slice(&[5.0, 5.0]);
let (f, nf, info) = bobyqb(
&mut sphere,
500,
5,
0.1,
0.7,
f64::NEG_INFINITY,
0.5,
2.0,
0.5,
1e-6,
&mut x,
&mut ws,
);
assert_eq!(info, SMALL_TR_RADIUS);
assert_eq!(nf, 22);
assert!(f < 1e-20, "f = {f:e}");
assert!(x.iter().all(|v| v.abs() < 1e-10));
}
#[test]
fn errbd_takes_the_interior_bound_and_crvmin_arms() {
let (n, npt) = (2, 5);
let xpt = Mat::zeros(n, npt);
let (hq, pq) = (Mat::zeros(n, n), vec![0.0; npt]);
let gopt = vec![1.0, -2.0];
let d = vec![0.1, 0.1];
let moderr_rec = [0.25, -0.5];
let (sl, su) = (vec![-1.0; n], vec![1.0; n]);
let mut ws = ErrbdWs::new(n, npt);
let e = errbd(
0.0,
&d,
&gopt,
&hq,
&moderr_rec,
&pq,
0.5,
&sl,
&su,
&[0.0; 2],
&xpt,
&mut ws,
);
assert_eq!(e, 0.5);
let e = errbd(
2.0,
&d,
&gopt,
&hq,
&moderr_rec,
&pq,
0.5,
&sl,
&su,
&[0.0; 2],
&xpt,
&mut ws,
);
assert_eq!(e, 0.0625);
let su_active = vec![0.1, 1.0];
let e = errbd(
0.0,
&d,
&gopt,
&hq,
&moderr_rec,
&pq,
0.5,
&sl,
&su_active,
&[0.0; 2],
&xpt,
&mut ws,
);
assert_eq!(e, -0.5);
}
#[test]
fn errbd_pins_the_bsecond_and_active_bound_paths() {
let xpt1 = Mat::from_col_major(1, 1, vec![3.0]);
let mut hq1 = Mat::zeros(1, 1);
hq1[[0, 0]] = 1.0;
let mut ws1 = ErrbdWs::new(1, 1);
let e = errbd(
0.0,
&[0.0],
&[0.0],
&hq1,
&[0.0, 0.0],
&[2.0],
2.0,
&[-10.0],
&[10.0],
&[0.0],
&xpt1,
&mut ws1,
);
assert_eq!(e, 38.0);
let mut hq2 = Mat::zeros(1, 1);
hq2[[0, 0]] = 2.0;
let xpt2 = Mat::zeros(1, 1);
let mut ws2 = ErrbdWs::new(1, 1);
let e = errbd(
0.0,
&[0.5],
&[1.0],
&hq2,
&[0.0, 0.0],
&[0.0],
0.5,
&[-10.0],
&[0.25],
&[0.0],
&xpt2,
&mut ws2,
);
assert_eq!(e, -0.75);
let mut hq3 = Mat::zeros(1, 1);
hq3[[0, 0]] = 2.0;
let xpt3 = Mat::zeros(1, 1);
let mut ws3 = ErrbdWs::new(1, 1);
let e = errbd(
0.0,
&[-0.5],
&[3.0],
&hq3,
&[0.0, 0.0],
&[0.0],
0.5,
&[-0.25],
&[10.0],
&[0.0],
&xpt3,
&mut ws3,
);
assert_eq!(e, 1.25);
}
#[test]
fn redrat_handles_nan_inf_and_the_plain_quotient() {
assert_eq!(redrat(f64::NAN, 1.0, 0.1), -REALMAX); assert_eq!(redrat(1.0, f64::NAN, 0.1), 0.05); assert_eq!(redrat(1.0, -2.0, 0.1), 0.05); assert_eq!(redrat(-1.0, f64::NAN, 0.1), -REALMAX); assert_eq!(redrat(f64::INFINITY, f64::INFINITY, 0.1), 1.0); assert_eq!(redrat(f64::NEG_INFINITY, f64::INFINITY, 0.1), -REALMAX); assert_eq!(redrat(1.0, 2.0, 0.1), 0.5); assert_eq!(redrat(0.0, -1.0, 0.1), -REALMAX);
assert_eq!(redrat(0.0, f64::NAN, 0.1), -REALMAX);
assert_eq!(redrat(1.0, f64::INFINITY, 0.1), 0.0); assert_eq!(redrat(f64::INFINITY, 2.0, 0.1), f64::INFINITY); assert_eq!(redrat(f64::NEG_INFINITY, 2.0, 0.1), f64::NEG_INFINITY); }
#[test]
fn redrho_takes_the_tenth_floor_and_sqrt_branches() {
assert_eq!(redrho(1.0, 1e-6), 0.1);
assert_eq!(redrho(1e-5, 1e-6), 1e-6);
assert_eq!(redrho(2e-5, 1e-6), 4.472_135_954_999_579e-6);
assert_eq!(redrho(250.0, 1.0), 15.811_388_300_841_896);
}
fn full_hessian(hq: &Mat, pq: &[f64], xpt: &Mat) -> Mat {
let n = xpt.nrows();
let mut h = Mat::zeros(n, n);
for i in 0..n {
for j in 0..n {
h[[i, j]] = hq[[i, j]];
for k in 0..xpt.ncols() {
h[[i, j]] += pq[k] * xpt[[i, k]] * xpt[[j, k]];
}
}
}
h
}
#[test]
#[expect(clippy::similar_names)] fn shiftbase_zeroes_the_kopt_column_moves_xbase_and_preserves_the_model_hessian() {
let (n, npt, kopt) = (2, 6, 3);
let xpt = Mat::from_col_major(
n,
npt,
vec![
0.0, 0.0, 0.5, 0.1, -0.2, 0.5, 0.3, -0.4, -0.5, 0.0, 0.2, 0.25,
],
);
let pq = vec![0.4, -0.3, 0.2, 0.1, -0.5, 0.6];
let mut hq = Mat::zeros(n, n);
hq[[0, 0]] = 1.0;
hq[[1, 1]] = 2.0;
hq[[0, 1]] = 0.5;
hq[[1, 0]] = 0.5;
let zmat = Mat::from_col_major(npt, npt - n - 1, (1..=18u8).map(f64::from).collect());
let mut bmat = Mat::from_col_major(n, npt + n, (1..=16u8).map(f64::from).collect());
bmat[[1, npt]] = bmat[[0, npt + 1]];
let mut xbase = vec![10.0, 20.0];
let xopt = xpt.col(kopt).to_vec();
let h_before = full_hessian(&hq, &pq, &xpt);
let mut xpt2 = xpt.clone();
let mut ws = ShiftbaseWs::new(n, npt);
shiftbase(
kopt, &mut xbase, &mut xpt2, &zmat, &mut bmat, &pq, &mut hq, &mut ws,
);
assert!(xpt2.col(kopt).iter().all(|&v| v == 0.0)); assert_eq!(xbase, vec![10.0 + xopt[0], 20.0 + xopt[1]]); for j in (0..npt).filter(|&j| j != kopt) {
for (i, &x) in xopt.iter().enumerate() {
assert_eq!(xpt2[[i, j]], xpt[[i, j]] - x); }
}
let h_after = full_hessian(&hq, &pq, &xpt2);
for i in 0..n {
for j in 0..n {
assert!(
(h_after[[i, j]] - h_before[[i, j]]).abs() <= 1e-12,
"model Hessian not preserved at [{i},{j}]"
);
}
}
for i in 0..n {
for j in 0..n {
assert_eq!(bmat[[i, npt + j]], bmat[[j, npt + i]]);
assert_eq!(hq[[i, j]], hq[[j, i]]);
}
}
}
}