using System.Diagnostics; using Core.Entities; using Google.OrTools.Sat; namespace Core.Calculation; public class TeamScheduler { private readonly IList _studentObjects; private readonly IList _teamObjects; private readonly int[] _students; private readonly int[] _teams; private readonly int[] _timeSlots; private readonly List> _scheduleSeparateTeams = []; public TeamScheduler(IEnumerable teams, int numTimeSlots) { _teamObjects = teams.ToArray(); _studentObjects = teams.SelectMany(t => t.Students).Distinct().ToList(); _students = Enumerable.Range(0, _studentObjects.Count).ToArray(); _teams = Enumerable.Range(0, _teamObjects.Count).ToArray(); _timeSlots = Enumerable.Range(0, numTimeSlots).ToArray(); } public void ScheduleSeparate(Team team1, Team team2) { var one = _teamObjects.IndexOf(team1); var two = _teamObjects.IndexOf(team2); _scheduleSeparateTeams.Add(Tuple.Create(one,two)); } public static TeamScheduler CreateInstance(IEnumerable teams, int numTimeSlots) { return new TeamScheduler(teams, numTimeSlots); } public TeamSchedulerSolution 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] = _studentObjects[i].Teams.Contains(_teamObjects[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]); var solver = new CpSolver(); solver.StringParameters = "max_time_in_seconds:2.0"; var cpSolverStatus = solver.Solve(model); Debug.WriteLine($"Solver status: {cpSolverStatus}"); var timeSlotTeams = new Team[_timeSlots.Length][]; if (cpSolverStatus is not (CpSolverStatus.Optimal or CpSolverStatus.Feasible)) return new TeamSchedulerSolution(timeSlotTeams, cpSolverStatus.ToString()); Debug.WriteLine($"Total cost: {solver.ObjectiveValue}\n"); foreach (var s in _timeSlots) { var teams = (from t in _teams where solver.Value(x[t, s]) > 0 select _teamObjects[t]).ToArray(); timeSlotTeams[s] = teams; } //Debug.WriteLine("No solution found."); return new TeamSchedulerSolution(timeSlotTeams, cpSolverStatus.ToString()); } }