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