001package org.cpsolver.studentsct.constraint;
002
003import java.util.Set;
004
005import org.cpsolver.ifs.assignment.Assignment;
006import org.cpsolver.ifs.model.Constraint;
007import org.cpsolver.studentsct.model.CourseRequest;
008import org.cpsolver.studentsct.model.Enrollment;
009import org.cpsolver.studentsct.model.Request;
010import org.cpsolver.studentsct.model.Student;
011
012
013/**
014 * This constraints ensures that a student is not enrolled into sections that
015 * are overlapping in time.
016 * 
017 * <br>
018 * <br>
019 * 
020 * @version StudentSct 1.3 (Student Sectioning)<br>
021 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
022 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
023 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
024 * <br>
025 *          This library is free software; you can redistribute it and/or modify
026 *          it under the terms of the GNU Lesser General Public License as
027 *          published by the Free Software Foundation; either version 3 of the
028 *          License, or (at your option) any later version. <br>
029 * <br>
030 *          This library is distributed in the hope that it will be useful, but
031 *          WITHOUT ANY WARRANTY; without even the implied warranty of
032 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
033 *          Lesser General Public License for more details. <br>
034 * <br>
035 *          You should have received a copy of the GNU Lesser General Public
036 *          License along with this library; if not see
037 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
038 */
039public class StudentConflict extends Constraint<Request, Enrollment> {
040    
041    /**
042     * Constructor
043     * @param student student for which the constraint is created
044     */
045    public StudentConflict(Student student) {
046        super();
047        for (Request request : student.getRequests())
048            addVariable(request);
049    }
050    
051    /**
052     * True if the given enrollment can be assigned to the student. A new enrollment
053     * cannot be assigned to a student when the student already has the desired
054     * number of requests assigned (i.e., number of non-alternative course
055     * requests).
056     * @param assignment current assignment
057     * @param enrollment given enrollment
058     * @return true if the given request can be assigned
059     **/
060    public static boolean canAssign(Assignment<Request, Enrollment> assignment, Enrollment enrollment, Set<Enrollment> conflicts) {
061        int alt = 0;
062        float credit = 0;
063        boolean found = false;
064        for (Request r : enrollment.getStudent().getRequests()) {
065            Enrollment e = (r.equals(enrollment.getRequest()) ? enrollment : r.getAssignment(assignment));
066            if (r.equals(enrollment.getRequest())) {
067                found = true;
068            } else if (e != null && conflicts != null && conflicts.contains(e)) {
069                e = null;
070            }
071            boolean assigned = (e != null || r.equals(enrollment.getRequest()));
072            boolean course = (r instanceof CourseRequest);
073            boolean waitlist = (course && ((CourseRequest) r).isWaitlist());
074            if (r.isAlternative()) {
075                if (assigned || (!found && waitlist))
076                    alt--;
077            } else {
078                if (course && !waitlist && !assigned)
079                    alt++;
080            }
081            if (e != null) credit += e.getCredit();
082        }
083        return (alt >= 0 && credit <= enrollment.getStudent().getMaxCredit());
084    }
085
086    /**
087     * A given enrollment is conflicting when the student is enrolled into
088     * another course / free time request that has an assignment that is
089     * overlapping with one or more assignments of the given section. See
090     * {@link Enrollment#isOverlapping(Enrollment)} for more details. All such
091     * overlapping enrollments are added into the provided set of conflicts.
092     * 
093     * @param enrollment
094     *            {@link Enrollment} that is being considered
095     * @param conflicts
096     *            resultant list of conflicting enrollments
097     */
098    @Override
099    public void computeConflicts(Assignment<Request, Enrollment> assignment, Enrollment enrollment, Set<Enrollment> conflicts) {
100        // for all assigned course requests -> if overlapping with this
101        // enrollment -> conflict
102        for (Request request : variables()) {
103            if (request.equals(enrollment.getRequest()))
104                continue;
105            Enrollment e = assignment.getValue(request);
106            if (e == null)
107                continue;
108            if (enrollment.isOverlapping(e))
109                conflicts.add(e);
110        }
111
112        // if this enrollment cannot be assigned (student already has a full
113        // schedule) -> unassignd a lowest priority request
114        while (!enrollment.getAssignments().isEmpty() && !canAssign(assignment, enrollment, conflicts)) {
115            Enrollment lowestPriorityEnrollment = null;
116            int lowestPriority = -1;
117            for (Request request : variables()) {
118                if (request.equals(enrollment.getRequest()))
119                    continue;
120                if (!(request instanceof CourseRequest) || ((CourseRequest)request).isWaitlist())
121                    continue;
122                Enrollment e = assignment.getValue(request);
123                if (e == null || conflicts.contains(e))
124                    continue;
125                if (lowestPriority < request.getPriority()) {
126                    lowestPriority = request.getPriority();
127                    lowestPriorityEnrollment = e;
128                }
129            }
130            if (lowestPriorityEnrollment != null) {
131                conflicts.add(lowestPriorityEnrollment);
132            } else {
133                conflicts.add(enrollment); // there are only alternatives or wait-listed courses
134                break;
135            }
136        }
137    }
138
139    /** Two enrollments are consistent if they are not overlapping in time */
140    @Override
141    public boolean isConsistent(Enrollment e1, Enrollment e2) {
142        return !e1.isOverlapping(e2);
143    }
144
145    /**
146     * A given enrollment is conflicting when the student is enrolled into
147     * another course / free time request that has an assignment that is
148     * overlapping with one or more assignments of the given section. See
149     * {@link Enrollment#isOverlapping(Enrollment)} for more details.
150     * 
151     * @param enrollment
152     *            {@link Enrollment} that is being considered
153     * @return true, if the student is enrolled into another enrollment of a
154     *         different request that is overlapping in time with the given
155     *         enrollment
156     */
157    @Override
158    public boolean inConflict(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
159        // for all assigned course requests -> if overlapping with this
160        // enrollment -> conflict
161        for (Request request : variables()) {
162            if (request.equals(enrollment.getRequest()))
163                continue;
164            Enrollment e = assignment.getValue(request);
165            if (e == null)
166                continue;
167            if (enrollment.isOverlapping(e))
168                return true;
169        }
170
171        // if this enrollment cannot be assigned (student already has a full
172        // schedule) -> conflict
173        if (!canAssign(assignment, enrollment, null))
174            return true;
175
176        // nothing above -> no conflict
177        return false;
178    }
179
180    @Override
181    public String toString() {
182        return "StudentConflicts";
183    }
184}