alpaqa cmake-targets
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 <cassert>
13#include <filesystem>
14#include <fstream>
15#include <memory>
16#include <optional>
17#include <stdexcept>
18
19namespace alpaqa {
20
21namespace fs = std::filesystem;
22
23namespace casadi_loader {
24
25template <Config Conf>
31 std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> g = std::nullopt;
32 std::optional<CasADiFunctionEvaluator<Conf, 3, 1>> grad_g_prod =
33 std::nullopt;
34 std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> jac_g = std::nullopt;
35 std::optional<CasADiFunctionEvaluator<Conf, 3, 1>> grad_L = std::nullopt;
36 std::optional<CasADiFunctionEvaluator<Conf, 5, 1>> hess_L_prod =
37 std::nullopt;
38 std::optional<CasADiFunctionEvaluator<Conf, 4, 1>> hess_L = std::nullopt;
39 std::optional<CasADiFunctionEvaluator<Conf, 6, 2>> ψ = std::nullopt;
40 std::optional<CasADiFunctionEvaluator<Conf, 6, 2>> ψ_grad_ψ = 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
45 template <class Loader>
46 requires requires(Loader &&loader, const char *name) {
47 { loader(name) } -> std::same_as<casadi::Function>;
48 { loader.format_name(name) } -> std::same_as<std::string>;
49 }
50 static std::unique_ptr<CasADiFunctionsWithParam> load(Loader &&loader) {
51 length_t n = 0, m = 0, p = 0;
52 auto load_g =
53 [&]() -> std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> {
54 casadi::Function gfun = loader("g");
55 using namespace std::literals::string_literals;
56 if (gfun.n_in() != 2)
58 "Invalid number of input arguments: got "s +
59 std::to_string(gfun.n_in()) + ", should be 2.");
60 if (gfun.n_out() > 1)
62 "Invalid number of output arguments: got "s +
63 std::to_string(gfun.n_in()) + ", should be 0 or 1.");
64 if (gfun.size2_in(0) != 1)
66 "First input argument should be a column vector.");
67 if (gfun.size2_in(1) != 1)
69 "Second input argument should be a column vector.");
70 if (gfun.n_out() == 1 && gfun.size2_out(0) != 1)
72 "First output argument should be a column vector.");
73 n = static_cast<length_t>(gfun.size1_in(0));
74 if (gfun.n_out() == 1)
75 m = static_cast<length_t>(gfun.size1_out(0));
76 p = static_cast<length_t>(gfun.size1_in(1));
77 if (gfun.n_out() == 0) {
78 if (m != 0)
80 "Function g has no outputs but m != 0");
81 return std::nullopt;
82 }
84 g.validate_dimensions({dim(n, 1), dim(p, 1)}, {dim(m, 1)});
85 return std::make_optional(std::move(g));
86 };
87
88 auto g = wrap_load(loader, "g", load_g);
89
90 return std::make_unique<CasADiFunctionsWithParam>(
92 .n = n,
93 .m = m,
94 .p = p,
96 loader, "f", dims(n, p), dims(1)),
98 loader, "f_grad_f", dims(n, p), dims(1, n)),
99 .g = std::move(g),
101 loader, "grad_g_prod", dims(n, p, m), dims(n)),
103 loader, "jacobian_g", dims(n, p), dims(dim(m, n))),
105 loader, "grad_L", dims(n, p, m), dims(n)),
107 loader, "hess_L_prod", dims(n, p, m, 1, n), dims(n)),
109 loader, "hess_L", dims(n, p, m, 1), dims(dim(n, n))),
111 loader, "psi", dims(n, p, m, m, m, m), dims(1, m)),
113 loader, "psi_grad_psi", dims(n, p, m, m, m, m), dims(1, n)),
115 loader, "hess_psi_prod", dims(n, p, m, m, 1, m, m, n),
116 dims(n)),
118 loader, "hess_psi", dims(n, p, m, m, 1, m, m),
119 dims(dim(n, n))),
120 });
121 }
122};
123
124} // namespace casadi_loader
125
126namespace detail {
127
128template <Config Conf>
130 return static_cast<index_t<Conf>>(i);
131}
132} // namespace detail
133
134template <Config Conf>
136 : BoxConstrProblem<Conf>{0, 0} {
137
138 struct {
139 const std::string &filename;
140 auto operator()(const std::string &name) const {
141 return casadi::external(name, filename);
142 }
143 auto format_name(const std::string &name) const {
144 return filename + ':' + name;
145 }
146 } loader{filename};
148
149 this->n = impl->n;
150 this->m = impl->m;
151 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
152 this->C = Box<config_t>{impl->n};
153 this->D = Box<config_t>{impl->m};
154
155 auto data_filepath = fs::path{filename}.replace_extension("csv");
156 if (fs::exists(data_filepath))
157 load_numerical_data(data_filepath);
158}
159
160template <Config Conf>
162 : BoxConstrProblem<Conf>{0, 0} {
163
164 struct {
165 const SerializedCasADiFunctions &functions;
166 auto operator()(const std::string &name) const {
167 return casadi::Function::deserialize(functions.functions.at(name));
168 }
169 auto format_name(const std::string &name) const {
170 return "SerializedCasADiFunctions['" + name + "']";
171 }
172 } loader{functions};
174
175 this->n = impl->n;
176 this->m = impl->m;
177 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
178 this->C = Box<config_t>{impl->n};
179 this->D = Box<config_t>{impl->m};
180}
181
182template <Config Conf>
184 : BoxConstrProblem<Conf>{0, 0} {
185
186 struct {
187 const CasADiFunctions &functions;
188 auto operator()(const std::string &name) const {
189 return functions.functions.at(name);
190 }
191 auto format_name(const std::string &name) const {
192 return "CasADiFunctions['" + name + "']";
193 }
194 } loader{functions};
196
197 this->n = impl->n;
198 this->m = impl->m;
199 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
200 this->C = Box<config_t>{impl->n};
201 this->D = Box<config_t>{impl->m};
202}
203
204template <Config Conf>
206 const std::filesystem::path &filepath, char sep) {
207 // Open data file
208 std::ifstream data_file{filepath};
209 if (!data_file)
210 throw std::runtime_error("Unable to open data file \"" +
211 filepath.string() + '"');
212
213 // Helper function for reading single line of (float) data
214 index_t line = 0;
215 auto wrap_data_load = [&](std::string_view name, auto &v, bool fixed_size) {
216 try {
217 ++line;
218 if (data_file.peek() == '\n') // Ignore empty lines
219 return static_cast<void>(data_file.get());
220 if (fixed_size) {
222 } else { // Dynamic size
223 auto s = csv::read_row_std_vector<real_t>(data_file, sep);
224 v = cmvec{s.data(), static_cast<index_t>(s.size())};
225 }
226 } catch (csv::read_error &e) {
227 // Transform any errors in something more readable
228 throw std::runtime_error("Unable to read " + std::string(name) +
229 " from data file \"" + filepath.string() +
230 ':' + std::to_string(line) +
231 "\": " + e.what());
232 }
233 };
234 // Helper function for reading a single value
235 auto read_single = [&](std::string_view name, auto &v) {
236 data_file >> v;
237 if (!data_file)
238 throw std::runtime_error("Unable to read " + std::string(name) +
239 " from data file \"" + filepath.string() +
240 ':' + std::to_string(line) + '"');
241 };
242 // Read the bounds, parameter value, and regularization
243 wrap_data_load("C.lowerbound", this->C.lowerbound, true);
244 wrap_data_load("C.upperbound", this->C.upperbound, true);
245 wrap_data_load("D.lowerbound", this->D.lowerbound, true);
246 wrap_data_load("D.upperbound", this->D.upperbound, true);
247 wrap_data_load("param", this->param, true);
248 wrap_data_load("l1_reg", this->l1_reg, false);
249 // Penalty/ALM split is a single integer
250 read_single("penalty_alm_split", this->penalty_alm_split);
251}
252
253template <Config Conf>
255template <Config Conf>
258template <Config Conf>
263
266
269 real_t f;
270 impl->f({x.data(), param.data()}, {&f});
271 return f;
272}
273
274template <Config Conf>
276 real_t f;
277 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
278}
279
280template <Config Conf>
282 real_t f;
283 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
284 return f;
285}
286
287template <Config Conf>
289 if (impl->m == 0)
290 return;
291 if (impl->g)
292 (*impl->g)({x.data(), param.data()}, {g.data()});
293 else
294 throw not_implemented_error("CasADiProblem::eval_g");
295}
296
297template <Config Conf>
299 if (impl->m == 0) {
300 gxy.setZero();
301 return;
302 }
303 if (impl->grad_g_prod)
304 (*impl->grad_g_prod)({x.data(), param.data(), y.data()}, {gxy.data()});
305 else
306 throw not_implemented_error("CasADiProblem::eval_grad_g_prod"); // TODO
307}
308
309template <Config Conf>
311 rvec work_n, rvec work_m) const {
312#if 0
313 impl->grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
314 this->D.lowerbound.data(), this->D.upperbound.data()},
315 {grad_ψ.data()});
316#else
317 // This seems to be faster than having a specialized function. Possibly
318 // cache-related?
319 eval_ψ_grad_ψ(x, y, Σ, grad_ψ, work_n, work_m);
320#endif
321}
322
323template <Config Conf>
326 rvec) const {
327 if (!impl->ψ_grad_ψ)
328 throw std::logic_error("CasADiProblem::eval_ψ_grad_ψ");
329 real_t ψ;
330 (*impl->ψ_grad_ψ)({x.data(), param.data(), y.data(), Σ.data(),
331 this->D.lowerbound.data(), this->D.upperbound.data()},
332 {&ψ, grad_ψ.data()});
333 return ψ;
334}
335
336template <Config Conf>
338 rvec) const {
339 if (!impl->grad_L)
340 throw std::logic_error("CasADiProblem::eval_grad_L");
341 (*impl->grad_L)({x.data(), param.data(), y.data()}, {grad_L.data()});
342}
343
344template <Config Conf>
347 if (!impl->ψ)
348 throw std::logic_error("CasADiProblem::eval_ψ");
349 real_t ψ;
350 (*impl->ψ)({x.data(), param.data(), y.data(), Σ.data(),
351 this->D.lowerbound.data(), this->D.upperbound.data()},
352 {&ψ, ŷ.data()});
353 return ψ;
354}
355
356template <Config Conf>
358 throw not_implemented_error("CasADiProblem::eval_grad_gi"); // TODO
359}
360
361template <Config Conf>
365 using map_t = typename SparseCSC::index_vector_map_t;
366 return SparseCSC{
367 .rows = static_cast<index_t>(sp.size1()),
368 .cols = static_cast<index_t>(sp.size2()),
369 .symmetry = symmetry,
370 .inner_idx = map_t{sp.row(), static_cast<index_t>(sp.nnz())},
371 .outer_ptr = map_t{sp.colind(), static_cast<index_t>(sp.size2()) + 1},
372 .order = SparseCSC::SortedRows,
373 };
374}
375
376template <Config Conf>
379 .rows = this->m,
380 .cols = this->n,
382 };
383 if (!impl->jac_g.has_value())
384 return dense;
385 const auto &sp = impl->jac_g->fun.sparsity_out(0); // Reference!
386 return sp.is_dense()
387 ? Sparsity{dense}
389}
390
391template <Config Conf>
393 if (!impl->jac_g)
394 throw std::logic_error("CasADiProblem::eval_jac_g");
395 (*impl->jac_g)({x.data(), param.data()}, {J_values.data()});
396}
397
398template <Config Conf>
400 crvec v, rvec Hv) const {
401 if (!impl->hess_L_prod)
402 throw std::logic_error("CasADiProblem::eval_ψ");
403 (*impl->hess_L_prod)({x.data(), param.data(), y.data(), &scale, v.data()},
404 {Hv.data()});
405}
406
407template <Config Conf>
410 .rows = this->n,
411 .cols = this->n,
412 .symmetry = sparsity::Symmetry::Upper,
413 };
414 if (!impl->hess_L.has_value())
415 return dense;
416 const auto &sp = impl->hess_L->fun.sparsity_out(0); // Reference!
417 return sp.is_dense() ? Sparsity{dense}
419}
420
421template <Config Conf>
423 rvec H_values) const {
424 if (!impl->hess_L)
425 throw std::logic_error("CasADiProblem::eval_hess_L");
426 (*impl->hess_L)({x.data(), param.data(), y.data(), &scale},
427 {H_values.data()});
428}
429
430template <Config Conf>
433 rvec Hv) const {
434 if (!impl->hess_ψ_prod)
435 throw std::logic_error("CasADiProblem::eval_hess_ψ_prod");
436 (*impl->hess_ψ_prod)({x.data(), param.data(), y.data(), Σ.data(), &scale,
437 this->D.lowerbound.data(), this->D.upperbound.data(),
438 v.data()},
439 {Hv.data()});
440}
441
442template <Config Conf>
445 .rows = this->n,
446 .cols = this->n,
447 .symmetry = sparsity::Symmetry::Upper,
448 };
449 if (!impl->hess_ψ.has_value())
450 return dense;
451 const auto &sp = impl->hess_ψ->fun.sparsity_out(0); // Reference!
452 return sp.is_dense() ? Sparsity{dense}
454}
455
456template <Config Conf>
458 rvec H_values) const {
459 if (!impl->hess_ψ)
460 throw std::logic_error("CasADiProblem::eval_hess_ψ");
461 (*impl->hess_ψ)({x.data(), param.data(), y.data(), Σ.data(), &scale,
462 this->D.lowerbound.data(), this->D.upperbound.data()},
463 {H_values.data()});
464}
465
466template <Config Conf>
468 return false; // TODO
469}
470template <Config Conf>
472 return impl->ψ.has_value();
473}
474template <Config Conf>
476 return impl->ψ_grad_ψ.has_value();
477}
478template <Config Conf>
480 return impl->ψ_grad_ψ.has_value();
481}
482template <Config Conf>
484 return impl->grad_L.has_value();
485}
486template <Config Conf>
488 return impl->jac_g.has_value();
489}
490template <Config Conf>
492 return impl->hess_L_prod.has_value();
493}
494template <Config Conf>
496 return impl->hess_L.has_value();
497}
498template <Config Conf>
500 return impl->hess_ψ_prod.has_value();
501}
502template <Config Conf>
504 return impl->hess_ψ.has_value();
505}
506
507} // 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
bool provides_eval_hess_ψ_prod() const
bool provides_eval_ψ_grad_ψ() const
void eval_jac_g(crvec x, rvec J_values) const
Sparsity get_jac_g_sparsity() const
Sparsity get_hess_ψ_sparsity() const
bool provides_eval_jac_g() const
void eval_hess_ψ(crvec x, crvec y, crvec Σ, real_t scale, rvec H_values) const
Sparsity get_hess_L_sparsity() 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
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
bool provides_eval_grad_L() const
bool provides_eval_grad_ψ() const
void eval_grad_gi(crvec x, index_t i, rvec grad_i) const
void eval_hess_L(crvec x, crvec y, real_t scale, rvec H_values) const
bool provides_eval_hess_ψ() const
void eval_grad_ψ(crvec x, crvec y, crvec Σ, rvec grad_ψ, rvec work_n, rvec work_m) const
bool provides_eval_ψ() const
void eval_hess_ψ_prod(crvec x, crvec y, crvec Σ, real_t scale, crvec v, rvec Hv) const
Class for evaluating CasADi functions, allocating the necessary workspace storage in advance for allo...
static void validate_dimensions(const casadi::Function &fun, const std::array< casadi_dim, N_in > &dim_in={}, const std::array< casadi_dim, N_out > &dim_out={})
#define USING_ALPAQA_CONFIG(Conf)
Definition config.hpp:56
auto wrapped_load(Loader &&loader, const char *name, Args &&...args)
std::optional< T > try_load(Loader &&loader, const char *name, Args &&...args)
constexpr auto dims(auto... a)
std::pair< casadi_int, casadi_int > dim
auto wrap_load(Loader &&loader, const char *name, F f)
void read_row(std::istream &is, Eigen::Ref< Eigen::VectorX< Eigen::Index > > v, char sep)
Definition csv.hpp:37
auto casadi_to_index(casadi_int i) -> index_t< Conf >
Symmetry
Describes the symmetry of matrices.
Definition sparsity.hpp:12
@ Upper
Symmetric, upper-triangular part is stored.
Dense matrix structure.
Definition sparsity.hpp:21
typename Conf::real_t real_t
Definition config.hpp:65
typename Conf::index_t index_t
Definition config.hpp:77
Sparsity< Conf > convert_csc(const auto &sp, sparsity::Symmetry symmetry)
typename Conf::length_t length_t
Definition config.hpp:76
typename Conf::cmvec cmvec
Definition config.hpp:68
constexpr const auto inf
Definition config.hpp:85
typename Conf::rvec rvec
Definition config.hpp:69
typename Conf::crvec crvec
Definition config.hpp:70
std::map< std::string, std::string > functions
std::map< std::string, casadi::Function > functions
std::optional< CasADiFunctionEvaluator< Conf, 5, 1 > > hess_L_prod
std::optional< CasADiFunctionEvaluator< Conf, 7, 1 > > hess_ψ
static std::unique_ptr< CasADiFunctionsWithParam > load(Loader &&loader)
std::optional< CasADiFunctionEvaluator< Conf, 6, 2 > > ψ_grad_ψ
std::optional< CasADiFunctionEvaluator< Conf, 4, 1 > > hess_L
CasADiFunctionEvaluator< Conf, 2, 1 > f
std::optional< CasADiFunctionEvaluator< Conf, 8, 1 > > hess_ψ_prod
std::optional< CasADiFunctionEvaluator< Conf, 3, 1 > > grad_L
std::optional< CasADiFunctionEvaluator< Conf, 2, 1 > > jac_g
std::optional< CasADiFunctionEvaluator< Conf, 3, 1 > > grad_g_prod
std::optional< CasADiFunctionEvaluator< Conf, 2, 1 > > g
std::optional< CasADiFunctionEvaluator< Conf, 6, 2 > > ψ
CasADiFunctionEvaluator< Conf, 2, 2 > f_grad_f
Sparse compressed-column structure (CCS or CSC).
Definition sparsity.hpp:29
Stores any of the supported sparsity patterns.
Definition sparsity.hpp:106