diff --git a/src/org/cpsolver/coursett/constraint/GroupConstraint.java b/src/org/cpsolver/coursett/constraint/GroupConstraint.java index 0d7aff31..72c32e3c 100644 --- a/src/org/cpsolver/coursett/constraint/GroupConstraint.java +++ b/src/org/cpsolver/coursett/constraint/GroupConstraint.java @@ -129,6 +129,7 @@ public class GroupConstraint extends ConstraintWithContext 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; @@ -1198,6 +1218,7 @@ public void setModel(Model model) { if (iNrWorkDays <= 0) iNrWorkDays += 7; if (iNrWorkDays > 7) iNrWorkDays -= 7; iFirstWorkDay = config.getPropertyInt("General.FirstWorkDay", 0); + iOnlineRoom = config.getProperty("General.OnlineRoom", "(?i)ONLINE|"); } } @@ -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; + } } diff --git a/src/org/cpsolver/coursett/constraint/NoStudentOnlineConflicts.java b/src/org/cpsolver/coursett/constraint/NoStudentOnlineConflicts.java new file mode 100644 index 00000000..d81a3c00 --- /dev/null +++ b/src/org/cpsolver/coursett/constraint/NoStudentOnlineConflicts.java @@ -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. + *
+ * + * @version CourseTT 1.3 (University Course Timetabling)
+ * Copyright (C) 2013 - 2023 Tomas Muller
+ * muller@unitime.org
+ * http://muller.unitime.org
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not see + * http://www.gnu.org/licenses/. + */ +public class NoStudentOnlineConflicts extends GlobalConstraint{ + private String iOnlineRoom = null; + + @Override + public void setModel(Model 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 assignment, Placement placement, Set 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 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"; + } +} diff --git a/src/org/cpsolver/coursett/criteria/additional/StudentOnlineConflict.java b/src/org/cpsolver/coursett/criteria/additional/StudentOnlineConflict.java index c1d5beb1..25d4462b 100644 --- a/src/org/cpsolver/coursett/criteria/additional/StudentOnlineConflict.java +++ b/src/org/cpsolver/coursett/criteria/additional/StudentOnlineConflict.java @@ -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. @@ -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) {