PANOC-ALM  quadratic-penalty
Nonconvex constrained optimization
alm.hpp
Go to the documentation of this file.
1 #pragma once
2 
3 #include <chrono>
6 
7 #include <iomanip>
8 #include <iostream>
9 
10 namespace pa {
11 
12 using std::chrono::duration_cast;
13 using std::chrono::microseconds;
14 
15 template <class InnerSolverT>
18  auto start_time = std::chrono::steady_clock::now();
19 
20  constexpr auto sigNaN = std::numeric_limits<real_t>::signaling_NaN();
21  vec Σ = vec::Constant(problem.m, sigNaN);
22  vec Σ_old = vec::Constant(problem.m, sigNaN);
23  vec error₁ = vec::Constant(problem.m, sigNaN);
24  vec error₂ = vec::Constant(problem.m, sigNaN);
25  real_t norm_e₁ = sigNaN;
26  real_t norm_e₂ = sigNaN;
27 
28  Stats s;
29 
30  Problem prec_problem;
31  real_t prec_f;
32  vec prec_g;
33 
34  if (params.preconditioning)
35  detail::apply_preconditioning(problem, prec_problem, x, prec_f, prec_g);
36  const auto &p = params.preconditioning ? prec_problem : problem;
37 
38  // Initialize the penalty weights
39  if (params.Σ₀ > 0) {
40  Σ.fill(params.Σ₀);
41  }
42  // Initial penalty weights from problem
43  else {
45  }
46 
47  real_t ε = params.ε₀;
48  real_t ε_old = sigNaN;
49  real_t Δ = params.Δ;
50  real_t ρ = params.ρ;
51  bool first_successful_iter = true;
52 
53  for (unsigned int i = 0; i < params.max_iter; ++i) {
54  // TODO: this is unnecessary when the previous iteration lowered the
55  // penalty update factor.
56  detail::project_y(y, p.D.lowerbound, p.D.upperbound, params.M);
57  // Check if we're allowed to lower the penalty factor even further.
58  bool out_of_penalty_factor_updates =
59  (first_successful_iter
60  ? s.initial_penalty_reduced == params.max_num_initial_retries
61  : s.penalty_reduced == params.max_num_retries) ||
63  params.max_total_num_retries);
64  bool out_of_iter = i + 1 == params.max_iter;
65  // If this is the final iteration, or the final chance to reduce the
66  // penalty update factor, the inner solver can just return its results,
67  // even if it doesn't converge.
68  bool overwrite_results = out_of_iter || out_of_penalty_factor_updates;
69 
70  // Inner solver
71  // ------------
72 
73  // Call the inner solver to minimize the augmented lagrangian for fixed
74  // Lagrange multipliers y.
75  auto ps = inner_solver(p, Σ, ε, overwrite_results, x, y, error₂);
76  bool inner_converged = ps.status == SolverStatus::Converged;
77  // Accumulate the inner solver statistics
78  s.inner_convergence_failures += not inner_converged;
79  s.inner += ps;
80 
81  auto time_elapsed = std::chrono::steady_clock::now() - start_time;
82  bool out_of_time = time_elapsed > params.max_time;
83  bool backtrack =
84  not inner_converged && not overwrite_results && not out_of_time;
85 
86  // Print statistics of current iteration
87  if (params.print_interval != 0 && i % params.print_interval == 0) {
88  real_t δ = backtrack ? NaN : vec_util::norm_inf(error₂);
89  auto color = inner_converged ? "\x1b[0;32m" : "\x1b[0;31m";
90  auto color_end = "\x1b[0m";
91  std::cout << "[\x1b[0;34mALM\x1b[0m] " << std::setw(5) << i
92  << ": ‖Σ‖ = " << std::setw(13) << Σ.norm()
93  << ", ‖y‖ = " << std::setw(13) << y.norm()
94  << ", δ = " << std::setw(13) << δ
95  << ", ε = " << std::setw(13) << ps.ε
96  << ", Δ = " << std::setw(13) << Δ
97  << ", status = " << color << std::setw(13) << ps.status
98  << color_end << ", iter = " << std::setw(13)
99  << ps.iterations << "\r\n";
100  }
101 
102  // TODO: check penalty size?
103  if (ps.status == SolverStatus::Interrupted) {
104  s.ε = ps.ε;
105  s.δ = vec_util::norm_inf(error₂);
106  s.norm_penalty = Σ.norm();
107  s.outer_iterations = i + 1;
108  s.elapsed_time = duration_cast<microseconds>(time_elapsed);
109  s.status = ps.status;
110  if (params.preconditioning)
111  y = prec_g.asDiagonal() * y / prec_f;
112  return s;
113  }
114 
115  // Backtrack and lower penalty if inner solver did not converge
116  if (backtrack) {
117  // This means the inner solver didn't produce a solution that
118  // satisfies the required tolerance.
119  // The best thing we can do now is to restore the penalty to its
120  // previous value (when the inner solver did converge), then lower
121  // the penalty factor, and update the penalty with this smaller
122  // factor.
123  // error₂ was not overwritten by the inner solver, so it still
124  // contains the error from the iteration before the previous
125  // successful iteration. error₁ contains the error of the last
126  // successful iteration.
127  if (not first_successful_iter) {
128  // We have a previous Σ and error
129  // Recompute penalty with smaller Δ
130  Δ = std::fmax(1., Δ * params.Δ_lower);
131  detail::update_penalty_weights(params, Δ, first_successful_iter,
132  error₁, error₂, norm_e₁, norm_e₂,
133  Σ_old, Σ);
134  // Recompute the primal tolerance with larger ρ
135  ρ = std::fmin(0.5, ρ * params.ρ_increase); // keep ρ <= 0.5
136  ε = std::fmax(ρ * ε_old, params.ε);
137  ++s.penalty_reduced;
138  } else {
139  // We don't have a previous Σ, simply lower the current Σ and
140  // increase ε
141  Σ *= params.Σ₀_lower;
142  ε *= params.ε₀_increase;
144  }
145  }
146 
147  // If the inner solver did converge, increase penalty
148  else {
149  // After this line, error₁ contains the error of the current
150  // (successful) iteration, and error₂ contains the error of the
151  // previous successful iteration.
152  error₂.swap(error₁);
153  norm_e₂ = std::exchange(norm_e₁, vec_util::norm_inf(error₁));
154 
155  // Check the termination criteria
156  bool alm_converged =
157  ps.ε <= params.ε && inner_converged && norm_e₁ <= params.δ;
158  bool exit = alm_converged || out_of_iter || out_of_time;
159  if (exit) {
160  s.ε = ps.ε;
161  s.δ = norm_e₁;
162  s.norm_penalty = Σ.norm();
163  s.outer_iterations = i + 1;
164  s.elapsed_time = duration_cast<microseconds>(time_elapsed);
165  s.status = alm_converged ? SolverStatus::Converged
166  : out_of_time ? SolverStatus::MaxTime
167  : out_of_iter ? SolverStatus::MaxIter
168  : SolverStatus::Unknown;
169  if (params.preconditioning)
170  y = prec_g.asDiagonal() * y / prec_f;
171  return s;
172  }
173  // After this line, Σ_old contains the penalty used in the current
174  // (successful) iteration.
175  Σ_old.swap(Σ);
176  // Update Σ to contain the penalty to use on the next iteration.
177  detail::update_penalty_weights(params, Δ, first_successful_iter,
178  error₁, error₂, norm_e₁, norm_e₂,
179  Σ_old, Σ);
180  // Lower the primal tolerance for the inner solver.
181  ε_old = std::exchange(ε, std::fmax(ρ * ε, params.ε));
182  first_successful_iter = false;
183  }
184  }
185  throw std::logic_error("[ALM] loop error");
186 }
187 
188 template <class InnerSolverT>
191  auto start_time = std::chrono::steady_clock::now();
192 
193  constexpr auto sigNaN = std::numeric_limits<real_t>::signaling_NaN();
194  vec Σ1 = vec::Constant(problem.m1, sigNaN);
195  vec Σ1_old = vec::Constant(problem.m1, sigNaN);
196  vec Σ2 = vec::Constant(problem.m2, sigNaN);
197  vec Σ2_old = vec::Constant(problem.m2, sigNaN);
198  vec error1₁ = vec::Constant(problem.m1, sigNaN);
199  vec error1₂ = vec::Constant(problem.m1, sigNaN);
200  vec error2₁ = vec::Constant(problem.m2, sigNaN);
201  vec error2₂ = vec::Constant(problem.m2, sigNaN);
202  real_t norm_e1₁ = sigNaN;
203  real_t norm_e1₂ = sigNaN;
204  real_t norm_e2₁ = sigNaN;
205  real_t norm_e2₂ = sigNaN;
206 
207  Stats s(problem.m2);
208 
209  // TODO: preconditioning
210  // ProblemFull prec_problem;
211  // real_t prec_f;
212  // vec prec_g;
213 
214  // if (params.preconditioning)
215  // detail::apply_preconditioning(problem, prec_problem, x, prec_f, prec_g);
216  // const auto &p = params.preconditioning ? prec_problem : problem;
217  const auto &p = problem;
218 
219  // Initialize the penalty weights
220  if (params.Σ₀ > 0) {
221  Σ1.fill(params.Σ₀);
222  Σ2.fill(params.Σ₀);
223  }
224  // Initial penalty weights from problem
225  else {
226  detail::initialize_penalty(p, params, x, Σ1, Σ2);
227  }
228 
229  real_t ε = params.ε₀;
230  real_t ε_old = sigNaN;
231  real_t Δ = params.Δ;
232  real_t ρ = params.ρ;
233  bool first_successful_iter = true;
234 
235  for (unsigned int i = 0; i < params.max_iter; ++i) {
236  // TODO: this is unnecessary when the previous iteration lowered the
237  // penalty update factor.
238  detail::project_y(y, p.D1.lowerbound, p.D1.upperbound, params.M);
239  // Check if we're allowed to lower the penalty factor even further.
240  bool out_of_penalty_factor_updates =
241  (first_successful_iter
242  ? s.initial_penalty_reduced == params.max_num_initial_retries
243  : s.penalty_reduced == params.max_num_retries) ||
245  params.max_total_num_retries);
246  bool out_of_iter = i + 1 == params.max_iter;
247  // If this is the final iteration, or the final chance to reduce the
248  // penalty update factor, the inner solver can just return its results,
249  // even if it doesn't converge.
250  bool overwrite_results = out_of_iter || out_of_penalty_factor_updates;
251 
252  // Inner solver
253  // ------------
254 
255  // Call the inner solver to minimize the augmented lagrangian for fixed
256  // Lagrange multipliers y.
257  auto time_elapsed = std::chrono::steady_clock::now() - start_time;
258  auto ps = inner_solver(p, Σ1, Σ2, ε, overwrite_results, x, y, error1₂, error2₂, std::chrono::duration_cast<std::chrono::microseconds>(params.max_time - time_elapsed));
259  bool inner_converged = ps.status == SolverStatus::Converged;
260  // Accumulate the inner solver statistics
261  s.inner_convergence_failures += not inner_converged;
262  s.inner += ps;
263 
264  time_elapsed = std::chrono::steady_clock::now() - start_time;
265  bool out_of_time = time_elapsed > params.max_time;
266  bool backtrack =
267  not inner_converged && not overwrite_results && not out_of_time;
268 
269  // Print statistics of current iteration
270  if (params.print_interval != 0 && i % params.print_interval == 0) {
271  real_t δ = backtrack ? NaN : vec_util::norm_inf(error1₂);
272  auto color = inner_converged ? "\x1b[0;32m" : "\x1b[0;31m";
273  auto color_end = "\x1b[0m";
274  std::cout << "[\x1b[0;34mALM\x1b[0m] " << std::setw(5) << i
275  << ": ‖Σ1‖ = " << std::setw(13) << Σ1.norm()
276  << ": ‖Σ2‖ = " << std::setw(13) << Σ2.norm()
277  << ", ‖y‖ = " << std::setw(13) << y.norm()
278  << ", δ = " << std::setw(13) << δ
279  << ", ε = " << std::setw(13) << ps.ε
280  << ", Δ = " << std::setw(13) << Δ
281  << ", status = " << color << std::setw(13) << ps.status
282  << color_end << ", iter = " << std::setw(13)
283  << ps.iterations << "\r\n";
284  }
285 
286  // TODO: check penalty size?
287  if (ps.status == SolverStatus::Interrupted) {
288  s.ε = ps.ε;
289  s.δ₁ = vec_util::norm_inf(error1₂);
290  s.δ₂ = vec_util::norm_inf(error2₂);
291  s.norm_penalty₁ = Σ1.norm();
292  s.norm_penalty₂ = Σ2.norm();
293  s.penalty₂ = Σ2;
294  s.outer_iterations = i + 1;
295  s.elapsed_time = duration_cast<microseconds>(time_elapsed);
296  s.status = ps.status;
297  // if (params.preconditioning)
298  // y = prec_g.asDiagonal() * y / prec_f;
299  return s;
300  }
301 
302  // Backtrack and lower penalty if inner solver did not converge
303  if (backtrack) {
304  // This means the inner solver didn't produce a solution that
305  // satisfies the required tolerance.
306  // The best thing we can do now is to restore the penalty to its
307  // previous value (when the inner solver did converge), then lower
308  // the penalty factor, and update the penalty with this smaller
309  // factor.
310  // error₂ was not overwritten by the inner solver, so it still
311  // contains the error from the iteration before the previous
312  // successful iteration. error₁ contains the error of the last
313  // successful iteration.
314  if (not first_successful_iter) {
315  // We have a previous Σ and error
316  // Recompute penalty with smaller Δ
317  Δ = std::fmax(1., Δ * params.Δ_lower);
318  detail::update_penalty_weights(params, Δ, first_successful_iter,
319  error1₁, error1₂, norm_e1₁, norm_e1₂,
320  Σ1_old, Σ1);
321  detail::update_penalty_weights(params, Δ, first_successful_iter,
322  error2₁, error2₂, norm_e2₁, norm_e2₂,
323  Σ2_old, Σ2);
324  // Recompute the primal tolerance with larger ρ
325  ρ = std::fmin(0.5, ρ * params.ρ_increase); // keep ρ <= 0.5
326  ε = std::fmax(ρ * ε_old, params.ε);
327  ++s.penalty_reduced;
328  } else {
329  // We don't have a previous Σ, simply lower the current Σ and
330  // increase ε
331  Σ1 *= params.Σ₀_lower;
332  Σ2 *= params.Σ₀_lower;
333  ε *= params.ε₀_increase;
335  }
336  }
337 
338  // If the inner solver did converge, increase penalty
339  else {
340  // After this line, error₁ contains the error of the current
341  // (successful) iteration, and error₂ contains the error of the
342  // previous successful iteration.
343  error1₂.swap(error1₁);
344  norm_e1₂ = std::exchange(norm_e1₁, vec_util::norm_inf(error1₁));
345  error2₂.swap(error2₁);
346  norm_e2₂ = std::exchange(norm_e2₁, vec_util::norm_inf(error2₁));
347 
348  // Check the termination criteria
349  bool alm_converged = ps.ε <= params.ε && inner_converged &&
350  norm_e1₁ <= params.δ && norm_e2₁ <= params.δ;
351  bool exit = alm_converged || out_of_iter || out_of_time;
352  if (exit) {
353  s.ε = ps.ε;
354  s.δ₁ = norm_e1₁;
355  s.δ₂ = norm_e2₁;
356  s.norm_penalty₁ = Σ1.norm();
357  s.norm_penalty₂ = Σ2.norm();
358  s.penalty₂ = Σ2;
359  s.outer_iterations = i + 1;
360  s.elapsed_time = duration_cast<microseconds>(time_elapsed);
361  s.status = alm_converged ? SolverStatus::Converged
362  : out_of_time ? SolverStatus::MaxTime
363  : out_of_iter ? SolverStatus::MaxIter
364  : SolverStatus::Unknown;
365  // if (params.preconditioning)
366  // y = prec_g.asDiagonal() * y / prec_f;
367  return s;
368  }
369  // After this line, Σ_old contains the penalty used in the current
370  // (successful) iteration.
371  Σ1_old.swap(Σ1);
372  Σ2_old.swap(Σ2);
373  // Update Σ to contain the penalty to use on the next iteration.
374  detail::update_penalty_weights(params, Δ, first_successful_iter,
375  error1₁, error1₂, norm_e1₁, norm_e1₂,
376  Σ1_old, Σ1);
377  detail::update_penalty_weights(params, Δ, first_successful_iter,
378  error2₁, error2₂, norm_e2₁, norm_e2₂,
379  Σ2_old, Σ2);
380  // Lower the primal tolerance for the inner solver.
381  ε_old = std::exchange(ε, std::fmax(ρ * ε, params.ε));
382  first_successful_iter = false;
383  }
384  }
385  throw std::logic_error("[ALM] loop error");
386 }
387 
388 } // namespace pa
pa::ALMSolverFull::Stats::initial_penalty_reduced
unsigned initial_penalty_reduced
Definition: decl/alm.hpp:136
pa::ALMSolverFull::Stats::norm_penalty₁
real_t norm_penalty₁
Definition: decl/alm.hpp:142
pa::ALMSolver::Stats
Definition: decl/alm.hpp:87
pa::SolverStatus::Unknown
@ Unknown
Initial value.
pa::rvec
Eigen::Ref< vec > rvec
Default type for mutable references to vectors.
Definition: vec.hpp:16
pa::NaN
constexpr real_t NaN
Not a number.
Definition: vec.hpp:28
panocpy.test.y
y
Definition: test.py:76
alm-helpers.hpp
pa::detail::initialize_penalty
void initialize_penalty(const Problem &p, const ALMParams &params, crvec x0, rvec Σ)
Definition: alm-helpers.hpp:53
panocpy.test.ε
int ε
Definition: test.py:71
pa::ALMSolverFull::Stats::inner_convergence_failures
unsigned inner_convergence_failures
Definition: decl/alm.hpp:138
pa::ALMSolverFull::Stats::δ₁
real_t δ₁
Definition: decl/alm.hpp:140
pa::ALMSolver::Stats::δ
real_t δ
Definition: decl/alm.hpp:94
pa::vec
realvec vec
Default type for vectors.
Definition: vec.hpp:14
pa::ALMSolver::Stats::norm_penalty
real_t norm_penalty
Definition: decl/alm.hpp:95
pa::ALMSolver::Stats::initial_penalty_reduced
unsigned initial_penalty_reduced
Definition: decl/alm.hpp:90
pa::ALMSolverFull::Stats::ε
real_t ε
Definition: decl/alm.hpp:139
main.problem
problem
Definition: main.py:16
rosenbrock.color
color
Definition: rosenbrock.py:83
pa::ALMSolver::Stats::inner
InnerStatsAccumulator< typename InnerSolver::Stats > inner
Definition: decl/alm.hpp:99
pa::ALMSolver::Stats::ε
real_t ε
Definition: decl/alm.hpp:93
pa::ALMSolverFull::operator()
Stats operator()(const ProblemFull &problem, rvec y, rvec x)
Definition: alm.hpp:190
pa
Definition: alm.hpp:10
panocpy.test.x
x
Definition: test.py:40
pa::ALMSolverFull::Stats::elapsed_time
std::chrono::microseconds elapsed_time
Definition: decl/alm.hpp:135
pa::ALMSolver::Stats::status
SolverStatus status
Definition: decl/alm.hpp:97
pa::ALMSolverFull::Stats::norm_penalty₂
real_t norm_penalty₂
Definition: decl/alm.hpp:143
panocpy.test.params
params
Definition: test.py:217
pa::ALMSolver::Stats::inner_convergence_failures
unsigned inner_convergence_failures
Definition: decl/alm.hpp:92
pa::ALMSolver::operator()
Stats operator()(const Problem &problem, rvec y, rvec x)
Definition: alm.hpp:17
pa::ALMSolverFull::Stats::outer_iterations
unsigned outer_iterations
Definition: decl/alm.hpp:134
pa::detail::project_y
void project_y(rvec y, crvec z_lb, crvec z_ub, real_t M)
Definition: alm-helpers.hpp:8
panocpy.test.Σ
int Σ
Definition: test.py:70
pa::ALMSolverFull::Stats
Definition: decl/alm.hpp:133
pa::vec_util::norm_inf
real_t norm_inf(const Vec &v)
Get the maximum or infinity-norm of the given vector.
Definition: vec.hpp:42
pa::ALMSolver::Stats::elapsed_time
std::chrono::microseconds elapsed_time
Definition: decl/alm.hpp:89
pa::ALMSolverFull::Stats::δ₂
real_t δ₂
Definition: decl/alm.hpp:141
panocpy.test.p
p
Definition: test.py:57
pa::ALMSolverFull::Stats::status
SolverStatus status
Definition: decl/alm.hpp:146
pa::ProblemFull
Problem description for minimization problems.
Definition: include/panoc-alm/util/problem.hpp:213
pa::ALMSolver::Stats::penalty_reduced
unsigned penalty_reduced
Definition: decl/alm.hpp:91
pa::ALMSolverFull::Stats::inner
InnerStatsAccumulator< typename InnerSolver::Stats > inner
Definition: decl/alm.hpp:148
pa::ALMSolverFull::Stats::penalty_reduced
unsigned penalty_reduced
Definition: decl/alm.hpp:137
pa::real_t
double real_t
Default floating point type.
Definition: vec.hpp:8
solverstatus.hpp
pa::ALMSolverFull::Stats::penalty₂
vec penalty₂
Definition: decl/alm.hpp:144
pa::Problem
Problem description for minimization problems.
Definition: include/panoc-alm/util/problem.hpp:24
pa::detail::update_penalty_weights
void update_penalty_weights(const ALMParams &params, real_t Δ, bool first_iter, rvec e, rvec old_e, real_t norm_e, real_t old_norm_e, crvec Σ_old, rvec Σ)
Definition: alm-helpers.hpp:25
pa::detail::apply_preconditioning
void apply_preconditioning(const Problem &problem, Problem &prec_problem, crvec x, real_t &prec_f, vec &prec_g)
Definition: alm-helpers.hpp:92
pa::ALMSolver::Stats::outer_iterations
unsigned outer_iterations
Definition: decl/alm.hpp:88