use crate::sqp::line_search::{
KAPPA_SOC, LineSearchResult, SOC_MAX_STEP_GROWTH, SocProvider, inf_norm, l1_violation,
};
use crate::sqp::options::SqpOptions;
use crate::sqp::problem::SqpProblemSpec;
use pounce_common::Number;
#[derive(Debug, Clone, Default)]
pub struct SqpFilter {
entries: Vec<(Number, Number)>,
pub gamma_theta: Number,
pub gamma_phi: Number,
}
impl SqpFilter {
pub fn new() -> Self {
Self {
entries: Vec::new(),
gamma_theta: 1e-5,
gamma_phi: 1e-5,
}
}
pub fn accepts(&self, theta: Number, phi: Number) -> bool {
for &(t_e, p_e) in &self.entries {
let ok = theta <= (1.0 - self.gamma_theta) * t_e || phi <= p_e - self.gamma_phi * t_e;
if !ok {
return false;
}
}
true
}
pub fn add(&mut self, theta_curr: Number, phi_curr: Number) {
let new_theta = theta_curr * (1.0 - self.gamma_theta);
let new_phi = phi_curr - self.gamma_phi * theta_curr;
self.entries
.retain(|&(t_e, p_e)| !(new_theta <= t_e && new_phi <= p_e));
self.entries.push((new_theta, new_phi));
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[allow(clippy::too_many_arguments)]
pub fn filter_line_search<N: SqpProblemSpec>(
nlp: &mut N,
filter: &mut SqpFilter,
x: &[Number],
p: &[Number],
f_curr: Number,
c_curr: &[Number],
bl: &[Number],
bu: &[Number],
xl: &[Number],
xu: &[Number],
current_nu: Number,
opts: &SqpOptions,
mut soc: Option<SocProvider<'_>>,
) -> LineSearchResult {
let theta_curr = l1_violation(x, c_curr, bl, bu, xl, xu);
let phi_curr = f_curr;
let mut alpha = 1.0_f64;
let mut x_trial = vec![0.0; x.len()];
let mut last_f = f_curr;
let mut last_c = c_curr.to_vec();
let mut first_trial = true;
while alpha > opts.bt_min_alpha {
for (xt, (&xi, &pi)) in x_trial.iter_mut().zip(x.iter().zip(p.iter())) {
*xt = xi + alpha * pi;
}
let f_trial = nlp.eval_f(&x_trial);
let c_trial = nlp.eval_c(&x_trial);
let theta_trial = l1_violation(&x_trial, &c_trial, bl, bu, xl, xu);
let phi_trial = f_trial;
last_f = f_trial;
last_c.clone_from(&c_trial);
let theta_progress = theta_trial <= (1.0 - filter.gamma_theta) * theta_curr;
let phi_progress = phi_trial <= phi_curr - filter.gamma_phi * theta_curr;
let progress = theta_progress || phi_progress;
if progress && filter.accepts(theta_trial, phi_trial) {
filter.add(theta_curr, phi_curr);
return LineSearchResult {
alpha,
nu: current_nu,
x_new: x_trial,
f_new: f_trial,
c_new: c_trial,
success: true,
soc_duals: None,
};
}
if first_trial {
first_trial = false;
if theta_trial > theta_curr {
if let Some(soc_fn) = soc.as_deref_mut() {
if let Some(step) = soc_fn(&c_trial) {
if inf_norm(&step.p) <= SOC_MAX_STEP_GROWTH * inf_norm(p) {
let mut x_soc = vec![0.0; x.len()];
for (xt, (&xi, &pi)) in
x_soc.iter_mut().zip(x.iter().zip(step.p.iter()))
{
*xt = xi + pi;
}
let f_soc = nlp.eval_f(&x_soc);
let c_soc = nlp.eval_c(&x_soc);
let theta_soc = l1_violation(&x_soc, &c_soc, bl, bu, xl, xu);
let phi_soc = f_soc;
let tp = theta_soc <= (1.0 - filter.gamma_theta) * theta_curr;
let pp = phi_soc <= phi_curr - filter.gamma_phi * theta_curr;
let soc_feasible = theta_soc <= KAPPA_SOC * theta_trial;
if soc_feasible && (tp || pp) && filter.accepts(theta_soc, phi_soc) {
filter.add(theta_curr, phi_curr);
return LineSearchResult {
alpha: 1.0,
nu: current_nu,
x_new: x_soc,
f_new: f_soc,
c_new: c_soc,
success: true,
soc_duals: Some((step.lambda_g, step.lambda_x)),
};
}
}
}
}
}
}
alpha *= opts.bt_reduction;
}
LineSearchResult {
alpha,
nu: current_nu,
x_new: x_trial,
f_new: last_f,
c_new: last_c,
success: false,
soc_duals: None,
}
}