port some CP C# examples to CP-SAT

This commit is contained in:
Laurent Perron
2018-11-20 14:21:43 -08:00
parent 44e52c1ef8
commit dbbe4d9829
12 changed files with 406 additions and 832 deletions

View File

@@ -28,7 +28,7 @@ using Google.OrTools.Sat;
public class GateSchedulingSat
{
static void Solve()
static void Main()
{
CpModel model = new CpModel();
@@ -146,8 +146,4 @@ public class GateSchedulingSat
Console.WriteLine(" - branches : " + solver.NumBranches());
Console.WriteLine(" - wall time : " + solver.WallTime() + " ms");
}
static void Main() {
Solve();
}
}

View File

@@ -14,7 +14,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="slow_scheduling.cs" />
<Compile Include="GateSchedulingSat.cs" />
<PackageReference Include="Google.OrTools" Version="6.9.*" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,208 @@
using System;
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 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, 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, 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, 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, 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>());
}
// 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].ToArray());
}
// 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);
// Createe 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!");
}
}
}

View File

@@ -14,7 +14,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="csjobshop.cs" />
<Compile Include="JobshopSat.cs" />
<PackageReference Include="Google.OrTools" Version="6.9.*" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,190 @@
// Copyright 2010-2018 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Google.OrTools.Sat;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System;
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;
}
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(all_vars.ToArray().Sum() == 1);
}
// Force the schedule to be consistent.
for (int slot = first_slot; slot <= last_slot; ++slot)
{
model.Add(contributions_per_slot[slot].ToArray().Sum() <= 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();
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());
}
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);
}
s.Stop();
Console.WriteLine("Finished in " + s.ElapsedMilliseconds + " ms");
}
}

View File

@@ -14,7 +14,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="techtalk_scheduling.cs" />
<Compile Include="SpeakerSchedulingSat.cs" />
<PackageReference Include="Google.OrTools" Version="6.9.*" />
</ItemGroup>
</Project>

View File

