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