aima.core.search.local.GeneticAlgorithm Maven / Gradle / Ivy
package aima.core.search.local;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import aima.core.search.framework.Metrics;
import aima.core.search.framework.problem.GoalTest;
import aima.core.util.CancelableThread;
import aima.core.util.Util;
/**
* Artificial Intelligence A Modern Approach (3rd Edition): Figure 4.8, page
* 129.
*
*
*
* function GENETIC-ALGORITHM(population, FITNESS-FN) returns an individual
* inputs: population, a set of individuals
* FITNESS-FN, a function that measures the fitness of an individual
*
* repeat
* new_population <- empty set
* for i = 1 to SIZE(population) do
* x <- RANDOM-SELECTION(population, FITNESS-FN)
* y <- RANDOM-SELECTION(population, FITNESS-FN)
* child <- REPRODUCE(x, y)
* if (small random probability) then child <- MUTATE(child)
* add child to new_population
* population <- new_population
* until some individual is fit enough, or enough time has elapsed
* return the best individual in population, according to FITNESS-FN
* --------------------------------------------------------------------------------
* function REPRODUCE(x, y) returns an individual
* inputs: x, y, parent individuals
*
* n <- LENGTH(x); c <- random number from 1 to n
* return APPEND(SUBSTRING(x, 1, c), SUBSTRING(y, c+1, n))
*
*
* Figure 4.8 A genetic algorithm. The algorithm is the same as the one
* diagrammed in Figure 4.6, with one variation: in this more popular version,
* each mating of two parents produces only one offspring, not two.
*
* @author Ciaran O'Reilly
* @author Mike Stampone
* @author Ruediger Lunde
*
* @param
* the type of the alphabet used in the representation of the
* individuals in the population (this is to provide flexibility in
* terms of how a problem can be encoded).
*/
public class GeneticAlgorithm {
protected static final String POPULATION_SIZE = "populationSize";
protected static final String ITERATIONS = "iterations";
protected static final String TIME_IN_MILLISECONDS = "timeInMSec";
//
protected Metrics metrics = new Metrics();
//
protected int individualLength;
protected List finiteAlphabet;
protected double mutationProbability;
protected Random random;
private List> progressTracers = new ArrayList>();
public GeneticAlgorithm(int individualLength, Collection finiteAlphabet, double mutationProbability) {
this(individualLength, finiteAlphabet, mutationProbability, new Random());
}
public GeneticAlgorithm(int individualLength, Collection finiteAlphabet, double mutationProbability,
Random random) {
this.individualLength = individualLength;
this.finiteAlphabet = new ArrayList(finiteAlphabet);
this.mutationProbability = mutationProbability;
this.random = random;
assert (this.mutationProbability >= 0.0 && this.mutationProbability <= 1.0);
}
/** Progress tracers can be used to display progress information. */
public void addProgressTracer(ProgressTracer pTracer) {
progressTracers.add(pTracer);
}
/**
* Starts the genetic algorithm and stops after a specified number of
* iterations.
*/
public Individual geneticAlgorithm(Collection> initPopulation,
FitnessFunction fitnessFn, final int maxIterations) {
GoalTest goalTest = new GoalTest() {
@Override
public boolean isGoalState(Object state) {
return getIterations() >= maxIterations;
}};
return geneticAlgorithm(initPopulation, fitnessFn, goalTest, 0L);
}
/**
* Template method controlling search. It returns the best individual in the
* specified population, according to the specified FITNESS-FN and goal
* test.
*
* @param population
* a set of individuals
* @param fitnessFn
* a function that measures the fitness of an individual
* @param goalTest
* test determines whether a given individual is fit enough to
* return. Can be used in subclasses to implement additional
* termination criteria, e.g. maximum number of iterations.
* @param maxTimeMilliseconds
* the maximum time in milliseconds that the algorithm is to run
* for (approximate). Only used if > 0L.
* @return the best individual in the specified population, according to the
* specified FITNESS-FN and goal test.
*/
// function GENETIC-ALGORITHM(population, FITNESS-FN) returns an individual
// inputs: population, a set of individuals
// FITNESS-FN, a function that measures the fitness of an individual
public Individual geneticAlgorithm(Collection> initPopulation, FitnessFunction fitnessFn,
GoalTest goalTest, long maxTimeMilliseconds) {
Individual bestIndividual = null;
// Create a local copy of the population to work with
List> population = new ArrayList>(initPopulation);
// Validate the population and setup the instrumentation
validatePopulation(population);
updateMetrics(population, 0, 0L);
long startTime = System.currentTimeMillis();
// repeat
int itCount = 0;
do {
population = nextGeneration(population, fitnessFn);
bestIndividual = retrieveBestIndividual(population, fitnessFn);
updateMetrics(population, ++itCount, System.currentTimeMillis() - startTime);
// until some individual is fit enough, or enough time has elapsed
if (maxTimeMilliseconds > 0L && (System.currentTimeMillis() - startTime) > maxTimeMilliseconds)
break;
if (CancelableThread.currIsCanceled())
break;
} while (!goalTest.isGoalState(bestIndividual));
notifyProgressTracers(itCount, population);
// return the best individual in population, according to FITNESS-FN
return bestIndividual;
}
public Individual retrieveBestIndividual(Collection> population, FitnessFunction fitnessFn) {
Individual bestIndividual = null;
double bestSoFarFValue = Double.NEGATIVE_INFINITY;
for (Individual individual : population) {
double fValue = fitnessFn.apply(individual);
if (fValue > bestSoFarFValue) {
bestIndividual = individual;
bestSoFarFValue = fValue;
}
}
return bestIndividual;
}
/**
* Sets the population size and number of iterations to zero.
*/
public void clearInstrumentation() {
updateMetrics(new ArrayList>(), 0, 0L);
}
/**
* Returns all the metrics of the genetic algorithm.
*
* @return all the metrics of the genetic algorithm.
*/
public Metrics getMetrics() {
return metrics;
}
/**
* Returns the population size.
*
* @return the population size.
*/
public int getPopulationSize() {
return metrics.getInt(POPULATION_SIZE);
}
/**
* Returns the number of iterations of the genetic algorithm.
*
* @return the number of iterations of the genetic algorithm.
*/
public int getIterations() {
return metrics.getInt(ITERATIONS);
}
/**
*
* @return the time in milliseconds that the genetic algorithm took.
*/
public long getTimeInMilliseconds() {
return metrics.getLong(TIME_IN_MILLISECONDS);
}
/**
* Updates statistic data collected during search.
*
* @param itCount
* the number of iterations.
* @param time
* the time in milliseconds that the genetic algorithm took.
*/
protected void updateMetrics(Collection> population, int itCount, long time) {
metrics.set(POPULATION_SIZE, population.size());
metrics.set(ITERATIONS, itCount);
metrics.set(TIME_IN_MILLISECONDS, time);
}
//
// PROTECTED METHODS
//
// Note: Override these protected methods to create your own desired
// behavior.
//
/**
* Primitive operation which is responsible for creating the next
* generation. Override to get progress information!
*/
protected List> nextGeneration(List> population, FitnessFunction fitnessFn) {
// new_population <- empty set
List> newPopulation = new ArrayList>(population.size());
// for i = 1 to SIZE(population) do
for (int i = 0; i < population.size(); i++) {
// x <- RANDOM-SELECTION(population, FITNESS-FN)
Individual x = randomSelection(population, fitnessFn);
// y <- RANDOM-SELECTION(population, FITNESS-FN)
Individual y = randomSelection(population, fitnessFn);
// child <- REPRODUCE(x, y)
Individual child = reproduce(x, y);
// if (small random probability) then child <- MUTATE(child)
if (random.nextDouble() <= mutationProbability) {
child = mutate(child);
}
// add child to new_population
newPopulation.add(child);
}
notifyProgressTracers(getIterations(), population);
return newPopulation;
}
// RANDOM-SELECTION(population, FITNESS-FN)
protected Individual randomSelection(List> population, FitnessFunction fitnessFn) {
// Default result is last individual
// (just to avoid problems with rounding errors)
Individual selected = population.get(population.size() - 1);
// Determine all of the fitness values
double[] fValues = new double[population.size()];
for (int i = 0; i < population.size(); i++) {
fValues[i] = fitnessFn.apply(population.get(i));
}
// Normalize the fitness values
fValues = Util.normalize(fValues);
double prob = random.nextDouble();
double totalSoFar = 0.0;
for (int i = 0; i < fValues.length; i++) {
// Are at last element so assign by default
// in case there are rounding issues with the normalized values
totalSoFar += fValues[i];
if (prob <= totalSoFar) {
selected = population.get(i);
break;
}
}
selected.incDescendants();
return selected;
}
// function REPRODUCE(x, y) returns an individual
// inputs: x, y, parent individuals
protected Individual reproduce(Individual x, Individual y) {
// n <- LENGTH(x);
// Note: this is = this.individualLength
// c <- random number from 1 to n
int c = randomOffset(individualLength);
// return APPEND(SUBSTRING(x, 1, c), SUBSTRING(y, c+1, n))
List childRepresentation = new ArrayList();
childRepresentation.addAll(x.getRepresentation().subList(0, c));
childRepresentation.addAll(y.getRepresentation().subList(c, individualLength));
Individual child = new Individual(childRepresentation);
return child;
}
protected Individual mutate(Individual child) {
int mutateOffset = randomOffset(individualLength);
int alphaOffset = randomOffset(finiteAlphabet.size());
List mutatedRepresentation = new ArrayList(child.getRepresentation());
mutatedRepresentation.set(mutateOffset, finiteAlphabet.get(alphaOffset));
Individual mutatedChild = new Individual(mutatedRepresentation);
return mutatedChild;
}
protected int randomOffset(int length) {
return random.nextInt(length);
}
protected void validatePopulation(Collection> population) {
// Require at least 1 individual in population in order
// for algorithm to work
if (population.size() < 1) {
throw new IllegalArgumentException("Must start with at least a population of size 1");
}
// String lengths are assumed to be of fixed size,
// therefore ensure initial populations lengths correspond to this
for (Individual individual : population) {
if (individual.length() != this.individualLength) {
throw new IllegalArgumentException("Individual [" + individual
+ "] in population is not the required length of " + this.individualLength);
}
}
}
private void notifyProgressTracers(int itCount, Collection> generation) {
for (ProgressTracer tracer : progressTracers)
tracer.traceProgress(getIterations(), generation);
}
/**
* Interface for progress tracers.
*
* @author Ruediger Lunde
*/
public interface ProgressTracer {
void traceProgress(int itCount, Collection> population);
}
}