alpaqa 1.0.0a8
Nonconvex constrained optimization
Loading...
Searching...
No Matches
panoc.tpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <cassert>
6#include <cmath>
7#include <iomanip>
8#include <iostream>
9#include <stdexcept>
10
16#include <alpaqa/util/timed.hpp>
17
18namespace alpaqa {
19
20template <class DirectionProviderT>
22 return "PANOCSolver<" + std::string(direction.get_name()) + ">";
23}
24
25template <class DirectionProviderT>
27 /// [in] Problem description
28 const Problem &problem,
29 /// [in] Solve options
30 const SolveOptions &opts,
31 /// [inout] Decision variable @f$ x @f$
32 rvec x,
33 /// [inout] Lagrange multipliers @f$ y @f$
34 rvec y,
35 /// [in] Constraint weights @f$ \Sigma @f$
36 crvec Σ,
37 /// [out] Slack variable error @f$ g(x) - \Pi_D(g(x) + \Sigma^{-1} y) @f$
38 rvec err_z) -> Stats {
39
40 if (opts.check)
41 problem.check();
42
43 using std::chrono::nanoseconds;
44 auto os = opts.os ? opts.os : this->os;
45 auto start_time = std::chrono::steady_clock::now();
46 Stats s;
47
48 const auto n = problem.get_n();
49 const auto m = problem.get_m();
50
51 // Represents an iterate in the algorithm, keeping track of some
52 // intermediate values and function evaluations.
53 struct Iterate {
54 vec x; //< Decision variables
55 vec x̂; //< Decision variables after proximal gradient step
56 vec grad_ψ; //< Gradient of cost in x
57 vec p; //< Proximal gradient step in x
58 vec ŷx̂; //< Candidate Lagrange multipliers in x̂
59 real_t ψx = NaN<config_t>; //< Cost in x
60 real_t ψx̂ = NaN<config_t>; //< Cost in x̂
61 real_t γ = NaN<config_t>; //< Step size γ
62 real_t L = NaN<config_t>; //< Lipschitz estimate L
63 real_t pᵀp = NaN<config_t>; //< Norm squared of p
64 real_t grad_ψᵀp = NaN<config_t>; //< Dot product of gradient and p
65 real_t hx̂ = NaN<config_t>; //< Non-smooth function value in x̂
66
67 // @pre @ref ψx, @ref hx̂ @ref pᵀp, @ref grad_ψᵀp
68 // @return φγ
69 real_t fbe() const { return ψx + hx̂ + pᵀp / (2 * γ) + grad_ψᵀp; }
70
71 Iterate(length_t n, length_t m) : x(n), x̂(n), grad_ψ(n), p(n), ŷx̂(m) {}
72 } iterates[2]{{n, m}, {n, m}};
73 Iterate *curr = &iterates[0];
74 Iterate *next = &iterates[1];
75
76 bool need_grad_ψx̂ = Helpers::stop_crit_requires_grad_ψx̂(params.stop_crit);
77 vec grad_ψx̂(n);
78 vec work_n(n), work_m(m);
79 vec q(n); // (quasi-)Newton step Hₖ pₖ
80
81 // Helper functions --------------------------------------------------------
82
83 auto qub_violated = [this](const Iterate &i) {
84 real_t margin =
85 (1 + std::abs(i.ψx)) * params.quadratic_upperbound_tolerance_factor;
86 return i.ψx̂ > i.ψx + i.grad_ψᵀp + real_t(0.5) * i.L * i.pᵀp + margin;
87 };
88
89 auto linesearch_violated = [this](const Iterate &curr,
90 const Iterate &next) {
91 if (params.force_linesearch)
92 return false;
93 real_t β = params.linesearch_strictness_factor;
94 real_t σ = β * (1 - curr.γ * curr.L) / (2 * curr.γ);
95 real_t φγ = curr.fbe();
96 real_t margin = (1 + std::abs(φγ)) * params.linesearch_tolerance_factor;
97 return next.fbe() > φγ - σ * curr.pᵀp + margin;
98 };
99
100 // Problem functions -------------------------------------------------------
101
102 auto eval_ψ_grad_ψ = [&problem, &y, &Σ, &work_n, &work_m](Iterate &i) {
103 i.ψx = problem.eval_ψ_grad_ψ(i.x, y, Σ, i.grad_ψ, work_n, work_m);
104 };
105 auto eval_prox_grad_step = [&problem](Iterate &i) {
106 i.hx̂ = problem.eval_prox_grad_step(i.γ, i.x, i.grad_ψ, i.x̂, i.p);
107 i.pᵀp = i.p.squaredNorm();
108 i.grad_ψᵀp = i.p.dot(i.grad_ψ);
109 };
110 auto eval_ψx̂ = [&problem, &y, &Σ](Iterate &i) {
111 i.ψx̂ = problem.eval_ψ(i.x̂, y, Σ, i.ŷx̂);
112 };
113 auto eval_grad_ψx̂ = [&problem, &work_n](Iterate &i, rvec grad_ψx̂) {
114 problem.eval_grad_L(i.x̂, i.ŷx̂, grad_ψx̂, work_n);
115 };
116
117 // Printing ----------------------------------------------------------------
118
119 std::array<char, 64> print_buf;
120 auto print_real = [this, &print_buf](real_t x) {
121 return float_to_str_vw(print_buf, x, params.print_precision);
122 };
123 auto print_real3 = [&print_buf](real_t x) {
124 return float_to_str_vw(print_buf, x, 3);
125 };
126 auto print_progress_1 = [&print_real, os](unsigned k, real_t φₖ, real_t ψₖ,
127 crvec grad_ψₖ, real_t pₖᵀpₖ,
128 real_t γₖ, real_t εₖ) {
129 if (k == 0)
130 *os << "┌─[PANOC]\n";
131 else
132 *os << "├─ " << std::setw(6) << k << '\n';
133 *os << "│ φγ = " << print_real(φₖ) //
134 << ", ψ = " << print_real(ψₖ) //
135 << ", ‖∇ψ‖ = " << print_real(grad_ψₖ.norm()) //
136 << ", ‖p‖ = " << print_real(std::sqrt(pₖᵀpₖ)) //
137 << ", γ = " << print_real(γₖ) //
138 << ", ε = " << print_real(εₖ) << '\n';
139 };
140 auto print_progress_2 = [&print_real, &print_real3, os](crvec qₖ, real_t τₖ,
141 bool reject) {
142 const char *color = τₖ == 1 ? "\033[0;32m"
143 : τₖ > 0 ? "\033[0;33m"
144 : "\033[0;35m";
145 *os << "│ ‖q‖ = " << print_real(qₖ.norm()) //
146 << ", τ = " << color << print_real3(τₖ) << "\033[0m" //
147 << ", dir update "
148 << (reject ? "\033[0;31mrejected\033[0m"
149 : "\033[0;32maccepted\033[0m") //
150 << std::endl; // Flush for Python buffering
151 };
152 auto print_progress_n = [&](SolverStatus status) {
153 *os << "└─ " << status << " ──"
154 << std::endl; // Flush for Python buffering
155 };
156
157 auto do_progress_cb = [this, &s, &problem, &Σ, &y, &opts](
158 unsigned k, Iterate &it, crvec q, crvec grad_ψx̂,
159 real_t τ, real_t εₖ, SolverStatus status) {
160 if (!progress_cb)
161 return;
164 progress_cb(ProgressInfo{
165 .k = k,
166 .status = status,
167 .x = it.x,
168 .p = it.p,
169 .norm_sq_p = it.pᵀp,
170 .x̂ = it.x̂,
171 .φγ = it.fbe(),
172 .ψ = it.ψx,
173 .grad_ψ = it.grad_ψ,
174 .ψ_hat = it.ψx̂,
175 .grad_ψ_hat = grad_ψx̂,
176 .q = q,
177 .L = it.L,
178 .γ = it.γ,
179 .τ = τ,
180 .ε = εₖ,
181 .Σ = Σ,
182 .y = y,
183 .outer_iter = opts.outer_iter,
184 .problem = &problem,
185 .params = &params,
186 });
187 };
188
189 // Initialization ----------------------------------------------------------
190
191 curr->x = x;
192
193 // Estimate Lipschitz constant ---------------------------------------------
194
195 // Finite difference approximation of ∇²ψ in starting point
196 if (params.Lipschitz.L_0 <= 0) {
197 curr->L = Helpers::initial_lipschitz_estimate(
198 problem, curr->x, y, Σ, params.Lipschitz.ε, params.Lipschitz.δ,
199 params.L_min, params.L_max,
200 /* in ⟹ out */ curr->ψx, curr->grad_ψ, curr->x̂, next->grad_ψ,
201 work_n, work_m);
202 }
203 // Initial Lipschitz constant provided by the user
204 else {
205 curr->L = params.Lipschitz.L_0;
206 // Calculate ψ(xₖ), ∇ψ(x₀)
207 eval_ψ_grad_ψ(*curr);
208 }
209 if (not std::isfinite(curr->L)) {
211 return s;
212 }
213 curr->γ = params.Lipschitz.Lγ_factor / curr->L;
214
215 // First proximal gradient step --------------------------------------------
216
217 eval_prox_grad_step(*curr);
218 eval_ψx̂(*curr);
219
220 // Quadratic upper bound
221 while (curr->L < params.L_max && qub_violated(*curr)) {
222 curr->γ /= 2;
223 curr->L *= 2;
224 eval_prox_grad_step(*curr);
225 eval_ψx̂(*curr);
226 }
227
228 // Loop data ---------------------------------------------------------------
229
230 unsigned k = 0; // iteration
231 real_t τ = NaN<config_t>; // line search parameter
232 // Keep track of how many successive iterations didn't update the iterate
233 unsigned no_progress = 0;
234
235 // Main PANOC loop
236 // =========================================================================
237
238 ScopedMallocBlocker mb; // Don't allocate in the inner loop
239 while (true) {
240
241 // Check stopping criteria ---------------------------------------------
242
243 // Calculate ∇ψ(x̂ₖ)
244 if (need_grad_ψx̂)
245 eval_grad_ψx̂(*curr, grad_ψx̂);
246 bool have_grad_ψx̂ = need_grad_ψx̂;
247
248 real_t εₖ = Helpers::calc_error_stop_crit(
249 problem, params.stop_crit, curr->p, curr->γ, curr->x, curr->x̂,
250 curr->ŷx̂, curr->grad_ψ, grad_ψx̂, work_n, next->p);
251
252 // Print progress ------------------------------------------------------
253 bool do_print =
254 params.print_interval != 0 && k % params.print_interval == 0;
255 if (do_print)
256 print_progress_1(k, curr->fbe(), curr->ψx, curr->grad_ψ, curr->pᵀp,
257 curr->γ, εₖ);
258
259 // Return solution -----------------------------------------------------
260
261 auto time_elapsed = std::chrono::steady_clock::now() - start_time;
262 auto stop_status = Helpers::check_all_stop_conditions(
263 params, opts, time_elapsed, k, stop_signal, εₖ, no_progress);
264 if (stop_status != SolverStatus::Busy) {
265 do_progress_cb(k, *curr, null_vec<config_t>, grad_ψx̂, -1, εₖ,
266 stop_status);
267 bool do_final_print = params.print_interval != 0;
268 if (!do_print && do_final_print)
269 print_progress_1(k, curr->fbe(), curr->ψx, curr->grad_ψ,
270 curr->pᵀp, curr->γ, εₖ);
271 if (do_print || do_final_print)
272 print_progress_n(stop_status);
273 if (stop_status == SolverStatus::Converged ||
274 stop_status == SolverStatus::Interrupted ||
275 opts.always_overwrite_results) {
276 auto &ŷ = curr->ŷx̂;
277 if (err_z.size() > 0)
278 err_z = Σ.asDiagonal().inverse() * (ŷ - y);
279 x = std::move(curr->x̂);
280 y = std::move(curr->ŷx̂);
281 }
282 s.iterations = k;
283 s.ε = εₖ;
284 s.elapsed_time = duration_cast<nanoseconds>(time_elapsed);
285 s.status = stop_status;
286 s.final_γ = curr->γ;
287 s.final_ψ = curr->ψx̂;
288 s.final_h = curr->hx̂;
289 s.final_φγ = curr->fbe();
290 return s;
291 }
292
293 // Calculate quasi-Newton step -----------------------------------------
294
295 real_t τ_init = NaN<config_t>;
296 if (k == 0) { // Initialize L-BFGS
298 direction.initialize(problem, y, Σ, curr->γ, curr->x, curr->x̂,
299 curr->p, curr->grad_ψ);
300 τ_init = 0;
301 }
302 if (k > 0 || direction.has_initial_direction()) {
303 τ_init = direction.apply(curr->γ, curr->x, curr->x̂, curr->p,
304 curr->grad_ψ, q)
305 ? 1
306 : 0;
307 // Make sure quasi-Newton step is valid
308 if (τ_init == 1 && not q.allFinite())
309 τ_init = 0;
310 if (τ_init != 1) { // If we computed a quasi-Newton step
311 ++s.lbfgs_failures;
312 direction.reset(); // Is there anything else we can do?
313 }
314 }
315
316 // Line search ---------------------------------------------------------
317
318 next->γ = curr->γ;
319 next->L = curr->L;
320 τ = τ_init;
321 real_t τ_prev = -1;
322 bool update_lbfgs_in_linesearch = params.update_direction_in_candidate;
323 bool updated_lbfgs = false;
324 bool dir_rejected = true;
325
326 // xₖ₊₁ = xₖ + pₖ
327 auto take_safe_step = [&] {
328 // Calculate ∇ψ(xₖ₊₁)
329 if (not have_grad_ψx̂)
330 eval_grad_ψx̂(*curr, grad_ψx̂);
331 have_grad_ψx̂ = true;
332 next->x = curr->x̂; // → safe prox step
333 next->ψx = curr->ψx̂;
334 next->grad_ψ.swap(grad_ψx̂);
335 };
336
337 // xₖ₊₁ = xₖ + (1-τ) pₖ + τ qₖ
338 auto take_accelerated_step = [&](real_t τ) {
339 if (τ == 1) // → faster quasi-Newton step
340 next->x = curr->x + q;
341 else
342 next->x = curr->x + (1 - τ) * curr->p + τ * q;
343 // Calculate ψ(xₖ₊₁), ∇ψ(xₖ₊₁)
344 eval_ψ_grad_ψ(*next);
345 };
346
347 while (!stop_signal.stop_requested()) {
348
349 // Recompute step only if τ changed
350 if (τ != τ_prev) {
351 τ != 0 ? take_accelerated_step(τ) : take_safe_step();
352 τ_prev = τ;
353 }
354
355 // If the cost is not finite, or if the quadratic upper bound could
356 // not be satisfied, abandon the direction entirely, don't even
357 // bother backtracking.
358 bool fail = next->L >= params.L_max || !std::isfinite(next->ψx);
359 if (τ > 0 && fail) {
360 // Don't allow a bad accelerated step to destroy the FBS step
361 // size
362 next->L = curr->L;
363 next->γ = curr->γ;
364 // Line search failed
365 τ = 0;
366 direction.reset();
367 // Update the direction in the FB iterate later
368 update_lbfgs_in_linesearch = false;
369 continue;
370 }
371
372 // Calculate x̂ₖ₊₁, ψ(x̂ₖ₊₁)
373 eval_prox_grad_step(*next);
374 eval_ψx̂(*next);
375
376 // Quadratic upper bound step size condition
377 if (next->L < params.L_max && qub_violated(*next)) {
378 next->γ /= 2;
379 next->L *= 2;
380 if (τ > 0)
381 τ = τ_init;
383 // If the step size changes, we need extra care when updating
384 // the direction later
385 update_lbfgs_in_linesearch = false;
386 continue;
387 }
388
389 // Update L-BFGS in candidate (even if we don't accept this point)
390 if (update_lbfgs_in_linesearch && !updated_lbfgs) {
391 s.lbfgs_rejected += dir_rejected = not direction.update(
392 curr->γ, next->γ, curr->x, next->x, curr->p, next->p,
393 curr->grad_ψ, next->grad_ψ);
394 update_lbfgs_in_linesearch = false;
395 updated_lbfgs = true;
396 }
397
398 // Line search condition
399 if (τ > 0 && linesearch_violated(*curr, *next)) {
400 τ /= 2;
401 if (τ < params.min_linesearch_coefficient)
402 τ = 0;
404 continue;
405 }
406
407 // QUB and line search satisfied (or τ is 0 and L > L_max)
408 break;
409 }
410 // If τ < τ_min the line search failed and we accepted the prox step
411 s.linesearch_failures += (τ == 0 && τ_init > 0);
412 s.τ_1_accepted += τ == 1;
413 s.count_τ += (τ_init > 0);
414 s.sum_τ += τ;
415
416 // Check if we made any progress
417 if (no_progress > 0 || k % params.max_no_progress == 0)
418 no_progress = curr->x == next->x ? no_progress + 1 : 0;
419
420 // Update L-BFGS -------------------------------------------------------
421
422 if (!updated_lbfgs) {
423 if (curr->γ != next->γ) { // Flush L-BFGS if γ changed
424 direction.changed_γ(next->γ, curr->γ);
425 if (params.recompute_last_prox_step_after_lbfgs_flush) {
426 curr->γ = next->γ;
427 curr->L = next->L;
428 eval_prox_grad_step(*curr);
429 }
430 }
431 s.lbfgs_rejected += dir_rejected = not direction.update(
432 curr->γ, next->γ, curr->x, next->x, curr->p, next->p,
433 curr->grad_ψ, next->grad_ψ);
434 }
435
436 // Print ---------------------------------------------------------------
437 do_progress_cb(k, *curr, q, grad_ψx̂, τ, εₖ, SolverStatus::Busy);
438 if (do_print && (k != 0 || direction.has_initial_direction()))
439 print_progress_2(q, τ, dir_rejected);
440
441 // Advance step --------------------------------------------------------
442 std::swap(curr, next);
443 ++k;
444 }
445 throw std::logic_error("[PANOC] loop error");
446}
447
448} // namespace alpaqa
std::string get_name() const
Definition: panoc.tpp:21
Stats operator()(const Problem &problem, const SolveOptions &opts, rvec x, rvec y, crvec Σ, rvec err_z)
Definition: panoc.tpp:26
unsigned stepsize_backtracks
Definition: panoc.hpp:76
unsigned lbfgs_rejected
Definition: panoc.hpp:78
unsigned τ_1_accepted
Definition: panoc.hpp:79
unsigned lbfgs_failures
Definition: panoc.hpp:77
real_t final_φγ
Definition: panoc.hpp:85
SolverStatus
Exit status of a numerical solver such as ALM or PANOC.
@ Interrupted
Solver was interrupted by the user.
@ Busy
In progress.
@ Converged
Converged and reached given tolerance.
@ NotFinite
Intermediate results were infinite or not-a-number.
std::chrono::nanoseconds time_progress_callback
Definition: panoc.hpp:72
std::chrono::nanoseconds elapsed_time
Definition: panoc.hpp:71
typename Conf::real_t real_t
Definition: config.hpp:51
unsigned linesearch_backtracks
Definition: panoc.hpp:75
real_t final_ψ
Definition: panoc.hpp:83
real_t final_h
Definition: panoc.hpp:84
typename Conf::length_t length_t
Definition: config.hpp:62
typename Conf::rvec rvec
Definition: config.hpp:55
std::string_view float_to_str_vw(auto &buf, double value, int precision=std::numeric_limits< double >::max_digits10)
Definition: print.tpp:39
typename Conf::crvec crvec
Definition: config.hpp:56
unsigned linesearch_failures
Definition: panoc.hpp:74
typename Conf::vec vec
Definition: config.hpp:52
real_t final_γ
Definition: panoc.hpp:82
unsigned iterations
Definition: panoc.hpp:73
SolverStatus status
Definition: panoc.hpp:69
unsigned count_τ
Definition: panoc.hpp:80