alpaqa 0.0.1
Nonconvex constrained optimization
pga.hpp
Go to the documentation of this file.
1#pragma once
2
8
9#include <cassert>
10#include <chrono>
11#include <cmath>
12#include <iomanip>
13#include <iostream>
14#include <stdexcept>
15
16namespace alpaqa {
17
18struct PGAParams {
19 /// Parameters related to the Lipschitz constant estimate and step size.
21 /// Maximum number of inner iterations.
22 unsigned max_iter = 100;
23 /// Maximum duration.
24 std::chrono::microseconds max_time = std::chrono::minutes(5);
25 /// Minimum Lipschitz constant estimate.
26 real_t L_min = 1e-5;
27 /// Maximum Lipschitz constant estimate.
28 real_t L_max = 1e20;
29 /// What stop criterion to use.
31
32 /// When to print progress. If set to zero, nothing will be printed.
33 /// If set to N != 0, progress is printed every N iterations.
34 unsigned print_interval = 0;
35
37 10 * std::numeric_limits<real_t>::epsilon();
38};
39
41 unsigned k;
57};
58
59/// Standard Proximal Gradient Algorithm without any bells and whistles.
60///
61/// @ingroup grp_InnerSolvers
62class PGASolver {
63 public:
65
67
68 struct Stats {
71 std::chrono::microseconds elapsed_time;
72 unsigned iterations = 0;
73 };
74
76
77 Stats operator()(const Problem &problem, // in
78 crvec Σ, // in
79 real_t ε, // in
80 bool always_overwrite_results, // in
81 rvec x, // inout
82 rvec λ, // inout
83 rvec err_z); // out
84
85 PGASolver &
86 set_progress_callback(std::function<void(const ProgressInfo &)> cb) {
87 this->progress_cb = cb;
88 return *this;
89 }
90
91 std::string get_name() const { return "PGA"; }
92
93 void stop() { stop_signal.stop(); }
94
95 const Params &get_params() const { return params; }
96
97 private:
100 std::function<void(const ProgressInfo &)> progress_cb;
101};
102
103using std::chrono::duration_cast;
104using std::chrono::microseconds;
105
106inline PGASolver::Stats
108 crvec Σ, // in
109 real_t ε, // in
110 bool always_overwrite_results, // in
111 rvec x, // inout
112 rvec y, // inout
113 rvec err_z // out
114) {
115 auto start_time = std::chrono::steady_clock::now();
116 Stats s;
117
118 const auto n = problem.n;
119 const auto m = problem.m;
120
121 vec xₖ = x, // Value of x at the beginning of the iteration
122 x̂ₖ(n), // Value of x after a projected gradient step
123 pₖ(n), // Projected gradient step
124 ŷₖ(m), // <?>
125 grad_ψₖ(n), // ∇ψ(xₖ)
126 grad_ψx̂ₖ(n); // ∇ψ(x̂ₖ)
127
128 vec work_n(n), work_m(m);
129
130 // Helper functions --------------------------------------------------------
131
132 // Wrappers for helper functions that automatically pass along any arguments
133 // that are constant within PGA (for readability in the main algorithm)
134 auto calc_ψ_ŷ = [&problem, &y, &Σ](crvec x, rvec ŷ) {
135 return detail::calc_ψ_ŷ(problem, x, y, Σ, ŷ);
136 };
137 auto calc_ψ_grad_ψ = [&problem, &y, &Σ, &work_n, &work_m](crvec x,
138 rvec grad_ψ) {
139 return detail::calc_ψ_grad_ψ(problem, x, y, Σ, grad_ψ, work_n, work_m);
140 };
141 auto calc_grad_ψ_from_ŷ = [&problem, &work_n](crvec x, crvec ŷ,
142 rvec grad_ψ) {
143 detail::calc_grad_ψ_from_ŷ(problem, x, ŷ, grad_ψ, work_n);
144 };
145 auto calc_x̂ = [&problem](real_t γ, crvec x, crvec grad_ψ, rvec x̂, rvec p) {
146 detail::calc_x̂(problem, γ, x, grad_ψ, x̂, p);
147 };
148 auto calc_err_z = [&problem, &y, &Σ](crvec x̂, rvec err_z) {
150 };
151 auto descent_lemma = [this, &problem, &y,
152 &Σ](crvec xₖ, real_t ψₖ, crvec grad_ψₖ, rvec x̂ₖ,
153 rvec pₖ, rvec ŷx̂ₖ, real_t &ψx̂ₖ, real_t &pₖᵀpₖ,
154 real_t &grad_ψₖᵀpₖ, real_t &Lₖ, real_t &γₖ) {
157 xₖ, ψₖ, grad_ψₖ, y, Σ, x̂ₖ, pₖ, ŷx̂ₖ, ψx̂ₖ, pₖᵀpₖ, grad_ψₖᵀpₖ, Lₖ, γₖ);
158 };
159 auto print_progress = [&](unsigned k, real_t ψₖ, crvec grad_ψₖ, crvec pₖ,
160 real_t γₖ, real_t εₖ) {
161 std::cout << "[PGA] " << std::setw(6) << k
162 << ": ψ = " << std::setw(13) << ψₖ
163 << ", ‖∇ψ‖ = " << std::setw(13) << grad_ψₖ.norm()
164 << ", ‖p‖ = " << std::setw(13) << pₖ.norm()
165 << ", γ = " << std::setw(13) << γₖ
166 << ", εₖ = " << std::setw(13) << εₖ << "\r\n";
167 };
168
169 // Estimate Lipschitz constant ---------------------------------------------
170
171 real_t ψₖ, Lₖ;
172 // Finite difference approximation of ∇²ψ in starting point
173 if (params.Lipschitz.L₀ <= 0) {
177 /* in ⟹ out */ ψₖ, grad_ψₖ, x̂ₖ, grad_ψx̂ₖ, work_n, work_m);
178 }
179 // Initial Lipschitz constant provided by the user
180 else {
181 Lₖ = params.Lipschitz.L₀;
182 // Calculate ψ(xₖ), ∇ψ(x₀)
183 ψₖ = calc_ψ_grad_ψ(xₖ, /* in ⟹ out */ grad_ψₖ);
184 }
185 if (not std::isfinite(Lₖ)) {
187 return s;
188 }
189 real_t γₖ = params.Lipschitz.Lγ_factor / Lₖ;
190
191 unsigned no_progress = 0;
192
193 // Main loop
194 // =========================================================================
195 for (unsigned k = 0; k <= params.max_iter; ++k) {
196 // From previous iteration:
197 // - xₖ
198 // - grad_ψₖ
199 // - ψₖ
200
201 // Quadratic upper bound -----------------------------------------------
202
203 // Projected gradient step: x̂ₖ and pₖ
204 calc_x̂(γₖ, xₖ, grad_ψₖ, /* in ⟹ out */ x̂ₖ, pₖ);
205 // Calculate ψ(x̂ₖ) and ŷ(x̂ₖ)
206 real_t ψx̂ₖ = calc_ψ_ŷ(x̂ₖ, /* in ⟹ out */ ŷₖ);
207 // Calculate ∇ψ(xₖ)ᵀpₖ and ‖pₖ‖²
208 real_t grad_ψₖᵀpₖ = grad_ψₖ.dot(pₖ);
209 real_t pₖᵀpₖ = pₖ.squaredNorm();
210
211 // Decrease step size until quadratic upper bound is satisfied
212 descent_lemma(xₖ, ψₖ, grad_ψₖ, x̂ₖ, pₖ, ŷₖ, ψx̂ₖ, pₖᵀpₖ, grad_ψₖᵀpₖ, Lₖ,
213 γₖ);
214
215 // Calculate ∇ψ(x̂ₖ)
216 calc_grad_ψ_from_ŷ(x̂ₖ, ŷₖ, /* in ⟹ out */ grad_ψx̂ₖ);
217
218 // Check stop condition ------------------------------------------------
219
221 problem.C, params.stop_crit, pₖ, γₖ, xₖ, x̂ₖ, ŷₖ, grad_ψₖ, grad_ψx̂ₖ);
222
223 // Print progress
224 if (params.print_interval != 0 && k % params.print_interval == 0)
225 print_progress(k, ψₖ, grad_ψₖ, pₖ, γₖ, εₖ);
226 if (progress_cb)
227 progress_cb({k, xₖ, pₖ, pₖᵀpₖ, x̂ₖ, ψₖ, grad_ψₖ, ψx̂ₖ, grad_ψx̂ₖ, Lₖ,
228 γₖ, εₖ, Σ, y, problem, params});
229
230 auto time_elapsed = std::chrono::steady_clock::now() - start_time;
231 bool out_of_time = time_elapsed > params.max_time;
232 bool out_of_iter = k == params.max_iter;
233 bool interrupted = stop_signal.stop_requested();
234 bool not_finite = not std::isfinite(εₖ);
235 bool conv = εₖ <= ε;
236 bool max_no_progress = no_progress > 1;
237 bool exit = conv || out_of_iter || out_of_time || not_finite ||
238 interrupted || max_no_progress;
239 if (exit) {
240 // TODO: We could cache g(x) and ẑ, but would that faster?
241 // It saves 1 evaluation of g per ALM iteration, but requires
242 // many extra stores in the inner loops.
243 // TODO: move the computation of ẑ and g(x) to ALM?
244 if (conv || interrupted || always_overwrite_results) {
245 calc_err_z(x̂ₖ, /* in ⟹ out */ err_z);
246 x = std::move(x̂ₖ);
247 y = std::move(ŷₖ);
248 }
249 s.iterations = k; // TODO: what do we count as an iteration?
250 s.ε = εₖ;
251 s.elapsed_time = duration_cast<microseconds>(time_elapsed);
253 : out_of_time ? SolverStatus::MaxTime
254 : out_of_iter ? SolverStatus::MaxIter
255 : not_finite ? SolverStatus::NotFinite
256 : max_no_progress ? SolverStatus::NoProgress
258 return s;
259 }
260
261 if (xₖ == x̂ₖ) // TODO: this is quite expensive to do on each iteration
262 ++no_progress;
263 else
264 no_progress = 0;
265
266 xₖ.swap(x̂ₖ);
267 grad_ψₖ.swap(grad_ψx̂ₖ);
268 ψₖ = ψx̂ₖ;
269 }
270 throw std::logic_error("[PGA] loop error");
271}
272
273template <class InnerSolverStats>
275
276template <>
278 std::chrono::microseconds elapsed_time;
279 unsigned iterations = 0;
280};
281
284 const PGASolver::Stats &s) {
285 acc.elapsed_time += s.elapsed_time;
286 acc.iterations += s.iterations;
287 return acc;
288}
289
290} // namespace alpaqa
Standard Proximal Gradient Algorithm without any bells and whistles.
Definition: pga.hpp:62
Params params
Definition: pga.hpp:98
std::string get_name() const
Definition: pga.hpp:91
std::function< void(const ProgressInfo &)> progress_cb
Definition: pga.hpp:100
PGASolver & set_progress_callback(std::function< void(const ProgressInfo &)> cb)
Definition: pga.hpp:86
AtomicStopSignal stop_signal
Definition: pga.hpp:99
void stop()
Definition: pga.hpp:93
const Params & get_params() const
Definition: pga.hpp:95
std::chrono::microseconds elapsed_time
Definition: pga.hpp:71
PGASolver(const Params &params)
Definition: pga.hpp:66
unsigned iterations
Definition: pga.hpp:72
SolverStatus status
Definition: pga.hpp:69
Stats operator()(const Problem &problem, crvec Σ, real_t ε, bool always_overwrite_results, rvec x, rvec λ, rvec err_z)
Definition: pga.hpp:107
real_t calc_error_stop_crit(const Box &C, PANOCStopCrit crit, crvec pₖ, real_t γ, crvec xₖ, crvec x̂ₖ, crvec ŷₖ, crvec grad_ψₖ, crvec grad_̂ψₖ)
Compute the ε from the stopping criterion, see PANOCStopCrit.
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 ∇ψ.
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 ...
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).
void calc_err_z(const Problem &p, crvec x̂, crvec y, crvec Σ, rvec err_z)
Calculate the error between ẑ and g(x).
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.
void calc_grad_ψ_from_ŷ(const Problem &p, crvec x, crvec ŷ, rvec grad_ψ, rvec work_n)
Calculate ∇ψ(x) using ŷ.
void calc_x̂(const Problem &prob, real_t γ, crvec x, crvec grad_ψ, rvec x̂, rvec p)
void print_progress(unsigned k, real_t ψₖ, crvec grad_ψₖ, real_t pₖᵀpₖ, real_t γₖ, real_t εₖ)
int Σ
Definition: test.py:72
int m
Definition: test.py:41
int n
Definition: test.py:40
int ε
Definition: test.py:73
LipschitzEstimateParams Lipschitz
Parameters related to the Lipschitz constant estimate and step size.
Definition: pga.hpp:20
InnerStatsAccumulator< PolymorphicInnerSolverWrapper::Stats > & operator+=(InnerStatsAccumulator< PolymorphicInnerSolverWrapper::Stats > &acc, const PolymorphicInnerSolverWrapper::Stats &s)
@ ApproxKKT
Find an ε-approximate KKT point in the ∞-norm:
real_t Lγ_factor
Factor that relates step size γ and Lipschitz constant.
Definition: lipschitz.hpp:15
const Problem & problem
Definition: pga.hpp:55
Eigen::Ref< const vec > crvec
Default type for immutable references to vectors.
Definition: vec.hpp:18
real_t δ
Minimum step size for initial finite difference Lipschitz estimate.
Definition: lipschitz.hpp:13
constexpr real_t inf
Definition: vec.hpp:26
real_t L_max
Maximum Lipschitz constant estimate.
Definition: pga.hpp:28
SolverStatus
Exit status of a numerical solver such as ALM or PANOC.
Definition: solverstatus.hpp:7
@ Interrupted
Solver was interrupted by the user.
@ Unknown
Initial value.
@ MaxTime
Maximum allowed execution time exceeded.
@ NoProgress
No progress was made in the last iteration.
@ MaxIter
Maximum number of iterations exceeded.
@ Converged
Converged and reached given tolerance.
@ NotFinite
Intermediate results were infinite or not-a-number.
std::chrono::microseconds max_time
Maximum duration.
Definition: pga.hpp:24
const PGAParams & params
Definition: pga.hpp:56
realvec vec
Default type for vectors.
Definition: vec.hpp:14
real_t L₀
Initial estimate of the Lipschitz constant of ∇ψ(x)
Definition: lipschitz.hpp:9
real_t quadratic_upperbound_tolerance_factor
Definition: pga.hpp:36
std::chrono::microseconds elapsed_time
Definition: pga.hpp:278
double real_t
Default floating point type.
Definition: vec.hpp:8
unsigned print_interval
When to print progress.
Definition: pga.hpp:34
real_t L_min
Minimum Lipschitz constant estimate.
Definition: pga.hpp:26
unsigned max_iter
Maximum number of inner iterations.
Definition: pga.hpp:22
PANOCStopCrit stop_crit
What stop criterion to use.
Definition: pga.hpp:30
Eigen::Ref< vec > rvec
Default type for mutable references to vectors.
Definition: vec.hpp:16
problem
Definition: main.py:16
def cb(it)
Definition: rosenbrock.py:56
Problem description for minimization problems.