Skip to content

Commit

Permalink
Add TaskAssigner project.
Browse files Browse the repository at this point in the history
  • Loading branch information
hcoona committed Jun 4, 2020
1 parent b94761f commit b019f9b
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 0 deletions.
8 changes: 8 additions & 0 deletions OneDotNet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SerilogLab", "codelab\Seril
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xceed.Wpf.Toolkit", "third_party\ExtendedWPFToolkit\Xceed.Wpf.Toolkit\Xceed.Wpf.Toolkit.csproj", "{6F79C3CF-633E-4645-A3B1-D9D6520526CD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TaskAssigner", "TaskAssigner\TaskAssigner.csproj", "{02544C21-1FEB-4B34-9F01-288EE78B3630}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -329,6 +331,11 @@ Global
{6F79C3CF-633E-4645-A3B1-D9D6520526CD}.DebugNonWindows|Any CPU.Build.0 = Debug|Any CPU
{6F79C3CF-633E-4645-A3B1-D9D6520526CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6F79C3CF-633E-4645-A3B1-D9D6520526CD}.Release|Any CPU.Build.0 = Release|Any CPU
{02544C21-1FEB-4B34-9F01-288EE78B3630}.Debug|Any CPU.ActiveCfg = Debug|x64
{02544C21-1FEB-4B34-9F01-288EE78B3630}.Debug|Any CPU.Build.0 = Debug|x64
{02544C21-1FEB-4B34-9F01-288EE78B3630}.DebugNonWindows|Any CPU.ActiveCfg = Release|x64
{02544C21-1FEB-4B34-9F01-288EE78B3630}.DebugNonWindows|Any CPU.Build.0 = Release|x64
{02544C21-1FEB-4B34-9F01-288EE78B3630}.Release|Any CPU.ActiveCfg = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -372,6 +379,7 @@ Global
{9578F6A5-5C44-46F3-9965-DEF4D39FC5D5} = {BB7EFF62-B03A-4996-9798-17DB422DD96F}
{B0AF0ADB-08FE-4312-BDDE-3C3562FB7E37} = {9D7C39A1-EF36-4491-8CC0-2D45C3B51580}
{6F79C3CF-633E-4645-A3B1-D9D6520526CD} = {E307CA64-13F5-446A-BCA5-C611A235A2E4}
{02544C21-1FEB-4B34-9F01-288EE78B3630} = {9D7C39A1-EF36-4491-8CC0-2D45C3B51580}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E0F2E2F1-944D-46B4-BA89-EE1F0BBD57A1}
Expand Down
26 changes: 26 additions & 0 deletions TaskAssigner/Assigner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Google.OrTools.LinearSolver;
using System;

namespace TaskAssigner
{
public class Assigner
{
private readonly Solver solver_;

public Assigner(Solver solver)
{
this.solver_ = solver ?? throw new ArgumentNullException(nameof(solver));
}

public AssignmentDescription Solve(AssignmentDescription assignment)
{
if (assignment is null)
{
throw new ArgumentNullException(nameof(assignment));
}

var assignerCore = new AssignerCore(this.solver_, assignment);
return assignerCore.Run();
}
}
}
146 changes: 146 additions & 0 deletions TaskAssigner/AssignerCore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using Google.OrTools.LinearSolver;
using System;
using System.Collections.Generic;
using System.Linq;

