ENH: QRMatrix: refactor the QR decomposition algorithms (fixes #2212)

This commit is contained in:
Kutalmis Bercin 2022-05-20 15:08:43 +01:00 committed by Mark Olesen
parent cf492d42a1
commit 1ef855cf0b
6 changed files with 980 additions and 1867 deletions

View File

@ -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 <chrono>
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@ -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<class MatrixType>
typename std::enable_if
@ -174,10 +196,14 @@ typename std::enable_if
const Type& x,
const Type& y,
const scalar absTol = 0, //<! useful for cmps near zero
const scalar relTol = 1e-8 //<! are values the same within 8 decimals
const scalar relTol = 1e-8, //<! are values the same within 8 decimals
const bool verbose = false
)
{
Info<< msg << x << "?=" << y << endl;
if (verbose)
{
Info<< msg << x << "?=" << y << endl;
}
unsigned nFail = 0;
@ -212,10 +238,14 @@ typename std::enable_if
const Type& x,
const Type& y,
const scalar absTol = 0,
const scalar relTol = 1e-8
const scalar relTol = 1e-8,
const bool verbose = false
)
{
Info<< msg << x << "?=" << y << endl;
if (verbose)
{
Info<< msg << x << "?=" << y << endl;
}
unsigned nFail = 0;
@ -253,10 +283,14 @@ typename std::enable_if
const Type1& x,
const Type2& y,
const scalar absTol = 0,
const scalar relTol = 1e-8
const scalar relTol = 1e-8,
const bool verbose = false
)
{
Info<< msg << x << "?=" << y << endl;
if (verbose)
{
Info<< msg << x << "?=" << y << endl;
}
unsigned nFail = 0;
@ -284,10 +318,14 @@ void cmp
(
const word& msg,
const bool x,
const bool y
const bool y,
const bool verbose = false
)
{
Info<< msg << x << "?=" << y << endl;
if (verbose)
{
Info<< msg << x << "?=" << y << endl;
}
unsigned nFail = 0;
@ -306,4 +344,39 @@ void cmp
}
// Print OpenFOAM matrix in NumPy format
template<class MatrixType>
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;
}
}
}
// ************************************************************************* //

View File

