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 java.util.function.DoubleUnaryOperator;
20  import org.apache.commons.rng.UniformRandomProvider;
21  
22  /**
23   * Sampling from a T distribution.
24   *
25   * <p>Uses Bailey's algorithm for t-distribution sampling:</p>
26   *
27   * <blockquote>
28   * <pre>
29   * Bailey, R. W. (1994)
30   * "Polar Generation of Random Variates with the t-Distribution."
31   * Mathematics of Computation 62, 779-781.
32   * </pre>
33   * </blockquote>
34   *
35   * <p>Sampling uses {@link UniformRandomProvider#nextLong()}.</p>
36   *
37   * @see <a href="https://en.wikipedia.org/wiki/Student%27s_t-distribution">Student's T distribution (wikipedia)</a>
38   * @see <a href="https://doi.org/10.2307/2153537">Mathematics of Computation, 62, 779-781</a>
39   * @since 1.5
40   */
41  public abstract class TSampler implements SharedStateContinuousSampler {
42      /** Threshold for huge degrees of freedom. Above this value the CDF of the t distribution
43       * matches the normal distribution. Value is 2/eps (where eps is the machine epsilon)
44       * or approximately 9.0e15.  */
45      private static final double HUGE_DF = 0x1.0p53;
46  
47      /** Source of randomness. */
48      private final UniformRandomProvider rng;
49  
50      /**
51       * Sample from a t-distribution using Bailey's algorithm.
52       */
53      private static final class StudentsTSampler extends TSampler {
54          /** Threshold for large degrees of freedom. */
55          private static final double LARGE_DF = 25;
56          /** The multiplier to convert the least significant 53-bits of a {@code long} to a
57           * uniform {@code double}. */
58          private static final double DOUBLE_MULTIPLIER = 0x1.0p-53;
59  
60          /** Degrees of freedom. */
61          private final double df;
62          /** Function to compute pow(x, -2/v) - 1, where v = degrees of freedom. */
63          private final DoubleUnaryOperator powm1;
64  
65          /**
66           * @param rng Generator of uniformly distributed random numbers.
67           * @param v Degrees of freedom.
68           */
69          StudentsTSampler(UniformRandomProvider rng,
70                           double v) {
71              super(rng);
72              df = v;
73  
74              // The sampler requires pow(w, -2/v) - 1 with
75              // 0 <= w <= 1; Expected(w) = sqrt(0.5).
76              // When the exponent is small then pow(x, y) -> 1.
77              // This affects large degrees of freedom.
78              final double exponent = -2 / v;
79              powm1 = v > LARGE_DF ?
80                  x -> Math.expm1(Math.log(x) * exponent) :
81                  x -> Math.pow(x, exponent) - 1;
82          }
83  
84          /**
85           * @param rng Generator of uniformly distributed random numbers.
86           * @param source Source to copy.
87           */
88          private StudentsTSampler(UniformRandomProvider rng,
89                                   StudentsTSampler source) {
90              super(rng);
91              df = source.df;
92              powm1 = source.powm1;
93          }
94  
95          /** {@inheritDoc} */
96          @Override
97          public double sample() {
98              // Require u and v in [0, 1] and a random sign.
99              // Create u in (0, 1] to avoid generating nan
100             // from u*u/w (0/0) or r2*c2 (inf*0).
101             final double u = InternalUtils.makeNonZeroDouble(nextLong());
102             final double v = makeSignedDouble(nextLong());
103             final double w = u * u + v * v;
104             if (w > 1) {
105                 // Rejection frequency = 1 - pi/4 = 0.215.
106                 // Recursion will generate stack overflow given a broken RNG
107                 // and avoids an infinite loop.
108                 return sample();
109             }
110             // Sidestep a square-root calculation.
111             final double c2 = u * u / w;
112             final double r2 = df * powm1.applyAsDouble(w);
113             // Choose sign at random from the sign of v.
114             return Math.copySign(Math.sqrt(r2 * c2), v);
115         }
116 
117         /** {@inheritDoc} */
118         @Override
119         public StudentsTSampler withUniformRandomProvider(UniformRandomProvider rng) {
120             return new StudentsTSampler(rng, this);
121         }
122 
123         /**
124          * Creates a signed double in the range {@code [-1, 1)}. The magnitude is sampled evenly
125          * from the 2<sup>54</sup> dyadic rationals in the range.
126          *
127          * <p>Note: This method will not return samples for both -0.0 and 0.0.
128          *
129          * @param bits the bits
130          * @return the double
131          */
132         private static double makeSignedDouble(long bits) {
133             // As per o.a.c.rng.core.utils.NumberFactory.makeDouble(long) but using a signed
134             // shift of 10 in place of an unsigned shift of 11.
135             // Use the upper 54 bits on the assumption they are more random.
136             // The sign bit is maintained by the signed shift.
137             // The next 53 bits generates a magnitude in the range [0, 2^53) or [-2^53, 0).
138             return (bits >> 10) * DOUBLE_MULTIPLIER;
139         }
140     }
141 
142     /**
143      * Sample from a t-distribution using a normal distribution.
144      * This is used when the degrees of freedom is extremely large (e.g. {@code > 1e16}).
145      */
146     private static final class NormalTSampler extends TSampler {
147         /** Underlying normalized Gaussian sampler. */
148         private final NormalizedGaussianSampler sampler;
149 
150         /**
151          * @param rng Generator of uniformly distributed random numbers.
152          */
153         NormalTSampler(UniformRandomProvider rng) {
154             super(rng);
155             this.sampler = ZigguratSampler.NormalizedGaussian.of(rng);
156         }
157 
158         /** {@inheritDoc} */
159         @Override
160         public double sample() {
161             return sampler.sample();
162 
163         }
164 
165         /** {@inheritDoc} */
166         @Override
167         public NormalTSampler withUniformRandomProvider(UniformRandomProvider rng) {
168             return new NormalTSampler(rng);
169         }
170     }
171 
172     /**
173      * @param rng Generator of uniformly distributed random numbers.
174      */
175     TSampler(UniformRandomProvider rng) {
176         this.rng = rng;
177     }
178 
179     /** {@inheritDoc} */
180     // Redeclare the signature to return a TSampler not a SharedStateContinuousSampler
181     @Override
182     public abstract TSampler withUniformRandomProvider(UniformRandomProvider rng);
183 
184     /**
185      * Generates a {@code long} value.
186      * Used by algorithm implementations without exposing access to the RNG.
187      *
188      * @return the next random value
189      */
190     long nextLong() {
191         return rng.nextLong();
192     }
193 
194     /** {@inheritDoc} */
195     @Override
196     public String toString() {
197         return "Student's t deviate [" + rng.toString() + "]";
198     }
199 
200     /**
201      * Create a new t distribution sampler.
202      *
203      * @param rng Generator of uniformly distributed random numbers.
204      * @param degreesOfFreedom Degrees of freedom.
205      * @return the sampler
206      * @throws IllegalArgumentException if {@code degreesOfFreedom <= 0}
207      */
208     public static TSampler of(UniformRandomProvider rng,
209                               double degreesOfFreedom) {
210         if (degreesOfFreedom > HUGE_DF) {
211             return new NormalTSampler(rng);
212         } else if (degreesOfFreedom > 0) {
213             return new StudentsTSampler(rng, degreesOfFreedom);
214         } else {
215             // df <= 0 or nan
216             throw new IllegalArgumentException(
217                 "degrees of freedom is not strictly positive: " + degreesOfFreedom);
218         }
219     }
220 }