madnlp_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 "madnlp_interface.hpp"
27 #include <madnlp_runtime_str.h>
28 
29 #include "casadi/core/casadi_misc.hpp"
30 #include "../../core/global_options.hpp"
31 #include "../../core/casadi_interrupt.hpp"
32 #include "../../core/convexify.hpp"
33 
34 #include <ctime>
35 #include <stdlib.h>
36 #include <iostream>
37 #include <iomanip>
38 #include <chrono>
39 #include <cstring>
40 #include <string>
41 
42 namespace casadi {
43 
44 extern "C"
45 int CASADI_NLPSOL_MADNLP_EXPORT
46 casadi_register_nlpsol_madnlp(Nlpsol::Plugin* plugin) {
47  plugin->creator = MadnlpInterface::creator;
48  plugin->name = "madnlp";
49  plugin->doc = MadnlpInterface::meta_doc.c_str();
50  plugin->version = CASADI_VERSION;
51  plugin->options = &MadnlpInterface::options_;
52  plugin->deserialize = &MadnlpInterface::deserialize;
53  return 0;
54 }
55 
56 extern "C"
57 void CASADI_NLPSOL_MADNLP_EXPORT casadi_load_nlpsol_madnlp() {
59 }
60 
61 MadnlpInterface::MadnlpInterface(const std::string& name, const Function& nlp)
62  : Nlpsol(name, nlp) {
63 }
64 
66  clear_mem();
67 }
68 
70 = {{&Nlpsol::options_},
71  {{"madnlp",
72  {OT_DICT,
73  "Options to be passed to madnlp"}},
74  {"convexify_strategy",
75  {OT_STRING,
76  "NONE|regularize|eigen-reflect|eigen-clip. "
77  "Strategy to convexify the Lagrange Hessian before passing it to the solver."}},
78  {"convexify_margin",
79  {OT_DOUBLE,
80  "When using a convexification strategy, make sure that "
81  "the smallest eigenvalue is at least this (default: 1e-7)."}},
82  }
83 };
84 
85 // Recursively flatten options for libmad options dict.
86 void flatten_opts(Dict& ret, const Dict& opts, const std::string& prefix) {
87  for (const auto& kv : opts) {
88  switch (kv.second.getType()) {
89  case OT_DICT:
90  flatten_opts(ret, kv.second, prefix + kv.first + ".");
91  break;
92  default:
93  ret[prefix + kv.first] = kv.second;
94  }
95  }
96 }
97 
98 void MadnlpInterface::init(const Dict& opts) {
99  // Call the init method of the base class
100  Nlpsol::init(opts);
101 
102  casadi_int struct_cnt=0;
103 
104  // Default options
105  std::string convexify_strategy = "none";
106  double convexify_margin = 1e-7;
107  casadi_int max_iter_eig = 200;
108  convexify_ = false;
109 
110  calc_g_ = true;
111  calc_f_ = true;
112 
113  // Read options
114  for (auto&& op : opts) {
115  if (op.first=="convexify_strategy") {
116  convexify_strategy = op.second.to_string();
117  } else if (op.first=="convexify_margin") {
118  convexify_margin = op.second;
119  } else if (op.first=="max_iter") {
120  max_iter_eig = op.second;
121  } else if (op.first=="madnlp") {
122  flatten_opts(opts_, op.second, "");
123  }
124  }
125 
126  // Do we need second order derivatives?
127  exact_hessian_ = true;
128  auto hessian_approximation = opts_.find("hessian_approximation");
129  if (hessian_approximation!=opts_.end()) {
130  exact_hessian_ = hessian_approximation->second == "exact";
131  }
132 
133  // Setup NLP functions
134  create_function("nlp_f", {"x", "p"}, {"f"});
135  create_function("nlp_g", {"x", "p"}, {"g"});
136 
137  if (!has_function("nlp_grad_f")) {
138  create_function("nlp_grad_f", {"x", "p"}, {"grad:f:x"});
139  }
140 
141  if (!has_function("nlp_jac_g")) {
142  create_function("nlp_jac_g", {"x", "p"}, {"jac:g:x"});
143  }
144  jacg_sp_ = get_function("nlp_jac_g").sparsity_out(0);
145 
146  if (!has_function("nlp_hess_l")) {
147  create_function("nlp_hess_l", {"x", "p", "lam:f", "lam:g"},
148  {"tril:hess:gamma:x:x"}, {{"gamma", {"f", "g"}}});
149  }
150  hesslag_sp_ = get_function("nlp_hess_l").sparsity_out(0);
151  casadi_assert(hesslag_sp_.is_tril(), "Hessian must be lower triangular");
152 
153  if (convexify_strategy!="none") {
154  convexify_ = true;
155  Dict opts;
156  opts["strategy"] = convexify_strategy;
157  opts["margin"] = convexify_margin;
158  opts["max_iter_eig"] = max_iter_eig;
159  opts["verbose"] = verbose_;
161  }
162 
163  set_madnlp_prob();
164 
165  // Allocate memory
166  casadi_int sz_arg, sz_res, sz_w, sz_iw;
167  casadi_madnlp_work(&p_, &sz_arg, &sz_res, &sz_iw, &sz_w);
168 
169  alloc_arg(sz_arg, true);
170  alloc_res(sz_res, true);
171  alloc_iw(sz_iw, true);
172  alloc_w(sz_w, true);
173 
174  std::vector<char*> _argv = {};
175  std::string s;
176 
177  int argc = _argv.size();
178  char** argv = reinterpret_cast<char**>(_argv.data());
179 
180 }
181 
182 int MadnlpInterface::init_mem(void* mem) const {
183  if (Nlpsol::init_mem(mem)) return 1;
184  if (!mem) return 1;
185  auto m = static_cast<MadnlpMemory*>(mem);
186 
187  // Now create the new options struct
188  libmad_create_options_dict(&(m->d.libmad_opts));
189  for (const auto& kv : opts_) {
190  switch (kv.second.getType()) {
191  case OT_DOUBLE:
192  libmad_set_double_option(m->d.libmad_opts, kv.first.c_str(), kv.second);
193  break;
194  case OT_INT:
195  libmad_set_int64_option(m->d.libmad_opts, kv.first.c_str(), kv.second.to_int());
196  break;
197  case OT_STRING:
198  {
199  std::string s = kv.second.to_string();
200  libmad_set_string_option(m->d.libmad_opts, kv.first.c_str(), s.c_str());
201  }
202  break;
203  case OT_BOOL:
204  libmad_set_bool_option(m->d.libmad_opts, kv.first.c_str(), kv.second.to_bool());
205  break;
206  default:
207  casadi_error("Unknown option type.");
208  }
209  }
210  casadi_madnlp_init_mem(&m->d);
211 
212  return 0;
213 }
214 
215 void MadnlpInterface::free_mem(void* mem) const {
216  auto m = static_cast<MadnlpMemory*>(mem);
217  casadi_madnlp_free_mem(&m->d);
218  delete static_cast<MadnlpMemory*>(mem);
219 }
220 
222 void MadnlpInterface::set_work(void* mem, const double**& arg, double**& res,
223  casadi_int*& iw, double*& w) const {
224  auto m = static_cast<MadnlpMemory*>(mem);
225 
226  // Set work in base classes
227  Nlpsol::set_work(mem, arg, res, iw, w);
228 
229  m->d.prob = &p_;
230  m->d.nlp = &m->d_nlp;
231 
232  casadi_madnlp_set_work(&m->d, &arg, &res, &iw, &w);
233 
234  m->d.nlp->oracle->m = static_cast<void*>(m);
235 }
236 
237 int MadnlpInterface::solve(void* mem) const {
238  int ret;
239  auto m = static_cast<MadnlpMemory*>(mem);
240 
241  ret = casadi_madnlp_presolve(&m->d);
242  casadi_assert(ret==0, "MadNLPError in presolve");
243 
244  ret = casadi_madnlp_solve(&m->d);
245  casadi_assert(ret==0, "MadNLPError in solve");
246 
247  m->success = m->d.success;
248  m->unified_return_status = static_cast<UnifiedReturnStatus>(m->d.unified_return_status);
249 
250  return 0;
251 }
252 
254  Dict stats = Nlpsol::get_stats(mem);
255  auto m = static_cast<MadnlpMemory*>(mem);
256  libmad_int iter, status;
257  int ret;
258  double primal_feas, dual_feas;
259  ret = madnlp_get_iters(m->d.stats, &iter); casadi_assert(ret==0, "MadNLPError in get_iters");
260  ret = madnlp_get_status(m->d.stats, &status); casadi_assert(ret==0, "MadNLPError in get_status");
261  ret = madnlp_get_dual_feas(m->d.stats, &dual_feas);
262  casadi_assert(ret==0, "MadNLPError in get_dual_feas");
263  ret = madnlp_get_primal_feas(m->d.stats, &primal_feas);
264  casadi_assert(ret==0, "MadNLPError in get_primal_feas");
265 
266  stats["iter_count"] = static_cast<casadi_int>(iter);
267  Dict madnlp;
268  madnlp["dual_feas"] = dual_feas;
269  madnlp["primal_feas"] = primal_feas;
270  madnlp["status"] = static_cast<casadi_int>(status);
271  stats["madnlp"] = madnlp;
272  return stats;
273 }
274 
276  // assign pointer to internal structur casadi_nlp_prob
277  // p_nlp_ ~ casadi_nlp_prob casadi internal
278  p_.nlp = &p_nlp_;
279  // p_ casadi_madnlp_prob
280 
281  p_.sp_a = jacg_sp_;
282  p_.sp_h = hesslag_sp_;
283 
284  p_.nlp_hess_l = OracleCallback("nlp_hess_l", this);
285  p_.nlp_jac_g = OracleCallback("nlp_jac_g", this);
286  p_.nlp_grad_f = OracleCallback("nlp_grad_f", this);
287  p_.nlp_f = OracleCallback("nlp_f", this);
288  p_.nlp_g = OracleCallback("nlp_g", this);
289 
290  casadi_madnlp_setup(&p_);
291 }
292 
294 
295  g << "libmad_create_options_dict(&(" + codegen_mem(g) + ".libmad_opts));\n";
296  for (const auto& kv : opts_) {
297  switch (kv.second.getType()) {
298  case OT_DOUBLE:
299  g << "libmad_set_double_option(" + codegen_mem(g) + ".libmad_opts, "
300  + kv.first + ", " + str(kv.second) + ");\n";
301  break;
302  case OT_INT:
303  g << "libmad_set_int64_option(" + codegen_mem(g) + ".libmad_opts, "
304  + kv.first + ", " + str(kv.second) + ");\n";
305  break;
306  case OT_STRING:
307  {
308  std::string s = kv.second.to_string();
309  g << "libmad_set_string_option(" + codegen_mem(g) + ".libmad_opts, "
310  + kv.first + ", " + s + ");\n";
311  }
312  break;
313  case OT_BOOL:
314  g << "libmad_set_bool_option(" + codegen_mem(g) + ".libmad_opts, "
315  + kv.first + ", " + str(kv.second) + ");\n";
316  break;
317  default:
318  casadi_error("Unknown option type.");
319  }
320  }
321  g << "casadi_madnlp_init_mem(&" + codegen_mem(g) + ");\n";
322  g << "return 0;\n";
323 }
324 
326  // memory deallocation
327  g << "casadi_madnlp_free_mem(&" + codegen_mem(g) + ");\n";
328 }
329 
342  g.add_dependency(get_function("nlp_f"));
343  g.add_dependency(get_function("nlp_grad_f"));
344  g.add_dependency(get_function("nlp_g"));
345  g.add_dependency(get_function("nlp_jac_g"));
346  g.add_dependency(get_function("nlp_hess_l"));
347  g.add_include("libMad.h");
348 }
349 
352  g.auxiliaries << g.sanitize_source(madnlp_runtime_str, {"casadi_real"});
353 
354  g.local("d", "struct casadi_madnlp_data*");
355  g.init_local("d", "&" + codegen_mem(g));
356  g.local("p", "struct casadi_madnlp_prob");
357  set_madnlp_prob(g);
358 
359  g << "casadi_madnlp_set_work(d, &arg, &res, &iw, &w);\n";
360  g << "casadi_oracle_set_work(d->nlp->oracle, &arg, &res, &iw, &w);\n";
361  g << "casadi_madnlp_presolve(d);\n";
362  g << "casadi_madnlp_solve(d);\n";
363 
365 
366  if (error_on_fail_) {
367  g << "return d->unified_return_status;\n";
368  } else {
369  g << "return 0;\n";
370  }
371 }
372 
374  if (jacg_sp_.size1()>0 && jacg_sp_.nnz()==0) {
375  casadi_error("Empty sparsity pattern not supported in MADNLP C interface");
376  }
377  g << "d->nlp = &d_nlp;\n";
378  g << "d->prob = &p;\n";
379  g << "p.nlp = &p_nlp;\n";
380 
381  g.setup_callback("p.nlp_jac_g", get_function("nlp_jac_g"));
382  g.setup_callback("p.nlp_grad_f", get_function("nlp_grad_f"));
383  g.setup_callback("p.nlp_f", get_function("nlp_f"));
384  g.setup_callback("p.nlp_g", get_function("nlp_g"));
385  g.setup_callback("p.nlp_hess_l", get_function("nlp_hess_l"));
386 
387  g << "p.sp_a = " << g.sparsity(jacg_sp_) << ";\n";
388  if (exact_hessian_) {
389  g << "p.sp_h = " << g.sparsity(hesslag_sp_) << ";\n";
390  } else {
391  g << "p.sp_h = 0;\n";
392  }
393 
394  g << "casadi_madnlp_setup(&p);\n";
395 }
396 
398  int version = s.version("MadnlpInterface", 1, 2);
399  s.unpack("MadnlpInterface::jacg_sp", jacg_sp_);
400  s.unpack("MadnlpInterface::hesslag_sp", hesslag_sp_);
401  s.unpack("MadnlpInterface::exact_hessian", exact_hessian_);
402  s.unpack("MadnlpInterface::opts", opts_);
403  s.unpack("MadnlpInterface::convexify", convexify_);
404 
405  if (version==1) {
406  std::vector<libmad_int> dummy;
407  s.unpack("MadnlpInterface::nzj_i", dummy);
408  s.unpack("MadnlpInterface::nzj_j", dummy);
409  s.unpack("MadnlpInterface::nzh_i", dummy);
410  s.unpack("MadnlpInterface::nzh_j", dummy);
411  }
412 
413  set_madnlp_prob();
414 }
415 
418  s.version("MadnlpInterface", 2);
419 
420  s.pack("MadnlpInterface::jacg_sp", jacg_sp_);
421  s.pack("MadnlpInterface::hesslag_sp", hesslag_sp_);
422  s.pack("MadnlpInterface::exact_hessian", exact_hessian_);
423  s.pack("MadnlpInterface::opts", opts_);
424  s.pack("MadnlpInterface::convexify", convexify_);
425 }
426 
427 } // namespace casadi
Helper class for C code generation.
std::string add_dependency(const Function &f)
Add a function dependency.
void local(const std::string &name, const std::string &type, const std::string &ref="")
Declare a local variable.
void setup_callback(const std::string &s, const Function &f)
Setup a callback.
void init_local(const std::string &name, const std::string &def)
Specify the default value for a local variable.
std::string sanitize_source(const std::string &src, const std::vector< std::string > &inst, bool add_shorthand=true)
Sanitize source files for codegen.
void add_include(const std::string &new_include, bool relative_path=false, const std::string &use_ifdef=std::string())
Add an include file optionally using a relative path "..." instead of an absolute path <....
std::string sparsity(const Sparsity &sp, bool canonical=true)
std::stringstream auxiliaries
void add_auxiliary(Auxiliary f, const std::vector< std::string > &inst={"casadi_real"})
Add a built-in auxiliary function.
static Sparsity setup(ConvexifyData &d, const Sparsity &H, const Dict &opts=Dict(), bool inplace=true)
Definition: convexify.cpp:167
Helper class for Serialization.
void unpack(Sparsity &e)
Reconstruct an object from the input stream.
void version(const std::string &name, int v)
void alloc_iw(size_t sz_iw, bool persistent=false)
Ensure required length of iw field.
void alloc_res(size_t sz_res, bool persistent=false)
Ensure required length of res field.
void alloc_arg(size_t sz_arg, bool persistent=false)
Ensure required length of arg field.
std::string codegen_mem(CodeGenerator &g, const std::string &index="mem") const
Get thread-local memory object.
size_t sz_res() const
Get required length of res field.
size_t sz_w() const
Get required length of w field.
void alloc_w(size_t sz_w, bool persistent=false)
Ensure required length of w field.
size_t sz_arg() const
Get required length of arg field.
size_t sz_iw() const
Get required length of iw field.
Function object.
Definition: function.hpp:60
static const Options options_
Options.
MadnlpInterface(const std::string &name, const Function &nlp)
void codegen_init_mem(CodeGenerator &g) const override
Codegen alloc_mem.
Dict get_stats(void *mem) const override
Get all statistics.
void codegen_declarations(CodeGenerator &g) const override
Generate code for the declarations of the C function.
void init(const Dict &opts) override
Initialize.
void codegen_free_mem(CodeGenerator &g) const override
Codegen free_mem.
static Nlpsol * creator(const std::string &name, const Function &nlp)
Create a new NLP Solver.
int init_mem(void *mem) const override
Initalize memory block.
void free_mem(void *mem) const override
Free memory block.
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
void set_work(void *mem, const double **&arg, double **&res, casadi_int *&iw, double *&w) const override
Set the (persistent) work vectors.
static const std::string meta_doc
A documentation string.
bool exact_hessian_
Exact Hessian?
static ProtoFunction * deserialize(DeserializingStream &s)
Deserialize into MX.
Dict opts_
All MADNLP options.
void codegen_body(CodeGenerator &g) const override
Generate code for the function body.
ConvexifyData convexify_data_
Data for convexification.
int solve(void *mem) const override
NLP solver storage class.
Definition: nlpsol_impl.hpp:59
void codegen_body_exit(CodeGenerator &g) const override
Generate code for the function body.
Definition: nlpsol.cpp:1368
Dict get_stats(void *mem) const override
Get all statistics.
Definition: nlpsol.cpp:1251
static const Options options_
Options.
void codegen_body_enter(CodeGenerator &g) const override
Generate code for the function body.
Definition: nlpsol.cpp:1268
void codegen_declarations(CodeGenerator &g) const override
Generate code for the declarations of the C function.
Definition: nlpsol.cpp:1348
void init(const Dict &opts) override
Initialize.
Definition: nlpsol.cpp:499
int init_mem(void *mem) const override
Initalize memory block.
Definition: nlpsol.cpp:692
casadi_nlpsol_prob< double > p_nlp_
Definition: nlpsol_impl.hpp:63
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
Definition: nlpsol.cpp:1409
bool calc_f_
Options.
Definition: nlpsol_impl.hpp:97
bool calc_g_
Options.
Definition: nlpsol_impl.hpp:97
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:884
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())
std::vector< std::string > get_function() const override
Get list of dependency functions.
bool has_function(const std::string &fname) const override
static void registerPlugin(const Plugin &plugin, bool needs_lock=true)
Register an integrator in the factory.
bool error_on_fail_
Throw an exception on failure?
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.
casadi_int size1() const
Get the number of rows.
Definition: sparsity.cpp:124
bool is_tril(bool strictly=false) const
Is lower triangular?
Definition: sparsity.cpp:321
casadi_int nnz() const
Get the number of (structural) non-zeros.
Definition: sparsity.cpp:148
The casadi namespace.
Definition: archiver.cpp:28
void CASADI_NLPSOL_MADNLP_EXPORT casadi_load_nlpsol_madnlp()
std::string str(const T &v)
String representation, any type.
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
int CASADI_NLPSOL_MADNLP_EXPORT casadi_register_nlpsol_madnlp(Nlpsol::Plugin *plugin)
void flatten_opts(Dict &ret, const Dict &opts, const std::string &prefix)
UnifiedReturnStatus
Options metadata for a class.
Definition: options.hpp:40
OracleCallback nlp_f
const casadi_int * sp_a
OracleCallback nlp_g
OracleCallback nlp_jac_g
OracleCallback nlp_hess_l
const casadi_nlpsol_prob< T1 > * nlp
const casadi_int * sp_h
OracleCallback nlp_grad_f