# accord-net/framework

Switch branches/tags
Nothing to show
Fetching contributors…
Cannot retrieve contributors at this time
753 lines (638 sloc) 26.8 KB
 // Accord Math Library // The Accord.NET Framework // http://accord-framework.net // // Copyright © César Souza, 2009-2017 // cesarsouza at gmail.com // // Original work copyright © Lutz Roeder, 2000 // Adapted from Mapack for .NET, September 2000 // Adapted from Mapack for COM and Jama routines // http://www.aisto.com/roeder/dotnet // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // namespace Accord.Math.Decompositions { using System; using Accord.Compat; using System.Threading.Tasks; /// /// Cholesky Decomposition of a symmetric, positive definite matrix. /// /// /// /// /// For a symmetric, positive definite matrix A, the Cholesky decomposition is a /// lower triangular matrix L so that A = L * L'. /// If the matrix is not positive definite, the constructor returns a partial /// decomposition and sets two internal variables that can be queried using the /// and properties. /// /// Any square matrix A with non-zero pivots can be written as the product of a /// lower triangular matrix L and an upper triangular matrix U; this is called /// the LU decomposition. However, if A is symmetric and positive definite, we /// can choose the factors such that U is the transpose of L, and this is called /// the Cholesky decomposition. Both the LU and the Cholesky decomposition are /// used to solve systems of linear equations. /// /// When it is applicable, the Cholesky decomposition is twice as efficient /// as the LU decomposition. /// /// [Serializable] public sealed class CholeskyDecompositionF : ICloneable, ISolverMatrixDecomposition { private Single[,] L; private Single[] D; private int n; private bool positiveDefinite; private bool destroyed; private bool robust; private bool undefined; // cache for lazy evaluation private Single[,] leftTriangularFactor; private Single[,] diagonalMatrix; private Single? determinant; private double? lndeterminant; private bool? nonsingular; /// /// Constructs a new Cholesky Decomposition. /// /// /// /// The symmetric matrix, given in upper triangular form, to be decomposed. /// /// True to perform a square-root free LDLt decomposition, false otherwise. /// /// True to perform the decomposition in place, storing the factorization in the /// lower triangular part of the given matrix. /// /// How to interpret the matrix given to be decomposed. Using this parameter, a lower or /// upper-triangular matrix can be interpreted as a symmetric matrix by assuming both lower /// and upper parts contain the same elements. Use this parameter in conjunction with inPlace /// to save memory by storing the original matrix and its decomposition at the same memory /// location (lower part will contain the decomposition's L matrix, upper part will contains /// the original matrix). /// public CholeskyDecompositionF(Single[,] value, bool robust = false, bool inPlace = false, MatrixType valueType = MatrixType.UpperTriangular) { if (value.Rows() != value.Columns()) throw new DimensionMismatchException("value", "Matrix is not square."); if (!inPlace) value = value.Copy(); this.n = value.Rows(); this.L = value.ToUpperTriangular(valueType, result: value); this.robust = robust; if (robust) { LDLt(); // Compute square-root free decomposition } else { LLt(); // Compute standard Cholesky decomposition } } /// /// Gets whether the decomposed matrix was positive definite. /// /// public bool IsPositiveDefinite { get { return this.positiveDefinite; } } /// /// Gets a value indicating whether the LDLt factorization /// has been computed successfully or if it is undefined. /// /// /// /// true if the factorization is not defined; otherwise, false. /// /// public bool IsUndefined { get { return this.undefined; } } /// /// Gets the left (lower) triangular factor /// L so that A = L * D * L'. /// /// public Single[,] LeftTriangularFactor { get { if (leftTriangularFactor == null) { if (destroyed) throw new InvalidOperationException("The decomposition has been destroyed."); if (undefined) throw new InvalidOperationException("The decomposition is undefined (zero in diagonal)."); leftTriangularFactor = L.GetLowerTriangle(); } return leftTriangularFactor; } } /// /// Gets the block diagonal matrix of diagonal elements in a LDLt decomposition. /// /// public Single[,] DiagonalMatrix { get { if (diagonalMatrix == null) { if (destroyed) throw new InvalidOperationException("The decomposition has been destroyed."); diagonalMatrix = Matrix.Diagonal(D); } return diagonalMatrix; } } /// /// Gets the one-dimensional array of diagonal elements in a LDLt decomposition. /// /// public Single[] Diagonal { get { return D; } } /// /// Gets the determinant of the decomposed matrix. /// /// public Single Determinant { get { if (!determinant.HasValue) { if (destroyed) throw new InvalidOperationException("The decomposition has been destroyed."); if (undefined) throw new InvalidOperationException("The decomposition is undefined (zero in diagonal)."); Single detL = 1, detD = 1; for (int i = 0; i < n; i++) detL *= L[i, i]; if (D != null) { for (int i = 0; i < n; i++) detD *= D[i]; } determinant = detL * detL * detD; } return determinant.Value; } } /// /// If the matrix is positive-definite, gets the /// log-determinant of the decomposed matrix. /// /// public double LogDeterminant { get { if (!lndeterminant.HasValue) { if (destroyed) throw new InvalidOperationException("The decomposition has been destroyed."); if (undefined) throw new InvalidOperationException("The decomposition is undefined (zero in diagonal)."); double detL = 0, detD = 0; for (int i = 0; i < n; i++) detL += Math.Log((double)L[i, i]); if (D != null) { for (int i = 0; i < D.Length; i++) detD += Math.Log((double)D[i]); } lndeterminant = detL + detL + detD; } return lndeterminant.Value; } } /// /// Gets a value indicating whether the decomposed /// matrix is non-singular (i.e. invertible). /// /// public bool Nonsingular { get { if (!nonsingular.HasValue) { if (destroyed) throw new InvalidOperationException("The decomposition has been destroyed."); if (undefined) throw new InvalidOperationException("The decomposition is undefined (zero in diagonal)."); bool nonSingular = true; for (int i = 0; i < n && nonSingular; i++) if (L[i, i] == 0 || D[i] == 0) nonSingular = false; nonsingular = nonSingular; } return nonsingular.Value; } } private unsafe void LLt() { D = Vector.Ones(n); this.positiveDefinite = true; for (int j = 0; j < n; j++) { Single s = 0; for (int k = 0; k < j; k++) { Single t = L[k, j]; for (int i = 0; i < k; i++) t -= L[j, i] * L[k, i]; t = t / L[k, k]; L[j, k] = t; s += t * t; } s = L[j, j] - s; // Use a tolerance for positive-definiteness this.positiveDefinite &= (s > (Single)1e-14 * Math.Abs(L[j, j])); L[j, j] = (Single)Math.Sqrt((double)s); } } private unsafe void LDLt() { D = new Single[n]; this.positiveDefinite = true; Single[] v = new Single[n]; for (int i = 0; i < n; i++) { for (int j = 0; j < i; j++) v[j] = L[i, j] * D[j]; Single d = 0; for (int k = 0; k < i; k++) d += L[i, k] * v[k]; d = D[i] = v[i] = L[i, i] - d; // Use a tolerance for positive-definiteness this.positiveDefinite &= (v[i] > (Single)1e-14 * Math.Abs(L[i, i])); // If one of the diagonal elements is zero, the // decomposition (without pivoting) is undefined. if (v[i] == 0) { undefined = true; return; } Parallel.For(i + 1, n, k => { Single s = 0; for (int j = 0; j < i; j++) s += L[k, j] * v[j]; L[k, i] = (L[i, k] - s) / d; }); } for (int i = 0; i < n; i++) L[i, i] = 1; } /// /// Solves a set of equation systems of type A * X = B. /// /// /// Right hand side matrix with as many rows as A and any number of columns. /// Matrix X so that L * L' * X = B. /// Matrix dimensions do not match. /// Matrix is not symmetric. /// Matrix is not positive-definite. /// public Single[,] Solve(Single[,] value) { return Solve(value, false); } /// /// Solves a set of equation systems of type A * X = B. /// /// /// Right hand side matrix with as many rows as A and any number of columns. /// Matrix X so that L * L' * X = B. /// Matrix dimensions do not match. /// Matrix is not symmetric. /// Matrix is not positive-definite. /// True to compute the solving in place, false otherwise. /// public Single[,] Solve(Single[,] value, bool inPlace) { if (value == null) throw new ArgumentNullException("value"); if (value.Rows() != n) throw new ArgumentException("Argument matrix should have the same number of rows as the decomposed matrix.", "value"); if (!robust && !positiveDefinite) throw new NonPositiveDefiniteMatrixException("Decomposed matrix is not positive definite."); if (undefined) throw new InvalidOperationException("The decomposition is undefined (zero in diagonal)."); if (destroyed) throw new InvalidOperationException("The decomposition has been destroyed."); // int count = value.Columns(); var B = inPlace ? value : value.MemberwiseClone(); int m = B.Columns(); // Solve L*Y = B; for (int k = 0; k < n; k++) { for (int j = 0; j < m; j++) { for (int i = 0; i < k; i++) B[k, j] -= B[i, j] * L[k, i]; B[k, j] /= L[k, k]; } } if (robust) { for (int k = 0; k < D.Length; k++) for (int j = 0; j < m; j++) B[k, j] /= D[k]; } // Solve L'*X = Y; for (int k = n - 1; k >= 0; k--) { for (int j = 0; j < m; j++) { for (int i = k + 1; i < n; i++) B[k, j] -= B[i, j] * L[i, k]; B[k, j] /= L[k, k]; } } return B; } /// /// Solves a set of equation systems of type A * x = b. /// /// /// Right hand side column vector with as many rows as A. /// Vector x so that L * L' * x = b. /// Matrix dimensions do not match. /// Matrix is not symmetric. /// Matrix is not positive-definite. /// public Single[] Solve(Single[] value) { return Solve(value, false); } /// /// Solves a set of equation systems of type A * x = b. /// /// /// Right hand side column vector with as many rows as A. /// Vector x so that L * L' * x = b. /// Matrix dimensions do not match. /// Matrix is not symmetric. /// Matrix is not positive-definite. /// True to compute the solving in place, false otherwise. /// public Single[] Solve(Single[] value, bool inPlace) { if (value == null) throw new ArgumentNullException("value"); if (value.Length != n) throw new ArgumentException("Argument vector should have the same length as rows in the decomposed matrix.", "value"); if (!robust && !positiveDefinite) throw new NonPositiveDefiniteMatrixException("Decomposed matrix is not positive definite."); if (undefined) throw new InvalidOperationException("The decomposition is undefined (zero in diagonal)."); if (destroyed) throw new InvalidOperationException("The decomposition has been destroyed."); var B = inPlace ? value : value.Copy(); // Solve L*Y = B; for (int k = 0; k < n; k++) { for (int i = 0; i < k; i++) B[k] -= B[i] * L[k, i]; B[k] /= L[k, k]; } if (robust) { for (int k = 0; k < D.Length; k++) B[k] /= D[k]; } // Solve L'*X = Y; for (int k = n - 1; k >= 0; k--) { for (int i = k + 1; i < n; i++) B[k] -= B[i] * L[i, k]; B[k] /= L[k, k]; } return B; } /// /// Solves a set of equation systems of type A * X = I. /// /// public Single[,] Inverse() { return Solve(Matrix.Identity(n)); } /// /// Computes the diagonal of the inverse of the decomposed matrix. /// /// public Single[] InverseDiagonal(bool destroy = false) { return InverseDiagonal(new Single[n], destroy); } /// /// Computes the diagonal of the inverse of the decomposed matrix. /// /// /// True to conserve memory by reusing the /// same space used to hold the decomposition, thus destroying /// it in the process. Pass false otherwise. /// The array to hold the result of the /// computation. Should be of same length as the the diagonal /// of the original matrix. /// public Single[] InverseDiagonal(Single[] result, bool destroy = false) { if (!robust && !positiveDefinite) throw new NonPositiveDefiniteMatrixException("Matrix is not positive definite."); if (undefined) throw new InvalidOperationException("The decomposition is undefined (zero in diagonal)."); if (destroyed) throw new InvalidOperationException("The decomposition has been destroyed."); Single[,] S; if (destroy) { S = L; destroyed = true; } else { S = Matrix.Zeros(n, n); } // References: // http://books.google.com/books?id=myzIPBwyBbcC&pg=PA119 // Compute the inverse S of the lower triangular matrix L // and store in place of the upper triangular part of S. for (int j = n - 1; j >= 0; j--) { S[j, j] = 1 / L[j, j]; for (int i = j - 1; i >= 0; i--) { Single sum = 0; for (int k = i + 1; k <= j; k++) sum += L[k, i] * S[k, j]; S[i, j] = -sum / L[i, i]; } } // Compute the 2-norm squared of the rows // of the upper (right) triangular matrix S. if (robust) { for (int i = 0; i < n; i++) { Single sum = 0; for (int j = i; j < n; j++) sum += S[i, j] * S[i, j] / D[j]; result[i] = sum; } } else { for (int i = 0; i < n; i++) { Single sum = 0; for (int j = i; j < n; j++) sum += S[i, j] * S[i, j]; result[i] = sum; } } return result; } /// /// Computes the trace of the inverse of the decomposed matrix. /// /// /// True to conserve memory by reusing the /// same space used to hold the decomposition, thus destroying /// it in the process. Pass false otherwise. /// public Single InverseTrace(bool destroy = false) { if (!robust && !positiveDefinite) throw new NonPositiveDefiniteMatrixException("Matrix is not positive definite."); if (undefined) throw new InvalidOperationException("The decomposition is undefined (zero in diagonal)."); if (destroyed) throw new InvalidOperationException("The decomposition has been destroyed."); Single[,] S; if (destroy) { S = L; destroyed = true; } else { S = Matrix.Zeros(n, n); } // References: // http://books.google.com/books?id=myzIPBwyBbcC&pg=PA119 // Compute the inverse S of the lower triangular matrix L // and store in place of the upper triangular part of S. for (int j = n - 1; j >= 0; j--) { S[j, j] = 1 / L[j, j]; for (int i = j - 1; i >= 0; i--) { Single sum = 0; for (int k = i + 1; k <= j; k++) sum += L[k, i] * S[k, j]; S[i, j] = -sum / L[i, i]; } } // Compute the 2-norm squared of the rows // of the upper (right) triangular matrix S. Single trace = 0; if (robust) { for (int i = 0; i < n; i++) for (int j = i; j < n; j++) trace += S[i, j] * S[i, j] / D[j]; } else { for (int i = 0; i < n; i++) for (int j = i; j < n; j++) trace += S[i, j] * S[i, j]; } return trace; } /// /// Reverses the decomposition, reconstructing the original matrix X. /// /// public Single[,] Reverse() { if (destroyed) throw new InvalidOperationException("The decomposition has been destroyed."); if (undefined) throw new InvalidOperationException("The decomposition is undefined (zero in diagonal)."); if (robust) return LeftTriangularFactor.Dot(DiagonalMatrix).DotWithTransposed(LeftTriangularFactor); return LeftTriangularFactor.DotWithTransposed(LeftTriangularFactor); } /// /// Computes (Xt * X)^1 (the inverse of the covariance matrix). This /// matrix can be used to determine standard errors for the coefficients when /// solving a linear set of equations through any of the /// methods. /// /// public Single[,] GetInformationMatrix() { var X = Reverse(); return X.TransposeAndDot(X).Inverse(); } /// /// Creates a new Cholesky decomposition directly from /// an already computed left triangular matrix L. /// /// The left triangular matrix from a Cholesky decomposition. /// public static CholeskyDecompositionF FromLeftTriangularMatrix(Single[,] leftTriangular) { var chol = new CholeskyDecompositionF(); chol.n = leftTriangular.Rows(); chol.L = leftTriangular; chol.positiveDefinite = true; chol.robust = false; chol.D = Vector.Ones(chol.n); return chol; } #region ICloneable Members private CholeskyDecompositionF() { } /// /// Creates a new object that is a copy of the current instance. /// /// /// A new object that is a copy of this instance. /// /// public object Clone() { var clone = new CholeskyDecompositionF(); clone.L = L.MemberwiseClone(); clone.D = (Single[])D.Clone(); clone.destroyed = destroyed; clone.n = n; clone.undefined = undefined; clone.robust = robust; clone.positiveDefinite = positiveDefinite; return clone; } #endregion } }