TSampler.java

  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. import java.util.function.DoubleUnaryOperator;
  19. import org.apache.commons.rng.UniformRandomProvider;

  20. /**
  21.  * Sampling from a T distribution.
  22.  *
  23.  * <p>Uses Bailey's algorithm for t-distribution sampling:</p>
  24.  *
  25.  * <blockquote>
  26.  * <pre>
  27.  * Bailey, R. W. (1994)
  28.  * "Polar Generation of Random Variates with the t-Distribution."
  29.  * Mathematics of Computation 62, 779-781.
  30.  * </pre>
  31.  * </blockquote>
  32.  *
  33.  * <p>Sampling uses {@link UniformRandomProvider#nextLong()}.</p>
  34.  *
  35.  * @see <a href="https://en.wikipedia.org/wiki/Student%27s_t-distribution">Student&#39;s T distribution (wikipedia)</a>
  36.  * @see <a href="https://doi.org/10.2307/2153537">Mathematics of Computation, 62, 779-781</a>
  37.  * @since 1.5
  38.  */
  39. public abstract class TSampler implements SharedStateContinuousSampler {
  40.     /** Threshold for huge degrees of freedom. Above this value the CDF of the t distribution
  41.      * matches the normal distribution. Value is 2/eps (where eps is the machine epsilon)
  42.      * or approximately 9.0e15.  */
  43.     private static final double HUGE_DF = 0x1.0p53;

  44.     /** Source of randomness. */
  45.     private final UniformRandomProvider rng;

  46.     /**
  47.      * Sample from a t-distribution using Bailey's algorithm.
  48.      */
  49.     private static final class StudentsTSampler extends TSampler {
  50.         /** Threshold for large degrees of freedom. */
  51.         private static final double LARGE_DF = 25;
  52.         /** The multiplier to convert the least significant 53-bits of a {@code long} to a
  53.          * uniform {@code double}. */
  54.         private static final double DOUBLE_MULTIPLIER = 0x1.0p-53;

  55.         /** Degrees of freedom. */
  56.         private final double df;
  57.         /** Function to compute pow(x, -2/v) - 1, where v = degrees of freedom. */
  58.         private final DoubleUnaryOperator powm1;

  59.         /**
  60.          * @param rng Generator of uniformly distributed random numbers.
  61.          * @param v Degrees of freedom.
  62.          */
  63.         StudentsTSampler(UniformRandomProvider rng,
  64.                          double v) {
  65.             super(rng);
  66.             df = v;

  67.             // The sampler requires pow(w, -2/v) - 1 with
  68.             // 0 <= w <= 1; Expected(w) = sqrt(0.5).
  69.             // When the exponent is small then pow(x, y) -> 1.
  70.             // This affects large degrees of freedom.
  71.             final double exponent = -2 / v;
  72.             powm1 = v > LARGE_DF ?
  73.                 x -> Math.expm1(Math.log(x) * exponent) :
  74.                 x -> Math.pow(x, exponent) - 1;
  75.         }

  76.         /**
  77.          * @param rng Generator of uniformly distributed random numbers.
  78.          * @param source Source to copy.
  79.          */
  80.         private StudentsTSampler(UniformRandomProvider rng,
  81.                                  StudentsTSampler source) {
  82.             super(rng);
  83.             df = source.df;
  84.             powm1 = source.powm1;
  85.         }

  86.         /** {@inheritDoc} */
  87.         @Override
  88.         public double sample() {
  89.             // Require u and v in [0, 1] and a random sign.
  90.             // Create u in (0, 1] to avoid generating nan
  91.             // from u*u/w (0/0) or r2*c2 (inf*0).
  92.             final double u = InternalUtils.makeNonZeroDouble(nextLong());
  93.             final double v = makeSignedDouble(nextLong());
  94.             final double w = u * u + v * v;
  95.             if (w > 1) {
  96.                 // Rejection frequency = 1 - pi/4 = 0.215.
  97.                 // Recursion will generate stack overflow given a broken RNG
  98.                 // and avoids an infinite loop.
  99.                 return sample();
  100.             }
  101.             // Sidestep a square-root calculation.
  102.             final double c2 = u * u / w;
  103.             final double r2 = df * powm1.applyAsDouble(w);
  104.             // Choose sign at random from the sign of v.
  105.             return Math.copySign(Math.sqrt(r2 * c2), v);
  106.         }

  107.         /** {@inheritDoc} */
  108.         @Override
  109.         public StudentsTSampler withUniformRandomProvider(UniformRandomProvider rng) {
  110.             return new StudentsTSampler(rng, this);
  111.         }

  112.         /**
  113.          * Creates a signed double in the range {@code [-1, 1)}. The magnitude is sampled evenly
  114.          * from the 2<sup>54</sup> dyadic rationals in the range.
  115.          *
  116.          * <p>Note: This method will not return samples for both -0.0 and 0.0.
  117.          *
  118.          * @param bits the bits
  119.          * @return the double
  120.          */
  121.         private static double makeSignedDouble(long bits) {
  122.             // As per o.a.c.rng.core.utils.NumberFactory.makeDouble(long) but using a signed
  123.             // shift of 10 in place of an unsigned shift of 11.
  124.             // Use the upper 54 bits on the assumption they are more random.
  125.             // The sign bit is maintained by the signed shift.
  126.             // The next 53 bits generates a magnitude in the range [0, 2^53) or [-2^53, 0).
  127.             return (bits >> 10) * DOUBLE_MULTIPLIER;
  128.         }
  129.     }

  130.     /**
  131.      * Sample from a t-distribution using a normal distribution.
  132.      * This is used when the degrees of freedom is extremely large (e.g. {@code > 1e16}).
  133.      */
  134.     private static final class NormalTSampler extends TSampler {
  135.         /** Underlying normalized Gaussian sampler. */
  136.         private final NormalizedGaussianSampler sampler;

  137.         /**
  138.          * @param rng Generator of uniformly distributed random numbers.
  139.          */
  140.         NormalTSampler(UniformRandomProvider rng) {
  141.             super(rng);
  142.             this.sampler = ZigguratSampler.NormalizedGaussian.of(rng);
  143.         }

  144.         /** {@inheritDoc} */
  145.         @Override
  146.         public double sample() {
  147.             return sampler.sample();

  148.         }

  149.         /** {@inheritDoc} */
  150.         @Override
  151.         public NormalTSampler withUniformRandomProvider(UniformRandomProvider rng) {
  152.             return new NormalTSampler(rng);
  153.         }
  154.     }

  155.     /**
  156.      * @param rng Generator of uniformly distributed random numbers.
  157.      */
  158.     TSampler(UniformRandomProvider rng) {
  159.         this.rng = rng;
  160.     }

  161.     /** {@inheritDoc} */
  162.     // Redeclare the signature to return a TSampler not a SharedStateContinuousSampler
  163.     @Override
  164.     public abstract TSampler withUniformRandomProvider(UniformRandomProvider rng);

  165.     /**
  166.      * Generates a {@code long} value.
  167.      * Used by algorithm implementations without exposing access to the RNG.
  168.      *
  169.      * @return the next random value
  170.      */
  171.     long nextLong() {
  172.         return rng.nextLong();
  173.     }

  174.     /** {@inheritDoc} */
  175.     @Override
  176.     public String toString() {
  177.         return "Student's t deviate [" + rng.toString() + "]";
  178.     }

  179.     /**
  180.      * Create a new t distribution sampler.
  181.      *
  182.      * @param rng Generator of uniformly distributed random numbers.
  183.      * @param degreesOfFreedom Degrees of freedom.
  184.      * @return the sampler
  185.      * @throws IllegalArgumentException if {@code degreesOfFreedom <= 0}
  186.      */
  187.     public static TSampler of(UniformRandomProvider rng,
  188.                               double degreesOfFreedom) {
  189.         if (degreesOfFreedom > HUGE_DF) {
  190.             return new NormalTSampler(rng);
  191.         } else if (degreesOfFreedom > 0) {
  192.             return new StudentsTSampler(rng, degreesOfFreedom);
  193.         } else {
  194.             // df <= 0 or nan
  195.             throw new IllegalArgumentException(
  196.                 "degrees of freedom is not strictly positive: " + degreesOfFreedom);
  197.         }
  198.     }
  199. }