Format all .Net using Microsoft style

This commit is contained in:
Corentin Le Molgat
2020-11-03 10:04:19 +01:00
parent eefa058d24
commit a9385fc3d2
196 changed files with 26140 additions and 23301 deletions

View File

@@ -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("]");
}
}
}
}
}

View File

@@ -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");
}
}

View File

@@ -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());
}
}

View File

@@ -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

View File

@@ -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());
}
}

View File

@@ -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 &lt; hardMin or &gt;
/// hardMax. Then it creates penalty terms if the length is &lt; softMin or
/// &gt; 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 &lt; hardMin or &gt;
/// hardMax. Then it creates penalty terms if the length is &lt; softMin or
/// &gt; 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 &lt; hardMin or &gt; hardMax.
/// Then it creates penalty terms if the sum is &lt; softMin or &gt; 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 &lt; hardMin or &gt; hardMax.
/// Then it creates penalty terms if the sum is &lt; softMin or &gt; 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);
}
}

View File

@@ -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");
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}