package ttsolver.heuristics;

import ifs.perturbations.*;
import ifs.util.*;
import ifs.model.*;
import ifs.solution.*;
import ttsolver.model.*;
import ttsolver.constraint.*;
import java.util.*;
import edu.purdue.smas.timetable.serverfwk.ParameterDefinition;

/**
 * Perturbation penalty computation.
 * <br><br>
 * In practise, the strategy for computing perturbations needs to be extended. For example, a change in time is usually 
 * much worse than a movement to a different classroom. The number of enrolled/involved students should also be taken 
 * into account. Another factor is whether the solution has already been published or not.
 * <br>
 * The priorities for evaluating perturbations are as follows. Before publishing timetable:<ul>
 * <li>minimize number of classes with time changes,
 * <li>minimize number of student conflicts,
 * <li>optimize satisfaction of problem soft constraints.
 * </ul><br>
 * After publishing the timetable (class time changes are not allowed):<ul>
 * <li>minimize number of additional (new) student conflicts,
 * <li>minimize number of students with time changes,
 * <li>minimize number of classes with time changes,
 * <li>optimize satisfaction of problem soft constraints.
 * </ul>
 * In both cases, the number of classes with room change is not significant at all. Before the timetable is published, 
 * minimizing the number of classes with time changes is the most important criteria for the MPP as long as it does not 
 * create too many additional student conflicts in the process. Therefore, as a compromise, the cost (in equivalent 
 * conflicts) of changing the time assigned to a class equals a number like 5% of the students enrolled in that class. 
 * Otherwise none of our other criteria would have any importance. 
 * <br><br>
 * Similar properties apply between other criteria as well. To fulfil all these needs we have crated a function (called 
 * perturbations penalty) which can be computed over a partial solution. This is a weighted sum of various perturbations 
 * criteria like the number of classes with time changes or the number of additional student conflicts. This 
 * perturbation penalty is added as an extra optimization criterion to the solution comparator and to value selection 
 * criterion, so we can also setup the weights between this perturbation penalty and other (initial) soft constraints.
 * <br><br>
 * Parameters:
 * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
 * <tr><td>Perturbations.DifferentPlacement</td><td>{@link Double}</td><td>Different value than initial is assigned</td></tr>
 * <tr><td>Perturbations.AffectedStudentWeight</td><td>{@link Double}</td><td>Number of students which are enrolled in a class which is placed to a different location than initial (a student can be included twice or more)</td></tr>
 * <tr><td>Perturbations.AffectedInstructorWeight</td><td>{@link Double}</td><td>Number of instructors which are assigned to classes which are placed to different locations than initial (an instructor can be included twice or more)</td></tr>
 * <tr><td>Perturbations.DifferentRoomWeight</td><td>{@link Double}</td><td>Number of classes which are placed to a different room than initial</td></tr>
 * <tr><td>Perturbations.DifferentBuildingWeight</td><td>{@link Double}</td><td>Number of classes which are placed to a different building than initial</td></tr>
 * <tr><td>Perturbations.DifferentTimeWeight</td><td>{@link Double}</td><td>Number of classes which are placed in a different time than initial</td></tr>
 * <tr><td>Perturbations.DifferentDayWeight</td><td>{@link Double}</td><td>Number of classes which are placed in a different days than initial</td></tr>
 * <tr><td>Perturbations.DifferentHourWeight</td><td>{@link Double}</td><td>Number of classes which are placed in a different hours than initial</td></tr>
 * <tr><td>Perturbations.DeltaStudentConflictsWeight</td><td>{@link Double}</td><td>Difference of student conflicts of classes assigned to current placements instead of initial placements. It is a difference between number of students conflicts which are in the initial solution and the current one. Student conflicts created by classes without initial placement are not taken into account</td></tr>
 * <tr><td>Perturbations.NewStudentConflictsWeight</td><td>{@link Double}</td><td>New created student conflicts -- particular students are taken into account. Student conflicts created by classes without initial placement are not taken into account</td></tr>
 * <tr><td>Perturbations.TooFarForInstructorsWeight</td><td>{@link Double}</td><td>New placement of a class is too far from the intial placement (instructor-wise). It is computed only when the class has an instructor assigned, moreover:<ul><li>0 < distance(currentPlacement,initialPlacement) <= 5  .. weight is taken once<li>5 < distance(currentPlacement,initialPlacement) <= 20 .. weight is taken twice<li>20 < distance(currentPlacement,initialPlacement)       .. weight is taken ten times</ul></td></tr>
 * <tr><td>Perturbations.TooFarForStudentsWeight</td><td>{@link Double}</td><td>New placement of a class is too far from the intial placement (instructor-student). It is weighted by the number of students enrolled in the class when distance(currentPlacement,initialPlacement) > 67</td></tr>
 * <tr><td>Perturbations.DeltaInstructorDistancePreferenceWeight</td><td>{@link Double}</td><td>Difference between number of instructor distance preferences of the initial (but maybe inconsistent) solution and the current solution. Instructor distance preferences of classes without initial placement are not taken into account</td></tr>
 * <tr><td>Perturbations.DeltaRoomPreferenceWeight</td><td>{@link Double}</td><td>Difference between room preferences of the initial and the current solution. Room preferences of classes without initial placement are not taken into account</td></tr>
 * <tr><td>Perturbations.DeltaTimePreferenceWeight</td><td>{@link Double}</td><td>Difference between time preferences of the initial and the current solution. Time preferences of classes without initial placement are not taken into account</td></tr>
 * <tr><td>Perturbations.AffectedStudentByTimeWeight</td><td>{@link Double}</td><td>Number of students which are enrolled in a class which is placed to a different time than initial </td></tr>
 * <tr><td>Perturbations.AffectedInstructorByTimeWeight</td><td>{@link Double}</td><td>Number of instructors which are assigned to classes which are placed to different time than initial</td></tr>
 * <tr><td>Perturbations.AffectedStudentByRoomWeight</td><td>{@link Double}</td><td>Number of students which are enrolled in a class which is placed to a different room than initial </td></tr>
 * <tr><td>Perturbations.AffectedInstructorByRoomWeight</td><td>{@link Double}</td><td>Number of instructors which are assigned to classes which are placed to different room than initial </td></tr>
 * <tr><td>Perturbations.AffectedStudentByBldgWeight</td><td>{@link Double}</td><td>Number of students which are enrolled in a class which is placed to a different building than initial</td></tr>
 * <tr><td>Perturbations.AffectedInstructorByBldgWeight</td><td>{@link Double}</td><td>Number of instructors which are assigned to classes which are placed to different building than initial </td></tr>
 * </table>
 *
 * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomas Muller</a>
 * @version 1.0
 */

