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    *      http://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  package org.apache.commons.math.analysis.interpolation;
18  
19  import static org.junit.Assert.assertEquals;
20  import static org.junit.Assert.assertTrue;
21  import static org.junit.Assert.fail;
22  
23  import org.apache.commons.math.MathException;
24  import org.junit.Test;
25  
26  /**
27   * Test of the LoessInterpolator class.
28   */
29  public class LoessInterpolatorTest {
30  
31      @Test
32      public void testOnOnePoint() throws MathException {
33          double[] xval = {0.5};
34          double[] yval = {0.7};
35          double[] res = new LoessInterpolator().smooth(xval, yval);
36          assertEquals(1, res.length);
37          assertEquals(0.7, res[0], 0.0);
38      }
39  
40      @Test
41      public void testOnTwoPoints() throws MathException {
42          double[] xval = {0.5, 0.6};
43          double[] yval = {0.7, 0.8};
44          double[] res = new LoessInterpolator().smooth(xval, yval);
45          assertEquals(2, res.length);
46          assertEquals(0.7, res[0], 0.0);
47          assertEquals(0.8, res[1], 0.0);
48      }
49  
50      @Test
51      public void testOnStraightLine() throws MathException {
52          double[] xval = {1,2,3,4,5};
53          double[] yval = {2,4,6,8,10};
54          LoessInterpolator li = new LoessInterpolator(0.6, 2);
55          double[] res = li.smooth(xval, yval);
56          assertEquals(5, res.length);
57          for(int i = 0; i < 5; ++i) {
58              assertEquals(yval[i], res[i], 1e-8);
59          }
60      }
61  
62      @Test
63      public void testOnDistortedSine() throws MathException {
64          int numPoints = 100;
65          double[] xval = new double[numPoints];
66          double[] yval = new double[numPoints];
67          double xnoise = 0.1;
68          double ynoise = 0.2;
69  
70          generateSineData(xval, yval, xnoise, ynoise);
71  
72          LoessInterpolator li = new LoessInterpolator(0.3, 4);
73  
74          double[] res = li.smooth(xval, yval);
75  
76          // Check that the resulting curve differs from
77          // the "real" sine less than the jittered one
78  
79          double noisyResidualSum = 0;
80          double fitResidualSum = 0;
81  
82          for(int i = 0; i < numPoints; ++i) {
83              double expected = Math.sin(xval[i]);
84              double noisy = yval[i];
85              double fit = res[i];
86  
87              noisyResidualSum += Math.pow(noisy - expected, 2);
88              fitResidualSum += Math.pow(fit - expected, 2);
89          }
90  
91          assertTrue(fitResidualSum < noisyResidualSum);
92      }
93  
94      @Test
95      public void testIncreasingBandwidthIncreasesSmoothness() throws MathException {
96          int numPoints = 100;
97          double[] xval = new double[numPoints];
98          double[] yval = new double[numPoints];
99          double xnoise = 0.1;
100         double ynoise = 0.1;
101 
102         generateSineData(xval, yval, xnoise, ynoise);
103 
104         // Check that variance decreases as bandwidth increases
105 
106         double[] bandwidths = {0.1, 0.5, 1.0};
107         double[] variances = new double[bandwidths.length];
108         for (int i = 0; i < bandwidths.length; i++) {
109             double bw = bandwidths[i];
110 
111             LoessInterpolator li = new LoessInterpolator(bw, 4);
112 
113             double[] res = li.smooth(xval, yval);
114 
115             for (int j = 1; j < res.length; ++j) {
116                 variances[i] += Math.pow(res[j] - res[j-1], 2);
117             }
118         }
119 
120         for(int i = 1; i < variances.length; ++i) {
121             assertTrue(variances[i] < variances[i-1]);
122         }
123     }
124 
125     @Test
126     public void testIncreasingRobustnessItersIncreasesSmoothnessWithOutliers() throws MathException {
127         int numPoints = 100;
128         double[] xval = new double[numPoints];
129         double[] yval = new double[numPoints];
130         double xnoise = 0.1;
131         double ynoise = 0.1;
132 
133         generateSineData(xval, yval, xnoise, ynoise);
134 
135         // Introduce a couple of outliers
136         yval[numPoints/3] *= 100;
137         yval[2 * numPoints/3] *= -100;
138 
139         // Check that variance decreases as the number of robustness
140         // iterations increases
141 
142         double[] variances = new double[4];
143         for (int i = 0; i < 4; i++) {
144             LoessInterpolator li = new LoessInterpolator(0.3, i);
145 
146             double[] res = li.smooth(xval, yval);
147 
148             for (int j = 1; j < res.length; ++j) {
149                 variances[i] += Math.abs(res[j] - res[j-1]);
150             }
151         }
152 
153         for(int i = 1; i < variances.length; ++i) {
154             assertTrue(variances[i] < variances[i-1]);
155         }
156     }
157 
158     @Test
159     public void testUnequalSizeArguments() {
160         try {
161             new LoessInterpolator().smooth(new double[] {1,2,3}, new double[] {1,2,3,4});
162             fail();
163         } catch(MathException e) {
164             // Expected
165         }
166     }
167 
168     @Test
169     public void testEmptyData() {
170         try {
171             new LoessInterpolator().smooth(new double[] {}, new double[] {});
172             fail();
173         } catch(MathException e) {
174             // Expected
175         }
176     }
177 
178     @Test
179     public void testNonStrictlyIncreasing() {
180         try {
181             new LoessInterpolator().smooth(new double[] {4,3,1,2}, new double[] {3,4,5,6});
182             fail();
183         } catch(MathException e) {
184             // Expected
185         }
186         try {
187             new LoessInterpolator().smooth(new double[] {1,2,2,3}, new double[] {3,4,5,6});
188             fail();
189         } catch(MathException e) {
190             // Expected
191         }
192     }
193 
194     @Test
195     public void testNotAllFiniteReal() {
196         try {
197             new LoessInterpolator().smooth(new double[] {1,2,Double.NaN}, new double[] {3,4,5});
198             fail();
199         } catch(MathException e) {
200             // Expected
201         }
202         try {
203             new LoessInterpolator().smooth(new double[] {1,2,Double.POSITIVE_INFINITY}, new double[] {3,4,5});
204             fail();
205         } catch(MathException e) {
206             // Expected
207         }
208         try {
209             new LoessInterpolator().smooth(new double[] {1,2,Double.NEGATIVE_INFINITY}, new double[] {3,4,5});
210             fail();
211         } catch(MathException e) {
212             // Expected
213         }
214         try {
215             new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.NaN});
216             fail();
217         } catch(MathException e) {
218             // Expected
219         }
220         try {
221             new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.POSITIVE_INFINITY});
222             fail();
223         } catch(MathException e) {
224             // Expected
225         }
226         try {
227             new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.NEGATIVE_INFINITY});
228             fail();
229         } catch(MathException e) {
230             // Expected
231         }
232     }
233 
234     @Test
235     public void testInsufficientBandwidth() {
236         try {
237             LoessInterpolator li = new LoessInterpolator(0.1, 3);
238             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});
239             fail();
240         } catch(MathException e) {
241             // Expected
242         }
243     }
244 
245     @Test
246     public void testCompletelyIncorrectBandwidth() {
247         try {
248             new LoessInterpolator(-0.2, 3);
249             fail();
250         } catch(MathException e) {
251             // Expected
252         }
253         try {
254             new LoessInterpolator(1.1, 3);
255             fail();
256         } catch(MathException e) {
257             // Expected
258         }
259     }
260 
261     private void generateSineData(double[] xval, double[] yval, double xnoise, double ynoise) {
262         double dx = 2 * Math.PI / xval.length;
263         double x = 0;
264         for(int i = 0; i < xval.length; ++i) {
265             xval[i] = x;
266             yval[i] = Math.sin(x) + (2 * Math.random() - 1) * ynoise;
267             x += dx * (1 + (2 * Math.random() - 1) * xnoise);
268         }
269     }
270 
271 }