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 }