001 package net.sf.cpsolver.studentsct.heuristics.selection; 002 003 import java.util.Collection; 004 import java.util.Enumeration; 005 import java.util.Hashtable; 006 import java.util.Iterator; 007 import java.util.Set; 008 import java.util.Vector; 009 010 import org.apache.log4j.Logger; 011 012 import net.sf.cpsolver.ifs.extension.Extension; 013 import net.sf.cpsolver.ifs.heuristics.NeighbourSelection; 014 import net.sf.cpsolver.ifs.model.Neighbour; 015 import net.sf.cpsolver.ifs.solution.Solution; 016 import net.sf.cpsolver.ifs.solver.Solver; 017 import net.sf.cpsolver.ifs.util.DataProperties; 018 import net.sf.cpsolver.ifs.util.Progress; 019 import net.sf.cpsolver.studentsct.StudentSectioningModel; 020 import net.sf.cpsolver.studentsct.extension.DistanceConflict; 021 import net.sf.cpsolver.studentsct.heuristics.studentord.StudentChoiceRealFirstOrder; 022 import net.sf.cpsolver.studentsct.heuristics.studentord.StudentOrder; 023 import net.sf.cpsolver.studentsct.model.CourseRequest; 024 import net.sf.cpsolver.studentsct.model.Enrollment; 025 import net.sf.cpsolver.studentsct.model.Request; 026 import net.sf.cpsolver.studentsct.model.Student; 027 028 /** 029 * Section all students using incremental branch & bound (no unassignments). 030 * All students are taken in a random order, for each student a branch & bound 031 * algorithm is used to find his/her best schedule on top of all other existing 032 * student schedules (no enrollment of a different student is unassigned). 033 * 034 * <br><br> 035 * Parameters: 036 * <br> 037 * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr> 038 * <tr><td>Neighbour.BranchAndBoundTimeout</td><td>{@link Integer}</td><td>Timeout for each neighbour selection (in milliseconds).</td></tr> 039 * <tr><td>Neighbour.BranchAndBoundMinimizePenalty</td><td>{@link Boolean}</td><td>If true, section penalties (instead of section values) are minimized: 040 * overall penalty is minimized together with the maximization of the number of assigned requests and minimization of distance conflicts 041 * -- this variant is to better mimic the case when students can choose their sections (section times).</td></tr> 042 * </table> 043 * <br><br> 044 * 045 * @version 046 * StudentSct 1.1 (Student Sectioning)<br> 047 * Copyright (C) 2007 Tomáš Müller<br> 048 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 049 * Lazenska 391, 76314 Zlin, Czech Republic<br> 050 * <br> 051 * This library is free software; you can redistribute it and/or 052 * modify it under the terms of the GNU Lesser General Public 053 * License as published by the Free Software Foundation; either 054 * version 2.1 of the License, or (at your option) any later version. 055 * <br><br> 056 * This library is distributed in the hope that it will be useful, 057 * but WITHOUT ANY WARRANTY; without even the implied warranty of 058 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 059 * Lesser General Public License for more details. 060 * <br><br> 061 * You should have received a copy of the GNU Lesser General Public 062 * License along with this library; if not, write to the Free Software 063 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 064 */ 065 066 public class BranchBoundSelection implements NeighbourSelection { 067 private static Logger sLog = Logger.getLogger(BranchBoundSelection.class); 068 protected int iTimeout = 10000; 069 protected DistanceConflict iDistanceConflict = null; 070 public static boolean sDebug = false; 071 protected Enumeration iStudentsEnumeration = null; 072 protected boolean iMinimizePenalty = false; 073 protected StudentOrder iOrder = new StudentChoiceRealFirstOrder(); 074 protected double iDistConfWeight = 1.0; 075 076 /** 077 * Constructor 078 * @param properties configuration 079 */ 080 public BranchBoundSelection(DataProperties properties) { 081 iTimeout = properties.getPropertyInt("Neighbour.BranchAndBoundTimeout", iTimeout); 082 iMinimizePenalty = properties.getPropertyBoolean("Neighbour.BranchAndBoundMinimizePenalty", iMinimizePenalty); 083 if (iMinimizePenalty) sLog.info("Overall penalty is going to be minimized (together with the maximization of the number of assigned requests and minimization of distance conflicts)."); 084 if (properties.getProperty("Neighbour.BranchAndBoundOrder")!=null) { 085 try { 086 iOrder = (StudentOrder)Class.forName(properties.getProperty("Neighbour.BranchAndBoundOrder")). 087 getConstructor(new Class[] {DataProperties.class}). 088 newInstance(new Object[] {properties}); 089 } catch (Exception e) { 090 sLog.error("Unable to set student order, reason:"+e.getMessage(),e); 091 } 092 } 093 iDistConfWeight = properties.getPropertyDouble("DistanceConflict.Weight", iDistConfWeight); 094 } 095 096 /** 097 * Initialize 098 */ 099 public void init(Solver solver, String name) { 100 Vector students = iOrder.order(((StudentSectioningModel)solver.currentSolution().getModel()).getStudents()); 101 iStudentsEnumeration = students.elements(); 102 if (iDistanceConflict==null) 103 for (Enumeration e=solver.getExtensions().elements();e.hasMoreElements();) { 104 Extension ext = (Extension)e.nextElement(); 105 if (ext instanceof DistanceConflict) 106 iDistanceConflict = (DistanceConflict)ext; 107 } 108 Progress.getInstance(solver.currentSolution().getModel()).setPhase(name, students.size()); 109 } 110 111 public void init(Solver solver) { 112 init(solver, "Branch&bound..."); 113 } 114 115 /** 116 * Select neighbour. All students are taken, one by one in a random order. 117 * For each student a branch & bound search is employed. 118 */ 119 public Neighbour selectNeighbour(Solution solution) { 120 while (iStudentsEnumeration.hasMoreElements()) { 121 Student student = (Student)iStudentsEnumeration.nextElement(); 122 Progress.getInstance(solution.getModel()).incProgress(); 123 Neighbour neighbour = getSelection(student).select(); 124 if (neighbour!=null) return neighbour; 125 } 126 return null; 127 } 128 129 /** 130 * Branch & bound selection for a student 131 */ 132 public Selection getSelection(Student student) { 133 return new Selection(student); 134 } 135 136 /** 137 * Branch & bound selection for a student 138 */ 139 public class Selection { 140 /** Student */ 141 protected Student iStudent; 142 /** Start time */ 143 protected long iT0; 144 /** End time */ 145 protected long iT1; 146 /** Was timeout reached */ 147 protected boolean iTimeoutReached; 148 /** Current assignment */ 149 protected Enrollment[] iAssignment; 150 /** Best assignment */ 151 protected Enrollment[] iBestAssignment; 152 /** Best value */ 153 protected double iBestValue; 154 /** Value cache */ 155 protected Hashtable iValues; 156 157 /** 158 * Constructor 159 * @param student selected student 160 */ 161 public Selection(Student student) { 162 iStudent = student; 163 } 164 165 /** 166 * Execute branch & bound, return the best found schedule for the selected student. 167 */ 168 public BranchBoundNeighbour select() { 169 iT0 = System.currentTimeMillis(); 170 iTimeoutReached = false; 171 iAssignment = new Enrollment[iStudent.getRequests().size()]; 172 iBestAssignment = null; 173 iBestValue = 0; 174 iValues = new Hashtable(); 175 backTrack(0); 176 iT1 = System.currentTimeMillis(); 177 if (iBestAssignment==null) return null; 178 return new BranchBoundNeighbour(iBestValue, iBestAssignment); 179 } 180 181 /** Was timeout reached */ 182 public boolean isTimeoutReached() { 183 return iTimeoutReached; 184 } 185 186 /** Time (in milliseconds) the branch & bound did run */ 187 public long getTime() { 188 return iT1 - iT0; 189 } 190 191 /** Best schedule */ 192 public Enrollment[] getBestAssignment() { 193 return iBestAssignment; 194 } 195 196 /** Value of the best schedule */ 197 public double getBestValue() { 198 return iBestValue; 199 } 200 201 /** Number of requests assigned in the best schedule */ 202 public int getBestNrAssigned() { 203 int nrAssigned = 0; 204 for (int i=0;i<iBestAssignment.length;i++) 205 if (iBestAssignment[i]!=null) nrAssigned++; 206 return nrAssigned; 207 } 208 209 /** Bound for the number of assigned requests in the current schedule */ 210 public int getNrAssignedBound(int idx) { 211 int bound = 0; 212 int i=0, alt=0; 213 for (Enumeration e=iStudent.getRequests().elements();e.hasMoreElements();i++) { 214 Request r = (Request)e.nextElement(); 215 if (i<idx) { 216 if (iAssignment[i]!=null) 217 bound++; 218 if (r.isAlternative()) { 219 if (iAssignment[i]!=null || (r instanceof CourseRequest && ((CourseRequest)r).isWaitlist())) alt--; 220 } else { 221 if (r instanceof CourseRequest && !((CourseRequest)r).isWaitlist() && iAssignment[i]==null) alt++; 222 } 223 } else { 224 if (!r.isAlternative()) 225 bound ++; 226 else if (alt>0) { 227 bound ++; alt--; 228 } 229 } 230 } 231 return bound; 232 } 233 234 /** Number of distance conflicts of idx-th assignment of the current schedule */ 235 public double getNrDistanceConflicts(int idx) { 236 if (iDistanceConflict==null || iAssignment[idx]==null) return 0; 237 double nrDist = iDistanceConflict.nrConflicts(iAssignment[idx]); 238 for (int x=0;x<idx;x++) 239 if (iAssignment[x]!=null) 240 nrDist+=iDistanceConflict.nrConflicts(iAssignment[x],iAssignment[idx]); 241 return nrDist; 242 } 243 244 /** Bound for the current schedule */ 245 public double getBound(int idx) { 246 double bound = 0.0; 247 int i=0, alt=0; 248 for (Enumeration e=iStudent.getRequests().elements();e.hasMoreElements();i++) { 249 Request r = (Request)e.nextElement(); 250 if (i<idx) { 251 if (iAssignment[i]!=null) 252 bound += iAssignment[i].toDouble(getNrDistanceConflicts(i)); 253 if (r.isAlternative()) { 254 if (iAssignment[i]!=null || (r instanceof CourseRequest && ((CourseRequest)r).isWaitlist())) alt--; 255 } else { 256 if (r instanceof CourseRequest && !((CourseRequest)r).isWaitlist() && iAssignment[i]==null) alt++; 257 } 258 } else { 259 if (!r.isAlternative()) 260 bound += r.getBound(); 261 else if (alt>0) { 262 bound += r.getBound(); alt--; 263 } 264 } 265 } 266 return bound; 267 } 268 269 /** Value of the current schedule */ 270 public double getValue() { 271 double value = 0.0; 272 for (int i=0;i<iAssignment.length;i++) 273 if (iAssignment[i]!=null) 274 value += iAssignment[i].toDouble(getNrDistanceConflicts(i)); 275 return value; 276 } 277 278 /** Assignment penalty */ 279 protected double getAssignmentPenalty(int i) { 280 return iAssignment[i].getPenalty() + iDistConfWeight*getNrDistanceConflicts(i); 281 } 282 283 /** Penalty of the current schedule */ 284 public double getPenalty() { 285 double bestPenalty = 0; 286 for (int i=0;i<iAssignment.length;i++) 287 if (iAssignment[i]!=null) bestPenalty += getAssignmentPenalty(i); 288 return bestPenalty; 289 } 290 291 292 /** Penalty bound of the current schedule */ 293 public double getPenaltyBound(int idx) { 294 double bound = 0.0; 295 int i=0, alt=0; 296 for (Enumeration e=iStudent.getRequests().elements();e.hasMoreElements();i++) { 297 Request r = (Request)e.nextElement(); 298 if (i<idx) { 299 if (iAssignment[i]!=null) 300 bound += getAssignmentPenalty(i); 301 if (r.isAlternative()) { 302 if (iAssignment[i]!=null || (r instanceof CourseRequest && ((CourseRequest)r).isWaitlist())) alt--; 303 } else { 304 if (r instanceof CourseRequest && !((CourseRequest)r).isWaitlist() && iAssignment[i]==null) alt++; 305 } 306 } else { 307 if (!r.isAlternative()) { 308 if (r instanceof CourseRequest) 309 bound += ((CourseRequest)r).getMinPenalty(); 310 } else if (alt>0) { 311 if (r instanceof CourseRequest) 312 bound += ((CourseRequest)r).getMinPenalty(); 313 alt--; 314 } 315 } 316 } 317 return bound; 318 } 319 320 /** Save the current schedule as the best */ 321 public void saveBest() { 322 if (iBestAssignment==null) 323 iBestAssignment = new Enrollment[iAssignment.length]; 324 for (int i=0;i<iAssignment.length;i++) 325 iBestAssignment[i] = iAssignment[i]; 326 if (iMinimizePenalty) 327 iBestValue = getPenalty(); 328 else 329 iBestValue = getValue(); 330 } 331 332 /** First conflicting enrollment */ 333 public Enrollment firstConflict(int idx, Enrollment enrollment) { 334 Set conflicts = enrollment.variable().getModel().conflictValues(enrollment); 335 if (conflicts.contains(enrollment)) return enrollment; 336 if (conflicts!=null && !conflicts.isEmpty()) { 337 for (Iterator i=conflicts.iterator();i.hasNext();) { 338 Enrollment conflict = (Enrollment)i.next(); 339 if (!conflict.getStudent().equals(iStudent)) return conflict; 340 } 341 } 342 for (int i=0;i<iAssignment.length;i++) { 343 if (iAssignment[i]==null) continue; 344 if (!iAssignment[i].isConsistent(enrollment)) return iAssignment[i]; 345 } 346 return null; 347 } 348 349 /** True if the given request can be assigned */ 350 public boolean canAssign(Request request, int idx) { 351 if (!request.isAlternative() || iAssignment[idx]!=null) return true; 352 int alt = 0; 353 int i = 0; 354 for (Enumeration e=iStudent.getRequests().elements();e.hasMoreElements();i++) { 355 Request r = (Request)e.nextElement(); 356 if (r.equals(request)) continue; 357 if (r.isAlternative()) { 358 if (iAssignment[i]!=null || (r instanceof CourseRequest && ((CourseRequest)r).isWaitlist())) alt--; 359 } else { 360 if (r instanceof CourseRequest && !((CourseRequest)r).isWaitlist() && iAssignment[i]==null) alt++; 361 } 362 } 363 return (alt>0); 364 } 365 366 /** Number of assigned requests in the current schedule */ 367 public int getNrAssigned() { 368 int assigned = 0; 369 for (int i=0;i<iAssignment.length;i++) 370 if (iAssignment[i]!=null) assigned++; 371 return assigned; 372 } 373 374 /** branch & bound search */ 375 public void backTrack(int idx) { 376 if (sDebug) sLog.debug("backTrack("+getNrAssigned()+"/"+getValue()+","+idx+")"); 377 if (iTimeout>0 && (System.currentTimeMillis()-iT0)>iTimeout) { 378 if (sDebug) sLog.debug(" -- timeout reached"); 379 iTimeoutReached=true; return; 380 } 381 if (iMinimizePenalty) { 382 if (iBestAssignment!=null && (getNrAssignedBound(idx)<getBestNrAssigned() || (getNrAssignedBound(idx)==getBestNrAssigned() && getPenaltyBound(idx)>=iBestValue))) { 383 if (sDebug) sLog.debug(" -- branch number of assigned "+getNrAssignedBound(idx)+"<"+getBestNrAssigned()+", or penalty "+getPenaltyBound(idx)+">="+iBestValue); 384 return; 385 } 386 if (idx==iAssignment.length) { 387 if (iBestAssignment==null || (getNrAssigned()>getBestNrAssigned() || (getNrAssigned()==getBestNrAssigned() && getPenalty()<iBestValue))) { 388 if (sDebug) sLog.debug(" -- best solution found "+getNrAssigned()+"/"+getPenalty()); 389 saveBest(); 390 } 391 return; 392 } 393 } else { 394 if (iBestAssignment!=null && getBound(idx)>=iBestValue) { 395 if (sDebug) sLog.debug(" -- branch "+getBound(idx)+" >= "+iBestValue); 396 return; 397 } 398 if (idx==iAssignment.length) { 399 if (iBestAssignment==null || getValue()<iBestValue) { 400 if (sDebug) sLog.debug(" -- best solution found "+getNrAssigned()+"/"+getValue()); 401 saveBest(); 402 } 403 return; 404 } 405 } 406 407 Request request = (Request)iStudent.getRequests().elementAt(idx); 408 if (sDebug) sLog.debug(" -- request: "+request); 409 if (!canAssign(request, idx)) { 410 if (sDebug) sLog.debug(" -- cannot assign"); 411 backTrack(idx+1); 412 return; 413 } 414 Collection values = null; 415 if (request instanceof CourseRequest) { 416 CourseRequest courseRequest = (CourseRequest)request; 417 if (!courseRequest.getSelectedChoices().isEmpty()) { 418 if (sDebug) sLog.debug(" -- selection among selected enrollments"); 419 values = courseRequest.getSelectedEnrollments(true); 420 if (values!=null && !values.isEmpty()) { 421 boolean hasNoConflictValue = false; 422 for (Iterator i=values.iterator();i.hasNext();) { 423 Enrollment enrollment = (Enrollment)i.next(); 424 if (firstConflict(idx, enrollment)!=null) continue; 425 hasNoConflictValue = true; 426 if (sDebug) sLog.debug(" -- nonconflicting enrollment found: "+enrollment); 427 iAssignment[idx] = enrollment; 428 backTrack(idx+1); 429 iAssignment[idx] = null; 430 } 431 if (hasNoConflictValue) return; 432 } 433 } 434 values = (Collection)iValues.get(courseRequest); 435 if (values==null) { 436 values = courseRequest.getAvaiableEnrollments(); 437 iValues.put(courseRequest, values); 438 } 439 } else { 440 values = request.computeEnrollments(); 441 } 442 if (sDebug) { 443 sLog.debug(" -- nrValues: "+values.size()); 444 int vIdx=1; 445 for (Iterator i=values.iterator();i.hasNext();vIdx++) { 446 Enrollment enrollment = (Enrollment)i.next(); 447 if (sDebug) sLog.debug(" -- ["+vIdx+"]: "+enrollment); 448 } 449 } 450 boolean hasNoConflictValue = false; 451 for (Iterator i=values.iterator();i.hasNext();) { 452 Enrollment enrollment = (Enrollment)i.next(); 453 if (sDebug) sLog.debug(" -- enrollment: "+enrollment); 454 Enrollment conflict = firstConflict(idx, enrollment); 455 if (conflict!=null) { 456 if (sDebug) sLog.debug(" -- in conflict with: "+conflict); 457 continue; 458 } 459 hasNoConflictValue = true; 460 iAssignment[idx] = enrollment; 461 backTrack(idx+1); 462 iAssignment[idx] = null; 463 } 464 if (!hasNoConflictValue || request instanceof CourseRequest) backTrack(idx+1); 465 } 466 } 467 468 /** Branch & bound neighbour -- a schedule of a student */ 469 public static class BranchBoundNeighbour extends Neighbour { 470 private double iValue; 471 private Enrollment[] iAssignment; 472 473 /** 474 * Constructor 475 * @param value value of the schedule 476 * @param assignment enrollments of student's requests 477 */ 478 public BranchBoundNeighbour(double value, Enrollment[] assignment) { 479 iValue = value; 480 iAssignment = assignment; 481 } 482 483 public double value() { 484 return iValue; 485 } 486 487 /** Assignment */ 488 public Enrollment[] getAssignment() { 489 return iAssignment; 490 } 491 492 /** Assign the schedule */ 493 public void assign(long iteration) { 494 for (int i=0;i<iAssignment.length;i++) 495 if (iAssignment[i]!=null) 496 iAssignment[i].variable().assign(iteration, iAssignment[i]); 497 } 498 499 public String toString() { 500 StringBuffer sb = new StringBuffer("B&B{"); 501 Student student = null; 502 for (int i=0;i<iAssignment.length;i++) { 503 if (iAssignment[i]!=null) { 504 student = iAssignment[i].getRequest().getStudent(); 505 sb.append(" "+student); 506 sb.append(" ("+iValue+")"); 507 break; 508 } 509 } 510 if (student!=null) { 511 int idx=0; 512 for (Enumeration e=student.getRequests().elements();e.hasMoreElements();idx++) { 513 Request request = (Request)e.nextElement(); 514 sb.append("\n"+request); 515 Enrollment enrollment = iAssignment[idx]; 516 if (enrollment==null) 517 sb.append(" -- not assigned"); 518 else 519 sb.append(" -- "+enrollment); 520 } 521 } 522 sb.append("\n}"); 523 return sb.toString(); 524 } 525 526 } 527 }