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 &gt; 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    /** &pi;<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}