vnl_solve_qp.cxx
Go to the documentation of this file.
1 #include <vector>
2 #include <cassert>
3 #include <iostream>
4 #include "vnl_solve_qp.h"
5 //:
6 // \file
7 // \brief Functions to solve various forms of constrained quadratic programming
8 // \author Tim Cootes
9 
10 #include <vnl/algo/vnl_svd.h>
11 #include <vnl/algo/vnl_cholesky.h>
12 
13 //: Solve Sx=b for symmetric S
14 static void vnl_solve_symmetric_le(const vnl_matrix<double>& S,
15  const vnl_vector<double>& b,
17 {
19  if (chol.rcond()>1e-8) x=chol.solve(b);
20  else
21  {
22  vnl_svd<double> svd(S);
23  x=svd.solve(b);
24  }
25 }
26 
27 //: Solve quadratic programming problem with linear constraints
28 // Minimise F(x)=0.5x'Hx + g'x subject to Ax=b
29 // \param H Hessian of F(x) - must be symmetric
30 // \retval True if successful
32  const vnl_vector<double>& g,
33  const vnl_matrix<double>& A,
34  const vnl_vector<double>& b,
36 {
37  // Test inputs
38  // unsigned n=H.rows(); // Number of unknowns
39  unsigned nc=A.rows(); // Number of equality constraints
40  assert(H.cols()==H.rows());
41  assert(g.size()==H.rows());
42  assert(A.cols()==H.rows());
43  assert(b.size()==nc);
44 
45  vnl_matrix<double> H_inv;
47  if (Hchol.rcond()>1e-8) H_inv=Hchol.inverse();
48  else
49  {
50  vnl_svd<double> Hsvd(H);
51  H_inv=Hsvd.inverse();
52  }
53 
54  if (nc==0)
55  {
56  // Unconstrained case
57  x=-1.0*H_inv*g;
58  return true;
59  }
60 
61  vnl_vector<double> b1=(b+A*H_inv*g)*-1.0;
62 
63  // Solve for lagrange multipliers, lambda
64  vnl_vector<double> lambda; // Lagrange multipliers
65  vnl_matrix<double> AHA = A*H_inv*A.transpose();
66  vnl_solve_symmetric_le(AHA,b1,lambda);
67 
68  x=(H_inv*(g+A.transpose()*lambda))*-1.0;
69  return true;
70 }
71 
72 //: Solve quadratic programming problem with constraint sum(x)=0
73 // Minimise F(x)=0.5x'Hx + g'x subject to sum(x)=0
74 // Special case of quadratic programming (Equality constraint: x.1=0)
75 // \param H Hessian of F(x) - must be symmetric
76 // \retval True if successful
78  const vnl_vector<double>& g,
80 {
81  // Test inputs
82  // unsigned n=H.rows(); // Number of unknowns
83  assert(H.cols()==H.rows());
84  assert(g.size()==H.rows());
85 
86  vnl_matrix<double> H_inv;
88  if (Hchol.rcond()>1e-8) H_inv=Hchol.inverse();
89  else
90  {
91  vnl_svd<double> Hsvd(H);
92  H_inv=Hsvd.inverse();
93  }
94 
95  double b1=-1.0*(H_inv*g).sum();
96 
97  // Sum of elements in H_inv (= 1'*H_inv*1)
98  double H_inv_sum = vnl_c_vector<double>::sum(H_inv.begin(),H_inv.size());
99 
100  if (std::fabs(H_inv_sum)<1e-8)
101  {
102  std::cerr<<"Uh-oh. H_inv.sum()="<<H_inv_sum<<std::endl
103  <<"H="<<H<<std::endl
104  <<"H_inv="<<H_inv<<std::endl;
105  }
106 
107  // Solve for lagrange multiplier, lambda
108  double lambda = b1/H_inv_sum;
109 
110  vnl_vector<double> g1(g);
111  g1+=lambda;
112 
113  x=(H_inv*g1);
114  x*=-1.0;
115 
116  return true;
117 }
118 
119 //: Update x, checking inequality constraints and modifying valid where necessary
120 static bool vnl_solve_qp_update_x(vnl_vector<double>& x,
121  const vnl_vector<double>& x1,
122  vnl_vector<double>& dx,
123  std::vector<bool>& valid,
124  unsigned& n_valid)
125 {
126  unsigned n=x.size();
127  // Check non-negativity constraints
128  int worst_i=-1;
129  double min_alpha=1.0;
130  for (unsigned i=0;i<n_valid;++i)
131  {
132  if (dx[i]<0.0)
133  {
134  double alpha = -1.0*x1[i]/dx[i];
135  if (alpha<min_alpha)
136  {
137  min_alpha=alpha; worst_i=i;
138  }
139  }
140  }
141 
142  // Update x and apply constraints
143  unsigned i1=0;
144  for (unsigned i=0;i<n;++i)
145  {
146  if (valid[i])
147  {
148  x[i]+=min_alpha*dx[i1];
149  if (i1==(unsigned int)worst_i)
150  {
151  // Set this variable to zero and indicate it cannot change
152  x[i]=0.0;
153  valid[i]=false;
154  n_valid--;
155  }
156  ++i1;
157  }
158  }
159 
160  return worst_i<0;
161 }
162 
163 //: Solve unconstrained problem and apply one extra constraint if necessary
164 // Used by vnl_non_neg_constrained_qp
165 // Returns true if valid minimum found
167  const vnl_vector<double>& g,
168  const vnl_matrix<double>& A,
169  const vnl_vector<double>& b,
171  std::vector<bool>& valid,
172  unsigned& n_valid)
173 {
174  // Find solution to H1(x+dx)+g1=0, subject to A1(x1+dx)=b
175  // H1,A1,g1,x1 contain subsets defined by valid array
176  // ie solve H1.dx+(H1.x+g1)=0 subject to A1.dx=(b-A1.x1)
177 
178  unsigned n=H.rows(); // Full number of unknowns
179  unsigned nc=A.rows(); // Number of equality constraints
180 
181  vnl_matrix<double> H1(n_valid,n_valid);
182  vnl_matrix<double> A1(nc,n_valid);
183  unsigned j1=0;
184  for (unsigned j=0;j<n;++j)
185  {
186  if (valid[j])
187  {
188  // Fill column j1 of H with elements from column j of H
189  // First from H:
190  unsigned i1=0;
191  for (unsigned i=0;i<n;++i)
192  {
193  if (valid[i]) { H1(i1,j1)=H(i,j); ++i1; }
194  }
195 
196  // Now fill column of A1
197  for (unsigned i=0;i<nc;++i,++i1) A1(i,j1)=A(i,j);
198 
199  ++j1; // Move to next column in M
200  }
201  }
202 
203  vnl_vector<double> x1(n_valid); // Will contain non-zero elements of x
204  vnl_vector<double> g1(n_valid);
205  unsigned i1=0;
206  for (unsigned i=0;i<n;++i)
207  {
208  if (valid[i]) { g1[i1]=g[i]; x1[i1]=x[i]; ++i1; }
209  }
210  g1 += H1*x1;
211 
212  vnl_vector<double> b1(b);
213  b1-= A1*x1;
214 
215  vnl_vector<double> dx(n_valid,0.0);
216 
218 
219  // Update x, checking inequality constraints and modifying valid where necessary
220  return vnl_solve_qp_update_x(x,x1,dx,valid,n_valid);
221 }
222 
223 
224 //: Solve unconstrained problem and apply one extra constraint if necessary
225 // Returns true if valid minimum found
227  const vnl_vector<double>& g,
229  std::vector<bool>& valid,
230  unsigned& n_valid)
231 {
232  // Find solution to H1(x+dx)+g1=0, subject to sum(dx)=0.0
233  // H1,g1,x1 contain subsets defined by valid array
234  // ie solve H1.dx+(H1.x+g1)=0 subject to sum(dx)=0
235 
236  unsigned n=H.rows(); // Full number of unknowns
237 
238  vnl_matrix<double> H1(n_valid,n_valid);
239  unsigned j1=0;
240  for (unsigned j=0;j<n;++j)
241  {
242  if (valid[j])
243  {
244  // Fill column j1 of H with elements from column j of H
245  // First from H:
246  unsigned i1=0;
247  for (unsigned i=0;i<n;++i)
248  {
249  if (valid[i]) { H1(i1,j1)=H(i,j); ++i1; }
250  }
251  ++j1; // Move to next column in M
252  }
253  }
254 
255  vnl_vector<double> x1(n_valid); // Will contain non-zero elements of x
256  vnl_vector<double> g1(n_valid);
257  unsigned i1=0;
258  for (unsigned i=0;i<n;++i)
259  {
260  if (valid[i]) { g1[i1]=g[i]; x1[i1]=x[i]; ++i1; }
261  }
262  g1 += H1*x1;
263 
264  vnl_vector<double> dx(n_valid,0.0);
265 
266  vnl_solve_qp_zero_sum(H1,g1,dx);
267 
268  // Update x, checking inequality constraints and modifying valid where necessary
269  return vnl_solve_qp_update_x(x,x1,dx,valid,n_valid);
270 }
271 
272 
273 //: Find non-negative solution to a constrained quadratic programming problem
274 // Minimise F(x)=0.5x'Hx + g'x subject to Ax=b and x(i)>=0 for all i
275 //
276 // Uses a variant of the active set strategy to solve the problem.
277 // This performs a sequence of unconstrained solutions. If the inequality
278 // constraints are violated, the most violated x(i) is set to zero
279 // and a slightly smaller problem is solved.
280 // \param H Hessian of F(x) - must be symmetric
281 // \param x On input, it must satisfy all constraints (Ax=b, x(i)>=0)
282 // \param con_tol Tolerance for testing constraints: |Ax-b|^2<con_tol
283 // \param verbose When true, output error messages to cerr if failed
284 // \retval True if successful
286  const vnl_vector<double>& g,
287  const vnl_matrix<double>& A,
288  const vnl_vector<double>& b,
290  double con_tol,
291  bool verbose)
292 {
293  // Test inputs
294  unsigned n=H.rows(); // Number of unknowns
295  //unsigned nc=A.rows(); // Number of equality constraints
296  assert(H.cols()==n);
297  assert(g.size()==n);
298  assert(A.cols()==n);
299  assert(b.size()==A.rows());
300 
301  if (vnl_vector_ssd(A*x,b)>con_tol)
302  {
303  if (verbose)
304  std::cerr<<"Supplied x does not satisfy equality constraints\n";
305  return false;
306  }
307  for (unsigned i=0;i<n;++i)
308  {
309  if (x[i]<0)
310  {
311  if (verbose)
312  std::cerr<<"Element "<<i<<" of x is negative. Must be >=0 on input.\n";
313  return false;
314  }
315  }
316 
317  // Indicate which elements of x are non-zero and to be optimised
318  std::vector<bool> valid(n,true);
319  unsigned n_valid=n;
320 
321  while (!vnl_solve_qp_non_neg_step(H,g,A,b,x,valid,n_valid)) {}
322 
323  if (vnl_vector_ssd(A*x,b)>con_tol)
324  {
325  if (verbose)
326  std::cerr<<"Oops: Final x does not satisfy equality constraints\n";
327  return false;
328  }
329  else
330  return true;
331 }
332 
333 //: Find non-negative solution to a constrained quadratic programming problem
334 // Minimise F(x)=0.5x'Hx + g'x subject to sum(x)=1 and x(i)>=0 for all i
335 //
336 // Uses a variant of the active set strategy to solve the problem.
337 // This performs a sequence of unconstrained solutions. If the inequality
338 // constraints are violated, the most violated x(i) is set to zero
339 // and a slightly smaller problem is solved.
340 // \param H Hessian of F(x) - must be symmetric
341 // \param x On input, it must satisfy all constraints (sum(x)=1, x(i)>=0)
342 // \param verbose When true, output error messages to cerr if failed
343 // \retval True if successful
345  const vnl_vector<double>& g,
347  bool verbose)
348 {
349  // Test inputs
350  unsigned n=H.rows(); // Number of unknowns
351  assert(H.cols()==n);
352  assert(g.size()==n);
353 
354  if (std::fabs(x.sum()-1.0)>1e-8)
355  {
356  if (verbose)
357  std::cerr<<"Supplied x does not sum to unity.\n";
358  return false;
359  }
360  for (unsigned i=0;i<n;++i)
361  {
362  if (x[i]<0)
363  {
364  if (verbose)
365  std::cerr<<"Element "<<i<<" of x is negative. Must be >=0 on input.\n";
366  return false;
367  }
368  }
369 
370  // Indicate which elements of x are non-zero and to be optimised
371  std::vector<bool> valid(n,true);
372  unsigned n_valid=n;
373 
374  while (!vnl_solve_qp_non_neg_sum_one_step(H,g,x,valid,n_valid)) {}
375 
376  if (std::fabs(x.sum()-1.0)>1e-8)
377  {
378  if (verbose)
379  std::cerr<<"Oops. Final x does not sum to unity.\n";
380  return false;
381  }
382  else
383  return true;
384 }
unsigned int cols() const
Return the number of columns.
Definition: vnl_matrix.h:183
T vnl_vector_ssd(vnl_vector< T > const &v1, vnl_vector< T > const &v2)
Euclidean Distance between two vectors.
Definition: vnl_vector.h:480
bool vnl_solve_qp_non_neg_step(const vnl_matrix< double > &H, const vnl_vector< double > &g, const vnl_matrix< double > &A, const vnl_vector< double > &b, vnl_vector< double > &x, std::vector< bool > &valid, unsigned &n_valid)
Solve unconstrained problem and apply one extra constraint if necessary.
bool vnl_solve_qp_with_equality_constraints(const vnl_matrix< double > &H, const vnl_vector< double > &g, const vnl_matrix< double > &A, const vnl_vector< double > &b, vnl_vector< double > &x)
Solve quadratic programming problem with linear constraints.
vnl_matrix< T > transpose() const
Return transpose.
Definition: vnl_matrix.hxx:682
Functions to solve various forms of constrained quadratic programming.
vnl_matrix< double > inverse() const
static T sum(T const *v, unsigned n)
Decomposition of symmetric matrix.
size_t size() const
Return the length, number of elements, dimension of this vector.
Definition: vnl_vector.h:126
Decomposition of symmetric matrix.
Definition: vnl_cholesky.h:31
T sum() const
Sum of values in a vector.
Definition: vnl_vector.h:318
vnl_matrix< T > inverse() const
Definition: vnl_svd.h:133
bool vnl_solve_qp_non_neg_sum_one(const vnl_matrix< double > &H, const vnl_vector< double > &g, vnl_vector< double > &x, bool verbose)
Find non-negative solution to a constrained quadratic programming problem.
double rcond() const
Return reciprocal condition number (smallest/largest singular values).
Definition: vnl_cholesky.h:76
unsigned int rows() const
Return the number of rows.
Definition: vnl_matrix.h:179
Holds the singular value decomposition of a vnl_matrix.
bool vnl_solve_qp_with_non_neg_constraints(const vnl_matrix< double > &H, const vnl_vector< double > &g, const vnl_matrix< double > &A, const vnl_vector< double > &b, vnl_vector< double > &x, double con_tol, bool verbose)
Find non-negative solution to a constrained quadratic programming problem.
Holds the singular value decomposition of a vnl_matrix.
Definition: vnl_algo_fwd.h:7
bool vnl_solve_qp_zero_sum(const vnl_matrix< double > &H, const vnl_vector< double > &g, vnl_vector< double > &x)
Solve quadratic programming problem with constraint sum(x)=0.
bool vnl_solve_qp_non_neg_sum_one_step(const vnl_matrix< double > &H, const vnl_vector< double > &g, vnl_vector< double > &x, std::vector< bool > &valid, unsigned &n_valid)
Solve unconstrained problem and apply one extra constraint if necessary.