25 #include "Eigen/Eigenvalues" 27 #include "Eigen/Cholesky" 28 #include "boost/format.hpp" 46 LOWER_FISHER_MATRIX = 0x001,
47 FULL_FISHER_MATRIX = 0x002,
49 SOLUTION_ARRAY = 0x008,
50 COVARIANCE_ARRAY = 0x010,
51 DIAGNOSTIC_ARRAY = 0x020,
52 DESIGN_AND_DATA = 0x040
72 void setRank(Eigen::MatrixBase<D>
const& values) {
73 double cond = threshold * values[0];
77 for (rank = dimension; (rank > 1) && (values[rank - 1] < cond); --rank)
83 if (state & FULL_FISHER_MATRIX) state |= LOWER_FISHER_MATRIX;
84 if (desired & FULL_FISHER_MATRIX) desired |= LOWER_FISHER_MATRIX;
85 int toAdd = ~state & desired;
86 if (toAdd & LOWER_FISHER_MATRIX) {
87 assert(state & DESIGN_AND_DATA);
88 fisher = Eigen::MatrixXd::Zero(design.cols(), design.cols());
89 fisher.selfadjointView<Eigen::Lower>().rankUpdate(design.adjoint());
91 if (toAdd & FULL_FISHER_MATRIX) {
92 fisher.triangularView<Eigen::StrictlyUpper>() = fisher.adjoint();
94 if (toAdd & RHS_VECTOR) {
95 assert(state & DESIGN_AND_DATA);
96 rhs = design.adjoint() *
data;
98 if (toAdd & SOLUTION_ARRAY) {
99 if (solution.isEmpty()) solution = ndarray::allocate(dimension);
102 if (toAdd & COVARIANCE_ARRAY) {
103 if (covariance.isEmpty()) covariance = ndarray::allocate(dimension, dimension);
106 if (toAdd & DIAGNOSTIC_ARRAY) {
107 if (diagnostic.isEmpty()) diagnostic = ndarray::allocate(dimension);
113 virtual void factor() = 0;
115 virtual void updateRank() = 0;
117 virtual void updateSolution() = 0;
118 virtual void updateCovariance() = 0;
120 virtual void updateDiagnostic() = 0;
123 : state(0), dimension(dimension_), rank(dimension_), threshold(threshold_) {}
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 {
162 if (whichDiagnostic == LeastSquares::NORMAL_CHOLESKY) {
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();
172 if (whichDiagnostic == LeastSquares::DIRECT_SVD) {
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 {
195 ndarray::asEigenMatrix(covariance).setZero();
198 if (_eig.info() == Eigen::Success) {
199 ndarray::asEigenMatrix(covariance) =
200 _eig.eigenvectors().rightCols(rank) *
201 _eig.eigenvalues().tail(rank).array().inverse().matrix().asDiagonal() *
202 _eig.eigenvectors().rightCols(rank).adjoint();
204 ndarray::asEigenMatrix(covariance) =
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 {
229 if (whichDiagnostic != LeastSquares::NORMAL_CHOLESKY) {
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;
251 explicit SvdSolver(
int dimension) :
Impl(dimension), _svd(), _tmp(dimension) {}
253 void factor()
override {
254 if (!(state & DESIGN_AND_DATA)) {
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) {
267 case LeastSquares::NORMAL_EIGENSYSTEM:
268 ndarray::asEigenArray(diagnostic) = _svd.singularValues().array().square();
270 case LeastSquares::NORMAL_CHOLESKY:
273 "Can only compute NORMAL_CHOLESKY diagnostic from DIRECT_SVD factorization.");
274 case LeastSquares::DIRECT_SVD:
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 {
292 ndarray::asEigenMatrix(covariance).setZero();
295 ndarray::asEigenMatrix(covariance) =
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;
308 void LeastSquares::setThreshold(
double threshold) {
309 _impl->threshold = threshold;
310 _impl->state &= ~
Impl::SOLUTION_ARRAY;
311 _impl->state &= ~
Impl::COVARIANCE_ARRAY;
315 double LeastSquares::getThreshold()
const {
return _impl->threshold; }
317 ndarray::Array<double const, 1, 1> LeastSquares::getSolution() {
318 _impl->ensure(Impl::SOLUTION_ARRAY);
319 return _impl->solution;
322 ndarray::Array<double const, 2, 2> LeastSquares::getCovariance() {
323 _impl->ensure(Impl::COVARIANCE_ARRAY);
324 return _impl->covariance;
327 ndarray::Array<double const, 2, 2> LeastSquares::getFisherMatrix() {
328 _impl->ensure(Impl::FULL_FISHER_MATRIX);
331 return ndarray::external(_impl->fisher.data(), ndarray::makeVector(_impl->dimension, _impl->dimension),
332 ndarray::makeVector(_impl->dimension, 1), _impl);
335 ndarray::Array<double const, 1, 1> LeastSquares::getDiagnostic(
Factorization factorization) {
336 if (_impl->whichDiagnostic != factorization) {
337 _impl->state &= ~
Impl::DIAGNOSTIC_ARRAY;
338 _impl->whichDiagnostic = factorization;
340 _impl->ensure(Impl::DIAGNOSTIC_ARRAY);
341 return _impl->diagnostic;
344 int LeastSquares::getDimension()
const {
return _impl->dimension; }
346 int LeastSquares::getRank()
const {
return _impl->rank; }
351 switch (factorization) {
352 case NORMAL_EIGENSYSTEM:
353 _impl = std::make_shared<EigensystemSolver>(dimension);
355 case NORMAL_CHOLESKY:
356 _impl = std::make_shared<CholeskySolver>(dimension);
359 _impl = std::make_shared<SvdSolver>(dimension);
362 _impl->factorization = factorization;
365 LeastSquares::LeastSquares(
LeastSquares const&) =
default;
370 LeastSquares::~LeastSquares() =
default;
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) {
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) {
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) {
396 (
boost::format(
"Number of elements in RHS vector (%d) does not match" 397 " dimension of LeastSquares solver.") %
398 _getRhsVector().size() % _impl->dimension)
401 _impl->state = Impl::RHS_VECTOR | Impl::FULL_FISHER_MATRIX | Impl::LOWER_FISHER_MATRIX;
403 if (_getDesignMatrix().cols() != _impl->dimension) {
406 "Number of columns of design matrix does not match dimension of LeastSquares solver.");
408 if (_getDesignMatrix().rows() != _getDataVector().size()) {
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()) {
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())
423 _impl->state = Impl::DESIGN_AND_DATA;
Impl(int dimension_, double threshold_=std::numeric_limits< double >::epsilon())
Solver for linear least-squares problems.
#define LOGL_DEBUG(logger, message...)
Log a debug-level message using a varargs/printf style interface.
LSST DM logging module built on log4cxx.
ndarray::Array< double, 1, 1 > diagnostic
Factorization factorization
A base class for image defects.
ndarray::Array< double, 1, 1 > solution
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Reports errors in the logical structure of the program.
ndarray::Array< double, 2, 2 > covariance
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Factorization whichDiagnostic
Factorization
Private implementation; forward-declared publicly so we can inherit from it in .cc.
Reports invalid arguments.
void setRank(Eigen::MatrixBase< D > const &values)
#define LOG_GET(logger)
Returns a Log object associated with logger.