Skip to content

Commit

Permalink
Course Timetabling: Online Conflicts
Browse files Browse the repository at this point in the history
- Online/Offline Room: added a distribution constraints Online/Offline Room that prevents (or penalizes) a pair of classes which are taught on the same day and one is placed in and online room and the other is not

- No Student Online Conflicts: added a global constraint No Student Online Conflicts that prevents a student from having two classes on the same day, one being online and the other not
  - to be registered by setting General.GlobalConstraints=org.cpsolver.coursett.constraint.NoStudentOnlineConflicts

- online classes are identified by a regular expression matching the room name and set in the General.OnlineRoom parameter
  - it defaults to (?i)ONLINE|
  - classes without a room are considered online when the parameter matches a blank string
  - if a class has multiple rooms, all rooms must be online for the class to be considered online
  • Loading branch information
tomas-muller committed Mar 10, 2023
1 parent e5f25e3 commit 7be9455
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 8 deletions.
36 changes: 35 additions & 1 deletion src/org/cpsolver/coursett/constraint/GroupConstraint.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ public class GroupConstraint extends ConstraintWithContext<Lecture, Placement, G
private int iForwardCheckMaxDomainSize = 1000;
private int iNrWorkDays = 5;
private int iFirstWorkDay = 0;
private String iOnlineRoom = null;

/**
* Group constraints that can be checked on pairs of classes (e.g., same room means any two classes are in the same room),
Expand Down Expand Up @@ -1042,7 +1043,26 @@ public ParametrizedConstraintType<Integer[]> create(String reference, String reg
}
return null;
}}),

/**
* Online/Offline Room: Given classes, if scheduled on the same day, must be all in the online room or
* none of them can be in the online room. This means there is a conflict when two classes
* are placed on the same day, but one is in online room and the other is not.
*/
ONLINE_ROOM("ONLINE_ROOM", "Online/Offline Room", new PairCheck() {
@Override
public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
TimeLocation t1 = plc1.getTimeLocation();
TimeLocation t2 = plc2.getTimeLocation();
if (t1.shareDays(t2) && t1.shareWeeks(t2)) {
return gc.isOnline(plc1) == gc.isOnline(plc2);
} else {
// different days > do not care
return true;
}
}
@Override
public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { return true; }
}),
;

String iReference, iName;
Expand Down Expand Up @@ -1198,6 +1218,7 @@ public void setModel(Model<Lecture, Placement> model) {
if (iNrWorkDays <= 0) iNrWorkDays += 7;
if (iNrWorkDays > 7) iNrWorkDays -= 7;
iFirstWorkDay = config.getPropertyInt("General.FirstWorkDay", 0);
iOnlineRoom = config.getProperty("General.OnlineRoom", "(?i)ONLINE|");
}
}

Expand Down Expand Up @@ -2434,4 +2455,17 @@ private boolean isSameDates(TimeLocation t1, TimeLocation t2) {
}
return true;
}

protected boolean isOnline(Placement p) {
// no room -- StudentConflict.OnlineRoom must allow for a blank string
if (p.getNrRooms() == 0)
return "".matches(iOnlineRoom);
// one room -- room name must match StudentConflict.OnlineRoom
if (p.getNrRooms() == 1)
return (p.getRoomLocation().getName() != null && p.getRoomLocation().getName().matches(iOnlineRoom));
// multiple rooms -- all rooms must match StudentConflict.OnlineRoom
for (RoomLocation r: p.getRoomLocations())
if (r.getName() == null || !r.getName().matches(iOnlineRoom)) return false;
return true;
}
}
113 changes: 113 additions & 0 deletions src/org/cpsolver/coursett/constraint/NoStudentOnlineConflicts.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package org.cpsolver.coursett.constraint;

import java.util.Set;

import org.cpsolver.coursett.criteria.additional.StudentOnlineConflict;
import org.cpsolver.coursett.model.Lecture;
import org.cpsolver.coursett.model.Placement;
import org.cpsolver.coursett.model.RoomLocation;
import org.cpsolver.coursett.model.TimetableModel;
import org.cpsolver.ifs.assignment.Assignment;
import org.cpsolver.ifs.model.GlobalConstraint;
import org.cpsolver.ifs.model.Model;
import org.cpsolver.ifs.util.DataProperties;

