use pounce_algorithm::application::IpoptApplication;
use pounce_common::types::Number;
use pounce_nlp::return_codes::ApplicationReturnStatus;
use pounce_nlp::tnlp::{
BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, Solution, SparsityRequest, StartingPoint,
TNLP,
};
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Default)]
struct CallbackQp {
h_calls: usize,
jac_calls: usize,
final_obj: Option<Number>,
final_x: Option<Vec<Number>>,
}
impl TNLP for CallbackQp {
fn get_nlp_info(&mut self) -> Option<NlpInfo> {
Some(NlpInfo {
n: 3,
m: 2,
nnz_jac_g: 5,
nnz_h_lag: 3,
index_style: IndexStyle::C,
})
}
fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
b.x_l.copy_from_slice(&[-10.0; 3]);
b.x_u.copy_from_slice(&[10.0; 3]);
b.g_l.copy_from_slice(&[3.0, -2.0e19]);
b.g_u.copy_from_slice(&[3.0, 1.0]);
true
}
fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
sp.x.copy_from_slice(&[0.0, 0.0, 0.0]);
true
}
fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
Some(x[0] * x[0] + 2.0 * x[1] * x[1] + x[2] * x[2])
}
fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
g[0] = 2.0 * x[0];
g[1] = 4.0 * x[1];
g[2] = 2.0 * x[2];
true
}
fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
g[0] = x[0] + x[1] + x[2];
g[1] = x[0] - x[1];
true
}
fn eval_jac_g(&mut self, _x: Option<&[Number]>, _: bool, mode: SparsityRequest<'_>) -> bool {
match mode {
SparsityRequest::Structure { irow, jcol } => {
irow.copy_from_slice(&[0, 0, 0, 1, 1]);
jcol.copy_from_slice(&[0, 1, 2, 0, 1]);
}
SparsityRequest::Values { values } => {
self.jac_calls += 1;
values.copy_from_slice(&[1.0, 1.0, 1.0, 1.0, -1.0]);
}
}
true
}
fn eval_h(
&mut self,
_x: Option<&[Number]>,
_: bool,
obj_factor: Number,
_lambda: Option<&[Number]>,
_: bool,
mode: SparsityRequest<'_>,
) -> bool {
match mode {
SparsityRequest::Structure { irow, jcol } => {
irow.copy_from_slice(&[0, 1, 2]);
jcol.copy_from_slice(&[0, 1, 2]);
}
SparsityRequest::Values { values } => {
self.h_calls += 1;
values.copy_from_slice(&[2.0 * obj_factor, 4.0 * obj_factor, 2.0 * obj_factor]);
}
}
true
}
fn finalize_solution(&mut self, sol: Solution<'_>, _: &IpoptData, _: &IpoptCq) {
self.final_obj = Some(sol.obj_value);
self.final_x = Some(sol.x.to_vec());
}
}
fn solve(hints: &[(&str, &str)]) -> (ApplicationReturnStatus, usize, usize, Number, Vec<Number>) {
let tnlp = Rc::new(RefCell::new(CallbackQp::default()));
let mut app = IpoptApplication::new();
app.options_mut()
.set_integer_value("print_level", 0, true, false)
.expect("print_level");
for (k, v) in hints {
app.options_mut()
.set_string_value(k, v, true, false)
.unwrap_or_else(|_| panic!("set {k}"));
}
let status = app.optimize_tnlp(Rc::clone(&tnlp) as Rc<RefCell<dyn TNLP>>);
let t = tnlp.borrow();
(
status,
t.h_calls,
t.jac_calls,
t.final_obj.expect("objective"),
t.final_x.clone().expect("x"),
)
}
#[test]
fn without_a_hint_a_callback_model_is_re_evaluated_every_iterate() {
let (status, h, jac, _, _) = solve(&[]);
assert_eq!(status, ApplicationReturnStatus::SolveSucceeded);
assert!(h > 1, "expected repeated Hessian evaluations, got {h}");
assert!(jac > 1, "expected repeated Jacobian evaluations, got {jac}");
}
#[test]
fn an_unprovable_hint_is_honoured_on_trust() {
let (base_status, base_h, base_jac, base_obj, base_x) = solve(&[]);
let (status, h, jac, obj, x) = solve(&[
("hessian_constant", "yes"),
("jac_c_constant", "yes"),
("jac_d_constant", "yes"),
]);
assert_eq!(status, base_status);
assert_eq!(h, 1, "`hessian_constant=yes` must stop the re-evaluation");
assert_eq!(jac, 2, "`jac_*_constant=yes` must stop the re-evaluation");
assert!(
base_h > h && base_jac > jac,
"hint made no difference: {base_h}/{base_jac} -> {h}/{jac}"
);
assert_eq!(obj.to_bits(), base_obj.to_bits(), "objective moved");
for (a, b) in x.iter().zip(base_x.iter()) {
assert_eq!(a.to_bits(), b.to_bits(), "solution moved");
}
}
#[test]
fn writing_the_default_explicitly_asserts_nothing() {
let (_, h, jac, _, _) = solve(&[("hessian_constant", "no"), ("jac_c_constant", "no")]);
assert!(h > 1, "an explicit `no` must not enable reuse, got {h}");
assert!(jac > 1, "an explicit `no` must not enable reuse, got {jac}");
}