View Javadoc
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   * Generic implementation of a set of objects that have a draw method with corresponding probabilities / frequencies.
14   * <p>
15   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
16   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
17   * <p>
18   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Mar 1, 2016 <br>
19   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
20   * @param <O> Type of the object returned by the draw method
21   */
22  public class Distribution<O> implements Generator<O>, Serializable
23  {
24      /** */
25      private static final long serialVersionUID = 20160301L;
26  
27      /** The generators (with their probabilities or frequencies). */
28      private final List<FrequencyAndObject<O>> generators = new ArrayList<>();
29  
30      /** Sum of all probabilities or frequencies. */
31      private double cumulativeTotal;
32  
33      /** The uniform random generator used to select a Generator. */
34      private final DistUniform random;
35  
36      /**
37       * Construct a new Distribution.
38       * @param generators List&lt;FrequencyAndObject&lt;O&gt;&gt;; the generators and their frequencies (or probabilities)
39       * @param stream StreamInterface; source for randomness
40       * @throws ProbabilityException when a frequency (or probability) is negative, or when generators is null or stream is null
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          // Store a defensive copy of the generator list (the generators are immutable; a list of them is not) and make sure it
47          // is a List that supports add, remove, etc.
48          this.generators.addAll(generators);
49          fixProbabilities();
50      }
51  
52      /**
53       * Construct a new Distribution with no generators.
54       * @param stream StreamInterface; source for randomness
55       * @throws ProbabilityException when a frequency (or probability) is negative, or when generators is null or stream is null
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       * Compute the cumulative frequencies of the storedGenerators.
65       * @throws ProbabilityException on negative frequency
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      /** {@inheritDoc} */
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          // If we get here we missed the intended object by a few ULP; return the first object that has non-zero frequency
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      * Append a generator to the internally stored list.
114      * @param generator FrequencyAndObject&lt;O&gt;; the generator to add
115      * @return Distribution&lt;O&gt;; this
116      * @throws ProbabilityException when frequency less than zero
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      * Insert a generator at the specified position in the internally stored list.
125      * @param index int; position to store the generator
126      * @param generator FrequencyAndObject&lt;O&gt;; the generator to add
127      * @return Distribution&lt;O&gt;; this
128      * @throws ProbabilityException when frequency less than zero
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      * Remove the generator at the specified position from the internally stored list.
141      * @param index int; the position
142      * @return this
143      * @throws IndexOutOfBoundsException when index is &lt; 0 or &gt;= size
144      * @throws ProbabilityException if the sum of the remaining probabilities or frequencies adds up to 0
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      * Replace the generator at the specified position.
155      * @param index int; the position of the generator that must be replaced
156      * @param generator FrequencyAndObject; the new generator and the frequency (or probability)
157      * @return this
158      * @throws ProbabilityException when the frequency (or probability) &lt; 0, or when index is &lt; 0 or &gt;= size
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      * Alter the frequency (or probability) of one of the stored generators.
178      * @param index int; index of the stored generator
179      * @param frequency double; new frequency (or probability)
180      * @return this
181      * @throws ProbabilityException when the frequency (or probability) &lt; 0, or when index is &lt; 0 or &gt;= size
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      * Empty the internally stored list.
190      * @return this
191      */
192     public final Distribution<O> clear()
193     {
194         this.generators.clear();
195         return this;
196     }
197 
198     /**
199      * Retrieve one of the internally stored generators.
200      * @param index int; the index of the FrequencyAndObject to retrieve
201      * @return FrequencyAndObject&lt;O&gt;; the generator stored at position <cite>index</cite>
202      * @throws ProbabilityException when index &lt; 0 or &gt;= size()
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      * Report the number of generators.
218      * @return int; the number of generators
219      */
220     public final int size()
221     {
222         return this.generators.size();
223     }
224 
225     /** {@inheritDoc} */
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     /** {@inheritDoc} */
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     /** {@inheritDoc} */
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      * Immutable storage for a frequency (or probability) plus a Generator.
287      * <p>
288      * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands.<br>
289      * All rights reserved. <br>
290      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
291      * <p>
292      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Mar 1, 2016 <br>
293      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
294      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
295      * @param <O> Type of the object returned by the draw method
296      */
297     public static class FrequencyAndObject<O> implements Serializable
298     {
299         /** */
300         private static final long serialVersionUID = 20160301L;
301 
302         /** Frequency (or probability) of an object. */
303         private final double frequency;
304 
305         /** The object. */
306         private final O object;
307 
308         /**
309          * Construct a new FrequencyAndObject instance.
310          * @param frequency double; the (<b>not cumulative</b>) frequency (or probability) of the <cite>generatingObject</cite>
311          * @param object O; an object
312          */
313         public FrequencyAndObject(final double frequency, final O object)
314         {
315             this.frequency = frequency;
316             this.object = object;
317         }
318 
319         /**
320          * Retrieve the frequency (or probability) of this FrequencyAndObject.
321          * @return double; the frequency (or probability) of this FrequencyAndObject
322          */
323         public final double getFrequency()
324         {
325             return this.frequency;
326         }
327 
328         /**
329          * Call the draw method of the generatingObject and return its result.
330          * @return O; the result of a call to the draw method of the generatingObject
331          */
332         public final O getObject()
333         {
334             return this.object;
335         }
336 
337         /** {@inheritDoc} */
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         /** {@inheritDoc} */
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         /** {@inheritDoc} */
375         @Override
376         public final String toString()
377         {
378             return "FrequencyAndObject [frequency=" + this.frequency + ", object=" + this.object + "]";
379         }
380 
381     }
382 
383 }