/* * 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.math3.optimization.general; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import org.apache.commons.math3.analysis.differentiation.DerivativeStructure; import org.apache.commons.math3.analysis.differentiation.MultivariateDifferentiableVectorFunction; import org.apache.commons.math3.exception.ConvergenceException; import org.apache.commons.math3.exception.DimensionMismatchException; import org.apache.commons.math3.exception.TooManyEvaluationsException; import org.apache.commons.math3.geometry.euclidean.twod.Vector2D; import org.apache.commons.math3.linear.SingularMatrixException; import org.apache.commons.math3.optimization.PointVectorValuePair; import org.apache.commons.math3.util.FastMath; import org.apache.commons.math3.util.Precision; import org.junit.Assert; import org.junit.Test; import org.junit.Ignore; /** * <p>Some of the unit tests are re-implementations of the MINPACK <a * href="http://www.netlib.org/minpack/ex/file17">file17</a> and <a * href="http://www.netlib.org/minpack/ex/file22">file22</a> test files. * The redistribution policy for MINPACK is available <a * href="http://www.netlib.org/minpack/disclaimer">here</a>, for * convenience, it is reproduced below.</p> * <table border="0" width="80%" cellpadding="10" align="center" bgcolor="#E0E0E0"> * <tr><td> * Minpack Copyright Notice (1999) University of Chicago. * All rights reserved * </td></tr> * <tr><td> * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * <ol> * <li>Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer.</li> * <li>Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution.</li> * <li>The end-user documentation included with the redistribution, if any, * must include the following acknowledgment: * <code>This product includes software developed by the University of * Chicago, as Operator of Argonne National Laboratory.</code> * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear.</li> * <li><strong>WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" * WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT HOLDER, THE * UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND * THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE * OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY * OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR * USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF * THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4) * DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION * UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL * BE CORRECTED.</strong></li> * <li><strong>LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT * HOLDER, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF * ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, * INCIDENTAL, CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF * ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED TO LOSS OF * PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER * SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT * (INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE, * EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE * POSSIBILITY OF SUCH LOSS OR DAMAGES.</strong></li> * <ol></td></tr> * </table> * @author Argonne National Laboratory. MINPACK project. March 1980 (original fortran minpack tests) * @author Burton S. Garbow (original fortran minpack tests) * @author Kenneth E. Hillstrom (original fortran minpack tests) * @author Jorge J. More (original fortran minpack tests) * @author Luc Maisonobe (non-minpack tests and minpack tests Java translation) */ @Deprecated public class LevenbergMarquardtOptimizerTest extends AbstractLeastSquaresOptimizerAbstractTest { @Override public AbstractLeastSquaresOptimizer createOptimizer() { return new LevenbergMarquardtOptimizer(); } @Override @Test(expected=SingularMatrixException.class) public void testNonInvertible() { /* * Overrides the method from parent class, since the default singularity * threshold (1e-14) does not trigger the expected exception. */ LinearProblem problem = new LinearProblem(new double[][] { { 1, 2, -3 }, { 2, 1, 3 }, { -3, 0, -9 } }, new double[] { 1, 1, 1 }); AbstractLeastSquaresOptimizer optimizer = createOptimizer(); PointVectorValuePair optimum = optimizer.optimize(100, problem, problem.target, new double[] { 1, 1, 1 }, new double[] { 0, 0, 0 }); Assert.assertTrue(FastMath.sqrt(problem.target.length) * optimizer.getRMS() > 0.6); optimizer.computeCovariances(optimum.getPoint(), 1.5e-14); } @Test public void testControlParameters() { CircleVectorial circle = new CircleVectorial(); circle.addPoint( 30.0, 68.0); circle.addPoint( 50.0, -6.0); circle.addPoint(110.0, -20.0); circle.addPoint( 35.0, 15.0); circle.addPoint( 45.0, 97.0); checkEstimate(circle, 0.1, 10, 1.0e-14, 1.0e-16, 1.0e-10, false); checkEstimate(circle, 0.1, 10, 1.0e-15, 1.0e-17, 1.0e-10, true); checkEstimate(circle, 0.1, 5, 1.0e-15, 1.0e-16, 1.0e-10, true); circle.addPoint(300, -300); checkEstimate(circle, 0.1, 20, 1.0e-18, 1.0e-16, 1.0e-10, true); } private void checkEstimate(MultivariateDifferentiableVectorFunction problem, double initialStepBoundFactor, int maxCostEval, double costRelativeTolerance, double parRelativeTolerance, double orthoTolerance, boolean shouldFail) { try { LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer(initialStepBoundFactor, costRelativeTolerance, parRelativeTolerance, orthoTolerance, Precision.SAFE_MIN); optimizer.optimize(maxCostEval, problem, new double[] { 0, 0, 0, 0, 0 }, new double[] { 1, 1, 1, 1, 1 }, new double[] { 98.680, 47.345 }); Assert.assertTrue(!shouldFail); } catch (DimensionMismatchException ee) { Assert.assertTrue(shouldFail); } catch (TooManyEvaluationsException ee) { Assert.assertTrue(shouldFail); } } // Test is skipped because it fails with the latest code update. @Ignore@Test public void testMath199() { try { QuadraticProblem problem = new QuadraticProblem(); problem.addPoint (0, -3.182591015485607); problem.addPoint (1, -2.5581184967730577); problem.addPoint (2, -2.1488478161387325); problem.addPoint (3, -1.9122489313410047); problem.addPoint (4, 1.7785661310051026); LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer(100, 1e-10, 1e-10, 1e-10, 0); optimizer.optimize(100, problem, new double[] { 0, 0, 0, 0, 0 }, new double[] { 0.0, 4.4e-323, 1.0, 4.4e-323, 0.0 }, new double[] { 0, 0, 0 }); Assert.fail("an exception should have been thrown"); } catch (ConvergenceException ee) { // expected behavior } } /** * Non-linear test case: fitting of decay curve (from Chapter 8 of * Bevington's textbook, "Data reduction and analysis for the physical sciences"). * XXX The expected ("reference") values may not be accurate and the tolerance too * relaxed for this test to be currently really useful (the issue is under * investigation). */ @Test public void testBevington() { final double[][] dataPoints = { // column 1 = times { 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, 285, 300, 315, 330, 345, 360, 375, 390, 405, 420, 435, 450, 465, 480, 495, 510, 525, 540, 555, 570, 585, 600, 615, 630, 645, 660, 675, 690, 705, 720, 735, 750, 765, 780, 795, 810, 825, 840, 855, 870, 885, }, // column 2 = measured counts { 775, 479, 380, 302, 185, 157, 137, 119, 110, 89, 74, 61, 66, 68, 48, 54, 51, 46, 55, 29, 28, 37, 49, 26, 35, 29, 31, 24, 25, 35, 24, 30, 26, 28, 21, 18, 20, 27, 17, 17, 14, 17, 24, 11, 22, 17, 12, 10, 13, 16, 9, 9, 14, 21, 17, 13, 12, 18, 10, }, }; final BevingtonProblem problem = new BevingtonProblem(); final int len = dataPoints[0].length; final double[] weights = new double[len]; for (int i = 0; i < len; i++) { problem.addPoint(dataPoints[0][i], dataPoints[1][i]); weights[i] = 1 / dataPoints[1][i]; } final LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer(); final PointVectorValuePair optimum = optimizer.optimize(100, problem, dataPoints[1], weights, new double[] { 10, 900, 80, 27, 225 }); final double[] solution = optimum.getPoint(); final double[] expectedSolution = { 10.4, 958.3, 131.4, 33.9, 205.0 }; final double[][] covarMatrix = optimizer.computeCovariances(solution, 1e-14); final double[][] expectedCovarMatrix = { { 3.38, -3.69, 27.98, -2.34, -49.24 }, { -3.69, 2492.26, 81.89, -69.21, -8.9 }, { 27.98, 81.89, 468.99, -44.22, -615.44 }, { -2.34, -69.21, -44.22, 6.39, 53.80 }, { -49.24, -8.9, -615.44, 53.8, 929.45 } }; final int numParams = expectedSolution.length; // Check that the computed solution is within the reference error range. for (int i = 0; i < numParams; i++) { final double error = FastMath.sqrt(expectedCovarMatrix[i][i]); Assert.assertEquals("Parameter " + i, expectedSolution[i], solution[i], error); } // Check that each entry of the computed covariance matrix is within 10% // of the reference matrix entry. for (int i = 0; i < numParams; i++) { for (int j = 0; j < numParams; j++) { Assert.assertEquals("Covariance matrix [" + i + "][" + j + "]", expectedCovarMatrix[i][j], covarMatrix[i][j], FastMath.abs(0.1 * expectedCovarMatrix[i][j])); } } } @Test public void testCircleFitting2() { final double xCenter = 123.456; final double yCenter = 654.321; final double xSigma = 10; final double ySigma = 15; final double radius = 111.111; // The test is extremely sensitive to the seed. final long seed = 59421061L; final RandomCirclePointGenerator factory = new RandomCirclePointGenerator(xCenter, yCenter, radius, xSigma, ySigma, seed); final CircleProblem circle = new CircleProblem(xSigma, ySigma); final int numPoints = 10; for (Vector2D p : factory.generate(numPoints)) { circle.addPoint(p); // System.out.println(p.x + " " + p.y); } // First guess for the center's coordinates and radius. final double[] init = { 90, 659, 115 }; final LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer(); final PointVectorValuePair optimum = optimizer.optimize(100, circle, circle.target(), circle.weight(), init); final double[] paramFound = optimum.getPoint(); // Retrieve errors estimation. final double[][] covMatrix = optimizer.computeCovariances(paramFound, 1e-14); final double[] asymptoticStandardErrorFound = optimizer.guessParametersErrors(); final double[] sigmaFound = new double[covMatrix.length]; for (int i = 0; i < covMatrix.length; i++) { sigmaFound[i] = FastMath.sqrt(covMatrix[i][i]); // System.out.println("i=" + i + " value=" + paramFound[i] // + " sigma=" + sigmaFound[i] // + " ase=" + asymptoticStandardErrorFound[i]); } // System.out.println("chi2=" + optimizer.getChiSquare()); // Check that the parameters are found within the assumed error bars. Assert.assertEquals(xCenter, paramFound[0], asymptoticStandardErrorFound[0]); Assert.assertEquals(yCenter, paramFound[1], asymptoticStandardErrorFound[1]); Assert.assertEquals(radius, paramFound[2], asymptoticStandardErrorFound[2]); } private static class QuadraticProblem implements MultivariateDifferentiableVectorFunction, Serializable { private static final long serialVersionUID = 7072187082052755854L; private List<Double> x; private List<Double> y; public QuadraticProblem() { x = new ArrayList<Double>(); y = new ArrayList<Double>(); } public void addPoint(double x, double y) { this.x.add(x); this.y.add(y); } public double[] value(double[] variables) { double[] values = new double[x.size()]; for (int i = 0; i < values.length; ++i) { values[i] = (variables[0] * x.get(i) + variables[1]) * x.get(i) + variables[2]; } return values; } public DerivativeStructure[] value(DerivativeStructure[] variables) { DerivativeStructure[] values = new DerivativeStructure[x.size()]; for (int i = 0; i < values.length; ++i) { values[i] = (variables[0].multiply(x.get(i)).add(variables[1])).multiply(x.get(i)).add(variables[2]); } return values; } } private static class BevingtonProblem implements MultivariateDifferentiableVectorFunction { private List<Double> time; private List<Double> count; public BevingtonProblem() { time = new ArrayList<Double>(); count = new ArrayList<Double>(); } public void addPoint(double t, double c) { time.add(t); count.add(c); } public double[] value(double[] params) { double[] values = new double[time.size()]; for (int i = 0; i < values.length; ++i) { final double t = time.get(i); values[i] = params[0] + params[1] * FastMath.exp(-t / params[3]) + params[2] * FastMath.exp(-t / params[4]); } return values; } public DerivativeStructure[] value(DerivativeStructure[] params) { DerivativeStructure[] values = new DerivativeStructure[time.size()]; for (int i = 0; i < values.length; ++i) { final double t = time.get(i); values[i] = params[0].add( params[1].multiply(params[3].reciprocal().multiply(-t).exp())).add( params[2].multiply(params[4].reciprocal().multiply(-t).exp())); } return values; } } }