Eigen-Contrib  5.0.1
 
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// SPDX-License-Identifier: MPL-2.0
11
29
30#ifndef EIGEN_NNLS_MODULE_H
31#define EIGEN_NNLS_MODULE_H
32
33#include "../../Eigen/Core"
34#include "../../Eigen/QR"
35
36#include <limits>
37
38#include "../../Eigen/src/Core/util/DisableStupidWarnings.h"
39
40namespace Eigen {
41
68template <class MatrixType_>
69class NNLS {
70 public:
71 typedef MatrixType_ MatrixType;
72
73 enum {
74 RowsAtCompileTime = MatrixType::RowsAtCompileTime,
75 ColsAtCompileTime = MatrixType::ColsAtCompileTime,
76 Options = MatrixType::Options,
77 MaxRowsAtCompileTime = MatrixType::MaxRowsAtCompileTime,
78 MaxColsAtCompileTime = MatrixType::MaxColsAtCompileTime
79 };
80
81 typedef typename MatrixType::Scalar Scalar;
82 typedef typename MatrixType::RealScalar RealScalar;
83 typedef typename MatrixType::Index Index;
84
89 typedef Matrix<Index, ColsAtCompileTime, 1> IndicesType;
90
92 NNLS();
93
101 NNLS(const MatrixType &A, Index max_iter = -1, Scalar tol = NumTraits<Scalar>::dummy_precision());
102
108 template <typename MatrixDerived>
109 NNLS<MatrixType> &compute(const EigenBase<MatrixDerived> &A);
110
118 const SolutionVectorType &solve(const RhsVectorType &b);
119
122 const SolutionVectorType &x() const { return x_; }
123
127 Scalar tolerance() const { return tolerance_; }
128
134 NNLS<MatrixType> &setTolerance(const Scalar &tolerance) {
135 tolerance_ = tolerance;
136 return *this;
137 }
138
142 Index maxIterations() const { return max_iter_ < 0 ? 2 * A_.cols() : max_iter_; }
143
148 NNLS<MatrixType> &setMaxIterations(Index maxIters) {
149 max_iter_ = maxIters;
150 return *this;
151 }
152
154 Index iterations() const { return iterations_; }
155
157 ComputationInfo info() const { return info_; }
158
159 private:
161 void moveToInactiveSet_(Index idx);
162
164 void moveToActiveSet_(Index idx);
165
167 void solveInactiveSet_(const RhsVectorType &b);
168
169 private:
171
174 Index max_iter_;
176 Index iterations_;
178 ComputationInfo info_;
180 Index numInactive_;
182 Scalar tolerance_;
184 MatrixType A_;
186 MatrixAtAType AtA_;
190 SolutionVectorType gradient_;
197 IndicesType index_sets_;
199 MatrixType QR_;
201 SolutionVectorType qrCoeffs_;
203 SolutionVectorType tempSolutionVector_;
204 RhsVectorType tempRhsVector_;
205};
206
207/* ********************************************************************************************
208 * Implementation
209 * ******************************************************************************************** */
210
211template <typename MatrixType>
212NNLS<MatrixType>::NNLS()
213 : max_iter_(-1),
214 iterations_(0),
215 info_(ComputationInfo::InvalidInput),
216 numInactive_(0),
217 tolerance_(NumTraits<Scalar>::dummy_precision()) {}
218
219template <typename MatrixType>
220NNLS<MatrixType>::NNLS(const MatrixType &A, Index max_iter, Scalar tol) : max_iter_(max_iter), tolerance_(tol) {
221 compute(A);
222}
223
224template <typename MatrixType>
225template <typename MatrixDerived>
227 // Ensure Scalar type is real. The non-negativity constraint doesn't obviously extend to complex numbers.
228 EIGEN_STATIC_ASSERT(!NumTraits<Scalar>::IsComplex, NUMERIC_TYPE_MUST_BE_REAL);
229
230 // max_iter_: unchanged
231 iterations_ = 0;
232 info_ = ComputationInfo::Success;
233 numInactive_ = 0;
234 // tolerance: unchanged
235 A_ = A.derived();
236 AtA_.noalias() = A_.transpose() * A_;
237 x_.resize(A_.cols());
238 gradient_.resize(A_.cols());
239 y_.resize(A_.cols());
240 Atb_.resize(A_.cols());
241 index_sets_.resize(A_.cols());
242 QR_.resize(A_.rows(), A_.cols());
243 qrCoeffs_.resize(A_.cols());
244 tempSolutionVector_.resize(A_.cols());
245 tempRhsVector_.resize(A_.rows());
246
247 return *this;
248}
249
250template <typename MatrixType>
252 // Initialize solver
253 iterations_ = 0;
254 info_ = ComputationInfo::NumericalIssue;
255 x_.setZero();
256
257 index_sets_ = IndicesType::LinSpaced(A_.cols(), 0, A_.cols() - 1); // Identity permutation.
258 numInactive_ = 0;
259
260 // Precompute A^T*b
261 Atb_.noalias() = A_.transpose() * b;
262
263 if (A_.rows() == 0) {
264 info_ = ComputationInfo::Success;
265 return x_;
266 }
267
268 const Index maxIterations = this->maxIterations();
269
270 // OUTER LOOP
271 while (true) {
272 // Early exit if all variables are inactive, which breaks 'maxCoeff' below.
273 if (A_.cols() == numInactive_) {
274 info_ = ComputationInfo::Success;
275 return x_;
276 }
277
278 // Find the maximum element of the gradient in the active set.
279 // If it is small or negative, then we have converged.
280 // Else, we move that variable to the inactive set.
281 gradient_.noalias() = Atb_ - AtA_ * x_;
282 if (!gradient_.allFinite()) return x_;
283
284 const Index numActive = A_.cols() - numInactive_;
285 Index argmaxGradient = -1;
286 const Scalar maxGradient = gradient_(index_sets_.tail(numActive)).maxCoeff(&argmaxGradient);
287 argmaxGradient += numInactive_; // because tail() skipped the first numInactive_ elements
288
289 if (maxGradient < tolerance_) {
290 info_ = ComputationInfo::Success;
291 return x_;
292 }
293
294 if (iterations_ >= maxIterations) {
295 info_ = ComputationInfo::NoConvergence;
296 return x_;
297 }
298
299 // In exact arithmetic, a positive gradient guarantees that the candidate
300 // column is independent of the inactive (passive) set, so a row-sized set
301 // would have satisfied the stopping condition above. If it does not,
302 // numerical dependence has invalidated that invariant. Another rank-1 QR
303 // update would exceed the row capacity, but the current iterate cannot be
304 // reported as optimal either.
305 if (numInactive_ == A_.rows()) {
306 info_ = ComputationInfo::NumericalIssue;
307 return x_;
308 }
309
310 moveToInactiveSet_(argmaxGradient);
311
312 // INNER LOOP
313 while (true) {
314 // Check if max. number of iterations is reached
315 if (iterations_ >= maxIterations) {
316 info_ = ComputationInfo::NoConvergence;
317 return x_;
318 }
319
320 // Solve least-squares problem in inactive set only,
321 // this step is rather trivial as moveToInactiveSet_ & moveToActiveSet_
322 // updates the QR decomposition of inactive columns A^N.
323 // solveInactiveSet_ puts the solution in y_
324 solveInactiveSet_(b);
325 ++iterations_; // The solve is expensive, so that is what we count as an iteration.
326 if (!y_.allFinite()) return x_;
327
328 // Check feasibility...
329 bool feasible = true;
330 Scalar alpha = NumTraits<Scalar>::highest();
331 Index infeasibleIdx = -1; // Which variable became infeasible first.
332 for (Index i = 0; i < numInactive_; i++) {
333 Index idx = index_sets_[i];
334 if (y_(idx) < 0) {
335 // t should always be in [0,1].
336 Scalar t = -x_(idx) / (y_(idx) - x_(idx));
337 if (alpha > t) {
338 alpha = t;
339 infeasibleIdx = i;
340 feasible = false;
341 }
342 }
343 }
344 eigen_assert(feasible || 0 <= infeasibleIdx);
345
346 // If solution is feasible, exit to outer loop
347 if (feasible) {
348 x_ = y_;
349 break;
350 }
351
352 // Infeasible solution -> interpolate to feasible one
353 for (Index i = 0; i < numInactive_; i++) {
354 Index idx = index_sets_[i];
355 x_(idx) += alpha * (y_(idx) - x_(idx));
356 }
357
358 // Remove these indices from the inactive set and update QR decomposition
359 moveToActiveSet_(infeasibleIdx);
360 }
361 }
362}
363
364template <typename MatrixType>
365void NNLS<MatrixType>::moveToInactiveSet_(Index idx) {
366 // Update permutation matrix:
367 std::swap(index_sets_(idx), index_sets_(numInactive_));
368 numInactive_++;
369
370 // Perform rank-1 update of the QR decomposition stored in QR_ & qrCoeff_
371 internal::householder_qr_inplace_update(QR_, qrCoeffs_, A_.col(index_sets_(numInactive_ - 1)), numInactive_ - 1,
372 tempSolutionVector_.data());
373}
374
375template <typename MatrixType>
376void NNLS<MatrixType>::moveToActiveSet_(Index idx) {
377 // swap index with last inactive one & reduce number of inactive columns
378 std::swap(index_sets_(idx), index_sets_(numInactive_ - 1));
379 numInactive_--;
380 // Update QR decomposition starting from the removed index up to the end [idx, ..., numInactive_]
381 for (Index i = idx; i < numInactive_; i++) {
382 Index col = index_sets_(i);
383 internal::householder_qr_inplace_update(QR_, qrCoeffs_, A_.col(col), i, tempSolutionVector_.data());
384 }
385}
386
387template <typename MatrixType>
388void NNLS<MatrixType>::solveInactiveSet_(const RhsVectorType &b) {
389 eigen_assert(numInactive_ > 0);
390
391 tempRhsVector_ = b;
392
393 // tmpRHS(0:numInactive_-1) := Q'*b
394 // tmpRHS(numInactive_:end) := useless stuff we would rather not compute at all.
395 tempRhsVector_.applyOnTheLeft(
396 householderSequence(QR_.leftCols(numInactive_), qrCoeffs_.head(numInactive_)).transpose());
397
398 // tempSol(0:numInactive_-1) := inv(R) * Q' * b
399 // = the least-squares solution for the inactive variables.
400 tempSolutionVector_.head(numInactive_) = //
401 QR_.topLeftCorner(numInactive_, numInactive_) //
402 .template triangularView<Upper>() //
403 .solve(tempRhsVector_.head(numInactive_)); //
404
405 // tempSol(numInactive_:end) := 0 = the value for the constrained variables.
406 tempSolutionVector_.tail(y_.size() - numInactive_).setZero();
407
408 // Back permute into original column order of A
409 y_.noalias() = index_sets_.asPermutation() * tempSolutionVector_.head(y_.size());
410}
411
412} // namespace Eigen
413
414#include "../../Eigen/src/Core/util/ReenableStupidWarnings.h"
415
416#endif // EIGEN_NNLS_MODULE_H
Matrix< Scalar, RowsAtCompileTime, 1 > RhsVectorType
Definition NNLS:88
Matrix< Scalar, ColsAtCompileTime, 1 > SolutionVectorType
Definition NNLS:86
Index iterations() const
Definition NNLS:154
ComputationInfo info() const
Definition NNLS:157
NNLS< MatrixType > & compute(const EigenBase< MatrixDerived > &A)
Definition NNLS:226
const SolutionVectorType & x() const
Returns the solution if a problem was solved. If not, an uninitialized vector may be returned.
Definition NNLS:122
Scalar tolerance() const
Definition NNLS:127
NNLS< MatrixType > & setMaxIterations(Index maxIters)
Definition NNLS:148
NNLS< MatrixType > & setTolerance(const Scalar &tolerance)
Definition NNLS:134
const SolutionVectorType & solve(const RhsVectorType &b)
Solves the NNLS problem.
Definition NNLS:251
Index maxIterations() const
Definition NNLS:142
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.
constexpr Derived & derived()