/* * Copyright © 2010 by Ondrej Skalicka. All Rights Reserved */ package cz.cvut.felk.cig.jcop.algorithm.simulatedannealing; import cz.cvut.felk.cig.jcop.algorithm.BaseAlgorithm; import cz.cvut.felk.cig.jcop.algorithm.CannotContinueException; import cz.cvut.felk.cig.jcop.algorithm.ChainAlgorithm; import cz.cvut.felk.cig.jcop.algorithm.InvalidProblemException; import cz.cvut.felk.cig.jcop.problem.Configuration; import cz.cvut.felk.cig.jcop.problem.ObjectiveProblem; import cz.cvut.felk.cig.jcop.problem.Operation; import cz.cvut.felk.cig.jcop.util.JcopRandom; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Simulated annealing algorithm. * <p/> * This algorithm is created with starting temperature and anneal coefficient. * In every step of {@link * #init(cz.cvut.felk.cig.jcop.problem.ObjectiveProblem)}, algorithm performs * one randomly selected operation on active configuration. New configuration is * accepted as active iff new configuration is better than current active or it * passes * <p/> * e<sup>-ΔD/T</sup> > Random(0.0, 0.1) * <p/> * equation, where ΔD is difference of normalized fitnesses of active * configuration and new configuration, and T is temperature. * <p/> * Every step temperature is lowered by anneal coefficient, eg. temperature = * temperature * anneal. * * @author Ondrej Skalicka */ public class SimulatedAnnealing extends BaseAlgorithm implements ChainAlgorithm { private static final Logger LOG = LoggerFactory.getLogger(SimulatedAnnealing.class); /** * Active configuration to be expanded */ protected Configuration activeConfiguration; /** * Fitness of active configuration */ protected double activeFitness; /** * Normalized fitness of active configuration */ protected double activeNormalizedFitness; /** * Current temperature */ protected double temperature; /** * Annealing coefficient */ protected double anneal; /** * Backup of {@link #temperature} in case algorithm will be used repeatedly */ protected double startingTemperature; /** * Creates new annealing algorithm instance with given anneal coefficient * and temperature. * * @param anneal annealing coefficient * @param temperature starting temperature */ public SimulatedAnnealing(double temperature, double anneal) { this.startingTemperature = temperature; this.anneal = anneal; } /** * Creates new annealing with default anneal coefficient (0.999) and * starting temperature (10.0). */ public SimulatedAnnealing() { this(10, 0.999); } public void init(ObjectiveProblem problem) throws InvalidProblemException { this.problem = problem; // SA requires either startingConfiguration or RandomStartingConfiguration problem if (!problem.hasStartingConfiguration() && !problem.hasRandomConfiguration()) { throw new InvalidProblemException( "SimulatedAnnealing algorithm requires either StartingConfigurationProblem or RandomConfigurationProblem"); } // fetch starting configuration if (problem.hasStartingConfiguration()) { this.activeConfiguration = problem.getStartingConfiguration(); } else { this.activeConfiguration = problem.getRandomConfiguration(); } this.initCommon(); } public void init(ObjectiveProblem problem, Configuration activeConfiguration) { this.problem = problem; this.activeConfiguration = activeConfiguration; this.initCommon(); } /** * Part of init common to all initialization types. */ protected void initCommon() { this.fitness = problem.getDefaultFitness(); this.temperature = this.startingTemperature; this.setLabel("T=" + this.temperature + ", A=" + this.anneal); this.bestConfiguration = this.activeConfiguration; this.bestFitness = this.fitness.getValue(this.activeConfiguration); // calculate fitness this.activeFitness = this.fitness.getValue(this.activeConfiguration); this.activeNormalizedFitness = this.fitness.normalize(activeFitness); } @Override public void optimize() throws CannotContinueException { // fetch random operation Operation operation = this.problem.getOperationIterator(this.activeConfiguration).getRandomOperation(); if (operation == null) { throw new CannotContinueException("Unable to get random operation"); } // expand to new configuration Configuration newConfiguration = operation.execute(this.activeConfiguration); // calculate fitness double newFitness = this.fitness.getValue(newConfiguration); double newNormalizedFitness = this.fitness.normalize(newFitness); // new configuration is better or passes temperature test double nextDouble = JcopRandom.nextDouble(); double delta = Math.exp((this.activeNormalizedFitness - newNormalizedFitness) / this.temperature); if (this.activeFitness < 0) { if (newFitness > this.activeFitness || nextDouble < delta) { // set new configuration as active this.activeConfiguration = newConfiguration; this.activeFitness = newFitness; this.activeNormalizedFitness = newNormalizedFitness; // if it is best, set it as best if ((newFitness > this.bestFitness && this.bestFitness < 0) || (newFitness < this.bestFitness && this.bestFitness >= 0 && newFitness >= 0)) { LOG.debug("Better solution {}, {}", newFitness, newConfiguration); this.bestConfiguration = newConfiguration; this.bestFitness = newFitness; } } } else { if ((newFitness < this.bestFitness && newFitness >= 0) || nextDouble < delta) { // set new configuration as active this.activeConfiguration = newConfiguration; this.activeFitness = newFitness; this.activeNormalizedFitness = newNormalizedFitness; // if it is best, set it as best if (newFitness < this.bestFitness && newFitness >= 0) { LOG.debug("Better solution {}, {}", newFitness, newConfiguration); this.bestConfiguration = newConfiguration; this.bestFitness = newFitness; } } } // anneal this.temperature *= this.anneal; } }