001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * 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, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.math.analysis.interpolation;
018    
019    import static org.junit.Assert.assertEquals;
020    import static org.junit.Assert.assertTrue;
021    import static org.junit.Assert.fail;
022    
023    import org.apache.commons.math.MathException;
024    import org.junit.Test;
025    
026    /**
027     * Test of the LoessInterpolator class.
028     */
029    public class LoessInterpolatorTest {
030    
031        @Test
032        public void testOnOnePoint() throws MathException {
033            double[] xval = {0.5};
034            double[] yval = {0.7};
035            double[] res = new LoessInterpolator().smooth(xval, yval);
036            assertEquals(1, res.length);
037            assertEquals(0.7, res[0], 0.0);
038        }
039    
040        @Test
041        public void testOnTwoPoints() throws MathException {
042            double[] xval = {0.5, 0.6};
043            double[] yval = {0.7, 0.8};
044            double[] res = new LoessInterpolator().smooth(xval, yval);
045            assertEquals(2, res.length);
046            assertEquals(0.7, res[0], 0.0);
047            assertEquals(0.8, res[1], 0.0);
048        }
049    
050        @Test
051        public void testOnStraightLine() throws MathException {
052            double[] xval = {1,2,3,4,5};
053            double[] yval = {2,4,6,8,10};
054            LoessInterpolator li = new LoessInterpolator(0.6, 2);
055            double[] res = li.smooth(xval, yval);
056            assertEquals(5, res.length);
057            for(int i = 0; i < 5; ++i) {
058                assertEquals(yval[i], res[i], 1e-8);
059            }
060        }
061    
062        @Test
063        public void testOnDistortedSine() throws MathException {
064            int numPoints = 100;
065            double[] xval = new double[numPoints];
066            double[] yval = new double[numPoints];
067            double xnoise = 0.1;
068            double ynoise = 0.2;
069    
070            generateSineData(xval, yval, xnoise, ynoise);
071    
072            LoessInterpolator li = new LoessInterpolator(0.3, 4);
073    
074            double[] res = li.smooth(xval, yval);
075    
076            // Check that the resulting curve differs from
077            // the "real" sine less than the jittered one
078    
079            double noisyResidualSum = 0;
080            double fitResidualSum = 0;
081    
082            for(int i = 0; i < numPoints; ++i) {
083                double expected = Math.sin(xval[i]);
084                double noisy = yval[i];
085                double fit = res[i];
086    
087                noisyResidualSum += Math.pow(noisy - expected, 2);
088                fitResidualSum += Math.pow(fit - expected, 2);
089            }
090    
091            assertTrue(fitResidualSum < noisyResidualSum);
092        }
093    
094        @Test
095        public void testIncreasingBandwidthIncreasesSmoothness() throws MathException {
096            int numPoints = 100;
097            double[] xval = new double[numPoints];
098            double[] yval = new double[numPoints];
099            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    }