001 package net.sf.cpsolver.coursett.constraint; 002 003 import java.util.Collection; 004 import java.util.Collections; 005 import java.util.Comparator; 006 import java.util.Enumeration; 007 import java.util.HashSet; 008 import java.util.Iterator; 009 import java.util.Set; 010 import java.util.Vector; 011 012 import net.sf.cpsolver.coursett.Constants; 013 import net.sf.cpsolver.coursett.model.Lecture; 014 import net.sf.cpsolver.coursett.model.Placement; 015 import net.sf.cpsolver.coursett.model.TimeLocation; 016 import net.sf.cpsolver.ifs.model.Constraint; 017 import net.sf.cpsolver.ifs.model.Value; 018 import net.sf.cpsolver.ifs.model.Variable; 019 import net.sf.cpsolver.ifs.util.DataProperties; 020 021 /** 022 * Minimize number of used groups of time within a set of classes. 023 * <br><br> 024 * 025 * This constraint implements the following distribution/group constraints: 026 * <br><br> 027 * 028 * MIN_GRUSE(10x1h) (Minimize Use Of 1h Groups)<br> 029 * Minimize number of groups of time that are used by the given classes. The time is spread into the following 10 groups of one hour: 7:30a-8:30a, 8:30a-9:30a, 9:30a-10:30a, ... 4:30p-5:30p. 030 * <br><br> 031 * 032 * MIN_GRUSE(5x2h) (Minimize Use Of 2h Groups)<br> 033 * Minimize number of groups of time that are used by the given classes. The time is spread into the following 5 groups of two hours: 7:30a-9:30a, 9:30a-11:30a, 11:30a-1:30p, 1:30p-3:30p, 3:30p-5:30p. 034 * <br><br> 035 * 036 * MIN_GRUSE(3x3h) (Minimize Use Of 3h Groups)<br> 037 * Minimize number of groups of time that are used by the given classes. The time is spread into the following 3 groups: 7:30a-10:30a, 10:30a-2:30p, 2:30p-5:30p. 038 * <br><br> 039 * 040 * MIN_GRUSE(2x5h) (Minimize Use Of 5h Groups)<br> 041 * Minimize number of groups of time that are used by the given classes. The time is spread into the following 2 groups: 7:30a-12:30a, 12:30a-5:30p. 042 * 043 * @version 044 * CourseTT 1.1 (University Course Timetabling)<br> 045 * Copyright (C) 2006 Tomáš Müller<br> 046 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 047 * Lazenska 391, 76314 Zlin, Czech Republic<br> 048 * <br> 049 * This library is free software; you can redistribute it and/or 050 * modify it under the terms of the GNU Lesser General Public 051 * License as published by the Free Software Foundation; either 052 * version 2.1 of the License, or (at your option) any later version. 053 * <br><br> 054 * This library is distributed in the hope that it will be useful, 055 * but WITHOUT ANY WARRANTY; without even the implied warranty of 056 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 057 * Lesser General Public License for more details. 058 * <br><br> 059 * You should have received a copy of the GNU Lesser General Public 060 * License along with this library; if not, write to the Free Software 061 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 062 */ 063 064 public class MinimizeNumberOfUsedGroupsOfTime extends Constraint implements WeakeningConstraint { 065 private int iUnassignmentsToWeaken = 250; 066 private long iUnassignment = 0; 067 private int iLimit = 1; 068 private GroupOfTime iGroupsOfTime[]; 069 private HashSet iUsage[]; 070 private boolean iEnabled = false; 071 072 private String iName = null; 073 074 public static GroupOfTime[] sGroups2of5h = new GroupOfTime[] { 075 new GroupOfTime(Constants.time2slot(7,30),Constants.time2slot(12,30),Constants.DAY_CODE_ALL), 076 new GroupOfTime(Constants.time2slot(12,30),Constants.time2slot(17,30),Constants.DAY_CODE_ALL), 077 }; 078 public static GroupOfTime[] sGroups3of3h = new GroupOfTime[] { 079 new GroupOfTime(Constants.time2slot(7,30),Constants.time2slot(10,30),Constants.DAY_CODE_ALL), 080 new GroupOfTime(Constants.time2slot(10,30),Constants.time2slot(14,30),Constants.DAY_CODE_ALL), 081 new GroupOfTime(Constants.time2slot(14,30),Constants.time2slot(17,30),Constants.DAY_CODE_ALL) 082 }; 083 public static GroupOfTime[] sGroups5of2h = new GroupOfTime[] { 084 new GroupOfTime(Constants.time2slot(7,30),Constants.time2slot(9,30),Constants.DAY_CODE_ALL), 085 new GroupOfTime(Constants.time2slot(9,30),Constants.time2slot(11,30),Constants.DAY_CODE_ALL), 086 new GroupOfTime(Constants.time2slot(11,30),Constants.time2slot(13,30),Constants.DAY_CODE_ALL), 087 new GroupOfTime(Constants.time2slot(13,30),Constants.time2slot(15,30),Constants.DAY_CODE_ALL), 088 new GroupOfTime(Constants.time2slot(15,30),Constants.time2slot(17,30),Constants.DAY_CODE_ALL) 089 }; 090 public static GroupOfTime[] sGroups10of1h = new GroupOfTime[] { 091 new GroupOfTime(Constants.time2slot(7,30),Constants.time2slot(8,30),Constants.DAY_CODE_ALL), 092 new GroupOfTime(Constants.time2slot(8,30),Constants.time2slot(9,30),Constants.DAY_CODE_ALL), 093 new GroupOfTime(Constants.time2slot(9,30),Constants.time2slot(10,30),Constants.DAY_CODE_ALL), 094 new GroupOfTime(Constants.time2slot(10,30),Constants.time2slot(11,30),Constants.DAY_CODE_ALL), 095 new GroupOfTime(Constants.time2slot(11,30),Constants.time2slot(12,30),Constants.DAY_CODE_ALL), 096 new GroupOfTime(Constants.time2slot(12,30),Constants.time2slot(13,30),Constants.DAY_CODE_ALL), 097 new GroupOfTime(Constants.time2slot(13,30),Constants.time2slot(14,30),Constants.DAY_CODE_ALL), 098 new GroupOfTime(Constants.time2slot(14,30),Constants.time2slot(15,30),Constants.DAY_CODE_ALL), 099 new GroupOfTime(Constants.time2slot(15,30),Constants.time2slot(16,30),Constants.DAY_CODE_ALL), 100 new GroupOfTime(Constants.time2slot(16,30),Constants.time2slot(17,30),Constants.DAY_CODE_ALL) 101 }; 102 103 public MinimizeNumberOfUsedGroupsOfTime(DataProperties config, String name, GroupOfTime[] groupsOfTime) { 104 iGroupsOfTime = groupsOfTime; 105 iUnassignmentsToWeaken = config.getPropertyInt("MinimizeNumberOfUsedGroupsOfTime.Unassignments2Weaken", iUnassignmentsToWeaken); 106 iName = name; 107 iUsage = new HashSet[iGroupsOfTime.length]; 108 for (int i=0;i<iUsage.length;i++) 109 iUsage[i] = new HashSet(); 110 } 111 112 public int currentUsage() { 113 int ret = 0; 114 for (int i=0;i<iUsage.length;i++) 115 if (!iUsage[i].isEmpty()) ret++; 116 return ret; 117 } 118 119 public void weaken() { 120 iUnassignment++; 121 if (iUnassignmentsToWeaken>0 && iUnassignment%iUnassignmentsToWeaken==0) iLimit++; 122 } 123 124 public boolean isOverLimit(Value value) { 125 return getOverLimit(value)>0; 126 } 127 128 public int getOverLimit(Value value) { 129 if (!iEnabled) return 0; //not enabled 130 if (iUnassignmentsToWeaken==0) return 0; //not working 131 132 Placement placement = (Placement) value; 133 Lecture lecture = (Lecture)placement.variable(); 134 TimeLocation time = placement.getTimeLocation(); 135 136 if (lecture.isCommitted()) return 0; //commited class 137 138 int usage = 0; 139 for (int i=0;i<iGroupsOfTime.length;i++) { 140 GroupOfTime groupOfTime = iGroupsOfTime[i]; 141 if (!iUsage[i].isEmpty() || groupOfTime.overlap(time)) usage++; 142 } 143 144 return usage-iLimit; 145 } 146 147 public int estimateLimit() { 148 int nrSlotsUsed = 0; 149 int minSlotsUsed = 0; 150 boolean firstLecture = true; 151 for (Enumeration e=variables().elements();e.hasMoreElements();) { 152 Lecture lecture = (Lecture)e.nextElement(); 153 boolean first = true; 154 int minSlotsUsedThisLecture = 0; 155 for (Enumeration f=lecture.timeLocations().elements();f.hasMoreElements();) { 156 TimeLocation time = (TimeLocation)f.nextElement(); 157 int min = 0; 158 for (int i=0;i<iGroupsOfTime.length;i++) { 159 if (iGroupsOfTime[i].overlap(time)) min++; 160 } 161 if (first) { 162 nrSlotsUsed += time.getLength()*time.getNrMeetings(); 163 minSlotsUsedThisLecture = min; 164 first = false; 165 } else { 166 minSlotsUsedThisLecture = Math.min(minSlotsUsedThisLecture,min); 167 } 168 } 169 if (firstLecture) { 170 minSlotsUsed = minSlotsUsedThisLecture; 171 firstLecture = false; 172 } else { 173 minSlotsUsed = Math.min(minSlotsUsed, minSlotsUsedThisLecture); 174 } 175 } 176 return Math.max(Math.max(1, (int)Math.ceil(((double)nrSlotsUsed)/iGroupsOfTime[0].size())), minSlotsUsed); 177 } 178 179 public void computeConflicts(Value value, Set conflicts) { 180 int overLimit = getOverLimit(value); 181 if (overLimit>0) { 182 Placement placement = (Placement) value; 183 Lecture lecture = (Lecture)placement.variable(); 184 TimeLocation time = placement.getTimeLocation(); 185 186 Vector adepts = new Vector(); 187 for (int i=0;i<iGroupsOfTime.length;i++) { 188 GroupOfTime groupOfTime = iGroupsOfTime[i]; 189 HashSet usage = iUsage[i]; 190 if (groupOfTime.overlap(time) || usage.isEmpty()) continue; 191 boolean canUnassign = true; 192 Vector placementsToUnassign = new Vector(usage.size()); 193 for (Iterator j=usage.iterator();j.hasNext();) { 194 Placement p = (Placement)j.next(); 195 Lecture l = (Lecture)p.variable(); 196 if (l.isCommitted()) { 197 canUnassign = false; break; 198 } 199 if (!conflicts.contains(p)) 200 placementsToUnassign.addElement(p); 201 } 202 if (!canUnassign) continue; 203 adepts.add(placementsToUnassign); 204 } 205 if (adepts.size()<overLimit) { 206 conflicts.add(value); 207 } else { 208 Collections.sort(adepts, new Comparator() { 209 public int compare(Object o1, Object o2) { 210 Collection c1 = (Collection)o1; 211 Collection c2 = (Collection)o2; 212 return Double.compare(c1.size(), c2.size()); 213 } 214 }); 215 for (int i=0;i<overLimit;i++) { 216 conflicts.addAll((Collection)adepts.elementAt(i)); 217 } 218 } 219 } 220 } 221 222 public boolean inConflict(Value value) { 223 return isOverLimit(value); 224 } 225 226 public boolean isConsistent(Value value1, Value value2) { 227 return (isOverLimit(value1) || isOverLimit(value2)); 228 } 229 230 231 public void assigned(long iteration, Value value) { 232 super.assigned(iteration, value); 233 Placement placement = (Placement) value; 234 TimeLocation time = placement.getTimeLocation(); 235 for (int i=0;i<iGroupsOfTime.length;i++) { 236 GroupOfTime groupOfTime = iGroupsOfTime[i]; 237 HashSet usage = iUsage[i]; 238 if (groupOfTime.overlap(time)) usage.add(placement); 239 } 240 } 241 242 public void unassigned(long iteration, Value value) { 243 super.unassigned(iteration, value); 244 Placement placement = (Placement) value; 245 TimeLocation time = placement.getTimeLocation(); 246 for (int i=0;i<iGroupsOfTime.length;i++) { 247 GroupOfTime groupOfTime = iGroupsOfTime[i]; 248 HashSet usage = iUsage[i]; 249 if (groupOfTime.overlap(time)) usage.remove(placement); 250 } 251 } 252 253 254 public String getConstraintName() { return "MIN_GRUSE("+iName+")"; } 255 256 public String getName() { return "Minimize number of used groups of time ("+iName+")"; } 257 258 public void setEnabled(boolean enabled) { 259 iEnabled = enabled; 260 iLimit = Math.max(currentUsage(), estimateLimit()); 261 } 262 263 public boolean isEnabled() { return iEnabled; } 264 265 private static class GroupOfTime { 266 private int iStartSlot = 0; 267 private int iEndSlot = 0; 268 private int iDays = 0; 269 public GroupOfTime(int startSlot, int endSlot, int days) { 270 iStartSlot = startSlot; 271 iEndSlot = endSlot; 272 iDays = days; 273 } 274 275 public int getStartSlot() { return iStartSlot; } 276 public int getEndSlot() { return iEndSlot; } 277 public int getDays() { return iDays; } 278 public int nrDays() { 279 int ret = 0; 280 for (int i=0;i<Constants.DAY_CODES.length;i++) { 281 if ((getDays()&Constants.DAY_CODES[i])!=0) ret++; 282 } 283 return ret; 284 } 285 public int size() { 286 return (getEndSlot()-getStartSlot())*nrDays(); 287 } 288 public boolean overlap(TimeLocation timeLocation) { 289 if ((timeLocation.getDayCode()&iDays)==0) return false; 290 int end = Math.min(iEndSlot, timeLocation.getStartSlot()+timeLocation.getLength()); 291 int start = Math.max(iStartSlot, timeLocation.getStartSlot()); 292 int nrSharedSlots = (end<start?0:end-start); 293 return (nrSharedSlots>0); 294 } 295 } 296 297 public String toString() { 298 StringBuffer sb = new StringBuffer(); 299 sb.append("Minimize Use Of "+(Constants.SLOT_LENGTH_MIN*(iGroupsOfTime[0].getEndSlot()-iGroupsOfTime[0].getStartSlot()))+"min Groups between "); 300 for (Enumeration e=variables().elements();e.hasMoreElements();) { 301 Variable v = (Variable)e.nextElement(); 302 sb.append(v.getName()); 303 if (e.hasMoreElements()) sb.append(", "); 304 } 305 return sb.toString(); 306 } 307 308 }