OR-Tools  9.6
sat/diffn.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/diffn.h"
15 
16 #include <algorithm>
17 #include <cstdint>
18 #include <limits>
19 #include <utility>
20 #include <vector>
21 
22 #include "absl/container/flat_hash_set.h"
23 #include "absl/types/span.h"
24 #include "ortools/base/logging.h"
26 #include "ortools/sat/diffn_util.h"
28 #include "ortools/sat/integer.h"
30 #include "ortools/sat/intervals.h"
32 #include "ortools/sat/model.h"
33 #include "ortools/sat/sat_base.h"
34 #include "ortools/sat/sat_parameters.pb.h"
35 #include "ortools/sat/timetable.h"
36 #include "ortools/sat/util.h"
39 
40 namespace operations_research {
41 namespace sat {
42 
43 namespace {
44 
45 // TODO(user): Use the faster variable only version if all expressions reduce
46 // to a single variable?
47 void AddIsEqualToMinOf(IntegerVariable min_var,
48  const std::vector<AffineExpression>& exprs,
49  Model* model) {
50  std::vector<LinearExpression> converted;
51  for (const AffineExpression& affine : exprs) {
52  LinearExpression e;
53  e.offset = affine.constant;
54  if (affine.var != kNoIntegerVariable) {
55  e.vars.push_back(affine.var);
56  e.coeffs.push_back(affine.coeff);
57  }
58  converted.push_back(e);
59  }
60  LinearExpression target;
61  target.vars.push_back(min_var);
62  target.coeffs.push_back(IntegerValue(1));
63  model->Add(IsEqualToMinOf(target, converted));
64 }
65 
66 void AddIsEqualToMaxOf(IntegerVariable max_var,
67  const std::vector<AffineExpression>& exprs,
68  Model* model) {
69  std::vector<LinearExpression> converted;
70  for (const AffineExpression& affine : exprs) {
71  LinearExpression e;
72  e.offset = affine.constant;
73  if (affine.var != kNoIntegerVariable) {
74  e.vars.push_back(affine.var);
75  e.coeffs.push_back(affine.coeff);
76  }
77  converted.push_back(NegationOf(e));
78  }
79  LinearExpression target;
80  target.vars.push_back(NegationOf(max_var));
81  target.coeffs.push_back(IntegerValue(1));
82  model->Add(IsEqualToMinOf(target, converted));
83 }
84 
85 } // namespace
86 
89  Model* model) {
90  int64_t min_starts = std::numeric_limits<int64_t>::max();
91  int64_t max_ends = std::numeric_limits<int64_t>::min();
92  std::vector<AffineExpression> sizes;
93  for (int box = 0; box < y->NumTasks(); ++box) {
94  min_starts = std::min(min_starts, y->StartMin(box).value());
95  max_ends = std::max(max_ends, y->EndMax(box).value());
96  sizes.push_back(y->Sizes()[box]);
97  }
98 
99  const IntegerVariable min_start_var =
100  model->Add(NewIntegerVariable(min_starts, max_ends));
101  AddIsEqualToMinOf(min_start_var, y->Starts(), model);
102 
103  const IntegerVariable max_end_var =
104  model->Add(NewIntegerVariable(min_starts, max_ends));
105  AddIsEqualToMaxOf(max_end_var, y->Ends(), model);
106 
107  // (max_end - min_start) >= capacity.
109  model->Add(NewIntegerVariable(0, CapSub(max_ends, min_starts))));
110  const std::vector<int64_t> coeffs = {-capacity.coeff.value(), -1, 1};
111  model->Add(
112  WeightedSumGreaterOrEqual({capacity.var, min_start_var, max_end_var},
113  coeffs, capacity.constant.value()));
114 
115  auto* watcher = model->GetOrCreate<GenericLiteralWatcher>();
116 
117  const SatParameters* params = model->GetOrCreate<SatParameters>();
118  const bool add_timetabling_relaxation =
119  params->use_timetabling_in_no_overlap_2d();
120  bool add_energetic_relaxation =
121  params->use_energetic_reasoning_in_no_overlap_2d();
122 
123  // Needed if we use one of the relaxation below.
124  SchedulingDemandHelper* demands;
125  if (add_timetabling_relaxation || add_energetic_relaxation) {
126  demands = model->TakeOwnership(new SchedulingDemandHelper(sizes, x, model));
127  }
128 
129  // Propagator responsible for applying Timetabling filtering rule. It
130  // increases the minimum of the start variables, decrease the maximum of the
131  // end variables, and increase the minimum of the capacity variable.
132  if (add_timetabling_relaxation) {
133  DCHECK(demands != nullptr);
134  TimeTablingPerTask* time_tabling =
135  new TimeTablingPerTask(capacity, x, demands, model);
136  time_tabling->RegisterWith(watcher);
137  model->TakeOwnership(time_tabling);
138  }
139 
140  // Propagator responsible for applying the Overload Checking filtering rule.
141  // It increases the minimum of the capacity variable.
142  if (add_energetic_relaxation) {
143  DCHECK(demands != nullptr);
145  }
146 }
147 
148 namespace {
149 
150 // We want for different propagation to reuse as much as possible the same
151 // line. The idea behind this is to compute the 'canonical' line to use
152 // when explaining that boxes overlap on the 'y_dim' dimension. We compute
153 // the multiple of the biggest power of two that is common to all boxes.
154 IntegerValue FindCanonicalValue(IntegerValue lb, IntegerValue ub) {
155  if (lb == ub) return lb;
156  if (lb <= 0 && ub > 0) return IntegerValue(0);
157  if (lb < 0 && ub <= 0) {
158  return -FindCanonicalValue(-ub, -lb);
159  }
160 
161  int64_t mask = 0;
162  IntegerValue candidate = ub;
163  for (int o = 0; o < 62; ++o) {
164  mask = 2 * mask + 1;
165  const IntegerValue masked_ub(ub.value() & ~mask);
166  if (masked_ub >= lb) {
167  candidate = masked_ub;
168  } else {
169  break;
170  }
171  }
172  return candidate;
173 }
174 
175 void SplitDisjointBoxes(const SchedulingConstraintHelper& x,
176  absl::Span<int> boxes,
177  std::vector<absl::Span<int>>* result) {
178  result->clear();
179  std::sort(boxes.begin(), boxes.end(), [&x](int a, int b) {
180  return x.ShiftedStartMin(a) < x.ShiftedStartMin(b);
181  });
182  int current_start = 0;
183  std::size_t current_length = 1;
184  IntegerValue current_max_end = x.EndMax(boxes[0]);
185 
186  for (int b = 1; b < boxes.size(); ++b) {
187  const int box = boxes[b];
188  if (x.ShiftedStartMin(box) < current_max_end) {
189  // Merge.
190  current_length++;
191  current_max_end = std::max(current_max_end, x.EndMax(box));
192  } else {
193  if (current_length > 1) { // Ignore lists of size 1.
194  result->emplace_back(&boxes[current_start], current_length);
195  }
196  current_start = b;
197  current_length = 1;
198  current_max_end = x.EndMax(box);
199  }
200  }
201 
202  // Push last span.
203  if (current_length > 1) {
204  result->emplace_back(&boxes[current_start], current_length);
205  }
206 }
207 
208 } // namespace
209 
210 // Note that x_ and y_ must be initialized with enough intervals when passed
211 // to the disjunctive propagators.
216  Model* model)
217  : global_x_(*x),
218  global_y_(*y),
219  x_(x->NumTasks(), model),
220  strict_(strict),
221  watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
222  overload_checker_(&x_),
223  forward_detectable_precedences_(true, &x_),
224  backward_detectable_precedences_(false, &x_),
225  forward_not_last_(true, &x_),
226  backward_not_last_(false, &x_),
227  forward_edge_finding_(true, &x_),
228  backward_edge_finding_(false, &x_) {}
229 
232 
234  int fast_priority, int slow_priority) {
235  fast_id_ = watcher_->Register(this);
236  watcher_->SetPropagatorPriority(fast_id_, fast_priority);
237  global_x_.WatchAllTasks(fast_id_, watcher_);
238  global_y_.WatchAllTasks(fast_id_, watcher_);
239 
240  // This propagator is the one making sure our propagation is complete, so
241  // we do need to make sure it is called again if it modified some bounds.
243 
244  const int slow_id = watcher_->Register(this);
245  watcher_->SetPropagatorPriority(slow_id, slow_priority);
246  global_x_.WatchAllTasks(slow_id, watcher_);
247  global_y_.WatchAllTasks(slow_id, watcher_);
248 }
249 
250 #define RETURN_IF_FALSE(f) \
251  if (!(f)) return false;
252 
253 bool NonOverlappingRectanglesDisjunctivePropagator::
254  FindBoxesThatMustOverlapAHorizontalLineAndPropagate(
255  bool fast_propagation, const SchedulingConstraintHelper& x,
256  SchedulingConstraintHelper* y) {
257  // Note that since we only push bounds on x, we cache the value for y just
258  // once.
259  if (!y->SynchronizeAndSetTimeDirection(true)) return false;
260 
261  // Compute relevant boxes, the one with a mandatory part of y. Because we will
262  // need to sort it this way, we consider them by increasing start max.
263  indexed_intervals_.clear();
264  const std::vector<TaskTime>& temp = y->TaskByDecreasingStartMax();
265  for (int i = temp.size(); --i >= 0;) {
266  const int box = temp[i].task_index;
267  if (!strict_ && (x.SizeMin(box) == 0 || y->SizeMin(box) == 0)) continue;
268 
269  // Ignore absent boxes.
270  if (x.IsAbsent(box) || y->IsAbsent(box)) continue;
271 
272  // Ignore boxes where the relevant presence literal is only on the y
273  // dimension, or if both intervals are optionals with different literals.
274  if (x.IsPresent(box) && !y->IsPresent(box)) continue;
275  if (!x.IsPresent(box) && !y->IsPresent(box) &&
276  x.PresenceLiteral(box) != y->PresenceLiteral(box)) {
277  continue;
278  }
279 
280  const IntegerValue start_max = temp[i].time;
281  const IntegerValue end_min = y->EndMin(box);
282  if (start_max < end_min) {
283  indexed_intervals_.push_back({box, start_max, end_min});
284  }
285  }
286 
287  // Less than 2 boxes, no propagation.
288  if (indexed_intervals_.size() < 2) return true;
289  ConstructOverlappingSets(/*already_sorted=*/true, &indexed_intervals_,
290  &events_overlapping_boxes_);
291 
292  // Split lists of boxes into disjoint set of boxes (w.r.t. overlap).
293  boxes_to_propagate_.clear();
294  reduced_overlapping_boxes_.clear();
295  for (int i = 0; i < events_overlapping_boxes_.size(); ++i) {
296  SplitDisjointBoxes(x, absl::MakeSpan(events_overlapping_boxes_[i]),
297  &disjoint_boxes_);
298  for (absl::Span<int> sub_boxes : disjoint_boxes_) {
299  // Boxes are sorted in a stable manner in the Split method.
300  // Note that we do not use reduced_overlapping_boxes_ directly so that
301  // the order of iteration is deterministic.
302  const auto& insertion = reduced_overlapping_boxes_.insert(sub_boxes);
303  if (insertion.second) boxes_to_propagate_.push_back(sub_boxes);
304  }
305  }
306 
307  // And finally propagate.
308  //
309  // TODO(user): Sorting of boxes seems influential on the performance. Test.
310  for (const absl::Span<const int> boxes : boxes_to_propagate_) {
311  // The case of two boxes should be taken care of during "fast" propagation,
312  // so we can skip it here.
313  if (!fast_propagation && boxes.size() <= 2) continue;
314 
315  x_.ClearOtherHelper();
316  if (!x_.ResetFromSubset(x, boxes)) return false;
317 
318  // Collect the common overlapping coordinates of all boxes.
319  IntegerValue lb(std::numeric_limits<int64_t>::min());
320  IntegerValue ub(std::numeric_limits<int64_t>::max());
321  for (const int b : boxes) {
322  lb = std::max(lb, y->StartMax(b));
323  ub = std::min(ub, y->EndMin(b) - 1);
324  }
325  CHECK_LE(lb, ub);
326 
327  // We want for different propagation to reuse as much as possible the same
328  // line. The idea behind this is to compute the 'canonical' line to use
329  // when explaining that boxes overlap on the 'y_dim' dimension. We compute
330  // the multiple of the biggest power of two that is common to all boxes.
331  //
332  // TODO(user): We should scan the integer trail to find the oldest
333  // non-empty common interval. Then we can pick the canonical value within
334  // it.
335  const IntegerValue line_to_use_for_reason = FindCanonicalValue(lb, ub);
336 
337  // Setup x_dim for propagation.
338  x_.SetOtherHelper(y, boxes, line_to_use_for_reason);
339 
340  if (fast_propagation) {
341  if (x_.NumTasks() == 2) {
342  // In that case, we can use simpler algorithms.
343  // Note that this case happens frequently (~30% of all calls to this
344  // method according to our tests).
345  RETURN_IF_FALSE(PropagateTwoBoxes());
346  } else {
347  RETURN_IF_FALSE(overload_checker_.Propagate());
348  RETURN_IF_FALSE(forward_detectable_precedences_.Propagate());
349  RETURN_IF_FALSE(backward_detectable_precedences_.Propagate());
350  }
351  } else {
352  DCHECK_GT(x_.NumTasks(), 2);
353  RETURN_IF_FALSE(forward_not_last_.Propagate());
354  RETURN_IF_FALSE(backward_not_last_.Propagate());
355  RETURN_IF_FALSE(backward_edge_finding_.Propagate());
356  RETURN_IF_FALSE(forward_edge_finding_.Propagate());
357  }
358  }
359 
360  return true;
361 }
362 
364  global_x_.SetTimeDirection(true);
365  global_y_.SetTimeDirection(true);
366 
367  // Note that the code assumes that this was registered twice in fast and slow
368  // mode. So we will not redo some propagation in slow mode that was already
369  // done by the fast mode.
370  const bool fast_propagation = watcher_->GetCurrentId() == fast_id_;
371  RETURN_IF_FALSE(FindBoxesThatMustOverlapAHorizontalLineAndPropagate(
372  fast_propagation, global_x_, &global_y_));
373 
374  // We can actually swap dimensions to propagate vertically.
375  RETURN_IF_FALSE(FindBoxesThatMustOverlapAHorizontalLineAndPropagate(
376  fast_propagation, global_y_, &global_x_));
377 
378  // If two boxes must overlap but do not have a mandatory line/column that
379  // crosses both of them, then the code above do not see it. So we manually
380  // propagate this case.
381  //
382  // TODO(user): Since we are at it, do more propagation even if no conflict?
383  // This rarely propagate, so disabled for now. Investigate if it is worth
384  // it.
385  if (/*DISABLES CODE*/ (false) && watcher_->GetCurrentId() == fast_id_) {
386  const int num_boxes = global_x_.NumTasks();
387  for (int box1 = 0; box1 < num_boxes; ++box1) {
388  if (!global_x_.IsPresent(box1)) continue;
389  for (int box2 = box1 + 1; box2 < num_boxes; ++box2) {
390  if (!global_x_.IsPresent(box2)) continue;
391  if (global_x_.EndMin(box1) <= global_x_.StartMax(box2)) continue;
392  if (global_x_.EndMin(box2) <= global_x_.StartMax(box1)) continue;
393  if (global_y_.EndMin(box1) <= global_y_.StartMax(box2)) continue;
394  if (global_y_.EndMin(box2) <= global_y_.StartMax(box1)) continue;
395 
396  // X and Y must overlap. This is a conflict.
397  global_x_.ClearReason();
398  global_x_.AddPresenceReason(box1);
399  global_x_.AddPresenceReason(box2);
400  global_x_.AddReasonForBeingBefore(box1, box2);
401  global_x_.AddReasonForBeingBefore(box2, box1);
402  global_y_.ClearReason();
403  global_y_.AddPresenceReason(box1);
404  global_y_.AddPresenceReason(box2);
405  global_y_.AddReasonForBeingBefore(box1, box2);
406  global_y_.AddReasonForBeingBefore(box2, box1);
407  global_x_.ImportOtherReasons(global_y_);
408  return global_x_.ReportConflict();
409  }
410  }
411  }
412 
413  return true;
414 }
415 
416 // Specialized propagation on only two boxes that must intersect with the
417 // given y_line_for_reason.
418 bool NonOverlappingRectanglesDisjunctivePropagator::PropagateTwoBoxes() {
419  if (!x_.IsPresent(0) || !x_.IsPresent(1)) return true;
420 
421  // For each direction and each order, we test if the boxes can be disjoint.
422  const int state =
423  (x_.EndMin(0) <= x_.StartMax(1)) + 2 * (x_.EndMin(1) <= x_.StartMax(0));
424 
425  const auto left_box_before_right_box = [this](int left, int right) {
426  // left box pushes right box.
427  const IntegerValue left_end_min = x_.EndMin(left);
428  if (left_end_min > x_.StartMin(right)) {
429  x_.ClearReason();
430  x_.AddPresenceReason(left);
431  x_.AddPresenceReason(right);
432  x_.AddReasonForBeingBefore(left, right);
433  x_.AddEndMinReason(left, left_end_min);
434  RETURN_IF_FALSE(x_.IncreaseStartMin(right, left_end_min));
435  }
436 
437  // right box pushes left box.
438  const IntegerValue right_start_max = x_.StartMax(right);
439  if (right_start_max < x_.EndMax(left)) {
440  x_.ClearReason();
441  x_.AddPresenceReason(left);
442  x_.AddPresenceReason(right);
443  x_.AddReasonForBeingBefore(left, right);
444  x_.AddStartMaxReason(right, right_start_max);
445  RETURN_IF_FALSE(x_.DecreaseEndMax(left, right_start_max));
446  }
447 
448  return true;
449  };
450 
451  switch (state) {
452  case 0: { // Conflict.
453  x_.ClearReason();
454  x_.AddPresenceReason(0);
455  x_.AddPresenceReason(1);
456  x_.AddReasonForBeingBefore(0, 1);
457  x_.AddReasonForBeingBefore(1, 0);
458  return x_.ReportConflict();
459  }
460  case 1: { // b1 is left of b2.
461  return left_box_before_right_box(0, 1);
462  }
463  case 2: { // b2 is left of b1.
464  return left_box_before_right_box(1, 0);
465  }
466  default: { // Nothing to deduce.
467  return true;
468  }
469  }
470 }
471 
472 #undef RETURN_IF_FALSE
473 } // namespace sat
474 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
void SetPropagatorPriority(int id, int priority)
Definition: integer.cc:2309
int Register(PropagatorInterface *propagator)
Definition: integer.cc:2286
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
NonOverlappingRectanglesDisjunctivePropagator(bool strict, SchedulingConstraintHelper *x, SchedulingConstraintHelper *y, Model *model)
Definition: sat/diffn.cc:213
ABSL_MUST_USE_RESULT bool IncreaseStartMin(int t, IntegerValue value)
Definition: intervals.cc:523
ABSL_MUST_USE_RESULT bool DecreaseEndMax(int t, IntegerValue value)
Definition: intervals.cc:539
void WatchAllTasks(int id, GenericLiteralWatcher *watcher, bool watch_start_max=true, bool watch_end_max=true) const
Definition: intervals.cc:589
ABSL_MUST_USE_RESULT bool ResetFromSubset(const SchedulingConstraintHelper &other, absl::Span< const int > tasks)
Definition: intervals.cc:251
void AddEndMinReason(int t, IntegerValue lower_bound)
Definition: intervals.h:736
const std::vector< AffineExpression > & Starts() const
Definition: intervals.h:373
void ImportOtherReasons(const SchedulingConstraintHelper &other_helper)
Definition: intervals.cc:622
void SetOtherHelper(SchedulingConstraintHelper *other_helper, absl::Span< const int > map_to_other_helper, IntegerValue event)
Definition: intervals.h:392
void AddReasonForBeingBefore(int before, int after)
Definition: intervals.cc:444
const std::vector< AffineExpression > & Sizes() const
Definition: intervals.h:375
void AddStartMaxReason(int t, IntegerValue upper_bound)
Definition: intervals.h:729
const std::vector< AffineExpression > & Ends() const
Definition: intervals.h:374
void RegisterWith(GenericLiteralWatcher *watcher)
Definition: timetable.cc:346
int64_t b
int64_t a
GRBmodel * model
void AddCumulativeOverloadChecker(AffineExpression capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
const IntegerVariable kNoIntegerVariable(-1)
void ConstructOverlappingSets(bool already_sorted, std::vector< IndexedInterval > *intervals, std::vector< std::vector< int >> *result)
Definition: diffn_util.cc:361
std::function< IntegerVariable(Model *)> NewIntegerVariable(int64_t lb, int64_t ub)
Definition: integer.h:1734
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:46
std::function< void(Model *)> IsEqualToMinOf(IntegerVariable min_var, const std::vector< IntegerVariable > &vars)
Definition: integer_expr.h:721
void AddDiffnCumulativeRelationOnX(SchedulingConstraintHelper *x, SchedulingConstraintHelper *y, Model *model)
Definition: sat/diffn.cc:87
std::function< void(Model *)> WeightedSumGreaterOrEqual(const std::vector< IntegerVariable > &vars, const VectorInt &coefficients, int64_t lower_bound)
Definition: integer_expr.h:427
Collection of objects used to extend the Constraint Solver library.
int64_t CapSub(int64_t x, int64_t y)
int64_t capacity
#define RETURN_IF_FALSE(f)
Definition: sat/diffn.cc:250
Rev< int64_t > start_max
Rev< int64_t > end_min