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