Sleipnir C++ API
Loading...
Searching...
No Matches
filter.hpp
1// Copyright (c) Sleipnir contributors
2
3#pragma once
4
5#include <algorithm>
6#include <cmath>
7
8#include <Eigen/Core>
9#include <gch/small_vector.hpp>
10
11#include "sleipnir/optimization/solver/util/constraint_violation.hpp"
12
13// See docs/algorithms.md#Works_cited for citation definitions.
14
15namespace slp {
16
20template <typename Scalar>
23 using DenseVector = Eigen::VectorX<Scalar>;
24
26 Scalar cost{0};
27
30
31 constexpr FilterEntry() = default;
32
37 explicit constexpr FilterEntry(Scalar cost,
38 Scalar constraint_violation = Scalar(0))
40
45 FilterEntry(Scalar f, const DenseVector& c_e)
46 : FilterEntry{f, slp::constraint_violation(c_e)} {}
47
55 FilterEntry(Scalar f, DenseVector& s, const DenseVector& c_e,
56 const DenseVector& c_i, Scalar μ)
57 : FilterEntry{f - μ * s.array().log().sum(),
58 slp::constraint_violation(c_e, c_i, s)} {}
59
64 constexpr bool dominated_by(const FilterEntry<Scalar>& entry) const {
65 return entry.cost <= cost &&
67 }
68};
69
75template <typename Scalar>
76class Filter {
77 public:
80
83
88 explicit constexpr Filter(Scalar initial_constraint_violation = Scalar(0)) {
89 using std::max;
90
92 Scalar(1e-4) * max(Scalar(1), initial_constraint_violation);
94 Scalar(1e4) * max(Scalar(1), initial_constraint_violation);
95 }
96
98 void reset() {
99 m_filter.clear();
100 m_last_rejection_due_to_filter = false;
101 m_resets = 0;
102 }
103
109 bool try_reset(const FilterEntry<Scalar>& current_entry) {
110 constexpr int max_resets = 5;
111
112 // The filter may be impeding progress if it rejected the most recent trial
113 // entry, and the maximum constraint violation is more than a tenth of the
114 // current iterate's. The number of resets is capped until the next reset().
115 //
116 // See section 3.2 case I of [2].
117 if (m_resets >= max_resets ||
119 current_entry.constraint_violation / Scalar(10) ||
120 !m_last_rejection_due_to_filter) {
121 return false;
122 }
123
124 max_constraint_violation *= Scalar(0.1);
125 m_filter.clear();
126 m_last_rejection_due_to_filter = false;
127 ++m_resets;
128
129 return true;
130 }
131
139 bool try_add(const FilterEntry<Scalar>& current_entry,
140 const FilterEntry<Scalar>& trial_entry, Scalar D_ϕ, Scalar α) {
141 using std::isfinite;
142 using std::pow;
143
144 // Reject steps with nonfinite cost or constraint violation above maximum
145 if (!isfinite(trial_entry.cost) ||
147 return false;
148 }
149
150 // Switching condition
151 bool switching_condition =
152 D_ϕ < Scalar(0) &&
153 α * pow(-D_ϕ, s_ϕ) > pow(current_entry.constraint_violation, s_θ);
154
155 // Armijo condition
156 constexpr Scalar η_ϕ(1e-8);
157 bool armijo_condition =
158 trial_entry.cost <= current_entry.cost + η_ϕ * α * D_ϕ;
159
160 // Sufficient decrease condition
161 bool sufficient_decrease =
162 trial_entry.cost <=
163 current_entry.cost - γ_cost * current_entry.constraint_violation ||
164 trial_entry.constraint_violation <=
165 (Scalar(1) - γ_constraint) * current_entry.constraint_violation;
166
167 // If constraint violation is below threshold and switching condition is
168 // true, check Armijo condition for step rejection. Otherwise, check
169 // sufficient decrease condition.
170 if (current_entry.constraint_violation <= min_constraint_violation &&
171 switching_condition) {
172 if (!armijo_condition) {
173 m_last_rejection_due_to_filter = false;
174 return false;
175 }
176 } else if (!sufficient_decrease) {
177 m_last_rejection_due_to_filter = false;
178 return false;
179 }
180
181 // Reject steps in filter (i.e., dominated by any filter entry)
182 if (in_filter(trial_entry)) {
183 m_last_rejection_due_to_filter = true;
184 return false;
185 }
186
187 // Augment filter with accepted iterate if switching condition or Armijo
188 // condition are false
189 if (!switching_condition || !armijo_condition) {
190 add(current_entry);
191 }
192
193 return true;
194 }
195
203 void add(const FilterEntry<Scalar>& entry) {
204 FilterEntry<Scalar> margin_entry{
205 entry.cost - γ_cost * entry.constraint_violation,
206 (Scalar(1) - γ_constraint) * entry.constraint_violation};
207
208 // Remove dominated entries
209 erase_if(m_filter,
210 [&](const auto& elem) { return elem.dominated_by(margin_entry); });
211
212 m_filter.push_back(margin_entry);
213 }
214
219 bool is_acceptable(const FilterEntry<Scalar>& entry) const {
220 using std::isfinite;
221
222 return isfinite(entry.cost) &&
224 !in_filter(entry);
225 }
226
236 Scalar min_step_size(const FilterEntry<Scalar>& current_entry,
237 Scalar D_ϕ) const {
238 // The minimum step size goes to zero as the constraint violation does so
239 // feasibility restoration is never invoked near feasible points where the
240 // search direction is a descent direction.
241 //
242 // See equation (23) of [2].
243
244 using std::min;
245 using std::pow;
246
247 // Minimum step size safety factor
248 constexpr Scalar γ_α(0.05);
249
250 const Scalar θ = current_entry.constraint_violation;
251
252 if (D_ϕ >= Scalar(0)) {
253 return γ_α * γ_constraint;
254 }
255
256 Scalar α_min = min(γ_constraint, γ_cost * θ / -D_ϕ);
257 if (θ <= min_constraint_violation) {
258 α_min = min(α_min, pow(θ, s_θ) / pow(-D_ϕ, s_ϕ));
259 }
260 return γ_α * α_min;
261 }
262
263 private:
264 static constexpr Scalar γ_cost{1e-8};
265 static constexpr Scalar γ_constraint{1e-5};
266
267 // Switching condition exponents
268 static constexpr Scalar s_ϕ{2.3};
269 static constexpr Scalar s_θ{1.1};
270
271 gch::small_vector<FilterEntry<Scalar>> m_filter;
272
273 bool m_last_rejection_due_to_filter = false;
274
275 // The number of resets done by try_reset() since the last reset()
276 int m_resets = 0;
277
282 bool in_filter(const FilterEntry<Scalar>& entry) const {
283 // An entry is in the filter if it's dominated by any filter entry
284 return std::any_of(m_filter.begin(), m_filter.end(), [&](const auto& elem) {
285 return entry.dominated_by(elem);
286 });
287 }
288};
289
293template <typename Scalar>
295 public:
304 bool update(const FilterEntry<Scalar>& current_entry,
305 const FilterEntry<Scalar>& trial_entry, Scalar α,
306 Scalar min_constraint_violation) {
307 // If step is tiny, current iterate is infeasible, and accepted trial
308 // iterate made insufficient progress toward feasibility, increment stalled
309 // step counter
310 if (α < Scalar(1e-3) &&
311 current_entry.constraint_violation > min_constraint_violation &&
312 trial_entry.constraint_violation >
313 Scalar(0.99) * current_entry.constraint_violation) {
314 ++m_stalled_step_counter;
315
316 // If there were too many consecutive tiny steps, report a stall
317 if (m_stalled_step_counter >= 5) {
318 m_stalled_step_counter = 0;
319 return true;
320 }
321 } else {
322 // A break in stalled steps resets the counter
323 m_stalled_step_counter = 0;
324 }
325
326 return false;
327 }
328
329 private:
330 int m_stalled_step_counter = 0;
331};
332
333} // namespace slp
Definition filter.hpp:76
void add(const FilterEntry< Scalar > &entry)
Definition filter.hpp:203
Scalar min_step_size(const FilterEntry< Scalar > &current_entry, Scalar D_ϕ) const
Definition filter.hpp:236
bool try_add(const FilterEntry< Scalar > &current_entry, const FilterEntry< Scalar > &trial_entry, Scalar D_ϕ, Scalar α)
Definition filter.hpp:139
Scalar min_constraint_violation
The minimum constraint violation.
Definition filter.hpp:79
bool is_acceptable(const FilterEntry< Scalar > &entry) const
Definition filter.hpp:219
bool try_reset(const FilterEntry< Scalar > &current_entry)
Definition filter.hpp:109
constexpr Filter(Scalar initial_constraint_violation=Scalar(0))
Definition filter.hpp:88
Scalar max_constraint_violation
The maximum constraint violation.
Definition filter.hpp:82
void reset()
Resets the filter.
Definition filter.hpp:98
Definition filter.hpp:294
bool update(const FilterEntry< Scalar > &current_entry, const FilterEntry< Scalar > &trial_entry, Scalar α, Scalar min_constraint_violation)
Definition filter.hpp:304
Definition filter.hpp:21
Scalar cost
The cost function's value.
Definition filter.hpp:26
FilterEntry(Scalar f, DenseVector &s, const DenseVector &c_e, const DenseVector &c_i, Scalar μ)
Definition filter.hpp:55
constexpr bool dominated_by(const FilterEntry< Scalar > &entry) const
Definition filter.hpp:64
Scalar constraint_violation
The constraint violation.
Definition filter.hpp:29
FilterEntry(Scalar f, const DenseVector &c_e)
Definition filter.hpp:45
Eigen::VectorX< Scalar > DenseVector
Type alias for dense vector.
Definition filter.hpp:23
constexpr FilterEntry(Scalar cost, Scalar constraint_violation=Scalar(0))
Definition filter.hpp:37