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    *      https://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.util.stream.IntStream;
21  import java.util.stream.Stream;
22  import org.apache.commons.numbers.core.Precision;
23  import org.apache.commons.rng.simple.RandomSource;
24  import org.junit.jupiter.api.Assertions;
25  import org.junit.jupiter.api.Test;
26  import org.junit.jupiter.params.ParameterizedTest;
27  import org.junit.jupiter.params.provider.Arguments;
28  import org.junit.jupiter.params.provider.CsvSource;
29  import org.junit.jupiter.params.provider.MethodSource;
30  
31  /**
32   * Test cases for {@link HypergeometricDistribution}.
33   * Extends {@link BaseDiscreteDistributionTest}. See javadoc of that class for details.
34   */
35  class HypergeometricDistributionTest extends BaseDiscreteDistributionTest {
36      @Override
37      DiscreteDistribution makeDistribution(Object... parameters) {
38          final int populationSize = (Integer) parameters[0];
39          final int numberOfSuccesses = (Integer) parameters[1];
40          final int sampleSize = (Integer) parameters[2];
41          return HypergeometricDistribution.of(populationSize, numberOfSuccesses, sampleSize);
42      }
43  
44      @Override
45      Object[][] makeInvalidParameters() {
46          return new Object[][] {
47              {0, 3, 5},
48              {-1, 3, 5},
49              {5, -1, 5},
50              {5, 3, -1},
51              {5, 6, 5},
52              {5, 3, 6},
53          };
54      }
55  
56      @Override
57      String[] getParameterNames() {
58          return new String[] {"PopulationSize", "NumberOfSuccesses", "SampleSize"};
59      }
60  
61      @Override
62      protected double getRelativeTolerance() {
63          return 5e-15;
64      }
65  
66      //-------------------- Additional test cases -------------------------------
67  
68      @ParameterizedTest
69      @MethodSource
70      void testAdditionalMoments(int populationSize,
71                                 int numberOfSuccesses,
72                                 int sampleSize,
73                                 double mean, double variance) {
74          final HypergeometricDistribution dist = HypergeometricDistribution.of(populationSize, numberOfSuccesses, sampleSize);
75          testMoments(dist, mean, variance, DoubleTolerances.ulps(1));
76      }
77  
78      static Stream<Arguments> testAdditionalMoments() {
79          return Stream.of(
80              Arguments.of(1500, 40, 100, 40d * 100d / 1500d, (100d * 40d * (1500d - 100d) * (1500d - 40d)) / (1500d * 1500d * 1499d)),
81              Arguments.of(3000, 55, 200, 55d * 200d / 3000d, (200d * 55d * (3000d - 200d) * (3000d - 55d)) / (3000d * 3000d * 2999d))
82          );
83      }
84  
85      @Test
86      void testLargeValues() {
87          final int populationSize = 3456;
88          final int sampleSize = 789;
89          final int numberOfSucceses = 101;
90          // data[i][3] contains P(x >= x).
91          // It is tested using survivalProbability(x - 1)
92          final double[][] data = {
93              {0.0, 2.75646034603961e-12, 2.75646034603961e-12, 1.0},
94              {1.0, 8.55705370142386e-11, 8.83269973602783e-11, 0.999999999997244},
95              {2.0, 1.31288129219665e-9, 1.40120828955693e-9, 0.999999999911673},
96              {3.0, 1.32724172984193e-8, 1.46736255879763e-8, 0.999999998598792},
97              {4.0, 9.94501711734089e-8, 1.14123796761385e-7, 0.999999985326375},
98              {5.0, 5.89080768883643e-7, 7.03204565645028e-7, 0.999999885876203},
99              {20.0, 0.0760051397707708, 0.27349758476299, 0.802507555007781},
100             {21.0, 0.087144222047629, 0.360641806810619, 0.72650241523701},
101             {22.0, 0.0940378846881819, 0.454679691498801, 0.639358193189381},
102             {23.0, 0.0956897500614809, 0.550369441560282, 0.545320308501199},
103             {24.0, 0.0919766921922999, 0.642346133752582, 0.449630558439718},
104             {25.0, 0.083641637261095, 0.725987771013677, 0.357653866247418},
105             {96.0, 5.93849188852098e-57, 1.0, 6.01900244560712e-57},
106             {97.0, 7.96593036832547e-59, 1.0, 8.05105570861321e-59},
107             {98.0, 8.44582921934367e-61, 1.0, 8.5125340287733e-61},
108             {99.0, 6.63604297068222e-63, 1.0, 6.670480942963e-63},
109             {100.0, 3.43501099007557e-65, 1.0, 3.4437972280786e-65},
110             {101.0, 8.78623800302957e-68, 1.0, 8.78623800302957e-68},
111             // Out of domain
112             {sampleSize + 1, 0, 1.0, 0},
113         };
114 
115         testHypergeometricDistributionProbabilities(populationSize, sampleSize, numberOfSucceses, data);
116     }
117 
118     private static void testHypergeometricDistributionProbabilities(int populationSize, int sampleSize,
119         int numberOfSuccesses, double[][] data) {
120         final HypergeometricDistribution dist = HypergeometricDistribution.of(populationSize, numberOfSuccesses, sampleSize);
121         for (int i = 0; i < data.length; ++i) {
122             final int x = (int)data[i][0];
123             final double pmf = data[i][1];
124             final double actualPmf = dist.probability(x);
125             TestUtils.assertRelativelyEquals(() -> "Expected equals for <" + x + "> pmf", pmf, actualPmf, 1.0e-9);
126 
127             final double cdf = data[i][2];
128             final double actualCdf = dist.cumulativeProbability(x);
129             TestUtils.assertRelativelyEquals(() -> "Expected equals for <" + x + "> cdf", cdf, actualCdf, 1.0e-9);
130 
131             final double cdf1 = data[i][3];
132             final double actualCdf1 = dist.survivalProbability(x - 1);
133             TestUtils.assertRelativelyEquals(() -> "Expected equals for <" + x + "> cdf1", cdf1, actualCdf1, 1.0e-9);
134         }
135     }
136 
137     @Test
138     void testMoreLargeValues() {
139         final int populationSize = 26896;
140         final int sampleSize = 895;
141         final int numberOfSucceses = 55;
142         final double[][] data = {
143             {0.0, 0.155168304750504, 0.155168304750504, 1.0},
144             {1.0, 0.29437545000746, 0.449543754757964, 0.844831695249496},
145             {2.0, 0.273841321577003, 0.723385076334967, 0.550456245242036},
146             {3.0, 0.166488572570786, 0.889873648905753, 0.276614923665033},
147             {4.0, 0.0743969744713231, 0.964270623377076, 0.110126351094247},
148             {5.0, 0.0260542785784855, 0.990324901955562, 0.0357293766229237},
149             {20.0, 3.57101101678792e-16, 1.0, 3.78252101622096e-16},
150             {21.0, 2.00551638598312e-17, 1.0, 2.11509999433041e-17},
151             {22.0, 1.04317070180562e-18, 1.0, 1.09583608347287e-18},
152             {23.0, 5.03153504903308e-20, 1.0, 5.266538166725e-20},
153             {24.0, 2.2525984149695e-21, 1.0, 2.35003117691919e-21},
154             {25.0, 9.3677424515947e-23, 1.0, 9.74327619496943e-23},
155             {50.0, 9.83633962945521e-69, 1.0, 9.8677629437617e-69},
156             {51.0, 3.13448949497553e-71, 1.0, 3.14233143064882e-71},
157             {52.0, 7.82755221928122e-74, 1.0, 7.84193567329055e-74},
158             {53.0, 1.43662126065532e-76, 1.0, 1.43834540093295e-76},
159             {54.0, 1.72312692517348e-79, 1.0, 1.7241402776278e-79},
160             {55.0, 1.01335245432581e-82, 1.0, 1.01335245432581e-82},
161         };
162         testHypergeometricDistributionProbabilities(populationSize, sampleSize, numberOfSucceses, data);
163     }
164 
165     /**
166      * Test Math-644 is ported from Commons Math 3 where the distribution had the function
167      * upperCumulativeProbability(x) to compute P(X >= x). This has been replaced
168      * in Commons Statistics with survivalProbability(x) which computes P(X > x). To
169      * create the equivalent use survivalProbability(x - 1).
170      */
171     @Test
172     void testMath644() {
173         final int N = 14761461;  // population
174         final int m = 1035;      // successes in population
175         final int n = 1841;      // number of trials
176 
177         final int k = 0;
178         final HypergeometricDistribution dist = HypergeometricDistribution.of(N, m, n);
179 
180         // Compute upper cumulative probability using the survival probability
181         Assertions.assertEquals(0, Precision.compareTo(1.0, dist.survivalProbability(k - 1), 1));
182         Assertions.assertTrue(Precision.compareTo(dist.cumulativeProbability(k), 0.0, 1) > 0);
183 
184         // another way to calculate the upper cumulative probability
185         final double upper = 1.0 - dist.cumulativeProbability(k) + dist.probability(k);
186         Assertions.assertEquals(0, Precision.compareTo(1.0, upper, 1));
187     }
188 
189     @Test
190     void testZeroTrials() {
191         final int n = 11; // population
192         final int m = 4;  // successes in population
193         final int s = 0;  // number of trials
194 
195         final HypergeometricDistribution dist = HypergeometricDistribution.of(n, m, s);
196 
197         for (int i = 1; i <= n; i++) {
198             final double p = dist.probability(i);
199             Assertions.assertEquals(0, p, () -> "p=" + p);
200         }
201     }
202 
203     @Test
204     void testMath1356() {
205         final int n = 11;  // population
206         final int m = 11;  // successes in population
207 
208         for (int s = 0; s <= n; s++) {
209             final HypergeometricDistribution dist = HypergeometricDistribution.of(n, m, s);
210             final double p = dist.probability(s);
211             Assertions.assertEquals(1, p, () -> "p=" + p);
212         }
213     }
214 
215     @Test
216     void testMath1021() {
217         final int N = 43130568;
218         final int m = 42976365;
219         final int n = 50;
220         final DiscreteDistribution.Sampler dist =
221             HypergeometricDistribution.of(N, m, n).createSampler(RandomSource.XO_SHI_RO_256_PP.create());
222 
223         for (int i = 0; i < 100; i++) {
224             final int sample = dist.sample();
225             Assertions.assertTrue(0 <= sample, () -> "sample=" + sample);
226             Assertions.assertTrue(sample <= n, () -> "sample=" + sample);
227         }
228     }
229 
230     @Test
231     void testAdditionalCumulativeProbabilityHighPrecision() {
232         // computed using R version 3.4.4
233         testCumulativeProbabilityHighPrecision(
234             HypergeometricDistribution.of(500, 70, 300),
235             new int[] {10, 8},
236             new double[] {2.4055720603264525e-17, 1.2848174992266236e-19},
237             DoubleTolerances.relative(5e-14));
238     }
239 
240     @Test
241     void testAdditionalSurvivalProbabilityHighPrecision() {
242         // computed using R version 3.4.4
243         testSurvivalProbabilityHighPrecision(
244             HypergeometricDistribution.of(500, 70, 300),
245             new int[] {68, 69},
246             new double[] {4.570379934029859e-16, 7.4187180434325268e-18},
247             DoubleTolerances.relative(5e-14));
248     }
249 
250     @ParameterizedTest
251     @CsvSource({
252         "1, 0, 0",
253         "1, 1, 0",
254         "1, 0, 1",
255         "1, 1, 1",
256         "2, 1, 1",
257         "2, 1, 2",
258         "2, 2, 1",
259         "2, 2, 2",
260         "3, 1, 1",
261         "3, 1, 2",
262         "3, 1, 3",
263         "3, 2, 1",
264         "3, 2, 2",
265         "3, 2, 3",
266         "3, 3, 1",
267         "3, 3, 2",
268         "3, 3, 3",
269         // Mean = n * K / N
270         "15, 9, 7", // 4.2
271         "23, 13, 11", // 6.22
272         "200, 130, 70", // 45.5
273     })
274     void testAdditionalInverseMapping(int populationSize, int numberOfSuccesses, int sampleSize) {
275         final HypergeometricDistribution dist = HypergeometricDistribution.of(populationSize, numberOfSuccesses, sampleSize);
276         final int[] points = IntStream.rangeClosed(dist.getSupportLowerBound(), dist.getSupportUpperBound()).toArray();
277         testCumulativeProbabilityInverseMapping(dist, points);
278         testSurvivalProbabilityInverseMapping(dist, points);
279     }
280 }