Exemple #1
0
 private void ExtractSingletonLabelsFrom(SolutionCollector theSolutionCollector)
 {
     foreach (var variableTuple in this.orToolsCache.SingletonVariableMap)
     {
         var solverValue = theSolutionCollector.Value(0, variableTuple.Value.Item2);
         var modelValue  = ConvertSolverValueToModel(variableTuple.Value.Item1, solverValue);
         var newValue    = new SingletonVariableLabelModel(variableTuple.Value.Item1, new ValueModel(modelValue));
         this.snapshot.AddSingletonLabel(newValue);
     }
 }
Exemple #2
0
 private void ExtractAggregateLabelsFrom(SolutionCollector theSolutionCollector)
 {
     foreach (var aggregateTuple in this.orToolsCache.AggregateVariableMap)
     {
         var newValueBindings = new List <ValueModel>();
         var orVariables      = aggregateTuple.Value.Item2;
         foreach (var orVariable in orVariables)
         {
             var solverValue = theSolutionCollector.Value(0, orVariable);
             var modelValue  = ConvertSolverValueToModel(aggregateTuple.Value.Item1, solverValue);
             newValueBindings.Add(new ValueModel(modelValue));
         }
         var newCompoundLabel = new AggregateVariableLabelModel(aggregateTuple.Value.Item1, newValueBindings);
         this.snapshot.AddAggregateLabel(newCompoundLabel);
     }
 }
Exemple #3
0
        private void ExtractBucketLabelFrom(SolutionCollector solutionCollector, OrBucketVariableMap bucketVariableMap)
        {
            var bundleLabels = new List <BundleLabelModel>();

            foreach (var bundleMap in bucketVariableMap.GetBundleMaps())
            {
                var variableLabels = new List <SingletonVariableLabelModel>();
                foreach (var variableMap in bundleMap.GetVariableMaps())
                {
                    var solverValue = solutionCollector.Value(0, variableMap.SolverVariable);
                    var modelValue  = ConvertSolverValueToModel(bucketVariableMap.Bucket, solverValue);
                    variableLabels.Add(new SingletonVariableLabelModel(variableMap.ModelVariable, new ValueModel(modelValue)));
                }
                bundleLabels.Add(new BundleLabelModel(bucketVariableMap.Bucket.Bundle, variableLabels));
            }

            var bucketLabel = new BucketLabelModel(bucketVariableMap.Bucket, bundleLabels);

            this.snapshot.AddBucketLabel(bucketLabel);
        }
Exemple #4
0
    //  We don't need helper functions here
    //  Csharp syntax is easier than C++ syntax!

    private static void CPisFun(int kBase, int time_limit_param, bool print)
    {
        // Use some profiling and change the default parameters of the solver
        SolverParameters solver_params = new SolverParameters();

        // Change the profile level
        solver_params.profile_level = SolverParameters.NORMAL_PROFILING;

        //  Constraint Programming engine
        Solver solver = new Solver("CP is fun!", solver_params);

        // Decision variables
        IntVar c = solver.MakeIntVar(1, kBase - 1, "C");
        IntVar p = solver.MakeIntVar(0, kBase - 1, "P");
        IntVar i = solver.MakeIntVar(1, kBase - 1, "I");
        IntVar s = solver.MakeIntVar(0, kBase - 1, "S");
        IntVar f = solver.MakeIntVar(1, kBase - 1, "F");
        IntVar u = solver.MakeIntVar(0, kBase - 1, "U");
        IntVar n = solver.MakeIntVar(0, kBase - 1, "N");
        IntVar t = solver.MakeIntVar(1, kBase - 1, "T");
        IntVar r = solver.MakeIntVar(0, kBase - 1, "R");
        IntVar e = solver.MakeIntVar(0, kBase - 1, "E");

        // We need to group variables in a vector to be able to use
        // the global constraint AllDifferent
        IntVar[] letters = new IntVar[] { c, p, i, s, f, u, n, t, r, e };

        // Check if we have enough digits
        if (kBase < letters.Length)
        {
            throw new Exception("kBase < letters.Length");
        }

        //  Constraints
        solver.Add(letters.AllDifferent());

        // CP + IS + FUN = TRUE
        solver.Add(p + s + n + kBase * (c + i + u) + kBase * kBase * f ==
                   e + kBase * u + kBase * kBase * r + kBase * kBase * kBase * t);

        SolutionCollector all_solutions = solver.MakeAllSolutionCollector();

        //  Add the interesting variables to the SolutionCollector
        all_solutions.Add(letters);

        //  Decision Builder: hot to scour the search tree
        DecisionBuilder db = solver.MakePhase(letters,
                                              Solver.CHOOSE_FIRST_UNBOUND,
                                              Solver.ASSIGN_MIN_VALUE);

        // Add some time limit
        SearchLimit time_limit = solver.MakeTimeLimit(time_limit_param);

        solver.Solve(db, all_solutions, time_limit);

        //  Retrieve the solutions
        int numberSolutions = all_solutions.SolutionCount();

        Console.WriteLine("Number of solutions: " + numberSolutions);

        if (print)
        {
            for (int index = 0; index < numberSolutions; ++index)
            {
                Console.Write("C=" + all_solutions.Value(index, c));
                Console.Write(" P=" + all_solutions.Value(index, p));
                Console.Write(" I=" + all_solutions.Value(index, i));
                Console.Write(" S=" + all_solutions.Value(index, s));
                Console.Write(" F=" + all_solutions.Value(index, f));
                Console.Write(" U=" + all_solutions.Value(index, u));
                Console.Write(" N=" + all_solutions.Value(index, n));
                Console.Write(" T=" + all_solutions.Value(index, t));
                Console.Write(" R=" + all_solutions.Value(index, r));
                Console.Write(" E=" + all_solutions.Value(index, e));
                Console.WriteLine();
            }
        }

        // Save profile in file
        solver.ExportProfilingOverview("profile.txt");
    }
