alpaqa develop
Nonconvex constrained optimization
Loading...
Searching...
No Matches
CasADiProblem.tpp
Go to the documentation of this file.
1#pragma once
2
9#include <tuple>
10
11#if ALPAQA_WITH_EXTERNAL_CASADI
12#include <casadi/core/external.hpp>
13#endif
14
15#include <algorithm>
16#include <filesystem>
17#include <fstream>
18#include <memory>
19#include <optional>
20#include <stdexcept>
21
22namespace alpaqa {
24
25namespace fs = std::filesystem;
26
27namespace casadi_loader {
28
29template <Config Conf>
35 std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> g = std::nullopt;
36 std::optional<CasADiFunctionEvaluator<Conf, 3, 1>> grad_g_prod =
37 std::nullopt;
38 std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> jac_g = std::nullopt;
39 std::optional<CasADiFunctionEvaluator<Conf, 3, 1>> grad_L = std::nullopt;
40 std::optional<CasADiFunctionEvaluator<Conf, 5, 1>> hess_L_prod =
41 std::nullopt;
42 std::optional<CasADiFunctionEvaluator<Conf, 4, 1>> hess_L = std::nullopt;
43 std::optional<CasADiFunctionEvaluator<Conf, 6, 2>> ψ = std::nullopt;
44 std::optional<CasADiFunctionEvaluator<Conf, 6, 2>> ψ_grad_ψ = std::nullopt;
45 std::optional<CasADiFunctionEvaluator<Conf, 8, 1>> hess_ψ_prod =
46 std::nullopt;
47 std::optional<CasADiFunctionEvaluator<Conf, 7, 1>> hess_ψ = std::nullopt;
48
49 template <class Loader>
50 requires requires(Loader &&loader, const char *name) {
51 { loader(name) } -> std::same_as<casadi::Function>;
52 { loader.format_name(name) } -> std::same_as<std::string>;
53 }
54 static std::unique_ptr<CasADiFunctionsWithParam> load(Loader &&loader) {
55 length_t n = 0, m = 0, p = 0;
56 auto load_g =
57 [&]() -> std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> {
58 auto gfun = loader("g");
59 using namespace std::literals::string_literals;
60 if (gfun.n_in() != 2)
62 "Invalid number of input arguments: got "s +
63 std::to_string(gfun.n_in()) + ", should be 2.");
64 if (gfun.n_out() > 1)
66 "Invalid number of output arguments: got "s +
67 std::to_string(gfun.n_in()) + ", should be 0 or 1.");
68 if (gfun.size2_in(0) != 1)
70 "First input argument should be a column vector.");
71 if (gfun.size2_in(1) != 1)
73 "Second input argument should be a column vector.");
74 if (gfun.n_out() == 1 && gfun.size2_out(0) != 1)
76 "First output argument should be a column vector.");
77 n = static_cast<length_t>(gfun.size1_in(0));
78 if (gfun.n_out() == 1)
79 m = static_cast<length_t>(gfun.size1_out(0));
80 p = static_cast<length_t>(gfun.size1_in(1));
81 if (gfun.n_out() == 0) {
82 if (m != 0)
84 "Function g has no outputs but m != 0");
85 return std::nullopt;
86 }
88 g.validate_dimensions({dim(n, 1), dim(p, 1)}, {dim(m, 1)});
89 return std::make_optional(std::move(g));
90 };
91
92 auto g = wrap_load(loader, "g", load_g);
93
94 return std::make_unique<CasADiFunctionsWithParam>(
96 .n = n,
97 .m = m,
98 .p = p,
100 loader, "f", dims(n, p), dims(1)),
102 loader, "f_grad_f", dims(n, p), dims(1, n)),
103 .g = std::move(g),
105 loader, "grad_g_prod", dims(n, p, m), dims(n)),
107 loader, "jacobian_g", dims(n, p), dims(dim(m, n))),
109 loader, "grad_L", dims(n, p, m), dims(n)),
111 loader, "hess_L_prod", dims(n, p, m, 1, n), dims(n)),
113 loader, "hess_L", dims(n, p, m, 1), dims(dim(n, n))),
115 loader, "psi", dims(n, p, m, m, m, m), dims(1, m)),
117 loader, "psi_grad_psi", dims(n, p, m, m, m, m), dims(1, n)),
119 loader, "hess_psi_prod", dims(n, p, m, m, 1, m, m, n),
120 dims(n)),
122 loader, "hess_psi", dims(n, p, m, m, 1, m, m),
123 dims(dim(n, n))),
124 });
125 }
126};
127
128} // namespace casadi_loader
129
130namespace detail {
131
132template <Config Conf>
134 return static_cast<index_t<Conf>>(i);
135}
136} // namespace detail
137
138template <Config Conf>
140 DynamicLoadFlags dl_flags)
141 : BoxConstrProblem<Conf>{0, 0} {
142
143 struct {
144 const std::string &filename;
145 DynamicLoadFlags dl_flags;
146 auto operator()(const std::string &name) const {
147#if ALPAQA_WITH_EXTERNAL_CASADI
148 return casadi::external(name, filename);
149#else
150 return casadi::external(name, filename, dl_flags);
151#endif
152 }
153 auto format_name(const std::string &name) const {
154 return filename + ':' + name;
155 }
156 } loader{filename, dl_flags};
158
159 this->n = impl->n;
160 this->m = impl->m;
161 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
162 this->C = Box<config_t>{impl->n};
163 this->D = Box<config_t>{impl->m};
164
165 auto data_filepath = fs::path{filename}.replace_extension("csv");
166 if (fs::exists(data_filepath))
167 load_numerical_data(data_filepath);
168}
169
170template <Config Conf>
172 : BoxConstrProblem<Conf>{0, 0} {
173#if ALPAQA_WITH_EXTERNAL_CASADI
174 struct {
175 const SerializedCasADiFunctions &functions;
176 auto operator()(const std::string &name) const {
177 return casadi::Function::deserialize(functions.functions.at(name));
178 }
179 auto format_name(const std::string &name) const {
180 return "SerializedCasADiFunctions['" + name + "']";
181 }
182 } loader{functions};
184
185 this->n = impl->n;
186 this->m = impl->m;
187 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
188 this->C = Box<config_t>{impl->n};
189 this->D = Box<config_t>{impl->m};
190#else
191 std::ignore = functions;
192 throw std::runtime_error(
193 "This version of alpaqa was compiled without the CasADi C++ library");
194#endif
195}
196
197template <Config Conf>
199 : BoxConstrProblem<Conf>{0, 0} {
200
201 struct {
202 const CasADiFunctions &functions;
203 auto operator()(const std::string &name) const {
204 return functions.functions.at(name);
205 }
206 auto format_name(const std::string &name) const {
207 return "CasADiFunctions['" + name + "']";
208 }
209 } loader{functions};
211
212 this->n = impl->n;
213 this->m = impl->m;
214 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
215 this->C = Box<config_t>{impl->n};
216 this->D = Box<config_t>{impl->m};
217}
218
219template <Config Conf>
221 const std::filesystem::path &filepath, char sep) {
222 // Open data file
223 std::ifstream data_file{filepath};
224 if (!data_file)
225 throw std::runtime_error("Unable to open data file \"" +
226 filepath.string() + '"');
227
228 // Helper function for reading single line of (float) data
229 index_t line = 0;
230 auto wrap_data_load = [&](std::string_view name, auto &v, bool fixed_size) {
231 try {
232 ++line;
233 if (data_file.peek() == '\n') // Ignore empty lines
234 return static_cast<void>(data_file.get());
235 if (fixed_size) {
237 } else { // Dynamic size
238 auto s = csv::read_row_std_vector<real_t>(data_file, sep);
239 v = cmvec{s.data(), static_cast<index_t>(s.size())};
240 }
241 } catch (csv::read_error &e) {
242 // Transform any errors in something more readable
243 throw std::runtime_error("Unable to read " + std::string(name) +
244 " from data file \"" + filepath.string() +
245 ':' + std::to_string(line) +
246 "\": " + e.what());
247 }
248 };
249 // Helper function for reading a single value
250 auto read_single = [&](std::string_view name, auto &v) {
251 data_file >> v;
252 if (!data_file)
253 throw std::runtime_error("Unable to read " + std::string(name) +
254 " from data file \"" + filepath.string() +
255 ':' + std::to_string(line) + '"');
256 };
257 // Read the bounds, parameter value, and regularization
258 wrap_data_load("C.lowerbound", this->C.lowerbound, true);
259 wrap_data_load("C.upperbound", this->C.upperbound, true);
260 wrap_data_load("D.lowerbound", this->D.lowerbound, true);
261 wrap_data_load("D.upperbound", this->D.upperbound, true);
262 wrap_data_load("param", this->param, true);
263 wrap_data_load("l1_reg", this->l1_reg, false);
264 // Penalty/ALM split is a single integer
265 read_single("penalty_alm_split", this->penalty_alm_split);
266 // Name is a string
267 data_file >> name;
268}
269
270template <Config Conf>
272template <Config Conf>
275template <Config Conf>
280
283
286 real_t f;
287 impl->f({x.data(), param.data()}, {&f});
288 return f;
289}
290
291template <Config Conf>
293 real_t f;
294 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
295}
296
297template <Config Conf>
299 real_t f;
300 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
301 return f;
302}
303
304template <Config Conf>
306 if (impl->m == 0)
307 return;
308 if (impl->g)
309 (*impl->g)({x.data(), param.data()}, {g.data()});
310 else
311 throw not_implemented_error("CasADiProblem::eval_g");
312}
313
314template <Config Conf>
316 if (impl->m == 0) {
317 gxy.setZero();
318 return;
319 }
320 if (impl->grad_g_prod)
321 (*impl->grad_g_prod)({x.data(), param.data(), y.data()}, {gxy.data()});
322 else
323 throw not_implemented_error("CasADiProblem::eval_grad_g_prod"); // TODO
324}
325
326template <Config Conf>
328 rvec work_n, rvec work_m) const {
329#if 0
330 impl->grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
331 this->D.lowerbound.data(), this->D.upperbound.data()},
332 {grad_ψ.data()});
333#else
334 // This seems to be faster than having a specialized function. Possibly
335 // cache-related?
336 eval_ψ_grad_ψ(x, y, Σ, grad_ψ, work_n, work_m);
337#endif
338}
339
340template <Config Conf>
343 rvec) const {
344 if (!impl->ψ_grad_ψ)
345 throw std::logic_error("CasADiProblem::eval_ψ_grad_ψ");
346 real_t ψ;
347 (*impl->ψ_grad_ψ)({x.data(), param.data(), y.data(), Σ.data(),
348 this->D.lowerbound.data(), this->D.upperbound.data()},
349 {&ψ, grad_ψ.data()});
350 return ψ;
351}
352
353template <Config Conf>
355 rvec) const {
356 if (!impl->grad_L)
357 throw std::logic_error("CasADiProblem::eval_grad_L");
358 (*impl->grad_L)({x.data(), param.data(), y.data()}, {grad_L.data()});
359}
360
361template <Config Conf>
364 if (!impl->ψ)
365 throw std::logic_error("CasADiProblem::eval_ψ");
366 real_t ψ;
367 (*impl->ψ)({x.data(), param.data(), y.data(), Σ.data(),
368 this->D.lowerbound.data(), this->D.upperbound.data()},
369 {&ψ, ŷ.data()});
370 return ψ;
371}
372
373template <Config Conf>
375 throw not_implemented_error("CasADiProblem::eval_grad_gi"); // TODO
376}
377
378template <Config Conf>
382 using map_t = typename SparseCSC::index_vector_map_t;
383 return SparseCSC{
384 .rows = static_cast<index_t>(sp.size1()),
385 .cols = static_cast<index_t>(sp.size2()),
386 .symmetry = symmetry,
387 .inner_idx = map_t{sp.row(), static_cast<index_t>(sp.nnz())},
388 .outer_ptr = map_t{sp.colind(), static_cast<index_t>(sp.size2()) + 1},
389 .order = SparseCSC::SortedRows,
390 };
391}
392
393template <Config Conf>
396 .rows = this->m,
397 .cols = this->n,
399 };
400 if (!impl->jac_g.has_value())
401 return dense;
402 const auto &sp = impl->jac_g->fun.sparsity_out(0); // Reference!
403 return sp.is_dense()
404 ? Sparsity{dense}
406}
407
408template <Config Conf>
410 if (!impl->jac_g)
411 throw std::logic_error("CasADiProblem::eval_jac_g");
412 (*impl->jac_g)({x.data(), param.data()}, {J_values.data()});
413}
414
415template <Config Conf>
417 crvec v, rvec Hv) const {
418 if (!impl->hess_L_prod)
419 throw std::logic_error("CasADiProblem::eval_ψ");
420 (*impl->hess_L_prod)({x.data(), param.data(), y.data(), &scale, v.data()},
421 {Hv.data()});
422}
423
424template <Config Conf>
427 .rows = this->n,
428 .cols = this->n,
429 .symmetry = sparsity::Symmetry::Upper,
430 };
431 if (!impl->hess_L.has_value())
432 return dense;
433 const auto &sp = impl->hess_L->fun.sparsity_out(0); // Reference!
434 return sp.is_dense() ? Sparsity{dense}
436}
437
438template <Config Conf>
440 rvec H_values) const {
441 if (!impl->hess_L)
442 throw std::logic_error("CasADiProblem::eval_hess_L");
443 (*impl->hess_L)({x.data(), param.data(), y.data(), &scale},
444 {H_values.data()});
445}
446
447template <Config Conf>
450 rvec Hv) const {
451 if (!impl->hess_ψ_prod)
452 throw std::logic_error("CasADiProblem::eval_hess_ψ_prod");
453 (*impl->hess_ψ_prod)({x.data(), param.data(), y.data(), Σ.data(), &scale,
454 this->D.lowerbound.data(), this->D.upperbound.data(),
455 v.data()},
456 {Hv.data()});
457}
458
459template <Config Conf>
462 .rows = this->n,
463 .cols = this->n,
464 .symmetry = sparsity::Symmetry::Upper,
465 };
466 if (!impl->hess_ψ.has_value())
467 return dense;
468 const auto &sp = impl->hess_ψ->fun.sparsity_out(0); // Reference!
469 return sp.is_dense() ? Sparsity{dense}
471}
472
473template <Config Conf>
475 rvec H_values) const {
476 if (!impl->hess_ψ)
477 throw std::logic_error("CasADiProblem::eval_hess_ψ");
478 (*impl->hess_ψ)({x.data(), param.data(), y.data(), Σ.data(), &scale,
479 this->D.lowerbound.data(), this->D.upperbound.data()},
480 {H_values.data()});
481}
482
483template <Config Conf>
485 return false; // TODO
486}
487template <Config Conf>
489 return impl->ψ.has_value();
490}
491template <Config Conf>
493 return impl->ψ_grad_ψ.has_value();
494}
495template <Config Conf>
497 return impl->ψ_grad_ψ.has_value();
498}
499template <Config Conf>
501 return impl->grad_L.has_value();
502}
503template <Config Conf>
505 return impl->jac_g.has_value();
506}
507template <Config Conf>
509 return impl->hess_L_prod.has_value();
510}
511template <Config Conf>
513 return impl->hess_L.has_value();
514}
515template <Config Conf>
517 return impl->hess_ψ_prod.has_value();
518}
519template <Config Conf>
521 return impl->hess_ψ.has_value();
522}
523
524template <Config Conf>
525std::string CasADiProblem<Conf>::get_name() const {
526 return name;
527}
528
530} // namespace alpaqa
#define BEGIN_ALPAQA_CASADI_LOADER_NAMESPACE
#define END_ALPAQA_CASADI_LOADER_NAMESPACE
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
std::string get_name() 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
bool provides_eval_hess_L_prod() const
CasADiProblem(const std::string &filename, DynamicLoadFlags dl_flags={})
Load a problem generated by CasADi (with parameters).
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:77
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)
Function external(const std::string &name, const std::string &bin_name, DynamicLoadFlags dl_flags)
Load the given CasADi function from the given DLL/SO file.
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:86
typename Conf::index_t index_t
Definition config.hpp:104
Sparsity< Conf > convert_csc(const auto &sp, sparsity::Symmetry symmetry)
typename Conf::length_t length_t
Definition config.hpp:103
typename Conf::cmvec cmvec
Definition config.hpp:90
constexpr const auto inf
Definition config.hpp:112
typename Conf::rvec rvec
Definition config.hpp:91
typename Conf::crvec crvec
Definition config.hpp:92
long long int casadi_int
std::map< std::string, std::string > functions
std::map< std::string, casadi::Function > functions
Flags to be passed to dlopen.
Definition dl-flags.hpp:8
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