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}