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