AbstractIntegerDistribution.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.discrete;

  22. import java.io.Serializable;

  23. import org.hipparchus.distribution.IntegerDistribution;
  24. import org.hipparchus.exception.LocalizedCoreFormats;
  25. import org.hipparchus.exception.MathIllegalArgumentException;
  26. import org.hipparchus.exception.MathRuntimeException;
  27. import org.hipparchus.util.FastMath;
  28. import org.hipparchus.util.MathUtils;

  29. /**
  30.  * Base class for integer-valued discrete distributions.
  31.  * <p>
  32.  * Default implementations are provided for some of the methods that
  33.  * do not vary from distribution to distribution.
  34.  */
  35. public abstract class AbstractIntegerDistribution implements IntegerDistribution, Serializable {

  36.     /** Serializable version identifier */
  37.     private static final long serialVersionUID = 20160320L;

  38.     /** Empty constructor.
  39.      * <p>
  40.      * This constructor is not strictly necessary, but it prevents spurious
  41.      * javadoc warnings with JDK 18 and later.
  42.      * </p>
  43.      * @since 3.0
  44.      */
  45.     protected AbstractIntegerDistribution() { // NOPMD - unnecessary constructor added intentionally to make javadoc happy
  46.         // nothing to do
  47.     }

  48.     /**
  49.      * {@inheritDoc}
  50.      *
  51.      * The default implementation uses the identity
  52.      * <p>
  53.      * {@code P(x0 < X <= x1) = P(X <= x1) - P(X <= x0)}
  54.      */
  55.     @Override
  56.     public double probability(int x0, int x1) throws MathIllegalArgumentException {
  57.         if (x1 < x0) {
  58.             throw new MathIllegalArgumentException(LocalizedCoreFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT,
  59.                                                    x0, x1, true);
  60.         }
  61.         return cumulativeProbability(x1) - cumulativeProbability(x0);
  62.     }

  63.     /**
  64.      * {@inheritDoc}
  65.      *
  66.      * The default implementation returns
  67.      * <ul>
  68.      * <li>{@link #getSupportLowerBound()} for {@code p = 0},</li>
  69.      * <li>{@link #getSupportUpperBound()} for {@code p = 1}, and</li>
  70.      * <li>{@link #solveInverseCumulativeProbability(double, int, int)} for
  71.      *     {@code 0 < p < 1}.</li>
  72.      * </ul>
  73.      */
  74.     @Override
  75.     public int inverseCumulativeProbability(final double p) throws MathIllegalArgumentException {
  76.         MathUtils.checkRangeInclusive(p, 0, 1);

  77.         int lower = getSupportLowerBound();
  78.         if (p == 0.0) {
  79.             return lower;
  80.         }
  81.         if (lower == Integer.MIN_VALUE) {
  82.             if (checkedCumulativeProbability(lower) >= p) {
  83.                 return lower;
  84.             }
  85.         } else {
  86.             lower -= 1; // this ensures cumulativeProbability(lower) < p, which
  87.                         // is important for the solving step
  88.         }

  89.         int upper = getSupportUpperBound();
  90.         if (p == 1.0) {
  91.             return upper;
  92.         }

  93.         // use the one-sided Chebyshev inequality to narrow the bracket
  94.         // cf. AbstractRealDistribution.inverseCumulativeProbability(double)
  95.         final double mu = getNumericalMean();
  96.         final double sigma = FastMath.sqrt(getNumericalVariance());
  97.         final boolean chebyshevApplies =
  98.                 !(Double.isInfinite(mu)    || Double.isNaN(mu)    ||
  99.                   Double.isInfinite(sigma) || Double.isNaN(sigma) ||
  100.                   sigma == 0.0);
  101.         if (chebyshevApplies) {
  102.             double k = FastMath.sqrt((1.0 - p) / p);
  103.             double tmp = mu - k * sigma;
  104.             if (tmp > lower) {
  105.                 lower = ((int) FastMath.ceil(tmp)) - 1;
  106.             }
  107.             k = 1.0 / k;
  108.             tmp = mu + k * sigma;
  109.             if (tmp < upper) {
  110.                 upper = ((int) FastMath.ceil(tmp)) - 1;
  111.             }
  112.         }

  113.         return solveInverseCumulativeProbability(p, lower, upper);
  114.     }

  115.     /**
  116.      * This is a utility function used by {@link
  117.      * #inverseCumulativeProbability(double)}. It assumes {@code 0 < p < 1} and
  118.      * that the inverse cumulative probability lies in the bracket {@code
  119.      * (lower, upper]}. The implementation does simple bisection to find the
  120.      * smallest {@code p}-quantile {@code inf{x in Z | P(X<=x) >= p}}.
  121.      *
  122.      * @param p the cumulative probability
  123.      * @param lower a value satisfying {@code cumulativeProbability(lower) < p}
  124.      * @param upper a value satisfying {@code p <= cumulativeProbability(upper)}
  125.      * @return the smallest {@code p}-quantile of this distribution
  126.      */
  127.     protected int solveInverseCumulativeProbability(final double p, int lower, int upper) {
  128.         while (lower + 1 < upper) {
  129.             int xm = (lower + upper) / 2;
  130.             if (xm < lower || xm > upper) {
  131.                 /*
  132.                  * Overflow.
  133.                  * There will never be an overflow in both calculation methods
  134.                  * for xm at the same time
  135.                  */
  136.                 xm = lower + (upper - lower) / 2;
  137.             }

  138.             double pm = checkedCumulativeProbability(xm);
  139.             if (pm >= p) {
  140.                 upper = xm;
  141.             } else {
  142.                 lower = xm;
  143.             }
  144.         }
  145.         return upper;
  146.     }

  147.     /**
  148.      * Computes the cumulative probability function and checks for {@code NaN}
  149.      * values returned.
  150.      * <p>
  151.      * Throws {@code MathRuntimeException} if the value is {@code NaN}.
  152.      * Rethrows any exception encountered evaluating the cumulative
  153.      * probability function.
  154.      * Throws {@code MathRuntimeException} if the cumulative
  155.      * probability function returns {@code NaN}.
  156.      *
  157.      * @param argument input value
  158.      * @return the cumulative probability
  159.      * @throws MathRuntimeException if the cumulative probability is {@code NaN}
  160.      */
  161.     private double checkedCumulativeProbability(int argument)
  162.         throws MathRuntimeException {
  163.         double result = cumulativeProbability(argument);
  164.         if (Double.isNaN(result)) {
  165.             throw new MathRuntimeException(LocalizedCoreFormats.DISCRETE_CUMULATIVE_PROBABILITY_RETURNED_NAN,
  166.                                            argument);
  167.         }
  168.         return result;
  169.     }

  170.     /**
  171.      * {@inheritDoc}
  172.      * <p>
  173.      * The default implementation simply computes the logarithm of {@code probability(x)}.
  174.      */
  175.     @Override
  176.     public double logProbability(int x) {
  177.         return FastMath.log(probability(x));
  178.     }
  179. }