    001package net.sf.cpsolver.coursett.criteria.additional;
    002
    003import java.util.BitSet;
    004import java.util.Collection;
    005import java.util.HashMap;
    006import java.util.HashSet;
    007import java.util.List;
    008import java.util.Map;
    009import java.util.Set;
    010import java.util.TreeSet;
    011
    012import net.sf.cpsolver.coursett.Constants;
    013import net.sf.cpsolver.coursett.constraint.InstructorConstraint;
    014import net.sf.cpsolver.coursett.model.Lecture;
    015import net.sf.cpsolver.coursett.model.Placement;
    016import net.sf.cpsolver.coursett.model.TimetableModel;
    017import net.sf.cpsolver.ifs.criteria.AbstractCriterion;
    018import net.sf.cpsolver.ifs.solver.Solver;
    019
    020/**
    021 * The class represents various criteria concerning compact timetables of
    022 * instructors. The criteria are checked and updated when a variable is
    023 * (un)assigned.
    024 * <br>
    025 * implemented criterion: lunch break
    026 * <br>
    027 * @version CourseTT 1.2 (University Course Timetabling)<br>
    028 *          Copyright (C) 2012 Matej Lukac<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 InstructorLunchBreak extends AbstractCriterion<Lecture, Placement> {
    045    // lunch attributes
    046    private double iMultiplier;
    047    private int iLunchStart, iLunchEnd, iLunchLength;
    048    private boolean iFullInfo;
    049    private List<BitSet> iWeeks = null;
    050
    051    private Map<InstructorConstraint, CompactInfo> iCompactInfos = new HashMap<InstructorConstraint, CompactInfo>();
    052
    053    public InstructorLunchBreak() {
    054        iValueUpdateType = ValueUpdateType.NoUpdate;
    055    }
    056
    057    @Override
    058    public boolean init(Solver<Lecture, Placement> solver) {
    059        super.init(solver);
    060
    061        iWeight = solver.getProperties().getPropertyDouble("InstructorLunch.Weight", 0.3d);
    062
    063        // lunch parameters
    064        iLunchStart = solver.getProperties().getPropertyInt("InstructorLunch.StartSlot", (11 * 60) / 5);
    065        iLunchEnd = solver.getProperties().getPropertyInt("InstructorLunch.EndSlot", (13 * 60 + 30) / 5);
    066        iLunchLength = solver.getProperties().getPropertyInt("InstructorLunch.Length", 30 / 5);
    067        iMultiplier = solver.getProperties().getPropertyDouble("InstructorLunch.Multiplier", 1.2d);
    068        iFullInfo = solver.getProperties().getPropertyBoolean("InstructorLunch.InfoShowViolations", false);
    069
    070        return true;
    071    }
    072
    073    /**
    074     * Get compact info that is associated with an instructor constraint.
    075     * Create a new one if none has been created yet.
    076     */
    077    protected CompactInfo getCompactInfo(InstructorConstraint constraint) {
    078        CompactInfo info = iCompactInfos.get(constraint);
    079        if (info == null) {
    080            info = new CompactInfo();
    081            iCompactInfos.put(constraint, info);
    082        }
    083        return info;
    084    }
    085
    086    /**
    087     * Update criterion after an assignment.
    088     */
    089    @Override
    090    public void afterAssigned(long iteration, Placement value) {
    091        super.afterAssigned(iteration, value);
    092        for (InstructorConstraint constraint: value.variable().getInstructorConstraints())
    093            updateCriterion(constraint, value);
    094    }
    095
    096    /**
    097     * Update criterion after an unassignment
    098     */
    099    @Override
    100    public void afterUnassigned(long iteration, Placement value) {
    101        super.afterUnassigned(iteration, value);
    102        for (InstructorConstraint constraint: value.variable().getInstructorConstraints())
    103            updateCriterion(constraint, value);
    104    }
    105
    106    /**
    107     * The method creates date patterns (bitsets) which represent the weeks of a
    108     * semester.
    109     *
    110     * @return a list of BitSets which represents the weeks of a semester.
    111     */
    112    protected List<BitSet> getWeeks() {
    113        if (iWeeks == null) {
    114            TimetableModel model = (TimetableModel) getModel();
    115            iWeeks = model.getWeeks();
    116        }
    117        return iWeeks;
    118    }
    119
    120    /**
    121     * Method updates number of violations in days (Mo, Tue, Wed,..) considering
    122     * each week in the semester separately. The current number of violations
    123     * for a day is stored in the CompactInfo.lunchDayViolations of the
    124     * constraint, which must be set properly before the calling of the method.
    125     *
    126     * @param constraint
    127     *            the Instructor constraint of an instructor checked for a lunch
    128     *            break
    129     * @param p
    130     *            placement of a lecture currently (un)assigned
    131     */
    132    public void updateLunchPenalty(InstructorConstraint constraint, Placement p) {
    133        // checks only placements in the lunch time
    134        if (p.getTimeLocation().getStartSlot() <= iLunchEnd && p.getTimeLocation().getStartSlot() + p.getTimeLocation().getLength() > iLunchStart) {
    135            CompactInfo compactInfo = getCompactInfo(constraint);
    136            for (int i = 0; i < Constants.NR_DAYS; i++) {
    137                // checks only days affected by the placement
    138                if ((p.getTimeLocation().getDayCode() & Constants.DAY_CODES[i]) != 0) {
    139                    int currentLunchStartSlot = Constants.SLOTS_PER_DAY * i + iLunchStart;
    140                    int currentLunchEndSlot = Constants.SLOTS_PER_DAY * i + iLunchEnd;
    141                    int semesterViolations = 0;
    142                    for (BitSet week : getWeeks()) {
    143                        int maxBreak = 0;
    144                        int currentBreak = 0;
    145                        for (int slot = currentLunchStartSlot; slot < currentLunchEndSlot; slot++) {
    146                            if (constraint.getPlacements(slot, week).isEmpty()) {
    147                                currentBreak++;
    148                                if (maxBreak < currentBreak) {
    149                                    maxBreak = currentBreak;
    150                                }
    151                            } else {
    152                                currentBreak = 0;
    153                            }
    154                        }
    155                        if (maxBreak < iLunchLength) {
    156                            semesterViolations++;
    157                        }
    158                    }
    159                    // saving the result in the CompactInfo of the
    160                    // InstructorConstraint
    161                    compactInfo.getLunchDayViolations()[i] = semesterViolations;
    162                }
    163            }
    164        }
    165    }
    166
    167    /**
    168     * Method checks or sets the CompactInfo of an InstructorConstraint. It
    169     * updates the preference of chosen criteria. The update consists of
    170     * decrementing the criterion value by previous preference, finding the
    171     * current preference and incrementing the criterion value by the current
    172     * preference.
    173     *
    174     * @param instructorConstraint
    175     *            the Instructor constraint of an instructor checked for
    176     *            criteria
    177     * @param placement
    178     *            placement of a lecture currently (un)assigned
    179     */
    180    public void updateCriterion(InstructorConstraint instructorConstraint, Placement placement) {
    181            iValue -= getLunchPreference(instructorConstraint);
    182            updateLunchPenalty(instructorConstraint, placement);
    183            iValue += getLunchPreference(instructorConstraint);
    184    }
    185
    186    /**
    187     * Method uses the CompactInfo of the InstructorConstraint and returns the
    188     * lunch preference for this constraint. Calculation formula does not use
    189     * linear function, the number of violations is multiplied by a power of
    190     * iMultiplier.
    191     *
    192     * @param instructorConstraint
    193     *            the Instructor constraint of an instructor checked for a lunch
    194     *            break
    195     * @return the lunch preference for this constraint
    196     */
    197    private double getLunchPreference(InstructorConstraint instructorConstraint) {
    198        double violations = 0d;
    199        CompactInfo info = getCompactInfo(instructorConstraint);
    200        for (int i = 0; i < Constants.NR_DAYS; i++)
    201            violations += info.getLunchDayViolations()[i];
    202        return Math.pow(violations, iMultiplier);
    203    }
    204
    205    @Override
    206    public double getValue(Placement value, Set<Placement> conflicts) {
    207        return iValue;
    208    }
    209
    210    @Override
    211    public double getWeightedValue(Placement value, Set<Placement> conflicts) {
    212        return iValue * iWeight;
    213    }
    214
    215    @Override
    216    public double getValue(Collection<Lecture> variables) {
    217        double lunchValue = 0.0d;
    218        Set<InstructorConstraint> constraints = new HashSet<InstructorConstraint>();
    219        for (Lecture lecture : variables) {
    220            constraints.addAll(lecture.getInstructorConstraints());
    221        }
    222        for (InstructorConstraint instructor : constraints) {
    223            lunchValue += getLunchPreference(instructor);
    224        }
    225        return lunchValue;
    226    }
    227
    228    @Override
    229    public void getInfo(Map<String, String> info) {
    230        Set<String> violatedLunchBreaks = new TreeSet<String>();
    231        int lunchViolations = 0;
    232        for (InstructorConstraint c : ((TimetableModel)getModel()).getInstructorConstraints()) {
    233            String days = "";
    234            CompactInfo compactInfo = getCompactInfo(c);
    235            for (int i = 0; i < Constants.NR_DAYS; i++) {
    236                if (compactInfo.getLunchDayViolations()[i] > 0) {
    237                    if (iFullInfo)
    238                        days += (days.isEmpty() ? "" : ", ") + compactInfo.getLunchDayViolations()[i] + " &times; " + Constants.DAY_NAMES_SHORT[i];
    239                    lunchViolations += compactInfo.getLunchDayViolations()[i];
    240                }
    241            }
    242            if (iFullInfo && !days.isEmpty())
    243                violatedLunchBreaks.add(c.getName() + ": " + days);
    244        }
    245        if (lunchViolations > 0) {
    246            info.put("Lunch breaks", getPerc(lunchViolations, 0, ((TimetableModel)getModel()).getInstructorConstraints().size() * Constants.NR_DAYS * getWeeks().size()) + "% (" + lunchViolations + ")");
    247            if (iFullInfo && !violatedLunchBreaks.isEmpty()) {
    248                String message = "";
    249                for (String s: violatedLunchBreaks)
    250                    message += (message.isEmpty() ? "" : "<br>") + s;
    251                info.put("Lunch break violations", message);
    252            }
    253        }
    254    }
    255
    256    @Override
    257    public void getInfo(Map<String, String> info, Collection<Lecture> variables) {
    258        Set<InstructorConstraint> constraints = new HashSet<InstructorConstraint>();
    259        for (Lecture lecture : variables) {
    260            for (InstructorConstraint c : lecture.getInstructorConstraints()) {
    261                constraints.add(c);
    262            }
    263        }
    264        Set<String> violatedLunchBreaks = new TreeSet<String>();
    265        int lunchViolations = 0;
    266        for (InstructorConstraint c : constraints) {
    267            String days = "";
    268            CompactInfo compactInfo = getCompactInfo(c);
    269            for (int i = 0; i < Constants.NR_DAYS; i++) {
    270                if (compactInfo.getLunchDayViolations()[i] > 0) {
    271                    if (iFullInfo)
    272                        days += (days.isEmpty() ? "" : ", ") + compactInfo.getLunchDayViolations()[i] + " &times; " + Constants.DAY_NAMES_SHORT[i];
    273                    lunchViolations += compactInfo.getLunchDayViolations()[i];
    274                }
    275            }
    276            if (iFullInfo && !days.isEmpty())
    277                violatedLunchBreaks.add(c.getName() + ": " + days);
    278        }
    279        if (lunchViolations > 0) {
    280            info.put("Lunch breaks", getPerc(lunchViolations, 0, constraints.size() * Constants.NR_DAYS * getWeeks().size()) + "% (" + lunchViolations + ")");
    281            if (iFullInfo && !violatedLunchBreaks.isEmpty()) {
    282                String message = "";
    283                for (String s: violatedLunchBreaks)
    284                    message += (message.isEmpty() ? "" : "; ") + s;
    285                info.put("Lunch break violations", message);
    286            }
    287        }
    288    }
    289
    290    /**
    291     * The class is used as a container of information concerning lunch break
    292     * of instructors. It is designed as an attribute of an
    293     * InstructorConstraint.
    294     */
    295    public static class CompactInfo {
    296        // lunch attributes
    297        private int[] iLunchDayViolations = new int[Constants.NR_DAYS];
    298
    299        public CompactInfo() {
    300        }
    301
    302        public int[] getLunchDayViolations() { return iLunchDayViolations; }
    303    }
    304}