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    }