001package org.cpsolver.coursett.constraint; 002 003import java.util.ArrayList; 004import java.util.BitSet; 005import java.util.Collections; 006import java.util.HashMap; 007import java.util.List; 008import java.util.Map; 009import java.util.Set; 010import java.util.regex.Matcher; 011import java.util.regex.Pattern; 012 013import org.cpsolver.coursett.Constants; 014import org.cpsolver.coursett.model.Lecture; 015import org.cpsolver.coursett.model.Placement; 016import org.cpsolver.ifs.assignment.Assignment; 017import org.cpsolver.ifs.model.WeakeningConstraint; 018import org.cpsolver.ifs.util.ToolBox; 019 020 021/** 022 * 023 * The MaxBreaks constraint limits the number of blocks of non back-to-back classes of an instructor on a day.<br> 024 * It has two parameters: a maximal number of breaks and a minimal length of a break between two classes not to be considered in the same block.<br> 025 * Reference _MaxBreaks:1:30_ translates to a maximum number of one break (two blocks) on a day of classes not more than 30 minutes a part.<br> 026 * 027 * @version CourseTT 1.3 (University Course Timetabling)<br> 028 * Copyright (C) 2013 - 2014 Tomáš Müller<br> 029 * <br> 030 * This library is free software; you can redistribute it and/or modify 031 * it under the terms of the GNU Lesser General Public License as 032 * published by the Free Software Foundation; either version 3 of the 033 * License, or (at your option) any later version. <br> 034 * <br> 035 * This library is distributed in the hope that it will be useful, but 036 * WITHOUT ANY WARRANTY; without even the implied warranty of 037 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 038 * Lesser General Public License for more details. <br> 039 * <br> 040 * You should have received a copy of the GNU Lesser General Public 041 * License along with this library; if not see 042 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 043 */ 044public class MaxBreaksFlexibleConstraint extends FlexibleConstraint implements WeakeningConstraint<Lecture, Placement> { 045 private int iMaxBreakBetweenBTB; 046 protected int iMaxBlocksOnADay; 047 048 public MaxBreaksFlexibleConstraint(Long id, String owner, String preference, String reference) { 049 super(id, owner, preference, reference); 050 051 Matcher matcher = Pattern.compile(FlexibleConstraintType.MAX_BREAKS.getPattern()).matcher(reference); 052 if (matcher.find()) { 053 iMaxBlocksOnADay = 1 + Integer.parseInt(matcher.group(2)); 054 iMaxBreakBetweenBTB = Integer.parseInt(matcher.group(3)) / Constants.SLOT_LENGTH_MIN; 055 iConstraintType = FlexibleConstraintType.MAX_BREAKS; 056 } 057 } 058 059 public List<Block> getBlocks(Assignment<Lecture, Placement> assignment, int dayCode, Set<Placement> conflicts, Placement value, HashMap<Lecture, Placement> assignments, BitSet week) { 060 List<Placement> toBeSorted = new ArrayList<Placement>(getRelevantPlacements(assignment, dayCode, conflicts, value, assignments, week)); 061 Collections.sort(toBeSorted, new PlacementTimeComparator()); 062 063 return mergeToBlocks(toBeSorted, iMaxBreakBetweenBTB); 064 } 065 066 @Override 067 public double getNrViolations(Assignment<Lecture, Placement> assignment, Set<Placement> conflicts, HashMap<Lecture, Placement> assignments) { 068 int penalty = 0; 069 // constraint is checked for every day in week 070 for (int dayCode : Constants.DAY_CODES) { 071 // constraint is checked for every week in semester (or for the whole semester) 072 for (BitSet week : getWeeks()) { 073 // each blocks contains placements which are BTB 074 List<Block> blocks = getBlocks(assignment, dayCode, null, null, assignments, week); 075 // too many blocks -> increase penalty 076 if (blocks.size() > iMaxBlocksOnADay) 077 penalty += (blocks.size() - iMaxBlocksOnADay) * (blocks.size() - iMaxBlocksOnADay); 078 } 079 } 080 return penalty; 081 } 082 083 @Override 084 public void computeConflicts(Assignment<Lecture, Placement> assignment, Placement value, Set<Placement> conflicts) { 085 if (!isHard()) return; 086 087 if (((MaxBreaksFlexibleConstraintContext)getContext(assignment)).isWeak(value)) { 088 for (Lecture v: variables()) 089 if (assignment.getValue(v) == null && !v.equals(value.variable())) { 090 // incomplete and week -- do not check for conflicts just yet 091 return; 092 } 093 } 094 095 // constraint is checked for every day in week 096 for (int dayCode : Constants.DAY_CODES) { 097 if ((value.getTimeLocation().getDayCode() & dayCode) == 0) continue; // ignore other days 098 // constraint is checked for every week in semester (or for the whole semester) 099 for (BitSet week : getWeeks()) { 100 if (isPreciseDateComputation()) { 101 if (!value.getTimeLocation().overlaps(dayCode, week, getDayOfWeekOffset())) continue; 102 } else { 103 if (week != null && !week.intersects(value.getTimeLocation().getWeekCode())) continue; // ignore other weeks 104 } 105 // each blocks contains placements which are BTB 106 List<Block> blocks = getBlocks(assignment, dayCode, conflicts, value, null, week); 107 while (blocks.size() > iMaxBlocksOnADay) { 108 // too many blocks -> identify adepts for unassignment 109 List<Block> adepts = new ArrayList<Block>(); int size = 0; 110 for (Block block: blocks) { 111 if (block.getPlacements().contains(value)) continue; // skip block containing given value 112 // select adepts of the smallest size 113 if (adepts.isEmpty() || size > block.getPlacements().size()) { 114 adepts.clear(); 115 adepts.add(block); 116 size = block.getPlacements().size(); 117 } else if (size == block.getPlacements().size()) { 118 adepts.add(block); 119 } 120 } 121 122 // pick one randomly 123 Block block = ToolBox.random(adepts); 124 blocks.remove(block); 125 for (Placement conflict: block.getPlacements()) 126 if (conflict.equals(assignment.getValue(conflict.variable()))) 127 conflicts.add(conflict); 128 } 129 } 130 } 131 } 132 133 @Override 134 public void weaken(Assignment<Lecture, Placement> assignment) { 135 } 136 137 @Override 138 public void weaken(Assignment<Lecture, Placement> assignment, Placement value) { 139 if (isHard()) 140 ((MaxBreaksFlexibleConstraintContext)getContext(assignment)).weaken(value); 141 } 142 143 @Override 144 public FlexibleConstraintContext createAssignmentContext(Assignment<Lecture, Placement> assignment) { 145 return new MaxBreaksFlexibleConstraintContext(assignment); 146 } 147 148 public class MaxBreaksFlexibleConstraintContext extends FlexibleConstraintContext { 149 private Map<Lecture, Placement> iWeakAssignment = new HashMap<Lecture, Placement>(); 150 151 public MaxBreaksFlexibleConstraintContext(Assignment<Lecture, Placement> assignment) { 152 super(assignment); 153 } 154 155 @Override 156 public void assigned(Assignment<Lecture, Placement> assignment, Placement value) { 157 super.assigned(assignment, value); 158 if (isHard()) 159 iWeakAssignment.remove(value.variable()); 160 } 161 162 public void weaken(Placement value) { 163 if (isHard()) 164 iWeakAssignment.put(value.variable(), value); 165 } 166 167 public boolean isWeak(Placement value) { 168 return value.equals(iWeakAssignment.get(value.variable())); 169 } 170 } 171 172}