mosek_interface.cpp
1 /*
2  * This file is part of CasADi.
3  *
4  * CasADi -- A symbolic framework for dynamic optimization.
5  * Copyright (C) 2010-2026 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 #include "mosek_interface.hpp"
26 #include "casadi/core/nlp_tools.hpp"
27 #include "casadi/core/casadi_misc.hpp"
28 
29 #include <mosek_runtime_str.h>
30 
31 #include <cstdio>
32 #include <cstring>
33 
34 namespace casadi {
35 
36  extern "C"
37  int CASADI_CONIC_MOSEK_EXPORT
38  casadi_register_conic_mosek(Conic::Plugin* plugin) {
39  plugin->creator = MosekInterface::creator;
40  plugin->name = "mosek";
41  plugin->doc = MosekInterface::meta_doc.c_str();
42  plugin->version = CASADI_VERSION;
43  plugin->options = &MosekInterface::options_;
44  plugin->deserialize = &MosekInterface::deserialize;
45  #ifdef MOSEK_ADAPTOR
46  char buffer[400];
47  int ret = mosek_adaptor_load(buffer, sizeof(buffer));
48  if (ret!=0) {
49  casadi_warning("Failed to load Mosek adaptor: " + std::string(buffer) + ".");
50  return 1;
51  }
52  #endif
53  return 0;
54  }
55 
56  extern "C"
57  void CASADI_CONIC_MOSEK_EXPORT casadi_load_conic_mosek() {
59  }
60 
61 
62  MosekInterface::MosekInterface(const std::string& name,
63  const std::map<std::string, Sparsity>& st)
64  : Conic(name, st) {
65  }
66 
68  = {{&Conic::options_},
69  {{"mosek",
70  {OT_DICT,
71  "Options to be passed to MOSEK. Each entry's key is the MOSEK "
72  "parameter name (e.g. \"MSK_IPAR_LOG\", \"MSK_DPAR_INTPNT_QO_TOL_REL_GAP\"); "
73  "the value's type must match the parameter's type "
74  "(int parameters take int, dpar take double, spar take string)."
75  }},
76  }
77  };
78 
79  void MosekInterface::init(const Dict& opts) {
80  // Call the init method of the base class
81  Conic::init(opts);
82 
83  // Read user options
84  for (auto&& op : opts) {
85  if (op.first=="mosek") {
86  opts_ = op.second;
87  }
88  }
89 
90  // Pre-compute SOCP mapping (must run before set_mosek_prob so the
91  // socp pointer is resolved in p_)
92  has_socp_ = !Q_.is_null() && Q_.nnz() > 0;
93  if (has_socp_) build_socp_config();
94 
97 
98  // Allocate memory
99  casadi_int sz_arg, sz_res, sz_w, sz_iw;
100  casadi_mosek_work(&p_, &sz_arg, &sz_res, &sz_iw, &sz_w);
101 
102  alloc_arg(sz_arg, true);
103  alloc_res(sz_res, true);
104  alloc_iw(sz_iw, true);
105  alloc_w(sz_w, true);
106  }
107 
109  colinda_.resize(A_.size2() + 1);
110  rowa_.resize(A_.nnz());
111  copy_vector(A_.colind(), colinda_);
112  copy_vector(A_.row(), rowa_);
113 
114  // Build lower-triangular triplets from the symmetric H_.
115  // Mosek's MSK_putqobj wants entries with row >= col (lower triangle).
116  qobj_row_.clear();
117  qobj_col_.clear();
118  qobj_nz_idx_.clear();
119  const casadi_int* Hc = H_.colind();
120  const casadi_int* Hr = H_.row();
121  for (casadi_int j = 0; j < H_.size2(); ++j) {
122  for (casadi_int k = Hc[j]; k < Hc[j + 1]; ++k) {
123  casadi_int i = Hr[k];
124  if (i >= j) {
125  qobj_row_.push_back(static_cast<int>(i));
126  qobj_col_.push_back(static_cast<int>(j));
127  qobj_nz_idx_.push_back(static_cast<int>(k));
128  }
129  }
130  }
131 
132  // Discrete variable flags: 'I' for integer, 'C' for continuous.
133  // Only mark as MIP (non-empty coltype_) if at least one variable is
134  // actually discrete -- callers (like Opti) pass an all-false discrete
135  // vector for pure LPs/QPs, which we should NOT classify as MIP.
136  coltype_.clear();
137  bool any_discrete = false;
138  for (bool d : discrete_) if (d) { any_discrete = true; break; }
139  if (any_discrete) {
140  coltype_.assign(nx_, 'C');
141  for (casadi_int i = 0; i < nx_; ++i) {
142  if (discrete_[i]) coltype_[i] = 'I';
143  }
144  }
145  }
146 
148  p_.qp = &p_qp_;
149  p_.colinda = get_ptr(colinda_);
150  p_.rowa = get_ptr(rowa_);
151  p_.qobj_row = qobj_row_.empty() ? nullptr : get_ptr(qobj_row_);
152  p_.qobj_col = qobj_col_.empty() ? nullptr : get_ptr(qobj_col_);
153  p_.qobj_nz_idx = qobj_nz_idx_.empty() ? nullptr : get_ptr(qobj_nz_idx_);
154  p_.nquad = static_cast<int>(qobj_row_.size());
155  p_.coltype = coltype_.empty() ? nullptr : get_ptr(coltype_);
156 
157  p_.socp = has_socp_ ? &socp_ : nullptr;
158 
159  casadi_mosek_setup(&p_);
160  }
161 
162  // Emit "p.<field> = <local int[]>;" for an int*-typed prob field.
163  // Empty vectors emit a NULL assignment directly (avoids the unused-constant
164  // warning from constant_copy on empty arrays).
165  static void codegen_int_field(CodeGenerator& g, const std::string& prob_field,
166  const std::string& local_name, const std::vector<int>& v) {
167  if (v.empty()) {
168  g << "p." << prob_field << " = 0;\n";
169  } else {
170  g.constant_copy(local_name, vector_static_cast<casadi_int>(v), "int");
171  g << "p." << prob_field << " = " << local_name << ";\n";
172  }
173  }
174 
176  g << "p.qp = &p_qp;\n";
177 
178  codegen_int_field(g, "colinda", "colinda", colinda_);
179  codegen_int_field(g, "rowa", "rowa", rowa_);
180  codegen_int_field(g, "qobj_row", "qobj_row", qobj_row_);
181  codegen_int_field(g, "qobj_col", "qobj_col", qobj_col_);
182  codegen_int_field(g, "qobj_nz_idx", "qobj_nz_idx", qobj_nz_idx_);
183  g << "p.nquad = " << qobj_row_.size() << ";\n";
184 
185  if (coltype_.empty()) {
186  g << "p.coltype = 0;\n";
187  } else {
188  g << "p.coltype = " << g.constant(coltype_) << ";\n";
189  }
190 
191  if (has_socp_) {
192  g.local("socp_p", "struct casadi_socp_prob");
193  g << "socp_p.n_blocks = " << socp_.n_blocks << ";\n";
194  g << "socp_p.n_lifted = " << socp_.n_lifted << ";\n";
195  g << "socp_p.nx = " << socp_.nx << ";\n";
196  g << "socp_p.r = " << g.constant(socp_r_) << ";\n";
197  g << "socp_p.mq_colind = " << g.constant(socp_mq_colind_) << ";\n";
198  g << "socp_p.mq_row = " << g.constant(socp_mq_row_) << ";\n";
199  g << "socp_p.mq_data = " << g.constant(socp_mq_data_) << ";\n";
200  g << "socp_p.map_P = " << g.constant(socp_map_P_) << ";\n";
201  g << "casadi_socp_setup(&socp_p);\n";
202  g << "p.socp = &socp_p;\n";
203  } else {
204  g << "p.socp = 0;\n";
205  }
206 
207  g << "casadi_mosek_setup(&p);\n";
208  }
209 
211  g << "casadi_mosek_init_mem(&" + codegen_mem(g) + ");\n";
212  g << "return 0;\n";
213  }
214 
216  g << "casadi_mosek_free_mem(&" + codegen_mem(g) + ");\n";
217  }
218 
220  qp_codegen_body(g);
222  // Always emit casadi_socp definitions: the mosek data struct
223  // references casadi_socp_data even when no Q/P is present.
225  g.add_include("mosek.h");
226  g.auxiliaries << g.sanitize_source(mosek_runtime_str, {"casadi_real"});
227 
228  g.local("d", "struct casadi_mosek_data*");
229  g.init_local("d", "&" + codegen_mem(g));
230  g.local("p", "struct casadi_mosek_prob");
231  set_mosek_prob(g);
232 
233  g << "d->prob = &p;\n";
234  g << "d->qp = &d_qp;\n";
235  g << "casadi_mosek_set_work(d, &arg, &res, &iw, &w);\n";
236 
237  if (has_socp_) {
238  g << "d->socp.q = arg[" << CONIC_Q << "];\n";
239  g << "d->socp.p = arg[" << CONIC_P << "];\n";
240  }
241 
242  // Apply user options. Resolve the parameter's type at runtime via
243  // MSK_getparamname/MSK_getparamtype is awkward; the simplest robust
244  // path is to dispatch on the CasADi-side value type and let Mosek's
245  // typed setter accept-or-reject by name.
246  for (auto&& op : opts_) {
247  if (op.second.is_double()) {
248  g << "MSK_putnadouparam(d->task, " << g.constant(op.first) << ", "
249  << op.second.to_double() << ");\n";
250  } else if (op.second.is_int() || op.second.is_bool()) {
251  g << "MSK_putnaintparam(d->task, " << g.constant(op.first) << ", "
252  << static_cast<int>(op.second.to_int()) << ");\n";
253  } else if (op.second.is_string()) {
254  g << "MSK_putnastrparam(d->task, " << g.constant(op.first) << ", "
255  << g.constant(op.second.to_string()) << ");\n";
256  } else {
257  casadi_error("Unsupported option type for '" + op.first + "'.");
258  }
259  }
260 
261  g << "casadi_mosek_solve(d, arg, res, iw, w);\n";
262 
263  g << "if (!d_qp.success) {\n";
264  if (error_on_fail_) {
265  g << " return -1000;\n";
266  } else {
267  g << " return -1;\n";
268  }
269  g << "}\n";
270  g << "return 0;\n";
271  }
272 
273  void MosekInterface::build_socp_config() {
274  SDPToSOCPMem sm;
275  sdp_to_socp_init(sm);
276 
277  socp_r_ = sm.r;
278  casadi_int n_eq = sm.map_Q.size2();
279  socp_mq_colind_.assign(sm.map_Q.colind(), sm.map_Q.colind() + n_eq + 1);
280  socp_mq_row_.assign(sm.map_Q.row(), sm.map_Q.row() + sm.map_Q.nnz());
281  socp_mq_data_.assign(sm.map_Q.ptr(), sm.map_Q.ptr() + sm.map_Q.nnz());
282  socp_map_P_ = sm.map_P;
283 
284  socp_.n_blocks = static_cast<casadi_int>(sm.r.size() - 1);
285  socp_.r = get_ptr(socp_r_);
286  socp_.n_lifted = sm.r.back();
287  socp_.nx = nx_;
288  socp_.mq_colind = get_ptr(socp_mq_colind_);
289  socp_.mq_row = get_ptr(socp_mq_row_);
290  socp_.mq_data = get_ptr(socp_mq_data_);
291  socp_.map_P = get_ptr(socp_map_P_);
292  casadi_socp_setup(&socp_);
293  }
294 
295  static void mosek_stream_cb(void* /*h*/, const char* s) { std::fputs(s, stderr); }
296 
297  int MosekInterface::init_mem(void* mem) const {
298  if (Conic::init_mem(mem)) return 1;
299  if (!mem) return 1;
300 
301  auto m = static_cast<MosekMemory*>(mem);
302  if (casadi_mosek_init_mem(&m->d)) {
303  casadi_error("MOSEK: MSK_makeenv()/MSK_maketask() failed. "
304  "Check that the MOSEK license is reachable "
305  "(MOSEKLM_LICENSE_FILE or $HOME/mosek/mosek.lic).");
306  }
307  if (verbose_) {
308  MSK_linkfunctotaskstream(m->d.task, MSK_STREAM_LOG, nullptr, mosek_stream_cb);
309  }
310 
311  m->add_stat("preprocessing");
312  m->add_stat("solver");
313  m->add_stat("postprocessing");
314 
315  return 0;
316  }
317 
318  void MosekInterface::free_mem(void* mem) const {
319  auto m = static_cast<MosekMemory*>(mem);
320  casadi_mosek_free_mem(&m->d);
321  delete static_cast<MosekMemory*>(mem);
322  }
323 
325  void MosekInterface::set_work(void* mem, const double**& arg, double**& res,
326  casadi_int*& iw, double*& w) const {
327 
328  auto m = static_cast<MosekMemory*>(mem);
329 
330  Conic::set_work(mem, arg, res, iw, w);
331 
332  m->d.prob = &p_;
333  m->d.qp = &m->d_qp;
334 
335  casadi_mosek_set_work(&m->d, &arg, &res, &iw, &w);
336 
337  // SOCP inputs
338  if (has_socp_) {
339  m->d.socp.q = arg[CONIC_Q];
340  m->d.socp.p = arg[CONIC_P];
341  }
342 
343  // Push user parameters to MOSEK using typed setters dispatched on
344  // CasADi-side value type. The "na" (name-addressed) variants accept
345  // a name string and silently fail with a return code if the name does
346  // not match the type -- assert success here so the user gets a clear
347  // error.
348  for (auto&& op : opts_) {
349  MSKrescodee rc = MSK_RES_OK;
350  if (op.second.is_double()) {
351  rc = MSK_putnadouparam(m->d.task, op.first.c_str(), op.second.to_double());
352  } else if (op.second.is_int() || op.second.is_bool()) {
353  rc = MSK_putnaintparam(m->d.task, op.first.c_str(),
354  static_cast<int>(op.second.to_int()));
355  } else if (op.second.is_string()) {
356  rc = MSK_putnastrparam(m->d.task, op.first.c_str(),
357  op.second.to_string().c_str());
358  } else {
359  casadi_error("MOSEK: unsupported value type for parameter '"
360  + op.first + "'.");
361  }
362  casadi_assert(rc == MSK_RES_OK,
363  "MOSEK: failed to set parameter '" + op.first + "' (rc=" + str(rc) + ").");
364  }
365  }
366 
368  solve(const double** arg, double** res, casadi_int* iw, double* w, void* mem) const {
369  auto m = static_cast<MosekMemory*>(mem);
370 
371  m->fstats.at("solver").tic();
372  int rc = casadi_mosek_solve(&m->d, arg, res, iw, w);
373  m->fstats.at("solver").toc();
374 
375  return rc;
376  }
377 
379  clear_mem();
380  }
381 
382  Dict MosekInterface::get_stats(void* mem) const {
383  Dict stats = Conic::get_stats(mem);
384  auto m = static_cast<MosekMemory*>(mem);
385  stats["return_status"] = m->d.return_status;
386  stats["prob_status"] = m->d.prob_status;
387  stats["sol_status"] = m->d.sol_status;
388  stats["objective"] = m->d.obj_val;
389  return stats;
390  }
391 
393  s.version("MosekInterface", 1);
394  s.unpack("MosekInterface::opts", opts_);
395  has_socp_ = !Q_.is_null() && Q_.nnz() > 0;
396  if (has_socp_) build_socp_config();
397  init_dependent();
398  set_mosek_prob();
399  }
400 
403 
404  s.version("MosekInterface", 1);
405  s.pack("MosekInterface::opts", opts_);
406  }
407 
408 } // end namespace casadi
Helper class for C code generation.
std::string constant(const std::vector< casadi_int > &v)
Represent an array constant; adding it when new.
void local(const std::string &name, const std::string &type, const std::string &ref="")
Declare a local variable.
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 <....
void constant_copy(const std::string &var_name, const std::vector< casadi_int > &v, const std::string &type="casadi_int")
Represent an array constant; adding it when new.
std::stringstream auxiliaries
void add_auxiliary(Auxiliary f, const std::vector< std::string > &inst={"casadi_real"})
Add a built-in auxiliary function.
Internal class.
Definition: conic_impl.hpp:44
static const Options options_
Options.
Definition: conic_impl.hpp:83
casadi_int nx_
Number of decision variables.
Definition: conic_impl.hpp:173
int init_mem(void *mem) const override
Initalize memory block.
Definition: conic.cpp:466
Sparsity H_
Problem structure.
Definition: conic_impl.hpp:170
void init(const Dict &opts) override
Initialize.
Definition: conic.cpp:415
std::vector< bool > discrete_
Options.
Definition: conic_impl.hpp:164
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
Definition: conic.cpp:753
void set_work(void *mem, const double **&arg, double **&res, casadi_int *&iw, double *&w) const override
Set the (persistent) work vectors.
Definition: conic.cpp:473
Dict get_stats(void *mem) const override
Get all statistics.
Definition: conic.cpp:726
casadi_qp_prob< double > p_qp_
Definition: conic_impl.hpp:47
void qp_codegen_body(CodeGenerator &g) const
Generate code for the function body.
Definition: conic.cpp:812
void sdp_to_socp_init(SDPToSOCPMem &mem) const
SDP to SOCP conversion initialization.
Definition: conic.cpp:595
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.
bool is_null() const
Is a null pointer?
void codegen_body(CodeGenerator &g) const override
Generate code for the function body.
void init(const Dict &opts) override
Initialize.
Dict get_stats(void *mem) const override
Get all statistics.
void set_work(void *mem, const double **&arg, double **&res, casadi_int *&iw, double *&w) const override
Set the (persistent) work vectors.
void codegen_free_mem(CodeGenerator &g) const override
Codegen for free_mem.
static ProtoFunction * deserialize(DeserializingStream &s)
Deserialize with type disambiguation.
void free_mem(void *mem) const override
Free memory block.
int init_mem(void *mem) const override
Initalize memory block.
int solve(const double **arg, double **res, casadi_int *iw, double *w, void *mem) const override
Solve the QP.
void codegen_init_mem(CodeGenerator &g) const override
Codegen init for init_mem.
static const std::string meta_doc
A documentation string.
~MosekInterface() override
Destructor.
Dict opts_
All Mosek parameters (by name -> value)
MosekInterface(const std::string &name, const std::map< std::string, Sparsity > &st)
Constructor using sparsity patterns.
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
static Conic * creator(const std::string &name, const std::map< std::string, Sparsity > &st)
Create a new QP Solver.
static const Options options_
Options.
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 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
const casadi_int * row() const
Get a reference to row-vector,.
Definition: sparsity.cpp:164
const casadi_int * colind() const
Get a reference to the colindex of all column element (see class description)
Definition: sparsity.cpp:168
The casadi namespace.
Definition: archiver.cpp:28
void copy_vector(const std::vector< S > &s, std::vector< D > &d)
@ CONIC_Q
The matrix Q: sparse symmetric, (np^2 x n)
Definition: conic.hpp:193
@ CONIC_P
The matrix P: sparse symmetric, (np x np)
Definition: conic.hpp:195
static void mosek_stream_cb(void *, const char *s)
static void codegen_int_field(CodeGenerator &g, const std::string &prob_field, const std::string &local_name, const std::vector< int > &v)
std::string str(const T &v)
String representation, any type.
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
T * get_ptr(std::vector< T > &v)
Get a pointer to the data contained in the vector.
int CASADI_CONIC_MOSEK_EXPORT casadi_register_conic_mosek(Conic::Plugin *plugin)
void CASADI_CONIC_MOSEK_EXPORT casadi_load_conic_mosek()
casadi_mosek_data< double > d
Options metadata for a class.
Definition: options.hpp:40
std::map< std::string, FStats > fstats
const int * qobj_col
const char * coltype
const int * qobj_nz_idx
const int * qobj_row
const casadi_socp_prob< T1 > * socp
const casadi_qp_prob< T1 > * qp
const int * colinda
const casadi_int * r
Definition: casadi_socp.hpp:58
casadi_int n_lifted
Definition: casadi_socp.hpp:60
casadi_int n_blocks
Definition: casadi_socp.hpp:54
const casadi_int * mq_colind
Definition: casadi_socp.hpp:67
const casadi_int * map_P
Definition: casadi_socp.hpp:76
const casadi_int * mq_row
Definition: casadi_socp.hpp:68
const casadi_int * mq_data
Definition: casadi_socp.hpp:72