package org.djutils.stats.summarizers.event;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Calendar;
import org.djutils.event.Event;
import org.djutils.event.EventInterface;
import org.djutils.event.EventListenerInterface;
import org.djutils.event.EventType;
import org.djutils.event.TimedEvent;
import org.djutils.event.TimedEventType;
import org.djutils.metadata.MetaData;
import org.junit.Test;
/**
* The EventBasedTimestampWeightedTallyTest test the weighted tally that receives event-observations with a timestamp.
*
* Copyright (c) 2002-2022 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information https://simulation.tudelft.nl. The DSOL
* project is distributed under a three-clause BSD-style license, which can be found at
*
* https://simulation.tudelft.nl/dsol/3.0/license.html.
* @author Peter Jacobs
* @since 1.5
*/
public class EventBasedTimestampWeightedTallyTest
{
/** an event to fire. */
private static final TimedEventType TIMED_VALUE_EVENT = new TimedEventType("VALUE_EVENT", MetaData.NO_META_DATA);
/** Test the EventBasedTimestampWeightedTally. */
@Test
public void testEventBasedTimestampWeightedTally()
{
String description = "THIS TIMESTAMP WEIGHTED TALLY IS TESTED";
EventBasedTimestampWeightedTally wt = new EventBasedTimestampWeightedTally(description);
// check the description
assertEquals(description, wt.getDescription());
assertTrue(wt.toString().contains(description));
// now we check the initial values
assertTrue(wt.isActive());
assertTrue(Double.isNaN(wt.getMin()));
assertTrue(Double.isNaN(wt.getMax()));
assertTrue(Double.isNaN(wt.getWeightedSampleMean()));
assertTrue(Double.isNaN(wt.getWeightedPopulationMean()));
assertTrue(Double.isNaN(wt.getWeightedSampleVariance()));
assertTrue(Double.isNaN(wt.getWeightedSampleStDev()));
assertEquals(0.0, wt.getWeightedSum(), 0.0);
assertEquals(0L, wt.getN());
// We fire a wrong event with wrong content
try
{
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", "ERROR", 0.0));
fail("tally should fail on events.value !instanceOf Double");
}
catch (Exception exception)
{
assertNotNull(exception);
}
// We fire a wrong event with wrong timestamp
try
{
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "ERROR", 1.0, Double.NaN));
fail("tally should fail on events.timestamp == NaN");
}
catch (Exception exception)
{
assertNotNull(exception);
}
// Now we fire some events
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.0, 0.0));
assertTrue(Double.isNaN(wt.getMin()));
assertTrue(Double.isNaN(wt.getMax()));
assertTrue(Double.isNaN(wt.getWeightedSampleMean()));
assertTrue(Double.isNaN(wt.getWeightedPopulationMean()));
assertTrue(Double.isNaN(wt.getWeightedSampleVariance()));
assertTrue(Double.isNaN(wt.getWeightedSampleStDev()));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.1, 0.1));
assertEquals(1.0, wt.getMin(), 0.000001);
assertEquals(1.0, wt.getMax(), 0.000001);
assertEquals(1.0, wt.getWeightedSampleMean(), 0.000001);
assertEquals(1.0, wt.getWeightedPopulationMean(), 0.000001);
assertTrue(Double.isNaN(wt.getWeightedSampleVariance()));
assertTrue(Double.isNaN(wt.getWeightedSampleStDev()));
assertEquals(0, wt.getWeightedPopulationVariance(), 0.000001);
assertEquals(0, wt.getWeightedPopulationStDev(), 0.0000001);
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.2, 0.2));
assertFalse(Double.isNaN(wt.getWeightedSampleVariance()));
assertFalse(Double.isNaN(wt.getWeightedSampleStDev()));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.3, 0.3));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.4, 0.4));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.5, 0.5));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.6, 0.6));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.7, 0.7));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.8, 0.8));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 1.9, 0.9));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 2.0, 1.0));
try
{
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 123.456, 0.8));
fail("timestamp out of order should have thrown an exception");
}
catch (IllegalArgumentException iae)
{
// Ignore expected exception
}
assertTrue(wt.isActive());
wt.endObservations(1.1);
assertFalse(wt.isActive());
// Now we check the EventBasedTimestampWeightedTally
assertEquals(2.0, wt.getMax(), 1.0E-6);
assertEquals(1.0, wt.getMin(), 1.0E-6);
assertEquals(11, wt.getN());
assertEquals(1.5 * 0.1 * 11, wt.getWeightedSum(), 1.0E-6);
assertEquals(1.5, wt.getWeightedSampleMean(), 1.0E-6);
// Let's compute the standard deviation
double varianceAccumulator = 0;
for (int i = 0; i < 11; i++)
{
varianceAccumulator += Math.pow(1.5 - (1.0 + i / 10.0), 2);
}
double variance = varianceAccumulator / 10.0;
double stDev = Math.sqrt(variance);
assertEquals(variance, wt.getWeightedSampleVariance(), 1.0E-6);
assertEquals(stDev, wt.getWeightedSampleStDev(), 1.0E-6);
variance = varianceAccumulator / 11.0;
stDev = Math.sqrt(variance);
assertEquals(variance, wt.getWeightedPopulationVariance(), 1.0E-6);
assertEquals(stDev, wt.getWeightedPopulationStDev(), 1.0E-6);
// Adding something after the active period should not make a change
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 10.0, 20.0));
assertFalse(wt.isActive());
assertEquals(2.0, wt.getMax(), 1.0E-6);
assertEquals(1.0, wt.getMin(), 1.0E-6);
assertEquals(11, wt.getN());
assertEquals(1.5 * 0.1 * 11, wt.getWeightedSum(), 1.0E-6);
assertEquals(1.5, wt.getWeightedSampleMean(), 1.0E-6);
// test some wrong events
try
{
wt.notify(new Event(new EventType("VALUE_EVENT", new MetaData("VALUE_EVENT", "non-timed event")),
"EventBasedTimestampWeightedTallyTest", 123.456));
fail("non time-based event should have thrown an exception");
}
catch (Exception e)
{
// Ignore expected exception
}
try
{
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 123.456, "abc"));
fail("non time-based evenevent with timestamp != Calendar or Number should have thrown an exception");
}
catch (Exception e)
{
// Ignore expected exception
}
}
/** Test the EventBasedTimestampWeightedTally on a simple example. */
@Test
public void testEventBasedTimestampWeightedTallySimple()
{
// From: https://sciencing.com/calculate-time-decimals-5962681.html
EventBasedTimestampWeightedTally wt =
new EventBasedTimestampWeightedTally("simple EventBasedTimestampWeightedTally statistic");
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 86.0, 0.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 26.0, 13.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 0.0, 36.0));
wt.endObservations(40.0);
assertEquals(1716.0, wt.getWeightedSum(), 0.001);
assertEquals(42.9, wt.getWeightedSampleMean(), 0.001);
assertEquals(3, wt.getN());
// When we shift the times, we should get the same answers
wt = new EventBasedTimestampWeightedTally("simple EventBasedTimestampWeightedTally statistic");
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 86.0, 10.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 26.0, 23.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 0.0, 46.0));
wt.endObservations(50.0);
assertEquals(1716.0, wt.getWeightedSum(), 0.001);
assertEquals(42.9, wt.getWeightedSampleMean(), 0.001);
assertEquals(3, wt.getN());
// When we have observations with duration 0, we should get the same answers
wt = new EventBasedTimestampWeightedTally("simple EventBasedTimestampWeightedTally statistic");
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 86.0, 0.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 26.0, 13.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 0.0, 13.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 26.0, 13.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 0.0, 36.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 0.0, 36.0));
wt.endObservations(40.0);
assertEquals(1716.0, wt.getWeightedSum(), 0.001);
assertEquals(42.9, wt.getWeightedSampleMean(), 0.001);
assertEquals(3, wt.getN()); // non-zero values only
// Example from NIST: https://www.itl.nist.gov/div898/software/dataplot/refman2/ch2/weightsd.pdf
wt = new EventBasedTimestampWeightedTally("NIST");
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 2, 0.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 3, 1.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 5, 2.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 7, 2.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 11, 2.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 13, 6.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 17, 7.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 19, 9.0));
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", 23, 10.0));
wt.endObservations(10.0);
assertEquals((2 + 3 + 4 * 11 + 13 + 2 * 17 + 19) / 10.0, wt.getWeightedSampleMean(), 0.001);
assertEquals(5.82, wt.getWeightedSampleStDev(), 0.01);
}
/** Test the TimestampWeightedTally for Calendar-based timestamps. */
@Test
public void testEBTimestampWeightedTallyCalendarNotify()
{
String description = "THIS TIMESTAMP WEIGHTED TALLY IS TESTED";
EventBasedTimestampWeightedTally wt = new EventBasedTimestampWeightedTally(description);
int index = 10;
for (int second = 30; second <= 40; second++)
{
Calendar calendar = new Calendar.Builder().setDate(2000, 2, 2).setTimeOfDay(4, 12, second, 10).build();
wt.notify(new TimedEvent(TIMED_VALUE_EVENT, "EventBasedTimestampWeightedTallyTest", index++, calendar));
}
assertTrue(wt.isActive());
wt.endObservations(new Calendar.Builder().setDate(2000, 2, 2).setTimeOfDay(4, 12, 41, 10).build());
assertFalse(wt.isActive());
// Now we check the TimestampWeightedTally
assertEquals(20.0, wt.getMax(), 1.0E-6);
assertEquals(10.0, wt.getMin(), 1.0E-6);
assertEquals(11, wt.getN());
assertEquals(1.5 * 10000 * 11, wt.getWeightedSum(), 1.0E-2);
assertEquals(15.0, wt.getWeightedSampleMean(), 1.0E-6);
// Let's compute the standard deviation
double variance = 0;
for (int i = 0; i < 11; i++)
{
variance += Math.pow(15.0 - (10 + i), 2);
}
variance = variance / 10.0; // n - 1
double stDev = Math.sqrt(variance);
assertEquals(variance, wt.getWeightedSampleVariance(), 1.0E-6);
assertEquals(stDev, wt.getWeightedSampleStDev(), 1.0E-6);
}
/** Test the TimestampWeightedTally for Calendar-based timestamps with ingest(). */
@Test
public void testEBTimestampWeightedTallyCalendarIngest()
{
String description = "THIS TIMESTAMP WEIGHTED TALLY IS TESTED";
EventBasedTimestampWeightedTally wt = new EventBasedTimestampWeightedTally(description);
int index = 10;
for (int second = 30; second <= 40; second++)
{
Calendar calendar = new Calendar.Builder().setDate(2000, 2, 2).setTimeOfDay(4, 12, second, 10).build();
wt.ingest(calendar, index++);
}
assertTrue(wt.isActive());
wt.endObservations(new Calendar.Builder().setDate(2000, 2, 2).setTimeOfDay(4, 12, 41, 10).build());
assertFalse(wt.isActive());
// Now we check the TimestampWeightedTally
assertEquals(20.0, wt.getMax(), 1.0E-6);
assertEquals(10.0, wt.getMin(), 1.0E-6);
assertEquals(11, wt.getN());
assertEquals(1.5 * 10000 * 11, wt.getWeightedSum(), 1.0E-2);
assertEquals(15.0, wt.getWeightedSampleMean(), 1.0E-6);
// Let's compute the standard deviation
double variance = 0;
for (int i = 0; i < 11; i++)
{
variance += Math.pow(15.0 - (10 + i), 2);
}
variance = variance / 10.0; // n - 1
double stDev = Math.sqrt(variance);
assertEquals(variance, wt.getWeightedSampleVariance(), 1.0E-6);
assertEquals(stDev, wt.getWeightedSampleStDev(), 1.0E-6);
}
/**
* Test produced events by EventBasedWeightedTally.
*/
@Test
public void testWeightedTallyEventProduction()
{
EventBasedTimestampWeightedTally timestampedTally = new EventBasedTimestampWeightedTally("testTally");
assertEquals(timestampedTally, timestampedTally.getSourceId());
TimestampedObservationEventListener toel = new TimestampedObservationEventListener();
timestampedTally.addListener(toel, StatisticsEvents.TIMESTAMPED_OBSERVATION_ADDED_EVENT);
assertEquals(0, toel.getObservationEvents());
TimedEventType[] types = new TimedEventType[] {StatisticsEvents.TIMED_N_EVENT, StatisticsEvents.TIMED_MIN_EVENT,
StatisticsEvents.TIMED_MAX_EVENT, StatisticsEvents.TIMED_WEIGHTED_POPULATION_MEAN_EVENT,
StatisticsEvents.TIMED_WEIGHTED_POPULATION_VARIANCE_EVENT,
StatisticsEvents.TIMED_WEIGHTED_POPULATION_STDEV_EVENT, StatisticsEvents.TIMED_WEIGHTED_SUM_EVENT,
StatisticsEvents.TIMED_WEIGHTED_SAMPLE_MEAN_EVENT, StatisticsEvents.TIMED_WEIGHTED_SAMPLE_VARIANCE_EVENT,
StatisticsEvents.TIMED_WEIGHTED_SAMPLE_STDEV_EVENT};
LoggingEventListener[] listeners = new LoggingEventListener[types.length];
for (int i = 0; i < types.length; i++)
{
listeners[i] = new LoggingEventListener();
timestampedTally.addListener(listeners[i], types[i]);
}
double prevTime = 0.0;
for (int i = 1; i <= 10; i++)
{
timestampedTally.ingest(prevTime, 10.0 * i);
prevTime += i;
}
timestampedTally.endObservations(prevTime);
// the endObservation fires an observation event, but does not increase N
assertEquals(11, toel.getObservationEvents());
// values based on formulas from https://www.itl.nist.gov/div898/software/dataplot/refman2/ch2/weightsd.pdf
Object[] expectedValues = new Object[] {10L, 10.0, 100.0, 70.0, 600.0, 24.4949, 3850.0, 70.0, 666.6667, 25.81989};
for (int i = 0; i < types.length; i++)
{
assertEquals("Number of events for listener " + types[i], 11, listeners[i].getNumberOfEvents());
assertEquals("Event sourceId for listener " + types[i], timestampedTally,
listeners[i].getLastEvent().getSourceId());
assertEquals("Event type for listener " + types[i], types[i], listeners[i].getLastEvent().getType());
if (expectedValues[i] instanceof Long)
{
assertEquals("Final value for listener " + types[i], expectedValues[i],
listeners[i].getLastEvent().getContent());
}
else
{
double e = ((Double) expectedValues[i]).doubleValue();
double c = ((Double) listeners[i].getLastEvent().getContent()).doubleValue();
assertEquals("Final value for listener " + types[i], e, c, 0.001);
}
}
}
/** The listener that counts the OBSERVATION_ADDED_EVENT events and checks correctness. */
class TimestampedObservationEventListener implements EventListenerInterface
{
/** */
private static final long serialVersionUID = 1L;
/** counter for the event. */
private int observationEvents = 0;
@Override
public void notify(final EventInterface event)
{
assertTrue(event.getType().equals(StatisticsEvents.TIMESTAMPED_OBSERVATION_ADDED_EVENT));
assertTrue("Content of the event has a wrong type, not Object[]: " + event.getContent().getClass(),
event.getContent() instanceof Object[]);
Object[] c = (Object[]) event.getContent();
assertTrue("Content[0] of the event has a wrong type, not double: " + c[0].getClass(), c[0] instanceof Double);
assertTrue("Content[1] of the event has a wrong type, not double: " + c[1].getClass(), c[1] instanceof Double);
assertTrue("SourceId of the event has a wrong type, not EventBasedTimestampWeightedTally: "
+ event.getSourceId().getClass(), event.getSourceId() instanceof EventBasedTimestampWeightedTally);
this.observationEvents++;
}
/**
* @return countEvents
*/
public int getObservationEvents()
{
return this.observationEvents;
}
}
}