1use oximo_core::ModelKind;
2use oximo_solver::{HasUniversal, UniversalOptions};
3
4#[derive(Clone, Debug, Default)]
19pub struct PounceOptions {
20 pub universal: UniversalOptions,
21 pub tol: Option<f64>,
23 pub max_iter: Option<u32>,
25 pub print_level: Option<u32>,
27 pub mu_strategy: Option<MuStrategy>,
29 pub algorithm: Option<PounceAlgorithm>,
32 num_opts: Vec<(&'static str, f64)>,
34 int_opts: Vec<(&'static str, i32)>,
35 str_opts: Vec<(&'static str, String)>,
36 bool_opts: Vec<(&'static str, bool)>,
37 pub extra: Vec<(String, PounceOptionValue)>,
39}
40
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum MuStrategy {
44 Monotone,
45 Adaptive,
46}
47
48#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
56pub enum PounceAlgorithm {
57 #[default]
59 InteriorPoint,
60 ActiveSetSqp,
62}
63
64impl PounceAlgorithm {
65 pub(crate) const fn as_str(self) -> &'static str {
66 match self {
67 Self::InteriorPoint => "interior-point",
68 Self::ActiveSetSqp => "active-set-sqp",
69 }
70 }
71
72 pub(crate) const fn supports(self, kind: ModelKind) -> bool {
73 match self {
74 Self::InteriorPoint | Self::ActiveSetSqp => {
75 matches!(kind, ModelKind::LP | ModelKind::QP | ModelKind::QCP | ModelKind::NLP)
76 }
77 }
78 }
79}
80
81#[derive(Clone, Debug, PartialEq)]
83pub enum PounceOptionValue {
84 Num(f64),
85 Int(i32),
86 Str(String),
87 Bool(bool),
88}
89
90impl From<f64> for PounceOptionValue {
91 fn from(v: f64) -> Self {
92 Self::Num(v)
93 }
94}
95
96impl From<i32> for PounceOptionValue {
97 fn from(v: i32) -> Self {
98 Self::Int(v)
99 }
100}
101
102impl From<&str> for PounceOptionValue {
103 fn from(v: &str) -> Self {
104 Self::Str(v.to_owned())
105 }
106}
107
108impl From<bool> for PounceOptionValue {
109 fn from(v: bool) -> Self {
110 Self::Bool(v)
111 }
112}
113
114macro_rules! pounce_options {
117 ($( ($kind:ident, $method:ident, $tag:literal) ),* $(,)?) => {
118 $(pounce_options!(@impl $kind, $method, $tag);)*
119 };
120 (@impl num, $method:ident, $tag:literal) => {
121 #[doc = concat!("Set the POUNCE `", $tag, "` option.")]
122 #[must_use]
123 pub fn $method(mut self, v: f64) -> Self {
124 self.num_opts.push(($tag, v));
125 self
126 }
127 };
128 (@impl int, $method:ident, $tag:literal) => {
129 #[doc = concat!("Set the POUNCE `", $tag, "` option.")]
130 #[must_use]
131 pub fn $method(mut self, v: i32) -> Self {
132 self.int_opts.push(($tag, v));
133 self
134 }
135 };
136 (@impl str, $method:ident, $tag:literal) => {
137 #[doc = concat!("Set the POUNCE `", $tag, "` option.")]
138 #[must_use]
139 pub fn $method(mut self, v: impl Into<String>) -> Self {
140 self.str_opts.push(($tag, v.into()));
141 self
142 }
143 };
144 (@impl bool, $method:ident, $tag:literal) => {
145 #[doc = concat!("Set the POUNCE `", $tag, "` option.")]
146 #[must_use]
147 pub fn $method(mut self, v: bool) -> Self {
148 self.bool_opts.push(($tag, v));
149 self
150 }
151 };
152}
153
154impl PounceOptions {
155 pounce_options!(
156 (str, mu_oracle, "mu_oracle"),
158 (num, mu_init, "mu_init"),
159 (num, mu_min, "mu_min"),
160 (num, mu_max, "mu_max"),
161 (num, mu_max_fact, "mu_max_fact"),
162 (num, mu_target, "mu_target"),
163 (num, mu_linear_decrease_factor, "mu_linear_decrease_factor"),
164 (num, mu_superlinear_decrease_power, "mu_superlinear_decrease_power"),
165 (num, barrier_tol_factor, "barrier_tol_factor"),
166 (num, sigma_max, "sigma_max"),
167 (num, sigma_min, "sigma_min"),
168 (str, adaptive_mu_globalization, "adaptive_mu_globalization"),
169 (str, quality_function_norm_type, "quality_function_norm_type"),
171 (str, quality_function_centrality, "quality_function_centrality"),
172 (str, quality_function_balancing_term, "quality_function_balancing_term"),
173 (int, quality_function_max_section_steps, "quality_function_max_section_steps"),
174 (num, quality_function_section_sigma_tol, "quality_function_section_sigma_tol"),
175 (num, quality_function_section_qf_tol, "quality_function_section_qf_tol"),
176 (num, adaptive_mu_safeguard_factor, "adaptive_mu_safeguard_factor"),
178 (num, adaptive_mu_monotone_init_factor, "adaptive_mu_monotone_init_factor"),
179 (bool, adaptive_mu_restore_previous_iterate, "adaptive_mu_restore_previous_iterate"),
180 (int, adaptive_mu_kkterror_red_iters, "adaptive_mu_kkterror_red_iters"),
181 (num, adaptive_mu_kkterror_red_fact, "adaptive_mu_kkterror_red_fact"),
182 (str, adaptive_mu_kkt_norm_type, "adaptive_mu_kkt_norm_type"),
183 (bool, l1_exact_penalty_barrier, "l1_exact_penalty_barrier"),
185 (bool, l1_fallback_on_restoration_failure, "l1_fallback_on_restoration_failure"),
186 (num, l1_penalty_init, "l1_penalty_init"),
187 (num, l1_penalty_max, "l1_penalty_max"),
188 (num, l1_penalty_increase_factor, "l1_penalty_increase_factor"),
189 (int, l1_penalty_max_outer_iter, "l1_penalty_max_outer_iter"),
190 (num, l1_slack_tol, "l1_slack_tol"),
191 (num, l1_steering_factor, "l1_steering_factor"),
192 (bool, presolve, "presolve"),
194 (bool, presolve_bound_tightening, "presolve_bound_tightening"),
195 (bool, presolve_redundant_constraint_removal, "presolve_redundant_constraint_removal"),
196 (bool, presolve_linear_eq_reduction, "presolve_linear_eq_reduction"),
197 (bool, presolve_licq_check, "presolve_licq_check"),
198 (str, presolve_licq_action, "presolve_licq_action"),
199 (bool, presolve_warm_z_bounds, "presolve_warm_z_bounds"),
200 (num, presolve_bound_mult_init_val, "presolve_bound_mult_init_val"),
201 (int, presolve_max_passes, "presolve_max_passes"),
202 (int, presolve_print_level, "presolve_print_level"),
203 (bool, presolve_fbbt, "presolve_fbbt"),
205 (num, fbbt_tol, "fbbt_tol"),
206 (int, fbbt_max_iter, "fbbt_max_iter"),
207 (int, fbbt_max_constraints, "fbbt_max_constraints"),
208 (bool, presolve_auxiliary, "presolve_auxiliary"),
210 (str, presolve_auxiliary_coupling, "presolve_auxiliary_coupling"),
211 (num, presolve_auxiliary_tol, "presolve_auxiliary_tol"),
212 (int, presolve_auxiliary_max_block_dim, "presolve_auxiliary_max_block_dim"),
213 (num, presolve_auxiliary_wall_time_fraction, "presolve_auxiliary_wall_time_fraction"),
214 (bool, presolve_auxiliary_diagnostics, "presolve_auxiliary_diagnostics"),
215 (str, linear_solver, "linear_solver"),
220 (str, feral_ordering, "feral_ordering"),
221 (str, feral_scaling, "feral_scaling"),
222 (num, feral_pivtol, "feral_pivtol"),
223 (bool, feral_refine, "feral_refine"),
224 (bool, feral_cascade_break, "feral_cascade_break"),
225 (bool, feral_fma, "feral_fma"),
226 (num, feral_singular_pivot_floor, "feral_singular_pivot_floor"),
227 );
228
229 #[must_use]
230 pub fn tol(mut self, tol: f64) -> Self {
231 self.tol = Some(tol);
232 self
233 }
234
235 #[must_use]
236 pub fn max_iter(mut self, n: u32) -> Self {
237 self.max_iter = Some(n);
238 self
239 }
240
241 #[must_use]
242 pub fn print_level(mut self, level: u32) -> Self {
243 self.print_level = Some(level);
244 self
245 }
246
247 #[must_use]
248 pub fn mu_strategy(mut self, s: MuStrategy) -> Self {
249 self.mu_strategy = Some(s);
250 self
251 }
252
253 #[must_use]
255 pub fn algorithm(mut self, algorithm: PounceAlgorithm) -> Self {
256 self.algorithm = Some(algorithm);
257 self
258 }
259
260 #[must_use]
264 pub fn set(mut self, name: impl Into<String>, value: impl Into<PounceOptionValue>) -> Self {
265 self.extra.push((name.into(), value.into()));
266 self
267 }
268
269 pub(crate) fn num_opts(&self) -> &[(&'static str, f64)] {
270 &self.num_opts
271 }
272
273 pub(crate) fn int_opts(&self) -> &[(&'static str, i32)] {
274 &self.int_opts
275 }
276
277 pub(crate) fn str_opts(&self) -> &[(&'static str, String)] {
278 &self.str_opts
279 }
280
281 pub(crate) fn bool_opts(&self) -> &[(&'static str, bool)] {
282 &self.bool_opts
283 }
284}
285
286impl HasUniversal for PounceOptions {
287 fn universal(&self) -> &UniversalOptions {
288 &self.universal
289 }
290
291 fn universal_mut(&mut self) -> &mut UniversalOptions {
292 &mut self.universal
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 #[test]
301 fn typed_setters_push_onto_the_right_vecs() {
302 let o = PounceOptions::default()
303 .mu_oracle("probing")
304 .mu_init(0.05)
305 .presolve(true)
306 .presolve_max_passes(5)
307 .feral_refine(false);
308 assert_eq!(o.str_opts, vec![("mu_oracle", "probing".to_owned())]);
309 assert_eq!(o.num_opts, vec![("mu_init", 0.05)]);
310 assert_eq!(o.int_opts, vec![("presolve_max_passes", 5)]);
311 assert_eq!(o.bool_opts, vec![("presolve", true), ("feral_refine", false)]);
312 }
313
314 #[test]
315 fn default_vecs_are_empty() {
316 let o = PounceOptions::default();
317 assert!(o.num_opts.is_empty());
318 assert!(o.int_opts.is_empty());
319 assert!(o.str_opts.is_empty());
320 assert!(o.bool_opts.is_empty());
321 assert!(o.extra.is_empty());
322 }
323
324 #[test]
325 fn same_option_twice_keeps_both_entries() {
326 let o = PounceOptions::default().mu_init(0.1).mu_init(0.5);
327 assert_eq!(o.num_opts, vec![("mu_init", 0.1), ("mu_init", 0.5)]);
328 }
329
330 #[test]
331 fn clone_preserves_all_vecs() {
332 let o = PounceOptions::default().mu_init(0.1).presolve_max_passes(2).presolve(true);
333 let c = o.clone();
334 assert_eq!(o.num_opts, c.num_opts);
335 assert_eq!(o.int_opts, c.int_opts);
336 assert_eq!(o.bool_opts, c.bool_opts);
337 }
338
339 #[test]
340 fn set_pushes_onto_extra_with_bool() {
341 let o = PounceOptions::default().set("presolve", true).set("acceptable_tol", 1e-5);
342 assert_eq!(
343 o.extra,
344 vec![
345 ("presolve".to_owned(), PounceOptionValue::Bool(true)),
346 ("acceptable_tol".to_owned(), PounceOptionValue::Num(1e-5)),
347 ]
348 );
349 }
350}