    001    package ttsolver.constraint;
    002
    003    import ifs.model.*;
    004    import ttsolver.model.*;
    005    import ifs.util.*;
    006    import java.util.*;
    007    import edu.purdue.smas.timetable.util.Constants;
    008
    009    /**
    010     * Departmental ballancing constraint.
    011     * <br><br>
    012     * The new implementation of the balancing times for departments works as follows: Initially, there is a histogram for
    013     * each department computed. For each time slot, it says how many placements of all classes (of the department) include
    014     * this time slot. Each such placement has the weight of 1 / number of placements of the class, so the total sum of all
    015     * values in the histogram (i.e., for all time slots) is equal to the total sum of half-hours required by the given set
    016     * of classes.
    017     * <br><br>
    018     * On the other hand, each class splits its number of half-hours (e.g., 2x50 has 4 half-hours, "4 points") into the
    019     * time slots which it can occupy, according to the frequencies of the utilization of each time slots (i.e., number of
    020     * placements containing the time slots divided by the number of all placements of the class).
    021     * <br><br>
    022     * For example, a histogram for department 1286:<code><br>
    023     * 1: [0.10,0.00,0.10,0.00,0.10] <- 7:30 [Mon, Tue, Wed, Thu, Fri]<br>
    024     * 2: [0.10,0.00,0.10,0.00,0.10] <- 8:00 [Mon, Tue, Wed, Thu, Fri]<br>
    025     * 3: [0.35,0.62,0.48,0.62,0.10] ... and so on<br>
    026     * 4: [0.35,0.62,0.48,0.62,0.10]<br>
    027     * 5: [1.35,1.12,1.48,0.12,1.10]<br>
    028     * 6: [1.35,1.12,1.48,0.12,1.10]<br>
    029     * 7: [0.35,0.62,0.48,1.63,0.10]<br>
    030     * 8: [0.35,0.62,0.48,1.63,0.10]<br>
    031     * 9: [0.35,0.12,0.48,0.12,0.10]<br>
    032     * 10:[0.35,0.12,0.48,0.12,0.10]<br>
    033     * 11:[0.35,0.12,0.48,0.12,0.10]<br>
    034     * 12:[0.35,0.12,0.48,0.12,0.10]<br>
    035     * 13:[0.35,0.12,0.48,1.12,0.10]<br>
    036     * 14:[0.35,0.12,0.48,1.12,0.10]<br>
    037     * 15:[0.35,0.12,0.48,0.12,0.10]<br>
    038     * 16:[0.35,0.12,0.48,0.12,0.10]<br>
    039     * 17:[0.35,0.12,0.48,0.12,0.10]<br>
    040     * 18:[0.35,0.12,0.48,0.12,0.10]<br>
    041     * 19:[0.10,0.00,0.10,0.00,0.10]<br>
    042     * 20:[0.10,0.00,0.10,0.00,0.10]<br>
    043     * 21:[0.00,0.00,0.00,0.00,0.00]<br>
    044     * </code><br>
    045     * You can easily see, that the time slots which are prohibited for all of the classes of the department have zero
    046     * values, also some time slots are used much often than the others. Note that there are no preferences involved in
    047     * this computation, only prohibited / not used times are less involved.
    048     * <br><br>
    049     * The idea is to setup the initial limits for each of the time slots according to the above values. The reason for doing
    050     * so is to take the requirements (time patterns, required/prohibited times) of all classes of the department into
    051     * account. For instance, take two classes A and B of type MWF 2x100 with two available locations starting from 7:30
    052     * and 8:30. Note that the time slot Monday 8:30-9:00 will be always used by both of the classes and for instance the
    053     * time slot Monday 7:30-8:00 (or Friday 9:30-10:00) can be used by none of them, only one of them or both of them.
    054     * From the balancing point of the view, I believe it should be preferred to have one class starting from 7:30 and the
    055     * other one from 8:30.
    056     * <br><br>
    057     * So, after the histogram is computed, its values are increased by the given percentage (same reason as before, to
    058     * allow some space between the actual value and the limit, also not to make the limits for a department with 21 time
    059     * slots less strict than a department with 20 time slots etc.). The initial limits are than computed as these values
    060     * rounded upwards (1.90 results in 2).
    061     * <br><br>
    062     * Moreover, the value is increased only when the histogram value of a time slot is below the following value:
    063     * spread factor * (number of used time slots / number of all time slots). Is assures a department of at least the
    064     * given percentage more time slots than required, but does not provide an additional reward for above average
    065     * use of time slots based on 'required' times.
    066     * <br><br>
    067     * For example, the department 1286 will have the following limits (histogram increased by 20% (i.e., each value is 20%
    068     * higher) and rounded upwards):
    069     * <code><br>
    070     * 1: [1,0,1,0,1]<br>
    071     * 2: [1,0,1,0,1]<br>
    072     * 3: [1,1,1,1,1]<br>
    073     * 4: [1,1,1,1,1]<br>
    074     * 5: [2,2,2,1,2]<br>
    075     * 6: [2,2,2,1,2]<br>
    076     * 7: [1,1,1,2,1]<br>
    077     * 8: [1,1,1,2,1]<br>
    078     * 9: [1,1,1,1,1]<br>
    079     * 10:[1,1,1,1,1]<br>
    080     * 11:[1,1,1,1,1]<br>
    081     * 12:[1,1,1,1,1]<br>
    082     * 13:[1,1,1,2,1]<br>
    083     * 14:[1,1,1,2,1]<br>
    084     * 15:[1,1,1,1,1]<br>
    085     * 16:[1,1,1,1,1]<br>
    086     * 17:[1,1,1,1,1]<br>
    087     * 18:[1,1,1,1,1]<br>
    088     * 19:[1,0,1,0,1]<br>
    089     * 20:[1,0,1,0,1]<br>
    090     * 21:[0,0,0,0,0]<br>
    091     * </code><br>
    092     * The maximal penalty (i.e., the maximal number of half-hours which can be used above the pre-computed limits by a
    093     * department) of a constraint is used. Initially, the maximal penalty is set to zero. It is increased by one after
    094     * each time when the constraint causes the given number (e.g., 100) of un-assignments.
    095     * <br><br>
    096     * Also, the balancing penalty (the total number of half-hours over the initial limits) is computed and it can be
    097     * minimized during the search (soft constraint).
    098     * <br><br>
    099     * Parameters:
    100     * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
    101     * <tr><td>DepartmentSpread.SpreadFactor</td><td>{@link Double}</td><td>Initial allowance of the slots for a particular time (factor)<br>Allowed slots = ROUND(SpreadFactor * (number of requested slots / number of slots per day))</td></tr>
    102     * <tr><td>DepartmentSpread.Unassignments2Weaken</td><td>{@link Integer}</td><td>Increase the initial allowance when it causes the given number of unassignments</td></tr>
    103     * </table>
    104     *
    105     * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomáš Müller</a>
    106     * @version 1.0
    107     */
    108
    109    public class DepartmentSpreadConstraint extends Constraint {
    110        private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(DepartmentSpreadConstraint.class);
    111        private static java.text.DecimalFormat sDoubleFormat = new java.text.DecimalFormat("0.00",new java.text.DecimalFormatSymbols(Locale.US));
    112        private static java.text.DecimalFormat sIntFormat = new java.text.DecimalFormat("0",new java.text.DecimalFormatSymbols(Locale.US));
    113        private int iMaxCourses[][] = null;
    114        private int iCurrentPenalty = 0;
    115        private int iMaxAllowedPenalty = 0;
    116
    117        private String iDepartment = null;
    118        private boolean iInitialized = false;
    119        private int[][] iNrCourses = null;
    120        private Vector [][] iCourses = null;
    121        private double iSpreadFactor = 1.20;
    122        private int iUnassignmentsToWeaken = 250;
    123        private long iUnassignment = 0;
    124
    125        public DepartmentSpreadConstraint(DataProperties config, String department) {
    126            iDepartment = department;
    127            iSpreadFactor = config.getPropertyDouble("DepartmentSpread.SpreadFactor",iSpreadFactor);
    128            iUnassignmentsToWeaken = config.getPropertyInt("DepartmentSpread.Unassignments2Weaken", iUnassignmentsToWeaken);
    129            iNrCourses = new int[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.DAY_CODES.length];
    130            iCourses = new Vector[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.DAY_CODES.length];
    131            for (int i=0;i<iNrCourses.length;i++) {
    132                for (int j=0;j<Constants.DAY_CODES.length;j++) {
    133                    iNrCourses[i][j] = 0;
    134                    iCourses[i][j]=new Vector(10,5);
    135                }
    136            }
    137        }
    138
    139        /** Initialize constraint (to be called after all variables are added to this constraint) */
    140        public void init() {
    141            if (iInitialized) return;
    142            double histogramPerDay[][] = new double[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.DAY_CODES.length];
    143            iMaxCourses = new int[Constants.SLOTS_PER_DAY_NO_EVENINGS][Constants.DAY_CODES.length];
    144            for (int i=0;i<Constants.SLOTS_PER_DAY_NO_EVENINGS;i++)
    145                for (int j=0;j<Constants.DAY_CODES.length;j++)
    146                    histogramPerDay[i][j]=0.0;
    147            int totalUsedSlots = 0;
    148            for (Enumeration e=variables().elements();e.hasMoreElements();) {
    149                Lecture lecture = (Lecture)e.nextElement();
    150                Placement firstPlacement = (Placement)lecture.values().firstElement();
    151                if (firstPlacement!=null) {
    152                    totalUsedSlots += firstPlacement.getTimeLocation().getNrHalfHoursPerMeeting()*firstPlacement.getTimeLocation().getNrMeetings();
    153                }
    154                for (Enumeration e2=lecture.values().elements();e2.hasMoreElements();) {
    155                    Placement p = (Placement)e2.nextElement();
    156                    int firstSlot = p.getTimeLocation().getStartSlot();
    157                    if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) continue;
    158                    int endSlot = firstSlot+p.getTimeLocation().getNrHalfHoursPerMeeting()-1;
    159                    for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
    160                        int dayCode = p.getTimeLocation().getDayCode();
    161                        for (int j=0;j<Constants.DAY_CODES.length;j++) {
    162                            if ((dayCode & Constants.DAY_CODES[j])!=0) {
    163                                histogramPerDay[i][j] += 1.0 / lecture.values().size();
    164                            }
    165                        }
    166                    }
    167                }
    168            }
    169            //System.out.println("Histogram for department "+iDepartment+":");
    170            double threshold = iSpreadFactor*((double)totalUsedSlots/(5.0*Constants.SLOTS_PER_DAY_NO_EVENINGS));
    171            //System.out.println("Threshold["+iDepartment+"] = "+threshold);
    172            int totalAvailableSlots = 0;
    173            for (int i=0;i<Constants.SLOTS_PER_DAY_NO_EVENINGS;i++) {
    174                //System.out.println("  "+fmt(i+1)+": "+fmt(histogramPerDay[i]));
    175                for (int j=0;j<Constants.DAY_CODES.length;j++) {
    176                    iMaxCourses[i][j]=(int)(0.999+(histogramPerDay[i][j]<=threshold?iSpreadFactor*histogramPerDay[i][j]:histogramPerDay[i][j]));
    177                    totalAvailableSlots += iMaxCourses[i][j];
    178                }
    179            }
    180            iCurrentPenalty = 0;
    181            for (Enumeration e=variables().elements();e.hasMoreElements();) {
    182                Lecture lecture = (Lecture)e.nextElement();
    183                Placement placement = (Placement)lecture.getAssignment();
    184                if (placement==null) continue;
    185                int firstSlot = placement.getTimeLocation().getStartSlot();
    186                if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) continue;
    187                int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
    188                for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
    189                    for (int j=0;j<Constants.DAY_CODES.length;j++) {
    190                        int dayCode = Constants.DAY_CODES[j];
    191                        if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
    192                            iNrCourses[i][j]++;
    193                            iCourses[i][j].addElement(placement);
    194                            iCurrentPenalty += Math.max(0,iNrCourses[i][j]-iMaxCourses[i][j]);
    195                        }
    196                    }
    197                }
    198            }
    199            iMaxAllowedPenalty = iCurrentPenalty;
    200            //System.out.println("Initial penalty = "+fmt(iMaxAllowedPenalty));
    201            iInitialized = true;
    202        }
    203
    204        private Lecture getAdept(Placement placement, int[][] nrCourses, Set conflicts) {
    205            //sLogger.debug("  -- looking for an adept");
    206            Lecture adept = null;
    207            int improvement = 0;
    208            for (Enumeration e=variables().elements();e.hasMoreElements();) {
    209                Lecture lect = (Lecture)e.nextElement();
    210                if (lect.getAssignment()==null || lect.equals(placement.variable())) continue;
    211                if (conflicts.contains(lect.getAssignment())) continue;
    212                int imp = getPenaltyIfUnassigned((Placement)lect.getAssignment(),nrCourses);
    213                if (imp==0) continue;
    214                //sLogger.debug("    -- "+lect+" can decrease penalty by "+imp);
    215                if (adept==null || imp>improvement) {
    216                    adept = lect;
    217                    improvement = imp;
    218                }
    219            }
    220            //sLogger.debug("  -- adept "+adept+" selected, penalty will be decreased by "+improvement);
    221            return adept;
    222        }
    223
    224        private int getPenaltyIfUnassigned(Placement placement, int[][] nrCourses)  {
    225            int firstSlot = placement.getTimeLocation().getStartSlot();
    226            if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return 0;
    227            int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
    228            int penalty = 0;
    229            for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
    230                for (int j=0;j<Constants.DAY_CODES.length;j++) {
    231                    int dayCode = Constants.DAY_CODES[j];
    232                    if ((dayCode & placement.getTimeLocation().getDayCode()) != 0 &&
    233                        nrCourses[i][j]>iMaxCourses[i][j]) penalty ++;
    234                }
    235            }
    236            return penalty;
    237        }
    238
    239        private int tryUnassign(Placement placement, int[][] nrCourses) {
    240            //sLogger.debug("  -- trying to unassign "+placement);
    241            int firstSlot = placement.getTimeLocation().getStartSlot();
    242            if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return 0;
    243            int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
    244            int improvement = 0;
    245            for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
    246                for (int j=0;j<Constants.DAY_CODES.length;j++) {
    247                    int dayCode = Constants.DAY_CODES[j];
    248                    if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
    249                        if (nrCourses[i][j]>iMaxCourses[i][j]) improvement++;
    250                        nrCourses[i][j]--;
    251                    }
    252                }
    253            }
    254            //sLogger.debug("  -- penalty is decreased by "+improvement);
    255            return improvement;
    256        }
    257
    258        private int tryAssign(Placement placement, int[][] nrCourses) {
    259            //sLogger.debug("  -- trying to assign "+placement);
    260            int firstSlot = placement.getTimeLocation().getStartSlot();
    261            if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return 0;
    262            int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
    263            int penalty = 0;
    264            for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
    265                for (int j=0;j<Constants.DAY_CODES.length;j++) {
    266                    int dayCode = Constants.DAY_CODES[j];
    267                    if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
    268                        nrCourses[i][j]++;
    269                        if (nrCourses[i][j]>iMaxCourses[i][j]) penalty++;
    270                    }
    271                }
    272            }
    273            //sLogger.debug("  -- penalty is incremented by "+penalty);
    274            return penalty;
    275        }
    276
    277        public void computeConflicts(Value value, Set conflicts) {
    278            if (!iInitialized || iUnassignmentsToWeaken==0) return;
    279            Placement placement = (Placement)value;
    280            int penalty = iCurrentPenalty + getPenalty(placement);
    281            if (penalty<=iMaxAllowedPenalty) return;
    282            int firstSlot = placement.getTimeLocation().getStartSlot();
    283            if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return;
    284            int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
    285                //sLogger.debug("-- computing conflict for value "+value+" ... (penalty="+iCurrentPenalty+", penalty with the value="+penalty+", max="+iMaxAllowedPenalty+")");
    286                int[][] nrCourses = new int[iNrCourses.length][Constants.DAY_CODES.length];
    287                for (int i=0;i<iNrCourses.length;i++)
    288                    for (int j=0;j<Constants.DAY_CODES.length;j++)
    289                        nrCourses[i][j] = iNrCourses[i][j];
    290                tryAssign(placement, nrCourses);
    291                //sLogger.debug("  -- nrCurses="+fmt(nrCourses));
    292                for (Enumeration e=variables().elements();e.hasMoreElements();) {
    293                    Lecture lect = (Lecture)e.nextElement();
    294                    if (conflicts.contains(lect)) {
    295                        penalty -= tryUnassign((Placement)lect.getAssignment(), nrCourses);
    296                    }
    297                    if (penalty<=iMaxAllowedPenalty) return;
    298                }
    299                while (penalty>iMaxAllowedPenalty) {
    300                    Lecture lect = getAdept(placement, nrCourses, conflicts);
    301                    if (lect==null) break;
    302                    conflicts.add(lect.getAssignment());
    303                    //sLogger.debug("  -- conflict "+lect.getAssignment()+" added");
    304                    penalty -= tryUnassign((Placement)lect.getAssignment(), nrCourses);
    305                }
    306        }
    307
    308        public boolean inConflict(Value value) {
    309            if (!iInitialized || iUnassignmentsToWeaken==0) return false;
    310            Placement placement = (Placement)value;
    311                return getPenalty(placement)+iCurrentPenalty>iMaxAllowedPenalty;
    312        }
    313
    314        public boolean isConsistent(Value value1, Value value2) {
    315            if (!iInitialized || iUnassignmentsToWeaken==0) return true;
    316            Placement p1 = (Placement)value1;
    317            Placement p2 = (Placement)value2;
    318            if (!p1.getTimeLocation().hasIntersection(p2.getTimeLocation())) return true;
    319            int firstSlot = Math.max(p1.getTimeLocation().getStartSlot(),p2.getTimeLocation().getStartSlot());
    320            if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return true;
    321            int endSlot = Math.min(p1.getTimeLocation().getStartSlot()+p1.getTimeLocation().getNrHalfHoursPerMeeting()-1,p2.getTimeLocation().getStartSlot()+p2.getTimeLocation().getNrHalfHoursPerMeeting()-1);
    322                int penalty = 0;
    323                for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
    324                    for (int j=0;j<Constants.DAY_CODES.length;j++) {
    325                        int dayCode = Constants.DAY_CODES[j];
    326                        if ((dayCode & p1.getTimeLocation().getDayCode()) != 0 && (dayCode & p2.getTimeLocation().getDayCode()) != 0) {
    327                            penalty += Math.max(0,2-iMaxCourses[i][j]);
    328                        }
    329                    }
    330                }
    331                return (penalty<iMaxAllowedPenalty);
    332        }
    333
    334        private void weaken() {
    335            if (!iInitialized) return;
    336            if (iUnassignmentsToWeaken==0) return;
    337                iMaxAllowedPenalty ++;
    338        }
    339
    340        public void assigned(long iteration, Value value) {
    341            super.assigned(iteration, value);
    342            if (!iInitialized) return;
    343            Placement placement = (Placement)value;
    344            int firstSlot = placement.getTimeLocation().getStartSlot();
    345            if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return;
    346            int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
    347            for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
    348                for (int j=0;j<Constants.DAY_CODES.length;j++) {
    349                    int dayCode = Constants.DAY_CODES[j];
    350                    if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
    351                        iNrCourses[i][j]++;
    352                        if (iNrCourses[i][j]>iMaxCourses[i][j]) iCurrentPenalty++;
    353                        iCourses[i][j].addElement(value);
    354                    }
    355                }
    356            }
    357        }
    358
    359        public void unassigned(long iteration, Value value) {
    360            super.unassigned(iteration, value);
    361            if (!iInitialized) return;
    362            Placement placement = (Placement)value;
    363            int firstSlot = placement.getTimeLocation().getStartSlot();
    364            if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return;
    365            int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
    366            for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
    367                for (int j=0;j<Constants.DAY_CODES.length;j++) {
    368                    int dayCode = Constants.DAY_CODES[j];
    369                    if ((dayCode & placement.getTimeLocation().getDayCode()) != 0) {
    370                        if (iNrCourses[i][j]>iMaxCourses[i][j]) iCurrentPenalty--;
    371                        iNrCourses[i][j]--;
    372                        iCourses[i][j].removeElement(value);
    373                    }
    374                }
    375            }
    376        }
    377
    378        /** Increment unassignment counter */
    379        public void incUnassignmentCounter(Value value) {
    380            iUnassignment ++;
    381            if (iUnassignment%iUnassignmentsToWeaken==0)
    382                weaken();
    383        }
    384
    385        public String toString() {
    386            if (!iInitialized) return iDepartment+" (not initialized)";
    387            return iDepartment+" (p="+fmt(getPenalty())+", mx="+fmt(iMaxCourses)+", mp="+fmt(iMaxAllowedPenalty)+"): "+fmt(iNrCourses);
    388        }
    389
    390        /** Department balancing penalty for this department */
    391        public int getPenalty() {
    392            if (!iInitialized) return 0;
    393            return iCurrentPenalty;
    394        }
    395
    396        /** Department balancing penalty of the given placement */
    397        public int getPenalty(Placement placement) {
    398            if (!iInitialized) return 0;
    399            int firstSlot = placement.getTimeLocation().getStartSlot();
    400            if (firstSlot>=Constants.SLOTS_PER_DAY_NO_EVENINGS) return 0;
    401            int endSlot = firstSlot+placement.getTimeLocation().getNrHalfHoursPerMeeting()-1;
    402            int penalty = 0;
    403            for (int i=firstSlot;i<=Math.min(endSlot,Constants.SLOTS_PER_DAY_NO_EVENINGS-1);i++) {
    404                for (int j=0;j<Constants.DAY_CODES.length;j++) {
    405                    int dayCode = Constants.DAY_CODES[j];
    406                    if ((dayCode & placement.getTimeLocation().getDayCode()) != 0 &&
    407                        iNrCourses[i][j]>=iMaxCourses[i][j]) penalty ++;
    408                }
    409            }
    410            return penalty;
    411        }
    412
    413        private String fmt(double value) {
    414            return sDoubleFormat.format(value);
    415        }
    416
    417        private String fmt(double[] values) {
    418            if (values==null) return null;
    419            StringBuffer sb = new StringBuffer("[");
    420            for (int i=0;i<Math.min(5,values.length);i++)
    421                sb.append(i==0?"":",").append(sDoubleFormat.format(values[i]));
    422                sb.append("]");
    423                return sb.toString();
    424        }
    425
    426        private String fmt(double[][] values) {
    427            if (values==null) return null;
    428            StringBuffer sb = new StringBuffer("[");
    429            for (int i=0;i<values.length;i++) {
    430                /*double total = 0;
    431                for (int j=0;j<values[i].length;j++)
    432                    total += values[i][j];
    433                sb.append(i==0?"":",").append(fmt(total));*/
    434                sb.append(i==0?"":",").append(fmt(values[i]));
    435            }
    436            sb.append("]");
    437            return sb.toString();
    438        }
    439
    440        private String fmt(int value) {
    441            return sIntFormat.format(value);
    442        }
    443
    444        private String fmt(int[] values) {
    445            if (values==null) return null;
    446            StringBuffer sb = new StringBuffer("[");
    447            for (int i=0;i<Math.min(5,values.length);i++)
    448                sb.append(i==0?"":",").append(sIntFormat.format(values[i]));
    449                sb.append("]");
    450                return sb.toString();
    451        }
    452
    453        private String fmt(int[][] values) {
    454            if (values==null) return null;
    455            StringBuffer sb = new StringBuffer("[");
    456            for (int i=0;i<values.length;i++) {
    457                /*int total = 0;
    458                for (int j=0;j<values[i].length;j++)
    459                    total += values[i][j];
    460                sb.append(i==0?"":",").append(fmt(total));*/
    461                sb.append(i==0?"":",").append(fmt(values[i]));
    462            }
    463            sb.append("]");
    464            return sb.toString();
    465        }
    466    }