blob: 89c7de72dcf7d62ed0a0083b834d50c280c36619 [file] [log] [blame]
Austin Schuh9049e202022-02-20 17:34:16 -08001#include "osqp.h" // For OSQP rho update
2#include "auxil.h"
3#include "proj.h"
4#include "lin_alg.h"
5#include "constants.h"
6#include "scaling.h"
7#include "util.h"
8
9/***********************************************************
10* Auxiliary functions needed to compute ADMM iterations * *
11***********************************************************/
12#if EMBEDDED != 1
13c_float compute_rho_estimate(OSQPWorkspace *work) {
14 c_int n, m; // Dimensions
15 c_float pri_res, dua_res; // Primal and dual residuals
16 c_float pri_res_norm, dua_res_norm; // Normalization for the residuals
17 c_float temp_res_norm; // Temporary residual norm
18 c_float rho_estimate; // Rho estimate value
19
20 // Get problem dimensions
21 n = work->data->n;
22 m = work->data->m;
23
24 // Get primal and dual residuals
25 pri_res = vec_norm_inf(work->z_prev, m);
26 dua_res = vec_norm_inf(work->x_prev, n);
27
28 // Normalize primal residual
29 pri_res_norm = vec_norm_inf(work->z, m); // ||z||
30 temp_res_norm = vec_norm_inf(work->Ax, m); // ||Ax||
31 pri_res_norm = c_max(pri_res_norm, temp_res_norm); // max (||z||,||Ax||)
32 pri_res /= (pri_res_norm + OSQP_DIVISION_TOL); // Normalize primal
33 // residual (prevent 0
34 // division)
35
36 // Normalize dual residual
37 dua_res_norm = vec_norm_inf(work->data->q, n); // ||q||
38 temp_res_norm = vec_norm_inf(work->Aty, n); // ||A' y||
39 dua_res_norm = c_max(dua_res_norm, temp_res_norm);
40 temp_res_norm = vec_norm_inf(work->Px, n); // ||P x||
41 dua_res_norm = c_max(dua_res_norm, temp_res_norm); // max(||q||,||A' y||,||P
42 // x||)
43 dua_res /= (dua_res_norm + OSQP_DIVISION_TOL); // Normalize dual residual
44 // (prevent 0 division)
45
46
47 // Return rho estimate
48 rho_estimate = work->settings->rho * c_sqrt(pri_res / dua_res);
49 rho_estimate = c_min(c_max(rho_estimate, RHO_MIN), RHO_MAX); // Constrain
50 // rho values
51 return rho_estimate;
52}
53
54c_int adapt_rho(OSQPWorkspace *work) {
55 c_int exitflag; // Exitflag
56 c_float rho_new; // New rho value
57
58 exitflag = 0; // Initialize exitflag to 0
59
60 // Compute new rho
61 rho_new = compute_rho_estimate(work);
62
63 // Set rho estimate in info
64 work->info->rho_estimate = rho_new;
65
66 // Check if the new rho is large or small enough and update it in case
67 if ((rho_new > work->settings->rho * work->settings->adaptive_rho_tolerance) ||
68 (rho_new < work->settings->rho / work->settings->adaptive_rho_tolerance)) {
69 exitflag = osqp_update_rho(work, rho_new);
70 work->info->rho_updates += 1;
71 }
72
73 return exitflag;
74}
75
76void set_rho_vec(OSQPWorkspace *work) {
77 c_int i;
78
79 work->settings->rho = c_min(c_max(work->settings->rho, RHO_MIN), RHO_MAX);
80
81 for (i = 0; i < work->data->m; i++) {
82 if ((work->data->l[i] < -OSQP_INFTY * MIN_SCALING) &&
83 (work->data->u[i] > OSQP_INFTY * MIN_SCALING)) {
84 // Loose bounds
85 work->constr_type[i] = -1;
86 work->rho_vec[i] = RHO_MIN;
87 } else if (work->data->u[i] - work->data->l[i] < RHO_TOL) {
88 // Equality constraints
89 work->constr_type[i] = 1;
90 work->rho_vec[i] = RHO_EQ_OVER_RHO_INEQ * work->settings->rho;
91 } else {
92 // Inequality constraints
93 work->constr_type[i] = 0;
94 work->rho_vec[i] = work->settings->rho;
95 }
96 work->rho_inv_vec[i] = 1. / work->rho_vec[i];
97 }
98}
99
100c_int update_rho_vec(OSQPWorkspace *work) {
101 c_int i, exitflag, constr_type_changed;
102
103 exitflag = 0;
104 constr_type_changed = 0;
105
106 for (i = 0; i < work->data->m; i++) {
107 if ((work->data->l[i] < -OSQP_INFTY * MIN_SCALING) &&
108 (work->data->u[i] > OSQP_INFTY * MIN_SCALING)) {
109 // Loose bounds
110 if (work->constr_type[i] != -1) {
111 work->constr_type[i] = -1;
112 work->rho_vec[i] = RHO_MIN;
113 work->rho_inv_vec[i] = 1. / RHO_MIN;
114 constr_type_changed = 1;
115 }
116 } else if (work->data->u[i] - work->data->l[i] < RHO_TOL) {
117 // Equality constraints
118 if (work->constr_type[i] != 1) {
119 work->constr_type[i] = 1;
120 work->rho_vec[i] = RHO_EQ_OVER_RHO_INEQ * work->settings->rho;
121 work->rho_inv_vec[i] = 1. / work->rho_vec[i];
122 constr_type_changed = 1;
123 }
124 } else {
125 // Inequality constraints
126 if (work->constr_type[i] != 0) {
127 work->constr_type[i] = 0;
128 work->rho_vec[i] = work->settings->rho;
129 work->rho_inv_vec[i] = 1. / work->settings->rho;
130 constr_type_changed = 1;
131 }
132 }
133 }
134
135 // Update rho_vec in KKT matrix if constraints type has changed
136 if (constr_type_changed == 1) {
137 exitflag = work->linsys_solver->update_rho_vec(work->linsys_solver,
138 work->rho_vec);
139 }
140
141 return exitflag;
142}
143
144#endif // EMBEDDED != 1
145
146
147void swap_vectors(c_float **a, c_float **b) {
148 c_float *temp;
149
150 temp = *b;
151 *b = *a;
152 *a = temp;
153}
154
155void cold_start(OSQPWorkspace *work) {
156 vec_set_scalar(work->x, 0., work->data->n);
157 vec_set_scalar(work->z, 0., work->data->m);
158 vec_set_scalar(work->y, 0., work->data->m);
159}
160
161static void compute_rhs(OSQPWorkspace *work) {
162 c_int i; // Index
163
164 for (i = 0; i < work->data->n; i++) {
165 // Cycle over part related to x variables
166 work->xz_tilde[i] = work->settings->sigma * work->x_prev[i] -
167 work->data->q[i];
168 }
169
170 for (i = 0; i < work->data->m; i++) {
171 // Cycle over dual variable in the first step (nu)
172 work->xz_tilde[i + work->data->n] = work->z_prev[i] - work->rho_inv_vec[i] *
173 work->y[i];
174 }
175}
176
177void update_xz_tilde(OSQPWorkspace *work) {
178 // Compute right-hand side
179 compute_rhs(work);
180
181 // Solve linear system
182 work->linsys_solver->solve(work->linsys_solver, work->xz_tilde);
183}
184
185void update_x(OSQPWorkspace *work) {
186 c_int i;
187
188 // update x
189 for (i = 0; i < work->data->n; i++) {
190 work->x[i] = work->settings->alpha * work->xz_tilde[i] +
191 ((c_float)1.0 - work->settings->alpha) * work->x_prev[i];
192 }
193
194 // update delta_x
195 for (i = 0; i < work->data->n; i++) {
196 work->delta_x[i] = work->x[i] - work->x_prev[i];
197 }
198}
199
200void update_z(OSQPWorkspace *work) {
201 c_int i;
202
203 // update z
204 for (i = 0; i < work->data->m; i++) {
205 work->z[i] = work->settings->alpha * work->xz_tilde[i + work->data->n] +
206 ((c_float)1.0 - work->settings->alpha) * work->z_prev[i] +
207 work->rho_inv_vec[i] * work->y[i];
208 }
209
210 // project z
211 project(work, work->z);
212}
213
214void update_y(OSQPWorkspace *work) {
215 c_int i; // Index
216
217 for (i = 0; i < work->data->m; i++) {
218 work->delta_y[i] = work->rho_vec[i] *
219 (work->settings->alpha *
220 work->xz_tilde[i + work->data->n] +
221 ((c_float)1.0 - work->settings->alpha) * work->z_prev[i] -
222 work->z[i]);
223 work->y[i] += work->delta_y[i];
224 }
225}
226
227c_float compute_obj_val(OSQPWorkspace *work, c_float *x) {
228 c_float obj_val;
229
230 obj_val = quad_form(work->data->P, x) +
231 vec_prod(work->data->q, x, work->data->n);
232
233 if (work->settings->scaling) {
234 obj_val *= work->scaling->cinv;
235 }
236
237 return obj_val;
238}
239
240c_float compute_pri_res(OSQPWorkspace *work, c_float *x, c_float *z) {
241 // NB: Use z_prev as working vector
242 // pr = Ax - z
243
244 mat_vec(work->data->A, x, work->Ax, 0); // Ax
245 vec_add_scaled(work->z_prev, work->Ax, z, work->data->m, -1);
246
247 // If scaling active -> rescale residual
248 if (work->settings->scaling && !work->settings->scaled_termination) {
249 return vec_scaled_norm_inf(work->scaling->Einv, work->z_prev, work->data->m);
250 }
251
252 // Return norm of the residual
253 return vec_norm_inf(work->z_prev, work->data->m);
254}
255
256c_float compute_pri_tol(OSQPWorkspace *work, c_float eps_abs, c_float eps_rel) {
257 c_float max_rel_eps, temp_rel_eps;
258
259 // max_rel_eps = max(||z||, ||A x||)
260 if (work->settings->scaling && !work->settings->scaled_termination) {
261 // ||Einv * z||
262 max_rel_eps =
263 vec_scaled_norm_inf(work->scaling->Einv, work->z, work->data->m);
264
265 // ||Einv * A * x||
266 temp_rel_eps = vec_scaled_norm_inf(work->scaling->Einv,
267 work->Ax,
268 work->data->m);
269
270 // Choose maximum
271 max_rel_eps = c_max(max_rel_eps, temp_rel_eps);
272 } else { // No unscaling required
273 // ||z||
274 max_rel_eps = vec_norm_inf(work->z, work->data->m);
275
276 // ||A * x||
277 temp_rel_eps = vec_norm_inf(work->Ax, work->data->m);
278
279 // Choose maximum
280 max_rel_eps = c_max(max_rel_eps, temp_rel_eps);
281 }
282
283 // eps_prim
284 return eps_abs + eps_rel * max_rel_eps;
285}
286
287c_float compute_dua_res(OSQPWorkspace *work, c_float *x, c_float *y) {
288 // NB: Use x_prev as temporary vector
289 // NB: Only upper triangular part of P is stored.
290 // dr = q + A'*y + P*x
291
292 // dr = q
293 prea_vec_copy(work->data->q, work->x_prev, work->data->n);
294
295 // P * x (upper triangular part)
296 mat_vec(work->data->P, x, work->Px, 0);
297
298 // P' * x (lower triangular part with no diagonal)
299 mat_tpose_vec(work->data->P, x, work->Px, 1, 1);
300
301 // dr += P * x (full P matrix)
302 vec_add_scaled(work->x_prev, work->x_prev, work->Px, work->data->n, 1);
303
304 // dr += A' * y
305 if (work->data->m > 0) {
306 mat_tpose_vec(work->data->A, y, work->Aty, 0, 0);
307 vec_add_scaled(work->x_prev, work->x_prev, work->Aty, work->data->n, 1);
308 }
309
310 // If scaling active -> rescale residual
311 if (work->settings->scaling && !work->settings->scaled_termination) {
312 return work->scaling->cinv * vec_scaled_norm_inf(work->scaling->Dinv,
313 work->x_prev,
314 work->data->n);
315 }
316
317 return vec_norm_inf(work->x_prev, work->data->n);
318}
319
320c_float compute_dua_tol(OSQPWorkspace *work, c_float eps_abs, c_float eps_rel) {
321 c_float max_rel_eps, temp_rel_eps;
322
323 // max_rel_eps = max(||q||, ||A' y|, ||P x||)
324 if (work->settings->scaling && !work->settings->scaled_termination) {
325 // || Dinv q||
326 max_rel_eps = vec_scaled_norm_inf(work->scaling->Dinv,
327 work->data->q,
328 work->data->n);
329
330 // || Dinv A' y ||
331 temp_rel_eps = vec_scaled_norm_inf(work->scaling->Dinv,
332 work->Aty,
333 work->data->n);
334 max_rel_eps = c_max(max_rel_eps, temp_rel_eps);
335
336 // || Dinv P x||
337 temp_rel_eps = vec_scaled_norm_inf(work->scaling->Dinv,
338 work->Px,
339 work->data->n);
340 max_rel_eps = c_max(max_rel_eps, temp_rel_eps);
341
342 // Multiply by cinv
343 max_rel_eps *= work->scaling->cinv;
344 } else { // No scaling required
345 // ||q||
346 max_rel_eps = vec_norm_inf(work->data->q, work->data->n);
347
348 // ||A'*y||
349 temp_rel_eps = vec_norm_inf(work->Aty, work->data->n);
350 max_rel_eps = c_max(max_rel_eps, temp_rel_eps);
351
352 // ||P*x||
353 temp_rel_eps = vec_norm_inf(work->Px, work->data->n);
354 max_rel_eps = c_max(max_rel_eps, temp_rel_eps);
355 }
356
357 // eps_dual
358 return eps_abs + eps_rel * max_rel_eps;
359}
360
361c_int is_primal_infeasible(OSQPWorkspace *work, c_float eps_prim_inf) {
362 // This function checks for the primal infeasibility termination criteria.
363 //
364 // 1) A' * delta_y < eps * ||delta_y||
365 //
366 // 2) u'*max(delta_y, 0) + l'*min(delta_y, 0) < eps * ||delta_y||
367 //
368
369 c_int i; // Index for loops
370 c_float norm_delta_y;
371 c_float ineq_lhs = 0.0;
372
373 // Project delta_y onto the polar of the recession cone of [l,u]
374 for (i = 0; i < work->data->m; i++) {
375 if (work->data->u[i] > OSQP_INFTY * MIN_SCALING) { // Infinite upper bound
376 if (work->data->l[i] < -OSQP_INFTY * MIN_SCALING) { // Infinite lower bound
377 // Both bounds infinite
378 work->delta_y[i] = 0.0;
379 } else {
380 // Only upper bound infinite
381 work->delta_y[i] = c_min(work->delta_y[i], 0.0);
382 }
383 } else if (work->data->l[i] < -OSQP_INFTY * MIN_SCALING) { // Infinite lower bound
384 // Only lower bound infinite
385 work->delta_y[i] = c_max(work->delta_y[i], 0.0);
386 }
387 }
388
389 // Compute infinity norm of delta_y (unscale if necessary)
390 if (work->settings->scaling && !work->settings->scaled_termination) {
391 // Use work->Adelta_x as temporary vector
392 vec_ew_prod(work->scaling->E, work->delta_y, work->Adelta_x, work->data->m);
393 norm_delta_y = vec_norm_inf(work->Adelta_x, work->data->m);
394 } else {
395 norm_delta_y = vec_norm_inf(work->delta_y, work->data->m);
396 }
397
398 if (norm_delta_y > OSQP_DIVISION_TOL) {
399
400 for (i = 0; i < work->data->m; i++) {
401 ineq_lhs += work->data->u[i] * c_max(work->delta_y[i], 0) + \
402 work->data->l[i] * c_min(work->delta_y[i], 0);
403 }
404
405 // Check if the condition is satisfied: ineq_lhs < -eps
406 if (ineq_lhs < eps_prim_inf * norm_delta_y) {
407 // Compute and return ||A'delta_y|| < eps_prim_inf
408 mat_tpose_vec(work->data->A, work->delta_y, work->Atdelta_y, 0, 0);
409
410 // Unscale if necessary
411 if (work->settings->scaling && !work->settings->scaled_termination) {
412 vec_ew_prod(work->scaling->Dinv,
413 work->Atdelta_y,
414 work->Atdelta_y,
415 work->data->n);
416 }
417
418 return vec_norm_inf(work->Atdelta_y, work->data->n) < eps_prim_inf * norm_delta_y;
419 }
420 }
421
422 // Conditions not satisfied -> not primal infeasible
423 return 0;
424}
425
426c_int is_dual_infeasible(OSQPWorkspace *work, c_float eps_dual_inf) {
427 // This function checks for the scaled dual infeasibility termination
428 // criteria.
429 //
430 // 1) q * delta_x < eps * || delta_x ||
431 //
432 // 2) ||P * delta_x || < eps * || delta_x ||
433 //
434 // 3) -> (A * delta_x)_i > -eps * || delta_x ||, l_i != -inf
435 // -> (A * delta_x)_i < eps * || delta_x ||, u_i != inf
436 //
437
438
439 c_int i; // Index for loops
440 c_float norm_delta_x;
441 c_float cost_scaling;
442
443 // Compute norm of delta_x
444 if (work->settings->scaling && !work->settings->scaled_termination) { // Unscale
445 // if
446 // necessary
447 norm_delta_x = vec_scaled_norm_inf(work->scaling->D,
448 work->delta_x,
449 work->data->n);
450 cost_scaling = work->scaling->c;
451 } else {
452 norm_delta_x = vec_norm_inf(work->delta_x, work->data->n);
453 cost_scaling = 1.0;
454 }
455
456 // Prevent 0 division || delta_x || > 0
457 if (norm_delta_x > OSQP_DIVISION_TOL) {
458 // Normalize delta_x by its norm
459
460 /* vec_mult_scalar(work->delta_x, 1./norm_delta_x, work->data->n); */
461
462 // Check first if q'*delta_x < 0
463 if (vec_prod(work->data->q, work->delta_x, work->data->n) <
464 cost_scaling * eps_dual_inf * norm_delta_x) {
465 // Compute product P * delta_x (NB: P is store in upper triangular form)
466 mat_vec(work->data->P, work->delta_x, work->Pdelta_x, 0);
467 mat_tpose_vec(work->data->P, work->delta_x, work->Pdelta_x, 1, 1);
468
469 // Scale if necessary
470 if (work->settings->scaling && !work->settings->scaled_termination) {
471 vec_ew_prod(work->scaling->Dinv,
472 work->Pdelta_x,
473 work->Pdelta_x,
474 work->data->n);
475 }
476
477 // Check if || P * delta_x || = 0
478 if (vec_norm_inf(work->Pdelta_x, work->data->n) <
479 cost_scaling * eps_dual_inf * norm_delta_x) {
480 // Compute A * delta_x
481 mat_vec(work->data->A, work->delta_x, work->Adelta_x, 0);
482
483 // Scale if necessary
484 if (work->settings->scaling && !work->settings->scaled_termination) {
485 vec_ew_prod(work->scaling->Einv,
486 work->Adelta_x,
487 work->Adelta_x,
488 work->data->m);
489 }
490
491 // De Morgan Law Applied to dual infeasibility conditions for A * x
492 // NB: Note that MIN_SCALING is used to adjust the infinity value
493 // in case the problem is scaled.
494 for (i = 0; i < work->data->m; i++) {
495 if (((work->data->u[i] < OSQP_INFTY * MIN_SCALING) &&
496 (work->Adelta_x[i] > eps_dual_inf * norm_delta_x)) ||
497 ((work->data->l[i] > -OSQP_INFTY * MIN_SCALING) &&
498 (work->Adelta_x[i] < -eps_dual_inf * norm_delta_x))) {
499 // At least one condition not satisfied -> not dual infeasible
500 return 0;
501 }
502 }
503
504 // All conditions passed -> dual infeasible
505 return 1;
506 }
507 }
508 }
509
510 // Conditions not satisfied -> not dual infeasible
511 return 0;
512}
513
514c_int has_solution(OSQPInfo * info){
515
516 return ((info->status_val != OSQP_PRIMAL_INFEASIBLE) &&
517 (info->status_val != OSQP_PRIMAL_INFEASIBLE_INACCURATE) &&
518 (info->status_val != OSQP_DUAL_INFEASIBLE) &&
519 (info->status_val != OSQP_DUAL_INFEASIBLE_INACCURATE) &&
520 (info->status_val != OSQP_NON_CVX));
521
522}
523
524void store_solution(OSQPWorkspace *work) {
525#ifndef EMBEDDED
526 c_float norm_vec;
527#endif /* ifndef EMBEDDED */
528
529 if (has_solution(work->info)) {
530 prea_vec_copy(work->x, work->solution->x, work->data->n); // primal
531 prea_vec_copy(work->y, work->solution->y, work->data->m); // dual
532
533 // Unscale solution if scaling has been performed
534 if (work->settings->scaling)
535 unscale_solution(work);
536 } else {
537 // No solution present. Solution is NaN
538 vec_set_scalar(work->solution->x, OSQP_NAN, work->data->n);
539 vec_set_scalar(work->solution->y, OSQP_NAN, work->data->m);
540
541#ifndef EMBEDDED
542
543 // Normalize infeasibility certificates if embedded is off
544 // NB: It requires a division
545 if ((work->info->status_val == OSQP_PRIMAL_INFEASIBLE) ||
546 ((work->info->status_val == OSQP_PRIMAL_INFEASIBLE_INACCURATE))) {
547 norm_vec = vec_norm_inf(work->delta_y, work->data->m);
548 vec_mult_scalar(work->delta_y, 1. / norm_vec, work->data->m);
549 }
550
551 if ((work->info->status_val == OSQP_DUAL_INFEASIBLE) ||
552 ((work->info->status_val == OSQP_DUAL_INFEASIBLE_INACCURATE))) {
553 norm_vec = vec_norm_inf(work->delta_x, work->data->n);
554 vec_mult_scalar(work->delta_x, 1. / norm_vec, work->data->n);
555 }
556
557#endif /* ifndef EMBEDDED */
558
559 // Cold start iterates to 0 for next runs (they cannot start from NaN)
560 cold_start(work);
561 }
562}
563
564void update_info(OSQPWorkspace *work,
565 c_int iter,
566 c_int compute_objective,
567 c_int polish) {
568 c_float *x, *z, *y; // Allocate pointers to variables
569 c_float *obj_val, *pri_res, *dua_res; // objective value, residuals
570
571#ifdef PROFILING
572 c_float *run_time; // Execution time
573#endif /* ifdef PROFILING */
574
575#ifndef EMBEDDED
576
577 if (polish) {
578 x = work->pol->x;
579 y = work->pol->y;
580 z = work->pol->z;
581 obj_val = &work->pol->obj_val;
582 pri_res = &work->pol->pri_res;
583 dua_res = &work->pol->dua_res;
584# ifdef PROFILING
585 run_time = &work->info->polish_time;
586# endif /* ifdef PROFILING */
587 } else {
588#endif // EMBEDDED
589 x = work->x;
590 y = work->y;
591 z = work->z;
592 obj_val = &work->info->obj_val;
593 pri_res = &work->info->pri_res;
594 dua_res = &work->info->dua_res;
595 work->info->iter = iter; // Update iteration number
596#ifdef PROFILING
597 run_time = &work->info->solve_time;
598#endif /* ifdef PROFILING */
599#ifndef EMBEDDED
600}
601
602#endif /* ifndef EMBEDDED */
603
604
605 // Compute the objective if needed
606 if (compute_objective) {
607 *obj_val = compute_obj_val(work, x);
608 }
609
610 // Compute primal residual
611 if (work->data->m == 0) {
612 // No constraints -> Always primal feasible
613 *pri_res = 0.;
614 } else {
615 *pri_res = compute_pri_res(work, x, z);
616 }
617
618 // Compute dual residual
619 *dua_res = compute_dua_res(work, x, y);
620
621 // Update timing
622#ifdef PROFILING
623 *run_time = osqp_toc(work->timer);
624#endif /* ifdef PROFILING */
625
626#ifdef PRINTING
627 work->summary_printed = 0; // The just updated info have not been printed
628#endif /* ifdef PRINTING */
629}
630
631
632void reset_info(OSQPInfo *info) {
633#ifdef PROFILING
634
635 // Initialize info values.
636 info->solve_time = 0.0; // Solve time to zero
637# ifndef EMBEDDED
638 info->polish_time = 0.0; // Polish time to zero
639# endif /* ifndef EMBEDDED */
640
641 // NB: We do not reset the setup_time because it is performed only once
642#endif /* ifdef PROFILING */
643
644 update_status(info, OSQP_UNSOLVED); // Problem is unsolved
645
646#if EMBEDDED != 1
647 info->rho_updates = 0; // Rho updates are now 0
648#endif /* if EMBEDDED != 1 */
649}
650
651void update_status(OSQPInfo *info, c_int status_val) {
652 // Update status value
653 info->status_val = status_val;
654
655 // Update status string depending on status val
656 if (status_val == OSQP_SOLVED) c_strcpy(info->status, "solved");
657
658 if (status_val == OSQP_SOLVED_INACCURATE) c_strcpy(info->status,
659 "solved inaccurate");
660 else if (status_val == OSQP_PRIMAL_INFEASIBLE) c_strcpy(info->status,
661 "primal infeasible");
662 else if (status_val == OSQP_PRIMAL_INFEASIBLE_INACCURATE) c_strcpy(info->status,
663 "primal infeasible inaccurate");
664 else if (status_val == OSQP_UNSOLVED) c_strcpy(info->status, "unsolved");
665 else if (status_val == OSQP_DUAL_INFEASIBLE) c_strcpy(info->status,
666 "dual infeasible");
667 else if (status_val == OSQP_DUAL_INFEASIBLE_INACCURATE) c_strcpy(info->status,
668 "dual infeasible inaccurate");
669 else if (status_val == OSQP_MAX_ITER_REACHED) c_strcpy(info->status,
670 "maximum iterations reached");
671#ifdef PROFILING
672 else if (status_val == OSQP_TIME_LIMIT_REACHED) c_strcpy(info->status,
673 "run time limit reached");
674#endif /* ifdef PROFILING */
675 else if (status_val == OSQP_SIGINT) c_strcpy(info->status, "interrupted");
676
677 else if (status_val == OSQP_NON_CVX) c_strcpy(info->status, "problem non convex");
678
679}
680
681c_int check_termination(OSQPWorkspace *work, c_int approximate) {
682 c_float eps_prim, eps_dual, eps_prim_inf, eps_dual_inf;
683 c_int exitflag;
684 c_int prim_res_check, dual_res_check, prim_inf_check, dual_inf_check;
685 c_float eps_abs, eps_rel;
686
687 // Initialize variables to 0
688 exitflag = 0;
689 prim_res_check = 0; dual_res_check = 0;
690 prim_inf_check = 0; dual_inf_check = 0;
691
692 // Initialize tolerances
693 eps_abs = work->settings->eps_abs;
694 eps_rel = work->settings->eps_rel;
695 eps_prim_inf = work->settings->eps_prim_inf;
696 eps_dual_inf = work->settings->eps_dual_inf;
697
698 // If residuals are too large, the problem is probably non convex
699 if ((work->info->pri_res > OSQP_INFTY) ||
700 (work->info->dua_res > OSQP_INFTY)){
701 // Looks like residuals are diverging. Probably the problem is non convex!
702 // Terminate and report it
703 update_status(work->info, OSQP_NON_CVX);
704 work->info->obj_val = OSQP_NAN;
705 return 1;
706 }
707
708 // If approximate solution required, increase tolerances by 10
709 if (approximate) {
710 eps_abs *= 10;
711 eps_rel *= 10;
712 eps_prim_inf *= 10;
713 eps_dual_inf *= 10;
714 }
715
716 // Check residuals
717 if (work->data->m == 0) {
718 prim_res_check = 1; // No constraints -> Primal feasibility always satisfied
719 }
720 else {
721 // Compute primal tolerance
722 eps_prim = compute_pri_tol(work, eps_abs, eps_rel);
723
724 // Primal feasibility check
725 if (work->info->pri_res < eps_prim) {
726 prim_res_check = 1;
727 } else {
728 // Primal infeasibility check
729 prim_inf_check = is_primal_infeasible(work, eps_prim_inf);
730 }
731 } // End check if m == 0
732
733 // Compute dual tolerance
734 eps_dual = compute_dua_tol(work, eps_abs, eps_rel);
735
736 // Dual feasibility check
737 if (work->info->dua_res < eps_dual) {
738 dual_res_check = 1;
739 } else {
740 // Check dual infeasibility
741 dual_inf_check = is_dual_infeasible(work, eps_dual_inf);
742 }
743
744 // Compare checks to determine solver status
745 if (prim_res_check && dual_res_check) {
746 // Update final information
747 if (approximate) {
748 update_status(work->info, OSQP_SOLVED_INACCURATE);
749 } else {
750 update_status(work->info, OSQP_SOLVED);
751 }
752 exitflag = 1;
753 }
754 else if (prim_inf_check) {
755 // Update final information
756 if (approximate) {
757 update_status(work->info, OSQP_PRIMAL_INFEASIBLE_INACCURATE);
758 } else {
759 update_status(work->info, OSQP_PRIMAL_INFEASIBLE);
760 }
761
762 if (work->settings->scaling && !work->settings->scaled_termination) {
763 // Update infeasibility certificate
764 vec_ew_prod(work->scaling->E, work->delta_y, work->delta_y, work->data->m);
765 }
766 work->info->obj_val = OSQP_INFTY;
767 exitflag = 1;
768 }
769 else if (dual_inf_check) {
770 // Update final information
771 if (approximate) {
772 update_status(work->info, OSQP_DUAL_INFEASIBLE_INACCURATE);
773 } else {
774 update_status(work->info, OSQP_DUAL_INFEASIBLE);
775 }
776
777 if (work->settings->scaling && !work->settings->scaled_termination) {
778 // Update infeasibility certificate
779 vec_ew_prod(work->scaling->D, work->delta_x, work->delta_x, work->data->n);
780 }
781 work->info->obj_val = -OSQP_INFTY;
782 exitflag = 1;
783 }
784
785 return exitflag;
786}
787
788
789#ifndef EMBEDDED
790
791c_int validate_data(const OSQPData *data) {
792 c_int j, ptr;
793
794 if (!data) {
795# ifdef PRINTING
796 c_eprint("Missing data");
797# endif
798 return 1;
799 }
800
801 if (!(data->P)) {
802# ifdef PRINTING
803 c_eprint("Missing matrix P");
804# endif
805 return 1;
806 }
807
808 if (!(data->A)) {
809# ifdef PRINTING
810 c_eprint("Missing matrix A");
811# endif
812 return 1;
813 }
814
815 if (!(data->q)) {
816# ifdef PRINTING
817 c_eprint("Missing vector q");
818# endif
819 return 1;
820 }
821
822 // General dimensions Tests
823 if ((data->n <= 0) || (data->m < 0)) {
824# ifdef PRINTING
825 c_eprint("n must be positive and m nonnegative; n = %i, m = %i",
826 (int)data->n, (int)data->m);
827# endif /* ifdef PRINTING */
828 return 1;
829 }
830
831 // Matrix P
832 if (data->P->m != data->n) {
833# ifdef PRINTING
834 c_eprint("P does not have dimension n x n with n = %i", (int)data->n);
835# endif /* ifdef PRINTING */
836 return 1;
837 }
838
839 if (data->P->m != data->P->n) {
840# ifdef PRINTING
841 c_eprint("P is not square");
842# endif /* ifdef PRINTING */
843 return 1;
844 }
845
846 for (j = 0; j < data->n; j++) { // COLUMN
847 for (ptr = data->P->p[j]; ptr < data->P->p[j + 1]; ptr++) {
848 if (data->P->i[ptr] > j) { // if ROW > COLUMN
849# ifdef PRINTING
850 c_eprint("P is not upper triangular");
851# endif /* ifdef PRINTING */
852 return 1;
853 }
854 }
855 }
856
857 // Matrix A
858 if ((data->A->m != data->m) || (data->A->n != data->n)) {
859# ifdef PRINTING
860 c_eprint("A does not have dimension %i x %i", (int)data->m, (int)data->n);
861# endif /* ifdef PRINTING */
862 return 1;
863 }
864
865 // Lower and upper bounds
866 for (j = 0; j < data->m; j++) {
867 if (data->l[j] > data->u[j]) {
868# ifdef PRINTING
869 c_eprint("Lower bound at index %d is greater than upper bound: %.4e > %.4e",
870 (int)j, data->l[j], data->u[j]);
871# endif /* ifdef PRINTING */
872 return 1;
873 }
874 }
875
876 // TODO: Complete with other checks
877
878 return 0;
879}
880
881c_int validate_linsys_solver(c_int linsys_solver) {
882 if ((linsys_solver != QDLDL_SOLVER) &&
883 (linsys_solver != MKL_PARDISO_SOLVER)) {
884 return 1;
885 }
886
887 // TODO: Add more solvers in case
888
889 // Valid solver
890 return 0;
891}
892
893c_int validate_settings(const OSQPSettings *settings) {
894 if (!settings) {
895# ifdef PRINTING
896 c_eprint("Missing settings!");
897# endif /* ifdef PRINTING */
898 return 1;
899 }
900
901 if (settings->scaling < 0) {
902# ifdef PRINTING
903 c_eprint("scaling must be nonnegative");
904# endif /* ifdef PRINTING */
905 return 1;
906 }
907
908 if ((settings->adaptive_rho != 0) && (settings->adaptive_rho != 1)) {
909# ifdef PRINTING
910 c_eprint("adaptive_rho must be either 0 or 1");
911# endif /* ifdef PRINTING */
912 return 1;
913 }
914
915 if (settings->adaptive_rho_interval < 0) {
916# ifdef PRINTING
917 c_eprint("adaptive_rho_interval must be nonnegative");
918# endif /* ifdef PRINTING */
919 return 1;
920 }
921# ifdef PROFILING
922
923 if (settings->adaptive_rho_fraction <= 0) {
924# ifdef PRINTING
925 c_eprint("adaptive_rho_fraction must be positive");
926# endif /* ifdef PRINTING */
927 return 1;
928 }
929# endif /* ifdef PROFILING */
930
931 if (settings->adaptive_rho_tolerance < 1.0) {
932# ifdef PRINTING
933 c_eprint("adaptive_rho_tolerance must be >= 1");
934# endif /* ifdef PRINTING */
935 return 1;
936 }
937
938 if (settings->polish_refine_iter < 0) {
939# ifdef PRINTING
940 c_eprint("polish_refine_iter must be nonnegative");
941# endif /* ifdef PRINTING */
942 return 1;
943 }
944
945 if (settings->rho <= 0.0) {
946# ifdef PRINTING
947 c_eprint("rho must be positive");
948# endif /* ifdef PRINTING */
949 return 1;
950 }
951
952 if (settings->sigma <= 0.0) {
953# ifdef PRINTING
954 c_eprint("sigma must be positive");
955# endif /* ifdef PRINTING */
956 return 1;
957 }
958
959 if (settings->delta <= 0.0) {
960# ifdef PRINTING
961 c_eprint("delta must be positive");
962# endif /* ifdef PRINTING */
963 return 1;
964 }
965
966 if (settings->max_iter <= 0) {
967# ifdef PRINTING
968 c_eprint("max_iter must be positive");
969# endif /* ifdef PRINTING */
970 return 1;
971 }
972
973 if (settings->eps_abs < 0.0) {
974# ifdef PRINTING
975 c_eprint("eps_abs must be nonnegative");
976# endif /* ifdef PRINTING */
977 return 1;
978 }
979
980 if (settings->eps_rel < 0.0) {
981# ifdef PRINTING
982 c_eprint("eps_rel must be nonnegative");
983# endif /* ifdef PRINTING */
984 return 1;
985 }
986
987 if ((settings->eps_rel == 0.0) &&
988 (settings->eps_abs == 0.0)) {
989# ifdef PRINTING
990 c_eprint("at least one of eps_abs and eps_rel must be positive");
991# endif /* ifdef PRINTING */
992 return 1;
993 }
994
995 if (settings->eps_prim_inf <= 0.0) {
996# ifdef PRINTING
997 c_eprint("eps_prim_inf must be positive");
998# endif /* ifdef PRINTING */
999 return 1;
1000 }
1001
1002 if (settings->eps_dual_inf <= 0.0) {
1003# ifdef PRINTING
1004 c_eprint("eps_dual_inf must be positive");
1005# endif /* ifdef PRINTING */
1006 return 1;
1007 }
1008
1009 if ((settings->alpha <= 0.0) ||
1010 (settings->alpha >= 2.0)) {
1011# ifdef PRINTING
1012 c_eprint("alpha must be strictly between 0 and 2");
1013# endif /* ifdef PRINTING */
1014 return 1;
1015 }
1016
1017 if (validate_linsys_solver(settings->linsys_solver)) {
1018# ifdef PRINTING
1019 c_eprint("linsys_solver not recognized");
1020# endif /* ifdef PRINTING */
1021 return 1;
1022 }
1023
1024 if ((settings->verbose != 0) &&
1025 (settings->verbose != 1)) {
1026# ifdef PRINTING
1027 c_eprint("verbose must be either 0 or 1");
1028# endif /* ifdef PRINTING */
1029 return 1;
1030 }
1031
1032 if ((settings->scaled_termination != 0) &&
1033 (settings->scaled_termination != 1)) {
1034# ifdef PRINTING
1035 c_eprint("scaled_termination must be either 0 or 1");
1036# endif /* ifdef PRINTING */
1037 return 1;
1038 }
1039
1040 if (settings->check_termination < 0) {
1041# ifdef PRINTING
1042 c_eprint("check_termination must be nonnegative");
1043# endif /* ifdef PRINTING */
1044 return 1;
1045 }
1046
1047 if ((settings->warm_start != 0) &&
1048 (settings->warm_start != 1)) {
1049# ifdef PRINTING
1050 c_eprint("warm_start must be either 0 or 1");
1051# endif /* ifdef PRINTING */
1052 return 1;
1053 }
1054# ifdef PROFILING
1055
1056 if (settings->time_limit < 0.0) {
1057# ifdef PRINTING
1058 c_eprint("time_limit must be nonnegative\n");
1059# endif /* ifdef PRINTING */
1060 return 1;
1061 }
1062# endif /* ifdef PROFILING */
1063
1064 return 0;
1065}
1066
1067#endif // #ifndef EMBEDDED