Exemple #5
0
    public void RunJobShopScheduling(String solverType)
    {
        Solver solver = new Solver(solverType);

        if (solver == null)
        {
            Console.WriteLine("JobShop failed to create a solver " + solverType);
            return;
        }

        // All tasks
        Dictionary <string, IntervalVar> allTasks = new Dictionary <string, IntervalVar>();

        // Creates jobs
        for (int i = 0; i < allJobs.Count; i++)
        {
            for (int j = 0; j < machines.ElementAt(i).Count; j++)
            {
                IntervalVar oneTask = solver.MakeFixedDurationIntervalVar(0,
                                                                          horizon,
                                                                          processingTimes.ElementAt(i).ElementAt(j),
                                                                          false,
                                                                          "Job_" + i + "_" + j);
                //Console.WriteLine("Job_" + i + "_" + j);
                allTasks.Add("Job_" + i + "_" + j, oneTask);
            }
        }

        // Create sequence variables and add disjuctive constraints
        List <SequenceVar> allSequences = new List <SequenceVar>();

        foreach (var machine in allMachines)
        {
            List <IntervalVar> machinesJobs = new List <IntervalVar>();
            for (int i = 0; i < allJobs.Count; i++)
            {
                for (int k = 0; k < machines.ElementAt(i).Count; k++)
                {
                    if (machines.ElementAt(i).ElementAt(k) == machine)
                    {
                        machinesJobs.Add(allTasks["Job_" + i + "_" + k]);
                    }
                }
            }

            DisjunctiveConstraint disj = solver.MakeDisjunctiveConstraint(machinesJobs.ToArray(),
                                                                          "machine " + machine);
            allSequences.Add(disj.SequenceVar());
            solver.Add(disj);
        }

        // Add conjunctive constraints
        foreach (var job in allJobs)
        {
            for (int j = 0; j < machines.ElementAt(job).Count - 1; j++)
            {
                solver.Add(allTasks["Job_" + job + "_" + (j + 1)].StartsAfterEnd(allTasks["Job_" + job + "_" + j]));
            }
        }

        // Set the objective
        IntVar[] allEnds = new IntVar[jobsCount];
        for (int i = 0; i < allJobs.Count; i++)
        {
            allEnds[i] = allTasks["Job_" + i + "_" + (machines.ElementAt(i).Count - 1)].EndExpr().Var();
        }

        // Objective: minimize the makespan (maximum end times of all tasks)
        // of the problem.
        IntVar      objectiveVar     = solver.MakeMax(allEnds).Var();
        OptimizeVar objectiveMonitor = solver.MakeMinimize(objectiveVar, 1);

        // Create search phases
        DecisionBuilder sequencePhase = solver.MakePhase(allSequences.ToArray(), Solver.SEQUENCE_DEFAULT);
        DecisionBuilder varsPhase     = solver.MakePhase(objectiveVar, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);

        // The main decision builder (ranks all tasks, then fixes the
        // objectiveVariable).
        DecisionBuilder mainPhase = solver.Compose(sequencePhase, varsPhase);

        SolutionCollector collector = solver.MakeLastSolutionCollector();

        collector.Add(allSequences.ToArray());
        collector.Add(objectiveVar);

        foreach (var i in allMachines)
        {
            SequenceVar sequence      = allSequences.ElementAt(i);
            long        sequenceCount = sequence.Size();
            for (int j = 0; j < sequenceCount; j++)
            {
                IntervalVar t = sequence.Interval(j);
                collector.Add(t.StartExpr().Var());
                collector.Add(t.EndExpr().Var());
            }
        }

        // Search.
        bool solutionFound = solver.Solve(mainPhase, null, objectiveMonitor, null, collector);

        if (solutionFound)
        {
            //The index of the solution from the collector
            const int  SOLUTION_INDEX = 0;
            Assignment solution       = collector.Solution(SOLUTION_INDEX);

            string solLine      = "";
            string solLineTasks = "";
            Console.WriteLine("Time Intervals for Tasks\n");

            List <List <TimeSpan> > tuplesSolution = new List <List <TimeSpan> >();

            for (int m = 0; m < this.machinesCount; m++)
            {
                //Console.WriteLine("MachineCount: " + this.machinesCount);
                solLine      = "Machine " + m + " :";
                solLineTasks = "Machine " + m + ": ";

                SequenceVar seq            = allSequences.ElementAt(m);
                int[]       storedSequence = collector.ForwardSequence(SOLUTION_INDEX, seq);

                foreach (int taskIndex in storedSequence)
                {
                    //Console.WriteLine("taskIndex: " + taskIndex);
                    IntervalVar task = seq.Interval(taskIndex);
                    solLineTasks += task.Name() + " ";
                    //Console.WriteLine("Len: " + storedSequence.Length);
                }

                // First GreenTime
                //TimeSpan timeToAdd = tuplesSolution.First().Last();
                TimeSpan timeEndBucket   = this.bucketInfo.EndBucket;
                TimeSpan timeStartBucket = this.bucketInfo.StartBucket;


                int  solutionSize = tuplesSolution.Count;
                bool isEnd        = false;

                List <int> list_id = jobIds.ElementAt(m);
                // Adding GreenTime to Solution
                while (timeStartBucket.CompareTo(timeEndBucket) < 0)
                {
                    foreach (int taskIndex in storedSequence)
                    {
                        IntervalVar task = seq.Interval(taskIndex);

                        var startValue = TimeSpan.FromSeconds(collector.Value(0, task.StartExpr().Var()));
                        var endValue   = TimeSpan.FromSeconds(collector.Value(0, task.EndExpr().Var()));

                        TimeSpan greenTime = endValue.Subtract(startValue);
                        TimeSpan timeEnd;

                        timeEnd = timeStartBucket.Add(greenTime);


                        List <TimeSpan> tuple = new List <TimeSpan>();
                        tuple.Add(timeStartBucket);
                        if (timeEndBucket.CompareTo(timeEnd) < 0)
                        {
                            timeEnd = timeEndBucket;
                            isEnd   = true;
                        }

                        tuple.Add(timeEnd);
                        tuplesSolution.Add(tuple);
                        if (taskIndex + 1 < list_id.Count() && list_id.ElementAt(taskIndex) == list_id.ElementAt(taskIndex + 1))
                        {
                            timeStartBucket = timeStartBucket.Add(TimeSpan.FromSeconds(this.cycleTime));
                        }
                        else
                        {
                            timeStartBucket = timeEnd;
                        }
                        if (isEnd)
                        {
                            break;
                        }
                    }
                }

                //
                // Saving the Solution to a XML file
                //
                JobShop.save(m, tuplesSolution);

                //solLine += "\n";
                //solLineTasks += "\n";

                //Console.WriteLine(solLineTasks);
                //Console.WriteLine(solLine);
            }
        }
        else
        {
            Console.WriteLine("No solution found!");
        }
    }
