use pounce_common::Number;
use pounce_common::timing::Deadline;
use pounce_qp::HessianInertia;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SqpGlobalization {
#[default]
Filter,
L1Elastic,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SqpHessianSource {
#[default]
Exact,
DampedBfgs,
Lbfgs,
QuasiNewtonAuto,
}
impl SqpHessianSource {
pub fn resolve(self, n: usize, dense_hessian_max_n: usize) -> Self {
match self {
Self::QuasiNewtonAuto if n <= dense_hessian_max_n => Self::DampedBfgs,
Self::QuasiNewtonAuto => Self::Lbfgs,
other => other,
}
}
pub fn is_quasi_newton(self) -> bool {
!matches!(self, Self::Exact)
}
}
pub const DEFAULT_QP_SCHUR_MIN_DIM: usize = 500;
pub const DEFAULT_QP_SCHUR_INDEFINITE_MAX_DIM: usize = 5000;
pub const DEFAULT_QP_IPM_COLD_START_MIN_DIM: usize = 5000;
#[derive(Debug, Clone)]
pub struct SqpOptions {
pub globalization: SqpGlobalization,
pub hessian: SqpHessianSource,
pub tol: Number,
pub constr_viol_tol: Number,
pub dual_inf_tol: Number,
pub max_iter: u32,
pub l1_penalty: Number,
pub l1_penalty_safety: Number,
pub l1_penalty_max: Number,
pub bt_reduction: Number,
pub bt_min_alpha: Number,
pub print_level: u8,
pub lbfgs_max_history: u32,
pub dense_hessian_max_n: usize,
pub qp_schur_min_dim: Option<usize>,
pub qp_schur_indefinite_max_dim: Option<usize>,
pub qp_ipm_cold_start_min_dim: Option<usize>,
pub deadline: Option<Deadline>,
}
impl Default for SqpOptions {
fn default() -> Self {
Self {
globalization: SqpGlobalization::default(),
hessian: SqpHessianSource::default(),
tol: 1e-8,
constr_viol_tol: 1e-6,
dual_inf_tol: 1e-4,
max_iter: 200,
l1_penalty: 1.0,
l1_penalty_safety: 0.1,
l1_penalty_max: 1e10,
bt_reduction: 0.5,
bt_min_alpha: 1e-12,
print_level: 0,
lbfgs_max_history: 6,
dense_hessian_max_n: 1000,
qp_schur_min_dim: Some(DEFAULT_QP_SCHUR_MIN_DIM),
qp_schur_indefinite_max_dim: Some(DEFAULT_QP_SCHUR_INDEFINITE_MAX_DIM),
qp_ipm_cold_start_min_dim: Some(DEFAULT_QP_IPM_COLD_START_MIN_DIM),
deadline: None,
}
}
}
impl SqpOptions {
pub fn step_qp_schur_updates(&self, dim: usize, warm: Option<HessianInertia>) -> Option<bool> {
let min = self.qp_schur_min_dim?;
let refactor = warm.is_some_and(|h| h != HessianInertia::Psd)
&& self.qp_schur_indefinite_max_dim.is_some_and(|d| dim >= d);
Some(dim >= min && !refactor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn warm_indefinite_step_qps_leave_schur_updates_off_at_size() {
use HessianInertia::{Indefinite, Psd, Unknown};
let o = SqpOptions::default();
let (lo, hi) = (
DEFAULT_QP_SCHUR_MIN_DIM,
DEFAULT_QP_SCHUR_INDEFINITE_MAX_DIM,
);
assert!(lo < hi);
for warm in [None, Some(Indefinite), Some(Psd), Some(Unknown)] {
let at = |d| o.step_qp_schur_updates(d, warm);
assert_eq!(at(lo - 1), Some(false), "{warm:?}");
assert_eq!(at(lo), Some(true), "{warm:?}");
assert_eq!(at(hi - 1), Some(true), "{warm:?}");
}
for h in [Indefinite, Unknown] {
assert_eq!(o.step_qp_schur_updates(hi, Some(h)), Some(false), "{h:?}");
}
assert_eq!(o.step_qp_schur_updates(hi, None), Some(true), "cold");
assert_eq!(o.step_qp_schur_updates(hi, Some(Psd)), Some(true));
let unset = SqpOptions {
qp_schur_indefinite_max_dim: None,
..SqpOptions::default()
};
assert_eq!(
unset.step_qp_schur_updates(hi, Some(Indefinite)),
Some(true)
);
let chosen = SqpOptions {
qp_schur_min_dim: None,
..SqpOptions::default()
};
assert_eq!(chosen.step_qp_schur_updates(hi, Some(Indefinite)), None);
}
}