alpaqa 1.0.0a11
Nonconvex constrained optimization
Loading...
Searching...
No Matches
alm.tpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <algorithm>
6#include <iomanip>
7#include <iostream>
8#include <utility>
9
15
16namespace alpaqa {
17
18template <class InnerSolverT>
19typename ALMSolver<InnerSolverT>::Stats
21 std::optional<rvec> Σ) {
22 using std::chrono::duration_cast;
23 using std::chrono::nanoseconds;
24 auto start_time = std::chrono::steady_clock::now();
25
26 // Check the problem dimensions etc.
27 p.check();
28
29 if (params.max_iter == 0)
30 return {.status = SolverStatus::MaxIter};
31
32 auto m = p.get_m();
33 if (m == 0) { // No general constraints, only box constraints
34 Stats s;
35 vec Σ_curr(0), error(0);
38 .max_time = params.max_time,
39 .tolerance = params.tolerance,
40 .os = os,
41 .check = false,
42 };
43 auto ps = inner_solver(p, opts, x, y, Σ_curr, error);
44 bool inner_converged = ps.status == SolverStatus::Converged;
45 auto time_elapsed = std::chrono::steady_clock::now() - start_time;
46 s.inner_convergence_failures = not inner_converged;
47 s.inner += ps;
48 s.ε = ps.ε;
49 s.δ = 0;
50 s.norm_penalty = 0;
51 s.outer_iterations = 1;
52 s.elapsed_time = duration_cast<nanoseconds>(time_elapsed);
53 s.status = ps.status;
54 return s;
55 }
56
57 constexpr auto NaN = alpaqa::NaN<config_t>;
58 vec Σ_curr = vec::Constant(m, NaN);
59 vec Σ_old = vec::Constant(m, NaN);
60 vec error_1 = vec::Constant(m, NaN);
61 vec error_2 = vec::Constant(m, NaN);
62 [[maybe_unused]] real_t norm_e_1 = NaN;
63 [[maybe_unused]] real_t norm_e_2 = NaN;
64
65 std::array<char, 64> printbuf;
66 auto print_real = [&](real_t x) {
67 return float_to_str_vw(printbuf, x, params.print_precision);
68 };
69
70 Stats s;
71
72 if (Σ && Σ->allFinite() && Σ->norm() > 0) {
73 Σ_curr = *Σ;
74 }
75 // Initialize the penalty weights
76 else if (params.initial_penalty > 0) {
77 Σ_curr.fill(params.initial_penalty);
78 }
79 // Initial penalty weights from problem
80 else {
81 Helpers::initialize_penalty(p, params, x, Σ_curr);
82 }
83
84 real_t ε = params.initial_tolerance;
85 [[maybe_unused]] real_t ε_old = NaN;
86 real_t Δ = params.penalty_update_factor;
87 real_t ρ = params.tolerance_update_factor;
88
89 unsigned num_successful_iters = 0;
90
91 for (unsigned i = 0; i < params.max_iter; ++i) {
92 // TODO: this is unnecessary when the previous iteration lowered the
93 // penalty update factor.
94 p.eval_proj_multipliers(y, params.max_multiplier);
95 // Check if we're allowed to lower the penalty factor even further.
96 bool out_of_penalty_factor_updates =
97 (num_successful_iters == 0
98 ? s.initial_penalty_reduced == params.max_num_initial_retries
99 : s.penalty_reduced == params.max_num_retries) ||
101 params.max_total_num_retries);
102 bool out_of_iter = i + 1 == params.max_iter;
103 // If this is the final iteration, or the final chance to reduce the
104 // penalty update factor, the inner solver can just return its results,
105 // even if it doesn't converge.
106 bool overwrite_results = out_of_iter || out_of_penalty_factor_updates;
107
108 // Inner solver
109 // ------------
110
111 auto time_elapsed = std::chrono::steady_clock::now() - start_time;
112 auto time_remaining = time_elapsed < params.max_time
113 ? params.max_time - time_elapsed
114 : decltype(time_elapsed){0};
116 .always_overwrite_results = overwrite_results,
117 .max_time = time_remaining,
118 .tolerance = ε,
119 .os = os,
120 .outer_iter = i,
121 .check = false,
122 };
123 // Call the inner solver to minimize the augmented lagrangian for fixed
124 // Lagrange multipliers y.
125 auto ps = inner_solver(p, opts, x, y, Σ_curr, error_2);
126 // Check if the inner solver converged
127 bool inner_converged = ps.status == SolverStatus::Converged;
128 // Accumulate the inner solver statistics
129 s.inner_convergence_failures += not inner_converged;
130 s.inner += ps;
131
132 time_elapsed = std::chrono::steady_clock::now() - start_time;
133 bool out_of_time = time_elapsed > params.max_time;
134 bool backtrack =
135 not inner_converged && not overwrite_results && not out_of_time;
136
137 // Print statistics of current iteration
138 if (params.print_interval != 0 && i % params.print_interval == 0) {
139 real_t δ = backtrack ? NaN : vec_util::norm_inf(error_2);
140 const char *color = inner_converged ? "\x1b[0;32m" : "\x1b[0;31m";
141 const char *color_end = "\x1b[0m";
142 *os << "[\x1b[0;34mALM\x1b[0m] " << std::setw(5) << i
143 << ": ‖Σ‖ = " << print_real(Σ_curr.norm())
144 << ", ‖y‖ = " << print_real(y.norm())
145 << ", δ = " << print_real(δ) << ", ε = " << print_real(ps.ε)
146 << ", Δ = " << print_real(Δ) << ", status = " << color
147 << std::setw(13) << ps.status << color_end
148 << ", iter = " << std::setw(13) << ps.iterations
149 << std::endl; // Flush for Python buffering
150 }
151
152 // TODO: check penalty size?
153 if (ps.status == SolverStatus::Interrupted) {
154 s.ε = ps.ε;
155 s.δ = vec_util::norm_inf(error_2);
156 s.norm_penalty = Σ_curr.norm() / std::sqrt(real_t(m));
157 s.outer_iterations = i + 1;
158 s.elapsed_time = duration_cast<nanoseconds>(time_elapsed);
159 s.status = ps.status;
160 if (Σ)
161 *Σ = Σ_curr;
162 return s;
163 }
164
165 // Backtrack and lower penalty if inner solver did not converge
166 if (backtrack) {
167 // This means the inner solver didn't produce a solution that
168 // satisfies the required tolerance.
169 // The best thing we can do now is to restore the penalty to its
170 // previous value (when the inner solver did converge), then lower
171 // the penalty update factor Δ, and update the penalty with this
172 // smaller factor.
173 // On convergence failure, error_2 is not overwritten by the inner
174 // solver, so it still contains the error from the iteration before
175 // the previous successful iteration. error_1 contains the error of
176 // the last successful iteration. (Unless, of course, there hasn't
177 // been a successful iteration yet, which is covered by the second
178 // branch of the following if statement.)
179 if (num_successful_iters > 0) {
180 // We have a previous Σ and error
181 // Recompute penalty with smaller Δ
182 Δ = std::fmax(params.min_penalty_update_factor,
183 Δ * params.penalty_update_factor_lower);
184 Helpers::update_penalty_weights(params, Δ, false, error_1,
185 error_2, norm_e_1, norm_e_2,
186 Σ_old, Σ_curr, true);
187 // Recompute the primal tolerance with larger ρ
188 ρ = std::fmin(params.ρ_max, ρ * params.ρ_increase);
189 ε = std::fmax(ρ * ε_old, params.tolerance);
190 ++s.penalty_reduced;
191 } else {
192 // We don't have a previous Σ, simply lower the current Σ and
193 // increase ε
194 Σ_curr *= params.initial_penalty_lower;
195 ε *= params.initial_tolerance_increase;
197 }
198 }
199
200 // If the inner solver did converge, increase penalty
201 else {
202 // After this line, error_1 contains the error of the current
203 // (successful) iteration, and error_2 contains the error of the
204 // previous successful iteration.
205 error_2.swap(error_1);
206 norm_e_2 = std::exchange(norm_e_1, vec_util::norm_inf(error_1));
207
208 // Check the termination criteria
209 bool alm_converged = ps.ε <= params.tolerance && inner_converged &&
210 norm_e_1 <= params.dual_tolerance;
211 bool exit = alm_converged || out_of_iter || out_of_time;
212 if (exit) {
213 s.ε = ps.ε;
214 s.δ = norm_e_1;
215 s.norm_penalty = Σ_curr.norm() / std::sqrt(real_t(m));
216 s.outer_iterations = i + 1;
217 s.elapsed_time = duration_cast<nanoseconds>(time_elapsed);
218 s.status = alm_converged ? SolverStatus::Converged
219 : out_of_time ? SolverStatus::MaxTime
220 : out_of_iter ? SolverStatus::MaxIter
222 if (Σ)
223 *Σ = Σ_curr;
224 return s;
225 }
226 // After this line, Σ_old contains the penalty used in the current
227 // (successful) iteration.
228 Σ_old.swap(Σ_curr);
229 // Update Σ to contain the penalty to use on the next iteration.
230 Helpers::update_penalty_weights(
231 params, Δ, num_successful_iters == 0, error_1, error_2,
232 norm_e_1, norm_e_2, Σ_old, Σ_curr, true);
233 // Lower the primal tolerance for the inner solver.
234 ε_old = std::exchange(ε, std::fmax(ρ * ε, params.tolerance));
235 ++num_successful_iters;
236 }
237 }
238 throw std::logic_error("[ALM] loop error");
239}
240
241} // namespace alpaqa
unsigned penalty_reduced
The number of times that the penalty update factor ALMParams::penalty_update_factor was reduced,...
Definition: alm.hpp:127
real_t δ
Final dual tolerance or constraint violation that was reached:
Definition: alm.hpp:138
real_t norm_penalty
2-norm of the final penalty factors .
Definition: alm.hpp:140
typename InnerSolver::Problem Problem
Definition: alm.hpp:102
std::chrono::nanoseconds elapsed_time
Total elapsed time.
Definition: alm.hpp:109
unsigned initial_penalty_reduced
The number of times that the initial penalty factor was reduced by ALMParams::initial_penalty_lower a...
Definition: alm.hpp:117
InnerStatsAccumulator< typename InnerSolver::Stats > inner
The statistics of the inner solver invocations, accumulated over all ALM iterations.
Definition: alm.hpp:148
unsigned inner_convergence_failures
The total number of times that the inner solver failed to converge.
Definition: alm.hpp:129
real_t ε
Final primal tolerance that was reached, depends on the stopping criterion used by the inner solver,...
Definition: alm.hpp:133
unsigned outer_iterations
Total number of outer ALM iterations (i.e.
Definition: alm.hpp:107
Stats operator()(const Problem &problem, rvec x, rvec y, std::optional< rvec > Σ=std::nullopt)
Definition: alm.tpp:20
SolverStatus status
Whether the solver converged or not.
Definition: alm.hpp:144
auto norm_inf(const Eigen::MatrixBase< Derived > &v)
Get the maximum or infinity-norm of the given vector.
Definition: config.hpp:152
@ Interrupted
Solver was interrupted by the user.
@ MaxTime
Maximum allowed execution time exceeded.
@ MaxIter
Maximum number of iterations exceeded.
@ Busy
In progress.
@ Converged
Converged and reached given tolerance.
typename Conf::real_t real_t
Definition: config.hpp:63
bool always_overwrite_results
Return the final iterate and multipliers, even if the solver did not converge.
constexpr const auto NaN
Definition: config.hpp:83
typename Conf::rvec rvec
Definition: config.hpp:67
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::vec vec
Definition: config.hpp:64