Skip to main content

pounce_cli/
counting_tnlp.rs

1//! TNLP wrapper that counts evaluation calls so the CLI can mirror
2//! Ipopt's end-of-run "Number of … evaluations = N" summary block.
3//!
4//! All eight required TNLP methods (and `intermediate_callback`) are
5//! forwarded transparently to the inner TNLP. The counters live in
6//! `Cell<i32>`s on the wrapper itself, so the CLI can read them via
7//! `Rc<RefCell<CountingTnlp>>::borrow()` after the solve completes.
8//!
9//! The wrapper does not count *every* call — calls that pass an
10//! `irow/jcol`-only `SparsityRequest::Structure` (the symbolic
11//! sparsity-pattern call, not the values call) don't represent a real
12//! Jacobian / Hessian evaluation, mirroring the way Ipopt reports
13//! these numbers.
14
15use pounce_common::types::{Index, Number};
16use pounce_nlp::tnlp::{
17    BoundsInfo, IpoptCq, IpoptData, IterStats, MetaData, NlpInfo, ScalingRequest, Solution,
18    SparsityRequest, StartingPoint, TNLP,
19};
20use std::cell::{Cell, RefCell};
21use std::rc::Rc;
22
23pub struct CountingTnlp {
24    inner: Rc<RefCell<dyn TNLP>>,
25    pub n_obj: Cell<i32>,
26    pub n_grad_f: Cell<i32>,
27    pub n_g: Cell<i32>,
28    pub n_jac_g: Cell<i32>,
29    pub n_h: Cell<i32>,
30}
31
32impl CountingTnlp {
33    pub fn new(inner: Rc<RefCell<dyn TNLP>>) -> Self {
34        Self {
35            inner,
36            n_obj: Cell::new(0),
37            n_grad_f: Cell::new(0),
38            n_g: Cell::new(0),
39            n_jac_g: Cell::new(0),
40            n_h: Cell::new(0),
41        }
42    }
43}
44
45impl TNLP for CountingTnlp {
46    fn get_nlp_info(&mut self) -> Option<NlpInfo> {
47        self.inner.borrow_mut().get_nlp_info()
48    }
49
50    fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
51        self.inner.borrow_mut().get_bounds_info(b)
52    }
53
54    fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
55        self.inner.borrow_mut().get_starting_point(sp)
56    }
57
58    fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
59        self.n_obj.set(self.n_obj.get() + 1);
60        self.inner.borrow_mut().eval_f(x, new_x)
61    }
62
63    fn eval_grad_f(&mut self, x: &[Number], new_x: bool, grad_f: &mut [Number]) -> bool {
64        self.n_grad_f.set(self.n_grad_f.get() + 1);
65        self.inner.borrow_mut().eval_grad_f(x, new_x, grad_f)
66    }
67
68    fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
69        self.n_g.set(self.n_g.get() + 1);
70        self.inner.borrow_mut().eval_g(x, new_x, g)
71    }
72
73    fn eval_jac_g(&mut self, x: Option<&[Number]>, new_x: bool, mode: SparsityRequest<'_>) -> bool {
74        // Only the values call counts as a real Jacobian evaluation;
75        // the symbolic Structure call is bookkeeping.
76        if matches!(mode, SparsityRequest::Values { .. }) {
77            self.n_jac_g.set(self.n_jac_g.get() + 1);
78        }
79        self.inner.borrow_mut().eval_jac_g(x, new_x, mode)
80    }
81
82    fn eval_h(
83        &mut self,
84        x: Option<&[Number]>,
85        new_x: bool,
86        obj_factor: Number,
87        lambda: Option<&[Number]>,
88        new_lambda: bool,
89        mode: SparsityRequest<'_>,
90    ) -> bool {
91        if matches!(mode, SparsityRequest::Values { .. }) {
92            self.n_h.set(self.n_h.get() + 1);
93        }
94        self.inner
95            .borrow_mut()
96            .eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
97    }
98
99    fn finalize_solution(&mut self, sol: Solution<'_>, ip_data: &IpoptData, ip_cq: &IpoptCq) {
100        self.inner
101            .borrow_mut()
102            .finalize_solution(sol, ip_data, ip_cq);
103    }
104
105    fn get_var_con_metadata(&mut self, var: &mut MetaData, con: &mut MetaData) -> bool {
106        self.inner.borrow_mut().get_var_con_metadata(var, con)
107    }
108
109    fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
110        self.inner.borrow_mut().get_scaling_parameters(req)
111    }
112
113    fn get_number_of_nonlinear_variables(&mut self) -> Index {
114        self.inner.borrow_mut().get_number_of_nonlinear_variables()
115    }
116
117    fn get_list_of_nonlinear_variables(&mut self, pos: &mut [Index]) -> bool {
118        self.inner.borrow_mut().get_list_of_nonlinear_variables(pos)
119    }
120
121    fn intermediate_callback(
122        &mut self,
123        stats: IterStats,
124        ip_data: &IpoptData,
125        ip_cq: &IpoptCq,
126    ) -> bool {
127        self.inner
128            .borrow_mut()
129            .intermediate_callback(stats, ip_data, ip_cq)
130    }
131
132    fn finalize_metadata(&mut self, var: &MetaData, con: &MetaData) {
133        self.inner.borrow_mut().finalize_metadata(var, con)
134    }
135}