TimestampWeightedTally.java
package org.djutils.stats.summarizers;
import java.util.Calendar;
import org.djutils.exceptions.Throw;
/**
* The TimestampWeightedTally class defines a time-weighted tally based on timestamped data. The difference with a normal
* time-weighed tally is that the weight of a value is only known at the occurrence of the next timestamp. Furthermore, a last
* timestamp needs to be specified to determine the weight of the last value. Timestamps can be Number based or Calendar based.
* <p>
* Copyright (c) 2020-2021 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>. The DSOL
* project is distributed under a three-clause BSD-style license, which can be found at
* <a href="https://simulation.tudelft.nl/dsol/3.0/license.html" target="_blank">
* https://simulation.tudelft.nl/dsol/3.0/license.html</a>. <br>
* @author <a href="https://www.tudelft.nl/averbraeck" target="_blank"> Alexander Verbraeck</a>
*/
public class TimestampWeightedTally implements TimestampTallyInterface
{
/** */
private static final long serialVersionUID = 20200228L;
/** the wrapped weighted tally. */
private WeightedTally wrappedWeightedTally;
/** startTime defines the time of the first observation. Often equals to 0.0, but can also have other value. */
private double startTime = Double.NaN;
/** lastTimestamp stores the time of the last observation. Stored separately to avoid ulp rounding errors and allow ==. */
private double lastTimestamp = Double.NaN;
/** lastValue tracks the last value. */
private double lastValue = Double.NaN;
/** indicate whether the statistic is active or not (false before first event and after last event). */
private boolean active = false;
/**
* constructs a new TimestampWeightedTally with a description.
* @param description String; the description of this TimestampWeightedTally
*/
public TimestampWeightedTally(final String description)
{
this.wrappedWeightedTally = new WeightedTally(description);
initialize();
}
/** {@inheritDoc} */
@Override
public void initialize()
{
synchronized (this.wrappedWeightedTally.semaphore)
{
this.wrappedWeightedTally.initialize();
this.startTime = Double.NaN;
this.lastTimestamp = Double.NaN;
this.lastValue = 0.0;
this.active = true;
}
}
/** {@inheritDoc} */
@Override
public final boolean isActive()
{
return this.active;
}
/** {@inheritDoc} */
@Override
public final void endObservations(final Number timestamp)
{
ingest(timestamp, this.lastValue);
this.active = false;
}
/** {@inheritDoc} */
@Override
public void endObservations(final Calendar timestamp)
{
endObservations(timestamp.getTimeInMillis());
}
/**
* Return the last observed value.
* @return double; the last observed value
*/
public double getLastValue()
{
return this.lastValue;
}
/**
* Process one observed value.
* @param timestamp Calendar; the Calendar object representing the timestamp
* @param value double; the value to process
* @return double; the value
*/
public double ingest(final Calendar timestamp, final double value)
{
Throw.whenNull(timestamp, "timestamp object may not be null");
return ingest(timestamp.getTimeInMillis(), value);
}
/**
* Process one observed value.
* @param timestamp Number; the object representing the timestamp
* @param value double; the value to process
* @return double; the value
*/
public double ingest(final Number timestamp, final double value)
{
Throw.whenNull(timestamp, "timestamp object may not be null");
Throw.when(Double.isNaN(value), IllegalArgumentException.class, "value may not be NaN");
double timestampDouble = timestamp.doubleValue();
Throw.when(Double.isNaN(timestampDouble), IllegalArgumentException.class, "timestamp may not be NaN");
Throw.when(timestampDouble < this.lastTimestamp, IllegalArgumentException.class,
"times not offered in ascending order. Last time was " + this.lastTimestamp + ", new timestamp was "
+ timestampDouble);
synchronized (this.wrappedWeightedTally.semaphore)
{
// only calculate anything when the time interval is larger than 0, and when the TimestampWeightedTally is active
if ((Double.isNaN(this.lastTimestamp) || timestampDouble > this.lastTimestamp) && this.active)
{
if (Double.isNaN(this.startTime))
{
this.startTime = timestampDouble;
}
else
{
double deltaTime = Math.max(0.0, timestampDouble - this.lastTimestamp);
this.wrappedWeightedTally.ingest(deltaTime, this.lastValue);
}
this.lastTimestamp = timestampDouble;
}
this.lastValue = value;
return value;
}
}
/** {@inheritDoc} */
@Override
public final String getDescription()
{
return this.wrappedWeightedTally.getDescription();
}
/** {@inheritDoc} */
@Override
public final long getN()
{
return this.wrappedWeightedTally.getN();
}
/** {@inheritDoc} */
@Override
public final double getMax()
{
return this.wrappedWeightedTally.getMax();
}
/** {@inheritDoc} */
@Override
public final double getMin()
{
return this.wrappedWeightedTally.getMin();
}
/** {@inheritDoc} */
@Override
public final double getWeightedSampleMean()
{
return this.wrappedWeightedTally.getWeightedSampleMean();
}
/** {@inheritDoc} */
@Override
public final double getWeightedSampleStDev()
{
return this.wrappedWeightedTally.getWeightedSampleStDev();
}
/** {@inheritDoc} */
@Override
public final double getWeightedPopulationStDev()
{
return this.wrappedWeightedTally.getWeightedPopulationStDev();
}
/** {@inheritDoc} */
@Override
public final double getWeightedSampleVariance()
{
return this.wrappedWeightedTally.getWeightedSampleVariance();
}
/** {@inheritDoc} */
@Override
public final double getWeightedPopulationVariance()
{
return this.wrappedWeightedTally.getWeightedPopulationVariance();
}
/** {@inheritDoc} */
@Override
public final double getWeightedSum()
{
return this.wrappedWeightedTally.getWeightedSum();
}
/** {@inheritDoc} */
@Override
@SuppressWarnings("checkstyle:designforextension")
public String toString()
{
return this.wrappedWeightedTally.toString();
}
}