42 return "PANOCOCPSolver<" + std::string(config_t::get_name()) +
'>';
63 using std::chrono::nanoseconds;
64 auto os = opts.os ? opts.os : this->os;
65 auto start_time = std::chrono::steady_clock::now();
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;
75 bool enable_lbfgs = params.gn_interval != 1;
83 auto &vars = eval.
vars;
86 LQRFactor lqr{{.N = N, .nx = nx, .nu = nu}};
88 mat jacs = vars.create_AB();
89 vec qr = vars.create_qr();
100 assert(μ.size() == nc * N + nc_N);
101 assert(y.size() == nc * N + nc_N);
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);
113 auto Rk = [&](
rvec storage) {
114 return [&, storage](
index_t k) {
117 return eval.Rk(storage, k, mask, out);
121 auto Sk = [&](
rvec storage) {
122 return [&, storage](
index_t k) {
125 return eval.Sk(storage, k, mask, out);
129 auto Rk_prod = [&](
rvec storage) {
130 return [&, storage](
index_t k) {
134 return eval.Rk_prod(storage, k, mask_J, mask_K, v, out);
138 auto Sk_prod = [&](
rvec storage) {
139 return [&, storage](
index_t k) {
142 return eval.Sk_prod(storage, k, mask_K, v, out);
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); };
164 real_t ψu = NaN<config_t>;
165 real_t ψû = NaN<config_t>;
168 real_t pᵀp = NaN<config_t>;
169 real_t grad_ψᵀp = NaN<config_t>;
173 real_t fbe()
const {
return ψu + pᵀp / (2 * γ) + grad_ψᵀp; }
177 p{vars.
N * vars.
nu()}, u{enable_lbfgs ? vars.
N * vars.
nu() : 0} {}
179 {vars, enable_lbfgs},
180 {vars, enable_lbfgs},
182 Iterate *curr = &iterates[0];
183 Iterate *next = &iterates[1];
191 .binaryExpr(U.
lowerbound - x, binary_real_f(std::fmax))
192 .binaryExpr(U.
upperbound - x, binary_real_f(std::fmin));
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 vars.uk(x̂u, t), p_t);
207 pᵀp += p_t.squaredNorm();
208 grad_ψᵀp += grad_ψ_t.dot(p_t);
210 return std::make_tuple(pᵀp, grad_ψᵀp);
213 auto calc_error_stop_crit = [
this, &eval_prox_impl](
217 switch (params.stop_crit) {
222 return std::sqrt(pₖᵀpₖ);
225 eval_prox_impl(1, xuₖ, grad_ψₖ, work_xu, work_p);
230 eval_prox_impl(1, xuₖ, grad_ψₖ, work_xu, work_p);
231 return std::sqrt(pTp);
237 return std::sqrt(pₖᵀpₖ) / γ;
244 throw std::invalid_argument(
"Unsupported stopping criterion");
248 auto check_all_stop_conditions =
257 unsigned no_progress) {
258 auto max_time = params.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;
277 auto assign_interleave_xu = [&vars](
crvec u,
rvec xu) {
280 auto assign_extract_u = [&vars](
crvec xu,
rvec u) {
284 auto write_solution = [&](Iterate &it) {
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);
294 et -= μt.asDiagonal().inverse() * yt;
295 yt += μt.asDiagonal() * et;
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);
303 et -= μt.asDiagonal().inverse() * yt;
304 yt += μt.asDiagonal() * et;
306 assign_extract_u(it.xû, u);
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);
319 auto eval_forward = [&](Iterate &i) {
321 i.ψu = eval.forward(i.xu, D, D_N, μ, y);
325 auto eval_forward_hat = [&](Iterate &i) {
327 i.ψû = eval.forward(i.xû, D, D_N, μ, y);
332 auto eval_backward = [&](Iterate &i) {
334 eval.backward(i.xu, i.grad_ψ, mut_qrk, mut_q_N, D, D_N, μ, y);
337 auto qub_violated = [
this](
const Iterate &i) {
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;
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.γ);
348 real_t margin = (1 + std::abs(φγ)) * params.linesearch_tolerance_factor;
349 return next.fbe() > φγ - σ * curr.pᵀp + margin;
352 auto initial_lipschitz_estimate =
372 auto h = it->grad_ψ.unaryExpr([&](
real_t g) {
373 return g > 0 ? std::max(g * ε, δ) : std::min(g * ε, -δ);
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);
383 eval.forward_simulate(work_xu);
387 eval.backward(work_xu, work_grad_ψ, mut_qrk, mut_q_N, D, D_N, μ,
391 it->L = (work_grad_ψ - it->grad_ψ).norm() / norm_h;
392 it->L = std::clamp(it->L, L_min, L_max);
397 std::array<char, 64> print_buf;
398 auto print_real = [&](
real_t x) {
401 auto print_real3 = [&](
real_t x) {
407 *os <<
"┌─[PANOCOCP]\n";
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';
419 *os <<
"│ ‖q‖ = " << print_real(qₖ.norm())
420 <<
", #J = " << std::setw(7 + params.print_precision) << nJ
421 <<
", cond = " << print_real3(
real_t(1) / min_rcond)
422 <<
", τ = " << print_real3(τₖ)
423 <<
", " << (did_gn ?
"GN" :
"L-BFGS")
427 *os <<
"└─ " << status <<
" ──"
431 auto do_progress_cb = [
this, &s, &problem, &lqr,
432 &opts](
unsigned k, Iterate &curr,
crvec q,
real_t τ,
445 .norm_sq_p = curr.pᵀp,
449 .grad_ψ = curr.grad_ψ,
454 .lqr_min_rcond = lqr.min_rcond,
457 .τ = status ==
Busy ? τ : NaN<config_t>,
459 .outer_iter = opts.outer_iter,
467 assign_interleave_xu(u, curr->xu);
468 problem.get_x_init(curr->xu.topRows(nx));
469 curr->xû.topRows(nx) = curr->xu.topRows(nx);
470 next->xu.topRows(nx) = curr->xu.topRows(nx);
471 next->xû.topRows(nx) = curr->xu.topRows(nx);
477 problem.get_D_N(D_N);
479 bool do_gn_step = params.gn_interval > 0 and !params.disable_acceleration;
488 if (params.Lipschitz.L_0 <= 0) {
489 initial_lipschitz_estimate(curr, params.Lipschitz.ε, params.Lipschitz.δ,
490 params.L_min, params.L_max, next->xu,
495 curr->L = params.Lipschitz.L_0;
498 eval_backward(*curr);
500 if (not std::isfinite(curr->L)) {
504 curr->γ = params.Lipschitz.Lγ_factor / curr->L;
506 eval_forward_hat(*curr);
513 unsigned no_progress = 0;
521 real_t εₖ = calc_error_stop_crit(curr->γ, curr->xu, curr->grad_ψ,
522 curr->p, curr->pᵀp, next->xû, next->p);
526 params.print_interval != 0 && k % params.print_interval == 0;
528 print_progress_1(k, curr->fbe(), curr->ψu, curr->grad_ψ, curr->pᵀp,
533 auto time_elapsed = std::chrono::steady_clock::now() - start_time;
535 check_all_stop_conditions(time_elapsed, k, εₖ, no_progress);
537 do_progress_cb(k, *curr, null_vec<config_t>, -1, εₖ,
false, 0,
539 bool do_final_print = params.print_interval != 0;
540 if (!do_print && do_final_print)
541 print_progress_1(k, curr->fbe(), curr->ψu, curr->grad_ψ,
542 curr->pᵀp, curr->γ, εₖ);
543 if (do_print || do_final_print)
544 print_progress_n(stop_status);
547 opts.always_overwrite_results) {
548 write_solution(*curr);
552 s.
elapsed_time = duration_cast<nanoseconds>(time_elapsed);
566 if (params.disable_acceleration) {
568 }
else if (do_gn_step) {
570 real_t ui = vars.uk(curr->xu, t)(i);
572 real_t gs = ui - curr->γ * curr->grad_ψ(t * nu + i);
579 }
else if (active_lb) {
588 J.update(is_constr_inactive);
589 nJ = J.sizes().sum();
593 for (
index_t t = 0; t < N; ++t)
594 problem.eval_jac_f(t, vars.xk(curr->xu, t),
595 vars.uk(curr->xu, t), vars.ABk(jacs, t));
599 lqr.factor_masked(ABk, Qk(curr->xu), Rk(curr->xu), Sk(curr->xu),
600 Rk_prod(curr->xu), Sk_prod(curr->xu), qk, rk,
601 uk_eq, Jk, Kk, params.lqr_factor_cholesky);
605 lqr.solve_masked(ABk, Jk, q, work_2x);
609 throw std::logic_error(
"enable_lbfgs");
613 real_t ui = vars.uk(curr->xu, t)(i);
614 real_t grad_i = curr->grad_ψ(t * nu + i);
616 real_t gs = ui - curr->γ * grad_i;
620 if (active_ub || active_lb) {
621 q(t * nu + i) = curr->p(t * nu + i);
624 q(t * nu + i) = -grad_i;
629 auto J_idx = J.indices();
633 for (
index_t t = 0; t < N; ++t)
634 for (
index_t i = 0; i < nu; ++i)
635 if (is_constr_inactive(t, i))
636 J_idx(nJ++) = t * nu + i;
638 auto J_lbfgs = J_idx.topRows(nJ);
645 return lbfgs.apply_masked(q, curr->γ, J_lbfgs);
654 if (not q.allFinite()) {
662 bool do_next_gn = params.gn_interval > 0 &&
663 ((k + 1) % params.gn_interval) == 0 &&
664 !params.disable_acceleration;
665 do_gn_step = do_next_gn || (do_gn_step && params.gn_sticky);
675 auto take_safe_step = [&] {
679 eval_backward(*next);
683 auto take_accelerated_step = [&](
real_t τ) {
685 for (
index_t t = 0; t < N; ++t)
686 vars.uk(next->xu, t) =
687 vars.uk(curr->xu, t) + q.segment(t * nu, nu);
689 do_gn_step = do_next_gn;
690 for (
index_t t = 0; t < N; ++t)
691 vars.uk(next->xu, t) =
692 vars.uk(curr->xu, t) +
693 (1 - τ) * curr->p.segment(t * nu, nu) +
694 τ * q.segment(t * nu, nu);
698 eval_backward(*next);
702 while (!stop_signal.stop_requested()) {
706 τ != 0 ? take_accelerated_step(τ) : take_safe_step();
712 eval_forward_hat(*next);
715 if (next->L < params.L_max && qub_violated(*next)) {
724 if (τ > 0 && linesearch_violated(*curr, *next)) {
726 if (τ < params.min_linesearch_coefficient)
743 if (no_progress > 0 || k % params.max_no_progress == 0)
744 no_progress = curr->xu == next->xu ? no_progress + 1 : 0;
749 const bool force =
true;
750 assign_extract_u(next->xu, next->u);
751 bool reset_because_gn = did_gn && params.reset_lbfgs_on_gn_step;
752 if (reset_because_gn || curr->γ != next->γ) {
755 if (!reset_because_gn) {
758 curr->u, next->u, curr->grad_ψ, next->grad_ψ,
765 if (do_print && (k != 0 || did_gn))
766 print_progress_2(q, τ, did_gn, nJ, lqr.min_rcond);
769 std::swap(curr, next);
772 throw std::logic_error(
"[PANOC] loop error");
Limited memory Broyden–Fletcher–Goldfarb–Shanno (L-BFGS) algorithm.
std::string get_name() const
Stats operator()(const Problem &problem, const SolveOptions &opts, rvec u, rvec y, crvec μ, rvec err_z)
void assign_extract_u(const OCPVariables< Conf > &dim, crvec< Conf > storage, rvec< Conf > u)
vec< Conf > extract_u(const TypeErasedControlProblem< Conf > &problem, crvec< Conf > xu)
vec< Conf > extract_x(const TypeErasedControlProblem< Conf > &problem, crvec< Conf > xu)
void assign_interleave_xu(const OCPVariables< Conf > &dim, crvec< Conf > u, rvec< Conf > storage)
auto norm_inf(const Eigen::MatrixBase< Derived > &v)
Get the maximum or infinity-norm of the given vector.
unsigned stepsize_backtracks
std::chrono::nanoseconds time_jacobians
auto projecting_difference(const auto &v, const Box< Conf > &box)
Get the difference between the given vector and its projection.
@ 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
std::chrono::nanoseconds time_hessians
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.
@ 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
std::chrono::nanoseconds time_lqr_factor
unsigned linesearch_backtracks
typename Conf::index_t index_t
typename Conf::length_t length_t
std::string_view float_to_str_vw(auto &buf, double value, int precision=std::numeric_limits< double >::max_digits10)
typename Conf::crvec crvec
std::chrono::nanoseconds time_prox
std::chrono::nanoseconds time_forward
unsigned linesearch_failures
std::chrono::nanoseconds time_lbfgs_update
std::chrono::nanoseconds time_lqr_solve
typename Conf::crindexvec crindexvec
static Box NaN(length_t n)