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