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