PANOC-ALM  quadratic-penalty
Nonconvex constrained optimization
inner/panoc.hpp
Go to the documentation of this file.
1 #pragma once
2 
3 #include <chrono>
7 
8 #include <cassert>
9 #include <cmath>
10 #include <iomanip>
11 #include <iostream>
12 #include <stdexcept>
13 
14 namespace pa {
15 
16 using std::chrono::duration_cast;
17 using std::chrono::microseconds;
18 
19 template <class DirectionProviderT>
21  return "PANOCSolver<" + direction_provider.get_name() + ">";
22 }
23 
24 template <class DirectionProviderT>
28  const Problem &problem,
30  crvec Σ,
32  real_t ε,
34  bool always_overwrite_results,
36  rvec x,
38  rvec y,
40  rvec err_z,
42  std::chrono::microseconds time_remaining) {
43 
44  std::chrono::microseconds delta_time = std::chrono::microseconds(0);
45  if (time_remaining > std::chrono::microseconds(0) && params.max_time > time_remaining) {
46  delta_time = params.max_time - time_remaining;
47  }
48 
49  auto start_time = std::chrono::steady_clock::now();
50  Stats s;
51 
52  const auto n = problem.n;
53  const auto m = problem.m;
54 
55  // Allocate vectors, init L-BFGS -------------------------------------------
56 
57  // TODO: the L-BFGS objects and vectors allocate on each iteration of ALM,
58  // and there are more vectors than strictly necessary.
59 
60  vec xₖ = x, // Value of x at the beginning of the iteration
61  x̂ₖ(n), // Value of x after a projected gradient step
62  xₖ₊₁(n), // xₖ for next iteration
63  x̂ₖ₊₁(n), // x̂ₖ for next iteration
64  ŷx̂ₖ(m), // ŷ(x̂ₖ) = Σ (g(x̂ₖ) - ẑₖ)
65  ŷx̂ₖ₊₁(m), // ŷ(x̂ₖ) for next iteration
66  pₖ(n), // Projected gradient step pₖ = x̂ₖ - xₖ
67  pₖ₊₁(n), // Projected gradient step pₖ₊₁ = x̂ₖ₊₁ - xₖ₊₁
68  qₖ(n), // Newton step Hₖ pₖ
69  grad_ψₖ(n), // ∇ψ(xₖ)
70  grad_̂ψₖ(n), // ∇ψ(x̂ₖ)
71  grad_ψₖ₊₁(n); // ∇ψ(xₖ₊₁)
72 
73  vec work_n(n), work_m(m);
74 
75  // Keep track of how many successive iterations didn't update the iterate
76  unsigned no_progress = 0;
77 
78  // Helper functions --------------------------------------------------------
79 
80  // Wrappers for helper functions that automatically pass along any arguments
81  // that are constant within PANOC (for readability in the main algorithm)
82  auto calc_ψ_ŷ = [&problem, &y, &Σ](crvec x, rvec ŷ) {
83  return detail::calc_ψ_ŷ(problem, x, y, Σ, ŷ);
84  };
85  auto calc_ψ_grad_ψ = [&problem, &y, &Σ, &work_n, &work_m](crvec x,
86  rvec grad_ψ) {
87  return detail::calc_ψ_grad_ψ(problem, x, y, Σ, grad_ψ, work_n, work_m);
88  };
89  auto calc_grad_ψ_from_ŷ = [&problem, &work_n](crvec x, crvec ŷ,
90  rvec grad_ψ) {
91  detail::calc_grad_ψ_from_ŷ(problem, x, ŷ, grad_ψ, work_n);
92  };
93  auto calc_x̂ = [&problem](real_t γ, crvec x, crvec grad_ψ, rvec x̂, rvec p) {
94  detail::calc_x̂(problem, γ, x, grad_ψ, x̂, p);
95  };
96  auto calc_err_z = [&problem, &y, &Σ](crvec x̂, rvec err_z) {
98  };
99  auto descent_lemma = [this, &problem, &y,
100  &Σ](crvec xₖ, real_t ψₖ, crvec grad_ψₖ, rvec x̂ₖ,
101  rvec pₖ, rvec ŷx̂ₖ, real_t &ψx̂ₖ, real_t &pₖᵀpₖ,
102  real_t &grad_ψₖᵀpₖ, real_t &Lₖ, real_t &γₖ) {
103  return detail::descent_lemma(
104  problem, params.quadratic_upperbound_tolerance_factor, params.L_max,
105  xₖ, ψₖ, grad_ψₖ, y, Σ, x̂ₖ, pₖ, ŷx̂ₖ, ψx̂ₖ, pₖᵀpₖ, grad_ψₖᵀpₖ, Lₖ, γₖ);
106  };
107  auto print_progress = [&](unsigned k, real_t ψₖ, crvec grad_ψₖ,
108  real_t pₖᵀpₖ, real_t γₖ, real_t εₖ) {
109  std::cout << "[PANOC] " << std::setw(6) << k
110  << ": ψ = " << std::setw(13) << ψₖ
111  << ", ‖∇ψ‖ = " << std::setw(13) << grad_ψₖ.norm()
112  << ", ‖p‖ = " << std::setw(13) << std::sqrt(pₖᵀpₖ)
113  << ", γ = " << std::setw(13) << γₖ
114  << ", εₖ = " << std::setw(13) << εₖ << "\r\n";
115  };
116 
117  // Estimate Lipschitz constant ---------------------------------------------
118 
119  real_t ψₖ, Lₖ;
120  // Finite difference approximation of ∇²ψ in starting point
121  if (params.Lipschitz.L₀ <= 0) {
123  problem, xₖ, y, Σ, params.Lipschitz.ε, params.Lipschitz.δ,
124  params.L_min, params.L_max,
125  /* in ⟹ out */ ψₖ, grad_ψₖ, x̂ₖ, grad_̂ψₖ, work_n, work_m);
126  }
127  // Initial Lipschitz constant provided by the user
128  else {
129  Lₖ = params.Lipschitz.L₀;
130  // Calculate ψ(xₖ), ∇ψ(x₀)
131  ψₖ = calc_ψ_grad_ψ(xₖ, /* in ⟹ out */ grad_ψₖ);
132  }
133  if (not std::isfinite(Lₖ)) {
134  s.status = SolverStatus::NotFinite;
135  return s;
136  }
137  real_t γₖ = params.Lipschitz.Lγ_factor / Lₖ;
138  real_t τ = NaN;
139 
140  // First projected gradient step -------------------------------------------
141 
142  // Calculate x̂₀, p₀ (projected gradient step)
143  calc_x̂(γₖ, xₖ, grad_ψₖ, /* in ⟹ out */ x̂ₖ, pₖ);
144  // Calculate ψ(x̂ₖ) and ŷ(x̂ₖ)
145  real_t ψx̂ₖ = calc_ψ_ŷ(x̂ₖ, /* in ⟹ out */ ŷx̂ₖ);
146  real_t grad_ψₖᵀpₖ = grad_ψₖ.dot(pₖ);
147  real_t pₖᵀpₖ = pₖ.squaredNorm();
148  // Compute forward-backward envelope
149  real_t φₖ = ψₖ + 1 / (2 * γₖ) * pₖᵀpₖ + grad_ψₖᵀpₖ;
150 
151  // Main PANOC loop
152  // =========================================================================
153  for (unsigned k = 0; k <= params.max_iter; ++k) {
154 
155  // Quadratic upper bound -----------------------------------------------
156  if (k == 0 || params.update_lipschitz_in_linesearch == false) {
157  // Decrease step size until quadratic upper bound is satisfied
158  real_t old_γₖ =
159  descent_lemma(xₖ, ψₖ, grad_ψₖ,
160  /* in ⟹ out */ x̂ₖ, pₖ, ŷx̂ₖ,
161  /* inout */ ψx̂ₖ, pₖᵀpₖ, grad_ψₖᵀpₖ, Lₖ, γₖ);
162  if (k > 0 && γₖ != old_γₖ) // Flush L-BFGS if γ changed
163  direction_provider.changed_γ(γₖ, old_γₖ);
164  else if (k == 0) // Initialize L-BFGS
165  direction_provider.initialize(xₖ, x̂ₖ, pₖ, grad_ψₖ);
166  if (γₖ != old_γₖ)
167  φₖ = ψₖ + 1 / (2 * γₖ) * pₖᵀpₖ + grad_ψₖᵀpₖ;
168  }
169  // Calculate ∇ψ(x̂ₖ)
170  calc_grad_ψ_from_ŷ(x̂ₖ, ŷx̂ₖ, /* in ⟹ out */ grad_̂ψₖ);
171 
172  // Check stop condition ------------------------------------------------
173  real_t εₖ = detail::calc_error_stop_crit(params.stop_crit, pₖ, γₖ, xₖ,
174  grad_̂ψₖ, grad_ψₖ, problem.C);
175 
176  // Print progress
177  if (params.print_interval != 0 && k % params.print_interval == 0)
178  print_progress(k, ψₖ, grad_ψₖ, pₖᵀpₖ, γₖ, εₖ);
179  if (progress_cb)
180  progress_cb({k, xₖ, pₖ, pₖᵀpₖ, x̂ₖ, φₖ, ψₖ, grad_ψₖ, ψx̂ₖ, grad_̂ψₖ,
181  Lₖ, γₖ, τ, εₖ, Σ, y, problem, params});
182 
183  auto time_elapsed = std::chrono::steady_clock::now() - start_time;
184  auto stop_status = detail::check_all_stop_conditions(
185  params, time_elapsed + delta_time, k, stop_signal, ε, εₖ, no_progress);
186  if (stop_status != SolverStatus::Unknown) {
187  // TODO: We could cache g(x) and ẑ, but would that faster?
188  // It saves 1 evaluation of g per ALM iteration, but requires
189  // many extra stores in the inner loops of PANOC.
190  // TODO: move the computation of ẑ and g(x) to ALM?
191  if (stop_status == SolverStatus::Converged ||
192  stop_status == SolverStatus::Interrupted ||
193  stop_status == SolverStatus::MaxTime ||
194  always_overwrite_results) {
195  calc_err_z(x̂ₖ, /* in ⟹ out */ err_z);
196  x = std::move(x̂ₖ);
197  y = std::move(ŷx̂ₖ);
198  }
199  s.iterations = k;
200  s.ε = εₖ;
201  s.elapsed_time = duration_cast<microseconds>(time_elapsed);
202  s.status = stop_status;
203  return s;
204  }
205 
206  // Calculate quasi-Newton step -----------------------------------------
207  real_t step_size =
208  params.lbfgs_stepsize == LBFGSStepSize::BasedOnGradientStepSize
209  ? 1
210  : -1;
211  if (k > 0)
212  direction_provider.apply(xₖ, x̂ₖ, pₖ, step_size,
213  /* in ⟹ out */ qₖ);
214 
215  // Line search initialization ------------------------------------------
216  τ = 1;
217  real_t σₖγₖ⁻¹pₖᵀpₖ = (1 - γₖ * Lₖ) * pₖᵀpₖ / (2 * γₖ);
218  real_t φₖ₊₁, ψₖ₊₁, ψx̂ₖ₊₁, grad_ψₖ₊₁ᵀpₖ₊₁, pₖ₊₁ᵀpₖ₊₁;
219  real_t Lₖ₊₁, γₖ₊₁;
220  real_t ls_cond;
221  // TODO: make separate parameter
222  real_t margin =
223  (1 + std::abs(φₖ)) * params.quadratic_upperbound_tolerance_factor;
224 
225  // Make sure quasi-Newton step is valid
226  if (k == 0) {
227  τ = 0; // Always use prox step on first iteration
228  } else if (not qₖ.allFinite()) {
229  τ = 0;
230  ++s.lbfgs_failures;
231  direction_provider.reset(); // Is there anything else we can do?
232  }
233 
234  // Line search loop ----------------------------------------------------
235  do {
236  Lₖ₊₁ = Lₖ;
237  γₖ₊₁ = γₖ;
238 
239  // Calculate xₖ₊₁
240  if (τ / 2 < params.τ_min) { // line search failed
241  xₖ₊₁.swap(x̂ₖ); // → safe prox step
242  ψₖ₊₁ = ψx̂ₖ;
243  grad_ψₖ₊₁.swap(grad_̂ψₖ);
244  } else { // line search didn't fail (yet)
245  if (τ == 1) // → faster quasi-Newton step
246  xₖ₊₁ = xₖ + qₖ;
247  else
248  xₖ₊₁ = xₖ + (1 - τ) * pₖ + τ * qₖ;
249  // Calculate ψ(xₖ₊₁), ∇ψ(xₖ₊₁)
250  ψₖ₊₁ = calc_ψ_grad_ψ(xₖ₊₁, /* in ⟹ out */ grad_ψₖ₊₁);
251  }
252 
253  // Calculate x̂ₖ₊₁, pₖ₊₁ (projected gradient step in xₖ₊₁)
254  calc_x̂(γₖ₊₁, xₖ₊₁, grad_ψₖ₊₁, /* in ⟹ out */ x̂ₖ₊₁, pₖ₊₁);
255  // Calculate ψ(x̂ₖ₊₁) and ŷ(x̂ₖ₊₁)
256  ψx̂ₖ₊₁ = calc_ψ_ŷ(x̂ₖ₊₁, /* in ⟹ out */ ŷx̂ₖ₊₁);
257 
258  // Quadratic upper bound -------------------------------------------
259  grad_ψₖ₊₁ᵀpₖ₊₁ = grad_ψₖ₊₁.dot(pₖ₊₁);
260  pₖ₊₁ᵀpₖ₊₁ = pₖ₊₁.squaredNorm();
261  real_t pₖ₊₁ᵀpₖ₊₁_ₖ = pₖ₊₁ᵀpₖ₊₁; // prox step with step size γₖ
262 
263  if (params.update_lipschitz_in_linesearch == true) {
264  // Decrease step size until quadratic upper bound is satisfied
265  (void)descent_lemma(xₖ₊₁, ψₖ₊₁, grad_ψₖ₊₁,
266  /* in ⟹ out */ x̂ₖ₊₁, pₖ₊₁, ŷx̂ₖ₊₁,
267  /* inout */ ψx̂ₖ₊₁, pₖ₊₁ᵀpₖ₊₁,
268  grad_ψₖ₊₁ᵀpₖ₊₁, Lₖ₊₁, γₖ₊₁);
269  }
270 
271  // Compute forward-backward envelope
272  φₖ₊₁ = ψₖ₊₁ + 1 / (2 * γₖ₊₁) * pₖ₊₁ᵀpₖ₊₁ + grad_ψₖ₊₁ᵀpₖ₊₁;
273  // Compute line search condition
274  ls_cond = φₖ₊₁ - (φₖ - σₖγₖ⁻¹pₖᵀpₖ);
275  if (params.alternative_linesearch_cond)
276  ls_cond -= (0.5 / γₖ₊₁ - 0.5 / γₖ) * pₖ₊₁ᵀpₖ₊₁_ₖ;
277 
278  τ /= 2;
279  } while (ls_cond > margin && τ >= params.τ_min);
280 
281  // If τ < τ_min the line search failed and we accepted the prox step
282  if (τ < params.τ_min && k != 0) {
284  τ = 0;
285  }
286  if (k != 0) {
287  s.count_τ += 1;
288  s.sum_τ += τ * 2;
289  s.τ_1_accepted += τ * 2 == 1;
290  }
291 
292  // Update L-BFGS -------------------------------------------------------
293  if (γₖ != γₖ₊₁) // Flush L-BFGS if γ changed
294  direction_provider.changed_γ(γₖ₊₁, γₖ);
295 
296  s.lbfgs_rejected += not direction_provider.update(
297  xₖ, xₖ₊₁, pₖ, pₖ₊₁, grad_ψₖ₊₁, problem.C, γₖ₊₁);
298 
299  // Check if we made any progress
300  if (no_progress > 0 || k % params.max_no_progress == 0)
301  no_progress = xₖ == xₖ₊₁ ? no_progress + 1 : 0;
302 
303  // Advance step --------------------------------------------------------
304  Lₖ = Lₖ₊₁;
305  γₖ = γₖ₊₁;
306 
307  ψₖ = ψₖ₊₁;
308  ψx̂ₖ = ψx̂ₖ₊₁;
309  φₖ = φₖ₊₁;
310 
311  xₖ.swap(xₖ₊₁);
312  x̂ₖ.swap(x̂ₖ₊₁);
313  ŷx̂ₖ.swap(ŷx̂ₖ₊₁);
314  pₖ.swap(pₖ₊₁);
315  grad_ψₖ.swap(grad_ψₖ₊₁);
316  grad_ψₖᵀpₖ = grad_ψₖ₊₁ᵀpₖ₊₁;
317  pₖᵀpₖ = pₖ₊₁ᵀpₖ₊₁;
318  }
319  throw std::logic_error("[PANOC] loop error");
320 }
321 
322 template <class DirectionProviderT>
324  return "PANOCSolverFull<" + direction_provider.get_name() + ">";
325 }
326 
327 template <class DirectionProviderT>
331  const ProblemFull &problem,
333  crvec Σ1,
335  crvec Σ2,
337  real_t ε,
339  bool always_overwrite_results,
341  rvec x,
343  rvec y,
345  rvec err_z1,
347  rvec err_z2,
349  std::chrono::microseconds time_remaining) {
350 
351  std::chrono::microseconds delta_time = std::chrono::microseconds(0);
352  if (time_remaining > std::chrono::microseconds(0) && params.max_time > time_remaining) {
353  delta_time = params.max_time - time_remaining;
354  }
355 
356  auto start_time = std::chrono::steady_clock::now();
357  Stats s;
358 
359  const auto n = problem.n;
360  const auto m1 = problem.m1;
361  const auto m2 = problem.m2;
362 
363  // Allocate vectors, init L-BFGS -------------------------------------------
364 
365  // TODO: the L-BFGS objects and vectors allocate on each iteration of ALM,
366  // and there are more vectors than strictly necessary.
367 
368  vec xₖ = x, // Value of x at the beginning of the iteration
369  x̂ₖ(n), // Value of x after a projected gradient step
370  xₖ₊₁(n), // xₖ for next iteration
371  x̂ₖ₊₁(n), // x̂ₖ for next iteration
372  ŷx̂ₖ1(m1), // ŷ(x̂ₖ) = Σ (g1(x̂ₖ) - ẑₖ)
373  ŷx̂ₖ2(m2), // ŷ(x̂ₖ) = Σ (g2(x̂ₖ) - ẑₖ)
374  ŷx̂ₖ₊₁1(m1), // ŷ(x̂ₖ) for next iteration
375  ŷx̂ₖ₊₁2(m2), // ŷ(x̂ₖ) for next iteration
376  pₖ(n), // Projected gradient step pₖ = x̂ₖ - xₖ
377  pₖ₊₁(n), // Projected gradient step pₖ₊₁ = x̂ₖ₊₁ - xₖ₊₁
378  qₖ(n), // Newton step Hₖ pₖ
379  grad_ψₖ(n), // ∇ψ(xₖ)
380  grad_̂ψₖ(n), // ∇ψ(x̂ₖ)
381  grad_ψₖ₊₁(n); // ∇ψ(xₖ₊₁)
382 
383  vec work_n(n), work_m1(m1), work_m2(m2);
384 
385  // Keep track of how many successive iterations didn't update the iterate
386  unsigned no_progress = 0;
387 
388  // Helper functions --------------------------------------------------------
389 
390  // Wrappers for helper functions that automatically pass along any arguments
391  // that are constant within PANOC (for readability in the main algorithm)
392  auto calc_ψ_ŷ = [&problem, &y, &Σ1, &Σ2](crvec x, rvec ŷ1, rvec ŷ2) {
393  return detail::calc_ψ_ŷ(problem, x, y, Σ1, Σ2, ŷ1, ŷ2);
394  };
395  auto calc_ψ_grad_ψ = [&problem, &y, &Σ1, &Σ2, &work_n, &work_m1, &work_m2](
396  crvec x, rvec grad_ψ) {
397  return detail::calc_ψ_grad_ψ(problem, x, y, Σ1, Σ2, grad_ψ, work_n, work_m1, work_m2);
398  };
399  auto calc_grad_ψ_from_ŷ = [&problem, &work_n](crvec x, crvec ŷ1,
400  crvec ŷ2, rvec grad_ψ) {
401  detail::calc_grad_ψ_from_ŷ(problem, x, ŷ1, ŷ2, grad_ψ, work_n);
402  };
403  auto calc_x̂ = [&problem](real_t γ, crvec x, crvec grad_ψ, rvec x̂, rvec p) {
404  detail::calc_x̂(problem, γ, x, grad_ψ, x̂, p);
405  };
406  auto calc_err_z = [&problem, &y, &Σ1](crvec x̂, rvec err_z1, rvec err_z2) {
407  detail::calc_err_z(problem, x̂, y, Σ1, err_z1, err_z2);
408  };
409  auto descent_lemma = [this, &problem, &y,
410  &Σ1, &Σ2](crvec xₖ, real_t ψₖ, crvec grad_ψₖ, rvec x̂ₖ,
411  rvec pₖ, rvec ŷx̂ₖ1, rvec ŷx̂ₖ2, real_t &ψx̂ₖ, real_t &pₖᵀpₖ,
412  real_t &grad_ψₖᵀpₖ, real_t &Lₖ, real_t &γₖ) {
413  return detail::descent_lemma(
414  problem, params.quadratic_upperbound_tolerance_factor, params.L_max,
415  xₖ, ψₖ, grad_ψₖ, y, Σ1, Σ2, x̂ₖ, pₖ, ŷx̂ₖ1, ŷx̂ₖ2, ψx̂ₖ, pₖᵀpₖ, grad_ψₖᵀpₖ, Lₖ, γₖ);
416  };
417  auto print_progress = [&](unsigned k, real_t ψₖ, crvec grad_ψₖ,
418  real_t pₖᵀpₖ, real_t γₖ, real_t εₖ) {
419  std::cout << "[PANOC] " << std::setw(6) << k
420  << ": ψ = " << std::setw(13) << ψₖ
421  << ", ‖∇ψ‖ = " << std::setw(13) << grad_ψₖ.norm()
422  << ", ‖p‖ = " << std::setw(13) << std::sqrt(pₖᵀpₖ)
423  << ", γ = " << std::setw(13) << γₖ
424  << ", εₖ = " << std::setw(13) << εₖ << "\r\n";
425  };
426 
427  // Estimate Lipschitz constant ---------------------------------------------
428 
429  real_t ψₖ, Lₖ;
430  // Finite difference approximation of ∇²ψ in starting point
431  if (params.Lipschitz.L₀ <= 0) {
433  problem, xₖ, y, Σ1, Σ2, params.Lipschitz.ε, params.Lipschitz.δ,
434  /* in ⟹ out */ ψₖ, grad_ψₖ, x̂ₖ, grad_̂ψₖ, work_n, work_m1, work_m2);
435  }
436  // Initial Lipschitz constant provided by the user
437  else {
438  Lₖ = params.Lipschitz.L₀;
439  // Calculate ψ(xₖ), ∇ψ(x₀)
440  ψₖ = calc_ψ_grad_ψ(xₖ, /* in ⟹ out */ grad_ψₖ);
441  }
442  if (not std::isfinite(Lₖ)) {
443  s.status = SolverStatus::NotFinite;
444  return s;
445  }
446  real_t γₖ = params.Lipschitz.Lγ_factor / Lₖ;
447  real_t τ = NaN;
448 
449  // First projected gradient step -------------------------------------------
450 
451  // Calculate x̂₀, p₀ (projected gradient step)
452  calc_x̂(γₖ, xₖ, grad_ψₖ, /* in ⟹ out */ x̂ₖ, pₖ);
453  // Calculate ψ(x̂ₖ) and ŷ(x̂ₖ)
454  real_t ψx̂ₖ = calc_ψ_ŷ(x̂ₖ, /* in ⟹ out */ ŷx̂ₖ1, ŷx̂ₖ2);
455  real_t grad_ψₖᵀpₖ = grad_ψₖ.dot(pₖ);
456  real_t pₖᵀpₖ = pₖ.squaredNorm();
457  // Compute forward-backward envelope
458  real_t φₖ = ψₖ + 1 / (2 * γₖ) * pₖᵀpₖ + grad_ψₖᵀpₖ;
459 
460  // Main PANOC loop
461  // =========================================================================
462  for (unsigned k = 0; k <= params.max_iter; ++k) {
463 
464  // Quadratic upper bound -----------------------------------------------
465  if (k == 0 || params.update_lipschitz_in_linesearch == false) {
466  // Decrease step size until quadratic upper bound is satisfied
467  real_t old_γₖ =
468  descent_lemma(xₖ, ψₖ, grad_ψₖ,
469  /* in ⟹ out */ x̂ₖ, pₖ, ŷx̂ₖ1, ŷx̂ₖ2,
470  /* inout */ ψx̂ₖ, pₖᵀpₖ, grad_ψₖᵀpₖ, Lₖ, γₖ);
471  if (k > 0 && γₖ != old_γₖ) // Flush L-BFGS if γ changed
472  direction_provider.changed_γ(γₖ, old_γₖ);
473  else if (k == 0) // Initialize L-BFGS
474  direction_provider.initialize(xₖ, x̂ₖ, pₖ, grad_ψₖ);
475  if (γₖ != old_γₖ)
476  φₖ = ψₖ + 1 / (2 * γₖ) * pₖᵀpₖ + grad_ψₖᵀpₖ;
477  }
478  // Calculate ∇ψ(x̂ₖ)
479  calc_grad_ψ_from_ŷ(x̂ₖ, ŷx̂ₖ1, ŷx̂ₖ2, /* in ⟹ out */ grad_̂ψₖ);
480 
481  // Check stop condition ------------------------------------------------
482  real_t εₖ = detail::calc_error_stop_crit(params.stop_crit, pₖ, γₖ, xₖ,
483  grad_̂ψₖ, grad_ψₖ, problem.C);
484 
485  // Print progress
486  if (params.print_interval != 0 && k % params.print_interval == 0)
487  print_progress(k, ψₖ, grad_ψₖ, pₖᵀpₖ, γₖ, εₖ);
488  if (progress_cb)
489  progress_cb({k, xₖ, pₖ, pₖᵀpₖ, x̂ₖ, φₖ, ψₖ, grad_ψₖ, ψx̂ₖ, grad_̂ψₖ,
490  Lₖ, γₖ, τ, εₖ, Σ1, Σ2, y, problem, params}); //TODO
491 
492  auto time_elapsed = std::chrono::steady_clock::now() - start_time;
493  auto stop_status = detail::check_all_stop_conditions(
494  params, time_elapsed + delta_time, k, stop_signal, ε, εₖ, no_progress);
495  if (stop_status != SolverStatus::Unknown) {
496  // TODO: We could cache g(x) and ẑ, but would that faster?
497  // It saves 1 evaluation of g per ALM iteration, but requires
498  // many extra stores in the inner loops of PANOC.
499  // TODO: move the computation of ẑ and g(x) to ALM?
500  if (stop_status == SolverStatus::Converged ||
501  stop_status == SolverStatus::Interrupted ||
502  stop_status == SolverStatus::MaxTime ||
503  always_overwrite_results) {
504  calc_err_z(x̂ₖ, /* in ⟹ out */ err_z1, err_z2);
505  x = std::move(x̂ₖ);
506  y = std::move(ŷx̂ₖ1);
507  }
508  s.iterations = k;
509  s.ε = εₖ;
510  s.elapsed_time = duration_cast<microseconds>(time_elapsed);
511  s.status = stop_status;
512  return s;
513  }
514 
515  // Calculate quasi-Newton step -----------------------------------------
516  real_t step_size =
517  params.lbfgs_stepsize == LBFGSStepSize::BasedOnGradientStepSize
518  ? 1
519  : -1;
520  if (k > 0)
521  direction_provider.apply(xₖ, x̂ₖ, pₖ, step_size,
522  /* in ⟹ out */ qₖ);
523 
524  // Line search initialization ------------------------------------------
525  τ = 1;
526  real_t σₖγₖ⁻¹pₖᵀpₖ = (1 - γₖ * Lₖ) * pₖᵀpₖ / (2 * γₖ);
527  real_t φₖ₊₁, ψₖ₊₁, ψx̂ₖ₊₁, grad_ψₖ₊₁ᵀpₖ₊₁, pₖ₊₁ᵀpₖ₊₁;
528  real_t Lₖ₊₁, γₖ₊₁;
529  real_t ls_cond;
530  // TODO: make separate parameter
531  real_t margin =
532  (1 + std::abs(φₖ)) * params.quadratic_upperbound_tolerance_factor;
533 
534  // Make sure quasi-Newton step is valid
535  if (k == 0) {
536  τ = 0; // Always use prox step on first iteration
537  } else if (not qₖ.allFinite()) {
538  τ = 0;
539  ++s.lbfgs_failures;
540  direction_provider.reset(); // Is there anything else we can do?
541  }
542 
543  // Line search loop ----------------------------------------------------
544  do {
545  Lₖ₊₁ = Lₖ;
546  γₖ₊₁ = γₖ;
547 
548  // Calculate xₖ₊₁
549  if (τ / 2 < params.τ_min) { // line search failed
550  xₖ₊₁.swap(x̂ₖ); // → safe prox step
551  ψₖ₊₁ = ψx̂ₖ;
552  grad_ψₖ₊₁.swap(grad_̂ψₖ);
553  } else { // line search didn't fail (yet)
554  if (τ == 1) // → faster quasi-Newton step
555  xₖ₊₁ = xₖ + qₖ;
556  else
557  xₖ₊₁ = xₖ + (1 - τ) * pₖ + τ * qₖ;
558  // Calculate ψ(xₖ₊₁), ∇ψ(xₖ₊₁)
559  ψₖ₊₁ = calc_ψ_grad_ψ(xₖ₊₁, /* in ⟹ out */ grad_ψₖ₊₁);
560  }
561 
562  // Calculate x̂ₖ₊₁, pₖ₊₁ (projected gradient step in xₖ₊₁)
563  calc_x̂(γₖ₊₁, xₖ₊₁, grad_ψₖ₊₁, /* in ⟹ out */ x̂ₖ₊₁, pₖ₊₁);
564  // Calculate ψ(x̂ₖ₊₁) and ŷ(x̂ₖ₊₁)
565  ψx̂ₖ₊₁ = calc_ψ_ŷ(x̂ₖ₊₁, /* in ⟹ out */ ŷx̂ₖ₊₁1, ŷx̂ₖ₊₁2);
566 
567  // Quadratic upper bound -------------------------------------------
568  grad_ψₖ₊₁ᵀpₖ₊₁ = grad_ψₖ₊₁.dot(pₖ₊₁);
569  pₖ₊₁ᵀpₖ₊₁ = pₖ₊₁.squaredNorm();
570  real_t pₖ₊₁ᵀpₖ₊₁_ₖ = pₖ₊₁ᵀpₖ₊₁; // prox step with step size γₖ
571 
572  if (params.update_lipschitz_in_linesearch == true) {
573  // Decrease step size until quadratic upper bound is satisfied
574  (void)descent_lemma(xₖ₊₁, ψₖ₊₁, grad_ψₖ₊₁,
575  /* in ⟹ out */ x̂ₖ₊₁, pₖ₊₁, ŷx̂ₖ₊₁1, ŷx̂ₖ₊₁2,
576  /* inout */ ψx̂ₖ₊₁, pₖ₊₁ᵀpₖ₊₁,
577  grad_ψₖ₊₁ᵀpₖ₊₁, Lₖ₊₁, γₖ₊₁);
578  }
579 
580  // Compute forward-backward envelope
581  φₖ₊₁ = ψₖ₊₁ + 1 / (2 * γₖ₊₁) * pₖ₊₁ᵀpₖ₊₁ + grad_ψₖ₊₁ᵀpₖ₊₁;
582  // Compute line search condition
583  ls_cond = φₖ₊₁ - (φₖ - σₖγₖ⁻¹pₖᵀpₖ);
584  if (params.alternative_linesearch_cond)
585  ls_cond -= (0.5 / γₖ₊₁ - 0.5 / γₖ) * pₖ₊₁ᵀpₖ₊₁_ₖ;
586 
587  τ /= 2;
588  } while (ls_cond > margin && τ >= params.τ_min);
589 
590  // If τ < τ_min the line search failed and we accepted the prox step
591  if (τ < params.τ_min && k != 0) {
593  τ = 0;
594  }
595  if (k != 0) {
596  s.count_τ += 1;
597  s.sum_τ += τ * 2;
598  s.τ_1_accepted += τ * 2 == 1;
599  }
600 
601  // Update L-BFGS -------------------------------------------------------
602  if (γₖ != γₖ₊₁) // Flush L-BFGS if γ changed
603  direction_provider.changed_γ(γₖ₊₁, γₖ);
604 
605  s.lbfgs_rejected += not direction_provider.update(
606  xₖ, xₖ₊₁, pₖ, pₖ₊₁, grad_ψₖ₊₁, problem.C, γₖ₊₁);
607 
608  // Check if we made any progress
609  if (no_progress > 0 || k % params.max_no_progress == 0)
610  no_progress = xₖ == xₖ₊₁ ? no_progress + 1 : 0;
611 
612  // Advance step --------------------------------------------------------
613  Lₖ = Lₖ₊₁;
614  γₖ = γₖ₊₁;
615 
616  ψₖ = ψₖ₊₁;
617  ψx̂ₖ = ψx̂ₖ₊₁;
618  φₖ = φₖ₊₁;
619 
620  xₖ.swap(xₖ₊₁);
621  x̂ₖ.swap(x̂ₖ₊₁);
622  ŷx̂ₖ1.swap(ŷx̂ₖ₊₁1);
623  ŷx̂ₖ2.swap(ŷx̂ₖ₊₁2);
624  pₖ.swap(pₖ₊₁);
625  grad_ψₖ.swap(grad_ψₖ₊₁);
626  grad_ψₖᵀpₖ = grad_ψₖ₊₁ᵀpₖ₊₁;
627  pₖᵀpₖ = pₖ₊₁ᵀpₖ₊₁;
628  }
629  throw std::logic_error("[PANOC] loop error");
630 }
631 
632 } // namespace pa
pa::PANOCStats::τ_1_accepted
unsigned τ_1_accepted
Definition: inner/decl/panoc.hpp:59
pa::detail::print_progress
void print_progress(unsigned k, real_t ψₖ, crvec grad_ψₖ, real_t pₖᵀpₖ, real_t γₖ, real_t εₖ)
Definition: standalone/panoc.hpp:139
pa::PANOCSolver::operator()
Stats operator()(const Problem &problem, crvec Σ, real_t ε, bool always_overwrite_results, rvec x, rvec y, rvec err_z, std::chrono::microseconds time_remaining=std::chrono::microseconds(0))
Definition: inner/panoc.hpp:26
pa::detail::calc_x̂
void calc_x̂(const ProblemT &prob, real_t γ, crvec x, crvec grad_ψ, rvec x̂, rvec p)
Definition: panoc-helpers.hpp:271
pa::PANOCSolver::get_name
std::string get_name() const
Definition: inner/panoc.hpp:20
pa::PANOCSolverFull::get_name
std::string get_name() const
Definition: inner/panoc.hpp:323
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
pa::PANOCStats::iterations
unsigned iterations
Definition: inner/decl/panoc.hpp:55
panocpy.test.y
y
Definition: test.py:76
panocpy.test.err_z
err_z
Definition: test.py:76
panocpy.test.ε
int ε
Definition: test.py:71
pa::vec
realvec vec
Default type for vectors.
Definition: vec.hpp:14
pa::PANOCStats::count_τ
unsigned count_τ
Definition: inner/decl/panoc.hpp:60
pa::PANOCStats::status
SolverStatus status
Definition: inner/decl/panoc.hpp:52
main.problem
problem
Definition: main.py:16
pa::PANOCSolverFull::operator()
Stats operator()(const ProblemFull &problem, crvec Σ1, crvec Σ2, real_t ε, bool always_overwrite_results, rvec x, rvec y, rvec err_z1, rvec err_z2, std::chrono::microseconds time_remaining=std::chrono::microseconds(0))
Definition: inner/panoc.hpp:329
pa
Definition: alm.hpp:10
pa::detail::calc_ψ_grad_ψ
real_t calc_ψ_grad_ψ(const Problem &p, crvec x, crvec y, crvec Σ, rvec grad_ψ, rvec work_n, rvec work_m)
Calculate both ψ(x) and its gradient ∇ψ(x).
Definition: panoc-helpers.hpp:113
panocpy.test.x
x
Definition: test.py:40
pa::PANOCStats::lbfgs_rejected
unsigned lbfgs_rejected
Definition: inner/decl/panoc.hpp:58
pa::detail::check_all_stop_conditions
SolverStatus check_all_stop_conditions(const ParamsT &params, DurationT time_elapsed, unsigned iteration, const AtomicStopSignal &stop_signal, real_t ε, real_t εₖ, unsigned no_progress)
Check all stop conditions (required tolerance reached, out of time, maximum number of iterations exce...
Definition: panoc-helpers.hpp:469
pa::detail::calc_ψ_ŷ
real_t calc_ψ_ŷ(const Problem &p, crvec x, crvec y, crvec Σ, rvec ŷ)
Calculate both ψ(x) and the vector ŷ that can later be used to compute ∇ψ.
Definition: panoc-helpers.hpp:16
panoc-helpers.hpp
pa::detail::calc_err_z
void calc_err_z(const Problem &p, crvec x̂, crvec y, crvec Σ, rvec err_z)
Calculate the error between ẑ and g(x).
Definition: panoc-helpers.hpp:212
pa::detail::calc_error_stop_crit
real_t calc_error_stop_crit(PANOCStopCrit crit, crvec pₖ, real_t γ, crvec xₖ, crvec grad_̂ψₖ, crvec grad_ψₖ, const Box &C)
Definition: panoc-helpers.hpp:284
pa::PANOCStats::ε
real_t ε
Definition: inner/decl/panoc.hpp:53
pa::detail::descent_lemma
real_t descent_lemma(const Problem &problem, real_t rounding_tolerance, real_t L_max, crvec xₖ, real_t ψₖ, crvec grad_ψₖ, crvec y, crvec Σ, rvec x̂ₖ, rvec pₖ, rvec ŷx̂ₖ, real_t &ψx̂ₖ, real_t &norm_sq_pₖ, real_t &grad_ψₖᵀpₖ, real_t &Lₖ, real_t &γₖ)
Increase the estimate of the Lipschitz constant of the objective gradient and decrease the step size ...
Definition: panoc-helpers.hpp:327
pa::crvec
Eigen::Ref< const vec > crvec
Default type for immutable references to vectors.
Definition: vec.hpp:18
panoc-direction-update.hpp
panocpy.test.params
params
Definition: test.py:217
panoc.hpp
panocpy.test.Σ
int Σ
Definition: test.py:70
panocpy.test.m
int m
Definition: test.py:39
pa::PANOCStats::sum_τ
real_t sum_τ
Definition: inner/decl/panoc.hpp:61
pa::PANOCStats
Definition: inner/decl/panoc.hpp:51
pa::detail::initial_lipschitz_estimate
real_t initial_lipschitz_estimate(const Problem &problem, crvec xₖ, crvec y, crvec Σ, real_t ε, real_t δ, real_t L_min, real_t L_max, real_t &ψ, rvec grad_ψ, rvec work_n1, rvec work_n2, rvec work_n3, rvec work_m)
Estimate the Lipschitz constant of the gradient using finite differences.
Definition: panoc-helpers.hpp:574
pa::detail::calc_grad_ψ_from_ŷ
void calc_grad_ψ_from_ŷ(const Problem &p, crvec x, crvec ŷ, rvec grad_ψ, rvec work_n)
Calculate ∇ψ(x) using ŷ.
Definition: panoc-helpers.hpp:81
panocpy.test.p
p
Definition: test.py:57
pa::ProblemFull
Problem description for minimization problems.
Definition: include/panoc-alm/util/problem.hpp:213
panocpy.test.n
int n
Definition: test.py:38
pa::PANOCStats::linesearch_failures
unsigned linesearch_failures
Definition: inner/decl/panoc.hpp:56
pa::PANOCStats::elapsed_time
std::chrono::microseconds elapsed_time
Definition: inner/decl/panoc.hpp:54
pa::real_t
double real_t
Default floating point type.
Definition: vec.hpp:8
pa::PANOCStats::lbfgs_failures
unsigned lbfgs_failures
Definition: inner/decl/panoc.hpp:57
pa::Problem
Problem description for minimization problems.
Definition: include/panoc-alm/util/problem.hpp:24