OR-Tools  9.6
disjunctive.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 
15 
16 #include <algorithm>
17 #include <functional>
18 #include <utility>
19 #include <vector>
20 
21 #include "ortools/base/logging.h"
23 #include "ortools/sat/integer.h"
25 #include "ortools/sat/intervals.h"
26 #include "ortools/sat/model.h"
28 #include "ortools/sat/sat_base.h"
29 #include "ortools/sat/sat_parameters.pb.h"
30 #include "ortools/sat/sat_solver.h"
31 #include "ortools/sat/theta_tree.h"
32 #include "ortools/sat/timetable.h"
33 #include "ortools/util/sort.h"
35 
36 namespace operations_research {
37 namespace sat {
38 
39 std::function<void(Model*)> Disjunctive(
40  const std::vector<IntervalVariable>& intervals) {
41  return [=](Model* model) {
42  bool is_all_different = true;
43  IntervalsRepository* repository = model->GetOrCreate<IntervalsRepository>();
44  for (const IntervalVariable var : intervals) {
45  if (repository->IsOptional(var) || repository->MinSize(var) != 1 ||
46  repository->MaxSize(var) != 1) {
47  is_all_different = false;
48  break;
49  }
50  }
51  if (is_all_different) {
52  std::vector<AffineExpression> starts;
53  starts.reserve(intervals.size());
54  for (const IntervalVariable interval : intervals) {
55  starts.push_back(repository->Start(interval));
56  }
57  model->Add(AllDifferentOnBounds(starts));
58  return;
59  }
60 
61  auto* watcher = model->GetOrCreate<GenericLiteralWatcher>();
62  const auto& sat_parameters = *model->GetOrCreate<SatParameters>();
63  if (intervals.size() > 2 && sat_parameters.use_combined_no_overlap()) {
64  model->GetOrCreate<CombinedDisjunctive<true>>()->AddNoOverlap(intervals);
65  model->GetOrCreate<CombinedDisjunctive<false>>()->AddNoOverlap(intervals);
66  return;
67  }
68 
70  new SchedulingConstraintHelper(intervals, model);
71  model->TakeOwnership(helper);
72 
73  // Experiments to use the timetable only to propagate the disjunctive.
74  if (/*DISABLES_CODE*/ (false)) {
75  const AffineExpression one(IntegerValue(1));
76  std::vector<AffineExpression> demands(intervals.size(), one);
77  SchedulingDemandHelper* demands_helper = model->TakeOwnership(
78  new SchedulingDemandHelper(demands, helper, model));
79 
80  TimeTablingPerTask* timetable =
81  new TimeTablingPerTask(one, helper, demands_helper, model);
82  timetable->RegisterWith(watcher);
83  model->TakeOwnership(timetable);
84  return;
85  }
86 
87  if (intervals.size() == 2) {
88  DisjunctiveWithTwoItems* propagator = new DisjunctiveWithTwoItems(helper);
89  propagator->RegisterWith(watcher);
90  model->TakeOwnership(propagator);
91  } else {
92  // We decided to create the propagators in this particular order, but it
93  // shouldn't matter much because of the different priorities used.
94  {
95  // Only one direction is needed by this one.
96  DisjunctiveOverloadChecker* overload_checker =
97  new DisjunctiveOverloadChecker(helper);
98  const int id = overload_checker->RegisterWith(watcher);
99  watcher->SetPropagatorPriority(id, 1);
100  model->TakeOwnership(overload_checker);
101  }
102  for (const bool time_direction : {true, false}) {
103  DisjunctiveDetectablePrecedences* detectable_precedences =
104  new DisjunctiveDetectablePrecedences(time_direction, helper);
105  const int id = detectable_precedences->RegisterWith(watcher);
106  watcher->SetPropagatorPriority(id, 2);
107  model->TakeOwnership(detectable_precedences);
108  }
109  for (const bool time_direction : {true, false}) {
110  DisjunctiveNotLast* not_last =
111  new DisjunctiveNotLast(time_direction, helper);
112  const int id = not_last->RegisterWith(watcher);
113  watcher->SetPropagatorPriority(id, 3);
114  model->TakeOwnership(not_last);
115  }
116  for (const bool time_direction : {true, false}) {
117  DisjunctiveEdgeFinding* edge_finding =
118  new DisjunctiveEdgeFinding(time_direction, helper);
119  const int id = edge_finding->RegisterWith(watcher);
120  watcher->SetPropagatorPriority(id, 4);
121  model->TakeOwnership(edge_finding);
122  }
123  }
124 
125  // Note that we keep this one even when there is just two intervals. This is
126  // because it might push a variable that is after both of the intervals
127  // using the fact that they are in disjunction.
128  if (sat_parameters.use_precedences_in_disjunctive_constraint() &&
129  !sat_parameters.use_combined_no_overlap()) {
130  for (const bool time_direction : {true, false}) {
132  time_direction, helper, model->GetOrCreate<IntegerTrail>(),
133  model->GetOrCreate<PrecedencesPropagator>());
134  const int id = precedences->RegisterWith(watcher);
135  watcher->SetPropagatorPriority(id, 5);
136  model->TakeOwnership(precedences);
137  }
138  }
139  };
140 }
141 
143  const std::vector<IntervalVariable>& intervals, Model* model) {
144  SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
145  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
146  IntervalsRepository* repository = model->GetOrCreate<IntervalsRepository>();
147  std::vector<Literal> enforcement_literals;
148  for (int i = 1; i < intervals.size(); ++i) {
149  enforcement_literals.clear();
150  const AffineExpression start_i = repository->Start(intervals[i]);
151  const AffineExpression end_i = repository->End(intervals[i]);
152  if (repository->IsOptional(intervals[i])) {
153  enforcement_literals.push_back(repository->PresenceLiteral(intervals[i]));
154  }
155  const int enforcement_literals_size = enforcement_literals.size();
156 
157  for (int j = 0; j < i; ++j) {
158  enforcement_literals.resize(enforcement_literals_size);
159  const AffineExpression start_j = repository->Start(intervals[j]);
160  const AffineExpression end_j = repository->End(intervals[j]);
161  if (repository->IsOptional(intervals[j])) {
162  enforcement_literals.push_back(
163  repository->PresenceLiteral(intervals[j]));
164  }
165 
166  DCHECK_LE(enforcement_literals.size(), 2);
167 
168  if (integer_trail->UpperBound(start_i) <
169  integer_trail->LowerBound(end_j)) {
170  // task_i is always before task_j.
171  AddConditionalAffinePrecedence(enforcement_literals, end_i, start_j,
172  model);
173  } else if (integer_trail->UpperBound(start_j) <
174  integer_trail->LowerBound(end_i)) {
175  // task_j is always before task_i.
176  AddConditionalAffinePrecedence(enforcement_literals, end_j, start_i,
177  model);
178  } else {
179  // TODO(user): Cache boolean_var.
180  const BooleanVariable boolean_var = sat_solver->NewBooleanVariable();
181  const Literal i_before_j = Literal(boolean_var, true);
182  enforcement_literals.push_back(i_before_j);
183  AddConditionalAffinePrecedence(enforcement_literals, end_i, start_j,
184  model);
185  DCHECK_LE(enforcement_literals.size(), 3);
186  enforcement_literals.pop_back();
187  enforcement_literals.push_back(i_before_j.Negated());
188  AddConditionalAffinePrecedence(enforcement_literals, end_j, start_i,
189  model);
190  DCHECK_LE(enforcement_literals.size(), 3);
191  enforcement_literals.pop_back();
192 
193  // Force the value of boolean_var in case the precedence is not
194  // active. This avoids duplicate solutions when enumerating all
195  // possible solutions.
196  if (repository->IsOptional(intervals[i])) {
197  model->Add(Implication(
198  repository->PresenceLiteral(intervals[i]).Negated(), i_before_j));
199  }
200  if (repository->IsOptional(intervals[j])) {
201  model->Add(Implication(
202  repository->PresenceLiteral(intervals[j]).Negated(), i_before_j));
203  }
204  }
205  }
206  }
207 }
208 
210  const std::vector<IntervalVariable>& intervals, Model* model) {
212  model->Add(Disjunctive(intervals));
213 }
214 
215 void TaskSet::AddEntry(const Entry& e) {
216  int j = sorted_tasks_.size();
217  sorted_tasks_.push_back(e);
218  while (j > 0 && sorted_tasks_[j - 1].start_min > e.start_min) {
219  sorted_tasks_[j] = sorted_tasks_[j - 1];
220  --j;
221  }
222  sorted_tasks_[j] = e;
223  DCHECK(std::is_sorted(sorted_tasks_.begin(), sorted_tasks_.end()));
224 
225  // If the task is added after optimized_restart_, we know that we don't need
226  // to scan the task before optimized_restart_ in the next ComputeEndMin().
227  if (j <= optimized_restart_) optimized_restart_ = 0;
228 }
229 
231  int t) {
232  const IntegerValue dmin = helper.SizeMin(t);
233  AddEntry({t, std::max(helper.StartMin(t), helper.EndMin(t) - dmin), dmin});
234 }
235 
237  const int size = sorted_tasks_.size();
238  for (int i = 0;; ++i) {
239  if (i == size) return;
240  if (sorted_tasks_[i].task == e.task) {
241  sorted_tasks_.erase(sorted_tasks_.begin() + i);
242  break;
243  }
244  }
245 
246  optimized_restart_ = sorted_tasks_.size();
247  sorted_tasks_.push_back(e);
248  DCHECK(std::is_sorted(sorted_tasks_.begin(), sorted_tasks_.end()));
249 }
250 
251 IntegerValue TaskSet::ComputeEndMin() const {
252  DCHECK(std::is_sorted(sorted_tasks_.begin(), sorted_tasks_.end()));
253  const int size = sorted_tasks_.size();
254  IntegerValue end_min = kMinIntegerValue;
255  for (int i = optimized_restart_; i < size; ++i) {
256  const Entry& e = sorted_tasks_[i];
257  if (e.start_min >= end_min) {
258  optimized_restart_ = i;
259  end_min = e.start_min + e.size_min;
260  } else {
261  end_min += e.size_min;
262  }
263  }
264  return end_min;
265 }
266 
267 IntegerValue TaskSet::ComputeEndMin(int task_to_ignore,
268  int* critical_index) const {
269  // The order in which we process tasks with the same start-min doesn't matter.
270  DCHECK(std::is_sorted(sorted_tasks_.begin(), sorted_tasks_.end()));
271  bool ignored = false;
272  const int size = sorted_tasks_.size();
273  IntegerValue end_min = kMinIntegerValue;
274 
275  // If the ignored task is last and was the start of the critical block, then
276  // we need to reset optimized_restart_.
277  if (optimized_restart_ + 1 == size &&
278  sorted_tasks_[optimized_restart_].task == task_to_ignore) {
279  optimized_restart_ = 0;
280  }
281 
282  for (int i = optimized_restart_; i < size; ++i) {
283  const Entry& e = sorted_tasks_[i];
284  if (e.task == task_to_ignore) {
285  ignored = true;
286  continue;
287  }
288  if (e.start_min >= end_min) {
289  *critical_index = i;
290  if (!ignored) optimized_restart_ = i;
291  end_min = e.start_min + e.size_min;
292  } else {
293  end_min += e.size_min;
294  }
295  }
296  return end_min;
297 }
298 
300  DCHECK_EQ(helper_->NumTasks(), 2);
301  if (!helper_->SynchronizeAndSetTimeDirection(true)) return false;
302 
303  // We can't propagate anything if one of the interval is absent for sure.
304  if (helper_->IsAbsent(0) || helper_->IsAbsent(1)) return true;
305 
306  // Note that this propagation also take care of the "overload checker" part.
307  // It also propagates as much as possible, even in the presence of task with
308  // variable sizes.
309  //
310  // TODO(user): For optional interval whose presence in unknown and without
311  // optional variable, the end-min may not be propagated to at least (start_min
312  // + size_min). Consider that into the computation so we may decide the
313  // interval forced absence? Same for the start-max.
314  int task_before = 0;
315  int task_after = 1;
316  if (helper_->StartMax(0) < helper_->EndMin(1)) {
317  // Task 0 must be before task 1.
318  } else if (helper_->StartMax(1) < helper_->EndMin(0)) {
319  // Task 1 must be before task 0.
320  std::swap(task_before, task_after);
321  } else {
322  return true;
323  }
324 
325  if (helper_->IsPresent(task_before)) {
326  const IntegerValue end_min_before = helper_->EndMin(task_before);
327  if (helper_->StartMin(task_after) < end_min_before) {
328  // Reason for precedences if both present.
329  helper_->ClearReason();
330  helper_->AddReasonForBeingBefore(task_before, task_after);
331 
332  // Reason for the bound push.
333  helper_->AddPresenceReason(task_before);
334  helper_->AddEndMinReason(task_before, end_min_before);
335  if (!helper_->IncreaseStartMin(task_after, end_min_before)) {
336  return false;
337  }
338  }
339  }
340 
341  if (helper_->IsPresent(task_after)) {
342  const IntegerValue start_max_after = helper_->StartMax(task_after);
343  if (helper_->EndMax(task_before) > start_max_after) {
344  // Reason for precedences if both present.
345  helper_->ClearReason();
346  helper_->AddReasonForBeingBefore(task_before, task_after);
347 
348  // Reason for the bound push.
349  helper_->AddPresenceReason(task_after);
350  helper_->AddStartMaxReason(task_after, start_max_after);
351  if (!helper_->DecreaseEndMax(task_before, start_max_after)) {
352  return false;
353  }
354  }
355  }
356 
357  return true;
358 }
359 
361  const int id = watcher->Register(this);
362  helper_->WatchAllTasks(id, watcher);
364  return id;
365 }
366 
367 template <bool time_direction>
369  : helper_(model->GetOrCreate<AllIntervalsHelper>()) {
370  task_to_disjunctives_.resize(helper_->NumTasks());
371 
372  auto* watcher = model->GetOrCreate<GenericLiteralWatcher>();
373  const int id = watcher->Register(this);
374  helper_->WatchAllTasks(id, watcher, /*watch_start_max=*/true,
375  /*watch_end_max=*/false);
376  watcher->NotifyThatPropagatorMayNotReachFixedPointInOnePass(id);
377 }
378 
379 template <bool time_direction>
381  const std::vector<IntervalVariable>& vars) {
382  const int index = task_sets_.size();
383  task_sets_.emplace_back(vars.size());
384  end_mins_.push_back(kMinIntegerValue);
385  for (const IntervalVariable var : vars) {
386  task_to_disjunctives_[var.value()].push_back(index);
387  }
388 }
389 
390 template <bool time_direction>
392  if (!helper_->SynchronizeAndSetTimeDirection(time_direction)) return false;
393  const auto& task_by_increasing_end_min = helper_->TaskByIncreasingEndMin();
394  const auto& task_by_decreasing_start_max =
395  helper_->TaskByDecreasingStartMax();
396 
397  for (auto& task_set : task_sets_) task_set.Clear();
398  end_mins_.assign(end_mins_.size(), kMinIntegerValue);
399  IntegerValue max_of_end_min = kMinIntegerValue;
400 
401  const int num_tasks = helper_->NumTasks();
402  task_is_added_.assign(num_tasks, false);
403  int queue_index = num_tasks - 1;
404  for (const auto task_time : task_by_increasing_end_min) {
405  const int t = task_time.task_index;
406  const IntegerValue end_min = task_time.time;
407  if (helper_->IsAbsent(t)) continue;
408 
409  // Update all task sets.
410  while (queue_index >= 0) {
411  const auto to_insert = task_by_decreasing_start_max[queue_index];
412  const int task_index = to_insert.task_index;
413  const IntegerValue start_max = to_insert.time;
414  if (end_min <= start_max) break;
415  if (helper_->IsPresent(task_index)) {
416  task_is_added_[task_index] = true;
417  const IntegerValue shifted_smin = helper_->ShiftedStartMin(task_index);
418  const IntegerValue size_min = helper_->SizeMin(task_index);
419  for (const int d_index : task_to_disjunctives_[task_index]) {
420  // TODO(user): AddEntry() and ComputeEndMin() could be combined.
421  task_sets_[d_index].AddEntry({task_index, shifted_smin, size_min});
422  end_mins_[d_index] = task_sets_[d_index].ComputeEndMin();
423  max_of_end_min = std::max(max_of_end_min, end_mins_[d_index]);
424  }
425  }
426  --queue_index;
427  }
428 
429  // Find out amongst the disjunctives in which t appear, the one with the
430  // largest end_min, ignoring t itself. This will be the new start min for t.
431  IntegerValue new_start_min = helper_->StartMin(t);
432  if (new_start_min >= max_of_end_min) continue;
433  int best_critical_index = 0;
434  int best_d_index = -1;
435  if (task_is_added_[t]) {
436  for (const int d_index : task_to_disjunctives_[t]) {
437  if (new_start_min >= end_mins_[d_index]) continue;
438  int critical_index = 0;
439  const IntegerValue end_min_of_critical_tasks =
440  task_sets_[d_index].ComputeEndMin(/*task_to_ignore=*/t,
441  &critical_index);
442  DCHECK_LE(end_min_of_critical_tasks, max_of_end_min);
443  if (end_min_of_critical_tasks > new_start_min) {
444  new_start_min = end_min_of_critical_tasks;
445  best_d_index = d_index;
446  best_critical_index = critical_index;
447  }
448  }
449  } else {
450  // If the task t was not added, then there is no task to ignore and
451  // end_mins_[d_index] is up to date.
452  for (const int d_index : task_to_disjunctives_[t]) {
453  if (end_mins_[d_index] > new_start_min) {
454  new_start_min = end_mins_[d_index];
455  best_d_index = d_index;
456  }
457  }
458  if (best_d_index != -1) {
459  const IntegerValue end_min_of_critical_tasks =
460  task_sets_[best_d_index].ComputeEndMin(/*task_to_ignore=*/t,
461  &best_critical_index);
462  CHECK_EQ(end_min_of_critical_tasks, new_start_min);
463  }
464  }
465 
466  // Do we push something?
467  if (best_d_index == -1) continue;
468 
469  // Same reason as DisjunctiveDetectablePrecedences.
470  // TODO(user): Maybe factor out the code? It does require a function with a
471  // lot of arguments though.
472  helper_->ClearReason();
473  const std::vector<TaskSet::Entry>& sorted_tasks =
474  task_sets_[best_d_index].SortedTasks();
475  const IntegerValue window_start =
476  sorted_tasks[best_critical_index].start_min;
477  for (int i = best_critical_index; i < sorted_tasks.size(); ++i) {
478  const int ct = sorted_tasks[i].task;
479  if (ct == t) continue;
480  helper_->AddPresenceReason(ct);
481  helper_->AddEnergyAfterReason(ct, sorted_tasks[i].size_min, window_start);
482  helper_->AddStartMaxReason(ct, end_min - 1);
483  }
484  helper_->AddEndMinReason(t, end_min);
485  if (!helper_->IncreaseStartMin(t, new_start_min)) {
486  return false;
487  }
488 
489  // We need to reorder t inside task_set_. Note that if t is in the set,
490  // it means that the task is present and that IncreaseStartMin() did push
491  // its start (by opposition to an optional interval where the push might
492  // not happen if its start is not optional).
493  if (task_is_added_[t]) {
494  const IntegerValue shifted_smin = helper_->ShiftedStartMin(t);
495  const IntegerValue size_min = helper_->SizeMin(t);
496  for (const int d_index : task_to_disjunctives_[t]) {
497  // TODO(user): Refactor the code to use the same algo as in
498  // DisjunctiveDetectablePrecedences, it is superior and do not need
499  // this function.
500  task_sets_[d_index].NotifyEntryIsNowLastIfPresent(
501  {t, shifted_smin, size_min});
502  end_mins_[d_index] = task_sets_[d_index].ComputeEndMin();
503  max_of_end_min = std::max(max_of_end_min, end_mins_[d_index]);
504  }
505  }
506  }
507  return true;
508 }
509 
511  if (!helper_->SynchronizeAndSetTimeDirection(/*is_forward=*/true))
512  return false;
513 
514  // Split problem into independent part.
515  //
516  // Many propagators in this file use the same approach, we start by processing
517  // the task by increasing start-min, packing everything to the left. We then
518  // process each "independent" set of task separately. A task is independent
519  // from the one before it, if its start-min wasn't pushed.
520  //
521  // This way, we get one or more window [window_start, window_end] so that for
522  // all task in the window, [start_min, end_min] is inside the window, and the
523  // end min of any set of task to the left is <= window_start, and the
524  // start_min of any task to the right is >= end_min.
525  window_.clear();
526  IntegerValue window_end = kMinIntegerValue;
527  IntegerValue relevant_end;
528  int relevant_size = 0;
529  for (const TaskTime task_time : helper_->TaskByIncreasingShiftedStartMin()) {
530  const int task = task_time.task_index;
531  if (helper_->IsAbsent(task)) continue;
532 
533  const IntegerValue start_min = task_time.time;
534  if (start_min < window_end) {
535  window_.push_back(task_time);
536  window_end += helper_->SizeMin(task);
537  if (window_end > helper_->EndMax(task)) {
538  relevant_size = window_.size();
539  relevant_end = window_end;
540  }
541  continue;
542  }
543 
544  // Process current window.
545  // We don't need to process the end of the window (after relevant_size)
546  // because these interval can be greedily assembled in a feasible solution.
547  window_.resize(relevant_size);
548  if (relevant_size > 0 && !PropagateSubwindow(relevant_end)) {
549  return false;
550  }
551 
552  // Start of the next window.
553  window_.clear();
554  window_.push_back(task_time);
555  window_end = start_min + helper_->SizeMin(task);
556  relevant_size = 0;
557  }
558 
559  // Process last window.
560  window_.resize(relevant_size);
561  if (relevant_size > 0 && !PropagateSubwindow(relevant_end)) {
562  return false;
563  }
564 
565  return true;
566 }
567 
568 // TODO(user): Improve the Overload Checker using delayed insertion.
569 // We insert events at the cost of O(log n) per insertion, and this is where
570 // the algorithm spends most of its time, thus it is worth improving.
571 // We can insert an arbitrary set of tasks at the cost of O(n) for the whole
572 // set. This is useless for the overload checker as is since we need to check
573 // overload after every insertion, but we could use an upper bound of the
574 // theta envelope to save us from checking the actual value.
575 bool DisjunctiveOverloadChecker::PropagateSubwindow(
576  IntegerValue global_window_end) {
577  // Set up theta tree and task_by_increasing_end_max_.
578  const int window_size = window_.size();
579  theta_tree_.Reset(window_size);
580  task_by_increasing_end_max_.clear();
581  for (int i = 0; i < window_size; ++i) {
582  // No point adding a task if its end_max is too large.
583  const int task = window_[i].task_index;
584  const IntegerValue end_max = helper_->EndMax(task);
585  if (end_max < global_window_end) {
586  task_to_event_[task] = i;
587  task_by_increasing_end_max_.push_back({task, end_max});
588  }
589  }
590 
591  // Introduce events by increasing end_max, check for overloads.
592  std::sort(task_by_increasing_end_max_.begin(),
593  task_by_increasing_end_max_.end());
594  for (const auto task_time : task_by_increasing_end_max_) {
595  const int current_task = task_time.task_index;
596 
597  // We filtered absent task while constructing the subwindow, but it is
598  // possible that as we propagate task absence below, other task also become
599  // absent (if they share the same presence Boolean).
600  if (helper_->IsAbsent(current_task)) continue;
601 
602  DCHECK_NE(task_to_event_[current_task], -1);
603  {
604  const int current_event = task_to_event_[current_task];
605  const IntegerValue energy_min = helper_->SizeMin(current_task);
606  if (helper_->IsPresent(current_task)) {
607  // TODO(user): Add max energy deduction for variable
608  // sizes by putting the energy_max here and modifying the code
609  // dealing with the optional envelope greater than current_end below.
610  theta_tree_.AddOrUpdateEvent(current_event, window_[current_event].time,
611  energy_min, energy_min);
612  } else {
613  theta_tree_.AddOrUpdateOptionalEvent(
614  current_event, window_[current_event].time, energy_min);
615  }
616  }
617 
618  const IntegerValue current_end = task_time.time;
619  if (theta_tree_.GetEnvelope() > current_end) {
620  // Explain failure with tasks in critical interval.
621  helper_->ClearReason();
622  const int critical_event =
623  theta_tree_.GetMaxEventWithEnvelopeGreaterThan(current_end);
624  const IntegerValue window_start = window_[critical_event].time;
625  const IntegerValue window_end =
626  theta_tree_.GetEnvelopeOf(critical_event) - 1;
627  for (int event = critical_event; event < window_size; event++) {
628  const IntegerValue energy_min = theta_tree_.EnergyMin(event);
629  if (energy_min > 0) {
630  const int task = window_[event].task_index;
631  helper_->AddPresenceReason(task);
632  helper_->AddEnergyAfterReason(task, energy_min, window_start);
633  helper_->AddEndMaxReason(task, window_end);
634  }
635  }
636  return helper_->ReportConflict();
637  }
638 
639  // Exclude all optional tasks that would overload an interval ending here.
640  while (theta_tree_.GetOptionalEnvelope() > current_end) {
641  // Explain exclusion with tasks present in the critical interval.
642  // TODO(user): This could be done lazily, like most of the loop to
643  // compute the reasons in this file.
644  helper_->ClearReason();
645  int critical_event;
646  int optional_event;
647  IntegerValue available_energy;
649  current_end, &critical_event, &optional_event, &available_energy);
650 
651  const int optional_task = window_[optional_event].task_index;
652 
653  // If tasks shares the same presence literal, it is possible that we
654  // already pushed this task absence.
655  if (!helper_->IsAbsent(optional_task)) {
656  const IntegerValue optional_size_min = helper_->SizeMin(optional_task);
657  const IntegerValue window_start = window_[critical_event].time;
658  const IntegerValue window_end =
659  current_end + optional_size_min - available_energy - 1;
660  for (int event = critical_event; event < window_size; event++) {
661  const IntegerValue energy_min = theta_tree_.EnergyMin(event);
662  if (energy_min > 0) {
663  const int task = window_[event].task_index;
664  helper_->AddPresenceReason(task);
665  helper_->AddEnergyAfterReason(task, energy_min, window_start);
666  helper_->AddEndMaxReason(task, window_end);
667  }
668  }
669 
670  helper_->AddEnergyAfterReason(optional_task, optional_size_min,
671  window_start);
672  helper_->AddEndMaxReason(optional_task, window_end);
673 
674  if (!helper_->PushTaskAbsence(optional_task)) return false;
675  }
676 
677  theta_tree_.RemoveEvent(optional_event);
678  }
679  }
680 
681  return true;
682 }
683 
685  // This propagator reach the fix point in one pass.
686  const int id = watcher->Register(this);
687  helper_->SetTimeDirection(/*is_forward=*/true);
688  helper_->WatchAllTasks(id, watcher, /*watch_start_max=*/false,
689  /*watch_end_max=*/true);
690  return id;
691 }
692 
694  if (!helper_->SynchronizeAndSetTimeDirection(time_direction_)) return false;
695 
696  to_propagate_.clear();
697  processed_.assign(helper_->NumTasks(), false);
698 
699  // Split problem into independent part.
700  //
701  // The "independent" window can be processed separately because for each of
702  // them, a task [start-min, end-min] is in the window [window_start,
703  // window_end]. So any task to the left of the window cannot push such
704  // task start_min, and any task to the right of the window will have a
705  // start_max >= end_min, so wouldn't be in detectable precedence.
706  task_by_increasing_end_min_.clear();
707  IntegerValue window_end = kMinIntegerValue;
708  for (const TaskTime task_time : helper_->TaskByIncreasingStartMin()) {
709  const int task = task_time.task_index;
710  if (helper_->IsAbsent(task)) continue;
711 
712  // Note that the helper returns value assuming the task is present.
713  const IntegerValue start_min = helper_->StartMin(task);
714  const IntegerValue size_min = helper_->SizeMin(task);
715  const IntegerValue end_min = helper_->EndMin(task);
716  DCHECK_GE(end_min, start_min + size_min);
717 
718  if (start_min < window_end) {
719  task_by_increasing_end_min_.push_back({task, end_min});
720  window_end = std::max(window_end, start_min) + size_min;
721  continue;
722  }
723 
724  // Process current window.
725  if (task_by_increasing_end_min_.size() > 1 && !PropagateSubwindow()) {
726  return false;
727  }
728 
729  // Start of the next window.
730  task_by_increasing_end_min_.clear();
731  task_by_increasing_end_min_.push_back({task, end_min});
732  window_end = end_min;
733  }
734 
735  if (task_by_increasing_end_min_.size() > 1 && !PropagateSubwindow()) {
736  return false;
737  }
738 
739  return true;
740 }
741 
742 bool DisjunctiveDetectablePrecedences::PropagateSubwindow() {
743  DCHECK(!task_by_increasing_end_min_.empty());
744 
745  // The vector is already sorted by shifted_start_min, so there is likely a
746  // good correlation, hence the incremental sort.
747  IncrementalSort(task_by_increasing_end_min_.begin(),
748  task_by_increasing_end_min_.end());
749  const IntegerValue max_end_min = task_by_increasing_end_min_.back().time;
750 
751  // Fill and sort task_by_increasing_start_max_.
752  //
753  // TODO(user): we should use start max if present, but more generally, all
754  // helper function should probably return values "if present".
755  task_by_increasing_start_max_.clear();
756  for (const TaskTime entry : task_by_increasing_end_min_) {
757  const int task = entry.task_index;
758  const IntegerValue start_max = helper_->StartMax(task);
759  if (start_max < max_end_min && helper_->IsPresent(task)) {
760  task_by_increasing_start_max_.push_back({task, start_max});
761  }
762  }
763  if (task_by_increasing_start_max_.empty()) return true;
764  std::sort(task_by_increasing_start_max_.begin(),
765  task_by_increasing_start_max_.end());
766 
767  // Invariant: need_update is false implies that task_set_end_min is equal to
768  // task_set_.ComputeEndMin().
769  //
770  // TODO(user): Maybe it is just faster to merge ComputeEndMin() with
771  // AddEntry().
772  task_set_.Clear();
773  to_propagate_.clear();
774  bool need_update = false;
775  IntegerValue task_set_end_min = kMinIntegerValue;
776 
777  int queue_index = 0;
778  int blocking_task = -1;
779  const int queue_size = task_by_increasing_start_max_.size();
780  for (const auto task_time : task_by_increasing_end_min_) {
781  // Note that we didn't put absent task in task_by_increasing_end_min_, but
782  // the absence might have been pushed while looping here. This is fine since
783  // any push we do on this task should handle this case correctly.
784  const int current_task = task_time.task_index;
785  const IntegerValue current_end_min = task_time.time;
786  if (helper_->IsAbsent(current_task)) continue;
787 
788  for (; queue_index < queue_size; ++queue_index) {
789  const auto to_insert = task_by_increasing_start_max_[queue_index];
790  const IntegerValue start_max = to_insert.time;
791  if (current_end_min <= start_max) break;
792 
793  const int t = to_insert.task_index;
794  DCHECK(helper_->IsPresent(t));
795 
796  // If t has not been processed yet, it has a mandatory part, and rather
797  // than adding it right away to task_set, we will delay all propagation
798  // until current_task is equal to this "blocking task".
799  //
800  // This idea is introduced in "Linear-Time Filtering Algorithms for the
801  // Disjunctive Constraints" Hamed Fahimi, Claude-Guy Quimper.
802  //
803  // Experiments seems to indicate that it is slighlty faster rather than
804  // having to ignore one of the task already inserted into task_set_ when
805  // we have tasks with mandatory parts. It also open-up more option for the
806  // data structure used in task_set_.
807  if (!processed_[t]) {
808  if (blocking_task != -1) {
809  // We have two blocking tasks, which means they are in conflict.
810  helper_->ClearReason();
811  helper_->AddPresenceReason(blocking_task);
812  helper_->AddPresenceReason(t);
813  helper_->AddReasonForBeingBefore(blocking_task, t);
814  helper_->AddReasonForBeingBefore(t, blocking_task);
815  return helper_->ReportConflict();
816  }
817  DCHECK_LT(start_max, helper_->ShiftedStartMin(t) + helper_->SizeMin(t))
818  << " task should have mandatory part: "
819  << helper_->TaskDebugString(t);
820  DCHECK(to_propagate_.empty());
821  blocking_task = t;
822  to_propagate_.push_back(t);
823  } else {
824  need_update = true;
825  task_set_.AddShiftedStartMinEntry(*helper_, t);
826  }
827  }
828 
829  // If we have a blocking task, we delay the propagation until current_task
830  // is the blocking task.
831  if (blocking_task != current_task) {
832  to_propagate_.push_back(current_task);
833  if (blocking_task != -1) continue;
834  }
835  for (const int t : to_propagate_) {
836  DCHECK(!processed_[t]);
837  processed_[t] = true;
838  if (need_update) {
839  need_update = false;
840  task_set_end_min = task_set_.ComputeEndMin();
841  }
842 
843  // Corner case if a previous push from to_propagate_ caused a subsequent
844  // task to be absent.
845  if (helper_->IsAbsent(t)) continue;
846 
847  // task_set_ contains all the tasks that must be executed before t. They
848  // are in "detectable precedence" because their start_max is smaller than
849  // the end-min of t like so:
850  // [(the task t)
851  // (a task in task_set_)]
852  // From there, we deduce that the start-min of t is greater or equal to
853  // the end-min of the critical tasks.
854  //
855  // Note that this works as well when IsPresent(t) is false.
856  if (task_set_end_min > helper_->StartMin(t)) {
857  const int critical_index = task_set_.GetCriticalIndex();
858  const std::vector<TaskSet::Entry>& sorted_tasks =
859  task_set_.SortedTasks();
860  helper_->ClearReason();
861 
862  // We need:
863  // - StartMax(ct) < EndMin(t) for the detectable precedence.
864  // - StartMin(ct) >= window_start for the value of task_set_end_min.
865  const IntegerValue end_min_if_present =
866  helper_->ShiftedStartMin(t) + helper_->SizeMin(t);
867  const IntegerValue window_start =
868  sorted_tasks[critical_index].start_min;
869  for (int i = critical_index; i < sorted_tasks.size(); ++i) {
870  const int ct = sorted_tasks[i].task;
871  DCHECK_NE(ct, t);
872  helper_->AddPresenceReason(ct);
873  helper_->AddEnergyAfterReason(ct, sorted_tasks[i].size_min,
874  window_start);
875  helper_->AddStartMaxReason(ct, end_min_if_present - 1);
876  }
877 
878  // Add the reason for t (we only need the end-min).
879  helper_->AddEndMinReason(t, end_min_if_present);
880 
881  // This augment the start-min of t. Note that t is not in task set
882  // yet, so we will use this updated start if we ever add it there.
883  if (!helper_->IncreaseStartMin(t, task_set_end_min)) {
884  return false;
885  }
886 
887  // This propagators assumes that every push is reflected for its
888  // correctness.
889  if (helper_->InPropagationLoop()) return true;
890  }
891 
892  if (t == blocking_task) {
893  // Insert the blocking_task. Note that because we just pushed it,
894  // it will be last in task_set_ and also the only reason used to push
895  // any of the subsequent tasks. In particular, the reason will be valid
896  // even though task_set might contains tasks with a start_max greater or
897  // equal to the end_min of the task we push.
898  need_update = true;
899  blocking_task = -1;
900  task_set_.AddShiftedStartMinEntry(*helper_, t);
901  }
902  }
903  to_propagate_.clear();
904  }
905  return true;
906 }
907 
909  GenericLiteralWatcher* watcher) {
910  const int id = watcher->Register(this);
911  helper_->SetTimeDirection(time_direction_);
912  helper_->WatchAllTasks(id, watcher, /*watch_start_max=*/true,
913  /*watch_end_max=*/false);
915  return id;
916 }
917 
919  if (!helper_->SynchronizeAndSetTimeDirection(time_direction_)) return false;
920  window_.clear();
921  IntegerValue window_end = kMinIntegerValue;
922  for (const TaskTime task_time : helper_->TaskByIncreasingShiftedStartMin()) {
923  const int task = task_time.task_index;
924  if (!helper_->IsPresent(task)) continue;
925 
926  const IntegerValue start_min = task_time.time;
927  if (start_min < window_end) {
928  window_.push_back(task_time);
929  window_end += helper_->SizeMin(task);
930  continue;
931  }
932 
933  if (window_.size() > 1 && !PropagateSubwindow()) {
934  return false;
935  }
936 
937  // Start of the next window.
938  window_.clear();
939  window_.push_back(task_time);
940  window_end = start_min + helper_->SizeMin(task);
941  }
942  if (window_.size() > 1 && !PropagateSubwindow()) {
943  return false;
944  }
945  return true;
946 }
947 
948 bool DisjunctivePrecedences::PropagateSubwindow() {
949  // TODO(user): We shouldn't consider ends for fixed intervals here. But
950  // then we should do a better job of computing the min-end of a subset of
951  // intervals from this disjunctive (like using fixed intervals even if there
952  // is no "before that variable" relationship). Ex: If a variable is after two
953  // intervals that cannot be both before a fixed one, we could propagate more.
954  index_to_end_vars_.clear();
955  int new_size = 0;
956  for (const auto task_time : window_) {
957  const int task = task_time.task_index;
958  const AffineExpression& end_exp = helper_->Ends()[task];
959 
960  // TODO(user): Handle generic affine relation?
961  if (end_exp.var == kNoIntegerVariable || end_exp.coeff != 1) continue;
962 
963  window_[new_size++] = task_time;
964  index_to_end_vars_.push_back(end_exp.var);
965  }
966  window_.resize(new_size);
967  precedences_->ComputePrecedences(index_to_end_vars_, &before_);
968 
969  const int size = before_.size();
970  for (int i = 0; i < size;) {
971  const IntegerVariable var = before_[i].var;
972  DCHECK_NE(var, kNoIntegerVariable);
973  task_set_.Clear();
974 
975  const int initial_i = i;
976  IntegerValue min_offset = kMaxIntegerValue;
977  for (; i < size && before_[i].var == var; ++i) {
978  // Because we resized the window, the index is valid.
979  const TaskTime task_time = window_[before_[i].index];
980 
981  // We have var >= end_exp.var + offset, so
982  // var >= (end_exp.var + end_exp.constant) + (offset - end_exp.constant)
983  // var >= task end + new_offset.
984  const AffineExpression& end_exp = helper_->Ends()[task_time.task_index];
985  min_offset = std::min(min_offset, before_[i].offset - end_exp.constant);
986 
987  // The task are actually in sorted order, so we do not need to call
988  // task_set_.Sort(). This property is DCHECKed.
989  task_set_.AddUnsortedEntry({task_time.task_index, task_time.time,
990  helper_->SizeMin(task_time.task_index)});
991  }
992  DCHECK_GE(task_set_.SortedTasks().size(), 2);
993  if (integer_trail_->IsCurrentlyIgnored(var)) continue;
994 
995  // TODO(user): Only use the min_offset of the critical task? Or maybe do a
996  // more general computation to find by how much we can push var?
997  const IntegerValue new_lb = task_set_.ComputeEndMin() + min_offset;
998  if (new_lb > integer_trail_->LowerBound(var)) {
999  const std::vector<TaskSet::Entry>& sorted_tasks = task_set_.SortedTasks();
1000  helper_->ClearReason();
1001 
1002  // Fill task_to_arc_index_ since we need it for the reason.
1003  // Note that we do not care about the initial content of this vector.
1004  for (int j = initial_i; j < i; ++j) {
1005  const int task = window_[before_[j].index].task_index;
1006  task_to_arc_index_[task] = before_[j].arc_index;
1007  }
1008 
1009  const int critical_index = task_set_.GetCriticalIndex();
1010  const IntegerValue window_start = sorted_tasks[critical_index].start_min;
1011  for (int i = critical_index; i < sorted_tasks.size(); ++i) {
1012  const int ct = sorted_tasks[i].task;
1013  helper_->AddPresenceReason(ct);
1014  helper_->AddEnergyAfterReason(ct, sorted_tasks[i].size_min,
1015  window_start);
1016 
1017  const AffineExpression& end_exp = helper_->Ends()[ct];
1018  precedences_->AddPrecedenceReason(
1019  task_to_arc_index_[ct], min_offset + end_exp.constant,
1020  helper_->MutableLiteralReason(), helper_->MutableIntegerReason());
1021  }
1022 
1023  // TODO(user): If var is actually a start-min of an interval, we
1024  // could push the end-min and check the interval consistency right away.
1025  if (!helper_->PushIntegerLiteral(
1026  IntegerLiteral::GreaterOrEqual(var, new_lb))) {
1027  return false;
1028  }
1029  }
1030  }
1031  return true;
1032 }
1033 
1035  // This propagator reach the fixed point in one go.
1036  const int id = watcher->Register(this);
1037  helper_->SetTimeDirection(time_direction_);
1038  helper_->WatchAllTasks(id, watcher, /*watch_start_max=*/false,
1039  /*watch_end_max=*/false);
1040  return id;
1041 }
1042 
1044  if (!helper_->SynchronizeAndSetTimeDirection(time_direction_)) return false;
1045 
1046  const auto& task_by_decreasing_start_max =
1047  helper_->TaskByDecreasingStartMax();
1048  const auto& task_by_increasing_shifted_start_min =
1050 
1051  // Split problem into independent part.
1052  //
1053  // The situation is trickier here, and we use two windows:
1054  // - The classical "start_min_window_" as in the other propagator.
1055  // - A second window, that includes all the task with a start_max inside
1056  // [window_start, window_end].
1057  //
1058  // Now, a task from the second window can be detected to be "not last" by only
1059  // looking at the task in the first window. Tasks to the left do not cause
1060  // issue for the task to be last, and tasks to the right will not lower the
1061  // end-min of the task under consideration.
1062  int queue_index = task_by_decreasing_start_max.size() - 1;
1063  const int num_tasks = task_by_increasing_shifted_start_min.size();
1064  for (int i = 0; i < num_tasks;) {
1065  start_min_window_.clear();
1066  IntegerValue window_end = kMinIntegerValue;
1067  for (; i < num_tasks; ++i) {
1068  const TaskTime task_time = task_by_increasing_shifted_start_min[i];
1069  const int task = task_time.task_index;
1070  if (!helper_->IsPresent(task)) continue;
1071 
1072  const IntegerValue start_min = task_time.time;
1073  if (start_min_window_.empty()) {
1074  start_min_window_.push_back(task_time);
1075  window_end = start_min + helper_->SizeMin(task);
1076  } else if (start_min < window_end) {
1077  start_min_window_.push_back(task_time);
1078  window_end += helper_->SizeMin(task);
1079  } else {
1080  break;
1081  }
1082  }
1083 
1084  // Add to start_max_window_ all the task whose start_max
1085  // fall into [window_start, window_end).
1086  start_max_window_.clear();
1087  for (; queue_index >= 0; queue_index--) {
1088  const auto task_time = task_by_decreasing_start_max[queue_index];
1089 
1090  // Note that we add task whose presence is still unknown here.
1091  if (task_time.time >= window_end) break;
1092  if (helper_->IsAbsent(task_time.task_index)) continue;
1093  start_max_window_.push_back(task_time);
1094  }
1095 
1096  // If this is the case, we cannot propagate more than the detectable
1097  // precedence propagator. Note that this continue must happen after we
1098  // computed start_max_window_ though.
1099  if (start_min_window_.size() <= 1) continue;
1100 
1101  // Process current window.
1102  if (!start_max_window_.empty() && !PropagateSubwindow()) {
1103  return false;
1104  }
1105  }
1106  return true;
1107 }
1108 
1109 bool DisjunctiveNotLast::PropagateSubwindow() {
1110  auto& task_by_increasing_end_max = start_max_window_;
1111  for (TaskTime& entry : task_by_increasing_end_max) {
1112  entry.time = helper_->EndMax(entry.task_index);
1113  }
1114  IncrementalSort(task_by_increasing_end_max.begin(),
1115  task_by_increasing_end_max.end());
1116 
1117  const IntegerValue threshold = task_by_increasing_end_max.back().time;
1118  auto& task_by_increasing_start_max = start_min_window_;
1119  int queue_size = 0;
1120  for (const TaskTime entry : task_by_increasing_start_max) {
1121  const int task = entry.task_index;
1122  const IntegerValue start_max = helper_->StartMax(task);
1123  DCHECK(helper_->IsPresent(task));
1124  if (start_max < threshold) {
1125  task_by_increasing_start_max[queue_size++] = {task, start_max};
1126  }
1127  }
1128 
1129  // If the size is one, we cannot propagate more than the detectable precedence
1130  // propagator.
1131  if (queue_size <= 1) return true;
1132 
1133  task_by_increasing_start_max.resize(queue_size);
1134  std::sort(task_by_increasing_start_max.begin(),
1135  task_by_increasing_start_max.end());
1136 
1137  task_set_.Clear();
1138  int queue_index = 0;
1139  for (const auto task_time : task_by_increasing_end_max) {
1140  const int t = task_time.task_index;
1141  const IntegerValue end_max = task_time.time;
1142 
1143  // We filtered absent task before, but it is possible that as we push
1144  // bounds of optional tasks, more task become absent.
1145  if (helper_->IsAbsent(t)) continue;
1146 
1147  // task_set_ contains all the tasks that must start before the end-max of t.
1148  // These are the only candidates that have a chance to decrease the end-max
1149  // of t.
1150  while (queue_index < queue_size) {
1151  const auto to_insert = task_by_increasing_start_max[queue_index];
1152  const IntegerValue start_max = to_insert.time;
1153  if (end_max <= start_max) break;
1154 
1155  const int task_index = to_insert.task_index;
1156  DCHECK(helper_->IsPresent(task_index));
1157  task_set_.AddEntry({task_index, helper_->ShiftedStartMin(task_index),
1158  helper_->SizeMin(task_index)});
1159  ++queue_index;
1160  }
1161 
1162  // In the following case, task t cannot be after all the critical tasks
1163  // (i.e. it cannot be last):
1164  //
1165  // [(critical tasks)
1166  // | <- t start-max
1167  //
1168  // So we can deduce that the end-max of t is smaller than or equal to the
1169  // largest start-max of the critical tasks.
1170  //
1171  // Note that this works as well when the presence of t is still unknown.
1172  int critical_index = 0;
1173  const IntegerValue end_min_of_critical_tasks =
1174  task_set_.ComputeEndMin(/*task_to_ignore=*/t, &critical_index);
1175  if (end_min_of_critical_tasks <= helper_->StartMax(t)) continue;
1176 
1177  // Find the largest start-max of the critical tasks (excluding t). The
1178  // end-max for t need to be smaller than or equal to this.
1179  IntegerValue largest_ct_start_max = kMinIntegerValue;
1180  const std::vector<TaskSet::Entry>& sorted_tasks = task_set_.SortedTasks();
1181  const int sorted_tasks_size = sorted_tasks.size();
1182  for (int i = critical_index; i < sorted_tasks_size; ++i) {
1183  const int ct = sorted_tasks[i].task;
1184  if (t == ct) continue;
1185  const IntegerValue start_max = helper_->StartMax(ct);
1186  if (start_max > largest_ct_start_max) {
1187  largest_ct_start_max = start_max;
1188  }
1189  }
1190 
1191  // If we have any critical task, the test will always be true because
1192  // of the tasks we put in task_set_.
1193  DCHECK(largest_ct_start_max == kMinIntegerValue ||
1194  end_max > largest_ct_start_max);
1195  if (end_max > largest_ct_start_max) {
1196  helper_->ClearReason();
1197 
1198  const IntegerValue window_start = sorted_tasks[critical_index].start_min;
1199  for (int i = critical_index; i < sorted_tasks_size; ++i) {
1200  const int ct = sorted_tasks[i].task;
1201  if (ct == t) continue;
1202  helper_->AddPresenceReason(ct);
1203  helper_->AddEnergyAfterReason(ct, sorted_tasks[i].size_min,
1204  window_start);
1205  helper_->AddStartMaxReason(ct, largest_ct_start_max);
1206  }
1207 
1208  // Add the reason for t, we only need the start-max.
1209  helper_->AddStartMaxReason(t, end_min_of_critical_tasks - 1);
1210 
1211  // Enqueue the new end-max for t.
1212  // Note that changing it will not influence the rest of the loop.
1213  if (!helper_->DecreaseEndMax(t, largest_ct_start_max)) return false;
1214  }
1215  }
1216  return true;
1217 }
1218 
1220  const int id = watcher->Register(this);
1221  helper_->WatchAllTasks(id, watcher);
1223  return id;
1224 }
1225 
1227  const int num_tasks = helper_->NumTasks();
1228  if (!helper_->SynchronizeAndSetTimeDirection(time_direction_)) return false;
1229  is_gray_.resize(num_tasks, false);
1230  non_gray_task_to_event_.resize(num_tasks);
1231 
1232  window_.clear();
1233  IntegerValue window_end = kMinIntegerValue;
1234  for (const TaskTime task_time : helper_->TaskByIncreasingShiftedStartMin()) {
1235  const int task = task_time.task_index;
1236  if (helper_->IsAbsent(task)) continue;
1237 
1238  // Note that we use the real start min here not the shifted one. This is
1239  // because we might be able to push it if it is smaller than window end.
1240  if (helper_->StartMin(task) < window_end) {
1241  window_.push_back(task_time);
1242  window_end += helper_->SizeMin(task);
1243  continue;
1244  }
1245 
1246  // We need at least 3 tasks for the edge-finding to be different from
1247  // detectable precedences.
1248  if (window_.size() > 2 && !PropagateSubwindow(window_end)) {
1249  return false;
1250  }
1251 
1252  // Start of the next window.
1253  window_.clear();
1254  window_.push_back(task_time);
1255  window_end = task_time.time + helper_->SizeMin(task);
1256  }
1257  if (window_.size() > 2 && !PropagateSubwindow(window_end)) {
1258  return false;
1259  }
1260  return true;
1261 }
1262 
1263 bool DisjunctiveEdgeFinding::PropagateSubwindow(IntegerValue window_end_min) {
1264  // Cache the task end-max and abort early if possible.
1265  task_by_increasing_end_max_.clear();
1266  for (const auto task_time : window_) {
1267  const int task = task_time.task_index;
1268  DCHECK(!helper_->IsAbsent(task));
1269 
1270  // We already mark all the non-present task as gray.
1271  //
1272  // Same for task with an end-max that is too large: Tasks that are not
1273  // present can never trigger propagation or an overload checking failure.
1274  // theta_tree_.GetOptionalEnvelope() is always <= window_end, so tasks whose
1275  // end_max is >= window_end can never trigger propagation or failure either.
1276  // Thus, those tasks can be marked as gray, which removes their contribution
1277  // to theta right away.
1278  const IntegerValue end_max = helper_->EndMax(task);
1279  if (helper_->IsPresent(task) && end_max < window_end_min) {
1280  is_gray_[task] = false;
1281  task_by_increasing_end_max_.push_back({task, end_max});
1282  } else {
1283  is_gray_[task] = true;
1284  }
1285  }
1286 
1287  // If we have just 1 non-gray task, then this propagator does not propagate
1288  // more than the detectable precedences, so we abort early.
1289  if (task_by_increasing_end_max_.size() < 2) return true;
1290  std::sort(task_by_increasing_end_max_.begin(),
1291  task_by_increasing_end_max_.end());
1292 
1293  // Set up theta tree.
1294  //
1295  // Some task in the theta tree will be considered "gray".
1296  // When computing the end-min of the sorted task, we will compute it for:
1297  // - All the non-gray task
1298  // - All the non-gray task + at most one gray task.
1299  //
1300  // TODO(user): it should be faster to initialize it all at once rather
1301  // than calling AddOrUpdate() n times.
1302  const int window_size = window_.size();
1303  event_size_.clear();
1304  theta_tree_.Reset(window_size);
1305  for (int event = 0; event < window_size; ++event) {
1306  const TaskTime task_time = window_[event];
1307  const int task = task_time.task_index;
1308  const IntegerValue energy_min = helper_->SizeMin(task);
1309  event_size_.push_back(energy_min);
1310  if (is_gray_[task]) {
1311  theta_tree_.AddOrUpdateOptionalEvent(event, task_time.time, energy_min);
1312  } else {
1313  non_gray_task_to_event_[task] = event;
1314  theta_tree_.AddOrUpdateEvent(event, task_time.time, energy_min,
1315  energy_min);
1316  }
1317  }
1318 
1319  // At each iteration we either transform a non-gray task into a gray one or
1320  // remove a gray task, so this loop is linear in complexity.
1321  while (true) {
1322  DCHECK(!is_gray_[task_by_increasing_end_max_.back().task_index]);
1323  const IntegerValue non_gray_end_max =
1324  task_by_increasing_end_max_.back().time;
1325 
1326  // Overload checking.
1327  const IntegerValue non_gray_end_min = theta_tree_.GetEnvelope();
1328  if (non_gray_end_min > non_gray_end_max) {
1329  helper_->ClearReason();
1330 
1331  // We need the reasons for the critical tasks to fall in:
1332  const int critical_event =
1333  theta_tree_.GetMaxEventWithEnvelopeGreaterThan(non_gray_end_max);
1334  const IntegerValue window_start = window_[critical_event].time;
1335  const IntegerValue window_end =
1336  theta_tree_.GetEnvelopeOf(critical_event) - 1;
1337  for (int event = critical_event; event < window_size; event++) {
1338  const int task = window_[event].task_index;
1339  if (is_gray_[task]) continue;
1340  helper_->AddPresenceReason(task);
1341  helper_->AddEnergyAfterReason(task, event_size_[event], window_start);
1342  helper_->AddEndMaxReason(task, window_end);
1343  }
1344  return helper_->ReportConflict();
1345  }
1346 
1347  // Edge-finding.
1348  // If we have a situation like:
1349  // [(critical_task_with_gray_task)
1350  // ]
1351  // ^ end-max without the gray task.
1352  //
1353  // Then the gray task must be after all the critical tasks (all the non-gray
1354  // tasks in the tree actually), otherwise there will be no way to schedule
1355  // the critical_tasks inside their time window.
1356  while (theta_tree_.GetOptionalEnvelope() > non_gray_end_max) {
1357  int critical_event_with_gray;
1358  int gray_event;
1359  IntegerValue available_energy;
1361  non_gray_end_max, &critical_event_with_gray, &gray_event,
1362  &available_energy);
1363  const int gray_task = window_[gray_event].task_index;
1364  DCHECK(is_gray_[gray_task]);
1365 
1366  // This might happen in the corner case where more than one interval are
1367  // controlled by the same Boolean.
1368  if (helper_->IsAbsent(gray_task)) {
1369  theta_tree_.RemoveEvent(gray_event);
1370  continue;
1371  }
1372 
1373  // Since the gray task is after all the other, we have a new lower bound.
1374  if (helper_->StartMin(gray_task) < non_gray_end_min) {
1375  // The API is not ideal here. We just want the start of the critical
1376  // tasks that explain the non_gray_end_min computed above.
1377  const int critical_event =
1378  theta_tree_.GetMaxEventWithEnvelopeGreaterThan(non_gray_end_min -
1379  1);
1380  const int first_event =
1381  std::min(critical_event, critical_event_with_gray);
1382  const int second_event =
1383  std::max(critical_event, critical_event_with_gray);
1384  const IntegerValue first_start = window_[first_event].time;
1385  const IntegerValue second_start = window_[second_event].time;
1386 
1387  // window_end is chosen to be has big as possible and still have an
1388  // overload if the gray task is not last.
1389  const IntegerValue window_end =
1390  non_gray_end_max + event_size_[gray_event] - available_energy - 1;
1391  CHECK_GE(window_end, non_gray_end_max);
1392 
1393  // The non-gray part of the explanation as detailed above.
1394  helper_->ClearReason();
1395  for (int event = first_event; event < window_size; event++) {
1396  const int task = window_[event].task_index;
1397  if (is_gray_[task]) continue;
1398  helper_->AddPresenceReason(task);
1399  helper_->AddEnergyAfterReason(
1400  task, event_size_[event],
1401  event >= second_event ? second_start : first_start);
1402  helper_->AddEndMaxReason(task, window_end);
1403  }
1404 
1405  // Add the reason for the gray_task (we don't need the end-max or
1406  // presence reason).
1407  helper_->AddEnergyAfterReason(gray_task, event_size_[gray_event],
1408  window_[critical_event_with_gray].time);
1409 
1410  // Enqueue the new start-min for gray_task.
1411  //
1412  // TODO(user): propagate the precedence Boolean here too? I think it
1413  // will be more powerful. Even if eventually all these precedence will
1414  // become detectable (see Petr Villim PhD).
1415  if (!helper_->IncreaseStartMin(gray_task, non_gray_end_min)) {
1416  return false;
1417  }
1418  }
1419 
1420  // Remove the gray_task.
1421  theta_tree_.RemoveEvent(gray_event);
1422  }
1423 
1424  // Stop before we get just one non-gray task.
1425  if (task_by_increasing_end_max_.size() <= 2) break;
1426 
1427  // Stop if the min of end_max is too big.
1428  if (task_by_increasing_end_max_[0].time >=
1429  theta_tree_.GetOptionalEnvelope()) {
1430  break;
1431  }
1432 
1433  // Make the non-gray task with larger end-max gray.
1434  const int new_gray_task = task_by_increasing_end_max_.back().task_index;
1435  task_by_increasing_end_max_.pop_back();
1436  const int new_gray_event = non_gray_task_to_event_[new_gray_task];
1437  DCHECK(!is_gray_[new_gray_task]);
1438  is_gray_[new_gray_task] = true;
1439  theta_tree_.AddOrUpdateOptionalEvent(new_gray_event,
1440  window_[new_gray_event].time,
1441  event_size_[new_gray_event]);
1442  }
1443 
1444  return true;
1445 }
1446 
1448  const int id = watcher->Register(this);
1449  helper_->SetTimeDirection(time_direction_);
1450  helper_->WatchAllTasks(id, watcher, /*watch_start_max=*/false,
1451  /*watch_end_max=*/true);
1453  return id;
1454 }
1455 
1456 } // namespace sat
1457 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
void AddNoOverlap(const std::vector< IntervalVariable > &var)
Definition: disjunctive.cc:380
int RegisterWith(GenericLiteralWatcher *watcher)
Definition: disjunctive.cc:908
int RegisterWith(GenericLiteralWatcher *watcher)
int RegisterWith(GenericLiteralWatcher *watcher)
int RegisterWith(GenericLiteralWatcher *watcher)
Definition: disjunctive.cc:684
int RegisterWith(GenericLiteralWatcher *watcher)
int RegisterWith(GenericLiteralWatcher *watcher)
Definition: disjunctive.cc:360
int Register(PropagatorInterface *propagator)
Definition: integer.cc:2286
bool IsCurrentlyIgnored(IntegerVariable i) const
Definition: integer.h:775
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1557
IntegerValue MaxSize(IntervalVariable i) const
Definition: intervals.h:132
AffineExpression Start(IntervalVariable i) const
Definition: intervals.h:100
IntegerValue MinSize(IntervalVariable i) const
Definition: intervals.h:127
bool IsOptional(IntervalVariable i) const
Definition: intervals.h:77
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
void AddPrecedenceReason(int arc_index, IntegerValue min_offset, std::vector< Literal > *literal_reason, std::vector< IntegerLiteral > *integer_reason) const
Definition: precedences.cc:382
void ComputePrecedences(const std::vector< IntegerVariable > &vars, std::vector< IntegerPrecedences > *output)
Definition: precedences.cc:160
BooleanVariable NewBooleanVariable()
Definition: sat_solver.h:88
ABSL_MUST_USE_RESULT bool PushIntegerLiteral(IntegerLiteral lit)
Definition: intervals.cc:496
ABSL_MUST_USE_RESULT bool PushTaskAbsence(int t)
Definition: intervals.cc:552
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
const std::vector< TaskTime > & TaskByIncreasingStartMin()
Definition: intervals.cc:349
void WatchAllTasks(int id, GenericLiteralWatcher *watcher, bool watch_start_max=true, bool watch_end_max=true) const
Definition: intervals.cc:589
std::vector< IntegerLiteral > * MutableIntegerReason()
Definition: intervals.h:348
void AddEnergyAfterReason(int t, IntegerValue energy_min, IntegerValue time)
Definition: intervals.h:751
void AddEndMinReason(int t, IntegerValue lower_bound)
Definition: intervals.h:736
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
Definition: intervals.cc:330
const std::vector< TaskTime > & TaskByDecreasingStartMax()
Definition: intervals.cc:373
void AddEndMaxReason(int t, IntegerValue upper_bound)
Definition: intervals.h:744
const std::vector< TaskTime > & TaskByIncreasingShiftedStartMin()
Definition: intervals.cc:398
void AddReasonForBeingBefore(int before, int after)
Definition: intervals.cc:444
void AddStartMaxReason(int t, IntegerValue upper_bound)
Definition: intervals.h:729
const std::vector< AffineExpression > & Ends() const
Definition: intervals.h:374
void AddUnsortedEntry(const Entry &e)
Definition: disjunctive.h:88
void NotifyEntryIsNowLastIfPresent(const Entry &e)
Definition: disjunctive.cc:236
void AddShiftedStartMinEntry(const SchedulingConstraintHelper &helper, int t)
Definition: disjunctive.cc:230
IntegerValue ComputeEndMin() const
Definition: disjunctive.cc:251
void AddEntry(const Entry &e)
Definition: disjunctive.cc:215
IntegerValue ComputeEndMin(int task_to_ignore, int *critical_index) const
Definition: disjunctive.cc:267
const std::vector< Entry > & SortedTasks() const
Definition: disjunctive.h:118
IntegerType GetEnvelopeOf(int event) const
Definition: theta_tree.cc:203
void GetEventsWithOptionalEnvelopeGreaterThan(IntegerType target_envelope, int *critical_event, int *optional_event, IntegerType *available_energy) const
Definition: theta_tree.cc:190
int GetMaxEventWithEnvelopeGreaterThan(IntegerType target_envelope) const
Definition: theta_tree.cc:180
void AddOrUpdateOptionalEvent(int event, IntegerType initial_envelope_opt, IntegerType energy_max)
Definition: theta_tree.cc:125
IntegerType EnergyMin(int event) const
Definition: theta_tree.h:198
void AddOrUpdateEvent(int event, IntegerType initial_envelope, IntegerType energy_min, IntegerType energy_max)
Definition: theta_tree.cc:112
void RegisterWith(GenericLiteralWatcher *watcher)
Definition: timetable.cc:346
const Constraint * ct
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
int index
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:269
void AddDisjunctiveWithBooleanPrecedencesOnly(const std::vector< IntervalVariable > &intervals, Model *model)
Definition: disjunctive.cc:142
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
void AddDisjunctiveWithBooleanPrecedences(const std::vector< IntervalVariable > &intervals, Model *model)
Definition: disjunctive.cc:209
std::function< void(Model *)> Disjunctive(const std::vector< IntervalVariable > &intervals)
Definition: disjunctive.cc:39
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
void AddConditionalAffinePrecedence(const std::vector< Literal > &enforcement_literals, AffineExpression left, AffineExpression right, Model *model)
Definition: integer_expr.h:639
const IntegerVariable kNoIntegerVariable(-1)
std::function< void(Model *)> Implication(const std::vector< Literal > &enforcement_literals, IntegerLiteral i)
Definition: integer.h:1845
std::function< void(Model *)> AllDifferentOnBounds(const std::vector< AffineExpression > &expressions)
Collection of objects used to extend the Constraint Solver library.
void IncrementalSort(int max_comparisons, Iterator begin, Iterator end, Compare comp=Compare{}, bool is_stable=false)
Definition: sort.h:46
int64_t time
Definition: resource.cc:1694
IntervalVar * interval
Definition: resource.cc:101
Rev< int64_t > start_max
Rev< int64_t > end_max
Rev< int64_t > start_min
Rev< int64_t > end_min
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1499
Definition: disjunctive.h:60
int task
Definition: disjunctive.h:61
IntegerValue size_min
Definition: disjunctive.h:63
IntegerValue start_min
Definition: disjunctive.h:62