package aima.core.search.local; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Random; import java.util.Set; import aima.core.search.framework.GoalTest; import aima.core.search.framework.Metrics; 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 * * @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 = "timeInMilliseconds"; // protected Metrics metrics = new Metrics(); // protected int individualLength; protected List finiteAlphabet; protected double mutationProbability; protected Random random; public GeneticAlgorithm(int individualLength, Set finiteAlphabet, double mutationProbability) { this(individualLength, finiteAlphabet, mutationProbability, new Random()); } public GeneticAlgorithm(int individualLength, Set 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); } /** * 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. * @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(Set> population, FitnessFunction fitnessFn, GoalTest goalTest, long maxTimeMilliseconds) { Individual bestIndividual = null; // Create a local copy of the population to work with population = new LinkedHashSet>(population); // Validate the population and setup the instrumentation validatePopulation(population); clearInstrumentation(); setPopulationSize(population.size()); long startTime = System.currentTimeMillis(); // repeat int cnt = 0; do { bestIndividual = nextGeneration(population, fitnessFn); cnt++; // until some individual is fit enough, or enough time has elapsed if (maxTimeMilliseconds > 0L) { if ((System.currentTimeMillis() - startTime) > maxTimeMilliseconds) { break; } } } while (!goalTest.isGoalState(bestIndividual)); setIterations(cnt); setTimeInMilliseconds(System.currentTimeMillis()-startTime); // return the best individual in population, according to FITNESS-FN return bestIndividual; } /** * Sets the population size and number of iterations to zero. */ public void clearInstrumentation() { setPopulationSize(0); setIterations(0); setTimeInMilliseconds(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); } /** * Sets the population size. * * @param size * the population size. */ public void setPopulationSize(int size) { metrics.set(POPULATION_SIZE, 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); } /** * Sets the number of iterations of the genetic algorithm. * * @param cnt * the number of iterations. */ public void setIterations(int cnt) { metrics.set(ITERATIONS, cnt); } /** * * @return the time in milliseconds that the genetic algorithm took. */ public long getTimeInMilliseconds() { return metrics.getLong(TIME_IN_MILLISECONDS); } /** * Set the time in milliseconds that the genetic algorithm took. * * @param time * the time in milliseconds that the genetic algorithm took. */ public void setTimeInMilliseconds(long time) { metrics.set(TIME_IN_MILLISECONDS, time); } // // PROTECTED METHODS // // Note: Override these protected methods to create your own desired // behavior. // protected Individual nextGeneration(Set> population, FitnessFunction fitnessFn) { // new_population <- empty set Set> newPopulation = new HashSet>(); // Represent the population as a list to simplify/optimize random // selection. List> populationAsList = new ArrayList>( population); // for i = 1 to SIZE(population) do for (int i = 0; i < population.size(); i++) { // x <- RANDOM-SELECTION(population, FITNESS-FN) Individual x = randomSelection(populationAsList, fitnessFn); // y <- RANDOM-SELECTION(population, FITNESS-FN) Individual y = randomSelection(populationAsList, fitnessFn); // child <- REPRODUCE(x, y) Individual child = reproduce(x, y); // if (small random probability) then child <- MUTATE(child) if (random.nextDouble() <= this.mutationProbability) { child = mutate(child); } // add child to new_population newPopulation.add(child); } // population <- new_population population.clear(); population.addAll(newPopulation); return retrieveBestIndividual(population, fitnessFn); } // RANDOM-SELECTION(population, FITNESS-FN) protected Individual randomSelection(List> population, FitnessFunction fitnessFn) { Individual selected = null; // Determine all of the fitness values double[] fValues = new double[population.size()]; for (int i = 0; i < population.size(); i++) { fValues[i] = fitnessFn.getValue(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 may not have been assigned // if there was a rounding error in the // addition of the normalized values (i.e. did not total to 1.0) if (null == selected) { // Assign the last value selected = population.get(population.size() - 1); } 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 Individual retrieveBestIndividual( Set> population, FitnessFunction fitnessFn) { Individual bestIndividual = null; double bestSoFarFValue = Double.NEGATIVE_INFINITY; for (Individual individual : population) { double fValue = fitnessFn.getValue(individual); if (fValue > bestSoFarFValue) { bestIndividual = individual; bestSoFarFValue = fValue; } } return bestIndividual; } protected int randomOffset(int length) { return random.nextInt(length); } protected void validatePopulation(Set> 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); } } } }