View Javadoc
1   package org.djutils.stats.summarizers;
2   
3   import org.djutils.exceptions.Throw;
4   
5   /**
6    * The WeightedTally class defines a statistical tally. A WeightedTally is a time-weighted tally. The WeightedTally used to
7    * extend the Tally, but because the calculation method and method signatures are different, the WeightedTally has been made
8    * self-contained.
9    * <p>
10   * Copyright (c) 2002-2023 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
11   * for project information <a href="https://simulation.tudelft.nl/" target="_blank"> https://simulation.tudelft.nl</a>. The DSOL
12   * project is distributed under a three-clause BSD-style license, which can be found at
13   * <a href="https://simulation.tudelft.nl/dsol/3.0/license.html" target="_blank">
14   * https://simulation.tudelft.nl/dsol/3.0/license.html</a>. <br>
15   * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank"> Alexander Verbraeck</a>
16   * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
17   */
18  public class WeightedTally implements TallyStatistic
19  {
20      /** */
21      private static final long serialVersionUID = 20200228L;
22  
23      /** The sum of the weights of this WeightedTally. */
24      private double sumOfWeights;
25  
26      /** The mean of this WeightedTally. */
27      private double weightedMean;
28  
29      /** The sum of this WeightedTally. */
30      private double weightedSum;
31  
32      /** The total registered weight times the variance of this WeightedTally. */
33      private double weightTimesVariance;
34  
35      /** The minimum observed value of this WeightedTally. */
36      private double min;
37  
38      /** The maximum observed value of this WeightedTally. */
39      private double max;
40  
41      /** The number of non-zero weight measurements of this WeightedTally. */
42      private long n;
43  
44      /** The description of this WeightedTally. */
45      private final String description;
46  
47      /** The synchronization lock. */
48      @SuppressWarnings("checkstyle:visibilitymodifier")
49      protected Object semaphore = new Object();
50  
51      /**
52       * Construct a new WeightedTally with a description.
53       * @param description String; the description of this WeightedTally
54       */
55      public WeightedTally(final String description)
56      {
57          Throw.whenNull(description, "description cannot be null");
58          this.description = description;
59          initialize();
60      }
61  
62      /** {@inheritDoc} */
63      @Override
64      public void initialize()
65      {
66          synchronized (this.semaphore)
67          {
68              this.min = Double.NaN;
69              this.max = Double.NaN;
70              this.n = 0;
71              this.sumOfWeights = 0.0;
72              this.weightedMean = 0.0;
73              this.weightTimesVariance = 0.0;
74              this.weightedSum = 0.0;
75          }
76      }
77  
78      /**
79       * Process one observed weighted value.
80       * @param weight double; the weight of the value to process
81       * @param value double; the value to process
82       * @return double; the value
83       */
84      public double register(final double weight, final double value)
85      {
86          Throw.when(Double.isNaN(weight), IllegalArgumentException.class, "weight may not be NaN");
87          Throw.when(weight < 0, IllegalArgumentException.class, "weight may not be negative");
88          Throw.when(Double.isNaN(value), IllegalArgumentException.class, "value may not be NaN");
89          if (0.0 == weight)
90          {
91              return value;
92          }
93          synchronized (this.semaphore)
94          {
95              if (this.n == 0)
96              {
97                  this.min = value;
98                  this.max = value;
99              }
100             this.n++;
101             // Eq 47 in https://fanf2.user.srcf.net/hermes/doc/antiforgery/stats.pdf
102             this.sumOfWeights += weight;
103             double prevWeightedMean = this.weightedMean;
104             // Eq 53 in https://fanf2.user.srcf.net/hermes/doc/antiforgery/stats.pdf
105             this.weightedMean += weight / this.sumOfWeights * (value - prevWeightedMean);
106             // Eq 68 in https://fanf2.user.srcf.net/hermes/doc/antiforgery/stats.pdf
107             this.weightTimesVariance += weight * (value - prevWeightedMean) * (value - this.weightedMean);
108             this.weightedSum += weight * value;
109             if (value < this.min)
110             {
111                 this.min = value;
112             }
113             if (value > this.max)
114             {
115                 this.max = value;
116             }
117         }
118         return value;
119     }
120 
121     /** {@inheritDoc} */
122     @Override
123     public String getDescription()
124     {
125         return this.description;
126     }
127 
128     /** {@inheritDoc} */
129     @Override
130     public double getMax()
131     {
132         return this.max;
133     }
134 
135     /** {@inheritDoc} */
136     @Override
137     public double getMin()
138     {
139         return this.min;
140     }
141 
142     /** {@inheritDoc} */
143     @Override
144     public long getN()
145     {
146         return this.n;
147     }
148 
149     /**
150      * Retrieve the current weighted sample mean of all observations since the initialization.
151      * @return double; the current weighted sample mean
152      */
153     public double getWeightedSampleMean()
154     {
155         synchronized (this.semaphore)
156         {
157             if (this.n > 0)
158             {
159                 return this.weightedMean;
160             }
161             return Double.NaN;
162         }
163     }
164 
165     /**
166      * Retrieve the current weighted mean of all observations since the initialization.
167      * @return double; the current weighted mean
168      */
169     public double getWeightedPopulationMean()
170     {
171         return getWeightedSampleMean();
172     }
173 
174     /**
175      * Retrieve the current weighted sample standard deviation of the observations.
176      * @return double; the current weighted sample standard deviation
177      */
178     public double getWeightedSampleStDev()
179     {
180         synchronized (this.semaphore)
181         {
182             if (this.n > 1)
183             {
184                 return Math.sqrt(getWeightedSampleVariance());
185             }
186             return Double.NaN;
187         }
188     }
189 
190     /**
191      * Retrieve the current weighted standard deviation of the observations.
192      * @return double; the current weighted standard deviation
193      */
194     public double getWeightedPopulationStDev()
195     {
196         synchronized (this.semaphore)
197         {
198             return Math.sqrt(getWeightedPopulationVariance());
199         }
200     }
201 
202     /**
203      * Retrieve the current weighted sample variance of the observations.
204      * @return double; the current weighted sample variance of the observations
205      */
206     public double getWeightedSampleVariance()
207     {
208         synchronized (this.semaphore)
209         {
210             if (this.n > 1)
211             {
212                 return getWeightedPopulationVariance() * this.n / (this.n - 1);
213             }
214             return Double.NaN;
215         }
216     }
217 
218     /**
219      * Retrieve the current weighted variance of the observations.
220      * @return double; the current weighted variance of the observations
221      */
222     public double getWeightedPopulationVariance()
223     {
224         synchronized (this.semaphore)
225         {
226             return this.weightTimesVariance / this.sumOfWeights;
227         }
228     }
229 
230     /**
231      * Retrieve the current weighted sum of the values of the observations.
232      * @return double; the current weighted sum of the values of the observations
233      */
234     public double getWeightedSum()
235     {
236         return this.weightedSum;
237     }
238 
239     /**
240      * Return a string representing a header for a textual table with a monospaced font that can contain multiple statistics.
241      * @return String; header for the textual table.
242      */
243     public static String reportHeader()
244     {
245         return "-".repeat(113) + String.format("%n| %-48.48s | %6.6s | %10.10s | %10.10s | %10.10s | %10.10s |%n",
246                 "Weighted Tally name", "n", "w.mean", "w.st.dev", "min obs", "max obs") + "-".repeat(113);
247     }
248 
249     /** {@inheritDoc} */
250     @Override
251     public String reportLine()
252     {
253         return String.format("| %-48.48s | %6d | %s | %s | %s | %s |", getDescription(), getN(),
254                 formatFixed(getWeightedPopulationMean(), 10), formatFixed(getWeightedPopulationStDev(), 10),
255                 formatFixed(getMin(), 10), formatFixed(getMax(), 10));
256     }
257 
258     /**
259      * Return a string representing a footer for a textual table with a monospaced font that can contain multiple statistics.
260      * @return String; footer for the textual table
261      */
262     public static String reportFooter()
263     {
264         return "-".repeat(113);
265     }
266 
267     /** {@inheritDoc} */
268     @Override
269     @SuppressWarnings("checkstyle:designforextension")
270     public String toString()
271     {
272         return "WeightedTally [sumOfWeights=" + this.sumOfWeights + ", weightedMean=" + this.weightedMean + ", weightedSum="
273                 + this.weightedSum + ", weightTimesVariance=" + this.weightTimesVariance + ", min=" + this.min + ", max="
274                 + this.max + ", n=" + this.n + ", description=" + this.description + "]";
275     }
276 
277 }