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.MathIllegalArgumentException;
26  import org.junit.jupiter.api.Test;
27  
28  import java.util.Random;
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.assertTrue;
33  import static org.junit.jupiter.api.Assertions.fail;
34  
35  class QRSolverTest {
36      double[][] testData3x3NonSingular = {
37              { 12, -51,   4 },
38              {  6, 167, -68 },
39              { -4,  24, -41 }
40      };
41  
42      double[][] testData3x3Singular = {
43              { 1, 2,  2 },
44              { 2, 4,  6 },
45              { 4, 8, 12 }
46      };
47  
48      double[][] testData3x4 = {
49              { 12, -51,   4, 1 },
50              {  6, 167, -68, 2 },
51              { -4,  24, -41, 3 }
52      };
53  
54      double[][] testData4x3 = {
55              { 12, -51,   4 },
56              {  6, 167, -68 },
57              { -4,  24, -41 },
58              { -5,  34,   7 }
59      };
60  
61      /** test rank */
62      @Test
63      void testRank() {
64          DecompositionSolver solver =
65              new QRDecomposition(MatrixUtils.createRealMatrix(testData3x3NonSingular)).getSolver();
66          assertTrue(solver.isNonSingular());
67  
68          solver = new QRDecomposition(MatrixUtils.createRealMatrix(testData3x3Singular)).getSolver();
69          assertFalse(solver.isNonSingular());
70  
71          solver = new QRDecomposition(MatrixUtils.createRealMatrix(testData3x4)).getSolver();
72          assertTrue(solver.isNonSingular());
73  
74          solver = new QRDecomposition(MatrixUtils.createRealMatrix(testData4x3)).getSolver();
75          assertTrue(solver.isNonSingular());
76  
77      }
78  
79      /** test solve dimension errors */
80      @Test
81      void testSolveDimensionErrors() {
82          DecompositionSolver solver =
83              new QRDecomposition(MatrixUtils.createRealMatrix(testData3x3NonSingular)).getSolver();
84          RealMatrix b = MatrixUtils.createRealMatrix(new double[2][2]);
85          try {
86              solver.solve(b);
87              fail("an exception should have been thrown");
88          } catch (MathIllegalArgumentException iae) {
89              // expected behavior
90          }
91          try {
92              solver.solve(b.getColumnVector(0));
93              fail("an exception should have been thrown");
94          } catch (MathIllegalArgumentException iae) {
95              // expected behavior
96          }
97      }
98  
99      /** test solve rank errors */
100     @Test
101     void testSolveRankErrors() {
102         DecompositionSolver solver =
103             new QRDecomposition(MatrixUtils.createRealMatrix(testData3x3Singular)).getSolver();
104         RealMatrix b = MatrixUtils.createRealMatrix(new double[3][2]);
105         try {
106             solver.solve(b);
107             fail("an exception should have been thrown");
108         } catch (MathIllegalArgumentException iae) {
109             // expected behavior
110         }
111         try {
112             solver.solve(b.getColumnVector(0));
113             fail("an exception should have been thrown");
114         } catch (MathIllegalArgumentException iae) {
115             // expected behavior
116         }
117     }
118 
119     /** test solve */
120     @Test
121     void testSolve() {
122         QRDecomposition decomposition =
123             new QRDecomposition(MatrixUtils.createRealMatrix(testData3x3NonSingular));
124         DecompositionSolver solver = decomposition.getSolver();
125         RealMatrix b = MatrixUtils.createRealMatrix(new double[][] {
126                 { -102, 12250 }, { 544, 24500 }, { 167, -36750 }
127         });
128         RealMatrix xRef = MatrixUtils.createRealMatrix(new double[][] {
129                 { 1, 2515 }, { 2, 422 }, { -3, 898 }
130         });
131 
132         // using RealMatrix
133         assertEquals(0, solver.solve(b).subtract(xRef).getNorm1(), 2.1e-16 * xRef.getNorm1());
134 
135         // using ArrayRealVector
136         for (int i = 0; i < b.getColumnDimension(); ++i) {
137             final RealVector x = solver.solve(b.getColumnVector(i));
138             final double error = x.subtract(xRef.getColumnVector(i)).getNorm();
139             assertEquals(0, error, 3.0e-16 * xRef.getColumnVector(i).getNorm());
140         }
141 
142         // using RealVector with an alternate implementation
143         for (int i = 0; i < b.getColumnDimension(); ++i) {
144             ArrayRealVectorTest.RealVectorTestImpl v =
145                 new ArrayRealVectorTest.RealVectorTestImpl(b.getColumn(i));
146             final RealVector x = solver.solve(v);
147             final double error = x.subtract(xRef.getColumnVector(i)).getNorm();
148             assertEquals(0, error, 3.0e-16 * xRef.getColumnVector(i).getNorm());
149         }
150 
151     }
152 
153     @Test
154     void testOverdetermined() {
155         final Random r    = new Random(5559252868205245l);
156         int          p    = (7 * BlockRealMatrix.BLOCK_SIZE) / 4;
157         int          q    = (5 * BlockRealMatrix.BLOCK_SIZE) / 4;
158         RealMatrix   a    = createTestMatrix(r, p, q);
159         RealMatrix   xRef = createTestMatrix(r, q, BlockRealMatrix.BLOCK_SIZE + 3);
160 
161         // build a perturbed system: A.X + noise = B
162         RealMatrix b = a.multiply(xRef);
163         final double noise = 0.001;
164         b.walkInOptimizedOrder(new DefaultRealMatrixChangingVisitor() {
165             @Override
166             public double visit(int row, int column, double value) {
167                 return value * (1.0 + noise * (2 * r.nextDouble() - 1));
168             }
169         });
170 
171         // despite perturbation, the least square solution should be pretty good
172         RealMatrix x = new QRDecomposition(a).getSolver().solve(b);
173         assertEquals(0, x.subtract(xRef).getNorm1(), 0.01 * noise * p * q);
174 
175     }
176 
177     @Test
178     void testUnderdetermined() {
179         final Random r    = new Random(42185006424567123l);
180         int          p    = (5 * BlockRealMatrix.BLOCK_SIZE) / 4;
181         int          q    = (7 * BlockRealMatrix.BLOCK_SIZE) / 4;
182         RealMatrix   a    = createTestMatrix(r, p, q);
183         RealMatrix   xRef = createTestMatrix(r, q, BlockRealMatrix.BLOCK_SIZE + 3);
184         RealMatrix   b    = a.multiply(xRef);
185         RealMatrix   x = new QRDecomposition(a).getSolver().solve(b);
186 
187         // too many equations, the system cannot be solved at all
188         assertTrue(x.subtract(xRef).getNorm1() / (p * q) > 0.01);
189 
190         // the last unknown should have been set to 0
191         assertEquals(0.0, x.getSubMatrix(p, q - 1, 0, x.getColumnDimension() - 1).getNorm1(), 0);
192     }
193 
194     private RealMatrix createTestMatrix(final Random r, final int rows, final int columns) {
195         RealMatrix m = MatrixUtils.createRealMatrix(rows, columns);
196         m.walkInOptimizedOrder(new DefaultRealMatrixChangingVisitor() {
197                 @Override
198                     public double visit(int row, int column, double value) {
199                     return 2.0 * r.nextDouble() - 1.0;
200                 }
201             });
202         return m;
203     }
204 }