/** * Copyright (C) 2010-2017 Gordon Fraser, Andrea Arcuri and EvoSuite * contributors * * This file is part of EvoSuite. * * EvoSuite is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3.0 of the License, or * (at your option) any later version. * * EvoSuite 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 * Lesser Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>. */ package org.evosuite.lm; import org.evosuite.testcase.ValueMinimizer; import org.evosuite.testcase.variable.ConstantValue; import org.evosuite.utils.Randomness; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; /** * Created by mat on 02/04/2014. */ public class LanguageModelGA extends LanguageModelSearch { private static final int TOURNAMENT_SIZE = 7; private static final double MUTATION_RATE = 0.15; private static final double CROSSOVER_RATE = 0.8; private static final double ELITIST_RATE = 0.3; private static final double REFILL_LEVEL = 1; private HashSet<Chromosome> population; private static int POPULATION_SIZE = 20; private static Logger logger = LoggerFactory.getLogger(LanguageModelGA.class); public LanguageModelGA(ConstantValue constantValue, ValueMinimizer.Minimization objective){ super(objective, constantValue); population = new HashSet<>(); setupPopulation(); } /** * Seed the population with one instance of the original string, plus * a population of random individuals. */ private void setupPopulation(){ Chromosome originalValue = new Chromosome(startPoint); population.add(originalValue); while(population.size() < POPULATION_SIZE - 1){ population.add(mutate(originalValue)); //population.add(mutate(originalValue)); } } private Chromosome select(){ return select(population, true); } private Chromosome select(HashSet<Chromosome> pool){ return select(pool, true); } private Chromosome select(HashSet<Chromosome> pool, boolean best){ ArrayList<Chromosome> sample = new ArrayList<>(); //Chose a random sample from the population (duplicates are handled by using a HashSet): while(sample.size() < TOURNAMENT_SIZE && sample.size() < pool.size()){ sample.add(Randomness.choice(pool)); } assert !sample.isEmpty(); if(best) { Collections.sort(sample, Collections.reverseOrder(this)); return sample.get(0); }else{ Collections.sort(sample,this); return sample.get(0); } } private HashSet<Chromosome> generation(HashSet<Chromosome> newPopulation){ int numMutants = 0; int numCrossovers = 0; int numElites = 0; for(int crossover =0; crossover < population.size(); crossover++){ if(Randomness.nextDouble() < CROSSOVER_RATE){ Chromosome parent1 = select(); Chromosome parent2 = select(); if(parent1 != parent2) { if(newPopulation.addAll(crossover(parent1, parent2))) { numCrossovers++; } } } } int numElitesToSelect = (int)Math.round(ELITIST_RATE * population.size()); //copy some elites over: ArrayList<Chromosome> rankedPopulation = new ArrayList<Chromosome>(population.size()); rankedPopulation.addAll(population); Collections.sort(rankedPopulation,Collections.reverseOrder(this)); for(Chromosome elite : rankedPopulation) { if (numElitesToSelect > 0) { if(newPopulation.add(elite)) { numElites++; numElitesToSelect--; } } else { break; } } //mutation for(int mutant = 0; mutant < population.size(); mutant++) { if(Randomness.nextDouble() < MUTATION_RATE) { if(newPopulation.add(mutate(select(newPopulation)))) numMutants++; } } //cull the weakest individuals to keep pop size under control: if(newPopulation.size() > POPULATION_SIZE){ logger.debug("Removing some weak individuals from old population"); } int numRemoved = 0; while(newPopulation.size() > POPULATION_SIZE){ Chromosome item = select(newPopulation, false); assert item != null; assert newPopulation.contains(item); newPopulation.remove(item); numRemoved++; } int numAdded = 0; //it's also possible that we haven't got enough individuals //if that's the case, randomly pull some from the previous generation if(newPopulation.size() < POPULATION_SIZE * REFILL_LEVEL){ HashSet<Chromosome> oldPopulation = new HashSet<>(population); logger.debug("Pulling individuals from old population (size = {})", newPopulation.size()); while(newPopulation.size() < POPULATION_SIZE * REFILL_LEVEL && !oldPopulation.isEmpty()) { Chromosome individual = Randomness.choice(oldPopulation); oldPopulation.remove(individual); if(newPopulation.add(individual)) { numAdded++; } } //have we done enough, or do we need to add more individuals? if(newPopulation.size() < POPULATION_SIZE * REFILL_LEVEL){ logger.debug("Adding random mutants to keep pop size up (current size is {})", newPopulation.size()); } while(newPopulation.size() < POPULATION_SIZE * REFILL_LEVEL && !population.isEmpty()){ newPopulation.add(mutate(Randomness.choice(population))); } } logger.debug("Finished a GA generation. Created {} mutants, performed {} crossovers and preserved {} elites on a population of {} (-{},+{}) New pop: {}", numMutants, numCrossovers, numElites, population.size(), numRemoved, numAdded, newPopulation.size()); return newPopulation; } protected Chromosome getBest(HashSet<Chromosome> population, Chromosome best){ for(Chromosome individual : population){ try { if (individual.compareTo(best) > 0) { best = individual; } }catch(EvaluationBudgetExpendedException e){ logger.debug("Evaluation limit hit by GA; will select best from evaluated population."); break; } } return best; } @Override public String optimise(){ resetEvaluationCounter(); Chromosome best = getBest(population, null); for(int generation = 0; generation < GENERATIONS && !isBudgetExpended(); generation++){ HashSet<Chromosome> newPopulation = new HashSet<Chromosome>(); try{ generation(newPopulation); if(newPopulation.isEmpty()){ assert isBudgetExpended(); //If we successfully ran a generation (didn't run out of evals) but didn't get any individuals, // we've had problems. throw new RuntimeException("LM GA: New population is empty"); } }catch(EvaluationBudgetExpendedException e){ //For now, we just ignore the exception. At this stage, the generation failed, but there might be chromosomes // that made it into the new population. //At the next iteration of the loop, we'll see if any of those were any better than the current best individual. logger.debug("Couldn't finish a generation, ran out of evaluations."); } logger.debug("LM GA: Generation {} of {}. Population size is {} (previously {}). Best fitness is [{}], fitness: {}", generation, GENERATIONS, newPopulation.size(), population.size(), best.getValue(), best.getFitness()); /*for(Chromosome c : population){ logger.debug("Gen {} Chromosome [{}] Fitness {}", generation, c.getValue(), c.getFitness()); }*/ population = newPopulation; best = getBest(population, best); } return best.getValue(); } protected List<? extends Chromosome> crossover(Chromosome parent1, Chromosome parent2) { String p1 = parent1.getValue(); String p2 = parent2.getValue(); int splitPoint = Randomness.nextInt(0,p1.length()); String c1 = p1.substring(0,splitPoint) + p2.substring(splitPoint); String c2 = p2.substring(0,splitPoint) + p1.substring(splitPoint); Chromosome child1 = new Chromosome(c1); Chromosome child2 = new Chromosome(c2); return Arrays.asList(child1, child2); } }