001    package net.sf.cpsolver.coursett.constraint;
002    
003    import java.util.Enumeration;
004    import java.util.HashSet;
005    import java.util.Iterator;
006    import java.util.Locale;
007    import java.util.Set;
008    import java.util.Vector;
009    
010    import net.sf.cpsolver.coursett.Constants;
011    import net.sf.cpsolver.coursett.model.Lecture;
012    import net.sf.cpsolver.coursett.model.Placement;
013    import net.sf.cpsolver.coursett.model.TimeLocation.IntEnumeration;
014    import net.sf.cpsolver.ifs.model.Constraint;
015    import net.sf.cpsolver.ifs.model.Value;
016    import net.sf.cpsolver.ifs.model.Variable;
017    import net.sf.cpsolver.ifs.util.DataProperties;
018    import net.sf.cpsolver.ifs.util.ToolBox;
019    
020    /** Spread given set of classes in time as much as possible. 
021     * See {@link DepartmentSpreadConstraint} for more details. 
022     * 
023     * @version
024     * CourseTT 1.1 (University Course Timetabling)<br>
025     * Copyright (C) 2006 Tomáš Müller<br>
026     * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
027     * Lazenska 391, 76314 Zlin, Czech Republic<br>
028     * <br>
029     * This library is free software; you can redistribute it and/or
030     * modify it under the terms of the GNU Lesser General Public
031     * License as published by the Free Software Foundation; either
032     * version 2.1 of the License, or (at your option) any later version.
033     * <br><br>
034     * This library is distributed in the hope that it will be useful,
035     * but WITHOUT ANY WARRANTY; without even the implied warranty of
036     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
037     * Lesser General Public License for more details.
038     * <br><br>
039     * You should have received a copy of the GNU Lesser General Public
040     * License along with this library; if not, write to the Free Software
041     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
042     */
043    
044    public class SpreadConstraint extends Constraint implements WeakeningConstraint {
045        private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(DepartmentSpreadConstraint.class);
046        private static java.text.DecimalFormat sDoubleFormat = new java.text.DecimalFormat("0.00",new java.text.DecimalFormatSymbols(Locale.US));
047        private static java.text.DecimalFormat sIntFormat = new java.text.DecimalFormat("0",new java.text.DecimalFormatSymbols(Locale.US));
048        private int iMaxCourses[][] = null;
049        private int iCurrentPenalty = 0;
050        private int iMaxAllowedPenalty = 0;
051        
052        private boolean iInitialized = false;
053        private int[][] iNrCourses = null;
054        private Vector [][] iCourses = null;
055        private double iSpreadFactor = 1.20;
056        private int iUnassignmentsToWeaken = 250;
057        private long iUnassignment = 0;
058        private String iName = null;
059        
060        public static boolean USE_MOST_IMPROVEMENT_ADEPTS = false;
061        
062        public SpreadConstraint(String name, double spreadFactor, int unassignmentsToWeaken, boolean interactiveMode) {
063            iName = name;
064            iSpreadFactor = spreadFactor;
065            iUnassignmentsToWeaken = unassignmentsToWeaken;
066            iNrCourses = new int[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.NR_DAYS_WEEK];
067            iCourses = new Vector[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.NR_DAYS_WEEK];
068            if (interactiveMode)
069                iUnassignmentsToWeaken = 0;
070            for (int i=0;i<iNrCourses.length;i++) {
071                for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
072                    iNrCourses[i][j] = 0;
073                    iCourses[i][j]=new Vector(10,5);
074                }
075            }
076        }
077        
078        public SpreadConstraint(DataProperties config, String name) {
079            this(
080                            name, 
081                            config.getPropertyDouble("Spread.SpreadFactor",1.20),
082                            config.getPropertyInt("Spread.Unassignments2Weaken", 250),
083                            config.getPropertyBoolean("General.InteractiveMode", false)
084                            );
085        }
086        
087        /** Initialize constraint (to be called after all variables are added to this constraint) */
088        public void init() {
089            if (iInitialized) return;
090            double histogramPerDay[][] = new double[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.NR_DAYS_WEEK];
091            iMaxCourses = new int[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.NR_DAYS_WEEK];
092            for (int i=0;i<Constants.SLOTS_PER_DAY_NO_EVENINGS;i++)
093                for (int j=0;j<Constants.NR_DAYS_WEEK;j++)
094                    histogramPerDay[i][j]=0.0;
095            int totalUsedSlots = 0;
096            for (Enumeration e=variables().elements();e.hasMoreElements();) {
097                Lecture lecture = (Lecture)e.nextElement();
098                Placement firstPlacement = (lecture.values().isEmpty()?null:(Placement)lecture.values().firstElement());
099                if (firstPlacement!=null) {
100                    totalUsedSlots += firstPlacement.getTimeLocation().getNrSlotsPerMeeting()*firstPlacement.getTimeLocation().getNrMeetings();
101                }
102                for (Enumeration e2=lecture.values().elements();e2.hasMoreElements();) {
103                    Placement p = (Placement)e2.nextElement();
104                    int firstSlot = p.getTimeLocation().getStartSlot();
105                    if (firstSlot>Constants.DAY_SLOTS_LAST) continue;
106                    int endSlot = firstSlot+p.getTimeLocation().getNrSlotsPerMeeting()-1;
107                    if (endSlot<Constants.DAY_SLOTS_FIRST) continue;
108                    for (int i=Math.max(firstSlot,Constants.DAY_SLOTS_FIRST);i<=Math.min(endSlot,Constants.DAY_SLOTS_LAST);i++) {
109                        int dayCode = p.getTimeLocation().getDayCode();
110                        for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
111                            if ((dayCode & Constants.DAY_CODES[j])!=0) {
112                                histogramPerDay[i-Constants.DAY_SLOTS_FIRST][j] += 1.0 / lecture.values().size();
113                            }
114                        }
115                    }
116                }
117            }
118            //System.out.println("Histogram for department "+iDepartment+":");
119            double threshold = iSpreadFactor*((double)totalUsedSlots/(Constants.NR_DAYS_WEEK*Constants.SLOTS_PER_DAY_NO_EVENINGS));
120            //System.out.println("Threshold["+iDepartment+"] = "+threshold);
121            int totalAvailableSlots = 0;
122            for (int i=0;i<Constants.SLOTS_PER_DAY_NO_EVENINGS;i++) {
123                //System.out.println("  "+fmt(i+1)+": "+fmt(histogramPerDay[i]));
124                for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
125                    iMaxCourses[i][j]=(int)(0.999+(histogramPerDay[i][j]<=threshold?iSpreadFactor*histogramPerDay[i][j]:histogramPerDay[i][j]));
126                    totalAvailableSlots += iMaxCourses[i][j];
127                }
128            }
129            for (Enumeration e=variables().elements();e.hasMoreElements();) {
130                Lecture lecture = (Lecture)e.nextElement();
131                Placement placement = (Placement)lecture.getAssignment();
132                if (placement==null) continue;
133                int firstSlot = placement.getTimeLocation().getStartSlot();
134                if (firstSlot>Constants.DAY_SLOTS_LAST) continue;
135                int endSlot = firstSlot+placement.getTimeLocation().getNrSlotsPerMeeting()-1;
136                if (endSlot<Constants.DAY_SLOTS_FIRST) continue;
137                for (int i=Math.max(firstSlot,Constants.DAY_SLOTS_FIRST);i<=Math.min(endSlot,Constants.DAY_SLOTS_LAST);i++) {
138                    for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
139                        int dayCode = Constants.DAY_CODES[j];
140                        if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
141                            iNrCourses[i-Constants.DAY_SLOTS_FIRST][j]++;
142                            iCourses[i-Constants.DAY_SLOTS_FIRST][j].addElement(placement);
143                        }
144                    }
145                }
146            }
147            iCurrentPenalty = 0;
148            for (int i=0;i<Constants.SLOTS_PER_DAY_NO_EVENINGS;i++) {
149                for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
150                    iCurrentPenalty += Math.max(0,iNrCourses[i][j]-iMaxCourses[i][j]);
151                }
152            }
153            iMaxAllowedPenalty = iCurrentPenalty;
154            //System.out.println("Initial penalty = "+fmt(iMaxAllowedPenalty));
155            iInitialized = true;
156        }
157        
158        public Placement getAdept(Placement placement, int[][] nrCourses, Set conflicts) {
159            Placement adept = null;
160            int improvement = 0;
161            
162            //take uncommitted placements first
163            for (Enumeration e=variables().elements();e.hasMoreElements();) {
164                Lecture lect = (Lecture)e.nextElement();
165                if (lect.isCommitted()) continue;
166                Placement plac = (Placement)lect.getAssignment();
167                if (plac==null || plac.equals(placement) || placement.variable().equals(plac.variable()) || conflicts.contains(plac)) continue;
168                int imp = getPenaltyIfUnassigned(plac,nrCourses);
169                if (imp==0) continue;
170                if (adept==null || imp>improvement) {
171                    adept = plac; improvement = imp;
172                }
173            }
174            if (adept!=null) return adept;
175            
176            // no uncommitted placement found -- take committed one
177            for (Enumeration e=variables().elements();e.hasMoreElements();) {
178                Lecture lect = (Lecture)e.nextElement();
179                if (!lect.isCommitted()) continue;
180                Placement plac = (Placement)lect.getAssignment();
181                if (plac==null || plac.equals(placement) || conflicts.contains(plac)) continue;
182                int imp = getPenaltyIfUnassigned(plac,nrCourses);
183                if (imp==0) continue;
184                if (adept==null || imp>improvement) {
185                    adept = plac; improvement = imp;
186                }
187            }
188            
189            return adept;
190        }
191        
192        private Set[] getAdepts(Placement placement, int[][] nrCourses, Set conflicts) {
193            int firstSlot = placement.getTimeLocation().getStartSlot();
194            if (firstSlot>Constants.DAY_SLOTS_LAST) return null;
195            int endSlot = firstSlot+placement.getTimeLocation().getNrSlotsPerMeeting()-1;
196            if (endSlot<Constants.DAY_SLOTS_FIRST) return null;
197            HashSet adepts[] = new HashSet[] {new HashSet(),new HashSet()};
198            for (int i=Math.max(firstSlot,Constants.DAY_SLOTS_FIRST);i<=Math.min(endSlot,Constants.DAY_SLOTS_LAST);i++) {
199                for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
200                    int dayCode = Constants.DAY_CODES[j];
201                    if ((dayCode & placement.getTimeLocation().getDayCode()) != 0 && nrCourses[i-Constants.DAY_SLOTS_FIRST][j]>=iMaxCourses[i-Constants.DAY_SLOTS_FIRST][j]) {
202                            for (Enumeration e=iCourses[i-Constants.DAY_SLOTS_FIRST][j].elements();e.hasMoreElements();) {
203                                    Placement p = (Placement)e.nextElement();
204                                    if (conflicts.contains(p)) continue;
205                                    if (p.equals(placement)) continue;
206                            if (p.variable().equals(placement.variable())) continue;
207                                    adepts[((Lecture)p.variable()).isCommitted()?1:0].add(p);
208                            }
209                    }
210                }
211            }
212            return adepts;
213            //sLogger.debug("  -- adept "+adept+" selected, penalty will be decreased by "+improvement);
214        }
215        
216        private int getPenaltyIfUnassigned(Placement placement, int[][] nrCourses)  {
217            int firstSlot = placement.getTimeLocation().getStartSlot();
218            if (firstSlot>Constants.DAY_SLOTS_LAST) return 0;
219            int endSlot = firstSlot+placement.getTimeLocation().getNrSlotsPerMeeting()-1;
220            if (endSlot<Constants.DAY_SLOTS_FIRST) return 0;
221            int penalty = 0;
222            for (int i=Math.max(firstSlot,Constants.DAY_SLOTS_FIRST);i<=Math.min(endSlot,Constants.DAY_SLOTS_LAST);i++) {
223                for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
224                    int dayCode = Constants.DAY_CODES[j];
225                    if ((dayCode & placement.getTimeLocation().getDayCode()) != 0 &&
226                        nrCourses[i-Constants.DAY_SLOTS_FIRST][j]>iMaxCourses[i-Constants.DAY_SLOTS_FIRST][j]) penalty ++;
227                }
228            }
229            return penalty;
230        }
231    
232        private int tryUnassign(Placement placement, int[][] nrCourses) {
233            //sLogger.debug("  -- trying to unassign "+placement);
234            int firstSlot = placement.getTimeLocation().getStartSlot();
235            if (firstSlot>Constants.DAY_SLOTS_LAST) return 0;
236            int endSlot = firstSlot+placement.getTimeLocation().getNrSlotsPerMeeting()-1;
237            if (endSlot<Constants.DAY_SLOTS_FIRST) return 0;
238            int improvement = 0;
239            for (int i=Math.max(firstSlot,Constants.DAY_SLOTS_FIRST);i<=Math.min(endSlot,Constants.DAY_SLOTS_LAST);i++) {
240                for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
241                    int dayCode = Constants.DAY_CODES[j];
242                    if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
243                        if (nrCourses[i-Constants.DAY_SLOTS_FIRST][j]>iMaxCourses[i-Constants.DAY_SLOTS_FIRST][j]) improvement++;
244                        nrCourses[i-Constants.DAY_SLOTS_FIRST][j]--;
245                    }
246                }
247            }
248            //sLogger.debug("  -- penalty is decreased by "+improvement);
249            return improvement;
250        }
251    
252        private int tryAssign(Placement placement, int[][] nrCourses) {
253            //sLogger.debug("  -- trying to assign "+placement);
254            int firstSlot = placement.getTimeLocation().getStartSlot();
255            if (firstSlot>Constants.DAY_SLOTS_LAST) return 0;
256            int endSlot = firstSlot+placement.getTimeLocation().getNrSlotsPerMeeting()-1;
257            if (endSlot<Constants.DAY_SLOTS_FIRST) return 0;
258            int penalty = 0;
259            for (int i=Math.max(firstSlot,Constants.DAY_SLOTS_FIRST);i<=Math.min(endSlot,Constants.DAY_SLOTS_LAST);i++) {
260                for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
261                    int dayCode = Constants.DAY_CODES[j];
262                    if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
263                        nrCourses[i-Constants.DAY_SLOTS_FIRST][j]++;
264                        if (nrCourses[i-Constants.DAY_SLOTS_FIRST][j]>iMaxCourses[i-Constants.DAY_SLOTS_FIRST][j]) penalty++;
265                    }
266                }
267            }
268            //sLogger.debug("  -- penalty is incremented by "+penalty);
269            return penalty;
270        }
271    
272        public void computeConflicts(Value value, Set conflicts) {
273            if (!iInitialized || iUnassignmentsToWeaken==0) return;
274            Placement placement = (Placement)value;
275            int penalty = iCurrentPenalty + getPenalty(placement);
276            if (penalty<=iMaxAllowedPenalty) return;
277            int firstSlot = placement.getTimeLocation().getStartSlot();
278            if (firstSlot>Constants.DAY_SLOTS_LAST) return;
279            int endSlot = firstSlot+placement.getTimeLocation().getNrSlotsPerMeeting()-1;
280            if (endSlot<Constants.DAY_SLOTS_FIRST) return;
281            //sLogger.debug("-- computing conflict for value "+value+" ... (penalty="+iCurrentPenalty+", penalty with the value="+penalty+", max="+iMaxAllowedPenalty+")");
282            int[][] nrCourses = new int[iNrCourses.length][Constants.NR_DAYS_WEEK];
283            for (int i=0;i<iNrCourses.length;i++)
284                for (int j=0;j<Constants.NR_DAYS_WEEK;j++)
285                    nrCourses[i][j] = iNrCourses[i][j];
286            tryAssign(placement, nrCourses);
287            //sLogger.debug("  -- nrCurses="+fmt(nrCourses));
288            for (Enumeration e=variables().elements();e.hasMoreElements();) {
289                Lecture lect = (Lecture)e.nextElement();
290                if (conflicts.contains(lect)) {
291                    penalty -= tryUnassign((Placement)lect.getAssignment(), nrCourses);
292                }
293                if (penalty<=iMaxAllowedPenalty) return;
294            }
295            if (USE_MOST_IMPROVEMENT_ADEPTS) {
296                    while (penalty>iMaxAllowedPenalty) {
297                            Placement plac = getAdept(placement, nrCourses, conflicts);
298                            if (plac==null) break;
299                            conflicts.add(plac);
300                            penalty -= tryUnassign(plac, nrCourses);
301                    }
302            } else {
303                if (penalty>iMaxAllowedPenalty) {
304                    Set adepts[] = getAdepts(placement, nrCourses, conflicts);
305                    for (int i=0;penalty>iMaxAllowedPenalty && i<adepts.length;i++) {
306                            while (!adepts[i].isEmpty() && penalty>iMaxAllowedPenalty) {
307                                    Placement plac = (Placement)ToolBox.random(adepts[i]);
308                                    adepts[i].remove(plac);
309                                    conflicts.add(plac);
310                                    //sLogger.debug("  -- conflict "+lect.getAssignment()+" added");
311                                    penalty -= tryUnassign(plac, nrCourses);
312                            }
313                    }
314                }
315            }
316        }
317        
318        public boolean inConflict(Value value) {
319            if (!iInitialized || iUnassignmentsToWeaken==0) return false;
320            Placement placement = (Placement)value;
321                return getPenalty(placement)+iCurrentPenalty>iMaxAllowedPenalty;
322        }
323        
324        public boolean isConsistent(Value value1, Value value2) {
325            if (!iInitialized || iUnassignmentsToWeaken==0) return true;
326            Placement p1 = (Placement)value1;
327            Placement p2 = (Placement)value2;
328            if (!p1.getTimeLocation().hasIntersection(p2.getTimeLocation())) return true;
329            int firstSlot = Math.max(p1.getTimeLocation().getStartSlot(),p2.getTimeLocation().getStartSlot());
330            if (firstSlot>Constants.DAY_SLOTS_LAST) return true;
331            int endSlot = Math.min(p1.getTimeLocation().getStartSlot()+p1.getTimeLocation().getNrSlotsPerMeeting()-1,p2.getTimeLocation().getStartSlot()+p2.getTimeLocation().getNrSlotsPerMeeting()-1);
332            if (endSlot<Constants.DAY_SLOTS_FIRST) return true;
333            int penalty = 0;
334            for (int i=Math.max(firstSlot,Constants.DAY_SLOTS_FIRST);i<=Math.min(endSlot,Constants.DAY_SLOTS_LAST);i++) {
335                for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
336                    int dayCode = Constants.DAY_CODES[j];
337                    if ((dayCode & p1.getTimeLocation().getDayCode()) != 0 && (dayCode & p2.getTimeLocation().getDayCode()) != 0) {
338                        penalty += Math.max(0,2-iMaxCourses[i-Constants.DAY_SLOTS_FIRST][j]);
339                    }
340                }
341            }
342            return (penalty<iMaxAllowedPenalty);
343        }
344        
345        public void weaken() {
346            if (!iInitialized || iUnassignmentsToWeaken==0) return;
347            iUnassignment ++;
348            if (iUnassignment%iUnassignmentsToWeaken==0)
349                iMaxAllowedPenalty ++;
350        }
351        
352        public void assigned(long iteration, Value value) {
353            super.assigned(iteration, value);
354            if (!iInitialized) return;
355            Placement placement = (Placement)value;
356            int firstSlot = placement.getTimeLocation().getStartSlot();
357            if (firstSlot>Constants.DAY_SLOTS_LAST) return;
358            int endSlot = firstSlot+placement.getTimeLocation().getNrSlotsPerMeeting()-1;
359            if (endSlot<Constants.DAY_SLOTS_FIRST) return;
360            for (int i=Math.max(firstSlot,Constants.DAY_SLOTS_FIRST);i<=Math.min(endSlot,Constants.DAY_SLOTS_LAST);i++) {
361                for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
362                    int dayCode = Constants.DAY_CODES[j];
363                    if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
364                        iNrCourses[i-Constants.DAY_SLOTS_FIRST][j]++;
365                        if (iNrCourses[i-Constants.DAY_SLOTS_FIRST][j]>iMaxCourses[i-Constants.DAY_SLOTS_FIRST][j]) iCurrentPenalty++;
366                        iCourses[i-Constants.DAY_SLOTS_FIRST][j].addElement(value);
367                    }
368                }
369            }
370        }
371        
372        public void unassigned(long iteration, Value value) {
373            super.unassigned(iteration, value);
374            if (!iInitialized) return;
375            Placement placement = (Placement)value;
376            int firstSlot = placement.getTimeLocation().getStartSlot();
377            if (firstSlot>Constants.DAY_SLOTS_LAST) return;
378            int endSlot = firstSlot+placement.getTimeLocation().getNrSlotsPerMeeting()-1;
379            if (endSlot<Constants.DAY_SLOTS_FIRST) return;
380            for (int i=Math.max(firstSlot,Constants.DAY_SLOTS_FIRST);i<=Math.min(endSlot,Constants.DAY_SLOTS_LAST);i++) {
381                for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
382                    int dayCode = Constants.DAY_CODES[j];
383                    if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
384                        if (iNrCourses[i-Constants.DAY_SLOTS_FIRST][j]>iMaxCourses[i-Constants.DAY_SLOTS_FIRST][j]) iCurrentPenalty--;
385                        iNrCourses[i-Constants.DAY_SLOTS_FIRST][j]--;
386                        iCourses[i-Constants.DAY_SLOTS_FIRST][j].removeElement(value);
387                    }
388                }
389            }
390        }
391        
392        public String getName() { return iName; }
393        
394        public String toString() {
395            StringBuffer sb = new StringBuffer();
396            sb.append("Time Spread between ");
397            for (Enumeration e=variables().elements();e.hasMoreElements();) {
398                Variable v = (Variable)e.nextElement();
399                sb.append(v.getName());
400                if (e.hasMoreElements()) sb.append(", ");
401            }
402            return sb.toString();
403        }
404        
405        /** Department balancing penalty for this department */
406        public int getPenalty() {
407            if (!iInitialized) return getPenaltyEstimate();
408            return iCurrentPenalty;
409        }
410        
411        public int getPenaltyEstimate() {
412            double histogramPerDay[][] = new double[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.NR_DAYS_WEEK];
413            int maxCourses[][] = new int[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.NR_DAYS_WEEK];
414            int nrCourses[][] = new int[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.NR_DAYS_WEEK];
415            for (int i=0;i<Constants.SLOTS_PER_DAY_NO_EVENINGS;i++)
416                for (int j=0;j<Constants.NR_DAYS_WEEK;j++)
417                    histogramPerDay[i][j]=0.0;
418            int totalUsedSlots = 0;
419            for (Enumeration e=variables().elements();e.hasMoreElements();) {
420                Lecture lecture = (Lecture)e.nextElement();
421                Placement firstPlacement = (lecture.values().isEmpty()?null:(Placement)lecture.values().firstElement());
422                if (firstPlacement!=null) {
423                    totalUsedSlots += firstPlacement.getTimeLocation().getNrSlotsPerMeeting()*firstPlacement.getTimeLocation().getNrMeetings();
424                }
425                for (Enumeration e2=lecture.values().elements();e2.hasMoreElements();) {
426                    Placement p = (Placement)e2.nextElement();
427                    int firstSlot = p.getTimeLocation().getStartSlot();
428                    if (firstSlot>Constants.DAY_SLOTS_LAST) continue;
429                    int endSlot = firstSlot+p.getTimeLocation().getNrSlotsPerMeeting()-1;
430                    if (endSlot<Constants.DAY_SLOTS_FIRST) continue;
431                    for (int i=Math.max(firstSlot,Constants.DAY_SLOTS_FIRST);i<=Math.min(endSlot,Constants.DAY_SLOTS_LAST);i++) {
432                        int dayCode = p.getTimeLocation().getDayCode();
433                        for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
434                            if ((dayCode & Constants.DAY_CODES[j])!=0) {
435                                histogramPerDay[i-Constants.DAY_SLOTS_FIRST][j] += 1.0 / lecture.values().size();
436                            }
437                        }
438                    }
439                }
440            }
441            double threshold = iSpreadFactor*((double)totalUsedSlots/(Constants.NR_DAYS_WEEK*Constants.SLOTS_PER_DAY_NO_EVENINGS));
442            for (int i=0;i<Constants.SLOTS_PER_DAY_NO_EVENINGS;i++) {
443                for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
444                    nrCourses[i][j]=0;
445                    maxCourses[i][j]=(int)(0.999+(histogramPerDay[i][j]<=threshold?iSpreadFactor*histogramPerDay[i][j]:histogramPerDay[i][j]));
446                }
447            }
448            int currentPenalty = 0;
449            for (Enumeration e=variables().elements();e.hasMoreElements();) {
450                Lecture lecture = (Lecture)e.nextElement();
451                Placement placement = (Placement)lecture.getAssignment();
452                if (placement==null) continue;
453                int firstSlot = placement.getTimeLocation().getStartSlot();
454                if (firstSlot>Constants.DAY_SLOTS_LAST) continue;
455                int endSlot = firstSlot+placement.getTimeLocation().getNrSlotsPerMeeting()-1;
456                if (endSlot<Constants.DAY_SLOTS_FIRST) continue;
457                for (int i=Math.max(firstSlot,Constants.DAY_SLOTS_FIRST);i<=Math.min(endSlot,Constants.DAY_SLOTS_LAST);i++) {
458                    for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
459                        int dayCode = Constants.DAY_CODES[j];
460                        if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
461                            nrCourses[i-Constants.DAY_SLOTS_FIRST][j]++;
462                        }
463                    }
464                }
465            }
466            for (int i=0;i<Constants.SLOTS_PER_DAY_NO_EVENINGS;i++) {
467                for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
468                    currentPenalty += Math.max(0,nrCourses[i][j]-maxCourses[i][j]);
469                }
470            }
471            iMaxAllowedPenalty = Math.max(iMaxAllowedPenalty,currentPenalty);
472            return currentPenalty;
473        }
474        
475        public int getMaxPenalty(Placement placement) {
476            int penalty = 0;
477            for (IntEnumeration e=placement.getTimeLocation().getSlots();e.hasMoreElements();) {
478                    int slot = e.nextInt();
479                    int day = slot/Constants.SLOTS_PER_DAY;
480                    int time = slot%Constants.SLOTS_PER_DAY;
481                    if (time<Constants.DAY_SLOTS_FIRST || time>Constants.DAY_SLOTS_LAST) continue;
482                    if (day>=Constants.NR_DAYS_WEEK) continue;
483                    int dif = iNrCourses[time-Constants.DAY_SLOTS_FIRST][day]-iMaxCourses[time-Constants.DAY_SLOTS_FIRST][day];
484                    if (dif>penalty) penalty = dif;
485            }
486            return penalty;
487        }
488        
489        /** Department balancing penalty of the given placement */
490        public int getPenalty(Placement placement) {
491            if (!iInitialized) return 0;
492            int firstSlot = placement.getTimeLocation().getStartSlot();
493            if (firstSlot>Constants.DAY_SLOTS_LAST) return 0;
494            Placement initialPlacement = (Placement)placement.variable().getAssignment();
495            int endSlot = firstSlot+placement.getTimeLocation().getNrSlotsPerMeeting()-1;
496            if (endSlot<Constants.DAY_SLOTS_FIRST) return 0;
497            int penalty = 0;
498            int min = Math.max(firstSlot,Constants.DAY_SLOTS_FIRST);
499            int max = Math.min(endSlot,Constants.DAY_SLOTS_LAST);
500            for (int j=0;j<Constants.NR_DAYS_WEEK;j++) {
501                    int dayCode = Constants.DAY_CODES[j];
502                    if ((dayCode & placement.getTimeLocation().getDayCode()) == 0) continue;
503                    for (int i=min;i<=max;i++) {
504                    if (iNrCourses[i-Constants.DAY_SLOTS_FIRST][j]>=iMaxCourses[i-Constants.DAY_SLOTS_FIRST][j]+(initialPlacement==null?0:iCourses[i-Constants.DAY_SLOTS_FIRST][j].contains(initialPlacement)?1:0))
505                            penalty ++;
506                }
507            }
508            return penalty;
509        }
510            
511        private String fmt(double value) {
512            return sDoubleFormat.format(value);
513        }
514        
515        private String fmt(double[] values) {
516            if (values==null) return null;
517            StringBuffer sb = new StringBuffer("[");
518            for (int i=0;i<Math.min(5,values.length);i++)
519                sb.append(i==0?"":",").append(sDoubleFormat.format(values[i]));
520                sb.append("]");
521                return sb.toString();
522        }
523        
524        private String fmt(double[][] values) {
525            if (values==null) return null;
526            StringBuffer sb = new StringBuffer("[");
527            for (int i=0;i<values.length;i++) {
528                sb.append(i==0?"":",").append(fmt(values[i]));
529            }
530            sb.append("]");
531            return sb.toString();
532        }
533        
534        private String fmt(int value) {
535            return sIntFormat.format(value);
536        }
537        
538        private String fmt(int[] values) {
539            if (values==null) return null;
540            StringBuffer sb = new StringBuffer("[");
541            for (int i=0;i<Math.min(5,values.length);i++)
542                sb.append(i==0?"":",").append(sIntFormat.format(values[i]));
543                sb.append("]");
544                return sb.toString();
545        }
546        
547        private String fmt(int[][] values) {
548            if (values==null) return null;
549            StringBuffer sb = new StringBuffer("[");
550            for (int i=0;i<values.length;i++) {
551                sb.append(i==0?"":",").append(fmt(values[i]));
552            }
553            sb.append("]");
554            return sb.toString();
555        }
556        
557        public int[][] getMaxCourses() { return iMaxCourses; }
558        public int[][] getNrCourses() { return iNrCourses; }
559        public Vector[][] getCourses() { return iCourses; }
560        
561        public void addVariable(Variable variable) {
562            Lecture lecture = (Lecture)variable;
563            if (lecture.canShareRoom()) {
564                    for (Iterator i=lecture.groupConstraints().iterator();i.hasNext();) {
565                            GroupConstraint gc = (GroupConstraint)i.next();
566                            if (gc.getType()==GroupConstraint.TYPE_MEET_WITH) {
567                                    if (gc.variables().indexOf(lecture)>0) return;
568                            }
569                    }
570            }
571            super.addVariable(variable);
572        }
573    }