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); };
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.
lower - x, binary_real_f(std::fmax))
192 .binaryExpr(U.
upper - 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](
218 switch (
params.stop_crit) {
223 return std::sqrt(pₖᵀpₖ);
226 eval_prox_impl(1, xuₖ, grad_ψₖ, work_xu, work_p);
227 return norm_inf(work_p);
231 eval_prox_impl(1, xuₖ, grad_ψₖ, work_xu, work_p);
232 return std::sqrt(pTp);
235 return norm_inf(pₖ) / γ;
238 return std::sqrt(pₖᵀpₖ) / γ;
245 throw std::invalid_argument(
"Unsupported stopping criterion");
249 auto check_all_stop_conditions =
258 unsigned no_progress) {
259 auto max_time =
params.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;
266 bool not_finite = not std::isfinite(εₖ);
267 bool conv = εₖ <= tolerance;
268 bool max_no_progress = no_progress >
params.max_no_progress;
278 auto assign_interleave_xu = [&vars](
crvec u,
rvec xu) {
281 auto assign_extract_u = [&vars](
crvec xu,
rvec u) {
285 auto write_solution = [&](Iterate &it) {
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;
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;
307 assign_extract_u(it.xû, u);
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);
320 auto eval_forward = [&](Iterate &i) {
322 i.ψu = eval.
forward(i.xu, D, D_N, μ, y);
326 auto eval_forward_hat = [&](Iterate &i) {
328 i.ψû = eval.
forward(i.xû, D, D_N, μ, y);
333 auto eval_backward = [&](Iterate &i) {
335 eval.
backward(i.xu, i.grad_ψ, mut_qrk, mut_q_N, D, D_N, μ, y);
338 auto qub_violated = [
this](
const Iterate &i) {
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;
344 auto linesearch_violated = [
this](
const Iterate &curr,
345 const Iterate &next) {
347 real_t σ = β * (1 - curr.γ * curr.L) / (2 * curr.γ);
349 real_t margin = (1 + std::abs(φγ)) *
params.linesearch_tolerance_factor;
350 return next.fbe() > φγ - σ * curr.pᵀp + margin;
353 auto initial_lipschitz_estimate =
373 auto h = it->grad_ψ.unaryExpr([&](
real_t g) {
374 return g > 0 ? std::max(g * ε, δ) : std::min(g * ε, -δ);
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);
388 eval.
backward(work_xu, work_grad_ψ, mut_qrk, mut_q_N, D, D_N, μ,
392 it->L = (work_grad_ψ - it->grad_ψ).norm() / norm_h;
393 it->L = std::clamp(it->L, L_min, L_max);
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);
402 auto print_real3 = [&](
real_t x) {
403 return float_to_str_vw(print_buf, x, 3);
408 *
os <<
"┌─[PANOCOCP]\n";
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';
419 real_t min_rcond,
bool reject) {
420 const char *color = τₖ == 1 ?
"\033[0;32m"
421 : τₖ > 0 ?
"\033[0;33m"
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")
429 << (reject ?
"\033[0;31mrejected\033[0m"
430 :
"\033[0;32maccepted\033[0m")
434 *
os <<
"└─ " << status <<
" ──"
438 auto do_progress_cb = [
this, &s, &problem, &lqr,
439 &opts](
unsigned k, Iterate &curr,
crvec q,
real_t τ,
451 .norm_sq_p = curr.pᵀp,
455 .grad_ψ = curr.grad_ψ,
460 .lqr_min_rcond = lqr.min_rcond,
465 .outer_iter = opts.outer_iter,
473 assign_interleave_xu(u, curr->xu);
474 problem.get_x_init(curr->xu.topRows(nx));
475 curr->xû.topRows(nx) = curr->xu.topRows(nx);
476 next->xu.topRows(nx) = curr->xu.topRows(nx);
477 next->xû.topRows(nx) = curr->xu.topRows(nx);
483 problem.get_D_N(D_N);
485 bool do_gn_step =
params.gn_interval > 0 and !
params.disable_acceleration;
494 if (
params.Lipschitz.L_0 <= 0) {
495 initial_lipschitz_estimate(curr,
params.Lipschitz.ε,
params.Lipschitz.δ,
501 curr->L =
params.Lipschitz.L_0;
504 eval_backward(*curr);
506 if (not std::isfinite(curr->L)) {
510 curr->γ =
params.Lipschitz.Lγ_factor / curr->L;
515 eval_forward_hat(*curr);
518 while (curr->L <
params.L_max && qub_violated(*curr)) {
522 eval_forward_hat(*curr);
531 unsigned no_progress = 0;
539 real_t εₖ = calc_error_stop_crit(curr->γ, curr->xu, curr->grad_ψ,
540 curr->p, curr->pᵀp, next->xû, next->p);
544 params.print_interval != 0 && k %
params.print_interval == 0;
546 print_progress_1(k, curr->fbe(), curr->ψu, curr->grad_ψ, curr->pᵀp,
551 auto time_elapsed = std::chrono::steady_clock::now() - start_time;
553 check_all_stop_conditions(time_elapsed, k, εₖ, no_progress);
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->γ, εₖ);
561 if (do_print || do_final_print)
562 print_progress_n(stop_status);
565 opts.always_overwrite_results) {
566 write_solution(*curr);
570 s.
elapsed_time = duration_cast<nanoseconds>(time_elapsed);
584 if (
params.disable_acceleration) {
586 }
else if (do_gn_step) {
588 real_t ui = vars.uk(curr->xu, t)(i);
590 real_t gs = ui - curr->γ * curr->grad_ψ(t * nu + i);
592 bool active_lb = gs <= U.
lower(i);
593 bool active_ub = gs >= U.
upper(i);
595 q(nu * t + i) = U.
upper(i) - ui;
597 }
else if (active_lb) {
598 q(nu * t + i) = U.
lower(i) - ui;
606 J.
update(is_constr_inactive);
607 nJ = J.
sizes().sum();
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));
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);
623 lqr.solve_masked(ABk, Jk, q, work_2x);
627 throw std::logic_error(
"enable_lbfgs");
631 real_t ui = vars.uk(curr->xu, t)(i);
632 real_t grad_i = curr->grad_ψ(t * nu + i);
634 real_t gs = ui - curr->γ * grad_i;
636 bool active_lb = gs <= U.
lower(i);
637 bool active_ub = gs >= U.
upper(i);
638 if (active_ub || active_lb) {
639 q(t * nu + i) = curr->p(t * nu + i);
642 q(t * nu + i) = -grad_i;
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;
656 auto J_lbfgs = J_idx.topRows(nJ);
672 if (not q.allFinite()) {
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);
691 bool dir_rejected =
true;
694 auto take_safe_step = [&] {
698 eval_backward(*next);
702 auto take_accelerated_step = [&](
real_t τ) {
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);
708 do_gn_step = do_next_gn;
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);
717 eval_backward(*next);
725 τ != 0 ? take_accelerated_step(τ) : take_safe_step();
732 bool fail = next->L >=
params.L_max || !std::isfinite(next->ψu);
747 eval_forward_hat(*next);
750 if (next->L <
params.L_max && qub_violated(*next)) {
760 if (τ > 0 && linesearch_violated(*curr, *next)) {
762 if (τ <
params.min_linesearch_coefficient)
779 if (no_progress > 0 || k %
params.max_no_progress == 0)
780 no_progress = curr->xu == next->xu ? no_progress + 1 : 0;
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->γ) {
791 if (!reset_because_gn) {
794 curr->u, next->u, curr->grad_ψ, next->grad_ψ,
801 if (do_print && (k != 0 || did_gn))
802 print_progress_2(q, τ, did_gn, nJ, lqr.min_rcond, dir_rejected);
805 std::swap(curr, next);
808 throw std::logic_error(
"[PANOC] loop error");