alpaqa 1.0.0a13
Nonconvex constrained optimization
Loading...
Searching...
No Matches
CasADiProblem.tpp
Go to the documentation of this file.
1#pragma once
2
8
9#include <casadi/core/external.hpp>
10
11#include <algorithm>
12#include <cassert>
13#include <filesystem>
14#include <fstream>
15#include <memory>
16#include <optional>
17#include <stdexcept>
18#include <type_traits>
19
20namespace alpaqa {
21
22namespace fs = std::filesystem;
23
24namespace casadi_loader {
25
26template <Config Conf>
32 // CasADiFunctionEvaluator<6, 1> grad_ψ;
39 std::optional<ConstrFun> constr = 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, 8, 1>> hess_ψ_prod =
44 std::nullopt;
45 std::optional<CasADiFunctionEvaluator<Conf, 7, 1>> hess_ψ = std::nullopt;
46 std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> jac_g = 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 casadi::Function 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 }
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 auto self =
94 std::make_unique<CasADiFunctionsWithParam>(CasADiFunctionsWithParam{
95 .n = n,
96 .m = m,
97 .p = p,
99 loader, "f", dims(n, p), dims(1)),
101 loader, "f_grad_f", dims(n, p), dims(1, n)),
102 // .grad_ψ = wrapped_load<CasADiFunctionEvaluator<6, 1>>(
103 // loader, "grad_psi", dims(n, p, m, m, m, m), dims(n)),
105 loader, "psi_grad_psi", dims(n, p, m, m, m, m), dims(1, n)),
106 });
107
108 if (g)
109 self->constr = {
110 std::move(*g),
112 loader, "grad_L", dims(n, p, m), dims(n)),
114 loader, "psi", dims(n, p, m, m, m, m), dims(1, m)),
115 };
116
118 loader, "hess_L_prod", dims(n, p, m, 1, n), dims(n));
120 loader, "hess_L", dims(n, p, m, 1), dims(dim(n, n)));
122 loader, "hess_psi_prod", dims(n, p, m, m, 1, m, m, n), dims(n));
124 loader, "hess_psi", dims(n, p, m, m, 1, m, m), dims(dim(n, n)));
126 loader, "jacobian_g", dims(n, p), dims(dim(m, n)));
127 return self;
128 }
129};
130
131} // namespace casadi_loader
132
133namespace detail {
134
135template <Config Conf>
137 return static_cast<index_t<Conf>>(i);
138}
139} // namespace detail
140
141template <Config Conf>
143 : BoxConstrProblem<Conf>{0, 0} {
144
145 struct {
146 const std::string &filename;
147 auto operator()(const std::string &name) const {
148 return casadi::external(name, filename);
149 }
150 auto format_name(const std::string &name) const {
151 return filename + ':' + name;
152 }
153 } loader{filename};
155
156 this->n = impl->n;
157 this->m = impl->m;
158 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
159 this->C = Box<config_t>{impl->n};
160 this->D = Box<config_t>{impl->m};
161
162 auto data_filepath = fs::path{filename}.replace_extension("csv");
163 if (fs::exists(data_filepath))
164 load_numerical_data(data_filepath);
165}
166
167template <Config Conf>
169 : BoxConstrProblem<Conf>{0, 0} {
170
171 struct {
172 const SerializedCasADiFunctions &functions;
173 auto operator()(const std::string &name) const {
174 return casadi::Function::deserialize(functions.functions.at(name));
175 }
176 auto format_name(const std::string &name) const {
177 return "SerializedCasADiFunctions['" + name + "']";
178 }
179 } loader{functions};
181
182 this->n = impl->n;
183 this->m = impl->m;
184 this->param = vec::Constant(impl->p, alpaqa::NaN<Conf>);
185 this->C = Box<config_t>{impl->n};
186 this->D = Box<config_t>{impl->m};
187}
188
189template <Config Conf>
191 const std::filesystem::path &filepath, char sep) {
192 // Open data file
193 std::ifstream data_file{filepath};
194 if (!data_file)
195 throw std::runtime_error("Unable to open data file \"" +
196 filepath.string() + '"');
197
198 // Helper function for reading single line of (float) data
199 index_t line = 0;
200 auto wrap_data_load = [&](std::string_view name, auto &v, bool fixed_size) {
201 try {
202 ++line;
203 if (data_file.peek() == '\n') // Ignore empty lines
204 return static_cast<void>(data_file.get());
205 if (fixed_size) {
207 } else { // Dynamic size
208 auto s = csv::read_row_std_vector<real_t>(data_file, sep);
209 v = cmvec{s.data(), static_cast<index_t>(s.size())};
210 }
211 } catch (csv::read_error &e) {
212 // Transform any errors in something more readable
213 throw std::runtime_error("Unable to read " + std::string(name) +
214 " from data file \"" + filepath.string() +
215 ':' + std::to_string(line) +
216 "\": " + e.what());
217 }
218 };
219 // Helper function for reading a single value
220 auto read_single = [&](std::string_view name, auto &v) {
221 data_file >> v;
222 if (!data_file)
223 throw std::runtime_error("Unable to read " + std::string(name) +
224 " from data file \"" + filepath.string() +
225 ':' + std::to_string(line) + '"');
226 };
227 // Read the bounds, parameter value, and regularization
228 wrap_data_load("C.lowerbound", this->C.lowerbound, true);
229 wrap_data_load("C.upperbound", this->C.upperbound, true);
230 wrap_data_load("D.lowerbound", this->D.lowerbound, true);
231 wrap_data_load("D.upperbound", this->D.upperbound, true);
232 wrap_data_load("param", this->param, true);
233 wrap_data_load("l1_reg", this->l1_reg, false);
234 // Penalty/ALM split is a single integer
235 read_single("penalty_alm_split", this->penalty_alm_split);
236}
237
238template <Config Conf>
240template <Config Conf>
243template <Config Conf>
248
251
254 real_t f;
255 impl->f({x.data(), param.data()}, {&f});
256 return f;
257}
258
259template <Config Conf>
261 real_t f;
262 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
263}
264
265template <Config Conf>
267 real_t f;
268 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
269 return f;
270}
271
272template <Config Conf>
274 if (impl->constr)
275 impl->constr->g({x.data(), param.data()}, {g.data()});
276}
277
278template <Config Conf>
280 throw not_implemented_error("CasADiProblem::eval_grad_g_prod"); // TODO
281}
282
283template <Config Conf>
285 rvec, rvec) const {
286#if 0
287 impl->grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
288 this->D.lowerbound.data(), this->D.upperbound.data()},
289 {grad_ψ.data()});
290#else
291 // This seems to be faster than having a specialized function. Possibly
292 // cache-related?
293 real_t ψ;
294 impl->ψ_grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
295 this->D.lowerbound.data(), this->D.upperbound.data()},
296 {&ψ, grad_ψ.data()});
297#endif
298}
299
300template <Config Conf>
303 rvec) const {
304 real_t ψ;
305 impl->ψ_grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
306 this->D.lowerbound.data(), this->D.upperbound.data()},
307 {&ψ, grad_ψ.data()});
308 return ψ;
309}
310
311template <Config Conf>
313 rvec) const {
314 if (impl->constr)
315 impl->constr->grad_L({x.data(), param.data(), y.data()},
316 {grad_L.data()});
317 else
318 eval_f_grad_f(x, grad_L);
319}
320
321template <Config Conf>
324 real_t ψ;
325 if (impl->constr)
326 impl->constr->ψ({x.data(), param.data(), y.data(), Σ.data(),
327 this->D.lowerbound.data(), this->D.upperbound.data()},
328 {&ψ, ŷ.data()});
329 else
330 impl->f({x.data(), param.data()}, {&ψ});
331 return ψ;
332}
333
334template <Config Conf>
336 throw not_implemented_error("CasADiProblem::eval_grad_gi"); // TODO
337}
338
339template <Config Conf>
343 using map_t = typename SparseCSC::index_vector_map_t;
344 return SparseCSC{
345 .rows = static_cast<index_t>(sp.size1()),
346 .cols = static_cast<index_t>(sp.size2()),
347 .symmetry = symmetry,
348 .inner_idx = map_t{sp.row(), static_cast<index_t>(sp.nnz())},
349 .outer_ptr = map_t{sp.colind(), static_cast<index_t>(sp.size2()) + 1},
350 .order = SparseCSC::SortedRows,
351 };
352}
353
354template <Config Conf>
357 .rows = this->m,
358 .cols = this->n,
360 };
361 if (!impl->jac_g.has_value())
362 return dense;
363 auto &&sp = impl->jac_g->fun.sparsity_out(0);
364 return sp.is_dense()
365 ? Sparsity{dense}
367}
368
369template <Config Conf>
371 assert(impl->jac_g.has_value());
372 (*impl->jac_g)({x.data(), param.data()}, {J_values.data()});
373}
374
375template <Config Conf>
377 crvec v, rvec Hv) const {
378 assert(impl->hess_L_prod.has_value());
379 (*impl->hess_L_prod)({x.data(), param.data(), y.data(), &scale, v.data()},
380 {Hv.data()});
381}
382
383template <Config Conf>
386 .rows = this->n,
387 .cols = this->n,
388 .symmetry = sparsity::Symmetry::Upper,
389 };
390 if (!impl->hess_L.has_value())
391 return dense;
392 auto &&sp = impl->hess_L->fun.sparsity_out(0);
393 return sp.is_dense() ? Sparsity{dense}
395}
396
397template <Config Conf>
399 rvec H_values) const {
400 assert(impl->hess_L.has_value());
401 (*impl->hess_L)({x.data(), param.data(), y.data(), &scale},
402 {H_values.data()});
403}
404
405template <Config Conf>
408 rvec Hv) const {
409 assert(impl->hess_ψ_prod.has_value());
410 (*impl->hess_ψ_prod)({x.data(), param.data(), y.data(), Σ.data(), &scale,
411 this->D.lowerbound.data(), this->D.upperbound.data(),
412 v.data()},
413 {Hv.data()});
414}
415
416template <Config Conf>
419 .rows = this->n,
420 .cols = this->n,
421 .symmetry = sparsity::Symmetry::Upper,
422 };
423 if (!impl->hess_ψ.has_value())
424 return dense;
425 auto &&sp = impl->hess_ψ->fun.sparsity_out(0);
426 return sp.is_dense() ? Sparsity{dense}
428}
429
430template <Config Conf>
432 rvec H_values) const {
433 assert(impl->hess_ψ.has_value());
434 (*impl->hess_ψ)({x.data(), param.data(), y.data(), Σ.data(), &scale,
435 this->D.lowerbound.data(), this->D.upperbound.data()},
436 {H_values.data()});
437}
438
439template <Config Conf>
441 return false; // TODO
442}
443template <Config Conf>
445 return impl->jac_g.has_value();
446}
447template <Config Conf>
449 return impl->hess_L_prod.has_value();
450}
451template <Config Conf>
453 return impl->hess_L.has_value();
454}
455template <Config Conf>
457 return impl->hess_ψ_prod.has_value();
458}
459template <Config Conf>
461 return impl->hess_ψ.has_value();
462}
463
464} // namespace alpaqa
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
void eval_g(crvec x, rvec g) const
bool provides_eval_hess_ψ_prod() 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
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
Class for evaluating CasADi functions, allocating the necessary workspace storage in advance for allo...
void validate_dimensions(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:56
auto wrapped_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)
void read_row(std::istream &is, Eigen::Ref< Eigen::VectorX< float > > v, char sep)
Definition csv.hpp:34
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:65
typename Conf::index_t index_t
Definition config.hpp:77
Sparsity< Conf > convert_csc(const auto &sp, sparsity::Symmetry symmetry)
typename Conf::length_t length_t
Definition config.hpp:76
typename Conf::cmvec cmvec
Definition config.hpp:68
constexpr const auto inf
Definition config.hpp:85
typename Conf::rvec rvec
Definition config.hpp:69
typename Conf::crvec crvec
Definition config.hpp:70
std::map< std::string, std::string > 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, 4, 1 > > hess_L
CasADiFunctionEvaluator< Conf, 2, 1 > f
std::optional< CasADiFunctionEvaluator< Conf, 8, 1 > > hess_ψ_prod
CasADiFunctionEvaluator< Conf, 6, 2 > ψ_grad_ψ
std::optional< CasADiFunctionEvaluator< Conf, 2, 1 > > jac_g
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