public class UniversalPerturbationsCounter extends DefaultPerturbationsCounter {
    private double iDifferentPlacement = 1.0;
    private double iAffectedStudentWeight = 0.0;
    private double iAffectedInstructorWeight = 0.0;
    private double iAffectedStudentByTimeWeight = 0.0;
    private double iAffectedInstructorByTimeWeight = 0.0;
    private double iAffectedStudentByRoomWeight = 0.0;
    private double iAffectedInstructorByRoomWeight = 0.0;
    private double iAffectedStudentByBldgWeight = 0.0;
    private double iAffectedInstructorByBldgWeight = 0.0;
    private double iDifferentRoomWeight = 0.0;
    private double iDifferentBuildingWeight = 0.0;
    private double iDifferentTimeWeight = 0.0;
    private double iDifferentDayWeight = 0.0;
    private double iDifferentHourWeight = 0.0;
    private double iNewStudentConflictsWeight = 0.0;
    private double iDeltaStudentConflictsWeight = 0.0;
    private double iTooFarForInstructorsWeight = 0.0;
    private double iTooFarForStudentsWeight = 0.0;
    private double iDeltaInstructorDistancePreferenceWeight = 0.0;
    private double iDeltaRoomPreferenceWeight = 0.0;
    private double iDeltaTimePreferenceWeight = 0.0;
    private boolean iMPP = false;
    
