use crate::capture_tnlp::{CaptureTnlp, FinalSlot};
use pounce_algorithm::application::{ConvergedCallback, IpoptApplication};
use pounce_common::types::Number;
use pounce_nlp::return_codes::ApplicationReturnStatus;
use pounce_nlp::tnlp::TNLP;
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HessianMode {
Exact,
QuasiNewton,
}
#[derive(Clone, Copy, Debug)]
pub struct SolveQuality {
pub unscaled_dual_inf: Number,
pub unscaled_dual_scale: Number,
pub start_obj_grad_max: Number,
}
impl Default for SolveQuality {
fn default() -> Self {
Self {
unscaled_dual_inf: Number::NAN,
unscaled_dual_scale: Number::NAN,
start_obj_grad_max: Number::NAN,
}
}
}
#[derive(Clone, Debug)]
pub struct LocalOutcome {
pub converged: bool,
pub x: Vec<Number>,
pub lambda: Vec<Number>,
pub z_l: Vec<Number>,
pub z_u: Vec<Number>,
pub quality: SolveQuality,
}
pub trait LocalSolve {
fn solve(&mut self, problem: Rc<RefCell<dyn TNLP>>, mode: HessianMode) -> LocalOutcome;
fn has_exact_hessian(&self) -> bool {
true
}
}
pub struct IpoptLocalSolve<'a> {
app: &'a mut IpoptApplication,
previous_hook: Option<ConvergedCallback>,
pin_mu: bool,
caller_hessian: Option<String>,
}
impl<'a> IpoptLocalSolve<'a> {
pub fn new(app: &'a mut IpoptApplication) -> Self {
let previous_hook = app.take_on_converged();
let pin_mu = matches!(
app.options().get_string_value("mu_strategy", ""),
Ok((_, false))
);
let caller_hessian = match app.options().get_string_value("hessian_approximation", "") {
Ok((v, true)) => Some(v),
_ => None,
};
Self {
app,
previous_hook,
pin_mu,
caller_hessian,
}
}
}
impl LocalSolve for IpoptLocalSolve<'_> {
fn solve(&mut self, problem: Rc<RefCell<dyn TNLP>>, mode: HessianMode) -> LocalOutcome {
let line = match mode {
HessianMode::Exact => "hessian_approximation exact\n",
HessianMode::QuasiNewton => "hessian_approximation limited-memory\n",
};
let opts = self.app.options_mut();
let _ = opts.read_from_str(line, true);
if self.pin_mu {
match mode {
HessianMode::QuasiNewton => {
let _ = opts.set_string_value("mu_strategy", "monotone", true, true);
}
HessianMode::Exact => {
opts.unset_value("mu_strategy");
}
}
}
let slot: FinalSlot = Rc::new(RefCell::new(None));
let wrapped: Rc<RefCell<dyn TNLP>> =
Rc::new(RefCell::new(CaptureTnlp::new(problem, Rc::clone(&slot))));
let status = self.app.optimize_tnlp(wrapped);
let succeeded = matches!(
status,
ApplicationReturnStatus::SolveSucceeded
| ApplicationReturnStatus::SolvedToAcceptableLevel
);
let stats = self.app.statistics();
let quality = SolveQuality {
unscaled_dual_inf: stats.final_unscaled_dual_inf,
unscaled_dual_scale: stats.final_unscaled_dual_scale,
start_obj_grad_max: stats.start_obj_grad_max,
};
match slot.borrow_mut().take() {
Some(f) => LocalOutcome {
converged: succeeded,
x: f.x,
lambda: f.lambda,
z_l: f.z_l,
z_u: f.z_u,
quality,
},
None => LocalOutcome {
converged: false,
x: Vec::new(),
lambda: Vec::new(),
z_l: Vec::new(),
z_u: Vec::new(),
quality,
},
}
}
}
impl Drop for IpoptLocalSolve<'_> {
fn drop(&mut self) {
let opts = self.app.options_mut();
if self.pin_mu {
opts.unset_value("mu_strategy");
}
match self.caller_hessian.take() {
Some(v) => {
let _ = opts.set_string_value("hessian_approximation", &v, true, true);
}
None => {
opts.unset_value("hessian_approximation");
}
}
if let Some(cb) = self.previous_hook.take() {
self.app.set_on_converged(cb);
}
}
}