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