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  package org.apache.commons.rng.sampling.distribution;
18  
19  import org.apache.commons.rng.UniformRandomProvider;
20  import org.apache.commons.rng.core.source64.SplitMix64;
21  import org.apache.commons.rng.sampling.RandomAssert;
22  import org.junit.jupiter.api.Assertions;
23  import org.junit.jupiter.api.Test;
24  
25  /**
26   * Test for the {@link ContinuousUniformSampler}.
27   */
28  class ContinuousUniformSamplerTest {
29      /**
30       * Test that the sampler algorithm does not require high to be above low.
31       */
32      @Test
33      void testNoRestrictionOnOrderOfLowAndHighParameters() {
34          final double low = 3.18;
35          final double high = 5.23;
36          final UniformRandomProvider rng = RandomAssert.createRNG();
37          testSampleInRange(rng, low, high);
38          testSampleInRange(rng, high, low);
39      }
40  
41      private static void testSampleInRange(UniformRandomProvider rng,
42                                            double low, double high) {
43          final SharedStateContinuousSampler sampler = ContinuousUniformSampler.of(rng, low, high);
44          final double min = Math.min(low,  high);
45          final double max = Math.max(low,  high);
46          for (int i = 0; i < 10; i++) {
47              final double value = sampler.sample();
48              Assertions.assertTrue(value >= min && value <= max, () -> "Value not in range: " + value);
49          }
50      }
51  
52      /**
53       * Test the sampler excludes the bounds when the underlying generator returns long values
54       * that produce the limit of the uniform double output.
55       */
56      @Test
57      void testExcludeBounds() {
58          // A broken RNG that will return in an alternating sequence from 0 up or -1 down.
59          // This is either zero bits or all the bits
60          final UniformRandomProvider rng = new SplitMix64(0L) {
61              private long l1;
62              private long l2;
63              @Override
64              public long nextLong() {
65                  long x;
66                  if (l1 > l2) {
67                      l2++;
68                      // Descending sequence: -1, -2, -3, ...
69                      x = -l2;
70                  } else {
71                      // Ascending sequence: 0, 1, 2, ...
72                      x = l1++;
73                  }
74                  // Shift by 11 bits to reverse the shift performed when computing the next
75                  // double from a long.
76                  return x << 11;
77              }
78          };
79          final double low = 3.18;
80          final double high = 5.23;
81          final SharedStateContinuousSampler sampler =
82              ContinuousUniformSampler.of(rng, low, high, true);
83          // Test the sampler excludes the end points
84          for (int i = 0; i < 10; i++) {
85              final double value = sampler.sample();
86              Assertions.assertTrue(value > low && value < high, () -> "Value not in range: " + value);
87          }
88      }
89  
90      /**
91       * Test open intervals {@code (lower,upper)} where there are not enough double values
92       * between the limits.
93       */
94      @Test
95      void testInvalidOpenIntervalThrows() {
96          final UniformRandomProvider rng = RandomAssert.seededRNG();
97          for (final double[] interval : new double[][] {
98              // Opposite signs. Require two doubles inside the range.
99              {-0.0, 0.0},
100             {-0.0, Double.MIN_VALUE},
101             {-0.0, Double.MIN_VALUE * 2},
102             {-Double.MIN_VALUE, 0.0},
103             {-Double.MIN_VALUE * 2, 0.0},
104             {-Double.MIN_VALUE, Double.MIN_VALUE},
105             // Same signs. Requires one double inside the range.
106             // Same exponent
107             {1.23, Math.nextUp(1.23)},
108             {1.23, Math.nextUp(1.23)},
109             // Different exponent
110             {2.0, Math.nextDown(2.0)},
111         }) {
112             final double low = interval[0];
113             final double high = interval[1];
114             Assertions.assertThrows(IllegalArgumentException.class,
115                 () -> ContinuousUniformSampler.of(rng, low, high, true),
116                 () -> "(" + low + "," + high + ")");
117             Assertions.assertThrows(IllegalArgumentException.class,
118                 () -> ContinuousUniformSampler.of(rng, high, low, true),
119                 () -> "(" + high + "," + low + ")");
120         }
121 
122         // Valid. This will overflow if the raw long bits are extracted and
123         // subtracted to obtain a ULP difference.
124         ContinuousUniformSampler.of(rng, Double.MAX_VALUE, -Double.MAX_VALUE, true);
125     }
126 
127     /**
128      * Test open intervals {@code (lower,upper)} where there is only the minimum number of
129      * double values between the limits.
130      */
131     @Test
132     void testTinyOpenIntervalSample() {
133         final UniformRandomProvider rng = RandomAssert.seededRNG();
134 
135         // Test sub-normal ranges
136         final double x = Double.MIN_VALUE;
137 
138         for (final double expected : new double[] {
139             1.23, 2, 56787.7893, 3 * x, 2 * x, x
140         }) {
141             final double low = Math.nextUp(expected);
142             final double high = Math.nextDown(expected);
143             Assertions.assertEquals(expected, ContinuousUniformSampler.of(rng, low, high, true).sample());
144             Assertions.assertEquals(expected, ContinuousUniformSampler.of(rng, high, low, true).sample());
145             Assertions.assertEquals(-expected, ContinuousUniformSampler.of(rng, -low, -high, true).sample());
146             Assertions.assertEquals(-expected, ContinuousUniformSampler.of(rng, -high, -low, true).sample());
147         }
148 
149         // Special case of sampling around zero.
150         // Requires 2 doubles inside the range.
151         final double y = ContinuousUniformSampler.of(rng, -x, 2 * x, true).sample();
152         Assertions.assertTrue(-x < y && y < 2 * x);
153         final double z = ContinuousUniformSampler.of(rng, -2 * x, x, true).sample();
154         Assertions.assertTrue(-2 * x < z && z < x);
155     }
156 
157     /**
158      * Test the SharedStateSampler implementation.
159      */
160     @Test
161     void testSharedStateSampler() {
162         testSharedStateSampler(false);
163         testSharedStateSampler(true);
164     }
165 
166     /**
167      * Test the SharedStateSampler implementation.
168      *
169      * @param excludedBounds Set to true to exclude the bounds.
170      */
171     private static void testSharedStateSampler(boolean excludedBounds) {
172         // Create RNGs that will generate a sample at the limits.
173         // This tests the bounds excluded sampler correctly shares state.
174         // Do this using a RNG that outputs 0 for the first nextDouble().
175         final UniformRandomProvider rng1 = new SplitMix64(0L) {
176             private double x;
177             @Override
178             public double nextDouble() {
179                 final double y = x;
180                 x = super.nextDouble();
181                 return y;
182             }
183         };
184         final UniformRandomProvider rng2 = new SplitMix64(0L) {
185             private double x;
186             @Override
187             public double nextDouble() {
188                 final double y = x;
189                 x = super.nextDouble();
190                 return y;
191             }
192         };
193         final double low = 1.23;
194         final double high = 4.56;
195         final SharedStateContinuousSampler sampler1 =
196             ContinuousUniformSampler.of(rng1, low, high, excludedBounds);
197         final SharedStateContinuousSampler sampler2 = sampler1.withUniformRandomProvider(rng2);
198         RandomAssert.assertProduceSameSequence(sampler1, sampler2);
199     }
200 
201     /**
202      * Test the sampler implementation with bounds excluded matches that with bounds included
203      * when the generator does not produce the limit of the uniform double output.
204      */
205     @Test
206     void testSamplerWithBoundsExcluded() {
207         // SplitMix64 only returns zero once in the output. Seeded with zero it outputs zero
208         // at the end of the period.
209         final UniformRandomProvider rng1 = RandomAssert.seededRNG();
210         final UniformRandomProvider rng2 = RandomAssert.seededRNG();
211         final double low = 1.23;
212         final double high = 4.56;
213         final SharedStateContinuousSampler sampler1 =
214             ContinuousUniformSampler.of(rng1, low, high, false);
215         final SharedStateContinuousSampler sampler2 =
216             ContinuousUniformSampler.of(rng2, low, high, true);
217         RandomAssert.assertProduceSameSequence(sampler1, sampler2);
218     }
219 }