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