25 #include "Eigen/Eigenvalues"
27 #include "Eigen/Cholesky"
28 #include "boost/format.hpp"
39 namespace lsst {
namespace afw {
namespace math {
71 void setRank(Eigen::MatrixBase<D>
const & values) {
83 int toAdd = ~
state & desired;
87 fisher.selfadjointView<Eigen::Lower>().rankUpdate(
design.adjoint());
89 if (toAdd & FULL_FISHER_MATRIX) {
90 fisher.triangularView<Eigen::StrictlyUpper>() =
fisher.adjoint();
111 virtual void factor() = 0;
120 Impl(
int dimension_,
double threshold_=std::numeric_limits<double>::epsilon()) :
132 explicit EigensystemSolver(
int dimension) :
133 Impl(dimension),
_eig(dimension),
_svd(),
_tmp(dimension)
136 virtual void factor() {
137 ensure(LOWER_FISHER_MATRIX | RHS_VECTOR);
138 _eig.compute(fisher);
139 if (
_eig.info() == Eigen::Success) {
140 setRank(
_eig.eigenvalues().reverse());
141 LOGL_DEBUG(_log,
"SelfAdjointEigenSolver succeeded: dimension=%d, rank=%d", dimension, rank);
146 ensure(FULL_FISHER_MATRIX);
147 _svd.compute(fisher, Eigen::ComputeFullU);
148 setRank(
_svd.singularValues());
150 "SelfAdjointEigenSolver failed; falling back to equivalent SVD: dimension=%d, rank=%d",
156 virtual void updateRank() {
157 if (
_eig.info() == Eigen::Success) {
158 setRank(
_eig.eigenvalues().reverse());
160 setRank(
_svd.singularValues());
164 virtual void updateDiagnostic() {
167 pex::exceptions::LogicError,
168 "Cannot compute NORMAL_CHOLESKY diagnostic from NORMAL_EIGENSYSTEM factorization."
171 if (
_eig.info() == Eigen::Success) {
172 diagnostic.asEigen() =
_eig.eigenvalues().reverse();
174 diagnostic.asEigen() =
_svd.singularValues();
177 diagnostic.asEigen<Eigen::ArrayXpr>() = diagnostic.asEigen<Eigen::ArrayXpr>().sqrt();
181 virtual void updateSolution() {
183 solution.asEigen().setZero();
186 if (
_eig.info() == Eigen::Success) {
187 _tmp.head(rank) =
_eig.eigenvectors().rightCols(rank).adjoint() * rhs;
188 _tmp.head(rank).array() /=
_eig.eigenvalues().tail(rank).array();
189 solution.asEigen() =
_eig.eigenvectors().rightCols(rank) *
_tmp.head(rank);
191 _tmp.head(rank) =
_svd.matrixU().leftCols(rank).adjoint() * rhs;
192 _tmp.head(rank).array() /=
_svd.singularValues().head(rank).array();
193 solution.asEigen() =
_svd.matrixU().leftCols(rank) *
_tmp.head(rank);
197 virtual void updateCovariance() {
199 covariance.asEigen().setZero();
202 if (
_eig.info() == Eigen::Success) {
203 covariance.asEigen() =
204 _eig.eigenvectors().rightCols(rank)
205 *
_eig.eigenvalues().tail(rank).array().inverse().matrix().asDiagonal()
206 *
_eig.eigenvectors().rightCols(rank).adjoint();
208 covariance.asEigen() =
209 _svd.matrixU().leftCols(rank)
210 *
_svd.singularValues().head(rank).array().inverse().matrix().asDiagonal()
211 *
_svd.matrixU().leftCols(rank).adjoint();
216 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd>
_eig;
217 Eigen::JacobiSVD<Eigen::MatrixXd>
_svd;
221 class CholeskySolver :
public LeastSquares::Impl {
224 explicit CholeskySolver(
int dimension) : Impl(dimension, 0.0),
_ldlt(dimension) {}
226 virtual void factor() {
227 ensure(LOWER_FISHER_MATRIX | RHS_VECTOR);
228 _ldlt.compute(fisher);
231 virtual void updateRank() {}
233 virtual void updateDiagnostic() {
236 pex::exceptions::LogicError,
237 "Can only compute NORMAL_CHOLESKY diagnostic from NORMAL_CHOLESKY factorization."
240 diagnostic.asEigen() =
_ldlt.vectorD();
243 virtual void updateSolution() { solution.asEigen() =
_ldlt.solve(rhs); }
245 virtual void updateCovariance() {
246 ndarray::EigenView<double,2,2> cov(covariance);
248 cov =
_ldlt.solve(cov);
255 class SvdSolver :
public LeastSquares::Impl {
258 explicit SvdSolver(
int dimension) : Impl(dimension),
_svd(),
_tmp(dimension) {}
260 virtual void factor() {
261 if (!(state & DESIGN_AND_DATA)) {
263 pex::exceptions::InvalidParameterError,
264 "Cannot initialize DIRECT_SVD solver with normal equations."
267 _svd.compute(design, Eigen::ComputeThinU | Eigen::ComputeThinV);
268 setRank(
_svd.singularValues());
269 LOGL_DEBUG(_log,
"Using direct SVD method; dimension=%d, rank=%d", dimension, rank);
272 virtual void updateRank() { setRank(
_svd.singularValues()); }
274 virtual void updateDiagnostic() {
275 switch (whichDiagnostic) {
277 diagnostic.asEigen<Eigen::ArrayXpr>() =
_svd.singularValues().array().square();
281 pex::exceptions::LogicError,
282 "Can only compute NORMAL_CHOLESKY diagnostic from DIRECT_SVD factorization."
285 diagnostic.asEigen() =
_svd.singularValues();
290 virtual void updateSolution() {
292 solution.asEigen().setZero();
295 _tmp.head(rank) =
_svd.matrixU().leftCols(rank).adjoint() * data;
296 _tmp.head(rank).array() /=
_svd.singularValues().head(rank).array();
297 solution.asEigen() =
_svd.matrixV().leftCols(rank) *
_tmp.head(rank);
300 virtual void updateCovariance() {
302 covariance.asEigen().setZero();
305 covariance.asEigen() =
306 _svd.matrixV().leftCols(rank)
307 *
_svd.singularValues().head(rank).array().inverse().square().matrix().asDiagonal()
308 *
_svd.matrixV().leftCols(rank).adjoint();
312 Eigen::JacobiSVD<Eigen::MatrixXd>
_svd;
313 Eigen::VectorXd
_tmp;
319 _impl->threshold = threshold;
329 return _impl->solution;
334 return _impl->covariance;
341 return ndarray::external(
342 _impl->fisher.data(),
343 ndarray::makeVector(
_impl->dimension,
_impl->dimension),
344 ndarray::makeVector(
_impl->dimension, 1),
350 if (
_impl->whichDiagnostic != factorization) {
352 _impl->whichDiagnostic = factorization;
355 return _impl->diagnostic;
365 switch (factorization) {
367 _impl = std::make_shared<EigensystemSolver>(dimension);
370 _impl = std::make_shared<CholeskySolver>(dimension);
373 _impl = std::make_shared<SvdSolver>(dimension);
376 _impl->factorization = factorization;
388 if (haveNormalEquations) {
391 pex::exceptions::InvalidParameterError,
392 (
boost::format(
"Number of rows of Fisher matrix (%d) does not match"
393 " dimension of LeastSquares solver.")
399 pex::exceptions::InvalidParameterError,
400 (
boost::format(
"Number of columns of Fisher matrix (%d) does not match"
401 " dimension of LeastSquares solver.")
407 pex::exceptions::InvalidParameterError,
408 (
boost::format(
"Number of elements in RHS vector (%d) does not match"
409 " dimension of LeastSquares solver.")
417 pex::exceptions::InvalidParameterError,
418 "Number of columns of design matrix does not match dimension of LeastSquares solver."
423 pex::exceptions::InvalidParameterError,
424 (
boost::format(
"Number of rows of design matrix (%d) does not match number of "
431 pex::exceptions::InvalidParameterError,
432 (
boost::format(
"Number of columns of design matrix (%d) must be smaller than number of "
virtual void updateRank()=0
Eigen::LDLT< Eigen::MatrixXd > _ldlt
Impl(int dimension_, double threshold_=std::numeric_limits< double >::epsilon())
Eigen::SelfAdjointEigenSolver< Eigen::MatrixXd > _eig
void _factor(bool haveNormalEquations)
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.
Eigen::VectorXd & _getDataVector()
Include files required for standard LSST Exception handling.
ndarray::Array< double, 1, 1 > diagnostic
LeastSquares(Factorization factorization, int dimension)
Construct a least-squares object for the given factorization and dimensionality.
virtual void updateDiagnostic()=0
Use a thin singular value decomposition of the design matrix.
#define LOGL_DEBUG(logger, message...)
Log a debug-level message using a varargs/printf style interface.
Use the normal equations with a Cholesky decomposition.
LSST DM logging module built on log4cxx.
Factorization factorization
int getRank() const
Return the rank of the problem (number of nonzero Eigenvalues).
Use the normal equations with a symmetric Eigensystem decomposition.
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...
boost::shared_ptr< Impl > _impl
ndarray::Array< double, 1, 1 > solution
ndarray::Array< double const, 2, 2 > getCovariance()
Return the covariance matrix of the least squares problem.
virtual void updateSolution()=0
virtual void updateCovariance()=0
#define LSST_EXCEPT(type,...)
Create an exception with a given type and message and optionally other arguments (dependent on the ty...
Factorization whichDiagnostic
Factorization
Private implementation; forward-declared publicly so we can inherit from it in .cc.
Factorization getFactorization() const
Retun the type of factorization used by the solver.
ndarray::Array< double const, 1, 1 > getSolution()
Return the vector solution to the least squares problem.
Eigen::MatrixXd & _getFisherMatrix()
void setRank(Eigen::MatrixBase< D > const &values)
Eigen::MatrixXd & _getDesignMatrix()
ndarray::Array< double, 2, 2 > covariance
int getDimension() const
Return the number of parameters.
#define LOG_GET(logger)
Returns a Log object associated with logger.
ndarray::Array< double const, 2, 2 > getFisherMatrix()
Return the Fisher matrix (inverse of the covariance) of the parameters.
Eigen::JacobiSVD< Eigen::MatrixXd > _svd
Eigen::VectorXd & _getRhsVector()