edu.utah.further.core.util.math.random.RandomUtilService.java Source code

Java tutorial

Introduction

Here is the source code for edu.utah.further.core.util.math.random.RandomUtilService.java

Source

/**
 * Copyright (C) [2013] [The FURTHeR Project]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package edu.utah.further.core.util.math.random;

import static edu.utah.further.core.api.collections.CollectionUtil.toList;
import static edu.utah.further.core.api.constant.Constants.INVALID_VALUE_INTEGER;
import static edu.utah.further.core.api.constant.Strings.EMPTY_STRING;
import static edu.utah.further.core.api.message.ValidationUtil.validateIsTrue;
import static java.lang.Double.NEGATIVE_INFINITY;
import static java.lang.Double.POSITIVE_INFINITY;
import static org.slf4j.LoggerFactory.getLogger;

import java.util.Arrays;
import java.util.List;

import org.apache.commons.math.random.RandomData;
import org.apache.commons.math.random.RandomDataImpl;
import org.apache.commons.math.random.RandomGenerator;
import org.slf4j.Logger;
import org.springframework.stereotype.Service;

import edu.utah.further.core.api.exception.BusinessRuleException;

/**
 * Centralized utilities related to random number generation. Centralizes all methods
 * related to randomization, shuffling, etc. Can be wired with any {@link RandomGenerator}
 * implementation (in testing a deterministic source may be desirable to reproduce
 * results).
 * <p>
 * -----------------------------------------------------------------------------------<br>
 * (c) 2008-2013 FURTHeR Project, AVP Health Sciences IT Office, University of Utah<br>
 * Contact: {@code <further@utah.edu>}<br>
 * Biomedical Informatics, 26 South 2000 East<br>
 * Room 5775 HSEB, Salt Lake City, UT 84112<br>
 * Day Phone: 1-801-581-4080<br>
 * -----------------------------------------------------------------------------------
 * 
 * @author Oren E. Livne {@code <oren.livne@utah.edu>}
 * @version Dec 17, 2008
 */
@Service("randomUtilService")
public class RandomUtilService {
    // ========================= CONSTANTS =================================

    /**
     * For logging printouts.
     */
    private static final Logger log = getLogger(RandomUtilService.class);

    // ==========================================================
    // Confidence intervals / t-Distributions
    // ==========================================================
    /*
     * Original table Reference: http://en.wikipedia.org/wiki/Student%27s_t-distribution r
     * 75% 80% 85% 90% 95% 97.5% 99% 99.5% 99.75% 99.9% 99.95% 1 1.000 1.376 1.963 3.078
     * 6.314 12.71 31.82 63.66 127.3 318.3 636.6 2 0.816 1.061 1.386 1.886 2.920 4.303
     * 6.965 9.925 14.09 22.33 31.60 3 0.765 0.978 1.250 1.638 2.353 3.182 4.541 5.841
     * 7.453 10.21 12.92 4 0.741 0.941 1.190 1.533 2.132 2.776 3.747 4.604 5.598 7.173
     * 8.610 5 0.727 0.920 1.156 1.476 2.015 2.571 3.365 4.032 4.773 5.893 6.869 6 0.718
     * 0.906 1.134 1.440 1.943 2.447 3.143 3.707 4.317 5.208 5.959 7 0.711 0.896 1.119
     * 1.415 1.895 2.365 2.998 3.499 4.029 4.785 5.408 8 0.706 0.889 1.108 1.397 1.860
     * 2.306 2.896 3.355 3.833 4.501 5.041 9 0.703 0.883 1.100 1.383 1.833 2.262 2.821
     * 3.250 3.690 4.297 4.781 10 0.700 0.879 1.093 1.372 1.812 2.228 2.764 3.169 3.581
     * 4.144 4.587
     */
    private static final double[] conf = { .75, .80, .85, .90, .95, .975, .99, .995, .9975, .999, .9995 };

