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 {
 
  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;
 
  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;
 
  332                              ndarray::makeVector(_impl->
dimension, 1), _impl);
 
  351     switch (factorization) {
 
  353             _impl = std::make_shared<EigensystemSolver>(dimension);
 
  356             _impl = std::make_shared<CholeskySolver>(dimension);
 
  359             _impl = std::make_shared<SvdSolver>(dimension);
 
  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.
 
Reports errors in the logical structure of the program.
 
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
 
A base class for image defects.