View Javadoc
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.assertTrue;
7   import static org.junit.Assert.fail;
8   
9   import java.util.Calendar;
10  
11  import org.djutils.event.Event;
12  import org.djutils.event.EventInterface;
13  import org.djutils.event.EventListenerInterface;
14  import org.djutils.event.EventType;
15  import org.djutils.event.TimedEvent;
16  import org.djutils.event.TimedEventType;
17  import org.djutils.metadata.MetaData;
18  import org.junit.Test;
19  
20  /**
21   * The EventBasedTimestampWeightedTallyTest test the weighted tally that receives event-observations with a timestamp.
22   * <p>
23   * Copyright (c) 2002-2022 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
24   * for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>. The DSOL
25   * project is distributed under a three-clause BSD-style license, which can be found at
26   * <a href="https://simulation.tudelft.nl/dsol/3.0/license.html" target="_blank">
27   * https://simulation.tudelft.nl/dsol/3.0/license.html</a>. <br>
28   * @author <a href="https://www.linkedin.com/in/peterhmjacobs">Peter Jacobs </a>
29   * @since 1.5
30   */
31  public class EventBasedTimestampWeightedTallyTest
32  {
33      /** an event to fire. */
34      private static final TimedEventType TIMED_VALUE_EVENT = new TimedEventType("VALUE_EVENT", MetaData.NO_META_DATA);
35  
36      /** Test the EventBasedTimestampWeightedTally. */
37      @Test
38      public void testEventBasedTimestampWeightedTally()
39      {
40          String description = "THIS TIMESTAMP WEIGHTED TALLY IS TESTED";
41          EventBasedTimestampWeightedTally wt = new EventBasedTimestampWeightedTally(description);
42  
43          // check the description
44          assertEquals(description, wt.getDescription());
45          assertTrue(wt.toString().contains(description));
46  
47          // now we check the initial values
48          assertTrue(wt.isActive());
49          assertTrue(Double.isNaN(wt.getMin()));
50          assertTrue(Double.isNaN(wt.getMax()));
51          assertTrue(Double.isNaN(wt.getWeightedSampleMean()));
52          assertTrue(Double.isNaN(wt.getWeightedPopulationMean()));
53          assertTrue(Double.isNaN(wt.getWeightedSampleVariance()));
54          assertTrue(Double.isNaN(wt.getWeightedSampleStDev()));
55          assertEquals(0.0, wt.getWeightedSum(), 0.0);
56          assertEquals(0L, wt.getN());
57  
58          // We fire a wrong event with wrong content
59          try
60          {
61              wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", "ERROR", 0.0));
62              fail("tally should fail on events.value !instanceOf Double");
63          }
64          catch (Exception exception)
65          {
66              assertNotNull(exception);
67          }
68  
69          // We fire a wrong event with wrong timestamp
70          try
71          {
72              wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "ERROR", 1.0, Double.NaN));
73              fail("tally should fail on events.timestamp == NaN");
74          }
75          catch (Exception exception)
76          {
77              assertNotNull(exception);
78          }
79  
80          // Now we fire some events
81          wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.0, 0.0));
82          assertTrue(Double.isNaN(wt.getMin()));
83          assertTrue(Double.isNaN(wt.getMax()));
84          assertTrue(Double.isNaN(wt.getWeightedSampleMean()));
85          assertTrue(Double.isNaN(wt.getWeightedPopulationMean()));
86          assertTrue(Double.isNaN(wt.getWeightedSampleVariance()));
87          assertTrue(Double.isNaN(wt.getWeightedSampleStDev()));
88          wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.1, 0.1));
89          assertEquals(1.0, wt.getMin(), 0.000001);
90          assertEquals(1.0, wt.getMax(), 0.000001);
91          assertEquals(1.0, wt.getWeightedSampleMean(), 0.000001);
92          assertEquals(1.0, wt.getWeightedPopulationMean(), 0.000001);
93          assertTrue(Double.isNaN(wt.getWeightedSampleVariance()));
94          assertTrue(Double.isNaN(wt.getWeightedSampleStDev()));
95          assertEquals(0, wt.getWeightedPopulationVariance(), 0.000001);
96          assertEquals(0, wt.getWeightedPopulationStDev(), 0.0000001);
97          wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.2, 0.2));
98          assertFalse(Double.isNaN(wt.getWeightedSampleVariance()));
99          assertFalse(Double.isNaN(wt.getWeightedSampleStDev()));
100         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.3, 0.3));
101         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.4, 0.4));
102         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.5, 0.5));
103         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.6, 0.6));
104         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.7, 0.7));
105         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.8, 0.8));
106         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.9, 0.9));
107         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 2.0, 1.0));
108 
109         try
110         {
111             wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 123.456, 0.8));
112             fail("timestamp out of order should have thrown an exception");
113         }
114         catch (IllegalArgumentException iae)
115         {
116             // Ignore expected exception
117         }
118 
119         assertTrue(wt.isActive());
120         wt.endObservations(1.1);
121         assertFalse(wt.isActive());
122 
123         // Now we check the EventBasedTimestampWeightedTally
124         assertEquals(2.0, wt.getMax(), 1.0E-6);
125         assertEquals(1.0, wt.getMin(), 1.0E-6);
126         assertEquals(11, wt.getN());
127         assertEquals(1.5 * 0.1 * 11, wt.getWeightedSum(), 1.0E-6);
128         assertEquals(1.5, wt.getWeightedSampleMean(), 1.0E-6);
129 
130         // Let's compute the standard deviation
131         double varianceAccumulator = 0;
132         for (int i = 0; i < 11; i++)
133         {
134             varianceAccumulator += Math.pow(1.5 - (1.0 + i / 10.0), 2);
135         }
136         double variance = varianceAccumulator / 10.0;
137         double stDev = Math.sqrt(variance);
138         assertEquals(variance, wt.getWeightedSampleVariance(), 1.0E-6);
139         assertEquals(stDev, wt.getWeightedSampleStDev(), 1.0E-6);
140 
141         variance = varianceAccumulator / 11.0;
142         stDev = Math.sqrt(variance);
143         assertEquals(variance, wt.getWeightedPopulationVariance(), 1.0E-6);
144         assertEquals(stDev, wt.getWeightedPopulationStDev(), 1.0E-6);
145 
146         // Adding something after the active period should not make a change
147         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 10.0, 20.0));
148         assertFalse(wt.isActive());
149         assertEquals(2.0, wt.getMax(), 1.0E-6);
150         assertEquals(1.0, wt.getMin(), 1.0E-6);
151         assertEquals(11, wt.getN());
152         assertEquals(1.5 * 0.1 * 11, wt.getWeightedSum(), 1.0E-6);
153         assertEquals(1.5, wt.getWeightedSampleMean(), 1.0E-6);
154 
155         // test some wrong events
156         try
157         {
158             wt.notify(new Event(new EventType("VALUE_EVENT", new MetaData("VALUE_EVENT", "non-timed event")),
159                     "EventBasedTimestampWeightedTallyTest", 123.456));
160             fail("non time-based event should have thrown an exception");
161         }
162         catch (Exception e)
163         {
164             // Ignore expected exception
165         }
166         try
167         {
168             wt.notify(new TimedEvent<String>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 123.456, "abc"));
169             fail("non time-based evenevent with timestamp != Calendar or Number should have thrown an exception");
170         }
171         catch (Exception e)
172         {
173             // Ignore expected exception
174         }
175 
176     }
177 
178     /** Test the EventBasedTimestampWeightedTally on a simple example. */
179     @Test
180     public void testEventBasedTimestampWeightedTallySimple()
181     {
182         // From: https://sciencing.com/calculate-time-decimals-5962681.html
183         EventBasedTimestampWeightedTally wt =
184                 new EventBasedTimestampWeightedTally("simple EventBasedTimestampWeightedTally statistic");
185         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 86.0, 0.0));
186         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 26.0, 13.0));
187         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 0.0, 36.0));
188         wt.endObservations(40.0);
189 
190         assertEquals(1716.0, wt.getWeightedSum(), 0.001);
191         assertEquals(42.9, wt.getWeightedSampleMean(), 0.001);
192         assertEquals(3, wt.getN());
193 
194         // When we shift the times, we should get the same answers
195         wt = new EventBasedTimestampWeightedTally("simple EventBasedTimestampWeightedTally statistic");
196         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 86.0, 10.0));
197         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 26.0, 23.0));
198         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 0.0, 46.0));
199         wt.endObservations(50.0);
200 
201         assertEquals(1716.0, wt.getWeightedSum(), 0.001);
202         assertEquals(42.9, wt.getWeightedSampleMean(), 0.001);
203         assertEquals(3, wt.getN());
204 
205         // When we have observations with duration 0, we should get the same answers
206         wt = new EventBasedTimestampWeightedTally("simple EventBasedTimestampWeightedTally statistic");
207         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 86.0, 0.0));
208         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 26.0, 13.0));
209         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 0.0, 13.0));
210         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 26.0, 13.0));
211         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 0.0, 36.0));
212         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 0.0, 36.0));
213         wt.endObservations(40.0);
214 
215         assertEquals(1716.0, wt.getWeightedSum(), 0.001);
216         assertEquals(42.9, wt.getWeightedSampleMean(), 0.001);
217         assertEquals(3, wt.getN()); // non-zero values only
218 
219         // Example from NIST: https://www.itl.nist.gov/div898/software/dataplot/refman2/ch2/weightsd.pdf
220         wt = new EventBasedTimestampWeightedTally("NIST");
221         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 2, 0.0));
222         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 3, 1.0));
223         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 5, 2.0));
224         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 7, 2.0));
225         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 11, 2.0));
226         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 13, 6.0));
227         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 17, 7.0));
228         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 19, 9.0));
229         wt.notify(new TimedEvent<Double>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 23, 10.0));
230         wt.endObservations(10.0);
231 
232         assertEquals((2 + 3 + 4 * 11 + 13 + 2 * 17 + 19) / 10.0, wt.getWeightedSampleMean(), 0.001);
233         assertEquals(5.82, wt.getWeightedSampleStDev(), 0.01);
234     }
235 
236     /** Test the TimestampWeightedTally for Calendar-based timestamps. */
237     @Test
238     public void testEBTimestampWeightedTallyCalendarNotify()
239     {
240         String description = "THIS TIMESTAMP WEIGHTED TALLY IS TESTED";
241         EventBasedTimestampWeightedTally wt = new EventBasedTimestampWeightedTally(description);
242 
243         int index = 10;
244         for (int second = 30; second <= 40; second++)
245         {
246             Calendar calendar = new Calendar.Builder().setDate(2000, 2, 2).setTimeOfDay(4, 12, second, 10).build();
247             wt.notify(new TimedEvent<Calendar>(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", index++, calendar));
248         }
249         assertTrue(wt.isActive());
250         wt.endObservations(new Calendar.Builder().setDate(2000, 2, 2).setTimeOfDay(4, 12, 41, 10).build());
251         assertFalse(wt.isActive());
252 
253         // Now we check the TimestampWeightedTally
254         assertEquals(20.0, wt.getMax(), 1.0E-6);
255         assertEquals(10.0, wt.getMin(), 1.0E-6);
256         assertEquals(11, wt.getN());
257         assertEquals(1.5 * 10000 * 11, wt.getWeightedSum(), 1.0E-2);
258         assertEquals(15.0, wt.getWeightedSampleMean(), 1.0E-6);
259 
260         // Let's compute the standard deviation
261         double variance = 0;
262         for (int i = 0; i < 11; i++)
263         {
264             variance += Math.pow(15.0 - (10 + i), 2);
265         }
266         variance = variance / 10.0; // n - 1
267         double stDev = Math.sqrt(variance);
268 
269         assertEquals(variance, wt.getWeightedSampleVariance(), 1.0E-6);
270         assertEquals(stDev, wt.getWeightedSampleStDev(), 1.0E-6);
271     }
272 
273     /** Test the TimestampWeightedTally for Calendar-based timestamps with register(). */
274     @Test
275     public void testEBTimestampWeightedTallyCalendarregister()
276     {
277         String description = "THIS TIMESTAMP WEIGHTED TALLY IS TESTED";
278         EventBasedTimestampWeightedTally wt = new EventBasedTimestampWeightedTally(description);
279 
280         int index = 10;
281         for (int second = 30; second <= 40; second++)
282         {
283             Calendar calendar = new Calendar.Builder().setDate(2000, 2, 2).setTimeOfDay(4, 12, second, 10).build();
284             wt.register(calendar, index++);
285         }
286         assertTrue(wt.isActive());
287         wt.endObservations(new Calendar.Builder().setDate(2000, 2, 2).setTimeOfDay(4, 12, 41, 10).build());
288         assertFalse(wt.isActive());
289 
290         // Now we check the TimestampWeightedTally
291         assertEquals(20.0, wt.getMax(), 1.0E-6);
292         assertEquals(10.0, wt.getMin(), 1.0E-6);
293         assertEquals(11, wt.getN());
294         assertEquals(1.5 * 10000 * 11, wt.getWeightedSum(), 1.0E-2);
295         assertEquals(15.0, wt.getWeightedSampleMean(), 1.0E-6);
296 
297         // Let's compute the standard deviation
298         double variance = 0;
299         for (int i = 0; i < 11; i++)
300         {
301             variance += Math.pow(15.0 - (10 + i), 2);
302         }
303         variance = variance / 10.0; // n - 1
304         double stDev = Math.sqrt(variance);
305 
306         assertEquals(variance, wt.getWeightedSampleVariance(), 1.0E-6);
307         assertEquals(stDev, wt.getWeightedSampleStDev(), 1.0E-6);
308     }
309 
310     /**
311      * Test produced events by EventBasedWeightedTally.
312      */
313     @Test
314     public void testWeightedTallyEventProduction()
315     {
316         EventBasedTimestampWeightedTally timestampedTally = new EventBasedTimestampWeightedTally("testTally");
317         assertEquals(timestampedTally, timestampedTally.getSourceId());
318         TimestampedObservationEventListener toel = new TimestampedObservationEventListener();
319         timestampedTally.addListener(toel, StatisticsEvents.TIMESTAMPED_OBSERVATION_ADDED_EVENT);
320         assertEquals(0, toel.getObservationEvents());
321 
322         TimedEventType[] types = new TimedEventType[] {StatisticsEvents.TIMED_N_EVENT, StatisticsEvents.TIMED_MIN_EVENT,
323                 StatisticsEvents.TIMED_MAX_EVENT, StatisticsEvents.TIMED_WEIGHTED_POPULATION_MEAN_EVENT,
324                 StatisticsEvents.TIMED_WEIGHTED_POPULATION_VARIANCE_EVENT,
325                 StatisticsEvents.TIMED_WEIGHTED_POPULATION_STDEV_EVENT, StatisticsEvents.TIMED_WEIGHTED_SUM_EVENT,
326                 StatisticsEvents.TIMED_WEIGHTED_SAMPLE_MEAN_EVENT, StatisticsEvents.TIMED_WEIGHTED_SAMPLE_VARIANCE_EVENT,
327                 StatisticsEvents.TIMED_WEIGHTED_SAMPLE_STDEV_EVENT};
328         LoggingEventListener[] listeners = new LoggingEventListener[types.length];
329         for (int i = 0; i < types.length; i++)
330         {
331             listeners[i] = new LoggingEventListener();
332             timestampedTally.addListener(listeners[i], types[i]);
333         }
334 
335         double prevTime = 0.0;
336         for (int i = 1; i <= 10; i++)
337         {
338             timestampedTally.register(prevTime, 10.0 * i);
339             prevTime += i;
340         }
341         timestampedTally.endObservations(prevTime);
342 
343         // the endObservation fires an observation event, but does not increase N
344         assertEquals(11, toel.getObservationEvents());
345 
346         // values based on formulas from https://www.itl.nist.gov/div898/software/dataplot/refman2/ch2/weightsd.pdf
347         Object[] expectedValues = new Object[] {10L, 10.0, 100.0, 70.0, 600.0, 24.4949, 3850.0, 70.0, 666.6667, 25.81989};
348         for (int i = 0; i < types.length; i++)
349         {
350             assertEquals("Number of events for listener " + types[i], 11, listeners[i].getNumberOfEvents());
351             assertEquals("Event sourceId for listener " + types[i], timestampedTally,
352                     listeners[i].getLastEvent().getSourceId());
353             assertEquals("Event type for listener " + types[i], types[i], listeners[i].getLastEvent().getType());
354             if (expectedValues[i] instanceof Long)
355             {
356                 assertEquals("Final value for listener " + types[i], expectedValues[i],
357                         listeners[i].getLastEvent().getContent());
358             }
359             else
360             {
361                 double e = ((Double) expectedValues[i]).doubleValue();
362                 double c = ((Double) listeners[i].getLastEvent().getContent()).doubleValue();
363                 assertEquals("Final value for listener " + types[i], e, c, 0.001);
364             }
365         }
366     }
367 
368     /** The listener that counts the OBSERVATION_ADDED_EVENT events and checks correctness. */
369     class TimestampedObservationEventListener implements EventListenerInterface
370     {
371         /** */
372         private static final long serialVersionUID = 1L;
373 
374         /** counter for the event. */
375         private int observationEvents = 0;
376 
377         @Override
378         public void notify(final EventInterface event)
379         {
380             assertTrue(event.getType().equals(StatisticsEvents.TIMESTAMPED_OBSERVATION_ADDED_EVENT));
381             assertTrue("Content of the event has a wrong type, not Object[]: " + event.getContent().getClass(),
382                     event.getContent() instanceof Object[]);
383             Object[] c = (Object[]) event.getContent();
384             assertTrue("Content[0] of the event has a wrong type, not double: " + c[0].getClass(), c[0] instanceof Double);
385             assertTrue("Content[1] of the event has a wrong type, not double: " + c[1].getClass(), c[1] instanceof Double);
386             assertTrue("SourceId of the event has a wrong type, not EventBasedTimestampWeightedTally: "
387                     + event.getSourceId().getClass(), event.getSourceId() instanceof EventBasedTimestampWeightedTally);
388             this.observationEvents++;
389         }
390 
391         /**
392          * @return countEvents
393          */
394         public int getObservationEvents()
395         {
396             return this.observationEvents;
397         }
398     }
399 
400 }