View Javadoc
1   package org.opentrafficsim.core.distributions;
2   
3   import java.util.ArrayList;
4   import java.util.List;
5   
6   import nl.tudelft.simulation.jstats.distributions.DistUniform;
7   import nl.tudelft.simulation.jstats.streams.StreamInterface;
8   
9   /**
10   * Generic implementation of a set of objects that have a draw method with corresponding probabilities / frequencies.
11   * <p>
12   * Copyright (c) 2013-2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
13   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
14   * <p>
15   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Mar 1, 2016 <br>
16   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
17   * @param <O> Type of the object returned by the draw method
18   */
19  public class Distribution<O> implements Generator<O>
20  {
21      /** The generators (with their probabilities or frequencies). */
22      private final List<FrequencyAndObject<O>> generators = new ArrayList<>();
23  
24      /** Sum of all probabilities or frequencies. */
25      private double cumulativeTotal;
26  
27      /** The uniform random generator used to select a Generator. */
28      private final DistUniform random;
29  
30      /**
31       * Construct a new Distribution.
32       * @param generators List&lt;FrequencyAndObject&lt;O&gt;&gt;; the generators and their frequencies (or probabilities)
33       * @param stream StreamInterface; source for randomness
34       * @throws ProbabilityException when a frequency (or probability) is negative, or when generators is null or stream is null
35       */
36      public Distribution(final List<FrequencyAndObject<O>> generators, final StreamInterface stream)
37          throws ProbabilityException
38      {
39          this(stream);
40          ProbabilityException.failIf(null == generators, "generators may not be null");
41          // Store a defensive copy of the generator list (the generators are immutable; a list of them is not) and make sure it
42          // is a List that supports add, remove, etc.
43          this.generators.addAll(generators);
44          fixProbabilities();
45      }
46  
47      /**
48       * Construct a new Distribution with no generators.
49       * @param stream StreamInterface; source for randomness
50       * @throws ProbabilityException when a frequency (or probability) is negative, or when generators is null or stream is null
51       */
52      public Distribution(final StreamInterface stream) throws ProbabilityException
53      {
54          ProbabilityException.failIf(null == stream, "random stream may not be null");
55          this.random = new DistUniform(stream, 0, 1);
56      }
57  
58      /**
59       * Compute the cumulative frequencies of the storedGenerators.
60       * @throws ProbabilityException on negative frequency
61       */
62      private void fixProbabilities() throws ProbabilityException
63      {
64          if (0 == this.generators.size())
65          {
66              return;
67          }
68          this.cumulativeTotal = 0;
69          for (FrequencyAndObject<O> generator : this.generators)
70          {
71              double frequency = generator.getFrequency();
72              ProbabilityException.failIf(frequency < 0, "Negative frequency or probability is not allowed (got "
73                  + frequency + ")");
74              this.cumulativeTotal += frequency;
75          }
76      }
77  
78      /** {@inheritDoc} */
79      public final O draw() throws ProbabilityException
80      {
81          ProbabilityException.failIf(0 == this.generators.size(), "Cannot draw from empty collection");
82          ProbabilityException.failIf(0 == this.cumulativeTotal, "Sum of frequencies or probabilities must be > 0");
83  
84          double randomValue = this.random.draw() * this.cumulativeTotal;
85          for (FrequencyAndObject<O> fAndO : this.generators)
86          {
87              double frequency = fAndO.getFrequency();
88              if (frequency >= randomValue)
89              {
90                  return fAndO.getObject();
91              }
92              randomValue -= frequency;
93          }
94          // If we get here we missed the intended object by a few ULP; return the first object that has non-zero frequency
95          FrequencyAndObject<O> useThisOne = this.generators.get(0);
96          for (FrequencyAndObject<O> fAndO : this.generators)
97          {
98              if (fAndO.getFrequency() > 0)
99              {
100                 useThisOne = fAndO;
101                 break;
102             }
103         }
104         return useThisOne.getObject();
105     }
106 
107     /**
108      * Append a generator to the internally stored list.
109      * @param generator FrequencyAndObject&lt;O&gt;; the generator to add
110      * @return Distribution&lt;O&gt;; this
111      * @throws ProbabilityException when frequency less than zero
112      */
113     public final Distribution<O> add(final FrequencyAndObject<O> generator) throws ProbabilityException
114     {
115         return add(this.generators.size(), generator);
116     }
117 
118     /**
119      * Insert a generator at the specified position in the internally stored list.
120      * @param index int; position to store the generator
121      * @param generator FrequencyAndObject&lt;O&gt;; the generator to add
122      * @return Distribution&lt;O&gt;; this
123      * @throws ProbabilityException when frequency less than zero
124      */
125     public final Distribution<O> add(final int index, final FrequencyAndObject<O> generator)
126         throws ProbabilityException
127     {
128         ProbabilityException.failIf(generator.getFrequency() < 0, "frequency (or probability) must be >= 0 (got "
129             + generator.getFrequency() + ")");
130         this.generators.add(index, generator);
131         fixProbabilities();
132         return this;
133     }
134 
135     /**
136      * Remove the generator at the specified position from the internally stored list.
137      * @param index int; the position
138      * @return this
139      * @throws IndexOutOfBoundsException when index is &lt; 0 or &gt;= size
140      * @throws ProbabilityException if the sum of the remaining probabilities or frequencies adds up to 0
141      */
142     public final Distribution<O> remove(final int index) throws IndexOutOfBoundsException, ProbabilityException
143     {
144         this.generators.remove(index);
145         fixProbabilities();
146         return this;
147     }
148 
149     /**
150      * Replace the generator at the specified position.
151      * @param index int; the position of the generator that must be replaced
152      * @param generator FrequencyAndObject; the new generator and the frequency (or probability)
153      * @return this
154      * @throws ProbabilityException when the frequency (or probability) &lt; 0, or when index is &lt; 0 or &gt;= size
155      */
156     public final Distribution<O> set(final int index, final FrequencyAndObject<O> generator)
157         throws ProbabilityException
158     {
159         ProbabilityException.failIf(generator.getFrequency() < 0, "frequency (or probability) must be >= 0 (got "
160             + generator.getFrequency() + ")");
161         try
162         {
163             this.generators.set(index, generator);
164         }
165         catch (IndexOutOfBoundsException ie)
166         {
167             throw new ProbabilityException("Index out of bounds for set operation, index=" + index);
168         }
169         fixProbabilities();
170         return this;
171     }
172 
173     /**
174      * Alter the frequency (or probability) of one of the stored generators.
175      * @param index int; index of the stored generator
176      * @param frequency double; new frequency (or probability)
177      * @return this
178      * @throws ProbabilityException when the frequency (or probability) &lt; 0, or when index is &lt; 0 or &gt;= size
179      */
180     public final Distribution<O> modifyFrequency(final int index, final double frequency) throws ProbabilityException
181     {
182         return set(index, new FrequencyAndObject<O>(frequency, this.generators.get(index).getObject()));
183     }
184 
185     /**
186      * Empty the internally stored list.
187      * @return this
188      */
189     public final Distribution<O> clear()
190     {
191         this.generators.clear();
192         return this;
193     }
194 
195     /**
196      * Retrieve one of the internally stored generators.
197      * @param index int; the index of the FrequencyAndObject to retrieve
198      * @return FrequencyAndObject&lt;O&gt;; the generator stored at position <cite>index</cite>
199      * @throws ProbabilityException when index &lt; 0 or &gt;= size()
200      */
201     public final FrequencyAndObject<O> get(final int index) throws ProbabilityException
202     {
203         try
204         {
205             return this.generators.get(index);
206         }
207         catch (IndexOutOfBoundsException ie)
208         {
209             throw new ProbabilityException("Index out of bounds for set operation, index=" + index);
210         }
211     }
212 
213     /**
214      * Report the number of generators.
215      * @return int; the number of generators
216      */
217     public final int size()
218     {
219         return this.generators.size();
220     }
221 
222     /** {@inheritDoc} */
223     @Override
224     public final int hashCode()
225     {
226         final int prime = 31;
227         int result = 1;
228         long temp;
229         temp = Double.doubleToLongBits(this.cumulativeTotal);
230         result = prime * result + (int) (temp ^ (temp >>> 32));
231         result = prime * result + ((this.generators == null) ? 0 : this.generators.hashCode());
232         result = prime * result + ((this.random == null) ? 0 : this.random.hashCode());
233         return result;
234     }
235 
236     /** {@inheritDoc} */
237     @Override
238     @SuppressWarnings("checkstyle:needbraces")
239     public final boolean equals(final Object obj)
240     {
241         if (this == obj)
242             return true;
243         if (obj == null)
244             return false;
245         if (getClass() != obj.getClass())
246             return false;
247         Distribution<?> other = (Distribution<?>) obj;
248         if (Double.doubleToLongBits(this.cumulativeTotal) != Double.doubleToLongBits(other.cumulativeTotal))
249             return false;
250         if (this.generators == null)
251         {
252             if (other.generators != null)
253                 return false;
254         }
255         else if (!this.generators.equals(other.generators))
256             return false;
257         if (this.random == null)
258         {
259             if (other.random != null)
260                 return false;
261         }
262         else if (!this.random.equals(other.random))
263             return false;
264         return true;
265     }
266 
267     /** {@inheritDoc} */
268     public final String toString()
269     {
270         StringBuilder result = new StringBuilder();
271         result.append("Distribution [");
272         String separator = "";
273         for (FrequencyAndObject<O> fAndO : this.generators)
274         {
275             result.append(separator + fAndO.getFrequency() + "->" + fAndO.getObject());
276             separator = ", ";
277         }
278         result.append(']');
279         return result.toString();
280     }
281 
282     /**
283      * Immutable storage for a frequency (or probability) plus a Generator.
284      * <p>
285      * Copyright (c) 2013-2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands.<br>
286      * All rights reserved. <br>
287      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
288      * <p>
289      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Mar 1, 2016 <br>
290      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
291      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
292      * @param <O> Type of the object returned by the draw method
293      */
294     public static class FrequencyAndObject<O>
295     {
296         /** Frequency (or probability) of an object. */
297         private final double frequency;
298 
299         /** The object. */
300         private final O object;
301 
302         /**
303          * Construct a new FrequencyAndObject instance.
304          * @param frequency double; the (<b>not cumulative</b>) frequency (or probability) of the <cite>generatingObject</cite>
305          * @param object O; an object
306          */
307         public FrequencyAndObject(final double frequency, final O object)
308         {
309             this.frequency = frequency;
310             this.object = object;
311         }
312 
313         /**
314          * Retrieve the frequency (or probability) of this FrequencyAndObject.
315          * @return double; the frequency (or probability) of this FrequencyAndObject
316          */
317         public final double getFrequency()
318         {
319             return this.frequency;
320         }
321 
322         /**
323          * Call the draw method of the generatingObject and return its result.
324          * @return O; the result of a call to the draw method of the generatingObject
325          */
326         public final O getObject()
327         {
328             return this.object;
329         }
330 
331         /** {@inheritDoc} */
332         @Override
333         public final int hashCode()
334         {
335             final int prime = 31;
336             int result = 1;
337             long temp;
338             temp = Double.doubleToLongBits(this.frequency);
339             result = prime * result + (int) (temp ^ (temp >>> 32));
340             result = prime * result + ((this.object == null) ? 0 : this.object.hashCode());
341             return result;
342         }
343 
344         /** {@inheritDoc} */
345         @Override
346         @SuppressWarnings("checkstyle:needbraces")
347         public final boolean equals(final Object obj)
348         {
349             if (this == obj)
350                 return true;
351             if (obj == null)
352                 return false;
353             if (getClass() != obj.getClass())
354                 return false;
355             FrequencyAndObject<?> other = (FrequencyAndObject<?>) obj;
356             if (Double.doubleToLongBits(this.frequency) != Double.doubleToLongBits(other.frequency))
357                 return false;
358             if (this.object == null)
359             {
360                 if (other.object != null)
361                     return false;
362             }
363             else if (!this.object.equals(other.object))
364                 return false;
365             return true;
366         }
367 
368         /** {@inheritDoc} */
369         @Override
370         public final String toString()
371         {
372             return "FrequencyAndObject [frequency=" + this.frequency + ", object=" + this.object + "]";
373         }
374         
375     }
376 
377 }