/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | \\ / A nd | www.openfoam.com \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2019 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. OpenFOAM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OpenFOAM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenFOAM. If not, see . \*---------------------------------------------------------------------------*/ #include "MatrixTools.H" #include "QRMatrix.H" #include "Random.H" #include "IOmanip.H" using namespace Foam; using namespace Foam::MatrixTools; #define equal MatrixTools::equal #define RUNALL true const bool verbose = true; // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // void horizontalLine() { Info<< "+---------+---------+---------+---------+---------+" << nl; } // Create random scalar-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 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); for (label i = 0; i < IMatrix.n(); ++i) { IMatrix(i,i) = pTraits::one; } equal(QTQ, IMatrix, verbose); equal(QQT, IMatrix, verbose); equal(QTQ, QQT, verbose); } // Checks if R matrix is an upper-triangular matrix template void cross_check_QRMatrix ( const MatrixType& R ) { InfoNumPyFormat(R, "R"); // 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); } } } } // Checks if the given matrix can be reconstructed by Q and R matrices template void cross_check_QRMatrix ( 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); } } // Checks if the given matrix can be reconstructed by column-pivoted Q and R template void cross_check_QRMatrix ( const MatrixType& Aorig, 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; const MatrixType AReconstruct(Q*R); const MatrixType AP(Aorig*P); equal(AP, AReconstruct, verbose); } // Checks each constructor of QRMatrix type template void verification_QRMatrix ( MatrixType& A ) { 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 } // Checks the direct solution of a linear system by QRMatrix template void verification_QRMatrixSolve ( MatrixType& A ) { typedef SquareMatrix SMatrix; // Create working copies of matrix A const MatrixType Aorig(A); // Direct solution of a linear system by QR decomposition #if (0 | RUNALL) { MatrixType A0(A); QRMatrix QRM ( A0, QRMatrix::outputTypes::FULL_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 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); forAll(residual, i) { isEqual(A0xMinusSource[i], residual[i], verbose); isEqual(xMinusQRinvSource[i], residual[i], verbose); } equal(QRinvA0, identity, verbose); } #endif } // Checks the parallel tall-skinny QR decomposition template void verification_tsqr ( Random& rndGen ) { typedef RectangularMatrix RMatrix; // Size of the full matrix and its partitions const label nColsSum = rndGen.position