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