alpaqa no-casadi-dep
Nonconvex constrained optimization
Loading...
Searching...
No Matches
qpalm-driver.cpp
Go to the documentation of this file.
1#ifdef ALPAQA_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(
83 "Invalid size for initial_guess_x (expected " + std::to_string(n) +
84 ", but got " + std::to_string(sz) + ")");
85 if (auto sz = problem.initial_guess_y.size(); sz != m)
86 throw std::invalid_argument(
87 "Invalid size for initial_guess_y (expected " + std::to_string(m) +
88 ", but got " + std::to_string(sz) + ")");
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 (expected " +
93 std::to_string(n * 2) + ", but got " + std::to_string(sz) +
94 ")");
95 initial_guess_mult.resize(static_cast<length_t>(qp.m));
96 compress_multipliers_bounds(n, m, qp, problem.initial_guess_w,
97 initial_guess_mult);
98 initial_guess_mult.bottomRows(m) = problem.initial_guess_y;
99 }
100 auto warm_start = [&] {
101 problem.initial_guess_w.size() > 0
102 ? solver.warm_start(problem.initial_guess_x, initial_guess_mult)
103 : solver.warm_start(problem.initial_guess_x, std::nullopt);
104 };
105
106 // Solve the problem
107 auto t0 = std::chrono::steady_clock::now();
108 warm_start();
109 solver.solve();
110 auto t1 = std::chrono::steady_clock::now();
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 auto evals = *problem.evaluations;
129
130 // Results
131 SolverResults results{
132 .status = info.status,
133 .success = info.status_val == QPALM_SOLVED,
134 .evals = evals,
135 .duration = avg_duration,
136 .solver = "QPALM",
137 .h = 0,
138 .δ = info.dua_res_norm,
139 .ε = info.pri_res_norm,
140 .γ = 0,
141 .Σ = 0,
142 .solution = sol_x,
143 .multipliers = sol_y.bottomRows(m),
144 .multipliers_bounds = vec::Zero(n * 2), // see bleow
145 .penalties = vec::Zero(n),
146 .outer_iter = info.iter_out,
147 .inner_iter = info.iter,
148 .extra = {{"dua2_res_norm", info.dua2_res_norm}},
149 };
150 // Expand the multipliers for the bounds constraints again
151 expand_multipliers_bounds(n, m, qp, sol_y, results.multipliers_bounds);
152 return results;
153}
154
155int print_wrap(const char *fmt, ...) {
156 static std::vector<char> buffer(1024);
157 std::va_list args, args2;
158 va_start(args, fmt);
159 va_copy(args2, args);
160 int needed = vsnprintf(buffer.data(), buffer.size(), fmt, args);
161 va_end(args);
162 // Error occurred
163 if (needed < 0) {
164 // ignore and return
165 }
166 // Buffer was too small
167 else if (auto buf_needed = static_cast<size_t>(needed) + 1;
168 buf_needed > buffer.size()) {
169 buffer.resize(buf_needed);
170 va_start(args2, fmt);
171 needed = vsnprintf(buffer.data(), buffer.size(), fmt, args2);
172 va_end(args2);
173 }
174 if (needed >= 0) {
175 assert(qpalm_os);
176 std::string_view out{buffer.data(), static_cast<size_t>(needed)};
177 *qpalm_os << out;
178 }
179 return needed;
180}
181
182int print_wrap_noop(const char *, ...) { return 0; }
183
184auto get_qpalm_settings(Options &opts) {
185 qpalm::Settings settings;
186 settings.eps_abs = 1e-8;
187 settings.eps_rel = 1e-8;
188 set_params(settings, "solver", opts);
189 return settings;
190}
191
192template <class LoadedProblem>
193SharedSolverWrapper make_qpalm_drive_impl(std::string_view direction,
194 Options &opts) {
195 if (!direction.empty())
196 throw std::invalid_argument(
197 "QPALM solver does not support any directions");
198 auto settings = get_qpalm_settings(opts);
199 unsigned N_exp = 0;
200 set_params(N_exp, "num_exp", opts);
201 return std::make_shared<SolverWrapper>(
202 [settings, N_exp](LoadedProblem &problem,
203 std::ostream &os) mutable -> SolverResults {
204 return run_qpalm_solver(problem, settings, os, N_exp);
205 });
206}
207
208} // namespace
209
210SharedSolverWrapper make_qpalm_driver(std::string_view direction,
211 Options &opts) {
212 static constexpr bool valid_config =
213 std::is_same_v<LoadedProblem::config_t, alpaqa::EigenConfigd>;
214 if constexpr (valid_config)
215 return make_qpalm_drive_impl<LoadedProblem>(direction, opts);
216 else
217 throw std::invalid_argument(
218 "QPALM solver only supports double precision");
219}
220
221#else
222
223#include "solver-driver.hpp"
224
226 throw std::invalid_argument(
227 "This version of alpaqa was compiled without QPALM support.");
228}
229
230#endif
#define USING_ALPAQA_CONFIG(Conf)
Definition config.hpp:77
OwningQPALMData build_qpalm_problem(const TypeErasedProblem< EigenConfigd > &problem)
typename Conf::index_t index_t
Definition config.hpp:104
typename Conf::length_t length_t
Definition config.hpp:103
typename Conf::vec vec
Definition config.hpp:88
void set_params(T &t, std::string_view prefix, Options &opts)
Definition options.hpp:128
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:174