1 package org.djutils.stats.summarizers.event;
2
3 import static org.junit.Assert.assertEquals;
4 import static org.junit.Assert.assertFalse;
5 import static org.junit.Assert.assertNotNull;
6 import static org.junit.Assert.assertNull;
7 import static org.junit.Assert.assertTrue;
8 import static org.junit.Assert.fail;
9
10 import java.util.Random;
11
12 import org.djutils.event.Event;
13 import org.djutils.event.EventInterface;
14 import org.djutils.event.EventListenerInterface;
15 import org.djutils.event.EventType;
16 import org.djutils.metadata.MetaData;
17 import org.djutils.stats.ConfidenceInterval;
18 import org.djutils.stats.DistNormalTable;
19 import org.djutils.stats.summarizers.quantileaccumulator.FullStorageAccumulator;
20 import org.djutils.stats.summarizers.quantileaccumulator.NoStorageAccumulator;
21 import org.junit.Test;
22
23
24
25
26
27
28
29
30
31
32
33
34 public class EventBasedTallyTest
35 {
36
37 private static final EventType VALUE_EVENT = new EventType("VALUE_EVENT", MetaData.NO_META_DATA);
38
39
40 @SuppressWarnings("checkstyle:methodlength")
41 @Test
42 public void testEventBasedTally()
43 {
44 String description = "THIS TALLY IS TESTED";
45 EventBasedTally tally = new EventBasedTally(description);
46
47
48 assertTrue(tally.toString().contains(description));
49 assertEquals(description, tally.getDescription());
50 assertTrue(tally.toString().startsWith("EventBasedTally"));
51
52
53 assertTrue(Double.valueOf(tally.getMin()).isNaN());
54 assertTrue(Double.valueOf(tally.getMax()).isNaN());
55 assertTrue(Double.valueOf(tally.getSampleMean()).isNaN());
56 assertTrue(Double.valueOf(tally.getSampleVariance()).isNaN());
57 assertTrue(Double.valueOf(tally.getPopulationVariance()).isNaN());
58 assertTrue(Double.valueOf(tally.getSampleStDev()).isNaN());
59 assertTrue(Double.valueOf(tally.getPopulationStDev()).isNaN());
60 assertTrue(Double.valueOf(tally.getSampleSkewness()).isNaN());
61 assertTrue(Double.valueOf(tally.getPopulationSkewness()).isNaN());
62 assertTrue(Double.valueOf(tally.getSampleKurtosis()).isNaN());
63 assertTrue(Double.valueOf(tally.getPopulationKurtosis()).isNaN());
64 assertTrue(Double.valueOf(tally.getSampleExcessKurtosis()).isNaN());
65 assertTrue(Double.valueOf(tally.getPopulationExcessKurtosis()).isNaN());
66 assertEquals(0, tally.getSum(), 0);
67 assertEquals(0L, tally.getN());
68 assertNull(tally.getConfidenceInterval(0.95));
69 assertNull(tally.getConfidenceInterval(0.95, ConfidenceInterval.LEFT_SIDE_CONFIDENCE));
70 assertNull(tally.getConfidenceInterval(0.95, ConfidenceInterval.RIGHT_SIDE_CONFIDENCE));
71 assertNull(tally.getConfidenceInterval(0.95, ConfidenceInterval.BOTH_SIDE_CONFIDENCE));
72
73
74 try
75 {
76 tally.notify(new Event(VALUE_EVENT, "ERROR", "ERROR"));
77 fail("tally should react on events.value !instanceOf Double");
78 }
79 catch (Exception exception)
80 {
81 assertNotNull(exception);
82 }
83
84
85 try
86 {
87 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", 1.1));
88 assertFalse("mean is now available", Double.isNaN(tally.getSampleMean()));
89 assertTrue("sample variance is not available", Double.isNaN(tally.getSampleVariance()));
90 assertFalse("variance is not available", Double.isNaN(tally.getPopulationVariance()));
91 assertTrue("skewness is not available", Double.isNaN(tally.getPopulationSkewness()));
92 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", 1.2));
93 assertFalse("sample variance is now available", Double.isNaN(tally.getSampleVariance()));
94 assertTrue("sample skewness is not available", Double.isNaN(tally.getSampleSkewness()));
95 assertFalse("skewness is available", Double.isNaN(tally.getPopulationSkewness()));
96 assertTrue("kurtosis is not available", Double.isNaN(tally.getPopulationKurtosis()));
97 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", 1.3));
98 assertFalse("skewness is now available", Double.isNaN(tally.getSampleSkewness()));
99 assertFalse("kurtosis is now available", Double.isNaN(tally.getPopulationKurtosis()));
100 assertTrue("sample kurtosis is not available", Double.isNaN(tally.getSampleKurtosis()));
101 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", 1.4));
102 assertFalse("sample kurtosis is now available", Double.isNaN(tally.getSampleKurtosis()));
103 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", 1.5));
104 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", 1.6));
105 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", 1.7));
106 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", 1.8));
107 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", 1.9));
108 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", 2.0));
109 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", 1.0));
110 }
111 catch (Exception exception)
112 {
113 fail(exception.getMessage());
114 }
115
116
117 assertEquals(2.0, tally.getMax(), 1.0E-6);
118 assertEquals(1.0, tally.getMin(), 1.0E-6);
119 assertEquals(11, tally.getN());
120 assertEquals(16.5, tally.getSum(), 1.0E-6);
121 assertEquals(1.5, tally.getSampleMean(), 1.0E-6);
122 assertEquals(0.110000, tally.getSampleVariance(), 1.0E-6);
123 assertEquals(0.331662, tally.getSampleStDev(), 1.0E-6);
124
125 assertEquals(1.304003602, tally.getConfidenceInterval(0.05)[0], 1E-05);
126 assertEquals(1.695996398, tally.getConfidenceInterval(0.05)[1], 1E-05);
127 assertEquals(1.335514637, tally.getConfidenceInterval(0.10)[0], 1E-05);
128 assertEquals(1.664485363, tally.getConfidenceInterval(0.10)[1], 1E-05);
129 assertEquals(1.356046853, tally.getConfidenceInterval(0.15)[0], 1E-05);
130 assertEquals(1.643953147, tally.getConfidenceInterval(0.15)[1], 1E-05);
131 assertEquals(1.432551025, tally.getConfidenceInterval(0.50)[0], 1E-05);
132 assertEquals(1.567448975, tally.getConfidenceInterval(0.50)[1], 1E-05);
133 assertEquals(1.474665290, tally.getConfidenceInterval(0.80)[0], 1E-05);
134 assertEquals(1.525334710, tally.getConfidenceInterval(0.80)[1], 1E-05);
135 assertEquals(1.493729322, tally.getConfidenceInterval(0.95)[0], 1E-05);
136 assertEquals(1.506270678, tally.getConfidenceInterval(0.95)[1], 1E-05);
137
138 assertEquals(1.304003602, tally.getConfidenceInterval(0.05, ConfidenceInterval.BOTH_SIDE_CONFIDENCE)[0], 1E-05);
139 assertEquals(1.695996398, tally.getConfidenceInterval(0.05, ConfidenceInterval.BOTH_SIDE_CONFIDENCE)[1], 1E-05);
140 assertEquals(1.432551025, tally.getConfidenceInterval(0.50, ConfidenceInterval.BOTH_SIDE_CONFIDENCE)[0], 1E-05);
141 assertEquals(1.567448975, tally.getConfidenceInterval(0.50, ConfidenceInterval.BOTH_SIDE_CONFIDENCE)[1], 1E-05);
142 assertEquals(1.493729322, tally.getConfidenceInterval(0.95, ConfidenceInterval.BOTH_SIDE_CONFIDENCE)[0], 1E-05);
143 assertEquals(1.506270678, tally.getConfidenceInterval(0.95, ConfidenceInterval.BOTH_SIDE_CONFIDENCE)[1], 1E-05);
144
145 assertEquals(1.304003602, tally.getConfidenceInterval(0.025, ConfidenceInterval.LEFT_SIDE_CONFIDENCE)[0], 1E-05);
146 assertEquals(1.500000000, tally.getConfidenceInterval(0.025, ConfidenceInterval.LEFT_SIDE_CONFIDENCE)[1], 1E-05);
147 assertEquals(1.432551025, tally.getConfidenceInterval(0.25, ConfidenceInterval.LEFT_SIDE_CONFIDENCE)[0], 1E-05);
148 assertEquals(1.500000000, tally.getConfidenceInterval(0.25, ConfidenceInterval.LEFT_SIDE_CONFIDENCE)[1], 1E-05);
149 assertEquals(1.474665290, tally.getConfidenceInterval(0.40, ConfidenceInterval.LEFT_SIDE_CONFIDENCE)[0], 1E-05);
150 assertEquals(1.500000000, tally.getConfidenceInterval(0.40, ConfidenceInterval.LEFT_SIDE_CONFIDENCE)[1], 1E-05);
151
152 assertEquals(1.500000000, tally.getConfidenceInterval(0.025, ConfidenceInterval.RIGHT_SIDE_CONFIDENCE)[0], 1E-05);
153 assertEquals(1.695996398, tally.getConfidenceInterval(0.025, ConfidenceInterval.RIGHT_SIDE_CONFIDENCE)[1], 1E-05);
154 assertEquals(1.500000000, tally.getConfidenceInterval(0.25, ConfidenceInterval.RIGHT_SIDE_CONFIDENCE)[0], 1E-05);
155 assertEquals(1.567448975, tally.getConfidenceInterval(0.25, ConfidenceInterval.RIGHT_SIDE_CONFIDENCE)[1], 1E-05);
156 assertEquals(1.500000000, tally.getConfidenceInterval(0.40, ConfidenceInterval.RIGHT_SIDE_CONFIDENCE)[0], 1E-05);
157 assertEquals(1.525334710, tally.getConfidenceInterval(0.40, ConfidenceInterval.RIGHT_SIDE_CONFIDENCE)[1], 1E-05);
158
159
160 try
161 {
162 tally.getConfidenceInterval(0.95, null);
163 fail("null is not defined as side of confidence level");
164 }
165 catch (Exception exception)
166 {
167 assertTrue(exception.getClass().equals(NullPointerException.class));
168 }
169 try
170 {
171 assertNull(tally.getConfidenceInterval(-0.95));
172 fail("should have reacted on wrong confidence level -0.95");
173 }
174 catch (Exception exception)
175 {
176 assertTrue(exception.getClass().equals(IllegalArgumentException.class));
177 }
178 try
179 {
180 assertNull(tally.getConfidenceInterval(1.14));
181 fail("should have reacted on wrong confidence level 1.14");
182 }
183 catch (Exception exception)
184 {
185 assertTrue(exception.getClass().equals(IllegalArgumentException.class));
186 }
187
188 assertTrue(Math.abs(tally.getSampleMean() - 1.5) < 10E-6);
189
190
191 double varianceAccumulator = 0;
192 for (int i = 0; i < 11; i++)
193 {
194 varianceAccumulator = Math.pow(1.5 - (1.0 + i / 10.0), 2) + varianceAccumulator;
195 }
196
197 assertEquals(varianceAccumulator / 10.0, tally.getSampleVariance(), 1.0E-6);
198 assertEquals(Math.sqrt(varianceAccumulator / 10.0), tally.getSampleStDev(), 1.0E-6);
199
200 assertEquals(varianceAccumulator / 11.0, tally.getPopulationVariance(), 1.0E-6);
201 assertEquals(Math.sqrt(varianceAccumulator / 11.0), tally.getPopulationStDev(), 1.0E-6);
202 }
203
204
205
206
207 @Test
208 public void testTallyEventProduction()
209 {
210 EventBasedTally tally = new EventBasedTally("testTally");
211 assertEquals(tally, tally.getSourceId());
212 ObservationEventListener oel = new ObservationEventListener();
213 tally.addListener(oel, StatisticsEvents.OBSERVATION_ADDED_EVENT);
214 assertEquals(0, oel.getObservationEvents());
215
216 EventType[] types = new EventType[] {StatisticsEvents.N_EVENT, StatisticsEvents.MIN_EVENT, StatisticsEvents.MAX_EVENT,
217 StatisticsEvents.POPULATION_MEAN_EVENT, StatisticsEvents.POPULATION_VARIANCE_EVENT,
218 StatisticsEvents.POPULATION_SKEWNESS_EVENT, StatisticsEvents.POPULATION_KURTOSIS_EVENT,
219 StatisticsEvents.POPULATION_STDEV_EVENT, StatisticsEvents.SUM_EVENT, StatisticsEvents.SAMPLE_MEAN_EVENT,
220 StatisticsEvents.SAMPLE_VARIANCE_EVENT, StatisticsEvents.SAMPLE_SKEWNESS_EVENT,
221 StatisticsEvents.SAMPLE_KURTOSIS_EVENT, StatisticsEvents.SAMPLE_STDEV_EVENT};
222 LoggingEventListener[] listeners = new LoggingEventListener[types.length];
223 for (int i = 0; i < types.length; i++)
224 {
225 listeners[i] = new LoggingEventListener();
226 tally.addListener(listeners[i], types[i]);
227 }
228
229 for (int i = 1; i <= 10; i++)
230 {
231 tally.register(10 * i);
232 }
233
234 assertEquals(10, oel.getObservationEvents());
235
236
237 Object[] expectedValues =
238 new Object[] {10L, 10.0, 100.0, 55.0, 825.0, 0.0, 1.7758, 28.7228, 550.0, 55.0, 916.6667, 0.0, 1.5982, 30.2765};
239 for (int i = 0; i < types.length; i++)
240 {
241 assertEquals("Number of events for listener " + types[i], 10, listeners[i].getNumberOfEvents());
242 assertEquals("Event sourceId for listener " + types[i], tally, listeners[i].getLastEvent().getSourceId());
243 assertEquals("Event type for listener " + types[i], types[i], listeners[i].getLastEvent().getType());
244 if (expectedValues[i] instanceof Long)
245 {
246 assertEquals("Final value for listener " + types[i], expectedValues[i],
247 listeners[i].getLastEvent().getContent());
248 }
249 else
250 {
251 double e = ((Double) expectedValues[i]).doubleValue();
252 double c = ((Double) listeners[i].getLastEvent().getContent()).doubleValue();
253 assertEquals("Final value for listener " + types[i], e, c, 0.001);
254 }
255 }
256 }
257
258
259
260
261 @Test
262 public void testNoStorageAccumulator()
263 {
264 EventBasedTally tally = new EventBasedTally("test with the NoStorageAccumulator", new NoStorageAccumulator());
265 assertTrue("mean of no data is NaN", Double.isNaN(tally.getSampleMean()));
266 try
267 {
268 tally.getQuantile(0.5);
269 fail("getQuantile of no data should have resulted in an IllegalArgumentException");
270 }
271 catch (IllegalArgumentException iae)
272 {
273
274 }
275
276 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", 90.0));
277 assertEquals("mean of one value is that value", 90.0, tally.getSampleMean(), 0);
278 try
279 {
280 tally.getQuantile(0.5);
281 fail("getQuantile of one value should have resulted in an IllegalArgumentException");
282 }
283 catch (IllegalArgumentException iae)
284 {
285
286 }
287
288 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", 110.0));
289 assertEquals("mean of two value", 100.0, tally.getSampleMean(), 0);
290 assertEquals("50% quantile", 100.0, tally.getQuantile(0.5), 0);
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307 assertEquals("84% is about one sigma", 1, DistNormalTable.getInverseCumulativeProbability(0, 1, 0.84), 0.01);
308 assertEquals("16% is about minus one sigma", -1, DistNormalTable.getInverseCumulativeProbability(0, 1, 0.16), 0.01);
309
310
311 double value = 166.0 / 25.0;
312 tally.initialize();
313 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", value));
314 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", value));
315 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", value));
316 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", value));
317 tally.initialize();
318
319 double mean = 123.456;
320 double stddev = 234.567;
321 Random random = new Random(123456);
322 for (int sample = 0; sample < 10000; sample++)
323 {
324 value = generateGaussianNoise(mean, stddev, random);
325 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", value));
326 }
327 assertEquals("mean should approximately match", mean, tally.getSampleMean(), stddev / 10);
328 assertEquals("stddev should approximately match", stddev, tally.getSampleStDev(), stddev / 10);
329 }
330
331
332
333
334 @Test
335 public void testFullStorageAccumulator()
336 {
337 EventBasedTally tally =
338 new EventBasedTally("EventBasedTally for FullStorageAccumulator test", new FullStorageAccumulator());
339
340 for (int step = 0; step <= 100; step++)
341 {
342 tally.notify(new Event(VALUE_EVENT, "EventBasedTallyTest", 1.0 * step));
343 }
344 for (double probability : new double[] {0.0, 0.01, 0.1, 0.49, 0.5, 0.51, 0.9, 0.99, 1.0})
345 {
346 double expected = 100 * probability;
347 double got = tally.getQuantile(probability);
348 assertEquals("quantile should match", expected, got, 0.00001);
349 }
350 try
351 {
352 tally.getQuantile(-0.01);
353 fail("negative probability should have thrown an exception");
354 }
355 catch (IllegalArgumentException iae)
356 {
357
358 }
359
360 try
361 {
362 tally.getQuantile(1.01);
363 fail("Probability > 1 should have thrown an exception");
364 }
365 catch (IllegalArgumentException iae)
366 {
367
368 }
369
370 assertTrue("toString returns something descriptive",
371 new FullStorageAccumulator().toString().startsWith("FullStorageAccumulator"));
372 }
373
374
375
376
377
378
379
380
381 double generateGaussianNoise(final double mu, final double sigma, final Random random)
382 {
383 final double epsilon = Double.MIN_VALUE;
384 final double twoPi = Math.PI * 2;
385
386 double u1, u2;
387 do
388 {
389 u1 = random.nextDouble();
390 u2 = random.nextDouble();
391 }
392 while (u1 <= epsilon);
393
394 return mu + sigma * Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(twoPi * u2);
395 }
396
397
398 class ObservationEventListener implements EventListenerInterface
399 {
400
401 private static final long serialVersionUID = 1L;
402
403
404 private int observationEvents = 0;
405
406 @Override
407 public void notify(final EventInterface event)
408 {
409 assertTrue(event.getType().equals(StatisticsEvents.OBSERVATION_ADDED_EVENT));
410 assertTrue("Content of the event has a wrong type, not DOuble: " + event.getContent().getClass(),
411 event.getContent() instanceof Double);
412 assertTrue("SourceId of the event has a wrong type, not EventBasedTally: " + event.getSourceId().getClass(),
413 event.getSourceId() instanceof EventBasedTally);
414 this.observationEvents++;
415 }
416
417
418
419
420 public int getObservationEvents()
421 {
422 return this.observationEvents;
423 }
424 }
425
426 }