OR-Tools  9.1
cumulative.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 #include "ortools/sat/cumulative.h"
15 
16 #include <algorithm>
17 #include <memory>
18 
19 #include "ortools/base/int_type.h"
20 #include "ortools/base/logging.h"
26 #include "ortools/sat/sat_base.h"
28 #include "ortools/sat/sat_solver.h"
29 #include "ortools/sat/timetable.h"
31 
32 namespace operations_research {
33 namespace sat {
34 
35 std::function<void(Model*)> Cumulative(
36  const std::vector<IntervalVariable>& vars,
37  const std::vector<AffineExpression>& demands, AffineExpression capacity,
39  return [=](Model* model) mutable {
40  if (vars.empty()) return;
41 
42  auto* intervals = model->GetOrCreate<IntervalsRepository>();
43  auto* encoder = model->GetOrCreate<IntegerEncoder>();
44  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
45  auto* watcher = model->GetOrCreate<GenericLiteralWatcher>();
46 
47  // Redundant constraints to ensure that the resource capacity is high enough
48  // for each task. Also ensure that no task consumes more resource than what
49  // is available. This is useful because the subsequent propagators do not
50  // filter the capacity variable very well.
51  for (int i = 0; i < demands.size(); ++i) {
52  if (intervals->MaxSize(vars[i]) == 0) continue;
53 
54  LinearConstraintBuilder builder(model, kMinIntegerValue, IntegerValue(0));
55  builder.AddTerm(demands[i], IntegerValue(1));
56  builder.AddTerm(capacity, IntegerValue(-1));
57  LinearConstraint ct = builder.Build();
58 
59  std::vector<Literal> enforcement_literals;
60  if (intervals->IsOptional(vars[i])) {
61  enforcement_literals.push_back(intervals->PresenceLiteral(vars[i]));
62  }
63 
64  // If the interval can be of size zero, it currently do not count towards
65  // the capacity. TODO(user): Change that since we have optional interval
66  // for this.
67  if (intervals->MinSize(vars[i]) == 0) {
68  enforcement_literals.push_back(encoder->GetOrCreateAssociatedLiteral(
69  intervals->Size(vars[i]).GreaterOrEqual(IntegerValue(1))));
70  }
71 
72  if (enforcement_literals.empty()) {
74  } else {
75  LoadConditionalLinearConstraint(enforcement_literals, ct, model);
76  }
77  }
78 
79  if (vars.size() == 1) return;
80 
81  const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
82 
83  // Detect a subset of intervals that needs to be in disjunction and add a
84  // Disjunctive() constraint over them.
86  // TODO(user): We need to exclude intervals that can be of size zero
87  // because the disjunctive do not "ignore" them like the cumulative
88  // does. That is, the interval [2,2) will be assumed to be in
89  // disjunction with [1, 3) for instance. We need to uniformize the
90  // handling of interval with size zero.
91  //
92  // TODO(user): improve the condition (see CL147454185).
93  std::vector<IntervalVariable> in_disjunction;
94  for (int i = 0; i < vars.size(); ++i) {
95  if (intervals->MinSize(vars[i]) > 0 &&
96  2 * integer_trail->LowerBound(demands[i]) >
97  integer_trail->UpperBound(capacity)) {
98  in_disjunction.push_back(vars[i]);
99  }
100  }
101 
102  // Add a disjunctive constraint on the intervals in in_disjunction. Do not
103  // create the cumulative at all when all intervals must be in disjunction.
104  //
105  // TODO(user): Do proper experiments to see how beneficial this is, the
106  // disjunctive will propagate more but is also using slower algorithms.
107  // That said, this is more a question of optimizing the disjunctive
108  // propagation code.
109  //
110  // TODO(user): Another "known" idea is to detect pair of tasks that must
111  // be in disjunction and to create a Boolean to indicate which one is
112  // before the other. It shouldn't change the propagation, but may result
113  // in a faster one with smaller explanations, and the solver can also take
114  // decision on such Boolean.
115  //
116  // TODO(user): A better place for stuff like this could be in the
117  // presolver so that it is easier to disable and play with alternatives.
118  if (in_disjunction.size() > 1) model->Add(Disjunctive(in_disjunction));
119  if (in_disjunction.size() == vars.size()) return;
120  }
121 
122  if (helper == nullptr) {
123  helper = new SchedulingConstraintHelper(vars, model);
124  model->TakeOwnership(helper);
125  }
126 
127  // For each variables that is after a subset of task ends (i.e. like a
128  // makespan objective), we detect it and add a special constraint to
129  // propagate it.
130  //
131  // TODO(user): Models that include the makespan as a special interval might
132  // be better, but then not everyone does that. In particular this code
133  // allows to have decent lower bound on the large cumulative minizinc
134  // instances.
135  //
136  // TODO(user): this require the precedence constraints to be already loaded,
137  // and there is no guarantee of that currently. Find a more robust way.
138  //
139  // TODO(user): There is a bit of code duplication with the disjunctive
140  // precedence propagator. Abstract more?
141  {
142  std::vector<IntegerVariable> index_to_end_vars;
143  std::vector<int> index_to_task;
144  std::vector<PrecedencesPropagator::IntegerPrecedences> before;
145  index_to_end_vars.clear();
146  for (int t = 0; t < helper->NumTasks(); ++t) {
147  const AffineExpression& end_exp = helper->Ends()[t];
148 
149  // TODO(user): Handle generic affine relation?
150  if (end_exp.var == kNoIntegerVariable || end_exp.coeff != 1) continue;
151  index_to_end_vars.push_back(end_exp.var);
152  index_to_task.push_back(t);
153  }
154  model->GetOrCreate<PrecedencesPropagator>()->ComputePrecedences(
155  index_to_end_vars, &before);
156  const int size = before.size();
157  for (int i = 0; i < size;) {
158  const IntegerVariable var = before[i].var;
160 
161  IntegerValue min_offset = kMaxIntegerValue;
162  std::vector<int> subtasks;
163  for (; i < size && before[i].var == var; ++i) {
164  const int t = index_to_task[before[i].index];
165  subtasks.push_back(t);
166 
167  // We have var >= end_exp.var + offset, so
168  // var >= (end_exp.var + end_exp.cte) + (offset - end_exp.cte)
169  // var >= task end + new_offset.
170  const AffineExpression& end_exp = helper->Ends()[t];
171  min_offset =
172  std::min(min_offset, before[i].offset - end_exp.constant);
173  }
174 
175  if (subtasks.size() > 1) {
178  demands, subtasks,
179  integer_trail, helper);
180  constraint->RegisterWith(watcher);
181  model->TakeOwnership(constraint);
182  }
183  }
184  }
185 
186  // Propagator responsible for applying Timetabling filtering rule. It
187  // increases the minimum of the start variables, decrease the maximum of the
188  // end variables, and increase the minimum of the capacity variable.
189  TimeTablingPerTask* time_tabling =
190  new TimeTablingPerTask(demands, capacity, integer_trail, helper);
191  time_tabling->RegisterWith(watcher);
192  model->TakeOwnership(time_tabling);
193 
194  // Propagator responsible for applying the Overload Checking filtering rule.
195  // It increases the minimum of the capacity variable.
197  AddCumulativeOverloadChecker(demands, capacity, helper, model);
198  }
199 
200  // Propagator responsible for applying the Timetable Edge finding filtering
201  // rule. It increases the minimum of the start variables and decreases the
202  // maximum of the end variables,
204  TimeTableEdgeFinding* time_table_edge_finding =
205  new TimeTableEdgeFinding(demands, capacity, helper, integer_trail);
206  time_table_edge_finding->RegisterWith(watcher);
207  model->TakeOwnership(time_table_edge_finding);
208  }
209  };
210 }
211 
212 std::function<void(Model*)> CumulativeTimeDecomposition(
213  const std::vector<IntervalVariable>& vars,
214  const std::vector<AffineExpression>& demands, AffineExpression capacity,
215  SchedulingConstraintHelper* helper) {
216  return [=](Model* model) {
217  if (vars.empty()) return;
218 
219  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
220  CHECK(integer_trail->IsFixed(capacity));
221  const Coefficient fixed_capacity(
222  integer_trail->UpperBound(capacity).value());
223 
224  const int num_tasks = vars.size();
225  SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
226  IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
227  IntervalsRepository* intervals = model->GetOrCreate<IntervalsRepository>();
228 
229  std::vector<IntegerVariable> start_vars;
230  std::vector<IntegerVariable> end_vars;
231  std::vector<IntegerValue> fixed_demands;
232 
233  for (int t = 0; t < num_tasks; ++t) {
234  start_vars.push_back(intervals->StartVar(vars[t]));
235  end_vars.push_back(intervals->EndVar(vars[t]));
236  CHECK(integer_trail->IsFixed(demands[t]));
237  fixed_demands.push_back(integer_trail->LowerBound(demands[t]));
238  }
239 
240  // Compute time range.
241  IntegerValue min_start = kMaxIntegerValue;
242  IntegerValue max_end = kMinIntegerValue;
243  for (int t = 0; t < num_tasks; ++t) {
244  min_start = std::min(min_start, integer_trail->LowerBound(start_vars[t]));
245  max_end = std::max(max_end, integer_trail->UpperBound(end_vars[t]));
246  }
247 
248  for (IntegerValue time = min_start; time < max_end; ++time) {
249  std::vector<LiteralWithCoeff> literals_with_coeff;
250  for (int t = 0; t < num_tasks; ++t) {
251  sat_solver->Propagate();
252  const IntegerValue start_min = integer_trail->LowerBound(start_vars[t]);
253  const IntegerValue end_max = integer_trail->UpperBound(end_vars[t]);
254  if (end_max <= time || time < start_min || fixed_demands[t] == 0) {
255  continue;
256  }
257 
258  // Task t consumes the resource at time if consume_condition is true.
259  std::vector<Literal> consume_condition;
260  const Literal consume = Literal(model->Add(NewBooleanVariable()), true);
261 
262  // Task t consumes the resource at time if it is present.
263  if (intervals->IsOptional(vars[t])) {
264  consume_condition.push_back(intervals->PresenceLiteral(vars[t]));
265  }
266 
267  // Task t overlaps time.
268  consume_condition.push_back(encoder->GetOrCreateAssociatedLiteral(
269  IntegerLiteral::LowerOrEqual(start_vars[t], IntegerValue(time))));
270  consume_condition.push_back(encoder->GetOrCreateAssociatedLiteral(
271  IntegerLiteral::GreaterOrEqual(end_vars[t],
272  IntegerValue(time + 1))));
273 
274  model->Add(ReifiedBoolAnd(consume_condition, consume));
275 
276  // TODO(user): this is needed because we currently can't create a
277  // boolean variable if the model is unsat.
278  if (sat_solver->IsModelUnsat()) return;
279 
280  literals_with_coeff.push_back(
281  LiteralWithCoeff(consume, Coefficient(fixed_demands[t].value())));
282  }
283  // The profile cannot exceed the capacity at time.
284  sat_solver->AddLinearConstraint(false, Coefficient(0), true,
285  fixed_capacity, &literals_with_coeff);
286 
287  // Abort if UNSAT.
288  if (sat_solver->IsModelUnsat()) return;
289  }
290  };
291 }
292 
293 std::function<void(Model*)> CumulativeUsingReservoir(
294  const std::vector<IntervalVariable>& vars,
295  const std::vector<AffineExpression>& demands, AffineExpression capacity,
296  SchedulingConstraintHelper* helper) {
297  return [=](Model* model) {
298  if (vars.empty()) return;
299 
300  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
301  auto* encoder = model->GetOrCreate<IntegerEncoder>();
302  auto* intervals = model->GetOrCreate<IntervalsRepository>();
303 
304  CHECK(integer_trail->IsFixed(capacity));
305  const IntegerValue fixed_capacity(
306  integer_trail->UpperBound(capacity).value());
307 
308  std::vector<AffineExpression> times;
309  std::vector<IntegerValue> deltas;
310  std::vector<Literal> presences;
311 
312  const int num_tasks = vars.size();
313  for (int t = 0; t < num_tasks; ++t) {
314  CHECK(integer_trail->IsFixed(demands[t]));
315  times.push_back(intervals->StartVar(vars[t]));
316  deltas.push_back(integer_trail->LowerBound(demands[t]));
317  times.push_back(intervals->EndVar(vars[t]));
318  deltas.push_back(-integer_trail->LowerBound(demands[t]));
319  if (intervals->IsOptional(vars[t])) {
320  presences.push_back(intervals->PresenceLiteral(vars[t]));
321  presences.push_back(intervals->PresenceLiteral(vars[t]));
322  } else {
323  presences.push_back(encoder->GetTrueLiteral());
324  presences.push_back(encoder->GetTrueLiteral());
325  }
326  }
327  AddReservoirConstraint(times, deltas, presences, 0, fixed_capacity.value(),
328  model);
329  };
330 }
331 
332 } // namespace sat
333 } // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:491
std::function< BooleanVariable(Model *)> NewBooleanVariable()
Definition: integer.h:1469
int64_t min
Definition: alldiff_cst.cc:139
void AddCumulativeOverloadChecker(const std::vector< AffineExpression > &demands, AffineExpression capacity, SchedulingConstraintHelper *helper, Model *model)
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1315
std::function< void(Model *)> ReifiedBoolAnd(const std::vector< Literal > &literals, Literal r)
Definition: sat_solver.h:970
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1345
void RegisterWith(GenericLiteralWatcher *watcher)
Definition: timetable.cc:314
Rev< int64_t > start_min
GRBmodel * model
void AddTerm(IntegerVariable var, IntegerValue coeff)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
std::function< void(Model *)> CumulativeUsingReservoir(const std::vector< IntervalVariable > &vars, const std::vector< AffineExpression > &demands, AffineExpression capacity, SchedulingConstraintHelper *helper=nullptr)
Definition: cumulative.h:61
int64_t max
Definition: alldiff_cst.cc:140
Rev< int64_t > end_max
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:887
std::function< void(Model *)> Disjunctive(const std::vector< IntervalVariable > &vars)
Definition: disjunctive.h:39
int64_t capacity
bool AddLinearConstraint(bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound, Coefficient upper_bound, std::vector< LiteralWithCoeff > *cst)
Definition: sat_solver.cc:300
const std::vector< AffineExpression > & Ends() const
Definition: intervals.h:337
std::tuple< int64_t, int64_t, const double > Coefficient
std::function< void(Model *)> CumulativeTimeDecomposition(const std::vector< IntervalVariable > &vars, const std::vector< AffineExpression > &demands, AffineExpression capacity, SchedulingConstraintHelper *helper=nullptr)
Definition: cumulative.h:55
void AddReservoirConstraint(std::vector< AffineExpression > times, std::vector< IntegerValue > deltas, std::vector< Literal > presences, int64_t min_level, int64_t max_level, Model *model)
Definition: timetable.cc:28
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1349
void RegisterWith(GenericLiteralWatcher *watcher)
Collection of objects used to extend the Constraint Solver library.
const IntegerVariable kNoIntegerVariable(-1)
int64_t time
Definition: resource.cc:1691
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1309
SatParameters parameters
IntVar * var
Definition: expr_array.cc:1874
void LoadLinearConstraint(const ConstraintProto &ct, Model *m)
void LoadConditionalLinearConstraint(const absl::Span< const Literal > enforcement_literals, const LinearConstraint &cst, Model *model)
Definition: integer_expr.h:575
int64_t value
std::function< void(Model *)> Cumulative(const std::vector< IntervalVariable > &vars, const std::vector< AffineExpression > &demands, AffineExpression capacity, SchedulingConstraintHelper *helper=nullptr)
Definition: cumulative.h:45
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1353
const Constraint * ct