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    *      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.math4.legacy.fitting;
18  
19  import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
20  import org.apache.commons.math4.legacy.exception.TooManyIterationsException;
21  import org.junit.Assert;
22  import org.junit.Test;
23  
24  /**
25   * Tests {@link GaussianCurveFitter}.
26   *
27   */
28  public class GaussianCurveFitterTest {
29      /** Good data. */
30      protected static final double[][] DATASET1 = new double[][] {
31          {4.0254623,  531026.0},
32          {4.02804905, 664002.0},
33          {4.02934242, 787079.0},
34          {4.03128248, 984167.0},
35          {4.03386923, 1294546.0},
36          {4.03580929, 1560230.0},
37          {4.03839603, 1887233.0},
38          {4.0396894,  2113240.0},
39          {4.04162946, 2375211.0},
40          {4.04421621, 2687152.0},
41          {4.04550958, 2862644.0},
42          {4.04744964, 3078898.0},
43          {4.05003639, 3327238.0},
44          {4.05132976, 3461228.0},
45          {4.05326982, 3580526.0},
46          {4.05585657, 3576946.0},
47          {4.05779662, 3439750.0},
48          {4.06038337, 3220296.0},
49          {4.06167674, 3070073.0},
50          {4.0636168,  2877648.0},
51          {4.06620355, 2595848.0},
52          {4.06749692, 2390157.0},
53          {4.06943698, 2175960.0},
54          {4.07202373, 1895104.0},
55          {4.0733171,  1687576.0},
56          {4.07525716, 1447024.0},
57          {4.0778439,  1130879.0},
58          {4.07978396, 904900.0},
59          {4.08237071, 717104.0},
60          {4.08366408, 620014.0}
61      };
62      /** Poor data: right of peak not symmetric with left of peak. */
63      protected static final double[][] DATASET2 = new double[][] {
64          {-20.15,   1523.0},
65          {-19.65,   1566.0},
66          {-19.15,   1592.0},
67          {-18.65,   1927.0},
68          {-18.15,   3089.0},
69          {-17.65,   6068.0},
70          {-17.15,  14239.0},
71          {-16.65,  34124.0},
72          {-16.15,  64097.0},
73          {-15.65, 110352.0},
74          {-15.15, 164742.0},
75          {-14.65, 209499.0},
76          {-14.15, 267274.0},
77          {-13.65, 283290.0},
78          {-13.15, 275363.0},
79          {-12.65, 258014.0},
80          {-12.15, 225000.0},
81          {-11.65, 200000.0},
82          {-11.15, 190000.0},
83          {-10.65, 185000.0},
84          {-10.15, 180000.0},
85          { -9.65, 179000.0},
86          { -9.15, 178000.0},
87          { -8.65, 177000.0},
88          { -8.15, 176000.0},
89          { -7.65, 175000.0},
90          { -7.15, 174000.0},
91          { -6.65, 173000.0},
92          { -6.15, 172000.0},
93          { -5.65, 171000.0},
94          { -5.15, 170000.0}
95      };
96      /** Poor data: long tails. */
97      protected static final double[][] DATASET3 = new double[][] {
98          {-90.15,   1513.0},
99          {-80.15,   1514.0},
100         {-70.15,   1513.0},
101         {-60.15,   1514.0},
102         {-50.15,   1513.0},
103         {-40.15,   1514.0},
104         {-30.15,   1513.0},
105         {-20.15,   1523.0},
106         {-19.65,   1566.0},
107         {-19.15,   1592.0},
108         {-18.65,   1927.0},
109         {-18.15,   3089.0},
110         {-17.65,   6068.0},
111         {-17.15,  14239.0},
112         {-16.65,  34124.0},
113         {-16.15,  64097.0},
114         {-15.65, 110352.0},
115         {-15.15, 164742.0},
116         {-14.65, 209499.0},
117         {-14.15, 267274.0},
118         {-13.65, 283290.0},
119         {-13.15, 275363.0},
120         {-12.65, 258014.0},
121         {-12.15, 214073.0},
122         {-11.65, 182244.0},
123         {-11.15, 136419.0},
124         {-10.65,  97823.0},
125         {-10.15,  58930.0},
126         { -9.65,  35404.0},
127         { -9.15,  16120.0},
128         { -8.65,   9823.0},
129         { -8.15,   5064.0},
130         { -7.65,   2575.0},
131         { -7.15,   1642.0},
132         { -6.65,   1101.0},
133         { -6.15,    812.0},
134         { -5.65,    690.0},
135         { -5.15,    565.0},
136         {  5.15,    564.0},
137         { 15.15,    565.0},
138         { 25.15,    564.0},
139         { 35.15,    565.0},
140         { 45.15,    564.0},
141         { 55.15,    565.0},
142         { 65.15,    564.0},
143         { 75.15,    565.0}
144     };
145     /** Poor data: right of peak is missing. */
146     protected static final double[][] DATASET4 = new double[][] {
147         {-20.15,   1523.0},
148         {-19.65,   1566.0},
149         {-19.15,   1592.0},
150         {-18.65,   1927.0},
151         {-18.15,   3089.0},
152         {-17.65,   6068.0},
153         {-17.15,  14239.0},
154         {-16.65,  34124.0},
155         {-16.15,  64097.0},
156         {-15.65, 110352.0},
157         {-15.15, 164742.0},
158         {-14.65, 209499.0},
159         {-14.15, 267274.0},
160         {-13.65, 283290.0}
161     };
162     /** Good data, but few points. */
163     protected static final double[][] DATASET5 = new double[][] {
164         {4.0254623,  531026.0},
165         {4.03128248, 984167.0},
166         {4.03839603, 1887233.0},
167         {4.04421621, 2687152.0},
168         {4.05132976, 3461228.0},
169         {4.05326982, 3580526.0},
170         {4.05779662, 3439750.0},
171         {4.0636168,  2877648.0},
172         {4.06943698, 2175960.0},
173         {4.07525716, 1447024.0},
174         {4.08237071, 717104.0},
175         {4.08366408, 620014.0}
176     };
177 
178     /**
179      * Basic.
180      */
181     @Test
182     public void testFit01() {
183         SimpleCurveFitter fitter = GaussianCurveFitter.create();
184         double[] parameters = fitter.fit(createDataset(DATASET1).toList());
185 
186         Assert.assertEquals(3496978.1837704973, parameters[0], 1e-7);
187         Assert.assertEquals(4.054933085999146, parameters[1], 1e-16);
188         Assert.assertEquals(0.015039355620304326, parameters[2], 1e-15);
189     }
190 
191     @Test
192     public void testDataset1LargeXShift() {
193         final SimpleCurveFitter fitter = GaussianCurveFitter.create();
194         final double xShift = 1e8;
195         final double[] parameters = fitter.fit(createDataset(DATASET1, xShift, 0).toList());
196 
197         Assert.assertEquals(1, parameters[0] / 3496978.1837704973, 1e-2);
198         Assert.assertEquals(1, parameters[1] / (xShift + 4.054933085999146), 1e-6);
199         Assert.assertEquals(1, parameters[2] / 0.015039355620304326, 1e-2);
200     }
201 
202     @Test
203     public void testWithMaxIterations1() {
204         final int maxIter = 20;
205         final double[] init = { 3.5e6, 4.2, 0.1 };
206 
207         SimpleCurveFitter fitter = GaussianCurveFitter.create();
208         double[] parameters = fitter
209             .withMaxIterations(maxIter)
210             .withStartPoint(init)
211             .fit(createDataset(DATASET1).toList());
212 
213         Assert.assertEquals(3496978.1837704973, parameters[0], 1e-2);
214         Assert.assertEquals(4.054933085999146, parameters[1], 1e-4);
215         Assert.assertEquals(0.015039355620304326, parameters[2], 1e-4);
216     }
217 
218     @Test(expected=TooManyIterationsException.class)
219     public void testWithMaxIterations2() {
220         final int maxIter = 1; // Too few iterations.
221         final double[] init = { 3.5e6, 4.2, 0.1 };
222 
223         SimpleCurveFitter fitter = GaussianCurveFitter.create();
224         fitter.withMaxIterations(maxIter)
225               .withStartPoint(init)
226               .fit(createDataset(DATASET1).toList());
227     }
228 
229     @Test
230     public void testWithStartPoint() {
231         final double[] init = { 3.5e6, 4.2, 0.1 };
232 
233         SimpleCurveFitter fitter = GaussianCurveFitter.create();
234         double[] parameters = fitter
235             .withStartPoint(init)
236             .fit(createDataset(DATASET1).toList());
237 
238         Assert.assertEquals(3496978.1837704973, parameters[0], 1e-2);
239         Assert.assertEquals(4.054933085999146, parameters[1], 1e-4);
240         Assert.assertEquals(0.015039355620304326, parameters[2], 1e-4);
241     }
242 
243     /**
244      * Zero points is not enough observed points.
245      */
246     @Test(expected=MathIllegalArgumentException.class)
247     public void testFit02() {
248         GaussianCurveFitter.create().fit(new WeightedObservedPoints().toList());
249     }
250 
251     /**
252      * Two points is not enough observed points.
253      */
254     @Test(expected=MathIllegalArgumentException.class)
255     public void testFit03() {
256         SimpleCurveFitter fitter = GaussianCurveFitter.create();
257         fitter.fit(createDataset(new double[][] {
258                     {4.0254623,  531026.0},
259                     {4.02804905, 664002.0}
260                 }).toList());
261     }
262 
263     /**
264      * Poor data: right of peak not symmetric with left of peak.
265      */
266     @Test
267     public void testFit04() {
268         SimpleCurveFitter fitter = GaussianCurveFitter.create();
269         double[] parameters = fitter.fit(createDataset(DATASET2).toList());
270 
271         Assert.assertEquals(233003.2967252038, parameters[0], 1e-4);
272         Assert.assertEquals(-10.654887521095983, parameters[1], 1e-4);
273         Assert.assertEquals(4.335937353196641, parameters[2], 1e-4);
274     }
275 
276     /**
277      * Poor data: long tails.
278      */
279     @Test
280     public void testFit05() {
281         SimpleCurveFitter fitter = GaussianCurveFitter.create();
282         double[] parameters = fitter.fit(createDataset(DATASET3).toList());
283 
284         Assert.assertEquals(283863.81929180305, parameters[0], 1e-4);
285         Assert.assertEquals(-13.29641995105174, parameters[1], 1e-4);
286         Assert.assertEquals(1.7297330293549908, parameters[2], 1e-4);
287     }
288 
289     /**
290      * Poor data: right of peak is missing.
291      */
292     @Test
293     public void testFit06() {
294         SimpleCurveFitter fitter = GaussianCurveFitter.create();
295         double[] parameters = fitter.fit(createDataset(DATASET4).toList());
296 
297         Assert.assertEquals(285250.66754309234, parameters[0], 1e-4);
298         Assert.assertEquals(-13.528375695228455, parameters[1], 1e-4);
299         Assert.assertEquals(1.5204344894331614, parameters[2], 1e-4);
300     }
301 
302     /**
303      * Basic with smaller dataset.
304      */
305     @Test
306     public void testFit07() {
307         SimpleCurveFitter fitter = GaussianCurveFitter.create();
308         double[] parameters = fitter.fit(createDataset(DATASET5).toList());
309 
310         Assert.assertEquals(3514384.729342235, parameters[0], 1e-4);
311         Assert.assertEquals(4.054970307455625, parameters[1], 1e-4);
312         Assert.assertEquals(0.015029412832160017, parameters[2], 1e-4);
313     }
314 
315     @Test
316     public void testMath519() {
317         // The optimizer will try negative sigma values but "GaussianCurveFitter"
318         // will catch the raised exceptions and return NaN values instead.
319 
320         final double[] data = {
321             1.1143831578403364E-29,
322             4.95281403484594E-28,
323             1.1171347211930288E-26,
324             1.7044813962636277E-25,
325             1.9784716574832164E-24,
326             1.8630236407866774E-23,
327             1.4820532905097742E-22,
328             1.0241963854632831E-21,
329             6.275077366673128E-21,
330             3.461808994532493E-20,
331             1.7407124684715706E-19,
332             8.056687953553974E-19,
333             3.460193945992071E-18,
334             1.3883326374011525E-17,
335             5.233894983671116E-17,
336             1.8630791465263745E-16,
337             6.288759227922111E-16,
338             2.0204433920597856E-15,
339             6.198768938576155E-15,
340             1.821419346860626E-14,
341             5.139176445538471E-14,
342             1.3956427429045787E-13,
343             3.655705706448139E-13,
344             9.253753324779779E-13,
345             2.267636001476696E-12,
346             5.3880460095836855E-12,
347             1.2431632654852931E-11
348         };
349 
350         final WeightedObservedPoints obs = new WeightedObservedPoints();
351         for (int i = 0; i < data.length; i++) {
352             obs.add(i, data[i]);
353         }
354         final double[] p = GaussianCurveFitter.create().fit(obs.toList());
355 
356         Assert.assertEquals(53.1572792, p[1], 1e-7);
357         Assert.assertEquals(5.75214622, p[2], 1e-8);
358     }
359 
360     @Test
361     public void testMath798() {
362         // When the data points are not commented out below, the fit stalls.
363         // This is expected however, since the whole dataset hardly looks like
364         // a Gaussian.
365         // When commented out, the fit proceeds fine.
366 
367         final WeightedObservedPoints obs = new WeightedObservedPoints();
368 
369         obs.add(0.23, 395.0);
370         //obs.add(0.68, 0.0);
371         obs.add(1.14, 376.0);
372         //obs.add(1.59, 0.0);
373         obs.add(2.05, 163.0);
374         //obs.add(2.50, 0.0);
375         obs.add(2.95, 49.0);
376         //obs.add(3.41, 0.0);
377         obs.add(3.86, 16.0);
378         //obs.add(4.32, 0.0);
379         obs.add(4.77, 1.0);
380 
381         final double[] p = GaussianCurveFitter.create().fit(obs.toList());
382 
383         // Values are copied from a previous run of this test.
384         Assert.assertEquals(420.8397296167364, p[0], 1e-12);
385         Assert.assertEquals(0.603770729862231, p[1], 1e-15);
386         Assert.assertEquals(1.0786447936766612, p[2], 1e-14);
387     }
388 
389     /**
390      * Adds the specified points to specified <code>GaussianCurveFitter</code>
391      * instance.
392      *
393      * @param points Data points where first dimension is a point index and
394      *        second dimension is an array of length two representing the point
395      *        with the first value corresponding to X and the second value
396      *        corresponding to Y.
397      * @param xShift Offset added to the abscissae.
398      * @param yShift Offset added to the ordinates.
399      * @return the collection of observed points.
400      */
401     private static WeightedObservedPoints createDataset(double[][] points,
402                                                         double xShift,
403                                                         double yShift) {
404         final WeightedObservedPoints obs = new WeightedObservedPoints();
405         for (int i = 0; i < points.length; i++) {
406             obs.add(points[i][0] + xShift, points[i][1] + yShift);
407         }
408         return obs;
409     }
410 
411     /**
412      * Adds the specified points to specified <code>GaussianCurveFitter</code>
413      * instance.
414      *
415      * @param points Data points where first dimension is a point index and
416      *        second dimension is an array of length two representing the point
417      *        with the first value corresponding to X and the second value
418      *        corresponding to Y.
419      * @return the collection of observed points.
420      */
421     private static WeightedObservedPoints createDataset(double[][] points) {
422         return createDataset(points, 0, 0);
423     }
424 }