use std::rc::Rc;
use pounce_algorithm::ipopt_data::IpoptDataHandle;
use pounce_algorithm::iterates_vector::IteratesVector;
use pounce_common::types::{Index, Number};
use pounce_linalg::Matrix;
use pounce_linalg::expansion_matrix::ExpansionMatrix;
use pounce_sens_core::barrier::{
ActivityData, BarrierIterate, BoundSide, EngineScaling, KktOperator, ModelMaps,
};
use crate::algorithm_backsolver::{CorrectorSigma, PdSensBacksolver};
use crate::backsolver::SensBacksolver;
use crate::vec_util::dense_to_vec;
impl BarrierIterate for PdSensBacksolver {
fn block_dims(&self) -> [usize; 8] {
PdSensBacksolver::block_dims(self)
}
fn offsets(&self) -> [usize; 9] {
self.offsets_public()
}
fn barrier_mu(&self) -> Number {
PdSensBacksolver::barrier_mu(self)
}
fn converged_iterate(&self, out: &mut [Number]) -> Result<(), String> {
self.curr_flat(out)
.map_err(|_| "the solve holds no current iterate of that length".to_string())
}
fn scaled_rhs_factor(&self) -> Option<&[Number]> {
PdSensBacksolver::scaled_rhs_factor(self)
}
fn barrier_residual(&self, point: &[Number], out: &mut [Number]) -> Result<(), String> {
let dim = self.dim();
if point.len() != dim || out.len() != dim {
return Err(format!(
"barrier residual: point of length {} and out of length {} for a KKT system of {dim}",
point.len(),
out.len()
));
}
let iv = self
.pack_public(point)
.map_err(|_| "corrector: pack failed".to_string())?;
let (data, cq, _) = self.activity_handles();
data.borrow_mut().set_trial(iv.freeze());
let off = self.offsets_public();
let cqb = cq.borrow();
let blocks: [Rc<dyn pounce_linalg::Vector>; 8] = [
cqb.trial_grad_lag_x(),
cqb.trial_grad_lag_s(),
cqb.trial_c(),
cqb.trial_d_minus_s(),
cqb.trial_compl_x_l(),
cqb.trial_compl_x_u(),
cqb.trial_compl_s_l(),
cqb.trial_compl_s_u(),
];
for (i, v) in blocks.into_iter().enumerate() {
let vals = dense_to_vec(&*v);
let (a, b) = (off[i], off[i + 1]);
if vals.len() != b - a {
return Err(format!(
"corrector: block {i} is {} long, expected {}",
vals.len(),
b - a
));
}
out[a..b].copy_from_slice(&vals);
}
Ok(())
}
fn corrector_operator<'a>(
&'a self,
predicted: &[Number],
pinned: &[(usize, Number)],
exact_hessian: bool,
) -> Result<Box<dyn KktOperator + 'a>, String> {
let (data_h, cq_h, _) = self.activity_handles();
let predicted = self
.pack_natural(predicted)
.ok_or_else(|| "corrector: packing the predicted point failed".to_string())?;
let mut restore = RestoreCurr {
data: data_h,
saved: data_h.borrow().curr.clone(),
saved_w: None,
restore_w: false,
};
data_h.borrow_mut().set_curr(predicted);
if exact_hessian {
restore.saved_w = data_h.borrow().w.clone();
restore.restore_w = true;
let w = cq_h.borrow().curr_exact_hessian();
data_h.borrow_mut().w = Some(w);
}
let op = self
.corrector_sigma(pinned)
.ok_or_else(|| "corrector: operator diagonal unavailable".to_string())?;
Ok(Box::new(PdCorrectorOperator {
bs: self,
op,
_restore: restore,
}))
}
fn activity_data(&self) -> Result<ActivityData, String> {
let (data, cq, nlp) = self.activity_handles();
let (z_l, z_u, v_l, v_u) = {
let d = data.borrow();
let curr = d
.curr
.as_ref()
.ok_or("activity: the converged state has no iterate")?;
(
dense_to_vec(curr.z_l.as_ref()),
dense_to_vec(curr.z_u.as_ref()),
dense_to_vec(curr.v_l.as_ref()),
dense_to_vec(curr.v_u.as_ref()),
)
};
let (px_l, px_u, pd_l, pd_u) = {
let nl = nlp.borrow();
(
positions(&nl.px_l())?,
positions(&nl.px_u())?,
positions(&nl.pd_l())?,
positions(&nl.pd_u())?,
)
};
let (s_x_l, s_x_u, s_d_l, s_d_u, hessian, jac_c, jac_d) = {
let c = cq.borrow();
(
dense_to_vec(c.curr_slack_x_l().as_ref()),
dense_to_vec(c.curr_slack_x_u().as_ref()),
dense_to_vec(c.curr_slack_s_l().as_ref()),
dense_to_vec(c.curr_slack_s_u().as_ref()),
c.curr_exact_hessian(),
c.curr_jac_c(),
c.curr_jac_d(),
)
};
let sigma_x = dense_to_vec(self.barrier_sigma_x().as_ref());
let sigma_s = dense_to_vec(self.barrier_sigma_s().as_ref());
let side =
|pos: Vec<usize>, mult: Vec<Number>, slack: Vec<Number>| BoundSide { pos, mult, slack };
Ok(ActivityData {
x_l: side(px_l, z_l, s_x_l),
x_u: side(px_u, z_u, s_x_u),
d_l: side(pd_l, v_l, s_d_l),
d_u: side(pd_u, v_u, s_d_u),
sigma_x,
sigma_s,
hessian,
jac_c,
jac_d,
})
}
fn model_maps(&self) -> Result<ModelMaps, String> {
let (_, _, nlp) = self.activity_handles();
let nl = nlp.borrow();
let n_x = self.block_dims()[0];
let n_full_g = nl.n_full_g();
let block = |p: Option<Index>| p.map(|p| p as usize);
Ok(ModelMaps {
n_full_x: nl.n_full_x() as usize,
var_x_to_full_x: (0..n_x)
.map(|i| nl.var_x_to_full_x(i as Index) as usize)
.collect(),
full_g_to_c: (0..n_full_g)
.map(|g| block(nl.full_g_to_c_block(g)))
.collect(),
full_g_to_d: (0..n_full_g)
.map(|g| block(nl.full_g_to_d_block(g)))
.collect(),
})
}
fn scaling(&self) -> EngineScaling {
let (obj, c_scale, d_scale) = self.nlp_scaling();
EngineScaling {
objective: obj,
variables: self.variable_scaling().map(|v| v.to_vec()),
equality_rows: c_scale,
inequality_rows: d_scale,
}
}
fn primal_bounds(&self) -> Result<(Vec<Number>, Vec<Number>), String> {
let (_, _, nlp) = self.activity_handles();
let nl = nlp.borrow();
let [n_x, n_s, ..] = self.block_dims();
let (mut lo, mut hi) = pounce_sens_core::boundcheck::expand_bounds(
n_x,
&nl.px_l(),
&nl.px_u(),
nl.x_l(),
nl.x_u(),
);
let (s_lo, s_hi) = pounce_sens_core::boundcheck::expand_bounds(
n_s,
&nl.pd_l(),
&nl.pd_u(),
nl.d_l(),
nl.d_u(),
);
lo.extend_from_slice(&s_lo);
hi.extend_from_slice(&s_hi);
Ok((lo, hi))
}
fn kkt_perturbations(&self) -> Option<[Number; 4]> {
Some(PdSensBacksolver::kkt_perturbations(self))
}
fn released_operator<'a>(
&'a self,
released: &[usize],
) -> Result<Box<dyn KktOperator + 'a>, String> {
let (sigma, sigma_s) = self
.released_sigmas(released)
.ok_or_else(|| "released sigma unavailable".to_string())?;
Ok(Box::new(PdReleasedOperator {
bs: self,
released: released.to_vec(),
sigma,
sigma_s,
}))
}
}
struct PdReleasedOperator<'a> {
bs: &'a PdSensBacksolver,
released: Vec<usize>,
sigma: Rc<dyn pounce_linalg::Vector>,
sigma_s: Option<Rc<dyn pounce_linalg::Vector>>,
}
impl KktOperator for PdReleasedOperator<'_> {
fn solve(&self, rhs: &[Number], lhs: &mut [Number]) -> bool {
self.bs.solve_released_prebuilt(
&self.released,
Rc::clone(&self.sigma),
self.sigma_s.as_ref().map(Rc::clone),
None,
rhs,
lhs,
false,
)
}
}
fn positions(m: &Rc<dyn Matrix>) -> Result<Vec<usize>, String> {
m.as_any()
.downcast_ref::<ExpansionMatrix>()
.map(|e| {
e.expanded_pos_indices()
.iter()
.map(|&i| i as usize)
.collect()
})
.ok_or_else(|| "activity: a bound projection is not an expansion matrix".to_string())
}
struct PdCorrectorOperator<'a> {
bs: &'a PdSensBacksolver,
op: CorrectorSigma,
_restore: RestoreCurr<'a>,
}
impl KktOperator for PdCorrectorOperator<'_> {
fn solve(&self, rhs: &[Number], lhs: &mut [Number]) -> bool {
self.bs.solve_released_prebuilt(
&[],
Rc::clone(&self.op.sigma_x),
Some(Rc::clone(&self.op.sigma_s)),
Some((&self.op.ratio_x, &self.op.ratio_s)),
rhs,
lhs,
false,
)
}
}
struct RestoreCurr<'a> {
data: &'a IpoptDataHandle,
saved: Option<IteratesVector>,
saved_w: Option<Rc<dyn pounce_linalg::SymMatrix>>,
restore_w: bool,
}
impl Drop for RestoreCurr<'_> {
fn drop(&mut self) {
if let Some(c) = self.saved.take() {
self.data.borrow_mut().set_curr(c);
}
if self.restore_w {
self.data.borrow_mut().w = self.saved_w.take();
}
}
}