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