View Javadoc
1   /*
2    * Copyright (C) 2015 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.statistics;
17  
18  /**
19   * Contains methods to work with normal (i.e. Gaussian) distributions.
20   * Methods of this class use methods of the Erf class.
21   * This class is based in code of Numerical Recipes 3rd ed. section 6.14.1.
22   */
23  public class NormalDist {
24      /**
25       * Square root of 2.
26       */
27      private static final double SQRT2 = Math.sqrt(2.0);
28  
29      /**
30       * Square root of 2 divided by 2.
31       */
32      private static final double HALF_SQRT2 = SQRT2 / 2.0;
33  
34      /**
35       * Term to normalize Gaussian so that its integral from -infinity to infinity is one.
36       */
37      private static final double GAUSSIAN_NORM = 1.0 / Math.sqrt(2.0 * Math.PI);
38  
39      /**
40       * Mean value of Gaussian distribution.
41       */
42      private double mu;
43  
44      /**
45       * Standard deviation of Gaussian distribution.
46       */
47      private double sig;
48  
49      /**
50       * Constructor. Initializes a Gaussian distribution with zero mean and
51       * unitary standard deviation (i.e. N(0,1)).
52       */
53      public NormalDist() {
54          mu = 0.0;
55          sig = 1.0;
56      }
57  
58      /**
59       * Constructor with mean and standard deviation.
60       *
61       * @param mu  mean value of Gaussian distribution.
62       * @param sig standard deviation of Gaussian distribution.
63       * @throws IllegalArgumentException if provided standard deviation is zero
64       *                                  or negative.
65       */
66      public NormalDist(final double mu, final double sig) {
67          setStandardDeviation(sig);
68          setMean(mu);
69      }
70  
71      /**
72       * Gets mean value of Gaussian distribution.
73       *
74       * @return mean value of Gaussian distribution.
75       */
76      public double getMean() {
77          return mu;
78      }
79  
80      /**
81       * Sets mean value of Gaussian distribution.
82       *
83       * @param mu mean value of Gaussian distribution.
84       */
85      public final void setMean(final double mu) {
86          this.mu = mu;
87      }
88  
89      /**
90       * Gets standard deviation of Gaussian distribution.
91       *
92       * @return standard deviation of Gaussian distribution.
93       */
94      public double getStandardDeviation() {
95          return sig;
96      }
97  
98      /**
99       * Sets standard deviation of Gaussian distribution.
100      *
101      * @param sig standard deviation to be set.
102      * @throws IllegalArgumentException if provided standard deviation is zero
103      *                                  or negative.
104      */
105     public final void setStandardDeviation(final double sig) {
106         if (sig <= 0.0) {
107             throw new IllegalArgumentException();
108         }
109         this.sig = sig;
110     }
111 
112     /**
113      * Gets variance of Gaussian distribution.
114      *
115      * @return variance of Gaussian distribution.
116      */
117     public double getVariance() {
118         return sig * sig;
119     }
120 
121     /**
122      * Sets variance of Gaussian distribution.
123      *
124      * @param variance variance of Gaussian distribution.
125      * @throws IllegalArgumentException if provided variance is zero or
126      *                                  negative.
127      */
128     public void setVariance(final double variance) {
129         if (variance <= 0.0) {
130             throw new IllegalArgumentException(
131                     "variance must be greater than zero");
132         }
133         sig = Math.sqrt(variance);
134     }
135 
136     /**
137      * Evaluates the probability density function (p.d.f.) of a Gaussian
138      * distribution having mean mu and standard deviation sig at provided point
139      * x.
140      *
141      * @param x   point where p.d.f. is evaluated.
142      * @param mu  mean of Gaussian distribution.
143      * @param sig standard deviation of Gaussian distribution.
144      * @return evaluation of p.d.f.
145      * @throws IllegalArgumentException if provided standard deviation is zero
146      *                                  or negative.
147      */
148     public static double p(final double x, final double mu, final double sig) {
149         if (sig <= 0.0) {
150             throw new IllegalArgumentException();
151         }
152 
153         return internalP(x, mu, sig);
154     }
155 
156     /**
157      * Evaluates the probability density function (p.d.f.) of a Gaussian
158      * distribution having the mean and standard deviation of this instance at
159      * provided point x.
160      *
161      * @param x point where p.d.f. is evaluated.
162      * @return evaluation of p.d.f.
163      */
164     public double p(final double x) {
165         return internalP(x, mu, sig);
166     }
167 
168     /**
169      * Evaluates the cumulative distribution function (c.d.f.) of a Gaussian
170      * distribution having mean mu and standard deviation sig at provided point
171      * x.
172      * The c.d.f is equivalent to the probability of the Gaussian distribution
173      * of having a value less than x, and it is computed as the integral from
174      * -infinity to x of the Gaussian p.d.f.
175      *
176      * @param x   point where c.d.f. is evaluated.
177      * @param mu  mean of Gaussian distribution.
178      * @param sig standard deviation of Gaussian distribution.
179      * @return evaluation of c.d.f.
180      * @throws IllegalArgumentException if provided standard deviation is zero
181      *                                  or negative.
182      */
183     public static double cdf(final double x, final double mu, final double sig) {
184         if (sig <= 0.0) {
185             throw new IllegalArgumentException();
186         }
187 
188         return internalCdf(x, mu, sig);
189     }
190 
191     /**
192      * Evaluates the cumulative distribution function (c.d.f.) of a Gaussian
193      * distribution having the mean and standard deviation of this instance at
194      * provided point x.
195      * The c.d.f is equivalent to the probability of the Gaussian distribution
196      * of having a value less than x, and it is computed as the integral from
197      * -infinity to x of the Gaussian p.d.f.
198      * Because the c.d.f is a probability, it always returns values between 0.0
199      * and 1.0.
200      *
201      * @param x point where c.d.f. is evaluated.
202      * @return evaluation of c.d.f.
203      */
204     public double cdf(final double x) {
205         return internalCdf(x, mu, sig);
206     }
207 
208     /**
209      * Evaluates the inverse cumulative distribution function of a Gaussian
210      * distribution having mean mu and standard deviation sig at provided point
211      * p.
212      * Because the c.d.f is a monotonically increasing function with values
213      * between 0.0 and 1.0, its inverse is uniquely defined between such range
214      * of values.
215      *
216      * @param p   value to evaluate the inverse c.d.f. at. This value is
217      *            equivalent to a probability and must be between 0.0 and 1.0.
218      * @param mu  mean of Gaussian distribution.
219      * @param sig standard deviation of Gaussian distribution.
220      * @return the value x for which the c.d.f. has value p.
221      * @throws IllegalArgumentException if provided standard deviation is zero
222      *                                  or negative, or if provided probability value is not between 0.0 and 1.0.
223      */
224     public static double invcdf(final double p, final double mu, final double sig) {
225         if (sig <= 0.0) {
226             throw new IllegalArgumentException("standard deviation must be greater than zero");
227         }
228 
229         return internalInvcdf(p, mu, sig);
230     }
231 
232     /**
233      * Evaluates the inverse cumulative distribution function of a Gaussian
234      * distribution having the mean and standard deviation of this instance at
235      * provided point p.
236      * Because the c.d.f is a monotonically increasing function with values
237      * between 0.0 and 1.0, its inverse is uniquely defined between such range
238      * of values.
239      *
240      * @param p value to evaluate the inverse c.d.f. at. This value is
241      *          equivalent to a probability and must be between 0.0 and 1.0.
242      * @return the value x for which the c.d.f. has value p.
243      * @throws IllegalArgumentException if provided probability value is not
244      *                                  between 0.0 and 1.0.
245      */
246     public double invcdf(final double p) {
247         return internalInvcdf(p, mu, sig);
248     }
249 
250     /**
251      * Computes the Mahalanobis distance of provided point x for provided
252      * mean and standard deviation values.
253      *
254      * @param x   point where Mahalanobis distance is evaluated.
255      * @param mu  mean of Gaussian distribution.
256      * @param sig standard deviation of Gaussian distribution.
257      * @return Mahalanobis distance of provided point respect to mean.
258      * @throws IllegalArgumentException if provided standard deviation is zero
259      *                                  or negative.
260      */
261     public static double mahalanobisDistance(final double x, final double mu, final double sig) {
262         if (sig <= 0.0) {
263             throw new IllegalArgumentException("standard deviation must be greater than zero");
264         }
265 
266         return internalMahalanobisDistance(x, mu, sig);
267     }
268 
269     /**
270      * Computes the Mahalanobis distance of provided point x for current mean
271      * and standard deviation values.
272      *
273      * @param x point where Mahalanobis distance is evaluated.
274      * @return Mahalanobis distance of provided point respect to current mean.
275      */
276     public double mahalanobisDistance(final double x) {
277         return internalMahalanobisDistance(x, mu, sig);
278     }
279 
280     /**
281      * Evaluates the probability density function (p.d.f.) of a Gaussian
282      * distribution having mean mu and standard deviation sig at provided point
283      * x.
284      * This method is used internally.
285      *
286      * @param x   point where p.d.f. is evaluated.
287      * @param mu  mean of Gaussian distribution.
288      * @param sig standard deviation of Gaussian distribution.
289      * @return evaluation of p.d.f.
290      * @throws IllegalArgumentException if provided standard deviation is zero
291      *                                  or negative.
292      */
293     private static double internalP(final double x, final double mu, final double sig) {
294         return (GAUSSIAN_NORM / sig) * Math.exp(-0.5 * Math.pow((x - mu) / sig, 2.0));
295     }
296 
297     /**
298      * Evaluates the cumulative distribution function (c.d.f.) of a Gaussian
299      * distribution having mean mu and standard deviation sig at provided point
300      * x.
301      * The c.d.f is equivalent to the probability of the Gaussian distribution
302      * of having a value less than x, and it is computed as the integral from
303      * -infinity to x of the Gaussian p.d.f.
304      * This method is used internally.
305      *
306      * @param x   point where c.d.f. is evaluated.
307      * @param mu  mean of Gaussian distribution.
308      * @param sig standard deviation of Gaussian distribution.
309      * @return evaluation of c.d.f.
310      * @throws IllegalArgumentException if provided standard deviation is zero
311      *                                  or negative.
312      */
313     private static double internalCdf(final double x, final double mu, final double sig) {
314         return 0.5 * Erf.erfc(-HALF_SQRT2 * (x - mu) / sig);
315     }
316 
317     /**
318      * Evaluates the inverse cumulative distribution function of a Gaussian
319      * distribution having mean mu and standard deviation sig at provided point
320      * p.
321      * Because the c.d.f is a monotonically increasing function with values
322      * between 0.0 and 1.0, its inverse is uniquely defined between such range
323      * of values.
324      * This method is used internally.
325      *
326      * @param p   value to evaluate the inverse c.d.f. at. This value is
327      *            equivalent to a probability and must be between 0.0 and 1.0.
328      * @param mu  mean of Gaussian distribution.
329      * @param sig standard deviation of Gaussian distribution.
330      * @return the value x for which the c.d.f. has value p.
331      * @throws IllegalArgumentException if provided probability value is not
332      *                                  between 0.0 and 1.0.
333      */
334     private static double internalInvcdf(final double p, final double mu, final double sig)
335             throws IllegalArgumentException {
336         if (p <= 0.0 || p >= 1.0) {
337             throw new IllegalArgumentException("probability value must be between 0.0 and 1.0");
338         }
339         return -SQRT2 * sig * Erf.inverfc(2.0 * p) + mu;
340     }
341 
342     /**
343      * Computes the Mahalanobis distance of provided point x for provided
344      * mean and standard deviation values.
345      *
346      * @param x   point where Mahalanobis distance is evaluated.
347      * @param mu  mean of Gaussian distribution.
348      * @param sig standard deviation of Gaussian distribution.
349      * @return Mahalanobis distance of provided point respect to mean.
350      */
351     private static double internalMahalanobisDistance(final double x, final double mu, final double sig) {
352         return Math.abs(x - mu) / sig;
353     }
354 
355     /**
356      * Evaluates the derivative and a 1D function at a certain mean point and
357      * computes the non-linear propagation of Gaussian uncertainty through such
358      * function at such point.
359      *
360      * @param evaluator         interface to evaluate derivative of a function at a
361      *                          certain point.
362      * @param mean              mean of original Gaussian distribution to be propagated.
363      * @param standardDeviation standard deviation of original Gaussian
364      *                          distribution to be propagated.
365      * @param result            instance where propagated Gaussian distribution will be
366      *                          stored.
367      * @see <a href="https://github.com/joansola/slamtb">propagateUncertainty.m at https://github.com/joansola/slamtb</a>
368      */
369     public static void propagate(
370             final DerivativeEvaluator evaluator, final double mean, final double standardDeviation,
371             final NormalDist result) {
372         final var evaluation = evaluator.evaluate(mean);
373         final var derivative = evaluator.evaluateDerivative(mean);
374         result.setMean(evaluation);
375         result.setStandardDeviation(Math.abs(derivative * standardDeviation));
376     }
377 
378     /**
379      * Evaluates the derivative and a 1D function at a certain mean point and
380      * computes the non-linear propagation of Gaussian uncertainty through such
381      * function at such point.
382      *
383      * @param evaluator         interface to evaluate derivative of a function at a
384      *                          certain point.
385      * @param mean              mean of original Gaussian distribution to be propagated.
386      * @param standardDeviation standard deviation of original Gaussian
387      *                          distribution to be propagated.
388      * @return a new propagated Gaussian distribution.
389      * @see <a href="https://github.com/joansola/slamtb">propagateUncertainty.m at https://github.com/joansola/slamtb</a>
390      */
391     public static NormalDist propagate(
392             final DerivativeEvaluator evaluator, final double mean, final double standardDeviation) {
393         final var result = new NormalDist();
394         propagate(evaluator, mean, standardDeviation, result);
395         return result;
396     }
397 
398     /**
399      * Evaluates the derivative and a 1D function at a certain mean point and
400      * computes the non-linear propagation of Gaussian uncertainty through such
401      * function at such point.
402      *
403      * @param evaluator interface to evaluate derivative of a function at a
404      *                  certain point.
405      * @param dist      1D Gaussian distribution to be propagated.
406      * @param result    instance where propagated Gaussian distribution will be
407      *                  stored.
408      * @see <a href="https://github.com/joansola/slamtb">propagateUncertainty.m at https://github.com/joansola/slamtb</a>
409      */
410     public static void propagate(final DerivativeEvaluator evaluator, final NormalDist dist, final NormalDist result) {
411         propagate(evaluator, dist.getMean(), dist.getStandardDeviation(), result);
412     }
413 
414     /**
415      * Evaluates the derivative and a 1D function at a certain mean point and
416      * computes the non-linear propagation of Gaussian uncertainty through such
417      * function at such point.
418      *
419      * @param evaluator interface to evaluate derivative of a function at a
420      *                  certain point.
421      * @param dist      1D Gaussian distribution to be propagated.
422      * @return a new propagated Gaussian distribution.
423      * @see <a href="https://github.com/joansola/slamtb">propagateUncertainty.m at https://github.com/joansola/slamtb</a>
424      */
425     public static NormalDist propagate(final DerivativeEvaluator evaluator, final NormalDist dist) {
426         final var result = new NormalDist();
427         propagate(evaluator, dist, result);
428         return result;
429     }
430 
431     /**
432      * Evaluates the derivative and a 1D function at the mean point of this
433      * normal distribution and computes the non-linear propagation of Gaussian
434      * uncertainty through such function at such point.
435      *
436      * @param evaluator interface to evaluate derivative of a function at the
437      *                  mean point of this normal distribution.
438      * @param result    instance where propagated Gaussian distribution will be
439      *                  stored.
440      * @see <a href="https://github.com/joansola/slamtb">propagateUncertainty.m at https://github.com/joansola/slamtb</a>
441      */
442     public void propagateThisDistribution(final DerivativeEvaluator evaluator, final NormalDist result) {
443         propagate(evaluator, this, result);
444     }
445 
446     /**
447      * Evaluates the derivative and a 1D function at the mean point of this
448      * normal distribution and computes the non-linear propagation of Gaussian
449      * uncertainty through such function at such point.
450      *
451      * @param evaluator interface to evaluate derivative of a function at the
452      *                  mean point of this normal distribution.
453      * @return a new propagated Gaussian distribution.
454      * @see <a href="https://github.com/joansola/slamtb">propagateUncertainty.m at https://github.com/joansola/slamtb</a>
455      */
456     public NormalDist propagateThisDistribution(final DerivativeEvaluator evaluator) {
457         final var result = new NormalDist();
458         propagateThisDistribution(evaluator, result);
459         return result;
460     }
461 
462     /**
463      * Interface to evaluate a one dimensional function at point x and to obtain
464      * its derivative at such point.
465      */
466     public interface DerivativeEvaluator {
467 
468         /**
469          * Evaluates function at point x.
470          *
471          * @param x point x where derivative is evaluated.
472          * @return evaluation of function at point x.
473          */
474         double evaluate(final double x);
475 
476         /**
477          * Evaluates derivative of a function at point x.
478          *
479          * @param x point x where derivative is evaluated.
480          * @return derivative of one dimensional function at point x.
481          */
482         double evaluateDerivative(final double x);
483     }
484 }