LogisticDistribution.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.statistics.distribution;
/**
* Implementation of the logistic distribution.
*
* <p>The probability density function of \( X \) is:
*
* <p>\[ f(x; \mu, s) = \frac{e^{-(x-\mu)/s}} {s\left(1+e^{-(x-\mu)/s}\right)^2} \]
*
* <p>for \( \mu \) the location,
* \( s > 0 \) the scale, and
* \( x \in (-\infty, \infty) \).
*
* @see <a href="https://en.wikipedia.org/wiki/Logistic_distribution">Logistic distribution (Wikipedia)</a>
* @see <a href="https://mathworld.wolfram.com/LogisticDistribution.html">Logistic distribution (MathWorld)</a>
*/
public final class LogisticDistribution extends AbstractContinuousDistribution {
/** Support lower bound. */
private static final double SUPPORT_LO = Double.NEGATIVE_INFINITY;
/** Support upper bound. */
private static final double SUPPORT_HI = Double.POSITIVE_INFINITY;
/** π<sup>2</sup>/3. https://oeis.org/A195055. */
private static final double PI_SQUARED_OVER_THREE = 3.289868133696452872944830;
/** Location parameter. */
private final double mu;
/** Scale parameter. */
private final double scale;
/** Logarithm of "scale". */
private final double logScale;
/**
* @param mu Location parameter.
* @param scale Scale parameter (must be positive).
*/
private LogisticDistribution(double mu,
double scale) {
this.mu = mu;
this.scale = scale;
this.logScale = Math.log(scale);
}
/**
* Creates a logistic distribution.
*
* @param mu Location parameter.
* @param scale Scale parameter (must be positive).
* @return the distribution
* @throws IllegalArgumentException if {@code scale <= 0}.
*/
public static LogisticDistribution of(double mu,
double scale) {
if (scale <= 0) {
throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE,
scale);
}
return new LogisticDistribution(mu, scale);
}
/**
* Gets the location parameter of this distribution.
*
* @return the location parameter.
*/
public double getLocation() {
return mu;
}
/**
* Gets the scale parameter of this distribution.
*
* @return the scale parameter.
*/
public double getScale() {
return scale;
}
/** {@inheritDoc} */
@Override
public double density(double x) {
if (x <= SUPPORT_LO ||
x >= SUPPORT_HI) {
return 0;
}
// Ensure symmetry around location by using the absolute.
// This also ensures exp(z) is between 1 and 0 and avoids
// overflow for large negative values of (x - mu).
// Exploits the reciprocal relation: exp(-x) == 1 / exp(x)
// exp(-z) 1 exp(z) exp(z)
// --------------- = -------------------------- * ------ = --------------
// (1 + exp(-z))^2 exp(z) (1 + 1 / exp(z))^2 exp(z) (1 + exp(z))^2
final double z = -Math.abs(x - mu) / scale;
final double v = Math.exp(z);
return v / ((1 + v) * (1 + v)) / scale;
}
/** {@inheritDoc} */
@Override
public double logDensity(double x) {
if (x <= SUPPORT_LO ||
x >= SUPPORT_HI) {
return Double.NEGATIVE_INFINITY;
}
// Ensure symmetry around location by using the absolute
final double z = -Math.abs(x - mu) / scale;
final double v = Math.exp(z);
return z - 2 * Math.log1p(v) - logScale;
}
/** {@inheritDoc} */
@Override
public double cumulativeProbability(double x) {
final double z = (x - mu) / scale;
return 1 / (1 + Math.exp(-z));
}
/** {@inheritDoc} */
@Override
public double survivalProbability(double x) {
final double z = (x - mu) / scale;
return 1 / (1 + Math.exp(z));
}
/** {@inheritDoc} */
@Override
public double inverseCumulativeProbability(double p) {
ArgumentUtils.checkProbability(p);
if (p == 0) {
return SUPPORT_LO;
} else if (p == 1) {
return SUPPORT_HI;
} else {
return scale * Math.log(p / (1 - p)) + mu;
}
}
/** {@inheritDoc} */
@Override
public double inverseSurvivalProbability(double p) {
ArgumentUtils.checkProbability(p);
if (p == 1) {
return SUPPORT_LO;
} else if (p == 0) {
return SUPPORT_HI;
} else {
return scale * -Math.log(p / (1 - p)) + mu;
}
}
/**
* {@inheritDoc}
*
* <p>The mean is equal to the {@linkplain #getLocation() location}.
*/
@Override
public double getMean() {
return getLocation();
}
/**
* {@inheritDoc}
*
* <p>For scale parameter \( s \), the variance is:
*
* <p>\[ \frac{s^2 \pi^2}{3} \]
*/
@Override
public double getVariance() {
return scale * scale * PI_SQUARED_OVER_THREE;
}
/**
* {@inheritDoc}
*
* <p>The lower bound of the support is always negative infinity.
*
* @return {@linkplain Double#NEGATIVE_INFINITY negative infinity}.
*/
@Override
public double getSupportLowerBound() {
return SUPPORT_LO;
}
/**
* {@inheritDoc}
*
* <p>The upper bound of the support is always positive infinity.
*
* @return {@linkplain Double#POSITIVE_INFINITY positive infinity}.
*/
@Override
public double getSupportUpperBound() {
return SUPPORT_HI;
}
/** {@inheritDoc} */
@Override
double getMedian() {
// Overridden for the probability(double, double) method.
// This is intentionally not a public method.
return mu;
}
}