Exemple #6
0
    public void RunJobShopScheduling(String solverType)
    {
        Solver solver = new Solver(solverType);

        if (solver == null)
        {
            Console.WriteLine("JobShop failed to create a solver " + solverType);
            return;
        }

        // All tasks
        Dictionary <string, IntervalVar> allTasks = new Dictionary <string, IntervalVar>();

        // Creates jobs
        for (int i = 0; i < allJobs.Count; i++)
        {
            for (int j = 0; j < machines.ElementAt(i).Count; j++)
            {
                IntervalVar oneTask = solver.MakeFixedDurationIntervalVar(0,
                                                                          horizon,
                                                                          processingTimes.ElementAt(i).ElementAt(j),
                                                                          false,
                                                                          "Job_" + i + "_" + j);

                allTasks.Add("Job_" + i + "_" + j, oneTask);
            }
        }

        // Create sequence variables and add disjuctive constraints
        List <SequenceVar> allSequences = new List <SequenceVar>();

        foreach (var machine in allMachines)
        {
            List <IntervalVar> machinesJobs = new List <IntervalVar>();
            for (int i = 0; i < allJobs.Count; i++)
            {
                for (int k = 0; k < machines.ElementAt(i).Count; k++)
                {
                    if (machines.ElementAt(i).ElementAt(k) == machine)
                    {
                        machinesJobs.Add(allTasks["Job_" + i + "_" + k]);
                    }
                }
            }

            DisjunctiveConstraint disj = solver.MakeDisjunctiveConstraint(machinesJobs.ToArray(),
                                                                          "machine " + machine);
            allSequences.Add(disj.SequenceVar());
            solver.Add(disj);
        }

        // Add conjunctive constraints
        foreach (var job in allJobs)
        {
            for (int j = 0; j < machines.ElementAt(job).Count - 1; j++)
            {
                solver.Add(allTasks["Job_" + job + "_" + (j + 1)].StartsAfterEnd(allTasks["Job_" + job + "_" + j]));
            }
        }

        // Set the objective
        IntVar[] allEnds = new IntVar[jobsCount];
        for (int i = 0; i < allJobs.Count; i++)
        {
            allEnds[i] = allTasks["Job_" + i + "_" + (machines.ElementAt(i).Count - 1)].EndExpr().Var();
        }

        // Objective: minimize the makespan (maximum end times of all tasks)
        // of the problem.
        IntVar      objectiveVar     = solver.MakeMax(allEnds).Var();
        OptimizeVar objectiveMonitor = solver.MakeMinimize(objectiveVar, 1);

        // Create search phases
        DecisionBuilder sequencePhase = solver.MakePhase(allSequences.ToArray(), Solver.SEQUENCE_DEFAULT);
        DecisionBuilder varsPhase     = solver.MakePhase(objectiveVar, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);

        // The main decision builder (ranks all tasks, then fixes the
        // objectiveVariable).
        DecisionBuilder mainPhase = solver.Compose(sequencePhase, varsPhase);

        SolutionCollector collector = solver.MakeLastSolutionCollector();

        collector.Add(allSequences.ToArray());
        collector.Add(objectiveVar);

        foreach (var i in allMachines)
        {
            SequenceVar sequence      = allSequences.ElementAt(i);
            long        sequenceCount = sequence.Size();
            for (int j = 0; j < sequenceCount; j++)
            {
                IntervalVar t = sequence.Interval(j);
                collector.Add(t.StartExpr().Var());
                collector.Add(t.EndExpr().Var());
            }
        }

        // Search.
        bool solutionFound = solver.Solve(mainPhase, null, objectiveMonitor, null, collector);

        if (solutionFound)
        {
            //The index of the solution from the collector
            const int  SOLUTION_INDEX = 0;
            Assignment solution       = collector.Solution(SOLUTION_INDEX);

            string solLine      = "";
            string solLineTasks = "";
            Console.WriteLine("Time Intervals for Tasks\n");

            for (int m = 0; m < this.machinesCount; m++)
            {
                //Console.WriteLine("MachineCount: " + this.machinesCount);
                solLine      = "Machine " + m + " :";
                solLineTasks = "Machine " + m + ": ";

                SequenceVar seq            = allSequences.ElementAt(m);
                int[]       storedSequence = collector.ForwardSequence(SOLUTION_INDEX, seq);

                foreach (int taskIndex in storedSequence)
                {
                    //Console.WriteLine("taskIndex: " + taskIndex);
                    IntervalVar task = seq.Interval(taskIndex);
                    solLineTasks += task.Name() + " ";
                    //Console.WriteLine("Len: " + storedSequence.Length);
                }

                foreach (int taskIndex in storedSequence)
                {
                    IntervalVar task    = seq.Interval(taskIndex);
                    string      solTemp = "[" + collector.Value(0, task.StartExpr().Var()) + ",";
                    solTemp += collector.Value(0, task.EndExpr().Var()) + "] ";
                    solLine += solTemp;
                }

                //solLine += "\n";
                solLineTasks += "\n";

                //Console.WriteLine(solLineTasks);
                Console.WriteLine(solLine);
            }
        }
        else
        {
            Console.WriteLine("No solution found!");
        }
    }
