casadi_socp.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 // Reusable runtime building block that converts CasADi's Q/P input pair
21 // (SDP-style cone description, see Conic::sdp_to_socp_init) into the
22 // per-cone SOCP material that solver plugins need:
23 //
24 // * lifted helper variables [X_1; Z_1; X_2; Z_2; ...] with bounds
25 // X in (-inf, +inf), Z in [0, +inf)
26 //
27 // * a block of linear equality constraints Q*[x; lifted] = -P,
28 // materialized as CSR-style arrays (start/colind/coef + rowtype/rhs/rng)
29 //
30 // * a block of "<= 0" rows, one per cone, that the consumer turns
31 // quadratic via its native API (e.g. XPRSaddqmatrix, GRBaddqconstr).
32 // The cone's diagonal quadratic triplet (1, 1, ..., 1, -1) is
33 // filled into a per-cone scratch by casadi_socp_cone_build.
34 //
35 // Typical consumer plugin flow (in solve):
36 // d->q = arg[CONIC_Q]; d->p = arg[CONIC_P];
37 // casadi_socp_build(d);
38 // solver_add_cols(n_lifted, d->obj_lift, d->lift_start,
39 // d->lb_lift, d->ub_lift);
40 // solver_add_rows(p->n_eq, p->eq_nnz, d->eq_type, d->eq_rhs, d->eq_rng,
41 // d->eq_start, d->eq_colind, d->eq_coef);
42 // solver_add_rows(p->n_blocks, 0, d->cone_type, d->cone_rhs, d->cone_rng,
43 // d->cone_start, NULL, NULL);
44 // for (b = 0; b < p->n_blocks; ++b) {
45 // casadi_int bs = casadi_socp_cone_build(d, b);
46 // solver_attach_qmatrix(cone_row_offset + b, bs,
47 // d->qcol1, d->qcol2, d->qcoef);
48 // }
49 
50 // SYMBOL "socp_prob"
51 template<typename T1>
53  // Number of cone blocks; 0 if no SOC constraints (entire component inert).
54  casadi_int n_blocks;
55  // Block boundaries in lifted space, length n_blocks+1. Block b owns
56  // lifted indices r[b]..r[b+1]-1. The last entry of each block is the
57  // cone's "Z" scalar; preceding entries are the cone's "X" vector.
58  const casadi_int *r;
59  // Total lifted variable count = r[n_blocks]
60  casadi_int n_lifted;
61  // Number of original (un-lifted) decision variables. Used to
62  // distinguish original-variable rows from lifted-variable rows in
63  // map_Q.
64  casadi_int nx;
65  // map_Q in CSC form. Rows = (nx + n_lifted), cols = n_lifted.
66  // Each column is one equality constraint.
67  const casadi_int *mq_colind; // length n_lifted+1
68  const casadi_int *mq_row; // length eq_nnz
69  // For nz k, if mq_row[k] < nx the entry's coefficient comes from
70  // q[mq_data[k]] (an index into the user-supplied Q value array);
71  // otherwise the coefficient is -1 (the -I block).
72  const casadi_int *mq_data; // length eq_nnz
73  // map_P, length n_lifted. rhs of equality row i is
74  // -p[map_P[i]] if map_P[i] != -1
75  // 0 otherwise
76  const casadi_int *map_P;
77 
78  // Derived (filled by casadi_socp_setup)
79  casadi_int n_eq;
80  casadi_int eq_nnz;
81  casadi_int max_block;
82 };
83 // C-REPLACE "casadi_socp_prob<T1>" "struct casadi_socp_prob"
84 // C-REPLACE "reinterpret_cast<int*>" "(int*) "
85 // C-REPLACE "reinterpret_cast<char*>" "(char*) "
86 // C-REPLACE "static_cast<int>" "(int) "
87 // C-REPLACE "std::numeric_limits<T1>::infinity()" "casadi_inf"
88 
89 // SYMBOL "socp_setup"
90 template<typename T1>
91 void casadi_socp_setup(casadi_socp_prob<T1>* p) {
92  casadi_int b;
93  if (p->n_blocks == 0) {
94  p->n_eq = 0;
95  p->eq_nnz = 0;
96  p->max_block = 0;
97  return;
98  }
99  p->n_eq = p->n_lifted;
100  p->eq_nnz = p->mq_colind[p->n_eq];
101  p->max_block = 0;
102  for (b = 0; b < p->n_blocks; ++b) {
103  casadi_int bs = p->r[b + 1] - p->r[b];
104  if (bs > p->max_block) p->max_block = bs;
105  }
106 }
107 
108 // SYMBOL "socp_data"
109 template<typename T1>
112 
113  // Inputs (pointed at user-supplied Q and P value arrays). May be NULL,
114  // in which case the corresponding entries are filled with 0.
115  const T1 *q;
116  const T1 *p;
117 
118  // Workspace populated in casadi_socp_init. All sized to fit one
119  // "build" pass; callers must not retain pointers across builds.
120 
121  // Lifted-variable column data (n_lifted entries each)
122  T1 *obj_lift;
123  T1 *lb_lift;
124  T1 *ub_lift;
125  int *lift_start; // n_lifted+1, all zero (no entries in existing rows)
126 
127  // Linear equality rows (n_eq rows, eq_nnz nonzeros)
128  int *eq_start; // n_eq+1
129  int *eq_colind; // eq_nnz
130  T1 *eq_coef; // eq_nnz
131  T1 *eq_rhs; // n_eq
132  T1 *eq_rng; // n_eq, all zero
133  char *eq_type; // n_eq, all 'E'
134 
135  // Per-cone "<= 0" placeholder rows (n_blocks rows, no linear nonzeros)
136  int *cone_start; // n_blocks+1, all zero
137  T1 *cone_rhs; // n_blocks, all zero
138  T1 *cone_rng; // n_blocks, all zero
139  char *cone_type; // n_blocks, all 'L'
140 
141  // Per-cone qmatrix scratch (max_block entries each). Re-filled per
142  // cone by casadi_socp_cone_build.
143  int *qcol1;
144  int *qcol2;
145  T1 *qcoef;
146 };
147 // C-REPLACE "casadi_socp_data<T1>" "struct casadi_socp_data"
148 
149 // SYMBOL "socp_work"
150 template<typename T1>
151 void casadi_socp_work(const casadi_socp_prob<T1>* p,
152  casadi_int* sz_iw, casadi_int* sz_w) {
153  if (p->n_blocks == 0) return;
154  // doubles
155  *sz_w += 3 * p->n_lifted; // obj_lift, lb_lift, ub_lift
156  *sz_w += p->eq_nnz; // eq_coef
157  *sz_w += 2 * p->n_eq; // eq_rhs, eq_rng
158  *sz_w += 2 * p->n_blocks; // cone_rhs, cone_rng
159  *sz_w += p->max_block; // qcoef
160  // ints / chars (overlaid on iw slots)
161  *sz_iw += p->n_lifted + 1; // lift_start
162  *sz_iw += p->n_eq + 1; // eq_start
163  *sz_iw += p->eq_nnz; // eq_colind
164  *sz_iw += p->n_eq; // eq_type
165  *sz_iw += p->n_blocks + 1; // cone_start
166  *sz_iw += p->n_blocks; // cone_type
167  *sz_iw += 2 * p->max_block; // qcol1, qcol2
168 }
169 
170 // SYMBOL "socp_init"
171 template<typename T1>
172 void casadi_socp_init(casadi_socp_data<T1>* d, casadi_int** iw, T1** w) {
173  const casadi_socp_prob<T1>* p = d->prob;
174  if (p->n_blocks == 0) return;
175  d->obj_lift = *w; *w += p->n_lifted;
176  d->lb_lift = *w; *w += p->n_lifted;
177  d->ub_lift = *w; *w += p->n_lifted;
178  d->eq_coef = *w; *w += p->eq_nnz;
179  d->eq_rhs = *w; *w += p->n_eq;
180  d->eq_rng = *w; *w += p->n_eq;
181  d->cone_rhs = *w; *w += p->n_blocks;
182  d->cone_rng = *w; *w += p->n_blocks;
183  d->qcoef = *w; *w += p->max_block;
184  d->lift_start = reinterpret_cast<int*>(*iw); *iw += p->n_lifted + 1;
185  d->eq_start = reinterpret_cast<int*>(*iw); *iw += p->n_eq + 1;
186  d->eq_colind = reinterpret_cast<int*>(*iw); *iw += p->eq_nnz;
187  d->eq_type = reinterpret_cast<char*>(*iw); *iw += p->n_eq;
188  d->cone_start = reinterpret_cast<int*>(*iw); *iw += p->n_blocks + 1;
189  d->cone_type = reinterpret_cast<char*>(*iw); *iw += p->n_blocks;
190  d->qcol1 = reinterpret_cast<int*>(*iw); *iw += p->max_block;
191  d->qcol2 = reinterpret_cast<int*>(*iw); *iw += p->max_block;
192 }
193 
194 // SYMBOL "socp_build"
195 // Populate all lifted-variable + equality-row + cone-row arrays from
196 // d->q / d->p. After this returns, the consumer can hand the arrays
197 // straight to its solver's add-cols / add-rows APIs.
198 //
199 // The per-cone qmatrix scratch (d->qcol1, d->qcol2, d->qcoef) is NOT
200 // filled by this function -- call casadi_socp_cone_build per block.
201 // C-REPLACE "std::numeric_limits<T1>::infinity()" "casadi_inf"
202 template<typename T1>
203 void casadi_socp_build(casadi_socp_data<T1>* d) {
204  const casadi_socp_prob<T1>* p = d->prob;
205  casadi_int b, j, k, kk, kbeg, kend;
206  if (p->n_blocks == 0) return;
207 
208  // Lifted column metadata
209  for (j = 0; j < p->n_lifted; ++j) {
210  d->obj_lift[j] = 0;
211  d->ub_lift[j] = std::numeric_limits<T1>::infinity();
212  }
213  for (j = 0; j < p->n_lifted + 1; ++j) d->lift_start[j] = 0;
214  for (b = 0; b < p->n_blocks; ++b) {
215  casadi_int bs = p->r[b + 1] - p->r[b];
216  for (j = 0; j < bs - 1; ++j) {
217  d->lb_lift[p->r[b] + j] = -std::numeric_limits<T1>::infinity();
218  }
219  d->lb_lift[p->r[b] + bs - 1] = 0;
220  }
221 
222  // Linear equality rows
223  kk = 0;
224  for (j = 0; j < p->n_eq; ++j) {
225  d->eq_start[j] = static_cast<int>(kk);
226  d->eq_type[j] = 'E';
227  casadi_int idx = p->map_P[j];
228  d->eq_rhs[j] = (d->p && idx >= 0) ? -d->p[idx] : 0;
229  d->eq_rng[j] = 0;
230  kbeg = p->mq_colind[j];
231  kend = p->mq_colind[j + 1];
232  for (k = kbeg; k < kend; ++k) {
233  casadi_int row = p->mq_row[k];
234  d->eq_colind[kk] = static_cast<int>(row);
235  d->eq_coef[kk] = (d->q && row < p->nx) ? d->q[p->mq_data[k]] : -1;
236  kk++;
237  }
238  }
239  d->eq_start[p->n_eq] = static_cast<int>(kk);
240 
241  // Per-cone "<= 0" placeholder rows (no linear coefficients).
242  for (b = 0; b < p->n_blocks; ++b) {
243  d->cone_type[b] = 'L';
244  d->cone_rhs[b] = 0;
245  d->cone_rng[b] = 0;
246  d->cone_start[b] = 0;
247  }
248  d->cone_start[p->n_blocks] = 0;
249 }
250 
251 // SYMBOL "socp_cone_build"
252 // Fill the per-cone qmatrix scratch (d->qcol1, d->qcol2, d->qcoef) for
253 // cone block b. Returns block_size.
254 //
255 // The constraint is X_1^2 + ... + X_{bs-1}^2 - Z^2 <= 0,
256 // expressed as the diagonal quadratic [1, 1, ..., 1, -1] over the
257 // lifted variables.
258 template<typename T1>
259 casadi_int casadi_socp_cone_build(casadi_socp_data<T1>* d, casadi_int b) {
260  const casadi_socp_prob<T1>* p = d->prob;
261  casadi_int bs = p->r[b + 1] - p->r[b];
262  casadi_int j;
263  for (j = 0; j < bs; ++j) {
264  int col = static_cast<int>(p->nx + p->r[b] + j);
265  d->qcol1[j] = col;
266  d->qcol2[j] = col;
267  d->qcoef[j] = (j == bs - 1) ? -1 : 1;
268  }
269  return bs;
270 }
const casadi_socp_prob< T1 > * prob
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
casadi_int eq_nnz
Definition: casadi_socp.hpp:80
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
casadi_int n_eq
Definition: casadi_socp.hpp:79
casadi_int max_block
Definition: casadi_socp.hpp:81