NNLS
Go to the documentation of this file.
1 /* Non-Negagive Least Squares Algorithm for Eigen.
2  *
3  * Copyright (C) 2021 Essex Edwards, <essex.edwards@gmail.com>
4  * Copyright (C) 2013 Hannes Matuschek, hannes.matuschek at uni-potsdam.de
5  *
6  * This Source Code Form is subject to the terms of the Mozilla
7  * Public License v. 2.0. If a copy of the MPL was not distributed
8  * with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
9  */
10 
29 #ifndef EIGEN_NNLS_H
30 #define EIGEN_NNLS_H
31 
32 #include "../../Eigen/Core"
33 #include "../../Eigen/QR"
34 
35 #include <limits>
36 
37 namespace Eigen {
38 
65 template <class MatrixType_>
66 class NNLS {
67  public:
68  typedef MatrixType_ MatrixType;
69 
70  enum {
71  RowsAtCompileTime = MatrixType::RowsAtCompileTime,
72  ColsAtCompileTime = MatrixType::ColsAtCompileTime,
73  Options = MatrixType::Options,
74  MaxRowsAtCompileTime = MatrixType::MaxRowsAtCompileTime,
75  MaxColsAtCompileTime = MatrixType::MaxColsAtCompileTime
76  };
77 
78  typedef typename MatrixType::Scalar Scalar;
79  typedef typename MatrixType::RealScalar RealScalar;
80  typedef typename MatrixType::Index Index;
81 
87 
89  NNLS();
90 
98  NNLS(const MatrixType &A, Index max_iter = -1, Scalar tol = NumTraits<Scalar>::dummy_precision());
99 
105  template <typename MatrixDerived>
107 
115  const SolutionVectorType &solve(const RhsVectorType &b);
116 
119  const SolutionVectorType &x() const { return x_; }
120 
124  Scalar tolerance() const { return tolerance_; }
125 
133  return *this;
134  }
135 
139  Index maxIterations() const { return max_iter_ < 0 ? 2 * A_.cols() : max_iter_; }
140 
146  max_iter_ = maxIters;
147  return *this;
148  }
149 
151  Index iterations() const { return iterations_; }
152 
154  ComputationInfo info() const { return info_; }
155 
156  private:
158  void moveToInactiveSet_(Index idx);
159 
161  void moveToActiveSet_(Index idx);
162 
164  void solveInactiveSet_(const RhsVectorType &b);
165 
166  private:
168 
202 };
203 
204 /* ********************************************************************************************
205  * Implementation
206  * ******************************************************************************************** */
207 
208 template <typename MatrixType>
210  : max_iter_(-1),
211  iterations_(0),
213  numInactive_(0),
214  tolerance_(NumTraits<Scalar>::dummy_precision()) {}
215 
216 template <typename MatrixType>
217 NNLS<MatrixType>::NNLS(const MatrixType &A, Index max_iter, Scalar tol) : max_iter_(max_iter), tolerance_(tol) {
218  compute(A);
219 }
220 
221 template <typename MatrixType>
222 template <typename MatrixDerived>
224  // Ensure Scalar type is real. The non-negativity constraint doesn't obviously extend to complex numbers.
225  EIGEN_STATIC_ASSERT(!NumTraits<Scalar>::IsComplex, NUMERIC_TYPE_MUST_BE_REAL);
226 
227  // max_iter_: unchanged
228  iterations_ = 0;
229  info_ = ComputationInfo::Success;
230  numInactive_ = 0;
231  // tolerance: unchanged
232  A_ = A.derived();
233  AtA_.noalias() = A_.transpose() * A_;
234  x_.resize(A_.cols());
235  gradient_.resize(A_.cols());
236  y_.resize(A_.cols());
237  Atb_.resize(A_.cols());
238  index_sets_.resize(A_.cols());
239  QR_.resize(A_.rows(), A_.cols());
240  qrCoeffs_.resize(A_.cols());
241  tempSolutionVector_.resize(A_.cols());
242  tempRhsVector_.resize(A_.rows());
243 
244  return *this;
245 }
246 
247 template <typename MatrixType>
249  // Initialize solver
250  iterations_ = 0;
252  x_.setZero();
253 
254  index_sets_ = IndicesType::LinSpaced(A_.cols(), 0, A_.cols() - 1); // Identity permutation.
255  numInactive_ = 0;
256 
257  // Precompute A^T*b
258  Atb_.noalias() = A_.transpose() * b;
259 
260  const Index maxIterations = this->maxIterations();
261 
262  // OUTER LOOP
263  while (true) {
264  // Early exit if all variables are inactive, which breaks 'maxCoeff' below.
265  if (A_.cols() == numInactive_) {
266  info_ = ComputationInfo::Success;
267  return x_;
268  }
269 
270  // Find the maximum element of the gradient in the active set.
271  // If it is small or negative, then we have converged.
272  // Else, we move that variable to the inactive set.
273  gradient_.noalias() = Atb_ - AtA_ * x_;
274 
275  const Index numActive = A_.cols() - numInactive_;
276  Index argmaxGradient = -1;
277  const Scalar maxGradient = gradient_(index_sets_.tail(numActive)).maxCoeff(&argmaxGradient);
278  argmaxGradient += numInactive_; // beacause tail() skipped the first numInactive_ elements
279 
280  if (maxGradient < tolerance_) {
281  info_ = ComputationInfo::Success;
282  return x_;
283  }
284 
285  moveToInactiveSet_(argmaxGradient);
286 
287  // INNER LOOP
288  while (true) {
289  // Check if max. number of iterations is reached
290  if (iterations_ >= maxIterations) {
292  return x_;
293  }
294 
295  // Solve least-squares problem in inactive set only,
296  // this step is rather trivial as moveToInactiveSet_ & moveToActiveSet_
297  // updates the QR decomposition of inactive columns A^N.
298  // solveInactiveSet_ puts the solution in y_
299  solveInactiveSet_(b);
300  ++iterations_; // The solve is expensive, so that is what we count as an iteration.
301 
302  // Check feasability...
303  bool feasible = true;
305  Index infeasibleIdx = -1; // Which variable became infeasible first.
306  for (Index i = 0; i < numInactive_; i++) {
307  Index idx = index_sets_[i];
308  if (y_(idx) < 0) {
309  // t should always be in [0,1].
310  Scalar t = -x_(idx) / (y_(idx) - x_(idx));
311  if (alpha > t) {
312  alpha = t;
313  infeasibleIdx = i;
314  feasible = false;
315  }
316  }
317  }
318  eigen_assert(feasible || 0 <= infeasibleIdx);
319 
320  // If solution is feasible, exit to outer loop
321  if (feasible) {
322  x_ = y_;
323  break;
324  }
325 
326  // Infeasible solution -> interpolate to feasible one
327  for (Index i = 0; i < numInactive_; i++) {
328  Index idx = index_sets_[i];
329  x_(idx) += alpha * (y_(idx) - x_(idx));
330  }
331 
332  // Remove these indices from the inactive set and update QR decomposition
333  moveToActiveSet_(infeasibleIdx);
334  }
335  }
336 }
337 
338 template <typename MatrixType>
340  // Update permutation matrix:
341  std::swap(index_sets_(idx), index_sets_(numInactive_));
342  numInactive_++;
343 
344  // Perform rank-1 update of the QR decomposition stored in QR_ & qrCoeff_
345  internal::householder_qr_inplace_update(QR_, qrCoeffs_, A_.col(index_sets_(numInactive_ - 1)), numInactive_ - 1,
346  tempSolutionVector_.data());
347 }
348 
349 template <typename MatrixType>
351  // swap index with last inactive one & reduce number of inactive columns
352  std::swap(index_sets_(idx), index_sets_(numInactive_ - 1));
353  numInactive_--;
354  // Update QR decomposition starting from the removed index up to the end [idx, ..., numInactive_]
355  for (Index i = idx; i < numInactive_; i++) {
356  Index col = index_sets_(i);
357  internal::householder_qr_inplace_update(QR_, qrCoeffs_, A_.col(col), i, tempSolutionVector_.data());
358  }
359 }
360 
361 template <typename MatrixType>
363  eigen_assert(numInactive_ > 0);
364 
365  tempRhsVector_ = b;
366 
367  // tmpRHS(0:numInactive_-1) := Q'*b
368  // tmpRHS(numInactive_:end) := useless stuff we would rather not compute at all.
369  tempRhsVector_.applyOnTheLeft(
370  householderSequence(QR_.leftCols(numInactive_), qrCoeffs_.head(numInactive_)).transpose());
371 
372  // tempSol(0:numInactive_-1) := inv(R) * Q' * b
373  // = the least-squares solution for the inactive variables.
374  tempSolutionVector_.head(numInactive_) = //
375  QR_.topLeftCorner(numInactive_, numInactive_) //
376  .template triangularView<Upper>() //
377  .solve(tempRhsVector_.head(numInactive_)); //
378 
379  // tempSol(numInactive_:end) := 0 = the value for the constrained variables.
380  tempSolutionVector_.tail(y_.size() - numInactive_).setZero();
381 
382  // Back permute into original column order of A
383  y_.noalias() = index_sets_.asPermutation() * tempSolutionVector_.head(y_.size());
384 }
385 
386 } // namespace Eigen
387 
388 #endif // EIGEN_NNLS_H
SparseMatrix< double > A(n, n)
int i
ColXpr col(Index i) const
#define eigen_assert(x)
#define EIGEN_STATIC_ASSERT(X, MSG)
Implementation of the Non-Negative Least Squares (NNLS) algorithm.
Definition: NNLS:66
RhsVectorType tempRhsVector_
Definition: NNLS:201
const SolutionVectorType & x() const
Returns the solution if a problem was solved. If not, an uninitialized vector may be returned.
Definition: NNLS:119
Matrix< Scalar, RowsAtCompileTime, 1 > RhsVectorType
Definition: NNLS:85
MatrixType_ MatrixType
Definition: NNLS:68
SolutionVectorType gradient_
Definition: NNLS:187
NNLS< MatrixType > & setMaxIterations(Index maxIters)
Definition: NNLS:145
Index iterations_
Definition: NNLS:173
Matrix< Scalar, ColsAtCompileTime, 1 > SolutionVectorType
Definition: NNLS:83
MatrixType::Scalar Scalar
Definition: NNLS:78
Matrix< Index, ColsAtCompileTime, 1 > IndicesType
Definition: NNLS:86
Index numInactive_
Definition: NNLS:177
MatrixAtAType AtA_
Definition: NNLS:183
void moveToActiveSet_(Index idx)
Definition: NNLS:350
SolutionVectorType qrCoeffs_
Definition: NNLS:198
MatrixType A_
Definition: NNLS:181
Index iterations() const
Definition: NNLS:151
IndicesType index_sets_
Definition: NNLS:194
ComputationInfo info() const
Definition: NNLS:154
MatrixType::RealScalar RealScalar
Definition: NNLS:79
NNLS< MatrixType > & compute(const EigenBase< MatrixDerived > &A)
Definition: NNLS:223
SolutionVectorType tempSolutionVector_
Definition: NNLS:200
Scalar tolerance() const
Definition: NNLS:124
void solveInactiveSet_(const RhsVectorType &b)
Definition: NNLS:362
SolutionVectorType x_
Definition: NNLS:185
@ RowsAtCompileTime
Definition: NNLS:71
@ MaxRowsAtCompileTime
Definition: NNLS:74
@ MaxColsAtCompileTime
Definition: NNLS:75
@ Options
Definition: NNLS:73
@ ColsAtCompileTime
Definition: NNLS:72
NNLS< MatrixType > & setTolerance(const Scalar &tolerance)
Definition: NNLS:131
Matrix< Scalar, ColsAtCompileTime, ColsAtCompileTime > MatrixAtAType
Definition: NNLS:167
SolutionVectorType y_
Definition: NNLS:189
MatrixType QR_
Definition: NNLS:196
MatrixType::Index Index
Definition: NNLS:80
Index max_iter_
Definition: NNLS:171
ComputationInfo info_
Definition: NNLS:175
const SolutionVectorType & solve(const RhsVectorType &b)
Solves the NNLS problem.
Definition: NNLS:248
NNLS()
Definition: NNLS:209
SolutionVectorType Atb_
Definition: NNLS:191
Scalar tolerance_
Definition: NNLS:179
void moveToInactiveSet_(Index idx)
Definition: NNLS:339
Index maxIterations() const
Definition: NNLS:139
HouseholderSequence< VectorsType, CoeffsType > householderSequence(const VectorsType &v, const CoeffsType &h)
ComputationInfo
NumericalIssue
Success
NoConvergence
void householder_qr_inplace_update(MatrixQR &mat, HCoeffs &hCoeffs, const VectorQR &newColumn, typename MatrixQR::Index k, typename MatrixQR::Scalar *tempData)
: TensorContractionSycl.h, provides various tensor contraction kernel for SYCL backend
EIGEN_DEFAULT_DENSE_INDEX_TYPE Index
SparseMatrix< double, Options_, StorageIndex_ > & derived()