Distribution.java

  1. package org.opentrafficsim.core.distributions;

  2. import java.io.Serializable;
  3. import java.util.ArrayList;
  4. import java.util.List;

  5. import org.djutils.exceptions.Throw;

  6. import nl.tudelft.simulation.jstats.distributions.DistUniform;
  7. import nl.tudelft.simulation.jstats.streams.StreamInterface;

  8. /**
  9.  * Generic implementation of a set of objects that have a draw method with corresponding probabilities / frequencies.
  10.  * <p>
  11.  * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  12.  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  13.  * </p>
  14.  * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  15.  * @param <O> Type of the object returned by the draw method
  16.  */
  17. public class Distribution<O> implements Generator<O>, Serializable
  18. {
  19.     /** */
  20.     private static final long serialVersionUID = 20160301L;

  21.     /** The generators (with their probabilities or frequencies). */
  22.     private final List<FrequencyAndObject<O>> generators = new ArrayList<>();

  23.     /** Sum of all probabilities or frequencies. */
  24.     private double cumulativeTotal;

  25.     /** The uniform random generator used to select a Generator. */
  26.     private final DistUniform random;

  27.     /**
  28.      * Construct a new Distribution.
  29.      * @param generators List&lt;FrequencyAndObject&lt;O&gt;&gt;; the generators and their frequencies (or probabilities)
  30.      * @param stream StreamInterface; source for randomness
  31.      * @throws ProbabilityException when a frequency (or probability) is negative, or when generators is null or stream is null
  32.      */
  33.     public Distribution(final List<FrequencyAndObject<O>> generators, final StreamInterface stream) throws ProbabilityException
  34.     {
  35.         this(stream);
  36.         Throw.when(null == generators, ProbabilityException.class, "generators may not be null");
  37.         // Store a defensive copy of the generator list (the generators are immutable; a list of them is not) and make sure it
  38.         // is a List that supports add, remove, etc.
  39.         this.generators.addAll(generators);
  40.         fixProbabilities();
  41.     }

  42.     /**
  43.      * Construct a new Distribution with no generators.
  44.      * @param stream StreamInterface; source for randomness
  45.      * @throws ProbabilityException when a frequency (or probability) is negative, or when generators is null or stream is null
  46.      */
  47.     public Distribution(final StreamInterface stream) throws ProbabilityException
  48.     {
  49.         Throw.when(null == stream, ProbabilityException.class, "random stream may not be null");
  50.         this.random = new DistUniform(stream, 0, 1);
  51.     }

  52.     /**
  53.      * Compute the cumulative frequencies of the storedGenerators.
  54.      * @throws ProbabilityException on negative frequency
  55.      */
  56.     private void fixProbabilities() throws ProbabilityException
  57.     {
  58.         if (0 == this.generators.size())
  59.         {
  60.             return;
  61.         }
  62.         this.cumulativeTotal = 0;
  63.         for (FrequencyAndObject<O> generator : this.generators)
  64.         {
  65.             double frequency = generator.getFrequency();
  66.             Throw.when(frequency < 0, ProbabilityException.class,
  67.                     "Negative frequency or probability is not allowed (got " + frequency + ")");
  68.             this.cumulativeTotal += frequency;
  69.         }
  70.     }

  71.     /** {@inheritDoc} */
  72.     @Override
  73.     public final O draw() throws ProbabilityException
  74.     {
  75.         Throw.when(0 == this.generators.size(), ProbabilityException.class, "Cannot draw from empty collection");
  76.         Throw.when(0 == this.cumulativeTotal, ProbabilityException.class, "Sum of frequencies or probabilities must be > 0");

  77.         double randomValue = this.random.draw() * this.cumulativeTotal;
  78.         for (FrequencyAndObject<O> fAndO : this.generators)
  79.         {
  80.             double frequency = fAndO.getFrequency();
  81.             if (frequency >= randomValue)
  82.             {
  83.                 return fAndO.getObject();
  84.             }
  85.             randomValue -= frequency;
  86.         }
  87.         // If we get here we missed the intended object by a few ULP; return the first object that has non-zero frequency
  88.         FrequencyAndObject<O> useThisOne = this.generators.get(0);
  89.         for (FrequencyAndObject<O> fAndO : this.generators)
  90.         {
  91.             if (fAndO.getFrequency() > 0)
  92.             {
  93.                 useThisOne = fAndO;
  94.                 break;
  95.             }
  96.         }
  97.         return useThisOne.getObject();
  98.     }

  99.     /**
  100.      * Append a generator to the internally stored list.
  101.      * @param generator FrequencyAndObject&lt;O&gt;; the generator to add
  102.      * @return Distribution&lt;O&gt;; this
  103.      * @throws ProbabilityException when frequency less than zero
  104.      */
  105.     public final Distribution<O> add(final FrequencyAndObject<O> generator) throws ProbabilityException
  106.     {
  107.         return add(this.generators.size(), generator);
  108.     }

  109.     /**
  110.      * Insert a generator at the specified position in the internally stored list.
  111.      * @param index int; position to store the generator
  112.      * @param generator FrequencyAndObject&lt;O&gt;; the generator to add
  113.      * @return Distribution&lt;O&gt;; this
  114.      * @throws ProbabilityException when frequency less than zero
  115.      */
  116.     public final Distribution<O> add(final int index, final FrequencyAndObject<O> generator) throws ProbabilityException
  117.     {
  118.         Throw.when(generator.getFrequency() < 0, ProbabilityException.class,
  119.                 "frequency (or probability) must be >= 0 (got " + generator.getFrequency() + ")");
  120.         this.generators.add(index, generator);
  121.         fixProbabilities();
  122.         return this;
  123.     }

  124.     /**
  125.      * Remove the generator at the specified position from the internally stored list.
  126.      * @param index int; the position
  127.      * @return this
  128.      * @throws IndexOutOfBoundsException when index is &lt; 0 or &gt;= size
  129.      * @throws ProbabilityException if the sum of the remaining probabilities or frequencies adds up to 0
  130.      */
  131.     public final Distribution<O> remove(final int index) throws IndexOutOfBoundsException, ProbabilityException
  132.     {
  133.         this.generators.remove(index);
  134.         fixProbabilities();
  135.         return this;
  136.     }

  137.     /**
  138.      * Replace the generator at the specified position.
  139.      * @param index int; the position of the generator that must be replaced
  140.      * @param generator FrequencyAndObject&lt;O&gt;; the new generator and the frequency (or probability)
  141.      * @return this
  142.      * @throws ProbabilityException when the frequency (or probability) &lt; 0, or when index is &lt; 0 or &gt;= size
  143.      */
  144.     public final Distribution<O> set(final int index, final FrequencyAndObject<O> generator) throws ProbabilityException
  145.     {
  146.         Throw.when(generator.getFrequency() < 0, ProbabilityException.class,
  147.                 "frequency (or probability) must be >= 0 (got " + generator.getFrequency() + ")");
  148.         try
  149.         {
  150.             this.generators.set(index, generator);
  151.         }
  152.         catch (IndexOutOfBoundsException ie)
  153.         {
  154.             throw new ProbabilityException("Index out of bounds for set operation, index=" + index);
  155.         }
  156.         fixProbabilities();
  157.         return this;
  158.     }

  159.     /**
  160.      * Alter the frequency (or probability) of one of the stored generators.
  161.      * @param index int; index of the stored generator
  162.      * @param frequency double; new frequency (or probability)
  163.      * @return this
  164.      * @throws ProbabilityException when the frequency (or probability) &lt; 0, or when index is &lt; 0 or &gt;= size
  165.      */
  166.     public final Distribution<O> modifyFrequency(final int index, final double frequency) throws ProbabilityException
  167.     {
  168.         Throw.when(index < 0 || index >= this.size(), ProbabilityException.class, "Index %s out of range (0..%d)", index,
  169.                 this.size() - 1);
  170.         return set(index, new FrequencyAndObject<O>(frequency, this.generators.get(index).getObject()));
  171.     }

  172.     /**
  173.      * Empty the internally stored list.
  174.      * @return this
  175.      */
  176.     public final Distribution<O> clear()
  177.     {
  178.         this.generators.clear();
  179.         return this;
  180.     }

  181.     /**
  182.      * Retrieve one of the internally stored generators.
  183.      * @param index int; the index of the FrequencyAndObject to retrieve
  184.      * @return FrequencyAndObject&lt;O&gt;; the generator stored at position <cite>index</cite>
  185.      * @throws ProbabilityException when index &lt; 0 or &gt;= size()
  186.      */
  187.     public final FrequencyAndObject<O> get(final int index) throws ProbabilityException
  188.     {
  189.         try
  190.         {
  191.             return this.generators.get(index);
  192.         }
  193.         catch (IndexOutOfBoundsException ie)
  194.         {
  195.             throw new ProbabilityException("Index out of bounds for set operation, index=" + index);
  196.         }
  197.     }

  198.     /**
  199.      * Report the number of generators.
  200.      * @return int; the number of generators
  201.      */
  202.     public final int size()
  203.     {
  204.         return this.generators.size();
  205.     }

  206.     /** {@inheritDoc} */
  207.     @Override
  208.     public final int hashCode()
  209.     {
  210.         final int prime = 31;
  211.         int result = 1;
  212.         long temp;
  213.         temp = Double.doubleToLongBits(this.cumulativeTotal);
  214.         result = prime * result + (int) (temp ^ (temp >>> 32));
  215.         result = prime * result + ((this.generators == null) ? 0 : this.generators.hashCode());
  216.         result = prime * result + ((this.random == null) ? 0 : this.random.hashCode());
  217.         return result;
  218.     }

  219.     /** {@inheritDoc} */
  220.     @Override
  221.     @SuppressWarnings("checkstyle:needbraces")
  222.     public final boolean equals(final Object obj)
  223.     {
  224.         if (this == obj)
  225.             return true;
  226.         if (obj == null)
  227.             return false;
  228.         if (getClass() != obj.getClass())
  229.             return false;
  230.         Distribution<?> other = (Distribution<?>) obj;
  231.         if (Double.doubleToLongBits(this.cumulativeTotal) != Double.doubleToLongBits(other.cumulativeTotal))
  232.             return false;
  233.         if (this.generators == null)
  234.         {
  235.             if (other.generators != null)
  236.                 return false;
  237.         }
  238.         else if (!this.generators.equals(other.generators))
  239.             return false;
  240.         if (this.random == null)
  241.         {
  242.             if (other.random != null)
  243.                 return false;
  244.         }
  245.         else if (!this.random.equals(other.random))
  246.             return false;
  247.         return true;
  248.     }

  249.     /** {@inheritDoc} */
  250.     @Override
  251.     public final String toString()
  252.     {
  253.         StringBuilder result = new StringBuilder();
  254.         result.append("Distribution [");
  255.         String separator = "";
  256.         for (FrequencyAndObject<O> fAndO : this.generators)
  257.         {
  258.             result.append(separator + fAndO.getFrequency() + "->" + fAndO.getObject());
  259.             separator = ", ";
  260.         }
  261.         result.append(']');
  262.         return result.toString();
  263.     }

  264.     /**
  265.      * Immutable storage for a frequency (or probability) plus a Generator.
  266.      * <p>
  267.      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands.<br>
  268.      * All rights reserved. <br>
  269.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  270.      * </p>
  271.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  272.      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  273.      * @param <O> Type of the object returned by the draw method
  274.      */
  275.     public static class FrequencyAndObject<O> implements Serializable
  276.     {
  277.         /** */
  278.         private static final long serialVersionUID = 20160301L;

  279.         /** Frequency (or probability) of an object. */
  280.         private final double frequency;

  281.         /** The object. */
  282.         private final O object;

  283.         /**
  284.          * Construct a new FrequencyAndObject instance.
  285.          * @param frequency double; the (<b>not cumulative</b>) frequency (or probability) of the <cite>generatingObject</cite>
  286.          * @param object O; an object
  287.          */
  288.         public FrequencyAndObject(final double frequency, final O object)
  289.         {
  290.             this.frequency = frequency;
  291.             this.object = object;
  292.         }

  293.         /**
  294.          * Retrieve the frequency (or probability) of this FrequencyAndObject.
  295.          * @return double; the frequency (or probability) of this FrequencyAndObject
  296.          */
  297.         public final double getFrequency()
  298.         {
  299.             return this.frequency;
  300.         }

  301.         /**
  302.          * Call the draw method of the generatingObject and return its result.
  303.          * @return O; the result of a call to the draw method of the generatingObject
  304.          */
  305.         public final O getObject()
  306.         {
  307.             return this.object;
  308.         }

  309.         /** {@inheritDoc} */
  310.         @Override
  311.         public final int hashCode()
  312.         {
  313.             final int prime = 31;
  314.             int result = 1;
  315.             long temp;
  316.             temp = Double.doubleToLongBits(this.frequency);
  317.             result = prime * result + (int) (temp ^ (temp >>> 32));
  318.             result = prime * result + ((this.object == null) ? 0 : this.object.hashCode());
  319.             return result;
  320.         }

  321.         /** {@inheritDoc} */
  322.         @Override
  323.         @SuppressWarnings("checkstyle:needbraces")
  324.         public final boolean equals(final Object obj)
  325.         {
  326.             if (this == obj)
  327.                 return true;
  328.             if (obj == null)
  329.                 return false;
  330.             if (getClass() != obj.getClass())
  331.                 return false;
  332.             FrequencyAndObject<?> other = (FrequencyAndObject<?>) obj;
  333.             if (Double.doubleToLongBits(this.frequency) != Double.doubleToLongBits(other.frequency))
  334.                 return false;
  335.             if (this.object == null)
  336.             {
  337.                 if (other.object != null)
  338.                     return false;
  339.             }
  340.             else if (!this.object.equals(other.object))
  341.                 return false;
  342.             return true;
  343.         }

  344.         /** {@inheritDoc} */
  345.         @Override
  346.         public final String toString()
  347.         {
  348.             return "FrequencyAndObject [frequency=" + this.frequency + ", object=" + this.object + "]";
  349.         }

  350.     }

  351. }