View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.fitting;
23  
24  import org.hipparchus.UnitTestUtils;
25  import org.hipparchus.analysis.polynomials.PolynomialFunction;
26  import org.hipparchus.exception.MathIllegalStateException;
27  import org.hipparchus.random.RandomDataGenerator;
28  import org.hipparchus.util.FastMath;
29  import org.junit.jupiter.api.Test;
30  
31  import java.util.Random;
32  
33  import static org.junit.jupiter.api.Assertions.assertEquals;
34  import static org.junit.jupiter.api.Assertions.assertTrue;
35  
36  /**
37   * Test for class {@link PolynomialCurveFitter}.
38   */
39  class PolynomialCurveFitterTest {
40      @Test
41      void testFit() {
42          final RandomDataGenerator randomDataGenerator = new RandomDataGenerator(64925784252L);
43  
44          final double[] coeff = { 12.9, -3.4, 2.1 }; // 12.9 - 3.4 x + 2.1 x^2
45          final PolynomialFunction f = new PolynomialFunction(coeff);
46  
47          // Collect data from a known polynomial.
48          final WeightedObservedPoints obs = new WeightedObservedPoints();
49          for (int i = 0; i < 100; i++) {
50              final double x = randomDataGenerator.nextUniform(-100, 100);
51              obs.add(x, f.value(x));
52          }
53  
54          // Start fit from initial guesses that are far from the optimal values.
55          final PolynomialCurveFitter fitter
56              = PolynomialCurveFitter.create(0).withStartPoint(new double[] { -1e-20, 3e15, -5e25 });
57          final double[] best = fitter.fit(obs.toList());
58  
59          UnitTestUtils.customAssertEquals("best != coeff", coeff, best, 1e-12);
60      }
61  
62      @Test
63      void testNoError() {
64          final Random randomizer = new Random(64925784252l);
65          for (int degree = 1; degree < 10; ++degree) {
66              final PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
67              final PolynomialCurveFitter fitter = PolynomialCurveFitter.create(degree);
68  
69              final WeightedObservedPoints obs = new WeightedObservedPoints();
70              for (int i = 0; i <= degree; ++i) {
71                  obs.add(1.0, i, p.value(i));
72              }
73  
74              final PolynomialFunction fitted = new PolynomialFunction(fitter.fit(obs.toList()));
75  
76              for (double x = -1.0; x < 1.0; x += 0.01) {
77                  final double error = FastMath.abs(p.value(x) - fitted.value(x)) /
78                      (1.0 + FastMath.abs(p.value(x)));
79                  assertEquals(0.0, error, 1.0e-6);
80              }
81          }
82      }
83  
84      @Test
85      void testSmallError() {
86          final Random randomizer = new Random(53882150042l);
87          double maxError = 0;
88          for (int degree = 0; degree < 10; ++degree) {
89              final PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
90              final PolynomialCurveFitter fitter = PolynomialCurveFitter.create(degree);
91  
92              final WeightedObservedPoints obs = new WeightedObservedPoints();
93              for (double x = -1.0; x < 1.0; x += 0.01) {
94                  obs.add(1.0, x, p.value(x) + 0.1 * randomizer.nextGaussian());
95              }
96  
97              final PolynomialFunction fitted = new PolynomialFunction(fitter.fit(obs.toList()));
98  
99              for (double x = -1.0; x < 1.0; x += 0.01) {
100                 final double error = FastMath.abs(p.value(x) - fitted.value(x)) /
101                     (1.0 + FastMath.abs(p.value(x)));
102                 maxError = FastMath.max(maxError, error);
103                 assertTrue(FastMath.abs(error) < 0.1);
104             }
105         }
106         assertTrue(maxError > 0.01);
107     }
108 
109     @Test
110     void testRedundantSolvable() {
111         // Levenberg-Marquardt should handle redundant information gracefully
112         checkUnsolvableProblem(true);
113     }
114 
115     @Test
116     void testLargeSample() {
117         final Random randomizer = new Random(0x5551480dca5b369bl);
118         double maxError = 0;
119         for (int degree = 0; degree < 10; ++degree) {
120             final PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
121             final PolynomialCurveFitter fitter = PolynomialCurveFitter.create(degree);
122 
123             final WeightedObservedPoints obs = new WeightedObservedPoints();
124             for (int i = 0; i < 40000; ++i) {
125                 final double x = -1.0 + i / 20000.0;
126                 obs.add(1.0, x, p.value(x) + 0.1 * randomizer.nextGaussian());
127             }
128 
129             final PolynomialFunction fitted = new PolynomialFunction(fitter.fit(obs.toList()));
130             for (double x = -1.0; x < 1.0; x += 0.01) {
131                 final double error = FastMath.abs(p.value(x) - fitted.value(x)) /
132                     (1.0 + FastMath.abs(p.value(x)));
133                 maxError = FastMath.max(maxError, error);
134                 assertTrue(FastMath.abs(error) < 0.01);
135             }
136         }
137         assertTrue(maxError > 0.001);
138     }
139 
140     private void checkUnsolvableProblem(boolean solvable) {
141         final Random randomizer = new Random(1248788532l);
142 
143         for (int degree = 0; degree < 10; ++degree) {
144             final PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
145             final PolynomialCurveFitter fitter = PolynomialCurveFitter.create(degree);
146             final WeightedObservedPoints obs = new WeightedObservedPoints();
147             // reusing the same point over and over again does not bring
148             // information, the problem cannot be solved in this case for
149             // degrees greater than 1 (but one point is sufficient for
150             // degree 0)
151             for (double x = -1.0; x < 1.0; x += 0.01) {
152                 obs.add(1.0, 0.0, p.value(0.0));
153             }
154 
155             try {
156                 fitter.fit(obs.toList());
157                 assertTrue(solvable || (degree == 0));
158             } catch(MathIllegalStateException e) {
159                 assertTrue((! solvable) && (degree > 0));
160             }
161         }
162     }
163 
164     private PolynomialFunction buildRandomPolynomial(int degree, Random randomizer) {
165         final double[] coefficients = new double[degree + 1];
166         for (int i = 0; i <= degree; ++i) {
167             coefficients[i] = randomizer.nextGaussian();
168         }
169         return new PolynomialFunction(coefficients);
170     }
171 }