proxsuite-nlp  0.10.0
A primal-dual augmented Lagrangian-type solver for nonlinear programming on manifolds.
Loading...
Searching...
No Matches
linesearch-armijo.hpp
Go to the documentation of this file.
1
4#pragma once
5
9
10#include <Eigen/QR>
11
12namespace proxsuite {
13namespace nlp {
14
17template <typename T> struct PolynomialTpl {
21 PolynomialTpl(const Eigen::Ref<const VectorXs> &c) : coeffs(c) {}
23 Eigen::Index degree() const { return coeffs.size() - 1; }
24 inline T evaluate(T a) const {
25 T r = 0.0;
26 for (int i = 0; i < coeffs.size(); i++) {
27 r = r * a + coeffs(i);
28 }
29 return r;
30 }
32 if (degree() == 0) {
33 return PolynomialTpl(VectorXs::Zero(1));
34 }
35 VectorXs out(degree());
36 for (int i = 0; i < coeffs.size() - 1; i++) {
37 out(i) = coeffs(i) * (T(degree()) - i);
38 }
39 return PolynomialTpl(out);
40 }
41};
42
44template <typename Scalar>
45class ArmijoLinesearch final : public Linesearch<Scalar> {
46public:
48 using Base::options_;
49 using FunctionSample = typename Base::FunctionSample;
52 using Matrix2s = Eigen::Matrix<Scalar, 2, 2>;
53 using Vector2s = Eigen::Matrix<Scalar, 2, 1>;
54
55 ArmijoLinesearch(const typename Base::Options &options) : Base(options) {}
56
57 using fun_t = std::function<Scalar(Scalar)>;
58
59 Scalar run(fun_t phi, const Scalar phi0, const Scalar dphi0,
60 Scalar &alpha_try) {
61 const FunctionSample lower_bound(0., phi0, dphi0);
62
63 alpha_try = 1.;
64 FunctionSample latest;
65 FunctionSample previous;
66
67 // try the full step; if failure encountered, aggressively
68 // backtrack until no exception is raised
69 while (true) {
70 try {
71 latest = FunctionSample(alpha_try, phi(alpha_try));
72 break;
73 } catch (const std::runtime_error &e) {
74 alpha_try *= 0.5;
75 if (alpha_try <= options_.alpha_min) {
76 alpha_try = options_.alpha_min;
77 break;
78 }
79 }
80 }
81
82 if (std::abs(dphi0) < options_.dphi_thresh) {
83 return latest.phi;
84 }
85
86 for (std::size_t i = 0; i < options_.max_num_steps; i++) {
87
88 const Scalar dM = latest.phi - phi0;
89 if (dM <= options_.armijo_c1 * alpha_try * dphi0) {
90 break;
91 }
92
93 // compute next alpha try
95 if (strat == LSInterpolation::BISECTION) {
96 alpha_try *= 0.5;
97 } else {
98 samples.reserve(3);
99 samples = {lower_bound};
100
101 // interpolation routines
102 switch (strat) {
104 // 2-point interp: value, derivative at 0 and latest value
105 samples.push_back(latest);
106 break;
107 }
109 // 3-point interp: phi(0), phi'(0) and last two values
110 samples.push_back(latest);
111 if (previous.valid) {
112 samples.push_back(previous);
113 }
114 break;
115 }
116 default:
118 "Unrecognized interpolation type in this context.\n");
119 break;
120 }
121
122 alpha_try = this->minimize_interpolant(
123 strat, options_.contraction_min * alpha_try,
124 options_.contraction_max * alpha_try);
125 }
126
127 if (std::isnan(alpha_try)) {
128 // handle NaN case
129 alpha_try = options_.contraction_min * previous.alpha;
130 } else {
131 alpha_try = std::max(alpha_try, options_.alpha_min);
132 }
133
134 try {
135 previous = latest;
136 latest = FunctionSample(alpha_try, phi(alpha_try));
137 } catch (const std::runtime_error &e) {
138 continue;
139 }
140
141 if (alpha_try <= options_.alpha_min) {
142 break;
143 }
144 }
145 alpha_try = std::max(alpha_try, options_.alpha_min);
146 return latest.phi;
147 }
148
150 Scalar minimize_interpolant(LSInterpolation strat, Scalar min_step_size,
151 Scalar max_step_size) {
152 Scalar anext = 0.0;
153 VectorXs &coeffs = interpolant.coeffs;
154
155 assert(samples.size() >= 2);
156 const FunctionSample &lower_bound = samples[0];
157 const Scalar &phi0 = lower_bound.phi;
158 const Scalar &dphi0 = lower_bound.dphi;
159
160 if (samples.size() == 2) {
162 }
163
164 switch (strat) {
166 assert(samples.size() >= 2);
167 const FunctionSample &cand0 = samples[1];
168 Scalar a = (cand0.phi - phi0 - cand0.alpha * dphi0) /
169 (cand0.alpha * cand0.alpha);
170 coeffs.conservativeResize(3);
171 coeffs << a, dphi0, phi0;
172 assert(interpolant.degree() == 2);
173 anext = -dphi0 / (2. * a);
174 break;
175 }
177 assert(samples.size() >= 3);
178 const FunctionSample &cand0 = samples[1];
179 const FunctionSample &cand1 = samples[2];
180 const Scalar &a0 = cand0.alpha;
181 const Scalar &a1 = cand1.alpha;
182 Matrix2s alph_mat;
183 Vector2s coeffs_cubic_interpolant;
185 alph_mat(0, 0) = a0 * a0 * a0;
186 alph_mat(0, 1) = a0 * a0;
187 alph_mat(1, 0) = a1 * a1 * a1;
188 alph_mat(1, 1) = a1 * a1;
189
190 Vector2s alph_rhs{cand1.phi - phi0 - dphi0 * a1,
191 cand0.phi - phi0 - dphi0 * a0};
192
193 Eigen::HouseholderQR<Matrix2s> decomp(alph_mat);
194 coeffs_cubic_interpolant = decomp.solve(alph_rhs);
195
196 const Scalar c3 = coeffs_cubic_interpolant(0);
197 const Scalar c2 = coeffs_cubic_interpolant(1);
198 coeffs.conservativeResize(4);
199 coeffs << c3, c2, dphi0, phi0;
200 assert(interpolant.degree() == 3);
201
202 // minimizer of cubic interpolant -> solve dinterp/da = 0
203 anext = (-c2 + std::sqrt(c2 * c2 - 3.0 * c3 * dphi0)) / (3.0 * c3);
204 break;
205 }
206 default:
207 break;
208 }
209
210 if ((anext > max_step_size) || (anext < min_step_size)) {
211 // if min outside of (amin; amax), look at the edges
212 Scalar pleft = interpolant.evaluate(min_step_size);
213 Scalar pright = interpolant.evaluate(max_step_size);
214 if (pleft < pright) {
215 anext = min_step_size;
216 } else {
217 anext = max_step_size;
218 }
219 }
220
221 return anext;
222 }
223
224protected:
226 std::vector<FunctionSample> samples; // interpolation samples
227};
228
229#ifdef PROXSUITE_NLP_ENABLE_TEMPLATE_INSTANTIATION
230extern template struct PROXSUITE_NLP_EXPLICIT_INSTANTIATION_DECLARATION_DLLAPI
232extern template class PROXSUITE_NLP_EXPLICIT_INSTANTIATION_DECLARATION_DLLAPI
234#endif
235
236} // namespace nlp
237} // namespace proxsuite
Basic backtracking Armijo line-search strategy.
Scalar minimize_interpolant(LSInterpolation strat, Scalar min_step_size, Scalar max_step_size)
Propose a new candidate step size through safeguarded interpolation.
typename Base::FunctionSample FunctionSample
ArmijoLinesearch(const typename Base::Options &options)
Eigen::Matrix< Scalar, 2, 1 > Vector2s
std::vector< FunctionSample > samples
Eigen::Matrix< Scalar, 2, 2 > Matrix2s
std::function< Scalar(Scalar)> fun_t
typename math_types< Scalar >::VectorXs VectorXs
Scalar run(fun_t phi, const Scalar phi0, const Scalar dphi0, Scalar &alpha_try)
Base linesearch class. Design pattern inspired by Google Ceres-Solver.
#define PROXSUITE_NLP_RUNTIME_ERROR(msg)
Definition exceptions.hpp:8
Base structs for linesearch algorithms.
Main package namespace.
Definition bcl-params.hpp:5
Polynomials represented by their coefficients in decreasing order of degree.
Eigen::Index degree() const
Polynomial degree (number of coefficients minus one).
PolynomialTpl(const Eigen::Ref< const VectorXs > &c)
typename math_types< T >::VectorXs VectorXs
Typedefs for math (Eigen vectors, matrices) depending on scalar type.
Definition math.hpp:43