@@ -1,229 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Google.OrTools.ConstraintSolver;
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 FlexibleJobshop
{
//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 timeLimitInMs = 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 ,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, 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, 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, 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();
Solver solver = new Solver("Jobshop");
// ----- Creates all Intervals and vars -----
// All tasks
List<IntervalVar> allTasks = new List<IntervalVar>();
// Stores all tasks attached interval variables per job.
List<List<IntervalVar>>
jobsToTasks = new List<List<IntervalVar>>(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);
for (int i=0; i<machinesCount; i++) {
machinesToTasks.Add(new List<IntervalVar>());
}
// Creates all individual interval variables.
foreach (List<Task> job in myJobList) {
jobsToTasks.Add(new List<IntervalVar>());
foreach (Task task in job) {
IntervalVar oneTask = solver.MakeFixedDurationIntervalVar(
0, horizon, task.Duration, false, task.Name);
jobsToTasks[task.JobId].Add(oneTask);
allTasks.Add(oneTask);
machinesToTasks[task.Machine].Add(oneTask);
}
}
// ----- Creates model -----
// Creates precedences inside jobs.
foreach (List<IntervalVar> jobToTask in jobsToTasks) {
int tasksCount = jobToTask.Count;
for (int task_index = 0; task_index < tasksCount - 1; ++task_index) {
IntervalVar t1 = jobToTask[task_index];
IntervalVar t2 = jobToTask[task_index + 1];
Constraint prec =
solver.MakeIntervalVarRelation(t2, Solver.STARTS_AFTER_END, t1);
solver.Add(prec);
}
}
// Adds disjunctive constraints on unary resources, and creates
// sequence variables. A sequence variable is a dedicated variable
// whose job is to sequence interval variables.
SequenceVar[] allSequences = new SequenceVar[machinesCount];
for (int machineId = 0; machineId < machinesCount; ++machineId) {
string name = "Machine_" + machineId;
DisjunctiveConstraint ct =
solver.MakeDisjunctiveConstraint(machinesToTasks[machineId].ToArray(),
name);
solver.Add(ct);
allSequences[machineId] = ct.SequenceVar();
}
// Creates array of end_times of jobs.
IntVar[] allEnds = new IntVar[jobsCount];
for (int i=0; i<jobsCount; i++) {
IntervalVar task = jobsToTasks[i].Last();
allEnds[i] = task.EndExpr().Var();
}
// Objective: minimize the makespan (maximum end times of all tasks)
// of the problem.
IntVar objectiveVar = solver.MakeMax(allEnds).Var();
OptimizeVar objectiveMonitor = solver.MakeMinimize(objectiveVar, 1);
// ----- Search monitors and decision builder -----
// This decision builder will rank all tasks on all machines.
DecisionBuilder sequencePhase =
solver.MakePhase(allSequences, Solver.SEQUENCE_DEFAULT);
// After the ranking of tasks, the schedule is still loose and any
// task can be postponed at will. But, because the problem is now a PERT
// (http://en.wikipedia.org/wiki/Program_Evaluation_and_Review_Technique),
// we can schedule each task at its earliest start time. This iscs
// conveniently done by fixing the objective variable to its
// minimum value.
DecisionBuilder objPhase = solver.MakePhase(
objectiveVar, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);
// The main decision builder (ranks all tasks, then fixes the
// objectiveVariable).
DecisionBuilder mainPhase = solver.Compose(sequencePhase, objPhase);
// Search log.
const int kLogFrequency = 1000000;
SearchMonitor searchLog =
solver.MakeSearchLog(kLogFrequency, objectiveMonitor);
SearchLimit limit = null;
if (timeLimitInMs > 0) {
limit = solver.MakeTimeLimit(timeLimitInMs);
}
SolutionCollector collector = solver.MakeLastSolutionCollector();
collector.Add(allSequences);
collector.Add(allTasks.ToArray());
// Search.
bool solutionFound = solver.Solve(mainPhase, searchLog, objectiveMonitor,
limit, collector);
if(solutionFound) {
//The index of the solution from the collector
const int SOLUTION_INDEX = 0;
Assignment solution = collector.Solution(SOLUTION_INDEX);
for (int m = 0; m < machinesCount; ++m) {
Console.WriteLine("Machine " + m + " :");
SequenceVar seq = allSequences[m];
int[] storedSequence = collector.ForwardSequence(SOLUTION_INDEX, seq);
foreach (int taskIndex in storedSequence) {
IntervalVar task = seq.Interval(taskIndex);
long startMin = solution.StartMin(task);
long startMax = solution.StartMax(task);
if(startMin == startMax) {
Console.WriteLine("Task " + task.Name() + " starts at " +
startMin + ".");
}
else {
Console.WriteLine("Task " + task.Name() + " starts between " +
startMin + " and " + startMax + ".");
}
}
}
}
else {
Console.WriteLine("No solution found!");
}
}
}

View File

@@ -1,20 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<LangVersion>7.2</LangVersion>
<TargetFramework>netcoreapp2.1</TargetFramework>
<EnableDefaultItems>false</EnableDefaultItems>
<RestoreSources>../../packages;$(RestoreSources);https://api.nuget.org/v3/index.json</RestoreSources>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>
<Optimize>true</Optimize>
<GenerateTailCalls>true</GenerateTailCalls>
</PropertyGroup>
<ItemGroup>
<Compile Include="gate_scheduling_sat.cs" />
<PackageReference Include="Google.OrTools" Version="6.9.*" />
</ItemGroup>
</Project>

View File

@@ -33,7 +33,7 @@ public class JobshopFt06Sat
public IntervalVar interval;
}
static void Solve()
static void Main()
{
int[,] durations = new int[,] { {1, 3, 6, 7, 3, 6},
{8, 5, 10, 10, 10, 4},
@@ -131,8 +131,4 @@ public class JobshopFt06Sat
// Statistics.
Console.WriteLine(solver.ResponseStats());
}
static void Main() {
Solve();
}
}

