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