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 
28 #include "casadi/core/casadi_misc.hpp"
29 #include "../../core/global_options.hpp"
30 #include "../../core/casadi_interrupt.hpp"
31 #include "../../core/convexify.hpp"
32 
33 #include <ctime>
34 #include <stdlib.h>
35 #include <iostream>
36 #include <iomanip>
37 #include <chrono>
38 #include <cstring>
39 #include <string>
40 
41 #include <madnlp_runtime_str.h>
42 
43 namespace casadi {
44 
45 extern "C"
46 int CASADI_NLPSOL_MADNLP_EXPORT
47 casadi_register_nlpsol_madnlp(Nlpsol::Plugin* plugin) {
48  plugin->creator = MadnlpInterface::creator;
49  plugin->name = "madnlp";
50  plugin->doc = MadnlpInterface::meta_doc.c_str();
51  plugin->version = CASADI_VERSION;
52  plugin->options = &MadnlpInterface::options_;
53  plugin->deserialize = &MadnlpInterface::deserialize;
54  return 0;
55 }
56 
57 extern "C"
58 void CASADI_NLPSOL_MADNLP_EXPORT casadi_load_nlpsol_madnlp() {
60 }
61 
62 MadnlpInterface::MadnlpInterface(const std::string& name, const Function& nlp)
63  : Nlpsol(name, nlp) {
64 }
65 
67  //shutdown_julia(0);
68  clear_mem();
69 }
70 
72 = {{&Nlpsol::options_},
73  {{"nw",
74  {OT_INTVECTOR,
75  "Number of variables"}},
76  {"ng",
77  {OT_INTVECTOR,
78  "Number of constraints"}},
79  {"madnlp",
80  {OT_DICT,
81  "Options to be passed to madnlp"}},
82  {"convexify_strategy",
83  {OT_STRING,
84  "NONE|regularize|eigen-reflect|eigen-clip. "
85  "Strategy to convexify the Lagrange Hessian before passing it to the solver."}},
86  {"convexify_margin",
87  {OT_DOUBLE,
88  "When using a convexification strategy, make sure that "
89  "the smallest eigenvalue is at least this (default: 1e-7)."}},
90  }
91 };
92 
93 void casadi_madnlp_sparsity(const casadi_int* sp, madnlp_int *coord_i, madnlp_int *coord_j) {
94  // convert ccs to cco
95  casadi_int ncol = sp[1];
96  const casadi_int* colind = sp+2;
97  const casadi_int* row = colind+ncol+1;
98 
99  for (casadi_int cc=0; cc<ncol; ++cc) {
100  for (casadi_int el=colind[cc]; el<colind[cc+1]; ++el) {
101  *coord_i++ = row[el]+1;
102  *coord_j++ = cc+1;
103  }
104  }
105 }
106 
107 void MadnlpInterface::init(const Dict& opts) {
108  // Call the init method of the base class
109  Nlpsol::init(opts);
110 
111  //std::cout << "MadnlpInterface::init" << std::endl;
112 
113  casadi_int struct_cnt=0;
114 
115  // Default options
116  std::string convexify_strategy = "none";
117  double convexify_margin = 1e-7;
118  casadi_int max_iter_eig = 200;
119  convexify_ = false;
120 
121  calc_g_ = true;
122  calc_f_ = true;
123 
124  // Read options
125  for (auto&& op : opts) {
126  if (op.first=="convexify_strategy") {
127  convexify_strategy = op.second.to_string();
128  } else if (op.first=="convexify_margin") {
129  convexify_margin = op.second;
130  } else if (op.first=="max_iter") {
131  max_iter_eig = op.second;
132  } else if (op.first=="madnlp") {
133  opts_ = op.second;
134  }
135  }
136 
137  // Do we need second order derivatives?
138  exact_hessian_ = true;
139  auto hessian_approximation = opts_.find("hessian_approximation");
140  if (hessian_approximation!=opts_.end()) {
141  exact_hessian_ = hessian_approximation->second == "exact";
142  }
143 
144  // Setup NLP functions
145  create_function("nlp_f", {"x", "p"}, {"f"});
146  create_function("nlp_g", {"x", "p"}, {"g"});
147 
148  if (!has_function("nlp_grad_f")) {
149  create_function("nlp_grad_f", {"x", "p"}, {"grad:f:x"});
150  }
151  gradf_sp_ = get_function("nlp_grad_f").sparsity_out(0);
152 
153  if (!has_function("nlp_jac_g")) {
154  create_function("nlp_jac_g", {"x", "p"}, {"jac:g:x"});
155  }
156  jacg_sp_ = get_function("nlp_jac_g").sparsity_out(0);
157 
158  if (!has_function("nlp_hess_l")) {
159  create_function("nlp_hess_l", {"x", "p", "lam:f", "lam:g"},
160  {"tril:hess:gamma:x:x"}, {{"gamma", {"f", "g"}}});
161  }
162  hesslag_sp_ = get_function("nlp_hess_l").sparsity_out(0);
163  casadi_assert(hesslag_sp_.is_tril(), "Hessian must be lower triangular");
164 
165  if (convexify_strategy!="none") {
166  convexify_ = true;
167  Dict opts;
168  opts["strategy"] = convexify_strategy;
169  opts["margin"] = convexify_margin;
170  opts["max_iter_eig"] = max_iter_eig;
171  opts["verbose"] = verbose_;
173  }
174 
175  // transform ccs sparsity to cco
176  nzj_i_.resize(jacg_sp_.nnz());
177  nzj_j_.resize(jacg_sp_.nnz());
178  nzh_i_.resize(hesslag_sp_.nnz());
179  nzh_j_.resize(hesslag_sp_.nnz());
180 
181  casadi_madnlp_sparsity(jacg_sp_, get_ptr(nzj_i_), get_ptr(nzj_j_));
183 
184  set_madnlp_prob();
185 
186  // Allocate memory
187  casadi_int sz_arg, sz_res, sz_w, sz_iw;
188  casadi_madnlp_work(&p_, &sz_arg, &sz_res, &sz_iw, &sz_w);
189 
190  alloc_arg(sz_arg, true);
191  alloc_res(sz_res, true);
192  alloc_iw(sz_iw, true);
193  alloc_w(sz_w, true);
194 
195  std::vector<char*> _argv = {};
196  std::string s;
197 
198  std::set<std::string> comp_values = {"no","yes","min","max"};
199 
200  auto comp = opts_.find("compile");
201  if (comp!=opts_.end()) {
202  std::string comp_value = comp->second;
203  if (comp_values.find(comp_value) != comp_values.end()) {
204  s = "--compile=" + comp_value;
205  const int comp_l = s.length();
206  char* option_comp = new char[comp_l + 1];
207  strcpy(option_comp, s.c_str());
208  _argv.push_back(option_comp);
209  } else {
210  std::cout << "Invalid value (" << comp_value << ")for option 'compile'" << std::endl;
211  std::cout << "Available values are: ";
212  for (auto v: comp_values) std::cout << v << " "; std::cout << std::endl;
213  }
214  }
215 
216  auto trace_opt = opts_.find("trace_compile");
217  if (trace_opt!=opts_.end() && bool(trace_opt->second)) {
218  std::string trace_compile_output = "stderr";
219  auto _trace_out_opt = opts_.find("trace_compile_output");
220  if (_trace_out_opt!=opts_.end())
221  trace_compile_output = (std::string) _trace_out_opt->second;
222  s = "--trace-compile=" + trace_compile_output;
223  const int trace_l = s.length();
224  char* option_trace = new char[trace_l + 1];
225  strcpy(option_trace, s.c_str());
226  _argv.push_back(option_trace);
227  }
228 
229  int argc = _argv.size();
230  char** argv = reinterpret_cast<char**>(_argv.data());
231 
233  init_julia(argc, argv);
234  std::cout << "Init julia runtime with options: "<< std::endl ;
235  for (auto s: _argv) std::cout << s << std::endl;
237  }
238 }
239 
240 int MadnlpInterface::init_mem(void* mem) const {
241  if (Nlpsol::init_mem(mem)) return 1;
242  if (!mem) return 1;
243  auto m = static_cast<MadnlpMemory*>(mem);
244  madnlp_init_mem(&m->d);
245 
246  return 0;
247 }
248 
249 void MadnlpInterface::free_mem(void* mem) const {
250  auto m = static_cast<MadnlpMemory*>(mem);
251  madnlp_free_mem(&m->d);
252  delete static_cast<MadnlpMemory*>(mem);
253 }
254 
256 void MadnlpInterface::set_work(void* mem, const double**& arg, double**& res,
257  casadi_int*& iw, double*& w) const {
258  auto m = static_cast<MadnlpMemory*>(mem);
259 
260  // Set work in base classes
261  Nlpsol::set_work(mem, arg, res, iw, w);
262 
263  m->d.prob = &p_;
264  m->d.nlp = &m->d_nlp;
265 
266  casadi_madnlp_init(&m->d, &arg, &res, &iw, &w);
267 
268  m->d.nlp->oracle->m = static_cast<void*>(m);
269 
270  // options
271 }
272 
273 int MadnlpInterface::solve(void* mem) const {
274  auto m = static_cast<MadnlpMemory*>(mem);
275 
276  casadi_madnlp_presolve(&m->d);
277 
278  for (const auto& kv : opts_) {
279  switch (madnlp_c_option_type(kv.first.c_str())) {
280  case 0:
281  madnlp_c_set_option_double(m->d.solver, kv.first.c_str(), kv.second);
282  break;
283  case 1:
284  madnlp_c_set_option_int(m->d.solver, kv.first.c_str(), kv.second.to_int());
285  break;
286  case 2:
287  madnlp_c_set_option_bool(m->d.solver, kv.first.c_str(), kv.second.to_bool());
288  break;
289  case 3:
290  {
291  std::string s = kv.second.to_string();
292  madnlp_c_set_option_string(m->d.solver, kv.first.c_str(), s.c_str());
293  }
294  break;
295  case -1:
296  casadi_error("Madnlp option not supported: " + kv.first);
297  default:
298  casadi_error("Unknown option type.");
299  }
300  }
301 
302  int ret = casadi_madnlp_solve(&m->d);
303  if ( ret != 0 ) throw CasadiException("MADNLPError");
304 
305  m->success = m->d.success;
306  m->unified_return_status = static_cast<UnifiedReturnStatus>(m->d.unified_return_status);
307 
308  return 0;
309 }
310 
312  Dict stats = Nlpsol::get_stats(mem);
313  auto m = static_cast<MadnlpMemory*>(mem);
314  stats["iter_count"] = m->d.stats.iter;
315  Dict madnlp;
316  madnlp["dual_feas"] = m->d.stats.dual_feas;
317  madnlp["primal_feas"] = m->d.stats.primal_feas;
318  madnlp["status"] = m->d.stats.status;
319  stats["madnlp"] = madnlp;
320  return stats;
321 }
322 
324  // assign pointer to internal structur casadi_nlp_prob
325  // p_nlp_ ~ casadi_nlp_prob casadi internal
326  p_.nlp = &p_nlp_;
327  // p_ casadi_madnlp_prob
328 
329  p_.nnz_jac_g = jacg_sp_.nnz();
330  p_.nnz_hess_l = hesslag_sp_.nnz();
331  p_.nzj_i = get_ptr(nzj_i_);
332  p_.nzj_j = get_ptr(nzj_j_);
333  p_.nzh_i = get_ptr(nzh_i_);
334  p_.nzh_j = get_ptr(nzh_j_);
335 
336  p_.nlp_hess_l = OracleCallback("nlp_hess_l", this);
337  p_.nlp_jac_g = OracleCallback("nlp_jac_g", this);
338  p_.nlp_grad_f = OracleCallback("nlp_grad_f", this);
339  p_.nlp_f = OracleCallback("nlp_f", this);
340  p_.nlp_g = OracleCallback("nlp_g", this);
341 
342  casadi_madnlp_setup(&p_);
343 }
344 
346  g << "madnlp_init_mem(&" + codegen_mem(g) + ");\n";
347  g << "return 0;\n";
348 }
349 
351  // memory deallocation
352  g << "madnlp_free_mem(&" + codegen_mem(g) + ");\n";
353 }
354 
367  g.add_dependency(get_function("nlp_f"));
368  g.add_dependency(get_function("nlp_grad_f"));
369  g.add_dependency(get_function("nlp_g"));
370  g.add_dependency(get_function("nlp_jac_g"));
371  g.add_dependency(get_function("nlp_hess_l"));
372  g.add_include("MadnlpCInterface.h");
373 }
374 
377  g.auxiliaries << g.sanitize_source(madnlp_runtime_str, {"casadi_real"});
378 
379  g.local("d", "struct casadi_madnlp_data*");
380  g.init_local("d", "&" + codegen_mem(g));
381  g.local("p", "struct casadi_madnlp_prob");
382  set_madnlp_prob(g);
383 
384  g << "casadi_madnlp_init(d, &arg, &res, &iw, &w);\n";
385  g << "casadi_oracle_init(d->nlp->oracle, &arg, &res, &iw, &w);\n";
386  g << "casadi_madnlp_presolve(d);\n";
387 
388  for (const auto& kv : opts_) {
389  switch (madnlp_c_option_type(kv.first.c_str())) {
390  case 0:
391  g << "madnlp_c_set_option_double(d->solver, \"" + kv.first + "\", "
392  + str(kv.second) + ");\n";
393  break;
394  case 1:
395  g << "madnlp_c_set_option_int(d->solver, \"" + kv.first + "\", "
396  + str(kv.second.to_int()) + ");\n";
397  break;
398  case 2:
399  g << "madnlp_c_set_option_bool(d->solver, \"" + kv.first + "\", "
400  + str(static_cast<int>(kv.second.to_bool())) + ");\n";
401  break;
402  case 3:
403  {
404  std::string s = kv.second.to_string();
405  g << "madnlp_c_set_option_string(d->solver, \"" + kv.first + "\", \""
406  + s + "\");\n";
407  }
408  break;
409  case -1:
410  casadi_error("Madnlp option not supported: " + kv.first);
411  default:
412  casadi_error("Unknown option type.");
413  }
414  }
415 
416  // Options
417  g << "casadi_madnlp_solve(d);\n";
418 
420 
421  if (error_on_fail_) {
422  g << "return d->unified_return_status;\n";
423  } else {
424  g << "return 0;\n";
425  }
426 }
427 
429  if (jacg_sp_.size1()>0 && jacg_sp_.nnz()==0) {
430  casadi_error("Empty sparsity pattern not supported in MADNLP C interface");
431  }
432  g << "d->nlp = &d_nlp;\n";
433  g << "d->prob = &p;\n";
434  g << "p.nlp = &p_nlp;\n";
435 
436  g.setup_callback("p.nlp_jac_g", get_function("nlp_jac_g"));
437  g.setup_callback("p.nlp_grad_f", get_function("nlp_grad_f"));
438  g.setup_callback("p.nlp_f", get_function("nlp_f"));
439  g.setup_callback("p.nlp_g", get_function("nlp_g"));
440  g.setup_callback("p.nlp_hess_l", get_function("nlp_hess_l"));
441 
442  g << "p.sp_a = " << g.sparsity(jacg_sp_) << ";\n";
443  if (exact_hessian_) {
444  g << "p.sp_h = " << g.sparsity(hesslag_sp_) << ";\n";
445  } else {
446  g << "p.sp_h = 0;\n";
447  }
448 
449  g << "casadi_madnlp_setup(&p);\n";
450 }
451 
453  s.version("MadnlpInterface", 1);
454  s.unpack("MadnlpInterface::jacg_sp", jacg_sp_);
455  s.unpack("MadnlpInterface::hesslag_sp", hesslag_sp_);
456  s.unpack("MadnlpInterface::exact_hessian", exact_hessian_);
457  s.unpack("MadnlpInterface::opts", opts_);
458  s.unpack("MadnlpInterface::convexify", convexify_);
459 
460  s.unpack("MadnlpInterface::nzj_i", nzj_i_);
461  s.unpack("MadnlpInterface::nzj_j", nzj_j_);
462  s.unpack("MadnlpInterface::nzh_i", nzh_i_);
463  s.unpack("MadnlpInterface::nzh_j", nzh_j_);
464 
465  set_madnlp_prob();
466 }
467 
470  s.version("MadnlpInterface", 1);
471 
472  s.pack("MadnlpInterface::jacg_sp", jacg_sp_);
473  s.pack("MadnlpInterface::hesslag_sp", hesslag_sp_);
474  s.pack("MadnlpInterface::exact_hessian", exact_hessian_);
475  s.pack("MadnlpInterface::opts", opts_);
476  s.pack("MadnlpInterface::convexify", convexify_);
477 
478  s.pack("MadnlpInterface::nzj_i", nzj_i_);
479  s.pack("MadnlpInterface::nzj_j", nzj_j_);
480  s.pack("MadnlpInterface::nzh_i", nzh_i_);
481  s.pack("MadnlpInterface::nzh_j", nzh_j_);
482 
483 }
484 
485 } // namespace casadi
Casadi exception class.
Definition: exception.hpp:77
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:166
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 bool julia_initialized
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:1269
Dict get_stats(void *mem) const override
Get all statistics.
Definition: nlpsol.cpp:1162
static const Options options_
Options.
void codegen_body_enter(CodeGenerator &g) const override
Generate code for the function body.
Definition: nlpsol.cpp:1179
void codegen_declarations(CodeGenerator &g) const override
Generate code for the declarations of the C function.
Definition: nlpsol.cpp:1250
void init(const Dict &opts) override
Initialize.
Definition: nlpsol.cpp:420
int init_mem(void *mem) const override
Initalize memory block.
Definition: nlpsol.cpp:603
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:1306
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:795
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()
void casadi_madnlp_sparsity(const casadi_int *sp, madnlp_int *coord_i, madnlp_int *coord_j)
@ OT_INTVECTOR
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)
T * get_ptr(std::vector< T > &v)
Get a pointer to the data contained in the vector.
UnifiedReturnStatus
casadi_madnlp_data< double > d
Options metadata for a class.
Definition: options.hpp:40
struct MadnlpCStats stats
OracleCallback nlp_f
OracleCallback nlp_g
OracleCallback nlp_jac_g
OracleCallback nlp_hess_l
const casadi_nlpsol_prob< T1 > * nlp
OracleCallback nlp_grad_f