use crate::ipopt_cq::IpoptCqHandle;
use crate::iterates_vector::IteratesVector;
use pounce_common::types::{Index, Number};
use pounce_linalg::Vector;
fn dense(v: &dyn Vector) -> Option<Vec<Number>> {
v.as_any()
.downcast_ref::<pounce_linalg::dense_vector::DenseVector>()
.map(|d| d.expanded_values())
}
pub const DEFAULT_FTB_CENTRALITY_FLOOR: Number = 0.0;
pub const DEFAULT_PERTURB_SLACK_FACT: Number = 1.0;
pub const DEFAULT_PERTURB_SLACK_MAX_ROWS: Index = 1000;
const MAX_FRACTION: Number = 1.0 - 1e-12;
pub fn floored_alphas(
cq: &IpoptCqHandle,
delta: &IteratesVector,
mu: Number,
gamma: Number,
tau: Number,
) -> Option<(Number, Number)> {
let cq = cq.borrow();
let iv = cq.curr_iv();
let nlp = cq.nlp().borrow();
let s_x_l = cq.curr_slack_x_l();
let s_x_u = cq.curr_slack_x_u();
let s_s_l = cq.curr_slack_s_l();
let s_s_u = cq.curr_slack_s_u();
let mut d_x_l = s_x_l.make_new();
nlp.px_l()
.trans_mult_vector(1.0, &*delta.x, 0.0, &mut *d_x_l);
let mut d_x_u = s_x_u.make_new();
nlp.px_u()
.trans_mult_vector(-1.0, &*delta.x, 0.0, &mut *d_x_u);
let mut d_s_l = s_s_l.make_new();
nlp.pd_l()
.trans_mult_vector(1.0, &*delta.s, 0.0, &mut *d_s_l);
let mut d_s_u = s_s_u.make_new();
nlp.pd_u()
.trans_mult_vector(-1.0, &*delta.s, 0.0, &mut *d_s_u);
let blocks: [(&dyn Vector, &dyn Vector, &dyn Vector, &dyn Vector); 4] = [
(&*s_x_l, &*d_x_l, &*iv.z_l, &*delta.z_l),
(&*s_x_u, &*d_x_u, &*iv.z_u, &*delta.z_u),
(&*s_s_l, &*d_s_l, &*iv.v_l, &*delta.v_l),
(&*s_s_u, &*d_s_u, &*iv.v_u, &*delta.v_u),
];
let mut dense_blocks = Vec::with_capacity(4);
for (s, ds, v, dv) in blocks {
if s.dim() == 0 {
continue;
}
dense_blocks.push((dense(s)?, dense(ds)?, dense(v)?, dense(dv)?));
}
Some(floored_from_blocks(&dense_blocks, mu, gamma, tau))
}
pub(crate) type Block = (Vec<Number>, Vec<Number>, Vec<Number>, Vec<Number>);
pub(crate) fn floored_from_blocks(
dense_blocks: &[Block],
mu: Number,
gamma: Number,
tau: Number,
) -> (Number, Number) {
let mut ap_bnd = 1.0_f64;
let mut ad_bnd = 1.0_f64;
let mut ip: Option<(usize, usize)> = None;
let mut id: Option<(usize, usize)> = None;
for (b, (s, ds, v, dv)) in dense_blocks.iter().enumerate() {
for i in 0..s.len() {
if ds[i] < 0.0 {
let a = -s[i] / ds[i];
if a < ap_bnd {
ap_bnd = a;
ip = Some((b, i));
}
}
if dv[i] < 0.0 {
let a = -v[i] / dv[i];
if a < ad_bnd {
ad_bnd = a;
id = Some((b, i));
}
}
}
}
let floor = 1.0 - gamma;
let uncentered = |b: usize, i: usize| dense_blocks[b].0[i] * dense_blocks[b].2[i] < gamma * mu;
let alpha_p = match ip {
None => 1.0,
Some((b, i)) if uncentered(b, i) => (tau * ap_bnd).min(1.0),
Some((b, i)) => {
let (s, ds, v, dv) = &dense_blocks[b];
let v_new = v[i] + ad_bnd * dv[i];
let f = if v_new > 0.0 {
(gamma * mu / v_new - s[i]) / (ap_bnd * ds[i])
} else {
floor
};
(floor.max(f).min(tau).min(MAX_FRACTION) * ap_bnd).min(1.0)
}
};
let alpha_d = match id {
None => 1.0,
Some((b, i)) if uncentered(b, i) => (tau * ad_bnd).min(1.0),
Some((b, i)) => {
let (s, ds, v, dv) = &dense_blocks[b];
let s_new = s[i] + alpha_p * ds[i];
let f = if s_new > 0.0 {
(gamma * mu / s_new - v[i]) / (ad_bnd * dv[i])
} else {
floor
};
(floor.max(f).min(tau).min(MAX_FRACTION) * ad_bnd).min(1.0)
}
};
(alpha_p, alpha_d)
}
#[cfg(test)]
mod tests {
use super::*;
fn blk(s: &[f64], ds: &[f64], v: &[f64], dv: &[f64]) -> Block {
(s.to_vec(), ds.to_vec(), v.to_vec(), dv.to_vec())
}
const TAU: f64 = 0.99;
#[test]
fn no_blocking_variable_takes_full_step() {
let b = [blk(&[1.0], &[1.0], &[1.0], &[1.0])];
assert_eq!(floored_from_blocks(&b, 1e-6, 0.01, TAU), (1.0, 1.0));
}
#[test]
fn uncentered_blocker_keeps_tau_rule() {
let mu = 1e-6;
let b = [blk(&[1e-6], &[-2e-6], &[1e-6], &[-4e-6])];
let (ap, ad) = floored_from_blocks(&b, mu, 0.01, TAU);
assert!((ap - TAU * 0.5).abs() < 1e-15);
assert!((ad - TAU * 0.25).abs() < 1e-15);
}
#[test]
fn centered_blocker_stays_at_least_gamma_mu() {
let (mu, gamma) = (1e-6, 0.01);
let b = [blk(&[1e-3], &[-2e-3], &[1e-3], &[0.0])];
let (ap, ad) = floored_from_blocks(&b, mu, gamma, 1.0 - mu);
assert_eq!(ad, 1.0);
let (s, ds, v, _) = &b[0];
let prod = (s[0] + ap * ds[0]) * v[0];
assert!(prod >= gamma * mu * (1.0 - 1e-9), "prod {prod}");
assert!(ap >= (1.0 - gamma) * 0.5 && ap < 0.5);
let prod_tau = (s[0] + (1.0 - mu) * 0.5 * ds[0]) * v[0];
assert!(prod_tau < 1e-11);
}
#[test]
fn floor_bounds_the_fraction_from_below() {
let (mu, gamma) = (1e-6, 0.01);
let b = [blk(&[1e-3], &[-2e-3], &[1e-3], &[-0.999e-3])];
let (ap, _) = floored_from_blocks(&b, mu, gamma, TAU);
assert!((ap - (1.0 - gamma) * 0.5).abs() < 1e-12, "ap {ap}");
}
#[test]
fn fraction_never_rounds_onto_the_bound() {
let b = [blk(&[1.0], &[-2.0], &[1e6], &[0.0])];
let (ap, _) = floored_from_blocks(&b, 1e-12, 0.01, TAU);
assert!(ap < 0.5);
assert!(1.0 + ap * -2.0 > 0.0);
}
#[test]
fn never_steps_further_than_the_tau_rule() {
let b = [blk(&[9.0], &[0.0], &[1.0], &[-1.09])];
let (_, ad) = floored_from_blocks(&b, 0.1, 0.01, TAU);
assert!((ad - TAU / 1.09).abs() < 1e-15, "ad {ad}");
}
}