alpaqa_interface.cpp
1 /*
2  * This file is part of CasADi.
3  *
4  * CasADi -- A symbolic framework for dynamic optimization.
5  * Copyright (C) 2010-2023 Joel Andersson, Joris Gillis, Moritz Diehl,
6  * KU Leuven. All rights reserved.
7  * Copyright (C) 2011-2014 Greg Horn
8  *
9  * CasADi is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 3 of the License, or (at your option) any later version.
13  *
14  * CasADi is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with CasADi; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22  *
23  */
24 
25 
26 #include "alpaqa_interface.hpp"
27 #include <vector>
28 #include <alpaqa/params/params.hpp>
29 
30 namespace casadi {
31 
32  extern "C"
33  int CASADI_NLPSOL_ALPAQA_EXPORT
34  casadi_register_nlpsol_alpaqa(Nlpsol::Plugin* plugin) {
35  plugin->creator = AlpaqaInterface::creator;
36  plugin->name = "alpaqa";
37  plugin->doc = AlpaqaInterface::meta_doc.c_str();
38  plugin->version = CASADI_VERSION;
39  plugin->options = &AlpaqaInterface::options_;
40  plugin->deserialize = &AlpaqaInterface::deserialize;
41  return 0;
42  }
43 
44  extern "C"
45  void CASADI_NLPSOL_ALPAQA_EXPORT casadi_load_nlpsol_alpaqa() {
47  }
48 
49  AlpaqaInterface::AlpaqaInterface(const std::string& name, const Function& nlp)
50  : Nlpsol(name, nlp) {
51  }
52 
54  clear_mem();
55  }
56 
58  = {{&Nlpsol::options_},
59  {{"alpaqa",
60  {OT_DICT,
61  "Options to be passed to Alpaqa"}}
62  }
63  };
64 
65  const std::string AlpaqaInterface::meta_doc = "";
66 
67 
68  void AlpaqaInterface::init(const Dict& opts) {
69  Nlpsol::init(opts);
70 
71  // Read user options
72  for (auto&& op : opts) {
73  if (op.first=="alpaqa") {
74  opts_ = Options::sanitize(op.second);
75  }
76  }
77 
78  calc_lam_x_ = true;
79 
80  MX x = MX::sym("x", nx_);
81  MX p = MX::sym("p", np_);
82  MX y = MX::sym("y", ng_);
83  MX s = MX::sym("s");
84  MX v = MX::sym("v", nx_);
85  MX sigma = MX::sym("sigma", ng_);
86  MX zl = MX::sym("zl", ng_);
87  MX zu = MX::sym("zu", ng_);
88 
89  std::vector<MX> ret = oracle_(std::vector<MX>{x, p});
90  MX f = ret[0];
91  MX g = ret[1];
92 
93  MX sL = s * f + dot(y, g);
94  MX L = f + dot(y, g);
95  MX zeta = g + (y / sigma);
96  MX z_hat = fmax(zl, fmin(zeta, zu));
97  MX d = zeta - z_hat;
98  MX y_hat = sigma * d;
99  MX s_psi = s * f + 0.5 * dot(y_hat, d);
100  MX psi = f + 0.5 * dot(y_hat, d);
101 
102  // Setup NLP functions
103  create_function("nlp_f", {"x", "p"}, {"f"});
104  create_function("nlp_grad_f", {"x", "p"}, {"f", "grad:f:x"});
105  create_function("nlp_g", {"x", "p"}, {"g"});
106  create_function("nlp_jac_g", {"x", "p"}, {"jac:g:x"});
107 
108  create_function("nlp_psi", {x, p, y, sigma, zl, zu}, {psi, y_hat},
109  {"x", "p", "y", "sigma", "zl", "zu"}, {"psi", "y_hat"});
110 
111  MX grad_psi = gradient(psi, x);
112  create_function("nlp_grad_psi", {x, p, y, sigma, zl, zu}, {psi, grad_psi},
113  {"x", "p", "y", "sigma", "zl", "zu"}, {"psi", "grad_psi"});
114 
115  MX grad_L = gradient(L, x);
116  create_function("nlp_grad_L", {x, p, y}, {grad_L},
117  {"x", "p", "y"}, {"grad_L"});
118 
119  MX hess_L = hessian(sL, x)(0);
120  if (!hess_L.is_dense()) hess_L = triu(hess_L);
121  create_function("nlp_hess_L", {x, p, y, s}, {hess_L},
122  {"x", "p", "y", "s"}, {"hess_L"});
123 
124  MX L_prod = gradient(jtimes(sL, x, v, false), x);
125  create_function("nlp_hess_L_prod", {x, p, y, s, v}, {L_prod},
126  {"x", "p", "y", "s", "v"}, {"L_prod"});
127 
128  MX hess_psi = hessian(sL, x)(0);
129  if (!hess_psi.is_dense()) hess_psi = triu(hess_psi);
130  create_function("nlp_hess_psi", {x, p, y, sigma, s, zl, zu}, {hess_psi},
131  {"x", "p", "y", "sigma", "s", "zl", "zu"}, {"hess_psi"});
132 
133  MX hess_psi_prod = gradient(jtimes(s_psi, x, v, false), x);
134  create_function("nlp_hess_psi_prod", {x, p, y, sigma, s, zl, zu, v}, {hess_psi},
135  {"x", "p", "y", "sigma", "s", "zl", "zu", "v"}, {"hess_psi_prod"});
136  }
137 
139  int AlpaqaInterface::init_mem(void* mem) const {
140  if (Nlpsol::init_mem(mem)) return 1;
141 
142  AlpaqaMemory* m = static_cast<AlpaqaMemory*>(mem);
143 
144 
145  return 0;
146  }
147 
148  void AlpaqaInterface::clear_mem_at(AlpaqaMemory* m) const {
149 
150  }
151 
152  void AlpaqaInterface::free_mem(void *mem) const {
153  AlpaqaMemory* m = static_cast<AlpaqaMemory*>(mem);
154 
155 
156  delete m;
157  }
158 
159 
161  Dict AlpaqaInterface::get_stats(void* mem) const {
162  Dict stats = Nlpsol::get_stats(mem);
163 
164  AlpaqaMemory* m = static_cast<AlpaqaMemory*>(mem);
165 
166  return stats;
167  }
168 
170  void AlpaqaInterface::set_work(void* mem, const double**& arg, double**& res,
171  casadi_int*& iw, double*& w) const {
172 
173  // Set work in base classes
174  Nlpsol::set_work(mem, arg, res, iw, w);
175 
176  AlpaqaMemory* m = static_cast<AlpaqaMemory*>(mem);
177 
178  check_inputs(m);
179 
180  // clear_mem(m);
181 
182  casadi_nlpsol_data<double>& d_nlp = m->d_nlp;
183 
184 
185  return;
186  }
187 
188  // Solve the NLP
189  int AlpaqaInterface::solve(void* mem) const {
190  AlpaqaMemory* m = static_cast<AlpaqaMemory*>(mem);
191  auto d_nlp = &m->d_nlp;
192  AlpaqaProblem problem(*this, m);
193 
194  //problem.l1_reg = 0;
195  //problem.penalty_alm_split = 0;
196  casadi_copy(d_nlp->lbz, nx_, problem.C.lowerbound.data());
197  casadi_copy(d_nlp->ubz, nx_, problem.C.upperbound.data());
198  casadi_copy(d_nlp->lbz+nx_, ng_, problem.D.lowerbound.data());
199  casadi_copy(d_nlp->ubz+nx_, ng_, problem.D.upperbound.data());
200 
201  // Wrap the problem to count the function evaluations
202  auto counted_problem = alpaqa::problem_with_counters_ref(problem);
203 
204  // Define the solvers to use
205  using Direction = alpaqa::LBFGSDirection<alpaqa::DefaultConfig>;
206  using InnerSolver = alpaqa::PANOCSolver<Direction>;
207  using OuterSolver = alpaqa::ALMSolver<InnerSolver>;
208 
209  // Settings for the outer augmented Lagrangian method
210  OuterSolver::Params almparam;
211 
212  // Settings for the inner PANOC solver
213  InnerSolver::Params panocparam;
214 
215  // Settings for the L-BFGS algorithm used by PANOC
216  Direction::AcceleratorParams lbfgsparam;
217 
218  for (auto&& op : opts_) {
219  if (op.first=="alm") {
220  for (auto&& kv : op.second.to_dict())
221  alpaqa::params::set_param(almparam, {.full_key = kv.first, .key = kv.first, .value = str(kv.second)});
222  } else if (op.first=="panoc") {
223  for (auto&& kv : op.second.to_dict())
224  alpaqa::params::set_param(panocparam, {.full_key = kv.first, .key = kv.first, .value = str(kv.second)});
225  } else if (op.first=="lbfgs") {
226  for (auto&& kv : op.second.to_dict())
227  alpaqa::params::set_param(lbfgsparam, {.full_key = kv.first, .key = kv.first, .value = str(kv.second)});
228  } else {
229  casadi_error("Unknown option: " + op.first);
230  }
231  }
232 
233  // Create an ALM solver using PANOC as inner solver
234  OuterSolver solver{
235  almparam, // params for outer solver
236  {panocparam, lbfgsparam}, // inner solver
237  };
238  m->solver = &solver;
239 
240  // Initial guess
241  alpaqa::DefaultConfig::vec x(nx_);
242  alpaqa::DefaultConfig::vec y(ng_);
243  casadi_copy(d_nlp->z, nx_, x.data());
244  casadi_copy(d_nlp->lam+nx_, ng_, y.data());
245 
246  // Solve the problem
247  auto stats = solver(counted_problem, x, y);
248  // y and x have been overwritten by the solution
249 
250  casadi_copy(x.data(), nx_, d_nlp->z);
251  casadi_copy(y.data(), ng_, d_nlp->lam+nx_);
252  d_nlp->objective = problem.eval_f(x);
253  alpaqa::DefaultConfig::vec g(ng_);
254  problem.eval_g(x, g);
255  casadi_copy(g.data(), ng_, d_nlp->z+nx_);
256 
257  if (stats.status == alpaqa::SolverStatus::Converged) {
258  m->success = true;
259  } else if (stats.status == alpaqa::SolverStatus::MaxTime || stats.status == alpaqa::SolverStatus::MaxIter) {
261  } else if (stats.status == alpaqa::SolverStatus::NotFinite) {
263  } else if (stats.status == alpaqa::SolverStatus::Interrupted) {
265  }
266 
267  if (verbose_) {
268  // Print the results
269  uout() << '\n' << *counted_problem.evaluations << '\n';
270  uout() << "status: " << stats.status << '\n'
271  << "f = " << problem.eval_f(x) << '\n'
272  << "inner iterations: " << stats.inner.iterations << '\n'
273  << "outer iterations: " << stats.outer_iterations << '\n'
274  << "ε = " << stats.ε << '\n'
275  << "δ = " << stats.δ << '\n'
276  << "elapsed time: "
277  << std::chrono::duration<double>{stats.elapsed_time}.count()
278  << " s" << '\n'
279  << "x = " << x.transpose() << '\n'
280  << "y = " << y.transpose() << '\n'
281  << "avg τ = " << (stats.inner.sum_τ / stats.inner.count_τ) << '\n'
282  << "L-BFGS rejected = " << stats.inner.lbfgs_rejected << '\n'
283  << "L-BFGS failures = " << stats.inner.lbfgs_failures << '\n'
284  << "Line search failures = " << stats.inner.linesearch_failures
285  << '\n'
286  << std::endl;
287  }
288 
289  return 0;
290  }
291 
292 
294  s.version("AlpaqaInterface", 1);
295  s.unpack("AlpaqaInterface::jacg_sp", jacg_sp_);
296  s.unpack("AlpaqaInterface::opts", opts_);
297  }
298 
301  s.version("AlpaqaInterface", 1);
302  s.pack("AlpaqaInterface::jacg_sp", jacg_sp_);
303  s.pack("AlpaqaInterface::opts", opts_);
304  }
305 
306 } // namespace casadi
AlpaqaInterface(const std::string &name, const Function &nlp)
int solve(void *mem) const override
static ProtoFunction * deserialize(DeserializingStream &s)
Deserialize into MX.
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
Dict get_stats(void *mem) const override
Get all statistics.
Dict opts_
All Alpaqa options.
static const Options options_
void free_mem(void *mem) const override
Free memory block.
int init_mem(void *mem) const override
Initalize memory block.
void init(const Dict &opts) override
Initialize.
void set_work(void *mem, const double **&arg, double **&res, casadi_int *&iw, double *&w) const override
Set the (persistent) work vectors.
static Nlpsol * creator(const std::string &name, const Function &nlp)
static const std::string meta_doc
void eval_g(crvec x, rvec g) const
real_t eval_f(crvec x) const
Helper class for Serialization.
void unpack(Sparsity &e)
Reconstruct an object from the input stream.
void version(const std::string &name, int v)
Function object.
Definition: function.hpp:60
static MX sym(const std::string &name, casadi_int nrow=1, casadi_int ncol=1)
Create an nrow-by-ncol symbolic primitive.
MX - Matrix expression.
Definition: mx.hpp:92
NLP solver storage class.
Definition: nlpsol_impl.hpp:59
Dict get_stats(void *mem) const override
Get all statistics.
Definition: nlpsol.cpp:1162
static const Options options_
Options.
void init(const Dict &opts) override
Initialize.
Definition: nlpsol.cpp:420
casadi_int ng_
Number of constraints.
Definition: nlpsol_impl.hpp:69
virtual void check_inputs(void *mem) const
Check if the inputs correspond to a well-posed problem.
Definition: nlpsol.cpp:613
int init_mem(void *mem) const override
Initalize memory block.
Definition: nlpsol.cpp:603
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
Definition: nlpsol.cpp:1306
casadi_int np_
Number of parameters.
Definition: nlpsol_impl.hpp:72
bool calc_lam_x_
Options.
Definition: nlpsol_impl.hpp:97
casadi_int nx_
Number of variables.
Definition: nlpsol_impl.hpp:66
void set_work(void *mem, const double **&arg, double **&res, casadi_int *&iw, double *&w) const override
Set the (persistent) work vectors.
Definition: nlpsol.cpp:795
Function oracle_
Oracle: Used to generate other functions.
Function create_function(const Function &oracle, const std::string &fname, const std::vector< std::string > &s_in, const std::vector< std::string > &s_out, const Function::AuxOut &aux=Function::AuxOut(), const Dict &opts=Dict())
static void registerPlugin(const Plugin &plugin, bool needs_lock=true)
Register an integrator in the factory.
bool verbose_
Verbose printout.
void clear_mem()
Clear all memory (called from destructor)
Helper class for Serialization.
void version(const std::string &name, int v)
void pack(const Sparsity &e)
Serializes an object to the output stream.
The casadi namespace.
Definition: archiver.cpp:28
void CASADI_NLPSOL_ALPAQA_EXPORT casadi_load_nlpsol_alpaqa()
void casadi_copy(const T1 *x, casadi_int n, T1 *y)
COPY: y <-x.
std::string str(const T &v)
String representation, any type.
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
T dot(const std::vector< T > &a, const std::vector< T > &b)
int CASADI_NLPSOL_ALPAQA_EXPORT casadi_register_nlpsol_alpaqa(Nlpsol::Plugin *plugin)
std::ostream & uout()
@ SOLVER_RET_NAN
@ SOLVER_RET_LIMITED
alpaqa::ALMSolver< InnerSolver > * solver
UnifiedReturnStatus unified_return_status
Definition: nlpsol_impl.hpp:48
casadi_nlpsol_data< double > d_nlp
Definition: nlpsol_impl.hpp:42
Options metadata for a class.
Definition: options.hpp:40
static Dict sanitize(const Dict &opts, bool top_level=true)
Sanitize a options dictionary.
Definition: options.cpp:173