diff --git a/examples/dotnet/CoverRectangleSat.cs b/examples/dotnet/CoverRectangleSat.cs
index 77b38d89d07..ee4d92ec2e2 100644
--- a/examples/dotnet/CoverRectangleSat.cs
+++ b/examples/dotnet/CoverRectangleSat.cs
@@ -1,150 +1,150 @@
-// Copyright 2010-2025 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 System;
-using System.Collections.Generic;
-using System.Linq;
-using Google.OrTools.Sat;
-
-///
-/// Fill a 60x50 rectangle exactly using a minimum number of non-overlapping squares."""
-///
-class CoverRectangleSat
-{
- static int sizeX = 60;
- static int sizeY = 50;
-
- static bool CoverRectangle(int numSquares)
- {
- CpModel model = new CpModel();
-
- var areas = new List();
- var sizes = new List();
- var xIntervals = new List();
- var yIntervals = new List();
- var xStarts = new List();
- var yStarts = new List();
-
- // Creates intervals for the NoOverlap2D and size variables.
- foreach (var i in Enumerable.Range(0, numSquares))
- {
- var size = model.NewIntVar(1, sizeY, String.Format("size_{0}", i));
- var startX = model.NewIntVar(0, sizeX, String.Format("startX_{0}", i));
- var endX = model.NewIntVar(0, sizeX, String.Format("endX_{0}", i));
- var startY = model.NewIntVar(0, sizeY, String.Format("startY_{0}", i));
- var endY = model.NewIntVar(0, sizeY, String.Format("endY_{0}", i));
-
- var intervalX = model.NewIntervalVar(startX, size, endX, String.Format("intervalX_{0}", i));
- var intervalY = model.NewIntervalVar(startY, size, endY, String.Format("intervalY_{0}", i));
-
- var area = model.NewIntVar(1, sizeY * sizeY, String.Format("area_{0}", i));
- model.AddMultiplicationEquality(area, size, size);
-
- areas.Add(area);
- xIntervals.Add(intervalX);
- yIntervals.Add(intervalY);
- sizes.Add(size);
- xStarts.Add(startX);
- yStarts.Add(startY);
- }
-
- // Main constraint.
- NoOverlap2dConstraint noOverlap2d = model.AddNoOverlap2D();
- foreach (var i in Enumerable.Range(0, numSquares))
- {
- noOverlap2d.AddRectangle(xIntervals[i], yIntervals[i]);
- }
-
- // Redundant constraints.
- model.AddCumulative(sizeY).AddDemands(xIntervals, sizes);
- model.AddCumulative(sizeX).AddDemands(yIntervals, sizes);
-
- // Forces the rectangle to be exactly covered.
- model.Add(LinearExpr.Sum(areas) == sizeX * sizeY);
-
- // Symmetry breaking 1: sizes are ordered.
- foreach (var i in Enumerable.Range(0, numSquares - 1))
- {
- model.Add(sizes[i] <= sizes[i + 1]);
-
- // Define same to be true iff sizes[i] == sizes[i + 1]
- var same = model.NewBoolVar("");
- model.Add(sizes[i] == sizes[i + 1]).OnlyEnforceIf(same);
- model.Add(sizes[i] < sizes[i + 1]).OnlyEnforceIf(same.Not());
-
- // Tie break with starts.
- model.Add(xStarts[i] <= xStarts[i + 1]).OnlyEnforceIf(same);
- }
-
- // Symmetry breaking 2: first square in one quadrant.
- model.Add(xStarts[0] < (sizeX + 1) / 2);
- model.Add(yStarts[0] < (sizeY + 1) / 2);
-
- // Creates a solver and solves.
- var solver = new CpSolver();
- solver.StringParameters = "num_search_workers:16, log_search_progress: false, max_time_in_seconds:10";
- var status = solver.Solve(model);
- Console.WriteLine(string.Format("{0} found in {1:0.00}s", status, solver.WallTime()));
-
- // Prints solution.
- bool solution_found = status == CpSolverStatus.Optimal || status == CpSolverStatus.Feasible;
- if (solution_found)
- {
- char[][] output = new char [sizeY][];
- foreach (var y in Enumerable.Range(0, sizeY))
- {
-
- output[y] = new char[sizeX];
- foreach (var x in Enumerable.Range(0, sizeX))
- {
- output[y][x] = ' ';
- }
- }
-
- foreach (var s in Enumerable.Range(0, numSquares))
- {
- int startX = (int)solver.Value(xStarts[s]);
- int startY = (int)solver.Value(yStarts[s]);
- int size = (int)solver.Value(sizes[s]);
- char c = (char)(65 + s);
- foreach (var x in Enumerable.Range(startX, size))
- {
- foreach (var y in Enumerable.Range(startY, size))
- {
- if (output[y][x] != ' ')
- {
- Console.WriteLine(
- string.Format("Error at position x={0} y{1}, found {2}", x, y, output[y][x]));
- }
- output[y][x] = c;
- }
- }
- }
- foreach (var y in Enumerable.Range(0, sizeY))
- {
- Console.WriteLine(new String(output[y], 0, sizeX));
- }
- }
- return solution_found;
- }
-
- static void Main()
- {
- foreach (int numSquares in Enumerable.Range(1, 15))
- {
- Console.WriteLine("Trying with size = {0}", numSquares);
- if (CoverRectangle(numSquares))
- break;
- }
- }
-}
+// Copyright 2010-2025 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 System;
+using System.Collections.Generic;
+using System.Linq;
+using Google.OrTools.Sat;
+
+///
+/// Fill a 60x50 rectangle exactly using a minimum number of non-overlapping squares."""
+///
+class CoverRectangleSat
+{
+ static int sizeX = 60;
+ static int sizeY = 50;
+
+ static bool CoverRectangle(int numSquares)
+ {
+ CpModel model = new CpModel();
+
+ var areas = new List();
+ var sizes = new List();
+ var xIntervals = new List();
+ var yIntervals = new List();
+ var xStarts = new List();
+ var yStarts = new List();
+
+ // Creates intervals for the NoOverlap2D and size variables.
+ foreach (var i in Enumerable.Range(0, numSquares))
+ {
+ var size = model.NewIntVar(1, sizeY, String.Format("size_{0}", i));
+ var startX = model.NewIntVar(0, sizeX, String.Format("startX_{0}", i));
+ var endX = model.NewIntVar(0, sizeX, String.Format("endX_{0}", i));
+ var startY = model.NewIntVar(0, sizeY, String.Format("startY_{0}", i));
+ var endY = model.NewIntVar(0, sizeY, String.Format("endY_{0}", i));
+
+ var intervalX = model.NewIntervalVar(startX, size, endX, String.Format("intervalX_{0}", i));
+ var intervalY = model.NewIntervalVar(startY, size, endY, String.Format("intervalY_{0}", i));
+
+ var area = model.NewIntVar(1, sizeY * sizeY, String.Format("area_{0}", i));
+ model.AddMultiplicationEquality(area, size, size);
+
+ areas.Add(area);
+ xIntervals.Add(intervalX);
+ yIntervals.Add(intervalY);
+ sizes.Add(size);
+ xStarts.Add(startX);
+ yStarts.Add(startY);
+ }
+
+ // Main constraint.
+ NoOverlap2dConstraint noOverlap2d = model.AddNoOverlap2D();
+ foreach (var i in Enumerable.Range(0, numSquares))
+ {
+ noOverlap2d.AddRectangle(xIntervals[i], yIntervals[i]);
+ }
+
+ // Redundant constraints.
+ model.AddCumulative(sizeY).AddDemands(xIntervals, sizes);
+ model.AddCumulative(sizeX).AddDemands(yIntervals, sizes);
+
+ // Forces the rectangle to be exactly covered.
+ model.Add(LinearExpr.Sum(areas) == sizeX * sizeY);
+
+ // Symmetry breaking 1: sizes are ordered.
+ foreach (var i in Enumerable.Range(0, numSquares - 1))
+ {
+ model.Add(sizes[i] <= sizes[i + 1]);
+
+ // Define same to be true iff sizes[i] == sizes[i + 1]
+ var same = model.NewBoolVar("");
+ model.Add(sizes[i] == sizes[i + 1]).OnlyEnforceIf(same);
+ model.Add(sizes[i] < sizes[i + 1]).OnlyEnforceIf(same.Not());
+
+ // Tie break with starts.
+ model.Add(xStarts[i] <= xStarts[i + 1]).OnlyEnforceIf(same);
+ }
+
+ // Symmetry breaking 2: first square in one quadrant.
+ model.Add(xStarts[0] < (sizeX + 1) / 2);
+ model.Add(yStarts[0] < (sizeY + 1) / 2);
+
+ // Creates a solver and solves.
+ var solver = new CpSolver();
+ solver.StringParameters = "num_search_workers:16, log_search_progress: false, max_time_in_seconds:10";
+ var status = solver.Solve(model);
+ Console.WriteLine(string.Format("{0} found in {1:0.00}s", status, solver.WallTime()));
+
+ // Prints solution.
+ bool solution_found = status == CpSolverStatus.Optimal || status == CpSolverStatus.Feasible;
+ if (solution_found)
+ {
+ char[][] output = new char [sizeY][];
+ foreach (var y in Enumerable.Range(0, sizeY))
+ {
+
+ output[y] = new char[sizeX];
+ foreach (var x in Enumerable.Range(0, sizeX))
+ {
+ output[y][x] = ' ';
+ }
+ }
+
+ foreach (var s in Enumerable.Range(0, numSquares))
+ {
+ int startX = (int)solver.Value(xStarts[s]);
+ int startY = (int)solver.Value(yStarts[s]);
+ int size = (int)solver.Value(sizes[s]);
+ char c = (char)(65 + s);
+ foreach (var x in Enumerable.Range(startX, size))
+ {
+ foreach (var y in Enumerable.Range(startY, size))
+ {
+ if (output[y][x] != ' ')
+ {
+ Console.WriteLine(
+ string.Format("Error at position x={0} y{1}, found {2}", x, y, output[y][x]));
+ }
+ output[y][x] = c;
+ }
+ }
+ }
+ foreach (var y in Enumerable.Range(0, sizeY))
+ {
+ Console.WriteLine(new String(output[y], 0, sizeX));
+ }
+ }
+ return solution_found;
+ }
+
+ static void Main()
+ {
+ foreach (int numSquares in Enumerable.Range(1, 15))
+ {
+ Console.WriteLine("Trying with size = {0}", numSquares);
+ if (CoverRectangle(numSquares))
+ break;
+ }
+ }
+}
diff --git a/examples/dotnet/TaskSchedulingSat.cs b/examples/dotnet/TaskSchedulingSat.cs
index bc020f0db71..dddd2bb3975 100644
--- a/examples/dotnet/TaskSchedulingSat.cs
+++ b/examples/dotnet/TaskSchedulingSat.cs
@@ -1,199 +1,199 @@
-// Copyright 2010-2025 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 System;
-using System.Collections.Generic;
-using Google.OrTools.Sat;
-
-class Job
-{
- public Job(List tasks)
- {
- AlternativeTasks = tasks;
- }
- public Job Successor { get; set; }
- public List 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 myJobList = new List();
- public static Dictionary> tasksToEquipment = new Dictionary>();
- public static Dictionary taskIndexes = new Dictionary();
-
- public static void InitTaskList()
- {
- List taskList = new List();
- 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();
- 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();
- 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();
- 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();
- 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();
- 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();
- 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();
- 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();
- 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];
- BoolVar[] taskChoosed = new BoolVar[taskCount];
- IntVar[] allEnds = new IntVar[GetEndTaskCount()];
-
- int endJobCounter = 0;
- foreach (Job j in myJobList)
- {
- BoolVar[] tmp = new BoolVar[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();
- tasksToEquipment[t.Equipment].Add(tasks[ti]);
- }
- model.AddExactlyOne(tmp);
- }
-
- foreach (KeyValuePair> 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());
- }
-}
+// Copyright 2010-2025 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 System;
+using System.Collections.Generic;
+using Google.OrTools.Sat;
+
+class Job
+{
+ public Job(List tasks)
+ {
+ AlternativeTasks = tasks;
+ }
+ public Job Successor { get; set; }
+ public List 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 myJobList = new List();
+ public static Dictionary> tasksToEquipment = new Dictionary>();
+ public static Dictionary taskIndexes = new Dictionary();
+
+ public static void InitTaskList()
+ {
+ List taskList = new List();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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];
+ BoolVar[] taskChoosed = new BoolVar[taskCount];
+ IntVar[] allEnds = new IntVar[GetEndTaskCount()];
+
+ int endJobCounter = 0;
+ foreach (Job j in myJobList)
+ {
+ BoolVar[] tmp = new BoolVar[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();
+ tasksToEquipment[t.Equipment].Add(tasks[ti]);
+ }
+ model.AddExactlyOne(tmp);
+ }
+
+ foreach (KeyValuePair> 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());
+ }
+}
diff --git a/examples/python/testdata/salbp_20_1.alb b/examples/python/testdata/salbp_20_1.alb
index 6b780dae89b..e3d93785827 100644
--- a/examples/python/testdata/salbp_20_1.alb
+++ b/examples/python/testdata/salbp_20_1.alb
@@ -1,51 +1,51 @@
-
-20
-
-
-1000
-
-
-0,268
-
-
-
-1 142
-2 34
-3 140
-4 214
-5 121
-6 279
-7 50
-8 282
-9 129
-10 175
-11 97
-12 132
-13 107
-14 132
-15 69
-16 169
-17 73
-18 231
-19 120
-20 186
-
-
-1,6
-2,7
-4,8
-5,9
-6,10
-7,11
-8,12
-10,13
-11,13
-12,14
-12,15
-13,16
-13,17
-13,18
-14,20
-15,19
-
-
+
+20
+
+
+1000
+
+
+0,268
+
+
+
+1 142
+2 34
+3 140
+4 214
+5 121
+6 279
+7 50
+8 282
+9 129
+10 175
+11 97
+12 132
+13 107
+14 132
+15 69
+16 169
+17 73
+18 231
+19 120
+20 186
+
+
+1,6
+2,7
+4,8
+5,9
+6,10
+7,11
+8,12
+10,13
+11,13
+12,14
+12,15
+13,16
+13,17
+13,18
+14,20
+15,19
+
+
diff --git a/examples/tests/issue33.cs b/examples/tests/issue33.cs
index 5c78383a347..2e888ca2072 100644
--- a/examples/tests/issue33.cs
+++ b/examples/tests/issue33.cs
@@ -1,676 +1,676 @@
-// Authors: Johan Wessén
-// Copyright 2010-2025 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 System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System;
-
-public class Task
-{
- public int Id { get; private set; }
- public int TaskType { get; private set; }
- public int LocationId { get; private set; }
- public Dictionary Durations { get; private set; }
- public int TaskPosition { get; private set; }
-
- public Task(int id, int taskType, int locationIndex, int taskPosition, Dictionary durations)
- {
- Id = id;
- TaskType = taskType;
- LocationId = locationIndex;
- Durations = durations;
- TaskPosition = taskPosition;
- }
-
- public Task(int id, int taskType, int locationIndex, int taskPosition)
- {
- Id = id;
- TaskType = taskType;
- LocationId = locationIndex;
- TaskPosition = taskPosition;
- Durations = new Dictionary();
- }
-}
-
-public class WorkLocation
-{
- public int Id { get; private set; }
- public int NbTasks
- {
- get {
- Debug.Assert(Tasks != null);
- return Tasks.Length;
- }
- set {
- Debug.Assert(Tasks == null);
- Tasks = new Task[value];
- }
- }
- public Task[] Tasks { get; private set; }
-
- public WorkLocation(int index)
- {
- Id = index;
- }
-}
-
-public class Tool
-{
- public int Id { get; private set; }
- public HashSet TaskTypes { get; set; }
- public int[,] TravellingTime { get; set; }
- public int InitialLocationId { get; set; }
-
- public Tool(int index, int initialLocation = 0)
- {
- Id = index;
- InitialLocationId = initialLocation;
- TaskTypes = new HashSet();
- }
-
- public void AddTaskType(int t)
- {
- TaskTypes.Add(t);
- }
-
- public bool CanPerformTaskType(int taskType)
- {
- return TaskTypes.Contains(taskType);
- }
-}
-
-public class FactoryDescription
-{
- public Tool[] Tools { get; private set; }
- public WorkLocation[] Locations { get; private set; }
-
- public int NbWorkLocations
- {
- get {
- return Locations.Length;
- }
- }
- public int NbTools
- {
- get {
- return Tools.Length;
- }
- }
-
- public int NbTaskPerCycle { get; private set; }
- // TaskType go typically from 0 to 6. InspectionType indicates which
- // is the TaskType that correspond to Inspection.
- public int Inspection { get; private set; }
- // All the time within the schedule horizon in which the blast can start.
- public long[] InspectionStarts { get; private set; }
-
- public int Horizon { get; private set; }
-
- // horizon equal to 2 weeks (in minutes).
- public FactoryDescription(int nbTools, int nbLocations, int nbTaskPerCycle, int horizon = 14 * 24 * 60)
- {
- Debug.Assert(nbTools > 0);
- Debug.Assert(nbLocations > 0);
- Debug.Assert(nbTaskPerCycle > 0);
- Debug.Assert(horizon > 0);
- NbTaskPerCycle = nbTaskPerCycle;
- Inspection = NbTaskPerCycle - 1;
- Tools = new Tool[nbTools];
- Horizon = horizon;
- for (int i = 0; i < nbTools; i++)
- Tools[i] = new Tool(i);
- Locations = new WorkLocation[nbLocations];
- for (int i = 0; i < nbLocations; i++)
- Locations[i] = new WorkLocation(i);
-
- InspectionStarts = new long[] { -1, 600, 1200, 1800, 2400, 2800 };
- }
-
- public Tool[] getToolPerTaskType(int taskType)
- {
- var elements = from tool in Tools
- where tool.CanPerformTaskType(taskType) select tool;
- return elements.ToArray();
- }
-
- public Task[] getFlatTaskList()
- {
- return (from location in Locations from task in location.Tasks orderby task.Id select task).ToArray();
- }
-
- public int[] getTaskTypes()
- {
- return (from location in Locations from task in location.Tasks select task.TaskType).Distinct().ToArray();
- }
-
- // TODO: This should be enhanced
- public void SanityCheck()
- {
- foreach (Tool tool in Tools)
- {
- Debug.Assert(tool.TravellingTime.GetLength(0) == NbWorkLocations);
- Debug.Assert(tool.TravellingTime.GetLength(1) == NbWorkLocations);
- for (int i = 0; i < NbWorkLocations; i++)
- Debug.Assert(tool.TravellingTime[i, i] == 0);
- }
- }
-}
-
-interface DataReader
-{
- FactoryDescription FetchData();
-}
-
-public class SmallSyntheticData : DataReader
-{
- public SmallSyntheticData()
- {
- }
-
- public FactoryDescription FetchData()
- {
- // deterministic seed for result reproducibility
- Random randomDuration = new Random(2);
-
- // FactoryDescription(nbTools, nblocations, nbTasks per cycle)
- FactoryDescription factoryDescription = new FactoryDescription(5, 4, 3);
-
- // Travelling time and distance are temporarily identical and they
- // are no different for different tools
- int[,] travellingTime = new int[factoryDescription.NbWorkLocations, factoryDescription.NbWorkLocations];
- for (int i = 0; i < travellingTime.GetLength(0); i++)
- {
- for (int j = 0; j < travellingTime.GetLength(1); j++)
- {
- if (i == j)
- travellingTime[i, j] = 0;
- else
- travellingTime[i, j] = (5 * Math.Abs(i - j)) * 10;
- }
- }
-
- factoryDescription.Tools[0].AddTaskType(0);
- factoryDescription.Tools[1].AddTaskType(0);
- factoryDescription.Tools[2].AddTaskType(1);
- factoryDescription.Tools[3].AddTaskType(1);
- factoryDescription.Tools[4].AddTaskType(2);
- factoryDescription.Tools[1].AddTaskType(1);
-
- foreach (Tool tool in factoryDescription.Tools)
- tool.TravellingTime = travellingTime;
-
- int c = 0;
- int nbCyclePerWorkLocation = 2;
- int[] boll = new int[100];
- for (int i = 0; i < factoryDescription.NbWorkLocations; i++)
- {
- factoryDescription.Locations[i].NbTasks = nbCyclePerWorkLocation * factoryDescription.NbTaskPerCycle;
- for (int j = 0; j < nbCyclePerWorkLocation; j++)
- {
- for (int k = 0; k < factoryDescription.NbTaskPerCycle; k++)
- {
- Task t = new Task(c, k, i, k + j * factoryDescription.NbTaskPerCycle);
-
- // Filling in tool-dependent durations
- Tool[] compatibleTools = factoryDescription.getToolPerTaskType(k);
- foreach (Tool tool in compatibleTools)
- {
- boll[c] = randomDuration.Next(13, 17) * 10;
- ;
- t.Durations[tool.Id] = boll[c];
- }
- factoryDescription.Locations[i].Tasks[t.TaskPosition] = t;
- c++;
- }
- }
- }
-
- factoryDescription.SanityCheck();
- return factoryDescription;
- }
-}
-
-public class RandomSelectToolHeuristic : NetDecisionBuilder
-{
- private FactoryScheduling factoryScheduling;
- private Random rnd;
-
- public RandomSelectToolHeuristic(FactoryScheduling factoryScheduling, int seed)
- {
- this.factoryScheduling = factoryScheduling;
- // deterministic seed for result reproducibility
- this.rnd = new Random(seed);
- }
-
- public override Decision Next(Solver solver)
- {
- foreach (IntVar var in factoryScheduling.SelectedTool)
- {
- if (!var.Bound())
- {
- int min = (int)var.Min();
- int max = (int)var.Max();
- int rndVal = rnd.Next(min, max + 1);
- while (!var.Contains(rndVal))
- rndVal = rnd.Next(min, max + 1);
- return solver.MakeAssignVariableValue(var, rndVal);
- }
- }
- return null;
- }
-}
-
-class TaskAlternative
-{
- public Task Task { get; private set; }
- public IntVar ToolVar { get; set; }
- public List Intervals { get; private set; }
-
- public TaskAlternative(Task t)
- {
- Task = t;
- Intervals = new List();
- }
-}
-
-public class FactoryScheduling
-{
- private FactoryDescription factoryData;
- private Solver solver;
-
- private Task[] tasks;
- private int[] taskTypes;
-
- /* Flat list of all the tasks */
- private TaskAlternative[] taskStructures;
-
- /* Task per WorkLocation: location2Task[d][i]: the i-th task of the
- * d-th location */
- private TaskAlternative[][] location2Task;
-
- /* Task per Tool: tool2Task[t][i]: the i-th task of the t-th tool.
- Note that it does NOT imply that the it will be the i-th
- executed. In other words, it should be considered as an unordered
- set. Furthermore, tool2Task[t][i] can also be *unperformed* */
- private List[] tool2Task;
-
- /* All the transition times for the tools.
- tool2TransitionTimes[t][i]: the transition time of the t-th tool
- from the i-th task to the next */
- private List[] tool2TransitionTimes;
-
- /* Map between the interval var of a tool to its related task id.
- toolIntervalVar2TaskId[t][k] = i: in the t-th tool, the k-th
- interval var correspond to tasks[i] */
- private List[] toolIntervalVar2TaskId;
-
- /* Tools per task type: taskType2Tool[tt][t]: the t-th tool capable
- * of doing the tt-th task type */
- private List[] taskType2Tool;
-
- /* For each task which tools is performed upon */
- private List selectedTool;
- public List SelectedTool
- {
- get {
- return selectedTool;
- }
- }
-
- /* Sequence of task for each tool */
- private SequenceVar[] allToolSequences;
- public SequenceVar[] AllToolSequences
- {
- get {
- return allToolSequences;
- }
- }
-
- /* Makespan var */
- private IntVar makespan;
-
- /* Objective */
- private OptimizeVar objective;
-
- /* maximum horizon */
- private int horizon;
-
- /* Start & End times of IntervalVars*/
- IntVar[][] startingTimes;
- IntVar[][] endTimes;
-
- public FactoryScheduling(FactoryDescription data)
- {
- factoryData = data;
- }
-
- private void Init()
- {
- horizon = factoryData.Horizon;
- solver = new Solver("Factory Scheduling");
- tasks = factoryData.getFlatTaskList();
- taskTypes = factoryData.getTaskTypes();
- taskStructures = new TaskAlternative[tasks.Length];
- location2Task = new TaskAlternative[factoryData.NbWorkLocations][];
- tool2Task = new List[factoryData.NbTools];
- toolIntervalVar2TaskId = new List[factoryData.NbTools];
- tool2TransitionTimes = new List[factoryData.NbTools];
-
- taskType2Tool = new List[taskTypes.Length];
- selectedTool = new List();
- for (int tt = 0; tt < taskTypes.Length; tt++)
- taskType2Tool[tt] = new List();
-
- foreach (Tool tool in factoryData.Tools)
- foreach (int taskType in tool.TaskTypes)
- taskType2Tool[taskType].Add(tool);
- for (int d = 0; d < factoryData.NbWorkLocations; d++)
- location2Task[d] = new TaskAlternative[factoryData.Locations[d].NbTasks];
- for (int t = 0; t < factoryData.NbTools; t++)
- {
- tool2Task[t] = new List();
- toolIntervalVar2TaskId[t] = new List();
- tool2TransitionTimes[t] = new List();
- }
-
- allToolSequences = new SequenceVar[factoryData.NbTools - 1];
-
- startingTimes = new IntVar[factoryData.NbTools - 1][];
- endTimes = new IntVar[factoryData.NbTools - 1][];
- }
-
- private void PostTransitionTimeConstraints(int t, bool postTransitionsConstraint = true)
- {
- Tool tool = factoryData.Tools[t];
- // if it is a inspection, we make sure there are no transitiontimes
- if (tool.CanPerformTaskType(factoryData.Inspection))
- tool2TransitionTimes[t].Add(null);
- else
- {
- int[,] tt = tool.TravellingTime;
-
- SequenceVar seq = allToolSequences[t];
- long s = seq.Size();
- IntVar[] nextLocation = new IntVar[s + 1];
-
- // The seq.Next(i) represents the task performed after the i-th
- // task in the sequence seq.Next(0) represents the first task
- // performed for extracting travelling times we need to get the
- // related location In case a task is not performed (seq.Next(i)
- // == i), i.e. it's pointing to itself The last performed task
- // (or pre-start task, if no tasks are performed) will have
- // seq.Next(i) == s + 1 therefore we add a virtual location
- // whose travelling time is equal to 0
- //
- // NOTE: The index of a SequenceVar are 0..n, but the domain
- // range is 1..(n+1), this is due to that the start node = 0 is
- // a dummy node, and the node where seq.Next(i) == n+1 is the
- // end node
-
- // Extra elements for the unreachable start node (0), and the
- // end node whose next task takes place in a virtual location
- int[] taskIndex2locationId = new int[s + 2];
- taskIndex2locationId[0] = -10;
- for (int i = 0; i < s; i++)
- taskIndex2locationId[i + 1] = tasks[toolIntervalVar2TaskId[t][i]].LocationId;
-
- // this is the virtual location for unperformed tasks
- taskIndex2locationId[s + 1] = factoryData.NbWorkLocations;
-
- // Build the travelling time matrix with the additional virtual location
- int[][] ttWithVirtualLocation = new int [factoryData.NbWorkLocations + 1][];
- for (int d1 = 0; d1 < ttWithVirtualLocation.Length; d1++)
- {
- ttWithVirtualLocation[d1] = new int[factoryData.NbWorkLocations + 1];
- for (int d2 = 0; d2 < ttWithVirtualLocation.Length; d2++)
- if (d1 == factoryData.NbWorkLocations)
- {
- ttWithVirtualLocation[d1][d2] = 0;
- }
- else
- {
- ttWithVirtualLocation[d1][d2] = (d2 == factoryData.NbWorkLocations) ? 0 : tt[d1, d2];
- }
- }
-
- for (int i = 0; i < nextLocation.Length; i++)
- {
- // this is the next-location associated with the i-th task
- nextLocation[i] = solver.MakeElement(taskIndex2locationId, seq.Next(i)).Var();
-
- int d = (i == 0) ? tool.InitialLocationId : tasks[toolIntervalVar2TaskId[t][i - 1]].LocationId;
- if (i == 0)
- {
- // To be changed - right now we don't have meaningful indata
- // of previous location Ugly way of setting initial travel
- // time to = 0, as this is how we find common grounds
- // between benchmark algorithm and this
- tool2TransitionTimes[t].Add(
- solver.MakeElement(new int[ttWithVirtualLocation[d].Length], nextLocation[i]).Var());
- }
- else
- {
- tool2TransitionTimes[t].Add(solver.MakeElement(ttWithVirtualLocation[d], nextLocation[i]).Var());
- }
- }
-
- // Extra elements for the unreachable start node (0), and the
- // end node whose next task takes place in a virtual location
- startingTimes[t] = new IntVar[s + 2];
- endTimes[t] = new IntVar[s + 2];
-
- startingTimes[t][0] = solver.MakeIntConst(0);
- // Tbd: Set this endtime to the estimated time of finishing
- // previous task for the current tool
- endTimes[t][0] = solver.MakeIntConst(0);
-
- for (int i = 0; i < s; i++)
- {
- startingTimes[t][i + 1] = tool2Task[t][i].SafeStartExpr(-1).Var();
- endTimes[t][i + 1] = tool2Task[t][i].SafeEndExpr(-1).Var();
- }
- startingTimes[t][s + 1] = solver.MakeIntConst(factoryData.Horizon);
- endTimes[t][s + 1] = solver.MakeIntConst(factoryData.Horizon);
-
- // Enforce (or not) that each task is separated by the
- // transition time to the next task
- for (int i = 0; i < nextLocation.Length; i++)
- {
- IntVar nextStart = solver.MakeElement(startingTimes[t], seq.Next(i).Var()).Var();
- if (postTransitionsConstraint)
- solver.Add(endTimes[t][i] + tool2TransitionTimes[t][i] <= nextStart);
- }
- }
- }
-
- private void Model()
- {
- /* Building basic task data structures */
- for (int i = 0; i < tasks.Length; i++)
- {
- /* Create a new set of possible IntervalVars & IntVar to decide
- * which one (and only 1) is performed */
- taskStructures[i] = new TaskAlternative(tasks[i]);
-
- /* Container to use when posting constraints */
- location2Task[tasks[i].LocationId][tasks[i].TaskPosition] = taskStructures[i];
-
- /* Get task type */
- int taskType = tasks[i].TaskType;
-
- /* Possible tool for this task */
- List tools = taskType2Tool[taskType];
- bool optional = tools.Count > 1;
-
- /* List of boolean variables. If performedOnTool[t] == true then
- * the task is performed on tool t */
- List performedOnTool = new List();
- for (int t = 0; t < tools.Count; t++)
- {
- /* Creating an IntervalVar. If tools.Count > 1 the intervalVar
- * is *OPTIONAL* */
- int toolId = tools[t].Id;
- Debug.Assert(tasks[i].Durations.ContainsKey(toolId));
- int duration = tasks[i].Durations[toolId];
- string name = "J " + tasks[i].Id + " [" + toolId + "]";
-
- IntervalVar intervalVar;
- if (taskType == factoryData.Inspection)
- {
- /* We set a 0 time if the task is an inspection */
- duration = 0;
- intervalVar = solver.MakeFixedDurationIntervalVar(0, horizon, duration, optional, name);
- IntVar start = intervalVar.SafeStartExpr(-1).Var();
-
- intervalVar.SafeStartExpr(-1).Var().SetValues(factoryData.InspectionStarts);
- }
- else
- {
- intervalVar = solver.MakeFixedDurationIntervalVar(0, horizon, duration, optional, name);
- }
-
- taskStructures[i].Intervals.Add(intervalVar);
- tool2Task[toolId].Add(intervalVar);
- toolIntervalVar2TaskId[toolId].Add(i);
-
- /* Collecting all the bool vars, even if they are optional */
- performedOnTool.Add(intervalVar.PerformedExpr().Var());
- }
-
- /* Linking the bool var to a single integer variable: */
- /* if alternativeToolVar == t <=> performedOnTool[t] == true */
- string alternativeName = "J " + tasks[i].Id;
- IntVar alternativeToolVar = solver.MakeIntVar(0, tools.Count - 1, alternativeName);
- taskStructures[i].ToolVar = alternativeToolVar;
-
- solver.Add(solver.MakeMapDomain(alternativeToolVar, performedOnTool.ToArray()));
- Debug.Assert(performedOnTool.ToArray().Length == alternativeToolVar.Max() + 1);
-
- selectedTool.Add(alternativeToolVar);
- }
-
- /* Creates precedences on a work Location in order to enforce a
- * fully ordered set within the same location
- */
- for (int d = 0; d < location2Task.Length; d++)
- {
- for (int i = 0; i < location2Task[d].Length - 1; i++)
- {
- TaskAlternative task1 = location2Task[d][i];
- TaskAlternative task2 = location2Task[d][i + 1];
- /* task1 must end before task2 starts */
- /* Adding precedence for each possible alternative pair */
- for (int t1 = 0; t1 < task1.Intervals.Count(); t1++)
- {
- IntervalVar task1Alternative = task1.Intervals[t1];
- for (int t2 = 0; t2 < task2.Intervals.Count(); t2++)
- {
- IntervalVar task2Alternative = task2.Intervals[t2];
- Constraint precedence =
- solver.MakeIntervalVarRelation(task2Alternative, Solver.STARTS_AFTER_END, task1Alternative);
- solver.Add(precedence);
- }
- }
- }
- }
-
- /* Adds disjunctive constraints on unary resources, and creates
- * sequence variables. */
- for (int t = 0; t < factoryData.NbTools; t++)
- {
- string name = "Tool " + t;
-
- if (!factoryData.Tools[t].CanPerformTaskType(factoryData.Inspection))
- {
- DisjunctiveConstraint ct = solver.MakeDisjunctiveConstraint(tool2Task[t].ToArray(), name);
- solver.Add(ct);
- allToolSequences[t] = ct.SequenceVar();
- }
- PostTransitionTimeConstraints(t, true);
- }
-
- /* Collecting all tasks end for makespan objective function */
- List intervalEnds = new List();
- for (int i = 0; i < tasks.Length; i++)
- foreach (IntervalVar var in taskStructures[i].Intervals)
- intervalEnds.Add(var.SafeEndExpr(-1).Var());
-
- /* Objective: minimize the makespan (maximum end times of all tasks) */
- makespan = solver.MakeMax(intervalEnds.ToArray()).Var();
- objective = solver.MakeMinimize(makespan, 1);
- }
-
- private void Search()
- {
- int seed = 2; // This is a good seed to show the crash
-
- /* Assigning first tools */
- DecisionBuilder myToolAssignmentPhase = new RandomSelectToolHeuristic(this, seed);
-
- /* Ranking of the tools */
- DecisionBuilder sequencingPhase = solver.MakePhase(allToolSequences, Solver.SEQUENCE_DEFAULT);
-
- /* Then fixing time of tasks as early as possible */
- DecisionBuilder timingPhase = solver.MakePhase(makespan, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);
-
- /* Overall phase */
- DecisionBuilder mainPhase = solver.Compose(myToolAssignmentPhase, sequencingPhase, timingPhase);
-
- /* Logging */
- const int logFrequency = 1000000;
- SearchMonitor searchLog = solver.MakeSearchLog(logFrequency, objective);
-
- /* Restarts */
- SearchMonitor searchRestart = solver.MakeLubyRestart(100);
-
- /* Search Limit in ms */
- SearchLimit limit = solver.MakeTimeLimit(180 * 1000);
-
- /* Collecting best solution */
- SolutionCollector collector = solver.MakeLastSolutionCollector();
- collector.AddObjective(makespan);
-
- // collector.Add( pile.ToArray() );
- solver.NewSearch(mainPhase, searchLog, searchRestart, objective, limit);
- while (solver.NextSolution())
- {
- Console.WriteLine("MAKESPAN: " + makespan.Value());
- }
- }
-
- public void Solve()
- {
- Init();
- Model();
- Search();
- }
-}
-
-public class Issue33Test
-{
- public static void FactorySchedulingTest()
- {
- FactoryScheduling scheduling = new FactoryScheduling(new SmallSyntheticData().FetchData());
- scheduling.Solve();
- }
- static void Main()
- {
- FactorySchedulingTest();
- }
-}
+// Authors: Johan Wessén
+// Copyright 2010-2025 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 System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System;
+
+public class Task
+{
+ public int Id { get; private set; }
+ public int TaskType { get; private set; }
+ public int LocationId { get; private set; }
+ public Dictionary Durations { get; private set; }
+ public int TaskPosition { get; private set; }
+
+ public Task(int id, int taskType, int locationIndex, int taskPosition, Dictionary durations)
+ {
+ Id = id;
+ TaskType = taskType;
+ LocationId = locationIndex;
+ Durations = durations;
+ TaskPosition = taskPosition;
+ }
+
+ public Task(int id, int taskType, int locationIndex, int taskPosition)
+ {
+ Id = id;
+ TaskType = taskType;
+ LocationId = locationIndex;
+ TaskPosition = taskPosition;
+ Durations = new Dictionary();
+ }
+}
+
+public class WorkLocation
+{
+ public int Id { get; private set; }
+ public int NbTasks
+ {
+ get {
+ Debug.Assert(Tasks != null);
+ return Tasks.Length;
+ }
+ set {
+ Debug.Assert(Tasks == null);
+ Tasks = new Task[value];
+ }
+ }
+ public Task[] Tasks { get; private set; }
+
+ public WorkLocation(int index)
+ {
+ Id = index;
+ }
+}
+
+public class Tool
+{
+ public int Id { get; private set; }
+ public HashSet TaskTypes { get; set; }
+ public int[,] TravellingTime { get; set; }
+ public int InitialLocationId { get; set; }
+
+ public Tool(int index, int initialLocation = 0)
+ {
+ Id = index;
+ InitialLocationId = initialLocation;
+ TaskTypes = new HashSet();
+ }
+
+ public void AddTaskType(int t)
+ {
+ TaskTypes.Add(t);
+ }
+
+ public bool CanPerformTaskType(int taskType)
+ {
+ return TaskTypes.Contains(taskType);
+ }
+}
+
+public class FactoryDescription
+{
+ public Tool[] Tools { get; private set; }
+ public WorkLocation[] Locations { get; private set; }
+
+ public int NbWorkLocations
+ {
+ get {
+ return Locations.Length;
+ }
+ }
+ public int NbTools
+ {
+ get {
+ return Tools.Length;
+ }
+ }
+
+ public int NbTaskPerCycle { get; private set; }
+ // TaskType go typically from 0 to 6. InspectionType indicates which
+ // is the TaskType that correspond to Inspection.
+ public int Inspection { get; private set; }
+ // All the time within the schedule horizon in which the blast can start.
+ public long[] InspectionStarts { get; private set; }
+
+ public int Horizon { get; private set; }
+
+ // horizon equal to 2 weeks (in minutes).
+ public FactoryDescription(int nbTools, int nbLocations, int nbTaskPerCycle, int horizon = 14 * 24 * 60)
+ {
+ Debug.Assert(nbTools > 0);
+ Debug.Assert(nbLocations > 0);
+ Debug.Assert(nbTaskPerCycle > 0);
+ Debug.Assert(horizon > 0);
+ NbTaskPerCycle = nbTaskPerCycle;
+ Inspection = NbTaskPerCycle - 1;
+ Tools = new Tool[nbTools];
+ Horizon = horizon;
+ for (int i = 0; i < nbTools; i++)
+ Tools[i] = new Tool(i);
+ Locations = new WorkLocation[nbLocations];
+ for (int i = 0; i < nbLocations; i++)
+ Locations[i] = new WorkLocation(i);
+
+ InspectionStarts = new long[] { -1, 600, 1200, 1800, 2400, 2800 };
+ }
+
+ public Tool[] getToolPerTaskType(int taskType)
+ {
+ var elements = from tool in Tools
+ where tool.CanPerformTaskType(taskType) select tool;
+ return elements.ToArray();
+ }
+
+ public Task[] getFlatTaskList()
+ {
+ return (from location in Locations from task in location.Tasks orderby task.Id select task).ToArray();
+ }
+
+ public int[] getTaskTypes()
+ {
+ return (from location in Locations from task in location.Tasks select task.TaskType).Distinct().ToArray();
+ }
+
+ // TODO: This should be enhanced
+ public void SanityCheck()
+ {
+ foreach (Tool tool in Tools)
+ {
+ Debug.Assert(tool.TravellingTime.GetLength(0) == NbWorkLocations);
+ Debug.Assert(tool.TravellingTime.GetLength(1) == NbWorkLocations);
+ for (int i = 0; i < NbWorkLocations; i++)
+ Debug.Assert(tool.TravellingTime[i, i] == 0);
+ }
+ }
+}
+
+interface DataReader
+{
+ FactoryDescription FetchData();
+}
+
+public class SmallSyntheticData : DataReader
+{
+ public SmallSyntheticData()
+ {
+ }
+
+ public FactoryDescription FetchData()
+ {
+ // deterministic seed for result reproducibility
+ Random randomDuration = new Random(2);
+
+ // FactoryDescription(nbTools, nblocations, nbTasks per cycle)
+ FactoryDescription factoryDescription = new FactoryDescription(5, 4, 3);
+
+ // Travelling time and distance are temporarily identical and they
+ // are no different for different tools
+ int[,] travellingTime = new int[factoryDescription.NbWorkLocations, factoryDescription.NbWorkLocations];
+ for (int i = 0; i < travellingTime.GetLength(0); i++)
+ {
+ for (int j = 0; j < travellingTime.GetLength(1); j++)
+ {
+ if (i == j)
+ travellingTime[i, j] = 0;
+ else
+ travellingTime[i, j] = (5 * Math.Abs(i - j)) * 10;
+ }
+ }
+
+ factoryDescription.Tools[0].AddTaskType(0);
+ factoryDescription.Tools[1].AddTaskType(0);
+ factoryDescription.Tools[2].AddTaskType(1);
+ factoryDescription.Tools[3].AddTaskType(1);
+ factoryDescription.Tools[4].AddTaskType(2);
+ factoryDescription.Tools[1].AddTaskType(1);
+
+ foreach (Tool tool in factoryDescription.Tools)
+ tool.TravellingTime = travellingTime;
+
+ int c = 0;
+ int nbCyclePerWorkLocation = 2;
+ int[] boll = new int[100];
+ for (int i = 0; i < factoryDescription.NbWorkLocations; i++)
+ {
+ factoryDescription.Locations[i].NbTasks = nbCyclePerWorkLocation * factoryDescription.NbTaskPerCycle;
+ for (int j = 0; j < nbCyclePerWorkLocation; j++)
+ {
+ for (int k = 0; k < factoryDescription.NbTaskPerCycle; k++)
+ {
+ Task t = new Task(c, k, i, k + j * factoryDescription.NbTaskPerCycle);
+
+ // Filling in tool-dependent durations
+ Tool[] compatibleTools = factoryDescription.getToolPerTaskType(k);
+ foreach (Tool tool in compatibleTools)
+ {
+ boll[c] = randomDuration.Next(13, 17) * 10;
+ ;
+ t.Durations[tool.Id] = boll[c];
+ }
+ factoryDescription.Locations[i].Tasks[t.TaskPosition] = t;
+ c++;
+ }
+ }
+ }
+
+ factoryDescription.SanityCheck();
+ return factoryDescription;
+ }
+}
+
+public class RandomSelectToolHeuristic : NetDecisionBuilder
+{
+ private FactoryScheduling factoryScheduling;
+ private Random rnd;
+
+ public RandomSelectToolHeuristic(FactoryScheduling factoryScheduling, int seed)
+ {
+ this.factoryScheduling = factoryScheduling;
+ // deterministic seed for result reproducibility
+ this.rnd = new Random(seed);
+ }
+
+ public override Decision Next(Solver solver)
+ {
+ foreach (IntVar var in factoryScheduling.SelectedTool)
+ {
+ if (!var.Bound())
+ {
+ int min = (int)var.Min();
+ int max = (int)var.Max();
+ int rndVal = rnd.Next(min, max + 1);
+ while (!var.Contains(rndVal))
+ rndVal = rnd.Next(min, max + 1);
+ return solver.MakeAssignVariableValue(var, rndVal);
+ }
+ }
+ return null;
+ }
+}
+
+class TaskAlternative
+{
+ public Task Task { get; private set; }
+ public IntVar ToolVar { get; set; }
+ public List Intervals { get; private set; }
+
+ public TaskAlternative(Task t)
+ {
+ Task = t;
+ Intervals = new List();
+ }
+}
+
+public class FactoryScheduling
+{
+ private FactoryDescription factoryData;
+ private Solver solver;
+
+ private Task[] tasks;
+ private int[] taskTypes;
+
+ /* Flat list of all the tasks */
+ private TaskAlternative[] taskStructures;
+
+ /* Task per WorkLocation: location2Task[d][i]: the i-th task of the
+ * d-th location */
+ private TaskAlternative[][] location2Task;
+
+ /* Task per Tool: tool2Task[t][i]: the i-th task of the t-th tool.
+ Note that it does NOT imply that the it will be the i-th
+ executed. In other words, it should be considered as an unordered
+ set. Furthermore, tool2Task[t][i] can also be *unperformed* */
+ private List[] tool2Task;
+
+ /* All the transition times for the tools.
+ tool2TransitionTimes[t][i]: the transition time of the t-th tool
+ from the i-th task to the next */
+ private List[] tool2TransitionTimes;
+
+ /* Map between the interval var of a tool to its related task id.
+ toolIntervalVar2TaskId[t][k] = i: in the t-th tool, the k-th
+ interval var correspond to tasks[i] */
+ private List[] toolIntervalVar2TaskId;
+
+ /* Tools per task type: taskType2Tool[tt][t]: the t-th tool capable
+ * of doing the tt-th task type */
+ private List[] taskType2Tool;
+
+ /* For each task which tools is performed upon */
+ private List selectedTool;
+ public List SelectedTool
+ {
+ get {
+ return selectedTool;
+ }
+ }
+
+ /* Sequence of task for each tool */
+ private SequenceVar[] allToolSequences;
+ public SequenceVar[] AllToolSequences
+ {
+ get {
+ return allToolSequences;
+ }
+ }
+
+ /* Makespan var */
+ private IntVar makespan;
+
+ /* Objective */
+ private OptimizeVar objective;
+
+ /* maximum horizon */
+ private int horizon;
+
+ /* Start & End times of IntervalVars*/
+ IntVar[][] startingTimes;
+ IntVar[][] endTimes;
+
+ public FactoryScheduling(FactoryDescription data)
+ {
+ factoryData = data;
+ }
+
+ private void Init()
+ {
+ horizon = factoryData.Horizon;
+ solver = new Solver("Factory Scheduling");
+ tasks = factoryData.getFlatTaskList();
+ taskTypes = factoryData.getTaskTypes();
+ taskStructures = new TaskAlternative[tasks.Length];
+ location2Task = new TaskAlternative[factoryData.NbWorkLocations][];
+ tool2Task = new List[factoryData.NbTools];
+ toolIntervalVar2TaskId = new List[factoryData.NbTools];
+ tool2TransitionTimes = new List[factoryData.NbTools];
+
+ taskType2Tool = new List[taskTypes.Length];
+ selectedTool = new List();
+ for (int tt = 0; tt < taskTypes.Length; tt++)
+ taskType2Tool[tt] = new List();
+
+ foreach (Tool tool in factoryData.Tools)
+ foreach (int taskType in tool.TaskTypes)
+ taskType2Tool[taskType].Add(tool);
+ for (int d = 0; d < factoryData.NbWorkLocations; d++)
+ location2Task[d] = new TaskAlternative[factoryData.Locations[d].NbTasks];
+ for (int t = 0; t < factoryData.NbTools; t++)
+ {
+ tool2Task[t] = new List();
+ toolIntervalVar2TaskId[t] = new List();
+ tool2TransitionTimes[t] = new List();
+ }
+
+ allToolSequences = new SequenceVar[factoryData.NbTools - 1];
+
+ startingTimes = new IntVar[factoryData.NbTools - 1][];
+ endTimes = new IntVar[factoryData.NbTools - 1][];
+ }
+
+ private void PostTransitionTimeConstraints(int t, bool postTransitionsConstraint = true)
+ {
+ Tool tool = factoryData.Tools[t];
+ // if it is a inspection, we make sure there are no transitiontimes
+ if (tool.CanPerformTaskType(factoryData.Inspection))
+ tool2TransitionTimes[t].Add(null);
+ else
+ {
+ int[,] tt = tool.TravellingTime;
+
+ SequenceVar seq = allToolSequences[t];
+ long s = seq.Size();
+ IntVar[] nextLocation = new IntVar[s + 1];
+
+ // The seq.Next(i) represents the task performed after the i-th
+ // task in the sequence seq.Next(0) represents the first task
+ // performed for extracting travelling times we need to get the
+ // related location In case a task is not performed (seq.Next(i)
+ // == i), i.e. it's pointing to itself The last performed task
+ // (or pre-start task, if no tasks are performed) will have
+ // seq.Next(i) == s + 1 therefore we add a virtual location
+ // whose travelling time is equal to 0
+ //
+ // NOTE: The index of a SequenceVar are 0..n, but the domain
+ // range is 1..(n+1), this is due to that the start node = 0 is
+ // a dummy node, and the node where seq.Next(i) == n+1 is the
+ // end node
+
+ // Extra elements for the unreachable start node (0), and the
+ // end node whose next task takes place in a virtual location
+ int[] taskIndex2locationId = new int[s + 2];
+ taskIndex2locationId[0] = -10;
+ for (int i = 0; i < s; i++)
+ taskIndex2locationId[i + 1] = tasks[toolIntervalVar2TaskId[t][i]].LocationId;
+
+ // this is the virtual location for unperformed tasks
+ taskIndex2locationId[s + 1] = factoryData.NbWorkLocations;
+
+ // Build the travelling time matrix with the additional virtual location
+ int[][] ttWithVirtualLocation = new int [factoryData.NbWorkLocations + 1][];
+ for (int d1 = 0; d1 < ttWithVirtualLocation.Length; d1++)
+ {
+ ttWithVirtualLocation[d1] = new int[factoryData.NbWorkLocations + 1];
+ for (int d2 = 0; d2 < ttWithVirtualLocation.Length; d2++)
+ if (d1 == factoryData.NbWorkLocations)
+ {
+ ttWithVirtualLocation[d1][d2] = 0;
+ }
+ else
+ {
+ ttWithVirtualLocation[d1][d2] = (d2 == factoryData.NbWorkLocations) ? 0 : tt[d1, d2];
+ }
+ }
+
+ for (int i = 0; i < nextLocation.Length; i++)
+ {
+ // this is the next-location associated with the i-th task
+ nextLocation[i] = solver.MakeElement(taskIndex2locationId, seq.Next(i)).Var();
+
+ int d = (i == 0) ? tool.InitialLocationId : tasks[toolIntervalVar2TaskId[t][i - 1]].LocationId;
+ if (i == 0)
+ {
+ // To be changed - right now we don't have meaningful indata
+ // of previous location Ugly way of setting initial travel
+ // time to = 0, as this is how we find common grounds
+ // between benchmark algorithm and this
+ tool2TransitionTimes[t].Add(
+ solver.MakeElement(new int[ttWithVirtualLocation[d].Length], nextLocation[i]).Var());
+ }
+ else
+ {
+ tool2TransitionTimes[t].Add(solver.MakeElement(ttWithVirtualLocation[d], nextLocation[i]).Var());
+ }
+ }
+
+ // Extra elements for the unreachable start node (0), and the
+ // end node whose next task takes place in a virtual location
+ startingTimes[t] = new IntVar[s + 2];
+ endTimes[t] = new IntVar[s + 2];
+
+ startingTimes[t][0] = solver.MakeIntConst(0);
+ // Tbd: Set this endtime to the estimated time of finishing
+ // previous task for the current tool
+ endTimes[t][0] = solver.MakeIntConst(0);
+
+ for (int i = 0; i < s; i++)
+ {
+ startingTimes[t][i + 1] = tool2Task[t][i].SafeStartExpr(-1).Var();
+ endTimes[t][i + 1] = tool2Task[t][i].SafeEndExpr(-1).Var();
+ }
+ startingTimes[t][s + 1] = solver.MakeIntConst(factoryData.Horizon);
+ endTimes[t][s + 1] = solver.MakeIntConst(factoryData.Horizon);
+
+ // Enforce (or not) that each task is separated by the
+ // transition time to the next task
+ for (int i = 0; i < nextLocation.Length; i++)
+ {
+ IntVar nextStart = solver.MakeElement(startingTimes[t], seq.Next(i).Var()).Var();
+ if (postTransitionsConstraint)
+ solver.Add(endTimes[t][i] + tool2TransitionTimes[t][i] <= nextStart);
+ }
+ }
+ }
+
+ private void Model()
+ {
+ /* Building basic task data structures */
+ for (int i = 0; i < tasks.Length; i++)
+ {
+ /* Create a new set of possible IntervalVars & IntVar to decide
+ * which one (and only 1) is performed */
+ taskStructures[i] = new TaskAlternative(tasks[i]);
+
+ /* Container to use when posting constraints */
+ location2Task[tasks[i].LocationId][tasks[i].TaskPosition] = taskStructures[i];
+
+ /* Get task type */
+ int taskType = tasks[i].TaskType;
+
+ /* Possible tool for this task */
+ List tools = taskType2Tool[taskType];
+ bool optional = tools.Count > 1;
+
+ /* List of boolean variables. If performedOnTool[t] == true then
+ * the task is performed on tool t */
+ List performedOnTool = new List();
+ for (int t = 0; t < tools.Count; t++)
+ {
+ /* Creating an IntervalVar. If tools.Count > 1 the intervalVar
+ * is *OPTIONAL* */
+ int toolId = tools[t].Id;
+ Debug.Assert(tasks[i].Durations.ContainsKey(toolId));
+ int duration = tasks[i].Durations[toolId];
+ string name = "J " + tasks[i].Id + " [" + toolId + "]";
+
+ IntervalVar intervalVar;
+ if (taskType == factoryData.Inspection)
+ {
+ /* We set a 0 time if the task is an inspection */
+ duration = 0;
+ intervalVar = solver.MakeFixedDurationIntervalVar(0, horizon, duration, optional, name);
+ IntVar start = intervalVar.SafeStartExpr(-1).Var();
+
+ intervalVar.SafeStartExpr(-1).Var().SetValues(factoryData.InspectionStarts);
+ }
+ else
+ {
+ intervalVar = solver.MakeFixedDurationIntervalVar(0, horizon, duration, optional, name);
+ }
+
+ taskStructures[i].Intervals.Add(intervalVar);
+ tool2Task[toolId].Add(intervalVar);
+ toolIntervalVar2TaskId[toolId].Add(i);
+
+ /* Collecting all the bool vars, even if they are optional */
+ performedOnTool.Add(intervalVar.PerformedExpr().Var());
+ }
+
+ /* Linking the bool var to a single integer variable: */
+ /* if alternativeToolVar == t <=> performedOnTool[t] == true */
+ string alternativeName = "J " + tasks[i].Id;
+ IntVar alternativeToolVar = solver.MakeIntVar(0, tools.Count - 1, alternativeName);
+ taskStructures[i].ToolVar = alternativeToolVar;
+
+ solver.Add(solver.MakeMapDomain(alternativeToolVar, performedOnTool.ToArray()));
+ Debug.Assert(performedOnTool.ToArray().Length == alternativeToolVar.Max() + 1);
+
+ selectedTool.Add(alternativeToolVar);
+ }
+
+ /* Creates precedences on a work Location in order to enforce a
+ * fully ordered set within the same location
+ */
+ for (int d = 0; d < location2Task.Length; d++)
+ {
+ for (int i = 0; i < location2Task[d].Length - 1; i++)
+ {
+ TaskAlternative task1 = location2Task[d][i];
+ TaskAlternative task2 = location2Task[d][i + 1];
+ /* task1 must end before task2 starts */
+ /* Adding precedence for each possible alternative pair */
+ for (int t1 = 0; t1 < task1.Intervals.Count(); t1++)
+ {
+ IntervalVar task1Alternative = task1.Intervals[t1];
+ for (int t2 = 0; t2 < task2.Intervals.Count(); t2++)
+ {
+ IntervalVar task2Alternative = task2.Intervals[t2];
+ Constraint precedence =
+ solver.MakeIntervalVarRelation(task2Alternative, Solver.STARTS_AFTER_END, task1Alternative);
+ solver.Add(precedence);
+ }
+ }
+ }
+ }
+
+ /* Adds disjunctive constraints on unary resources, and creates
+ * sequence variables. */
+ for (int t = 0; t < factoryData.NbTools; t++)
+ {
+ string name = "Tool " + t;
+
+ if (!factoryData.Tools[t].CanPerformTaskType(factoryData.Inspection))
+ {
+ DisjunctiveConstraint ct = solver.MakeDisjunctiveConstraint(tool2Task[t].ToArray(), name);
+ solver.Add(ct);
+ allToolSequences[t] = ct.SequenceVar();
+ }
+ PostTransitionTimeConstraints(t, true);
+ }
+
+ /* Collecting all tasks end for makespan objective function */
+ List intervalEnds = new List();
+ for (int i = 0; i < tasks.Length; i++)
+ foreach (IntervalVar var in taskStructures[i].Intervals)
+ intervalEnds.Add(var.SafeEndExpr(-1).Var());
+
+ /* Objective: minimize the makespan (maximum end times of all tasks) */
+ makespan = solver.MakeMax(intervalEnds.ToArray()).Var();
+ objective = solver.MakeMinimize(makespan, 1);
+ }
+
+ private void Search()
+ {
+ int seed = 2; // This is a good seed to show the crash
+
+ /* Assigning first tools */
+ DecisionBuilder myToolAssignmentPhase = new RandomSelectToolHeuristic(this, seed);
+
+ /* Ranking of the tools */
+ DecisionBuilder sequencingPhase = solver.MakePhase(allToolSequences, Solver.SEQUENCE_DEFAULT);
+
+ /* Then fixing time of tasks as early as possible */
+ DecisionBuilder timingPhase = solver.MakePhase(makespan, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);
+
+ /* Overall phase */
+ DecisionBuilder mainPhase = solver.Compose(myToolAssignmentPhase, sequencingPhase, timingPhase);
+
+ /* Logging */
+ const int logFrequency = 1000000;
+ SearchMonitor searchLog = solver.MakeSearchLog(logFrequency, objective);
+
+ /* Restarts */
+ SearchMonitor searchRestart = solver.MakeLubyRestart(100);
+
+ /* Search Limit in ms */
+ SearchLimit limit = solver.MakeTimeLimit(180 * 1000);
+
+ /* Collecting best solution */
+ SolutionCollector collector = solver.MakeLastSolutionCollector();
+ collector.AddObjective(makespan);
+
+ // collector.Add( pile.ToArray() );
+ solver.NewSearch(mainPhase, searchLog, searchRestart, objective, limit);
+ while (solver.NextSolution())
+ {
+ Console.WriteLine("MAKESPAN: " + makespan.Value());
+ }
+ }
+
+ public void Solve()
+ {
+ Init();
+ Model();
+ Search();
+ }
+}
+
+public class Issue33Test
+{
+ public static void FactorySchedulingTest()
+ {
+ FactoryScheduling scheduling = new FactoryScheduling(new SmallSyntheticData().FetchData());
+ scheduling.Solve();
+ }
+ static void Main()
+ {
+ FactorySchedulingTest();
+ }
+}
diff --git a/ortools/dotnet/Google.OrTools.sln b/ortools/dotnet/Google.OrTools.sln
index c2ef81d8af4..d906c3e4eca 100644
--- a/ortools/dotnet/Google.OrTools.sln
+++ b/ortools/dotnet/Google.OrTools.sln
@@ -1,37 +1,37 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26124.0
-MinimumVisualStudioVersion = 15.0.26124.0
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Google.OrTools.runtime.linux-x64", "Google.OrTools.runtime.linux-x64\Google.OrTools.runtime.linux-x64.csproj", "{FC646C34-8541-427D-B9F6-1247798F4574}"
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Google.OrTools.runtime.osx-x64", "Google.OrTools.runtime.osx-x64\Google.OrTools.runtime.osx-x64.csproj", "{FC646C34-8541-427D-B9F6-1247798F4574}"
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Google.OrTools.runtime.win-x64", "Google.OrTools.runtime.win-x64\Google.OrTools.runtime.win-x64.csproj", "{FC646C34-8541-427D-B9F6-1247798F4574}"
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Google.OrTools", "Google.OrTools\Google.OrTools.csproj", "{FC646C34-8541-427D-B9F6-1247798F4574}"
-EndProject
-Global
-GlobalSection(SolutionConfigurationPlatforms) = preSolution
-Debug|Any CPU = Debug|Any CPU
-Debug|x64 = Debug|x64
-Debug|x86 = Debug|x86
-Release|Any CPU = Release|Any CPU
-Release|x64 = Release|x64
-Release|x86 = Release|x86
-EndGlobalSection
-GlobalSection(SolutionProperties) = preSolution
-HideSolutionNode = FALSE
-EndGlobalSection
-GlobalSection(ProjectConfigurationPlatforms) = postSolution
-{FC646C34-8541-427D-B9F6-1247798F4574}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-{FC646C34-8541-427D-B9F6-1247798F4574}.Debug|Any CPU.Build.0 = Debug|Any CPU
-{FC646C34-8541-427D-B9F6-1247798F4574}.Debug|x64.ActiveCfg = Debug|Any CPU
-{FC646C34-8541-427D-B9F6-1247798F4574}.Debug|x64.Build.0 = Debug|Any CPU
-{FC646C34-8541-427D-B9F6-1247798F4574}.Debug|x86.ActiveCfg = Debug|Any CPU
-{FC646C34-8541-427D-B9F6-1247798F4574}.Debug|x86.Build.0 = Debug|Any CPU
-{FC646C34-8541-427D-B9F6-1247798F4574}.Release|Any CPU.ActiveCfg = Release|Any CPU
-{FC646C34-8541-427D-B9F6-1247798F4574}.Release|Any CPU.Build.0 = Release|Any CPU
-{FC646C34-8541-427D-B9F6-1247798F4574}.Release|x64.ActiveCfg = Release|Any CPU
-{FC646C34-8541-427D-B9F6-1247798F4574}.Release|x64.Build.0 = Release|Any CPU
-{FC646C34-8541-427D-B9F6-1247798F4574}.Release|x86.ActiveCfg = Release|Any CPU
-{FC646C34-8541-427D-B9F6-1247798F4574}.Release|x86.Build.0 = Release|Any CPU
-EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Google.OrTools.runtime.linux-x64", "Google.OrTools.runtime.linux-x64\Google.OrTools.runtime.linux-x64.csproj", "{FC646C34-8541-427D-B9F6-1247798F4574}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Google.OrTools.runtime.osx-x64", "Google.OrTools.runtime.osx-x64\Google.OrTools.runtime.osx-x64.csproj", "{FC646C34-8541-427D-B9F6-1247798F4574}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Google.OrTools.runtime.win-x64", "Google.OrTools.runtime.win-x64\Google.OrTools.runtime.win-x64.csproj", "{FC646C34-8541-427D-B9F6-1247798F4574}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Google.OrTools", "Google.OrTools\Google.OrTools.csproj", "{FC646C34-8541-427D-B9F6-1247798F4574}"
+EndProject
+Global
+GlobalSection(SolutionConfigurationPlatforms) = preSolution
+Debug|Any CPU = Debug|Any CPU
+Debug|x64 = Debug|x64
+Debug|x86 = Debug|x86
+Release|Any CPU = Release|Any CPU
+Release|x64 = Release|x64
+Release|x86 = Release|x86
+EndGlobalSection
+GlobalSection(SolutionProperties) = preSolution
+HideSolutionNode = FALSE
+EndGlobalSection
+GlobalSection(ProjectConfigurationPlatforms) = postSolution
+{FC646C34-8541-427D-B9F6-1247798F4574}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+{FC646C34-8541-427D-B9F6-1247798F4574}.Debug|Any CPU.Build.0 = Debug|Any CPU
+{FC646C34-8541-427D-B9F6-1247798F4574}.Debug|x64.ActiveCfg = Debug|Any CPU
+{FC646C34-8541-427D-B9F6-1247798F4574}.Debug|x64.Build.0 = Debug|Any CPU
+{FC646C34-8541-427D-B9F6-1247798F4574}.Debug|x86.ActiveCfg = Debug|Any CPU
+{FC646C34-8541-427D-B9F6-1247798F4574}.Debug|x86.Build.0 = Debug|Any CPU
+{FC646C34-8541-427D-B9F6-1247798F4574}.Release|Any CPU.ActiveCfg = Release|Any CPU
+{FC646C34-8541-427D-B9F6-1247798F4574}.Release|Any CPU.Build.0 = Release|Any CPU
+{FC646C34-8541-427D-B9F6-1247798F4574}.Release|x64.ActiveCfg = Release|Any CPU
+{FC646C34-8541-427D-B9F6-1247798F4574}.Release|x64.Build.0 = Release|Any CPU
+{FC646C34-8541-427D-B9F6-1247798F4574}.Release|x86.ActiveCfg = Release|Any CPU
+{FC646C34-8541-427D-B9F6-1247798F4574}.Release|x86.Build.0 = Release|Any CPU
+EndGlobalSection
+EndGlobal
diff --git a/ortools/routing/parsers/testdata/pdtsp_prob10b.txt b/ortools/routing/parsers/testdata/pdtsp_prob10b.txt
index d1194cb91c2..69abc76207d 100644
--- a/ortools/routing/parsers/testdata/pdtsp_prob10b.txt
+++ b/ortools/routing/parsers/testdata/pdtsp_prob10b.txt
@@ -1,23 +1,23 @@
-21
-1 682 266
-2 129 265 0 12
-3 298 495 0 13
-4 720 160 0 14
-5 93 10 0 15
-6 891 782 0 16
-7 888 533 0 17
-8 414 290 0 18
-9 61 22 0 19
-10 485 352 0 20
-11 817 619 0 21
-12 669 775 1 2
-13 628 117 1 3
-14 178 31 1 4
-15 733 97 1 5
-16 985 320 1 6
-17 319 0 1 7
-18 545 283 1 8
-19 331 664 1 9
-20 598 785 1 10
-21 245 810 1 11
--999
+21
+1 682 266
+2 129 265 0 12
+3 298 495 0 13
+4 720 160 0 14
+5 93 10 0 15
+6 891 782 0 16
+7 888 533 0 17
+8 414 290 0 18
+9 61 22 0 19
+10 485 352 0 20
+11 817 619 0 21
+12 669 775 1 2
+13 628 117 1 3
+14 178 31 1 4
+15 733 97 1 5
+16 985 320 1 6
+17 319 0 1 7
+18 545 283 1 8
+19 331 664 1 9
+20 598 785 1 10
+21 245 810 1 11
+-999