View File

@@ -1,284 +0,0 @@
//
// Copyright 2013 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Google.OrTools.ConstraintSolver;
using Google.OrTools.Graph;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System;
public class SpeakerScheduling
{
public class FlowAssign : NetDecisionBuilder
{
public FlowAssign(IntVar[] vars, int first_slot, IntVar last_slot_var)
{
vars_ = vars;
first_slot_ = first_slot;
last_slot_var_ = last_slot_var;
}
public override Decision Next(Solver solver)
{
int large = 100000;
int number_of_variables = vars_.Length;
long last_slot = last_slot_var_.Max();
// Lets build a bipartite graph with equal number of nodes left and right.
// Variables will be on the left, slots on the right.
// We will add dummy variables when needed.
// Arcs will have a cost x is slot x is possible for a variable, a large
// number otherwise. For dummy variables, the cost will be 0 always.
LinearSumAssignment matching = new LinearSumAssignment();
for (int speaker = 0; speaker < number_of_variables; ++speaker)
{
IntVar var = vars_[speaker];
for (int value = first_slot_; value <= last_slot; ++value)
{
if (var.Contains(value))
{
matching.AddArcWithCost(speaker, value - first_slot_, value);
}
else
{
matching.AddArcWithCost(speaker, value - first_slot_, large);
}
}
}
// The Matching algorithms expect the same number of left and right nodes.
// So we fill the rest with dense zero-cost arcs.
for (int dummy = number_of_variables;
dummy <= last_slot - first_slot_; ++dummy) {
for (int value = first_slot_; value <= last_slot; ++value)
{
matching.AddArcWithCost(dummy, value - first_slot_, 0);
}
}
if (matching.Solve() == LinearSumAssignment.OPTIMAL &&
matching.OptimalCost() < large) // No violated arcs.
{
for (int speaker = 0; speaker < number_of_variables; ++speaker)
{
vars_[speaker].SetValue(matching.RightMate(speaker) + first_slot_);
}
} else {
solver.Fail();
}
return null;
}
private IntVar[] vars_;
private int first_slot_;
private IntVar last_slot_var_;
}
private static void Solve(int first_slot)
{
Console.WriteLine("----------------------------------------------------");
Solver solver = new Solver("SpeakerScheduling");
// 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 {0} speakers, for a total of {1} slots, during [{2}..{3}]",
number_of_speakers, sum_of_durations, first_slot, last_slot);
// Start variable for all talks.
IntVar[] starts = new IntVar[number_of_speakers];
// We store the possible starts for all talks filtered from the
// duration and the speaker availability.
int[][] possible_starts = new int[number_of_speakers][];
for (int speaker = 0; speaker < number_of_speakers; ++speaker)
{
int duration = durations[speaker];
// Let's filter the possible starts.
List<int> filtered_starts = new List<int>();
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 < durations[speaker]; ++offset)
{
if (index + offset >= availability ||
speaker_availability[speaker][index + offset] != slot + offset)
{
// discontinuity.
ok = false;
break;
}
}
if (ok)
{
filtered_starts.Add(slot);
}
possible_starts[speaker] = filtered_starts.ToArray();
}
starts[speaker] =
solver.MakeIntVar(possible_starts[speaker], "start[" + speaker + "]");
}
List<IntVar>[] contributions_per_slot =
new List<IntVar>[last_slot + 1];
for (int slot = first_slot; slot <= last_slot; ++slot)
{
contributions_per_slot[slot] = new List<IntVar>();
}
for (int speaker = 0; speaker < number_of_speakers; ++speaker)
{
int duration = durations[speaker];
IntVar start_var = starts[speaker];
foreach (int start in possible_starts[speaker])
{
for (int offset = 0; offset < duration; ++offset)
{
contributions_per_slot[start + offset].Add(start_var.IsEqual(start));
}
}
}
// Force the schedule to be consistent.
for (int slot = first_slot; slot <= last_slot; ++slot)
{
solver.Add(
solver.MakeSumLessOrEqual(contributions_per_slot[slot].ToArray(), 1));
}
// Add minimum start info.
for (int speaker = 0; speaker < number_of_speakers; ++speaker)
{
solver.Add(starts[speaker] >= first_slot);
}
// Creates makespan.
IntVar[] end_times = new IntVar[number_of_speakers];
for (int speaker = 0; speaker < number_of_speakers; speaker++)
{
end_times[speaker] = (starts[speaker] + (durations[speaker] - 1)).Var();
}
IntVar last_slot_var = end_times.Max().VarWithName("last_slot");
// Add trivial bound to objective.
last_slot_var.SetMin(first_slot + sum_of_durations - 1);
// Redundant scheduling constraint.
IntervalVar[] intervals =
solver.MakeFixedDurationIntervalVarArray(starts, durations, "intervals");
DisjunctiveConstraint disjunctive =
solver.MakeDisjunctiveConstraint(intervals, "disjunctive");
solver.Add(disjunctive);
//
// Search
//
List<IntVar> short_talks = new List<IntVar>();
List<IntVar> long_talks = new List<IntVar>();
for (int speaker = 0; speaker < number_of_speakers; ++speaker)
{
if (durations[speaker] == 1)
{
short_talks.Add(starts[speaker]);
}
else
{
long_talks.Add(starts[speaker]);
}
}
OptimizeVar objective_monitor = solver.MakeMinimize(last_slot_var, 1);
DecisionBuilder long_phase =
solver.MakePhase(long_talks.ToArray(),
Solver.CHOOSE_MIN_SIZE_LOWEST_MIN,
Solver.ASSIGN_MIN_VALUE);
DecisionBuilder short_phase =
new FlowAssign(short_talks.ToArray(), first_slot, last_slot_var);
DecisionBuilder obj_phase =
solver.MakePhase(last_slot_var,
Solver.CHOOSE_FIRST_UNBOUND,
Solver.ASSIGN_MIN_VALUE);
DecisionBuilder main_phase =
solver.Compose(long_phase, short_phase, obj_phase);
solver.NewSearch(main_phase, objective_monitor);
while (solver.NextSolution())
{
Console.WriteLine("\nLast used slot: " + (last_slot_var.Value()));
Console.WriteLine("Speakers (start..end):");
for (int s = 0; s < number_of_speakers; s++)
{
long sstart = starts[s].Value();
Console.WriteLine(" - speaker {0,2}: {1,2}..{2,2}", (s + 1),
sstart, (sstart + durations[s] - 1));
}
}
Console.WriteLine("\nSolutions: {0}", solver.Solutions());
Console.WriteLine("WallTime: {0}ms", solver.WallTime());
Console.WriteLine("Failures: {0}", solver.Failures());
Console.WriteLine("Branches: {0} ", solver.Branches());
solver.EndSearch();
}
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);
}
s.Stop();
Console.WriteLine("Finished in " + s.ElapsedMilliseconds + " ms");
}
}

