View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.statistics.distribution;
19  
20  import java.math.BigDecimal;
21  import org.junit.jupiter.api.Assertions;
22  import org.junit.jupiter.api.Test;
23  import org.junit.jupiter.params.ParameterizedTest;
24  import org.junit.jupiter.params.provider.CsvFileSource;
25  
26  /**
27   * Test cases for {@link NormalDistribution}.
28   * Extends {@link BaseContinuousDistributionTest}. See javadoc of that class for details.
29   */
30  class NormalDistributionTest extends BaseContinuousDistributionTest {
31      /** A standard normal distribution used for calculations.
32       * This is immutable and thread-safe and can be used across instances. */
33      private static final NormalDistribution STANDARD_NORMAL = NormalDistribution.of(0, 1);
34  
35      @Override
36      ContinuousDistribution makeDistribution(Object... parameters) {
37          final double mean = (Double) parameters[0];
38          final double sd = (Double) parameters[1];
39          return NormalDistribution.of(mean, sd);
40      }
41  
42      @Override
43      Object[][] makeInvalidParameters() {
44          return new Object[][] {
45              {0.0, 0.0},
46              {0.0, -0.1}
47          };
48      }
49  
50      @Override
51      String[] getParameterNames() {
52          return new String[] {"Mean", "StandardDeviation"};
53      }
54  
55      @Override
56      protected double getRelativeTolerance() {
57          // Tolerance is 2.220446049250313E-15
58          return 10 * RELATIVE_EPS;
59      }
60  
61      //-------------------- Additional test cases -------------------------------
62  
63      @Test
64      void testCumulativeProbabilityExtremes() {
65          final NormalDistribution dist = NormalDistribution.of(0, 1);
66          testCumulativeProbability(dist,
67                                    new double[] {-Double.MAX_VALUE, Double.MAX_VALUE,
68                                                  Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY},
69                                    new double[] {0, 1, 0, 1},
70                                    DoubleTolerances.equals());
71      }
72  
73      /**
74       * Check to make sure top-coding of extreme values works correctly.
75       * Verifies fixes for JIRA MATH-167, MATH-414.
76       */
77      @Test
78      void testLowerTail() {
79          final NormalDistribution dist = NormalDistribution.of(0, 1);
80          for (int i = 0; i < 100; i++) { // make sure no convergence exception
81              final double cdf = dist.cumulativeProbability(-i);
82              if (i < 39) { // make sure not top-coded
83                  Assertions.assertTrue(cdf > 0);
84              } else { // make sure top coding not reversed
85                  Assertions.assertEquals(0, cdf);
86              }
87              final double sf = dist.survivalProbability(-i);
88              if (i < 9) { // make sure not top-coded
89                  Assertions.assertTrue(sf < 1);
90              } else { // make sure top coding not reversed
91                  Assertions.assertEquals(1, sf);
92              }
93          }
94      }
95  
96      /**
97       * Check to make sure top-coding of extreme values works correctly.
98       * Verifies fixes for JIRA MATH-167, MATH-414.
99       */
100     @Test
101     void testUpperTail() {
102         final NormalDistribution dist = NormalDistribution.of(0, 1);
103         for (int i = 0; i < 100; i++) { // make sure no convergence exception
104             final double cdf = dist.cumulativeProbability(i);
105             if (i < 9) { // make sure not top-coded
106                 Assertions.assertTrue(cdf < 1);
107             } else { // make sure top coding not reversed
108                 Assertions.assertEquals(1, cdf);
109             }
110             // Test survival probability
111             final double sf = dist.survivalProbability(i);
112             if (i < 39) { // make sure not top-coded
113                 Assertions.assertTrue(sf > 0);
114             } else { // make sure top coding not reversed
115                 Assertions.assertEquals(0, sf);
116             }
117         }
118     }
119 
120     @Test
121     void testMath1257() {
122         final ContinuousDistribution dist = NormalDistribution.of(0, 1);
123         final double x = -10;
124         final double expected = 7.61985e-24;
125         final double v = dist.cumulativeProbability(x);
126         Assertions.assertEquals(1.0, v / expected, 1e-5);
127     }
128 
129     @Test
130     void testMath280() {
131         final NormalDistribution dist = NormalDistribution.of(0, 1);
132         // Tolerance limited by precision of p close to 1.
133         // Lower the tolerance as the p value approaches 1.
134         double result;
135         result = dist.inverseCumulativeProbability(0.841344746068543);
136         TestUtils.assertEquals(1.0, result, createRelTolerance(1e-15));
137         result = dist.inverseCumulativeProbability(0.9772498680518209);
138         TestUtils.assertEquals(2.0, result, createRelTolerance(1e-14));
139         result = dist.inverseCumulativeProbability(0.9986501019683698);
140         TestUtils.assertEquals(3.0, result, createRelTolerance(1e-13));
141         result = dist.inverseCumulativeProbability(0.9999683287581673);
142         TestUtils.assertEquals(4.0, result, createRelTolerance(1e-12));
143     }
144 
145     /**
146      * Test the inverse CDF is supported through the entire range of small values
147      * that can be computed by the CDF. Approximate limit is x down to -38
148      * (CDF around 2.8854e-316).
149      * Verifies fix for STATISTICS-37.
150      */
151     @Test
152     void testInverseCDF() {
153         final NormalDistribution dist = NormalDistribution.of(0, 1);
154         Assertions.assertEquals(0.0, dist.inverseCumulativeProbability(0.5));
155         // Get smaller and the CDF should reduce.
156         double x = 0;
157         for (;;) {
158             x -= 1;
159             final double cdf = dist.cumulativeProbability(x);
160             if (cdf == 0) {
161                 break;
162             }
163             final double x0 = dist.inverseCumulativeProbability(cdf);
164             // Must be close
165             Assertions.assertEquals(x, x0, Math.abs(x) * 1e-11, () -> "CDF = " + cdf);
166         }
167     }
168 
169     /**
170      * Test the PDF using high-accuracy uniform x data.
171      *
172      * <p>This dataset uses uniformly spaced machine representable x values that have no
173      * round-off component when squared. If the density is implemented using
174      * {@code exp(logDensity(x))} the test will fail. Using the log density requires a
175      * tolerance of approximately 53 ULP to pass the test of larger x values.
176      */
177     @ParameterizedTest
178     @CsvFileSource(resources = "normpdf.csv")
179     void testPDF(double x, BigDecimal expected) {
180         assertPDF(x, expected, 2);
181     }
182 
183     /**
184      * Test the PDF using high-accuracy random x data.
185      *
186      * <p>This dataset uses random x values with full usage of the 52-bit mantissa to ensure
187      * that there is a round-off component when squared. It requires a high precision exponential
188      * function using the round-off to compute {@code exp(-0.5*x*x)} accurately.
189      * Using a standard precision computation requires a tolerance of approximately 383 ULP
190      * to pass the test of larger x values.
191      *
192      * <p>See STATISTICS-52.
193      */
194     @ParameterizedTest
195     @CsvFileSource(resources = "normpdf2.csv")
196     void testPDF2(double x, BigDecimal expected) {
197         assertPDF(x, expected, 3);
198     }
199 
200     private static void assertPDF(double x, BigDecimal expected, int ulpTolerance) {
201         final double e = expected.doubleValue();
202         final double a = STANDARD_NORMAL.density(x);
203         Assertions.assertEquals(e, a, Math.ulp(e) * ulpTolerance,
204             () -> "ULP error: " + expected.subtract(new BigDecimal(a)).doubleValue() / Math.ulp(e));
205     }
206 }