MATH-1014
"PolynomialCurveFitter" as replacement of "PolynomialFitter". Some tests have been obsoleted by the refactoring (which hides the optimizer and thus avoids some potential misuses). git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@1543906 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
54a7796ff2
commit
c1ba07bb65
|
@ -51,6 +51,9 @@ If the output is not quite correct, check for invisible trailing spaces!
|
|||
</properties>
|
||||
<body>
|
||||
<release version="3.3" date="TBD" description="TBD">
|
||||
<action dev="erans" type="add" issue="MATH-1014">
|
||||
Refactoring of curve fitters (package "o.a.c.m.fitting").
|
||||
</action>
|
||||
<action dev="tn" type="add" issue="MATH-970">
|
||||
Added possibility to retrieve the best found solution of the "SimplexSolver" in case
|
||||
the iteration limit has been reached. The "optimize(OptimizationData...)" method now
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* 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.fitting;
|
||||
|
||||
import java.util.Collection;
|
||||
import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
|
||||
import org.apache.commons.math3.exception.MathInternalError;
|
||||
import org.apache.commons.math3.fitting.leastsquares.LevenbergMarquardtOptimizer;
|
||||
import org.apache.commons.math3.fitting.leastsquares.WithStartPoint;
|
||||
import org.apache.commons.math3.fitting.leastsquares.WithMaxIterations;
|
||||
import org.apache.commons.math3.linear.DiagonalMatrix;
|
||||
|
||||
/**
|
||||
* Fits points to a {@link
|
||||
* org.apache.commons.math3.analysis.polynomials.PolynomialFunction.Parametric polynomial}
|
||||
* function.
|
||||
* <br/>
|
||||
* The size of the {@link #withStartPoint(double[]) initial guess} array defines the
|
||||
* degree of the polynomial to be fitted.
|
||||
* They must be sorted in increasing order of the polynomial's degree.
|
||||
* The optimal values of the coefficients will be returned in the same order.
|
||||
*
|
||||
* @version $Id$
|
||||
* @since 3.3
|
||||
*/
|
||||
public class PolynomialCurveFitter extends AbstractCurveFitter<LevenbergMarquardtOptimizer>
|
||||
implements WithStartPoint<PolynomialCurveFitter>,
|
||||
WithMaxIterations<PolynomialCurveFitter> {
|
||||
/** Parametric function to be fitted. */
|
||||
private static final PolynomialFunction.Parametric FUNCTION = new PolynomialFunction.Parametric();
|
||||
/** Initial guess. */
|
||||
private final double[] initialGuess;
|
||||
/** Maximum number of iterations of the optimization algorithm. */
|
||||
private final int maxIter;
|
||||
|
||||
/**
|
||||
* Contructor used by the factory methods.
|
||||
*
|
||||
* @param initialGuess Initial guess.
|
||||
* @param maxIter Maximum number of iterations of the optimization algorithm.
|
||||
* @throws MathInternalError if {@code initialGuess} is {@code null}.
|
||||
*/
|
||||
private PolynomialCurveFitter(double[] initialGuess,
|
||||
int maxIter) {
|
||||
this.initialGuess = initialGuess;
|
||||
this.maxIter = maxIter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default curve fitter.
|
||||
* Zero will be used as initial guess for the coefficients, and the maximum
|
||||
* number of iterations of the optimization algorithm is set to
|
||||
* {@link Integer#MAX_VALUE}.
|
||||
*
|
||||
* @param degree Degree of the polynomial to be fitted.
|
||||
* @return a curve fitter.
|
||||
*
|
||||
* @see #withStartPoint(double[])
|
||||
* @see #withMaxIterations(int)
|
||||
*/
|
||||
public static PolynomialCurveFitter create(int degree) {
|
||||
return new PolynomialCurveFitter(new double[degree + 1], Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public PolynomialCurveFitter withStartPoint(double[] start) {
|
||||
return new PolynomialCurveFitter(start.clone(),
|
||||
maxIter);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public PolynomialCurveFitter withMaxIterations(int max) {
|
||||
return new PolynomialCurveFitter(initialGuess,
|
||||
max);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
protected LevenbergMarquardtOptimizer getOptimizer(Collection<WeightedObservedPoint> observations) {
|
||||
// Prepare least-squares problem.
|
||||
final int len = observations.size();
|
||||
final double[] target = new double[len];
|
||||
final double[] weights = new double[len];
|
||||
|
||||
int i = 0;
|
||||
for (WeightedObservedPoint obs : observations) {
|
||||
target[i] = obs.getY();
|
||||
weights[i] = obs.getWeight();
|
||||
++i;
|
||||
}
|
||||
|
||||
final AbstractCurveFitter.TheoreticalValuesFunction model
|
||||
= new AbstractCurveFitter.TheoreticalValuesFunction(FUNCTION,
|
||||
observations);
|
||||
|
||||
if (initialGuess == null) {
|
||||
throw new MathInternalError();
|
||||
}
|
||||
|
||||
// Return a new optimizer set up to fit a Gaussian curve to the
|
||||
// observed points.
|
||||
return LevenbergMarquardtOptimizer.create()
|
||||
.withMaxEvaluations(Integer.MAX_VALUE)
|
||||
.withMaxIterations(maxIter)
|
||||
.withStartPoint(initialGuess)
|
||||
.withTarget(target)
|
||||
.withWeight(new DiagonalMatrix(weights))
|
||||
.withModelAndJacobian(model.getModelFunction(),
|
||||
model.getModelFunctionJacobian());
|
||||
}
|
||||
}
|
|
@ -26,7 +26,10 @@ import org.apache.commons.math3.optim.nonlinear.vector.MultivariateVectorOptimiz
|
|||
*
|
||||
* @version $Id: PolynomialFitter.java 1416643 2012-12-03 19:37:14Z tn $
|
||||
* @since 2.0
|
||||
* @deprecated As of 3.3. Please use {@link PolynomialCurveFitter} and
|
||||
* {@link WeightedObservedPoints} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class PolynomialFitter extends CurveFitter<PolynomialFunction.Parametric> {
|
||||
/**
|
||||
* Simple constructor.
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* 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.fitting;
|
||||
|
||||
import java.util.Random;
|
||||
import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
|
||||
import org.apache.commons.math3.exception.ConvergenceException;
|
||||
import org.apache.commons.math3.optim.nonlinear.vector.MultivariateVectorOptimizer;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.apache.commons.math3.distribution.RealDistribution;
|
||||
import org.apache.commons.math3.distribution.UniformRealDistribution;
|
||||
import org.apache.commons.math3.TestUtils;
|
||||
import org.junit.Test;
|
||||
import org.junit.Assert;
|
||||
|
||||
/**
|
||||
* Test for class {@link PolynomialCurveFitter}.
|
||||
*/
|
||||
public class PolynomialCurveFitterTest {
|
||||
@Test
|
||||
public void testFit() {
|
||||
final RealDistribution rng = new UniformRealDistribution(-100, 100);
|
||||
rng.reseedRandomGenerator(64925784252L);
|
||||
|
||||
final double[] coeff = { 12.9, -3.4, 2.1 }; // 12.9 - 3.4 x + 2.1 x^2
|
||||
final PolynomialFunction f = new PolynomialFunction(coeff);
|
||||
|
||||
// Collect data from a known polynomial.
|
||||
final WeightedObservedPoints obs = new WeightedObservedPoints();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
final double x = rng.sample();
|
||||
obs.add(x, f.value(x));
|
||||
}
|
||||
|
||||
// Start fit from initial guesses that are far from the optimal values.
|
||||
final PolynomialCurveFitter fitter
|
||||
= PolynomialCurveFitter.create(0).withStartPoint(new double[] { -1e-20, 3e15, -5e25 });
|
||||
final double[] best = fitter.fit(obs.toList());
|
||||
|
||||
TestUtils.assertEquals("best != coeff", coeff, best, 1e-12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoError() {
|
||||
final Random randomizer = new Random(64925784252l);
|
||||
for (int degree = 1; degree < 10; ++degree) {
|
||||
final PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
|
||||
final PolynomialCurveFitter fitter = PolynomialCurveFitter.create(degree);
|
||||
|
||||
final WeightedObservedPoints obs = new WeightedObservedPoints();
|
||||
for (int i = 0; i <= degree; ++i) {
|
||||
obs.add(1.0, i, p.value(i));
|
||||
}
|
||||
|
||||
final PolynomialFunction fitted = new PolynomialFunction(fitter.fit(obs.toList()));
|
||||
|
||||
for (double x = -1.0; x < 1.0; x += 0.01) {
|
||||
final double error = FastMath.abs(p.value(x) - fitted.value(x)) /
|
||||
(1.0 + FastMath.abs(p.value(x)));
|
||||
Assert.assertEquals(0.0, error, 1.0e-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallError() {
|
||||
final Random randomizer = new Random(53882150042l);
|
||||
double maxError = 0;
|
||||
for (int degree = 0; degree < 10; ++degree) {
|
||||
final PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
|
||||
final PolynomialCurveFitter fitter = PolynomialCurveFitter.create(degree);
|
||||
|
||||
final WeightedObservedPoints obs = new WeightedObservedPoints();
|
||||
for (double x = -1.0; x < 1.0; x += 0.01) {
|
||||
obs.add(1.0, x, p.value(x) + 0.1 * randomizer.nextGaussian());
|
||||
}
|
||||
|
||||
final PolynomialFunction fitted = new PolynomialFunction(fitter.fit(obs.toList()));
|
||||
|
||||
for (double x = -1.0; x < 1.0; x += 0.01) {
|
||||
final double error = FastMath.abs(p.value(x) - fitted.value(x)) /
|
||||
(1.0 + FastMath.abs(p.value(x)));
|
||||
maxError = FastMath.max(maxError, error);
|
||||
Assert.assertTrue(FastMath.abs(error) < 0.1);
|
||||
}
|
||||
}
|
||||
Assert.assertTrue(maxError > 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRedundantSolvable() {
|
||||
// Levenberg-Marquardt should handle redundant information gracefully
|
||||
checkUnsolvableProblem(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLargeSample() {
|
||||
final Random randomizer = new Random(0x5551480dca5b369bl);
|
||||
double maxError = 0;
|
||||
for (int degree = 0; degree < 10; ++degree) {
|
||||
final PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
|
||||
final PolynomialCurveFitter fitter = PolynomialCurveFitter.create(degree);
|
||||
|
||||
final WeightedObservedPoints obs = new WeightedObservedPoints();
|
||||
for (int i = 0; i < 40000; ++i) {
|
||||
final double x = -1.0 + i / 20000.0;
|
||||
obs.add(1.0, x, p.value(x) + 0.1 * randomizer.nextGaussian());
|
||||
}
|
||||
|
||||
final PolynomialFunction fitted = new PolynomialFunction(fitter.fit(obs.toList()));
|
||||
for (double x = -1.0; x < 1.0; x += 0.01) {
|
||||
final double error = FastMath.abs(p.value(x) - fitted.value(x)) /
|
||||
(1.0 + FastMath.abs(p.value(x)));
|
||||
maxError = FastMath.max(maxError, error);
|
||||
Assert.assertTrue(FastMath.abs(error) < 0.01);
|
||||
}
|
||||
}
|
||||
Assert.assertTrue(maxError > 0.001);
|
||||
}
|
||||
|
||||
private void checkUnsolvableProblem(boolean solvable) {
|
||||
final Random randomizer = new Random(1248788532l);
|
||||
|
||||
for (int degree = 0; degree < 10; ++degree) {
|
||||
final PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
|
||||
final PolynomialCurveFitter fitter = PolynomialCurveFitter.create(degree);
|
||||
final WeightedObservedPoints obs = new WeightedObservedPoints();
|
||||
// reusing the same point over and over again does not bring
|
||||
// information, the problem cannot be solved in this case for
|
||||
// degrees greater than 1 (but one point is sufficient for
|
||||
// degree 0)
|
||||
for (double x = -1.0; x < 1.0; x += 0.01) {
|
||||
obs.add(1.0, 0.0, p.value(0.0));
|
||||
}
|
||||
|
||||
try {
|
||||
fitter.fit(obs.toList());
|
||||
Assert.assertTrue(solvable || (degree == 0));
|
||||
} catch(ConvergenceException e) {
|
||||
Assert.assertTrue((! solvable) && (degree > 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PolynomialFunction buildRandomPolynomial(int degree, Random randomizer) {
|
||||
final double[] coefficients = new double[degree + 1];
|
||||
for (int i = 0; i <= degree; ++i) {
|
||||
coefficients[i] = randomizer.nextGaussian();
|
||||
}
|
||||
return new PolynomialFunction(coefficients);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue