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