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 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,
77 "Negative frequency or probability is not allowed (got " + frequency + ")");
78 this.cumulativeTotal += frequency;
79 }
80 }
81
82
83 @Override
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,
133 "frequency (or probability) must be >= 0 (got " + 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,
163 "frequency (or probability) must be >= 0 (got " + 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 Throw.when(index < 0 || index >= this.size(), ProbabilityException.class, "Index %s out of range (0..%d)", index,
186 this.size() - 1);
187 return set(index, new FrequencyAndObject<O>(frequency, this.generators.get(index).getObject()));
188 }
189
190
191
192
193
194 public final Distribution<O> clear()
195 {
196 this.generators.clear();
197 return this;
198 }
199
200
201
202
203
204
205
206 public final FrequencyAndObject<O> get(final int index) throws ProbabilityException
207 {
208 try
209 {
210 return this.generators.get(index);
211 }
212 catch (IndexOutOfBoundsException ie)
213 {
214 throw new ProbabilityException("Index out of bounds for set operation, index=" + index);
215 }
216 }
217
218
219
220
221
222 public final int size()
223 {
224 return this.generators.size();
225 }
226
227
228 @Override
229 public final int hashCode()
230 {
231 final int prime = 31;
232 int result = 1;
233 long temp;
234 temp = Double.doubleToLongBits(this.cumulativeTotal);
235 result = prime * result + (int) (temp ^ (temp >>> 32));
236 result = prime * result + ((this.generators == null) ? 0 : this.generators.hashCode());
237 result = prime * result + ((this.random == null) ? 0 : this.random.hashCode());
238 return result;
239 }
240
241
242 @Override
243 @SuppressWarnings("checkstyle:needbraces")
244 public final boolean equals(final Object obj)
245 {
246 if (this == obj)
247 return true;
248 if (obj == null)
249 return false;
250 if (getClass() != obj.getClass())
251 return false;
252 Distribution<?> other = (Distribution<?>) obj;
253 if (Double.doubleToLongBits(this.cumulativeTotal) != Double.doubleToLongBits(other.cumulativeTotal))
254 return false;
255 if (this.generators == null)
256 {
257 if (other.generators != null)
258 return false;
259 }
260 else if (!this.generators.equals(other.generators))
261 return false;
262 if (this.random == null)
263 {
264 if (other.random != null)
265 return false;
266 }
267 else if (!this.random.equals(other.random))
268 return false;
269 return true;
270 }
271
272
273 @Override
274 public final String toString()
275 {
276 StringBuilder result = new StringBuilder();
277 result.append("Distribution [");
278 String separator = "";
279 for (FrequencyAndObject<O> fAndO : this.generators)
280 {
281 result.append(separator + fAndO.getFrequency() + "->" + fAndO.getObject());
282 separator = ", ";
283 }
284 result.append(']');
285 return result.toString();
286 }
287
288
289
290
291
292
293
294
295
296
297
298
299 public static class FrequencyAndObject<O> implements Serializable
300 {
301
302 private static final long serialVersionUID = 20160301L;
303
304
305 private final double frequency;
306
307
308 private final O object;
309
310
311
312
313
314
315 public FrequencyAndObject(final double frequency, final O object)
316 {
317 this.frequency = frequency;
318 this.object = object;
319 }
320
321
322
323
324
325 public final double getFrequency()
326 {
327 return this.frequency;
328 }
329
330
331
332
333
334 public final O getObject()
335 {
336 return this.object;
337 }
338
339
340 @Override
341 public final int hashCode()
342 {
343 final int prime = 31;
344 int result = 1;
345 long temp;
346 temp = Double.doubleToLongBits(this.frequency);
347 result = prime * result + (int) (temp ^ (temp >>> 32));
348 result = prime * result + ((this.object == null) ? 0 : this.object.hashCode());
349 return result;
350 }
351
352
353 @Override
354 @SuppressWarnings("checkstyle:needbraces")
355 public final boolean equals(final Object obj)
356 {
357 if (this == obj)
358 return true;
359 if (obj == null)
360 return false;
361 if (getClass() != obj.getClass())
362 return false;
363 FrequencyAndObject<?> other = (FrequencyAndObject<?>) obj;
364 if (Double.doubleToLongBits(this.frequency) != Double.doubleToLongBits(other.frequency))
365 return false;
366 if (this.object == null)
367 {
368 if (other.object != null)
369 return false;
370 }
371 else if (!this.object.equals(other.object))
372 return false;
373 return true;
374 }
375
376
377 @Override
378 public final String toString()
379 {
380 return "FrequencyAndObject [frequency=" + this.frequency + ", object=" + this.object + "]";
381 }
382
383 }
384
385 }