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.DiscreteUniformSampler;
022
023/**
024 * Implementation of the uniform discrete distribution.
025 *
026 * <p>The probability mass function of \( X \) is:
027 *
028 * <p>\[ f(k; a, b) = \frac{1}{b-a+1} \]
029 *
030 * <p>for integer \( a, b \) and \( a \le b \) and
031 * \( k \in [a, b] \).
032 *
033 * @see <a href="https://en.wikipedia.org/wiki/Uniform_distribution_(discrete)">
034 * Uniform distribution (discrete) (Wikipedia)</a>
035 * @see <a href="https://mathworld.wolfram.com/DiscreteUniformDistribution.html">
036 * Discrete uniform distribution (MathWorld)</a>
037 */
038public final class UniformDiscreteDistribution extends AbstractDiscreteDistribution {
039    /** Lower bound (inclusive) of this distribution. */
040    private final int lower;
041    /** Upper bound (inclusive) of this distribution. */
042    private final int upper;
043    /** "upper" - "lower" + 1 (as a double to avoid overflow). */
044    private final double upperMinusLowerPlus1;
045    /** Cache of the probability. */
046    private final double pmf;
047    /** Cache of the log probability. */
048    private final double logPmf;
049    /** Value of survival probability for x=0. Used in the inverse survival function. */
050    private final double sf0;
051
052    /**
053     * @param lower Lower bound (inclusive) of this distribution.
054     * @param upper Upper bound (inclusive) of this distribution.
055     */
056    private UniformDiscreteDistribution(int lower,
057                                        int upper) {
058        this.lower = lower;
059        this.upper = upper;
060        upperMinusLowerPlus1 = (double) upper - (double) lower + 1;
061        pmf = 1.0 / upperMinusLowerPlus1;
062        logPmf = -Math.log(upperMinusLowerPlus1);
063        sf0 = (upperMinusLowerPlus1 - 1) / upperMinusLowerPlus1;
064    }
065
066    /**
067     * Creates a new uniform discrete distribution.
068     *
069     * @param lower Lower bound (inclusive) of this distribution.
070     * @param upper Upper bound (inclusive) of this distribution.
071     * @return the distribution
072     * @throws IllegalArgumentException if {@code lower > upper}.
073     */
074    public static UniformDiscreteDistribution of(int lower,
075                                                 int upper) {
076        if (lower > upper) {
077            throw new DistributionException(DistributionException.INVALID_RANGE_LOW_GT_HIGH,
078                                            lower, upper);
079        }
080        return new UniformDiscreteDistribution(lower, upper);
081    }
082
083    /** {@inheritDoc} */
084    @Override
085    public double probability(int x) {
086        if (x < lower || x > upper) {
087            return 0;
088        }
089        return pmf;
090    }
091
092    /** {@inheritDoc} */
093    @Override
094    public double probability(int x0,
095                              int x1) {
096        if (x0 > x1) {
097            throw new DistributionException(DistributionException.INVALID_RANGE_LOW_GT_HIGH, x0, x1);
098        }
099        if (x0 >= upper || x1 < lower) {
100            // (x0, x1] does not overlap [lower, upper]
101            return 0;
102        }
103
104        // x0 < upper
105        // x1 >= lower
106
107        // Find the range between x0 (exclusive) and x1 (inclusive) within [lower, upper].
108        // In the case of x0 < lower set l so that u - l == (u - lower) + 1
109        // long arithmetic prevents overflow
110        final long l = Math.max(lower - 1L, x0);
111        final long u = Math.min(upper, x1);
112
113        return (u - l) / upperMinusLowerPlus1;
114    }
115
116    /** {@inheritDoc} */
117    @Override
118    public double logProbability(int x) {
119        if (x < lower || x > upper) {
120            return Double.NEGATIVE_INFINITY;
121        }
122        return logPmf;
123    }
124
125    /** {@inheritDoc} */
126    @Override
127    public double cumulativeProbability(int x) {
128        if (x <= lower) {
129            // Note: CDF(x=0) = PDF(x=0)
130            return x == lower ? pmf : 0;
131        }
132        if (x >= upper) {
133            return 1;
134        }
135        return ((double) x - lower + 1) / upperMinusLowerPlus1;
136    }
137
138    /** {@inheritDoc} */
139    @Override
140    public double survivalProbability(int x) {
141        if (x <= lower) {
142            // Note: SF(x=0) = 1 - PDF(x=0)
143            // Use a pre-computed value to avoid cancellation when probabilityOfSuccess -> 0
144            return x == lower ? sf0 : 1;
145        }
146        if (x >= upper) {
147            return 0;
148        }
149        return ((double) upper - x) / upperMinusLowerPlus1;
150    }
151
152    /** {@inheritDoc} */
153    @Override
154    public int inverseCumulativeProbability(double p) {
155        ArgumentUtils.checkProbability(p);
156        if (p > sf0) {
157            return upper;
158        }
159        if (p <= pmf) {
160            return lower;
161        }
162        // p in ( pmf         , sf0             ]
163        // p in ( 1 / {u-l+1} , {u-l} / {u-l+1} ]
164        // x in ( l           , u-1             ]
165        int x = (int) (lower + Math.ceil(p * upperMinusLowerPlus1) - 1);
166
167        // Correct rounding errors.
168        // This ensures x == icdf(cdf(x))
169        // Note: Directly computing the CDF(x-1) avoids integer overflow if x=min_value
170
171        if (((double) x - lower) / upperMinusLowerPlus1 >= p) {
172            // No check for x > lower: cdf(x=lower) = 0 and thus is below p
173            // cdf(x-1) >= p
174            x--;
175        } else if (((double) x - lower + 1) / upperMinusLowerPlus1 < p) {
176            // No check for x < upper: cdf(x=upper) = 1 and thus is above p
177            // cdf(x) < p
178            x++;
179        }
180
181        return x;
182    }
183
184    /** {@inheritDoc} */
185    @Override
186    public int inverseSurvivalProbability(final double p) {
187        ArgumentUtils.checkProbability(p);
188        if (p < pmf) {
189            return upper;
190        }
191        if (p >= sf0) {
192            return lower;
193        }
194        // p in [ pmf         , sf0             )
195        // p in [ 1 / {u-l+1} , {u-l} / {u-l+1} )
196        // x in [ u-1         , l               )
197        int x = (int) (upper - Math.floor(p * upperMinusLowerPlus1));
198
199        // Correct rounding errors.
200        // This ensures x == isf(sf(x))
201        // Note: Directly computing the SF(x-1) avoids integer overflow if x=min_value
202
203        if (((double) upper - x + 1) / upperMinusLowerPlus1 <= p) {
204            // No check for x > lower: sf(x=lower) = 1 and thus is above p
205            // sf(x-1) <= p
206            x--;
207        } else if (((double) upper - x) / upperMinusLowerPlus1 > p) {
208            // No check for x < upper: sf(x=upper) = 0 and thus is below p
209            // sf(x) > p
210            x++;
211        }
212
213        return x;
214    }
215
216    /**
217     * {@inheritDoc}
218     *
219     * <p>For lower bound \( a \) and upper bound \( b \), the mean is \( \frac{1}{2} (a + b) \).
220     */
221    @Override
222    public double getMean() {
223        // Avoid overflow
224        return 0.5 * ((double) upper + (double) lower);
225    }
226
227    /**
228     * {@inheritDoc}
229     *
230     * <p>For lower bound \( a \) and upper bound \( b \), the variance is:
231     *
232     * <p>\[ \frac{1}{12} (n^2 - 1) \]
233     *
234     * <p>where \( n = b - a + 1 \).
235     */
236    @Override
237    public double getVariance() {
238        return (upperMinusLowerPlus1 * upperMinusLowerPlus1 - 1) / 12;
239    }
240
241    /**
242     * {@inheritDoc}
243     *
244     * <p>The lower bound of the support is equal to the lower bound parameter
245     * of the distribution.
246     */
247    @Override
248    public int getSupportLowerBound() {
249        return lower;
250    }
251
252    /**
253     * {@inheritDoc}
254     *
255     * <p>The upper bound of the support is equal to the upper bound parameter
256     * of the distribution.
257     */
258    @Override
259    public int getSupportUpperBound() {
260        return upper;
261    }
262
263    /** {@inheritDoc} */
264    @Override
265    public DiscreteDistribution.Sampler createSampler(final UniformRandomProvider rng) {
266        // Discrete uniform distribution sampler.
267        return DiscreteUniformSampler.of(rng, lower, upper)::sample;
268    }
269}