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