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  package org.apache.commons.statistics.distribution;
18  
19  import java.util.concurrent.ThreadLocalRandom;
20  import org.apache.commons.rng.UniformRandomProvider;
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.ValueSource;
25  
26  /**
27   * Test default implementations in the {@link DiscreteDistribution} interface.
28   */
29  class DiscreteDistributionTest {
30      /**
31       * Test the default interface methods.
32       */
33      @Test
34      void testDefaultMethods() {
35          // Implement methods called by the defaults
36          final DiscreteDistribution dist = new InvalidDiscreteDistribution() {
37              @Override
38              public double probability(int x) {
39                  return x;
40              }
41  
42              @Override
43              public double cumulativeProbability(int x) {
44                  // Return some different values to allow the survival probability to be
45                  // tested
46                  if (x < 0) {
47                      return x < -5 ? 0.25 : 0.5;
48                  }
49                  return x > 5 ? 1.0 : 0.75;
50              }
51  
52              @Override
53              public int inverseCumulativeProbability(double p) {
54                  // For the default inverseSurvivalProbability(double) method
55                  return (int) (10 * p);
56              }
57          };
58  
59          for (final int x : new int[] {Integer.MIN_VALUE, -1, 0, 1, 2, Integer.MAX_VALUE}) {
60              // Return the log of the density
61              Assertions.assertEquals(Math.log(x), dist.logProbability(x));
62              // Must return 1 - CDF(x)
63              Assertions.assertEquals(1.0 - dist.cumulativeProbability(x), dist.survivalProbability(x));
64          }
65  
66          for (final double p : new double[] {0.2, 0.5, 0.7}) {
67              Assertions.assertEquals(dist.inverseCumulativeProbability(1 - p),
68                                      dist.inverseSurvivalProbability(p));
69          }
70      }
71  
72      /**
73       * Test the default implementation of probability in a range.
74       */
75      @Test
76      void testDefaultProbabilityRange() {
77          // Return a marker probability. This should be unique for each input.
78          final DiscreteDistribution dist = new InvalidDiscreteDistribution() {
79              @Override
80              public double probability(int x) {
81                  // Value >= 1
82                  return x + (1L << 31) + 1;
83              }
84  
85              @Override
86              public double cumulativeProbability(int x) {
87                  // Value in [-1, 1)
88                  return x * 0x1.0p-31;
89              }
90  
91              @Override
92              public int getSupportLowerBound() {
93                  return Integer.MIN_VALUE;
94              }
95  
96              @Override
97              public int getSupportUpperBound() {
98                  return Integer.MAX_VALUE;
99              }
100         };
101 
102         // Test default implementation
103         final int[] values = {
104             Integer.MIN_VALUE, Integer.MIN_VALUE + 1,
105             -3, -2, -1, 0, 1, 2, 3,
106             Integer.MAX_VALUE - 1, Integer.MAX_VALUE
107         };
108         for (final int x0 : values) {
109             // probability(x, x) == 0.0
110             Assertions.assertEquals(0.0, dist.probability(x0, x0));
111             if (x0 < dist.getSupportUpperBound()) {
112                 // probability(x, x + 1) == probability(x + 1)
113                 Assertions.assertEquals(dist.probability(x0 + 1), dist.probability(x0, x0 + 1));
114                 for (final int x1 : values) {
115                     if (x1 > x0 + 1) {
116                         // probability(x0, x1) == cdf(x1) - cdf(x0)
117                         Assertions.assertEquals(dist.cumulativeProbability(x1) - dist.cumulativeProbability(x0),
118                                                 dist.probability(x0, x1));
119                     } else if (x1 < x0) {
120                         Assertions.assertThrows(IllegalArgumentException.class, () -> dist.probability(x0, x1));
121                     }
122                 }
123             }
124         }
125     }
126 
127     /**
128      * Test the default implementation of probability in a range calls the probability function
129      * when x+1 or x-1 would overflow.
130      */
131     @Test
132     void testDefaultProbabilityRangeOverflow() {
133         // Return only the probability.
134         // The CDF should not be called.
135         final DiscreteDistribution dist = new InvalidDiscreteDistribution() {
136             @Override
137             public double probability(int x) {
138                 return x;
139             }
140         };
141 
142         // Extreme x at the integer limits
143         final int min = Integer.MIN_VALUE;
144         final int max = Integer.MAX_VALUE;
145         Assertions.assertEquals(0.0, dist.probability(min, min));
146         Assertions.assertEquals(0.0, dist.probability(max, max));
147         Assertions.assertEquals(min + 1, dist.probability(min, min + 1));
148         Assertions.assertEquals(max, dist.probability(max - 1, max));
149     }
150 
151     /**
152      * Test the {@link DiscreteDistribution.Sampler} default stream methods.
153      *
154      * @param streamSize Number of values to generate.
155      */
156     @ParameterizedTest
157     @ValueSource(longs = {0, 1, 13})
158     void testSamplerStreamMethods(long streamSize) {
159         final int seed = ThreadLocalRandom.current().nextInt();
160         final DiscreteDistribution.Sampler s1 = createIncrementSampler(seed);
161         final DiscreteDistribution.Sampler s2 = createIncrementSampler(seed);
162         final DiscreteDistribution.Sampler s3 = createIncrementSampler(seed);
163         // Get the reference output from the sample() method
164         final int[] x = new int[(int) streamSize];
165         for (int i = 0; i < x.length; i++) {
166             x[i] = s1.sample();
167         }
168         // Test default stream methods
169         Assertions.assertArrayEquals(x, s2.samples().limit(streamSize).toArray(), "samples()");
170         Assertions.assertArrayEquals(x, s3.samples(streamSize).toArray(), "samples(long)");
171     }
172 
173     /**
174      * Test the {@link DiscreteDistribution.Sampler} default stream method with a bad stream size.
175      *
176      * @param streamSize Number of values to generate.
177      */
178     @ParameterizedTest
179     @ValueSource(longs = {-1, -6576237846822L})
180     void testSamplerStreamMethodsThrow(long streamSize) {
181         final DiscreteDistribution.Sampler s = createIncrementSampler(42);
182         Assertions.assertThrows(IllegalArgumentException.class, () -> s.samples(streamSize));
183     }
184 
185     /**
186      * Test the {@link DiscreteDistribution.Sampler} default stream methods are not parallel.
187      */
188     @Test
189     void testSamplerStreamMethodsNotParallel() {
190         final DiscreteDistribution.Sampler s = createIncrementSampler(42);
191         Assertions.assertFalse(s.samples().isParallel(), "samples() should not be parallel");
192         Assertions.assertFalse(s.samples(11).isParallel(), "samples(long) should not be parallel");
193     }
194 
195     /**
196      * Creates the sampler with a given seed value.
197      * Each successive output sample will increment this value by 1.
198      *
199      * @param seed Seed value.
200      * @return the sampler
201      */
202     private static DiscreteDistribution.Sampler createIncrementSampler(int seed) {
203         return new DiscreteDistribution.Sampler() {
204             private int x = seed;
205 
206             @Override
207             public int sample() {
208                 return x += 1;
209             }
210         };
211     }
212 
213     /**
214      * Invalid implementation of DiscreteDistribution that raise an exception for all methods.
215      * Ensures the methods are not called.
216      */
217     private abstract static class InvalidDiscreteDistribution implements DiscreteDistribution {
218         @Override
219         public double probability(int x) {
220             throw new AssertionError();
221         }
222         @Override
223         public double cumulativeProbability(int x) {
224             throw new AssertionError();
225         }
226         @Override
227         public int inverseCumulativeProbability(double p) {
228             throw new AssertionError();
229         }
230         @Override
231         public double getMean() {
232             throw new AssertionError();
233         }
234         @Override
235         public double getVariance() {
236             throw new AssertionError();
237         }
238         @Override
239         public int getSupportLowerBound() {
240             throw new AssertionError();
241         }
242         @Override
243         public int getSupportUpperBound() {
244             throw new AssertionError();
245         }
246         @Override
247         public Sampler createSampler(UniformRandomProvider rng) {
248             throw new AssertionError();
249         }
250     }
251 }