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
12#if ALPAQA_WITH_EXTERNAL_CASADI
13#include <casadi/core/external.hpp>
14#endif
15
16#include <algorithm>
17#include <filesystem>
18#include <fstream>
19#include <memory>
20#include <optional>
21#include <stdexcept>
22
23namespace alpaqa {
25
26namespace fs = std::filesystem;
27
28namespace casadi_loader {
29
30template <Config Conf>
36 std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> g = std::nullopt;
37 std::optional<CasADiFunctionEvaluator<Conf, 3, 1>> grad_g_prod =
38 std::nullopt;
39 std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> jac_g = std::nullopt;
40 std::optional<CasADiFunctionEvaluator<Conf, 3, 1>> grad_L = std::nullopt;
41 std::optional<CasADiFunctionEvaluator<Conf, 5, 1>> hess_L_prod =
42 std::nullopt;
43 std::optional<CasADiFunctionEvaluator<Conf, 4, 1>> hess_L = std::nullopt;
44 std::optional<CasADiFunctionEvaluator<Conf, 6, 2>> ψ = std::nullopt;
45 std::optional<CasADiFunctionEvaluator<Conf, 6, 2>> ψ_grad_ψ = std::nullopt;
46 std::optional<CasADiFunctionEvaluator<Conf, 8, 1>> hess_ψ_prod =
47 std::nullopt;
48 std::optional<CasADiFunctionEvaluator<Conf, 7, 1>> hess_ψ = std::nullopt;
49
50 template <class Loader>
51 requires requires(Loader &&loader, const char *name) {
52 { loader(name) } -> std::same_as<casadi::Function>;
53 { loader.format_name(name) } -> std::same_as<std::string>;
54 }
55 static std::unique_ptr<CasADiFunctionsWithParam> load(Loader &&loader) {
56 length_t n = 0, m = 0, p = 0;
57 auto load_g =
58 [&]() -> std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> {
59 auto gfun = loader("g");
60 using namespace std::literals::string_literals;
61 if (gfun.n_in() != 2)
63 "Invalid number of input arguments: got "s +
64 std::to_string(gfun.n_in()) + ", should be 2.");
65 if (gfun.n_out() > 1)
67 "Invalid number of output arguments: got "s +
68 std::to_string(gfun.n_in()) + ", should be 0 or 1.");
69 if (gfun.size2_in(0) != 1)
71 "First input argument should be a column vector.");
72 if (gfun.size2_in(1) != 1)
74 "Second input argument should be a column vector.");
75 if (gfun.n_out() == 1 && gfun.size2_out(0) != 1)
77 "First output argument should be a column vector.");
78 n = static_cast<length_t>(gfun.size1_in(0));
79 if (gfun.n_out() == 1)
80 m = static_cast<length_t>(gfun.size1_out(0));
81 p = static_cast<length_t>(gfun.size1_in(1));
82 if (gfun.n_out() == 0) {
83 if (m != 0)
85 "Function g has no outputs but m != 0");
86 return std::nullopt;
87 }
89 g.validate_dimensions({dim(n, 1), dim(p, 1)}, {dim(m, 1)});
90 return std::make_optional(std::move(g));
91 };
92
93 auto g = wrap_load(loader, "g", load_g);
94
95 return std::make_unique<CasADiFunctionsWithParam>(
97 .n = n,
98 .m = m,
99 .p = p,
101 loader, "f", dims(n, p), dims(1)),
103 loader, "f_grad_f", dims(n, p), dims(1, n)),
104 .g = std::move(g),
106 loader, "grad_g_prod", dims(n, p, m), dims(n)),
108 loader, "jacobian_g", dims(n, p), dims(dim(m, n))),
110 loader, "grad_L", dims(n, p, m), dims(n)),
112 loader, "hess_L_prod", dims(n, p, m, 1, n), dims(n)),
114 loader, "hess_L", dims(n, p, m, 1), dims(dim(n, n))),
116 loader, "psi", dims(n, p, m, m, m, m), dims(1, m)),
118 loader, "psi_grad_psi", dims(n, p, m, m, m, m), dims(1, n)),
120 loader, "hess_psi_prod", dims(n, p, m, m, 1, m, m, n),
121 dims(n)),
123 loader, "hess_psi", dims(n, p, m, m, 1, m, m),
124 dims(dim(n, n))),
125 });
126 }
127};
128
129} // namespace casadi_loader
130
131namespace detail {
132
133template <Config Conf>
135 return static_cast<index_t<Conf>>(i);
136}
137} // namespace detail
138
139template <Config Conf>
140CasADiProblem<Conf>::CasADiProblem(const std::string &filename,
141 DynamicLoadFlags dl_flags)
142 : BoxConstrProblem<Conf>{0, 0} {
143
144 struct {
145 const std::string &filename;
146 DynamicLoadFlags dl_flags;
147 auto operator()(const std::string &name) const {
148#if ALPAQA_WITH_EXTERNAL_CASADI
149 return casadi::external(name, filename);
150#else
151 return casadi::external(name, filename, dl_flags);
152#endif
153 }
154 auto format_name(const std::string &name) const {
155 return filename + ':' + name;
156 }
157 } loader{filename, dl_flags};
159
160 this->num_variables = impl->n;
161 this->num_constraints = impl->m;
162 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
163 this->variable_bounds = Box<config_t>{impl->n};
164 this->general_bounds = Box<config_t>{impl->m};
165
166 auto data_filepath = fs::path{filename}.replace_extension("csv");
167 if (fs::exists(data_filepath))
168 load_numerical_data(data_filepath);
169}
170
171template <Config Conf>
173 : BoxConstrProblem<Conf>{0, 0} {
174#if ALPAQA_WITH_EXTERNAL_CASADI
175 struct {
177 auto operator()(const std::string &name) const {
178 return casadi::Function::deserialize(functions.functions.at(name));
179 }
180 auto format_name(const std::string &name) const {
181 return "SerializedCasADiFunctions['" + name + "']";
182 }
183 } loader{functions};
185
186 this->num_variables = impl->n;
187 this->num_constraints = impl->m;
188 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
189 this->variable_bounds = Box<config_t>{impl->n};
190 this->general_bounds = Box<config_t>{impl->m};
191#else
192 std::ignore = functions;
193 throw std::runtime_error(
194 "This version of alpaqa was compiled without the CasADi C++ library");
195#endif
196}
197
198template <Config Conf>
200 : BoxConstrProblem<Conf>{0, 0} {
201
202 struct {
204 auto operator()(const std::string &name) const {
205 return functions.functions.at(name);
206 }
207 auto format_name(const std::string &name) const {
208 return "CasADiFunctions['" + name + "']";
209 }
210 } loader{functions};
212
213 this->num_variables = impl->n;
214 this->num_constraints = impl->m;
215 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
216 this->variable_bounds = Box<config_t>{impl->n};
217 this->general_bounds = Box<config_t>{impl->m};
218}
219
220template <Config Conf>
222 const std::filesystem::path &filepath, char sep) {
223 // Open data file
224 std::ifstream data_file{filepath};
225 if (!data_file)
226 throw std::runtime_error("Unable to open data file \"" +
227 filepath.string() + '"');
228
229 // Helper function for reading single line of (float) data
230 index_t line = 0;
231 auto wrap_data_load = [&](std::string_view name, auto &v, bool fixed_size) {
232 using namespace guanaqo::io;
233 try {
234 ++line;
235 if (data_file.peek() == '\n') // Ignore empty lines
236 return static_cast<void>(data_file.get());
237 if (fixed_size) {
238 csv_read_row(data_file, as_span(v), sep);
239 } else { // Dynamic size
240 auto s = csv_read_row_std_vector<real_t>(data_file, sep);
241 v = as_vec(std::span{s});
242 }
243 } catch (csv_read_error &e) {
244 // Transform any errors in something more readable
245 throw std::runtime_error("Unable to read " + std::string(name) +
246 " from data file \"" + filepath.string() +
247 ':' + std::to_string(line) +
248 "\": " + e.what());
249 }
250 };
251 // Helper function for reading a single value
252 auto read_single = [&](std::string_view name, auto &v) {
253 data_file >> v;
254 if (!data_file)
255 throw std::runtime_error("Unable to read " + std::string(name) +
256 " from data file \"" + filepath.string() +
257 ':' + std::to_string(line) + '"');
258 };
259 // Read the bounds, parameter value, and regularization
260 wrap_data_load("variable_bounds.lower", this->variable_bounds.lower, true);
261 wrap_data_load("variable_bounds.upper", this->variable_bounds.upper, true);
262 wrap_data_load("general_bounds.lower", this->general_bounds.lower, true);
263 wrap_data_load("general_bounds.upper", this->general_bounds.upper, true);
264 wrap_data_load("param", this->param, true);
265 wrap_data_load("l1_reg", this->l1_reg, false);
266 // Penalty/ALM split is a single integer
267 read_single("penalty_alm_split", this->penalty_alm_split);
268 // Name is a string
269 data_file >> name;
270}
271
272template <Config Conf>
274template <Config Conf>
277template <Config Conf>
279template <Config Conf>
280CasADiProblem<Conf> &
281CasADiProblem<Conf>::operator=(CasADiProblem &&) noexcept = default;
282
283template <Config Conf>
284CasADiProblem<Conf>::~CasADiProblem() = default;
285
286template <Config Conf>
287auto CasADiProblem<Conf>::eval_objective(crvec x) const -> real_t {
288 real_t f;
289 impl->f({x.data(), param.data()}, {&f});
290 return f;
291}
292
293template <Config Conf>
295 real_t f;
296 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
297}
298
299template <Config Conf>
301 crvec x, rvec grad_fx) const -> real_t {
302 real_t f;
303 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
304 return f;
305}
306
307template <Config Conf>
309 if (impl->m == 0)
310 return;
311 if (impl->g)
312 (*impl->g)({x.data(), param.data()}, {g.data()});
313 else
314 throw not_implemented_error("CasADiProblem::eval_constraints");
315}
316
317template <Config Conf>
319 rvec gxy) const {
320 if (impl->m == 0) {
321 gxy.setZero();
322 return;
323 }
324 if (impl->grad_g_prod)
325 (*impl->grad_g_prod)({x.data(), param.data(), y.data()}, {gxy.data()});
326 else
327 throw not_implemented_error(
328 "CasADiProblem::eval_constraints_gradient_product"); // TODO
329}
330
331template <Config Conf>
333 crvec x, crvec y, crvec Σ, rvec grad_ψ, rvec work_n, rvec work_m) const {
334#if 0
335 impl->grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
336 this->D.lower.data(), this->D.upper.data()},
337 {grad_ψ.data()});
338#else
339 // This seems to be faster than having a specialized function. Possibly
340 // cache-related?
341 eval_augmented_lagrangian_and_gradient(x, y, Σ, grad_ψ, work_n, work_m);
342#endif
343}
344
345template <Config Conf>
348 crvec Σ,
349 rvec grad_ψ, rvec,
350 rvec) const {
351 if (!impl->ψ_grad_ψ)
352 throw std::logic_error(
353 "CasADiProblem::eval_augmented_lagrangian_and_gradient");
354 real_t ψ;
355 (*impl->ψ_grad_ψ)({x.data(), param.data(), y.data(), Σ.data(),
356 this->general_bounds.lower.data(),
357 this->general_bounds.upper.data()},
358 {&ψ, grad_ψ.data()});
359 return ψ;
360}
361
362template <Config Conf>
364 rvec grad_L, rvec) const {
365 if (!impl->grad_L)
366 throw std::logic_error("CasADiProblem::eval_lagrangian_gradient");
367 (*impl->grad_L)({x.data(), param.data(), y.data()}, {grad_L.data()});
368}
369
370template <Config Conf>
373 rvec ŷ) const {
374 if (!impl->ψ)
375 throw std::logic_error("CasADiProblem::eval_augmented_lagrangian");
376 real_t ψ;
377 (*impl->ψ)({x.data(), param.data(), y.data(), Σ.data(),
378 this->general_bounds.lower.data(),
379 this->general_bounds.upper.data()},
380 {&ψ, ŷ.data()});
381 return ψ;
382}
383
384template <Config Conf>
386 throw not_implemented_error("CasADiProblem::eval_grad_gi"); // TODO
387}
388
389template <Config Conf>
390Sparsity convert_csc(const auto &sp, sparsity::Symmetry symmetry) {
392 using SparseCSC = sparsity::SparseCSC<casadi_int, casadi_int>;
393 using std::span;
394 return SparseCSC{
395 .rows = static_cast<index_t>(sp.size1()),
396 .cols = static_cast<index_t>(sp.size2()),
397 .symmetry = symmetry,
398 .inner_idx = span{sp.row(), static_cast<size_t>(sp.nnz())},
399 .outer_ptr = span{sp.colind(), static_cast<size_t>(sp.size2()) + 1},
400 .order = SparseCSC::SortedRows,
401 };
402}
403
404template <Config Conf>
406 -> Sparsity {
407 sparsity::Dense dense{
408 .rows = this->num_constraints,
409 .cols = this->num_variables,
410 .symmetry = sparsity::Symmetry::Unsymmetric,
411 };
412 if (!impl->jac_g.has_value())
413 return dense;
414 const auto &sp = impl->jac_g->fun.sparsity_out(0); // Reference!
415 return sp.is_dense()
416 ? Sparsity{dense}
417 : convert_csc<config_t>(sp, sparsity::Symmetry::Unsymmetric);
418}
419
420template <Config Conf>
422 rvec J_values) const {
423 if (!impl->jac_g)
424 throw std::logic_error("CasADiProblem::eval_constraints_jacobian");
425 (*impl->jac_g)({x.data(), param.data()}, {J_values.data()});
426}
427
428template <Config Conf>
430 real_t scale, crvec v,
431 rvec Hv) const {
432 if (!impl->hess_L_prod)
433 throw std::logic_error("CasADiProblem::eval_augmented_lagrangian");
434 (*impl->hess_L_prod)({x.data(), param.data(), y.data(), &scale, v.data()},
435 {Hv.data()});
436}
437
438template <Config Conf>
440 sparsity::Dense dense{
441 .rows = this->num_variables,
442 .cols = this->num_variables,
443 .symmetry = sparsity::Symmetry::Upper,
444 };
445 if (!impl->hess_L.has_value())
446 return dense;
447 const auto &sp = impl->hess_L->fun.sparsity_out(0); // Reference!
448 return sp.is_dense() ? Sparsity{dense}
449 : convert_csc<config_t>(sp, sparsity::Symmetry::Upper);
450}
451
452template <Config Conf>
454 real_t scale,
455 rvec H_values) const {
456 if (!impl->hess_L)
457 throw std::logic_error("CasADiProblem::eval_lagrangian_hessian");
458 (*impl->hess_L)({x.data(), param.data(), y.data(), &scale},
459 {H_values.data()});
460}
461
462template <Config Conf>
464 crvec x, crvec y, crvec Σ, real_t scale, crvec v, rvec Hv) const {
465 if (!impl->hess_ψ_prod)
466 throw std::logic_error(
467 "CasADiProblem::eval_augmented_lagrangian_hessian_product");
468 (*impl->hess_ψ_prod)({x.data(), param.data(), y.data(), Σ.data(), &scale,
469 this->general_bounds.lower.data(),
470 this->general_bounds.upper.data(), v.data()},
471 {Hv.data()});
472}
473
474template <Config Conf>
476 -> Sparsity {
477 sparsity::Dense dense{
478 .rows = this->num_variables,
479 .cols = this->num_variables,
480 .symmetry = sparsity::Symmetry::Upper,
481 };
482 if (!impl->hess_ψ.has_value())
483 return dense;
484 const auto &sp = impl->hess_ψ->fun.sparsity_out(0); // Reference!
485 return sp.is_dense() ? Sparsity{dense}
486 : convert_csc<config_t>(sp, sparsity::Symmetry::Upper);
487}
488
489template <Config Conf>
491 crvec x, crvec y, crvec Σ, real_t scale, rvec H_values) const {
492 if (!impl->hess_ψ)
493 throw std::logic_error(
494 "CasADiProblem::eval_augmented_lagrangian_hessian");
495 (*impl->hess_ψ)({x.data(), param.data(), y.data(), Σ.data(), &scale,
496 this->general_bounds.lower.data(),
497 this->general_bounds.upper.data()},
498 {H_values.data()});
499}
500
501template <Config Conf>
503 return false; // TODO
504}
505template <Config Conf>
507 return impl->ψ.has_value();
508}
509template <Config Conf>
511 return impl->ψ_grad_ψ.has_value();
512}
513template <Config Conf>
515 const {
516 return impl->ψ_grad_ψ.has_value();
517}
518template <Config Conf>
520 return impl->grad_L.has_value();
521}
522template <Config Conf>
524 return impl->jac_g.has_value();
525}
526template <Config Conf>
528 return impl->hess_L_prod.has_value();
529}
530template <Config Conf>
532 return impl->hess_L.has_value();
533}
534template <Config Conf>
536 const {
537 return impl->hess_ψ_prod.has_value();
538}
539template <Config Conf>
541 return impl->hess_ψ.has_value();
542}
543
544template <Config Conf>
545std::string CasADiProblem<Conf>::get_name() const {
546 return name;
547}
548
550} // 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
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 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::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