Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
scheduling_helpers.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 <functional>
18#include <string>
19#include <utility>
20#include <vector>
21
22#include "absl/log/check.h"
23#include "absl/strings/str_cat.h"
24#include "absl/types/span.h"
28#include "ortools/sat/integer.h"
32#include "ortools/sat/model.h"
36#include "ortools/util/sort.h"
38
39namespace operations_research {
40namespace sat {
41
43 std::vector<AffineExpression> starts, std::vector<AffineExpression> ends,
44 std::vector<AffineExpression> sizes,
45 std::vector<LiteralIndex> reason_for_presence, Model* model)
46 : model_(model),
47 sat_solver_(model->GetOrCreate<SatSolver>()),
48 assignment_(sat_solver_->Assignment()),
49 trail_(model->GetOrCreate<Trail>()),
50 integer_trail_(model->GetOrCreate<IntegerTrail>()),
51 watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
52 linear2_bounds_(model->GetOrCreate<Linear2Bounds>()),
53 root_level_lin2_bounds_(model->GetOrCreate<RootLevelLinear2Bounds>()),
54 enforcement_helper_(*model->GetOrCreate<EnforcementHelper>()),
55 enforcement_id_(-1),
56 starts_(std::move(starts)),
57 ends_(std::move(ends)),
58 sizes_(std::move(sizes)),
59 reason_for_presence_(std::move(reason_for_presence)),
60 capacity_(starts_.size()),
61 cached_size_min_(new IntegerValue[capacity_]),
62 cached_start_min_(new IntegerValue[capacity_]),
63 cached_end_min_(new IntegerValue[capacity_]),
64 cached_negated_start_max_(new IntegerValue[capacity_]),
65 cached_negated_end_max_(new IntegerValue[capacity_]),
66 cached_shifted_start_min_(new IntegerValue[capacity_]),
67 cached_negated_shifted_end_max_(new IntegerValue[capacity_]) {
68 DCHECK_EQ(starts_.size(), ends_.size());
69 DCHECK_EQ(starts_.size(), sizes_.size());
70 DCHECK_EQ(starts_.size(), reason_for_presence_.size());
71
72 minus_starts_.clear();
73 minus_starts_.reserve(starts_.size());
74 minus_ends_.clear();
75 minus_ends_.reserve(starts_.size());
76 for (int i = 0; i < starts_.size(); ++i) {
77 minus_starts_.push_back(starts_[i].Negated());
78 minus_ends_.push_back(ends_[i].Negated());
79 }
80
81 InitSortedVectors();
83 model->GetOrCreate<SatSolver>()->NotifyThatModelIsUnsat();
84 }
85}
86
88 Model* model)
89 : model_(model),
90 sat_solver_(model->GetOrCreate<SatSolver>()),
91 assignment_(sat_solver_->Assignment()),
92 trail_(model->GetOrCreate<Trail>()),
93 integer_trail_(model->GetOrCreate<IntegerTrail>()),
94 linear2_bounds_(model->GetOrCreate<Linear2Bounds>()),
95 root_level_lin2_bounds_(model->GetOrCreate<RootLevelLinear2Bounds>()),
96 enforcement_helper_(*model->GetOrCreate<EnforcementHelper>()),
97 enforcement_id_(-1),
98 capacity_(num_tasks),
99 cached_size_min_(new IntegerValue[capacity_]),
100 cached_start_min_(new IntegerValue[capacity_]),
101 cached_end_min_(new IntegerValue[capacity_]),
102 cached_negated_start_max_(new IntegerValue[capacity_]),
103 cached_negated_end_max_(new IntegerValue[capacity_]),
104 cached_shifted_start_min_(new IntegerValue[capacity_]),
105 cached_negated_shifted_end_max_(new IntegerValue[capacity_]) {
106 starts_.resize(num_tasks);
107 CHECK_EQ(NumTasks(), num_tasks);
108}
109
111 return enforcement_helper_.Status(enforcement_id_) ==
113}
114
116 if (!IsEnforced()) return true;
117 recompute_all_cache_ = true;
118 for (const int id : propagator_ids_) watcher_->CallOnNextPropagate(id);
119 return true;
120}
121
123 const std::vector<int>& watch_indices) {
124 if (!IsEnforced()) return true;
125 for (const int t : watch_indices) recompute_cache_.Set(t);
126 for (const int id : propagator_ids_) watcher_->CallOnNextPropagate(id);
127 return true;
128}
129
131 GenericLiteralWatcher* watcher,
132 absl::Span<const Literal> enforcement_literals) {
133 const int id = watcher->Register(this);
134 const int num_tasks = starts_.size();
135 for (int t = 0; t < num_tasks; ++t) {
136 watcher->WatchIntegerVariable(sizes_[t].var, id, t);
137 watcher->WatchIntegerVariable(starts_[t].var, id, t);
138 watcher->WatchIntegerVariable(ends_[t].var, id, t);
139
140 // This class do not need to be waked up on presence change, since this is
141 // not cached. However given that we can have many propagators that use the
142 // same helper, it is nicer to only register this one, and wake up all
143 // propagator through it rather than registering all of them individually.
144 // Note that IncrementalPropagate() will do nothing if this is the only
145 // change except waking up registered propagators.
146 if (!IsPresent(t) && !IsAbsent(t)) {
147 watcher->WatchLiteral(Literal(reason_for_presence_[t]), id);
148 }
149 }
150 watcher->SetPropagatorPriority(id, 0);
151 enforcement_id_ =
152 enforcement_helper_.Register(enforcement_literals, watcher, id);
153}
154
155bool SchedulingConstraintHelper::UpdateCachedValues(int t) {
156 if (IsAbsent(t)) return true;
157
158 IntegerValue smin = integer_trail_->LowerBound(starts_[t]);
159 IntegerValue smax = integer_trail_->UpperBound(starts_[t]);
160 IntegerValue emin = integer_trail_->LowerBound(ends_[t]);
161 IntegerValue emax = integer_trail_->UpperBound(ends_[t]);
162
163 // We take the max for the corner case where the size of an optional interval
164 // is used elsewhere and has a domain with negative value.
165 //
166 // TODO(user): maybe we should just disallow size with a negative domain, but
167 // is is harder to enforce if we have a linear expression for size.
168 IntegerValue dmin =
169 std::max(IntegerValue(0), integer_trail_->LowerBound(sizes_[t]));
170 IntegerValue dmax = integer_trail_->UpperBound(sizes_[t]);
171
172 // Detect first if we have a conflict using the relation start + size = end.
173 if (dmax < 0) {
174 ResetReason();
175 AddSizeMaxReason(t, dmax);
176 return PushTaskAbsence(t);
177 }
178 if (smin + dmin - emax > 0) {
179 ResetReason();
180 AddStartMinReason(t, smin);
181 AddSizeMinReason(t, dmin);
182 AddEndMaxReason(t, emax);
183 return PushTaskAbsence(t);
184 }
185 if (smax + dmax - emin < 0) {
186 ResetReason();
187 AddStartMaxReason(t, smax);
188 AddSizeMaxReason(t, dmax);
189 AddEndMinReason(t, emin);
190 return PushTaskAbsence(t);
191 }
192
193 // Sometimes, for optional interval with non-optional bounds, this propagation
194 // give tighter bounds. We always consider the value assuming
195 // the interval is present.
196 //
197 // Note that this is also useful in case not everything was propagated. Note
198 // also that since there is no conflict, we reach the fix point in one pass.
199 smin = std::max(smin, emin - dmax);
200 smax = std::min(smax, emax - dmin);
201 dmin = std::max(dmin, emin - smax);
202 emin = std::max(emin, smin + dmin);
203 emax = std::min(emax, smax + dmax);
204
205 if (emin != cached_end_min_[t]) {
206 recompute_energy_profile_ = true;
207 }
208
209 // We might only want to do that if the value changed, but I am not sure it
210 // is worth the test.
211 recompute_by_start_max_ = true;
212 recompute_by_end_min_ = true;
213
214 cached_start_min_[t] = smin;
215 cached_end_min_[t] = emin;
216 cached_negated_start_max_[t] = -smax;
217 cached_negated_end_max_[t] = -emax;
218 cached_size_min_[t] = dmin;
219
220 // Note that we use the cached value here for EndMin()/StartMax().
221 const IntegerValue new_shifted_start_min = emin - dmin;
222 if (new_shifted_start_min != cached_shifted_start_min_[t]) {
223 recompute_energy_profile_ = true;
224 recompute_shifted_start_min_ = true;
225 cached_shifted_start_min_[t] = new_shifted_start_min;
226 }
227 const IntegerValue new_negated_shifted_end_max = -(smax + dmin);
228 if (new_negated_shifted_end_max != cached_negated_shifted_end_max_[t]) {
229 recompute_negated_shifted_end_max_ = true;
230 cached_negated_shifted_end_max_[t] = new_negated_shifted_end_max;
231 }
232 return true;
233}
234
236 const SchedulingConstraintHelper& other, absl::Span<const int> tasks) {
237 current_time_direction_ = other.current_time_direction_;
238
239 const int num_tasks = tasks.size();
240 CHECK_LE(num_tasks, capacity_);
241 starts_.resize(num_tasks);
242 ends_.resize(num_tasks);
243 minus_ends_.resize(num_tasks);
244 minus_starts_.resize(num_tasks);
245 sizes_.resize(num_tasks);
246 reason_for_presence_.resize(num_tasks);
247 for (int i = 0; i < num_tasks; ++i) {
248 const int t = tasks[i];
249 starts_[i] = other.starts_[t];
250 ends_[i] = other.ends_[t];
251 minus_ends_[i] = other.minus_ends_[t];
252 minus_starts_[i] = other.minus_starts_[t];
253 sizes_[i] = other.sizes_[t];
254 reason_for_presence_[i] = other.reason_for_presence_[t];
255 }
256
257 InitSortedVectors();
259}
260
261void SchedulingConstraintHelper::InitSortedVectors() {
262 const int num_tasks = starts_.size();
263
264 recompute_all_cache_ = true;
265 recompute_cache_.Resize(num_tasks);
266 non_fixed_intervals_.resize(num_tasks);
267 for (int t = 0; t < num_tasks; ++t) {
268 non_fixed_intervals_[t] = t;
269 recompute_cache_.Set(t);
270 }
271
272 // Make sure all the cached_* arrays can hold enough data.
273 CHECK_LE(num_tasks, capacity_);
274
275 task_by_increasing_start_min_.resize(num_tasks);
276 task_by_increasing_end_min_.resize(num_tasks);
277 task_by_increasing_negated_start_max_.resize(num_tasks);
278 task_by_decreasing_end_max_.resize(num_tasks);
279 task_by_increasing_shifted_start_min_.resize(num_tasks);
280 task_by_negated_shifted_end_max_.resize(num_tasks);
281 for (int t = 0; t < num_tasks; ++t) {
282 task_by_increasing_start_min_[t].task_index = t;
283 task_by_increasing_end_min_[t].task_index = t;
284 task_by_increasing_negated_start_max_[t].task_index = t;
285 task_by_decreasing_end_max_[t].task_index = t;
286
287 task_by_increasing_shifted_start_min_[t].task_index = t;
288 task_by_increasing_shifted_start_min_[t].presence_lit =
289 reason_for_presence_[t];
290 task_by_negated_shifted_end_max_[t].task_index = t;
291 task_by_negated_shifted_end_max_[t].presence_lit = reason_for_presence_[t];
292 }
293
294 recompute_by_start_max_ = true;
295 recompute_by_end_min_ = true;
296 recompute_energy_profile_ = true;
297 recompute_shifted_start_min_ = true;
298 recompute_negated_shifted_end_max_ = true;
299}
300
302 if (current_time_direction_ != is_forward) {
303 current_time_direction_ = is_forward;
304
305 std::swap(starts_, minus_ends_);
306 std::swap(ends_, minus_starts_);
307
308 std::swap(task_by_increasing_start_min_, task_by_decreasing_end_max_);
309 std::swap(task_by_increasing_end_min_,
310 task_by_increasing_negated_start_max_);
311 std::swap(recompute_by_end_min_, recompute_by_start_max_);
312 std::swap(task_by_increasing_shifted_start_min_,
313 task_by_negated_shifted_end_max_);
314
315 recompute_energy_profile_ = true;
316 std::swap(cached_start_min_, cached_negated_end_max_);
317 std::swap(cached_end_min_, cached_negated_start_max_);
318 std::swap(cached_shifted_start_min_, cached_negated_shifted_end_max_);
319 std::swap(recompute_shifted_start_min_, recompute_negated_shifted_end_max_);
320 }
321}
322
324 bool is_forward) {
325 SetTimeDirection(is_forward);
326
327 // If there was any backtracks since the last time this was called, we
328 // recompute our cache.
329 if (sat_solver_->num_backtracks() != saved_num_backtracks_) {
330 recompute_all_cache_ = true;
331 saved_num_backtracks_ = sat_solver_->num_backtracks();
332 }
333
334 if (recompute_all_cache_) {
335 for (const int t : non_fixed_intervals_) {
336 if (!UpdateCachedValues(t)) return false;
337 }
338
339 // We also update non_fixed_intervals_ at level zero so that we will never
340 // scan them again.
341 if (sat_solver_->CurrentDecisionLevel() == 0) {
342 int new_size = 0;
343 for (const int t : non_fixed_intervals_) {
344 if (IsPresent(t) && StartIsFixed(t) && EndIsFixed(t) &&
345 SizeIsFixed(t)) {
346 continue;
347 }
348 non_fixed_intervals_[new_size++] = t;
349 }
350 non_fixed_intervals_.resize(new_size);
351 }
352 } else {
353 for (const int t : recompute_cache_) {
354 if (!UpdateCachedValues(t)) return false;
355 }
356 }
357 recompute_cache_.ClearAll();
358 recompute_all_cache_ = false;
359 return true;
360}
361
363 int a, int b) {
364 const AffineExpression before = ends_[a];
365 const AffineExpression after = starts_[b];
366 const LinearExpression2 expr(before.var, after.var, before.coeff,
367 -after.coeff);
368 const IntegerValue expr_ub = linear2_bounds_->UpperBound(expr);
369 const IntegerValue needed_offset = before.constant - after.constant;
370 const IntegerValue ub_of_end_minus_start = expr_ub + needed_offset;
371 const IntegerValue distance = -ub_of_end_minus_start;
372 return distance;
373}
374
375// Note that we could call this at a positive level to propagate any literal
376// associated to task a before task b. However we only call this for task that
377// are in detectable precedence, which means the normal precedence or linear
378// propagator should have already propagated that Boolean too.
380 CHECK(IsPresent(a));
381 CHECK(IsPresent(b));
382 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
383
384 // Convert ends_[a] <= starts[b] to linear2 <= rhs and canonicalize.
385 const auto [expr, rhs] = EncodeDifferenceLowerThan(ends_[a], starts_[b], 0);
386
387 // Trivial case.
388 if (expr.coeffs[0] == 0 && expr.coeffs[1] == 0) {
389 if (rhs < 0) {
390 sat_solver_->NotifyThatModelIsUnsat();
391 return false;
392 }
393 return true;
394 }
395
396 if (root_level_lin2_bounds_->AddUpperBound(expr, rhs)) {
397 VLOG(2) << "new relation " << TaskDebugString(a)
398 << " <= " << TaskDebugString(b);
399 // TODO(user): Adding new constraint during propagation might not be the
400 // best idea as it can create some complication.
401 AddWeightedSumLowerOrEqual({}, {expr.vars[0], expr.vars[1]},
402 {expr.coeffs[0].value(), expr.coeffs[1].value()},
403 rhs.value(), model_);
404 if (sat_solver_->ModelIsUnsat()) return false;
405 }
406 return true;
407}
408
409absl::Span<const TaskTime>
411 for (TaskTime& ref : task_by_increasing_start_min_) {
412 ref.time = StartMin(ref.task_index);
413 }
414 IncrementalSort(task_by_increasing_start_min_.begin(),
415 task_by_increasing_start_min_.end());
416 return task_by_increasing_start_min_;
417}
418
419absl::Span<const TaskTime>
421 if (!recompute_by_end_min_) return task_by_increasing_end_min_;
422 for (TaskTime& ref : task_by_increasing_end_min_) {
423 ref.time = EndMin(ref.task_index);
424 }
425 IncrementalSort(task_by_increasing_end_min_.begin(),
426 task_by_increasing_end_min_.end());
427 recompute_by_end_min_ = false;
428 return task_by_increasing_end_min_;
429}
430
431absl::Span<const TaskTime>
433 if (!recompute_by_start_max_) return task_by_increasing_negated_start_max_;
434 for (TaskTime& ref : task_by_increasing_negated_start_max_) {
435 ref.time = cached_negated_start_max_[ref.task_index];
436 }
437 IncrementalSort(task_by_increasing_negated_start_max_.begin(),
438 task_by_increasing_negated_start_max_.end());
439 recompute_by_start_max_ = false;
440 return task_by_increasing_negated_start_max_;
441}
442
443absl::Span<const TaskTime>
445 for (TaskTime& ref : task_by_decreasing_end_max_) {
446 ref.time = EndMax(ref.task_index);
447 }
448 IncrementalSort(task_by_decreasing_end_max_.begin(),
449 task_by_decreasing_end_max_.end(), std::greater<TaskTime>());
450 return task_by_decreasing_end_max_;
451}
452
453absl::Span<const CachedTaskBounds>
455 if (recompute_shifted_start_min_) {
456 recompute_shifted_start_min_ = false;
457 bool is_sorted = true;
458 IntegerValue previous = kMinIntegerValue;
459 for (CachedTaskBounds& ref : task_by_increasing_shifted_start_min_) {
460 ref.time = ShiftedStartMin(ref.task_index);
461 is_sorted = is_sorted && ref.time >= previous;
462 previous = ref.time;
463 }
464 if (is_sorted) return task_by_increasing_shifted_start_min_;
465 IncrementalSort(task_by_increasing_shifted_start_min_.begin(),
466 task_by_increasing_shifted_start_min_.end());
467 }
468 return task_by_increasing_shifted_start_min_;
469}
470
471absl::Span<const CachedTaskBounds>
473 if (recompute_negated_shifted_end_max_) {
474 recompute_negated_shifted_end_max_ = false;
475 bool is_sorted = true;
476 IntegerValue previous = kMinIntegerValue;
477 for (CachedTaskBounds& ref : task_by_negated_shifted_end_max_) {
478 ref.time = -ShiftedEndMax(ref.task_index);
479 is_sorted = is_sorted && ref.time >= previous;
480 previous = ref.time;
481 }
482 if (is_sorted) return task_by_negated_shifted_end_max_;
483 IncrementalSort(task_by_negated_shifted_end_max_.begin(),
484 task_by_negated_shifted_end_max_.end());
485 }
486 return task_by_negated_shifted_end_max_;
487}
488
489// TODO(user): Avoid recomputing it if nothing changed.
490const std::vector<SchedulingConstraintHelper::ProfileEvent>&
492 if (energy_profile_.empty()) {
493 const int num_tasks = NumTasks();
494 for (int t = 0; t < num_tasks; ++t) {
495 energy_profile_.push_back(
496 {cached_shifted_start_min_[t], t, /*is_first=*/true});
497 energy_profile_.push_back({cached_end_min_[t], t, /*is_first=*/false});
498 }
499 } else {
500 if (!recompute_energy_profile_) return energy_profile_;
501 for (ProfileEvent& ref : energy_profile_) {
502 const int t = ref.task;
503 if (ref.is_first) {
504 ref.time = cached_shifted_start_min_[t];
505 } else {
506 ref.time = cached_end_min_[t];
507 }
508 }
509 }
510 IncrementalSort(energy_profile_.begin(), energy_profile_.end());
511 recompute_energy_profile_ = false;
512 return energy_profile_;
513}
514
516 int after) {
517 const auto [expr, ub] =
518 EncodeDifferenceLowerThan(starts_[before], ends_[after], -1);
519 return linear2_bounds_->UpperBound(expr) <= ub;
520}
521
523 int before, int after) {
524 FlagItemAsUsedInReason(before);
525 FlagItemAsUsedInReason(after);
526
527 // We compute this as an optimization, since for fixed sizes all linear2
528 // options are equivalent.
529 const bool fixed_size = sizes_[before].var == kNoIntegerVariable &&
530 sizes_[after].var == kNoIntegerVariable &&
531 starts_[before].var == ends_[before].var &&
532 starts_[after].var == ends_[after].var;
533
534 if (fixed_size && sizes_[after].constant == 0 &&
535 sizes_[before].constant == 0) {
536 // If both sizes are fixed to zero use the trivial explanation.
537 const auto [expr, ub] =
538 EncodeDifferenceLowerThan(ends_[before], starts_[after], 0);
539 DCHECK_LE(linear2_bounds_->UpperBound(expr), ub);
540 linear2_bounds_->AddReasonForUpperBoundLowerThan(expr, ub, &literal_reason_,
541 &integer_reason_);
542 return;
543 }
544
545 // Prefer the straightforward linear2 explanation as it is more likely this
546 // comes from level zero or a single enforcement literal. Also handle the
547 // fixed size case. This explains with at most two integer bounds.
548 {
549 const auto [expr, ub] =
550 EncodeDifferenceLowerThan(starts_[before], ends_[after], -1);
551 if (fixed_size || linear2_bounds_->UpperBound(expr) <= ub) {
552 linear2_bounds_->AddReasonForUpperBoundLowerThan(
553 expr, ub, &literal_reason_, &integer_reason_);
554 return;
555 }
556 }
557
558 // Another choice of Linear2. We need Start(before) < End(after). We rewrite
559 // as:
560 // End(before) - Size(before) < Start(after) + Size(after)
561 //
562 // Note that the generic code below not based on linear2 will not work
563 // if we can only get a good bound for End(before)-Start(after), so we also
564 // handle it here even if the linear2 is not known.
565 //
566 // TODO(user): check also the linear2 constructed with (end_before,
567 // end_after) and (start_after, start_before). Or maybe keep a index of pair
568 // of intervals that have non-trivial linear2 bounds and use that instead.
569 {
570 const auto [expr, ub] = EncodeDifferenceLowerThan(
571 ends_[before], starts_[after], SizeMin(before) + SizeMin(after) - 1);
572 if (linear2_bounds_->UpperBound(expr) <= ub) {
573 AddSizeMinReason(before);
574 AddSizeMinReason(after);
575 linear2_bounds_->AddReasonForUpperBoundLowerThan(
576 expr, ub, &literal_reason_, &integer_reason_);
577 return;
578 }
579 }
580
581 // We prefer to explain StartMax(before) < EndMin(after), but this is false
582 // for zero-size intervals. For example, imagine two tasks:
583 // t1: start=0, end=0, size=0
584 // t2: start=0, end=[0-1], size=[0-1]
585 // We can say that t1 is "before" t2, but StartMax(t1) == EndMin(t2) == 0.
586 if (SizeMin(before) <= 0) {
587 // Encode the straightforward expression End(before) - Start(after) <= 0.
588 const auto [expr, ub] =
589 EncodeDifferenceLowerThan(ends_[before], starts_[after], 0);
590 if (linear2_bounds_->UpperBound(expr) <= ub) {
591 linear2_bounds_->AddReasonForUpperBoundLowerThan(
592 expr, ub, &literal_reason_, &integer_reason_);
593 return;
594 }
595 }
596 DCHECK_LT(StartMax(before), EndMin(after));
597
598 // The reason will be a linear expression greater than a value. Note that all
599 // coeff must be positive, and we will use the variable lower bound.
600 std::vector<IntegerVariable> vars;
601 std::vector<IntegerValue> coeffs;
602
603 // Reason for StartMax(before).
604 const IntegerValue smax_before = StartMax(before);
605 if (smax_before >= integer_trail_->UpperBound(starts_[before])) {
606 if (starts_[before].var != kNoIntegerVariable) {
607 vars.push_back(NegationOf(starts_[before].var));
608 coeffs.push_back(starts_[before].coeff);
609 }
610 } else {
611 if (ends_[before].var != kNoIntegerVariable) {
612 vars.push_back(NegationOf(ends_[before].var));
613 coeffs.push_back(ends_[before].coeff);
614 }
615 if (sizes_[before].var != kNoIntegerVariable) {
616 vars.push_back(sizes_[before].var);
617 coeffs.push_back(sizes_[before].coeff);
618 }
619 }
620
621 // Reason for EndMin(after);
622 const IntegerValue emin_after = EndMin(after);
623 if (emin_after <= integer_trail_->LowerBound(ends_[after])) {
624 if (ends_[after].var != kNoIntegerVariable) {
625 vars.push_back(ends_[after].var);
626 coeffs.push_back(ends_[after].coeff);
627 }
628 } else {
629 if (starts_[after].var != kNoIntegerVariable) {
630 vars.push_back(starts_[after].var);
631 coeffs.push_back(starts_[after].coeff);
632 }
633 if (sizes_[after].var != kNoIntegerVariable) {
634 vars.push_back(sizes_[after].var);
635 coeffs.push_back(sizes_[after].coeff);
636 }
637 }
638
639 DCHECK_LT(smax_before, emin_after);
640 const IntegerValue slack = emin_after - smax_before - 1;
641 integer_trail_->AppendRelaxedLinearReason(slack, coeffs, vars,
642 &integer_reason_);
643}
644
646 CHECK(extra_explanation_callback_ == nullptr);
647 return integer_trail_->Enqueue(lit, literal_reason_, integer_reason_);
648}
649
651 int t, IntegerLiteral lit) {
652 if (IsAbsent(t)) return true;
653 FlagItemAsUsedInReason(t);
654 RunCallbackIfSet();
655 if (IsOptional(t)) {
656 return integer_trail_->ConditionalEnqueue(
657 PresenceLiteral(t), lit, &literal_reason_, &integer_reason_);
658 }
659 return integer_trail_->Enqueue(lit, literal_reason_, integer_reason_);
660}
661
662// We also run directly the precedence propagator for this variable so that when
663// we push an interval start for example, we have a chance to push its end.
664bool SchedulingConstraintHelper::PushIntervalBound(int t, IntegerLiteral lit) {
665 if (!PushIntegerLiteralIfTaskPresent(t, lit)) return false;
666 if (IsAbsent(t)) return true;
667 if (!UpdateCachedValues(t)) return false;
668 recompute_cache_.Clear(t);
669 return true;
670}
671
672bool SchedulingConstraintHelper::IncreaseStartMin(int t, IntegerValue value) {
673 if (starts_[t].var == kNoIntegerVariable) {
674 if (value > starts_[t].constant) return PushTaskAbsence(t);
675 return true;
676 }
677 return PushIntervalBound(t, starts_[t].GreaterOrEqual(value));
678}
679
680bool SchedulingConstraintHelper::IncreaseEndMin(int t, IntegerValue value) {
681 if (ends_[t].var == kNoIntegerVariable) {
682 if (value > ends_[t].constant) return PushTaskAbsence(t);
683 return true;
684 }
685 return PushIntervalBound(t, ends_[t].GreaterOrEqual(value));
686}
687
688bool SchedulingConstraintHelper::DecreaseEndMax(int t, IntegerValue value) {
689 if (ends_[t].var == kNoIntegerVariable) {
690 if (value < ends_[t].constant) return PushTaskAbsence(t);
691 return true;
692 }
693 return PushIntervalBound(t, ends_[t].LowerOrEqual(value));
694}
695
697 return integer_trail_->EnqueueLiteral(l, literal_reason_, integer_reason_);
698}
699
701 if (IsAbsent(t)) return true;
702 if (!IsOptional(t)) return ReportConflict();
703
704 FlagItemAsUsedInReason(t);
705
706 if (IsPresent(t)) {
707 literal_reason_.push_back(Literal(reason_for_presence_[t]).Negated());
708 return ReportConflict();
709 }
710 RunCallbackIfSet();
711 return integer_trail_->EnqueueLiteral(
712 Literal(reason_for_presence_[t]).Negated(), literal_reason_,
713 integer_reason_);
714}
715
717 DCHECK_NE(reason_for_presence_[t], kNoLiteralIndex);
718 DCHECK(!IsPresent(t));
719
720 FlagItemAsUsedInReason(t);
721
722 if (IsAbsent(t)) {
723 literal_reason_.push_back(Literal(reason_for_presence_[t]));
724 return ReportConflict();
725 }
726 RunCallbackIfSet();
727 return integer_trail_->EnqueueLiteral(Literal(reason_for_presence_[t]),
728 literal_reason_, integer_reason_);
729}
730
732 int t_after) {
733 CHECK_NE(t_before, t_after);
734 if (IsAbsent(t_before)) return true;
735 if (IsAbsent(t_after)) return true;
736 if (!IsPresent(t_before) && !IsPresent(t_after)) return true;
737
738 const auto [expr, rhs] =
739 EncodeDifferenceLowerThan(ends_[t_before], starts_[t_after], 0);
740 const auto status = linear2_bounds_->GetStatus(expr, kMinIntegerValue, rhs);
741
742 if (status == RelationStatus::IS_TRUE) return true;
743
744 if (status == RelationStatus::IS_FALSE) {
745 LinearExpression2 negated_expr = expr;
746 negated_expr.Negate();
747 linear2_bounds_->AddReasonForUpperBoundLowerThan(
748 negated_expr, -rhs - 1, &literal_reason_, &integer_reason_);
749 if (!IsPresent(t_before)) {
750 AddPresenceReason(t_after);
751 return PushTaskAbsence(t_before);
752 } else if (!IsPresent(t_after)) {
753 AddPresenceReason(t_before);
754 return PushTaskAbsence(t_after);
755 } else {
756 AddPresenceReason(t_before);
757 AddPresenceReason(t_after);
758 return ReportConflict();
759 }
760 }
761
762 if (!IsPresent(t_before) || !IsPresent(t_after)) return true;
763
764 AddPresenceReason(t_before);
765 AddPresenceReason(t_after);
766
767 FlagItemAsUsedInReason(t_before);
768 FlagItemAsUsedInReason(t_after);
769 RunCallbackIfSet();
770
771 return linear2_bounds_->EnqueueLowerOrEqual(expr, rhs, literal_reason_,
772 integer_reason_);
773}
774
776 RunCallbackIfSet();
777
778 return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
779}
780
782 // It is more efficient to enqueue the propagator
783 // when the helper Propagate() is called. This result in less entries in our
784 // watched lists.
785 propagator_ids_.push_back(id);
786}
787
788void SchedulingConstraintHelper::FlagItemAsUsedInReason(int t) {
789 if (extra_explanation_callback_ == nullptr) {
790 return;
791 }
792 used_items_for_reason_.Set(t);
793}
794
795void SchedulingConstraintHelper::RunCallbackIfSet() {
796 if (extra_explanation_callback_ == nullptr) return;
797 extra_explanation_callback_(used_items_for_reason_.PositionsSetAtLeastOnce(),
798 &literal_reason_, &integer_reason_);
799}
800
802 const SchedulingConstraintHelper& other_helper) {
803 CHECK(other_helper.extra_explanation_callback_ == nullptr);
804 literal_reason_.insert(literal_reason_.end(),
805 other_helper.literal_reason_.begin(),
806 other_helper.literal_reason_.end());
807 integer_reason_.insert(integer_reason_.end(),
808 other_helper.integer_reason_.begin(),
809 other_helper.integer_reason_.end());
810}
811
813 return absl::StrCat("t=", t, " is_present=",
814 (IsPresent(t) ? "1"
815 : IsAbsent(t) ? "0"
816 : "?"),
817 " size=[", SizeMin(t).value(), ",", SizeMax(t).value(),
818 "]", " start=[", StartMin(t).value(), ",",
819 StartMax(t).value(), "]", " end=[", EndMin(t).value(),
820 ",", EndMax(t).value(), "]");
821}
822
824 IntegerValue start,
825 IntegerValue end) const {
826 return std::min(std::min(end - start, SizeMin(t)),
827 std::min(EndMin(t) - start, end - StartMax(t)));
828}
829
831 IntegerValue start_min, IntegerValue start_max, IntegerValue end_min,
832 IntegerValue end_max, IntegerValue size_min, IntegerValue demand_min,
833 absl::Span<const LiteralValueValue> filtered_energy,
834 IntegerValue window_start, IntegerValue window_end) {
835 if (window_end <= window_start) return IntegerValue(0);
836
837 // Returns zero if the interval do not necessarily overlap.
838 if (end_min <= window_start) return IntegerValue(0);
839 if (start_max >= window_end) return IntegerValue(0);
840 const IntegerValue window_size = window_end - window_start;
841 const IntegerValue simple_energy_min =
842 demand_min * std::min({end_min - window_start, window_end - start_max,
843 size_min, window_size});
844 if (filtered_energy.empty()) return simple_energy_min;
845
846 IntegerValue result = kMaxIntegerValue;
847 for (const auto [lit, fixed_size, fixed_demand] : filtered_energy) {
848 const IntegerValue alt_end_min = std::max(end_min, start_min + fixed_size);
849 const IntegerValue alt_start_max =
850 std::min(start_max, end_max - fixed_size);
851 const IntegerValue energy_min =
852 fixed_demand *
853 std::min({alt_end_min - window_start, window_end - alt_start_max,
854 fixed_size, window_size});
855 result = std::min(result, energy_min);
856 }
857 if (result == kMaxIntegerValue) return simple_energy_min;
858 return std::max(simple_energy_min, result);
859}
860
862 absl::Span<const AffineExpression> demands,
863 SchedulingConstraintHelper* helper, Model* model)
864 : integer_trail_(model->GetOrCreate<IntegerTrail>()),
865 product_decomposer_(model->GetOrCreate<ProductDecomposer>()),
866 sat_solver_(model->GetOrCreate<SatSolver>()),
867 assignment_(model->GetOrCreate<SatSolver>()->Assignment()),
868 demands_(demands.begin(), demands.end()),
869 helper_(helper) {
870 const int num_tasks = helper->NumTasks();
871 decomposed_energies_.resize(num_tasks);
872 cached_energies_min_.resize(num_tasks, kMinIntegerValue);
873 cached_energies_max_.resize(num_tasks, kMaxIntegerValue);
874 energy_is_quadratic_.resize(num_tasks, false);
875
876 // We try to init decomposed energies. This is needed for the cuts that are
877 // created after we call InitAllDecomposedEnergies().
879}
880
882 // For the special case were demands is empty.
883 const int num_tasks = helper_->NumTasks();
884 if (demands_.size() != num_tasks) return;
885 for (int t = 0; t < num_tasks; ++t) {
886 const AffineExpression size = helper_->Sizes()[t];
887 const AffineExpression demand = demands_[t];
888 decomposed_energies_[t] = product_decomposer_->TryToDecompose(size, demand);
889 }
890}
891
892IntegerValue SchedulingDemandHelper::SimpleEnergyMin(int t) const {
893 if (demands_.empty()) return kMinIntegerValue;
894 return CapProdI(DemandMin(t), helper_->SizeMin(t));
895}
896
897IntegerValue SchedulingDemandHelper::DecomposedEnergyMin(int t) const {
898 if (decomposed_energies_[t].empty()) return kMinIntegerValue;
899 IntegerValue result = kMaxIntegerValue;
900 for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
901 if (assignment_.LiteralIsTrue(lit)) {
902 return fixed_size * fixed_demand;
903 }
904 if (assignment_.LiteralIsFalse(lit)) continue;
905 result = std::min(result, fixed_size * fixed_demand);
906 }
907 DCHECK_NE(result, kMaxIntegerValue);
908 return result;
909}
910
911IntegerValue SchedulingDemandHelper::SimpleEnergyMax(int t) const {
912 if (demands_.empty()) return kMaxIntegerValue;
913 return CapProdI(DemandMax(t), helper_->SizeMax(t));
914}
915
916IntegerValue SchedulingDemandHelper::DecomposedEnergyMax(int t) const {
917 if (decomposed_energies_[t].empty()) return kMaxIntegerValue;
918 IntegerValue result = kMinIntegerValue;
919 for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
920 if (assignment_.LiteralIsTrue(lit)) {
921 return fixed_size * fixed_demand;
922 }
923 if (assignment_.LiteralIsFalse(lit)) continue;
924 result = std::max(result, fixed_size * fixed_demand);
925 }
926 DCHECK_NE(result, kMinIntegerValue);
927 return result;
928}
929
931 const int num_tasks = cached_energies_min_.size();
932 const bool is_at_level_zero = sat_solver_->CurrentDecisionLevel() == 0;
933 for (int t = 0; t < num_tasks; ++t) {
934 // Try to reduce the size of the decomposed energy vector.
935 if (is_at_level_zero) {
936 int new_size = 0;
937 for (int i = 0; i < decomposed_energies_[t].size(); ++i) {
938 if (assignment_.LiteralIsFalse(decomposed_energies_[t][i].literal)) {
939 continue;
940 }
941 decomposed_energies_[t][new_size++] = decomposed_energies_[t][i];
942 }
943 decomposed_energies_[t].resize(new_size);
944 }
945
946 cached_energies_min_[t] =
947 std::max(SimpleEnergyMin(t), DecomposedEnergyMin(t));
948 if (cached_energies_min_[t] <= kMinIntegerValue) return false;
949 energy_is_quadratic_[t] =
950 decomposed_energies_[t].empty() && !demands_.empty() &&
951 !integer_trail_->IsFixed(demands_[t]) && !helper_->SizeIsFixed(t);
952 cached_energies_max_[t] =
953 std::min(SimpleEnergyMax(t), DecomposedEnergyMax(t));
954 if (cached_energies_max_[t] >= kMaxIntegerValue) return false;
955 }
956
957 return true;
958}
959
960IntegerValue SchedulingDemandHelper::DemandMin(int t) const {
961 DCHECK_LT(t, demands_.size());
962 return integer_trail_->LowerBound(demands_[t]);
963}
964
965IntegerValue SchedulingDemandHelper::DemandMax(int t) const {
966 DCHECK_LT(t, demands_.size());
967 return integer_trail_->UpperBound(demands_[t]);
968}
969
971 return integer_trail_->IsFixed(demands_[t]);
972}
973
974bool SchedulingDemandHelper::DecreaseEnergyMax(int t, IntegerValue value) {
975 if (helper_->IsAbsent(t)) return true;
976 if (value < EnergyMin(t)) return helper_->PushTaskAbsence(t);
977
978 if (!decomposed_energies_[t].empty()) {
979 for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
980 if (fixed_size * fixed_demand > value) {
981 // `lit` encodes that the energy is higher than value. So either lit
982 // must be false or the task must be absent.
983 if (assignment_.LiteralIsFalse(lit)) continue;
984 if (assignment_.LiteralIsTrue(lit)) {
985 // Task must be absent.
986 if (helper_->PresenceLiteral(t) != lit) {
987 helper_->AddLiteralReason(lit.Negated());
988 }
989 return helper_->PushTaskAbsence(t);
990 }
991 if (helper_->IsPresent(t)) {
992 // Task is present, `lit` must be false.
993 DCHECK(!helper_->IsOptional(t) || helper_->PresenceLiteral(t) != lit);
994 helper_->AddPresenceReason(t);
995 if (!helper_->PushLiteral(lit.Negated())) return false;
996 }
997 }
998 }
999 } else {
1000 // TODO(user): Propagate if possible.
1001 VLOG(3) << "Cumulative energy missed propagation";
1002 }
1003 return true;
1004}
1005
1007 DCHECK_LT(t, demands_.size());
1008 if (demands_[t].var != kNoIntegerVariable) {
1009 helper_->AddIntegerReason(
1010 integer_trail_->LowerBoundAsLiteral(demands_[t].var));
1011 }
1012}
1013
1015 IntegerValue min_demand) {
1016 DCHECK_LT(t, demands_.size());
1017 if (demands_[t].var != kNoIntegerVariable) {
1018 helper_->AddIntegerReason(demands_[t].GreaterOrEqual(min_demand));
1019 }
1020}
1021
1023 // We prefer these reason in order.
1024 const IntegerValue value = cached_energies_min_[t];
1025 if (DecomposedEnergyMin(t) >= value) {
1026 for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
1027 if (assignment_.LiteralIsTrue(lit)) {
1028 helper_->AddLiteralReason(lit.Negated());
1029 return;
1030 }
1031 }
1032 for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
1033 if (fixed_size * fixed_demand < value &&
1034 assignment_.LiteralIsFalse(lit)) {
1035 helper_->AddLiteralReason(lit);
1036 }
1037 }
1038 } else if (SimpleEnergyMin(t) >= value) {
1040 helper_->AddSizeMinReason(t);
1041 }
1042}
1043
1045 int t, LinearConstraintBuilder* builder) const {
1046 if (helper_->IsPresent(t)) {
1047 if (!decomposed_energies_[t].empty()) {
1048 for (const LiteralValueValue& entry : decomposed_energies_[t]) {
1049 if (!builder->AddLiteralTerm(entry.literal, entry.right_value)) {
1050 return false;
1051 }
1052 }
1053 } else {
1054 builder->AddTerm(demands_[t], IntegerValue(1));
1055 }
1056 } else if (!helper_->IsAbsent(t)) {
1057 return builder->AddLiteralTerm(helper_->PresenceLiteral(t), DemandMin(t));
1058 }
1059 return true;
1060}
1061
1063 int index) {
1064 if (decomposed_energies_[index].empty()) return {};
1065 if (sat_solver_->CurrentDecisionLevel() == 0) {
1066 // CacheAllEnergyValues has already filtered false literals.
1067 return decomposed_energies_[index];
1068 }
1069
1070 // Scan and filter false literals.
1071 std::vector<LiteralValueValue> result;
1072 for (const auto& e : decomposed_energies_[index]) {
1073 if (assignment_.LiteralIsFalse(e.literal)) continue;
1074 result.push_back(e);
1075 }
1076 return result;
1077}
1078
1080 const std::vector<std::vector<LiteralValueValue>>& energies) {
1081 DCHECK_EQ(energies.size(), helper_->NumTasks());
1082 decomposed_energies_ = energies;
1083}
1084
1086 int t, IntegerValue window_start, IntegerValue window_end) {
1088 helper_->StartMin(t), helper_->StartMax(t), helper_->EndMin(t),
1089 helper_->EndMax(t), helper_->SizeMin(t), DemandMin(t),
1090 FilteredDecomposedEnergy(t), window_start, window_end);
1091}
1092
1093// Since we usually ask way less often for the reason, we redo the computation
1094// here.
1096 int t, IntegerValue window_start, IntegerValue window_end) {
1097 const IntegerValue actual_energy_min =
1098 EnergyMinInWindow(t, window_start, window_end);
1099 if (actual_energy_min == 0) return;
1100
1101 // Return simple reason right away if there is no decomposition or the simple
1102 // energy is enough.
1103 const IntegerValue start_max = helper_->StartMax(t);
1104 const IntegerValue end_min = helper_->EndMin(t);
1105 const IntegerValue min_overlap =
1106 helper_->GetMinOverlap(t, window_start, window_end);
1107 const IntegerValue simple_energy_min = DemandMin(t) * min_overlap;
1108 if (simple_energy_min == actual_energy_min) {
1110 helper_->AddSizeMinReason(t);
1111 helper_->AddStartMaxReason(t, start_max);
1112 helper_->AddEndMinReason(t, end_min);
1113 return;
1114 }
1115
1116 // TODO(user): only include the one we need?
1117 const IntegerValue start_min = helper_->StartMin(t);
1118 const IntegerValue end_max = helper_->EndMax(t);
1119 DCHECK(!decomposed_energies_[t].empty());
1120 helper_->AddStartMinReason(t, start_min);
1121 helper_->AddStartMaxReason(t, start_max);
1122 helper_->AddEndMinReason(t, end_min);
1123 helper_->AddEndMaxReason(t, end_max);
1124
1125 DCHECK(!decomposed_energies_[t].empty());
1126 for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
1127 // Should be the same in most cases.
1128 if (assignment_.LiteralIsTrue(lit)) {
1129 helper_->AddLiteralReason(lit.Negated());
1130 return;
1131 }
1132 }
1133 for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
1134 if (assignment_.LiteralIsFalse(lit)) {
1135 const IntegerValue alt_em = std::max(end_min, start_min + fixed_size);
1136 const IntegerValue alt_sm = std::min(start_max, end_max - fixed_size);
1137 const IntegerValue energy_min =
1138 fixed_demand *
1139 std::min({alt_em - window_start, window_end - alt_sm, fixed_size});
1140 if (energy_min >= actual_energy_min) continue;
1141 helper_->AddLiteralReason(lit);
1142 }
1143 }
1144}
1145
1147 LinearExpression2 expr, IntegerValue ub) {
1148 linear2_bounds_->AddReasonForUpperBoundLowerThan(expr, ub, &literal_reason_,
1149 &integer_reason_);
1150}
1151
1153 std::vector<IntegerLiteral>* integer_reason,
1154 std::vector<Literal>* literal_reason) {
1155 RunCallbackIfSet();
1156 for (const IntegerLiteral l : integer_reason_) {
1157 integer_reason->push_back(l);
1158 }
1159 for (const Literal l : literal_reason_) {
1160 literal_reason->push_back(l);
1161 }
1162 ResetReason();
1163}
1164
1166 Model* model,
1167 std::vector<IntegerVariable>* vars,
1168 int mask) {
1169 IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
1170 for (int t = 0; t < helper->NumTasks(); ++t) {
1171 if ((mask & IntegerVariablesToAddMask::kStart) &&
1172 helper->Starts()[t].var != kNoIntegerVariable) {
1173 vars->push_back(helper->Starts()[t].var);
1174 }
1175 if ((mask & IntegerVariablesToAddMask::kSize) &&
1176 helper->Sizes()[t].var != kNoIntegerVariable) {
1177 vars->push_back(helper->Sizes()[t].var);
1178 }
1179 if ((mask & IntegerVariablesToAddMask::kEnd) &&
1180 helper->Ends()[t].var != kNoIntegerVariable) {
1181 vars->push_back(helper->Ends()[t].var);
1182 }
1184 helper->IsOptional(t) && !helper->IsAbsent(t) &&
1185 !helper->IsPresent(t)) {
1186 const Literal l = helper->PresenceLiteral(t);
1187 IntegerVariable view = kNoIntegerVariable;
1188 if (!encoder->LiteralOrNegationHasView(l, &view)) {
1189 view = model->Add(NewIntegerVariableFromLiteral(l));
1190 }
1191 vars->push_back(view);
1192 }
1193 }
1194}
1195
1197 const AffineExpression& capacity, SchedulingDemandHelper* demands_helper,
1198 Model* model, std::vector<IntegerVariable>* vars) {
1199 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1200 for (const AffineExpression& demand_expr : demands_helper->Demands()) {
1201 if (!integer_trail->IsFixed(demand_expr)) {
1202 vars->push_back(demand_expr.var);
1203 }
1204 }
1205 IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
1206 for (const auto& product : demands_helper->DecomposedEnergies()) {
1207 for (const auto& lit_val_val : product) {
1208 IntegerVariable view = kNoIntegerVariable;
1209 if (!encoder->LiteralOrNegationHasView(lit_val_val.literal, &view)) {
1210 view = model->Add(NewIntegerVariableFromLiteral(lit_val_val.literal));
1211 }
1212 vars->push_back(view);
1213 }
1214 }
1215
1216 if (!integer_trail->IsFixed(capacity)) {
1217 vars->push_back(capacity.var);
1218 }
1219}
1220
1221} // namespace sat
1222} // namespace operations_research
void Clear(IndexType i)
Definition bitset.h:505
void Resize(IndexType size)
Definition bitset.h:474
void Set(IndexType i)
Definition bitset.h:543
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition bitset.h:905
void Set(IntegerType index)
Definition bitset.h:878
void WatchLiteral(Literal l, int id, int watch_index=-1)
Definition integer.h:1708
void SetPropagatorPriority(int id, int priority)
Definition integer.cc:2700
void WatchIntegerVariable(IntegerVariable i, int id, int watch_index=-1)
Definition integer.h:1740
int Register(PropagatorInterface *propagator)
Definition integer.cc:2674
ABSL_MUST_USE_RESULT bool LiteralOrNegationHasView(Literal lit, IntegerVariable *view=nullptr, bool *view_is_direct=nullptr) const
Definition integer.cc:600
IntegerValue LowerBound(IntegerVariable i) const
Definition integer.h:1537
IntegerValue UpperBound(IntegerVariable i) const
Definition integer.h:1541
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff=IntegerValue(1))
void AddTerm(IntegerVariable var, IntegerValue coeff)
T Add(std::function< T(Model *)> f)
Definition model.h:104
bool IncrementalPropagate(const std::vector< int > &watch_indices) final
ABSL_MUST_USE_RESULT bool PushTaskOrderWhenPresent(int t_before, int t_after)
void AddStartMaxReason(int t, IntegerValue upper_bound)
ABSL_MUST_USE_RESULT bool IncreaseEndMin(int t, IntegerValue value)
void AddSizeMaxReason(int t, IntegerValue upper_bound)
void AppendAndResetReason(std::vector< IntegerLiteral > *integer_reason, std::vector< Literal > *literal_reason)
ABSL_MUST_USE_RESULT bool DecreaseEndMax(int t, IntegerValue value)
absl::Span< const AffineExpression > Sizes() const
const std::vector< ProfileEvent > & GetEnergyProfile()
ABSL_MUST_USE_RESULT bool PushIntegerLiteralIfTaskPresent(int t, IntegerLiteral lit)
absl::Span< const AffineExpression > Starts() const
void AddEndMaxReason(int t, IntegerValue upper_bound)
absl::Span< const AffineExpression > Ends() const
ABSL_MUST_USE_RESULT bool ResetFromSubset(const SchedulingConstraintHelper &other, absl::Span< const int > tasks)
ABSL_MUST_USE_RESULT bool IncreaseStartMin(int t, IntegerValue value)
void AddEndMinReason(int t, IntegerValue lower_bound)
void AddReasonForUpperBoundLowerThan(LinearExpression2 expr, IntegerValue ub)
SchedulingConstraintHelper(std::vector< AffineExpression > starts, std::vector< AffineExpression > ends, std::vector< AffineExpression > sizes, std::vector< LiteralIndex > reason_for_presence, Model *model)
absl::Span< const CachedTaskBounds > TaskByIncreasingShiftedStartMin()
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
ABSL_MUST_USE_RESULT bool PushIntegerLiteral(IntegerLiteral lit)
IntegerValue GetMinOverlap(int t, IntegerValue start, IntegerValue end) const
void RegisterWith(GenericLiteralWatcher *watcher, absl::Span< const Literal > enforcement_literals)
void AddStartMinReason(int t, IntegerValue lower_bound)
absl::Span< const CachedTaskBounds > TaskByIncreasingNegatedShiftedEndMax()
void ImportReasonsFromOther(const SchedulingConstraintHelper &other_helper)
std::vector< LiteralValueValue > FilteredDecomposedEnergy(int index)
IntegerValue EnergyMinInWindow(int t, IntegerValue window_start, IntegerValue window_end)
const std::vector< std::vector< LiteralValueValue > > & DecomposedEnergies() const
void AddEnergyMinInWindowReason(int t, IntegerValue window_start, IntegerValue window_end)
SchedulingDemandHelper(absl::Span< const AffineExpression > demands, SchedulingConstraintHelper *helper, Model *model)
void OverrideDecomposedEnergies(const std::vector< std::vector< LiteralValueValue > > &energies)
const std::vector< AffineExpression > & Demands() const
ABSL_MUST_USE_RESULT bool DecreaseEnergyMax(int t, IntegerValue value)
ABSL_MUST_USE_RESULT bool AddLinearizedDemand(int t, LinearConstraintBuilder *builder) const
bool LiteralIsFalse(Literal literal) const
Definition sat_base.h:203
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:206
std::function< IntegerVariable(Model *)> NewIntegerVariableFromLiteral(Literal lit)
Definition integer.h:1812
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
void AddWeightedSumLowerOrEqual(absl::Span< const Literal > enforcement_literals, absl::Span< const IntegerVariable > vars, absl::Span< const IntegerValue > coefficients, int64_t upper_bound, Model *model)
const LiteralIndex kNoLiteralIndex(-1)
std::function< int64_t(const Model &)> LowerBound(IntegerVariable v)
Definition integer.h:1819
std::function< void(Model *)> LowerOrEqual(IntegerVariable v, int64_t ub)
Definition integer.h:1862
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Definition integer.cc:52
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
void AddIntegerVariableFromIntervals(const SchedulingConstraintHelper *helper, Model *model, std::vector< IntegerVariable > *vars, int mask)
void AppendVariablesFromCapacityAndDemands(const AffineExpression &capacity, SchedulingDemandHelper *demands_helper, Model *model, std::vector< IntegerVariable > *vars)
std::function< void(Model *)> GreaterOrEqual(IntegerVariable v, int64_t lb)
Definition integer.h:1847
std::pair< LinearExpression2, IntegerValue > EncodeDifferenceLowerThan(AffineExpression a, AffineExpression b, IntegerValue ub)
IntegerValue CapProdI(IntegerValue a, IntegerValue b)
IntegerValue ComputeEnergyMinInWindow(IntegerValue start_min, IntegerValue start_max, IntegerValue end_min, IntegerValue end_max, IntegerValue size_min, IntegerValue demand_min, absl::Span< const LiteralValueValue > filtered_energy, IntegerValue window_start, IntegerValue window_end)
OR-Tools root namespace.
ClosedInterval::Iterator end(ClosedInterval interval)
void IncrementalSort(int max_comparisons, Iterator begin, Iterator end, Compare comp=Compare{}, bool is_stable=false)
Definition sort.h:46
ClosedInterval::Iterator begin(ClosedInterval interval)
STL namespace.