AbstractRealDistribution.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.distribution.continuous;

  22. import java.io.Serializable;

  23. import org.hipparchus.analysis.UnivariateFunction;
  24. import org.hipparchus.analysis.solvers.UnivariateSolverUtils;
  25. import org.hipparchus.distribution.RealDistribution;
  26. import org.hipparchus.exception.LocalizedCoreFormats;
  27. import org.hipparchus.exception.MathIllegalArgumentException;
  28. import org.hipparchus.util.FastMath;
  29. import org.hipparchus.util.MathUtils;

  30. /**
  31.  * Base class for probability distributions on the reals.
  32.  * <p>
  33.  * Default implementations are provided for some of the methods
  34.  * that do not vary from distribution to distribution.
  35.  */
  36. public abstract class AbstractRealDistribution
  37.     implements RealDistribution, Serializable {

  38.     /** Default absolute accuracy for inverse cumulative computation. */
  39.     protected static final double DEFAULT_SOLVER_ABSOLUTE_ACCURACY = 1e-9;
  40.     /** Serializable version identifier */
  41.     private static final long serialVersionUID = 20160320L;

  42.     /** Inverse cumulative probability accuracy. */
  43.     private final double solverAbsoluteAccuracy;

  44.     /** Simple constructor.
  45.      * @param solverAbsoluteAccuracy the absolute accuracy to use when
  46.      * computing the inverse cumulative probability.
  47.      */
  48.     protected AbstractRealDistribution(double solverAbsoluteAccuracy) {
  49.         this.solverAbsoluteAccuracy = solverAbsoluteAccuracy;
  50.     }

  51.     /**
  52.      * Create a real distribution with default solver absolute accuracy.
  53.      */
  54.     protected AbstractRealDistribution() {
  55.         this.solverAbsoluteAccuracy = DEFAULT_SOLVER_ABSOLUTE_ACCURACY;
  56.     }

  57.     /**
  58.      * For a random variable {@code X} whose values are distributed according
  59.      * to this distribution, this method returns {@code P(x0 < X <= x1)}.
  60.      *
  61.      * @param x0 Lower bound (excluded).
  62.      * @param x1 Upper bound (included).
  63.      * @return the probability that a random variable with this distribution
  64.      * takes a value between {@code x0} and {@code x1}, excluding the lower
  65.      * and including the upper endpoint.
  66.      * @throws MathIllegalArgumentException if {@code x0 > x1}.
  67.      * The default implementation uses the identity
  68.      * {@code P(x0 < X <= x1) = P(X <= x1) - P(X <= x0)}
  69.      */
  70.     @Override
  71.     public double probability(double x0,
  72.                               double x1) throws MathIllegalArgumentException {
  73.         if (x0 > x1) {
  74.             throw new MathIllegalArgumentException(LocalizedCoreFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT,
  75.                                                    x0, x1, true);
  76.         }
  77.         return cumulativeProbability(x1) - cumulativeProbability(x0);
  78.     }

  79.     /**
  80.      * {@inheritDoc}
  81.      *
  82.      * The default implementation returns
  83.      * <ul>
  84.      * <li>{@link #getSupportLowerBound()} for {@code p = 0},</li>
  85.      * <li>{@link #getSupportUpperBound()} for {@code p = 1}.</li>
  86.      * </ul>
  87.      */
  88.     @Override
  89.     public double inverseCumulativeProbability(final double p) throws MathIllegalArgumentException {
  90.         /*
  91.          * IMPLEMENTATION NOTES
  92.          * --------------------
  93.          * Where applicable, use is made of the one-sided Chebyshev inequality
  94.          * to bracket the root. This inequality states that
  95.          * P(X - mu >= k * sig) <= 1 / (1 + k^2),
  96.          * mu: mean, sig: standard deviation. Equivalently
  97.          * 1 - P(X < mu + k * sig) <= 1 / (1 + k^2),
  98.          * F(mu + k * sig) >= k^2 / (1 + k^2).
  99.          *
  100.          * For k = sqrt(p / (1 - p)), we find
  101.          * F(mu + k * sig) >= p,
  102.          * and (mu + k * sig) is an upper-bound for the root.
  103.          *
  104.          * Then, introducing Y = -X, mean(Y) = -mu, sd(Y) = sig, and
  105.          * P(Y >= -mu + k * sig) <= 1 / (1 + k^2),
  106.          * P(-X >= -mu + k * sig) <= 1 / (1 + k^2),
  107.          * P(X <= mu - k * sig) <= 1 / (1 + k^2),
  108.          * F(mu - k * sig) <= 1 / (1 + k^2).
  109.          *
  110.          * For k = sqrt((1 - p) / p), we find
  111.          * F(mu - k * sig) <= p,
  112.          * and (mu - k * sig) is a lower-bound for the root.
  113.          *
  114.          * In cases where the Chebyshev inequality does not apply, geometric
  115.          * progressions 1, 2, 4, ... and -1, -2, -4, ... are used to bracket
  116.          * the root.
  117.          */

  118.         MathUtils.checkRangeInclusive(p, 0, 1);

  119.         double lowerBound = getSupportLowerBound();
  120.         if (p == 0.0) {
  121.             return lowerBound;
  122.         }

  123.         double upperBound = getSupportUpperBound();
  124.         if (p == 1.0) {
  125.             return upperBound;
  126.         }

  127.         final double mu = getNumericalMean();
  128.         final double sig = FastMath.sqrt(getNumericalVariance());
  129.         final boolean chebyshevApplies;
  130.         chebyshevApplies = !(Double.isInfinite(mu) || Double.isNaN(mu) ||
  131.                              Double.isInfinite(sig) || Double.isNaN(sig));

  132.         if (lowerBound == Double.NEGATIVE_INFINITY) {
  133.             if (chebyshevApplies) {
  134.                 lowerBound = mu - sig * FastMath.sqrt((1. - p) / p);
  135.             } else {
  136.                 lowerBound = -1.0;
  137.                 while (cumulativeProbability(lowerBound) >= p) {
  138.                     lowerBound *= 2.0;
  139.                 }
  140.             }
  141.         }

  142.         if (upperBound == Double.POSITIVE_INFINITY) {
  143.             if (chebyshevApplies) {
  144.                 upperBound = mu + sig * FastMath.sqrt(p / (1. - p));
  145.             } else {
  146.                 upperBound = 1.0;
  147.                 while (cumulativeProbability(upperBound) < p) {
  148.                     upperBound *= 2.0;
  149.                 }
  150.             }
  151.         }

  152.         final UnivariateFunction toSolve = new UnivariateFunction() {
  153.             /** {@inheritDoc} */
  154.             @Override
  155.             public double value(final double x) {
  156.                 return cumulativeProbability(x) - p;
  157.             }
  158.         };

  159.         double x = UnivariateSolverUtils.solve(toSolve,
  160.                                                lowerBound,
  161.                                                upperBound,
  162.                                                getSolverAbsoluteAccuracy());

  163.         if (!isSupportConnected()) {
  164.             /* Test for plateau. */
  165.             final double dx = getSolverAbsoluteAccuracy();
  166.             if (x - dx >= getSupportLowerBound()) {
  167.                 double px = cumulativeProbability(x);
  168.                 if (cumulativeProbability(x - dx) == px) {
  169.                     upperBound = x;
  170.                     while (upperBound - lowerBound > dx) {
  171.                         final double midPoint = 0.5 * (lowerBound + upperBound);
  172.                         if (cumulativeProbability(midPoint) < px) {
  173.                             lowerBound = midPoint;
  174.                         } else {
  175.                             upperBound = midPoint;
  176.                         }
  177.                     }
  178.                     return upperBound;
  179.                 }
  180.             }
  181.         }
  182.         return x;
  183.     }

  184.     /**
  185.      * Returns the solver absolute accuracy for inverse cumulative computation.
  186.      * You can override this method in order to use a Brent solver with an
  187.      * absolute accuracy different from the default.
  188.      *
  189.      * @return the maximum absolute error in inverse cumulative probability estimates
  190.      */
  191.     protected double getSolverAbsoluteAccuracy() {
  192.         return solverAbsoluteAccuracy;
  193.     }

  194.     /**
  195.      * {@inheritDoc}
  196.      * <p>
  197.      * The default implementation simply computes the logarithm of {@code density(x)}.
  198.      */
  199.     @Override
  200.     public double logDensity(double x) {
  201.         return FastMath.log(density(x));
  202.     }
  203. }