25 #include "Eigen/Eigenvalues"
27 #include "Eigen/Cholesky"
28 #include "boost/format.hpp"
72 void setRank(Eigen::MatrixBase<D>
const& values) {
85 int toAdd = ~
state & desired;
89 fisher.selfadjointView<Eigen::Lower>().rankUpdate(
design.adjoint());
92 fisher.triangularView<Eigen::StrictlyUpper>() =
fisher.adjoint();
132 explicit EigensystemSolver(
int dimension) : Impl(dimension), _eig(dimension), _svd(), _tmp(dimension) {}
134 void factor()
override {
135 ensure(LOWER_FISHER_MATRIX | RHS_VECTOR);
136 _eig.compute(fisher);
137 if (_eig.info() == Eigen::Success) {
138 setRank(_eig.eigenvalues().reverse());
139 LOGL_DEBUG(_log,
"SelfAdjointEigenSolver succeeded: dimension=%d, rank=%d", dimension, rank);
144 ensure(FULL_FISHER_MATRIX);
145 _svd.compute(fisher, Eigen::ComputeFullU);
146 setRank(_svd.singularValues());
148 "SelfAdjointEigenSolver failed; falling back to equivalent SVD: dimension=%d, rank=%d",
153 void updateRank()
override {
154 if (_eig.info() == Eigen::Success) {
155 setRank(_eig.eigenvalues().reverse());
157 setRank(_svd.singularValues());
161 void updateDiagnostic()
override {
164 pex::exceptions::LogicError,
165 "Cannot compute NORMAL_CHOLESKY diagnostic from NORMAL_EIGENSYSTEM factorization.");
167 if (_eig.info() == Eigen::Success) {
168 ndarray::asEigenMatrix(diagnostic) = _eig.eigenvalues().reverse();
170 ndarray::asEigenMatrix(diagnostic) = _svd.singularValues();
173 ndarray::asEigenArray(diagnostic) = ndarray::asEigenArray(diagnostic).sqrt();
177 void updateSolution()
override {
179 ndarray::asEigenMatrix(solution).setZero();
182 if (_eig.info() == Eigen::Success) {
183 _tmp.head(rank) = _eig.eigenvectors().rightCols(rank).adjoint() * rhs;
184 _tmp.head(rank).array() /= _eig.eigenvalues().tail(rank).array();
185 ndarray::asEigenMatrix(solution) = _eig.eigenvectors().rightCols(rank) * _tmp.head(rank);
187 _tmp.head(rank) = _svd.matrixU().leftCols(rank).adjoint() * rhs;
188 _tmp.head(rank).array() /= _svd.singularValues().head(rank).array();
189 ndarray::asEigenMatrix(solution) = _svd.matrixU().leftCols(rank) * _tmp.head(rank);
193 void updateCovariance()
override {
198 if (_eig.info() == Eigen::Success) {
200 _eig.eigenvectors().rightCols(rank) *
201 _eig.eigenvalues().tail(rank).array().inverse().matrix().asDiagonal() *
202 _eig.eigenvectors().rightCols(rank).adjoint();
205 _svd.matrixU().leftCols(rank) *
206 _svd.singularValues().head(rank).array().inverse().matrix().asDiagonal() *
207 _svd.matrixU().leftCols(rank).adjoint();
212 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> _eig;
213 Eigen::JacobiSVD<Eigen::MatrixXd> _svd;
214 Eigen::VectorXd _tmp;
217 class CholeskySolver :
public LeastSquares::Impl {
219 explicit CholeskySolver(
int dimension) : Impl(dimension, 0.0), _ldlt(dimension) {}
221 void factor()
override {
222 ensure(LOWER_FISHER_MATRIX | RHS_VECTOR);
223 _ldlt.compute(fisher);
226 void updateRank()
override {}
228 void updateDiagnostic()
override {
231 pex::exceptions::LogicError,
232 "Can only compute NORMAL_CHOLESKY diagnostic from NORMAL_CHOLESKY factorization.");
234 ndarray::asEigenMatrix(diagnostic) = _ldlt.vectorD();
237 void updateSolution()
override { ndarray::asEigenMatrix(solution) = _ldlt.solve(rhs); }
239 void updateCovariance()
override {
240 auto cov = ndarray::asEigenMatrix(
covariance);
242 cov = _ldlt.solve(cov);
246 Eigen::LDLT<Eigen::MatrixXd> _ldlt;
249 class SvdSolver :
public LeastSquares::Impl {
251 explicit SvdSolver(
int dimension) : Impl(dimension), _svd(), _tmp(dimension) {}
253 void factor()
override {
254 if (!(state & DESIGN_AND_DATA)) {
255 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
256 "Cannot initialize DIRECT_SVD solver with normal equations.");
258 _svd.compute(design, Eigen::ComputeThinU | Eigen::ComputeThinV);
259 setRank(_svd.singularValues());
260 LOGL_DEBUG(_log,
"Using direct SVD method; dimension=%d, rank=%d", dimension, rank);
263 void updateRank()
override { setRank(_svd.singularValues()); }
265 void updateDiagnostic()
override {
266 switch (whichDiagnostic) {
268 ndarray::asEigenArray(diagnostic) = _svd.singularValues().array().square();
272 pex::exceptions::LogicError,
273 "Can only compute NORMAL_CHOLESKY diagnostic from DIRECT_SVD factorization.");
275 ndarray::asEigenMatrix(diagnostic) = _svd.singularValues();
280 void updateSolution()
override {
282 ndarray::asEigenMatrix(solution).setZero();
285 _tmp.head(rank) = _svd.matrixU().leftCols(rank).adjoint() *
data;
286 _tmp.head(rank).array() /= _svd.singularValues().head(rank).array();
287 ndarray::asEigenMatrix(solution) = _svd.matrixV().leftCols(rank) * _tmp.head(rank);
290 void updateCovariance()
override {
296 _svd.matrixV().leftCols(rank) *
297 _svd.singularValues().head(rank).array().inverse().square().matrix().asDiagonal() *
298 _svd.matrixV().leftCols(rank).adjoint();
302 Eigen::JacobiSVD<Eigen::MatrixXd> _svd;
303 Eigen::VectorXd _tmp;
309 _impl->threshold = threshold;
310 _impl->state &= ~
Impl::SOLUTION_ARRAY;
311 _impl->state &= ~
Impl::COVARIANCE_ARRAY;
319 return _impl->solution;
324 return _impl->covariance;
331 return ndarray::external(_impl->fisher.data(), ndarray::makeVector(_impl->dimension, _impl->dimension),
332 ndarray::makeVector(_impl->dimension, 1), _impl);
336 if (_impl->whichDiagnostic != factorization) {
337 _impl->state &= ~
Impl::DIAGNOSTIC_ARRAY;
338 _impl->whichDiagnostic = factorization;
341 return _impl->diagnostic;
351 switch (factorization) {
353 _impl = std::make_shared<EigensystemSolver>(dimension);
356 _impl = std::make_shared<CholeskySolver>(dimension);
359 _impl = std::make_shared<SvdSolver>(dimension);
362 _impl->factorization = factorization;
372 Eigen::MatrixXd& LeastSquares::_getDesignMatrix() {
return _impl->design; }
373 Eigen::VectorXd& LeastSquares::_getDataVector() {
return _impl->data; }
375 Eigen::MatrixXd& LeastSquares::_getFisherMatrix() {
return _impl->fisher; }
376 Eigen::VectorXd& LeastSquares::_getRhsVector() {
return _impl->rhs; }
378 void LeastSquares::_factor(
bool haveNormalEquations) {
379 if (haveNormalEquations) {
380 if (_getFisherMatrix().rows() != _impl->dimension) {
381 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
382 (
boost::format(
"Number of rows of Fisher matrix (%d) does not match"
383 " dimension of LeastSquares solver.") %
384 _getFisherMatrix().rows() % _impl->dimension)
387 if (_getFisherMatrix().cols() != _impl->dimension) {
388 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
389 (
boost::format(
"Number of columns of Fisher matrix (%d) does not match"
390 " dimension of LeastSquares solver.") %
391 _getFisherMatrix().cols() % _impl->dimension)
394 if (_getRhsVector().size() != _impl->dimension) {
395 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
396 (
boost::format(
"Number of elements in RHS vector (%d) does not match"
397 " dimension of LeastSquares solver.") %
398 _getRhsVector().size() % _impl->dimension)
403 if (_getDesignMatrix().cols() != _impl->dimension) {
405 pex::exceptions::InvalidParameterError,
406 "Number of columns of design matrix does not match dimension of LeastSquares solver.");
408 if (_getDesignMatrix().rows() != _getDataVector().size()) {
409 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
410 (
boost::format(
"Number of rows of design matrix (%d) does not match number of "
411 "data points (%d)") %
412 _getDesignMatrix().rows() % _getDataVector().size())
415 if (_getDesignMatrix().cols() > _getDataVector().size()) {
417 pex::exceptions::InvalidParameterError,
418 (
boost::format(
"Number of columns of design matrix (%d) must be smaller than number of "
419 "data points (%d)") %
420 _getDesignMatrix().cols() % _getDataVector().size())
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
LSST DM logging module built on log4cxx.
#define LOG_GET(logger)
Returns a Log object associated with logger.
#define LOGL_DEBUG(logger, message...)
Log a debug-level message using a varargs/printf style interface.
virtual void updateRank()=0
ndarray::Array< double, 1, 1 > solution
ndarray::Array< double, 2, 2 > covariance
virtual void updateSolution()=0
virtual void updateCovariance()=0
Factorization whichDiagnostic
ndarray::Array< double, 1, 1 > diagnostic
virtual void updateDiagnostic()=0
Factorization factorization
void setRank(Eigen::MatrixBase< D > const &values)
Impl(int dimension_, double threshold_=std::numeric_limits< double >::epsilon())
Solver for linear least-squares problems.
ndarray::Array< double const, 1, 1 > getSolution()
Return the vector solution to the least squares problem.
ndarray::Array< double const, 2, 2 > getFisherMatrix()
Return the Fisher matrix (inverse of the covariance) of the parameters.
int getRank() const
Return the rank of the problem (number of nonzero Eigenvalues).
ndarray::Array< double const, 2, 2 > getCovariance()
Return the covariance matrix of the least squares problem.
LeastSquares & operator=(LeastSquares const &)
Factorization
Private implementation; forward-declared publicly so we can inherit from it in .cc.
@ NORMAL_EIGENSYSTEM
Use the normal equations with a symmetric Eigensystem decomposition.
@ NORMAL_CHOLESKY
Use the normal equations with a Cholesky decomposition.
@ DIRECT_SVD
Use a thin singular value decomposition of the design matrix.
LeastSquares(Factorization factorization, int dimension)
Construct a least-squares object for the given factorization and dimensionality.
int getDimension() const
Return the number of parameters.
Factorization getFactorization() const
Retun the type of factorization used by the solver.
ndarray::Array< double const, 1, 1 > getDiagnostic(Factorization factorization)
Return a factorization-dependent vector that can be used to characterize the stability of the solutio...
double getThreshold() const
Get the threshold used to determine when to truncate Eigenvalues.
void setThreshold(double threshold)
Set the threshold used to determine when to truncate Eigenvalues.
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
A base class for image defects.