use pounce_cinterface::*;
use std::ffi::{CString, c_void};
const N: usize = 6;
const M: usize = 2;
const NONLIN: [Index; 2] = [0, 1];
unsafe extern "C" fn ev_f(
_n: Index,
x: *const Number,
_new_x: Bool,
obj: *mut Number,
_u: *mut c_void,
) -> Bool {
unsafe {
let x = std::slice::from_raw_parts(x, N);
*obj = (1.0 - x[0]).powi(2)
+ 100.0 * (x[1] - x[0] * x[0]).powi(2)
+ 2.0 * x[2]
+ 3.0 * x[3]
+ x[4]
+ x[5];
1
}
}
unsafe extern "C" fn ev_grad_f(
_n: Index,
x: *const Number,
_new_x: Bool,
grad: *mut Number,
_u: *mut c_void,
) -> Bool {
unsafe {
let x = std::slice::from_raw_parts(x, N);
let g = std::slice::from_raw_parts_mut(grad, N);
g[0] = -2.0 * (1.0 - x[0]) - 400.0 * x[0] * (x[1] - x[0] * x[0]);
g[1] = 200.0 * (x[1] - x[0] * x[0]);
g[2] = 2.0;
g[3] = 3.0;
g[4] = 1.0;
g[5] = 1.0;
1
}
}
unsafe extern "C" fn ev_g(
_n: Index,
x: *const Number,
_new_x: Bool,
_m: Index,
gout: *mut Number,
_u: *mut c_void,
) -> Bool {
unsafe {
let x = std::slice::from_raw_parts(x, N);
let g = std::slice::from_raw_parts_mut(gout, M);
g[0] = x[0] + x[1] + x[2] + x[3];
g[1] = x[0] * x[0] + x[1] * x[1] - x[4] - x[5];
1
}
}
unsafe extern "C" fn ev_jac_g(
_n: Index,
x: *const Number,
_new_x: Bool,
_m: Index,
_nele_jac: Index,
i_row: *mut Index,
j_col: *mut Index,
values: *mut Number,
_u: *mut c_void,
) -> Bool {
unsafe {
if values.is_null() {
let rows = std::slice::from_raw_parts_mut(i_row, M * N);
let cols = std::slice::from_raw_parts_mut(j_col, M * N);
let mut k = 0;
for i in 0..M {
for j in 0..N {
rows[k] = i as Index;
cols[k] = j as Index;
k += 1;
}
}
return 1;
}
let x = std::slice::from_raw_parts(x, N);
let v = std::slice::from_raw_parts_mut(values, M * N);
v[..N].copy_from_slice(&[1.0, 1.0, 1.0, 1.0, 0.0, 0.0]);
v[N..].copy_from_slice(&[2.0 * x[0], 2.0 * x[1], 0.0, 0.0, -1.0, -1.0]);
1
}
}
#[allow(clippy::too_many_arguments)]
unsafe extern "C" fn ev_h(
_n: Index,
x: *const Number,
_new_x: Bool,
obj_factor: Number,
_m: Index,
lambda: *const Number,
_new_lambda: Bool,
_nele_hess: Index,
i_row: *mut Index,
j_col: *mut Index,
values: *mut Number,
_u: *mut c_void,
) -> Bool {
const NNZ: usize = 3;
unsafe {
if values.is_null() {
let rows = std::slice::from_raw_parts_mut(i_row, NNZ);
let cols = std::slice::from_raw_parts_mut(j_col, NNZ);
rows.copy_from_slice(&[0, 1, 1]);
cols.copy_from_slice(&[0, 0, 1]);
return 1;
}
let x = std::slice::from_raw_parts(x, N);
let lam = std::slice::from_raw_parts(lambda, M);
let v = std::slice::from_raw_parts_mut(values, NNZ);
v[0] = obj_factor * (2.0 - 400.0 * (x[1] - 3.0 * x[0] * x[0])) + lam[1] * 2.0;
v[1] = obj_factor * (-400.0 * x[0]);
v[2] = obj_factor * 200.0 + lam[1] * 2.0;
1
}
}
struct Solved {
status: i32,
x: [Number; N],
obj: Number,
iters: Index,
}
fn solve(limited_memory: bool, mask: Option<&[Index]>) -> Solved {
unsafe {
let x_l = [-5.0 as Number; N];
let x_u = [5.0 as Number; N];
let g_l = [4.0 as Number, -2.0e19];
let g_u = [4.0 as Number, 1.0];
let prob = CreateIpoptProblem(
N as Index,
x_l.as_ptr(),
x_u.as_ptr(),
M as Index,
g_l.as_ptr(),
g_u.as_ptr(),
(M * N) as Index,
if limited_memory { 0 } else { 3 },
0,
Some(ev_f),
Some(ev_g),
Some(ev_grad_f),
Some(ev_jac_g),
if limited_memory { None } else { Some(ev_h) },
);
assert!(!prob.is_null());
if limited_memory {
let key = CString::new("hessian_approximation").unwrap();
let val = CString::new("limited-memory").unwrap();
assert_ne!(
AddIpoptStrOption(prob, key.as_ptr() as *mut _, val.as_ptr() as *mut _),
0
);
}
let key = CString::new("print_level").unwrap();
assert_ne!(AddIpoptIntOption(prob, key.as_ptr() as *mut _, 0), 0);
let key = CString::new("tol").unwrap();
assert_ne!(AddIpoptNumOption(prob, key.as_ptr() as *mut _, 1e-9), 0);
if let Some(mask) = mask {
assert_ne!(
IpoptSetNonlinearVariables(prob, mask.len() as Index, mask.as_ptr()),
0,
"IpoptSetNonlinearVariables refused a valid subset"
);
}
let mut x = [0.0 as Number, 0.0, 1.0, 1.0, 0.0, 0.0];
let mut g = [0.0 as Number; M];
let mut obj = 0.0 as Number;
let mut mult_g = [0.0 as Number; M];
let mut z_l = [0.0 as Number; N];
let mut z_u = [0.0 as Number; N];
let status = IpoptSolve(
prob,
x.as_mut_ptr(),
g.as_mut_ptr(),
&mut obj,
mult_g.as_mut_ptr(),
z_l.as_mut_ptr(),
z_u.as_mut_ptr(),
std::ptr::null_mut(),
);
let iters = GetIpoptIterCount(prob);
FreeIpoptProblem(prob);
Solved {
status: status as i32,
x,
obj,
iters,
}
}
}
fn assert_same_solution(a: &Solved, b: &Solved, tol: Number, what: &str) {
assert_eq!(a.status, 0, "{what}: first solve did not succeed");
assert_eq!(b.status, 0, "{what}: second solve did not succeed");
assert!(
(a.obj - b.obj).abs() < tol,
"{what}: objectives differ: {} vs {}",
a.obj,
b.obj
);
for i in 0..N {
assert!(
(a.x[i] - b.x[i]).abs() < tol,
"{what}: x[{i}] differs: {} vs {}",
a.x[i],
b.x[i]
);
}
}
#[test]
fn masked_and_full_space_lbfgs_reach_the_same_kkt_point() {
let full = solve(true, None);
let masked = solve(true, Some(&NONLIN));
assert_same_solution(&full, &masked, 1e-5, "limited-memory");
assert!(masked.iters > 0);
}
#[test]
fn masked_lbfgs_matches_the_exact_hessian_solution() {
let exact = solve(false, None);
let masked = solve(true, Some(&NONLIN));
assert_same_solution(&exact, &masked, 1e-5, "masked vs exact");
}
#[test]
fn declaring_the_subset_does_not_disturb_the_exact_hessian_path() {
let plain = solve(false, None);
let declared = solve(false, Some(&NONLIN));
assert_eq!(plain.iters, declared.iters);
for i in 0..N {
assert_eq!(plain.x[i], declared.x[i]);
}
assert_eq!(plain.obj, declared.obj);
}
#[test]
fn declaring_every_variable_is_the_same_as_declaring_nothing() {
let all: Vec<Index> = (0..N as Index).collect();
let none = solve(true, None);
let everything = solve(true, Some(&all));
assert_eq!(none.iters, everything.iters);
assert_eq!(none.obj, everything.obj);
}
#[test]
fn bad_declarations_are_refused_and_leave_the_problem_untouched() {
unsafe {
let x_l = [-5.0 as Number; N];
let x_u = [5.0 as Number; N];
let g_l = [4.0 as Number, -2.0e19];
let g_u = [4.0 as Number, 1.0];
let prob = CreateIpoptProblem(
N as Index,
x_l.as_ptr(),
x_u.as_ptr(),
M as Index,
g_l.as_ptr(),
g_u.as_ptr(),
(M * N) as Index,
0,
0,
Some(ev_f),
Some(ev_g),
Some(ev_grad_f),
Some(ev_jac_g),
None,
);
assert!(!prob.is_null());
let good: [Index; 2] = [0, 1];
assert_ne!(IpoptSetNonlinearVariables(prob, 2, good.as_ptr()), 0);
let bad: [Index; 2] = [0, N as Index];
assert_eq!(IpoptSetNonlinearVariables(prob, 2, bad.as_ptr()), 0);
let oversized: Vec<Index> = vec![0; N + 1];
assert_eq!(
IpoptSetNonlinearVariables(prob, (N + 1) as Index, oversized.as_ptr()),
0
);
assert_eq!(IpoptSetNonlinearVariables(prob, -1, good.as_ptr()), 0);
assert_eq!(IpoptSetNonlinearVariables(prob, 2, std::ptr::null()), 0);
assert_eq!(
IpoptSetNonlinearVariables(std::ptr::null_mut(), 2, good.as_ptr()),
0
);
let key = CString::new("hessian_approximation").unwrap();
let val = CString::new("limited-memory").unwrap();
AddIpoptStrOption(prob, key.as_ptr() as *mut _, val.as_ptr() as *mut _);
let key = CString::new("print_level").unwrap();
AddIpoptIntOption(prob, key.as_ptr() as *mut _, 0);
let mut x = [0.0 as Number, 0.0, 1.0, 1.0, 0.0, 0.0];
let mut obj = 0.0 as Number;
let status = IpoptSolve(
prob,
x.as_mut_ptr(),
std::ptr::null_mut(),
&mut obj,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
);
assert_ne!(IpoptClearNonlinearVariables(prob), 0);
FreeIpoptProblem(prob);
assert_eq!(status as i32, 0);
let reference = solve(true, None);
for i in 0..N {
assert!((x[i] - reference.x[i]).abs() < 1e-5);
}
}
}