1use crate::conv_check::opt_error::OptErrorConvCheck;
24use crate::eq_mult::least_square::LeastSquareMults;
25use crate::hess::exact::ExactHessianUpdater;
26use crate::hess::lim_mem_quasi_newton::{LimMemQuasiNewtonUpdater, UpdateType};
27use crate::init::default::DefaultIterateInitializer;
28use crate::init::warm_start::WarmStartIterateInitializer;
29use crate::kkt::pd_full_space_solver::PdFullSpaceSolver;
30use crate::kkt::pd_search_dir_calc::PdSearchDirCalc;
31use crate::kkt::perturbation_handler::PdPerturbationHandler;
32use crate::kkt::std_aug_system_solver::StdAugSystemSolver;
33use crate::line_search::backtracking::BacktrackingLineSearch;
34use crate::line_search::filter_acceptor::FilterLsAcceptor;
35use crate::line_search::ls_acceptor::BacktrackingLsAcceptor;
36use crate::line_search::penalty_acceptor::PenaltyLsAcceptor;
37use crate::mu::adaptive::{AdaptiveMuUpdate, MuOracleKind};
38use crate::mu::monotone::MonotoneMuUpdate;
39use crate::output::orig::OrigIterationOutput;
40use pounce_common::types::{Index, Number};
41use pounce_linsol::{SparseSymLinearSolverInterface, TSymLinearSolver};
42use std::cell::RefCell;
43use std::rc::Rc;
44
45pub type LinearBackendFactory =
51 Box<dyn FnMut(LinearSolverChoice) -> Box<dyn SparseSymLinearSolverInterface>>;
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum LinearSolverChoice {
55 Ma57,
56 Feral,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum MuStrategyChoice {
61 Monotone,
62 Adaptive,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum HessianApproxChoice {
67 Exact,
68 LimitedMemory,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum LineSearchChoice {
73 Filter,
74 CgPenalty,
75 Penalty,
76}
77
78pub struct AlgorithmBundle {
81 pub mu_update: Box<dyn crate::mu::r#trait::MuUpdate>,
82 pub conv_check: Box<dyn crate::conv_check::r#trait::ConvCheck>,
83 pub init: Box<dyn crate::init::r#trait::IterateInitializer>,
84 pub eq_mult: Box<dyn crate::eq_mult::r#trait::EqMultCalculator>,
85 pub hess: Box<dyn crate::hess::r#trait::HessianUpdater>,
86 pub line_search: BacktrackingLineSearch,
87 pub iter_output: Box<dyn crate::output::r#trait::IterationOutput>,
88 pub search_dir: Option<PdSearchDirCalc>,
92}
93
94#[derive(Debug, Clone)]
98pub struct ConvCheckOptions {
99 pub tol: Number,
100 pub dual_inf_tol: Number,
101 pub constr_viol_tol: Number,
102 pub compl_inf_tol: Number,
103 pub acceptable_tol: Number,
104 pub acceptable_dual_inf_tol: Number,
105 pub acceptable_constr_viol_tol: Number,
106 pub acceptable_compl_inf_tol: Number,
107 pub acceptable_obj_change_tol: Number,
108 pub acceptable_iter: Index,
109 pub max_iter: Index,
110 pub max_cpu_time: Number,
111 pub max_wall_time: Number,
112 pub infeas_stationarity_tol: Number,
113 pub infeas_viol_kappa: Number,
114 pub infeas_max_streak: Index,
115}
116
117impl Default for ConvCheckOptions {
118 fn default() -> Self {
119 Self {
120 tol: 1e-8,
121 dual_inf_tol: 1.0,
122 constr_viol_tol: 1e-4,
123 compl_inf_tol: 1e-4,
124 acceptable_tol: 1e-6,
125 acceptable_dual_inf_tol: 1e10,
126 acceptable_constr_viol_tol: 1e-2,
127 acceptable_compl_inf_tol: 1e-2,
128 acceptable_obj_change_tol: 1e20,
129 acceptable_iter: 15,
130 max_iter: 3000,
131 max_cpu_time: 1e6,
132 max_wall_time: 1e6,
133 infeas_stationarity_tol: 1e-8,
134 infeas_viol_kappa: 1e2,
135 infeas_max_streak: 5,
136 }
137 }
138}
139
140#[derive(Debug, Clone)]
141pub struct AlgorithmBuilder {
142 pub linear_solver: LinearSolverChoice,
143 pub mu_strategy: MuStrategyChoice,
144 pub mu_oracle: MuOracleKind,
148 pub hessian_approximation: HessianApproxChoice,
149 pub limited_memory_update_type: UpdateType,
150 pub line_search_method: LineSearchChoice,
151 pub warm_start_init_point: bool,
152 pub conv_check: ConvCheckOptions,
153 pub mu: MuOptions,
154 pub line_search: LineSearchOptions,
155 pub output: OutputOptions,
156 pub warm: WarmStartOptions,
157}
158
159#[derive(Debug, Clone)]
172pub struct WarmStartOptions {
173 pub bound_push: Number,
174 pub bound_frac: Number,
175 pub slack_bound_push: Number,
176 pub slack_bound_frac: Number,
177 pub mult_bound_push: Number,
178 pub mult_init_max: Number,
179 pub target_mu: Number,
180 pub entire_iterate: bool,
181 pub same_structure: bool,
182}
183
184impl Default for WarmStartOptions {
185 fn default() -> Self {
186 Self {
187 bound_push: 1e-3,
188 bound_frac: 1e-3,
189 slack_bound_push: 1e-3,
190 slack_bound_frac: 1e-3,
191 mult_bound_push: 1e-3,
192 mult_init_max: 1e6,
193 target_mu: 0.0,
194 entire_iterate: false,
195 same_structure: false,
196 }
197 }
198}
199
200#[derive(Debug, Clone)]
207pub struct MuOptions {
208 pub mu_init: Number,
209 pub mu_max: Number,
210 pub mu_max_fact: Number,
211 pub mu_min: Number,
212 pub mu_target: Number,
213 pub mu_linear_decrease_factor: Number,
214 pub mu_superlinear_decrease_power: Number,
215 pub mu_allow_fast_monotone_decrease: bool,
216 pub barrier_tol_factor: Number,
217 pub sigma_max: Number,
222 pub sigma_min: Number,
223}
224
225impl Default for MuOptions {
226 fn default() -> Self {
227 Self {
228 mu_init: 0.1,
229 mu_max: -1.0,
230 mu_max_fact: 1e3,
231 mu_min: 1e-11,
232 mu_target: 0.0,
233 mu_linear_decrease_factor: 0.2,
234 mu_superlinear_decrease_power: 1.5,
235 mu_allow_fast_monotone_decrease: true,
236 barrier_tol_factor: 10.0,
237 sigma_max: 1e2,
238 sigma_min: 1e-6,
239 }
240 }
241}
242
243#[derive(Debug, Clone)]
246pub struct LineSearchOptions {
247 pub watchdog_shortened_iter_trigger: Index,
248 pub watchdog_trial_iter_max: Index,
249 pub soft_resto_pderror_reduction_factor: Number,
253 pub max_soft_resto_iters: Index,
256}
257
258impl Default for LineSearchOptions {
259 fn default() -> Self {
260 Self {
261 watchdog_shortened_iter_trigger: 10,
262 watchdog_trial_iter_max: 3,
263 soft_resto_pderror_reduction_factor: 1.0 - 1e-4,
264 max_soft_resto_iters: 10,
265 }
266 }
267}
268
269#[derive(Debug, Clone)]
273pub struct OutputOptions {
274 pub print_frequency_iter: Index,
275 pub print_frequency_time: Number,
276 pub print_info_string: bool,
280 pub inf_pr_output_internal: bool,
285}
286
287impl Default for OutputOptions {
288 fn default() -> Self {
289 Self {
290 print_frequency_iter: 1,
291 print_frequency_time: 0.0,
292 print_info_string: false,
293 inf_pr_output_internal: false,
294 }
295 }
296}
297
298impl Default for AlgorithmBuilder {
299 fn default() -> Self {
300 Self {
301 linear_solver: LinearSolverChoice::Feral,
302 mu_strategy: MuStrategyChoice::Monotone,
303 mu_oracle: MuOracleKind::QualityFunction,
304 hessian_approximation: HessianApproxChoice::Exact,
305 limited_memory_update_type: UpdateType::Bfgs,
306 line_search_method: LineSearchChoice::Filter,
307 warm_start_init_point: false,
308 conv_check: ConvCheckOptions::default(),
309 mu: MuOptions::default(),
310 line_search: LineSearchOptions::default(),
311 output: OutputOptions::default(),
312 warm: WarmStartOptions::default(),
313 }
314 }
315}
316
317impl AlgorithmBuilder {
318 pub fn new() -> Self {
319 Self::default()
320 }
321
322 pub fn build(&self) -> AlgorithmBundle {
326 self.build_inner(None)
327 }
328
329 pub fn build_with_backend(&self, mut factory: LinearBackendFactory) -> AlgorithmBundle {
333 let backend = factory(self.linear_solver);
334 let linsol = TSymLinearSolver::new(backend, None, false);
335 let aug_solver = StdAugSystemSolver::new(linsol);
336 let perturb = Rc::new(RefCell::new(PdPerturbationHandler::new()));
337 let pd_solver = PdFullSpaceSolver::new(Box::new(aug_solver), perturb);
338 let search_dir = PdSearchDirCalc::new(pd_solver);
339 self.build_inner(Some(search_dir))
340 }
341
342 fn build_inner(&self, search_dir: Option<PdSearchDirCalc>) -> AlgorithmBundle {
343 let mu_update: Box<dyn crate::mu::r#trait::MuUpdate> = match self.mu_strategy {
344 MuStrategyChoice::Monotone => {
345 let mut m = MonotoneMuUpdate::new();
346 m.mu_init = self.mu.mu_init;
347 if self.mu.mu_max > 0.0 {
350 m.mu_max = self.mu.mu_max;
351 }
352 m.mu_min = self.mu.mu_min;
353 m.mu_target = self.mu.mu_target;
354 m.mu_linear_decrease_factor = self.mu.mu_linear_decrease_factor;
355 m.mu_superlinear_decrease_power = self.mu.mu_superlinear_decrease_power;
356 m.mu_allow_fast_monotone_decrease = self.mu.mu_allow_fast_monotone_decrease;
357 m.barrier_tol_factor = self.mu.barrier_tol_factor;
358 m.compl_inf_tol = self.conv_check.compl_inf_tol;
359 Box::new(m)
360 }
361 MuStrategyChoice::Adaptive => {
362 let mut adaptive = AdaptiveMuUpdate::new();
363 adaptive.mu_oracle = self.mu_oracle;
364 adaptive.mu_init = self.mu.mu_init;
365 adaptive.mu_max = self.mu.mu_max;
369 adaptive.mu_max_fact = self.mu.mu_max_fact;
370 adaptive.mu_min = self.mu.mu_min;
371 adaptive.mu_linear_decrease_factor = self.mu.mu_linear_decrease_factor;
372 adaptive.mu_superlinear_decrease_power = self.mu.mu_superlinear_decrease_power;
373 adaptive.barrier_tol_factor = self.mu.barrier_tol_factor;
374 adaptive.sigma_min = self.mu.sigma_min;
375 adaptive.sigma_max = self.mu.sigma_max;
376 Box::new(adaptive)
377 }
378 };
379
380 let acceptor: Box<dyn BacktrackingLsAcceptor> = match self.line_search_method {
381 LineSearchChoice::Filter => Box::new(FilterLsAcceptor::default()),
382 LineSearchChoice::Penalty => Box::new(PenaltyLsAcceptor::default()),
383 LineSearchChoice::CgPenalty => Box::new(PenaltyLsAcceptor::default()),
387 };
388 let mut line_search = BacktrackingLineSearch::new(acceptor);
389 line_search.watchdog_shortened_iter_trigger =
390 self.line_search.watchdog_shortened_iter_trigger;
391 line_search.watchdog_trial_iter_max = self.line_search.watchdog_trial_iter_max;
392 line_search.soft_resto_pderror_reduction_factor =
393 self.line_search.soft_resto_pderror_reduction_factor;
394 line_search.max_soft_resto_iters = self.line_search.max_soft_resto_iters;
395
396 let conv_check: Box<dyn crate::conv_check::r#trait::ConvCheck> =
397 Box::new(OptErrorConvCheck {
398 tol: self.conv_check.tol,
399 dual_inf_tol: self.conv_check.dual_inf_tol,
400 constr_viol_tol: self.conv_check.constr_viol_tol,
401 compl_inf_tol: self.conv_check.compl_inf_tol,
402 acceptable_tol: self.conv_check.acceptable_tol,
403 acceptable_dual_inf_tol: self.conv_check.acceptable_dual_inf_tol,
404 acceptable_constr_viol_tol: self.conv_check.acceptable_constr_viol_tol,
405 acceptable_compl_inf_tol: self.conv_check.acceptable_compl_inf_tol,
406 acceptable_obj_change_tol: self.conv_check.acceptable_obj_change_tol,
407 acceptable_iter: self.conv_check.acceptable_iter,
408 max_iter: self.conv_check.max_iter,
409 max_cpu_time: self.conv_check.max_cpu_time,
410 max_wall_time: self.conv_check.max_wall_time,
411 acceptable_count: 0,
412 last_acceptable_obj: None,
413 infeas_stationarity_tol: self.conv_check.infeas_stationarity_tol,
414 infeas_viol_kappa: self.conv_check.infeas_viol_kappa,
415 infeas_max_streak: self.conv_check.infeas_max_streak,
416 infeas_streak: 0,
417 });
418
419 let init: Box<dyn crate::init::r#trait::IterateInitializer> = if self.warm_start_init_point
420 {
421 Box::new(WarmStartIterateInitializer::with_options(self.warm.clone()))
422 } else {
423 Box::new(DefaultIterateInitializer::with_eq_mult_calculator(
424 Box::new(LeastSquareMults::new()),
425 ))
426 };
427
428 let eq_mult: Box<dyn crate::eq_mult::r#trait::EqMultCalculator> =
429 Box::new(LeastSquareMults::new());
430
431 let hess: Box<dyn crate::hess::r#trait::HessianUpdater> = match self.hessian_approximation {
432 HessianApproxChoice::Exact => Box::new(ExactHessianUpdater::new()),
433 HessianApproxChoice::LimitedMemory => Box::new(LimMemQuasiNewtonUpdater {
434 update_type: self.limited_memory_update_type,
435 ..LimMemQuasiNewtonUpdater::default()
436 }),
437 };
438
439 let iter_output: Box<dyn crate::output::r#trait::IterationOutput> = {
440 use crate::output::orig::{InfPrTag, PrintInfoString};
441 let mut o = OrigIterationOutput::new();
442 o.print_frequency_iter = self.output.print_frequency_iter;
443 o.print_frequency_time = self.output.print_frequency_time;
444 o.print_info_string = if self.output.print_info_string {
445 PrintInfoString::Yes
446 } else {
447 PrintInfoString::No
448 };
449 o.inf_pr_output = if self.output.inf_pr_output_internal {
450 InfPrTag::Internal
451 } else {
452 InfPrTag::Original
453 };
454 Box::new(o)
455 };
456
457 AlgorithmBundle {
458 mu_update,
459 conv_check,
460 init,
461 eq_mult,
462 hess,
463 line_search,
464 iter_output,
465 search_dir,
466 }
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473
474 #[test]
475 fn default_builder_assembles() {
476 let bundle = AlgorithmBuilder::new().build();
477 let _ = bundle.line_search.acceptor();
480 assert!(bundle.search_dir.is_none());
481 }
482
483 #[test]
484 fn build_with_backend_assembles_search_dir_chain() {
485 let factory: LinearBackendFactory = Box::new(|_| {
488 Box::new(pounce_feral::FeralSolverInterface::new())
489 as Box<dyn SparseSymLinearSolverInterface>
490 });
491 let bundle = AlgorithmBuilder::new().build_with_backend(factory);
492 assert!(bundle.search_dir.is_some());
493 }
494
495 #[test]
496 fn limited_memory_sr1_propagates() {
497 let b = AlgorithmBuilder {
498 hessian_approximation: HessianApproxChoice::LimitedMemory,
499 limited_memory_update_type: UpdateType::Sr1,
500 ..AlgorithmBuilder::default()
501 };
502 let _bundle = b.build();
503 }
504
505 #[test]
506 fn every_strategy_combination_assembles_without_panic() {
507 let solvers = [LinearSolverChoice::Ma57, LinearSolverChoice::Feral];
508 let mu = [MuStrategyChoice::Monotone, MuStrategyChoice::Adaptive];
509 let hess = [
510 HessianApproxChoice::Exact,
511 HessianApproxChoice::LimitedMemory,
512 ];
513 let ls = [
514 LineSearchChoice::Filter,
515 LineSearchChoice::CgPenalty,
516 LineSearchChoice::Penalty,
517 ];
518 for &linear_solver in &solvers {
519 for &mu_strategy in &mu {
520 for &hessian_approximation in &hess {
521 for &line_search_method in &ls {
522 let _ = AlgorithmBuilder {
523 linear_solver,
524 mu_strategy,
525 mu_oracle: MuOracleKind::QualityFunction,
526 hessian_approximation,
527 limited_memory_update_type: UpdateType::Bfgs,
528 line_search_method,
529 warm_start_init_point: false,
530 conv_check: ConvCheckOptions::default(),
531 mu: MuOptions::default(),
532 line_search: LineSearchOptions::default(),
533 output: OutputOptions::default(),
534 warm: WarmStartOptions::default(),
535 }
536 .build();
537 }
538 }
539 }
540 }
541 }
542}