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}