001 package net.sf.cpsolver.exam.heuristics; 002 003 import java.text.DecimalFormat; 004 005 import net.sf.cpsolver.exam.neighbours.ExamRandomMove; 006 import net.sf.cpsolver.exam.neighbours.ExamRoomMove; 007 import net.sf.cpsolver.exam.neighbours.ExamSimpleNeighbour; 008 import net.sf.cpsolver.exam.neighbours.ExamTimeMove; 009 import net.sf.cpsolver.ifs.heuristics.NeighbourSelection; 010 import net.sf.cpsolver.ifs.model.Neighbour; 011 import net.sf.cpsolver.ifs.solution.Solution; 012 import net.sf.cpsolver.ifs.solution.SolutionListener; 013 import net.sf.cpsolver.ifs.solver.Solver; 014 import net.sf.cpsolver.ifs.util.DataProperties; 015 import net.sf.cpsolver.ifs.util.Progress; 016 import net.sf.cpsolver.ifs.util.ToolBox; 017 018 019 import org.apache.log4j.Logger; 020 021 /** 022 * Simulated annealing. In each iteration, one of the following three neighbourhoods is selected first 023 * <ul> 024 * <li>random move ({@link ExamRandomMove}) 025 * <li>period swap ({@link ExamTimeMove}) 026 * <li>room swap ({@link ExamRoomMove}) 027 * </ul>, 028 * then a neighbour is generated and it is accepted with probability {@link ExamSimulatedAnnealing#prob(double)}. 029 * The search is guided by the temperature, which starts at <i>SimulatedAnnealing.InitialTemperature</i>. 030 * After each <i>SimulatedAnnealing.TemperatureLength</i> iterations, the temperature is reduced 031 * by <i>SimulatedAnnealing.CoolingRate</i>. If there was no improvement in the past 032 * <i>SimulatedAnnealing.ReheatLengthCoef * SimulatedAnnealing.TemperatureLength</i> iterations, 033 * the temperature is increased by <i>SimulatedAnnealing.ReheatRate</i>. 034 * If there was no improvement in the past 035 * <i>SimulatedAnnealing.RestoreBestLengthCoef * SimulatedAnnealing.TemperatureLength</i> iterations, 036 * the best ever found solution is restored. 037 * <br><br> 038 * If <i>SimulatedAnnealing.StochasticHC</i> is true, the acceptance probability is computed using 039 * stochastic hill climbing criterion, i.e., <code>1.0 / (1.0 + Math.exp(value/temperature))</code>, 040 * otherwise it is cumputed using simlated annealing criterion, i.e., 041 * <code>(value<=0.0?1.0:Math.exp(-value/temperature))</code>. 042 * If <i>SimulatedAnnealing.RelativeAcceptance</i> neighbour value {@link ExamSimpleNeighbour#value()} is taken 043 * as the value of the selected neighbour (difference between the new and the current solution, if the 044 * neighbour is accepted), otherwise the value is computed as the difference 045 * between the value of the current solution if the neighbour is accepted and the best ever found solution. 046 * <br><br> 047 * 048 * @version 049 * ExamTT 1.1 (Examination Timetabling)<br> 050 * Copyright (C) 2008 Tomáš Müller<br> 051 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 052 * Lazenska 391, 76314 Zlin, Czech Republic<br> 053 * <br> 054 * This library is free software; you can redistribute it and/or 055 * modify it under the terms of the GNU Lesser General Public 056 * License as published by the Free Software Foundation; either 057 * version 2.1 of the License, or (at your option) any later version. 058 * <br><br> 059 * This library is distributed in the hope that it will be useful, 060 * but WITHOUT ANY WARRANTY; without even the implied warranty of 061 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 062 * Lesser General Public License for more details. 063 * <br><br> 064 * You should have received a copy of the GNU Lesser General Public 065 * License along with this library; if not, write to the Free Software 066 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 067 */ 068 public class ExamSimulatedAnnealing implements NeighbourSelection, SolutionListener { 069 private static Logger sLog = Logger.getLogger(ExamSimulatedAnnealing.class); 070 private static DecimalFormat sDF2 = new DecimalFormat("0.00"); 071 private static DecimalFormat sDF5 = new DecimalFormat("0.00000"); 072 private static DecimalFormat sDF10 = new DecimalFormat("0.0000000000"); 073 private double iInitialTemperature = 1.5; 074 private double iCoolingRate = 0.95; 075 private double iReheatRate = -1; 076 private long iTemperatureLength = 250000; 077 private long iReheatLength = 0; 078 private long iRestoreBestLength = 0; 079 private double iTemperature = 0.0; 080 private double iTempLengthCoef = 1.0; 081 private double iReheatLengthCoef = 5.0; 082 private double iRestoreBestLengthCoef = -1; 083 private long iIter = 0; 084 private long iLastImprovingIter = 0; 085 private long iLastReheatIter = 0; 086 private long iLastCoolingIter = 0; 087 private int iAcceptIter[] = new int[] {0,0,0}; 088 private boolean iStochasticHC = false; 089 private int iMoves = 0; 090 private double iAbsValue = 0; 091 private long iT0 = -1; 092 private double iBestValue = 0; 093 private Progress iProgress = null; 094 private boolean iActive; 095 096 private NeighbourSelection[] iNeighbours = null; 097 098 private boolean iRelativeAcceptance = true; 099 100 /** 101 * Constructor. Following problem properties are considered: 102 * <ul> 103 * <li>SimulatedAnnealing.InitialTemperature ... initial temperature (default 1.5) 104 * <li>SimulatedAnnealing.TemperatureLength ... temperature length (number of iterations between temperature decrements, default 25000) 105 * <li>SimulatedAnnealing.CoolingRate ... temperature cooling rate (default 0.95) 106 * <li>SimulatedAnnealing.ReheatLengthCoef ... temperature re-heat length coefficient (multiple of temperature length, default 5) 107 * <li>SimulatedAnnealing.ReheatRate ... temperature re-heating rate (default (1/coolingRate)^(reheatLengthCoef*1.7)) 108 * <li>SimulatedAnnealing.RestoreBestLengthCoef ... restore best length coefficient (multiple of re-heat length, default reheatLengthCoef^2) 109 * <li>SimulatedAnnealing.StochasticHC ... true for stochastic search acceptance criterion, false for simulated annealing acceptance (default false) 110 * <li>SimulatedAnnealing.RelativeAcceptance ... true for relative acceptance (different between the new and the current solutions, if the neighbour is accepted), false for absolute acceptance (difference between the new and the best solutions, if the neighbour is accepted) 111 * </ul> 112 * @param properties problem properties 113 */ 114 public ExamSimulatedAnnealing(DataProperties properties) { 115 iInitialTemperature = properties.getPropertyDouble("SimulatedAnnealing.InitialTemperature", iInitialTemperature); 116 iReheatRate = properties.getPropertyDouble("SimulatedAnnealing.ReheatRate", iReheatRate); 117 iCoolingRate = properties.getPropertyDouble("SimulatedAnnealing.CoolingRate", iCoolingRate); 118 iRelativeAcceptance = properties.getPropertyBoolean("SimulatedAnnealing.RelativeAcceptance", iRelativeAcceptance); 119 iStochasticHC = properties.getPropertyBoolean("SimulatedAnnealing.StochasticHC", iStochasticHC); 120 iTemperatureLength = properties.getPropertyLong("SimulatedAnnealing.TemperatureLength", iTemperatureLength); 121 iReheatLengthCoef = properties.getPropertyDouble("SimulatedAnnealing.ReheatLengthCoef", iReheatLengthCoef); 122 iRestoreBestLengthCoef = properties.getPropertyDouble("SimulatedAnnealing.RestoreBestLengthCoef", iRestoreBestLengthCoef); 123 if (iReheatRate<0) iReheatRate = Math.pow(1/iCoolingRate,iReheatLengthCoef*1.7); 124 if (iRestoreBestLengthCoef<0) iRestoreBestLengthCoef = iReheatLengthCoef * iReheatLengthCoef; 125 iNeighbours = new NeighbourSelection[] { 126 new ExamRandomMove(properties), 127 new ExamRoomMove(properties), 128 new ExamTimeMove(properties) 129 }; 130 } 131 132 /** 133 * Initialization 134 */ 135 public void init(Solver solver) { 136 iTemperature = iInitialTemperature; 137 iReheatLength = Math.round(iReheatLengthCoef*iTemperatureLength); 138 iRestoreBestLength = Math.round(iRestoreBestLengthCoef*iTemperatureLength); 139 solver.currentSolution().addSolutionListener(this); 140 for (int i=0;i<iNeighbours.length;i++) 141 iNeighbours[i].init(solver); 142 solver.setUpdateProgress(false); 143 iProgress = Progress.getInstance(solver.currentSolution().getModel()); 144 iActive = false; 145 } 146 147 /** 148 * Cool temperature 149 */ 150 protected void cool(Solution solution) { 151 iTemperature *= iCoolingRate; 152 sLog.info("Iter="+iIter/1000+"k, NonImpIter="+sDF2.format((iIter-iLastImprovingIter)/1000.0)+"k, Speed="+sDF2.format(1000.0*iIter/(System.currentTimeMillis()-iT0))+" it/s"); 153 sLog.info("Temperature decreased to "+sDF5.format(iTemperature)+" " + 154 "(#moves="+iMoves+", rms(value)="+sDF2.format(Math.sqrt(iAbsValue/iMoves))+", "+ 155 "accept=-"+sDF2.format(100.0*iAcceptIter[0]/iTemperatureLength)+"/"+sDF2.format(100.0*iAcceptIter[1]/iTemperatureLength)+"/+"+sDF2.format(100.0*iAcceptIter[2]/iTemperatureLength)+"%, " + 156 (prob(-1)<1.0?"p(-1)="+sDF2.format(100.0*prob(-1))+"%, ":"")+ 157 "p(+1)="+sDF2.format(100.0*prob(1))+"%, "+ 158 "p(+10)="+sDF5.format(100.0*prob(10))+"%)"); 159 iLastCoolingIter=iIter; 160 iAcceptIter=new int[] {0,0,0}; 161 iMoves = 0; iAbsValue = 0; 162 } 163 164 /** 165 * Reheat temperature 166 */ 167 protected void reheat(Solution solution) { 168 iTemperature *= iReheatRate; 169 sLog.info("Iter="+iIter/1000+"k, NonImpIter="+sDF2.format((iIter-iLastImprovingIter)/1000.0)+"k, Speed="+sDF2.format(1000.0*iIter/(System.currentTimeMillis()-iT0))+" it/s"); 170 sLog.info("Temperature increased to "+sDF5.format(iTemperature)+" "+ 171 (prob(-1)<1.0?"p(-1)="+sDF2.format(100.0*prob(-1))+"%, ":"")+ 172 "p(+1)="+sDF2.format(100.0*prob(1))+"%, "+ 173 "p(+10)="+sDF5.format(100.0*prob(10))+"%, "+ 174 "p(+100)="+sDF10.format(100.0*prob(100))+"%)"); 175 iLastReheatIter=iIter; 176 iProgress.setPhase("Simulated Annealing ["+sDF2.format(iTemperature)+"]..."); 177 } 178 179 /** 180 * restore best ever found solution 181 */ 182 protected void restoreBest(Solution solution) { 183 sLog.info("Best restored"); 184 iLastImprovingIter=iIter; 185 } 186 187 /** 188 * Generate neighbour -- select neighbourhood randomly, select neighbour 189 */ 190 public Neighbour genMove(Solution solution) { 191 while (true) { 192 incIter(solution); 193 NeighbourSelection ns = iNeighbours[ToolBox.random(iNeighbours.length)]; 194 Neighbour n = ns.selectNeighbour(solution); 195 if (n!=null) return n; 196 } 197 } 198 199 /** 200 * Neighbour acceptance probability 201 * @param value absolute or relative value of the proposed change (neighbour) 202 * @return probability of acceptance of a change (neighbour) 203 */ 204 protected double prob(double value) { 205 if (iStochasticHC) 206 return 1.0 / (1.0 + Math.exp(value/iTemperature)); 207 else 208 return (value<=0.0?1.0:Math.exp(-value/iTemperature)); 209 } 210 211 /** 212 * True if the given neighboir is to be be accepted 213 * @param solution current solution 214 * @param neighbour proposed move 215 * @return true if generated random number is below {@link ExamSimulatedAnnealing#prob(double)} 216 */ 217 protected boolean accept(Solution solution, Neighbour neighbour) { 218 double value = (iRelativeAcceptance?neighbour.value():solution.getModel().getTotalValue()+neighbour.value()-solution.getBestValue()); 219 double prob = prob(value); 220 if (prob>=1.0 || ToolBox.random()<prob) { 221 iAcceptIter[neighbour.value()<0.0?0:neighbour.value()>0.0?2:1]++; 222 return true; 223 } 224 return false; 225 } 226 227 /** 228 * Increment iteration counter, cool/reheat/restoreBest if necessary 229 */ 230 protected void incIter(Solution solution) { 231 if (iT0<0) iT0 = System.currentTimeMillis(); 232 iIter++; 233 if (iIter>iLastImprovingIter+iRestoreBestLength) restoreBest(solution); 234 if (iIter>Math.max(iLastReheatIter,iLastImprovingIter)+iReheatLength) reheat(solution); 235 if (iIter>iLastCoolingIter+iTemperatureLength) cool(solution); 236 iProgress.setProgress(Math.round(100.0 * (iIter-Math.max(iLastReheatIter,iLastImprovingIter)) / iReheatLength)); 237 } 238 239 /** 240 * Select neighbour -- generate a move {@link ExamSimulatedAnnealing#genMove(Solution)} until an acceptable neighbour 241 * is found {@link ExamSimulatedAnnealing#accept(Solution, Neighbour)}, keep increasing iteration 242 * {@link ExamSimulatedAnnealing#incIter(Solution)}. 243 */ 244 public Neighbour selectNeighbour(Solution solution) { 245 if (!iActive) { 246 iProgress.setPhase("Simulated Annealing ["+sDF2.format(iTemperature)+"]..."); 247 iActive = true; 248 } 249 Neighbour neighbour = null; 250 while ((neighbour=genMove(solution))!=null) { 251 iMoves++; iAbsValue += neighbour.value() * neighbour.value(); 252 if (accept(solution,neighbour)) break; 253 } 254 if (neighbour==null) iActive = false; 255 return (neighbour==null?null:neighbour); 256 } 257 258 /** 259 * Memorize the iteration when the last best solution was found. 260 */ 261 public void bestSaved(Solution solution) { 262 if (Math.abs(iBestValue-solution.getBestValue())>=1.0) { 263 iLastImprovingIter = iIter; 264 iBestValue = solution.getBestValue(); 265 } 266 iLastImprovingIter = iIter; 267 } 268 public void solutionUpdated(Solution solution) {} 269 public void getInfo(Solution solution, java.util.Dictionary info) {} 270 public void getInfo(Solution solution, java.util.Dictionary info, java.util.Vector variables) {} 271 public void bestCleared(Solution solution) {} 272 public void bestRestored(Solution solution){} 273 274 275 276 }