    private static final int[] dof = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    private static final double[][] tDistrib = {
            { 1.000, 1.376, 1.963, 3.078, 6.314, 12.71, 31.82, 63.66, 127.3, 318.3, 636.6 },
            { 0.816, 1.061, 1.386, 1.886, 2.920, 4.303, 6.965, 9.925, 14.09, 22.33, 31.60 },
            { 0.765, 0.978, 1.250, 1.638, 2.353, 3.182, 4.541, 5.841, 7.453, 10.21, 12.92 },
            { 0.741, 0.941, 1.190, 1.533, 2.132, 2.776, 3.747, 4.604, 5.598, 7.173, 8.610 },
            { 0.727, 0.920, 1.156, 1.476, 2.015, 2.571, 3.365, 4.032, 4.773, 5.893, 6.869 },
            { 0.718, 0.906, 1.134, 1.440, 1.943, 2.447, 3.143, 3.707, 4.317, 5.208, 5.959 },
            { 0.711, 0.896, 1.119, 1.415, 1.895, 2.365, 2.998, 3.499, 4.029, 4.785, 5.408 },
            { 0.706, 0.889, 1.108, 1.397, 1.860, 2.306, 2.896, 3.355, 3.833, 4.501, 5.041 },
            { 0.703, 0.883, 1.100, 1.383, 1.833, 2.262, 2.821, 3.250, 3.690, 4.297, 4.781 },
            { 0.700, 0.879, 1.093, 1.372, 1.812, 2.228, 2.764, 3.169, 3.581, 4.144, 4.587 } };

    // ========================= DEPENDENCIES ==============================

    /**
     * Random number generator.
     */
    private RandomGenerator randomGenerator;

    // ========================= FIELDS ====================================

    /**
     * Commons math random generator utility class.
     */
    private RandomData randomData = newRandomData(randomGenerator);

    // ========================= METHODS ===================================

    /**
     * Returns a reference to commons math random generator utility class. This is
     * basically a factory of random numbers in various forms.
     * 
     * @return random data factory instance
     */
    public RandomData getRandomData() {
        return randomData;
    }

    /**
     * Generate a random double in [0.0,1.0].
     * 
     * @return random integer in [low:high]
     */
    public double randomDouble() {
        return randomDouble(0.0d, 1.0d);
    }

    /**
     * Generate a random double in an interval.
     * 
     * @param low
     *            lower bound (inclusive)
     * @param high
     *            upper bound (inclusive)
     * @return random integer in [low:high]
     */
    public double randomDouble(final double low, final double high) {
        validateIsTrue(low < high, "Empty range [" + low + "," + high + "]");
        return getRandomData().nextUniform(low, high);
    }

    /**
     * Generate a random integer in an interval (including endpoints). It's OK to specify
     * <code>low=high</code>.
     * 
     * @param low
     *            lower bound (inclusive)
     * @param high
     *            upper bound (inclusive)
     * @return random integer in [low:high]
     */
    public int randomInteger(final int low, final int high) {
        validateIsTrue(low <= high, "Empty range [" + low + "," + high + "]");
        return (low == high) ? low : getRandomData().nextInt(low, high);
    }

    /**
     * Generate a random boolean.
     * 
     * @param frequency
     *            average (number of returning false + 1) per one true return
     * @return random boolean; average (number of returning false + 1) per one true return
     */
    public boolean randomBoolean(final int frequency) {
        // Generate a random integer in the range
        return (randomInteger(1, frequency) == 1);
    }

    /**
     * Generate a random boolean with equal probabilities for returning true and false.
     * 
     * @return a random boolean with equal probabilities for returning true and false
     */
    public boolean randomBoolean() {
        return (randomDouble() < 0.5);
    }

    /**
     * Generate a random enumerated type.
     * 
     * @param <E>
     *            enum type
     * @param enumType
     *            the Class object of the enumerated type
     * @return random enum constant
     */
    public <E extends Enum<E>> E randomEnum(final Class<E> enumType) {
        final E[] values = enumType.getEnumConstants();
        return values[randomInteger(0, values.length - 1)];
    }

