blob: 89c7de72dcf7d62ed0a0083b834d50c280c36619 [file] [log] [blame]
#include "osqp.h" // For OSQP rho update
#include "auxil.h"
#include "proj.h"
#include "lin_alg.h"
#include "constants.h"
#include "scaling.h"
#include "util.h"
/***********************************************************
* Auxiliary functions needed to compute ADMM iterations * *
***********************************************************/
#if EMBEDDED != 1
c_float compute_rho_estimate(OSQPWorkspace *work) {
c_int n, m; // Dimensions
c_float pri_res, dua_res; // Primal and dual residuals
c_float pri_res_norm, dua_res_norm; // Normalization for the residuals
c_float temp_res_norm; // Temporary residual norm
c_float rho_estimate; // Rho estimate value
// Get problem dimensions
n = work->data->n;
m = work->data->m;
// Get primal and dual residuals
pri_res = vec_norm_inf(work->z_prev, m);
dua_res = vec_norm_inf(work->x_prev, n);
// Normalize primal residual
pri_res_norm = vec_norm_inf(work->z, m); // ||z||
temp_res_norm = vec_norm_inf(work->Ax, m); // ||Ax||
pri_res_norm = c_max(pri_res_norm, temp_res_norm); // max (||z||,||Ax||)
pri_res /= (pri_res_norm + OSQP_DIVISION_TOL); // Normalize primal
// residual (prevent 0
// division)
// Normalize dual residual
dua_res_norm = vec_norm_inf(work->data->q, n); // ||q||
temp_res_norm = vec_norm_inf(work->Aty, n); // ||A' y||
dua_res_norm = c_max(dua_res_norm, temp_res_norm);
temp_res_norm = vec_norm_inf(work->Px, n); // ||P x||
dua_res_norm = c_max(dua_res_norm, temp_res_norm); // max(||q||,||A' y||,||P
// x||)
dua_res /= (dua_res_norm + OSQP_DIVISION_TOL); // Normalize dual residual
// (prevent 0 division)
// Return rho estimate
rho_estimate = work->settings->rho * c_sqrt(pri_res / dua_res);
rho_estimate = c_min(c_max(rho_estimate, RHO_MIN), RHO_MAX); // Constrain
// rho values
return rho_estimate;
}
c_int adapt_rho(OSQPWorkspace *work) {
c_int exitflag; // Exitflag
c_float rho_new; // New rho value
exitflag = 0; // Initialize exitflag to 0
// Compute new rho
rho_new = compute_rho_estimate(work);
// Set rho estimate in info
work->info->rho_estimate = rho_new;
// Check if the new rho is large or small enough and update it in case
if ((rho_new > work->settings->rho * work->settings->adaptive_rho_tolerance) ||
(rho_new < work->settings->rho / work->settings->adaptive_rho_tolerance)) {
exitflag = osqp_update_rho(work, rho_new);
work->info->rho_updates += 1;
}
return exitflag;
}
void set_rho_vec(OSQPWorkspace *work) {
c_int i;
work->settings->rho = c_min(c_max(work->settings->rho, RHO_MIN), RHO_MAX);
for (i = 0; i < work->data->m; i++) {
if ((work->data->l[i] < -OSQP_INFTY * MIN_SCALING) &&
(work->data->u[i] > OSQP_INFTY * MIN_SCALING)) {
// Loose bounds
work->constr_type[i] = -1;
work->rho_vec[i] = RHO_MIN;
} else if (work->data->u[i] - work->data->l[i] < RHO_TOL) {
// Equality constraints
work->constr_type[i] = 1;
work->rho_vec[i] = RHO_EQ_OVER_RHO_INEQ * work->settings->rho;
} else {
// Inequality constraints
work->constr_type[i] = 0;
work->rho_vec[i] = work->settings->rho;
}
work->rho_inv_vec[i] = 1. / work->rho_vec[i];
}
}
c_int update_rho_vec(OSQPWorkspace *work) {
c_int i, exitflag, constr_type_changed;
exitflag = 0;
constr_type_changed = 0;
for (i = 0; i < work->data->m; i++) {
if ((work->data->l[i] < -OSQP_INFTY * MIN_SCALING) &&
(work->data->u[i] > OSQP_INFTY * MIN_SCALING)) {
// Loose bounds
if (work->constr_type[i] != -1) {
work->constr_type[i] = -1;
work->rho_vec[i] = RHO_MIN;
work->rho_inv_vec[i] = 1. / RHO_MIN;
constr_type_changed = 1;
}
} else if (work->data->u[i] - work->data->l[i] < RHO_TOL) {
// Equality constraints
if (work->constr_type[i] != 1) {
work->constr_type[i] = 1;
work->rho_vec[i] = RHO_EQ_OVER_RHO_INEQ * work->settings->rho;
work->rho_inv_vec[i] = 1. / work->rho_vec[i];
constr_type_changed = 1;
}
} else {
// Inequality constraints
if (work->constr_type[i] != 0) {
work->constr_type[i] = 0;
work->rho_vec[i] = work->settings->rho;
work->rho_inv_vec[i] = 1. / work->settings->rho;
constr_type_changed = 1;
}
}
}
// Update rho_vec in KKT matrix if constraints type has changed
if (constr_type_changed == 1) {
exitflag = work->linsys_solver->update_rho_vec(work->linsys_solver,
work->rho_vec);
}
return exitflag;
}
#endif // EMBEDDED != 1
void swap_vectors(c_float **a, c_float **b) {
c_float *temp;
temp = *b;
*b = *a;
*a = temp;
}
void cold_start(OSQPWorkspace *work) {
vec_set_scalar(work->x, 0., work->data->n);
vec_set_scalar(work->z, 0., work->data->m);
vec_set_scalar(work->y, 0., work->data->m);
}
static void compute_rhs(OSQPWorkspace *work) {
c_int i; // Index
for (i = 0; i < work->data->n; i++) {
// Cycle over part related to x variables
work->xz_tilde[i] = work->settings->sigma * work->x_prev[i] -
work->data->q[i];
}
for (i = 0; i < work->data->m; i++) {
// Cycle over dual variable in the first step (nu)
work->xz_tilde[i + work->data->n] = work->z_prev[i] - work->rho_inv_vec[i] *
work->y[i];
}
}
void update_xz_tilde(OSQPWorkspace *work) {
// Compute right-hand side
compute_rhs(work);
// Solve linear system
work->linsys_solver->solve(work->linsys_solver, work->xz_tilde);
}
void update_x(OSQPWorkspace *work) {
c_int i;
// update x
for (i = 0; i < work->data->n; i++) {
work->x[i] = work->settings->alpha * work->xz_tilde[i] +
((c_float)1.0 - work->settings->alpha) * work->x_prev[i];
}
// update delta_x
for (i = 0; i < work->data->n; i++) {
work->delta_x[i] = work->x[i] - work->x_prev[i];
}
}
void update_z(OSQPWorkspace *work) {
c_int i;
// update z
for (i = 0; i < work->data->m; i++) {
work->z[i] = work->settings->alpha * work->xz_tilde[i + work->data->n] +
((c_float)1.0 - work->settings->alpha) * work->z_prev[i] +
work->rho_inv_vec[i] * work->y[i];
}
// project z
project(work, work->z);
}
void update_y(OSQPWorkspace *work) {
c_int i; // Index
for (i = 0; i < work->data->m; i++) {
work->delta_y[i] = work->rho_vec[i] *
(work->settings->alpha *
work->xz_tilde[i + work->data->n] +
((c_float)1.0 - work->settings->alpha) * work->z_prev[i] -
work->z[i]);
work->y[i] += work->delta_y[i];
}
}
c_float compute_obj_val(OSQPWorkspace *work, c_float *x) {
c_float obj_val;
obj_val = quad_form(work->data->P, x) +
vec_prod(work->data->q, x, work->data->n);
if (work->settings->scaling) {
obj_val *= work->scaling->cinv;
}
return obj_val;
}
c_float compute_pri_res(OSQPWorkspace *work, c_float *x, c_float *z) {
// NB: Use z_prev as working vector
// pr = Ax - z
mat_vec(work->data->A, x, work->Ax, 0); // Ax
vec_add_scaled(work->z_prev, work->Ax, z, work->data->m, -1);
// If scaling active -> rescale residual
if (work->settings->scaling && !work->settings->scaled_termination) {
return vec_scaled_norm_inf(work->scaling->Einv, work->z_prev, work->data->m);
}
// Return norm of the residual
return vec_norm_inf(work->z_prev, work->data->m);
}
c_float compute_pri_tol(OSQPWorkspace *work, c_float eps_abs, c_float eps_rel) {
c_float max_rel_eps, temp_rel_eps;
// max_rel_eps = max(||z||, ||A x||)
if (work->settings->scaling && !work->settings->scaled_termination) {
// ||Einv * z||
max_rel_eps =
vec_scaled_norm_inf(work->scaling->Einv, work->z, work->data->m);
// ||Einv * A * x||
temp_rel_eps = vec_scaled_norm_inf(work->scaling->Einv,
work->Ax,
work->data->m);
// Choose maximum
max_rel_eps = c_max(max_rel_eps, temp_rel_eps);
} else { // No unscaling required
// ||z||
max_rel_eps = vec_norm_inf(work->z, work->data->m);
// ||A * x||
temp_rel_eps = vec_norm_inf(work->Ax, work->data->m);
// Choose maximum
max_rel_eps = c_max(max_rel_eps, temp_rel_eps);
}
// eps_prim
return eps_abs + eps_rel * max_rel_eps;
}
c_float compute_dua_res(OSQPWorkspace *work, c_float *x, c_float *y) {
// NB: Use x_prev as temporary vector
// NB: Only upper triangular part of P is stored.
// dr = q + A'*y + P*x
// dr = q
prea_vec_copy(work->data->q, work->x_prev, work->data->n);
// P * x (upper triangular part)
mat_vec(work->data->P, x, work->Px, 0);
// P' * x (lower triangular part with no diagonal)
mat_tpose_vec(work->data->P, x, work->Px, 1, 1);
// dr += P * x (full P matrix)
vec_add_scaled(work->x_prev, work->x_prev, work->Px, work->data->n, 1);
// dr += A' * y
if (work->data->m > 0) {
mat_tpose_vec(work->data->A, y, work->Aty, 0, 0);
vec_add_scaled(work->x_prev, work->x_prev, work->Aty, work->data->n, 1);
}
// If scaling active -> rescale residual
if (work->settings->scaling && !work->settings->scaled_termination) {
return work->scaling->cinv * vec_scaled_norm_inf(work->scaling->Dinv,
work->x_prev,
work->data->n);
}
return vec_norm_inf(work->x_prev, work->data->n);
}
c_float compute_dua_tol(OSQPWorkspace *work, c_float eps_abs, c_float eps_rel) {
c_float max_rel_eps, temp_rel_eps;
// max_rel_eps = max(||q||, ||A' y|, ||P x||)
if (work->settings->scaling && !work->settings->scaled_termination) {
// || Dinv q||
max_rel_eps = vec_scaled_norm_inf(work->scaling->Dinv,
work->data->q,
work->data->n);
// || Dinv A' y ||
temp_rel_eps = vec_scaled_norm_inf(work->scaling->Dinv,
work->Aty,
work->data->n);
max_rel_eps = c_max(max_rel_eps, temp_rel_eps);
// || Dinv P x||
temp_rel_eps = vec_scaled_norm_inf(work->scaling->Dinv,
work->Px,
work->data->n);
max_rel_eps = c_max(max_rel_eps, temp_rel_eps);
// Multiply by cinv
max_rel_eps *= work->scaling->cinv;
} else { // No scaling required
// ||q||
max_rel_eps = vec_norm_inf(work->data->q, work->data->n);
// ||A'*y||
temp_rel_eps = vec_norm_inf(work->Aty, work->data->n);
max_rel_eps = c_max(max_rel_eps, temp_rel_eps);
// ||P*x||
temp_rel_eps = vec_norm_inf(work->Px, work->data->n);
max_rel_eps = c_max(max_rel_eps, temp_rel_eps);
}
// eps_dual
return eps_abs + eps_rel * max_rel_eps;
}
c_int is_primal_infeasible(OSQPWorkspace *work, c_float eps_prim_inf) {
// This function checks for the primal infeasibility termination criteria.
//
// 1) A' * delta_y < eps * ||delta_y||
//
// 2) u'*max(delta_y, 0) + l'*min(delta_y, 0) < eps * ||delta_y||
//
c_int i; // Index for loops
c_float norm_delta_y;
c_float ineq_lhs = 0.0;
// Project delta_y onto the polar of the recession cone of [l,u]
for (i = 0; i < work->data->m; i++) {
if (work->data->u[i] > OSQP_INFTY * MIN_SCALING) { // Infinite upper bound
if (work->data->l[i] < -OSQP_INFTY * MIN_SCALING) { // Infinite lower bound
// Both bounds infinite
work->delta_y[i] = 0.0;
} else {
// Only upper bound infinite
work->delta_y[i] = c_min(work->delta_y[i], 0.0);
}
} else if (work->data->l[i] < -OSQP_INFTY * MIN_SCALING) { // Infinite lower bound
// Only lower bound infinite
work->delta_y[i] = c_max(work->delta_y[i], 0.0);
}
}
// Compute infinity norm of delta_y (unscale if necessary)
if (work->settings->scaling && !work->settings->scaled_termination) {
// Use work->Adelta_x as temporary vector
vec_ew_prod(work->scaling->E, work->delta_y, work->Adelta_x, work->data->m);
norm_delta_y = vec_norm_inf(work->Adelta_x, work->data->m);
} else {
norm_delta_y = vec_norm_inf(work->delta_y, work->data->m);
}
if (norm_delta_y > OSQP_DIVISION_TOL) {
for (i = 0; i < work->data->m; i++) {
ineq_lhs += work->data->u[i] * c_max(work->delta_y[i], 0) + \
work->data->l[i] * c_min(work->delta_y[i], 0);
}
// Check if the condition is satisfied: ineq_lhs < -eps
if (ineq_lhs < eps_prim_inf * norm_delta_y) {
// Compute and return ||A'delta_y|| < eps_prim_inf
mat_tpose_vec(work->data->A, work->delta_y, work->Atdelta_y, 0, 0);
// Unscale if necessary
if (work->settings->scaling && !work->settings->scaled_termination) {
vec_ew_prod(work->scaling->Dinv,
work->Atdelta_y,
work->Atdelta_y,
work->data->n);
}
return vec_norm_inf(work->Atdelta_y, work->data->n) < eps_prim_inf * norm_delta_y;
}
}
// Conditions not satisfied -> not primal infeasible
return 0;
}
c_int is_dual_infeasible(OSQPWorkspace *work, c_float eps_dual_inf) {
// This function checks for the scaled dual infeasibility termination
// criteria.
//
// 1) q * delta_x < eps * || delta_x ||
//
// 2) ||P * delta_x || < eps * || delta_x ||
//
// 3) -> (A * delta_x)_i > -eps * || delta_x ||, l_i != -inf
// -> (A * delta_x)_i < eps * || delta_x ||, u_i != inf
//
c_int i; // Index for loops
c_float norm_delta_x;
c_float cost_scaling;
// Compute norm of delta_x
if (work->settings->scaling && !work->settings->scaled_termination) { // Unscale
// if
// necessary
norm_delta_x = vec_scaled_norm_inf(work->scaling->D,
work->delta_x,
work->data->n);
cost_scaling = work->scaling->c;
} else {
norm_delta_x = vec_norm_inf(work->delta_x, work->data->n);
cost_scaling = 1.0;
}
// Prevent 0 division || delta_x || > 0
if (norm_delta_x > OSQP_DIVISION_TOL) {
// Normalize delta_x by its norm
/* vec_mult_scalar(work->delta_x, 1./norm_delta_x, work->data->n); */
// Check first if q'*delta_x < 0
if (vec_prod(work->data->q, work->delta_x, work->data->n) <
cost_scaling * eps_dual_inf * norm_delta_x) {
// Compute product P * delta_x (NB: P is store in upper triangular form)
mat_vec(work->data->P, work->delta_x, work->Pdelta_x, 0);
mat_tpose_vec(work->data->P, work->delta_x, work->Pdelta_x, 1, 1);
// Scale if necessary
if (work->settings->scaling && !work->settings->scaled_termination) {
vec_ew_prod(work->scaling->Dinv,
work->Pdelta_x,
work->Pdelta_x,
work->data->n);
}
// Check if || P * delta_x || = 0
if (vec_norm_inf(work->Pdelta_x, work->data->n) <
cost_scaling * eps_dual_inf * norm_delta_x) {
// Compute A * delta_x
mat_vec(work->data->A, work->delta_x, work->Adelta_x, 0);
// Scale if necessary
if (work->settings->scaling && !work->settings->scaled_termination) {
vec_ew_prod(work->scaling->Einv,
work->Adelta_x,
work->Adelta_x,
work->data->m);
}
// De Morgan Law Applied to dual infeasibility conditions for A * x
// NB: Note that MIN_SCALING is used to adjust the infinity value
// in case the problem is scaled.
for (i = 0; i < work->data->m; i++) {
if (((work->data->u[i] < OSQP_INFTY * MIN_SCALING) &&
(work->Adelta_x[i] > eps_dual_inf * norm_delta_x)) ||
((work->data->l[i] > -OSQP_INFTY * MIN_SCALING) &&
(work->Adelta_x[i] < -eps_dual_inf * norm_delta_x))) {
// At least one condition not satisfied -> not dual infeasible
return 0;
}
}
// All conditions passed -> dual infeasible
return 1;
}
}
}
// Conditions not satisfied -> not dual infeasible
return 0;
}
c_int has_solution(OSQPInfo * info){
return ((info->status_val != OSQP_PRIMAL_INFEASIBLE) &&
(info->status_val != OSQP_PRIMAL_INFEASIBLE_INACCURATE) &&
(info->status_val != OSQP_DUAL_INFEASIBLE) &&
(info->status_val != OSQP_DUAL_INFEASIBLE_INACCURATE) &&
(info->status_val != OSQP_NON_CVX));
}
void store_solution(OSQPWorkspace *work) {
#ifndef EMBEDDED
c_float norm_vec;
#endif /* ifndef EMBEDDED */
if (has_solution(work->info)) {
prea_vec_copy(work->x, work->solution->x, work->data->n); // primal
prea_vec_copy(work->y, work->solution->y, work->data->m); // dual
// Unscale solution if scaling has been performed
if (work->settings->scaling)
unscale_solution(work);
} else {
// No solution present. Solution is NaN
vec_set_scalar(work->solution->x, OSQP_NAN, work->data->n);
vec_set_scalar(work->solution->y, OSQP_NAN, work->data->m);
#ifndef EMBEDDED
// Normalize infeasibility certificates if embedded is off
// NB: It requires a division
if ((work->info->status_val == OSQP_PRIMAL_INFEASIBLE) ||
((work->info->status_val == OSQP_PRIMAL_INFEASIBLE_INACCURATE))) {
norm_vec = vec_norm_inf(work->delta_y, work->data->m);
vec_mult_scalar(work->delta_y, 1. / norm_vec, work->data->m);
}
if ((work->info->status_val == OSQP_DUAL_INFEASIBLE) ||
((work->info->status_val == OSQP_DUAL_INFEASIBLE_INACCURATE))) {
norm_vec = vec_norm_inf(work->delta_x, work->data->n);
vec_mult_scalar(work->delta_x, 1. / norm_vec, work->data->n);
}
#endif /* ifndef EMBEDDED */
// Cold start iterates to 0 for next runs (they cannot start from NaN)
cold_start(work);
}
}
void update_info(OSQPWorkspace *work,
c_int iter,
c_int compute_objective,
c_int polish) {
c_float *x, *z, *y; // Allocate pointers to variables
c_float *obj_val, *pri_res, *dua_res; // objective value, residuals
#ifdef PROFILING
c_float *run_time; // Execution time
#endif /* ifdef PROFILING */
#ifndef EMBEDDED
if (polish) {
x = work->pol->x;
y = work->pol->y;
z = work->pol->z;
obj_val = &work->pol->obj_val;
pri_res = &work->pol->pri_res;
dua_res = &work->pol->dua_res;
# ifdef PROFILING
run_time = &work->info->polish_time;
# endif /* ifdef PROFILING */
} else {
#endif // EMBEDDED
x = work->x;
y = work->y;
z = work->z;
obj_val = &work->info->obj_val;
pri_res = &work->info->pri_res;
dua_res = &work->info->dua_res;
work->info->iter = iter; // Update iteration number
#ifdef PROFILING
run_time = &work->info->solve_time;
#endif /* ifdef PROFILING */
#ifndef EMBEDDED
}
#endif /* ifndef EMBEDDED */
// Compute the objective if needed
if (compute_objective) {
*obj_val = compute_obj_val(work, x);
}
// Compute primal residual
if (work->data->m == 0) {
// No constraints -> Always primal feasible
*pri_res = 0.;
} else {
*pri_res = compute_pri_res(work, x, z);
}
// Compute dual residual
*dua_res = compute_dua_res(work, x, y);
// Update timing
#ifdef PROFILING
*run_time = osqp_toc(work->timer);
#endif /* ifdef PROFILING */
#ifdef PRINTING
work->summary_printed = 0; // The just updated info have not been printed
#endif /* ifdef PRINTING */
}
void reset_info(OSQPInfo *info) {
#ifdef PROFILING
// Initialize info values.
info->solve_time = 0.0; // Solve time to zero
# ifndef EMBEDDED
info->polish_time = 0.0; // Polish time to zero
# endif /* ifndef EMBEDDED */
// NB: We do not reset the setup_time because it is performed only once
#endif /* ifdef PROFILING */
update_status(info, OSQP_UNSOLVED); // Problem is unsolved
#if EMBEDDED != 1
info->rho_updates = 0; // Rho updates are now 0
#endif /* if EMBEDDED != 1 */
}
void update_status(OSQPInfo *info, c_int status_val) {
// Update status value
info->status_val = status_val;
// Update status string depending on status val
if (status_val == OSQP_SOLVED) c_strcpy(info->status, "solved");
if (status_val == OSQP_SOLVED_INACCURATE) c_strcpy(info->status,
"solved inaccurate");
else if (status_val == OSQP_PRIMAL_INFEASIBLE) c_strcpy(info->status,
"primal infeasible");
else if (status_val == OSQP_PRIMAL_INFEASIBLE_INACCURATE) c_strcpy(info->status,
"primal infeasible inaccurate");
else if (status_val == OSQP_UNSOLVED) c_strcpy(info->status, "unsolved");
else if (status_val == OSQP_DUAL_INFEASIBLE) c_strcpy(info->status,
"dual infeasible");
else if (status_val == OSQP_DUAL_INFEASIBLE_INACCURATE) c_strcpy(info->status,
"dual infeasible inaccurate");
else if (status_val == OSQP_MAX_ITER_REACHED) c_strcpy(info->status,
"maximum iterations reached");
#ifdef PROFILING
else if (status_val == OSQP_TIME_LIMIT_REACHED) c_strcpy(info->status,
"run time limit reached");
#endif /* ifdef PROFILING */
else if (status_val == OSQP_SIGINT) c_strcpy(info->status, "interrupted");
else if (status_val == OSQP_NON_CVX) c_strcpy(info->status, "problem non convex");
}
c_int check_termination(OSQPWorkspace *work, c_int approximate) {
c_float eps_prim, eps_dual, eps_prim_inf, eps_dual_inf;
c_int exitflag;
c_int prim_res_check, dual_res_check, prim_inf_check, dual_inf_check;
c_float eps_abs, eps_rel;
// Initialize variables to 0
exitflag = 0;
prim_res_check = 0; dual_res_check = 0;
prim_inf_check = 0; dual_inf_check = 0;
// Initialize tolerances
eps_abs = work->settings->eps_abs;
eps_rel = work->settings->eps_rel;
eps_prim_inf = work->settings->eps_prim_inf;
eps_dual_inf = work->settings->eps_dual_inf;
// If residuals are too large, the problem is probably non convex
if ((work->info->pri_res > OSQP_INFTY) ||
(work->info->dua_res > OSQP_INFTY)){
// Looks like residuals are diverging. Probably the problem is non convex!
// Terminate and report it
update_status(work->info, OSQP_NON_CVX);
work->info->obj_val = OSQP_NAN;
return 1;
}
// If approximate solution required, increase tolerances by 10
if (approximate) {
eps_abs *= 10;
eps_rel *= 10;
eps_prim_inf *= 10;
eps_dual_inf *= 10;
}
// Check residuals
if (work->data->m == 0) {
prim_res_check = 1; // No constraints -> Primal feasibility always satisfied
}
else {
// Compute primal tolerance
eps_prim = compute_pri_tol(work, eps_abs, eps_rel);
// Primal feasibility check
if (work->info->pri_res < eps_prim) {
prim_res_check = 1;
} else {
// Primal infeasibility check
prim_inf_check = is_primal_infeasible(work, eps_prim_inf);
}
} // End check if m == 0
// Compute dual tolerance
eps_dual = compute_dua_tol(work, eps_abs, eps_rel);
// Dual feasibility check
if (work->info->dua_res < eps_dual) {
dual_res_check = 1;
} else {
// Check dual infeasibility
dual_inf_check = is_dual_infeasible(work, eps_dual_inf);
}
// Compare checks to determine solver status
if (prim_res_check && dual_res_check) {
// Update final information
if (approximate) {
update_status(work->info, OSQP_SOLVED_INACCURATE);
} else {
update_status(work->info, OSQP_SOLVED);
}
exitflag = 1;
}
else if (prim_inf_check) {
// Update final information
if (approximate) {
update_status(work->info, OSQP_PRIMAL_INFEASIBLE_INACCURATE);
} else {
update_status(work->info, OSQP_PRIMAL_INFEASIBLE);
}
if (work->settings->scaling && !work->settings->scaled_termination) {
// Update infeasibility certificate
vec_ew_prod(work->scaling->E, work->delta_y, work->delta_y, work->data->m);
}
work->info->obj_val = OSQP_INFTY;
exitflag = 1;
}
else if (dual_inf_check) {
// Update final information
if (approximate) {
update_status(work->info, OSQP_DUAL_INFEASIBLE_INACCURATE);
} else {
update_status(work->info, OSQP_DUAL_INFEASIBLE);
}
if (work->settings->scaling && !work->settings->scaled_termination) {
// Update infeasibility certificate
vec_ew_prod(work->scaling->D, work->delta_x, work->delta_x, work->data->n);
}
work->info->obj_val = -OSQP_INFTY;
exitflag = 1;
}
return exitflag;
}
#ifndef EMBEDDED
c_int validate_data(const OSQPData *data) {
c_int j, ptr;
if (!data) {
# ifdef PRINTING
c_eprint("Missing data");
# endif
return 1;
}
if (!(data->P)) {
# ifdef PRINTING
c_eprint("Missing matrix P");
# endif
return 1;
}
if (!(data->A)) {
# ifdef PRINTING
c_eprint("Missing matrix A");
# endif
return 1;
}
if (!(data->q)) {
# ifdef PRINTING
c_eprint("Missing vector q");
# endif
return 1;
}
// General dimensions Tests
if ((data->n <= 0) || (data->m < 0)) {
# ifdef PRINTING
c_eprint("n must be positive and m nonnegative; n = %i, m = %i",
(int)data->n, (int)data->m);
# endif /* ifdef PRINTING */
return 1;
}
// Matrix P
if (data->P->m != data->n) {
# ifdef PRINTING
c_eprint("P does not have dimension n x n with n = %i", (int)data->n);
# endif /* ifdef PRINTING */
return 1;
}
if (data->P->m != data->P->n) {
# ifdef PRINTING
c_eprint("P is not square");
# endif /* ifdef PRINTING */
return 1;
}
for (j = 0; j < data->n; j++) { // COLUMN
for (ptr = data->P->p[j]; ptr < data->P->p[j + 1]; ptr++) {
if (data->P->i[ptr] > j) { // if ROW > COLUMN
# ifdef PRINTING
c_eprint("P is not upper triangular");
# endif /* ifdef PRINTING */
return 1;
}
}
}
// Matrix A
if ((data->A->m != data->m) || (data->A->n != data->n)) {
# ifdef PRINTING
c_eprint("A does not have dimension %i x %i", (int)data->m, (int)data->n);
# endif /* ifdef PRINTING */
return 1;
}
// Lower and upper bounds
for (j = 0; j < data->m; j++) {
if (data->l[j] > data->u[j]) {
# ifdef PRINTING
c_eprint("Lower bound at index %d is greater than upper bound: %.4e > %.4e",
(int)j, data->l[j], data->u[j]);
# endif /* ifdef PRINTING */
return 1;
}
}
// TODO: Complete with other checks
return 0;
}
c_int validate_linsys_solver(c_int linsys_solver) {
if ((linsys_solver != QDLDL_SOLVER) &&
(linsys_solver != MKL_PARDISO_SOLVER)) {
return 1;
}
// TODO: Add more solvers in case
// Valid solver
return 0;
}
c_int validate_settings(const OSQPSettings *settings) {
if (!settings) {
# ifdef PRINTING
c_eprint("Missing settings!");
# endif /* ifdef PRINTING */
return 1;
}
if (settings->scaling < 0) {
# ifdef PRINTING
c_eprint("scaling must be nonnegative");
# endif /* ifdef PRINTING */
return 1;
}
if ((settings->adaptive_rho != 0) && (settings->adaptive_rho != 1)) {
# ifdef PRINTING
c_eprint("adaptive_rho must be either 0 or 1");
# endif /* ifdef PRINTING */
return 1;
}
if (settings->adaptive_rho_interval < 0) {
# ifdef PRINTING
c_eprint("adaptive_rho_interval must be nonnegative");
# endif /* ifdef PRINTING */
return 1;
}
# ifdef PROFILING
if (settings->adaptive_rho_fraction <= 0) {
# ifdef PRINTING
c_eprint("adaptive_rho_fraction must be positive");
# endif /* ifdef PRINTING */
return 1;
}
# endif /* ifdef PROFILING */
if (settings->adaptive_rho_tolerance < 1.0) {
# ifdef PRINTING
c_eprint("adaptive_rho_tolerance must be >= 1");
# endif /* ifdef PRINTING */
return 1;
}
if (settings->polish_refine_iter < 0) {
# ifdef PRINTING
c_eprint("polish_refine_iter must be nonnegative");
# endif /* ifdef PRINTING */
return 1;
}
if (settings->rho <= 0.0) {
# ifdef PRINTING
c_eprint("rho must be positive");
# endif /* ifdef PRINTING */
return 1;
}
if (settings->sigma <= 0.0) {
# ifdef PRINTING
c_eprint("sigma must be positive");
# endif /* ifdef PRINTING */
return 1;
}
if (settings->delta <= 0.0) {
# ifdef PRINTING
c_eprint("delta must be positive");
# endif /* ifdef PRINTING */
return 1;
}
if (settings->max_iter <= 0) {
# ifdef PRINTING
c_eprint("max_iter must be positive");
# endif /* ifdef PRINTING */
return 1;
}
if (settings->eps_abs < 0.0) {
# ifdef PRINTING
c_eprint("eps_abs must be nonnegative");
# endif /* ifdef PRINTING */
return 1;
}
if (settings->eps_rel < 0.0) {
# ifdef PRINTING
c_eprint("eps_rel must be nonnegative");
# endif /* ifdef PRINTING */
return 1;
}
if ((settings->eps_rel == 0.0) &&
(settings->eps_abs == 0.0)) {
# ifdef PRINTING
c_eprint("at least one of eps_abs and eps_rel must be positive");
# endif /* ifdef PRINTING */
return 1;
}
if (settings->eps_prim_inf <= 0.0) {
# ifdef PRINTING
c_eprint("eps_prim_inf must be positive");
# endif /* ifdef PRINTING */
return 1;
}
if (settings->eps_dual_inf <= 0.0) {
# ifdef PRINTING
c_eprint("eps_dual_inf must be positive");
# endif /* ifdef PRINTING */
return 1;
}
if ((settings->alpha <= 0.0) ||
(settings->alpha >= 2.0)) {
# ifdef PRINTING
c_eprint("alpha must be strictly between 0 and 2");
# endif /* ifdef PRINTING */
return 1;
}
if (validate_linsys_solver(settings->linsys_solver)) {
# ifdef PRINTING
c_eprint("linsys_solver not recognized");
# endif /* ifdef PRINTING */
return 1;
}
if ((settings->verbose != 0) &&
(settings->verbose != 1)) {
# ifdef PRINTING
c_eprint("verbose must be either 0 or 1");
# endif /* ifdef PRINTING */
return 1;
}
if ((settings->scaled_termination != 0) &&
(settings->scaled_termination != 1)) {
# ifdef PRINTING
c_eprint("scaled_termination must be either 0 or 1");
# endif /* ifdef PRINTING */
return 1;
}
if (settings->check_termination < 0) {
# ifdef PRINTING
c_eprint("check_termination must be nonnegative");
# endif /* ifdef PRINTING */
return 1;
}
if ((settings->warm_start != 0) &&
(settings->warm_start != 1)) {
# ifdef PRINTING
c_eprint("warm_start must be either 0 or 1");
# endif /* ifdef PRINTING */
return 1;
}
# ifdef PROFILING
if (settings->time_limit < 0.0) {
# ifdef PRINTING
c_eprint("time_limit must be nonnegative\n");
# endif /* ifdef PRINTING */
return 1;
}
# endif /* ifdef PROFILING */
return 0;
}
#endif // #ifndef EMBEDDED