    public UniversalPerturbationsCounter(DataProperties properties) {
        super(properties);
        iMPP = properties.getPropertyBoolean("General.MPP",false);
        iDifferentPlacement = properties.getPropertyDouble("Perturbations.DifferentPlacement",iDifferentPlacement);
        iAffectedStudentWeight = properties.getPropertyDouble("Perturbations.AffectedStudentWeight",iAffectedStudentWeight);
        iAffectedInstructorWeight = properties.getPropertyDouble("Perturbations.AffectedInstructorWeight",iAffectedInstructorWeight);
        iAffectedStudentByTimeWeight = properties.getPropertyDouble("Perturbations.AffectedStudentByTimeWeight",iAffectedStudentByTimeWeight);
        iAffectedInstructorByTimeWeight = properties.getPropertyDouble("Perturbations.AffectedInstructorByTimeWeight",iAffectedInstructorByTimeWeight);
        iAffectedStudentByRoomWeight = properties.getPropertyDouble("Perturbations.AffectedStudentByRoomWeight",iAffectedStudentByRoomWeight);
        iAffectedInstructorByRoomWeight = properties.getPropertyDouble("Perturbations.AffectedInstructorByRoomWeight",iAffectedInstructorByRoomWeight);
        iAffectedStudentByBldgWeight = properties.getPropertyDouble("Perturbations.AffectedStudentByBldgWeight",iAffectedStudentByBldgWeight);
        iAffectedInstructorByBldgWeight = properties.getPropertyDouble("Perturbations.AffectedInstructorByBldgWeight",iAffectedInstructorByBldgWeight);
        iDifferentRoomWeight = properties.getPropertyDouble("Perturbations.DifferentRoomWeight",iDifferentRoomWeight);
        iDifferentBuildingWeight = properties.getPropertyDouble("Perturbations.DifferentBuildingWeight",iDifferentBuildingWeight);
        iDifferentTimeWeight = properties.getPropertyDouble("Perturbations.DifferentTimeWeight",iDifferentTimeWeight);
        iDifferentDayWeight = properties.getPropertyDouble("Perturbations.DifferentDayWeight",iDifferentDayWeight);
        iDifferentHourWeight = properties.getPropertyDouble("Perturbations.DifferentHourWeight",iDifferentHourWeight);
        iDeltaStudentConflictsWeight = properties.getPropertyDouble("Perturbations.DeltaStudentConflictsWeight",iDeltaStudentConflictsWeight);
        iNewStudentConflictsWeight = properties.getPropertyDouble("Perturbations.NewStudentConflictsWeight",iNewStudentConflictsWeight);
        iTooFarForInstructorsWeight = properties.getPropertyDouble("Perturbations.TooFarForInstructorsWeight",iTooFarForInstructorsWeight);
        iTooFarForStudentsWeight = properties.getPropertyDouble("Perturbations.TooFarForStudentsWeight",iTooFarForStudentsWeight);
        iDeltaInstructorDistancePreferenceWeight = properties.getPropertyDouble("Perturbations.DeltaInstructorDistancePreferenceWeight",iDeltaInstructorDistancePreferenceWeight);
        iDeltaRoomPreferenceWeight = properties.getPropertyDouble("Perturbations.DeltaRoomPreferenceWeight",iDeltaRoomPreferenceWeight);
        iDeltaTimePreferenceWeight = properties.getPropertyDouble("Perturbations.DeltaTimePreferenceWeight",iDeltaTimePreferenceWeight);
    }

    public static Vector parameters() {
        Vector ret = new FastVector();
        
        ParameterDefinition.Dependency mppDep = new ParameterDefinition.Dependency("General.MPP","true");
        
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.DifferentPlacement", "Different placement than initial", ParameterDefinition.TYPE_DOUBLE, "1.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.AffectedStudentWeight", "Number of students", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.AffectedInstructorWeight", "An instructor is assigned", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.AffectedStudentByTimeWeight", "Number of students (time change)", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.AffectedInstructorByTimeWeight", "An instructor is assigned (time change)", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.AffectedStudentByRoomWeight", "Number of students (room change)", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.AffectedInstructorByRoomWeight", "An instructor is assigned (room change)", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.AffectedStudentByBldgWeight", "Number of students (building change)", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.AffectedInstructorByBldgWeight", "An instructor is assigned (building change)", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.DifferentRoomWeight", "Different room used", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.DifferentBuildingWeight", "Different building used", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.DifferentTimeWeight", "Different time used", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.DifferentDayWeight", "Different day(s) used", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.DifferentHourWeight", "Different hour(s) used", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.DeltaStudentConflictsWeight", "Delta of student conflicts", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.NewStudentConflictsWeight", "New students in conflict", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.TooFarForInstructorsWeight", "New placement too far from initial (fot instructors)", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.TooFarForStudentsWeight", "New placement too far from initial (for students)", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.DeltaInstructorDistancePreferenceWeight", "Delta of instructor distance preferences", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.DeltaRoomPreferenceWeight", "Delta of room preferences", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Perturbations - Weights","Perturbations.DeltaTimePreferenceWeight", "Delta of time preferences", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(mppDep));

