use pounce_algorithm::application::IpoptApplication;
use pounce_common::types::Number;
use pounce_nlp::return_codes::ApplicationReturnStatus;
use pounce_nlp::tnlp::{
BoundsInfo, IndexStyle, IpoptCq, IpoptData, Linearity, NlpInfo, Solution, SparsityRequest,
StartingPoint, TNLP,
};
use std::cell::RefCell;
use std::rc::Rc;
const SCALES: [i32; 10] = [-12, -10, -8, -6, -4, -2, 0, 2, 4, 6];
struct ProductAndSum {
scale: Number,
sum: Number,
start: [Number; 2],
}
impl ProductAndSum {
fn contradictory(scale: Number) -> Self {
Self {
scale,
sum: 0.5,
start: [1.0, 1.0],
}
}
fn feasible(scale: Number) -> Self {
Self {
scale,
sum: 2.5,
start: [0.5, -0.5],
}
}
}
impl TNLP for ProductAndSum {
fn get_nlp_info(&mut self) -> Option<NlpInfo> {
Some(NlpInfo {
n: 2,
m: 2,
nnz_jac_g: 4,
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, -10.0]);
b.x_u.copy_from_slice(&[10.0, 10.0]);
b.g_l.copy_from_slice(&[self.scale, self.scale * self.sum]);
b.g_u.copy_from_slice(&[self.scale, self.scale * self.sum]);
true
}
fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
sp.x.copy_from_slice(&self.start);
true
}
fn get_constraints_linearity(&mut self, types: &mut [Linearity]) -> bool {
types.copy_from_slice(&[Linearity::NonLinear, Linearity::Linear]);
true
}
fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
Some(x[0] * x[0] + x[1] * x[1])
}
fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, grad: &mut [Number]) -> bool {
grad[0] = 2.0 * x[0];
grad[1] = 2.0 * x[1];
true
}
fn eval_g(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
g[0] = self.scale * x[0] * x[1];
g[1] = self.scale * (x[0] + x[1]);
true
}
fn eval_jac_g(
&mut self,
x: Option<&[Number]>,
_new_x: bool,
mode: SparsityRequest<'_>,
) -> bool {
match mode {
SparsityRequest::Structure { irow, jcol } => {
irow.copy_from_slice(&[0, 0, 1, 1]);
jcol.copy_from_slice(&[0, 1, 0, 1]);
}
SparsityRequest::Values { values } => {
let x = x.expect("jacobian values need x");
values[0] = self.scale * x[1];
values[1] = self.scale * x[0];
values[2] = self.scale;
values[3] = self.scale;
}
}
true
}
fn eval_h(
&mut self,
_x: Option<&[Number]>,
_new_x: bool,
obj_factor: Number,
lambda: Option<&[Number]>,
_new_lambda: bool,
mode: SparsityRequest<'_>,
) -> bool {
match mode {
SparsityRequest::Structure { irow, jcol } => {
irow.copy_from_slice(&[0, 1, 1]);
jcol.copy_from_slice(&[0, 0, 1]);
}
SparsityRequest::Values { values } => {
let l0 = lambda.map(|l| l[0]).unwrap_or(0.0);
values[0] = 2.0 * obj_factor;
values[1] = l0 * self.scale;
values[2] = 2.0 * obj_factor;
}
}
true
}
fn finalize_solution(&mut self, _sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {}
}
fn solve(problem: ProductAndSum) -> ApplicationReturnStatus {
let mut app = IpoptApplication::new();
app.options_mut()
.set_integer_value("print_level", 0, true, false)
.unwrap();
app.initialize().unwrap();
let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(problem));
app.optimize_tnlp(tnlp)
}
fn is_success(status: ApplicationReturnStatus) -> bool {
matches!(
status,
ApplicationReturnStatus::SolveSucceeded | ApplicationReturnStatus::SolvedToAcceptableLevel
)
}
#[test]
fn contradictory_equalities_are_never_reported_solved() {
for k in SCALES {
let status = solve(ProductAndSum::contradictory(10.0_f64.powi(k)));
assert!(
!is_success(status),
"row scale 1e{k}: `x*y == 1, x + y == 0.5` has no real solution, \
yet the solve reported {status:?}"
);
}
}
#[test]
fn contradictory_equalities_are_diagnosed_at_small_row_scales() {
for k in [-12, -10, -8, -6] {
assert_eq!(
solve(ProductAndSum::contradictory(10.0_f64.powi(k))),
ApplicationReturnStatus::InfeasibleProblemDetected,
"row scale 1e{k}"
);
}
}
#[test]
fn feasible_twin_solves_at_every_scale() {
for k in SCALES {
let status = solve(ProductAndSum::feasible(10.0_f64.powi(k)));
assert!(
is_success(status),
"row scale 1e{k}: `x*y == 1, x + y == 2.5` has the solution \
(2, 0.5); got {status:?}"
);
}
}