1 package org.opentrafficsim.core.distributions;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.List;
6
7 import org.djutils.exceptions.Throw;
8
9 import nl.tudelft.simulation.jstats.distributions.DistUniform;
10 import nl.tudelft.simulation.jstats.streams.StreamInterface;
11
12
13
14
15
16
17
18
19
20
21
22 public class Distribution<O> implements Generator<O>, Serializable
23 {
24
25 private static final long serialVersionUID = 20160301L;
26
27
28 private final List<FrequencyAndObject<O>> generators = new ArrayList<>();
29
30
31 private double cumulativeTotal;
32
33
34 private final DistUniform random;
35
36
37
38
39
40
41
42 public Distribution(final List<FrequencyAndObject<O>> generators, final StreamInterface stream) throws ProbabilityException
43 {
44 this(stream);
45 Throw.when(null == generators, ProbabilityException.class, "generators may not be null");
46
47
48 this.generators.addAll(generators);
49 fixProbabilities();
50 }
51
52
53
54
55
56
57 public Distribution(final StreamInterface stream) throws ProbabilityException
58 {
59 Throw.when(null == stream, ProbabilityException.class, "random stream may not be null");
60 this.random = new DistUniform(stream, 0, 1);
61 }
62
63
64
65
66
67 private void fixProbabilities() throws ProbabilityException
68 {
69 if (0 == this.generators.size())
70 {
71 return;
72 }
73 this.cumulativeTotal = 0;
74 for (FrequencyAndObject<O> generator : this.generators)
75 {
76 double frequency = generator.getFrequency();
77 Throw.when(frequency < 0, ProbabilityException.class,
78 "Negative frequency or probability is not allowed (got " + frequency + ")");
79 this.cumulativeTotal += frequency;
80 }
81 }
82
83
84 @Override
85 public final O draw() throws ProbabilityException
86 {
87 Throw.when(0 == this.generators.size(), ProbabilityException.class, "Cannot draw from empty collection");
88 Throw.when(0 == this.cumulativeTotal, ProbabilityException.class, "Sum of frequencies or probabilities must be > 0");
89
90 double randomValue = this.random.draw() * this.cumulativeTotal;
91 for (FrequencyAndObject<O> fAndO : this.generators)
92 {
93 double frequency = fAndO.getFrequency();
94 if (frequency >= randomValue)
95 {
96 return fAndO.getObject();
97 }
98 randomValue -= frequency;
99 }
100
101 FrequencyAndObject<O> useThisOne = this.generators.get(0);
102 for (FrequencyAndObject<O> fAndO : this.generators)
103 {
104 if (fAndO.getFrequency() > 0)
105 {
106 useThisOne = fAndO;
107 break;
108 }
109 }
110 return useThisOne.getObject();
111 }
112
113
114
115
116
117
118
119 public final Distribution<O> add(final FrequencyAndObject<O> generator) throws ProbabilityException
120 {
121 return add(this.generators.size(), generator);
122 }
123
124
125
126
127
128
129
130
131 public final Distribution<O> add(final int index, final FrequencyAndObject<O> generator) throws ProbabilityException
132 {
133 Throw.when(generator.getFrequency() < 0, ProbabilityException.class,
134 "frequency (or probability) must be >= 0 (got " + generator.getFrequency() + ")");
135 this.generators.add(index, generator);
136 fixProbabilities();
137 return this;
138 }
139
140
141
142
143
144
145
146
147 public final Distribution<O> remove(final int index) throws IndexOutOfBoundsException, ProbabilityException
148 {
149 this.generators.remove(index);
150 fixProbabilities();
151 return this;
152 }
153
154
155
156
157
158
159
160
161 public final Distribution<O> set(final int index, final FrequencyAndObject<O> generator) throws ProbabilityException
162 {
163 Throw.when(generator.getFrequency() < 0, ProbabilityException.class,
164 "frequency (or probability) must be >= 0 (got " + generator.getFrequency() + ")");
165 try
166 {
167 this.generators.set(index, generator);
168 }
169 catch (IndexOutOfBoundsException ie)
170 {
171 throw new ProbabilityException("Index out of bounds for set operation, index=" + index);
172 }
173 fixProbabilities();
174 return this;
175 }
176
177
178
179
180
181
182
183
184 public final Distribution<O> modifyFrequency(final int index, final double frequency) throws ProbabilityException
185 {
186 Throw.when(index < 0 || index >= this.size(), ProbabilityException.class, "Index %s out of range (0..%d)", index,
187 this.size() - 1);
188 return set(index, new FrequencyAndObject<O>(frequency, this.generators.get(index).getObject()));
189 }
190
191
192
193
194
195 public final Distribution<O> clear()
196 {
197 this.generators.clear();
198 return this;
199 }
200
201
202
203
204
205
206
207 public final FrequencyAndObject<O> get(final int index) throws ProbabilityException
208 {
209 try
210 {
211 return this.generators.get(index);
212 }
213 catch (IndexOutOfBoundsException ie)
214 {
215 throw new ProbabilityException("Index out of bounds for set operation, index=" + index);
216 }
217 }
218
219
220
221
222
223 public final int size()
224 {
225 return this.generators.size();
226 }
227
228
229 @Override
230 public final int hashCode()
231 {
232 final int prime = 31;
233 int result = 1;
234 long temp;
235 temp = Double.doubleToLongBits(this.cumulativeTotal);
236 result = prime * result + (int) (temp ^ (temp >>> 32));
237 result = prime * result + ((this.generators == null) ? 0 : this.generators.hashCode());
238 result = prime * result + ((this.random == null) ? 0 : this.random.hashCode());
239 return result;
240 }
241
242
243 @Override
244 @SuppressWarnings("checkstyle:needbraces")
245 public final boolean equals(final Object obj)
246 {
247 if (this == obj)
248 return true;
249 if (obj == null)
250 return false;
251 if (getClass() != obj.getClass())
252 return false;
253 Distribution<?> other = (Distribution<?>) obj;
254 if (Double.doubleToLongBits(this.cumulativeTotal) != Double.doubleToLongBits(other.cumulativeTotal))
255 return false;
256 if (this.generators == null)
257 {
258 if (other.generators != null)
259 return false;
260 }
261 else if (!this.generators.equals(other.generators))
262 return false;
263 if (this.random == null)
264 {
265 if (other.random != null)
266 return false;
267 }
268 else if (!this.random.equals(other.random))
269 return false;
270 return true;
271 }
272
273
274 @Override
275 public final String toString()
276 {
277 StringBuilder result = new StringBuilder();
278 result.append("Distribution [");
279 String separator = "";
280 for (FrequencyAndObject<O> fAndO : this.generators)
281 {
282 result.append(separator + fAndO.getFrequency() + "->" + fAndO.getObject());
283 separator = ", ";
284 }
285 result.append(']');
286 return result.toString();
287 }
288
289
290
291
292
293
294
295
296
297
298
299
300
301 public static class FrequencyAndObject<O> implements Serializable
302 {
303
304 private static final long serialVersionUID = 20160301L;
305
306
307 private final double frequency;
308
309
310 private final O object;
311
312
313
314
315
316
317 public FrequencyAndObject(final double frequency, final O object)
318 {
319 this.frequency = frequency;
320 this.object = object;
321 }
322
323
324
325
326
327 public final double getFrequency()
328 {
329 return this.frequency;
330 }
331
332
333
334
335
336 public final O getObject()
337 {
338 return this.object;
339 }
340
341
342 @Override
343 public final int hashCode()
344 {
345 final int prime = 31;
346 int result = 1;
347 long temp;
348 temp = Double.doubleToLongBits(this.frequency);
349 result = prime * result + (int) (temp ^ (temp >>> 32));
350 result = prime * result + ((this.object == null) ? 0 : this.object.hashCode());
351 return result;
352 }
353
354
355 @Override
356 @SuppressWarnings("checkstyle:needbraces")
357 public final boolean equals(final Object obj)
358 {
359 if (this == obj)
360 return true;
361 if (obj == null)
362 return false;
363 if (getClass() != obj.getClass())
364 return false;
365 FrequencyAndObject<?> other = (FrequencyAndObject<?>) obj;
366 if (Double.doubleToLongBits(this.frequency) != Double.doubleToLongBits(other.frequency))
367 return false;
368 if (this.object == null)
369 {
370 if (other.object != null)
371 return false;
372 }
373 else if (!this.object.equals(other.object))
374 return false;
375 return true;
376 }
377
378
379 @Override
380 public final String toString()
381 {
382 return "FrequencyAndObject [frequency=" + this.frequency + ", object=" + this.object + "]";
383 }
384
385 }
386
387 }