1/* Non-Negagive Least Squares Algorithm for Eigen.
3 * Copyright (C) 2021 Essex Edwards, <essex.edwards@gmail.com>
4 * Copyright (C) 2013 Hannes Matuschek, hannes.matuschek at uni-potsdam.de
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/.
11/** \defgroup nnls Non-Negative Least Squares (NNLS) Module
12 * This module provides a single class @c Eigen::NNLS implementing the NNLS algorithm.
13 * The algorithm is described in "SOLVING LEAST SQUARES PROBLEMS", by Charles L. Lawson and
14 * Richard J. Hanson, Prentice-Hall, 1974 and solves optimization problems of the form
16 * \f[ \min \left\Vert Ax-b\right\Vert_2^2\quad s.t.\, x\ge 0\,.\f]
18 * The algorithm solves the constrained least-squares problem above by iteratively improving
19 * an estimate of which constraints are active (elements of \f$x\f$ equal to zero)
20 * and which constraints are inactive (elements of \f$x\f$ greater than zero).
21 * Each iteration, an unconstrained linear least-squares problem solves for the
22 * components of \f$x\f$ in the (estimated) inactive set and the sets are updated.
23 * The unconstrained problem minimizes \f$\left\Vert A^Nx^N-b\right\Vert_2^2\f$,
24 * where \f$A^N\f$ is a matrix formed by selecting all columns of A which are
25 * in the inactive set \f$N\f$.
32#include "../../Eigen/Core"
33#include "../../Eigen/QR"
41 * \brief Implementation of the Non-Negative Least Squares (NNLS) algorithm.
42 * \tparam MatrixType The type of the system matrix \f$A\f$.
44 * This class implements the NNLS algorithm as described in "SOLVING LEAST SQUARES PROBLEMS",
45 * Charles L. Lawson and Richard J. Hanson, Prentice-Hall, 1974. This algorithm solves a least
46 * squares problem iteratively and ensures that the solution is non-negative. I.e.
48 * \f[ \min \left\Vert Ax-b\right\Vert_2^2\quad s.t.\, x\ge 0 \f]
50 * The algorithm solves the constrained least-squares problem above by iteratively improving
51 * an estimate of which constraints are active (elements of \f$x\f$ equal to zero)
52 * and which constraints are inactive (elements of \f$x\f$ greater than zero).
53 * Each iteration, an unconstrained linear least-squares problem solves for the
54 * components of \f$x\f$ in the (estimated) inactive set and the sets are updated.
55 * The unconstrained problem minimizes \f$\left\Vert A^Nx^N-b\right\Vert_2^2\f$,
56 * where \f$A^N\f$ is a matrix formed by selecting all columns of A which are
57 * in the inactive set \f$N\f$.
59 * See <a href="https://en.wikipedia.org/wiki/Non-negative_least_squares">the
60 * wikipedia page on non-negative least squares</a> for more background information.
62 * \note Please note that it is possible to construct an NNLS problem for which the
63 * algorithm does not converge. In practice these cases are extremely rare.
65template <class MatrixType_>
68 typedef MatrixType_ MatrixType;
71 RowsAtCompileTime = MatrixType::RowsAtCompileTime,
72 ColsAtCompileTime = MatrixType::ColsAtCompileTime,
73 Options = MatrixType::Options,
74 MaxRowsAtCompileTime = MatrixType::MaxRowsAtCompileTime,
75 MaxColsAtCompileTime = MatrixType::MaxColsAtCompileTime
78 typedef typename MatrixType::Scalar Scalar;
79 typedef typename MatrixType::RealScalar RealScalar;
80 typedef typename MatrixType::Index Index;
82 /** Type of a row vector of the system matrix \f$A\f$. */
83 typedef Matrix<Scalar, ColsAtCompileTime, 1> SolutionVectorType;
84 /** Type of a column vector of the system matrix \f$A\f$. */
85 typedef Matrix<Scalar, RowsAtCompileTime, 1> RhsVectorType;
86 typedef Matrix<Index, ColsAtCompileTime, 1> IndicesType;
91 /** \brief Constructs a NNLS sovler and initializes it with the given system matrix @c A.
92 * \param A Specifies the system matrix.
93 * \param max_iter Specifies the maximum number of iterations to solve the system.
94 * \param tol Specifies the precision of the optimum.
95 * This is an absolute tolerance on the gradient of the Lagrangian, \f$A^T(Ax-b)-\lambda\f$
96 * (with Lagrange multipliers \f$\lambda\f$).
98 NNLS(const MatrixType &A, Index max_iter = -1, Scalar tol = NumTraits<Scalar>::dummy_precision());
100 /** Initializes the solver with the matrix \a A for further solving NNLS problems.
102 * This function mostly initializes/computes the preconditioner. In the future
103 * we might, for instance, implement column reordering for faster matrix vector products.
105 template <typename MatrixDerived>
106 NNLS<MatrixType> &compute(const EigenBase<MatrixDerived> &A);
108 /** \brief Solves the NNLS problem.
110 * The dimension of @c b must be equal to the number of rows of @c A, given to the constructor.
112 * \returns The approximate solution vector \f$ x \f$. Use info() to determine if the solve was a success or not.
115 const SolutionVectorType &solve(const RhsVectorType &b);
117 /** \brief Returns the solution if a problem was solved.
118 * If not, an uninitialized vector may be returned. */
119 const SolutionVectorType &x() const { return x_; }
121 /** \returns the tolerance threshold used by the stopping criteria.
124 Scalar tolerance() const { return tolerance_; }
126 /** Sets the tolerance threshold used by the stopping criteria.
128 * This is an absolute tolerance on the gradient of the Lagrangian, \f$A^T(Ax-b)-\lambda\f$
129 * (with Lagrange multipliers \f$\lambda\f$).
131 NNLS<MatrixType> &setTolerance(const Scalar &tolerance) {
132 tolerance_ = tolerance;
136 /** \returns the max number of iterations.
137 * It is either the value set by setMaxIterations or, by default, twice the number of columns of the matrix.
139 Index maxIterations() const { return max_iter_ < 0 ? 2 * A_.cols() : max_iter_; }
141 /** Sets the max number of iterations.
142 * Default is twice the number of columns of the matrix.
143 * The algorithm requires at least k iterations to produce a solution vector with k non-zero entries.
145 NNLS<MatrixType> &setMaxIterations(Index maxIters) {
146 max_iter_ = maxIters;
150 /** \returns the number of iterations (least-squares solves) performed during the last solve */
151 Index iterations() const { return iterations_; }
153 /** \returns Success if the iterations converged, and an error values otherwise. */
154 ComputationInfo info() const { return info_; }
157 /** \internal Adds the given index @c idx to the inactive set N and updates the QR decomposition of \f$A^N\f$. */
158 void moveToInactiveSet_(Index idx);
160 /** \internal Removes the given index idx from the inactive set N and updates the QR decomposition of \f$A^N\f$. */
161 void moveToActiveSet_(Index idx);
163 /** \internal Solves the least-squares problem \f$\left\Vert y-A^Nx\right\Vert_2^2\f$. */
164 void solveInactiveSet_(const RhsVectorType &b);
167 typedef Matrix<Scalar, ColsAtCompileTime, ColsAtCompileTime> MatrixAtAType;
169 /** \internal Holds the maximum number of iterations for the NNLS algorithm.
170 * @c -1 means to use the default value. */
172 /** \internal Holds the number of iterations. */
174 /** \internal Holds success/fail of the last solve. */
175 ComputationInfo info_;
176 /** \internal Size of the inactive set. */
178 /** \internal Accuracy of the algorithm w.r.t the optimality of the solution (gradient). */
180 /** \internal The system matrix, a copy of the one given to the constructor. */
182 /** \internal Precomputed product \f$A^TA\f$. */
184 /** \internal Will hold the solution. */
185 SolutionVectorType x_;
186 /** \internal Will hold the current gradient.\f$A^Tb - A^TAx\f$ */
187 SolutionVectorType gradient_;
188 /** \internal Will hold the partial solution. */
189 SolutionVectorType y_;
190 /** \internal Precomputed product \f$A^Tb\f$. */
191 SolutionVectorType Atb_;
192 /** \internal Holds the current permutation partitioning the active and inactive sets.
193 * The first @c numInactive_ elements form the inactive set and the rest the active set. */
194 IndicesType index_sets_;
195 /** \internal QR decomposition to solve the (inactive) sub system (together with @c qrCoeffs_). */
197 /** \internal QR decomposition to solve the (inactive) sub system (together with @c QR_). */
198 SolutionVectorType qrCoeffs_;
199 /** \internal Some workspace for QR decomposition. */
200 SolutionVectorType tempSolutionVector_;
201 RhsVectorType tempRhsVector_;
204/* ********************************************************************************************
206 * ******************************************************************************************** */
208template <typename MatrixType>
209NNLS<MatrixType>::NNLS()
212 info_(ComputationInfo::InvalidInput),
214 tolerance_(NumTraits<Scalar>::dummy_precision()) {}
216template <typename MatrixType>
217NNLS<MatrixType>::NNLS(const MatrixType &A, Index max_iter, Scalar tol) : max_iter_(max_iter), tolerance_(tol) {
221template <typename MatrixType>
222template <typename MatrixDerived>
223NNLS<MatrixType> &NNLS<MatrixType>::compute(const EigenBase<MatrixDerived> &A) {
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);
227 // max_iter_: unchanged
229 info_ = ComputationInfo::Success;
231 // tolerance: unchanged
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());
247template <typename MatrixType>
248const typename NNLS<MatrixType>::SolutionVectorType &NNLS<MatrixType>::solve(const RhsVectorType &b) {
251 info_ = ComputationInfo::NumericalIssue;
254 index_sets_ = IndicesType::LinSpaced(A_.cols(), 0, A_.cols() - 1); // Identity permutation.
258 Atb_.noalias() = A_.transpose() * b;
260 const Index maxIterations = this->maxIterations();
264 // Early exit if all variables are inactive, which breaks 'maxCoeff' below.
265 if (A_.cols() == numInactive_) {
266 info_ = ComputationInfo::Success;
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_;
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
280 if (maxGradient < tolerance_) {
281 info_ = ComputationInfo::Success;
285 moveToInactiveSet_(argmaxGradient);
289 // Check if max. number of iterations is reached
290 if (iterations_ >= maxIterations) {
291 info_ = ComputationInfo::NoConvergence;
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.
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];
309 // t should always be in [0,1].
310 Scalar t = -x_(idx) / (y_(idx) - x_(idx));
318 eigen_assert(feasible || 0 <= infeasibleIdx);
320 // If solution is feasible, exit to outer loop
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));
332 // Remove these indices from the inactive set and update QR decomposition
333 moveToActiveSet_(infeasibleIdx);
338template <typename MatrixType>
339void NNLS<MatrixType>::moveToInactiveSet_(Index idx) {
340 // Update permutation matrix:
341 std::swap(index_sets_(idx), index_sets_(numInactive_));
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());
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));
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());
361template <typename MatrixType>
362void NNLS<MatrixType>::solveInactiveSet_(const RhsVectorType &b) {
363 eigen_assert(numInactive_ > 0);
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());
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_)); //
379 // tempSol(numInactive_:end) := 0 = the value for the constrained variables.
380 tempSolutionVector_.tail(y_.size() - numInactive_).setZero();
382 // Back permute into original column order of A
383 y_.noalias() = index_sets_.asPermutation() * tempSolutionVector_.head(y_.size());
388#endif // EIGEN_NNLS_H