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