namespace TaskAssigner
{
public class AssignerCore
{
private readonly Solver solver_;
private readonly AssignmentDescription assignment_;
private readonly int[,] costs_;
private readonly Variable[,] variables_;

// TODO(zhangshuai.ds): Pass in 3 policies.
public AssignerCore(Solver solver, AssignmentDescription assignment)
{
this.solver_ = solver ?? throw new ArgumentNullException(nameof(solver));
this.assignment_ = assignment ?? throw new ArgumentNullException(nameof(assignment));
this.costs_ = this.ComputeCosts(assignment);
this.variables_ = this.solver_.MakeBoolVarMatrix(assignment.Nodes.Count, assignment.Tasks.Count);
}

public AssignmentDescription Run()
{
this.AddObjective();
this.AddConstraints();

Solver.ResultStatus status = this.solver_.Solve();
if (status == Solver.ResultStatus.OPTIMAL)
{
var result = new AssignmentDescription();

foreach (NodeDescription n in this.assignment_.Nodes)
{
result.Nodes.Add(n);
}

foreach (TaskDescription t in this.assignment_.Tasks)
{
result.Tasks.Add(t);
}

for (int i = 1; i < this.assignment_.Nodes.Count; i++)
{
for (int j = 0; j < this.assignment_.Tasks.Count; j++)
{
if (this.variables_[i, j].SolutionValue() > 0)
{
if (!result.Assignments.TryGetValue(i, out ISet<int> tasks))
{
tasks = new HashSet<int>();
result.Assignments.Add(i, tasks);
}
tasks.Add(j);
Console.WriteLine($"Task {j} assigned to node {i} with cost {this.costs_[i, j]}.");
}
}
}
Console.WriteLine($"Total cost is {this.solver_.Objective().Value()}");

return result;
}

throw new NotImplementedException($"Not implemented for status: {status}");
}

protected int[,] ComputeCosts(AssignmentDescription assignment)
{
int[,] costs = new int[assignment.Nodes.Count, assignment.Tasks.Count];
for (int j = 0; j < assignment.Tasks.Count; j++)
{
costs[0, j] = 500; // UNASSIGNED cost
if (assignment.Assignments[0].Contains(j)) // Not assigned yet.
{
for (int i = 1; i < assignment.Nodes.Count; i++)
{
costs[i, j] = 0; // No fee for assignment.
}
}
else
{
for (int i = 1; i < assignment.Nodes.Count; i++)
{
if (assignment.Assignments[i].Contains(j))
{
costs[i, j] = 0; // Don't move.
}
else
{
costs[i, j] = 200; // Moving cost.
}
}
}
}
return costs;
}

protected void AddObjective()
{
var movingCosts = new LinearExpr();
for (int i = 0; i < this.assignment_.Nodes.Count; i++)
{
for (int j = 0; j < this.assignment_.Tasks.Count; j++)
{
movingCosts += this.costs_[i, j] * this.variables_[i, j];
}
}

// TODO(zhangshuai.ds): Computes it.
var imbalanceCosts = new LinearExpr();

// TODO(zhangshuai.ds): Add coefficients for the two costs.
this.solver_.Minimize(movingCosts + imbalanceCosts);
}

protected void AddConstraints()
{
// Each task is assigned to exactly one node (including UNASSIGNED virtual node).
for (int j = 0; j < this.assignment_.Tasks.Count; j++)
{
LinearExpr expr = Enumerable.Range(0, this.assignment_.Nodes.Count)
.Select(i => this.variables_[i, j])
.ToArray()
.Sum();
this.solver_.Add(expr == 1);
}

// Each worker cannot exceed its capacity.
for (int i = 0; i < this.assignment_.Nodes.Count; i++)
{
var cpuCores = new LinearExpr();
var memoryMiB = new LinearExpr();

for (int j = 0; j < this.assignment_.Tasks.Count; j++)
{
cpuCores += this.variables_[i, j] * this.assignment_.Tasks[j].CpuCores;
memoryMiB += this.variables_[i, j] * this.assignment_.Tasks[j].MemoryMiB;
}

this.solver_.Add(cpuCores <= this.assignment_.Nodes[i].CpuCores);
this.solver_.Add(memoryMiB <= this.assignment_.Nodes[i].MemoryMiB);
}
}
}
}
15 changes: 15 additions & 0 deletions TaskAssigner/AssignmentDescription.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Collections.Generic;

namespace TaskAssigner
{
public class AssignmentDescription
{
// Nodes[0] means UNASSIGNED
public IList<NodeDescription> Nodes { get; } = new List<NodeDescription>();

public IList<TaskDescription> Tasks { get; } = new List<TaskDescription>();

public IDictionary<int /* nodeIndex */, ISet<int /* taskIndex */>> Assignments { get; } =
new Dictionary<int, ISet<int>>();
}
}
11 changes: 11 additions & 0 deletions TaskAssigner/NodeDescription.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace TaskAssigner
{
public class NodeDescription
{
public int Id { get; set; }

public int CpuCores { get; set; }

public int MemoryMiB { get; set; }
}
}
63 changes: 63 additions & 0 deletions TaskAssigner/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Google.OrTools.LinearSolver;
using System.Collections.Generic;

namespace TaskAssigner
{
class Program
{
static void Main()
{
using var solver = Solver.CreateSolver(
"SimpleMipProgram",
"CBC_MIXED_INTEGER_PROGRAMMING");

var assigner = new Assigner(solver);

var assignment = new AssignmentDescription();
assignment.Nodes.Add(new NodeDescription
{
Id = 0,
CpuCores = int.MaxValue,
MemoryMiB = int.MaxValue,
});
for (int i = 1; i <= 3; i++)
{
assignment.Nodes.Add(new NodeDescription
{
Id = i,
CpuCores = 48,
MemoryMiB = 64 << 10, // 64 GiB
});
}
for (int i = 0; i < 13; i++)
{
assignment.Tasks.Add(new TaskDescription
{
Id = i,
CpuCores = 8,
MemoryMiB = 32 << 10, // 32 GiB, CPU:MEM=1:4
});
}
for (int i = 0; i < 17; i++)
{
assignment.Tasks.Add(new TaskDescription
{
Id = 13 + i,
CpuCores = 16,
MemoryMiB = 32 << 10, // 16 GiB, CPU:MEM=1:2
});
}
assignment.Assignments[0] = new HashSet<int>();
for (int i = 0; i < assignment.Tasks.Count; i++)
{
assignment.Assignments[0].Add(i);
}
for (int i = 1; i < assignment.Nodes.Count; i++)
{
assignment.Assignments[i] = new HashSet<int>();
}

assignment = assigner.Solve(assignment);
}
}
}
13 changes: 13 additions & 0 deletions TaskAssigner/TaskAssigner.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(NetfxCurrentTargetFramework)</TargetFramework>
<Platforms>x64</Platforms>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Google.OrTools" Version="7.6.7691" />
</ItemGroup>

</Project>
11 changes: 11 additions & 0 deletions TaskAssigner/TaskDescription.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace TaskAssigner
{
public class TaskDescription
{
public int Id { get; set; }

public int CpuCores { get; set; }

public int MemoryMiB { get; set; }
}
}

0 comments on commit b019f9b

Please sign in to comment.