1use pounce_common::types::{Index, Number};
13use pounce_nlp::tnlp::{
14 BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, Solution, SparsityRequest, StartingPoint,
15 TNLP,
16};
17use std::cell::RefCell;
18use std::rc::Rc;
19
20pub fn list() -> Vec<&'static str> {
21 vec![
22 "quadratic",
23 "rosenbrock",
24 "bounded-quadratic",
25 "eq-quadratic",
26 "circle",
27 ]
28}
29
30pub fn lookup(name: &str) -> Option<Rc<RefCell<dyn TNLP>>> {
31 match name {
32 "quadratic" => Some(Rc::new(RefCell::new(Quadratic::default()))),
33 "rosenbrock" => Some(Rc::new(RefCell::new(Rosenbrock::default()))),
34 "bounded-quadratic" => Some(Rc::new(RefCell::new(BoundedQuadratic::default()))),
35 "eq-quadratic" => Some(Rc::new(RefCell::new(EqQuadratic::default()))),
36 "circle" => Some(Rc::new(RefCell::new(Circle::default()))),
37 _ => None,
38 }
39}
40
41#[derive(Debug, Default)]
46pub struct Quadratic {
47 pub final_x: Option<[Number; 2]>,
48 pub final_obj: Number,
49}
50
51impl TNLP for Quadratic {
52 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
53 Some(NlpInfo {
54 n: 2,
55 m: 0,
56 nnz_jac_g: 0,
57 nnz_h_lag: 2, index_style: IndexStyle::C,
59 })
60 }
61
62 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
63 b.x_l.iter_mut().for_each(|v| *v = -2e19);
64 b.x_u.iter_mut().for_each(|v| *v = 2e19);
65 true
66 }
67
68 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
69 sp.x.copy_from_slice(&[0.0, 0.0]);
70 true
71 }
72
73 fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
74 Some((x[0] - 3.0).powi(2) + (x[1] - 4.0).powi(2))
75 }
76
77 fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, grad: &mut [Number]) -> bool {
78 grad[0] = 2.0 * (x[0] - 3.0);
79 grad[1] = 2.0 * (x[1] - 4.0);
80 true
81 }
82
83 fn eval_g(&mut self, _x: &[Number], _new_x: bool, _g: &mut [Number]) -> bool {
84 true
85 }
86
87 fn eval_jac_g(
88 &mut self,
89 _x: Option<&[Number]>,
90 _new_x: bool,
91 _mode: SparsityRequest<'_>,
92 ) -> bool {
93 true
94 }
95
96 fn eval_h(
97 &mut self,
98 _x: Option<&[Number]>,
99 _new_x: bool,
100 obj_factor: Number,
101 _lambda: Option<&[Number]>,
102 _new_lambda: bool,
103 mode: SparsityRequest<'_>,
104 ) -> bool {
105 match mode {
106 SparsityRequest::Structure { irow, jcol } => {
107 irow.copy_from_slice(&[0, 1]);
108 jcol.copy_from_slice(&[0, 1]);
109 }
110 SparsityRequest::Values { values } => {
111 values[0] = 2.0 * obj_factor;
112 values[1] = 2.0 * obj_factor;
113 }
114 }
115 true
116 }
117
118 fn finalize_solution(&mut self, sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {
119 self.final_x = Some([sol.x[0], sol.x[1]]);
120 self.final_obj = sol.obj_value;
121 }
122}
123
124#[derive(Debug, Default)]
129pub struct Rosenbrock {
130 pub final_x: Option<[Number; 2]>,
131 pub final_obj: Number,
132}
133
134impl TNLP for Rosenbrock {
135 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
136 Some(NlpInfo {
137 n: 2,
138 m: 0,
139 nnz_jac_g: 0,
140 nnz_h_lag: 3, index_style: IndexStyle::C,
142 })
143 }
144
145 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
146 b.x_l.iter_mut().for_each(|v| *v = -2e19);
147 b.x_u.iter_mut().for_each(|v| *v = 2e19);
148 true
149 }
150
151 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
152 sp.x.copy_from_slice(&[-1.2, 1.0]);
153 true
154 }
155
156 fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
157 let a = x[1] - x[0] * x[0];
158 let b = 1.0 - x[0];
159 Some(100.0 * a * a + b * b)
160 }
161
162 fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, grad: &mut [Number]) -> bool {
163 grad[0] = -400.0 * x[0] * (x[1] - x[0] * x[0]) - 2.0 * (1.0 - x[0]);
166 grad[1] = 200.0 * (x[1] - x[0] * x[0]);
167 true
168 }
169
170 fn eval_g(&mut self, _x: &[Number], _new_x: bool, _g: &mut [Number]) -> bool {
171 true
172 }
173
174 fn eval_jac_g(
175 &mut self,
176 _x: Option<&[Number]>,
177 _new_x: bool,
178 _mode: SparsityRequest<'_>,
179 ) -> bool {
180 true
181 }
182
183 fn eval_h(
184 &mut self,
185 x: Option<&[Number]>,
186 _new_x: bool,
187 obj_factor: Number,
188 _lambda: Option<&[Number]>,
189 _new_lambda: bool,
190 mode: SparsityRequest<'_>,
191 ) -> bool {
192 match mode {
193 SparsityRequest::Structure { irow, jcol } => {
194 irow.copy_from_slice(&[0, 1, 1]);
196 jcol.copy_from_slice(&[0, 0, 1]);
197 }
198 SparsityRequest::Values { values } => {
199 let x = x.unwrap_or(&[0.0, 0.0]);
200 let h00 = -400.0 * (x[1] - 3.0 * x[0] * x[0]) + 2.0;
201 let h10 = -400.0 * x[0];
202 let h11 = 200.0;
203 values[0] = obj_factor * h00;
204 values[1] = obj_factor * h10;
205 values[2] = obj_factor * h11;
206 }
207 }
208 true
209 }
210
211 fn finalize_solution(&mut self, sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {
212 self.final_x = Some([sol.x[0], sol.x[1]]);
213 self.final_obj = sol.obj_value;
214 }
215}
216
217#[allow(dead_code)]
219fn _ix(_: Index) {}
220
221#[derive(Debug, Default)]
227pub struct BoundedQuadratic {
228 pub final_x: Option<[Number; 2]>,
229 pub final_obj: Number,
230}
231
232impl TNLP for BoundedQuadratic {
233 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
234 Some(NlpInfo {
235 n: 2,
236 m: 0,
237 nnz_jac_g: 0,
238 nnz_h_lag: 2,
239 index_style: IndexStyle::C,
240 })
241 }
242
243 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
244 b.x_l.copy_from_slice(&[0.0, 0.0]);
245 b.x_u.copy_from_slice(&[2.0, 2.0]);
246 true
247 }
248
249 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
250 sp.x.copy_from_slice(&[1.0, 1.0]);
251 true
252 }
253
254 fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
255 Some((x[0] - 3.0).powi(2) + (x[1] - 4.0).powi(2))
256 }
257
258 fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, grad: &mut [Number]) -> bool {
259 grad[0] = 2.0 * (x[0] - 3.0);
260 grad[1] = 2.0 * (x[1] - 4.0);
261 true
262 }
263
264 fn eval_g(&mut self, _x: &[Number], _new_x: bool, _g: &mut [Number]) -> bool {
265 true
266 }
267
268 fn eval_jac_g(
269 &mut self,
270 _x: Option<&[Number]>,
271 _new_x: bool,
272 _mode: SparsityRequest<'_>,
273 ) -> bool {
274 true
275 }
276
277 fn eval_h(
278 &mut self,
279 _x: Option<&[Number]>,
280 _new_x: bool,
281 obj_factor: Number,
282 _lambda: Option<&[Number]>,
283 _new_lambda: bool,
284 mode: SparsityRequest<'_>,
285 ) -> bool {
286 match mode {
287 SparsityRequest::Structure { irow, jcol } => {
288 irow.copy_from_slice(&[0, 1]);
289 jcol.copy_from_slice(&[0, 1]);
290 }
291 SparsityRequest::Values { values } => {
292 values[0] = 2.0 * obj_factor;
293 values[1] = 2.0 * obj_factor;
294 }
295 }
296 true
297 }
298
299 fn finalize_solution(&mut self, sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {
300 self.final_x = Some([sol.x[0], sol.x[1]]);
301 self.final_obj = sol.obj_value;
302 }
303}
304
305#[derive(Debug, Default)]
311pub struct EqQuadratic {
312 pub final_x: Option<[Number; 2]>,
313 pub final_obj: Number,
314}
315
316impl TNLP for EqQuadratic {
317 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
318 Some(NlpInfo {
319 n: 2,
320 m: 1,
321 nnz_jac_g: 2,
322 nnz_h_lag: 2,
323 index_style: IndexStyle::C,
324 })
325 }
326
327 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
328 b.x_l.iter_mut().for_each(|v| *v = -2e19);
329 b.x_u.iter_mut().for_each(|v| *v = 2e19);
330 b.g_l[0] = 1.0;
331 b.g_u[0] = 1.0;
332 true
333 }
334
335 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
336 sp.x.copy_from_slice(&[0.0, 0.0]);
337 true
338 }
339
340 fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
341 Some(x[0] * x[0] + x[1] * x[1])
342 }
343
344 fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, grad: &mut [Number]) -> bool {
345 grad[0] = 2.0 * x[0];
346 grad[1] = 2.0 * x[1];
347 true
348 }
349
350 fn eval_g(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
351 g[0] = x[0] + x[1];
352 true
353 }
354
355 fn eval_jac_g(
356 &mut self,
357 _x: Option<&[Number]>,
358 _new_x: bool,
359 mode: SparsityRequest<'_>,
360 ) -> bool {
361 match mode {
362 SparsityRequest::Structure { irow, jcol } => {
363 irow.copy_from_slice(&[0, 0]);
364 jcol.copy_from_slice(&[0, 1]);
365 }
366 SparsityRequest::Values { values } => {
367 values[0] = 1.0;
368 values[1] = 1.0;
369 }
370 }
371 true
372 }
373
374 fn eval_h(
375 &mut self,
376 _x: Option<&[Number]>,
377 _new_x: bool,
378 obj_factor: Number,
379 _lambda: Option<&[Number]>,
380 _new_lambda: bool,
381 mode: SparsityRequest<'_>,
382 ) -> bool {
383 match mode {
384 SparsityRequest::Structure { irow, jcol } => {
385 irow.copy_from_slice(&[0, 1]);
386 jcol.copy_from_slice(&[0, 1]);
387 }
388 SparsityRequest::Values { values } => {
389 values[0] = 2.0 * obj_factor;
390 values[1] = 2.0 * obj_factor;
391 }
392 }
393 true
394 }
395
396 fn finalize_solution(&mut self, sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {
397 self.final_x = Some([sol.x[0], sol.x[1]]);
398 self.final_obj = sol.obj_value;
399 }
400}
401
402#[derive(Debug, Default)]
410pub struct Circle {
411 pub final_x: Option<[Number; 2]>,
412 pub final_obj: Number,
413}
414
415impl TNLP for Circle {
416 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
417 Some(NlpInfo {
418 n: 2,
419 m: 1,
420 nnz_jac_g: 2,
421 nnz_h_lag: 2,
422 index_style: IndexStyle::C,
423 })
424 }
425
426 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
427 b.x_l.iter_mut().for_each(|v| *v = -2e19);
428 b.x_u.iter_mut().for_each(|v| *v = 2e19);
429 b.g_l[0] = 1.0;
430 b.g_u[0] = 1.0;
431 true
432 }
433
434 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
435 sp.x.copy_from_slice(&[-0.5, 0.5]);
436 true
437 }
438
439 fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
440 Some(x[0])
441 }
442
443 fn eval_grad_f(&mut self, _x: &[Number], _new_x: bool, grad: &mut [Number]) -> bool {
444 grad[0] = 1.0;
445 grad[1] = 0.0;
446 true
447 }
448
449 fn eval_g(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
450 g[0] = x[0] * x[0] + x[1] * x[1];
451 true
452 }
453
454 fn eval_jac_g(
455 &mut self,
456 x: Option<&[Number]>,
457 _new_x: bool,
458 mode: SparsityRequest<'_>,
459 ) -> bool {
460 match mode {
461 SparsityRequest::Structure { irow, jcol } => {
462 irow.copy_from_slice(&[0, 0]);
463 jcol.copy_from_slice(&[0, 1]);
464 }
465 SparsityRequest::Values { values } => {
466 let x = x.unwrap_or(&[0.0, 0.0]);
467 values[0] = 2.0 * x[0];
468 values[1] = 2.0 * x[1];
469 }
470 }
471 true
472 }
473
474 fn eval_h(
475 &mut self,
476 _x: Option<&[Number]>,
477 _new_x: bool,
478 _obj_factor: Number,
479 lambda: Option<&[Number]>,
480 _new_lambda: bool,
481 mode: SparsityRequest<'_>,
482 ) -> bool {
483 match mode {
484 SparsityRequest::Structure { irow, jcol } => {
485 irow.copy_from_slice(&[0, 1]);
486 jcol.copy_from_slice(&[0, 1]);
487 }
488 SparsityRequest::Values { values } => {
489 let lam = lambda.map(|l| l[0]).unwrap_or(0.0);
491 values[0] = 2.0 * lam;
492 values[1] = 2.0 * lam;
493 }
494 }
495 true
496 }
497
498 fn finalize_solution(&mut self, sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {
499 self.final_x = Some([sol.x[0], sol.x[1]]);
500 self.final_obj = sol.obj_value;
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 use super::*;
507
508 #[test]
509 fn list_contains_known_problems() {
510 let l = list();
511 assert!(l.contains(&"quadratic"));
512 assert!(l.contains(&"rosenbrock"));
513 }
514
515 #[test]
516 fn quadratic_evaluates_correctly() {
517 let mut q = Quadratic::default();
518 let f = q.eval_f(&[3.0, 4.0], true).unwrap();
519 assert_eq!(f, 0.0);
520 let mut g = [0.0; 2];
521 q.eval_grad_f(&[0.0, 0.0], true, &mut g);
522 assert_eq!(g, [-6.0, -8.0]);
523 }
524
525 #[test]
526 fn rosenbrock_grad_zero_at_optimum() {
527 let mut r = Rosenbrock::default();
528 let f = r.eval_f(&[1.0, 1.0], true).unwrap();
529 assert!(f.abs() < 1e-15);
530 let mut g = [0.0; 2];
531 r.eval_grad_f(&[1.0, 1.0], true, &mut g);
532 assert!(g[0].abs() < 1e-12);
533 assert!(g[1].abs() < 1e-12);
534 }
535
536 #[test]
537 fn lookup_returns_known_and_rejects_unknown() {
538 assert!(lookup("quadratic").is_some());
539 assert!(lookup("rosenbrock").is_some());
540 assert!(lookup("nonsense").is_none());
541 }
542}