use crate::core::math::Scalar;
use super::linalg::{col, dot, eye, hypotenuse, isminor, planerot, row_times_mat};
pub(crate) fn trstlp<F: Scalar>(a: &[F], n: usize, m: usize, b: &[F], delta: F, g: &[F]) -> Vec<F> {
let mcon = m + 1;
let mut a_aug = vec![F::zero(); n * mcon];
a_aug[..n * m].copy_from_slice(&a[..n * m]);
a_aug[n * m..n * mcon].copy_from_slice(&g[..n]);
let mut b_aug = vec![F::zero(); mcon];
b_aug[..m].copy_from_slice(&b[..m]);
let big = F::from_f64(1.0e12).unwrap();
let realmin = F::min_positive_value();
for i in 0..mcon {
let mx = (0..n)
.map(|r| a_aug[r + i * n].abs())
.fold(F::zero(), F::max);
if mx > big {
let scal = ((F::one() + F::one()) * realmin).max(F::one() / mx);
for r in 0..n {
a_aug[r + i * n] = a_aug[r + i * n] * scal;
}
b_aug[i] = b_aug[i] * scal;
}
}
let mut iact = vec![0usize; mcon];
let mut vmultc = vec![F::zero(); mcon];
let mut z = eye::<F>(n);
let mut d = vec![F::zero(); n];
let mut nact = 0usize;
trstlp_sub(
1,
&a_aug,
n,
m,
&b_aug,
delta,
&mut d,
&mut iact,
&mut vmultc,
&mut z,
&mut nact,
);
trstlp_sub(
2,
&a_aug,
n,
mcon,
&b_aug,
delta,
&mut d,
&mut iact,
&mut vmultc,
&mut z,
&mut nact,
);
d
}
fn zdota_active<F: Scalar>(a: &[F], n: usize, iact: &[usize], nact: usize, z: &[F]) -> Vec<F> {
(0..nact)
.map(|k| dot(col(z, n, k), col(a, n, iact[k])))
.collect()
}
fn active_cols<F: Scalar>(a: &[F], n: usize, iact: &[usize], nact: usize) -> Vec<F> {
let mut out = vec![F::zero(); n * nact];
for k in 0..nact {
out[k * n..(k + 1) * n].copy_from_slice(col(a, n, iact[k]));
}
out
}
fn qradd<F: Scalar>(c: &[F], z: &mut [F], zdota: &mut [F], nact: &mut usize, n: usize) {
let mut cq = row_times_mat(c, z, n, n);
let cabs: Vec<F> = c.iter().map(|&x| x.abs()).collect();
let zabs: Vec<F> = z.iter().map(|&x| x.abs()).collect();
let cqa = row_times_mat(&cabs, &zabs, n, n);
for k in 0..n {
if isminor(cq[k], cqa[k]) {
cq[k] = F::zero();
}
}
let mut k = n as isize - 2;
while k >= *nact as isize {
let ku = k as usize;
if cq[ku + 1].abs() > F::zero() {
let (cc, ss) = planerot(cq[ku], cq[ku + 1]);
for r in 0..n {
let a0 = z[r + ku * n];
let a1 = z[r + (ku + 1) * n];
z[r + ku * n] = cc * a0 + ss * a1;
z[r + (ku + 1) * n] = -ss * a0 + cc * a1;
}
cq[ku] = hypotenuse(cq[ku], cq[ku + 1]);
}
k -= 1;
}
let eps2 = F::epsilon() * F::epsilon();
if *nact < n && cq[*nact].abs() > eps2 && !isminor(cq[*nact], cqa[*nact]) {
*nact += 1;
}
if *nact >= 1 && *nact <= n {
zdota[*nact - 1] = cq[*nact - 1];
}
}
fn qrexc<F: Scalar>(aact: &[F], z: &mut [F], zdota: &mut [F], n: usize, nact: usize, i: usize) {
if i + 1 >= nact {
return;
}
for k in i..(nact - 1) {
let (cc, ss) = planerot(zdota[k + 1], dot(col(z, n, k), col(aact, n, k + 1)));
for r in 0..n {
let p1 = z[r + (k + 1) * n]; let p2 = z[r + k * n]; z[r + k * n] = cc * p1 + ss * p2;
z[r + (k + 1) * n] = -ss * p1 + cc * p2;
}
}
for k in i..(nact - 1) {
zdota[k] = dot(col(z, n, k), col(aact, n, k + 1));
}
zdota[nact - 1] = dot(col(z, n, nact - 1), col(aact, n, i));
}
fn lsqr<F: Scalar>(
aact: &[F],
target: &[F],
z: &[F],
zdota: &[F],
n: usize,
nact: usize,
) -> Vec<F> {
let mut x = vec![F::zero(); nact];
let mut y = target.to_vec();
for i in (0..nact).rev() {
let zi = col(z, n, i);
let yq = dot(&y, zi);
let ya: Vec<F> = y.iter().map(|&v| v.abs()).collect();
let za: Vec<F> = zi.iter().map(|&v| v.abs()).collect();
let yqa = dot(&ya, &za);
if isminor(yq, yqa) {
x[i] = F::zero();
} else {
x[i] = yq / zdota[i];
let aci = col(aact, n, i);
for r in 0..n {
y[r] = y[r] - x[i] * aci[r];
}
}
}
x
}
#[allow(clippy::too_many_arguments)]
fn trstlp_sub<F: Scalar>(
stage: u8,
a: &[F],
n: usize,
mcon: usize,
b: &[F],
delta: F,
d: &mut [F],
iact: &mut [usize],
vmultc: &mut [F],
z: &mut [F],
nact: &mut usize,
) {
let zero = F::zero();
let one = F::one();
let eps = F::epsilon();
let realmax = F::max_value();
let m_real = if stage == 1 { mcon } else { mcon - 1 };
let mut icon: usize;
let mut sdirn = vec![zero; n];
if stage == 1 {
for k in 0..mcon {
iact[k] = k;
}
*nact = 0;
for v in d.iter_mut() {
*v = zero;
}
let mut cviol = zero;
for k in 0..mcon {
cviol = cviol.max(-b[k]);
}
for k in 0..mcon {
vmultc[k] = cviol + b[k];
}
z.copy_from_slice(&eye::<F>(n));
if mcon == 0 || cviol <= zero {
return;
}
if b[..mcon].iter().all(|x| x.is_nan()) {
return;
}
let mut best = F::neg_infinity();
icon = 0;
for k in 0..mcon {
if !b[k].is_nan() && -b[k] > best {
best = -b[k];
icon = k;
}
}
} else {
if dot(&d[..n], &d[..n]) >= delta * delta {
return;
}
iact[mcon - 1] = mcon - 1;
vmultc[mcon - 1] = zero;
icon = mcon - 1;
}
let mut cviol = if stage == 1 {
let mut c = zero;
for k in 0..mcon {
c = c.max(-b[k]);
}
c
} else {
let mut c = zero;
for k in 0..m_real {
c = c.max(dot(col(a, n, k), &d[..n]) - b[k]);
}
c
};
let mut zdota = vec![zero; n];
{
let zd = zdota_active(a, n, iact, *nact, z);
zdota[..*nact].copy_from_slice(&zd);
}
let mut optold = realmax;
let mut nactold = *nact;
let mut nfail = 0i32;
let maxiter = 10_000.min(100 * m_real.max(n));
for _iter in 0..maxiter {
let optnew = if stage == 1 {
cviol
} else {
dot(col(a, n, mcon - 1), &d[..n])
};
if optnew < optold || *nact > nactold {
nactold = *nact;
nfail = 0;
} else {
nfail += 1;
}
optold = optold.min(optnew);
if nfail == 3 {
break;
}
if icon >= *nact {
let zdasav: Vec<F> = zdota[..*nact].to_vec();
let nactsav = *nact;
let ccol = col(a, n, iact[icon]).to_vec();
qradd(&ccol, z, &mut zdota, nact, n);
if *nact == nactsav + 1 {
if *nact != icon + 1 {
let nm1 = *nact - 1;
vmultc[icon] = vmultc[nm1];
vmultc[nm1] = zero;
iact.swap(icon, nm1);
} else {
vmultc[*nact - 1] = zero;
}
} else {
let aact = active_cols(a, n, iact, *nact);
let target = col(a, n, iact[icon]).to_vec();
let mut vmultd = vec![zero; mcon];
{
let vd = lsqr(&aact, &target, z, &zdasav, n, *nact);
vmultd[..*nact].copy_from_slice(&vd);
}
let any_pos = (0..*nact).any(|k| vmultd[k] > zero && iact[k] < m_real);
if !any_pos {
break;
}
let mut frac = realmax;
for k in 0..*nact {
if vmultd[k] > zero && iact[k] < m_real {
frac = frac.min(vmultc[k] / vmultd[k]);
}
}
for k in 0..*nact {
vmultc[k] = zero.max(vmultc[k] - frac * vmultd[k]);
}
if zdota[*nact - 1].is_nan() || zdota[*nact - 1].abs() <= eps * eps {
break;
}
let nm1 = *nact - 1;
vmultc[icon] = zero;
vmultc[nm1] = frac;
iact.swap(icon, nm1);
}
if stage == 2 && iact[*nact - 1] != mcon - 1 {
if *nact <= 1 {
break;
}
let aact = active_cols(a, n, iact, *nact);
qrexc(&aact, z, &mut zdota, n, *nact, *nact - 2);
iact.swap(*nact - 2, *nact - 1);
vmultc.swap(*nact - 2, *nact - 1);
}
if zdota[*nact - 1].is_nan() || zdota[*nact - 1].abs() <= eps * eps {
break;
}
if stage == 1 {
let sa = dot(&sdirn, col(a, n, iact[*nact - 1]));
let zc = col(z, n, *nact - 1);
let coef = (sa + one) / zdota[*nact - 1];
for r in 0..n {
sdirn[r] = sdirn[r] - coef * zc[r];
}
} else {
let zc = col(z, n, *nact - 1);
let coef = one / zdota[*nact - 1];
for r in 0..n {
sdirn[r] = -coef * zc[r];
}
}
} else {
let aact = active_cols(a, n, iact, *nact);
qrexc(&aact, z, &mut zdota, n, *nact, icon);
let saved = iact[icon];
for k in icon..(*nact - 1) {
iact[k] = iact[k + 1];
}
iact[*nact - 1] = saved;
let savedc = vmultc[icon];
for k in icon..(*nact - 1) {
vmultc[k] = vmultc[k + 1];
}
vmultc[*nact - 1] = savedc;
*nact -= 1;
if stage == 2 && *nact == 0 {
break;
}
if *nact > 0 && (zdota[*nact - 1].is_nan() || zdota[*nact - 1].abs() <= eps * eps) {
break;
}
if stage == 1 {
let zc = col(z, n, *nact).to_vec(); let coef = dot(&sdirn, &zc);
for r in 0..n {
sdirn[r] = sdirn[r] - coef * zc[r];
}
} else {
let zc = col(z, n, *nact - 1);
let coef = one / zdota[*nact - 1];
for r in 0..n {
sdirn[r] = -coef * zc[r];
}
}
}
let dd = delta * delta - dot(&d[..n], &d[..n]);
let ss = dot(&sdirn, &sdirn);
let sd = dot(&sdirn, &d[..n]);
if dd <= zero || ss <= eps * delta * delta || sd.is_nan() {
break;
}
let sqrtd = (ss * dd + sd * sd)
.sqrt()
.max(sd.abs())
.max((ss * dd).sqrt());
let mut step = if sd > zero {
dd / (sqrtd + sd)
} else {
(sqrtd - sd) / ss
};
if step <= zero || !step.is_finite() {
break;
}
if stage == 1 {
if isminor(cviol, step) {
break;
}
step = step.min(cviol);
}
let mut dnew = vec![zero; n];
for r in 0..n {
dnew[r] = d[r] + step * sdirn[r];
}
if stage == 1 {
let mut c = zero;
for k in 0..*nact {
c = c.max(dot(col(a, n, iact[k]), &dnew) - b[iact[k]]);
}
cviol = c;
}
let mut vmultd = vec![zero; mcon];
{
let aact = active_cols(a, n, iact, *nact);
let vd = lsqr(&aact, &dnew, z, &zdota, n, *nact);
for k in 0..*nact {
vmultd[k] = -vd[k];
}
}
if stage == 2 && *nact >= 1 {
vmultd[*nact - 1] = zero.max(vmultd[*nact - 1]);
}
for k in *nact..mcon {
let j = iact[k];
let adn = dot(col(a, n, j), &dnew);
let cvshift = cviol - (adn - b[j]);
let dabs: Vec<F> = dnew.iter().map(|&v| v.abs()).collect();
let aabs: Vec<F> = col(a, n, j).iter().map(|&v| v.abs()).collect();
let cvsabs = dot(&dabs, &aabs) + b[j].abs() + cviol;
vmultd[k] = if isminor(cvshift, cvsabs) {
zero
} else {
cvshift
};
}
let mut frac = one;
icon = usize::MAX; let mut have = false;
for k in 0..mcon {
if vmultd[k] < zero {
let f = vmultc[k] / (vmultc[k] - vmultd[k]);
if !have || f < frac {
frac = f;
icon = k;
have = true;
}
}
}
if !have {
frac = one;
}
let dold = d[..n].to_vec();
for r in 0..n {
d[r] = (one - frac) * d[r] + frac * dnew[r];
}
if !d[..n].iter().map(|v| v.abs()).sum::<F>().is_finite() {
d[..n].copy_from_slice(&dold);
break;
}
for k in 0..mcon {
vmultc[k] = zero.max((one - frac) * vmultc[k] + frac * vmultd[k]);
}
if stage == 1 {
let mut c = zero;
for k in 0..mcon {
c = c.max(dot(col(a, n, k), &d[..n]) - b[k]);
}
cviol = c;
}
if !have {
break;
}
}
}
pub(crate) fn trrad<F: Scalar>(
delta_in: F,
dnorm: F,
eta1: F,
eta2: F,
gamma1: F,
gamma2: F,
ratio: F,
) -> F {
if ratio <= eta1 {
gamma1 * dnorm
} else if ratio <= eta2 {
(gamma1 * delta_in).max(dnorm)
} else {
(gamma1 * delta_in).max(gamma2 * dnorm)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn norm(d: &[f64]) -> f64 {
dot(d, d).sqrt()
}
#[test]
fn unconstrained_step_is_minus_delta_gradient_direction() {
let n = 2;
let g = vec![3.0_f64, 4.0];
let delta = 2.0;
let d = trstlp::<f64>(&[], n, 0, &[], delta, &g);
assert!((norm(&d) - delta).abs() < 1e-9, "‖d‖ = {}", norm(&d));
let gn = norm(&g);
assert!((d[0] - (-delta * g[0] / gn)).abs() < 1e-9);
assert!((d[1] - (-delta * g[1] / gn)).abs() < 1e-9);
}
#[test]
fn inactive_constraint_does_not_bind() {
let n = 2;
let a = vec![1.0_f64, 0.0]; let b = vec![5.0_f64];
let g = vec![1.0_f64, 0.0];
let d = trstlp::<f64>(&a, n, 1, &b, 1.0, &g);
assert!((d[0] - (-1.0)).abs() < 1e-9, "d = {:?}", d);
assert!(d[1].abs() < 1e-9);
}
#[test]
fn active_constraint_binds_the_step() {
let n = 2;
let a = vec![1.0_f64, 0.0]; let b = vec![0.3_f64];
let g = vec![-1.0_f64, 0.0];
let d = trstlp::<f64>(&a, n, 1, &b, 1.0, &g);
assert!((d[0] - 0.3).abs() < 1e-9, "d = {:?}", d);
assert!(d[1].abs() < 1e-9, "d = {:?}", d);
}
#[test]
fn infeasible_center_reduced_in_stage1() {
let n = 2;
let a = vec![1.0_f64, 0.0];
let b = vec![-0.5_f64];
let g = vec![0.0_f64, 0.0];
let d = trstlp::<f64>(&a, n, 1, &b, 1.0, &g);
assert!(d[0] <= -0.5 + 1e-9, "d = {:?}", d);
}
}