001package org.cpsolver.coursett.heuristics; 002 003import java.util.ArrayList; 004import java.util.Collection; 005import java.util.HashMap; 006import java.util.Iterator; 007import java.util.List; 008import java.util.Map; 009import java.util.Set; 010import java.util.concurrent.locks.Lock; 011 012import org.cpsolver.coursett.model.Lecture; 013import org.cpsolver.coursett.model.Placement; 014import org.cpsolver.coursett.model.TimetableModel; 015import org.cpsolver.ifs.assignment.Assignment; 016import org.cpsolver.ifs.heuristics.StandardNeighbourSelection; 017import org.cpsolver.ifs.model.Neighbour; 018import org.cpsolver.ifs.solution.Solution; 019import org.cpsolver.ifs.solver.Solver; 020import org.cpsolver.ifs.util.DataProperties; 021import org.cpsolver.ifs.util.JProf; 022import org.cpsolver.ifs.util.ToolBox; 023 024 025/** 026 * Neighbour selection which does the standard time neighbour selection most of 027 * the time, however, the very best neighbour is selected time to time (using 028 * backtracking based search). 029 * 030 * @see StandardNeighbourSelection 031 * @version CourseTT 1.3 (University Course Timetabling)<br> 032 * Copyright (C) 2006 - 2014 Tomáš Müller<br> 033 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 034 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 035 * <br> 036 * This library is free software; you can redistribute it and/or modify 037 * it under the terms of the GNU Lesser General Public License as 038 * published by the Free Software Foundation; either version 3 of the 039 * License, or (at your option) any later version. <br> 040 * <br> 041 * This library is distributed in the hope that it will be useful, but 042 * WITHOUT ANY WARRANTY; without even the implied warranty of 043 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 044 * Lesser General Public License for more details. <br> 045 * <br> 046 * You should have received a copy of the GNU Lesser General Public 047 * License along with this library; if not see 048 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 049 */ 050 051public class NeighbourSelectionWithSuggestions extends StandardNeighbourSelection<Lecture, Placement> { 052 private double iSuggestionProbability = 0.1; 053 private double iSuggestionProbabilityAllAssigned = 0.5; 054 protected int iSuggestionTimeout = 500; 055 protected int iSuggestionDepth = 4; 056 057 public NeighbourSelectionWithSuggestions(DataProperties properties) throws Exception { 058 super(properties); 059 iSuggestionProbability = properties.getPropertyDouble("Neighbour.SuggestionProbability", iSuggestionProbability); 060 iSuggestionProbabilityAllAssigned = properties.getPropertyDouble("Neighbour.SuggestionProbabilityAllAssigned", iSuggestionProbabilityAllAssigned); 061 iSuggestionTimeout = properties.getPropertyInt("Neighbour.SuggestionTimeout", iSuggestionTimeout); 062 iSuggestionDepth = properties.getPropertyInt("Neighbour.SuggestionDepth", iSuggestionDepth); 063 } 064 065 public NeighbourSelectionWithSuggestions(Solver<Lecture, Placement> solver) throws Exception { 066 this(solver.getProperties()); 067 init(solver); 068 } 069 070 @Override 071 public void init(Solver<Lecture, Placement> solver) { 072 super.init(solver); 073 } 074 075 @Override 076 public Neighbour<Lecture, Placement> selectNeighbour(Solution<Lecture, Placement> solution) { 077 Neighbour<Lecture, Placement> neighbour = null; 078 if (solution.getModel().unassignedVariables(solution.getAssignment()).isEmpty()) { 079 for (int d = iSuggestionDepth; d > 1; d--) { 080 if (ToolBox.random() < Math.pow(iSuggestionProbabilityAllAssigned, d - 1)) { 081 neighbour = selectNeighbourWithSuggestions(solution, selectVariable(solution), d); 082 break; 083 } 084 } 085 } else { 086 for (int d = iSuggestionDepth; d > 1; d--) { 087 if (ToolBox.random() < Math.pow(iSuggestionProbability, d - 1)) { 088 neighbour = selectNeighbourWithSuggestions(solution, selectVariable(solution), d); 089 break; 090 } 091 } 092 } 093 return (neighbour != null ? neighbour : super.selectNeighbour(solution)); 094 } 095 096 public Neighbour<Lecture, Placement> selectNeighbourWithSuggestions(Solution<Lecture, Placement> solution, Lecture lecture, int depth) { 097 if (lecture == null) 098 return null; 099 100 NeighbourSelectionWithSuggestionsContext context = new NeighbourSelectionWithSuggestionsContext(solution); 101 102 Lock lock = solution.getLock().writeLock(); 103 lock.lock(); 104 try { 105 // System.out.println("BEFORE BT ("+lecture.getName()+"): nrAssigned="+iSolution.getModel().assignedVariables().size()+", value="+iCmp.currentValue(iSolution)); 106 107 List<Lecture> initialLectures = new ArrayList<Lecture>(1); 108 initialLectures.add(lecture); 109 backtrack(context, initialLectures, new HashMap<Lecture, Placement>(), new HashMap<Lecture, Placement>(), depth); 110 111 // System.out.println("AFTER BT ("+lecture.getName()+"): nrAssigned="+iSolution.getModel().assignedVariables().size()+", value="+iCmp.currentValue(iSolution)); 112 } finally { 113 lock.unlock(); 114 } 115 116 return context.getSuggestionNeighbour(); 117 } 118 119 private boolean containsCommited(NeighbourSelectionWithSuggestionsContext context, Collection<Placement> values) { 120 if (context.getModel().hasConstantVariables()) { 121 for (Placement placement : values) { 122 Lecture lecture = placement.variable(); 123 if (lecture.isCommitted()) 124 return true; 125 } 126 } 127 return false; 128 } 129 130 private void backtrack(NeighbourSelectionWithSuggestionsContext context, List<Lecture> initialLectures, Map<Lecture, Placement> resolvedLectures, HashMap<Lecture, Placement> conflictsToResolve, int depth) { 131 int nrUnassigned = conflictsToResolve.size(); 132 if ((initialLectures == null || initialLectures.isEmpty()) && nrUnassigned == 0) { 133 context.setSuggestionNeighbourIfImproving(resolvedLectures); 134 return; 135 } 136 if (depth <= 0 || context.checkTimeoutReached()) 137 return; 138 139 Assignment<Lecture, Placement> assignment = context.getAssignment(); 140 for (Lecture lecture: initialLectures != null && !initialLectures.isEmpty() ? initialLectures : new ArrayList<Lecture>(conflictsToResolve.keySet())) { 141 if (context.isTimeoutReached()) break; 142 if (resolvedLectures.containsKey(lecture)) 143 continue; 144 List<Placement> placements = lecture.values(assignment); 145 int rnd = ToolBox.random(placements.size()); 146 placements: for (int idx = 0; idx < placements.size(); idx++) { 147 Placement placement = placements.get((idx + rnd) % placements.size()); 148 if (context.isTimeoutReached()) break; 149 Placement cur = assignment.getValue(lecture); 150 if (placement.equals(cur)) 151 continue; 152 if (placement.isHard(assignment)) 153 continue; 154 Set<Placement> conflicts = context.getModel().conflictValues(assignment, placement); 155 if (nrUnassigned + conflicts.size() > depth) 156 continue; 157 if (conflicts.contains(placement)) 158 continue; 159 if (containsCommited(context, conflicts)) 160 continue; 161 for (Iterator<Placement> i = conflicts.iterator();i.hasNext();) { 162 Placement c = i.next(); 163 if (resolvedLectures.containsKey(c.variable())) 164 continue placements; 165 } 166 for (Iterator<Placement> i = conflicts.iterator(); i.hasNext();) { 167 Placement c = i.next(); 168 assignment.unassign(0, c.variable()); 169 } 170 assignment.assign(0, placement); 171 for (Iterator<Placement> i = conflicts.iterator(); i.hasNext();) { 172 Placement c = i.next(); 173 conflictsToResolve.put(c.variable(), c); 174 } 175 Placement resolvedConf = conflictsToResolve.remove(lecture); 176 resolvedLectures.put(lecture, placement); 177 backtrack(context, null, resolvedLectures, conflictsToResolve, depth - 1); 178 resolvedLectures.remove(lecture); 179 if (cur == null) 180 assignment.unassign(0, lecture); 181 else 182 assignment.assign(0, cur); 183 for (Iterator<Placement> i = conflicts.iterator(); i.hasNext();) { 184 Placement p = i.next(); 185 assignment.assign(0, p); 186 conflictsToResolve.remove(p.variable()); 187 } 188 if (resolvedConf != null) 189 conflictsToResolve.put(lecture, resolvedConf); 190 } 191 } 192 } 193 194 public class SuggestionNeighbour implements Neighbour<Lecture, Placement> { 195 private double iValue = 0; 196 private List<Placement> iDifferentAssignments = null; 197 198 public SuggestionNeighbour(Map<Lecture, Placement> resolvedLectures, double value) { 199 iValue = value; 200 iDifferentAssignments = new ArrayList<Placement>(resolvedLectures.values()); 201 } 202 203 @Override 204 public double value(Assignment<Lecture, Placement> assignment) { 205 return iValue; 206 } 207 208 @Override 209 public void assign(Assignment<Lecture, Placement> assignment, long iteration) { 210 // System.out.println("START ASSIGN: nrAssigned="+iSolution.getModel().assignedVariables().size()+", value="+iCmp.currentValue(iSolution)); 211 // System.out.println(" "+this); 212 for (Placement p : iDifferentAssignments) 213 assignment.unassign(iteration, p.variable()); 214 for (Placement p : iDifferentAssignments) 215 assignment.assign(iteration, p); 216 // System.out.println("END ASSIGN: nrAssigned="+iSolution.getModel().assignedVariables().size()+", value="+iCmp.currentValue(iSolution)); 217 } 218 219 public int compareTo(Solution<Lecture, Placement> solution) { 220 return Double.compare(iValue, solution.getModel().getTotalValue(solution.getAssignment())); 221 } 222 223 @Override 224 public String toString() { 225 StringBuffer sb = new StringBuffer("Suggestion{value=" + iValue + ": "); 226 for (Iterator<Placement> e = iDifferentAssignments.iterator(); e.hasNext();) { 227 Placement p = e.next(); 228 sb.append("\n " + p.variable().getName() + " " + p.getName() + (e.hasNext() ? "," : "")); 229 } 230 sb.append("}"); 231 return sb.toString(); 232 } 233 234 @Override 235 public Map<Lecture, Placement> assignments() { 236 Map<Lecture, Placement> ret = new HashMap<Lecture, Placement>(); 237 for (Placement p : iDifferentAssignments) 238 ret.put(p.variable(), p); 239 return ret; 240 } 241 } 242 243 public class NeighbourSelectionWithSuggestionsContext { 244 private Solution<Lecture, Placement> iSolution = null; 245 private SuggestionNeighbour iSuggestionNeighbour = null; 246 private double iValue = 0; 247 private int iNrAssigned = 0; 248 private boolean iTimeoutReached = false; 249 private long iStartTime; 250 251 public NeighbourSelectionWithSuggestionsContext(Solution<Lecture, Placement> solution) { 252 iSolution = solution; 253 iSuggestionNeighbour = null; 254 iValue = solution.getModel().getTotalValue(solution.getAssignment()); 255 iNrAssigned = solution.getAssignment().nrAssignedVariables(); 256 iTimeoutReached = false; 257 iStartTime = JProf.currentTimeMillis(); 258 } 259 260 public SuggestionNeighbour getSuggestionNeighbour() { return iSuggestionNeighbour; } 261 262 public boolean setSuggestionNeighbourIfImproving(Map<Lecture, Placement> assignment) { 263 if (getAssignment().nrAssignedVariables() > getNrAssigned() || (getAssignment().nrAssignedVariables() == getNrAssigned() && getValue() > getModel().getTotalValue(getAssignment()))) { 264 double value = getModel().getTotalValue(getAssignment()); 265 if (getSuggestionNeighbour() == null || getSuggestionNeighbour().value(getAssignment()) >= value) { 266 iSuggestionNeighbour = new SuggestionNeighbour(assignment, value); 267 return true; 268 } 269 } 270 return false; 271 } 272 273 public Solution<Lecture, Placement> getSolution() { return iSolution; } 274 public Assignment<Lecture, Placement> getAssignment() { return getSolution().getAssignment(); } 275 public TimetableModel getModel() { return (TimetableModel)getSolution().getModel(); } 276 public int getNrAssigned() { return iNrAssigned; } 277 public double getValue() { return iValue; } 278 279 public boolean isTimeoutReached() { return iTimeoutReached; } 280 public boolean checkTimeoutReached() { 281 if (iTimeoutReached) return true; 282 if (iSuggestionTimeout > 0 && JProf.currentTimeMillis() - iStartTime > iSuggestionTimeout) 283 iTimeoutReached = true; 284 return iTimeoutReached; 285 } 286 public void setTimeoutReached(boolean timeoutReached) { iTimeoutReached = timeoutReached; } 287 } 288}