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}