StableRandomGenerator.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.  *      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.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.random;

  22. import org.hipparchus.exception.LocalizedCoreFormats;
  23. import org.hipparchus.exception.MathIllegalArgumentException;
  24. import org.hipparchus.exception.NullArgumentException;
  25. import org.hipparchus.util.FastMath;
  26. import org.hipparchus.util.MathUtils;
  27. import org.hipparchus.util.SinCos;

  28. /**
  29.  * <p>This class provides a stable normalized random generator. It samples from a stable
  30.  * distribution with location parameter 0 and scale 1.</p>
  31.  *
  32.  * <p>The implementation uses the Chambers-Mallows-Stuck method as described in
  33.  * <i>Handbook of computational statistics: concepts and methods</i> by
  34.  * James E. Gentle, Wolfgang H&auml;rdle, Yuichi Mori.</p>
  35.  *
  36.  */
  37. public class StableRandomGenerator implements NormalizedRandomGenerator {
  38.     /** Underlying generator. */
  39.     private final RandomGenerator generator;

  40.     /** stability parameter */
  41.     private final double alpha;

  42.     /** skewness parameter */
  43.     private final double beta;

  44.     /** cache of expression value used in generation */
  45.     private final double zeta;

  46.     /**
  47.      * Create a new generator.
  48.      *
  49.      * @param generator underlying random generator to use
  50.      * @param alpha Stability parameter. Must be in range (0, 2]
  51.      * @param beta Skewness parameter. Must be in range [-1, 1]
  52.      * @throws NullArgumentException if generator is null
  53.      * @throws MathIllegalArgumentException if {@code alpha <= 0} or {@code alpha > 2}
  54.      * or {@code beta < -1} or {@code beta > 1}
  55.      */
  56.     public StableRandomGenerator(final RandomGenerator generator,
  57.                                  final double alpha, final double beta)
  58.         throws MathIllegalArgumentException, NullArgumentException {
  59.         if (generator == null) {
  60.             throw new NullArgumentException();
  61.         }

  62.         if (!(alpha > 0d && alpha <= 2d)) {
  63.             throw new MathIllegalArgumentException(LocalizedCoreFormats.OUT_OF_RANGE_LEFT,
  64.                     alpha, 0, 2);
  65.         }

  66.         MathUtils.checkRangeInclusive(beta, -1, 1);

  67.         this.generator = generator;
  68.         this.alpha = alpha;
  69.         this.beta = beta;
  70.         if (alpha < 2d && beta != 0d) {
  71.             zeta = beta * FastMath.tan(FastMath.PI * alpha / 2);
  72.         } else {
  73.             zeta = 0d;
  74.         }
  75.     }

  76.     /**
  77.      * Generate a random scalar with zero location and unit scale.
  78.      *
  79.      * @return a random scalar with zero location and unit scale
  80.      */
  81.     @Override
  82.     public double nextNormalizedDouble() {
  83.         // we need 2 uniform random numbers to calculate omega and phi
  84.         double omega = -FastMath.log(generator.nextDouble());
  85.         double phi = FastMath.PI * (generator.nextDouble() - 0.5);

  86.         // Normal distribution case (Box-Muller algorithm)
  87.         if (alpha == 2d) {
  88.             return FastMath.sqrt(2d * omega) * FastMath.sin(phi);
  89.         }

  90.         double x;
  91.         // when beta = 0, zeta is zero as well
  92.         // Thus we can exclude it from the formula
  93.         if (beta == 0d) {
  94.             // Cauchy distribution case
  95.             if (alpha == 1d) {
  96.                 x = FastMath.tan(phi);
  97.             } else {
  98.                 x = FastMath.pow(omega * FastMath.cos((1 - alpha) * phi),
  99.                     1d / alpha - 1d) *
  100.                     FastMath.sin(alpha * phi) /
  101.                     FastMath.pow(FastMath.cos(phi), 1d / alpha);
  102.             }
  103.         } else {
  104.             // Generic stable distribution
  105.             double cosPhi = FastMath.cos(phi);
  106.             // to avoid rounding errors around alpha = 1
  107.             if (FastMath.abs(alpha - 1d) > 1e-8) {
  108.                 final SinCos scAlphaPhi    = FastMath.sinCos(alpha * phi);
  109.                 final SinCos scInvAlphaPhi = FastMath.sinCos(phi * (1.0 - alpha));
  110.                 x = (scAlphaPhi.sin()    + zeta * scAlphaPhi.cos()) / cosPhi *
  111.                     (scInvAlphaPhi.cos() + zeta * scInvAlphaPhi.sin()) /
  112.                      FastMath.pow(omega * cosPhi, (1 - alpha) / alpha);
  113.             } else {
  114.                 double betaPhi = FastMath.PI / 2 + beta * phi;
  115.                 x = 2d / FastMath.PI * (betaPhi * FastMath.tan(phi) - beta *
  116.                     FastMath.log(FastMath.PI / 2d * omega * cosPhi / betaPhi));

  117.                 if (alpha != 1d) {
  118.                     x += beta * FastMath.tan(FastMath.PI * alpha / 2);
  119.                 }
  120.             }
  121.         }
  122.         return x;
  123.     }
  124. }