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 /**
28  * LU Decomposition.
29  * <p>
30  * For an m-by-n matrix A with m &gt;= n, the LU decomposition is an m-by-n unit
31  * lower triangular matrix L, an n-by-n upper triangular matrix U, and a
32  * permutation vector piv of length m so that A(piv,:) = L*U. If m &lt; n, then
33  * L is m-by-m and U is m-by-n.
34  * <p>
35  * The LU decompostion with pivoting always exists, even if the matrix is
36  * singular, so the constructor will never fail. The primary use of the LU
37  * decomposition is in the solution of square systems of simultaneous linear
38  * equations. This will fail if isNonsingular() returns false.
39  *
40  * @author Arthur Zimek
41  * @since 0.1
42  *
43  * @assoc - transforms - Matrix
44  */
45 public class LUDecomposition implements java.io.Serializable {
46   /**
47    * Serial version
48    */
49   private static final long serialVersionUID = 1L;
50 
51   /**
52    * Array for internal storage of decomposition.
53    *
54    * @serial internal array storage.
55    */
56   private double[][] LU;
57 
58   /**
59    * Row and column dimensions, and pivot sign.
60    *
61    * @serial column dimension.
62    * @serial row dimension.
63    * @serial pivot sign.
64    */
65   private int m, n, pivsign;
66 
67   /**
68    * Internal storage of pivot vector.
69    *
70    * @serial pivot vector.
71    */
72   private int[] piv;
73 
74   /**
75    * LU Decomposition
76    *
77    * @param LU Rectangular matrix
78    */
LUDecomposition(double[][] LU)79   public LUDecomposition(double[][] LU) {
80     this(LU, LU.length, LU[0].length);
81   }
82 
83   /**
84    * LU Decomposition
85    *
86    * @param LU Rectangular matrix
87    * @param m row dimensionality
88    * @param n column dimensionality
89    */
LUDecomposition(double[][] LU, int m, int n)90   public LUDecomposition(double[][] LU, int m, int n) {
91     this.LU = LU = copy(LU);
92     this.m = m;
93     this.n = n;
94     // Use a "left-looking", dot-product, Crout/Doolittle algorithm.
95     piv = new int[m];
96     for(int i = 0; i < m; i++) {
97       piv[i] = i;
98     }
99     pivsign = 1;
100     // Copy of column
101     double[] LUcolj = new double[m];
102 
103     // Outer loop.
104     for(int j = 0; j < n; j++) {
105       // Make a copy of the j-th column to localize references.
106       for(int i = 0; i < m; i++) {
107         LUcolj[i] = LU[i][j];
108       }
109 
110       // Apply previous transformations.
111       for(int i = 0; i < m; i++) {
112         // Most of the time is spent in the following dot product.
113         int kmax = Math.min(i, j);
114         double s = 0.0;
115         double[] LUrowi = LU[i];
116         for(int k = 0; k < kmax; k++) {
117           s += LUrowi[k] * LUcolj[k];
118         }
119         LUrowi[j] = LUcolj[i] -= s;
120       }
121 
122       // Find pivot and exchange if necessary.
123       int p = j;
124       for(int i = j + 1; i < m; i++) {
125         if(Math.abs(LUcolj[i]) > Math.abs(LUcolj[p])) {
126           p = i;
127         }
128       }
129       if(p != j) {
130         double[] tmp = LU[j];
131         LU[j] = LU[p];
132         LU[p] = tmp;
133         int k = piv[p];
134         piv[p] = piv[j];
135         piv[j] = k;
136         pivsign = -pivsign;
137       }
138 
139       // Compute multipliers.
140       final double LUjj = LU[j][j];
141       if(j < m && LUjj != 0.0) {
142         for(int i = j + 1; i < m; i++) {
143           LU[i][j] /= LUjj;
144         }
145       }
146     }
147   }
148 
149   /**
150    * Is the matrix nonsingular?
151    *
152    * @return true if U, and hence A, is nonsingular.
153    */
isNonsingular()154   public boolean isNonsingular() {
155     for(int j = 0; j < n; j++) {
156       if(LU[j][j] == 0) {
157         return false;
158       }
159     }
160     return true;
161   }
162 
163   /**
164    * Return lower triangular factor
165    *
166    * @return L
167    */
getL()168   public double[][] getL() {
169     double[][] L = new double[m][n];
170     L[0][0] = 1.;
171     for(int i = 1; i < m; i++) {
172       final double[] Li = L[i];
173       System.arraycopy(LU[i], 0, Li, 0, Math.min(i, n));
174       if (i < n) {
175         Li[i] = 1.;
176       }
177     }
178     return L;
179   }
180 
181   /**
182    * Return upper triangular factor
183    *
184    * @return U
185    */
getU()186   public double[][] getU() {
187     double[][] U = new double[n][n];
188     for(int i = 0; i < n; i++) {
189       System.arraycopy(LU[i], i, U[i], i, n - i);
190     }
191     return U;
192   }
193 
194   /**
195    * Return pivot permutation vector
196    *
197    * @return piv
198    */
getPivot()199   public int[] getPivot() {
200     return piv.clone();
201   }
202 
203   /**
204    * Determinant
205    *
206    * @return det(A)
207    * @exception IllegalArgumentException Matrix must be square
208    */
det()209   public double det() {
210     if(m != n) {
211       throw new IllegalArgumentException(ERR_MATRIX_NONSQUARE);
212     }
213     double d = pivsign;
214     for(int j = 0; j < n; j++) {
215       d *= LU[j][j];
216     }
217     return d;
218   }
219 
220   /**
221    * Solve A*X = B
222    *
223    * @param B A Matrix with as many rows as A and any number of columns.
224    * @return X so that L*U*X = B(piv,:)
225    * @throws IllegalArgumentException Matrix row dimensions must agree.
226    * @throws ArithmeticException Matrix is singular.
227    */
solve(double[][] B)228   public double[][] solve(double[][] B) {
229     if(B.length != m) {
230       throw new IllegalArgumentException(ERR_MATRIX_DIMENSIONS);
231     }
232     double[][] sol = solveInplace(getMatrix(B, piv, 0, B[0].length));
233     return n < sol.length ? Arrays.copyOf(sol, n) : sol;
234   }
235 
236   /**
237    * Solve A*X = B
238    *
239    * @param B A Matrix with as many rows as A and any number of columns.
240    * @return B
241    * @throws IllegalArgumentException Matrix row dimensions must agree.
242    * @throws ArithmeticException Matrix is singular.
243    */
solveInplace(double[][] B)244   private double[][] solveInplace(double[][] B) {
245     int mx = B.length;
246     if(mx != m) {
247       throw new IllegalArgumentException(ERR_MATRIX_DIMENSIONS);
248     }
249     if(!this.isNonsingular()) {
250       throw new ArithmeticException(ERR_SINGULAR);
251     }
252     // Solve L*Y = B(piv,:)
253     for(int k = 0; k < n; k++) {
254       final double[] Bk = B[k];
255       for(int i = k + 1; i < n; i++) {
256         minusTimesEquals(B[i], Bk, LU[i][k]);
257       }
258     }
259     // Solve U*X = Y;
260     for(int k = n - 1; k >= 0; k--) {
261       final double[] Bk = B[k];
262       timesEquals(Bk, 1. / LU[k][k]);
263       for(int i = 0; i < k; i++) {
264         minusTimesEquals(B[i], Bk, LU[i][k]);
265       }
266     }
267     return B;
268   }
269 
270   /**
271    * Solve A*X = b
272    *
273    * @param b A column vector with as many rows as A
274    * @return X so that L*U*X = b(piv)
275    * @throws IllegalArgumentException Matrix row dimensions must agree.
276    * @throws ArithmeticException Matrix is singular.
277    */
solve(double[] b)278   public double[] solve(double[] b) {
279     if(b.length != m) {
280       throw new IllegalArgumentException(ERR_MATRIX_DIMENSIONS);
281     }
282     if(!this.isNonsingular()) {
283       throw new ArithmeticException(ERR_SINGULAR);
284     }
285     double[] bc = new double[piv.length];
286     for(int i = 0; i < piv.length; i++) {
287       bc[i] = b[piv[i]];
288     }
289     solveInplace(bc);
290     return n < bc.length ? Arrays.copyOf(bc, n) : bc;
291   }
292 
293   /**
294    * Solve A*X = b
295    *
296    * @param b A vector
297    * @return b
298    * @throws IllegalArgumentException Matrix row dimensions must agree.
299    * @throws ArithmeticException Matrix is singular.
300    */
solveInplace(double[] b)301   public double[] solveInplace(double[] b) {
302     if(b.length != m) {
303       throw new IllegalArgumentException(ERR_MATRIX_DIMENSIONS);
304     }
305     if(!this.isNonsingular()) {
306       throw new ArithmeticException(ERR_SINGULAR);
307     }
308     // Solve L*Y = B(piv,:)
309     for(int k = 0; k < n; k++) {
310       for(int i = k + 1; i < n; i++) {
311         b[i] -= b[k] * LU[i][k];
312       }
313     }
314     // Solve U*X = Y;
315     for(int k = n - 1; k >= 0; k--) {
316       b[k] /= LU[k][k];
317       for(int i = 0; i < k; i++) {
318         b[i] -= b[k] * LU[i][k];
319       }
320     }
321     return b;
322   }
323 
324   /**
325    * Find the inverse matrix.
326    *
327    * @return Inverse matrix
328    * @throws ArithmeticException Matrix is rank deficient.
329    */
inverse()330   public double[][] inverse() {
331     // Build permuted identity matrix efficiently:
332     double[][] b = new double[piv.length][m];
333     for(int i = 0; i < piv.length; i++) {
334       b[piv[i]][i] = 1.;
335     }
336     return solveInplace(b);
337   }
338 }
339