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 }