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