Skip to content

Commit

Permalink
Batch Student Scheduling: Standard Selection (IFS)
Browse files Browse the repository at this point in the history
- in the enrollment selection, allow to only select values with conflicts that can be unassigned
  (passing the AssignmentCheck interface from StandardSelection and CriticalStandardSelection who already implement the canUnassign method)
- Standard Selection: added ability to only select neighbors that are not worsening the total value (when Neighbour.StandardCanWorsen is set to false, default to true)
- Critical Standard Selection: always only select neighbors that are not worsening the total value
  • Loading branch information
tomas-muller committed Nov 16, 2023
1 parent 84de73e commit edf1e54
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 15 deletions.
40 changes: 40 additions & 0 deletions src/org/cpsolver/studentsct/heuristics/AssignmentCheck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.cpsolver.studentsct.heuristics;

import org.cpsolver.ifs.assignment.Assignment;
import org.cpsolver.ifs.model.Value;
import org.cpsolver.ifs.model.Variable;

/**
* Simple interface providing an assignment check for the {@link EnrollmentSelection}.
*
* @version StudentSct 1.3 (Student Sectioning)<br>
* Copyright (C) 2007 - 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 interface AssignmentCheck<V extends Variable<V, T>, T extends Value<V, T>> {

/**
* Check whether the given conflict can be unassigned
* @param value value to be assigned
* @param conflict conflicting value
* @param assignment current assignment
* @return true if the conflict can be unassigned due to the given value
*/
public boolean canUnassign(T value, T conflict, Assignment<V, T> assignment);

}
53 changes: 41 additions & 12 deletions src/org/cpsolver/studentsct/heuristics/EnrollmentSelection.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ public void init(Solver<Request, Enrollment> solver) {
* @param value given value
* @return true if it is allowed
**/
public boolean isAllowed(Assignment<Request, Enrollment> assignment, Enrollment value) {
return isAllowed(assignment, value, null);
public boolean isAllowed(Assignment<Request, Enrollment> assignment, Enrollment value, AssignmentCheck<Request, Enrollment> test) {
return isAllowed(assignment, value, null, test);
}

/** true, if it is allowed to assign given value
Expand All @@ -123,32 +123,57 @@ public boolean isAllowed(Assignment<Request, Enrollment> assignment, Enrollment
* @param conflicts conflicting assignments
* @return true if it is allowed
**/
public boolean isAllowed(Assignment<Request, Enrollment> assignment, Enrollment value, Set<Enrollment> conflicts) {
public boolean isAllowed(Assignment<Request, Enrollment> assignment, Enrollment value, Set<Enrollment> conflicts, AssignmentCheck<Request, Enrollment> test) {
if (value == null)
return true;
StudentSectioningModel model = (StudentSectioningModel) value.variable().getModel();
if (model.getNrLastLikeRequests(false) == 0 || model.getNrRealRequests(false) == 0)
if (model.getNrLastLikeRequests(false) == 0 || model.getNrRealRequests(false) == 0) {
// all students are dummy or all are real
if (test != null) {
// there is an assignment check >> check if all conflicts can be unassigned
if (conflicts == null)
conflicts = value.variable().getModel().conflictValues(assignment, value);
for (Enrollment conflict : conflicts)
if (!test.canUnassign(value, conflict, assignment)) return false;
}
return true;
}
Request request = value.variable();
if (request.getStudent().isDummy()) {
// dummy student cannot unassign real student
if (conflicts == null)
conflicts = value.variable().getModel().conflictValues(assignment, value);
for (Enrollment conflict : conflicts) {
if (!conflict.getRequest().getStudent().isDummy())
return false;
if (test != null && !test.canUnassign(value, conflict, assignment))
return false;
}
} else {
// real student
if (conflicts == null)
conflicts = value.variable().getModel().conflictValues(assignment, value);
if (conflicts.size() > (assignment.getValue(request) == null ? 1 : 0))
return false;
if (test == null) {
// no assignment check >> legacy behavior
if (conflicts.size() > (assignment.getValue(request) == null ? 1 : 0))
return false;
} else {
// there is an assignment check >> check if all conflicts can be unassigned
for (Enrollment conflict : conflicts)
if (!test.canUnassign(value, conflict, assignment))
return false;
}
}
return true;
}

/** Value selection */
@Override
public Enrollment selectValue(Solution<Request, Enrollment> solution, Request selectedVariable) {
return selectValue(solution, selectedVariable, null);
}

public Enrollment selectValue(Solution<Request, Enrollment> solution, Request selectedVariable, AssignmentCheck<Request, Enrollment> test) {
Assignment<Request, Enrollment> assignment = solution.getAssignment();
if (iMPP) {
if (selectedVariable.getInitialAssignment() != null) {
Expand All @@ -157,11 +182,11 @@ public Enrollment selectValue(Solution<Request, Enrollment> solution, Request se
iMPPLimit = solution.getModel().perturbVariables(assignment).size() - 1;
}
if (iMPPLimit >= 0 && solution.getModel().perturbVariables(assignment).size() > iMPPLimit) {
if (isAllowed(assignment, selectedVariable.getInitialAssignment()))
if (isAllowed(assignment, selectedVariable.getInitialAssignment(), test))
return selectedVariable.getInitialAssignment();
}
if (selectedVariable.getInitialAssignment() != null && ToolBox.random() <= iInitialSelectionProb) {
if (isAllowed(assignment, selectedVariable.getInitialAssignment()))
if (isAllowed(assignment, selectedVariable.getInitialAssignment(), test))
return selectedVariable.getInitialAssignment();
}
}
Expand All @@ -170,7 +195,7 @@ public Enrollment selectValue(Solution<Request, Enrollment> solution, Request se
List<Enrollment> values = selectedVariable.values(assignment);
if (ToolBox.random() <= iRandomWalkProb) {
Enrollment value = ToolBox.random(values);
if (isAllowed(assignment, value))
if (isAllowed(assignment, value, test))
return value;
}
if (iProp != null && assignment.getValue(selectedVariable) == null && ToolBox.random() <= iGoodSelectionProb) {
Expand All @@ -180,7 +205,7 @@ public Enrollment selectValue(Solution<Request, Enrollment> solution, Request se
}
if (values.size() == 1) {
Enrollment value = values.get(0);
if (isAllowed(assignment, value))
if (isAllowed(assignment, value, test))
return value;
else
return null;
Expand All @@ -199,7 +224,7 @@ public Enrollment selectValue(Solution<Request, Enrollment> solution, Request se
if (conf.contains(value))
continue;

if (!isAllowed(assignment, value, conf))
if (!isAllowed(assignment, value, conf, test))
continue;

double weightedConflicts = (iStat == null || iWeightWeightedCoflicts == 0.0 ? 0.0 : iStat.countRemovals(solution.getIteration(), conf, value));
Expand Down Expand Up @@ -227,11 +252,15 @@ public Enrollment selectValue(Solution<Request, Enrollment> solution, Request se
if (iMPPLimit >= 0 && (solution.getModel().perturbVariables(assignment).size() + deltaInitialAssignments) > iMPPLimit)
continue;
}

double val = value.toDouble(assignment);
for (Enrollment c: conf)
val -= c.toDouble(assignment);

double weightedSum = (iWeightDeltaInitialAssignment * deltaInitialAssignments)
+ (iWeightPotentialConflicts * potentialConflicts) + (iWeightWeightedCoflicts * weightedConflicts)
+ (iWeightCoflicts * conf.size())
+ (iWeightValue * value.toDouble(assignment));
+ (iWeightValue * val);

if (bestValues == null || bestWeightedSum > weightedSum) {
bestWeightedSum = weightedSum;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.cpsolver.ifs.assignment.Assignment;
import org.cpsolver.ifs.heuristics.ValueSelection;
import org.cpsolver.ifs.heuristics.VariableSelection;
import org.cpsolver.ifs.model.SimpleNeighbour;
import org.cpsolver.ifs.solution.Solution;
import org.cpsolver.ifs.solver.Solver;
import org.cpsolver.ifs.util.DataProperties;
Expand Down Expand Up @@ -82,6 +83,14 @@ public boolean canUnassign(Enrollment enrollment, Enrollment conflict, Assignmen
return true;
}

/**
* Only accept neighbors that are not worsening the solution
*/
@Override
public boolean accept(SimpleNeighbour<Request, Enrollment> n, Solution<Request, Enrollment> solution) {
return n.value(solution.getAssignment()) <= 0.0;
}

/**
* Returns the unassigned critical course requests in a random order.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import org.cpsolver.ifs.util.DataProperties;
import org.cpsolver.ifs.util.Progress;
import org.cpsolver.studentsct.filter.StudentFilter;
import org.cpsolver.studentsct.heuristics.AssignmentCheck;
import org.cpsolver.studentsct.heuristics.EnrollmentSelection;
import org.cpsolver.studentsct.model.Enrollment;
import org.cpsolver.studentsct.model.Request;

Expand Down Expand Up @@ -62,7 +64,7 @@
* License along with this library; if not see
* <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
*/
public class StandardSelection implements NeighbourSelection<Request, Enrollment> {
public class StandardSelection implements NeighbourSelection<Request, Enrollment>, AssignmentCheck<Request, Enrollment> {
private long iIteration = 0;
protected ValueSelection<Request, Enrollment> iValueSelection = null;
protected VariableSelection<Request, Enrollment> iVariableSelection = null;
Expand All @@ -71,6 +73,7 @@ public class StandardSelection implements NeighbourSelection<Request, Enrollment
protected long iConflictTimeOut = -7200;
protected long iTimeOut = -3600;
protected boolean iCanConflict = true;
protected boolean iCanWorsen = true;
protected boolean iCanHigherPriorityConflict = true;

/**
Expand All @@ -96,6 +99,7 @@ public StandardSelection(DataProperties properties, VariableSelection<Request, E
if (iConflictTimeOut < 0)
iConflictTimeOut = Math.max(0, properties.getPropertyLong("Termination.TimeOut", -1l) + iConflictTimeOut);
iCanConflict = properties.getPropertyBoolean("Neighbour.StandardCanConflict", true);
iCanWorsen = properties.getPropertyBoolean("Neighbour.StandardCanWorsen", false);
iCanHigherPriorityConflict = properties.getPropertyBoolean("Neighbour.StandardCanHigherPriorityConflict", true);
}

Expand Down Expand Up @@ -129,6 +133,7 @@ protected void init(Solver<Request, Enrollment> solver, String name) {
* @param conflict given enrollment
* @return if running MPP, do not unassign initial enrollments
*/
@Override
public boolean canUnassign(Enrollment enrollment, Enrollment conflict, Assignment<Request, Enrollment> assignment) {
if (!iCanConflict) return false;
if (!iCanHigherPriorityConflict && conflict.getRequest().getPriority() < enrollment.getRequest().getPriority()) return false;
Expand All @@ -143,6 +148,15 @@ public boolean canUnassign(Enrollment enrollment, Enrollment conflict, Assignmen
}
return true;
}

/**
* Check whether the given neighbors can be returned
* @return by default, any neighbors is accepted
*/
public boolean accept(SimpleNeighbour<Request, Enrollment> n, Solution<Request, Enrollment> solution) {
if (iCanWorsen) return true;
return n.value(solution.getAssignment()) <= 0.0;
}

/**
* Employ the provided {@link VariableSelection} and {@link ValueSelection}
Expand All @@ -159,13 +173,17 @@ public Neighbour<Request, Enrollment> selectNeighbour(Solution<Request, Enrollme
try {
Request request = iVariableSelection.selectVariable(solution);
if (request == null) continue;
Enrollment enrollment = iValueSelection.selectValue(solution, request);
Enrollment enrollment =
(iValueSelection instanceof EnrollmentSelection)
? ((EnrollmentSelection)iValueSelection).selectValue(solution, request, this)
: iValueSelection.selectValue(solution, request);
if (enrollment == null) continue;
Set<Enrollment> conflicts = enrollment.variable().getModel().conflictValues(solution.getAssignment(), enrollment);
if (conflicts.contains(enrollment)) continue;
for (Enrollment conflict: conflicts)
if (!canUnassign(enrollment, conflict, solution.getAssignment())) continue attempts;
return new SimpleNeighbour<Request, Enrollment>(request, enrollment, conflicts);
SimpleNeighbour<Request, Enrollment> n = new SimpleNeighbour<Request, Enrollment>(request, enrollment, conflicts);
if (accept(n, solution)) return n;
} catch (ConcurrentModificationException e) {}
}
return null;
Expand Down

0 comments on commit edf1e54

Please sign in to comment.