public static Constraint Transition(this IntVar[] vars, IntTupleSet transitions, long initial_state, int[] final_states) { Solver solver = GetSolver(vars); return(solver.MakeTransitionConstraint(vars, transitions, initial_state, final_states)); }
// Allowed assignment public static Constraint AllowedAssignments(this IntVar[] vars, IntTupleSet tuples) { Solver solver = GetSolver(vars); return(solver.MakeAllowedAssignments(vars, tuples)); }
/* * Build closeness pairs for consecutive numbers. * * Build set of allowed pairs such that two consecutive numbers touch * each other in the grid. * * Returns: * A list of pairs for allowed consecutive position of numbers. * * Args: * rows: the number of rows in the grid * cols: the number of columns in the grid */ public static IntTupleSet BuildPairs(int rows, int cols) { int[] ix = { -1, 0, 1 }; var result_tmp = (from x in Enumerable.Range(0, rows) from y in Enumerable.Range(0, cols) from dx in ix from dy in ix where x + dx >= 0 && x + dx < rows && y + dy >= 0 && y + dy < cols && (dx != 0 || dy != 0) select new int[] { x *cols + y, (x + dx) * cols + (y + dy) } ).ToArray(); // Convert to len x 2 matrix int len = result_tmp.Length; IntTupleSet result = new IntTupleSet(2); foreach (int[] r in result_tmp) { result.Insert(r); } return(result); }
/** * * Build the table of valid connections of the grid. * */ public static IntTupleSet ValidConnections(int rows, int cols) { IEnumerable<int> ROWS = Enumerable.Range(0, rows); IEnumerable<int> COLS = Enumerable.Range(0, cols); var result_tmp = ( from i1 in ROWS from j1 in COLS from i2 in ROWS from j2 in COLS where (Math.Abs(j1-j2) == 1 && i1 == i2) || (Math.Abs(i1-i2) == 1 && j1 % cols == j2 % cols) select new int[] {i1*cols+j1, i2*cols+j2} ).ToArray(); // Convert to len x 2 matrix int len = result_tmp.Length; IntTupleSet result = new IntTupleSet(2); foreach(int[] r in result_tmp) { result.Insert(r); } return result; }
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)); }
/** * * Build the table of valid connections of the grid. * */ public static IntTupleSet ValidConnections(int rows, int cols) { IEnumerable <int> ROWS = Enumerable.Range(0, rows); IEnumerable <int> COLS = Enumerable.Range(0, cols); var result_tmp = ( from i1 in ROWS from j1 in COLS from i2 in ROWS from j2 in COLS where (Math.Abs(j1 - j2) == 1 && i1 == i2) || (Math.Abs(i1 - i2) == 1 && j1 % cols == j2 % cols) select new int[] { i1 *cols + j1, i2 *cols + j2 } ).ToArray(); // Convert to len x 2 matrix int len = result_tmp.Length; IntTupleSet result = new IntTupleSet(2); foreach (int[] r in result_tmp) { result.Insert(r); } return(result); }
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)); }
/* * Build closeness pairs for consecutive numbers. * * Build set of allowed pairs such that two consecutive numbers touch * each other in the grid. * * Returns: * A list of pairs for allowed consecutive position of numbers. * * Args: * rows: the number of rows in the grid * cols: the number of columns in the grid */ public static IntTupleSet BuildPairs(int rows, int cols) { int[] ix = {-1, 0, 1}; var result_tmp = (from x in Enumerable.Range(0, rows) from y in Enumerable.Range(0, cols) from dx in ix from dy in ix where x + dx >= 0 && x + dx < rows && y + dy >= 0 && y + dy < cols && (dx != 0 || dy != 0) select new int[] {x * cols + y, (x + dx) * cols + (y + dy)} ).ToArray(); // Convert to len x 2 matrix int len = result_tmp.Length; IntTupleSet result = new IntTupleSet(2); foreach(int[] r in result_tmp) { result.Insert(r); } return result; }
/** * * 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(); }
/** * * Hidato puzzle in Google CP Solver. * * http://www.hidato.com/ * """ * Puzzles start semi-filled with numbered tiles. * The first and last numbers are circled. * Connect the numbers together to win. Consecutive * number must touch horizontally, vertically, or * diagonally. * """ * * This is a port of the Python model hidato_table.py * made by Laurent Perron (using AllowedAssignments), * based on my (much slower) model hidato.py. * */ private static void Solve(int model = 1) { Solver solver = new Solver("HidatoTable"); // // models, a 0 indicates an open cell which number is not yet known. // int[,] puzzle = null; if (model == 1) { // Simple problem // Solution 1: // 6 7 9 // 5 2 8 // 1 4 3 int[,] puzzle1 = { { 6, 0, 9 }, { 0, 2, 8 }, { 1, 0, 0 } }; puzzle = puzzle1; } else if (model == 2) { int[,] puzzle2 = { { 0, 44, 41, 0, 0, 0, 0 }, { 0, 43, 0, 28, 29, 0, 0 }, { 0, 1, 0, 0, 0, 33, 0 }, { 0, 2, 25, 4, 34, 0, 36 }, { 49, 16, 0, 23, 0, 0, 0 }, { 0, 19, 0, 0, 12, 7, 0 }, { 0, 0, 0, 14, 0, 0, 0 } }; puzzle = puzzle2; } else if (model == 3) { // Problems from the book: // Gyora Bededek: "Hidato: 2000 Pure Logic Puzzles" // Problem 1 (Practice) int[,] puzzle3 = { { 0, 0, 20, 0, 0 }, { 0, 0, 0, 16, 18 }, { 22, 0, 15, 0, 0 }, { 23, 0, 1, 14, 11 }, { 0, 25, 0, 0, 12 } }; puzzle = puzzle3; } else if (model == 4) { // problem 2 (Practice) int[,] puzzle4 = { { 0, 0, 0, 0, 14 }, { 0, 18, 12, 0, 0 }, { 0, 0, 17, 4, 5 }, { 0, 0, 7, 0, 0 }, { 9, 8, 25, 1, 0 } }; puzzle = puzzle4; } else if (model == 5) { // problem 3 (Beginner) int[,] puzzle5 = { { 0, 26, 0, 0, 0, 18 }, { 0, 0, 27, 0, 0, 19 }, { 31, 23, 0, 0, 14, 0 }, { 0, 33, 8, 0, 15, 1 }, { 0, 0, 0, 5, 0, 0 }, { 35, 36, 0, 10, 0, 0 } }; puzzle = puzzle5; } else if (model == 6) { // Problem 15 (Intermediate) int[,] puzzle6 = { { 64, 0, 0, 0, 0, 0, 0, 0 }, { 1, 63, 0, 59, 15, 57, 53, 0 }, { 0, 4, 0, 14, 0, 0, 0, 0 }, { 3, 0, 11, 0, 20, 19, 0, 50 }, { 0, 0, 0, 0, 22, 0, 48, 40 }, { 9, 0, 0, 32, 23, 0, 0, 41 }, { 27, 0, 0, 0, 36, 0, 46, 0 }, { 28, 30, 0, 35, 0, 0, 0, 0 } }; puzzle = puzzle6; } int r = puzzle.GetLength(0); int c = puzzle.GetLength(1); Console.WriteLine(); Console.WriteLine("----- Solving problem {0} -----", model); Console.WriteLine(); PrintMatrix(puzzle); // // Decision variables // IntVar[] positions = solver.MakeIntVarArray(r * c, 0, r * c - 1, "p"); // // Constraints // solver.Add(positions.AllDifferent()); // // Fill in the clues // for (int i = 0; i < r; i++) { for (int j = 0; j < c; j++) { if (puzzle[i, j] > 0) { solver.Add(positions[puzzle[i, j] - 1] == i * c + j); } } } // Consecutive numbers much touch each other in the grid. // We use an allowed assignment constraint to model it. IntTupleSet close_tuples = BuildPairs(r, c); for (int k = 1; k < r * c - 1; k++) { IntVar[] tmp = new IntVar[] { positions[k], positions[k + 1] }; solver.Add(tmp.AllowedAssignments(close_tuples)); } // // Search // DecisionBuilder db = solver.MakePhase(positions, Solver.CHOOSE_MIN_SIZE_LOWEST_MIN, Solver.ASSIGN_MIN_VALUE); solver.NewSearch(db); int num_solution = 0; while (solver.NextSolution()) { num_solution++; PrintOneSolution(positions, r, c, num_solution); } Console.WriteLine("\nSolutions: " + solver.Solutions()); Console.WriteLine("WallTime: " + solver.WallTime() + "ms "); Console.WriteLine("Failures: " + solver.Failures()); Console.WriteLine("Branches: " + 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(); }
/** * * Rogo puzzle solver. * * From http://www.rogopuzzle.co.nz/ * """ * The object is to collect the biggest score possible using a given * number of steps in a loop around a grid. The best possible score * for a puzzle is given with it, so you can easily check that you have * solved the puzzle. Rogo puzzles can also include forbidden squares, * which must be avoided in your loop. * """ * * Also see Mike Trick: * "Operations Research, Sudoko, Rogo, and Puzzles" * http://mat.tepper.cmu.edu/blog/?p=1302 * * * Also see, http://www.hakank.org/or-tools/rogo2.py * though this model differs in a couple of central points * which makes it much faster: * * - it use a table ( * AllowedAssignments) with the valid connections * - instead of two coordinates arrays, it use a single path array * */ private static void Solve() { Solver solver = new Solver("Rogo2"); Console.WriteLine("\n"); Console.WriteLine("**********************************************"); Console.WriteLine(" {0}", problem_name); Console.WriteLine("**********************************************\n"); // // Data // int B = -1; Console.WriteLine("Rows: {0} Cols: {1} Max Steps: {2}", rows, cols, max_steps); int[] problem_flatten = problem.Cast <int>().ToArray(); int max_point = problem_flatten.Max(); int max_sum = problem_flatten.Sum(); Console.WriteLine("max_point: {0} max_sum: {1} best: {2}", max_point, max_sum, best); IEnumerable <int> STEPS = Enumerable.Range(0, max_steps); IEnumerable <int> STEPS1 = Enumerable.Range(0, max_steps - 1); // the valid connections, to be used with AllowedAssignments IntTupleSet valid_connections = ValidConnections(rows, cols); // // Decision variables // IntVar[] path = solver.MakeIntVarArray(max_steps, 0, rows * cols - 1, "path"); IntVar[] points = solver.MakeIntVarArray(max_steps, 0, best, "points"); IntVar sum_points = points.Sum().VarWithName("sum_points"); // // Constraints // foreach (int s in STEPS) { // calculate the points (to maximize) solver.Add(points[s] == problem_flatten.Element(path[s])); // ensure that there are no black cells in // the path solver.Add(problem_flatten.Element(path[s]) != B); } solver.Add(path.AllDifferent()); // valid connections foreach (int s in STEPS1) { solver.Add(new IntVar[] { path[s], path[s + 1] }. AllowedAssignments(valid_connections)); } // around the corner solver.Add(new IntVar[] { path[max_steps - 1], path[0] }. AllowedAssignments(valid_connections)); // Symmetry breaking for (int s = 1; s < max_steps; s++) { solver.Add(path[0] < path[s]); } // // Objective // OptimizeVar obj = sum_points.Maximize(1); // // Search // DecisionBuilder db = solver.MakePhase(path, Solver.INT_VAR_DEFAULT, Solver.INT_VALUE_DEFAULT); solver.NewSearch(db, obj); while (solver.NextSolution()) { Console.WriteLine("sum_points: {0}", sum_points.Value()); Console.Write("path: "); foreach (int s in STEPS) { Console.Write("{0} ", path[s].Value()); } Console.WriteLine(); Console.WriteLine("(Adding 1 to coords...)"); int[,] sol = new int[rows, cols]; foreach (int s in STEPS) { int p = (int)path[s].Value(); int x = (int)(p / cols); int y = (int)(p % cols); Console.WriteLine("{0,2},{1,2} ({2} points)", x + 1, y + 1, points[s].Value()); sol[x, y] = 1; } Console.WriteLine("\nThe path is marked by 'X's:"); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { String p = sol[i, j] == 1 ? "X" : " "; String q = problem[i, j] == B ? "B" : problem[i, j] == 0 ? "." : problem[i, j].ToString(); Console.Write("{0,2}{1} ", q, p); } Console.WriteLine(); } 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(); }