001    package net.sf.cpsolver.exam.heuristics;
002    
003    import java.util.Enumeration;
004    import java.util.HashSet;
005    import java.util.Set;
006    
007    import net.sf.cpsolver.exam.model.Exam;
008    import net.sf.cpsolver.exam.model.ExamModel;
009    import net.sf.cpsolver.exam.model.ExamPeriodPlacement;
010    import net.sf.cpsolver.exam.model.ExamPlacement;
011    import net.sf.cpsolver.exam.neighbours.ExamSimpleNeighbour;
012    import net.sf.cpsolver.ifs.heuristics.NeighbourSelection;
013    import net.sf.cpsolver.ifs.model.Neighbour;
014    import net.sf.cpsolver.ifs.solution.Solution;
015    import net.sf.cpsolver.ifs.solver.Solver;
016    import net.sf.cpsolver.ifs.util.DataProperties;
017    import net.sf.cpsolver.ifs.util.Progress;
018    
019    
020    import org.apache.log4j.Logger;
021    /**
022     * Initial solution construction heuristics.
023     * <br><br>
024     * While there are exams that are still not assigned:
025     * <ul>
026     * <li>The worst unassigned exam is selected (using {@link Exam#compareTo(Object)})
027     * <li>The best period that does not break any hard constraint and
028     * for which there is a room assignment available is selected together with 
029     * the set the best available rooms for the exam in the best period
030     * (computed using {@link Exam#findBestAvailableRooms(ExamPeriodPlacement)}).
031     * </ul>
032     * <br><br>
033     * If problem property ExamConstruction.CheckLocalOptimality is true,
034     * local (time) optimality is enforced at the end of this phase. During this
035     * procedure, for each exam, it tries to change the period of the exam
036     * so that the time cost is lower (see {@link ExamPlacement#getTimeCost()}),
037     * but no hard constraint is violated. The problem is considered locally 
038     * optimal if there is no such move. 
039     * <br><br>
040     * 
041     * @version
042     * ExamTT 1.1 (Examination Timetabling)<br>
043     * Copyright (C) 2008 Tomáš Müller<br>
044     * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
045     * Lazenska 391, 76314 Zlin, Czech Republic<br>
046     * <br>
047     * This library is free software; you can redistribute it and/or
048     * modify it under the terms of the GNU Lesser General Public
049     * License as published by the Free Software Foundation; either
050     * version 2.1 of the License, or (at your option) any later version.
051     * <br><br>
052     * This library is distributed in the hope that it will be useful,
053     * but WITHOUT ANY WARRANTY; without even the implied warranty of
054     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
055     * Lesser General Public License for more details.
056     * <br><br>
057     * You should have received a copy of the GNU Lesser General Public
058     * License along with this library; if not, write to the Free Software
059     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
060     */
061    public class ExamConstruction implements NeighbourSelection {
062        private static Logger sLog = Logger.getLogger(ExamConstruction.class);
063        private HashSet iAssignments = new HashSet();
064        private boolean iCheckLocalOptimality = false;
065        private HashSet iSkip = new HashSet();
066        private Progress iProgress = null;
067        private boolean iActive;
068    
069        /**
070         * Constructor
071         * @param properties problem properties
072         */
073        public ExamConstruction(DataProperties properties) {
074            iCheckLocalOptimality = properties.getPropertyBoolean("ExamConstruction.CheckLocalOptimality", iCheckLocalOptimality);
075        }
076        
077        /**
078         * Initialization
079         */
080        public void init(Solver solver) {
081            iSkip.clear();
082            solver.setUpdateProgress(false);
083            iProgress = Progress.getInstance(solver.currentSolution().getModel());
084            iActive = false;
085        }
086        
087        /**
088         * Find a new assignment of one of the assigned exams that improves
089         * the time cost {@link ExamPlacement#getTimeCost()} and for which 
090         * there is a set of available rooms {@link Exam#findBestAvailableRooms(ExamPeriodPlacement)}.
091         * Return null, if there is no such assignment (the problem is considered
092         * locally optimal).
093         */
094        public Neighbour checkLocalOptimality(ExamModel model) {
095            if (iCheckLocalOptimality) {
096                for (Enumeration e=model.assignedVariables().elements();e.hasMoreElements();) {
097                    Exam exam = (Exam)e.nextElement();
098                    ExamPlacement current = (ExamPlacement)exam.getAssignment(); 
099                    if (current.getTimeCost()<=0) continue;
100                    ExamPlacement best = null;
101                    for (Enumeration f=exam.getPeriodPlacements().elements();f.hasMoreElements();) {
102                        ExamPeriodPlacement period = (ExamPeriodPlacement)f.nextElement();
103                        if (exam.countStudentConflicts(period)>0) {
104                            if (iAssignments.contains(exam.getId()+":"+period.getIndex())) continue;
105                        }
106                        if (!exam.checkDistributionConstraints(period)) continue;
107                        ExamPlacement placement = new ExamPlacement(exam, period, null);
108                        if (best==null || best.getTimeCost()>placement.getTimeCost()) {
109                            Set rooms = exam.findBestAvailableRooms(period);
110                            if (rooms!=null) best = new ExamPlacement(exam, period, rooms);
111                        }
112                    }
113                    if (best!=null && best.getTimeCost()<current.getTimeCost()) return new ExamSimpleNeighbour(best);
114                }
115            }
116            iActive = false;
117            return null;
118        }
119        
120        /**
121         * Select a neighbour.
122         * While there are exams that are still not assigned:
123         * <ul>
124         * <li>The worst unassigned exam is selected (using {@link Exam#compareTo(Object)})
125         * <li>The best period that does not break any hard constraint and
126         * for which there is a room assignment available is selected together with 
127         * the set the best available rooms for the exam in the best period
128         * (computed using {@link Exam#findBestAvailableRooms(ExamPeriodPlacement)}).
129         * </ul>
130         * Return null when done (all variables are assigned and the problem is locally optimal).
131         */
132        public Neighbour selectNeighbour(Solution solution) {
133            ExamModel model = (ExamModel)solution.getModel();
134            if (!iActive) {
135                iActive = true;
136                iProgress.setPhase("Construction ...", model.variables().size());
137            }
138            iProgress.setProgress(model.nrAssignedVariables());
139            if (model.nrUnassignedVariables()<=iSkip.size()) return checkLocalOptimality(model);
140            Exam bestExam = null;
141            for (Enumeration e=model.unassignedVariables().elements();e.hasMoreElements();) {
142                Exam exam = (Exam)e.nextElement();
143                if (iSkip.contains(exam)) continue;
144                if (bestExam==null || exam.compareTo(bestExam)<0)
145                    bestExam = exam;
146            }
147            ExamPlacement best = null;
148            for (Enumeration e=bestExam.getPeriodPlacements().elements();e.hasMoreElements();) {
149                ExamPeriodPlacement period = (ExamPeriodPlacement)e.nextElement();
150                if (bestExam.countStudentConflicts(period)>0) {
151                    if (iAssignments.contains(bestExam.getId()+":"+period.getIndex())) continue;
152                }
153                if (!bestExam.checkDistributionConstraints(period)) continue;
154                ExamPlacement placement = new ExamPlacement(bestExam, period, null);
155                if (best==null || best.getTimeCost()>placement.getTimeCost()) {
156                    Set rooms = bestExam.findBestAvailableRooms(period);
157                    if (rooms!=null)
158                        best = new ExamPlacement(bestExam, period, rooms);
159                }
160            }
161            if (best!=null) {
162                iAssignments.add(bestExam.getId()+":"+best.getPeriod().getIndex());
163                return new ExamSimpleNeighbour(best);
164            } /*else {
165                for (Enumeration e=bestExam.getPeriodPlacements().elements();e.hasMoreElements();) {
166                    ExamPeriodPlacement period = (ExamPeriodPlacement)e.nextElement();
167                    ExamPlacement placement = new ExamPlacement(bestExam, period, null);
168                    if (best==null || best.getTimeCost()>placement.getTimeCost()) {
169                        Set rooms = bestExam.findBestAvailableRooms(period);
170                        if (rooms!=null)
171                            best = new ExamPlacement(bestExam, period, rooms);
172                    }
173                }
174                if (best!=null) {
175                    bestExam.allowAllStudentConflicts(best.getPeriod());
176                    iAssignments.add(bestExam.getId()+":"+best.getPeriod().getIndex());
177                    return new ExamSimpleNeighbour(best);
178                }
179            }
180            */
181            if (!iSkip.contains(bestExam)) {
182                iSkip.add(bestExam); return selectNeighbour(solution);
183            }
184            return checkLocalOptimality(model);
185        }
186        
187    }