    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}