Skip to main content

auxiliary_noop/
auxiliary_noop.rs

1//! Worked example for PR 1 of issue #53.
2//!
3//! Builds a tiny 2-variable / 1-equality TNLP, wraps it in a
4//! `PresolveTnlp` with `presolve_auxiliary=yes`, runs `eval_jac_g`
5//! once, and prints the auxiliary-preprocessing diagnostics. In PR 1
6//! the orchestrator is a no-op, so every counter prints as zero —
7//! the point is to demonstrate that the wiring is in place and the
8//! `presolve_auxiliary=yes` path doesn't perturb the inner problem.
9//!
10//! Run with:
11//! ```bash
12//! cargo run -p pounce-presolve --example auxiliary_noop
13//! ```
14
15#![allow(clippy::expect_used, clippy::unwrap_used)]
16
17use std::cell::RefCell;
18use std::rc::Rc;
19
20use pounce_nlp::tnlp::{
21    BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, Solution, SparsityRequest, StartingPoint,
22    TNLP,
23};
24use pounce_presolve::{wrap_with_presolve, AuxiliaryCouplingPolicy, PresolveOptions, PresolveTnlp};
25
26/// `min x[0]^2 + x[1]^2  s.t.  x[0] + x[1] = 1`.
27struct Mini;
28
29impl TNLP for Mini {
30    fn get_nlp_info(&mut self) -> Option<NlpInfo> {
31        Some(NlpInfo {
32            n: 2,
33            m: 1,
34            nnz_jac_g: 2,
35            nnz_h_lag: 2,
36            index_style: IndexStyle::C,
37        })
38    }
39    fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
40        b.x_l.iter_mut().for_each(|v| *v = -1e19);
41        b.x_u.iter_mut().for_each(|v| *v = 1e19);
42        b.g_l[0] = 1.0;
43        b.g_u[0] = 1.0;
44        true
45    }
46    fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
47        sp.x[0] = 0.5;
48        sp.x[1] = 0.5;
49        true
50    }
51    fn eval_f(&mut self, x: &[f64], _new_x: bool) -> Option<f64> {
52        Some(x[0] * x[0] + x[1] * x[1])
53    }
54    fn eval_grad_f(&mut self, x: &[f64], _new_x: bool, g: &mut [f64]) -> bool {
55        g[0] = 2.0 * x[0];
56        g[1] = 2.0 * x[1];
57        true
58    }
59    fn eval_g(&mut self, x: &[f64], _new_x: bool, g: &mut [f64]) -> bool {
60        g[0] = x[0] + x[1];
61        true
62    }
63    fn eval_jac_g(&mut self, _x: Option<&[f64]>, _new_x: bool, mode: SparsityRequest<'_>) -> bool {
64        match mode {
65            SparsityRequest::Structure { irow, jcol } => {
66                irow.copy_from_slice(&[0, 0]);
67                jcol.copy_from_slice(&[0, 1]);
68            }
69            SparsityRequest::Values { values } => {
70                values.copy_from_slice(&[1.0, 1.0]);
71            }
72        }
73        true
74    }
75    fn finalize_solution(&mut self, _sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {}
76}
77
78fn main() {
79    let opts = PresolveOptions {
80        enabled: true,
81        auxiliary: true,
82        auxiliary_coupling: AuxiliaryCouplingPolicy::Safe,
83        auxiliary_diagnostics: true,
84        ..PresolveOptions::defaults()
85    };
86    let inner: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Mini));
87    let wrapped = wrap_with_presolve(inner, opts).expect("wrap ok");
88
89    // Trigger lazy init through any TNLP method.
90    let info = wrapped.borrow_mut().get_nlp_info().expect("get_nlp_info");
91
92    // Force one Jacobian-values call so the example exercises the
93    // forwarding path (the wrapper does not change anything in PR 1).
94    let mut values = vec![0.0; info.nnz_jac_g as usize];
95    let ok = wrapped.borrow_mut().eval_jac_g(
96        Some(&[0.5, 0.5]),
97        true,
98        SparsityRequest::Values {
99            values: &mut values,
100        },
101    );
102    assert!(ok, "eval_jac_g(Values) must succeed");
103
104    // Downcast back to PresolveTnlp via a typed handle would require
105    // a different constructor; instead, build a second wrapper using
106    // PresolveTnlp::new so we can read the diagnostics.
107    let inner2: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Mini));
108    let mut typed = PresolveTnlp::new(inner2, opts);
109    let _ = typed.get_nlp_info();
110    let d = typed.auxiliary_diagnostics();
111
112    println!("auxiliary_noop example — PR 1 of pounce#53");
113    println!(
114        "inner shape: n={}, m={}, nnz_jac_g={}",
115        info.n, info.m, info.nnz_jac_g
116    );
117    println!("jac values at (0.5, 0.5): {:?}", values);
118    println!(
119        "diagnostics: blocks_eliminated={}, vars_eliminated={}, rows_eliminated={}, total_time_ms={}",
120        d.blocks_eliminated, d.vars_eliminated, d.rows_eliminated, d.total_time_ms,
121    );
122    println!("rejection_reasons: {:?}", d.rejection_reasons);
123}