mosek_runtime.hpp
1 //
2 // MIT No Attribution
3 //
4 // Copyright (C) 2010-2026 Joel Andersson, Joris Gillis, Moritz Diehl, KU Leuven.
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a copy of this
7 // software and associated documentation files (the "Software"), to deal in the Software
8 // without restriction, including without limitation the rights to use, copy, modify,
9 // merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
10 // permit persons to whom the Software is furnished to do so.
11 //
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13 // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
14 // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
15 // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
16 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
17 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18 //
19 
20 // C-REPLACE "casadi_qp_prob<T1>" "struct casadi_qp_prob"
21 // C-REPLACE "casadi_qp_data<T1>" "struct casadi_qp_data"
22 // C-REPLACE "casadi_socp_prob<T1>" "struct casadi_socp_prob"
23 // C-REPLACE "casadi_socp_data<T1>" "struct casadi_socp_data"
24 
25 // C-REPLACE "reinterpret_cast<int**>" "(int**) "
26 // C-REPLACE "reinterpret_cast<int*>" "(int*) "
27 // C-REPLACE "reinterpret_cast<char*>" "(char*) "
28 // C-REPLACE "const_cast<int*>" "(int*) "
29 // C-REPLACE "static_cast<int>" "(int) "
30 // C-REPLACE "static_cast<MSKboundkeye>" "(MSKboundkeye) "
31 // C-REPLACE "static_cast<MSKvariabletypee>" "(MSKvariabletypee) "
32 
33 template<typename T1>
36 
37  // Linear constraint matrix in CSC form (cast to int, since Mosek takes int)
38  const int *colinda, *rowa;
39  // Quadratic objective in lower-triangular triplet form. Mosek's
40  // MSK_putqobj wants entries with row >= col (lower triangle), and
41  // interprets the objective as 0.5 x'Qx + c'x with Q symmetric --
42  // matching CasADi's convention. Pass the H[i,j] values as-is.
43  const int *qobj_row, *qobj_col;
44  // qobj_nz_idx[k] = index into d_qp->h[] of the k-th lower-tri entry
45  const int *qobj_nz_idx;
46  int nquad;
47  // Discrete variable flags ('I' for integer, 'C' for continuous), or NULL
48  const char *coltype;
49 
50  // SOCP support. Null if no SOC cones.
52 };
53 // C-REPLACE "casadi_mosek_prob<T1>" "struct casadi_mosek_prob"
54 
55 // SYMBOL "mosek_setup"
56 template<typename T1>
57 void casadi_mosek_setup(casadi_mosek_prob<T1>* p) {
58 
59 }
60 
61 // SYMBOL "mosek_data"
62 template<typename T1>
64  // Problem structure
66  // QP runtime data
68 
69  // Mosek environment + task handles. The env is a per-instance handle
70  // (created in init_mem) -- Mosek allows multiple envs per process.
71  MSKenv_t env;
72  MSKtask_t task;
73 
74  // Workspace owned by the C runtime (sized via casadi_mosek_work)
75  int *col_idx; // 0..nx-1, scratch for MSK_putvartypelist
76  int *vtype; // var type code int[nx], scratch for MSK_putvartypelist
77  T1 *qobj_val; // scaled Q triplet values, length nquad
78  T1 *dual_scratch; // length nx, scratch for sux retrieval
79  int *cone_idx; // scratch for MSK_appendcone variable list (length max_block)
80 
81  // SOCP runtime data (only meaningful when prob->socp != NULL)
83 
84  // 0 until the first solve has appended vars/cons/cones to the task;
85  // gates the one-shot model-building path inside solve.
87 
88  // Status / statistics
94  int mip_nodes;
95  T1 obj_val;
96 };
97 // C-REPLACE "casadi_mosek_data<T1>" "struct casadi_mosek_data"
98 
99 // SYMBOL "mosek_init_mem"
100 template<typename T1>
101 int casadi_mosek_init_mem(casadi_mosek_data<T1>* d) {
102  d->env = 0;
103  d->task = 0;
104  d->model_built = 0;
105  if (MSK_makeenv(&d->env, 0) != MSK_RES_OK) return 1;
106  if (MSK_maketask(d->env, 0, 0, &d->task) != MSK_RES_OK) return 1;
107  return 0;
108 }
109 
110 // SYMBOL "mosek_free_mem"
111 template<typename T1>
112 void casadi_mosek_free_mem(casadi_mosek_data<T1>* d) {
113  if (d->task) {
114  MSK_deletetask(&d->task);
115  d->task = 0;
116  }
117  if (d->env) {
118  MSK_deleteenv(&d->env);
119  d->env = 0;
120  }
121 }
122 
123 // SYMBOL "mosek_work"
124 template<typename T1>
125 void casadi_mosek_work(const casadi_mosek_prob<T1>* p,
126  casadi_int* sz_arg, casadi_int* sz_res, casadi_int* sz_iw, casadi_int* sz_w) {
127  casadi_qp_work(p->qp, sz_arg, sz_res, sz_iw, sz_w);
128  // int scratch (overlaid on iw slots)
129  *sz_iw += p->qp->nx; // col_idx
130  *sz_iw += p->qp->nx; // vtype
131  // double scratch
132  *sz_w += p->nquad; // qobj_val
133  *sz_w += p->qp->nx; // dual_scratch (sux retrieval)
134 
135  if (p->socp) {
136  casadi_socp_work(p->socp, sz_iw, sz_w);
137  // Cone variable index scratch, sized to the largest cone block.
138  *sz_iw += p->socp->max_block;
139  }
140 }
141 
142 // SYMBOL "mosek_set_work"
143 template<typename T1>
144 void casadi_mosek_set_work(casadi_mosek_data<T1>* d,
145  const T1*** arg, T1*** res, casadi_int** iw, T1** w) {
146  const casadi_mosek_prob<T1>* p = d->prob;
147  d->col_idx = reinterpret_cast<int*>(*iw); *iw += p->qp->nx;
148  d->vtype = reinterpret_cast<int*>(*iw); *iw += p->qp->nx;
149  d->qobj_val = *w; *w += p->nquad;
150  d->dual_scratch = *w; *w += p->qp->nx;
151  d->cone_idx = 0;
152 
153  if (p->socp) {
154  d->socp.prob = p->socp;
155  casadi_socp_init(&d->socp, iw, w);
156  d->cone_idx = reinterpret_cast<int*>(*iw); *iw += p->socp->max_block;
157  }
158 }
159 
160 
161 // C-REPLACE "SOLVER_RET_SUCCESS" "0"
162 // C-REPLACE "SOLVER_RET_LIMITED" "2"
163 // C-REPLACE "std::numeric_limits<T1>::infinity()" "casadi_inf"
164 
165 
166 // SYMBOL "mosek_solve"
167 template<typename T1>
168 int casadi_mosek_solve(casadi_mosek_data<T1>* d,
169  const double** arg, double** res, casadi_int* iw, double* w) {
170 
171  const casadi_mosek_prob<T1>* p = d->prob;
172  const casadi_qp_prob<T1>* p_qp = p->qp;
173  casadi_qp_data<T1>* d_qp = d->qp;
174 
175  int i, k, j;
176  int has_mip = p->coltype ? 1 : 0;
177  MSKtask_t task = d->task;
178  MSKrescodee r;
179 
180  // Total variable count: nx + (lifted, if SOCP)
181  int n_lifted = (p->socp) ? static_cast<int>(p->socp->n_lifted) : 0;
182  int nvar = p_qp->nx + n_lifted;
183  int ncon = p_qp->na + ((p->socp) ? static_cast<int>(p->socp->n_eq) : 0);
184 
185  if (!d->model_built) {
186  if (MSK_appendvars(task, nvar) != MSK_RES_OK) return 1;
187  if (MSK_appendcons(task, ncon) != MSK_RES_OK) return 1;
188  MSK_putobjsense(task, MSK_OBJECTIVE_SENSE_MINIMIZE);
189  }
190 
191  // Linear objective coefficient: c = g (for original variables)
192  for (j = 0; j < p_qp->nx; ++j) {
193  MSK_putcj(task, j, d_qp->g[j]);
194  }
195  // Lifted variables get c = 0 (handled by default; appendvars zero-inits).
196 
197  // Variable bounds: original variables use lbx/ubx
198  for (j = 0; j < p_qp->nx; ++j) {
199  T1 lo = d_qp->lbx[j], up = d_qp->ubx[j];
200  int lo_inf = (lo <= -std::numeric_limits<T1>::infinity());
201  int up_inf = (up >= std::numeric_limits<T1>::infinity());
202  MSKboundkeye bk;
203  if (lo_inf && up_inf) {
204  bk = MSK_BK_FR;
205  } else if (lo_inf) {
206  bk = MSK_BK_UP; lo = -std::numeric_limits<T1>::infinity();
207  } else if (up_inf) {
208  bk = MSK_BK_LO; up = std::numeric_limits<T1>::infinity();
209  } else if (lo == up) {
210  bk = MSK_BK_FX;
211  } else {
212  bk = MSK_BK_RA;
213  }
214  MSK_putvarbound(task, j, bk, lo, up);
215  }
216 
217  // Linear A: column-slice from CSC
218  for (j = 0; j < p_qp->nx; ++j) {
219  int kbeg = p->colinda[j];
220  int kend = p->colinda[j + 1];
221  if (kend > kbeg) {
222  // Allow MSK_putaijlist or per-column put. We use MSK_putacol.
223  MSK_putacol(task, j, kend - kbeg, p->rowa + kbeg, d_qp->a + kbeg);
224  }
225  }
226 
227  // Constraint bounds: [con bk][lba/uba]
228  for (i = 0; i < p_qp->na; ++i) {
229  T1 lo = d_qp->lba[i], up = d_qp->uba[i];
230  int lo_inf = (lo <= -std::numeric_limits<T1>::infinity());
231  int up_inf = (up >= std::numeric_limits<T1>::infinity());
232  MSKboundkeye bk;
233  if (lo_inf && up_inf) {
234  bk = MSK_BK_FR;
235  } else if (lo_inf) {
236  bk = MSK_BK_UP; lo = -std::numeric_limits<T1>::infinity();
237  } else if (up_inf) {
238  bk = MSK_BK_LO; up = std::numeric_limits<T1>::infinity();
239  } else if (lo == up) {
240  bk = MSK_BK_FX;
241  } else {
242  bk = MSK_BK_RA;
243  }
244  MSK_putconbound(task, i, bk, lo, up);
245  }
246 
247  // Quadratic objective: lower triangle, values as-is. Mosek interprets
248  // the qobj as 0.5 x'Qx with Q symmetric -- matches CasADi's H.
249  for (k = 0; k < p->nquad; ++k) {
250  d->qobj_val[k] = d_qp->h[p->qobj_nz_idx[k]];
251  }
252  if (p->nquad > 0) {
253  if (MSK_putqobj(task, p->nquad, p->qobj_row, p->qobj_col, d->qobj_val)
254  != MSK_RES_OK) return 1;
255  }
256 
257  // SOCP: add lifted variables, equality rows, then native cones.
258  if (p->socp) {
259  casadi_socp_data<T1>* sd = &d->socp;
260  const casadi_socp_prob<T1>* sp = sd->prob;
261  casadi_int b;
262 
263  casadi_socp_build(sd);
264 
265  // Lifted variable bounds (X free, Z >= 0).
266  for (j = 0; j < n_lifted; ++j) {
267  int jvar = p_qp->nx + j;
268  T1 lo = sd->lb_lift[j], up = sd->ub_lift[j];
269  int lo_inf = (lo <= -std::numeric_limits<T1>::infinity());
270  int up_inf = (up >= std::numeric_limits<T1>::infinity());
271  MSKboundkeye bk;
272  if (lo_inf && up_inf) {
273  bk = MSK_BK_FR;
274  } else if (lo_inf) {
275  bk = MSK_BK_UP; lo = -std::numeric_limits<T1>::infinity();
276  } else if (up_inf) {
277  bk = MSK_BK_LO; up = std::numeric_limits<T1>::infinity();
278  } else if (lo == up) {
279  bk = MSK_BK_FX;
280  } else {
281  bk = MSK_BK_RA;
282  }
283  MSK_putvarbound(task, jvar, bk, lo, up);
284  }
285 
286  // Equality rows Q*[x; lifted] = -P, placed at constraint indices na..na+n_eq-1.
287  // eq_colind[] are full-variable-space indices already (0..nx+n_lifted-1).
288  int eq_offset = p_qp->na;
289  for (i = 0; i < static_cast<int>(sp->n_eq); ++i) {
290  int kbeg = sd->eq_start[i];
291  int kend = sd->eq_start[i + 1];
292  MSK_putarow(task, eq_offset + i, kend - kbeg,
293  sd->eq_colind + kbeg, sd->eq_coef + kbeg);
294  MSK_putconbound(task, eq_offset + i, MSK_BK_FX,
295  sd->eq_rhs[i], sd->eq_rhs[i]);
296  }
297 
298  // Native quadratic cones: per cone block b, the lifted layout is
299  // [X_0, X_1, ..., X_{bs-2}, Z]. Mosek's MSK_CT_QUAD expects
300  // [t, x_0, x_1, ...] with t the cone tip. Permute Z to position 0.
301  // Cones are model structure (not data) -- append once.
302  if (!d->model_built) {
303  for (b = 0; b < sp->n_blocks; ++b) {
304  casadi_int bs = sp->r[b + 1] - sp->r[b];
305  int base = static_cast<int>(p_qp->nx + sp->r[b]);
306  d->cone_idx[0] = base + static_cast<int>(bs - 1);
307  for (j = 0; j < static_cast<int>(bs) - 1; ++j) d->cone_idx[1 + j] = base + j;
308  MSK_appendcone(task, MSK_CT_QUAD, 0.0, static_cast<int>(bs), d->cone_idx);
309  }
310  }
311  }
312 
313  // MIP integer flags (model structure, set once)
314  if (has_mip && !d->model_built) {
315  for (j = 0; j < p_qp->nx; ++j) {
316  d->vtype[j] = (p->coltype[j] == 'I')
317  ? MSK_VAR_TYPE_INT : MSK_VAR_TYPE_CONT;
318  }
319  for (j = 0; j < p_qp->nx; ++j) {
320  MSK_putvartype(task, j,
321  static_cast<MSKvariabletypee>(d->vtype[j]));
322  }
323  }
324 
325  d->model_built = 1;
326 
327  // Solve.
328  MSKrescodee trm = MSK_RES_OK;
329  r = MSK_optimizetrm(task, &trm);
330  d->return_status = static_cast<int>(r);
331  if (r != MSK_RES_OK) {
332  d_qp->success = 0;
333  return 1;
334  }
335 
336  // Pick the best available solution slice. After Mosek's IPM, basis
337  // identification fills SOL_BAS (sharper at vertices) for LP/QP without
338  // SOC; for QPs and SOCPs only SOL_ITR is meaningful; for MIP, SOL_ITG.
339  MSKsoltypee soltype;
340  if (has_mip) {
341  soltype = MSK_SOL_ITG;
342  } else {
343  MSKbooleant bas_def = 0;
344  MSK_solutiondef(task, MSK_SOL_BAS, &bas_def);
345  soltype = bas_def ? MSK_SOL_BAS : MSK_SOL_ITR;
346  }
347 
348  // Retrieve solution status
349  MSKsolstae solsta = MSK_SOL_STA_UNKNOWN;
350  MSKprostae prosta = MSK_PRO_STA_UNKNOWN;
351  MSK_getsolsta(task, soltype, &solsta);
352  MSK_getprosta(task, soltype, &prosta);
353  d->sol_status = static_cast<int>(solsta);
354  d->prob_status = static_cast<int>(prosta);
355 
356  d_qp->success = (solsta == MSK_SOL_STA_OPTIMAL ||
357  solsta == MSK_SOL_STA_INTEGER_OPTIMAL ||
358  solsta == MSK_SOL_STA_PRIM_AND_DUAL_FEAS);
360 
361  // Solution: x for original variables only (drop lifted SOCP slack vars)
362  if (d_qp->x) {
363  MSK_getxxslice(task, soltype, 0, p_qp->nx, d_qp->x);
364  }
365 
366  // Objective
367  MSK_getprimalobj(task, soltype, &d->obj_val);
368  if (d_qp->f) *d_qp->f = d->obj_val;
369 
370  // Duals
371  if (has_mip) {
372  if (d_qp->lam_x) for (i = 0; i < p_qp->nx; ++i) d_qp->lam_x[i] = 0;
373  if (d_qp->lam_a) for (i = 0; i < p_qp->na; ++i) d_qp->lam_a[i] = 0;
374  } else {
375  // Constraint duals: y = slc - suc. CasADi sign convention: lam = -y.
376  if (d_qp->lam_a && p_qp->na > 0) {
377  MSK_getyslice(task, soltype, 0, p_qp->na, d_qp->lam_a);
378  for (i = 0; i < p_qp->na; ++i) d_qp->lam_a[i] = -d_qp->lam_a[i];
379  }
380  // Variable duals: slx - sux. Use combined snx for QP; for LP fall back to slx-sux.
381  if (d_qp->lam_x && p_qp->nx > 0) {
382  // Pull slx and sux separately; lam_x = -(slx - sux) per CasADi convention.
383  MSK_getslxslice(task, soltype, 0, p_qp->nx, d_qp->lam_x);
384  MSK_getsuxslice(task, soltype, 0, p_qp->nx, d->dual_scratch);
385  for (i = 0; i < p_qp->nx; ++i)
386  d_qp->lam_x[i] = -(d_qp->lam_x[i] - d->dual_scratch[i]);
387  }
388  }
389 
390  return 0;
391 }
@ SOLVER_RET_SUCCESS
const casadi_mosek_prob< T1 > * prob
casadi_qp_data< T1 > * qp
casadi_socp_data< T1 > socp
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 T1 * h
Definition: casadi_qp.hpp:64
const T1 * lba
Definition: casadi_qp.hpp:64
const T1 * lbx
Definition: casadi_qp.hpp:64
const T1 * uba
Definition: casadi_qp.hpp:64
const T1 * ubx
Definition: casadi_qp.hpp:64
UnifiedReturnStatus unified_return_status
Definition: casadi_qp.hpp:57
const T1 * g
Definition: casadi_qp.hpp:64
const T1 * a
Definition: casadi_qp.hpp:64
casadi_int nx
Definition: casadi_qp.hpp:33
casadi_int na
Definition: casadi_qp.hpp:33
const casadi_socp_prob< T1 > * prob
const casadi_int * r
Definition: casadi_socp.hpp:58
casadi_int n_blocks
Definition: casadi_socp.hpp:54
casadi_int n_eq
Definition: casadi_socp.hpp:79