You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
520 lines
17 KiB
520 lines
17 KiB
// David Eberly, Geometric Tools, Redmond WA 98052
|
|
// Copyright (c) 1998-2021
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// https://www.boost.org/LICENSE_1_0.txt
|
|
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
|
|
// Version: 4.0.2019.08.13
|
|
|
|
#pragma once
|
|
|
|
#include <Mathematics/Math.h>
|
|
#include <Mathematics/LexicoArray2.h>
|
|
#include <vector>
|
|
|
|
namespace gte
|
|
{
|
|
template <typename Real>
|
|
class BandedMatrix
|
|
{
|
|
public:
|
|
// Construction and destruction.
|
|
BandedMatrix(int size, int numLBands, int numUBands)
|
|
:
|
|
mSize(size),
|
|
mZero((Real)0)
|
|
{
|
|
if (size > 0
|
|
&& 0 <= numLBands && numLBands < size
|
|
&& 0 <= numUBands && numUBands < size)
|
|
{
|
|
mDBand.resize(size);
|
|
std::fill(mDBand.begin(), mDBand.end(), (Real)0);
|
|
|
|
if (numLBands > 0)
|
|
{
|
|
mLBands.resize(numLBands);
|
|
int numElements = size - 1;
|
|
for (auto& band : mLBands)
|
|
{
|
|
band.resize(numElements--);
|
|
std::fill(band.begin(), band.end(), (Real)0);
|
|
}
|
|
}
|
|
|
|
if (numUBands > 0)
|
|
{
|
|
mUBands.resize(numUBands);
|
|
int numElements = size - 1;
|
|
for (auto& band : mUBands)
|
|
{
|
|
band.resize(numElements--);
|
|
std::fill(band.begin(), band.end(), (Real)0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Invalid argument to BandedMatrix constructor.
|
|
mSize = 0;
|
|
}
|
|
}
|
|
|
|
~BandedMatrix()
|
|
{
|
|
}
|
|
|
|
// Member access.
|
|
inline int GetSize() const
|
|
{
|
|
return mSize;
|
|
}
|
|
|
|
inline std::vector<Real>& GetDBand()
|
|
{
|
|
return mDBand;
|
|
}
|
|
|
|
inline std::vector<Real> const& GetDBand() const
|
|
{
|
|
return mDBand;
|
|
}
|
|
|
|
inline std::vector<std::vector<Real>>& GetLBands()
|
|
{
|
|
return mLBands;
|
|
}
|
|
|
|
inline std::vector<std::vector<Real>> const& GetLBands() const
|
|
{
|
|
return mLBands;
|
|
}
|
|
|
|
inline std::vector<std::vector<Real>>& GetUBands()
|
|
{
|
|
return mUBands;
|
|
}
|
|
|
|
inline std::vector<std::vector<Real>> const& GetUBands() const
|
|
{
|
|
return mUBands;
|
|
}
|
|
|
|
Real& operator()(int r, int c)
|
|
{
|
|
if (0 <= r && r < mSize && 0 <= c && c < mSize)
|
|
{
|
|
int band = c - r;
|
|
if (band > 0)
|
|
{
|
|
int const numUBands = static_cast<int>(mUBands.size());
|
|
if (--band < numUBands && r < mSize - 1 - band)
|
|
{
|
|
return mUBands[band][r];
|
|
}
|
|
}
|
|
else if (band < 0)
|
|
{
|
|
band = -band;
|
|
int const numLBands = static_cast<int>(mLBands.size());
|
|
if (--band < numLBands && c < mSize - 1 - band)
|
|
{
|
|
return mLBands[band][c];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return mDBand[r];
|
|
}
|
|
}
|
|
// else invalid index
|
|
|
|
|
|
// Set the value to zero in case someone unknowingly modified mZero on a
|
|
// previous call to operator(int,int).
|
|
mZero = (Real)0;
|
|
return mZero;
|
|
}
|
|
|
|
Real const& operator()(int r, int c) const
|
|
{
|
|
if (0 <= r && r < mSize && 0 <= c && c < mSize)
|
|
{
|
|
int band = c - r;
|
|
if (band > 0)
|
|
{
|
|
int const numUBands = static_cast<int>(mUBands.size());
|
|
if (--band < numUBands && r < mSize - 1 - band)
|
|
{
|
|
return mUBands[band][r];
|
|
}
|
|
}
|
|
else if (band < 0)
|
|
{
|
|
band = -band;
|
|
int const numLBands = static_cast<int>(mLBands.size());
|
|
if (--band < numLBands && c < mSize - 1 - band)
|
|
{
|
|
return mLBands[band][c];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return mDBand[r];
|
|
}
|
|
}
|
|
// else invalid index
|
|
|
|
|
|
// Set the value to zero in case someone unknowingly modified
|
|
// mZero on a previous call to operator(int,int).
|
|
mZero = (Real)0;
|
|
return mZero;
|
|
}
|
|
|
|
// Factor the square banded matrix A into A = L*L^T, where L is a
|
|
// lower-triangular matrix (L^T is an upper-triangular matrix). This
|
|
// is an LU decomposition that allows for stable inversion of A to
|
|
// solve A*X = B. The return value is 'true' iff the factorizing is
|
|
// successful (L is invertible). If successful, A contains the
|
|
// Cholesky factorization: L in the lower-triangular part of A and
|
|
// L^T in the upper-triangular part of A.
|
|
bool CholeskyFactor()
|
|
{
|
|
if (mDBand.size() == 0 || mLBands.size() != mUBands.size())
|
|
{
|
|
// Invalid number of bands.
|
|
return false;
|
|
}
|
|
|
|
int const sizeM1 = mSize - 1;
|
|
int const numBands = static_cast<int>(mLBands.size());
|
|
|
|
int k, kMax;
|
|
for (int i = 0; i < mSize; ++i)
|
|
{
|
|
int jMin = i - numBands;
|
|
if (jMin < 0)
|
|
{
|
|
jMin = 0;
|
|
}
|
|
|
|
int j;
|
|
for (j = jMin; j < i; ++j)
|
|
{
|
|
kMax = j + numBands;
|
|
if (kMax > sizeM1)
|
|
{
|
|
kMax = sizeM1;
|
|
}
|
|
|
|
for (k = i; k <= kMax; ++k)
|
|
{
|
|
operator()(k, i) -= operator()(i, j) * operator()(k, j);
|
|
}
|
|
}
|
|
|
|
kMax = j + numBands;
|
|
if (kMax > sizeM1)
|
|
{
|
|
kMax = sizeM1;
|
|
}
|
|
|
|
for (k = 0; k < i; ++k)
|
|
{
|
|
operator()(k, i) = operator()(i, k);
|
|
}
|
|
|
|
Real diagonal = operator()(i, i);
|
|
if (diagonal <= (Real)0)
|
|
{
|
|
return false;
|
|
}
|
|
Real invSqrt = ((Real)1) / std::sqrt(diagonal);
|
|
for (k = i; k <= kMax; ++k)
|
|
{
|
|
operator()(k, i) *= invSqrt;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Solve the linear system A*X = B, where A is an NxN banded matrix
|
|
// and B is an Nx1 vector. The unknown X is also Nx1. The input to
|
|
// this function is B. The output X is computed and stored in B. The
|
|
// return value is 'true' iff the system has a solution. The matrix A
|
|
// and the vector B are both modified by this function. If
|
|
// successful, A contains the Cholesky factorization: L in the
|
|
// lower-triangular part of A and L^T in the upper-triangular part
|
|
// of A.
|
|
bool SolveSystem(Real* bVector)
|
|
{
|
|
return CholeskyFactor()
|
|
&& SolveLower(bVector)
|
|
&& SolveUpper(bVector);
|
|
}
|
|
|
|
// Solve the linear system A*X = B, where A is an NxN banded matrix
|
|
// and B is an NxM matrix. The unknown X is also NxM. The input to
|
|
// this function is B. The output X is computed and stored in B. The
|
|
// return value is 'true' iff the system has a solution. The matrix A
|
|
// and the vector B are both modified by this function. If
|
|
// successful, A contains the Cholesky factorization: L in the
|
|
// lower-triangular part of A and L^T in the upper-triangular part
|
|
// of A.
|
|
//
|
|
// 'bMatrix' must have the storage order specified by the template
|
|
// parameter.
|
|
template <bool RowMajor>
|
|
bool SolveSystem(Real* bMatrix, int numBColumns)
|
|
{
|
|
return CholeskyFactor()
|
|
&& SolveLower<RowMajor>(bMatrix, numBColumns)
|
|
&& SolveUpper<RowMajor>(bMatrix, numBColumns);
|
|
}
|
|
|
|
// Compute the inverse of the banded matrix. The return value is
|
|
// 'true' when the matrix is invertible, in which case the 'inverse'
|
|
// output is valid. The return value is 'false' when the matrix is
|
|
// not invertible, in which case 'inverse' is invalid and should not
|
|
// be used. The input matrix 'inverse' must be the same size as
|
|
// 'this'.
|
|
//
|
|
// 'bMatrix' must have the storage order specified by the template
|
|
// parameter.
|
|
template <bool RowMajor>
|
|
bool ComputeInverse(Real* inverse) const
|
|
{
|
|
LexicoArray2<RowMajor, Real> invA(mSize, mSize, inverse);
|
|
|
|
BandedMatrix<Real> tmpA = *this;
|
|
for (int row = 0; row < mSize; ++row)
|
|
{
|
|
for (int col = 0; col < mSize; ++col)
|
|
{
|
|
if (row != col)
|
|
{
|
|
invA(row, col) = (Real)0;
|
|
}
|
|
else
|
|
{
|
|
invA(row, row) = (Real)1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Forward elimination.
|
|
for (int row = 0; row < mSize; ++row)
|
|
{
|
|
// The pivot must be nonzero in order to proceed.
|
|
Real diag = tmpA(row, row);
|
|
if (diag == (Real)0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Real invDiag = ((Real)1) / diag;
|
|
tmpA(row, row) = (Real)1;
|
|
|
|
// Multiply the row to be consistent with diagonal term of 1.
|
|
int colMin = row + 1;
|
|
int colMax = colMin + static_cast<int>(mUBands.size());
|
|
if (colMax > mSize)
|
|
{
|
|
colMax = mSize;
|
|
}
|
|
|
|
int c;
|
|
for (c = colMin; c < colMax; ++c)
|
|
{
|
|
tmpA(row, c) *= invDiag;
|
|
}
|
|
for (c = 0; c <= row; ++c)
|
|
{
|
|
invA(row, c) *= invDiag;
|
|
}
|
|
|
|
// Reduce the remaining rows.
|
|
int rowMin = row + 1;
|
|
int rowMax = rowMin + static_cast<int>(mLBands.size());
|
|
if (rowMax > mSize)
|
|
{
|
|
rowMax = mSize;
|
|
}
|
|
|
|
for (int r = rowMin; r < rowMax; ++r)
|
|
{
|
|
Real mult = tmpA(r, row);
|
|
tmpA(r, row) = (Real)0;
|
|
for (c = colMin; c < colMax; ++c)
|
|
{
|
|
tmpA(r, c) -= mult * tmpA(row, c);
|
|
}
|
|
for (c = 0; c <= row; ++c)
|
|
{
|
|
invA(r, c) -= mult * invA(row, c);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Backward elimination.
|
|
for (int row = mSize - 1; row >= 1; --row)
|
|
{
|
|
int rowMax = row - 1;
|
|
int rowMin = row - static_cast<int>(mUBands.size());
|
|
if (rowMin < 0)
|
|
{
|
|
rowMin = 0;
|
|
}
|
|
|
|
for (int r = rowMax; r >= rowMin; --r)
|
|
{
|
|
Real mult = tmpA(r, row);
|
|
tmpA(r, row) = (Real)0;
|
|
for (int c = 0; c < mSize; ++c)
|
|
{
|
|
invA(r, c) -= mult * invA(row, c);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
// The linear system is L*U*X = B, where A = L*U and U = L^T, Reduce
|
|
// this to U*X = L^{-1}*B. The return value is 'true' iff the
|
|
// operation is successful.
|
|
bool SolveLower(Real* dataVector) const
|
|
{
|
|
int const size = static_cast<int>(mDBand.size());
|
|
for (int r = 0; r < size; ++r)
|
|
{
|
|
Real lowerRR = operator()(r, r);
|
|
if (lowerRR > (Real)0)
|
|
{
|
|
for (int c = 0; c < r; ++c)
|
|
{
|
|
Real lowerRC = operator()(r, c);
|
|
dataVector[r] -= lowerRC * dataVector[c];
|
|
}
|
|
dataVector[r] /= lowerRR;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// The linear system is U*X = L^{-1}*B. Reduce this to
|
|
// X = U^{-1}*L^{-1}*B. The return value is 'true' iff the operation
|
|
// is successful.
|
|
bool SolveUpper(Real* dataVector) const
|
|
{
|
|
int const size = static_cast<int>(mDBand.size());
|
|
for (int r = size - 1; r >= 0; --r)
|
|
{
|
|
Real upperRR = operator()(r, r);
|
|
if (upperRR > (Real)0)
|
|
{
|
|
for (int c = r + 1; c < size; ++c)
|
|
{
|
|
Real upperRC = operator()(r, c);
|
|
dataVector[r] -= upperRC * dataVector[c];
|
|
}
|
|
|
|
dataVector[r] /= upperRR;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// The linear system is L*U*X = B, where A = L*U and U = L^T, Reduce
|
|
// this to U*X = L^{-1}*B. The return value is 'true' iff the
|
|
// operation is successful. See the comments for
|
|
// SolveSystem(Real*,int) about the storage for dataMatrix.
|
|
template <bool RowMajor>
|
|
bool SolveLower(Real* dataMatrix, int numColumns) const
|
|
{
|
|
LexicoArray2<RowMajor, Real> data(mSize, numColumns, dataMatrix);
|
|
|
|
for (int r = 0; r < mSize; ++r)
|
|
{
|
|
Real lowerRR = operator()(r, r);
|
|
if (lowerRR > (Real)0)
|
|
{
|
|
for (int c = 0; c < r; ++c)
|
|
{
|
|
Real lowerRC = operator()(r, c);
|
|
for (int bCol = 0; bCol < numColumns; ++bCol)
|
|
{
|
|
data(r, bCol) -= lowerRC * data(c, bCol);
|
|
}
|
|
}
|
|
|
|
Real inverse = ((Real)1) / lowerRR;
|
|
for (int bCol = 0; bCol < numColumns; ++bCol)
|
|
{
|
|
data(r, bCol) *= inverse;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// The linear system is U*X = L^{-1}*B. Reduce this to
|
|
// X = U^{-1}*L^{-1}*B. The return value is 'true' iff the operation
|
|
// is successful. See the comments for SolveSystem(Real*,int) about
|
|
// the storage for dataMatrix.
|
|
template <bool RowMajor>
|
|
bool SolveUpper(Real* dataMatrix, int numColumns) const
|
|
{
|
|
LexicoArray2<RowMajor, Real> data(mSize, numColumns, dataMatrix);
|
|
|
|
for (int r = mSize - 1; r >= 0; --r)
|
|
{
|
|
Real upperRR = operator()(r, r);
|
|
if (upperRR > (Real)0)
|
|
{
|
|
for (int c = r + 1; c < mSize; ++c)
|
|
{
|
|
Real upperRC = operator()(r, c);
|
|
for (int bCol = 0; bCol < numColumns; ++bCol)
|
|
{
|
|
data(r, bCol) -= upperRC * data(c, bCol);
|
|
}
|
|
}
|
|
|
|
Real inverse = ((Real)1) / upperRR;
|
|
for (int bCol = 0; bCol < numColumns; ++bCol)
|
|
{
|
|
data(r, bCol) *= inverse;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int mSize;
|
|
std::vector<Real> mDBand;
|
|
std::vector<std::vector<Real>> mLBands, mUBands;
|
|
|
|
// For return by operator()(int,int) for valid indices not in the
|
|
// bands, in which case the matrix entries are zero,
|
|
mutable Real mZero;
|
|
};
|
|
}
|
|
|