diff --git a/ortools/sat/doc/scheduling.md b/ortools/sat/doc/scheduling.md index 481f92f68c..1a5f9d2415 100644 --- a/ortools/sat/doc/scheduling.md +++ b/ortools/sat/doc/scheduling.md @@ -253,6 +253,264 @@ public class CodeSamplesSat ## NoOverlap constraint A no overlap constraint simply states that all intervals are disjoint. +It is build with a list of interval variables. Fixed intervals are useful to +exclude part of the timeline. + +### Python code + +```python +"""Code sample to demonstrates how to build a no overlap constraint.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from ortools.sat.python import cp_model + + +def NoOverlapSample(): + """No overlap sample with fixed activities.""" + model = cp_model.CpModel() + horizon = 21 # 3 weeks. + + # Task 0, duration 2. + start_0 = model.NewIntVar(0, horizon, 'start_0') + duration_0 = 2 # Python cp/sat code accept integer variables or constants. + end_0 = model.NewIntVar(0, horizon, 'end_0') + task_0 = model.NewIntervalVar(start_0, duration_0, end_0, 'task_0') + # Task 1, duration 4. + start_1 = model.NewIntVar(0, horizon, 'start_1') + duration_1 = 4 # Python cp/sat code accept integer variables or constants. + end_1 = model.NewIntVar(0, horizon, 'end_1') + task_1 = model.NewIntervalVar(start_1, duration_1, end_1, 'task_1') + + # Task 2, duration 3. + start_2 = model.NewIntVar(0, horizon, 'start_2') + duration_2 = 3 # Python cp/sat code accept integer variables or constants. + end_2 = model.NewIntVar(0, horizon, 'end_2') + task_2 = model.NewIntervalVar(start_2, duration_2, end_2, 'task_2') + + # Week ends. + weekend_0 = model.NewIntervalVar(5, 2, 7, 'weekend_0') + weekend_1 = model.NewIntervalVar(12, 2, 14, 'weekend_1') + weekend_2 = model.NewIntervalVar(19, 2, 21, 'weekend_2') + + # No Overlap constraint. + model.AddNoOverlap([task_0, task_1, task_2, weekend_0, weekend_1, weekend_2]) + + # Makespan objective. + obj = model.NewIntVar(0, horizon, 'makespan') + model.AddMaxEquality(obj, [end_0, end_1, end_2]) + model.Minimize(obj) + + # Solve model. + solver = cp_model.CpSolver() + status = solver.Solve(model) + + if status == cp_model.OPTIMAL: + # Print out makespan and the starts of all tasks. + print('Optimal Schedule Length: %i' % solver.ObjectiveValue()) + print('Task 0 starts at %i' % solver.Value(start_0)) + print('Task 1 starts at %i' % solver.Value(start_1)) + print('Task 2 starts at %i' % solver.Value(start_2)) + + +NoOverlapSample() +``` + +### C++ code + +```cpp +#include "ortools/sat/cp_model.pb.h" +#include "ortools/sat/cp_model_solver.h" +#include "ortools/sat/cp_model_utils.h" +#include "ortools/sat/model.h" + +namespace operations_research { +namespace sat { + +void NoOverlapSample() { + CpModelProto cp_model; + const int kHorizon = 21; // 3 weeks. + + auto new_variable = [&cp_model](int64 lb, int64 ub) { + CHECK_LE(lb, ub); + const int index = cp_model.variables_size(); + IntegerVariableProto* const var = cp_model.add_variables(); + var->add_domain(lb); + var->add_domain(ub); + return index; + }; + + auto new_constant = [&new_variable](int64 v) { return new_variable(v, v); }; + + auto new_interval = [&cp_model](int start, int duration, int end) { + const int index = cp_model.constraints_size(); + IntervalConstraintProto* const interval = + cp_model.add_constraints()->mutable_interval(); + interval->set_start(start); + interval->set_size(duration); + interval->set_end(end); + return index; + }; + + auto new_fixed_interval = [&cp_model, &new_constant](int64 start, + int64 duration) { + const int index = cp_model.constraints_size(); + IntervalConstraintProto* const interval = + cp_model.add_constraints()->mutable_interval(); + interval->set_start(new_constant(start)); + interval->set_size(new_constant(duration)); + interval->set_end(new_constant(start + duration)); + return index; + }; + + auto add_no_overlap = [&cp_model](const std::vector& intervals) { + NoOverlapConstraintProto* const no_overlap = + cp_model.add_constraints()->mutable_no_overlap(); + for (const int i : intervals) { + no_overlap->add_intervals(i); + } + }; + + auto add_precedence = [&cp_model](int before, int after) { + LinearConstraintProto* const lin = + cp_model.add_constraints()->mutable_linear(); + lin->add_vars(after); + lin->add_coeffs(1L); + lin->add_vars(before); + lin->add_coeffs(-1L); + lin->add_domain(0); + lin->add_domain(kint64max); + }; + + // Task 0, duration 2. + const int start_0 = new_variable(0, kHorizon); + const int duration_0 = new_constant(2); + const int end_0 = new_variable(0, kHorizon); + const int task_0 = new_interval(start_0, duration_0, end_0); + + // Task 1, duration 4. + const int start_1 = new_variable(0, kHorizon); + const int duration_1 = new_constant(4); + const int end_1 = new_variable(0, kHorizon); + const int task_1 = new_interval(start_1, duration_1, end_1); + + // Task 2, duration 3. + const int start_2 = new_variable(0, kHorizon); + const int duration_2 = new_constant(3); + const int end_2 = new_variable(0, kHorizon); + const int task_2 = new_interval(start_2, duration_2, end_2); + + // Week ends. + const int weekend_0 = new_fixed_interval(5, 2); + const int weekend_1 = new_fixed_interval(12, 2); + const int weekend_2 = new_fixed_interval(19, 2); + + // No Overlap constraint. + add_no_overlap({task_0, task_1, task_2, weekend_0, weekend_1, weekend_2}); + + // Makespan. + const int makespan = new_variable(0, kHorizon); + add_precedence(end_0, makespan); + add_precedence(end_1, makespan); + add_precedence(end_2, makespan); + CpObjectiveProto* const obj = cp_model.mutable_objective(); + obj->add_vars(makespan); + obj->add_coeffs(1); // Minimization. + + // Solving part. + Model model; + LOG(INFO) << CpModelStats(cp_model); + const CpSolverResponse response = SolveCpModel(cp_model, &model); + LOG(INFO) << CpSolverResponseStats(response); + + if (response.status() == CpSolverStatus::OPTIMAL) { + LOG(INFO) << "Optimal Schedule Length: " << response.objective_value(); + LOG(INFO) << "Task 0 starts at " << response.solution(start_0); + LOG(INFO) << "Task 1 starts at " << response.solution(start_1); + LOG(INFO) << "Task 2 starts at " << response.solution(start_2); + } +} + + +} // namespace sat +} // namespace operations_research + +int main() { + operations_research::sat::NoOverlapSample(); + + return EXIT_SUCCESS; +} +``` + +### C\# code + +```cs +using System; +using Google.OrTools.Sat; + +public class CodeSamplesSat +{ + static void NoOverlapSample() + { + CpModel model = new CpModel(); + int horizon = 21; + + // Task 0, duration 2. + IntVar start_0 = model.NewIntVar(0, horizon, "start_0"); + int duration_0 = 2; + IntVar end_0 = model.NewIntVar(0, horizon, "end_0"); + IntervalVar task_0 = + model.NewIntervalVar(start_0, duration_0, end_0, "task_0"); + + // Task 1, duration 4. + IntVar start_1 = model.NewIntVar(0, horizon, "start_1"); + int duration_1 = 4; + IntVar end_1 = model.NewIntVar(0, horizon, "end_1"); + IntervalVar task_1 = + model.NewIntervalVar(start_1, duration_1, end_1, "task_1"); + + // Task 2, duration 3. + IntVar start_2 = model.NewIntVar(0, horizon, "start_2"); + int duration_2 = 3; + IntVar end_2 = model.NewIntVar(0, horizon, "end_2"); + IntervalVar task_2 = + model.NewIntervalVar(start_2, duration_2, end_2, "task_2"); + + // Week ends. + IntervalVar weekend_0 = model.NewIntervalVar(5, 2, 7, "weekend_0"); + IntervalVar weekend_1 = model.NewIntervalVar(12, 2, 14, "weekend_1"); + IntervalVar weekend_2 = model.NewIntervalVar(19, 2, 21, "weekend_2"); + + // No Overlap constraint. + model.AddNoOverlap(new IntervalVar[] {task_0, task_1, task_2, weekend_0, + weekend_1, weekend_2}); + + // Makespan objective. + IntVar obj = model.NewIntVar(0, horizon, "makespan"); + model.AddMaxEquality(obj, new IntVar[] {end_0, end_1, end_2}); + model.Minimize(obj); + + // Creates a solver and solves the model. + CpSolver solver = new CpSolver(); + CpSolverStatus status = solver.Solve(model); + + if (status == CpSolverStatus.Optimal) + { + Console.WriteLine("Optimal Schedule Length: " + solver.ObjectiveValue); + Console.WriteLine("Task 0 starts at " + solver.Value(start_0)); + Console.WriteLine("Task 1 starts at " + solver.Value(start_1)); + Console.WriteLine("Task 2 starts at " + solver.Value(start_2)); + } + } + + static void Main() + { + NoOverlapSample(); + } +} +``` ## Cumulative constraint @@ -262,6 +520,8 @@ at that time point is less than a given capacity. ## Alternative resources for one interval +## Ranking or tasks in a disjunctive resource + ## Transitions in a disjunctive resource ## Precedences between intervals diff --git a/ortools/sat/samples/no_overlap_sample.cc b/ortools/sat/samples/no_overlap_sample.cc new file mode 100644 index 0000000000..3dc68b2680 --- /dev/null +++ b/ortools/sat/samples/no_overlap_sample.cc @@ -0,0 +1,134 @@ +// Copyright 2010-2017 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ortools/sat/cp_model.pb.h" +#include "ortools/sat/cp_model_solver.h" +#include "ortools/sat/cp_model_utils.h" +#include "ortools/sat/model.h" + +namespace operations_research { +namespace sat { + +void NoOverlapSample() { + CpModelProto cp_model; + const int kHorizon = 21; // 3 weeks. + + auto new_variable = [&cp_model](int64 lb, int64 ub) { + CHECK_LE(lb, ub); + const int index = cp_model.variables_size(); + IntegerVariableProto* const var = cp_model.add_variables(); + var->add_domain(lb); + var->add_domain(ub); + return index; + }; + + auto new_constant = [&new_variable](int64 v) { return new_variable(v, v); }; + + auto new_interval = [&cp_model](int start, int duration, int end) { + const int index = cp_model.constraints_size(); + IntervalConstraintProto* const interval = + cp_model.add_constraints()->mutable_interval(); + interval->set_start(start); + interval->set_size(duration); + interval->set_end(end); + return index; + }; + + auto new_fixed_interval = [&cp_model, &new_constant](int64 start, + int64 duration) { + const int index = cp_model.constraints_size(); + IntervalConstraintProto* const interval = + cp_model.add_constraints()->mutable_interval(); + interval->set_start(new_constant(start)); + interval->set_size(new_constant(duration)); + interval->set_end(new_constant(start + duration)); + return index; + }; + + auto add_no_overlap = [&cp_model](const std::vector& intervals) { + NoOverlapConstraintProto* const no_overlap = + cp_model.add_constraints()->mutable_no_overlap(); + for (const int i : intervals) { + no_overlap->add_intervals(i); + } + }; + + auto add_precedence = [&cp_model](int before, int after) { + LinearConstraintProto* const lin = + cp_model.add_constraints()->mutable_linear(); + lin->add_vars(after); + lin->add_coeffs(1L); + lin->add_vars(before); + lin->add_coeffs(-1L); + lin->add_domain(0); + lin->add_domain(kint64max); + }; + + // Task 0, duration 2. + const int start_0 = new_variable(0, kHorizon); + const int duration_0 = new_constant(2); + const int end_0 = new_variable(0, kHorizon); + const int task_0 = new_interval(start_0, duration_0, end_0); + + // Task 1, duration 4. + const int start_1 = new_variable(0, kHorizon); + const int duration_1 = new_constant(4); + const int end_1 = new_variable(0, kHorizon); + const int task_1 = new_interval(start_1, duration_1, end_1); + + // Task 2, duration 3. + const int start_2 = new_variable(0, kHorizon); + const int duration_2 = new_constant(3); + const int end_2 = new_variable(0, kHorizon); + const int task_2 = new_interval(start_2, duration_2, end_2); + + // Week ends. + const int weekend_0 = new_fixed_interval(5, 2); + const int weekend_1 = new_fixed_interval(12, 2); + const int weekend_2 = new_fixed_interval(19, 2); + + // No Overlap constraint. + add_no_overlap({task_0, task_1, task_2, weekend_0, weekend_1, weekend_2}); + + // Makespan. + const int makespan = new_variable(0, kHorizon); + add_precedence(end_0, makespan); + add_precedence(end_1, makespan); + add_precedence(end_2, makespan); + CpObjectiveProto* const obj = cp_model.mutable_objective(); + obj->add_vars(makespan); + obj->add_coeffs(1); // Minimization. + + // Solving part. + Model model; + LOG(INFO) << CpModelStats(cp_model); + const CpSolverResponse response = SolveCpModel(cp_model, &model); + LOG(INFO) << CpSolverResponseStats(response); + + if (response.status() == CpSolverStatus::OPTIMAL) { + LOG(INFO) << "Optimal Schedule Length: " << response.objective_value(); + LOG(INFO) << "Task 0 starts at " << response.solution(start_0); + LOG(INFO) << "Task 1 starts at " << response.solution(start_1); + LOG(INFO) << "Task 2 starts at " << response.solution(start_2); + } +} + + +} // namespace sat +} // namespace operations_research + +int main() { + operations_research::sat::NoOverlapSample(); + + return EXIT_SUCCESS; +} diff --git a/ortools/sat/samples/no_overlap_sample.cs b/ortools/sat/samples/no_overlap_sample.cs new file mode 100644 index 0000000000..9d51bbc913 --- /dev/null +++ b/ortools/sat/samples/no_overlap_sample.cs @@ -0,0 +1,76 @@ +// Copyright 2010-2017 Google +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Google.OrTools.Sat; + +public class CodeSamplesSat +{ + static void NoOverlapSample() + { + CpModel model = new CpModel(); + int horizon = 21; + + // Task 0, duration 2. + IntVar start_0 = model.NewIntVar(0, horizon, "start_0"); + int duration_0 = 2; + IntVar end_0 = model.NewIntVar(0, horizon, "end_0"); + IntervalVar task_0 = + model.NewIntervalVar(start_0, duration_0, end_0, "task_0"); + + // Task 1, duration 4. + IntVar start_1 = model.NewIntVar(0, horizon, "start_1"); + int duration_1 = 4; + IntVar end_1 = model.NewIntVar(0, horizon, "end_1"); + IntervalVar task_1 = + model.NewIntervalVar(start_1, duration_1, end_1, "task_1"); + + // Task 2, duration 3. + IntVar start_2 = model.NewIntVar(0, horizon, "start_2"); + int duration_2 = 3; + IntVar end_2 = model.NewIntVar(0, horizon, "end_2"); + IntervalVar task_2 = + model.NewIntervalVar(start_2, duration_2, end_2, "task_2"); + + // Week ends. + IntervalVar weekend_0 = model.NewIntervalVar(5, 2, 7, "weekend_0"); + IntervalVar weekend_1 = model.NewIntervalVar(12, 2, 14, "weekend_1"); + IntervalVar weekend_2 = model.NewIntervalVar(19, 2, 21, "weekend_2"); + + // No Overlap constraint. + model.AddNoOverlap(new IntervalVar[] {task_0, task_1, task_2, weekend_0, + weekend_1, weekend_2}); + + // Makespan objective. + IntVar obj = model.NewIntVar(0, horizon, "makespan"); + model.AddMaxEquality(obj, new IntVar[] {end_0, end_1, end_2}); + model.Minimize(obj); + + // Creates a solver and solves the model. + CpSolver solver = new CpSolver(); + CpSolverStatus status = solver.Solve(model); + + if (status == CpSolverStatus.Optimal) + { + Console.WriteLine("Optimal Schedule Length: " + solver.ObjectiveValue); + Console.WriteLine("Task 0 starts at " + solver.Value(start_0)); + Console.WriteLine("Task 1 starts at " + solver.Value(start_1)); + Console.WriteLine("Task 2 starts at " + solver.Value(start_2)); + } + } + + static void Main() + { + NoOverlapSample(); + } +} diff --git a/ortools/sat/samples/no_overlap_sample.py b/ortools/sat/samples/no_overlap_sample.py new file mode 100644 index 0000000000..fd395094cd --- /dev/null +++ b/ortools/sat/samples/no_overlap_sample.py @@ -0,0 +1,69 @@ +# Copyright 2010-2017 Google +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Code sample to demonstrates how to build a no overlap constraint.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from ortools.sat.python import cp_model + + +def NoOverlapSample(): + """No overlap sample with fixed activities.""" + model = cp_model.CpModel() + horizon = 21 # 3 weeks. + + # Task 0, duration 2. + start_0 = model.NewIntVar(0, horizon, 'start_0') + duration_0 = 2 # Python cp/sat code accept integer variables or constants. + end_0 = model.NewIntVar(0, horizon, 'end_0') + task_0 = model.NewIntervalVar(start_0, duration_0, end_0, 'task_0') + # Task 1, duration 4. + start_1 = model.NewIntVar(0, horizon, 'start_1') + duration_1 = 4 # Python cp/sat code accept integer variables or constants. + end_1 = model.NewIntVar(0, horizon, 'end_1') + task_1 = model.NewIntervalVar(start_1, duration_1, end_1, 'task_1') + + # Task 2, duration 3. + start_2 = model.NewIntVar(0, horizon, 'start_2') + duration_2 = 3 # Python cp/sat code accept integer variables or constants. + end_2 = model.NewIntVar(0, horizon, 'end_2') + task_2 = model.NewIntervalVar(start_2, duration_2, end_2, 'task_2') + + # Week ends. + weekend_0 = model.NewIntervalVar(5, 2, 7, 'weekend_0') + weekend_1 = model.NewIntervalVar(12, 2, 14, 'weekend_1') + weekend_2 = model.NewIntervalVar(19, 2, 21, 'weekend_2') + + # No Overlap constraint. + model.AddNoOverlap([task_0, task_1, task_2, weekend_0, weekend_1, weekend_2]) + + # Makespan objective. + obj = model.NewIntVar(0, horizon, 'makespan') + model.AddMaxEquality(obj, [end_0, end_1, end_2]) + model.Minimize(obj) + + # Solve model. + solver = cp_model.CpSolver() + status = solver.Solve(model) + + if status == cp_model.OPTIMAL: + # Print out makespan and the starts of all tasks. + print('Optimal Schedule Length: %i' % solver.ObjectiveValue()) + print('Task 0 starts at %i' % solver.Value(start_0)) + print('Task 1 starts at %i' % solver.Value(start_1)) + print('Task 2 starts at %i' % solver.Value(start_2)) + + +NoOverlapSample()