/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with this * work for additional information regarding copyright ownership. The ASF * licenses this file to You under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law * or agreed to in writing, software distributed under the License is * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.apache.commons.math4.fitting.leastsquares; import org.apache.commons.math4.TestUtils; import org.apache.commons.math4.analysis.MultivariateMatrixFunction; import org.apache.commons.math4.analysis.MultivariateVectorFunction; import org.apache.commons.math4.exception.MathIllegalStateException; import org.apache.commons.math4.fitting.leastsquares.LeastSquaresBuilder; import org.apache.commons.math4.fitting.leastsquares.LeastSquaresFactory; import org.apache.commons.math4.fitting.leastsquares.LeastSquaresProblem; import org.apache.commons.math4.fitting.leastsquares.MultivariateJacobianFunction; import org.apache.commons.math4.fitting.leastsquares.ValueAndJacobianFunction; import org.apache.commons.math4.fitting.leastsquares.LeastSquaresProblem.Evaluation; import org.apache.commons.math4.linear.ArrayRealVector; import org.apache.commons.math4.linear.DiagonalMatrix; import org.apache.commons.math4.linear.MatrixUtils; import org.apache.commons.math4.linear.RealMatrix; import org.apache.commons.math4.linear.RealVector; import org.apache.commons.math4.linear.SingularMatrixException; import org.apache.commons.math4.util.FastMath; import org.apache.commons.math4.util.Pair; import org.apache.commons.numbers.core.Precision; import org.junit.Assert; import org.junit.Test; import java.io.IOException; import java.util.Arrays; /** * The only features tested here are utility methods defined * in {@link LeastSquaresProblem.Evaluation} that compute the * chi-square and parameters standard-deviations. */ public class EvaluationTest { /** * Create a {@link LeastSquaresBuilder} from a {@link StatisticalReferenceDataset}. * * @param dataset the source data * @return a builder for further customization. */ public LeastSquaresBuilder builder(StatisticalReferenceDataset dataset) { StatisticalReferenceDataset.LeastSquaresProblem problem = dataset.getLeastSquaresProblem(); final double[] start = dataset.getParameters(); final double[] observed = dataset.getData()[1]; final double[] weights = new double[observed.length]; Arrays.fill(weights, 1d); return new LeastSquaresBuilder() .model(problem.getModelFunction(), problem.getModelFunctionJacobian()) .target(observed) .weight(new DiagonalMatrix(weights)) .start(start); } @Test public void testComputeResiduals() { //setup RealVector point = new ArrayRealVector(2); Evaluation evaluation = new LeastSquaresBuilder() .target(new ArrayRealVector(new double[]{3,-1})) .model(new MultivariateJacobianFunction() { @Override public Pair<RealVector, RealMatrix> value(RealVector point) { return new Pair<RealVector, RealMatrix>( new ArrayRealVector(new double[]{1, 2}), MatrixUtils.createRealIdentityMatrix(2) ); } }) .weight(MatrixUtils.createRealIdentityMatrix(2)) .build() .evaluate(point); //action + verify Assert.assertArrayEquals( evaluation.getResiduals().toArray(), new double[]{2, -3}, Precision.EPSILON); } @Test public void testComputeCovariance() throws IOException { //setup RealVector point = new ArrayRealVector(2); Evaluation evaluation = new LeastSquaresBuilder() .model(new MultivariateJacobianFunction() { @Override public Pair<RealVector, RealMatrix> value(RealVector point) { return new Pair<RealVector, RealMatrix>( new ArrayRealVector(2), MatrixUtils.createRealDiagonalMatrix(new double[]{1, 1e-2}) ); } }) .weight(MatrixUtils.createRealDiagonalMatrix(new double[]{1, 1})) .target(new ArrayRealVector(2)) .build() .evaluate(point); //action TestUtils.assertEquals( "covariance", evaluation.getCovariances(FastMath.nextAfter(1e-4, 0.0)), MatrixUtils.createRealMatrix(new double[][]{{1, 0}, {0, 1e4}}), Precision.EPSILON ); //singularity fail try { evaluation.getCovariances(FastMath.nextAfter(1e-4, 1.0)); Assert.fail("Expected Exception"); } catch (SingularMatrixException e) { //expected } } @Test public void testComputeValueAndJacobian() { //setup final RealVector point = new ArrayRealVector(new double[]{1, 2}); Evaluation evaluation = new LeastSquaresBuilder() .weight(new DiagonalMatrix(new double[]{16, 4})) .model(new MultivariateJacobianFunction() { @Override public Pair<RealVector, RealMatrix> value(RealVector actualPoint) { //verify correct values passed in Assert.assertArrayEquals( point.toArray(), actualPoint.toArray(), Precision.EPSILON); //return values return new Pair<RealVector, RealMatrix>( new ArrayRealVector(new double[]{3, 4}), MatrixUtils.createRealMatrix(new double[][]{{5, 6}, {7, 8}}) ); } }) .target(new double[2]) .build() .evaluate(point); //action RealVector residuals = evaluation.getResiduals(); RealMatrix jacobian = evaluation.getJacobian(); //verify Assert.assertArrayEquals(evaluation.getPoint().toArray(), point.toArray(), 0); Assert.assertArrayEquals(new double[]{-12, -8}, residuals.toArray(), Precision.EPSILON); TestUtils.assertEquals( "jacobian", jacobian, MatrixUtils.createRealMatrix(new double[][]{{20, 24},{14, 16}}), Precision.EPSILON); } @Test public void testComputeCost() throws IOException { final StatisticalReferenceDataset dataset = StatisticalReferenceDatasetFactory.createKirby2(); final LeastSquaresProblem lsp = builder(dataset).build(); final double expected = dataset.getResidualSumOfSquares(); final double cost = lsp.evaluate(lsp.getStart()).getCost(); final double actual = cost * cost; Assert.assertEquals(dataset.getName(), expected, actual, 1e-11 * expected); } @Test public void testComputeRMS() throws IOException { final StatisticalReferenceDataset dataset = StatisticalReferenceDatasetFactory.createKirby2(); final LeastSquaresProblem lsp = builder(dataset).build(); final double expected = FastMath.sqrt(dataset.getResidualSumOfSquares() / dataset.getNumObservations()); final double actual = lsp.evaluate(lsp.getStart()).getRMS(); Assert.assertEquals(dataset.getName(), expected, actual, 1e-11 * expected); } @Test public void testComputeSigma() throws IOException { final StatisticalReferenceDataset dataset = StatisticalReferenceDatasetFactory.createKirby2(); final LeastSquaresProblem lsp = builder(dataset).build(); final double[] expected = dataset.getParametersStandardDeviations(); final Evaluation evaluation = lsp.evaluate(lsp.getStart()); final double cost = evaluation.getCost(); final RealVector sig = evaluation.getSigma(1e-14); final int dof = lsp.getObservationSize() - lsp.getParameterSize(); for (int i = 0; i < sig.getDimension(); i++) { final double actual = FastMath.sqrt(cost * cost / dof) * sig.getEntry(i); Assert.assertEquals(dataset.getName() + ", parameter #" + i, expected[i], actual, 1e-6 * expected[i]); } } @Test public void testEvaluateCopiesPoint() throws IOException { //setup StatisticalReferenceDataset dataset = StatisticalReferenceDatasetFactory.createKirby2(); LeastSquaresProblem lsp = builder(dataset).build(); RealVector point = new ArrayRealVector(lsp.getParameterSize()); //action Evaluation evaluation = lsp.evaluate(point); //verify Assert.assertNotSame(point, evaluation.getPoint()); point.setEntry(0, 1); Assert.assertEquals(evaluation.getPoint().getEntry(0), 0, 0); } @Test public void testLazyEvaluation() { final RealVector dummy = new ArrayRealVector(new double[] { 0 }); final LeastSquaresProblem p = LeastSquaresFactory.create(LeastSquaresFactory.model(dummyModel(), dummyJacobian()), dummy, dummy, null, null, 0, 0, true, null); // Should not throw because actual evaluation is deferred. final Evaluation eval = p.evaluate(dummy); try { eval.getResiduals(); Assert.fail("Exception expected"); } catch (RuntimeException e) { // Expecting exception. Assert.assertEquals("dummyModel", e.getMessage()); } try { eval.getJacobian(); Assert.fail("Exception expected"); } catch (RuntimeException e) { // Expecting exception. Assert.assertEquals("dummyJacobian", e.getMessage()); } } // MATH-1151 @Test public void testLazyEvaluationPrecondition() { final RealVector dummy = new ArrayRealVector(new double[] { 0 }); // "ValueAndJacobianFunction" is required but we implement only // "MultivariateJacobianFunction". final MultivariateJacobianFunction m1 = new MultivariateJacobianFunction() { @Override public Pair<RealVector, RealMatrix> value(RealVector notUsed) { return new Pair<>(null, null); } }; try { // Should throw. LeastSquaresFactory.create(m1, dummy, dummy, null, null, 0, 0, true, null); Assert.fail("Expecting MathIllegalStateException"); } catch (MathIllegalStateException e) { // Expected. } final MultivariateJacobianFunction m2 = new ValueAndJacobianFunction() { @Override public Pair<RealVector, RealMatrix> value(RealVector notUsed) { return new Pair<>(null, null); } @Override public RealVector computeValue(final double[] params) { return null; } @Override public RealMatrix computeJacobian(final double[] params) { return null; } }; // Should pass. LeastSquaresFactory.create(m2, dummy, dummy, null, null, 0, 0, true, null); } @Test public void testDirectEvaluation() { final RealVector dummy = new ArrayRealVector(new double[] { 0 }); final LeastSquaresProblem p = LeastSquaresFactory.create(LeastSquaresFactory.model(dummyModel(), dummyJacobian()), dummy, dummy, null, null, 0, 0, false, null); try { // Should throw. p.evaluate(dummy); Assert.fail("Exception expected"); } catch (RuntimeException e) { // Expecting exception. // Whether it is model or Jacobian that caused it is not significant. final String msg = e.getMessage(); Assert.assertTrue(msg.equals("dummyModel") || msg.equals("dummyJacobian")); } } /** Used for testing direct vs lazy evaluation. */ private MultivariateVectorFunction dummyModel() { return new MultivariateVectorFunction() { @Override public double[] value(double[] p) { throw new RuntimeException("dummyModel"); } }; } /** Used for testing direct vs lazy evaluation. */ private MultivariateMatrixFunction dummyJacobian() { return new MultivariateMatrixFunction() { @Override public double[][] value(double[] p) { throw new RuntimeException("dummyJacobian"); } }; } }