Skip to main content

pounce_algorithm/
ipopt_data.rs

1//! Mutable algorithm state — port of `Algorithm/IpIpoptData.{hpp,cpp}`.
2//!
3//! Holds the iterate trio (`curr` / `trial` / `delta`), the affine
4//! step `delta_aff`, current barrier parameter `mu`, fraction-to-the-
5//! boundary `tau`, iteration count, tolerances, and the four PD
6//! perturbations. No additional-data plug in v1.0 (CG penalty is
7//! deferred to Phase 10).
8//!
9//! Phase 5 ships the full state holder; concrete strategies (line
10//! search, mu update, etc.) read/write fields here as their inputs
11//! and outputs.
12
13use crate::iterates_vector::IteratesVector;
14use pounce_common::timing::TimingStatistics;
15use pounce_common::types::{Index, Number};
16use pounce_linalg::SymMatrix;
17use std::cell::RefCell;
18use std::rc::Rc;
19
20/// Primal-dual perturbation triple (`delta_x`, `delta_s`, `delta_c`,
21/// `delta_d`) — port of the four `current_perturbation` fields in
22/// `IpIpoptData.hpp`.
23#[derive(Debug, Default, Clone, Copy)]
24pub struct PdPerturbations {
25    pub delta_x: Number,
26    pub delta_s: Number,
27    pub delta_c: Number,
28    pub delta_d: Number,
29}
30
31/// Mutable state passed down through the algorithm. Owned by
32/// `IpoptAlgorithm`; strategies access via `Rc<RefCell<IpoptData>>`.
33pub struct IpoptData {
34    pub curr: Option<IteratesVector>,
35    pub trial: Option<IteratesVector>,
36    pub delta: Option<IteratesVector>,
37    pub delta_aff: Option<IteratesVector>,
38    /// Pure centering step — solution of the primal-dual system with
39    /// RHS `(0, 0, 0, 0, μ̄·1, μ̄·1, μ̄·1, μ̄·1)` (where μ̄ = avrg_compl)
40    /// per upstream `IpQualityFunctionMuOracle.cpp:227-247`. Used by
41    /// the quality-function oracle to assemble σ-step trial points
42    /// without re-factorising for each candidate σ.
43    pub delta_cen: Option<IteratesVector>,
44
45    /// Hessian of the Lagrangian for the *current* iterate. Set by
46    /// `HessianUpdater` (exact or quasi-Newton). Mirrors `IpIpoptData::W_`.
47    pub w: Option<Rc<dyn SymMatrix>>,
48
49    pub iter_count: Index,
50    pub curr_mu: Number,
51    pub curr_tau: Number,
52    pub tol: Number,
53
54    pub perturbations: PdPerturbations,
55
56    /// Set after a successful trial-acceptance step in the line
57    /// search. Cleared on accept.
58    pub info_alpha_primal: Number,
59    pub info_alpha_dual: Number,
60
61    /// Mirrors `IpIpoptData::info_regu_x_`.
62    pub info_regu_x: Number,
63
64    /// Mirrors `IpIpoptData::info_skip_output_`.
65    pub info_skip_output: bool,
66
67    /// Mirrors `IpIpoptData::info_string_`. Free-form text the
68    /// iteration output appends to its line.
69    pub info_string: String,
70
71    /// Mirrors `IpIpoptData::tiny_step_flag_`. Set by the line search
72    /// when an alpha→0 trial is detected; the main loop reads it on
73    /// the next pass to decide between "tiny step accept" and bail.
74    pub tiny_step_flag: bool,
75
76    /// Emergency restoration request from the μ-update layer. Set by
77    /// [`AdaptiveMuUpdate`] when the probing oracle's input iterate
78    /// is corrupted (`curr_avrg_compl` ≫ `curr_mu`) so the main loop
79    /// invokes restoration instead of letting the oracle snap μ up
80    /// many orders of magnitude. Pounce-specific guard; no upstream
81    /// counterpart. See pounce#58.
82    pub request_resto: bool,
83
84    /// One-char marker the iteration output puts in front of
85    /// `alpha_primal` (e.g. `'f'` for filter, `'r'` for restoration,
86    /// `'h'` for the very first iterate). Mirrors
87    /// `IpIpoptData::info_alpha_primal_char_`.
88    pub info_alpha_primal_char: char,
89
90    /// Number of trial points evaluated in the most recent line
91    /// search. Mirrors `IpIpoptData::info_ls_count_`.
92    pub info_ls_count: Index,
93
94    /// The wall-clock at the last `OrigIterationOutput::WriteOutput`
95    /// pass. Phase 7 uses this to decide whether to re-print the
96    /// header. Mirrors `IpIpoptData::info_last_output_`.
97    pub info_last_output: Number,
98
99    /// Iterations since the iteration header was last printed. Phase
100    /// 7 reprints every `print_frequency_iter` lines. Mirrors
101    /// `IpIpoptData::info_iters_since_header_`.
102    pub info_iters_since_header: Index,
103
104    /// Shared per-subsystem timing accumulator. Mirrors upstream's
105    /// `IpoptData::TimingStats_`. `IpoptApplication` constructs a single
106    /// instance per solve and shares it (via `Rc`) with the algorithm,
107    /// NLP, and KKT solver so each can record its own contribution.
108    /// Defaults to a fresh empty instance for the structural unit tests
109    /// that don't go through `IpoptApplication`.
110    pub timing: Rc<TimingStatistics>,
111}
112
113impl Default for IpoptData {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119impl IpoptData {
120    pub fn new() -> Self {
121        Self {
122            curr: None,
123            trial: None,
124            delta: None,
125            delta_aff: None,
126            delta_cen: None,
127            w: None,
128            iter_count: 0,
129            curr_mu: 0.1,
130            curr_tau: 0.99,
131            tol: 1e-8,
132            perturbations: PdPerturbations::default(),
133            info_alpha_primal: 0.0,
134            info_alpha_dual: 0.0,
135            info_regu_x: 0.0,
136            info_skip_output: false,
137            info_string: String::new(),
138            tiny_step_flag: false,
139            request_resto: false,
140            info_alpha_primal_char: ' ',
141            info_ls_count: 0,
142            info_last_output: -1.0,
143            info_iters_since_header: 0,
144            timing: Rc::new(TimingStatistics::new()),
145        }
146    }
147
148    /// Append text to `info_string`. Mirrors `IpIpoptData::Append_info_string`.
149    pub fn append_info_string(&mut self, s: &str) {
150        self.info_string.push_str(s);
151    }
152
153    /// Reset per-iteration info fields. Mirrors the top of
154    /// `IpoptAlgorithm::Optimize`'s loop body.
155    pub fn reset_info(&mut self) {
156        self.info_string.clear();
157        self.info_skip_output = false;
158        self.info_alpha_primal_char = ' ';
159        self.info_ls_count = 0;
160        self.info_regu_x = 0.0;
161    }
162
163    /// Replace `curr` with the previously-set `trial`. Mirrors
164    /// `IpIpoptData::AcceptTrialPoint`.
165    pub fn accept_trial_point(&mut self) {
166        self.curr = self.trial.take();
167    }
168
169    /// Set the trial iterate from a primal step `delta_x`/`delta_s`
170    /// scaled by `alpha_p` and a dual step scaled by `alpha_d`.
171    /// Phase 5 ships only the structural plumbing; the actual
172    /// arithmetic is implemented once the line search lands in
173    /// Phase 7.
174    pub fn set_trial(&mut self, trial: IteratesVector) {
175        self.trial = Some(trial);
176    }
177
178    pub fn set_curr(&mut self, curr: IteratesVector) {
179        self.curr = Some(curr);
180    }
181
182    pub fn set_delta(&mut self, d: IteratesVector) {
183        self.delta = Some(d);
184    }
185
186    pub fn set_delta_aff(&mut self, d: IteratesVector) {
187        self.delta_aff = Some(d);
188    }
189
190    pub fn set_delta_cen(&mut self, d: IteratesVector) {
191        self.delta_cen = Some(d);
192    }
193}
194
195/// Convenience handle. Mirrors how upstream passes the data object
196/// around as `SmartPtr<IpoptData>`.
197pub type IpoptDataHandle = Rc<RefCell<IpoptData>>;
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use crate::iterates_vector::IteratesVector;
203    use pounce_linalg::dense_vector::DenseVectorSpace;
204    use pounce_linalg::Vector;
205    use std::rc::Rc as StdRc;
206
207    fn zero_iv() -> IteratesVector {
208        let z = |n| StdRc::new(DenseVectorSpace::new(n).make_new_dense()) as StdRc<dyn Vector>;
209        IteratesVector::new(z(2), z(1), z(1), z(1), z(2), z(2), z(1), z(1))
210    }
211
212    #[test]
213    fn accept_trial_point_promotes_trial_to_curr() {
214        let mut d = IpoptData::new();
215        d.set_trial(zero_iv());
216        assert!(d.curr.is_none());
217        d.accept_trial_point();
218        assert!(d.curr.is_some());
219        assert!(d.trial.is_none());
220    }
221}