@ -1,2 +1,2 @@
/* EXE_INC = -I$(LIB_SRC)/finiteVolume/lnInclude */
EXE_INC = -I../../TestTools
/* EXE_LIBS = -lfiniteVolume */

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2016 OpenFOAM Foundation
Copyright (C) 2019-2021 OpenCFD Ltd.
Copyright (C) 2019-2022 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -31,96 +31,292 @@ License
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
template<class MatrixType>
void Foam::QRMatrix<MatrixType>::qr
Foam::label Foam::QRMatrix<MatrixType>::calcShapeFactor
(
MatrixType& A
)
const MatrixType& A
) const
{
const label nIter = min(A.m() - 1, A.n());
// Loop through all subcolumns of which the diagonal elem is the first elem
for (label k = 0; k < nIter; ++k)
if (mode_ == modes::FULL)
{
const RMatrix u(householderReflector(A.subColumn(k, k)));
applyHouseholder(A, u, k);
return A.m();
}
else if (mode_ == modes::ECONOMY)
{
return min(A.m(), A.n());
}
if (outputType_ == outputTypes::REDUCED_R)
{
A.resize(A.n(), A.n());
}
return 0;
}
template<class MatrixType>
void Foam::QRMatrix<MatrixType>::qrPivot
void Foam::QRMatrix<MatrixType>::decompose
(
MatrixType& A
MatrixType& AT
)
{
const label nCols = A.n();
const label nIter = min(A.m() - 1, A.n());
const label n = AT.m();
const label m = AT.n();
// Initialise permutation vector, and column norm vector
P_ = identity(nCols);
List<cmptType> Rdiag(n, Zero);
// Initialise vector norms of each column of A
List<scalar> colNorms(nCols);
for (label k = 0; k < nCols; ++k)
for (label k = 0; k < n; ++k)
{
colNorms[k] = A.columnNorm(k, true);
// Compute 2-norm of k-th column without under/overflow
scalar nrm = 0;
for (label i = k; i < m; ++i)
{
nrm = Foam::hypot(nrm, mag(AT(k,i)));
}
// Avoid divide-by-zero. Use compare with == 0 not mag or VSMALL etc,
// otherwise wrong results (may need more investigation)
if (nrm != scalar(0))
{
// Form k-th Householder vector
if (mag(AT(k,k)) < 0)
{
nrm *= -1;
}
for (label i = k; i < m; ++i)
{
AT(k,i) /= nrm;
}
AT(k,k) += scalar(1);
// Apply transformation to remaining columns
for (label j = k + 1; j < n; ++j)
{
cmptType s(Zero);
for (label i = k; i < m; ++i)
{
s += Detail::conj(AT(k,i))*AT(j,i);
}
if (mag(AT(k,k)) > SMALL)
{
s /= -AT(k,k);
}
for (label i = k; i < m; ++i)
{
AT(j,i) += s*AT(k,i);
}
}
}
Rdiag[k] = -nrm;
}
// Loop through all subcolumns of which the diagonal elem is the first elem
for (label k = 0; k < nIter; ++k)
{
const labelRange colRange(k, nCols);
const SubList<scalar> subColNorms(colNorms, colRange);
calcQ(AT);
// Column pivoting
const label maxColNormi =
calcR(AT, Rdiag);
}
template<class MatrixType>
void Foam::QRMatrix<MatrixType>::decompose
(
MatrixType& AT,
const bool pivoting
)
{
const label n = AT.m();
const label m = AT.n();
const label sz = min(m,n);
// Initialise permutation vector, and column norm vector
p_ = identity(n);
// Initialise vector norms of each column of A
List<scalar> norms(n, Zero);
for (label k = 0; k < n; ++k)
{
for (label i = 0; i < m; ++i)
{
norms[k] += magSqr(AT(k, i));
}
}
List<cmptType> Rdiag(n, Zero);
for (label k = 0; k < sz; ++k)
{
const auto it = std::next(norms.cbegin(), k);
const label maxNormi =
std::distance
(
subColNorms.cbegin(),
std::max_element(subColNorms.cbegin(), subColNorms.cend())
it,
std::max_element(it, norms.cend())
);
// Swap R_, P_ and colNorms_ according to pivot column if the current
// column is not the max norm column by avoiding any column swap where
// the leading elem is very small
if (maxColNormi != 0 && SMALL < mag(A(k, k + maxColNormi)))
// Swap A, P and norms according to pivot column if the current
// column is not the max norm column. Also avoid any column swaps
// where the leading element is very small
if (mag(AT(k + maxNormi,k)) > SMALL && maxNormi != 0)
{
const RMatrix R1(A.subColumn(k));
const RMatrix R2(A.subColumn(maxColNormi + k));
A.subColumn(k) = R2;
A.subColumn(maxColNormi + k) = R1;
const RMatrix R1(AT.subRow(k));
const RMatrix R2(AT.subRow(maxNormi + k));
Swap(P_[k], P_[maxColNormi + k]);
Swap(colNorms[k], colNorms[maxColNormi + k]);
AT.subRow(k) = R2;
AT.subRow(maxNormi + k) = R1;
Swap(p_[k], p_[maxNormi + k]);
Swap(norms[k], norms[maxNormi + k]);
}
{
const RMatrix u(householderReflector(A.subColumn(k, k)));
// Compute 2-norm of k-th column without under/overflow
scalar nrm = 0;
applyHouseholder(A, u, k);
for (label i = k; i < m; ++i)
{
nrm = Foam::hypot(nrm, mag(AT(k,i)));
}
if (nrm != scalar(0))
{
// Form k-th Householder vector
if (mag(AT(k,k)) < 0)
{
nrm *= -1;
}
for (label i = k; i < m; ++i)
{
AT(k,i) /= nrm;
}
AT(k,k) += scalar(1);
// Apply transformation to remaining columns
for (label j = k + 1; j < n; ++j)
{
cmptType s(Zero);
for (label i = k; i < m; ++i)
{
s += Detail::conj(AT(k,i))*AT(j,i);
}
if (mag(AT(k,k)) > SMALL)
{
s /= -AT(k,k);
}
for (label i = k; i < m; ++i)
{
AT(j,i) += s*AT(k,i);
}
}
}
Rdiag[k] = -nrm;
}
// Update norms
if (k < nIter - 1)
if (k < sz - 1)
{
label q = k + 1;
for (const auto& val : RMatrix(A.subRow(k, q)))
for (const auto& val : RMatrix(AT.subColumn(k, q)))
{
colNorms[q] -= magSqr(val);
norms[q] -= magSqr(val);
++q;
}
}
}
if (outputType_ == outputTypes::REDUCED_R)
calcQ(AT);
calcR(AT, Rdiag);
}
template<class MatrixType>
void Foam::QRMatrix<MatrixType>::calcQ
(
const MatrixType& AT
)
{
if (output_ == outputs::ONLY_R)
{
A.resize(A.n(), A.n());
return;
}
const label n = AT.m();
const label m = AT.n();
Q_.resize(m, sz_);
MatrixType QT(Q_.transpose());
for (label k = sz_ - 1; k >= 0; --k)
{
for (label i = 0; i < m; ++i)
{
QT(k,i) = Zero;
}
QT(k,k) = scalar(1);
for (label j = k; j < sz_; ++j)
{
if (n > k && mag(AT(k,k)) > SMALL)
{
cmptType s(Zero);
for (label i = k; i < m; ++i)
{
s += AT(k,i)*QT(j,i);
}
s /= -AT(k,k);
for (label i = k; i < m; ++i)
{
QT(j,i) += s*Detail::conj(AT(k,i));
}
}
}
}
Q_ = QT.T();
}
template<class MatrixType>
void Foam::QRMatrix<MatrixType>::calcR
(
const MatrixType& AT,
const List<cmptType>& diag
)
{
if (output_ == outputs::ONLY_Q)
{
return;
}
const label n = AT.m();
R_.resize(sz_, n);
for (label i = 0; i < sz_; ++i)
{
for (label j = 0; j < n; ++j)
{
if (i < j)
{
R_(i,j) = AT(j,i);
}
else if (i == j)
{
R_(i,j) = diag[i];
}
}
}
}
@ -182,49 +378,36 @@ void Foam::QRMatrix<MatrixType>::solveImpl
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
template<class MatrixType>
Foam::QRMatrix<MatrixType>::QRMatrix()
:
outputType_(),
storeMethod_(),
colPivot_()
{}
template<class MatrixType>
Foam::QRMatrix<MatrixType>::QRMatrix
(
const outputTypes outputType,
const storeMethods storeMethod,
const colPivoting colPivot
const modes mode,
const outputs output,
const bool pivoting,
MatrixType& A
)
:
outputType_(outputType),
storeMethod_(storeMethod),
colPivot_(colPivot),
mode_(mode),
output_(output),
sz_(calcShapeFactor(A)),
Q_(),
R_(),
P_()
{}
template<class MatrixType>
Foam::QRMatrix<MatrixType>::QRMatrix
(
MatrixType& A,
const outputTypes outputType,
const storeMethods storeMethod,
const colPivoting colPivot
)
:
outputType_(outputType),
storeMethod_(storeMethod),
colPivot_(colPivot),
Q_(),
R_(),
P_()
p_()
{
decompose(A);
// Non-conjugate transpose of input matrix
MatrixType AT(A.transpose());
A.clear();
if (pivoting)
{
decompose(AT, pivoting);
}
else
{
decompose(AT);
}
AT.clear();
}
@ -232,127 +415,35 @@ template<class MatrixType>
Foam::QRMatrix<MatrixType>::QRMatrix
(
const MatrixType& A,
const outputTypes outputType,
const colPivoting colPivot
const modes mode,
const outputs output,
const bool pivoting
)
:
outputType_(outputType),
storeMethod_(storeMethods::OUT_OF_PLACE),
colPivot_(colPivot),
mode_(mode),
output_(output),
sz_(calcShapeFactor(A)),
Q_(),
R_(),
P_()
p_()
{
decompose(A);
MatrixType AT(A.transpose());
if (pivoting)
{
decompose(AT, pivoting);
}
else
{
decompose(AT);
}
AT.clear();
}
// * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
template<class MatrixType>
void Foam::QRMatrix<MatrixType>::decompose
(
MatrixType& A
)
{
// Check whether settings and input are consistent for reduced QRMatrix
if (A.m() <= A.n() && outputType_ == outputTypes::REDUCED_R)
{
outputType_ = outputTypes::FULL_R;
#if FULLDEBUG
WarningInFunction
<< "Reduced QR decomposition is by definition limited to matrices"
<< " wherein rows > columns, thus computing FULL decomposition."
<< nl;
#endif
}
// Allocate resources for Q_, if need be
if (outputType_ == outputTypes::FULL_QR)
{
Q_ = MatrixType({A.m(), A.m()}, I);
}
// Allocate resources for R_ and execute decomposition
switch (storeMethod_)
{
case storeMethods::IN_PLACE:
{
if (colPivot_)
{
qrPivot(A);
}
else
{
qr(A);
}
break;
}
case storeMethods::OUT_OF_PLACE:
{
R_ = A;
if (colPivot_)
{
qrPivot(R_);
}
else
{
qr(R_);
}
break;
}
}
}
template<class MatrixType>
void Foam::QRMatrix<MatrixType>::decompose
(
const MatrixType& A
)
{
if (storeMethod_ == storeMethods::IN_PLACE)
{
WarningInFunction
<< "const type qualifier invalidates storeMethods::IN_PLACE." << nl;
}
// Check whether settings and input are consistent for reduced QRMatrix
if (A.m() <= A.n() && outputType_ == outputTypes::REDUCED_R)
{
outputType_ = outputTypes::FULL_R;
#if FULLDEBUG
WarningInFunction
<< "Reduced QR decomposition is by definition limited to matrices"
<< " wherein rows > columns, thus computing FULL decomposition."
<< nl;
#endif
}
// Allocate resources for Q_, if need be
if (outputType_ == outputTypes::FULL_QR)
{
Q_ = MatrixType({A.m(), A.m()}, I);
}
// Allocate resources for R_ and execute decomposition
R_ = A;
if (colPivot_)
{
qrPivot(R_);
}
else
{
qr(R_);
}
}
template<class MatrixType>
void Foam::QRMatrix<MatrixType>::solve
(
@ -407,6 +498,62 @@ Foam::QRMatrix<MatrixType>::solve
}
template<class MatrixType>
Foam::RectangularMatrix<typename MatrixType::cmptType>
Foam::QRMatrix<MatrixType>::solve
(
const RMatrix& rhs
)
{
const label mRows = R_.m();
const label nCols = R_.n();
const label pCols = rhs.n();
#ifdef FULLDEBUG
{
const label qRows = rhs.m();
if (mRows != qRows)
{
FatalErrorInFunction
<< "Linear system is not solvable since the number of rows of"
<< "A and rhs are not equal:" << tab << mRows << "vs." << qRows
<< abort(FatalError);
}
const List<cmptType> diag(R_.diag());
for (const auto& val : diag)
{
if (mag(val) < SMALL)
{
WarningInFunction
<< "SMALL-valued diagonal elem in back-substitution."
<< endl;
}
}
}
#endif
RMatrix b({nCols, pCols}, Zero);
for (label i = 0; i < pCols; ++i)
{
for (label j = mRows - 1; -1 < j; --j)
{
cmptType alpha(rhs(j, i));
for (label k = j + 1; k < mRows; ++k)
{
alpha -= b(k, i)*R_(j, k);
}
b(j, i) = alpha/R_(j, j);
}
}
return b;
}
template<class MatrixType>
typename Foam::QRMatrix<MatrixType>::SMatrix
Foam::QRMatrix<MatrixType>::inv() const
@ -430,72 +577,15 @@ Foam::QRMatrix<MatrixType>::inv() const
}
template<class MatrixType>
Foam::RectangularMatrix<typename MatrixType::cmptType>
Foam::QRMatrix<MatrixType>::backSubstitution
(
const SMatrix& A,
const RMatrix& rhs
)
{
const label mRows = A.m();
const label nCols = A.n();
const label pCols = rhs.n();
#ifdef FULLDEBUG
{
const label qRows = rhs.m();
if (mRows != qRows)
{
FatalErrorInFunction
<< "Linear system is not solvable since the number of rows of"
<< "A and rhs are not equal:" << tab << mRows << "vs." << qRows
<< abort(FatalError);
}
const List<cmptType> diag(A.diag());
for (const auto& val : diag)
{
if (mag(val) < SMALL)
{
WarningInFunction
<< "SMALL-valued diagonal elem in back-substitution." << nl;
}
}
}
#endif
RMatrix X({nCols, pCols}, Zero);
for (label i = 0; i < pCols; ++i)
{
for (label j = mRows - 1; -1 < j; --j)
{
cmptType alpha(rhs(j, i));
for (label k = j + 1; k < mRows; ++k)
{
alpha -= X(k, i)*A(j, k);
}
X(j, i) = alpha/A(j, j);
}
}
return X;
}
// * * * * * * * * * * * * * * * Global Functions * * * * * * * * * * * * * //
template<class MatrixType>
MatrixType Foam::pinv
MatrixType Foam::MatrixTools::pinv
(
const MatrixType& A,
const scalar tolerance
scalar tol
)
{
scalar tol = tolerance;
typedef typename MatrixType::cmptType cmptType;
if (A.empty())
@ -518,15 +608,16 @@ MatrixType Foam::pinv
QRMatrix<MatrixType> QRM
(
A,
QRMatrix<MatrixType>::outputTypes::FULL_QR,
QRMatrix<MatrixType>::colPivoting::TRUE
QRMatrix<MatrixType>::modes::FULL,
QRMatrix<MatrixType>::outputs::BOTH_QR,
QRMatrix<MatrixType>::pivoting::TRUE
);
const MatrixType& R(QRM.R());
const MatrixType& Q(QRM.Q());
const MatrixType& R = QRM.R();
const MatrixType& Q = QRM.Q();
// R1 (KP:p. 648)
// Find the first diagonal element index with (almost) zero value in R
label firstZeroElemi = 0;
label elemi = 0;
label i = 0;
while (i < 2)
{
@ -534,14 +625,14 @@ MatrixType Foam::pinv
auto lessThan = [=](const cmptType& x) { return tol > mag(x); };
firstZeroElemi =
elemi =
std::distance
(
diag.cbegin(),
std::find_if(diag.cbegin(), diag.cend(), lessThan)
);
if (firstZeroElemi == 0)
if (elemi == 0)
{
if (i == 0)
{
@ -572,7 +663,7 @@ MatrixType Foam::pinv
// Exclude the first (almost) zero diagonal row and the rows below
// since R diagonal is already descending due to the QR column pivoting
const RectangularMatrix<cmptType> R1(R.subMatrix(0, 0, firstZeroElemi));
const RectangularMatrix<cmptType> R1(R.subMatrix(0, 0, elemi));
// R2 (KP:p. 648)
if (R1.n() < R1.m())
@ -582,15 +673,15 @@ MatrixType Foam::pinv
QRMatrix<SquareMatrix<cmptType>> QRSolve
(
C,
QRMatrix<SquareMatrix<cmptType>>::outputTypes::FULL_QR
QRMatrix<SquareMatrix<cmptType>>::modes::FULL,
QRMatrix<SquareMatrix<cmptType>>::outputs::BOTH_QR
);
RectangularMatrix<cmptType> R2
(
QRSolve.backSubstitution
QRSolve.solve
(
QRSolve.R(),
QRSolve.Q() & (R1.T())
QRSolve.Q() & R1.T()
)
);
@ -606,14 +697,14 @@ MatrixType Foam::pinv
QRMatrix<SquareMatrix<cmptType>> QRSolve
(
C,
QRMatrix<SquareMatrix<cmptType>>::outputTypes::FULL_QR
QRMatrix<SquareMatrix<cmptType>>::modes::FULL,
QRMatrix<SquareMatrix<cmptType>>::outputs::BOTH_QR
);
RectangularMatrix<cmptType> R2
(
QRSolve.backSubstitution
QRSolve.solve
(
QRSolve.R(),
QRSolve.Q() & R1
)
);

View File

@ -6,7 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2016 OpenFOAM Foundation
Copyright (C) 2019-2021 OpenCFD Ltd.
Copyright (C) 2019-2022 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -28,29 +28,36 @@ Class
Foam::QRMatrix
Description
QRMatrix (i.e. QR decomposition, QR factorisation or orthogonal-triangular
decomposition) decomposes a scalar/complex matrix \c A into the following
matrix product:
\c QRMatrix computes QR decomposition of a given
scalar/complex matrix \c A into the following:
\verbatim
A = Q*R,
A = Q R
\endverbatim
or in case of QR decomposition with column pivoting:
\verbatim
A P = Q R
\endverbatim
where
\c Q is a unitary similarity matrix,
\c R is an upper triangular matrix.
\vartable
Q | Unitary/orthogonal matrix
R | Upper triangular matrix
P | Permutation matrix
\endvartable
Reference:
References:
\verbatim
QR decomposition:
mathworld.wolfram.com/QRDecomposition.html (Retrieved:17-06-19)
w.wiki/52X (Retrieved: 17-06-19)
QR decomposition with Householder reflector (tag:M):
Monahan, J. F. (2011).
Numerical methods of statistics.
Cambridge: Cambridge University Press.
DOI:10.1017/CBO9780511977176
TNT implementation:
Pozo, R. (1997).
Template Numerical Toolkit for linear algebra:
High performance programming with C++
and the Standard Template Library.
The International Journal of Supercomputer Applications
and High Performance Computing, 11(3), 251-263.
DOI:10.1177/109434209701100307
QR decomposition with column pivoting (tag:QSB):
Quintana-Ortí, G., Sun, X., & Bischof, C. H. (1998).
@ -75,54 +82,47 @@ Description
A new method for computing MoorePenrose inverse matrices.
Journal of Computational and applied Mathematics, 228(1), 412-417.
DOI:10.1016/j.cam.2008.10.008
Operands of QR decomposition:
mathworld.wolfram.com/UnitaryMatrix.html (Retrieved:13-06-19)
mathworld.wolfram.com/UpperTriangularMatrix.html (Retrieved:16-06-19)
mathworld.wolfram.com/PermutationMatrix.html (Retrieved:20-06-19)
Back substitution:
mathworld.wolfram.com/GaussianElimination.html (Retrieved:15-06-19)
\endverbatim
Usage
Input types:
- \c A can be a \c SquareMatrix<Type> or \c RectangularMatrix<Type>
Output types:
- \c Q is always of the type of the matrix \c A
- \c R is always of the type of the matrix \c A
\heading Input:
\vartable
A | RectangularMatrix<Type> or SquareMatrix<Type>
\endvartable
Options for the output forms of \c QRMatrix (for an (m-by-n) input matrix
\c A with k = min(m, n)):
- outputTypes::FULL_R: computes only \c R (m-by-n)
- outputTypes::FULL_QR: computes both \c R and \c Q (m-by-m)
- outputTypes::REDUCED_R: computes only reduced \c R (k-by-n)
\heading Options for the decomposition mode:
\vartable
modes::FULL | compute full-size decomposition
modes::ECONOMY | compute economy-size decomposition
\endvartable
Options where to store \c R:
- storeMethods::IN_PLACE: replaces input matrix content with \c R
- storeMethods::OUT_OF_PLACE: creates new object of \c R
\heading Options for the output types:
\vartable
outputs::ONLY\_Q | compute only Q
outputs::ONLY\_R | compute only R
outputs::BOTH\_QR | compute both Q and R
\endvartable
Options for the computation of column pivoting:
- colPivoting::FALSE: switches off column pivoting
- colPivoting::TRUE: switches on column pivoting
\heading Options for the column pivoting:
\vartable
pivoting::FALSE | switch off column pivoting
pivoting::TRUE | switch on column pivoting
\endvartable
Direct solution of linear systems A x = b is possible by solve() alongside
the following limitations:
- \c A = a scalar square matrix
- output type = outputTypes::FULL_QR
- store method = storeMethods::IN_PLACE
\heading Output:
\vartable
Q | m-by-m (FULL) or m-by-k (ECONOMY) with k = min(m,n)
R | m-by-n (FULL) or k-by-n (ECONOMY) with k = min(m,n)
p | n-element label list
P | n-by-n permutation matrix
\endvartable
Notes
- QR decomposition is not unique if \c R is not positive diagonal \c R.
- The option combination:
- outputTypes::REDUCED_R
- storeMethods::IN_PLACE
will not modify the rows of input matrix \c A after its nth row.
- Both FULL_R and REDUCED_R QR decompositions execute the same number of
operations. Yet REDUCED_R QR decomposition returns only the first n rows
of \c R if m > n for an input m-by-n matrix \c A.
- For m <= n, FULL_R and REDUCED_R will produce the same matrices.
- \c QRMatrix involves modified implementations of the public-domain
library \c TNT, which is available at https://math.nist.gov/tnt/index.html.
- \c Q and \c R are always of the same type of the matrix \c A.
- \c Type can be \c scalar or \c complex.
See also
Test-QRMatrix.C
@ -133,12 +133,11 @@ SourceFiles
\*---------------------------------------------------------------------------*/
#ifndef QRMatrix_H
#define QRMatrix_H
#ifndef Foam_QRMatrix_H
#define Foam_QRMatrix_H
#include "RectangularMatrix.H"
#include "SquareMatrix.H"
#include "complex.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@ -146,39 +145,38 @@ namespace Foam
{
/*---------------------------------------------------------------------------*\
Class QRMatrix Declaration
Class QRMatrix Declaration
\*---------------------------------------------------------------------------*/
template<class MatrixType>
class QRMatrix
{
public:
typedef typename MatrixType::cmptType cmptType;
typedef SquareMatrix<cmptType> SMatrix;
typedef RectangularMatrix<cmptType> RMatrix;
//- Options for the output matrix forms of QRMatrix
enum outputTypes : uint8_t
//- Options for the decomposition mode
enum modes : uint8_t
{
FULL_R = 1, //!< computes only \c R
FULL_QR = 2, //!< computes both \c R and \c Q
REDUCED_R = 3 //!< computes only reduced \c R
FULL = 1, //!< compute full-size decomposition
ECONOMY = 2, //!< compute economy-size decomposition
};
//- Options where to store R
enum storeMethods : uint8_t
//- Options for the output types
enum outputs : uint8_t
{
IN_PLACE = 1, //!< replaces input matrix content with \c R
OUT_OF_PLACE = 2 //!< creates new object of \c R
ONLY_Q = 1, //!< compute only Q
ONLY_R = 2, //!< compute only R
BOTH_QR = 3 //!< compute both Q and R
};
//- Options for the computation of column pivoting
enum colPivoting : bool
//- Options for the column pivoting
enum pivoting : bool
{
FALSE = false, //!< switches off column pivoting
TRUE = true //!< switches on column pivoting
FALSE = false, //!< switch off column pivoting
TRUE = true //!< switch on column pivoting
};
@ -186,53 +184,50 @@ private:
// Private Data
//- Selected option for the output matrix forms of QRMatrix
outputTypes outputType_;
//- Decomposition mode
const modes mode_;
//- Selected option where to store R
const storeMethods storeMethod_;
//- Output type
const outputs output_;
//- Selected option for the computation of column pivoting
const colPivoting colPivot_;
//- Output shape factor based on the decomposition mode
const label sz_;
//- Unitary similarity matrix
//- Unitary/orthogonal matrix
MatrixType Q_;
//- Upper triangular matrix
MatrixType R_;
//- Permutation list (if column-pivoting is on)
labelList P_;
labelList p_;
// Private Member Functions
//- Compute the QR decomposition without the column pivoting
void qr
(
MatrixType& A
);
// Evaluation
//- Compute the QR decomposition with the column pivoting
// (QSB:Fig. 1)
void qrPivot
(
MatrixType& A
);
//- Calculate output shape factor based on the decomposition mode
label calcShapeFactor(const MatrixType& A) const;
//- Compute output matrices in selected forms
//- using Householder reflectors
// (M:Section 4)
inline void applyHouseholder
(
MatrixType& A,
const RMatrix& reflector,
const label k = 0
);
//- Compute QR decomposition
void decompose(MatrixType& AT);
//- Solve the linear system with the Field argument x initialized to
//- the appropriate transformed source (e.g. Q.T()*source)
//- and return the solution in x
//- Compute QR decomposition with column pivoting
void decompose(MatrixType& AT, const bool pivot);
//- Calculate Q
void calcQ(const MatrixType& AT);
//- Calculate R
void calcR(const MatrixType& AT, const List<cmptType>& diag);
// Linear system solution
//- Solve the linear system with the Field argument x
//- initialized to the appropriate transformed source
//- (e.g. Q.T()*source) and return the solution in x
template<template<typename> class ListContainer>
void solvex(ListContainer<cmptType>& x) const;
@ -246,33 +241,11 @@ private:
) const;
protected:
//- No copy construct
QRMatrix(const QRMatrix&) = delete;
// Protected Member Functions
//- Compute Householder reflector on a given matrix column, u
// (M:Eq.6.814)
inline RMatrix householderReflector
(
RMatrix u
);
//- Apply (in-place) Householder reflectors from the left side: u*A
inline void applyLeftReflector
(
MatrixType& A,
const RMatrix& reflector,
const label k = 0,
const label k1 = 0
);
//- Apply (in-place) Householder reflectors from the right side: (u*A)*u
inline void applyRightReflector
(
MatrixType& A,
const RMatrix& reflector,
const label k = 0
);
//- No copy assignment
QRMatrix& operator=(const QRMatrix&) = delete;
public:
@ -280,65 +253,66 @@ public:
// Constructors
//- Construct null
QRMatrix();
QRMatrix() = delete;
//- Construct QRMatrix without performing the decomposition
QRMatrix
//- Construct with a matrix and perform QR decomposition
explicit QRMatrix
(
const outputTypes outputType,
const storeMethods storeMethod = storeMethods::OUT_OF_PLACE,
const colPivoting colPivot = colPivoting::FALSE
const modes mode,
const outputs output,
const bool pivoting,
MatrixType& A
);
//- Construct QRMatrix and perform the QR decomposition
QRMatrix
(
MatrixType& A,
const outputTypes outputType,
const storeMethods storeMethod = storeMethods::OUT_OF_PLACE,
const colPivoting colPivot = colPivoting::FALSE
);
//- Construct QRMatrix and perform the QR decomposition
QRMatrix
//- Construct with a const matrix and perform QR decomposition
explicit QRMatrix
(
const MatrixType& A,
const outputTypes outputType,
const colPivoting colPivot = colPivoting::FALSE
const modes mode,
const outputs output = outputs::BOTH_QR,
const bool pivoting = false
);
// Member Functions
// Information
// Access
//- Return the unitary similarity matrix
// Includes implicit round-to-zero as mutable operation
inline const MatrixType& Q() const;
//- Return const reference to Q
const MatrixType& Q() const noexcept
{
return Q_;
}
//- Return the upper triangular matrix
inline const MatrixType& R() const;
//- Return reference to Q
MatrixType& Q() noexcept
{
return Q_;
}
//- Return the permutation order (P) as a list
inline const labelList& orderP() const;
//- Return const reference to R
const MatrixType& R() const noexcept
{
return R_;
}
//- Return reference to R
MatrixType& R() noexcept
{
return R_;
}
//- Return const reference to p
const labelList& p() const noexcept
{
return p_;
}
//- Create and return the permutation matrix
inline SMatrix P() const;
// Algorithm
//- Compute QR decomposition according to constructor settings
void decompose
(
MatrixType& A
);
//- Compute QR decomposition according to constructor settings
void decompose
(
const MatrixType& A
);
// Linear system solution
//- Solve the linear system with the given source
//- and return the solution in the argument x
@ -372,41 +346,39 @@ public:
const IndirectListBase<cmptType, Addr>& source
) const;
//- Return the inverse of (Q*R), so that solving x = (Q*R).inv()*source
SMatrix inv() const;
//- Solve a row-echelon-form linear system starting from the bottom
// Back substitution: Ax = b where:
// A = Non-singular upper-triangular square matrix (m-by-m)
// x = Solution (m-by-p)
// b = Source (m-by-p)
RMatrix backSubstitution
//- Solve a row-echelon-form linear system (Ax = b)
//- starting from the bottom by back substitution
// A = R: Non-singular upper-triangular square matrix (m-by-m)
// b: Source (m-by-p)
// x: Solution (m-by-p)
RMatrix solve
(
const SMatrix& A,
const RMatrix& b
);
//- Return the inverse of (Q*R), solving x = (Q*R).inv()*source
SMatrix inv() const;
};
namespace MatrixTools
{
// * * * * * * * * * * * * * * Global Functions * * * * * * * * * * * * * * //
//- (Out-of-place) Moore-Penrose inverse of singular/non-singular
//- square/rectangular scalar/complex matrices
// (KPP:p. 9834; KP:p. 648)
// The tolerance (i.e. tol) to ensure the R1 matrix full-rank is given as 1e-13
// in the original paper (KPP:p. 9834). Nonetheless, the tolerance = 1e-5
// given by (TA; mentioned in (KPP:p. 9832)) was observed to be more robust
// for the tested scenarios.
//- Moore-Penrose inverse of singular/non-singular square/rectangular
//- scalar/complex matrices (KPP:p. 9834; KP:p. 648)
// The tolerance to ensure the R1 matrix full-rank is set to 1e-5
// by (TA; mentioned in (KPP:p. 9832)) in contrast to 1e-13 (KPP:p. 9834).
template<class MatrixType>
MatrixType pinv
(
const MatrixType& A,
const scalar tol = 1e-5
scalar tol = 1e-5
);
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
} // End namespace MatrixTools
} // End namespace Foam
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

View File

@ -6,7 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2016 OpenFOAM Foundation
Copyright (C) 2019 OpenCFD Ltd.
Copyright (C) 2019-2022 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -26,166 +26,20 @@ License
\*---------------------------------------------------------------------------*/
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
template<class MatrixType>
inline void Foam::QRMatrix<MatrixType>::applyHouseholder
(
MatrixType& A,
const RMatrix& reflector,
const label k
)
{
applyLeftReflector(A, reflector, k, k);
if (outputType_ == outputTypes::FULL_QR)
{
applyRightReflector(Q_, reflector, k);
}
}
// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
template<class MatrixType>
inline Foam::RectangularMatrix<typename MatrixType::cmptType>
Foam::QRMatrix<MatrixType>::householderReflector
(
RMatrix u
)
{
#ifdef FULLDEBUG
// Check if the given RectangularMatrix is effectively a column vector
if (u.n() != 1)
{
FatalErrorInFunction
<< "Input matrix is not a column vector." << exit(FatalError);
}
#endif
scalar magnitude(mag(u(0,0)));
if (magnitude < VSMALL)
{
magnitude = SMALL;
#if FULLDEBUG
FatalErrorInFunction
<< "Almost zero leading elem in Householder matrix."
<< abort(FatalError);
#endif
}
u(0,0) += u(0,0)/magnitude*u.columnNorm(0);
scalar colNorm(u.columnNorm(0));
if (colNorm < VSMALL)
{
colNorm = SMALL;
#if FULLDEBUG
FatalErrorInFunction
<< "Almost zero norm in the Householder matrix."
<< abort(FatalError);
#endif
}
u /= cmptType(colNorm);
return u;
}
template<class MatrixType>
inline void Foam::QRMatrix<MatrixType>::applyLeftReflector
(
MatrixType& A,
const RMatrix& reflector,
const label k,
const label k1
)
{
// const RMatrix& A0(A.subMatrix(k1, k));
// A0 -= (cmptType(2)*reflector)*(reflector & A0);
for (label j = k; j < A.n(); ++j)
{
cmptType sum = Zero;
for (label i = 0; i < reflector.m(); ++i)
{
sum += Detail::conj(reflector(i, 0))*A(i + k1, j);
}
sum *= cmptType(2);
for (label i = 0; i < reflector.m(); ++i)
{
A(i + k1, j) -= reflector(i, 0)*sum;
}
}
}
template<class MatrixType>
inline void Foam::QRMatrix<MatrixType>::applyRightReflector
(
MatrixType& A,
const RMatrix& reflector,
const label k
)
{
// const RMatrix A0(A.subMatrix(0, k));
// A0 -= ((A0*reflector)^(cmptType(2)*reflector))
for (label i = 0; i < A.m(); ++i)
{
cmptType sum = Zero;
for (label j = 0; j < reflector.m(); ++j)
{
sum += A(i, j + k)*reflector(j, 0);
}
sum *= cmptType(2);
for (label j = 0; j < reflector.m(); ++j)
{
A(i, j + k) -= Detail::conj(reflector(j, 0))*sum;
}
}
}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
template<class MatrixType>
inline const MatrixType& Foam::QRMatrix<MatrixType>::Q() const
{
const_cast<MatrixType&>(Q_).round();
return Q_;
}
template<class MatrixType>
inline const MatrixType& Foam::QRMatrix<MatrixType>::R() const
{
return R_;
}
template<class MatrixType>
inline const Foam::labelList& Foam::QRMatrix<MatrixType>::orderP() const
{
return P_;
}
template<class MatrixType>
inline Foam::SquareMatrix<typename Foam::QRMatrix<MatrixType>::cmptType>
inline Foam::SquareMatrix<typename MatrixType::cmptType>
Foam::QRMatrix<MatrixType>::P() const
{
SquareMatrix<cmptType> permMat(P_.size(), Zero);
SquareMatrix<cmptType> P(p_.size(), Zero);
forAll(P_, jcol)
forAll(p_, jcol)
{
permMat(P_[jcol], jcol) = pTraits<cmptType>::one;
P(p_[jcol], jcol) = pTraits<cmptType>::one;
}
return permMat;
return P;
}