alpaqa no-casadi-dep
Nonconvex constrained optimization
Loading...
Searching...
No Matches
panoc-ocp.tpp
Go to the documentation of this file.
10#include <alpaqa/util/timed.hpp>
11#include <concepts>
12#include <iomanip>
13#include <iostream>
14#include <numeric>
15#include <stdexcept>
16#include <type_traits>
17
18namespace alpaqa {
19
20template <Config Conf>
22 return detail::extract_u(*problem, xu);
23}
24
25template <Config Conf>
27 return detail::extract_x(*problem, xu);
28}
29
30template <Config Conf>
32 return detail::extract_u(*problem, x̂u);
33}
34
35template <Config Conf>
37 return detail::extract_x(*problem, x̂u);
38}
39
40template <Config Conf>
41std::string PANOCOCPSolver<Conf>::get_name() const {
42 return "PANOCOCPSolver<" + std::string(config_t::get_name()) + '>';
43}
44
45template <Config Conf>
47 /// [in] Problem description
48 const Problem &problem,
49 /// [in] Solve options
50 const SolveOptions &opts,
51 /// [inout] Decision variable @f$ u @f$
52 rvec u,
53 /// [inout] Lagrange multipliers @f$ y @f$
54 rvec y,
55 /// [in] Penalty factors @f$ \mu @f$
56 crvec μ,
57 /// [out] Slack variable error @f$ c(x) - \Pi_D(c(x) + \mu^{-1} y) @f$
58 rvec err_z) -> Stats {
59
60 if (opts.check)
61 problem.check();
62
63 using std::chrono::nanoseconds;
64 auto os = opts.os ? opts.os : this->os;
65 auto start_time = std::chrono::steady_clock::now();
66 Stats s;
67
68 const auto N = problem.get_N();
69 const auto nu = problem.get_nu();
70 const auto nx = problem.get_nx();
71 const auto nc = problem.get_nc();
72 const auto nc_N = problem.get_nc_N();
73 const auto n = nu * N;
74
75 bool enable_lbfgs = params.gn_interval != 1;
76
77 // Allocate storage --------------------------------------------------------
78
79 // TODO: the L-BFGS objects and vectors allocate on each iteration of ALM,
80 // and there are more vectors than strictly necessary.
81
83 auto &vars = eval.vars;
86 LQRFactor lqr{{.N = N, .nx = nx, .nu = nu}};
87 LBFGS<config_t> lbfgs{params.lbfgs_params, enable_lbfgs ? n : 0};
88 mat jacs = vars.create_AB();
89 vec qr = vars.create_qr();
90
91 vec q(n); // Newton step, including states
95
96 // Workspace storage
97 vec work_2x(nx * 2);
98
99 // ALM
100 assert(μ.size() == nc * N + nc_N);
101 assert(y.size() == nc * N + nc_N);
102
103 // Functions for accessing the LQR matrices and index sets
104 auto ABk = [&](index_t i) -> crmat { return vars.ABk(jacs, i); };
105 auto Qk = [&](rvec storage) {
106 return [&, storage](index_t k) {
107 return [&, k](rmat out) {
109 return eval.Qk(storage, y, μ, D, D_N, k, out);
110 };
111 };
112 };
113 auto Rk = [&](rvec storage) {
114 return [&, storage](index_t k) {
115 return [&, k](crindexvec mask, rmat out) {
117 return eval.Rk(storage, k, mask, out);
118 };
119 };
120 };
121 auto Sk = [&](rvec storage) {
122 return [&, storage](index_t k) {
123 return [&, k](crindexvec mask, rmat out) {
125 return eval.Sk(storage, k, mask, out);
126 };
127 };
128 };
129 auto Rk_prod = [&](rvec storage) {
130 return [&, storage](index_t k) {
131 return [&, k](crindexvec mask_J, crindexvec mask_K, crvec v,
132 rvec out) {
134 return eval.Rk_prod(storage, k, mask_J, mask_K, v, out);
135 };
136 };
137 };
138 auto Sk_prod = [&](rvec storage) {
139 return [&, storage](index_t k) {
140 return [&, k](crindexvec mask_K, crvec v, rvec out) {
142 return eval.Sk_prod(storage, k, mask_K, v, out);
143 };
144 };
145 };
146 auto mut_qrk = [&](index_t k) -> rvec { return vars.qrk(qr, k); };
147 auto mut_q_N = [&]() -> rvec { return vars.qk(qr, N); };
148 auto qk = [&](index_t k) -> crvec { return vars.qk(qr, k); };
149 auto rk = [&](index_t k) -> crvec { return vars.rk(qr, k); };
150 auto uk_eq = [&](index_t k) -> crvec { return q.segment(k * nu, nu); };
151 auto Jk = [&](index_t k) -> crindexvec { return J.indices(k); };
152 auto Kk = [&](index_t k) -> crindexvec { return J.compl_indices(k); };
153
154 // Iterates ----------------------------------------------------------------
155
156 // Represents an iterate in the algorithm, keeping track of some
157 // intermediate values and function evaluations.
158 struct Iterate {
159 vec xu; //< Inputs u interleaved with states x
160 vec ; //< Inputs u interleaved with states x after prox grad
161 vec grad_ψ; //< Gradient of cost in u
162 vec p; //< Proximal gradient step in u
163 vec u; //< Inputs u (used for L-BFGS only)
164 real_t ψu = NaN<config_t>; //< Cost in u
165 real_t ψû = NaN<config_t>; //< Cost in û
166 real_t γ = NaN<config_t>; //< Step size γ
167 real_t L = NaN<config_t>; //< Lipschitz estimate L
168 real_t pᵀp = NaN<config_t>; //< Norm squared of p
169 real_t grad_ψᵀp = NaN<config_t>; //< Dot product of gradient and p
170
171 // @pre @ref ψu, @ref pᵀp, @pre grad_ψᵀp
172 // @return φγ
173 real_t fbe() const { return ψu + pᵀp / (2 * γ) + grad_ψᵀp; }
174
176 : xu{vars.create()}, {vars.create()}, grad_ψ{vars.N * vars.nu()},
177 p{vars.N * vars.nu()}, u{enable_lbfgs ? vars.N * vars.nu() : 0} {}
178 } iterates[2]{
179 {vars, enable_lbfgs},
180 {vars, enable_lbfgs},
181 };
182 Iterate *curr = &iterates[0];
183 Iterate *next = &iterates[1];
184
185 // Helper functions --------------------------------------------------------
186
187 auto eval_proj_grad_step_box = [&U](real_t γ, crvec x, crvec grad_ψ, rvec x̂,
188 rvec p) {
189 using binary_real_f = real_t (*)(real_t, real_t);
190 p = (-γ * grad_ψ)
191 .binaryExpr(U.lowerbound - x, binary_real_f(std::fmax))
192 .binaryExpr(U.upperbound - x, binary_real_f(std::fmin));
193 x̂ = x + p;
194 };
195
196 auto eval_prox_impl = [&](real_t γ, crvec xu, crvec grad_ψ, rvec x̂u,
197 rvec p) {
199 real_t pᵀp = 0;
201 for (index_t t = 0; t < N; ++t) {
202 auto &&grad_ψ_t = grad_ψ.segment(t * nu, nu);
203 auto &&p_t = p.segment(t * nu, nu);
204 eval_proj_grad_step_box(γ, vars.uk(xu, t), grad_ψ_t,
205 /* in ⟹ out */ vars.uk(x̂u, t), p_t);
206 // Calculate ∇ψ(x)ᵀp and ‖p‖²
207 pᵀp += p_t.squaredNorm();
208 grad_ψᵀp += grad_ψ_t.dot(p_t);
209 }
210 return std::make_tuple(pᵀp, grad_ψᵀp);
211 };
212
213 auto calc_error_stop_crit = [this, &eval_prox_impl](
216 rvec work_p) {
217 using vec_util::norm_inf;
218 switch (params.stop_crit) {
220 return norm_inf(pₖ);
221 }
223 return std::sqrt(pₖᵀpₖ);
224 }
227 return norm_inf(work_p);
228 }
230 auto [pTp, gTp] =
232 return std::sqrt(pTp);
233 }
235 return norm_inf(pₖ) / γ;
236 }
238 return std::sqrt(pₖᵀpₖ) / γ;
239 }
244 default:
245 throw std::invalid_argument("Unsupported stopping criterion");
246 }
247 };
248
249 auto check_all_stop_conditions =
250 [this, &opts](
251 /// [in] Time elapsed since the start of the algorithm
252 auto time_elapsed,
253 /// [in] The current iteration number
254 unsigned iteration,
255 /// [in] Tolerance of the current iterate
257 /// [in] The number of successive iterations no progress was made
258 unsigned no_progress) {
259 auto max_time = params.max_time;
260 if (opts.max_time)
261 max_time = std::min(max_time, *opts.max_time);
262 auto tolerance = opts.tolerance > 0 ? opts.tolerance : real_t(1e-8);
263 bool out_of_time = time_elapsed > max_time;
264 bool out_of_iter = iteration == params.max_iter;
265 bool interrupted = stop_signal.stop_requested();
266 bool not_finite = not std::isfinite(εₖ);
267 bool conv = εₖ <= tolerance;
268 bool max_no_progress = no_progress > params.max_no_progress;
273 : max_no_progress ? SolverStatus::NoProgress
276 };
277
278 auto assign_interleave_xu = [&vars](crvec u, rvec xu) {
279 detail::assign_interleave_xu(vars, u, xu);
280 };
281 auto assign_extract_u = [&vars](crvec xu, rvec u) {
282 detail::assign_extract_u(vars, xu, u);
283 };
284
285 auto write_solution = [&](Iterate &it) {
286 // Update multipliers and constraint error
287 if (nc > 0 || nc_N > 0) {
288 for (index_t t = 0; t < N; ++t) {
289 auto ct = vars.ck(it.xû, t);
290 auto yt = y.segment(nc * t, nc);
291 auto μt = μ.segment(nc * t, nc);
292 auto ζ = ct + μt.asDiagonal().inverse() * yt;
293 auto et = err_z.segment(nc * t, nc);
294 et = projecting_difference(ζ, D);
295 et -= μt.asDiagonal().inverse() * yt;
296 yt += μt.asDiagonal() * et;
297 }
298 auto ct = vars.ck(it.xû, N);
299 auto yt = y.segment(nc * N, nc_N);
300 auto μt = μ.segment(nc * N, nc_N);
301 auto ζ = ct + μt.asDiagonal().inverse() * yt;
302 auto et = err_z.segment(nc * N, nc_N);
303 et = projecting_difference(ζ, D_N);
304 et -= μt.asDiagonal().inverse() * yt;
305 yt += μt.asDiagonal() * et;
306 }
307 assign_extract_u(it.xû, u);
308 };
309
310 // @pre @ref Iterate::γ, @ref Iterate::xu, @ref Iterate::grad_ψ
311 // @post @ref Iterate::xû, @ref Iterate::p, @ref Iterate::pᵀp,
312 // @ref Iterate::grad_ψᵀp
313 auto eval_prox = [&](Iterate &i) {
314 std::tie(i.pᵀp, i.grad_ψᵀp) =
315 eval_prox_impl(i.γ, i.xu, i.grad_ψ, i.xû, i.p);
316 };
317
318 // @pre @ref Iterate::xu
319 // @post @ref Iterate::ψu
320 auto eval_forward = [&](Iterate &i) {
322 i.ψu = eval.forward(i.xu, D, D_N, μ, y);
323 };
324 // @pre @ref Iterate::xû
325 // @post @ref Iterate::ψû
326 auto eval_forward_hat = [&](Iterate &i) {
328 i.ψû = eval.forward(i.xû, D, D_N, μ, y);
329 };
330
331 // @pre @ref Iterate::xu
332 // @post @ref Iterate::grad_ψ, q, q_N
333 auto eval_backward = [&](Iterate &i) {
335 eval.backward(i.xu, i.grad_ψ, mut_qrk, mut_q_N, D, D_N, μ, y);
336 };
337
338 auto qub_violated = [this](const Iterate &i) {
339 real_t margin =
340 (1 + std::abs(i.ψu)) * params.quadratic_upperbound_tolerance_factor;
341 return i.ψû > i.ψu + i.grad_ψᵀp + real_t(0.5) * i.L * i.pᵀp + margin;
342 };
343
344 auto linesearch_violated = [this](const Iterate &curr,
345 const Iterate &next) {
346 real_t β = params.linesearch_strictness_factor;
347 real_t σ = β * (1 - curr.γ * curr.L) / (2 * curr.γ);
348 real_t φγ = curr.fbe();
349 real_t margin = (1 + std::abs(φγ)) * params.linesearch_tolerance_factor;
350 return next.fbe() > φγ - σ * curr.pᵀp + margin;
351 };
352
353 auto initial_lipschitz_estimate =
354 [&](
355 /// Iterate, updates xu, ψ, grad_ψ, have_jacobians, L
356 Iterate *it,
357 /// [in] Finite difference step size relative to x
358 real_t ε,
359 /// [in] Minimum absolute finite difference step size
360 real_t δ,
361 /// [in] Minimum allowed Lipschitz estimate.
362 real_t L_min,
363 /// [in] Maximum allowed Lipschitz estimate.
364 real_t L_max,
365 /// Workspace with the same dimensions as xu, with x_init
367 /// Workspace with the same dimensions as grad_ψ
369 // Calculate ψ(x₀), ∇ψ(x₀)
370 eval_forward(*it);
371 eval_backward(*it);
372 // Select a small step h for finite differences
373 auto h = it->grad_ψ.unaryExpr([&](real_t g) {
374 return g > 0 ? std::max(g * ε, δ) : std::min(g * ε, -δ);
375 });
376 real_t norm_h = h.norm();
377 // work_xu = xu - h
378 for (index_t t = 0; t < N; ++t)
379 vars.uk(work_xu, t) =
380 vars.uk(it->xu, t) - h.segment(t * nu, nu);
381
382 { // Calculate ψ(x₀ - h)
384 eval.forward_simulate(work_xu); // needed for backwards sweep
385 }
386 { // Calculate ∇ψ(x₀ + h)
388 eval.backward(work_xu, work_grad_ψ, mut_qrk, mut_q_N, D, D_N, μ,
389 y);
390 }
391 // Estimate Lipschitz constant using finite differences
392 it->L = (work_grad_ψ - it->grad_ψ).norm() / norm_h;
393 it->L = std::clamp(it->L, L_min, L_max);
394 };
395
396 // Printing ----------------------------------------------------------------
397
398 std::array<char, 64> print_buf;
399 auto print_real = [&](real_t x) {
400 return float_to_str_vw(print_buf, x, params.print_precision);
401 };
402 auto print_real3 = [&](real_t x) {
403 return float_to_str_vw(print_buf, x, 3);
404 };
405 auto print_progress_1 = [&](unsigned k, real_t φₖ, real_t ψₖ, crvec grad_ψₖ,
407 if (k == 0)
408 *os << "┌─[PANOCOCP]\n";
409 else
410 *os << "├─ " << std::setw(6) << k << '\n';
411 *os << "│ φγ = " << print_real(φₖ) //
412 << ", ψ = " << print_real(ψₖ) //
413 << ", ‖∇ψ‖ = " << print_real(grad_ψₖ.norm()) //
414 << ", ‖p‖ = " << print_real(std::sqrt(pₖᵀpₖ)) //
415 << ", γ = " << print_real(γₖ) //
416 << ", ε = " << print_real(εₖ) << '\n';
417 };
418 auto print_progress_2 = [&](crvec qₖ, real_t τₖ, bool did_gn, length_t nJ,
419 real_t min_rcond, bool reject) {
420 const char *color = τₖ == 1 ? "\033[0;32m"
421 : τₖ > 0 ? "\033[0;33m"
422 : "\033[0;35m";
423 *os << "│ ‖q‖ = " << print_real(qₖ.norm()) //
424 << ", #J = " << std::setw(7 + params.print_precision) << nJ //
425 << ", cond = " << print_real3(real_t(1) / min_rcond) //
426 << ", τ = " << color << print_real3(τₖ) << "\033[0m" //
427 << ", " << (did_gn ? "GN" : "L-BFGS") //
428 << ", dir update "
429 << (reject ? "\033[0;31mrejected\033[0m"
430 : "\033[0;32maccepted\033[0m") //
431 << std::endl; // Flush for Python buffering
432 };
433 auto print_progress_n = [&](SolverStatus status) {
434 *os << "└─ " << status << " ──"
435 << std::endl; // Flush for Python buffering
436 };
437
438 auto do_progress_cb = [this, &s, &problem, &lqr,
439 &opts](unsigned k, Iterate &curr, crvec q, real_t τ,
440 real_t εₖ, bool did_gn, index_t nJ,
441 SolverStatus status) {
442 if (!progress_cb)
443 return;
446 progress_cb({
447 .k = k,
448 .status = status,
449 .xu = curr.xu,
450 .p = curr.p,
451 .norm_sq_p = curr.pᵀp,
452 .x̂u = curr.xû,
453 .φγ = curr.fbe(),
454 .ψ = curr.ψu,
455 .grad_ψ = curr.grad_ψ,
456 .ψ_hat = curr.ψû,
457 .q = q,
458 .gn = did_gn,
459 .nJ = nJ,
460 .lqr_min_rcond = lqr.min_rcond,
461 .L = curr.L,
462 .γ = curr.γ,
463 .τ = status == SolverStatus::Busy ? τ : NaN<config_t>,
464 .ε = εₖ,
465 .outer_iter = opts.outer_iter,
466 .problem = &problem,
467 .params = &params,
468 });
469 };
470
471 // Initialize inputs and initial state (do not simulate states yet) --------
472
473 assign_interleave_xu(u, curr->xu); // initial guess
474 problem.get_x_init(curr->xu.topRows(nx)); // initial state
475 curr->xû.topRows(nx) = curr->xu.topRows(nx); // initial state
476 next->xu.topRows(nx) = curr->xu.topRows(nx); // initial state
477 next->xû.topRows(nx) = curr->xu.topRows(nx); // initial state
478 if (enable_lbfgs)
479 curr->u = u;
480
481 problem.get_U(U); // input box constraints
482 problem.get_D(D); // general constraints
483 problem.get_D_N(D_N); // general terminal constraints
484
485 bool do_gn_step = params.gn_interval > 0 and !params.disable_acceleration;
486 bool did_gn = false;
487
488 // Make sure that we don't allocate any memory in the inner loop
490
491 // Estimate Lipschitz constant ---------------------------------------------
492
493 // Finite difference approximation of ∇²ψ in starting point
494 if (params.Lipschitz.L_0 <= 0) {
495 initial_lipschitz_estimate(curr, params.Lipschitz.ε, params.Lipschitz.δ,
496 params.L_min, params.L_max, next->xu,
497 next->grad_ψ);
498 }
499 // Initial Lipschitz constant provided by the user
500 else {
501 curr->L = params.Lipschitz.L_0;
502 // Calculate ψ(x₀), ∇ψ(x₀)
505 }
506 if (not std::isfinite(curr->L)) {
508 return s;
509 }
510 curr->γ = params.Lipschitz.Lγ_factor / curr->L;
511
512 // First proximal gradient step --------------------------------------------
513
514 eval_prox(*curr);
516
517 // Quadratic upper bound
518 while (curr->L < params.L_max && qub_violated(*curr)) {
519 curr->γ /= 2;
520 curr->L *= 2;
521 eval_prox(*curr);
524 }
525
526 unsigned k = 0;
528 length_t nJ = -1;
529
530 // Keep track of how many successive iterations didn't update the iterate
531 unsigned no_progress = 0;
532
533 // Main PANOC loop
534 // =========================================================================
535 while (true) {
536
537 // Check stop condition ------------------------------------------------
538
539 real_t εₖ = calc_error_stop_crit(curr->γ, curr->xu, curr->grad_ψ,
540 curr->p, curr->pᵀp, next->xû, next->p);
541
542 // Print progress ------------------------------------------------------
543 bool do_print =
544 params.print_interval != 0 && k % params.print_interval == 0;
545 if (do_print)
546 print_progress_1(k, curr->fbe(), curr->ψu, curr->grad_ψ, curr->pᵀp,
547 curr->γ, εₖ);
548
549 // Return solution -----------------------------------------------------
550
551 auto time_elapsed = std::chrono::steady_clock::now() - start_time;
552 auto stop_status =
553 check_all_stop_conditions(time_elapsed, k, εₖ, no_progress);
555 do_progress_cb(k, *curr, null_vec<config_t>, -1, εₖ, false, 0,
557 bool do_final_print = params.print_interval != 0;
558 if (!do_print && do_final_print)
559 print_progress_1(k, curr->fbe(), curr->ψu, curr->grad_ψ,
560 curr->pᵀp, curr->γ, εₖ);
565 opts.always_overwrite_results) {
567 }
568 s.iterations = k;
569 s.ε = εₖ;
573 s.final_γ = curr->γ;
574 s.final_ψ = curr->ψû;
575 s.final_h = 0; // only box constraints
576 s.final_φγ = curr->fbe();
577 return s;
578 }
579
580 // Calculate Gauss-Newton step -----------------------------------------
581
582 real_t τ_init = 1;
584 if (params.disable_acceleration) {
585 τ_init = 0;
586 } else if (do_gn_step) {
587 auto is_constr_inactive = [&](index_t t, index_t i) {
588 real_t ui = vars.uk(curr->xu, t)(i);
589 // Gradient descent step.
590 real_t gs = ui - curr->γ * curr->grad_ψ(t * nu + i);
591 // Check whether the box constraints are active for this index.
592 bool active_lb = gs <= U.lowerbound(i);
593 bool active_ub = gs >= U.upperbound(i);
594 if (active_ub) {
595 q(nu * t + i) = U.upperbound(i) - ui;
596 return false;
597 } else if (active_lb) {
598 q(nu * t + i) = U.lowerbound(i) - ui;
599 return false;
600 } else { // Store inactive indices
601 return true;
602 }
603 };
604 { // Find active indices J
606 J.update(is_constr_inactive);
607 nJ = J.sizes().sum();
608 }
609 { // evaluate the Jacobians
611 for (index_t t = 0; t < N; ++t)
612 problem.eval_jac_f(t, vars.xk(curr->xu, t),
613 vars.uk(curr->xu, t), vars.ABk(jacs, t));
614 }
615 { // LQR factor
617 lqr.factor_masked(ABk, Qk(curr->xu), Rk(curr->xu), Sk(curr->xu),
618 Rk_prod(curr->xu), Sk_prod(curr->xu), qk, rk,
619 uk_eq, Jk, Kk, params.lqr_factor_cholesky);
620 }
621 { // LQR solve
623 lqr.solve_masked(ABk, Jk, q, work_2x);
624 }
625 } else {
626 if (!enable_lbfgs)
627 throw std::logic_error("enable_lbfgs");
628
629 // Find inactive indices J
630 auto is_constr_inactive = [&](index_t t, index_t i) {
631 real_t ui = vars.uk(curr->xu, t)(i);
632 real_t grad_i = curr->grad_ψ(t * nu + i);
633 // Gradient descent step.
634 real_t gs = ui - curr->γ * grad_i;
635 // Check whether the box constraints are active for this index.
636 bool active_lb = gs <= U.lowerbound(i);
637 bool active_ub = gs >= U.upperbound(i);
638 if (active_ub || active_lb) {
639 q(t * nu + i) = curr->p(t * nu + i);
640 return false;
641 } else { // Store inactive indices
642 q(t * nu + i) = -grad_i;
643 return true;
644 }
645 };
646
647 auto J_idx = J.indices();
648 nJ = 0;
649 {
651 for (index_t t = 0; t < N; ++t)
652 for (index_t i = 0; i < nu; ++i)
653 if (is_constr_inactive(t, i))
654 J_idx(nJ++) = t * nu + i;
655 }
656 auto J_lbfgs = J_idx.topRows(nJ);
657
658 // If all indices are inactive, we can use standard L-BFGS,
659 // if there are active indices, we need the specialized version
660 // that only applies L-BFGS to the inactive indices
661 bool success = [&] {
663 return lbfgs.apply_masked(q, curr->γ, J_lbfgs);
664 }();
665 // If L-BFGS application failed, qₖ(J) still contains
666 // -∇ψ(x)(J) - HqK(J) or -∇ψ(x)(J), which is not a valid step.
667 if (not success)
668 τ_init = 0;
669 }
670
671 // Make sure quasi-Newton step is valid
672 if (not q.allFinite()) {
673 τ_init = 0;
674 // Is there anything we can do?
675 if (not did_gn)
676 lbfgs.reset();
677 }
678 s.lbfgs_failures += (τ_init == 0 && k > 0);
679
680 bool do_next_gn = params.gn_interval > 0 &&
681 ((k + 1) % params.gn_interval) == 0 &&
682 !params.disable_acceleration;
683 do_gn_step = do_next_gn || (do_gn_step && params.gn_sticky);
684
685 // Line search ---------------------------------------------------------
686
687 next->γ = curr->γ;
688 next->L = curr->L;
689 τ = τ_init;
690 real_t τ_prev = -1;
691 bool dir_rejected = true;
692
693 // xₖ₊₁ = xₖ + pₖ
694 auto take_safe_step = [&] {
695 next->xu = curr->xû;
696 next->ψu = curr->ψû;
697 // Calculate ∇ψ(xₖ₊₁)
699 };
700
701 // xₖ₊₁ = xₖ + (1-τ) pₖ + τ qₖ
702 auto take_accelerated_step = [&](real_t τ) {
703 if (τ == 1) {
704 for (index_t t = 0; t < N; ++t)
705 vars.uk(next->xu, t) =
706 vars.uk(curr->xu, t) + q.segment(t * nu, nu);
707 } else {
709 for (index_t t = 0; t < N; ++t)
710 vars.uk(next->xu, t) =
711 vars.uk(curr->xu, t) +
712 (1 - τ) * curr->p.segment(t * nu, nu) +
713 τ * q.segment(t * nu, nu);
714 }
715 // Calculate ψ(xₖ₊₁), ∇ψ(xₖ₊₁)
716 eval_forward(*next); // Not necessary for DDP
718 };
719
720 // Backtracking line search loop
721 while (!stop_signal.stop_requested()) {
722
723 // Recompute step only if τ changed
724 if (τ != τ_prev) {
725 τ != 0 ? take_accelerated_step(τ) : take_safe_step();
726 τ_prev = τ;
727 }
728
729 // If the cost is not finite, or if the quadratic upper bound could
730 // not be satisfied, abandon the direction entirely, don't even
731 // bother backtracking.
732 bool fail = next->L >= params.L_max || !std::isfinite(next->ψu);
733 if (τ > 0 && fail) {
734 // Don't allow a bad accelerated step to destroy the FBS step
735 // size
736 next->L = curr->L;
737 next->γ = curr->γ;
738 // Line search failed
739 τ = 0;
740 if (enable_lbfgs)
741 lbfgs.reset();
742 continue;
743 }
744
745 // Calculate x̂ₖ₊₁, ψ(x̂ₖ₊₁)
746 eval_prox(*next);
748
749 // Quadratic upper bound step size condition
750 if (next->L < params.L_max && qub_violated(*next)) {
751 next->γ /= 2;
752 next->L *= 2;
753 if (τ > 0)
754 τ = τ_init;
756 continue;
757 }
758
759 // Line search condition
760 if (τ > 0 && linesearch_violated(*curr, *next)) {
761 τ /= 2;
762 if (τ < params.min_linesearch_coefficient)
763 τ = 0;
765 continue;
766 }
767
768 // QUB and line search satisfied (or τ is 0 and L > L_max)
769 break;
770 }
771
772 // If τ < τ_min the line search failed and we accepted the prox step
773 s.linesearch_failures += (τ == 0 && τ_init > 0);
774 s.τ_1_accepted += τ == 1;
775 s.count_τ += (τ_init > 0);
776 s.sum_τ += τ;
777
778 // Check if we made any progress
779 if (no_progress > 0 || k % params.max_no_progress == 0)
780 no_progress = curr->xu == next->xu ? no_progress + 1 : 0;
781
782 // Update L-BFGS -------------------------------------------------------
783
784 if (enable_lbfgs) {
785 const bool force = true;
786 assign_extract_u(next->xu, next->u);
787 bool reset_because_gn = did_gn && params.reset_lbfgs_on_gn_step;
788 if (reset_because_gn || curr->γ != next->γ) {
789 lbfgs.reset();
790 }
791 if (!reset_because_gn) { // TODO: this may be too restrictive
793 s.lbfgs_rejected += dir_rejected = not lbfgs.update(
794 curr->u, next->u, curr->grad_ψ, next->grad_ψ,
796 }
797 }
798
799 // Print ---------------------------------------------------------------
801 if (do_print && (k != 0 || did_gn))
802 print_progress_2(q, τ, did_gn, nJ, lqr.min_rcond, dir_rejected);
803
804 // Advance step --------------------------------------------------------
805 std::swap(curr, next);
806 ++k;
807 }
808 throw std::logic_error("[PANOC] loop error");
809}
810
811} // namespace alpaqa
Limited memory Broyden–Fletcher–Goldfarb–Shanno (L-BFGS) algorithm.
Definition lbfgs.hpp:117
std::string get_name() const
Definition panoc-ocp.tpp:41
Stats operator()(const Problem &problem, const SolveOptions &opts, rvec u, rvec y, crvec μ, rvec err_z)
Definition panoc-ocp.tpp:46
void assign_extract_u(const OCPVariables< Conf > &dim, crvec< Conf > storage, rvec< Conf > u)
Definition ocp-vars.hpp:460
vec< Conf > extract_u(const TypeErasedControlProblem< Conf > &problem, crvec< Conf > xu)
Definition ocp-vars.hpp:474
vec< Conf > extract_x(const TypeErasedControlProblem< Conf > &problem, crvec< Conf > xu)
Definition ocp-vars.hpp:482
void assign_interleave_xu(const OCPVariables< Conf > &dim, crvec< Conf > u, rvec< Conf > storage)
Definition ocp-vars.hpp:445
auto norm_inf(const Eigen::MatrixBase< Derived > &v)
Get the maximum or infinity-norm of the given vector.
Definition config.hpp:202
typename Conf::mat mat
Definition config.hpp:93
std::chrono::nanoseconds time_jacobians
@ LBFGSBpp
The stopping criterion used by LBFGS++, see https://lbfgspp.statr.me/doc/classLBFGSpp_1_1LBFGSBParam....
@ ProjGradUnitNorm
∞-norm of the projected gradient with unit step size:
@ ProjGradNorm
∞-norm of the projected gradient with step size γ:
@ Ipopt
The stopping criterion used by Ipopt, see https://link.springer.com/article/10.1007/s10107-004-0559-y...
@ FPRNorm2
2-norm of fixed point residual:
@ ProjGradNorm2
2-norm of the projected gradient with step size γ:
@ ApproxKKT
Find an ε-approximate KKT point in the ∞-norm:
@ FPRNorm
∞-norm of fixed point residual:
@ ApproxKKT2
Find an ε-approximate KKT point in the 2-norm:
@ ProjGradUnitNorm2
2-norm of the projected gradient with unit step size:
typename Conf::crmat crmat
Definition config.hpp:97
std::chrono::nanoseconds time_hessians
typename Conf::rmat rmat
Definition config.hpp:96
std::chrono::nanoseconds time_lbfgs_apply
std::chrono::nanoseconds time_lbfgs_indices
SolverStatus
Exit status of a numerical solver such as ALM or PANOC.
@ Interrupted
Solver was interrupted by the user.
@ MaxTime
Maximum allowed execution time exceeded.
@ NoProgress
No progress was made in the last iteration.
@ MaxIter
Maximum number of iterations exceeded.
@ Busy
In progress.
@ Converged
Converged and reached given tolerance.
@ NotFinite
Intermediate results were infinite or not-a-number.
std::chrono::nanoseconds time_progress_callback
std::chrono::nanoseconds time_indices
std::chrono::nanoseconds elapsed_time
std::chrono::nanoseconds time_backward
typename Conf::real_t real_t
Definition config.hpp:86
std::chrono::nanoseconds time_lqr_factor
unsigned linesearch_backtracks
typename Conf::index_t index_t
Definition config.hpp:104
typename Conf::length_t length_t
Definition config.hpp:103
constexpr const auto inf
Definition config.hpp:112
typename Conf::rvec rvec
Definition config.hpp:91
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:92
std::chrono::nanoseconds time_prox
std::chrono::nanoseconds time_forward
typename Conf::vec vec
Definition config.hpp:88
std::chrono::nanoseconds time_lbfgs_update
std::chrono::nanoseconds time_lqr_solve
typename Conf::crindexvec crindexvec
Definition config.hpp:107
vec create() const
Definition ocp-vars.hpp:65
length_t nu() const
Definition ocp-vars.hpp:57
static Box NaN(length_t n)
Definition box.hpp:19