View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  
23  package org.hipparchus.linear;
24  
25  import org.hipparchus.exception.LocalizedCoreFormats;
26  import org.hipparchus.exception.MathIllegalArgumentException;
27  import org.hipparchus.fraction.Fraction;
28  import org.junit.jupiter.api.Test;
29  
30  import static org.junit.jupiter.api.Assertions.assertEquals;
31  import static org.junit.jupiter.api.Assertions.assertFalse;
32  import static org.junit.jupiter.api.Assertions.assertNull;
33  import static org.junit.jupiter.api.Assertions.assertTrue;
34  import static org.junit.jupiter.api.Assertions.fail;
35  
36  class LUDecompositionTest {
37      private double[][] testData = {
38              { 1.0, 2.0, 3.0},
39              { 2.0, 5.0, 3.0},
40              { 1.0, 0.0, 8.0}
41      };
42      private double[][] testDataMinus = {
43              { -1.0, -2.0, -3.0},
44              { -2.0, -5.0, -3.0},
45              { -1.0,  0.0, -8.0}
46      };
47      private double[][] luData = {
48              { 2.0, 3.0, 3.0 },
49              { 0.0, 5.0, 7.0 },
50              { 6.0, 9.0, 8.0 }
51      };
52  
53      // singular matrices
54      private double[][] singular = {
55              { 2.0, 3.0 },
56              { 2.0, 3.0 }
57      };
58      private double[][] bigSingular = {
59              { 1.0, 2.0,   3.0,    4.0 },
60              { 2.0, 5.0,   3.0,    4.0 },
61              { 7.0, 3.0, 256.0, 1930.0 },
62              { 3.0, 7.0,   6.0,    8.0 }
63      }; // 4th row = 1st + 2nd
64  
65      private static final double entryTolerance = 10e-16;
66  
67      private static final double normTolerance = 10e-14;
68  
69      /** test dimensions */
70      @Test
71      void testDimensions() {
72          RealMatrix matrix = MatrixUtils.createRealMatrix(testData);
73          LUDecomposition LU = new LUDecomposition(matrix);
74          assertEquals(testData.length, LU.getL().getRowDimension());
75          assertEquals(testData.length, LU.getL().getColumnDimension());
76          assertEquals(testData.length, LU.getU().getRowDimension());
77          assertEquals(testData.length, LU.getU().getColumnDimension());
78          assertEquals(testData.length, LU.getP().getRowDimension());
79          assertEquals(testData.length, LU.getP().getColumnDimension());
80  
81      }
82  
83      /** test non-square matrix */
84      @Test
85      void testNonSquare() {
86          try {
87              new LUDecomposition(MatrixUtils.createRealMatrix(new double[3][2]));
88              fail("Expecting MathIllegalArgumentException");
89          } catch (MathIllegalArgumentException ime) {
90              assertEquals(LocalizedCoreFormats.NON_SQUARE_MATRIX, ime.getSpecifier());
91          }
92      }
93  
94      /** test PA = LU */
95      @Test
96      void testPAEqualLU() {
97          RealMatrix matrix = MatrixUtils.createRealMatrix(testData);
98          LUDecomposition lu = new LUDecomposition(matrix);
99          RealMatrix l = lu.getL();
100         RealMatrix u = lu.getU();
101         RealMatrix p = lu.getP();
102         double norm = l.multiply(u).subtract(p.multiply(matrix)).getNorm1();
103         assertEquals(0, norm, normTolerance);
104 
105         matrix = MatrixUtils.createRealMatrix(testDataMinus);
106         lu = new LUDecomposition(matrix);
107         l = lu.getL();
108         u = lu.getU();
109         p = lu.getP();
110         norm = l.multiply(u).subtract(p.multiply(matrix)).getNorm1();
111         assertEquals(0, norm, normTolerance);
112 
113         matrix = MatrixUtils.createRealIdentityMatrix(17);
114         lu = new LUDecomposition(matrix);
115         l = lu.getL();
116         u = lu.getU();
117         p = lu.getP();
118         norm = l.multiply(u).subtract(p.multiply(matrix)).getNorm1();
119         assertEquals(0, norm, normTolerance);
120 
121         matrix = MatrixUtils.createRealMatrix(singular);
122         lu = new LUDecomposition(matrix);
123         assertFalse(lu.getSolver().isNonSingular());
124         assertNull(lu.getL());
125         assertNull(lu.getU());
126         assertNull(lu.getP());
127 
128         matrix = MatrixUtils.createRealMatrix(bigSingular);
129         lu = new LUDecomposition(matrix);
130         assertFalse(lu.getSolver().isNonSingular());
131         assertNull(lu.getL());
132         assertNull(lu.getU());
133         assertNull(lu.getP());
134 
135     }
136 
137     /** test that L is lower triangular with unit diagonal */
138     @Test
139     void testLLowerTriangular() {
140         RealMatrix matrix = MatrixUtils.createRealMatrix(testData);
141         RealMatrix l = new LUDecomposition(matrix).getL();
142         for (int i = 0; i < l.getRowDimension(); i++) {
143             assertEquals(1, l.getEntry(i, i), entryTolerance);
144             for (int j = i + 1; j < l.getColumnDimension(); j++) {
145                 assertEquals(0, l.getEntry(i, j), entryTolerance);
146             }
147         }
148     }
149 
150     /** test that U is upper triangular */
151     @Test
152     void testUUpperTriangular() {
153         RealMatrix matrix = MatrixUtils.createRealMatrix(testData);
154         RealMatrix u = new LUDecomposition(matrix).getU();
155         for (int i = 0; i < u.getRowDimension(); i++) {
156             for (int j = 0; j < i; j++) {
157                 assertEquals(0, u.getEntry(i, j), entryTolerance);
158             }
159         }
160     }
161 
162     /** test that P is a permutation matrix */
163     @Test
164     void testPPermutation() {
165         RealMatrix matrix = MatrixUtils.createRealMatrix(testData);
166         RealMatrix p   = new LUDecomposition(matrix).getP();
167 
168         RealMatrix ppT = p.multiplyTransposed(p);
169         RealMatrix id  = MatrixUtils.createRealIdentityMatrix(p.getRowDimension());
170         assertEquals(0, ppT.subtract(id).getNorm1(), normTolerance);
171 
172         for (int i = 0; i < p.getRowDimension(); i++) {
173             int zeroCount  = 0;
174             int oneCount   = 0;
175             int otherCount = 0;
176             for (int j = 0; j < p.getColumnDimension(); j++) {
177                 final double e = p.getEntry(i, j);
178                 if (e == 0) {
179                     ++zeroCount;
180                 } else if (e == 1) {
181                     ++oneCount;
182                 } else {
183                     ++otherCount;
184                 }
185             }
186             assertEquals(p.getColumnDimension() - 1, zeroCount);
187             assertEquals(1, oneCount);
188             assertEquals(0, otherCount);
189         }
190 
191         for (int j = 0; j < p.getColumnDimension(); j++) {
192             int zeroCount  = 0;
193             int oneCount   = 0;
194             int otherCount = 0;
195             for (int i = 0; i < p.getRowDimension(); i++) {
196                 final double e = p.getEntry(i, j);
197                 if (e == 0) {
198                     ++zeroCount;
199                 } else if (e == 1) {
200                     ++oneCount;
201                 } else {
202                     ++otherCount;
203                 }
204             }
205             assertEquals(p.getRowDimension() - 1, zeroCount);
206             assertEquals(1, oneCount);
207             assertEquals(0, otherCount);
208         }
209 
210     }
211 
212     /** test singular */
213     @Test
214     void testSingular() {
215         final RealMatrix m = MatrixUtils.createRealMatrix(testData);
216         LUDecomposition lu = new LUDecomposition(m);
217         assertTrue(lu.getSolver().isNonSingular());
218         assertEquals(-1.0, lu.getDeterminant(), 1.0e-15);
219         lu = new LUDecomposition(m.getSubMatrix(0, 1, 0, 1));
220         assertTrue(lu.getSolver().isNonSingular());
221         assertEquals(+1.0, lu.getDeterminant(), 1.0e-15);
222         lu = new LUDecomposition(MatrixUtils.createRealMatrix(singular));
223         assertFalse(lu.getSolver().isNonSingular());
224         assertEquals(0.0, lu.getDeterminant(), 1.0e-15);
225         lu = new LUDecomposition(MatrixUtils.createRealMatrix(bigSingular));
226         assertFalse(lu.getSolver().isNonSingular());
227         assertEquals(0.0, lu.getDeterminant(), 1.0e-15);
228         try {
229             lu.getSolver().solve(new ArrayRealVector(new double[] { 1, 1, 1, 1 }));
230             fail("an exception should have been thrown");
231         } catch (MathIllegalArgumentException miae) {
232             assertEquals(LocalizedCoreFormats.SINGULAR_MATRIX, miae.getSpecifier());
233         }
234     }
235 
236     /** test matrices values */
237     @Test
238     void testMatricesValues1() {
239        LUDecomposition lu =
240             new LUDecomposition(MatrixUtils.createRealMatrix(testData));
241         RealMatrix lRef = MatrixUtils.createRealMatrix(new double[][] {
242                 { 1.0, 0.0, 0.0 },
243                 { 0.5, 1.0, 0.0 },
244                 { 0.5, 0.2, 1.0 }
245         });
246         RealMatrix uRef = MatrixUtils.createRealMatrix(new double[][] {
247                 { 2.0,  5.0, 3.0 },
248                 { 0.0, -2.5, 6.5 },
249                 { 0.0,  0.0, 0.2 }
250         });
251         RealMatrix pRef = MatrixUtils.createRealMatrix(new double[][] {
252                 { 0.0, 1.0, 0.0 },
253                 { 0.0, 0.0, 1.0 },
254                 { 1.0, 0.0, 0.0 }
255         });
256         int[] pivotRef = { 1, 2, 0 };
257 
258         // check values against known references
259         RealMatrix l = lu.getL();
260         assertEquals(0, l.subtract(lRef).getNorm1(), 1.0e-13);
261         RealMatrix u = lu.getU();
262         assertEquals(0, u.subtract(uRef).getNorm1(), 1.0e-13);
263         RealMatrix p = lu.getP();
264         assertEquals(0, p.subtract(pRef).getNorm1(), 1.0e-13);
265         int[] pivot = lu.getPivot();
266         for (int i = 0; i < pivotRef.length; ++i) {
267             assertEquals(pivotRef[i], pivot[i]);
268         }
269 
270         // check the same cached instance is returned the second time
271         assertTrue(l == lu.getL());
272         assertTrue(u == lu.getU());
273         assertTrue(p == lu.getP());
274 
275     }
276 
277     /** test matrices values */
278     @Test
279     void testMatricesValues2() {
280        LUDecomposition lu =
281             new LUDecomposition(MatrixUtils.createRealMatrix(luData));
282         RealMatrix lRef = MatrixUtils.createRealMatrix(new double[][] {
283                 {    1.0,    0.0, 0.0 },
284                 {    0.0,    1.0, 0.0 },
285                 { 1.0 / 3.0, 0.0, 1.0 }
286         });
287         RealMatrix uRef = MatrixUtils.createRealMatrix(new double[][] {
288                 { 6.0, 9.0,    8.0    },
289                 { 0.0, 5.0,    7.0    },
290                 { 0.0, 0.0, 1.0 / 3.0 }
291         });
292         RealMatrix pRef = MatrixUtils.createRealMatrix(new double[][] {
293                 { 0.0, 0.0, 1.0 },
294                 { 0.0, 1.0, 0.0 },
295                 { 1.0, 0.0, 0.0 }
296         });
297         int[] pivotRef = { 2, 1, 0 };
298 
299         // check values against known references
300         RealMatrix l = lu.getL();
301         assertEquals(0, l.subtract(lRef).getNorm1(), 1.0e-13);
302         RealMatrix u = lu.getU();
303         assertEquals(0, u.subtract(uRef).getNorm1(), 1.0e-13);
304         RealMatrix p = lu.getP();
305         assertEquals(0, p.subtract(pRef).getNorm1(), 1.0e-13);
306         int[] pivot = lu.getPivot();
307         for (int i = 0; i < pivotRef.length; ++i) {
308             assertEquals(pivotRef[i], pivot[i]);
309         }
310 
311         // check the same cached instance is returned the second time
312         assertTrue(l == lu.getL());
313         assertTrue(u == lu.getU());
314         assertTrue(p == lu.getP());
315     }
316 
317     @Test
318     void testSolve() {
319         LUDecomposition lu =
320                         new LUDecomposition(new Array2DRowRealMatrix(testData));
321         DecompositionSolver solver = lu.getSolver();
322         RealVector solution = solver.solve(new ArrayRealVector(new double[] {
323             new Fraction(1, 2).doubleValue(), new Fraction(2, 3).doubleValue(), new Fraction(3,4).doubleValue()
324         }));
325         assertEquals(testData.length, solution.getDimension());
326         assertEquals(new Fraction(-31, 12).doubleValue(), solution.getEntry(0), 1.0e-14);
327         assertEquals(new Fraction( 11, 12).doubleValue(), solution.getEntry(1), 1.0e-14);
328         assertEquals(new Fraction(  5, 12).doubleValue(), solution.getEntry(2), 1.0e-14);
329         assertEquals(testData.length,    solver.getRowDimension());
330         assertEquals(testData[0].length, solver.getColumnDimension());
331         try {
332             solver.solve(new ArrayRealVector(new double[] { 1, 1 }));
333             fail("an exception should have been thrown");
334         } catch (MathIllegalArgumentException miae) {
335             assertEquals(LocalizedCoreFormats.DIMENSIONS_MISMATCH, miae.getSpecifier());
336         }
337     }
338 
339 }