use std::cell::RefCell;
use std::rc::Rc;
use pounce_common::types::Number;
use pounce_nlp::alg_types::SolverReturn;
use pounce_nlp::live_iterate::{self, IterateScaling, UserIterate};
use pounce_nlp::tnlp::{
BoundsInfo, IpoptCq, IpoptData, NlpInfo, Solution, SparsityRequest, StartingPoint, TNLP,
};
#[derive(Clone, Debug, Default)]
pub(crate) struct EvalRecord {
on: Option<bool>,
grad_f: Option<(Vec<Number>, Vec<Number>)>,
g: Option<(Vec<Number>, Vec<Number>)>,
jac: Option<(Vec<Number>, Vec<Number>)>,
}
impl EvalRecord {
pub(crate) fn enabled(&mut self, inner: &Rc<RefCell<dyn TNLP>>) -> bool {
*self
.on
.get_or_insert_with(|| inner.borrow().wants_current_iterate())
}
pub(crate) fn grad_f(&mut self, x: &[Number], v: &[Number]) {
self.grad_f = Some((x.to_vec(), v.to_vec()));
}
pub(crate) fn g(&mut self, x: &[Number], v: &[Number]) {
self.g = Some((x.to_vec(), v.to_vec()));
}
pub(crate) fn jac(&mut self, x: &[Number], v: &[Number]) {
self.jac = Some((x.to_vec(), v.to_vec()));
}
}
fn serve(rec: &Option<(Vec<Number>, Vec<Number>)>, x: &[Number], out: &mut [Number]) -> bool {
match rec {
Some((at, v)) if at.as_slice() == x && v.len() == out.len() => {
out.copy_from_slice(v);
true
}
_ => false,
}
}
struct Capture {
inner: Rc<RefCell<dyn TNLP>>,
captured: Option<UserIterate>,
record: EvalRecord,
}
impl TNLP for Capture {
fn get_nlp_info(&mut self) -> Option<NlpInfo> {
self.inner.borrow_mut().get_nlp_info()
}
fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
self.inner.borrow_mut().get_bounds_info(b)
}
fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
self.inner.borrow_mut().get_starting_point(sp)
}
fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
self.inner.borrow_mut().eval_f(x, new_x)
}
fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
if serve(&self.record.grad_f, x, g) {
return true;
}
let _ = new_x;
let ok = self.inner.borrow_mut().eval_grad_f(x, true, g);
if ok {
self.record.grad_f(x, g);
}
ok
}
fn eval_g(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
if serve(&self.record.g, x, g) {
return true;
}
let ok = self.inner.borrow_mut().eval_g(x, true, g);
if ok {
self.record.g(x, g);
}
ok
}
fn eval_jac_g(&mut self, x: Option<&[Number]>, new_x: bool, mode: SparsityRequest<'_>) -> bool {
match (x, mode) {
(Some(x), SparsityRequest::Values { values }) => {
if serve(&self.record.jac, x, values) {
return true;
}
let ok = self.inner.borrow_mut().eval_jac_g(
Some(x),
true,
SparsityRequest::Values { values },
);
if ok {
self.record.jac(x, values);
}
ok
}
(x, mode) => self.inner.borrow_mut().eval_jac_g(x, new_x, mode),
}
}
fn eval_h(
&mut self,
x: Option<&[Number]>,
new_x: bool,
obj_factor: Number,
lambda: Option<&[Number]>,
new_lambda: bool,
mode: SparsityRequest<'_>,
) -> bool {
self.inner
.borrow_mut()
.eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
}
fn finalize_solution(&mut self, sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {
self.captured = Some(UserIterate {
x: sol.x.to_vec(),
z_l: sol.z_l.to_vec(),
z_u: sol.z_u.to_vec(),
g: sol.g.to_vec(),
lambda: sol.lambda.to_vec(),
..Default::default()
});
}
}
pub(crate) struct LiftMaps<'a> {
pub cols: &'a [usize],
pub rows: &'a [usize],
pub declared_x_l: &'a [Number],
pub declared_x_u: &'a [Number],
pub record: EvalRecord,
}
fn lift_alg_bounds(
outer: &[Number],
maps: &LiftMaps<'_>,
declared: &[Number],
n_in: usize,
) -> Vec<Number> {
if outer.len() != maps.cols.len() {
return Vec::new();
}
let mut out: Vec<Number> = (0..n_in)
.map(|j| declared.get(j).copied().unwrap_or(Number::NAN))
.collect();
for (k, &j) in maps.cols.iter().enumerate() {
if j < n_in {
out[j] = outer[k];
}
}
out
}
fn lift_scaling(
s: &IterateScaling,
maps: &LiftMaps<'_>,
n_in: usize,
m_in: usize,
) -> Option<IterateScaling> {
if s.x.len() != maps.cols.len() || s.g.len() != maps.rows.len() {
return None;
}
let mut out = IterateScaling {
obj: s.obj,
x: vec![1.0; n_in],
x_in_alg: vec![false; n_in],
g: vec![1.0; m_in],
};
for (k, &j) in maps.cols.iter().enumerate() {
if j < n_in {
out.x[j] = s.x[k];
out.x_in_alg[j] = s.x_in_alg.get(k).copied().unwrap_or(true);
}
}
for (k, &i) in maps.rows.iter().enumerate() {
if i < m_in {
out.g[i] = s.g[k];
}
}
Some(out)
}
fn inner_lagrangian_gradient(c: &mut Capture, it: &UserIterate) -> Option<Vec<Number>> {
let info = c.get_nlp_info()?;
let (n, nnz) = (info.n as usize, info.nnz_jac_g as usize);
if it.x.len() != n {
return None;
}
let mut grad = vec![0.0; n];
if !c.eval_grad_f(&it.x, true, &mut grad) {
return None;
}
let (mut irow, mut jcol, mut vals) = (vec![0; nnz], vec![0; nnz], vec![0.0; nnz]);
if nnz > 0
&& (!c.eval_jac_g(
None,
false,
SparsityRequest::Structure {
irow: &mut irow,
jcol: &mut jcol,
},
) || !c.eval_jac_g(
Some(&it.x),
false,
SparsityRequest::Values { values: &mut vals },
))
{
return None;
}
let off = match info.index_style {
pounce_nlp::tnlp::IndexStyle::Fortran => 1,
pounce_nlp::tnlp::IndexStyle::C => 0,
};
let idx = |v: &[pounce_common::types::Index]| -> Vec<usize> {
v.iter()
.map(|&k| usize::try_from(k - off).unwrap_or(usize::MAX))
.collect()
};
Some(live_iterate::lagrangian_gradient(
it,
&grad,
&idx(&irow),
&idx(&jcol),
&vals,
))
}
pub(crate) fn lift<T: TNLP>(
this: &mut T,
slot: fn(&mut T) -> &mut Rc<RefCell<dyn TNLP>>,
outer_dims: (usize, usize),
maps: LiftMaps<'_>,
) -> Option<UserIterate> {
let it = live_iterate::current_user_iterate()?;
let (n, m) = outer_dims;
if it.x.len() != n
|| it.z_l.len() != n
|| it.z_u.len() != n
|| it.g.len() != m
|| it.lambda.len() != m
{
return None;
}
let original = Rc::clone(slot(this));
let capture = Rc::new(RefCell::new(Capture {
inner: Rc::clone(&original),
captured: None,
record: maps.record.clone(),
}));
let as_dyn: Rc<RefCell<dyn TNLP>> = capture.clone();
*slot(this) = as_dyn;
this.finalize_solution(
Solution {
status: SolverReturn::Success,
x: &it.x,
z_l: &it.z_l,
z_u: &it.z_u,
g: &it.g,
lambda: &it.lambda,
obj_value: Number::NAN,
},
&IpoptData::default(),
&IpoptCq::default(),
);
*slot(this) = original;
let mut lifted = capture.borrow_mut().captured.take()?;
lifted.grad_lag_x =
inner_lagrangian_gradient(&mut capture.borrow_mut(), &lifted).unwrap_or_default();
let (n_in, m_in) = (lifted.x.len(), lifted.g.len());
lifted.scaling = it
.scaling
.as_ref()
.and_then(|s| lift_scaling(s, &maps, n_in, m_in));
lifted.x_l_alg = lift_alg_bounds(&it.x_l_alg, &maps, maps.declared_x_l, n_in);
lifted.x_u_alg = lift_alg_bounds(&it.x_u_alg, &maps, maps.declared_x_u, n_in);
Some(lifted)
}