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-2023 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>.
17   * </p>
18   * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank"> Alexander Verbraeck</a>
19   */
20  public class TimestampWeightedTally extends WeightedTally
21  {
22      /** */
23      private static final long serialVersionUID = 20200228L;
24  
25      /** startTime defines the time of the first observation. Often equals to 0.0, but can also have other value. */
26      private double startTime;
27  
28      /** lastTimestamp stores the time of the last observation. Stored separately to avoid ulp rounding errors and allow ==. */
29      private double lastTimestamp;
30  
31      /** lastValue tracks the last value. */
32      private double lastValue;
33  
34      /** indicate whether the statistic is active or not (false before first event and after last event). */
35      private boolean active;
36  
37      /**
38       * constructs a new TimestampWeightedTally with a description.
39       * @param description String; the description of this TimestampWeightedTally
40       */
41      public TimestampWeightedTally(final String description)
42      {
43          super(description);
44      }
45  
46      /** {@inheritDoc} */
47      @Override
48      public void initialize()
49      {
50          synchronized (super.semaphore)
51          {
52              super.initialize();
53              this.startTime = Double.NaN;
54              this.lastTimestamp = Double.NaN;
55              this.lastValue = 0.0;
56              this.active = true;
57          }
58      }
59  
60      /**
61       * Return whether the statistic is active (accepting observations) or not.
62       * @return boolean; whether the statistic is active (accepting observations) or not
63       */
64      public boolean isActive()
65      {
66          return this.active;
67      }
68  
69      /**
70       * End the observations and closes the last interval of observations. After ending, no more observations will be accepted.
71       * Calling this method will create an extra observation, and corresponding events for the EventBased implementations of this
72       * interface will be called.
73       * @param timestamp Number; the Number object representing the final timestamp
74       */
75      public void endObservations(final Number timestamp)
76      {
77          register(timestamp, this.lastValue);
78          this.active = false;
79      }
80  
81      /**
82       * End the observations and closes the last interval of observations. After ending, no more observations will be accepted.
83       * Calling this method will create an extra observation, and corresponding events for the EventBased implementations of this
84       * interface will be called.
85       * @param timestamp Calendar; the Calendar object representing the final timestamp
86       */
87      public void endObservations(final Calendar timestamp)
88      {
89          register(timestamp, this.lastValue);
90          this.active = false;
91      }
92  
93      /**
94       * Return the last observed value.
95       * @return double; the last observed value
96       */
97      public double getLastValue()
98      {
99          return this.lastValue;
100     }
101 
102     /**
103      * Process one observed Calender-based value. The time used will be the Calendar's time in milliseconds. Silently ignore
104      * when a value is registered, but tally is not active, i.e. when endObservations() has been called.
105      * @param timestamp Calendar; the Calendar object representing the timestamp
106      * @param value double; the value to process
107      * @return double; the value
108      * @throws NullPointerException when timestamp is null
109      * @throws IllegalArgumentException when value is NaN
110      * @throws IllegalArgumentException when given timestamp is before last timestamp
111      */
112     public double register(final Calendar timestamp, final double value)
113     {
114         Throw.whenNull(timestamp, "timestamp object may not be null");
115         return registerValue(timestamp.getTimeInMillis(), value);
116     }
117 
118     /**
119      * Process one observed Number-based value. Silently ignore when a value is registered, but tally is not active, i.e. when
120      * endObservations() has been called.
121      * @param timestamp Number; the object representing the timestamp
122      * @param value double; the value to process
123      * @return double; the value
124      * @throws NullPointerException when timestamp is null
125      * @throws IllegalArgumentException when value is NaN or timestamp is NaN
126      * @throws IllegalArgumentException when given timestamp is before last timestamp
127      */
128     public double register(final Number timestamp, final double value)
129     {
130         return registerValue(timestamp, value);
131     }
132 
133     /**
134      * Explicit;y override the double value method signature of WeightedTally to call the right method.<br>
135      * Process one observed double value. Silently ignore when a value is registered, but tally is not active, i.e. when
136      * endObservations() has been called.
137      * @param timestamp Number; the object representing the timestamp
138      * @param value double; the value to process
139      * @return double; the value
140      * @throws NullPointerException when timestamp is null
141      * @throws IllegalArgumentException when value is NaN or timestamp is NaN
142      * @throws IllegalArgumentException when given timestamp is before last timestamp
143      */
144     @Override
145     public double register(final double timestamp, final double value)
146     {
147         return registerValue(timestamp, value);
148     }
149 
150     /**
151      * Process one observed Number-based value. Silently ignore when a value is registered, but tally is not active, i.e. when
152      * endObservations() has been called.
153      * @param timestamp Number; the object representing the timestamp
154      * @param value double; the value to process
155      * @return double; the value
156      * @throws NullPointerException when timestamp is null
157      * @throws IllegalArgumentException when value is NaN or timestamp is NaN
158      * @throws IllegalArgumentException when given timestamp is before last timestamp
159      */
160     protected double registerValue(final Number timestamp, final double value)
161     {
162         Throw.whenNull(timestamp, "timestamp object may not be null");
163         Throw.when(Double.isNaN(value), IllegalArgumentException.class, "value may not be NaN");
164         double timestampDouble = timestamp.doubleValue();
165         Throw.when(Double.isNaN(timestampDouble), IllegalArgumentException.class, "timestamp may not be NaN");
166         Throw.when(timestampDouble < this.lastTimestamp, IllegalArgumentException.class,
167                 "times not offered in ascending order. Last time was " + this.lastTimestamp + ", new timestamp was "
168                         + timestampDouble);
169 
170         synchronized (super.semaphore)
171         {
172             // only calculate anything when the time interval is larger than 0, and when the TimestampWeightedTally is active
173             if ((Double.isNaN(this.lastTimestamp) || timestampDouble > this.lastTimestamp) && this.active)
174             {
175                 if (Double.isNaN(this.startTime))
176                 {
177                     this.startTime = timestampDouble;
178                 }
179                 else
180                 {
181                     double deltaTime = Math.max(0.0, timestampDouble - this.lastTimestamp);
182                     super.register(deltaTime, this.lastValue);
183                 }
184                 this.lastTimestamp = timestampDouble;
185             }
186             this.lastValue = value;
187             return value;
188         }
189     }
190 
191     /**
192      * Return a string representing a header for a textual table with a monospaced font that can contain multiple statistics.
193      * @return String; header for the textual table.
194      */
195     public static String reportHeader()
196     {
197         return "-".repeat(126)
198                 + String.format("%n| %-48.48s | %6.6s | %10.10s | %10.10s | %10.10s | %10.10s | %10.10s |%n",
199                         "Timestamp-based weighted Tally name", "n", "interval", "w.mean", "w.st.dev", "min obs", "max obs")
200                 + "-".repeat(126);
201     }
202 
203     /** {@inheritDoc} */
204     @Override
205     public String reportLine()
206     {
207         return String.format("| %-48.48s | %6d | %s | %s | %s | %s | %s |", getDescription(), getN(),
208                 formatFixed(this.lastTimestamp - this.startTime, 10), formatFixed(getWeightedPopulationMean(), 10),
209                 formatFixed(getWeightedPopulationStDev(), 10), formatFixed(getMin(), 10), formatFixed(getMax(), 10));
210     }
211 
212     /**
213      * Return a string representing a footer for a textual table with a monospaced font that can contain multiple statistics.
214      * @return String; footer for the textual table
215      */
216     public static String reportFooter()
217     {
218         return "-".repeat(126);
219     }
220 
221     /** {@inheritDoc} */
222     @Override
223     public String toString()
224     {
225         return "TimestampWeightedTally" + super.toString().substring("WeightedTally".length());
226     }
227 
228 }