        return ret;
    }
    
    protected double getPenalty(Value assignedValue, Value initialValue) {
        // assigned and initial value of the same lecture
        // assigned might be null
        Lecture lecture = (Lecture)initialValue.variable();
        Placement assignedPlacement = (assignedValue==null?null:(Placement)assignedValue);
        Placement initialPlacement = (Placement)initialValue;
        double penalty = 0.0;
        if (iDifferentPlacement!=0.0)
            penalty += iDifferentPlacement;
        if (iAffectedStudentWeight!=0.0)
            penalty += iAffectedStudentWeight*lecture.countStudents();
        if (iAffectedInstructorWeight!=0.0 && initialPlacement.getInstructorId()!=null)
            penalty += iAffectedInstructorWeight;
        if (assignedValue!=null) {
            if ((iDifferentRoomWeight!=0.0 || iAffectedInstructorByRoomWeight!=0.0 || iAffectedStudentByRoomWeight!=0.0) && !initialPlacement.getRoomId().equals(assignedPlacement.getRoomId())) {
                penalty += iDifferentRoomWeight;
                if (initialPlacement.getInstructorId()!=null) penalty += iAffectedInstructorByRoomWeight;
                penalty += iAffectedStudentByRoomWeight * lecture.countStudents();
            } if ((iDifferentBuildingWeight!=0.0 || iAffectedInstructorByBldgWeight!=0.0 || iAffectedStudentByBldgWeight!=0.0) && !initialPlacement.getBuildingId().equals(assignedPlacement.getBuildingId())) {
                penalty += iDifferentBuildingWeight;
                if (initialPlacement.getInstructorId()!=null) penalty += iAffectedInstructorByBldgWeight;
                penalty += iAffectedStudentByBldgWeight * lecture.countStudents();
            } if ((iDifferentTimeWeight!=0.0 || iAffectedInstructorByTimeWeight!=0.0 || iAffectedStudentByTimeWeight!=0.0) && !initialPlacement.getTimeLocation().equals(assignedPlacement.getTimeLocation())) {
                penalty += iDifferentTimeWeight;
                if (initialPlacement.getInstructorId()!=null) penalty += iAffectedInstructorByTimeWeight;
                penalty += iAffectedStudentByTimeWeight * lecture.countStudents();
            } if (iDifferentDayWeight!=0.0 && initialPlacement.getTimeLocation().getDayCode()!=assignedPlacement.getTimeLocation().getDayCode())
                penalty += iDifferentDayWeight;
            if (iDifferentHourWeight!=0.0 && initialPlacement.getTimeLocation().getTimeCode()!=assignedPlacement.getTimeLocation().getTimeCode())
                penalty += iDifferentHourWeight;
            if ((iTooFarForInstructorsWeight!=0.0 || iTooFarForStudentsWeight!=0.0) && !initialPlacement.getTimeLocation().equals(assignedPlacement.getTimeLocation())) {
                double distance = Placement.getDistance(initialPlacement, assignedPlacement);
                if (initialPlacement.getInstructorId()!=null && iTooFarForInstructorsWeight!=0.0) {
                    if (distance>0.0 && distance<=5.0) {
                        penalty += iTooFarForInstructorsWeight;
                    } else if (distance>5.0 && distance<=20.0) {
                        penalty += 2*iTooFarForInstructorsWeight;
                    } else if (distance>20.0) {
                        penalty += 10*iTooFarForInstructorsWeight;
                    }
                }
                if (iTooFarForStudentsWeight!=0.0 && distance > 67.0)
                    penalty += iTooFarForStudentsWeight*lecture.countStudents();
            }
            if (iDeltaStudentConflictsWeight!=0.0) {
                int newStudentConflicts = lecture.countStudentConflicts(assignedValue);
                int oldStudentConflicts = lecture.countInitialStudentConflicts();
                penalty += iDeltaStudentConflictsWeight * (newStudentConflicts-oldStudentConflicts);
            }
            if (iNewStudentConflictsWeight!=0.0) {
                Vector newStudentConflicts = lecture.conflictStudents(assignedValue);
                Set initialStudentConflicts = lecture.initialStudentConflicts();
                for (Enumeration e=newStudentConflicts.elements();e.hasMoreElements();)
                    if (!initialStudentConflicts.contains(e.nextElement())) penalty += iNewStudentConflictsWeight;
            }
            if (iDeltaTimePreferenceWeight!=0.0) {
                penalty += iDeltaTimePreferenceWeight * (assignedPlacement.getTimeLocation().getPreference() - initialPlacement.getTimeLocation().getPreference());
            }
            if (iDeltaRoomPreferenceWeight!=0.0) {
                penalty += iDeltaRoomPreferenceWeight * (assignedPlacement.getRoomLocation().getPreference() - initialPlacement.getRoomLocation().getPreference());
            }
            if (iDeltaInstructorDistancePreferenceWeight!=0.0) {
                InstructorConstraint ic = lecture.getInstructorConstraint();
                if (ic!=null) for (Enumeration e=ic.variables().elements();e.hasMoreElements();) {
                    Lecture lect = (Lecture)e.nextElement();
                    if (lect.equals(lecture)) continue;
                    int initialPreference = (lect.getInitialAssignment()==null?0:InstructorConstraint.getDistancePreference(initialPlacement,(Placement)lect.getInitialAssignment()));
                    int assignedPreference = (lect.getAssignment()==null?0:InstructorConstraint.getDistancePreference(assignedPlacement,(Placement)lect.getAssignment()));
                    penalty += iDeltaInstructorDistancePreferenceWeight * (assignedPreference - initialPreference);
                }
            }
        }
        return penalty;
    }
    
    public void getInfo(Dictionary info, Solution solution) {
        super.getInfo(info, solution);
        if (!iMPP) return;
        int perts = 0;
        long affectedStudents=0;
        int affectedInstructors=0;
        long affectedStudentsByTime=0;
        int affectedInstructorsByTime=0;
        long affectedStudentsByRoom=0;
        int affectedInstructorsByRoom=0;
        long affectedStudentsByBldg=0;
        int affectedInstructorsByBldg=0;
        int differentRoom=0;
        int differentBuilding=0;
        int differentTime=0;
        int differentDay=0;
        int differentHour=0;
        int tooFarForInstructors=0;
        int tooFarForStudents=0;
        int deltaStudentConflicts=0;
        int newStudentConflicts=0;
        int deltaTimePreferences=0;
        int deltaRoomPreferences=0;
        int deltaInstructorDistancePreferences=0;
        for (Enumeration e=solution.getModel().perturbVariables().elements();e.hasMoreElements();) {
            Variable variable = (Variable)e.nextElement();
            if (variable.getAssignment()==null || variable.getInitialAssignment()==null || variable.getAssignment().equals(variable.getInitialAssignment())) continue;
            perts++;
            Lecture lecture = (Lecture)variable ;
            Placement assignedPlacement = (Placement)variable.getAssignment();
            Placement initialPlacement = (Placement)variable.getInitialAssignment();
            affectedStudents += lecture.countStudents();
            if (initialPlacement.getInstructorId()!=null) affectedInstructors++;
            if (!initialPlacement.getRoomId().equals(assignedPlacement.getRoomId())) {
                differentRoom++;
                if (initialPlacement.getInstructorId()!=null) affectedInstructorsByRoom++;
                affectedStudentsByRoom += lecture.countStudents();
            }
            if (!initialPlacement.getBuildingId().equals(assignedPlacement.getBuildingId())) {
                differentBuilding++;
                if (initialPlacement.getInstructorId()!=null) affectedInstructorsByBldg++;
                affectedStudentsByBldg += lecture.countStudents();
            }
            if (!initialPlacement.getTimeLocation().equals(assignedPlacement.getTimeLocation())) {
                differentTime++;
                if (initialPlacement.getInstructorId()!=null) affectedInstructorsByTime++;
                affectedStudentsByTime += lecture.countStudents();
            }
            if (initialPlacement.getTimeLocation().getDayCode()!=assignedPlacement.getTimeLocation().getDayCode()) differentDay++;
            if (initialPlacement.getTimeLocation().getTimeCode()!=assignedPlacement.getTimeLocation().getTimeCode()) differentHour++;
            if (!initialPlacement.getTimeLocation().equals(assignedPlacement.getTimeLocation())) {
                double distance = Placement.getDistance(initialPlacement, assignedPlacement);
                if (initialPlacement.getInstructorId()!=null) {
                    if (distance>0.0 && distance<=5.0) tooFarForInstructors+=1;
                    else if (distance>5.0 && distance<=20.0) tooFarForInstructors+=2;
                    else if (distance>20.0) tooFarForInstructors+=10;
                }
                if (distance > 67.0) tooFarForStudents+=lecture.countStudents();
            }
            deltaStudentConflicts += lecture.countStudentConflicts(assignedPlacement) - lecture.countInitialStudentConflicts();
            Vector newStudentConflictsSet = lecture.conflictStudents(assignedPlacement);
            Set initialStudentConflicts = lecture.initialStudentConflicts();
            for (Enumeration e1=newStudentConflictsSet.elements();e1.hasMoreElements();)
                if (!initialStudentConflicts.contains(e1.nextElement())) newStudentConflicts++;
            deltaTimePreferences += assignedPlacement.getTimeLocation().getPreference() - initialPlacement.getTimeLocation().getPreference();
            deltaRoomPreferences += assignedPlacement.getRoomLocation().getPreference() - initialPlacement.getRoomLocation().getPreference();
            InstructorConstraint ic = lecture.getInstructorConstraint();
            if (ic!=null) for (Enumeration e1=ic.variables().elements();e1.hasMoreElements();) {
                Lecture lect = (Lecture)e1.nextElement();
                if (lect.equals(lecture)) continue;
                int initialPreference = (lect.getInitialAssignment()==null?0:InstructorConstraint.getDistancePreference(initialPlacement,(Placement)lect.getInitialAssignment()));
                int assignedPreference = (lect.getAssignment()==null?0:InstructorConstraint.getDistancePreference(assignedPlacement,(Placement)lect.getAssignment()));
                deltaInstructorDistancePreferences += assignedPreference - initialPreference;
            }
        }
        info.put("Perturbations: Different placement", String.valueOf(perts)+" (weighted "+sDoubleFormat.format(iDifferentPlacement*perts)+")");
        info.put("Perturbations: Number of affected students", String.valueOf(affectedStudents)+" (weighted "+sDoubleFormat.format(iAffectedStudentWeight*affectedStudents)+")");
        info.put("Perturbations: Number of affected instructors", String.valueOf(affectedInstructors)+" (weighted "+sDoubleFormat.format(iAffectedInstructorWeight*affectedInstructors)+")");
        info.put("Perturbations: Number of affected students (time change)", String.valueOf(affectedStudentsByTime)+" (weighted "+sDoubleFormat.format(iAffectedStudentByTimeWeight*affectedStudentsByTime)+")");
        info.put("Perturbations: Number of affected instructors (time change)", String.valueOf(affectedInstructorsByTime)+" (weighted "+sDoubleFormat.format(iAffectedInstructorByTimeWeight*affectedInstructorsByTime)+")");
        info.put("Perturbations: Number of affected students (room change)", String.valueOf(affectedStudentsByRoom)+" (weighted "+sDoubleFormat.format(iAffectedStudentByRoomWeight*affectedStudentsByRoom)+")");
        info.put("Perturbations: Number of affected instructors (room change)", String.valueOf(affectedInstructorsByRoom)+" (weighted "+sDoubleFormat.format(iAffectedInstructorByRoomWeight*affectedInstructorsByRoom)+")");
        info.put("Perturbations: Number of affected students (bldg change)", String.valueOf(affectedStudentsByBldg)+" (weighted "+sDoubleFormat.format(iAffectedStudentByBldgWeight*affectedStudentsByBldg)+")");
        info.put("Perturbations: Number of affected instructors (bldg change)", String.valueOf(affectedInstructorsByBldg)+" (weighted "+sDoubleFormat.format(iAffectedInstructorByBldgWeight*affectedInstructorsByBldg)+")");
        info.put("Perturbations: Different room", String.valueOf(differentRoom)+" (weighted "+sDoubleFormat.format(iDifferentRoomWeight*differentRoom)+")");
        info.put("Perturbations: Different building", String.valueOf(differentBuilding)+" (weighted "+sDoubleFormat.format(iDifferentBuildingWeight*differentBuilding)+")");
        info.put("Perturbations: Different time", String.valueOf(differentTime)+" (weighted "+sDoubleFormat.format(iDifferentTimeWeight*differentTime)+")");
        info.put("Perturbations: Different day", String.valueOf(differentDay)+" (weighted "+sDoubleFormat.format(iDifferentDayWeight*differentDay)+")");
        info.put("Perturbations: Different hour", String.valueOf(differentHour)+" (weighted "+sDoubleFormat.format(iDifferentHourWeight*differentHour)+")");
        info.put("Perturbations: New placement too far from initial (for instructors)", String.valueOf(tooFarForInstructors)+" (weighted "+sDoubleFormat.format(iTooFarForInstructorsWeight*tooFarForInstructors)+")");
        info.put("Perturbations: New placement too far from initial (for students)", String.valueOf(tooFarForStudents)+" (weighted "+sDoubleFormat.format(iTooFarForStudentsWeight*tooFarForStudents)+")");
        info.put("Perturbations: Delta student conflicts", String.valueOf(deltaStudentConflicts)+" (weighted "+sDoubleFormat.format(iDeltaStudentConflictsWeight*deltaStudentConflicts)+")");
        info.put("Perturbations: New student conflicts", String.valueOf(newStudentConflicts)+" (weighted "+sDoubleFormat.format(iNewStudentConflictsWeight*newStudentConflicts)+")");
        info.put("Perturbations: Delta time preferences", String.valueOf(deltaTimePreferences)+" (weighted "+sDoubleFormat.format(iDeltaTimePreferenceWeight*deltaTimePreferences)+")");
        info.put("Perturbations: Delta room preferences", String.valueOf(deltaRoomPreferences)+" (weighted "+sDoubleFormat.format(iDeltaRoomPreferenceWeight*deltaRoomPreferences)+")");
        info.put("Perturbations: Delta instructor distance preferences", String.valueOf(deltaInstructorDistancePreferences)+" (weighted "+sDoubleFormat.format(iDeltaInstructorDistancePreferenceWeight*deltaInstructorDistancePreferences)+")");
    }
    
    public Hashtable getCompactInfo(Solution solution, boolean includeZero, boolean weighted) {
        Hashtable info = new Hashtable();
        if (!iMPP) return info;
        int perts = 0;
        long affectedStudents=0;
        int affectedInstructors=0;
        long affectedStudentsByTime=0;
        int affectedInstructorsByTime=0;
        long affectedStudentsByRoom=0;
        int affectedInstructorsByRoom=0;
        long affectedStudentsByBldg=0;
        int affectedInstructorsByBldg=0;
        int differentRoom=0;
        int differentBuilding=0;
        int differentTime=0;
        int differentDay=0;
        int differentHour=0;
        int tooFarForInstructors=0;
        int tooFarForStudents=0;
        int deltaStudentConflicts=0;
        int newStudentConflicts=0;
        int deltaTimePreferences=0;
        int deltaRoomPreferences=0;
        int deltaInstructorDistancePreferences=0;
        for (Enumeration e=solution.getModel().perturbVariables().elements();e.hasMoreElements();) {
            Variable variable = (Variable)e.nextElement();
            if (variable.getAssignment()==null || variable.getInitialAssignment()==null || variable.getAssignment().equals(variable.getInitialAssignment())) continue;
            perts++;
            Lecture lecture = (Lecture)variable ;
            Placement assignedPlacement = (Placement)variable.getAssignment();
            Placement initialPlacement = (Placement)variable.getInitialAssignment();
            affectedStudents += lecture.countStudents();
            if (initialPlacement.getInstructorId()!=null) affectedInstructors++;
            if (!initialPlacement.getRoomId().equals(assignedPlacement.getRoomId())) {
                differentRoom++;
                if (initialPlacement.getInstructorId()!=null) affectedInstructorsByRoom++;
                affectedStudentsByRoom += lecture.countStudents();
            }
            if (!initialPlacement.getBuildingId().equals(assignedPlacement.getBuildingId())) {
                differentBuilding++;
                if (initialPlacement.getInstructorId()!=null) affectedInstructorsByBldg++;
                affectedStudentsByBldg += lecture.countStudents();
            }
            if (!initialPlacement.getTimeLocation().equals(assignedPlacement.getTimeLocation())) {
                differentTime++;
                if (initialPlacement.getInstructorId()!=null) affectedInstructorsByTime++;
                affectedStudentsByTime += lecture.countStudents();
            }
            if (initialPlacement.getTimeLocation().getDayCode()!=assignedPlacement.getTimeLocation().getDayCode()) differentDay++;
            if (initialPlacement.getTimeLocation().getTimeCode()!=assignedPlacement.getTimeLocation().getTimeCode()) differentHour++;
            if (!initialPlacement.getTimeLocation().equals(assignedPlacement.getTimeLocation())) {
                double distance = Placement.getDistance(initialPlacement, assignedPlacement);
                if (initialPlacement.getInstructorId()!=null) {
                    if (distance>0.0 && distance<=5.0) tooFarForInstructors+=1;
                    else if (distance>5.0 && distance<=20.0) tooFarForInstructors+=2;
                    else if (distance>20.0) tooFarForInstructors+=10;
                }
                if (distance > 67.0) tooFarForStudents+=lecture.countStudents();
            }
            deltaStudentConflicts += lecture.countStudentConflicts(assignedPlacement) - lecture.countInitialStudentConflicts();
            Vector newStudentConflictsSet = lecture.conflictStudents(assignedPlacement);
            Set initialStudentConflicts = lecture.initialStudentConflicts();
            for (Enumeration e1=newStudentConflictsSet.elements();e1.hasMoreElements();)
                if (!initialStudentConflicts.contains(e1.nextElement())) newStudentConflicts++;
            deltaTimePreferences += assignedPlacement.getTimeLocation().getPreference() - initialPlacement.getTimeLocation().getPreference();
            deltaRoomPreferences += assignedPlacement.getRoomLocation().getPreference() - initialPlacement.getRoomLocation().getPreference();
            InstructorConstraint ic = lecture.getInstructorConstraint();
            if (ic!=null) for (Enumeration e1=ic.variables().elements();e1.hasMoreElements();) {
                Lecture lect = (Lecture)e1.nextElement();
                if (lect.equals(lecture)) continue;
                int initialPreference = (lect.getInitialAssignment()==null?0:InstructorConstraint.getDistancePreference(initialPlacement,(Placement)lect.getInitialAssignment()));
                int assignedPreference = (lect.getAssignment()==null?0:InstructorConstraint.getDistancePreference(assignedPlacement,(Placement)lect.getAssignment()));
                deltaInstructorDistancePreferences += assignedPreference - initialPreference;
            }
        }
        if (includeZero || iDifferentPlacement!=0.0)
            info.put("Different placement", new Double(weighted?iDifferentPlacement*perts:perts));
        if (includeZero || iAffectedStudentWeight!=0.0)
            info.put("Affected students", new Double(weighted?iAffectedStudentWeight*affectedStudents:affectedStudents));
        if (includeZero || iAffectedInstructorWeight!=0.0)
            info.put("Affected instructors", new Double(weighted?iAffectedInstructorWeight*affectedInstructors:affectedInstructors));
        if (includeZero || iAffectedStudentByTimeWeight!=0.0)
            info.put("Affected students [time]", new Double(weighted?iAffectedStudentByTimeWeight*affectedStudentsByTime:affectedStudentsByTime));
        if (includeZero || iAffectedInstructorByTimeWeight!=0.0)
            info.put("Affected instructors [time]", new Double(weighted?iAffectedInstructorByTimeWeight*affectedInstructorsByTime:affectedInstructorsByTime));
        if (includeZero || iAffectedStudentByRoomWeight!=0.0)
            info.put("Affected students [room]", new Double(weighted?iAffectedStudentByRoomWeight*affectedStudentsByRoom:affectedStudentsByRoom));
        if (includeZero || iAffectedInstructorByRoomWeight!=0.0)
            info.put("Affected instructors [room]", new Double(weighted?iAffectedInstructorByRoomWeight*affectedInstructorsByRoom:affectedInstructorsByRoom));
        if (includeZero || iAffectedStudentByBldgWeight!=0.0)
            info.put("Affected students [bldg]", new Double(weighted?iAffectedStudentByBldgWeight*affectedStudentsByBldg:affectedStudentsByBldg));
        if (includeZero || iAffectedInstructorByBldgWeight!=0.0)
            info.put("Affected instructors [bldg]", new Double(weighted?iAffectedInstructorByBldgWeight*affectedInstructorsByBldg:affectedInstructorsByBldg));
        if (includeZero || iDifferentRoomWeight!=0.0)
            info.put("Different room", new Double(weighted?iDifferentRoomWeight*differentRoom:differentRoom));
        if (includeZero || iDifferentBuildingWeight!=0.0)
            info.put("Different building", new Double(weighted?iDifferentBuildingWeight*differentBuilding:differentBuilding));
        if (includeZero || iDifferentTimeWeight!=0.0)
            info.put("Different time", new Double(weighted?iDifferentTimeWeight*differentTime:differentTime));
        if (includeZero || iDifferentDayWeight!=0.0)
            info.put("Different day", new Double(weighted?iDifferentDayWeight*differentDay:differentDay));
        if (includeZero || iDifferentHourWeight!=0.0)
            info.put("Different hour", new Double(weighted?iDifferentHourWeight*differentHour:differentHour));
        if (includeZero || iTooFarForInstructorsWeight!=0.0)
            info.put("New placement too far for initial [instructors]", new Double(weighted?iTooFarForInstructorsWeight*tooFarForInstructors:tooFarForInstructors));
        if (includeZero || iTooFarForStudentsWeight!=0.0)
            info.put("New placement too far for initial [students]", new Double(weighted?iTooFarForStudentsWeight*tooFarForStudents:tooFarForStudents));
        if (includeZero || iDeltaStudentConflictsWeight!=0.0)
            info.put("Delta student conflicts", new Double(weighted?iDeltaStudentConflictsWeight*deltaStudentConflicts:deltaStudentConflicts));
        if (includeZero || iNewStudentConflictsWeight!=0.0)
            info.put("New student conflicts", new Double(weighted?iNewStudentConflictsWeight*newStudentConflicts:newStudentConflicts));
        if (includeZero || iDeltaTimePreferenceWeight!=0.0)
            info.put("Delta time preferences", new Double(weighted?iDeltaTimePreferenceWeight*deltaTimePreferences:deltaTimePreferences));
        if (includeZero || iDeltaRoomPreferenceWeight!=0.0)
            info.put("Delta room preferences", new Double(weighted?iDeltaRoomPreferenceWeight*deltaRoomPreferences:deltaRoomPreferences));
        if (includeZero || iDeltaInstructorDistancePreferenceWeight!=0.0)
            info.put("Delta instructor distance preferences", new Double(weighted?iDeltaInstructorDistancePreferenceWeight*deltaInstructorDistancePreferences:deltaInstructorDistancePreferences));
        return info;
    }    
}
