static void MyContiguity(Solver solver, IntVar[] x) { // the DFA (for regular) int initial_state = 1; // all states are accepting states int[] accepting_states = { 1, 2, 3 }; // The regular expression 0*1*0* {state, input, next state} long[][] transition_tuples = { new long[] { 1, 0, 1 }, new long[] { 1, 1, 2 }, new long[] { 2, 0, 3 }, new long[] { 2, 1, 2 }, new long[] { 3, 0, 3 } }; IntTupleSet result = new IntTupleSet(3); result.InsertAll(transition_tuples); solver.Add(x.Transition(result, initial_state, accepting_states)); }
static void MyContiguity(Solver solver, IntVar[] x) { // the DFA (for regular) int initial_state = 1; // all states are accepting states int[] accepting_states = {1,2,3}; // The regular expression 0*1*0* {state, input, next state} int[,] transition_tuples = { {1, 0, 1}, {1, 1, 2}, {2, 0, 3}, {2, 1, 2}, {3, 0, 3} }; IntTupleSet result = new IntTupleSet(3); result.InsertAll(transition_tuples); solver.Add(x.Transition(result, initial_state, accepting_states)); }
/** * * Nurse rostering * * This is a simple nurse rostering model using a DFA and * the built-in TransitionConstraint. * * The DFA is from MiniZinc Tutorial, Nurse Rostering example: * - one day off every 4 days * - no 3 nights in a row. * * Also see: * - http://www.hakank.org/or-tools/nurse_rostering.py * - http://www.hakank.org/or-tools/nurse_rostering_regular.cs * which use (a decomposition of) regular constraint * */ private static void Solve(int nurse_multiplier, int week_multiplier) { Console.WriteLine("Starting Nurse Rostering"); Console.WriteLine(" - {0} teams of 7 nurses", nurse_multiplier); Console.WriteLine(" - {0} blocks of 14 days", week_multiplier); Solver solver = new Solver("NurseRostering"); // // Data // // Note: If you change num_nurses or num_days, // please also change the constraints // on nurse_stat and/or day_stat. int num_nurses = 7 * nurse_multiplier; int num_days = 14 * week_multiplier; // Note: I had to add a dummy shift. int dummy_shift = 0; int day_shift = 1; int night_shift = 2; int off_shift = 3; int[] shifts = { dummy_shift, day_shift, night_shift, off_shift }; int[] valid_shifts = { day_shift, night_shift, off_shift }; // the DFA (for regular) int initial_state = 1; int[] accepting_states = { 1, 2, 3, 4, 5, 6 }; /* * // This is the transition function * // used in nurse_rostering_regular.cs * int[,] transition_fn = { * // d,n,o * {2,3,1}, // state 1 * {4,4,1}, // state 2 * {4,5,1}, // state 3 * {6,6,1}, // state 4 * {6,0,1}, // state 5 * {0,0,1} // state 6 * }; */ // For TransitionConstraint IntTupleSet transition_tuples = new IntTupleSet(3); // state, input, next state transition_tuples.InsertAll(new long[][] { new long[] { 1, 1, 2 }, new long[] { 1, 2, 3 }, new long[] { 1, 3, 1 }, new long[] { 2, 1, 4 }, new long[] { 2, 2, 4 }, new long[] { 2, 3, 1 }, new long[] { 3, 1, 4 }, new long[] { 3, 2, 5 }, new long[] { 3, 3, 1 }, new long[] { 4, 1, 6 }, new long[] { 4, 2, 6 }, new long[] { 4, 3, 1 }, new long[] { 5, 1, 6 }, new long[] { 5, 3, 1 }, new long[] { 6, 3, 1 } }); string[] days = { "d", "n", "o" }; // for presentation // // Decision variables // // // For TransitionConstraint // IntVar[,] x = solver.MakeIntVarMatrix(num_nurses, num_days, valid_shifts, "x"); IntVar[] x_flat = x.Flatten(); // // summary of the nurses // IntVar[] nurse_stat = new IntVar[num_nurses]; // // summary of the shifts per day // int num_shifts = shifts.Length; IntVar[,] day_stat = new IntVar[num_days, num_shifts]; for (int i = 0; i < num_days; i++) { for (int j = 0; j < num_shifts; j++) { day_stat[i, j] = solver.MakeIntVar(0, num_nurses, "day_stat"); } } // // Constraints // for (int i = 0; i < num_nurses; i++) { IntVar[] reg_input = new IntVar[num_days]; for (int j = 0; j < num_days; j++) { reg_input[j] = x[i, j]; } solver.Add(reg_input.Transition(transition_tuples, initial_state, accepting_states)); } // // Statistics and constraints for each nurse // for (int nurse = 0; nurse < num_nurses; nurse++) { // Number of worked days (either day or night shift) IntVar[] nurse_days = new IntVar[num_days]; for (int day = 0; day < num_days; day++) { nurse_days[day] = x[nurse, day].IsMember(new int[] { day_shift, night_shift }); } nurse_stat[nurse] = nurse_days.Sum().Var(); // Each nurse must work between 7 and 10 // days/nights during this period solver.Add(nurse_stat[nurse] >= 7 * week_multiplier / nurse_multiplier); solver.Add(nurse_stat[nurse] <= 10 * week_multiplier / nurse_multiplier); } // // Statistics and constraints for each day // for (int day = 0; day < num_days; day++) { IntVar[] nurses = new IntVar[num_nurses]; for (int nurse = 0; nurse < num_nurses; nurse++) { nurses[nurse] = x[nurse, day]; } IntVar[] stats = new IntVar[num_shifts]; for (int shift = 0; shift < num_shifts; ++shift) { stats[shift] = day_stat[day, shift]; } solver.Add(nurses.Distribute(stats)); // // Some constraints for each day: // // Note: We have a strict requirements of // the number of shifts. // Using atleast constraints is harder // in this model. // if (day % 7 == 5 || day % 7 == 6) { // special constraints for the weekends solver.Add(day_stat[day, day_shift] == 2 * nurse_multiplier); solver.Add(day_stat[day, night_shift] == nurse_multiplier); solver.Add(day_stat[day, off_shift] == 4 * nurse_multiplier); } else { // for workdays: // - exactly 3 on day shift solver.Add(day_stat[day, day_shift] == 3 * nurse_multiplier); // - exactly 2 on night solver.Add(day_stat[day, night_shift] == 2 * nurse_multiplier); // - exactly 2 off duty solver.Add(day_stat[day, off_shift] == 2 * nurse_multiplier); } } // // Search // DecisionBuilder db = solver.MakePhase(x_flat, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE); SearchMonitor log = solver.MakeSearchLog(1000000); solver.NewSearch(db, log); int num_solutions = 0; while (solver.NextSolution()) { num_solutions++; for (int i = 0; i < num_nurses; i++) { Console.Write("Nurse #{0,-2}: ", i); var occ = new Dictionary <int, int>(); for (int j = 0; j < num_days; j++) { int v = (int)x[i, j].Value() - 1; if (!occ.ContainsKey(v)) { occ[v] = 0; } occ[v]++; Console.Write(days[v] + " "); } Console.Write(" #workdays: {0,2}", nurse_stat[i].Value()); foreach (int s in valid_shifts) { int v = 0; if (occ.ContainsKey(s - 1)) { v = occ[s - 1]; } Console.Write(" {0}:{1}", days[s - 1], v); } Console.WriteLine(); } Console.WriteLine(); Console.WriteLine("Statistics per day:\nDay d n o"); for (int j = 0; j < num_days; j++) { Console.Write("Day #{0,2}: ", j); foreach (int t in valid_shifts) { Console.Write(day_stat[j, t].Value() + " "); } Console.WriteLine(); } Console.WriteLine(); // We just show 2 solutions if (num_solutions > 1) { break; } } Console.WriteLine("\nSolutions: {0}", solver.Solutions()); Console.WriteLine("WallTime: {0}ms", solver.WallTime()); Console.WriteLine("Failures: {0}", solver.Failures()); Console.WriteLine("Branches: {0} ", solver.Branches()); solver.EndSearch(); }
/** * * Traffic lights problem. * * CSPLib problem 16 * http://www.cs.st-andrews.ac.uk/~ianm/CSPLib/prob/prob016/index.html * """ * Specification: * Consider a four way traffic junction with eight traffic lights. Four of the traffic * lights are for the vehicles and can be represented by the variables V1 to V4 with domains * {r,ry,g,y} (for red, red-yellow, green and yellow). The other four traffic lights are * for the pedestrians and can be represented by the variables P1 to P4 with domains {r,g}. * * The constraints on these variables can be modelled by quaternary constraints on * (Vi, Pi, Vj, Pj ) for 1<=i<=4, j=(1+i)mod 4 which allow just the tuples * {(r,r,g,g), (ry,r,y,r), (g,g,r,r), (y,r,ry,r)}. * * It would be interesting to consider other types of junction (e.g. five roads * intersecting) as well as modelling the evolution over time of the traffic light sequence. * ... * * Results * Only 2^2 out of the 2^12 possible assignments are solutions. * * (V1,P1,V2,P2,V3,P3,V4,P4) = * {(r,r,g,g,r,r,g,g), (ry,r,y,r,ry,r,y,r), (g,g,r,r,g,g,r,r), (y,r,ry,r,y,r,ry,r)} * [(1,1,3,3,1,1,3,3), ( 2,1,4,1, 2,1,4,1), (3,3,1,1,3,3,1,1), (4,1, 2,1,4,1, 2,1)} * The problem has relative few constraints, but each is very * tight. Local propagation appears to be rather ineffective on this * problem. * * """ * Note: In this model we use only the constraint * solver.AllowedAssignments(). * * * See http://www.hakank.org/or-tools/traffic_lights.py * */ private static void Solve() { Solver solver = new Solver("TrafficLights"); // // data // int n = 4; int r = 0; int ry = 1; int g = 2; int y = 3; string[] lights = { "r", "ry", "g", "y" }; // The allowed combinations IntTupleSet allowed = new IntTupleSet(4); allowed.InsertAll(new int[, ] { { r, r, g, g }, { ry, r, y, r }, { g, g, r, r }, { y, r, ry, r } }); // // Decision variables // IntVar[] V = solver.MakeIntVarArray(n, 0, n - 1, "V"); IntVar[] P = solver.MakeIntVarArray(n, 0, n - 1, "P"); // for search IntVar[] VP = new IntVar[2 * n]; for (int i = 0; i < n; i++) { VP[i] = V[i]; VP[i + n] = P[i]; } // // Constraints // for (int i = 0; i < n; i++) { int j = (1 + i) % n; IntVar[] tmp = new IntVar[] { V[i], P[i], V[j], P[j] }; solver.Add(tmp.AllowedAssignments(allowed)); } // // Search // DecisionBuilder db = solver.MakePhase(VP, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE); solver.NewSearch(db); while (solver.NextSolution()) { for (int i = 0; i < n; i++) { Console.Write("{0,2} {1,2} ", lights[V[i].Value()], lights[P[i].Value()]); } Console.WriteLine(); } Console.WriteLine("\nSolutions: {0}", solver.Solutions()); Console.WriteLine("WallTime: {0}ms", solver.WallTime()); Console.WriteLine("Failures: {0}", solver.Failures()); Console.WriteLine("Branches: {0} ", solver.Branches()); solver.EndSearch(); }
/** * * Traffic lights problem. * * CSPLib problem 16 * http://www.csplib.org/Problems/prob016 * """ * Specification: * Consider a four way traffic junction with eight traffic lights. Four of the traffic * lights are for the vehicles and can be represented by the variables V1 to V4 with domains * {r,ry,g,y} (for red, red-yellow, green and yellow). The other four traffic lights are * for the pedestrians and can be represented by the variables P1 to P4 with domains {r,g}. * * The constraints on these variables can be modelled by quaternary constraints on * (Vi, Pi, Vj, Pj ) for 1<=i<=4, j=(1+i)mod 4 which allow just the tuples * {(r,r,g,g), (ry,r,y,r), (g,g,r,r), (y,r,ry,r)}. * * It would be interesting to consider other types of junction (e.g. five roads * intersecting) as well as modelling the evolution over time of the traffic light sequence. * ... * * Results * Only 2^2 out of the 2^12 possible assignments are solutions. * * (V1,P1,V2,P2,V3,P3,V4,P4) = * {(r,r,g,g,r,r,g,g), (ry,r,y,r,ry,r,y,r), (g,g,r,r,g,g,r,r), (y,r,ry,r,y,r,ry,r)} * [(1,1,3,3,1,1,3,3), ( 2,1,4,1, 2,1,4,1), (3,3,1,1,3,3,1,1), (4,1, 2,1,4,1, 2,1)} * The problem has relative few constraints, but each is very * tight. Local propagation appears to be rather ineffective on this * problem. * * """ * Note: In this model we use only the constraint * solver.AllowedAssignments(). * * * See http://www.hakank.org/or-tools/traffic_lights.py * */ private static void Solve() { Solver solver = new Solver("TrafficLights"); // // data // int n = 4; int r = 0; int ry = 1; int g = 2; int y = 3; string[] lights = {"r", "ry", "g", "y"}; // The allowed combinations IntTupleSet allowed = new IntTupleSet(4); allowed.InsertAll(new int[,] {{r,r,g,g}, {ry,r,y,r}, {g,g,r,r}, {y,r,ry,r}}); // // Decision variables // IntVar[] V = solver.MakeIntVarArray(n, 0, n-1, "V"); IntVar[] P = solver.MakeIntVarArray(n, 0, n-1, "P"); // for search IntVar[] VP = new IntVar[2 * n]; for(int i = 0; i < n; i++) { VP[i] = V[i]; VP[i+n] = P[i]; } // // Constraints // for(int i = 0; i < n; i++) { int j = (1+i) % n; IntVar[] tmp = new IntVar[] {V[i],P[i],V[j],P[j]}; solver.Add(tmp.AllowedAssignments(allowed)); } // // Search // DecisionBuilder db = solver.MakePhase(VP, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE); solver.NewSearch(db); while (solver.NextSolution()) { for(int i = 0; i < n; i++) { Console.Write("{0,2} {1,2} ", lights[V[i].Value()], lights[P[i].Value()]); } Console.WriteLine(); } Console.WriteLine("\nSolutions: {0}", solver.Solutions()); Console.WriteLine("WallTime: {0}ms", solver.WallTime()); Console.WriteLine("Failures: {0}", solver.Failures()); Console.WriteLine("Branches: {0} ", solver.Branches()); solver.EndSearch(); }
/// <summary> /// Get a new schedule based on number of teams and days. /// Provide a rule set for transitions and day and employee constraints /// </summary> /// <param name="ruleSet">Rule set to use for transitions</param> /// <param name="shiftEmployees">Participating employees for the shifts</param> /// <param name="startDate">Schedule start date</param> /// <param name="numberOfDays">Number of days in each cycle</param> /// <param name="teamSize">Number of employees in each team</param> /// <param name="minShiftsPerCycle">Required number of shifts per cycle</param> /// <param name="startHour">Starting hour of the day shift</param> /// <param name="shiftHours">Hours in every shift, up to 12</param> /// <param name="maxSolutions">Max returned results</param> /// <returns></returns> public Task <List <Schedule> > CreateNewScheduleAsync( RuleSet ruleSet, IList <Employee> shiftEmployees, DateTime startDate, int numberOfDays, int teamSize, int minShiftsPerCycle, int startHour, int shiftHours, int maxSolutions = 1) { // Some sanity checks if (ruleSet == null) { throw new ArgumentOutOfRangeException($"Rule set is empty."); } if (shiftEmployees == null || shiftEmployees.Count < 1) { throw new ArgumentOutOfRangeException($"Employee collection is empty."); } if (numberOfDays < 1) { throw new ArgumentOutOfRangeException($"Invalid number for days in a cycle."); } if (teamSize < 1) { throw new ArgumentOutOfRangeException($"Invalid number for employees in each shift."); } if (minShiftsPerCycle < 0) { throw new ArgumentOutOfRangeException($"Invalid number for minimum shifts per cycle."); } if (startHour > 12) { throw new ArgumentOutOfRangeException( $"Starting hour is bigger than expected. Please provide a number between 0-12"); } if (shiftHours > 12) { throw new ArgumentOutOfRangeException( $"Shift hours cannot be bigger than twelve. Please provide a number between 1-12"); } var numberOfEmployees = shiftEmployees.Count; LastError = null; AddDiagnostics("Starting to solve a new schedule\n\n"); AddDiagnostics("This is a schedule for {0} employees in {1} days\n", numberOfEmployees, numberOfDays); AddDiagnostics("Shift team size: {0}, minimum shifts per employee: {1}\n\n", teamSize, minShiftsPerCycle); /* * Solver */ // Initiate a new solver var solver = new Solver("Schedule"); int[] shifts = { ShiftConsts.None, ShiftConsts.Day, ShiftConsts.Night, ShiftConsts.Off }; int[] validShifts = { ShiftConsts.Day, ShiftConsts.Night, ShiftConsts.Off }; /* * DFA and Transitions */ var initialState = ruleSet.InitialState; // Everybody starts at this state int[] acceptingStates = ruleSet.AcceptingStates; // Transition tuples For TransitionConstraint var transitionTuples = new IntTupleSet(3); // Every tuple contains { state, input, next state } transitionTuples.InsertAll(ruleSet.Tuples); // Just for presentation in stats string[] days = { "d", "n", "o" }; /* * Decision variables */ // TransitionConstraint var x = solver.MakeIntVarMatrix(numberOfEmployees, numberOfDays, validShifts, "x"); var flattenedX = x.Flatten(); // Shift count var shiftCount = shifts.Length; // Shifts per day statistics var dayStats = new IntVar[numberOfDays, shiftCount]; for (var i = 0; i < numberOfDays; i++) { for (var j = 0; j < shiftCount; j++) { dayStats[i, j] = solver.MakeIntVar(0, numberOfEmployees, "dayStats"); } } // Team statistics var teamStats = new IntVar[numberOfEmployees]; /* * Constraints */ for (var i = 0; i < numberOfEmployees; i++) { var regInput = new IntVar[numberOfDays]; for (var j = 0; j < numberOfDays; j++) { regInput[j] = x[i, j]; } solver.Add(regInput.Transition(transitionTuples, initialState, acceptingStates)); } // Statistics and constraints for each team for (var team = 0; team < numberOfEmployees; team++) { // Number of worked days (either day or night shift) var teamDays = new IntVar[numberOfDays]; for (var day = 0; day < numberOfDays; day++) { teamDays[day] = x[team, day].IsMember(new[] { ShiftConsts.Day, ShiftConsts.Night }); } teamStats[team] = teamDays.Sum().Var(); // At least two shifts per cycle solver.Add(teamStats[team] >= minShiftsPerCycle); } // Statistics and constraints for each day for (var day = 0; day < numberOfDays; day++) { var teams = new IntVar[numberOfEmployees]; for (var team = 0; team < numberOfEmployees; team++) { teams[team] = x[team, day]; } var stats = new IntVar[shiftCount]; for (var shift = 0; shift < shiftCount; ++shift) { stats[shift] = dayStats[day, shift]; } solver.Add(teams.Distribute(stats)); // Constraints for each day // - exactly teamSize on day shift solver.Add(dayStats[day, ShiftConsts.Day] == teamSize); // - exactly teamSize on night shift solver.Add(dayStats[day, ShiftConsts.Night] == teamSize); // - The rest of the employees are off duty solver.Add(dayStats[day, ShiftConsts.Off] == numberOfEmployees - teamSize * 2); /* We can customize constraints even further * For example, a special constraints for weekends(1 employee each shift as weekends are quiet): * if (day % 7 == 5 || day % 7 == 6) * { * solver.Add(dayStats[day, ShiftConsts.Day] == weekendTeamSize); * solver.Add(dayStats[day, ShiftConsts.Night] == weekendTeamSize); * solver.Add(dayStats[day, ShiftConsts.Off] == numberOfEmployees - weekendTeamSize * 2); * } */ } /* * Decision Builder and Solution Search */ // A simple random selection var db = solver.MakePhase(flattenedX, Solver.CHOOSE_DYNAMIC_GLOBAL_BEST, Solver.ASSIGN_RANDOM_VALUE); var log = solver.MakeSearchLog(1000000); // Don't search after a certain miliseconds var timeLimit = solver.MakeTimeLimit(1000); // a second // Start the search solver.NewSearch(db, log, timeLimit); // Return solutions as result var schedules = new List <Schedule>(); var numSolutions = 0; while (solver.NextSolution()) { numSolutions++; // A new schedule for the time period var schedule = new Schedule { Id = numSolutions, Name = string.Format("Schedule for {0}-{1} for {2} employees, team size {3}", startDate.Date.ToShortDateString(), startDate.Date.AddDays(numberOfDays).ToShortDateString(), numberOfEmployees, teamSize), StartDate = startDate.Date, EndDate = startDate.Date.AddDays(numberOfDays), Shifts = new List <Shift>() }; var idCounter = 1; for (var i = 0; i < numberOfEmployees; i++) { AddDiagnostics("Employee #{0,-2}: ", i + 1, shiftEmployees[i].ToString()); var occ = new Dictionary <int, int>(); for (var j = 0; j < numberOfDays; j++) { var shiftVal = (int)x[i, j].Value() - 1; if (!occ.ContainsKey(shiftVal)) { occ[shiftVal] = 0; } occ[shiftVal]++; // Add a shift var shiftType = (ShiftType)shiftVal + 1; var shiftStart = startDate.Date .AddDays(j) .AddHours(shiftType == ShiftType.Off ? 0 : (shiftType == ShiftType.Day ? startHour : startHour + shiftHours)); // i.e Day shift starts at 07:00, night shift at 19:00 schedule.Shifts.Add(new Shift { Id = idCounter, Employee = shiftEmployees[i], Type = shiftType, StartDate = shiftStart, EndDate = shiftType == ShiftType.Off ? shiftStart : shiftStart.AddHours(shiftHours) }); idCounter++; AddDiagnostics(days[shiftVal] + " "); } AddDiagnostics(" #Total days: {0,2}", teamStats[i].Value()); foreach (var s in validShifts) { var v = 0; if (occ.ContainsKey(s - 1)) { v = occ[s - 1]; } AddDiagnostics(" {0}:{1}", days[s - 1], v); } AddDiagnostics("\t- {0}\n", shiftEmployees[i].ToString()); } AddDiagnostics("\n"); AddDiagnostics("Daily Statistics\nDay\t\td n o\n"); for (var j = 0; j < numberOfDays; j++) { AddDiagnostics("Day #{0,2}: \t", j + 1); foreach (var t in validShifts) { AddDiagnostics(dayStats[j, t].Value() + " "); } AddDiagnostics("\n"); } AddDiagnostics("\n"); // Add this schedule to list schedules.Add(schedule); // defaults to just the first one if (numSolutions >= maxSolutions) { break; } } AddDiagnostics("\nSolutions: {0}", solver.Solutions()); AddDiagnostics("\nFailures: {0}", solver.Failures()); AddDiagnostics("\nBranches: {0} ", solver.Branches()); AddDiagnostics("\nWallTime: {0}ms", solver.WallTime()); solver.EndSearch(); AddDiagnostics("\n\nFinished solving the schedule."); if (schedules.Count < 1) { LastError = "There's no solution in the model for your input."; // We reached the limit and there's no solution AddDiagnostics("\n\nThere's no solution in the model for your input."); } return(Task.FromResult(schedules)); }
/** * * Nurse rostering * * This is a simple nurse rostering model using a DFA and * the built-in TransitionConstraint. * * The DFA is from MiniZinc Tutorial, Nurse Rostering example: * - one day off every 4 days * - no 3 nights in a row. * * Also see: * - http://www.hakank.org/or-tools/nurse_rostering.py * - http://www.hakank.org/or-tools/nurse_rostering_regular.cs * which use (a decomposition of) regular constraint * */ private static void Solve(int nurse_multiplier, int week_multiplier) { Console.WriteLine("Starting Nurse Rostering"); Console.WriteLine(" - {0} teams of 7 nurses", nurse_multiplier); Console.WriteLine(" - {0} blocks of 14 days", week_multiplier); Solver solver = new Solver("NurseRostering"); // // Data // // Note: If you change num_nurses or num_days, // please also change the constraints // on nurse_stat and/or day_stat. int num_nurses = 7 * nurse_multiplier; int num_days = 14 * week_multiplier; // Note: I had to add a dummy shift. int dummy_shift = 0; int day_shift = 1; int night_shift = 2; int off_shift = 3; int[] shifts = {dummy_shift, day_shift, night_shift, off_shift}; int[] valid_shifts = {day_shift, night_shift, off_shift}; // the DFA (for regular) int initial_state = 1; int[] accepting_states = {1,2,3,4,5,6}; /* // This is the transition function // used in nurse_rostering_regular.cs int[,] transition_fn = { // d,n,o {2,3,1}, // state 1 {4,4,1}, // state 2 {4,5,1}, // state 3 {6,6,1}, // state 4 {6,0,1}, // state 5 {0,0,1} // state 6 }; */ // For TransitionConstraint IntTupleSet transition_tuples = new IntTupleSet(3); // state, input, next state transition_tuples.InsertAll(new int[,] { {1,1,2}, {1,2,3}, {1,3,1}, {2,1,4}, {2,2,4}, {2,3,1}, {3,1,4}, {3,2,5}, {3,3,1}, {4,1,6}, {4,2,6}, {4,3,1}, {5,1,6}, {5,3,1}, {6,3,1} }); string[] days = {"d","n","o"}; // for presentation // // Decision variables // // // For TransitionConstraint // IntVar[,] x = solver.MakeIntVarMatrix(num_nurses, num_days, valid_shifts, "x"); IntVar[] x_flat = x.Flatten(); // // summary of the nurses // IntVar[] nurse_stat = new IntVar[num_nurses]; // // summary of the shifts per day // int num_shifts = shifts.Length; IntVar[,] day_stat = new IntVar[num_days, num_shifts]; for(int i = 0; i < num_days; i++) { for(int j = 0; j < num_shifts; j++) { day_stat[i,j] = solver.MakeIntVar(0, num_nurses, "day_stat"); } } // // Constraints // for(int i = 0; i < num_nurses; i++) { IntVar[] reg_input = new IntVar[num_days]; for(int j = 0; j < num_days; j++) { reg_input[j] = x[i,j]; } solver.Add(reg_input.Transition(transition_tuples, initial_state, accepting_states)); } // // Statistics and constraints for each nurse // for(int nurse = 0; nurse < num_nurses; nurse++) { // Number of worked days (either day or night shift) IntVar[] nurse_days = new IntVar[num_days]; for(int day = 0; day < num_days; day++) { nurse_days[day] = x[nurse, day].IsMember(new int[] { day_shift, night_shift }); } nurse_stat[nurse] = nurse_days.Sum().Var(); // Each nurse must work between 7 and 10 // days/nights during this period solver.Add(nurse_stat[nurse] >= 7 * week_multiplier / nurse_multiplier); solver.Add(nurse_stat[nurse] <= 10 * week_multiplier / nurse_multiplier); } // // Statistics and constraints for each day // for(int day = 0; day < num_days; day++) { IntVar[] nurses = new IntVar[num_nurses]; for(int nurse = 0; nurse < num_nurses; nurse++) { nurses[nurse] = x[nurse, day]; } IntVar[] stats = new IntVar[num_shifts]; for (int shift = 0; shift < num_shifts; ++shift) { stats[shift] = day_stat[day, shift]; } solver.Add(nurses.Distribute(stats)); // // Some constraints for each day: // // Note: We have a strict requirements of // the number of shifts. // Using atleast constraints is harder // in this model. // if (day % 7 == 5 || day % 7 == 6) { // special constraints for the weekends solver.Add(day_stat[day, day_shift] == 2 * nurse_multiplier); solver.Add(day_stat[day, night_shift] == nurse_multiplier); solver.Add(day_stat[day, off_shift] == 4 * nurse_multiplier); } else { // for workdays: // - exactly 3 on day shift solver.Add(day_stat[day, day_shift] == 3 * nurse_multiplier); // - exactly 2 on night solver.Add(day_stat[day, night_shift] == 2 * nurse_multiplier); // - exactly 2 off duty solver.Add(day_stat[day, off_shift] == 2 * nurse_multiplier); } } // // Search // DecisionBuilder db = solver.MakePhase(x_flat, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE); SearchMonitor log = solver.MakeSearchLog(1000000); solver.NewSearch(db, log); int num_solutions = 0; while (solver.NextSolution()) { num_solutions++; for(int i = 0; i < num_nurses; i++) { Console.Write("Nurse #{0,-2}: ", i); var occ = new Dictionary<int, int>(); for(int j = 0; j < num_days; j++) { int v = (int)x[i,j].Value()-1; if (!occ.ContainsKey(v)) { occ[v] = 0; } occ[v]++; Console.Write(days[v] + " "); } Console.Write(" #workdays: {0,2}", nurse_stat[i].Value()); foreach(int s in valid_shifts) { int v = 0; if (occ.ContainsKey(s-1)) { v = occ[s-1]; } Console.Write(" {0}:{1}", days[s-1], v); } Console.WriteLine(); } Console.WriteLine(); Console.WriteLine("Statistics per day:\nDay d n o"); for(int j = 0; j < num_days; j++) { Console.Write("Day #{0,2}: ", j); foreach(int t in valid_shifts) { Console.Write(day_stat[j,t].Value() + " "); } Console.WriteLine(); } Console.WriteLine(); // We just show 2 solutions if (num_solutions > 1) { break; } } Console.WriteLine("\nSolutions: {0}", solver.Solutions()); Console.WriteLine("WallTime: {0}ms", solver.WallTime()); Console.WriteLine("Failures: {0}", solver.Failures()); Console.WriteLine("Branches: {0} ", solver.Branches()); solver.EndSearch(); }