001    package net.sf.cpsolver.studentsct.heuristics.selection;
002    
003    import java.util.Enumeration;
004    import java.util.HashSet;
005    import java.util.Iterator;
006    import java.util.Set;
007    import java.util.Vector;
008    
009    import org.apache.log4j.Logger;
010    
011    import net.sf.cpsolver.ifs.heuristics.NeighbourSelection;
012    import net.sf.cpsolver.ifs.model.Neighbour;
013    import net.sf.cpsolver.ifs.solution.Solution;
014    import net.sf.cpsolver.ifs.solver.Solver;
015    import net.sf.cpsolver.ifs.util.DataProperties;
016    import net.sf.cpsolver.ifs.util.Progress;
017    import net.sf.cpsolver.studentsct.StudentSectioningModel;
018    import net.sf.cpsolver.studentsct.heuristics.studentord.StudentChoiceRealFirstOrder;
019    import net.sf.cpsolver.studentsct.heuristics.studentord.StudentOrder;
020    import net.sf.cpsolver.studentsct.model.CourseRequest;
021    import net.sf.cpsolver.studentsct.model.Enrollment;
022    import net.sf.cpsolver.studentsct.model.Request;
023    import net.sf.cpsolver.studentsct.model.Student;
024    
025    /**
026     * Pick a student (one by one) with an incomplete schedule, try to find an improvement, identify problematic students.
027     * <br><br>
028     * For each request that does not have an assignment and that can be assined (see {@link Student#canAssign(Request)})
029     * a randomly selected sub-domain is visited. For every such enrollment, a set of conflicting enrollments 
030     * is computed and a possible student that can get an alternative enrollment is identified (if there is any). For each
031     * such move a value (the cost of moving the problematic student somewhere else) is computed and the best possible move
032     * is selected at the end. If there is no such move, a set of problematic students is identified, i.e., the students 
033     * whose enrollments are preventing this student to get a request.
034     * <br><br>
035     * Each student can be selected for this swap move multiple times, but only if there is still a request that can be resolved.
036     * At the end (when there is no other neighbour), the set of all such problematic students can be returned using the 
037     * {@link ProblemStudentsProvider} interface.
038     * <br><br>
039     * Parameters:
040     * <br>
041     * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
042     * <tr><td>Neighbour.SwapStudentsTimeout</td><td>{@link Integer}</td><td>Timeout for each neighbour selection (in milliseconds).</td></tr>
043     * <tr><td>Neighbour.SwapStudentsMaxValues</td><td>{@link Integer}</td><td>Limit for the number of considered values for each course request (see {@link CourseRequest#computeRandomEnrollments(int)}).</td></tr>
044     * </table>
045     * <br><br>
046     * 
047     * @version
048     * StudentSct 1.1 (Student Sectioning)<br>
049     * Copyright (C) 2007 Tomáš Müller<br>
050     * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
051     * Lazenska 391, 76314 Zlin, Czech Republic<br>
052     * <br>
053     * This library is free software; you can redistribute it and/or
054     * modify it under the terms of the GNU Lesser General Public
055     * License as published by the Free Software Foundation; either
056     * version 2.1 of the License, or (at your option) any later version.
057     * <br><br>
058     * This library is distributed in the hope that it will be useful,
059     * but WITHOUT ANY WARRANTY; without even the implied warranty of
060     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
061     * Lesser General Public License for more details.
062     * <br><br>
063     * You should have received a copy of the GNU Lesser General Public
064     * License along with this library; if not, write to the Free Software
065     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
066     */
067    
068    public class SwapStudentSelection implements NeighbourSelection, ProblemStudentsProvider {
069        private static Logger sLog = Logger.getLogger(SwapStudentSelection.class);
070        private HashSet iProblemStudents = new HashSet();
071        private Student iStudent = null;
072        private Enumeration iStudentsEnumeration = null;
073        private int iTimeout = 5000;
074        private int iMaxValues = 100;
075        public static boolean sDebug = false;
076        protected StudentOrder iOrder = new StudentChoiceRealFirstOrder();
077        
078        /**
079         * Constructor
080         * @param properties configuration
081         */
082        public SwapStudentSelection(DataProperties properties) {
083            iTimeout = properties.getPropertyInt("Neighbour.SwapStudentsTimeout", iTimeout);
084            iMaxValues = properties.getPropertyInt("Neighbour.SwapStudentsMaxValues", iMaxValues);
085            if (properties.getProperty("Neighbour.SwapStudentsOrder")!=null) {
086                try {
087                    iOrder = (StudentOrder)Class.forName(properties.getProperty("Neighbour.SwapStudentsOrder")).
088                        getConstructor(new Class[] {DataProperties.class}).
089                        newInstance(new Object[] {properties});
090                } catch (Exception e) {
091                    sLog.error("Unable to set student order, reason:"+e.getMessage(),e);
092                }
093            }
094        }
095        
096        /** Initialization */
097        public void init(Solver solver) {
098            Vector students = iOrder.order(((StudentSectioningModel)solver.currentSolution().getModel()).getStudents());
099            iStudentsEnumeration = students.elements();
100            iProblemStudents.clear();
101            Progress.getInstance(solver.currentSolution().getModel()).setPhase("Student swap...", students.size());
102        }
103        
104        /** 
105         * For each student that does not have a complete schedule, try to find a request and a 
106         * student that can be moved out of an enrollment so that the selected student
107         * can be assigned to the selected request.
108         */
109        public Neighbour selectNeighbour(Solution solution) {
110            if (iStudent!=null && !iStudent.isComplete()) {
111                Selection selection = getSelection(iStudent);
112                Neighbour neighbour = selection.select();
113                if (neighbour!=null) return neighbour;
114                else
115                    iProblemStudents.addAll(selection.getProblemStudents());
116            }
117            iStudent = null;
118            while (iStudentsEnumeration.hasMoreElements()) {
119                Student student = (Student)iStudentsEnumeration.nextElement();
120                Progress.getInstance(solution.getModel()).incProgress();
121                if (student.isComplete() || student.nrAssignedRequests()==0) continue;
122                Selection selection = getSelection(student);
123                Neighbour neighbour = selection.select();
124                if (neighbour!=null) {
125                    iStudent = student;
126                    return neighbour;
127                } else
128                    iProblemStudents.addAll(selection.getProblemStudents());
129            }
130            return null;
131        }
132        
133        /** List of problematic students */
134        public HashSet getProblemStudents() {
135            return iProblemStudents;
136        }
137        
138        /** Selection subclass for a student */
139        public Selection getSelection(Student student) {
140            return new Selection(student);
141        }
142    
143        /** This class looks for a possible swap move for the given student */
144        public class Selection {
145            private Student iStudent;
146            private long iT0, iT1;
147            private boolean iTimeoutReached;
148            private Enrollment iBestEnrollment;
149            private double iBestValue;
150            private HashSet iProblemStudents;
151            private Vector iBestSwaps;
152            
153            /**
154             * Constructor
155             * @param student given student
156             */
157            public Selection(Student student) {
158                iStudent = student;
159            }
160            
161            /**
162             * The actual selection
163             */
164            public SwapStudentNeighbour select() {
165                if (sDebug) sLog.debug("select(S"+iStudent.getId()+")");
166                iT0 = System.currentTimeMillis();
167                iTimeoutReached = false;
168                iBestEnrollment = null;
169                iProblemStudents = new HashSet();
170                for (Enumeration e=iStudent.getRequests().elements();e.hasMoreElements();) {
171                    if (iTimeout>0 && (System.currentTimeMillis()-iT0)>iTimeout) {
172                        if (!iTimeoutReached) {
173                            if (sDebug) sLog.debug("  -- timeout reached");
174                            iTimeoutReached=true; 
175                        }
176                        break;
177                    }
178                    Request request = (Request)e.nextElement();
179                    if (request.getAssignment()!=null) continue;
180                    if (!iStudent.canAssign(request)) continue;
181                    if (sDebug) sLog.debug("  -- checking request "+request);
182                    Vector values = null;
183                    if (iMaxValues>0 && request instanceof CourseRequest) {
184                        values = ((CourseRequest)request).computeRandomEnrollments(iMaxValues);
185                    } else values = request.values();
186                    for (Enumeration f=values.elements();f.hasMoreElements();) {
187                        if (iTimeout>0 && (System.currentTimeMillis()-iT0)>iTimeout) {
188                            if (!iTimeoutReached) {
189                                if (sDebug) sLog.debug("  -- timeout reached");
190                                iTimeoutReached=true; 
191                            }
192                            break;
193                        }
194                        Enrollment enrollment = (Enrollment)f.nextElement();
195                        if (sDebug) sLog.debug("      -- enrollment "+enrollment);
196                        Set conflicts = enrollment.variable().getModel().conflictValues(enrollment);
197                        if (conflicts.contains(enrollment)) continue;
198                        double value = enrollment.toDouble();
199                        boolean unresolvedConflict = false;
200                        Vector swaps = new Vector(conflicts.size());
201                        for (Iterator j=conflicts.iterator();j.hasNext();) {
202                            Enrollment conflict = (Enrollment)j.next();
203                            if (sDebug) sLog.debug("        -- conflict "+conflict);
204                            Enrollment other = bestSwap(conflict, enrollment, iProblemStudents);
205                            if (other==null) {
206                                if (sDebug) sLog.debug("          -- unable to resolve");
207                                unresolvedConflict = true; break;
208                            }
209                            swaps.add(other);
210                            if (sDebug) sLog.debug("          -- can be resolved by switching to "+other.getName());
211                            value -= conflict.toDouble();
212                            value += other.toDouble();
213                        }
214                        if (unresolvedConflict) continue;
215                        if (iBestEnrollment==null || iBestEnrollment.toDouble()>value) {
216                            iBestEnrollment = enrollment; iBestValue = value; iBestSwaps = swaps;
217                        };
218                    }
219                }
220                iT1 = System.currentTimeMillis();
221                if (sDebug) sLog.debug("  -- done, best enrollment is "+iBestEnrollment);
222                if (iBestEnrollment==null) {
223                    if (iProblemStudents.isEmpty()) iProblemStudents.add(iStudent);
224                    if (sDebug) sLog.debug("  -- problem students are: "+iProblemStudents);
225                    return null;
226                }
227                if (sDebug) sLog.debug("  -- value "+iBestValue);
228                Enrollment[] assignment = new Enrollment[iStudent.getRequests().size()];
229                int idx = 0;
230                for (Enumeration e=iStudent.getRequests().elements();e.hasMoreElements();) {
231                    Request request = (Request)e.nextElement();
232                    assignment[idx++] = (iBestEnrollment.getRequest().equals(request)?iBestEnrollment:(Enrollment)request.getAssignment());
233                }
234                return new SwapStudentNeighbour(iBestValue, iBestEnrollment, iBestSwaps);
235            }
236            
237            /** Was timeout reached during the selection */
238            public boolean isTimeoutReached() {
239                return iTimeoutReached;
240            }
241    
242            /** Time spent in the last selection */
243            public long getTime() {
244                return iT1 - iT0;
245            }
246            
247            /** The best enrollment found. */ 
248            public Enrollment getBestEnrollment() {
249                return iBestEnrollment;
250            }
251            
252            /** Cost of the best enrollment found */
253            public double getBestValue() {
254                return iBestValue;
255            }
256            
257            /** Set of problematic students computed in the last selection */
258            public Set getProblemStudents() {
259                return iProblemStudents;
260            }
261            
262        }
263    
264        /**
265         * Identify the best swap for the given student
266         * @param conflict conflicting enrollment
267         * @param enrl enrollment that is visited (to be assigned to the given student)
268         * @param problematicStudents the current set of problematic students
269         * @return best alternative enrollment for the student of the conflicting enrollment
270         */
271        public static Enrollment bestSwap(Enrollment conflict, Enrollment enrl, Set problematicStudents) {
272            Enrollment bestEnrollment = null;
273            for (Iterator i=conflict.getRequest().values().iterator();i.hasNext();) {
274                Enrollment enrollment = (Enrollment)i.next();
275                if (enrollment.equals(conflict)) continue;
276                if (!enrl.isConsistent(enrollment)) continue;
277                if (conflict.variable().getModel().conflictValues(enrollment).isEmpty()) {
278                    if (bestEnrollment==null || bestEnrollment.toDouble()>enrollment.toDouble())
279                        bestEnrollment = enrollment;
280                }
281            }
282            if (bestEnrollment==null && problematicStudents!=null) {
283                boolean added = false;
284                for (Iterator i=conflict.getRequest().values().iterator();i.hasNext();) {
285                    Enrollment enrollment = (Enrollment)i.next();
286                    if (enrollment.equals(conflict)) continue;
287                    if (!enrl.isConsistent(enrollment)) continue;
288                    Set conflicts = conflict.variable().getModel().conflictValues(enrollment);
289                    for (Iterator j=conflicts.iterator();j.hasNext();) {
290                        Enrollment c = (Enrollment)j.next();
291                        if (enrl.getStudent().isDummy() && !c.getStudent().isDummy()) continue;
292                        if (enrl.getStudent().equals(c.getStudent()) || conflict.getStudent().equals(c.getStudent())) continue;
293                        problematicStudents.add(c.getStudent());
294                    }
295                }
296                if (!added && !enrl.getStudent().equals(conflict.getStudent()))
297                    problematicStudents.add(conflict.getStudent());
298            }
299            return bestEnrollment;
300        }
301        
302        /** Neighbour that contains the swap */
303        public static class SwapStudentNeighbour extends Neighbour {
304            private double iValue;
305            private Enrollment iEnrollment;
306            private Vector iSwaps;
307            
308            /**
309             * Constructor
310             * @param value cost of the move
311             * @param enrollment the enrollment which is to be assigned to the given student
312             * @param swaps enrollment swaps 
313             */
314            public SwapStudentNeighbour(double value, Enrollment enrollment, Vector swaps) {
315                iValue = value;
316                iEnrollment = enrollment;
317                iSwaps = swaps;
318            }
319            
320            public double value() {
321                return iValue;
322            }
323            
324            /** 
325             * Perform the move. 
326             * All the requeired swaps are identified and performed as well.
327             **/
328            public void assign(long iteration) {
329                if (iEnrollment.variable().getAssignment()!=null)
330                    iEnrollment.variable().unassign(iteration);
331                for (Enumeration e=iSwaps.elements();e.hasMoreElements();) {
332                    Enrollment swap = (Enrollment)e.nextElement();
333                    swap.variable().unassign(iteration);
334                }
335                iEnrollment.variable().assign(iteration, iEnrollment);
336                for (Enumeration e=iSwaps.elements();e.hasMoreElements();) {
337                    Enrollment swap = (Enrollment)e.nextElement();
338                    swap.variable().assign(iteration, swap);
339                }
340            }
341            
342            public String toString() {
343                StringBuffer sb = new StringBuffer("SwSt{");
344                sb.append(" "+iEnrollment.getRequest().getStudent());
345                sb.append(" ("+iValue+")");
346                sb.append("\n "+iEnrollment.getRequest());
347                sb.append(" "+iEnrollment);
348                for (Enumeration e=iSwaps.elements();e.hasMoreElements();) {
349                    Enrollment swap = (Enrollment)e.nextElement();
350                    sb.append("\n "+swap.getRequest());
351                    sb.append(" "+swap.variable().getAssignment());
352                    sb.append(" -> "+swap);
353                }
354                sb.append("\n}");
355                return sb.toString();
356            }
357        }
358    }