xpress_runtime.hpp
1 //
2 // MIT No Attribution
3 //
4 // Copyright (C) 2010-2023 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 
31 template<typename T1>
34 
35  // Linear constraint matrix in CSC form
36  const int *colinda, *rowa;
37  // Quadratic objective in upper-triangular triplet form
38  // (Xpress expects only one triangle of the symmetric Q matrix;
39  // diagonal entries pass full value, off-diagonal entries pass
40  // half their actual value -- this is handled at solve time)
41  const int *qobj_col1, *qobj_col2;
42  // qobj_nz_idx[k] = index into the d_qp->h[] nonzero array of H_
43  // (CasADi stores the full symmetric H; we picked upper-tri entries here)
44  const int *qobj_nz_idx;
45  int nquad;
46  // Discrete variable flags ('I' for integer, 'C' for continuous), or NULL
47  const char *coltype;
48 
49  // SOS constraints (empty if no SOS). Stored in CSR-like form:
50  // sos_setstart[k]..sos_setstart[k+1]-1 indexes into sos_setind/sos_refval
51  // for the k-th SOS group. sos_settype[k] is '1' or '2'.
54  const char *sos_settype;
55  const int *sos_setstart;
56  const int *sos_setind;
57  const double *sos_refval;
58 
59  // SOCP support. Null if no SOC cones.
61 
62  // 1 = pass d->x0 as a MIP start hint via XPRSaddmipsol before solving.
63  int mip_start;
64 };
65 // C-REPLACE "casadi_xpress_prob<T1>" "struct casadi_xpress_prob"
66 
67 // SYMBOL "xpress_setup"
68 template<typename T1>
69 void casadi_xpress_setup(casadi_xpress_prob<T1>* p) {
70 
71 }
72 
73 
74 // SYMBOL "xpress_data"
75 template<typename T1>
77  // Problem structure
79  // Problem structure
81 
82  // Xpress problem handle
83  XPRSprob xprob;
84 
85  // Workspace owned by the C runtime (sized via casadi_xpress_work)
86  char *qrtype;
87  int *col_idx; // 0..nx-1, scratch for XPRSchgcoltype
88  T1 *rhs;
89  T1 *rng;
90  T1 *qobj_val;
91 
92  // SOCP runtime data (only meaningful when prob->socp != NULL)
94 
95  // Pointer to the initial guess for MIP warm start.
96  // Set by the C++ layer (XpressInterface::set_work) when mip_start is on;
97  // NULL otherwise.
98  const T1 *x0;
99 
100  // Status / statistics
108 };
109 // C-REPLACE "casadi_xpress_data<T1>" "struct casadi_xpress_data"
110 
111 // SYMBOL "xpress_init_mem"
112 template<typename T1>
113 int casadi_xpress_init_mem(casadi_xpress_data<T1>* d) {
114  d->xprob = 0;
115  d->x0 = 0;
116  if (XPRScreateprob(&d->xprob)) return 1;
117  return 0;
118 }
119 
120 // SYMBOL "xpress_free_mem"
121 template<typename T1>
122 void casadi_xpress_free_mem(casadi_xpress_data<T1>* d) {
123  if (d->xprob) {
124  XPRSdestroyprob(d->xprob);
125  d->xprob = 0;
126  }
127 }
128 
129 // SYMBOL "xpress_work"
130 template<typename T1>
131 void casadi_xpress_work(const casadi_xpress_prob<T1>* p,
132  casadi_int* sz_arg, casadi_int* sz_res, casadi_int* sz_iw, casadi_int* sz_w) {
133  casadi_qp_work(p->qp, sz_arg, sz_res, sz_iw, sz_w);
134  // Workspace for row sense, rhs, range, and quadratic-objective values
135  *sz_iw += p->qp->na; // qrtype (chars packed in iw)
136  *sz_iw += p->qp->nx; // scratch int[] for XPRSchgcoltype indices
137  *sz_w += p->qp->na; // rhs
138  *sz_w += p->qp->na; // rng
139  *sz_w += p->nquad; // qobj_val (scaled Q triplet values)
140 
141  if (p->socp) casadi_socp_work(p->socp, sz_iw, sz_w);
142 }
143 
144 // SYMBOL "xpress_set_work"
145 template<typename T1>
146 void casadi_xpress_set_work(casadi_xpress_data<T1>* d,
147  const T1*** arg, T1*** res, casadi_int** iw, T1** w) {
148  const casadi_xpress_prob<T1>* p = d->prob;
149  d->qrtype = reinterpret_cast<char*>(*iw); *iw += p->qp->na;
150  d->col_idx = reinterpret_cast<int*>(*iw); *iw += p->qp->nx;
151  d->rhs = *w; *w += p->qp->na;
152  d->rng = *w; *w += p->qp->na;
153  d->qobj_val = *w; *w += p->nquad;
154 
155  if (p->socp) {
156  d->socp.prob = p->socp;
157  casadi_socp_init(&d->socp, iw, w);
158  }
159 }
160 
161 // FILTER-MACROS OFF
162 
163 // C-REPLACE "uerr() << LEVEL \" (\" #FN \"): \" << xprs_msg << \"\\n\"" "fprintf(stderr, LEVEL \" (\" #FN \"): %s\\n\", xprs_msg)" // NOLINT(whitespace/line_length)
164 #ifndef XPRS_LOG_ERROR
165 #define XPRS_LOG_ERROR(FN, PROB, LEVEL) \
166  char xprs_msg[XPRS_MAXMESSAGELENGTH] = {0}; \
167  XPRSgetlasterror((PROB), xprs_msg); \
168  uerr() << LEVEL " (" #FN "): " << xprs_msg << "\n"
169 #endif
170 #ifndef XPRS_WARN
171 #define XPRS_WARN(FN, PROB, ...) \
172  do { \
173  if (FN((PROB), __VA_ARGS__) != 0) { \
174  XPRS_LOG_ERROR(FN, PROB, "Warning"); \
175  } \
176  } while (0)
177 #endif
178 #ifndef XPRS_RETURN
179 #define XPRS_RETURN(FN, PROB, ...) \
180  do { \
181  if (FN((PROB), __VA_ARGS__) != 0) { \
182  XPRS_LOG_ERROR(FN, PROB, "Error"); \
183  return 1; \
184  } \
185  } while (0)
186 #endif
187 // FILTER-MACROS ON
188 
189 // C-REPLACE "SOLVER_RET_SUCCESS" "0"
190 // C-REPLACE "SOLVER_RET_LIMITED" "2"
191 // C-REPLACE "SOLVER_RET_INFEASIBLE" "3"
192 // C-REPLACE "std::numeric_limits<T1>::infinity()" "casadi_inf"
193 // C-REPLACE "std::numeric_limits<T1>::quiet_NaN()" "casadi_nan"
194 
195 // SYMBOL "xpress_solve"
196 template<typename T1>
197 int casadi_xpress_solve(casadi_xpress_data<T1>* d,
198  const double** arg, double** res, casadi_int* iw, double* w) {
199 
200  const casadi_xpress_prob<T1>* p = d->prob;
201  const casadi_qp_prob<T1>* p_qp = p->qp;
202  casadi_qp_data<T1>* d_qp = d->qp;
203 
204  int i, k;
205  int has_mip = p->coltype ? 1 : 0;
206 
207  // Build per-row sense/rhs/range from (lba, uba)
208  for (i = 0; i < p_qp->na; ++i) {
209  T1 lo = d_qp->lba[i];
210  T1 up = d_qp->uba[i];
211  int lo_inf = (lo <= -std::numeric_limits<T1>::infinity());
212  int up_inf = (up >= std::numeric_limits<T1>::infinity());
213  if (!lo_inf && !up_inf) {
214  if (lo == up) {
215  d->qrtype[i] = 'E'; d->rhs[i] = up; d->rng[i] = 0;
216  } else {
217  d->qrtype[i] = 'R'; d->rhs[i] = up; d->rng[i] = up - lo;
218  }
219  } else if (!up_inf) {
220  d->qrtype[i] = 'L'; d->rhs[i] = up; d->rng[i] = 0;
221  } else if (!lo_inf) {
222  d->qrtype[i] = 'G'; d->rhs[i] = lo; d->rng[i] = 0;
223  } else {
224  // Free row: model as <= +inf
225  d->qrtype[i] = 'L'; d->rhs[i] = std::numeric_limits<T1>::infinity(); d->rng[i] = 0;
226  }
227  }
228 
229  // CasADi: f = g'x + 0.5 x'Hx with H symmetric. Xpress: 0.5 x'Qx; pass
230  // ONE triangle of Q only (Xpress auto-symmetrizes). We pass H[i,j]
231  // unmodified for both diagonal and off-diagonal entries.
232  for (k = 0; k < p->nquad; ++k) {
233  d->qobj_val[k] = d_qp->h[p->qobj_nz_idx[k]];
234  }
235 
236  // Load the (Q)P into Xpress
237  XPRS_RETURN(XPRSloadqp, d->xprob, "casadi_qp",
238  p_qp->nx, p_qp->na,
239  d->qrtype, d->rhs, d->rng,
240  d_qp->g,
241  p->colinda, 0, p->rowa, d_qp->a,
242  d_qp->lbx, d_qp->ubx,
243  p->nquad, p->qobj_col1, p->qobj_col2, d->qobj_val);
244 
245  // SOCP: lift the model with helper variables, link them to the
246  // original variables via Q*[x; lifted] = -P, and add a "<= 0"
247  // row per cone that we make quadratic via XPRSaddqmatrix.
248  if (p->socp) {
249  casadi_socp_data<T1>* sd = &d->socp;
250  const casadi_socp_prob<T1>* sp = sd->prob;
251  casadi_int b;
252  int n_rows_after_eq;
253 
254  casadi_socp_build(sd);
255 
256  XPRS_RETURN(XPRSaddcols, d->xprob, static_cast<int>(sp->n_lifted), 0,
257  sd->obj_lift, sd->lift_start, 0, 0,
258  sd->lb_lift, sd->ub_lift);
259 
260  XPRS_RETURN(XPRSaddrows, d->xprob, static_cast<int>(sp->n_eq),
261  static_cast<int>(sp->eq_nnz),
262  sd->eq_type, sd->eq_rhs, sd->eq_rng,
263  sd->eq_start, sd->eq_colind, sd->eq_coef);
264 
265  XPRS_RETURN(XPRSgetintattrib, d->xprob, XPRS_ROWS, &n_rows_after_eq);
266 
267  XPRS_RETURN(XPRSaddrows, d->xprob, static_cast<int>(sp->n_blocks), 0,
268  sd->cone_type, sd->cone_rhs, sd->cone_rng,
269  sd->cone_start, 0, 0);
270 
271  for (b = 0; b < sp->n_blocks; ++b) {
272  casadi_int bs = casadi_socp_cone_build(sd, b);
273  XPRS_RETURN(XPRSaddqmatrix, d->xprob, n_rows_after_eq + static_cast<int>(b),
274  static_cast<int>(bs), sd->qcol1, sd->qcol2, sd->qcoef);
275  }
276  }
277 
278  if (has_mip) {
279  // Mark column types -- pass the index list and types in lockstep so
280  // coltype[k] applies to colind[k] (a subtle XPRSchgcoltype API contract).
281  for (i = 0; i < p_qp->nx; ++i) d->col_idx[i] = i;
282  XPRS_RETURN(XPRSchgcoltype, d->xprob, p_qp->nx, d->col_idx, p->coltype);
283  if (p->n_sos_sets > 0) {
284  XPRS_RETURN(XPRSaddsets, d->xprob, p->n_sos_sets, p->n_sos_elems,
286  }
287  if (p->mip_start && d->x0) {
288  // Warm start is non-fatal: the solve continues even if the hint is rejected.
289  XPRS_WARN(XPRSaddmipsol, d->xprob, p_qp->nx, d->x0, 0, 0);
290  }
291  XPRS_RETURN(XPRSmipoptimize, d->xprob, "");
292  } else {
293  XPRS_RETURN(XPRSlpoptimize, d->xprob, "");
294  }
295 
296  // Retrieve status
297  if (has_mip) {
298  XPRS_WARN(XPRSgetintattrib, d->xprob, XPRS_MIPSTATUS, &d->mip_status);
299  XPRS_WARN(XPRSgetdblattrib, d->xprob, XPRS_MIPOBJVAL, &d->obj_val);
300  d->return_status = d->mip_status;
301  d_qp->success = (d->mip_status == XPRS_MIP_OPTIMAL ||
302  d->mip_status == XPRS_MIP_SOLUTION);
303  if (d->mip_status == XPRS_MIP_OPTIMAL)
305  else if (d->mip_status == XPRS_MIP_SOLUTION)
307  else if (d->mip_status == XPRS_MIP_INFEAS)
309  } else {
310  XPRS_WARN(XPRSgetintattrib, d->xprob, XPRS_LPSTATUS, &d->lp_status);
311  XPRS_WARN(XPRSgetdblattrib, d->xprob, XPRS_LPOBJVAL, &d->obj_val);
312  d->return_status = d->lp_status;
313  d_qp->success = (d->lp_status == XPRS_LP_OPTIMAL);
314  if (d->lp_status == XPRS_LP_OPTIMAL)
316  else if (d->lp_status == XPRS_LP_UNFINISHED ||
317  d->lp_status == XPRS_LP_CUTOFF ||
318  d->lp_status == XPRS_LP_CUTOFF_IN_DUAL)
320  else if (d->lp_status == XPRS_LP_INFEAS)
322  // Postsolve is a no-op when the solve completed normally, and mandatory
323  // after any stopped/cutoff status; call it unconditionally to cover all
324  // cases (LP_UNFINISHED, LP_CUTOFF, LP_CUTOFF_IN_DUAL, etc.).
325  // XPRS_RETURN not usable here: it needs at least one extra macro argument.
326  if (XPRSpostsolve(d->xprob) != 0) {
327  XPRS_LOG_ERROR(XPRSpostsolve, d->xprob, "Error");
328  return 1;
329  }
330  }
331 
332  // Retrieve solution. XPRSgetsolution works for both LP and MIP.
333  // Duals/reduced costs are only meaningful for the LP case.
334  {
335  int sol_status = XPRS_SOLAVAILABLE_NOTFOUND;
336  XPRS_WARN(XPRSgetsolution, d->xprob, &sol_status, d_qp->x, 0, p_qp->nx - 1);
337  if (sol_status == XPRS_SOLAVAILABLE_NOTFOUND) {
338  fprintf(stderr, "Warning (XPRSgetsolution): no primal solution available.\n");
339  casadi_fill(d_qp->x, p_qp->nx, std::numeric_limits<T1>::quiet_NaN());
340  }
341  }
342 
343  if (has_mip) {
344  // Dual variables are not computed for MIP; signal unavailability with NaN.
345  casadi_fill(d_qp->lam_x, p_qp->nx, std::numeric_limits<T1>::quiet_NaN());
346  casadi_fill(d_qp->lam_a, p_qp->na, std::numeric_limits<T1>::quiet_NaN());
347  } else {
348  // CasADi sign convention: lam = -dual returned by Xpress
349  if (d_qp->lam_a && p_qp->na > 0) {
350  int dual_status = XPRS_SOLAVAILABLE_NOTFOUND;
351  XPRS_WARN(XPRSgetduals, d->xprob, &dual_status, d_qp->lam_a, 0, p_qp->na - 1);
352  if (dual_status == XPRS_SOLAVAILABLE_NOTFOUND) {
353  fprintf(stderr, "Warning (XPRSgetduals): no dual solution available.\n");
354  casadi_fill(d_qp->lam_a, p_qp->na, std::numeric_limits<T1>::quiet_NaN());
355  } else {
356  for (i = 0; i < p_qp->na; ++i) d_qp->lam_a[i] = -d_qp->lam_a[i];
357  }
358  }
359  if (d_qp->lam_x && p_qp->nx > 0) {
360  int rc_status = XPRS_SOLAVAILABLE_NOTFOUND;
361  XPRS_WARN(XPRSgetredcosts, d->xprob, &rc_status, d_qp->lam_x, 0, p_qp->nx - 1);
362  if (rc_status == XPRS_SOLAVAILABLE_NOTFOUND) {
363  fprintf(stderr, "Warning (XPRSgetredcosts): no reduced costs available.\n");
364  casadi_fill(d_qp->lam_x, p_qp->nx, std::numeric_limits<T1>::quiet_NaN());
365  } else {
366  for (i = 0; i < p_qp->nx; ++i) d_qp->lam_x[i] = -d_qp->lam_x[i];
367  }
368  }
369  }
370 
371  if (d_qp->f) *d_qp->f = d->obj_val;
372 
373  XPRS_WARN(XPRSgetintattrib, d->xprob, XPRS_SIMPLEXITER, &d->simplex_iter);
374  XPRS_WARN(XPRSgetintattrib, d->xprob, XPRS_BARITER, &d->barrier_iter);
375  XPRS_WARN(XPRSgetintattrib, d->xprob, XPRS_NODES, &d->mip_nodes);
376 
377  return 0;
378 }
@ SOLVER_RET_INFEASIBLE
@ SOLVER_RET_LIMITED
@ SOLVER_RET_SUCCESS
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
casadi_int n_lifted
Definition: casadi_socp.hpp:60
casadi_int n_blocks
Definition: casadi_socp.hpp:54
casadi_int eq_nnz
Definition: casadi_socp.hpp:80
casadi_int n_eq
Definition: casadi_socp.hpp:79
casadi_qp_data< T1 > * qp
casadi_socp_data< T1 > socp
const casadi_xpress_prob< T1 > * prob
const double * sos_refval
const int * sos_setstart
const int * qobj_nz_idx
const char * sos_settype
const int * sos_setind
const casadi_qp_prob< T1 > * qp
const char * coltype
const casadi_socp_prob< T1 > * socp