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.
544 lines
15 KiB
544 lines
15 KiB
/** |
|
* @file SquareMatrix.hpp |
|
* |
|
* A square matrix |
|
* |
|
* @author James Goppert <james.goppert@gmail.com> |
|
*/ |
|
|
|
#pragma once |
|
|
|
#include "math.hpp" |
|
|
|
namespace matrix |
|
{ |
|
|
|
template <typename Type, size_t M, size_t N> |
|
class Matrix; |
|
|
|
template <typename Type, size_t M> |
|
class Vector; |
|
|
|
template <typename Type, size_t P, size_t Q, size_t M, size_t N> |
|
class Slice; |
|
|
|
template <typename Type, size_t M> |
|
class SquareMatrix : public Matrix<Type, M, M> |
|
{ |
|
public: |
|
SquareMatrix() = default; |
|
|
|
explicit SquareMatrix(const Type data_[M][M]) : |
|
Matrix<Type, M, M>(data_) |
|
{ |
|
} |
|
|
|
explicit SquareMatrix(const Type data_[M*M]) : |
|
Matrix<Type, M, M>(data_) |
|
{ |
|
} |
|
|
|
SquareMatrix(const Matrix<Type, M, M> &other) : |
|
Matrix<Type, M, M>(other) |
|
{ |
|
} |
|
|
|
template<size_t P, size_t Q> |
|
SquareMatrix(const Slice<Type, M, M, P, Q>& in_slice) : Matrix<Type, M, M>(in_slice) |
|
{ |
|
} |
|
|
|
SquareMatrix<Type, M>& operator=(const Matrix<Type, M, M>& other) |
|
{ |
|
Matrix<Type, M, M>::operator=(other); |
|
return *this; |
|
} |
|
|
|
template <size_t P, size_t Q> |
|
SquareMatrix<Type, M> & operator=(const Slice<Type, M, M, P, Q>& in_slice) |
|
{ |
|
Matrix<Type, M, M>::operator=(in_slice); |
|
return *this; |
|
} |
|
|
|
template<size_t P, size_t Q> |
|
const Slice<Type, P, Q, M, M> slice(size_t x0, size_t y0) const |
|
{ |
|
return Slice<Type, P, Q, M, M>(x0, y0, this); |
|
} |
|
|
|
template<size_t P, size_t Q> |
|
Slice<Type, P, Q, M, M> slice(size_t x0, size_t y0) |
|
{ |
|
return Slice<Type, P, Q, M, M>(x0, y0, this); |
|
} |
|
|
|
// inverse alias |
|
inline SquareMatrix<Type, M> I() const |
|
{ |
|
SquareMatrix<Type, M> i; |
|
if (inv(*this, i)) { |
|
return i; |
|
} else { |
|
i.setZero(); |
|
return i; |
|
} |
|
} |
|
|
|
// inverse alias |
|
inline bool I(SquareMatrix<Type, M> &i) const |
|
{ |
|
return inv(*this, i); |
|
} |
|
|
|
|
|
Vector<Type, M> diag() const |
|
{ |
|
Vector<Type, M> res; |
|
const SquareMatrix<Type, M> &self = *this; |
|
|
|
for (size_t i = 0; i < M; i++) { |
|
res(i) = self(i, i); |
|
} |
|
return res; |
|
} |
|
|
|
// get matrix upper right triangle in a row-major vector format |
|
Vector<Type, M * (M + 1) / 2> upper_right_triangle() const |
|
{ |
|
Vector<Type, M * (M + 1) / 2> res; |
|
const SquareMatrix<Type, M> &self = *this; |
|
|
|
unsigned idx = 0; |
|
for (size_t x = 0; x < M; x++) { |
|
for (size_t y = x; y < M; y++) { |
|
res(idx) = self(x, y); |
|
++idx; |
|
} |
|
} |
|
|
|
return res; |
|
} |
|
|
|
Type trace() const |
|
{ |
|
Type res = 0; |
|
const SquareMatrix<Type, M> &self = *this; |
|
|
|
for (size_t i = 0; i < M; i++) { |
|
res += self(i, i); |
|
} |
|
return res; |
|
} |
|
|
|
// zero all offdiagonal elements and keep corresponding diagonal elements |
|
template <size_t Width> |
|
void uncorrelateCovariance(size_t first) |
|
{ |
|
static_assert(Width <= M, "Width bigger than matrix"); |
|
assert(first + Width <= M); |
|
|
|
SquareMatrix<Type, M> &self = *this; |
|
Vector<Type, Width> diag_elements = self.slice<Width, Width>(first, first).diag(); |
|
self.uncorrelateCovarianceSetVariance(first, diag_elements); |
|
} |
|
|
|
template <size_t Width> |
|
void uncorrelateCovarianceSetVariance(size_t first, const Vector<Type, Width> &vec) |
|
{ |
|
static_assert(Width <= M, "Width bigger than matrix"); |
|
assert(first + Width <= M); |
|
|
|
SquareMatrix<Type, M> &self = *this; |
|
// zero rows and columns |
|
self.slice<Width, M>(first, 0) = Type(0); |
|
self.slice<M, Width>(0, first) = Type(0); |
|
|
|
// set diagonals |
|
unsigned vec_idx = 0; |
|
for (size_t idx = first; idx < first+Width; idx++) { |
|
self(idx,idx) = vec(vec_idx); |
|
vec_idx ++; |
|
} |
|
} |
|
|
|
template <size_t Width> |
|
void uncorrelateCovarianceSetVariance(size_t first, Type val) |
|
{ |
|
static_assert(Width <= M, "Width bigger than matrix"); |
|
assert(first + Width <= M); |
|
|
|
SquareMatrix<Type, M> &self = *this; |
|
// zero rows and columns |
|
self.slice<Width, M>(first, 0) = Type(0); |
|
self.slice<M, Width>(0, first) = Type(0); |
|
|
|
// set diagonals |
|
for (size_t idx = first; idx < first+Width; idx++) { |
|
self(idx,idx) = val; |
|
} |
|
} |
|
|
|
// make block diagonal symmetric by taking the average of the two corresponding off diagonal values |
|
template <size_t Width> |
|
void makeBlockSymmetric(size_t first) |
|
{ |
|
static_assert(Width <= M, "Width bigger than matrix"); |
|
assert(first + Width <= M); |
|
|
|
SquareMatrix<Type, M> &self = *this; |
|
if(Width>1) { |
|
for (size_t row_idx = first+1; row_idx < first+Width; row_idx++) { |
|
for (size_t col_idx = first; col_idx < row_idx; col_idx++) { |
|
Type tmp = (self(row_idx,col_idx) + self(col_idx,row_idx)) / Type(2); |
|
self(row_idx,col_idx) = tmp; |
|
self(col_idx,row_idx) = tmp; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// make rows and columns symmetric by taking the average of the two corresponding off diagonal values |
|
template <size_t Width> |
|
void makeRowColSymmetric(size_t first) |
|
{ |
|
static_assert(Width <= M, "Width bigger than matrix"); |
|
assert(first + Width <= M); |
|
|
|
SquareMatrix<Type, M> &self = *this; |
|
self.makeBlockSymmetric<Width>(first); |
|
for (size_t row_idx = first; row_idx < first+Width; row_idx++) { |
|
for (size_t col_idx = 0; col_idx < first; col_idx++) { |
|
Type tmp = (self(row_idx,col_idx) + self(col_idx,row_idx)) / Type(2); |
|
self(row_idx,col_idx) = tmp; |
|
self(col_idx,row_idx) = tmp; |
|
} |
|
for (size_t col_idx = first+Width; col_idx < M; col_idx++) { |
|
Type tmp = (self(row_idx,col_idx) + self(col_idx,row_idx)) / Type(2); |
|
self(row_idx,col_idx) = tmp; |
|
self(col_idx,row_idx) = tmp; |
|
} |
|
} |
|
} |
|
|
|
// checks if block diagonal is symmetric |
|
template <size_t Width> |
|
bool isBlockSymmetric(size_t first, const Type eps = Type(1e-8f)) |
|
{ |
|
static_assert(Width <= M, "Width bigger than matrix"); |
|
assert(first + Width <= M); |
|
|
|
SquareMatrix<Type, M> &self = *this; |
|
if(Width>1) { |
|
for (size_t row_idx = first+1; row_idx < first+Width; row_idx++) { |
|
for (size_t col_idx = first; col_idx < row_idx; col_idx++) { |
|
if(!isEqualF(self(row_idx,col_idx), self(col_idx,row_idx), eps)) { |
|
return false; |
|
} |
|
} |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
// checks if rows and columns are symmetric |
|
template <size_t Width> |
|
bool isRowColSymmetric(size_t first, const Type eps = Type(1e-8f)) |
|
{ |
|
static_assert(Width <= M, "Width bigger than matrix"); |
|
assert(first + Width <= M); |
|
|
|
SquareMatrix<Type, M> &self = *this; |
|
for (size_t row_idx = first; row_idx < first+Width; row_idx++) { |
|
for (size_t col_idx = 0; col_idx < first; col_idx++) { |
|
if(!isEqualF(self(row_idx,col_idx), self(col_idx,row_idx), eps)) { |
|
return false; |
|
} |
|
} |
|
for (size_t col_idx = first+Width; col_idx < M; col_idx++) { |
|
if(!isEqualF(self(row_idx,col_idx), self(col_idx,row_idx), eps)) { |
|
return false; |
|
} |
|
} |
|
} |
|
return self.isBlockSymmetric<Width>(first, eps); |
|
} |
|
|
|
}; |
|
|
|
using SquareMatrix3f = SquareMatrix<float, 3>; |
|
using SquareMatrix3d = SquareMatrix<double, 3>; |
|
|
|
template<typename Type, size_t M> |
|
SquareMatrix<Type, M> eye() { |
|
SquareMatrix<Type, M> m; |
|
m.setIdentity(); |
|
return m; |
|
} |
|
|
|
template<typename Type, size_t M> |
|
SquareMatrix<Type, M> diag(Vector<Type, M> d) { |
|
SquareMatrix<Type, M> m; |
|
for (size_t i=0; i<M; i++) { |
|
m(i,i) = d(i); |
|
} |
|
return m; |
|
} |
|
|
|
template<typename Type, size_t M> |
|
SquareMatrix<Type, M> expm(const Matrix<Type, M, M> & A, size_t order=5) |
|
{ |
|
SquareMatrix<Type, M> res; |
|
SquareMatrix<Type, M> A_pow = A; |
|
res.setIdentity(); |
|
size_t i_factorial = 1; |
|
for (size_t i=1; i<=order; i++) { |
|
i_factorial *= i; |
|
res += A_pow / Type(i_factorial); |
|
A_pow *= A_pow; |
|
} |
|
|
|
return res; |
|
} |
|
|
|
|
|
/** |
|
* inverse based on LU factorization with partial pivotting |
|
*/ |
|
template<typename Type, size_t M> |
|
bool inv(const SquareMatrix<Type, M> & A, SquareMatrix<Type, M> & inv, size_t rank = M) |
|
{ |
|
SquareMatrix<Type, M> L; |
|
L.setIdentity(); |
|
SquareMatrix<Type, M> U = A; |
|
SquareMatrix<Type, M> P; |
|
P.setIdentity(); |
|
|
|
//printf("A:\n"); A.print(); |
|
|
|
// for all diagonal elements |
|
for (size_t n = 0; n < rank; n++) { |
|
|
|
// if diagonal is zero, swap with row below |
|
if (fabs(U(n, n)) < Type(FLT_EPSILON)) { |
|
//printf("trying pivot for row %d\n",n); |
|
for (size_t i = n + 1; i < rank; i++) { |
|
|
|
//printf("\ttrying row %d\n",i); |
|
if (fabs(U(i, n)) > Type(FLT_EPSILON)) { |
|
//printf("swapped %d\n",i); |
|
U.swapRows(i, n); |
|
P.swapRows(i, n); |
|
L.swapRows(i, n); |
|
L.swapCols(i, n); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
#ifdef MATRIX_ASSERT |
|
//printf("A:\n"); A.print(); |
|
//printf("U:\n"); U.print(); |
|
//printf("P:\n"); P.print(); |
|
//fflush(stdout); |
|
//ASSERT(fabs(U(n, n)) > 1e-8f); |
|
#endif |
|
|
|
// failsafe, return zero matrix |
|
if (fabs(static_cast<float>(U(n, n))) < FLT_EPSILON) { |
|
return false; |
|
} |
|
|
|
// for all rows below diagonal |
|
for (size_t i = (n + 1); i < rank; i++) { |
|
L(i, n) = U(i, n) / U(n, n); |
|
|
|
// add i-th row and n-th row |
|
// multiplied by: -a(i,n)/a(n,n) |
|
for (size_t k = n; k < rank; k++) { |
|
U(i, k) -= L(i, n) * U(n, k); |
|
} |
|
} |
|
} |
|
|
|
//printf("L:\n"); L.print(); |
|
//printf("U:\n"); U.print(); |
|
|
|
// solve LY=P*I for Y by forward subst |
|
//SquareMatrix<Type, M> Y = P; |
|
|
|
// for all columns of Y |
|
for (size_t c = 0; c < rank; c++) { |
|
// for all rows of L |
|
for (size_t i = 0; i < rank; i++) { |
|
// for all columns of L |
|
for (size_t j = 0; j < i; j++) { |
|
// for all existing y |
|
// subtract the component they |
|
// contribute to the solution |
|
P(i, c) -= L(i, j) * P(j, c); |
|
} |
|
|
|
// divide by the factor |
|
// on current |
|
// term to be solved |
|
// Y(i,c) /= L(i,i); |
|
// but L(i,i) = 1.0 |
|
} |
|
} |
|
|
|
//printf("Y:\n"); Y.print(); |
|
|
|
// solve Ux=y for x by back subst |
|
//SquareMatrix<Type, M> X = Y; |
|
|
|
// for all columns of X |
|
for (size_t c = 0; c < rank; c++) { |
|
// for all rows of U |
|
for (size_t k = 0; k < rank; k++) { |
|
// have to go in reverse order |
|
size_t i = rank - 1 - k; |
|
|
|
// for all columns of U |
|
for (size_t j = i + 1; j < rank; j++) { |
|
// for all existing x |
|
// subtract the component they |
|
// contribute to the solution |
|
P(i, c) -= U(i, j) * P(j, c); |
|
} |
|
|
|
// divide by the factor |
|
// on current |
|
// term to be solved |
|
// |
|
// we know that U(i, i) != 0 from above |
|
P(i, c) /= U(i, i); |
|
} |
|
} |
|
|
|
//check sanity of results |
|
for (size_t i = 0; i < rank; i++) { |
|
for (size_t j = 0; j < rank; j++) { |
|
if (!is_finite(P(i,j))) { |
|
return false; |
|
} |
|
} |
|
} |
|
//printf("X:\n"); X.print(); |
|
inv = P; |
|
return true; |
|
} |
|
|
|
template<typename Type> |
|
bool inv(const SquareMatrix<Type, 2> & A, SquareMatrix<Type, 2> & inv) |
|
{ |
|
Type det = A(0, 0) * A(1, 1) - A(1, 0) * A(0, 1); |
|
|
|
if(fabs(static_cast<float>(det)) < FLT_EPSILON || !is_finite(det)) { |
|
return false; |
|
} |
|
|
|
inv(0, 0) = A(1, 1); |
|
inv(1, 0) = -A(1, 0); |
|
inv(0, 1) = -A(0, 1); |
|
inv(1, 1) = A(0, 0); |
|
inv /= det; |
|
return true; |
|
} |
|
|
|
template<typename Type> |
|
bool inv(const SquareMatrix<Type, 3> & A, SquareMatrix<Type, 3> & inv) |
|
{ |
|
Type det = A(0, 0) * (A(1, 1) * A(2, 2) - A(2, 1) * A(1, 2)) - |
|
A(0, 1) * (A(1, 0) * A(2, 2) - A(1, 2) * A(2, 0)) + |
|
A(0, 2) * (A(1, 0) * A(2, 1) - A(1, 1) * A(2, 0)); |
|
|
|
if(fabs(static_cast<float>(det)) < FLT_EPSILON || !is_finite(det)) { |
|
return false; |
|
} |
|
|
|
inv(0, 0) = A(1, 1) * A(2, 2) - A(2, 1) * A(1, 2); |
|
inv(0, 1) = A(0, 2) * A(2, 1) - A(0, 1) * A(2, 2); |
|
inv(0, 2) = A(0, 1) * A(1, 2) - A(0, 2) * A(1, 1); |
|
inv(1, 0) = A(1, 2) * A(2, 0) - A(1, 0) * A(2, 2); |
|
inv(1, 1) = A(0, 0) * A(2, 2) - A(0, 2) * A(2, 0); |
|
inv(1, 2) = A(1, 0) * A(0, 2) - A(0, 0) * A(1, 2); |
|
inv(2, 0) = A(1, 0) * A(2, 1) - A(2, 0) * A(1, 1); |
|
inv(2, 1) = A(2, 0) * A(0, 1) - A(0, 0) * A(2, 1); |
|
inv(2, 2) = A(0, 0) * A(1, 1) - A(1, 0) * A(0, 1); |
|
inv /= det; |
|
return true; |
|
} |
|
|
|
/** |
|
* inverse based on LU factorization with partial pivotting |
|
*/ |
|
template<typename Type, size_t M> |
|
SquareMatrix<Type, M> inv(const SquareMatrix<Type, M> & A) |
|
{ |
|
SquareMatrix<Type, M> i; |
|
if (inv(A, i)) { |
|
return i; |
|
} else { |
|
i.setZero(); |
|
return i; |
|
} |
|
} |
|
|
|
/** |
|
* cholesky decomposition |
|
* |
|
* Note: A must be positive definite |
|
*/ |
|
template<typename Type, size_t M> |
|
SquareMatrix <Type, M> cholesky(const SquareMatrix<Type, M> & A) |
|
{ |
|
SquareMatrix<Type, M> L; |
|
for (size_t j = 0; j < M; j++) { |
|
for (size_t i = j; i < M; i++) { |
|
if (i==j) { |
|
float sum = 0; |
|
for (size_t k = 0; k < j; k++) { |
|
sum += L(j, k)*L(j, k); |
|
} |
|
Type res = A(j, j) - sum; |
|
if (res <= 0) { |
|
L(j, j) = 0; |
|
} else { |
|
L(j, j) = sqrt(res); |
|
} |
|
} else { |
|
float sum = 0; |
|
for (size_t k = 0; k < j; k++) { |
|
sum += L(i, k)*L(j, k); |
|
} |
|
if (L(j, j) <= 0) { |
|
L(i, j) = 0; |
|
} else { |
|
L(i, j) = (A(i, j) - sum)/L(j, j); |
|
} |
|
} |
|
} |
|
} |
|
return L; |
|
} |
|
|
|
/** |
|
* cholesky inverse |
|
* |
|
* TODO: Check if gaussian elimination jumps straight to back-substitution |
|
* for L or we need to do it manually. Will impact speed otherwise. |
|
*/ |
|
template<typename Type, size_t M> |
|
SquareMatrix <Type, M> choleskyInv(const SquareMatrix<Type, M> & A) |
|
{ |
|
SquareMatrix<Type, M> L_inv = inv(cholesky(A)); |
|
return L_inv.T()*L_inv; |
|
} |
|
|
|
using Matrix3f = SquareMatrix<float, 3>; |
|
using Matrix3d = SquareMatrix<double, 3>; |
|
|
|
} // namespace matrix |
|
|
|
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
|
|
|