    /**
     * Create a new random numerical password for a user. Consists of digits only between
     * 0 and <code>maxPassword-1</code>.
     * 
     * @param maxPassword
     *            maximum number allowed for the password
     * @return password String
     */
    public String randomLongPassword(final int maxPassword) {
        if (maxPassword <= 0) {
            throw new BusinessRuleException("Cannot generate password with non-positive length " + maxPassword);
        }
        int num = 0;
        while (num == 0) {
            num = getRandomData().nextSecureInt(0, maxPassword - 1);
        }
        String password = EMPTY_STRING;
        int pad = 10 * num;
        while (pad < maxPassword) {
            password = password + "0";
            pad *= 10;
        }
        password = password + num; // Bizare long-to-String conversion
        return password;
    } // randomPassword()

    /**
     * Generate a random element in a discrete set (list), where each element has a
     * probability of being selected. This is a multinomial distribution with values
     * <code>[0..p.length-1]</code> and corresponding probabilities
     * <code>[p[0],...,p[p.length-1]]</code>.
     * 
     * @param p
     *            array of probabilities. <code>p[i]</code> is the probability of
     *            selecting <code>i, sum(p[i]) = 1, 0 <= p[i] <= 1</code>.
     * @param numSamples
     *            #generated random indices
     * @return random integer in <code>[0..p.length-1]</code>
     */
    public int randomMultinomial(final double[] p) {
        // x is uniformly random in [0,1]
        final double x = randomDouble();

        // If x is in the interval [q[i]..q[i+1]), choose i
        // where q[i] = sum(p[j], j=0..i-1), q[0] = 0
        double low, high = 0.0d;
        for (int i = 0; i < p.length; i++) {
            low = high;
            high += p[i];
            if ((x >= low) && (x < high)) {
                return i;
            }
        }
        // Something went wrong
        return INVALID_VALUE_INTEGER;
    }

    /**
     * Generate a random element in a discrete set (list), where each element has a
     * probability of being selected. This is a multinomial distribution with values
     * <code>[0..p.length-1]</code> and corresponding probabilities
     * <code>[p[0],...,p[p.length-1]]</code>.
     * 
     * @param p
     *            array of probabilities. <code>p[i]</code> is the probability of
     *            selecting <code>i, sum(p[i]) = 1, 0 <= p[i] <= 1</code>.
     * @param numSamples
     *            #generated random indices
     * @return random integer in <code>[0..p.length-1]</code>
     */
    public int randomMultinomial(final Double[] p) {
        // x is uniformly random in [0,1]
        final double x = randomDouble();

        // If x is in the interval [q[i]..q[i+1]), choose i
        // where q[i] = sum(p[j], j=0..i-1), q[0] = 0
        double low, high = 0.0d;
        for (int i = 0; i < p.length; i++) {
            low = high;
            high += p[i].doubleValue();
            if ((x >= low) && (x < high)) {
                return i;
            }
        }

        // Something went wrong
        log.error("Could not select random multinomial, check that p sums to 1: " + Arrays.toString(p));
        return INVALID_VALUE_INTEGER;
    }

    /**
     * Return the constant A in the one-sided confidence interval lower bound on a
     * Gaussian random variable X, which is (mu - A*std/sqrt(n-1)) where mu = average of n
     * samples of X and std = their standard deviation. Here #dof = n-1.
     * 
     * @param confidence
     *            desired confidence level (in [0,1])
     * @param thisDof
     *            degrees of freedom
     * @return confidence interval constant A
     */
    @Deprecated
    public double tDistribution(final double confidence, final int thisDof) {
        if (thisDof <= 0) {
            return POSITIVE_INFINITY;
        }
        int dofIndex = 0;
        for (int i = 0; i < dof.length; i++, dofIndex++) {
            if (thisDof <= dof[i]) {
                break;
            }
        }
        if (dofIndex == dof.length) {
            dofIndex--;
        }

        int confIndex = 0;
        for (int i = 0; i < conf.length; i++, confIndex++) {
            if (confidence <= conf[i]) {
                break;
            }
        }
        if (confIndex == conf.length)
            confIndex--;

        return tDistrib[dofIndex][confIndex];
    }

