fatrop_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 
27 #include "fatrop_interface.hpp"
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 #include "casadi/casadi_c.h"
33 
34 #include <ctime>
35 #include <stdlib.h>
36 #include <iostream>
37 #include <iomanip>
38 #include <chrono>
39 
40 #ifdef CASADI_WITH_THREAD
41 #ifdef CASADI_WITH_THREAD_MINGW
42 #include <mingw.mutex.h>
43 #else // CASADI_WITH_THREAD_MINGW
44 #include <mutex>
45 #endif // CASADI_WITH_THREAD_MINGW
46 #endif //CASADI_WITH_THREAD
47 
48 #include <fatrop_runtime_str.h>
49 
50 namespace casadi {
51  extern "C"
52  int CASADI_NLPSOL_FATROP_EXPORT
53  casadi_register_nlpsol_fatrop(Nlpsol::Plugin* plugin) {
54  plugin->creator = FatropInterface::creator;
55  plugin->name = "fatrop";
56  plugin->doc = FatropInterface::meta_doc.c_str();
57  plugin->version = CASADI_VERSION;
58  plugin->options = &FatropInterface::options_;
59  plugin->deserialize = &FatropInterface::deserialize;
60  return 0;
61  }
62 
63  extern "C"
64  void CASADI_NLPSOL_FATROP_EXPORT casadi_load_nlpsol_fatrop() {
66  }
67 
68  FatropInterface::FatropInterface(const std::string& name, const Function& nlp)
69  : Nlpsol(name, nlp) {
70  }
71 
73  clear_mem();
74  }
75 
76  Sparsity FatropInterface::blocksparsity(casadi_int rows, casadi_int cols,
77  const std::vector<casadi_ocp_block>& blocks, bool eye) {
78  DM r(rows, cols);
79  for (auto && b : blocks) {
80  if (eye) {
81  r(range(b.offset_r, b.offset_r+b.rows),
82  range(b.offset_c, b.offset_c+b.cols)) = DM::eye(b.rows);
83  casadi_assert_dev(b.rows==b.cols);
84  } else {
85  r(range(b.offset_r, b.offset_r+b.rows),
86  range(b.offset_c, b.offset_c+b.cols)) = DM::zeros(b.rows, b.cols);
87  }
88  }
89  return r.sparsity();
90  }
91 
92  void report_issue(casadi_int i, const std::string& msg) {
93  casadi_int idx = i+GlobalOptions::start_index;
94  casadi_warning("Structure detection error on row " + str(idx) + ". " + msg);
95  }
96 
97  const Options FatropInterface::options_
98  = {{&Nlpsol::options_},
99  {{"N",
100  {OT_INT,
101  "OCP horizon"}},
102  {"nx",
103  {OT_INTVECTOR,
104  "Number of states, length N+1"}},
105  {"nu",
106  {OT_INTVECTOR,
107  "Number of controls, length N+1"}},
108  {"ng",
109  {OT_INTVECTOR,
110  "Number of non-dynamic constraints, length N+1"}},
111  {"fatrop",
112  {OT_DICT,
113  "Options to be passed to fatrop"}},
114  {"structure_detection",
115  {OT_STRING,
116  "NONE | auto | manual"}},
117  {"convexify_strategy",
118  {OT_STRING,
119  "NONE|regularize|eigen-reflect|eigen-clip. "
120  "Strategy to convexify the Lagrange Hessian before passing it to the solver."}},
121  {"convexify_margin",
122  {OT_DOUBLE,
123  "When using a convexification strategy, make sure that "
124  "the smallest eigenvalue is at least this (default: 1e-7)."}},
125  {"debug",
126  {OT_BOOL,
127  "Produce debug information (default: false)"}},
128  {"fatrop",
129  {OT_DICT,
130  "Options to be passed to fatrop"
131  }}
132  }
133  };
134 
135  void FatropInterface::init(const Dict& opts) {
136  // Call the init method of the base class
137  Nlpsol::init(opts);
138 
139  casadi_int struct_cnt=0;
140 
141  // Default options
142  std::string convexify_strategy = "none";
143  double convexify_margin = 1e-7;
144  casadi_int max_iter_eig = 200;
145  structure_detection_ = STRUCTURE_NONE;
146  debug_ = false;
147 
148  calc_g_ = true;
149  calc_f_ = true;
150 
151  // Read options
152  for (auto&& op : opts) {
153  if (op.first=="N") {
154  N_ = op.second;
155  struct_cnt++;
156  } else if (op.first=="nx") {
157  nxs_ = op.second;
158  struct_cnt++;
159  } else if (op.first=="nu") {
160  nus_ = op.second;
161  struct_cnt++;
162  } else if (op.first=="ng") {
163  ngs_ = op.second;
164  struct_cnt++;
165  } else if (op.first=="convexify_strategy") {
166  convexify_strategy = op.second.to_string();
167  } else if (op.first=="convexify_margin") {
168  convexify_margin = op.second;
169  } else if (op.first=="max_iter_eig") {
170  max_iter_eig = op.second;
171  } else if (op.first=="fatrop") {
172  opts_ = op.second;
173  } else if (op.first=="structure_detection") {
174  std::string v = op.second;
175  if (v=="auto") {
176  structure_detection_ = STRUCTURE_AUTO;
177  } else if (v=="manual") {
178  structure_detection_ = STRUCTURE_MANUAL;
179  } else if (v=="none") {
180  structure_detection_ = STRUCTURE_NONE;
181  } else {
182  casadi_error("Unknown option for structure_detection: '" + v + "'.");
183  }
184  } else if (op.first=="debug") {
185  debug_ = op.second;
186  }
187  }
188 
189  // Do we need second order derivatives?
190  exact_hessian_ = true;
191  auto hessian_approximation = opts_.find("hessian_approximation");
192  if (hessian_approximation!=opts_.end()) {
193  exact_hessian_ = hessian_approximation->second == "exact";
194  }
195 
196  // Setup NLP functions
197  create_function("nlp_f", {"x", "p"}, {"f"});
198  create_function("nlp_g", {"x", "p"}, {"g"});
199  if (!has_function("nlp_grad_f")) {
200  create_function("nlp_grad_f", {"x", "p"}, {"grad:f:x"});
201  }
202  if (!has_function("nlp_jac_g")) {
203  create_function("nlp_jac_g", {"x", "p"}, {"g", "jac:g:x"});
204  }
205  jacg_sp_ = get_function("nlp_jac_g").sparsity_out(1);
206 
207  convexify_ = false;
208 
209  // Allocate temporary work vectors
210  if (exact_hessian_) {
211  if (!has_function("nlp_hess_l")) {
212  create_function("nlp_hess_l", {"x", "p", "lam:f", "lam:g"},
213  {"grad:gamma:x", "hess:gamma:x:x"}, {{"gamma", {"f", "g"}}});
214  }
215  hesslag_sp_ = get_function("nlp_hess_l").sparsity_out(1);
216  casadi_assert(hesslag_sp_.is_symmetric(), "Hessian must be symmetric");
217  if (convexify_strategy!="none") {
218  convexify_ = true;
219  Dict opts;
220  opts["strategy"] = convexify_strategy;
221  opts["margin"] = convexify_margin;
222  opts["max_iter_eig"] = max_iter_eig;
223  opts["verbose"] = verbose_;
225  }
226  }
227 
228  const std::vector<casadi_int>& nx = nxs_;
229  const std::vector<casadi_int>& ng = ngs_;
230  const std::vector<casadi_int>& nu = nus_;
231 
232  Sparsity lamg_csp_, lam_ulsp_, lam_uusp_, lam_xlsp_, lam_xusp_, lam_clsp_;
233 
234  const Sparsity& A_ = jacg_sp_;
235  if (debug_) {
236  A_.to_file("debug_fatrop_actual.mtx");
237  }
238  // Keep list of erroring rows
239  std::set<casadi_int> errors;
240 
241  casadi_int na_ = A_.size1(); //TODO(jgillis): replace with ng
242 
243  if (struct_cnt>0) {
244  casadi_assert(structure_detection_ == STRUCTURE_MANUAL,
245  "You must set structure_detection to 'manual' if you set N, nx, nu, ng.");
246  }
247 
248  if (structure_detection_==STRUCTURE_MANUAL) {
249  casadi_assert(struct_cnt==4,
250  "You must set all of N, nx, nu, ng.");
251  } else if (structure_detection_==STRUCTURE_NONE) {
252  N_ = 0;
253  nxs_ = {0};
254  nus_ = {nx_};
255  ngs_ = {ng_};
256  } else if (structure_detection_==STRUCTURE_AUTO) {
257  casadi_assert(!equality_.empty(),
258  "Structure detection auto requires the 'equality' option to be set");
259  /* General strategy: look for the xk+1 diagonal part in A
260  */
261 
262  // Find the right-most column for each row in A -> A_skyline
263  // Find the second-to-right-most column -> A_skyline2
264  // Find the left-most column -> A_bottomline
265  Sparsity AT = A_.T();
266  std::vector<casadi_int> A_skyline;
267  std::vector<casadi_int> A_skyline2;
268  std::vector<casadi_int> A_bottomline;
269 
270  std::vector<casadi_int> AT_colind = AT.get_colind();
271  std::vector<casadi_int> AT_row = AT.get_row();
272  for (casadi_int i=0;i<AT.size2();++i) {
273  casadi_int pivot = AT_colind.at(i+1);
274  if (pivot>AT_colind.at(i)) {
275  A_bottomline.push_back(AT_row.at(AT_colind.at(i)));
276  } else {
277  A_bottomline.push_back(-1);
278  }
279  if (pivot>AT_colind.at(i)) {
280  A_skyline.push_back(AT_row.at(pivot-1));
281  if (pivot>AT_colind.at(i)+1) {
282  A_skyline2.push_back(AT_row.at(pivot-2));
283  } else {
284  A_skyline2.push_back(-1);
285  }
286  } else {
287  A_skyline.push_back(-1);
288  A_skyline2.push_back(-1);
289  }
290  }
291 
292  casadi_assert(equality_[0],
293  "Constraint Jcobian must start with gap-closing constraint "
294  "(tagged 'true' in equality vector).");
295 
296  casadi_int pivot = A_skyline[0]; // Current right-most element
297  casadi_int start_pivot = pivot; // First right-most element that started the stage
298  casadi_int prev_start_pivot = 0;
299 
300  bool walking = true;
301 
302  nxs_.push_back(1);
303  nus_.push_back(0);
304  ngs_.push_back(0);
305  for (casadi_int i=1;i<na_;++i) { // Loop over all rows
306  bool is_gap_closing = true;
307  if (A_bottomline[i]!=-1 && A_bottomline[i]<prev_start_pivot) {
308  errors.insert(i);
309  report_issue(i, "Constraint found depending on a state of the previous interval.");
310  }
311  if (equality_[i]) {
312  // A candidate for a gap-closing constraint must tagged as equality
313  if (A_skyline[i]>pivot+1) { // Jump to a diagonal in the future
314  if (A_bottomline[i]!=-1 && A_bottomline[i]<start_pivot) {
315  errors.insert(i);
316  report_issue(i, "Constraint found depending on a state of the previous interval.");
317  }
318  //if (A_bottomline[i]<start_pivot || A_bottomline[i]>pivot) {
319  // errors.insert(i);
320  // report_issue(i, "Gap-closing constraint must depend on a state.");
321  //}
322  nxs_.push_back(1);
323  nus_.push_back(A_skyline[i]-pivot-1); // Size of jump equals number of states
324  ngs_.push_back(0);
325  prev_start_pivot = start_pivot;
326  start_pivot = A_skyline[i];
327  pivot = A_skyline[i];
328  walking = true;
329  } else if (A_skyline[i]==pivot+1) { // Walking the diagonal
330  if (A_skyline2[i]<start_pivot) { // Free of below-diagonal entries?
331  if (A_bottomline[i]>=prev_start_pivot) { // We must depend on at least one state
332  pivot++;
333  nxs_.back()++;
334  walking = true;
335  } else {
336  if (A_bottomline[i]!=-1 && A_bottomline[i]<start_pivot) {
337  errors.insert(i);
338  report_issue(i, "Constraint found depending "
339  "on a state of the previous interval.");
340  }
341  is_gap_closing = false;
342  }
343  } else {
344  nxs_.push_back(1);
345  nus_.push_back(0);
346  ngs_.push_back(0);
347  if (A_bottomline[i]!=-1 && A_bottomline[i]<start_pivot) {
348  errors.insert(i);
349  report_issue(i, "Gap-closing constraint found depending "
350  "on a state of the previous interval.");
351  }
352  prev_start_pivot = start_pivot;
353  start_pivot = A_skyline[i];
354  pivot = A_skyline[i];
355  walking = true;
356  }
357  } else {
358  is_gap_closing = false;
359  }
360  } else {
361  is_gap_closing = false;
362  }
363 
364  if (!is_gap_closing) {
365  if (walking) {
366  if (A_skyline[i]>=start_pivot) {
367  nxs_.push_back(0);
368  nus_.push_back(0);
369  ngs_.push_back(0);
370  walking = false;
371  }
372  }
373  ngs_.back()++; // non-gap-closing constraint detected
374  }
375 
376 
377  }
378 
379  if (nxs_.back()!=0) {
380  nxs_.push_back(0);
381  nus_.push_back(0);
382  ngs_.push_back(0);
383  }
384 
385  // Set nx0==nx1 unless not allowed
386  nxs_.insert(nxs_.begin(), std::min(A_skyline[0], nxs_.front()));
387 
388  // Patch loose ends
389  nus_.front() += std::max(A_skyline[0]-nxs_.front(), static_cast<casadi_int>(0));
390  nus_.back() += nx_-sum(nu)-sum(nx);
391 
392  casadi_assert_dev(nxs_.back()==0);
393  nxs_.pop_back();
394 
395  casadi_assert_dev(nx.size()==nu.size());
396  casadi_assert_dev(nx.size()==ng.size());
397 
398  casadi_assert_dev(sum(ng)+sum(nx)==na_+nx.front());
399  casadi_assert_dev(sum(nx)+sum(nu)==nx_);
400 
401  N_ = nxs_.size()-1;
402  }
403 
404  casadi_assert(nx.size()==N_+1, "nx must have length N+1.");
405  casadi_assert(nu.size()==N_+1, "nu must have length N+1.");
406  casadi_assert(ng.size()==N_+1, "ng must have length N+1.");
407 
408  if (verbose_) {
409  casadi_message("Using structure: N " + str(N_) + ", nx " + str(nx) + ", "
410  "nu " + str(nu) + ", ng " + str(ng) + ".");
411  }
412 
413  // Dor debugging purposes
414  std::vector< casadi_ocp_block > A_blocks, B_blocks, C_blocks, D_blocks;
415 
416  /* Disassemble A input into:
417  A B I
418  C D
419  A B I
420  C D
421  C D
422  */
423  casadi_int offset_r = 0, offset_c = 0;
424  for (casadi_int k=0;k<N_;++k) { // Loop over blocks
425  AB_blocks_.push_back({offset_r, offset_c, nx[k+1], nx[k]+nu[k]});
426  CD_blocks_.push_back({offset_r+nx[k+1], offset_c, ng[k], nx[k]+nu[k]});
427  A_blocks.push_back({offset_r, offset_c, nx[k+1], nx[k]});
428  B_blocks.push_back({offset_r, offset_c+nx[k], nx[k+1], nu[k]});
429  C_blocks.push_back({offset_r+nx[k+1], offset_c, ng[k], nx[k]});
430  D_blocks.push_back({offset_r+nx[k+1], offset_c+nx[k], ng[k], nu[k]});
431  offset_c+= nx[k]+nu[k];
432  if (k+1<N_)
433  I_blocks_.push_back({offset_r, offset_c, nx[k+1], nx[k+1]});
434  // TODO(jgillis) actually use these
435  // test5.py versus tesst6.py
436  // test5 changes behaviour when piping stdout to file -> memory corruption
437  // logs are ever so slightly different
438  else
439  I_blocks_.push_back({offset_r, offset_c, nx[k+1], nx[k+1]});
440  offset_r+= nx[k+1]+ng[k];
441  }
442  CD_blocks_.push_back({offset_r, offset_c, ng[N_], nx[N_]+nu[N_]});
443  C_blocks.push_back({offset_r, offset_c, ng[N_], nx[N_]});
444  D_blocks.push_back({offset_r, offset_c+nx[N_], ng[N_], nu[N_]});
445 
446  casadi_int offset = 0;
447  AB_offsets_.push_back(0);
448  for (auto e : AB_blocks_) {
449  offset += e.rows*e.cols;
450  AB_offsets_.push_back(offset);
451  }
452  offset = 0;
453  CD_offsets_.push_back(0);
454  for (auto e : CD_blocks_) {
455  offset += e.rows*e.cols;
456  CD_offsets_.push_back(offset);
457  }
458 
459  ABsp_ = blocksparsity(na_, nx_, AB_blocks_);
460  CDsp_ = blocksparsity(na_, nx_, CD_blocks_);
461  Isp_ = blocksparsity(na_, nx_, I_blocks_, true);
462 
463  Sparsity total = ABsp_ + CDsp_ + Isp_;
464 
465  if (debug_) {
466  total.to_file("debug_fatrop_expected.mtx");
467  blocksparsity(na_, nx_, A_blocks).to_file("debug_fatrop_A.mtx");
468  blocksparsity(na_, nx_, B_blocks).to_file("debug_fatrop_B.mtx");
469  blocksparsity(na_, nx_, C_blocks).to_file("debug_fatrop_C.mtx");
470  blocksparsity(na_, nx_, D_blocks).to_file("debug_fatrop_D.mtx");
471  Isp_.to_file("debug_fatrop_I.mtx");
472  std::vector<casadi_int> errors_vec(errors.begin(), errors.end());
473  std::vector<casadi_int> colind = {0, static_cast<casadi_int>(errors_vec.size())};
474  Sparsity(na_, 1, colind, errors_vec).to_file("debug_fatrop_errors.mtx");
475  }
476 
477  casadi_assert(errors.empty() && (A_ + total).nnz() == total.nnz(),
478  "Fatrop: specified structure of A does not correspond to what the interface can handle. "
479  "Structure is: N " + str(N_) + ", nx " + str(nx) + ", nu " + str(nu) + ", "
480  "ng " + str(ng) + ".\n"
481  "Note that debug_fatrop_expected.mtx and debug_fatrop_actual.mtx are written "
482  "to the current directory when 'debug' option is true.\n"
483  "These can be read with Sparsity.from_file(...)."
484  "For a ready-to-use script, "
485  "see https://gist.github.com/jgillis/dec56fa16c90a8e4a69465e8422c5459");
486  casadi_assert_dev(total.nnz() == ABsp_.nnz() + CDsp_.nnz() + Isp_.nnz());
487 
488  /* Disassemble H input into:
489  Q S'
490  S R
491  Q S'
492  S R
493 
494  Multiply by 2
495  */
496  offset = 0;
497  for (casadi_int k=0;k<N_+1;++k) { // Loop over blocks
498  RSQ_blocks_.push_back({offset, offset, nx[k]+nu[k], nx[k]+nu[k]});
499  offset+= nx[k]+nu[k];
500  }
501  RSQsp_ = blocksparsity(nx_, nx_, RSQ_blocks_);
502 
503  offset = 0;
504  RSQ_offsets_.push_back(0);
505  for (auto e : RSQ_blocks_) {
506  offset += e.rows*e.cols;
507  RSQ_offsets_.push_back(offset);
508  }
509 
510  set_fatrop_prob();
511 
512  // Allocate memory
513  casadi_int sz_arg, sz_res, sz_w, sz_iw;
514  casadi_fatrop_work(&p_, &sz_arg, &sz_res, &sz_iw, &sz_w);
515 
516  alloc_arg(sz_arg, true);
517  alloc_res(sz_res, true);
518  alloc_iw(sz_iw, true);
519  alloc_w(sz_w, true);
520  }
521 
522  int FatropInterface::init_mem(void* mem) const {
523  if (Nlpsol::init_mem(mem)) return 1;
524  if (!mem) return 1;
525  auto m = static_cast<FatropMemory*>(mem);
526  casadi_fatrop_init_mem(&m->d);
527 
528  return 0;
529  }
530 
531  void FatropInterface::free_mem(void* mem) const {
532  auto m = static_cast<FatropMemory*>(mem);
533  casadi_fatrop_free_mem(&m->d);
534  delete static_cast<FatropMemory*>(mem);
535  }
536 
538  void FatropInterface::set_work(void* mem, const double**& arg, double**& res,
539  casadi_int*& iw, double*& w) const {
540  auto m = static_cast<FatropMemory*>(mem);
541 
542  // Set work in base classes
543  Nlpsol::set_work(mem, arg, res, iw, w);
544 
545  m->d.prob = &p_;
546  m->d.nlp = &m->d_nlp;
547 
548  casadi_fatrop_set_work(&m->d, &arg, &res, &iw, &w);
549 
550  m->d.nlp->oracle->m = static_cast<void*>(m);
551 
552  // options
553  }
554 
555  int FatropInterface::solve(void* mem) const {
556  auto m = static_cast<FatropMemory*>(mem);
557 
558  // Cache the solver: presolve (re)creates it only when needed
559  bool new_solver = (m->d.solver == 0);
560  // fatrop's solve repoints a process-wide singleton stream via
561  // fatrop::OutputStreamManager::set_stream, which is not thread-safe.
562  // Serialize across threads.
563  {
564 #ifdef CASADI_WITH_THREAD
565  static std::mutex mutex_fatrop_create;
566  std::lock_guard<std::mutex> lock(mutex_fatrop_create);
567 #endif //CASADI_WITH_THREAD
568  casadi_fatrop_presolve(&m->d);
569  }
570 
571  // Set options only when a new solver was created (options persist across solves)
572  if (new_solver) {
573  for (const auto& kv : opts_) {
574  switch (fatrop_ocp_c_option_type(kv.first.c_str())) {
575  case 0:
576  fatrop_ocp_c_set_option_double(m->d.solver, kv.first.c_str(), kv.second);
577  break;
578  case 1:
579  fatrop_ocp_c_set_option_int(m->d.solver, kv.first.c_str(), kv.second.to_int());
580  break;
581  case 2:
582  fatrop_ocp_c_set_option_bool(m->d.solver, kv.first.c_str(), kv.second.to_bool());
583  break;
584  case 3:
585  {
586  std::string s = kv.second.to_string();
587  fatrop_ocp_c_set_option_string(m->d.solver, kv.first.c_str(), s.c_str());
588  }
589  break;
590  case -1:
591  casadi_error("Fatrop option not supported: " + kv.first);
592  default:
593  casadi_error("Unknown option type.");
594  }
595  }
596  }
597 
598  casadi_fatrop_solve(&m->d);
599 
600  m->success = m->d.success;
601  m->unified_return_status = static_cast<UnifiedReturnStatus>(m->d.unified_return_status);
602 
603  return 0;
604  }
605 
606  Dict FatropInterface::get_stats(void* mem) const {
607  Dict stats = Nlpsol::get_stats(mem);
608  auto m = static_cast<FatropMemory*>(mem);
609  Dict fatrop;
610  fatrop["compute_sd_time"] = m->d.stats.compute_sd_time;
611  fatrop["duinf_time"] = m->d.stats.duinf_time;
612  fatrop["eval_hess_time"] = m->d.stats.eval_hess_time;
613  fatrop["eval_jac_time"] = m->d.stats.eval_jac_time;
614  fatrop["eval_cv_time"] = m->d.stats.eval_cv_time;
615  fatrop["eval_grad_time"] = m->d.stats.eval_grad_time;
616  fatrop["eval_obj_time"] = m->d.stats.eval_obj_time;
617  fatrop["initialization_time"] = m->d.stats.initialization_time;
618  fatrop["time_total"] = m->d.stats.time_total;
619  fatrop["eval_hess_count"] = m->d.stats.eval_hess_count;
620  fatrop["eval_jac_count"] = m->d.stats.eval_jac_count;
621  fatrop["eval_cv_count"] = m->d.stats.eval_cv_count;
622  fatrop["eval_grad_count"] = m->d.stats.eval_grad_count;
623  fatrop["eval_obj_count"] = m->d.stats.eval_obj_count;
624  fatrop["iterations_count"] = m->d.stats.iterations_count;
625  fatrop["return_flag"] = m->d.stats.return_flag;
626  stats["fatrop"] = fatrop;
627  stats["iter_count"] = m->d.stats.iterations_count;
628  stats["nx"] = nxs_;
629  stats["nu"] = nus_;
630  stats["ng"] = ngs_;
631  stats["N"] = N_;
632  stats["return_status"] = m->d.return_status;
633  return stats;
634  }
635 
637  g << "casadi_fatrop_init_mem(&" + codegen_mem(g) + ");\n";
638  g << "return 0;\n";
639  }
640 
642  g << "casadi_fatrop_free_mem(&" + codegen_mem(g) + ");\n";
643  }
644 
661  g.add_dependency(get_function("nlp_f"));
662  g.add_dependency(get_function("nlp_grad_f"));
663  g.add_dependency(get_function("nlp_g"));
664  g.add_dependency(get_function("nlp_jac_g"));
665  if (exact_hessian_) {
666  g.add_dependency(get_function("nlp_hess_l"));
667  }
668  g.add_include("fatrop/ocp/OCPCInterface.h");
669 
670  std::string name = "fatrop_cb_write";
671  std::string f = g.shorthand(name);
672 
673  g << "void " << f
674  << "(const char* msg, int num) {\n";
675  g.flush(g.body);
676  g.scope_enter();
677  g << "CASADI_PRINTF(\"%.*s\", num, msg);\n";
678  g.scope_exit();
679  g << "}\n";
680 
681  name = "fatrop_cb_flush";
682  f = g.shorthand(name);
683 
684  g << "void " << f
685  << "(void) {\n";
686  g.flush(g.body);
687  g.scope_enter();
688  g.scope_exit();
689  g << "}\n";
690 }
691 
694  g.auxiliaries << g.sanitize_source(fatrop_runtime_str, {"casadi_real"});
695 
696  g.local("d", "struct casadi_fatrop_data*");
697  g.init_local("d", "&" + codegen_mem(g));
698  g.local("p", "struct casadi_fatrop_prob");
699  set_fatrop_prob(g);
700 
701  g << "casadi_fatrop_set_work(d, &arg, &res, &iw, &w);\n";
702  g << "casadi_oracle_set_work(d->nlp->oracle, &arg, &res, &iw, &w);\n";
703 
704  // Cache the solver: presolve (re)creates it only when needed
705  g << "{\n";
706  g << "int new_solver = (d->solver == 0);\n";
707  // fatrop's solve repoints a process-wide singleton stream via
708  // fatrop::OutputStreamManager::set_stream, which is not thread-safe.
709  // Serialize across threads.
710  if (g.thread_safe()) {
711  Function F = shared_from_this<Function>();
712  std::string mutex_name = codegen_name(g, false) + "_fatrop_create_mutex";
713  g.define_local_mutex(F, mutex_name);
714  std::string mtx = g.local_mutex(F, mutex_name);
715  g << "CASADI_MUTEX_LOCK(&" << mtx << ");\n";
716  g << "casadi_fatrop_presolve(d);\n";
717  g << "CASADI_MUTEX_UNLOCK(&" << mtx << ");\n";
718  } else {
719  g << "casadi_fatrop_presolve(d);\n";
720  }
721  g << "if (new_solver) {\n";
722 
723  for (const auto& kv : opts_) {
724  switch (fatrop_ocp_c_option_type(kv.first.c_str())) {
725  case 0:
726  g << "fatrop_ocp_c_set_option_double(d->solver, \"" + kv.first + "\", "
727  + g.constant(kv.second.to_double()) + ");\n";
728  break;
729  case 1:
730  g << "fatrop_ocp_c_set_option_int(d->solver, \"" + kv.first + "\", "
731  + str(kv.second.to_int()) + ");\n";
732  break;
733  case 2:
734  g << "fatrop_ocp_c_set_option_bool(d->solver, \"" + kv.first + "\", "
735  + str(static_cast<int>(kv.second.to_bool())) + ");\n";
736  break;
737  case 3:
738  {
739  std::string s = kv.second.to_string();
740  g << "fatrop_ocp_c_set_option_bool(d->solver, \"" + kv.first + "\", \""
741  + s + "\");\n";
742  }
743  break;
744  case -1:
745  casadi_error("Fatrop option not supported: " + kv.first);
746  default:
747  casadi_error("Unknown option type.");
748  }
749  }
750 
751  g << "}\n";
752  g << "}\n";
753  g << "casadi_fatrop_solve(d);\n";
754 
756 
757  if (error_on_fail_) {
758  g << "return d->unified_return_status;\n";
759  } else {
760  g << "return 0;\n";
761  }
762 }
763 
764 std::vector<casadi_int> fatrop_blocks_pack(const std::vector<casadi_ocp_block>& blocks) {
765  size_t N = blocks.size();
766  std::vector<casadi_int> ret(4*N+1);
767  casadi_int* r = get_ptr(ret);
768  *r++ = N;
769  for (casadi_int i=0;i<N;++i) {
770  *r++ = blocks[i].offset_r;
771  *r++ = blocks[i].offset_c;
772  *r++ = blocks[i].rows;
773  *r++ = blocks[i].cols;
774  }
775  return ret;
776 }
777 
778 
779 
781  p_.nlp = &p_nlp_;
782  p_.nx = get_ptr(nxs_);
783  p_.nu = get_ptr(nus_);
784  p_.ABsp = ABsp_;
785  p_.AB_offsets = get_ptr(AB_offsets_);
786  p_.CDsp = CDsp_;
787  p_.CD_offsets = get_ptr(CD_offsets_);
788  p_.RSQsp = RSQsp_;
789  p_.RSQ_offsets = get_ptr(RSQ_offsets_);
790  p_.Isp = Isp_;
791  p_.I_offsets = get_ptr(I_offsets_);
792 
793  p_.AB = get_ptr(AB_blocks_);
794  p_.CD = get_ptr(CD_blocks_);
795  p_.RSQ = get_ptr(RSQ_blocks_);
796  p_.I = get_ptr(I_blocks_);
797  p_.N = N_;
798 
799  p_.sp_a = jacg_sp_;
800  p_.sp_h = hesslag_sp_;
801 
802  p_.nlp_hess_l = OracleCallback("nlp_hess_l", this);
803  p_.nlp_jac_g = OracleCallback("nlp_jac_g", this);
804  p_.nlp_grad_f = OracleCallback("nlp_grad_f", this);
805  p_.nlp_f = OracleCallback("nlp_f", this);
806  p_.nlp_g = OracleCallback("nlp_g", this);
807  p_.write = &casadi_c_logger_write;
808  p_.flush = &casadi_c_logger_flush;
809 
810  casadi_fatrop_setup(&p_);
811 }
812 
813  void codegen_unpack_block(CodeGenerator& g, const std::string& name,
814  const std::vector<casadi_ocp_block>& blocks) {
815  casadi_int sz = blocks.size();
816  if (sz==0) sz++;
817  std::string n = "block_" + name + "[" + str(sz) + "]";
818  g.local(n, "static struct casadi_ocp_block");
819  g << "p." << name << " = block_" + name + ";\n";
820  g << "casadi_unpack_ocp_blocks(" << "p." << name
821  << ", " << g.constant(fatrop_blocks_pack(blocks)) << ");\n";
822  }
823 
824  void unpack_block(const std::vector<casadi_int>& p, std::vector<casadi_ocp_block>& blocks) {
825  const casadi_int* packed = get_ptr(p);
826  casadi_int N = *packed++;
827  blocks.resize(N);
828  for (casadi_int i=0;i<N;++i) {
829  blocks[i].offset_r = *packed++;
830  blocks[i].offset_c = *packed++;
831  blocks[i].rows = *packed++;
832  blocks[i].cols = *packed++;
833  }
834  }
835 
837  if (jacg_sp_.size1()>0 && jacg_sp_.nnz()==0) {
838  casadi_error("Empty sparsity pattern not supported in FATROP C interface");
839  }
840  g << "d->nlp = &d_nlp;\n";
841  g << "d->prob = &p;\n";
842  g << "p.nlp = &p_nlp;\n";
843 
844  g << "p.nx = " << g.constant(nxs_) << ";\n";
845  g << "p.nu = " << g.constant(nus_) << ";\n";
846  g << "p.ABsp = " << g.sparsity(ABsp_) << ";\n";
847  g << "p.AB_offsets = " << g.constant(AB_offsets_) << ";\n";
848  g << "p.CDsp = " << g.sparsity(CDsp_) << ";\n";
849  g << "p.CD_offsets = " << g.constant(CD_offsets_) << ";\n";
850  g << "p.RSQsp = " << g.sparsity(RSQsp_) << ";\n";
851  g << "p.RSQ_offsets = " << g.constant(RSQ_offsets_) << ";\n";
852  g << "p.Isp = " << g.sparsity(Isp_) << ";\n";
853  g << "p.I_offsets = " << g.constant(I_offsets_) << ";\n";
854 
855  codegen_unpack_block(g, "AB", AB_blocks_);
856  codegen_unpack_block(g, "CD", CD_blocks_);
857  codegen_unpack_block(g, "RSQ", RSQ_blocks_);
858  codegen_unpack_block(g, "I", I_blocks_);
859  g << "p.N = " << N_ << ";\n";
860 
861  g.setup_callback("p.nlp_jac_g", get_function("nlp_jac_g"));
862  g.setup_callback("p.nlp_grad_f", get_function("nlp_grad_f"));
863  g.setup_callback("p.nlp_f", get_function("nlp_f"));
864  g.setup_callback("p.nlp_g", get_function("nlp_g"));
865  g.setup_callback("p.nlp_hess_l", get_function("nlp_hess_l"));
866 
867  g << "p.sp_a = " << g.sparsity(jacg_sp_) << ";\n";
868  if (exact_hessian_) {
869  g << "p.sp_h = " << g.sparsity(hesslag_sp_) << ";\n";
870  } else {
871  g << "p.sp_h = 0;\n";
872  }
873 
874  g << "p.write = &" << g.shorthand("fatrop_cb_write") << ";\n";
875  g << "p.flush = &" << g.shorthand("fatrop_cb_flush") << ";\n";
876 
877  g << "casadi_fatrop_setup(&p);\n";
878 
879 }
880 
882  s.version("FatropInterface", 1);
883  s.unpack("FatropInterface::jacg_sp", jacg_sp_);
884  s.unpack("FatropInterface::hesslag_sp", hesslag_sp_);
885  s.unpack("FatropInterface::exact_hessian", exact_hessian_);
886  s.unpack("FatropInterface::opts", opts_);
887  s.unpack("FatropInterface::convexify", convexify_);
888 
889 
890  s.unpack("FatropInterface::Isp", Isp_);
891  s.unpack("FatropInterface::ABsp", ABsp_);
892  s.unpack("FatropInterface::CDsp", CDsp_);
893  s.unpack("FatropInterface::RSQsp", RSQsp_);
894 
895  std::vector<casadi_int> AB_blocks;
896  s.unpack("FatropInterface::AB_blocks", AB_blocks);
897  unpack_block(AB_blocks, AB_blocks_);
898  std::vector<casadi_int> CD_blocks;
899  s.unpack("FatropInterface::CD_blocks", CD_blocks);
900  unpack_block(CD_blocks, CD_blocks_);
901  std::vector<casadi_int> RSQ_blocks;
902  s.unpack("FatropInterface::RSQ_blocks", RSQ_blocks);
903  unpack_block(RSQ_blocks, RSQ_blocks_);
904  std::vector<casadi_int> I_blocks;
905  s.unpack("FatropInterface::I_blocks", I_blocks);
906  unpack_block(I_blocks, I_blocks_);
907 
908  s.unpack("FatropInterface::nxs", nxs_);
909  s.unpack("FatropInterface::nus", nus_);
910  s.unpack("FatropInterface::ngs", ngs_);
911  s.unpack("FatropInterface::N", N_);
912 
913  casadi_int structure_detection;
914  s.unpack("FatropInterface::structure_detection", structure_detection);
915  structure_detection_ = static_cast<StructureDetection>(structure_detection);
916 
917 
918  s.unpack("FatropInterface::AB_offsets", AB_offsets_);
919  s.unpack("FatropInterface::CD_offsets", CD_offsets_);
920  s.unpack("FatropInterface::RSQ_offsets", RSQ_offsets_);
921  s.unpack("FatropInterface::I_offsets", I_offsets_);
922  s.unpack("FatropInterface::debug", debug_);
923 
924  set_fatrop_prob();
925 }
926 
929  s.version("FatropInterface", 1);
930 
931  s.pack("FatropInterface::jacg_sp", jacg_sp_);
932  s.pack("FatropInterface::hesslag_sp", hesslag_sp_);
933  s.pack("FatropInterface::exact_hessian", exact_hessian_);
934  s.pack("FatropInterface::opts", opts_);
935  s.pack("FatropInterface::convexify", convexify_);
936 
937  s.pack("FatropInterface::Isp", Isp_);
938  s.pack("FatropInterface::ABsp", ABsp_);
939  s.pack("FatropInterface::CDsp", CDsp_);
940  s.pack("FatropInterface::RSQsp", RSQsp_);
941 
942  s.pack("FatropInterface::AB_blocks", fatrop_blocks_pack(AB_blocks_));
943  s.pack("FatropInterface::CD_blocks", fatrop_blocks_pack(CD_blocks_));
944  s.pack("FatropInterface::RSQ_blocks", fatrop_blocks_pack(RSQ_blocks_));
945  s.pack("FatropInterface::I_blocks", fatrop_blocks_pack(I_blocks_));
946 
947  s.pack("FatropInterface::nxs", nxs_);
948  s.pack("FatropInterface::nus", nus_);
949  s.pack("FatropInterface::ngs", ngs_);
950  s.pack("FatropInterface::N", N_);
951  s.pack("FatropInterface::structure_detection", static_cast<casadi_int>(structure_detection_));
952  s.pack("FatropInterface::AB_offsets", AB_offsets_);
953  s.pack("FatropInterface::CD_offsets", CD_offsets_);
954  s.pack("FatropInterface::RSQ_offsets", RSQ_offsets_);
955  s.pack("FatropInterface::I_offsets", I_offsets_);
956  s.pack("FatropInterface::debug", debug_);
957 }
958 
959 } // namespace casadi
Helper class for C code generation.
std::string add_dependency(const Function &f)
Add a function dependency.
void scope_enter()
Enter a local scope.
std::string constant(const std::vector< casadi_int > &v)
Represent an array constant; adding it when new.
void flush(std::ostream &s)
Flush the buffer to a stream of choice.
bool thread_safe() const
Emit thead safe code chekout/release?
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 scope_exit()
Exit a local scope.
void init_local(const std::string &name, const std::string &def)
Specify the default value for a local variable.
std::string local_mutex(const Function &f, const std::string &name) const
Access a static mutex associated with a function.
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 shorthand(const std::string &name) const
Get a shorthand.
std::stringstream body
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.
void define_local_mutex(const Function &f, const std::string &name)
Declare a static mutex associated with a 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 codegen_init_mem(CodeGenerator &g) const override
Codegen alloc_mem.
void codegen_body(CodeGenerator &g) const override
Generate code for the function body.
void free_mem(void *mem) const override
Free memory block.
void init(const Dict &opts) override
Initialize.
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.
int init_mem(void *mem) const override
Initalize memory block.
Dict opts_
All FATROP options.
static ProtoFunction * deserialize(DeserializingStream &s)
Deserialize into MX.
void set_work(void *mem, const double **&arg, double **&res, casadi_int *&iw, double *&w) const override
Set the (persistent) work vectors.
ConvexifyData convexify_data_
Data for convexification.
FatropInterface(const std::string &name, const Function &nlp)
void codegen_free_mem(CodeGenerator &g) const override
Codegen free_mem.
static const Options options_
Options.
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
static const std::string meta_doc
A documentation string.
bool exact_hessian_
Exact Hessian?
int solve(void *mem) const override
static Nlpsol * creator(const std::string &name, const Function &nlp)
Create a new NLP Solver.
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.
virtual std::string codegen_name(const CodeGenerator &g, bool ns=true) const
Get name in codegen.
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 MatType zeros(casadi_int nrow=1, casadi_int ncol=1)
Create a dense matrix or a matrix with specified sparsity with all entries zero.
static casadi_int start_index
static Matrix< double > eye(casadi_int n)
create an n-by-n identity matrix
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
casadi_int ng_
Number of constraints.
Definition: nlpsol_impl.hpp:69
int init_mem(void *mem) const override
Initalize memory block.
Definition: nlpsol.cpp:692
std::vector< bool > equality_
Options.
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
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: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.
General sparsity class.
Definition: sparsity.hpp:106
casadi_int size1() const
Get the number of rows.
Definition: sparsity.cpp:124
Sparsity T() const
Transpose the matrix.
Definition: sparsity.cpp:394
casadi_int nnz() const
Get the number of (structural) non-zeros.
Definition: sparsity.cpp:148
casadi_int size2() const
Get the number of columns.
Definition: sparsity.cpp:128
std::vector< casadi_int > get_colind() const
Get the column index for each column.
Definition: sparsity.cpp:364
void to_file(const std::string &filename, const std::string &format_hint="") const
Definition: sparsity.cpp:1996
std::vector< casadi_int > get_row() const
Get the row for each non-zero entry.
Definition: sparsity.cpp:372
bool is_symmetric() const
Is symmetric?
Definition: sparsity.cpp:317
The casadi namespace.
Definition: archiver.cpp:28
void unpack_block(const std::vector< casadi_int > &p, std::vector< casadi_ocp_block > &blocks)
std::vector< casadi_int > range(casadi_int start, casadi_int stop, casadi_int step, casadi_int len)
Range function.
std::vector< casadi_int > fatrop_blocks_pack(const std::vector< casadi_ocp_block > &blocks)
void CASADI_NLPSOL_FATROP_EXPORT casadi_load_nlpsol_fatrop()
@ 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_FATROP_EXPORT casadi_register_nlpsol_fatrop(Nlpsol::Plugin *plugin)
T * get_ptr(std::vector< T > &v)
Get a pointer to the data contained in the vector.
T sum(const std::vector< T > &values)
sum
void report_issue(casadi_int i, const std::string &msg)
void codegen_unpack_block(CodeGenerator &g, const std::string &name, const std::vector< casadi_ocp_block > &blocks)
UnifiedReturnStatus
casadi_fatrop_data< double > d
struct FatropOcpCStats stats
OracleCallback nlp_grad_f
const casadi_int * I_offsets
const casadi_int * sp_h
const casadi_int * nx
const casadi_int * RSQsp
OracleCallback nlp_hess_l
FatropOcpCWrite write
const casadi_int * nu
casadi_ocp_block * I
const casadi_int * sp_a
FatropOcpCFlush flush
casadi_ocp_block * AB
const casadi_int * CDsp
OracleCallback nlp_g
casadi_ocp_block * RSQ
const casadi_int * RSQ_offsets
casadi_ocp_block * CD
const casadi_int * CD_offsets
const casadi_int * Isp
const casadi_nlpsol_prob< T1 > * nlp
OracleCallback nlp_f
OracleCallback nlp_jac_g
const casadi_int * ABsp
const casadi_int * AB_offsets