001 package aima.search.informed.ga; 002 003 import java.util.HashSet; 004 import java.util.Random; 005 import java.util.Set; 006 007 import aima.search.framework.GoalTest; 008 import aima.search.framework.Metrics; 009 import aima.util.Util; 010 011 /** 012 * Artificial Intelligence A Modern Approach (2nd Edition): Figure 4.17, page 119. 013 * 014 * <code> 015 * function GENETIC-ALGORITHM(population, FITNESS-FN) returns an individual 016 * inputs: population, a set of individuals 017 * FITNESS-FN, a function that measures the fitness of an individual 018 * 019 * repeat 020 * new_population <- empty set 021 * loop for i from 1 to SIZE(population) do 022 * x <- RANDOM-SELECTION(population, FITNESS-FN) 023 * y <- RANDOM-SELECTION(population, FITNESS-FN) 024 * child <- REPRODUCE(x, y) 025 * if (small random probability) then child <- MUTATE(child) 026 * add child to new_population 027 * population <- new_population 028 * until some individual is fit enough, or enough time has elapsed 029 * return the best individual in population, according to FITNESS-FN 030 * -------------------------------------------------------------------------------- 031 * function REPRODUCE(x, y) returns an individual 032 * inputs: x, y, parent individuals 033 * 034 * n <- LENGTH(x) 035 * c <- random number from 1 to n 036 * return APPEND(SUBSTRING(x, 1, c), SUBSTRING(y, c+1, n)) 037 * </code> 038 * 039 * Figure 4.17 A genetic algorithm. The algorithm is the same as the one diagrammed 040 * in Figure 4.15, with one variation: in this more popular version, each mating of 041 * two parents produces only one offspring, not two. 042 */ 043 044 /** 045 * @author Ciaran O'Reilly 046 * 047 */ 048 public class GeneticAlgorithm { 049 // 050 protected Metrics metrics = new Metrics(); 051 protected static final String POPULATION_SIZE = "populationSize"; 052 protected static final String ITERATIONS = "iterations"; 053 054 // 055 private final int individualLength; 056 private final Character[] finiteAlphabet; 057 private final double mutationProbability; 058 private final Random random = new Random(); 059 060 public GeneticAlgorithm(int individualLength, 061 Set<Character> finiteAlphabet, double mutationProbability) { 062 this.individualLength = individualLength; 063 this.finiteAlphabet = finiteAlphabet 064 .toArray(new Character[finiteAlphabet.size()]); 065 this.mutationProbability = mutationProbability; 066 assert (this.mutationProbability >= 0.0 && this.mutationProbability <= 1.0); 067 } 068 069 // function GENETIC-ALGORITHM(population, FITNESS-FN) returns an individual 070 // inputs: population, a set of individuals 071 // FITNESS-FN, a function that measures the fitness of an individual 072 public String geneticAlgorithm(Set<String> population, 073 FitnessFunction fitnessFn, GoalTest goalTest) { 074 String bestIndividual = null; 075 076 validatePopulation(population); 077 clearInstrumentation(); 078 setPopulationSize(population.size()); 079 080 // repeat 081 int cnt = 0; 082 do { 083 bestIndividual = ga(population, fitnessFn); 084 cnt++; 085 // until some individual is fit enough, or enough time has elapsed 086 } while (!goalTest.isGoalState(bestIndividual)); 087 setIterations(cnt); 088 089 // return the best individual in population, according to FITNESS-FN 090 return bestIndividual; 091 } 092 093 // function GENETIC-ALGORITHM(population, FITNESS-FN) returns an individual 094 // inputs: population, a set of individuals 095 // FITNESS-FN, a function that measures the fitness of an individual 096 public String geneticAlgorithm(Set<String> population, 097 FitnessFunction fitnessFn, int iterations) { 098 String bestIndividual = null; 099 100 validatePopulation(population); 101 clearInstrumentation(); 102 setPopulationSize(population.size()); 103 104 // repeat 105 // until some individual is fit enough, or enough time has elapsed 106 for (int i = 0; i < iterations; i++) { 107 bestIndividual = ga(population, fitnessFn); 108 } 109 setIterations(iterations); 110 111 // return the best individual in population, according to FITNESS-FN 112 return bestIndividual; 113 } 114 115 public void clearInstrumentation() { 116 setPopulationSize(0); 117 setIterations(0); 118 } 119 120 public Metrics getMetrics() { 121 return metrics; 122 } 123 124 public int getPopulationSize() { 125 return metrics.getInt(POPULATION_SIZE); 126 } 127 128 public void setPopulationSize(int size) { 129 metrics.set(POPULATION_SIZE, size); 130 } 131 132 public int getIterations() { 133 return metrics.getInt(ITERATIONS); 134 } 135 136 public void setIterations(int cnt) { 137 metrics.set(ITERATIONS, cnt); 138 } 139 140 // 141 // PRIVATE METHODS 142 // 143 private void validatePopulation(Set<String> population) { 144 // Require at least 1 individual in population in order 145 // for algorithm to work 146 assert (population.size() >= 1); 147 // String lengths are assumed to be of fixed size, 148 // therefore ensure initial populations lengths correspond to this 149 for (String individual : population) { 150 assert (individual.length() == this.individualLength); 151 } 152 } 153 154 private String ga(Set<String> population, FitnessFunction fitnessFn) { 155 // new_population <- empty set 156 Set<String> newPopulation = new HashSet<String>(); 157 158 // loop for i from 1 to SIZE(population) do 159 for (int i = 0; i < population.size(); i++) { 160 // x <- RANDOM-SELECTION(population, FITNESS-FN) 161 String x = randomSelection(population, fitnessFn); 162 // y <- RANDOM-SELECTION(population, FITNESS-FN) 163 String y = randomSelection(population, fitnessFn); 164 // child <- REPRODUCE(x, y) 165 String child = reproduce(x, y); 166 // if (small random probability) then child <- MUTATE(child) 167 if (random.nextDouble() <= this.mutationProbability) { 168 child = mutate(child); 169 } 170 // add child to new_population 171 newPopulation.add(child); 172 } 173 // population <- new_population 174 population.clear(); 175 population.addAll(newPopulation); 176 177 return retrieveBestIndividual(population, fitnessFn); 178 } 179 180 private String randomSelection(Set<String> population, 181 FitnessFunction fitnessFn) { 182 String selected = null; 183 184 // Determine all of the fitness values 185 double[] fValues = new double[population.size()]; 186 String[] popArray = population.toArray(new String[population.size()]); 187 for (int i = 0; i < popArray.length; i++) { 188 fValues[i] = fitnessFn.getValue(popArray[i]); 189 } 190 191 // Normalize the fitness values 192 fValues = Util.normalize(fValues); 193 double prob = random.nextDouble(); 194 double totalSoFar = 0.0; 195 for (int i = 0; i < fValues.length; i++) { 196 // Are at last element so assign by default 197 // in case there are rounding issues with the normalized values 198 totalSoFar += fValues[i]; 199 if (prob <= totalSoFar) { 200 selected = popArray[i]; 201 break; 202 } 203 } 204 205 // selected may not have been assigned 206 // if there was a rounding error in the 207 // addition of the normalized values (i.e. did not total to 1.0) 208 if (null == selected) { 209 // Assign the last value 210 selected = popArray[popArray.length - 1]; 211 } 212 213 return selected; 214 } 215 216 // function REPRODUCE(x, y) returns an individual 217 // inputs: x, y, parent individuals 218 private String reproduce(String x, String y) { 219 // n <- LENGTH(x) 220 // Note: this is = this.individualLength 221 // c <- random number from 1 to n 222 int c = randomOffset(individualLength); 223 // return APPEND(SUBSTRING(x, 1, c), SUBSTRING(y, c+1, n)) 224 return x.substring(0, c) + y.substring(c); 225 } 226 227 private String mutate(String individual) { 228 StringBuffer mutInd = new StringBuffer(individual); 229 230 int posOffset = randomOffset(individualLength); 231 int charOffset = randomOffset(finiteAlphabet.length); 232 233 mutInd.setCharAt(posOffset, finiteAlphabet[charOffset]); 234 235 return mutInd.toString(); 236 } 237 238 private String retrieveBestIndividual(Set<String> population, 239 FitnessFunction fitnessFn) { 240 String bestIndividual = null; 241 double bestSoFarFValue = Double.MIN_VALUE; 242 243 for (String individual : population) { 244 double fValue = fitnessFn.getValue(individual); 245 if (fValue > bestSoFarFValue) { 246 bestIndividual = individual; 247 bestSoFarFValue = fValue; 248 } 249 } 250 251 return bestIndividual; 252 } 253 254 private int randomOffset(int length) { 255 return random.nextInt(length); 256 } 257 }