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