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 }