001package org.cpsolver.coursett.model;
002
003import java.util.ArrayList;
004import java.util.Collection;
005import java.util.HashSet;
006import java.util.List;
007import java.util.Set;
008
009import org.cpsolver.coursett.constraint.JenrlConstraint;
010import org.cpsolver.coursett.criteria.StudentCommittedConflict;
011import org.cpsolver.coursett.criteria.StudentConflict;
012import org.cpsolver.ifs.assignment.Assignment;
013import org.cpsolver.ifs.solution.Solution;
014import org.cpsolver.ifs.termination.TerminationCondition;
015import org.cpsolver.ifs.util.Progress;
016import org.cpsolver.ifs.util.ToolBox;
017
018
019/**
020 * Student sectioning (after a solution is found). <br>
021 * <br>
022 * In the current implementation, students are not re-sectioned during the
023 * search, but a student re-sectioning algorithm is called after the solver is
024 * finished or upon the user's request. The re-sectioning is based on a local
025 * search algorithm where the neighboring assignment is obtained from the
026 * current assignment by applying one of the following moves:
027 * <ul>
028 * <li>Two students enrolled in the same course swap all of their class
029 * assignments.
030 * <li>A student is re-enrolled into classes associated with a course such that
031 * the number of conflicts involving that student is minimized.
032 * </ul>
033 * The solver maintains a queue, initially containing all courses with multiple
034 * classes. During each iteration, an improving move (i.e., a move decreasing
035 * the overall number of student conflicts) is applied once discovered.
036 * Re-sectioning is complete once no more improving moves are possible. Only
037 * consistent moves (i.e., moves that respect class limits and other
038 * constraints) are considered. Any additional courses having student conflicts
039 * after a move is accepted are added to the queue. <br>
040 * Since students are not re-sectioned during the timetabling search, the
041 * computed number of student conflicts is really an upper bound on the actual
042 * number that may exist afterward. To compensate for this during the search,
043 * student conflicts between subparts with multiple classes are weighted lower
044 * than conflicts between classes that meet at a single time (i.e., having
045 * student conflicts that cannot be avoided by re-sectioning).
046 * 
047 * @author  Tomáš Müller
048 * @version CourseTT 1.3 (University Course Timetabling)<br>
049 *          Copyright (C) 2006 - 2014 Tomáš Müller<br>
050 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
051 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
052 * <br>
053 *          This library is free software; you can redistribute it and/or modify
054 *          it under the terms of the GNU Lesser General Public License as
055 *          published by the Free Software Foundation; either version 3 of the
056 *          License, or (at your option) any later version. <br>
057 * <br>
058 *          This library is distributed in the hope that it will be useful, but
059 *          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. <br>
062 * <br>
063 *          You should have received a copy of the GNU Lesser General Public
064 *          License along with this library; if not see
065 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
066 */
067
068public class FinalSectioning {
069    private TimetableModel iModel = null;
070    public static double sEps = 0.0001;
071    private boolean iWeighStudents = false;
072
073    public FinalSectioning(TimetableModel model) {
074        iModel = model;
075        iWeighStudents = model.getProperties().getPropertyBoolean("General.WeightStudents", iWeighStudents);
076    }
077    
078    public void execute(Solution<Lecture, Placement> solution, TerminationCondition<Lecture, Placement> termination) {
079        Progress p = Progress.getInstance(iModel);
080        p.setStatus("Student Sectioning...");
081        Collection<Lecture> variables = new ArrayList<Lecture>(iModel.variables());
082        // include committed classes that have structure
083        if (iModel.hasConstantVariables())
084            for (Lecture lecture: iModel.constantVariables()) {
085                // if (lecture.getParent() != null || (lecture.sameStudentsLectures()!= null && !lecture.sameStudentsLectures().isEmpty()))
086                variables.add(lecture);
087            }
088        while (!variables.isEmpty() && (termination == null || termination.canContinue(solution))) {
089            // sLogger.debug("Shifting students ...");
090            p.setPhase("moving students ...", variables.size());
091            HashSet<Lecture> lecturesToRecompute = new HashSet<Lecture>(variables.size());
092
093            for (Lecture lecture : variables) {
094                if (lecture.getParent() == null) {
095                    Configuration cfg = lecture.getConfiguration();
096                    if (cfg != null && cfg.getAltConfigurations().size() > 1)
097                        findAndPerformMoves(solution.getAssignment(), cfg, lecturesToRecompute);
098                }
099                // sLogger.debug("Shifting students for "+lecture);
100                findAndPerformMoves(solution.getAssignment(), lecture, lecturesToRecompute);
101                // sLogger.debug("Lectures to recompute: "+lects);
102                p.incProgress();
103            }
104            // sLogger.debug("Shifting done, "+getViolatedStudentConflictsCounter().get()+" conflicts.");
105            variables = lecturesToRecompute;
106        }
107    }
108
109    /**
110     * Perform sectioning on the given lecture
111     * 
112     * @param assignment current assignment
113     * @param lecture
114     *            given lecture
115     * @param recursive
116     *            recursively resection lectures affected by a student swap
117     * @param configAsWell
118     *            resection students between configurations as well
119     **/
120    public void resection(Assignment<Lecture, Placement> assignment, Lecture lecture, boolean recursive, boolean configAsWell) {
121        HashSet<Lecture> variables = new HashSet<Lecture>();
122        findAndPerformMoves(assignment, lecture, variables);
123        if (configAsWell) {
124            Configuration cfg = lecture.getConfiguration();
125            if (cfg != null && cfg.getAltConfigurations().size() > 1)
126                findAndPerformMoves(assignment, cfg, variables);
127        }
128        if (recursive) {
129            while (!variables.isEmpty()) {
130                HashSet<Lecture> lecturesToRecompute = new HashSet<Lecture>();
131                for (Lecture l : variables) {
132                    if (configAsWell && l.getParent() == null) {
133                        Configuration cfg = l.getConfiguration();
134                        if (cfg != null && cfg.getAltConfigurations().size() > 1)
135                            findAndPerformMoves(assignment, cfg, lecturesToRecompute);
136                    }
137                    findAndPerformMoves(assignment, l, lecturesToRecompute);
138                }
139                variables = lecturesToRecompute;
140            }
141        }
142    }
143
144    /**
145     * Swap students between this and the same lectures (lectures which differ
146     * only in the section)
147     * @param assignment current assignment
148     * @param lecture a class that is being considered
149     * @param lecturesToRecompute set of classes that may need to be re-considered
150     */
151    public void findAndPerformMoves(Assignment<Lecture, Placement> assignment, Lecture lecture, HashSet<Lecture> lecturesToRecompute) {
152        if (lecture.sameSubpartLectures() == null || assignment.getValue(lecture) == null)
153            return;
154
155        if (lecture.getClassLimitConstraint() != null) {
156            while (lecture.nrWeightedStudents() > sEps + lecture.minClassLimit()) {
157                Move m = findAwayMove(assignment, lecture);
158                if (m == null)
159                    break;
160                if (m.perform(assignment))
161                    lecturesToRecompute.add(m.secondLecture());
162                else
163                    m.getUndoMove().perform(assignment);
164            }
165        } else if (!iWeighStudents) {
166            while (true) {
167                Move m = findAwayMove(assignment, lecture);
168                if (m == null)
169                    break;
170                if (m.perform(assignment))
171                    lecturesToRecompute.add(m.secondLecture());
172                else
173                    m.getUndoMove().perform(assignment);
174            }
175        }
176
177        Set<Student> conflictStudents = lecture.conflictStudents(assignment);
178        if (conflictStudents == null || conflictStudents.isEmpty())
179            return;
180        // sLogger.debug("  conflicts:"+conflictStudents.size()+"/"+conflictStudents);
181        // sLogger.debug("Solution before swap is "+iModel.getInfo()+".");
182        if (lecture.sameSubpartLectures().size() > 1) {
183            for (Student student : conflictStudents) {
184                if (assignment.getValue(lecture) == null)
185                        continue;
186                Move m = findMove(assignment, lecture, student);
187                if (m != null) {
188                    if (m.perform(assignment))
189                        lecturesToRecompute.add(m.secondLecture());
190                    else
191                        m.getUndoMove().perform(assignment);
192                }
193            }
194        } else {
195            for (Student student : conflictStudents) {
196                for (Lecture anotherLecture : lecture.conflictLectures(assignment, student)) {
197                    if (anotherLecture.equals(lecture) || anotherLecture.sameSubpartLectures() == null
198                            || assignment.getValue(anotherLecture) == null
199                            || anotherLecture.sameSubpartLectures().size() <= 1)
200                        continue;
201                    lecturesToRecompute.add(anotherLecture);
202                }
203            }
204        }
205    }
206
207    public void findAndPerformMoves(Assignment<Lecture, Placement> assignment, Configuration configuration, HashSet<Lecture> lecturesToRecompute) {
208        for (Student student : configuration.students()) {
209            if (!configuration.hasConflict(assignment, student))
210                continue;
211            
212            MoveBetweenCfgs m = findMove(assignment, configuration, student);
213
214            if (m != null) {
215                if (m.perform(assignment))
216                    lecturesToRecompute.addAll(m.secondLectures());
217                else
218                    m.getUndoMove().perform(assignment);
219            }
220        }
221    }
222
223    public Move findAwayMove(Assignment<Lecture, Placement> assignment, Lecture lecture) {
224        List<Move> bestMoves = null;
225        double bestDelta = 0;
226        for (Student student : lecture.students()) {
227            if (!student.canUnenroll(lecture))
228                continue;
229            for (Lecture sameLecture : lecture.sameSubpartLectures()) {
230                double studentWeight = student.getOfferingWeight(sameLecture.getConfiguration());
231                if (!student.canEnroll(sameLecture))
232                    continue;
233                if (sameLecture.equals(lecture) || assignment.getValue(sameLecture) == null)
234                    continue;
235                if (sameLecture.nrWeightedStudents() + studentWeight <= sEps + sameLecture.classLimit(assignment)) {
236                    Move m = createMove(assignment, lecture, student, sameLecture, null);
237                    if (m == null || m.isTabu())
238                        continue;
239                    double delta = m.getDelta(assignment);
240                    if (delta < bestDelta) {
241                        if (bestMoves == null)
242                            bestMoves = new ArrayList<Move>();
243                        else
244                            bestMoves.clear();
245                        bestMoves.add(m);
246                        bestDelta = delta;
247                    } else if (delta == bestDelta) {
248                        if (bestMoves == null)
249                            bestMoves = new ArrayList<Move>();
250                        bestMoves.add(m);
251                    }
252                }
253            }
254        }
255        if (bestDelta < -sEps && bestMoves != null) {
256            Move m = ToolBox.random(bestMoves);
257            return m;
258        }
259        return null;
260    }
261
262    public Move findMove(Assignment<Lecture, Placement> assignment, Lecture lecture, Student student) {
263        if (!student.canUnenroll(lecture)) return null;
264        double bestDelta = 0;
265        List<Move> bestMoves = null;
266        double studentWeight = student.getOfferingWeight(lecture.getConfiguration());
267        for (Lecture sameLecture : lecture.sameSubpartLectures()) { // sameStudentLectures
268            if (!student.canEnroll(sameLecture))
269                continue;
270            if (sameLecture.equals(lecture) || assignment.getValue(sameLecture) == null)
271                continue;
272            if (sameLecture.nrWeightedStudents() + studentWeight <= sEps + sameLecture.classLimit(assignment)) {
273                Move m = createMove(assignment, lecture, student, sameLecture, null);
274                if (m == null || m.isTabu())
275                    continue;
276                double delta = m.getDelta(assignment);
277                if (delta < bestDelta) {
278                    if (bestMoves == null)
279                        bestMoves = new ArrayList<Move>();
280                    else
281                        bestMoves.clear();
282                    bestMoves.add(m);
283                    bestDelta = delta;
284                } else if (delta == bestDelta) {
285                    if (bestMoves == null)
286                        bestMoves = new ArrayList<Move>();
287                    bestMoves.add(m);
288                }
289            }
290            for (Student anotherStudent : sameLecture.students()) {
291                if (!anotherStudent.canUnenroll(sameLecture) || !anotherStudent.canEnroll(lecture))
292                    continue;
293                double anotherStudentWeight = anotherStudent.getOfferingWeight(lecture.getConfiguration());
294                if (anotherStudentWeight != studentWeight) {
295                    if (sameLecture.nrWeightedStudents() - anotherStudentWeight + studentWeight > sEps
296                            + sameLecture.classLimit(assignment))
297                        continue;
298                    if (lecture.nrWeightedStudents() - studentWeight + anotherStudentWeight > sEps
299                            + lecture.classLimit(assignment))
300                        continue;
301                }
302                if (bestDelta < -sEps && bestMoves != null && bestMoves.size() > 10)
303                    break;
304                Move m = createMove(assignment, lecture, student, sameLecture, anotherStudent);
305                if (m == null || m.isTabu())
306                    continue;
307                double delta = m.getDelta(assignment);
308                if (delta < bestDelta) {
309                    if (bestMoves == null)
310                        bestMoves = new ArrayList<Move>();
311                    else
312                        bestMoves.clear();
313                    bestMoves.add(m);
314                    bestDelta = delta;
315                } else if (delta == bestDelta) {
316                    if (bestMoves == null)
317                        bestMoves = new ArrayList<Move>();
318                    bestMoves.add(m);
319                }
320            }
321            if (Math.abs(bestDelta) < sEps && bestMoves != null && bestMoves.size() > 10)
322                break;
323        }
324        if (bestDelta < -sEps && bestMoves != null)
325            return ToolBox.random(bestMoves);
326        return null;
327    }
328
329    public MoveBetweenCfgs findMove(Assignment<Lecture, Placement> assignment, Configuration config, Student student) {
330        double bestDelta = 0;
331        List<MoveBetweenCfgs> bestMoves = null;
332        for (Configuration altConfig : config.getAltConfigurations()) {
333            if (altConfig.equals(config))
334                continue;
335
336            MoveBetweenCfgs m = createMove(assignment, config, student, altConfig, null);
337            if (m != null && !m.isTabu()) {
338                double delta = m.getDelta(assignment);
339                if (delta < bestDelta) {
340                    if (bestMoves == null)
341                        bestMoves = new ArrayList<MoveBetweenCfgs>();
342                    else
343                        bestMoves.clear();
344                    bestMoves.add(m);
345                    bestDelta = delta;
346                } else if (delta == bestDelta) {
347                    if (bestMoves == null)
348                        bestMoves = new ArrayList<MoveBetweenCfgs>();
349                    bestMoves.add(m);
350                }
351            }
352
353            for (Student anotherStudent : altConfig.students()) {
354                if (bestDelta < -sEps && bestMoves != null && bestMoves.size() > 10)
355                    break;
356
357                m = createMove(assignment, config, student, altConfig, anotherStudent);
358                if (m != null && !m.isTabu()) {
359                    double delta = m.getDelta(assignment);
360                    if (delta < bestDelta) {
361                        if (bestMoves == null)
362                            bestMoves = new ArrayList<MoveBetweenCfgs>();
363                        else
364                            bestMoves.clear();
365                        bestMoves.add(m);
366                        bestDelta = delta;
367                    } else if (delta == bestDelta) {
368                        if (bestMoves == null)
369                            bestMoves = new ArrayList<MoveBetweenCfgs>();
370                        bestMoves.add(m);
371                    }
372                }
373            }
374            if (Math.abs(bestDelta) < sEps && bestMoves != null && bestMoves.size() > 10)
375                break;
376        }
377        if (bestDelta < -sEps && bestMoves != null)
378            return ToolBox.random(bestMoves);
379        return null;
380    }
381    
382    public Move createMove(Assignment<Lecture, Placement> assignment, Lecture firstLecture, Student firstStudent, Lecture secondLecture, Student secondStudent) {
383        return createMove(assignment, firstLecture, firstStudent, secondLecture, secondStudent, null);
384    }
385
386    public Move createMove(Assignment<Lecture, Placement> assignment, Lecture firstLecture, Student firstStudent, Lecture secondLecture, Student secondStudent, Move parentMove) {
387        if (!firstStudent.canUnenroll(firstLecture) || !firstStudent.canEnroll(secondLecture))
388            return null;
389        if (secondStudent != null && (!secondStudent.canUnenroll(secondLecture) || !secondStudent.canEnroll(firstLecture)))
390            return null;
391        if (firstLecture.getParent() != null && secondLecture.getParent() == null)
392            return null;
393        if (firstLecture.getParent() == null && secondLecture.getParent() != null)
394            return null;
395        
396        Move move = new Move(firstLecture, firstStudent, secondLecture, secondStudent);
397
398        if (parentMove == null) {
399            Lecture l1 = firstLecture, l2 = secondLecture;
400            while (l1.getParent() != null && l2.getParent() != null && !l1.getParent().equals(l2.getParent())) {
401                Lecture p1 = l1.getParent();
402                Lecture p2 = l2.getParent();
403                if (assignment.getValue(p1) == null || assignment.getValue(p2) == null) return null;
404                double w1 = firstStudent.getOfferingWeight(p1.getConfiguration());
405                double w2 = (secondStudent == null ? 0.0 : secondStudent.getOfferingWeight(p2.getConfiguration()));
406                if (w1 != w2) {
407                    if (p1.nrWeightedStudents() - w1 + w2 > sEps + p1.classLimit(assignment))
408                        return null;
409                    if (p2.nrWeightedStudents() - w2 + w1 > sEps + p2.classLimit(assignment))
410                        return null;
411                }
412                if (firstStudent.canUnenroll(p2) && firstStudent.canEnroll(p1) && (secondStudent == null || (secondStudent.canUnenroll(p1) && secondStudent.canEnroll(p2)))) {
413                    move.addChildMove(new Move(p1, firstStudent, p2, secondStudent));
414                } else {
415                    return null;
416                }
417                l1 = p1; l2 = p2;
418            }
419        }
420
421        if (firstLecture.hasAnyChildren() != secondLecture.hasAnyChildren())
422            return null;
423        if (firstLecture.hasAnyChildren()) {
424            if (secondStudent != null) {
425                for (Long subpartId: firstLecture.getChildrenSubpartIds()) {
426                    Lecture firstChildLecture = firstLecture.getChild(firstStudent, subpartId);
427                    Lecture secondChildLecture = secondLecture.getChild(secondStudent, subpartId);
428                    if (firstChildLecture == null || secondChildLecture == null)
429                        return null;
430                    double firstStudentWeight = firstStudent.getOfferingWeight(firstChildLecture.getConfiguration());
431                    double secondStudentWeight = secondStudent.getOfferingWeight(secondChildLecture.getConfiguration());
432                    if (firstStudentWeight != secondStudentWeight) {
433                        if (firstChildLecture.nrWeightedStudents() - firstStudentWeight + secondStudentWeight > sEps
434                                + firstChildLecture.classLimit(assignment))
435                            return null;
436                        if (secondChildLecture.nrWeightedStudents() - secondStudentWeight + firstStudentWeight > sEps
437                                + secondChildLecture.classLimit(assignment))
438                            return null;
439                    }
440                    if (assignment.getValue(firstChildLecture) != null && assignment.getValue(secondChildLecture) != null) {
441                        Move m = createMove(assignment, firstChildLecture, firstStudent, secondChildLecture, secondStudent, move);
442                        if (m == null)
443                            return null;
444                        move.addChildMove(m);
445                    } else
446                        return null;
447                }
448            } else {
449                for (Long subpartId: firstLecture.getChildrenSubpartIds()) {
450                    Lecture firstChildLecture = firstLecture.getChild(firstStudent, subpartId);
451                    if (firstChildLecture == null || assignment.getValue(firstChildLecture) == null)
452                        return null;
453                    double firstStudentWeight = firstStudent.getOfferingWeight(firstChildLecture.getConfiguration());
454                    List<Lecture> secondChildLectures = secondLecture.getChildren(subpartId);
455                    if (secondChildLectures == null || secondChildLectures.isEmpty())
456                        return null;
457                    List<Move> bestMoves = null;
458                    double bestDelta = 0;
459                    for (Lecture secondChildLecture : secondChildLectures) {
460                        if (assignment.getValue(secondChildLecture) == null)
461                            continue;
462                        if (secondChildLecture.nrWeightedStudents() + firstStudentWeight > sEps
463                                + secondChildLecture.classLimit(assignment))
464                            continue;
465                        Move m = createMove(assignment, firstChildLecture, firstStudent, secondChildLecture, secondStudent, move);
466                        if (m == null)
467                            continue;
468                        double delta = m.getDelta(assignment);
469                        if (bestMoves == null || delta < bestDelta) {
470                            if (bestMoves == null)
471                                bestMoves = new ArrayList<Move>();
472                            else
473                                bestMoves.clear();
474                            bestMoves.add(m);
475                            bestDelta = delta;
476                        } else if (delta == bestDelta) {
477                            bestMoves.add(m);
478                        }
479                    }
480                    if (bestDelta >= 0 || bestMoves == null)
481                        return null;
482                    Move m = ToolBox.random(bestMoves);
483                    move.addChildMove(m);
484                }
485            }
486        }
487        return move;
488    }
489
490    public class Move {
491        Lecture iFirstLecture = null;
492        Student iFirstStudent = null;
493        Lecture iSecondLecture = null;
494        Student iSecondStudent = null;
495        List<Move> iChildMoves = null;
496
497        private Move(Lecture firstLecture, Student firstStudent, Lecture secondLecture, Student secondStudent) {
498            iFirstLecture = firstLecture;
499            iFirstStudent = firstStudent;
500            iSecondLecture = secondLecture;
501            iSecondStudent = secondStudent;
502        }
503
504        public Lecture firstLecture() {
505            return iFirstLecture;
506        }
507
508        public Student firstStudent() {
509            return iFirstStudent;
510        }
511
512        public Lecture secondLecture() {
513            return iSecondLecture;
514        }
515
516        public Student secondStudent() {
517            return iSecondStudent;
518        }
519
520        public void addChildMove(Move move) {
521            if (iChildMoves == null)
522                iChildMoves = new ArrayList<Move>();
523            iChildMoves.add(move);
524        }
525
526        public List<Move> getChildMoves() {
527            return iChildMoves;
528        }
529        
530        public Move getUndoMove() {
531            Move ret = new Move(iSecondLecture, iFirstStudent, iFirstLecture, iSecondStudent);
532            if (iChildMoves != null)
533                for (Move move: iChildMoves)
534                    ret.addChildMove(move.getUndoMove());
535            return ret;
536        }
537
538        public boolean perform(Assignment<Lecture, Placement> assignment) {
539            double conflicts = firstLecture().getModel().getCriterion(StudentConflict.class).getValue(assignment) +
540                    firstLecture().getModel().getCriterion(StudentCommittedConflict.class).getValue(assignment);
541            for (Lecture lecture : firstStudent().getLectures()) {
542                if (lecture.equals(firstLecture()))
543                    continue;
544                JenrlConstraint jenrl = firstLecture().jenrlConstraint(lecture);
545                if (jenrl == null)
546                    continue;
547                jenrl.decJenrl(assignment, firstStudent());
548                if (jenrl.getNrStudents() == 0) {
549                    jenrl.getContext(assignment).unassigned(assignment, null);
550                    Object[] vars = jenrl.variables().toArray();
551                    for (int j = 0; j < vars.length; j++)
552                        jenrl.removeVariable((Lecture) vars[j]);
553                    iModel.removeConstraint(jenrl);
554                }
555            }
556            if (secondStudent() != null) {
557                for (Lecture lecture : secondStudent().getLectures()) {
558                    if (lecture.equals(secondLecture()))
559                        continue;
560                    JenrlConstraint jenrl = secondLecture().jenrlConstraint(lecture);
561                    if (jenrl == null)
562                        continue;
563                    jenrl.decJenrl(assignment, secondStudent());
564                    if (jenrl.getNrStudents() == 0) {
565                        jenrl.getContext(assignment).unassigned(assignment, null);
566                        Object[] vars = jenrl.variables().toArray();
567                        for (int j = 0; j < vars.length; j++)
568                            jenrl.removeVariable((Lecture) vars[j]);
569                        iModel.removeConstraint(jenrl);
570                    }
571                }
572            }
573
574            firstLecture().removeStudent(assignment, firstStudent());
575            firstStudent().removeLecture(firstLecture());
576            secondLecture().addStudent(assignment, firstStudent());
577            firstStudent().addLecture(secondLecture());
578            if (secondStudent() != null) {
579                secondLecture().removeStudent(assignment, secondStudent());
580                secondStudent().removeLecture(secondLecture());
581                firstLecture().addStudent(assignment, secondStudent());
582                secondStudent().addLecture(firstLecture());
583            }
584
585            for (Lecture lecture : firstStudent().getLectures()) {
586                if (lecture.equals(secondLecture()))
587                    continue;
588                JenrlConstraint jenrl = secondLecture().jenrlConstraint(lecture);
589                if (jenrl == null) {
590                    jenrl = new JenrlConstraint();
591                    jenrl.addVariable(secondLecture());
592                    jenrl.addVariable(lecture);
593                    iModel.addConstraint(jenrl);
594                    // sLogger.debug(getName()+": add jenr {conf="+jenrl.isInConflict()+", lect="+anotherLecture.getName()+", jenr="+jenrl+"}");
595                }
596                jenrl.incJenrl(assignment, firstStudent());
597            }
598            if (secondStudent() != null) {
599                for (Lecture lecture : secondStudent().getLectures()) {
600                    if (lecture.equals(firstLecture()))
601                        continue;
602                    JenrlConstraint jenrl = firstLecture().jenrlConstraint(lecture);
603                    if (jenrl == null) {
604                        jenrl = new JenrlConstraint();
605                        jenrl.addVariable(lecture);
606                        jenrl.addVariable(firstLecture());
607                        iModel.addConstraint(jenrl);
608                        // sLogger.debug(getName()+": add jenr {conf="+jenrl.isInConflict()+", lect="+anotherLecture.getName()+", jenr="+jenrl+"}");
609                    }
610                    jenrl.incJenrl(assignment, secondStudent());
611                }
612            }
613
614            if (getChildMoves() != null) {
615                for (Move move : getChildMoves()) {
616                    move.perform(assignment);
617                }
618            }
619            // sLogger.debug("Solution after swap is "+iModel.getInfo()+".");
620            return firstLecture().getModel().getCriterion(StudentConflict.class).getValue(assignment) + firstLecture().getModel().getCriterion(StudentCommittedConflict.class).getValue(assignment) < conflicts;
621        }
622
623        public double getDelta(Assignment<Lecture, Placement> assignment) {
624            double delta = 0;
625            for (Lecture lecture : firstStudent().getLectures()) {
626                if (assignment.getValue(lecture) == null || lecture.equals(firstLecture()))
627                    continue;
628                JenrlConstraint jenrl = firstLecture().jenrlConstraint(lecture);
629                if (jenrl == null)
630                    continue;
631                if (jenrl.isInConflict(assignment))
632                    delta -= jenrl.getJenrlWeight(firstStudent());
633            }
634            if (secondStudent() != null) {
635                for (Lecture lecture : secondStudent().getLectures()) {
636                    if (assignment.getValue(lecture) == null || lecture.equals(secondLecture()))
637                        continue;
638                    JenrlConstraint jenrl = secondLecture().jenrlConstraint(lecture);
639                    if (jenrl == null)
640                        continue;
641                    if (jenrl.isInConflict(assignment))
642                        delta -= jenrl.getJenrlWeight(secondStudent());
643                }
644            }
645
646            for (Lecture lecture : firstStudent().getLectures()) {
647                if (assignment.getValue(lecture) == null || lecture.equals(firstLecture()))
648                    continue;
649                JenrlConstraint jenrl = secondLecture().jenrlConstraint(lecture);
650                if (jenrl != null) {
651                    if (jenrl.isInConflict(assignment))
652                        delta += jenrl.getJenrlWeight(firstStudent());
653                } else {
654                    if (JenrlConstraint.isInConflict(assignment.getValue(secondLecture()), assignment.getValue(lecture), iModel.getDistanceMetric(), iModel.getStudentWorkDayLimit()))
655                        delta += firstStudent().getJenrlWeight(secondLecture(), lecture);
656                }
657            }
658            if (secondStudent() != null) {
659                for (Lecture lecture : secondStudent().getLectures()) {
660                    if (assignment.getValue(lecture) == null || lecture.equals(secondLecture()))
661                        continue;
662                    JenrlConstraint jenrl = firstLecture().jenrlConstraint(lecture);
663                    if (jenrl != null) {
664                        if (jenrl.isInConflict(assignment))
665                            delta += jenrl.getJenrlWeight(secondStudent());
666                    } else {
667                        if (JenrlConstraint.isInConflict(assignment.getValue(firstLecture()), assignment.getValue(lecture), iModel.getDistanceMetric(), iModel.getStudentWorkDayLimit()))
668                            delta += secondStudent().getJenrlWeight(firstLecture(), lecture);
669                    }
670                }
671            }
672
673            Placement p1 = assignment.getValue(firstLecture());
674            Placement p2 = assignment.getValue(secondLecture());
675            delta += firstStudent().countConflictPlacements(p2) - firstStudent().countConflictPlacements(p1);
676            if (secondStudent() != null)
677                delta += secondStudent().countConflictPlacements(p1) - secondStudent().countConflictPlacements(p2);
678
679            if (getChildMoves() != null) {
680                for (Move move : getChildMoves()) {
681                    delta += move.getDelta(assignment);
682                }
683            }
684            return delta;
685        }
686
687        public boolean isTabu() {
688            return false;
689        }
690
691        @Override
692        public String toString() {
693            return "Move{" + firstStudent() + "/" + firstLecture() + " <-> " + secondStudent() + "/" + secondLecture()
694                    + ", ch=" + getChildMoves() + "}";
695
696        }
697
698    }
699
700    public MoveBetweenCfgs createMove(Assignment<Lecture, Placement> assignment, Configuration firstConfig, Student firstStudent, Configuration secondConfig,
701            Student secondStudent) {
702        MoveBetweenCfgs m = new MoveBetweenCfgs(firstConfig, firstStudent, secondConfig, secondStudent);
703
704        for (Long subpartId: firstConfig.getTopSubpartIds()) {
705            if (!addLectures(assignment, firstStudent, secondStudent, m.firstLectures(), firstConfig.getTopLectures(subpartId)))
706                return null;
707        }
708
709        for (Long subpartId: secondConfig.getTopSubpartIds()) {
710            if (!addLectures(assignment, secondStudent, firstStudent, m.secondLectures(), secondConfig.getTopLectures(subpartId)))
711                return null;
712        }
713        
714        if (m.firstLectures().isEmpty() || m.secondLectures().isEmpty()) return null;
715
716        return m;
717    }
718
719    private boolean addLectures(Assignment<Lecture, Placement> assignment, Student student, Student newStudent, Set<Lecture> studentLectures,
720            Collection<Lecture> lectures) {
721        Lecture lecture = null;
722        if (lectures == null)
723            return false;
724
725        if (student != null) {
726            for (Lecture l : lectures) {
727                if (l.students().contains(student)) {
728                    lecture = l;
729                    if (!student.canUnenroll(lecture)) return false;
730                    break;
731                }
732            }
733        } else {
734            int bestValue = 0;
735            Lecture bestLecture = null;
736            for (Lecture l : lectures) {
737                int val = test(assignment, newStudent, l);
738                if (val < 0)
739                    continue;
740                if (bestLecture == null || bestValue > val) {
741                    bestValue = val;
742                    bestLecture = l;
743                }
744            }
745            lecture = bestLecture;
746        }
747
748        if (lecture == null)
749            return false;
750        if (newStudent != null && !newStudent.canEnroll(lecture))
751            return false;
752        if (lecture.getModel() == null) return false;
753        studentLectures.add(lecture);
754        if (lecture.getChildrenSubpartIds() != null) {
755            for (Long subpartId: lecture.getChildrenSubpartIds()) {
756                if (!addLectures(assignment, student, newStudent, studentLectures, lecture.getChildren(subpartId)))
757                    return false;
758            }
759        }
760
761        return true;
762    }
763
764    public int test(Assignment<Lecture, Placement> assignment, Student student, Lecture lecture) {
765        if (assignment.getValue(lecture) == null)
766            return -1;
767        double studentWeight = student.getOfferingWeight(lecture.getConfiguration());
768        if (lecture.nrWeightedStudents() + studentWeight > sEps + lecture.classLimit(assignment))
769            return -1;
770        if (!student.canEnroll(lecture))
771            return -1;
772
773        int test = 0;
774        for (Lecture x : student.getLectures()) {
775            if (assignment.getValue(x) == null)
776                continue;
777            if (JenrlConstraint.isInConflict(assignment.getValue(lecture), assignment.getValue(x), iModel.getDistanceMetric(), iModel.getStudentWorkDayLimit()))
778                test++;
779        }
780        test += student.countConflictPlacements(assignment.getValue(lecture));
781
782        if (lecture.getChildrenSubpartIds() != null) {
783            for (Long subpartId: lecture.getChildrenSubpartIds()) {
784                int bestTest = -1;
785                for (Lecture child : lecture.getChildren(subpartId)) {
786                    int t = test(assignment, student, child);
787                    if (t < 0)
788                        continue;
789                    if (bestTest < 0 || bestTest > t)
790                        bestTest = t;
791                }
792                if (bestTest < 0)
793                    return -1;
794                test += bestTest;
795            }
796        }
797        return test;
798    }
799
800    public class MoveBetweenCfgs {
801        Configuration iFirstConfig = null;
802        Set<Lecture> iFirstLectures = new HashSet<Lecture>();
803        Student iFirstStudent = null;
804        Configuration iSecondConfig = null;
805        Set<Lecture> iSecondLectures = new HashSet<Lecture>();
806        Student iSecondStudent = null;
807
808        public MoveBetweenCfgs(Configuration firstConfig, Student firstStudent, Configuration secondConfig,
809                Student secondStudent) {
810            iFirstConfig = firstConfig;
811            iFirstStudent = firstStudent;
812            iSecondConfig = secondConfig;
813            iSecondStudent = secondStudent;
814        }
815
816        public Configuration firstConfiguration() {
817            return iFirstConfig;
818        }
819
820        public Student firstStudent() {
821            return iFirstStudent;
822        }
823
824        public Set<Lecture> firstLectures() {
825            return iFirstLectures;
826        }
827
828        public Configuration secondConfiguration() {
829            return iSecondConfig;
830        }
831
832        public Student secondStudent() {
833            return iSecondStudent;
834        }
835
836        public Set<Lecture> secondLectures() {
837            return iSecondLectures;
838        }
839        
840        public MoveBetweenCfgs getUndoMove() {
841            MoveBetweenCfgs ret = new MoveBetweenCfgs(iSecondConfig, iFirstStudent, iFirstConfig, iSecondStudent);
842            ret.secondLectures().addAll(iFirstLectures);
843            ret.firstLectures().addAll(iSecondLectures);
844            return ret;
845        }
846
847        public boolean perform(Assignment<Lecture, Placement> assignment) {
848            double conflicts = firstLectures().iterator().next().getModel().getCriterion(StudentConflict.class).getValue(assignment) +
849                    firstLectures().iterator().next().getModel().getCriterion(StudentCommittedConflict.class).getValue(assignment);
850            firstStudent().removeConfiguration(firstConfiguration());
851            firstStudent().addConfiguration(secondConfiguration());
852            for (Lecture lecture : firstStudent().getLectures()) {
853
854                for (Lecture firstLecture : firstLectures()) {
855                    if (firstLecture.equals(lecture))
856                        continue;
857                    if (firstLectures().contains(lecture)
858                            && firstLecture.getClassId().compareTo(lecture.getClassId()) > 0)
859                        continue;
860
861                    JenrlConstraint jenrl = firstLecture.jenrlConstraint(lecture);
862                    if (jenrl == null)
863                        continue;
864                    jenrl.decJenrl(assignment, firstStudent());
865                    if (jenrl.getNrStudents() == 0) {
866                        jenrl.getContext(assignment).unassigned(assignment, null);
867                        Object[] vars = jenrl.variables().toArray();
868                        for (int k = 0; k < vars.length; k++)
869                            jenrl.removeVariable((Lecture) vars[k]);
870                        iModel.removeConstraint(jenrl);
871                    }
872                }
873            }
874
875            if (secondStudent() != null) {
876                secondStudent().removeConfiguration(secondConfiguration());
877                secondStudent().addConfiguration(firstConfiguration());
878                for (Lecture lecture : secondStudent().getLectures()) {
879
880                    for (Lecture secondLecture : secondLectures()) {
881                        if (secondLecture.equals(lecture))
882                            continue;
883                        if (secondLectures().contains(lecture)
884                                && secondLecture.getClassId().compareTo(lecture.getClassId()) > 0)
885                            continue;
886
887                        JenrlConstraint jenrl = secondLecture.jenrlConstraint(lecture);
888                        if (jenrl == null)
889                            continue;
890                        jenrl.decJenrl(assignment, secondStudent());
891                        if (jenrl.getNrStudents() == 0) {
892                            jenrl.getContext(assignment).unassigned(assignment, null);
893                            Object[] vars = jenrl.variables().toArray();
894                            for (int k = 0; k < vars.length; k++)
895                                jenrl.removeVariable((Lecture) vars[k]);
896                            iModel.removeConstraint(jenrl);
897                        }
898                    }
899                }
900            }
901
902            for (Lecture firstLecture : firstLectures()) {
903                firstLecture.removeStudent(assignment, firstStudent());
904                firstStudent().removeLecture(firstLecture);
905                if (secondStudent() != null) {
906                    firstLecture.addStudent(assignment, secondStudent());
907                    secondStudent().addLecture(firstLecture);
908                }
909            }
910            for (Lecture secondLecture : secondLectures()) {
911                secondLecture.addStudent(assignment, firstStudent());
912                firstStudent().addLecture(secondLecture);
913                if (secondStudent() != null) {
914                    secondLecture.removeStudent(assignment, secondStudent());
915                    secondStudent().removeLecture(secondLecture);
916                }
917            }
918
919            for (Lecture lecture : firstStudent().getLectures()) {
920
921                for (Lecture secondLecture : secondLectures()) {
922                    if (secondLecture.equals(lecture))
923                        continue;
924                    if (secondLectures().contains(lecture)
925                            && secondLecture.getClassId().compareTo(lecture.getClassId()) > 0)
926                        continue;
927
928                    JenrlConstraint jenrl = secondLecture.jenrlConstraint(lecture);
929                    if (jenrl == null) {
930                        jenrl = new JenrlConstraint();
931                        jenrl.addVariable(secondLecture);
932                        jenrl.addVariable(lecture);
933                        iModel.addConstraint(jenrl);
934                    }
935                    jenrl.incJenrl(assignment, firstStudent());
936                }
937            }
938
939            if (secondStudent() != null) {
940                for (Lecture lecture : secondStudent().getLectures()) {
941
942                    for (Lecture firstLecture : firstLectures()) {
943                        if (firstLecture.equals(lecture))
944                            continue;
945                        if (firstLectures().contains(lecture)
946                                && firstLecture.getClassId().compareTo(lecture.getClassId()) > 0)
947                            continue;
948
949                        JenrlConstraint jenrl = firstLecture.jenrlConstraint(lecture);
950                        if (jenrl == null) {
951                            jenrl = new JenrlConstraint();
952                            jenrl.addVariable(firstLecture);
953                            jenrl.addVariable(lecture);
954                            iModel.addConstraint(jenrl);
955                        }
956                        jenrl.incJenrl(assignment, secondStudent());
957                    }
958                }
959            }
960            return firstLectures().iterator().next().getModel().getCriterion(StudentConflict.class).getValue(assignment) + 
961                    firstLectures().iterator().next().getModel().getCriterion(StudentCommittedConflict.class).getValue(assignment) < conflicts;
962        }
963
964        public double getDelta(Assignment<Lecture, Placement> assignment) {
965            double delta = 0;
966
967            for (Lecture lecture : firstStudent().getLectures()) {
968                if (assignment.getValue(lecture) == null)
969                    continue;
970
971                for (Lecture firstLecture : firstLectures()) {
972                    if (assignment.getValue(firstLecture) == null || firstLecture.equals(lecture))
973                        continue;
974                    JenrlConstraint jenrl = firstLecture.jenrlConstraint(lecture);
975                    if (jenrl == null)
976                        continue;
977                    if (jenrl.isInConflict(assignment))
978                        delta -= jenrl.getJenrlWeight(firstStudent());
979                }
980
981                for (Lecture secondLecture : secondLectures()) {
982                    if (assignment.getValue(secondLecture) == null || secondLecture.equals(lecture))
983                        continue;
984                    JenrlConstraint jenrl = secondLecture.jenrlConstraint(lecture);
985                    if (jenrl != null) {
986                        if (jenrl.isInConflict(assignment))
987                            delta += jenrl.getJenrlWeight(firstStudent());
988                    } else {
989                        if (JenrlConstraint.isInConflict(assignment.getValue(secondLecture), assignment.getValue(lecture), iModel.getDistanceMetric(), iModel.getStudentWorkDayLimit()))
990                            delta += firstStudent().getJenrlWeight(secondLecture, lecture);
991                    }
992                }
993            }
994
995            if (secondStudent() != null) {
996                for (Lecture lecture : secondStudent().getLectures()) {
997                    if (assignment.getValue(lecture) == null)
998                        continue;
999
1000                    for (Lecture secondLecture : secondLectures()) {
1001                        if (assignment.getValue(secondLecture) == null || secondLecture.equals(lecture))
1002                            continue;
1003                        JenrlConstraint jenrl = secondLecture.jenrlConstraint(lecture);
1004                        if (jenrl == null)
1005                            continue;
1006                        if (jenrl.isInConflict(assignment))
1007                            delta -= jenrl.getJenrlWeight(secondStudent());
1008                    }
1009
1010                    for (Lecture firstLecture : firstLectures()) {
1011                        if (assignment.getValue(firstLecture) == null || firstLecture.equals(lecture))
1012                            continue;
1013                        JenrlConstraint jenrl = firstLecture.jenrlConstraint(lecture);
1014                        if (jenrl != null) {
1015                            if (jenrl.isInConflict(assignment))
1016                                delta += jenrl.getJenrlWeight(secondStudent());
1017                        } else {
1018                            if (JenrlConstraint.isInConflict(assignment.getValue(firstLecture), assignment.getValue(lecture), iModel.getDistanceMetric(), iModel.getStudentWorkDayLimit()))
1019                                delta += secondStudent().getJenrlWeight(firstLecture, lecture);
1020                        }
1021                    }
1022                }
1023            }
1024
1025            for (Lecture firstLecture : firstLectures()) {
1026                Placement p1 = assignment.getValue(firstLecture);
1027                if (p1 == null)
1028                    continue;
1029                delta -= firstStudent().countConflictPlacements(p1);
1030                if (secondStudent() != null)
1031                    delta += secondStudent().countConflictPlacements(p1);
1032            }
1033
1034            for (Lecture secondLecture : secondLectures()) {
1035                Placement p2 = assignment.getValue(secondLecture);
1036                if (p2 == null)
1037                    continue;
1038                delta += firstStudent().countConflictPlacements(p2);
1039                if (secondStudent() != null)
1040                    delta -= secondStudent().countConflictPlacements(p2);
1041            }
1042
1043            return delta;
1044        }
1045
1046        public boolean isTabu() {
1047            return false;
1048        }
1049
1050        @Override
1051        public String toString() {
1052            return "Move{" + firstStudent() + "/" + firstConfiguration().getConfigId() + " <-> " + secondStudent()
1053                    + "/" + secondConfiguration().getConfigId() + "}";
1054        }
1055
1056    }
1057}