Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
scheduling_helpers.h
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
14#ifndef OR_TOOLS_SAT_SCHEDULING_HELPERS_H_
15#define OR_TOOLS_SAT_SCHEDULING_HELPERS_H_
16
17#include <algorithm>
18#include <cstdint>
19#include <memory>
20#include <optional>
21#include <string>
22#include <vector>
23
24#include "absl/base/attributes.h"
25#include "absl/log/check.h"
26#include "absl/types/span.h"
28#include "ortools/sat/integer.h"
31#include "ortools/sat/model.h"
35#include "ortools/util/bitset.h"
37
38namespace operations_research {
39namespace sat {
40
41// An helper struct to sort task by time. This is used by the
42// SchedulingConstraintHelper but also by many scheduling propagators to sort
43// tasks.
44struct TaskTime {
46 IntegerValue time;
47 bool operator<(TaskTime other) const { return time < other.time; }
48 bool operator>(TaskTime other) const { return time > other.time; }
49};
50
51// We have some free space in TaskTime.
52// We stick the presence_lit to save an indirection in some algo.
53//
54// TODO(user): Experiment caching more value. In particular
55// TaskByIncreasingShiftedStartMin() could tie break task for better heuristics?
58 LiteralIndex presence_lit;
59 IntegerValue time;
60 bool operator<(CachedTaskBounds other) const { return time < other.time; }
61 bool operator>(CachedTaskBounds other) const { return time > other.time; }
62};
63
68 std::optional<Literal> is_present;
69
70 template <typename H>
71 friend H AbslHashValue(H h, const IntervalDefinition& i) {
72 return H::combine(std::move(h), i.start, i.end, i.size, i.is_present);
73 }
74
75 bool operator==(const IntervalDefinition& other) const {
76 return start == other.start && end == other.end && size == other.size &&
77 is_present == other.is_present;
78 }
79};
80
81// Helper class shared by the propagators that manage a given list of tasks.
82//
83// One of the main advantage of this class is that it allows to share the
84// vectors of tasks sorted by various criteria between propagator for a faster
85// code. It is also helpful to allow in-processing: the intervals that are
86// handled by this class are not necessarily the same as the ones in the model.
88 public:
89 // All the functions below refer to a task by its index t in the tasks
90 // vector given at construction.
91 SchedulingConstraintHelper(std::vector<AffineExpression> starts,
92 std::vector<AffineExpression> ends,
93 std::vector<AffineExpression> sizes,
94 std::vector<LiteralIndex> reason_for_presence,
95 Model* model);
96
97 // Temporary constructor.
98 // The class will not be usable until ResetFromSubset() is called.
99 //
100 // TODO(user): Remove this. It is a hack because the disjunctive class needs
101 // to fetch the maximum possible number of task at construction.
102 SchedulingConstraintHelper(int num_tasks, Model* model);
103
104 // This is a propagator so we can "cache" all the intervals relevant
105 // information. This gives good speedup. Note however that the info is stale
106 // except if a bound was pushed by this helper or if this was called. We run
107 // it at the highest priority, so that will mostly be the case at the
108 // beginning of each Propagate() call of the classes using this.
109 bool Propagate() final;
110 bool IncrementalPropagate(const std::vector<int>& watch_indices) final;
111 void RegisterWith(GenericLiteralWatcher* watcher);
112
113 // Resets the class to the same state as if it was constructed with
114 // the given subset of tasks from other.
115 ABSL_MUST_USE_RESULT bool ResetFromSubset(
116 const SchedulingConstraintHelper& other, absl::Span<const int> tasks);
117
118 // Returns the number of task.
119 int NumTasks() const { return starts_.size(); }
120
121 // Make sure the cached values are up to date. Also sets the time direction to
122 // either forward/backward. This will impact all the functions below. This
123 // MUST be called at the beginning of all Propagate() call that uses this
124 // helper.
125 void SetTimeDirection(bool is_forward);
126 bool CurrentTimeIsForward() const { return current_time_direction_; }
127 ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward);
128
129 // Helpers for the current bounds on the current task time window.
130 // [ (size-min) ... (size-min) ]
131 // ^ ^ ^ ^
132 // start-min end-min start-max end-max
133 //
134 // Note that for tasks with variable durations, we don't necessarily have
135 // duration-min between the XXX-min and XXX-max value.
136 //
137 // Remark: We use cached values for most of these function as this is faster.
138 // In practice, the cache will almost always be up to date, but not in corner
139 // cases where pushing the start of one task will change values for many
140 // others. This is fine as the new values will be picked up as we reach the
141 // propagation fixed point.
142 IntegerValue SizeMin(int t) const { return cached_size_min_[t]; }
143 IntegerValue SizeMax(int t) const {
144 // This one is "rare" so we don't cache it.
145 return integer_trail_->UpperBound(sizes_[t]);
146 }
147 IntegerValue StartMin(int t) const { return cached_start_min_[t]; }
148 IntegerValue EndMin(int t) const { return cached_end_min_[t]; }
149 IntegerValue StartMax(int t) const { return -cached_negated_start_max_[t]; }
150 IntegerValue EndMax(int t) const { return -cached_negated_end_max_[t]; }
151
152 IntegerValue LevelZeroSizeMin(int t) const {
153 // Note the variable that encodes the size of an absent task can be
154 // negative.
155 return std::max(IntegerValue(0),
156 integer_trail_->LevelZeroLowerBound(sizes_[t]));
157 }
158 IntegerValue LevelZeroStartMin(int t) const {
159 return integer_trail_->LevelZeroLowerBound(starts_[t]);
160 }
161 IntegerValue LevelZeroStartMax(int t) const {
162 return integer_trail_->LevelZeroUpperBound(starts_[t]);
163 }
164 IntegerValue LevelZeroEndMax(int t) const {
165 return integer_trail_->LevelZeroUpperBound(ends_[t]);
166 }
167
168 // In the presence of tasks with a variable size, we do not necessarily
169 // have start_min + size_min = end_min, we can instead have a situation
170 // like:
171 // | |<--- size-min --->|
172 // ^ ^ ^
173 // start-min | end-min
174 // |
175 // We define the "shifted start min" to be the right most time such that
176 // we known that we must have min-size "energy" to the right of it if the
177 // task is present. Using it in our scheduling propagators allows to propagate
178 // more in the presence of tasks with variable size (or optional task
179 // where we also do not necessarily have start_min + size_min = end_min.
180 //
181 // To explain this shifted start min, one must use the AddEnergyAfterReason().
182 IntegerValue ShiftedStartMin(int t) const {
183 return cached_shifted_start_min_[t];
184 }
185
186 // As with ShiftedStartMin(), we can compute the shifted end max (that is
187 // start_max + size_min.
188 IntegerValue ShiftedEndMax(int t) const {
189 return -cached_negated_shifted_end_max_[t];
190 }
191
192 bool StartIsFixed(int t) const;
193 bool EndIsFixed(int t) const;
194 bool SizeIsFixed(int t) const;
195
196 // Returns true if the corresponding fact is known for sure. A normal task is
197 // always present. For optional task for which the presence is still unknown,
198 // both of these function will return false.
199 bool IsOptional(int t) const;
200 bool IsPresent(int t) const;
201 bool IsAbsent(int t) const;
202
203 // Same if one already have the presence LiteralIndex of a task.
204 bool IsOptional(LiteralIndex lit) const;
205 bool IsPresent(LiteralIndex lit) const;
206 bool IsAbsent(LiteralIndex lit) const;
207
208 // Return a value so that End(a) + dist <= Start(b).
209 // Returns kMinInterValue if we don't have any such relation.
211 int a, int b, bool add_reason_if_after = false);
212
213 // We detected a precedence between two tasks.
214 // If we are at level zero, we might want to add the constraint.
215 // If we are at positive level, we might want to propagate the associated
216 // precedence literal if it exists.
217 bool PropagatePrecedence(int a, int b);
218
219 // Return the minimum overlap of interval i with the time window [start..end].
220 //
221 // Note: this is different from the mandatory part of an interval.
222 IntegerValue GetMinOverlap(int t, IntegerValue start, IntegerValue end) const;
223
224 // Returns a string with the current task bounds.
225 std::string TaskDebugString(int t) const;
226
227 // Sorts and returns the tasks in corresponding order at the time of the call.
228 // Note that we do not mean strictly-increasing/strictly-decreasing, there
229 // will be duplicate time values in these vectors.
230 //
231 // TODO(user): we could merge the first loop of IncrementalSort() with the
232 // loop that fill TaskTime.time at each call.
233 absl::Span<const TaskTime> TaskByIncreasingStartMin();
234 absl::Span<const TaskTime> TaskByDecreasingEndMax();
235
236 absl::Span<const TaskTime> TaskByIncreasingNegatedStartMax();
237 absl::Span<const TaskTime> TaskByIncreasingEndMin();
238
239 absl::Span<const CachedTaskBounds> TaskByIncreasingShiftedStartMin();
240
241 // Returns a sorted vector where each task appear twice, the first occurrence
242 // is at size (end_min - size_min) and the second one at (end_min).
243 //
244 // This is quite usage specific.
246 IntegerValue time;
247 int task;
249
250 bool operator<(const ProfileEvent& other) const {
251 if (time == other.time) {
252 if (task == other.task) return is_first > other.is_first;
253 return task < other.task;
254 }
255 return time < other.time;
256 }
257 };
258 const std::vector<ProfileEvent>& GetEnergyProfile();
259
260 // Functions to clear and then set the current reason.
261 void ClearReason();
262 void AddPresenceReason(int t);
263 void AddAbsenceReason(int t);
264 void AddSizeMinReason(int t);
265 void AddSizeMinReason(int t, IntegerValue lower_bound);
266 void AddSizeMaxReason(int t, IntegerValue upper_bound);
267 void AddStartMinReason(int t, IntegerValue lower_bound);
268 void AddStartMaxReason(int t, IntegerValue upper_bound);
269 void AddEndMinReason(int t, IntegerValue lower_bound);
270 void AddEndMaxReason(int t, IntegerValue upper_bound);
271 void AddShiftedEndMaxReason(int t, IntegerValue upper_bound);
272
273 void AddEnergyAfterReason(int t, IntegerValue energy_min, IntegerValue time);
274 void AddEnergyMinInIntervalReason(int t, IntegerValue min, IntegerValue max);
275
276 // Adds the reason why task "before" must be before task "after".
277 // That is StartMax(before) < EndMin(after).
278 void AddReasonForBeingBefore(int before, int after);
279
280 // It is also possible to directly manipulates the underlying reason vectors
281 // that will be used when pushing something.
282 std::vector<Literal>* MutableLiteralReason() { return &literal_reason_; }
283 std::vector<IntegerLiteral>* MutableIntegerReason() {
284 return &integer_reason_;
285 }
286
287 // Push something using the current reason. Note that IncreaseStartMin() will
288 // also increase the end-min, and DecreaseEndMax() will also decrease the
289 // start-max.
290 //
291 // Important: IncreaseStartMin() and DecreaseEndMax() can be called on an
292 // optional interval whose presence is still unknown and push a bound
293 // conditioned on its presence. The functions will do the correct thing
294 // depending on whether or not the start_min/end_max are optional variables
295 // whose presence implies the interval presence.
296 ABSL_MUST_USE_RESULT bool IncreaseStartMin(int t, IntegerValue value);
297 ABSL_MUST_USE_RESULT bool IncreaseEndMin(int t, IntegerValue value);
298 ABSL_MUST_USE_RESULT bool DecreaseEndMax(int t, IntegerValue value);
299 ABSL_MUST_USE_RESULT bool PushLiteral(Literal l);
300 ABSL_MUST_USE_RESULT bool PushTaskAbsence(int t);
301 ABSL_MUST_USE_RESULT bool PushTaskPresence(int t);
302 ABSL_MUST_USE_RESULT bool PushIntegerLiteral(IntegerLiteral lit);
303 ABSL_MUST_USE_RESULT bool ReportConflict();
304 ABSL_MUST_USE_RESULT bool PushIntegerLiteralIfTaskPresent(int t,
305 IntegerLiteral lit);
306
307 absl::Span<const AffineExpression> Starts() const { return starts_; }
308 absl::Span<const AffineExpression> Ends() const { return ends_; }
309 absl::Span<const AffineExpression> Sizes() const { return sizes_; }
310
312 return IntervalDefinition{
313 .start = starts_[index],
314 .end = ends_[index],
315 .size = sizes_[index],
316 .is_present = (reason_for_presence_[index] == kNoLiteralIndex
317 ? std::optional<Literal>()
318 : Literal(reason_for_presence_[index]))};
319 }
320
321 Literal PresenceLiteral(int index) const {
322 DCHECK(IsOptional(index));
323 return Literal(reason_for_presence_[index]);
324 }
325
326 // Registers the given propagator id to be called if any of the tasks
327 // in this class change. Note that we do not watch size max though.
328 void WatchAllTasks(int id);
329
330 // Manages the other helper (used by the diffn constraint).
331 //
332 // For each interval appearing in a reason on this helper, another reason
333 // will be added. This other reason specifies that on the other helper, the
334 // corresponding interval overlaps 'event'.
336 absl::Span<const int> map_to_other_helper,
337 IntegerValue event) {
338 CHECK(other_helper != nullptr);
339 other_helper_ = other_helper;
340 map_to_other_helper_ = map_to_other_helper;
341 event_for_other_helper_ = event;
342 }
343
344 bool HasOtherHelper() const { return other_helper_ != nullptr; }
345
346 void ClearOtherHelper() { other_helper_ = nullptr; }
347
348 // Adds to this helper reason all the explanation of the other helper.
349 // This checks that other_helper_ is null.
350 //
351 // This is used in the 2D energetic reasoning in the diffn constraint.
352 void ImportOtherReasons(const SchedulingConstraintHelper& other_helper);
353
354 // TODO(user): Change the propagation loop code so that we don't stop
355 // pushing in the middle of the propagation as more advanced propagator do
356 // not handle this correctly.
357 bool InPropagationLoop() const { return integer_trail_->InPropagationLoop(); }
358
360 return sat_solver_->CurrentDecisionLevel();
361 }
362
363 private:
364 // Tricky: when a task is optional, it is possible it size min is negative,
365 // but we know that if a task is present, its size should be >= 0. So in the
366 // reason, when we need the size_min and it is currently negative, we can just
367 // ignore it and use zero instead.
368 AffineExpression NegatedSizeOrZero(int t) {
369 if (integer_trail_->LowerBound(sizes_[t]) <= 0) {
370 return AffineExpression(0);
371 }
372 return sizes_[t].Negated();
373 }
374
375 // Generic reason for a <= upper_bound, given that a = b + c in case the
376 // current upper bound of a is not good enough.
377 void AddGenericReason(const AffineExpression& a, IntegerValue upper_bound,
378 const AffineExpression& b, const AffineExpression& c);
379
380 void InitSortedVectors();
381 ABSL_MUST_USE_RESULT bool UpdateCachedValues(int t);
382
383 // Internal function for IncreaseStartMin()/DecreaseEndMax().
384 bool PushIntervalBound(int t, IntegerLiteral lit);
385
386 // This will be called on any interval that is part of a reason or
387 // a bound push. Since the last call to ClearReason(), for each unique
388 // t, we will add once to other_helper_ the reason for t containing
389 // the point event_for_other_helper_.
390 void AddOtherReason(int t);
391
392 // Import the reasons on the other helper into this helper.
393 void ImportOtherReasons();
394
395 Model* model_;
396 SatSolver* sat_solver_;
397 const VariablesAssignment& assignment_;
398 IntegerTrail* integer_trail_;
399 GenericLiteralWatcher* watcher_;
400 PrecedenceRelations* precedence_relations_;
401
402 // The current direction of time, true for forward, false for backward.
403 bool current_time_direction_ = true;
404
405 // All the underlying variables of the tasks.
406 // The vectors are indexed by the task index t.
407 std::vector<AffineExpression> starts_;
408 std::vector<AffineExpression> ends_;
409 std::vector<AffineExpression> sizes_;
410 std::vector<LiteralIndex> reason_for_presence_;
411
412 // The negation of the start/end variable so that SetTimeDirection()
413 // can do its job in O(1) instead of calling NegationOf() on each entry.
414 std::vector<AffineExpression> minus_starts_;
415 std::vector<AffineExpression> minus_ends_;
416
417 // This is used to detect when we need to invalidate the cache.
418 int64_t saved_num_backtracks_ = 0;
419
420 // The caches of all relevant interval values.
421 // These are initially of size capacity and never resized.
422 //
423 // TODO(user): Because of std::swap() in SetTimeDirection, we cannot mark
424 // most of them as "const" and as a result we loose some performance since
425 // the address need to be re-fetched on most access.
426 const int capacity_;
427 const std::unique_ptr<IntegerValue[]> cached_size_min_;
428 std::unique_ptr<IntegerValue[]> cached_start_min_;
429 std::unique_ptr<IntegerValue[]> cached_end_min_;
430 std::unique_ptr<IntegerValue[]> cached_negated_start_max_;
431 std::unique_ptr<IntegerValue[]> cached_negated_end_max_;
432 std::unique_ptr<IntegerValue[]> cached_shifted_start_min_;
433 std::unique_ptr<IntegerValue[]> cached_negated_shifted_end_max_;
434
435 // Sorted vectors returned by the TasksBy*() functions.
436 std::vector<TaskTime> task_by_increasing_start_min_;
437 std::vector<TaskTime> task_by_decreasing_end_max_;
438
439 bool recompute_by_start_max_ = true;
440 bool recompute_by_end_min_ = true;
441 std::vector<TaskTime> task_by_increasing_negated_start_max_;
442 std::vector<TaskTime> task_by_increasing_end_min_;
443
444 // Sorted vector returned by GetEnergyProfile().
445 bool recompute_energy_profile_ = true;
446 std::vector<ProfileEvent> energy_profile_;
447
448 // This one is the most commonly used, so we optimized a bit more its
449 // computation by detecting when there is nothing to do.
450 std::vector<CachedTaskBounds> task_by_increasing_shifted_start_min_;
451 std::vector<CachedTaskBounds> task_by_negated_shifted_end_max_;
452 bool recompute_shifted_start_min_ = true;
453 bool recompute_negated_shifted_end_max_ = true;
454
455 // If recompute_cache_[t] is true, then we need to update all the cached
456 // value for the task t in SynchronizeAndSetTimeDirection().
457 bool recompute_all_cache_ = true;
458 Bitset64<int> recompute_cache_;
459
460 // For large problems, LNS will have a lot of fixed intervals.
461 // And fixed intervals will never changes, so there is no point recomputing
462 // the cache for them.
463 std::vector<int> non_fixed_intervals_;
464
465 // Reason vectors.
466 std::vector<Literal> literal_reason_;
467 std::vector<IntegerLiteral> integer_reason_;
468
469 // Optional 'proxy' helper used in the diffn constraint.
470 SchedulingConstraintHelper* other_helper_ = nullptr;
471 absl::Span<const int> map_to_other_helper_;
472 IntegerValue event_for_other_helper_;
473 std::vector<bool> already_added_to_other_reasons_;
474
475 // List of watcher to "wake-up" each time one of the task bounds changes.
476 std::vector<int> propagator_ids_;
477};
478
479// Helper class for cumulative constraint to wrap demands and expose concept
480// like energy.
481//
482// In a cumulative constraint, an interval always has a size and a demand, but
483// it can also have a set of "selector" literals each associated with a fixed
484// size / fixed demands. This allows more precise energy estimation.
485//
486// TODO(user): Cache energy min and reason for the non O(1) cases.
488 public:
489 // Hack: this can be called with and empty demand vector as long as
490 // OverrideEnergies() is called to define the energies.
491 SchedulingDemandHelper(absl::Span<const AffineExpression> demands,
492 SchedulingConstraintHelper* helper, Model* model);
493
494 // When defined, the interval will consume this much demand during its whole
495 // duration. Some propagator only relies on the "energy" and thus never uses
496 // this.
497 IntegerValue DemandMin(int t) const;
498 IntegerValue DemandMax(int t) const;
499 IntegerValue LevelZeroDemandMin(int t) const {
500 return integer_trail_->LevelZeroLowerBound(demands_[t]);
501 }
502 bool DemandIsFixed(int t) const;
503 void AddDemandMinReason(int t);
504 void AddDemandMinReason(int t, IntegerValue min_demand);
505 const std::vector<AffineExpression>& Demands() const { return demands_; }
506
507 // Adds the linearized demand (either the affine demand expression, or the
508 // demand part of the decomposed energy if present) to the builder.
509 // It returns false and do not add any term to the builder.if any literal
510 // involved has no integer view.
511 ABSL_MUST_USE_RESULT bool AddLinearizedDemand(
512 int t, LinearConstraintBuilder* builder) const;
513
514 // The "energy" is usually size * demand, but in some non-conventional usage
515 // it might have a more complex formula. In all case, the energy is assumed
516 // to be only consumed during the interval duration.
517 //
518 // Returns false if the energy can overflow and was not computed.
519 //
520 // IMPORTANT: One must call CacheAllEnergyValues() for the values to be
521 // updated. TODO(user): this is error prone, maybe we should revisit. But if
522 // there is many alternatives, we don't want to rescan the list more than a
523 // linear number of time per propagation.
524 //
525 // TODO(user): Add more complex EnergyMinBefore(time) once we also support
526 // expressing the interval as a set of alternatives.
527 //
528 // At level 0, it will filter false literals from decomposed energies.
530 IntegerValue EnergyMin(int t) const { return cached_energies_min_[t]; }
531 IntegerValue EnergyMax(int t) const { return cached_energies_max_[t]; }
532 bool EnergyIsQuadratic(int t) const { return energy_is_quadratic_[t]; }
533 void AddEnergyMinReason(int t);
534
535 // Returns the energy min in [start, end].
536 //
537 // Note(user): These functions are not in O(1) if the decomposition is used,
538 // so we have to be careful in not calling them too often.
539 IntegerValue EnergyMinInWindow(int t, IntegerValue window_start,
540 IntegerValue window_end);
541 void AddEnergyMinInWindowReason(int t, IntegerValue window_start,
542 IntegerValue window_end);
543
544 // Important: This might not do anything depending on the representation of
545 // the energy we have.
546 ABSL_MUST_USE_RESULT bool DecreaseEnergyMax(int t, IntegerValue value);
547
548 // Different optional representation of the energy of an interval.
549 //
550 // Important: first value is size, second value is demand.
551 const std::vector<std::vector<LiteralValueValue>>& DecomposedEnergies()
552 const {
553 return decomposed_energies_;
554 }
555
556 // Visible for testing.
558 const std::vector<std::vector<LiteralValueValue>>& energies);
559 // Returns the decomposed energy terms compatible with the current literal
560 // assignment. It must not be used to create reasons if not at level 0.
561 // It returns en empty vector if the decomposed energy is not available.
562 //
563 // Important: first value is size, second value is demand.
564 std::vector<LiteralValueValue> FilteredDecomposedEnergy(int index);
565
566 // Init all decomposed energies. It needs probing to be finished. This happens
567 // after the creation of the helper.
569
570 private:
571 IntegerValue SimpleEnergyMin(int t) const;
572 IntegerValue SimpleEnergyMax(int t) const;
573 IntegerValue DecomposedEnergyMin(int t) const;
574 IntegerValue DecomposedEnergyMax(int t) const;
575
576 IntegerTrail* integer_trail_;
577 ProductDecomposer* product_decomposer_;
578 SatSolver* sat_solver_; // To get the current propagation level.
579 const VariablesAssignment& assignment_;
580 std::vector<AffineExpression> demands_;
582
583 // Cached value of the energies, as it can be a bit costly to compute.
584 std::vector<IntegerValue> cached_energies_min_;
585 std::vector<IntegerValue> cached_energies_max_;
586 std::vector<bool> energy_is_quadratic_;
587
588 // A representation of the energies as a set of alternative.
589 // If subvector is empty, we don't have this representation.
590 std::vector<std::vector<LiteralValueValue>> decomposed_energies_;
591};
592
593// =============================================================================
594// Utilities
595// =============================================================================
596
597IntegerValue ComputeEnergyMinInWindow(
598 IntegerValue start_min, IntegerValue start_max, IntegerValue end_min,
599 IntegerValue end_max, IntegerValue size_min, IntegerValue demand_min,
600 absl::Span<const LiteralValueValue> filtered_energy,
601 IntegerValue window_start, IntegerValue window_end);
602
603// =============================================================================
604// SchedulingConstraintHelper inlined functions.
605// =============================================================================
606
607inline bool SchedulingConstraintHelper::StartIsFixed(int t) const {
608 return integer_trail_->IsFixed(starts_[t]);
609}
610
612 return integer_trail_->IsFixed(ends_[t]);
613}
614
616 return integer_trail_->IsFixed(sizes_[t]);
617}
618
620 DCHECK_GE(t, 0);
621 DCHECK_LT(t, reason_for_presence_.size());
622 return reason_for_presence_.data()[t] != kNoLiteralIndex;
624
625inline bool SchedulingConstraintHelper::IsPresent(int t) const {
626 DCHECK_GE(t, 0);
627 DCHECK_LT(t, reason_for_presence_.size());
628 const LiteralIndex lit = reason_for_presence_.data()[t];
629 if (lit == kNoLiteralIndex) return true;
630 return assignment_.LiteralIsTrue(Literal(lit));
631}
632
633inline bool SchedulingConstraintHelper::IsAbsent(int t) const {
634 DCHECK_GE(t, 0);
635 DCHECK_LT(t, reason_for_presence_.size());
636 const LiteralIndex lit = reason_for_presence_.data()[t];
637 if (lit == kNoLiteralIndex) return false;
638 return assignment_.LiteralIsFalse(Literal(lit));
639}
640
641inline bool SchedulingConstraintHelper::IsOptional(LiteralIndex lit) const {
642 return lit != kNoLiteralIndex;
643}
644
645inline bool SchedulingConstraintHelper::IsPresent(LiteralIndex lit) const {
646 if (lit == kNoLiteralIndex) return true;
647 return assignment_.LiteralIsTrue(Literal(lit));
648}
650inline bool SchedulingConstraintHelper::IsAbsent(LiteralIndex lit) const {
651 if (lit == kNoLiteralIndex) return false;
652 return assignment_.LiteralIsFalse(Literal(lit));
653}
656 integer_reason_.clear();
657 literal_reason_.clear();
658 if (other_helper_) {
659 other_helper_->ClearReason();
660 already_added_to_other_reasons_.assign(NumTasks(), false);
661 }
662}
663
665 DCHECK(IsPresent(t));
666 AddOtherReason(t);
667 if (reason_for_presence_[t] != kNoLiteralIndex) {
668 literal_reason_.push_back(Literal(reason_for_presence_[t]).Negated());
669 }
670}
671
673 DCHECK(IsAbsent(t));
674 AddOtherReason(t);
675 if (reason_for_presence_[t] != kNoLiteralIndex) {
676 literal_reason_.push_back(Literal(reason_for_presence_[t]));
677 }
678}
679
682}
683
684inline void SchedulingConstraintHelper::AddGenericReason(
685 const AffineExpression& a, IntegerValue upper_bound,
686 const AffineExpression& b, const AffineExpression& c) {
687 if (integer_trail_->UpperBound(a) <= upper_bound) {
688 if (a.var != kNoIntegerVariable) {
689 integer_reason_.push_back(a.LowerOrEqual(upper_bound));
690 }
691 return;
692 }
693 CHECK_NE(a.var, kNoIntegerVariable);
694
695 // Here we assume that the upper_bound on a comes from the bound on b + c.
696 const IntegerValue slack = upper_bound - integer_trail_->UpperBound(b) -
697 integer_trail_->UpperBound(c);
698 CHECK_GE(slack, 0);
699 if (b.var == kNoIntegerVariable && c.var == kNoIntegerVariable) return;
700 if (b.var == kNoIntegerVariable) {
701 integer_reason_.push_back(c.LowerOrEqual(upper_bound - b.constant));
702 } else if (c.var == kNoIntegerVariable) {
703 integer_reason_.push_back(b.LowerOrEqual(upper_bound - c.constant));
704 } else {
705 integer_trail_->AppendRelaxedLinearReason(
706 slack, {b.coeff, c.coeff}, {NegationOf(b.var), NegationOf(c.var)},
707 &integer_reason_);
708 }
709}
710
712 int t, IntegerValue lower_bound) {
713 AddOtherReason(t);
714 DCHECK(!IsAbsent(t));
715 if (lower_bound <= 0) return;
716 AddGenericReason(sizes_[t].Negated(), -lower_bound, minus_ends_[t],
717 starts_[t]);
718}
719
721 int t, IntegerValue upper_bound) {
722 AddOtherReason(t);
723 DCHECK(!IsAbsent(t));
724 AddGenericReason(sizes_[t], upper_bound, ends_[t], minus_starts_[t]);
725}
726
728 int t, IntegerValue lower_bound) {
729 AddOtherReason(t);
730 DCHECK(!IsAbsent(t));
731 AddGenericReason(minus_starts_[t], -lower_bound, minus_ends_[t], sizes_[t]);
732}
733
735 int t, IntegerValue upper_bound) {
736 AddOtherReason(t);
737 DCHECK(!IsAbsent(t));
738 AddGenericReason(starts_[t], upper_bound, ends_[t], NegatedSizeOrZero(t));
739}
740
742 int t, IntegerValue lower_bound) {
743 AddOtherReason(t);
744 DCHECK(!IsAbsent(t));
745 AddGenericReason(minus_ends_[t], -lower_bound, minus_starts_[t],
746 NegatedSizeOrZero(t));
747}
748
750 int t, IntegerValue upper_bound) {
751 AddOtherReason(t);
752 DCHECK(!IsAbsent(t));
753 AddGenericReason(ends_[t], upper_bound, starts_[t], sizes_[t]);
754}
755
757 int t, IntegerValue upper_bound) {
758 AddStartMaxReason(t, upper_bound - SizeMin(t));
759}
762 int t, IntegerValue energy_min, IntegerValue time) {
763 if (StartMin(t) >= time) {
764 AddStartMinReason(t, time);
765 } else {
766 AddEndMinReason(t, time + energy_min);
767 }
768 AddSizeMinReason(t, energy_min);
769}
770
772 int t, IntegerValue time_min, IntegerValue time_max) {
773 const IntegerValue energy_min = SizeMin(t);
774 CHECK_LE(time_min + energy_min, time_max);
775 if (StartMin(t) >= time_min) {
776 AddStartMinReason(t, time_min);
777 } else {
778 AddEndMinReason(t, time_min + energy_min);
779 }
780 if (EndMax(t) <= time_max) {
781 AddEndMaxReason(t, time_max);
782 } else {
783 AddStartMaxReason(t, time_max - energy_min);
784 }
785 AddSizeMinReason(t, energy_min);
786}
787
788// Cuts helpers.
790 kStart = 1 << 0,
791 kEnd = 1 << 1,
792 kSize = 1 << 2,
793 kPresence = 1 << 3,
796 Model* model,
797 std::vector<IntegerVariable>* vars,
798 int mask);
799
801 const AffineExpression& capacity, SchedulingDemandHelper* demands_helper,
802 Model* model, std::vector<IntegerVariable>* vars);
803
804} // namespace sat
805} // namespace operations_research
806
807#endif // OR_TOOLS_SAT_SCHEDULING_HELPERS_H_
Definition model.h:341
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
Definition integer.h:1317
bool IsFixed(IntegerVariable i) const
Checks if the variable is fixed.
Definition integer.h:1325
Helper class to express a product as a linear constraint.
bool IncrementalPropagate(const std::vector< int > &watch_indices) final
void AddReasonForBeingBefore(int before, int after)
Produces a relaxed reason for StartMax(before) < EndMin(after).
IntervalDefinition GetIntervalDefinition(int index) const
void AddEnergyAfterReason(int t, IntegerValue energy_min, IntegerValue time)
void AddStartMaxReason(int t, IntegerValue upper_bound)
ABSL_MUST_USE_RESULT bool IncreaseEndMin(int t, IntegerValue value)
void AddSizeMaxReason(int t, IntegerValue upper_bound)
ABSL_MUST_USE_RESULT bool DecreaseEndMax(int t, IntegerValue value)
int NumTasks() const
Returns the number of task.
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 ClearReason()
Functions to clear and then set the current reason.
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)
void SetOtherHelper(SchedulingConstraintHelper *other_helper, absl::Span< const int > map_to_other_helper, IntegerValue event)
std::string TaskDebugString(int t) const
Returns a string with the current task bounds.
ABSL_MUST_USE_RESULT bool PushIntegerLiteral(IntegerLiteral lit)
void ImportOtherReasons(const SchedulingConstraintHelper &other_helper)
void AddShiftedEndMaxReason(int t, IntegerValue upper_bound)
IntegerValue GetCurrentMinDistanceBetweenTasks(int a, int b, bool add_reason_if_after=false)
IntegerValue GetMinOverlap(int t, IntegerValue start, IntegerValue end) const
void AddStartMinReason(int t, IntegerValue lower_bound)
void AddEnergyMinInIntervalReason(int t, IntegerValue min, IntegerValue max)
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)
Visible for testing.
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
const LiteralIndex kNoLiteralIndex(-1)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Returns the vector of the negated variables.
Definition integer.cc:52
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)
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)
In SWIG mode, we don't want anything besides these top-level includes.
ClosedInterval::Iterator end(ClosedInterval interval)
IntegerLiteral LowerOrEqual(IntegerValue bound) const
var * coeff + constant <= bound.
bool operator>(CachedTaskBounds other) const
bool operator<(CachedTaskBounds other) const
friend H AbslHashValue(H h, const IntervalDefinition &i)
bool operator==(const IntervalDefinition &other) const
bool operator<(TaskTime other) const
bool operator>(TaskTime other) const