    /**
     * One-sided confidence interval lower bound on a Gaussian random variable X, which is
     * (mu - A*std/sqrt(n-1)) where mu = average of n samples of X and std = their
     * standard deviation. Here #dof = n-1 and A is a function of the dof
     * 
     * @param mu
     *            average of X measured over n samples
     * @param std
     *            standard deviation of X (continuous, or measured over the same n
     *            samples)
     * @param confidence
     *            desired confidence level (in [0,1])
     * @param n
     *            number of samples
     * @return One-sided confidence interval lower bound
     */
    public double confidenceLowerBound(final double confidence, final int n, final double mu, final double std) {
        final int degreesOfFreedom = n - 1;
        if (degreesOfFreedom <= 0) {
            return NEGATIVE_INFINITY;
        }
        final double A = tDistribution(confidence, degreesOfFreedom);
        return mu - A * std / Math.sqrt(degreesOfFreedom);
        // TODO: test this code and if it yields the same resulst as the above
        // deprecated call, use it instead and delete the tDistribution() method
        // final TDistribution tDistribution = new TDistributionImpl(degreesOfFreedom);
        // try
        // {
        // double A = tDistribution.cumulativeProbability(confidence);
        // return mu - A * std / Math.sqrt(degreesOfFreedom);
        // }
        // catch (final MathException e)
        // {
        // throw new ApplicationException(ErrorCode.MATH_ERROR,
        // "Failed to evaluate t-distribution at " + confidence, e);
        // }
    }

    /**
     * Generate a random letter.
     * 
     * @return random letter
     */
    public char randomLetter() {
        return (char) randomInteger('a', 'z');
    }

    /**
     * Generate a random string consisting of letters only.
     * 
     * @param low
     *            minimum string length
     * @param high
     *            maximum string length
     * @return random string consisting of letters only
     */
    public String randomWord(final int low, final int high) {
        final int n = randomInteger(low, high);
        final byte b[] = new byte[n];
        for (int i = 0; i < n; i++) {
            b[i] = (byte) randomInteger('a', 'z');
        }
        return new String(b);
    }

    /**
     * Generate a random member of a list.
     * 
     * @param list
     *            list
     * @return random member of the list
     */
    public <E> E randomInList(final List<? extends E> list) {
        return list.get(randomInteger(0, list.size() - 1));
    }

    /**
     * Generate a random member of an array.
     * 
     * @param array
     *            array
     * @return random member of the array
     */
    public <E> E randomInList(final E[] array) {
        return array[randomInteger(0, array.length - 1)];
    }

    /**
     * Return a random permutation of <code>[0..n-1]</code> an array.
     * 
     * @param n
     *            size of permutation
     * @return an array of integers that are a random permutation of [0..n-1].
     */
    public int[] randomPermutation(final int n) {
        return randomPermutation(n, n);
    }

    /**
     * Generates an integer array of length <code>k</code> whose entries are selected
     * randomly, without repetition, from the integers <code>0</code> through
     * <code>n-1</code> (inclusive).
     * <p>
     * Generated arrays represent permutations of n taken k at a time.
     * <p>
     * 
     * @param n
     *            domain of the permutation
     * @param k
     *            size of the permutation
     * @return random k-permutation of n
     * @see RandomData#nextPermutation(int, int)
     */
    public int[] randomPermutation(final int n, final int k) {
        return getRandomData().nextPermutation(n, k);
    }

    /**
     * Return a random permutation of [0..n-1] a list (uniformly distributed).
     * 
     * @param n
     *            size of permutation
     * @return a list of integers that are a random permutation of [0..n-1].
     */
    public List<Integer> randomPermutationAsList(final int n) {
        return toList(getRandomData().nextPermutation(n, n));
    }

    // ========================= DEPENDENCY INJECTION ======================

    /**
     * Set a new value for the randomGenerator property.
     * 
     * @param randomGenerator
     *            the randomGenerator to set
     */
    public void setRandomGenerator(final RandomGenerator randomGenerator) {
        this.randomGenerator = randomGenerator;
        this.randomData = newRandomData(randomGenerator);
    }

    // ========================= PRIVATE METHODS ===========================

    /**
     * @param randomGenerator
     * @return
     */
    private static RandomData newRandomData(final RandomGenerator randomGenerator) {
        return (randomGenerator == null) ? new RandomDataImpl() : new RandomDataImpl(randomGenerator);
    }
}