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 this
4    * work for additional information regarding copyright ownership. The ASF
5    * licenses this file to You under the Apache License, Version 2.0 (the
6    * "License"); you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    * https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
9    * or agreed to in writing, software distributed under the License is
10   * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11   * KIND, either express or implied. See the License for the specific language
12   * governing permissions and limitations under the License.
13   */
14  package org.hipparchus.optim.nonlinear.vector.leastsquares;
15  
16  import java.io.IOException;
17  import java.util.Arrays;
18  
19  import org.hipparchus.UnitTestUtils;
20  import org.hipparchus.analysis.MultivariateMatrixFunction;
21  import org.hipparchus.analysis.MultivariateVectorFunction;
22  import org.hipparchus.exception.MathIllegalArgumentException;
23  import org.hipparchus.exception.MathIllegalStateException;
24  import org.hipparchus.linear.ArrayRealVector;
25  import org.hipparchus.linear.DiagonalMatrix;
26  import org.hipparchus.linear.MatrixUtils;
27  import org.hipparchus.linear.RealMatrix;
28  import org.hipparchus.linear.RealVector;
29  import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem.Evaluation;
30  import org.hipparchus.util.FastMath;
31  import org.hipparchus.util.Pair;
32  import org.hipparchus.util.Precision;
33  import org.junit.Assert;
34  import org.junit.Test;
35  
36  /**
37   * The only features tested here are utility methods defined
38   * in {@link LeastSquaresProblem.Evaluation} that compute the
39   * chi-square and parameters standard-deviations.
40   */
41  public class EvaluationTest {
42  
43      /**
44       * Create a {@link LeastSquaresBuilder} from a {@link StatisticalReferenceDataset}.
45       *
46       * @param dataset the source data
47       * @return a builder for further customization.
48       */
49      public LeastSquaresBuilder builder(StatisticalReferenceDataset dataset) {
50          StatisticalReferenceDataset.LeastSquaresProblem problem
51                  = dataset.getLeastSquaresProblem();
52          final double[] start = dataset.getParameters();
53          final double[] observed = dataset.getData()[1];
54          final double[] weights = new double[observed.length];
55          Arrays.fill(weights, 1d);
56  
57          return new LeastSquaresBuilder()
58                  .model(problem.getModelFunction(), problem.getModelFunctionJacobian())
59                  .target(observed)
60                  .weight(new DiagonalMatrix(weights))
61                  .start(start);
62      }
63  
64      @Test
65      public void testComputeResiduals() {
66          //setup
67          RealVector point = new ArrayRealVector(2);
68          Evaluation evaluation = new LeastSquaresBuilder()
69                  .target(new ArrayRealVector(new double[]{3,-1}))
70                  .model(new MultivariateJacobianFunction() {
71                      public Pair<RealVector, RealMatrix> value(RealVector point) {
72                          return new Pair<RealVector, RealMatrix>(
73                                  new ArrayRealVector(new double[]{1, 2}),
74                                  MatrixUtils.createRealIdentityMatrix(2)
75                          );
76                      }
77                  })
78                  .weight(MatrixUtils.createRealIdentityMatrix(2))
79                  .build()
80                  .evaluate(point);
81  
82          //action + verify
83          Assert.assertArrayEquals(
84                  evaluation.getResiduals().toArray(),
85                  new double[]{2, -3},
86                  Precision.EPSILON);
87      }
88  
89      @Test
90      public void testComputeCovariance() throws IOException {
91          //setup
92          RealVector point = new ArrayRealVector(2);
93          Evaluation evaluation = new LeastSquaresBuilder()
94                  .model(new MultivariateJacobianFunction() {
95                      public Pair<RealVector, RealMatrix> value(RealVector point) {
96                          return new Pair<RealVector, RealMatrix>(
97                                  new ArrayRealVector(2),
98                                  MatrixUtils.createRealDiagonalMatrix(new double[]{1, 1e-2})
99                          );
100                     }
101                 })
102                 .weight(MatrixUtils.createRealDiagonalMatrix(new double[]{1, 1}))
103                 .target(new ArrayRealVector(2))
104                 .build()
105                 .evaluate(point);
106 
107         //action
108         UnitTestUtils.assertEquals(
109                 "covariance",
110                 evaluation.getCovariances(FastMath.nextAfter(1e-4, 0.0)),
111                 MatrixUtils.createRealMatrix(new double[][]{{1, 0}, {0, 1e4}}),
112                 Precision.EPSILON
113         );
114 
115         //singularity fail
116         try {
117             evaluation.getCovariances(FastMath.nextAfter(1e-4, 1.0));
118             Assert.fail("Expected Exception");
119         } catch (MathIllegalArgumentException e) {
120             //expected
121         }
122     }
123 
124     @Test
125     public void testComputeValueAndJacobian() {
126         //setup
127         final RealVector point = new ArrayRealVector(new double[]{1, 2});
128         Evaluation evaluation = new LeastSquaresBuilder()
129                 .weight(new DiagonalMatrix(new double[]{16, 4}))
130                 .model(new MultivariateJacobianFunction() {
131                     public Pair<RealVector, RealMatrix> value(RealVector actualPoint) {
132                         //verify correct values passed in
133                         Assert.assertArrayEquals(
134                                 point.toArray(), actualPoint.toArray(), Precision.EPSILON);
135                         //return values
136                         return new Pair<RealVector, RealMatrix>(
137                                 new ArrayRealVector(new double[]{3, 4}),
138                                 MatrixUtils.createRealMatrix(new double[][]{{5, 6}, {7, 8}})
139                         );
140                     }
141                 })
142                 .target(new double[2])
143                 .build()
144                 .evaluate(point);
145 
146         //action
147         RealVector residuals = evaluation.getResiduals();
148         RealMatrix jacobian = evaluation.getJacobian();
149 
150         //verify
151         Assert.assertArrayEquals(evaluation.getPoint().toArray(), point.toArray(), 0);
152         Assert.assertArrayEquals(new double[]{-12, -8}, residuals.toArray(), Precision.EPSILON);
153         UnitTestUtils.assertEquals(
154                 "jacobian",
155                 jacobian,
156                 MatrixUtils.createRealMatrix(new double[][]{{20, 24},{14, 16}}),
157                 Precision.EPSILON);
158     }
159 
160     @Test
161     public void testComputeCost() throws IOException {
162         final StatisticalReferenceDataset dataset
163             = StatisticalReferenceDatasetFactory.createKirby2();
164 
165         final LeastSquaresProblem lsp = builder(dataset).build();
166 
167         final double expected = dataset.getResidualSumOfSquares();
168         final double cost = lsp.evaluate(lsp.getStart()).getCost();
169         final double actual = cost * cost;
170         Assert.assertEquals(dataset.getName(), expected, actual, 1e-11 * expected);
171     }
172 
173     @Test
174     public void testComputeRMS() throws IOException {
175         final StatisticalReferenceDataset dataset
176             = StatisticalReferenceDatasetFactory.createKirby2();
177 
178         final LeastSquaresProblem lsp = builder(dataset).build();
179 
180         final double expected = FastMath.sqrt(dataset.getResidualSumOfSquares() /
181                                               dataset.getNumObservations());
182         final double actual = lsp.evaluate(lsp.getStart()).getRMS();
183         Assert.assertEquals(dataset.getName(), expected, actual, 1e-11 * expected);
184     }
185 
186     @Test
187     public void testComputeSigma() throws IOException {
188         final StatisticalReferenceDataset dataset
189             = StatisticalReferenceDatasetFactory.createKirby2();
190 
191         final LeastSquaresProblem lsp = builder(dataset).build();
192 
193         final double[] expected = dataset.getParametersStandardDeviations();
194 
195         final Evaluation evaluation = lsp.evaluate(lsp.getStart());
196         final double cost = evaluation.getCost();
197         final RealVector sig = evaluation.getSigma(1e-14);
198         final int dof = lsp.getObservationSize() - lsp.getParameterSize();
199         for (int i = 0; i < sig.getDimension(); i++) {
200             final double actual = FastMath.sqrt(cost * cost / dof) * sig.getEntry(i);
201             Assert.assertEquals(dataset.getName() + ", parameter #" + i,
202                                 expected[i], actual, 1e-6 * expected[i]);
203         }
204     }
205 
206     @Test
207     public void testEvaluateCopiesPoint() throws IOException {
208         //setup
209         StatisticalReferenceDataset dataset
210                 = StatisticalReferenceDatasetFactory.createKirby2();
211         LeastSquaresProblem lsp = builder(dataset).build();
212         RealVector point = new ArrayRealVector(lsp.getParameterSize());
213 
214         //action
215         Evaluation evaluation = lsp.evaluate(point);
216 
217         //verify
218         Assert.assertNotSame(point, evaluation.getPoint());
219         point.setEntry(0, 1);
220         Assert.assertEquals(evaluation.getPoint().getEntry(0), 0, 0);
221     }
222 
223     @Test
224     public void testLazyEvaluation() {
225         final RealVector dummy = new ArrayRealVector(new double[] { 0 });
226 
227         final LeastSquaresProblem p
228             = LeastSquaresFactory.create(LeastSquaresFactory.model(dummyModel(), dummyJacobian()),
229                                          dummy, dummy, null, null, 0, 0, true, null);
230 
231         // Should not throw because actual evaluation is deferred.
232         final Evaluation eval = p.evaluate(dummy);
233 
234         try {
235             eval.getResiduals();
236             Assert.fail("Exception expected");
237         } catch (RuntimeException e) {
238             // Expecting exception.
239             Assert.assertEquals("dummyModel", e.getMessage());
240         }
241 
242         try {
243             eval.getJacobian();
244             Assert.fail("Exception expected");
245         } catch (RuntimeException e) {
246             // Expecting exception.
247             Assert.assertEquals("dummyJacobian", e.getMessage());
248         }
249     }
250 
251     // MATH-1151
252     @Test
253     public void testLazyEvaluationPrecondition() {
254         final RealVector dummy = new ArrayRealVector(new double[] { 0 });
255 
256         // "ValueAndJacobianFunction" is required but we implement only
257         // "MultivariateJacobianFunction".
258         final MultivariateJacobianFunction m1 = new MultivariateJacobianFunction() {
259                 public Pair<RealVector, RealMatrix> value(RealVector notUsed) {
260                     return new Pair<RealVector, RealMatrix>(null, null);
261                 }
262             };
263 
264         try {
265             // Should throw.
266             LeastSquaresFactory.create(m1, dummy, dummy, null, null, 0, 0, true, null);
267             Assert.fail("Expecting MathIllegalStateException");
268         } catch (MathIllegalStateException e) {
269             // Expected.
270         }
271 
272         final MultivariateJacobianFunction m2 = new ValueAndJacobianFunction() {
273                 public Pair<RealVector, RealMatrix> value(RealVector notUsed) {
274                     return new Pair<RealVector, RealMatrix>(null, null);
275                 }
276                 public RealVector computeValue(final double[] params) {
277                     return null;
278                 }
279                 public RealMatrix computeJacobian(final double[] params) {
280                     return null;
281                 }
282             };
283 
284         // Should pass.
285         LeastSquaresFactory.create(m2, dummy, dummy, null, null, 0, 0, true, null);
286     }
287 
288     @Test
289     public void testDirectEvaluation() {
290         final RealVector dummy = new ArrayRealVector(new double[] { 0 });
291 
292         final LeastSquaresProblem p
293             = LeastSquaresFactory.create(LeastSquaresFactory.model(dummyModel(), dummyJacobian()),
294                                          dummy, dummy, null, null, 0, 0, false, null);
295 
296         try {
297             // Should throw.
298             p.evaluate(dummy);
299             Assert.fail("Exception expected");
300         } catch (RuntimeException e) {
301             // Expecting exception.
302             // Whether it is model or Jacobian that caused it is not significant.
303             final String msg = e.getMessage();
304             Assert.assertTrue(msg.equals("dummyModel") ||
305                               msg.equals("dummyJacobian"));
306         }
307     }
308 
309     /** Used for testing direct vs lazy evaluation. */
310     private MultivariateVectorFunction dummyModel() {
311         return new MultivariateVectorFunction() {
312             public double[] value(double[] p) {
313                 throw new RuntimeException("dummyModel");
314             }
315         };
316     }
317 
318     /** Used for testing direct vs lazy evaluation. */
319     private MultivariateMatrixFunction dummyJacobian() {
320         return new MultivariateMatrixFunction() {
321             public double[][] value(double[] p) {
322                 throw new RuntimeException("dummyJacobian");
323             }
324         };
325     }
326 }