use crate::backsolver::SensBacksolver;
use crate::boundcheck::clamp_with_nlp;
use crate::schur_data::IndexSchurData;
use crate::sens_app::{SensApplication, SensOptions};
use crate::PdSensBacksolver;
use pounce_algorithm::IpoptApplication;
use pounce_common::types::{Index, Number};
use pounce_nlp::return_codes::ApplicationReturnStatus;
use pounce_nlp::TNLP;
use std::cell::RefCell;
use std::rc::Rc;
pub struct SensSolve {
pin_constraint_indices: Vec<Index>,
deltas: Option<Vec<Number>>,
compute_reduced_hessian: bool,
rh_eigendecomp: bool,
obj_scal: Number,
boundcheck_eps: Option<Number>,
}
#[derive(Debug, Clone)]
pub struct SensResult {
pub status: ApplicationReturnStatus,
pub x: Option<Vec<Number>>,
pub obj_val: Option<Number>,
pub dx: Option<Vec<Number>>,
pub dx_full: Option<Vec<Number>>,
pub reduced_hessian: Option<Vec<Number>>,
pub reduced_hessian_eigenvalues: Option<Vec<Number>>,
pub reduced_hessian_eigenvectors: Option<Vec<Number>>,
pub mult_g: Option<Vec<Number>>,
pub mult_x_l: Option<Vec<Number>>,
pub mult_x_u: Option<Vec<Number>>,
pub g: Option<Vec<Number>>,
}
impl SensSolve {
pub fn new(pin_constraint_indices: Vec<Index>) -> Self {
Self {
pin_constraint_indices,
deltas: None,
compute_reduced_hessian: false,
rh_eigendecomp: false,
obj_scal: 1.0,
boundcheck_eps: None,
}
}
pub fn with_deltas(mut self, deltas: Vec<Number>) -> Self {
self.deltas = Some(deltas);
self
}
pub fn with_reduced_hessian(mut self) -> Self {
self.compute_reduced_hessian = true;
self
}
pub fn with_reduced_hessian_eigen(mut self) -> Self {
self.compute_reduced_hessian = true;
self.rh_eigendecomp = true;
self
}
pub fn with_obj_scal(mut self, obj_scal: Number) -> Self {
self.obj_scal = obj_scal;
self
}
pub fn with_boundcheck(mut self, eps: Number) -> Self {
self.boundcheck_eps = Some(eps);
self
}
pub fn run(self, app: &mut IpoptApplication, tnlp: Rc<RefCell<dyn TNLP>>) -> SensResult {
let n_params = self.pin_constraint_indices.len();
if let Some(d) = &self.deltas {
assert_eq!(
d.len(),
n_params,
"deltas.len() ({}) must equal pin_constraint_indices.len() ({})",
d.len(),
n_params,
);
}
let want_dx = self.deltas.is_some();
let want_rh = self.compute_reduced_hessian;
let want_eigen = self.rh_eigendecomp;
let pin_indices = self.pin_constraint_indices.clone();
let deltas = self.deltas.clone();
let obj_scal = self.obj_scal;
let boundcheck_eps = self.boundcheck_eps;
let outbox: Rc<RefCell<CallbackOut>> = Rc::new(RefCell::new(CallbackOut::default()));
let outbox_cb = Rc::clone(&outbox);
app.set_on_converged(Box::new(move |data, cq, nlp, pd| {
let curr = match data.borrow().curr.clone() {
Some(c) => c,
None => {
outbox_cb.borrow_mut().error = Some("no current iterate at convergence".into());
return;
}
};
outbox_cb.borrow_mut().x = Some(dense_to_vec(&*curr.x));
outbox_cb.borrow_mut().obj_val = Some(cq.borrow_mut().curr_f());
let g_curr = cq.borrow_mut().curr_c();
let d_curr = cq.borrow_mut().curr_d();
{
let nlp_borrow = nlp.borrow();
let lambda = nlp_borrow.pack_lambda_for_user(&*curr.y_c, &*curr.y_d);
if !lambda.is_empty() {
outbox_cb.borrow_mut().mult_g = Some(lambda);
}
let z_l = nlp_borrow.pack_z_l_for_user(&*curr.z_l);
if !z_l.is_empty() {
outbox_cb.borrow_mut().mult_x_l = Some(z_l);
}
let z_u = nlp_borrow.pack_z_u_for_user(&*curr.z_u);
if !z_u.is_empty() {
outbox_cb.borrow_mut().mult_x_u = Some(z_u);
}
let g_user = nlp_borrow.pack_g_for_user(&*g_curr, &*d_curr);
if !g_user.is_empty() {
outbox_cb.borrow_mut().g = Some(g_user);
}
}
let n_x = curr.x.dim() as usize;
let n_s = curr.s.dim() as usize;
let y_c_offset = (n_x + n_s) as Index;
let param_rows: Vec<Index> = pin_indices.iter().map(|&i| y_c_offset + i).collect();
let signs = vec![1; n_params];
let backsolver = match PdSensBacksolver::new(data, cq, nlp, Rc::clone(&pd)) {
Ok(b) => b,
Err(e) => {
outbox_cb.borrow_mut().error =
Some(format!("PdSensBacksolver::new failed: {e:?}"));
return;
}
};
let n_full = backsolver.dim();
let a_data = match IndexSchurData::from_parts(param_rows, signs) {
Ok(a) => a,
Err(e) => {
outbox_cb.borrow_mut().error =
Some(format!("IndexSchurData::from_parts failed: {e:?}"));
return;
}
};
let opts = SensOptions {
run_sens: want_dx,
compute_red_hessian: want_rh,
rh_eigendecomp: want_eigen,
obj_scal,
..SensOptions::default()
};
let mut sens_app = SensApplication::new(a_data, backsolver, opts);
if let Some(d) = &deltas {
let mut dx_full = vec![0.0; n_full];
if !sens_app.parametric_step(d, &mut dx_full) {
outbox_cb.borrow_mut().error =
Some("SensApplication::parametric_step failed".into());
return;
}
if let Some(eps) = boundcheck_eps {
let x_curr = dense_to_vec(&*curr.x);
let mut dx_primal = dx_full[..n_x].to_vec();
let _ = clamp_with_nlp(&*nlp.borrow(), &x_curr, &mut dx_primal, eps);
dx_full[..n_x].copy_from_slice(&dx_primal);
}
let dx_primal = dx_full[..n_x].to_vec();
outbox_cb.borrow_mut().dx = Some(dx_primal);
outbox_cb.borrow_mut().dx_full = Some(dx_full);
}
if want_rh {
let mut hr = vec![0.0; n_params * n_params];
if want_eigen {
let mut w = vec![0.0; n_params];
let mut v = vec![0.0; n_params * n_params];
if !sens_app.compute_reduced_hessian_eigen(&mut hr, &mut w, &mut v) {
outbox_cb.borrow_mut().error =
Some("SensApplication::compute_reduced_hessian_eigen failed".into());
return;
}
outbox_cb.borrow_mut().reduced_hessian = Some(hr);
outbox_cb.borrow_mut().reduced_hessian_eigenvalues = Some(w);
outbox_cb.borrow_mut().reduced_hessian_eigenvectors = Some(v);
} else if !sens_app.compute_reduced_hessian(&mut hr) {
outbox_cb.borrow_mut().error =
Some("SensApplication::compute_reduced_hessian failed".into());
return;
} else {
outbox_cb.borrow_mut().reduced_hessian = Some(hr);
}
}
}));
let status = app.optimize_tnlp(tnlp);
let out = outbox.borrow();
SensResult {
status,
x: out.x.clone(),
obj_val: out.obj_val,
dx: out.dx.clone(),
dx_full: out.dx_full.clone(),
reduced_hessian: out.reduced_hessian.clone(),
reduced_hessian_eigenvalues: out.reduced_hessian_eigenvalues.clone(),
reduced_hessian_eigenvectors: out.reduced_hessian_eigenvectors.clone(),
mult_g: out.mult_g.clone(),
mult_x_l: out.mult_x_l.clone(),
mult_x_u: out.mult_x_u.clone(),
g: out.g.clone(),
}
}
}
#[derive(Default)]
struct CallbackOut {
x: Option<Vec<Number>>,
obj_val: Option<Number>,
dx: Option<Vec<Number>>,
dx_full: Option<Vec<Number>>,
reduced_hessian: Option<Vec<Number>>,
reduced_hessian_eigenvalues: Option<Vec<Number>>,
reduced_hessian_eigenvectors: Option<Vec<Number>>,
mult_g: Option<Vec<Number>>,
mult_x_l: Option<Vec<Number>>,
mult_x_u: Option<Vec<Number>>,
g: Option<Vec<Number>>,
#[allow(dead_code)]
error: Option<String>,
}
fn dense_to_vec(v: &dyn pounce_linalg::Vector) -> Vec<Number> {
match v
.as_any()
.downcast_ref::<pounce_linalg::dense_vector::DenseVector>()
{
Some(d) => d.values().to_vec(),
None => vec![0.0; v.dim() as usize],
}
}