Mercurial > octave-nkf
comparison scripts/optimization/qp.m @ 20298:5c088348fddb
qp.m: Overhaul function (fixes bug #45324).
* qp.m: Put input validation first. Validate both x0 and q are vectors.
Transform x0 and q to column vectors for rest of computation (bug #45324).
Use double quotes instead of single to match Octave coding conventions.
Re-wrap multi-line comments to break at meaningful boundaries. Use isargout
to avoid calculating unnecessary outputs.
author | Rik <rik@octave.org> |
---|---|
date | Mon, 15 Jun 2015 21:56:34 +0200 |
parents | 83792dd9bcc1 |
children |
comparison
equal
deleted
inserted
replaced
20293:530803d4f65f | 20298:5c088348fddb |
---|---|
114 ## PKG_ADD: ## Discard result to avoid polluting workspace with ans at startup. | 114 ## PKG_ADD: ## Discard result to avoid polluting workspace with ans at startup. |
115 ## PKG_ADD: [~] = __all_opts__ ("qp"); | 115 ## PKG_ADD: [~] = __all_opts__ ("qp"); |
116 | 116 |
117 function [x, obj, INFO, lambda] = qp (x0, H, varargin) | 117 function [x, obj, INFO, lambda] = qp (x0, H, varargin) |
118 | 118 |
119 nargs = nargin; | 119 if (nargin == 1 && ischar (x0) && strcmp (x0, "defaults")) |
120 | |
121 if (nargin == 1 && ischar (x0) && strcmp (x0, 'defaults')) | |
122 x = optimset ("MaxIter", 200); | 120 x = optimset ("MaxIter", 200); |
123 return; | 121 return; |
124 endif | 122 endif |
125 | 123 |
124 nargs = nargin; | |
126 if (nargs > 2 && isstruct (varargin{end})) | 125 if (nargs > 2 && isstruct (varargin{end})) |
127 options = varargin{end}; | 126 options = varargin{end}; |
128 nargs--; | 127 nargs--; |
129 else | 128 else |
130 options = struct (); | 129 options = struct (); |
130 endif | |
131 | |
132 if (nargs != 2 && nargs != 3 && nargs != 5 && nargs != 7 && nargs != 10) | |
133 print_usage (); | |
131 endif | 134 endif |
132 | 135 |
133 if (nargs >= 3) | 136 if (nargs >= 3) |
134 q = varargin{1}; | 137 q = varargin{1}; |
135 else | 138 else |
160 A_lb = []; | 163 A_lb = []; |
161 A_in = []; | 164 A_in = []; |
162 A_ub = []; | 165 A_ub = []; |
163 endif | 166 endif |
164 | 167 |
165 if (nargs == 2 || nargs == 3 || nargs == 5 || nargs == 7 || nargs == 10) | 168 maxit = optimget (options, "MaxIter", 200); |
166 | 169 |
167 maxit = optimget (options, "MaxIter", 200); | 170 ## Validate the quadratic penalty. |
168 | 171 if (! issquare (H)) |
169 ## Checking the quadratic penalty | 172 error ("qp: quadratic penalty matrix must be square"); |
170 if (! issquare (H)) | 173 elseif (! ishermitian (H)) |
171 error ("qp: quadratic penalty matrix not square"); | 174 ## warning ("qp: quadratic penalty matrix not hermitian"); |
172 elseif (! ishermitian (H)) | 175 H = (H + H')/2; |
173 ## warning ("qp: quadratic penalty matrix not hermitian"); | 176 endif |
174 H = (H + H')/2; | 177 n = rows (H); |
175 endif | 178 |
176 n = rows (H); | 179 ## Validate the initial guess. |
177 | 180 ## If empty it is resized to the right dimension and filled with 0. |
178 ## Checking the initial guess (if empty it is resized to the | 181 if (isempty (x0)) |
179 ## right dimension and filled with 0) | 182 x0 = zeros (n, 1); |
180 if (isempty (x0)) | 183 else |
181 x0 = zeros (n, 1); | 184 if (! isvector (x0)) |
185 error ("qp: the initial guess X0 must be a vector"); | |
182 elseif (numel (x0) != n) | 186 elseif (numel (x0) != n) |
183 error ("qp: the initial guess has incorrect length"); | 187 error ("qp: the initial guess X0 has incorrect length"); |
184 endif | 188 endif |
185 | 189 x0 = x0(:); # always use column vector. |
186 ## Linear penalty. | 190 endif |
187 if (isempty (q)) | 191 |
188 q = zeros (n, 1); | 192 ## Validate linear penalty. |
193 if (isempty (q)) | |
194 q = zeros (n, 1); | |
195 else | |
196 if (! isvector (q)) | |
197 error ("qp: Q must be a vector"); | |
189 elseif (numel (q) != n) | 198 elseif (numel (q) != n) |
190 error ("qp: the linear term has incorrect length"); | 199 error ("qp: Q has incorrect length"); |
191 endif | 200 endif |
192 | 201 q = q(:); # always use column vector. |
193 ## Equality constraint matrices | 202 endif |
194 if (isempty (A) || isempty (b)) | 203 |
195 A = zeros (0, n); | 204 ## Validate equality constraint matrices. |
196 b = zeros (0, 1); | 205 if (isempty (A) || isempty (b)) |
197 n_eq = 0; | 206 A = zeros (0, n); |
207 b = zeros (0, 1); | |
208 n_eq = 0; | |
209 else | |
210 [n_eq, n1] = size (A); | |
211 if (n1 != n) | |
212 error ("qp: equality constraint matrix has incorrect column dimension"); | |
213 endif | |
214 if (numel (b) != n_eq) | |
215 error ("qp: equality constraint matrix and vector have inconsistent dimensions"); | |
216 endif | |
217 endif | |
218 | |
219 ## Validate bound constraints. | |
220 Ain = zeros (0, n); | |
221 bin = zeros (0, 1); | |
222 n_in = 0; | |
223 if (nargs > 5) | |
224 if (! isempty (lb)) | |
225 if (numel (lb) != n) | |
226 error ("qp: lower bound LB has incorrect length"); | |
227 elseif (isempty (ub)) | |
228 Ain = [Ain; eye(n)]; | |
229 bin = [bin; lb]; | |
230 endif | |
231 endif | |
232 | |
233 if (! isempty (ub)) | |
234 if (numel (ub) != n) | |
235 error ("qp: upper bound UB has incorrect length"); | |
236 elseif (isempty (lb)) | |
237 Ain = [Ain; -eye(n)]; | |
238 bin = [bin; -ub]; | |
239 endif | |
240 endif | |
241 | |
242 if (! isempty (lb) && ! isempty (ub)) | |
243 rtol = sqrt (eps); | |
244 for i = 1:n | |
245 if (abs (lb (i) - ub(i)) < rtol*(1 + max (abs (lb(i) + ub(i))))) | |
246 ## These are actually an equality constraint | |
247 tmprow = zeros (1,n); | |
248 tmprow(i) = 1; | |
249 A = [A;tmprow]; | |
250 b = [b; 0.5*(lb(i) + ub(i))]; | |
251 n_eq += 1; | |
252 else | |
253 tmprow = zeros (1,n); | |
254 tmprow(i) = 1; | |
255 Ain = [Ain; tmprow; -tmprow]; | |
256 bin = [bin; lb(i); -ub(i)]; | |
257 n_in += 2; | |
258 endif | |
259 endfor | |
260 endif | |
261 endif | |
262 | |
263 ## Validate inequality constraints. | |
264 if (nargs > 7) | |
265 [dimA_in, n1] = size (A_in); | |
266 if (n1 != n) | |
267 error ("qp: inequality constraint matrix has incorrect column dimension"); | |
198 else | 268 else |
199 [n_eq, n1] = size (A); | 269 if (! isempty (A_lb)) |
200 if (n1 != n) | 270 if (numel (A_lb) != dimA_in) |
201 error ("qp: equality constraint matrix has incorrect column dimension"); | 271 error ("qp: inequality constraint matrix and lower bound vector are inconsistent"); |
202 endif | 272 elseif (isempty (A_ub)) |
203 if (numel (b) != n_eq) | 273 Ain = [Ain; A_in]; |
204 error ("qp: equality constraint matrix and vector have inconsistent dimension"); | 274 bin = [bin; A_lb]; |
205 endif | |
206 endif | |
207 | |
208 ## Bound constraints | |
209 Ain = zeros (0, n); | |
210 bin = zeros (0, 1); | |
211 n_in = 0; | |
212 if (nargs > 5) | |
213 if (! isempty (lb)) | |
214 if (numel (lb) != n) | |
215 error ("qp: lower bound has incorrect length"); | |
216 elseif (isempty (ub)) | |
217 Ain = [Ain; eye(n)]; | |
218 bin = [bin; lb]; | |
219 endif | 275 endif |
220 endif | 276 endif |
221 | 277 if (! isempty (A_ub)) |
222 if (! isempty (ub)) | 278 if (numel (A_ub) != dimA_in) |
223 if (numel (ub) != n) | 279 error ("qp: inequality constraint matrix and upper bound vector are inconsistent"); |
224 error ("qp: upper bound has incorrect length"); | 280 elseif (isempty (A_lb)) |
225 elseif (isempty (lb)) | 281 Ain = [Ain; -A_in]; |
226 Ain = [Ain; -eye(n)]; | 282 bin = [bin; -A_ub]; |
227 bin = [bin; -ub]; | |
228 endif | 283 endif |
229 endif | 284 endif |
230 | 285 |
231 if (! isempty (lb) && ! isempty (ub)) | 286 if (! isempty (A_lb) && ! isempty (A_ub)) |
232 rtol = sqrt (eps); | 287 rtol = sqrt (eps); |
233 for i = 1:n | 288 for i = 1:dimA_in |
234 if (abs (lb (i) - ub(i)) < rtol*(1 + max (abs (lb(i) + ub(i))))) | 289 if (abs (A_lb(i) - A_ub(i)) |
290 < rtol*(1 + max (abs (A_lb(i) + A_ub(i))))) | |
235 ## These are actually an equality constraint | 291 ## These are actually an equality constraint |
236 tmprow = zeros (1,n); | 292 tmprow = A_in(i,:); |
237 tmprow(i) = 1; | |
238 A = [A;tmprow]; | 293 A = [A;tmprow]; |
239 b = [b; 0.5*(lb(i) + ub(i))]; | 294 b = [b; 0.5*(A_lb(i) + A_ub(i))]; |
240 n_eq += 1; | 295 n_eq += 1; |
241 else | 296 else |
242 tmprow = zeros (1,n); | 297 tmprow = A_in(i,:); |
243 tmprow(i) = 1; | |
244 Ain = [Ain; tmprow; -tmprow]; | 298 Ain = [Ain; tmprow; -tmprow]; |
245 bin = [bin; lb(i); -ub(i)]; | 299 bin = [bin; A_lb(i); -A_ub(i)]; |
246 n_in += 2; | 300 n_in += 2; |
247 endif | 301 endif |
248 endfor | 302 endfor |
249 endif | 303 endif |
250 endif | 304 endif |
251 | 305 endif |
252 ## Inequality constraints | 306 |
253 if (nargs > 7) | 307 ## Now we should have the following QP: |
254 [dimA_in, n1] = size (A_in); | 308 ## |
255 if (n1 != n) | 309 ## min_x 0.5*x'*H*x + x'*q |
256 error ("qp: inequality constraint matrix has incorrect column dimension"); | 310 ## s.t. A*x = b |
257 else | 311 ## Ain*x >= bin |
258 if (! isempty (A_lb)) | 312 |
259 if (numel (A_lb) != dimA_in) | 313 ## Discard inequality constraints that have -Inf bounds since those |
260 error ("qp: inequality constraint matrix and lower bound vector inconsistent"); | 314 ## will never be active. |
261 elseif (isempty (A_ub)) | 315 idx = (bin == -Inf); |
262 Ain = [Ain; A_in]; | 316 |
263 bin = [bin; A_lb]; | 317 bin(idx) = []; |
318 Ain(idx,:) = []; | |
319 | |
320 n_in = numel (bin); | |
321 | |
322 ## Check if the initial guess is feasible. | |
323 if (isa (x0, "single") || isa (H, "single") || isa (q, "single") | |
324 || isa (A, "single") || isa (b, "single")) | |
325 rtol = sqrt (eps ("single")); | |
326 else | |
327 rtol = sqrt (eps); | |
328 endif | |
329 | |
330 eq_infeasible = (n_eq > 0 && norm (A*x0-b) > rtol*(1+abs (b))); | |
331 in_infeasible = (n_in > 0 && any (Ain*x0-bin < -rtol*(1+abs (bin)))); | |
332 | |
333 info = 0; | |
334 if (eq_infeasible || in_infeasible) | |
335 ## The initial guess is not feasible. | |
336 ## First, define an xbar that is feasible with respect to the | |
337 ## equality constraints. | |
338 if (eq_infeasible) | |
339 if (rank (A) < n_eq) | |
340 error ("qp: equality constraint matrix must be full row rank"); | |
341 endif | |
342 xbar = pinv (A) * b; | |
343 else | |
344 xbar = x0; | |
345 endif | |
346 | |
347 ## Second, check that xbar is also feasible with respect to the | |
348 ## inequality constraints. | |
349 if (n_in > 0) | |
350 res = Ain * xbar - bin; | |
351 if (any (res < -rtol * (1 + abs (bin)))) | |
352 ## xbar is not feasible with respect to the inequality constraints. | |
353 ## Compute a step in the null space of the equality constraints, | |
354 ## by solving a QP. If the slack is small, we have a feasible initial | |
355 ## guess. Otherwise, the problem is infeasible. | |
356 if (n_eq > 0) | |
357 Z = null (A); | |
358 if (isempty (Z)) | |
359 ## The problem is infeasible because A is square and full rank, | |
360 ## but xbar is not feasible. | |
361 info = 6; | |
264 endif | 362 endif |
265 endif | 363 endif |
266 if (! isempty (A_ub)) | 364 |
267 if (numel (A_ub) != dimA_in) | 365 if (info != 6) |
268 error ("qp: inequality constraint matrix and upper bound vector inconsistent"); | 366 ## Solve an LP with additional slack variables |
269 elseif (isempty (A_lb)) | 367 ## to find a feasible starting point. |
270 Ain = [Ain; -A_in]; | 368 gamma = eye (n_in); |
271 bin = [bin; -A_ub]; | 369 if (n_eq > 0) |
370 Atmp = [Ain*Z, gamma]; | |
371 btmp = -res; | |
372 else | |
373 Atmp = [Ain, gamma]; | |
374 btmp = bin; | |
272 endif | 375 endif |
273 endif | 376 ctmp = [zeros(n-n_eq, 1); ones(n_in, 1)]; |
274 | 377 lb = [-Inf(n-n_eq,1); zeros(n_in,1)]; |
275 if (! isempty (A_lb) && ! isempty (A_ub)) | 378 ub = []; |
276 rtol = sqrt (eps); | 379 ctype = repmat ("L", n_in, 1); |
277 for i = 1:dimA_in | 380 [P, dummy, status] = glpk (ctmp, Atmp, btmp, lb, ub, ctype); |
278 if (abs (A_lb(i) - A_ub(i)) | 381 if ((status == 0) |
279 < rtol*(1 + max (abs (A_lb(i) + A_ub(i))))) | 382 && all (abs (P(n-n_eq+1:end)) < rtol * (1 + norm (btmp)))) |
280 ## These are actually an equality constraint | 383 ## We found a feasible starting point |
281 tmprow = A_in(i,:); | 384 if (n_eq > 0) |
282 A = [A;tmprow]; | 385 x0 = xbar + Z*P(1:n-n_eq); |
283 b = [b; 0.5*(A_lb(i) + A_ub(i))]; | |
284 n_eq += 1; | |
285 else | 386 else |
286 tmprow = A_in(i,:); | 387 x0 = P(1:n); |
287 Ain = [Ain; tmprow; -tmprow]; | |
288 bin = [bin; A_lb(i); -A_ub(i)]; | |
289 n_in += 2; | |
290 endif | 388 endif |
291 endfor | 389 else |
292 endif | 390 ## The problem is infeasible |
293 endif | 391 info = 6; |
294 endif | |
295 | |
296 ## Now we should have the following QP: | |
297 ## | |
298 ## min_x 0.5*x'*H*x + x'*q | |
299 ## s.t. A*x = b | |
300 ## Ain*x >= bin | |
301 | |
302 ## Discard inequality constraints that have -Inf bounds since those | |
303 ## will never be active. | |
304 idx = isinf (bin) & bin < 0; | |
305 | |
306 bin(idx) = []; | |
307 Ain(idx,:) = []; | |
308 | |
309 n_in = numel (bin); | |
310 | |
311 ## Check if the initial guess is feasible. | |
312 if (isa (x0, "single") || isa (H, "single") || isa (q, "single") | |
313 || isa (A, "single") || isa (b, "single")) | |
314 rtol = sqrt (eps ("single")); | |
315 else | |
316 rtol = sqrt (eps); | |
317 endif | |
318 | |
319 eq_infeasible = (n_eq > 0 && norm (A*x0-b) > rtol*(1+abs (b))); | |
320 in_infeasible = (n_in > 0 && any (Ain*x0-bin < -rtol*(1+abs (bin)))); | |
321 | |
322 info = 0; | |
323 if (eq_infeasible || in_infeasible) | |
324 ## The initial guess is not feasible. | |
325 ## First define xbar that is feasible with respect to the equality | |
326 ## constraints. | |
327 if (eq_infeasible) | |
328 if (rank (A) < n_eq) | |
329 error ("qp: equality constraint matrix must be full row rank"); | |
330 endif | |
331 xbar = pinv (A) * b; | |
332 else | |
333 xbar = x0; | |
334 endif | |
335 | |
336 ## Check if xbar is feasible with respect to the inequality | |
337 ## constraints also. | |
338 if (n_in > 0) | |
339 res = Ain * xbar - bin; | |
340 if (any (res < -rtol * (1 + abs (bin)))) | |
341 ## xbar is not feasible with respect to the inequality | |
342 ## constraints. Compute a step in the null space of the | |
343 ## equality constraints, by solving a QP. If the slack is | |
344 ## small, we have a feasible initial guess. Otherwise, the | |
345 ## problem is infeasible. | |
346 if (n_eq > 0) | |
347 Z = null (A); | |
348 if (isempty (Z)) | |
349 ## The problem is infeasible because A is square and full | |
350 ## rank, but xbar is not feasible. | |
351 info = 6; | |
352 endif | |
353 endif | 392 endif |
354 | |
355 if (info != 6) | |
356 ## Solve an LP with additional slack variables to find | |
357 ## a feasible starting point. | |
358 gamma = eye (n_in); | |
359 if (n_eq > 0) | |
360 Atmp = [Ain*Z, gamma]; | |
361 btmp = -res; | |
362 else | |
363 Atmp = [Ain, gamma]; | |
364 btmp = bin; | |
365 endif | |
366 ctmp = [zeros(n-n_eq, 1); ones(n_in, 1)]; | |
367 lb = [-Inf(n-n_eq,1); zeros(n_in,1)]; | |
368 ub = []; | |
369 ctype = repmat ("L", n_in, 1); | |
370 [P, dummy, status] = glpk (ctmp, Atmp, btmp, lb, ub, ctype); | |
371 if ((status == 0) | |
372 && all (abs (P(n-n_eq+1:end)) < rtol * (1 + norm (btmp)))) | |
373 ## We found a feasible starting point | |
374 if (n_eq > 0) | |
375 x0 = xbar + Z*P(1:n-n_eq); | |
376 else | |
377 x0 = P(1:n); | |
378 endif | |
379 else | |
380 ## The problem is infeasible | |
381 info = 6; | |
382 endif | |
383 endif | |
384 else | |
385 ## xbar is feasible. We use it a starting point. | |
386 x0 = xbar; | |
387 endif | 393 endif |
388 else | 394 else |
389 ## xbar is feasible. We use it a starting point. | 395 ## xbar is feasible. We use it a starting point. |
390 x0 = xbar; | 396 x0 = xbar; |
391 endif | 397 endif |
392 endif | |
393 | |
394 if (info == 0) | |
395 ## The initial (or computed) guess is feasible. | |
396 ## We call the solver. | |
397 [x, lambda, info, iter] = __qp__ (x0, H, q, A, b, Ain, bin, maxit); | |
398 else | 398 else |
399 iter = 0; | 399 ## xbar is feasible. We use it a starting point. |
400 x = x0; | 400 x0 = xbar; |
401 lambda = []; | 401 endif |
402 endif | 402 endif |
403 | |
404 if (info == 0) | |
405 ## The initial (or computed) guess is feasible. Call the solver. | |
406 [x, lambda, info, iter] = __qp__ (x0, H, q, A, b, Ain, bin, maxit); | |
407 else | |
408 iter = 0; | |
409 x = x0; | |
410 lambda = []; | |
411 endif | |
412 if (isargout (2)) | |
403 obj = 0.5 * x' * H * x + q' * x; | 413 obj = 0.5 * x' * H * x + q' * x; |
414 endif | |
415 if (isargout (3)) | |
404 INFO.solveiter = iter; | 416 INFO.solveiter = iter; |
405 INFO.info = info; | 417 INFO.info = info; |
406 | |
407 else | |
408 print_usage (); | |
409 endif | 418 endif |
410 | 419 |
411 endfunction | 420 endfunction |
412 | 421 |
413 | 422 |