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