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 */ 017package org.apache.commons.statistics.distribution; 018 019/** 020 * Implementation of the logistic distribution. 021 * 022 * <p>The probability density function of \( X \) is: 023 * 024 * <p>\[ f(x; \mu, s) = \frac{e^{-(x-\mu)/s}} {s\left(1+e^{-(x-\mu)/s}\right)^2} \] 025 * 026 * <p>for \( \mu \) the location, 027 * \( s > 0 \) the scale, and 028 * \( x \in (-\infty, \infty) \). 029 * 030 * @see <a href="https://en.wikipedia.org/wiki/Logistic_distribution">Logistic distribution (Wikipedia)</a> 031 * @see <a href="https://mathworld.wolfram.com/LogisticDistribution.html">Logistic distribution (MathWorld)</a> 032 */ 033public final class LogisticDistribution extends AbstractContinuousDistribution { 034 /** Support lower bound. */ 035 private static final double SUPPORT_LO = Double.NEGATIVE_INFINITY; 036 /** Support upper bound. */ 037 private static final double SUPPORT_HI = Double.POSITIVE_INFINITY; 038 /** π<sup>2</sup>/3. https://oeis.org/A195055. */ 039 private static final double PI_SQUARED_OVER_THREE = 3.289868133696452872944830; 040 /** Location parameter. */ 041 private final double mu; 042 /** Scale parameter. */ 043 private final double scale; 044 /** Logarithm of "scale". */ 045 private final double logScale; 046 047 /** 048 * @param mu Location parameter. 049 * @param scale Scale parameter (must be positive). 050 */ 051 private LogisticDistribution(double mu, 052 double scale) { 053 this.mu = mu; 054 this.scale = scale; 055 this.logScale = Math.log(scale); 056 } 057 058 /** 059 * Creates a logistic distribution. 060 * 061 * @param mu Location parameter. 062 * @param scale Scale parameter (must be positive). 063 * @return the distribution 064 * @throws IllegalArgumentException if {@code scale <= 0}. 065 */ 066 public static LogisticDistribution of(double mu, 067 double scale) { 068 if (scale <= 0) { 069 throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, 070 scale); 071 } 072 return new LogisticDistribution(mu, scale); 073 } 074 075 /** 076 * Gets the location parameter of this distribution. 077 * 078 * @return the location parameter. 079 */ 080 public double getLocation() { 081 return mu; 082 } 083 084 /** 085 * Gets the scale parameter of this distribution. 086 * 087 * @return the scale parameter. 088 */ 089 public double getScale() { 090 return scale; 091 } 092 093 /** {@inheritDoc} */ 094 @Override 095 public double density(double x) { 096 if (x <= SUPPORT_LO || 097 x >= SUPPORT_HI) { 098 return 0; 099 } 100 101 // Ensure symmetry around location by using the absolute. 102 // This also ensures exp(z) is between 1 and 0 and avoids 103 // overflow for large negative values of (x - mu). 104 // Exploits the reciprocal relation: exp(-x) == 1 / exp(x) 105 // exp(-z) 1 exp(z) exp(z) 106 // --------------- = -------------------------- * ------ = -------------- 107 // (1 + exp(-z))^2 exp(z) (1 + 1 / exp(z))^2 exp(z) (1 + exp(z))^2 108 final double z = -Math.abs(x - mu) / scale; 109 final double v = Math.exp(z); 110 return v / ((1 + v) * (1 + v)) / scale; 111 } 112 113 /** {@inheritDoc} */ 114 @Override 115 public double logDensity(double x) { 116 if (x <= SUPPORT_LO || 117 x >= SUPPORT_HI) { 118 return Double.NEGATIVE_INFINITY; 119 } 120 121 // Ensure symmetry around location by using the absolute 122 final double z = -Math.abs(x - mu) / scale; 123 final double v = Math.exp(z); 124 return z - 2 * Math.log1p(v) - logScale; 125 } 126 127 /** {@inheritDoc} */ 128 @Override 129 public double cumulativeProbability(double x) { 130 final double z = (x - mu) / scale; 131 return 1 / (1 + Math.exp(-z)); 132 } 133 134 /** {@inheritDoc} */ 135 @Override 136 public double survivalProbability(double x) { 137 final double z = (x - mu) / scale; 138 return 1 / (1 + Math.exp(z)); 139 } 140 141 /** {@inheritDoc} */ 142 @Override 143 public double inverseCumulativeProbability(double p) { 144 ArgumentUtils.checkProbability(p); 145 if (p == 0) { 146 return SUPPORT_LO; 147 } else if (p == 1) { 148 return SUPPORT_HI; 149 } else { 150 return scale * Math.log(p / (1 - p)) + mu; 151 } 152 } 153 154 /** {@inheritDoc} */ 155 @Override 156 public double inverseSurvivalProbability(double p) { 157 ArgumentUtils.checkProbability(p); 158 if (p == 1) { 159 return SUPPORT_LO; 160 } else if (p == 0) { 161 return SUPPORT_HI; 162 } else { 163 return scale * -Math.log(p / (1 - p)) + mu; 164 } 165 } 166 167 /** 168 * {@inheritDoc} 169 * 170 * <p>The mean is equal to the {@linkplain #getLocation() location}. 171 */ 172 @Override 173 public double getMean() { 174 return getLocation(); 175 } 176 177 /** 178 * {@inheritDoc} 179 * 180 * <p>For scale parameter \( s \), the variance is: 181 * 182 * <p>\[ \frac{s^2 \pi^2}{3} \] 183 */ 184 @Override 185 public double getVariance() { 186 return scale * scale * PI_SQUARED_OVER_THREE; 187 } 188 189 /** 190 * {@inheritDoc} 191 * 192 * <p>The lower bound of the support is always negative infinity. 193 * 194 * @return {@linkplain Double#NEGATIVE_INFINITY negative infinity}. 195 */ 196 @Override 197 public double getSupportLowerBound() { 198 return SUPPORT_LO; 199 } 200 201 /** 202 * {@inheritDoc} 203 * 204 * <p>The upper bound of the support is always positive infinity. 205 * 206 * @return {@linkplain Double#POSITIVE_INFINITY positive infinity}. 207 */ 208 @Override 209 public double getSupportUpperBound() { 210 return SUPPORT_HI; 211 } 212 213 /** {@inheritDoc} */ 214 @Override 215 double getMedian() { 216 // Overridden for the probability(double, double) method. 217 // This is intentionally not a public method. 218 return mu; 219 } 220}