Eigen-unsupported  3.4.90 (git rev 9589cc4e7fd8e4538bedef80dd36c7738977a8be)
 
Loading...
Searching...
No Matches
NNLS
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
28
29#ifndef EIGEN_NNLS_H
30#define EIGEN_NNLS_H
31
32#include "../../Eigen/Core"
33#include "../../Eigen/QR"
34
35#include <limits>
36
37namespace Eigen {
38
65template <class MatrixType_>
66class 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
86 typedef Matrix<Index, ColsAtCompileTime, 1> IndicesType;
87
89 NNLS();
90
98 NNLS(const MatrixType &A, Index max_iter = -1, Scalar tol = NumTraits<Scalar>::dummy_precision());
99
105 template <typename MatrixDerived>
106 NNLS<MatrixType> &compute(const EigenBase<MatrixDerived> &A);
107
115 const SolutionVectorType &solve(const RhsVectorType &b);
116
119 const SolutionVectorType &x() const { return x_; }
120
124 Scalar tolerance() const { return tolerance_; }
125
131 NNLS<MatrixType> &setTolerance(const Scalar &tolerance) {
132 tolerance_ = tolerance;
133 return *this;
134 }
135
139 Index maxIterations() const { return max_iter_ < 0 ? 2 * A_.cols() : max_iter_; }
140
145 NNLS<MatrixType> &setMaxIterations(Index maxIters) {
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
171 Index max_iter_;
173 Index iterations_;
175 ComputationInfo info_;
177 Index numInactive_;
179 Scalar tolerance_;
181 MatrixType A_;
183 MatrixAtAType AtA_;
187 SolutionVectorType gradient_;
194 IndicesType index_sets_;
196 MatrixType QR_;
198 SolutionVectorType qrCoeffs_;
200 SolutionVectorType tempSolutionVector_;
201 RhsVectorType tempRhsVector_;
202};
203
204/* ********************************************************************************************
205 * Implementation
206 * ******************************************************************************************** */
207
208template <typename MatrixType>
209NNLS<MatrixType>::NNLS()
210 : max_iter_(-1),
211 iterations_(0),
212 info_(ComputationInfo::InvalidInput),
213 numInactive_(0),
214 tolerance_(NumTraits<Scalar>::dummy_precision()) {}
215
216template <typename MatrixType>
217NNLS<MatrixType>::NNLS(const MatrixType &A, Index max_iter, Scalar tol) : max_iter_(max_iter), tolerance_(tol) {
218 compute(A);
219}
220
221template <typename MatrixType>
222template <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
247template <typename MatrixType>
249 // Initialize solver
250 iterations_ = 0;
251 info_ = ComputationInfo::NumericalIssue;
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_; // because 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) {
291 info_ = ComputationInfo::NoConvergence;
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 feasibility...
303 bool feasible = true;
304 Scalar alpha = NumTraits<Scalar>::highest();
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
338template <typename MatrixType>
339void NNLS<MatrixType>::moveToInactiveSet_(Index idx) {
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
349template <typename MatrixType>
350void NNLS<MatrixType>::moveToActiveSet_(Index idx) {
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
361template <typename MatrixType>
362void NNLS<MatrixType>::solveInactiveSet_(const RhsVectorType &b) {
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
Matrix< Scalar, RowsAtCompileTime, 1 > RhsVectorType
Definition NNLS:85
Matrix< Scalar, ColsAtCompileTime, 1 > SolutionVectorType
Definition NNLS:83
Index iterations() const
Definition NNLS:151
ComputationInfo info() const
Definition NNLS:154
NNLS< MatrixType > & compute(const EigenBase< MatrixDerived > &A)
Definition NNLS:223
const SolutionVectorType & x() const
Returns the solution if a problem was solved. If not, an uninitialized vector may be returned.
Definition NNLS:119
Scalar tolerance() const
Definition NNLS:124
NNLS< MatrixType > & setMaxIterations(Index maxIters)
Definition NNLS:145
NNLS< MatrixType > & setTolerance(const Scalar &tolerance)
Definition NNLS:131
const SolutionVectorType & solve(const RhsVectorType &b)
Solves the NNLS problem.
Definition NNLS:248
Index maxIterations() const
Definition NNLS:139
Derived & setZero(Index rows, Index cols)
HouseholderSequence< VectorsType, CoeffsType > householderSequence(const VectorsType &v, const CoeffsType &h)
ComputationInfo
Namespace containing all symbols from the Eigen library.
EIGEN_DEFAULT_DENSE_INDEX_TYPE Index
constexpr Derived & derived()