use crate::problem::HessianInertia;
use crate::working_set::{BoundStatus, ConsStatus, WorkingSet};
use crate::QpProblem;
use pounce_common::types::{Index, Number, NLP_LOWER_BOUND_INF, NLP_UPPER_BOUND_INF};
use pounce_linalg::triplet::{GenTMatrix, GenTMatrixSpace, SymTMatrix, SymTMatrixSpace};
use std::rc::Rc;
pub struct ElasticReformulation {
pub n_orig: usize,
pub m_orig: usize,
pub n_aug: usize,
pub m_aug: usize,
pub h_aug: SymTMatrix,
pub g_aug: Vec<Number>,
pub a_aug: GenTMatrix,
pub bl_aug: Vec<Number>,
pub bu_aug: Vec<Number>,
pub xl_aug: Vec<Number>,
pub xu_aug: Vec<Number>,
pub gamma: Number,
}
impl ElasticReformulation {
pub fn build(qp: &QpProblem<'_>, gamma: Number) -> Self {
let n = qp.n;
let m = qp.m;
let n_aug = n + 2 * m;
let m_aug = m;
let h_irows: Vec<Index> = qp.h.irows().to_vec();
let h_jcols: Vec<Index> = qp.h.jcols().to_vec();
let h_vals: Vec<Number> = qp.h.values().to_vec();
let h_space = SymTMatrixSpace::new(n_aug as Index, h_irows, h_jcols);
let mut h_aug = SymTMatrix::new(Rc::clone(&h_space));
h_aug.set_values(&h_vals);
let na_orig = qp.a.nonzeros() as usize;
let na_aug = na_orig + 2 * m;
let mut a_irows = Vec::with_capacity(na_aug);
let mut a_jcols = Vec::with_capacity(na_aug);
let mut a_vals = Vec::with_capacity(na_aug);
a_irows.extend_from_slice(qp.a.irows());
a_jcols.extend_from_slice(qp.a.jcols());
a_vals.extend_from_slice(qp.a.values());
let n_i = n as Index;
let m_i = m as Index;
for i in 1..=m_i {
a_irows.push(i);
a_jcols.push(n_i + i);
a_vals.push(1.0);
a_irows.push(i);
a_jcols.push(n_i + m_i + i);
a_vals.push(-1.0);
}
let a_space = GenTMatrixSpace::new(m_aug as Index, n_aug as Index, a_irows, a_jcols);
let mut a_aug = GenTMatrix::new(Rc::clone(&a_space));
a_aug.set_values(&a_vals);
let mut g_aug = vec![0.0; n_aug];
g_aug[..n].copy_from_slice(qp.g);
g_aug[n..n + m].fill(gamma);
g_aug[n + m..].fill(gamma);
let bl_aug = qp.bl.to_vec();
let bu_aug = qp.bu.to_vec();
let mut xl_aug = vec![0.0; n_aug];
let mut xu_aug = vec![NLP_UPPER_BOUND_INF; n_aug];
xl_aug[..n].copy_from_slice(qp.xl);
xu_aug[..n].copy_from_slice(qp.xu);
Self {
n_orig: n,
m_orig: m,
n_aug,
m_aug,
h_aug,
g_aug,
a_aug,
bl_aug,
bu_aug,
xl_aug,
xu_aug,
gamma,
}
}
pub fn as_qp(&self) -> QpProblem<'_> {
QpProblem {
n: self.n_aug,
m: self.m_aug,
h: &self.h_aug,
g: &self.g_aug,
a: &self.a_aug,
bl: &self.bl_aug,
bu: &self.bu_aug,
xl: &self.xl_aug,
xu: &self.xu_aug,
hessian_inertia: match self.original_inertia() {
HessianInertia::Psd | HessianInertia::Unknown => HessianInertia::Psd,
HessianInertia::Indefinite => HessianInertia::Indefinite,
},
}
}
fn original_inertia(&self) -> HessianInertia {
HessianInertia::Psd
}
pub fn initial_seed(
&self,
qp: &QpProblem<'_>,
x_orig: &[Number],
feas_tol: Number,
) -> (Vec<Number>, WorkingSet) {
let n = self.n_orig;
let m = self.m_orig;
let mut x_aug = vec![0.0; self.n_aug];
x_aug[..n].copy_from_slice(x_orig);
let mut working = WorkingSet::cold(self.n_aug, self.m_aug);
let ax = crate::kkt::a_times_x(qp.a, x_orig, m);
for i in 0..m {
let l = qp.bl[i];
let u = qp.bu[i];
let mut v_l = 0.0;
let mut v_u = 0.0;
if l > NLP_LOWER_BOUND_INF && ax[i] < l {
v_l = l - ax[i];
}
if u < NLP_UPPER_BOUND_INF && ax[i] > u {
v_u = ax[i] - u;
}
x_aug[n + i] = v_l;
x_aug[n + m + i] = v_u;
let lhs = ax[i] + v_l - v_u;
working.constraints[i] = if l == u {
ConsStatus::Equality
} else if l > NLP_LOWER_BOUND_INF && (lhs - l).abs() <= feas_tol {
ConsStatus::AtLower
} else if u < NLP_UPPER_BOUND_INF && (lhs - u).abs() <= feas_tol {
ConsStatus::AtUpper
} else {
ConsStatus::Inactive
};
working.bounds[n + i] = if v_l == 0.0 {
BoundStatus::AtLower
} else {
BoundStatus::Inactive
};
working.bounds[n + m + i] = if v_u == 0.0 {
BoundStatus::AtLower
} else {
BoundStatus::Inactive
};
}
for (i, &xi) in x_orig.iter().enumerate() {
let l = qp.xl[i];
let u = qp.xu[i];
let l_finite = l > NLP_LOWER_BOUND_INF;
let u_finite = u < NLP_UPPER_BOUND_INF;
if l_finite && u_finite && (l - u).abs() <= feas_tol {
working.bounds[i] = BoundStatus::Fixed;
x_aug[i] = l;
} else if l_finite && (xi - l).abs() <= feas_tol {
working.bounds[i] = BoundStatus::AtLower;
x_aug[i] = l;
} else if u_finite && (xi - u).abs() <= feas_tol {
working.bounds[i] = BoundStatus::AtUpper;
x_aug[i] = u;
}
}
(x_aug, working)
}
pub fn is_feasible(&self, x_aug: &[Number], feas_tol: Number) -> bool {
let n = self.n_orig;
let m = self.m_orig;
for i in 0..m {
if x_aug[n + i] > feas_tol || x_aug[n + m + i] > feas_tol {
return false;
}
}
true
}
}