1 /* 2 * This file is part of ELKI: 3 * Environment for Developing KDD-Applications Supported by Index-Structures 4 * 5 * Copyright (C) 2018 6 * ELKI Development Team 7 * 8 * This program is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU Affero General Public License as published by 10 * the Free Software Foundation, either version 3 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU Affero General Public License for more details. 17 * 18 * You should have received a copy of the GNU Affero General Public License 19 * along with this program. If not, see <http://www.gnu.org/licenses/>. 20 */ 21 package de.lmu.ifi.dbs.elki.math.linearalgebra; 22 23 import static de.lmu.ifi.dbs.elki.math.linearalgebra.VMath.*; 24 25 import java.util.Arrays; 26 27 import net.jafama.FastMath; 28 29 /** 30 * Cholesky Decomposition. 31 * 32 * For a symmetric, positive definite matrix A, the Cholesky decomposition is an 33 * lower triangular matrix L so that A = L*L^T. 34 * 35 * If the matrix is not symmetric or positive definite, the constructor returns 36 * a partial decomposition and sets an internal flag that may be queried by the 37 * isSPD() method. 38 * 39 * @author Arthur Zimek 40 * @since 0.1 41 * 42 * @assoc - transforms - Matrix 43 */ 44 public class CholeskyDecomposition { 45 /** 46 * Array for internal storage of decomposition. 47 */ 48 private double[][] L; 49 50 /** 51 * Symmetric and positive definite flag. 52 */ 53 private boolean isspd; 54 55 /** 56 * Cholesky algorithm for symmetric and positive definite matrix. 57 * 58 * @param A Square, symmetric matrix. 59 */ CholeskyDecomposition(double[][] A)60 public CholeskyDecomposition(double[][] A) { 61 final int n = A.length; 62 L = new double[n][n]; 63 isspd = A[0].length == n; 64 { // First iteration 65 double d = A[0][0]; 66 isspd &= d > 0.0; 67 L[0][0] = d > 0 ? FastMath.sqrt(d) : 0; 68 Arrays.fill(L[0], 1, n, 0.0); 69 } 70 // Main loop. 71 for(int j = 1; j < n; j++) { 72 final double[] Lj = L[j], Aj = A[j]; 73 double d = 0.0; 74 for(int k = 0; k < j; k++) { 75 final double[] Lk = L[k]; 76 double s = 0.0; 77 for(int i = 0; i < k; i++) { 78 s += Lk[i] * Lj[i]; 79 } 80 Lj[k] = s = (Aj[k] - s) / Lk[k]; 81 d += s * s; 82 isspd &= (A[k][j] == Aj[k]); 83 } 84 d = Aj[j] - d; 85 isspd &= d > 0.0; 86 Lj[j] = d > 0 ? FastMath.sqrt(d) : 0; 87 Arrays.fill(Lj, j + 1, n, 0.0); 88 } 89 } 90 91 /** 92 * Is the matrix symmetric and positive definite? 93 * 94 * @return true if A is symmetric and positive definite. 95 */ isSPD()96 public boolean isSPD() { 97 return isspd; 98 } 99 100 /** 101 * Return triangular factor. 102 * 103 * @return L 104 */ getL()105 public double[][] getL() { 106 return L; 107 } 108 109 /** 110 * Solve A*X = B 111 * 112 * @param B A Matrix with as many rows as A and any number of columns. 113 * @return X so that L*L^T*X = B 114 * @exception IllegalArgumentException Matrix row dimensions must agree. 115 * @exception RuntimeException Matrix is not symmetric positive definite. 116 */ solve(double[][] B)117 public double[][] solve(double[][] B) { 118 if(B.length != L.length) { 119 throw new IllegalArgumentException(ERR_MATRIX_DIMENSIONS); 120 } 121 if(!isspd) { 122 throw new ArithmeticException(ERR_MATRIX_NOT_SPD); 123 } 124 // Work on a copy! 125 return solveLtransposed(solveL(copy(B))); 126 } 127 128 /** 129 * Solve L*Y = B 130 * 131 * @param X Copy of B. 132 * @return X 133 */ solveL(double[][] X)134 private double[][] solveL(double[][] X) { 135 final int n = L.length, m = X[0].length; 136 { // First iteration, simplified. 137 final double[] X0 = X[0], L0 = L[0]; 138 for(int j = 0; j < m; j++) { 139 X0[j] /= L0[0]; 140 } 141 } 142 for(int k = 1; k < n; k++) { 143 final double[] Xk = X[k], Lk = L[k]; 144 final double iLkk = 1. / Lk[k]; 145 for(int j = 0; j < m; j++) { 146 double Xkj = Xk[j]; 147 for(int i = 0; i < k; i++) { 148 Xkj -= X[i][j] * Lk[i]; 149 } 150 Xk[j] = Xkj * iLkk; 151 } 152 } 153 return X; 154 } 155 156 /** 157 * Solve L^T*X = Y 158 * 159 * @param X Solution of L*Y=B 160 * @return X 161 */ solveLtransposed(double[][] X)162 private double[][] solveLtransposed(double[][] X) { 163 for(int k = L.length - 1; k >= 0; k--) { 164 final double[] Lk = L[k], Xk = X[k]; 165 timesEquals(Xk, 1. / Lk[k]); 166 for(int i = 0; i < k; i++) { 167 minusTimesEquals(X[i], Xk, Lk[i]); 168 } 169 } 170 return X; 171 } 172 173 /** 174 * Solve A*X = b 175 * 176 * @param b A column vector with as many rows as A. 177 * @return X so that L*L^T*X = b 178 * @exception IllegalArgumentException Matrix row dimensions must agree. 179 * @exception RuntimeException Matrix is not symmetric positive definite. 180 */ solve(double[] b)181 public double[] solve(double[] b) { 182 if(b.length != L.length) { 183 throw new IllegalArgumentException(ERR_MATRIX_DIMENSIONS); 184 } 185 if(!isspd) { 186 throw new ArithmeticException(ERR_MATRIX_NOT_SPD); 187 } 188 // Work on a copy! 189 return solveLtransposed(solveLInplace(copy(b))); 190 } 191 192 /** 193 * Solve L*X = b, <b>modifying X</b>. 194 * 195 * @param X Copy of b. 196 * @return X 197 */ solveLInplace(double[] X)198 public double[] solveLInplace(double[] X) { 199 final int n = L.length; 200 X[0] /= L[0][0]; // First iteration, simplified. 201 for(int k = 1; k < n; k++) { 202 final double[] Lk = L[k]; 203 for(int i = 0; i < k; i++) { 204 X[k] -= X[i] * Lk[i]; 205 } 206 X[k] /= Lk[k]; 207 } 208 return X; 209 } 210 211 /** 212 * Solve L^T*X = Y 213 * 214 * @param X Solution of L*Y=b 215 * @return X 216 */ solveLtransposed(double[] X)217 private double[] solveLtransposed(double[] X) { 218 for(int k = L.length - 1; k >= 0; k--) { 219 final double[] Lk = L[k]; 220 X[k] /= Lk[k]; 221 for(int i = 0; i < k; i++) { 222 X[i] -= X[k] * Lk[i]; 223 } 224 } 225 return X; 226 } 227 } 228