use crate::ipopt_cq::IpoptCqHandle;
use crate::ipopt_data::IpoptDataHandle;
use crate::ipopt_nlp::IpoptNlp;
use pounce_common::types::{Index, Number};
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone)]
pub struct IntermediateContext {
pub data: IpoptDataHandle,
pub cq: IpoptCqHandle,
pub nlp: Rc<RefCell<dyn IpoptNlp>>,
}
pub use pounce_nlp::live_iterate::{UserIterate, UserIterateGuard, with_current_user_iterate};
thread_local! {
static CURRENT_CTX: RefCell<Option<IntermediateContext>> = const { RefCell::new(None) };
}
pub fn ipm_user_iterate(
nlp: &Rc<RefCell<dyn IpoptNlp>>,
data: &IpoptDataHandle,
cq: &IpoptCqHandle,
fixed_var_duals: bool,
) -> Option<UserIterate> {
let curr = data.borrow().curr.clone()?;
let (c, d, glx) = {
let cq = cq.borrow();
(cq.curr_c(), cq.curr_d(), cq.curr_grad_lag_x())
};
let nlp = nlp.borrow();
let n = nlp.n_full_x() as usize;
let m = nlp.n_full_g() as usize;
let x = nlp.finalize_solution_x(&*curr.x);
let mut z_l = nlp.finalize_solution_z_l(&*curr.z_l);
if z_l.is_empty() {
z_l = vec![0.0; n];
}
let mut z_u = nlp.finalize_solution_z_u(&*curr.z_u);
if z_u.is_empty() {
z_u = vec![0.0; n];
}
let mut lambda = nlp.finalize_solution_lambda(&*curr.y_c, &*curr.y_d);
if lambda.is_empty() {
lambda = vec![0.0; m];
}
if fixed_var_duals {
nlp.complete_fixed_var_bound_multipliers(&x, &lambda, &mut z_l, &mut z_u);
}
let g = nlp.pack_g_for_user(&*c, &*d);
let scaling = Some(iterate_scaling(&*nlp));
let (x_l_alg, x_u_alg) = algorithm_x_bounds(&*nlp, &x);
let grad_lag_x = user_grad_lag(&*nlp, &dense_values(&*glx));
Some(UserIterate {
x,
z_l,
z_u,
g,
lambda,
scaling,
x_l_alg,
x_u_alg,
grad_lag_x,
})
}
fn dense_values(v: &dyn pounce_linalg::Vector) -> Vec<Number> {
v.as_any()
.downcast_ref::<pounce_linalg::dense_vector::DenseVector>()
.map(|d| d.expanded_values())
.unwrap_or_default()
}
pub fn user_grad_lag(nlp: &dyn IpoptNlp, glx: &[Number]) -> Vec<Number> {
let n = nlp.n_full_x() as usize;
let obj = nlp.obj_scaling_factor();
let inv = if obj == 0.0 { 1.0 } else { 1.0 / obj };
(0..n)
.map(|j| match nlp.full_x_to_var_x(j as Index) {
Some(k) => glx.get(k as usize).map_or(Number::NAN, |g| g * inv),
None => 0.0,
})
.collect()
}
pub fn algorithm_x_bounds(nlp: &dyn IpoptNlp, x: &[Number]) -> (Vec<Number>, Vec<Number>) {
use pounce_linalg::dense_vector::DenseVectorSpace;
let n = nlp.n_full_x() as usize;
let side = |live: &dyn pounce_linalg::Vector,
upper: bool,
packed: &dyn Fn(&dyn pounce_linalg::Vector) -> Vec<Number>| {
let mut ones = DenseVectorSpace::new(live.dim()).make_new_dense();
if live.dim() > 0 {
ones.set_values(&vec![1.0; live.dim() as usize]);
}
let (vals, mask) = (packed(live), packed(&ones));
(0..n)
.map(|j| {
if nlp.full_x_to_var_x(j as Index).is_none() {
x.get(j).copied().unwrap_or(Number::NAN)
} else if mask.get(j).copied().unwrap_or(0.0) != 0.0 {
vals[j]
} else if upper {
Number::INFINITY
} else {
Number::NEG_INFINITY
}
})
.collect::<Vec<Number>>()
};
(
side(nlp.x_l(), false, &|v| nlp.pack_z_l_for_user(v)),
side(nlp.x_u(), true, &|v| nlp.pack_z_u_for_user(v)),
)
}
pub fn iterate_scaling(nlp: &dyn IpoptNlp) -> pounce_nlp::live_iterate::IterateScaling {
let n = nlp.n_full_x() as usize;
let m = nlp.n_full_g() as usize;
let x_in_alg: Vec<bool> = (0..n)
.map(|j| nlp.full_x_to_var_x(j as Index).is_some())
.collect();
let x = vec![1.0; n];
let cs = nlp.c_scale_vec();
let ds = nlp.d_scale_vec();
let g = (0..m)
.map(|i| {
if let Some(k) = nlp.full_g_to_c_block(i as Index) {
cs.as_ref()
.and_then(|v| v.get(k as usize).copied())
.unwrap_or(1.0)
} else if let Some(k) = nlp.full_g_to_d_block(i as Index) {
ds.as_ref()
.and_then(|v| v.get(k as usize).copied())
.unwrap_or(1.0)
} else {
1.0
}
})
.collect();
pounce_nlp::live_iterate::IterateScaling {
obj: nlp.obj_scaling_factor(),
x,
x_in_alg,
g,
}
}
pub struct CtxGuard {
_private: (),
}
impl CtxGuard {
pub fn install(ctx: IntermediateContext) -> Self {
CURRENT_CTX.with(|c| *c.borrow_mut() = Some(ctx));
Self { _private: () }
}
}
impl Drop for CtxGuard {
fn drop(&mut self) {
CURRENT_CTX.with(|c| *c.borrow_mut() = None);
}
}
pub fn with_current<F, R>(f: F) -> Option<R>
where
F: FnOnce(&IntermediateContext) -> R,
{
CURRENT_CTX.with(|c| c.borrow().as_ref().map(f))
}
pub fn is_active() -> bool {
CURRENT_CTX.with(|c| c.borrow().is_some()) || pounce_nlp::live_iterate::is_installed()
}