Refactor/cleanup the EventAssignemnt

This commit is contained in:
2025-12-01 21:38:14 -05:00
parent feaaf76f46
commit 8039e751d8
+286 -233
View File
@@ -1,150 +1,209 @@
using System.Diagnostics; using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
using Core.Entities; using Core.Entities;
using Google.OrTools.Sat; using Google.OrTools.Sat;
using Microsoft.EntityFrameworkCore.Metadata;
using IntVar = Google.OrTools.Sat.IntVar;
namespace Core.Calculation namespace Core.Calculation
{ {
/// <summary>
/// Solves the event assignment problem using constraint programming.
/// Assigns students to events based on rankings, capacity constraints, and team requirements.
/// </summary>
public class EventAssignment public class EventAssignment
{ {
private readonly IList<EventDefinition> _events; // Constants for magic numbers
private const double TEAM_DIVISION_MULTIPLIER = 1.25;
private const int MIN_REGIONAL_EVENTS = 1;
private const int MAX_REGIONAL_EVENTS = 2;
private readonly IList<EventDefinition> _events;
private readonly IList<Student> _students; private readonly IList<Student> _students;
private readonly AssignmentParameters _parameters; private readonly AssignmentParameters _parameters;
private readonly int[] _allEvents; private readonly double _maxSolveTimeSeconds;
private readonly int[] _allEvents;
private readonly int[] _allStudents; private readonly int[] _allStudents;
// how many students have picked each eventDefinition? // how many students have picked each event?
private readonly int[] _eventPickCounts; private readonly int[] _eventPickCounts;
private IList<AssignmentRequirement> _assignmentRequirements = new List<AssignmentRequirement>(); private IList<AssignmentRequirement> _assignmentRequirements = [];
private IList<EventDefinition> _droppedEvents = new List<EventDefinition>(); private IList<EventDefinition> _droppedEvents = [];
private IList<EventDefinition> _includedEvents = new List<EventDefinition>(); private IList<EventDefinition> _includedEvents = [];
private IList<EventDefinition> _twoTeams = new List<EventDefinition>(); private IList<EventDefinition> _twoTeams = [];
public EventAssignment(IList<EventDefinition> events, IList<Student> students, AssignmentParameters parameters) /// <summary>
/// Creates a new event assignment optimizer.
/// </summary>
/// <param name="events">The events to assign students to (must not be null or empty)</param>
/// <param name="students">The students to assign to events (must not be null or empty)</param>
/// <param name="parameters">Assignment parameters and constraints</param>
/// <param name="maxSolveTimeSeconds">Maximum solver time in seconds (default: 60.0)</param>
/// <exception cref="ArgumentNullException">Thrown when events, students, or parameters is null</exception>
/// <exception cref="ArgumentException">Thrown when collections are empty</exception>
public EventAssignment(IList<EventDefinition> events, IList<Student> students,
AssignmentParameters parameters, double maxSolveTimeSeconds = 60.0)
{ {
_events = events; if (events == null)
throw new ArgumentNullException(nameof(events));
if (students == null)
throw new ArgumentNullException(nameof(students));
if (parameters == null)
throw new ArgumentNullException(nameof(parameters));
if (events.Count == 0)
throw new ArgumentException("Events collection cannot be empty", nameof(events));
if (students.Count == 0)
throw new ArgumentException("Students collection cannot be empty", nameof(students));
_events = events;
_students = students; _students = students;
_parameters = parameters; _parameters = parameters;
_allEvents = Enumerable.Range(0, _events.Count).ToArray(); _maxSolveTimeSeconds = maxSolveTimeSeconds;
_allEvents = Enumerable.Range(0, _events.Count).ToArray();
_allStudents = Enumerable.Range(0, _students.Count).ToArray(); _allStudents = Enumerable.Range(0, _students.Count).ToArray();
_eventPickCounts = new int[_allEvents.Length]; _eventPickCounts = new int[_allEvents.Length];
for (var i = 0; i < _events.Count; i++) for (var i = 0; i < _events.Count; i++)
{ {
var e = _events[i]; var e = _events[i];
// Performance: Use Any() instead of Count() > 0
_eventPickCounts[i] = _students.Count(s => s.EventRankings.Count(er => er.EventDefinition == e) > 0); _eventPickCounts[i] = _students.Count(s => s.EventRankings.Any(er => er.EventDefinition == e));
} }
} }
/// <summary>
/// Adds a requirement to include or exclude a specific student-event pairing.
/// </summary>
/// <param name="assignmentRequirement">The assignment requirement to add</param>
public void AddAssignmentRequirement(AssignmentRequirement assignmentRequirement) public void AddAssignmentRequirement(AssignmentRequirement assignmentRequirement)
{ {
_assignmentRequirements.Add(assignmentRequirement); _assignmentRequirements.Add(assignmentRequirement);
} }
public void RemoveEvents(IList<EventDefinition> events) /// <summary>
/// Marks events to be excluded from the assignment solution.
/// </summary>
/// <param name="events">Events to drop from consideration</param>
public void RemoveEvents(IList<EventDefinition> events)
{ {
_droppedEvents = events; _droppedEvents = events;
} }
public void IncludedEvents(IList<EventDefinition> events) /// <summary>
/// Forces specific events to be included in the solution.
/// </summary>
/// <param name="events">Events that must be included</param>
public void SetIncludedEvents(IList<EventDefinition> events)
{ {
_includedEvents = events; _includedEvents = events;
} }
/// <summary>
/// Allows specific events to have two teams instead of one.
/// </summary>
/// <param name="events">Events that can have two teams</param>
public void AllowTwoTeams(IList<EventDefinition> events) public void AllowTwoTeams(IList<EventDefinition> events)
{ {
_twoTeams = events; _twoTeams = events;
} }
/// <summary>
/// Solves the event assignment problem using constraint programming.
/// Assigns students to events based on rankings, capacity constraints, and requirements.
/// </summary>
/// <returns>A solution containing team assignments and status</returns>
public async Task<EventAssignmentSolution> Solve() public async Task<EventAssignmentSolution> Solve()
{ {
Debug.WriteLine(_parameters); try
{
Debug.WriteLine(_parameters);
// Model. // Create constraint programming model
var model = new CpModel(); var model = new CpModel();
// Variables. // Decision variables: x[e,s] = 1 if student s is assigned to event e
var x = new BoolVar[_allEvents.Length, _allStudents.Length]; var x = new BoolVar[_allEvents.Length, _allStudents.Length];
foreach (var e in _allEvents) foreach (var e in _allEvents)
foreach (var s in _allStudents) foreach (var s in _allStudents)
x[e, s] = model.NewBoolVar($"eventAssignments[{e},{s}]"); x[e, s] = model.NewBoolVar($"eventAssignments[{e},{s}]");
AddAssignmentRequirements(model, x); // Add all constraints
AddAssignmentRequirements(model, x);
var assignmentThresholdsList = AddEventAssignmentThresholds(model, x);
LimitStudentAssignment(model, x);
SetLevelOfEffort(model, x);
var assignmentThresholdsList = AddEventAssignmentThresholds(model, x); if (_parameters.RequireOnSite)
RequireOnSiteActivity(model, x);
LimitStudentAssignment(model, x); if (_parameters.RequireRegional)
RequireRegionalEvent(model, x);
// set the range for level of effort AtMostOneIndividualEvent(model, x);
SetLevelOfEffort(model, x); IndividualEventsMustBeRanked(model, x);
OptimizeStudentEventRankings(model, x);
// each student should be assigned at least one on site activity
if (_parameters.RequireOnSite)
RequireOnSiteActivity(model, x);
// students should have at least one regional event
if (_parameters.RequireRegional)
RequireRegionalEvent(model, x);
// students should have at maximum one individual event // Configure and run solver
AtMostOneIndividualEvent(model, x); Debug.WriteLine("Starting optimization");
var solver = new CpSolver();
solver.StringParameters = $"max_time_in_seconds:{_maxSolveTimeSeconds}";
var cpSolverStatus = await Task.Run(() => solver.Solve(model));
//EventHasInterestedStudent(model, x); Debug.WriteLine($"Solver status: {cpSolverStatus}");
IndividualEventsMustBeRanked(model, x); if (cpSolverStatus == CpSolverStatus.Infeasible)
{
Debug.WriteLine("Problem is infeasible - constraints cannot be satisfied");
}
OptimizeStudentEventRankings(model, x); var eventAssignmentsList = GetEventAssignments(x, solver, cpSolverStatus);
var eventAssignmentSolution =
new EventAssignmentSolution
(
eventAssignmentsList.ToArray(),
cpSolverStatus.ToString(),
assignmentThresholdsList
);
Debug.WriteLine("Starting optimization"); return eventAssignmentSolution;
var solver = new CpSolver(); }
var cpSolverStatus = await Task.Run(() => solver.Solve(model)); catch (Exception ex)
{
// print solver status Debug.WriteLine($"Error solving event assignment: {ex}");
Debug.WriteLine($"Solver status: {cpSolverStatus}"); throw;
}
var eventAssignmentsList = GetEventAssignments(x, solver, cpSolverStatus);
var eventAssignmentSolution =
new EventAssignmentSolution
(
eventAssignmentsList.ToArray(),
cpSolverStatus.ToString(),
assignmentThresholdsList
);
return eventAssignmentSolution;
} }
// Take the solution and map it back to the entities /// <summary>
/// Extracts the solution from the solver results.
/// </summary>
private List<Team> GetEventAssignments(BoolVar[,] x, CpSolver solver, CpSolverStatus cpSolverStatus) private List<Team> GetEventAssignments(BoolVar[,] x, CpSolver solver, CpSolverStatus cpSolverStatus)
{ {
if (cpSolverStatus is not (CpSolverStatus.Optimal or CpSolverStatus.Feasible)) if (cpSolverStatus is not (CpSolverStatus.Optimal or CpSolverStatus.Feasible))
return []; return [];
var eventAssignments = var eventAssignments =
from e in _allEvents from e in _allEvents
let students = let students =
from s in _allStudents from s in _allStudents
where solver.BooleanValue(x[e, s]) where solver.BooleanValue(x[e, s])
select _students[s] select _students[s]
where students.Any() where students.Any()
select new Team select new Team
{ {
Identifier = _events[e].Name, Identifier = _events[e].Name,
Event = _events[e], Event = _events[e],
Students = students.ToList() Students = students.ToList()
}; };
return eventAssignments.ToList(); return eventAssignments.ToList();
} }
// Maximize student event rankings /// <summary>
/// Objective function: Maximize student event rankings (students get higher-ranked events).
/// </summary>
private void OptimizeStudentEventRankings(CpModel model, BoolVar[,] x) private void OptimizeStudentEventRankings(CpModel model, BoolVar[,] x)
{ {
var maximizePicks = LinearExpr.NewBuilder(); var maximizePicks = LinearExpr.NewBuilder();
foreach (var s in _allStudents) foreach (var s in _allStudents)
{ {
var eventPickCoefficients = GetEventPickCoefficients(_events, _students[s].EventRankings); var eventPickCoefficients = GetEventPickCoefficients(_events, _students[s].EventRankings);
@@ -157,58 +216,64 @@ namespace Core.Calculation
model.Maximize(maximizePicks); model.Maximize(maximizePicks);
} }
/// <summary>
/// Constraint: Each student must have 1-2 regional events.
/// </summary>
private void RequireRegionalEvent(CpModel model, BoolVar[,] x) private void RequireRegionalEvent(CpModel model, BoolVar[,] x)
{ {
foreach (var s in _allStudents) AddStudentConstraint(model, x,
{ evt => evt.RegionalEvent,
var regionalEvent = new List<ILiteral>(); (m, list) => m.AddLinearConstraint(LinearExpr.Sum(list), MIN_REGIONAL_EVENTS, MAX_REGIONAL_EVENTS));
foreach (var e in _allEvents)
{
if (_events[e].RegionalEvent)
regionalEvent.Add(x[e, s]);
}
// between 1 and 2 regional events
model.AddLinearConstraint(LinearExpr.Sum(regionalEvent), 1, 2);
regionalEvent.Clear();
}
} }
/// <summary>
/// Constraint: Each student can have at most one individual event.
/// </summary>
private void AtMostOneIndividualEvent(CpModel model, BoolVar[,] x) private void AtMostOneIndividualEvent(CpModel model, BoolVar[,] x)
{ {
foreach (var s in _allStudents) AddStudentConstraint(model, x,
{ evt => evt.EventFormat == EventFormat.Individual,
var individualEvent = new List<ILiteral>(); (m, list) => m.AddAtMostOne(list));
foreach (var e in _allEvents)
{
if (_events[e].EventFormat == EventFormat.Individual)
individualEvent.Add(x[e, s]);
}
model.AddAtMostOne(individualEvent);
individualEvent.Clear();
}
} }
/// <summary>
/// Constraint: Each student must have at least one on-site activity.
/// </summary>
private void RequireOnSiteActivity(CpModel model, BoolVar[,] x) private void RequireOnSiteActivity(CpModel model, BoolVar[,] x)
{ {
AddStudentConstraint(model, x,
evt => evt.OnSiteActivity,
(m, list) => m.AddAtLeastOne(list));
}
/// <summary>
/// Helper method to add constraints for all students based on event filters.
/// Reduces code duplication and improves performance by reusing the list buffer.
/// </summary>
private void AddStudentConstraint(CpModel model, BoolVar[,] x,
Func<EventDefinition, bool> eventFilter, Action<CpModel, List<ILiteral>> constraintAction)
{
var buffer = new List<ILiteral>();
foreach (var s in _allStudents) foreach (var s in _allStudents)
{ {
var onSiteActivity = new List<ILiteral>(); buffer.Clear();
foreach (var e in _allEvents) foreach (var e in _allEvents)
{ {
if (_events[e].OnSiteActivity) if (eventFilter(_events[e]))
onSiteActivity.Add(x[e, s]); buffer.Add(x[e, s]);
} }
if (buffer.Count > 0)
//if (_parameters.RequireOnSite) constraintAction(model, buffer);
model.AddAtLeastOne(onSiteActivity);
onSiteActivity.Clear();
} }
} }
/// <summary>
/// Constraint: Individual events can only be assigned if the student ranked them.
/// </summary>
private void IndividualEventsMustBeRanked(CpModel model, BoolVar[,] x) private void IndividualEventsMustBeRanked(CpModel model, BoolVar[,] x)
{ {
var prohibitVar = new List<IntVar>(1);
foreach (var s in _allStudents) foreach (var s in _allStudents)
{ {
var student = _students[s]; var student = _students[s];
@@ -216,15 +281,20 @@ namespace Core.Calculation
{ {
var evt = _events[e]; var evt = _events[e];
var prohibitVar = new List<IntVar> { x[e, s] }; if (evt.EventFormat == EventFormat.Individual
if (evt.EventFormat == EventFormat.Individual
&& student.EventRankings.Find(er => er.EventDefinition == evt) == null) && student.EventRankings.Find(er => er.EventDefinition == evt) == null)
{
prohibitVar.Clear();
prohibitVar.Add(x[e, s]);
model.AddLinearConstraint(LinearExpr.Sum(prohibitVar), 0, 0); model.AddLinearConstraint(LinearExpr.Sum(prohibitVar), 0, 0);
prohibitVar.Clear(); }
} }
} }
} }
/// <summary>
/// Constraint: Each student's total level of effort must be within bounds.
/// </summary>
private void SetLevelOfEffort(CpModel model, BoolVar[,] x) private void SetLevelOfEffort(CpModel model, BoolVar[,] x)
{ {
long[] eventEffortCoefficients = _events.Select( long[] eventEffortCoefficients = _events.Select(
@@ -240,12 +310,7 @@ namespace Core.Calculation
effortVar[e] = x[e, s]; effortVar[e] = x[e, s];
} }
var student = _students[s]; var student = _students[s];
var experienceOffset = 0; var experienceOffset = student.TsaYear == 1 ? 1 : 0;
switch (student.TsaYear)
{
case 1: experienceOffset = 1; break;
default: break;
}
var ub = _parameters.EffortUpperBound - experienceOffset; var ub = _parameters.EffortUpperBound - experienceOffset;
var lb = _parameters.EffortLowerBound; var lb = _parameters.EffortLowerBound;
@@ -257,12 +322,16 @@ namespace Core.Calculation
} }
} }
// Limit the number of events a student is assigned /// <summary>
/// Constraint: Limit the number of events a student is assigned.
/// </summary>
private void LimitStudentAssignment(CpModel model, BoolVar[,] x) private void LimitStudentAssignment(CpModel model, BoolVar[,] x)
{ {
var studentCapacity = new List<IntVar>();
foreach (var s in _allStudents) foreach (var s in _allStudents)
{ {
var studentCapacity = new List<IntVar>(); studentCapacity.Clear();
foreach (var e in _allEvents) foreach (var e in _allEvents)
{ {
studentCapacity.Add(x[e, s]); studentCapacity.Add(x[e, s]);
@@ -270,162 +339,146 @@ namespace Core.Calculation
model.AddLinearConstraint( model.AddLinearConstraint(
LinearExpr.Sum(studentCapacity), _parameters.EventsLowerBound, _parameters.EventsUpperBound); LinearExpr.Sum(studentCapacity), _parameters.EventsLowerBound, _parameters.EventsUpperBound);
studentCapacity.Clear();
}
}
private void EventHasInterestedStudent(CpModel model, BoolVar[,] x)
{
foreach (var e in _allEvents)
{
var evt = _events[e];
var studentInterest = new List<IntVar>();
foreach (var s in _allStudents)
{
var student = _students[s];
if (student.EventRankings.Find(er => er.EventDefinition == evt) != null)
studentInterest.Add(x[e, s]);
}
model.AddLinearConstraint(
LinearExpr.Sum(studentInterest), 1, 10);
studentInterest.Clear();
} }
} }
/// <summary>
/// Adds event capacity constraints and calculates assignment thresholds.
/// </summary>
private List<EventAssignmentThresholds> AddEventAssignmentThresholds(CpModel model, BoolVar[,] x) private List<EventAssignmentThresholds> AddEventAssignmentThresholds(CpModel model, BoolVar[,] x)
{ {
var assignmentThresholdsList = new List<EventAssignmentThresholds>(); var assignmentThresholdsList = new List<EventAssignmentThresholds>();
// Limit the capacity of each event
foreach (var e in _allEvents) foreach (var e in _allEvents)
{ {
var evt = _events[e]; var evt = _events[e];
var eventPickCounts = _eventPickCounts[e]; var teamCount = CalculateTeamCount(evt, e);
var (lb, ub) = CalculateEventBounds(evt, teamCount);
var evtMinTeamSize = evt.MinTeamSize; AddEventConstraint(model, x, e, lb, ub);
var evtMaxTeamSize = evt.MaxTeamSize; assignmentThresholdsList.Add(CreateThreshold(evt, teamCount, e));
var teamDivs = eventPickCounts / (evtMinTeamSize * 1.25);
if (_includedEvents.Contains(evt))
teamDivs = 1;
//var teamsCount = (int)Math.Ceiling(teamDivs);
var teamCount = (int)Math.Round(teamDivs);
if (teamCount > evt.ChapterEligibilityCountState)
teamCount = evt.ChapterEligibilityCountState;
// limit to one team for group events
if (_parameters.LimitTeamsToOne
&& evt.EventFormat is EventFormat.Team
&& teamCount > 1
&& !_twoTeams.Contains(evt)
)
teamCount = 1;
if (_twoTeams.Contains(evt))
teamCount = 2;
if (evt.Name == "Tech Bowl")
teamCount = 1;
var eventCapacity = new List<IntVar>();
foreach (var s in _allStudents)
{
eventCapacity.Add(x[e, s]);
}
if (_droppedEvents != null && _droppedEvents.Contains(evt))
teamCount = 0;
if (evt.EventFormat == EventFormat.Individual)
evtMinTeamSize = 0;
var lb = evtMinTeamSize * teamCount;
var ub = Math.Min(evtMaxTeamSize * teamCount, _parameters.TeamSizeLimit * teamCount);
assignmentThresholdsList.Add(
new EventAssignmentThresholds
{
Event = evt,
TeamCount = teamCount,
LowerBound = evtMinTeamSize,
UpperBound = evtMaxTeamSize,
StudentRankingCount = eventPickCounts
});
model.AddLinearConstraint(LinearExpr.Sum(eventCapacity), lb, ub);
Debug.WriteLine($"{evt.Name,30}\t{evt.EventFormat,-10}\t{lb} - {ub}"); Debug.WriteLine($"{evt.Name,30}\t{evt.EventFormat,-10}\t{lb} - {ub}");
model.Minimize(LinearExpr.Sum(eventCapacity));
eventCapacity.Clear();
} }
return assignmentThresholdsList; return assignmentThresholdsList;
} }
/// <summary>
/// Calculates how many teams should be formed for an event.
/// </summary>
private int CalculateTeamCount(EventDefinition evt, int eventIndex)
{
var eventPickCounts = _eventPickCounts[eventIndex];
var teamDivs = eventPickCounts / (evt.MinTeamSize * TEAM_DIVISION_MULTIPLIER);
if (_includedEvents.Contains(evt))
return 1;
var teamCount = (int)Math.Round(teamDivs);
if (teamCount > evt.ChapterEligibilityCountState)
teamCount = evt.ChapterEligibilityCountState;
// Limit to one team for group events
if (_parameters.LimitTeamsToOne
&& evt.EventFormat is EventFormat.Team
&& teamCount > 1
&& !_twoTeams.Contains(evt))
teamCount = 1;
if (_twoTeams.Contains(evt))
teamCount = 2;
if (_droppedEvents.Contains(evt))
teamCount = 0;
return teamCount;
}
/// <summary>
/// Calculates the lower and upper bounds for event capacity.
/// </summary>
private (int lb, int ub) CalculateEventBounds(EventDefinition evt, int teamCount)
{
var evtMinTeamSize = evt.MinTeamSize;
var evtMaxTeamSize = evt.MaxTeamSize;
if (evt.EventFormat == EventFormat.Individual)
evtMinTeamSize = 0;
var lb = evtMinTeamSize * teamCount;
var ub = Math.Min(evtMaxTeamSize * teamCount, _parameters.TeamSizeLimit * teamCount);
return (lb, ub);
}
/// <summary>
/// Adds capacity constraint for a specific event.
/// </summary>
private void AddEventConstraint(CpModel model, BoolVar[,] x, int eventIndex, int lb, int ub)
{
var eventCapacity = new List<IntVar>();
foreach (var s in _allStudents)
{
eventCapacity.Add(x[eventIndex, s]);
}
model.AddLinearConstraint(LinearExpr.Sum(eventCapacity), lb, ub);
model.Minimize(LinearExpr.Sum(eventCapacity));
}
/// <summary>
/// Creates a threshold object for tracking event assignment metadata.
/// </summary>
private EventAssignmentThresholds CreateThreshold(EventDefinition evt, int teamCount, int eventIndex)
{
return new EventAssignmentThresholds
{
Event = evt,
TeamCount = teamCount,
LowerBound = evt.MinTeamSize,
UpperBound = evt.MaxTeamSize,
StudentRankingCount = _eventPickCounts[eventIndex]
};
}
/// <summary>
/// Adds user-specified assignment requirements (include/exclude student-event pairs).
/// </summary>
private void AddAssignmentRequirements(CpModel model, BoolVar[,] x) private void AddAssignmentRequirements(CpModel model, BoolVar[,] x)
{ {
foreach (var includedAssignment in _assignmentRequirements.Where(e => e.Requirement == Requirement.Include)) foreach (var includedAssignment in _assignmentRequirements.Where(a => a.Requirement == Requirement.Include))
{ {
var e = _events.IndexOf(includedAssignment.EventDefinition); var e = _events.IndexOf(includedAssignment.EventDefinition);
var s = _students.IndexOf(includedAssignment.Student); var s = _students.IndexOf(includedAssignment.Student);
model.AddAssumption(x[e, s]); model.AddAssumption(x[e, s]);
} }
foreach (var excludedAssignment in _assignmentRequirements.Where(e => e.Requirement == Requirement.Exclude)) var prohibitVar = new List<IntVar>(1);
foreach (var excludedAssignment in _assignmentRequirements.Where(a => a.Requirement == Requirement.Exclude))
{ {
var e = _events.IndexOf(excludedAssignment.EventDefinition); var e = _events.IndexOf(excludedAssignment.EventDefinition);
var s = _students.IndexOf(excludedAssignment.Student); var s = _students.IndexOf(excludedAssignment.Student);
var prohibitVar = new List<IntVar> { x[e, s] };
model.AddLinearConstraint(LinearExpr.Sum(prohibitVar), 0, 0);
prohibitVar.Clear(); prohibitVar.Clear();
prohibitVar.Add(x[e, s]);
model.AddLinearConstraint(LinearExpr.Sum(prohibitVar), 0, 0);
} }
} }
/// <summary>
/// Calculates coefficients for optimizing student preferences.
/// Higher-ranked events get higher coefficients.
/// </summary>
private static long[] GetEventPickCoefficients(IList<EventDefinition> events, List<StudentEventRanking> eventRankings) private static long[] GetEventPickCoefficients(IList<EventDefinition> events, List<StudentEventRanking> eventRankings)
{ {
return return events.Select(e =>
events.Select(e =>
{ {
var eventRank = eventRankings.FirstOrDefault(er => er.EventDefinition == e); var eventRank = eventRankings.FirstOrDefault(er => er.EventDefinition == e);
return return eventRank == null
eventRank == null ? 0L
? 0L : StudentEventRanking.MaxRank - eventRank.Rank; // Inverse ranking
// TODO: MaxRank can be calculated
: StudentEventRanking.MaxRank - eventRank.Rank; // inverse
}).ToArray(); }).ToArray();
} }
public class SolutionPrinter : CpSolverSolutionCallback
{
private readonly IList<EventDefinition> _events;
private readonly IList<Student> _students;
private readonly BoolVar[,] _eventAssignment;
public SolutionPrinter(IList<EventDefinition> events, IList<Student> students, BoolVar[,] eventAssignment)
{
_events = events;
_students = students;
_eventAssignment = eventAssignment;
}
public override void OnSolutionCallback()
{
Console.WriteLine($"Solution ");
foreach (var evt in Enumerable.Range(0, _events.Count))
{
foreach (var student in Enumerable.Range(0, _students.Count))
{
if (Value(_eventAssignment[evt, student]) == 1L)
{
}
}
}
}
}
} }
} }