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