1 // Authors: Johan Wessén
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 using Google.OrTools.ConstraintSolver;
16 using System.Collections.Generic;
17 using System.Diagnostics;
18 using System.IO;
19 using System.Linq;
20 using System.Text;
21 using System.Threading.Tasks;
22 using System;
23 using Xunit;
24 
25 namespace Google.OrTools.Test
26 {
27     public class Task
28     {
29         public int Id { get; private set; }
30         public int TaskType { get; private set; }
31         public int LocationId { get; private set; }
32         public Dictionary<int, int> Durations { get; private set; }
33         public int TaskPosition { get; private set; }
34 
Task(int id, int taskType, int locationIndex, int taskPosition, Dictionary<int, int> durations)35         public Task(int id, int taskType, int locationIndex, int taskPosition, Dictionary<int, int> durations)
36         {
37             Id = id;
38             TaskType = taskType;
39             LocationId = locationIndex;
40             Durations = durations;
41             TaskPosition = taskPosition;
42         }
43 
Task(int id, int taskType, int locationIndex, int taskPosition)44         public Task(int id, int taskType, int locationIndex, int taskPosition)
45         {
46             Id = id;
47             TaskType = taskType;
48             LocationId = locationIndex;
49             TaskPosition = taskPosition;
50             Durations = new Dictionary<int, int>();
51         }
52     }
53 
54     public class WorkLocation
55     {
56         public int Id { get; private set; }
57         public int NbTasks
58         {
59             get {
60                 Debug.Assert(Tasks != null);
61                 return Tasks.Length;
62             }
63             set {
64                 Debug.Assert(Tasks == null);
65                 Tasks = new Task[value];
66             }
67         }
68         public Task[] Tasks { get; private set; }
69 
WorkLocation(int index)70         public WorkLocation(int index)
71         {
72             Id = index;
73         }
74     }
75 
76     public class Tool
77     {
78         public int Id { get; private set; }
79         public HashSet<int> TaskTypes { get; set; }
80         public int[,] TravellingTime { get; set; }
81         public int InitialLocationId { get; set; }
82 
Tool(int index, int initialLocation = 0)83         public Tool(int index, int initialLocation = 0)
84         {
85             Id = index;
86             InitialLocationId = initialLocation;
87             TaskTypes = new HashSet<int>();
88         }
89 
AddTaskType(int t)90         public void AddTaskType(int t)
91         {
92             TaskTypes.Add(t);
93         }
94 
CanPerformTaskType(int taskType)95         public bool CanPerformTaskType(int taskType)
96         {
97             return TaskTypes.Contains(taskType);
98         }
99     }
100 
101     public class FactoryDescription
102     {
103         public Tool[] Tools { get; private set; }
104         public WorkLocation[] Locations { get; private set; }
105 
106         public int NbWorkLocations
107         {
108             get {
109                 return Locations.Length;
110             }
111         }
112         public int NbTools
113         {
114             get {
115                 return Tools.Length;
116             }
117         }
118 
119         public int NbTaskPerCycle { get; private set; }
120         // TaskType go typically from 0 to 6. InspectionType indicates which
121         // is the TaskType that correspond to Inspection.
122         public int Inspection { get; private set; }
123         // All the time within the schedule horizon in which the blast can start.
124         public long[] InspectionStarts { get; private set; }
125 
126         public int Horizon { get; private set; }
127 
128         // horizon equal to 2 weeks (in minutes).
FactoryDescription(int nbTools, int nbLocations, int nbTaskPerCycle, int horizon = 14 * 24 * 60)129         public FactoryDescription(int nbTools, int nbLocations, int nbTaskPerCycle, int horizon = 14 * 24 * 60)
130         {
131             Debug.Assert(nbTools > 0);
132             Debug.Assert(nbLocations > 0);
133             Debug.Assert(nbTaskPerCycle > 0);
134             Debug.Assert(horizon > 0);
135             NbTaskPerCycle = nbTaskPerCycle;
136             Inspection = NbTaskPerCycle - 1;
137             Tools = new Tool[nbTools];
138             Horizon = horizon;
139             for (int i = 0; i < nbTools; i++)
140                 Tools[i] = new Tool(i);
141             Locations = new WorkLocation[nbLocations];
142             for (int i = 0; i < nbLocations; i++)
143                 Locations[i] = new WorkLocation(i);
144 
145             InspectionStarts = new long[] { -1, 600, 1200, 1800, 2400, 2800 };
146         }
147 
getToolPerTaskType(int taskType)148         public Tool[] getToolPerTaskType(int taskType)
149         {
150             var elements = from tool in Tools
151                            where tool.CanPerformTaskType(taskType) select tool;
152             return elements.ToArray();
153         }
154 
getFlatTaskList()155         public Task[] getFlatTaskList()
156         {
157             return (from location in Locations from task in location.Tasks orderby task.Id select task).ToArray();
158         }
159 
getTaskTypes()160         public int[] getTaskTypes()
161         {
162             return (from location in Locations from task in location.Tasks select task.TaskType).Distinct().ToArray();
163         }
164 
165         // TODO: This should be enhanced
SanityCheck()166         public void SanityCheck()
167         {
168             foreach (Tool tool in Tools)
169             {
170                 Debug.Assert(tool.TravellingTime.GetLength(0) == NbWorkLocations);
171                 Debug.Assert(tool.TravellingTime.GetLength(1) == NbWorkLocations);
172                 for (int i = 0; i < NbWorkLocations; i++)
173                     Debug.Assert(tool.TravellingTime[i, i] == 0);
174             }
175         }
176     }
177 
178     interface DataReader
179     {
FetchData()180         FactoryDescription FetchData();
181     }
182 
183     public class SmallSyntheticData : DataReader
184     {
SmallSyntheticData()185         public SmallSyntheticData()
186         {
187         }
188 
FetchData()189         public FactoryDescription FetchData()
190         {
191             // deterministic seed for result reproducibility
192             Random randomDuration = new Random(2);
193 
194             // FactoryDescription(nbTools, nblocations, nbTasks per cycle)
195             FactoryDescription factoryDescription = new FactoryDescription(5, 4, 3);
196 
197             // Travelling time and distance are temporarily identical and they
198             // are no different for different tools
199             int[,] travellingTime = new int[factoryDescription.NbWorkLocations, factoryDescription.NbWorkLocations];
200             for (int i = 0; i < travellingTime.GetLength(0); i++)
201             {
202                 for (int j = 0; j < travellingTime.GetLength(1); j++)
203                 {
204                     if (i == j)
205                         travellingTime[i, j] = 0;
206                     else
207                         travellingTime[i, j] = (5 * Math.Abs(i - j)) * 10;
208                 }
209             }
210 
211             factoryDescription.Tools[0].AddTaskType(0);
212             factoryDescription.Tools[1].AddTaskType(0);
213             factoryDescription.Tools[2].AddTaskType(1);
214             factoryDescription.Tools[3].AddTaskType(1);
215             factoryDescription.Tools[4].AddTaskType(2);
216             factoryDescription.Tools[1].AddTaskType(1);
217 
218             foreach (Tool tool in factoryDescription.Tools)
219                 tool.TravellingTime = travellingTime;
220 
221             int c = 0;
222             int nbCyclePerWorkLocation = 2;
223             int[] boll = new int[100];
224             for (int i = 0; i < factoryDescription.NbWorkLocations; i++)
225             {
226                 factoryDescription.Locations[i].NbTasks = nbCyclePerWorkLocation * factoryDescription.NbTaskPerCycle;
227                 for (int j = 0; j < nbCyclePerWorkLocation; j++)
228                 {
229                     for (int k = 0; k < factoryDescription.NbTaskPerCycle; k++)
230                     {
231                         Task t = new Task(c, k, i, k + j * factoryDescription.NbTaskPerCycle);
232 
233                         // Filling in tool-dependent durations
234                         Tool[] compatibleTools = factoryDescription.getToolPerTaskType(k);
235                         foreach (Tool tool in compatibleTools)
236                         {
237                             boll[c] = randomDuration.Next(13, 17) * 10;
238                             ;
239                             t.Durations[tool.Id] = boll[c];
240                         }
241                         factoryDescription.Locations[i].Tasks[t.TaskPosition] = t;
242                         c++;
243                     }
244                 }
245             }
246 
247             factoryDescription.SanityCheck();
248             return factoryDescription;
249         }
250     }
251 
252     public class RandomSelectToolHeuristic : NetDecisionBuilder
253     {
254         private FactoryScheduling factoryScheduling;
255         private Random rnd;
256 
RandomSelectToolHeuristic(FactoryScheduling factoryScheduling, int seed)257         public RandomSelectToolHeuristic(FactoryScheduling factoryScheduling, int seed)
258         {
259             this.factoryScheduling = factoryScheduling;
260             // deterministic seed for result reproducibility
261             this.rnd = new Random(seed);
262         }
263 
Next(Solver solver)264         public override Decision Next(Solver solver)
265         {
266             foreach (IntVar var in factoryScheduling.SelectedTool)
267             {
268                 if (!var.Bound())
269                 {
270                     int min = (int)var.Min();
271                     int max = (int)var.Max();
272                     int rndVal = rnd.Next(min, max + 1);
273                     while (!var.Contains(rndVal))
274                         rndVal = rnd.Next(min, max + 1);
275                     return solver.MakeAssignVariableValue(var, rndVal);
276                 }
277             }
278             return null;
279         }
280     }
281 
282     class TaskAlternative
283     {
284         public Task Task { get; private set; }
285         public IntVar ToolVar { get; set; }
286         public List<IntervalVar> Intervals { get; private set; }
287 
TaskAlternative(Task t)288         public TaskAlternative(Task t)
289         {
290             Task = t;
291             Intervals = new List<IntervalVar>();
292         }
293     }
294 
295     public class FactoryScheduling
296     {
297         private FactoryDescription factoryData;
298         private Solver solver;
299 
300         private Task[] tasks;
301         private int[] taskTypes;
302 
303         /* Flat list of all the tasks */
304         private TaskAlternative[] taskStructures;
305 
306         /* Task per WorkLocation: location2Task[d][i]: the i-th task of the
307          * d-th location */
308         private TaskAlternative[][] location2Task;
309 
310         /* Task per Tool: tool2Task[t][i]: the i-th task of the t-th tool.
311            Note that it does NOT imply that the it will be the i-th
312            executed. In other words, it should be considered as an unordered
313            set.  Furthermore, tool2Task[t][i] can also be *unperformed* */
314         private List<IntervalVar>[] tool2Task;
315 
316         /* All the transition times for the tools.
317            tool2TransitionTimes[t][i]: the transition time of the t-th tool
318            from the i-th task to the next */
319         private List<IntVar>[] tool2TransitionTimes;
320 
321         /* Map between the interval var of a tool to its related task id.
322            toolIntervalVar2TaskId[t][k] = i: in the t-th tool, the k-th
323            interval var correspond to tasks[i] */
324         private List<int>[] toolIntervalVar2TaskId;
325 
326         /* Tools per task type: taskType2Tool[tt][t]: the t-th tool capable
327          * of doing the tt-th task type */
328         private List<Tool>[] taskType2Tool;
329 
330         /* For each task which tools is performed upon */
331         private List<IntVar> selectedTool;
332         public List<IntVar> SelectedTool
333         {
334             get {
335                 return selectedTool;
336             }
337         }
338 
339         /* Sequence of task for each tool */
340         private SequenceVar[] allToolSequences;
341         public SequenceVar[] AllToolSequences
342         {
343             get {
344                 return allToolSequences;
345             }
346         }
347 
348         /* Makespan var */
349         private IntVar makespan;
350 
351         /* Objective */
352         private OptimizeVar objective;
353 
354         /* maximum horizon */
355         private int horizon;
356 
357         /* Start & End times of IntervalVars*/
358         IntVar[][] startingTimes;
359         IntVar[][] endTimes;
360 
FactoryScheduling(FactoryDescription data)361         public FactoryScheduling(FactoryDescription data)
362         {
363             factoryData = data;
364         }
365 
Init()366         private void Init()
367         {
368             horizon = factoryData.Horizon;
369             solver = new Solver("Factory Scheduling");
370             tasks = factoryData.getFlatTaskList();
371             taskTypes = factoryData.getTaskTypes();
372             taskStructures = new TaskAlternative[tasks.Length];
373             location2Task = new TaskAlternative[factoryData.NbWorkLocations][];
374             tool2Task = new List<IntervalVar>[factoryData.NbTools];
375             toolIntervalVar2TaskId = new List<int>[factoryData.NbTools];
376             tool2TransitionTimes = new List<IntVar>[factoryData.NbTools];
377 
378             taskType2Tool = new List<Tool>[taskTypes.Length];
379             selectedTool = new List<IntVar>();
380             for (int tt = 0; tt < taskTypes.Length; tt++)
381                 taskType2Tool[tt] = new List<Tool>();
382 
383             foreach (Tool tool in factoryData.Tools)
384                 foreach (int taskType in tool.TaskTypes)
385                     taskType2Tool[taskType].Add(tool);
386             for (int d = 0; d < factoryData.NbWorkLocations; d++)
387                 location2Task[d] = new TaskAlternative[factoryData.Locations[d].NbTasks];
388             for (int t = 0; t < factoryData.NbTools; t++)
389             {
390                 tool2Task[t] = new List<IntervalVar>();
391                 toolIntervalVar2TaskId[t] = new List<int>();
392                 tool2TransitionTimes[t] = new List<IntVar>();
393             }
394 
395             allToolSequences = new SequenceVar[factoryData.NbTools - 1];
396 
397             startingTimes = new IntVar[factoryData.NbTools - 1][];
398             endTimes = new IntVar[factoryData.NbTools - 1][];
399         }
400 
PostTransitionTimeConstraints(int t, bool postTransitionsConstraint = true)401         private void PostTransitionTimeConstraints(int t, bool postTransitionsConstraint = true)
402         {
403             Tool tool = factoryData.Tools[t];
404             // if it is a inspection, we make sure there are no transitiontimes
405             if (tool.CanPerformTaskType(factoryData.Inspection))
406                 tool2TransitionTimes[t].Add(null);
407             else
408             {
409                 int[,] tt = tool.TravellingTime;
410 
411                 SequenceVar seq = allToolSequences[t];
412                 long s = seq.Size();
413                 IntVar[] nextLocation = new IntVar[s + 1];
414 
415                 // The seq.Next(i) represents the task performed after the i-th
416                 // task in the sequence seq.Next(0) represents the first task
417                 // performed for extracting travelling times we need to get the
418                 // related location In case a task is not performed (seq.Next(i)
419                 // == i), i.e. it's pointing to itself The last performed task
420                 // (or pre-start task, if no tasks are performed) will have
421                 // seq.Next(i) == s + 1 therefore we add a virtual location
422                 // whose travelling time is equal to 0
423                 //
424                 // NOTE: The index of a SequenceVar are 0..n, but the domain
425                 // range is 1..(n+1), this is due to that the start node = 0 is
426                 // a dummy node, and the node where seq.Next(i) == n+1 is the
427                 // end node
428 
429                 // Extra elements for the unreachable start node (0), and the
430                 // end node whose next task takes place in a virtual location
431                 int[] taskIndex2locationId = new int[s + 2];
432                 taskIndex2locationId[0] = -10;
433                 for (int i = 0; i < s; i++)
434                     taskIndex2locationId[i + 1] = tasks[toolIntervalVar2TaskId[t][i]].LocationId;
435 
436                 // this is the virtual location for unperformed tasks
437                 taskIndex2locationId[s + 1] = factoryData.NbWorkLocations;
438 
439                 // Build the travelling time matrix with the additional virtual location
440                 int[][] ttWithVirtualLocation = new int [factoryData.NbWorkLocations + 1][];
441                 for (int d1 = 0; d1 < ttWithVirtualLocation.Length; d1++)
442                 {
443                     ttWithVirtualLocation[d1] = new int[factoryData.NbWorkLocations + 1];
444                     for (int d2 = 0; d2 < ttWithVirtualLocation.Length; d2++)
445                         if (d1 == factoryData.NbWorkLocations)
446                         {
447                             ttWithVirtualLocation[d1][d2] = 0;
448                         }
449                         else
450                         {
451                             ttWithVirtualLocation[d1][d2] = (d2 == factoryData.NbWorkLocations) ? 0 : tt[d1, d2];
452                         }
453                 }
454 
455                 for (int i = 0; i < nextLocation.Length; i++)
456                 {
457                     // this is the next-location associated with the i-th task
458                     nextLocation[i] = solver.MakeElement(taskIndex2locationId, seq.Next(i)).Var();
459 
460                     int d = (i == 0) ? tool.InitialLocationId : tasks[toolIntervalVar2TaskId[t][i - 1]].LocationId;
461                     if (i == 0)
462                     {
463                         // To be changed - right now we don't have meaningful indata
464                         // of previous location Ugly way of setting initial travel
465                         // time to = 0, as this is how we find common grounds
466                         // between benchmark algorithm and this
467                         tool2TransitionTimes[t].Add(
468                             solver.MakeElement(new int[ttWithVirtualLocation[d].Length], nextLocation[i]).Var());
469                     }
470                     else
471                     {
472                         tool2TransitionTimes[t].Add(
473                             solver.MakeElement(ttWithVirtualLocation[d], nextLocation[i]).Var());
474                     }
475                 }
476 
477                 // Extra elements for the unreachable start node (0), and the
478                 // end node whose next task takes place in a virtual location
479                 startingTimes[t] = new IntVar[s + 2];
480                 endTimes[t] = new IntVar[s + 2];
481 
482                 startingTimes[t][0] = solver.MakeIntConst(0);
483                 // Tbd: Set this endtime to the estimated time of finishing
484                 // previous task for the current tool
485                 endTimes[t][0] = solver.MakeIntConst(0);
486 
487                 for (int i = 0; i < s; i++)
488                 {
489                     startingTimes[t][i + 1] = tool2Task[t][i].SafeStartExpr(-1).Var();
490                     endTimes[t][i + 1] = tool2Task[t][i].SafeEndExpr(-1).Var();
491                 }
492                 startingTimes[t][s + 1] = solver.MakeIntConst(factoryData.Horizon);
493                 endTimes[t][s + 1] = solver.MakeIntConst(factoryData.Horizon);
494 
495                 // Enforce (or not) that each task is separated by the
496                 // transition time to the next task
497                 for (int i = 0; i < nextLocation.Length; i++)
498                 {
499                     IntVar nextStart = solver.MakeElement(startingTimes[t], seq.Next(i).Var()).Var();
500                     if (postTransitionsConstraint)
501                         solver.Add(endTimes[t][i] + tool2TransitionTimes[t][i] <= nextStart);
502                 }
503             }
504         }
505 
Model()506         private void Model()
507         {
508             /* Building basic task data structures */
509             for (int i = 0; i < tasks.Length; i++)
510             {
511                 /* Create a new set of possible IntervalVars & IntVar to decide
512                  * which one (and only 1) is performed */
513                 taskStructures[i] = new TaskAlternative(tasks[i]);
514 
515                 /* Container to use when posting constraints */
516                 location2Task[tasks[i].LocationId][tasks[i].TaskPosition] = taskStructures[i];
517 
518                 /* Get task type */
519                 int taskType = tasks[i].TaskType;
520 
521                 /* Possible tool for this task */
522                 List<Tool> tools = taskType2Tool[taskType];
523                 bool optional = tools.Count > 1;
524 
525                 /* List of boolean variables. If performedOnTool[t] == true then
526                  * the task is performed on tool t */
527                 List<IntVar> performedOnTool = new List<IntVar>();
528                 for (int t = 0; t < tools.Count; t++)
529                 {
530                     /* Creating an IntervalVar. If tools.Count > 1 the intervalVar
531                      * is *OPTIONAL* */
532                     int toolId = tools[t].Id;
533                     Debug.Assert(tasks[i].Durations.ContainsKey(toolId));
534                     int duration = tasks[i].Durations[toolId];
535                     string name = "J " + tasks[i].Id + " [" + toolId + "]";
536 
537                     IntervalVar intervalVar;
538                     if (taskType == factoryData.Inspection)
539                     {
540                         /* We set a 0 time if the task is an inspection */
541                         duration = 0;
542                         intervalVar = solver.MakeFixedDurationIntervalVar(0, horizon, duration, optional, name);
543                         IntVar start = intervalVar.SafeStartExpr(-1).Var();
544 
545                         intervalVar.SafeStartExpr(-1).Var().SetValues(factoryData.InspectionStarts);
546                     }
547                     else
548                     {
549                         intervalVar = solver.MakeFixedDurationIntervalVar(0, horizon, duration, optional, name);
550                     }
551 
552                     taskStructures[i].Intervals.Add(intervalVar);
553                     tool2Task[toolId].Add(intervalVar);
554                     toolIntervalVar2TaskId[toolId].Add(i);
555 
556                     /* Collecting all the bool vars, even if they are optional */
557                     performedOnTool.Add(intervalVar.PerformedExpr().Var());
558                 }
559 
560                 /* Linking the bool var to a single integer variable: */
561                 /* if alternativeToolVar == t <=> performedOnTool[t] == true */
562                 string alternativeName = "J " + tasks[i].Id;
563                 IntVar alternativeToolVar = solver.MakeIntVar(0, tools.Count - 1, alternativeName);
564                 taskStructures[i].ToolVar = alternativeToolVar;
565 
566                 solver.Add(solver.MakeMapDomain(alternativeToolVar, performedOnTool.ToArray()));
567                 Debug.Assert(performedOnTool.ToArray().Length == alternativeToolVar.Max() + 1);
568 
569                 selectedTool.Add(alternativeToolVar);
570             }
571 
572             /* Creates precedences on a work Location in order to enforce a
573              * fully ordered set within the same location
574              */
575             for (int d = 0; d < location2Task.Length; d++)
576             {
577                 for (int i = 0; i < location2Task[d].Length - 1; i++)
578                 {
579                     TaskAlternative task1 = location2Task[d][i];
580                     TaskAlternative task2 = location2Task[d][i + 1];
581                     /* task1 must end before task2 starts */
582                     /* Adding precedence for each possible alternative pair */
583                     for (int t1 = 0; t1 < task1.Intervals.Count(); t1++)
584                     {
585                         IntervalVar task1Alternative = task1.Intervals[t1];
586                         for (int t2 = 0; t2 < task2.Intervals.Count(); t2++)
587                         {
588                             IntervalVar task2Alternative = task2.Intervals[t2];
589                             Constraint precedence = solver.MakeIntervalVarRelation(
590                                 task2Alternative, Solver.STARTS_AFTER_END, task1Alternative);
591                             solver.Add(precedence);
592                         }
593                     }
594                 }
595             }
596 
597             /* Adds disjunctive constraints on unary resources, and creates
598              * sequence variables. */
599             for (int t = 0; t < factoryData.NbTools; t++)
600             {
601                 string name = "Tool " + t;
602 
603                 if (!factoryData.Tools[t].CanPerformTaskType(factoryData.Inspection))
604                 {
605                     DisjunctiveConstraint ct = solver.MakeDisjunctiveConstraint(tool2Task[t].ToArray(), name);
606                     solver.Add(ct);
607                     allToolSequences[t] = ct.SequenceVar();
608                 }
609                 PostTransitionTimeConstraints(t, true);
610             }
611 
612             /* Collecting all tasks end for makespan objective function */
613             List<IntVar> intervalEnds = new List<IntVar>();
614             for (int i = 0; i < tasks.Length; i++)
615                 foreach (IntervalVar var in taskStructures[i].Intervals)
616                     intervalEnds.Add(var.SafeEndExpr(-1).Var());
617 
618             /* Objective: minimize the makespan (maximum end times of all tasks) */
619             makespan = solver.MakeMax(intervalEnds.ToArray()).Var();
620             objective = solver.MakeMinimize(makespan, 1);
621         }
622 
Search()623         private void Search()
624         {
625             int seed = 2; // This is a good seed to show the crash
626 
627             /* Assigning first tools */
628             DecisionBuilder myToolAssignmentPhase = new RandomSelectToolHeuristic(this, seed);
629 
630             /* Ranking of the tools */
631             DecisionBuilder sequencingPhase = solver.MakePhase(allToolSequences, Solver.SEQUENCE_DEFAULT);
632 
633             /* Then fixing time of tasks as early as possible */
634             DecisionBuilder timingPhase =
635                 solver.MakePhase(makespan, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);
636 
637             /* Overall phase */
638             DecisionBuilder mainPhase = solver.Compose(myToolAssignmentPhase, sequencingPhase, timingPhase);
639 
640             /* Logging */
641             const int logFrequency = 1000000;
642             SearchMonitor searchLog = solver.MakeSearchLog(logFrequency, objective);
643 
644             /* Restarts */
645             SearchMonitor searchRestart = solver.MakeLubyRestart(100);
646 
647             /* Search Limit in ms */
648             SearchLimit limit = solver.MakeTimeLimit(180 * 1000);
649 
650             /* Collecting best solution */
651             SolutionCollector collector = solver.MakeLastSolutionCollector();
652             collector.AddObjective(makespan);
653 
654             // collector.Add( pile.ToArray() );
655             solver.NewSearch(mainPhase, searchLog, searchRestart, objective, limit);
656             while (solver.NextSolution())
657             {
658                 Console.WriteLine("MAKESPAN: " + makespan.Value());
659             }
660         }
661 
Solve()662         public void Solve()
663         {
664             Init();
665             Model();
666             Search();
667         }
668     }
669 
670     public class Issue18Test
671     {
672         [Fact]
FactorySchedulingTest()673         public void FactorySchedulingTest()
674         {
675             FactoryScheduling scheduling = new FactoryScheduling(new SmallSyntheticData().FetchData());
676             scheduling.Solve();
677         }
678     }
679 } // namespace Google.OrTools.Test
680