blob: 1d2378815138c7af9661808336c569e96f58b3ed [file] [log] [blame]
#include "polish.h"
#include "lin_alg.h"
#include "util.h"
#include "auxil.h"
#include "lin_sys.h"
#include "kkt.h"
#include "proj.h"
#include "osqp_error.h"
/**
* Form reduced matrix A that contains only rows that are active at the
* solution.
* Ared = vstack[Alow, Aupp]
* Active constraints are guessed from the primal and dual solution returned by
* the ADMM.
* @param work Workspace
* @return Number of rows in Ared, negative if error
*/
static c_int form_Ared(OSQPWorkspace *work) {
c_int j, ptr;
c_int Ared_nnz = 0;
// Initialize counters for active constraints
work->pol->n_low = 0;
work->pol->n_upp = 0;
/* Guess which linear constraints are lower-active, upper-active and free
* A_to_Alow[j] = -1 (if j-th row of A is not inserted in Alow)
* A_to_Alow[j] = i (if j-th row of A is inserted at i-th row of Alow)
* Aupp is formed in the equivalent way.
* Ared is formed by stacking vertically Alow and Aupp.
*/
for (j = 0; j < work->data->m; j++) {
if (work->z[j] - work->data->l[j] < -work->y[j]) { // lower-active
work->pol->Alow_to_A[work->pol->n_low] = j;
work->pol->A_to_Alow[j] = work->pol->n_low++;
} else {
work->pol->A_to_Alow[j] = -1;
}
}
for (j = 0; j < work->data->m; j++) {
if (work->data->u[j] - work->z[j] < work->y[j]) { // upper-active
work->pol->Aupp_to_A[work->pol->n_upp] = j;
work->pol->A_to_Aupp[j] = work->pol->n_upp++;
} else {
work->pol->A_to_Aupp[j] = -1;
}
}
// Check if there are no active constraints
if (work->pol->n_low + work->pol->n_upp == 0) {
// Form empty Ared
work->pol->Ared = csc_spalloc(0, work->data->n, 0, 1, 0);
if (!(work->pol->Ared)) return -1;
int_vec_set_scalar(work->pol->Ared->p, 0, work->data->n + 1);
return 0; // mred = 0
}
// Count number of elements in Ared
for (j = 0; j < work->data->A->p[work->data->A->n]; j++) {
if ((work->pol->A_to_Alow[work->data->A->i[j]] != -1) ||
(work->pol->A_to_Aupp[work->data->A->i[j]] != -1)) Ared_nnz++;
}
// Form Ared
// Ared = vstack[Alow, Aupp]
work->pol->Ared = csc_spalloc(work->pol->n_low + work->pol->n_upp,
work->data->n, Ared_nnz, 1, 0);
if (!(work->pol->Ared)) return -1;
Ared_nnz = 0; // counter
for (j = 0; j < work->data->n; j++) { // Cycle over columns of A
work->pol->Ared->p[j] = Ared_nnz;
for (ptr = work->data->A->p[j]; ptr < work->data->A->p[j + 1]; ptr++) {
// Cycle over elements in j-th column
if (work->pol->A_to_Alow[work->data->A->i[ptr]] != -1) {
// Lower-active rows of A
work->pol->Ared->i[Ared_nnz] =
work->pol->A_to_Alow[work->data->A->i[ptr]];
work->pol->Ared->x[Ared_nnz++] = work->data->A->x[ptr];
} else if (work->pol->A_to_Aupp[work->data->A->i[ptr]] != -1) {
// Upper-active rows of A
work->pol->Ared->i[Ared_nnz] = work->pol->A_to_Aupp[work->data->A->i[ptr]] \
+ work->pol->n_low;
work->pol->Ared->x[Ared_nnz++] = work->data->A->x[ptr];
}
}
}
// Update the last element in Ared->p
work->pol->Ared->p[work->data->n] = Ared_nnz;
// Return number of rows in Ared
return work->pol->n_low + work->pol->n_upp;
}
/**
* Form reduced right-hand side rhs_red = vstack[-q, l_low, u_upp]
* @param work Workspace
* @param rhs right-hand-side
* @return reduced rhs
*/
static void form_rhs_red(OSQPWorkspace *work, c_float *rhs) {
c_int j;
// Form the rhs of the reduced KKT linear system
for (j = 0; j < work->data->n; j++) { // -q
rhs[j] = -work->data->q[j];
}
for (j = 0; j < work->pol->n_low; j++) { // l_low
rhs[work->data->n + j] = work->data->l[work->pol->Alow_to_A[j]];
}
for (j = 0; j < work->pol->n_upp; j++) { // u_upp
rhs[work->data->n + work->pol->n_low + j] =
work->data->u[work->pol->Aupp_to_A[j]];
}
}
/**
* Perform iterative refinement on the polished solution:
* (repeat)
* 1. (K + dK) * dz = b - K*z
* 2. z <- z + dz
* @param work Solver workspace
* @param p Private variable for solving linear system
* @param z Initial z value
* @param b RHS of the linear system
* @return Exitflag
*/
static c_int iterative_refinement(OSQPWorkspace *work,
LinSysSolver *p,
c_float *z,
c_float *b) {
c_int i, j, n;
c_float *rhs;
if (work->settings->polish_refine_iter > 0) {
// Assign dimension n
n = work->data->n + work->pol->Ared->m;
// Allocate rhs vector
rhs = (c_float *)c_malloc(sizeof(c_float) * n);
if (!rhs) {
return osqp_error(OSQP_MEM_ALLOC_ERROR);
} else {
for (i = 0; i < work->settings->polish_refine_iter; i++) {
// Form the RHS for the iterative refinement: b - K*z
prea_vec_copy(b, rhs, n);
// Upper Part: R^{n}
// -= Px (upper triang)
mat_vec(work->data->P, z, rhs, -1);
// -= Px (lower triang)
mat_tpose_vec(work->data->P, z, rhs, -1, 1);
// -= Ared'*y_red
mat_tpose_vec(work->pol->Ared, z + work->data->n, rhs, -1, 0);
// Lower Part: R^{m}
mat_vec(work->pol->Ared, z, rhs + work->data->n, -1);
// Solve linear system. Store solution in rhs
p->solve(p, rhs);
// Update solution
for (j = 0; j < n; j++) {
z[j] += rhs[j];
}
}
}
if (rhs) c_free(rhs);
}
return 0;
}
/**
* Compute dual variable y from yred
* @param work Workspace
* @param yred Dual variables associated to active constraints
*/
static void get_ypol_from_yred(OSQPWorkspace *work, c_float *yred) {
c_int j;
// If there are no active constraints
if (work->pol->n_low + work->pol->n_upp == 0) {
vec_set_scalar(work->pol->y, 0., work->data->m);
return;
}
// NB: yred = vstack[ylow, yupp]
for (j = 0; j < work->data->m; j++) {
if (work->pol->A_to_Alow[j] != -1) {
// lower-active
work->pol->y[j] = yred[work->pol->A_to_Alow[j]];
} else if (work->pol->A_to_Aupp[j] != -1) {
// upper-active
work->pol->y[j] = yred[work->pol->A_to_Aupp[j] + work->pol->n_low];
} else {
// inactive
work->pol->y[j] = 0.0;
}
}
}
c_int polish(OSQPWorkspace *work) {
c_int mred, polish_successful, exitflag;
c_float *rhs_red;
LinSysSolver *plsh;
c_float *pol_sol; // Polished solution
#ifdef PROFILING
osqp_tic(work->timer); // Start timer
#endif /* ifdef PROFILING */
// Form Ared by assuming the active constraints and store in work->pol->Ared
mred = form_Ared(work);
if (mred < 0) { // work->pol->red = OSQP_NULL
// Polishing failed
work->info->status_polish = -1;
return -1;
}
// Form and factorize reduced KKT
exitflag = init_linsys_solver(&plsh, work->data->P, work->pol->Ared,
work->settings->delta, OSQP_NULL,
work->settings->linsys_solver, 1);
if (exitflag) {
// Polishing failed
work->info->status_polish = -1;
// Memory clean-up
if (work->pol->Ared) csc_spfree(work->pol->Ared);
return 1;
}
// Form reduced right-hand side rhs_red
rhs_red = c_malloc(sizeof(c_float) * (work->data->n + mred));
if (!rhs_red) {
// Polishing failed
work->info->status_polish = -1;
// Memory clean-up
csc_spfree(work->pol->Ared);
return -1;
}
form_rhs_red(work, rhs_red);
pol_sol = vec_copy(rhs_red, work->data->n + mred);
if (!pol_sol) {
// Polishing failed
work->info->status_polish = -1;
// Memory clean-up
csc_spfree(work->pol->Ared);
c_free(rhs_red);
return -1;
}
// Solve the reduced KKT system
plsh->solve(plsh, pol_sol);
// Perform iterative refinement to compensate for the regularization error
exitflag = iterative_refinement(work, plsh, pol_sol, rhs_red);
if (exitflag) {
// Polishing failed
work->info->status_polish = -1;
// Memory clean-up
csc_spfree(work->pol->Ared);
c_free(rhs_red);
c_free(pol_sol);
return -1;
}
// Store the polished solution (x,z,y)
prea_vec_copy(pol_sol, work->pol->x, work->data->n); // pol->x
mat_vec(work->data->A, work->pol->x, work->pol->z, 0); // pol->z
get_ypol_from_yred(work, pol_sol + work->data->n); // pol->y
// Ensure (z,y) satisfies normal cone constraint
project_normalcone(work, work->pol->z, work->pol->y);
// Compute primal and dual residuals at the polished solution
update_info(work, 0, 1, 1);
// Check if polish was successful
polish_successful = (work->pol->pri_res < work->info->pri_res &&
work->pol->dua_res < work->info->dua_res) || // Residuals
// are
// reduced
(work->pol->pri_res < work->info->pri_res &&
work->info->dua_res < 1e-10) || // Dual
// residual
// already
// tiny
(work->pol->dua_res < work->info->dua_res &&
work->info->pri_res < 1e-10); // Primal
// residual
// already
// tiny
if (polish_successful) {
// Update solver information
work->info->obj_val = work->pol->obj_val;
work->info->pri_res = work->pol->pri_res;
work->info->dua_res = work->pol->dua_res;
work->info->status_polish = 1;
// Update (x, z, y) in ADMM iterations
// NB: z needed for warm starting
prea_vec_copy(work->pol->x, work->x, work->data->n);
prea_vec_copy(work->pol->z, work->z, work->data->m);
prea_vec_copy(work->pol->y, work->y, work->data->m);
// Print summary
#ifdef PRINTING
if (work->settings->verbose) print_polish(work);
#endif /* ifdef PRINTING */
} else { // Polishing failed
work->info->status_polish = -1;
// TODO: Try to find a better solution on the line connecting ADMM
// and polished solution
}
// Memory clean-up
plsh->free(plsh);
// Checks that they are not NULL are already performed earlier
csc_spfree(work->pol->Ared);
c_free(rhs_red);
c_free(pol_sol);
return 0;
}