alpaqa 1.0.0a18
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
23
24namespace fs = std::filesystem;
25
26namespace casadi_loader {
27
28template <Config Conf>
31 length_t n, m, p;
34 std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> g = std::nullopt;
35 std::optional<CasADiFunctionEvaluator<Conf, 3, 1>> grad_g_prod =
36 std::nullopt;
37 std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> jac_g = std::nullopt;
38 std::optional<CasADiFunctionEvaluator<Conf, 3, 1>> grad_L = std::nullopt;
39 std::optional<CasADiFunctionEvaluator<Conf, 5, 1>> hess_L_prod =
40 std::nullopt;
41 std::optional<CasADiFunctionEvaluator<Conf, 4, 1>> hess_L = std::nullopt;
42 std::optional<CasADiFunctionEvaluator<Conf, 6, 2>> ψ = std::nullopt;
43 std::optional<CasADiFunctionEvaluator<Conf, 6, 2>> ψ_grad_ψ = std::nullopt;
44 std::optional<CasADiFunctionEvaluator<Conf, 8, 1>> hess_ψ_prod =
45 std::nullopt;
46 std::optional<CasADiFunctionEvaluator<Conf, 7, 1>> hess_ψ = std::nullopt;
47
48 template <class Loader>
49 requires requires(Loader &&loader, const char *name) {
50 { loader(name) } -> std::same_as<casadi::Function>;
51 { loader.format_name(name) } -> std::same_as<std::string>;
52 }
53 static std::unique_ptr<CasADiFunctionsWithParam> load(Loader &&loader) {
54 length_t n = 0, m = 0, p = 0;
55 auto load_g =
56 [&]() -> std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> {
57 auto gfun = loader("g");
58 using namespace std::literals::string_literals;
59 if (gfun.n_in() != 2)
61 "Invalid number of input arguments: got "s +
62 std::to_string(gfun.n_in()) + ", should be 2.");
63 if (gfun.n_out() > 1)
65 "Invalid number of output arguments: got "s +
66 std::to_string(gfun.n_in()) + ", should be 0 or 1.");
67 if (gfun.size2_in(0) != 1)
69 "First input argument should be a column vector.");
70 if (gfun.size2_in(1) != 1)
72 "Second input argument should be a column vector.");
73 if (gfun.n_out() == 1 && gfun.size2_out(0) != 1)
75 "First output argument should be a column vector.");
76 n = static_cast<length_t>(gfun.size1_in(0));
77 if (gfun.n_out() == 1)
78 m = static_cast<length_t>(gfun.size1_out(0));
79 p = static_cast<length_t>(gfun.size1_in(1));
80 if (gfun.n_out() == 0) {
81 if (m != 0)
83 "Function g has no outputs but m != 0");
84 return std::nullopt;
85 }
86 CasADiFunctionEvaluator<Conf, 2, 1> g{std::move(gfun)};
87 g.validate_dimensions({dim(n, 1), dim(p, 1)}, {dim(m, 1)});
88 return std::make_optional(std::move(g));
89 };
90
91 auto g = wrap_load(loader, "g", load_g);
92
93 return std::make_unique<CasADiFunctionsWithParam>(
95 .n = n,
96 .m = m,
97 .p = p,
98 .f = wrapped_load<CasADiFunctionEvaluator<Conf, 2, 1>>(
99 loader, "f", dims(n, p), dims(1)),
101 loader, "f_grad_f", dims(n, p), dims(1, n)),
102 .g = std::move(g),
103 .grad_g_prod = try_load<CasADiFunctionEvaluator<Conf, 3, 1>>(
104 loader, "grad_g_prod", dims(n, p, m), dims(n)),
106 loader, "jacobian_g", dims(n, p), dims(dim(m, n))),
108 loader, "grad_L", dims(n, p, m), dims(n)),
110 loader, "hess_L_prod", dims(n, p, m, 1, n), dims(n)),
112 loader, "hess_L", dims(n, p, m, 1), dims(dim(n, n))),
114 loader, "psi", dims(n, p, m, m, m, m), dims(1, m)),
116 loader, "psi_grad_psi", dims(n, p, m, m, m, m), dims(1, n)),
118 loader, "hess_psi_prod", dims(n, p, m, m, 1, m, m, n),
119 dims(n)),
121 loader, "hess_psi", dims(n, p, m, m, 1, m, m),
122 dims(dim(n, n))),
123 });
124 }
125};
126
127} // namespace casadi_loader
128
129namespace detail {
130
131template <Config Conf>
132auto casadi_to_index(casadi_int i) -> index_t<Conf> {
133 return static_cast<index_t<Conf>>(i);
134}
135} // namespace detail
136
137template <Config Conf>
139 : BoxConstrProblem<Conf>{0, 0} {
140
141 struct {
142 const std::string &filename;
143 auto operator()(const std::string &name) const {
144 return casadi::external(name, filename);
145 }
146 auto format_name(const std::string &name) const {
147 return filename + ':' + name;
148 }
149 } loader{filename};
151
152 this->n = impl->n;
153 this->m = impl->m;
154 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
155 this->C = Box<config_t>{impl->n};
156 this->D = Box<config_t>{impl->m};
157
158 auto data_filepath = fs::path{filename}.replace_extension("csv");
159 if (fs::exists(data_filepath))
160 load_numerical_data(data_filepath);
161}
162
163template <Config Conf>
165 : BoxConstrProblem<Conf>{0, 0} {
166#if ALPAQA_WITH_EXTERNAL_CASADI
167 struct {
168 const SerializedCasADiFunctions &functions;
169 auto operator()(const std::string &name) const {
170 return casadi::Function::deserialize(functions.functions.at(name));
171 }
172 auto format_name(const std::string &name) const {
173 return "SerializedCasADiFunctions['" + name + "']";
174 }
175 } loader{functions};
177
178 this->n = impl->n;
179 this->m = impl->m;
180 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
181 this->C = Box<config_t>{impl->n};
182 this->D = Box<config_t>{impl->m};
183#else
184 std::ignore = functions;
185 throw std::runtime_error(
186 "This version of alpaqa was compiled without the CasADi C++ library");
187#endif
188}
189
190template <Config Conf>
192 : BoxConstrProblem<Conf>{0, 0} {
193
194 struct {
195 const CasADiFunctions &functions;
196 auto operator()(const std::string &name) const {
197 return functions.functions.at(name);
198 }
199 auto format_name(const std::string &name) const {
200 return "CasADiFunctions['" + name + "']";
201 }
202 } loader{functions};
204
205 this->n = impl->n;
206 this->m = impl->m;
207 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
208 this->C = Box<config_t>{impl->n};
209 this->D = Box<config_t>{impl->m};
210}
211
212template <Config Conf>
214 const std::filesystem::path &filepath, char sep) {
215 // Open data file
216 std::ifstream data_file{filepath};
217 if (!data_file)
218 throw std::runtime_error("Unable to open data file \"" +
219 filepath.string() + '"');
220
221 // Helper function for reading single line of (float) data
222 index_t line = 0;
223 auto wrap_data_load = [&](std::string_view name, auto &v, bool fixed_size) {
224 try {
225 ++line;
226 if (data_file.peek() == '\n') // Ignore empty lines
227 return static_cast<void>(data_file.get());
228 if (fixed_size) {
229 csv::read_row(data_file, v, sep);
230 } else { // Dynamic size
231 auto s = csv::read_row_std_vector<real_t>(data_file, sep);
232 v = cmvec{s.data(), static_cast<index_t>(s.size())};
233 }
234 } catch (csv::read_error &e) {
235 // Transform any errors in something more readable
236 throw std::runtime_error("Unable to read " + std::string(name) +
237 " from data file \"" + filepath.string() +
238 ':' + std::to_string(line) +
239 "\": " + e.what());
240 }
241 };
242 // Helper function for reading a single value
243 auto read_single = [&](std::string_view name, auto &v) {
244 data_file >> v;
245 if (!data_file)
246 throw std::runtime_error("Unable to read " + std::string(name) +
247 " from data file \"" + filepath.string() +
248 ':' + std::to_string(line) + '"');
249 };
250 // Read the bounds, parameter value, and regularization
251 wrap_data_load("C.lowerbound", this->C.lowerbound, true);
252 wrap_data_load("C.upperbound", this->C.upperbound, true);
253 wrap_data_load("D.lowerbound", this->D.lowerbound, true);
254 wrap_data_load("D.upperbound", this->D.upperbound, true);
255 wrap_data_load("param", this->param, true);
256 wrap_data_load("l1_reg", this->l1_reg, false);
257 // Penalty/ALM split is a single integer
258 read_single("penalty_alm_split", this->penalty_alm_split);
259 // Name is a string
260 data_file >> name;
261}
262
263template <Config Conf>
265template <Config Conf>
268template <Config Conf>
270template <Config Conf>
271CasADiProblem<Conf> &
272CasADiProblem<Conf>::operator=(CasADiProblem &&) noexcept = default;
273
274template <Config Conf>
275CasADiProblem<Conf>::~CasADiProblem() = default;
276
277template <Config Conf>
278auto CasADiProblem<Conf>::eval_f(crvec x) const -> real_t {
279 real_t f;
280 impl->f({x.data(), param.data()}, {&f});
281 return f;
282}
283
284template <Config Conf>
285void CasADiProblem<Conf>::eval_grad_f(crvec x, rvec grad_fx) const {
286 real_t f;
287 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
288}
289
290template <Config Conf>
291auto CasADiProblem<Conf>::eval_f_grad_f(crvec x, rvec grad_fx) const -> real_t {
292 real_t f;
293 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
294 return f;
295}
296
297template <Config Conf>
298void CasADiProblem<Conf>::eval_g(crvec x, rvec g) const {
299 if (impl->m == 0)
300 return;
301 if (impl->g)
302 (*impl->g)({x.data(), param.data()}, {g.data()});
303 else
304 throw not_implemented_error("CasADiProblem::eval_g");
305}
306
307template <Config Conf>
308void CasADiProblem<Conf>::eval_grad_g_prod(crvec x, crvec y, rvec gxy) const {
309 if (impl->m == 0) {
310 gxy.setZero();
311 return;
312 }
313 if (impl->grad_g_prod)
314 (*impl->grad_g_prod)({x.data(), param.data(), y.data()}, {gxy.data()});
315 else
316 throw not_implemented_error("CasADiProblem::eval_grad_g_prod"); // TODO
317}
318
319template <Config Conf>
320void CasADiProblem<Conf>::eval_grad_ψ(crvec x, crvec y, crvec Σ, rvec grad_ψ,
321 rvec work_n, rvec work_m) const {
322#if 0
323 impl->grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
324 this->D.lowerbound.data(), this->D.upperbound.data()},
325 {grad_ψ.data()});
326#else
327 // This seems to be faster than having a specialized function. Possibly
328 // cache-related?
329 eval_ψ_grad_ψ(x, y, Σ, grad_ψ, work_n, work_m);
330#endif
331}
332
333template <Config Conf>
335CasADiProblem<Conf>::eval_ψ_grad_ψ(crvec x, crvec y, crvec Σ, rvec grad_ψ, rvec,
336 rvec) const {
337 if (!impl->ψ_grad_ψ)
338 throw std::logic_error("CasADiProblem::eval_ψ_grad_ψ");
339 real_t ψ;
340 (*impl->ψ_grad_ψ)({x.data(), param.data(), y.data(), Σ.data(),
341 this->D.lowerbound.data(), this->D.upperbound.data()},
342 {&ψ, grad_ψ.data()});
343 return ψ;
344}
345
346template <Config Conf>
347void CasADiProblem<Conf>::eval_grad_L(crvec x, crvec y, rvec grad_L,
348 rvec) const {
349 if (!impl->grad_L)
350 throw std::logic_error("CasADiProblem::eval_grad_L");
351 (*impl->grad_L)({x.data(), param.data(), y.data()}, {grad_L.data()});
352}
353
354template <Config Conf>
356CasADiProblem<Conf>::eval_ψ(crvec x, crvec y, crvec Σ, rvec ŷ) const {
357 if (!impl->ψ)
358 throw std::logic_error("CasADiProblem::eval_ψ");
359 real_t ψ;
360 (*impl->ψ)({x.data(), param.data(), y.data(), Σ.data(),
361 this->D.lowerbound.data(), this->D.upperbound.data()},
362 {&ψ, ŷ.data()});
363 return ψ;
364}
365
366template <Config Conf>
367void CasADiProblem<Conf>::eval_grad_gi(crvec, index_t, rvec) const {
368 throw not_implemented_error("CasADiProblem::eval_grad_gi"); // TODO
369}
370
371template <Config Conf>
372Sparsity<Conf> convert_csc(const auto &sp, sparsity::Symmetry symmetry) {
374 using SparseCSC = sparsity::SparseCSC<Conf, casadi_int>;
375 using map_t = typename SparseCSC::index_vector_map_t;
376 return SparseCSC{
377 .rows = static_cast<index_t>(sp.size1()),
378 .cols = static_cast<index_t>(sp.size2()),
379 .symmetry = symmetry,
380 .inner_idx = map_t{sp.row(), static_cast<index_t>(sp.nnz())},
381 .outer_ptr = map_t{sp.colind(), static_cast<index_t>(sp.size2()) + 1},
382 .order = SparseCSC::SortedRows,
383 };
384}
385
386template <Config Conf>
388 sparsity::Dense<config_t> dense{
389 .rows = this->m,
390 .cols = this->n,
391 .symmetry = sparsity::Symmetry::Unsymmetric,
392 };
393 if (!impl->jac_g.has_value())
394 return dense;
395 const auto &sp = impl->jac_g->fun.sparsity_out(0); // Reference!
396 return sp.is_dense()
397 ? Sparsity{dense}
398 : convert_csc<config_t>(sp, sparsity::Symmetry::Unsymmetric);
399}
400
401template <Config Conf>
402void CasADiProblem<Conf>::eval_jac_g(crvec x, rvec J_values) const {
403 if (!impl->jac_g)
404 throw std::logic_error("CasADiProblem::eval_jac_g");
405 (*impl->jac_g)({x.data(), param.data()}, {J_values.data()});
406}
407
408template <Config Conf>
409void CasADiProblem<Conf>::eval_hess_L_prod(crvec x, crvec y, real_t scale,
410 crvec v, rvec Hv) const {
411 if (!impl->hess_L_prod)
412 throw std::logic_error("CasADiProblem::eval_ψ");
413 (*impl->hess_L_prod)({x.data(), param.data(), y.data(), &scale, v.data()},
414 {Hv.data()});
415}
416
417template <Config Conf>
419 sparsity::Dense<config_t> dense{
420 .rows = this->n,
421 .cols = this->n,
422 .symmetry = sparsity::Symmetry::Upper,
423 };
424 if (!impl->hess_L.has_value())
425 return dense;
426 const auto &sp = impl->hess_L->fun.sparsity_out(0); // Reference!
427 return sp.is_dense() ? Sparsity{dense}
428 : convert_csc<config_t>(sp, sparsity::Symmetry::Upper);
429}
430
431template <Config Conf>
432void CasADiProblem<Conf>::eval_hess_L(crvec x, crvec y, real_t scale,
433 rvec H_values) const {
434 if (!impl->hess_L)
435 throw std::logic_error("CasADiProblem::eval_hess_L");
436 (*impl->hess_L)({x.data(), param.data(), y.data(), &scale},
437 {H_values.data()});
438}
439
440template <Config Conf>
441void CasADiProblem<Conf>::eval_hess_ψ_prod(crvec x, crvec y, crvec Σ,
442 real_t scale, crvec v,
443 rvec Hv) const {
444 if (!impl->hess_ψ_prod)
445 throw std::logic_error("CasADiProblem::eval_hess_ψ_prod");
446 (*impl->hess_ψ_prod)({x.data(), param.data(), y.data(), Σ.data(), &scale,
447 this->D.lowerbound.data(), this->D.upperbound.data(),
448 v.data()},
449 {Hv.data()});
450}
451
452template <Config Conf>
454 sparsity::Dense<config_t> dense{
455 .rows = this->n,
456 .cols = this->n,
457 .symmetry = sparsity::Symmetry::Upper,
458 };
459 if (!impl->hess_ψ.has_value())
460 return dense;
461 const auto &sp = impl->hess_ψ->fun.sparsity_out(0); // Reference!
462 return sp.is_dense() ? Sparsity{dense}
463 : convert_csc<config_t>(sp, sparsity::Symmetry::Upper);
464}
465
466template <Config Conf>
467void CasADiProblem<Conf>::eval_hess_ψ(crvec x, crvec y, crvec Σ, real_t scale,
468 rvec H_values) const {
469 if (!impl->hess_ψ)
470 throw std::logic_error("CasADiProblem::eval_hess_ψ");
471 (*impl->hess_ψ)({x.data(), param.data(), y.data(), Σ.data(), &scale,
472 this->D.lowerbound.data(), this->D.upperbound.data()},
473 {H_values.data()});
474}
475
476template <Config Conf>
478 return false; // TODO
479}
480template <Config Conf>
482 return impl->ψ.has_value();
483}
484template <Config Conf>
486 return impl->ψ_grad_ψ.has_value();
487}
488template <Config Conf>
490 return impl->ψ_grad_ψ.has_value();
491}
492template <Config Conf>
494 return impl->grad_L.has_value();
495}
496template <Config Conf>
498 return impl->jac_g.has_value();
499}
500template <Config Conf>
502 return impl->hess_L_prod.has_value();
503}
504template <Config Conf>
506 return impl->hess_L.has_value();
507}
508template <Config Conf>
510 return impl->hess_ψ_prod.has_value();
511}
512template <Config Conf>
514 return impl->hess_ψ.has_value();
515}
516
517template <Config Conf>
518std::string CasADiProblem<Conf>::get_name() const {
519 return name;
520}
521
522} // namespace alpaqa::inline ALPAQA_CASADI_LOADER_NAMESPACE
#define ALPAQA_CASADI_LOADER_NAMESPACE
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={})
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
void eval_hess_ψ_prod(crvec x, crvec y, crvec Σ, real_t scale, crvec v, rvec Hv) const
#define USING_ALPAQA_CONFIG(Conf)
Definition config.hpp:77
constexpr const auto inf
Definition config.hpp:112
auto casadi_to_index(casadi_int i) -> index_t< Conf >
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
Sparsity< Conf > convert_csc(const auto &sp, sparsity::Symmetry symmetry)
long long int casadi_int
std::map< std::string, std::string > functions
auto wrap_load(Loader &&loader, const char *name, F f)
Stores any of the supported sparsity patterns.
Definition sparsity.hpp:106
std::map< std::string, casadi::Function > functions
static std::unique_ptr< CasADiFunctionsWithParam > load(Loader &&loader)
CasADiFunctionEvaluator< Conf, 2, 1 > f
CasADiFunctionEvaluator< Conf, 2, 2 > f_grad_f