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.math3.distribution;
018
019import java.util.ArrayList;
020import java.util.List;
021
022import org.apache.commons.math3.exception.DimensionMismatchException;
023import org.apache.commons.math3.exception.MathArithmeticException;
024import org.apache.commons.math3.exception.NotANumberException;
025import org.apache.commons.math3.exception.NotFiniteNumberException;
026import org.apache.commons.math3.exception.NotPositiveException;
027import org.apache.commons.math3.exception.OutOfRangeException;
028import org.apache.commons.math3.random.RandomGenerator;
029import org.apache.commons.math3.random.Well19937c;
030import org.apache.commons.math3.util.Pair;
031
032/**
033 * <p>Implementation of a real-valued {@link EnumeratedDistribution}.
034 *
035 * <p>Values with zero-probability are allowed but they do not extend the
036 * support.<br/>
037 * Duplicate values are allowed. Probabilities of duplicate values are combined
038 * when computing cumulative probabilities and statistics.</p>
039 *
040 * @since 3.2
041 */
042public class EnumeratedRealDistribution extends AbstractRealDistribution {
043
044    /** Serializable UID. */
045    private static final long serialVersionUID = 20130308L;
046
047    /**
048     * {@link EnumeratedDistribution} (using the {@link Double} wrapper)
049     * used to generate the pmf.
050     */
051    protected final EnumeratedDistribution<Double> innerDistribution;
052
053    /**
054     * Create a discrete distribution using the given probability mass function
055     * enumeration.
056     * <p>
057     * <b>Note:</b> this constructor will implicitly create an instance of
058     * {@link Well19937c} as random generator to be used for sampling only (see
059     * {@link #sample()} and {@link #sample(int)}). In case no sampling is
060     * needed for the created distribution, it is advised to pass {@code null}
061     * as random generator via the appropriate constructors to avoid the
062     * additional initialisation overhead.
063     *
064     * @param singletons array of random variable values.
065     * @param probabilities array of probabilities.
066     * @throws DimensionMismatchException if
067     * {@code singletons.length != probabilities.length}
068     * @throws NotPositiveException if any of the probabilities are negative.
069     * @throws NotFiniteNumberException if any of the probabilities are infinite.
070     * @throws NotANumberException if any of the probabilities are NaN.
071     * @throws MathArithmeticException all of the probabilities are 0.
072     */
073    public EnumeratedRealDistribution(final double[] singletons, final double[] probabilities)
074    throws DimensionMismatchException, NotPositiveException, MathArithmeticException,
075           NotFiniteNumberException, NotANumberException {
076        this(new Well19937c(), singletons, probabilities);
077    }
078
079    /**
080     * Create a discrete distribution using the given random number generator
081     * and probability mass function enumeration.
082     *
083     * @param rng random number generator.
084     * @param singletons array of random variable values.
085     * @param probabilities array of probabilities.
086     * @throws DimensionMismatchException if
087     * {@code singletons.length != probabilities.length}
088     * @throws NotPositiveException if any of the probabilities are negative.
089     * @throws NotFiniteNumberException if any of the probabilities are infinite.
090     * @throws NotANumberException if any of the probabilities are NaN.
091     * @throws MathArithmeticException all of the probabilities are 0.
092     */
093    public EnumeratedRealDistribution(final RandomGenerator rng,
094                                    final double[] singletons, final double[] probabilities)
095        throws DimensionMismatchException, NotPositiveException, MathArithmeticException,
096               NotFiniteNumberException, NotANumberException {
097        super(rng);
098        if (singletons.length != probabilities.length) {
099            throw new DimensionMismatchException(probabilities.length, singletons.length);
100        }
101
102        List<Pair<Double, Double>> samples = new ArrayList<Pair<Double, Double>>(singletons.length);
103
104        for (int i = 0; i < singletons.length; i++) {
105            samples.add(new Pair<Double, Double>(singletons[i], probabilities[i]));
106        }
107
108        innerDistribution = new EnumeratedDistribution<Double>(rng, samples);
109    }
110
111    /**
112     * {@inheritDoc}
113     */
114    @Override
115    public double probability(final double x) {
116        return innerDistribution.probability(x);
117    }
118
119    /**
120     * For a random variable {@code X} whose values are distributed according to
121     * this distribution, this method returns {@code P(X = x)}. In other words,
122     * this method represents the probability mass function (PMF) for the
123     * distribution.
124     *
125     * @param x the point at which the PMF is evaluated
126     * @return the value of the probability mass function at point {@code x}
127     */
128    public double density(final double x) {
129        return probability(x);
130    }
131
132    /**
133     * {@inheritDoc}
134     */
135    public double cumulativeProbability(final double x) {
136        double probability = 0;
137
138        for (final Pair<Double, Double> sample : innerDistribution.getPmf()) {
139            if (sample.getKey() <= x) {
140                probability += sample.getValue();
141            }
142        }
143
144        return probability;
145    }
146
147    /**
148     * {@inheritDoc}
149     */
150    @Override
151    public double inverseCumulativeProbability(final double p) throws OutOfRangeException {
152        if (p < 0.0 || p > 1.0) {
153            throw new OutOfRangeException(p, 0, 1);
154        }
155
156        double probability = 0;
157        double x = getSupportLowerBound();
158        for (final Pair<Double, Double> sample : innerDistribution.getPmf()) {
159            if (sample.getValue() == 0.0) {
160                continue;
161            }
162
163            probability += sample.getValue();
164            x = sample.getKey();
165
166            if (probability >= p) {
167                break;
168            }
169        }
170
171        return x;
172    }
173
174    /**
175     * {@inheritDoc}
176     *
177     * @return {@code sum(singletons[i] * probabilities[i])}
178     */
179    public double getNumericalMean() {
180        double mean = 0;
181
182        for (final Pair<Double, Double> sample : innerDistribution.getPmf()) {
183            mean += sample.getValue() * sample.getKey();
184        }
185
186        return mean;
187    }
188
189    /**
190     * {@inheritDoc}
191     *
192     * @return {@code sum((singletons[i] - mean) ^ 2 * probabilities[i])}
193     */
194    public double getNumericalVariance() {
195        double mean = 0;
196        double meanOfSquares = 0;
197
198        for (final Pair<Double, Double> sample : innerDistribution.getPmf()) {
199            mean += sample.getValue() * sample.getKey();
200            meanOfSquares += sample.getValue() * sample.getKey() * sample.getKey();
201        }
202
203        return meanOfSquares - mean * mean;
204    }
205
206    /**
207     * {@inheritDoc}
208     *
209     * Returns the lowest value with non-zero probability.
210     *
211     * @return the lowest value with non-zero probability.
212     */
213    public double getSupportLowerBound() {
214        double min = Double.POSITIVE_INFINITY;
215        for (final Pair<Double, Double> sample : innerDistribution.getPmf()) {
216            if (sample.getKey() < min && sample.getValue() > 0) {
217                min = sample.getKey();
218            }
219        }
220
221        return min;
222    }
223
224    /**
225     * {@inheritDoc}
226     *
227     * Returns the highest value with non-zero probability.
228     *
229     * @return the highest value with non-zero probability.
230     */
231    public double getSupportUpperBound() {
232        double max = Double.NEGATIVE_INFINITY;
233        for (final Pair<Double, Double> sample : innerDistribution.getPmf()) {
234            if (sample.getKey() > max && sample.getValue() > 0) {
235                max = sample.getKey();
236            }
237        }
238
239        return max;
240    }
241
242    /**
243     * {@inheritDoc}
244     *
245     * The support of this distribution includes the lower bound.
246     *
247     * @return {@code true}
248     */
249    public boolean isSupportLowerBoundInclusive() {
250        return true;
251    }
252
253    /**
254     * {@inheritDoc}
255     *
256     * The support of this distribution includes the upper bound.
257     *
258     * @return {@code true}
259     */
260    public boolean isSupportUpperBoundInclusive() {
261        return true;
262    }
263
264    /**
265     * {@inheritDoc}
266     *
267     * The support of this distribution is connected.
268     *
269     * @return {@code true}
270     */
271    public boolean isSupportConnected() {
272        return true;
273    }
274
275    /**
276     * {@inheritDoc}
277     */
278    @Override
279    public double sample() {
280        return innerDistribution.sample();
281    }
282}