/* * (c) Copyright 2010-2011 by Volker Bergmann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, is permitted under the terms of the * GNU General Public License (GPL). * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, * REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE * HEREBY EXCLUDED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package org.databene.benerator.distribution; import java.util.List; import java.util.Random; import org.databene.benerator.Generator; import org.databene.benerator.NonNullGenerator; import org.databene.benerator.primitive.number.AbstractNonNullNumberGenerator; import org.databene.benerator.sample.ConstantGenerator; import org.databene.benerator.sample.SampleGenerator; import org.databene.benerator.util.GeneratorUtil; import org.databene.commons.Converter; import org.databene.commons.converter.ConverterManager; /** * {@link Distribution} implementation which uses the inverse of a probability function integral * for efficiently generating numbers with a given probability distribution. * See <a href="http://www.stat.wisc.edu/~larget/math496/random2.html">Random * Number Generation from Non-uniform Distributions</a>.<br/><br/> * Created: 12.03.2010 13:31:16 * @since 0.6.0 * @author Volker Bergmann */ public abstract class CumulativeDistributionFunction implements Distribution { public abstract double cumulativeProbability(double value); public abstract double inverse(double probability); public <T> Generator<T> applyTo(Generator<T> source, boolean unique) { if (unique) throw new IllegalArgumentException(this + " cannot generate unique values"); List<T> allProducts = GeneratorUtil.allProducts(source); if (allProducts.size() == 1) return new ConstantGenerator<T>(allProducts.get(0)); return new SampleGenerator<T>(source.getGeneratedType(), this, unique, allProducts); } public <T extends Number> NonNullGenerator<T> createNumberGenerator( Class<T> numberType, T min, T max, T granularity, boolean unique) { if (unique) throw new IllegalArgumentException(this + " cannot generate unique values"); return new IPINumberGenerator<T>(this, numberType, min, max, granularity); } @Override public String toString() { return getClass().getSimpleName(); } /** * Generates numbers according to an {@link CumulativeDistributionFunction}.<br/><br/> * Created: 12.03.2010 14:37:33 * @since 0.6.0 * @author Volker Bergmann */ public static class IPINumberGenerator<E extends Number> extends AbstractNonNullNumberGenerator<E> { private CumulativeDistributionFunction fcn; private Random random = new Random(); private Converter<Double, E> converter; private double minProb; private double probScale; private double minD; private double maxD; private double granularityD; public IPINumberGenerator(CumulativeDistributionFunction fcn, Class<E> targetType, E min, E max, E granularity) { super(targetType, min, max, granularity); this.fcn = fcn; this.minD = (min != null ? min.doubleValue() : (max != null ? maxD - 9 : 0)); this.maxD = (max != null ? max.doubleValue() : (min != null ? minD + 9 : 0)); this.granularityD = granularity.doubleValue(); this.minProb = fcn.cumulativeProbability(minD); this.probScale = fcn.cumulativeProbability(maxD + granularityD) - this.minProb; this.converter = ConverterManager.getInstance().createConverter(Double.class, targetType); } @Override public E generate() { double tmp; double prob = minProb + random.nextDouble() * probScale; tmp = fcn.inverse(prob); tmp = Math.floor((tmp - minD) / granularityD) * granularityD + minD; return converter.convert(tmp); } } }