129 using clock_t = std::chrono::steady_clock;
136 clock_t::time_point start_time = clock_t::now();
138 real_t ineq_constr_resid = std::numeric_limits<real_t>::quiet_NaN();
139 unsigned inner_iter = 0, outer_iter = 0;
141 std::optional<DetailedStats> detailed_stats{};
143 detailed_stats.emplace();
146 backend.initial_variables(ctx,
x);
147 backend.initial_multipliers_eq(ctx,
λ);
148 backend.initial_multipliers_ineq(ctx,
y);
152 auto y = std::ref(this->
y),
ŷ = std::ref(this->
ŷ);
154 auto e = std::ref(this->
e),
e_old = std::ref(this->
e_old);
158 backend.eq_constr_resid(ctx, x, Mxb);
161 backend.mat_vec_A(ctx, x, Ax);
163 backend.ineq_constr_resid(ctx,
Ax,
e);
164 real_t f0 = timed(timings.
mat_vec_Q, [&] {
165 return backend.f_grad_f(ctx, x, grad);
168 backend.mat_vec_MT(ctx, λ, Mᵀλ);
180 unsigned no_change_active_set = 0;
181 bool force_τ_1_active_set =
false;
183 remaining_iter = std::min(remaining_iter, settings.
max_inner_iter);
184 real_t stationarity = std::numeric_limits<real_t>::infinity();
185 real_t eq_resid = std::numeric_limits<real_t>::infinity();
186 bool increase_penalty_y =
true;
187 for (
unsigned inner = 0;
true; ++inner) {
188 const unsigned inner_total = inner_iter + inner;
191 return backend.calc_ŷ_Aᵀŷ(ctx, Ax, Σ, y, ŷ, Aᵀŷ, active_set);
195 auto leave_inner = [&] {
198 backend.grad_f_remove_regularization(ctx, S,
x,
x_outer,
grad);
205 bool first_iter = outer_iter == 0 && inner == 0;
209 eq_resid = check_eq ? backend.unscaled_eq_constr_viol(ctx,
Mxb) : 0;
210 stationarity = backend.unscaled_aug_lagr_norm(ctx,
grad,
Mᵀλ,
Aᵀŷ);
213 bool out_of_iter = inner >= remaining_iter;
214 bool out_of_time = clock_t::now() >= start_time + settings.
max_time;
215 bool inf_err = !isfinite(stationarity + eq_resid);
217 bool fail = out_of_iter || out_of_time || inf_err || stop;
218 bool inner_conv = stationarity <= inner_tol && eq_resid <= eq_tol;
220 if (detailed_stats) {
221 detailed_stats->entries.push_back({
222 .outer_iter = outer_iter,
224 .stationarity = stationarity,
225 .ineq_constr_viol = ineq_constr_resid,
226 .eq_constr_viol = eq_resid,
227 .linesearch_step_size = std::numeric_limits<real_t>::quiet_NaN(),
228 .linesearch_breakpoint_index = 0,
229 .num_active_constr = nJ,
230 .num_changing_constr = 0,
235 if (inner_conv || fail) {
236 increase_penalty_y &= !fail;
238 detailed_stats->entries.back().exit_reason =
241 if (settings.
verbose && ctx.is_master()) {
243 const char *status = inner_conv ?
"\x1b[0;32mConverged\x1b[0m"
244 :
"\x1b[0;31mFail\x1b[0m" ;
245 std::cout <<
" Exit inner: " << status <<
": #J = " << std::setw(6) << nJ
246 <<
", stationarity=" << float_to_str(stationarity, prec)
247 <<
", eq constr resid=" << float_to_str(eq_resid, prec) <<
"\n\n";
255 GUANAQO_TRACE(
"active_set_change", inner_total);
256 return backend.active_set_change(ctx, S, Σ, active_set, active_set_old);
260 detailed_stats->entries.back().num_changing_constr = active_set_change;
265 if (!active_set_change &&
268 increase_penalty_y =
false;
270 detailed_stats->entries.back().exit_reason =
272 if (settings.
verbose && ctx.is_master())
273 std::cout <<
" Exit inner: \x1b[0;32mNo active set "
278 force_τ_1_active_set =
true;
280 force_τ_1_active_set =
false;
285 bool upd_reg_iter = inner == 0 && outer_iter > 0;
287 if (!active_set_change)
289 S = backend.boost_regularization(ctx, S, settings.boost_penalty_x);
293 timed(timings.
solve, [&] {
294 GUANAQO_TRACE(
"solve", inner_total);
295 backend.solve(ctx, x, grad, Mᵀλ, Aᵀŷ, Mxb, S, Σ, active_set_old,
300 scal_d = 1 / sqrt(backend.norm_squared(ctx,
d));
301 backend.scale(ctx, scal_d,
d);
302 backend.scale(ctx, scal_d,
ξ);
303 backend.scale(ctx, scal_d,
Ad);
304 backend.scale(ctx, scal_d,
Δλ);
305 backend.scale(ctx, scal_d,
MᵀΔλ);
309 bool force_τ_1_dir_deriv =
false;
314 real_t dir_deriv = backend.dot(ctx,
d,
grad_add);
316 const char *color = dir_deriv < 0 ?
"\x1b[0;33m"
318 std::cout <<
"dir deriv: " << color << dir_deriv <<
"\x1b[0m" << std::endl;
321 force_τ_1_dir_deriv =
true;
325 bool force_τ_1_first_iter = inner_total == 0;
326 typename decltype(
linesearch)::Result ls{.τ = 1 / scal_d, .index = 999999999};
332 if (force_τ_1_active_set || force_τ_1_dir_deriv || force_τ_1_first_iter) {
333 if (settings.
verbose && !force_τ_1_first_iter && ctx.is_master())
334 std::cout <<
" \x1b[0;33mWarning\x1b[0m: Forcing line "
339 GUANAQO_TRACE(
"line_search", inner_total);
341 if (settings.linesearch_include_multipliers) {
346 auto [η, β, dMᵀΔλ, dMᵀλ] =
347 backend.dots(ctx, d, ξ, d, grad, d, MᵀΔλ, d, Mᵀλ);
348 if (settings.print_linesearch_inputs && ctx.is_master())
349 std::cout <<
" η = " << η <<
"\n"
350 <<
" <d, MᵀΔλ> = " << dMᵀΔλ <<
"\n"
351 <<
" β = " << β <<
"\n"
352 <<
" <d, Mᵀλ> = " << dMᵀλ <<
"\n";
353 return std::make_pair(η + dMᵀΔλ, β + dMᵀλ);
355 auto [η, β] = backend.dots(ctx, d, ξ, d, grad);
356 if (settings.print_linesearch_inputs && ctx.is_master())
357 std::cout <<
" η = " << η <<
"\n"
358 <<
" β = " << β <<
"\n";
359 return std::make_pair(η, β);
366 if (detailed_stats) {
367 detailed_stats->entries.back().linesearch_step_size = ls.τ;
368 detailed_stats->entries.back().linesearch_breakpoint_index = ls.index;
372 const real_t τ_min = 1
e-8 / scal_d, τ_max = 1e2 / scal_d;
373 if (settings.
verbose && ctx.is_master()) {
375 const auto eps = cbrt(std::numeric_limits<real_t>::epsilon());
376 const char *color = abs(1 - ls.τ) < eps ?
"\x1b[0;32m"
377 : ls.τ > τ_max ?
"\x1b[0;35m"
378 : ls.τ > τ_min ?
"\x1b[0;33m"
380 std::cout <<
" inner " << std::setw(4) << inner <<
" (" << std::setw(4)
381 << inner_total <<
"): #J = " << std::setw(6) << nJ
382 <<
", #ΔJ = " << std::setw(6) << active_set_change
383 <<
", stationarity=" << float_to_str(stationarity, prec)
384 <<
", eq constr resid=" << float_to_str(eq_resid, prec) <<
", τ=" << color
385 << float_to_str(ls.τ) <<
"\x1b[0m (" << ls.index <<
")\n";
387 ls.τ = std::clamp(ls.τ, τ_min, τ_max);
391 backend.xaxpy(ctx, ls.τ,
d,
x);
392 backend.xaxpy(ctx, ls.τ,
Δλ,
λ);
398 GUANAQO_TRACE(
"recompute_inner", inner_total);
399 backend.recompute_inner(ctx, S, x_outer, x, λ, grad, Ax, Mᵀλ);
403 backend.xaxpy(ctx, ls.τ,
Ad,
Ax);
404 backend.xaxpy(ctx, ls.τ,
MᵀΔλ,
Mᵀλ);
405 backend.xaxpy(ctx, ls.τ,
ξ,
grad);
411 backend.eq_constr_resid(ctx, x, Mxb);
414 backend.set_constant(ctx,
Mxb, real_t{});
420 ineq_constr_resid = backend.ineq_constr_resid_al(ctx,
y,
ŷ,
Σ,
e);
424 GUANAQO_TRACE(
"recompute_outer", outer_iter);
425 return backend.recompute_outer(ctx, x, Aᵀŷ, λ, grad, Ax, Mᵀλ);
431 auto nrm_Σ = backend.norm_inf(ctx,
Σ);
432 if (ctx.is_master()) {
434 std::cout <<
"outer " << std::setw(4) << outer_iter
435 <<
": stationarity=" << float_to_str(stationarity, prec)
436 <<
", constraints=" << float_to_str(ineq_constr_resid, prec)
437 <<
", penalty=" << float_to_str(nrm_Σ, prec)
438 <<
", regularization=" << float_to_str(1 / S, prec) <<
'\n'
444 bool converged = stationarity <= settings.
tolerance &&
449 bool out_of_time = clock_t::now() - start_time >= settings.
max_time;
450 bool inf_err = !std::isfinite(stationarity + ineq_constr_resid);
453 if (converged || out_of_iter || out_of_time || inf_err || stop) {
456 stats.
detail = std::move(detailed_stats);
460 stats.
timings = std::move(timings);
464 backend.project_multipliers_ineq(ctx,
y);
465 if (&this->
y != &
y.get())
466 backend.xcopy(ctx,
y.get(), this->y);
477 if (increase_penalty_y && ineq_constr_resid > settings.
dual_tolerance) {
479 if (num_Σ_changed > 0)
481 [&] { backend.update_penalty_changed(ctx, Σ, num_Σ_changed); });
487 backend.update_regularization_changed(ctx, S, S_old);
494 backend.project_multipliers_ineq(ctx,
y);
497 inner_tol = std::max(inner_tol * settings.
ρ, settings.
tolerance);