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