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