| // This file is part of Eigen, a lightweight C++ template library | |
| // for linear algebra. | |
| // | |
| // This Source Code Form is subject to the terms of the Mozilla | |
| // Public License v. 2.0. If a copy of the MPL was not distributed | |
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. | |
| // SPDX-FileCopyrightText: The Eigen Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| #include "sparse_solver.h" | |
| #include <Eigen/IterativeLinearSolvers> | |
| // Sparse square and least-squares solving with the default (identity) and the | |
| // least-squares diagonal preconditioner, in both storage orders. | |
| template <typename T> | |
| void test_lsmr_T() { | |
| LSMR<SparseMatrix<T> > lsmr_colmajor_I; | |
| LSMR<SparseMatrix<T>, LeastSquareDiagonalPreconditioner<T> > lsmr_colmajor_diag; | |
| LSMR<SparseMatrix<T, RowMajor> > lsmr_rowmajor_I; | |
| LSMR<SparseMatrix<T, RowMajor>, LeastSquareDiagonalPreconditioner<T> > lsmr_rowmajor_diag; | |
| CALL_SUBTEST(check_sparse_square_solving(lsmr_colmajor_I)); | |
| CALL_SUBTEST(check_sparse_square_solving(lsmr_colmajor_diag)); | |
| CALL_SUBTEST(check_sparse_leastsquare_solving(lsmr_colmajor_I)); | |
| CALL_SUBTEST(check_sparse_leastsquare_solving(lsmr_colmajor_diag)); | |
| CALL_SUBTEST(check_sparse_square_solving(lsmr_rowmajor_I)); | |
| CALL_SUBTEST(check_sparse_leastsquare_solving(lsmr_rowmajor_diag)); | |
| } | |
| // LSMR on dense, rectangular, overdetermined problems: the solution must match | |
| // the dense QR least-squares solution. | |
| template <typename T> | |
| void test_lsmr_dense() { | |
| typedef typename NumTraits<T>::Real RealScalar; | |
| typedef Matrix<T, Dynamic, Dynamic> DenseMatrix; | |
| typedef Matrix<T, Dynamic, 1> DenseVector; | |
| for (int k = 0; k < g_repeat; ++k) { | |
| Index rows = internal::random<Index>(20, 80); | |
| Index cols = internal::random<Index>(4, rows); | |
| DenseMatrix A = DenseMatrix::Random(rows, cols); | |
| DenseVector b = DenseVector::Random(rows); | |
| LSMR<DenseMatrix> lsmr(A); | |
| lsmr.setTolerance(NumTraits<RealScalar>::epsilon() * RealScalar(100)); | |
| DenseVector x = lsmr.solve(b); | |
| VERIFY_IS_EQUAL(lsmr.info(), Success); | |
| DenseVector xref = A.householderQr().solve(b); | |
| VERIFY_IS_APPROX(x, xref); | |
| } | |
| } | |
| // solveWithGuess() must actually consume the initial guess. Starting from the | |
| // exact solution, the normal-equation residual A^T(b - A x0) is ~0, so LSMR | |
| // converges in very few iterations -- far fewer than from x = 0. Were the guess | |
| // silently dropped, both runs would start from x = 0 and use the same count, so | |
| // the strict iteration-count comparison below is load-bearing. We use cols >= 10 | |
| // so that the from-zero run needs many iterations, giving a wide margin. | |
| template <typename T> | |
| void test_lsmr_guess() { | |
| typedef typename NumTraits<T>::Real RealScalar; | |
| typedef Matrix<T, Dynamic, Dynamic> DenseMatrix; | |
| typedef Matrix<T, Dynamic, 1> DenseVector; | |
| for (int k = 0; k < g_repeat; ++k) { | |
| Index cols = internal::random<Index>(10, 20); | |
| Index rows = cols + internal::random<Index>(10, 40); | |
| DenseMatrix A = DenseMatrix::Random(rows, cols); | |
| DenseVector b = DenseVector::Random(rows); | |
| DenseVector xref = A.householderQr().solve(b); | |
| LSMR<DenseMatrix> lsmr(A); | |
| lsmr.setTolerance(NumTraits<RealScalar>::epsilon() * RealScalar(100)); | |
| DenseVector x0 = lsmr.solve(b); | |
| VERIFY_IS_EQUAL(lsmr.info(), Success); | |
| VERIFY_IS_APPROX(x0, xref); | |
| const Index iters_from_zero = lsmr.iterations(); | |
| DenseVector xg = lsmr.solveWithGuess(b, xref); | |
| VERIFY_IS_EQUAL(lsmr.info(), Success); | |
| VERIFY_IS_APPROX(xg, xref); | |
| VERIFY(lsmr.iterations() < iters_from_zero); | |
| } | |
| } | |
| // On rank-deficient or underdetermined (rows < cols) problems the least-squares | |
| // solution is not unique. Started from x = 0, LSMR returns the *minimum-norm* | |
| // least-squares solution -- exactly what completeOrthogonalDecomposition | |
| // returns, whereas householderQr would return some other valid LS solution. | |
| template <typename T> | |
| void test_lsmr_minnorm() { | |
| typedef typename NumTraits<T>::Real RealScalar; | |
| typedef Matrix<T, Dynamic, Dynamic> DenseMatrix; | |
| typedef Matrix<T, Dynamic, 1> DenseVector; | |
| for (int k = 0; k < g_repeat; ++k) { | |
| // (a) Underdetermined, full row rank: rows < cols, many exact solutions. | |
| { | |
| Index rows = internal::random<Index>(10, 30); | |
| Index cols = rows + internal::random<Index>(5, 30); | |
| DenseMatrix A = DenseMatrix::Random(rows, cols); | |
| DenseVector b = DenseVector::Random(rows); | |
| LSMR<DenseMatrix> lsmr(A); | |
| lsmr.setTolerance(NumTraits<RealScalar>::epsilon() * RealScalar(100)); | |
| DenseVector x = lsmr.solve(b); | |
| VERIFY_IS_EQUAL(lsmr.info(), Success); | |
| DenseVector xref = A.completeOrthogonalDecomposition().solve(b); | |
| VERIFY_IS_APPROX(x, xref); | |
| } | |
| // (b) Explicitly rank-deficient, overdetermined: a duplicated column. | |
| { | |
| Index rows = internal::random<Index>(30, 60); | |
| Index cols = internal::random<Index>(5, 15); | |
| DenseMatrix A = DenseMatrix::Random(rows, cols); | |
| A.col(1) = A.col(0); // make A rank-deficient | |
| DenseVector b = DenseVector::Random(rows); | |
| LSMR<DenseMatrix> lsmr(A); | |
| lsmr.setTolerance(NumTraits<RealScalar>::epsilon() * RealScalar(100)); | |
| DenseVector x = lsmr.solve(b); | |
| VERIFY_IS_EQUAL(lsmr.info(), Success); | |
| DenseVector xref = A.completeOrthogonalDecomposition().solve(b); | |
| VERIFY_IS_APPROX(x, xref); | |
| } | |
| } | |
| } | |
| // With damping lambda > 0 LSMR solves the regularized problem | |
| // min ||Ax-b||^2 + lambda^2 ||x||^2, | |
| // whose solution satisfies the (well-conditioned) normal equation | |
| // (A^T A + lambda^2 I) x = A^T b. | |
| template <typename T> | |
| void test_lsmr_damping() { | |
| typedef typename NumTraits<T>::Real RealScalar; | |
| typedef Matrix<T, Dynamic, Dynamic> DenseMatrix; | |
| typedef Matrix<T, Dynamic, 1> DenseVector; | |
| for (int k = 0; k < g_repeat; ++k) { | |
| Index rows = internal::random<Index>(20, 60); | |
| Index cols = internal::random<Index>(4, rows); | |
| DenseMatrix A = DenseMatrix::Random(rows, cols); | |
| DenseVector b = DenseVector::Random(rows); | |
| RealScalar lambda = internal::random<RealScalar>(RealScalar(0.2), RealScalar(2)); | |
| LSMR<DenseMatrix> lsmr(A); | |
| lsmr.setDamping(lambda); | |
| lsmr.setTolerance(NumTraits<RealScalar>::epsilon() * RealScalar(100)); | |
| DenseVector x = lsmr.solve(b); | |
| VERIFY_IS_EQUAL(lsmr.info(), Success); | |
| DenseMatrix normalMat = A.adjoint() * A + (lambda * lambda) * DenseMatrix::Identity(cols, cols); | |
| DenseVector xref = normalMat.ldlt().solve(A.adjoint() * b); | |
| VERIFY_IS_APPROX(x, xref); | |
| } | |
| } | |
| // Robustness against extreme right-hand-side scaling: LSMR normalizes by ||b|| | |
| // immediately, so the solution should scale linearly with b. | |
| void test_lsmr_extreme_rhs() { | |
| const Matrix2d mat = Matrix2d::Identity(); | |
| const Vector2d direction = (Vector2d() << 1, -1).finished(); | |
| LSMR<Matrix2d> solver(mat); | |
| solver.setTolerance(1e-12); | |
| for (double scale : {1e-200, 1e200}) { | |
| const Vector2d rhs = scale * direction; | |
| Vector2d x = solver.solve(rhs); | |
| VERIFY_IS_EQUAL(solver.info(), Success); | |
| VERIFY(x.allFinite()); | |
| VERIFY_IS_APPROX(x / scale, direction); | |
| } | |
| } | |
| // The atol/btol stopping tolerances can be set independently. setTolerance() | |
| // sets both; setToleranceA()/setToleranceB() override each one, and an unset | |
| // component falls back to tolerance(). A tight atol must still reach the QR | |
| // least-squares solution even when btol is left loose. | |
| template <typename T> | |
| void test_lsmr_tolerances() { | |
| typedef typename NumTraits<T>::Real RealScalar; | |
| typedef Matrix<T, Dynamic, Dynamic> DenseMatrix; | |
| typedef Matrix<T, Dynamic, 1> DenseVector; | |
| const RealScalar def = NumTraits<RealScalar>::epsilon(); | |
| const RealScalar tight = def * RealScalar(100); | |
| // By default both tolerances track tolerance(). | |
| LSMR<DenseMatrix> lsmr; | |
| VERIFY_IS_EQUAL(lsmr.toleranceA(), def); | |
| VERIFY_IS_EQUAL(lsmr.toleranceB(), def); | |
| // setTolerance() sets both. | |
| lsmr.setTolerance(tight); | |
| VERIFY_IS_EQUAL(lsmr.toleranceA(), tight); | |
| VERIFY_IS_EQUAL(lsmr.toleranceB(), tight); | |
| // An explicit override wins; the other component still tracks tolerance(). | |
| lsmr.setToleranceA(def); | |
| VERIFY_IS_EQUAL(lsmr.toleranceA(), def); | |
| VERIFY_IS_EQUAL(lsmr.toleranceB(), tight); | |
| lsmr.setToleranceB(def); | |
| VERIFY_IS_EQUAL(lsmr.toleranceB(), def); | |
| // Use a strongly overdetermined, inconsistent system (rows >> cols) so the | |
| // minimum-residual ratio ||A x_LS - b|| / ||b|| stays well above btol. That | |
| // keeps the residual stopping rule (istop 1, governed by btol) from firing, | |
| // leaving the tight atol to drive convergence to the least-squares solution. | |
| // A near-square system would let ||r||/||b|| dip below btol first, stopping | |
| // LSMR early on an iterate that is nowhere near the QR solution. | |
| for (int k = 0; k < g_repeat; ++k) { | |
| Index rows = internal::random<Index>(40, 80); | |
| Index cols = internal::random<Index>(4, rows / 4); | |
| DenseMatrix A = DenseMatrix::Random(rows, cols); | |
| DenseVector b = DenseVector::Random(rows); | |
| DenseVector xref = A.householderQr().solve(b); | |
| // Tight atol drives the least-squares stopping rule, so the solution must | |
| // match QR even with btol left loose. | |
| LSMR<DenseMatrix> solver(A); | |
| solver.setToleranceA(tight).setToleranceB(RealScalar(0.1)); | |
| DenseVector x = solver.solve(b); | |
| VERIFY_IS_EQUAL(solver.info(), Success); | |
| VERIFY_IS_APPROX(x, xref); | |
| } | |
| } | |
| EIGEN_DECLARE_TEST(lsmr) { | |
| CALL_SUBTEST_1(test_lsmr_T<double>()); | |
| CALL_SUBTEST_2(test_lsmr_T<std::complex<double> >()); | |
| CALL_SUBTEST_3(test_lsmr_dense<double>()); | |
| CALL_SUBTEST_4(test_lsmr_dense<std::complex<double> >()); | |
| CALL_SUBTEST_5(test_lsmr_damping<double>()); | |
| CALL_SUBTEST_6(test_lsmr_extreme_rhs()); | |
| CALL_SUBTEST_7(test_lsmr_minnorm<double>()); | |
| CALL_SUBTEST_8(test_lsmr_damping<std::complex<double> >()); | |
| CALL_SUBTEST_9(test_lsmr_minnorm<std::complex<double> >()); | |
| CALL_SUBTEST_10(test_lsmr_guess<double>()); | |
| CALL_SUBTEST_11(test_lsmr_tolerances<double>()); | |
| } |