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