Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
intervals.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 <cstdint>
18#include <functional>
19#include <string>
20#include <utility>
21#include <vector>
22
23#include "absl/container/flat_hash_map.h"
24#include "absl/log/check.h"
25#include "absl/meta/type_traits.h"
26#include "absl/strings/str_cat.h"
27#include "absl/types/span.h"
31#include "ortools/sat/integer.h"
34#include "ortools/sat/model.h"
38#include "ortools/util/sort.h"
40
41namespace operations_research {
42namespace sat {
43
44IntervalVariable IntervalsRepository::CreateInterval(IntegerVariable start,
45 IntegerVariable end,
46 IntegerVariable size,
47 IntegerValue fixed_size,
48 LiteralIndex is_present) {
51 ? AffineExpression(fixed_size)
53 is_present, /*add_linear_relation=*/true);
54}
55
59 LiteralIndex is_present,
60 bool add_linear_relation) {
61 // Create the interval.
62 const IntervalVariable i(starts_.size());
63 starts_.push_back(start);
64 ends_.push_back(end);
65 sizes_.push_back(size);
66 is_present_.push_back(is_present);
67
68 std::vector<Literal> enforcement_literals;
69 if (is_present != kNoLiteralIndex) {
70 enforcement_literals.push_back(Literal(is_present));
71 }
72
73 if (add_linear_relation) {
74 LinearConstraintBuilder builder(model_, IntegerValue(0), IntegerValue(0));
75 builder.AddTerm(Start(i), IntegerValue(1));
76 builder.AddTerm(Size(i), IntegerValue(1));
77 builder.AddTerm(End(i), IntegerValue(-1));
78 LoadConditionalLinearConstraint(enforcement_literals, builder.Build(),
79 model_);
80 }
81
82 return i;
83}
84
86 IntervalVariable a, IntervalVariable b) {
87 if (disjunctive_precedences_.contains({a, b})) return;
88
89 std::vector<Literal> enforcement_literals;
90 if (IsOptional(a)) enforcement_literals.push_back(PresenceLiteral(a));
91 if (IsOptional(b)) enforcement_literals.push_back(PresenceLiteral(b));
92 if (sat_solver_->CurrentDecisionLevel() == 0) {
93 int new_size = 0;
94 for (const Literal l : enforcement_literals) {
95 // We can ignore always absent interval, and skip the literal of the
96 // interval that are now always present.
97 if (assignment_.LiteralIsTrue(l)) continue;
98 if (assignment_.LiteralIsFalse(l)) return;
99 enforcement_literals[new_size++] = l;
100 }
101 enforcement_literals.resize(new_size);
102 }
103
104 const AffineExpression start_a = Start(a);
105 const AffineExpression end_a = End(a);
106 const AffineExpression start_b = Start(b);
107 const AffineExpression end_b = End(b);
108
109 // task_a is always before task_b ?
110 if (integer_trail_->UpperBound(start_a) < integer_trail_->LowerBound(end_b)) {
111 AddConditionalAffinePrecedence(enforcement_literals, end_a, start_b,
112 model_);
113 return;
114 }
115
116 // task_b is always before task_a ?
117 if (integer_trail_->UpperBound(start_b) < integer_trail_->LowerBound(end_a)) {
118 AddConditionalAffinePrecedence(enforcement_literals, end_b, start_a,
119 model_);
120 return;
121 }
122
123 // Create a new literal.
124 const BooleanVariable boolean_var = sat_solver_->NewBooleanVariable();
125 const Literal a_before_b = Literal(boolean_var, true);
126 disjunctive_precedences_.insert({{a, b}, a_before_b});
127 disjunctive_precedences_.insert({{b, a}, a_before_b.Negated()});
128
129 // Also insert it in precedences.
130 // TODO(user): also add the reverse like start_b + 1 <= end_a if negated?
131 precedences_.insert({{end_a, start_b}, a_before_b});
132 precedences_.insert({{end_b, start_a}, a_before_b.Negated()});
133
134 enforcement_literals.push_back(a_before_b);
135 AddConditionalAffinePrecedence(enforcement_literals, end_a, start_b, model_);
136 enforcement_literals.pop_back();
137
138 enforcement_literals.push_back(a_before_b.Negated());
139 AddConditionalAffinePrecedence(enforcement_literals, end_b, start_a, model_);
140 enforcement_literals.pop_back();
141
142 // Force the value of boolean_var in case the precedence is not active. This
143 // avoids duplicate solutions when enumerating all possible solutions.
144 for (const Literal l : enforcement_literals) {
145 implications_->AddBinaryClause(l, a_before_b);
146 }
147}
148
150 IntervalVariable b) {
151 const AffineExpression x = End(a);
152 const AffineExpression y = Start(b);
153 if (precedences_.contains({x, y})) return false;
154
155 // We want l => x <= y and not(l) => x > y <=> y + 1 <= x
156 // Do not create l if the relation is always true or false.
157 if (integer_trail_->UpperBound(x) <= integer_trail_->LowerBound(y)) {
158 return false;
159 }
160 if (integer_trail_->LowerBound(x) > integer_trail_->UpperBound(y)) {
161 return false;
162 }
163
164 // Create a new literal.
165 const BooleanVariable boolean_var = sat_solver_->NewBooleanVariable();
166 const Literal x_before_y = Literal(boolean_var, true);
167
168 // TODO(user): Also add {{y_plus_one, x}, x_before_y.Negated()} ?
169 precedences_.insert({{x, y}, x_before_y});
170
171 AffineExpression y_plus_one = y;
172 y_plus_one.constant += 1;
173 AddConditionalAffinePrecedence({x_before_y}, x, y, model_);
174 AddConditionalAffinePrecedence({x_before_y.Negated()}, y_plus_one, x, model_);
175 return true;
176}
177
179 IntervalVariable a, IntervalVariable b) const {
180 const AffineExpression x = End(a);
181 const AffineExpression y = Start(b);
182 const auto it = precedences_.find({x, y});
183 if (it != precedences_.end()) return it->second.Index();
184 return kNoLiteralIndex;
185}
186
187// TODO(user): Ideally we should sort the vector of variables, but right now
188// we cannot since we often use this with a parallel vector of demands. So this
189// "sorting" should happen in the presolver so we can share as much as possible.
191 const std::vector<IntervalVariable>& variables,
192 bool register_as_disjunctive_helper) {
193 const auto it = helper_repository_.find(variables);
194 if (it != helper_repository_.end()) return it->second;
195
197 new SchedulingConstraintHelper(variables, model_);
198 helper_repository_[variables] = helper;
199 model_->TakeOwnership(helper);
200 if (register_as_disjunctive_helper) {
201 disjunctive_helpers_.push_back(helper);
202 }
203 return helper;
204}
205
208 absl::Span<const AffineExpression> demands) {
209 const std::pair<SchedulingConstraintHelper*, std::vector<AffineExpression>>
210 key = {helper,
211 std::vector<AffineExpression>(demands.begin(), demands.end())};
212 const auto it = demand_helper_repository_.find(key);
213 if (it != demand_helper_repository_.end()) return it->second;
214
215 SchedulingDemandHelper* demand_helper =
216 new SchedulingDemandHelper(demands, helper, model_);
217 model_->TakeOwnership(demand_helper);
218 demand_helper_repository_[key] = demand_helper;
219 return demand_helper;
220}
221
223 for (const auto& it : demand_helper_repository_) {
224 it.second->InitDecomposedEnergies();
225 }
226}
227
229 const std::vector<IntervalVariable>& tasks, Model* model)
230 : model_(model),
231 trail_(model->GetOrCreate<Trail>()),
232 sat_solver_(model->GetOrCreate<SatSolver>()),
233 integer_trail_(model->GetOrCreate<IntegerTrail>()),
234 watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
235 precedence_relations_(model->GetOrCreate<PrecedenceRelations>()),
236 interval_variables_(tasks),
237 capacity_(tasks.size()),
238 cached_size_min_(new IntegerValue[capacity_]),
239 cached_start_min_(new IntegerValue[capacity_]),
240 cached_end_min_(new IntegerValue[capacity_]),
241 cached_negated_start_max_(new IntegerValue[capacity_]),
242 cached_negated_end_max_(new IntegerValue[capacity_]),
243 cached_shifted_start_min_(new IntegerValue[capacity_]),
244 cached_negated_shifted_end_max_(new IntegerValue[capacity_]) {
245 starts_.clear();
246 ends_.clear();
247 minus_ends_.clear();
248 minus_starts_.clear();
249 sizes_.clear();
250 reason_for_presence_.clear();
251
252 auto* repository = model->GetOrCreate<IntervalsRepository>();
253 for (const IntervalVariable i : tasks) {
254 if (repository->IsOptional(i)) {
255 reason_for_presence_.push_back(repository->PresenceLiteral(i).Index());
256 } else {
257 reason_for_presence_.push_back(kNoLiteralIndex);
258 }
259 sizes_.push_back(repository->Size(i));
260 starts_.push_back(repository->Start(i));
261 ends_.push_back(repository->End(i));
262 minus_starts_.push_back(repository->Start(i).Negated());
263 minus_ends_.push_back(repository->End(i).Negated());
264 }
265
267 InitSortedVectors();
269 model->GetOrCreate<SatSolver>()->NotifyThatModelIsUnsat();
270 }
271}
272
274 Model* model)
275 : model_(model),
276 trail_(model->GetOrCreate<Trail>()),
277 sat_solver_(model->GetOrCreate<SatSolver>()),
278 integer_trail_(model->GetOrCreate<IntegerTrail>()),
279 precedence_relations_(model->GetOrCreate<PrecedenceRelations>()),
280 capacity_(num_tasks),
281 cached_size_min_(new IntegerValue[capacity_]),
282 cached_start_min_(new IntegerValue[capacity_]),
283 cached_end_min_(new IntegerValue[capacity_]),
284 cached_negated_start_max_(new IntegerValue[capacity_]),
285 cached_negated_end_max_(new IntegerValue[capacity_]),
286 cached_shifted_start_min_(new IntegerValue[capacity_]),
287 cached_negated_shifted_end_max_(new IntegerValue[capacity_]) {
288 starts_.resize(num_tasks);
289 CHECK_EQ(NumTasks(), num_tasks);
290}
291
293 recompute_all_cache_ = true;
294 for (const int id : propagator_ids_) watcher_->CallOnNextPropagate(id);
295 return true;
296}
297
299 const std::vector<int>& watch_indices) {
300 for (const int t : watch_indices) recompute_cache_.Set(t);
301 for (const int id : propagator_ids_) watcher_->CallOnNextPropagate(id);
302 return true;
303}
304
306 const int id = watcher->Register(this);
307 const int num_tasks = starts_.size();
308 for (int t = 0; t < num_tasks; ++t) {
309 watcher->WatchIntegerVariable(sizes_[t].var, id, t);
310 watcher->WatchIntegerVariable(starts_[t].var, id, t);
311 watcher->WatchIntegerVariable(ends_[t].var, id, t);
312 }
313 watcher->SetPropagatorPriority(id, 0);
314}
315
316bool SchedulingConstraintHelper::UpdateCachedValues(int t) {
317 if (IsAbsent(t)) return true;
318
319 IntegerValue smin = integer_trail_->LowerBound(starts_[t]);
320 IntegerValue smax = integer_trail_->UpperBound(starts_[t]);
321 IntegerValue emin = integer_trail_->LowerBound(ends_[t]);
322 IntegerValue emax = integer_trail_->UpperBound(ends_[t]);
323
324 // We take the max for the corner case where the size of an optional interval
325 // is used elsewhere and has a domain with negative value.
326 //
327 // TODO(user): maybe we should just disallow size with a negative domain, but
328 // is is harder to enforce if we have a linear expression for size.
329 IntegerValue dmin =
330 std::max(IntegerValue(0), integer_trail_->LowerBound(sizes_[t]));
331 IntegerValue dmax = integer_trail_->UpperBound(sizes_[t]);
332
333 // Detect first if we have a conflict using the relation start + size = end.
334 if (dmax < 0) {
335 ClearReason();
336 AddSizeMaxReason(t, dmax);
337 return PushTaskAbsence(t);
338 }
339 if (smin + dmin - emax > 0) {
340 ClearReason();
341 AddStartMinReason(t, smin);
342 AddSizeMinReason(t, dmin);
343 AddEndMaxReason(t, emax);
344 return PushTaskAbsence(t);
345 }
346 if (smax + dmax - emin < 0) {
347 ClearReason();
348 AddStartMaxReason(t, smax);
349 AddSizeMaxReason(t, dmax);
350 AddEndMinReason(t, emin);
351 return PushTaskAbsence(t);
352 }
353
354 // Sometimes, for optional interval with non-optional bounds, this propagation
355 // give tighter bounds. We always consider the value assuming
356 // the interval is present.
357 //
358 // Note that this is also useful in case not everything was propagated. Note
359 // also that since there is no conflict, we reach the fix point in one pass.
360 smin = std::max(smin, emin - dmax);
361 smax = std::min(smax, emax - dmin);
362 dmin = std::max(dmin, emin - smax);
363 emin = std::max(emin, smin + dmin);
364 emax = std::min(emax, smax + dmax);
365
366 if (emin != cached_end_min_[t]) {
367 recompute_energy_profile_ = true;
368 }
369
370 // We might only want to do that if the value changed, but I am not sure it
371 // is worth the test.
372 recompute_by_start_max_ = true;
373 recompute_by_end_min_ = true;
374
375 cached_start_min_[t] = smin;
376 cached_end_min_[t] = emin;
377 cached_negated_start_max_[t] = -smax;
378 cached_negated_end_max_[t] = -emax;
379 cached_size_min_[t] = dmin;
380
381 // Note that we use the cached value here for EndMin()/StartMax().
382 const IntegerValue new_shifted_start_min = emin - dmin;
383 if (new_shifted_start_min != cached_shifted_start_min_[t]) {
384 recompute_energy_profile_ = true;
385 recompute_shifted_start_min_ = true;
386 cached_shifted_start_min_[t] = new_shifted_start_min;
387 }
388 const IntegerValue new_negated_shifted_end_max = -(smax + dmin);
389 if (new_negated_shifted_end_max != cached_negated_shifted_end_max_[t]) {
390 recompute_negated_shifted_end_max_ = true;
391 cached_negated_shifted_end_max_[t] = new_negated_shifted_end_max;
392 }
393 return true;
394}
395
397 const SchedulingConstraintHelper& other, absl::Span<const int> tasks) {
398 current_time_direction_ = other.current_time_direction_;
399
400 const int num_tasks = tasks.size();
401 interval_variables_.resize(num_tasks);
402 starts_.resize(num_tasks);
403 ends_.resize(num_tasks);
404 minus_ends_.resize(num_tasks);
405 minus_starts_.resize(num_tasks);
406 sizes_.resize(num_tasks);
407 reason_for_presence_.resize(num_tasks);
408 for (int i = 0; i < num_tasks; ++i) {
409 const int t = tasks[i];
410 interval_variables_[i] = other.interval_variables_[t];
411 starts_[i] = other.starts_[t];
412 ends_[i] = other.ends_[t];
413 minus_ends_[i] = other.minus_ends_[t];
414 minus_starts_[i] = other.minus_starts_[t];
415 sizes_[i] = other.sizes_[t];
416 reason_for_presence_[i] = other.reason_for_presence_[t];
417 }
418
419 InitSortedVectors();
421}
422
423void SchedulingConstraintHelper::InitSortedVectors() {
424 const int num_tasks = starts_.size();
425
426 recompute_all_cache_ = true;
427 recompute_cache_.Resize(num_tasks);
428 for (int t = 0; t < num_tasks; ++t) {
429 recompute_cache_.Set(t);
430 }
431
432 // Make sure all the cached_* arrays can hold enough data.
433 CHECK_LE(num_tasks, capacity_);
434
435 task_by_increasing_start_min_.resize(num_tasks);
436 task_by_increasing_end_min_.resize(num_tasks);
437 task_by_increasing_negated_start_max_.resize(num_tasks);
438 task_by_decreasing_end_max_.resize(num_tasks);
439 task_by_increasing_shifted_start_min_.resize(num_tasks);
440 task_by_negated_shifted_end_max_.resize(num_tasks);
441 for (int t = 0; t < num_tasks; ++t) {
442 task_by_increasing_start_min_[t].task_index = t;
443 task_by_increasing_end_min_[t].task_index = t;
444 task_by_increasing_negated_start_max_[t].task_index = t;
445 task_by_decreasing_end_max_[t].task_index = t;
446
447 task_by_increasing_shifted_start_min_[t].task_index = t;
448 task_by_increasing_shifted_start_min_[t].presence_lit =
449 reason_for_presence_[t];
450 task_by_negated_shifted_end_max_[t].task_index = t;
451 task_by_negated_shifted_end_max_[t].presence_lit = reason_for_presence_[t];
452 }
453
454 recompute_by_start_max_ = true;
455 recompute_by_end_min_ = true;
456 recompute_energy_profile_ = true;
457 recompute_shifted_start_min_ = true;
458 recompute_negated_shifted_end_max_ = true;
459}
460
462 if (current_time_direction_ != is_forward) {
463 current_time_direction_ = is_forward;
464
465 std::swap(starts_, minus_ends_);
466 std::swap(ends_, minus_starts_);
467
468 std::swap(task_by_increasing_start_min_, task_by_decreasing_end_max_);
469 std::swap(task_by_increasing_end_min_,
470 task_by_increasing_negated_start_max_);
471 std::swap(recompute_by_end_min_, recompute_by_start_max_);
472 std::swap(task_by_increasing_shifted_start_min_,
473 task_by_negated_shifted_end_max_);
474
475 recompute_energy_profile_ = true;
476 std::swap(cached_start_min_, cached_negated_end_max_);
477 std::swap(cached_end_min_, cached_negated_start_max_);
478 std::swap(cached_shifted_start_min_, cached_negated_shifted_end_max_);
479 std::swap(recompute_shifted_start_min_, recompute_negated_shifted_end_max_);
480 }
481}
482
484 bool is_forward) {
485 SetTimeDirection(is_forward);
486
487 // If there was any backtracks since the last time this was called, we
488 // recompute our cache.
489 if (sat_solver_->num_backtracks() != saved_num_backtracks_) {
490 recompute_all_cache_ = true;
491 saved_num_backtracks_ = sat_solver_->num_backtracks();
492 }
493
494 if (recompute_all_cache_) {
495 for (int t = 0; t < recompute_cache_.size(); ++t) {
496 if (!UpdateCachedValues(t)) return false;
497 }
498 } else {
499 for (const int t : recompute_cache_) {
500 if (!UpdateCachedValues(t)) return false;
501 }
502 }
503 recompute_cache_.ClearAll();
504 recompute_all_cache_ = false;
505 return true;
506}
507
508// TODO(user): be more precise when we know a and b are in disjunction.
509// we really just need start_b > start_a, or even >= if duration is non-zero.
511 int a, int b, bool add_reason_if_after) {
512 const AffineExpression before = ends_[a];
513 const AffineExpression after = starts_[b];
514 if (before.var == kNoIntegerVariable || before.coeff != 1 ||
515 after.var == kNoIntegerVariable || after.coeff != 1) {
516 return kMinIntegerValue;
517 }
518
519 // We take the max of the level zero offset and the one coming from a
520 // conditional precedence at true.
521 const IntegerValue conditional_offset =
522 precedence_relations_->GetConditionalOffset(before.var, after.var);
523 const IntegerValue known = integer_trail_->LevelZeroLowerBound(after.var) -
524 integer_trail_->LevelZeroUpperBound(before.var);
525 const IntegerValue offset = std::max(conditional_offset, known);
526
527 const IntegerValue needed_offset = before.constant - after.constant;
528 const IntegerValue distance = offset - needed_offset;
529 if (add_reason_if_after && distance >= 0 && known < conditional_offset) {
530 for (const Literal l : precedence_relations_->GetConditionalEnforcements(
531 before.var, after.var)) {
532 literal_reason_.push_back(l.Negated());
533 }
534 }
535 return distance;
536}
537
538// Note that we could call this at a positive level to propagate any literal
539// associated to task a before task b. However we only call this for task that
540// are in detectable precedence, which means the normal precedence or linear
541// propagator should have already propagated that Boolean too.
543 CHECK(IsPresent(a));
544 CHECK(IsPresent(b));
545 CHECK_EQ(trail_->CurrentDecisionLevel(), 0);
546
547 const AffineExpression before = ends_[a];
548 const AffineExpression after = starts_[b];
549 if (after.coeff != 1) return true;
550 if (before.coeff != 1) return true;
551 if (after.var == kNoIntegerVariable) return true;
552 if (before.var == kNoIntegerVariable) return true;
553 const IntegerValue offset = before.constant - after.constant;
554 if (precedence_relations_->Add(before.var, after.var, offset)) {
555 VLOG(2) << "new relation " << TaskDebugString(a)
556 << " <= " << TaskDebugString(b);
557
558 // TODO(user): Adding new constraint during propagation might not be the
559 // best idea as it can create some complication.
560 AddWeightedSumLowerOrEqual({}, {before.var, after.var},
561 {int64_t{1}, int64_t{-1}}, -offset.value(),
562 model_);
563 if (model_->GetOrCreate<SatSolver>()->ModelIsUnsat()) return false;
564 }
565 return true;
566}
567
568absl::Span<const TaskTime>
570 for (TaskTime& ref : task_by_increasing_start_min_) {
571 ref.time = StartMin(ref.task_index);
572 }
573 IncrementalSort(task_by_increasing_start_min_.begin(),
574 task_by_increasing_start_min_.end());
575 return task_by_increasing_start_min_;
576}
577
578absl::Span<const TaskTime>
580 if (!recompute_by_end_min_) return task_by_increasing_end_min_;
581 for (TaskTime& ref : task_by_increasing_end_min_) {
582 ref.time = EndMin(ref.task_index);
583 }
584 IncrementalSort(task_by_increasing_end_min_.begin(),
585 task_by_increasing_end_min_.end());
586 recompute_by_end_min_ = false;
587 return task_by_increasing_end_min_;
588}
589
590absl::Span<const TaskTime>
592 if (!recompute_by_start_max_) return task_by_increasing_negated_start_max_;
593 for (TaskTime& ref : task_by_increasing_negated_start_max_) {
594 ref.time = cached_negated_start_max_[ref.task_index];
595 }
596 IncrementalSort(task_by_increasing_negated_start_max_.begin(),
597 task_by_increasing_negated_start_max_.end());
598 recompute_by_start_max_ = false;
599 return task_by_increasing_negated_start_max_;
600}
601
602absl::Span<const TaskTime>
604 for (TaskTime& ref : task_by_decreasing_end_max_) {
605 ref.time = EndMax(ref.task_index);
606 }
607 IncrementalSort(task_by_decreasing_end_max_.begin(),
608 task_by_decreasing_end_max_.end(), std::greater<TaskTime>());
609 return task_by_decreasing_end_max_;
610}
611
612absl::Span<const CachedTaskBounds>
614 if (recompute_shifted_start_min_) {
615 recompute_shifted_start_min_ = false;
616 bool is_sorted = true;
617 IntegerValue previous = kMinIntegerValue;
618 for (CachedTaskBounds& ref : task_by_increasing_shifted_start_min_) {
619 ref.time = ShiftedStartMin(ref.task_index);
620 is_sorted = is_sorted && ref.time >= previous;
621 previous = ref.time;
622 }
623 if (is_sorted) return task_by_increasing_shifted_start_min_;
624 IncrementalSort(task_by_increasing_shifted_start_min_.begin(),
625 task_by_increasing_shifted_start_min_.end());
626 }
627 return task_by_increasing_shifted_start_min_;
628}
629
630// TODO(user): Avoid recomputing it if nothing changed.
631const std::vector<SchedulingConstraintHelper::ProfileEvent>&
633 if (energy_profile_.empty()) {
634 const int num_tasks = NumTasks();
635 for (int t = 0; t < num_tasks; ++t) {
636 energy_profile_.push_back(
637 {cached_shifted_start_min_[t], t, /*is_first=*/true});
638 energy_profile_.push_back({cached_end_min_[t], t, /*is_first=*/false});
639 }
640 } else {
641 if (!recompute_energy_profile_) return energy_profile_;
642 for (ProfileEvent& ref : energy_profile_) {
643 const int t = ref.task;
644 if (ref.is_first) {
645 ref.time = cached_shifted_start_min_[t];
646 } else {
647 ref.time = cached_end_min_[t];
648 }
649 }
650 }
651 IncrementalSort(energy_profile_.begin(), energy_profile_.end());
652 recompute_energy_profile_ = false;
653 return energy_profile_;
654}
655
656// Produces a relaxed reason for StartMax(before) < EndMin(after).
658 int after) {
659 AddOtherReason(before);
660 AddOtherReason(after);
661
662 // The reason will be a linear expression greater than a value. Note that all
663 // coeff must be positive, and we will use the variable lower bound.
664 std::vector<IntegerVariable> vars;
665 std::vector<IntegerValue> coeffs;
666
667 // Reason for StartMax(before).
668 const IntegerValue smax_before = StartMax(before);
669 if (smax_before >= integer_trail_->UpperBound(starts_[before])) {
670 if (starts_[before].var != kNoIntegerVariable) {
671 vars.push_back(NegationOf(starts_[before].var));
672 coeffs.push_back(starts_[before].coeff);
673 }
674 } else {
675 if (ends_[before].var != kNoIntegerVariable) {
676 vars.push_back(NegationOf(ends_[before].var));
677 coeffs.push_back(ends_[before].coeff);
678 }
679 if (sizes_[before].var != kNoIntegerVariable) {
680 vars.push_back(sizes_[before].var);
681 coeffs.push_back(sizes_[before].coeff);
682 }
683 }
684
685 // Reason for EndMin(after);
686 const IntegerValue emin_after = EndMin(after);
687 if (emin_after <= integer_trail_->LowerBound(ends_[after])) {
688 if (ends_[after].var != kNoIntegerVariable) {
689 vars.push_back(ends_[after].var);
690 coeffs.push_back(ends_[after].coeff);
691 }
692 } else {
693 if (starts_[after].var != kNoIntegerVariable) {
694 vars.push_back(starts_[after].var);
695 coeffs.push_back(starts_[after].coeff);
696 }
697 if (sizes_[after].var != kNoIntegerVariable) {
698 vars.push_back(sizes_[after].var);
699 coeffs.push_back(sizes_[after].coeff);
700 }
701 }
702
703 DCHECK_LT(smax_before, emin_after);
704 const IntegerValue slack = emin_after - smax_before - 1;
705 integer_trail_->AppendRelaxedLinearReason(slack, coeffs, vars,
706 &integer_reason_);
707}
708
710 CHECK(other_helper_ == nullptr);
711 return integer_trail_->Enqueue(lit, literal_reason_, integer_reason_);
712}
713
715 int t, IntegerLiteral lit) {
716 if (IsAbsent(t)) return true;
717 AddOtherReason(t);
719 if (IsOptional(t)) {
720 return integer_trail_->ConditionalEnqueue(
721 PresenceLiteral(t), lit, &literal_reason_, &integer_reason_);
722 }
723 return integer_trail_->Enqueue(lit, literal_reason_, integer_reason_);
724}
725
726// We also run directly the precedence propagator for this variable so that when
727// we push an interval start for example, we have a chance to push its end.
728bool SchedulingConstraintHelper::PushIntervalBound(int t, IntegerLiteral lit) {
729 if (!PushIntegerLiteralIfTaskPresent(t, lit)) return false;
730 if (IsAbsent(t)) return true;
731 if (!UpdateCachedValues(t)) return false;
732 recompute_cache_.Clear(t);
733 return true;
734}
735
737 if (starts_[t].var == kNoIntegerVariable) {
738 if (value > starts_[t].constant) return PushTaskAbsence(t);
739 return true;
740 }
741 return PushIntervalBound(t, starts_[t].GreaterOrEqual(value));
742}
743
745 if (ends_[t].var == kNoIntegerVariable) {
746 if (value > ends_[t].constant) return PushTaskAbsence(t);
747 return true;
748 }
749 return PushIntervalBound(t, ends_[t].GreaterOrEqual(value));
750}
751
753 if (ends_[t].var == kNoIntegerVariable) {
754 if (value < ends_[t].constant) return PushTaskAbsence(t);
755 return true;
756 }
757 return PushIntervalBound(t, ends_[t].LowerOrEqual(value));
758}
759
761 integer_trail_->EnqueueLiteral(l, literal_reason_, integer_reason_);
762 return true;
763}
764
766 if (IsAbsent(t)) return true;
767 if (!IsOptional(t)) return ReportConflict();
768
769 AddOtherReason(t);
770
771 if (IsPresent(t)) {
772 literal_reason_.push_back(Literal(reason_for_presence_[t]).Negated());
773 return ReportConflict();
774 }
776 integer_trail_->EnqueueLiteral(Literal(reason_for_presence_[t]).Negated(),
777 literal_reason_, integer_reason_);
778 return true;
779}
780
782 DCHECK_NE(reason_for_presence_[t], kNoLiteralIndex);
783 DCHECK(!IsPresent(t));
784
785 AddOtherReason(t);
786
787 if (IsAbsent(t)) {
788 literal_reason_.push_back(Literal(reason_for_presence_[t]));
789 return ReportConflict();
790 }
792 integer_trail_->EnqueueLiteral(Literal(reason_for_presence_[t]),
793 literal_reason_, integer_reason_);
794 return true;
795}
796
799 return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
800}
801
802void SchedulingConstraintHelper::WatchAllTasks(int id, bool watch_max_side) {
803 // In all cases, we watch presence literals since this class is not waked up
804 // when those changes.
805 const int num_tasks = starts_.size();
806 for (int t = 0; t < num_tasks; ++t) {
807 if (!IsPresent(t) && !IsAbsent(t)) {
808 watcher_->WatchLiteral(Literal(reason_for_presence_[t]), id);
809 }
810 }
811
812 // If everything is watched, it is slighlty more efficient to enqueue the
813 // propagator when the helper Propagate() is called. This result in less
814 // entries in our watched lists.
815 if (watch_max_side) {
816 propagator_ids_.push_back(id);
817 return;
818 }
819
820 // We only watch "min" side.
821 for (int t = 0; t < num_tasks; ++t) {
822 watcher_->WatchLowerBound(starts_[t], id);
823 watcher_->WatchLowerBound(ends_[t], id);
824 watcher_->WatchLowerBound(sizes_[t], id);
825 }
826}
827
828void SchedulingConstraintHelper::AddOtherReason(int t) {
829 if (other_helper_ == nullptr || already_added_to_other_reasons_[t]) return;
830 already_added_to_other_reasons_[t] = true;
831 const int mapped_t = map_to_other_helper_[t];
832 other_helper_->AddStartMaxReason(mapped_t, event_for_other_helper_);
833 other_helper_->AddEndMinReason(mapped_t, event_for_other_helper_ + 1);
834}
835
836void SchedulingConstraintHelper::ImportOtherReasons() {
837 if (other_helper_ != nullptr) ImportOtherReasons(*other_helper_);
838}
839
840void SchedulingConstraintHelper::ImportOtherReasons(
841 const SchedulingConstraintHelper& other_helper) {
842 literal_reason_.insert(literal_reason_.end(),
843 other_helper.literal_reason_.begin(),
844 other_helper.literal_reason_.end());
845 integer_reason_.insert(integer_reason_.end(),
846 other_helper.integer_reason_.begin(),
847 other_helper.integer_reason_.end());
848}
849
851 return absl::StrCat("t=", t, " is_present=", IsPresent(t), " size=[",
852 SizeMin(t).value(), ",", SizeMax(t).value(), "]",
853 " start=[", StartMin(t).value(), ",", StartMax(t).value(),
854 "]", " end=[", EndMin(t).value(), ",", EndMax(t).value(),
855 "]");
856}
857
859 IntegerValue start,
860 IntegerValue end) const {
861 return std::min(std::min(end - start, SizeMin(t)),
862 std::min(EndMin(t) - start, end - StartMax(t)));
863}
864
866 IntegerValue start_min, IntegerValue start_max, IntegerValue end_min,
867 IntegerValue end_max, IntegerValue size_min, IntegerValue demand_min,
868 absl::Span<const LiteralValueValue> filtered_energy,
869 IntegerValue window_start, IntegerValue window_end) {
870 if (window_end <= window_start) return IntegerValue(0);
871
872 // Returns zero if the interval do not necessarily overlap.
873 if (end_min <= window_start) return IntegerValue(0);
874 if (start_max >= window_end) return IntegerValue(0);
875 const IntegerValue window_size = window_end - window_start;
876 const IntegerValue simple_energy_min =
877 demand_min * std::min({end_min - window_start, window_end - start_max,
878 size_min, window_size});
879 if (filtered_energy.empty()) return simple_energy_min;
880
881 IntegerValue result = kMaxIntegerValue;
882 for (const auto [lit, fixed_size, fixed_demand] : filtered_energy) {
883 const IntegerValue alt_end_min = std::max(end_min, start_min + fixed_size);
884 const IntegerValue alt_start_max =
885 std::min(start_max, end_max - fixed_size);
886 const IntegerValue energy_min =
887 fixed_demand *
888 std::min({alt_end_min - window_start, window_end - alt_start_max,
889 fixed_size, window_size});
890 result = std::min(result, energy_min);
891 }
892 if (result == kMaxIntegerValue) return simple_energy_min;
893 return std::max(simple_energy_min, result);
894}
895
897 absl::Span<const AffineExpression> demands,
899 : integer_trail_(model->GetOrCreate<IntegerTrail>()),
900 product_decomposer_(model->GetOrCreate<ProductDecomposer>()),
901 sat_solver_(model->GetOrCreate<SatSolver>()),
902 assignment_(model->GetOrCreate<SatSolver>()->Assignment()),
903 demands_(demands.begin(), demands.end()),
904 helper_(helper) {
905 const int num_tasks = helper->NumTasks();
906 linearized_energies_.resize(num_tasks);
907 decomposed_energies_.resize(num_tasks);
908 cached_energies_min_.resize(num_tasks, kMinIntegerValue);
909 cached_energies_max_.resize(num_tasks, kMaxIntegerValue);
910 energy_is_quadratic_.resize(num_tasks, false);
911
912 // We try to init decomposed energies. This is needed for the cuts that are
913 // created after we call InitAllDecomposedEnergies().
915}
916
918 // For the special case were demands is empty.
919 const int num_tasks = helper_->NumTasks();
920 if (demands_.size() != num_tasks) return;
921 for (int t = 0; t < num_tasks; ++t) {
922 const AffineExpression size = helper_->Sizes()[t];
923 const AffineExpression demand = demands_[t];
924 decomposed_energies_[t] = product_decomposer_->TryToDecompose(size, demand);
925 }
926}
927
928IntegerValue SchedulingDemandHelper::SimpleEnergyMin(int t) const {
929 if (demands_.empty()) return kMinIntegerValue;
930 return DemandMin(t) * helper_->SizeMin(t);
931}
932
933IntegerValue SchedulingDemandHelper::LinearEnergyMin(int t) const {
934 if (!linearized_energies_[t].has_value()) return kMinIntegerValue;
935 return linearized_energies_[t]->Min(*integer_trail_);
936}
937
938IntegerValue SchedulingDemandHelper::DecomposedEnergyMin(int t) const {
939 if (decomposed_energies_[t].empty()) return kMinIntegerValue;
940 IntegerValue result = kMaxIntegerValue;
941 for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
942 if (assignment_.LiteralIsTrue(lit)) {
943 return fixed_size * fixed_demand;
944 }
945 if (assignment_.LiteralIsFalse(lit)) continue;
946 result = std::min(result, fixed_size * fixed_demand);
947 }
948 DCHECK_NE(result, kMaxIntegerValue);
949 return result;
950}
951
952IntegerValue SchedulingDemandHelper::SimpleEnergyMax(int t) const {
953 if (demands_.empty()) return kMaxIntegerValue;
954 return DemandMax(t) * helper_->SizeMax(t);
955}
956
957IntegerValue SchedulingDemandHelper::LinearEnergyMax(int t) const {
958 if (!linearized_energies_[t].has_value()) return kMaxIntegerValue;
959 return linearized_energies_[t]->Max(*integer_trail_);
960}
961
962IntegerValue SchedulingDemandHelper::DecomposedEnergyMax(int t) const {
963 if (decomposed_energies_[t].empty()) return kMaxIntegerValue;
964 IntegerValue result = kMinIntegerValue;
965 for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
966 if (assignment_.LiteralIsTrue(lit)) {
967 return fixed_size * fixed_demand;
968 }
969 if (assignment_.LiteralIsFalse(lit)) continue;
970 result = std::max(result, fixed_size * fixed_demand);
971 }
972 DCHECK_NE(result, kMinIntegerValue);
973 return result;
974}
975
977 const int num_tasks = cached_energies_min_.size();
978 const bool is_at_level_zero = sat_solver_->CurrentDecisionLevel() == 0;
979 for (int t = 0; t < num_tasks; ++t) {
980 // Try to reduce the size of the decomposed energy vector.
981 if (is_at_level_zero) {
982 int new_size = 0;
983 for (int i = 0; i < decomposed_energies_[t].size(); ++i) {
984 if (assignment_.LiteralIsFalse(decomposed_energies_[t][i].literal)) {
985 continue;
986 }
987 decomposed_energies_[t][new_size++] = decomposed_energies_[t][i];
988 }
989 decomposed_energies_[t].resize(new_size);
990 }
991
992 cached_energies_min_[t] = std::max(
993 {SimpleEnergyMin(t), LinearEnergyMin(t), DecomposedEnergyMin(t)});
994 CHECK_NE(cached_energies_min_[t], kMinIntegerValue);
995 energy_is_quadratic_[t] =
996 decomposed_energies_[t].empty() && !demands_.empty() &&
997 !integer_trail_->IsFixed(demands_[t]) && !helper_->SizeIsFixed(t);
998 cached_energies_max_[t] = std::min(
999 {SimpleEnergyMax(t), LinearEnergyMax(t), DecomposedEnergyMax(t)});
1000 CHECK_NE(cached_energies_max_[t], kMaxIntegerValue);
1001 }
1002}
1003
1004IntegerValue SchedulingDemandHelper::DemandMin(int t) const {
1005 DCHECK_LT(t, demands_.size());
1006 return integer_trail_->LowerBound(demands_[t]);
1007}
1008
1009IntegerValue SchedulingDemandHelper::DemandMax(int t) const {
1010 DCHECK_LT(t, demands_.size());
1011 return integer_trail_->UpperBound(demands_[t]);
1012}
1013
1015 return integer_trail_->IsFixed(demands_[t]);
1016}
1017
1019 if (value < EnergyMin(t)) {
1020 if (helper_->IsOptional(t)) {
1021 return helper_->PushTaskAbsence(t);
1022 } else {
1023 return helper_->ReportConflict();
1024 }
1025 } else if (!decomposed_energies_[t].empty()) {
1026 for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
1027 if (fixed_size * fixed_demand > value) {
1028 if (assignment_.LiteralIsTrue(lit)) return helper_->ReportConflict();
1029 if (assignment_.LiteralIsFalse(lit)) continue;
1030 if (!helper_->PushLiteral(lit.Negated())) return false;
1031 }
1032 }
1033 } else if (linearized_energies_[t].has_value() &&
1034 linearized_energies_[t]->vars.size() == 1) {
1035 const LinearExpression& e = linearized_energies_[t].value();
1036 const AffineExpression affine_energy(e.vars[0], e.coeffs[0], e.offset);
1037 const IntegerLiteral deduction = affine_energy.LowerOrEqual(value);
1038 if (!helper_->PushIntegerLiteralIfTaskPresent(t, deduction)) {
1039 return false;
1040 }
1041 } else {
1042 // TODO(user): Propagate if possible.
1043 VLOG(3) << "Cumulative energy missed propagation";
1044 }
1045 return true;
1046}
1047
1049 DCHECK_LT(t, demands_.size());
1050 if (demands_[t].var != kNoIntegerVariable) {
1051 helper_->MutableIntegerReason()->push_back(
1052 integer_trail_->LowerBoundAsLiteral(demands_[t].var));
1053 }
1054}
1055
1057 IntegerValue min_demand) {
1058 DCHECK_LT(t, demands_.size());
1059 if (demands_[t].var != kNoIntegerVariable) {
1060 helper_->MutableIntegerReason()->push_back(
1061 demands_[t].GreaterOrEqual(min_demand));
1062 }
1063}
1064
1066 // We prefer these reason in order.
1067 const IntegerValue value = cached_energies_min_[t];
1068 if (DecomposedEnergyMin(t) >= value) {
1069 auto* reason = helper_->MutableLiteralReason();
1070 const int old_size = reason->size();
1071 for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
1072 if (assignment_.LiteralIsTrue(lit)) {
1073 reason->resize(old_size);
1074 reason->push_back(lit.Negated());
1075 return;
1076 } else if (fixed_size * fixed_demand < value &&
1077 assignment_.LiteralIsFalse(lit)) {
1078 reason->push_back(lit);
1079 }
1080 }
1081 } else if (SimpleEnergyMin(t) >= value) {
1083 helper_->AddSizeMinReason(t);
1084 } else {
1085 DCHECK_GE(LinearEnergyMin(t), value);
1086 for (const IntegerVariable var : linearized_energies_[t]->vars) {
1087 helper_->MutableIntegerReason()->push_back(
1088 integer_trail_->LowerBoundAsLiteral(var));
1089 }
1090 }
1091}
1092
1094 int t, LinearConstraintBuilder* builder) const {
1095 if (helper_->IsPresent(t)) {
1096 if (!decomposed_energies_[t].empty()) {
1097 for (const LiteralValueValue& entry : decomposed_energies_[t]) {
1098 if (!builder->AddLiteralTerm(entry.literal, entry.right_value)) {
1099 return false;
1100 }
1101 }
1102 } else {
1103 builder->AddTerm(demands_[t], IntegerValue(1));
1104 }
1105 } else if (!helper_->IsAbsent(t)) {
1106 return builder->AddLiteralTerm(helper_->PresenceLiteral(t), DemandMin(t));
1107 }
1108 return true;
1109}
1110
1112 absl::Span<const LinearExpression> energies) {
1113 const int num_tasks = energies.size();
1114 DCHECK_EQ(num_tasks, helper_->NumTasks());
1115 linearized_energies_.resize(num_tasks);
1116 for (int t = 0; t < num_tasks; ++t) {
1117 linearized_energies_[t] = energies[t];
1118 if (DEBUG_MODE) {
1119 for (const IntegerValue coeff : linearized_energies_[t]->coeffs) {
1120 DCHECK_GE(coeff, 0);
1121 }
1122 }
1123 }
1124}
1125
1127 int index) {
1128 if (decomposed_energies_[index].empty()) return {};
1129 if (sat_solver_->CurrentDecisionLevel() == 0) {
1130 // CacheAllEnergyValues has already filtered false literals.
1131 return decomposed_energies_[index];
1132 }
1133
1134 // Scan and filter false literals.
1135 std::vector<LiteralValueValue> result;
1136 for (const auto& e : decomposed_energies_[index]) {
1137 if (assignment_.LiteralIsFalse(e.literal)) continue;
1138 result.push_back(e);
1139 }
1140 return result;
1141}
1142
1144 const std::vector<std::vector<LiteralValueValue>>& energies) {
1145 DCHECK_EQ(energies.size(), helper_->NumTasks());
1146 decomposed_energies_ = energies;
1147}
1148
1150 int t, IntegerValue window_start, IntegerValue window_end) {
1152 helper_->StartMin(t), helper_->StartMax(t), helper_->EndMin(t),
1153 helper_->EndMax(t), helper_->SizeMin(t), DemandMin(t),
1154 FilteredDecomposedEnergy(t), window_start, window_end);
1155}
1156
1157// Since we usually ask way less often for the reason, we redo the computation
1158// here.
1160 int t, IntegerValue window_start, IntegerValue window_end) {
1161 const IntegerValue actual_energy_min =
1162 EnergyMinInWindow(t, window_start, window_end);
1163 if (actual_energy_min == 0) return;
1164
1165 // Return simple reason right away if there is no decomposition or the simple
1166 // energy is enough.
1167 const IntegerValue start_max = helper_->StartMax(t);
1168 const IntegerValue end_min = helper_->EndMin(t);
1169 const IntegerValue min_overlap =
1170 helper_->GetMinOverlap(t, window_start, window_end);
1171 const IntegerValue simple_energy_min = DemandMin(t) * min_overlap;
1172 if (simple_energy_min == actual_energy_min) {
1174 helper_->AddSizeMinReason(t);
1175 helper_->AddStartMaxReason(t, start_max);
1176 helper_->AddEndMinReason(t, end_min);
1177 return;
1178 }
1179
1180 // TODO(user): only include the one we need?
1181 const IntegerValue start_min = helper_->StartMin(t);
1182 const IntegerValue end_max = helper_->EndMax(t);
1183 DCHECK(!decomposed_energies_[t].empty());
1184 helper_->AddStartMinReason(t, start_min);
1185 helper_->AddStartMaxReason(t, start_max);
1186 helper_->AddEndMinReason(t, end_min);
1187 helper_->AddEndMaxReason(t, end_max);
1188
1189 auto* literal_reason = helper_->MutableLiteralReason();
1190 const int old_size = literal_reason->size();
1191
1192 DCHECK(!decomposed_energies_[t].empty());
1193 for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
1194 // Should be the same in most cases.
1195 if (assignment_.LiteralIsTrue(lit)) {
1196 literal_reason->resize(old_size);
1197 literal_reason->push_back(lit.Negated());
1198 return;
1199 }
1200 if (assignment_.LiteralIsFalse(lit)) {
1201 const IntegerValue alt_em = std::max(end_min, start_min + fixed_size);
1202 const IntegerValue alt_sm = std::min(start_max, end_max - fixed_size);
1203 const IntegerValue energy_min =
1204 fixed_demand *
1205 std::min({alt_em - window_start, window_end - alt_sm, fixed_size});
1206 if (energy_min >= actual_energy_min) continue;
1207 literal_reason->push_back(lit);
1208 }
1209 }
1210}
1211
1213 Model* model,
1214 std::vector<IntegerVariable>* vars) {
1215 IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
1216 for (int t = 0; t < helper->NumTasks(); ++t) {
1217 if (helper->Starts()[t].var != kNoIntegerVariable) {
1218 vars->push_back(helper->Starts()[t].var);
1219 }
1220 if (helper->Sizes()[t].var != kNoIntegerVariable) {
1221 vars->push_back(helper->Sizes()[t].var);
1222 }
1223 if (helper->Ends()[t].var != kNoIntegerVariable) {
1224 vars->push_back(helper->Ends()[t].var);
1225 }
1226 if (helper->IsOptional(t) && !helper->IsAbsent(t) &&
1227 !helper->IsPresent(t)) {
1228 const Literal l = helper->PresenceLiteral(t);
1229 IntegerVariable view = kNoIntegerVariable;
1230 if (!encoder->LiteralOrNegationHasView(l, &view)) {
1231 view = model->Add(NewIntegerVariableFromLiteral(l));
1232 }
1233 vars->push_back(view);
1234 }
1235 }
1236}
1237
1239 const AffineExpression& capacity, SchedulingDemandHelper* demands_helper,
1240 Model* model, std::vector<IntegerVariable>* vars) {
1241 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1242 for (const AffineExpression& demand_expr : demands_helper->Demands()) {
1243 if (!integer_trail->IsFixed(demand_expr)) {
1244 vars->push_back(demand_expr.var);
1245 }
1246 }
1247 IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
1248 for (const auto& product : demands_helper->DecomposedEnergies()) {
1249 for (const auto& lit_val_val : product) {
1250 IntegerVariable view = kNoIntegerVariable;
1251 if (!encoder->LiteralOrNegationHasView(lit_val_val.literal, &view)) {
1252 view = model->Add(NewIntegerVariableFromLiteral(lit_val_val.literal));
1253 }
1254 vars->push_back(view);
1255 }
1256 }
1257
1258 if (!integer_trail->IsFixed(capacity)) {
1259 vars->push_back(capacity.var);
1260 }
1261}
1262
1263} // namespace sat
1264} // namespace operations_research
IntegerValue y
IntegerValue size
void ClearAll()
Sets all bits to 0.
Definition bitset.h:507
IndexType size() const
Returns how many bits this Bitset64 can hold.
Definition bitset.h:468
void Clear(IndexType i)
Sets the bit at position i to 0.
Definition bitset.h:510
void Resize(IndexType size)
Definition bitset.h:479
void Set(IndexType i)
Sets the bit at position i to 1.
Definition bitset.h:548
bool AddBinaryClause(Literal a, Literal b)
Definition clause.cc:531
void WatchLiteral(Literal l, int id, int watch_index=-1)
Definition integer.h:1844
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
Definition integer.h:1852
void SetPropagatorPriority(int id, int priority)
Definition integer.cc:2358
void WatchIntegerVariable(IntegerVariable i, int id, int watch_index=-1)
Definition integer.h:1876
int Register(PropagatorInterface *propagator)
Registers a propagator and returns its unique ids.
Definition integer.cc:2332
void CallOnNextPropagate(int id)
Add the given propagator to its queue.
Definition integer.cc:2108
ABSL_MUST_USE_RESULT bool LiteralOrNegationHasView(Literal lit, IntegerVariable *view=nullptr, bool *view_is_direct=nullptr) const
Definition integer.cc:582
ABSL_MUST_USE_RESULT bool ConditionalEnqueue(Literal lit, IntegerLiteral i_lit, std::vector< Literal > *literal_reason, std::vector< IntegerLiteral > *integer_reason)
Definition integer.cc:1269
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
Definition integer.h:1717
IntegerValue UpperBound(IntegerVariable i) const
Definition integer.h:1721
IntegerLiteral LowerBoundAsLiteral(IntegerVariable i) const
Definition integer.h:1760
bool ReportConflict(absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.h:1076
void AppendRelaxedLinearReason(IntegerValue slack, absl::Span< const IntegerValue > coeffs, absl::Span< const IntegerVariable > vars, std::vector< IntegerLiteral > *reason) const
Same as above but take in IntegerVariables instead of IntegerLiterals.
Definition integer.cc:1047
bool IsFixed(IntegerVariable i) const
Checks if the variable is fixed.
Definition integer.h:1725
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition integer.h:1817
void EnqueueLiteral(Literal literal, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.cc:1392
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Returns globally valid lower/upper bound on the given integer variable.
Definition integer.h:1812
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit)
Definition integer.h:972
SchedulingDemandHelper * GetOrCreateDemandHelper(SchedulingConstraintHelper *helper, absl::Span< const AffineExpression > demands)
Definition intervals.cc:206
AffineExpression Start(IntervalVariable i) const
Definition intervals.h:111
AffineExpression Size(IntervalVariable i) const
Definition intervals.h:110
AffineExpression End(IntervalVariable i) const
Definition intervals.h:112
void InitAllDecomposedEnergies()
Calls InitDecomposedEnergies on all SchedulingDemandHelper created.
Definition intervals.cc:222
Literal PresenceLiteral(IntervalVariable i) const
Definition intervals.h:91
void CreateDisjunctivePrecedenceLiteral(IntervalVariable a, IntervalVariable b)
Definition intervals.cc:85
LiteralIndex GetPrecedenceLiteral(IntervalVariable a, IntervalVariable b) const
Definition intervals.cc:178
bool CreatePrecedenceLiteral(IntervalVariable a, IntervalVariable b)
Definition intervals.cc:149
bool IsOptional(IntervalVariable i) const
Returns whether or not a interval is optional and the associated literal.
Definition intervals.h:88
IntervalVariable CreateInterval(IntegerVariable start, IntegerVariable end, IntegerVariable size, IntegerValue fixed_size, LiteralIndex is_present)
Definition intervals.cc:44
SchedulingConstraintHelper * GetOrCreateHelper(const std::vector< IntervalVariable > &variables, bool register_as_disjunctive_helper=false)
Definition intervals.cc:190
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff=IntegerValue(1))
void AddTerm(IntegerVariable var, IntegerValue coeff)
bool Add(IntegerVariable tail, IntegerVariable head, IntegerValue offset)
IntegerValue GetConditionalOffset(IntegerVariable a, IntegerVariable b) const
absl::Span< const Literal > GetConditionalEnforcements(IntegerVariable a, IntegerVariable b) const
Helper class to express a product as a linear constraint.
std::vector< LiteralValueValue > TryToDecompose(const AffineExpression &left, const AffineExpression &right)
BooleanVariable NewBooleanVariable()
Definition sat_solver.h:94
bool IncrementalPropagate(const std::vector< int > &watch_indices) final
Definition intervals.cc:298
absl::Span< const TaskTime > TaskByIncreasingStartMin()
Definition intervals.cc:569
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 AddStartMaxReason(int t, IntegerValue upper_bound)
Definition intervals.h:880
ABSL_MUST_USE_RESULT bool IncreaseEndMin(int t, IntegerValue value)
Definition intervals.cc:744
std::vector< IntegerLiteral > * MutableIntegerReason()
Definition intervals.h:451
void RegisterWith(GenericLiteralWatcher *watcher)
Definition intervals.cc:305
absl::Span< const TaskTime > TaskByIncreasingNegatedStartMax()
Definition intervals.cc:591
SchedulingConstraintHelper(const std::vector< IntervalVariable > &tasks, Model *model)
Definition intervals.cc:228
void AddSizeMaxReason(int t, IntegerValue upper_bound)
Definition intervals.h:866
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::Span< const AffineExpression > Sizes() const
Definition intervals.h:481
ABSL_MUST_USE_RESULT bool PushLiteral(Literal l)
Definition intervals.cc:760
const std::vector< ProfileEvent > & GetEnergyProfile()
Definition intervals.cc:632
ABSL_MUST_USE_RESULT bool PushTaskAbsence(int t)
Definition intervals.cc:765
ABSL_MUST_USE_RESULT bool PushIntegerLiteralIfTaskPresent(int t, IntegerLiteral lit)
Definition intervals.cc:714
absl::Span< const AffineExpression > Starts() const
Definition intervals.h:479
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 ResetFromSubset(const SchedulingConstraintHelper &other, absl::Span< const int > tasks)
Definition intervals.cc:396
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::Span< const TaskTime > TaskByDecreasingEndMax()
Definition intervals.cc:603
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
Definition intervals.cc:483
ABSL_MUST_USE_RESULT bool PushTaskPresence(int t)
Definition intervals.cc:781
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 ImportOtherReasons(const SchedulingConstraintHelper &other_helper)
Definition intervals.cc:840
IntegerValue GetCurrentMinDistanceBetweenTasks(int a, int b, bool add_reason_if_after=false)
Definition intervals.cc:510
IntegerValue GetMinOverlap(int t, IntegerValue start, IntegerValue end) const
Definition intervals.cc:858
void AddStartMinReason(int t, IntegerValue lower_bound)
Definition intervals.h:873
absl::Span< const TaskTime > TaskByIncreasingEndMin()
Definition intervals.cc:579
std::vector< LiteralValueValue > FilteredDecomposedEnergy(int index)
IntegerValue EnergyMinInWindow(int t, IntegerValue window_start, IntegerValue window_end)
const std::vector< std::vector< LiteralValueValue > > & DecomposedEnergies() const
Definition intervals.h:694
void OverrideLinearizedEnergies(absl::Span< const LinearExpression > energies)
Visible for testing.
void AddEnergyMinInWindowReason(int t, IntegerValue window_start, IntegerValue window_end)
SchedulingDemandHelper(absl::Span< const AffineExpression > demands, SchedulingConstraintHelper *helper, Model *model)
Definition intervals.cc:896
void OverrideDecomposedEnergies(const std::vector< std::vector< LiteralValueValue > > &energies)
const std::vector< AffineExpression > & Demands() const
Definition intervals.h:650
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:185
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:188
void push_back(const value_type &val)
int64_t a
Definition table.cc:44
int64_t value
IntVar * var
GRBmodel * model
int lit
int index
const bool DEBUG_MODE
Definition macros.h:24
std::function< IntegerVariable(Model *)> NewIntegerVariableFromLiteral(Literal lit)
Definition integer.h:1948
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
const LiteralIndex kNoLiteralIndex(-1)
std::function< int64_t(const Model &)> LowerBound(IntegerVariable v)
Definition integer.h:1955
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Returns the vector of the negated variables.
Definition integer.cc:51
std::function< void(Model *)> LowerOrEqual(IntegerVariable v, int64_t ub)
Definition integer.h:1998
void LoadConditionalLinearConstraint(const absl::Span< const Literal > enforcement_literals, const LinearConstraint &cst, Model *model)
LinearConstraint version.
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
void AddConditionalAffinePrecedence(const absl::Span< const Literal > enforcement_literals, AffineExpression left, AffineExpression right, Model *model)
void AppendVariablesFromCapacityAndDemands(const AffineExpression &capacity, SchedulingDemandHelper *demands_helper, Model *model, std::vector< IntegerVariable > *vars)
void AddWeightedSumLowerOrEqual(absl::Span< const Literal > enforcement_literals, absl::Span< const IntegerVariable > vars, absl::Span< const int64_t > coefficients, int64_t upper_bound, Model *model)
enforcement_literals => sum <= upper_bound
std::function< void(Model *)> GreaterOrEqual(IntegerVariable v, int64_t lb)
Definition integer.h:1983
void AddIntegerVariableFromIntervals(SchedulingConstraintHelper *helper, Model *model, std::vector< IntegerVariable > *vars)
Cuts helpers.
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)
Definition intervals.cc:865
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
const Variable x
Definition qp_tests.cc:127
int64_t demand
Definition resource.cc:126
double distance
Rev< int64_t > start_max
Rev< int64_t > end_max
Rev< int64_t > start_min
Rev< int64_t > end_min
std::optional< int64_t > end
int64_t start
IntegerLiteral LowerOrEqual(IntegerValue bound) const
var * coeff + constant <= bound.
Definition integer.h:1708