alpaqa 1.0.0a19
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 : BoxConstrProblem<Conf>{0, 0} {
141
142 struct {
143 const std::string &filename;
144 auto operator()(const std::string &name) const {
145 return casadi::external(name, filename);
146 }
147 auto format_name(const std::string &name) const {
148 return filename + ':' + name;
149 }
150 } loader{filename};
152
153 this->n = impl->n;
154 this->m = impl->m;
155 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
156 this->C = Box<config_t>{impl->n};
157 this->D = Box<config_t>{impl->m};
158
159 auto data_filepath = fs::path{filename}.replace_extension("csv");
160 if (fs::exists(data_filepath))
161 load_numerical_data(data_filepath);
162}
163
164template <Config Conf>
166 : BoxConstrProblem<Conf>{0, 0} {
167#if ALPAQA_WITH_EXTERNAL_CASADI
168 struct {
169 const SerializedCasADiFunctions &functions;
170 auto operator()(const std::string &name) const {
171 return casadi::Function::deserialize(functions.functions.at(name));
172 }
173 auto format_name(const std::string &name) const {
174 return "SerializedCasADiFunctions['" + name + "']";
175 }
176 } loader{functions};
178
179 this->n = impl->n;
180 this->m = impl->m;
181 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
182 this->C = Box<config_t>{impl->n};
183 this->D = Box<config_t>{impl->m};
184#else
185 std::ignore = functions;
186 throw std::runtime_error(
187 "This version of alpaqa was compiled without the CasADi C++ library");
188#endif
189}
190
191template <Config Conf>
193 : BoxConstrProblem<Conf>{0, 0} {
194
195 struct {
196 const CasADiFunctions &functions;
197 auto operator()(const std::string &name) const {
198 return functions.functions.at(name);
199 }
200 auto format_name(const std::string &name) const {
201 return "CasADiFunctions['" + name + "']";
202 }
203 } loader{functions};
205
206 this->n = impl->n;
207 this->m = impl->m;
208 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
209 this->C = Box<config_t>{impl->n};
210 this->D = Box<config_t>{impl->m};
211}
212
213template <Config Conf>
215 const std::filesystem::path &filepath, char sep) {
216 // Open data file
217 std::ifstream data_file{filepath};
218 if (!data_file)
219 throw std::runtime_error("Unable to open data file \"" +
220 filepath.string() + '"');
221
222 // Helper function for reading single line of (float) data
223 index_t line = 0;
224 auto wrap_data_load = [&](std::string_view name, auto &v, bool fixed_size) {
225 try {
226 ++line;
227 if (data_file.peek() == '\n') // Ignore empty lines
228 return static_cast<void>(data_file.get());
229 if (fixed_size) {
231 } else { // Dynamic size
232 auto s = csv::read_row_std_vector<real_t>(data_file, sep);
233 v = cmvec{s.data(), static_cast<index_t>(s.size())};
234 }
235 } catch (csv::read_error &e) {
236 // Transform any errors in something more readable
237 throw std::runtime_error("Unable to read " + std::string(name) +
238 " from data file \"" + filepath.string() +
239 ':' + std::to_string(line) +
240 "\": " + e.what());
241 }
242 };
243 // Helper function for reading a single value
244 auto read_single = [&](std::string_view name, auto &v) {
245 data_file >> v;
246 if (!data_file)
247 throw std::runtime_error("Unable to read " + std::string(name) +
248 " from data file \"" + filepath.string() +
249 ':' + std::to_string(line) + '"');
250 };
251 // Read the bounds, parameter value, and regularization
252 wrap_data_load("C.lowerbound", this->C.lowerbound, true);
253 wrap_data_load("C.upperbound", this->C.upperbound, true);
254 wrap_data_load("D.lowerbound", this->D.lowerbound, true);
255 wrap_data_load("D.upperbound", this->D.upperbound, true);
256 wrap_data_load("param", this->param, true);
257 wrap_data_load("l1_reg", this->l1_reg, false);
258 // Penalty/ALM split is a single integer
259 read_single("penalty_alm_split", this->penalty_alm_split);
260 // Name is a string
261 data_file >> name;
262}
263
264template <Config Conf>
266template <Config Conf>
269template <Config Conf>
274
277
280 real_t f;
281 impl->f({x.data(), param.data()}, {&f});
282 return f;
283}
284
285template <Config Conf>
287 real_t f;
288 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
289}
290
291template <Config Conf>
293 real_t f;
294 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
295 return f;
296}
297
298template <Config Conf>
300 if (impl->m == 0)
301 return;
302 if (impl->g)
303 (*impl->g)({x.data(), param.data()}, {g.data()});
304 else
305 throw not_implemented_error("CasADiProblem::eval_g");
306}
307
308template <Config Conf>
310 if (impl->m == 0) {
311 gxy.setZero();
312 return;
313 }
314 if (impl->grad_g_prod)
315 (*impl->grad_g_prod)({x.data(), param.data(), y.data()}, {gxy.data()});
316 else
317 throw not_implemented_error("CasADiProblem::eval_grad_g_prod"); // TODO
318}
319
320template <Config Conf>
322 rvec work_n, rvec work_m) const {
323#if 0
324 impl->grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
325 this->D.lowerbound.data(), this->D.upperbound.data()},
326 {grad_ψ.data()});
327#else
328 // This seems to be faster than having a specialized function. Possibly
329 // cache-related?
330 eval_ψ_grad_ψ(x, y, Σ, grad_ψ, work_n, work_m);
331#endif
332}
333
334template <Config Conf>
337 rvec) const {
338 if (!impl->ψ_grad_ψ)
339 throw std::logic_error("CasADiProblem::eval_ψ_grad_ψ");
340 real_t ψ;
341 (*impl->ψ_grad_ψ)({x.data(), param.data(), y.data(), Σ.data(),
342 this->D.lowerbound.data(), this->D.upperbound.data()},
343 {&ψ, grad_ψ.data()});
344 return ψ;
345}
346
347template <Config Conf>
349 rvec) const {
350 if (!impl->grad_L)
351 throw std::logic_error("CasADiProblem::eval_grad_L");
352 (*impl->grad_L)({x.data(), param.data(), y.data()}, {grad_L.data()});
353}
354
355template <Config Conf>
358 if (!impl->ψ)
359 throw std::logic_error("CasADiProblem::eval_ψ");
360 real_t ψ;
361 (*impl->ψ)({x.data(), param.data(), y.data(), Σ.data(),
362 this->D.lowerbound.data(), this->D.upperbound.data()},
363 {&ψ, ŷ.data()});
364 return ψ;
365}
366
367template <Config Conf>
369 throw not_implemented_error("CasADiProblem::eval_grad_gi"); // TODO
370}
371
372template <Config Conf>
376 using map_t = typename SparseCSC::index_vector_map_t;
377 return SparseCSC{
378 .rows = static_cast<index_t>(sp.size1()),
379 .cols = static_cast<index_t>(sp.size2()),
380 .symmetry = symmetry,
381 .inner_idx = map_t{sp.row(), static_cast<index_t>(sp.nnz())},
382 .outer_ptr = map_t{sp.colind(), static_cast<index_t>(sp.size2()) + 1},
383 .order = SparseCSC::SortedRows,
384 };
385}
386
387template <Config Conf>
390 .rows = this->m,
391 .cols = this->n,
393 };
394 if (!impl->jac_g.has_value())
395 return dense;
396 const auto &sp = impl->jac_g->fun.sparsity_out(0); // Reference!
397 return sp.is_dense()
398 ? Sparsity{dense}
400}
401
402template <Config Conf>
404 if (!impl->jac_g)
405 throw std::logic_error("CasADiProblem::eval_jac_g");
406 (*impl->jac_g)({x.data(), param.data()}, {J_values.data()});
407}
408
409template <Config Conf>
411 crvec v, rvec Hv) const {
412 if (!impl->hess_L_prod)
413 throw std::logic_error("CasADiProblem::eval_ψ");
414 (*impl->hess_L_prod)({x.data(), param.data(), y.data(), &scale, v.data()},
415 {Hv.data()});
416}
417
418template <Config Conf>
421 .rows = this->n,
422 .cols = this->n,
423 .symmetry = sparsity::Symmetry::Upper,
424 };
425 if (!impl->hess_L.has_value())
426 return dense;
427 const auto &sp = impl->hess_L->fun.sparsity_out(0); // Reference!
428 return sp.is_dense() ? Sparsity{dense}
430}
431
432template <Config Conf>
434 rvec H_values) const {
435 if (!impl->hess_L)
436 throw std::logic_error("CasADiProblem::eval_hess_L");
437 (*impl->hess_L)({x.data(), param.data(), y.data(), &scale},
438 {H_values.data()});
439}
440
441template <Config Conf>
444 rvec Hv) const {
445 if (!impl->hess_ψ_prod)
446 throw std::logic_error("CasADiProblem::eval_hess_ψ_prod");
447 (*impl->hess_ψ_prod)({x.data(), param.data(), y.data(), Σ.data(), &scale,
448 this->D.lowerbound.data(), this->D.upperbound.data(),
449 v.data()},
450 {Hv.data()});
451}
452
453template <Config Conf>
456 .rows = this->n,
457 .cols = this->n,
458 .symmetry = sparsity::Symmetry::Upper,
459 };
460 if (!impl->hess_ψ.has_value())
461 return dense;
462 const auto &sp = impl->hess_ψ->fun.sparsity_out(0); // Reference!
463 return sp.is_dense() ? Sparsity{dense}
465}
466
467template <Config Conf>
469 rvec H_values) const {
470 if (!impl->hess_ψ)
471 throw std::logic_error("CasADiProblem::eval_hess_ψ");
472 (*impl->hess_ψ)({x.data(), param.data(), y.data(), Σ.data(), &scale,
473 this->D.lowerbound.data(), this->D.upperbound.data()},
474 {H_values.data()});
475}
476
477template <Config Conf>
479 return false; // TODO
480}
481template <Config Conf>
483 return impl->ψ.has_value();
484}
485template <Config Conf>
487 return impl->ψ_grad_ψ.has_value();
488}
489template <Config Conf>
491 return impl->ψ_grad_ψ.has_value();
492}
493template <Config Conf>
495 return impl->grad_L.has_value();
496}
497template <Config Conf>
499 return impl->jac_g.has_value();
500}
501template <Config Conf>
503 return impl->hess_L_prod.has_value();
504}
505template <Config Conf>
507 return impl->hess_L.has_value();
508}
509template <Config Conf>
511 return impl->hess_ψ_prod.has_value();
512}
513template <Config Conf>
515 return impl->hess_ψ.has_value();
516}
517
518template <Config Conf>
519std::string CasADiProblem<Conf>::get_name() const {
520 return name;
521}
522
524} // 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
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: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)
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
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