LogUniformDistribution.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.statistics.distribution;

  18. import org.apache.commons.rng.UniformRandomProvider;
  19. import org.apache.commons.rng.sampling.distribution.ContinuousUniformSampler;
  20. import org.apache.commons.rng.sampling.distribution.SharedStateContinuousSampler;

  21. /**
  22.  * Implementation of the log-uniform distribution. This is also known as the reciprocal distribution.
  23.  *
  24.  * <p>The probability density function of \( X \) is:
  25.  *
  26.  * <p>\[ f(x; a, b) = \frac{1}{x \ln \frac b a} \]
  27.  *
  28.  * <p>for \( 0 \lt a \lt b \lt \infty \) and
  29.  * \( x \in [a, b] \).
  30.  *
  31.  * @see <a href="https://en.wikipedia.org/wiki/Reciprocal_distribution">Reciprocal distribution (Wikipedia)</a>
  32.  * @since 1.1
  33.  */
  34. public final class LogUniformDistribution extends AbstractContinuousDistribution {
  35.     /** Lower bound (a) of this distribution (inclusive). */
  36.     private final double lower;
  37.     /** Upper bound (b) of this distribution (exclusive). */
  38.     private final double upper;
  39.     /** log(a). */
  40.     private final double logA;
  41.     /** log(b). */
  42.     private final double logB;
  43.     /** log(b) - log(a). */
  44.     private final double logBmLogA;
  45.     /** log(log(b) - log(a)). */
  46.     private final double logLogBmLogA;

  47.     /**
  48.      * @param lower Lower bound of this distribution (inclusive).
  49.      * @param upper Upper bound of this distribution (inclusive).
  50.      */
  51.     private LogUniformDistribution(double lower,
  52.                                    double upper) {
  53.         this.lower = lower;
  54.         this.upper = upper;
  55.         logA = Math.log(lower);
  56.         logB = Math.log(upper);
  57.         logBmLogA = logB - logA;
  58.         logLogBmLogA = Math.log(logBmLogA);
  59.     }

  60.     /**
  61.      * Creates a log-uniform distribution.
  62.      *
  63.      * @param lower Lower bound of this distribution (inclusive).
  64.      * @param upper Upper bound of this distribution (inclusive).
  65.      * @return the distribution
  66.      * @throws IllegalArgumentException if {@code lower >= upper}; the range between the bounds
  67.      * is not finite; or {@code lower <= 0}
  68.      */
  69.     public static LogUniformDistribution of(double lower,
  70.                                             double upper) {
  71.         if (lower >= upper) {
  72.             throw new DistributionException(DistributionException.INVALID_RANGE_LOW_GTE_HIGH,
  73.                                             lower, upper);
  74.         }
  75.         if (!Double.isFinite(upper - lower)) {
  76.             throw new DistributionException("Range %s is not finite", upper - lower);
  77.         }
  78.         if (lower <= 0) {
  79.             throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, lower);
  80.         }
  81.         return new LogUniformDistribution(lower, upper);
  82.     }

  83.     /** {@inheritDoc} */
  84.     @Override
  85.     public double density(double x) {
  86.         if (x < lower || x > upper) {
  87.             return 0;
  88.         }
  89.         return Math.exp(logDensity(x));
  90.     }

  91.     /** {@inheritDoc} */
  92.     @Override
  93.     public double logDensity(double x) {
  94.         if (x < lower || x > upper) {
  95.             return Double.NEGATIVE_INFINITY;
  96.         }
  97.         return -Math.log(x) - logLogBmLogA;
  98.     }

  99.     /** {@inheritDoc} */
  100.     @Override
  101.     public double cumulativeProbability(double x)  {
  102.         if (x <= lower) {
  103.             return 0;
  104.         }
  105.         if (x >= upper) {
  106.             return 1;
  107.         }
  108.         return (Math.log(x) - logA) / logBmLogA;
  109.     }

  110.     /** {@inheritDoc} */
  111.     @Override
  112.     public double survivalProbability(double x) {
  113.         if (x <= lower) {
  114.             return 1;
  115.         }
  116.         if (x >= upper) {
  117.             return 0;
  118.         }
  119.         return (logB - Math.log(x)) / logBmLogA;
  120.     }

  121.     /** {@inheritDoc} */
  122.     @Override
  123.     public double inverseCumulativeProbability(double p) {
  124.         ArgumentUtils.checkProbability(p);
  125.         // Avoid floating-point error at the bounds
  126.         return clipToRange(Math.exp(logA + p * logBmLogA));
  127.     }

  128.     @Override
  129.     public double inverseSurvivalProbability(double p) {
  130.         ArgumentUtils.checkProbability(p);
  131.         // Avoid floating-point error at the bounds
  132.         return clipToRange(Math.exp(logB - p * logBmLogA));
  133.     }

  134.     /**
  135.      * {@inheritDoc}
  136.      *
  137.      * <p>For lower bound \( a \) and upper bound \( b \), the mean is:
  138.      *
  139.      * <p>\[ \frac{b - a}{\ln \frac b a} \]
  140.      */
  141.     @Override
  142.     public double getMean() {
  143.         return (upper - lower) / logBmLogA;
  144.     }

  145.     /**
  146.      * {@inheritDoc}
  147.      *
  148.      * <p>For lower bound \( a \) and upper bound \( b \), the variance is:
  149.      *
  150.      * <p>\[ \frac{b^2 - a^2}{2 \ln \frac b a} - \left( \frac{b - a}{\ln \frac b a} \right)^2 \]
  151.      */
  152.     @Override
  153.     public double getVariance() {
  154.         // Compute u_2 via a stabilising rearrangement:
  155.         // https://docs.scipy.org/doc/scipy/tutorial/stats/continuous_loguniform.html
  156.         final double a = lower;
  157.         final double b = upper;
  158.         final double d = -logBmLogA;
  159.         return (a - b) * (a * (d - 2) + b * (d + 2)) / (2 * d * d);
  160.     }

  161.     /**
  162.      * {@inheritDoc}
  163.      *
  164.      * <p>The lower bound of the support is equal to the lower bound parameter
  165.      * of the distribution.
  166.      */
  167.     @Override
  168.     public double getSupportLowerBound() {
  169.         return lower;
  170.     }

  171.     /**
  172.      * {@inheritDoc}
  173.      *
  174.      * <p>The upper bound of the support is equal to the upper bound parameter
  175.      * of the distribution.
  176.      */
  177.     @Override
  178.     public double getSupportUpperBound() {
  179.         return upper;
  180.     }

  181.     /**
  182.      * Clip the value to the range [lower, upper].
  183.      * This is used to handle floating-point error at the support bound.
  184.      *
  185.      * @param x Value x
  186.      * @return x clipped to the range
  187.      */
  188.     private double clipToRange(double x) {
  189.         return clip(x, lower, upper);
  190.     }

  191.     /**
  192.      * Clip the value to the range [lower, upper].
  193.      *
  194.      * @param x Value x
  195.      * @param lower Lower bound (inclusive)
  196.      * @param upper Upper bound (inclusive)
  197.      * @return x clipped to the range
  198.      */
  199.     private static double clip(double x, double lower, double upper) {
  200.         if (x <= lower) {
  201.             return lower;
  202.         }
  203.         return x < upper ? x : upper;
  204.     }

  205.     /** {@inheritDoc} */
  206.     @Override
  207.     double getMedian() {
  208.         // Overridden for the probability(double, double) method.
  209.         // This is intentionally not a public method.
  210.         // sqrt(ab) avoiding overflow
  211.         return Math.exp(0.5 * (logA + logB));
  212.     }

  213.     /** {@inheritDoc} */
  214.     @Override
  215.     public ContinuousDistribution.Sampler createSampler(final UniformRandomProvider rng) {
  216.         // Exponentiate a uniform distribution sampler of the logarithmic range.
  217.         final SharedStateContinuousSampler s = ContinuousUniformSampler.of(rng, logA, logB);
  218.         return () -> Math.exp(s.sample());
  219.     }
  220. }