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.
551 lines
19 KiB
551 lines
19 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/Matrix.h>
|
|
#include <Mathematics/GMatrix.h>
|
|
|
|
namespace gte
|
|
{
|
|
// Implementation for size known at compile time.
|
|
template <typename Real, int N = 0>
|
|
class CholeskyDecomposition
|
|
{
|
|
public:
|
|
// Ensure that N > 0 at compile time.
|
|
CholeskyDecomposition()
|
|
{
|
|
static_assert(N > 0, "Invalid size in CholeskyDecomposition constructor.");
|
|
}
|
|
|
|
// Disallow copies and moves.
|
|
CholeskyDecomposition(CholeskyDecomposition const&) = delete;
|
|
CholeskyDecomposition& operator=(CholeskyDecomposition const&) = delete;
|
|
CholeskyDecomposition(CholeskyDecomposition&&) = delete;
|
|
CholeskyDecomposition& operator=(CholeskyDecomposition&&) = delete;
|
|
|
|
// On input, A is symmetric. Only the lower-triangular portion is
|
|
// modified. On output, the lower-triangular portion is L where
|
|
// A = L * L^T.
|
|
bool Factor(Matrix<N, N, Real>& A)
|
|
{
|
|
for (int c = 0; c < N; ++c)
|
|
{
|
|
if (A(c, c) <= (Real)0)
|
|
{
|
|
return false;
|
|
}
|
|
A(c, c) = std::sqrt(A(c, c));
|
|
|
|
for (int r = c + 1; r < N; ++r)
|
|
{
|
|
A(r, c) /= A(c, c);
|
|
}
|
|
|
|
for (int k = c + 1; k < N; ++k)
|
|
{
|
|
for (int r = k; r < N; ++r)
|
|
{
|
|
A(r, k) -= A(r, c) * A(k, c);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Solve L*Y = B, where L is lower triangular and invertible. The
|
|
// input value of Y is B. On output, Y is the solution.
|
|
void SolveLower(Matrix<N, N, Real> const& L, Vector<N, Real>& Y)
|
|
{
|
|
for (int r = 0; r < N; ++r)
|
|
{
|
|
for (int c = 0; c < r; ++c)
|
|
{
|
|
Y[r] -= L(r, c) * Y[c];
|
|
}
|
|
Y[r] /= L(r, r);
|
|
}
|
|
}
|
|
|
|
// Solve L^T*X = Y, where L is lower triangular (L^T is upper
|
|
// triangular) and invertible. The input value of X is Y. On
|
|
// output, X is the solution.
|
|
void SolveUpper(Matrix<N, N, Real> const& L, Vector<N, Real>& X)
|
|
{
|
|
for (int r = N - 1; r >= 0; --r)
|
|
{
|
|
for (int c = r + 1; c < N; ++c)
|
|
{
|
|
X[r] -= L(c, r) * X[c];
|
|
}
|
|
X[r] /= L(r, r);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Implementation for size known only at run time.
|
|
template <typename Real>
|
|
class CholeskyDecomposition<Real, 0>
|
|
{
|
|
public:
|
|
int const N;
|
|
|
|
// Ensure that N > 0 at run time.
|
|
CholeskyDecomposition(int n)
|
|
:
|
|
N(n)
|
|
{
|
|
}
|
|
|
|
// Disallow copies and moves. This is required to avoid compiler
|
|
// complaints about the 'int const N' member.
|
|
CholeskyDecomposition(CholeskyDecomposition const&) = delete;
|
|
CholeskyDecomposition& operator=(CholeskyDecomposition const&) = delete;
|
|
CholeskyDecomposition(CholeskyDecomposition&&) = delete;
|
|
CholeskyDecomposition& operator=(CholeskyDecomposition&&) = delete;
|
|
|
|
// On input, A is symmetric. Only the lower-triangular portion is
|
|
// modified. On output, the lower-triangular portion is L where
|
|
// A = L * L^T.
|
|
bool Factor(GMatrix<Real>& A)
|
|
{
|
|
if (A.GetNumRows() == N && A.GetNumCols() == N)
|
|
{
|
|
for (int c = 0; c < N; ++c)
|
|
{
|
|
if (A(c, c) <= (Real)0)
|
|
{
|
|
return false;
|
|
}
|
|
A(c, c) = std::sqrt(A(c, c));
|
|
|
|
for (int r = c + 1; r < N; ++r)
|
|
{
|
|
A(r, c) /= A(c, c);
|
|
}
|
|
|
|
for (int k = c + 1; k < N; ++k)
|
|
{
|
|
for (int r = k; r < N; ++r)
|
|
{
|
|
A(r, k) -= A(r, c) * A(k, c);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
LogError("Matrix must be square.");
|
|
}
|
|
|
|
// Solve L*Y = B, where L is lower triangular and invertible. The
|
|
// input value of Y is B. On output, Y is the solution.
|
|
void SolveLower(GMatrix<Real> const& L, GVector<Real>& Y)
|
|
{
|
|
if (L.GetNumRows() == N && L.GetNumCols() == N && Y.GetSize() == N)
|
|
{
|
|
for (int r = 0; r < N; ++r)
|
|
{
|
|
for (int c = 0; c < r; ++c)
|
|
{
|
|
Y[r] -= L(r, c) * Y[c];
|
|
}
|
|
Y[r] /= L(r, r);
|
|
}
|
|
return;
|
|
}
|
|
LogError("Invalid size.");
|
|
}
|
|
|
|
// Solve L^T*X = Y, where L is lower triangular (L^T is upper
|
|
// triangular) and invertible. The input value of X is Y. On
|
|
// output, X is the solution.
|
|
void SolveUpper(GMatrix<Real> const& L, GVector<Real>& X)
|
|
{
|
|
if (L.GetNumRows() == N && L.GetNumCols() == N && X.GetSize() == N)
|
|
{
|
|
for (int r = N - 1; r >= 0; --r)
|
|
{
|
|
for (int c = r + 1; c < N; ++c)
|
|
{
|
|
X[r] -= L(c, r) * X[c];
|
|
}
|
|
X[r] /= L(r, r);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogError("Invalid size.");
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
// Implementation for sizes known at compile time.
|
|
template <typename Real, int BlockSize = 0, int NumBlocks = 0>
|
|
class BlockCholeskyDecomposition
|
|
{
|
|
public:
|
|
// Let B represent the block size and N represent the number of
|
|
// blocks. The matrix A is (N*B)-by-(N*B) but partitioned into an
|
|
// N-by-N matrix of blocks, each block of size B-by-B. The value
|
|
// N*B is NumDimensions.
|
|
enum
|
|
{
|
|
NumDimensions = NumBlocks * BlockSize
|
|
};
|
|
|
|
typedef std::array<Vector<BlockSize, Real>, NumBlocks> BlockVector;
|
|
typedef std::array<std::array<Matrix<BlockSize, BlockSize, Real>, NumBlocks>, NumBlocks> BlockMatrix;
|
|
|
|
// Ensure that BlockSize > 0 and NumBlocks > 0 at compile time.
|
|
BlockCholeskyDecomposition()
|
|
{
|
|
static_assert(BlockSize > 0 && NumBlocks > 0, "Invalid size in BlockCholeskyDecomposition constructor.");
|
|
}
|
|
|
|
// Disallow copies and moves.
|
|
BlockCholeskyDecomposition(BlockCholeskyDecomposition const&) = delete;
|
|
BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition const&) = delete;
|
|
BlockCholeskyDecomposition(BlockCholeskyDecomposition&&) = delete;
|
|
BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition&&) = delete;
|
|
|
|
// Treating the matrix as a 2D table of scalars with NUM_DIMENSIONS
|
|
// rows and NUM_DIMENSIONS columns, look up the correct block that
|
|
// stores the requested element and return a reference.
|
|
Real Get(BlockMatrix const& M, int row, int col)
|
|
{
|
|
int b0 = col / BlockSize, b1 = row / BlockSize;
|
|
int i0 = col % BlockSize, i1 = row % BlockSize;
|
|
auto const& block = M[b1][b0];
|
|
return block(i1, i0);
|
|
}
|
|
|
|
void Set(BlockMatrix& M, int row, int col, Real value)
|
|
{
|
|
int b0 = col / BlockSize, b1 = row / BlockSize;
|
|
int i0 = col % BlockSize, i1 = row % BlockSize;
|
|
auto& block = M[b1][b0];
|
|
block(i1, i0) = value;
|
|
}
|
|
|
|
bool Factor(BlockMatrix& A)
|
|
{
|
|
for (int c = 0; c < NumBlocks; ++c)
|
|
{
|
|
if (!mDecomposer.Factor(A[c][c]))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int r = c + 1; r < NumBlocks; ++r)
|
|
{
|
|
LowerTriangularSolver(r, c, A);
|
|
}
|
|
|
|
for (int k = c + 1; k < NumBlocks; ++k)
|
|
{
|
|
for (int r = k; r < NumBlocks; ++r)
|
|
{
|
|
SubtractiveUpdate(r, k, c, A);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Solve L*Y = B, where L is an invertible lower-triangular block
|
|
// matrix whose diagonal blocks are lower-triangular matrices.
|
|
// The input B is a block vector of commensurate size. The input
|
|
// value of Y is B. On output, Y is the solution.
|
|
void SolveLower(BlockMatrix const& L, BlockVector& Y)
|
|
{
|
|
for (int r = 0; r < NumBlocks; ++r)
|
|
{
|
|
auto& Yr = Y[r];
|
|
for (int c = 0; c < r; ++c)
|
|
{
|
|
auto const& Lrc = L[r][c];
|
|
auto const& Yc = Y[c];
|
|
for (int i = 0; i < BlockSize; ++i)
|
|
{
|
|
for (int j = 0; j < BlockSize; ++j)
|
|
{
|
|
Yr[i] -= Lrc(i, j) * Yc[j];
|
|
}
|
|
}
|
|
}
|
|
mDecomposer.SolveLower(L[r][r], Yr);
|
|
}
|
|
}
|
|
|
|
// Solve L^T*X = Y, where L is an invertible lower-triangular block
|
|
// matrix (L^T is an upper-triangular block matrix) whose diagonal
|
|
// blocks are lower-triangular matrices. The input value of X is Y.
|
|
// On output, X is the solution.
|
|
void SolveUpper(BlockMatrix const& L, BlockVector& X)
|
|
{
|
|
for (int r = NumBlocks - 1; r >= 0; --r)
|
|
{
|
|
auto& Xr = X[r];
|
|
for (int c = r + 1; c < NumBlocks; ++c)
|
|
{
|
|
auto const& Lcr = L[c][r];
|
|
auto const& Xc = X[c];
|
|
for (int i = 0; i < BlockSize; ++i)
|
|
{
|
|
for (int j = 0; j < BlockSize; ++j)
|
|
{
|
|
Xr[i] -= Lcr(j, i) * Xc[j];
|
|
}
|
|
}
|
|
}
|
|
mDecomposer.SolveUpper(L[r][r], Xr);
|
|
}
|
|
}
|
|
|
|
private:
|
|
// Solve G(c,c)*G(r,c)^T = A(r,c)^T for G(r,c). The matrices
|
|
// G(c,c) and A(r,c) are known quantities, and G(c,c) occupies
|
|
// the lower triangular portion of A(c,c). The solver stores
|
|
// its results in-place, so A(r,c) stores the G(r,c) result.
|
|
void LowerTriangularSolver(int r, int c, BlockMatrix& A)
|
|
{
|
|
auto const& Acc = A[c][c];
|
|
auto& Arc = A[r][c];
|
|
for (int j = 0; j < BlockSize; ++j)
|
|
{
|
|
for (int i = 0; i < j; ++i)
|
|
{
|
|
Real Lji = Acc(j, i);
|
|
for (int k = 0; k < BlockSize; ++k)
|
|
{
|
|
Arc(k, j) -= Lji * Arc(k, i);
|
|
}
|
|
}
|
|
|
|
Real Ljj = Acc(j, j);
|
|
for (int k = 0; k < BlockSize; ++k)
|
|
{
|
|
Arc(k, j) /= Ljj;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SubtractiveUpdate(int r, int k, int c, BlockMatrix& A)
|
|
{
|
|
auto const& Arc = A[r][c];
|
|
auto const& Akc = A[k][c];
|
|
auto& Ark = A[r][k];
|
|
for (int j = 0; j < BlockSize; ++j)
|
|
{
|
|
for (int i = 0; i < BlockSize; ++i)
|
|
{
|
|
for (int m = 0; m < BlockSize; ++m)
|
|
{
|
|
Ark(j, i) -= Arc(j, m) * Akc(i, m);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CholeskyDecomposition<Real, BlockSize> mDecomposer;
|
|
};
|
|
|
|
// Implementation for sizes known only at run time.
|
|
template <typename Real>
|
|
class BlockCholeskyDecomposition<Real, 0, 0>
|
|
{
|
|
public:
|
|
// Let B represent the block size and N represent the number of
|
|
// blocks. The matrix A is (N*B)-by-(N*B) but partitioned into an
|
|
// N-by-N matrix of blocks, each block of size B-by-B. The value
|
|
// N*B is NumDimensions.
|
|
int const BlockSize;
|
|
int const NumBlocks;
|
|
int const NumDimensions;
|
|
|
|
// The number of elements in a BlockVector object must be NumBlocks
|
|
// and each GVector element has BlockSize components.
|
|
typedef std::vector<GVector<Real>> BlockVector;
|
|
|
|
// The BlockMatrix is an array of NumBlocks-by-NumBlocks matrices.
|
|
// Each block matrix is stored in row-major order. The BlockMatrix
|
|
// elements themselves are stored in row-major order. The block
|
|
// matrix element M = BlockMatrix[col + NumBlocks * row] is of size
|
|
// BlockSize-by-BlockSize (in row-major order) and is in the (row,col)
|
|
// location of the full matrix of blocks.
|
|
typedef std::vector<GMatrix<Real>> BlockMatrix;
|
|
|
|
// Ensure that BlockSize > 0 and NumDimensions > 0 at run time.
|
|
BlockCholeskyDecomposition(int blockSize, int numBlocks)
|
|
:
|
|
BlockSize(blockSize),
|
|
NumBlocks(numBlocks),
|
|
NumDimensions(numBlocks * blockSize),
|
|
mDecomposer(blockSize)
|
|
{
|
|
LogAssert(blockSize > 0 && numBlocks > 0, "Invalid input.");
|
|
}
|
|
|
|
// Disallow copies and moves. This is required to avoid compiler
|
|
// complaints about the 'int const' members.
|
|
BlockCholeskyDecomposition(BlockCholeskyDecomposition const&) = delete;
|
|
BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition const&) = delete;
|
|
BlockCholeskyDecomposition(BlockCholeskyDecomposition&&) = delete;
|
|
BlockCholeskyDecomposition& operator=(BlockCholeskyDecomposition&&) = delete;
|
|
|
|
// Treating the matrix as a 2D table of scalars with NumDimensions
|
|
// rows and NumDimensions columns, look up the correct block that
|
|
// stores the requested element and return a reference.
|
|
Real Get(BlockMatrix const& M, int row, int col)
|
|
{
|
|
int b0 = col / BlockSize, b1 = row / BlockSize;
|
|
int i0 = col % BlockSize, i1 = row % BlockSize;
|
|
auto const& block = M[GetIndex(b1, b0)];
|
|
return block(i1, i0);
|
|
}
|
|
|
|
void Set(BlockMatrix& M, int row, int col, Real value)
|
|
{
|
|
int b0 = col / BlockSize, b1 = row / BlockSize;
|
|
int i0 = col % BlockSize, i1 = row % BlockSize;
|
|
auto& block = M[GetIndex(b1, b0)];
|
|
block(i1, i0) = value;
|
|
}
|
|
|
|
bool Factor(BlockMatrix& A)
|
|
{
|
|
for (int c = 0; c < NumBlocks; ++c)
|
|
{
|
|
if (!mDecomposer.Factor(A[GetIndex(c, c)]))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int r = c + 1; r < NumBlocks; ++r)
|
|
{
|
|
LowerTriangularSolver(r, c, A);
|
|
}
|
|
|
|
for (int k = c + 1; k < NumBlocks; ++k)
|
|
{
|
|
for (int r = k; r < NumBlocks; ++r)
|
|
{
|
|
SubtractiveUpdate(r, k, c, A);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Solve L*Y = B, where L is an invertible lower-triangular block
|
|
// matrix whose diagonal blocks are lower-triangular matrices.
|
|
// The input B is a block vector of commensurate size. The input
|
|
// value of Y is B. On output, Y is the solution.
|
|
void SolveLower(BlockMatrix const& L, BlockVector& Y)
|
|
{
|
|
for (int r = 0; r < NumBlocks; ++r)
|
|
{
|
|
auto& Yr = Y[r];
|
|
for (int c = 0; c < r; ++c)
|
|
{
|
|
auto const& Lrc = L[GetIndex(r, c)];
|
|
auto const& Yc = Y[c];
|
|
for (int i = 0; i < NumBlocks; ++i)
|
|
{
|
|
for (int j = 0; j < NumBlocks; ++j)
|
|
{
|
|
Yr[i] -= Lrc[GetIndex(i, j)] * Yc[j];
|
|
}
|
|
}
|
|
}
|
|
mDecomposer.SolveLower(L[GetIndex(r, r)], Yr);
|
|
}
|
|
}
|
|
|
|
// Solve L^T*X = Y, where L is an invertible lower-triangular block
|
|
// matrix (L^T is an upper-triangular block matrix) whose diagonal
|
|
// blocks are lower-triangular matrices. The input value of X is Y.
|
|
// On output, X is the solution.
|
|
void SolveUpper(BlockMatrix const& L, BlockVector& X)
|
|
{
|
|
for (int r = NumBlocks - 1; r >= 0; --r)
|
|
{
|
|
auto& Xr = X[r];
|
|
for (int c = r + 1; c < NumBlocks; ++c)
|
|
{
|
|
auto const& Lcr = L[GetIndex(c, r)];
|
|
auto const& Xc = X[c];
|
|
for (int i = 0; i < BlockSize; ++i)
|
|
{
|
|
for (int j = 0; j < BlockSize; ++j)
|
|
{
|
|
Xr[i] -= Lcr[GetIndex(j, i)] * Xc[j];
|
|
}
|
|
}
|
|
}
|
|
mDecomposer.SolveUpper(L[GetIndex(r, r)], Xr);
|
|
}
|
|
}
|
|
|
|
private:
|
|
// Compute the 1-dimensional index of the block matrix in a
|
|
// 2-dimensional BlockMatrix object.
|
|
inline int GetIndex(int row, int col) const
|
|
{
|
|
return col + row * NumBlocks;
|
|
}
|
|
|
|
// Solve G(c,c)*G(r,c)^T = A(r,c)^T for G(r,c). The matrices
|
|
// G(c,c) and A(r,c) are known quantities, and G(c,c) occupies
|
|
// the lower triangular portion of A(c,c). The solver stores
|
|
// its results in-place, so A(r,c) stores the G(r,c) result.
|
|
void LowerTriangularSolver(int r, int c, BlockMatrix& A)
|
|
{
|
|
auto const& Acc = A[GetIndex(c, c)];
|
|
auto& Arc = A[GetIndex(r, c)];
|
|
for (int j = 0; j < BlockSize; ++j)
|
|
{
|
|
for (int i = 0; i < j; ++i)
|
|
{
|
|
Real Lji = Acc[GetIndex(j, i)];
|
|
for (int k = 0; k < BlockSize; ++k)
|
|
{
|
|
Arc[GetIndex(k, j)] -= Lji * Arc[GetIndex(k, i)];
|
|
}
|
|
}
|
|
|
|
Real Ljj = Acc[GetIndex(j, j)];
|
|
for (int k = 0; k < BlockSize; ++k)
|
|
{
|
|
Arc[GetIndex(k, j)] /= Ljj;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SubtractiveUpdate(int r, int k, int c, BlockMatrix& A)
|
|
{
|
|
auto const& Arc = A[GetIndex(r, c)];
|
|
auto const& Akc = A[GetIndex(k, c)];
|
|
auto& Ark = A[GetIndex(r, k)];
|
|
for (int j = 0; j < BlockSize; ++j)
|
|
{
|
|
for (int i = 0; i < BlockSize; ++i)
|
|
{
|
|
for (int m = 0; m < BlockSize; ++m)
|
|
{
|
|
Ark[GetIndex(j, i)] -= Arc[GetIndex(j, m)] * Akc[GetIndex(i, m)];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The decomposer has size BlockSize.
|
|
CholeskyDecomposition<Real> mDecomposer;
|
|
};
|
|
}
|
|
|