Skip to main content

pounce_nlp/
ipopt_nlp.rs

1//! NLP traits consumed by the algorithm core — port of `IpNLP.hpp` /
2//! `IpIpoptNLP.hpp`.
3//!
4//! These traits live in `pounce-nlp` (rather than `pounce-algorithm`)
5//! so that the concrete [`crate::orig_ipopt_nlp::OrigIpoptNlp`], which
6//! wraps a `TNLPAdapter` from this same crate, can implement them
7//! without forcing `pounce-nlp` to depend on `pounce-algorithm` (the
8//! reverse dependency already exists). `pounce-algorithm` re-exports
9//! both traits from its own `ipopt_nlp` module so the rest of the
10//! algorithm-side code continues to use the canonical
11//! `crate::ipopt_nlp::IpoptNlp` path.
12
13use pounce_common::types::{Index, Number};
14use pounce_linalg::{DenseVector, Matrix, SymMatrix, Vector};
15use std::rc::Rc;
16
17/// Lower-level NLP interface (post-`TNLPAdapter`). Equality and
18/// inequality constraints are already separated; bounds are already
19/// classified into `x_l_map` / `x_u_map` / etc.
20///
21/// This is the equivalent of upstream `Ipopt::NLP`.
22pub trait Nlp {
23    fn n(&self) -> Index;
24    fn m_eq(&self) -> Index;
25    fn m_ineq(&self) -> Index;
26
27    fn eval_f(&mut self, x: &dyn Vector) -> Number;
28    fn eval_grad_f(&mut self, x: &dyn Vector, g: &mut dyn Vector);
29    fn eval_c(&mut self, x: &dyn Vector, c: &mut dyn Vector);
30    fn eval_d(&mut self, x: &dyn Vector, d: &mut dyn Vector);
31    fn eval_jac_c(&mut self, x: &dyn Vector) -> Rc<dyn Matrix>;
32    fn eval_jac_d(&mut self, x: &dyn Vector) -> Rc<dyn Matrix>;
33    fn eval_h(
34        &mut self,
35        x: &dyn Vector,
36        obj_factor: Number,
37        y_c: &dyn Vector,
38        y_d: &dyn Vector,
39    ) -> Rc<dyn SymMatrix>;
40}
41
42/// Algorithm-side NLP (adds scaling-aware variants and provides the
43/// bound expansion matrices `Px_L`, `Px_U`, `Pd_L`, `Pd_U`). Mirrors
44/// upstream `Ipopt::IpoptNLP`.
45pub trait IpoptNlp: Nlp {
46    fn x_l(&self) -> &dyn Vector;
47    fn x_u(&self) -> &dyn Vector;
48    fn d_l(&self) -> &dyn Vector;
49    fn d_u(&self) -> &dyn Vector;
50
51    /// Bound expansion matrices: `Px_L` extracts the
52    /// `x` components that have a finite lower bound, etc.
53    fn px_l(&self) -> Rc<dyn Matrix>;
54    fn px_u(&self) -> Rc<dyn Matrix>;
55    fn pd_l(&self) -> Rc<dyn Matrix>;
56    fn pd_u(&self) -> Rc<dyn Matrix>;
57
58    /// Fill `x` with the initial primal values (mirrors upstream
59    /// `IpoptNLP::GetStartingPoint`'s `init_x` flag). Default impl
60    /// leaves `x` at its current contents (typically the zero vector
61    /// produced by `make_new`).
62    fn get_starting_x(&mut self, _x: &mut dyn Vector) -> bool {
63        true
64    }
65
66    /// Fill `y_c` / `y_d` with initial multiplier guesses (mirrors
67    /// `IpoptNLP::GetStartingPoint`'s `init_lambda` flag). Default
68    /// impl leaves them at their current contents (zeros).
69    fn get_starting_y(&mut self, _y_c: &mut dyn Vector, _y_d: &mut dyn Vector) -> bool {
70        true
71    }
72
73    /// Fill `z_l` / `z_u` / `v_l` / `v_u` with initial bound-multiplier
74    /// guesses (mirrors `init_z`). Default impl leaves them at zeros.
75    #[allow(clippy::too_many_arguments)]
76    fn get_starting_z(
77        &mut self,
78        _z_l: &mut dyn Vector,
79        _z_u: &mut dyn Vector,
80        _v_l: &mut dyn Vector,
81        _v_u: &mut dyn Vector,
82    ) -> bool {
83        true
84    }
85
86    /// Lift a compressed `x_var` (length `n_x_var`) to the full-x
87    /// length (`n_full_x` = user TNLP's `n`), splicing fixed-variable
88    /// values back in. Used at finalize-solution time to hand the user
89    /// a full-length x. Default impl returns x as-is, valid when the
90    /// problem has no fixed variables.
91    fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
92        let dx = x
93            .as_any()
94            .downcast_ref::<DenseVector>()
95            .expect("IpoptNlp::lift_x_to_full expects DenseVector");
96        dx.expanded_values().to_vec()
97    }
98
99    /// Pack the algorithm-side `(y_c, y_d)` constraint multipliers into
100    /// the user TNLP's `lambda` array (length `n_full_g`, ordered by
101    /// the original `g` index). Used by `GetIpoptCurrentIterate` and
102    /// `finalize_solution`. Default impl returns an empty vector — the
103    /// canonical `OrigIpoptNlp` implementation overrides it to perform
104    /// the c/d-split inverse and scaling unwind.
105    fn pack_lambda_for_user(&self, _y_c: &dyn Vector, _y_d: &dyn Vector) -> Vec<Number> {
106        Vec::new()
107    }
108
109    /// Pack the algorithm-side `(c, d)` constraint values into the user
110    /// TNLP's `g` array (length `n_full_g`, ordered by the original `g`
111    /// index, in user-unscaled space). Default impl returns an empty
112    /// vector; `OrigIpoptNlp` overrides.
113    fn pack_g_for_user(&self, _c: &dyn Vector, _d: &dyn Vector) -> Vec<Number> {
114        Vec::new()
115    }
116
117    /// Expand a compressed lower-bound-multiplier vector
118    /// (length = number of finite-lower-bound free variables) into the
119    /// user TNLP's full-`n` length `z_L` array. Default impl returns an
120    /// empty vector; `OrigIpoptNlp` overrides.
121    fn pack_z_l_for_user(&self, _z_l: &dyn Vector) -> Vec<Number> {
122        Vec::new()
123    }
124
125    /// Expand a compressed upper-bound-multiplier vector into the user
126    /// TNLP's full-`n` length `z_U` array. Default impl returns an
127    /// empty vector; `OrigIpoptNlp` overrides.
128    fn pack_z_u_for_user(&self, _z_u: &dyn Vector) -> Vec<Number> {
129        Vec::new()
130    }
131
132    /// Number of variables `n` as the user TNLP declared it (= `n_full_x`,
133    /// before fixed-variable elimination). Used by inspector entry
134    /// points that need to size full-`n` buffers. Default impl returns
135    /// 0; `OrigIpoptNlp` overrides.
136    fn n_full_x(&self) -> Index {
137        0
138    }
139
140    /// Number of constraints `m` as the user TNLP declared it (= `n_full_g`).
141    /// Default impl returns 0; `OrigIpoptNlp` overrides.
142    fn n_full_g(&self) -> Index {
143        0
144    }
145
146    /// Lift the algorithm-side `(y_c, y_d)` multipliers back to the
147    /// user TNLP's `lambda` array (length `m_full = n_c + n_d`),
148    /// matching upstream `IpOrigIpoptNLP::FinalizeSolution`. Sibling
149    /// to `pack_lambda_for_user`; added by pounce#11 for the
150    /// `finalize_solution` path. Default returns empty; `OrigIpoptNlp`
151    /// overrides.
152    fn finalize_solution_lambda(&self, _y_c: &dyn Vector, _y_d: &dyn Vector) -> Vec<Number> {
153        Vec::new()
154    }
155
156    /// Lift compressed `z_l` back to full-x. Sibling to
157    /// `pack_z_l_for_user`; added by pounce#11. Default returns empty.
158    fn finalize_solution_z_l(&self, _z_l: &dyn Vector) -> Vec<Number> {
159        Vec::new()
160    }
161
162    /// Lift compressed `z_u` back to full-x. Sibling to
163    /// `pack_z_u_for_user`; added by pounce#11. Default returns empty.
164    fn finalize_solution_z_u(&self, _z_u: &dyn Vector) -> Vec<Number> {
165        Vec::new()
166    }
167
168    /// Map a 0-based **full-x** index (user-TNLP space, length
169    /// `n_full_x()`) to a 0-based **var-x** index (algorithm-side,
170    /// length `n()`). Returns `None` when the variable was eliminated
171    /// because `x_l[i] == x_u[i]` under
172    /// `fixed_variable_treatment = make_parameter`.
173    ///
174    /// Default impl assumes no fixed variables (identity mapping). The
175    /// `OrigIpoptNlp` implementation consults
176    /// `BoundClassification::full_to_var`.
177    fn full_x_to_var_x(&self, full_idx: Index) -> Option<Index> {
178        Some(full_idx)
179    }
180
181    /// Map a 0-based **full-g** index (user-TNLP space, length
182    /// `n_full_g()`) to a 0-based position in the c-block (algorithm-side
183    /// equality multiplier vector `y_c`, length `m_eq()`). Returns
184    /// `None` when the constraint is an inequality (lives in `d`, not
185    /// `c`).
186    ///
187    /// Default impl assumes the c-block matches the user's g order
188    /// (no c/d split); `OrigIpoptNlp` overrides via
189    /// `BoundClassification::c_map`.
190    fn full_g_to_c_block(&self, full_idx: Index) -> Option<Index> {
191        Some(full_idx)
192    }
193
194    /// Inverse of [`Self::full_x_to_var_x`]: map a 0-based var-x index
195    /// (length `n()`) to the corresponding full-x index (length
196    /// `n_full_x()`). Used when scattering a compressed step or
197    /// iterate back into the user's full-x array.
198    ///
199    /// Default impl assumes no fixed variables (identity); `OrigIpoptNlp`
200    /// returns `classification.x_not_fixed_map[var_idx]`.
201    fn var_x_to_full_x(&self, var_idx: Index) -> Index {
202        var_idx
203    }
204
205    /// Effective objective scaling factor (`df_` upstream): the value
206    /// `f` is multiplied by inside [`Self::eval_f`]. Used to recover the
207    /// unscaled objective for display. Default `1.0` (no scaling);
208    /// `OrigIpoptNlp` overrides.
209    fn obj_scaling_factor(&self) -> Number {
210        1.0
211    }
212}