alpaqa 1.0.0a13
Nonconvex constrained optimization
Loading...
Searching...
No Matches
qpalm-driver.cpp
Go to the documentation of this file.
1#ifdef WITH_QPALM
2
4#include <qpalm/constants.h>
5
6#include <cstdarg>
7#include <stdexcept>
8#include <string>
9
10#include "results.hpp"
11#include "solver-driver.hpp"
12
13namespace {
14
16
17std::ostream *qpalm_os = nullptr;
18int print_wrap(const char *fmt, ...) LADEL_ATTR_PRINTF_LIKE;
19int print_wrap_noop(const char *, ...) LADEL_ATTR_PRINTF_LIKE;
20
21void compress_multipliers_bounds(length_t n, length_t m,
22 const alpaqa::OwningQPALMData &qp,
23 crvec multipliers_bounds, rvec y) {
24 for (index_t col = 0; col < n; ++col) {
25 // Loop over the constraint matrix
26 auto outer = qp.A->p[col];
27 auto nnz = qp.A->p[col + 1] - outer;
28 if (nnz == 0) // No constraints for this variable
29 continue;
30 auto row = qp.A->i[outer];
31 if (row + m >= y.size()) // Only top rows are bound constraints
32 continue;
33 // Negative multipliers correspond to the lower bound (first half of the
34 // vector), positive multipliers to the upper bound (second half)
35 auto val_lb = multipliers_bounds(col);
36 auto val_ub = multipliers_bounds(col + n);
37 y(row) = val_lb < 0 ? val_lb : val_ub;
38 }
39}
40
41void expand_multipliers_bounds(length_t n, length_t m,
42 const alpaqa::OwningQPALMData &qp, crvec sol_y,
43 rvec multipliers_bounds) {
44 for (index_t col = 0; col < n; ++col) {
45 // Loop over the constraint matrix
46 auto outer = qp.A->p[col];
47 auto nnz = qp.A->p[col + 1] - outer;
48 if (nnz == 0) // No constraints for this variable
49 continue;
50 auto row = qp.A->i[outer];
51 if (row + m >= sol_y.size()) // Only top rows are bound constraints
52 continue;
53 // Negative multipliers correspond to the lower bound (first half of the
54 // vector), positive multipliers to the upper bound (second half)
55 auto value = sol_y(row);
56 index_t offset = value > 0 ? n : 0;
57 multipliers_bounds(col + offset) = value;
58 }
59}
60
61SolverResults run_qpalm_solver(auto &problem, const qpalm::Settings &settings,
62 std::ostream &os, unsigned N_exp) {
63
64 // Set up output stream
65 qpalm_os = &os;
66 auto old_print = ladel_set_print_config_printf(&print_wrap);
67 struct PrintRestorer {
68 printf_sig *print;
69 ~PrintRestorer() { ladel_set_print_config_printf(print); }
70 } restore_print{old_print};
71
72 // Dimensions
73 length_t n = problem.problem.get_n(), m = problem.problem.get_m();
74
75 // Adapt problem
76 auto qp = alpaqa::build_qpalm_problem(problem.problem);
77 qpalm::Solver solver{&qp, settings};
78
79 // Initial guess
80 vec initial_guess_mult;
81 if (auto sz = problem.initial_guess_x.size(); sz != n)
82 throw std::invalid_argument("Invalid size for initial_guess_x (got " +
83 std::to_string(sz) + ", expected " +
84 std::to_string(n) + ")");
85 if (auto sz = problem.initial_guess_y.size(); sz != m)
86 throw std::invalid_argument("Invalid size for initial_guess_y (got " +
87 std::to_string(sz) + ", expected " +
88 std::to_string(m) + ")");
89 if (auto sz = problem.initial_guess_w.size(); sz > 0) {
90 if (sz != n * 2)
91 throw std::invalid_argument(
92 "Invalid size for initial_guess_w (got " + std::to_string(sz) +
93 ", expected " + std::to_string(n * 2) + ")");
94 initial_guess_mult.resize(static_cast<length_t>(qp.m));
95 compress_multipliers_bounds(n, m, qp, problem.initial_guess_w,
96 initial_guess_mult);
97 initial_guess_mult.bottomRows(m) = problem.initial_guess_y;
98 }
99 auto warm_start = [&] {
100 problem.initial_guess_w.size() > 0
101 ? solver.warm_start(problem.initial_guess_x, initial_guess_mult)
102 : solver.warm_start(problem.initial_guess_x, std::nullopt);
103 };
104
105 // Solve the problem
106 auto t0 = std::chrono::steady_clock::now();
107 warm_start();
108 solver.solve();
109 auto t1 = std::chrono::steady_clock::now();
110 auto evals = *problem.evaluations;
111 auto info = solver.get_info();
112 vec sol_x = solver.get_solution().x, sol_y = solver.get_solution().y;
113
114 // Solve the problems again to average runtimes
115 using ns = std::chrono::nanoseconds;
116 auto avg_duration = duration_cast<ns>(t1 - t0);
117 ladel_set_print_config_printf(&print_wrap_noop);
118 os.setstate(std::ios_base::badbit);
119 for (unsigned i = 0; i < N_exp; ++i) {
120 auto t0 = std::chrono::steady_clock::now();
121 warm_start();
122 solver.solve();
123 auto t1 = std::chrono::steady_clock::now();
124 avg_duration += duration_cast<ns>(t1 - t0);
125 }
126 os.clear();
127 avg_duration /= (N_exp + 1);
128
129 // Results
130 SolverResults results{
131 .status = info.status,
132 .success = info.status_val == QPALM_SOLVED,
133 .evals = evals,
134 .duration = avg_duration,
135 .solver = "QPALM",
136 .h = 0,
137 .δ = info.dua_res_norm,
138 .ε = info.pri_res_norm,
139 .γ = 0,
140 .Σ = 0,
141 .solution = sol_x,
142 .multipliers = sol_y.bottomRows(m),
143 .multipliers_bounds = vec::Zero(n * 2), // see bleow
144 .penalties = vec::Zero(n),
145 .outer_iter = info.iter_out,
146 .inner_iter = info.iter,
147 .extra = {{"dua2_res_norm", info.dua2_res_norm}},
148 };
149 // Expand the multipliers for the bounds constraints again
150 expand_multipliers_bounds(n, m, qp, sol_y, results.multipliers_bounds);
151 return results;
152}
153
154int print_wrap(const char *fmt, ...) {
155 static std::vector<char> buffer(1024);
156 std::va_list args, args2;
157 va_start(args, fmt);
158 va_copy(args2, args);
159 int needed = vsnprintf(buffer.data(), buffer.size(), fmt, args);
160 va_end(args);
161 // Error occurred
162 if (needed < 0) {
163 // ignore and return
164 }
165 // Buffer was too small
166 else if (auto buf_needed = static_cast<size_t>(needed) + 1;
167 buf_needed > buffer.size()) {
168 buffer.resize(buf_needed);
169 va_start(args2, fmt);
170 needed = vsnprintf(buffer.data(), buffer.size(), fmt, args2);
171 va_end(args2);
172 }
173 if (needed >= 0) {
174 assert(qpalm_os);
175 std::string_view out{buffer.data(), static_cast<size_t>(needed)};
176 *qpalm_os << out;
177 }
178 return needed;
179}
180
181int print_wrap_noop(const char *, ...) { return 0; }
182
183auto get_qpalm_settings(Options &opts) {
184 qpalm::Settings settings;
185 settings.eps_abs = 1e-8;
186 settings.eps_rel = 1e-8;
187 set_params(settings, "solver", opts);
188 return settings;
189}
190
191template <class LoadedProblem>
192SharedSolverWrapper make_qpalm_drive_impl(std::string_view direction,
193 Options &opts) {
194 if (!direction.empty())
195 throw std::invalid_argument(
196 "QPALM solver does not support any directions");
197 auto settings = get_qpalm_settings(opts);
198 unsigned N_exp = 0;
199 set_params(N_exp, "num_exp", opts);
200 return std::make_shared<SolverWrapper>(
201 [settings, N_exp](LoadedProblem &problem,
202 std::ostream &os) mutable -> SolverResults {
203 return run_qpalm_solver(problem, settings, os, N_exp);
204 });
205}
206
207} // namespace
208
209SharedSolverWrapper make_qpalm_driver(std::string_view direction,
210 Options &opts) {
211 static constexpr bool valid_config =
212 std::is_same_v<LoadedProblem::config_t, alpaqa::EigenConfigd>;
213 if constexpr (valid_config)
214 return make_qpalm_drive_impl<LoadedProblem>(direction, opts);
215 else
216 throw std::invalid_argument(
217 "QPALM solver only supports double precision");
218}
219
220#else
221
222#include "solver-driver.hpp"
223
225 throw std::invalid_argument(
226 "This version of alpaqa was compiled without QPALM support.");
227}
228
229#endif
#define USING_ALPAQA_CONFIG(Conf)
Definition config.hpp:56
OwningQPALMData build_qpalm_problem(const TypeErasedProblem< EigenConfigd > &problem)
typename Conf::index_t index_t
Definition config.hpp:77
typename Conf::length_t length_t
Definition config.hpp:76
typename Conf::vec vec
Definition config.hpp:66
decltype(auto) set_params(T &t, std::string_view prefix, Options &opts)
Definition options.hpp:27
SharedSolverWrapper make_qpalm_driver(std::string_view, Options &)
std::shared_ptr< SolverWrapper > SharedSolverWrapper
std::string status
Definition results.hpp:25
Double-precision double configuration.
Definition config.hpp:135