Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 92 additions & 38 deletions Csp/Global/CumulativeInteger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ public void Propagate(out ConstraintOperationResult result)

var profile = BuildCompulsoryProfile();

foreach (var time in profile.Keys)
foreach (var (time, cumulativeDemand) in profile)
{
if (profile[time] <= this.Capacity)
if (cumulativeDemand <= this.Capacity)
continue;

if (this.GenerateReasons)
Expand Down Expand Up @@ -248,47 +248,114 @@ private void SetReasonIfEnabled(IList<int>? contributors, int taskIndex)
this.LastReason = CollectReasonForTasks(contributors.Concat(new[] { taskIndex }));
}

private Dictionary<int, int> BuildCompulsoryProfile()
private List<(int Time, int CumulativeDemand)> BuildCompulsoryProfile()
{
var profile = new Dictionary<int, int>();
var events = new List<(int Time, int Delta)>();

for (var i = 0; i < this.Starts.Count; ++i)
{
if (!GetCompulsoryPart(i, out var compulsoryStart, out var compulsoryEnd))
continue;

for (var t = compulsoryStart; t < compulsoryEnd; ++t)
{
if (!profile.ContainsKey(t))
profile[t] = 0;
events.Add((compulsoryStart, this.Demands[i]));
events.Add((compulsoryEnd, -this.Demands[i]));
}

profile[t] += this.Demands[i];
}
if (events.Count == 0)
return new List<(int, int)>();

events.Sort((a, b) => a.Time.CompareTo(b.Time));

var profile = new List<(int Time, int CumulativeDemand)>();
var runningTotal = 0;

foreach (var (time, delta) in events)
{
runningTotal += delta;

if (profile.Count > 0 && profile[profile.Count - 1].Time == time)
profile[profile.Count - 1] = (time, runningTotal);
else
profile.Add((time, runningTotal));
}

return profile;
}

private bool IsInfeasibleWithProfile(int taskIndex, int candidateStart, Dictionary<int, int> profile)
private static int GetProfileDemandAt(List<(int Time, int CumulativeDemand)> profile, int time)
{
if (profile.Count == 0 || time < profile[0].Time)
return 0;

var lo = 0;
var hi = profile.Count - 1;

while (lo < hi)
{
var mid = lo + (hi - lo + 1) / 2;

if (profile[mid].Time <= time)
lo = mid;
else
hi = mid - 1;
}

return profile[lo].CumulativeDemand;
}

private static int FindFirstProfileIndex(List<(int Time, int CumulativeDemand)> profile, int minTime)
{
var lo = 0;
var hi = profile.Count;

while (lo < hi)
{
var mid = lo + (hi - lo) / 2;

if (profile[mid].Time < minTime)
lo = mid + 1;
else
hi = mid;
}

return lo;
}

private int FindFirstViolatingTime(int taskIndex, int candidateStart, List<(int Time, int CumulativeDemand)> profile)
{
var end = candidateStart + this.Durations[taskIndex];
var demand = this.Demands[taskIndex];

GetCompulsoryPart(taskIndex, out var compulsoryStart, out var compulsoryEnd);

for (var t = candidateStart; t < end; ++t)
{
if (!profile.TryGetValue(t, out var profileDemand))
profileDemand = 0;
var profileDemand = GetProfileDemandAt(profile, candidateStart);
var taskInCompulsory = candidateStart >= compulsoryStart && candidateStart < compulsoryEnd;
if ((taskInCompulsory ? profileDemand : profileDemand + demand) > this.Capacity)
return candidateStart;

var taskInCompulsory = t >= compulsoryStart && t < compulsoryEnd;
var totalDemand = taskInCompulsory ? profileDemand : profileDemand + demand;
var idx = FindFirstProfileIndex(profile, candidateStart + 1);
for (; idx < profile.Count && profile[idx].Time < end; ++idx)
{
taskInCompulsory = profile[idx].Time >= compulsoryStart && profile[idx].Time < compulsoryEnd;
var totalDemand = taskInCompulsory ? profile[idx].CumulativeDemand : profile[idx].CumulativeDemand + demand;

if (totalDemand > this.Capacity)
return true;
return profile[idx].Time;
}

return false;
if (compulsoryEnd > candidateStart && compulsoryEnd < end)
{
profileDemand = GetProfileDemandAt(profile, compulsoryEnd);

if (profileDemand + demand > this.Capacity)
return compulsoryEnd;
}

return -1;
}

private bool IsInfeasibleWithProfile(int taskIndex, int candidateStart, List<(int Time, int CumulativeDemand)> profile)
{
return FindFirstViolatingTime(taskIndex, candidateStart, profile) >= 0;
}

private IList<BoundReason> ReasonForProfileOverload(int time)
Expand All @@ -310,27 +377,14 @@ private IList<BoundReason> ReasonForProfileOverload(int time)
return reasons;
}

private IList<BoundReason> ReasonForTimetableFilter(int taskIndex, int candidateStart, Dictionary<int, int> profile)
private IList<BoundReason> ReasonForTimetableFilter(int taskIndex, int candidateStart, List<(int Time, int CumulativeDemand)> profile)
{
var end = candidateStart + this.Durations[taskIndex];
var demand = this.Demands[taskIndex];

GetCompulsoryPart(taskIndex, out var compulsoryStart, out var compulsoryEnd);
var violatingTime = FindFirstViolatingTime(taskIndex, candidateStart, profile);

for (var t = candidateStart; t < end; ++t)
{
profile.TryGetValue(t, out var profileDemand);

var taskInCompulsory = t >= compulsoryStart && t < compulsoryEnd;
var totalDemand = taskInCompulsory ? profileDemand : profileDemand + demand;

if (totalDemand <= this.Capacity)
continue;

return ReasonForProfileOverload(t);
}
if (violatingTime < 0)
return new List<BoundReason>();

return new List<BoundReason>();
return ReasonForProfileOverload(violatingTime);
}

private IList<BoundReason> CollectReasonForTasks(IEnumerable<int> taskIndices)
Expand Down
12 changes: 6 additions & 6 deletions Tests/Unit/OrderingHeuristicTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public void DomWdeg_NoWeights_FallsBackToSmallestDomain()
var v1 = new VariableInteger("v1", 0, 4);
var v2 = new VariableInteger("v2", 0, 14);
var variables = new List<IVariable<int>> { v0, v1, v2 };
var ordering = new DomWdegOrdering(variables, new List<IConstraint>());
var ordering = new DomWdegOrdering(variables, []);

var list = new List<IVariable<int>>(variables);
var selected = list[ordering.SelectVariableIndex(list)];
Expand All @@ -37,7 +37,7 @@ public void DomWdeg_WithWeights_SelectsLowestRatio()

var constraint = new ConstraintInteger(v1 > 0);
constraint.FailureWeight = 10;
var ordering = new DomWdegOrdering(variables, new List<IConstraint> { constraint });
var ordering = new DomWdegOrdering(variables, [constraint]);

var list = new List<IVariable<int>>(variables);
var selected = list[ordering.SelectVariableIndex(list)];
Expand All @@ -50,10 +50,10 @@ public void DomWdeg_SolvesNQueens4_WithCorrectSolutionCount()
{
var queens = new List<VariableInteger>
{
new VariableInteger("0", 0, 3),
new VariableInteger("1", 0, 3),
new VariableInteger("2", 0, 3),
new VariableInteger("3", 0, 3)
new("0", 0, 3),
new("1", 0, 3),
new("2", 0, 3),
new("3", 0, 3)
};

var constraints = new List<IConstraint>();
Expand Down