Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 1 | // This file is part of Eigen, a lightweight C++ template library |
| 2 | // for linear algebra. |
| 3 | // |
| 4 | // Copyright (C) 2008 Gael Guennebaud <gael.guennebaud@inria.fr> |
| 5 | // Copyright (C) 2009 Benoit Jacob <jacob.benoit.1@gmail.com> |
| 6 | // |
| 7 | // This Source Code Form is subject to the terms of the Mozilla |
| 8 | // Public License v. 2.0. If a copy of the MPL was not distributed |
| 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| 10 | |
| 11 | #include "main.h" |
| 12 | #include <Eigen/QR> |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame] | 13 | #include <Eigen/SVD> |
| 14 | |
| 15 | template <typename MatrixType> |
| 16 | void cod() { |
| 17 | Index rows = internal::random<Index>(2, EIGEN_TEST_MAX_SIZE); |
| 18 | Index cols = internal::random<Index>(2, EIGEN_TEST_MAX_SIZE); |
| 19 | Index cols2 = internal::random<Index>(2, EIGEN_TEST_MAX_SIZE); |
| 20 | Index rank = internal::random<Index>(1, (std::min)(rows, cols) - 1); |
| 21 | |
| 22 | typedef typename MatrixType::Scalar Scalar; |
| 23 | typedef Matrix<Scalar, MatrixType::RowsAtCompileTime, |
| 24 | MatrixType::RowsAtCompileTime> |
| 25 | MatrixQType; |
| 26 | MatrixType matrix; |
| 27 | createRandomPIMatrixOfRank(rank, rows, cols, matrix); |
| 28 | CompleteOrthogonalDecomposition<MatrixType> cod(matrix); |
| 29 | VERIFY(rank == cod.rank()); |
| 30 | VERIFY(cols - cod.rank() == cod.dimensionOfKernel()); |
| 31 | VERIFY(!cod.isInjective()); |
| 32 | VERIFY(!cod.isInvertible()); |
| 33 | VERIFY(!cod.isSurjective()); |
| 34 | |
| 35 | MatrixQType q = cod.householderQ(); |
| 36 | VERIFY_IS_UNITARY(q); |
| 37 | |
| 38 | MatrixType z = cod.matrixZ(); |
| 39 | VERIFY_IS_UNITARY(z); |
| 40 | |
| 41 | MatrixType t; |
| 42 | t.setZero(rows, cols); |
| 43 | t.topLeftCorner(rank, rank) = |
| 44 | cod.matrixT().topLeftCorner(rank, rank).template triangularView<Upper>(); |
| 45 | |
| 46 | MatrixType c = q * t * z * cod.colsPermutation().inverse(); |
| 47 | VERIFY_IS_APPROX(matrix, c); |
| 48 | |
| 49 | MatrixType exact_solution = MatrixType::Random(cols, cols2); |
| 50 | MatrixType rhs = matrix * exact_solution; |
| 51 | MatrixType cod_solution = cod.solve(rhs); |
| 52 | VERIFY_IS_APPROX(rhs, matrix * cod_solution); |
| 53 | |
| 54 | // Verify that we get the same minimum-norm solution as the SVD. |
| 55 | JacobiSVD<MatrixType> svd(matrix, ComputeThinU | ComputeThinV); |
| 56 | MatrixType svd_solution = svd.solve(rhs); |
| 57 | VERIFY_IS_APPROX(cod_solution, svd_solution); |
| 58 | |
| 59 | MatrixType pinv = cod.pseudoInverse(); |
| 60 | VERIFY_IS_APPROX(cod_solution, pinv * rhs); |
| 61 | } |
| 62 | |
| 63 | template <typename MatrixType, int Cols2> |
| 64 | void cod_fixedsize() { |
| 65 | enum { |
| 66 | Rows = MatrixType::RowsAtCompileTime, |
| 67 | Cols = MatrixType::ColsAtCompileTime |
| 68 | }; |
| 69 | typedef typename MatrixType::Scalar Scalar; |
| 70 | int rank = internal::random<int>(1, (std::min)(int(Rows), int(Cols)) - 1); |
| 71 | Matrix<Scalar, Rows, Cols> matrix; |
| 72 | createRandomPIMatrixOfRank(rank, Rows, Cols, matrix); |
| 73 | CompleteOrthogonalDecomposition<Matrix<Scalar, Rows, Cols> > cod(matrix); |
| 74 | VERIFY(rank == cod.rank()); |
| 75 | VERIFY(Cols - cod.rank() == cod.dimensionOfKernel()); |
| 76 | VERIFY(cod.isInjective() == (rank == Rows)); |
| 77 | VERIFY(cod.isSurjective() == (rank == Cols)); |
| 78 | VERIFY(cod.isInvertible() == (cod.isInjective() && cod.isSurjective())); |
| 79 | |
| 80 | Matrix<Scalar, Cols, Cols2> exact_solution; |
| 81 | exact_solution.setRandom(Cols, Cols2); |
| 82 | Matrix<Scalar, Rows, Cols2> rhs = matrix * exact_solution; |
| 83 | Matrix<Scalar, Cols, Cols2> cod_solution = cod.solve(rhs); |
| 84 | VERIFY_IS_APPROX(rhs, matrix * cod_solution); |
| 85 | |
| 86 | // Verify that we get the same minimum-norm solution as the SVD. |
| 87 | JacobiSVD<MatrixType> svd(matrix, ComputeFullU | ComputeFullV); |
| 88 | Matrix<Scalar, Cols, Cols2> svd_solution = svd.solve(rhs); |
| 89 | VERIFY_IS_APPROX(cod_solution, svd_solution); |
| 90 | } |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 91 | |
| 92 | template<typename MatrixType> void qr() |
| 93 | { |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame] | 94 | using std::sqrt; |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 95 | |
| 96 | Index rows = internal::random<Index>(2,EIGEN_TEST_MAX_SIZE), cols = internal::random<Index>(2,EIGEN_TEST_MAX_SIZE), cols2 = internal::random<Index>(2,EIGEN_TEST_MAX_SIZE); |
| 97 | Index rank = internal::random<Index>(1, (std::min)(rows, cols)-1); |
| 98 | |
| 99 | typedef typename MatrixType::Scalar Scalar; |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame] | 100 | typedef typename MatrixType::RealScalar RealScalar; |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 101 | typedef Matrix<Scalar, MatrixType::RowsAtCompileTime, MatrixType::RowsAtCompileTime> MatrixQType; |
| 102 | MatrixType m1; |
| 103 | createRandomPIMatrixOfRank(rank,rows,cols,m1); |
| 104 | ColPivHouseholderQR<MatrixType> qr(m1); |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame] | 105 | VERIFY_IS_EQUAL(rank, qr.rank()); |
| 106 | VERIFY_IS_EQUAL(cols - qr.rank(), qr.dimensionOfKernel()); |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 107 | VERIFY(!qr.isInjective()); |
| 108 | VERIFY(!qr.isInvertible()); |
| 109 | VERIFY(!qr.isSurjective()); |
| 110 | |
| 111 | MatrixQType q = qr.householderQ(); |
| 112 | VERIFY_IS_UNITARY(q); |
| 113 | |
| 114 | MatrixType r = qr.matrixQR().template triangularView<Upper>(); |
| 115 | MatrixType c = q * r * qr.colsPermutation().inverse(); |
| 116 | VERIFY_IS_APPROX(m1, c); |
| 117 | |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame] | 118 | // Verify that the absolute value of the diagonal elements in R are |
| 119 | // non-increasing until they reach the singularity threshold. |
| 120 | RealScalar threshold = |
| 121 | sqrt(RealScalar(rows)) * numext::abs(r(0, 0)) * NumTraits<Scalar>::epsilon(); |
| 122 | for (Index i = 0; i < (std::min)(rows, cols) - 1; ++i) { |
| 123 | RealScalar x = numext::abs(r(i, i)); |
| 124 | RealScalar y = numext::abs(r(i + 1, i + 1)); |
| 125 | if (x < threshold && y < threshold) continue; |
| 126 | if (!test_isApproxOrLessThan(y, x)) { |
| 127 | for (Index j = 0; j < (std::min)(rows, cols); ++j) { |
| 128 | std::cout << "i = " << j << ", |r_ii| = " << numext::abs(r(j, j)) << std::endl; |
| 129 | } |
| 130 | std::cout << "Failure at i=" << i << ", rank=" << rank |
| 131 | << ", threshold=" << threshold << std::endl; |
| 132 | } |
| 133 | VERIFY_IS_APPROX_OR_LESS_THAN(y, x); |
| 134 | } |
| 135 | |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 136 | MatrixType m2 = MatrixType::Random(cols,cols2); |
| 137 | MatrixType m3 = m1*m2; |
| 138 | m2 = MatrixType::Random(cols,cols2); |
| 139 | m2 = qr.solve(m3); |
| 140 | VERIFY_IS_APPROX(m3, m1*m2); |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame] | 141 | |
| 142 | { |
| 143 | Index size = rows; |
| 144 | do { |
| 145 | m1 = MatrixType::Random(size,size); |
| 146 | qr.compute(m1); |
| 147 | } while(!qr.isInvertible()); |
| 148 | MatrixType m1_inv = qr.inverse(); |
| 149 | m3 = m1 * MatrixType::Random(size,cols2); |
| 150 | m2 = qr.solve(m3); |
| 151 | VERIFY_IS_APPROX(m2, m1_inv*m3); |
| 152 | } |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 153 | } |
| 154 | |
| 155 | template<typename MatrixType, int Cols2> void qr_fixedsize() |
| 156 | { |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame] | 157 | using std::sqrt; |
| 158 | using std::abs; |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 159 | enum { Rows = MatrixType::RowsAtCompileTime, Cols = MatrixType::ColsAtCompileTime }; |
| 160 | typedef typename MatrixType::Scalar Scalar; |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame] | 161 | typedef typename MatrixType::RealScalar RealScalar; |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 162 | int rank = internal::random<int>(1, (std::min)(int(Rows), int(Cols))-1); |
| 163 | Matrix<Scalar,Rows,Cols> m1; |
| 164 | createRandomPIMatrixOfRank(rank,Rows,Cols,m1); |
| 165 | ColPivHouseholderQR<Matrix<Scalar,Rows,Cols> > qr(m1); |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame] | 166 | VERIFY_IS_EQUAL(rank, qr.rank()); |
| 167 | VERIFY_IS_EQUAL(Cols - qr.rank(), qr.dimensionOfKernel()); |
| 168 | VERIFY_IS_EQUAL(qr.isInjective(), (rank == Rows)); |
| 169 | VERIFY_IS_EQUAL(qr.isSurjective(), (rank == Cols)); |
| 170 | VERIFY_IS_EQUAL(qr.isInvertible(), (qr.isInjective() && qr.isSurjective())); |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 171 | |
| 172 | Matrix<Scalar,Rows,Cols> r = qr.matrixQR().template triangularView<Upper>(); |
| 173 | Matrix<Scalar,Rows,Cols> c = qr.householderQ() * r * qr.colsPermutation().inverse(); |
| 174 | VERIFY_IS_APPROX(m1, c); |
| 175 | |
| 176 | Matrix<Scalar,Cols,Cols2> m2 = Matrix<Scalar,Cols,Cols2>::Random(Cols,Cols2); |
| 177 | Matrix<Scalar,Rows,Cols2> m3 = m1*m2; |
| 178 | m2 = Matrix<Scalar,Cols,Cols2>::Random(Cols,Cols2); |
| 179 | m2 = qr.solve(m3); |
| 180 | VERIFY_IS_APPROX(m3, m1*m2); |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame] | 181 | // Verify that the absolute value of the diagonal elements in R are |
| 182 | // non-increasing until they reache the singularity threshold. |
| 183 | RealScalar threshold = |
| 184 | sqrt(RealScalar(Rows)) * (std::abs)(r(0, 0)) * NumTraits<Scalar>::epsilon(); |
| 185 | for (Index i = 0; i < (std::min)(int(Rows), int(Cols)) - 1; ++i) { |
| 186 | RealScalar x = numext::abs(r(i, i)); |
| 187 | RealScalar y = numext::abs(r(i + 1, i + 1)); |
| 188 | if (x < threshold && y < threshold) continue; |
| 189 | if (!test_isApproxOrLessThan(y, x)) { |
| 190 | for (Index j = 0; j < (std::min)(int(Rows), int(Cols)); ++j) { |
| 191 | std::cout << "i = " << j << ", |r_ii| = " << numext::abs(r(j, j)) << std::endl; |
| 192 | } |
| 193 | std::cout << "Failure at i=" << i << ", rank=" << rank |
| 194 | << ", threshold=" << threshold << std::endl; |
| 195 | } |
| 196 | VERIFY_IS_APPROX_OR_LESS_THAN(y, x); |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | // This test is meant to verify that pivots are chosen such that |
| 201 | // even for a graded matrix, the diagonal of R falls of roughly |
| 202 | // monotonically until it reaches the threshold for singularity. |
| 203 | // We use the so-called Kahan matrix, which is a famous counter-example |
| 204 | // for rank-revealing QR. See |
| 205 | // http://www.netlib.org/lapack/lawnspdf/lawn176.pdf |
| 206 | // page 3 for more detail. |
| 207 | template<typename MatrixType> void qr_kahan_matrix() |
| 208 | { |
| 209 | using std::sqrt; |
| 210 | using std::abs; |
| 211 | typedef typename MatrixType::Scalar Scalar; |
| 212 | typedef typename MatrixType::RealScalar RealScalar; |
| 213 | |
| 214 | Index rows = 300, cols = rows; |
| 215 | |
| 216 | MatrixType m1; |
| 217 | m1.setZero(rows,cols); |
| 218 | RealScalar s = std::pow(NumTraits<RealScalar>::epsilon(), 1.0 / rows); |
| 219 | RealScalar c = std::sqrt(1 - s*s); |
| 220 | RealScalar pow_s_i(1.0); // pow(s,i) |
| 221 | for (Index i = 0; i < rows; ++i) { |
| 222 | m1(i, i) = pow_s_i; |
| 223 | m1.row(i).tail(rows - i - 1) = -pow_s_i * c * MatrixType::Ones(1, rows - i - 1); |
| 224 | pow_s_i *= s; |
| 225 | } |
| 226 | m1 = (m1 + m1.transpose()).eval(); |
| 227 | ColPivHouseholderQR<MatrixType> qr(m1); |
| 228 | MatrixType r = qr.matrixQR().template triangularView<Upper>(); |
| 229 | |
| 230 | RealScalar threshold = |
| 231 | std::sqrt(RealScalar(rows)) * numext::abs(r(0, 0)) * NumTraits<Scalar>::epsilon(); |
| 232 | for (Index i = 0; i < (std::min)(rows, cols) - 1; ++i) { |
| 233 | RealScalar x = numext::abs(r(i, i)); |
| 234 | RealScalar y = numext::abs(r(i + 1, i + 1)); |
| 235 | if (x < threshold && y < threshold) continue; |
| 236 | if (!test_isApproxOrLessThan(y, x)) { |
| 237 | for (Index j = 0; j < (std::min)(rows, cols); ++j) { |
| 238 | std::cout << "i = " << j << ", |r_ii| = " << numext::abs(r(j, j)) << std::endl; |
| 239 | } |
| 240 | std::cout << "Failure at i=" << i << ", rank=" << qr.rank() |
| 241 | << ", threshold=" << threshold << std::endl; |
| 242 | } |
| 243 | VERIFY_IS_APPROX_OR_LESS_THAN(y, x); |
| 244 | } |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 245 | } |
| 246 | |
| 247 | template<typename MatrixType> void qr_invertible() |
| 248 | { |
| 249 | using std::log; |
| 250 | using std::abs; |
| 251 | typedef typename NumTraits<typename MatrixType::Scalar>::Real RealScalar; |
| 252 | typedef typename MatrixType::Scalar Scalar; |
| 253 | |
| 254 | int size = internal::random<int>(10,50); |
| 255 | |
| 256 | MatrixType m1(size, size), m2(size, size), m3(size, size); |
| 257 | m1 = MatrixType::Random(size,size); |
| 258 | |
| 259 | if (internal::is_same<RealScalar,float>::value) |
| 260 | { |
| 261 | // let's build a matrix more stable to inverse |
| 262 | MatrixType a = MatrixType::Random(size,size*2); |
| 263 | m1 += a * a.adjoint(); |
| 264 | } |
| 265 | |
| 266 | ColPivHouseholderQR<MatrixType> qr(m1); |
| 267 | m3 = MatrixType::Random(size,size); |
| 268 | m2 = qr.solve(m3); |
| 269 | //VERIFY_IS_APPROX(m3, m1*m2); |
| 270 | |
| 271 | // now construct a matrix with prescribed determinant |
| 272 | m1.setZero(); |
| 273 | for(int i = 0; i < size; i++) m1(i,i) = internal::random<Scalar>(); |
| 274 | RealScalar absdet = abs(m1.diagonal().prod()); |
| 275 | m3 = qr.householderQ(); // get a unitary |
| 276 | m1 = m3 * m1 * m3; |
| 277 | qr.compute(m1); |
| 278 | VERIFY_IS_APPROX(absdet, qr.absDeterminant()); |
| 279 | VERIFY_IS_APPROX(log(absdet), qr.logAbsDeterminant()); |
| 280 | } |
| 281 | |
| 282 | template<typename MatrixType> void qr_verify_assert() |
| 283 | { |
| 284 | MatrixType tmp; |
| 285 | |
| 286 | ColPivHouseholderQR<MatrixType> qr; |
| 287 | VERIFY_RAISES_ASSERT(qr.matrixQR()) |
| 288 | VERIFY_RAISES_ASSERT(qr.solve(tmp)) |
| 289 | VERIFY_RAISES_ASSERT(qr.householderQ()) |
| 290 | VERIFY_RAISES_ASSERT(qr.dimensionOfKernel()) |
| 291 | VERIFY_RAISES_ASSERT(qr.isInjective()) |
| 292 | VERIFY_RAISES_ASSERT(qr.isSurjective()) |
| 293 | VERIFY_RAISES_ASSERT(qr.isInvertible()) |
| 294 | VERIFY_RAISES_ASSERT(qr.inverse()) |
| 295 | VERIFY_RAISES_ASSERT(qr.absDeterminant()) |
| 296 | VERIFY_RAISES_ASSERT(qr.logAbsDeterminant()) |
| 297 | } |
| 298 | |
| 299 | void test_qr_colpivoting() |
| 300 | { |
| 301 | for(int i = 0; i < g_repeat; i++) { |
| 302 | CALL_SUBTEST_1( qr<MatrixXf>() ); |
| 303 | CALL_SUBTEST_2( qr<MatrixXd>() ); |
| 304 | CALL_SUBTEST_3( qr<MatrixXcd>() ); |
| 305 | CALL_SUBTEST_4(( qr_fixedsize<Matrix<float,3,5>, 4 >() )); |
| 306 | CALL_SUBTEST_5(( qr_fixedsize<Matrix<double,6,2>, 3 >() )); |
| 307 | CALL_SUBTEST_5(( qr_fixedsize<Matrix<double,1,1>, 1 >() )); |
| 308 | } |
| 309 | |
| 310 | for(int i = 0; i < g_repeat; i++) { |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame] | 311 | CALL_SUBTEST_1( cod<MatrixXf>() ); |
| 312 | CALL_SUBTEST_2( cod<MatrixXd>() ); |
| 313 | CALL_SUBTEST_3( cod<MatrixXcd>() ); |
| 314 | CALL_SUBTEST_4(( cod_fixedsize<Matrix<float,3,5>, 4 >() )); |
| 315 | CALL_SUBTEST_5(( cod_fixedsize<Matrix<double,6,2>, 3 >() )); |
| 316 | CALL_SUBTEST_5(( cod_fixedsize<Matrix<double,1,1>, 1 >() )); |
| 317 | } |
| 318 | |
| 319 | for(int i = 0; i < g_repeat; i++) { |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 320 | CALL_SUBTEST_1( qr_invertible<MatrixXf>() ); |
| 321 | CALL_SUBTEST_2( qr_invertible<MatrixXd>() ); |
| 322 | CALL_SUBTEST_6( qr_invertible<MatrixXcf>() ); |
| 323 | CALL_SUBTEST_3( qr_invertible<MatrixXcd>() ); |
| 324 | } |
| 325 | |
| 326 | CALL_SUBTEST_7(qr_verify_assert<Matrix3f>()); |
| 327 | CALL_SUBTEST_8(qr_verify_assert<Matrix3d>()); |
| 328 | CALL_SUBTEST_1(qr_verify_assert<MatrixXf>()); |
| 329 | CALL_SUBTEST_2(qr_verify_assert<MatrixXd>()); |
| 330 | CALL_SUBTEST_6(qr_verify_assert<MatrixXcf>()); |
| 331 | CALL_SUBTEST_3(qr_verify_assert<MatrixXcd>()); |
| 332 | |
| 333 | // Test problem size constructors |
| 334 | CALL_SUBTEST_9(ColPivHouseholderQR<MatrixXf>(10, 20)); |
Austin Schuh | 189376f | 2018-12-20 22:11:15 +1100 | [diff] [blame] | 335 | |
| 336 | CALL_SUBTEST_1( qr_kahan_matrix<MatrixXf>() ); |
| 337 | CALL_SUBTEST_2( qr_kahan_matrix<MatrixXd>() ); |
Brian Silverman | 72890c2 | 2015-09-19 14:37:37 -0400 | [diff] [blame] | 338 | } |