001 package net.sf.cpsolver.exam.model;
002
003 import java.text.DecimalFormat;
004 import java.util.Enumeration;
005 import java.util.HashSet;
006 import java.util.Iterator;
007 import java.util.Set;
008
009 import net.sf.cpsolver.ifs.model.Value;
010
011 /**
012 * Representation of an exam placement (problem value), i.e., assignment of an exam to period and room(s).
013 * Each placement has defined a period and a set of rooms. The exam as well as the rooms have to be available
014 * during the given period (see {@link Exam#getPeriodPlacements()} and {@link Exam#getRoomPlacements()}).
015 * The total size of rooms have to be equal or greater than the number of students enrolled in the exam
016 * {@link Exam#getSize()}, using either
017 * {@link ExamRoom#getSize()} or {@link ExamRoom#getAltSize()}, depending on {@link Exam#hasAltSeating()}.
018 * Also, the number of rooms has to be smaller or equal to {@link Exam#getMaxRooms()}. If
019 * {@link Exam#getMaxRooms()} is zero, the exam is only to be assigned to period (the set of rooms is empty).
020 * <br><br>
021 * The cost of an assignment consists of the following criteria:
022 * <ul>
023 * <li> Direct student conflicts {@link ExamPlacement#getNrDirectConflicts()}, weighted by {@link ExamModel#getDirectConflictWeight()}
024 * <li> More than two exams a day student conflicts {@link ExamPlacement#getNrMoreThanTwoADayConflicts()}, weighted by {@link ExamModel#getMoreThanTwoADayWeight()}
025 * <li> Back-to-back student conflicts {@link ExamPlacement#getNrBackToBackConflicts()}, weighted by {@link ExamModel#getBackToBackConflictWeight()}
026 * <li> Distance back-to-back student conflicts {@link ExamPlacement#getNrDistanceBackToBackConflicts()}, weighted by {@link ExamModel#getDistanceBackToBackConflictWeight()}
027 * <li> Period penalty {@link ExamPlacement#getPeriodPenalty()}, weighted by {@link ExamModel#getPeriodWeight()}
028 * <li> Room size penalty {@link ExamPlacement#getRoomSizePenalty()}, weighted by {@link ExamModel#getRoomSizeWeight()}
029 * <li> Room split penalty {@link ExamPlacement#getRoomSplitPenalty()}, weighted by {@link ExamModel#getRoomSplitWeight()}
030 * <li> Room penalty {@link ExamPlacement#getRoomPenalty()}, weighted by {@link ExamModel#getRoomWeight()}
031 * <li> Exam rotation penalty {@link ExamPlacement#getRotationPenalty()}, weighted by {@link ExamModel#getExamRotationWeight()}
032 * <li> Direct instructor conflicts {@link ExamPlacement#getNrInstructorDirectConflicts()}, weighted by {@link ExamModel#getInstructorDirectConflictWeight()}
033 * <li> More than two exams a day instructor conflicts {@link ExamPlacement#getNrInstructorMoreThanTwoADayConflicts()}, weighted by {@link ExamModel#getInstructorMoreThanTwoADayWeight()}
034 * <li> Back-to-back instructor conflicts {@link ExamPlacement#getNrInstructorBackToBackConflicts()}, weighted by {@link ExamModel#getInstructorBackToBackConflictWeight()}
035 * <li> Distance back-to-back instructor conflicts {@link ExamPlacement#getNrInstructorDistanceBackToBackConflicts()}, weighted by {@link ExamModel#getInstructorDistanceBackToBackConflictWeight()}
036 * </ul>
037 * <br><br>
038 *
039 * @version
040 * ExamTT 1.1 (Examination Timetabling)<br>
041 * Copyright (C) 2008 Tomáš Müller<br>
042 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
043 * Lazenska 391, 76314 Zlin, Czech Republic<br>
044 * <br>
045 * This library is free software; you can redistribute it and/or
046 * modify it under the terms of the GNU Lesser General Public
047 * License as published by the Free Software Foundation; either
048 * version 2.1 of the License, or (at your option) any later version.
049 * <br><br>
050 * This library is distributed in the hope that it will be useful,
051 * but WITHOUT ANY WARRANTY; without even the implied warranty of
052 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
053 * Lesser General Public License for more details.
054 * <br><br>
055 * You should have received a copy of the GNU Lesser General Public
056 * License along with this library; if not, write to the Free Software
057 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
058 */
059 public class ExamPlacement extends Value {
060 private ExamPeriodPlacement iPeriodPlacement;
061 private Set iRoomPlacements;
062 private int iSize;
063 private int iRoomPenalty;
064 private double iRoomSplitDistance;
065
066 private int iHashCode;
067
068 /**
069 * Constructor
070 * @param exam an exam
071 * @param periodPlacement period placement
072 * @param roomPlacements a set of room placements {@link ExamRoomPlacement}
073 */
074 public ExamPlacement(Exam exam, ExamPeriodPlacement periodPlacement, Set roomPlacements) {
075 super(exam);
076 iPeriodPlacement = periodPlacement;
077 if (roomPlacements==null)
078 iRoomPlacements = new HashSet();
079 else
080 iRoomPlacements = roomPlacements;
081 iSize = 0;
082 iRoomPenalty = 0;
083 iRoomSplitDistance = 0.0;
084 for (Iterator i=iRoomPlacements.iterator();i.hasNext();) {
085 ExamRoomPlacement r = (ExamRoomPlacement)i.next();
086 iSize += r.getSize(exam.hasAltSeating());
087 iRoomPenalty += r.getPenalty(periodPlacement.getPeriod());
088 if (iRoomPlacements.size()>1) {
089 for (Iterator j=iRoomPlacements.iterator();j.hasNext();) {
090 ExamRoomPlacement w = (ExamRoomPlacement)j.next();
091 if (r.getRoom().getId()<w.getRoom().getId())
092 iRoomSplitDistance += r.getRoom().getDistance(w.getRoom());
093 }
094 }
095 }
096 if (iRoomPlacements.size()>2) {
097 iRoomSplitDistance /= iRoomPlacements.size()*(iRoomPlacements.size()-1)/2;
098 }
099 iHashCode = getName().hashCode();
100 }
101
102 /**
103 * Assigned period
104 */
105 public ExamPeriod getPeriod() { return iPeriodPlacement.getPeriod(); }
106
107 /**
108 * Assigned period placement
109 */
110 public ExamPeriodPlacement getPeriodPlacement() { return iPeriodPlacement; }
111
112 /**
113 * Assigned rooms (it is empty when {@link Exam#getMaxRooms()} is zero)
114 * @return list of {@link ExamRoomPlacement}
115 */
116 public Set getRoomPlacements() { return iRoomPlacements; }
117
118 /**
119 * Overall size of assigned rooms
120 */
121 public int getSize() { return iSize; }
122
123 /**
124 * Number of direct student conflicts, i.e., number of cases when this exam
125 * is attended by a student that attends some other exam at the same period
126 */
127 public int getNrDirectConflicts() {
128 Exam exam = (Exam)variable();
129 //if (!exam.isAllowDirectConflicts()) return 0;
130 int penalty = 0;
131 for (Enumeration e=exam.getStudents().elements();e.hasMoreElements();) {
132 ExamStudent s = (ExamStudent)e.nextElement();
133 Set exams = s.getExams(getPeriod());
134 int nrExams = exams.size() + (exams.contains(exam)?0:1);
135 if (nrExams>1) penalty++;
136 else if (!s.isAvailable(getPeriod())) penalty++;
137 }
138 return penalty;
139 }
140
141 /**
142 * Number of direct student conflicts caused by the fact that a student is not available
143 */
144 public int getNrNotAvailableConflicts() {
145 Exam exam = (Exam)variable();
146 int penalty = 0;
147 for (Enumeration e=exam.getStudents().elements();e.hasMoreElements();) {
148 ExamStudent s = (ExamStudent)e.nextElement();
149 if (!s.isAvailable(getPeriod())) penalty++;
150 }
151 return penalty;
152 }
153
154 /**
155 * Number of back-to-back student conflicts, i.e., number of cases when this exam
156 * is attended by a student that attends some other exam at the previous {@link ExamPeriod#prev()}
157 * or following {@link ExamPeriod#next()} period. If {@link ExamModel#isDayBreakBackToBack()} is false,
158 * back-to-back conflicts are only considered between consecutive periods that are of the
159 * same day.
160 */
161 public int getNrBackToBackConflicts() {
162 Exam exam = (Exam)variable();
163 ExamModel model = (ExamModel)exam.getModel();
164 int penalty = 0;
165 for (Enumeration e=exam.getStudents().elements();e.hasMoreElements();) {
166 ExamStudent s = (ExamStudent)e.nextElement();
167 if (getPeriod().prev()!=null) {
168 if (model.isDayBreakBackToBack() || getPeriod().prev().getDay()==getPeriod().getDay()) {
169 Set exams = s.getExams(getPeriod().prev());
170 int nrExams = exams.size() + (exams.contains(exam)?-1:0);
171 penalty += nrExams;
172 }
173 }
174 if (getPeriod().next()!=null) {
175 if (model.isDayBreakBackToBack() || getPeriod().next().getDay()==getPeriod().getDay()) {
176 Set exams = s.getExams(getPeriod().next());
177 int nrExams = exams.size() + (exams.contains(exam)?-1:0);
178 penalty += nrExams;
179 }
180 }
181 }
182 return penalty;
183 }
184
185 /**
186 * Distance between two placements, i.e., maximal distance between a room
187 * of this placement and a room of the given placement.
188 * Method {@link ExamRoom#getDistance(ExamRoom)} is used to get a distance between two rooms.
189 */
190 public int getDistance(ExamPlacement other) {
191 if (getRoomPlacements().isEmpty() || other.getRoomPlacements().isEmpty()) return 0;
192 int maxDistance = 0;
193 for (Iterator i1=getRoomPlacements().iterator();i1.hasNext();) {
194 ExamRoomPlacement r1 = (ExamRoomPlacement)i1.next();
195 for (Iterator i2=other.getRoomPlacements().iterator();i2.hasNext();) {
196 ExamRoomPlacement r2 = (ExamRoomPlacement)i2.next();
197 maxDistance = Math.max(maxDistance, r1.getDistance(r2));
198 }
199 }
200 return maxDistance;
201 }
202
203 /**
204 * Number of back-to-back distance student conflicts, i.e., number of cases when this exam
205 * is attended by a student that attends some other exam at the previous {@link ExamPeriod#prev()}
206 * or following {@link ExamPeriod#next()} period and the distance {@link ExamPlacement#getDistance(ExamPlacement)}
207 * between these two exams is greater than {@link ExamModel#getBackToBackDistance()}.
208 * Distance back-to-back conflicts are only
209 * considered between consecutive periods that are of the
210 * same day.
211 */
212 public int getNrDistanceBackToBackConflicts() {
213 Exam exam = (Exam)variable();
214 ExamModel model = (ExamModel)exam.getModel();
215 int btbDist = model.getBackToBackDistance();
216 if (btbDist<0) return 0;
217 int penalty = 0;
218 for (Enumeration e=exam.getStudents().elements();e.hasMoreElements();) {
219 ExamStudent s = (ExamStudent)e.nextElement();
220 if (getPeriod().prev()!=null) {
221 if (getPeriod().prev().getDay()==getPeriod().getDay()) {
222 for (Iterator i=s.getExams(getPeriod().prev()).iterator();i.hasNext();) {
223 Exam x = (Exam)i.next();
224 if (x.equals(exam)) continue;
225 if (getDistance((ExamPlacement)x.getAssignment())>btbDist) penalty++;
226 }
227 }
228 }
229 if (getPeriod().next()!=null) {
230 if (getPeriod().next().getDay()==getPeriod().getDay()) {
231 for (Iterator i=s.getExams(getPeriod().next()).iterator();i.hasNext();) {
232 Exam x = (Exam)i.next();
233 if (x.equals(exam)) continue;
234 if (getDistance((ExamPlacement)x.getAssignment())>btbDist) penalty++;
235 }
236 }
237 }
238 }
239 return penalty;
240 }
241
242 /**
243 * Number of more than two exams a day student conflicts, i.e., when this exam
244 * is attended by a student that attends two or more other exams at the same day.
245 */
246 public int getNrMoreThanTwoADayConflicts() {
247 Exam exam = (Exam)variable();
248 int penalty = 0;
249 for (Enumeration e=exam.getStudents().elements();e.hasMoreElements();) {
250 ExamStudent s = (ExamStudent)e.nextElement();
251 Set exams = s.getExamsADay(getPeriod());
252 int nrExams = exams.size() + (exams.contains(exam)?0:1);
253 if (nrExams>2) penalty++;
254 }
255 return penalty;
256 }
257
258 /**
259 * Number of direct instructor conflicts, i.e., number of cases when this exam
260 * is attended by an instructor that attends some other exam at the same period
261 */
262 public int getNrInstructorDirectConflicts() {
263 Exam exam = (Exam)variable();
264 //if (!exam.isAllowDirectConflicts()) return 0;
265 int penalty = 0;
266 for (Enumeration e=exam.getInstructors().elements();e.hasMoreElements();) {
267 ExamInstructor s = (ExamInstructor)e.nextElement();
268 Set exams = s.getExams(getPeriod());
269 int nrExams = exams.size() + (exams.contains(exam)?0:1);
270 if (nrExams>1) penalty++;
271 else if (!s.isAvailable(getPeriod())) penalty++;
272 }
273 return penalty;
274 }
275
276 /**
277 * Number of direct instructor conflicts caused by the fact that a student is not available
278 */
279 public int getNrInstructorNotAvailableConflicts() {
280 Exam exam = (Exam)variable();
281 int penalty = 0;
282 for (Enumeration e=exam.getInstructors().elements();e.hasMoreElements();) {
283 ExamInstructor s = (ExamInstructor)e.nextElement();
284 if (!s.isAvailable(getPeriod())) penalty++;
285 }
286 return penalty;
287 }
288
289
290 /**
291 * Number of back-to-back instructor conflicts, i.e., number of cases when this exam
292 * is attended by an instructor that attends some other exam at the previous {@link ExamPeriod#prev()}
293 * or following {@link ExamPeriod#next()} period. If {@link ExamModel#isDayBreakBackToBack()} is false,
294 * back-to-back conflicts are only considered between consecutive periods that are of the
295 * same day.
296 */
297 public int getNrInstructorBackToBackConflicts() {
298 Exam exam = (Exam)variable();
299 ExamModel model = (ExamModel)exam.getModel();
300 int penalty = 0;
301 for (Enumeration e=exam.getInstructors().elements();e.hasMoreElements();) {
302 ExamInstructor s = (ExamInstructor)e.nextElement();
303 if (getPeriod().prev()!=null) {
304 if (model.isDayBreakBackToBack() || getPeriod().prev().getDay()==getPeriod().getDay()) {
305 Set exams = s.getExams(getPeriod().prev());
306 int nrExams = exams.size() + (exams.contains(exam)?-1:0);
307 penalty += nrExams;
308 }
309 }
310 if (getPeriod().next()!=null) {
311 if (model.isDayBreakBackToBack() || getPeriod().next().getDay()==getPeriod().getDay()) {
312 Set exams = s.getExams(getPeriod().next());
313 int nrExams = exams.size() + (exams.contains(exam)?-1:0);
314 penalty += nrExams;
315 }
316 }
317 }
318 return penalty;
319 }
320
321 /**
322 * Number of back-to-back distance instructor conflicts, i.e., number of cases when this exam
323 * is attended by an instructor that attends some other exam at the previous {@link ExamPeriod#prev()}
324 * or following {@link ExamPeriod#next()} period and the distance {@link ExamPlacement#getDistance(ExamPlacement)}
325 * between these two exams is greater than {@link ExamModel#getBackToBackDistance()}.
326 * Distance back-to-back conflicts are only
327 * considered between consecutive periods that are of the
328 * same day.
329 */
330 public int getNrInstructorDistanceBackToBackConflicts() {
331 Exam exam = (Exam)variable();
332 ExamModel model = (ExamModel)exam.getModel();
333 int btbDist = model.getBackToBackDistance();
334 if (btbDist<0) return 0;
335 int penalty = 0;
336 for (Enumeration e=exam.getInstructors().elements();e.hasMoreElements();) {
337 ExamInstructor s = (ExamInstructor)e.nextElement();
338 if (getPeriod().prev()!=null) {
339 if (getPeriod().prev().getDay()==getPeriod().getDay()) {
340 for (Iterator i=s.getExams(getPeriod().prev()).iterator();i.hasNext();) {
341 Exam x = (Exam)i.next();
342 if (x.equals(exam)) continue;
343 if (getDistance((ExamPlacement)x.getAssignment())>btbDist) penalty++;
344 }
345 }
346 }
347 if (getPeriod().next()!=null) {
348 if (getPeriod().next().getDay()==getPeriod().getDay()) {
349 for (Iterator i=s.getExams(getPeriod().next()).iterator();i.hasNext();) {
350 Exam x = (Exam)i.next();
351 if (x.equals(exam)) continue;
352 if (getDistance((ExamPlacement)x.getAssignment())>btbDist) penalty++;
353 }
354 }
355 }
356 }
357 return penalty;
358 }
359
360 /**
361 * Number of more than two exams a day instructor conflicts, i.e., when this exam
362 * is attended by an instructor student that attends two or more other exams at the same day.
363 */
364 public int getNrInstructorMoreThanTwoADayConflicts() {
365 Exam exam = (Exam)variable();
366 int penalty = 0;
367 for (Enumeration e=exam.getInstructors().elements();e.hasMoreElements();) {
368 ExamInstructor s = (ExamInstructor)e.nextElement();
369 Set exams = s.getExamsADay(getPeriod());
370 int nrExams = exams.size() + (exams.contains(exam)?0:1);
371 if (nrExams>2) penalty++;
372 }
373 return penalty;
374 }
375
376 /**
377 * Cost for using room(s) that are too big
378 * @return difference between total room size (computed using either {@link ExamRoom#getSize()} or
379 * {@link ExamRoom#getAltSize()} based on {@link Exam#hasAltSeating()}) and the number of students {@link Exam#getSize()}
380 */
381 public int getRoomSizePenalty() {
382 Exam exam = (Exam)variable();
383 int diff = getSize()-exam.getSize();
384 return (diff<0?0:diff);
385 }
386
387 /**
388 * Cost for using more than one room (nrSplits^2).
389 * @return penalty (1 for 2 rooms, 4 for 3 rooms, 9 for 4 rooms, etc.)
390 */
391 public int getRoomSplitPenalty() {
392 return (iRoomPlacements.size()<=1?0:(iRoomPlacements.size()-1)*(iRoomPlacements.size()-1));
393 }
394
395 /**
396 * Cost for using a period, i.e., {@link ExamPeriodPlacement#getPenalty()}
397 */
398 public int getPeriodPenalty() {
399 return iPeriodPlacement.getPenalty();
400 }
401
402
403 /**
404 * Rotation penalty (an exam that has been in later period last times tries to be in an earlier period)
405 */
406 public int getRotationPenalty() {
407 Exam exam = (Exam)variable();
408 if (exam.getAveragePeriod()<0) return 0;
409 return (1+getPeriod().getIndex())*(1+exam.getAveragePeriod());
410 }
411
412 /**
413 * Front load penalty (large exam is discouraged to be placed on or after a certain period)
414 * @return zero if not large exam or if before {@link ExamModel#getLargePeriod()}, one otherwise
415 */
416 public int getLargePenalty() {
417 Exam exam = (Exam)variable();
418 ExamModel model = (ExamModel)exam.getModel();
419 if (model.getLargeSize()<0 || exam.getSize()<model.getLargeSize()) return 0;
420 int periodIdx = (int)Math.round(model.getPeriods().size() * model.getLargePeriod());
421 return (getPeriod().getIndex()<periodIdx?0:1);
422 }
423
424 /**
425 * Room penalty (penalty for using given rooms), i.e., sum of {@link ExamRoomPlacement#getPenalty(ExamPeriod)} of assigned rooms
426 */
427 public int getRoomPenalty() {
428 return iRoomPenalty;
429 }
430
431 /**
432 * Perturbation penalty, i.e., penalty for using a different assignment than initial.
433 * Only applicable when {@link ExamModel#isMPP()} is true (minimal perturbation problem).
434 * @return |period index - initial period index | * exam size
435 */
436 public int getPerturbationPenalty() {
437 Exam exam = (Exam)variable();
438 if (!((ExamModel)exam.getModel()).isMPP()) return 0;
439 ExamPlacement initial = (ExamPlacement)exam.getInitialAssignment();
440 if (initial==null) return 0;
441 return Math.abs(initial.getPeriod().getIndex()-getPeriod().getIndex())*(1+exam.getSize());
442 }
443
444 /**
445 * Room perturbation penalty, i.e., number of assigned rooms different from initial.
446 * Only applicable when {@link ExamModel#isMPP()} is true (minimal perturbation problem).
447 * @return |period index - initial period index | * exam size
448 */
449 public int getRoomPerturbationPenalty() {
450 Exam exam = (Exam)variable();
451 if (!((ExamModel)exam.getModel()).isMPP()) return 0;
452 ExamPlacement initial = (ExamPlacement)exam.getInitialAssignment();
453 if (initial==null) return 0;
454 int penalty = 0;
455 for (Iterator i=getRoomPlacements().iterator();i.hasNext();) {
456 ExamRoomPlacement rp = (ExamRoomPlacement)i.next();
457 if (!initial.getRoomPlacements().contains(rp)) penalty++;
458 }
459 return penalty;
460 }
461
462 /**
463 * Room split distance penalty, i.e., average distance between two rooms of this placement
464 */
465 public double getRoomSplitDistancePenalty() {
466 return iRoomSplitDistance;
467 }
468
469 /**
470 * Distribution penalty, i.e., sum weights of violated distribution constraints
471 */
472 public double getDistributionPenalty() {
473 int penalty = 0;
474 for (Enumeration e=((Exam)variable()).getDistributionConstraints().elements();e.hasMoreElements();) {
475 ExamDistributionConstraint dc = (ExamDistributionConstraint)e.nextElement();
476 if (dc.isHard()) continue;
477 boolean sat = dc.isSatisfied(this);
478 if (sat!=dc.isSatisfied())
479 penalty += (sat?-dc.getWeight():dc.getWeight());
480 }
481 return penalty;
482 }
483
484 /**
485 * Room related distribution penalty, i.e., sum weights of violated distribution constraints
486 */
487 public double getRoomDistributionPenalty() {
488 int penalty = 0;
489 for (Enumeration e=((Exam)variable()).getDistributionConstraints().elements();e.hasMoreElements();) {
490 ExamDistributionConstraint dc = (ExamDistributionConstraint)e.nextElement();
491 if (dc.isHard() || !dc.isRoomRelated()) continue;
492 boolean sat = dc.isSatisfied(this);
493 if (sat!=dc.isSatisfied())
494 penalty += (sat?-dc.getWeight():dc.getWeight());
495 }
496 return penalty;
497 }
498
499 /**
500 * Period related distribution penalty, i.e., sum weights of violated distribution constraints
501 */
502 public double getPeriodDistributionPenalty() {
503 int penalty = 0;
504 for (Enumeration e=((Exam)variable()).getDistributionConstraints().elements();e.hasMoreElements();) {
505 ExamDistributionConstraint dc = (ExamDistributionConstraint)e.nextElement();
506 if (dc.isHard() || !dc.isPeriodRelated()) continue;
507 boolean sat = dc.isSatisfied(this);
508 if (sat!=dc.isSatisfied())
509 penalty += (sat?-dc.getWeight():dc.getWeight());
510 }
511 return penalty;
512 }
513
514 /**
515 * Overall cost of using this placement.
516 * The cost of an assignment consists of the following criteria:
517 * <ul>
518 * <li> Direct student conflicts {@link ExamPlacement#getNrDirectConflicts()}, weighted by {@link ExamModel#getDirectConflictWeight()}
519 * <li> More than two exams a day student conflicts {@link ExamPlacement#getNrMoreThanTwoADayConflicts()}, weighted by {@link ExamModel#getMoreThanTwoADayWeight()}
520 * <li> Back-to-back student conflicts {@link ExamPlacement#getNrBackToBackConflicts()}, weighted by {@link ExamModel#getBackToBackConflictWeight()}
521 * <li> Distance back-to-back student conflicts {@link ExamPlacement#getNrDistanceBackToBackConflicts()}, weighted by {@link ExamModel#getDistanceBackToBackConflictWeight()}
522 * <li> Period penalty {@link ExamPlacement#getPeriodPenalty()}, weighted by {@link ExamModel#getPeriodWeight()}
523 * <li> Room size penalty {@link ExamPlacement#getRoomSizePenalty()}, weighted by {@link ExamModel#getRoomSizeWeight()}
524 * <li> Room split penalty {@link ExamPlacement#getRoomSplitPenalty()}, weighted by {@link ExamModel#getRoomSplitWeight()}
525 * <li> Room split distance penalty {@link ExamPlacement#getRoomSplitDistancePenalty()}, weighted by {@link ExamModel#getRoomSplitDistanceWeight()}
526 * <li> Room penalty {@link ExamPlacement#getRoomPenalty()}, weighted by {@link ExamModel#getRoomWeight()}
527 * <li> Exam rotation penalty {@link ExamPlacement#getRotationPenalty()}, weighted by {@link ExamModel#getExamRotationWeight()}
528 * <li> Direct instructor conflicts {@link ExamPlacement#getNrInstructorDirectConflicts()}, weighted by {@link ExamModel#getInstructorDirectConflictWeight()}
529 * <li> More than two exams a day instructor conflicts {@link ExamPlacement#getNrInstructorMoreThanTwoADayConflicts()}, weighted by {@link ExamModel#getInstructorMoreThanTwoADayWeight()}
530 * <li> Back-to-back instructor conflicts {@link ExamPlacement#getNrInstructorBackToBackConflicts()}, weighted by {@link ExamModel#getInstructorBackToBackConflictWeight()}
531 * <li> Distance back-to-back instructor conflicts {@link ExamPlacement#getNrInstructorDistanceBackToBackConflicts()}, weighted by {@link ExamModel#getInstructorDistanceBackToBackConflictWeight()}
532 * <li> Front load penalty {@link ExamPlacement#getLargePenalty()}, weighted by {@link ExamModel#getLargeWeight()}
533 * </ul>
534 */
535 public double toDouble() {
536 Exam exam = (Exam)variable();
537 ExamModel model = (ExamModel)exam.getModel();
538 return
539 model.getDirectConflictWeight()*getNrDirectConflicts()+
540 model.getMoreThanTwoADayWeight()*getNrMoreThanTwoADayConflicts()+
541 model.getBackToBackConflictWeight()*getNrBackToBackConflicts()+
542 model.getDistanceBackToBackConflictWeight()*getNrDistanceBackToBackConflicts()+
543 model.getPeriodWeight()*getPeriodPenalty()+
544 model.getPeriodSizeWeight()*getPeriodPenalty()*(exam.getSize()+1)+
545 model.getPeriodIndexWeight()*getPeriod().getIndex()+
546 model.getRoomSizeWeight()*getRoomSizePenalty()+
547 model.getRoomSplitWeight()*getRoomSplitPenalty()+
548 model.getExamRotationWeight()*getRotationPenalty()+
549 model.getRoomWeight()*getRoomPenalty()+
550 model.getInstructorDirectConflictWeight()*getNrInstructorDirectConflicts()+
551 model.getInstructorMoreThanTwoADayWeight()*getNrInstructorMoreThanTwoADayConflicts()+
552 model.getInstructorBackToBackConflictWeight()*getNrInstructorBackToBackConflicts()+
553 model.getInstructorDistanceBackToBackConflictWeight()*getNrInstructorDistanceBackToBackConflicts()+
554 model.getRoomSplitDistanceWeight()*getRoomSplitDistancePenalty()+
555 model.getPerturbationWeight()*getPerturbationPenalty()+
556 model.getRoomPerturbationWeight()*getRoomPerturbationPenalty()+
557 model.getDistributionWeight()*getDistributionPenalty()+
558 model.getLargeWeight()*getLargePenalty();
559 }
560
561 /**
562 * Overall cost of using this period.
563 * The time cost of an assignment consists of the following criteria:
564 * <ul>
565 * <li> Direct student conflicts {@link ExamPlacement#getNrDirectConflicts()}, weighted by {@link ExamModel#getDirectConflictWeight()}
566 * <li> More than two exams a day student conflicts {@link ExamPlacement#getNrMoreThanTwoADayConflicts()}, weighted by {@link ExamModel#getMoreThanTwoADayWeight()}
567 * <li> Back-to-back student conflicts {@link ExamPlacement#getNrBackToBackConflicts()}, weighted by {@link ExamModel#getBackToBackConflictWeight()}
568 * <li> Period penalty {@link ExamPlacement#getPeriodPenalty()}, weighted by {@link ExamModel#getPeriodWeight()}
569 * <li> Exam rotation penalty {@link ExamPlacement#getRotationPenalty()}, weighted by {@link ExamModel#getExamRotationWeight()}
570 * <li> Direct instructor conflicts {@link ExamPlacement#getNrInstructorDirectConflicts()}, weighted by {@link ExamModel#getInstructorDirectConflictWeight()}
571 * <li> More than two exams a day instructor conflicts {@link ExamPlacement#getNrInstructorMoreThanTwoADayConflicts()}, weighted by {@link ExamModel#getInstructorMoreThanTwoADayWeight()}
572 * <li> Back-to-back instructor conflicts {@link ExamPlacement#getNrInstructorBackToBackConflicts()}, weighted by {@link ExamModel#getInstructorBackToBackConflictWeight()}
573 * <li> Distance back-to-back instructor conflicts {@link ExamPlacement#getNrInstructorDistanceBackToBackConflicts()}, weighted by {@link ExamModel#getInstructorDistanceBackToBackConflictWeight()}
574 * <li> Front load penalty {@link ExamPlacement#getLargePenalty()}, weighted by {@link ExamModel#getLargeWeight()}
575 * </ul>
576 */
577 public double getTimeCost() {
578 Exam exam = (Exam)variable();
579 ExamModel model = (ExamModel)exam.getModel();
580 return
581 model.getDirectConflictWeight()*getNrDirectConflicts()+
582 model.getBackToBackConflictWeight()*getNrBackToBackConflicts()+
583 model.getMoreThanTwoADayWeight()*getNrMoreThanTwoADayConflicts()+
584 model.getPeriodWeight()*getPeriodPenalty()+
585 model.getPeriodSizeWeight()*getPeriodPenalty()*(exam.getSize()+1)+
586 model.getPeriodIndexWeight()*getPeriod().getIndex()+
587 model.getExamRotationWeight()*getRotationPenalty()+
588 model.getInstructorDirectConflictWeight()*getNrInstructorDirectConflicts()+
589 model.getInstructorMoreThanTwoADayWeight()*getNrInstructorMoreThanTwoADayConflicts()+
590 model.getInstructorBackToBackConflictWeight()*getNrInstructorBackToBackConflicts()+
591 model.getPerturbationWeight()*getPerturbationPenalty()+
592 model.getDistributionWeight()*getPeriodDistributionPenalty()+
593 model.getLargeWeight()*getLargePenalty();
594 }
595
596 /**
597 * Overall cost of using this set or rooms.
598 * The room cost of an assignment consists of the following criteria:
599 * <ul>
600 * <li> Distance back-to-back student conflicts {@link ExamPlacement#getNrDistanceBackToBackConflicts()}, weighted by {@link ExamModel#getDistanceBackToBackConflictWeight()}
601 * <li> Distance back-to-back instructor conflicts {@link ExamPlacement#getNrInstructorDistanceBackToBackConflicts()}, weighted by {@link ExamModel#getInstructorDistanceBackToBackConflictWeight()}
602 * <li> Room size penalty {@link ExamPlacement#getRoomSizePenalty()}, weighted by {@link ExamModel#getRoomSizeWeight()}
603 * <li> Room split penalty {@link ExamPlacement#getRoomSplitPenalty()}, weighted by {@link ExamModel#getRoomSplitWeight()}
604 * <li> Room split distance penalty {@link ExamPlacement#getRoomSplitDistancePenalty()}, weighted by {@link ExamModel#getRoomSplitDistanceWeight()}
605 * <li> Room penalty {@link ExamPlacement#getRoomPenalty()}, weighted by {@link ExamModel#getRoomWeight()}
606 * </ul>
607 */
608 public double getRoomCost() {
609 Exam exam = (Exam)variable();
610 ExamModel model = (ExamModel)exam.getModel();
611 return
612 model.getDistanceBackToBackConflictWeight()*getNrDistanceBackToBackConflicts()+
613 model.getRoomSizeWeight()*getRoomSizePenalty()+
614 model.getRoomSplitWeight()*getRoomSplitPenalty()+
615 model.getRoomWeight()*getRoomPenalty()+
616 model.getInstructorDistanceBackToBackConflictWeight()*getNrInstructorDistanceBackToBackConflicts()+
617 model.getRoomSplitDistanceWeight()*getRoomSizePenalty()+
618 model.getDistributionWeight()*getRoomDistributionPenalty()+
619 model.getRoomPerturbationWeight()*getRoomPerturbationPenalty();
620 }
621
622 /**
623 * Room names separated with the given delimiter
624 */
625 public String getRoomName(String delim) {
626 String roomName = "";
627 for (Iterator i=getRoomPlacements().iterator();i.hasNext();) {
628 ExamRoomPlacement r = (ExamRoomPlacement)i.next();
629 roomName += r.getRoom().getName();
630 if (i.hasNext()) roomName += delim;
631 }
632 return roomName;
633 }
634
635 /**
636 * Assignment name (period / room(s))
637 */
638 public String getName() {
639 return getPeriod()+"/"+getRoomName(",");
640 }
641
642 /**
643 * String representation -- returns a list of assignment costs
644 */
645 public String toString() {
646 DecimalFormat df = new DecimalFormat("0.00");
647 Exam exam = (Exam)variable();
648 ExamModel model = (ExamModel)exam.getModel();
649 return variable().getName()+" = "+getName()+" ("+
650 df.format(toDouble())+"/"+
651 "DC:"+getNrDirectConflicts()+","+
652 "M2D:"+getNrMoreThanTwoADayConflicts()+","+
653 "BTB:"+getNrBackToBackConflicts()+","+
654 (model.getBackToBackDistance()<0?"":"dBTB:"+getNrDistanceBackToBackConflicts()+",")+
655 "PP:"+getPeriodPenalty()+","+
656 "@P:"+getRotationPenalty()+","+
657 "RSz:"+getRoomSizePenalty()+","+
658 "RSp:"+getRoomSplitPenalty()+","+
659 "RD:"+df.format(getRoomSplitDistancePenalty())+","+
660 "RP:"+getRoomPenalty()+
661 (model.isMPP()?",IP:"+getPerturbationPenalty()+",IRP:"+getRoomPerturbationPenalty():"")+
662 ")";
663 }
664
665 /**
666 * Compare two assignments for equality
667 */
668 public boolean equals(Object o) {
669 if (o==null || !(o instanceof ExamPlacement)) return false;
670 ExamPlacement p = (ExamPlacement)o;
671 return p.variable().equals(variable()) && p.getPeriod().equals(getPeriod()) && p.getRoomPlacements().equals(getRoomPlacements());
672 }
673
674 /**
675 * Hash code
676 */
677 public int hashCode() {
678 return iHashCode;
679 }
680
681 /**
682 * True if given room is between {@link ExamPlacement#getRoomPlacements()}
683 */
684 public boolean contains(ExamRoom room) {
685 return getRoomPlacements().contains(new ExamRoomPlacement(room));
686 }
687 }