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