OR-Tools  9.6
cumulative.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <functional>
18 #include <vector>
19 
20 #include "absl/strings/str_join.h"
21 #include "ortools/base/logging.h"
24 #include "ortools/sat/integer.h"
26 #include "ortools/sat/intervals.h"
28 #include "ortools/sat/model.h"
31 #include "ortools/sat/sat_base.h"
32 #include "ortools/sat/sat_parameters.pb.h"
33 #include "ortools/sat/sat_solver.h"
34 #include "ortools/sat/timetable.h"
37 
38 namespace operations_research {
39 namespace sat {
40 
41 std::function<void(Model*)> Cumulative(
42  const std::vector<IntervalVariable>& vars,
43  const std::vector<AffineExpression>& demands, AffineExpression capacity,
45  return [=](Model* model) mutable {
46  if (vars.empty()) return;
47 
48  auto* intervals = model->GetOrCreate<IntervalsRepository>();
49  auto* encoder = model->GetOrCreate<IntegerEncoder>();
50  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
51  auto* watcher = model->GetOrCreate<GenericLiteralWatcher>();
52 
53  // Redundant constraints to ensure that the resource capacity is high enough
54  // for each task. Also ensure that no task consumes more resource than what
55  // is available. This is useful because the subsequent propagators do not
56  // filter the capacity variable very well.
57  for (int i = 0; i < demands.size(); ++i) {
58  if (intervals->MaxSize(vars[i]) == 0) continue;
59 
60  LinearConstraintBuilder builder(model, kMinIntegerValue, IntegerValue(0));
61  builder.AddTerm(demands[i], IntegerValue(1));
62  builder.AddTerm(capacity, IntegerValue(-1));
63  LinearConstraint ct = builder.Build();
64 
65  std::vector<Literal> enforcement_literals;
66  if (intervals->IsOptional(vars[i])) {
67  enforcement_literals.push_back(intervals->PresenceLiteral(vars[i]));
68  }
69 
70  // If the interval can be of size zero, it currently do not count towards
71  // the capacity. TODO(user): Change that since we have optional interval
72  // for this.
73  if (intervals->MinSize(vars[i]) == 0) {
74  enforcement_literals.push_back(encoder->GetOrCreateAssociatedLiteral(
75  intervals->Size(vars[i]).GreaterOrEqual(IntegerValue(1))));
76  }
77 
78  if (enforcement_literals.empty()) {
80  } else {
81  LoadConditionalLinearConstraint(enforcement_literals, ct, model);
82  }
83  }
84 
85  if (vars.size() == 1) return;
86 
87  const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
88 
89  // Detect a subset of intervals that needs to be in disjunction and add a
90  // Disjunctive() constraint over them.
91  if (parameters.use_disjunctive_constraint_in_cumulative()) {
92  // TODO(user): We need to exclude intervals that can be of size zero
93  // because the disjunctive do not "ignore" them like the cumulative
94  // does. That is, the interval [2,2) will be assumed to be in
95  // disjunction with [1, 3) for instance. We need to uniformize the
96  // handling of interval with size zero.
97  std::vector<IntervalVariable> in_disjunction;
98  IntegerValue min_of_demands = kMaxIntegerValue;
99  const IntegerValue capa_max = integer_trail->UpperBound(capacity);
100  for (int i = 0; i < vars.size(); ++i) {
101  const IntegerValue size_min = intervals->MinSize(vars[i]);
102  if (size_min == 0) continue;
103  const IntegerValue demand_min = integer_trail->LowerBound(demands[i]);
104  if (2 * demand_min > capa_max) {
105  in_disjunction.push_back(vars[i]);
106  min_of_demands = std::min(min_of_demands, demand_min);
107  }
108  }
109 
110  // Liftable? We might be able to add one more interval!
111  if (!in_disjunction.empty()) {
112  IntervalVariable lift_var;
113  IntegerValue lift_size(0);
114  for (int i = 0; i < vars.size(); ++i) {
115  const IntegerValue size_min = intervals->MinSize(vars[i]);
116  if (size_min == 0) continue;
117  const IntegerValue demand_min = integer_trail->LowerBound(demands[i]);
118  if (2 * demand_min > capa_max) continue;
119  if (min_of_demands + demand_min > capa_max && size_min > lift_size) {
120  lift_var = vars[i];
121  lift_size = size_min;
122  }
123  }
124  if (lift_size > 0) {
125  in_disjunction.push_back(lift_var);
126  }
127  }
128 
129  // Add a disjunctive constraint on the intervals in in_disjunction. Do not
130  // create the cumulative at all when all intervals must be in disjunction.
131  //
132  // TODO(user): Do proper experiments to see how beneficial this is, the
133  // disjunctive will propagate more but is also using slower algorithms.
134  // That said, this is more a question of optimizing the disjunctive
135  // propagation code.
136  //
137  // TODO(user): Another "known" idea is to detect pair of tasks that must
138  // be in disjunction and to create a Boolean to indicate which one is
139  // before the other. It shouldn't change the propagation, but may result
140  // in a faster one with smaller explanations, and the solver can also take
141  // decision on such Boolean.
142  //
143  // TODO(user): A better place for stuff like this could be in the
144  // presolver so that it is easier to disable and play with alternatives.
145  if (in_disjunction.size() > 1) model->Add(Disjunctive(in_disjunction));
146  if (in_disjunction.size() == vars.size()) return;
147  }
148 
149  if (helper == nullptr) {
150  helper = intervals->GetOrCreateHelper(vars);
151  }
152  SchedulingDemandHelper* demands_helper =
153  new SchedulingDemandHelper(demands, helper, model);
154  model->TakeOwnership(demands_helper);
155 
156  // For each variables that is after a subset of task ends (i.e. like a
157  // makespan objective), we detect it and add a special constraint to
158  // propagate it.
159  //
160  // TODO(user): Models that include the makespan as a special interval might
161  // be better, but then not everyone does that. In particular this code
162  // allows to have decent lower bound on the large cumulative minizinc
163  // instances.
164  //
165  // TODO(user): this require the precedence constraints to be already loaded,
166  // and there is no guarantee of that currently. Find a more robust way.
167  //
168  // TODO(user): There is a bit of code duplication with the disjunctive
169  // precedence propagator. Abstract more?
170  if (parameters.use_hard_precedences_in_cumulative()) {
171  // The CumulativeIsAfterSubsetConstraint() always reset the helper to the
172  // forward time direction, so it is important to also precompute the
173  // precedence relation using the same direction! This is needed in case
174  // the helper has already been used and set in the other direction.
175  if (!helper->SynchronizeAndSetTimeDirection(true)) {
176  model->GetOrCreate<SatSolver>()->NotifyThatModelIsUnsat();
177  return;
178  }
179 
180  std::vector<IntegerVariable> index_to_end_vars;
181  std::vector<int> index_to_task;
182  index_to_end_vars.clear();
183  for (int t = 0; t < helper->NumTasks(); ++t) {
184  const AffineExpression& end_exp = helper->Ends()[t];
185 
186  // TODO(user): Handle generic affine relation?
187  if (end_exp.var == kNoIntegerVariable || end_exp.coeff != 1) continue;
188  index_to_end_vars.push_back(end_exp.var);
189  index_to_task.push_back(t);
190  }
191 
192  // TODO(user): This can lead to many constraints. By analyzing a bit more
193  // the precedences, we could restrict that. In particular for cases were
194  // the cumulative is always (bunch of tasks B), T, (bunch of tasks A) and
195  // task T always in the middle, we never need to explicit list the
196  // precedence of a task in B with a task in A.
197  //
198  // TODO(user): If more than one variable are after the same set of
199  // intervals, we should regroup them in a single constraint rather than
200  // having two independent constraint doing the same propagation.
201  std::vector<PrecedencesPropagator::FullIntegerPrecedence>
202  full_precedences;
203  model->GetOrCreate<PrecedencesPropagator>()->ComputeFullPrecedences(
204  !parameters.exploit_all_precedences(), index_to_end_vars,
205  &full_precedences);
207  full_precedences) {
208  const int size = data.indices.size();
209  if (size <= 1) continue;
210 
211  const IntegerVariable var = data.var;
212  std::vector<int> subtasks;
213  std::vector<IntegerValue> offsets;
214  IntegerValue sum_of_demand_max(0);
215  for (int i = 0; i < size; ++i) {
216  const int t = index_to_task[data.indices[i]];
217  subtasks.push_back(t);
218  sum_of_demand_max += integer_trail->LevelZeroUpperBound(demands[t]);
219 
220  // We have var >= end_exp.var + offset, so
221  // var >= (end_exp.var + end_exp.cte) + (offset - end_exp.cte)
222  // var >= task end + new_offset.
223  const AffineExpression& end_exp = helper->Ends()[t];
224  offsets.push_back(data.offsets[i] - end_exp.constant);
225  }
226  if (sum_of_demand_max > integer_trail->LevelZeroLowerBound(capacity)) {
227  VLOG(2) << "Cumulative precedence constraint! var= " << var
228  << " #task: " << absl::StrJoin(subtasks, ",");
231  offsets, helper,
232  demands_helper, model);
233  constraint->RegisterWith(watcher);
234  model->TakeOwnership(constraint);
235  }
236  }
237  }
238 
239  // Propagator responsible for applying Timetabling filtering rule. It
240  // increases the minimum of the start variables, decrease the maximum of the
241  // end variables, and increase the minimum of the capacity variable.
242  TimeTablingPerTask* time_tabling =
243  new TimeTablingPerTask(capacity, helper, demands_helper, model);
244  time_tabling->RegisterWith(watcher);
245  model->TakeOwnership(time_tabling);
246 
247  // Propagator responsible for applying the Overload Checking filtering rule.
248  // It increases the minimum of the capacity variable.
249  if (parameters.use_overload_checker_in_cumulative()) {
250  AddCumulativeOverloadChecker(capacity, helper, demands_helper, model);
251  }
252 
253  // Propagator responsible for applying the Timetable Edge finding filtering
254  // rule. It increases the minimum of the start variables and decreases the
255  // maximum of the end variables,
256  if (parameters.use_timetable_edge_finding_in_cumulative()) {
257  TimeTableEdgeFinding* time_table_edge_finding =
258  new TimeTableEdgeFinding(capacity, helper, demands_helper, model);
259  time_table_edge_finding->RegisterWith(watcher);
260  model->TakeOwnership(time_table_edge_finding);
261  }
262  };
263 }
264 
265 std::function<void(Model*)> CumulativeTimeDecomposition(
266  const std::vector<IntervalVariable>& vars,
267  const std::vector<AffineExpression>& demands, AffineExpression capacity,
268  SchedulingConstraintHelper* helper) {
269  return [=](Model* model) {
270  if (vars.empty()) return;
271 
272  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
273  CHECK(integer_trail->IsFixed(capacity));
274  const Coefficient fixed_capacity(
275  integer_trail->UpperBound(capacity).value());
276 
277  const int num_tasks = vars.size();
278  SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
279  IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
280  IntervalsRepository* intervals = model->GetOrCreate<IntervalsRepository>();
281 
282  std::vector<IntegerVariable> start_vars;
283  std::vector<IntegerVariable> end_vars;
284  std::vector<IntegerValue> fixed_demands;
285 
286  for (int t = 0; t < num_tasks; ++t) {
287  start_vars.push_back(intervals->StartVar(vars[t]));
288  end_vars.push_back(intervals->EndVar(vars[t]));
289  CHECK(integer_trail->IsFixed(demands[t]));
290  fixed_demands.push_back(integer_trail->LowerBound(demands[t]));
291  }
292 
293  // Compute time range.
294  IntegerValue min_start = kMaxIntegerValue;
295  IntegerValue max_end = kMinIntegerValue;
296  for (int t = 0; t < num_tasks; ++t) {
297  min_start = std::min(min_start, integer_trail->LowerBound(start_vars[t]));
298  max_end = std::max(max_end, integer_trail->UpperBound(end_vars[t]));
299  }
300 
301  for (IntegerValue time = min_start; time < max_end; ++time) {
302  std::vector<LiteralWithCoeff> literals_with_coeff;
303  for (int t = 0; t < num_tasks; ++t) {
304  sat_solver->Propagate();
305  const IntegerValue start_min = integer_trail->LowerBound(start_vars[t]);
306  const IntegerValue end_max = integer_trail->UpperBound(end_vars[t]);
307  if (end_max <= time || time < start_min || fixed_demands[t] == 0) {
308  continue;
309  }
310 
311  // Task t consumes the resource at time if consume_condition is true.
312  std::vector<Literal> consume_condition;
313  const Literal consume = Literal(model->Add(NewBooleanVariable()), true);
314 
315  // Task t consumes the resource at time if it is present.
316  if (intervals->IsOptional(vars[t])) {
317  consume_condition.push_back(intervals->PresenceLiteral(vars[t]));
318  }
319 
320  // Task t overlaps time.
321  consume_condition.push_back(encoder->GetOrCreateAssociatedLiteral(
322  IntegerLiteral::LowerOrEqual(start_vars[t], IntegerValue(time))));
323  consume_condition.push_back(encoder->GetOrCreateAssociatedLiteral(
324  IntegerLiteral::GreaterOrEqual(end_vars[t],
325  IntegerValue(time + 1))));
326 
327  model->Add(ReifiedBoolAnd(consume_condition, consume));
328 
329  // this is needed because we currently can't create a boolean variable
330  // if the model is unsat.
331  if (sat_solver->ModelIsUnsat()) return;
332 
333  literals_with_coeff.push_back(
334  LiteralWithCoeff(consume, Coefficient(fixed_demands[t].value())));
335  }
336  // The profile cannot exceed the capacity at time.
337  sat_solver->AddLinearConstraint(false, Coefficient(0), true,
338  fixed_capacity, &literals_with_coeff);
339 
340  // Abort if UNSAT.
341  if (sat_solver->ModelIsUnsat()) return;
342  }
343  };
344 }
345 
346 std::function<void(Model*)> CumulativeUsingReservoir(
347  const std::vector<IntervalVariable>& vars,
348  const std::vector<AffineExpression>& demands, AffineExpression capacity,
349  SchedulingConstraintHelper* helper) {
350  return [=](Model* model) {
351  if (vars.empty()) return;
352 
353  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
354  auto* encoder = model->GetOrCreate<IntegerEncoder>();
355  auto* intervals = model->GetOrCreate<IntervalsRepository>();
356 
357  CHECK(integer_trail->IsFixed(capacity));
358  const IntegerValue fixed_capacity(
359  integer_trail->UpperBound(capacity).value());
360 
361  std::vector<AffineExpression> times;
362  std::vector<AffineExpression> deltas;
363  std::vector<Literal> presences;
364 
365  const int num_tasks = vars.size();
366  for (int t = 0; t < num_tasks; ++t) {
367  CHECK(integer_trail->IsFixed(demands[t]));
368  times.push_back(intervals->StartVar(vars[t]));
369  deltas.push_back(demands[t]);
370  times.push_back(intervals->EndVar(vars[t]));
371  deltas.push_back(demands[t].Negated());
372  if (intervals->IsOptional(vars[t])) {
373  presences.push_back(intervals->PresenceLiteral(vars[t]));
374  presences.push_back(intervals->PresenceLiteral(vars[t]));
375  } else {
376  presences.push_back(encoder->GetTrueLiteral());
377  presences.push_back(encoder->GetTrueLiteral());
378  }
379  }
380  AddReservoirConstraint(times, deltas, presences, 0, fixed_capacity.value(),
381  model);
382  };
383 }
384 
385 } // namespace sat
386 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1565
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1561
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1557
void AddTerm(IntegerVariable var, IntegerValue coeff)
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
bool AddLinearConstraint(bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound, Coefficient upper_bound, std::vector< LiteralWithCoeff > *cst)
Definition: sat_solver.cc:354
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
Definition: intervals.cc:330
const std::vector< AffineExpression > & Ends() const
Definition: intervals.h:374
void RegisterWith(GenericLiteralWatcher *watcher)
void RegisterWith(GenericLiteralWatcher *watcher)
Definition: timetable.cc:346
SatParameters parameters
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
std::tuple< int64_t, int64_t, const double > Coefficient
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
void AddCumulativeOverloadChecker(AffineExpression capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
std::function< void(Model *)> Disjunctive(const std::vector< IntervalVariable > &intervals)
Definition: disjunctive.cc:39
std::function< BooleanVariable(Model *)> NewBooleanVariable()
Definition: integer.h:1720
void LoadConditionalLinearConstraint(const absl::Span< const Literal > enforcement_literals, const LinearConstraint &cst, Model *model)
Definition: integer_expr.h:606
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
std::function< void(Model *)> Cumulative(const std::vector< IntervalVariable > &vars, const std::vector< AffineExpression > &demands, AffineExpression capacity, SchedulingConstraintHelper *helper)
Definition: cumulative.cc:41
void AddReservoirConstraint(std::vector< AffineExpression > times, std::vector< AffineExpression > deltas, std::vector< Literal > presences, int64_t min_level, int64_t max_level, Model *model)
Definition: timetable.cc:32
void LoadLinearConstraint(const ConstraintProto &ct, Model *m)
std::function< void(Model *)> ReifiedBoolAnd(const std::vector< Literal > &literals, Literal r)
Definition: sat_solver.h:1004
std::function< void(Model *)> CumulativeTimeDecomposition(const std::vector< IntervalVariable > &vars, const std::vector< AffineExpression > &demands, AffineExpression capacity, SchedulingConstraintHelper *helper)
Definition: cumulative.cc:265
std::function< void(Model *)> CumulativeUsingReservoir(const std::vector< IntervalVariable > &vars, const std::vector< AffineExpression > &demands, AffineExpression capacity, SchedulingConstraintHelper *helper)
Definition: cumulative.cc:346
Collection of objects used to extend the Constraint Solver library.
int64_t time
Definition: resource.cc:1694
int64_t capacity
Rev< int64_t > end_max
Rev< int64_t > start_min
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1505
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1499
#define VLOG(verboselevel)
Definition: vlog.h:39