use pounce_algorithm::application::IpoptApplication;
use pounce_common::types::{Index, 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;
struct Quadratic;
impl TNLP for Quadratic {
fn get_nlp_info(&mut self) -> Option<NlpInfo> {
Some(NlpInfo {
n: 1,
m: 0,
nnz_jac_g: 0,
nnz_h_lag: 1,
index_style: IndexStyle::C,
})
}
fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
b.x_l.copy_from_slice(&[-2.0e19]);
b.x_u.copy_from_slice(&[2.0e19]);
true
}
fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
sp.x.copy_from_slice(&[0.0]);
true
}
fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
Some((x[0] - 1.0) * (x[0] - 1.0))
}
fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
g[0] = 2.0 * (x[0] - 1.0);
true
}
fn eval_g(&mut self, _x: &[Number], _new_x: bool, _g: &mut [Number]) -> bool {
true
}
fn eval_jac_g(
&mut self,
_x: Option<&[Number]>,
_new_x: bool,
_mode: SparsityRequest<'_>,
) -> bool {
true
}
fn eval_h(
&mut self,
_x: Option<&[Number]>,
_new_x: bool,
of: Number,
_lambda: Option<&[Number]>,
_new_lambda: bool,
mode: SparsityRequest<'_>,
) -> bool {
match mode {
SparsityRequest::Structure { irow, jcol } => {
let z: [Index; 1] = [0];
irow.copy_from_slice(&z);
jcol.copy_from_slice(&z);
}
SparsityRequest::Values { values, .. } => values[0] = of * 2.0,
}
true
}
fn finalize_solution(&mut self, _sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {}
}
fn solve_with(options: &str) -> ApplicationReturnStatus {
let mut app = IpoptApplication::new();
app.initialize().expect("registry initializes");
app.initialize_with_options_str(&format!("print_level 0\n{options}"))
.unwrap_or_else(|e| panic!("options rejected at set time: {e:?}\n{options}"));
let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Quadratic));
app.optimize_tnlp(tnlp)
}
fn solved(s: ApplicationReturnStatus) -> bool {
matches!(
s,
ApplicationReturnStatus::SolveSucceeded | ApplicationReturnStatus::SolvedToAcceptableLevel
)
}
#[test]
fn an_explicit_pivtolmax_below_pivtol_is_refused() {
let status = solve_with("ma57_pivtol 0.5\nma57_pivtolmax 1e-9\n");
assert_eq!(
status,
ApplicationReturnStatus::InvalidOption,
"an explicit ma57_pivtolmax under ma57_pivtol must be refused, not lifted"
);
}
#[test]
fn the_resto_prefixed_pair_is_refused_too() {
let status = solve_with("resto.ma57_pivtol 0.5\nresto.ma57_pivtolmax 1e-9\n");
assert_eq!(
status,
ApplicationReturnStatus::InvalidOption,
"the `resto.` prefix has its own MA57 backend and its own pair to keep consistent"
);
}
#[test]
fn an_unset_pivtolmax_is_lifted_not_refused() {
let status = solve_with("ma57_pivtol 0.5\n");
assert!(
solved(status),
"raising ma57_pivtol above the ma57_pivtolmax *default* must lift the default, \
not refuse the solve — got {status:?}"
);
}
#[test]
fn an_explicit_pivtolmax_equal_to_pivtol_is_accepted() {
let status = solve_with("ma57_pivtol 0.5\nma57_pivtolmax 0.5\n");
assert!(
solved(status),
"pivtolmax == pivtol is legal — got {status:?}"
);
}
#[test]
fn an_explicit_pivtolmax_above_pivtol_is_accepted() {
let status = solve_with("ma57_pivtol 1e-6\nma57_pivtolmax 0.5\n");
assert!(solved(status), "status = {status:?}");
}
#[test]
fn a_solve_with_no_ma57_options_is_unaffected() {
let status = solve_with("");
assert!(solved(status), "status = {status:?}");
}