View Javadoc
1   package org.opentrafficsim.core.dsol;
2   
3   import static nl.tudelft.simulation.jstats.distributions.DistNormal.CUMULATIVE_NORMAL_PROBABILITIES;
4   
5   import java.util.Locale;
6   
7   import nl.tudelft.simulation.jstats.distributions.DistContinuous;
8   import nl.tudelft.simulation.jstats.streams.MersenneTwister;
9   import nl.tudelft.simulation.jstats.streams.StreamInterface;
10  
11  /**
12   * The Normal Truncated distribution. For more information on the normal distribution see
13   * <a href="http://mathworld.wolfram.com/NormalDistribution.html"> http://mathworld.wolfram.com/NormalDistribution.html </a>
14   * <p>
15   * This version of the normal distribution uses the numerically approached inverse cumulative distribution.
16   * <p>
17   * (c) copyright 2002-2018 <a href="http://www.simulation.tudelft.nl">Delft University of Technology </a>, the Netherlands. <br>
18   * See for project information <a href="http://www.simulation.tudelft.nl"> www.simulation.tudelft.nl </a> <br>
19   * License of use: <a href="http://www.gnu.org/copyleft/lesser.html">Lesser General Public License (LGPL) </a>, no warranty.
20   * @author <a href="mailto:a.verbraeck@tudelft.nl"> Alexander Verbraeck </a> <br>
21   *         <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
22   */
23  public class DistNormalTrunc extends DistContinuous
24  {
25      /** */
26      private static final long serialVersionUID = 1L;
27  
28      /** mu refers to the mean of the normal distribution. */
29      private final double mu;
30  
31      /** mu refers to the mean of the normal distribution. */
32      private final double sigma;
33  
34      /** minimum x-value of the distribution. */
35      private final double min;
36  
37      /** maximum x-value of the distribution. */
38      private final double max;
39  
40      /** cumulative distribution value of the minimum. */
41      private final double cumulMin;
42  
43      /** cumulative distribution value difference between max and min. */
44      private final double cumulDiff;
45  
46      /** factor on probability density to normalize to 1. */
47      private final double probDensFactor;
48  
49      /**
50       * constructs a normal distribution with mu=0 and sigma=1. Errors of various types, e.g., in the impact point of a bomb;
51       * quantities that are the sum of a large number of other quantities by the virtue of the central limit theorem.
52       * @param stream StreamInterface; the numberstream
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 min, final double max)
57      {
58          this(stream, 0.0, 1.0, min, max);
59      }
60  
61      /**
62       * constructs a normal distribution with mu and sigma.
63       * @param stream StreamInterface; the numberstream
64       * @param mu double; the medium
65       * @param sigma double; the standard deviation
66       * @param min double; minimum x-value of the distribution
67       * @param max double; maximum x-value of the distribution
68       */
69      public DistNormalTrunc(final StreamInterface stream, final double mu, final double sigma, final double min,
70              final double max)
71      {
72          super(stream);
73          if (max < min)
74          {
75              throw new IllegalArgumentException("Error Normal Truncated - max < min");
76          }
77          this.mu = mu;
78          this.sigma = sigma;
79          this.min = min;
80          this.max = max;
81          this.cumulMin = getCumulativeProbabilityNotTruncated(min);
82          this.cumulDiff = getCumulativeProbabilityNotTruncated(max) - this.cumulMin;
83          this.probDensFactor = 1.0 / this.cumulDiff;
84      }
85  
86      /** {@inheritDoc} */
87      @Override
88      public double draw()
89      {
90          return getInverseCumulativeProbabilityNotTruncated(this.cumulMin + this.cumulDiff * this.stream.nextDouble());
91      }
92  
93      /**
94       * returns the cumulative probability of the x-value.
95       * @param x double; the observation x
96       * @return double the cumulative probability
97       */
98      public double getCumulativeProbability(final double x)
99      {
100         if (x < this.min)
101         {
102             return 0.0;
103         }
104         if (x > this.max)
105         {
106             return 1.0;
107         }
108         return (getCumulativeProbabilityNotTruncated(x) - this.cumulMin) * this.probDensFactor;
109     }
110 
111     /**
112      * returns the cumulative probability of the x-value.
113      * @param x double; the observation x
114      * @return double the cumulative probability
115      */
116     private double getCumulativeProbabilityNotTruncated(final double x)
117     {
118         double z = (x - this.mu) / this.sigma * 100;
119         double absZ = Math.abs(z);
120         int intZ = (int) absZ;
121         double f = 0.0;
122         if (intZ >= 1000)
123         {
124             intZ = 999;
125             f = 1.0;
126         }
127         else
128         {
129             f = absZ - intZ;
130         }
131         if (z >= 0)
132         {
133             return (1 - f) * CUMULATIVE_NORMAL_PROBABILITIES[intZ] + f * CUMULATIVE_NORMAL_PROBABILITIES[intZ + 1];
134         }
135         return 1 - ((1 - f) * CUMULATIVE_NORMAL_PROBABILITIES[intZ] + f * CUMULATIVE_NORMAL_PROBABILITIES[intZ + 1]);
136     }
137 
138     /**
139      * returns the x-value of the given cumulativePropability.
140      * @param cumulativeProbability double; reflects cum prob
141      * @return double the inverse cumulative probability
142      */
143     public double getInverseCumulativeProbability(final double cumulativeProbability)
144     {
145         if (cumulativeProbability < 0 || cumulativeProbability > 1)
146         {
147             throw new IllegalArgumentException("1<cumulativeProbability<0 ?");
148         }
149         /*
150          * For extreme cases we return the min and max directly. The method getInverseCumulativeProbabilityNotTruncated() can
151          * only return values from "mu - 10*sigma" to "mu + 10*sigma". If min or max is beyond these values, those values would
152          * result erroneously. For any cumulative probability that is slightly above 0.0 or slightly below 1.0, values in the
153          * range from "mu - 10*sigma" to "mu + 10*sigma" will always result.
154          */
155         if (cumulativeProbability == 0.0)
156         {
157             return this.min;
158         }
159         if (cumulativeProbability == 1.0)
160         {
161             return this.max;
162         }
163         return getInverseCumulativeProbabilityNotTruncated(this.cumulMin + cumulativeProbability * this.cumulDiff);
164     }
165 
166     /** {@inheritDoc} */
167     @Override
168     public double probDensity(final double x)
169     {
170         if (x < this.min || x > this.max)
171         {
172             return 0.0;
173         }
174         return this.probDensFactor / (Math.sqrt(2 * Math.PI * Math.pow(this.sigma, 2)))
175                 * Math.exp(-1 * Math.pow(x - this.mu, 2) / (2 * Math.pow(this.sigma, 2)));
176     }
177 
178     /** {@inheritDoc} */
179     @Override
180     public String toString()
181     {
182         return "NormalTrunc(" + this.mu + "," + this.sigma + "," + this.min + "," + this.max + ")";
183     }
184 
185     /**
186      * Test.
187      * @param args String[]; args
188      */
189     public static void main(final String[] args)
190     {
191         StreamInterface stream = new MersenneTwister();
192         double mu = 2.0;
193         double sigma = 3.0;
194         double min = -5.0;
195         double max = 4.0;
196         DistNormalTrunc dist = new DistNormalTrunc(stream, mu, sigma, min, max);
197 
198         System.out.println("<< probability density >>");
199         double sum = 0.0;
200         double step = (max - min) / 96;
201         for (double x = min - 2 * step; x <= max + 2 * step; x += step)
202         {
203             double p = dist.probDensity(x);
204             System.out.println(String.format(Locale.GERMAN, "%.8f;%.8f", x, p));
205             sum += p * step;
206         }
207         System.out.println(String.format(Locale.GERMAN, "Approx. sum = %.8f", sum));
208         System.out.println("");
209 
210         System.out.println("<< cumulative density >>");
211         for (double x = min - 2 * step; x <= max + 2 * step; x += step)
212         {
213             double c = dist.getCumulativeProbability(x);
214             System.out.println(String.format(Locale.GERMAN, "%.8f;%.8f", x, c));
215         }
216         System.out.println("");
217 
218         System.out.println("<< inverse cumulative density >>");
219         for (double c = 0.0; c < 1.005; c += 0.01)
220         {
221             double x = dist.getInverseCumulativeProbability(Math.min(c, 1.0)); // want to include 1.0, also if 1.0000000000001
222             System.out.println(String.format(Locale.GERMAN, "%.8f;%.8f", c, x));
223         }
224         System.out.println("");
225 
226         System.out.println("<< 10000 random numbers. >>");
227         for (int i = 1; i < 10000; i++)
228         {
229             System.out.println(String.format(Locale.GERMAN, "%,8f", dist.draw()));
230         }
231 
232     }
233 
234     /**
235      * returns the x-value of the given cumulativePropability.
236      * @param cumulativeProbability double; reflects cum prob
237      * @return double the inverse cumulative probability
238      */
239     private double getInverseCumulativeProbabilityNotTruncated(final double cumulativeProbability)
240     {
241         if (cumulativeProbability < 0 || cumulativeProbability > 1)
242         {
243             throw new IllegalArgumentException("1<cumulativeProbability<0 ?");
244         }
245         boolean located = false;
246         double prob = cumulativeProbability;
247         if (cumulativeProbability < 0.5)
248         {
249             prob = 1 - cumulativeProbability;
250         }
251         int i = 0;
252         double f = 0.0;
253         while (!located)
254         {
255             if (CUMULATIVE_NORMAL_PROBABILITIES[i] < prob && CUMULATIVE_NORMAL_PROBABILITIES[i + 1] >= prob)
256             {
257                 located = true;
258                 if (CUMULATIVE_NORMAL_PROBABILITIES[i] < CUMULATIVE_NORMAL_PROBABILITIES[i + 1])
259                 {
260                     f = (prob - CUMULATIVE_NORMAL_PROBABILITIES[i])
261                             / (CUMULATIVE_NORMAL_PROBABILITIES[i + 1] - CUMULATIVE_NORMAL_PROBABILITIES[i]);
262                 }
263             }
264             else
265             {
266                 i++;
267             }
268         }
269         if (cumulativeProbability < 0.5)
270         {
271             return this.mu - ((f + i) / 100.0) * this.sigma;
272         }
273         return ((f + i) / 100.0) * this.sigma + this.mu;
274     }
275 
276 }