Add Blazor WebApp and rework data handling to utilize Entity Framework
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
using Core.Entities;
|
||||
|
||||
namespace Core.Calculation;
|
||||
|
||||
public class DataProcessing
|
||||
{
|
||||
public static EventStudentPicks[] GetEventStudentPicks(IList<CompetitiveEvent> events, IList<Student> students)
|
||||
{
|
||||
return
|
||||
students.SelectMany(
|
||||
student => student.RankedEventPicks.Select((e, i) => (e, student, i + 1)))
|
||||
.OrderBy(tuple => tuple.Item3)
|
||||
.ThenByDescending(tuple => tuple.student.Grade + tuple.student.TsaYear)
|
||||
.GroupBy(tuple => tuple.e)
|
||||
.OrderBy(tuples => tuples.Key.Name)
|
||||
.Select(tuples =>
|
||||
new EventStudentPicks(tuples.Key, tuples.Select(tuple => Tuple.Create(tuple.student, tuple.Item3)).ToList())
|
||||
).ToArray();
|
||||
}
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using Core.Entities;
|
||||
using Google.OrTools.Sat;
|
||||
using IntVar = Google.OrTools.Sat.IntVar;
|
||||
|
||||
namespace Core.Calculation
|
||||
{
|
||||
public class EventAssigner
|
||||
{
|
||||
private readonly IList<CompetitiveEvent> _events;
|
||||
private readonly IList<Student> _students;
|
||||
private readonly AssignmentParameters _parameters;
|
||||
private readonly int[] _allEvents;
|
||||
private readonly int[] _allStudents;
|
||||
// how many students have picked each event?
|
||||
private readonly int[] _eventPickCounts;
|
||||
private IList<EventAssignment> _preAssigned = new List<EventAssignment>();
|
||||
private IList<EventAssignment> _prohibited = new List<EventAssignment>();
|
||||
private IList<CompetitiveEvent> _droppedEvents;
|
||||
private IList<CompetitiveEvent> _includedEvents;
|
||||
|
||||
public EventAssigner(IList<CompetitiveEvent> events, IList<Student> students, AssignmentParameters parameters)
|
||||
{
|
||||
_events = events;
|
||||
_students = students;
|
||||
_parameters = parameters;
|
||||
_allEvents = Enumerable.Range(0, _events.Count).ToArray();
|
||||
_allStudents = Enumerable.Range(0, _students.Count).ToArray();
|
||||
_eventPickCounts = new int[_allEvents.Length];
|
||||
_preAssigned = new List<EventAssignment>();
|
||||
_droppedEvents = new List<CompetitiveEvent>();
|
||||
_includedEvents = new List<CompetitiveEvent>();
|
||||
for (var i = 0; i < _events.Count; i++)
|
||||
{
|
||||
var e = _events[i];
|
||||
_eventPickCounts[i] = _students.Count(s => s.RankedEventPicks.Contains(e));
|
||||
}
|
||||
}
|
||||
|
||||
public void AssignToEvent(EventAssignment preAssigned)
|
||||
{
|
||||
_preAssigned.Add(preAssigned);
|
||||
}
|
||||
|
||||
|
||||
public void ExcludeFromEvent(EventAssignment prohibited)
|
||||
{
|
||||
_prohibited.Add(prohibited);
|
||||
}
|
||||
|
||||
public void RemoveEvent(IList<CompetitiveEvent> events)
|
||||
{
|
||||
_droppedEvents = events;
|
||||
}
|
||||
public void IncludedEvents(IList<CompetitiveEvent> events)
|
||||
{
|
||||
_includedEvents = events;
|
||||
}
|
||||
|
||||
public class SolutionPrinter : CpSolverSolutionCallback
|
||||
{
|
||||
private readonly IList<CompetitiveEvent> _events;
|
||||
private readonly IList<Student> _students;
|
||||
private readonly BoolVar[,] _eventAssignment;
|
||||
|
||||
public SolutionPrinter(IList<CompetitiveEvent> 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Team[] Solve()
|
||||
{
|
||||
|
||||
// Model.
|
||||
var model = new CpModel();
|
||||
|
||||
// Variables.
|
||||
var x = new BoolVar[_allEvents.Length, _allStudents.Length];
|
||||
foreach (var e in _allEvents)
|
||||
foreach (var s in _allStudents)
|
||||
x[e, s] = model.NewBoolVar($"eventAssignments[{e},{s}]");
|
||||
|
||||
foreach (var preAssignment in _preAssigned)
|
||||
{
|
||||
var e = _events.IndexOf(preAssignment.Event);
|
||||
var s = _students.IndexOf(preAssignment.Student);
|
||||
model.AddAssumption(x[e, s]);
|
||||
}
|
||||
|
||||
foreach (var prohibit in _prohibited)
|
||||
{
|
||||
var e = _events.IndexOf(prohibit.Event);
|
||||
var s = _students.IndexOf(prohibit.Student);
|
||||
|
||||
var prohibitVar = new List<IntVar> { x[e, s] };
|
||||
|
||||
model.AddLinearConstraint(LinearExpr.Sum(prohibitVar), 0, 0);
|
||||
prohibitVar.Clear();
|
||||
}
|
||||
|
||||
// Limit the capacity of each event
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
var evt = _events[e];
|
||||
var eventPickCounts = _eventPickCounts[e];
|
||||
|
||||
var evtMinTeamSize = evt.MinTeamSize;
|
||||
var evtMaxTeamSize = evt.MaxTeamSize;
|
||||
|
||||
var teamDivs = eventPickCounts / (evtMinTeamSize * 1.0);
|
||||
if (_includedEvents.Contains(evt))
|
||||
teamDivs = 1;
|
||||
|
||||
//var teamsCount = (int)Math.Ceiling(teamDivs);
|
||||
var teamCount = (int)Math.Round(teamDivs);
|
||||
if (teamCount > evt.MaxTeamCountState)
|
||||
teamCount = evt.MaxTeamCountState;
|
||||
|
||||
// limit to one team for group events
|
||||
if (_parameters.LimitTeamsToOne && evt.Format is EventFormat.Team && teamCount > 1) teamCount = 1;
|
||||
|
||||
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;
|
||||
|
||||
var lb = evtMinTeamSize * teamCount;
|
||||
var ub = Math.Min(evtMaxTeamSize * teamCount, _parameters.TeamSizeLimit);
|
||||
model.AddLinearConstraint(LinearExpr.Sum(eventCapacity), lb, ub);
|
||||
Debug.WriteLine($"{evt.Name,30}\t{evt.Format,-10}\t{lb} - {ub}");
|
||||
|
||||
model.Minimize(LinearExpr.Sum(eventCapacity));
|
||||
eventCapacity.Clear();
|
||||
}
|
||||
|
||||
// Limit the number of events a student is assigned
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var student = _students[s];
|
||||
|
||||
var studentCapacity = new List<IntVar>();
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
studentCapacity.Add(x[e, s]);
|
||||
}
|
||||
|
||||
model.AddLinearConstraint(LinearExpr.Sum(studentCapacity), _parameters.AssignmentLowerBound, _parameters.AssignmentUpperBound);
|
||||
studentCapacity.Clear();
|
||||
}
|
||||
|
||||
var eventEffortCoefficients = GetEventEffortCoefficients(_events);
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var effortVar = new BoolVar[_allEvents.Length];
|
||||
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
effortVar[e] = x[e, s];
|
||||
}
|
||||
var student = _students[s];
|
||||
var experienceOffset = 0;
|
||||
switch (student.TsaYear)
|
||||
{
|
||||
case 1: experienceOffset = 1; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
var ub = _parameters.EffortUpperBound - experienceOffset;
|
||||
var lb = _parameters.EffortLowerBound;
|
||||
if (ub <= lb)
|
||||
lb = ub - 1;
|
||||
|
||||
model.Add(LinearExpr.WeightedSum(effortVar, eventEffortCoefficients) >= lb);
|
||||
model.Add(LinearExpr.WeightedSum(effortVar, eventEffortCoefficients) <= ub);
|
||||
}
|
||||
|
||||
// each student should be assigned at least one on site activity event
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var onSiteActivity = new List<ILiteral>();
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
if (_events[e].OnSiteActivity)
|
||||
onSiteActivity.Add(x[e, s]);
|
||||
}
|
||||
|
||||
if (_parameters.RequireOnSite)
|
||||
model.AddAtLeastOne(onSiteActivity);
|
||||
onSiteActivity.Clear();
|
||||
}
|
||||
|
||||
// students should have at maximum one individual event
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var individualEvent = new List<ILiteral>();
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
if (_events[e].Format == EventFormat.Individual)
|
||||
individualEvent.Add(x[e, s]);
|
||||
}
|
||||
|
||||
model.AddAtMostOne(individualEvent);
|
||||
individualEvent.Clear();
|
||||
}
|
||||
|
||||
// students should have at maximum regional events
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var regionalEvent = new List<ILiteral>();
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
if (_events[e].RegionalEvent)
|
||||
regionalEvent.Add(x[e, s]);
|
||||
}
|
||||
|
||||
if (_parameters.RequireRegional)
|
||||
model.AddLinearConstraint(LinearExpr.Sum(regionalEvent), 1, 2);
|
||||
regionalEvent.Clear();
|
||||
}
|
||||
|
||||
var maximizePicks = LinearExpr.NewBuilder();
|
||||
// optimize student selections
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var eventPickCoefficients = GetEventPickCoefficients(_students[s].RankedEventPicks, _events);
|
||||
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
maximizePicks.AddTerm(x[e, s], eventPickCoefficients[e]);
|
||||
}
|
||||
}
|
||||
model.Maximize(maximizePicks);
|
||||
|
||||
var solver = new CpSolver();
|
||||
var cpSolverStatus = solver.Solve(model);
|
||||
|
||||
// print solver status
|
||||
Console.WriteLine($"Solver status: {cpSolverStatus}");
|
||||
|
||||
var eventAssignmentsList = new List<Team>();
|
||||
|
||||
if (cpSolverStatus == CpSolverStatus.Optimal || cpSolverStatus == CpSolverStatus.Feasible)
|
||||
{
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
var students = new List<Student>();
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
if (solver.BooleanValue(x[e, s]))
|
||||
{
|
||||
students.Add(_students[s]);
|
||||
//Console.WriteLine($"{_events[evt].Name} : {_students[s].Name}");
|
||||
}
|
||||
}
|
||||
if (students.Count > 0)
|
||||
eventAssignmentsList.Add(new Team(_events[e].Name, _events[e], students));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Console.WriteLine("No solution found.");
|
||||
}
|
||||
|
||||
return eventAssignmentsList.ToArray();
|
||||
}
|
||||
|
||||
private long[] GetEventPickCoefficients(IList<CompetitiveEvent> rankedEvents, IEnumerable<CompetitiveEvent> events)
|
||||
{
|
||||
var eventPickCount = rankedEvents.Count;
|
||||
|
||||
return
|
||||
events.Select(e =>
|
||||
{
|
||||
var eventPickIndex = rankedEvents.IndexOf(e);
|
||||
return eventPickIndex switch
|
||||
{
|
||||
0 => eventPickCount + 1,
|
||||
1 => eventPickCount + 0,
|
||||
> 1 => eventPickCount ,
|
||||
_ => 0L
|
||||
};
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
|
||||
private long[] GetEventEffortCoefficients(IEnumerable<CompetitiveEvent> events)
|
||||
{
|
||||
return
|
||||
events.Select(e => e.Name == "Tech Bowl" ? 0 : e.LevelOfEffort.HasValue ? e.LevelOfEffort.Value : 10L).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Core.Entities;
|
||||
using Google.OrTools.Sat;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using IntVar = Google.OrTools.Sat.IntVar;
|
||||
|
||||
namespace Core.Calculation
|
||||
{
|
||||
public class EventAssignment
|
||||
{
|
||||
private readonly IList<EventDefinition> _events;
|
||||
private readonly IList<Student> _students;
|
||||
private readonly AssignmentParameters _parameters;
|
||||
private readonly int[] _allEvents;
|
||||
private readonly int[] _allStudents;
|
||||
// how many students have picked each eventDefinition?
|
||||
private readonly int[] _eventPickCounts;
|
||||
private IList<AssignmentRequirement> _assignmentRequirements = new List<AssignmentRequirement>();
|
||||
private IList<EventDefinition> _droppedEvents = new List<EventDefinition>();
|
||||
private IList<EventDefinition> _includedEvents = new List<EventDefinition>();
|
||||
private IList<EventDefinition> _twoTeams = new List<EventDefinition>();
|
||||
|
||||
public EventAssignment(IList<EventDefinition> events, IList<Student> students, AssignmentParameters parameters)
|
||||
{
|
||||
_events = events;
|
||||
_students = students;
|
||||
_parameters = parameters;
|
||||
_allEvents = Enumerable.Range(0, _events.Count).ToArray();
|
||||
_allStudents = Enumerable.Range(0, _students.Count).ToArray();
|
||||
_eventPickCounts = new int[_allEvents.Length];
|
||||
for (var i = 0; i < _events.Count; i++)
|
||||
{
|
||||
var e = _events[i];
|
||||
|
||||
_eventPickCounts[i] = _students.Count(s => s.EventRankings.Count(er => er.EventDefinition == e) > 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddAssignmentRequirement(AssignmentRequirement assignmentRequirement)
|
||||
{
|
||||
_assignmentRequirements.Add(assignmentRequirement);
|
||||
}
|
||||
|
||||
public void RemoveEvents(IList<EventDefinition> events)
|
||||
{
|
||||
_droppedEvents = events;
|
||||
}
|
||||
|
||||
public void IncludedEvents(IList<EventDefinition> events)
|
||||
{
|
||||
_includedEvents = events;
|
||||
}
|
||||
|
||||
public void AllowTwoTeams(IList<EventDefinition> events)
|
||||
{
|
||||
_twoTeams = events;
|
||||
}
|
||||
|
||||
|
||||
public async Task<EventAssignmentSolution> Solve()
|
||||
{
|
||||
Debug.WriteLine(_parameters);
|
||||
|
||||
// Model.
|
||||
var model = new CpModel();
|
||||
|
||||
// Variables.
|
||||
var x = new BoolVar[_allEvents.Length, _allStudents.Length];
|
||||
foreach (var e in _allEvents)
|
||||
foreach (var s in _allStudents)
|
||||
x[e, s] = model.NewBoolVar($"eventAssignments[{e},{s}]");
|
||||
|
||||
AddAssignmentRequirements(model, x);
|
||||
|
||||
var assignmentThresholdsList = AddEventAssignmentThresholds(model, x);
|
||||
|
||||
LimitStudentAssignment(model, x);
|
||||
|
||||
// set the range for level of effort
|
||||
var eventEffortCoefficients = GetEventEffortCoefficients(_events);
|
||||
SetLevelOfEffort(model, x, eventEffortCoefficients);
|
||||
|
||||
// each student should be assigned at least one on site activity eventDefinition
|
||||
if (_parameters.RequireOnSite)
|
||||
RequireOnSiteActivity(model, x);
|
||||
|
||||
// students should have at maximum one individual eventDefinition
|
||||
LimitIndividualEvent(model, x);
|
||||
|
||||
// students should have at least one regional event
|
||||
if (_parameters.RequireRegional)
|
||||
RequireRegionalEvent(model, x);
|
||||
|
||||
OptimizeStudentEventRankings(model, x);
|
||||
|
||||
Debug.WriteLine("Starting optimization");
|
||||
var solver = new CpSolver();
|
||||
var cpSolverStatus = await Task.Run(() => solver.Solve(model));
|
||||
|
||||
// print solver status
|
||||
Debug.WriteLine($"Solver status: {cpSolverStatus}");
|
||||
|
||||
var eventAssignmentsList = GetEventAssignments(x, solver, cpSolverStatus);
|
||||
|
||||
var eventAssignmentSolution =
|
||||
new EventAssignmentSolution
|
||||
(
|
||||
eventAssignmentsList.ToArray(),
|
||||
cpSolverStatus.ToString(),
|
||||
assignmentThresholdsList
|
||||
);
|
||||
|
||||
return eventAssignmentSolution;
|
||||
}
|
||||
|
||||
private List<Team> GetEventAssignments(BoolVar[,] x, CpSolver solver, CpSolverStatus cpSolverStatus)
|
||||
{
|
||||
var eventAssignmentsList = new List<Team>();
|
||||
|
||||
if (cpSolverStatus == CpSolverStatus.Optimal || cpSolverStatus == CpSolverStatus.Feasible)
|
||||
{
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
var students = new List<Student>();
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
if (solver.BooleanValue(x[e, s]))
|
||||
{
|
||||
students.Add(_students[s]);
|
||||
|
||||
}
|
||||
}
|
||||
if (students.Count > 0)
|
||||
eventAssignmentsList.Add(new Team { TeamId = _events[e].Name, Event = _events[e], Students = students});
|
||||
}
|
||||
}
|
||||
|
||||
return eventAssignmentsList;
|
||||
}
|
||||
|
||||
private void OptimizeStudentEventRankings(CpModel model, BoolVar[,] x)
|
||||
{
|
||||
var maximizePicks = LinearExpr.NewBuilder();
|
||||
|
||||
// optimize student event rankings
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var eventPickCoefficients = GetEventPickCoefficients(_events, _students[s].EventRankings);
|
||||
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
maximizePicks.AddTerm(x[e, s], eventPickCoefficients[e]);
|
||||
}
|
||||
}
|
||||
model.Maximize(maximizePicks);
|
||||
}
|
||||
|
||||
private void RequireRegionalEvent(CpModel model, BoolVar[,] x)
|
||||
{
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var regionalEvent = new List<ILiteral>();
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
if (_events[e].RegionalEvent)
|
||||
regionalEvent.Add(x[e, s]);
|
||||
}
|
||||
|
||||
//if (_parameters.RequireRegional)
|
||||
model.AddLinearConstraint(LinearExpr.Sum(regionalEvent), 1, 2);
|
||||
regionalEvent.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void LimitIndividualEvent(CpModel model, BoolVar[,] x)
|
||||
{
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var individualEvent = new List<ILiteral>();
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
if (_events[e].EventFormat == EventFormat.Individual)
|
||||
individualEvent.Add(x[e, s]);
|
||||
}
|
||||
|
||||
model.AddAtMostOne(individualEvent);
|
||||
individualEvent.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void RequireOnSiteActivity(CpModel model, BoolVar[,] x)
|
||||
{
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var onSiteActivity = new List<ILiteral>();
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
if (_events[e].OnSiteActivity)
|
||||
onSiteActivity.Add(x[e, s]);
|
||||
}
|
||||
|
||||
//if (_parameters.RequireOnSite)
|
||||
model.AddAtLeastOne(onSiteActivity);
|
||||
onSiteActivity.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLevelOfEffort(CpModel model, BoolVar[,] x, long[] eventEffortCoefficients)
|
||||
{
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var effortVar = new BoolVar[_allEvents.Length];
|
||||
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
effortVar[e] = x[e, s];
|
||||
}
|
||||
var student = _students[s];
|
||||
var experienceOffset = 0;
|
||||
switch (student.TsaYear)
|
||||
{
|
||||
case 1: experienceOffset = 1; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
var ub = _parameters.EffortUpperBound - experienceOffset;
|
||||
var lb = _parameters.EffortLowerBound;
|
||||
if (ub <= lb)
|
||||
lb = ub - 1;
|
||||
|
||||
model.Add(LinearExpr.WeightedSum(effortVar, eventEffortCoefficients) >= lb);
|
||||
model.Add(LinearExpr.WeightedSum(effortVar, eventEffortCoefficients) <= ub);
|
||||
}
|
||||
}
|
||||
|
||||
private void LimitStudentAssignment(CpModel model, BoolVar[,] x)
|
||||
{
|
||||
// Limit the number of events a student is assigned
|
||||
foreach (var s in _allStudents)
|
||||
{
|
||||
var studentCapacity = new List<IntVar>();
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
studentCapacity.Add(x[e, s]);
|
||||
}
|
||||
|
||||
model.AddLinearConstraint(
|
||||
LinearExpr.Sum(studentCapacity), _parameters.EventsLowerBound, _parameters.EventsUpperBound);
|
||||
studentCapacity.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private List<EventAssignmentThresholds> AddEventAssignmentThresholds(CpModel model, BoolVar[,] x)
|
||||
{
|
||||
var assignmentThresholdsList = new List<EventAssignmentThresholds>();
|
||||
// Limit the capacity of each event
|
||||
foreach (var e in _allEvents)
|
||||
{
|
||||
var evt = _events[e];
|
||||
var eventPickCounts = _eventPickCounts[e];
|
||||
|
||||
var evtMinTeamSize = evt.MinTeamSize;
|
||||
var evtMaxTeamSize = evt.MaxTeamSize;
|
||||
|
||||
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.MaxTeamCountState)
|
||||
teamCount = evt.MaxTeamCountState;
|
||||
|
||||
// limit to one team for group events
|
||||
if (_parameters.LimitTeamsToOne
|
||||
&& evt.EventFormat is EventFormat.Team
|
||||
&& teamCount > 1
|
||||
&& !_twoTeams.Contains(evt)
|
||||
)
|
||||
teamCount = 1;
|
||||
|
||||
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;
|
||||
|
||||
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}");
|
||||
|
||||
model.Minimize(LinearExpr.Sum(eventCapacity));
|
||||
eventCapacity.Clear();
|
||||
}
|
||||
|
||||
return assignmentThresholdsList;
|
||||
}
|
||||
|
||||
private void AddAssignmentRequirements(CpModel model, BoolVar[,] x)
|
||||
{
|
||||
foreach (var includedAssignment in _assignmentRequirements.Where(e => e.Requirement == Requirement.Include))
|
||||
{
|
||||
var e = _events.IndexOf(includedAssignment.EventDefinition);
|
||||
var s = _students.IndexOf(includedAssignment.Student);
|
||||
model.AddAssumption(x[e, s]);
|
||||
}
|
||||
|
||||
foreach (var excludedAssignment in _assignmentRequirements.Where(e => e.Requirement == Requirement.Exclude))
|
||||
{
|
||||
var e = _events.IndexOf(excludedAssignment.EventDefinition);
|
||||
var s = _students.IndexOf(excludedAssignment.Student);
|
||||
|
||||
var prohibitVar = new List<IntVar> { x[e, s] };
|
||||
|
||||
model.AddLinearConstraint(LinearExpr.Sum(prohibitVar), 0, 0);
|
||||
prohibitVar.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static long[] GetEventPickCoefficients(IList<EventDefinition> events, List<StudentEventRanking> eventRankings)
|
||||
{
|
||||
return
|
||||
events.Select(e =>
|
||||
{
|
||||
var eventRank = eventRankings.FirstOrDefault(er => er.EventDefinition == e);
|
||||
return
|
||||
eventRank == null
|
||||
? 0L
|
||||
: StudentEventRanking.MaxRank - eventRank.Rank; // inverse
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
private long[] GetEventEffortCoefficients(IEnumerable<EventDefinition> events)
|
||||
{
|
||||
return
|
||||
events.Select(
|
||||
e =>
|
||||
//e.Name == "Tech Bowl"
|
||||
// ? 0 // Tech bowl gets requires no effort?
|
||||
//:
|
||||
e.LevelOfEffort ?? 10L
|
||||
).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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Core.Entities;
|
||||
|
||||
namespace Core.Calculation;
|
||||
|
||||
public class EventAssignmentSolution(
|
||||
Team[] teams,
|
||||
string status,
|
||||
List<EventAssignmentThresholds> assignmentThresholds)
|
||||
{
|
||||
public Team[] Teams { get; set; } = teams;
|
||||
public string Status { get; set; } = status;
|
||||
public List<EventAssignmentThresholds> AssignmentThresholds { get; } = assignmentThresholds;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Core.Entities;
|
||||
|
||||
namespace Core.Calculation;
|
||||
|
||||
public class EventAssignmentThresholds
|
||||
{
|
||||
public EventDefinition Event { get; set; }
|
||||
public int TeamCount { get; set; }
|
||||
public int LowerBound { get; set; }
|
||||
public int UpperBound { get; set; }
|
||||
public int StudentRankingCount { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Core.Calculation;
|
||||
|
||||
public enum UnassignedScheduleStrategy
|
||||
{
|
||||
BiggestGroup,
|
||||
IndividualEvents,
|
||||
AnyNotMeetingAlready,
|
||||
LevelOfEffort,
|
||||
Any
|
||||
}
|
||||
@@ -2,15 +2,6 @@
|
||||
|
||||
namespace Core.Calculation;
|
||||
|
||||
public enum UnassignedScheduleStrategy
|
||||
{
|
||||
BiggestGroup,
|
||||
IndividualEvents,
|
||||
AnyNotMeetingAlready,
|
||||
LevelOfEffort,
|
||||
Any
|
||||
}
|
||||
|
||||
public class UnassignedStudentScheduler
|
||||
{
|
||||
private readonly IList<Student> _students;
|
||||
@@ -86,21 +77,21 @@ public class UnassignedStudentScheduler
|
||||
_teams
|
||||
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
|
||||
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
||||
.Where(t => t.Students.Count > 1) //|| t.Event.Format is EventFormat.Individual
|
||||
.Where(t => t.Students.Count > 1) //|| t.Event.EventFormat is EventFormat.Individual
|
||||
//.OrderBy(t => scheduledTeams.Count(st => st.Name == t.Name))
|
||||
.OrderByDescending(t => t.Students.Count); // select descending greatest number of students assigned
|
||||
//.ThenBy(t => Random.Shared.Next()); // todo: sort by student historic record of event assignment
|
||||
//.ThenBy(t => Random.Shared.Next()); // todo: sort by student historic record of eventDefinition assignment
|
||||
|
||||
// find individual events unassigned students can work on
|
||||
private IEnumerable<Team> GetAvailableTeams_Individual(
|
||||
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
|
||||
_teams
|
||||
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
|
||||
.Where(t => t.Event.Format == EventFormat.Individual || t.Students.Count == 1)
|
||||
.Where(t => t.Event.EventFormat == EventFormat.Individual || t.Students.Count == 1)
|
||||
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
||||
.Where(t => t.Students.Count > 0);
|
||||
|
||||
// find any unassigned event students can work on
|
||||
// find any unassigned eventDefinition students can work on
|
||||
private IEnumerable<Team> GetAvailableTeams_AnyNotMeetingAlready(
|
||||
IEnumerable<Team> scheduledTeams, IEnumerable<Student> assignedStudents) =>
|
||||
_teams
|
||||
@@ -121,7 +112,7 @@ public class UnassignedStudentScheduler
|
||||
_teams
|
||||
.Where(t => scheduledTeams.All(st => st.Name != t.Name))
|
||||
.Select(t => t.CloneWithOmittedStudents(assignedStudents))
|
||||
.Where(t => t.Students.Count > 1) //|| t.Event.Format is EventFormat.Individual
|
||||
.Where(t => t.Students.Count > 1) //|| t.Event.EventFormat is EventFormat.Individual
|
||||
//.OrderBy(t => scheduledTeams.Count(st => st.Name == t.Name))
|
||||
.OrderByDescending(t => t.Event.LevelOfEffort); // select descending greatest number of students assigned
|
||||
|
||||
|
||||
Reference in New Issue
Block a user