Nonconvex constrained optimization
Loading...
Searching...
No Matches
lbfgsb-adapter.cpp
Go to the documentation of this file.
3#include <guanaqo/timed.hpp>
4
5#include <iomanip>
6#include <stdexcept>
7
8extern "C" {
9void alpaqa_setulb_c(int n, int m, double *x, const double *l, const double *u,
10 const int *nbd, double &f, double *g, double factr,
11 double pgtol, double *wa, int *iwa, char *task, int iprint,
12 char *csave, bool *lsave, int *isave, double *dsave);
13}
14
15namespace alpaqa::lbfgsb {
16
17std::string LBFGSBSolver::get_name() const { return "LBFGSBSolver"; }
19 /// [in] Problem description
20 const Problem &problem,
21 /// [in] Solve options
22 const SolveOptions &opts,
23 /// [inout] Decision variable @f$ x @f$
24 rvec x,
25 /// [inout] Lagrange multipliers @f$ y @f$
26 rvec y,
27 /// [in] Constraint weights @f$ \Sigma @f$
28 crvec Σ,
29 /// [out] Slack variable error @f$ g(x) - \Pi_D(g(x) + \Sigma^{-1} y) @f$
30 rvec err_z) -> Stats {
31
32 if (opts.check)
33 problem.check();
34
35 using std::chrono::nanoseconds;
36 using clock = std::chrono::steady_clock;
37 auto start_time = clock::now();
38 auto *os = opts.os ? opts.os : this->os;
39 auto max_time = params.max_time;
40 if (opts.max_time)
41 max_time = std::min(max_time, *opts.max_time);
42 Stats s;
43
44 if (!problem.provides_get_variable_bounds())
45 throw std::invalid_argument("LBFGSBSolver requires box constraints");
47 throw std::invalid_argument("LBFGSBSolver only supports "
48 "PANOCStopCrit::ProjGradUnitNorm");
49
50 const auto n = problem.get_num_variables();
51 const auto m_constr = problem.get_num_constraints();
52 const auto &C = problem.get_variable_bounds();
53 const auto mem = static_cast<length_t>(params.memory);
54
55 auto do_progress_cb = [this, &s, &problem, &Σ, &y,
56 &opts](unsigned k, crvec x, real_t ψx, crvec grad_ψx,
57 real_t τ_max, real_t τ, real_t ε,
58 SolverStatus status) {
59 if (!progress_cb)
60 return;
62 guanaqo::Timed t{s.time_progress_callback};
64 .k = k,
65 .status = status,
66 .x = x,
67 .ψ = ψx,
68 .grad_ψ = grad_ψx,
69 .τ_max = τ_max,
70 .τ = τ,
71 .ε = ε,
72 .Σ = Σ,
73 .y = y,
74 .outer_iter = opts.outer_iter,
75 .problem = &problem,
76 .params = &params,
77 });
78 };
79
80 using intvec = Eigen::VectorX<int>;
81
82 vec work_n(n), work_m(m_constr);
83 vec grad_ψ(n);
85 vec wa(2 * mem * n + 5 * n + 11 * mem * mem + 8 * mem);
86 vec dsave(29);
87 intvec iwa(3 * n), isave(44);
88 std::array<bool, 4> lsave{};
89 std::array<char, 60> task{}, csave{};
90
91 // Determine constraint type for each variable
92 intvec nbd(n);
93 for (index_t i = 0; i < n; ++i) {
94 static constexpr int nbd_legend[2][2] /* NOLINT(*c-arrays) */ {{0, 3},
95 {1, 2}};
96 int lowerbounded = C.lower(i) == -inf<config_t> ? 0 : 1;
97 int upperbounded = C.upper(i) == +inf<config_t> ? 0 : 1;
98 nbd(i) = nbd_legend[lowerbounded][upperbounded];
99 }
100
101 vec x_solve = x;
102
103 real_t factr = params.factr;
104 real_t pgtol = 0; // we check this manually
105 int print = params.print;
106
107 std::string_view task_sv{task.begin(), task.end()};
108 const int &num_iter = isave.coeffRef(29);
109 const int &lbfgs_skipped = isave.coeffRef(25);
110 const int &num_free_var = isave.coeffRef(37);
111 const real_t &q_norm = dsave.coeffRef(3);
112 const real_t &τ_max = dsave.coeffRef(11);
113 const real_t &proj_grad_norm = dsave.coeffRef(12);
114 const real_t &τ_rel = dsave.coeffRef(13);
115 // const int &lbfgs_tot = isave.coeffRef(30);
116 int num_iter_tot = 0; // including restarts of the algorithm
117 int lbfgs_skipped_tot = 0;
118
119 auto set_task = [&](std::string_view s) {
120 std::fill(std::copy(s.begin(), s.end(), task.begin()), task.end(), ' ');
121 };
122
123 auto check_termination = [&] {
124 if (proj_grad_norm <= opts.tolerance) {
126 set_task("STOP: projected gradient norm");
127 return true;
128 } else if (clock::now() - start_time >= max_time) {
130 set_task("STOP: time");
131 return true;
132 } else if (static_cast<unsigned>(num_iter + num_iter_tot) >=
133 params.max_iter) {
135 set_task("STOP: number iterations");
136 return true;
137 } else if (stop_signal.stop_requested()) {
139 set_task("STOP: user request");
140 return true;
141 }
142 return false;
143 };
144
145 std::array<char, 64> print_buf;
146 auto print_real = [this, &print_buf](real_t x) {
147 return float_to_str_vw(print_buf, x, params.print_precision);
148 };
149 auto print_real3 = [&print_buf](real_t x) {
150 return float_to_str_vw(print_buf, x, 3);
151 };
152 auto print_progress_1 = [&](unsigned k, real_t ψₖ, crvec grad_ψₖ,
153 real_t εₖ) {
154 if (k == 0)
155 *os << "┌─[LBFGSB]\n";
156 else
157 *os << "├─ " << std::setw(6) << k << " ──\n";
158 *os << "│ ψ = " << print_real(ψₖ) //
159 << ", ‖∇ψ‖ = " << print_real(grad_ψₖ.norm()) //
160 << ", ε = " << print_real(εₖ) << '\n';
161 };
162 auto print_progress_2 = [&](real_t q_norm, real_t τ_max, real_t τ_rel,
163 int nJ) {
164 const auto thres = std::sqrt(std::numeric_limits<real_t>::epsilon());
165 const char *color = τ_rel == 1 ? "\033[0;32m"
166 : τ_rel > thres ? "\033[0;33m"
167 : "\033[0;35m";
168 *os << "│ ‖q‖ = " << print_real(q_norm) //
169 << ", τ = " << color << print_real3(τ_rel) << "\033[0m" //
170 << ", τ_max = " << print_real3(τ_max) //
171 << ", #J = " << std::setw(6) << nJ
172 << std::endl; // Flush for Python buffering
173 };
174 auto print_progress_n = [&](SolverStatus status) {
175 *os << "└─ " << status << " ──"
176 << std::endl; // Flush for Python buffering
177 };
178 auto print_error = [&](std::string_view task_sv) {
179 std::string_view task_trimmed = task_sv;
180 auto trim_pos = task_sv.find('\0');
181 trim_pos = task_sv.find_last_not_of(' ', trim_pos);
182 if (trim_pos != task_trimmed.npos)
183 task_trimmed.remove_suffix(task_trimmed.size() - trim_pos);
184 *os << "│ \033[0;31mLBFGSB failure\033[0m: '\033[0;33m" << task_trimmed
185 << "\033[0m'" << std::endl;
186 };
187 bool did_print = false;
188
189 // Start solving
190 set_task("START");
191
192 while (true) {
193 // Invoke solver
194 alpaqa_setulb_c(static_cast<int>(n), static_cast<int>(mem),
195 x_solve.data(), C.lower.data(), C.upper.data(),
196 nbd.data(), ψ, grad_ψ.data(), factr, pgtol, wa.data(),
197 iwa.data(), task.data(), print, csave.data(),
198 lsave.data(), isave.data(), dsave.data());
199
200 // New evaluation
201 if (task_sv.starts_with("FG")) {
202 ψ = problem.eval_augmented_lagrangian_and_gradient(
203 x_solve, y, Σ, grad_ψ, work_n, work_m);
204 }
205 // Converged
206 else if (task_sv.starts_with(
207 "CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL")) {
209 break;
210 }
211 // Next iteration
212 else if (task_sv.starts_with("NEW_X")) {
213 // Check termination
214 if (check_termination()) {
215 break;
216 } else {
217 auto k = static_cast<unsigned>(num_iter) - 1;
218 bool do_print = params.print_interval != 0 &&
219 k % params.print_interval == 0;
220 // Print info
221 if (std::exchange(did_print, do_print))
222 print_progress_2(q_norm, τ_max, τ_rel, num_free_var);
223 if (do_print)
224 print_progress_1(k, ψ, grad_ψ, proj_grad_norm);
225 // Progress callback
226 do_progress_cb(k, x, ψ, grad_ψ, τ_max, τ_rel, proj_grad_norm,
228 }
229 }
230 // Stop
231 else if (task_sv.starts_with("CONVERGENCE: REL_REDUCTION_OF_F")) {
233 break;
234 } else if (task_sv.starts_with("ERROR: ")) {
236 break;
237 }
238 // Unexpected status
239 else {
241 if (check_termination() || !params.restart_on_error) {
242 break;
243 } else {
244 if (params.print_interval != 0) {
245 auto k = static_cast<unsigned>(num_iter) - 2;
246 if (!std::exchange(did_print, false))
247 print_progress_1(k, ψ, grad_ψ, proj_grad_norm);
248 print_progress_2(q_norm, τ_max, τ_rel, num_free_var);
249 print_error(task_sv);
250 print_progress_n(s.status);
251 }
252 set_task("START"); // and hope for the best ...
253 num_iter_tot += std::max(0, num_iter - 1);
254 lbfgs_skipped_tot += lbfgs_skipped;
256 }
257 }
258 }
259
260 // Print info
261 auto k = static_cast<unsigned>(num_iter) - 1;
262 if (std::exchange(did_print, false))
263 print_progress_2(q_norm, τ_max, τ_rel, num_free_var);
264 else if (params.print_interval != 0)
265 print_progress_1(k, ψ, grad_ψ, proj_grad_norm);
266 // Error reporting
268 print_error(task_sv);
269 if (params.print_interval != 0)
270 print_progress_n(s.status);
271
272 // Progress callback
273 do_progress_cb(k, x, ψ, grad_ψ, τ_max, τ_rel, proj_grad_norm, s.status);
274
275 auto time_elapsed = clock::now() - start_time;
276 s.elapsed_time = duration_cast<nanoseconds>(time_elapsed);
278 static_cast<unsigned>(lbfgs_skipped + lbfgs_skipped_tot);
279 s.iterations = static_cast<unsigned>(num_iter + num_iter_tot);
280
281 // Check final error
282 s.ε = proj_grad_norm;
283 // Update result vectors
286 opts.always_overwrite_results) {
287 auto &ŷ = work_m;
288 s.final_ψ = problem.eval_augmented_lagrangian(x_solve, y, Σ, ŷ);
289 if (err_z.size() > 0)
290 err_z = (ŷ - y).cwiseQuotient(Σ);
291 x = x_solve;
292 y = ŷ;
293 }
294 return s;
295}
296
297} // namespace alpaqa::lbfgsb
std::function< void(const ProgressInfo &)> progress_cb
Stats operator()(const Problem &problem, const SolveOptions &opts, rvec x, rvec y, crvec Σ, rvec err_z)
InnerSolveOptions< config_t > SolveOptions
LBFGSBProgressInfo ProgressInfo
guanaqo::AtomicStopSignal stop_signal
TypeErasedProblem< config_t > Problem
void alpaqa_setulb_c(int n, int m, double *x, const double *l, const double *u, const int *nbd, double &f, double *g, double factr, double pgtol, double *wa, int *iwa, char *task, int iprint, char *csave, bool *lsave, int *isave, double *dsave)
std::chrono::nanoseconds time_progress_callback
std::chrono::nanoseconds elapsed_time
@ ProjGradUnitNorm
∞-norm of the projected gradient with unit step size:
SolverStatus
Exit status of a numerical solver such as ALM or PANOC.
@ Interrupted
Solver was interrupted by the user.
@ MaxTime
Maximum allowed execution time exceeded.
@ Exception
An unexpected exception was thrown.
@ NoProgress
No progress was made in the last iteration.
@ MaxIter
Maximum number of iterations exceeded.
@ Converged
Converged and reached given tolerance.
typename Conf::real_t real_t
Definition config.hpp:86
constexpr const auto NaN
Definition config.hpp:114
typename Conf::index_t index_t
Definition config.hpp:104
typename Conf::length_t length_t
Definition config.hpp:103
constexpr const auto inf
Definition config.hpp:112
typename Conf::rvec rvec
Definition config.hpp:91
typename Conf::crvec crvec
Definition config.hpp:92
typename Conf::vec vec
Definition config.hpp:88