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