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