001package org.cpsolver.coursett.criteria; 002 003import java.util.Collection; 004import java.util.HashSet; 005import java.util.List; 006import java.util.Set; 007 008import org.cpsolver.coursett.Constants; 009import org.cpsolver.coursett.constraint.RoomConstraint; 010import org.cpsolver.coursett.constraint.RoomConstraint.RoomConstraintContext; 011import org.cpsolver.coursett.model.Lecture; 012import org.cpsolver.coursett.model.Placement; 013import org.cpsolver.coursett.model.RoomLocation; 014import org.cpsolver.coursett.model.TimeLocation; 015import org.cpsolver.coursett.model.TimetableModel; 016import org.cpsolver.ifs.assignment.Assignment; 017import org.cpsolver.ifs.util.DataProperties; 018 019 020/** 021 * Broken time patterns. This criterion counts cases when an unused space is in a room 022 * which follows one of the standard MWF or TTh pattern. E.g., there is a penalty of 023 * Monday is available during a time when Wednesday and/or Friday is occupied. The aim 024 * is to use this space if possible in order to leave the available space in a way that 025 * can be used by MWF or TTh classes. 026 * <br> 027 * 028 * @version CourseTT 1.3 (University Course Timetabling)<br> 029 * Copyright (C) 2006 - 2014 Tomáš Müller<br> 030 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 031 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 032 * <br> 033 * This library is free software; you can redistribute it and/or modify 034 * it under the terms of the GNU Lesser General Public License as 035 * published by the Free Software Foundation; either version 3 of the 036 * License, or (at your option) any later version. <br> 037 * <br> 038 * This library is distributed in the hope that it will be useful, but 039 * WITHOUT ANY WARRANTY; without even the implied warranty of 040 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 041 * Lesser General Public License for more details. <br> 042 * <br> 043 * You should have received a copy of the GNU Lesser General Public 044 * License along with this library; if not see 045 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 046 */ 047public class BrokenTimePatterns extends TimetablingCriterion { 048 private int iFirstDaySlot, iLastDaySlot, iFirstWorkDay, iLastWorkDay; 049 050 public BrokenTimePatterns() { 051 setValueUpdateType(ValueUpdateType.NoUpdate); 052 } 053 054 @Override 055 public void configure(DataProperties properties) { 056 super.configure(properties); 057 iFirstDaySlot = properties.getPropertyInt("General.FirstDaySlot", Constants.DAY_SLOTS_FIRST); 058 iLastDaySlot = properties.getPropertyInt("General.LastDaySlot", Constants.DAY_SLOTS_LAST); 059 iFirstWorkDay = properties.getPropertyInt("General.FirstWorkDay", 0); 060 iLastWorkDay = properties.getPropertyInt("General.LastWorkDay", Constants.NR_DAYS_WEEK - 1); 061 if (iLastWorkDay < iFirstWorkDay) iLastWorkDay += 7; 062 } 063 064 @Override 065 public double getWeightDefault(DataProperties config) { 066 return Constants.sPreferenceLevelDiscouraged * config.getPropertyDouble("Comparator.UselessSlotWeight", 0.1); 067 } 068 069 @Override 070 public String getPlacementSelectionWeightName() { 071 return "Placement.UselessSlotsWeight"; 072 } 073 074 protected double penalty(Assignment<Lecture, Placement> assignment, Placement value) { 075 if (value.isMultiRoom()) { 076 int ret = 0; 077 for (RoomLocation r : value.getRoomLocations()) { 078 if (r.getRoomConstraint() == null) 079 continue; 080 ret += penalty(r.getRoomConstraint().getContext(assignment), value); 081 } 082 return ret; 083 } else { 084 return (value.getRoomLocation().getRoomConstraint() == null ? 0 : penalty(value.getRoomLocation().getRoomConstraint().getContext(assignment), value)); 085 } 086 } 087 088 protected double penalty(RoomConstraintContext rc) { 089 return countUselessSlotsBrokenTimePatterns(rc) / 6.0; 090 } 091 092 protected double penalty(RoomConstraintContext rc, Placement value) { 093 return countUselessSlotsBrokenTimePatterns(rc, value) / 6.0; 094 } 095 096 @Override 097 public double getValue(Assignment<Lecture, Placement> assignment, Placement value, Set<Placement> conflicts) { 098 double ret = penalty(assignment, value); 099 if (conflicts != null) 100 for (Placement conflict: conflicts) 101 ret -= penalty(assignment, conflict); 102 return ret; 103 } 104 105 @Override 106 public double getValue(Assignment<Lecture, Placement> assignment, Collection<Lecture> variables) { 107 double ret = 0; 108 Set<RoomConstraint> constraints = new HashSet<RoomConstraint>(); 109 for (Lecture lect: variables) { 110 Placement placement = assignment.getValue(lect); 111 if (placement == null) continue; 112 if (placement.isMultiRoom()) { 113 for (RoomLocation r : placement.getRoomLocations()) { 114 if (r.getRoomConstraint() != null && constraints.add(r.getRoomConstraint())) 115 ret += penalty(r.getRoomConstraint().getContext(assignment)); 116 } 117 } else if (placement.getRoomLocation().getRoomConstraint() != null && 118 constraints.add(placement.getRoomLocation().getRoomConstraint())) { 119 ret += penalty(placement.getRoomLocation().getRoomConstraint().getContext(assignment)); 120 } 121 } 122 return ret; 123 } 124 125 @Override 126 protected double[] computeBounds(Assignment<Lecture, Placement> assignment) { 127 return new double[] { 128 ((TimetableModel)getModel()).getRoomConstraints().size() * (iLastWorkDay - iFirstWorkDay + 1) * (iLastDaySlot - iFirstDaySlot + 1), 129 0.0 }; 130 } 131 132 @Override 133 public double[] getBounds(Assignment<Lecture, Placement> assignment, Collection<Lecture> variables) { 134 Set<RoomConstraint> constraints = new HashSet<RoomConstraint>(); 135 for (Lecture lect: variables) { 136 Placement placement = assignment.getValue(lect); 137 if (placement == null) continue; 138 if (placement.isMultiRoom()) { 139 for (RoomLocation r : placement.getRoomLocations()) { 140 if (r.getRoomConstraint() != null) 141 constraints.add(r.getRoomConstraint()); 142 } 143 } else if (placement.getRoomLocation().getRoomConstraint() != null) { 144 constraints.add(placement.getRoomLocation().getRoomConstraint()); 145 } 146 } 147 return new double[] { 148 constraints.size() * (iLastWorkDay - iFirstWorkDay + 1) * (iLastDaySlot - iFirstDaySlot + 1), 149 0.0 }; 150 } 151 152 private static int sDaysMWF = Constants.DAY_CODES[0] + Constants.DAY_CODES[2] + Constants.DAY_CODES[4]; 153 private static int sDaysTTh = Constants.DAY_CODES[1] + Constants.DAY_CODES[3]; 154 155 private static boolean isEmpty(RoomConstraintContext rc, int s, int d, Placement placement) { 156 List<Placement> assigned = rc.getPlacements(d * Constants.SLOTS_PER_DAY + s); 157 return assigned.isEmpty() || (placement != null && assigned.size() == 1 && assigned.get(0).variable().equals(placement.variable())); 158 } 159 160 /** Number of broken time patterns for this room 161 * @param rc room constraint 162 * @param placement placement that is being considered 163 * @return number of broken time patterns caused by the given placement 164 **/ 165 protected static int countUselessSlotsBrokenTimePatterns(RoomConstraintContext rc, Placement placement) { 166 int ret = 0; 167 TimeLocation time = placement.getTimeLocation(); 168 int slot = time.getStartSlot() % Constants.SLOTS_PER_DAY; 169 int days = time.getDayCode(); 170 if ((days & sDaysMWF) != 0 && (days & sDaysMWF) != sDaysMWF) { 171 for (int s = slot; s < slot + time.getLength(); s++) { 172 int d = (days & sDaysMWF); 173 if (d == Constants.DAY_CODES[0] && isEmpty(rc, s, 0, placement)) { 174 if (isEmpty(rc, s, 2, placement) != isEmpty(rc, s, 4, placement)) ret ++; 175 if (!isEmpty(rc, s, 2, placement) && !isEmpty(rc, s, 4, placement)) ret --; 176 } else if (d == Constants.DAY_CODES[2] && isEmpty(rc, s, 2, placement)) { 177 if (isEmpty(rc, s, 0, placement) != isEmpty(rc, s, 4, placement)) ret ++; 178 if (!isEmpty(rc, s, 0, placement) && !isEmpty(rc, s, 4, placement)) ret --; 179 } else if (d == Constants.DAY_CODES[4] && isEmpty(rc, s, 4, placement)) { 180 if (isEmpty(rc, s, 0, placement) != isEmpty(rc, s, 2, placement)) ret ++; 181 if (!isEmpty(rc, s, 0, placement) && !isEmpty(rc, s, 2, placement)) ret --; 182 } else if (d == (Constants.DAY_CODES[0] | Constants.DAY_CODES[2]) && isEmpty(rc, s, 0, placement) && isEmpty(rc, s, 2, placement)) { 183 if (isEmpty(rc, s, 4, placement)) ret ++; 184 } else if (d == (Constants.DAY_CODES[2] | Constants.DAY_CODES[4]) && isEmpty(rc, s, 2, placement) && isEmpty(rc, s, 4, placement)) { 185 if (isEmpty(rc, s, 0, placement)) ret ++; 186 } else if (d == (Constants.DAY_CODES[0] | Constants.DAY_CODES[4]) && isEmpty(rc, s, 0, placement) && isEmpty(rc, s, 4, placement)) { 187 if (isEmpty(rc, s, 2, placement)) ret ++; 188 } 189 } 190 } 191 if ((days & sDaysTTh) != 0 && (days & sDaysTTh) != sDaysTTh) { 192 for (int s = slot; s < slot + time.getLength(); s++) { 193 if (isEmpty(rc, s, 1, placement) && isEmpty(rc, s, 3, placement)) ret ++; 194 int d = (days & sDaysTTh); 195 if (d == Constants.DAY_CODES[1] && isEmpty(rc, s, 1, placement) && !isEmpty(rc, s, 3, placement)) ret --; 196 if (d == Constants.DAY_CODES[3] && isEmpty(rc, s, 3, placement) && !isEmpty(rc, s, 1, placement)) ret --; 197 } 198 } 199 return ret; 200 } 201 202 /** Number of useless slots for this room 203 * @param rc room constraint 204 * @return current penalty for the given room 205 **/ 206 public static int countUselessSlotsBrokenTimePatterns(RoomConstraintContext rc) { 207 int ret = 0; 208 for (int d = 0; d < Constants.NR_DAYS; d++) { 209 for (int s = 0; s < Constants.SLOTS_PER_DAY; s++) { 210 int slot = d * Constants.SLOTS_PER_DAY + s; 211 if (rc.getPlacements(slot).isEmpty()) { 212 switch (d) { 213 case 0: 214 if (!rc.getPlacements(2 * Constants.SLOTS_PER_DAY + s).isEmpty() && !rc.getPlacements(4 * Constants.SLOTS_PER_DAY + s).isEmpty()) 215 ret++; 216 break; 217 case 1: 218 if (!rc.getPlacements(3 * Constants.SLOTS_PER_DAY + s).isEmpty()) 219 ret++; 220 break; 221 case 2: 222 if (!rc.getPlacements(0 * Constants.SLOTS_PER_DAY + s).isEmpty() && !rc.getPlacements(4 * Constants.SLOTS_PER_DAY + s).isEmpty()) 223 ret++; 224 break; 225 case 3: 226 if (!rc.getPlacements(1 * Constants.SLOTS_PER_DAY + s).isEmpty()) 227 ret++; 228 break; 229 case 4: 230 if (!rc.getPlacements(0 * Constants.SLOTS_PER_DAY + s).isEmpty() && !rc.getPlacements(2 * Constants.SLOTS_PER_DAY + s).isEmpty()) 231 ret++; 232 break; 233 } 234 } 235 } 236 } 237 return ret; 238 } 239}