Skip to content

Commit

Permalink
Student Scheduling: Reservations
Browse files Browse the repository at this point in the history
- Student Scheduling Solver: improved handling of reservations that have restrictions from two or more configurations of the same course
  - by computing and using configuration-specific limit caps and reservation limits
  - this makes the computation of reserved and unreserved space for a config or a section much more accurate

- Online Student Scheduling: corrected the computation of reservation limit for the XReservation in this case
  - this fixes an issue causing some space not being reserved when a reservation was set between a configuration and one or more sections of a different config

- Online Student Scheduling: corrected checking of available space (e.g., when a student clicks Submit Schedule) for such reservations
  - especially when the restrictions are exclusive (only checked at the levels they are set)

- Add/Edit Reservation: corrected computation of the selected spaces when restrictions from two or more configurations are selected
  • Loading branch information
tomas-muller committed May 17, 2024
1 parent 320a309 commit 2528b68
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 39 deletions.
Binary file modified 3rd_party/sources/cpsolver-1.4-SNAPSHOT-sources.jar
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,20 @@ public void computeLimit() {
int total = 0, limit = -1;
boolean totalUnlimited = false, unlimited = false;
for (Config config: iOffering.getConfigs()) {
if (config.getLimit() == null)
totalUnlimited = true;
else
total += config.getLimit();

int limitThisConfig = -1;
Node cfg = iConfigs.get(config.getId());
if (cfg != null && cfg.getValue()) {
if (cfg.getConfig().getLimit() == null)
unlimited = true;
else
limitThisConfig = cfg.getConfig().getLimit();
}

for (Subpart subpart: config.getSubparts()) {
int lim = 0; boolean selected = false;
for (Clazz clazz: subpart.getClasses()) {
Expand All @@ -465,25 +479,16 @@ public void computeLimit() {
selected = true;
}
}
if (selected && (limit < 0 || limit > lim)) { limit = lim; }
if (selected && (limitThisConfig < 0 || limitThisConfig > lim)) { limitThisConfig = lim; }
}
}
int lim = 0; boolean selected = false;
for (Config config: iOffering.getConfigs()) {
if (config.getLimit() == null)
totalUnlimited = true;
else
total += config.getLimit();
Node cfg = iConfigs.get(config.getId());
if (cfg != null && cfg.getValue()) {
selected = true;
if (cfg.getConfig().getLimit() == null)
unlimited = true;

if (limitThisConfig >= 0) {
if (limit < 0)
limit = limitThisConfig;
else
lim += cfg.getConfig().getLimit();
limit += limitThisConfig;
}
}
if (selected && (limit < 0 || limit > lim)) { limit = lim; }
int entered = Integer.MAX_VALUE;
try {
entered = Integer.parseInt(iLimit.getWidget().getValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1320,17 +1320,15 @@ protected void checkRequests(OnlineSectioningServer server, OnlineSectioningHelp

List<XReservation> reservations = new ArrayList<XReservation>();
boolean canAssignOverLimit = false;
reservations: for (XReservation r: offering.getReservations()) {
for (XReservation r: offering.getReservations()) {
if (!r.isApplicable(student, course)) continue;
if (!r.isIncluded(config.getConfigId(), sections)) continue;
if (r.getLimit() >= 0 && r.getLimit() <= enrollments.countEnrollmentsForReservation(r.getReservationId())) {
boolean contain = false;
for (XEnrollment e: enrollments.getEnrollmentsForReservation(r.getReservationId()))
if (e.getStudentId().equals(student.getStudentId())) { contain = true; break; }
if (!contain) continue;
}
if (!r.getConfigsIds().isEmpty() && !r.getConfigsIds().contains(config.getConfigId())) continue;
for (XSection section: sections)
if (r.getSectionIds(section.getSubpartId()) != null && !r.getSectionIds(section.getSubpartId()).contains(section.getSectionId())) continue reservations;
if (r.canAssignOverLimit())
canAssignOverLimit = true;
reservations.add(r);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ public int countEnrollmentsForReservation(Long reservationId) {
return ret == null ? 0 : ret.size();
}

public int countEnrollmentsForReservation(Long reservationId, Long config) {
List<XEnrollment> ret = iReservation2Enrl.get(reservationId);
if (ret == null) return 0;
int count = 0;
for (XEnrollment e: ret) {
if (config.equals(e.getConfigId())) count ++;
}
return count;
}

private boolean contain(List<XEnrollment> enrollments, Long studentId) {
if (studentId == null || enrollments == null) return false;
for (XEnrollment e: enrollments)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public XLearningCommunityReservation(org.cpsolver.studentsct.reservation.Learnin
super(XReservationType.LearningCommunity, reservation);
iStudentIds.addAll(reservation.getStudentIds());
iCourseId = new XCourseId(reservation.getCourse());
iLimit = (int)Math.round(reservation.getLimit());
iLimit = (int)Math.round(reservation.getReservationLimit());
setAllowDisabled(reservation.isAllowDisabled());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,8 @@ public List<XReservation> getConfigReservations(Long configId) {

public int getUnreservedSectionSpace(Long sectionId, XEnrollments enrollments) {
XSection section = getSection(sectionId);
// section is unlimited -> there is unreserved space unless there is an unlimited reservation too
Long configId = getSubpart(section.getSubpartId()).getConfigId();
// section is unlimited -> there is unreserved space unless there is an unlimited reservation too
// (in which case there is no unreserved space)
if (section.getLimit() < 0) {
// exclude reservations that are not directly set on this section
Expand All @@ -402,7 +403,7 @@ public int getUnreservedSectionSpace(Long sectionId, XEnrollments enrollments) {
// ignore reservations NOT set directly on the section
if (!r.hasSectionRestriction(sectionId)) continue;
// there is an unlimited reservation -> no unreserved space
if (r.getLimit() < 0) return 0;
if (r.getLimit(configId) < 0) return 0;
}
return Integer.MAX_VALUE;
}
Expand All @@ -415,9 +416,9 @@ public int getUnreservedSectionSpace(Long sectionId, XEnrollments enrollments) {
// ignore reservations NOT set directly on the section
if (!r.hasSectionRestriction(sectionId)) continue;
// unlimited reservation -> all the space is reserved
if (r.getLimit() < 0.0) return 0;
if (r.getLimit(configId) < 0.0) return 0;
// compute space that can be potentially taken by this reservation
int reserved = r.getReservedAvailableSpace(enrollments);
int reserved = r.getReservedAvailableSpace(enrollments, configId);
// deduct the space from available space
available -= Math.max(0, reserved);
}
Expand All @@ -437,7 +438,7 @@ public int getUnreservedConfigSpace(Long configId, XEnrollments enrollments) {
// ignore reservations NOT set directly on the config
if (!r.hasConfigRestriction(configId)) continue;
// there is an unlimited reservation -> no unreserved space
if (r.getLimit() < 0) return 0;
if (r.getLimit(configId) < 0) return 0;
}
return Integer.MAX_VALUE;
}
Expand All @@ -450,9 +451,9 @@ public int getUnreservedConfigSpace(Long configId, XEnrollments enrollments) {
// ignore reservations NOT set directly on the config
if (!r.hasConfigRestriction(configId)) continue;
// unlimited reservation -> all the space is reserved
if (r.getLimit() < 0) return 0;
if (r.getLimit(configId) < 0) return 0;
// compute space that can be potentially taken by this reservation
double reserved = r.getReservedAvailableSpace(enrollments);
double reserved = r.getReservedAvailableSpace(enrollments, configId);
// deduct the space from available space
available -= Math.max(0, reserved);
}
Expand Down Expand Up @@ -584,12 +585,12 @@ public XReservationId guessReservation(Collection<XCourseRequest> other, XStuden
List<XSection> sections = getSections(enrollment);
for (XReservation reservation: reservations) {
if (reservation.isIncluded(enrollment.getConfigId(), sections)) {
if (reservation.getLimit() < 0.0 || other == null || mustBeUsed)
if (reservation.getLimit(enrollment.getConfigId()) < 0.0 || other == null || mustBeUsed)
return new XReservationId(reservation.getType(), getOfferingId(), reservation.getReservationId());
int used = 0;
for (XCourseRequest r: other)
if (r.getEnrollment() != null && r.getEnrollment().getOfferingId().equals(getOfferingId()) && !enrollment.getStudentId().equals(r.getStudentId()) && reservation.equals(r.getEnrollment().getReservation())) used ++;
if (used < reservation.getLimit())
if (used < reservation.getLimit(enrollment.getConfigId()))
return new XReservationId(reservation.getType(), getOfferingId(), reservation.getReservationId());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public abstract class XReservation extends XReservationId implements Comparable<
private Map<Long, Set<Long>> iSections = new HashMap<Long, Set<Long>>();
private Set<Long> iIds = new HashSet<Long>();
private int iLimitCap = -1;
private Map<Long, Integer> iConfigLimitCap = new HashMap<Long, Integer>();
private double iRestrictivity = 1.0;

private int iPriority = 1000;
Expand Down Expand Up @@ -170,10 +171,7 @@ public XReservation(XReservationType type, XOffering offering, Reservation reser
// config cap
int cap = 0;
for (XConfig config: offering.getConfigs()) {
if (iConfigs.contains(config.getConfigId()))
cap = add(cap, config.getLimit());
}
for (XConfig config: offering.getConfigs()) {
int configCap = config.getLimit();
for (XSubpart subpart: config.getSubparts()) {
Set<Long> sections = iSections.get(subpart.getSubpartId());
if (sections == null) continue;
Expand All @@ -183,7 +181,13 @@ public XReservation(XReservationType type, XOffering offering, Reservation reser
if (sections.contains(section.getSectionId()))
subpartCap = add(subpartCap, section.getLimit());
// minimize
cap = min(cap, subpartCap);
configCap = min(configCap, subpartCap);
}
if (iConfigs.contains(config.getConfigId())) {
cap = add(cap, configCap);
iConfigLimitCap.put(config.getConfigId(), configCap);
} else {
iConfigLimitCap.put(config.getConfigId(), 0);
}
}
iLimitCap = cap;
Expand All @@ -192,8 +196,10 @@ public XReservation(XReservationType type, XOffering offering, Reservation reser
if ((!iConfigs.isEmpty() || !iSections.isEmpty()) && !canAssignOverLimit()) {
int cap = 0;
for (XConfig config: offering.getConfigs()) {
int configCap = 0;
if (iConfigs.contains(config.getConfigId())) {
cap = add(cap, config.getLimit());
configCap = add(configCap, config.getLimit());
} else {
for (XSubpart subpart: config.getSubparts()) {
Set<Long> sections = iSections.get(subpart.getSubpartId());
Expand All @@ -203,8 +209,10 @@ public XReservation(XReservationType type, XOffering offering, Reservation reser
if (sections.contains(section.getSectionId()))
subpartCap = add(subpartCap, section.getLimit());
cap = add(cap, subpartCap);
configCap = add(configCap, subpartCap);
}
}
iConfigLimitCap.put(config.getConfigId(), configCap);
}
iLimitCap = cap;
}
Expand All @@ -216,6 +224,9 @@ public XReservation(XReservationType type, XOffering offering, Reservation reser
public XReservation(XReservationType type, org.cpsolver.studentsct.reservation.Reservation reservation) {
super(type, reservation.getOffering().getId(), reservation.getId());
iLimitCap = (int)Math.round(reservation.getLimitCap());
for (Config config: reservation.getOffering().getConfigs()) {
iConfigLimitCap.put(config.getId(), (int)Math.round(reservation.getLimitCap(config)));
}
iRestrictivity = reservation.getRestrictivity();
iExpirationDate = (reservation.isExpired() ? new Date(0) : null);
iNeverIncluded = reservation.neverIncluded();
Expand Down Expand Up @@ -321,7 +332,10 @@ public Set<Long> getSectionIds(Long subpartId) {

public void setCanAssignOverLimit(boolean canAssignOverLimit) {
iFlags = Flags.CanAssignOverLimit.set(iFlags, canAssignOverLimit);
if (canAssignOverLimit) iLimitCap = -1;
if (canAssignOverLimit) {
iLimitCap = -1;
iConfigLimitCap.clear();
}
}

/**
Expand Down Expand Up @@ -362,6 +376,12 @@ public int getLimit() {
return min(iLimitCap, getReservationLimit());
}

public int getLimit(Long configId) {
if (iConfigLimitCap.isEmpty() || canAssignOverLimit()) return getReservationLimit();
Integer limitCap = iConfigLimitCap.get(configId);
return limitCap == null ? 0 : min(limitCap, getReservationLimit());
}

/**
* True if holding this reservation allows a student to have attend overlapping class.
*/
Expand Down Expand Up @@ -537,6 +557,13 @@ public int getReservedAvailableSpace(XEnrollments enrollments) {
return getLimit() - enrollments.countEnrollmentsForReservation(getReservationId());
}

public int getReservedAvailableSpace(XEnrollments enrollments, Long configId) {
// Unlimited
if (getLimit(configId) < 0) return Integer.MAX_VALUE;

return getLimit(configId) - enrollments.countEnrollmentsForReservation(getReservationId(), configId);
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
Expand All @@ -558,6 +585,10 @@ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundExcept
}
}
iLimitCap = in.readInt();
iConfigLimitCap.clear();
int nrCaps = in.readInt();
for (int i = 0; i < nrCaps; i++)
iConfigLimitCap.put(in.readLong(), in.readInt());
iRestrictivity = in.readDouble();
iPriority = in.readInt();
iFlags = in.readInt();
Expand Down Expand Up @@ -590,6 +621,11 @@ public void writeExternal(ObjectOutput out) throws IOException {
}

out.writeInt(iLimitCap);
out.writeInt(iConfigLimitCap.size());
for (Map.Entry<Long, Integer> e: iConfigLimitCap.entrySet()) {
out.writeLong(e.getKey());
out.writeInt(e.getValue());
}
out.writeDouble(iRestrictivity);
out.writeInt(iPriority);
out.writeInt(iFlags);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,17 +211,15 @@ public List<EnrollmentRequest> check(OnlineSectioningServer server, OnlineSectio

List<XReservation> reservations = new ArrayList<XReservation>();
boolean canAssignOverLimit = false;
reservations: for (XReservation r: offering.getReservations()) {
for (XReservation r: offering.getReservations()) {
if (!r.isApplicable(student, course)) continue;
if (!r.isIncluded(config.getConfigId(), sections)) continue;
if (r.getLimit() >= 0 && r.getLimit() <= enrollments.countEnrollmentsForReservation(r.getReservationId())) {
boolean contain = false;
for (XEnrollment e: enrollments.getEnrollmentsForReservation(r.getReservationId()))
if (e.getStudentId().equals(student.getStudentId())) { contain = true; break; }
if (!contain) continue;
}
if (!r.getConfigsIds().isEmpty() && !r.getConfigsIds().contains(config.getConfigId())) continue;
for (XSection section: sections)
if (r.getSectionIds(section.getSubpartId()) != null && !r.getSectionIds(section.getSubpartId()).contains(section.getSectionId())) continue reservations;
if (r.canAssignOverLimit())
canAssignOverLimit = true;
reservations.add(r);
Expand Down
Binary file modified WebContent/WEB-INF/lib/cpsolver-1.4-SNAPSHOT.jar
Binary file not shown.
15 changes: 15 additions & 0 deletions WebContent/help/Release-Notes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,21 @@
<line>The problem seems to be related to the recent Hibernate 6.2.22 update.</line>
</description>
</item>
<item>
<name>Student Scheduling: Reservations</name>
<description>
<line>Student Scheduling Solver: Improved handling of reservations with restrictions from two or more configurations by computing and using configuration-specific limit caps and reservation limits.
<line>This makes the computation of reserved and unreserved space for a config or a section much more accurate.</line>
</line>
<line>Online Student Scheduling: Corrected the computation of reservation limit for reservations with restrictions from two or more configurations.
<line>This fixes an issue causing some space not to be reserved when a reservation was set between a configuration and one or more sections of a different config.</line>
</line>
<line>Online Student Scheduling: The checking of available space (e.g., when a student clicks Submit Schedule) for reservations with restrictions from two or more configurations has been corrected.
<line>This is especially applicable to reservations marked as exclusive (to be checked only at the levels they are set).</line>
</line>
<line>Add/Edit Reservation: Corrected computation of the selected spaces when restrictions from two or more configurations are selected.</line>
</description>
</item>
</category>
<category>
<title>Examination Timetabling</title>
Expand Down

0 comments on commit 2528b68

Please sign in to comment.