Exemple #7
0
        public static void Solve(FlexibleJobShopData data, bool chatty = false)
        {
            // Compute horizon
            var horizon = data.JobList.Sum(
                j => j.TaskList.Sum(
                    t => t.Durations.Max()));


            // ----- Create all intervals and vars -----

            /*
             * // Use some profiling and change the default parameters of the solver
             * SolverParameters solverParams = new SolverParameters();
             * // Change the profile level
             * solverParams.profile_level = SolverParameters.NORMAL_PROFILING;
             * // Constraint programming engine
             * Solver solver = new Solver("JobShop", solverParams);
             */
            // Constraint programming engine
            Solver solver = new Solver($"FlexibleJobShop: {data.Name}");

            // Initialize dictionaries to hold references to task intervals
            var tasksByJobId = new Dictionary <uint, List <TaskAlternative> >();

            foreach (var job in data.JobList)
            {
                tasksByJobId[job.Id] = new List <TaskAlternative>(job.TaskList.Count);
            }
            var tasksByMachineId = new Dictionary <uint, IntervalVarVector>();

            foreach (var machine in data.MachineList)
            {
                tasksByMachineId[machine.Id] = new IntervalVarVector();
            }

            // Creates all individual interval variables and collect in dictionaries
            foreach (var job in data.JobList)
            {
                foreach (var task in job.TaskList)
                {
                    var alternative = new TaskAlternative(job.Id);
                    tasksByJobId[job.Id].Add(alternative);
                    var activeVariables = new IntVarVector();
                    var hasAlternatives = task.AlternativesCount > 1;
                    for (int alt = 0; alt < task.AlternativesCount; alt++)
                    {
                        var         machine  = task.Machines[alt];
                        var         duration = task.Durations[alt];
                        var         name     = $"{task.Name}; Alternative {alt}: {machine.Name}, Duration {duration}";
                        IntervalVar interval = solver.MakeFixedDurationIntervalVar(0, horizon, duration, hasAlternatives, name);
                        alternative.Add(interval);
                        tasksByMachineId[machine.Id].Add(interval);
                        if (hasAlternatives)
                        {
                            activeVariables.Add(interval.PerformedExpr().Var());
                        }
                    }
                    alternative.AlternativeVar = solver.MakeIntVar(0, task.AlternativesCount - 1, task.Name);
                    if (hasAlternatives)
                    {
                        solver.Add(solver.MakeMapDomain(alternative.AlternativeVar, activeVariables));
                    }
                }
            }


            // ----- Create model -----

            // Create precedences inside jobs
            foreach (var taskAltList in tasksByJobId.Values)
            {
                for (int i = 0; i < taskAltList.Count - 1; i++)
                {
                    TaskAlternative currentTaskAlt = taskAltList[i];
                    TaskAlternative nextTaskAlt    = taskAltList[i + 1];
                    foreach (var alt1 in currentTaskAlt)
                    {
                        foreach (var alt2 in nextTaskAlt)
                        {
                            solver.Add(solver.MakeIntervalVarRelation(
                                           alt2, Solver.STARTS_AFTER_END, alt1));
                        }
                    }
                }
            }

            // Collect alternative variables.
            IntVarVector alternativeVariableVec = new IntVarVector();

            foreach (var taskAltList in tasksByJobId.Values)
            {
                foreach (var taskAlt in taskAltList)
                {
                    if (!taskAlt.AlternativeVar.Bound())
                    {
                        alternativeVariableVec.Add(taskAlt.AlternativeVar);
                    }
                }
            }

            // Add disjunctive constraints on unary resources, and create
            // sequence variables. A sequence variable is a dedicated variable
            // whose job is to sequence interval variables.
            SequenceVarVector allSequences = new SequenceVarVector();

            foreach (var machine in data.MachineList)
            {
                DisjunctiveConstraint disjCt = solver.MakeDisjunctiveConstraint(
                    tasksByMachineId[machine.Id], machine.Name);
                solver.Add(disjCt);
                allSequences.Add(disjCt.SequenceVar());
            }

            // Create array of end_times of jobs
            IntVarVector endsVec = new IntVarVector();

            foreach (var taskAltList in tasksByJobId.Values)
            {
                TaskAlternative lastTaskAlt = taskAltList.Last();
                foreach (var alt in lastTaskAlt)
                {
                    endsVec.Add(alt.SafeEndExpr(-1).Var());
                }
            }

            // Objective: minimize the makespan (maximum end times of all tasks)
            // of the problem.
            IntVar      objectiveVar     = solver.MakeMax(endsVec).Var();
            OptimizeVar objectiveMonitor = solver.MakeMinimize(objectiveVar, 1);


            // ----- Search monitors and decision builder -----

            // This decision builder will assign all alternative variables.
            DecisionBuilder alternativePhase = solver.MakePhase(alternativeVariableVec, Solver.CHOOSE_MIN_SIZE, Solver.ASSIGN_MIN_VALUE);

            // This decision builder will rank all tasks on all machines.
            DecisionBuilder sequencePhase = solver.MakePhase(allSequences, Solver.SEQUENCE_DEFAULT);

            // After the ranking of tasks, the schedule is still loose and any
            // task can be postponed at will. But, because the problem is now a PERT
            // (http://en.wikipedia.org/wiki/Program_Evaluation_and_Review_Technique),
            // we can schedule each task at its earliest start time. This is
            // conveniently done by fixing the objective variable to its
            // minimum value.
            DecisionBuilder objectivePhase = solver.MakePhase(objectiveVar, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);

            // The main decision builder (ranks all tasks, then fixes the
            // objective_variable).
            DecisionBuilder mainPhase = solver.Compose(alternativePhase, sequencePhase, objectivePhase);

            // Search log
            const int     kLogFrequency = 1000000;
            SearchMonitor searchLog     = solver.MakeSearchLog(kLogFrequency, objectiveMonitor);

            const long  FLAGS_time_limit_in_ms = 1000 * 60 * 20;
            SearchLimit limit = null;

            if (FLAGS_time_limit_in_ms > 0)
            {
                limit = solver.MakeTimeLimit(FLAGS_time_limit_in_ms);
            }

            SolutionCollector collector = solver.MakeLastSolutionCollector();

            collector.AddObjective(objectiveVar);
            collector.Add(alternativeVariableVec);
            collector.Add(allSequences);

            foreach (var taskVec in tasksByMachineId.Values)
            {
                foreach (var task in taskVec)
                {
                    collector.Add(task.StartExpr().Var());
                }
            }


            // ----- Search -----

            bool solutionFound = solver.Solve(mainPhase, searchLog, objectiveMonitor, limit, collector);

            if (solutionFound)
            {
                // The index of the solution from the collector
                const int  SOLUTION_INDEX = 0;
                Assignment solution       = collector.Solution(SOLUTION_INDEX);

                Console.WriteLine();
                uint machineIdx = 0;
                foreach (var seq in allSequences)
                {
                    machineIdx++;
                    var taskSeq = collector.ForwardSequence(SOLUTION_INDEX, seq);
                    Console.WriteLine($"{seq.Name()}:");
                    Console.WriteLine("  Tasks: " + string.Join(", ", taskSeq));

                    //foreach (var taskIndex in storedSequence)
                    //{
                    //    IntervalVar task = sequence.Interval(taskIndex);
                    //    long startMin = solution.StartMin(task);
                    //    long startMax = solution.StartMax(task);
                    //    if (startMin == startMax)
                    //    {
                    //        Console.WriteLine($"Task {task.Name()} starts at {startMin}.");
                    //    }
                    //    else
                    //    {
                    //        Console.WriteLine($"Task {task.Name()} starts between {startMin} and {startMax}.");
                    //    }
                    //}


                    Console.WriteLine();
                    Console.Write("  Starting times:");
                    foreach (var s in taskSeq)
                    {
                        Console.Write(" " + collector.Value(0, tasksByMachineId[machineIdx][s].StartExpr().Var()).ToString());
                    }
                    Console.WriteLine();
                }
                //var x = tasksByMachineId[1][0].StartExpr().Var();
                //var xValStr = collector.Value(0, x).ToString();
                Console.WriteLine("objective function value = " + solution.ObjectiveValue());  //TEMP
            }

            // Save profile in file
            //solver.ExportProfilingOverview("profile.txt");

            // Done
            solver.EndSearch();
        }