You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by er...@apache.org on 2013/11/20 19:57:53 UTC

svn commit: r1543906 - in /commons/proper/math/trunk/src: changes/ main/java/org/apache/commons/math3/fitting/ test/java/org/apache/commons/math3/fitting/

Author: erans
Date: Wed Nov 20 18:57:53 2013
New Revision: 1543906

URL: http://svn.apache.org/r1543906
Log:
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).

Added:
    commons/proper/math/trunk/src/main/java/org/apache/commons/math3/fitting/PolynomialCurveFitter.java   (with props)
    commons/proper/math/trunk/src/test/java/org/apache/commons/math3/fitting/PolynomialCurveFitterTest.java   (with props)
Modified:
    commons/proper/math/trunk/src/changes/changes.xml
    commons/proper/math/trunk/src/main/java/org/apache/commons/math3/fitting/PolynomialFitter.java

Modified: commons/proper/math/trunk/src/changes/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/changes/changes.xml?rev=1543906&r1=1543905&r2=1543906&view=diff
==============================================================================
--- commons/proper/math/trunk/src/changes/changes.xml (original)
+++ commons/proper/math/trunk/src/changes/changes.xml Wed Nov 20 18:57:53 2013
@@ -51,6 +51,9 @@ If the output is not quite correct, chec
   </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

Added: commons/proper/math/trunk/src/main/java/org/apache/commons/math3/fitting/PolynomialCurveFitter.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math3/fitting/PolynomialCurveFitter.java?rev=1543906&view=auto
==============================================================================
--- commons/proper/math/trunk/src/main/java/org/apache/commons/math3/fitting/PolynomialCurveFitter.java (added)
+++ commons/proper/math/trunk/src/main/java/org/apache/commons/math3/fitting/PolynomialCurveFitter.java Wed Nov 20 18:57:53 2013
@@ -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());
+    }
+}

Propchange: commons/proper/math/trunk/src/main/java/org/apache/commons/math3/fitting/PolynomialCurveFitter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/math/trunk/src/main/java/org/apache/commons/math3/fitting/PolynomialCurveFitter.java
------------------------------------------------------------------------------
    svn:keywords = Id Revision

Modified: commons/proper/math/trunk/src/main/java/org/apache/commons/math3/fitting/PolynomialFitter.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math3/fitting/PolynomialFitter.java?rev=1543906&r1=1543905&r2=1543906&view=diff
==============================================================================
--- commons/proper/math/trunk/src/main/java/org/apache/commons/math3/fitting/PolynomialFitter.java (original)
+++ commons/proper/math/trunk/src/main/java/org/apache/commons/math3/fitting/PolynomialFitter.java Wed Nov 20 18:57:53 2013
@@ -26,7 +26,10 @@ import org.apache.commons.math3.optim.no
  *
  * @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.

Added: commons/proper/math/trunk/src/test/java/org/apache/commons/math3/fitting/PolynomialCurveFitterTest.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/java/org/apache/commons/math3/fitting/PolynomialCurveFitterTest.java?rev=1543906&view=auto
==============================================================================
--- commons/proper/math/trunk/src/test/java/org/apache/commons/math3/fitting/PolynomialCurveFitterTest.java (added)
+++ commons/proper/math/trunk/src/test/java/org/apache/commons/math3/fitting/PolynomialCurveFitterTest.java Wed Nov 20 18:57:53 2013
@@ -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);
+    }
+}

Propchange: commons/proper/math/trunk/src/test/java/org/apache/commons/math3/fitting/PolynomialCurveFitterTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/math/trunk/src/test/java/org/apache/commons/math3/fitting/PolynomialCurveFitterTest.java
------------------------------------------------------------------------------
    svn:keywords = Id Revision