/**
* An experimental global constraints that prohibits cases where a student has an online and in-person
* class on the same day. Online classes are identified by a regular expression matching the room name
* and set in the General.OnlineRoom parameter (defaults to (?i)ONLINE|). Classes without a
* room are considered online when the General.OnlineRoom parameter matches a blank string.
* If a class has multiple rooms, all rooms must be online for the class to be considered online.
* See {@link StudentOnlineConflict} criterion for a soft variant.
* <br>
*
* @version CourseTT 1.3 (University Course Timetabling)<br>
* Copyright (C) 2013 - 2023 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 NoStudentOnlineConflicts extends GlobalConstraint<Lecture, Placement>{
private String iOnlineRoom = null;

@Override
public void setModel(Model<Lecture, Placement> model) {
super.setModel(model);
if (model != null && model instanceof TimetableModel) {
DataProperties config = ((TimetableModel)model).getProperties();
iOnlineRoom = config.getProperty("General.OnlineRoom", "(?i)ONLINE|");
}
}

@Override
public void computeConflicts(Assignment<Lecture, Placement> assignment, Placement placement, Set<Placement> conflicts) {
Lecture lecture = placement.variable();
for (JenrlConstraint jenrl: lecture.jenrlConstraints()) {
if (jenrl.getJenrl() > 0l) {
Placement other = assignment.getValue(jenrl.another(lecture));
if (isConsistent(placement, other))
conflicts.add(other);
}
}
}

@Override
public boolean inConflict(Assignment<Lecture, Placement> assignment, Placement placement) {
Lecture lecture = placement.variable();
for (JenrlConstraint jenrl: lecture.jenrlConstraints()) {
if (jenrl.getJenrl() > 0l && isConsistent(placement, assignment.getValue(jenrl.another(lecture))))
return true;
}
return false;
}

@Override
public boolean isConsistent(Placement p1, Placement p2) {
if (p1 == null || p2 == null) {
// at least one class is not assigned > not a problem
return false;
} else if (p1.getTimeLocation().shareDays(p2.getTimeLocation()) && p1.getTimeLocation().shareWeeks(p2.getTimeLocation())) {
return isOnline(p1) != isOnline(p2);
} else {
// different days > not a problem
return false;
}
}

protected boolean isOnline(Placement p) {
if (iOnlineRoom == null) return false;
// no room -- General.OnlineRoom must allow for a blank string
if (p.getNrRooms() == 0)
return "".matches(iOnlineRoom);
// one room -- room name must match StudentConflict.OnlineRoom
if (p.getNrRooms() == 1)
return (p.getRoomLocation().getName() != null && p.getRoomLocation().getName().matches(iOnlineRoom));
// multiple rooms -- all rooms must match StudentConflict.OnlineRoom
for (RoomLocation r: p.getRoomLocations())
if (r.getName() == null || !r.getName().matches(iOnlineRoom)) return false;
return true;
}

@Override
public String getName() {
return "No Student Online Conflicts";
}

@Override
public String toString() {
return "No Student Online Conflicts";
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
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.JenrlConstraint;
import org.cpsolver.coursett.criteria.StudentConflict;
import org.cpsolver.coursett.model.Lecture;
import org.cpsolver.coursett.model.Placement;
import org.cpsolver.coursett.model.RoomLocation;
import org.cpsolver.coursett.model.TimetableModel;
import org.cpsolver.ifs.assignment.Assignment;
import org.cpsolver.ifs.util.DataProperties;

/**
* An experimental criterion that tries to minimize cases where a student has an online and in-person
* class on the same day. Online classes are identified by a regular expression matching the room name
* and set in the StudentConflict.OnlineRoom parameter (defaults to (?i)ONLINE|). Classes without a
* room are considered online when the StudentConflict.OnlineRoom parameter matches a blank string.
* and set in the General.OnlineRoom parameter (defaults to (?i)ONLINE|). Classes without a
* room are considered online when the General.OnlineRoom parameter matches a blank string.
* If a class has multiple rooms, all rooms must be online for the class to be considered online.
* The criterion is weighted by the Comparator.StudentOnlineConflictWeight parameter, defaults
* to one half of the Comparator.StudentConflictWeight.
Expand Down Expand Up @@ -49,7 +45,8 @@ public class StudentOnlineConflict extends StudentConflict {
@Override
public void configure(DataProperties properties) {
super.configure(properties);
iOnlineRoom = properties.getProperty("StudentConflict.OnlineRoom", "(?i)ONLINE|");
iOnlineRoom = properties.getProperty("StudentConflict.OnlineRoom",
properties.getProperty("General.OnlineRoom", "(?i)ONLINE|"));
}

public boolean isOnline(Placement p) {
Expand Down

0 comments on commit 7be9455

Please sign in to comment.