Download
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.chocosolver.solver.Model;
import org.chocosolver.solver.variables.IntVar;
/**
* @author Sven Löffler, BTU Cottbus-Senftenberg
* @version 1.0 A Rotating Rostering Problem see the specification in the CSPLib
* (https://www.csplib.org/) for more details.
*/
public class RosteringProblem {
// Early, late, night + day off
private static final int NUMBER_OF_SHIFTS = 3;
// The number of days of each week
private static final int DAYS_PER_WEEK = 7;
/**
* The main method which set the parameters and starts the rostering problem.
* The parameters create the Rostering-8-M-2-4 instance with M presented in the
* CSPLib.
*
* @param args
*/
public static void main(String[] args) {
// w is the number of weeks (here 8)
int w = 8;
// the shift requirement matrix, a 4 times 7 matrix, including at M_i,j the
// needed employees for shift (i-1) and day j (shift types are 0-indexed, the
// matrix M is 1-indexed)
int[][] m = { { 2, 2, 2, 2 }, { 2, 2, 2, 2 }, { 2, 2, 2, 2 }, { 2, 2, 2, 2 }, { 2, 2, 2, 2 }, { 4, 2, 1, 1 },
{ 4, 2, 1, 1 } };
// is the minimum number of days in a row with the same shift (here 2)
int sMin = 2;
// is the maximum number of days in a row with the same shift (here 4)
int sMax = 4;
plan(w, m, sMin, sMax);
}
/**
* Create and solve the Rostering problem given by the parameters.
*
* @param weeks is the number of weeks
* @param shiftCapacity a 4 times 7 matrix, including at M_i,j the needed
* employees for shift (i-1) and day j (shift types are
* 0-indexed, the matrix M is 1-indexed)
* @param sMin is the minimum number of days in a row with the same
* shift
* @param sMax is the maximum number of days in a row with the same
* shift
*/
public static void plan(int weeks, int[][] shiftCapacity, int sMin, int sMax) {
// 1- Create the model
Model model = new Model("Choco4 Model");
// 2- Create the variables
IntVar[][] plan2D = model.intVarMatrix("Plan2D", weeks, DAYS_PER_WEEK, 0, NUMBER_OF_SHIFTS);
IntVar[] plan = flatten(plan2D);
IntVar[][] plan2DT = transpose(plan2D);
// 3- Post constraints
staffingCapacity(model, plan2DT, shiftCapacity);
// values for n = 7
int saturday = 5;
int sunday = 6;
weekdaysWithSameShift(model, plan2D, saturday, sunday);
minimumShiftLengthIs(model, plan, sMin);
maximumShiftLength(model, plan, sMax);
minNFreeDaysInMWeeks(model, plan, 2, 2, weeks);
admissibleShiftOrders(model, plan);
model.getSolver().limitSolution(1);
// 4- start the solver
while (model.getSolver().solve()) {
System.out.println(model.getSolver().getSolutionCount());
// show current solution
for (int i = 0; i < plan2D.length; i++) {
for (int j = 0; j < plan2D[0].length; j++) {
System.out.print(plan2D[i][j].getValue() + ", ");
}
System.out.println();
}
System.out.println();
}
model.getSolver().printStatistics();
}
/**
* flatten a regular 2D array into an 1D array
*
* @param array the 2D array
* @return the coresponding 1D array
*/
public static IntVar[] flatten(IntVar[][] array) {
List<IntVar> list = new ArrayList<IntVar>();
for (IntVar[] i : array) {
list.addAll(Arrays.asList(i));
}
return (IntVar[]) list.toArray(new IntVar[0]);
}
/**
* transpose an array
*
* @param array the original array
* @return the transpoesd array
*/
public static IntVar[][] transpose(IntVar[][] array) {
int rows = array.length;
int cols = array[0].length;
IntVar[][] arrayT = new IntVar[cols][rows];
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
arrayT[i][j] = array[j][i];
}
}
return arrayT;
}
/**
* The CshiftRepetitions 2 constraints
*
* @param model The model to which the constraints are added
* @param plan The array of variables of the model
* @param sMax The maximum number of shift repetitions in a row
*/
public static void maximumShiftLength(Model model, IntVar[] plan, int sMax) {
int days = plan.length;
for (int currentDay = 0; currentDay < days; currentDay++) {
IntVar[] equalDays = new IntVar[sMax];
for (int i = 0; i < sMax; i++) {
equalDays[i] = plan[(currentDay + i) % days];
}
model.ifThen(model.allEqual(equalDays),
model.arithm(plan[currentDay], "!=", plan[(currentDay + sMax) % days]));
}
}
/**
* Create the CshiftRepetitions 1 constraints
*
* @param model The model to which the constraints are added
* @param plan The array of variables of the model
* @param sMin The minimum number of shift repetitions in a row
*/
public static void minimumShiftLengthIs(Model model, IntVar[] plan, int sMin) {
int days = plan.length;
for (int currentDay = 0; currentDay < days; currentDay++) {
IntVar[] equalDays = new IntVar[sMin];
for (int i = 0; i < sMin; i++) {
equalDays[i] = plan[(currentDay - i + days) % days];
}
model.ifThen(model.arithm(plan[currentDay], "!=", plan[(currentDay + 1) % days]),
model.allEqual(equalDays));
}
}
/**
* Create the CrestDays constraints
*
* @param model The model to which the constraints are added
* @param plan The array of variables of the model
* @param nDays The needed number of rest days
* @param mWeeks The number of weeks in which the rest days are needed
* @param numberOfWeeks The number of weeks of the problem
*/
public static void minNFreeDaysInMWeeks(Model model, IntVar[] plan, int nDays, int mWeeks, int numberOfWeeks) {
// build data structure and propagate constraint to express, that
// for each two weeks there are 2 days free at minimum
int days = DAYS_PER_WEEK * mWeeks;
for (int currentDay = 0; currentDay < DAYS_PER_WEEK; currentDay++) {
IntVar[] arrayOfTwoWeeks = new IntVar[DAYS_PER_WEEK * mWeeks];
for (int day = 0; day < mWeeks * DAYS_PER_WEEK; day++) {
arrayOfTwoWeeks[day] = plan[(currentDay * DAYS_PER_WEEK + day) % days];
}
model.count(0, arrayOfTwoWeeks, model.intVar(nDays, DAYS_PER_WEEK * mWeeks)).post();
}
}
/**
* Create the CequalDays constraints
*
* @param model The model to which the constraints are added
* @param plan2D The two dimensional array of variables of the model
* @param indexDayA The first index of day of every week which should be equal
* @param indexDayB The second index of day of every week which should be equal
*/
public static void weekdaysWithSameShift(Model model, IntVar[][] plan2D, int indexDayA, int indexDayB) {
// a stuff member has always the same shift on weekdays a and b
int weeks = plan2D.length;
for (int currentWeekNumber = 0; currentWeekNumber < weeks; currentWeekNumber++) {
model.arithm(plan2D[currentWeekNumber][indexDayA], "=", plan2D[currentWeekNumber][indexDayB]).post();
}
}
/**
* Create the CshiftOrder constraints
*
* @param model The model to which the constraints are added
* @param plan The array of variables of the model
*/
public static void admissibleShiftOrders(Model model, IntVar[] plan) {
int days = plan.length;
// propagate followRelation constraint on all days
for (int currentDay = 0; currentDay < days; currentDay++) {
model.or(model.arithm(plan[currentDay], "<=", plan[(currentDay + 1) % days]),
model.arithm(plan[(currentDay + 1) % days], "=", 0)).post();
}
}
/**
* Create the CshiftRequirements constraints
*
* @param model The model to which the constraints are added
* @param plan2DT The two dimensional array of variables of the model
* @param shiftCapacity The shift capacity matrix
*/
public static void staffingCapacity(Model model, IntVar[][] plan2DT, int[][] shiftCapacity) {
int daysPerWeek = plan2DT.length;
int[] shiftValues = new int[NUMBER_OF_SHIFTS + 1];
for (int i = 0; i <= NUMBER_OF_SHIFTS; i++) {
shiftValues[i] = i;
}
IntVar[][] luDaysIntVar = new IntVar[shiftCapacity.length][shiftCapacity[0].length];
for (int x = 0; x < shiftCapacity.length; x++) {
for (int y = 0; y < shiftCapacity[0].length; y++) {
luDaysIntVar[x][y] = model.intVar(shiftCapacity[x][y], shiftCapacity[x][y]);
}
}
// propagate constraints on number of staff per shift and weekday
for (int i = 0; i < daysPerWeek - 1; i++) {
model.globalCardinality(plan2DT[i], shiftValues, luDaysIntVar[i], true).post();
}
}
}