Format all .Net using Microsoft style
This commit is contained in:
@@ -22,164 +22,188 @@ using Google.OrTools.Sat;
|
||||
/// to be as close to the average as possible. Furthermore, if one color is an a
|
||||
/// group, at least k items with this color must be in that group.
|
||||
/// </summary>
|
||||
public class BalanceGroupSat {
|
||||
static void Main(string[] args) {
|
||||
int numberGroups = 10;
|
||||
int numberItems = 100;
|
||||
int numberColors = 3;
|
||||
int minItemsOfSameColorPerGroup = 4;
|
||||
public class BalanceGroupSat
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
int numberGroups = 10;
|
||||
int numberItems = 100;
|
||||
int numberColors = 3;
|
||||
int minItemsOfSameColorPerGroup = 4;
|
||||
|
||||
var allGroups = Enumerable.Range(0, numberGroups).ToArray();
|
||||
var allItems = Enumerable.Range(0, numberItems).ToArray();
|
||||
var allColors = Enumerable.Range(0, numberColors).ToArray();
|
||||
var allGroups = Enumerable.Range(0, numberGroups).ToArray();
|
||||
var allItems = Enumerable.Range(0, numberItems).ToArray();
|
||||
var allColors = Enumerable.Range(0, numberColors).ToArray();
|
||||
|
||||
var values = allItems.Select(i => 1 + i + (i * i / 200)).ToArray();
|
||||
var colors = allItems.Select(i => i % numberColors).ToArray();
|
||||
var values = allItems.Select(i => 1 + i + (i * i / 200)).ToArray();
|
||||
var colors = allItems.Select(i => i % numberColors).ToArray();
|
||||
|
||||
var sumOfValues = values.Sum();
|
||||
var averageSumPerGroup = sumOfValues / numberGroups;
|
||||
var numItemsPerGroup = numberItems / numberGroups;
|
||||
var sumOfValues = values.Sum();
|
||||
var averageSumPerGroup = sumOfValues / numberGroups;
|
||||
var numItemsPerGroup = numberItems / numberGroups;
|
||||
|
||||
var itemsPerColor = new Dictionary<int, List<int>>();
|
||||
var itemsPerColor = new Dictionary<int, List<int>>();
|
||||
|
||||
foreach (var color in allColors) {
|
||||
itemsPerColor[color] = new List<int>();
|
||||
foreach (var item in allItems) {
|
||||
if (colors[item] == color)
|
||||
itemsPerColor[color].Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"Model has {numberItems}, {numberGroups} groups and {numberColors} colors");
|
||||
Console.WriteLine($" Average sum per group = {averageSumPerGroup}");
|
||||
|
||||
var model = new CpModel();
|
||||
|
||||
var itemInGroup = new IntVar[numberItems, numberGroups];
|
||||
foreach (var item in allItems) {
|
||||
foreach (var @group in allGroups) {
|
||||
itemInGroup[item, @group] = model.NewBoolVar($"item {item} in group {@group}");
|
||||
}
|
||||
}
|
||||
|
||||
// Each group must have the same size.
|
||||
foreach (var @group in allGroups) {
|
||||
var itemsInGroup = allItems.Select(x => itemInGroup[x, @group]).ToArray();
|
||||
model.AddLinearConstraint(LinearExpr.Sum(itemsInGroup), numItemsPerGroup, numItemsPerGroup);
|
||||
}
|
||||
|
||||
//# One item must belong to exactly one group.
|
||||
foreach (var item in allItems) {
|
||||
var groupsForItem = allGroups.Select(x => itemInGroup[item, x]).ToArray();
|
||||
model.Add(LinearExpr.Sum(groupsForItem) == 1);
|
||||
}
|
||||
|
||||
// The deviation of the sum of each items in a group against the average.
|
||||
var e = model.NewIntVar(0, 550, "epsilon");
|
||||
|
||||
// Constrain the sum of values in one group around the average sum per
|
||||
// group.
|
||||
foreach (var @group in allGroups) {
|
||||
var itemValues = allItems.Select(x => itemInGroup[x, @group]).ToArray();
|
||||
|
||||
var sum = LinearExpr.ScalProd(itemValues, values);
|
||||
model.Add(sum <= averageSumPerGroup + e);
|
||||
model.Add(sum >= averageSumPerGroup - e);
|
||||
}
|
||||
|
||||
// colorInGroup variables.
|
||||
var colorInGroup = new IntVar[numberColors, numberGroups];
|
||||
foreach (var @group in allGroups) {
|
||||
foreach (var color in allColors) {
|
||||
colorInGroup[color, @group] = model.NewBoolVar($"color {color} is in group {@group}");
|
||||
}
|
||||
}
|
||||
|
||||
// Item is in a group implies its color is in that group.
|
||||
foreach (var item in allItems) {
|
||||
foreach (var @group in allGroups) {
|
||||
model.AddImplication(itemInGroup[item, @group], colorInGroup[colors[item], @group]);
|
||||
}
|
||||
}
|
||||
|
||||
// If a color is in a group, it must contains at least
|
||||
// min_items_of_same_color_per_group items from that color.
|
||||
foreach (var color in allColors) {
|
||||
foreach (var @group in allGroups) {
|
||||
var literal = colorInGroup[color, @group];
|
||||
var items = itemsPerColor[color].Select(x => itemInGroup[x, @group]).ToArray();
|
||||
model.Add(LinearExpr.Sum(items) >= minItemsOfSameColorPerGroup).OnlyEnforceIf(literal);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the maximum number of colors in a group.
|
||||
int maxColor = numItemsPerGroup / minItemsOfSameColorPerGroup;
|
||||
// Redundant contraint: The problem does not solve in reasonable time
|
||||
// without it.
|
||||
if (maxColor < numberColors) {
|
||||
foreach (var @group in allGroups) {
|
||||
var all = allColors.Select(x => colorInGroup[x, @group]).ToArray();
|
||||
model.Add(LinearExpr.Sum(all) <= maxColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Minimize epsilon
|
||||
model.Minimize(e);
|
||||
|
||||
var solver = new CpSolver();
|
||||
solver.StringParameters = "";
|
||||
|
||||
var solutionPrinter = new SolutionPrinter(values, colors, allGroups, allItems, itemInGroup);
|
||||
|
||||
var status = solver.SolveWithSolutionCallback(model, solutionPrinter);
|
||||
}
|
||||
|
||||
public class SolutionPrinter : CpSolverSolutionCallback {
|
||||
private int[] _values;
|
||||
private int[] _colors;
|
||||
private int[] _allGroups;
|
||||
private int[] _allItems;
|
||||
private IntVar[,] _itemInGroup;
|
||||
|
||||
private int _solutionCount;
|
||||
|
||||
public SolutionPrinter(int[] values, int[] colors, int[] allGroups, int[] allItems,
|
||||
IntVar[,] itemInGroup) {
|
||||
this._values = values;
|
||||
this._colors = colors;
|
||||
this._allGroups = allGroups;
|
||||
this._allItems = allItems;
|
||||
this._itemInGroup = itemInGroup;
|
||||
}
|
||||
|
||||
public override void OnSolutionCallback() {
|
||||
Console.WriteLine($"Solution {_solutionCount}");
|
||||
_solutionCount++;
|
||||
|
||||
Console.WriteLine($" objective value = {this.ObjectiveValue()}");
|
||||
|
||||
Dictionary<int, List<int>> groups = new Dictionary<int, List<int>>();
|
||||
int[] sum = new int[_allGroups.Length];
|
||||
|
||||
foreach (var @group in _allGroups) {
|
||||
groups[@group] = new List<int>();
|
||||
foreach (var item in _allItems) {
|
||||
if (BooleanValue(_itemInGroup[item, @group])) {
|
||||
groups[@group].Add(item);
|
||||
sum[@group] += _values[item];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var g in _allGroups) {
|
||||
var group = groups[g];
|
||||
Console.Write($"Group {g}: sum = {sum[g]} [");
|
||||
foreach (var item in group) {
|
||||
Console.Write($"({item}, {_values[item]}, {_colors[item]})");
|
||||
foreach (var color in allColors)
|
||||
{
|
||||
itemsPerColor[color] = new List<int>();
|
||||
foreach (var item in allItems)
|
||||
{
|
||||
if (colors[item] == color)
|
||||
itemsPerColor[color].Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("]");
|
||||
}
|
||||
Console.WriteLine($"Model has {numberItems}, {numberGroups} groups and {numberColors} colors");
|
||||
Console.WriteLine($" Average sum per group = {averageSumPerGroup}");
|
||||
|
||||
var model = new CpModel();
|
||||
|
||||
var itemInGroup = new IntVar[numberItems, numberGroups];
|
||||
foreach (var item in allItems)
|
||||
{
|
||||
foreach (var @group in allGroups)
|
||||
{
|
||||
itemInGroup[item, @group] = model.NewBoolVar($"item {item} in group {@group}");
|
||||
}
|
||||
}
|
||||
|
||||
// Each group must have the same size.
|
||||
foreach (var @group in allGroups)
|
||||
{
|
||||
var itemsInGroup = allItems.Select(x => itemInGroup[x, @group]).ToArray();
|
||||
model.AddLinearConstraint(LinearExpr.Sum(itemsInGroup), numItemsPerGroup, numItemsPerGroup);
|
||||
}
|
||||
|
||||
//# One item must belong to exactly one group.
|
||||
foreach (var item in allItems)
|
||||
{
|
||||
var groupsForItem = allGroups.Select(x => itemInGroup[item, x]).ToArray();
|
||||
model.Add(LinearExpr.Sum(groupsForItem) == 1);
|
||||
}
|
||||
|
||||
// The deviation of the sum of each items in a group against the average.
|
||||
var e = model.NewIntVar(0, 550, "epsilon");
|
||||
|
||||
// Constrain the sum of values in one group around the average sum per
|
||||
// group.
|
||||
foreach (var @group in allGroups)
|
||||
{
|
||||
var itemValues = allItems.Select(x => itemInGroup[x, @group]).ToArray();
|
||||
|
||||
var sum = LinearExpr.ScalProd(itemValues, values);
|
||||
model.Add(sum <= averageSumPerGroup + e);
|
||||
model.Add(sum >= averageSumPerGroup - e);
|
||||
}
|
||||
|
||||
// colorInGroup variables.
|
||||
var colorInGroup = new IntVar[numberColors, numberGroups];
|
||||
foreach (var @group in allGroups)
|
||||
{
|
||||
foreach (var color in allColors)
|
||||
{
|
||||
colorInGroup[color, @group] = model.NewBoolVar($"color {color} is in group {@group}");
|
||||
}
|
||||
}
|
||||
|
||||
// Item is in a group implies its color is in that group.
|
||||
foreach (var item in allItems)
|
||||
{
|
||||
foreach (var @group in allGroups)
|
||||
{
|
||||
model.AddImplication(itemInGroup[item, @group], colorInGroup[colors[item], @group]);
|
||||
}
|
||||
}
|
||||
|
||||
// If a color is in a group, it must contains at least
|
||||
// min_items_of_same_color_per_group items from that color.
|
||||
foreach (var color in allColors)
|
||||
{
|
||||
foreach (var @group in allGroups)
|
||||
{
|
||||
var literal = colorInGroup[color, @group];
|
||||
var items = itemsPerColor[color].Select(x => itemInGroup[x, @group]).ToArray();
|
||||
model.Add(LinearExpr.Sum(items) >= minItemsOfSameColorPerGroup).OnlyEnforceIf(literal);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the maximum number of colors in a group.
|
||||
int maxColor = numItemsPerGroup / minItemsOfSameColorPerGroup;
|
||||
// Redundant contraint: The problem does not solve in reasonable time
|
||||
// without it.
|
||||
if (maxColor < numberColors)
|
||||
{
|
||||
foreach (var @group in allGroups)
|
||||
{
|
||||
var all = allColors.Select(x => colorInGroup[x, @group]).ToArray();
|
||||
model.Add(LinearExpr.Sum(all) <= maxColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Minimize epsilon
|
||||
model.Minimize(e);
|
||||
|
||||
var solver = new CpSolver();
|
||||
solver.StringParameters = "";
|
||||
|
||||
var solutionPrinter = new SolutionPrinter(values, colors, allGroups, allItems, itemInGroup);
|
||||
|
||||
var status = solver.SolveWithSolutionCallback(model, solutionPrinter);
|
||||
}
|
||||
|
||||
public class SolutionPrinter : CpSolverSolutionCallback
|
||||
{
|
||||
private int[] _values;
|
||||
private int[] _colors;
|
||||
private int[] _allGroups;
|
||||
private int[] _allItems;
|
||||
private IntVar[,] _itemInGroup;
|
||||
|
||||
private int _solutionCount;
|
||||
|
||||
public SolutionPrinter(int[] values, int[] colors, int[] allGroups, int[] allItems, IntVar[,] itemInGroup)
|
||||
{
|
||||
this._values = values;
|
||||
this._colors = colors;
|
||||
this._allGroups = allGroups;
|
||||
this._allItems = allItems;
|
||||
this._itemInGroup = itemInGroup;
|
||||
}
|
||||
|
||||
public override void OnSolutionCallback()
|
||||
{
|
||||
Console.WriteLine($"Solution {_solutionCount}");
|
||||
_solutionCount++;
|
||||
|
||||
Console.WriteLine($" objective value = {this.ObjectiveValue()}");
|
||||
|
||||
Dictionary<int, List<int>> groups = new Dictionary<int, List<int>>();
|
||||
int[] sum = new int[_allGroups.Length];
|
||||
|
||||
foreach (var @group in _allGroups)
|
||||
{
|
||||
groups[@group] = new List<int>();
|
||||
foreach (var item in _allItems)
|
||||
{
|
||||
if (BooleanValue(_itemInGroup[item, @group]))
|
||||
{
|
||||
groups[@group].Add(item);
|
||||
sum[@group] += _values[item];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var g in _allGroups)
|
||||
{
|
||||
var group = groups[g];
|
||||
Console.Write($"Group {g}: sum = {sum[g]} [");
|
||||
foreach (var item in group)
|
||||
{
|
||||
Console.Write($"({item}, {_values[item]}, {_colors[item]})");
|
||||
}
|
||||
|
||||
Console.WriteLine("]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,97 +26,99 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Google.OrTools.Sat;
|
||||
|
||||
public class GateSchedulingSat {
|
||||
static void Main() {
|
||||
CpModel model = new CpModel();
|
||||
public class GateSchedulingSat
|
||||
{
|
||||
static void Main()
|
||||
{
|
||||
CpModel model = new CpModel();
|
||||
|
||||
int[,] jobs =
|
||||
new[,] { { 3, 3 }, { 2, 5 }, { 1, 3 }, { 3, 7 }, { 7, 3 }, { 2, 2 }, { 2, 2 }, { 5, 5 },
|
||||
{ 10, 2 }, { 4, 3 }, { 2, 6 }, { 1, 2 }, { 6, 8 }, { 4, 5 }, { 3, 7 } };
|
||||
int[,] jobs = new[,] { { 3, 3 }, { 2, 5 }, { 1, 3 }, { 3, 7 }, { 7, 3 }, { 2, 2 }, { 2, 2 }, { 5, 5 },
|
||||
{ 10, 2 }, { 4, 3 }, { 2, 6 }, { 1, 2 }, { 6, 8 }, { 4, 5 }, { 3, 7 } };
|
||||
|
||||
int max_length = 10;
|
||||
int num_jobs = jobs.GetLength(0);
|
||||
var all_jobs = Enumerable.Range(0, num_jobs);
|
||||
int max_length = 10;
|
||||
int num_jobs = jobs.GetLength(0);
|
||||
var all_jobs = Enumerable.Range(0, num_jobs);
|
||||
|
||||
int horizon = 0;
|
||||
foreach (int j in all_jobs) {
|
||||
horizon += jobs[j, 0];
|
||||
int horizon = 0;
|
||||
foreach (int j in all_jobs)
|
||||
{
|
||||
horizon += jobs[j, 0];
|
||||
}
|
||||
|
||||
List<IntervalVar> intervals = new List<IntervalVar>();
|
||||
List<IntervalVar> intervals0 = new List<IntervalVar>();
|
||||
List<IntervalVar> intervals1 = new List<IntervalVar>();
|
||||
List<IntVar> performed = new List<IntVar>();
|
||||
List<IntVar> starts = new List<IntVar>();
|
||||
List<IntVar> ends = new List<IntVar>();
|
||||
List<int> demands = new List<int>();
|
||||
|
||||
foreach (int i in all_jobs)
|
||||
{
|
||||
// Create main interval.
|
||||
IntVar start = model.NewIntVar(0, horizon, String.Format("start_{0}", i));
|
||||
int duration = jobs[i, 0];
|
||||
IntVar end = model.NewIntVar(0, horizon, String.Format("end_{0}", i));
|
||||
IntervalVar interval = model.NewIntervalVar(start, duration, end, String.Format("interval_{0}", i));
|
||||
starts.Add(start);
|
||||
intervals.Add(interval);
|
||||
ends.Add(end);
|
||||
demands.Add(jobs[i, 1]);
|
||||
|
||||
IntVar performed_on_m0 = model.NewBoolVar(String.Format("perform_{0}_on_m0", i));
|
||||
performed.Add(performed_on_m0);
|
||||
|
||||
// Create an optional copy of interval to be executed on machine 0.
|
||||
IntVar start0 = model.NewIntVar(0, horizon, String.Format("start_{0}_on_m0", i));
|
||||
IntVar end0 = model.NewIntVar(0, horizon, String.Format("end_{0}_on_m0", i));
|
||||
IntervalVar interval0 = model.NewOptionalIntervalVar(start0, duration, end0, performed_on_m0,
|
||||
String.Format("interval_{0}_on_m0", i));
|
||||
intervals0.Add(interval0);
|
||||
|
||||
// Create an optional copy of interval to be executed on machine 1.
|
||||
IntVar start1 = model.NewIntVar(0, horizon, String.Format("start_{0}_on_m1", i));
|
||||
IntVar end1 = model.NewIntVar(0, horizon, String.Format("end_{0}_on_m1", i));
|
||||
IntervalVar interval1 = model.NewOptionalIntervalVar(start1, duration, end1, performed_on_m0.Not(),
|
||||
String.Format("interval_{0}_on_m1", i));
|
||||
intervals1.Add(interval1);
|
||||
|
||||
// We only propagate the constraint if the tasks is performed on the
|
||||
// machine.
|
||||
model.Add(start0 == start).OnlyEnforceIf(performed_on_m0);
|
||||
model.Add(start1 == start).OnlyEnforceIf(performed_on_m0.Not());
|
||||
}
|
||||
|
||||
// Max Length constraint (modeled as a cumulative)
|
||||
model.AddCumulative(intervals, demands, max_length);
|
||||
|
||||
// Choose which machine to perform the jobs on.
|
||||
model.AddNoOverlap(intervals0);
|
||||
model.AddNoOverlap(intervals1);
|
||||
|
||||
// Objective variable.
|
||||
IntVar makespan = model.NewIntVar(0, horizon, "makespan");
|
||||
model.AddMaxEquality(makespan, ends);
|
||||
model.Minimize(makespan);
|
||||
|
||||
// Symmetry breaking.
|
||||
model.Add(performed[0] == 0);
|
||||
|
||||
// Creates the solver and solve.
|
||||
CpSolver solver = new CpSolver();
|
||||
solver.Solve(model);
|
||||
|
||||
// Output solution.
|
||||
Console.WriteLine("Solution");
|
||||
Console.WriteLine(" - makespan = " + solver.ObjectiveValue);
|
||||
foreach (int i in all_jobs)
|
||||
{
|
||||
long performed_machine = 1 - solver.Value(performed[i]);
|
||||
long start = solver.Value(starts[i]);
|
||||
Console.WriteLine(String.Format(" - Job {0} starts at {1} on machine {2}", i, start, performed_machine));
|
||||
}
|
||||
Console.WriteLine("Statistics");
|
||||
Console.WriteLine(" - conflicts : " + solver.NumConflicts());
|
||||
Console.WriteLine(" - branches : " + solver.NumBranches());
|
||||
Console.WriteLine(" - wall time : " + solver.WallTime() + " ms");
|
||||
}
|
||||
|
||||
List<IntervalVar> intervals = new List<IntervalVar>();
|
||||
List<IntervalVar> intervals0 = new List<IntervalVar>();
|
||||
List<IntervalVar> intervals1 = new List<IntervalVar>();
|
||||
List<IntVar> performed = new List<IntVar>();
|
||||
List<IntVar> starts = new List<IntVar>();
|
||||
List<IntVar> ends = new List<IntVar>();
|
||||
List<int> demands = new List<int>();
|
||||
|
||||
foreach (int i in all_jobs) {
|
||||
// Create main interval.
|
||||
IntVar start = model.NewIntVar(0, horizon, String.Format("start_{0}", i));
|
||||
int duration = jobs[i, 0];
|
||||
IntVar end = model.NewIntVar(0, horizon, String.Format("end_{0}", i));
|
||||
IntervalVar interval =
|
||||
model.NewIntervalVar(start, duration, end, String.Format("interval_{0}", i));
|
||||
starts.Add(start);
|
||||
intervals.Add(interval);
|
||||
ends.Add(end);
|
||||
demands.Add(jobs[i, 1]);
|
||||
|
||||
IntVar performed_on_m0 = model.NewBoolVar(String.Format("perform_{0}_on_m0", i));
|
||||
performed.Add(performed_on_m0);
|
||||
|
||||
// Create an optional copy of interval to be executed on machine 0.
|
||||
IntVar start0 = model.NewIntVar(0, horizon, String.Format("start_{0}_on_m0", i));
|
||||
IntVar end0 = model.NewIntVar(0, horizon, String.Format("end_{0}_on_m0", i));
|
||||
IntervalVar interval0 = model.NewOptionalIntervalVar(start0, duration, end0, performed_on_m0,
|
||||
String.Format("interval_{0}_on_m0", i));
|
||||
intervals0.Add(interval0);
|
||||
|
||||
// Create an optional copy of interval to be executed on machine 1.
|
||||
IntVar start1 = model.NewIntVar(0, horizon, String.Format("start_{0}_on_m1", i));
|
||||
IntVar end1 = model.NewIntVar(0, horizon, String.Format("end_{0}_on_m1", i));
|
||||
IntervalVar interval1 = model.NewOptionalIntervalVar(
|
||||
start1, duration, end1, performed_on_m0.Not(), String.Format("interval_{0}_on_m1", i));
|
||||
intervals1.Add(interval1);
|
||||
|
||||
// We only propagate the constraint if the tasks is performed on the
|
||||
// machine.
|
||||
model.Add(start0 == start).OnlyEnforceIf(performed_on_m0);
|
||||
model.Add(start1 == start).OnlyEnforceIf(performed_on_m0.Not());
|
||||
}
|
||||
|
||||
// Max Length constraint (modeled as a cumulative)
|
||||
model.AddCumulative(intervals, demands, max_length);
|
||||
|
||||
// Choose which machine to perform the jobs on.
|
||||
model.AddNoOverlap(intervals0);
|
||||
model.AddNoOverlap(intervals1);
|
||||
|
||||
// Objective variable.
|
||||
IntVar makespan = model.NewIntVar(0, horizon, "makespan");
|
||||
model.AddMaxEquality(makespan, ends);
|
||||
model.Minimize(makespan);
|
||||
|
||||
// Symmetry breaking.
|
||||
model.Add(performed[0] == 0);
|
||||
|
||||
// Creates the solver and solve.
|
||||
CpSolver solver = new CpSolver();
|
||||
solver.Solve(model);
|
||||
|
||||
// Output solution.
|
||||
Console.WriteLine("Solution");
|
||||
Console.WriteLine(" - makespan = " + solver.ObjectiveValue);
|
||||
foreach (int i in all_jobs) {
|
||||
long performed_machine = 1 - solver.Value(performed[i]);
|
||||
long start = solver.Value(starts[i]);
|
||||
Console.WriteLine(
|
||||
String.Format(" - Job {0} starts at {1} on machine {2}", i, start, performed_machine));
|
||||
}
|
||||
Console.WriteLine("Statistics");
|
||||
Console.WriteLine(" - conflicts : " + solver.NumConflicts());
|
||||
Console.WriteLine(" - branches : " + solver.NumBranches());
|
||||
Console.WriteLine(" - wall time : " + solver.WallTime() + " ms");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,93 +16,106 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Google.OrTools.Sat;
|
||||
|
||||
public class JobshopFt06Sat {
|
||||
public struct Task {
|
||||
public Task(IntVar s, IntVar e, IntervalVar i) {
|
||||
start = s;
|
||||
end = e;
|
||||
interval = i;
|
||||
public class JobshopFt06Sat
|
||||
{
|
||||
public struct Task
|
||||
{
|
||||
public Task(IntVar s, IntVar e, IntervalVar i)
|
||||
{
|
||||
start = s;
|
||||
end = e;
|
||||
interval = i;
|
||||
}
|
||||
|
||||
public IntVar start;
|
||||
public IntVar end;
|
||||
public IntervalVar interval;
|
||||
}
|
||||
|
||||
public IntVar start;
|
||||
public IntVar end;
|
||||
public IntervalVar interval;
|
||||
}
|
||||
static void Main()
|
||||
{
|
||||
int[,] durations = new int[,] { { 1, 3, 6, 7, 3, 6 }, { 8, 5, 10, 10, 10, 4 }, { 5, 4, 8, 9, 1, 7 },
|
||||
{ 5, 5, 5, 3, 8, 9 }, { 9, 3, 5, 4, 3, 1 }, { 3, 3, 9, 10, 4, 1 } };
|
||||
int[,] machines = new int[,] { { 2, 0, 1, 3, 5, 4 }, { 1, 2, 4, 5, 0, 3 }, { 2, 3, 5, 0, 1, 4 },
|
||||
{ 1, 0, 2, 3, 4, 5 }, { 2, 1, 4, 5, 0, 3 }, { 1, 3, 5, 0, 4, 2 } };
|
||||
|
||||
static void Main() {
|
||||
int[,] durations =
|
||||
new int[,] { { 1, 3, 6, 7, 3, 6 }, { 8, 5, 10, 10, 10, 4 }, { 5, 4, 8, 9, 1, 7 },
|
||||
{ 5, 5, 5, 3, 8, 9 }, { 9, 3, 5, 4, 3, 1 }, { 3, 3, 9, 10, 4, 1 } };
|
||||
int[,] machines =
|
||||
new int[,] { { 2, 0, 1, 3, 5, 4 }, { 1, 2, 4, 5, 0, 3 }, { 2, 3, 5, 0, 1, 4 },
|
||||
{ 1, 0, 2, 3, 4, 5 }, { 2, 1, 4, 5, 0, 3 }, { 1, 3, 5, 0, 4, 2 } };
|
||||
int num_jobs = durations.GetLength(0);
|
||||
int num_machines = durations.GetLength(1);
|
||||
var all_jobs = Enumerable.Range(0, num_jobs);
|
||||
var all_machines = Enumerable.Range(0, num_machines);
|
||||
|
||||
int num_jobs = durations.GetLength(0);
|
||||
int num_machines = durations.GetLength(1);
|
||||
var all_jobs = Enumerable.Range(0, num_jobs);
|
||||
var all_machines = Enumerable.Range(0, num_machines);
|
||||
int horizon = 0;
|
||||
foreach (int j in all_jobs)
|
||||
{
|
||||
foreach (int m in all_machines)
|
||||
{
|
||||
horizon += durations[j, m];
|
||||
}
|
||||
}
|
||||
|
||||
int horizon = 0;
|
||||
foreach (int j in all_jobs) {
|
||||
foreach (int m in all_machines) {
|
||||
horizon += durations[j, m];
|
||||
}
|
||||
// Creates the model.
|
||||
CpModel model = new CpModel();
|
||||
|
||||
// Creates jobs.
|
||||
Task[,] all_tasks = new Task[num_jobs, num_machines];
|
||||
foreach (int j in all_jobs)
|
||||
{
|
||||
foreach (int m in all_machines)
|
||||
{
|
||||
IntVar start_var = model.NewIntVar(0, horizon, String.Format("start_{0}_{1}", j, m));
|
||||
int duration = durations[j, m];
|
||||
IntVar end_var = model.NewIntVar(0, horizon, String.Format("end_{0}_{1}", j, m));
|
||||
IntervalVar interval_var =
|
||||
model.NewIntervalVar(start_var, duration, end_var, String.Format("interval_{0}_{1}", j, m));
|
||||
all_tasks[j, m] = new Task(start_var, end_var, interval_var);
|
||||
}
|
||||
}
|
||||
|
||||
// Create disjuctive constraints.
|
||||
List<IntervalVar>[] machine_to_jobs = new List<IntervalVar>[num_machines];
|
||||
foreach (int m in all_machines)
|
||||
{
|
||||
machine_to_jobs[m] = new List<IntervalVar>();
|
||||
}
|
||||
foreach (int j in all_jobs)
|
||||
{
|
||||
foreach (int m in all_machines)
|
||||
{
|
||||
machine_to_jobs[machines[j, m]].Add(all_tasks[j, m].interval);
|
||||
}
|
||||
}
|
||||
foreach (int m in all_machines)
|
||||
{
|
||||
model.AddNoOverlap(machine_to_jobs[m]);
|
||||
}
|
||||
|
||||
// Precedences inside a job.
|
||||
foreach (int j in all_jobs)
|
||||
{
|
||||
for (int k = 0; k < num_machines - 1; ++k)
|
||||
{
|
||||
model.Add(all_tasks[j, k + 1].start >= all_tasks[j, k].end);
|
||||
}
|
||||
}
|
||||
|
||||
// Makespan objective.
|
||||
IntVar[] all_ends = new IntVar[num_jobs];
|
||||
foreach (int j in all_jobs)
|
||||
{
|
||||
all_ends[j] = all_tasks[j, num_machines - 1].end;
|
||||
}
|
||||
IntVar makespan = model.NewIntVar(0, horizon, "makespan");
|
||||
model.AddMaxEquality(makespan, all_ends);
|
||||
model.Minimize(makespan);
|
||||
|
||||
Console.WriteLine(model.ModelStats());
|
||||
|
||||
// Creates the solver and solve.
|
||||
CpSolver solver = new CpSolver();
|
||||
// Display a few solutions picked at random.
|
||||
solver.Solve(model);
|
||||
|
||||
// Statistics.
|
||||
Console.WriteLine(solver.ResponseStats());
|
||||
}
|
||||
|
||||
// Creates the model.
|
||||
CpModel model = new CpModel();
|
||||
|
||||
// Creates jobs.
|
||||
Task[,] all_tasks = new Task[num_jobs, num_machines];
|
||||
foreach (int j in all_jobs) {
|
||||
foreach (int m in all_machines) {
|
||||
IntVar start_var = model.NewIntVar(0, horizon, String.Format("start_{0}_{1}", j, m));
|
||||
int duration = durations[j, m];
|
||||
IntVar end_var = model.NewIntVar(0, horizon, String.Format("end_{0}_{1}", j, m));
|
||||
IntervalVar interval_var = model.NewIntervalVar(start_var, duration, end_var,
|
||||
String.Format("interval_{0}_{1}", j, m));
|
||||
all_tasks[j, m] = new Task(start_var, end_var, interval_var);
|
||||
}
|
||||
}
|
||||
|
||||
// Create disjuctive constraints.
|
||||
List<IntervalVar>[] machine_to_jobs = new List<IntervalVar>[num_machines];
|
||||
foreach (int m in all_machines) {
|
||||
machine_to_jobs[m] = new List<IntervalVar>();
|
||||
}
|
||||
foreach (int j in all_jobs) {
|
||||
foreach (int m in all_machines) {
|
||||
machine_to_jobs[machines[j, m]].Add(all_tasks[j, m].interval);
|
||||
}
|
||||
}
|
||||
foreach (int m in all_machines) {
|
||||
model.AddNoOverlap(machine_to_jobs[m]);
|
||||
}
|
||||
|
||||
// Precedences inside a job.
|
||||
foreach (int j in all_jobs) {
|
||||
for (int k = 0; k < num_machines - 1; ++k) {
|
||||
model.Add(all_tasks[j, k + 1].start >= all_tasks[j, k].end);
|
||||
}
|
||||
}
|
||||
|
||||
// Makespan objective.
|
||||
IntVar[] all_ends = new IntVar[num_jobs];
|
||||
foreach (int j in all_jobs) {
|
||||
all_ends[j] = all_tasks[j, num_machines - 1].end;
|
||||
}
|
||||
IntVar makespan = model.NewIntVar(0, horizon, "makespan");
|
||||
model.AddMaxEquality(makespan, all_ends);
|
||||
model.Minimize(makespan);
|
||||
|
||||
Console.WriteLine(model.ModelStats());
|
||||
|
||||
// Creates the solver and solve.
|
||||
CpSolver solver = new CpSolver();
|
||||
// Display a few solutions picked at random.
|
||||
solver.Solve(model);
|
||||
|
||||
// Statistics.
|
||||
Console.WriteLine(solver.ResponseStats());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,176 +3,195 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Google.OrTools.Sat;
|
||||
|
||||
class Task {
|
||||
public Task(int taskId, int jobId, int duration, int machine) {
|
||||
TaskId = taskId;
|
||||
JobId = jobId;
|
||||
Duration = duration;
|
||||
Machine = machine;
|
||||
Name = "T" + taskId + "J" + jobId + "M" + machine + "D" + duration;
|
||||
}
|
||||
public int TaskId { get; set; }
|
||||
public int JobId { get; set; }
|
||||
public int Machine { get; set; }
|
||||
public int Duration { get; set; }
|
||||
public string Name { get; }
|
||||
class Task
|
||||
{
|
||||
public Task(int taskId, int jobId, int duration, int machine)
|
||||
{
|
||||
TaskId = taskId;
|
||||
JobId = jobId;
|
||||
Duration = duration;
|
||||
Machine = machine;
|
||||
Name = "T" + taskId + "J" + jobId + "M" + machine + "D" + duration;
|
||||
}
|
||||
public int TaskId { get; set; }
|
||||
public int JobId { get; set; }
|
||||
public int Machine { get; set; }
|
||||
public int Duration { get; set; }
|
||||
public string Name { get; }
|
||||
}
|
||||
|
||||
class JobshopSat {
|
||||
// Number of machines.
|
||||
public const int machinesCount = 3;
|
||||
// horizon is the upper bound of the start time of all tasks.
|
||||
public const int horizon = 300;
|
||||
// this will be set to the size of myJobList variable.
|
||||
public static int jobsCount;
|
||||
/*Search time limit in milliseconds. if it's equal to 0,
|
||||
then no time limit will be used.*/
|
||||
public const int timeLimitInSeconds = 0;
|
||||
public static List<List<Task>> myJobList = new List<List<Task>>();
|
||||
public static void InitTaskList() {
|
||||
List<Task> taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 0, 65, 0));
|
||||
taskList.Add(new Task(1, 0, 5, 1));
|
||||
taskList.Add(new Task(2, 0, 15, 2));
|
||||
myJobList.Add(taskList);
|
||||
class JobshopSat
|
||||
{
|
||||
// Number of machines.
|
||||
public const int machinesCount = 3;
|
||||
// horizon is the upper bound of the start time of all tasks.
|
||||
public const int horizon = 300;
|
||||
// this will be set to the size of myJobList variable.
|
||||
public static int jobsCount;
|
||||
/*Search time limit in milliseconds. if it's equal to 0,
|
||||
then no time limit will be used.*/
|
||||
public const int timeLimitInSeconds = 0;
|
||||
public static List<List<Task>> myJobList = new List<List<Task>>();
|
||||
public static void InitTaskList()
|
||||
{
|
||||
List<Task> taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 0, 65, 0));
|
||||
taskList.Add(new Task(1, 0, 5, 1));
|
||||
taskList.Add(new Task(2, 0, 15, 2));
|
||||
myJobList.Add(taskList);
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 1, 15, 0));
|
||||
taskList.Add(new Task(1, 1, 25, 1));
|
||||
taskList.Add(new Task(2, 1, 10, 2));
|
||||
myJobList.Add(taskList);
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 1, 15, 0));
|
||||
taskList.Add(new Task(1, 1, 25, 1));
|
||||
taskList.Add(new Task(2, 1, 10, 2));
|
||||
myJobList.Add(taskList);
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 2, 25, 0));
|
||||
taskList.Add(new Task(1, 2, 30, 1));
|
||||
taskList.Add(new Task(2, 2, 40, 2));
|
||||
myJobList.Add(taskList);
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 2, 25, 0));
|
||||
taskList.Add(new Task(1, 2, 30, 1));
|
||||
taskList.Add(new Task(2, 2, 40, 2));
|
||||
myJobList.Add(taskList);
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 3, 20, 0));
|
||||
taskList.Add(new Task(1, 3, 35, 1));
|
||||
taskList.Add(new Task(2, 3, 10, 2));
|
||||
myJobList.Add(taskList);
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 3, 20, 0));
|
||||
taskList.Add(new Task(1, 3, 35, 1));
|
||||
taskList.Add(new Task(2, 3, 10, 2));
|
||||
myJobList.Add(taskList);
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 4, 15, 0));
|
||||
taskList.Add(new Task(1, 4, 25, 1));
|
||||
taskList.Add(new Task(2, 4, 10, 2));
|
||||
myJobList.Add(taskList);
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 4, 15, 0));
|
||||
taskList.Add(new Task(1, 4, 25, 1));
|
||||
taskList.Add(new Task(2, 4, 10, 2));
|
||||
myJobList.Add(taskList);
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 5, 25, 0));
|
||||
taskList.Add(new Task(1, 5, 30, 1));
|
||||
taskList.Add(new Task(2, 5, 40, 2));
|
||||
myJobList.Add(taskList);
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 5, 25, 0));
|
||||
taskList.Add(new Task(1, 5, 30, 1));
|
||||
taskList.Add(new Task(2, 5, 40, 2));
|
||||
myJobList.Add(taskList);
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 6, 20, 0));
|
||||
taskList.Add(new Task(1, 6, 35, 1));
|
||||
taskList.Add(new Task(2, 6, 10, 2));
|
||||
myJobList.Add(taskList);
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 6, 20, 0));
|
||||
taskList.Add(new Task(1, 6, 35, 1));
|
||||
taskList.Add(new Task(2, 6, 10, 2));
|
||||
myJobList.Add(taskList);
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 7, 10, 0));
|
||||
taskList.Add(new Task(1, 7, 15, 1));
|
||||
taskList.Add(new Task(2, 7, 50, 2));
|
||||
myJobList.Add(taskList);
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 7, 10, 0));
|
||||
taskList.Add(new Task(1, 7, 15, 1));
|
||||
taskList.Add(new Task(2, 7, 50, 2));
|
||||
myJobList.Add(taskList);
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 8, 50, 0));
|
||||
taskList.Add(new Task(1, 8, 10, 1));
|
||||
taskList.Add(new Task(2, 8, 20, 2));
|
||||
myJobList.Add(taskList);
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task(0, 8, 50, 0));
|
||||
taskList.Add(new Task(1, 8, 10, 1));
|
||||
taskList.Add(new Task(2, 8, 20, 2));
|
||||
myJobList.Add(taskList);
|
||||
|
||||
jobsCount = myJobList.Count;
|
||||
}
|
||||
|
||||
public static void Main(String[] args) {
|
||||
InitTaskList();
|
||||
CpModel model = new CpModel();
|
||||
|
||||
// ----- Creates all intervals and integer variables -----
|
||||
|
||||
// Stores all tasks attached interval variables per job.
|
||||
List<List<IntervalVar>> jobsToTasks = new List<List<IntervalVar>>(jobsCount);
|
||||
List<List<IntVar>> jobsToStarts = new List<List<IntVar>>(jobsCount);
|
||||
List<List<IntVar>> jobsToEnds = new List<List<IntVar>>(jobsCount);
|
||||
|
||||
// machinesToTasks stores the same interval variables as above, but
|
||||
// grouped my machines instead of grouped by jobs.
|
||||
List<List<IntervalVar>> machinesToTasks = new List<List<IntervalVar>>(machinesCount);
|
||||
List<List<IntVar>> machinesToStarts = new List<List<IntVar>>(machinesCount);
|
||||
for (int i = 0; i < machinesCount; i++) {
|
||||
machinesToTasks.Add(new List<IntervalVar>());
|
||||
machinesToStarts.Add(new List<IntVar>());
|
||||
jobsCount = myJobList.Count;
|
||||
}
|
||||
|
||||
// Creates all individual interval variables.
|
||||
foreach (List<Task> job in myJobList) {
|
||||
jobsToTasks.Add(new List<IntervalVar>());
|
||||
jobsToStarts.Add(new List<IntVar>());
|
||||
jobsToEnds.Add(new List<IntVar>());
|
||||
foreach (Task task in job) {
|
||||
IntVar start = model.NewIntVar(0, horizon, task.Name);
|
||||
IntVar end = model.NewIntVar(0, horizon, task.Name);
|
||||
IntervalVar oneTask = model.NewIntervalVar(start, task.Duration, end, task.Name);
|
||||
jobsToTasks[task.JobId].Add(oneTask);
|
||||
jobsToStarts[task.JobId].Add(start);
|
||||
jobsToEnds[task.JobId].Add(end);
|
||||
machinesToTasks[task.Machine].Add(oneTask);
|
||||
machinesToStarts[task.Machine].Add(start);
|
||||
}
|
||||
}
|
||||
public static void Main(String[] args)
|
||||
{
|
||||
InitTaskList();
|
||||
CpModel model = new CpModel();
|
||||
|
||||
// ----- Creates model -----
|
||||
// ----- Creates all intervals and integer variables -----
|
||||
|
||||
// Creates precedences inside jobs.
|
||||
for (int j = 0; j < jobsToTasks.Count; ++j) {
|
||||
for (int t = 0; t < jobsToTasks[j].Count - 1; ++t) {
|
||||
model.Add(jobsToEnds[j][t] <= jobsToStarts[j][t + 1]);
|
||||
}
|
||||
}
|
||||
// Stores all tasks attached interval variables per job.
|
||||
List<List<IntervalVar>> jobsToTasks = new List<List<IntervalVar>>(jobsCount);
|
||||
List<List<IntVar>> jobsToStarts = new List<List<IntVar>>(jobsCount);
|
||||
List<List<IntVar>> jobsToEnds = new List<List<IntVar>>(jobsCount);
|
||||
|
||||
// Adds no_overkap constraints on unary resources.
|
||||
for (int machineId = 0; machineId < machinesCount; ++machineId) {
|
||||
model.AddNoOverlap(machinesToTasks[machineId]);
|
||||
}
|
||||
|
||||
// Creates array of end_times of jobs.
|
||||
IntVar[] allEnds = new IntVar[jobsCount];
|
||||
for (int i = 0; i < jobsCount; i++) {
|
||||
allEnds[i] = jobsToEnds[i].Last();
|
||||
}
|
||||
|
||||
// Objective: minimize the makespan (maximum end times of all tasks)
|
||||
// of the problem.
|
||||
IntVar makespan = model.NewIntVar(0, horizon, "makespan");
|
||||
model.AddMaxEquality(makespan, allEnds);
|
||||
model.Minimize(makespan);
|
||||
|
||||
// Create the solver.
|
||||
CpSolver solver = new CpSolver();
|
||||
// Set the time limit.
|
||||
if (timeLimitInSeconds > 0) {
|
||||
solver.StringParameters = "max_time_in_seconds:" + timeLimitInSeconds;
|
||||
}
|
||||
// Solve the problem.
|
||||
CpSolverStatus status = solver.Solve(model);
|
||||
|
||||
if (status == CpSolverStatus.Optimal) {
|
||||
Console.WriteLine("Makespan = " + solver.ObjectiveValue);
|
||||
for (int m = 0; m < machinesCount; ++m) {
|
||||
Console.WriteLine($"Machine {m}:");
|
||||
SortedDictionary<long, string> starts = new SortedDictionary<long, string>();
|
||||
foreach (IntVar var in machinesToStarts[m]) {
|
||||
starts[solver.Value(var)] = var.Name();
|
||||
// machinesToTasks stores the same interval variables as above, but
|
||||
// grouped my machines instead of grouped by jobs.
|
||||
List<List<IntervalVar>> machinesToTasks = new List<List<IntervalVar>>(machinesCount);
|
||||
List<List<IntVar>> machinesToStarts = new List<List<IntVar>>(machinesCount);
|
||||
for (int i = 0; i < machinesCount; i++)
|
||||
{
|
||||
machinesToTasks.Add(new List<IntervalVar>());
|
||||
machinesToStarts.Add(new List<IntVar>());
|
||||
}
|
||||
foreach (KeyValuePair<long, string> p in starts) {
|
||||
Console.WriteLine($" Task {p.Value} starts at {p.Key}");
|
||||
|
||||
// Creates all individual interval variables.
|
||||
foreach (List<Task> job in myJobList)
|
||||
{
|
||||
jobsToTasks.Add(new List<IntervalVar>());
|
||||
jobsToStarts.Add(new List<IntVar>());
|
||||
jobsToEnds.Add(new List<IntVar>());
|
||||
foreach (Task task in job)
|
||||
{
|
||||
IntVar start = model.NewIntVar(0, horizon, task.Name);
|
||||
IntVar end = model.NewIntVar(0, horizon, task.Name);
|
||||
IntervalVar oneTask = model.NewIntervalVar(start, task.Duration, end, task.Name);
|
||||
jobsToTasks[task.JobId].Add(oneTask);
|
||||
jobsToStarts[task.JobId].Add(start);
|
||||
jobsToEnds[task.JobId].Add(end);
|
||||
machinesToTasks[task.Machine].Add(oneTask);
|
||||
machinesToStarts[task.Machine].Add(start);
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Creates model -----
|
||||
|
||||
// Creates precedences inside jobs.
|
||||
for (int j = 0; j < jobsToTasks.Count; ++j)
|
||||
{
|
||||
for (int t = 0; t < jobsToTasks[j].Count - 1; ++t)
|
||||
{
|
||||
model.Add(jobsToEnds[j][t] <= jobsToStarts[j][t + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds no_overkap constraints on unary resources.
|
||||
for (int machineId = 0; machineId < machinesCount; ++machineId)
|
||||
{
|
||||
model.AddNoOverlap(machinesToTasks[machineId]);
|
||||
}
|
||||
|
||||
// Creates array of end_times of jobs.
|
||||
IntVar[] allEnds = new IntVar[jobsCount];
|
||||
for (int i = 0; i < jobsCount; i++)
|
||||
{
|
||||
allEnds[i] = jobsToEnds[i].Last();
|
||||
}
|
||||
|
||||
// Objective: minimize the makespan (maximum end times of all tasks)
|
||||
// of the problem.
|
||||
IntVar makespan = model.NewIntVar(0, horizon, "makespan");
|
||||
model.AddMaxEquality(makespan, allEnds);
|
||||
model.Minimize(makespan);
|
||||
|
||||
// Create the solver.
|
||||
CpSolver solver = new CpSolver();
|
||||
// Set the time limit.
|
||||
if (timeLimitInSeconds > 0)
|
||||
{
|
||||
solver.StringParameters = "max_time_in_seconds:" + timeLimitInSeconds;
|
||||
}
|
||||
// Solve the problem.
|
||||
CpSolverStatus status = solver.Solve(model);
|
||||
|
||||
if (status == CpSolverStatus.Optimal)
|
||||
{
|
||||
Console.WriteLine("Makespan = " + solver.ObjectiveValue);
|
||||
for (int m = 0; m < machinesCount; ++m)
|
||||
{
|
||||
Console.WriteLine($"Machine {m}:");
|
||||
SortedDictionary<long, string> starts = new SortedDictionary<long, string>();
|
||||
foreach (IntVar var in machinesToStarts[m])
|
||||
{
|
||||
starts[solver.Value(var)] = var.Name();
|
||||
}
|
||||
foreach (KeyValuePair<long, string> p in starts)
|
||||
{
|
||||
Console.WriteLine($" Task {p.Value} starts at {p.Key}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("No solution found!");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Console.WriteLine("No solution found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,166 +16,193 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Google.OrTools.Sat;
|
||||
|
||||
public class NurseSolutionObserver : CpSolverSolutionCallback {
|
||||
public NurseSolutionObserver(IntVar[,,] shifts, int num_nurses, int num_days, int num_shifts,
|
||||
HashSet<int> to_print) {
|
||||
shifts_ = shifts;
|
||||
num_nurses_ = num_nurses;
|
||||
num_days_ = num_days;
|
||||
num_shifts_ = num_shifts;
|
||||
to_print_ = to_print;
|
||||
}
|
||||
public class NurseSolutionObserver : CpSolverSolutionCallback
|
||||
{
|
||||
public NurseSolutionObserver(IntVar[,,] shifts, int num_nurses, int num_days, int num_shifts, HashSet<int> to_print)
|
||||
{
|
||||
shifts_ = shifts;
|
||||
num_nurses_ = num_nurses;
|
||||
num_days_ = num_days;
|
||||
num_shifts_ = num_shifts;
|
||||
to_print_ = to_print;
|
||||
}
|
||||
|
||||
public override void OnSolutionCallback() {
|
||||
solution_count_++;
|
||||
if (to_print_.Contains(solution_count_)) {
|
||||
Console.WriteLine(
|
||||
String.Format("Solution #{0}: time = {1:.02} s", solution_count_, WallTime()));
|
||||
for (int d = 0; d < num_days_; ++d) {
|
||||
Console.WriteLine(String.Format("Day #{0}", d));
|
||||
for (int n = 0; n < num_nurses_; ++n) {
|
||||
for (int s = 0; s < num_shifts_; ++s) {
|
||||
if (BooleanValue(shifts_[n, d, s])) {
|
||||
Console.WriteLine(String.Format(" Nurse #{0} is working shift #{1}", n, s));
|
||||
public override void OnSolutionCallback()
|
||||
{
|
||||
solution_count_++;
|
||||
if (to_print_.Contains(solution_count_))
|
||||
{
|
||||
Console.WriteLine(String.Format("Solution #{0}: time = {1:.02} s", solution_count_, WallTime()));
|
||||
for (int d = 0; d < num_days_; ++d)
|
||||
{
|
||||
Console.WriteLine(String.Format("Day #{0}", d));
|
||||
for (int n = 0; n < num_nurses_; ++n)
|
||||
{
|
||||
for (int s = 0; s < num_shifts_; ++s)
|
||||
{
|
||||
if (BooleanValue(shifts_[n, d, s]))
|
||||
{
|
||||
Console.WriteLine(String.Format(" Nurse #{0} is working shift #{1}", n, s));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int SolutionCount() {
|
||||
return solution_count_;
|
||||
}
|
||||
public int SolutionCount()
|
||||
{
|
||||
return solution_count_;
|
||||
}
|
||||
|
||||
private int solution_count_;
|
||||
private IntVar[,,] shifts_;
|
||||
private int num_nurses_;
|
||||
private int num_days_;
|
||||
private int num_shifts_;
|
||||
private HashSet<int> to_print_;
|
||||
private int solution_count_;
|
||||
private IntVar[,,] shifts_;
|
||||
private int num_nurses_;
|
||||
private int num_days_;
|
||||
private int num_shifts_;
|
||||
private HashSet<int> to_print_;
|
||||
}
|
||||
|
||||
public class NursesSat {
|
||||
static void Main() {
|
||||
// Data.
|
||||
int num_nurses = 4;
|
||||
// Nurse assigned to shift 0 means not working that day.
|
||||
int num_shifts = 4;
|
||||
int num_days = 7;
|
||||
public class NursesSat
|
||||
{
|
||||
static void Main()
|
||||
{
|
||||
// Data.
|
||||
int num_nurses = 4;
|
||||
// Nurse assigned to shift 0 means not working that day.
|
||||
int num_shifts = 4;
|
||||
int num_days = 7;
|
||||
|
||||
var all_nurses = Enumerable.Range(0, num_nurses);
|
||||
var all_shifts = Enumerable.Range(0, num_shifts);
|
||||
var all_working_shifts = Enumerable.Range(1, num_shifts - 1);
|
||||
var all_days = Enumerable.Range(0, num_days);
|
||||
var all_nurses = Enumerable.Range(0, num_nurses);
|
||||
var all_shifts = Enumerable.Range(0, num_shifts);
|
||||
var all_working_shifts = Enumerable.Range(1, num_shifts - 1);
|
||||
var all_days = Enumerable.Range(0, num_days);
|
||||
|
||||
// Creates the model.
|
||||
CpModel model = new CpModel();
|
||||
// Creates the model.
|
||||
CpModel model = new CpModel();
|
||||
|
||||
// Creates shift variables.
|
||||
// shift[n, d, s]: nurse "n" works shift "s" on day "d".
|
||||
IntVar[,,] shift = new IntVar[num_nurses, num_days, num_shifts];
|
||||
foreach (int n in all_nurses) {
|
||||
foreach (int d in all_days) {
|
||||
foreach (int s in all_shifts) {
|
||||
shift[n, d, s] = model.NewBoolVar(String.Format("shift_n{0}d{1}s{2}", n, d, s));
|
||||
// Creates shift variables.
|
||||
// shift[n, d, s]: nurse "n" works shift "s" on day "d".
|
||||
IntVar[,,] shift = new IntVar[num_nurses, num_days, num_shifts];
|
||||
foreach (int n in all_nurses)
|
||||
{
|
||||
foreach (int d in all_days)
|
||||
{
|
||||
foreach (int s in all_shifts)
|
||||
{
|
||||
shift[n, d, s] = model.NewBoolVar(String.Format("shift_n{0}d{1}s{2}", n, d, s));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Makes assignments different on each day, that is each shift is
|
||||
// assigned at most one nurse. As we have the same number of
|
||||
// nurses and shifts, then each day, each shift is assigned to
|
||||
// exactly one nurse.
|
||||
foreach (int d in all_days) {
|
||||
foreach (int s in all_shifts) {
|
||||
IntVar[] tmp = new IntVar[num_nurses];
|
||||
foreach (int n in all_nurses) {
|
||||
tmp[n] = shift[n, d, s];
|
||||
// Makes assignments different on each day, that is each shift is
|
||||
// assigned at most one nurse. As we have the same number of
|
||||
// nurses and shifts, then each day, each shift is assigned to
|
||||
// exactly one nurse.
|
||||
foreach (int d in all_days)
|
||||
{
|
||||
foreach (int s in all_shifts)
|
||||
{
|
||||
IntVar[] tmp = new IntVar[num_nurses];
|
||||
foreach (int n in all_nurses)
|
||||
{
|
||||
tmp[n] = shift[n, d, s];
|
||||
}
|
||||
model.Add(LinearExpr.Sum(tmp) == 1);
|
||||
}
|
||||
}
|
||||
model.Add(LinearExpr.Sum(tmp) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Nurses do 1 shift per day.
|
||||
foreach (int n in all_nurses) {
|
||||
foreach (int d in all_days) {
|
||||
IntVar[] tmp = new IntVar[num_shifts];
|
||||
foreach (int s in all_shifts) {
|
||||
tmp[s] = shift[n, d, s];
|
||||
// Nurses do 1 shift per day.
|
||||
foreach (int n in all_nurses)
|
||||
{
|
||||
foreach (int d in all_days)
|
||||
{
|
||||
IntVar[] tmp = new IntVar[num_shifts];
|
||||
foreach (int s in all_shifts)
|
||||
{
|
||||
tmp[s] = shift[n, d, s];
|
||||
}
|
||||
model.Add(LinearExpr.Sum(tmp) == 1);
|
||||
}
|
||||
}
|
||||
model.Add(LinearExpr.Sum(tmp) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Each nurse works 5 or 6 days in a week.
|
||||
// That is each nurse works shift 0 at most 2 times.
|
||||
foreach (int n in all_nurses) {
|
||||
IntVar[] tmp = new IntVar[num_days];
|
||||
foreach (int d in all_days) {
|
||||
tmp[d] = shift[n, d, 0];
|
||||
}
|
||||
model.AddLinearConstraint(LinearExpr.Sum(tmp), 1, 2);
|
||||
}
|
||||
|
||||
// works_shift[(n, s)] is 1 if nurse n works shift s at least one day in
|
||||
// the week.
|
||||
IntVar[,] works_shift = new IntVar[num_nurses, num_shifts];
|
||||
foreach (int n in all_nurses) {
|
||||
foreach (int s in all_shifts) {
|
||||
works_shift[n, s] = model.NewBoolVar(String.Format("works_shift_n{0}s{1}", n, s));
|
||||
IntVar[] tmp = new IntVar[num_days];
|
||||
foreach (int d in all_days) {
|
||||
tmp[d] = shift[n, d, s];
|
||||
// Each nurse works 5 or 6 days in a week.
|
||||
// That is each nurse works shift 0 at most 2 times.
|
||||
foreach (int n in all_nurses)
|
||||
{
|
||||
IntVar[] tmp = new IntVar[num_days];
|
||||
foreach (int d in all_days)
|
||||
{
|
||||
tmp[d] = shift[n, d, 0];
|
||||
}
|
||||
model.AddLinearConstraint(LinearExpr.Sum(tmp), 1, 2);
|
||||
}
|
||||
model.AddMaxEquality(works_shift[n, s], tmp);
|
||||
}
|
||||
}
|
||||
|
||||
// For each working shift, at most 2 nurses are assigned to that shift
|
||||
// during the week.
|
||||
foreach (int s in all_working_shifts) {
|
||||
IntVar[] tmp = new IntVar[num_nurses];
|
||||
foreach (int n in all_nurses) {
|
||||
tmp[n] = works_shift[n, s];
|
||||
}
|
||||
model.Add(LinearExpr.Sum(tmp) <= 2);
|
||||
}
|
||||
|
||||
// If a nurse works shifts 2 or 3 on, she must also work that
|
||||
// shift the previous day or the following day. This means that
|
||||
// on a given day and shift, either she does not work that shift
|
||||
// on that day, or she works that shift on the day before, or the
|
||||
// day after.
|
||||
foreach (int n in all_nurses) {
|
||||
for (int s = 2; s <= 3; ++s) {
|
||||
foreach (int d in all_days) {
|
||||
int yesterday = d == 0 ? num_days - 1 : d - 1;
|
||||
int tomorrow = d == num_days - 1 ? 0 : d + 1;
|
||||
model.AddBoolOr(new ILiteral[] { shift[n, yesterday, s], shift[n, d, s].Not(),
|
||||
shift[n, tomorrow, s] });
|
||||
// works_shift[(n, s)] is 1 if nurse n works shift s at least one day in
|
||||
// the week.
|
||||
IntVar[,] works_shift = new IntVar[num_nurses, num_shifts];
|
||||
foreach (int n in all_nurses)
|
||||
{
|
||||
foreach (int s in all_shifts)
|
||||
{
|
||||
works_shift[n, s] = model.NewBoolVar(String.Format("works_shift_n{0}s{1}", n, s));
|
||||
IntVar[] tmp = new IntVar[num_days];
|
||||
foreach (int d in all_days)
|
||||
{
|
||||
tmp[d] = shift[n, d, s];
|
||||
}
|
||||
model.AddMaxEquality(works_shift[n, s], tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For each working shift, at most 2 nurses are assigned to that shift
|
||||
// during the week.
|
||||
foreach (int s in all_working_shifts)
|
||||
{
|
||||
IntVar[] tmp = new IntVar[num_nurses];
|
||||
foreach (int n in all_nurses)
|
||||
{
|
||||
tmp[n] = works_shift[n, s];
|
||||
}
|
||||
model.Add(LinearExpr.Sum(tmp) <= 2);
|
||||
}
|
||||
|
||||
// If a nurse works shifts 2 or 3 on, she must also work that
|
||||
// shift the previous day or the following day. This means that
|
||||
// on a given day and shift, either she does not work that shift
|
||||
// on that day, or she works that shift on the day before, or the
|
||||
// day after.
|
||||
foreach (int n in all_nurses)
|
||||
{
|
||||
for (int s = 2; s <= 3; ++s)
|
||||
{
|
||||
foreach (int d in all_days)
|
||||
{
|
||||
int yesterday = d == 0 ? num_days - 1 : d - 1;
|
||||
int tomorrow = d == num_days - 1 ? 0 : d + 1;
|
||||
model.AddBoolOr(
|
||||
new ILiteral[] { shift[n, yesterday, s], shift[n, d, s].Not(), shift[n, tomorrow, s] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates the solver and solve.
|
||||
CpSolver solver = new CpSolver();
|
||||
// Display a few solutions picked at random.
|
||||
HashSet<int> to_print = new HashSet<int>();
|
||||
to_print.Add(859);
|
||||
to_print.Add(2034);
|
||||
to_print.Add(5091);
|
||||
to_print.Add(7003);
|
||||
NurseSolutionObserver cb = new NurseSolutionObserver(shift, num_nurses, num_days, num_shifts, to_print);
|
||||
CpSolverStatus status = solver.SearchAllSolutions(model, cb);
|
||||
|
||||
// Statistics.
|
||||
Console.WriteLine("Statistics");
|
||||
Console.WriteLine(String.Format(" - solve status : {0}", status));
|
||||
Console.WriteLine(" - conflicts : " + solver.NumConflicts());
|
||||
Console.WriteLine(" - branches : " + solver.NumBranches());
|
||||
Console.WriteLine(" - wall time : " + solver.WallTime() + " ms");
|
||||
Console.WriteLine(" - #solutions : " + cb.SolutionCount());
|
||||
}
|
||||
|
||||
// Creates the solver and solve.
|
||||
CpSolver solver = new CpSolver();
|
||||
// Display a few solutions picked at random.
|
||||
HashSet<int> to_print = new HashSet<int>();
|
||||
to_print.Add(859);
|
||||
to_print.Add(2034);
|
||||
to_print.Add(5091);
|
||||
to_print.Add(7003);
|
||||
NurseSolutionObserver cb =
|
||||
new NurseSolutionObserver(shift, num_nurses, num_days, num_shifts, to_print);
|
||||
CpSolverStatus status = solver.SearchAllSolutions(model, cb);
|
||||
|
||||
// Statistics.
|
||||
Console.WriteLine("Statistics");
|
||||
Console.WriteLine(String.Format(" - solve status : {0}", status));
|
||||
Console.WriteLine(" - conflicts : " + solver.NumConflicts());
|
||||
Console.WriteLine(" - branches : " + solver.NumBranches());
|
||||
Console.WriteLine(" - wall time : " + solver.WallTime() + " ms");
|
||||
Console.WriteLine(" - #solutions : " + cb.SolutionCount());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,458 +19,517 @@ using Google.OrTools.Sat;
|
||||
/// <summary>
|
||||
/// Creates a shift scheduling problem and solves it
|
||||
/// </summary>
|
||||
public class ShiftSchedulingSat {
|
||||
static void Main(string[] args) {
|
||||
SolveShiftScheduling();
|
||||
}
|
||||
|
||||
static void SolveShiftScheduling() {
|
||||
int numEmployees = 8;
|
||||
int numWeeks = 3;
|
||||
var shifts = new[] { "O", "M", "A", "N" };
|
||||
|
||||
// Fixed assignment: (employee, shift, day).
|
||||
// This fixes the first 2 days of the schedule.
|
||||
var fixedAssignments = new(int Employee, int Shift, int Day)[] {
|
||||
(0, 0, 0), (1, 0, 0), (2, 1, 0), (3, 1, 0), (4, 2, 0), (5, 2, 0), (6, 2, 3), (7, 3, 0),
|
||||
(0, 1, 1), (1, 1, 1), (2, 2, 1), (3, 2, 1), (4, 2, 1), (5, 0, 1), (6, 0, 1), (7, 3, 1),
|
||||
};
|
||||
|
||||
// Request: (employee, shift, day, weight)
|
||||
// A negative weight indicates that the employee desire this assignment.
|
||||
var requests = new(int Employee, int Shift, int Day, int Weight)[] {
|
||||
// Employee 3 wants the first Saturday off.
|
||||
(3, 0, 5, -2),
|
||||
// Employee 5 wants a night shift on the second Thursday.
|
||||
(5, 3, 10, -2),
|
||||
// Employee 2 does not want a night shift on the third Friday.
|
||||
(2, 3, 4, 4)
|
||||
};
|
||||
|
||||
// Shift constraints on continuous sequence :
|
||||
// (shift, hard_min, soft_min, min_penalty,
|
||||
// soft_max, hard_max, max_penalty)
|
||||
var shiftConstraints = new(int Shift, int HardMin, int SoftMin, int MinPenalty, int SoftMax,
|
||||
int HardMax, int MaxPenalty)[] {
|
||||
// One or two consecutive days of rest, this is a hard constraint.
|
||||
(0, 1, 1, 0, 2, 2, 0),
|
||||
// Between 2 and 3 consecutive days of night shifts, 1 and 4 are
|
||||
// possible but penalized.
|
||||
(3, 1, 2, 20, 3, 4, 5),
|
||||
};
|
||||
|
||||
// Weekly sum constraints on shifts days:
|
||||
// (shift, hardMin, softMin, minPenalty,
|
||||
// softMax, hardMax, maxPenalty)
|
||||
var weeklySumConstraints = new(int Shift, int HardMin, int SoftMin, int MinPenalty, int SoftMax,
|
||||
int HardMax, int MaxPenalty)[] {
|
||||
// Constraints on rests per week.
|
||||
(0, 1, 2, 7, 2, 3, 4),
|
||||
// At least 1 night shift per week (penalized). At most 4 (hard).
|
||||
(3, 0, 1, 3, 4, 4, 0),
|
||||
};
|
||||
|
||||
// Penalized transitions:
|
||||
// (previous_shift, next_shift, penalty (0 means forbidden))
|
||||
var penalizedTransitions = new(int PreviousShift, int NextShift, int Penalty)[] {
|
||||
// Afternoon to night has a penalty of 4.
|
||||
(2, 3, 4),
|
||||
// Night to morning is forbidden.
|
||||
(3, 1, 0),
|
||||
};
|
||||
|
||||
// daily demands for work shifts (morning, afternon, night) for each day
|
||||
// of the week starting on Monday.
|
||||
var weeklyCoverDemands = new int[][] {
|
||||
new[] { 2, 3, 1 }, // Monday
|
||||
new[] { 2, 3, 1 }, // Tuesday
|
||||
new[] { 2, 2, 2 }, // Wednesday
|
||||
new[] { 2, 3, 1 }, // Thursday
|
||||
new[] { 2, 2, 2 }, // Friday
|
||||
new[] { 1, 2, 3 }, // Saturday
|
||||
new[] { 1, 3, 1 }, // Sunday
|
||||
};
|
||||
|
||||
// Penalty for exceeding the cover constraint per shift type.
|
||||
var excessCoverPenalties = new[] { 2, 2, 5 };
|
||||
|
||||
var numDays = numWeeks * 7;
|
||||
var numShifts = shifts.Length;
|
||||
|
||||
var model = new CpModel();
|
||||
|
||||
IntVar[,,] work = new IntVar[numEmployees, numShifts, numDays];
|
||||
|
||||
foreach (int e in Range(numEmployees)) {
|
||||
foreach (int s in Range(numShifts)) {
|
||||
foreach (int d in Range(numDays)) {
|
||||
work[e, s, d] = model.NewBoolVar($"work{e}_{s}_{d}");
|
||||
}
|
||||
}
|
||||
public class ShiftSchedulingSat
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
SolveShiftScheduling();
|
||||
}
|
||||
|
||||
// Linear terms of the objective in a minimization context.
|
||||
var objIntVars = new List<IntVar>();
|
||||
var objIntCoeffs = new List<int>();
|
||||
var objBoolVars = new List<IntVar>();
|
||||
var objBoolCoeffs = new List<int>();
|
||||
static void SolveShiftScheduling()
|
||||
{
|
||||
int numEmployees = 8;
|
||||
int numWeeks = 3;
|
||||
var shifts = new[] { "O", "M", "A", "N" };
|
||||
|
||||
// Exactly one shift per day.
|
||||
foreach (int e in Range(numEmployees)) {
|
||||
foreach (int d in Range(numDays)) {
|
||||
var temp = new IntVar[numShifts];
|
||||
foreach (int s in Range(numShifts)) {
|
||||
temp[s] = work[e, s, d];
|
||||
}
|
||||
// Fixed assignment: (employee, shift, day).
|
||||
// This fixes the first 2 days of the schedule.
|
||||
var fixedAssignments = new(int Employee, int Shift, int Day)[] {
|
||||
(0, 0, 0), (1, 0, 0), (2, 1, 0), (3, 1, 0), (4, 2, 0), (5, 2, 0), (6, 2, 3), (7, 3, 0),
|
||||
(0, 1, 1), (1, 1, 1), (2, 2, 1), (3, 2, 1), (4, 2, 1), (5, 0, 1), (6, 0, 1), (7, 3, 1),
|
||||
};
|
||||
|
||||
model.Add(LinearExpr.Sum(temp) == 1);
|
||||
}
|
||||
}
|
||||
// Request: (employee, shift, day, weight)
|
||||
// A negative weight indicates that the employee desire this assignment.
|
||||
var requests = new(int Employee, int Shift, int Day,
|
||||
int Weight)[] {// Employee 3 wants the first Saturday off.
|
||||
(3, 0, 5, -2),
|
||||
// Employee 5 wants a night shift on the second Thursday.
|
||||
(5, 3, 10, -2),
|
||||
// Employee 2 does not want a night shift on the third Friday.
|
||||
(2, 3, 4, 4)
|
||||
};
|
||||
|
||||
// Fixed assignments.
|
||||
foreach (var (e, s, d) in fixedAssignments) {
|
||||
model.Add(work[e, s, d] == 1);
|
||||
}
|
||||
// Shift constraints on continuous sequence :
|
||||
// (shift, hard_min, soft_min, min_penalty,
|
||||
// soft_max, hard_max, max_penalty)
|
||||
var shiftConstraints =
|
||||
new(int Shift, int HardMin, int SoftMin, int MinPenalty, int SoftMax, int HardMax, int MaxPenalty)[] {
|
||||
// One or two consecutive days of rest, this is a hard constraint.
|
||||
(0, 1, 1, 0, 2, 2, 0),
|
||||
// Between 2 and 3 consecutive days of night shifts, 1 and 4 are
|
||||
// possible but penalized.
|
||||
(3, 1, 2, 20, 3, 4, 5),
|
||||
};
|
||||
|
||||
// Employee requests
|
||||
foreach (var (e, s, d, w) in requests) {
|
||||
objBoolVars.Add(work[e, s, d]);
|
||||
objBoolCoeffs.Add(w);
|
||||
}
|
||||
// Weekly sum constraints on shifts days:
|
||||
// (shift, hardMin, softMin, minPenalty,
|
||||
// softMax, hardMax, maxPenalty)
|
||||
var weeklySumConstraints =
|
||||
new(int Shift, int HardMin, int SoftMin, int MinPenalty, int SoftMax, int HardMax, int MaxPenalty)[] {
|
||||
// Constraints on rests per week.
|
||||
(0, 1, 2, 7, 2, 3, 4),
|
||||
// At least 1 night shift per week (penalized). At most 4 (hard).
|
||||
(3, 0, 1, 3, 4, 4, 0),
|
||||
};
|
||||
|
||||
// Shift constraints
|
||||
foreach (var constraint in shiftConstraints) {
|
||||
foreach (int e in Range(numEmployees)) {
|
||||
var works = new IntVar[numDays];
|
||||
foreach (int d in Range(numDays)) {
|
||||
works[d] = work[e, constraint.Shift, d];
|
||||
}
|
||||
// Penalized transitions:
|
||||
// (previous_shift, next_shift, penalty (0 means forbidden))
|
||||
var penalizedTransitions = new(int PreviousShift, int NextShift, int Penalty)[] {
|
||||
// Afternoon to night has a penalty of 4.
|
||||
(2, 3, 4),
|
||||
// Night to morning is forbidden.
|
||||
(3, 1, 0),
|
||||
};
|
||||
|
||||
var (variables, coeffs) = AddSoftSequenceConstraint(
|
||||
model, works, constraint.HardMin, constraint.SoftMin, constraint.MinPenalty,
|
||||
constraint.SoftMax, constraint.HardMax, constraint.MaxPenalty,
|
||||
$"shift_constraint(employee {e}, shift {constraint.Shift}");
|
||||
// daily demands for work shifts (morning, afternon, night) for each day
|
||||
// of the week starting on Monday.
|
||||
var weeklyCoverDemands = new int[][] {
|
||||
new[] { 2, 3, 1 }, // Monday
|
||||
new[] { 2, 3, 1 }, // Tuesday
|
||||
new[] { 2, 2, 2 }, // Wednesday
|
||||
new[] { 2, 3, 1 }, // Thursday
|
||||
new[] { 2, 2, 2 }, // Friday
|
||||
new[] { 1, 2, 3 }, // Saturday
|
||||
new[] { 1, 3, 1 }, // Sunday
|
||||
};
|
||||
|
||||
objBoolVars.AddRange(variables);
|
||||
objBoolCoeffs.AddRange(coeffs);
|
||||
}
|
||||
}
|
||||
// Penalty for exceeding the cover constraint per shift type.
|
||||
var excessCoverPenalties = new[] { 2, 2, 5 };
|
||||
|
||||
// Weekly sum constraints
|
||||
foreach (var constraint in weeklySumConstraints) {
|
||||
foreach (int e in Range(numEmployees)) {
|
||||
foreach (int w in Range(numWeeks)) {
|
||||
var works = new IntVar[7];
|
||||
var numDays = numWeeks * 7;
|
||||
var numShifts = shifts.Length;
|
||||
|
||||
foreach (int d in Range(7)) {
|
||||
works[d] = work[e, constraint.Shift, d + w * 7];
|
||||
}
|
||||
var model = new CpModel();
|
||||
|
||||
var (variables, coeffs) = AddSoftSumConstraint(
|
||||
model, works, constraint.HardMin, constraint.SoftMin, constraint.MinPenalty,
|
||||
constraint.SoftMax, constraint.HardMax, constraint.MaxPenalty,
|
||||
$"weekly_sum_constraint(employee {e}, shift {constraint.Shift}, week {w}");
|
||||
IntVar[,,] work = new IntVar[numEmployees, numShifts, numDays];
|
||||
|
||||
objBoolVars.AddRange(variables);
|
||||
objBoolCoeffs.AddRange(coeffs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Penalized transitions
|
||||
foreach (var penalizedTransition in penalizedTransitions) {
|
||||
foreach (int e in Range(numEmployees)) {
|
||||
foreach (int d in Range(numDays - 1)) {
|
||||
var transition =
|
||||
new List<ILiteral>() { work[e, penalizedTransition.PreviousShift, d].Not(),
|
||||
work[e, penalizedTransition.NextShift, d + 1].Not() };
|
||||
|
||||
if (penalizedTransition.Penalty == 0) {
|
||||
model.AddBoolOr(transition);
|
||||
} else {
|
||||
var transVar = model.NewBoolVar($"transition (employee {e}, day={d}");
|
||||
transition.Add(transVar);
|
||||
model.AddBoolOr(transition);
|
||||
objBoolVars.Add(transVar);
|
||||
objBoolCoeffs.Add(penalizedTransition.Penalty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cover constraints
|
||||
foreach (int s in Range(1, numShifts)) {
|
||||
foreach (int w in Range(numWeeks)) {
|
||||
foreach (int d in Range(7)) {
|
||||
var works = new IntVar[numEmployees];
|
||||
foreach (int e in Range(numEmployees)) {
|
||||
works[e] = work[e, s, w * 7 + d];
|
||||
}
|
||||
|
||||
// Ignore off shift
|
||||
var minDemand = weeklyCoverDemands[d][s - 1];
|
||||
var worked = model.NewIntVar(minDemand, numEmployees, "");
|
||||
model.Add(LinearExpr.Sum(works) == worked);
|
||||
|
||||
var overPenalty = excessCoverPenalties[s - 1];
|
||||
if (overPenalty > 0) {
|
||||
var name = $"excess_demand(shift={s}, week={w}, day={d}";
|
||||
var excess = model.NewIntVar(0, numEmployees - minDemand, name);
|
||||
model.Add(excess == worked - minDemand);
|
||||
objIntVars.Add(excess);
|
||||
objIntCoeffs.Add(overPenalty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Objective
|
||||
var objBoolSum = LinearExpr.ScalProd(objBoolVars, objBoolCoeffs);
|
||||
var objIntSum = LinearExpr.ScalProd(objIntVars, objIntCoeffs);
|
||||
|
||||
model.Minimize(objBoolSum + objIntSum);
|
||||
|
||||
// Solve model
|
||||
var solver = new CpSolver();
|
||||
solver.StringParameters =
|
||||
"num_search_workers:8, log_search_progress: true, max_time_in_seconds:30";
|
||||
|
||||
var status = solver.Solve(model);
|
||||
|
||||
// Print solution
|
||||
if (status == CpSolverStatus.Optimal || status == CpSolverStatus.Feasible) {
|
||||
Console.WriteLine();
|
||||
var header = " ";
|
||||
for (int w = 0; w < numWeeks; w++) {
|
||||
header += "M T W T F S S ";
|
||||
}
|
||||
|
||||
Console.WriteLine(header);
|
||||
|
||||
foreach (int e in Range(numEmployees)) {
|
||||
var schedule = "";
|
||||
foreach (int d in Range(numDays)) {
|
||||
foreach (int s in Range(numShifts)) {
|
||||
if (solver.BooleanValue(work[e, s, d])) {
|
||||
schedule += shifts[s] + " ";
|
||||
foreach (int e in Range(numEmployees))
|
||||
{
|
||||
foreach (int s in Range(numShifts))
|
||||
{
|
||||
foreach (int d in Range(numDays))
|
||||
{
|
||||
work[e, s, d] = model.NewBoolVar($"work{e}_{s}_{d}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"worker {e}: {schedule}");
|
||||
}
|
||||
// Linear terms of the objective in a minimization context.
|
||||
var objIntVars = new List<IntVar>();
|
||||
var objIntCoeffs = new List<int>();
|
||||
var objBoolVars = new List<IntVar>();
|
||||
var objBoolCoeffs = new List<int>();
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Penalties:");
|
||||
// Exactly one shift per day.
|
||||
foreach (int e in Range(numEmployees))
|
||||
{
|
||||
foreach (int d in Range(numDays))
|
||||
{
|
||||
var temp = new IntVar[numShifts];
|
||||
foreach (int s in Range(numShifts))
|
||||
{
|
||||
temp[s] = work[e, s, d];
|
||||
}
|
||||
|
||||
foreach (var (i, var) in objBoolVars.Select((x, i) => (i, x))) {
|
||||
if (solver.BooleanValue(var)) {
|
||||
var penalty = objBoolCoeffs[i];
|
||||
if (penalty > 0) {
|
||||
Console.WriteLine($" {var.Name()} violated, penalty={penalty}");
|
||||
} else {
|
||||
Console.WriteLine($" {var.Name()} fulfilled, gain={-penalty}");
|
||||
}
|
||||
model.Add(LinearExpr.Sum(temp) == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (i, var) in objIntVars.Select((x, i) => (i, x))) {
|
||||
if (solver.Value(var) > 0) {
|
||||
Console.WriteLine(
|
||||
$" {var.Name()} violated by {solver.Value(var)}, linear penalty={objIntCoeffs[i]}");
|
||||
// Fixed assignments.
|
||||
foreach (var (e, s, d) in fixedAssignments)
|
||||
{
|
||||
model.Add(work[e, s, d] == 1);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Statistics");
|
||||
Console.WriteLine($" - status : {status}");
|
||||
Console.WriteLine($" - conflicts : {solver.NumConflicts()}");
|
||||
Console.WriteLine($" - branches : {solver.NumBranches()}");
|
||||
Console.WriteLine($" - wall time : {solver.WallTime()}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters an isolated sub-sequence of variables assigned to True.
|
||||
/// Extract the span of Boolean variables[start, start + length), negate them,
|
||||
/// and if there is variables to the left / right of this span, surround the
|
||||
/// span by them in non negated form.
|
||||
/// </summary>
|
||||
/// <param name="works">A list of variables to extract the span from.</param>
|
||||
/// <param name="start">The start to the span.</param>
|
||||
/// <param name="length">The length of the span.</param>
|
||||
/// <returns>An array of variables which conjunction will be false if the
|
||||
/// sub-list is assigned to True, and correctly bounded by variables assigned
|
||||
/// to False, or by the start or end of works.</returns>
|
||||
static ILiteral[] NegatedBoundedSpan(IntVar[] works, int start, int length) {
|
||||
var sequence = new List<ILiteral>();
|
||||
|
||||
if (start > 0)
|
||||
sequence.Add(works[start - 1]);
|
||||
|
||||
foreach (var i in Range(length)) sequence.Add(works[start + i].Not());
|
||||
|
||||
if (start + length < works.Length)
|
||||
sequence.Add(works[start + length]);
|
||||
|
||||
return sequence.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sequence constraint on true variables with soft and hard bounds.
|
||||
/// This constraint look at every maximal contiguous sequence of variables
|
||||
/// assigned to true. If forbids sequence of length < hardMin or >
|
||||
/// hardMax. Then it creates penalty terms if the length is < softMin or
|
||||
/// > softMax.
|
||||
/// </summary>
|
||||
/// <param name="model">The sequence constraint is built on this
|
||||
/// model.</param> <param name="works">A list of Boolean variables.</param>
|
||||
/// <param name="hardMin">Any sequence of true variables must have a length of
|
||||
/// at least hardMin.</param> <param name="softMin">Any sequence should have a
|
||||
/// length of at least softMin, or a linear penalty on the delta will be added
|
||||
/// to the objective.</param> <param name="minCost">The coefficient of the
|
||||
/// linear penalty if the length is less than softMin.</param> <param
|
||||
/// name="softMax">Any sequence should have a length of at most softMax, or a
|
||||
/// linear penalty on the delta will be added to the objective.</param> <param
|
||||
/// name="hardMax">Any sequence of true variables must have a length of at
|
||||
/// most hardMax.</param> <param name="maxCost">The coefficient of the linear
|
||||
/// penalty if the length is more than softMax.</param> <param name="prefix">A
|
||||
/// base name for penalty literals.</param> <returns>A tuple (costLiterals,
|
||||
/// costCoefficients) containing the different penalties created by the
|
||||
/// sequence constraint.</returns>
|
||||
static (IntVar[] costLiterals, int[] costCoefficients)
|
||||
AddSoftSequenceConstraint(CpModel model, IntVar[] works, int hardMin, int softMin,
|
||||
int minCost, int softMax, int hardMax, int maxCost, string prefix) {
|
||||
var costLiterals = new List<IntVar>();
|
||||
var costCoefficients = new List<int>();
|
||||
|
||||
// Forbid sequences that are too short.
|
||||
foreach (var length in Range(1, hardMin)) {
|
||||
foreach (var start in Range(works.Length - length + 1)) {
|
||||
model.AddBoolOr(NegatedBoundedSpan(works, start, length));
|
||||
}
|
||||
}
|
||||
|
||||
// Penalize sequences that are below the soft limit.
|
||||
|
||||
if (minCost > 0) {
|
||||
foreach (var length in Range(hardMin, softMin)) {
|
||||
foreach (var start in Range(works.Length - length + 1)) {
|
||||
var span = NegatedBoundedSpan(works, start, length).ToList();
|
||||
var name = $": under_span(start={start}, length={length})";
|
||||
var lit = model.NewBoolVar(prefix + name);
|
||||
span.Add(lit);
|
||||
model.AddBoolOr(span);
|
||||
costLiterals.Add(lit);
|
||||
// We filter exactly the sequence with a short length.
|
||||
// The penalty is proportional to the delta with softMin.
|
||||
costCoefficients.Add(minCost * (softMin - length));
|
||||
// Employee requests
|
||||
foreach (var (e, s, d, w) in requests)
|
||||
{
|
||||
objBoolVars.Add(work[e, s, d]);
|
||||
objBoolCoeffs.Add(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Penalize sequences that are above the soft limit.
|
||||
if (maxCost > 0) {
|
||||
foreach (var length in Range(softMax + 1, hardMax + 1)) {
|
||||
foreach (var start in Range(works.Length - length + 1)) {
|
||||
var span = NegatedBoundedSpan(works, start, length).ToList();
|
||||
var name = $": over_span(start={start}, length={length})";
|
||||
var lit = model.NewBoolVar(prefix + name);
|
||||
span.Add(lit);
|
||||
model.AddBoolOr(span);
|
||||
costLiterals.Add(lit);
|
||||
// Cost paid is max_cost * excess length.
|
||||
costCoefficients.Add(maxCost * (length - softMax));
|
||||
// Shift constraints
|
||||
foreach (var constraint in shiftConstraints)
|
||||
{
|
||||
foreach (int e in Range(numEmployees))
|
||||
{
|
||||
var works = new IntVar[numDays];
|
||||
foreach (int d in Range(numDays))
|
||||
{
|
||||
works[d] = work[e, constraint.Shift, d];
|
||||
}
|
||||
|
||||
var (variables, coeffs) = AddSoftSequenceConstraint(
|
||||
model, works, constraint.HardMin, constraint.SoftMin, constraint.MinPenalty, constraint.SoftMax,
|
||||
constraint.HardMax, constraint.MaxPenalty,
|
||||
$"shift_constraint(employee {e}, shift {constraint.Shift}");
|
||||
|
||||
objBoolVars.AddRange(variables);
|
||||
objBoolCoeffs.AddRange(coeffs);
|
||||
}
|
||||
}
|
||||
|
||||
// Weekly sum constraints
|
||||
foreach (var constraint in weeklySumConstraints)
|
||||
{
|
||||
foreach (int e in Range(numEmployees))
|
||||
{
|
||||
foreach (int w in Range(numWeeks))
|
||||
{
|
||||
var works = new IntVar[7];
|
||||
|
||||
foreach (int d in Range(7))
|
||||
{
|
||||
works[d] = work[e, constraint.Shift, d + w * 7];
|
||||
}
|
||||
|
||||
var (variables, coeffs) = AddSoftSumConstraint(
|
||||
model, works, constraint.HardMin, constraint.SoftMin, constraint.MinPenalty, constraint.SoftMax,
|
||||
constraint.HardMax, constraint.MaxPenalty,
|
||||
$"weekly_sum_constraint(employee {e}, shift {constraint.Shift}, week {w}");
|
||||
|
||||
objBoolVars.AddRange(variables);
|
||||
objBoolCoeffs.AddRange(coeffs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Penalized transitions
|
||||
foreach (var penalizedTransition in penalizedTransitions)
|
||||
{
|
||||
foreach (int e in Range(numEmployees))
|
||||
{
|
||||
foreach (int d in Range(numDays - 1))
|
||||
{
|
||||
var transition = new List<ILiteral>() { work[e, penalizedTransition.PreviousShift, d].Not(),
|
||||
work[e, penalizedTransition.NextShift, d + 1].Not() };
|
||||
|
||||
if (penalizedTransition.Penalty == 0)
|
||||
{
|
||||
model.AddBoolOr(transition);
|
||||
}
|
||||
else
|
||||
{
|
||||
var transVar = model.NewBoolVar($"transition (employee {e}, day={d}");
|
||||
transition.Add(transVar);
|
||||
model.AddBoolOr(transition);
|
||||
objBoolVars.Add(transVar);
|
||||
objBoolCoeffs.Add(penalizedTransition.Penalty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cover constraints
|
||||
foreach (int s in Range(1, numShifts))
|
||||
{
|
||||
foreach (int w in Range(numWeeks))
|
||||
{
|
||||
foreach (int d in Range(7))
|
||||
{
|
||||
var works = new IntVar[numEmployees];
|
||||
foreach (int e in Range(numEmployees))
|
||||
{
|
||||
works[e] = work[e, s, w * 7 + d];
|
||||
}
|
||||
|
||||
// Ignore off shift
|
||||
var minDemand = weeklyCoverDemands[d][s - 1];
|
||||
var worked = model.NewIntVar(minDemand, numEmployees, "");
|
||||
model.Add(LinearExpr.Sum(works) == worked);
|
||||
|
||||
var overPenalty = excessCoverPenalties[s - 1];
|
||||
if (overPenalty > 0)
|
||||
{
|
||||
var name = $"excess_demand(shift={s}, week={w}, day={d}";
|
||||
var excess = model.NewIntVar(0, numEmployees - minDemand, name);
|
||||
model.Add(excess == worked - minDemand);
|
||||
objIntVars.Add(excess);
|
||||
objIntCoeffs.Add(overPenalty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Objective
|
||||
var objBoolSum = LinearExpr.ScalProd(objBoolVars, objBoolCoeffs);
|
||||
var objIntSum = LinearExpr.ScalProd(objIntVars, objIntCoeffs);
|
||||
|
||||
model.Minimize(objBoolSum + objIntSum);
|
||||
|
||||
// Solve model
|
||||
var solver = new CpSolver();
|
||||
solver.StringParameters = "num_search_workers:8, log_search_progress: true, max_time_in_seconds:30";
|
||||
|
||||
var status = solver.Solve(model);
|
||||
|
||||
// Print solution
|
||||
if (status == CpSolverStatus.Optimal || status == CpSolverStatus.Feasible)
|
||||
{
|
||||
Console.WriteLine();
|
||||
var header = " ";
|
||||
for (int w = 0; w < numWeeks; w++)
|
||||
{
|
||||
header += "M T W T F S S ";
|
||||
}
|
||||
|
||||
Console.WriteLine(header);
|
||||
|
||||
foreach (int e in Range(numEmployees))
|
||||
{
|
||||
var schedule = "";
|
||||
foreach (int d in Range(numDays))
|
||||
{
|
||||
foreach (int s in Range(numShifts))
|
||||
{
|
||||
if (solver.BooleanValue(work[e, s, d]))
|
||||
{
|
||||
schedule += shifts[s] + " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"worker {e}: {schedule}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Penalties:");
|
||||
|
||||
foreach (var (i, var) in objBoolVars.Select((x, i) => (i, x)))
|
||||
{
|
||||
if (solver.BooleanValue(var))
|
||||
{
|
||||
var penalty = objBoolCoeffs[i];
|
||||
if (penalty > 0)
|
||||
{
|
||||
Console.WriteLine($" {var.Name()} violated, penalty={penalty}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($" {var.Name()} fulfilled, gain={-penalty}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (i, var) in objIntVars.Select((x, i) => (i, x)))
|
||||
{
|
||||
if (solver.Value(var) > 0)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$" {var.Name()} violated by {solver.Value(var)}, linear penalty={objIntCoeffs[i]}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Statistics");
|
||||
Console.WriteLine($" - status : {status}");
|
||||
Console.WriteLine($" - conflicts : {solver.NumConflicts()}");
|
||||
Console.WriteLine($" - branches : {solver.NumBranches()}");
|
||||
Console.WriteLine($" - wall time : {solver.WallTime()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Just forbid any sequence of true variables with length hardMax + 1
|
||||
foreach (var start in Range(works.Length - hardMax)) {
|
||||
var temp = new List<ILiteral>();
|
||||
/// <summary>
|
||||
/// Filters an isolated sub-sequence of variables assigned to True.
|
||||
/// Extract the span of Boolean variables[start, start + length), negate them,
|
||||
/// and if there is variables to the left / right of this span, surround the
|
||||
/// span by them in non negated form.
|
||||
/// </summary>
|
||||
/// <param name="works">A list of variables to extract the span from.</param>
|
||||
/// <param name="start">The start to the span.</param>
|
||||
/// <param name="length">The length of the span.</param>
|
||||
/// <returns>An array of variables which conjunction will be false if the
|
||||
/// sub-list is assigned to True, and correctly bounded by variables assigned
|
||||
/// to False, or by the start or end of works.</returns>
|
||||
static ILiteral[] NegatedBoundedSpan(IntVar[] works, int start, int length)
|
||||
{
|
||||
var sequence = new List<ILiteral>();
|
||||
|
||||
foreach (var i in Range(start, start + hardMax + 1)) {
|
||||
temp.Add(works[i].Not());
|
||||
}
|
||||
if (start > 0)
|
||||
sequence.Add(works[start - 1]);
|
||||
|
||||
model.AddBoolOr(temp);
|
||||
foreach (var i in Range(length))
|
||||
sequence.Add(works[start + i].Not());
|
||||
|
||||
if (start + length < works.Length)
|
||||
sequence.Add(works[start + length]);
|
||||
|
||||
return sequence.ToArray();
|
||||
}
|
||||
|
||||
return (costLiterals.ToArray(), costCoefficients.ToArray());
|
||||
}
|
||||
/// <summary>
|
||||
/// Sequence constraint on true variables with soft and hard bounds.
|
||||
/// This constraint look at every maximal contiguous sequence of variables
|
||||
/// assigned to true. If forbids sequence of length < hardMin or >
|
||||
/// hardMax. Then it creates penalty terms if the length is < softMin or
|
||||
/// > softMax.
|
||||
/// </summary>
|
||||
/// <param name="model">The sequence constraint is built on this
|
||||
/// model.</param> <param name="works">A list of Boolean variables.</param>
|
||||
/// <param name="hardMin">Any sequence of true variables must have a length of
|
||||
/// at least hardMin.</param> <param name="softMin">Any sequence should have a
|
||||
/// length of at least softMin, or a linear penalty on the delta will be added
|
||||
/// to the objective.</param> <param name="minCost">The coefficient of the
|
||||
/// linear penalty if the length is less than softMin.</param> <param
|
||||
/// name="softMax">Any sequence should have a length of at most softMax, or a
|
||||
/// linear penalty on the delta will be added to the objective.</param> <param
|
||||
/// name="hardMax">Any sequence of true variables must have a length of at
|
||||
/// most hardMax.</param> <param name="maxCost">The coefficient of the linear
|
||||
/// penalty if the length is more than softMax.</param> <param name="prefix">A
|
||||
/// base name for penalty literals.</param> <returns>A tuple (costLiterals,
|
||||
/// costCoefficients) containing the different penalties created by the
|
||||
/// sequence constraint.</returns>
|
||||
static (IntVar[] costLiterals, int[] costCoefficients)
|
||||
AddSoftSequenceConstraint(CpModel model, IntVar[] works, int hardMin, int softMin, int minCost, int softMax,
|
||||
int hardMax, int maxCost, string prefix)
|
||||
{
|
||||
var costLiterals = new List<IntVar>();
|
||||
var costCoefficients = new List<int>();
|
||||
|
||||
/// <summary>
|
||||
/// Sum constraint with soft and hard bounds.
|
||||
/// This constraint counts the variables assigned to true from works.
|
||||
/// If forbids sum < hardMin or > hardMax.
|
||||
/// Then it creates penalty terms if the sum is < softMin or > softMax.
|
||||
/// </summary>
|
||||
/// <param name="model">The sequence constraint is built on this
|
||||
/// model.</param> <param name="works">A list of Boolean variables.</param>
|
||||
/// <param name="hardMin">Any sequence of true variables must have a length of
|
||||
/// at least hardMin.</param> <param name="softMin">Any sequence should have a
|
||||
/// length of at least softMin, or a linear penalty on the delta will be added
|
||||
/// to the objective.</param> <param name="minCost">The coefficient of the
|
||||
/// linear penalty if the length is less than softMin.</param> <param
|
||||
/// name="softMax">Any sequence should have a length of at most softMax, or a
|
||||
/// linear penalty on the delta will be added to the objective.</param> <param
|
||||
/// name="hardMax">Any sequence of true variables must have a length of at
|
||||
/// most hardMax.</param> <param name="maxCost">The coefficient of the linear
|
||||
/// penalty if the length is more than softMax.</param> <param name="prefix">A
|
||||
/// base name for penalty literals.</param> <returns>A tuple (costVariables,
|
||||
/// costCoefficients) containing the different penalties created by the
|
||||
/// sequence constraint.</returns>
|
||||
static (IntVar[] costVariables, int[] costCoefficients)
|
||||
AddSoftSumConstraint(CpModel model, IntVar[] works, int hardMin, int softMin, int minCost,
|
||||
int softMax, int hardMax, int maxCost, string prefix) {
|
||||
var costVariables = new List<IntVar>();
|
||||
var costCoefficients = new List<int>();
|
||||
var sumVar = model.NewIntVar(hardMin, hardMax, "");
|
||||
// This adds the hard constraints on the sum.
|
||||
model.Add(sumVar == LinearExpr.Sum(works));
|
||||
// Forbid sequences that are too short.
|
||||
foreach (var length in Range(1, hardMin))
|
||||
{
|
||||
foreach (var start in Range(works.Length - length + 1))
|
||||
{
|
||||
model.AddBoolOr(NegatedBoundedSpan(works, start, length));
|
||||
}
|
||||
}
|
||||
|
||||
var zero = model.NewConstant(0);
|
||||
// Penalize sequences that are below the soft limit.
|
||||
|
||||
// Penalize sums below the soft_min target.
|
||||
if (minCost > 0)
|
||||
{
|
||||
foreach (var length in Range(hardMin, softMin))
|
||||
{
|
||||
foreach (var start in Range(works.Length - length + 1))
|
||||
{
|
||||
var span = NegatedBoundedSpan(works, start, length).ToList();
|
||||
var name = $": under_span(start={start}, length={length})";
|
||||
var lit = model.NewBoolVar(prefix + name);
|
||||
span.Add(lit);
|
||||
model.AddBoolOr(span);
|
||||
costLiterals.Add(lit);
|
||||
// We filter exactly the sequence with a short length.
|
||||
// The penalty is proportional to the delta with softMin.
|
||||
costCoefficients.Add(minCost * (softMin - length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (softMin > hardMin && minCost > 0) {
|
||||
var delta = model.NewIntVar(-works.Length, works.Length, "");
|
||||
model.Add(delta == (softMin - sumVar));
|
||||
var excess = model.NewIntVar(0, works.Length, prefix + ": under_sum");
|
||||
model.AddMaxEquality(excess, new[] { delta, zero });
|
||||
costVariables.Add(excess);
|
||||
costCoefficients.Add(minCost);
|
||||
// Penalize sequences that are above the soft limit.
|
||||
if (maxCost > 0)
|
||||
{
|
||||
foreach (var length in Range(softMax + 1, hardMax + 1))
|
||||
{
|
||||
foreach (var start in Range(works.Length - length + 1))
|
||||
{
|
||||
var span = NegatedBoundedSpan(works, start, length).ToList();
|
||||
var name = $": over_span(start={start}, length={length})";
|
||||
var lit = model.NewBoolVar(prefix + name);
|
||||
span.Add(lit);
|
||||
model.AddBoolOr(span);
|
||||
costLiterals.Add(lit);
|
||||
// Cost paid is max_cost * excess length.
|
||||
costCoefficients.Add(maxCost * (length - softMax));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Just forbid any sequence of true variables with length hardMax + 1
|
||||
foreach (var start in Range(works.Length - hardMax))
|
||||
{
|
||||
var temp = new List<ILiteral>();
|
||||
|
||||
foreach (var i in Range(start, start + hardMax + 1))
|
||||
{
|
||||
temp.Add(works[i].Not());
|
||||
}
|
||||
|
||||
model.AddBoolOr(temp);
|
||||
}
|
||||
|
||||
return (costLiterals.ToArray(), costCoefficients.ToArray());
|
||||
}
|
||||
|
||||
// Penalize sums above the soft_max target.
|
||||
if (softMax < hardMax && maxCost > 0) {
|
||||
var delta = model.NewIntVar(-works.Length, works.Length, "");
|
||||
model.Add(delta == sumVar - softMax);
|
||||
var excess = model.NewIntVar(0, works.Length, prefix + ": over_sum");
|
||||
model.AddMaxEquality(excess, new[] { delta, zero });
|
||||
costVariables.Add(excess);
|
||||
costCoefficients.Add(maxCost);
|
||||
/// <summary>
|
||||
/// Sum constraint with soft and hard bounds.
|
||||
/// This constraint counts the variables assigned to true from works.
|
||||
/// If forbids sum < hardMin or > hardMax.
|
||||
/// Then it creates penalty terms if the sum is < softMin or > softMax.
|
||||
/// </summary>
|
||||
/// <param name="model">The sequence constraint is built on this
|
||||
/// model.</param> <param name="works">A list of Boolean variables.</param>
|
||||
/// <param name="hardMin">Any sequence of true variables must have a length of
|
||||
/// at least hardMin.</param> <param name="softMin">Any sequence should have a
|
||||
/// length of at least softMin, or a linear penalty on the delta will be added
|
||||
/// to the objective.</param> <param name="minCost">The coefficient of the
|
||||
/// linear penalty if the length is less than softMin.</param> <param
|
||||
/// name="softMax">Any sequence should have a length of at most softMax, or a
|
||||
/// linear penalty on the delta will be added to the objective.</param> <param
|
||||
/// name="hardMax">Any sequence of true variables must have a length of at
|
||||
/// most hardMax.</param> <param name="maxCost">The coefficient of the linear
|
||||
/// penalty if the length is more than softMax.</param> <param name="prefix">A
|
||||
/// base name for penalty literals.</param> <returns>A tuple (costVariables,
|
||||
/// costCoefficients) containing the different penalties created by the
|
||||
/// sequence constraint.</returns>
|
||||
static (IntVar[] costVariables, int[] costCoefficients)
|
||||
AddSoftSumConstraint(CpModel model, IntVar[] works, int hardMin, int softMin, int minCost, int softMax,
|
||||
int hardMax, int maxCost, string prefix)
|
||||
{
|
||||
var costVariables = new List<IntVar>();
|
||||
var costCoefficients = new List<int>();
|
||||
var sumVar = model.NewIntVar(hardMin, hardMax, "");
|
||||
// This adds the hard constraints on the sum.
|
||||
model.Add(sumVar == LinearExpr.Sum(works));
|
||||
|
||||
var zero = model.NewConstant(0);
|
||||
|
||||
// Penalize sums below the soft_min target.
|
||||
|
||||
if (softMin > hardMin && minCost > 0)
|
||||
{
|
||||
var delta = model.NewIntVar(-works.Length, works.Length, "");
|
||||
model.Add(delta == (softMin - sumVar));
|
||||
var excess = model.NewIntVar(0, works.Length, prefix + ": under_sum");
|
||||
model.AddMaxEquality(excess, new[] { delta, zero });
|
||||
costVariables.Add(excess);
|
||||
costCoefficients.Add(minCost);
|
||||
}
|
||||
|
||||
// Penalize sums above the soft_max target.
|
||||
if (softMax < hardMax && maxCost > 0)
|
||||
{
|
||||
var delta = model.NewIntVar(-works.Length, works.Length, "");
|
||||
model.Add(delta == sumVar - softMax);
|
||||
var excess = model.NewIntVar(0, works.Length, prefix + ": over_sum");
|
||||
model.AddMaxEquality(excess, new[] { delta, zero });
|
||||
costVariables.Add(excess);
|
||||
costCoefficients.Add(maxCost);
|
||||
}
|
||||
|
||||
return (costVariables.ToArray(), costCoefficients.ToArray());
|
||||
}
|
||||
|
||||
return (costVariables.ToArray(), costCoefficients.ToArray());
|
||||
}
|
||||
/// <summary>
|
||||
/// C# equivalent of Python range (start, stop)
|
||||
/// </summary>
|
||||
/// <param name="start">The inclusive start.</param>
|
||||
/// <param name="stop">The exclusive stop.</param>
|
||||
/// <returns>A sequence of integers.</returns>
|
||||
static IEnumerable<int> Range(int start, int stop)
|
||||
{
|
||||
foreach (var i in Enumerable.Range(start, stop - start))
|
||||
yield return i;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// C# equivalent of Python range (start, stop)
|
||||
/// </summary>
|
||||
/// <param name="start">The inclusive start.</param>
|
||||
/// <param name="stop">The exclusive stop.</param>
|
||||
/// <returns>A sequence of integers.</returns>
|
||||
static IEnumerable<int> Range(int start, int stop) {
|
||||
foreach (var i in Enumerable.Range(start, stop - start)) yield return i;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// C# equivalent of Python range (stop)
|
||||
/// </summary>
|
||||
/// <param name="stop">The exclusive stop.</param>
|
||||
/// <returns>A sequence of integers.</returns>
|
||||
static IEnumerable<int> Range(int stop) {
|
||||
return Range(0, stop);
|
||||
}
|
||||
/// <summary>
|
||||
/// C# equivalent of Python range (stop)
|
||||
/// </summary>
|
||||
/// <param name="stop">The exclusive stop.</param>
|
||||
/// <returns>A sequence of integers.</returns>
|
||||
static IEnumerable<int> Range(int stop)
|
||||
{
|
||||
return Range(0, stop);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,169 +17,185 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
class SpeakerScheduling {
|
||||
struct Entry {
|
||||
public IntVar var;
|
||||
public int start;
|
||||
class SpeakerScheduling
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
public IntVar var;
|
||||
public int start;
|
||||
|
||||
public Entry(IntVar v, int s) {
|
||||
var = v;
|
||||
start = s;
|
||||
}
|
||||
}
|
||||
|
||||
static void Solve(int first_slot) {
|
||||
Console.WriteLine("----------------------------------------------------");
|
||||
|
||||
// the slots each speaker is available
|
||||
int[][] speaker_availability = {
|
||||
new int[] { 1, 3, 4, 6, 7, 10, 12, 13, 14, 15, 16, 18, 19, 20, 21,
|
||||
22, 23, 24, 25, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 44,
|
||||
45, 46, 47, 48, 49, 50, 51, 52, 54, 55, 56, 57, 58, 59 },
|
||||
new int[] { 1, 2, 7, 8, 10, 11, 16, 17, 18, 21, 22, 23, 24, 25, 33, 34, 35, 36, 37, 38,
|
||||
39, 40, 42, 43, 44, 45, 46, 47, 48, 49, 52, 53, 54, 55, 56, 57, 58, 59, 60 },
|
||||
new int[] { 5, 6, 7, 10, 12, 14, 16, 17, 21, 22, 23, 24, 33, 35, 37, 38,
|
||||
39, 40, 41, 42, 43, 44, 45, 46, 51, 53, 55, 56, 57, 58, 59 },
|
||||
new int[] { 1, 2, 3, 4, 5, 6, 7, 11, 13, 14, 15, 16, 20, 24, 25, 33, 34, 35, 37, 38,
|
||||
39, 40, 41, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 58, 59, 60 },
|
||||
new int[] { 4, 7, 8, 9, 16, 17, 19, 20, 21, 22, 23, 24, 25, 33, 34, 35, 36,
|
||||
37, 38, 39, 40, 41, 42, 43, 49, 50, 51, 53, 55, 56, 57, 58, 59, 60 },
|
||||
new int[] { 4, 7, 9, 11, 12, 13, 14, 15, 16, 17, 18, 22, 23, 24, 33, 34,
|
||||
35, 36, 38, 39, 42, 44, 46, 48, 49, 51, 53, 54, 55, 56, 57 },
|
||||
new int[] { 1, 2, 3, 4, 5, 6, 7, 33, 34, 35, 36, 37,
|
||||
38, 39, 40, 41, 42, 54, 55, 56, 57, 58, 59, 60 },
|
||||
new int[] { 1, 3, 11, 14, 15, 16, 17, 21, 22, 23, 24, 25, 33, 35, 36, 37, 39, 40,
|
||||
41, 42, 43, 44, 45, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 },
|
||||
new int[] { 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 13, 18, 19, 20, 21,
|
||||
22, 23, 24, 25, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
|
||||
44, 45, 46, 50, 51, 52, 53, 54, 55, 56, 57, 59, 60 },
|
||||
new int[] { 24, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 45,
|
||||
49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 },
|
||||
new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18,
|
||||
19, 20, 22, 23, 24, 25, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
|
||||
44, 45, 46, 47, 48, 50, 51, 52, 53, 55, 56, 57, 58, 59, 60 },
|
||||
new int[] { 3, 4, 5, 6, 13, 15, 16, 17, 18, 19, 21, 22, 24, 25, 33, 34, 35,
|
||||
36, 37, 39, 40, 41, 42, 43, 44, 45, 47, 52, 53, 55, 57, 58, 59, 60 },
|
||||
new int[] { 4, 5, 6, 8, 11, 12, 13, 14, 17, 19, 20, 22, 23, 24, 25, 33, 34,
|
||||
35, 36, 37, 39, 40, 41, 42, 43, 47, 48, 49, 50, 51, 52, 55, 56, 57 },
|
||||
new int[] { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
20, 21, 22, 23, 24, 25, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
|
||||
45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 },
|
||||
new int[] { 12, 25, 33, 35, 36, 37, 39, 41, 42, 43, 48, 51, 52, 53, 54, 57, 59, 60 },
|
||||
new int[] { 4, 8, 16, 17, 19, 23, 25, 33, 34, 35, 37, 41, 44, 45, 47, 48, 49, 50 },
|
||||
new int[] { 3, 23, 24, 25, 33, 35, 36, 37, 38, 39, 40, 42,
|
||||
43, 44, 49, 50, 53, 54, 55, 56, 57, 58, 60 },
|
||||
new int[] { 7, 13, 19, 20, 22, 23, 24, 25, 33, 34, 35, 38, 40, 41,
|
||||
42, 44, 45, 46, 47, 48, 49, 52, 53, 54, 58, 59, 60 }
|
||||
};
|
||||
// how long each talk lasts for each speaker
|
||||
int[] durations = { 1, 2, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
|
||||
int sum_of_durations = durations.Sum();
|
||||
int number_of_speakers = durations.Length;
|
||||
|
||||
// calculate the total number of slots (maximum in the availability array)
|
||||
// (and add the max durations)
|
||||
int last_slot =
|
||||
(from s in Enumerable.Range(0, number_of_speakers) select speaker_availability[s].Max())
|
||||
.Max();
|
||||
Console.WriteLine($"Scheduling {number_of_speakers} speakers, for a total of " +
|
||||
$"{sum_of_durations} slots, during [{first_slot}..{last_slot}]");
|
||||
|
||||
CpModel model = new CpModel();
|
||||
|
||||
// We store the possible entries (var, start) for all talks filtered
|
||||
// from the duration and the speaker availability.
|
||||
List<Entry>[] entries = new List<Entry>[number_of_speakers];
|
||||
for (int speaker = 0; speaker < number_of_speakers; ++speaker) {
|
||||
entries[speaker] = new List<Entry>();
|
||||
}
|
||||
|
||||
List<IntVar>[] contributions_per_slot = new List<IntVar>[last_slot + 1];
|
||||
for (int slot = 1; slot <= last_slot; ++slot) {
|
||||
contributions_per_slot[slot] = new List<IntVar>();
|
||||
}
|
||||
|
||||
for (int speaker = 0; speaker < number_of_speakers; ++speaker) {
|
||||
List<IntVar> all_vars = new List<IntVar>();
|
||||
int duration = durations[speaker];
|
||||
// Let's filter the possible starts.
|
||||
int availability = speaker_availability[speaker].Length;
|
||||
for (int index = 0; index < availability; ++index) {
|
||||
bool ok = true;
|
||||
int slot = speaker_availability[speaker][index];
|
||||
if (slot < first_slot) {
|
||||
continue;
|
||||
public Entry(IntVar v, int s)
|
||||
{
|
||||
var = v;
|
||||
start = s;
|
||||
}
|
||||
for (int offset = 1; offset < duration; ++offset) {
|
||||
if (index + offset >= availability ||
|
||||
speaker_availability[speaker][index + offset] != slot + offset) {
|
||||
// discontinuity.
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void Solve(int first_slot)
|
||||
{
|
||||
Console.WriteLine("----------------------------------------------------");
|
||||
|
||||
// the slots each speaker is available
|
||||
int[][] speaker_availability = {
|
||||
new int[] { 1, 3, 4, 6, 7, 10, 12, 13, 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 33, 34, 35,
|
||||
36, 37, 38, 39, 40, 41, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 54, 55, 56, 57, 58, 59 },
|
||||
new int[] { 1, 2, 7, 8, 10, 11, 16, 17, 18, 21, 22, 23, 24, 25, 33, 34, 35, 36, 37, 38,
|
||||
39, 40, 42, 43, 44, 45, 46, 47, 48, 49, 52, 53, 54, 55, 56, 57, 58, 59, 60 },
|
||||
new int[] { 5, 6, 7, 10, 12, 14, 16, 17, 21, 22, 23, 24, 33, 35, 37, 38,
|
||||
39, 40, 41, 42, 43, 44, 45, 46, 51, 53, 55, 56, 57, 58, 59 },
|
||||
new int[] { 1, 2, 3, 4, 5, 6, 7, 11, 13, 14, 15, 16, 20, 24, 25, 33, 34, 35, 37, 38,
|
||||
39, 40, 41, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 58, 59, 60 },
|
||||
new int[] { 4, 7, 8, 9, 16, 17, 19, 20, 21, 22, 23, 24, 25, 33, 34, 35, 36,
|
||||
37, 38, 39, 40, 41, 42, 43, 49, 50, 51, 53, 55, 56, 57, 58, 59, 60 },
|
||||
new int[] { 4, 7, 9, 11, 12, 13, 14, 15, 16, 17, 18, 22, 23, 24, 33, 34,
|
||||
35, 36, 38, 39, 42, 44, 46, 48, 49, 51, 53, 54, 55, 56, 57 },
|
||||
new int[] { 1, 2, 3, 4, 5, 6, 7, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 54, 55, 56, 57, 58, 59, 60 },
|
||||
new int[] { 1, 3, 11, 14, 15, 16, 17, 21, 22, 23, 24, 25, 33, 35, 36, 37, 39, 40,
|
||||
41, 42, 43, 44, 45, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 },
|
||||
new int[] { 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 13, 18, 19, 20, 21, 22, 23, 24, 25, 33, 34, 35,
|
||||
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 50, 51, 52, 53, 54, 55, 56, 57, 59, 60 },
|
||||
new int[] { 24, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 45,
|
||||
49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 },
|
||||
new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18,
|
||||
19, 20, 22, 23, 24, 25, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
|
||||
44, 45, 46, 47, 48, 50, 51, 52, 53, 55, 56, 57, 58, 59, 60 },
|
||||
new int[] { 3, 4, 5, 6, 13, 15, 16, 17, 18, 19, 21, 22, 24, 25, 33, 34, 35,
|
||||
36, 37, 39, 40, 41, 42, 43, 44, 45, 47, 52, 53, 55, 57, 58, 59, 60 },
|
||||
new int[] { 4, 5, 6, 8, 11, 12, 13, 14, 17, 19, 20, 22, 23, 24, 25, 33, 34,
|
||||
35, 36, 37, 39, 40, 41, 42, 43, 47, 48, 49, 50, 51, 52, 55, 56, 57 },
|
||||
new int[] { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
20, 21, 22, 23, 24, 25, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
|
||||
45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 },
|
||||
new int[] { 12, 25, 33, 35, 36, 37, 39, 41, 42, 43, 48, 51, 52, 53, 54, 57, 59, 60 },
|
||||
new int[] { 4, 8, 16, 17, 19, 23, 25, 33, 34, 35, 37, 41, 44, 45, 47, 48, 49, 50 },
|
||||
new int[] { 3, 23, 24, 25, 33, 35, 36, 37, 38, 39, 40, 42, 43, 44, 49, 50, 53, 54, 55, 56, 57, 58, 60 },
|
||||
new int[] { 7, 13, 19, 20, 22, 23, 24, 25, 33, 34, 35, 38, 40, 41,
|
||||
42, 44, 45, 46, 47, 48, 49, 52, 53, 54, 58, 59, 60 }
|
||||
};
|
||||
// how long each talk lasts for each speaker
|
||||
int[] durations = { 1, 2, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
|
||||
int sum_of_durations = durations.Sum();
|
||||
int number_of_speakers = durations.Length;
|
||||
|
||||
// calculate the total number of slots (maximum in the availability array)
|
||||
// (and add the max durations)
|
||||
int last_slot = (from s in Enumerable.Range(0, number_of_speakers) select speaker_availability[s].Max()).Max();
|
||||
Console.WriteLine($"Scheduling {number_of_speakers} speakers, for a total of " +
|
||||
$"{sum_of_durations} slots, during [{first_slot}..{last_slot}]");
|
||||
|
||||
CpModel model = new CpModel();
|
||||
|
||||
// We store the possible entries (var, start) for all talks filtered
|
||||
// from the duration and the speaker availability.
|
||||
List<Entry>[] entries = new List<Entry>[number_of_speakers];
|
||||
for (int speaker = 0; speaker < number_of_speakers; ++speaker)
|
||||
{
|
||||
entries[speaker] = new List<Entry>();
|
||||
}
|
||||
if (ok) {
|
||||
IntVar var = model.NewBoolVar("speaker " + (speaker + 1) + " starts at " + slot);
|
||||
entries[speaker].Add(new Entry(var, slot));
|
||||
all_vars.Add(var);
|
||||
for (int offset = 0; offset < duration; ++offset) {
|
||||
contributions_per_slot[slot + offset].Add(var);
|
||||
}
|
||||
|
||||
List<IntVar>[] contributions_per_slot = new List<IntVar>[last_slot + 1];
|
||||
for (int slot = 1; slot <= last_slot; ++slot)
|
||||
{
|
||||
contributions_per_slot[slot] = new List<IntVar>();
|
||||
}
|
||||
}
|
||||
model.Add(LinearExpr.Sum(all_vars) == 1);
|
||||
}
|
||||
// Force the schedule to be consistent.
|
||||
for (int slot = first_slot; slot <= last_slot; ++slot) {
|
||||
model.Add(LinearExpr.Sum(contributions_per_slot[slot]) <= 1);
|
||||
}
|
||||
|
||||
// Creates last_slot.
|
||||
IntVar last_slot_var =
|
||||
model.NewIntVar(first_slot + sum_of_durations - 1, last_slot, "last_slot");
|
||||
for (int speaker = 0; speaker < number_of_speakers; speaker++) {
|
||||
int duration = durations[speaker];
|
||||
foreach (Entry e in entries[speaker]) {
|
||||
model.Add(last_slot_var >= e.start + duration - 1).OnlyEnforceIf(e.var);
|
||||
}
|
||||
}
|
||||
|
||||
model.Minimize(last_slot_var);
|
||||
|
||||
// Creates the solver and solve.
|
||||
CpSolver solver = new CpSolver();
|
||||
solver.StringParameters = "num_search_workers:8";
|
||||
CpSolverStatus status = solver.Solve(model);
|
||||
|
||||
if (status == CpSolverStatus.Optimal) {
|
||||
Console.WriteLine("\nLast used slot: " + solver.Value(last_slot_var));
|
||||
Console.WriteLine("Speakers (start..end):");
|
||||
for (int speaker = 0; speaker < number_of_speakers; speaker++) {
|
||||
foreach (Entry e in entries[speaker]) {
|
||||
if (solver.BooleanValue(e.var)) {
|
||||
Console.WriteLine(" - speaker {0,2}: {1,2}..{2,2}", (speaker + 1), e.start,
|
||||
(e.start + durations[speaker] - 1));
|
||||
}
|
||||
for (int speaker = 0; speaker < number_of_speakers; ++speaker)
|
||||
{
|
||||
List<IntVar> all_vars = new List<IntVar>();
|
||||
int duration = durations[speaker];
|
||||
// Let's filter the possible starts.
|
||||
int availability = speaker_availability[speaker].Length;
|
||||
for (int index = 0; index < availability; ++index)
|
||||
{
|
||||
bool ok = true;
|
||||
int slot = speaker_availability[speaker][index];
|
||||
if (slot < first_slot)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (int offset = 1; offset < duration; ++offset)
|
||||
{
|
||||
if (index + offset >= availability ||
|
||||
speaker_availability[speaker][index + offset] != slot + offset)
|
||||
{
|
||||
// discontinuity.
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ok)
|
||||
{
|
||||
IntVar var = model.NewBoolVar("speaker " + (speaker + 1) + " starts at " + slot);
|
||||
entries[speaker].Add(new Entry(var, slot));
|
||||
all_vars.Add(var);
|
||||
for (int offset = 0; offset < duration; ++offset)
|
||||
{
|
||||
contributions_per_slot[slot + offset].Add(var);
|
||||
}
|
||||
}
|
||||
}
|
||||
model.Add(LinearExpr.Sum(all_vars) == 1);
|
||||
}
|
||||
}
|
||||
// Force the schedule to be consistent.
|
||||
for (int slot = first_slot; slot <= last_slot; ++slot)
|
||||
{
|
||||
model.Add(LinearExpr.Sum(contributions_per_slot[slot]) <= 1);
|
||||
}
|
||||
|
||||
// Creates last_slot.
|
||||
IntVar last_slot_var = model.NewIntVar(first_slot + sum_of_durations - 1, last_slot, "last_slot");
|
||||
for (int speaker = 0; speaker < number_of_speakers; speaker++)
|
||||
{
|
||||
int duration = durations[speaker];
|
||||
foreach (Entry e in entries[speaker])
|
||||
{
|
||||
model.Add(last_slot_var >= e.start + duration - 1).OnlyEnforceIf(e.var);
|
||||
}
|
||||
}
|
||||
|
||||
model.Minimize(last_slot_var);
|
||||
|
||||
// Creates the solver and solve.
|
||||
CpSolver solver = new CpSolver();
|
||||
solver.StringParameters = "num_search_workers:8";
|
||||
CpSolverStatus status = solver.Solve(model);
|
||||
|
||||
if (status == CpSolverStatus.Optimal)
|
||||
{
|
||||
Console.WriteLine("\nLast used slot: " + solver.Value(last_slot_var));
|
||||
Console.WriteLine("Speakers (start..end):");
|
||||
for (int speaker = 0; speaker < number_of_speakers; speaker++)
|
||||
{
|
||||
foreach (Entry e in entries[speaker])
|
||||
{
|
||||
if (solver.BooleanValue(e.var))
|
||||
{
|
||||
Console.WriteLine(" - speaker {0,2}: {1,2}..{2,2}", (speaker + 1), e.start,
|
||||
(e.start + durations[speaker] - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Statistics.
|
||||
Console.WriteLine(solver.ResponseStats());
|
||||
}
|
||||
|
||||
// Statistics.
|
||||
Console.WriteLine(solver.ResponseStats());
|
||||
}
|
||||
public static void Main(String[] args)
|
||||
{
|
||||
int start = 1;
|
||||
if (args.Length == 1)
|
||||
{
|
||||
start = int.Parse(args[0]);
|
||||
}
|
||||
Stopwatch s = new Stopwatch();
|
||||
s.Start();
|
||||
for (int i = start; i < 40; i++)
|
||||
{
|
||||
Solve(i);
|
||||
}
|
||||
|
||||
public static void Main(String[] args) {
|
||||
int start = 1;
|
||||
if (args.Length == 1) {
|
||||
start = int.Parse(args[0]);
|
||||
s.Stop();
|
||||
Console.WriteLine("Finished in " + s.ElapsedMilliseconds + " ms");
|
||||
}
|
||||
Stopwatch s = new Stopwatch();
|
||||
s.Start();
|
||||
for (int i = start; i < 40; i++) {
|
||||
Solve(i);
|
||||
}
|
||||
|
||||
s.Stop();
|
||||
Console.WriteLine("Finished in " + s.ElapsedMilliseconds + " ms");
|
||||
}
|
||||
}
|
||||
@@ -2,170 +2,185 @@
|
||||
using System.Collections.Generic;
|
||||
using Google.OrTools.Sat;
|
||||
|
||||
class Job {
|
||||
public Job(List<Task> tasks) {
|
||||
AlternativeTasks = tasks;
|
||||
}
|
||||
public Job Successor { get; set; }
|
||||
public List<Task> AlternativeTasks { get; set; }
|
||||
class Job
|
||||
{
|
||||
public Job(List<Task> tasks)
|
||||
{
|
||||
AlternativeTasks = tasks;
|
||||
}
|
||||
public Job Successor { get; set; }
|
||||
public List<Task> AlternativeTasks { get; set; }
|
||||
}
|
||||
|
||||
class Task {
|
||||
public Task(string name, long duration, long equipment) {
|
||||
Name = name;
|
||||
Duration = duration;
|
||||
Equipment = equipment;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public long StartTime { get; set; }
|
||||
public long EndTime {
|
||||
get { return StartTime + Duration; }
|
||||
}
|
||||
public long Duration { get; set; }
|
||||
public long Equipment { get; set; }
|
||||
|
||||
public override string ToString() {
|
||||
return Name + " [ " + Equipment + " ]\tstarts: " + StartTime + " ends:" + EndTime +
|
||||
", duration: " + Duration;
|
||||
}
|
||||
}
|
||||
|
||||
class TaskSchedulingSat {
|
||||
public static List<Job> myJobList = new List<Job>();
|
||||
public static Dictionary<long, List<IntervalVar>> tasksToEquipment =
|
||||
new Dictionary<long, List<IntervalVar>>();
|
||||
public static Dictionary<string, long> taskIndexes = new Dictionary<string, long>();
|
||||
|
||||
public static void InitTaskList() {
|
||||
List<Task> taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job1Task0a", 15, 0));
|
||||
taskList.Add(new Task("Job1Task0b", 25, 1));
|
||||
taskList.Add(new Task("Job1Task0c", 10, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job1Task1a", 25, 0));
|
||||
taskList.Add(new Task("Job1Task1b", 30, 1));
|
||||
taskList.Add(new Task("Job1Task1c", 40, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job1Task2a", 20, 0));
|
||||
taskList.Add(new Task("Job1Task2b", 35, 1));
|
||||
taskList.Add(new Task("Job1Task2c", 10, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job2Task0a", 15, 0));
|
||||
taskList.Add(new Task("Job2Task0b", 25, 1));
|
||||
taskList.Add(new Task("Job2Task0c", 10, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job2Task1a", 25, 0));
|
||||
taskList.Add(new Task("Job2Task1b", 30, 1));
|
||||
taskList.Add(new Task("Job2Task1c", 40, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job2Task2a", 20, 0));
|
||||
taskList.Add(new Task("Job2Task2b", 35, 1));
|
||||
taskList.Add(new Task("Job2Task2c", 10, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job3Task0a", 10, 0));
|
||||
taskList.Add(new Task("Job3Task0b", 15, 1));
|
||||
taskList.Add(new Task("Job3Task0c", 50, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job3Task1a", 50, 0));
|
||||
taskList.Add(new Task("Job3Task1b", 10, 1));
|
||||
taskList.Add(new Task("Job3Task1c", 20, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job3Task2a", 65, 0));
|
||||
taskList.Add(new Task("Job3Task2b", 5, 1));
|
||||
taskList.Add(new Task("Job3Task2c", 15, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
myJobList[0].Successor = myJobList[1];
|
||||
myJobList[1].Successor = myJobList[2];
|
||||
myJobList[2].Successor = null;
|
||||
|
||||
myJobList[3].Successor = myJobList[4];
|
||||
myJobList[4].Successor = myJobList[5];
|
||||
myJobList[5].Successor = null;
|
||||
|
||||
myJobList[6].Successor = myJobList[7];
|
||||
myJobList[7].Successor = myJobList[8];
|
||||
myJobList[8].Successor = null;
|
||||
}
|
||||
|
||||
public static int GetTaskCount() {
|
||||
int c = 0;
|
||||
foreach (Job j in myJobList)
|
||||
foreach (Task t in j.AlternativeTasks) {
|
||||
taskIndexes[t.Name] = c;
|
||||
c++;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
public static int GetEndTaskCount() {
|
||||
int c = 0;
|
||||
foreach (Job j in myJobList)
|
||||
if (j.Successor == null)
|
||||
c += j.AlternativeTasks.Count;
|
||||
return c;
|
||||
}
|
||||
|
||||
static void Main() {
|
||||
InitTaskList();
|
||||
int taskCount = GetTaskCount();
|
||||
|
||||
CpModel model = new CpModel();
|
||||
|
||||
IntervalVar[] tasks = new IntervalVar[taskCount];
|
||||
IntVar[] taskChoosed = new IntVar[taskCount];
|
||||
IntVar[] allEnds = new IntVar[GetEndTaskCount()];
|
||||
|
||||
int endJobCounter = 0;
|
||||
foreach (Job j in myJobList) {
|
||||
IntVar[] tmp = new IntVar[j.AlternativeTasks.Count];
|
||||
int i = 0;
|
||||
foreach (Task t in j.AlternativeTasks) {
|
||||
long ti = taskIndexes[t.Name];
|
||||
taskChoosed[ti] = model.NewBoolVar(t.Name + "_choose");
|
||||
tmp[i++] = taskChoosed[ti];
|
||||
IntVar start = model.NewIntVar(0, 10000, t.Name + "_start");
|
||||
IntVar end = model.NewIntVar(0, 10000, t.Name + "_end");
|
||||
tasks[ti] = model.NewIntervalVar(start, t.Duration, end, t.Name + "_interval");
|
||||
if (j.Successor == null)
|
||||
allEnds[endJobCounter++] = end;
|
||||
if (!tasksToEquipment.ContainsKey(t.Equipment))
|
||||
tasksToEquipment[t.Equipment] = new List<IntervalVar>();
|
||||
tasksToEquipment[t.Equipment].Add(tasks[ti]);
|
||||
}
|
||||
model.Add(LinearExpr.Sum(tmp) == 1);
|
||||
class Task
|
||||
{
|
||||
public Task(string name, long duration, long equipment)
|
||||
{
|
||||
Name = name;
|
||||
Duration = duration;
|
||||
Equipment = equipment;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<long, List<IntervalVar>> pair in tasksToEquipment) {
|
||||
model.AddNoOverlap(pair.Value);
|
||||
public string Name { get; set; }
|
||||
public long StartTime { get; set; }
|
||||
public long EndTime
|
||||
{
|
||||
get {
|
||||
return StartTime + Duration;
|
||||
}
|
||||
}
|
||||
public long Duration { get; set; }
|
||||
public long Equipment { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name + " [ " + Equipment + " ]\tstarts: " + StartTime + " ends:" + EndTime + ", duration: " + Duration;
|
||||
}
|
||||
}
|
||||
|
||||
class TaskSchedulingSat
|
||||
{
|
||||
public static List<Job> myJobList = new List<Job>();
|
||||
public static Dictionary<long, List<IntervalVar>> tasksToEquipment = new Dictionary<long, List<IntervalVar>>();
|
||||
public static Dictionary<string, long> taskIndexes = new Dictionary<string, long>();
|
||||
|
||||
public static void InitTaskList()
|
||||
{
|
||||
List<Task> taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job1Task0a", 15, 0));
|
||||
taskList.Add(new Task("Job1Task0b", 25, 1));
|
||||
taskList.Add(new Task("Job1Task0c", 10, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job1Task1a", 25, 0));
|
||||
taskList.Add(new Task("Job1Task1b", 30, 1));
|
||||
taskList.Add(new Task("Job1Task1c", 40, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job1Task2a", 20, 0));
|
||||
taskList.Add(new Task("Job1Task2b", 35, 1));
|
||||
taskList.Add(new Task("Job1Task2c", 10, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job2Task0a", 15, 0));
|
||||
taskList.Add(new Task("Job2Task0b", 25, 1));
|
||||
taskList.Add(new Task("Job2Task0c", 10, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job2Task1a", 25, 0));
|
||||
taskList.Add(new Task("Job2Task1b", 30, 1));
|
||||
taskList.Add(new Task("Job2Task1c", 40, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job2Task2a", 20, 0));
|
||||
taskList.Add(new Task("Job2Task2b", 35, 1));
|
||||
taskList.Add(new Task("Job2Task2c", 10, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job3Task0a", 10, 0));
|
||||
taskList.Add(new Task("Job3Task0b", 15, 1));
|
||||
taskList.Add(new Task("Job3Task0c", 50, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job3Task1a", 50, 0));
|
||||
taskList.Add(new Task("Job3Task1b", 10, 1));
|
||||
taskList.Add(new Task("Job3Task1c", 20, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
taskList = new List<Task>();
|
||||
taskList.Add(new Task("Job3Task2a", 65, 0));
|
||||
taskList.Add(new Task("Job3Task2b", 5, 1));
|
||||
taskList.Add(new Task("Job3Task2c", 15, 2));
|
||||
myJobList.Add(new Job(taskList));
|
||||
|
||||
myJobList[0].Successor = myJobList[1];
|
||||
myJobList[1].Successor = myJobList[2];
|
||||
myJobList[2].Successor = null;
|
||||
|
||||
myJobList[3].Successor = myJobList[4];
|
||||
myJobList[4].Successor = myJobList[5];
|
||||
myJobList[5].Successor = null;
|
||||
|
||||
myJobList[6].Successor = myJobList[7];
|
||||
myJobList[7].Successor = myJobList[8];
|
||||
myJobList[8].Successor = null;
|
||||
}
|
||||
|
||||
IntVar makespan = model.NewIntVar(0, 100000, "makespan");
|
||||
model.AddMaxEquality(makespan, allEnds);
|
||||
model.Minimize(makespan);
|
||||
public static int GetTaskCount()
|
||||
{
|
||||
int c = 0;
|
||||
foreach (Job j in myJobList)
|
||||
foreach (Task t in j.AlternativeTasks)
|
||||
{
|
||||
taskIndexes[t.Name] = c;
|
||||
c++;
|
||||
}
|
||||
|
||||
// Create the solver.
|
||||
CpSolver solver = new CpSolver();
|
||||
// Solve the problem.
|
||||
solver.Solve(model);
|
||||
Console.WriteLine(solver.ResponseStats());
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
public static int GetEndTaskCount()
|
||||
{
|
||||
int c = 0;
|
||||
foreach (Job j in myJobList)
|
||||
if (j.Successor == null)
|
||||
c += j.AlternativeTasks.Count;
|
||||
return c;
|
||||
}
|
||||
|
||||
static void Main()
|
||||
{
|
||||
InitTaskList();
|
||||
int taskCount = GetTaskCount();
|
||||
|
||||
CpModel model = new CpModel();
|
||||
|
||||
IntervalVar[] tasks = new IntervalVar[taskCount];
|
||||
IntVar[] taskChoosed = new IntVar[taskCount];
|
||||
IntVar[] allEnds = new IntVar[GetEndTaskCount()];
|
||||
|
||||
int endJobCounter = 0;
|
||||
foreach (Job j in myJobList)
|
||||
{
|
||||
IntVar[] tmp = new IntVar[j.AlternativeTasks.Count];
|
||||
int i = 0;
|
||||
foreach (Task t in j.AlternativeTasks)
|
||||
{
|
||||
long ti = taskIndexes[t.Name];
|
||||
taskChoosed[ti] = model.NewBoolVar(t.Name + "_choose");
|
||||
tmp[i++] = taskChoosed[ti];
|
||||
IntVar start = model.NewIntVar(0, 10000, t.Name + "_start");
|
||||
IntVar end = model.NewIntVar(0, 10000, t.Name + "_end");
|
||||
tasks[ti] = model.NewIntervalVar(start, t.Duration, end, t.Name + "_interval");
|
||||
if (j.Successor == null)
|
||||
allEnds[endJobCounter++] = end;
|
||||
if (!tasksToEquipment.ContainsKey(t.Equipment))
|
||||
tasksToEquipment[t.Equipment] = new List<IntervalVar>();
|
||||
tasksToEquipment[t.Equipment].Add(tasks[ti]);
|
||||
}
|
||||
model.Add(LinearExpr.Sum(tmp) == 1);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<long, List<IntervalVar>> pair in tasksToEquipment)
|
||||
{
|
||||
model.AddNoOverlap(pair.Value);
|
||||
}
|
||||
|
||||
IntVar makespan = model.NewIntVar(0, 100000, "makespan");
|
||||
model.AddMaxEquality(makespan, allEnds);
|
||||
model.Minimize(makespan);
|
||||
|
||||
// Create the solver.
|
||||
CpSolver solver = new CpSolver();
|
||||
// Solve the problem.
|
||||
solver.Solve(model);
|
||||
Console.WriteLine(solver.ResponseStats());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,302 +20,329 @@ using Google.OrTools.ConstraintSolver;
|
||||
/// problem with time windows using the swig-wrapped version of the vehicle
|
||||
/// routing library in src/constraint_solver.
|
||||
/// </summary>
|
||||
public class CapacitatedVehicleRoutingProblemWithTimeWindows {
|
||||
/// <summary>
|
||||
/// A position on the map with (x, y) coordinates.
|
||||
/// </summary>
|
||||
class Position {
|
||||
public Position() {
|
||||
this.x_ = 0;
|
||||
this.y_ = 0;
|
||||
}
|
||||
|
||||
public Position(int x, int y) {
|
||||
this.x_ = x;
|
||||
this.y_ = y;
|
||||
}
|
||||
|
||||
public int x_;
|
||||
public int y_;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A time window with start/end data.
|
||||
/// </summary>
|
||||
class TimeWindow {
|
||||
public TimeWindow() {
|
||||
this.start_ = -1;
|
||||
this.end_ = -1;
|
||||
}
|
||||
|
||||
public TimeWindow(int start, int end) {
|
||||
this.start_ = start;
|
||||
this.end_ = end;
|
||||
}
|
||||
|
||||
public int start_;
|
||||
public int end_;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manhattan distance implemented as a callback. It uses an array of
|
||||
/// positions and computes the Manhattan distance between the two
|
||||
/// positions of two different indices.
|
||||
/// </summary>
|
||||
class Manhattan {
|
||||
public Manhattan(RoutingIndexManager manager, Position[] locations, int coefficient) {
|
||||
this.manager_ = manager;
|
||||
this.locations_ = locations;
|
||||
this.coefficient_ = coefficient;
|
||||
}
|
||||
|
||||
public long Call(long first_index, long second_index) {
|
||||
if (first_index >= locations_.Length || second_index >= locations_.Length) {
|
||||
return 0;
|
||||
}
|
||||
int first_node = manager_.IndexToNode(first_index);
|
||||
int second_node = manager_.IndexToNode(second_index);
|
||||
return (Math.Abs(locations_[first_node].x_ - locations_[second_node].x_) +
|
||||
Math.Abs(locations_[first_node].y_ - locations_[second_node].y_)) *
|
||||
coefficient_;
|
||||
}
|
||||
|
||||
private readonly RoutingIndexManager manager_;
|
||||
private readonly Position[] locations_;
|
||||
private readonly int coefficient_;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A callback that computes the volume of a demand stored in an
|
||||
/// integer array.
|
||||
/// </summary>
|
||||
class Demand {
|
||||
public Demand(RoutingIndexManager manager, int[] order_demands) {
|
||||
this.manager_ = manager;
|
||||
this.order_demands_ = order_demands;
|
||||
}
|
||||
|
||||
public long Call(long index) {
|
||||
if (index < order_demands_.Length) {
|
||||
int node = manager_.IndexToNode(index);
|
||||
return order_demands_[node];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private readonly RoutingIndexManager manager_;
|
||||
private readonly int[] order_demands_;
|
||||
};
|
||||
|
||||
/// Locations representing either an order location or a vehicle route
|
||||
/// start/end.
|
||||
private Position[] locations_;
|
||||
/// Quantity to be picked up for each order.
|
||||
private int[] order_demands_;
|
||||
/// Time window in which each order must be performed.
|
||||
private TimeWindow[] order_time_windows_;
|
||||
/// Penalty cost "paid" for dropping an order.
|
||||
private int[] order_penalties_;
|
||||
/// Capacity of the vehicles.
|
||||
private int vehicle_capacity_ = 0;
|
||||
/// Latest time at which each vehicle must end its tour.
|
||||
private int[] vehicle_end_time_;
|
||||
/// Cost per unit of distance of each vehicle.
|
||||
private int[] vehicle_cost_coefficients_;
|
||||
/// Vehicle start and end indices. They have to be implemented as int[] due
|
||||
/// to the available SWIG-ed interface.
|
||||
private int[] vehicle_starts_;
|
||||
private int[] vehicle_ends_;
|
||||
|
||||
/// Random number generator to produce data.
|
||||
private Random random_generator = new Random(0xBEEF);
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a capacitated vehicle routing problem with time windows.
|
||||
/// </summary>
|
||||
private CapacitatedVehicleRoutingProblemWithTimeWindows() {}
|
||||
|
||||
/// <summary>
|
||||
/// Creates order data. Location of the order is random, as well
|
||||
/// as its demand (quantity), time window and penalty. ///
|
||||
/// </summary>
|
||||
/// <param name="number_of_orders"> number of orders to build. </param>
|
||||
/// <param name="x_max"> maximum x coordinate in which orders are located.
|
||||
/// </param>
|
||||
/// <param name="y_max"> maximum y coordinate in which orders are located.
|
||||
/// </param>
|
||||
/// <param name="demand_max"> maximum quantity of a demand. </param>
|
||||
/// <param name="time_window_max"> maximum starting time of the order time
|
||||
/// window. </param>
|
||||
/// <param name="time_window_width"> duration of the order time window.
|
||||
/// </param>
|
||||
/// <param name="penalty_min"> minimum pernalty cost if order is dropped.
|
||||
/// </param>
|
||||
/// <param name="penalty_max"> maximum pernalty cost if order is dropped.
|
||||
/// </param>
|
||||
private void BuildOrders(int number_of_orders, int number_of_vehicles, int x_max, int y_max,
|
||||
int demand_max, int time_window_max, int time_window_width,
|
||||
int penalty_min, int penalty_max) {
|
||||
Console.WriteLine("Building orders.");
|
||||
locations_ = new Position[number_of_orders + 2 * number_of_vehicles];
|
||||
order_demands_ = new int[number_of_orders];
|
||||
order_time_windows_ = new TimeWindow[number_of_orders];
|
||||
order_penalties_ = new int[number_of_orders];
|
||||
for (int order = 0; order < number_of_orders; ++order) {
|
||||
locations_[order] =
|
||||
new Position(random_generator.Next(x_max + 1), random_generator.Next(y_max + 1));
|
||||
order_demands_[order] = random_generator.Next(demand_max + 1);
|
||||
int time_window_start = random_generator.Next(time_window_max + 1);
|
||||
order_time_windows_[order] =
|
||||
new TimeWindow(time_window_start, time_window_start + time_window_width);
|
||||
order_penalties_[order] = random_generator.Next(penalty_max - penalty_min + 1) + penalty_min;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates fleet data. Vehicle starting and ending locations are
|
||||
/// random, as well as vehicle costs per distance unit.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="number_of_orders"> number of orders</param>
|
||||
/// <param name="number_of_vehicles"> number of vehicles</param>
|
||||
/// <param name="x_max"> maximum x coordinate in which orders are located.
|
||||
/// </param>
|
||||
/// <param name="y_max"> maximum y coordinate in which orders are located.
|
||||
/// </param>
|
||||
/// <param name="end_time"> latest end time of a tour of a vehicle. </param>
|
||||
/// <param name="capacity"> capacity of a vehicle. </param>
|
||||
/// <param name="cost_coefficient_max"> maximum cost per distance unit of a
|
||||
/// vehicle (minimum is 1)</param>
|
||||
private void BuildFleet(int number_of_orders, int number_of_vehicles, int x_max, int y_max,
|
||||
int end_time, int capacity, int cost_coefficient_max) {
|
||||
Console.WriteLine("Building fleet.");
|
||||
vehicle_capacity_ = capacity;
|
||||
vehicle_starts_ = new int[number_of_vehicles];
|
||||
vehicle_ends_ = new int[number_of_vehicles];
|
||||
vehicle_end_time_ = new int[number_of_vehicles];
|
||||
vehicle_cost_coefficients_ = new int[number_of_vehicles];
|
||||
for (int vehicle = 0; vehicle < number_of_vehicles; ++vehicle) {
|
||||
int index = 2 * vehicle + number_of_orders;
|
||||
vehicle_starts_[vehicle] = index;
|
||||
locations_[index] =
|
||||
new Position(random_generator.Next(x_max + 1), random_generator.Next(y_max + 1));
|
||||
vehicle_ends_[vehicle] = index + 1;
|
||||
locations_[index + 1] =
|
||||
new Position(random_generator.Next(x_max + 1), random_generator.Next(y_max + 1));
|
||||
vehicle_end_time_[vehicle] = end_time;
|
||||
vehicle_cost_coefficients_[vehicle] = random_generator.Next(cost_coefficient_max) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Solves the current routing problem.
|
||||
/// </summary>
|
||||
private void Solve(int number_of_orders, int number_of_vehicles) {
|
||||
Console.WriteLine("Creating model with " + number_of_orders + " orders and " +
|
||||
number_of_vehicles + " vehicles.");
|
||||
// Finalizing model
|
||||
int number_of_locations = locations_.Length;
|
||||
|
||||
RoutingIndexManager manager = new RoutingIndexManager(number_of_locations, number_of_vehicles,
|
||||
vehicle_starts_, vehicle_ends_);
|
||||
RoutingModel model = new RoutingModel(manager);
|
||||
|
||||
// Setting up dimensions
|
||||
const int big_number = 100000;
|
||||
Manhattan manhattan_callback = new Manhattan(manager, locations_, 1);
|
||||
model.AddDimension(model.RegisterTransitCallback(manhattan_callback.Call), big_number,
|
||||
big_number, false, "time");
|
||||
RoutingDimension time_dimension = model.GetDimensionOrDie("time");
|
||||
|
||||
Demand demand_callback = new Demand(manager, order_demands_);
|
||||
model.AddDimension(model.RegisterUnaryTransitCallback(demand_callback.Call), 0,
|
||||
vehicle_capacity_, true, "capacity");
|
||||
RoutingDimension capacity_dimension = model.GetDimensionOrDie("capacity");
|
||||
|
||||
// Setting up vehicles
|
||||
Manhattan[] cost_callbacks = new Manhattan[number_of_vehicles];
|
||||
for (int vehicle = 0; vehicle < number_of_vehicles; ++vehicle) {
|
||||
int cost_coefficient = vehicle_cost_coefficients_[vehicle];
|
||||
Manhattan manhattan_cost_callback = new Manhattan(manager, locations_, cost_coefficient);
|
||||
cost_callbacks[vehicle] = manhattan_cost_callback;
|
||||
int manhattan_cost_index = model.RegisterTransitCallback(manhattan_cost_callback.Call);
|
||||
model.SetArcCostEvaluatorOfVehicle(manhattan_cost_index, vehicle);
|
||||
time_dimension.CumulVar(model.End(vehicle)).SetMax(vehicle_end_time_[vehicle]);
|
||||
}
|
||||
|
||||
// Setting up orders
|
||||
for (int order = 0; order < number_of_orders; ++order) {
|
||||
time_dimension.CumulVar(order).SetRange(order_time_windows_[order].start_,
|
||||
order_time_windows_[order].end_);
|
||||
long[] orders = { manager.NodeToIndex(order) };
|
||||
model.AddDisjunction(orders, order_penalties_[order]);
|
||||
}
|
||||
|
||||
// Solving
|
||||
RoutingSearchParameters search_parameters =
|
||||
operations_research_constraint_solver.DefaultRoutingSearchParameters();
|
||||
search_parameters.FirstSolutionStrategy = FirstSolutionStrategy.Types.Value.AllUnperformed;
|
||||
|
||||
Console.WriteLine("Search...");
|
||||
Assignment solution = model.SolveWithParameters(search_parameters);
|
||||
|
||||
if (solution != null) {
|
||||
String output = "Total cost: " + solution.ObjectiveValue() + "\n";
|
||||
// Dropped orders
|
||||
String dropped = "";
|
||||
for (int order = 0; order < number_of_orders; ++order) {
|
||||
if (solution.Value(model.NextVar(order)) == order) {
|
||||
dropped += " " + order;
|
||||
public class CapacitatedVehicleRoutingProblemWithTimeWindows
|
||||
{
|
||||
/// <summary>
|
||||
/// A position on the map with (x, y) coordinates.
|
||||
/// </summary>
|
||||
class Position
|
||||
{
|
||||
public Position()
|
||||
{
|
||||
this.x_ = 0;
|
||||
this.y_ = 0;
|
||||
}
|
||||
}
|
||||
if (dropped.Length > 0) {
|
||||
output += "Dropped orders:" + dropped + "\n";
|
||||
}
|
||||
// Routes
|
||||
for (int vehicle = 0; vehicle < number_of_vehicles; ++vehicle) {
|
||||
String route = "Vehicle " + vehicle + ": ";
|
||||
long order = model.Start(vehicle);
|
||||
if (model.IsEnd(solution.Value(model.NextVar(order)))) {
|
||||
route += "Empty";
|
||||
} else {
|
||||
for (; !model.IsEnd(order); order = solution.Value(model.NextVar(order))) {
|
||||
IntVar local_load = capacity_dimension.CumulVar(order);
|
||||
IntVar local_time = time_dimension.CumulVar(order);
|
||||
route += order + " Load(" + solution.Value(local_load) + ") " + "Time(" +
|
||||
solution.Min(local_time) + ", " + solution.Max(local_time) + ") -> ";
|
||||
}
|
||||
IntVar load = capacity_dimension.CumulVar(order);
|
||||
IntVar time = time_dimension.CumulVar(order);
|
||||
route += order + " Load(" + solution.Value(load) + ") " + "Time(" + solution.Min(time) +
|
||||
", " + solution.Max(time) + ")";
|
||||
|
||||
public Position(int x, int y)
|
||||
{
|
||||
this.x_ = x;
|
||||
this.y_ = y;
|
||||
}
|
||||
output += route + "\n";
|
||||
}
|
||||
Console.WriteLine(output);
|
||||
|
||||
public int x_;
|
||||
public int y_;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Main(String[] args) {
|
||||
CapacitatedVehicleRoutingProblemWithTimeWindows problem =
|
||||
new CapacitatedVehicleRoutingProblemWithTimeWindows();
|
||||
int x_max = 20;
|
||||
int y_max = 20;
|
||||
int demand_max = 3;
|
||||
int time_window_max = 24 * 60;
|
||||
int time_window_width = 4 * 60;
|
||||
int penalty_min = 50;
|
||||
int penalty_max = 100;
|
||||
int end_time = 24 * 60;
|
||||
int cost_coefficient_max = 3;
|
||||
/// <summary>
|
||||
/// A time window with start/end data.
|
||||
/// </summary>
|
||||
class TimeWindow
|
||||
{
|
||||
public TimeWindow()
|
||||
{
|
||||
this.start_ = -1;
|
||||
this.end_ = -1;
|
||||
}
|
||||
|
||||
int orders = 100;
|
||||
int vehicles = 20;
|
||||
int capacity = 50;
|
||||
public TimeWindow(int start, int end)
|
||||
{
|
||||
this.start_ = start;
|
||||
this.end_ = end;
|
||||
}
|
||||
|
||||
problem.BuildOrders(orders, vehicles, x_max, y_max, demand_max, time_window_max,
|
||||
time_window_width, penalty_min, penalty_max);
|
||||
problem.BuildFleet(orders, vehicles, x_max, y_max, end_time, capacity, cost_coefficient_max);
|
||||
problem.Solve(orders, vehicles);
|
||||
}
|
||||
public int start_;
|
||||
public int end_;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manhattan distance implemented as a callback. It uses an array of
|
||||
/// positions and computes the Manhattan distance between the two
|
||||
/// positions of two different indices.
|
||||
/// </summary>
|
||||
class Manhattan
|
||||
{
|
||||
public Manhattan(RoutingIndexManager manager, Position[] locations, int coefficient)
|
||||
{
|
||||
this.manager_ = manager;
|
||||
this.locations_ = locations;
|
||||
this.coefficient_ = coefficient;
|
||||
}
|
||||
|
||||
public long Call(long first_index, long second_index)
|
||||
{
|
||||
if (first_index >= locations_.Length || second_index >= locations_.Length)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int first_node = manager_.IndexToNode(first_index);
|
||||
int second_node = manager_.IndexToNode(second_index);
|
||||
return (Math.Abs(locations_[first_node].x_ - locations_[second_node].x_) +
|
||||
Math.Abs(locations_[first_node].y_ - locations_[second_node].y_)) *
|
||||
coefficient_;
|
||||
}
|
||||
|
||||
private readonly RoutingIndexManager manager_;
|
||||
private readonly Position[] locations_;
|
||||
private readonly int coefficient_;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A callback that computes the volume of a demand stored in an
|
||||
/// integer array.
|
||||
/// </summary>
|
||||
class Demand
|
||||
{
|
||||
public Demand(RoutingIndexManager manager, int[] order_demands)
|
||||
{
|
||||
this.manager_ = manager;
|
||||
this.order_demands_ = order_demands;
|
||||
}
|
||||
|
||||
public long Call(long index)
|
||||
{
|
||||
if (index < order_demands_.Length)
|
||||
{
|
||||
int node = manager_.IndexToNode(index);
|
||||
return order_demands_[node];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private readonly RoutingIndexManager manager_;
|
||||
private readonly int[] order_demands_;
|
||||
};
|
||||
|
||||
/// Locations representing either an order location or a vehicle route
|
||||
/// start/end.
|
||||
private Position[] locations_;
|
||||
/// Quantity to be picked up for each order.
|
||||
private int[] order_demands_;
|
||||
/// Time window in which each order must be performed.
|
||||
private TimeWindow[] order_time_windows_;
|
||||
/// Penalty cost "paid" for dropping an order.
|
||||
private int[] order_penalties_;
|
||||
/// Capacity of the vehicles.
|
||||
private int vehicle_capacity_ = 0;
|
||||
/// Latest time at which each vehicle must end its tour.
|
||||
private int[] vehicle_end_time_;
|
||||
/// Cost per unit of distance of each vehicle.
|
||||
private int[] vehicle_cost_coefficients_;
|
||||
/// Vehicle start and end indices. They have to be implemented as int[] due
|
||||
/// to the available SWIG-ed interface.
|
||||
private int[] vehicle_starts_;
|
||||
private int[] vehicle_ends_;
|
||||
|
||||
/// Random number generator to produce data.
|
||||
private Random random_generator = new Random(0xBEEF);
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a capacitated vehicle routing problem with time windows.
|
||||
/// </summary>
|
||||
private CapacitatedVehicleRoutingProblemWithTimeWindows()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates order data. Location of the order is random, as well
|
||||
/// as its demand (quantity), time window and penalty. ///
|
||||
/// </summary>
|
||||
/// <param name="number_of_orders"> number of orders to build. </param>
|
||||
/// <param name="x_max"> maximum x coordinate in which orders are located.
|
||||
/// </param>
|
||||
/// <param name="y_max"> maximum y coordinate in which orders are located.
|
||||
/// </param>
|
||||
/// <param name="demand_max"> maximum quantity of a demand. </param>
|
||||
/// <param name="time_window_max"> maximum starting time of the order time
|
||||
/// window. </param>
|
||||
/// <param name="time_window_width"> duration of the order time window.
|
||||
/// </param>
|
||||
/// <param name="penalty_min"> minimum pernalty cost if order is dropped.
|
||||
/// </param>
|
||||
/// <param name="penalty_max"> maximum pernalty cost if order is dropped.
|
||||
/// </param>
|
||||
private void BuildOrders(int number_of_orders, int number_of_vehicles, int x_max, int y_max, int demand_max,
|
||||
int time_window_max, int time_window_width, int penalty_min, int penalty_max)
|
||||
{
|
||||
Console.WriteLine("Building orders.");
|
||||
locations_ = new Position[number_of_orders + 2 * number_of_vehicles];
|
||||
order_demands_ = new int[number_of_orders];
|
||||
order_time_windows_ = new TimeWindow[number_of_orders];
|
||||
order_penalties_ = new int[number_of_orders];
|
||||
for (int order = 0; order < number_of_orders; ++order)
|
||||
{
|
||||
locations_[order] = new Position(random_generator.Next(x_max + 1), random_generator.Next(y_max + 1));
|
||||
order_demands_[order] = random_generator.Next(demand_max + 1);
|
||||
int time_window_start = random_generator.Next(time_window_max + 1);
|
||||
order_time_windows_[order] = new TimeWindow(time_window_start, time_window_start + time_window_width);
|
||||
order_penalties_[order] = random_generator.Next(penalty_max - penalty_min + 1) + penalty_min;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates fleet data. Vehicle starting and ending locations are
|
||||
/// random, as well as vehicle costs per distance unit.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="number_of_orders"> number of orders</param>
|
||||
/// <param name="number_of_vehicles"> number of vehicles</param>
|
||||
/// <param name="x_max"> maximum x coordinate in which orders are located.
|
||||
/// </param>
|
||||
/// <param name="y_max"> maximum y coordinate in which orders are located.
|
||||
/// </param>
|
||||
/// <param name="end_time"> latest end time of a tour of a vehicle. </param>
|
||||
/// <param name="capacity"> capacity of a vehicle. </param>
|
||||
/// <param name="cost_coefficient_max"> maximum cost per distance unit of a
|
||||
/// vehicle (minimum is 1)</param>
|
||||
private void BuildFleet(int number_of_orders, int number_of_vehicles, int x_max, int y_max, int end_time,
|
||||
int capacity, int cost_coefficient_max)
|
||||
{
|
||||
Console.WriteLine("Building fleet.");
|
||||
vehicle_capacity_ = capacity;
|
||||
vehicle_starts_ = new int[number_of_vehicles];
|
||||
vehicle_ends_ = new int[number_of_vehicles];
|
||||
vehicle_end_time_ = new int[number_of_vehicles];
|
||||
vehicle_cost_coefficients_ = new int[number_of_vehicles];
|
||||
for (int vehicle = 0; vehicle < number_of_vehicles; ++vehicle)
|
||||
{
|
||||
int index = 2 * vehicle + number_of_orders;
|
||||
vehicle_starts_[vehicle] = index;
|
||||
locations_[index] = new Position(random_generator.Next(x_max + 1), random_generator.Next(y_max + 1));
|
||||
vehicle_ends_[vehicle] = index + 1;
|
||||
locations_[index + 1] = new Position(random_generator.Next(x_max + 1), random_generator.Next(y_max + 1));
|
||||
vehicle_end_time_[vehicle] = end_time;
|
||||
vehicle_cost_coefficients_[vehicle] = random_generator.Next(cost_coefficient_max) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Solves the current routing problem.
|
||||
/// </summary>
|
||||
private void Solve(int number_of_orders, int number_of_vehicles)
|
||||
{
|
||||
Console.WriteLine("Creating model with " + number_of_orders + " orders and " + number_of_vehicles +
|
||||
" vehicles.");
|
||||
// Finalizing model
|
||||
int number_of_locations = locations_.Length;
|
||||
|
||||
RoutingIndexManager manager =
|
||||
new RoutingIndexManager(number_of_locations, number_of_vehicles, vehicle_starts_, vehicle_ends_);
|
||||
RoutingModel model = new RoutingModel(manager);
|
||||
|
||||
// Setting up dimensions
|
||||
const int big_number = 100000;
|
||||
Manhattan manhattan_callback = new Manhattan(manager, locations_, 1);
|
||||
model.AddDimension(model.RegisterTransitCallback(manhattan_callback.Call), big_number, big_number, false,
|
||||
"time");
|
||||
RoutingDimension time_dimension = model.GetDimensionOrDie("time");
|
||||
|
||||
Demand demand_callback = new Demand(manager, order_demands_);
|
||||
model.AddDimension(model.RegisterUnaryTransitCallback(demand_callback.Call), 0, vehicle_capacity_, true,
|
||||
"capacity");
|
||||
RoutingDimension capacity_dimension = model.GetDimensionOrDie("capacity");
|
||||
|
||||
// Setting up vehicles
|
||||
Manhattan[] cost_callbacks = new Manhattan[number_of_vehicles];
|
||||
for (int vehicle = 0; vehicle < number_of_vehicles; ++vehicle)
|
||||
{
|
||||
int cost_coefficient = vehicle_cost_coefficients_[vehicle];
|
||||
Manhattan manhattan_cost_callback = new Manhattan(manager, locations_, cost_coefficient);
|
||||
cost_callbacks[vehicle] = manhattan_cost_callback;
|
||||
int manhattan_cost_index = model.RegisterTransitCallback(manhattan_cost_callback.Call);
|
||||
model.SetArcCostEvaluatorOfVehicle(manhattan_cost_index, vehicle);
|
||||
time_dimension.CumulVar(model.End(vehicle)).SetMax(vehicle_end_time_[vehicle]);
|
||||
}
|
||||
|
||||
// Setting up orders
|
||||
for (int order = 0; order < number_of_orders; ++order)
|
||||
{
|
||||
time_dimension.CumulVar(order).SetRange(order_time_windows_[order].start_, order_time_windows_[order].end_);
|
||||
long[] orders = { manager.NodeToIndex(order) };
|
||||
model.AddDisjunction(orders, order_penalties_[order]);
|
||||
}
|
||||
|
||||
// Solving
|
||||
RoutingSearchParameters search_parameters =
|
||||
operations_research_constraint_solver.DefaultRoutingSearchParameters();
|
||||
search_parameters.FirstSolutionStrategy = FirstSolutionStrategy.Types.Value.AllUnperformed;
|
||||
|
||||
Console.WriteLine("Search...");
|
||||
Assignment solution = model.SolveWithParameters(search_parameters);
|
||||
|
||||
if (solution != null)
|
||||
{
|
||||
String output = "Total cost: " + solution.ObjectiveValue() + "\n";
|
||||
// Dropped orders
|
||||
String dropped = "";
|
||||
for (int order = 0; order < number_of_orders; ++order)
|
||||
{
|
||||
if (solution.Value(model.NextVar(order)) == order)
|
||||
{
|
||||
dropped += " " + order;
|
||||
}
|
||||
}
|
||||
if (dropped.Length > 0)
|
||||
{
|
||||
output += "Dropped orders:" + dropped + "\n";
|
||||
}
|
||||
// Routes
|
||||
for (int vehicle = 0; vehicle < number_of_vehicles; ++vehicle)
|
||||
{
|
||||
String route = "Vehicle " + vehicle + ": ";
|
||||
long order = model.Start(vehicle);
|
||||
if (model.IsEnd(solution.Value(model.NextVar(order))))
|
||||
{
|
||||
route += "Empty";
|
||||
}
|
||||
else
|
||||
{
|
||||
for (; !model.IsEnd(order); order = solution.Value(model.NextVar(order)))
|
||||
{
|
||||
IntVar local_load = capacity_dimension.CumulVar(order);
|
||||
IntVar local_time = time_dimension.CumulVar(order);
|
||||
route += order + " Load(" + solution.Value(local_load) + ") " + "Time(" +
|
||||
solution.Min(local_time) + ", " + solution.Max(local_time) + ") -> ";
|
||||
}
|
||||
IntVar load = capacity_dimension.CumulVar(order);
|
||||
IntVar time = time_dimension.CumulVar(order);
|
||||
route += order + " Load(" + solution.Value(load) + ") " + "Time(" + solution.Min(time) + ", " +
|
||||
solution.Max(time) + ")";
|
||||
}
|
||||
output += route + "\n";
|
||||
}
|
||||
Console.WriteLine(output);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Main(String[] args)
|
||||
{
|
||||
CapacitatedVehicleRoutingProblemWithTimeWindows problem = new CapacitatedVehicleRoutingProblemWithTimeWindows();
|
||||
int x_max = 20;
|
||||
int y_max = 20;
|
||||
int demand_max = 3;
|
||||
int time_window_max = 24 * 60;
|
||||
int time_window_width = 4 * 60;
|
||||
int penalty_min = 50;
|
||||
int penalty_max = 100;
|
||||
int end_time = 24 * 60;
|
||||
int cost_coefficient_max = 3;
|
||||
|
||||
int orders = 100;
|
||||
int vehicles = 20;
|
||||
int capacity = 50;
|
||||
|
||||
problem.BuildOrders(orders, vehicles, x_max, y_max, demand_max, time_window_max, time_window_width, penalty_min,
|
||||
penalty_max);
|
||||
problem.BuildFleet(orders, vehicles, x_max, y_max, end_time, capacity, cost_coefficient_max);
|
||||
problem.Solve(orders, vehicles);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,76 +14,88 @@
|
||||
using System;
|
||||
using Google.OrTools.Graph;
|
||||
|
||||
public class CsFlow {
|
||||
private static void SolveMaxFlow() {
|
||||
Console.WriteLine("Max Flow Problem");
|
||||
int numNodes = 6;
|
||||
int numArcs = 9;
|
||||
int[] tails = { 0, 0, 0, 0, 1, 2, 3, 3, 4 };
|
||||
int[] heads = { 1, 2, 3, 4, 3, 4, 4, 5, 5 };
|
||||
int[] capacities = { 5, 8, 5, 3, 4, 5, 6, 6, 4 };
|
||||
int[] expectedFlows = { 4, 4, 2, 0, 4, 4, 0, 6, 4 };
|
||||
int expectedTotalFlow = 10;
|
||||
MaxFlow maxFlow = new MaxFlow();
|
||||
for (int i = 0; i < numArcs; ++i) {
|
||||
int arc = maxFlow.AddArcWithCapacity(tails[i], heads[i], capacities[i]);
|
||||
if (arc != i)
|
||||
throw new Exception("Internal error");
|
||||
public class CsFlow
|
||||
{
|
||||
private static void SolveMaxFlow()
|
||||
{
|
||||
Console.WriteLine("Max Flow Problem");
|
||||
int numNodes = 6;
|
||||
int numArcs = 9;
|
||||
int[] tails = { 0, 0, 0, 0, 1, 2, 3, 3, 4 };
|
||||
int[] heads = { 1, 2, 3, 4, 3, 4, 4, 5, 5 };
|
||||
int[] capacities = { 5, 8, 5, 3, 4, 5, 6, 6, 4 };
|
||||
int[] expectedFlows = { 4, 4, 2, 0, 4, 4, 0, 6, 4 };
|
||||
int expectedTotalFlow = 10;
|
||||
MaxFlow maxFlow = new MaxFlow();
|
||||
for (int i = 0; i < numArcs; ++i)
|
||||
{
|
||||
int arc = maxFlow.AddArcWithCapacity(tails[i], heads[i], capacities[i]);
|
||||
if (arc != i)
|
||||
throw new Exception("Internal error");
|
||||
}
|
||||
int source = 0;
|
||||
int sink = numNodes - 1;
|
||||
Console.WriteLine("Solving max flow with " + numNodes + " nodes, and " + numArcs + " arcs, source=" + source +
|
||||
", sink=" + sink);
|
||||
MaxFlow.Status solveStatus = maxFlow.Solve(source, sink);
|
||||
if (solveStatus == MaxFlow.Status.OPTIMAL)
|
||||
{
|
||||
long totalFlow = maxFlow.OptimalFlow();
|
||||
Console.WriteLine("total computed flow " + totalFlow + ", expected = " + expectedTotalFlow);
|
||||
for (int i = 0; i < numArcs; ++i)
|
||||
{
|
||||
Console.WriteLine("Arc " + i + " (" + maxFlow.Head(i) + " -> " + maxFlow.Tail(i) +
|
||||
"), capacity = " + maxFlow.Capacity(i) + ") computed = " + maxFlow.Flow(i) +
|
||||
", expected = " + expectedFlows[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Solving the max flow problem failed. Solver status: " + solveStatus);
|
||||
}
|
||||
}
|
||||
int source = 0;
|
||||
int sink = numNodes - 1;
|
||||
Console.WriteLine("Solving max flow with " + numNodes + " nodes, and " + numArcs +
|
||||
" arcs, source=" + source + ", sink=" + sink);
|
||||
MaxFlow.Status solveStatus = maxFlow.Solve(source, sink);
|
||||
if (solveStatus == MaxFlow.Status.OPTIMAL) {
|
||||
long totalFlow = maxFlow.OptimalFlow();
|
||||
Console.WriteLine("total computed flow " + totalFlow + ", expected = " + expectedTotalFlow);
|
||||
for (int i = 0; i < numArcs; ++i) {
|
||||
Console.WriteLine("Arc " + i + " (" + maxFlow.Head(i) + " -> " + maxFlow.Tail(i) +
|
||||
"), capacity = " + maxFlow.Capacity(i) +
|
||||
") computed = " + maxFlow.Flow(i) + ", expected = " + expectedFlows[i]);
|
||||
}
|
||||
} else {
|
||||
Console.WriteLine("Solving the max flow problem failed. Solver status: " + solveStatus);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SolveMinCostFlow() {
|
||||
Console.WriteLine("Min Cost Flow Problem");
|
||||
int numSources = 4;
|
||||
int numTargets = 4;
|
||||
int[,] costs = {
|
||||
{ 90, 75, 75, 80 }, { 35, 85, 55, 65 }, { 125, 95, 90, 105 }, { 45, 110, 95, 115 }
|
||||
};
|
||||
int expectedCost = 275;
|
||||
MinCostFlow minCostFlow = new MinCostFlow();
|
||||
for (int source = 0; source < numSources; ++source) {
|
||||
for (int target = 0; target < numTargets; ++target) {
|
||||
minCostFlow.AddArcWithCapacityAndUnitCost(source, /*target=*/numSources + target,
|
||||
/*capacity=*/1,
|
||||
/*flow unit cost=*/costs[source, target]);
|
||||
}
|
||||
private static void SolveMinCostFlow()
|
||||
{
|
||||
Console.WriteLine("Min Cost Flow Problem");
|
||||
int numSources = 4;
|
||||
int numTargets = 4;
|
||||
int[,] costs = { { 90, 75, 75, 80 }, { 35, 85, 55, 65 }, { 125, 95, 90, 105 }, { 45, 110, 95, 115 } };
|
||||
int expectedCost = 275;
|
||||
MinCostFlow minCostFlow = new MinCostFlow();
|
||||
for (int source = 0; source < numSources; ++source)
|
||||
{
|
||||
for (int target = 0; target < numTargets; ++target)
|
||||
{
|
||||
minCostFlow.AddArcWithCapacityAndUnitCost(source, /*target=*/numSources + target,
|
||||
/*capacity=*/1,
|
||||
/*flow unit cost=*/costs[source, target]);
|
||||
}
|
||||
}
|
||||
for (int source = 0; source < numSources; ++source)
|
||||
{
|
||||
minCostFlow.SetNodeSupply(source, 1);
|
||||
}
|
||||
for (int target = 0; target < numTargets; ++target)
|
||||
{
|
||||
minCostFlow.SetNodeSupply(numSources + target, -1);
|
||||
}
|
||||
Console.WriteLine("Solving min cost flow with " + numSources + " sources, and " + numTargets + " targets.");
|
||||
MinCostFlow.Status solveStatus = minCostFlow.Solve();
|
||||
if (solveStatus == MinCostFlow.Status.OPTIMAL)
|
||||
{
|
||||
Console.WriteLine("total computed flow cost = " + minCostFlow.OptimalCost() +
|
||||
", expected = " + expectedCost);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Solving the min cost flow problem failed." + " Solver status: " + solveStatus);
|
||||
}
|
||||
}
|
||||
for (int source = 0; source < numSources; ++source) {
|
||||
minCostFlow.SetNodeSupply(source, 1);
|
||||
}
|
||||
for (int target = 0; target < numTargets; ++target) {
|
||||
minCostFlow.SetNodeSupply(numSources + target, -1);
|
||||
}
|
||||
Console.WriteLine("Solving min cost flow with " + numSources + " sources, and " + numTargets +
|
||||
" targets.");
|
||||
MinCostFlow.Status solveStatus = minCostFlow.Solve();
|
||||
if (solveStatus == MinCostFlow.Status.OPTIMAL) {
|
||||
Console.WriteLine("total computed flow cost = " + minCostFlow.OptimalCost() +
|
||||
", expected = " + expectedCost);
|
||||
} else {
|
||||
Console.WriteLine("Solving the min cost flow problem failed." +
|
||||
" Solver status: " + solveStatus);
|
||||
}
|
||||
}
|
||||
|
||||
static void Main() {
|
||||
SolveMaxFlow();
|
||||
SolveMinCostFlow();
|
||||
}
|
||||
static void Main()
|
||||
{
|
||||
SolveMaxFlow();
|
||||
SolveMinCostFlow();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,103 +14,111 @@
|
||||
using System;
|
||||
using Google.OrTools.LinearSolver;
|
||||
|
||||
public class CsIntegerProgramming {
|
||||
private static void RunIntegerProgrammingExample(String solverType) {
|
||||
Solver solver = Solver.CreateSolver(solverType);
|
||||
if (solver == null) {
|
||||
Console.WriteLine("Could not create solver " + solverType);
|
||||
return;
|
||||
}
|
||||
// x1 and x2 are integer non-negative variables.
|
||||
Variable x1 = solver.MakeIntVar(0.0, double.PositiveInfinity, "x1");
|
||||
Variable x2 = solver.MakeIntVar(0.0, double.PositiveInfinity, "x2");
|
||||
public class CsIntegerProgramming
|
||||
{
|
||||
private static void RunIntegerProgrammingExample(String solverType)
|
||||
{
|
||||
Solver solver = Solver.CreateSolver(solverType);
|
||||
if (solver == null)
|
||||
{
|
||||
Console.WriteLine("Could not create solver " + solverType);
|
||||
return;
|
||||
}
|
||||
// x1 and x2 are integer non-negative variables.
|
||||
Variable x1 = solver.MakeIntVar(0.0, double.PositiveInfinity, "x1");
|
||||
Variable x2 = solver.MakeIntVar(0.0, double.PositiveInfinity, "x2");
|
||||
|
||||
// Minimize x1 + 2 * x2.
|
||||
Objective objective = solver.Objective();
|
||||
objective.SetMinimization();
|
||||
objective.SetCoefficient(x1, 1);
|
||||
objective.SetCoefficient(x2, 2);
|
||||
// Minimize x1 + 2 * x2.
|
||||
Objective objective = solver.Objective();
|
||||
objective.SetMinimization();
|
||||
objective.SetCoefficient(x1, 1);
|
||||
objective.SetCoefficient(x2, 2);
|
||||
|
||||
// 2 * x2 + 3 * x1 >= 17.
|
||||
Constraint ct = solver.MakeConstraint(17, double.PositiveInfinity);
|
||||
ct.SetCoefficient(x1, 3);
|
||||
ct.SetCoefficient(x2, 2);
|
||||
// 2 * x2 + 3 * x1 >= 17.
|
||||
Constraint ct = solver.MakeConstraint(17, double.PositiveInfinity);
|
||||
ct.SetCoefficient(x1, 3);
|
||||
ct.SetCoefficient(x2, 2);
|
||||
|
||||
Solver.ResultStatus resultStatus = solver.Solve();
|
||||
Solver.ResultStatus resultStatus = solver.Solve();
|
||||
|
||||
// Check that the problem has an optimal solution.
|
||||
if (resultStatus != Solver.ResultStatus.OPTIMAL) {
|
||||
Console.WriteLine("The problem does not have an optimal solution!");
|
||||
return;
|
||||
// Check that the problem has an optimal solution.
|
||||
if (resultStatus != Solver.ResultStatus.OPTIMAL)
|
||||
{
|
||||
Console.WriteLine("The problem does not have an optimal solution!");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("Problem solved in " + solver.WallTime() + " milliseconds");
|
||||
|
||||
// The objective value of the solution.
|
||||
Console.WriteLine("Optimal objective value = " + objective.Value());
|
||||
|
||||
// The value of each variable in the solution.
|
||||
Console.WriteLine("x1 = " + x1.SolutionValue());
|
||||
Console.WriteLine("x2 = " + x2.SolutionValue());
|
||||
|
||||
Console.WriteLine("Advanced usage:");
|
||||
Console.WriteLine("Problem solved in " + solver.Nodes() + " branch-and-bound nodes");
|
||||
}
|
||||
|
||||
Console.WriteLine("Problem solved in " + solver.WallTime() + " milliseconds");
|
||||
private static void RunIntegerProgrammingExampleNaturalApi(String solverType)
|
||||
{
|
||||
Solver solver = Solver.CreateSolver(solverType);
|
||||
if (solver == null)
|
||||
{
|
||||
Console.WriteLine("Could not create solver " + solverType);
|
||||
return;
|
||||
}
|
||||
// x1 and x2 are integer non-negative variables.
|
||||
Variable x1 = solver.MakeIntVar(0.0, double.PositiveInfinity, "x1");
|
||||
Variable x2 = solver.MakeIntVar(0.0, double.PositiveInfinity, "x2");
|
||||
|
||||
// The objective value of the solution.
|
||||
Console.WriteLine("Optimal objective value = " + objective.Value());
|
||||
solver.Minimize(x1 + 2 * x2);
|
||||
solver.Add(2 * x2 + 3 * x1 >= 17);
|
||||
|
||||
// The value of each variable in the solution.
|
||||
Console.WriteLine("x1 = " + x1.SolutionValue());
|
||||
Console.WriteLine("x2 = " + x2.SolutionValue());
|
||||
Solver.ResultStatus resultStatus = solver.Solve();
|
||||
|
||||
Console.WriteLine("Advanced usage:");
|
||||
Console.WriteLine("Problem solved in " + solver.Nodes() + " branch-and-bound nodes");
|
||||
}
|
||||
// Check that the problem has an optimal solution.
|
||||
if (resultStatus != Solver.ResultStatus.OPTIMAL)
|
||||
{
|
||||
Console.WriteLine("The problem does not have an optimal solution!");
|
||||
return;
|
||||
}
|
||||
|
||||
private static void RunIntegerProgrammingExampleNaturalApi(String solverType) {
|
||||
Solver solver = Solver.CreateSolver(solverType);
|
||||
if (solver == null) {
|
||||
Console.WriteLine("Could not create solver " + solverType);
|
||||
return;
|
||||
}
|
||||
// x1 and x2 are integer non-negative variables.
|
||||
Variable x1 = solver.MakeIntVar(0.0, double.PositiveInfinity, "x1");
|
||||
Variable x2 = solver.MakeIntVar(0.0, double.PositiveInfinity, "x2");
|
||||
Console.WriteLine("Problem solved in " + solver.WallTime() + " milliseconds");
|
||||
|
||||
solver.Minimize(x1 + 2 * x2);
|
||||
solver.Add(2 * x2 + 3 * x1 >= 17);
|
||||
// The objective value of the solution.
|
||||
Console.WriteLine("Optimal objective value = " + solver.Objective().Value());
|
||||
|
||||
Solver.ResultStatus resultStatus = solver.Solve();
|
||||
// The value of each variable in the solution.
|
||||
Console.WriteLine("x1 = " + x1.SolutionValue());
|
||||
Console.WriteLine("x2 = " + x2.SolutionValue());
|
||||
|
||||
// Check that the problem has an optimal solution.
|
||||
if (resultStatus != Solver.ResultStatus.OPTIMAL) {
|
||||
Console.WriteLine("The problem does not have an optimal solution!");
|
||||
return;
|
||||
Console.WriteLine("Advanced usage:");
|
||||
Console.WriteLine("Problem solved in " + solver.Nodes() + " branch-and-bound nodes");
|
||||
}
|
||||
|
||||
Console.WriteLine("Problem solved in " + solver.WallTime() + " milliseconds");
|
||||
|
||||
// The objective value of the solution.
|
||||
Console.WriteLine("Optimal objective value = " + solver.Objective().Value());
|
||||
|
||||
// The value of each variable in the solution.
|
||||
Console.WriteLine("x1 = " + x1.SolutionValue());
|
||||
Console.WriteLine("x2 = " + x2.SolutionValue());
|
||||
|
||||
Console.WriteLine("Advanced usage:");
|
||||
Console.WriteLine("Problem solved in " + solver.Nodes() + " branch-and-bound nodes");
|
||||
}
|
||||
|
||||
static void Main() {
|
||||
Console.WriteLine("---- Integer programming example with GLPK ----");
|
||||
RunIntegerProgrammingExample("GLPK");
|
||||
Console.WriteLine("---- Linear programming example with CBC ----");
|
||||
RunIntegerProgrammingExample("CBC");
|
||||
Console.WriteLine("---- Linear programming example with SCIP ----");
|
||||
RunIntegerProgrammingExample("SCIP");
|
||||
Console.WriteLine("---- Linear programming example with SAT ----");
|
||||
RunIntegerProgrammingExample("SAT");
|
||||
Console.WriteLine("---- Linear programming example with GUROBI ----");
|
||||
RunIntegerProgrammingExample("GUROBI");
|
||||
Console.WriteLine("---- Integer programming example (Natural API) with GLPK ----");
|
||||
RunIntegerProgrammingExampleNaturalApi("GLPK");
|
||||
Console.WriteLine("---- Linear programming example (Natural API) with CBC ----");
|
||||
RunIntegerProgrammingExampleNaturalApi("CBC");
|
||||
Console.WriteLine("---- Linear programming example (Natural API) with SCIP ----");
|
||||
RunIntegerProgrammingExampleNaturalApi("SCIP");
|
||||
Console.WriteLine("---- Linear programming example (Natural API) with SAT ----");
|
||||
RunIntegerProgrammingExampleNaturalApi("SAT");
|
||||
Console.WriteLine("---- Linear programming example (Natural API) with GUROBI ----");
|
||||
RunIntegerProgrammingExampleNaturalApi("GUROBI");
|
||||
}
|
||||
static void Main()
|
||||
{
|
||||
Console.WriteLine("---- Integer programming example with GLPK ----");
|
||||
RunIntegerProgrammingExample("GLPK");
|
||||
Console.WriteLine("---- Linear programming example with CBC ----");
|
||||
RunIntegerProgrammingExample("CBC");
|
||||
Console.WriteLine("---- Linear programming example with SCIP ----");
|
||||
RunIntegerProgrammingExample("SCIP");
|
||||
Console.WriteLine("---- Linear programming example with SAT ----");
|
||||
RunIntegerProgrammingExample("SAT");
|
||||
Console.WriteLine("---- Linear programming example with GUROBI ----");
|
||||
RunIntegerProgrammingExample("GUROBI");
|
||||
Console.WriteLine("---- Integer programming example (Natural API) with GLPK ----");
|
||||
RunIntegerProgrammingExampleNaturalApi("GLPK");
|
||||
Console.WriteLine("---- Linear programming example (Natural API) with CBC ----");
|
||||
RunIntegerProgrammingExampleNaturalApi("CBC");
|
||||
Console.WriteLine("---- Linear programming example (Natural API) with SCIP ----");
|
||||
RunIntegerProgrammingExampleNaturalApi("SCIP");
|
||||
Console.WriteLine("---- Linear programming example (Natural API) with SAT ----");
|
||||
RunIntegerProgrammingExampleNaturalApi("SAT");
|
||||
Console.WriteLine("---- Linear programming example (Natural API) with GUROBI ----");
|
||||
RunIntegerProgrammingExampleNaturalApi("GUROBI");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,28 +14,29 @@
|
||||
using System;
|
||||
using Google.OrTools.Algorithms;
|
||||
|
||||
public class CsKnapsack {
|
||||
static void Main() {
|
||||
KnapsackSolver solver = new KnapsackSolver(
|
||||
KnapsackSolver.SolverType.KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER, "test");
|
||||
long[] profits = { 360, 83, 59, 130, 431, 67, 230, 52, 93, 125, 670, 892, 600,
|
||||
38, 48, 147, 78, 256, 63, 17, 120, 164, 432, 35, 92, 110,
|
||||
22, 42, 50, 323, 514, 28, 87, 73, 78, 15, 26, 78, 210,
|
||||
36, 85, 189, 274, 43, 33, 10, 19, 389, 276, 312 };
|
||||
public class CsKnapsack
|
||||
{
|
||||
static void Main()
|
||||
{
|
||||
KnapsackSolver solver =
|
||||
new KnapsackSolver(KnapsackSolver.SolverType.KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER, "test");
|
||||
long[] profits = { 360, 83, 59, 130, 431, 67, 230, 52, 93, 125, 670, 892, 600, 38, 48, 147, 78,
|
||||
256, 63, 17, 120, 164, 432, 35, 92, 110, 22, 42, 50, 323, 514, 28, 87, 73,
|
||||
78, 15, 26, 78, 210, 36, 85, 189, 274, 43, 33, 10, 19, 389, 276, 312 };
|
||||
|
||||
long[,] weights = { { 7, 0, 30, 22, 80, 94, 11, 81, 70, 64, 59, 18, 0, 36, 3, 8, 15,
|
||||
42, 9, 0, 42, 47, 52, 32, 26, 48, 55, 6, 29, 84, 2, 4, 18, 56,
|
||||
7, 29, 93, 44, 71, 3, 86, 66, 31, 65, 0, 79, 20, 65, 52, 13 } };
|
||||
long[,] weights = { { 7, 0, 30, 22, 80, 94, 11, 81, 70, 64, 59, 18, 0, 36, 3, 8, 15,
|
||||
42, 9, 0, 42, 47, 52, 32, 26, 48, 55, 6, 29, 84, 2, 4, 18, 56,
|
||||
7, 29, 93, 44, 71, 3, 86, 66, 31, 65, 0, 79, 20, 65, 52, 13 } };
|
||||
|
||||
long[] capacities = { 850 };
|
||||
long[] capacities = { 850 };
|
||||
|
||||
long optimalProfit = 7534;
|
||||
long optimalProfit = 7534;
|
||||
|
||||
Console.WriteLine("Solving knapsack with " + profits.Length + " items, and " +
|
||||
weights.GetLength(0) + " dimension");
|
||||
solver.Init(profits, weights, capacities);
|
||||
long computedProfit = solver.Solve();
|
||||
Console.WriteLine("Solving knapsack with " + profits.Length + " items, and " + weights.GetLength(0) +
|
||||
" dimension");
|
||||
solver.Init(profits, weights, capacities);
|
||||
long computedProfit = solver.Solve();
|
||||
|
||||
Console.WriteLine("Optimal Profit = " + computedProfit + ", expected = " + optimalProfit);
|
||||
}
|
||||
Console.WriteLine("Optimal Profit = " + computedProfit + ", expected = " + optimalProfit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,146 +14,155 @@
|
||||
using System;
|
||||
using Google.OrTools.LinearSolver;
|
||||
|
||||
public class CsLinearProgramming {
|
||||
private static void RunLinearProgrammingExample(String solverType) {
|
||||
Console.WriteLine($"---- Linear programming example with {solverType} ----");
|
||||
public class CsLinearProgramming
|
||||
{
|
||||
private static void RunLinearProgrammingExample(String solverType)
|
||||
{
|
||||
Console.WriteLine($"---- Linear programming example with {solverType} ----");
|
||||
|
||||
Solver solver = Solver.CreateSolver(solverType);
|
||||
if (solver == null) {
|
||||
Console.WriteLine("Could not create solver " + solverType);
|
||||
return;
|
||||
}
|
||||
// x1, x2 and x3 are continuous non-negative variables.
|
||||
Variable x1 = solver.MakeNumVar(0.0, double.PositiveInfinity, "x1");
|
||||
Variable x2 = solver.MakeNumVar(0.0, double.PositiveInfinity, "x2");
|
||||
Variable x3 = solver.MakeNumVar(0.0, double.PositiveInfinity, "x3");
|
||||
Solver solver = Solver.CreateSolver(solverType);
|
||||
if (solver == null)
|
||||
{
|
||||
Console.WriteLine("Could not create solver " + solverType);
|
||||
return;
|
||||
}
|
||||
// x1, x2 and x3 are continuous non-negative variables.
|
||||
Variable x1 = solver.MakeNumVar(0.0, double.PositiveInfinity, "x1");
|
||||
Variable x2 = solver.MakeNumVar(0.0, double.PositiveInfinity, "x2");
|
||||
Variable x3 = solver.MakeNumVar(0.0, double.PositiveInfinity, "x3");
|
||||
|
||||
// Maximize 10 * x1 + 6 * x2 + 4 * x3.
|
||||
Objective objective = solver.Objective();
|
||||
objective.SetCoefficient(x1, 10);
|
||||
objective.SetCoefficient(x2, 6);
|
||||
objective.SetCoefficient(x3, 4);
|
||||
objective.SetMaximization();
|
||||
// Maximize 10 * x1 + 6 * x2 + 4 * x3.
|
||||
Objective objective = solver.Objective();
|
||||
objective.SetCoefficient(x1, 10);
|
||||
objective.SetCoefficient(x2, 6);
|
||||
objective.SetCoefficient(x3, 4);
|
||||
objective.SetMaximization();
|
||||
|
||||
// x1 + x2 + x3 <= 100.
|
||||
Constraint c0 = solver.MakeConstraint(double.NegativeInfinity, 100.0);
|
||||
c0.SetCoefficient(x1, 1);
|
||||
c0.SetCoefficient(x2, 1);
|
||||
c0.SetCoefficient(x3, 1);
|
||||
// x1 + x2 + x3 <= 100.
|
||||
Constraint c0 = solver.MakeConstraint(double.NegativeInfinity, 100.0);
|
||||
c0.SetCoefficient(x1, 1);
|
||||
c0.SetCoefficient(x2, 1);
|
||||
c0.SetCoefficient(x3, 1);
|
||||
|
||||
// 10 * x1 + 4 * x2 + 5 * x3 <= 600.
|
||||
Constraint c1 = solver.MakeConstraint(double.NegativeInfinity, 600.0);
|
||||
c1.SetCoefficient(x1, 10);
|
||||
c1.SetCoefficient(x2, 4);
|
||||
c1.SetCoefficient(x3, 5);
|
||||
// 10 * x1 + 4 * x2 + 5 * x3 <= 600.
|
||||
Constraint c1 = solver.MakeConstraint(double.NegativeInfinity, 600.0);
|
||||
c1.SetCoefficient(x1, 10);
|
||||
c1.SetCoefficient(x2, 4);
|
||||
c1.SetCoefficient(x3, 5);
|
||||
|
||||
// 2 * x1 + 2 * x2 + 6 * x3 <= 300.
|
||||
Constraint c2 = solver.MakeConstraint(double.NegativeInfinity, 300.0);
|
||||
c2.SetCoefficient(x1, 2);
|
||||
c2.SetCoefficient(x2, 2);
|
||||
c2.SetCoefficient(x3, 6);
|
||||
// 2 * x1 + 2 * x2 + 6 * x3 <= 300.
|
||||
Constraint c2 = solver.MakeConstraint(double.NegativeInfinity, 300.0);
|
||||
c2.SetCoefficient(x1, 2);
|
||||
c2.SetCoefficient(x2, 2);
|
||||
c2.SetCoefficient(x3, 6);
|
||||
|
||||
Console.WriteLine("Number of variables = " + solver.NumVariables());
|
||||
Console.WriteLine("Number of constraints = " + solver.NumConstraints());
|
||||
Console.WriteLine("Number of variables = " + solver.NumVariables());
|
||||
Console.WriteLine("Number of constraints = " + solver.NumConstraints());
|
||||
|
||||
Solver.ResultStatus resultStatus = solver.Solve();
|
||||
Solver.ResultStatus resultStatus = solver.Solve();
|
||||
|
||||
// Check that the problem has an optimal solution.
|
||||
if (resultStatus != Solver.ResultStatus.OPTIMAL) {
|
||||
Console.WriteLine("The problem does not have an optimal solution!");
|
||||
return;
|
||||
// Check that the problem has an optimal solution.
|
||||
if (resultStatus != Solver.ResultStatus.OPTIMAL)
|
||||
{
|
||||
Console.WriteLine("The problem does not have an optimal solution!");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("Problem solved in " + solver.WallTime() + " milliseconds");
|
||||
|
||||
// The objective value of the solution.
|
||||
Console.WriteLine("Optimal objective value = " + solver.Objective().Value());
|
||||
|
||||
// The value of each variable in the solution.
|
||||
Console.WriteLine("x1 = " + x1.SolutionValue());
|
||||
Console.WriteLine("x2 = " + x2.SolutionValue());
|
||||
Console.WriteLine("x3 = " + x3.SolutionValue());
|
||||
|
||||
Console.WriteLine("Advanced usage:");
|
||||
double[] activities = solver.ComputeConstraintActivities();
|
||||
|
||||
Console.WriteLine("Problem solved in " + solver.Iterations() + " iterations");
|
||||
Console.WriteLine("x1: reduced cost = " + x1.ReducedCost());
|
||||
Console.WriteLine("x2: reduced cost = " + x2.ReducedCost());
|
||||
Console.WriteLine("x3: reduced cost = " + x3.ReducedCost());
|
||||
Console.WriteLine("c0: dual value = " + c0.DualValue());
|
||||
Console.WriteLine(" activity = " + activities[c0.Index()]);
|
||||
Console.WriteLine("c1: dual value = " + c1.DualValue());
|
||||
Console.WriteLine(" activity = " + activities[c1.Index()]);
|
||||
Console.WriteLine("c2: dual value = " + c2.DualValue());
|
||||
Console.WriteLine(" activity = " + activities[c2.Index()]);
|
||||
}
|
||||
|
||||
Console.WriteLine("Problem solved in " + solver.WallTime() + " milliseconds");
|
||||
private static void RunLinearProgrammingExampleNaturalApi(String solverType, bool printModel)
|
||||
{
|
||||
Console.WriteLine($"---- Linear programming example (Natural API) with {solverType} ----");
|
||||
|
||||
// The objective value of the solution.
|
||||
Console.WriteLine("Optimal objective value = " + solver.Objective().Value());
|
||||
Solver solver = Solver.CreateSolver(solverType);
|
||||
if (solver == null)
|
||||
{
|
||||
Console.WriteLine("Could not create solver " + solverType);
|
||||
return;
|
||||
}
|
||||
// x1, x2 and x3 are continuous non-negative variables.
|
||||
Variable x1 = solver.MakeNumVar(0.0, double.PositiveInfinity, "x1");
|
||||
Variable x2 = solver.MakeNumVar(0.0, double.PositiveInfinity, "x2");
|
||||
Variable x3 = solver.MakeNumVar(0.0, double.PositiveInfinity, "x3");
|
||||
|
||||
// The value of each variable in the solution.
|
||||
Console.WriteLine("x1 = " + x1.SolutionValue());
|
||||
Console.WriteLine("x2 = " + x2.SolutionValue());
|
||||
Console.WriteLine("x3 = " + x3.SolutionValue());
|
||||
solver.Maximize(10 * x1 + 6 * x2 + 4 * x3);
|
||||
Constraint c0 = solver.Add(x1 + x2 + x3 <= 100);
|
||||
Constraint c1 = solver.Add(10 * x1 + x2 * 4 + 5 * x3 <= 600);
|
||||
Constraint c2 = solver.Add(2 * x1 + 2 * x2 + 6 * x3 <= 300);
|
||||
|
||||
Console.WriteLine("Advanced usage:");
|
||||
double[] activities = solver.ComputeConstraintActivities();
|
||||
Console.WriteLine("Number of variables = " + solver.NumVariables());
|
||||
Console.WriteLine("Number of constraints = " + solver.NumConstraints());
|
||||
|
||||
Console.WriteLine("Problem solved in " + solver.Iterations() + " iterations");
|
||||
Console.WriteLine("x1: reduced cost = " + x1.ReducedCost());
|
||||
Console.WriteLine("x2: reduced cost = " + x2.ReducedCost());
|
||||
Console.WriteLine("x3: reduced cost = " + x3.ReducedCost());
|
||||
Console.WriteLine("c0: dual value = " + c0.DualValue());
|
||||
Console.WriteLine(" activity = " + activities[c0.Index()]);
|
||||
Console.WriteLine("c1: dual value = " + c1.DualValue());
|
||||
Console.WriteLine(" activity = " + activities[c1.Index()]);
|
||||
Console.WriteLine("c2: dual value = " + c2.DualValue());
|
||||
Console.WriteLine(" activity = " + activities[c2.Index()]);
|
||||
}
|
||||
if (printModel)
|
||||
{
|
||||
string model = solver.ExportModelAsLpFormat(false);
|
||||
Console.WriteLine(model);
|
||||
}
|
||||
|
||||
private static void RunLinearProgrammingExampleNaturalApi(String solverType, bool printModel) {
|
||||
Console.WriteLine($"---- Linear programming example (Natural API) with {solverType} ----");
|
||||
Solver.ResultStatus resultStatus = solver.Solve();
|
||||
|
||||
Solver solver = Solver.CreateSolver(solverType);
|
||||
if (solver == null) {
|
||||
Console.WriteLine("Could not create solver " + solverType);
|
||||
return;
|
||||
}
|
||||
// x1, x2 and x3 are continuous non-negative variables.
|
||||
Variable x1 = solver.MakeNumVar(0.0, double.PositiveInfinity, "x1");
|
||||
Variable x2 = solver.MakeNumVar(0.0, double.PositiveInfinity, "x2");
|
||||
Variable x3 = solver.MakeNumVar(0.0, double.PositiveInfinity, "x3");
|
||||
// Check that the problem has an optimal solution.
|
||||
if (resultStatus != Solver.ResultStatus.OPTIMAL)
|
||||
{
|
||||
Console.WriteLine("The problem does not have an optimal solution!");
|
||||
return;
|
||||
}
|
||||
|
||||
solver.Maximize(10 * x1 + 6 * x2 + 4 * x3);
|
||||
Constraint c0 = solver.Add(x1 + x2 + x3 <= 100);
|
||||
Constraint c1 = solver.Add(10 * x1 + x2 * 4 + 5 * x3 <= 600);
|
||||
Constraint c2 = solver.Add(2 * x1 + 2 * x2 + 6 * x3 <= 300);
|
||||
Console.WriteLine("Problem solved in " + solver.WallTime() + " milliseconds");
|
||||
|
||||
Console.WriteLine("Number of variables = " + solver.NumVariables());
|
||||
Console.WriteLine("Number of constraints = " + solver.NumConstraints());
|
||||
// The objective value of the solution.
|
||||
Console.WriteLine("Optimal objective value = " + solver.Objective().Value());
|
||||
|
||||
if (printModel) {
|
||||
string model = solver.ExportModelAsLpFormat(false);
|
||||
Console.WriteLine(model);
|
||||
// The value of each variable in the solution.
|
||||
Console.WriteLine("x1 = " + x1.SolutionValue());
|
||||
Console.WriteLine("x2 = " + x2.SolutionValue());
|
||||
Console.WriteLine("x3 = " + x3.SolutionValue());
|
||||
|
||||
Console.WriteLine("Advanced usage:");
|
||||
double[] activities = solver.ComputeConstraintActivities();
|
||||
Console.WriteLine("Problem solved in " + solver.Iterations() + " iterations");
|
||||
Console.WriteLine("x1: reduced cost = " + x1.ReducedCost());
|
||||
Console.WriteLine("x2: reduced cost = " + x2.ReducedCost());
|
||||
Console.WriteLine("x3: reduced cost = " + x3.ReducedCost());
|
||||
Console.WriteLine("c0: dual value = " + c0.DualValue());
|
||||
Console.WriteLine(" activity = " + activities[c0.Index()]);
|
||||
Console.WriteLine("c1: dual value = " + c1.DualValue());
|
||||
Console.WriteLine(" activity = " + activities[c1.Index()]);
|
||||
Console.WriteLine("c2: dual value = " + c2.DualValue());
|
||||
Console.WriteLine(" activity = " + activities[c2.Index()]);
|
||||
}
|
||||
|
||||
Solver.ResultStatus resultStatus = solver.Solve();
|
||||
static void Main()
|
||||
{
|
||||
RunLinearProgrammingExample("GLOP");
|
||||
RunLinearProgrammingExample("GLPK_LP");
|
||||
RunLinearProgrammingExample("CLP");
|
||||
|
||||
// Check that the problem has an optimal solution.
|
||||
if (resultStatus != Solver.ResultStatus.OPTIMAL) {
|
||||
Console.WriteLine("The problem does not have an optimal solution!");
|
||||
return;
|
||||
RunLinearProgrammingExampleNaturalApi("GLOP", true);
|
||||
RunLinearProgrammingExampleNaturalApi("GLPK_LP", false);
|
||||
RunLinearProgrammingExampleNaturalApi("CLP", false);
|
||||
}
|
||||
|
||||
Console.WriteLine("Problem solved in " + solver.WallTime() + " milliseconds");
|
||||
|
||||
// The objective value of the solution.
|
||||
Console.WriteLine("Optimal objective value = " + solver.Objective().Value());
|
||||
|
||||
// The value of each variable in the solution.
|
||||
Console.WriteLine("x1 = " + x1.SolutionValue());
|
||||
Console.WriteLine("x2 = " + x2.SolutionValue());
|
||||
Console.WriteLine("x3 = " + x3.SolutionValue());
|
||||
|
||||
Console.WriteLine("Advanced usage:");
|
||||
double[] activities = solver.ComputeConstraintActivities();
|
||||
Console.WriteLine("Problem solved in " + solver.Iterations() + " iterations");
|
||||
Console.WriteLine("x1: reduced cost = " + x1.ReducedCost());
|
||||
Console.WriteLine("x2: reduced cost = " + x2.ReducedCost());
|
||||
Console.WriteLine("x3: reduced cost = " + x3.ReducedCost());
|
||||
Console.WriteLine("c0: dual value = " + c0.DualValue());
|
||||
Console.WriteLine(" activity = " + activities[c0.Index()]);
|
||||
Console.WriteLine("c1: dual value = " + c1.DualValue());
|
||||
Console.WriteLine(" activity = " + activities[c1.Index()]);
|
||||
Console.WriteLine("c2: dual value = " + c2.DualValue());
|
||||
Console.WriteLine(" activity = " + activities[c2.Index()]);
|
||||
}
|
||||
|
||||
static void Main() {
|
||||
RunLinearProgrammingExample("GLOP");
|
||||
RunLinearProgrammingExample("GLPK_LP");
|
||||
RunLinearProgrammingExample("CLP");
|
||||
|
||||
RunLinearProgrammingExampleNaturalApi("GLOP", true);
|
||||
RunLinearProgrammingExampleNaturalApi("GLPK_LP", false);
|
||||
RunLinearProgrammingExampleNaturalApi("CLP", false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,150 +18,172 @@ using Google.OrTools.ConstraintSolver;
|
||||
* Shows how to write a custom lns operator.
|
||||
*/
|
||||
|
||||
public class OneVarLns : BaseLns {
|
||||
public OneVarLns(IntVar[] vars) : base(vars) {}
|
||||
|
||||
public override void InitFragments() {
|
||||
index_ = 0;
|
||||
}
|
||||
|
||||
public override bool NextFragment() {
|
||||
int size = Size();
|
||||
if (index_ < size) {
|
||||
AppendToFragment(index_);
|
||||
++index_;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
public class OneVarLns : BaseLns
|
||||
{
|
||||
public OneVarLns(IntVar[] vars) : base(vars)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private int index_;
|
||||
public override void InitFragments()
|
||||
{
|
||||
index_ = 0;
|
||||
}
|
||||
|
||||
public override bool NextFragment()
|
||||
{
|
||||
int size = Size();
|
||||
if (index_ < size)
|
||||
{
|
||||
AppendToFragment(index_);
|
||||
++index_;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int index_;
|
||||
}
|
||||
|
||||
class MoveOneVar : IntVarLocalSearchOperator {
|
||||
public MoveOneVar(IntVar[] variables) : base(variables) {
|
||||
variable_index_ = 0;
|
||||
move_up_ = false;
|
||||
}
|
||||
|
||||
protected override bool MakeOneNeighbor() {
|
||||
long current_value = OldValue(variable_index_);
|
||||
if (move_up_) {
|
||||
SetValue(variable_index_, current_value + 1);
|
||||
variable_index_ = (variable_index_ + 1) % Size();
|
||||
} else {
|
||||
SetValue(variable_index_, current_value - 1);
|
||||
class MoveOneVar : IntVarLocalSearchOperator
|
||||
{
|
||||
public MoveOneVar(IntVar[] variables) : base(variables)
|
||||
{
|
||||
variable_index_ = 0;
|
||||
move_up_ = false;
|
||||
}
|
||||
move_up_ = !move_up_;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Index of the next variable to try to restore
|
||||
private long variable_index_;
|
||||
// Direction of the modification.
|
||||
private bool move_up_;
|
||||
};
|
||||
|
||||
public class SumFilter : IntVarLocalSearchFilter {
|
||||
public SumFilter(IntVar[] vars) : base(vars) {
|
||||
sum_ = 0;
|
||||
}
|
||||
|
||||
protected override void OnSynchronize(Assignment delta) {
|
||||
sum_ = 0;
|
||||
for (int index = 0; index < Size(); ++index) {
|
||||
sum_ += Value(index);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Accept(Assignment delta, Assignment unused_deltadelta,
|
||||
long unused_objective_min, long unused_objective_max) {
|
||||
AssignmentIntContainer solution_delta = delta.IntVarContainer();
|
||||
int solution_delta_size = solution_delta.Size();
|
||||
|
||||
for (int i = 0; i < solution_delta_size; ++i) {
|
||||
if (!solution_delta.Element(i).Activated()) {
|
||||
protected override bool MakeOneNeighbor()
|
||||
{
|
||||
long current_value = OldValue(variable_index_);
|
||||
if (move_up_)
|
||||
{
|
||||
SetValue(variable_index_, current_value + 1);
|
||||
variable_index_ = (variable_index_ + 1) % Size();
|
||||
}
|
||||
else
|
||||
{
|
||||
SetValue(variable_index_, current_value - 1);
|
||||
}
|
||||
move_up_ = !move_up_;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
long new_sum = sum_;
|
||||
for (int index = 0; index < solution_delta_size; ++index) {
|
||||
int touched_var = Index(solution_delta.Element(index).Var());
|
||||
long old_value = Value(touched_var);
|
||||
long new_value = solution_delta.Element(index).Value();
|
||||
new_sum += new_value - old_value;
|
||||
}
|
||||
return new_sum < sum_;
|
||||
}
|
||||
|
||||
private long sum_;
|
||||
// Index of the next variable to try to restore
|
||||
private long variable_index_;
|
||||
// Direction of the modification.
|
||||
private bool move_up_;
|
||||
};
|
||||
|
||||
public class CsLsApi {
|
||||
private static void BasicLns() {
|
||||
Console.WriteLine("BasicLns");
|
||||
Solver solver = new Solver("BasicLns");
|
||||
IntVar[] vars = solver.MakeIntVarArray(4, 0, 4, "vars");
|
||||
IntVar sum_var = vars.Sum().Var();
|
||||
OptimizeVar obj = sum_var.Minimize(1);
|
||||
DecisionBuilder db =
|
||||
solver.MakePhase(vars, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MAX_VALUE);
|
||||
OneVarLns one_var_lns = new OneVarLns(vars);
|
||||
LocalSearchPhaseParameters ls_params =
|
||||
solver.MakeLocalSearchPhaseParameters(sum_var, one_var_lns, db);
|
||||
DecisionBuilder ls = solver.MakeLocalSearchPhase(vars, db, ls_params);
|
||||
SolutionCollector collector = solver.MakeLastSolutionCollector();
|
||||
collector.AddObjective(sum_var);
|
||||
SearchMonitor log = solver.MakeSearchLog(1000, obj);
|
||||
solver.Solve(ls, collector, obj, log);
|
||||
Console.WriteLine("Objective value = {0}", collector.ObjectiveValue(0));
|
||||
}
|
||||
public class SumFilter : IntVarLocalSearchFilter
|
||||
{
|
||||
public SumFilter(IntVar[] vars) : base(vars)
|
||||
{
|
||||
sum_ = 0;
|
||||
}
|
||||
|
||||
private static void BasicLs() {
|
||||
Console.WriteLine("BasicLs");
|
||||
Solver solver = new Solver("BasicLs");
|
||||
IntVar[] vars = solver.MakeIntVarArray(4, 0, 4, "vars");
|
||||
IntVar sum_var = vars.Sum().Var();
|
||||
OptimizeVar obj = sum_var.Minimize(1);
|
||||
DecisionBuilder db =
|
||||
solver.MakePhase(vars, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MAX_VALUE);
|
||||
MoveOneVar move_one_var = new MoveOneVar(vars);
|
||||
LocalSearchPhaseParameters ls_params =
|
||||
solver.MakeLocalSearchPhaseParameters(sum_var, move_one_var, db);
|
||||
DecisionBuilder ls = solver.MakeLocalSearchPhase(vars, db, ls_params);
|
||||
SolutionCollector collector = solver.MakeLastSolutionCollector();
|
||||
collector.AddObjective(sum_var);
|
||||
SearchMonitor log = solver.MakeSearchLog(1000, obj);
|
||||
solver.Solve(ls, collector, obj, log);
|
||||
Console.WriteLine("Objective value = {0}", collector.ObjectiveValue(0));
|
||||
}
|
||||
protected override void OnSynchronize(Assignment delta)
|
||||
{
|
||||
sum_ = 0;
|
||||
for (int index = 0; index < Size(); ++index)
|
||||
{
|
||||
sum_ += Value(index);
|
||||
}
|
||||
}
|
||||
|
||||
private static void BasicLsWithFilter() {
|
||||
Console.WriteLine("BasicLsWithFilter");
|
||||
Solver solver = new Solver("BasicLs");
|
||||
IntVar[] vars = solver.MakeIntVarArray(4, 0, 4, "vars");
|
||||
IntVar sum_var = vars.Sum().Var();
|
||||
OptimizeVar obj = sum_var.Minimize(1);
|
||||
DecisionBuilder db =
|
||||
solver.MakePhase(vars, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MAX_VALUE);
|
||||
MoveOneVar move_one_var = new MoveOneVar(vars);
|
||||
SumFilter filter = new SumFilter(vars);
|
||||
IntVarLocalSearchFilter[] filters = new IntVarLocalSearchFilter[] { filter };
|
||||
LocalSearchFilterManager filter_manager = new LocalSearchFilterManager(filters);
|
||||
LocalSearchPhaseParameters ls_params =
|
||||
solver.MakeLocalSearchPhaseParameters(sum_var, move_one_var, db, null, filter_manager);
|
||||
DecisionBuilder ls = solver.MakeLocalSearchPhase(vars, db, ls_params);
|
||||
SolutionCollector collector = solver.MakeLastSolutionCollector();
|
||||
collector.AddObjective(sum_var);
|
||||
SearchMonitor log = solver.MakeSearchLog(1000, obj);
|
||||
solver.Solve(ls, collector, obj, log);
|
||||
Console.WriteLine("Objective value = {0}", collector.ObjectiveValue(0));
|
||||
}
|
||||
public override bool Accept(Assignment delta, Assignment unused_deltadelta, long unused_objective_min,
|
||||
long unused_objective_max)
|
||||
{
|
||||
AssignmentIntContainer solution_delta = delta.IntVarContainer();
|
||||
int solution_delta_size = solution_delta.Size();
|
||||
|
||||
public static void Main(String[] args) {
|
||||
BasicLns();
|
||||
BasicLs();
|
||||
BasicLsWithFilter();
|
||||
}
|
||||
for (int i = 0; i < solution_delta_size; ++i)
|
||||
{
|
||||
if (!solution_delta.Element(i).Activated())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
long new_sum = sum_;
|
||||
for (int index = 0; index < solution_delta_size; ++index)
|
||||
{
|
||||
int touched_var = Index(solution_delta.Element(index).Var());
|
||||
long old_value = Value(touched_var);
|
||||
long new_value = solution_delta.Element(index).Value();
|
||||
new_sum += new_value - old_value;
|
||||
}
|
||||
return new_sum < sum_;
|
||||
}
|
||||
|
||||
private long sum_;
|
||||
};
|
||||
|
||||
public class CsLsApi
|
||||
{
|
||||
private static void BasicLns()
|
||||
{
|
||||
Console.WriteLine("BasicLns");
|
||||
Solver solver = new Solver("BasicLns");
|
||||
IntVar[] vars = solver.MakeIntVarArray(4, 0, 4, "vars");
|
||||
IntVar sum_var = vars.Sum().Var();
|
||||
OptimizeVar obj = sum_var.Minimize(1);
|
||||
DecisionBuilder db = solver.MakePhase(vars, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MAX_VALUE);
|
||||
OneVarLns one_var_lns = new OneVarLns(vars);
|
||||
LocalSearchPhaseParameters ls_params = solver.MakeLocalSearchPhaseParameters(sum_var, one_var_lns, db);
|
||||
DecisionBuilder ls = solver.MakeLocalSearchPhase(vars, db, ls_params);
|
||||
SolutionCollector collector = solver.MakeLastSolutionCollector();
|
||||
collector.AddObjective(sum_var);
|
||||
SearchMonitor log = solver.MakeSearchLog(1000, obj);
|
||||
solver.Solve(ls, collector, obj, log);
|
||||
Console.WriteLine("Objective value = {0}", collector.ObjectiveValue(0));
|
||||
}
|
||||
|
||||
private static void BasicLs()
|
||||
{
|
||||
Console.WriteLine("BasicLs");
|
||||
Solver solver = new Solver("BasicLs");
|
||||
IntVar[] vars = solver.MakeIntVarArray(4, 0, 4, "vars");
|
||||
IntVar sum_var = vars.Sum().Var();
|
||||
OptimizeVar obj = sum_var.Minimize(1);
|
||||
DecisionBuilder db = solver.MakePhase(vars, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MAX_VALUE);
|
||||
MoveOneVar move_one_var = new MoveOneVar(vars);
|
||||
LocalSearchPhaseParameters ls_params = solver.MakeLocalSearchPhaseParameters(sum_var, move_one_var, db);
|
||||
DecisionBuilder ls = solver.MakeLocalSearchPhase(vars, db, ls_params);
|
||||
SolutionCollector collector = solver.MakeLastSolutionCollector();
|
||||
collector.AddObjective(sum_var);
|
||||
SearchMonitor log = solver.MakeSearchLog(1000, obj);
|
||||
solver.Solve(ls, collector, obj, log);
|
||||
Console.WriteLine("Objective value = {0}", collector.ObjectiveValue(0));
|
||||
}
|
||||
|
||||
private static void BasicLsWithFilter()
|
||||
{
|
||||
Console.WriteLine("BasicLsWithFilter");
|
||||
Solver solver = new Solver("BasicLs");
|
||||
IntVar[] vars = solver.MakeIntVarArray(4, 0, 4, "vars");
|
||||
IntVar sum_var = vars.Sum().Var();
|
||||
OptimizeVar obj = sum_var.Minimize(1);
|
||||
DecisionBuilder db = solver.MakePhase(vars, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MAX_VALUE);
|
||||
MoveOneVar move_one_var = new MoveOneVar(vars);
|
||||
SumFilter filter = new SumFilter(vars);
|
||||
IntVarLocalSearchFilter[] filters = new IntVarLocalSearchFilter[] { filter };
|
||||
LocalSearchFilterManager filter_manager = new LocalSearchFilterManager(filters);
|
||||
LocalSearchPhaseParameters ls_params =
|
||||
solver.MakeLocalSearchPhaseParameters(sum_var, move_one_var, db, null, filter_manager);
|
||||
DecisionBuilder ls = solver.MakeLocalSearchPhase(vars, db, ls_params);
|
||||
SolutionCollector collector = solver.MakeLastSolutionCollector();
|
||||
collector.AddObjective(sum_var);
|
||||
SearchMonitor log = solver.MakeSearchLog(1000, obj);
|
||||
solver.Solve(ls, collector, obj, log);
|
||||
Console.WriteLine("Objective value = {0}", collector.ObjectiveValue(0));
|
||||
}
|
||||
|
||||
public static void Main(String[] args)
|
||||
{
|
||||
BasicLns();
|
||||
BasicLs();
|
||||
BasicLsWithFilter();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,46 +17,54 @@ using Google.OrTools.ConstraintSolver;
|
||||
/**
|
||||
* Shows how to write a custom decision builder.
|
||||
*/
|
||||
public class AssignFirstUnboundToMin : NetDecisionBuilder {
|
||||
public AssignFirstUnboundToMin(IntVar[] vars) {
|
||||
vars_ = vars;
|
||||
}
|
||||
|
||||
public override Decision Next(Solver solver) {
|
||||
foreach (IntVar var in vars_) {
|
||||
if (!var.Bound()) {
|
||||
return solver.MakeAssignVariableValue(var, var.Min());
|
||||
}
|
||||
public class AssignFirstUnboundToMin : NetDecisionBuilder
|
||||
{
|
||||
public AssignFirstUnboundToMin(IntVar[] vars)
|
||||
{
|
||||
vars_ = vars;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private IntVar[] vars_;
|
||||
public override Decision Next(Solver solver)
|
||||
{
|
||||
foreach (IntVar var in vars_)
|
||||
{
|
||||
if (!var.Bound())
|
||||
{
|
||||
return solver.MakeAssignVariableValue(var, var.Min());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private IntVar[] vars_;
|
||||
}
|
||||
|
||||
public class CsRabbitsPheasants {
|
||||
/**
|
||||
* Solves the rabbits + pheasants problem. We are seing 20 heads
|
||||
* and 56 legs. How many rabbits and how many pheasants are we thus
|
||||
* seeing?
|
||||
*/
|
||||
private static void Solve() {
|
||||
Solver solver = new Solver("RabbitsPheasants");
|
||||
IntVar rabbits = solver.MakeIntVar(0, 100, "rabbits");
|
||||
IntVar pheasants = solver.MakeIntVar(0, 100, "pheasants");
|
||||
solver.Add(rabbits + pheasants == 20);
|
||||
solver.Add(rabbits * 4 + pheasants * 2 == 56);
|
||||
DecisionBuilder db = new AssignFirstUnboundToMin(new IntVar[] { rabbits, pheasants });
|
||||
solver.NewSearch(db);
|
||||
solver.NextSolution();
|
||||
Console.WriteLine("Solved Rabbits + Pheasants in {0} ms, and {1} search tree branches.",
|
||||
solver.WallTime(), solver.Branches());
|
||||
Console.WriteLine(rabbits.ToString());
|
||||
Console.WriteLine(pheasants.ToString());
|
||||
solver.EndSearch();
|
||||
}
|
||||
public class CsRabbitsPheasants
|
||||
{
|
||||
/**
|
||||
* Solves the rabbits + pheasants problem. We are seing 20 heads
|
||||
* and 56 legs. How many rabbits and how many pheasants are we thus
|
||||
* seeing?
|
||||
*/
|
||||
private static void Solve()
|
||||
{
|
||||
Solver solver = new Solver("RabbitsPheasants");
|
||||
IntVar rabbits = solver.MakeIntVar(0, 100, "rabbits");
|
||||
IntVar pheasants = solver.MakeIntVar(0, 100, "pheasants");
|
||||
solver.Add(rabbits + pheasants == 20);
|
||||
solver.Add(rabbits * 4 + pheasants * 2 == 56);
|
||||
DecisionBuilder db = new AssignFirstUnboundToMin(new IntVar[] { rabbits, pheasants });
|
||||
solver.NewSearch(db);
|
||||
solver.NextSolution();
|
||||
Console.WriteLine("Solved Rabbits + Pheasants in {0} ms, and {1} search tree branches.", solver.WallTime(),
|
||||
solver.Branches());
|
||||
Console.WriteLine(rabbits.ToString());
|
||||
Console.WriteLine(pheasants.ToString());
|
||||
solver.EndSearch();
|
||||
}
|
||||
|
||||
public static void Main(String[] args) {
|
||||
Solve();
|
||||
}
|
||||
public static void Main(String[] args)
|
||||
{
|
||||
Solve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,95 +15,108 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Google.OrTools.ConstraintSolver;
|
||||
|
||||
class Tsp {
|
||||
class RandomManhattan {
|
||||
public RandomManhattan(RoutingIndexManager manager, int size, int seed) {
|
||||
this.xs_ = new int[size];
|
||||
this.ys_ = new int[size];
|
||||
this.manager_ = manager;
|
||||
Random generator = new Random(seed);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
xs_[i] = generator.Next(1000);
|
||||
ys_[i] = generator.Next(1000);
|
||||
}
|
||||
class Tsp
|
||||
{
|
||||
class RandomManhattan
|
||||
{
|
||||
public RandomManhattan(RoutingIndexManager manager, int size, int seed)
|
||||
{
|
||||
this.xs_ = new int[size];
|
||||
this.ys_ = new int[size];
|
||||
this.manager_ = manager;
|
||||
Random generator = new Random(seed);
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
xs_[i] = generator.Next(1000);
|
||||
ys_[i] = generator.Next(1000);
|
||||
}
|
||||
}
|
||||
|
||||
public long Call(long first_index, long second_index)
|
||||
{
|
||||
int first_node = manager_.IndexToNode(first_index);
|
||||
int second_node = manager_.IndexToNode(second_index);
|
||||
return Math.Abs(xs_[first_node] - xs_[second_node]) + Math.Abs(ys_[first_node] - ys_[second_node]);
|
||||
}
|
||||
|
||||
private readonly int[] xs_;
|
||||
private readonly int[] ys_;
|
||||
private readonly RoutingIndexManager manager_;
|
||||
};
|
||||
|
||||
static void Solve(int size, int forbidden, int seed)
|
||||
{
|
||||
RoutingIndexManager manager = new RoutingIndexManager(size, 1, 0);
|
||||
RoutingModel routing = new RoutingModel(manager);
|
||||
|
||||
// Setting the cost function.
|
||||
// Put a permanent callback to the distance accessor here. The callback
|
||||
// has the following signature: ResultCallback2<int64, int64, int64>.
|
||||
// The two arguments are the from and to node inidices.
|
||||
RandomManhattan distances = new RandomManhattan(manager, size, seed);
|
||||
routing.SetArcCostEvaluatorOfAllVehicles(routing.RegisterTransitCallback(distances.Call));
|
||||
|
||||
// Forbid node connections (randomly).
|
||||
Random randomizer = new Random();
|
||||
long forbidden_connections = 0;
|
||||
while (forbidden_connections < forbidden)
|
||||
{
|
||||
long from = randomizer.Next(size - 1);
|
||||
long to = randomizer.Next(size - 1) + 1;
|
||||
if (routing.NextVar(from).Contains(to))
|
||||
{
|
||||
Console.WriteLine("Forbidding connection {0} -> {1}", from, to);
|
||||
routing.NextVar(from).RemoveValue(to);
|
||||
++forbidden_connections;
|
||||
}
|
||||
}
|
||||
|
||||
// Add dummy dimension to test API.
|
||||
routing.AddDimension(routing.RegisterUnaryTransitCallback((long index) => { return 1; }), size + 1, size + 1,
|
||||
true, "dummy");
|
||||
|
||||
// Solve, returns a solution if any (owned by RoutingModel).
|
||||
RoutingSearchParameters search_parameters =
|
||||
operations_research_constraint_solver.DefaultRoutingSearchParameters();
|
||||
// Setting first solution heuristic (cheapest addition).
|
||||
search_parameters.FirstSolutionStrategy = FirstSolutionStrategy.Types.Value.PathCheapestArc;
|
||||
|
||||
Assignment solution = routing.SolveWithParameters(search_parameters);
|
||||
Console.WriteLine("Status = {0}", routing.GetStatus());
|
||||
if (solution != null)
|
||||
{
|
||||
// Solution cost.
|
||||
Console.WriteLine("Cost = {0}", solution.ObjectiveValue());
|
||||
// Inspect solution.
|
||||
// Only one route here; otherwise iterate from 0 to routing.vehicles() - 1
|
||||
int route_number = 0;
|
||||
for (long node = routing.Start(route_number); !routing.IsEnd(node);
|
||||
node = solution.Value(routing.NextVar(node)))
|
||||
{
|
||||
Console.Write("{0} -> ", node);
|
||||
}
|
||||
Console.WriteLine("0");
|
||||
}
|
||||
}
|
||||
|
||||
public long Call(long first_index, long second_index) {
|
||||
int first_node = manager_.IndexToNode(first_index);
|
||||
int second_node = manager_.IndexToNode(second_index);
|
||||
return Math.Abs(xs_[first_node] - xs_[second_node]) +
|
||||
Math.Abs(ys_[first_node] - ys_[second_node]);
|
||||
public static void Main(String[] args)
|
||||
{
|
||||
int size = 10;
|
||||
if (args.Length > 0)
|
||||
{
|
||||
size = Convert.ToInt32(args[0]);
|
||||
}
|
||||
int forbidden = 0;
|
||||
if (args.Length > 1)
|
||||
{
|
||||
forbidden = Convert.ToInt32(args[1]);
|
||||
}
|
||||
int seed = 0;
|
||||
if (args.Length > 2)
|
||||
{
|
||||
seed = Convert.ToInt32(args[2]);
|
||||
}
|
||||
|
||||
Solve(size, forbidden, seed);
|
||||
}
|
||||
|
||||
private readonly int[] xs_;
|
||||
private readonly int[] ys_;
|
||||
private readonly RoutingIndexManager manager_;
|
||||
};
|
||||
|
||||
static void Solve(int size, int forbidden, int seed) {
|
||||
RoutingIndexManager manager = new RoutingIndexManager(size, 1, 0);
|
||||
RoutingModel routing = new RoutingModel(manager);
|
||||
|
||||
// Setting the cost function.
|
||||
// Put a permanent callback to the distance accessor here. The callback
|
||||
// has the following signature: ResultCallback2<int64, int64, int64>.
|
||||
// The two arguments are the from and to node inidices.
|
||||
RandomManhattan distances = new RandomManhattan(manager, size, seed);
|
||||
routing.SetArcCostEvaluatorOfAllVehicles(routing.RegisterTransitCallback(distances.Call));
|
||||
|
||||
// Forbid node connections (randomly).
|
||||
Random randomizer = new Random();
|
||||
long forbidden_connections = 0;
|
||||
while (forbidden_connections < forbidden) {
|
||||
long from = randomizer.Next(size - 1);
|
||||
long to = randomizer.Next(size - 1) + 1;
|
||||
if (routing.NextVar(from).Contains(to)) {
|
||||
Console.WriteLine("Forbidding connection {0} -> {1}", from, to);
|
||||
routing.NextVar(from).RemoveValue(to);
|
||||
++forbidden_connections;
|
||||
}
|
||||
}
|
||||
|
||||
// Add dummy dimension to test API.
|
||||
routing.AddDimension(routing.RegisterUnaryTransitCallback((long index) => { return 1; }),
|
||||
size + 1, size + 1, true, "dummy");
|
||||
|
||||
// Solve, returns a solution if any (owned by RoutingModel).
|
||||
RoutingSearchParameters search_parameters =
|
||||
operations_research_constraint_solver.DefaultRoutingSearchParameters();
|
||||
// Setting first solution heuristic (cheapest addition).
|
||||
search_parameters.FirstSolutionStrategy = FirstSolutionStrategy.Types.Value.PathCheapestArc;
|
||||
|
||||
Assignment solution = routing.SolveWithParameters(search_parameters);
|
||||
Console.WriteLine("Status = {0}", routing.GetStatus());
|
||||
if (solution != null) {
|
||||
// Solution cost.
|
||||
Console.WriteLine("Cost = {0}", solution.ObjectiveValue());
|
||||
// Inspect solution.
|
||||
// Only one route here; otherwise iterate from 0 to routing.vehicles() - 1
|
||||
int route_number = 0;
|
||||
for (long node = routing.Start(route_number); !routing.IsEnd(node);
|
||||
node = solution.Value(routing.NextVar(node))) {
|
||||
Console.Write("{0} -> ", node);
|
||||
}
|
||||
Console.WriteLine("0");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Main(String[] args) {
|
||||
int size = 10;
|
||||
if (args.Length > 0) {
|
||||
size = Convert.ToInt32(args[0]);
|
||||
}
|
||||
int forbidden = 0;
|
||||
if (args.Length > 1) {
|
||||
forbidden = Convert.ToInt32(args[1]);
|
||||
}
|
||||
int seed = 0;
|
||||
if (args.Length > 2) {
|
||||
seed = Convert.ToInt32(args[2]);
|
||||
}
|
||||
|
||||
Solve(size, forbidden, seed);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user