001    package net.sf.cpsolver.exam.model;
002    
003    import java.util.ArrayList;
004    import java.util.Date;
005    import java.util.HashSet;
006    import java.util.HashMap;
007    import java.util.Iterator;
008    import java.util.List;
009    import java.util.Map;
010    import java.util.Set;
011    import java.util.StringTokenizer;
012    import java.util.TreeSet;
013    
014    import net.sf.cpsolver.coursett.IdConvertor;
015    import net.sf.cpsolver.ifs.model.Constraint;
016    import net.sf.cpsolver.ifs.model.Model;
017    import net.sf.cpsolver.ifs.util.Callback;
018    import net.sf.cpsolver.ifs.util.DataProperties;
019    import net.sf.cpsolver.ifs.util.DistanceMetric;
020    import net.sf.cpsolver.ifs.util.ToolBox;
021    
022    import org.apache.log4j.Logger;
023    import org.dom4j.Document;
024    import org.dom4j.DocumentHelper;
025    import org.dom4j.Element;
026    
027    /**
028     * Examination timetabling model. Exams {@link Exam} are modeled as variables,
029     * rooms {@link ExamRoom} and students {@link ExamStudent} as constraints.
030     * Assignment of an exam to time (modeled as non-overlapping periods
031     * {@link ExamPeriod}) and space (set of rooms) is modeled using values
032     * {@link ExamPlacement}. In order to be able to model individual period and
033     * room preferences, period and room assignments are wrapped with
034     * {@link ExamPeriodPlacement} and {@link ExamRoomPlacement} classes
035     * respectively. Moreover, additional distribution constraint
036     * {@link ExamDistributionConstraint} can be defined in the model. <br>
037     * <br>
038     * The objective function consists of the following criteria:
039     * <ul>
040     * <li>Direct student conflicts (a student is enrolled in two exams that are
041     * scheduled at the same period, weighted by Exams.DirectConflictWeight)
042     * <li>Back-to-Back student conflicts (a student is enrolled in two exams that
043     * are scheduled in consecutive periods, weighted by
044     * Exams.BackToBackConflictWeight). If Exams.IsDayBreakBackToBack is false,
045     * there is no conflict between the last period and the first period of
046     * consecutive days.
047     * <li>Distance Back-to-Back student conflicts (same as Back-to-Back student
048     * conflict, but the maximum distance between rooms in which both exam take
049     * place is greater than Exams.BackToBackDistance, weighted by
050     * Exams.DistanceBackToBackConflictWeight).
051     * <li>More than two exams a day (a student is enrolled in three exams that are
052     * scheduled at the same day, weighted by Exams.MoreThanTwoADayWeight).
053     * <li>Period penalty (total of period penalties
054     * {@link ExamPlacement#getPeriodPenalty()} of all assigned exams, weighted by
055     * Exams.PeriodWeight).
056     * <li>Room size penalty (total of room size penalties
057     * {@link ExamPlacement#getRoomSizePenalty()} of all assigned exams, weighted by
058     * Exams.RoomSizeWeight).
059     * <li>Room split penalty (total of room split penalties
060     * {@link ExamPlacement#getRoomSplitPenalty()} of all assigned exams, weighted
061     * by Exams.RoomSplitWeight).
062     * <li>Room penalty (total of room penalties
063     * {@link ExamPlacement#getRoomPenalty()} of all assigned exams, weighted by
064     * Exams.RoomWeight).
065     * <li>Distribution penalty (total of distribution constraint weights
066     * {@link ExamDistributionConstraint#getWeight()} of all soft distribution
067     * constraints that are not satisfied, i.e.,
068     * {@link ExamDistributionConstraint#isSatisfied()} = false; weighted by
069     * Exams.DistributionWeight).
070     * <li>Direct instructor conflicts (an instructor is enrolled in two exams that
071     * are scheduled at the same period, weighted by
072     * Exams.InstructorDirectConflictWeight)
073     * <li>Back-to-Back instructor conflicts (an instructor is enrolled in two exams
074     * that are scheduled in consecutive periods, weighted by
075     * Exams.InstructorBackToBackConflictWeight). If Exams.IsDayBreakBackToBack is
076     * false, there is no conflict between the last period and the first period of
077     * consecutive days.
078     * <li>Distance Back-to-Back instructor conflicts (same as Back-to-Back
079     * instructor conflict, but the maximum distance between rooms in which both
080     * exam take place is greater than Exams.BackToBackDistance, weighted by
081     * Exams.InstructorDistanceBackToBackConflictWeight).
082     * <li>Room split distance penalty (if an examination is assigned between two or
083     * three rooms, distance between these rooms can be minimized using this
084     * criterion)
085     * <li>Front load penalty (large exams can be penalized if assigned on or after
086     * a certain period)
087     * </ul>
088     * 
089     * @version ExamTT 1.2 (Examination Timetabling)<br>
090     *          Copyright (C) 2008 - 2010 Tomas Muller<br>
091     *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
092     *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
093     * <br>
094     *          This library is free software; you can redistribute it and/or modify
095     *          it under the terms of the GNU Lesser General Public License as
096     *          published by the Free Software Foundation; either version 3 of the
097     *          License, or (at your option) any later version. <br>
098     * <br>
099     *          This library is distributed in the hope that it will be useful, but
100     *          WITHOUT ANY WARRANTY; without even the implied warranty of
101     *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
102     *          Lesser General Public License for more details. <br>
103     * <br>
104     *          You should have received a copy of the GNU Lesser General Public
105     *          License along with this library; if not see
106     *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
107     */
108    public class ExamModel extends Model<Exam, ExamPlacement> {
109        private static Logger sLog = Logger.getLogger(ExamModel.class);
110        private DataProperties iProperties = null;
111        private int iMaxRooms = 4;
112        private List<ExamPeriod> iPeriods = new ArrayList<ExamPeriod>();
113        private List<ExamRoom> iRooms = new ArrayList<ExamRoom>();
114        private List<ExamStudent> iStudents = new ArrayList<ExamStudent>();
115        private List<ExamDistributionConstraint> iDistributionConstraints = new ArrayList<ExamDistributionConstraint>();
116        private List<ExamInstructor> iInstructors = new ArrayList<ExamInstructor>();
117    
118        private boolean iDayBreakBackToBack = false;
119        private double iDirectConflictWeight = 1000.0;
120        private double iMoreThanTwoADayWeight = 100.0;
121        private double iBackToBackConflictWeight = 10.0;
122        private double iDistanceBackToBackConflictWeight = 25.0;
123        private double iPeriodWeight = 1.0;
124        private double iPeriodSizeWeight = 1.0;
125        private double iPeriodIndexWeight = 0.0000001;
126        private double iExamRotationWeight = 0.001;
127        private double iRoomSizeWeight = 0.0001;
128        private double iRoomSplitWeight = 10.0;
129        private double iRoomWeight = 0.1;
130        private double iDistributionWeight = 1.0;
131        private double iBackToBackDistance = -1; // 67
132        private double iInstructorDirectConflictWeight = 1000.0;
133        private double iInstructorMoreThanTwoADayWeight = 100.0;
134        private double iInstructorBackToBackConflictWeight = 10.0;
135        private double iInstructorDistanceBackToBackConflictWeight = 25.0;
136        private boolean iMPP = false;
137        private double iPerturbationWeight = 0.01;
138        private double iRoomPerturbationWeight = 0.01;
139        private double iRoomSplitDistanceWeight = 0.01;
140        private int iLargeSize = -1;
141        private double iLargePeriod = 0.67;
142        private double iLargeWeight = 1.0;
143    
144        private int iNrDirectConflicts = 0;
145        private int iNrNADirectConflicts = 0;
146        private int iNrBackToBackConflicts = 0;
147        private int iNrDistanceBackToBackConflicts = 0;
148        private int iNrMoreThanTwoADayConflicts = 0;
149        private int iRoomSizePenalty = 0;
150        private int iRoomSplitPenalty = 0;
151        private int iRoomSplits = 0;
152        private int iRoomSplitPenalties[] = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
153        private int iRoomPenalty = 0;
154        private int iDistributionPenalty = 0;
155        private int iPeriodPenalty = 0;
156        private int iPeriodSizePenalty = 0;
157        private int iPeriodIndexPenalty = 0;
158        private int iExamRotationPenalty = 0;
159        private int iPerturbationPenalty = 0;
160        private int iRoomPerturbationPenalty = 0;
161        private int iNrInstructorDirectConflicts = 0;
162        private int iNrNAInstructorDirectConflicts = 0;
163        private int iNrInstructorBackToBackConflicts = 0;
164        private int iNrInstructorDistanceBackToBackConflicts = 0;
165        private int iNrInstructorMoreThanTwoADayConflicts = 0;
166        private int iLargePenalty = 0;
167        private double iRoomSplitDistancePenalty = 0;
168        private int iNrLargeExams;
169        
170        private DistanceMetric iDistanceMetric = null;
171    
172        /**
173         * Constructor
174         * 
175         * @param properties
176         *            problem properties
177         */
178        public ExamModel(DataProperties properties) {
179            iAssignedVariables = null;
180            iUnassignedVariables = null;
181            iPerturbVariables = null;
182            iProperties = properties;
183            iMaxRooms = properties.getPropertyInt("Exams.MaxRooms", iMaxRooms);
184            iDayBreakBackToBack = properties.getPropertyBoolean("Exams.IsDayBreakBackToBack", iDayBreakBackToBack);
185            iDirectConflictWeight = properties.getPropertyDouble("Exams.DirectConflictWeight", iDirectConflictWeight);
186            iBackToBackConflictWeight = properties.getPropertyDouble("Exams.BackToBackConflictWeight",
187                    iBackToBackConflictWeight);
188            iDistanceBackToBackConflictWeight = properties.getPropertyDouble("Exams.DistanceBackToBackConflictWeight",
189                    iDistanceBackToBackConflictWeight);
190            iMoreThanTwoADayWeight = properties.getPropertyDouble("Exams.MoreThanTwoADayWeight", iMoreThanTwoADayWeight);
191            iPeriodWeight = properties.getPropertyDouble("Exams.PeriodWeight", iPeriodWeight);
192            iPeriodIndexWeight = properties.getPropertyDouble("Exams.PeriodIndexWeight", iPeriodIndexWeight);
193            iPeriodSizeWeight = properties.getPropertyDouble("Exams.PeriodSizeWeight", iPeriodSizeWeight);
194            iExamRotationWeight = properties.getPropertyDouble("Exams.RotationWeight", iExamRotationWeight);
195            iRoomSizeWeight = properties.getPropertyDouble("Exams.RoomSizeWeight", iRoomSizeWeight);
196            iRoomWeight = properties.getPropertyDouble("Exams.RoomWeight", iRoomWeight);
197            iRoomSplitWeight = properties.getPropertyDouble("Exams.RoomSplitWeight", iRoomSplitWeight);
198            iBackToBackDistance = properties.getPropertyDouble("Exams.BackToBackDistance", iBackToBackDistance);
199            iDistributionWeight = properties.getPropertyDouble("Exams.DistributionWeight", iDistributionWeight);
200            iInstructorDirectConflictWeight = properties.getPropertyDouble("Exams.InstructorDirectConflictWeight",
201                    iInstructorDirectConflictWeight);
202            iInstructorBackToBackConflictWeight = properties.getPropertyDouble("Exams.InstructorBackToBackConflictWeight",
203                    iInstructorBackToBackConflictWeight);
204            iInstructorDistanceBackToBackConflictWeight = properties.getPropertyDouble(
205                    "Exams.InstructorDistanceBackToBackConflictWeight", iInstructorDistanceBackToBackConflictWeight);
206            iInstructorMoreThanTwoADayWeight = properties.getPropertyDouble("Exams.InstructorMoreThanTwoADayWeight",
207                    iInstructorMoreThanTwoADayWeight);
208            iMPP = properties.getPropertyBoolean("General.MPP", iMPP);
209            iPerturbationWeight = properties.getPropertyDouble("Exams.PerturbationWeight", iPerturbationWeight);
210            iRoomPerturbationWeight = properties.getPropertyDouble("Exams.RoomPerturbationWeight", iRoomPerturbationWeight);
211            iRoomSplitDistanceWeight = properties.getPropertyDouble("Exams.RoomSplitDistanceWeight",
212                    iRoomSplitDistanceWeight);
213            iLargeSize = properties.getPropertyInt("Exams.LargeSize", iLargeSize);
214            iLargePeriod = properties.getPropertyDouble("Exams.LargePeriod", iLargePeriod);
215            iLargeWeight = properties.getPropertyDouble("Exams.LargeWeight", iLargeWeight);
216            iDistanceMetric = new DistanceMetric(properties);
217        }
218        
219        public DistanceMetric getDistanceMetric() {
220            return iDistanceMetric;
221        }
222    
223        /**
224         * Initialization of the model
225         */
226        public void init() {
227            iNrLargeExams = 0;
228            for (Exam exam : variables()) {
229                if (getLargeSize() >= 0 && exam.getSize() >= getLargeSize())
230                    iNrLargeExams++;
231                for (ExamRoomPlacement room : exam.getRoomPlacements()) {
232                    room.getRoom().addVariable(exam);
233                }
234            }
235            iLimits = null;
236            iMaxDistributionPenalty = null;
237        }
238    
239        /**
240         * Default maximum number of rooms (can be set by problem property
241         * Exams.MaxRooms, or in the input xml file, property maxRooms)
242         */
243        public int getMaxRooms() {
244            return iMaxRooms;
245        }
246    
247        /**
248         * Default maximum number of rooms (can be set by problem property
249         * Exams.MaxRooms, or in the input xml file, property maxRooms)
250         */
251        public void setMaxRooms(int maxRooms) {
252            iMaxRooms = maxRooms;
253        }
254    
255        /**
256         * Add a period
257         * 
258         * @param id
259         *            period unique identifier
260         * @param day
261         *            day (e.g., 07/12/10)
262         * @param time
263         *            (e.g., 8:00am-10:00am)
264         * @param length
265         *            length of period in minutes
266         * @param penalty
267         *            period penalty
268         */
269        public ExamPeriod addPeriod(Long id, String day, String time, int length, int penalty) {
270            ExamPeriod lastPeriod = (iPeriods.isEmpty() ? null : (ExamPeriod) iPeriods.get(iPeriods.size() - 1));
271            ExamPeriod p = new ExamPeriod(id, day, time, length, penalty);
272            if (lastPeriod == null)
273                p.setIndex(iPeriods.size(), 0, 0);
274            else if (lastPeriod.getDayStr().equals(day)) {
275                p.setIndex(iPeriods.size(), lastPeriod.getDay(), lastPeriod.getTime() + 1);
276            } else
277                p.setIndex(iPeriods.size(), lastPeriod.getDay() + 1, 0);
278            if (lastPeriod != null) {
279                lastPeriod.setNext(p);
280                p.setPrev(lastPeriod);
281            }
282            iPeriods.add(p);
283            return p;
284        }
285    
286        /**
287         * Number of days
288         */
289        public int getNrDays() {
290            return (iPeriods.get(iPeriods.size() - 1)).getDay() + 1;
291        }
292    
293        /**
294         * Number of periods
295         */
296        public int getNrPeriods() {
297            return iPeriods.size();
298        }
299    
300        /**
301         * List of periods, use
302         * {@link ExamModel#addPeriod(Long, String, String, int, int)} to add a
303         * period
304         * 
305         * @return list of {@link ExamPeriod}
306         */
307        public List<ExamPeriod> getPeriods() {
308            return iPeriods;
309        }
310    
311        /** Period of given unique id */
312        public ExamPeriod getPeriod(Long id) {
313            for (ExamPeriod period : iPeriods) {
314                if (period.getId().equals(id))
315                    return period;
316            }
317            return null;
318        }
319    
320        /**
321         * Direct student conflict weight (can be set by problem property
322         * Exams.DirectConflictWeight, or in the input xml file, property
323         * directConflictWeight)
324         */
325        public double getDirectConflictWeight() {
326            return iDirectConflictWeight;
327        }
328    
329        /**
330         * Direct student conflict weight (can be set by problem property
331         * Exams.DirectConflictWeight, or in the input xml file, property
332         * directConflictWeight)
333         */
334        public void setDirectConflictWeight(double directConflictWeight) {
335            iDirectConflictWeight = directConflictWeight;
336        }
337    
338        /**
339         * Back-to-back student conflict weight (can be set by problem property
340         * Exams.BackToBackConflictWeight, or in the input xml file, property
341         * backToBackConflictWeight)
342         */
343        public double getBackToBackConflictWeight() {
344            return iBackToBackConflictWeight;
345        }
346    
347        /**
348         * Back-to-back student conflict weight (can be set by problem property
349         * Exams.BackToBackConflictWeight, or in the input xml file, property
350         * backToBackConflictWeight)
351         */
352        public void setBackToBackConflictWeight(double backToBackConflictWeight) {
353            iBackToBackConflictWeight = backToBackConflictWeight;
354        }
355    
356        /**
357         * Distance back-to-back student conflict weight (can be set by problem
358         * property Exams.DistanceBackToBackConflictWeight, or in the input xml
359         * file, property distanceBackToBackConflictWeight)
360         */
361        public double getDistanceBackToBackConflictWeight() {
362            return iDistanceBackToBackConflictWeight;
363        }
364    
365        /**
366         * Distance back-to-back student conflict weight (can be set by problem
367         * property Exams.DistanceBackToBackConflictWeight, or in the input xml
368         * file, property distanceBackToBackConflictWeight)
369         */
370        public void setDistanceBackToBackConflictWeight(double distanceBackToBackConflictWeight) {
371            iDistanceBackToBackConflictWeight = distanceBackToBackConflictWeight;
372        }
373    
374        /**
375         * More than two exams a day student conflict weight (can be set by problem
376         * property Exams.MoreThanTwoADayWeight, or in the input xml file, property
377         * moreThanTwoADayWeight)
378         */
379        public double getMoreThanTwoADayWeight() {
380            return iMoreThanTwoADayWeight;
381        }
382    
383        /**
384         * More than two exams a day student conflict weight (can be set by problem
385         * property Exams.MoreThanTwoADayWeight, or in the input xml file, property
386         * moreThanTwoADayWeight)
387         */
388        public void setMoreThanTwoADayWeight(double moreThanTwoADayWeight) {
389            iMoreThanTwoADayWeight = moreThanTwoADayWeight;
390        }
391    
392        /**
393         * Direct instructor conflict weight (can be set by problem property
394         * Exams.InstructorDirectConflictWeight, or in the input xml file, property
395         * instructorDirectConflictWeight)
396         */
397        public double getInstructorDirectConflictWeight() {
398            return iInstructorDirectConflictWeight;
399        }
400    
401        /**
402         * Direct instructor conflict weight (can be set by problem property
403         * Exams.InstructorDirectConflictWeight, or in the input xml file, property
404         * instructorDirectConflictWeight)
405         */
406        public void setInstructorDirectConflictWeight(double directConflictWeight) {
407            iInstructorDirectConflictWeight = directConflictWeight;
408        }
409    
410        /**
411         * Back-to-back instructor conflict weight (can be set by problem property
412         * Exams.InstructorBackToBackConflictWeight, or in the input xml file,
413         * property instructorBackToBackConflictWeight)
414         */
415        public double getInstructorBackToBackConflictWeight() {
416            return iInstructorBackToBackConflictWeight;
417        }
418    
419        /**
420         * Back-to-back instructor conflict weight (can be set by problem property
421         * Exams.InstructorBackToBackConflictWeight, or in the input xml file,
422         * property instructorBackToBackConflictWeight)
423         */
424        public void setInstructorBackToBackConflictWeight(double backToBackConflictWeight) {
425            iInstructorBackToBackConflictWeight = backToBackConflictWeight;
426        }
427    
428        /**
429         * Distance back-to-back instructor conflict weight (can be set by problem
430         * property Exams.InstructorDistanceBackToBackConflictWeight, or in the
431         * input xml file, property instructorDistanceBackToBackConflictWeight)
432         */
433        public double getInstructorDistanceBackToBackConflictWeight() {
434            return iInstructorDistanceBackToBackConflictWeight;
435        }
436    
437        /**
438         * Distance back-to-back instructor conflict weight (can be set by problem
439         * property Exams.InstructorDistanceBackToBackConflictWeight, or in the
440         * input xml file, property instructorDistanceBackToBackConflictWeight)
441         */
442        public void setInstructorDistanceBackToBackConflictWeight(double distanceBackToBackConflictWeight) {
443            iInstructorDistanceBackToBackConflictWeight = distanceBackToBackConflictWeight;
444        }
445    
446        /**
447         * More than two exams a day instructor conflict weight (can be set by
448         * problem property Exams.InstructorMoreThanTwoADayWeight, or in the input
449         * xml file, property instructorMoreThanTwoADayWeight)
450         */
451        public double getInstructorMoreThanTwoADayWeight() {
452            return iInstructorMoreThanTwoADayWeight;
453        }
454    
455        /**
456         * More than two exams a day instructor conflict weight (can be set by
457         * problem property Exams.InstructorMoreThanTwoADayWeight, or in the input
458         * xml file, property instructorMoreThanTwoADayWeight)
459         */
460        public void setInstructorMoreThanTwoADayWeight(double moreThanTwoADayWeight) {
461            iInstructorMoreThanTwoADayWeight = moreThanTwoADayWeight;
462        }
463    
464        /**
465         * True when back-to-back student conflict is to be encountered when a
466         * student is enrolled into an exam that is on the last period of one day
467         * and another exam that is on the first period of the consecutive day. It
468         * can be set by problem property Exams.IsDayBreakBackToBack, or in the
469         * input xml file, property isDayBreakBackToBack)
470         * 
471         */
472        public boolean isDayBreakBackToBack() {
473            return iDayBreakBackToBack;
474        }
475    
476        /**
477         * True when back-to-back student conflict is to be encountered when a
478         * student is enrolled into an exam that is on the last period of one day
479         * and another exam that is on the first period of the consecutive day. It
480         * can be set by problem property Exams.IsDayBreakBackToBack, or in the
481         * input xml file, property isDayBreakBackToBack)
482         * 
483         */
484        public void setDayBreakBackToBack(boolean dayBreakBackToBack) {
485            iDayBreakBackToBack = dayBreakBackToBack;
486        }
487    
488        /**
489         * A weight for period penalty (used in
490         * {@link ExamPlacement#getPeriodPenalty()}, can be set by problem property
491         * Exams.PeriodWeight, or in the input xml file, property periodWeight)
492         * 
493         */
494        public double getPeriodWeight() {
495            return iPeriodWeight;
496        }
497    
498        /**
499         * A weight for period penalty (used in
500         * {@link ExamPlacement#getPeriodPenalty()}, can be set by problem property
501         * Exams.PeriodWeight, or in the input xml file, property periodWeight)
502         * 
503         */
504        public void setPeriodWeight(double periodWeight) {
505            iPeriodWeight = periodWeight;
506        }
507    
508        /**
509         * A weight for period penalty (used in
510         * {@link ExamPlacement#getPeriodPenalty()} multiplied by examination size
511         * {@link Exam#getSize()}, can be set by problem property
512         * Exams.PeriodSizeWeight, or in the input xml file, property periodWeight)
513         * 
514         */
515        public double getPeriodSizeWeight() {
516            return iPeriodSizeWeight;
517        }
518    
519        /**
520         * A weight for period penalty (used in
521         * {@link ExamPlacement#getPeriodPenalty()} multiplied by examination size
522         * {@link Exam#getSize()}, can be set by problem property
523         * Exams.PeriodSizeWeight, or in the input xml file, property periodWeight)
524         * 
525         */
526        public void setPeriodSizeWeight(double periodSizeWeight) {
527            iPeriodSizeWeight = periodSizeWeight;
528        }
529    
530        /**
531         * A weight for period index, can be set by problem property
532         * Exams.PeriodIndexWeight, or in the input xml file, property periodWeight)
533         * 
534         */
535        public double getPeriodIndexWeight() {
536            return iPeriodIndexWeight;
537        }
538    
539        /**
540         * A weight for period index, can be set by problem property
541         * Exams.PeriodIndexWeight, or in the input xml file, property periodWeight)
542         * 
543         */
544        public void setPeriodIndexWeight(double periodIndexWeight) {
545            iPeriodIndexWeight = periodIndexWeight;
546        }
547    
548        /**
549         * A weight for exam rotation penalty (used in
550         * {@link ExamPlacement#getRotationPenalty()} can be set by problem property
551         * Exams.RotationWeight, or in the input xml file, property
552         * examRotationWeight)
553         * 
554         */
555        public double getExamRotationWeight() {
556            return iExamRotationWeight;
557        }
558    
559        /**
560         * A weight for period penalty (used in
561         * {@link ExamPlacement#getRotationPenalty()}, can be set by problem
562         * property Exams.RotationWeight, or in the input xml file, property
563         * examRotationWeight)
564         * 
565         */
566        public void setExamRotationWeight(double examRotationWeight) {
567            iExamRotationWeight = examRotationWeight;
568        }
569    
570        /**
571         * A weight for room size penalty (used in
572         * {@link ExamPlacement#getRoomSizePenalty()}, can be set by problem
573         * property Exams.RoomSizeWeight, or in the input xml file, property
574         * roomSizeWeight)
575         * 
576         */
577        public double getRoomSizeWeight() {
578            return iRoomSizeWeight;
579        }
580    
581        /**
582         * A weight for room size penalty (used in
583         * {@link ExamPlacement#getRoomSizePenalty()}, can be set by problem
584         * property Exams.RoomSizeWeight, or in the input xml file, property
585         * roomSizeWeight)
586         * 
587         */
588        public void setRoomSizeWeight(double roomSizeWeight) {
589            iRoomSizeWeight = roomSizeWeight;
590        }
591    
592        /**
593         * A weight for room penalty weight (used in
594         * {@link ExamPlacement#getRoomPenalty()}, can be set by problem property
595         * Exams.RoomPreferenceWeight, or in the input xml file, property
596         * roomPreferenceWeight)
597         * 
598         */
599        public double getRoomWeight() {
600            return iRoomWeight;
601        }
602    
603        /**
604         * A weight for room penalty weight (used in
605         * {@link ExamPlacement#getRoomPenalty()}, can be set by problem property
606         * Exams.RoomWeight, or in the input xml file, property roomWeight)
607         * 
608         */
609        public void setRoomWeight(double roomWeight) {
610            iRoomWeight = roomWeight;
611        }
612    
613        /**
614         * A weight for room split penalty (used in
615         * {@link ExamPlacement#getRoomSplitPenalty()}, can be set by problem
616         * property Exams.RoomSplitWeight, or in the input xml file, property
617         * roomSplitWeight)
618         * 
619         */
620        public double getRoomSplitWeight() {
621            return iRoomSplitWeight;
622        }
623    
624        /**
625         * A weight for room split penalty (used in
626         * {@link ExamPlacement#getRoomSplitPenalty()}, can be set by problem
627         * property Exams.RoomSplitWeight, or in the input xml file, property
628         * roomSplitWeight)
629         * 
630         */
631        public void setRoomSplitWeight(double roomSplitWeight) {
632            iRoomSplitWeight = roomSplitWeight;
633        }
634    
635        /**
636         * Back-to-back distance (used in
637         * {@link ExamPlacement#getNrDistanceBackToBackConflicts()}, can be set by
638         * problem property Exams.BackToBackDistance, or in the input xml file,
639         * property backToBackDistance)
640         */
641        public double getBackToBackDistance() {
642            return iBackToBackDistance;
643        }
644    
645        /**
646         * Back-to-back distance (used in
647         * {@link ExamPlacement#getNrDistanceBackToBackConflicts()}, can be set by
648         * problem property Exams.BackToBackDistance, or in the input xml file,
649         * property backToBackDistance)
650         */
651        public void setBackToBackDistance(double backToBackDistance) {
652            iBackToBackDistance = backToBackDistance;
653        }
654    
655        /**
656         * A weight of violated distribution soft constraints (see
657         * {@link ExamDistributionConstraint}, can be set by problem property
658         * Exams.RoomDistributionWeight, or in the input xml file, property
659         * roomDistributionWeight)
660         */
661        public double getDistributionWeight() {
662            return iDistributionWeight;
663        }
664    
665        /**
666         * A weight of violated distribution soft constraints (see
667         * {@link ExamDistributionConstraint}, can be set by problem property
668         * Exams.RoomDistributionWeight, or in the input xml file, property
669         * roomDistributionWeight)
670         * 
671         */
672        public void setDistributionWeight(double distributionWeight) {
673            iDistributionWeight = distributionWeight;
674        }
675    
676        /**
677         * A weight of perturbations (see
678         * {@link ExamPlacement#getPerturbationPenalty()}), i.e., a penalty for an
679         * assignment of an exam to a place different from the initial one. Can by
680         * set by problem property Exams.PerturbationWeight, or in the input xml
681         * file, property perturbationWeight)
682         */
683        public double getPerturbationWeight() {
684            return iPerturbationWeight;
685        }
686    
687        /**
688         * A weight of perturbations (see
689         * {@link ExamPlacement#getPerturbationPenalty()}), i.e., a penalty for an
690         * assignment of an exam to a place different from the initial one. Can by
691         * set by problem property Exams.PerturbationWeight, or in the input xml
692         * file, property perturbationWeight)
693         */
694        public void setPerturbationWeight(double perturbationWeight) {
695            iPerturbationWeight = perturbationWeight;
696        }
697    
698        /**
699         * A weight of room perturbations (see
700         * {@link ExamPlacement#getRoomPerturbationPenalty()}), i.e., a penalty for
701         * an assignment of an exam to a room different from the initial one. Can by
702         * set by problem property Exams.RoomPerturbationWeight, or in the input xml
703         * file, property perturbationWeight)
704         */
705        public double getRoomPerturbationWeight() {
706            return iRoomPerturbationWeight;
707        }
708    
709        /**
710         * A weight of room perturbations (see
711         * {@link ExamPlacement#getRoomPerturbationPenalty()}), i.e., a penalty for
712         * an assignment of an exam to a room different from the initial one. Can by
713         * set by problem property Exams.RoomPerturbationWeight, or in the input xml
714         * file, property perturbationWeight)
715         */
716        public void setRoomPerturbationWeight(double perturbationWeight) {
717            iRoomPerturbationWeight = perturbationWeight;
718        }
719    
720        /**
721         * A weight for distance between two or more rooms into which an exam is
722         * split. Can by set by problem property Exams.RoomSplitDistanceWeight, or
723         * in the input xml file, property roomSplitDistanceWeight)
724         **/
725        public double getRoomSplitDistanceWeight() {
726            return iRoomSplitDistanceWeight;
727        }
728    
729        /**
730         * A weight for distance between two or more rooms into which an exam is
731         * split. Can by set by problem property Exams.RoomSplitDistanceWeight, or
732         * in the input xml file, property roomSplitDistanceWeight)
733         **/
734        public void setRoomSplitDistanceWeight(double roomSplitDistanceWeight) {
735            iRoomSplitDistanceWeight = roomSplitDistanceWeight;
736        }
737    
738        /**
739         * An exam is considered large, if its size is greater or equal to this
740         * large size. Value -1 means all exams are small. Can by set by problem
741         * property Exams.LargeSize, or in the input xml file, property largeSize)
742         **/
743        public int getLargeSize() {
744            return iLargeSize;
745        }
746    
747        /**
748         * An exam is considered large, if its size is greater or equal to this
749         * large size. Value -1 means all exams are small. Can by set by problem
750         * property Exams.LargeSize, or in the input xml file, property largeSize)
751         **/
752        public void setLargeSize(int largeSize) {
753            iLargeSize = largeSize;
754        }
755    
756        /**
757         * Period index (number of periods multiplied by this number) for front load
758         * criteria for large exams Can by set by problem property
759         * Exams.LargePeriod, or in the input xml file, property largePeriod)
760         **/
761        public double getLargePeriod() {
762            return iLargePeriod;
763        }
764    
765        /**
766         * Period index (number of periods multiplied by this number) for front load
767         * criteria for large exams Can by set by problem property
768         * Exams.LargePeriod, or in the input xml file, property largePeriod)
769         **/
770        public void setLargePeriod(double largePeriod) {
771            iLargePeriod = largePeriod;
772        }
773    
774        /**
775         * Weight of front load criteria, i.e., a weight for assigning a large exam
776         * after large period Can by set by problem property Exams.LargeWeight, or
777         * in the input xml file, property largeWeight)
778         **/
779        public double getLargeWeight() {
780            return iLargeWeight;
781        }
782    
783        /**
784         * Weight of front load criteria, i.e., a weight for assigning a large exam
785         * after large period Can by set by problem property Exams.LargeWeight, or
786         * in the input xml file, property largeWeight)
787         **/
788        public void setLargeWeight(double largeWeight) {
789            iLargeWeight = largeWeight;
790        }
791    
792        /**
793         * Called before a value is unassigned from its variable, optimization
794         * criteria are updated
795         */
796        @Override
797        public void beforeUnassigned(long iteration, ExamPlacement placement) {
798            super.beforeUnassigned(iteration, placement);
799            Exam exam = placement.variable();
800            iNrDirectConflicts -= placement.getNrDirectConflicts();
801            iNrNADirectConflicts -= placement.getNrNotAvailableConflicts();
802            iNrBackToBackConflicts -= placement.getNrBackToBackConflicts();
803            iNrMoreThanTwoADayConflicts -= placement.getNrMoreThanTwoADayConflicts();
804            iRoomSizePenalty -= placement.getRoomSizePenalty();
805            iNrDistanceBackToBackConflicts -= placement.getNrDistanceBackToBackConflicts();
806            iRoomSplitPenalty -= placement.getRoomSplitPenalty();
807            iRoomSplitPenalties[placement.getRoomPlacements().size()]--;
808            iPeriodPenalty -= placement.getPeriodPenalty();
809            iPeriodIndexPenalty -= placement.getPeriod().getIndex();
810            iPeriodSizePenalty -= placement.getPeriodPenalty() * (exam.getSize() + 1);
811            iExamRotationPenalty -= placement.getRotationPenalty();
812            iRoomPenalty -= placement.getRoomPenalty();
813            iNrInstructorDirectConflicts -= placement.getNrInstructorDirectConflicts();
814            iNrNAInstructorDirectConflicts -= placement.getNrInstructorNotAvailableConflicts();
815            iNrInstructorBackToBackConflicts -= placement.getNrInstructorBackToBackConflicts();
816            iNrInstructorMoreThanTwoADayConflicts -= placement.getNrInstructorMoreThanTwoADayConflicts();
817            iNrInstructorDistanceBackToBackConflicts -= placement.getNrInstructorDistanceBackToBackConflicts();
818            iPerturbationPenalty -= placement.getPerturbationPenalty();
819            iRoomPerturbationPenalty -= placement.getRoomPerturbationPenalty();
820            iRoomSplitDistancePenalty -= placement.getRoomSplitDistancePenalty();
821            iLargePenalty -= placement.getLargePenalty();
822            if (placement.getRoomPlacements().size() > 1)
823                iRoomSplits--;
824            for (ExamStudent s : exam.getStudents())
825                s.afterUnassigned(iteration, placement);
826            for (ExamInstructor i : exam.getInstructors())
827                i.afterUnassigned(iteration, placement);
828            for (ExamRoomPlacement r : placement.getRoomPlacements())
829                r.getRoom().afterUnassigned(iteration, placement);
830        }
831    
832        /**
833         * Called after a value is assigned to its variable, optimization criteria
834         * are updated
835         */
836        @Override
837        public void afterAssigned(long iteration, ExamPlacement placement) {
838            super.afterAssigned(iteration, placement);
839            Exam exam = placement.variable();
840            iNrDirectConflicts += placement.getNrDirectConflicts();
841            iNrNADirectConflicts += placement.getNrNotAvailableConflicts();
842            iNrBackToBackConflicts += placement.getNrBackToBackConflicts();
843            iNrMoreThanTwoADayConflicts += placement.getNrMoreThanTwoADayConflicts();
844            iRoomSizePenalty += placement.getRoomSizePenalty();
845            iNrDistanceBackToBackConflicts += placement.getNrDistanceBackToBackConflicts();
846            iRoomSplitPenalty += placement.getRoomSplitPenalty();
847            iRoomSplitPenalties[placement.getRoomPlacements().size()]++;
848            iPeriodPenalty += placement.getPeriodPenalty();
849            iPeriodIndexPenalty += placement.getPeriod().getIndex();
850            iPeriodSizePenalty += placement.getPeriodPenalty() * (exam.getSize() + 1);
851            iExamRotationPenalty += placement.getRotationPenalty();
852            iRoomPenalty += placement.getRoomPenalty();
853            iNrInstructorDirectConflicts += placement.getNrInstructorDirectConflicts();
854            iNrNAInstructorDirectConflicts += placement.getNrInstructorNotAvailableConflicts();
855            iNrInstructorBackToBackConflicts += placement.getNrInstructorBackToBackConflicts();
856            iNrInstructorMoreThanTwoADayConflicts += placement.getNrInstructorMoreThanTwoADayConflicts();
857            iNrInstructorDistanceBackToBackConflicts += placement.getNrInstructorDistanceBackToBackConflicts();
858            iPerturbationPenalty += placement.getPerturbationPenalty();
859            iRoomPerturbationPenalty += placement.getRoomPerturbationPenalty();
860            iRoomSplitDistancePenalty += placement.getRoomSplitDistancePenalty();
861            iLargePenalty += placement.getLargePenalty();
862            if (placement.getRoomPlacements().size() > 1)
863                iRoomSplits++;
864            for (ExamStudent s : exam.getStudents())
865                s.afterAssigned(iteration, placement);
866            for (ExamInstructor i : exam.getInstructors())
867                i.afterAssigned(iteration, placement);
868            for (ExamRoomPlacement r : placement.getRoomPlacements())
869                r.getRoom().afterAssigned(iteration, placement);
870        }
871    
872        /**
873         * Objective function. The objective function consists of the following
874         * criteria:
875         * <ul>
876         * <li>Direct student conflicts (a student is enrolled in two exams that are
877         * scheduled at the same period, weighted by Exams.DirectConflictWeight)
878         * <li>Back-to-Back student conflicts (a student is enrolled in two exams
879         * that are scheduled in consecutive periods, weighted by
880         * Exams.BackToBackConflictWeight). If Exams.IsDayBreakBackToBack is false,
881         * there is no conflict between the last period and the first period of
882         * consecutive days.
883         * <li>Distance Back-to-Back student conflicts (same as Back-to-Back student
884         * conflict, but the maximum distance between rooms in which both exam take
885         * place is greater than Exams.BackToBackDistance, weighted by
886         * Exams.DistanceBackToBackConflictWeight).
887         * <li>More than two exams a day (a student is enrolled in three exams that
888         * are scheduled at the same day, weighted by Exams.MoreThanTwoADayWeight).
889         * <li>Period penalty (total of period penalties
890         * {@link ExamPlacement#getPeriodPenalty()} of all assigned exams, weighted
891         * by Exams.PeriodWeight).
892         * <li>Room size penalty (total of room size penalties
893         * {@link ExamPlacement#getRoomSizePenalty()} of all assigned exams,
894         * weighted by Exams.RoomSizeWeight).
895         * <li>Room split penalty (total of room split penalties
896         * {@link ExamPlacement#getRoomSplitPenalty()} of all assigned exams,
897         * weighted by Exams.RoomSplitWeight).
898         * <li>Room split distance penalty
899         * {@link ExamPlacement#getRoomSplitDistancePenalty()}, of all assigned
900         * exams, weighted by {@link ExamModel#getRoomSplitDistanceWeight()}
901         * <li>Room penalty (total of room penalties
902         * {@link ExamPlacement#getRoomPenalty()} of all assigned exams, weighted by
903         * Exams.RoomWeight).
904         * <li>Distribution penalty (total of room split penalties
905         * {@link ExamDistributionConstraint#getWeight()} of all soft violated
906         * distribution constraints, weighted by Exams.DistributionWeight).
907         * <li>Direct instructor conflicts (an instructor is enrolled in two exams
908         * that are scheduled at the same period, weighted by
909         * Exams.InstructorDirectConflictWeight)
910         * <li>Back-to-Back instructor conflicts (an instructor is enrolled in two
911         * exams that are scheduled in consecutive periods, weighted by
912         * Exams.InstructorBackToBackConflictWeight). If Exams.IsDayBreakBackToBack
913         * is false, there is no conflict between the last period and the first
914         * period of consecutive days.
915         * <li>Distance Back-to-Back instructor conflicts (same as Back-to-Back
916         * instructor conflict, but the maximum distance between rooms in which both
917         * exam take place is greater than Exams.BackToBackDistance, weighted by
918         * Exams.InstructorDistanceBackToBackConflictWeight).
919         * <li>More than two exams a day (an instructor is enrolled in three exams
920         * that are scheduled at the same day, weighted by
921         * Exams.InstructorMoreThanTwoADayWeight).
922         * <li>Perturbation penalty (total of period penalties
923         * {@link ExamPlacement#getPerturbationPenalty()} of all assigned exams,
924         * weighted by Exams.PerturbationWeight).
925         * <li>Front load penalty {@link ExamPlacement#getLargePenalty()} of all
926         * assigned exams, weighted by Exam.LargeWeight
927         * </ul>
928         * 
929         * @return weighted sum of objective criteria
930         */
931        @Override
932        public double getTotalValue() {
933            return getDirectConflictWeight() * getNrDirectConflicts(false) + getMoreThanTwoADayWeight()
934                    * getNrMoreThanTwoADayConflicts(false) + getBackToBackConflictWeight()
935                    * getNrBackToBackConflicts(false) + getDistanceBackToBackConflictWeight()
936                    * getNrDistanceBackToBackConflicts(false) + getPeriodWeight() * getPeriodPenalty(false)
937                    + getPeriodIndexWeight() * getPeriodIndexPenalty(false) + getPeriodSizeWeight()
938                    * getPeriodSizePenalty(false) + getPeriodIndexWeight() * getPeriodIndexPenalty(false)
939                    + getRoomSizeWeight() * getRoomSizePenalty(false) + getRoomSplitWeight() * getRoomSplitPenalty(false)
940                    + getRoomWeight() * getRoomPenalty(false) + getDistributionWeight() * getDistributionPenalty(false)
941                    + getInstructorDirectConflictWeight() * getNrInstructorDirectConflicts(false)
942                    + getInstructorMoreThanTwoADayWeight() * getNrInstructorMoreThanTwoADayConflicts(false)
943                    + getInstructorBackToBackConflictWeight() * getNrInstructorBackToBackConflicts(false)
944                    + getInstructorDistanceBackToBackConflictWeight() * getNrInstructorDistanceBackToBackConflicts(false)
945                    + getExamRotationWeight() * getExamRotationPenalty(false) + getPerturbationWeight()
946                    * getPerturbationPenalty(false) + getRoomPerturbationWeight() * getRoomPerturbationPenalty(false)
947                    + getRoomSplitDistanceWeight() * getRoomSplitDistancePenalty(false) + getLargeWeight()
948                    * getLargePenalty(false);
949        }
950    
951        /**
952         * Return weighted individual objective criteria. The objective function
953         * consists of the following criteria:
954         * <ul>
955         * <li>Direct student conflicts (a student is enrolled in two exams that are
956         * scheduled at the same period, weighted by Exams.DirectConflictWeight)
957         * <li>Back-to-Back student conflicts (a student is enrolled in two exams
958         * that are scheduled in consecutive periods, weighted by
959         * Exams.BackToBackConflictWeight). If Exams.IsDayBreakBackToBack is false,
960         * there is no conflict between the last period and the first period of
961         * consecutive days.
962         * <li>Distance Back-to-Back student conflicts (same as Back-to-Back student
963         * conflict, but the maximum distance between rooms in which both exam take
964         * place is greater than Exams.BackToBackDistance, weighted by
965         * Exams.DistanceBackToBackConflictWeight).
966         * <li>More than two exams a day (a student is enrolled in three exams that
967         * are scheduled at the same day, weighted by Exams.MoreThanTwoADayWeight).
968         * <li>Period penalty (total of period penalties
969         * {@link ExamPlacement#getPeriodPenalty()} of all assigned exams, weighted
970         * by Exams.PeriodWeight).
971         * <li>Room size penalty (total of room size penalties
972         * {@link ExamPlacement#getRoomSizePenalty()} of all assigned exams,
973         * weighted by Exams.RoomSizeWeight).
974         * <li>Room split penalty (total of room split penalties
975         * {@link ExamPlacement#getRoomSplitPenalty()} of all assigned exams,
976         * weighted by Exams.RoomSplitWeight).
977         * <li>Room split distance penalty
978         * {@link ExamPlacement#getRoomSplitDistancePenalty()}, of all assigned
979         * exams, weighted by {@link ExamModel#getRoomSplitDistanceWeight()}
980         * <li>Room penalty (total of room penalties
981         * {@link ExamPlacement#getRoomPenalty()} of all assigned exams, weighted by
982         * Exams.RoomWeight).
983         * <li>Distribution penalty (total of room split penalties
984         * {@link ExamDistributionConstraint#getWeight()} of all soft violated
985         * distribution constraints, weighted by Exams.DistributionWeight).
986         * <li>Direct instructor conflicts (an instructor is enrolled in two exams
987         * that are scheduled at the same period, weighted by
988         * Exams.InstructorDirectConflictWeight)
989         * <li>Back-to-Back instructor conflicts (an instructor is enrolled in two
990         * exams that are scheduled in consecutive periods, weighted by
991         * Exams.InstructorBackToBackConflictWeight). If Exams.IsDayBreakBackToBack
992         * is false, there is no conflict between the last period and the first
993         * period of consecutive days.
994         * <li>Distance Back-to-Back instructor conflicts (same as Back-to-Back
995         * instructor conflict, but the maximum distance between rooms in which both
996         * exam take place is greater than Exams.BackToBackDistance, weighted by
997         * Exams.InstructorDistanceBackToBackConflictWeight).
998         * <li>More than two exams a day (an instructor is enrolled in three exams
999         * that are scheduled at the same day, weighted by
1000         * Exams.InstructorMoreThanTwoADayWeight).
1001         * <li>Perturbation penalty (total of period penalties
1002         * {@link ExamPlacement#getPerturbationPenalty()} of all assigned exams,
1003         * weighted by Exams.PerturbationWeight).
1004         * <li>Front load penalty {@link ExamPlacement#getLargePenalty()} of all
1005         * assigned exams, weighted by Exam.LargeWeight
1006         * </ul>
1007         * 
1008         * @return an array of weighted objective criteria
1009         */
1010        public double[] getTotalMultiValue() {
1011            return new double[] { getDirectConflictWeight() * getNrDirectConflicts(false),
1012                    getMoreThanTwoADayWeight() * getNrMoreThanTwoADayConflicts(false),
1013                    getBackToBackConflictWeight() * getNrBackToBackConflicts(false),
1014                    getDistanceBackToBackConflictWeight() * getNrDistanceBackToBackConflicts(false),
1015                    getPeriodWeight() * getPeriodPenalty(false), getPeriodSizeWeight() * getPeriodSizePenalty(false),
1016                    getPeriodIndexWeight() * getPeriodIndexPenalty(false), getRoomSizeWeight() * getRoomSizePenalty(false),
1017                    getRoomSplitWeight() * getRoomSplitPenalty(false),
1018                    getRoomSplitDistanceWeight() * getRoomSplitDistancePenalty(false),
1019                    getRoomWeight() * getRoomPenalty(false), getDistributionWeight() * getDistributionPenalty(false),
1020                    getInstructorDirectConflictWeight() * getNrInstructorDirectConflicts(false),
1021                    getInstructorMoreThanTwoADayWeight() * getNrInstructorMoreThanTwoADayConflicts(false),
1022                    getInstructorBackToBackConflictWeight() * getNrInstructorBackToBackConflicts(false),
1023                    getInstructorDistanceBackToBackConflictWeight() * getNrInstructorDistanceBackToBackConflicts(false),
1024                    getExamRotationWeight() * getExamRotationPenalty(false),
1025                    getPerturbationWeight() * getPerturbationPenalty(false),
1026                    getRoomPerturbationWeight() * getRoomPerturbationPenalty(false),
1027                    getLargeWeight() * getLargePenalty(false) };
1028        }
1029    
1030        /**
1031         * String representation -- returns a list of values of objective criteria
1032         */
1033        @Override
1034        public String toString() {
1035            return "DC:" + getNrDirectConflicts(false) + "," + "M2D:" + getNrMoreThanTwoADayConflicts(false) + "," + "BTB:"
1036                    + getNrBackToBackConflicts(false) + ","
1037                    + (getBackToBackDistance() < 0 ? "" : "dBTB:" + getNrDistanceBackToBackConflicts(false) + ",") + "PP:"
1038                    + getPeriodPenalty(false) + "," + "PSP:" + getPeriodSizePenalty(false) + "," + "PX:"
1039                    + getPeriodIndexPenalty(false) + "," + "@P:" + getExamRotationPenalty(false) + "," + "RSz:"
1040                    + getRoomSizePenalty(false) + "," + "RSp:" + getRoomSplitPenalty(false) + "," + "RD:"
1041                    + sDoubleFormat.format(getRoomSplitDistancePenalty(false)) + "," + "RP:" + getRoomPenalty(false) + ","
1042                    + "DP:" + getDistributionPenalty(false) + (getLargeSize() >= 0 ? ",LP:" + getLargePenalty(false) : "")
1043                    + (isMPP() ? ",IP:" + getPerturbationPenalty(false) + ",IRP:" + getRoomPerturbationPenalty(false) : "");
1044        }
1045    
1046        /**
1047         * Return number of direct student conflicts, i.e., the total number of
1048         * cases where a student is enrolled into two exams that are scheduled at
1049         * the same period.
1050         * 
1051         * @param precise
1052         *            if false, the cached value is used
1053         * @return number of direct student conflicts
1054         */
1055        public int getNrDirectConflicts(boolean precise) {
1056            if (!precise)
1057                return iNrDirectConflicts;
1058            int conflicts = 0;
1059            for (ExamStudent student : getStudents()) {
1060                for (ExamPeriod period : getPeriods()) {
1061                    int nrExams = student.getExams(period).size();
1062                    if (!student.isAvailable(period))
1063                        conflicts += nrExams;
1064                    else if (nrExams > 1)
1065                        conflicts += nrExams - 1;
1066                }
1067            }
1068            return conflicts;
1069        }
1070    
1071        /**
1072         * Return number of back-to-back student conflicts, i.e., the total number
1073         * of cases where a student is enrolled into two exams that are scheduled at
1074         * consecutive periods. If {@link ExamModel#isDayBreakBackToBack()} is
1075         * false, the last period of one day and the first period of the following
1076         * day are not considered as consecutive periods.
1077         * 
1078         * @param precise
1079         *            if false, the cached value is used
1080         * @return number of back-to-back student conflicts
1081         */
1082        public int getNrBackToBackConflicts(boolean precise) {
1083            if (!precise)
1084                return iNrBackToBackConflicts;
1085            int conflicts = 0;
1086            for (ExamStudent student : getStudents()) {
1087                for (ExamPeriod period : getPeriods()) {
1088                    int nrExams = student.getExams(period).size();
1089                    if (nrExams == 0)
1090                        continue;
1091                    if (period.next() != null && !student.getExams(period.next()).isEmpty()
1092                            && (isDayBreakBackToBack() || period.next().getDay() == period.getDay()))
1093                        conflicts += nrExams * student.getExams(period.next()).size();
1094                }
1095            }
1096            return conflicts;
1097        }
1098    
1099        /**
1100         * Return number of distance back-to-back student conflicts, i.e., the total
1101         * number of back-to-back student conflicts where the two exam take place in
1102         * rooms that are too far a part (i.e.,
1103         * {@link ExamPlacement#getDistanceInMeters(ExamPlacement)} is greater than
1104         * {@link ExamModel#getBackToBackDistance()}).
1105         * 
1106         * @param precise
1107         *            if false, the cached value is used
1108         * @return number of distance back-to-back student conflicts
1109         */
1110        public int getNrDistanceBackToBackConflicts(boolean precise) {
1111            if (getBackToBackDistance() < 0)
1112                return 0;
1113            if (!precise)
1114                return iNrDistanceBackToBackConflicts;
1115            int conflicts = 0;
1116            for (ExamStudent student : getStudents()) {
1117                for (ExamPeriod period : getPeriods()) {
1118                    Set<Exam> exams = student.getExams(period);
1119                    if (exams.isEmpty())
1120                        continue;
1121                    if (period.next() != null && !student.getExams(period.next()).isEmpty()
1122                            && period.next().getDay() == period.getDay()) {
1123                        for (Exam x1 : exams) {
1124                            ExamPlacement p1 = x1.getAssignment();
1125                            for (Exam x2 : student.getExams(period.next())) {
1126                                ExamPlacement p2 = x2.getAssignment();
1127                                if (p1.getDistanceInMeters(p2) > getBackToBackDistance())
1128                                    conflicts++;
1129                            }
1130                        }
1131                    }
1132                }
1133            }
1134            return conflicts;
1135        }
1136    
1137        /**
1138         * Return number of more than two exams a day student conflicts, i.e., the
1139         * total number of cases where a student is enrolled into three exams that
1140         * are scheduled at the same day (i.e., {@link ExamPeriod#getDay()} is the
1141         * same).
1142         * 
1143         * @param precise
1144         *            if false, the cached value is used
1145         * @return number of more than two exams a day student conflicts
1146         */
1147        public int getNrMoreThanTwoADayConflicts(boolean precise) {
1148            if (!precise)
1149                return iNrMoreThanTwoADayConflicts;
1150            int conflicts = 0;
1151            for (ExamStudent student : getStudents()) {
1152                for (int d = 0; d < getNrDays(); d++) {
1153                    int nrExams = student.getExamsADay(d).size();
1154                    if (nrExams > 2)
1155                        conflicts += nrExams - 2;
1156                }
1157            }
1158            return conflicts;
1159        }
1160    
1161        /**
1162         * Return number of direct instructor conflicts, i.e., the total number of
1163         * cases where an instructor is enrolled into two exams that are scheduled
1164         * at the same period.
1165         * 
1166         * @param precise
1167         *            if false, the cached value is used
1168         * @return number of direct instructor conflicts
1169         */
1170        public int getNrInstructorDirectConflicts(boolean precise) {
1171            if (!precise)
1172                return iNrInstructorDirectConflicts;
1173            int conflicts = 0;
1174            for (ExamInstructor instructor : getInstructors()) {
1175                for (ExamPeriod period : getPeriods()) {
1176                    int nrExams = instructor.getExams(period).size();
1177                    if (!instructor.isAvailable(period))
1178                        conflicts += nrExams;
1179                    else if (nrExams > 1)
1180                        conflicts += nrExams - 1;
1181                }
1182            }
1183            return conflicts;
1184        }
1185    
1186        /**
1187         * Return number of back-to-back instructor conflicts, i.e., the total
1188         * number of cases where an instructor is enrolled into two exams that are
1189         * scheduled at consecutive periods. If
1190         * {@link ExamModel#isDayBreakBackToBack()} is false, the last period of one
1191         * day and the first period of the following day are not considered as
1192         * consecutive periods.
1193         * 
1194         * @param precise
1195         *            if false, the cached value is used
1196         * @return number of back-to-back instructor conflicts
1197         */
1198        public int getNrInstructorBackToBackConflicts(boolean precise) {
1199            if (!precise)
1200                return iNrInstructorBackToBackConflicts;
1201            int conflicts = 0;
1202            for (ExamInstructor instructor : getInstructors()) {
1203                for (ExamPeriod period : getPeriods()) {
1204                    int nrExams = instructor.getExams(period).size();
1205                    if (nrExams == 0)
1206                        continue;
1207                    if (period.next() != null && !instructor.getExams(period.next()).isEmpty()
1208                            && (isDayBreakBackToBack() || period.next().getDay() == period.getDay()))
1209                        conflicts += nrExams * instructor.getExams(period.next()).size();
1210                }
1211            }
1212            return conflicts;
1213        }
1214    
1215        /**
1216         * Return number of distance back-to-back instructor conflicts, i.e., the
1217         * total number of back-to-back instructor conflicts where the two exam take
1218         * place in rooms that are too far a part (i.e.,
1219         * {@link ExamPlacement#getDistanceInMeters(ExamPlacement)} is greater than
1220         * {@link ExamModel#getBackToBackDistance()}).
1221         * 
1222         * @param precise
1223         *            if false, the cached value is used
1224         * @return number of distance back-to-back student conflicts
1225         */
1226        public int getNrInstructorDistanceBackToBackConflicts(boolean precise) {
1227            if (getBackToBackDistance() < 0)
1228                return 0;
1229            if (!precise)
1230                return iNrInstructorDistanceBackToBackConflicts;
1231            int conflicts = 0;
1232            for (ExamInstructor instructor : getInstructors()) {
1233                for (ExamPeriod period : getPeriods()) {
1234                    Set<Exam> exams = instructor.getExams(period);
1235                    if (exams.isEmpty())
1236                        continue;
1237                    if (period.next() != null && !instructor.getExams(period.next()).isEmpty()
1238                            && period.next().getDay() == period.getDay()) {
1239                        for (Exam x1 : exams) {
1240                            ExamPlacement p1 = x1.getAssignment();
1241                            for (Exam x2 : instructor.getExams(period.next())) {
1242                                ExamPlacement p2 = x2.getAssignment();
1243                                if (p1.getDistanceInMeters(p2) > getBackToBackDistance())
1244                                    conflicts++;
1245                            }
1246                        }
1247                    }
1248                }
1249            }
1250            return conflicts;
1251        }
1252    
1253        /**
1254         * Return number of more than two exams a day instructor conflicts, i.e.,
1255         * the total number of cases where an instructor is enrolled into three
1256         * exams that are scheduled at the same day (i.e.,
1257         * {@link ExamPeriod#getDay()} is the same).
1258         * 
1259         * @param precise
1260         *            if false, the cached value is used
1261         * @return number of more than two exams a day student conflicts
1262         */
1263        public int getNrInstructorMoreThanTwoADayConflicts(boolean precise) {
1264            if (!precise)
1265                return iNrInstructorMoreThanTwoADayConflicts;
1266            int conflicts = 0;
1267            for (ExamInstructor instructor : getInstructors()) {
1268                for (int d = 0; d < getNrDays(); d++) {
1269                    int nrExams = instructor.getExamsADay(d).size();
1270                    if (nrExams > 2)
1271                        conflicts += nrExams - 2;
1272                }
1273            }
1274            return conflicts;
1275        }
1276    
1277        /**
1278         * Return total room size penalty, i.e., the sum of
1279         * {@link ExamPlacement#getRoomSizePenalty()} of all assigned placements.
1280         * 
1281         * @param precise
1282         *            if false, the cached value is used
1283         * @return total room size penalty
1284         */
1285        public int getRoomSizePenalty(boolean precise) {
1286            if (!precise)
1287                return iRoomSizePenalty;
1288            int penalty = 0;
1289            for (Exam exam : assignedVariables()) {
1290                penalty += exam.getAssignment().getRoomSizePenalty();
1291            }
1292            return penalty;
1293        }
1294    
1295        /**
1296         * Return total room split penalty, i.e., the sum of
1297         * {@link ExamPlacement#getRoomSplitPenalty()} of all assigned placements.
1298         * 
1299         * @param precise
1300         *            if false, the cached value is used
1301         * @return total room split penalty
1302         */
1303        public int getRoomSplitPenalty(boolean precise) {
1304            if (!precise)
1305                return iRoomSplitPenalty;
1306            int penalty = 0;
1307            for (Exam exam : assignedVariables()) {
1308                penalty += exam.getAssignment().getRoomSplitPenalty();
1309            }
1310            return penalty;
1311        }
1312    
1313        /**
1314         * Return total period penalty, i.e., the sum of
1315         * {@link ExamPlacement#getPeriodPenalty()} of all assigned placements.
1316         * 
1317         * @param precise
1318         *            if false, the cached value is used
1319         * @return total period penalty
1320         */
1321        public int getPeriodPenalty(boolean precise) {
1322            if (!precise)
1323                return iPeriodPenalty;
1324            int penalty = 0;
1325            for (Exam exam : assignedVariables()) {
1326                penalty += exam.getAssignment().getPeriodPenalty();
1327            }
1328            return penalty;
1329        }
1330    
1331        /**
1332         * Return total period index of all assigned placements.
1333         * 
1334         * @param precise
1335         *            if false, the cached value is used
1336         * @return total period penalty
1337         */
1338        public int getPeriodIndexPenalty(boolean precise) {
1339            if (!precise)
1340                return iPeriodIndexPenalty;
1341            int penalty = 0;
1342            for (Exam exam : assignedVariables()) {
1343                penalty += (exam.getAssignment()).getPeriod().getIndex();
1344            }
1345            return penalty;
1346        }
1347    
1348        /**
1349         * Return total period size penalty, i.e., the sum of
1350         * {@link ExamPlacement#getPeriodPenalty()} multiplied by
1351         * {@link Exam#getSize()} of all assigned placements.
1352         * 
1353         * @param precise
1354         *            if false, the cached value is used
1355         * @return total period penalty
1356         */
1357        public int getPeriodSizePenalty(boolean precise) {
1358            if (!precise)
1359                return iPeriodSizePenalty;
1360            int penalty = 0;
1361            for (Exam exam : assignedVariables()) {
1362                penalty += exam.getAssignment().getPeriodPenalty() * (exam.getSize() + 1);
1363            }
1364            return penalty;
1365        }
1366    
1367        /**
1368         * Return total exam rotation penalty, i.e., the sum of
1369         * {@link ExamPlacement#getRotationPenalty()} of all assigned placements.
1370         * 
1371         * @param precise
1372         *            if false, the cached value is used
1373         * @return total period penalty
1374         */
1375        public int getExamRotationPenalty(boolean precise) {
1376            if (!precise)
1377                return iExamRotationPenalty;
1378            int penalty = 0;
1379            for (Exam exam : assignedVariables()) {
1380                penalty += exam.getAssignment().getRotationPenalty();
1381            }
1382            return penalty;
1383        }
1384    
1385        /**
1386         * Return total room (weight) penalty, i.e., the sum of
1387         * {@link ExamPlacement#getRoomPenalty()} of all assigned placements.
1388         * 
1389         * @param precise
1390         *            if false, the cached value is used
1391         * @return total room penalty
1392         */
1393        public int getRoomPenalty(boolean precise) {
1394            if (!precise)
1395                return iRoomPenalty;
1396            int penalty = 0;
1397            for (Exam exam : assignedVariables()) {
1398                penalty += exam.getAssignment().getRoomPenalty();
1399            }
1400            return penalty;
1401        }
1402    
1403        /**
1404         * Return total distribution penalty, i.e., the sum of
1405         * {@link ExamDistributionConstraint#getWeight()} of all violated soft
1406         * distribution constraints.
1407         * 
1408         * @param precise
1409         *            if false, the cached value is used
1410         * @return total distribution penalty
1411         */
1412        public int getDistributionPenalty(boolean precise) {
1413            if (!precise)
1414                return iDistributionPenalty;
1415            int penalty = 0;
1416            for (ExamDistributionConstraint dc : getDistributionConstraints()) {
1417                if (!dc.isSatisfied())
1418                    penalty += dc.getWeight();
1419            }
1420            return penalty;
1421        }
1422    
1423        /**
1424         * Return total room split distance penalty, i.e., the sum of
1425         * {@link ExamPlacement#getRoomSplitDistancePenalty()} of all assigned
1426         * placements.
1427         * 
1428         * @param precise
1429         *            if false, the cached value is used
1430         * @return total room split distance penalty
1431         */
1432        public double getRoomSplitDistancePenalty(boolean precise) {
1433            if (!precise)
1434                return iRoomSplitDistancePenalty;
1435            double penalty = 0;
1436            for (Exam exam : assignedVariables()) {
1437                penalty += exam.getAssignment().getRoomSplitDistancePenalty();
1438            }
1439            return penalty;
1440        }
1441    
1442        /**
1443         * Count exam placements with a room split.
1444         * 
1445         * @param precise
1446         *            if false, the cached value is used
1447         * @return total number of exams that are assigned into two or more rooms
1448         */
1449        public double getNrRoomSplits(boolean precise) {
1450            if (!precise)
1451                return iRoomSplits;
1452            int penalty = 0;
1453            for (Exam exam : assignedVariables()) {
1454                penalty += (exam.getAssignment().getRoomPlacements().size() > 1 ? 1 : 0);
1455            }
1456            return penalty;
1457        }
1458    
1459        /**
1460         * To be called by soft {@link ExamDistributionConstraint} when satisfaction
1461         * changes.
1462         */
1463        protected void addDistributionPenalty(int inc) {
1464            iDistributionPenalty += inc;
1465        }
1466    
1467        private Integer iMaxDistributionPenalty = null;
1468    
1469        private int getMaxDistributionPenalty() {
1470            if (iMaxDistributionPenalty == null) {
1471                int maxDistributionPenalty = 0;
1472                for (ExamDistributionConstraint dc : getDistributionConstraints()) {
1473                    if (dc.isHard())
1474                        continue;
1475                    maxDistributionPenalty += dc.getWeight();
1476                }
1477                iMaxDistributionPenalty = new Integer(maxDistributionPenalty);
1478            }
1479            return iMaxDistributionPenalty.intValue();
1480        }
1481    
1482        private int[] iLimits = null;
1483    
1484        private int getMinPenalty(ExamRoom r) {
1485            int min = Integer.MAX_VALUE;
1486            for (ExamPeriod p : getPeriods()) {
1487                if (r.isAvailable(p)) {
1488                    min = Math.min(min, r.getPenalty(p));
1489                }
1490            }
1491            return min;
1492        }
1493    
1494        private int getMaxPenalty(ExamRoom r) {
1495            int max = Integer.MIN_VALUE;
1496            for (ExamPeriod p : getPeriods()) {
1497                if (r.isAvailable(p)) {
1498                    max = Math.max(max, r.getPenalty(p));
1499                }
1500            }
1501            return max;
1502        }
1503    
1504        private int[] getLimits() {
1505            if (iLimits == null) {
1506                int minPeriodPenalty = 0, maxPeriodPenalty = 0;
1507                int minPeriodSizePenalty = 0, maxPeriodSizePenalty = 0;
1508                int minRoomPenalty = 0, maxRoomPenalty = 0;
1509                for (Exam exam : variables()) {
1510                    if (!exam.getPeriodPlacements().isEmpty()) {
1511                        int minPenalty = Integer.MAX_VALUE, maxPenalty = Integer.MIN_VALUE;
1512                        int minSizePenalty = Integer.MAX_VALUE, maxSizePenalty = Integer.MIN_VALUE;
1513                        for (ExamPeriodPlacement periodPlacement : exam.getPeriodPlacements()) {
1514                            minPenalty = Math.min(minPenalty, periodPlacement.getPenalty());
1515                            maxPenalty = Math.max(maxPenalty, periodPlacement.getPenalty());
1516                            minSizePenalty = Math.min(minSizePenalty, periodPlacement.getPenalty() * (exam.getSize() + 1));
1517                            maxSizePenalty = Math.max(maxSizePenalty, periodPlacement.getPenalty() * (exam.getSize() + 1));
1518                        }
1519                        minPeriodPenalty += minPenalty;
1520                        maxPeriodPenalty += maxPenalty;
1521                        minPeriodSizePenalty += minSizePenalty;
1522                        maxPeriodSizePenalty += maxSizePenalty;
1523                    }
1524                    if (!exam.getRoomPlacements().isEmpty()) {
1525                        int minPenalty = Integer.MAX_VALUE, maxPenalty = Integer.MIN_VALUE;
1526                        for (ExamRoomPlacement roomPlacement : exam.getRoomPlacements()) {
1527                            minPenalty = Math.min(minPenalty, (roomPlacement.getPenalty() != 0 ? roomPlacement.getPenalty()
1528                                    : getMinPenalty(roomPlacement.getRoom())));
1529                            maxPenalty = Math.max(maxPenalty, (roomPlacement.getPenalty() != 0 ? roomPlacement.getPenalty()
1530                                    : getMaxPenalty(roomPlacement.getRoom())));
1531                        }
1532                        minRoomPenalty += minPenalty;
1533                        maxRoomPenalty += maxPenalty;
1534                    }
1535                }
1536                iLimits = new int[] { minPeriodPenalty, maxPeriodPenalty, minRoomPenalty, maxRoomPenalty,
1537                        minPeriodSizePenalty, maxPeriodSizePenalty };
1538            }
1539            return iLimits;
1540        }
1541    
1542        /**
1543         * Return total perturbation penalty, i.e., the sum of
1544         * {@link ExamPlacement#getPerturbationPenalty()} of all assigned
1545         * placements.
1546         * 
1547         * @param precise
1548         *            if false, the cached value is used
1549         * @return total period penalty
1550         */
1551        public int getPerturbationPenalty(boolean precise) {
1552            if (!precise)
1553                return iPerturbationPenalty;
1554            int penalty = 0;
1555            for (Exam exam : assignedVariables()) {
1556                penalty += exam.getAssignment().getPerturbationPenalty();
1557            }
1558            return penalty;
1559        }
1560    
1561        /**
1562         * Return total room perturbation penalty, i.e., the sum of
1563         * {@link ExamPlacement#getRoomPerturbationPenalty()} of all assigned
1564         * placements.
1565         * 
1566         * @param precise
1567         *            if false, the cached value is used
1568         * @return total room period penalty
1569         */
1570        public int getRoomPerturbationPenalty(boolean precise) {
1571            if (!precise)
1572                return iRoomPerturbationPenalty;
1573            int penalty = 0;
1574            for (Exam exam : assignedVariables()) {
1575                penalty += exam.getAssignment().getRoomPerturbationPenalty();
1576            }
1577            return penalty;
1578        }
1579    
1580        /**
1581         * Return total front load penalty, i.e., the sum of
1582         * {@link ExamPlacement#getLargePenalty()} of all assigned placements.
1583         * 
1584         * @param precise
1585         *            if false, the cached value is used
1586         * @return total period penalty
1587         */
1588        public int getLargePenalty(boolean precise) {
1589            if (!precise)
1590                return iLargePenalty;
1591            int penalty = 0;
1592            for (Exam exam : assignedVariables()) {
1593                penalty += exam.getAssignment().getLargePenalty();
1594            }
1595            return penalty;
1596        }
1597    
1598        /**
1599         * Info table
1600         */
1601        @Override
1602        public Map<String, String> getInfo() {
1603            Map<String, String> info = super.getInfo();
1604            info.put("Direct Conflicts", getNrDirectConflicts(false)
1605                    + (iNrNADirectConflicts > 0 ? " (" + iNrNADirectConflicts + " N/A)" : ""));
1606            info.put("More Than 2 A Day Conflicts", String.valueOf(getNrMoreThanTwoADayConflicts(false)));
1607            info.put("Back-To-Back Conflicts", String.valueOf(getNrBackToBackConflicts(false)));
1608            if (getBackToBackDistance() >= 0 && getNrDistanceBackToBackConflicts(false) > 0)
1609                info.put("Distance Back-To-Back Conflicts", String.valueOf(getNrDistanceBackToBackConflicts(false)));
1610            if (getNrInstructorDirectConflicts(false) > 0)
1611                info.put("Instructor Direct Conflicts", getNrInstructorDirectConflicts(false)
1612                        + (iNrNAInstructorDirectConflicts > 0 ? " (" + iNrNAInstructorDirectConflicts + " N/A)" : ""));
1613            if (getNrInstructorMoreThanTwoADayConflicts(false) > 0)
1614                info.put("Instructor More Than 2 A Day Conflicts", String
1615                        .valueOf(getNrInstructorMoreThanTwoADayConflicts(false)));
1616            if (getNrInstructorBackToBackConflicts(false) > 0)
1617                info.put("Instructor Back-To-Back Conflicts", String.valueOf(getNrInstructorBackToBackConflicts(false)));
1618            if (getBackToBackDistance() >= 0 && getNrInstructorDistanceBackToBackConflicts(false) > 0)
1619                info.put("Instructor Distance Back-To-Back Conflicts", String
1620                        .valueOf(getNrInstructorDistanceBackToBackConflicts(false)));
1621            if (nrAssignedVariables() > 0 && getRoomSizePenalty(false) > 0)
1622                info.put("Room Size Penalty", sDoubleFormat.format(((double) getRoomSizePenalty(false))
1623                        / nrAssignedVariables()));
1624            if (getRoomSplitPenalty(false) > 0) {
1625                String split = "";
1626                for (int i = 2; i < getMaxRooms(); i++)
1627                    if (iRoomSplitPenalties[i] > 0) {
1628                        if (split.length() > 0)
1629                            split += ", ";
1630                        split += iRoomSplitPenalties[i] + "&times;" + i;
1631                    }
1632                info.put("Room Split Penalty", getRoomSplitPenalty(false) + " (" + split + ")");
1633            }
1634            info.put("Period Penalty", getPerc(getPeriodPenalty(false), getLimits()[0], getLimits()[1]) + "% ("
1635                    + getPeriodPenalty(false) + ")");
1636            info.put("Period&times;Size Penalty", getPerc(getPeriodSizePenalty(false), getLimits()[4], getLimits()[5])
1637                    + "% (" + getPeriodSizePenalty(false) + ")");
1638            info.put("Average Period", sDoubleFormat
1639                    .format(((double) getPeriodIndexPenalty(false)) / nrAssignedVariables()));
1640            info.put("Room Penalty", getPerc(getRoomPenalty(false), getLimits()[2], getLimits()[3]) + "% ("
1641                    + getRoomPenalty(false) + ")");
1642            info.put("Distribution Penalty", getPerc(getDistributionPenalty(false), 0, getMaxDistributionPenalty()) + "% ("
1643                    + getDistributionPenalty(false) + ")");
1644            info.put("Room Split Distance Penalty", sDoubleFormat.format(getRoomSplitDistancePenalty(false)
1645                    / getNrRoomSplits(false)));
1646            if (getExamRotationPenalty(false) > 0)
1647                info.put("Exam Rotation Penalty", String.valueOf(getExamRotationPenalty(false)));
1648            if (isMPP()) {
1649                info.put("Perturbation Penalty", sDoubleFormat.format(((double) getPerturbationPenalty(false))
1650                        / nrAssignedVariables()));
1651                info.put("Room Perturbation Penalty", sDoubleFormat.format(((double) getRoomPerturbationPenalty(false))
1652                        / nrAssignedVariables()));
1653            }
1654            if (getLargeSize() >= 0)
1655                info.put("Large Exams Penalty", getPerc(getLargePenalty(false), 0, iNrLargeExams) + "% ("
1656                        + getLargePenalty(false) + ")");
1657            return info;
1658        }
1659    
1660        /**
1661         * Extended info table
1662         */
1663        @Override
1664        public Map<String, String> getExtendedInfo() {
1665            Map<String, String> info = super.getExtendedInfo();
1666            info.put("Direct Conflicts [p]", String.valueOf(getNrDirectConflicts(true)));
1667            info.put("More Than 2 A Day Conflicts [p]", String.valueOf(getNrMoreThanTwoADayConflicts(true)));
1668            info.put("Back-To-Back Conflicts [p]", String.valueOf(getNrBackToBackConflicts(true)));
1669            info.put("Distance Back-To-Back Conflicts [p]", String.valueOf(getNrDistanceBackToBackConflicts(true)));
1670            info.put("Instructor Direct Conflicts [p]", String.valueOf(getNrInstructorDirectConflicts(true)));
1671            info.put("Instructor More Than 2 A Day Conflicts [p]", String
1672                    .valueOf(getNrInstructorMoreThanTwoADayConflicts(true)));
1673            info.put("Instructor Back-To-Back Conflicts [p]", String.valueOf(getNrInstructorBackToBackConflicts(true)));
1674            info.put("Instructor Distance Back-To-Back Conflicts [p]", String
1675                    .valueOf(getNrInstructorDistanceBackToBackConflicts(true)));
1676            info.put("Room Size Penalty [p]", String.valueOf(getRoomSizePenalty(true)));
1677            info.put("Room Split Penalty [p]", String.valueOf(getRoomSplitPenalty(true)));
1678            info.put("Period Penalty [p]", String.valueOf(getPeriodPenalty(true)));
1679            info.put("Period Size Penalty [p]", String.valueOf(getPeriodSizePenalty(true)));
1680            info.put("Period Index Penalty [p]", String.valueOf(getPeriodIndexPenalty(true)));
1681            info.put("Room Penalty [p]", String.valueOf(getRoomPenalty(true)));
1682            info.put("Distribution Penalty [p]", String.valueOf(getDistributionPenalty(true)));
1683            info.put("Perturbation Penalty [p]", String.valueOf(getPerturbationPenalty(true)));
1684            info.put("Room Perturbation Penalty [p]", String.valueOf(getRoomPerturbationPenalty(true)));
1685            info.put("Room Split Distance Penalty [p]", sDoubleFormat.format(getRoomSplitDistancePenalty(true)) + " / "
1686                    + getNrRoomSplits(true));
1687            info.put("Number of Periods", String.valueOf(getPeriods().size()));
1688            info.put("Number of Exams", String.valueOf(variables().size()));
1689            info.put("Number of Rooms", String.valueOf(getRooms().size()));
1690            info.put("Number of Students", String.valueOf(getStudents().size()));
1691            int nrStudentExams = 0;
1692            for (ExamStudent student : getStudents()) {
1693                nrStudentExams += student.getOwners().size();
1694            }
1695            info.put("Number of Student Exams", String.valueOf(nrStudentExams));
1696            int nrAltExams = 0, nrSmallExams = 0;
1697            for (Exam exam : variables()) {
1698                if (exam.hasAltSeating())
1699                    nrAltExams++;
1700                if (exam.getMaxRooms() == 0)
1701                    nrSmallExams++;
1702            }
1703            info.put("Number of Exams Requiring Alt Seating", String.valueOf(nrAltExams));
1704            info.put("Number of Small Exams (Exams W/O Room)", String.valueOf(nrSmallExams));
1705            int[] nbrMtgs = new int[11];
1706            for (int i = 0; i <= 10; i++)
1707                nbrMtgs[i] = 0;
1708            for (ExamStudent student : getStudents()) {
1709                nbrMtgs[Math.min(10, student.variables().size())]++;
1710            }
1711            for (int i = 0; i <= 10; i++) {
1712                if (nbrMtgs[i] == 0)
1713                    continue;
1714                info.put("Number of Students with " + (i == 0 ? "no" : String.valueOf(i)) + (i == 10 ? " or more" : "")
1715                        + " meeting" + (i != 1 ? "s" : ""), String.valueOf(nbrMtgs[i]));
1716            }
1717            return info;
1718        }
1719    
1720        /**
1721         * Problem properties
1722         */
1723        public DataProperties getProperties() {
1724            return iProperties;
1725        }
1726    
1727        /**
1728         * Problem rooms
1729         * 
1730         * @return list of {@link ExamRoom}
1731         */
1732        public List<ExamRoom> getRooms() {
1733            return iRooms;
1734        }
1735    
1736        /**
1737         * Problem students
1738         * 
1739         * @return list of {@link ExamStudent}
1740         */
1741        public List<ExamStudent> getStudents() {
1742            return iStudents;
1743        }
1744    
1745        /**
1746         * Problem instructors
1747         * 
1748         * @return list of {@link ExamInstructor}
1749         */
1750        public List<ExamInstructor> getInstructors() {
1751            return iInstructors;
1752        }
1753    
1754        /**
1755         * Distribution constraints
1756         * 
1757         * @return list of {@link ExamDistributionConstraint}
1758         */
1759        public List<ExamDistributionConstraint> getDistributionConstraints() {
1760            return iDistributionConstraints;
1761        }
1762    
1763        private String getId(boolean anonymize, String type, String id) {
1764            return (anonymize ? IdConvertor.getInstance().convert(type, id) : id);
1765        }
1766    
1767        /**
1768         * Save model (including its solution) into XML.
1769         */
1770        public Document save() {
1771            boolean saveInitial = getProperties().getPropertyBoolean("Xml.SaveInitial", true);
1772            boolean saveBest = getProperties().getPropertyBoolean("Xml.SaveBest", true);
1773            boolean saveSolution = getProperties().getPropertyBoolean("Xml.SaveSolution", true);
1774            boolean saveConflictTable = getProperties().getPropertyBoolean("Xml.SaveConflictTable", false);
1775            boolean saveParams = getProperties().getPropertyBoolean("Xml.SaveParameters", true);
1776            boolean anonymize = getProperties().getPropertyBoolean("Xml.Anonymize", false);
1777            Document document = DocumentHelper.createDocument();
1778            document.addComment("Examination Timetable");
1779            if (nrAssignedVariables() > 0) {
1780                StringBuffer comments = new StringBuffer("Solution Info:\n");
1781                Map<String, String> solutionInfo = (getProperties().getPropertyBoolean("Xml.ExtendedInfo", false) ? getExtendedInfo()
1782                        : getInfo());
1783                for (String key : new TreeSet<String>(solutionInfo.keySet())) {
1784                    String value = solutionInfo.get(key);
1785                    comments.append("    " + key + ": " + value + "\n");
1786                }
1787                document.addComment(comments.toString());
1788            }
1789            Element root = document.addElement("examtt");
1790            root.addAttribute("version", "1.0");
1791            root.addAttribute("campus", getProperties().getProperty("Data.Initiative"));
1792            root.addAttribute("term", getProperties().getProperty("Data.Term"));
1793            root.addAttribute("year", getProperties().getProperty("Data.Year"));
1794            root.addAttribute("created", String.valueOf(new Date()));
1795            if (saveParams) {
1796                Element params = root.addElement("parameters");
1797                params.addElement("property").addAttribute("name", "isDayBreakBackToBack").addAttribute("value",
1798                        (isDayBreakBackToBack() ? "true" : "false"));
1799                params.addElement("property").addAttribute("name", "directConflictWeight").addAttribute("value",
1800                        String.valueOf(getDirectConflictWeight()));
1801                params.addElement("property").addAttribute("name", "moreThanTwoADayWeight").addAttribute("value",
1802                        String.valueOf(getMoreThanTwoADayWeight()));
1803                params.addElement("property").addAttribute("name", "backToBackConflictWeight").addAttribute("value",
1804                        String.valueOf(getBackToBackConflictWeight()));
1805                params.addElement("property").addAttribute("name", "distanceBackToBackConflictWeight").addAttribute(
1806                        "value", String.valueOf(getDistanceBackToBackConflictWeight()));
1807                params.addElement("property").addAttribute("name", "backToBackDistance").addAttribute("value",
1808                        String.valueOf(getBackToBackDistance()));
1809                params.addElement("property").addAttribute("name", "maxRooms").addAttribute("value",
1810                        String.valueOf(getMaxRooms()));
1811                params.addElement("property").addAttribute("name", "periodWeight").addAttribute("value",
1812                        String.valueOf(getPeriodWeight()));
1813                params.addElement("property").addAttribute("name", "periodSizeWeight").addAttribute("value",
1814                        String.valueOf(getPeriodSizeWeight()));
1815                params.addElement("property").addAttribute("name", "periodIndexWeight").addAttribute("value",
1816                        String.valueOf(getPeriodIndexWeight()));
1817                params.addElement("property").addAttribute("name", "examRotationWeight").addAttribute("value",
1818                        String.valueOf(getExamRotationWeight()));
1819                params.addElement("property").addAttribute("name", "roomSizeWeight").addAttribute("value",
1820                        String.valueOf(getRoomSizeWeight()));
1821                params.addElement("property").addAttribute("name", "roomSplitWeight").addAttribute("value",
1822                        String.valueOf(getRoomSplitWeight()));
1823                params.addElement("property").addAttribute("name", "roomWeight").addAttribute("value",
1824                        String.valueOf(getRoomWeight()));
1825                params.addElement("property").addAttribute("name", "distributionWeight").addAttribute("value",
1826                        String.valueOf(getDistributionWeight()));
1827                params.addElement("property").addAttribute("name", "instructorDirectConflictWeight").addAttribute("value",
1828                        String.valueOf(getInstructorDirectConflictWeight()));
1829                params.addElement("property").addAttribute("name", "instructorMoreThanTwoADayWeight").addAttribute("value",
1830                        String.valueOf(getInstructorMoreThanTwoADayWeight()));
1831                params.addElement("property").addAttribute("name", "instructorBackToBackConflictWeight").addAttribute(
1832                        "value", String.valueOf(getInstructorBackToBackConflictWeight()));
1833                params.addElement("property").addAttribute("name", "instructorDistanceBackToBackConflictWeight")
1834                        .addAttribute("value", String.valueOf(getInstructorDistanceBackToBackConflictWeight()));
1835                params.addElement("property").addAttribute("name", "perturbationWeight").addAttribute("value",
1836                        String.valueOf(getPerturbationWeight()));
1837                params.addElement("property").addAttribute("name", "roomPerturbationWeight").addAttribute("value",
1838                        String.valueOf(getRoomPerturbationWeight()));
1839                params.addElement("property").addAttribute("name", "roomSplitDistanceWeight").addAttribute("value",
1840                        String.valueOf(getRoomSplitDistanceWeight()));
1841                params.addElement("property").addAttribute("name", "largeSize").addAttribute("value",
1842                        String.valueOf(getLargeSize()));
1843                params.addElement("property").addAttribute("name", "largePeriod").addAttribute("value",
1844                        String.valueOf(getLargePeriod()));
1845                params.addElement("property").addAttribute("name", "largeWeight").addAttribute("value",
1846                        String.valueOf(getLargeWeight()));
1847            }
1848            Element periods = root.addElement("periods");
1849            for (ExamPeriod period : getPeriods()) {
1850                periods.addElement("period").addAttribute("id", getId(anonymize, "period", String.valueOf(period.getId())))
1851                        .addAttribute("length", String.valueOf(period.getLength())).addAttribute("day", period.getDayStr())
1852                        .addAttribute("time", period.getTimeStr()).addAttribute("penalty",
1853                                String.valueOf(period.getPenalty()));
1854            }
1855            Element rooms = root.addElement("rooms");
1856            for (ExamRoom room : getRooms()) {
1857                Element r = rooms.addElement("room");
1858                r.addAttribute("id", getId(anonymize, "room", String.valueOf(room.getId())));
1859                if (!anonymize && room.hasName())
1860                    r.addAttribute("name", room.getName());
1861                r.addAttribute("size", String.valueOf(room.getSize()));
1862                r.addAttribute("alt", String.valueOf(room.getAltSize()));
1863                if (room.getCoordX() != null && room.getCoordY() != null)
1864                    r.addAttribute("coordinates", room.getCoordX() + "," + room.getCoordY());
1865                for (ExamPeriod period : getPeriods()) {
1866                    if (!room.isAvailable(period))
1867                        r.addElement("period").addAttribute("id",
1868                                getId(anonymize, "period", String.valueOf(period.getId()))).addAttribute("available",
1869                                "false");
1870                    else if (room.getPenalty(period) != 0)
1871                        r.addElement("period").addAttribute("id",
1872                                getId(anonymize, "period", String.valueOf(period.getId()))).addAttribute("penalty",
1873                                String.valueOf(room.getPenalty(period)));
1874                }
1875                Map<Long, Integer> travelTimes = getDistanceMetric().getTravelTimes().get(room.getId());
1876                if (travelTimes != null)
1877                    for (Map.Entry<Long, Integer> time: travelTimes.entrySet())
1878                        r.addElement("travel-time").addAttribute("id", getId(anonymize, "room", time.getKey().toString())).addAttribute("minutes", time.getValue().toString());
1879            }
1880            Element exams = root.addElement("exams");
1881            for (Exam exam : variables()) {
1882                Element ex = exams.addElement("exam");
1883                ex.addAttribute("id", getId(anonymize, "exam", String.valueOf(exam.getId())));
1884                if (!anonymize && exam.hasName())
1885                    ex.addAttribute("name", exam.getName());
1886                ex.addAttribute("length", String.valueOf(exam.getLength()));
1887                if (exam.getSizeOverride() != null)
1888                    ex.addAttribute("size", exam.getSizeOverride().toString());
1889                if (exam.getMinSize() != 0)
1890                    ex.addAttribute("minSize", String.valueOf(exam.getMinSize()));
1891                ex.addAttribute("alt", (exam.hasAltSeating() ? "true" : "false"));
1892                if (exam.getMaxRooms() != getMaxRooms())
1893                    ex.addAttribute("maxRooms", String.valueOf(exam.getMaxRooms()));
1894                if (exam.getPrintOffset() != null)
1895                    ex.addAttribute("printOffset", exam.getPrintOffset().toString());
1896                if (!anonymize)
1897                    ex.addAttribute("enrl", String.valueOf(exam.getStudents().size()));
1898                if (!anonymize)
1899                    for (ExamOwner owner : exam.getOwners()) {
1900                        Element o = ex.addElement("owner");
1901                        o.addAttribute("id", getId(anonymize, "owner", String.valueOf(owner.getId())));
1902                        o.addAttribute("name", owner.getName());
1903                    }
1904                for (ExamPeriodPlacement period : exam.getPeriodPlacements()) {
1905                    Element pe = ex.addElement("period").addAttribute("id",
1906                            getId(anonymize, "period", String.valueOf(period.getId())));
1907                    int penalty = period.getPenalty() - period.getPeriod().getPenalty();
1908                    if (penalty != 0)
1909                        pe.addAttribute("penalty", String.valueOf(penalty));
1910                }
1911                for (ExamRoomPlacement room : exam.getRoomPlacements()) {
1912                    Element re = ex.addElement("room").addAttribute("id",
1913                            getId(anonymize, "room", String.valueOf(room.getId())));
1914                    if (room.getPenalty() != 0)
1915                        re.addAttribute("penalty", String.valueOf(room.getPenalty()));
1916                    if (room.getMaxPenalty() != 100)
1917                        re.addAttribute("maxPenalty", String.valueOf(room.getMaxPenalty()));
1918                }
1919                if (exam.hasAveragePeriod())
1920                    ex.addAttribute("average", String.valueOf(exam.getAveragePeriod()));
1921                ExamPlacement p = exam.getAssignment();
1922                if (p != null && saveSolution) {
1923                    Element asg = ex.addElement("assignment");
1924                    asg.addElement("period").addAttribute("id",
1925                            getId(anonymize, "period", String.valueOf(p.getPeriod().getId())));
1926                    for (ExamRoomPlacement r : p.getRoomPlacements()) {
1927                        asg.addElement("room").addAttribute("id", getId(anonymize, "room", String.valueOf(r.getId())));
1928                    }
1929                }
1930                p = exam.getInitialAssignment();
1931                if (p != null && saveInitial) {
1932                    Element ini = ex.addElement("initial");
1933                    ini.addElement("period").addAttribute("id",
1934                            getId(anonymize, "period", String.valueOf(p.getPeriod().getId())));
1935                    for (ExamRoomPlacement r : p.getRoomPlacements()) {
1936                        ini.addElement("room").addAttribute("id", getId(anonymize, "room", String.valueOf(r.getId())));
1937                    }
1938                }
1939                p = exam.getBestAssignment();
1940                if (p != null && saveBest) {
1941                    Element ini = ex.addElement("best");
1942                    ini.addElement("period").addAttribute("id",
1943                            getId(anonymize, "period", String.valueOf(p.getPeriod().getId())));
1944                    for (ExamRoomPlacement r : p.getRoomPlacements()) {
1945                        ini.addElement("room").addAttribute("id", getId(anonymize, "room", String.valueOf(r.getId())));
1946                    }
1947                }
1948            }
1949            Element students = root.addElement("students");
1950            for (ExamStudent student : getStudents()) {
1951                Element s = students.addElement("student");
1952                s.addAttribute("id", getId(anonymize, "student", String.valueOf(student.getId())));
1953                for (Exam ex : student.variables()) {
1954                    Element x = s.addElement("exam").addAttribute("id",
1955                            getId(anonymize, "exam", String.valueOf(ex.getId())));
1956                    if (!anonymize)
1957                        for (ExamOwner owner : ex.getOwners(student)) {
1958                            x.addElement("owner").addAttribute("id",
1959                                    getId(anonymize, "owner", String.valueOf(owner.getId())));
1960                        }
1961                }
1962                for (ExamPeriod period : getPeriods()) {
1963                    if (!student.isAvailable(period))
1964                        s.addElement("period").addAttribute("id",
1965                                getId(anonymize, "period", String.valueOf(period.getId()))).addAttribute("available",
1966                                "false");
1967                }
1968            }
1969            Element instructors = root.addElement("instructors");
1970            for (ExamInstructor instructor : getInstructors()) {
1971                Element i = instructors.addElement("instructor");
1972                i.addAttribute("id", getId(anonymize, "instructor", String.valueOf(instructor.getId())));
1973                if (!anonymize && instructor.hasName())
1974                    i.addAttribute("name", instructor.getName());
1975                for (Exam ex : instructor.variables()) {
1976                    Element x = i.addElement("exam").addAttribute("id",
1977                            getId(anonymize, "exam", String.valueOf(ex.getId())));
1978                    if (!anonymize)
1979                        for (ExamOwner owner : ex.getOwners(instructor)) {
1980                            x.addElement("owner").addAttribute("id",
1981                                    getId(anonymize, "owner", String.valueOf(owner.getId())));
1982                        }
1983                }
1984                for (ExamPeriod period : getPeriods()) {
1985                    if (!instructor.isAvailable(period))
1986                        i.addElement("period").addAttribute("id",
1987                                getId(anonymize, "period", String.valueOf(period.getId()))).addAttribute("available",
1988                                "false");
1989                }
1990            }
1991            Element distConstraints = root.addElement("constraints");
1992            for (ExamDistributionConstraint distConstraint : getDistributionConstraints()) {
1993                Element dc = distConstraints.addElement(distConstraint.getTypeString());
1994                dc.addAttribute("id", getId(anonymize, "constraint", String.valueOf(distConstraint.getId())));
1995                if (!distConstraint.isHard()) {
1996                    dc.addAttribute("hard", "false");
1997                    dc.addAttribute("weight", String.valueOf(distConstraint.getWeight()));
1998                }
1999                for (Exam exam : distConstraint.variables()) {
2000                    dc.addElement("exam").addAttribute("id", getId(anonymize, "exam", String.valueOf(exam.getId())));
2001                }
2002            }
2003            if (saveConflictTable) {
2004                Element conflicts = root.addElement("conflicts");
2005                for (ExamStudent student : getStudents()) {
2006                    for (ExamPeriod period : getPeriods()) {
2007                        int nrExams = student.getExams(period).size();
2008                        if (nrExams > 1) {
2009                            Element dir = conflicts.addElement("direct").addAttribute("student",
2010                                    getId(anonymize, "student", String.valueOf(student.getId())));
2011                            for (Exam exam : student.getExams(period)) {
2012                                dir.addElement("exam").addAttribute("id",
2013                                        getId(anonymize, "exam", String.valueOf(exam.getId())));
2014                            }
2015                        }
2016                        if (nrExams > 0) {
2017                            if (period.next() != null && !student.getExams(period.next()).isEmpty()
2018                                    && (!isDayBreakBackToBack() || period.next().getDay() == period.getDay())) {
2019                                for (Exam ex1 : student.getExams(period)) {
2020                                    for (Exam ex2 : student.getExams(period.next())) {
2021                                        Element btb = conflicts.addElement("back-to-back").addAttribute("student",
2022                                                getId(anonymize, "student", String.valueOf(student.getId())));
2023                                        btb.addElement("exam").addAttribute("id",
2024                                                getId(anonymize, "exam", String.valueOf(ex1.getId())));
2025                                        btb.addElement("exam").addAttribute("id",
2026                                                getId(anonymize, "exam", String.valueOf(ex2.getId())));
2027                                        if (getBackToBackDistance() >= 0) {
2028                                            double dist = (ex1.getAssignment()).getDistanceInMeters(ex2.getAssignment());
2029                                            if (dist > 0)
2030                                                btb.addAttribute("distance", String.valueOf(dist));
2031                                        }
2032                                    }
2033                                }
2034                            }
2035                        }
2036                        if (period.next() == null || period.next().getDay() != period.getDay()) {
2037                            int nrExamsADay = student.getExamsADay(period.getDay()).size();
2038                            if (nrExamsADay > 2) {
2039                                Element mt2 = conflicts.addElement("more-2-day").addAttribute("student",
2040                                        getId(anonymize, "student", String.valueOf(student.getId())));
2041                                for (Exam exam : student.getExamsADay(period.getDay())) {
2042                                    mt2.addElement("exam").addAttribute("id",
2043                                            getId(anonymize, "exam", String.valueOf(exam.getId())));
2044                                }
2045                            }
2046                        }
2047                    }
2048                }
2049    
2050            }
2051            return document;
2052        }
2053    
2054        /**
2055         * Load model (including its solution) from XML.
2056         */
2057        public boolean load(Document document) {
2058            return load(document, null);
2059        }
2060    
2061        /**
2062         * Load model (including its solution) from XML.
2063         */
2064        public boolean load(Document document, Callback saveBest) {
2065            boolean loadInitial = getProperties().getPropertyBoolean("Xml.LoadInitial", true);
2066            boolean loadBest = getProperties().getPropertyBoolean("Xml.LoadBest", true);
2067            boolean loadSolution = getProperties().getPropertyBoolean("Xml.LoadSolution", true);
2068            boolean loadParams = getProperties().getPropertyBoolean("Xml.LoadParameters", false);
2069            Element root = document.getRootElement();
2070            if (!"examtt".equals(root.getName()))
2071                return false;
2072            if (root.attribute("campus") != null)
2073                getProperties().setProperty("Data.Initiative", root.attributeValue("campus"));
2074            else if (root.attribute("initiative") != null)
2075                getProperties().setProperty("Data.Initiative", root.attributeValue("initiative"));
2076            if (root.attribute("term") != null)
2077                getProperties().setProperty("Data.Term", root.attributeValue("term"));
2078            if (root.attribute("year") != null)
2079                getProperties().setProperty("Data.Year", root.attributeValue("year"));
2080            if (loadParams && root.element("parameters") != null)
2081                for (Iterator<?> i = root.element("parameters").elementIterator("property"); i.hasNext();) {
2082                    Element e = (Element) i.next();
2083                    String name = e.attributeValue("name");
2084                    String value = e.attributeValue("value");
2085                    if ("isDayBreakBackToBack".equals(name))
2086                        setDayBreakBackToBack("true".equals(value));
2087                    else if ("directConflictWeight".equals(name))
2088                        setDirectConflictWeight(Double.parseDouble(value));
2089                    else if ("moreThanTwoADayWeight".equals(name))
2090                        setMoreThanTwoADayWeight(Double.parseDouble(value));
2091                    else if ("backToBackConflictWeight".equals(name))
2092                        setBackToBackConflictWeight(Double.parseDouble(value));
2093                    else if ("distanceBackToBackConflictWeight".equals(name))
2094                        setDistanceBackToBackConflictWeight(Double.parseDouble(value));
2095                    else if ("backToBackDistance".equals(name))
2096                        setBackToBackDistance(Double.parseDouble(value));
2097                    else if ("maxRooms".equals(name))
2098                        setMaxRooms(Integer.parseInt(value));
2099                    else if ("periodWeight".equals(name))
2100                        setPeriodWeight(Double.parseDouble(value));
2101                    else if ("periodSizeWeight".equals(name))
2102                        setPeriodSizeWeight(Double.parseDouble(value));
2103                    else if ("periodIndexWeight".equals(name))
2104                        setPeriodIndexWeight(Double.parseDouble(value));
2105                    else if ("examRotationWeight".equals(name))
2106                        setExamRotationWeight(Double.parseDouble(value));
2107                    else if ("roomSizeWeight".equals(name))
2108                        setRoomSizeWeight(Double.parseDouble(value));
2109                    else if ("roomSplitWeight".equals(name))
2110                        setRoomSplitWeight(Double.parseDouble(value));
2111                    else if ("roomWeight".equals(name))
2112                        setRoomWeight(Double.parseDouble(value));
2113                    else if ("distributionWeight".equals(name))
2114                        setDistributionWeight(Double.parseDouble(value));
2115                    else if ("instructorDirectConflictWeight".equals(name))
2116                        setInstructorDirectConflictWeight(Double.parseDouble(value));
2117                    else if ("instructorMoreThanTwoADayWeight".equals(name))
2118                        setInstructorMoreThanTwoADayWeight(Double.parseDouble(value));
2119                    else if ("instructorBackToBackConflictWeight".equals(name))
2120                        setInstructorBackToBackConflictWeight(Double.parseDouble(value));
2121                    else if ("instructorDistanceBackToBackConflictWeight".equals(name))
2122                        setInstructorDistanceBackToBackConflictWeight(Double.parseDouble(value));
2123                    else if ("perturbationWeight".equals(name))
2124                        setPerturbationWeight(Double.parseDouble(value));
2125                    else if ("roomPerturbationWeight".equals(name))
2126                        setRoomPerturbationWeight(Double.parseDouble(value));
2127                    else if ("roomSplitDistanceWeight".equals(name))
2128                        setRoomSplitDistanceWeight(Double.parseDouble(value));
2129                    else if ("largeSize".equals(name))
2130                        setLargeSize(Integer.parseInt(value));
2131                    else if ("largePeriod".equals(name))
2132                        setLargePeriod(Double.parseDouble(value));
2133                    else if ("largeWeight".equals(name))
2134                        setLargeWeight(Double.parseDouble(value));
2135                    else
2136                        getProperties().setProperty(name, value);
2137                }
2138            for (Iterator<?> i = root.element("periods").elementIterator("period"); i.hasNext();) {
2139                Element e = (Element) i.next();
2140                addPeriod(Long.valueOf(e.attributeValue("id")), e.attributeValue("day"), e.attributeValue("time"), Integer
2141                        .parseInt(e.attributeValue("length")), Integer.parseInt(e.attributeValue("penalty") == null ? e
2142                        .attributeValue("weight", "0") : e.attributeValue("penalty")));
2143            }
2144            HashMap<Long, ExamRoom> rooms = new HashMap<Long, ExamRoom>();
2145            HashMap<String, ArrayList<ExamRoom>> roomGroups = new HashMap<String, ArrayList<ExamRoom>>();
2146            for (Iterator<?> i = root.element("rooms").elementIterator("room"); i.hasNext();) {
2147                Element e = (Element) i.next();
2148                String coords = e.attributeValue("coordinates");
2149                ExamRoom room = new ExamRoom(this, Long.parseLong(e.attributeValue("id")), e.attributeValue("name"),
2150                        Integer.parseInt(e.attributeValue("size")), Integer.parseInt(e.attributeValue("alt")),
2151                        (coords == null ? null : Double.valueOf(coords.substring(0, coords.indexOf(',')))),
2152                        (coords == null ? null : Double.valueOf(coords.substring(coords.indexOf(',') + 1))));
2153                addConstraint(room);
2154                getRooms().add(room);
2155                rooms.put(new Long(room.getId()), room);
2156                for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) {
2157                    Element pe = (Element) j.next();
2158                    ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id")));
2159                    if ("false".equals(pe.attributeValue("available")))
2160                        room.setAvailable(period, false);
2161                    else
2162                        room.setPenalty(period, Integer.parseInt(pe.attributeValue("penalty")));
2163                }
2164                String av = e.attributeValue("available");
2165                if (av != null) {
2166                    for (int j = 0; j < getPeriods().size(); j++)
2167                        if ('0' == av.charAt(j))
2168                            room.setAvailable(getPeriods().get(j), false);
2169                }
2170                String g = e.attributeValue("groups");
2171                if (g != null) {
2172                    for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) {
2173                        String gr = s.nextToken();
2174                        ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr);
2175                        if (roomsThisGrop == null) {
2176                            roomsThisGrop = new ArrayList<ExamRoom>();
2177                            roomGroups.put(gr, roomsThisGrop);
2178                        }
2179                        roomsThisGrop.add(room);
2180                    }
2181                }
2182                for (Iterator<?> j = e.elementIterator("travel-time"); j.hasNext();) {
2183                    Element travelTimeEl = (Element)j.next();
2184                    getDistanceMetric().addTravelTime(room.getId(),
2185                            Long.valueOf(travelTimeEl.attributeValue("id")),
2186                            Integer.valueOf(travelTimeEl.attributeValue("minutes")));
2187                }
2188            }
2189            ArrayList<ExamPlacement> assignments = new ArrayList<ExamPlacement>();
2190            HashMap<Long, Exam> exams = new HashMap<Long, Exam>();
2191            HashMap<Long, ExamOwner> courseSections = new HashMap<Long, ExamOwner>();
2192            for (Iterator<?> i = root.element("exams").elementIterator("exam"); i.hasNext();) {
2193                Element e = (Element) i.next();
2194                ArrayList<ExamPeriodPlacement> periodPlacements = new ArrayList<ExamPeriodPlacement>();
2195                for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) {
2196                    Element pe = (Element) j.next();
2197                    periodPlacements.add(new ExamPeriodPlacement(getPeriod(Long.valueOf(pe.attributeValue("id"))), Integer
2198                            .parseInt(pe.attributeValue("penalty", "0"))));
2199                }
2200                ArrayList<ExamRoomPlacement> roomPlacements = new ArrayList<ExamRoomPlacement>();
2201                for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) {
2202                    Element re = (Element) j.next();
2203                    ExamRoomPlacement room = new ExamRoomPlacement(rooms.get(Long.valueOf(re.attributeValue("id"))),
2204                            Integer.parseInt(re.attributeValue("penalty", "0")), Integer.parseInt(re.attributeValue(
2205                                    "maxPenalty", "100")));
2206                    roomPlacements.add(room);
2207                }
2208                String g = e.attributeValue("groups");
2209                if (g != null) {
2210                    HashMap<ExamRoom, Integer> allRooms = new HashMap<ExamRoom, Integer>();
2211                    for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) {
2212                        String gr = s.nextToken();
2213                        ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr);
2214                        if (roomsThisGrop != null)
2215                            for (ExamRoom r : roomsThisGrop)
2216                                allRooms.put(r, 0);
2217                    }
2218                    for (Iterator<?> j = e.elementIterator("original-room"); j.hasNext();) {
2219                        allRooms.put((rooms.get(Long.valueOf(((Element) j.next()).attributeValue("id")))), new Integer(-1));
2220                    }
2221                    for (Map.Entry<ExamRoom, Integer> entry : allRooms.entrySet()) {
2222                        ExamRoomPlacement room = new ExamRoomPlacement(entry.getKey(), entry.getValue(), 100);
2223                        roomPlacements.add(room);
2224                    }
2225                    if (periodPlacements.isEmpty()) {
2226                        for (ExamPeriod p : getPeriods()) {
2227                            periodPlacements.add(new ExamPeriodPlacement(p, 0));
2228                        }
2229                    }
2230                }
2231                Exam exam = new Exam(Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), Integer.parseInt(e
2232                        .attributeValue("length")), "true".equals(e.attributeValue("alt")),
2233                        (e.attribute("maxRooms") == null ? getMaxRooms() : Integer.parseInt(e.attributeValue("maxRooms"))),
2234                        Integer.parseInt(e.attributeValue("minSize", "0")), periodPlacements, roomPlacements);
2235                if (e.attributeValue("size") != null)
2236                    exam.setSizeOverride(Integer.valueOf(e.attributeValue("size")));
2237                if (e.attributeValue("printOffset") != null)
2238                    exam.setPrintOffset(Integer.valueOf(e.attributeValue("printOffset")));
2239                exams.put(new Long(exam.getId()), exam);
2240                addVariable(exam);
2241                if (e.attribute("average") != null)
2242                    exam.setAveragePeriod(Integer.parseInt(e.attributeValue("average")));
2243                Element asg = e.element("assignment");
2244                if (asg != null && loadSolution) {
2245                    Element per = asg.element("period");
2246                    if (per != null) {
2247                        HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>();
2248                        for (Iterator<?> j = asg.elementIterator("room"); j.hasNext();)
2249                            rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id"))));
2250                        ExamPlacement p = new ExamPlacement(exam, exam.getPeriodPlacement(Long.valueOf(per
2251                                .attributeValue("id"))), rp);
2252                        assignments.add(p);
2253                    }
2254                }
2255                Element ini = e.element("initial");
2256                if (ini != null && loadInitial) {
2257                    Element per = ini.element("period");
2258                    if (per != null) {
2259                        HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>();
2260                        for (Iterator<?> j = ini.elementIterator("room"); j.hasNext();)
2261                            rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id"))));
2262                        ExamPlacement p = new ExamPlacement(exam, exam.getPeriodPlacement(Long.valueOf(per
2263                                .attributeValue("id"))), rp);
2264                        exam.setInitialAssignment(p);
2265                    }
2266                }
2267                Element best = e.element("best");
2268                if (best != null && loadBest) {
2269                    Element per = best.element("period");
2270                    if (per != null) {
2271                        HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>();
2272                        for (Iterator<?> j = best.elementIterator("room"); j.hasNext();)
2273                            rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id"))));
2274                        ExamPlacement p = new ExamPlacement(exam, exam.getPeriodPlacement(Long.valueOf(per
2275                                .attributeValue("id"))), rp);
2276                        exam.setBestAssignment(p);
2277                    }
2278                }
2279                for (Iterator<?> j = e.elementIterator("owner"); j.hasNext();) {
2280                    Element f = (Element) j.next();
2281                    ExamOwner owner = new ExamOwner(exam, Long.parseLong(f.attributeValue("id")), f.attributeValue("name"));
2282                    exam.getOwners().add(owner);
2283                    courseSections.put(new Long(owner.getId()), owner);
2284                }
2285            }
2286            for (Iterator<?> i = root.element("students").elementIterator("student"); i.hasNext();) {
2287                Element e = (Element) i.next();
2288                ExamStudent student = new ExamStudent(this, Long.parseLong(e.attributeValue("id")));
2289                for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) {
2290                    Element x = (Element) j.next();
2291                    Exam ex = exams.get(Long.valueOf(x.attributeValue("id")));
2292                    student.addVariable(ex);
2293                    for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) {
2294                        Element f = (Element) k.next();
2295                        ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id")));
2296                        student.getOwners().add(owner);
2297                        owner.getStudents().add(student);
2298                    }
2299                }
2300                String available = e.attributeValue("available");
2301                if (available != null)
2302                    for (ExamPeriod period : getPeriods()) {
2303                        if (available.charAt(period.getIndex()) == '0')
2304                            student.setAvailable(period.getIndex(), false);
2305                    }
2306                for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) {
2307                    Element pe = (Element) j.next();
2308                    ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id")));
2309                    if ("false".equals(pe.attributeValue("available")))
2310                        student.setAvailable(period.getIndex(), false);
2311                }
2312                addConstraint(student);
2313                getStudents().add(student);
2314            }
2315            if (root.element("instructors") != null)
2316                for (Iterator<?> i = root.element("instructors").elementIterator("instructor"); i.hasNext();) {
2317                    Element e = (Element) i.next();
2318                    ExamInstructor instructor = new ExamInstructor(this, Long.parseLong(e.attributeValue("id")), e
2319                            .attributeValue("name"));
2320                    for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) {
2321                        Element x = (Element) j.next();
2322                        Exam ex = exams.get(Long.valueOf(x.attributeValue("id")));
2323                        instructor.addVariable(ex);
2324                        for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) {
2325                            Element f = (Element) k.next();
2326                            ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id")));
2327                            instructor.getOwners().add(owner);
2328                            owner.getIntructors().add(instructor);
2329                        }
2330                    }
2331                    String available = e.attributeValue("available");
2332                    if (available != null)
2333                        for (ExamPeriod period : getPeriods()) {
2334                            if (available.charAt(period.getIndex()) == '0')
2335                                instructor.setAvailable(period.getIndex(), false);
2336                        }
2337                    for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) {
2338                        Element pe = (Element) j.next();
2339                        ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id")));
2340                        if ("false".equals(pe.attributeValue("available")))
2341                            instructor.setAvailable(period.getIndex(), false);
2342                    }
2343                    addConstraint(instructor);
2344                    getInstructors().add(instructor);
2345                }
2346            if (root.element("constraints") != null)
2347                for (Iterator<?> i = root.element("constraints").elementIterator(); i.hasNext();) {
2348                    Element e = (Element) i.next();
2349                    ExamDistributionConstraint dc = new ExamDistributionConstraint(Long.parseLong(e.attributeValue("id")),
2350                            e.getName(), "true".equals(e.attributeValue("hard", "true")), Integer.parseInt(e
2351                                    .attributeValue("weight", "0")));
2352                    for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) {
2353                        dc.addVariable(exams.get(Long.valueOf(((Element) j.next()).attributeValue("id"))));
2354                    }
2355                    addConstraint(dc);
2356                    getDistributionConstraints().add(dc);
2357                }
2358            init();
2359            if (loadBest && saveBest != null) {
2360                for (Exam exam : variables()) {
2361                    ExamPlacement placement = exam.getBestAssignment();
2362                    if (placement == null)
2363                        continue;
2364                    exam.assign(0, placement);
2365                }
2366                saveBest.execute();
2367                for (Exam exam : variables()) {
2368                    if (exam.getAssignment() != null)
2369                        exam.unassign(0);
2370                }
2371            }
2372            for (ExamPlacement placement : assignments) {
2373                Exam exam = placement.variable();
2374                Set<ExamPlacement> conf = conflictValues(placement);
2375                if (!conf.isEmpty()) {
2376                    for (Map.Entry<Constraint<Exam, ExamPlacement>, Set<ExamPlacement>> entry : conflictConstraints(
2377                            placement).entrySet()) {
2378                        Constraint<Exam, ExamPlacement> constraint = entry.getKey();
2379                        Set<ExamPlacement> values = entry.getValue();
2380                        if (constraint instanceof ExamStudent) {
2381                            ((ExamStudent) constraint).setAllowDirectConflicts(true);
2382                            exam.setAllowDirectConflicts(true);
2383                            for (ExamPlacement p : values)
2384                                p.variable().setAllowDirectConflicts(true);
2385                        }
2386                    }
2387                    conf = conflictValues(placement);
2388                }
2389                if (conf.isEmpty()) {
2390                    exam.assign(0, placement);
2391                } else {
2392                    sLog.error("Unable to assign " + exam.getInitialAssignment().getName() + " to exam " + exam.getName());
2393                    sLog.error("Conflicts:" + ToolBox.dict2string(conflictConstraints(exam.getInitialAssignment()), 2));
2394                }
2395            }
2396            return true;
2397        }
2398    
2399        public boolean isMPP() {
2400            return iMPP;
2401        }
2402    }