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);
293 et = projecting_difference(ζ, D);
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);
302 et = projecting_difference(ζ, D_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';
418 real_t min_rcond,
bool reject) {
419 const char *color = τₖ == 1 ?
"\033[0;32m"
420 : τₖ > 0 ?
"\033[0;33m"
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")
428 << (reject ?
"\033[0;31mrejected\033[0m"
429 :
"\033[0;32maccepted\033[0m")
433 *os <<
"└─ " << status <<
" ──"
437 auto do_progress_cb = [
this, &s, &problem, &lqr,
438 &opts](
unsigned k, Iterate &curr,
crvec q,
real_t τ,
450 .norm_sq_p = curr.pᵀp,
454 .grad_ψ = curr.grad_ψ,
459 .lqr_min_rcond = lqr.min_rcond,
464 .outer_iter = opts.outer_iter,
472 assign_interleave_xu(u, curr->xu);
473 problem.get_x_init(curr->xu.topRows(nx));
474 curr->xû.topRows(nx) = curr->xu.topRows(nx);
475 next->xu.topRows(nx) = curr->xu.topRows(nx);
476 next->xû.topRows(nx) = curr->xu.topRows(nx);
482 problem.get_D_N(D_N);
484 bool do_gn_step = params.gn_interval > 0 and !params.disable_acceleration;
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,
500 curr->L = params.Lipschitz.L_0;
503 eval_backward(*curr);
505 if (not std::isfinite(curr->L)) {
509 curr->γ = params.Lipschitz.Lγ_factor / curr->L;
514 eval_forward_hat(*curr);
517 while (curr->L < params.L_max && qub_violated(*curr)) {
521 eval_forward_hat(*curr);
530 unsigned no_progress = 0;
538 real_t εₖ = calc_error_stop_crit(curr->γ, curr->xu, curr->grad_ψ,
539 curr->p, curr->pᵀp, next->xû, next->p);
543 params.print_interval != 0 && k % params.print_interval == 0;
545 print_progress_1(k, curr->fbe(), curr->ψu, curr->grad_ψ, curr->pᵀp,
550 auto time_elapsed = std::chrono::steady_clock::now() - start_time;
552 check_all_stop_conditions(time_elapsed, k, εₖ, no_progress);
554 do_progress_cb(k, *curr, null_vec<config_t>, -1, εₖ,
false, 0,
556 bool do_final_print = params.print_interval != 0;
557 if (!do_print && do_final_print)
558 print_progress_1(k, curr->fbe(), curr->ψu, curr->grad_ψ,
559 curr->pᵀp, curr->γ, εₖ);
560 if (do_print || do_final_print)
561 print_progress_n(stop_status);
564 opts.always_overwrite_results) {
565 write_solution(*curr);
569 s.
elapsed_time = duration_cast<nanoseconds>(time_elapsed);
583 if (params.disable_acceleration) {
585 }
else if (do_gn_step) {
587 real_t ui = vars.uk(curr->xu, t)(i);
589 real_t gs = ui - curr->γ * curr->grad_ψ(t * nu + i);
596 }
else if (active_lb) {
605 J.update(is_constr_inactive);
606 nJ = J.sizes().sum();
610 for (
index_t t = 0; t < N; ++t)
611 problem.eval_jac_f(t, vars.xk(curr->xu, t),
612 vars.uk(curr->xu, t), vars.ABk(jacs, t));
616 lqr.factor_masked(ABk, Qk(curr->xu), Rk(curr->xu), Sk(curr->xu),
617 Rk_prod(curr->xu), Sk_prod(curr->xu), qk, rk,
618 uk_eq, Jk, Kk, params.lqr_factor_cholesky);
622 lqr.solve_masked(ABk, Jk, q, work_2x);
626 throw std::logic_error(
"enable_lbfgs");
630 real_t ui = vars.uk(curr->xu, t)(i);
631 real_t grad_i = curr->grad_ψ(t * nu + i);
633 real_t gs = ui - curr->γ * grad_i;
637 if (active_ub || active_lb) {
638 q(t * nu + i) = curr->p(t * nu + i);
641 q(t * nu + i) = -grad_i;
646 auto J_idx = J.indices();
650 for (
index_t t = 0; t < N; ++t)
651 for (
index_t i = 0; i < nu; ++i)
652 if (is_constr_inactive(t, i))
653 J_idx(nJ++) = t * nu + i;
655 auto J_lbfgs = J_idx.topRows(nJ);
662 return lbfgs.apply_masked(q, curr->γ, J_lbfgs);
671 if (not q.allFinite()) {
679 bool do_next_gn = params.gn_interval > 0 &&
680 ((k + 1) % params.gn_interval) == 0 &&
681 !params.disable_acceleration;
682 do_gn_step = do_next_gn || (do_gn_step && params.gn_sticky);
690 bool dir_rejected =
true;
693 auto take_safe_step = [&] {
697 eval_backward(*next);
701 auto take_accelerated_step = [&](
real_t τ) {
703 for (
index_t t = 0; t < N; ++t)
704 vars.uk(next->xu, t) =
705 vars.uk(curr->xu, t) + q.segment(t * nu, nu);
707 do_gn_step = do_next_gn;
708 for (
index_t t = 0; t < N; ++t)
709 vars.uk(next->xu, t) =
710 vars.uk(curr->xu, t) +
711 (1 - τ) * curr->p.segment(t * nu, nu) +
712 τ * q.segment(t * nu, nu);
716 eval_backward(*next);
720 while (!stop_signal.stop_requested()) {
724 τ != 0 ? take_accelerated_step(τ) : take_safe_step();
731 bool fail = next->L >= params.L_max || !std::isfinite(next->ψu);
746 eval_forward_hat(*next);
749 if (next->L < params.L_max && qub_violated(*next)) {
759 if (τ > 0 && linesearch_violated(*curr, *next)) {
761 if (τ < params.min_linesearch_coefficient)
778 if (no_progress > 0 || k % params.max_no_progress == 0)
779 no_progress = curr->xu == next->xu ? no_progress + 1 : 0;
784 const bool force =
true;
785 assign_extract_u(next->xu, next->u);
786 bool reset_because_gn = did_gn && params.reset_lbfgs_on_gn_step;
787 if (reset_because_gn || curr->γ != next->γ) {
790 if (!reset_because_gn) {
793 curr->u, next->u, curr->grad_ψ, next->grad_ψ,
800 if (do_print && (k != 0 || did_gn))
801 print_progress_2(q, τ, did_gn, nJ, lqr.min_rcond, dir_rejected);
804 std::swap(curr, next);
807 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
@ 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)