Skip to content

Commit

Permalink
Course Timetabling: Soft Instructor Constraint
Browse files Browse the repository at this point in the history
- added a soft version of the instructor constraint (instructor conflicts are allowed, but penalized)
  • Loading branch information
tomas-muller committed May 19, 2020
1 parent 4bcb362 commit c8b7992
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 4 deletions.
11 changes: 10 additions & 1 deletion src/org/cpsolver/coursett/TimetableXMLLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.cpsolver.coursett.constraint.MinimizeNumberOfUsedGroupsOfTime;
import org.cpsolver.coursett.constraint.MinimizeNumberOfUsedRoomsConstraint;
import org.cpsolver.coursett.constraint.RoomConstraint;
import org.cpsolver.coursett.constraint.SoftInstructorConstraint;
import org.cpsolver.coursett.constraint.SpreadConstraint;
import org.cpsolver.coursett.constraint.FlexibleConstraint.FlexibleConstraintType;
import org.cpsolver.coursett.model.Configuration;
Expand Down Expand Up @@ -318,10 +319,18 @@ else if (root.attributeValue("term") != null)
if (root.element("instructors") != null) {
for (Iterator<?> i = root.element("instructors").elementIterator("instructor"); i.hasNext();) {
Element instructorEl = (Element) i.next();
InstructorConstraint instructorConstraint = new InstructorConstraint(Long.valueOf(instructorEl
InstructorConstraint instructorConstraint = null;
if ("true".equalsIgnoreCase(instructorEl.attributeValue("soft", "false"))) {
instructorConstraint = new SoftInstructorConstraint(Long.valueOf(instructorEl
.attributeValue("id")), instructorEl.attributeValue("puid"), (instructorEl
.attributeValue("name") != null ? instructorEl.attributeValue("name") : "i"
+ instructorEl.attributeValue("id")), "true".equals(instructorEl.attributeValue("ignDist")));
} else {
instructorConstraint = new InstructorConstraint(Long.valueOf(instructorEl
.attributeValue("id")), instructorEl.attributeValue("puid"), (instructorEl
.attributeValue("name") != null ? instructorEl.attributeValue("name") : "i"
+ instructorEl.attributeValue("id")), "true".equals(instructorEl.attributeValue("ignDist")));
}
if (instructorEl.attributeValue("type") != null)
instructorConstraint.setType(Long.valueOf(instructorEl.attributeValue("type")));
instructorConstraints.put(instructorEl.attributeValue("id"), instructorConstraint);
Expand Down
4 changes: 3 additions & 1 deletion src/org/cpsolver/coursett/TimetableXMLSaver.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.cpsolver.coursett.constraint.MinimizeNumberOfUsedGroupsOfTime;
import org.cpsolver.coursett.constraint.MinimizeNumberOfUsedRoomsConstraint;
import org.cpsolver.coursett.constraint.RoomConstraint;
import org.cpsolver.coursett.constraint.SoftInstructorConstraint;
import org.cpsolver.coursett.constraint.SpreadConstraint;
import org.cpsolver.coursett.model.Configuration;
import org.cpsolver.coursett.model.Lecture;
Expand Down Expand Up @@ -472,7 +473,7 @@ protected void doSave(Element root) {
}

for (InstructorConstraint ic : getModel().getInstructorConstraints()) {
if (iShowNames || ic.isIgnoreDistances()) {
if (iShowNames || ic.isIgnoreDistances() || ic instanceof SoftInstructorConstraint) {
Element instrEl = instructorsEl.addElement("instructor").addAttribute("id",
getId("inst", ic.getResourceId()));
if (iShowNames) {
Expand All @@ -485,6 +486,7 @@ protected void doSave(Element root) {
if (ic.isIgnoreDistances()) {
instrEl.addAttribute("ignDist", "true");
}
if (ic instanceof SoftInstructorConstraint) instrEl.addAttribute("soft", "true");
}
if (ic.getUnavailabilities() != null) {
for (Placement placement: ic.getUnavailabilities()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public boolean isAvailable(Lecture lecture, TimeLocation time) {
return true;
}

private DistanceMetric getDistanceMetric() {
protected DistanceMetric getDistanceMetric() {
return ((TimetableModel)getModel()).getDistanceMetric();
}

Expand Down
178 changes: 178 additions & 0 deletions src/org/cpsolver/coursett/constraint/SoftInstructorConstraint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package org.cpsolver.coursett.constraint;

import java.util.BitSet;
import java.util.Enumeration;
import java.util.Set;

import org.cpsolver.coursett.Constants;
import org.cpsolver.coursett.criteria.additional.InstructorConflict;
import org.cpsolver.coursett.model.Lecture;
import org.cpsolver.coursett.model.Placement;
import org.cpsolver.coursett.model.TimeLocation;
import org.cpsolver.ifs.assignment.Assignment;

/**
* Soft version of the instructor constraint.
*
* @version CourseTT 1.3 (University Course Timetabling)<br>
* Copyright (C) 2006 - 2014 Tomas Muller<br>
* <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
* <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
* <br>
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version. <br>
* <br>
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. <br>
* <br>
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not see
* <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
*/
public class SoftInstructorConstraint extends InstructorConstraint {

public SoftInstructorConstraint(Long id, String puid, String name, boolean ignDist) {
super(id, puid, name, ignDist);
}


@Override
public void computeConflicts(Assignment<Lecture, Placement> assignment, Placement placement, Set<Placement> conflicts) {
return;
}

@Override
public boolean inConflict(Assignment<Lecture, Placement> assignment, Placement placement) {
return false;
}

@Override
public boolean isConsistent(Placement p1, Placement p2) {
return true;
}

@Override
public boolean isHard() {
return false;
}

@Override
public InstructorConstraintContext createAssignmentContext(Assignment<Lecture, Placement> assignment) {
return new SoftInstructorConstraintContext(assignment);
}

public int getConflicts(Assignment<Lecture, Placement> assignment) {
return ((SoftInstructorConstraintContext)getContext(assignment)).getConflicts();
}

public int getConflicts(Assignment<Lecture, Placement> assignment, Placement placement) {
if (((SoftInstructorConstraintContext)getContext(assignment)).inConflict(assignment, placement)) return 1;
return 0;
}

public int getWorstConflicts() {
if (variables().size() < 2) return 0;
return variables().size() - 1;
}

public class SoftInstructorConstraintContext extends InstructorConstraintContext {
private int iConficts = 0;

public SoftInstructorConstraintContext(Assignment<Lecture, Placement> assignment) {
super(assignment);
iConficts = countConflicts(assignment);
getModel().getCriterion(InstructorConflict.class).inc(assignment, iConficts);
}

@Override
public void assigned(Assignment<Lecture, Placement> assignment, Placement placement) {
super.assigned(assignment, placement);
getModel().getCriterion(InstructorConflict.class).inc(assignment, -iConficts);
iConficts = countConflicts(assignment);
getModel().getCriterion(InstructorConflict.class).inc(assignment, iConficts);
}

@Override
public void unassigned(Assignment<Lecture, Placement> assignment, Placement placement) {
super.unassigned(assignment, placement);
getModel().getCriterion(InstructorConflict.class).inc(assignment, -iConficts);
iConficts = countConflicts(assignment);
getModel().getCriterion(InstructorConflict.class).inc(assignment, iConficts);
}

public int getConflicts() { return iConficts; }

protected int countConflicts(Assignment<Lecture, Placement> assignment) {
int conflicts = 0;
for (Lecture lecture: variables()) {
Placement placement = assignment.getValue(lecture);
if (placement != null && inConflict(assignment, placement))
conflicts ++;
}
return conflicts;
}

public boolean inConflict(Assignment<Lecture, Placement> assignment, Placement placement) {
Lecture lecture = placement.variable();
Placement current = assignment.getValue(lecture);
BitSet weekCode = placement.getTimeLocation().getWeekCode();

for (Enumeration<Integer> e = placement.getTimeLocation().getSlots(); e.hasMoreElements();) {
int slot = e.nextElement();
for (Placement p : getPlacements(slot)) {
if (!p.equals(current) && p.getTimeLocation().shareWeeks(weekCode)) {
if (p.canShareRooms(placement) && p.sameRooms(placement))
continue;
return true;
}
}
}
if (!isIgnoreDistances()) {
for (Enumeration<Integer> e = placement.getTimeLocation().getStartSlots(); e.hasMoreElements();) {
int startSlot = e.nextElement();

int prevSlot = startSlot - 1;
if (prevSlot >= 0 && (prevSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) {
for (Placement c : getPlacements(prevSlot, placement)) {
if (lecture.equals(c.variable())) continue;
if (c.canShareRooms(placement) && c.sameRooms(placement)) continue;
if (Placement.getDistanceInMeters(getDistanceMetric(), placement, c) > getDistanceMetric().getInstructorProhibitedLimit())
return true;
}
}
int nextSlot = startSlot + placement.getTimeLocation().getLength();
if ((nextSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) {
for (Placement c : getPlacements(nextSlot, placement)) {
if (lecture.equals(c.variable())) continue;
if (c.canShareRooms(placement) && c.sameRooms(placement)) continue;
if (Placement.getDistanceInMeters(getDistanceMetric(), placement, c) > getDistanceMetric().getInstructorProhibitedLimit())
return true;
}
}

if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) {
TimeLocation t1 = placement.getTimeLocation();
for (Lecture other: variables()) {
Placement otherPlacement = assignment.getValue(other);
if (otherPlacement == null || other.equals(placement.variable())) continue;
TimeLocation t2 = otherPlacement.getTimeLocation();
if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue;
if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot()) {
if (Placement.getDistanceInMinutes(getDistanceMetric(), placement, otherPlacement) > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength()))
return true;
} else if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) {
if (Placement.getDistanceInMinutes(getDistanceMetric(), placement, otherPlacement) > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength()))
return true;
}
}
}
}
}
return false;
}
}
}
125 changes: 125 additions & 0 deletions src/org/cpsolver/coursett/criteria/additional/InstructorConflict.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package org.cpsolver.coursett.criteria.additional;

import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.cpsolver.coursett.constraint.InstructorConstraint;
import org.cpsolver.coursett.constraint.SoftInstructorConstraint;
import org.cpsolver.coursett.criteria.TimetablingCriterion;
import org.cpsolver.coursett.model.Lecture;
import org.cpsolver.coursett.model.Placement;
import org.cpsolver.coursett.model.TimetableModel;
import org.cpsolver.ifs.assignment.Assignment;
import org.cpsolver.ifs.util.DataProperties;

/**
* Instructor conflict counting criterion. Used by the {@link SoftInstructorConstraint}.
*
* @version CourseTT 1.3 (University Course Timetabling)<br>
* Copyright (C) 2006 - 2014 Tomas Muller<br>
* <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
* <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
* <br>
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version. <br>
* <br>
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. <br>
* <br>
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not see
* <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
*/
public class InstructorConflict extends TimetablingCriterion {

public InstructorConflict() {
setValueUpdateType(ValueUpdateType.NoUpdate);
}

@Override
public double getWeightDefault(DataProperties config) {
return config.getPropertyDouble("Comparator.InstructorConflictWeight", 100.0);
}

@Override
public String getPlacementSelectionWeightName() {
return "Placement.InstructorConflictWeight";
}

protected int penalty(Assignment<Lecture, Placement> assignment, Placement value) {
int ret = 0;
for (InstructorConstraint ic: value.variable().getInstructorConstraints()) {
if (ic instanceof SoftInstructorConstraint)
ret += ((SoftInstructorConstraint)ic).getConflicts(assignment, value);
}
return ret;
}

@Override
public double getValue(Assignment<Lecture, Placement> assignment, Placement value, Set<Placement> conflicts) {
double ret = penalty(assignment, value);
if (conflicts != null)
for (Placement conflict: conflicts)
ret -= penalty(assignment, conflict);
return ret;
}

@Override
public double getValue(Assignment<Lecture, Placement> assignment, Collection<Lecture> variables) {
double ret = 0;
Set<InstructorConstraint> constraints = new HashSet<InstructorConstraint>();
for (Lecture lect: variables) {
for (InstructorConstraint ic: lect.getInstructorConstraints()) {
if (!constraints.add(ic)) continue;
if (ic instanceof SoftInstructorConstraint)
ret += ((SoftInstructorConstraint)ic).getConflicts(assignment);
}
}
return ret;
}

@Override
protected double[] computeBounds(Assignment<Lecture, Placement> assignment) {
double[] bounds = new double[] { 0.0, 0.0 };
for (InstructorConstraint ic: ((TimetableModel)getModel()).getInstructorConstraints())
if (ic instanceof SoftInstructorConstraint)
bounds[1] += ((SoftInstructorConstraint)ic).getWorstConflicts();
return bounds;
}

@Override
public double[] getBounds(Assignment<Lecture, Placement> assignment, Collection<Lecture> variables) {
double[] bounds = new double[] { 0.0, 0.0 };
Set<InstructorConstraint> constraints = new HashSet<InstructorConstraint>();
for (Lecture lect: variables) {
for (InstructorConstraint ic: lect.getInstructorConstraints()) {
if (!constraints.add(ic)) continue;
if (ic instanceof SoftInstructorConstraint)
bounds[1] += ((SoftInstructorConstraint)ic).getWorstConflicts();
}
}
return bounds;
}

@Override
public void getInfo(Assignment<Lecture, Placement> assignment, Map<String, String> info) {
super.getInfo(assignment, info);
double conf = getValue(assignment);
if (conf > 0.0)
info.put("Instructor conflicts", sDoubleFormat.format(conf));
}

@Override
public void getInfo(Assignment<Lecture, Placement> assignment, Map<String, String> info, Collection<Lecture> variables) {
super.getInfo(assignment, info, variables);
double conf = getValue(assignment, variables);
if (conf > 0.0)
info.put("Instructor conflicts", sDoubleFormat.format(conf));
}
}
2 changes: 1 addition & 1 deletion src/org/cpsolver/coursett/model/Lecture.java
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ public List<Placement> computeValues(Assignment<Lecture, Placement> assignment,
continue;
boolean notAvailable = false;
for (InstructorConstraint ic : getInstructorConstraints()) {
if (!ic.isAvailable(this, timeLocation)) {
if (!ic.isAvailable(this, timeLocation) && ic.isHard()) {
notAvailable = true;
break;
}
Expand Down
5 changes: 5 additions & 0 deletions src/org/cpsolver/coursett/model/TimetableModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.cpsolver.coursett.criteria.TimeViolations;
import org.cpsolver.coursett.criteria.TooBigRooms;
import org.cpsolver.coursett.criteria.UselessHalfHours;
import org.cpsolver.coursett.criteria.additional.InstructorConflict;
import org.cpsolver.coursett.criteria.placement.DeltaTimePreference;
import org.cpsolver.coursett.criteria.placement.HardConflicts;
import org.cpsolver.coursett.criteria.placement.PotentialHardConflicts;
Expand Down Expand Up @@ -155,6 +156,10 @@ public TimetableModel(DataProperties properties) {
sLogger.error("Unable to use " + criterion + ": " + e.getMessage());
}
}
if (properties.getPropertyBoolean("General.SoftInstructorConstraints", false)) {
InstructorConflict ic = new InstructorConflict(); ic.configure(properties);
addCriterion(ic);
}
try {
String studentSectioningClassName = properties.getProperty("StudentSectioning.Class", DefaultStudentSectioning.class.getName());
Class<?> studentSectioningClass = Class.forName(studentSectioningClassName);
Expand Down

0 comments on commit c8b7992

Please sign in to comment.