From 1ef855cf0bb2dac72aa4701276ee68ddacf09e82 Mon Sep 17 00:00:00 2001 From: Kutalmis Bercin Date: Fri, 20 May 2022 15:08:43 +0100 Subject: [PATCH] ENH: QRMatrix: refactor the QR decomposition algorithms (fixes #2212) --- applications/test/TestTools/TestTools.H | 91 +- .../test/matrices/QRMatrix/Make/options | 2 +- .../test/matrices/QRMatrix/Test-QRMatrix.C | 1613 ++++------------- src/OpenFOAM/matrices/QRMatrix/QRMatrix.C | 633 ++++--- src/OpenFOAM/matrices/QRMatrix/QRMatrix.H | 350 ++-- src/OpenFOAM/matrices/QRMatrix/QRMatrixI.H | 158 +- 6 files changed, 980 insertions(+), 1867 deletions(-) diff --git a/applications/test/TestTools/TestTools.H b/applications/test/TestTools/TestTools.H index 3fed3b7d5d..82dfe5e38b 100644 --- a/applications/test/TestTools/TestTools.H +++ b/applications/test/TestTools/TestTools.H @@ -5,7 +5,7 @@ \\ / A nd | www.openfoam.com \\/ M anipulation | ------------------------------------------------------------------------------- - Copyright (C) 2020-2021 OpenCFD Ltd. + Copyright (C) 2020-2022 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -35,6 +35,7 @@ using namespace Foam; #include "complex.H" #include "Matrix.H" #include "Random.H" +#include // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // @@ -46,6 +47,27 @@ unsigned nTest_ = 0; unsigned nFail_ = 0; +// Return the absolute tolerance value for bitwise comparisons of floatScalars +floatScalar getTol(floatScalar) +{ + return 1e-2; +} + + +// Return the absolute tolerance value for bitwise comparisons of doubleScalars +doubleScalar getTol(doubleScalar) +{ + return 1e-10; +} + + +// Return the absolute tolerance value for bitwise comparisons of doubleScalars +doubleScalar getTol(complex) +{ + return 1e-10; +} + + // Create a non-complex random Matrix. template typename std::enable_if @@ -174,10 +196,14 @@ typename std::enable_if const Type& x, const Type& y, const scalar absTol = 0, // +void InfoNumPyFormat +( + const MatrixType& mat, + const word objName +) +{ + Info<< objName << ": " << mat.m() << "x" << mat.n() << nl; + for (label m = 0; m < mat.m(); ++m) + { + Info<< "["; + for (label n = 0; n < mat.n(); ++n) + { + if (n == mat.n() - 1) + { + Info<< mat(m,n); + } + else + { + Info<< mat(m,n) << ","; + } + } + if (m == mat.m() - 1) + { + Info << "]" << nl; + } + else + { + Info << "]," << nl; + } + } +} + + // ************************************************************************* // diff --git a/applications/test/matrices/QRMatrix/Make/options b/applications/test/matrices/QRMatrix/Make/options index 4e772fdf9d..b23590707c 100644 --- a/applications/test/matrices/QRMatrix/Make/options +++ b/applications/test/matrices/QRMatrix/Make/options @@ -1,2 +1,2 @@ -/* EXE_INC = -I$(LIB_SRC)/finiteVolume/lnInclude */ +EXE_INC = -I../../TestTools /* EXE_LIBS = -lfiniteVolume */ diff --git a/applications/test/matrices/QRMatrix/Test-QRMatrix.C b/applications/test/matrices/QRMatrix/Test-QRMatrix.C index 6790727152..9e51d67eb0 100644 --- a/applications/test/matrices/QRMatrix/Test-QRMatrix.C +++ b/applications/test/matrices/QRMatrix/Test-QRMatrix.C @@ -5,7 +5,7 @@ \\ / A nd | www.openfoam.com \\/ M anipulation | ------------------------------------------------------------------------------- - Copyright (C) 2019-2020 OpenCFD Ltd. + Copyright (C) 2020-2022 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -23,946 +23,338 @@ License You should have received a copy of the GNU General Public License along with OpenFOAM. If not, see . +Application + Test-QRMatrix + +Description + Tests for \c QRMatrix constructors, and member functions + using \c doubleScalar and \c complex base types. + \*---------------------------------------------------------------------------*/ #include "MatrixTools.H" #include "QRMatrix.H" -#include "Random.H" +#include "complex.H" #include "IOmanip.H" +#include "TestTools.H" + +#define PRINTALL false using namespace Foam; -using namespace Foam::MatrixTools; - -#define equal MatrixTools::equal -#define RUNALL true -const bool verbose = true; // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // -void horizontalLine() +// Verify if Q is a unitary matrix for rectangular matrices +template +void verify_Q(const RectangularMatrix& Q) { - Info<< "+---------+---------+---------+---------+---------+" << nl; + // non-square unitary matrices are not possible - do nothing } -// Create random scalar-type matrix -template -typename std::enable_if -< - !std::is_same::value, - MatrixType ->::type makeRandomMatrix +// Verify if Q is a unitary matrix for square matrices +template +void verify_Q ( - const labelPair& dims, - Random& rndGen + const SquareMatrix& Q ) { - MatrixType mat(dims); - - std::generate - ( - mat.begin(), - mat.end(), - [&]{ return rndGen.GaussNormal(); } - ); - - return mat; -} - - -// Create random complex-type matrix -template -typename std::enable_if -< - std::is_same::value, - MatrixType ->::type makeRandomMatrix -( - const labelPair& dims, - Random& rndGen -) -{ - MatrixType mat(dims); - - std::generate - ( - mat.begin(), - mat.end(), - [&] - { - return complex - ( - rndGen.GaussNormal(), - rndGen.GaussNormal() - ); - } - ); - - return mat; -} - - -// Print OpenFOAM matrix in NumPy format -template -void InfoNumPyFormat -( - const MatrixType& mat, - const word objName -) -{ - Info<< objName << ": " << mat.m() << "x" << mat.n() << nl; - for (label m = 0; m < mat.m(); ++m) - { - Info<< "["; - for (label n = 0; n < mat.n(); ++n) - { - if (n == mat.n() - 1) - { - Info<< mat(m,n); - } - else - { - Info<< mat(m,n) << ","; - } - } - if (m == mat.m() - 1) - { - Info << "]" << nl; - } - else - { - Info << "]," << nl; - } - } -} - - -// Returns true if two scalars are equal within a given tolerance, and v.v. -bool isEqual -( - const scalar a, - const scalar b, - const bool verbose = false, - const scalar relTol = 1e-5, - const scalar absTol = 1e-8 -) -{ - if ((absTol + relTol*mag(b)) < mag(a - b)) - { - if (verbose) - { - Info<< "Elements are not close in terms of tolerances:" - << nl << a << tab << b << nl; - } - return false; - } - - if (verbose) - { - Info<< "All elems are the same within the tolerances" << nl; - } - - return true; -} - - -// Prints true if a given matrix is empty, and v.v. -template -void isEmpty -( - const MatrixType& A, - const word objName -) -{ - Info<< "Empty " << objName << " = "; - if (A.empty()) - { - Info<< "true" << nl; - } - else - { - Info<< "false" << nl; - } -} - - -// Checks if Q matrix is a unitary matrix -template -void cross_check_QRMatrix -( - const MatrixType& Aorig, - const MatrixType& Q -) -{ - InfoNumPyFormat(Q, "Q"); - // mathworld.wolfram.com/UnitaryMatrix.html (Retrieved:18-06-19) - Info<< nl << "# Q.T()*Q = Q*Q.T() = I:" << nl; - const MatrixType QTQ(Q&Q); - const MatrixType QQT(Q^Q); - MatrixType IMatrix({Q.m(), Q.n()}, Zero); + const SquareMatrix QTQ(Q&Q); + const SquareMatrix QQT(Q^Q); + SquareMatrix IMatrix(Q.sizes(), Zero); for (label i = 0; i < IMatrix.n(); ++i) { - IMatrix(i,i) = pTraits::one; + IMatrix(i,i) = pTraits::one; } - equal(QTQ, IMatrix, verbose); - equal(QQT, IMatrix, verbose); - equal(QTQ, QQT, verbose); + cmp("# Q.T()*Q = I: ", flt(QTQ), flt(IMatrix), getTol(Type(0))); + cmp("# Q*Q.T() = I: ", flt(QQT), flt(IMatrix), getTol(Type(0))); + cmp("# QTQ = QQT: ", flt(QTQ), flt(QQT), getTol(Type(0))); } -// Checks if R matrix is an upper-triangular matrix +// Verify if R is an upper-triangular matrix template -void cross_check_QRMatrix +void verify_R ( const MatrixType& R ) { - InfoNumPyFormat(R, "R"); - + DynamicList ltriR; + label k = 0; // mathworld.wolfram.com/UpperTriangularMatrix.html (Retrieved:18-06-19) - Info<< nl << "# R(i, j) = 0 for i > j:" << nl; for (label i = 0; i < R.m(); ++i) { for (label j = 0; j < R.n(); ++j) { if (j < i) { - isEqual(mag(R(i, j)), 0.0, verbose); + ltriR.append(mag(R(i, j))); + ++k; } } } + const List ltri(k, Zero); + cmp("# R(i, j) = 0 for i > j: ", ltri, ltriR); } -// Checks if the given matrix can be reconstructed by Q and R matrices +// Verify if R is an upper-triangular matrix with diag elems non-increasing template -void cross_check_QRMatrix +void verify_R_pivoting ( - const MatrixType& Aorig, - const MatrixType& Q, const MatrixType& R ) { - InfoNumPyFormat(Aorig, "Aorig"); - - cross_check_QRMatrix(Aorig, Q); - - cross_check_QRMatrix(R); - - // mathworld.wolfram.com/QRDecomposition.html (Retrieved:18-06-19) - Info<< nl << "# Q*R = A:" << nl; - const MatrixType AReconstruct(Q*R); - equal(Aorig, AReconstruct, verbose); -} - - -// Checks if R matrix is an upper-triangular matrix, and obeys column pivoting -template -void cross_check_QRMatrix -( - const MatrixType& Aorig, - const labelList& orderP, - const SquareMatrix& P, - const MatrixType& R -) -{ - InfoNumPyFormat(Aorig, "Aorig"); - - InfoNumPyFormat(P, "P"); - - cross_check_QRMatrix(R); - // w.wiki/54m (Retrieved:20-06-19) - Info<< nl << "# Diag elems of R non-increasing:" - << "|R11| >= |R22| >= ... >= |Rnn|:" << nl; const List diag0(mag(R.diag())); List diag1(diag0); Foam::sort(diag1, std::greater()); - forAll(diag0, i) - { - isEqual(diag0[i], diag1[i], verbose); - } + cmp + ( + "# Diag elems of R non-increasing: |R11| >= |R22| >= ... >= |Rnn|: ", + diag0, + diag1 + ); } -// Checks if the given matrix can be reconstructed by column-pivoted Q and R +// Verify if the given matrix can be reconstructed by Q and R matrices template -void cross_check_QRMatrix +void verify_QR ( - const MatrixType& Aorig, + const MatrixType& A, const MatrixType& Q, - const labelList& orderP, - const SquareMatrix& P, const MatrixType& R ) { - cross_check_QRMatrix(Aorig, orderP, P, R); - cross_check_QRMatrix(Aorig, Q); - - // w.wiki/54m (Retrieved:20-06-19) - Info<< nl << "# Q*R = A*P:" << nl; + // mathworld.wolfram.com/QRDecomposition.html (Retrieved:18-06-19) const MatrixType AReconstruct(Q*R); - const MatrixType AP(Aorig*P); - equal(AP, AReconstruct, verbose); + cmp("# Q*R = A: ", flt(A), flt(AReconstruct)); } -// Checks each constructor of QRMatrix type +// Verify if the given matrix can be reconstructed by Q, R and P matrices template -void verification_QRMatrix +void verify_QR_pivoting ( - MatrixType& A + const MatrixType& A, + const MatrixType& Q, + const MatrixType& R, + const SquareMatrix& P ) { - typedef SquareMatrix SMatrix; - - // Create working copies of matrix A - const MatrixType Aorig(A); - - // QRMatrix Constructors - #if (0 | RUNALL) - { - QRMatrix QRNull; - } - #endif - - #if (0 | RUNALL) - { - Info<< "# FULL_R | OUT_OF_PLACE" << nl; - QRMatrix QRM - ( - QRMatrix::outputTypes::FULL_R, - QRMatrix::storeMethods::OUT_OF_PLACE - ); - QRM.decompose(A); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - isEmpty(Q, "Q"); - cross_check_QRMatrix(R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# FULL_R | IN_PLACE" << nl; - QRMatrix QRM - ( - QRMatrix::outputTypes::FULL_R, - QRMatrix::storeMethods::IN_PLACE - ); - MatrixType A0(A); - QRM.decompose(A0); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - isEmpty(Q, "Q"); - isEmpty(R, "R"); - cross_check_QRMatrix(A0); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# FULL_QR | OUT_OF_PLACE" << nl; - QRMatrix QRM - ( - QRMatrix::outputTypes::FULL_QR, - QRMatrix::storeMethods::OUT_OF_PLACE - ); - QRM.decompose(A); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - cross_check_QRMatrix(Aorig, Q, R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# FULL_QR | IN_PLACE" << nl; - QRMatrix QRM - ( - QRMatrix::outputTypes::FULL_QR, - QRMatrix::storeMethods::IN_PLACE - ); - MatrixType A0(A); - QRM.decompose(A0); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - isEmpty(R, "R"); - cross_check_QRMatrix(Aorig, Q, A0); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# FULL_R | OUT_OF_PLACE | colPivot = on" << nl; - QRMatrix QRM - ( - QRMatrix::outputTypes::FULL_R, - QRMatrix::storeMethods::OUT_OF_PLACE, - QRMatrix::colPivoting::TRUE - ); - QRM.decompose(A); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - isEmpty(Q, "Q"); - cross_check_QRMatrix(Aorig, PList, P, R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# FULL_R | IN_PLACE | colPivot = on" << nl; - QRMatrix QRM - ( - QRMatrix::outputTypes::FULL_R, - QRMatrix::storeMethods::IN_PLACE, - QRMatrix::colPivoting::TRUE - ); - MatrixType A0(A); - QRM.decompose(A0); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - isEmpty(Q, "Q"); - isEmpty(R, "R"); - cross_check_QRMatrix(Aorig, PList, P, A0); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# FULL_QR | OUT_OF_PLACE | colPivot = on" << nl; - QRMatrix QRM - ( - QRMatrix::outputTypes::FULL_QR, - QRMatrix::storeMethods::OUT_OF_PLACE, - QRMatrix::colPivoting::TRUE - ); - QRM.decompose(A); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - cross_check_QRMatrix(Aorig, Q, PList, P, R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# FULL_QR | IN_PLACE | colPivot = on" << nl; - QRMatrix QRM - ( - QRMatrix::outputTypes::FULL_QR, - QRMatrix::storeMethods::IN_PLACE, - QRMatrix::colPivoting::TRUE - ); - MatrixType A0(A); - QRM.decompose(A0); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - isEmpty(R, "R"); - cross_check_QRMatrix(Aorig, Q, PList, P, A0); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# A | FULL_R | OUT_OF_PLACE | colPivot = off" << nl; - MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::FULL_R, - QRMatrix::storeMethods::OUT_OF_PLACE, - QRMatrix::colPivoting::FALSE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - isEmpty(Q, "Q"); - cross_check_QRMatrix(R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# A | FULL_R | IN_PLACE | colPivot = off" << nl; - MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::FULL_R, - QRMatrix::storeMethods::IN_PLACE, - QRMatrix::colPivoting::FALSE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - isEmpty(Q, "Q"); - isEmpty(R, "R"); - cross_check_QRMatrix(A0); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# A | FULL_QR | OUT_OF_PLACE | colPivot = off" << nl; - MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::FULL_QR, - QRMatrix::storeMethods::OUT_OF_PLACE, - QRMatrix::colPivoting::FALSE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - cross_check_QRMatrix(Aorig, Q, R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# A | FULL_QR | IN_PLACE | colPivot = off" << nl; - MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::FULL_QR, - QRMatrix::storeMethods::IN_PLACE, - QRMatrix::colPivoting::FALSE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - isEmpty(R, "R"); - cross_check_QRMatrix(Aorig, Q, A0); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# A | FULL_R | OUT_OF_PLACE | colPivot = on" << nl; - MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::FULL_R, - QRMatrix::storeMethods::OUT_OF_PLACE, - QRMatrix::colPivoting::TRUE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - isEmpty(Q, "Q"); - cross_check_QRMatrix(Aorig, PList, P, R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# A | FULL_R | IN_PLACE | colPivot = on" << nl; - MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::FULL_R, - QRMatrix::storeMethods::IN_PLACE, - QRMatrix::colPivoting::TRUE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - isEmpty(Q, "Q"); - isEmpty(R, "R"); - cross_check_QRMatrix(Aorig, PList, P, A0); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# A | FULL_QR | OUT_OF_PLACE | colPivot = on" << nl; - MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::FULL_QR, - QRMatrix::storeMethods::OUT_OF_PLACE, - QRMatrix::colPivoting::TRUE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - cross_check_QRMatrix(Aorig, Q, PList, P, R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# A | FULL_QR | IN_PLACE | colPivot = on" << nl; - MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::FULL_QR, - QRMatrix::storeMethods::IN_PLACE, - QRMatrix::colPivoting::TRUE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - isEmpty(R, "R"); - cross_check_QRMatrix(Aorig, Q, PList, P, A0); - } - #endif - - // constructors with the const type specifier - #if (0 | RUNALL) - { - Info<< "# const A | FULL_R | colPivot = off" << nl; - const MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::FULL_R, - QRMatrix::colPivoting::FALSE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - isEmpty(Q, "Q"); - cross_check_QRMatrix(R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# const A | FULL_QR | colPivot = off" << nl; - const MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::FULL_QR, - QRMatrix::colPivoting::FALSE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - cross_check_QRMatrix(Aorig, Q, R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# const A | FULL_R | colPivot = on" << nl; - const MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::FULL_R, - QRMatrix::colPivoting::TRUE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - isEmpty(Q, "Q"); - cross_check_QRMatrix(Aorig, PList, P, R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# const A | FULL_QR | colPivot = on" << nl; - const MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::FULL_QR, - QRMatrix::colPivoting::TRUE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - cross_check_QRMatrix(Aorig, Q, PList, P, R); - } - #endif - - // - #if (0 | RUNALL) - { - Info<< "# REDUCED_R | OUT_OF_PLACE" << nl; - QRMatrix QRM - ( - QRMatrix::outputTypes::REDUCED_R, - QRMatrix::storeMethods::OUT_OF_PLACE - ); - QRM.decompose(A); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - isEmpty(Q, "Q"); - cross_check_QRMatrix(R); - - // check if full R and reduced R give the same answer - if (A.n() < A.m()) - { - QRMatrix fullQRM - ( - QRMatrix::outputTypes::FULL_QR, - QRMatrix::storeMethods::OUT_OF_PLACE - ); - fullQRM.decompose(A); - const MatrixType& fullR(fullQRM.R()); - MatrixType fullR2(fullR.subMatrix(0,0,R.m())); - equal(fullR2, R, verbose); - } - } - #endif - - #if (0 | RUNALL) - { - Info<< "# REDUCED_R | IN_PLACE" << nl; - QRMatrix QRM - ( - QRMatrix::outputTypes::REDUCED_R, - QRMatrix::storeMethods::IN_PLACE - ); - MatrixType A0(A); - QRM.decompose(A0); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - isEmpty(Q, "Q"); - isEmpty(R, "R"); - cross_check_QRMatrix(A0); - - // check if full R and reduced R give the same answer - if (A.n() < A.m()) - { - QRMatrix fullQRM - ( - QRMatrix::outputTypes::FULL_QR, - QRMatrix::storeMethods::IN_PLACE - ); - MatrixType A1(A); - fullQRM.decompose(A1); - MatrixType fullR(A1.subMatrix(0,0,A0.m())); - equal(fullR, A0, verbose); - } - } - #endif - - #if (0 | RUNALL) - { - Info<< "# REDUCED_R | OUT_OF_PLACE | colPivot = on" << nl; - QRMatrix QRM - ( - QRMatrix::outputTypes::REDUCED_R, - QRMatrix::storeMethods::OUT_OF_PLACE, - QRMatrix::colPivoting::TRUE - ); - QRM.decompose(A); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - isEmpty(Q, "Q"); - cross_check_QRMatrix(Aorig, PList, P, R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# REDUCED_R | IN_PLACE | colPivot = on" << nl; - QRMatrix QRM - ( - QRMatrix::outputTypes::REDUCED_R, - QRMatrix::storeMethods::IN_PLACE, - QRMatrix::colPivoting::TRUE - ); - MatrixType A0(A); - QRM.decompose(A0); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - isEmpty(Q, "Q"); - isEmpty(R, "R"); - cross_check_QRMatrix(Aorig, PList, P, A0); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# A | REDUCED_R | OUT_OF_PLACE | colPivot = off" << nl; - MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::REDUCED_R, - QRMatrix::storeMethods::OUT_OF_PLACE, - QRMatrix::colPivoting::FALSE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - isEmpty(Q, "Q"); - cross_check_QRMatrix(R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# A | REDUCED_R | IN_PLACE | colPivot = off" << nl; - MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::REDUCED_R, - QRMatrix::storeMethods::IN_PLACE, - QRMatrix::colPivoting::FALSE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - isEmpty(Q, "Q"); - isEmpty(R, "R"); - cross_check_QRMatrix(A0); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# A | REDUCED_R | OUT_OF_PLACE | colPivot = on" << nl; - MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::REDUCED_R, - QRMatrix::storeMethods::OUT_OF_PLACE, - QRMatrix::colPivoting::TRUE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - isEmpty(Q, "Q"); - cross_check_QRMatrix(Aorig, PList, P, R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# A | REDUCED_R | IN_PLACE | colPivot = on" << nl; - MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::REDUCED_R, - QRMatrix::storeMethods::IN_PLACE, - QRMatrix::colPivoting::TRUE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - isEmpty(Q, "Q"); - isEmpty(R, "R"); - cross_check_QRMatrix(Aorig, PList, P, A0); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# const A | REDUCED_R | colPivot = off" << nl; - const MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::REDUCED_R, - QRMatrix::colPivoting::FALSE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - isEmpty(Q, "Q"); - cross_check_QRMatrix(R); - } - #endif - - #if (0 | RUNALL) - { - Info<< "# const A | REDUCED_R | colPivot = on" << nl; - const MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::REDUCED_R, - QRMatrix::colPivoting::TRUE - ); - const MatrixType& Q(QRM.Q()); - const MatrixType& R(QRM.R()); - const labelList& PList(QRM.orderP()); - const SMatrix P(QRM.P()); - isEmpty(Q, "Q"); - cross_check_QRMatrix(Aorig, PList, P, R); - } - #endif + // w.wiki/54m (Retrieved:20-06-19) + const MatrixType AReconstruct(Q*R); + const MatrixType AP(A*P); + cmp("# Q*R = A*P: ", flt(AReconstruct), flt(AP)); } -// Checks the direct solution of a linear system by QRMatrix +// Verify the given QR decomposition template -void verification_QRMatrixSolve +void verify ( - MatrixType& A + const QRMatrix& QRM, + const MatrixType& A ) { - typedef SquareMatrix SMatrix; + const MatrixType& Q = QRM.Q(); + const MatrixType& R = QRM.R(); - // Create working copies of matrix A - const MatrixType Aorig(A); + #if PRINTALL + InfoNumPyFormat(A, "A"); + InfoNumPyFormat(Q, "Q"); + InfoNumPyFormat(R, "R"); + #endif + + verify_Q(Q); + verify_R(R); + verify_QR(A, Q, R); + Info<< endl; +} + + +// Verify the given QR decomposition with column pivoting +template +void verify_pivoting +( + const QRMatrix& QRM, + const MatrixType& A +) +{ + const MatrixType& Q = QRM.Q(); + const MatrixType& R = QRM.R(); + const SquareMatrix& P = QRM.P(); + + #if PRINTALL + InfoNumPyFormat(A, "A"); + InfoNumPyFormat(Q, "Q"); + InfoNumPyFormat(R, "R"); + InfoNumPyFormat(P, "P"); + #endif + + verify_Q(Q); + verify_R(R); + verify_R_pivoting(R); + verify_QR_pivoting(A, Q, R, P); + Info<< endl; +} + + +// Verify various QR decompositions +template +void verify_decomposition +( + const MatrixType& A +) +{ + Info<< "## Verify various QR decompositions" << nl << endl; + { + Info<< "# modes::FULL" << nl; + QRMatrix QRM + ( + A, + QRMatrix::modes::FULL + ); + verify(QRM, A); + } + { + Info<< "# modes::ECONOMY" << nl; + QRMatrix QRM + ( + A, + QRMatrix::modes::ECONOMY + ); + verify(QRM, A); + } + { + Info<< "# modes::FULL, outputs::BOTH_QR, column-pivoting" << nl; + QRMatrix QRM + ( + A, + QRMatrix::modes::FULL, + QRMatrix::outputs::BOTH_QR, + QRMatrix::pivoting::TRUE + ); + verify_pivoting(QRM, A); + } + { + Info<< "# modes::ECONOMY, outputs::BOTH_QR, column-pivoting" << nl; + QRMatrix QRM + ( + A, + QRMatrix::modes::ECONOMY, + QRMatrix::outputs::BOTH_QR, + QRMatrix::pivoting::TRUE + ); + verify_pivoting(QRM, A); + } +} + + +// Verify the Moore-Penrose pseudo-inverse function +template +void verify_pinv +( + const MatrixType& A +) +{ + const scalar absTol = 1e-6; + + Info<< "## Verify Moore-Penrose pseudo-inverse: pinv()" << nl << endl; + const MatrixType APlus(MatrixTools::pinv(A)); + + #if PRINTALL + InfoNumPyFormat(A, "A"); + InfoNumPyFormat(APlus, "A^+"); + #endif + + { + const MatrixType APlusPlus(MatrixTools::pinv(APlus)); + cmp("# (A^+)^+ = A: ", flt(APlusPlus), flt(A), absTol); + } + + // The Moore-Penrose conditions + // w.wiki/5RL (Retrieved: 29-06-19) + { + const MatrixType AAPlusA((A*APlus)*A); + cmp("# A*(A^+)*A = A: ", flt(AAPlusA), flt(A), absTol); + } + + { + const MatrixType APlusAAPlus((APlus*A)*APlus); + cmp("# (A^+)*A*(A^+) = A: ", flt(APlusAAPlus), flt(APlus), absTol); + } + + { + const MatrixType AAPlus(A*APlus); + const MatrixType AAPlusT(AAPlus.T()); + cmp("# (A*(A^+)).T() = A*(A^+): ", flt(AAPlusT), flt(AAPlus), absTol); + } + + { + const MatrixType APlusA(APlus*A); + const MatrixType APlusAT(APlusA.T()); + cmp("# ((A^+)*A = ((A^+)*A).T(): ", flt(APlusA), flt(APlusAT), absTol); + } +} + + +// Verify the direct solution of a linear system by QRMatrix +void verify_solve +( + const SquareMatrix& A +) +{ + // do nothing +} + + +// Verify the direct solution of a linear system by QRMatrix +void verify_solve +( + const SquareMatrix& A +) +{ + Info<< "## Verify direct solution of A*x = b with A = Q*R:" << nl << endl; + + typedef SquareMatrix SMatrix; + const scalar absTol = 1e-6; // Direct solution of a linear system by QR decomposition - #if (0 | RUNALL) - { - MatrixType A0(A); - QRMatrix QRM - ( - A0, - QRMatrix::outputTypes::FULL_QR - ); + QRMatrix QRM + ( + A, + QRMatrix::modes::FULL, + QRMatrix::outputs::BOTH_QR + ); - Info<< nl << "# Solution of A*x = b with A = Q*R:" << nl; - const scalarField residual(A0.m(), 0); - const scalarField source(A0.m(), 1); + const scalarField residual(A.m(), 0); + const scalarField source(A.m(), 1); - const scalarField x(QRM.solve(source)); - const scalarField A0xMinusSource(A0*x - source); - const scalarField xMinusQRinvSource(x - QRM.inv()*source); - const SMatrix QRinvA0(QRM.inv()*A0); - const SMatrix identity(A0.m(), I); + const scalarField x(QRM.solve(source)); + const scalarField AxMinusSource(A*x - source); + const scalarField xMinusQRinvSource(x - QRM.inv()*source); + const SMatrix QRinvA(QRM.inv()*A); + const SMatrix identity(A.m(), I); - forAll(residual, i) - { - isEqual(A0xMinusSource[i], residual[i], verbose); - isEqual(xMinusQRinvSource[i], residual[i], verbose); - } - equal(QRinvA0, identity, verbose); - } - #endif + cmp("# A*x - b = residual: ", AxMinusSource, residual, absTol); + cmp("# x - inv(QR)*b = residual: ", xMinusQRinvSource, residual, absTol); + cmp("# inv(QR)*A = I: ", flt(QRinvA), flt(identity), absTol); } -// Checks the parallel tall-skinny QR decomposition +// Verify the parallel tall-skinny QR decomposition template -void verification_tsqr +void verify_tsqr ( Random& rndGen ) { + Info<< "## Verify parallel direct tall-skinny QR decomposition:" + << nl << endl; + typedef RectangularMatrix RMatrix; // Size of the full matrix and its partitions @@ -978,7 +370,7 @@ void verification_tsqr mRowsSum += mRows; } - # if 0 + #if PRINTALL Info<< "mRowsSum = " << mRowsSum << nl; Info<< "nColsSum = " << nColsSum << nl; Info<< "mRowsList = " << mRowsList << nl; @@ -999,8 +391,9 @@ void verification_tsqr QRMatrix QRM ( A, - QRMatrix::outputTypes::REDUCED_R, - QRMatrix::colPivoting::FALSE + QRMatrix::modes::ECONOMY, + QRMatrix::outputs::ONLY_R, + QRMatrix::pivoting::FALSE ); const RMatrix& R = QRM.R(); @@ -1023,8 +416,9 @@ void verification_tsqr QRMatrix subQRM ( subA, - QRMatrix::outputTypes::REDUCED_R, - QRMatrix::colPivoting::FALSE + QRMatrix::modes::ECONOMY, + QRMatrix::outputs::ONLY_R, + QRMatrix::pivoting::FALSE ); const RMatrix& subR = subQRM.R(); @@ -1036,8 +430,9 @@ void verification_tsqr QRMatrix TSQR ( subRs, - QRMatrix::outputTypes::REDUCED_R, - QRMatrix::colPivoting::FALSE + QRMatrix::modes::ECONOMY, + QRMatrix::outputs::ONLY_R, + QRMatrix::pivoting::FALSE ); const RMatrix& TSQRR = TSQR.R(); @@ -1048,433 +443,161 @@ void verification_tsqr } // Compare magnitude of R, since R is not unique up to the sign - equal(RMag, TSQRRMag, verbose); + cmp("# TSQR: ", flt(RMag), flt(TSQRRMag)); - #if 0 + #if PRINTALL InfoNumPyFormat(R, "R"); InfoNumPyFormat(TSQRR, "TSQRR"); #endif } -// Checks the back substitution function -template -void verification_backSubstitution -( - const SquareMatrix& A, - const RectangularMatrix& b -) +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +template +void test_constructors(MatrixType) { - // Ax = b - typedef RectangularMatrix RMatrix; - - QRMatrix QRNull; - const RMatrix X(QRNull.backSubstitution(A, b)); - - #if 1 { - InfoNumPyFormat(A, "A"); - InfoNumPyFormat(b, "b"); - InfoNumPyFormat(X, "x"); + Info<< "# Construct from a Matrix and modes" << nl; + MatrixType A({5,5}, Zero); + const QRMatrix QRM + ( + A, + QRMatrix::modes::FULL + ); } - #endif - - #if (0 | RUNALL) { - Info<< nl << "# A*X = b:" << nl; - const RMatrix AX(A*X); - equal(AX, b, verbose, 10, 1e-3, 1e-3); + Info<< "# Construct from a const Matrix and modes" << nl; + const MatrixType A({5,5}, Zero); + const QRMatrix QRM + ( + A, + QRMatrix::modes::FULL + ); } - #endif } -// Checks the Moore-Penrose pseudo-inverse function template -void verification_pinv -( - const MatrixType& A -) +void test_decomposition(MatrixType) { - Info<< "### Moore-Penrose pseudo-inverse: pinv()" << nl << nl; - const MatrixType APlus(pinv(A)); + typedef typename MatrixType::cmptType cmptType; + typedef SquareMatrix SMatrix; + typedef RectangularMatrix RMatrix; - #if 0 - InfoNumPyFormat(A, "A"); - InfoNumPyFormat(APlus, "A^+"); - #endif + Random rndGen(1234); + const label szTests = 20; + const label min = 10; + const label max = 50; - if (0 | RUNALL) { - Info<< nl << "# (A^+)^+ = A:" << nl; - const MatrixType APlusPlus(pinv(APlus)); - equal(APlusPlus, A, verbose); + for (label i = 0; i < szTests; ++i) + { + const label m = rndGen.position(min, max); + const label n = rndGen.position(min, max); + + const RMatrix A + ( + makeRandomMatrix({m,n}, rndGen) + ); + + verify_decomposition(A); + verify_pinv(A); + } } - // The four Moore-Penrose conditions - // w.wiki/5RL (Retrieved: 29-06-19) - if (0 | RUNALL) { - Info<< nl << "# A*(A^+)*A = A:" << nl; - const MatrixType AAPlusA((A*APlus)*A); - equal(AAPlusA, A, verbose); + for (label i = 0; i < szTests; ++i) + { + const label m = rndGen.position(min, max); + + const SMatrix A + ( + makeRandomMatrix({m,m}, rndGen) + ); + + verify_decomposition(A); + verify_pinv(A); + verify_solve(A); + } } - if (0 | RUNALL) + for (label i = 0; i < szTests; ++i) { - Info<< nl << "# (A^+)*A*(A^+) = A:" << nl; - const MatrixType APlusAAPlus((APlus*A)*APlus); - equal(APlusAAPlus, APlus, verbose); + verify_tsqr(rndGen); } +} - if (0 | RUNALL) - { - Info<< nl << "# (A*(A^+)).T() = A*(A^+):" << nl; - const MatrixType AAPlus(A*APlus); - const MatrixType AAPlusT(AAPlus.T()); - equal(AAPlusT, AAPlus, verbose); - } - if (0 | RUNALL) - { - Info<< nl << "# ((A^+)*A = ((A^+)*A).T():" << nl; - const MatrixType APlusA(APlus*A); - const MatrixType APlusAT(APlusA.T()); - equal(APlusA, APlusAT, verbose); - } +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +// Do compile-time recursion over the given types +template +inline typename std::enable_if::type +run_tests(const std::tuple& types, const List& typeID){} + + +template +inline typename std::enable_if::type +run_tests(const std::tuple& types, const List& typeID) +{ + Info<< nl << " ## Test constructors: "<< typeID[I] <<" ##" << nl; + test_constructors(std::get(types)); + + Info<< nl << " ## Test decomposition: "<< typeID[I] <<" ##" << nl; + test_decomposition(std::get(types)); + + run_tests(types, typeID); } // * * * * * * * * * * * * * * * Main Program * * * * * * * * * * * * * * * // -int main(int argc, char *argv[]) +int main() { - typedef SquareMatrix SMatrix; - typedef RectangularMatrix RMatrix; - typedef SquareMatrix SCMatrix; - typedef RectangularMatrix RCMatrix; - Info<< setprecision(15); - Random rndGen(1234); - label numberOfTests = 10; - Info<< "### QR Decomposition:" << nl << nl; + const std::tuple + < + RectangularMatrix, + RectangularMatrix, + SquareMatrix, + SquareMatrix + > types + ( + std::make_tuple(Zero, Zero, Zero, Zero) + ); - // SquareMatrix - #if (0 | RUNALL) + const List typeID + ({ + "RectangularMatrix", + "RectangularMatrix", + "SquareMatrix", + "SquareMatrix" + }); + + std::chrono::steady_clock::time_point begin = + std::chrono::steady_clock::now(); + + run_tests(types, typeID); + + std::chrono::steady_clock::time_point end = + std::chrono::steady_clock::now(); + Info<< "Time elapsed = " + << std::chrono::duration_cast(end - begin) + .count() + << "[µs]" << endl; + + if (nFail_) { - horizontalLine(); - - Info<< "## " << numberOfTests << " SquareMatrix A:" << nl; - - for (label i = 0; i < numberOfTests; ++i) - { - const label mRows = rndGen.position