use crate::core::math::Scalar;
use crate::solver::powell::QuadraticModel;
use super::getact::ActiveSetQr;
fn col_dot<F: Scalar>(v: &[F], amat: &[F], j: usize, n: usize) -> F {
(0..n).fold(F::zero(), |acc, r| acc + v[r] * amat[r + j * n])
}
fn dot<F: Scalar>(a: &[F], b: &[F]) -> F {
a.iter().zip(b).fold(F::zero(), |acc, (x, y)| acc + *x * *y)
}
fn norm<F: Scalar>(v: &[F]) -> F {
dot(v, v).sqrt()
}
fn denominators<F: Scalar>(model: &QuadraticModel<F>, d: &[F]) -> Vec<F> {
let n = model.n();
let kopt = model.kopt();
let xopt = model.xpt_row(kopt).to_vec();
let xnew_disp: Vec<F> = (0..n).map(|i| xopt[i] + d[i]).collect();
let ctx = model.prepare_update(&xnew_disp);
(0..model.m())
.map(|k| model.update_params(k, &ctx).sigma)
.collect()
}
fn distsq_to<F: Scalar>(model: &QuadraticModel<F>, center: &[F]) -> Vec<F> {
let n = model.n();
(0..model.m())
.map(|k| {
let row = model.xpt_row(k);
(0..n).fold(F::zero(), |a, i| {
a + (row[i] - center[i]) * (row[i] - center[i])
})
})
.collect()
}
fn project_null<F: Scalar>(qfac: &[F], n: usize, nact: usize, g: &[F]) -> Vec<F> {
let mut p = vec![F::zero(); n];
for col in nact..n {
let coeff = (0..n).fold(F::zero(), |a, r| a + g[r] * qfac[r + col * n]);
for r in 0..n {
p[r] = p[r] + coeff * qfac[r + col * n];
}
}
p
}
pub(crate) fn setdrop_tr<F: Scalar>(
model: &QuadraticModel<F>,
ximproved: bool,
d: &[F],
delta: F,
rho: F,
) -> Option<usize> {
let n = model.n();
let kopt = model.kopt();
let zero = F::zero();
let one = F::one();
let tenth = F::from_f64(0.1).expect("0.1 representable");
let center: Vec<F> = if ximproved {
let xopt = model.xpt_row(kopt);
(0..n).map(|i| xopt[i] + d[i]).collect()
} else {
model.xpt_row(kopt).to_vec()
};
let distsq = distsq_to(model, ¢er);
let scale = (tenth * delta).max(rho);
let scale2 = scale * scale;
let den = denominators(model, d);
let mut score = vec![zero; model.m()];
for k in 0..model.m() {
let w = (distsq[k] / scale2).max(one);
let w3 = w * w * w; score[k] = w3 * den[k].abs();
}
if !ximproved {
score[kopt] = -one;
}
if score.iter().any(|&s| s > zero) {
let mut knew = 0;
let mut best = F::neg_infinity();
for k in 0..model.m() {
if !score[k].is_nan() && score[k] > best {
best = score[k];
knew = k;
}
}
Some(knew)
} else if ximproved {
let mut knew = 0;
let mut best = F::neg_infinity();
for k in 0..model.m() {
if !distsq[k].is_nan() && distsq[k] > best {
best = distsq[k];
knew = k;
}
}
Some(knew)
} else {
None
}
}
pub(crate) fn geostep<F: Scalar>(
model: &QuadraticModel<F>,
knew: usize,
delbar: F,
amat: &[F],
rescon: &[F],
warm: &ActiveSetQr<F>,
) -> (Vec<F>, bool) {
let n = model.n();
let m = rescon.len();
let kopt = model.kopt();
let zero = F::zero();
let one = F::one();
let half = F::from_f64(0.5).expect("0.5 representable");
let ten = F::from_f64(10.0).expect("10 representable");
let eps = F::epsilon();
let xopt = model.xpt_row(kopt).to_vec();
let (g0, pqlag) = model.lagrange_coeffs(knew);
let hxopt = model.lagrange_hessian_matvec(&pqlag, &xopt);
let glag: Vec<F> = (0..n).map(|i| g0[i] + hxopt[i]).collect();
let mut distsq = distsq_to(model, &xopt);
distsq[kopt] = one; let dderiv: Vec<F> = (0..model.m())
.map(|k| {
let row = model.xpt_row(k);
(0..n).fold(zero, |a, i| a + glag[i] * (row[i] - xopt[i]))
})
.collect();
let mut stplen: Vec<F> = (0..model.m()).map(|k| -delbar / distsq[k].sqrt()).collect();
let mut vlagabs: Vec<F> = (0..model.m())
.map(|k| (stplen[k] * (one - stplen[k]) * dderiv[k]).abs())
.collect();
if dderiv[knew] * (dderiv[knew] - one) < zero {
stplen[knew] = -stplen[knew];
}
vlagabs[knew] = (stplen[knew] * dderiv[knew]).abs()
+ stplen[knew] * stplen[knew] * (dderiv[knew] - one).abs();
vlagabs[kopt] = -one;
let mut kline = knew;
if (0..model.m()).any(|k| vlagabs[k] > vlagabs[knew] && !vlagabs[k].is_nan()) {
let mut best = F::neg_infinity();
for k in 0..model.m() {
if !vlagabs[k].is_nan() && vlagabs[k] > best {
best = vlagabs[k];
kline = k;
}
}
}
let row = model.xpt_row(kline);
let mut s: Vec<F> = (0..n).map(|i| stplen[kline] * (row[i] - xopt[i])).collect();
let mut denabs = denominators(model, &s)[knew].abs();
let gnorm = norm(&glag);
if gnorm > eps && gnorm.is_finite() {
let mut gstp: Vec<F> = (0..n).map(|i| (delbar / gnorm) * glag[i]).collect();
let hg = model.lagrange_hessian_matvec(&pqlag, &gstp);
if dot(&gstp, &hg) < zero {
for v in gstp.iter_mut() {
*v = -*v;
}
}
let den_knew = denominators(model, &gstp)[knew].abs();
if den_knew > denabs || denabs.is_nan() {
denabs = den_knew;
s = gstp;
}
}
let mut rstat = vec![1i8; m];
for j in 0..m {
if rescon[j].abs() >= delbar {
rstat[j] = -1;
}
}
for ic in 0..warm.nact {
rstat[warm.iact[ic]] = 0;
}
let cstrv_of = |step: &[F], pred: &dyn Fn(i8) -> bool| -> F {
let mut cv = zero;
for j in 0..m {
if pred(rstat[j]) {
let viol = col_dot(step, amat, j, n) - rescon[j];
if viol > cv {
cv = viol;
}
}
}
cv
};
let mut feasible = cstrv_of(&s, &|r| r >= 0) <= zero;
if warm.nact > 0 {
let pglag = project_null(&warm.qfac, n, warm.nact, &glag);
let pgnorm = norm(&pglag);
if pgnorm > eps && pgnorm.is_finite() {
let mut pgstp: Vec<F> = (0..n).map(|i| (delbar / pgnorm) * pglag[i]).collect();
let hpg = model.lagrange_hessian_matvec(&pqlag, &pgstp);
if dot(&pgstp, &hpg) < zero {
for v in pgstp.iter_mut() {
*v = -*v;
}
}
let cstrv = cstrv_of(&pgstp, &|r| r == 1);
let mut active_inf = zero;
for ic in 0..warm.nact {
let v = col_dot(&pgstp, amat, warm.iact[ic], n).abs();
if v > active_inf {
active_inf = v;
}
}
let cvtol = (eps * norm(&pgstp)).max(ten * active_inf);
let mut take = false;
if cstrv <= cvtol {
let tenth = F::from_f64(0.1).expect("0.1 representable");
take = denominators(model, &pgstp)[knew].abs() > tenth * denabs;
}
if take || denabs.is_nan() {
s = pgstp;
feasible = cstrv <= cvtol;
}
}
}
if s.iter().any(|v| v.is_nan()) {
let raw: Vec<F> = (0..n).map(|i| model.xpt_row(knew)[i] - xopt[i]).collect();
let scaling = delbar / norm(&raw);
let factor =
(F::from_f64(0.6).expect("0.6 representable") * scaling).max(half.min(scaling));
s = raw.iter().map(|&v| factor * v).collect();
feasible = cstrv_of(&s, &|r| r >= 0) <= zero;
}
(s, feasible)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn update_rescon<F: Scalar>(
ximproved: bool,
amat: &[F],
bvec: &[F],
delta: F,
dnorm: F,
xopt: &[F],
rescon: &mut [F],
n: usize,
) {
if !ximproved {
return;
}
let zero = F::zero();
let m = bvec.len();
for j in 0..m {
if rescon[j].abs() < dnorm + delta {
let ax = col_dot(xopt, amat, j, n);
rescon[j] = (bvec[j] - ax).max(zero);
} else {
rescon[j] = (-rescon[j].abs() + dnorm).min(-delta);
}
if rescon[j] >= delta {
rescon[j] = -rescon[j];
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn diag_quadratic(h: &'static [f64], c: &'static [f64]) -> impl Fn(&[f64]) -> f64 {
move |x: &[f64]| {
0.5 * (0..x.len())
.map(|i| h[i] * (x[i] - c[i]).powi(2))
.sum::<f64>()
}
}
#[test]
fn rescon_encoding_near_and_far() {
let amat = [1.0f64, 1.0];
let bvec = [0.5f64, 10.0];
let xopt = [0.0f64];
let mut rescon = [0.4f64, 9.0];
update_rescon(true, &amat, &bvec, 1.0, 0.3, &xopt, &mut rescon, 1);
assert!((rescon[0] - 0.5).abs() < 1e-12);
assert!((rescon[1] - (-8.7)).abs() < 1e-12);
}
#[test]
fn rescon_negates_when_ge_delta() {
let amat = [1.0f64];
let bvec = [2.0f64];
let xopt = [0.0f64];
let mut rescon = [0.5f64];
update_rescon(true, &amat, &bvec, 1.0, 0.3, &xopt, &mut rescon, 1);
assert!((rescon[0] - (-2.0)).abs() < 1e-12);
}
#[test]
fn rescon_no_update_when_not_improved() {
let amat = [1.0f64];
let bvec = [2.0f64];
let xopt = [0.0f64];
let mut rescon = [0.4f64];
update_rescon(false, &amat, &bvec, 1.0, 0.3, &xopt, &mut rescon, 1);
assert_eq!(rescon[0], 0.4);
}
#[test]
fn setdrop_returns_point_for_improving_step() {
let h = &[2.0, 4.0];
let c = &[0.4, -0.3];
let model = QuadraticModel::initialize(vec![0.0, 0.0], 0.3, 5, &diag_quadratic(h, c));
let d = [0.1, 0.05];
let knew = setdrop_tr(&model, true, &d, 0.3, 0.3);
assert!(knew.is_some());
}
#[test]
fn geostep_unconstrained_has_radius_and_denominator() {
let h = &[2.0, 4.0];
let c = &[0.4, -0.3];
let model = QuadraticModel::initialize(vec![0.0, 0.0], 0.3, 5, &diag_quadratic(h, c));
let n = 2;
let knew = (0..model.m()).find(|&k| k != model.kopt()).unwrap();
let warm = ActiveSetQr::<f64>::new(n, 0);
let delbar = 0.2;
let (s, feasible) = geostep(&model, knew, delbar, &[], &[], &warm);
let sn = norm(&s);
assert!(sn > 0.5 * delbar && sn < 2.0 * delbar, "‖s‖ = {sn}");
assert!(feasible, "unconstrained step is always feasible");
let den = denominators(&model, &s)[knew];
assert!(den.abs() > 0.0);
}
}