View Javadoc
1   package org.djutils.stats.summarizers;
2   
3   import java.util.Calendar;
4   
5   import org.djutils.exceptions.Throw;
6   
7   /**
8    * The TimestampWeightedTally class defines a time-weighted tally based on timestamped data. The difference with a normal
9    * time-weighed tally is that the weight of a value is only known at the occurrence of the next timestamp. Furthermore, a last
10   * timestamp needs to be specified to determine the weight of the last value. Timestamps can be Number based or Calendar based.
11   * <p>
12   * Copyright (c) 2020-2020 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
13   * for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>. The DSOL
14   * project is distributed under a three-clause BSD-style license, which can be found at
15   * <a href="https://simulation.tudelft.nl/dsol/3.0/license.html" target="_blank">
16   * https://simulation.tudelft.nl/dsol/3.0/license.html</a>. <br>
17   * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank"> Alexander Verbraeck</a>
18   */
19  public class TimestampWeightedTally implements TimestampTallyInterface
20  {
21      /** */
22      private static final long serialVersionUID = 20200228L;
23  
24      /** the wrapped weighted tally. */
25      private WeightedTally wrappedWeightedTally;
26  
27      /** startTime defines the time of the first observation. Often equals to 0.0, but can also have other value. */
28      private double startTime = Double.NaN;
29  
30      /** elapsedTime tracks the time during which we have calculated observations. This excludes the start time. */
31      private double elapsedTime = Double.NaN;
32  
33      /** lastTimestamp stores the time of the last observation. Stored separately to avoid ulp rounding errors and allow ==. */
34      private double lastTimestamp = Double.NaN;
35  
36      /** lastValue tracks the last value. */
37      private double lastValue = Double.NaN;
38  
39      /** indicate whether the statistic is active or not (false before first event and after last event). */
40      private boolean active = false;
41  
42      /**
43       * constructs a new TimestampWeightedTally with a description.
44       * @param description String; the description of this TimestampWeightedTally
45       */
46      public TimestampWeightedTally(final String description)
47      {
48          this.wrappedWeightedTally = new WeightedTally(description);
49          initialize();
50      }
51  
52      /** {@inheritDoc} */
53      @Override
54      public void initialize()
55      {
56          synchronized (this.wrappedWeightedTally.semaphore)
57          {
58              this.wrappedWeightedTally.initialize();
59              this.startTime = Double.NaN;
60              this.elapsedTime = 0.0;
61              this.lastTimestamp = Double.NaN;
62              this.lastValue = 0.0;
63              this.active = true;
64          }
65      }
66  
67      /** {@inheritDoc} */
68      @Override
69      public final boolean isActive()
70      {
71          return this.active;
72      }
73  
74      /** {@inheritDoc} */
75      @Override
76      public final void endObservations(final Number timestamp)
77      {
78          ingest(timestamp, this.lastValue);
79          this.active = false;
80      }
81  
82      /** {@inheritDoc} */
83      @Override
84      public void endObservations(final Calendar timestamp)
85      {
86          endObservations(timestamp.getTimeInMillis());
87      }
88  
89      /**
90       * Return the last observed value.
91       * @return double; the last observed value
92       */
93      public double getLastValue()
94      {
95          return this.lastValue;
96      }
97  
98      /**
99       * Process one observed value.
100      * @param timestamp Calendar; the Calendar object representing the timestamp
101      * @param value double; the value to process
102      * @return double; the value
103      */
104     public double ingest(final Calendar timestamp, final double value)
105     {
106         Throw.whenNull(timestamp, "timestamp object may not be null");
107         return ingest(timestamp.getTimeInMillis(), value);
108     }
109 
110     /**
111      * Process one observed value.
112      * @param timestamp Number; the object representing the timestamp
113      * @param value double; the value to process
114      * @return double; the value
115      */
116     public double ingest(final Number timestamp, final double value)
117     {
118         Throw.whenNull(timestamp, "timestamp object may not be null");
119         Throw.when(Double.isNaN(value), IllegalArgumentException.class, "value may not be NaN");
120         double timestampDouble = timestamp.doubleValue();
121         Throw.when(Double.isNaN(timestampDouble), IllegalArgumentException.class, "timestamp may not be NaN");
122         Throw.when(timestampDouble < (this.elapsedTime + this.startTime), IllegalArgumentException.class,
123                 "times not offered in ascending order. Last time was " + (this.elapsedTime + this.startTime)
124                         + ", new timestamp was " + timestampDouble);
125 
126         synchronized (this.wrappedWeightedTally.semaphore)
127         {
128             // only calculate anything when the time interval is larger than 0, and when the TimestampWeightedTally is active
129             if ((Double.isNaN(this.lastTimestamp) || timestampDouble > this.lastTimestamp) && this.active)
130             {
131                 if (Double.isNaN(this.startTime))
132                 {
133                     this.startTime = timestampDouble;
134                     this.elapsedTime = 0.0;
135                 }
136                 else
137                 {
138                     double deltaTime = timestampDouble - this.lastTimestamp;
139                     this.elapsedTime += deltaTime;
140                     this.wrappedWeightedTally.ingest(deltaTime, this.lastValue);
141                 }
142                 this.lastTimestamp = timestampDouble;
143             }
144             this.lastValue = value;
145             return value;
146         }
147     }
148 
149     /** {@inheritDoc} */
150     @Override
151     public final String getDescription()
152     {
153         return this.wrappedWeightedTally.getDescription();
154     }
155 
156     /** {@inheritDoc} */
157     @Override
158     public final long getN()
159     {
160         return this.wrappedWeightedTally.getN();
161     }
162 
163     /** {@inheritDoc} */
164     @Override
165     public final double getMax()
166     {
167         return this.wrappedWeightedTally.getMax();
168     }
169 
170     /** {@inheritDoc} */
171     @Override
172     public final double getMin()
173     {
174         return this.wrappedWeightedTally.getMin();
175     }
176 
177     /** {@inheritDoc} */
178     @Override
179     public final double getWeightedSampleMean()
180     {
181         return this.wrappedWeightedTally.getWeightedSampleMean();
182     }
183 
184     /** {@inheritDoc} */
185     @Override
186     public final double getWeightedSampleStDev()
187     {
188         return this.wrappedWeightedTally.getWeightedSampleStDev();
189     }
190 
191     /** {@inheritDoc} */
192     @Override
193     public final double getWeightedPopulationStDev()
194     {
195         return this.wrappedWeightedTally.getWeightedPopulationStDev();
196     }
197 
198     /** {@inheritDoc} */
199     @Override
200     public final double getWeightedSampleVariance()
201     {
202         return this.wrappedWeightedTally.getWeightedSampleVariance();
203     }
204 
205     /** {@inheritDoc} */
206     @Override
207     public final double getWeightedPopulationVariance()
208     {
209         return this.wrappedWeightedTally.getWeightedPopulationVariance();
210     }
211 
212     /** {@inheritDoc} */
213     @Override
214     public final double getWeightedSum()
215     {
216         return this.wrappedWeightedTally.getWeightedSum();
217     }
218 
219     /** {@inheritDoc} */
220     @Override
221     @SuppressWarnings("checkstyle:designforextension")
222     public String toString()
223     {
224         return this.wrappedWeightedTally.toString();
225     }
226 
227 }