package bsearch.algorithms; import java.util.HashMap; import org.nlogo.util.MersenneTwisterFast; import bsearch.app.BehaviorSearchException; import bsearch.app.SearchProtocol; import bsearch.evaluation.SearchManager; import bsearch.nlogolink.NetLogoLinkException; import bsearch.representations.Chromosome; import bsearch.representations.ChromosomeFactory; import bsearch.space.SearchSpace; public strictfp class SimulatedAnnealing extends AbstractSearchMethod { double mutationRate = 0.05; int restartAfterStallCount = 0; double initialTemperature = 1.0; double temperatureChangeFactor = 0.99; public SimulatedAnnealing() { } public String getName() { return "SimulatedAnnealing"; } public String getDescription() { return "Simulated Annealing is similar to a hill climbing approach, except that a downhill (inferior) move may also occur, but only with a certain probability based on the 'temperature' of the system, which decreases over time." + "Simulated annealing is inspired by the physical annealing process in metallurgy: heating followed by the controlled cooling of a material in order to increase crystal size."; } public void setSearchParams( HashMap<String , String> searchMethodParams ) throws SearchParameterException { mutationRate = validDoubleParam(searchMethodParams, "mutation-rate", 0.0, 1.0); restartAfterStallCount = validIntParam(searchMethodParams, "restart-after-stall-count", 0, 1000); initialTemperature = validDoubleParam(searchMethodParams, "initial-temperature", 0, Double.MAX_VALUE); temperatureChangeFactor = validDoubleParam(searchMethodParams, "temperature-change-factor", 0, 1.0); } public HashMap<String , String> getSearchParams() { HashMap<String,String> params = new HashMap<String,String>(); params.put("mutation-rate", Double.toString( mutationRate )); params.put("restart-after-stall-count", Integer.toString( restartAfterStallCount )); params.put("initial-temperature", Double.toString( initialTemperature)); params.put("temperature-change-factor", Double.toString( temperatureChangeFactor )); return params; } public HashMap<String , String> getSearchParamsHelp() { HashMap<String,String> params = new HashMap<String,String>(); params.put("mutation-rate", "controls how much mutation occurs when choosing a new location to try to climb uphill"); params.put("restart-after-stall-count", "if it doesn't move to a new location after X attempts, reset the temperature, jump to a random location in the search space and try again."); params.put("initial-temperature", "the system's initial 'temperature': a reasonable choice would be the average expected difference in the fitness function's value for two random points in the search space."); params.put("temperature-change-factor", "the system's current 'temperature' is multiplied by this factor (which needs to be less than 1!) after each move. (Using this exponential temperature decay means that temperature will approach 0 over time.)"); return params; } boolean acceptChange(double currentFitness, double candidateFitness, double temperature, SearchManager manager, MersenneTwisterFast rng ) { if (manager.fitnessStrictlyBetter(candidateFitness, currentFitness)) { return true; } if (temperature <= 0.0) { return false; } double fitnessDiff = StrictMath.abs(candidateFitness - currentFitness); return (rng.nextDouble() < StrictMath.exp(-fitnessDiff / temperature)); } public void search( SearchSpace space , ChromosomeFactory cFactory, SearchProtocol protocol, SearchManager manager, MersenneTwisterFast rng ) throws BehaviorSearchException, NetLogoLinkException, InterruptedException { double temperature = initialTemperature ; while (!manager.searchFinished()) { Chromosome current = cFactory.createChromosome(space, rng); double currentFitness = manager.computeFitnessSingle(current, protocol.fitnessSamplingReplications, rng); int stallCount = 0; while (!manager.searchFinished()) { // Use mutation to find a different point to evaluate Chromosome candidate = current.mutate(mutationRate, rng); int failedMutationCounter = 0; final int MAX_MUTATION_ATTEMPTS = 1000000; while (candidate.equals(current) && failedMutationCounter < MAX_MUTATION_ATTEMPTS ) { candidate = current.mutate(mutationRate, rng); } if (failedMutationCounter == MAX_MUTATION_ATTEMPTS) { throw new BehaviorSearchException("An extremely large number of mutation attempts all resulted in no mutation - perhaps your mutation-rate is too low?"); } double candidateFitness = manager.computeFitnessSingle(candidate, protocol.fitnessSamplingReplications, rng); if (acceptChange(currentFitness, candidateFitness, temperature, manager, rng)) { current = candidate; currentFitness = candidateFitness; temperature = temperature * temperatureChangeFactor; } else { stallCount++; // if we haven't made any progress after a specified amount of time, // start over in a new random location (and reset the temperature) if (restartAfterStallCount > 0 && stallCount >= restartAfterStallCount) { temperature = initialTemperature ; break; } } } } } }