alpaqa 1.0.0a8
Nonconvex constrained optimization
Loading...
Searching...
No Matches
CasADiProblem.tpp
Go to the documentation of this file.
1#pragma once
2
8
9#include <casadi/core/external.hpp>
10
11#include <algorithm>
12#include <filesystem>
13#include <fstream>
14#include <memory>
15#include <optional>
16#include <stdexcept>
17#include <type_traits>
18
19namespace alpaqa {
20
21namespace fs = std::filesystem;
22
23namespace casadi_loader {
24
25template <Config Conf>
30 // CasADiFunctionEvaluator<6, 1> grad_ψ;
32 struct ConstrFun {
36 };
37 std::optional<ConstrFun> constr = std::nullopt;
38 std::optional<CasADiFunctionEvaluator<Conf, 5, 1>> hess_L_prod =
39 std::nullopt;
40 std::optional<CasADiFunctionEvaluator<Conf, 4, 1>> hess_L = std::nullopt;
41 std::optional<CasADiFunctionEvaluator<Conf, 8, 1>> hess_ψ_prod =
42 std::nullopt;
43 std::optional<CasADiFunctionEvaluator<Conf, 7, 1>> hess_ψ = std::nullopt;
44 std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> jac_g = std::nullopt;
47};
48
49} // namespace casadi_loader
50
51namespace detail {
52
53template <Config Conf>
54auto casadi_to_index(casadi_int i) -> index_t<Conf> {
55 return static_cast<index_t<Conf>>(i);
56}
57} // namespace detail
58
59template <Config Conf>
60CasADiProblem<Conf>::CasADiProblem(const std::string &so_name)
61 : BoxConstrProblem<Conf>{0, 0} {
62 using namespace casadi_loader;
63 length_t n = 0, m = 0, p = 0;
64 auto load_g_unknown_dims =
65 [&]() -> std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> {
66 casadi::Function gfun = casadi::external("g", so_name);
67 using namespace std::literals::string_literals;
68 if (gfun.n_in() != 2)
69 throw std::invalid_argument(
70 "Invalid number of input arguments: got "s +
71 std::to_string(gfun.n_in()) + ", should be 2.");
72 if (gfun.n_out() > 1)
73 throw std::invalid_argument(
74 "Invalid number of output arguments: got "s +
75 std::to_string(gfun.n_in()) + ", should be 0 or 1.");
76 if (gfun.size2_in(0) != 1)
77 throw std::invalid_argument(
78 "First input argument should be a column vector.");
79 if (gfun.size2_in(1) != 1)
80 throw std::invalid_argument(
81 "Second input argument should be a column vector.");
82 if (gfun.n_out() == 1 && gfun.size2_out(0) != 1)
83 throw std::invalid_argument(
84 "First output argument should be a column vector.");
85 n = static_cast<length_t>(gfun.size1_in(0));
86 if (gfun.n_out() == 1)
87 m = static_cast<length_t>(gfun.size1_out(0));
88 p = static_cast<length_t>(gfun.size1_in(1));
89 if (gfun.n_out() == 0) {
90 if (m != 0)
91 throw std::invalid_argument(
92 "Function g has no outputs but m != 0");
93 return std::nullopt;
94 }
95 CasADiFunctionEvaluator<Conf, 2, 1> g{std::move(gfun)};
96 g.validate_dimensions({dim(n, 1), dim(p, 1)}, {dim(m, 1)});
97 return std::make_optional(std::move(g));
98 };
99
100 auto g = wrap_load(so_name, "g", load_g_unknown_dims);
101
102 this->n = n;
103 this->m = m;
104 this->param = vec::Constant(p, alpaqa::NaN<Conf>);
105 this->C = Box<config_t>{n};
106 this->D = Box<config_t>{m};
107
108 impl = std::make_unique<CasADiFunctionsWithParam<Conf>>(
109 CasADiFunctionsWithParam<Conf>{
110 .f = wrapped_load<CasADiFunctionEvaluator<Conf, 2, 1>>( //
111 so_name, "f", dims(n, p), dims(1)),
112 .f_grad_f = wrapped_load<CasADiFunctionEvaluator<Conf, 2, 2>>( //
113 so_name, "f_grad_f", dims(n, p), dims(1, n)),
114 // .grad_ψ = wrapped_load<CasADiFunctionEvaluator<6, 1>>( //
115 // so_name, "grad_psi", dims(n, p, m, m, m, m), dims(n)),
116 .ψ_grad_ψ = wrapped_load<CasADiFunctionEvaluator<Conf, 6, 2>>( //
117 so_name, "psi_grad_psi", dims(n, p, m, m, m, m), dims(1, n)),
118 });
119
120 if (g)
121 impl->constr = {
122 std::move(*g),
123 wrapped_load<CasADiFunctionEvaluator<Conf, 3, 1>>( //
124 so_name, "grad_L", dims(n, p, m), dims(n)),
125 wrapped_load<CasADiFunctionEvaluator<Conf, 6, 2>>( //
126 so_name, "psi", dims(n, p, m, m, m, m), dims(1, m)),
127 };
128
129 impl->hess_L_prod = try_load<CasADiFunctionEvaluator<Conf, 5, 1>>( //
130 so_name, "hess_L_prod", dims(n, p, m, 1, n), dims(n));
131 impl->hess_L = try_load<CasADiFunctionEvaluator<Conf, 4, 1>>( //
132 so_name, "hess_L", dims(n, p, m, 1), dims(dim(n, n)));
133 impl->hess_ψ_prod = try_load<CasADiFunctionEvaluator<Conf, 8, 1>>( //
134 so_name, "hess_psi_prod", dims(n, p, m, m, 1, m, m, n), dims(n));
135 impl->hess_ψ = try_load<CasADiFunctionEvaluator<Conf, 7, 1>>( //
136 so_name, "hess_psi", dims(n, p, m, m, 1, m, m), dims(dim(n, n)));
137 impl->jac_g = try_load<CasADiFunctionEvaluator<Conf, 2, 1>>( //
138 so_name, "jacobian_g", dims(n, p), dims(dim(m, n)));
139
140 auto data_filepath = fs::path{so_name}.replace_extension("csv");
141 if (fs::exists(data_filepath))
142 load_numerical_data(data_filepath);
143}
144
145template <Config Conf>
147 const std::filesystem::path &filepath, char sep) {
148 // Open data file
149 std::ifstream data_file{filepath};
150 if (!data_file)
151 throw std::runtime_error("Unable to open data file \"" +
152 filepath.string() + '"');
153
154 // Helper function for reading single line of (float) data
155 index_t line = 0;
156 auto wrap_data_load = [&](std::string_view name, auto &v, bool fixed_size) {
157 try {
158 ++line;
159 if (data_file.peek() == '\n') // Ignore empty lines
160 return static_cast<void>(data_file.get());
161 if (fixed_size) {
162 csv::read_row(data_file, v, sep);
163 } else { // Dynamic size
164 auto s = csv::read_row_std_vector<real_t>(data_file, sep);
165 v = cmvec{s.data(), static_cast<index_t>(s.size())};
166 }
167 } catch (csv::read_error &e) {
168 // Transform any errors in something more readable
169 throw std::runtime_error("Unable to read " + std::string(name) +
170 " from data file \"" + filepath.string() +
171 ':' + std::to_string(line) +
172 "\": " + e.what());
173 }
174 };
175 // Helper function for reading a single value
176 auto read_single = [&](std::string_view name, auto &v) {
177 data_file >> v;
178 if (!data_file)
179 throw std::runtime_error("Unable to read " + std::string(name) +
180 " from data file \"" + filepath.string() +
181 ':' + std::to_string(line) + '"');
182 };
183 // Read the bounds, parameter value, and regularization
184 wrap_data_load("C.lowerbound", this->C.lowerbound, true);
185 wrap_data_load("C.upperbound", this->C.upperbound, true);
186 wrap_data_load("D.lowerbound", this->D.lowerbound, true);
187 wrap_data_load("D.upperbound", this->D.upperbound, true);
188 wrap_data_load("param", this->param, true);
189 wrap_data_load("l1_reg", this->l1_reg, false);
190 // Penalty/ALM split is a single integer
191 read_single("penalty_alm_split", this->penalty_alm_split);
192}
193
194template <Config Conf>
196template <Config Conf>
199template <Config Conf>
201template <Config Conf>
202CasADiProblem<Conf> &
203CasADiProblem<Conf>::operator=(CasADiProblem &&) noexcept = default;
204
205template <Config Conf>
206CasADiProblem<Conf>::~CasADiProblem() = default;
207
208template <Config Conf>
209auto CasADiProblem<Conf>::eval_f(crvec x) const -> real_t {
210 real_t f;
211 impl->f({x.data(), param.data()}, {&f});
212 return f;
213}
214
215template <Config Conf>
217 real_t f;
218 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
219}
220
221template <Config Conf>
223 real_t f;
224 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
225 return f;
226}
227
228template <Config Conf>
230 if (impl->constr)
231 impl->constr->g({x.data(), param.data()}, {g.data()});
232}
233
234template <Config Conf>
236 throw not_implemented_error("CasADiProblem::eval_grad_g_prod"); // TODO
237}
238
239template <Config Conf>
241 rvec, rvec) const {
242#if 0
243 impl->grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
244 this->D.lowerbound.data(), this->D.upperbound.data()},
245 {grad_ψ.data()});
246#else
247 // This seems to be faster than having a specialized function. Possibly
248 // cache-related?
249 real_t ψ;
250 impl->ψ_grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
251 this->D.lowerbound.data(), this->D.upperbound.data()},
252 {&ψ, grad_ψ.data()});
253#endif
254}
255
256template <Config Conf>
259 rvec) const {
260 real_t ψ;
261 impl->ψ_grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
262 this->D.lowerbound.data(), this->D.upperbound.data()},
263 {&ψ, grad_ψ.data()});
264 return ψ;
265}
266
267template <Config Conf>
269 rvec) const {
270 if (impl->constr)
271 impl->constr->grad_L({x.data(), param.data(), y.data()},
272 {grad_L.data()});
273 else
274 eval_f_grad_f(x, grad_L);
275}
276
277template <Config Conf>
280 real_t ψ;
281 if (impl->constr)
282 impl->constr->ψ({x.data(), param.data(), y.data(), Σ.data(),
283 this->D.lowerbound.data(), this->D.upperbound.data()},
284 {&ψ, ŷ.data()});
285 else
286 impl->f({x.data(), param.data()}, {&ψ});
287 return ψ;
288}
289
290template <Config Conf>
292 throw not_implemented_error("CasADiProblem::eval_grad_gi"); // TODO
293}
294
295template <Config Conf>
297 if (!impl->jac_g.has_value())
298 return -1;
299 auto &&sparsity = impl->jac_g->fun.sparsity_out(0);
300 return sparsity.is_dense() ? -1 : static_cast<length_t>(sparsity.nnz());
301}
302
303template <Config Conf>
305 rindexvec outer_ptr, rvec J_values) const {
306 assert(impl->jac_g.has_value());
307 if (J_values.size() > 0) {
308 (*impl->jac_g)({x.data(), param.data()}, {J_values.data()});
309 } else {
310 auto &&sparsity = impl->jac_g->fun.sparsity_out(0);
312 if (!sparsity.is_dense()) {
313 std::transform(sparsity.row(), sparsity.row() + sparsity.nnz(),
314 inner_idx.begin(), casadi_to_index<config_t>);
315 std::transform(sparsity.colind(),
316 sparsity.colind() + this->get_n() + 1,
317 outer_ptr.begin(), casadi_to_index<config_t>);
318 }
319 }
320}
321
322template <Config Conf>
324 crvec v, rvec Hv) const {
325 assert(impl->hess_L_prod.has_value());
326 (*impl->hess_L_prod)({x.data(), param.data(), y.data(), &scale, v.data()},
327 {Hv.data()});
328}
329
330template <Config Conf>
332 if (!impl->hess_L.has_value())
333 return -1;
334 auto &&sparsity = impl->hess_L->fun.sparsity_out(0);
335 return sparsity.is_dense() ? -1 : static_cast<length_t>(sparsity.nnz());
336}
337
338template <Config Conf>
340 rindexvec inner_idx, rindexvec outer_ptr,
341 rvec H_values) const {
342 assert(impl->hess_L.has_value());
343 if (H_values.size() > 0) {
344 (*impl->hess_L)({x.data(), param.data(), y.data(), &scale},
345 {H_values.data()});
346 } else {
347 auto &&sparsity = impl->hess_L->fun.sparsity_out(0);
349 if (!sparsity.is_dense()) {
350 std::transform(sparsity.row(), sparsity.row() + sparsity.nnz(),
351 inner_idx.begin(), casadi_to_index<config_t>);
352 std::transform(sparsity.colind(),
353 sparsity.colind() + this->get_n() + 1,
354 outer_ptr.begin(), casadi_to_index<config_t>);
355 }
356 }
357}
358
359template <Config Conf>
361 real_t scale, crvec v,
362 rvec Hv) const {
363 assert(impl->hess_ψ_prod.has_value());
364 (*impl->hess_ψ_prod)({x.data(), param.data(), y.data(), Σ.data(), &scale,
365 this->D.lowerbound.data(), this->D.upperbound.data(),
366 v.data()},
367 {Hv.data()});
368}
369
370template <Config Conf>
372 if (!impl->hess_ψ.has_value())
373 return 0;
374 auto &&sparsity = impl->hess_ψ->fun.sparsity_out(0);
375 return sparsity.is_dense() ? 0 : static_cast<length_t>(sparsity.nnz());
376}
377
378template <Config Conf>
380 rindexvec inner_idx, rindexvec outer_ptr,
381 rvec H_values) const {
382 assert(impl->hess_ψ.has_value());
383 if (H_values.size() > 0) {
384 (*impl->hess_ψ)({x.data(), param.data(), y.data(), Σ.data(), &scale,
385 this->D.lowerbound.data(), this->D.upperbound.data()},
386 {H_values.data()});
387 } else {
388 auto &&sparsity = impl->hess_ψ->fun.sparsity_out(0);
390 if (!sparsity.is_dense()) {
391 std::transform(sparsity.row(), sparsity.row() + sparsity.nnz(),
392 inner_idx.begin(), casadi_to_index<config_t>);
393 std::transform(sparsity.colind(),
394 sparsity.colind() + this->get_n() + 1,
395 outer_ptr.begin(), casadi_to_index<config_t>);
396 }
397 }
398}
399
400template <Config Conf>
402 return false; // TODO
403}
404template <Config Conf>
406 return impl->jac_g.has_value();
407}
408template <Config Conf>
410 return impl->hess_L_prod.has_value();
411}
412template <Config Conf>
414 return impl->hess_L.has_value();
415}
416template <Config Conf>
418 return impl->hess_ψ_prod.has_value();
419}
420template <Config Conf>
422 return impl->hess_ψ.has_value();
423}
424
425} // namespace alpaqa
Implements common problem functions for minimization problems with box constraints.
Problem definition for a CasADi problem, loaded from a DLL.
bool provides_eval_hess_L() const
void eval_g(crvec x, rvec g) const
void eval_hess_ψ(crvec x, crvec y, crvec Σ, real_t scale, rindexvec inner_idx, rindexvec outer_ptr, rvec H_values) const
bool provides_eval_hess_ψ_prod() const
bool provides_eval_jac_g() const
void load_numerical_data(const std::filesystem::path &filepath, char sep=',')
Load the numerical problem data (bounds and parameters) from a CSV file.
CasADiProblem & operator=(const CasADiProblem &)
void eval_grad_L(crvec x, crvec y, rvec grad_L, rvec work_n) const
real_t eval_ψ(crvec x, crvec y, crvec Σ, rvec ŷ) const
void eval_hess_L(crvec x, crvec y, real_t scale, rindexvec inner_idx, rindexvec outer_ptr, rvec H_values) const
CasADiProblem(const std::string &filename)
Load a problem generated by CasADi (with parameters).
bool provides_eval_hess_L_prod() const
real_t eval_f_grad_f(crvec x, rvec grad_fx) const
void eval_grad_g_prod(crvec x, crvec y, rvec grad_gxy) const
void eval_hess_L_prod(crvec x, crvec y, real_t scale, crvec v, rvec Hv) const
bool provides_eval_grad_gi() const
void eval_grad_f(crvec x, rvec grad_fx) const
real_t eval_ψ_grad_ψ(crvec x, crvec y, crvec Σ, rvec grad_ψ, rvec work_n, rvec work_m) const
void eval_grad_gi(crvec x, index_t i, rvec grad_i) const
length_t get_jac_g_num_nonzeros() const
bool provides_eval_hess_ψ() const
void eval_jac_g(crvec x, rindexvec inner_idx, rindexvec outer_ptr, rvec J_values) const
void eval_grad_ψ(crvec x, crvec y, crvec Σ, rvec grad_ψ, rvec work_n, rvec work_m) const
void eval_hess_ψ_prod(crvec x, crvec y, crvec Σ, real_t scale, crvec v, rvec Hv) const
length_t get_hess_ψ_num_nonzeros() const
length_t get_hess_L_num_nonzeros() const
Class for evaluating CasADi functions, allocating the necessary workspace storage in advance for allo...
#define USING_ALPAQA_CONFIG(Conf)
Definition: config.hpp:42
std::optional< CasADiFunctionEvaluator< Conf, 5, 1 > > hess_L_prod
std::optional< CasADiFunctionEvaluator< Conf, 7, 1 > > hess_ψ
constexpr auto dims(auto... a)
std::optional< CasADiFunctionEvaluator< Conf, 4, 1 > > hess_L
std::pair< casadi_int, casadi_int > dim
CasADiFunctionEvaluator< Conf, 2, 1 > f
auto wrap_load(const std::string &so_name, const char *name, F f)
std::optional< CasADiFunctionEvaluator< Conf, 8, 1 > > hess_ψ_prod
CasADiFunctionEvaluator< Conf, 6, 2 > ψ_grad_ψ
std::optional< CasADiFunctionEvaluator< Conf, 2, 1 > > jac_g
CasADiFunctionEvaluator< Conf, 2, 2 > f_grad_f
void read_row(std::istream &is, Eigen::Ref< Eigen::VectorX< float > > v, char sep)
Definition: csv.hpp:34
auto casadi_to_index(casadi_int i) -> index_t< Conf >
typename Conf::indexvec indexvec
Definition: config.hpp:64
typename Conf::real_t real_t
Definition: config.hpp:51
typename Conf::rindexvec rindexvec
Definition: config.hpp:65
typename Conf::index_t index_t
Definition: config.hpp:63
typename Conf::length_t length_t
Definition: config.hpp:62
typename Conf::cmvec cmvec
Definition: config.hpp:54
typename Conf::rvec rvec
Definition: config.hpp:55
typename Conf::crvec crvec
Definition: config.hpp:56