I am having an issue regarding scheduling in google or tools.
I am trying to build scheduler based on how many employees needed for a shift so during the day I need 10 and then from 17:00 to 20:00 (night shift) I may need anything from 10% to 100% of the employees working.
They can work a minimum from 8 hours a day to 12 hours. They can work a minimum of 45 hours a week to 60 hours a week, They can work a max of consecutive days then they need a day off.
Here is what I have so far
public class ShiftSchedulingSat
{
static void Main(string[] args)
{
SolveShiftScheduling();
}
static void SolveShiftScheduling()
{
int numEmployees = 15;
int numWeeks = 1;
//Shift day "0" day off
//Shift day "8" hours for the day
//Shift day "12" hours for the day
var shifts = new[] { "0", "8", "12" };
var numDays = numWeeks * 7;
var numShifts = shifts.Length;
LinearExprBuilder obj = LinearExpr.NewBuilder();
//The Demands needed
var weeklyCoverDemands = new int[][] {
new[] { 12, 12 }, // Monday
new[] { 12, 12 }, // Tuesday
new[] { 12, 12 }, // Wednesday
new[] { 12, 12 }, // Thursday
new[] { 12, 12 }, // Friday
new[] { 12, 12 }, // Saturday
new[] { 12, 12 }, // Sunday
};
var model = new CpModel();
BoolVar[,,] work = new BoolVar[numEmployees, numDays, numShifts];
foreach (int e in Range(numEmployees))
{
foreach (int d in Range(numDays))
{
foreach (int s in Range(numShifts))
{
work[e, d, s] = model.NewBoolVar($"work{e}_{d}_{s}");
}
}
}
// Max 3 days
foreach (int e in Range(numEmployees))
{
foreach (int d in Range(2, numDays-2))
{
foreach (int s in Range(numShifts))
{
model.AddBoolOr(new ILiteral[] { work[e, d - 2, s], work[e, d, s].Not(), work[e, d + 2, s] });
}
}
}
// number of required employees for shift 8 hours.
foreach (int e in Range(numEmployees))
{
var temp = new BoolVar[numEmployees];
foreach (int d in Range(numDays))
{
temp[e] = work[e, d, 1];
}
model.Add(LinearExpr.Sum(temp) == 12);
}
// 1 Type of shift per day.
foreach (int e in Range(numEmployees))
{
foreach (int d in Range(numDays))
{
var temp = new BoolVar[numShifts];
foreach (int s in Range(numShifts))
{
temp[s] = work[e, d, s];
}
model.Add(LinearExpr.Sum(temp) == 1);
}
}
// Objective
model.Maximize(obj);
// Solve model
var solver = new CpSolver();
solver.StringParameters = "num_search_workers:8, log_search_progress: true, max_time_in_seconds:30";
var status = solver.Solve(model);
// Print solution
if (status == CpSolverStatus.Optimal || status == CpSolverStatus.Feasible)
{
Console.WriteLine();
var header = " ";
for (int w = 0; w < numWeeks; w++)
{
header += "M T W T F S S ";
}
Console.WriteLine(header);
foreach (int e in Range(numEmployees))
{
var schedule = "";
foreach (int d in Range(numDays))
{
foreach (int s in Range(numShifts))
{
if (solver.BooleanValue(work[e, d, s]))
{
schedule += shifts[s] + " ";
}
}
}
Console.WriteLine($"worker {e}: {schedule}");
}
Console.WriteLine();
Console.WriteLine("Penalties:");
Console.WriteLine();
Console.WriteLine("Statistics");
Console.WriteLine($" - status : {status}");
Console.WriteLine($" - conflicts : {solver.NumConflicts()}");
Console.WriteLine($" - branches : {solver.NumBranches()}");
Console.WriteLine($" - wall time : {solver.WallTime()}");
}
}
/// <summary>
/// Filters an isolated sub-sequence of variables assigned to True.
/// Extract the span of Boolean variables[start, start + length), negate them,
/// and if there is variables to the left / right of this span, surround the
/// span by them in non negated form.
/// </summary>
/// <param name="works">A list of variables to extract the span from.</param>
/// <param name="start">The start to the span.</param>
/// <param name="length">The length of the span.</param>
/// <returns>An array of variables which conjunction will be false if the
/// sub-list is assigned to True, and correctly bounded by variables assigned
/// to False, or by the start or end of works.</returns>
static ILiteral[] NegatedBoundedSpan(BoolVar[] works, int start, int length)
{
var sequence = new List<ILiteral>();
if (start > 0)
sequence.Add(works[start - 1]);
foreach (var i in Range(length))
sequence.Add(works[start + i].Not());
if (start + length < works.Length)
sequence.Add(works[start + length]);
return sequence.ToArray();
}
/// <summary>
/// C# equivalent of Python range (start, stop)
/// </summary>
/// <param name="start">The inclusive start.</param>
/// <param name="stop">The exclusive stop.</param>
/// <returns>A sequence of integers.</returns>
static IEnumerable<int> Range(int start, int stop)
{
foreach (var i in Enumerable.Range(start, stop - start))
yield return i;
}
/// <summary>
/// C# equivalent of Python range (stop)
/// </summary>
/// <param name="stop">The exclusive stop.</param>
/// <returns>A sequence of integers.</returns>
static IEnumerable<int> Range(int stop)
{
return Range(0, stop);
}
}
I tried following this ShiftSchedulingSat guide
But keep getting infeasible results if add my constraints
Any suggestion? Could it be modelled in another way?