using Core.Entities; using Google.OrTools.Sat; namespace Core.Calculation; public class TeamScheduler { private readonly IList _studentObjs; private readonly IList _teamObjs; private readonly int[] _students; private readonly int[] _teams; private readonly int[] _timeSlots; private readonly List> _scheduleSeparateTeams = new (); public TeamScheduler(IList teams, int numTimeSlots) { _teamObjs = teams; _studentObjs = teams.SelectMany(t => t.Students).Distinct().ToList(); _students = Enumerable.Range(0, _studentObjs.Count).ToArray(); _teams = Enumerable.Range(0, _teamObjs.Count).ToArray(); _timeSlots = Enumerable.Range(0, numTimeSlots).ToArray(); } public void ScheduleSeparate(Team team1, Team team2) { var one = _teamObjs.IndexOf(team1); var two = _teamObjs.IndexOf(team2); _scheduleSeparateTeams.Add(Tuple.Create(one,two)); } public static TeamScheduler CreateInstance(IList teams, int numTimeSlots) { return new TeamScheduler(teams, numTimeSlots); } public IList[] Solve() { // Model. var model = new CpModel(); // Data var m = new int[_students.Length,_teams.Length]; foreach (var i in _students) foreach (var t in _teams) m[i, t] = _studentObjs[i].Teams.Contains(_teamObjs[t]) ? 1 : 0; // Variables. // x - 1 if meeting of team t takes place at time slot s, else 0 var x = new IntVar[_teams.Length, _timeSlots.Length]; foreach (var t in _teams) foreach (var s in _timeSlots) x[t, s] = model.NewIntVar(0, 1,$"team time slots[{t},{s}]"); // y - 1 if individual i has meetings at time slot s, 0 otherwise var y = new IntVar[_students.Length, _timeSlots.Length]; foreach (var i in _students) foreach (var s in _timeSlots) y[i, s] = model.NewIntVar(0, 1, $"individual time slots[{i},{s}]"); // each team meets exactly one time foreach (var t in _teams) model.AddLinearConstraint(LinearExpr.Sum(_timeSlots.Select(s => x[t, s])), 1L, 1L); // individual must have at least one team meeting at the given time slot to attend foreach (var i in _students) foreach (var s in _timeSlots) model.Add( new BoundedLinearExpression( y[i, s], LinearExpr.Sum(_teams.Select(t => m[i, t] * x[t, s])), false)); // maximize number of times individuals meet var indTimeSlotVars = LinearExpr.NewBuilder(); foreach (var i in _students) foreach (var s in _timeSlots) indTimeSlotVars.Add(y[i, s]); model.Minimize(indTimeSlotVars); foreach (var ts in _scheduleSeparateTeams) foreach (var s in _timeSlots) model.Add(x[ts.Item1, s] != x[ts.Item2, s]); //model.Add( // new BoundedLinearExpression( // x[ts.Item1, s], // x[ts.Item2, s], // false)); var solver = new CpSolver(); var cpSolverStatus = solver.Solve(model); Console.WriteLine($"Solver status: {cpSolverStatus}"); if (cpSolverStatus is CpSolverStatus.Optimal or CpSolverStatus.Feasible) { Console.WriteLine($"Total cost: {solver.ObjectiveValue}\n"); //foreach (var t in _teams) //foreach (var s in _timeSlots) //{ // if (solver.Value(x[t, s]) > 0) // Console.WriteLine($"{_teamObjs[t].Name} : {s}"); //} //foreach (var i in _students) //foreach (var s in _timeSlots) //{ // if (solver.Value(y[i, s]) > 0) // Console.WriteLine($"{_studentObjs[i].Name} : {s}"); //} var timeSlotTeams = new List[_timeSlots.Length]; foreach (var s in _timeSlots) { var teams = new List(); foreach (var t in _teams) { if (solver.Value(x[t, s]) > 0) { teams.Add(_teamObjs[t]); } } timeSlotTeams[s] = teams; } return timeSlotTeams; } else { //Console.WriteLine("No solution found."); return Array.Empty>(); } } }