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