Skip to main content

pounce_algorithm/line_search/
ls_acceptor.rs

1//! Line-search acceptor trait — port of `IpBacktrackingLSAcceptor.hpp`
2//! and `IpLineSearch.hpp`.
3
4use crate::ipopt_cq::IpoptCqHandle;
5use crate::ipopt_data::IpoptDataHandle;
6use crate::iterates_vector::IteratesVector;
7use crate::line_search::filter_acceptor::AcceptDecision;
8use crate::restoration::OrigProgressCallback;
9use pounce_common::types::Number;
10
11/// Acceptor side of the backtracking line search. Concrete impls:
12/// [`super::filter_acceptor::FilterLsAcceptor`] (Phase 7),
13/// `PenaltyLsAcceptor` (Phase 10), `CGPenaltyLsAcceptor` (Phase 10).
14///
15/// The driver calls `check_trial_point` on each backtracking step.
16/// Acceptors that need the trial-iterate components (rather than
17/// scalar `(theta, phi)`) can extend this surface in later phases —
18/// the filter acceptor only needs the four scalars upstream feeds in
19/// at line `IpFilterLSAcceptor.cpp:CheckAcceptabilityOfTrialPoint`.
20pub trait BacktrackingLsAcceptor {
21    /// Reset acceptor state for a new outer iteration.
22    fn reset(&mut self);
23
24    /// Hook called once per outer iteration, after the search direction
25    /// `delta` has been computed and before the α-loop. Mirrors
26    /// `IpPenaltyLSAcceptor.cpp:InitThisLineSearch` — the penalty
27    /// acceptor uses it to snapshot reference (θ, φ, ∇φᵀδ, δᵀWδ) and to
28    /// bump the penalty parameter ν. Default: no-op (filter acceptor
29    /// has nothing to cache between α-loop iterations).
30    fn init_this_line_search(
31        &mut self,
32        _data: &IpoptDataHandle,
33        _cq: &IpoptCqHandle,
34        _delta: &IteratesVector,
35    ) {
36    }
37
38    /// Compute the minimum primal step length below which the
39    /// driver should declare a tiny step / hand off to restoration.
40    /// Mirrors `IpFilterLSAcceptor.cpp:CalculateAlphaMin` — the value
41    /// depends on the current `(theta, d_phi)` pair (the directional
42    /// derivative of the barrier objective along the search step) and
43    /// on the acceptor's lazily-initialised `theta_min`. Default impl
44    /// returns 0.0 so non-filter acceptors degenerate to the driver's
45    /// own absolute `alpha_min` floor.
46    fn calc_alpha_min(&mut self, _d_phi: Number, _theta: Number) -> Number {
47        0.0
48    }
49
50    /// Decide whether the trial `(theta_trial, phi_trial)` at primal
51    /// step `alpha_primal` is acceptable, given the current iterate's
52    /// `(theta, phi)` and the directional derivative `d_phi`.
53    /// Default: always accept (lets stub acceptors compose without
54    /// interfering with the driver's α-loop).
55    ///
56    /// Mutable receiver so concrete acceptors (notably
57    /// [`super::filter_acceptor::FilterLsAcceptor`]) can record per-trial
58    /// state used by the filter-reset heuristic
59    /// (`IpFilterLSAcceptor.cpp:407-433`).
60    fn check_trial_point(
61        &mut self,
62        _alpha_primal: Number,
63        _theta: Number,
64        _phi: Number,
65        _d_phi: Number,
66        _theta_trial: Number,
67        _phi_trial: Number,
68    ) -> AcceptDecision {
69        AcceptDecision::Accept
70    }
71
72    /// Post-accept hook — port of
73    /// `IpFilterLSAcceptor::UpdateForNextIteration`. Both decides the
74    /// `info_alpha_primal_char` tag *and* augments the filter when
75    /// upstream would. Returns:
76    ///
77    /// * `'f'` — F-type Armijo step (`IsFtype && ArmijoHolds`); filter
78    ///   is **not** augmented.
79    /// * `'h'` — anything else (`!IsFtype || !ArmijoHolds`); filter
80    ///   **is** augmented with `(theta_add, phi_add) = ((1 - γ_θ)·θ_ref,
81    ///   φ_ref - γ_φ·θ_ref)`.
82    ///
83    /// The driver calls this once per accepted step, after
84    /// `check_trial_point` returns Accept and before
85    /// `accept_trial_point` promotes `trial → curr`. Default impl
86    /// returns `'h'` (no filter), so non-filter acceptors remain valid.
87    fn update_for_next_iteration(
88        &mut self,
89        _alpha_primal: Number,
90        _theta: Number,
91        _phi: Number,
92        _d_phi: Number,
93        _phi_trial: Number,
94    ) -> char {
95        'h'
96    }
97
98    /// Build the orig-progress callback the inner restoration IPM
99    /// should consult to decide whether the recovered iterate is
100    /// acceptable to *this* (outer) acceptor's filter and reference
101    /// iterate. Mirrors upstream
102    /// `IpRestoFilterConvCheck::SetOrigLSAcceptor` /
103    /// `TestOrigProgress`. Default returns `None` — penalty / CG-penalty
104    /// acceptors do not gate restoration on a filter, so they fall
105    /// through to the kappa-reduction-only guard.
106    ///
107    /// `reference_theta` and `reference_barr` are the outer iterate's
108    /// `(curr_constraint_violation, curr_barrier_obj)` at restoration
109    /// entry; `obj_max_inc` is the upstream `obj_max_inc` option
110    /// (default 5.0).
111    fn make_orig_progress_check(
112        &self,
113        _reference_theta: Number,
114        _reference_barr: Number,
115        _obj_max_inc: Number,
116    ) -> Option<OrigProgressCallback> {
117        None
118    }
119
120    /// Hook called by the algorithm immediately before invoking the
121    /// restoration phase — port of
122    /// `IpFilterLSAcceptor::PrepareRestoPhaseStart` →
123    /// `AugmentFilter` (`IpFilterLSAcceptor.cpp:898-901`, called from
124    /// `IpBacktrackingLineSearch.cpp:566`). The filter acceptor
125    /// augments the filter with the resto-entry iterate's shrunk
126    /// envelope `((1 - γ_θ)·θ_ref, φ_ref - γ_φ·θ_ref)`. After
127    /// restoration recovers, the outer's Newton step is then forced
128    /// by the filter to make real progress vs the entry point —
129    /// without this, the outer can accept null-progress 'h' steps
130    /// and re-enter restoration (observed on DECONVBNE: 323 R-accepts
131    /// vs ipopt's 21). Default: no-op for non-filter acceptors.
132    fn prepare_resto_phase_start(&mut self, _reference_theta: Number, _reference_barr: Number) {}
133
134    /// Override the filter acceptor's `theta_max_fact` (default 1e4).
135    /// Used by the resto sub-IPM wiring to bump the gate to 1e8, which
136    /// mirrors upstream `IpRestoMinC_1Nrm.cpp:91`
137    /// (`resto.theta_max_fact = 1e8`). Without this override the inner
138    /// IPM's first line search caps `theta_max = 1e4` (since reference
139    /// θ ≈ 0 after slack init), and the first non-trivial trial whose
140    /// resto-NLP θ_trial exceeds 1e4 is rejected at the `theta_max`
141    /// gate before reaching f-type/Armijo dispatch. Default: no-op for
142    /// non-filter acceptors.
143    fn set_theta_max_fact(&mut self, _theta_max_fact: Number) {}
144}