1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
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
85
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
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
144 yval[numPoints/3] *= 100;
145 yval[2 * numPoints/3] *= -100;
146
147
148
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
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 }