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-2024 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      @Override
63      public void initialize()
64      {
65          synchronized (this.semaphore)
66          {
67              this.min = Double.NaN;
68              this.max = Double.NaN;
69              this.n = 0;
70              this.sumOfWeights = 0.0;
71              this.weightedMean = 0.0;
72              this.weightTimesVariance = 0.0;
73              this.weightedSum = 0.0;
74          }
75      }
76  
77      /**
78       * Process one observed weighted value.
79       * @param weight double; the weight of the value to process
80       * @param value double; the value to process
81       * @return double; the value
82       */
83      public double register(final double weight, final double value)
84      {
85          Throw.when(Double.isNaN(weight), IllegalArgumentException.class, "weight may not be NaN");
86          Throw.when(weight < 0, IllegalArgumentException.class, "weight may not be negative");
87          Throw.when(Double.isNaN(value), IllegalArgumentException.class, "value may not be NaN");
88          if (0.0 == weight)
89          {
90              return value;
91          }
92          synchronized (this.semaphore)
93          {
94              if (this.n == 0)
95              {
96                  this.min = value;
97                  this.max = value;
98              }
99              this.n++;
100             // Eq 47 in https://fanf2.user.srcf.net/hermes/doc/antiforgery/stats.pdf
101             this.sumOfWeights += weight;
102             double prevWeightedMean = this.weightedMean;
103             // Eq 53 in https://fanf2.user.srcf.net/hermes/doc/antiforgery/stats.pdf
104             this.weightedMean += weight / this.sumOfWeights * (value - prevWeightedMean);
105             // Eq 68 in https://fanf2.user.srcf.net/hermes/doc/antiforgery/stats.pdf
106             this.weightTimesVariance += weight * (value - prevWeightedMean) * (value - this.weightedMean);
107             this.weightedSum += weight * value;
108             if (value < this.min)
109             {
110                 this.min = value;
111             }
112             if (value > this.max)
113             {
114                 this.max = value;
115             }
116         }
117         return value;
118     }
119 
120     @Override
121     public String getDescription()
122     {
123         return this.description;
124     }
125 
126     @Override
127     public double getMax()
128     {
129         return this.max;
130     }
131 
132     @Override
133     public double getMin()
134     {
135         return this.min;
136     }
137 
138     @Override
139     public long getN()
140     {
141         return this.n;
142     }
143 
144     /**
145      * Retrieve the current weighted sample mean of all observations since the initialization.
146      * @return double; the current weighted sample mean
147      */
148     public double getWeightedSampleMean()
149     {
150         synchronized (this.semaphore)
151         {
152             if (this.n > 0)
153             {
154                 return this.weightedMean;
155             }
156             return Double.NaN;
157         }
158     }
159 
160     /**
161      * Retrieve the current weighted mean of all observations since the initialization.
162      * @return double; the current weighted mean
163      */
164     public double getWeightedPopulationMean()
165     {
166         return getWeightedSampleMean();
167     }
168 
169     /**
170      * Retrieve the current weighted sample standard deviation of the observations.
171      * @return double; the current weighted sample standard deviation
172      */
173     public double getWeightedSampleStDev()
174     {
175         synchronized (this.semaphore)
176         {
177             if (this.n > 1)
178             {
179                 return Math.sqrt(getWeightedSampleVariance());
180             }
181             return Double.NaN;
182         }
183     }
184 
185     /**
186      * Retrieve the current weighted standard deviation of the observations.
187      * @return double; the current weighted standard deviation
188      */
189     public double getWeightedPopulationStDev()
190     {
191         synchronized (this.semaphore)
192         {
193             return Math.sqrt(getWeightedPopulationVariance());
194         }
195     }
196 
197     /**
198      * Retrieve the current weighted sample variance of the observations.
199      * @return double; the current weighted sample variance of the observations
200      */
201     public double getWeightedSampleVariance()
202     {
203         synchronized (this.semaphore)
204         {
205             if (this.n > 1)
206             {
207                 return getWeightedPopulationVariance() * this.n / (this.n - 1);
208             }
209             return Double.NaN;
210         }
211     }
212 
213     /**
214      * Retrieve the current weighted variance of the observations.
215      * @return double; the current weighted variance of the observations
216      */
217     public double getWeightedPopulationVariance()
218     {
219         synchronized (this.semaphore)
220         {
221             return this.weightTimesVariance / this.sumOfWeights;
222         }
223     }
224 
225     /**
226      * Retrieve the current weighted sum of the values of the observations.
227      * @return double; the current weighted sum of the values of the observations
228      */
229     public double getWeightedSum()
230     {
231         return this.weightedSum;
232     }
233 
234     /**
235      * Return a string representing a header for a textual table with a monospaced font that can contain multiple statistics.
236      * @return String; header for the textual table.
237      */
238     public static String reportHeader()
239     {
240         return "-".repeat(113) + String.format("%n| %-48.48s | %6.6s | %10.10s | %10.10s | %10.10s | %10.10s |%n",
241                 "Weighted Tally name", "n", "w.mean", "w.st.dev", "min obs", "max obs") + "-".repeat(113);
242     }
243 
244     @Override
245     public String reportLine()
246     {
247         return String.format("| %-48.48s | %6d | %s | %s | %s | %s |", getDescription(), getN(),
248                 formatFixed(getWeightedPopulationMean(), 10), formatFixed(getWeightedPopulationStDev(), 10),
249                 formatFixed(getMin(), 10), formatFixed(getMax(), 10));
250     }
251 
252     /**
253      * Return a string representing a footer for a textual table with a monospaced font that can contain multiple statistics.
254      * @return String; footer for the textual table
255      */
256     public static String reportFooter()
257     {
258         return "-".repeat(113);
259     }
260 
261     @Override
262     @SuppressWarnings("checkstyle:designforextension")
263     public String toString()
264     {
265         return "WeightedTally [sumOfWeights=" + this.sumOfWeights + ", weightedMean=" + this.weightedMean + ", weightedSum="
266                 + this.weightedSum + ", weightTimesVariance=" + this.weightTimesVariance + ", min=" + this.min + ", max="
267                 + this.max + ", n=" + this.n + ", description=" + this.description + "]";
268     }
269 
270 }