    001package org.cpsolver.coursett.model;
    002
    003import java.util.ArrayList;
    004import java.util.BitSet;
    005import java.util.Collection;
    006import java.util.HashSet;
    007import java.util.Iterator;
    008import java.util.HashMap;
    009import java.util.List;
    010import java.util.Locale;
    011import java.util.Map;
    012import java.util.Set;
    013
    014import org.cpsolver.coursett.Constants;
    015import org.cpsolver.coursett.constraint.ClassLimitConstraint;
    016import org.cpsolver.coursett.constraint.DepartmentSpreadConstraint;
    017import org.cpsolver.coursett.constraint.FlexibleConstraint;
    018import org.cpsolver.coursett.constraint.GroupConstraint;
    019import org.cpsolver.coursett.constraint.InstructorConstraint;
    020import org.cpsolver.coursett.constraint.JenrlConstraint;
    021import org.cpsolver.coursett.constraint.RoomConstraint;
    022import org.cpsolver.coursett.constraint.SpreadConstraint;
    023import org.cpsolver.coursett.criteria.BackToBackInstructorPreferences;
    024import org.cpsolver.coursett.criteria.BrokenTimePatterns;
    025import org.cpsolver.coursett.criteria.DepartmentBalancingPenalty;
    026import org.cpsolver.coursett.criteria.DistributionPreferences;
    027import org.cpsolver.coursett.criteria.FlexibleConstraintCriterion;
    028import org.cpsolver.coursett.criteria.Perturbations;
    029import org.cpsolver.coursett.criteria.RoomPreferences;
    030import org.cpsolver.coursett.criteria.RoomViolations;
    031import org.cpsolver.coursett.criteria.SameSubpartBalancingPenalty;
    032import org.cpsolver.coursett.criteria.StudentCommittedConflict;
    033import org.cpsolver.coursett.criteria.StudentConflict;
    034import org.cpsolver.coursett.criteria.StudentDistanceConflict;
    035import org.cpsolver.coursett.criteria.StudentHardConflict;
    036import org.cpsolver.coursett.criteria.StudentOverlapConflict;
    037import org.cpsolver.coursett.criteria.StudentWorkdayConflict;
    038import org.cpsolver.coursett.criteria.TimePreferences;
    039import org.cpsolver.coursett.criteria.TimeViolations;
    040import org.cpsolver.coursett.criteria.TooBigRooms;
    041import org.cpsolver.coursett.criteria.UselessHalfHours;
    042import org.cpsolver.coursett.criteria.additional.InstructorConflict;
    043import org.cpsolver.coursett.criteria.placement.DeltaTimePreference;
    044import org.cpsolver.coursett.criteria.placement.HardConflicts;
    045import org.cpsolver.coursett.criteria.placement.PotentialHardConflicts;
    046import org.cpsolver.coursett.criteria.placement.WeightedHardConflicts;
    047import org.cpsolver.ifs.assignment.Assignment;
    048import org.cpsolver.ifs.constant.ConstantModel;
    049import org.cpsolver.ifs.criteria.Criterion;
    050import org.cpsolver.ifs.model.Constraint;
    051import org.cpsolver.ifs.model.GlobalConstraint;
    052import org.cpsolver.ifs.model.InfoProvider;
    053import org.cpsolver.ifs.model.WeakeningConstraint;
    054import org.cpsolver.ifs.solution.Solution;
    055import org.cpsolver.ifs.termination.TerminationCondition;
    056import org.cpsolver.ifs.util.DataProperties;
    057import org.cpsolver.ifs.util.DistanceMetric;
    058
    059
    060/**
    061 * Timetable model.
    062 *
    063 * @author  Tomáš Müller
    064 * @version CourseTT 1.3 (University Course Timetabling)<br>
    065 *          Copyright (C) 2006 - 2014 Tomáš Müller<br>
    066 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    067 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    068 * <br>
    069 *          This library is free software; you can redistribute it and/or modify
    070 *          it under the terms of the GNU Lesser General Public License as
    071 *          published by the Free Software Foundation; either version 3 of the
    072 *          License, or (at your option) any later version. <br>
    073 * <br>
    074 *          This library is distributed in the hope that it will be useful, but
    075 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    076 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    077 *          Lesser General Public License for more details. <br>
    078 * <br>
    079 *          You should have received a copy of the GNU Lesser General Public
    080 *          License along with this library; if not see
    081 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    082 */
    083
    084public class TimetableModel extends ConstantModel<Lecture, Placement> {
    085    private static org.apache.logging.log4j.Logger sLogger = org.apache.logging.log4j.LogManager.getLogger(TimetableModel.class);
    086    private static java.text.DecimalFormat sDoubleFormat = new java.text.DecimalFormat("0.00",
    087            new java.text.DecimalFormatSymbols(Locale.US));
    088
    089    private List<InstructorConstraint> iInstructorConstraints = new ArrayList<InstructorConstraint>();
    090    private List<JenrlConstraint> iJenrlConstraints = new ArrayList<JenrlConstraint>();
    091    private List<RoomConstraint> iRoomConstraints = new ArrayList<RoomConstraint>();
    092    private List<DepartmentSpreadConstraint> iDepartmentSpreadConstraints = new ArrayList<DepartmentSpreadConstraint>();
    093    private List<SpreadConstraint> iSpreadConstraints = new ArrayList<SpreadConstraint>();
    094    private List<GroupConstraint> iGroupConstraints = new ArrayList<GroupConstraint>();
    095    private List<ClassLimitConstraint> iClassLimitConstraints = new ArrayList<ClassLimitConstraint>();
    096    private List<FlexibleConstraint> iFlexibleConstraints = new ArrayList<FlexibleConstraint>();
    097    private DataProperties iProperties = null;
    098    private int iYear = -1;
    099    private BitSet iFullTerm = null, iHoliday = null;
    100    private List<BitSet> iWeeks = null;
    101    private boolean iOnFlySectioning = false;
    102    private int iStudentWorkDayLimit = -1;
    103    private boolean iAllowBreakHard = false;
    104
    105    private HashSet<Student> iAllStudents = new HashSet<Student>();
    106
    107    private DistanceMetric iDistanceMetric = null;
    108
    109    private StudentSectioning iStudentSectioning = null;
    110    private List<StudentGroup> iStudentGroups = new ArrayList<StudentGroup>();
    111
    112    private boolean iUseCriteria = true;
    113    private List<StudentConflict> iStudentConflictCriteria = null;
    114
    115    @SuppressWarnings("unchecked")
    116    public TimetableModel(DataProperties properties) {
    117        super();
    118        iProperties = properties;
    119        iDistanceMetric = new DistanceMetric(properties);
    120        if (properties.getPropertyBoolean("OnFlySectioning.Enabled", false)) {
    121            addModelListener(new OnFlySectioning(this)); iOnFlySectioning = true;
    122        }
    123        iStudentWorkDayLimit = properties.getPropertyInt("StudentConflict.WorkDayLimit", -1);
    124        iAllowBreakHard = properties.getPropertyBoolean("General.AllowBreakHard", false);
    125        String criteria = properties.getProperty("General.Criteria",
    126                // Objectives
    127                StudentConflict.class.getName() + ";" +
    128                StudentDistanceConflict.class.getName() + ";" +
    129                StudentHardConflict.class.getName() + ";" +
    130                StudentCommittedConflict.class.getName() + ";" +
    131                StudentOverlapConflict.class.getName() + ";" +
    132                UselessHalfHours.class.getName() + ";" +
    133                BrokenTimePatterns.class.getName() + ";" +
    134                TooBigRooms.class.getName() + ";" +
    135                TimePreferences.class.getName() + ";" +
    136                RoomPreferences.class.getName() + ";" +
    137                DistributionPreferences.class.getName() + ";" +
    138                SameSubpartBalancingPenalty.class.getName() + ";" +
    139                DepartmentBalancingPenalty.class.getName() + ";" +
    140                BackToBackInstructorPreferences.class.getName() + ";" +
    141                Perturbations.class.getName() + ";" +
    142                // Additional placement selection criteria
    143                // AssignmentCount.class.getName() + ";" +
    144                DeltaTimePreference.class.getName() + ";" +
    145                HardConflicts.class.getName() + ";" +
    146                PotentialHardConflicts.class.getName() + ";" +
    147                FlexibleConstraintCriterion.class.getName() + ";" +
    148                WeightedHardConflicts.class.getName());
    149        if (iStudentWorkDayLimit > 0)
    150            criteria += ";" + StudentWorkdayConflict.class.getName();
    151        // Interactive mode -- count time / room violations
    152        if (properties.getPropertyBoolean("General.InteractiveMode", false))
    153            criteria += ";" + TimeViolations.class.getName() + ";" + RoomViolations.class.getName();
    154        else if (properties.getPropertyBoolean("General.AllowProhibitedRooms", false)) {
    155            criteria += ";" + RoomViolations.class.getName();
    156            iAllowBreakHard = true;
    157        }
    158        // Additional (custom) criteria
    159        criteria += ";" + properties.getProperty("General.AdditionalCriteria", "");
    160        for (String criterion: criteria.split("\\;")) {
    161            if (criterion == null || criterion.isEmpty()) continue;
    162            try {
    163                Class<Criterion<Lecture, Placement>> clazz = (Class<Criterion<Lecture, Placement>>)Class.forName(criterion);
    164                Criterion<Lecture, Placement> c = clazz.newInstance();
    165                c.configure(properties);
    166                addCriterion(c);
    167            } catch (Exception e) {
    168                sLogger.error("Unable to use " + criterion + ": " + e.getMessage());
    169            }
    170        }
    171        if (properties.getPropertyBoolean("General.SoftInstructorConstraints", false)) {
    172            InstructorConflict ic = new InstructorConflict(); ic.configure(properties);
    173            addCriterion(ic);
    174        }
    175        try {
    176            String studentSectioningClassName = properties.getProperty("StudentSectioning.Class", DefaultStudentSectioning.class.getName());
    177            Class<?> studentSectioningClass = Class.forName(studentSectioningClassName);
    178            iStudentSectioning = (StudentSectioning)studentSectioningClass.getConstructor(TimetableModel.class).newInstance(this);
    179        } catch (Exception e) {
    180            sLogger.error("Failed to load custom student sectioning class: " + e.getMessage());
    181            iStudentSectioning = new DefaultStudentSectioning(this);
    182        }
    183        if (iStudentSectioning instanceof InfoProvider<?, ?>) {
    184            getInfoProviders().add((InfoProvider<Lecture, Placement>)iStudentSectioning);
    185        }
    186        String constraints = properties.getProperty("General.GlobalConstraints", "");
    187        for (String constraint: constraints.split("\\;")) {
    188            if (constraint == null || constraint.isEmpty()) continue;
    189            try {
    190                Class<GlobalConstraint<Lecture, Placement>> clazz = (Class<GlobalConstraint<Lecture, Placement>>)Class.forName(constraint);
    191                GlobalConstraint<Lecture, Placement> c = clazz.newInstance();
    192                addGlobalConstraint(c);
    193            } catch (Exception e) {
    194                sLogger.error("Unable to use " + constraint + ": " + e.getMessage());
    195            }
    196        }
    197        iUseCriteria = properties.getPropertyBoolean("SctSectioning.UseCriteria", true);
    198    }
    199
    200    public DistanceMetric getDistanceMetric() {
    201        return iDistanceMetric;
    202    }
    203
    204    public int getStudentWorkDayLimit() {
    205        return iStudentWorkDayLimit;
    206    }
    207
    208    /**
    209     * Returns interface to the student sectioning functions needed during course timetabling.
    210     * Defaults to an instance of {@link DefaultStudentSectioning}, can be changed using the StudentSectioning.Class parameter.
    211     * @return student sectioning
    212     */
    213    public StudentSectioning getStudentSectioning() {
    214        return iStudentSectioning;
    215    }
    216
    217    public DataProperties getProperties() {
    218        return iProperties;
    219    }
    220
    221    /**
    222     * Student final sectioning (switching students between sections of the same
    223     * class in order to minimize overall number of student conflicts)
    224     * @param assignment current assignment
    225     * @param termination optional termination condition
    226     */
    227    public void switchStudents(Assignment<Lecture, Placement> assignment, TerminationCondition<Lecture, Placement> termination) {
    228        getStudentSectioning().switchStudents(new Solution<Lecture, Placement>(this, assignment), termination);
    229    }
    230
    231    /**
    232     * Student final sectioning (switching students between sections of the same
    233     * class in order to minimize overall number of student conflicts)
    234     * @param assignment current assignment
    235     */
    236    public void switchStudents(Assignment<Lecture, Placement> assignment) {
    237        getStudentSectioning().switchStudents(new Solution<Lecture, Placement>(this, assignment), null);
    238    }
    239
    240    public Map<String, String> getBounds(Assignment<Lecture, Placement> assignment) {
    241        Map<String, String> ret = new HashMap<String, String>();
    242        ret.put("Room preferences min", "" + getCriterion(RoomPreferences.class).getBounds(assignment)[0]);
    243        ret.put("Room preferences max", "" + getCriterion(RoomPreferences.class).getBounds(assignment)[1]);
    244        ret.put("Time preferences min", "" + getCriterion(TimePreferences.class).getBounds(assignment)[0]);
    245        ret.put("Time preferences max", "" + getCriterion(TimePreferences.class).getBounds(assignment)[1]);
    246        ret.put("Distribution preferences min", "" + getCriterion(DistributionPreferences.class).getBounds(assignment)[0]);
    247        ret.put("Distribution preferences max", "" + getCriterion(DistributionPreferences.class).getBounds(assignment)[1]);
    248        if (getProperties().getPropertyBoolean("General.UseDistanceConstraints", false)) {
    249            ret.put("Back-to-back instructor preferences max", "" + getCriterion(BackToBackInstructorPreferences.class).getBounds(assignment)[1]);
    250        }
    251        ret.put("Too big rooms max", "" + getCriterion(TooBigRooms.class).getBounds(assignment)[0]);
    252        ret.put("Useless half-hours", "" + getCriterion(UselessHalfHours.class).getBounds(assignment)[0]);
    253        return ret;
    254    }
    255
    256    /** Global info */
    257    @Override
    258    public Map<String, String> getInfo(Assignment<Lecture, Placement> assignment) {
    259        Map<String, String> ret = super.getInfo(assignment);
    260        ret.put("Memory usage", getMem());
    261
    262        Criterion<Lecture, Placement> rp = getCriterion(RoomPreferences.class);
    263        Criterion<Lecture, Placement> rv = getCriterion(RoomViolations.class);
    264        ret.put("Room preferences", getPerc(rp.getValue(assignment), rp.getBounds(assignment)[0], rp.getBounds(assignment)[1]) + "% (" + Math.round(rp.getValue(assignment)) + ")"
    265                + (rv != null && rv.getValue(assignment) >= 0.5 ? " [hard:" + Math.round(rv.getValue(assignment)) + "]" : ""));
    266
    267        Criterion<Lecture, Placement> tp = getCriterion(TimePreferences.class);
    268        Criterion<Lecture, Placement> tv = getCriterion(TimeViolations.class);
    269        ret.put("Time preferences", getPerc(tp.getValue(assignment), tp.getBounds(assignment)[0], tp.getBounds(assignment)[1]) + "% (" + sDoubleFormat.format(tp.getValue(assignment)) + ")"
    270                + (tv != null && tv.getValue(assignment) >= 0.5 ? " [hard:" + Math.round(tv.getValue(assignment)) + "]" : ""));
    271
    272        Criterion<Lecture, Placement> dp = getCriterion(DistributionPreferences.class);
    273        ret.put("Distribution preferences", getPerc(dp.getValue(assignment), dp.getBounds(assignment)[0], dp.getBounds(assignment)[1]) + "% (" + sDoubleFormat.format(dp.getValue(assignment)) + ")");
    274
    275        Criterion<Lecture, Placement> sc = getCriterion(StudentConflict.class);
    276        Criterion<Lecture, Placement> shc = getCriterion(StudentHardConflict.class);
    277        Criterion<Lecture, Placement> sdc = getCriterion(StudentDistanceConflict.class);
    278        Criterion<Lecture, Placement> scc = getCriterion(StudentCommittedConflict.class);
    279        ret.put("Student conflicts", Math.round(scc.getValue(assignment) + sc.getValue(assignment)) +
    280                " [committed:" + Math.round(scc.getValue(assignment)) +
    281                ", distance:" + Math.round(sdc.getValue(assignment)) +
    282                ", hard:" + Math.round(shc.getValue(assignment)) + "]");
    283
    284        if (!getSpreadConstraints().isEmpty()) {
    285            Criterion<Lecture, Placement> ip = getCriterion(BackToBackInstructorPreferences.class);
    286            ret.put("Back-to-back instructor preferences", getPerc(ip.getValue(assignment), ip.getBounds(assignment)[0], ip.getBounds(assignment)[1]) + "% (" + Math.round(ip.getValue(assignment)) + ")");
    287        }
    288
    289        if (!getDepartmentSpreadConstraints().isEmpty()) {
    290            Criterion<Lecture, Placement> dbp = getCriterion(DepartmentBalancingPenalty.class);
    291            ret.put("Department balancing penalty", sDoubleFormat.format(dbp.getValue(assignment)));
    292        }
    293
    294        Criterion<Lecture, Placement> sbp = getCriterion(SameSubpartBalancingPenalty.class);
    295        ret.put("Same subpart balancing penalty", sDoubleFormat.format(sbp.getValue(assignment)));
    296
    297        Criterion<Lecture, Placement> tbr = getCriterion(TooBigRooms.class);
    298        ret.put("Too big rooms", getPercRev(tbr.getValue(assignment), tbr.getBounds(assignment)[1], tbr.getBounds(assignment)[0]) + "% (" + Math.round(tbr.getValue(assignment)) + ")");
    299
    300        Criterion<Lecture, Placement> uh = getCriterion(UselessHalfHours.class);
    301        Criterion<Lecture, Placement> bt = getCriterion(BrokenTimePatterns.class);
    302
    303        ret.put("Useless half-hours", getPercRev(uh.getValue(assignment) + bt.getValue(assignment), 0, Constants.sPreferenceLevelStronglyDiscouraged * bt.getBounds(assignment)[0]) +
    304                "% (" + Math.round(uh.getValue(assignment)) + " + " + Math.round(bt.getValue(assignment)) + ")");
    305        return ret;
    306    }
    307
    308    @Override
    309    public Map<String, String> getInfo(Assignment<Lecture, Placement> assignment, Collection<Lecture> variables) {
    310        Map<String, String> ret = super.getInfo(assignment, variables);
    311
    312        ret.put("Memory usage", getMem());
    313
    314        Criterion<Lecture, Placement> rp = getCriterion(RoomPreferences.class);
    315        ret.put("Room preferences", getPerc(rp.getValue(assignment, variables), rp.getBounds(assignment, variables)[0], rp.getBounds(assignment, variables)[1]) + "% (" + Math.round(rp.getValue(assignment, variables)) + ")");
    316
    317        Criterion<Lecture, Placement> tp = getCriterion(TimePreferences.class);
    318        ret.put("Time preferences", getPerc(tp.getValue(assignment, variables), tp.getBounds(assignment, variables)[0], tp.getBounds(assignment, variables)[1]) + "% (" + sDoubleFormat.format(tp.getValue(assignment, variables)) + ")");
    319
    320        Criterion<Lecture, Placement> dp = getCriterion(DistributionPreferences.class);
    321        ret.put("Distribution preferences", getPerc(dp.getValue(assignment, variables), dp.getBounds(assignment, variables)[0], dp.getBounds(assignment, variables)[1]) + "% (" + sDoubleFormat.format(dp.getValue(assignment, variables)) + ")");
    322
    323        Criterion<Lecture, Placement> sc = getCriterion(StudentConflict.class);
    324        Criterion<Lecture, Placement> shc = getCriterion(StudentHardConflict.class);
    325        Criterion<Lecture, Placement> sdc = getCriterion(StudentDistanceConflict.class);
    326        Criterion<Lecture, Placement> scc = getCriterion(StudentCommittedConflict.class);
    327        ret.put("Student conflicts", Math.round(scc.getValue(assignment, variables) + sc.getValue(assignment, variables)) +
    328                " [committed:" + Math.round(scc.getValue(assignment, variables)) +
    329                ", distance:" + Math.round(sdc.getValue(assignment, variables)) +
    330                ", hard:" + Math.round(shc.getValue(assignment, variables)) + "]");
    331
    332        if (!getSpreadConstraints().isEmpty()) {
    333            Criterion<Lecture, Placement> ip = getCriterion(BackToBackInstructorPreferences.class);
    334            ret.put("Back-to-back instructor preferences", getPerc(ip.getValue(assignment, variables), ip.getBounds(assignment, variables)[0], ip.getBounds(assignment, variables)[1]) + "% (" + Math.round(ip.getValue(assignment, variables)) + ")");
    335        }
    336
    337        if (!getDepartmentSpreadConstraints().isEmpty()) {
    338            Criterion<Lecture, Placement> dbp = getCriterion(DepartmentBalancingPenalty.class);
    339            ret.put("Department balancing penalty", sDoubleFormat.format(dbp.getValue(assignment, variables)));
    340        }
    341
    342        Criterion<Lecture, Placement> sbp = getCriterion(SameSubpartBalancingPenalty.class);
    343        ret.put("Same subpart balancing penalty", sDoubleFormat.format(sbp.getValue(assignment, variables)));
    344
    345        Criterion<Lecture, Placement> tbr = getCriterion(TooBigRooms.class);
    346        ret.put("Too big rooms", getPercRev(tbr.getValue(assignment, variables), tbr.getBounds(assignment, variables)[1], tbr.getBounds(assignment, variables)[0]) + "% (" + Math.round(tbr.getValue(assignment, variables)) + ")");
    347
    348        Criterion<Lecture, Placement> uh = getCriterion(UselessHalfHours.class);
    349        Criterion<Lecture, Placement> bt = getCriterion(BrokenTimePatterns.class);
    350
    351        ret.put("Useless half-hours", getPercRev(uh.getValue(assignment, variables) + bt.getValue(assignment, variables), 0, Constants.sPreferenceLevelStronglyDiscouraged * bt.getBounds(assignment, variables)[0]) +
    352                "% (" + Math.round(uh.getValue(assignment, variables)) + " + " + Math.round(bt.getValue(assignment, variables)) + ")");
    353        return ret;
    354    }
    355
    356    @Override
    357    public void addConstraint(Constraint<Lecture, Placement> constraint) {
    358        super.addConstraint(constraint);
    359        if (constraint instanceof InstructorConstraint) {
    360            iInstructorConstraints.add((InstructorConstraint) constraint);
    361        } else if (constraint instanceof JenrlConstraint) {
    362            iJenrlConstraints.add((JenrlConstraint) constraint);
    363        } else if (constraint instanceof RoomConstraint) {
    364            iRoomConstraints.add((RoomConstraint) constraint);
    365        } else if (constraint instanceof DepartmentSpreadConstraint) {
    366            iDepartmentSpreadConstraints.add((DepartmentSpreadConstraint) constraint);
    367        } else if (constraint instanceof SpreadConstraint) {
    368            iSpreadConstraints.add((SpreadConstraint) constraint);
    369        } else if (constraint instanceof ClassLimitConstraint) {
    370            iClassLimitConstraints.add((ClassLimitConstraint) constraint);
    371        } else if (constraint instanceof GroupConstraint) {
    372            iGroupConstraints.add((GroupConstraint) constraint);
    373        } else if (constraint instanceof FlexibleConstraint) {
    374            iFlexibleConstraints.add((FlexibleConstraint) constraint);
    375        }
    376    }
    377
    378    @Override
    379    public void removeConstraint(Constraint<Lecture, Placement> constraint) {
    380        super.removeConstraint(constraint);
    381        if (constraint instanceof InstructorConstraint) {
    382            iInstructorConstraints.remove(constraint);
    383        } else if (constraint instanceof JenrlConstraint) {
    384            iJenrlConstraints.remove(constraint);
    385        } else if (constraint instanceof RoomConstraint) {
    386            iRoomConstraints.remove(constraint);
    387        } else if (constraint instanceof DepartmentSpreadConstraint) {
    388            iDepartmentSpreadConstraints.remove(constraint);
    389        } else if (constraint instanceof SpreadConstraint) {
    390            iSpreadConstraints.remove(constraint);
    391        } else if (constraint instanceof ClassLimitConstraint) {
    392            iClassLimitConstraints.remove(constraint);
    393        } else if (constraint instanceof GroupConstraint) {
    394            iGroupConstraints.remove(constraint);
    395        } else if (constraint instanceof FlexibleConstraint) {
    396            iFlexibleConstraints.remove(constraint);
    397        }
    398    }
    399
    400    /** The list of all instructor constraints
    401     * @return list of instructor constraints
    402     **/
    403    public List<InstructorConstraint> getInstructorConstraints() {
    404        return iInstructorConstraints;
    405    }
    406
    407    /** The list of all group constraints
    408     * @return list of group (distribution) constraints
    409     **/
    410    public List<GroupConstraint> getGroupConstraints() {
    411        return iGroupConstraints;
    412    }
    413
    414    /** The list of all jenrl constraints
    415     * @return list of join enrollment constraints
    416     **/
    417    public List<JenrlConstraint> getJenrlConstraints() {
    418        return iJenrlConstraints;
    419    }
    420
    421    /** The list of all room constraints
    422     * @return list of room constraints
    423     **/
    424    public List<RoomConstraint> getRoomConstraints() {
    425        return iRoomConstraints;
    426    }
    427
    428    /** The list of all departmental spread constraints
    429     * @return list of department spread constraints
    430     **/
    431    public List<DepartmentSpreadConstraint> getDepartmentSpreadConstraints() {
    432        return iDepartmentSpreadConstraints;
    433    }
    434
    435    public List<SpreadConstraint> getSpreadConstraints() {
    436        return iSpreadConstraints;
    437    }
    438
    439    public List<ClassLimitConstraint> getClassLimitConstraints() {
    440        return iClassLimitConstraints;
    441    }
    442
    443    public List<FlexibleConstraint> getFlexibleConstraints() {
    444        return iFlexibleConstraints;
    445    }
    446
    447    @Override
    448    public double getTotalValue(Assignment<Lecture, Placement> assignment) {
    449        double ret = 0;
    450        for (Criterion<Lecture, Placement> criterion: getCriteria())
    451            ret += criterion.getWeightedValue(assignment);
    452        return ret;
    453    }
    454
    455    @Override
    456    public double getTotalValue(Assignment<Lecture, Placement> assignment, Collection<Lecture> variables) {
    457        double ret = 0;
    458        for (Criterion<Lecture, Placement> criterion: getCriteria())
    459            ret += criterion.getWeightedValue(assignment, variables);
    460        return ret;
    461    }
    462
    463    public int getYear() {
    464        return iYear;
    465    }
    466
    467    public void setYear(int year) {
    468        iYear = year;
    469    }
    470
    471    public Set<Student> getAllStudents() {
    472        return iAllStudents;
    473    }
    474
    475    public void addStudent(Student student) {
    476        iAllStudents.add(student);
    477    }
    478
    479    public void removeStudent(Student student) {
    480        iAllStudents.remove(student);
    481    }
    482
    483    /**
    484     * Returns amount of allocated memory.
    485     *
    486     * @return amount of allocated memory to be written in the log
    487     */
    488    public static synchronized String getMem() {
    489        Runtime rt = Runtime.getRuntime();
    490        return sDoubleFormat.format(((double) (rt.totalMemory() - rt.freeMemory())) / 1048576) + "M";
    491    }
    492
    493
    494    /**
    495     * Returns the set of conflicting variables with this value, if it is
    496     * assigned to its variable. Conflicts with constraints that implement
    497     * {@link WeakeningConstraint} are ignored.
    498     * @param assignment current assignment
    499     * @param value placement that is being considered
    500     * @return computed conflicting assignments
    501     */
    502    public Set<Placement> conflictValuesSkipWeakeningConstraints(Assignment<Lecture, Placement> assignment, Placement value) {
    503        Set<Placement> conflictValues = new HashSet<Placement>();
    504        for (Constraint<Lecture, Placement> constraint : value.variable().hardConstraints()) {
    505            if (constraint instanceof WeakeningConstraint) continue;
    506            if (constraint instanceof GroupConstraint)
    507                ((GroupConstraint)constraint).computeConflictsNoForwardCheck(assignment, value, conflictValues);
    508            else
    509                constraint.computeConflicts(assignment, value, conflictValues);
    510        }
    511        for (GlobalConstraint<Lecture, Placement> constraint : globalConstraints()) {
    512            if (constraint instanceof WeakeningConstraint) continue;
    513            constraint.computeConflicts(assignment, value, conflictValues);
    514        }
    515        return conflictValues;
    516    }
    517
    518
    519    /**
    520     * The method returns the default date pattern.
    521     * It is typically set using the DatePattern.Default parameter.
    522     * If not set, return the most commonly used date pattern.
    523     *
    524     * @return default date pattern
    525     */
    526    public BitSet getDefaultDatePattern() {
    527        if (iFullTerm == null) {
    528            String defaultDatePattern = getProperties().getProperty("DatePattern.CustomDatePattern", null);
    529            if (defaultDatePattern == null){
    530                defaultDatePattern = getProperties().getProperty("DatePattern.Default");
    531            }
    532            if (defaultDatePattern == null) {
    533                // Take the date pattern that is being used most often
    534                Map<Long, Integer> counter = new HashMap<Long, Integer>();
    535                int max = 0; String name = null; Long id = null;
    536                for (Lecture lecture: variables()) {
    537                    if (lecture.isCommitted()) continue;
    538                    for (TimeLocation time: lecture.timeLocations()) {
    539                        if (time.getWeekCode() != null && time.getDatePatternId() != null) {
    540                            int count = 1;
    541                            if (counter.containsKey(time.getDatePatternId()))
    542                                count += counter.get(time.getDatePatternId());
    543                            counter.put(time.getDatePatternId(), count);
    544                            if (count > max) {
    545                                max = count; iFullTerm = time.getWeekCode(); name = time.getDatePatternName(); id = time.getDatePatternId();
    546                            }
    547                        }
    548                    }
    549                }
    550                sLogger.info("Using date pattern " + name + " (id " + id + ") as the default.");
    551            } else {
    552                // Create default date pattern
    553                iFullTerm = new BitSet(defaultDatePattern.length());
    554                for (int i = 0; i < defaultDatePattern.length(); i++) {
    555                    if (defaultDatePattern.charAt(i) == 49) {
    556                        iFullTerm.set(i);
    557                    }
    558                }
    559            }
    560        }
    561        return iFullTerm;
    562    }
    563
    564    /**
    565     * The method returns the holiday date pattern.
    566     * It is typically set using the DatePattern.Holiday parameter.
    567     *
    568     * @return holiday date pattern (1 holiday, 0 ordinary day)
    569     */
    570    public BitSet getHolidayDatePattern() {
    571        if (iHoliday == null) {
    572            String holidayDatePattern = getProperties().getProperty("DatePattern.Holidays", null);
    573            if (holidayDatePattern != null) {
    574                // Create default date pattern
    575                iHoliday = new BitSet(holidayDatePattern.length());
    576                for (int i = 0; i < holidayDatePattern.length(); i++) {
    577                    if (holidayDatePattern.charAt(i) == '1' || holidayDatePattern.charAt(i) == '2') {
    578                        iHoliday.set(i);
    579                    }
    580                }
    581            }
    582        }
    583        return iHoliday;
    584    }
    585
    586    /**
    587     * The method creates date patterns (bitsets) which represent the weeks of a
    588     * semester.
    589     *
    590     * @return a list of BitSets which represents the weeks of a semester.
    591     */
    592    public List<BitSet> getWeeks() {
    593        if (iWeeks == null) {
    594            BitSet fullTerm = getDefaultDatePattern();
    595            if (fullTerm == null) return null;
    596
    597            iWeeks = new ArrayList<BitSet>();
    598            if (getProperties().getPropertyBoolean("DatePattern.ShiftWeeks", false)) {
    599                // Cut date pattern into weeks (each week takes 7 consecutive bits, starting on the next positive bit)
    600                for (int i = fullTerm.nextSetBit(0); i < fullTerm.length(); ) {
    601                    if (!fullTerm.get(i)) {
    602                        i++; continue;
    603                    }
    604                    BitSet w = new BitSet(i + 7);
    605                    for (int j = 0; j < 7; j++)
    606                        if (fullTerm.get(i + j)) w.set(i + j);
    607                    iWeeks.add(w);
    608                    i += 7;
    609                }
    610            } else {
    611                // Cut date pattern into weeks (each week takes 7 consecutive bits starting on the first bit of the default date pattern, no pauses between weeks)
    612                for (int i = fullTerm.nextSetBit(0); i < fullTerm.length(); ) {
    613                    BitSet w = new BitSet(i + 7);
    614                    for (int j = 0; j < 7; j++)
    615                        if (fullTerm.get(i + j)) w.set(i + j);
    616                    iWeeks.add(w);
    617                    i += 7;
    618                }
    619            }
    620        }
    621        return iWeeks;
    622    }
    623
    624    public List<StudentGroup> getStudentGroups() { return iStudentGroups; }
    625    public void addStudentGroup(StudentGroup group) { iStudentGroups.add(group); }
    626
    627    Map<Student, Set<Lecture>> iBestEnrollment = null;
    628    @Override
    629    public void saveBest(Assignment<Lecture, Placement> assignment) {
    630        super.saveBest(assignment);
    631        if (iOnFlySectioning) {
    632            if (iBestEnrollment == null)
    633                iBestEnrollment = new HashMap<Student, Set<Lecture>>();
    634            else
    635                iBestEnrollment.clear();
    636            for (Student student: getAllStudents())
    637                iBestEnrollment.put(student, new HashSet<Lecture>(student.getLectures()));
    638        }
    639    }
    640
    641    /**
    642     * Increment {@link JenrlConstraint} between the given two classes by the given student
    643     */
    644    protected void incJenrl(Assignment<Lecture, Placement> assignment, Student student, Lecture l1, Lecture l2) {
    645        if (l1.equals(l2)) return;
    646        JenrlConstraint jenrl = l1.jenrlConstraint(l2);
    647        if (jenrl == null) {
    648            jenrl = new JenrlConstraint();
    649            jenrl.addVariable(l1);
    650            jenrl.addVariable(l2);
    651            addConstraint(jenrl);
    652        }
    653        jenrl.incJenrl(assignment, student);
    654    }
    655
    656    /**
    657     * Decrement {@link JenrlConstraint} between the given two classes by the given student
    658     */
    659    protected void decJenrl(Assignment<Lecture, Placement> assignment, Student student, Lecture l1, Lecture l2) {
    660        if (l1.equals(l2)) return;
    661        JenrlConstraint jenrl = l1.jenrlConstraint(l2);
    662        if (jenrl != null) {
    663            jenrl.decJenrl(assignment, student);
    664        }
    665    }
    666
    667    @Override
    668    public void restoreBest(Assignment<Lecture, Placement> assignment) {
    669        if (iOnFlySectioning && iBestEnrollment != null) {
    670
    671            // unassign changed classes
    672            for (Lecture lecture: variables()) {
    673                Placement placement = assignment.getValue(lecture);
    674                if (placement != null && !placement.equals(lecture.getBestAssignment()))
    675                    assignment.unassign(0, lecture);
    676            }
    677
    678            for (Map.Entry<Student, Set<Lecture>> entry: iBestEnrollment.entrySet()) {
    679                Student student = entry.getKey();
    680                Set<Lecture> lectures = entry.getValue();
    681                Set<Configuration> configs = new HashSet<Configuration>();
    682                for (Lecture lecture: lectures)
    683                    if (lecture.getConfiguration() != null) configs.add(lecture.getConfiguration());
    684
    685                // drop student from classes that are not in the best enrollment
    686                for (Lecture lecture: new ArrayList<Lecture>(student.getLectures())) {
    687                    if (lectures.contains(lecture)) continue; // included in best
    688                    for (Lecture other: student.getLectures())
    689                        decJenrl(assignment, student, lecture, other);
    690                    lecture.removeStudent(assignment, student);
    691                    student.removeLecture(lecture);
    692                    if (lecture.getConfiguration() != null && !configs.contains(lecture.getConfiguration()))
    693                        student.removeConfiguration(lecture.getConfiguration());
    694                }
    695
    696                // add student to classes that are in the best enrollment
    697                for (Lecture lecture: lectures) {
    698                    if (student.getLectures().contains(lecture)) continue; // already in
    699                    for (Lecture other: student.getLectures())
    700                        incJenrl(assignment, student, lecture, other);
    701                    lecture.addStudent(assignment, student);
    702                    student.addLecture(lecture);
    703                    student.addConfiguration(lecture.getConfiguration());
    704                }
    705            }
    706            // remove empty joint enrollments
    707            for (Iterator<JenrlConstraint> i = iJenrlConstraints.iterator(); i.hasNext(); ) {
    708                JenrlConstraint jenrl = i.next();
    709                if (jenrl.getNrStudents() == 0) {
    710                    jenrl.getContext(assignment).unassigned(assignment, null);
    711                    Object[] vars = jenrl.variables().toArray();
    712                    for (int k = 0; k < vars.length; k++)
    713                        jenrl.removeVariable((Lecture) vars[k]);
    714                    i.remove();
    715                }
    716            }
    717            for (Iterator<Constraint<Lecture, Placement>> i = constraints().iterator(); i.hasNext(); ) {
    718                Constraint<Lecture, Placement> c = i.next();
    719                if (c instanceof JenrlConstraint && ((JenrlConstraint)c).getNrStudents() == 0) {
    720                    removeReference((JenrlConstraint)c);
    721                    i.remove();
    722                }
    723            }
    724            /*
    725            for (JenrlConstraint jenrl: new ArrayList<JenrlConstraint>(getJenrlConstraints())) {
    726                if (jenrl.getNrStudents() == 0) {
    727                    jenrl.getContext(assignment).unassigned(assignment, null);
    728                    Object[] vars = jenrl.variables().toArray();
    729                    for (int k = 0; k < vars.length; k++)
    730                        jenrl.removeVariable((Lecture) vars[k]);
    731                    removeConstraint(jenrl);
    732                }
    733            }
    734            */
    735        }
    736        super.restoreBest(assignment);
    737    }
    738
    739    public boolean isAllowBreakHard() { return iAllowBreakHard; }
    740
    741    public boolean isOnFlySectioningEnabled() { return iOnFlySectioning; }
    742    public void setOnFlySectioningEnabled(boolean onFlySectioning) { iOnFlySectioning = onFlySectioning; }
    743
    744    @Override
    745    public void addCriterion(Criterion<Lecture, Placement> criterion) {
    746        super.addCriterion(criterion);
    747        iStudentConflictCriteria = null;
    748    }
    749
    750    @Override
    751    public void removeCriterion(Criterion<Lecture, Placement> criterion) {
    752        super.removeCriterion(criterion);
    753        iStudentConflictCriteria = null;
    754    }
    755
    756    /**
    757     * List of student conflict criteria
    758     */
    759    public List<StudentConflict> getStudentConflictCriteria() {
    760        if (!iUseCriteria) return null;
    761        if (iStudentConflictCriteria == null) {
    762            iStudentConflictCriteria = new ArrayList<StudentConflict>();
    763            for (Criterion<Lecture, Placement> criterion: getCriteria())
    764                if (criterion instanceof StudentConflict)
    765                    iStudentConflictCriteria.add((StudentConflict)criterion);
    766        }
    767        return iStudentConflictCriteria;
    768    }
    769}