View File

@@ -1,282 +0,0 @@
// Copyright 2010-2018 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using Google.OrTools.ConstraintSolver;
using Google.OrTools.Graph;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System;
public class SpeakerScheduling
{
public class FlowAssign : NetDecisionBuilder
{
public FlowAssign(IntVar[] vars, int first_slot, IntVar last_slot_var)
{
vars_ = vars;
first_slot_ = first_slot;
last_slot_var_ = last_slot_var;
}
public override Decision Next(Solver solver)
{
int large = 100000;
int number_of_variables = vars_.Length;
long last_slot = last_slot_var_.Max();
// Lets build a bipartite graph with equal number of nodes left and right.
// Variables will be on the left, slots on the right.
// We will add dummy variables when needed.
// Arcs will have a cost x is slot x is possible for a variable, a large
// number otherwise. For dummy variables, the cost will be 0 always.
LinearSumAssignment matching = new LinearSumAssignment();
for (int speaker = 0; speaker < number_of_variables; ++speaker)
{
IntVar var = vars_[speaker];
for (int value = first_slot_; value <= last_slot; ++value)
{
if (var.Contains(value))
{
matching.AddArcWithCost(speaker, value - first_slot_, value);
}
else
{
matching.AddArcWithCost(speaker, value - first_slot_, large);
}
}
}
// The Matching algorithms expect the same number of left and right nodes.
// So we fill the rest with dense zero-cost arcs.
for (int dummy = number_of_variables;
dummy <= last_slot - first_slot_; ++dummy) {
for (int value = first_slot_; value <= last_slot; ++value)
{
matching.AddArcWithCost(dummy, value - first_slot_, 0);
}
}
if (matching.Solve() == LinearSumAssignment.OPTIMAL &&
matching.OptimalCost() < large) // No violated arcs.
{
for (int speaker = 0; speaker < number_of_variables; ++speaker)
{
vars_[speaker].SetValue(matching.RightMate(speaker) + first_slot_);
}
} else {
solver.Fail();
}
return null;
}
private IntVar[] vars_;
private int first_slot_;
private IntVar last_slot_var_;
}
private static void Solve(int first_slot)
{
Console.WriteLine("----------------------------------------------------");
Solver solver = new Solver("SpeakerScheduling");
// 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 {0} speakers, for a total of {1} slots, during [{2}..{3}]",
number_of_speakers, sum_of_durations, first_slot, last_slot);
// Start variable for all talks.
IntVar[] starts = new IntVar[number_of_speakers];
// We store the possible starts for all talks filtered from the
// duration and the speaker availability.
int[][] possible_starts = new int[number_of_speakers][];
for (int speaker = 0; speaker < number_of_speakers; ++speaker)
{
int duration = durations[speaker];
// Let's filter the possible starts.
List<int> filtered_starts = new List<int>();
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)
{
filtered_starts.Add(slot);
}
possible_starts[speaker] = filtered_starts.ToArray();
}
starts[speaker] =
solver.MakeIntVar(possible_starts[speaker], "start[" + speaker + "]");
}
List<IntVar>[] contributions_per_slot =
new List<IntVar>[last_slot + 1];
for (int slot = first_slot; slot <= last_slot; ++slot)
{
contributions_per_slot[slot] = new List<IntVar>();
}
for (int speaker = 0; speaker < number_of_speakers; ++speaker)
{
int duration = durations[speaker];
IntVar start_var = starts[speaker];
foreach (int start in possible_starts[speaker])
{
for (int offset = 0; offset < duration; ++offset)
{
contributions_per_slot[start + offset].Add(start_var.IsEqual(start));
}
}
}
// Force the schedule to be consistent.
for (int slot = first_slot; slot <= last_slot; ++slot)
{
solver.Add(
solver.MakeSumLessOrEqual(contributions_per_slot[slot].ToArray(), 1));
}
// Add minimum start info.
for (int speaker = 0; speaker < number_of_speakers; ++speaker)
{
solver.Add(starts[speaker] >= first_slot);
}
// Creates makespan.
IntVar[] end_times = new IntVar[number_of_speakers];
for (int speaker = 0; speaker < number_of_speakers; speaker++)
{
end_times[speaker] = (starts[speaker] + (durations[speaker] - 1)).Var();
}
IntVar last_slot_var = end_times.Max().VarWithName("last_slot");
// Add trivial bound to objective.
last_slot_var.SetMin(first_slot + sum_of_durations - 1);
// Redundant scheduling constraint.
IntervalVar[] intervals =
solver.MakeFixedDurationIntervalVarArray(starts, durations, "intervals");
DisjunctiveConstraint disjunctive =
solver.MakeDisjunctiveConstraint(intervals, "disjunctive");
solver.Add(disjunctive);
//
// Search
//
List<IntVar> short_talks = new List<IntVar>();
List<IntVar> long_talks = new List<IntVar>();
for (int speaker = 0; speaker < number_of_speakers; ++speaker)
{
if (durations[speaker] == 1)
{
short_talks.Add(starts[speaker]);
}
else
{
long_talks.Add(starts[speaker]);
}
}
OptimizeVar objective_monitor = solver.MakeMinimize(last_slot_var, 1);
DecisionBuilder long_phase =
solver.MakePhase(long_talks.ToArray(),
Solver.CHOOSE_MIN_SIZE_LOWEST_MIN,
Solver.ASSIGN_MIN_VALUE);
DecisionBuilder short_phase =
new FlowAssign(short_talks.ToArray(), first_slot, last_slot_var);
DecisionBuilder obj_phase =
solver.MakePhase(last_slot_var,
Solver.CHOOSE_FIRST_UNBOUND,
Solver.ASSIGN_MIN_VALUE);
DecisionBuilder main_phase =
solver.Compose(long_phase, short_phase, obj_phase);
solver.NewSearch(main_phase, objective_monitor);
while (solver.NextSolution())
{
Console.WriteLine("\nLast used slot: " + (last_slot_var.Value()));
Console.WriteLine("Speakers (start..end):");
for (int s = 0; s < number_of_speakers; s++)
{
long sstart = starts[s].Value();
Console.WriteLine(" - speaker {0,2}: {1,2}..{2,2}", (s + 1),
sstart, (sstart + durations[s] - 1));
}
}
Console.WriteLine("\nSolutions: {0}", solver.Solutions());
Console.WriteLine("WallTime: {0}ms", solver.WallTime());
Console.WriteLine("Failures: {0}", solver.Failures());
Console.WriteLine("Branches: {0} ", solver.Branches());
solver.EndSearch();
}
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);
}
s.Stop();
Console.WriteLine("Finished in " + s.ElapsedMilliseconds + " ms");
}
}

