001package org.cpsolver.instructor.constraints;
002
003import java.util.Set;
004
005import org.cpsolver.coursett.Constants;
006import org.cpsolver.ifs.assignment.Assignment;
007import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
008import org.cpsolver.ifs.assignment.context.ConstraintWithContext;
009import org.cpsolver.instructor.criteria.SameInstructor;
010import org.cpsolver.instructor.model.TeachingAssignment;
011import org.cpsolver.instructor.model.TeachingRequest;
012
013/**
014 * Same Instructor Constraint. Teaching requests linked with this constraint must/should have the same
015 * instructor assigned. If discouraged/prohibited, every pair of teaching requests should/must have a different
016 * instructor.
017 * 
018 * @version IFS 1.3 (Instructor Sectioning)<br>
019 *          Copyright (C) 2016 Tomáš Müller<br>
020 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
021 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
022 * <br>
023 *          This library is free software; you can redistribute it and/or modify
024 *          it under the terms of the GNU Lesser General Public License as
025 *          published by the Free Software Foundation; either version 3 of the
026 *          License, or (at your option) any later version. <br>
027 * <br>
028 *          This library is distributed in the hope that it will be useful, but
029 *          WITHOUT ANY WARRANTY; without even the implied warranty of
030 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
031 *          Lesser General Public License for more details. <br>
032 * <br>
033 *          You should have received a copy of the GNU Lesser General Public
034 *          License along with this library; if not see
035 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
036 */
037public class SameInstructorConstraint extends ConstraintWithContext<TeachingRequest.Variable, TeachingAssignment, SameInstructorConstraint.Context> {
038    private Long iId;
039    private String iName;
040    private int iPreference = 0;
041    private boolean iRequired = false, iProhibited = false;
042    
043    /**
044     * Constructor
045     * @param id constraint id
046     * @param name constrain (link) name
047     * @param preference preference (R for required, P for prohibited, etc.)
048     */
049    public SameInstructorConstraint(Long id, String name, String preference) {
050        iId = id;
051        iName = name;
052        iPreference = Constants.preference2preferenceLevel(preference);
053        if (Constants.sPreferenceRequired.equals(preference)) {
054            iRequired = true;
055        } else if (Constants.sPreferenceProhibited.equals(preference)) {
056            iProhibited = true;
057        }
058    }
059    
060    /**
061     * Constraint id that was provided in the constructor
062     * @return constraint id
063     */
064    public Long getConstraintId() { return iId; }
065    
066    @Override
067    public String getName() { return iName; }
068    
069    @Override
070    public String toString() { return "Same Instructor " + getName(); }
071    
072    /**
073     * Is required?
074     * @return true if the constraint is required
075     */
076    public boolean isRequired() { return iRequired; }
077    
078    /**
079     * Is prohibited?
080     * @return true if the constraint is prohibited
081     */
082    public boolean isProhibited() { return iProhibited; }
083    
084    /**
085     * Constraint preference that was provided in the constructor
086     * @return constraint preference
087     */
088    public int getPreference() { return iPreference; }
089    
090    @Override
091    public boolean isHard() {
092        return isRequired() || isProhibited();
093    }
094    
095    /**
096     * Does a pair of teaching assignments satisfy this constraint?  
097     * @param assignment current assignment
098     * @param a1 first teaching assignment
099     * @param a2 second teaching assignment
100     * @return True if the two assignments (of this constraint) have the same instructor (prohibited/preferred case) or a different instructor (prohibited/discouraged case).
101     */
102    public boolean isSatisfiedPair(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment a1, TeachingAssignment a2) {
103        if (isRequired() || (!isProhibited() && getPreference() <= 0))
104            return a1.getInstructor().equals(a2.getInstructor());
105        else if (isProhibited() || (!isRequired() && getPreference() > 0))
106            return !a1.getInstructor().equals(a2.getInstructor());
107        return true;
108    }
109
110    @Override
111    public void computeConflicts(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value, Set<TeachingAssignment> conflicts) {
112        if (isHard()) {
113            for (TeachingRequest.Variable request: variables()) {
114                if (request.equals(value.variable())) continue;
115                
116                TeachingAssignment conflict = assignment.getValue(request);
117                if (conflict == null) continue;
118                
119                if (!isSatisfiedPair(assignment, value, conflict))
120                    conflicts.add(conflict);
121            }
122        }
123    }
124   
125    /**
126     * Current constraint preference (if soft)
127     * @param assignment current assignment
128     * @param value proposed change
129     * @return change in the current preference value of this constraint
130     */
131    public int getCurrentPreference(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value) {
132        if (isHard()) return 0; // no preference
133        if (countAssignedVariables(assignment) + (assignment.getValue(value.variable()) == null ? 1 : 0) < 2) return 0; // not enough variables
134        int nrViolatedPairsAfter = 0;
135        int nrViolatedPairsBefore = 0;
136        for (TeachingRequest.Variable v1 : variables()) {
137            for (TeachingRequest.Variable v2 : variables()) {
138                if (v1.getId() >= v2.getId()) continue;
139                TeachingAssignment p1 = (v1.equals(value.variable()) ? null : assignment.getValue(v1));
140                TeachingAssignment p2 = (v2.equals(value.variable()) ? null : assignment.getValue(v2));
141                if (p1 != null && p2 != null && !isSatisfiedPair(assignment, p1, p2))
142                    nrViolatedPairsBefore ++;
143                if (v1.equals(value.variable())) p1 = value;
144                if (v2.equals(value.variable())) p2 = value;
145                if (p1 != null && p2 != null && !isSatisfiedPair(assignment, p1, p2))
146                    nrViolatedPairsAfter ++;
147            }
148        }
149        return (nrViolatedPairsAfter > 0 ? Math.abs(iPreference) * nrViolatedPairsAfter : 0) - (nrViolatedPairsBefore > 0 ? Math.abs(iPreference) * nrViolatedPairsBefore : 0);
150    }
151    
152    /**
153     * Current constraint preference (if soft)
154     * @param assignment current assignment
155     * @return number of violated pairs weighted by the absolute value of the preference
156     */
157    public int getCurrentPreference(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
158        if (isHard()) return 0; // no preference
159        if (countAssignedVariables(assignment) < 2) return 0; // not enough variable
160        int nrViolatedPairs = 0;
161        for (TeachingRequest.Variable v1 : variables()) {
162            TeachingAssignment p1 = assignment.getValue(v1);
163            if (p1 == null) continue;
164            for (TeachingRequest.Variable v2 : variables()) {
165                TeachingAssignment p2 = assignment.getValue(v2);
166                if (p2 == null || v1.getId() >= v2.getId()) continue;
167                if (!isSatisfiedPair(assignment, p1, p2)) nrViolatedPairs++;
168            }
169        }
170        return (nrViolatedPairs > 0 ? Math.abs(iPreference) * nrViolatedPairs : 0);
171    }
172    
173    @Override
174    public Context createAssignmentContext(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
175        return new Context(assignment);
176    }
177
178    /**
179     * Same Instructor Constraint Context. This context keeps the last preference value and updates the {@link SameInstructor} criterion.
180     *
181     */
182    public class Context implements AssignmentConstraintContext<TeachingRequest.Variable, TeachingAssignment> {
183        private int iLastPreference = 0;
184        
185        public Context(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
186            updateCriterion(assignment);
187        }
188
189        @Override
190        public void assigned(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value) {
191            updateCriterion(assignment);
192        }
193
194        @Override
195        public void unassigned(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value) {
196            updateCriterion(assignment);
197        }
198        
199        /**
200         * Update the current preference value
201         * @param assignment current assignment
202         */
203        private void updateCriterion(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
204            if (!isHard()) {
205                getModel().getCriterion(SameInstructor.class).inc(assignment, -iLastPreference);
206                iLastPreference = getCurrentPreference(assignment);
207                getModel().getCriterion(SameInstructor.class).inc(assignment, iLastPreference);
208            }
209        }
210        
211        /**
212         * Current preference value (see {@link SameInstructorConstraint#getCurrentPreference(Assignment)})
213         * @return current preference value
214         */
215        public int getPreference() { return iLastPreference; }
216    }
217}