alpaqa sparse
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>
31 // CasADiFunctionEvaluator<6, 1> grad_ψ;
38 std::optional<ConstrFun> constr = 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, 8, 1>> hess_ψ_prod =
43 std::nullopt;
44 std::optional<CasADiFunctionEvaluator<Conf, 7, 1>> hess_ψ = std::nullopt;
45 std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> jac_g = std::nullopt;
46};
47
48} // namespace casadi_loader
49
50namespace detail {
51
52template <Config Conf>
54 return static_cast<index_t<Conf>>(i);
55}
56} // namespace detail
57
58template <Config Conf>
60 : BoxConstrProblem<Conf>{0, 0} {
61 using namespace casadi_loader;
62 length_t n = 0, m = 0, p = 0;
64 [&]() -> std::optional<CasADiFunctionEvaluator<Conf, 2, 1>> {
65 casadi::Function gfun = casadi::external("g", so_name);
66 using namespace std::literals::string_literals;
67 if (gfun.n_in() != 2)
68 throw std::invalid_argument(
69 "Invalid number of input arguments: got "s +
70 std::to_string(gfun.n_in()) + ", should be 2.");
71 if (gfun.n_out() > 1)
72 throw std::invalid_argument(
73 "Invalid number of output arguments: got "s +
74 std::to_string(gfun.n_in()) + ", should be 0 or 1.");
75 if (gfun.size2_in(0) != 1)
76 throw std::invalid_argument(
77 "First input argument should be a column vector.");
78 if (gfun.size2_in(1) != 1)
79 throw std::invalid_argument(
80 "Second input argument should be a column vector.");
81 if (gfun.n_out() == 1 && gfun.size2_out(0) != 1)
82 throw std::invalid_argument(
83 "First output argument should be a column vector.");
84 n = static_cast<length_t>(gfun.size1_in(0));
85 if (gfun.n_out() == 1)
86 m = static_cast<length_t>(gfun.size1_out(0));
87 p = static_cast<length_t>(gfun.size1_in(1));
88 if (gfun.n_out() == 0) {
89 if (m != 0)
90 throw std::invalid_argument(
91 "Function g has no outputs but m != 0");
92 return std::nullopt;
93 }
94 CasADiFunctionEvaluator<Conf, 2, 1> g{std::move(gfun)};
95 g.validate_dimensions({dim(n, 1), dim(p, 1)}, {dim(m, 1)});
96 return std::make_optional(std::move(g));
97 };
98
100
101 this->n = n;
102 this->m = m;
103 this->param = vec::Constant(p, alpaqa::NaN<Conf>);
104 this->C = Box<config_t>{n};
105 this->D = Box<config_t>{m};
106
107 impl = std::make_unique<CasADiFunctionsWithParam<Conf>>(
110 so_name, "f", dims(n, p), dims(1)),
111 .f_grad_f = wrapped_load<CasADiFunctionEvaluator<Conf, 2, 2>>( //
112 so_name, "f_grad_f", dims(n, p), dims(1, n)),
113 // .grad_ψ = wrapped_load<CasADiFunctionEvaluator<6, 1>>( //
114 // so_name, "grad_psi", dims(n, p, m, m, m, m), dims(n)),
115 .ψ_grad_ψ = wrapped_load<CasADiFunctionEvaluator<Conf, 6, 2>>( //
116 so_name, "psi_grad_psi", dims(n, p, m, m, m, m), dims(1, n)),
117 });
118
119 if (g)
120 impl->constr = {
121 std::move(*g),
123 so_name, "grad_L", dims(n, p, m), dims(n)),
124 wrapped_load<CasADiFunctionEvaluator<Conf, 6, 2>>( //
125 so_name, "psi", dims(n, p, m, m, m, m), dims(1, m)),
126 };
127
128 impl->hess_L_prod = try_load<CasADiFunctionEvaluator<Conf, 5, 1>>( //
129 so_name, "hess_L_prod", dims(n, p, m, 1, n), dims(n));
131 so_name, "hess_L", dims(n, p, m, 1), dims(dim(n, n)));
132 impl->hess_ψ_prod = try_load<CasADiFunctionEvaluator<Conf, 8, 1>>( //
133 so_name, "hess_psi_prod", dims(n, p, m, m, 1, m, m, n), dims(n));
135 so_name, "hess_psi", dims(n, p, m, m, 1, m, m), dims(dim(n, n)));
137 so_name, "jacobian_g", dims(n, p), dims(dim(m, n)));
138
139 auto data_filepath = fs::path{so_name}.replace_extension("csv");
140 if (fs::exists(data_filepath))
141 load_numerical_data(data_filepath);
142}
143
144template <Config Conf>
146 const std::filesystem::path &filepath, char sep) {
147 // Open data file
148 std::ifstream data_file{filepath};
149 if (!data_file)
150 throw std::runtime_error("Unable to open data file \"" +
151 filepath.string() + '"');
152
153 // Helper function for reading single line of (float) data
154 index_t line = 0;
155 auto wrap_data_load = [&](std::string_view name, auto &v, bool fixed_size) {
156 try {
157 ++line;
158 if (data_file.peek() == '\n') // Ignore empty lines
159 return static_cast<void>(data_file.get());
160 if (fixed_size) {
162 } else { // Dynamic size
163 auto s = csv::read_row_std_vector<real_t>(data_file, sep);
164 v = cmvec{s.data(), static_cast<index_t>(s.size())};
165 }
166 } catch (csv::read_error &e) {
167 // Transform any errors in something more readable
168 throw std::runtime_error("Unable to read " + std::string(name) +
169 " from data file \"" + filepath.string() +
170 ':' + std::to_string(line) +
171 "\": " + e.what());
172 }
173 };
174 // Helper function for reading a single value
175 auto read_single = [&](std::string_view name, auto &v) {
176 data_file >> v;
177 if (!data_file)
178 throw std::runtime_error("Unable to read " + std::string(name) +
179 " from data file \"" + filepath.string() +
180 ':' + std::to_string(line) + '"');
181 };
182 // Read the bounds, parameter value, and regularization
183 wrap_data_load("C.lowerbound", this->C.lowerbound, true);
184 wrap_data_load("C.upperbound", this->C.upperbound, true);
185 wrap_data_load("D.lowerbound", this->D.lowerbound, true);
186 wrap_data_load("D.upperbound", this->D.upperbound, true);
187 wrap_data_load("param", this->param, true);
188 wrap_data_load("l1_reg", this->l1_reg, false);
189 // Penalty/ALM split is a single integer
190 read_single("penalty_alm_split", this->penalty_alm_split);
191}
192
193template <Config Conf>
195template <Config Conf>
198template <Config Conf>
203
206
209 real_t f;
210 impl->f({x.data(), param.data()}, {&f});
211 return f;
212}
213
214template <Config Conf>
216 real_t f;
217 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
218}
219
220template <Config Conf>
222 real_t f;
223 impl->f_grad_f({x.data(), param.data()}, {&f, grad_fx.data()});
224 return f;
225}
226
227template <Config Conf>
229 if (impl->constr)
230 impl->constr->g({x.data(), param.data()}, {g.data()});
231}
232
233template <Config Conf>
235 throw not_implemented_error("CasADiProblem::eval_grad_g_prod"); // TODO
236}
237
238template <Config Conf>
240 rvec, rvec) const {
241#if 0
242 impl->grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
243 this->D.lowerbound.data(), this->D.upperbound.data()},
244 {grad_ψ.data()});
245#else
246 // This seems to be faster than having a specialized function. Possibly
247 // cache-related?
248 real_t ψ;
249 impl->ψ_grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
250 this->D.lowerbound.data(), this->D.upperbound.data()},
251 {&ψ, grad_ψ.data()});
252#endif
253}
254
255template <Config Conf>
258 rvec) const {
259 real_t ψ;
260 impl->ψ_grad_ψ({x.data(), param.data(), y.data(), Σ.data(),
261 this->D.lowerbound.data(), this->D.upperbound.data()},
262 {&ψ, grad_ψ.data()});
263 return ψ;
264}
265
266template <Config Conf>
268 rvec) const {
269 if (impl->constr)
270 impl->constr->grad_L({x.data(), param.data(), y.data()},
271 {grad_L.data()});
272 else
273 eval_f_grad_f(x, grad_L);
274}
275
276template <Config Conf>
279 real_t ψ;
280 if (impl->constr)
281 impl->constr->ψ({x.data(), param.data(), y.data(), Σ.data(),
282 this->D.lowerbound.data(), this->D.upperbound.data()},
283 {&ψ, ŷ.data()});
284 else
285 impl->f({x.data(), param.data()}, {&ψ});
286 return ψ;
287}
288
289template <Config Conf>
291 throw not_implemented_error("CasADiProblem::eval_grad_gi"); // TODO
292}
293
294template <Config Conf>
297 using index_vector_map_t = typename SparseCSC::index_vector_map_t;
298 return SparseCSC{
299 .rows = sp.size1(),
300 .cols = sp.size2(),
301 .symmetry = symmetry,
302 .inner_idx = index_vector_map_t{sp.row(), sp.nnz()},
303 .outer_ptr = index_vector_map_t{sp.colind(), sp.size2() + 1},
304 .order = SparseCSC::SortedRows,
305 };
306}
307
308template <Config Conf>
311 .rows = this->m,
312 .cols = this->n,
314 };
315 if (!impl->jac_g.has_value())
316 return dense;
317 auto &&sp = impl->jac_g->fun.sparsity_out(0);
318 return sp.is_dense()
319 ? Sparsity{dense}
321}
322
323template <Config Conf>
325 assert(impl->jac_g.has_value());
326 (*impl->jac_g)({x.data(), param.data()}, {J_values.data()});
327}
328
329template <Config Conf>
331 crvec v, rvec Hv) const {
332 assert(impl->hess_L_prod.has_value());
333 (*impl->hess_L_prod)({x.data(), param.data(), y.data(), &scale, v.data()},
334 {Hv.data()});
335}
336
337template <Config Conf>
340 .rows = this->n,
341 .cols = this->n,
342 .symmetry = sparsity::Symmetry::Upper,
343 };
344 if (!impl->hess_L.has_value())
345 return dense;
346 auto &&sp = impl->hess_L->fun.sparsity_out(0);
347 return sp.is_dense() ? Sparsity{dense}
349}
350
351template <Config Conf>
353 rvec H_values) const {
354 assert(impl->hess_L.has_value());
355 (*impl->hess_L)({x.data(), param.data(), y.data(), &scale},
356 {H_values.data()});
357}
358
359template <Config Conf>
362 rvec Hv) const {
363 assert(impl->hess_ψ_prod.has_value());
364 (*impl->hess_ψ_prod)({x.data(), param.data(), y.data(), Σ.data(), &scale,
365 this->D.lowerbound.data(), this->D.upperbound.data(),
366 v.data()},
367 {Hv.data()});
368}
369
370template <Config Conf>
373 .rows = this->n,
374 .cols = this->n,
375 .symmetry = sparsity::Symmetry::Upper,
376 };
377 if (!impl->hess_ψ.has_value())
378 return dense;
379 auto &&sp = impl->hess_ψ->fun.sparsity_out(0);
380 return sp.is_dense() ? Sparsity{dense}
382}
383
384template <Config Conf>
386 rvec H_values) const {
387 assert(impl->hess_ψ.has_value());
388 (*impl->hess_ψ)({x.data(), param.data(), y.data(), Σ.data(), &scale,
389 this->D.lowerbound.data(), this->D.upperbound.data()},
390 {H_values.data()});
391}
392
393template <Config Conf>
395 return false; // TODO
396}
397template <Config Conf>
399 return impl->jac_g.has_value();
400}
401template <Config Conf>
403 return impl->hess_L_prod.has_value();
404}
405template <Config Conf>
407 return impl->hess_L.has_value();
408}
409template <Config Conf>
411 return impl->hess_ψ_prod.has_value();
412}
413template <Config Conf>
415 return impl->hess_ψ.has_value();
416}
417
418} // 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...
#define USING_ALPAQA_CONFIG(Conf)
Definition config.hpp:56
std::optional< CasADiFunctionEvaluator< Conf, 5, 1 > > hess_L_prod
std::optional< CasADiFunctionEvaluator< Conf, 7, 1 > > hess_ψ
constexpr auto dims(auto... a)
std::optional< CasADiFunctionEvaluator< Conf, 4, 1 > > hess_L
std::pair< casadi_int, casadi_int > dim
CasADiFunctionEvaluator< Conf, 2, 1 > f
auto wrap_load(const std::string &so_name, const char *name, F 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
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:11
@ Upper
Symmetric, upper-triangular part is stored.
Dense matrix structure.
Definition sparsity.hpp:20
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
Sparse compressed-column structure (CCS or CSC).
Definition sparsity.hpp:28
Stores any of the supported sparsity patterns.
Definition sparsity.hpp:105