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}