View File

@@ -568,7 +568,6 @@ test_dotnet_examples_csharp: \
rdotnet_cscvrptw.cs \
rdotnet_csflow.cs \
rdotnet_csintegerprogramming.cs \
rdotnet_csjobshop.cs \
rdotnet_csknapsack.cs \
rdotnet_cslinearprogramming.cs \
rdotnet_csls_api.cs \
@@ -587,11 +586,12 @@ test_dotnet_examples_csharp: \
rdotnet_furniture_moving.cs \
rdotnet_furniture_moving_intervals.cs \
rdotnet_futoshiki.cs \
rdotnet_gate_scheduling_sat.cs \
rdotnet_GateSchedulingSat.cs \
rdotnet_golomb_ruler.cs \
rdotnet_grocery.cs \
rdotnet_hidato_table.cs \
rdotnet_jobshop_ft06_sat.cs \
rdotnet_JobshopSat.cs \
rdotnet_just_forgotten.cs \
rdotnet_kakuro.cs \
rdotnet_kenken2.cs \
@@ -641,14 +641,13 @@ test_dotnet_examples_csharp: \
rdotnet_set_partition.cs \
rdotnet_sicherman_dice.cs \
rdotnet_ski_assignment.cs \
rdotnet_slow_scheduling.cs \
rdotnet_SpeakerSchedulingSat.cs \
rdotnet_stable_marriage.cs \
rdotnet_strimko2.cs \
rdotnet_subset_sum.cs \
rdotnet_sudoku.cs \
rdotnet_survo_puzzle.cs \
rdotnet_TaskScheduling.cs \
rdotnet_techtalk_scheduling.cs \
rdotnet_to_num.cs \
rdotnet_traffic_lights.cs \
rdotnet_tsp.cs \