DistNormalTrunc.java

  1. package org.opentrafficsim.core.dsol;

  2. import static nl.tudelft.simulation.jstats.distributions.DistNormal.CUMULATIVE_NORMAL_PROBABILITIES;

  3. import java.util.Locale;

  4. import nl.tudelft.simulation.jstats.distributions.DistContinuous;
  5. import nl.tudelft.simulation.jstats.streams.MersenneTwister;
  6. import nl.tudelft.simulation.jstats.streams.StreamInterface;

  7. /**
  8.  * The Normal Truncated distribution. For more information on the normal distribution see
  9.  * <a href="http://mathworld.wolfram.com/NormalDistribution.html"> http://mathworld.wolfram.com/NormalDistribution.html </a>
  10.  * <p>
  11.  * This version of the normal distribution uses the numerically approached inverse cumulative distribution.
  12.  * <p>
  13.  * (c) copyright 2002-2018 <a href="http://www.simulation.tudelft.nl">Delft University of Technology </a>, the Netherlands. <br>
  14.  * See for project information <a href="http://www.simulation.tudelft.nl"> www.simulation.tudelft.nl </a> <br>
  15.  * License of use: <a href="http://www.gnu.org/copyleft/lesser.html">Lesser General Public License (LGPL) </a>, no warranty.
  16.  * @author <a href="mailto:a.verbraeck@tudelft.nl"> Alexander Verbraeck </a> <br>
  17.  *         <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  18.  */
  19. public class DistNormalTrunc extends DistContinuous
  20. {
  21.     /** */
  22.     private static final long serialVersionUID = 1L;

  23.     /** mu refers to the mean of the normal distribution. */
  24.     private final double mu;

  25.     /** mu refers to the mean of the normal distribution. */
  26.     private final double sigma;

  27.     /** minimum x-value of the distribution. */
  28.     private final double min;

  29.     /** maximum x-value of the distribution. */
  30.     private final double max;

  31.     /** cumulative distribution value of the minimum. */
  32.     private final double cumulMin;

  33.     /** cumulative distribution value difference between max and min. */
  34.     private final double cumulDiff;

  35.     /** factor on probability density to normalize to 1. */
  36.     private final double probDensFactor;

  37.     /**
  38.      * constructs a normal distribution with mu=0 and sigma=1. Errors of various types, e.g., in the impact point of a bomb;
  39.      * quantities that are the sum of a large number of other quantities by the virtue of the central limit theorem.
  40.      * @param stream StreamInterface; the numberstream
  41.      * @param min double; minimum x-value of the distribution
  42.      * @param max double; maximum x-value of the distribution
  43.      */
  44.     public DistNormalTrunc(final StreamInterface stream, final double min, final double max)
  45.     {
  46.         this(stream, 0.0, 1.0, min, max);
  47.     }

  48.     /**
  49.      * constructs a normal distribution with mu and sigma.
  50.      * @param stream StreamInterface; the numberstream
  51.      * @param mu double; the medium
  52.      * @param sigma double; the standard deviation
  53.      * @param min double; minimum x-value of the distribution
  54.      * @param max double; maximum x-value of the distribution
  55.      */
  56.     public DistNormalTrunc(final StreamInterface stream, final double mu, final double sigma, final double min,
  57.             final double max)
  58.     {
  59.         super(stream);
  60.         if (max < min)
  61.         {
  62.             throw new IllegalArgumentException("Error Normal Truncated - max < min");
  63.         }
  64.         this.mu = mu;
  65.         this.sigma = sigma;
  66.         this.min = min;
  67.         this.max = max;
  68.         this.cumulMin = getCumulativeProbabilityNotTruncated(min);
  69.         this.cumulDiff = getCumulativeProbabilityNotTruncated(max) - this.cumulMin;
  70.         this.probDensFactor = 1.0 / this.cumulDiff;
  71.     }

  72.     /** {@inheritDoc} */
  73.     @Override
  74.     public double draw()
  75.     {
  76.         return getInverseCumulativeProbabilityNotTruncated(this.cumulMin + this.cumulDiff * this.stream.nextDouble());
  77.     }

  78.     /**
  79.      * returns the cumulative probability of the x-value.
  80.      * @param x double; the observation x
  81.      * @return double the cumulative probability
  82.      */
  83.     public double getCumulativeProbability(final double x)
  84.     {
  85.         if (x < this.min)
  86.         {
  87.             return 0.0;
  88.         }
  89.         if (x > this.max)
  90.         {
  91.             return 1.0;
  92.         }
  93.         return (getCumulativeProbabilityNotTruncated(x) - this.cumulMin) * this.probDensFactor;
  94.     }

  95.     /**
  96.      * returns the cumulative probability of the x-value.
  97.      * @param x double; the observation x
  98.      * @return double the cumulative probability
  99.      */
  100.     private double getCumulativeProbabilityNotTruncated(final double x)
  101.     {
  102.         double z = (x - this.mu) / this.sigma * 100;
  103.         double absZ = Math.abs(z);
  104.         int intZ = (int) absZ;
  105.         double f = 0.0;
  106.         if (intZ >= 1000)
  107.         {
  108.             intZ = 999;
  109.             f = 1.0;
  110.         }
  111.         else
  112.         {
  113.             f = absZ - intZ;
  114.         }
  115.         if (z >= 0)
  116.         {
  117.             return (1 - f) * CUMULATIVE_NORMAL_PROBABILITIES[intZ] + f * CUMULATIVE_NORMAL_PROBABILITIES[intZ + 1];
  118.         }
  119.         return 1 - ((1 - f) * CUMULATIVE_NORMAL_PROBABILITIES[intZ] + f * CUMULATIVE_NORMAL_PROBABILITIES[intZ + 1]);
  120.     }

  121.     /**
  122.      * returns the x-value of the given cumulativePropability.
  123.      * @param cumulativeProbability double; reflects cum prob
  124.      * @return double the inverse cumulative probability
  125.      */
  126.     public double getInverseCumulativeProbability(final double cumulativeProbability)
  127.     {
  128.         if (cumulativeProbability < 0 || cumulativeProbability > 1)
  129.         {
  130.             throw new IllegalArgumentException("1<cumulativeProbability<0 ?");
  131.         }
  132.         /*
  133.          * For extreme cases we return the min and max directly. The method getInverseCumulativeProbabilityNotTruncated() can
  134.          * only return values from "mu - 10*sigma" to "mu + 10*sigma". If min or max is beyond these values, those values would
  135.          * result erroneously. For any cumulative probability that is slightly above 0.0 or slightly below 1.0, values in the
  136.          * range from "mu - 10*sigma" to "mu + 10*sigma" will always result.
  137.          */
  138.         if (cumulativeProbability == 0.0)
  139.         {
  140.             return this.min;
  141.         }
  142.         if (cumulativeProbability == 1.0)
  143.         {
  144.             return this.max;
  145.         }
  146.         return getInverseCumulativeProbabilityNotTruncated(this.cumulMin + cumulativeProbability * this.cumulDiff);
  147.     }

  148.     /** {@inheritDoc} */
  149.     @Override
  150.     public double probDensity(final double x)
  151.     {
  152.         if (x < this.min || x > this.max)
  153.         {
  154.             return 0.0;
  155.         }
  156.         return this.probDensFactor / (Math.sqrt(2 * Math.PI * Math.pow(this.sigma, 2)))
  157.                 * Math.exp(-1 * Math.pow(x - this.mu, 2) / (2 * Math.pow(this.sigma, 2)));
  158.     }

  159.     /** {@inheritDoc} */
  160.     @Override
  161.     public String toString()
  162.     {
  163.         return "NormalTrunc(" + this.mu + "," + this.sigma + "," + this.min + "," + this.max + ")";
  164.     }

  165.     /**
  166.      * Test.
  167.      * @param args String[]; args
  168.      */
  169.     public static void main(final String[] args)
  170.     {
  171.         StreamInterface stream = new MersenneTwister();
  172.         double mu = 2.0;
  173.         double sigma = 3.0;
  174.         double min = -5.0;
  175.         double max = 4.0;
  176.         DistNormalTrunc dist = new DistNormalTrunc(stream, mu, sigma, min, max);

  177.         System.out.println("<< probability density >>");
  178.         double sum = 0.0;
  179.         double step = (max - min) / 96;
  180.         for (double x = min - 2 * step; x <= max + 2 * step; x += step)
  181.         {
  182.             double p = dist.probDensity(x);
  183.             System.out.println(String.format(Locale.GERMAN, "%.8f;%.8f", x, p));
  184.             sum += p * step;
  185.         }
  186.         System.out.println(String.format(Locale.GERMAN, "Approx. sum = %.8f", sum));
  187.         System.out.println("");

  188.         System.out.println("<< cumulative density >>");
  189.         for (double x = min - 2 * step; x <= max + 2 * step; x += step)
  190.         {
  191.             double c = dist.getCumulativeProbability(x);
  192.             System.out.println(String.format(Locale.GERMAN, "%.8f;%.8f", x, c));
  193.         }
  194.         System.out.println("");

  195.         System.out.println("<< inverse cumulative density >>");
  196.         for (double c = 0.0; c < 1.005; c += 0.01)
  197.         {
  198.             double x = dist.getInverseCumulativeProbability(Math.min(c, 1.0)); // want to include 1.0, also if 1.0000000000001
  199.             System.out.println(String.format(Locale.GERMAN, "%.8f;%.8f", c, x));
  200.         }
  201.         System.out.println("");

  202.         System.out.println("<< 10000 random numbers. >>");
  203.         for (int i = 1; i < 10000; i++)
  204.         {
  205.             System.out.println(String.format(Locale.GERMAN, "%,8f", dist.draw()));
  206.         }

  207.     }

  208.     /**
  209.      * returns the x-value of the given cumulativePropability.
  210.      * @param cumulativeProbability double; reflects cum prob
  211.      * @return double the inverse cumulative probability
  212.      */
  213.     private double getInverseCumulativeProbabilityNotTruncated(final double cumulativeProbability)
  214.     {
  215.         if (cumulativeProbability < 0 || cumulativeProbability > 1)
  216.         {
  217.             throw new IllegalArgumentException("1<cumulativeProbability<0 ?");
  218.         }
  219.         boolean located = false;
  220.         double prob = cumulativeProbability;
  221.         if (cumulativeProbability < 0.5)
  222.         {
  223.             prob = 1 - cumulativeProbability;
  224.         }
  225.         int i = 0;
  226.         double f = 0.0;
  227.         while (!located)
  228.         {
  229.             if (CUMULATIVE_NORMAL_PROBABILITIES[i] < prob && CUMULATIVE_NORMAL_PROBABILITIES[i + 1] >= prob)
  230.             {
  231.                 located = true;
  232.                 if (CUMULATIVE_NORMAL_PROBABILITIES[i] < CUMULATIVE_NORMAL_PROBABILITIES[i + 1])
  233.                 {
  234.                     f = (prob - CUMULATIVE_NORMAL_PROBABILITIES[i])
  235.                             / (CUMULATIVE_NORMAL_PROBABILITIES[i + 1] - CUMULATIVE_NORMAL_PROBABILITIES[i]);
  236.                 }
  237.             }
  238.             else
  239.             {
  240.                 i++;
  241.             }
  242.         }
  243.         if (cumulativeProbability < 0.5)
  244.         {
  245.             return this.mu - ((f + i) / 100.0) * this.sigma;
  246.         }
  247.         return ((f + i) / 100.0) * this.sigma + this.mu;
  248.     }

  249. }