1 2 module polish; 3 4 version(EMBEDDED){} 5 else { 6 7 nothrow @nogc extern(C): 8 9 import types; 10 import glob_opts; 11 12 import lin_alg; 13 import util; 14 import auxil; 15 import lin_sys; 16 import kkt; 17 import proj; 18 import error; 19 import cs; 20 import constants; // for OSQP_NULL 21 22 /** 23 * Form reduced matrix A that contains only rows that are active at the 24 * solution. 25 * Ared = vstack[Alow, Aupp] 26 * Active constraints are guessed from the primal and dual solution returned by 27 * the ADMM. 28 * @param work Workspace 29 * @return Number of rows in Ared, negative if error 30 */ 31 static c_int form_Ared(OSQPWorkspace *work) { 32 c_int j, ptr; 33 c_int Ared_nnz = 0; 34 35 // Initialize counters for active constraints 36 work.pol.n_low = 0; 37 work.pol.n_upp = 0; 38 39 /* Guess which linear constraints are lower-active, upper-active and free 40 * A_to_Alow[j] = -1 (if j-th row of A is not inserted in Alow) 41 * A_to_Alow[j] = i (if j-th row of A is inserted at i-th row of Alow) 42 * Aupp is formed in the equivalent way. 43 * Ared is formed by stacking vertically Alow and Aupp. 44 */ 45 for (j = 0; j < work.data.m; j++) { 46 if (work.z[j] - work.data.l[j] < -work.y[j]) { // lower-active 47 work.pol.Alow_to_A[work.pol.n_low] = j; 48 work.pol.A_to_Alow[j] = work.pol.n_low++; 49 } else { 50 work.pol.A_to_Alow[j] = -1; 51 } 52 } 53 54 for (j = 0; j < work.data.m; j++) { 55 if (work.data.u[j] - work.z[j] < work.y[j]) { // upper-active 56 work.pol.Aupp_to_A[work.pol.n_upp] = j; 57 work.pol.A_to_Aupp[j] = work.pol.n_upp++; 58 } else { 59 work.pol.A_to_Aupp[j] = -1; 60 } 61 } 62 63 // Check if there are no active constraints 64 if (work.pol.n_low + work.pol.n_upp == 0) { 65 // Form empty Ared 66 work.pol.Ared = csc_spalloc(0, work.data.n, 0, 1, 0); 67 if (!(work.pol.Ared)) return -1; 68 int_vec_set_scalar(work.pol.Ared.p, 0, work.data.n + 1); 69 return 0; // mred = 0 70 } 71 72 // Count number of elements in Ared 73 for (j = 0; j < work.data.A.p[work.data.A.n]; j++) { 74 if ((work.pol.A_to_Alow[work.data.A.i[j]] != -1) || 75 (work.pol.A_to_Aupp[work.data.A.i[j]] != -1)) Ared_nnz++; 76 } 77 78 // Form Ared 79 // Ared = vstack[Alow, Aupp] 80 work.pol.Ared = csc_spalloc(work.pol.n_low + work.pol.n_upp, 81 work.data.n, Ared_nnz, 1, 0); 82 if (!(work.pol.Ared)) return -1; 83 Ared_nnz = 0; // counter 84 85 for (j = 0; j < work.data.n; j++) { // Cycle over columns of A 86 work.pol.Ared.p[j] = Ared_nnz; 87 88 for (ptr = work.data.A.p[j]; ptr < work.data.A.p[j + 1]; ptr++) { 89 // Cycle over elements in j-th column 90 if (work.pol.A_to_Alow[work.data.A.i[ptr]] != -1) { 91 // Lower-active rows of A 92 work.pol.Ared.i[Ared_nnz] = 93 work.pol.A_to_Alow[work.data.A.i[ptr]]; 94 work.pol.Ared.x[Ared_nnz++] = work.data.A.x[ptr]; 95 } else if (work.pol.A_to_Aupp[work.data.A.i[ptr]] != -1) { 96 // Upper-active rows of A 97 work.pol.Ared.i[Ared_nnz] = work.pol.A_to_Aupp[work.data.A.i[ptr]] + work.pol.n_low; 98 work.pol.Ared.x[Ared_nnz++] = work.data.A.x[ptr]; 99 } 100 } 101 } 102 103 // Update the last element in Ared.p 104 work.pol.Ared.p[work.data.n] = Ared_nnz; 105 106 // Return number of rows in Ared 107 return work.pol.n_low + work.pol.n_upp; 108 } 109 110 /** 111 * Form reduced right-hand side rhs_red = vstack[-q, l_low, u_upp] 112 * @param work Workspace 113 * @param rhs right-hand-side 114 * @return reduced rhs 115 */ 116 static void form_rhs_red(OSQPWorkspace *work, c_float *rhs) { 117 c_int j; 118 119 // Form the rhs of the reduced KKT linear system 120 for (j = 0; j < work.data.n; j++) { // -q 121 rhs[j] = -work.data.q[j]; 122 } 123 124 for (j = 0; j < work.pol.n_low; j++) { // l_low 125 rhs[work.data.n + j] = work.data.l[work.pol.Alow_to_A[j]]; 126 } 127 128 for (j = 0; j < work.pol.n_upp; j++) { // u_upp 129 rhs[work.data.n + work.pol.n_low + j] = 130 work.data.u[work.pol.Aupp_to_A[j]]; 131 } 132 } 133 134 /** 135 * Perform iterative refinement on the polished solution: 136 * (repeat) 137 * 1. (K + dK) * dz = b - K*z 138 * 2. z <- z + dz 139 * @param work Solver workspace 140 * @param p Private variable for solving linear system 141 * @param z Initial z value 142 * @param b RHS of the linear system 143 * @return Exitflag 144 */ 145 static c_int iterative_refinement(OSQPWorkspace *work, 146 LinSysSolver *p, 147 c_float *z, 148 c_float *b) { 149 c_int i, j, n; 150 c_float *rhs; 151 152 if (work.settings.polish_refine_iter > 0) { 153 154 // Assign dimension n 155 n = work.data.n + work.pol.Ared.m; 156 157 // Allocate rhs vector 158 rhs = cast(c_float *)c_malloc((c_float.sizeof) * n); 159 160 if (!rhs) { 161 return osqp_error(cast(osqp_error_type)OSQP_MEM_ALLOC_ERROR, __FUNCTION__); 162 } else { 163 for (i = 0; i < work.settings.polish_refine_iter; i++) { 164 // Form the RHS for the iterative refinement: b - K*z 165 prea_vec_copy(b, rhs, n); 166 167 // Upper Part: R^{n} 168 // -= Px (upper triang) 169 mat_vec(work.data.P, z, rhs, -1); 170 171 // -= Px (lower triang) 172 mat_tpose_vec(work.data.P, z, rhs, -1, 1); 173 174 // -= Ared'*y_red 175 mat_tpose_vec(work.pol.Ared, z + work.data.n, rhs, -1, 0); 176 177 // Lower Part: R^{m} 178 mat_vec(work.pol.Ared, z, rhs + work.data.n, -1); 179 180 // Solve linear system. Store solution in rhs 181 p.solve(p, rhs); 182 183 // Update solution 184 for (j = 0; j < n; j++) { 185 z[j] += rhs[j]; 186 } 187 } 188 } 189 if (rhs) c_free(rhs); 190 } 191 return 0; 192 } 193 194 /** 195 * Compute dual variable y from yred 196 * @param work Workspace 197 * @param yred Dual variables associated to active constraints 198 */ 199 static void get_ypol_from_yred(OSQPWorkspace *work, c_float *yred) { 200 c_int j; 201 202 // If there are no active constraints 203 if (work.pol.n_low + work.pol.n_upp == 0) { 204 vec_set_scalar(work.pol.y, 0., work.data.m); 205 return; 206 } 207 208 // NB: yred = vstack[ylow, yupp] 209 for (j = 0; j < work.data.m; j++) { 210 if (work.pol.A_to_Alow[j] != -1) { 211 // lower-active 212 work.pol.y[j] = yred[work.pol.A_to_Alow[j]]; 213 } else if (work.pol.A_to_Aupp[j] != -1) { 214 // upper-active 215 work.pol.y[j] = yred[work.pol.A_to_Aupp[j] + work.pol.n_low]; 216 } else { 217 // inactive 218 work.pol.y[j] = 0.0; 219 } 220 } 221 } 222 223 c_int polish(OSQPWorkspace *work) { 224 c_int mred; 225 c_int polish_successful; 226 c_int exitflag; 227 c_float *rhs_red; 228 LinSysSolver *plsh; 229 c_float *pol_sol; // Polished solution 230 231 version(PROFILING){ 232 osqp_tic(work.timer); // Start timer 233 }/* ifdef PROFILING */ 234 235 // Form Ared by assuming the active constraints and store in work.pol.Ared 236 mred = form_Ared(work); 237 if (mred < 0) { // work.pol.red = OSQP_NULL 238 // Polishing failed 239 work.info.status_polish = -1; 240 241 return -1; 242 } 243 244 // Form and factorize reduced KKT 245 exitflag = init_linsys_solver(&plsh, work.data.P, work.pol.Ared, 246 work.settings.delta, OSQP_NULL, 247 work.settings.linsys_solver, 1); 248 249 if (exitflag) { 250 // Polishing failed 251 work.info.status_polish = -1; 252 253 // Memory clean-up 254 if (work.pol.Ared) csc_spfree(work.pol.Ared); 255 256 return 1; 257 } 258 259 // Form reduced right-hand side rhs_red 260 rhs_red = cast(c_float*)c_malloc((c_float.sizeof) * (work.data.n + mred)); 261 if (!rhs_red) { 262 // Polishing failed 263 work.info.status_polish = -1; 264 265 // Memory clean-up 266 csc_spfree(work.pol.Ared); 267 268 return -1; 269 } 270 form_rhs_red(work, rhs_red); 271 272 pol_sol = vec_copy(rhs_red, work.data.n + mred); 273 if (!pol_sol) { 274 // Polishing failed 275 work.info.status_polish = -1; 276 277 // Memory clean-up 278 csc_spfree(work.pol.Ared); 279 c_free(rhs_red); 280 281 return -1; 282 } 283 284 // Solve the reduced KKT system 285 plsh.solve(plsh, pol_sol); 286 287 // Perform iterative refinement to compensate for the regularization error 288 exitflag = iterative_refinement(work, plsh, pol_sol, rhs_red); 289 290 if (exitflag) { 291 // Polishing failed 292 work.info.status_polish = -1; 293 294 // Memory clean-up 295 csc_spfree(work.pol.Ared); 296 c_free(rhs_red); 297 c_free(pol_sol); 298 299 return -1; 300 } 301 302 // Store the polished solution (x,z,y) 303 prea_vec_copy(pol_sol, work.pol.x, work.data.n); // pol.x 304 mat_vec(work.data.A, work.pol.x, work.pol.z, 0); // pol.z 305 get_ypol_from_yred(work, pol_sol + work.data.n); // pol.y 306 307 // Ensure (z,y) satisfies normal cone constraint 308 project_normalcone(work, work.pol.z, work.pol.y); 309 310 // Compute primal and dual residuals at the polished solution 311 update_info(work, 0, 1, 1); 312 313 // Check if polish was successful 314 polish_successful = (work.pol.pri_res < work.info.pri_res && 315 work.pol.dua_res < work.info.dua_res) || // Residuals 316 // are 317 // reduced 318 (work.pol.pri_res < work.info.pri_res && 319 work.info.dua_res < 1e-10) || // Dual 320 // residual 321 // already 322 // tiny 323 (work.pol.dua_res < work.info.dua_res && 324 work.info.pri_res < 1e-10); // Primal 325 // residual 326 // already 327 // tiny 328 329 if (polish_successful) { 330 // Update solver information 331 work.info.obj_val = work.pol.obj_val; 332 work.info.pri_res = work.pol.pri_res; 333 work.info.dua_res = work.pol.dua_res; 334 work.info.status_polish = 1; 335 336 // Update (x, z, y) in ADMM iterations 337 // NB: z needed for warm starting 338 prea_vec_copy(work.pol.x, work.x, work.data.n); 339 prea_vec_copy(work.pol.z, work.z, work.data.m); 340 prea_vec_copy(work.pol.y, work.y, work.data.m); 341 342 // Print summary 343 version(PRINTING){ 344 if (work.settings.verbose) print_polish(work); 345 } /* ifdef PRINTING */ 346 } else { // Polishing failed 347 work.info.status_polish = -1; 348 349 // TODO: Try to find a better solution on the line connecting ADMM 350 // and polished solution 351 } 352 353 // Memory clean-up 354 plsh.free(plsh); 355 356 // Checks that they are not NULL are already performed earlier 357 csc_spfree(work.pol.Ared); 358 c_free(rhs_red); 359 c_free(pol_sol); 360 361 return 0; 362 } 363 364 } // !EMBEDDED