Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
disjunctive.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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 <utility>
18#include <vector>
19
20#include "absl/algorithm/container.h"
21#include "absl/log/check.h"
22#include "absl/types/span.h"
25#include "ortools/sat/integer.h"
28#include "ortools/sat/model.h"
31#include "ortools/sat/sat_parameters.pb.h"
34#include "ortools/util/sort.h"
36
37namespace operations_research {
38namespace sat {
39
40void AddDisjunctive(const std::vector<IntervalVariable>& intervals,
41 Model* model) {
42 // Depending on the parameters, create all pair of conditional precedences.
43 // TODO(user): create them dynamically instead?
44 const auto& params = *model->GetOrCreate<SatParameters>();
45 if (intervals.size() <=
46 params.max_size_to_create_precedence_literals_in_disjunctive() &&
47 params.use_strong_propagation_in_disjunctive()) {
49 }
50
51 bool is_all_different = true;
53 for (const IntervalVariable var : intervals) {
54 if (repository->IsOptional(var) || repository->MinSize(var) != 1 ||
55 repository->MaxSize(var) != 1) {
56 is_all_different = false;
57 break;
58 }
59 }
60 if (is_all_different) {
61 std::vector<AffineExpression> starts;
62 starts.reserve(intervals.size());
63 for (const IntervalVariable interval : intervals) {
64 starts.push_back(repository->Start(interval));
65 }
66 model->Add(AllDifferentOnBounds(starts));
67 return;
68 }
69
70 auto* watcher = model->GetOrCreate<GenericLiteralWatcher>();
71 if (intervals.size() > 2 && params.use_combined_no_overlap()) {
72 model->GetOrCreate<CombinedDisjunctive<true>>()->AddNoOverlap(intervals);
73 model->GetOrCreate<CombinedDisjunctive<false>>()->AddNoOverlap(intervals);
74 return;
75 }
76
78 intervals, /*register_as_disjunctive_helper=*/true);
79
80 // Experiments to use the timetable only to propagate the disjunctive.
81 if (/*DISABLES_CODE*/ (false)) {
82 const AffineExpression one(IntegerValue(1));
83 std::vector<AffineExpression> demands(intervals.size(), one);
84 SchedulingDemandHelper* demands_helper =
85 repository->GetOrCreateDemandHelper(helper, demands);
86
87 TimeTablingPerTask* timetable =
88 new TimeTablingPerTask(one, helper, demands_helper, model);
89 timetable->RegisterWith(watcher);
90 model->TakeOwnership(timetable);
91 return;
92 }
93
94 if (intervals.size() == 2) {
95 DisjunctiveWithTwoItems* propagator = new DisjunctiveWithTwoItems(helper);
96 propagator->RegisterWith(watcher);
97 model->TakeOwnership(propagator);
98 } else {
99 // We decided to create the propagators in this particular order, but it
100 // shouldn't matter much because of the different priorities used.
101 if (!params.use_strong_propagation_in_disjunctive()) {
102 // This one will not propagate anything if we added all precedence
103 // literals since the linear propagator will already do that in that case.
104 DisjunctiveSimplePrecedences* simple_precedences =
105 new DisjunctiveSimplePrecedences(helper, model);
106 const int id = simple_precedences->RegisterWith(watcher);
107 watcher->SetPropagatorPriority(id, 1);
108 model->TakeOwnership(simple_precedences);
109 }
110 {
111 // Only one direction is needed by this one.
112 DisjunctiveOverloadChecker* overload_checker =
113 new DisjunctiveOverloadChecker(helper, model);
114 const int id = overload_checker->RegisterWith(watcher);
115 watcher->SetPropagatorPriority(id, 1);
116 model->TakeOwnership(overload_checker);
117 }
118 for (const bool time_direction : {true, false}) {
119 DisjunctiveDetectablePrecedences* detectable_precedences =
120 new DisjunctiveDetectablePrecedences(time_direction, helper, model);
121 const int id = detectable_precedences->RegisterWith(watcher);
122 watcher->SetPropagatorPriority(id, 2);
123 model->TakeOwnership(detectable_precedences);
124 }
125 for (const bool time_direction : {true, false}) {
126 DisjunctiveNotLast* not_last =
127 new DisjunctiveNotLast(time_direction, helper, model);
128 const int id = not_last->RegisterWith(watcher);
129 watcher->SetPropagatorPriority(id, 3);
130 model->TakeOwnership(not_last);
131 }
132 for (const bool time_direction : {true, false}) {
133 DisjunctiveEdgeFinding* edge_finding =
134 new DisjunctiveEdgeFinding(time_direction, helper, model);
135 const int id = edge_finding->RegisterWith(watcher);
136 watcher->SetPropagatorPriority(id, 4);
137 model->TakeOwnership(edge_finding);
138 }
139 }
140
141 // Note that we keep this one even when there is just two intervals. This is
142 // because it might push a variable that is after both of the intervals
143 // using the fact that they are in disjunction.
144 if (params.use_precedences_in_disjunctive_constraint() &&
145 !params.use_combined_no_overlap()) {
146 for (const bool time_direction : {true, false}) {
147 DisjunctivePrecedences* precedences =
148 new DisjunctivePrecedences(time_direction, helper, model);
149 const int id = precedences->RegisterWith(watcher);
150 watcher->SetPropagatorPriority(id, 5);
151 model->TakeOwnership(precedences);
152 }
153 }
154}
155
157 absl::Span<const IntervalVariable> intervals, Model* model) {
158 auto* repo = model->GetOrCreate<IntervalsRepository>();
159 for (int i = 0; i < intervals.size(); ++i) {
160 for (int j = i + 1; j < intervals.size(); ++j) {
161 repo->CreateDisjunctivePrecedenceLiteral(intervals[i], intervals[j]);
162 }
163 }
164}
165
166void TaskSet::AddEntry(const Entry& e) {
167 int j = sorted_tasks_.size();
168 sorted_tasks_.push_back(e);
169 while (j > 0 && sorted_tasks_[j - 1].start_min > e.start_min) {
170 sorted_tasks_[j] = sorted_tasks_[j - 1];
171 --j;
172 }
173 sorted_tasks_[j] = e;
174 DCHECK(std::is_sorted(sorted_tasks_.begin(), sorted_tasks_.end()));
175
176 // If the task is added after optimized_restart_, we know that we don't need
177 // to scan the task before optimized_restart_ in the next ComputeEndMin().
178 if (j <= optimized_restart_) optimized_restart_ = 0;
179}
180
182 int t) {
183 const IntegerValue dmin = helper.SizeMin(t);
184 AddEntry({t, std::max(helper.StartMin(t), helper.EndMin(t) - dmin), dmin});
185}
186
188 const int size = sorted_tasks_.size();
189 for (int i = 0;; ++i) {
190 if (i == size) return;
191 if (sorted_tasks_[i].task == e.task) {
192 for (int j = i; j + 1 < size; ++j) {
193 sorted_tasks_[j] = sorted_tasks_[j + 1];
194 }
195 sorted_tasks_.pop_back();
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
205IntegerValue TaskSet::ComputeEndMin() const {
206 DCHECK(std::is_sorted(sorted_tasks_.begin(), sorted_tasks_.end()));
207 const int size = sorted_tasks_.size();
208 IntegerValue end_min = kMinIntegerValue;
209 for (int i = optimized_restart_; i < size; ++i) {
210 const Entry& e = sorted_tasks_[i];
211 if (e.start_min >= end_min) {
212 optimized_restart_ = i;
213 end_min = e.start_min + e.size_min;
214 } else {
215 end_min += e.size_min;
216 }
217 }
218 return end_min;
219}
220
221IntegerValue TaskSet::ComputeEndMin(int task_to_ignore,
222 int* critical_index) const {
223 // The order in which we process tasks with the same start-min doesn't matter.
224 DCHECK(std::is_sorted(sorted_tasks_.begin(), sorted_tasks_.end()));
225 bool ignored = false;
226 const int size = sorted_tasks_.size();
227 IntegerValue end_min = kMinIntegerValue;
228
229 // If the ignored task is last and was the start of the critical block, then
230 // we need to reset optimized_restart_.
231 if (optimized_restart_ + 1 == size &&
232 sorted_tasks_[optimized_restart_].task == task_to_ignore) {
233 optimized_restart_ = 0;
234 }
235
236 for (int i = optimized_restart_; i < size; ++i) {
237 const Entry& e = sorted_tasks_[i];
238 if (e.task == task_to_ignore) {
239 ignored = true;
240 continue;
241 }
242 if (e.start_min >= end_min) {
243 *critical_index = i;
244 if (!ignored) optimized_restart_ = i;
245 end_min = e.start_min + e.size_min;
246 } else {
247 end_min += e.size_min;
248 }
249 }
250 return end_min;
251}
252
254 DCHECK_EQ(helper_->NumTasks(), 2);
255 if (!helper_->SynchronizeAndSetTimeDirection(true)) return false;
256
257 // We can't propagate anything if one of the interval is absent for sure.
258 if (helper_->IsAbsent(0) || helper_->IsAbsent(1)) return true;
259
260 // Note that this propagation also take care of the "overload checker" part.
261 // It also propagates as much as possible, even in the presence of task with
262 // variable sizes.
263 //
264 // TODO(user): For optional interval whose presence in unknown and without
265 // optional variable, the end-min may not be propagated to at least (start_min
266 // + size_min). Consider that into the computation so we may decide the
267 // interval forced absence? Same for the start-max.
268 int task_before = 0;
269 int task_after = 1;
270
271 const bool task_0_before_task_1 = helper_->StartMax(0) < helper_->EndMin(1);
272 const bool task_1_before_task_0 = helper_->StartMax(1) < helper_->EndMin(0);
273
274 if (task_0_before_task_1 && task_1_before_task_0 &&
275 helper_->IsPresent(task_before) && helper_->IsPresent(task_after)) {
276 helper_->ClearReason();
277 helper_->AddPresenceReason(task_before);
278 helper_->AddPresenceReason(task_after);
279 helper_->AddReasonForBeingBefore(task_before, task_after);
280 helper_->AddReasonForBeingBefore(task_after, task_before);
281 return helper_->ReportConflict();
282 }
283
284 if (task_0_before_task_1) {
285 // Task 0 must be before task 1.
286 } else if (task_1_before_task_0) {
287 // Task 1 must be before task 0.
288 std::swap(task_before, task_after);
289 } else {
290 return true;
291 }
292
293 if (helper_->IsPresent(task_before)) {
294 const IntegerValue end_min_before = helper_->EndMin(task_before);
295 if (helper_->StartMin(task_after) < end_min_before) {
296 // Reason for precedences if both present.
297 helper_->ClearReason();
298 helper_->AddReasonForBeingBefore(task_before, task_after);
299
300 // Reason for the bound push.
301 helper_->AddPresenceReason(task_before);
302 helper_->AddEndMinReason(task_before, end_min_before);
303 if (!helper_->IncreaseStartMin(task_after, end_min_before)) {
304 return false;
305 }
306 }
307 }
308
309 if (helper_->IsPresent(task_after)) {
310 const IntegerValue start_max_after = helper_->StartMax(task_after);
311 if (helper_->EndMax(task_before) > start_max_after) {
312 // Reason for precedences if both present.
313 helper_->ClearReason();
314 helper_->AddReasonForBeingBefore(task_before, task_after);
315
316 // Reason for the bound push.
317 helper_->AddPresenceReason(task_after);
318 helper_->AddStartMaxReason(task_after, start_max_after);
319 if (!helper_->DecreaseEndMax(task_before, start_max_after)) {
320 return false;
321 }
322 }
323 }
324
325 return true;
326}
327
329 const int id = watcher->Register(this);
330 helper_->WatchAllTasks(id);
332 return id;
333}
334
335template <bool time_direction>
337 : helper_(model->GetOrCreate<IntervalsRepository>()->GetOrCreateHelper(
338 model->GetOrCreate<IntervalsRepository>()->AllIntervals())) {
339 task_to_disjunctives_.resize(helper_->NumTasks());
340
341 auto* watcher = model->GetOrCreate<GenericLiteralWatcher>();
342 const int id = watcher->Register(this);
343 helper_->WatchAllTasks(id);
344 watcher->NotifyThatPropagatorMayNotReachFixedPointInOnePass(id);
345}
346
347template <bool time_direction>
349 absl::Span<const IntervalVariable> vars) {
350 const int index = task_sets_.size();
351 task_sets_.emplace_back(vars.size());
352 end_mins_.push_back(kMinIntegerValue);
353 for (const IntervalVariable var : vars) {
354 task_to_disjunctives_[var.value()].push_back(index);
355 }
356}
357
358template <bool time_direction>
360 if (!helper_->SynchronizeAndSetTimeDirection(time_direction)) return false;
361 const auto& task_by_increasing_end_min = helper_->TaskByIncreasingEndMin();
362 const auto& task_by_negated_start_max =
363 helper_->TaskByIncreasingNegatedStartMax();
364
365 for (auto& task_set : task_sets_) task_set.Clear();
366 end_mins_.assign(end_mins_.size(), kMinIntegerValue);
367 IntegerValue max_of_end_min = kMinIntegerValue;
368
369 const int num_tasks = helper_->NumTasks();
370 task_is_added_.assign(num_tasks, false);
371 int queue_index = num_tasks - 1;
372 for (const auto task_time : task_by_increasing_end_min) {
373 const int t = task_time.task_index;
374 const IntegerValue end_min = task_time.time;
375 if (helper_->IsAbsent(t)) continue;
376
377 // Update all task sets.
378 while (queue_index >= 0) {
379 const auto to_insert = task_by_negated_start_max[queue_index];
380 const int task_index = to_insert.task_index;
381 const IntegerValue start_max = -to_insert.time;
382 if (end_min <= start_max) break;
383 if (helper_->IsPresent(task_index)) {
384 task_is_added_[task_index] = true;
385 const IntegerValue shifted_smin = helper_->ShiftedStartMin(task_index);
386 const IntegerValue size_min = helper_->SizeMin(task_index);
387 for (const int d_index : task_to_disjunctives_[task_index]) {
388 // TODO(user): AddEntry() and ComputeEndMin() could be combined.
389 task_sets_[d_index].AddEntry({task_index, shifted_smin, size_min});
390 end_mins_[d_index] = task_sets_[d_index].ComputeEndMin();
391 max_of_end_min = std::max(max_of_end_min, end_mins_[d_index]);
392 }
393 }
394 --queue_index;
395 }
396
397 // Find out amongst the disjunctives in which t appear, the one with the
398 // largest end_min, ignoring t itself. This will be the new start min for t.
399 IntegerValue new_start_min = helper_->StartMin(t);
400 if (new_start_min >= max_of_end_min) continue;
401 int best_critical_index = 0;
402 int best_d_index = -1;
403 if (task_is_added_[t]) {
404 for (const int d_index : task_to_disjunctives_[t]) {
405 if (new_start_min >= end_mins_[d_index]) continue;
406 int critical_index = 0;
407 const IntegerValue end_min_of_critical_tasks =
408 task_sets_[d_index].ComputeEndMin(/*task_to_ignore=*/t,
409 &critical_index);
410 DCHECK_LE(end_min_of_critical_tasks, max_of_end_min);
411 if (end_min_of_critical_tasks > new_start_min) {
412 new_start_min = end_min_of_critical_tasks;
413 best_d_index = d_index;
414 best_critical_index = critical_index;
415 }
416 }
417 } else {
418 // If the task t was not added, then there is no task to ignore and
419 // end_mins_[d_index] is up to date.
420 for (const int d_index : task_to_disjunctives_[t]) {
421 if (end_mins_[d_index] > new_start_min) {
422 new_start_min = end_mins_[d_index];
423 best_d_index = d_index;
424 }
425 }
426 if (best_d_index != -1) {
427 const IntegerValue end_min_of_critical_tasks =
428 task_sets_[best_d_index].ComputeEndMin(/*task_to_ignore=*/t,
429 &best_critical_index);
430 CHECK_EQ(end_min_of_critical_tasks, new_start_min);
431 }
432 }
433
434 // Do we push something?
435 if (best_d_index == -1) continue;
436
437 // Same reason as DisjunctiveDetectablePrecedences.
438 // TODO(user): Maybe factor out the code? It does require a function with a
439 // lot of arguments though.
440 helper_->ClearReason();
441 const absl::Span<const TaskSet::Entry> sorted_tasks =
442 task_sets_[best_d_index].SortedTasks();
443 const IntegerValue window_start =
444 sorted_tasks[best_critical_index].start_min;
445 for (int i = best_critical_index; i < sorted_tasks.size(); ++i) {
446 const int ct = sorted_tasks[i].task;
447 if (ct == t) continue;
448 helper_->AddPresenceReason(ct);
449 helper_->AddEnergyAfterReason(ct, sorted_tasks[i].size_min, window_start);
450 helper_->AddStartMaxReason(ct, end_min - 1);
451 }
452 helper_->AddEndMinReason(t, end_min);
453 if (!helper_->IncreaseStartMin(t, new_start_min)) {
454 return false;
455 }
456
457 // We need to reorder t inside task_set_. Note that if t is in the set,
458 // it means that the task is present and that IncreaseStartMin() did push
459 // its start (by opposition to an optional interval where the push might
460 // not happen if its start is not optional).
461 if (task_is_added_[t]) {
462 const IntegerValue shifted_smin = helper_->ShiftedStartMin(t);
463 const IntegerValue size_min = helper_->SizeMin(t);
464 for (const int d_index : task_to_disjunctives_[t]) {
465 // TODO(user): Refactor the code to use the same algo as in
466 // DisjunctiveDetectablePrecedences, it is superior and do not need
467 // this function.
468 task_sets_[d_index].NotifyEntryIsNowLastIfPresent(
469 {t, shifted_smin, size_min});
470 end_mins_[d_index] = task_sets_[d_index].ComputeEndMin();
471 max_of_end_min = std::max(max_of_end_min, end_mins_[d_index]);
472 }
473 }
474 }
475 return true;
476}
477
479 stats_.OnPropagate();
480 if (!helper_->SynchronizeAndSetTimeDirection(/*is_forward=*/true)) {
481 ++stats_.num_conflicts;
482 return false;
483 }
484
485 // We use this to detect precedence between task that must cause a push.
486 TaskTime task_with_max_end_min = {0, kMinIntegerValue};
487
488 // Split problem into independent part.
489 //
490 // Many propagators in this file use the same approach, we start by processing
491 // the task by increasing start-min, packing everything to the left. We then
492 // process each "independent" set of task separately. A task is independent
493 // from the one before it, if its start-min wasn't pushed.
494 //
495 // This way, we get one or more window [window_start, window_end] so that for
496 // all task in the window, [start_min, end_min] is inside the window, and the
497 // end min of any set of task to the left is <= window_start, and the
498 // start_min of any task to the right is >= end_min.
499 IntegerValue window_end = kMinIntegerValue;
500 IntegerValue relevant_end;
501 int window_size = 0;
502 int relevant_size = 0;
503 TaskTime* const window = window_.get();
504 for (const auto [task, presence_lit, start_min] :
505 helper_->TaskByIncreasingShiftedStartMin()) {
506 if (helper_->IsAbsent(presence_lit)) continue;
507
508 // Nothing to do with overload checking, but almost free to do that here.
509 const IntegerValue size_min = helper_->SizeMin(task);
510 const IntegerValue end_min = start_min + size_min;
511 const IntegerValue start_max = helper_->StartMax(task);
512 if (start_max < task_with_max_end_min.time &&
513 helper_->IsPresent(presence_lit) && size_min > 0) {
514 // We have a detectable precedence that must cause a push.
515 //
516 // Remarks: If we added all precedence literals + linear relation, this
517 // propagation would have been done by the linear propagator, but if we
518 // didn't add such relations yet, it is beneficial to detect that here!
519 //
520 // TODO(user): Actually, we just infered a "not last" so we could check
521 // for relevant_size > 2 potential propagation?
522 //
523 // TODO(user): Can we detect and propagate all such relations easily and
524 // do a pass before this maybe? On a related note, because this
525 // propagator is not instantiated in both direction, we might miss some
526 // easy propag.
527 const int to_push = task_with_max_end_min.task_index;
528 helper_->ClearReason();
529 helper_->AddPresenceReason(task);
530 helper_->AddReasonForBeingBefore(task, to_push);
531 helper_->AddEndMinReason(task, end_min);
532
533 if (!helper_->IncreaseStartMin(to_push, end_min)) {
534 ++stats_.num_conflicts;
535 return false;
536 }
537
538 // TODO(user): Shall we keep propagating? we know the prefix didn't
539 // change, so we could be faster here. On another hand, it might be
540 // better to propagate all the linear constraints before returning
541 // here.
542 ++stats_.num_propagations;
543
544 stats_.EndWithoutConflicts();
545 return true;
546 }
547
548 // Note that we need to do that AFTER the block above.
549 if (end_min > task_with_max_end_min.time) {
550 task_with_max_end_min = {task, end_min};
551 }
552
553 // Extend window.
554 if (start_min < window_end) {
555 window[window_size++] = {task, start_min};
556 if (window_end > start_max) {
557 window_end += size_min;
558 relevant_size = window_size;
559 relevant_end = window_end;
560 } else {
561 window_end += size_min;
562 }
563 continue;
564 }
565
566 // Process current window.
567 // We don't need to process the end of the window (after relevant_size)
568 // because these interval can be greedily assembled in a feasible solution.
569 if (relevant_size > 0 && !PropagateSubwindow(relevant_size, relevant_end)) {
570 ++stats_.num_conflicts;
571 return false;
572 }
573
574 // Start of the next window.
575 window_size = 0;
576 window[window_size++] = {task, start_min};
577 window_end = start_min + size_min;
578 relevant_size = 0;
579 }
580
581 // Process last window.
582 if (relevant_size > 0 && !PropagateSubwindow(relevant_size, relevant_end)) {
583 ++stats_.num_conflicts;
584 return false;
585 }
586
587 stats_.EndWithoutConflicts();
588 return true;
589}
590
591// TODO(user): Improve the Overload Checker using delayed insertion.
592// We insert events at the cost of O(log n) per insertion, and this is where
593// the algorithm spends most of its time, thus it is worth improving.
594// We can insert an arbitrary set of tasks at the cost of O(n) for the whole
595// set. This is useless for the overload checker as is since we need to check
596// overload after every insertion, but we could use an upper bound of the
597// theta envelope to save us from checking the actual value.
598bool DisjunctiveOverloadChecker::PropagateSubwindow(
599 int relevant_size, IntegerValue global_window_end) {
600 // Set up theta tree and task_by_increasing_end_max_.
601 int num_events = 0;
602 task_by_increasing_end_max_.clear();
603 for (int i = 0; i < relevant_size; ++i) {
604 // No point adding a task if its end_max is too large.
605 const TaskTime& task_time = window_[i];
606 const int task = task_time.task_index;
607
608 // We use the shifted end-max.
609 const IntegerValue end_max =
610 helper_->StartMax(task) + helper_->SizeMin(task);
611 if (end_max < global_window_end) {
612 window_[num_events] = task_time;
613 task_to_event_[task] = num_events;
614 task_by_increasing_end_max_.push_back({task, end_max});
615 ++num_events;
616 }
617 }
618 if (num_events <= 1) return true;
619 theta_tree_.Reset(num_events);
620
621 // Introduce events by increasing end_max, check for overloads.
622 // If end_max is the same, we want to add high start-min first.
623 absl::c_reverse(task_by_increasing_end_max_);
624 absl::c_stable_sort(task_by_increasing_end_max_);
625 for (const auto task_time : task_by_increasing_end_max_) {
626 const int current_task = task_time.task_index;
627
628 // We filtered absent task while constructing the subwindow, but it is
629 // possible that as we propagate task absence below, other task also become
630 // absent (if they share the same presence Boolean).
631 if (helper_->IsAbsent(current_task)) continue;
632
633 DCHECK_NE(task_to_event_[current_task], -1);
634 {
635 const int current_event = task_to_event_[current_task];
636 const IntegerValue energy_min = helper_->SizeMin(current_task);
637 if (helper_->IsPresent(current_task)) {
638 // TODO(user): Add max energy deduction for variable
639 // sizes by putting the energy_max here and modifying the code
640 // dealing with the optional envelope greater than current_end below.
641 theta_tree_.AddOrUpdateEvent(current_event, window_[current_event].time,
642 energy_min, energy_min);
643 } else {
644 theta_tree_.AddOrUpdateOptionalEvent(
645 current_event, window_[current_event].time, energy_min);
646 }
647 }
648
649 const IntegerValue current_end = task_time.time;
650 if (theta_tree_.GetEnvelope() > current_end) {
651 // Explain failure with tasks in critical interval.
652 helper_->ClearReason();
653 const int critical_event =
654 theta_tree_.GetMaxEventWithEnvelopeGreaterThan(current_end);
655 const IntegerValue window_start = window_[critical_event].time;
656 const IntegerValue window_end =
657 theta_tree_.GetEnvelopeOf(critical_event) - 1;
658 for (int event = critical_event; event < num_events; event++) {
659 const IntegerValue energy_min = theta_tree_.EnergyMin(event);
660 if (energy_min > 0) {
661 const int task = window_[event].task_index;
662 helper_->AddPresenceReason(task);
663 helper_->AddEnergyAfterReason(task, energy_min, window_start);
664 helper_->AddShiftedEndMaxReason(task, window_end);
665 }
666 }
667 return helper_->ReportConflict();
668 }
669
670 // Exclude all optional tasks that would overload an interval ending here.
671 while (theta_tree_.GetOptionalEnvelope() > current_end) {
672 // Explain exclusion with tasks present in the critical interval.
673 // TODO(user): This could be done lazily, like most of the loop to
674 // compute the reasons in this file.
675 helper_->ClearReason();
676 int critical_event;
677 int optional_event;
678 IntegerValue available_energy;
679 theta_tree_.GetEventsWithOptionalEnvelopeGreaterThan(
680 current_end, &critical_event, &optional_event, &available_energy);
681
682 const int optional_task = window_[optional_event].task_index;
683
684 // If tasks shares the same presence literal, it is possible that we
685 // already pushed this task absence.
686 if (!helper_->IsAbsent(optional_task)) {
687 const IntegerValue optional_size_min = helper_->SizeMin(optional_task);
688 const IntegerValue window_start = window_[critical_event].time;
689 const IntegerValue window_end =
690 current_end + optional_size_min - available_energy - 1;
691 for (int event = critical_event; event < num_events; event++) {
692 const IntegerValue energy_min = theta_tree_.EnergyMin(event);
693 if (energy_min > 0) {
694 const int task = window_[event].task_index;
695 helper_->AddPresenceReason(task);
696 helper_->AddEnergyAfterReason(task, energy_min, window_start);
697 helper_->AddShiftedEndMaxReason(task, window_end);
698 }
699 }
700
701 helper_->AddEnergyAfterReason(optional_task, optional_size_min,
702 window_start);
703 helper_->AddShiftedEndMaxReason(optional_task, window_end);
704
705 ++stats_.num_propagations;
706 if (!helper_->PushTaskAbsence(optional_task)) return false;
707 }
708
709 theta_tree_.RemoveEvent(optional_event);
710 }
711 }
712
713 return true;
714}
715
717 // This propagator reach the fix point in one pass.
718 const int id = watcher->Register(this);
719 helper_->SetTimeDirection(/*is_forward=*/true);
721 helper_->WatchAllTasks(id);
722 return id;
723}
724
726 const int id = watcher->Register(this);
727 helper_->WatchAllTasks(id);
729 return id;
730}
731
733 stats_.OnPropagate();
734
735 const bool current_direction = helper_->CurrentTimeIsForward();
736 for (const bool direction : {current_direction, !current_direction}) {
737 if (!helper_->SynchronizeAndSetTimeDirection(direction)) {
738 ++stats_.num_conflicts;
739 return false;
740 }
741 if (!PropagateOneDirection()) {
742 ++stats_.num_conflicts;
743 return false;
744 }
745 }
746
747 stats_.EndWithoutConflicts();
748 return true;
749}
750
751bool DisjunctiveSimplePrecedences::Push(TaskTime before, int t) {
752 const int t_before = before.task_index;
753 DCHECK_NE(t_before, t);
754 helper_->ClearReason();
755 helper_->AddPresenceReason(t_before);
756 helper_->AddReasonForBeingBefore(t_before, t);
757 helper_->AddEndMinReason(t_before, before.time);
758 if (!helper_->IncreaseStartMin(t, before.time)) {
759 return false;
760 }
761 ++stats_.num_propagations;
762 return true;
763}
764
765bool DisjunctiveSimplePrecedences::PropagateOneDirection() {
766 // We will loop in a decreasing way here.
767 // And add tasks that are present to the task_set_.
768 absl::Span<const TaskTime> task_by_negated_start_max =
770
771 // We just keep amongst all the task before current_end_min, the one with the
772 // highesh end-min.
773 TaskTime best_task_before = {-1, kMinIntegerValue};
774
775 // We will loop in an increasing way here and consume task from beginning.
776 absl::Span<const TaskTime> task_by_increasing_end_min =
777 helper_->TaskByIncreasingEndMin();
778
779 for (; !task_by_increasing_end_min.empty();) {
780 // Skip absent task.
781 if (helper_->IsAbsent(task_by_increasing_end_min.front().task_index)) {
782 task_by_increasing_end_min.remove_prefix(1);
783 continue;
784 }
785
786 // Consider all task with a start_max < current_end_min.
787 int blocking_task = -1;
788 IntegerValue blocking_start_max;
789 IntegerValue current_end_min = task_by_increasing_end_min.front().time;
790 for (; true; task_by_negated_start_max.remove_suffix(1)) {
791 if (task_by_negated_start_max.empty()) {
792 // Small optim: this allows to process all remaining task rather than
793 // looping around are retesting this for all remaining tasks.
794 current_end_min = kMaxIntegerValue;
795 break;
796 }
797
798 const auto [t, negated_start_max] = task_by_negated_start_max.back();
799 const IntegerValue start_max = -negated_start_max;
800 if (current_end_min <= start_max) break;
801 if (!helper_->IsPresent(t)) continue;
802
803 // If t has a mandatory part, and extend further than current_end_min
804 // then we can push it first. All tasks for which their push is delayed
805 // are necessarily after this "blocking task".
806 //
807 // This idea is introduced in "Linear-Time Filtering Algorithms for the
808 // Disjunctive Constraints" Hamed Fahimi, Claude-Guy Quimper.
809 const IntegerValue end_min = helper_->EndMin(t);
810 if (blocking_task == -1 && end_min >= current_end_min) {
811 DCHECK_LT(start_max, end_min) << " task should have mandatory part: "
812 << helper_->TaskDebugString(t);
813 blocking_task = t;
814 blocking_start_max = start_max;
815 current_end_min = end_min;
816 } else if (blocking_task != -1 && blocking_start_max < end_min) {
817 // Conflict! the task is after the blocking_task but also before.
818 helper_->ClearReason();
819 helper_->AddPresenceReason(blocking_task);
820 helper_->AddPresenceReason(t);
821 helper_->AddReasonForBeingBefore(blocking_task, t);
822 helper_->AddReasonForBeingBefore(t, blocking_task);
823 return helper_->ReportConflict();
824 } else if (end_min > best_task_before.time) {
825 best_task_before = {t, end_min};
826 }
827 }
828
829 // If we have a blocking task. We need to propagate it first.
830 if (blocking_task != -1) {
831 DCHECK(!helper_->IsAbsent(blocking_task));
832 if (best_task_before.time > helper_->StartMin(blocking_task)) {
833 if (!Push(best_task_before, blocking_task)) return false;
834 }
835
836 // Update best_task_before.
837 //
838 // Note that we want the blocking task here as it is the only one
839 // guaranteed to be before all the task we push below. If we are not in a
840 // propagation loop, it should also be the best.
841 const IntegerValue end_min = helper_->EndMin(blocking_task);
842 best_task_before = {blocking_task, end_min};
843 }
844
845 // Lets propagate all task after best_task_before.
846 for (; !task_by_increasing_end_min.empty();
847 task_by_increasing_end_min.remove_prefix(1)) {
848 const auto [t, end_min] = task_by_increasing_end_min.front();
849 if (end_min > current_end_min) break;
850 if (t == blocking_task) continue; // Already done.
851
852 // Lets propagate current_task.
853 if (best_task_before.time > helper_->StartMin(t)) {
854 // Corner case if a previous push caused a subsequent task to be absent.
855 if (helper_->IsAbsent(t)) continue;
856 if (!Push(best_task_before, t)) return false;
857 }
858 }
859 }
860 return true;
861}
862
864 stats_.OnPropagate();
865 if (!helper_->SynchronizeAndSetTimeDirection(time_direction_)) {
866 ++stats_.num_conflicts;
867 return false;
868 }
869
870 // Compute the "rank" of each task.
871 const auto by_shifted_smin = helper_->TaskByIncreasingShiftedStartMin();
872 int rank = -1;
873 IntegerValue window_end = kMinIntegerValue;
874 int* const ranks = ranks_.data();
875 for (const auto [task, presence_lit, start_min] : by_shifted_smin) {
876 if (!helper_->IsPresent(presence_lit)) {
877 ranks[task] = -1;
878 continue;
879 }
880
881 const IntegerValue size_min = helper_->SizeMin(task);
882 if (start_min < window_end) {
883 ranks[task] = rank;
884 window_end += size_min;
885 } else {
886 ranks[task] = ++rank;
887 window_end = start_min + size_min;
888 }
889 }
890
891 if (!PropagateWithRanks()) {
892 ++stats_.num_conflicts;
893 return false;
894 }
895 stats_.EndWithoutConflicts();
896 return true;
897}
898
899// task_set_ contains all the tasks that must be executed before t. They
900// are in "detectable precedence" because their start_max is smaller than
901// the end-min of t like so:
902// [(the task t)
903// (a task in task_set_)]
904// From there, we deduce that the start-min of t is greater or equal to
905// the end-min of the critical tasks.
906//
907// Note that this works as well when IsPresent(t) is false.
908bool DisjunctiveDetectablePrecedences::Push(IntegerValue task_set_end_min,
909 int t) {
910 const int critical_index = task_set_.GetCriticalIndex();
911 const absl::Span<const TaskSet::Entry> sorted_tasks = task_set_.SortedTasks();
912 helper_->ClearReason();
913
914 // We need:
915 // - StartMax(ct) < EndMin(t) for the detectable precedence.
916 // - StartMin(ct) >= window_start for the value of task_set_end_min.
917 const IntegerValue end_min_if_present =
918 helper_->ShiftedStartMin(t) + helper_->SizeMin(t);
919 const IntegerValue window_start = sorted_tasks[critical_index].start_min;
920 IntegerValue min_slack = kMaxIntegerValue;
921 bool all_already_before = true;
922 IntegerValue energy_of_task_before = 0;
923 for (int i = critical_index; i < sorted_tasks.size(); ++i) {
924 const int ct = sorted_tasks[i].task;
925 DCHECK_NE(ct, t);
926 helper_->AddPresenceReason(ct);
927
928 // Heuristic, if some tasks are known to be after the first one,
929 // we just add the min-size as a reason.
930 if (i > critical_index && helper_->GetCurrentMinDistanceBetweenTasks(
931 sorted_tasks[critical_index].task, ct,
932 /*add_reason_if_after=*/true) >= 0) {
933 helper_->AddSizeMinReason(ct);
934 } else {
935 helper_->AddEnergyAfterReason(ct, sorted_tasks[i].size_min, window_start);
936 }
937
938 // We only need the reason for being before if we don't already have
939 // a static precedence between the tasks.
940 const IntegerValue dist = helper_->GetCurrentMinDistanceBetweenTasks(
941 ct, t, /*add_reason_if_after=*/true);
942 if (dist >= 0) {
943 energy_of_task_before += sorted_tasks[i].size_min;
944 min_slack = std::min(min_slack, dist);
945 } else {
946 all_already_before = false;
947 helper_->AddStartMaxReason(ct, end_min_if_present - 1);
948 }
949 }
950
951 // We only need the end-min of t if not all the task are already known
952 // to be before.
953 IntegerValue new_start_min = task_set_end_min;
954 if (all_already_before) {
955 // We can actually push further!
956 // And we don't need other reason except the precedences.
957 new_start_min += min_slack;
958 } else {
959 helper_->AddEndMinReason(t, end_min_if_present);
960 }
961
962 // In some situation, we can push the task further.
963 // TODO(user): We can also reduce the reason in this case.
964 if (min_slack != kMaxIntegerValue &&
965 window_start + energy_of_task_before + min_slack > new_start_min) {
966 new_start_min = window_start + energy_of_task_before + min_slack;
967 }
968
969 // Process detected precedence.
970 if (helper_->CurrentDecisionLevel() == 0 && helper_->IsPresent(t)) {
971 for (int i = critical_index; i < sorted_tasks.size(); ++i) {
972 if (!helper_->PropagatePrecedence(sorted_tasks[i].task, t)) {
973 return false;
974 }
975 }
976 }
977
978 // This augment the start-min of t. Note that t is not in task set
979 // yet, so we will use this updated start if we ever add it there.
980 ++stats_.num_propagations;
981 if (!helper_->IncreaseStartMin(t, new_start_min)) {
982 return false;
983 }
984
985 return true;
986}
987
988bool DisjunctiveDetectablePrecedences::PropagateWithRanks() {
989 // We will "consume" tasks from here.
990 absl::Span<const TaskTime> task_by_increasing_end_min =
991 helper_->TaskByIncreasingEndMin();
992 absl::Span<const TaskTime> task_by_negated_start_max =
993 helper_->TaskByIncreasingNegatedStartMax();
994
995 // We will stop using ranks as soon as we propagated something. This allow to
996 // be sure this propagate as much as possible in a single pass and seems to
997 // help slightly.
998 int highest_rank = 0;
999 bool some_propagation = false;
1000
1001 // Invariant: need_update is false implies that task_set_end_min is equal to
1002 // task_set_.ComputeEndMin().
1003 //
1004 // TODO(user): Maybe it is just faster to merge ComputeEndMin() with
1005 // AddEntry().
1006 task_set_.Clear();
1007 to_add_.clear();
1008 IntegerValue task_set_end_min = kMinIntegerValue;
1009 for (; !task_by_increasing_end_min.empty();) {
1010 // Consider all task with a start_max < current_end_min.
1011 int blocking_task = -1;
1012 IntegerValue blocking_start_max;
1013 IntegerValue current_end_min = task_by_increasing_end_min.front().time;
1014
1015 for (; true; task_by_negated_start_max.remove_suffix(1)) {
1016 if (task_by_negated_start_max.empty()) {
1017 // Small optim: this allows to process all remaining task rather than
1018 // looping around are retesting this for all remaining tasks.
1019 current_end_min = kMaxIntegerValue;
1020 break;
1021 }
1022
1023 const auto [t, negated_start_max] = task_by_negated_start_max.back();
1024 const IntegerValue start_max = -negated_start_max;
1025 if (current_end_min <= start_max) break;
1026
1027 // If the task is not present, its rank will be -1.
1028 const int rank = ranks_[t];
1029 if (rank < highest_rank) continue;
1030 DCHECK(helper_->IsPresent(t));
1031
1032 // If t has a mandatory part, and extend further than current_end_min
1033 // then we can push it first. All tasks for which their push is delayed
1034 // are necessarily after this "blocking task".
1035 //
1036 // This idea is introduced in "Linear-Time Filtering Algorithms for the
1037 // Disjunctive Constraints" Hamed Fahimi, Claude-Guy Quimper.
1038 const IntegerValue end_min = helper_->EndMin(t);
1039 if (blocking_task == -1 && end_min >= current_end_min) {
1040 DCHECK_LT(start_max, end_min) << " task should have mandatory part: "
1041 << helper_->TaskDebugString(t);
1042 blocking_task = t;
1043 blocking_start_max = start_max;
1044 current_end_min = end_min;
1045 } else if (blocking_task != -1 && blocking_start_max < end_min) {
1046 // Conflict! the task is after the blocking_task but also before.
1047 helper_->ClearReason();
1048 helper_->AddPresenceReason(blocking_task);
1049 helper_->AddPresenceReason(t);
1050 helper_->AddReasonForBeingBefore(blocking_task, t);
1051 helper_->AddReasonForBeingBefore(t, blocking_task);
1052 return helper_->ReportConflict();
1053 } else {
1054 if (!some_propagation && rank > highest_rank) {
1055 to_add_.clear();
1056 task_set_.Clear();
1057 highest_rank = rank;
1058 }
1059 to_add_.push_back(t);
1060 }
1061 }
1062
1063 // If we have a blocking task. We need to propagate it first.
1064 if (blocking_task != -1) {
1065 DCHECK(!helper_->IsAbsent(blocking_task));
1066
1067 if (!to_add_.empty()) {
1068 for (const int t : to_add_) {
1069 task_set_.AddShiftedStartMinEntry(*helper_, t);
1070 }
1071 to_add_.clear();
1072 task_set_end_min = task_set_.ComputeEndMin();
1073 }
1074
1075 if (task_set_end_min > helper_->StartMin(blocking_task)) {
1076 some_propagation = true;
1077 if (!Push(task_set_end_min, blocking_task)) return false;
1078 }
1079
1080 // This propagators assumes that every push is reflected for its
1081 // correctness.
1082 if (helper_->InPropagationLoop()) return true;
1083
1084 // Insert the blocking_task. Note that because we just pushed it,
1085 // it will be last in task_set_ and also the only reason used to push
1086 // any of the subsequent tasks below. In particular, the reason will be
1087 // valid even though task_set might contains tasks with a start_max
1088 // greater or equal to the end_min of the task we push.
1089 if (!some_propagation && ranks_[blocking_task] > highest_rank) {
1090 to_add_.clear();
1091 task_set_.Clear();
1092 highest_rank = ranks_[blocking_task];
1093 }
1094 to_add_.push_back(blocking_task);
1095 }
1096
1097 // Lets propagate all task with end_min <= current_end_min that are also
1098 // after all the task in task_set_.
1099 for (; !task_by_increasing_end_min.empty();
1100 task_by_increasing_end_min.remove_prefix(1)) {
1101 const auto [t, end_min] = task_by_increasing_end_min.front();
1102 if (end_min > current_end_min) break;
1103 if (t == blocking_task) continue; // Already done.
1104
1105 if (!to_add_.empty()) {
1106 for (const int t : to_add_) {
1107 task_set_.AddShiftedStartMinEntry(*helper_, t);
1108 }
1109 to_add_.clear();
1110 task_set_end_min = task_set_.ComputeEndMin();
1111 }
1112
1113 // Lets propagate current_task.
1114 if (task_set_end_min > helper_->StartMin(t)) {
1115 // Corner case if a previous push caused a subsequent task to be absent.
1116 if (helper_->IsAbsent(t)) continue;
1117
1118 some_propagation = true;
1119 if (!Push(task_set_end_min, t)) return false;
1120 }
1121 }
1122 }
1123
1124 return true;
1125}
1126
1128 GenericLiteralWatcher* watcher) {
1129 const int id = watcher->Register(this);
1130 helper_->SetTimeDirection(time_direction_);
1131 helper_->WatchAllTasks(id);
1133 return id;
1134}
1135
1137 stats_.OnPropagate();
1138 if (!helper_->SynchronizeAndSetTimeDirection(time_direction_)) {
1139 ++stats_.num_conflicts;
1140 return false;
1141 }
1142 window_.clear();
1143
1144 // We only need to consider "critical" set of tasks given how we compute the
1145 // min-offset in PropagateSubwindow().
1146 IntegerValue window_end = kMinIntegerValue;
1147 for (const auto [task, presence_lit, start_min] :
1148 helper_->TaskByIncreasingShiftedStartMin()) {
1149 if (!helper_->IsPresent(presence_lit)) continue;
1150
1151 if (start_min < window_end) {
1152 window_.push_back({task, start_min});
1153 window_end += helper_->SizeMin(task);
1154 continue;
1155 }
1156
1157 if (window_.size() > 1 && !PropagateSubwindow()) {
1158 ++stats_.num_conflicts;
1159 return false;
1160 }
1161
1162 // Start of the next window.
1163 window_.clear();
1164 window_.push_back({task, start_min});
1165 window_end = start_min + helper_->SizeMin(task);
1166 }
1167 if (window_.size() > 1 && !PropagateSubwindow()) {
1168 ++stats_.num_conflicts;
1169 return false;
1170 }
1171
1172 stats_.EndWithoutConflicts();
1173 return true;
1174}
1175
1176bool DisjunctivePrecedences::PropagateSubwindow() {
1177 // TODO(user): We shouldn't consider ends for fixed intervals here. But
1178 // then we should do a better job of computing the min-end of a subset of
1179 // intervals from this disjunctive (like using fixed intervals even if there
1180 // is no "before that variable" relationship). Ex: If a variable is after two
1181 // intervals that cannot be both before a fixed one, we could propagate more.
1182 index_to_end_vars_.clear();
1183 int new_size = 0;
1184 for (const auto task_time : window_) {
1185 const int task = task_time.task_index;
1186 const AffineExpression& end_exp = helper_->Ends()[task];
1187
1188 // TODO(user): Handle generic affine relation?
1189 if (end_exp.var == kNoIntegerVariable || end_exp.coeff != 1) continue;
1190
1191 window_[new_size++] = task_time;
1192 index_to_end_vars_.push_back(end_exp.var);
1193 }
1194 window_.resize(new_size);
1195
1196 // Because we use the cached value in the window, we don't really care
1197 // on which order we process them.
1198 precedence_relations_->CollectPrecedences(index_to_end_vars_, &before_);
1199
1200 const int size = before_.size();
1201 for (int global_i = 0; global_i < size;) {
1202 const int global_start_i = global_i;
1203 const IntegerVariable var = before_[global_i].var;
1204 DCHECK_NE(var, kNoIntegerVariable);
1205
1206 // Decode the set of task before var.
1207 // Note that like in Propagate() we split this set of task into critical
1208 // subpart as there is no point considering them together.
1209 //
1210 // TODO(user): we should probably change the api to return a Span.
1211 //
1212 // TODO(user): If more than one set of task push the same variable, we
1213 // probabaly only want to keep the best push? Maybe we want to process them
1214 // in reverse order of what we do here?
1215 indices_before_.clear();
1216 IntegerValue local_start;
1217 IntegerValue local_end;
1218 for (; global_i < size; ++global_i) {
1219 const PrecedenceRelations::PrecedenceData& data = before_[global_i];
1220 if (data.var != var) break;
1221 const int index = data.index;
1222 const auto [t, start_of_t] = window_[index];
1223 if (global_i == global_start_i) { // First loop.
1224 local_start = start_of_t;
1225 local_end = local_start + helper_->SizeMin(t);
1226 } else {
1227 if (start_of_t >= local_end) break;
1228 local_end += helper_->SizeMin(t);
1229 }
1230 indices_before_.push_back(index);
1231 }
1232
1233 // No need to consider if we don't have at least two tasks before var.
1234 const int num_before = indices_before_.size();
1235 if (num_before < 2) continue;
1236 skip_.assign(num_before, false);
1237
1238 // Heuristic.
1239 // We will use the current end-min of all the task in indices_before_
1240 // to skip task with an offset not large enough.
1241 IntegerValue end_min_when_all_present = local_end;
1242
1243 // We will consider the end-min of all the subsets [i, num_items) to try to
1244 // push var using the min-offset between var and items of such subset. This
1245 // can be done in linear time by scanning from i = num_items - 1 to 0.
1246 //
1247 // Note that this needs the items in indices_before_ to be sorted by
1248 // their shifted start min (it should be the case).
1249 int best_index = -1;
1250 const IntegerValue current_var_lb = integer_trail_->LowerBound(var);
1251 IntegerValue best_new_lb = current_var_lb;
1252 IntegerValue min_offset = kMaxIntegerValue;
1253 IntegerValue sum_of_duration = 0;
1254 for (int i = num_before; --i >= 0;) {
1255 const TaskTime task_time = window_[indices_before_[i]];
1256 const AffineExpression& end_exp = helper_->Ends()[task_time.task_index];
1257
1258 // TODO(user): The hash lookup here is a bit slow, so we avoid fetching
1259 // the offset as much as possible. Note that the alternative of storing it
1260 // in PrecedenceData is not necessarily better and harder to update as we
1261 // dive/backtrack.
1262 const IntegerValue inner_offset =
1263 precedence_relations_->GetConditionalOffset(end_exp.var, var);
1264 DCHECK_NE(inner_offset, kMinIntegerValue);
1265
1266 // We have var >= end_exp.var + inner_offset, so
1267 // var >= (end_exp.var + end_exp.constant)
1268 // + (inner_offset - end_exp.constant)
1269 // var >= task end + offset.
1270 const IntegerValue offset = inner_offset - end_exp.constant;
1271
1272 // Heuristic: do not consider this relations if its offset is clearly bad.
1273 const IntegerValue task_size = helper_->SizeMin(task_time.task_index);
1274 if (end_min_when_all_present + offset <= best_new_lb) {
1275 // This is true if we skipped all task so far in this block.
1276 if (min_offset == kMaxIntegerValue) {
1277 // If only one task is left, we can abort.
1278 // This avoid a GetConditionalOffset() lookup.
1279 if (i == 1) break;
1280
1281 // Lower the end_min_when_all_present for better filtering later.
1282 end_min_when_all_present -= task_size;
1283 }
1284
1285 skip_[i] = true;
1286 continue;
1287 }
1288
1289 // Add this task to the current subset and compute the new bound.
1290 min_offset = std::min(min_offset, offset);
1291 sum_of_duration += task_size;
1292 const IntegerValue start = task_time.time;
1293 const IntegerValue new_lb = start + sum_of_duration + min_offset;
1294 if (new_lb > best_new_lb) {
1295 best_new_lb = new_lb;
1296 best_index = i;
1297 }
1298 }
1299
1300 // Push?
1301 if (best_new_lb > current_var_lb) {
1302 DCHECK_NE(best_index, -1);
1303 helper_->ClearReason();
1304 const IntegerValue window_start =
1305 window_[indices_before_[best_index]].time;
1306 for (int i = best_index; i < num_before; ++i) {
1307 if (skip_[i]) continue;
1308 const int ct = window_[indices_before_[i]].task_index;
1309 helper_->AddPresenceReason(ct);
1310 helper_->AddEnergyAfterReason(ct, helper_->SizeMin(ct), window_start);
1311
1312 // Fetch the explanation.
1313 // This is okay if a bit slow since we only do that when we push.
1314 const AffineExpression& end_exp = helper_->Ends()[ct];
1315 for (const Literal l :
1316 precedence_relations_->GetConditionalEnforcements(end_exp.var,
1317 var)) {
1318 helper_->MutableLiteralReason()->push_back(l.Negated());
1319 }
1320 }
1321 ++stats_.num_propagations;
1322 if (!helper_->PushIntegerLiteral(
1323 IntegerLiteral::GreaterOrEqual(var, best_new_lb))) {
1324 return false;
1325 }
1326 }
1327 }
1328 return true;
1329}
1330
1332 // This propagator reach the fixed point in one go.
1333 // Maybe not in corner cases, but since it is expansive, it is okay not to
1334 // run it again right away
1335 //
1336 // Note also that technically, we don't need to be waked up if only the upper
1337 // bound of the task changes, but this require to use more memory and the gain
1338 // is unclear as this runs with the highest priority.
1339 const int id = watcher->Register(this);
1340 helper_->SetTimeDirection(time_direction_);
1341 helper_->WatchAllTasks(id);
1342 return id;
1343}
1344
1346 stats_.OnPropagate();
1347 if (!helper_->SynchronizeAndSetTimeDirection(time_direction_)) {
1348 ++stats_.num_conflicts;
1349 return false;
1350 }
1351
1352 const auto task_by_negated_start_max =
1353 helper_->TaskByIncreasingNegatedStartMax();
1354 const auto task_by_increasing_shifted_start_min =
1355 helper_->TaskByIncreasingShiftedStartMin();
1356
1357 // Split problem into independent part.
1358 //
1359 // The situation is trickier here, and we use two windows:
1360 // - The classical "start_min_window_" as in the other propagator.
1361 // - A second window, that includes all the task with a start_max inside
1362 // [window_start, window_end].
1363 //
1364 // Now, a task from the second window can be detected to be "not last" by only
1365 // looking at the task in the first window. Tasks to the left do not cause
1366 // issue for the task to be last, and tasks to the right will not lower the
1367 // end-min of the task under consideration.
1368 int queue_index = task_by_negated_start_max.size() - 1;
1369 const int num_tasks = task_by_increasing_shifted_start_min.size();
1370 for (int i = 0; i < num_tasks;) {
1371 start_min_window_.clear();
1372 IntegerValue window_end = kMinIntegerValue;
1373 for (; i < num_tasks; ++i) {
1374 const auto [task, presence_lit, start_min] =
1375 task_by_increasing_shifted_start_min[i];
1376 if (!helper_->IsPresent(presence_lit)) continue;
1377
1378 if (start_min_window_.empty()) {
1379 start_min_window_.push_back({task, start_min});
1380 window_end = start_min + helper_->SizeMin(task);
1381 } else if (start_min < window_end) {
1382 start_min_window_.push_back({task, start_min});
1383 window_end += helper_->SizeMin(task);
1384 } else {
1385 break;
1386 }
1387 }
1388
1389 // Add to start_max_window_ all the task whose start_max
1390 // fall into [window_start, window_end).
1391 start_max_window_.clear();
1392 for (; queue_index >= 0; queue_index--) {
1393 const auto [t, negated_start_max] =
1394 task_by_negated_start_max[queue_index];
1395 const IntegerValue start_max = -negated_start_max;
1396
1397 // Note that we add task whose presence is still unknown here.
1398 if (start_max >= window_end) break;
1399 if (helper_->IsAbsent(t)) continue;
1400 start_max_window_.push_back({t, start_max});
1401 }
1402
1403 // If this is the case, we cannot propagate more than the detectable
1404 // precedence propagator. Note that this continue must happen after we
1405 // computed start_max_window_ though.
1406 if (start_min_window_.size() <= 1) continue;
1407
1408 // Process current window.
1409 if (!start_max_window_.empty() && !PropagateSubwindow()) {
1410 ++stats_.num_conflicts;
1411 return false;
1412 }
1413 }
1414
1415 stats_.EndWithoutConflicts();
1416 return true;
1417}
1418
1419bool DisjunctiveNotLast::PropagateSubwindow() {
1420 auto& task_by_increasing_end_max = start_max_window_;
1421 for (TaskTime& entry : task_by_increasing_end_max) {
1422 entry.time = helper_->EndMax(entry.task_index);
1423 }
1424 IncrementalSort(task_by_increasing_end_max.begin(),
1425 task_by_increasing_end_max.end());
1426
1427 const IntegerValue threshold = task_by_increasing_end_max.back().time;
1428 auto& task_by_increasing_start_max = start_min_window_;
1429 int queue_size = 0;
1430 for (const TaskTime entry : task_by_increasing_start_max) {
1431 const int task = entry.task_index;
1432 const IntegerValue start_max = helper_->StartMax(task);
1433 DCHECK(helper_->IsPresent(task));
1434 if (start_max < threshold) {
1435 task_by_increasing_start_max[queue_size++] = {task, start_max};
1436 }
1437 }
1438
1439 // If the size is one, we cannot propagate more than the detectable precedence
1440 // propagator.
1441 if (queue_size <= 1) return true;
1442
1443 task_by_increasing_start_max.resize(queue_size);
1444 std::sort(task_by_increasing_start_max.begin(),
1445 task_by_increasing_start_max.end());
1446
1447 task_set_.Clear();
1448 int queue_index = 0;
1449 for (const auto task_time : task_by_increasing_end_max) {
1450 const int t = task_time.task_index;
1451 const IntegerValue end_max = task_time.time;
1452
1453 // We filtered absent task before, but it is possible that as we push
1454 // bounds of optional tasks, more task become absent.
1455 if (helper_->IsAbsent(t)) continue;
1456
1457 // task_set_ contains all the tasks that must start before the end-max of t.
1458 // These are the only candidates that have a chance to decrease the end-max
1459 // of t.
1460 while (queue_index < queue_size) {
1461 const auto to_insert = task_by_increasing_start_max[queue_index];
1462 const IntegerValue start_max = to_insert.time;
1463 if (end_max <= start_max) break;
1464
1465 const int task_index = to_insert.task_index;
1466 DCHECK(helper_->IsPresent(task_index));
1467 task_set_.AddEntry({task_index, helper_->ShiftedStartMin(task_index),
1468 helper_->SizeMin(task_index)});
1469 ++queue_index;
1470 }
1471
1472 // In the following case, task t cannot be after all the critical tasks
1473 // (i.e. it cannot be last):
1474 //
1475 // [(critical tasks)
1476 // | <- t start-max
1477 //
1478 // So we can deduce that the end-max of t is smaller than or equal to the
1479 // largest start-max of the critical tasks.
1480 //
1481 // Note that this works as well when the presence of t is still unknown.
1482 int critical_index = 0;
1483 const IntegerValue end_min_of_critical_tasks =
1484 task_set_.ComputeEndMin(/*task_to_ignore=*/t, &critical_index);
1485 if (end_min_of_critical_tasks <= helper_->StartMax(t)) continue;
1486
1487 // Find the largest start-max of the critical tasks (excluding t). The
1488 // end-max for t need to be smaller than or equal to this.
1489 IntegerValue largest_ct_start_max = kMinIntegerValue;
1490 const absl::Span<const TaskSet::Entry> sorted_tasks =
1491 task_set_.SortedTasks();
1492 const int sorted_tasks_size = sorted_tasks.size();
1493 for (int i = critical_index; i < sorted_tasks_size; ++i) {
1494 const int ct = sorted_tasks[i].task;
1495 if (t == ct) continue;
1496 const IntegerValue start_max = helper_->StartMax(ct);
1497
1498 // If we already known that t is after ct we can have a tighter start-max.
1499 if (start_max > largest_ct_start_max &&
1500 helper_->GetCurrentMinDistanceBetweenTasks(ct, t) < 0) {
1501 largest_ct_start_max = start_max;
1502 }
1503 }
1504
1505 // If we have any critical task, the test will always be true because
1506 // of the tasks we put in task_set_.
1507 DCHECK(largest_ct_start_max == kMinIntegerValue ||
1508 end_max > largest_ct_start_max);
1509 if (end_max > largest_ct_start_max) {
1510 helper_->ClearReason();
1511
1512 const IntegerValue window_start = sorted_tasks[critical_index].start_min;
1513 for (int i = critical_index; i < sorted_tasks_size; ++i) {
1514 const int ct = sorted_tasks[i].task;
1515 if (ct == t) continue;
1516 helper_->AddPresenceReason(ct);
1517 helper_->AddEnergyAfterReason(ct, sorted_tasks[i].size_min,
1518 window_start);
1519 if (helper_->GetCurrentMinDistanceBetweenTasks(
1520 ct, t, /*add_reason_if_after=*/true) < 0) {
1521 helper_->AddStartMaxReason(ct, largest_ct_start_max);
1522 }
1523 }
1524
1525 // Add the reason for t, we only need the start-max.
1526 helper_->AddStartMaxReason(t, end_min_of_critical_tasks - 1);
1527
1528 // If largest_ct_start_max == kMinIntegerValue, we have a conflict. To
1529 // avoid integer overflow, we report it directly. This might happen
1530 // because the task is known to be after all the other, and thus it cannot
1531 // be "not last".
1532 if (largest_ct_start_max == kMinIntegerValue) {
1533 return helper_->ReportConflict();
1534 }
1535
1536 // Enqueue the new end-max for t.
1537 // Note that changing it will not influence the rest of the loop.
1538 ++stats_.num_propagations;
1539 if (!helper_->DecreaseEndMax(t, largest_ct_start_max)) return false;
1540 }
1541 }
1542 return true;
1543}
1544
1546 const int id = watcher->Register(this);
1547 helper_->WatchAllTasks(id);
1549 return id;
1550}
1551
1553 stats_.OnPropagate();
1554 const int num_tasks = helper_->NumTasks();
1555 if (!helper_->SynchronizeAndSetTimeDirection(time_direction_)) {
1556 ++stats_.num_conflicts;
1557 return false;
1558 }
1559 is_gray_.resize(num_tasks, false);
1560 non_gray_task_to_event_.resize(num_tasks);
1561
1562 window_.clear();
1563 IntegerValue window_end = kMinIntegerValue;
1564 for (const auto [task, presence_lit, shifted_smin] :
1565 helper_->TaskByIncreasingShiftedStartMin()) {
1566 if (helper_->IsAbsent(presence_lit)) continue;
1567
1568 // Note that we use the real start min here not the shifted one. This is
1569 // because we might be able to push it if it is smaller than window end.
1570 if (helper_->StartMin(task) < window_end) {
1571 window_.push_back({task, shifted_smin});
1572 window_end += helper_->SizeMin(task);
1573 continue;
1574 }
1575
1576 // We need at least 3 tasks for the edge-finding to be different from
1577 // detectable precedences.
1578 if (window_.size() > 2 && !PropagateSubwindow(window_end)) {
1579 ++stats_.num_conflicts;
1580 return false;
1581 }
1582
1583 // Corner case: The propagation of the previous window might have made the
1584 // current task absent even if it wasn't at the loop beginning.
1585 if (helper_->IsAbsent(presence_lit)) {
1586 window_.clear();
1587 window_end = kMinIntegerValue;
1588 continue;
1589 }
1590
1591 // Start of the next window.
1592 window_.clear();
1593 window_.push_back({task, shifted_smin});
1594 window_end = shifted_smin + helper_->SizeMin(task);
1595 }
1596 if (window_.size() > 2 && !PropagateSubwindow(window_end)) {
1597 ++stats_.num_conflicts;
1598 return false;
1599 }
1600
1601 stats_.EndWithoutConflicts();
1602 return true;
1603}
1604
1605bool DisjunctiveEdgeFinding::PropagateSubwindow(IntegerValue window_end_min) {
1606 // Cache the task end-max and abort early if possible.
1607 task_by_increasing_end_max_.clear();
1608 for (const auto task_time : window_) {
1609 const int task = task_time.task_index;
1610 DCHECK(!helper_->IsAbsent(task));
1611
1612 // We already mark all the non-present task as gray.
1613 //
1614 // Same for task with an end-max that is too large: Tasks that are not
1615 // present can never trigger propagation or an overload checking failure.
1616 // theta_tree_.GetOptionalEnvelope() is always <= window_end, so tasks whose
1617 // end_max is >= window_end can never trigger propagation or failure either.
1618 // Thus, those tasks can be marked as gray, which removes their contribution
1619 // to theta right away.
1620 const IntegerValue end_max = helper_->EndMax(task);
1621 if (helper_->IsPresent(task) && end_max < window_end_min) {
1622 is_gray_[task] = false;
1623 task_by_increasing_end_max_.push_back({task, end_max});
1624 } else {
1625 is_gray_[task] = true;
1626 }
1627 }
1628
1629 // If we have just 1 non-gray task, then this propagator does not propagate
1630 // more than the detectable precedences, so we abort early.
1631 if (task_by_increasing_end_max_.size() < 2) return true;
1632 std::sort(task_by_increasing_end_max_.begin(),
1633 task_by_increasing_end_max_.end());
1634
1635 // Set up theta tree.
1636 //
1637 // Some task in the theta tree will be considered "gray".
1638 // When computing the end-min of the sorted task, we will compute it for:
1639 // - All the non-gray task
1640 // - All the non-gray task + at most one gray task.
1641 //
1642 // TODO(user): it should be faster to initialize it all at once rather
1643 // than calling AddOrUpdate() n times.
1644 const int window_size = window_.size();
1645 event_size_.clear();
1646 theta_tree_.Reset(window_size);
1647 for (int event = 0; event < window_size; ++event) {
1648 const TaskTime task_time = window_[event];
1649 const int task = task_time.task_index;
1650 const IntegerValue energy_min = helper_->SizeMin(task);
1651 event_size_.push_back(energy_min);
1652 if (is_gray_[task]) {
1653 theta_tree_.AddOrUpdateOptionalEvent(event, task_time.time, energy_min);
1654 } else {
1655 non_gray_task_to_event_[task] = event;
1656 theta_tree_.AddOrUpdateEvent(event, task_time.time, energy_min,
1657 energy_min);
1658 }
1659 }
1660
1661 // At each iteration we either transform a non-gray task into a gray one or
1662 // remove a gray task, so this loop is linear in complexity.
1663 while (true) {
1664 DCHECK(!is_gray_[task_by_increasing_end_max_.back().task_index]);
1665 const IntegerValue non_gray_end_max =
1666 task_by_increasing_end_max_.back().time;
1667
1668 // Overload checking.
1669 const IntegerValue non_gray_end_min = theta_tree_.GetEnvelope();
1670 if (non_gray_end_min > non_gray_end_max) {
1671 helper_->ClearReason();
1672
1673 // We need the reasons for the critical tasks to fall in:
1674 const int critical_event =
1675 theta_tree_.GetMaxEventWithEnvelopeGreaterThan(non_gray_end_max);
1676 const IntegerValue window_start = window_[critical_event].time;
1677 const IntegerValue window_end =
1678 theta_tree_.GetEnvelopeOf(critical_event) - 1;
1679 for (int event = critical_event; event < window_size; event++) {
1680 const int task = window_[event].task_index;
1681 if (is_gray_[task]) continue;
1682 helper_->AddPresenceReason(task);
1683 helper_->AddEnergyAfterReason(task, event_size_[event], window_start);
1684 helper_->AddEndMaxReason(task, window_end);
1685 }
1686 return helper_->ReportConflict();
1687 }
1688
1689 // Edge-finding.
1690 // If we have a situation like:
1691 // [(critical_task_with_gray_task)
1692 // ]
1693 // ^ end-max without the gray task.
1694 //
1695 // Then the gray task must be after all the critical tasks (all the non-gray
1696 // tasks in the tree actually), otherwise there will be no way to schedule
1697 // the critical_tasks inside their time window.
1698 while (theta_tree_.GetOptionalEnvelope() > non_gray_end_max) {
1699 const IntegerValue end_min_with_gray = theta_tree_.GetOptionalEnvelope();
1700 int critical_event_with_gray;
1701 int gray_event;
1702 IntegerValue available_energy;
1703 theta_tree_.GetEventsWithOptionalEnvelopeGreaterThan(
1704 non_gray_end_max, &critical_event_with_gray, &gray_event,
1705 &available_energy);
1706 const int gray_task = window_[gray_event].task_index;
1707 DCHECK(is_gray_[gray_task]);
1708
1709 // This might happen in the corner case where more than one interval are
1710 // controlled by the same Boolean.
1711 if (helper_->IsAbsent(gray_task)) {
1712 theta_tree_.RemoveEvent(gray_event);
1713 continue;
1714 }
1715
1716 // Since the gray task is after all the other, we have a new lower bound.
1717 if (helper_->StartMin(gray_task) < non_gray_end_min) {
1718 // The API is not ideal here. We just want the start of the critical
1719 // tasks that explain the non_gray_end_min computed above.
1720 const int critical_event =
1721 theta_tree_.GetMaxEventWithEnvelopeGreaterThan(non_gray_end_min -
1722 1);
1723
1724 // Even if we need less task to explain the overload, because we are
1725 // going to explain the full non_gray_end_min, we can relax the
1726 // explanation.
1727 if (critical_event_with_gray > critical_event) {
1728 critical_event_with_gray = critical_event;
1729 }
1730
1731 const int first_event = critical_event_with_gray;
1732 const int second_event = critical_event;
1733 const IntegerValue first_start = window_[first_event].time;
1734 const IntegerValue second_start = window_[second_event].time;
1735
1736 // window_end is chosen to be has big as possible and still have an
1737 // overload if the gray task is not last.
1738 //
1739 // If gray_task is with variable duration, it is possible that it must
1740 // be last just because is end-min is large.
1741 bool use_energy_reason = true;
1742 IntegerValue window_end;
1743 if (helper_->EndMin(gray_task) > non_gray_end_max) {
1744 // This is actually a form of detectable precedence.
1745 //
1746 // TODO(user): We could relax the reason more, but this happen really
1747 // rarely it seems.
1748 use_energy_reason = false;
1749 window_end = helper_->EndMin(gray_task) - 1;
1750 } else {
1751 window_end = end_min_with_gray - 1;
1752 }
1753 CHECK_GE(window_end, non_gray_end_max);
1754
1755 // The non-gray part of the explanation as detailed above.
1756 helper_->ClearReason();
1757 bool all_before = true;
1758 for (int event = first_event; event < window_size; event++) {
1759 const int task = window_[event].task_index;
1760 if (is_gray_[task]) continue;
1761 helper_->AddPresenceReason(task);
1762 helper_->AddEnergyAfterReason(
1763 task, event_size_[event],
1764 event >= second_event ? second_start : first_start);
1765
1766 const IntegerValue dist = helper_->GetCurrentMinDistanceBetweenTasks(
1767 task, gray_task, /*add_reason_if_after=*/true);
1768 if (dist < 0) {
1769 all_before = false;
1770 helper_->AddEndMaxReason(task, window_end);
1771 }
1772 }
1773
1774 // Add the reason for the gray_task (we don't need the end-max or
1775 // presence reason) needed for the precedences.
1776 if (!all_before) {
1777 if (use_energy_reason) {
1778 helper_->AddEnergyAfterReason(gray_task, event_size_[gray_event],
1779 first_start);
1780 } else {
1781 helper_->AddEndMinReason(gray_task, helper_->EndMin(gray_task));
1782 }
1783 }
1784
1785 // Process detected precedence.
1786 if (helper_->CurrentDecisionLevel() == 0 &&
1787 helper_->IsPresent(gray_task)) {
1788 for (int i = first_event; i < window_size; ++i) {
1789 const int task = window_[i].task_index;
1790 if (!is_gray_[task]) {
1791 if (!helper_->PropagatePrecedence(task, gray_task)) {
1792 return false;
1793 }
1794 }
1795 }
1796 }
1797
1798 // Enqueue the new start-min for gray_task.
1799 //
1800 // TODO(user): propagate the precedence Boolean here too? I think it
1801 // will be more powerful. Even if eventually all these precedence will
1802 // become detectable (see Petr Villim PhD).
1803 ++stats_.num_propagations;
1804 if (!helper_->IncreaseStartMin(gray_task, non_gray_end_min)) {
1805 return false;
1806 }
1807 }
1808
1809 // Remove the gray_task.
1810 theta_tree_.RemoveEvent(gray_event);
1811 }
1812
1813 // Stop before we get just one non-gray task.
1814 if (task_by_increasing_end_max_.size() <= 2) break;
1815
1816 // Stop if the min of end_max is too big.
1817 if (task_by_increasing_end_max_[0].time >=
1818 theta_tree_.GetOptionalEnvelope()) {
1819 break;
1820 }
1821
1822 // Make the non-gray task with larger end-max gray.
1823 const int new_gray_task = task_by_increasing_end_max_.back().task_index;
1824 task_by_increasing_end_max_.pop_back();
1825 const int new_gray_event = non_gray_task_to_event_[new_gray_task];
1826 DCHECK(!is_gray_[new_gray_task]);
1827 is_gray_[new_gray_task] = true;
1828 theta_tree_.AddOrUpdateOptionalEvent(new_gray_event,
1829 window_[new_gray_event].time,
1830 event_size_[new_gray_event]);
1831 }
1832
1833 return true;
1834}
1835
1837 const int id = watcher->Register(this);
1838 helper_->SetTimeDirection(time_direction_);
1839 helper_->WatchAllTasks(id);
1841 return id;
1842}
1843
1844} // namespace sat
1845} // namespace operations_research
void AddNoOverlap(absl::Span< const IntervalVariable > var)
int RegisterWith(GenericLiteralWatcher *watcher)
int RegisterWith(GenericLiteralWatcher *watcher)
int RegisterWith(GenericLiteralWatcher *watcher)
int RegisterWith(GenericLiteralWatcher *watcher)
int RegisterWith(GenericLiteralWatcher *watcher)
int RegisterWith(GenericLiteralWatcher *watcher)
int Register(PropagatorInterface *propagator)
Registers a propagator and returns its unique ids.
Definition integer.cc:2327
SchedulingDemandHelper * GetOrCreateDemandHelper(SchedulingConstraintHelper *helper, absl::Span< const AffineExpression > demands)
Definition intervals.cc:305
AffineExpression Start(IntervalVariable i) const
Definition intervals.h:98
bool IsOptional(IntervalVariable i) const
Returns whether or not a interval is optional and the associated literal.
Definition intervals.h:75
IntegerValue MaxSize(IntervalVariable i) const
Return the maximum size of the given IntervalVariable.
Definition intervals.h:107
IntegerValue MinSize(IntervalVariable i) const
Return the minimum size of the given IntervalVariable.
Definition intervals.h:102
SchedulingConstraintHelper * GetOrCreateHelper(const std::vector< IntervalVariable > &variables, bool register_as_disjunctive_helper=false)
Definition intervals.cc:219
T Add(std::function< T(Model *)> f)
Definition model.h:87
void CollectPrecedences(absl::Span< const IntegerVariable > vars, std::vector< PrecedenceData > *output)
void AddReasonForBeingBefore(int before, int after)
Produces a relaxed reason for StartMax(before) < EndMin(after).
void AddEnergyAfterReason(int t, IntegerValue energy_min, IntegerValue time)
absl::Span< const AffineExpression > Ends() const
ABSL_MUST_USE_RESULT bool IncreaseStartMin(int t, IntegerValue value)
void AddEndMinReason(int t, IntegerValue lower_bound)
void ClearReason()
Functions to clear and then set the current reason.
IntegerValue GetCurrentMinDistanceBetweenTasks(int a, int b, bool add_reason_if_after=false)
void NotifyEntryIsNowLastIfPresent(const Entry &e)
IntegerValue ComputeEndMin() const
absl::Span< const Entry > SortedTasks() const
void AddShiftedStartMinEntry(const SchedulingConstraintHelper &helper, int t)
void RegisterWith(GenericLiteralWatcher *watcher)
Definition timetable.cc:344
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
void AddDisjunctive(const std::vector< IntervalVariable > &intervals, Model *model)
std::function< void(Model *)> AllDifferentOnBounds(const std::vector< AffineExpression > &expressions)
void AddDisjunctiveWithBooleanPrecedencesOnly(absl::Span< const IntervalVariable > intervals, Model *model)
In SWIG mode, we don't want anything besides these top-level includes.
void IncrementalSort(int max_comparisons, Iterator begin, Iterator end, Compare comp=Compare{}, bool is_stable=false)
Definition sort.h:46
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)