use crate::backsolver::SensBacksolver;
use crate::boundcheck::clamp_with_nlp;
use crate::schur_data::IndexSchurData;
use crate::sens_app::{SensApplication, SensOptions};
use crate::vec_util::dense_to_vec;
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 error: Option<String>,
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_scaled: Option<Vec<Number>>,
pub obj_scaling_factor: Option<Number>,
pub pin_g_scaling: Option<Vec<Number>>,
pub kkt_perturbations: Option<[Number; 4]>,
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.finalize_solution_lambda(&*curr.y_c, &*curr.y_d);
if !lambda.is_empty() {
outbox_cb.borrow_mut().mult_g = Some(lambda);
}
let z_l = nlp_borrow.finalize_solution_z_l(&*curr.z_l);
if !z_l.is_empty() {
outbox_cb.borrow_mut().mult_x_l = Some(z_l);
}
let z_u = nlp_borrow.finalize_solution_z_u(&*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 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 (param_rows, pin_scales) = match backsolver.pin_rows_and_c_scales(&pin_indices) {
Ok(rs) => rs,
Err(e) => {
outbox_cb.borrow_mut().error = Some(e);
return;
}
};
let df = backsolver.obj_scaling_factor();
outbox_cb.borrow_mut().obj_scaling_factor = Some(df);
outbox_cb.borrow_mut().pin_g_scaling = Some(pin_scales.clone());
outbox_cb.borrow_mut().kkt_perturbations = Some(backsolver.kkt_perturbations());
let signs = vec![1; n_params];
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_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;
}
let mut hr_scaled = hr.clone();
crate::reduced_hessian::scale_to_solver_space(&mut hr_scaled, df, &pin_scales);
outbox_cb.borrow_mut().reduced_hessian = Some(hr);
outbox_cb.borrow_mut().reduced_hessian_scaled = Some(hr_scaled);
}
}));
let status = app.optimize_tnlp(tnlp);
let out = outbox.borrow();
SensResult {
status,
error: out.error.clone(),
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_scaled: out.reduced_hessian_scaled.clone(),
obj_scaling_factor: out.obj_scaling_factor,
pin_g_scaling: out.pin_g_scaling.clone(),
kkt_perturbations: out.kkt_perturbations,
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_scaled: Option<Vec<Number>>,
obj_scaling_factor: Option<Number>,
pin_g_scaling: Option<Vec<Number>>,
kkt_perturbations: Option<[Number; 4]>,
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>>,
error: Option<String>,
}