use pounce_common::types::{Index, NLP_LOWER_BOUND_INF, NLP_UPPER_BOUND_INF, Number};
use pounce_linalg::triplet::{GenTMatrix, GenTMatrixSpace, SymTMatrix, SymTMatrixSpace};
use pounce_qp::{BoundStatus, ConsStatus, HessianInertia, QpProblem, QpSolution, WorkingSet};
use std::rc::Rc;
pub struct SqpQpData {
pub n: usize,
pub m: usize,
pub h: SymTMatrix,
pub g: Vec<Number>,
pub a: GenTMatrix,
pub bl: Vec<Number>,
pub bu: Vec<Number>,
pub xl: Vec<Number>,
pub xu: Vec<Number>,
pub hessian_inertia: HessianInertia,
pub hess_inf: Number,
lift: Option<Lift>,
}
struct Lift {
r: usize,
a: GenTMatrix,
}
#[derive(Clone)]
pub struct Triplet {
pub n_rows: usize,
pub n_cols: usize,
pub irow: Vec<Index>,
pub jcol: Vec<Index>,
pub vals: Vec<Number>,
}
impl SqpQpData {
pub fn build(
x: &[Number],
grad_f: &[Number],
c_vals: &[Number],
bl_c: &[Number],
bu_c: &[Number],
xl_orig: &[Number],
xu_orig: &[Number],
jac_c: Triplet,
hess_lag: Triplet,
hessian_inertia: HessianInertia,
) -> Self {
let n = grad_f.len();
let m = c_vals.len();
assert_eq!(x.len(), n);
assert_eq!(bl_c.len(), m);
assert_eq!(bu_c.len(), m);
assert_eq!(xl_orig.len(), n);
assert_eq!(xu_orig.len(), n);
assert_eq!(jac_c.n_rows, m);
assert_eq!(jac_c.n_cols, n);
assert_eq!(hess_lag.n_rows, n);
assert_eq!(hess_lag.n_cols, n);
let h_space = SymTMatrixSpace::new(n as Index, hess_lag.irow, hess_lag.jcol);
let mut h = SymTMatrix::new(Rc::clone(&h_space));
h.set_values(&hess_lag.vals);
let a_space = GenTMatrixSpace::new(m as Index, n as Index, jac_c.irow, jac_c.jcol);
let mut a = GenTMatrix::new(Rc::clone(&a_space));
a.set_values(&jac_c.vals);
let mut bl = Vec::with_capacity(m);
let mut bu = Vec::with_capacity(m);
for i in 0..m {
bl.push(shift_bound(bl_c[i], c_vals[i], true));
bu.push(shift_bound(bu_c[i], c_vals[i], false));
}
let mut xl = Vec::with_capacity(n);
let mut xu = Vec::with_capacity(n);
for i in 0..n {
xl.push(shift_bound(xl_orig[i], x[i], true));
xu.push(shift_bound(xu_orig[i], x[i], false));
}
let hess_inf = h.values().iter().map(|v| v.abs()).fold(0.0, f64::max);
Self {
n,
m,
h,
g: grad_f.to_vec(),
a,
bl,
bu,
xl,
xu,
hessian_inertia,
hess_inf,
lift: None,
}
}
#[allow(clippy::too_many_arguments)]
pub fn build_low_rank(
x: &[Number],
grad_f: &[Number],
c_vals: &[Number],
bl_c: &[Number],
bu_c: &[Number],
xl_orig: &[Number],
xu_orig: &[Number],
jac_c: Triplet,
b: &crate::sqp::lbfgs::LowRankHessian,
) -> Self {
let n = grad_f.len();
let m = c_vals.len();
let r = b.vecs.len();
let diag = Triplet {
n_rows: n,
n_cols: n,
irow: (1..=n as Index).collect(),
jcol: (1..=n as Index).collect(),
vals: vec![b.sigma; n],
};
let mut out = Self::build(
x,
grad_f,
c_vals,
bl_c,
bu_c,
xl_orig,
xu_orig,
jac_c.clone(),
diag,
HessianInertia::Psd,
);
out.hess_inf = b.max_abs_entry();
if r == 0 {
return out;
}
let (nq, mq) = ((n + r) as Index, (m + r) as Index);
let h_space = SymTMatrixSpace::new(nq, (1..=nq).collect(), (1..=nq).collect());
let mut h = SymTMatrix::new(h_space);
let mut h_vals = vec![b.sigma; n];
h_vals.extend_from_slice(&b.signs);
h.set_values(&h_vals);
let mut irow = jac_c.irow;
let mut jcol = jac_c.jcol;
let mut vals = jac_c.vals;
for (i, v) in b.vecs.iter().enumerate() {
let row = (m + i + 1) as Index;
for (j, &vj) in v.iter().enumerate() {
if vj != 0.0 {
irow.push(row);
jcol.push((j + 1) as Index);
vals.push(-vj);
}
}
irow.push(row);
jcol.push((n + i + 1) as Index);
vals.push(1.0);
}
let a_space = GenTMatrixSpace::new(mq, nq, irow, jcol);
let mut a = GenTMatrix::new(a_space);
a.set_values(&vals);
out.h = h;
out.g.resize(n + r, 0.0);
out.bl.resize(m + r, 0.0);
out.bu.resize(m + r, 0.0);
out.xl.resize(n + r, NLP_LOWER_BOUND_INF);
out.xu.resize(n + r, NLP_UPPER_BOUND_INF);
out.hessian_inertia = HessianInertia::Indefinite;
out.lift = Some(Lift { r, a });
out
}
pub fn as_qp(&self) -> QpProblem<'_> {
self.as_qp_with_row_bounds(&self.bl, &self.bu)
}
pub fn as_qp_with_row_bounds<'a>(
&'a self,
bl: &'a [Number],
bu: &'a [Number],
) -> QpProblem<'a> {
let r = self.lift.as_ref().map_or(0, |l| l.r);
QpProblem {
n: self.n + r,
m: self.m + r,
h: &self.h,
g: &self.g,
a: self.lift.as_ref().map_or(&self.a, |l| &l.a),
bl,
bu,
xl: &self.xl,
xu: &self.xu,
hessian_inertia: self.hessian_inertia,
}
}
pub fn is_lifted(&self) -> bool {
self.lift.is_some()
}
pub fn lift_working_set(&self, w: &WorkingSet) -> WorkingSet {
let mut w = w.clone();
if let Some(l) = &self.lift {
w.bounds.truncate(self.n);
w.constraints.truncate(self.m);
w.bounds.resize(self.n + l.r, BoundStatus::Inactive);
w.constraints.resize(self.m + l.r, ConsStatus::Equality);
}
w
}
pub fn unlift_solution(&self, mut sol: QpSolution) -> QpSolution {
if self.lift.is_some() {
sol.x.truncate(self.n);
sol.lambda_g.truncate(self.m);
sol.lambda_x.truncate(self.n);
sol.working.bounds.truncate(self.n);
sol.working.constraints.truncate(self.m);
if let Some(ray) = sol.unbounded_ray.as_mut() {
ray.truncate(self.n);
}
}
sol
}
}
fn shift_bound(bound: Number, current: Number, is_lower: bool) -> Number {
if is_lower {
if bound <= NLP_LOWER_BOUND_INF {
NLP_LOWER_BOUND_INF
} else {
bound - current
}
} else if bound >= NLP_UPPER_BOUND_INF {
NLP_UPPER_BOUND_INF
} else {
bound - current
}
}