/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.tools;
import com.rapidminer.Process;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.ProcessRootOperator;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.ParameterTypeBoolean;
import com.rapidminer.parameter.ParameterTypeInt;
import com.rapidminer.parameter.UndefinedParameterError;
import com.rapidminer.parameter.conditions.BooleanParameterCondition;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;
/**
* The global random number generator. This should be used for all random purposes of RapidMiner to
* ensure that two runs of the same process setup provide the same results.
*
* @author Ralf Klinkenberg, Ingo Mierswa
*/
public class RandomGenerator extends Random {
private static final long serialVersionUID = 7562534107359981433L;
public static final String PARAMETER_USE_LOCAL_RANDOM_SEED = "use_local_random_seed";
public static final String PARAMETER_LOCAL_RANDOM_SEED = "local_random_seed";
/** The default seed (used by the ProcessRootOperator) */
public static final int DEFAULT_SEED = 2001;
/** Magic seed of the ProcessRootOperator to use the current system time as seed */
private static final int USE_SYSTEM_TIME = -1;
/** Use this alphabet for random String creation. */
private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
/**
* Global random number generator using the random number generator seed specified for the root
* operator (ProcessRootOperator).
*/
private static final ThreadLocal<RandomGenerator> GLOBAL_RANDOM_GENERATOR = new ThreadLocal<RandomGenerator>() {
@Override
protected RandomGenerator initialValue() {
return new RandomGenerator(DEFAULT_SEED);
}
};
/** Initializes the random number generator without a seed. */
private RandomGenerator() {
super();
}
/** Initializes the random number generator with the given <code>seed</code> */
public RandomGenerator(long seed) {
super(seed);
}
// ================================================================================
/** Returns the global random number generator. */
public static RandomGenerator getGlobalRandomGenerator() {
return GLOBAL_RANDOM_GENERATOR.get();
}
/**
* Returns the global random number generator if useLocalGenerator is false and a new
* RandomGenerator with the given seed if the seed is positive or zero. This way is is possible
* to allow for local random seeds. Operators like learners or validation operators should
* definitely make use of such a local random generator.
*/
public static RandomGenerator getRandomGenerator(boolean useLocalGenerator, int localSeed) {
return (useLocalGenerator) ? getRandomGenerator(null, localSeed) : getGlobalRandomGenerator();
}
/**
* Returns the global random number generator if the seed is negative and a new RandomGenerator
* with the given seed if the seed is positive or zero. This way is is possible to allow for
* local random seeds. Operators like learners or validation operators should definitely make
* use of such a local random generator.
*
* @param process
* Not used
* @param seed
* The seed to use in the RandomGenerator
* @return new RandomGenerator if seed >=0, globalRandomGenerator otherwise
*/
public static RandomGenerator getRandomGenerator(Process process, int seed) {
if (seed < 0) {
return GLOBAL_RANDOM_GENERATOR.get();
} else {
return new RandomGenerator(seed);
}
}
/**
* Instantiates the global random number generator and initializes it with the random number
* generator seed specified in the <code>global</code> section of the configuration file. Should
* be invoked before the process starts.
*/
public static void init(Process process) {
long seed = DEFAULT_SEED;
if (process != null) {
try {
seed = process.getRootOperator().getParameterAsInt(ProcessRootOperator.PARAMETER_RANDOM_SEED);
} catch (UndefinedParameterError e) {
// tries to read the general random seed
// if no seed was specified (cannot happen) use default seed
seed = DEFAULT_SEED;
}
}
if (seed == USE_SYSTEM_TIME) {
GLOBAL_RANDOM_GENERATOR.set(new RandomGenerator());
} else {
GLOBAL_RANDOM_GENERATOR.set(new RandomGenerator(seed));
}
}
/**
* This method returns a list of parameters usable to conveniently provide parameters for random
* generator use within operators
*
* @param operator
* the operator
*/
public static List<ParameterType> getRandomGeneratorParameters(Operator operator) {
List<ParameterType> types = new LinkedList<>();
types.add(new ParameterTypeBoolean(PARAMETER_USE_LOCAL_RANDOM_SEED,
"Indicates if a local random seed should be used.", false));
ParameterType type = new ParameterTypeInt(PARAMETER_LOCAL_RANDOM_SEED, "Specifies the local random seed", 1,
Integer.MAX_VALUE, 1992);
type.registerDependencyCondition(new BooleanParameterCondition(operator, PARAMETER_USE_LOCAL_RANDOM_SEED, false,
true));
types.add(type);
return types;
}
/**
* This method returns the appropriate RandomGenerator for the user chosen parameter combination
*
* @param operator
* @throws UndefinedParameterError
*/
public static RandomGenerator getRandomGenerator(Operator operator) throws UndefinedParameterError {
if (operator.getParameterAsBoolean(PARAMETER_USE_LOCAL_RANDOM_SEED)) {
return new RandomGenerator(operator.getParameterAsInt(PARAMETER_LOCAL_RANDOM_SEED));
} else {
return GLOBAL_RANDOM_GENERATOR.get();
}
}
// ================================================================================
/**
* Returns the next pseudorandom, uniformly distributed <code>double</code> value between
* <code>lowerBound</code> and <code>upperBound</code> from this random number generator's
* sequence (exclusive of the interval endpoint values).
*
* @throws IllegalArgumentException
* if upperBound < lowerBound
*/
public double nextDoubleInRange(double lowerBound, double upperBound) throws IllegalArgumentException {
if (upperBound < lowerBound) {
throw new IllegalArgumentException("RandomGenerator.nextDoubleInRange : the upper bound of the "
+ "random number range should be greater than the lower bound.");
}
return ((nextDouble() * (upperBound - lowerBound)) + lowerBound);
}
/**
* returns the next pseudorandom, uniformly distributed <code>long</code> value between
* <code>lowerBound</code> and <code>upperBound</code> from this random number generator's
* sequence (exclusive of the interval endpoint values).
*/
public long nextLongInRange(long lowerBound, long upperBound) {
if (upperBound <= lowerBound) {
throw new IllegalArgumentException("RandomGenerator.nextLongInRange : the upper bound of the "
+ "random number range should be greater than the lower bound.");
}
return ((long) (nextDouble() * (upperBound - lowerBound + 1)) + lowerBound);
}
/**
* Returns the next pseudorandom, uniformly distributed <code>int</code> value between
* <code>lowerBound</code> and <code>upperBound</code> from this random number generator's
* sequence (lower bound inclusive, upper bound exclusive).
*/
public int nextIntInRange(int lowerBound, int upperBound) {
if (upperBound <= lowerBound) {
throw new IllegalArgumentException("RandomGenerator.nextIntInRange : the upper bound of the "
+ "random number range should be greater than the lower bound.");
}
return nextInt(upperBound - lowerBound) + lowerBound;
}
/** Returns a random String of the given length. */
public String nextString(int length) {
char[] chars = new char[length];
for (int i = 0; i < chars.length; i++) {
chars[i] = ALPHABET.charAt(nextInt(ALPHABET.length()));
}
return new String(chars);
}
/**
* Returns a randomly selected integer between 0 and the length of the given array. Uses the
* given probabilities to determine the index, all values in this array must sum up to 1.
*/
public int randomIndex(double[] probs) {
double r = nextDouble();
double sum = 0.0d;
for (int i = 0; i < probs.length; i++) {
sum += probs[i];
if (r < sum) {
return i;
}
}
return probs.length - 1;
}
/**
* This method returns a randomly filled array of given length
*
* @param length
* the length of the returned array
* @return the filled array
*/
public double[] nextDoubleArray(int length) {
double[] values = new double[length];
for (int i = 0; i < length; i++) {
values[i] = nextDouble();
}
return values;
}
/** Returns a random date between the given ones. */
public Date nextDateInRange(Date start, Date end) {
return new Date(nextLongInRange(start.getTime(), end.getTime()));
}
/** Returns a set of integer within the given range and given size */
public Set<Integer> nextIntSetWithRange(int lowerBound, int upperBound, int size) {
if (upperBound <= lowerBound) {
throw new IllegalArgumentException("RandomGenerator.nextIntInRange : the upper bound of the "
+ "random number range should be greater than the lower bound.");
}
if ((upperBound - lowerBound) < size) {
throw new IllegalArgumentException(
"RandomGenerator.nextIntInRange : impossible to deliver the desired set of integeres --> range is too small.");
}
Set<Integer> set = new HashSet<>();
while (set.size() < size) {
set.add(nextIntInRange(lowerBound, upperBound));
}
return set;
}
}