1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.statistics.distribution;
18
19 /**
20 * Implementation of the logistic distribution.
21 *
22 * <p>The probability density function of \( X \) is:
23 *
24 * <p>\[ f(x; \mu, s) = \frac{e^{-(x-\mu)/s}} {s\left(1+e^{-(x-\mu)/s}\right)^2} \]
25 *
26 * <p>for \( \mu \) the location,
27 * \( s > 0 \) the scale, and
28 * \( x \in (-\infty, \infty) \).
29 *
30 * @see <a href="https://en.wikipedia.org/wiki/Logistic_distribution">Logistic distribution (Wikipedia)</a>
31 * @see <a href="https://mathworld.wolfram.com/LogisticDistribution.html">Logistic distribution (MathWorld)</a>
32 */
33 public final class LogisticDistribution extends AbstractContinuousDistribution {
34 /** Support lower bound. */
35 private static final double SUPPORT_LO = Double.NEGATIVE_INFINITY;
36 /** Support upper bound. */
37 private static final double SUPPORT_HI = Double.POSITIVE_INFINITY;
38 /** π<sup>2</sup>/3. https://oeis.org/A195055. */
39 private static final double PI_SQUARED_OVER_THREE = 3.289868133696452872944830;
40 /** Location parameter. */
41 private final double mu;
42 /** Scale parameter. */
43 private final double scale;
44 /** Logarithm of "scale". */
45 private final double logScale;
46
47 /**
48 * @param mu Location parameter.
49 * @param scale Scale parameter (must be positive).
50 */
51 private LogisticDistribution(double mu,
52 double scale) {
53 this.mu = mu;
54 this.scale = scale;
55 this.logScale = Math.log(scale);
56 }
57
58 /**
59 * Creates a logistic distribution.
60 *
61 * @param mu Location parameter.
62 * @param scale Scale parameter (must be positive).
63 * @return the distribution
64 * @throws IllegalArgumentException if {@code scale <= 0}.
65 */
66 public static LogisticDistribution of(double mu,
67 double scale) {
68 if (scale <= 0) {
69 throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE,
70 scale);
71 }
72 return new LogisticDistribution(mu, scale);
73 }
74
75 /**
76 * Gets the location parameter of this distribution.
77 *
78 * @return the location parameter.
79 */
80 public double getLocation() {
81 return mu;
82 }
83
84 /**
85 * Gets the scale parameter of this distribution.
86 *
87 * @return the scale parameter.
88 */
89 public double getScale() {
90 return scale;
91 }
92
93 /** {@inheritDoc} */
94 @Override
95 public double density(double x) {
96 if (x <= SUPPORT_LO ||
97 x >= SUPPORT_HI) {
98 return 0;
99 }
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 }