001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.statistics.distribution;
019
020import org.apache.commons.rng.UniformRandomProvider;
021import org.apache.commons.rng.sampling.distribution.ContinuousUniformSampler;
022import org.apache.commons.rng.sampling.distribution.SharedStateContinuousSampler;
023
024/**
025 * Implementation of the log-uniform distribution. This is also known as the reciprocal distribution.
026 *
027 * <p>The probability density function of \( X \) is:
028 *
029 * <p>\[ f(x; a, b) = \frac{1}{x \ln \frac b a} \]
030 *
031 * <p>for \( 0 \lt a \lt b \lt \infty \) and
032 * \( x \in [a, b] \).
033 *
034 * @see <a href="https://en.wikipedia.org/wiki/Reciprocal_distribution">Reciprocal distribution (Wikipedia)</a>
035 * @since 1.1
036 */
037public final class LogUniformDistribution extends AbstractContinuousDistribution {
038    /** Lower bound (a) of this distribution (inclusive). */
039    private final double lower;
040    /** Upper bound (b) of this distribution (exclusive). */
041    private final double upper;
042    /** log(a). */
043    private final double logA;
044    /** log(b). */
045    private final double logB;
046    /** log(b) - log(a). */
047    private final double logBmLogA;
048    /** log(log(b) - log(a)). */
049    private final double logLogBmLogA;
050
051    /**
052     * @param lower Lower bound of this distribution (inclusive).
053     * @param upper Upper bound of this distribution (inclusive).
054     */
055    private LogUniformDistribution(double lower,
056                                   double upper) {
057        this.lower = lower;
058        this.upper = upper;
059        logA = Math.log(lower);
060        logB = Math.log(upper);
061        logBmLogA = logB - logA;
062        logLogBmLogA = Math.log(logBmLogA);
063    }
064
065    /**
066     * Creates a log-uniform distribution.
067     *
068     * @param lower Lower bound of this distribution (inclusive).
069     * @param upper Upper bound of this distribution (inclusive).
070     * @return the distribution
071     * @throws IllegalArgumentException if {@code lower >= upper}; the range between the bounds
072     * is not finite; or {@code lower <= 0}
073     */
074    public static LogUniformDistribution of(double lower,
075                                            double upper) {
076        if (lower >= upper) {
077            throw new DistributionException(DistributionException.INVALID_RANGE_LOW_GTE_HIGH,
078                                            lower, upper);
079        }
080        if (!Double.isFinite(upper - lower)) {
081            throw new DistributionException("Range %s is not finite", upper - lower);
082        }
083        if (lower <= 0) {
084            throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, lower);
085        }
086        return new LogUniformDistribution(lower, upper);
087    }
088
089    /** {@inheritDoc} */
090    @Override
091    public double density(double x) {
092        if (x < lower || x > upper) {
093            return 0;
094        }
095        return Math.exp(logDensity(x));
096    }
097
098    /** {@inheritDoc} */
099    @Override
100    public double logDensity(double x) {
101        if (x < lower || x > upper) {
102            return Double.NEGATIVE_INFINITY;
103        }
104        return -Math.log(x) - logLogBmLogA;
105    }
106
107    /** {@inheritDoc} */
108    @Override
109    public double cumulativeProbability(double x)  {
110        if (x <= lower) {
111            return 0;
112        }
113        if (x >= upper) {
114            return 1;
115        }
116        return (Math.log(x) - logA) / logBmLogA;
117    }
118
119    /** {@inheritDoc} */
120    @Override
121    public double survivalProbability(double x) {
122        if (x <= lower) {
123            return 1;
124        }
125        if (x >= upper) {
126            return 0;
127        }
128        return (logB - Math.log(x)) / logBmLogA;
129    }
130
131    /** {@inheritDoc} */
132    @Override
133    public double inverseCumulativeProbability(double p) {
134        ArgumentUtils.checkProbability(p);
135        // Avoid floating-point error at the bounds
136        return clipToRange(Math.exp(logA + p * logBmLogA));
137    }
138
139    @Override
140    public double inverseSurvivalProbability(double p) {
141        ArgumentUtils.checkProbability(p);
142        // Avoid floating-point error at the bounds
143        return clipToRange(Math.exp(logB - p * logBmLogA));
144    }
145
146    /**
147     * {@inheritDoc}
148     *
149     * <p>For lower bound \( a \) and upper bound \( b \), the mean is:
150     *
151     * <p>\[ \frac{b - a}{\ln \frac b a} \]
152     */
153    @Override
154    public double getMean() {
155        return (upper - lower) / logBmLogA;
156    }
157
158    /**
159     * {@inheritDoc}
160     *
161     * <p>For lower bound \( a \) and upper bound \( b \), the variance is:
162     *
163     * <p>\[ \frac{b^2 - a^2}{2 \ln \frac b a} - \left( \frac{b - a}{\ln \frac b a} \right)^2 \]
164     */
165    @Override
166    public double getVariance() {
167        // Compute u_2 via a stabilising rearrangement:
168        // https://docs.scipy.org/doc/scipy/tutorial/stats/continuous_loguniform.html
169        final double a = lower;
170        final double b = upper;
171        final double d = -logBmLogA;
172        return (a - b) * (a * (d - 2) + b * (d + 2)) / (2 * d * d);
173    }
174
175    /**
176     * {@inheritDoc}
177     *
178     * <p>The lower bound of the support is equal to the lower bound parameter
179     * of the distribution.
180     */
181    @Override
182    public double getSupportLowerBound() {
183        return lower;
184    }
185
186    /**
187     * {@inheritDoc}
188     *
189     * <p>The upper bound of the support is equal to the upper bound parameter
190     * of the distribution.
191     */
192    @Override
193    public double getSupportUpperBound() {
194        return upper;
195    }
196
197    /**
198     * Clip the value to the range [lower, upper].
199     * This is used to handle floating-point error at the support bound.
200     *
201     * @param x Value x
202     * @return x clipped to the range
203     */
204    private double clipToRange(double x) {
205        return clip(x, lower, upper);
206    }
207
208    /**
209     * Clip the value to the range [lower, upper].
210     *
211     * @param x Value x
212     * @param lower Lower bound (inclusive)
213     * @param upper Upper bound (inclusive)
214     * @return x clipped to the range
215     */
216    private static double clip(double x, double lower, double upper) {
217        if (x <= lower) {
218            return lower;
219        }
220        return x < upper ? x : upper;
221    }
222
223    /** {@inheritDoc} */
224    @Override
225    double getMedian() {
226        // Overridden for the probability(double, double) method.
227        // This is intentionally not a public method.
228        // sqrt(ab) avoiding overflow
229        return Math.exp(0.5 * (logA + logB));
230    }
231
232    /** {@inheritDoc} */
233    @Override
234    public ContinuousDistribution.Sampler createSampler(final UniformRandomProvider rng) {
235        // Exponentiate a uniform distribution sampler of the logarithmic range.
236        final SharedStateContinuousSampler s = ContinuousUniformSampler.of(rng, logA, logB);
237        return () -> Math.exp(s.sample());
238    }
239}