    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}