Nonconvex constrained optimization
Loading...
Searching...
No Matches
CasADiProblem.tpp
Go to the documentation of this file.
1#pragma once
2
8#include <guanaqo/io/csv.hpp>
9#include <guanaqo/not-implemented.hpp>
10#include <tuple>
11#include <vector>
12
13#if ALPAQA_WITH_EXTERNAL_CASADI
14#include <casadi/core/external.hpp>
15#endif
16
17#include <algorithm>
18#include <filesystem>
19#include <fstream>
20#include <memory>
21#include <optional>
22#include <stdexcept>
23
24namespace alpaqa {
26
27namespace fs = std::filesystem;
28
29namespace casadi_loader {
30
31template <Config Conf>
37 std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> g = std::nullopt;
38 std::optional<CasADiFunctionEvaluator<Conf, 3, 1>> grad_g_prod =
39 std::nullopt;
40 std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> jac_g = std::nullopt;
41 std::optional<CasADiFunctionEvaluator<Conf, 3, 1>> grad_L = std::nullopt;
42 std::optional<CasADiFunctionEvaluator<Conf, 5, 1>> hess_L_prod =
43 std::nullopt;
44 std::optional<CasADiFunctionEvaluator<Conf, 4, 1>> hess_L = std::nullopt;
45 std::optional<CasADiFunctionEvaluator<Conf, 6, 2>> ψ = std::nullopt;
46 std::optional<CasADiFunctionEvaluator<Conf, 6, 2>> ψ_grad_ψ = std::nullopt;
47 std::optional<CasADiFunctionEvaluator<Conf, 8, 1>> hess_ψ_prod =
48 std::nullopt;
49 std::optional<CasADiFunctionEvaluator<Conf, 7, 1>> hess_ψ = std::nullopt;
50
51#if !ALPAQA_WITH_EXTERNAL_CASADI
52 std::vector<casadi::Function> extra_functions;
53#endif
54
55 template <class Loader>
56 requires requires(Loader &&loader, const char *name) {
57 { loader(name) } -> std::same_as<casadi::Function>;
58 { loader.format_name(name) } -> std::same_as<std::string>;
59 }
60 static std::unique_ptr<CasADiFunctionsWithParam> load(Loader &&loader) {
61 length_t n = 0, m = 0, p = 0;
62 auto load_g =
63 [&]() -> std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> {
64 auto gfun = loader("g");
65 using namespace std::literals::string_literals;
66 if (gfun.n_in() != 2)
68 "Invalid number of input arguments: got "s +
69 std::to_string(gfun.n_in()) + ", should be 2.");
70 if (gfun.n_out() > 1)
72 "Invalid number of output arguments: got "s +
73 std::to_string(gfun.n_in()) + ", should be 0 or 1.");
74 if (gfun.size2_in(0) != 1)
76 "First input argument should be a column vector.");
77 if (gfun.size2_in(1) != 1)
79 "Second input argument should be a column vector.");
80 if (gfun.n_out() == 1 && gfun.size2_out(0) != 1)
82 "First output argument should be a column vector.");
83 n = static_cast<length_t>(gfun.size1_in(0));
84 if (gfun.n_out() == 1)
85 m = static_cast<length_t>(gfun.size1_out(0));
86 p = static_cast<length_t>(gfun.size1_in(1));
87 if (gfun.n_out() == 0) {
88 if (m != 0)
90 "Function g has no outputs but m != 0");
91 return std::nullopt;
92 }
94 g.validate_dimensions({dim(n, 1), dim(p, 1)}, {dim(m, 1)});
95 return std::make_optional(std::move(g));
96 };
97
98 auto g = wrap_load(loader, "g", load_g);
99
100#if !ALPAQA_WITH_EXTERNAL_CASADI
101 // Load any extra functions
102 std::vector<casadi::Function> extra_functions;
103 try {
104 int i = 0;
105 while (true)
106 extra_functions.push_back(
107 loader("extra_func_" + std::to_string(i++)));
108 } catch (dynamic_load_error &) {
109 // No more extra functions in dll
110 }
111#endif
112
113 return std::make_unique<CasADiFunctionsWithParam>(
115 .n = n,
116 .m = m,
117 .p = p,
119 loader, "f", dims(n, p), dims(1)),
121 loader, "f_grad_f", dims(n, p), dims(1, n)),
122 .g = std::move(g),
124 loader, "grad_g_prod", dims(n, p, m), dims(n)),
126 loader, "jacobian_g", dims(n, p), dims(dim(m, n))),
128 loader, "grad_L", dims(n, p, m), dims(n)),
130 loader, "hess_L_prod", dims(n, p, m, 1, n), dims(n)),
132 loader, "hess_L", dims(n, p, m, 1), dims(dim(n, n))),
134 loader, "psi", dims(n, p, m, m, m, m), dims(1, m)),
136 loader, "psi_grad_psi", dims(n, p, m, m, m, m), dims(1, n)),
138 loader, "hess_psi_prod", dims(n, p, m, m, 1, m, m, n),
139 dims(n)),
141 loader, "hess_psi", dims(n, p, m, m, 1, m, m),
142 dims(dim(n, n))),
143#if !ALPAQA_WITH_EXTERNAL_CASADI
144 .extra_functions = std::move(extra_functions),
145#endif
146 });
147 }
148};
149
150} // namespace casadi_loader
151
152namespace detail {
153
154template <Config Conf>
156 return static_cast<index_t<Conf>>(i);
157}
158} // namespace detail
159
160template <Config Conf>
161CasADiProblem<Conf>::CasADiProblem(const std::string &filename,
162 DynamicLoadFlags dl_flags)
163 : BoxConstrProblem<Conf>{0, 0} {
164
165 struct {
166 const std::string &filename;
167 DynamicLoadFlags dl_flags;
168 auto operator()(const std::string &name) const {
169#if ALPAQA_WITH_EXTERNAL_CASADI
170 return casadi::external(name, filename);
171#else
172 return casadi::external(name, filename, dl_flags);
173#endif
174 }
175 auto format_name(const std::string &name) const {
176 return filename + ':' + name;
177 }
178 } loader{filename, dl_flags};
180
181 this->num_variables = impl->n;
182 this->num_constraints = impl->m;
183 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
184 this->variable_bounds = Box<config_t>{impl->n};
185 this->general_bounds = Box<config_t>{impl->m};
186
187 auto data_filepath = fs::path{filename}.replace_extension("csv");
188 if (fs::exists(data_filepath))
189 load_numerical_data(data_filepath);
190}
191
192template <Config Conf>
194 : BoxConstrProblem<Conf>{0, 0} {
195#if ALPAQA_WITH_EXTERNAL_CASADI
196 struct {
198 auto operator()(const std::string &name) const {
199 return casadi::Function::deserialize(functions.functions.at(name));
200 }
201 auto format_name(const std::string &name) const {
202 return "SerializedCasADiFunctions['" + name + "']";
203 }
204 } loader{functions};
206
207 this->num_variables = impl->n;
208 this->num_constraints = impl->m;
209 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
210 this->variable_bounds = Box<config_t>{impl->n};
211 this->general_bounds = Box<config_t>{impl->m};
212#else
213 std::ignore = functions;
214 throw std::runtime_error(
215 "This version of alpaqa was compiled without the CasADi C++ library");
216#endif
217}
218
219template <Config Conf>
221 : BoxConstrProblem<Conf>{0, 0} {
222
223 struct {
225 auto operator()(const std::string &name) const {
226 return functions.functions.at(name);
227 }
228 auto format_name(const std::string &name) const {
229 return "CasADiFunctions['" + name + "']";
230 }
231 } loader{functions};
233
234 this->num_variables = impl->n;
235 this->num_constraints = impl->m;
236 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
237 this->variable_bounds = Box<config_t>{impl->n};
238 this->general_bounds = Box<config_t>{impl->m};
239}
240
241template <Config Conf>
243 const std::filesystem::path &filepath, char sep) {
244 // Open data file
245 std::ifstream data_file{filepath};
246 if (!data_file)
247 throw std::runtime_error("Unable to open data file \"" +
248 filepath.string() + '"');
249
250 // Helper function for reading single line of (float) data
251 index_t line = 0;
252 auto wrap_data_load = [&](std::string_view name, auto &v, bool fixed_size) {
253 using namespace guanaqo::io;
254 try {
255 ++line;
256 if (data_file.peek() == '\n') // Ignore empty lines
257 return static_cast<void>(data_file.get());
258 if (fixed_size) {
259 csv_read_row(data_file, as_span(v), sep);
260 } else { // Dynamic size
261 auto s = csv_read_row_std_vector<real_t>(data_file, sep);
262 v = as_vec(std::span{s});
263 }
264 } catch (csv_read_error &e) {
265 // Transform any errors in something more readable
266 throw std::runtime_error("Unable to read " + std::string(name) +
267 " from data file \"" + filepath.string() +
268 ':' + std::to_string(line) +
269 "\": " + e.what());
270 }
271 };
272 // Helper function for reading a single value
273 auto read_single = [&](std::string_view name, auto &v) {
274 data_file >> v;
275 if (!data_file)
276 throw std::runtime_error("Unable to read " + std::string(name) +
277 " from data file \"" + filepath.string() +
278 ':' + std::to_string(line) + '"');
279 };
280 // Read the bounds, parameter value, and regularization
281 wrap_data_load("variable_bounds.lower", this->variable_bounds.lower, true);
282 wrap_data_load("variable_bounds.upper", this->variable_bounds.upper, true);
283 wrap_data_load("general_bounds.lower", this->general_bounds.lower, true);
284 wrap_data_load("general_bounds.upper", this->general_bounds.upper, true);
285 wrap_data_load("param", this->param, true);
286 wrap_data_load("l1_reg", this->l1_reg, false);
287 // Penalty/ALM split is a single integer
288 read_single("penalty_alm_split", this->penalty_alm_split);
289 // Name is a string
290 data_file >> name;
291}
292
293template <Config Conf>
295template <Config Conf>
298template <Config Conf>
300template <Config Conf>
301CasADiProblem<Conf> &
302CasADiProblem<Conf>::operator=(CasADiProblem &&) noexcept = default;
303
304template <Config Conf>
305CasADiProblem<Conf>::~CasADiProblem() = default;
306
307template <Config Conf>
308auto CasADiProblem<Conf>::eval_objective(crvec x) const -> real_t {
309 real_t f;
310 impl->f({x.data(), param.data()}, {&f});
311 return f;
312}
313
314template <Config Conf>
316 real_t f;
317 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
318}
319
320template <Config Conf>
322 crvec x, rvec grad_fx) const -> real_t {
323 real_t f;
324 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
325 return f;
326}
327
328template <Config Conf>
330 if (impl->m == 0)
331 return;
332 if (impl->g)
333 (*impl->g)({x.data(), param.data()}, {g.data()});
334 else
335 throw not_implemented_error("CasADiProblem::eval_constraints");
336}
337
338template <Config Conf>
340 rvec gxy) const {
341 if (impl->m == 0) {
342 gxy.setZero();
343 return;
344 }
345 if (impl->grad_g_prod)
346 (*impl->grad_g_prod)({x.data(), param.data(), y.data()}, {gxy.data()});
347 else
348 throw not_implemented_error(
349 "CasADiProblem::eval_constraints_gradient_product"); // TODO
350}
351
352template <Config Conf>
354 crvec x, crvec y, crvec Σ, rvec grad_ψ, rvec work_n, rvec work_m) const {
355#if 0
356 impl->grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
357 this->D.lower.data(), this->D.upper.data()},
358 {grad_ψ.data()});
359#else
360 // This seems to be faster than having a specialized function. Possibly
361 // cache-related?
362 eval_augmented_lagrangian_and_gradient(x, y, Σ, grad_ψ, work_n, work_m);
363#endif
364}
365
366template <Config Conf>
369 crvec Σ,
370 rvec grad_ψ, rvec,
371 rvec) const {
372 if (!impl->ψ_grad_ψ)
373 throw std::logic_error(
374 "CasADiProblem::eval_augmented_lagrangian_and_gradient");
375 real_t ψ;
376 (*impl->ψ_grad_ψ)({x.data(), param.data(), y.data(), Σ.data(),
377 this->general_bounds.lower.data(),
378 this->general_bounds.upper.data()},
379 {&ψ, grad_ψ.data()});
380 return ψ;
381}
382
383template <Config Conf>
385 rvec grad_L, rvec) const {
386 if (!impl->grad_L)
387 throw std::logic_error("CasADiProblem::eval_lagrangian_gradient");
388 (*impl->grad_L)({x.data(), param.data(), y.data()}, {grad_L.data()});
389}
390
391template <Config Conf>
394 rvec ŷ) const {
395 if (!impl->ψ)
396 throw std::logic_error("CasADiProblem::eval_augmented_lagrangian");
397 real_t ψ;
398 (*impl->ψ)({x.data(), param.data(), y.data(), Σ.data(),
399 this->general_bounds.lower.data(),
400 this->general_bounds.upper.data()},
401 {&ψ, ŷ.data()});
402 return ψ;
403}
404
405template <Config Conf>
407 throw not_implemented_error("CasADiProblem::eval_grad_gi"); // TODO
408}
409
410template <Config Conf>
411Sparsity convert_csc(const auto &sp, sparsity::Symmetry symmetry) {
413 using SparseCSC = sparsity::SparseCSC<casadi_int, casadi_int>;
414 using std::span;
415 return SparseCSC{
416 .rows = static_cast<index_t>(sp.size1()),
417 .cols = static_cast<index_t>(sp.size2()),
418 .symmetry = symmetry,
419 .inner_idx = span{sp.row(), static_cast<size_t>(sp.nnz())},
420 .outer_ptr = span{sp.colind(), static_cast<size_t>(sp.size2()) + 1},
421 .order = SparseCSC::SortedRows,
422 };
423}
424
425template <Config Conf>
427 -> Sparsity {
428 sparsity::Dense dense{
429 .rows = this->num_constraints,
430 .cols = this->num_variables,
431 .symmetry = sparsity::Symmetry::Unsymmetric,
432 };
433 if (!impl->jac_g.has_value())
434 return dense;
435 const auto &sp = impl->jac_g->fun.sparsity_out(0); // Reference!
436 return sp.is_dense()
437 ? Sparsity{dense}
438 : convert_csc<config_t>(sp, sparsity::Symmetry::Unsymmetric);
439}
440
441template <Config Conf>
443 rvec J_values) const {
444 if (!impl->jac_g)
445 throw std::logic_error("CasADiProblem::eval_constraints_jacobian");
446 (*impl->jac_g)({x.data(), param.data()}, {J_values.data()});
447}
448
449template <Config Conf>
451 real_t scale, crvec v,
452 rvec Hv) const {
453 if (!impl->hess_L_prod)
454 throw std::logic_error("CasADiProblem::eval_augmented_lagrangian");
455 (*impl->hess_L_prod)({x.data(), param.data(), y.data(), &scale, v.data()},
456 {Hv.data()});
457}
458
459template <Config Conf>
461 sparsity::Dense dense{
462 .rows = this->num_variables,
463 .cols = this->num_variables,
464 .symmetry = sparsity::Symmetry::Upper,
465 };
466 if (!impl->hess_L.has_value())
467 return dense;
468 const auto &sp = impl->hess_L->fun.sparsity_out(0); // Reference!
469 return sp.is_dense() ? Sparsity{dense}
470 : convert_csc<config_t>(sp, sparsity::Symmetry::Upper);
471}
472
473template <Config Conf>
475 real_t scale,
476 rvec H_values) const {
477 if (!impl->hess_L)
478 throw std::logic_error("CasADiProblem::eval_lagrangian_hessian");
479 (*impl->hess_L)({x.data(), param.data(), y.data(), &scale},
480 {H_values.data()});
481}
482
483template <Config Conf>
485 crvec x, crvec y, crvec Σ, real_t scale, crvec v, rvec Hv) const {
486 if (!impl->hess_ψ_prod)
487 throw std::logic_error(
488 "CasADiProblem::eval_augmented_lagrangian_hessian_product");
489 (*impl->hess_ψ_prod)({x.data(), param.data(), y.data(), Σ.data(), &scale,
490 this->general_bounds.lower.data(),
491 this->general_bounds.upper.data(), v.data()},
492 {Hv.data()});
493}
494
495template <Config Conf>
497 -> Sparsity {
498 sparsity::Dense dense{
499 .rows = this->num_variables,
500 .cols = this->num_variables,
501 .symmetry = sparsity::Symmetry::Upper,
502 };
503 if (!impl->hess_ψ.has_value())
504 return dense;
505 const auto &sp = impl->hess_ψ->fun.sparsity_out(0); // Reference!
506 return sp.is_dense() ? Sparsity{dense}
507 : convert_csc<config_t>(sp, sparsity::Symmetry::Upper);
508}
509
510template <Config Conf>
512 crvec x, crvec y, crvec Σ, real_t scale, rvec H_values) const {
513 if (!impl->hess_ψ)
514 throw std::logic_error(
515 "CasADiProblem::eval_augmented_lagrangian_hessian");
516 (*impl->hess_ψ)({x.data(), param.data(), y.data(), Σ.data(), &scale,
517 this->general_bounds.lower.data(),
518 this->general_bounds.upper.data()},
519 {H_values.data()});
520}
521
522template <Config Conf>
524 return false; // TODO
525}
526template <Config Conf>
528 return impl->ψ.has_value();
529}
530template <Config Conf>
532 return impl->ψ_grad_ψ.has_value();
533}
534template <Config Conf>
536 const {
537 return impl->ψ_grad_ψ.has_value();
538}
539template <Config Conf>
541 return impl->grad_L.has_value();
542}
543template <Config Conf>
545 return impl->jac_g.has_value();
546}
547template <Config Conf>
549 return impl->hess_L_prod.has_value();
550}
551template <Config Conf>
553 return impl->hess_L.has_value();
554}
555template <Config Conf>
557 const {
558 return impl->hess_ψ_prod.has_value();
559}
560template <Config Conf>
562 return impl->hess_ψ.has_value();
563}
564
565template <Config Conf>
566std::string CasADiProblem<Conf>::get_name() const {
567 return name;
568}
569
570#if !ALPAQA_WITH_EXTERNAL_CASADI
571template <Config Conf>
573 auto iz = static_cast<size_t>(i);
574 if (iz >= impl->extra_functions.size())
575 return nullptr;
576 return &impl->extra_functions[iz];
577}
578#endif
579
581} // namespace alpaqa
#define BEGIN_ALPAQA_CASADI_LOADER_NAMESPACE
#define END_ALPAQA_CASADI_LOADER_NAMESPACE
BoxConstrProblem(length_t num_variables, length_t num_constraints)
Problem definition for a CasADi problem, loaded from a DLL.
std::string get_name() const
bool provides_eval_constraints_jacobian() const
Sparsity get_lagrangian_hessian_sparsity() const
void eval_lagrangian_hessian_product(crvec x, crvec y, real_t scale, crvec v, rvec Hv) const
guanaqo::copyable_unique_ptr< Functions > impl
void eval_lagrangian_hessian(crvec x, crvec y, real_t scale, rvec H_values) const
bool provides_eval_augmented_lagrangian() const
real_t eval_objective_and_gradient(crvec x, rvec grad_fx) const
real_t eval_augmented_lagrangian(crvec x, crvec y, crvec Σ, rvec ŷ) const
bool provides_eval_augmented_lagrangian_and_gradient() const
void eval_constraints_jacobian(crvec x, rvec J_values) 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 &)
real_t eval_objective(crvec x) const
void eval_augmented_lagrangian_hessian_product(crvec x, crvec y, crvec Σ, real_t scale, crvec v, rvec Hv) const
bool provides_eval_lagrangian_hessian_product() const
void eval_lagrangian_gradient(crvec x, crvec y, rvec grad_L, rvec work_n) const
CasADiProblem(const std::string &filename, DynamicLoadFlags dl_flags={})
Load a problem generated by CasADi (with parameters).
real_t eval_augmented_lagrangian_and_gradient(crvec x, crvec y, crvec Σ, rvec grad_ψ, rvec work_n, rvec work_m) const
Sparsity get_constraints_jacobian_sparsity() const
void eval_augmented_lagrangian_gradient(crvec x, crvec y, crvec Σ, rvec grad_ψ, rvec work_n, rvec work_m) const
void eval_augmented_lagrangian_hessian(crvec x, crvec y, crvec Σ, real_t scale, rvec H_values) const
casadi::Function * extra_function(index_t i)
bool provides_eval_augmented_lagrangian_hessian() const
bool provides_eval_augmented_lagrangian_gradient() const
void eval_constraints(crvec x, rvec g) const
bool provides_eval_grad_gi() const
void eval_objective_gradient(crvec x, rvec grad_fx) const
bool provides_eval_augmented_lagrangian_hessian_product() const
void eval_grad_gi(crvec x, index_t i, rvec grad_i) const
bool provides_eval_lagrangian_hessian() const
void eval_constraints_gradient_product(crvec x, crvec y, rvec grad_gxy) const
Sparsity get_augmented_lagrangian_hessian_sparsity() const
bool provides_eval_lagrangian_gradient() const
Class that loads and calls pre-compiled CasADi functions in a DLL/SO file.
Class for evaluating CasADi functions, allocating the necessary workspace storage in advance for allo...
#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.
auto casadi_to_index(casadi_int i) -> index_t< Conf >
auto as_span(Eigen::DenseBase< Derived > &v)
Convert an Eigen vector view to a std::span.
Definition span.hpp:21
Sparsity convert_csc(const auto &sp, sparsity::Symmetry symmetry)
typename Conf::real_t real_t
Definition config.hpp:86
constexpr const auto NaN
Definition config.hpp:114
typename Conf::index_t index_t
Definition config.hpp:104
auto as_vec(std::span< T, E > s)
Convert a std::span to an Eigen::Vector view.
Definition span.hpp:51
typename Conf::length_t length_t
Definition config.hpp:103
typename Conf::rvec rvec
Definition config.hpp:91
typename Conf::crvec crvec
Definition config.hpp:92
long long int casadi_int
std::vector< casadi::Function > extra_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