    001package org.cpsolver.instructor.constraints;
    002
    003import java.util.HashSet;
    004import java.util.Set;
    005
    006import org.cpsolver.coursett.Constants;
    007import org.cpsolver.ifs.assignment.Assignment;
    008import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
    009import org.cpsolver.ifs.assignment.context.ConstraintWithContext;
    010import org.cpsolver.instructor.criteria.SameLink;
    011import org.cpsolver.instructor.model.Instructor;
    012import org.cpsolver.instructor.model.TeachingAssignment;
    013import org.cpsolver.instructor.model.TeachingRequest;
    014
    015/**
    016 * Same Link Constraint. Much like the {@link SameInstructorConstraint}, but it is possible to assign multiple instructors
    017 * to the teaching requests of the same link. It is, however, prohibited (or discouraged) for an instructor to teach
    018 * have teaching requests than the ones of the link. In prohibited / discouraged variant, each request must / should get
    019 * a different instructor.
    020 *
    021 * @version IFS 1.3 (Instructor Sectioning)<br>
    022 *          Copyright (C) 2016 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 SameLinkConstraint extends ConstraintWithContext<TeachingRequest.Variable, TeachingAssignment, SameLinkConstraint.Context> {
    041    private Long iId;
    042    private String iName;
    043    private int iPreference = 0;
    044    private boolean iRequired = false, iProhibited = false;
    045
    046    /**
    047     * Constructor
    048     * @param id constraint id
    049     * @param name link name
    050     * @param preference constraint preference (R for required, etc.)
    051     */
    052    public SameLinkConstraint(Long id, String name, String preference) {
    053        iId = id;
    054        iName = name;
    055        iPreference = Constants.preference2preferenceLevel(preference);
    056        if (Constants.sPreferenceRequired.equals(preference)) {
    057            iRequired = true;
    058        } else if (Constants.sPreferenceProhibited.equals(preference)) {
    059            iProhibited = true;
    060        }
    061    }
    062
    063    /**
    064     * Constraint id that was given in the constructor.
    065     * @return constraint id
    066     */
    067    public Long getConstraintId() { return iId; }
    068
    069    @Override
    070    public String getName() { return iName; }
    071
    072    @Override
    073    public String toString() { return "Same Link " + getName(); }
    074
    075    /**
    076     * Is required?
    077     * @return true if the constraint is required
    078     */
    079    public boolean isRequired() { return iRequired; }
    080
    081    /**
    082     * Is prohibited?
    083     * @return true if the constraint is prohibited
    084     */
    085    public boolean isProhibited() { return iProhibited; }
    086
    087    /**
    088     * Constraint preference that was provided in the constructor
    089     * @return constraint preference
    090     */
    091    public int getPreference() { return iPreference; }
    092
    093    @Override
    094    public boolean isHard() {
    095        return isRequired() || isProhibited();
    096    }
    097
    098    @Override
    099    public void computeConflicts(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value, Set<TeachingAssignment> conflicts) {
    100        if (isHard()) {
    101            Instructor.Context context = value.getInstructor().getContext(assignment);
    102            for (TeachingAssignment ta : context.getAssignments()) {
    103                if (ta.variable().equals(value.variable()) || conflicts.contains(ta))
    104                    continue;
    105                if (isRequired() && !variables().contains(ta.variable()))
    106                    conflicts.add(ta);
    107                if (isProhibited() && variables().contains(ta.variable()))
    108                    conflicts.add(ta);
    109            }
    110        }
    111    }
    112
    113    /**
    114     * Current constraint preference (if soft)
    115     * @param assignment current assignment
    116     * @param value proposed change
    117     * @return change in the current preference value of this constraint
    118     */
    119    public int getCurrentPreference(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value) {
    120        if (isHard()) return 0; // no preference
    121        TeachingAssignment current = assignment.getValue(value.variable());
    122        if (current != null && current.getInstructor().equals(value.getInstructor())) return 0;
    123        int ret = 0;
    124        if (getPreference() < 0) { // preferred
    125            for (TeachingAssignment other : value.getInstructor().getContext(assignment).getAssignments()) {
    126                if (!variables().equals(value.variable()) && !variables().contains(other.variable())) {
    127                    ret++;
    128                }
    129            }
    130            if (current != null) {
    131                for (TeachingAssignment other : current.getInstructor().getContext(assignment).getAssignments()) {
    132                    if (!variables().equals(value.variable()) && !variables().contains(other.variable())) {
    133                        ret--;
    134                    }
    135                }
    136            }
    137        } else if (getPreference() > 0) {
    138            for (TeachingAssignment other : value.getInstructor().getContext(assignment).getAssignments()) {
    139                if (!variables().equals(value.variable()) && variables().contains(other.variable()))
    140                    ret++;
    141            }
    142            if (current != null) {
    143                for (TeachingAssignment other : current.getInstructor().getContext(assignment).getAssignments()) {
    144                    if (!variables().equals(value.variable()) && variables().contains(other.variable())) {
    145                        ret--;
    146                    }
    147                }
    148            }
    149        }
    150        return ret;
    151    }
    152
    153    /**
    154     * Current constraint preference (if soft)
    155     * @param assignment current assignment
    156     * @return that is number of requests that are not of this link assigned to the instructors that have at least one request of this link if preferred;
    157     * number of additional requests of this link given to the same instructor if discouraged
    158     */
    159    public int getCurrentPreference(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
    160        if (isHard()) return 0; // no preference
    161        if (getPreference() < 0) { // preferred
    162            int ret = 0;
    163            Set<Instructor> checked = new HashSet<Instructor>();
    164            for (TeachingRequest.Variable tr: variables()) {
    165                TeachingAssignment ta = assignment.getValue(tr);
    166                if (ta == null || !checked.add(ta.getInstructor())) continue;
    167                Instructor.Context context = ta.getInstructor().getContext(assignment);
    168                for (TeachingAssignment other : context.getAssignments()) {
    169                    if (!variables().contains(other.variable())) {
    170                        ret++;
    171                    }
    172                }
    173            }
    174            return ret;
    175        } else if (getPreference() > 0) {
    176            int ret = 0;
    177            Set<Instructor> checked = new HashSet<Instructor>();
    178            for (TeachingRequest.Variable tr: variables()) {
    179                TeachingAssignment ta = assignment.getValue(tr);
    180                if (ta == null || !checked.add(ta.getInstructor())) continue;
    181                Instructor.Context context = ta.getInstructor().getContext(assignment);
    182                for (TeachingAssignment other : context.getAssignments()) {
    183                    if (!variables().equals(tr) && variables().contains(other.variable())) {
    184                        ret++;
    185                    }
    186                }
    187            }
    188            return ret;
    189        } else {
    190            return 0;
    191        }
    192    }
    193
    194    @Override
    195    public Context createAssignmentContext(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
    196        return new Context(assignment);
    197    }
    198
    199    /**
    200     * Same Link Constraint Context. This context keeps the last preference value and updates the {@link SameLink} criterion.
    201     */
    202    public class Context implements AssignmentConstraintContext<TeachingRequest.Variable, TeachingAssignment> {
    203        private int iLastPreference = 0;
    204
    205        public Context(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
    206            updateCriterion(assignment);
    207        }
    208
    209        @Override
    210        public void assigned(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value) {
    211            updateCriterion(assignment);
    212        }
    213
    214        @Override
    215        public void unassigned(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value) {
    216            updateCriterion(assignment);
    217        }
    218
    219        /**
    220         * Update the current preference value
    221         * @param assignment current assignment
    222         */
    223        private void updateCriterion(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
    224            if (!isHard()) {
    225                getModel().getCriterion(SameLink.class).inc(assignment, -iLastPreference);
    226                iLastPreference = getCurrentPreference(assignment);
    227                getModel().getCriterion(SameLink.class).inc(assignment, iLastPreference);
    228            }
    229        }
    230
    231        /**
    232         * Current preference value (see {@link SameLinkConstraint#getCurrentPreference(Assignment)})
    233         * @return current preference value
    234         */
    235        public int getPreference() { return iLastPreference; }
    236    }
    237}