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;
}
}
}
}
}
}