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