001    // Licensed to the Apache Software Foundation (ASF) under one
002    // or more contributor license agreements.  See the NOTICE file
003    // distributed with this work for additional information
004    // regarding copyright ownership.  The ASF licenses this file
005    // to you under the Apache License, Version 2.0 (the
006    // "License"); you may not use this file except in compliance
007    // with the License.  You may obtain a copy of the License at
008    // 
009    //   http://www.apache.org/licenses/LICENSE-2.0
010    // 
011    // Unless required by applicable law or agreed to in writing,
012    // software distributed under the License is distributed on an
013    // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
014    // KIND, either express or implied.  See the License for the
015    // specific language governing permissions and limitations
016    // under the License.
017    
018    package org.apache.commons.math.optimization.fitting;
019    
020    import static org.junit.Assert.assertEquals;
021    import static org.junit.Assert.assertTrue;
022    
023    import java.util.Random;
024    
025    import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
026    import org.apache.commons.math.optimization.DifferentiableMultivariateVectorialOptimizer;
027    import org.apache.commons.math.optimization.OptimizationException;
028    import org.apache.commons.math.optimization.general.GaussNewtonOptimizer;
029    import org.apache.commons.math.optimization.general.LevenbergMarquardtOptimizer;
030    import org.junit.Test;
031    
032    public class PolynomialFitterTest {
033    
034        @Test
035        public void testNoError() throws OptimizationException {
036            Random randomizer = new Random(64925784252l);
037            for (int degree = 1; degree < 10; ++degree) {
038                PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
039    
040                PolynomialFitter fitter =
041                    new PolynomialFitter(degree, new LevenbergMarquardtOptimizer());
042                for (int i = 0; i <= degree; ++i) {
043                    fitter.addObservedPoint(1.0, i, p.value(i));
044                }
045    
046                PolynomialFunction fitted = fitter.fit();
047    
048                for (double x = -1.0; x < 1.0; x += 0.01) {
049                    double error = Math.abs(p.value(x) - fitted.value(x)) /
050                                   (1.0 + Math.abs(p.value(x)));
051                    assertEquals(0.0, error, 1.0e-6);
052                }
053    
054            }
055    
056        }
057    
058        @Test
059        public void testSmallError() throws OptimizationException {
060            Random randomizer = new Random(53882150042l);
061            double maxError = 0;
062            for (int degree = 0; degree < 10; ++degree) {
063                PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
064    
065                PolynomialFitter fitter =
066                    new PolynomialFitter(degree, new LevenbergMarquardtOptimizer());
067                for (double x = -1.0; x < 1.0; x += 0.01) {
068                    fitter.addObservedPoint(1.0, x,
069                                            p.value(x) + 0.1 * randomizer.nextGaussian());
070                }
071    
072                PolynomialFunction fitted = fitter.fit();
073    
074                for (double x = -1.0; x < 1.0; x += 0.01) {
075                    double error = Math.abs(p.value(x) - fitted.value(x)) /
076                                  (1.0 + Math.abs(p.value(x)));
077                    maxError = Math.max(maxError, error);
078                    assertTrue(Math.abs(error) < 0.1);
079                }
080            }
081            assertTrue(maxError > 0.01);
082    
083        }
084    
085        @Test
086        public void testRedundantSolvable() {
087            // Levenberg-Marquardt should handle redundant information gracefully
088            checkUnsolvableProblem(new LevenbergMarquardtOptimizer(), true);
089        }
090    
091        @Test
092        public void testRedundantUnsolvable() {
093            // Gauss-Newton should not be able to solve redundant information
094            DifferentiableMultivariateVectorialOptimizer optimizer =
095                new GaussNewtonOptimizer(true);
096            checkUnsolvableProblem(optimizer, false);
097        }
098    
099        private void checkUnsolvableProblem(DifferentiableMultivariateVectorialOptimizer optimizer,
100                                            boolean solvable) {
101            Random randomizer = new Random(1248788532l);
102            for (int degree = 0; degree < 10; ++degree) {
103                PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
104    
105                PolynomialFitter fitter = new PolynomialFitter(degree, optimizer);
106    
107                // reusing the same point over and over again does not bring
108                // information, the problem cannot be solved in this case for
109                // degrees greater than 1 (but one point is sufficient for
110                // degree 0)
111                for (double x = -1.0; x < 1.0; x += 0.01) {
112                    fitter.addObservedPoint(1.0, 0.0, p.value(0.0));
113                }
114    
115                try {
116                    fitter.fit();
117                    assertTrue(solvable || (degree == 0));
118                } catch(OptimizationException e) {
119                    assertTrue((! solvable) && (degree > 0));
120                }
121    
122            }
123    
124        }
125    
126        private PolynomialFunction buildRandomPolynomial(int degree, Random randomizer) {
127            final double[] coefficients = new double[degree + 1];
128            for (int i = 0; i <= degree; ++i) {
129                coefficients[i] = randomizer.nextGaussian();
130            }
131            return new PolynomialFunction(coefficients);
132        }
133    
134    }