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.Stream;
21  import org.apache.commons.math3.util.MathArrays;
22  import org.junit.jupiter.api.Assertions;
23  import org.junit.jupiter.api.Test;
24  import org.junit.jupiter.params.ParameterizedTest;
25  import org.junit.jupiter.params.provider.Arguments;
26  import org.junit.jupiter.params.provider.CsvSource;
27  import org.junit.jupiter.params.provider.MethodSource;
28  
29  /**
30   * Test cases for {@link UniformDiscreteDistribution}.
31   * Extends {@link BaseDiscreteDistributionTest}. See javadoc of that class for details.
32   */
33  class UniformDiscreteDistributionTest extends BaseDiscreteDistributionTest {
34      @Override
35      DiscreteDistribution makeDistribution(Object... parameters) {
36          final int lower = (Integer) parameters[0];
37          final int upper = (Integer) parameters[1];
38          return UniformDiscreteDistribution.of(lower, upper);
39      }
40  
41  
42      @Override
43      Object[][] makeInvalidParameters() {
44          return new Object[][] {
45              // MATH-1141
46              {1, 0},
47              {3, 2},
48          };
49      }
50  
51      @Override
52      String[] getParameterNames() {
53          return new String[] {"SupportLowerBound", "SupportUpperBound"};
54      }
55  
56      @Override
57      protected double getRelativeTolerance() {
58          // Tolerance is 4.440892098500626E-16
59          return 2 * RELATIVE_EPS;
60      }
61  
62      //-------------------- Additional test cases -------------------------------
63  
64      @ParameterizedTest
65      @MethodSource
66      void testAdditionalMoments(int lower, int upper, double mean, double variance) {
67          final UniformDiscreteDistribution dist = UniformDiscreteDistribution.of(lower, upper);
68          testMoments(dist, mean, variance, DoubleTolerances.equals());
69      }
70  
71      static Stream<Arguments> testAdditionalMoments() {
72          return Stream.of(
73              Arguments.of(0, 5, 2.5, 35 / 12.0),
74              Arguments.of(0, 1, 0.5, 3 / 12.0)
75          );
76      }
77  
78      // MATH-1396
79      @Test
80      void testLargeRangeSubtractionOverflow() {
81          final int hi = Integer.MAX_VALUE / 2 + 10;
82          final int lower = -hi;
83          final int upper = hi - 1;
84  
85          // range = upper - lower + 1 would overflow
86          Assertions.assertTrue(upper - lower < 0);
87  
88          final UniformDiscreteDistribution dist = UniformDiscreteDistribution.of(lower, upper);
89  
90          Assertions.assertEquals(0.5 / hi, dist.probability(123456));
91          Assertions.assertEquals(0.5, dist.cumulativeProbability(-1));
92  
93          Assertions.assertEquals((Math.pow(2d * hi, 2) - 1) / 12, dist.getVariance());
94      }
95  
96      // MATH-1396
97      @Test
98      void testLargeRangeAdditionOverflow() {
99          final int hi = Integer.MAX_VALUE / 2 + 10;
100         final int lower = hi - 1;
101         final int upper = hi + 1;
102 
103         // mean = (lower + upper) / 2 would overflow
104         Assertions.assertTrue(lower + upper < 0);
105 
106         final UniformDiscreteDistribution dist = UniformDiscreteDistribution.of(lower, upper);
107 
108         Assertions.assertEquals(1d / 3d, dist.probability(hi));
109         Assertions.assertEquals(2d / 3d, dist.cumulativeProbability(hi));
110 
111         Assertions.assertEquals(hi, dist.getMean());
112     }
113 
114     /**
115      * Test the inverse CDF returns the correct x from the CDF result.
116      * Test cases created to generate rounding errors on the inversion.
117      */
118     @ParameterizedTest
119     @CsvSource(value = {
120         // Extreme bounds
121         "-2147483648, -2147483648",
122         "-2147483648, -2147483647",
123         "-2147483648, -2147483646",
124         "-2147483648, -2147483638",
125         "2147483647, 2147483647",
126         "2147483646, 2147483647",
127         "2147483645, 2147483647",
128         "2147483637, 2147483647",
129         // icdf(cdf(x)) requires rounding up
130         "3, 40",
131         "71, 201",
132         "223, 267",
133         "45, 125",
134         "53, 81",
135         // icdf(cdf(x)) requires rounding down
136         "48, 247",
137         "141, 222",
138         "106, 223",
139         "156, 201",
140         "86, 265",
141     })
142     void testInverseCDF(int lower, int upper) {
143         final UniformDiscreteDistribution dist = UniformDiscreteDistribution.of(lower, upper);
144         final int[] x = MathArrays.sequence(upper - lower, lower, 1);
145         testCumulativeProbabilityInverseMapping(dist, x);
146     }
147 
148     /**
149      * Test the inverse SF returns the correct x from the SF result.
150      * Test cases created to generate rounding errors on the inversion.
151      */
152     @ParameterizedTest
153     @CsvSource(value = {
154         // Extreme bounds
155         "-2147483648, -2147483648",
156         "-2147483648, -2147483647",
157         "-2147483648, -2147483646",
158         "-2147483648, -2147483638",
159         "2147483647, 2147483647",
160         "2147483646, 2147483647",
161         "2147483645, 2147483647",
162         "2147483637, 2147483647",
163         // isf(sf(x)) requires rounding up
164         "52, 91",
165         "81, 106",
166         "79, 268",
167         "54, 249",
168         "189, 267",
169         // isf(sf(x)) requires rounding down
170         "105, 279",
171         "42, 261",
172         "37, 133",
173         "59, 214",
174         "33, 118",
175     })
176     void testInverseSF(int lower, int upper) {
177         final UniformDiscreteDistribution dist = UniformDiscreteDistribution.of(lower, upper);
178         final int[] x = MathArrays.sequence(upper - lower, lower, 1);
179         testSurvivalProbabilityInverseMapping(dist, x);
180     }
181 
182     /**
183      * Test the probability in a range uses the exact computation of
184      * {@code (x1 - x0) / (upper - lower + 1)} assuming x0 and x1 are within [lower, upper].
185      * This test will fail if the distribution uses the default implementation in
186      * {@link AbstractDiscreteDistribution}.
187      */
188     @ParameterizedTest
189     @CsvSource(value = {
190         // Extreme bounds
191         "-2147483648, -2147483648",
192         "-2147483648, -2147483647",
193         "-2147483648, -2147483646",
194         "-2147483648, -2147483638",
195         "2147483647, 2147483647",
196         "2147483646, 2147483647",
197         "2147483645, 2147483647",
198         "2147483637, 2147483647",
199         // Range is a prime number
200         "-10, 2", // 13
201         "10, 16",  // 7
202         "-20, -10", // 11
203         // Range is even
204         "-10, 3", // 14
205         "10, 17",  // 8
206         "-20, -9", // 12
207         // Large range
208         "-2147483648, 2147483647",
209         "-2147483648, 1263781682",
210         "-2147483648, 1781682",
211         "-2147483648, -231781682",
212         "-1324234584, 2147483647",
213         "-324234584, 2147483647",
214         "6234584, 2147483647",
215         "-1256362376, 125637",
216         "-62378468, 1325657374",
217     })
218     void testProbabilityRange(int lower, int upper) {
219         final UniformDiscreteDistribution dist = UniformDiscreteDistribution.of(lower, upper);
220         final double r = (double) upper - lower + 1;
221         final long stride = r < 20 ? 1 : (long) (r / 20);
222         for (long x0 = lower; x0 <= upper; x0 += stride) {
223             for (long x1 = x0; x1 <= upper; x1 += stride) {
224                 final double p = (x1 - x0) / r;
225                 Assertions.assertEquals(p, dist.probability((int) x0, (int) x1));
226             }
227         }
228     }
229 
230     @Test
231     void testProbabilityRangeEdgeCases() {
232         final UniformDiscreteDistribution dist = UniformDiscreteDistribution.of(3, 5);
233 
234         Assertions.assertThrows(DistributionException.class, () -> dist.probability(4, 3));
235 
236         // x0 >= upper
237         Assertions.assertEquals(0, dist.probability(5, 6));
238         Assertions.assertEquals(0, dist.probability(15, 16));
239         // x1 < lower
240         Assertions.assertEquals(0, dist.probability(-3, 1));
241 
242         // x0 == x1
243         Assertions.assertEquals(0, dist.probability(3, 3));
244         Assertions.assertEquals(0, dist.probability(4, 4));
245         Assertions.assertEquals(0, dist.probability(5, 5));
246         Assertions.assertEquals(0, dist.probability(6, 6));
247 
248         // x0+1 == x1
249         Assertions.assertEquals(1.0 / 3, dist.probability(3, 4));
250         Assertions.assertEquals(1.0 / 3, dist.probability(4, 5));
251 
252         // x1 > upper
253         Assertions.assertEquals(1, dist.probability(2, 6));
254         Assertions.assertEquals(2.0 / 3, dist.probability(3, 6));
255         Assertions.assertEquals(1.0 / 3, dist.probability(4, 6));
256         Assertions.assertEquals(0, dist.probability(5, 6));
257 
258         // x0 < lower
259         Assertions.assertEquals(0, dist.probability(-2, 2));
260         Assertions.assertEquals(1.0 / 3, dist.probability(-2, 3));
261         Assertions.assertEquals(2.0 / 3, dist.probability(-2, 4));
262         Assertions.assertEquals(1.0, dist.probability(-2, 5));
263 
264         // x1 > upper && x0 < lower
265         Assertions.assertEquals(1, dist.probability(-2, 6));
266     }
267 }