use pounce_common::types::{Index, Number};
use pounce_nlp::tnlp::{
BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, Solution, SparsityRequest, StartingPoint,
TNLP,
};
use std::cell::RefCell;
use std::rc::Rc;
pub fn list() -> Vec<&'static str> {
vec![
"quadratic",
"rosenbrock",
"bounded-quadratic",
"eq-quadratic",
"circle",
"infeasible-eq",
]
}
pub fn lookup(name: &str) -> Option<Rc<RefCell<dyn TNLP>>> {
match name {
"quadratic" => Some(Rc::new(RefCell::new(Quadratic::default()))),
"rosenbrock" => Some(Rc::new(RefCell::new(Rosenbrock::default()))),
"bounded-quadratic" => Some(Rc::new(RefCell::new(BoundedQuadratic::default()))),
"eq-quadratic" => Some(Rc::new(RefCell::new(EqQuadratic::default()))),
"circle" => Some(Rc::new(RefCell::new(Circle::default()))),
"infeasible-eq" => Some(Rc::new(RefCell::new(InfeasibleEq::default()))),
_ => None,
}
}
#[derive(Debug, Default)]
pub struct Quadratic {
pub final_x: Option<[Number; 2]>,
pub final_obj: Number,
}
impl TNLP for Quadratic {
fn get_nlp_info(&mut self) -> Option<NlpInfo> {
Some(NlpInfo {
n: 2,
m: 0,
nnz_jac_g: 0,
nnz_h_lag: 2, index_style: IndexStyle::C,
})
}
fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
b.x_l.iter_mut().for_each(|v| *v = -2e19);
b.x_u.iter_mut().for_each(|v| *v = 2e19);
true
}
fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
sp.x.copy_from_slice(&[0.0, 0.0]);
true
}
fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
Some((x[0] - 3.0).powi(2) + (x[1] - 4.0).powi(2))
}
fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, grad: &mut [Number]) -> bool {
grad[0] = 2.0 * (x[0] - 3.0);
grad[1] = 2.0 * (x[1] - 4.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,
obj_factor: Number,
_lambda: Option<&[Number]>,
_new_lambda: bool,
mode: SparsityRequest<'_>,
) -> bool {
match mode {
SparsityRequest::Structure { irow, jcol } => {
irow.copy_from_slice(&[0, 1]);
jcol.copy_from_slice(&[0, 1]);
}
SparsityRequest::Values { values } => {
values[0] = 2.0 * obj_factor;
values[1] = 2.0 * obj_factor;
}
}
true
}
fn finalize_solution(&mut self, sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {
self.final_x = Some([sol.x[0], sol.x[1]]);
self.final_obj = sol.obj_value;
}
}
#[derive(Debug, Default)]
pub struct Rosenbrock {
pub final_x: Option<[Number; 2]>,
pub final_obj: Number,
}
impl TNLP for Rosenbrock {
fn get_nlp_info(&mut self) -> Option<NlpInfo> {
Some(NlpInfo {
n: 2,
m: 0,
nnz_jac_g: 0,
nnz_h_lag: 3, index_style: IndexStyle::C,
})
}
fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
b.x_l.iter_mut().for_each(|v| *v = -2e19);
b.x_u.iter_mut().for_each(|v| *v = 2e19);
true
}
fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
sp.x.copy_from_slice(&[-1.2, 1.0]);
true
}
fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
let a = x[1] - x[0] * x[0];
let b = 1.0 - x[0];
Some(100.0 * a * a + b * b)
}
fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, grad: &mut [Number]) -> bool {
grad[0] = -400.0 * x[0] * (x[1] - x[0] * x[0]) - 2.0 * (1.0 - x[0]);
grad[1] = 200.0 * (x[1] - x[0] * x[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,
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 x = x.unwrap_or(&[0.0, 0.0]);
let h00 = -400.0 * (x[1] - 3.0 * x[0] * x[0]) + 2.0;
let h10 = -400.0 * x[0];
let h11 = 200.0;
values[0] = obj_factor * h00;
values[1] = obj_factor * h10;
values[2] = obj_factor * h11;
}
}
true
}
fn finalize_solution(&mut self, sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {
self.final_x = Some([sol.x[0], sol.x[1]]);
self.final_obj = sol.obj_value;
}
}
#[allow(dead_code)]
fn _ix(_: Index) {}
#[derive(Debug, Default)]
pub struct BoundedQuadratic {
pub final_x: Option<[Number; 2]>,
pub final_obj: Number,
}
impl TNLP for BoundedQuadratic {
fn get_nlp_info(&mut self) -> Option<NlpInfo> {
Some(NlpInfo {
n: 2,
m: 0,
nnz_jac_g: 0,
nnz_h_lag: 2,
index_style: IndexStyle::C,
})
}
fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
b.x_l.copy_from_slice(&[0.0, 0.0]);
b.x_u.copy_from_slice(&[2.0, 2.0]);
true
}
fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
sp.x.copy_from_slice(&[1.0, 1.0]);
true
}
fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
Some((x[0] - 3.0).powi(2) + (x[1] - 4.0).powi(2))
}
fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, grad: &mut [Number]) -> bool {
grad[0] = 2.0 * (x[0] - 3.0);
grad[1] = 2.0 * (x[1] - 4.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,
obj_factor: Number,
_lambda: Option<&[Number]>,
_new_lambda: bool,
mode: SparsityRequest<'_>,
) -> bool {
match mode {
SparsityRequest::Structure { irow, jcol } => {
irow.copy_from_slice(&[0, 1]);
jcol.copy_from_slice(&[0, 1]);
}
SparsityRequest::Values { values } => {
values[0] = 2.0 * obj_factor;
values[1] = 2.0 * obj_factor;
}
}
true
}
fn finalize_solution(&mut self, sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {
self.final_x = Some([sol.x[0], sol.x[1]]);
self.final_obj = sol.obj_value;
}
}
#[derive(Debug, Default)]
pub struct EqQuadratic {
pub final_x: Option<[Number; 2]>,
pub final_obj: Number,
}
impl TNLP for EqQuadratic {
fn get_nlp_info(&mut self) -> Option<NlpInfo> {
Some(NlpInfo {
n: 2,
m: 1,
nnz_jac_g: 2,
nnz_h_lag: 2,
index_style: IndexStyle::C,
})
}
fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
b.x_l.iter_mut().for_each(|v| *v = -2e19);
b.x_u.iter_mut().for_each(|v| *v = 2e19);
b.g_l[0] = 1.0;
b.g_u[0] = 1.0;
true
}
fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
sp.x.copy_from_slice(&[0.0, 0.0]);
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] = 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]);
jcol.copy_from_slice(&[0, 1]);
}
SparsityRequest::Values { values } => {
values[0] = 1.0;
values[1] = 1.0;
}
}
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]);
jcol.copy_from_slice(&[0, 1]);
}
SparsityRequest::Values { values } => {
values[0] = 2.0 * obj_factor;
values[1] = 2.0 * obj_factor;
}
}
true
}
fn finalize_solution(&mut self, sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {
self.final_x = Some([sol.x[0], sol.x[1]]);
self.final_obj = sol.obj_value;
}
}
#[derive(Debug, Default)]
pub struct Circle {
pub final_x: Option<[Number; 2]>,
pub final_obj: Number,
}
impl TNLP for Circle {
fn get_nlp_info(&mut self) -> Option<NlpInfo> {
Some(NlpInfo {
n: 2,
m: 1,
nnz_jac_g: 2,
nnz_h_lag: 2,
index_style: IndexStyle::C,
})
}
fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
b.x_l.iter_mut().for_each(|v| *v = -2e19);
b.x_u.iter_mut().for_each(|v| *v = 2e19);
b.g_l[0] = 1.0;
b.g_u[0] = 1.0;
true
}
fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
sp.x.copy_from_slice(&[-0.5, 0.5]);
true
}
fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
Some(x[0])
}
fn eval_grad_f(&mut self, _x: &[Number], _new_x: bool, grad: &mut [Number]) -> bool {
grad[0] = 1.0;
grad[1] = 0.0;
true
}
fn eval_g(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
g[0] = x[0] * x[0] + x[1] * 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]);
jcol.copy_from_slice(&[0, 1]);
}
SparsityRequest::Values { values } => {
let x = x.unwrap_or(&[0.0, 0.0]);
values[0] = 2.0 * x[0];
values[1] = 2.0 * x[1];
}
}
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]);
jcol.copy_from_slice(&[0, 1]);
}
SparsityRequest::Values { values } => {
let lam = lambda.map(|l| l[0]).unwrap_or(0.0);
values[0] = 2.0 * lam;
values[1] = 2.0 * lam;
}
}
true
}
fn finalize_solution(&mut self, sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {
self.final_x = Some([sol.x[0], sol.x[1]]);
self.final_obj = sol.obj_value;
}
}
#[derive(Debug, Default)]
pub struct InfeasibleEq {
pub final_x: Option<[Number; 2]>,
pub final_obj: Number,
}
impl TNLP for InfeasibleEq {
fn get_nlp_info(&mut self) -> Option<NlpInfo> {
Some(NlpInfo {
n: 2,
m: 2,
nnz_jac_g: 4,
nnz_h_lag: 2,
index_style: IndexStyle::C,
})
}
fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
b.x_l.iter_mut().for_each(|v| *v = -2e19);
b.x_u.iter_mut().for_each(|v| *v = 2e19);
b.g_l[0] = 1.0;
b.g_u[0] = 1.0;
b.g_l[1] = 2.0;
b.g_u[1] = 2.0;
true
}
fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
sp.x.copy_from_slice(&[0.0, 0.0]);
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] = x[0] + x[1];
g[1] = 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 } => {
values.copy_from_slice(&[1.0, 1.0, 1.0, 1.0]);
}
}
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]);
jcol.copy_from_slice(&[0, 1]);
}
SparsityRequest::Values { values } => {
values[0] = 2.0 * obj_factor;
values[1] = 2.0 * obj_factor;
}
}
true
}
fn finalize_solution(&mut self, sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {
self.final_x = Some([sol.x[0], sol.x[1]]);
self.final_obj = sol.obj_value;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn list_contains_known_problems() {
let l = list();
assert!(l.contains(&"quadratic"));
assert!(l.contains(&"rosenbrock"));
}
#[test]
fn quadratic_evaluates_correctly() {
let mut q = Quadratic::default();
let f = q.eval_f(&[3.0, 4.0], true).unwrap();
assert_eq!(f, 0.0);
let mut g = [0.0; 2];
q.eval_grad_f(&[0.0, 0.0], true, &mut g);
assert_eq!(g, [-6.0, -8.0]);
}
#[test]
fn rosenbrock_grad_zero_at_optimum() {
let mut r = Rosenbrock::default();
let f = r.eval_f(&[1.0, 1.0], true).unwrap();
assert!(f.abs() < 1e-15);
let mut g = [0.0; 2];
r.eval_grad_f(&[1.0, 1.0], true, &mut g);
assert!(g[0].abs() < 1e-12);
assert!(g[1].abs() < 1e-12);
}
#[test]
fn lookup_returns_known_and_rejects_unknown() {
assert!(lookup("quadratic").is_some());
assert!(lookup("rosenbrock").is_some());
assert!(lookup("nonsense").is_none());
}
}