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-2021 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      /** lastTimestamp stores the time of the last observation. Stored separately to avoid ulp rounding errors and allow ==. */
31      private double lastTimestamp = Double.NaN;
32  
33      /** lastValue tracks the last value. */
34      private double lastValue = Double.NaN;
35  
36      /** indicate whether the statistic is active or not (false before first event and after last event). */
37      private boolean active = false;
38  
39      /**
40       * constructs a new TimestampWeightedTally with a description.
41       * @param description String; the description of this TimestampWeightedTally
42       */
43      public TimestampWeightedTally(final String description)
44      {
45          this.wrappedWeightedTally = new WeightedTally(description);
46          initialize();
47      }
48  
49      /** {@inheritDoc} */
50      @Override
51      public void initialize()
52      {
53          synchronized (this.wrappedWeightedTally.semaphore)
54          {
55              this.wrappedWeightedTally.initialize();
56              this.startTime = Double.NaN;
57              this.lastTimestamp = Double.NaN;
58              this.lastValue = 0.0;
59              this.active = true;
60          }
61      }
62  
63      /** {@inheritDoc} */
64      @Override
65      public final boolean isActive()
66      {
67          return this.active;
68      }
69  
70      /** {@inheritDoc} */
71      @Override
72      public final void endObservations(final Number timestamp)
73      {
74          ingest(timestamp, this.lastValue);
75          this.active = false;
76      }
77  
78      /** {@inheritDoc} */
79      @Override
80      public void endObservations(final Calendar timestamp)
81      {
82          endObservations(timestamp.getTimeInMillis());
83      }
84  
85      /**
86       * Return the last observed value.
87       * @return double; the last observed value
88       */
89      public double getLastValue()
90      {
91          return this.lastValue;
92      }
93  
94      /**
95       * Process one observed value.
96       * @param timestamp Calendar; the Calendar object representing the timestamp
97       * @param value double; the value to process
98       * @return double; the value
99       */
100     public double ingest(final Calendar timestamp, final double value)
101     {
102         Throw.whenNull(timestamp, "timestamp object may not be null");
103         return ingest(timestamp.getTimeInMillis(), value);
104     }
105 
106     /**
107      * Process one observed value.
108      * @param timestamp Number; the object representing the timestamp
109      * @param value double; the value to process
110      * @return double; the value
111      */
112     public double ingest(final Number timestamp, final double value)
113     {
114         Throw.whenNull(timestamp, "timestamp object may not be null");
115         Throw.when(Double.isNaN(value), IllegalArgumentException.class, "value may not be NaN");
116         double timestampDouble = timestamp.doubleValue();
117         Throw.when(Double.isNaN(timestampDouble), IllegalArgumentException.class, "timestamp may not be NaN");
118         Throw.when(timestampDouble < this.lastTimestamp, IllegalArgumentException.class,
119                 "times not offered in ascending order. Last time was " + this.lastTimestamp + ", new timestamp was "
120                         + timestampDouble);
121 
122         synchronized (this.wrappedWeightedTally.semaphore)
123         {
124             // only calculate anything when the time interval is larger than 0, and when the TimestampWeightedTally is active
125             if ((Double.isNaN(this.lastTimestamp) || timestampDouble > this.lastTimestamp) && this.active)
126             {
127                 if (Double.isNaN(this.startTime))
128                 {
129                     this.startTime = timestampDouble;
130                 }
131                 else
132                 {
133                     double deltaTime = Math.max(0.0, timestampDouble - this.lastTimestamp);
134                     this.wrappedWeightedTally.ingest(deltaTime, this.lastValue);
135                 }
136                 this.lastTimestamp = timestampDouble;
137             }
138             this.lastValue = value;
139             return value;
140         }
141     }
142 
143     /** {@inheritDoc} */
144     @Override
145     public final String getDescription()
146     {
147         return this.wrappedWeightedTally.getDescription();
148     }
149 
150     /** {@inheritDoc} */
151     @Override
152     public final long getN()
153     {
154         return this.wrappedWeightedTally.getN();
155     }
156 
157     /** {@inheritDoc} */
158     @Override
159     public final double getMax()
160     {
161         return this.wrappedWeightedTally.getMax();
162     }
163 
164     /** {@inheritDoc} */
165     @Override
166     public final double getMin()
167     {
168         return this.wrappedWeightedTally.getMin();
169     }
170 
171     /** {@inheritDoc} */
172     @Override
173     public final double getWeightedSampleMean()
174     {
175         return this.wrappedWeightedTally.getWeightedSampleMean();
176     }
177 
178     /** {@inheritDoc} */
179     @Override
180     public final double getWeightedSampleStDev()
181     {
182         return this.wrappedWeightedTally.getWeightedSampleStDev();
183     }
184 
185     /** {@inheritDoc} */
186     @Override
187     public final double getWeightedPopulationStDev()
188     {
189         return this.wrappedWeightedTally.getWeightedPopulationStDev();
190     }
191 
192     /** {@inheritDoc} */
193     @Override
194     public final double getWeightedSampleVariance()
195     {
196         return this.wrappedWeightedTally.getWeightedSampleVariance();
197     }
198 
199     /** {@inheritDoc} */
200     @Override
201     public final double getWeightedPopulationVariance()
202     {
203         return this.wrappedWeightedTally.getWeightedPopulationVariance();
204     }
205 
206     /** {@inheritDoc} */
207     @Override
208     public final double getWeightedSum()
209     {
210         return this.wrappedWeightedTally.getWeightedSum();
211     }
212 
213     /** {@inheritDoc} */
214     @Override
215     @SuppressWarnings("checkstyle:designforextension")
216     public String toString()
217     {
218         return this.wrappedWeightedTally.toString();
219     }
220 
221 }