alpaqa 1.0.0a8
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
82 OCPEvaluator<config_t> eval{problem};
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 xû; //< 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
175 Iterate(const OCPVariables<config_t> &vars, bool enable_lbfgs)
176 : xu{vars.create()}, xû{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;
200 real_t grad_ψᵀ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](
214 real_t γ, crvec xuₖ, crvec grad_ψₖ,
215 crvec pₖ, real_t pₖᵀpₖ, rvec work_xu,
216 rvec work_p) {
217 switch (params.stop_crit) {
219 return vec_util::norm_inf(pₖ);
220 }
222 return std::sqrt(pₖᵀpₖ);
223 }
225 eval_prox_impl(1, xuₖ, grad_ψₖ, work_xu, work_p);
226 return vec_util::norm_inf(work_p);
227 }
229 auto [pTp, gTp] =
230 eval_prox_impl(1, xuₖ, grad_ψₖ, work_xu, work_p);
231 return std::sqrt(pTp);
232 }
234 return vec_util::norm_inf(pₖ) / γ;
235 }
237 return std::sqrt(pₖᵀpₖ) / γ;
238 }
239 case PANOCStopCrit::ApproxKKT: [[fallthrough]];
240 case PANOCStopCrit::ApproxKKT2: [[fallthrough]];
241 case PANOCStopCrit::Ipopt: [[fallthrough]];
242 case PANOCStopCrit::LBFGSBpp: [[fallthrough]];
243 default:
244 throw std::invalid_argument("Unsupported stopping criterion");
245 }
246 };
247
248 auto check_all_stop_conditions =
249 [this, &opts](
250 /// [in] Time elapsed since the start of the algorithm
251 auto time_elapsed,
252 /// [in] The current iteration number
253 unsigned iteration,
254 /// [in] Tolerance of the current iterate
255 real_t εₖ,
256 /// [in] The number of successive iterations no progress was made
257 unsigned no_progress) {
258 auto max_time = params.max_time;
259 if (opts.max_time)
260 max_time = std::min(max_time, *opts.max_time);
261 auto tolerance = opts.tolerance > 0 ? opts.tolerance : real_t(1e-8);
262 bool out_of_time = time_elapsed > max_time;
263 bool out_of_iter = iteration == params.max_iter;
264 bool interrupted = stop_signal.stop_requested();
265 bool not_finite = not std::isfinite(εₖ);
266 bool conv = εₖ <= tolerance;
267 bool max_no_progress = no_progress > params.max_no_progress;
268 return conv ? SolverStatus::Converged
269 : out_of_time ? SolverStatus::MaxTime
270 : out_of_iter ? SolverStatus::MaxIter
271 : not_finite ? SolverStatus::NotFinite
272 : max_no_progress ? SolverStatus::NoProgress
273 : interrupted ? SolverStatus::Interrupted
275 };
276
277 auto assign_interleave_xu = [&vars](crvec u, rvec xu) {
278 detail::assign_interleave_xu(vars, u, xu);
279 };
280 auto assign_extract_u = [&vars](crvec xu, rvec u) {
281 detail::assign_extract_u(vars, xu, u);
282 };
283
284 auto write_solution = [&](Iterate &it) {
285 // Update multipliers and constraint error
286 if (nc > 0 || nc_N > 0) {
287 for (index_t t = 0; t < N; ++t) {
288 auto ct = vars.ck(it.xû, t);
289 auto yt = y.segment(nc * t, nc);
290 auto μt = μ.segment(nc * t, nc);
291 auto ζ = ct + μt.asDiagonal().inverse() * yt;
292 auto et = err_z.segment(nc * t, nc);
293 et = projecting_difference(ζ, D);
294 et -= μt.asDiagonal().inverse() * yt;
295 yt += μt.asDiagonal() * et;
296 }
297 auto ct = vars.ck(it.xû, N);
298 auto yt = y.segment(nc * N, nc_N);
299 auto μt = μ.segment(nc * N, nc_N);
300 auto ζ = ct + μt.asDiagonal().inverse() * yt;
301 auto et = err_z.segment(nc * N, nc_N);
302 et = projecting_difference(ζ, D_N);
303 et -= μt.asDiagonal().inverse() * yt;
304 yt += μt.asDiagonal() * et;
305 }
306 assign_extract_u(it.xû, u);
307 };
308
309 // @pre @ref Iterate::γ, @ref Iterate::xu, @ref Iterate::grad_ψ
310 // @post @ref Iterate::xû, @ref Iterate::p, @ref Iterate::pᵀp,
311 // @ref Iterate::grad_ψᵀp
312 auto eval_prox = [&](Iterate &i) {
313 std::tie(i.pᵀp, i.grad_ψᵀp) =
314 eval_prox_impl(i.γ, i.xu, i.grad_ψ, i.xû, i.p);
315 };
316
317 // @pre @ref Iterate::xu
318 // @post @ref Iterate::ψu
319 auto eval_forward = [&](Iterate &i) {
321 i.ψu = eval.forward(i.xu, D, D_N, μ, y);
322 };
323 // @pre @ref Iterate::xû
324 // @post @ref Iterate::ψû
325 auto eval_forward_hat = [&](Iterate &i) {
327 i.ψû = eval.forward(i.xû, D, D_N, μ, y);
328 };
329
330 // @pre @ref Iterate::xu
331 // @post @ref Iterate::grad_ψ, q, q_N
332 auto eval_backward = [&](Iterate &i) {
334 eval.backward(i.xu, i.grad_ψ, mut_qrk, mut_q_N, D, D_N, μ, y);
335 };
336
337 auto qub_violated = [this](const Iterate &i) {
338 real_t margin =
339 (1 + std::abs(i.ψu)) * params.quadratic_upperbound_tolerance_factor;
340 return i.ψû > i.ψu + i.grad_ψᵀp + real_t(0.5) * i.L * i.pᵀp + margin;
341 };
342
343 auto linesearch_violated = [this](const Iterate &curr,
344 const Iterate &next) {
345 real_t β = params.linesearch_strictness_factor;
346 real_t σ = β * (1 - curr.γ * curr.L) / (2 * curr.γ);
347 real_t φγ = curr.fbe();
348 real_t margin = (1 + std::abs(φγ)) * params.linesearch_tolerance_factor;
349 return next.fbe() > φγ - σ * curr.pᵀp + margin;
350 };
351
352 auto initial_lipschitz_estimate =
353 [&](
354 /// Iterate, updates xu, ψ, grad_ψ, have_jacobians, L
355 Iterate *it,
356 /// [in] Finite difference step size relative to x
357 real_t ε,
358 /// [in] Minimum absolute finite difference step size
359 real_t δ,
360 /// [in] Minimum allowed Lipschitz estimate.
361 real_t L_min,
362 /// [in] Maximum allowed Lipschitz estimate.
363 real_t L_max,
364 /// Workspace with the same dimensions as xu, with x_init
365 rvec work_xu,
366 /// Workspace with the same dimensions as grad_ψ
367 rvec work_grad_ψ) {
368 // Calculate ψ(x₀), ∇ψ(x₀)
369 eval_forward(*it);
370 eval_backward(*it);
371 // Select a small step h for finite differences
372 auto h = it->grad_ψ.unaryExpr([&](real_t g) {
373 return g > 0 ? std::max(g * ε, δ) : std::min(g * ε, -δ);
374 });
375 real_t norm_h = h.norm();
376 // work_xu = xu - h
377 for (index_t t = 0; t < N; ++t)
378 vars.uk(work_xu, t) =
379 vars.uk(it->xu, t) - h.segment(t * nu, nu);
380
381 { // Calculate ψ(x₀ - h)
383 eval.forward_simulate(work_xu); // needed for backwards sweep
384 }
385 { // Calculate ∇ψ(x₀ + h)
387 eval.backward(work_xu, work_grad_ψ, mut_qrk, mut_q_N, D, D_N, μ,
388 y);
389 }
390 // Estimate Lipschitz constant using finite differences
391 it->L = (work_grad_ψ - it->grad_ψ).norm() / norm_h;
392 it->L = std::clamp(it->L, L_min, L_max);
393 };
394
395 // Printing ----------------------------------------------------------------
396
397 std::array<char, 64> print_buf;
398 auto print_real = [&](real_t x) {
399 return float_to_str_vw(print_buf, x, params.print_precision);
400 };
401 auto print_real3 = [&](real_t x) {
402 return float_to_str_vw(print_buf, x, 3);
403 };
404 auto print_progress_1 = [&](unsigned k, real_t φₖ, real_t ψₖ, crvec grad_ψₖ,
405 real_t pₖᵀpₖ, real_t γₖ, real_t εₖ) {
406 if (k == 0)
407 *os << "┌─[PANOCOCP]\n";
408 else
409 *os << "├─ " << std::setw(6) << k << '\n';
410 *os << "│ φγ = " << print_real(φₖ) //
411 << ", ψ = " << print_real(ψₖ) //
412 << ", ‖∇ψ‖ = " << print_real(grad_ψₖ.norm()) //
413 << ", ‖p‖ = " << print_real(std::sqrt(pₖᵀpₖ)) //
414 << ", γ = " << print_real(γₖ) //
415 << ", ε = " << print_real(εₖ) << '\n';
416 };
417 auto print_progress_2 = [&](crvec qₖ, real_t τₖ, bool did_gn, length_t nJ,
418 real_t min_rcond, bool reject) {
419 const char *color = τₖ == 1 ? "\033[0;32m"
420 : τₖ > 0 ? "\033[0;33m"
421 : "\033[0;35m";
422 *os << "│ ‖q‖ = " << print_real(qₖ.norm()) //
423 << ", #J = " << std::setw(7 + params.print_precision) << nJ //
424 << ", cond = " << print_real3(real_t(1) / min_rcond) //
425 << ", τ = " << color << print_real3(τₖ) << "\033[0m" //
426 << ", " << (did_gn ? "GN" : "L-BFGS") //
427 << ", dir update "
428 << (reject ? "\033[0;31mrejected\033[0m"
429 : "\033[0;32maccepted\033[0m") //
430 << std::endl; // Flush for Python buffering
431 };
432 auto print_progress_n = [&](SolverStatus status) {
433 *os << "└─ " << status << " ──"
434 << std::endl; // Flush for Python buffering
435 };
436
437 auto do_progress_cb = [this, &s, &problem, &lqr,
438 &opts](unsigned k, Iterate &curr, crvec q, real_t τ,
439 real_t εₖ, bool did_gn, index_t nJ,
440 SolverStatus status) {
441 if (!progress_cb)
442 return;
445 progress_cb({
446 .k = k,
447 .status = status,
448 .xu = curr.xu,
449 .p = curr.p,
450 .norm_sq_p = curr.pᵀp,
451 .x̂u = curr.xû,
452 .φγ = curr.fbe(),
453 .ψ = curr.ψu,
454 .grad_ψ = curr.grad_ψ,
455 .ψ_hat = curr.ψû,
456 .q = q,
457 .gn = did_gn,
458 .nJ = nJ,
459 .lqr_min_rcond = lqr.min_rcond,
460 .L = curr.L,
461 .γ = curr.γ,
462 .τ = status == SolverStatus::Busy ? τ : NaN<config_t>,
463 .ε = εₖ,
464 .outer_iter = opts.outer_iter,
465 .problem = &problem,
466 .params = &params,
467 });
468 };
469
470 // Initialize inputs and initial state (do not simulate states yet) --------
471
472 assign_interleave_xu(u, curr->xu); // initial guess
473 problem.get_x_init(curr->xu.topRows(nx)); // initial state
474 curr->xû.topRows(nx) = curr->xu.topRows(nx); // initial state
475 next->xu.topRows(nx) = curr->xu.topRows(nx); // initial state
476 next->xû.topRows(nx) = curr->xu.topRows(nx); // initial state
477 if (enable_lbfgs)
478 curr->u = u;
479
480 problem.get_U(U); // input box constraints
481 problem.get_D(D); // general constraints
482 problem.get_D_N(D_N); // general terminal constraints
483
484 bool do_gn_step = params.gn_interval > 0 and !params.disable_acceleration;
485 bool did_gn = false;
486
487 // Make sure that we don't allocate any memory in the inner loop
489
490 // Estimate Lipschitz constant ---------------------------------------------
491
492 // Finite difference approximation of ∇²ψ in starting point
493 if (params.Lipschitz.L_0 <= 0) {
494 initial_lipschitz_estimate(curr, params.Lipschitz.ε, params.Lipschitz.δ,
495 params.L_min, params.L_max, next->xu,
496 next->grad_ψ);
497 }
498 // Initial Lipschitz constant provided by the user
499 else {
500 curr->L = params.Lipschitz.L_0;
501 // Calculate ψ(x₀), ∇ψ(x₀)
502 eval_forward(*curr);
503 eval_backward(*curr);
504 }
505 if (not std::isfinite(curr->L)) {
507 return s;
508 }
509 curr->γ = params.Lipschitz.Lγ_factor / curr->L;
510 eval_prox(*curr);
511 eval_forward_hat(*curr);
512
513 unsigned k = 0;
514 real_t τ = NaN<config_t>;
515 length_t nJ = -1;
516
517 // Keep track of how many successive iterations didn't update the iterate
518 unsigned no_progress = 0;
519
520 // Main PANOC loop
521 // =========================================================================
522 while (true) {
523
524 // Check stop condition ------------------------------------------------
525
526 real_t εₖ = calc_error_stop_crit(curr->γ, curr->xu, curr->grad_ψ,
527 curr->p, curr->pᵀp, next->xû, next->p);
528
529 // Print progress ------------------------------------------------------
530 bool do_print =
531 params.print_interval != 0 && k % params.print_interval == 0;
532 if (do_print)
533 print_progress_1(k, curr->fbe(), curr->ψu, curr->grad_ψ, curr->pᵀp,
534 curr->γ, εₖ);
535
536 // Return solution -----------------------------------------------------
537
538 auto time_elapsed = std::chrono::steady_clock::now() - start_time;
539 auto stop_status =
540 check_all_stop_conditions(time_elapsed, k, εₖ, no_progress);
541 if (stop_status != SolverStatus::Busy) {
542 do_progress_cb(k, *curr, null_vec<config_t>, -1, εₖ, false, 0,
543 stop_status);
544 bool do_final_print = params.print_interval != 0;
545 if (!do_print && do_final_print)
546 print_progress_1(k, curr->fbe(), curr->ψu, curr->grad_ψ,
547 curr->pᵀp, curr->γ, εₖ);
548 if (do_print || do_final_print)
549 print_progress_n(stop_status);
550 if (stop_status == SolverStatus::Converged ||
551 stop_status == SolverStatus::Interrupted ||
552 opts.always_overwrite_results) {
553 write_solution(*curr);
554 }
555 s.iterations = k;
556 s.ε = εₖ;
557 s.elapsed_time = duration_cast<nanoseconds>(time_elapsed);
559 s.status = stop_status;
560 s.final_γ = curr->γ;
561 s.final_ψ = curr->ψû;
562 s.final_h = 0; // only box constraints
563 s.final_φγ = curr->fbe();
564 return s;
565 }
566
567 // Calculate Gauss-Newton step -----------------------------------------
568
569 real_t τ_init = 1;
570 did_gn = do_gn_step;
571 if (params.disable_acceleration) {
572 τ_init = 0;
573 } else if (do_gn_step) {
574 auto is_constr_inactive = [&](index_t t, index_t i) {
575 real_t ui = vars.uk(curr->xu, t)(i);
576 // Gradient descent step.
577 real_t gs = ui - curr->γ * curr->grad_ψ(t * nu + i);
578 // Check whether the box constraints are active for this index.
579 bool active_lb = gs <= U.lowerbound(i);
580 bool active_ub = gs >= U.upperbound(i);
581 if (active_ub) {
582 q(nu * t + i) = U.upperbound(i) - ui;
583 return false;
584 } else if (active_lb) {
585 q(nu * t + i) = U.lowerbound(i) - ui;
586 return false;
587 } else { // Store inactive indices
588 return true;
589 }
590 };
591 { // Find active indices J
593 J.update(is_constr_inactive);
594 nJ = J.sizes().sum();
595 }
596 { // evaluate the Jacobians
598 for (index_t t = 0; t < N; ++t)
599 problem.eval_jac_f(t, vars.xk(curr->xu, t),
600 vars.uk(curr->xu, t), vars.ABk(jacs, t));
601 }
602 { // LQR factor
604 lqr.factor_masked(ABk, Qk(curr->xu), Rk(curr->xu), Sk(curr->xu),
605 Rk_prod(curr->xu), Sk_prod(curr->xu), qk, rk,
606 uk_eq, Jk, Kk, params.lqr_factor_cholesky);
607 }
608 { // LQR solve
610 lqr.solve_masked(ABk, Jk, q, work_2x);
611 }
612 } else {
613 if (!enable_lbfgs)
614 throw std::logic_error("enable_lbfgs");
615
616 // Find inactive indices J
617 auto is_constr_inactive = [&](index_t t, index_t i) {
618 real_t ui = vars.uk(curr->xu, t)(i);
619 real_t grad_i = curr->grad_ψ(t * nu + i);
620 // Gradient descent step.
621 real_t gs = ui - curr->γ * grad_i;
622 // Check whether the box constraints are active for this index.
623 bool active_lb = gs <= U.lowerbound(i);
624 bool active_ub = gs >= U.upperbound(i);
625 if (active_ub || active_lb) {
626 q(t * nu + i) = curr->p(t * nu + i);
627 return false;
628 } else { // Store inactive indices
629 q(t * nu + i) = -grad_i;
630 return true;
631 }
632 };
633
634 auto J_idx = J.indices();
635 nJ = 0;
636 {
638 for (index_t t = 0; t < N; ++t)
639 for (index_t i = 0; i < nu; ++i)
640 if (is_constr_inactive(t, i))
641 J_idx(nJ++) = t * nu + i;
642 }
643 auto J_lbfgs = J_idx.topRows(nJ);
644
645 // If all indices are inactive, we can use standard L-BFGS,
646 // if there are active indices, we need the specialized version
647 // that only applies L-BFGS to the inactive indices
648 bool success = [&] {
650 return lbfgs.apply_masked(q, curr->γ, J_lbfgs);
651 }();
652 // If L-BFGS application failed, qₖ(J) still contains
653 // -∇ψ(x)(J) - HqK(J) or -∇ψ(x)(J), which is not a valid step.
654 if (not success)
655 τ_init = 0;
656 }
657
658 // Make sure quasi-Newton step is valid
659 if (not q.allFinite()) {
660 τ_init = 0;
661 // Is there anything we can do?
662 if (not did_gn)
663 lbfgs.reset();
664 }
665 s.lbfgs_failures += (τ_init == 0 && k > 0);
666
667 bool do_next_gn = params.gn_interval > 0 &&
668 ((k + 1) % params.gn_interval) == 0 &&
669 !params.disable_acceleration;
670 do_gn_step = do_next_gn || (do_gn_step && params.gn_sticky);
671
672 // Line search ---------------------------------------------------------
673
674 next->γ = curr->γ;
675 next->L = curr->L;
676 τ = τ_init;
677 real_t τ_prev = -1;
678 bool dir_rejected = true;
679
680 // xₖ₊₁ = xₖ + pₖ
681 auto take_safe_step = [&] {
682 next->xu = curr->xû;
683 next->ψu = curr->ψû;
684 // Calculate ∇ψ(xₖ₊₁)
685 eval_backward(*next);
686 };
687
688 // xₖ₊₁ = xₖ + (1-τ) pₖ + τ qₖ
689 auto take_accelerated_step = [&](real_t τ) {
690 if (τ == 1) {
691 for (index_t t = 0; t < N; ++t)
692 vars.uk(next->xu, t) =
693 vars.uk(curr->xu, t) + q.segment(t * nu, nu);
694 } else {
695 do_gn_step = do_next_gn;
696 for (index_t t = 0; t < N; ++t)
697 vars.uk(next->xu, t) =
698 vars.uk(curr->xu, t) +
699 (1 - τ) * curr->p.segment(t * nu, nu) +
700 τ * q.segment(t * nu, nu);
701 }
702 // Calculate ψ(xₖ₊₁), ∇ψ(xₖ₊₁)
703 eval_forward(*next); // Not necessary for DDP
704 eval_backward(*next);
705 };
706
707 // Backtracking line search loop
708 while (!stop_signal.stop_requested()) {
709
710 // Recompute step only if τ changed
711 if (τ != τ_prev) {
712 τ != 0 ? take_accelerated_step(τ) : take_safe_step();
713 τ_prev = τ;
714 }
715
716 // If the cost is not finite, or if the quadratic upper bound could
717 // not be satisfied, abandon the direction entirely, don't even
718 // bother backtracking.
719 bool fail = next->L >= params.L_max || !std::isfinite(next->ψu);
720 if (τ > 0 && fail) {
721 // Don't allow a bad accelerated step to destroy the FBS step
722 // size
723 next->L = curr->L;
724 next->γ = curr->γ;
725 // Line search failed
726 τ = 0;
727 if (enable_lbfgs)
728 lbfgs.reset();
729 continue;
730 }
731
732 // Calculate x̂ₖ₊₁, ψ(x̂ₖ₊₁)
733 eval_prox(*next);
734 eval_forward_hat(*next);
735
736 // Quadratic upper bound step size condition
737 if (next->L < params.L_max && qub_violated(*next)) {
738 next->γ /= 2;
739 next->L *= 2;
740 if (τ > 0)
741 τ = τ_init;
743 continue;
744 }
745
746 // Line search condition
747 if (τ > 0 && linesearch_violated(*curr, *next)) {
748 τ /= 2;
749 if (τ < params.min_linesearch_coefficient)
750 τ = 0;
752 continue;
753 }
754
755 // QUB and line search satisfied (or τ is 0 and L > L_max)
756 break;
757 }
758
759 // If τ < τ_min the line search failed and we accepted the prox step
760 s.linesearch_failures += (τ == 0 && τ_init > 0);
761 s.τ_1_accepted += τ == 1;
762 s.count_τ += (τ_init > 0);
763 s.sum_τ += τ;
764
765 // Check if we made any progress
766 if (no_progress > 0 || k % params.max_no_progress == 0)
767 no_progress = curr->xu == next->xu ? no_progress + 1 : 0;
768
769 // Update L-BFGS -------------------------------------------------------
770
771 if (enable_lbfgs) {
772 const bool force = true;
773 assign_extract_u(next->xu, next->u);
774 bool reset_because_gn = did_gn && params.reset_lbfgs_on_gn_step;
775 if (reset_because_gn || curr->γ != next->γ) {
776 lbfgs.reset();
777 }
778 if (!reset_because_gn) { // TODO: this may be too restrictive
780 s.lbfgs_rejected += dir_rejected = not lbfgs.update(
781 curr->u, next->u, curr->grad_ψ, next->grad_ψ,
783 }
784 }
785
786 // Print ---------------------------------------------------------------
787 do_progress_cb(k, *curr, q, τ, εₖ, did_gn, nJ, SolverStatus::Busy);
788 if (do_print && (k != 0 || did_gn))
789 print_progress_2(q, τ, did_gn, nJ, lqr.min_rcond, dir_rejected);
790
791 // Advance step --------------------------------------------------------
792 std::swap(curr, next);
793 ++k;
794 }
795 throw std::logic_error("[PANOC] loop error");
796}
797
798} // namespace alpaqa
Limited memory Broyden–Fletcher–Goldfarb–Shanno (L-BFGS) algorithm.
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:151
unsigned stepsize_backtracks
Definition: panoc-ocp.hpp:119
typename Conf::mat mat
Definition: config.hpp:57
std::chrono::nanoseconds time_jacobians
Definition: panoc-ocp.hpp:107
auto projecting_difference(const auto &v, const Box< Conf > &box)
Get the difference between the given vector and its projection.
Definition: box.hpp:49
@ 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:61
std::chrono::nanoseconds time_hessians
Definition: panoc-ocp.hpp:108
typename Conf::rmat rmat
Definition: config.hpp:60
std::chrono::nanoseconds time_lbfgs_apply
Definition: panoc-ocp.hpp:113
std::chrono::nanoseconds time_lbfgs_indices
Definition: panoc-ocp.hpp:112
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
Definition: panoc-ocp.hpp:115
std::chrono::nanoseconds time_indices
Definition: panoc-ocp.hpp:109
std::chrono::nanoseconds elapsed_time
Definition: panoc-ocp.hpp:103
std::chrono::nanoseconds time_backward
Definition: panoc-ocp.hpp:106
typename Conf::real_t real_t
Definition: config.hpp:51
std::chrono::nanoseconds time_lqr_factor
Definition: panoc-ocp.hpp:110
unsigned linesearch_backtracks
Definition: panoc-ocp.hpp:118
typename Conf::index_t index_t
Definition: config.hpp:63
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
std::chrono::nanoseconds time_prox
Definition: panoc-ocp.hpp:104
std::chrono::nanoseconds time_forward
Definition: panoc-ocp.hpp:105
unsigned linesearch_failures
Definition: panoc-ocp.hpp:117
typename Conf::vec vec
Definition: config.hpp:52
std::chrono::nanoseconds time_lbfgs_update
Definition: panoc-ocp.hpp:114
SolverStatus status
Definition: panoc-ocp.hpp:101
std::chrono::nanoseconds time_lqr_solve
Definition: panoc-ocp.hpp:111
typename Conf::crindexvec crindexvec
Definition: config.hpp:66
vec upperbound
Definition: box.hpp:26
static Box NaN(length_t n)
Definition: box.hpp:17
vec lowerbound
Definition: box.hpp:25
vec create() const
Definition: ocp-vars.hpp:65
length_t nu() const
Definition: ocp-vars.hpp:57