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.analysis.interpolation;
23  
24  import org.hipparchus.exception.LocalizedCoreFormats;
25  import org.hipparchus.exception.MathIllegalArgumentException;
26  import org.hipparchus.util.FastMath;
27  import org.junit.jupiter.api.Test;
28  
29  import static org.junit.jupiter.api.Assertions.assertEquals;
30  import static org.junit.jupiter.api.Assertions.assertThrows;
31  import static org.junit.jupiter.api.Assertions.assertTrue;
32  import static org.junit.jupiter.api.Assertions.fail;
33  
34  /**
35   * Test of the LoessInterpolator class.
36   */
37  class LoessInterpolatorTest {
38  
39      @Test
40      void testOnOnePoint() {
41          double[] xval = {0.5};
42          double[] yval = {0.7};
43          double[] res = new LoessInterpolator().smooth(xval, yval);
44          assertEquals(1, res.length);
45          assertEquals(0.7, res[0], 0.0);
46      }
47  
48      @Test
49      void testOnTwoPoints() {
50          double[] xval = {0.5, 0.6};
51          double[] yval = {0.7, 0.8};
52          double[] res = new LoessInterpolator().smooth(xval, yval);
53          assertEquals(2, res.length);
54          assertEquals(0.7, res[0], 0.0);
55          assertEquals(0.8, res[1], 0.0);
56      }
57  
58      @Test
59      void testOnStraightLine() {
60          double[] xval = {1,2,3,4,5};
61          double[] yval = {2,4,6,8,10};
62          LoessInterpolator li = new LoessInterpolator(0.6, 2, 1e-12);
63          double[] res = li.smooth(xval, yval);
64          assertEquals(5, res.length);
65          for(int i = 0; i < 5; ++i) {
66              assertEquals(yval[i], res[i], 1e-8);
67          }
68      }
69  
70      @Test
71      void testOnDistortedSine() {
72          int numPoints = 100;
73          double[] xval = new double[numPoints];
74          double[] yval = new double[numPoints];
75          double xnoise = 0.1;
76          double ynoise = 0.2;
77  
78          generateSineData(xval, yval, xnoise, ynoise);
79  
80          LoessInterpolator li = new LoessInterpolator(0.3, 4, 1e-12);
81  
82          double[] res = li.smooth(xval, yval);
83  
84          // Check that the resulting curve differs from
85          // the "real" sine less than the jittered one
86  
87          double noisyResidualSum = 0;
88          double fitResidualSum = 0;
89  
90          for(int i = 0; i < numPoints; ++i) {
91              double expected = FastMath.sin(xval[i]);
92              double noisy = yval[i];
93              double fit = res[i];
94  
95              noisyResidualSum += FastMath.pow(noisy - expected, 2);
96              fitResidualSum += FastMath.pow(fit - expected, 2);
97          }
98  
99          assertTrue(fitResidualSum < noisyResidualSum);
100     }
101 
102     @Test
103     void testIncreasingBandwidthIncreasesSmoothness() {
104         int numPoints = 100;
105         double[] xval = new double[numPoints];
106         double[] yval = new double[numPoints];
107         double xnoise = 0.1;
108         double ynoise = 0.1;
109 
110         generateSineData(xval, yval, xnoise, ynoise);
111 
112         // Check that variance decreases as bandwidth increases
113 
114         double[] bandwidths = {0.1, 0.5, 1.0};
115         double[] variances = new double[bandwidths.length];
116         for (int i = 0; i < bandwidths.length; i++) {
117             double bw = bandwidths[i];
118 
119             LoessInterpolator li = new LoessInterpolator(bw, 4, 1e-12);
120 
121             double[] res = li.smooth(xval, yval);
122 
123             for (int j = 1; j < res.length; ++j) {
124                 variances[i] += FastMath.pow(res[j] - res[j-1], 2);
125             }
126         }
127 
128         for(int i = 1; i < variances.length; ++i) {
129             assertTrue(variances[i] < variances[i-1]);
130         }
131     }
132 
133     @Test
134     void testIncreasingRobustnessItersIncreasesSmoothnessWithOutliers() {
135         int numPoints = 100;
136         double[] xval = new double[numPoints];
137         double[] yval = new double[numPoints];
138         double xnoise = 0.1;
139         double ynoise = 0.1;
140 
141         generateSineData(xval, yval, xnoise, ynoise);
142 
143         // Introduce a couple of outliers
144         yval[numPoints/3] *= 100;
145         yval[2 * numPoints/3] *= -100;
146 
147         // Check that variance decreases as the number of robustness
148         // iterations increases
149 
150         double[] variances = new double[4];
151         for (int i = 0; i < 4; i++) {
152             LoessInterpolator li = new LoessInterpolator(0.3, i, 1e-12);
153 
154             double[] res = li.smooth(xval, yval);
155 
156             for (int j = 1; j < res.length; ++j) {
157                 variances[i] += FastMath.abs(res[j] - res[j-1]);
158             }
159         }
160 
161         for(int i = 1; i < variances.length; ++i) {
162             assertTrue(variances[i] < variances[i-1]);
163         }
164     }
165 
166     @Test
167     void testUnequalSizeArguments() {
168         assertThrows(MathIllegalArgumentException.class, () -> {
169             new LoessInterpolator().smooth(new double[]{1, 2, 3}, new double[]{1, 2, 3, 4});
170         });
171     }
172 
173     @Test
174     void testEmptyData() {
175         assertThrows(MathIllegalArgumentException.class, () -> {
176             new LoessInterpolator().smooth(new double[]{}, new double[]{});
177         });
178     }
179 
180     @Test
181     void testNonStrictlyIncreasing1() {
182         assertThrows(MathIllegalArgumentException.class, () -> {
183             new LoessInterpolator().smooth(new double[]{4, 3, 1, 2}, new double[]{3, 4, 5, 6});
184         });
185     }
186 
187     @Test
188     void testNonStrictlyIncreasing2() {
189         assertThrows(MathIllegalArgumentException.class, () -> {
190             new LoessInterpolator().smooth(new double[]{1, 2, 2, 3}, new double[]{3, 4, 5, 6});
191         });
192     }
193 
194     @Test
195     void testNotAllFiniteReal1() {
196         try {
197             new LoessInterpolator().smooth(new double[] {1,2,Double.NaN}, new double[] {3,4,5});
198             fail("an exception should have been thrown");
199         } catch (MathIllegalArgumentException e) {
200             assertEquals(LocalizedCoreFormats.NOT_FINITE_NUMBER, e.getSpecifier());
201         }
202     }
203 
204     @Test
205     void testNotAllFiniteReal2() {
206         try {
207             new LoessInterpolator().smooth(new double[] {1,2,Double.POSITIVE_INFINITY}, new double[] {3,4,5});
208         } catch (MathIllegalArgumentException e) {
209             assertEquals(LocalizedCoreFormats.NOT_FINITE_NUMBER, e.getSpecifier());
210         }
211     }
212 
213     @Test
214     void testNotAllFiniteReal3() {
215         try {
216             new LoessInterpolator().smooth(new double[] {1,2,Double.NEGATIVE_INFINITY}, new double[] {3,4,5});
217         } catch (MathIllegalArgumentException e) {
218             assertEquals(LocalizedCoreFormats.NOT_FINITE_NUMBER, e.getSpecifier());
219         }
220     }
221 
222     @Test
223     void testNotAllFiniteReal4() {
224         try {
225             new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.NaN});
226         } catch (MathIllegalArgumentException e) {
227             assertEquals(LocalizedCoreFormats.NOT_FINITE_NUMBER, e.getSpecifier());
228         }
229     }
230 
231     @Test
232     void testNotAllFiniteReal5() {
233         try {
234             new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.POSITIVE_INFINITY});
235         } catch (MathIllegalArgumentException e) {
236             assertEquals(LocalizedCoreFormats.NOT_FINITE_NUMBER, e.getSpecifier());
237         }
238     }
239 
240     @Test
241     void testNotAllFiniteReal6() {
242         try {
243             new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.NEGATIVE_INFINITY});
244         } catch (MathIllegalArgumentException e) {
245             assertEquals(LocalizedCoreFormats.NOT_FINITE_NUMBER, e.getSpecifier());
246         }
247     }
248 
249     @Test
250     void testInsufficientBandwidth() {
251         assertThrows(MathIllegalArgumentException.class, () -> {
252             LoessInterpolator li = new LoessInterpolator(0.1, 3, 1e-12);
253             li.smooth(new double[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, new double[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
254         });
255     }
256 
257     @Test
258     void testCompletelyIncorrectBandwidth1() {
259         assertThrows(MathIllegalArgumentException.class, () -> {
260             new LoessInterpolator(-0.2, 3, 1e-12);
261         });
262     }
263 
264     @Test
265     void testCompletelyIncorrectBandwidth2() {
266         assertThrows(MathIllegalArgumentException.class, () -> {
267             new LoessInterpolator(1.1, 3, 1e-12);
268         });
269     }
270 
271     @Test
272     void testMath296withoutWeights() {
273         double[] xval = {
274                 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
275                  1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0};
276         double[] yval = {
277                 0.47, 0.48, 0.55, 0.56, -0.08, -0.04, -0.07, -0.07,
278                 -0.56, -0.46, -0.56, -0.52, -3.03, -3.08, -3.09,
279                 -3.04, 3.54, 3.46, 3.36, 3.35};
280         // Output from R, rounded to .001
281         double[] yref = {
282                 0.461, 0.499, 0.541, 0.308, 0.175, -0.042, -0.072,
283                 -0.196, -0.311, -0.446, -0.557, -1.497, -2.133,
284                 -3.08, -3.09, -0.621, 0.982, 3.449, 3.389, 3.336
285         };
286         LoessInterpolator li = new LoessInterpolator(0.3, 4, 1e-12);
287         double[] res = li.smooth(xval, yval);
288         assertEquals(xval.length, res.length);
289         for(int i = 0; i < res.length; ++i) {
290             assertEquals(yref[i], res[i], 0.02);
291         }
292     }
293 
294     private void generateSineData(double[] xval, double[] yval, double xnoise, double ynoise) {
295         double dx = 2 * FastMath.PI / xval.length;
296         double x = 0;
297         for(int i = 0; i < xval.length; ++i) {
298             xval[i] = x;
299             yval[i] = FastMath.sin(x) + (2 * FastMath.random() - 1) * ynoise;
300             x += dx * (1 + (2 * FastMath.random() - 1) * xnoise);
301         }
302     }
303 }