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