Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
disjunctive.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 ORTOOLS_SAT_DISJUNCTIVE_H_
15#define ORTOOLS_SAT_DISJUNCTIVE_H_
16
17#include <algorithm>
18#include <cstdint>
19#include <memory>
20#include <string>
21#include <utility>
22#include <vector>
23
24#include "absl/log/log.h"
25#include "absl/log/vlog_is_on.h"
26#include "absl/strings/str_cat.h"
27#include "absl/types/span.h"
28#include "ortools/sat/integer.h"
30#include "ortools/sat/model.h"
35#include "ortools/sat/util.h"
39
40namespace operations_research {
41namespace sat {
42
43// Enforces a disjunctive (or no overlap) constraint on the given interval
44// variables. The intervals are interpreted as [start, end) and the constraint
45// enforces that no time point belongs to two intervals.
46//
47// TODO(user): This is not completely true for empty intervals (start == end).
48// Make sure such intervals are ignored by the constraint.
49void AddDisjunctive(const std::vector<Literal>& enforcement_literals,
50 const std::vector<IntervalVariable>& intervals,
51 Model* model);
52
53// Creates Boolean variables for all the possible precedences of the form (task
54// i is before task j) and forces that, for each couple of task (i,j), either i
55// is before j or j is before i. Do not create any other propagators.
57 absl::Span<const IntervalVariable> intervals, Model* model);
58
59// Helper class to compute the end-min of a set of tasks given their start-min
60// and size-min. In Petr Vilim's PhD "Global Constraints in Scheduling",
61// this corresponds to his Theta-tree except that we use a O(n) implementation
62// for most of the function here, not a O(log(n)) one.
63class TaskSet {
64 public:
67 : sorted_tasks_(storage) {}
68
69 // Insertion and modification. These leave sorted_tasks_ sorted.
70 void Clear() {
71 sorted_tasks_.clear();
72 optimized_restart_ = 0;
73 }
74 void AddEntry(const Entry& e);
75
76 // Same as AddEntry({t, helper->ShiftedStartMin(t), helper->SizeMin(t)}).
77 // This is a minor optimization to not call SizeMin(t) twice.
78 void AddShiftedStartMinEntry(const SchedulingConstraintHelper& helper, int t);
79
80 // Advanced usage, if the entry is present, this assumes that its start_min is
81 // >= the end min without it, and update the datastructure accordingly.
82 void NotifyEntryIsNowLastIfPresent(const Entry& e);
83
84 // Advanced usage. Instead of calling many AddEntry(), it is more efficient to
85 // call AddUnsortedEntry() instead, but then Sort() MUST be called just after
86 // the insertions. Nothing is checked here, so it is up to the client to do
87 // that properly.
88 void AddUnsortedEntry(const Entry& e) { sorted_tasks_.push_back(e); }
89 void Sort() { std::sort(sorted_tasks_.begin(), sorted_tasks_.end()); }
90
91 // Returns the end-min for the task in the set. The time profile of the tasks
92 // packed to the left will always be a set of contiguous tasks separated by
93 // empty space:
94 //
95 // [Bunch of tasks] ... [Bunch of tasks] ... [critical tasks].
96 //
97 // We call "critical tasks" the last group. These tasks will be solely
98 // responsible for the end-min of the whole set. The returned
99 // critical_index will be the index of the first critical task in
100 // SortedTasks().
101 //
102 // A reason for the min end is:
103 // - The size-min of all the critical tasks.
104 // - The fact that all critical tasks have a start-min greater or equal to the
105 // first of them, that is SortedTasks()[critical_index].start_min.
106 //
107 // It is possible to behave like if one task was not in the set by setting
108 // task_to_ignore to the id of this task. This returns 0 if the set is empty
109 // in which case critical_index will be left unchanged.
110 IntegerValue ComputeEndMin(int task_to_ignore, int* critical_index) const;
111 IntegerValue ComputeEndMin() const;
112
113 // Warning, this is only valid if ComputeEndMin() was just called. It is the
114 // same index as if one called ComputeEndMin(-1, &critical_index), but saves
115 // another unneeded loop.
116 int GetCriticalIndex() const { return optimized_restart_; }
117
118 absl::Span<const Entry> SortedTasks() const { return sorted_tasks_; }
119
120 private:
121 FixedCapacityVector<Entry>& sorted_tasks_;
122 mutable int optimized_restart_ = 0;
123};
124
125// Simple class to display statistics at the end if --v=1.
127 explicit PropagationStatistics(std::string _name, Model* model = nullptr)
128 : name(_name),
129 shared_stats(model == nullptr
130 ? nullptr
131 : model->GetOrCreate<SharedStatistics>()) {};
132
134 if (shared_stats == nullptr) return;
135 if (!VLOG_IS_ON(1)) return;
136 std::vector<std::pair<std::string, int64_t>> stats;
137 stats.push_back({absl::StrCat(name, "/num_calls"), num_calls});
138 stats.push_back({absl::StrCat(name, "/num_calls_with_propagation"),
140 stats.push_back(
141 {absl::StrCat(name, "/num_calls_with_conflicts"), num_conflicts});
142 stats.push_back(
143 {absl::StrCat(name, "/num_propagations"), num_propagations});
144 shared_stats->AddStats(stats);
145 }
146
151
157
158 const std::string name;
161
162 int64_t num_calls = 0;
163 int64_t num_calls_with_propagation = 0; // Only count if we did something.
164 int64_t num_conflicts = 0;
165 int64_t num_propagations = 0;
166};
167
168// ============================================================================
169// Below are many of the known propagation techniques for the disjunctive, each
170// implemented in only one time direction and in its own propagator class. The
171// Disjunctive() model function above will instantiate the used ones (according
172// to the solver parameters) in both time directions.
173//
174// See Petr Vilim PhD "Global Constraints in Scheduling" for a description of
175// some of the algorithm.
176// ============================================================================
177
179 public:
181 Model* model = nullptr)
182 : helper_(helper),
183 task_to_event_(new int[helper->NumTasks()]),
184 stats_("DisjunctiveOverloadChecker", model) {
185 // This is just needed to prevent use of uninitialized values.
186 std::fill(task_to_event_.get(), task_to_event_.get() + helper->NumTasks(),
187 -1);
188 }
189
190 bool Propagate() final;
192
193 private:
194 bool PropagateSubwindow(absl::Span<TaskTime> sub_window,
195 IntegerValue global_window_end,
196 absl::Span<const CachedTaskBounds>*
197 task_by_increasing_negated_shifted_end_max);
198
200
201 // Size assigned at construction, stay fixed afterwards.
202 std::unique_ptr<int[]> task_to_event_;
203
204 ThetaLambdaTree<IntegerValue> theta_tree_;
206};
207
208// This one is a simpler version of DisjunctiveDetectablePrecedences, it
209// detect all implied precedences between TWO tasks and push bounds accordingly.
210// If we created all pairwise precedence Booleans, this would already be
211// propagated and in this case we don't create this propagator.
212//
213// Otherwise, this generate short reason and is good to do early as it
214// propagates a lot.
216 public:
218 Model* model = nullptr)
219 : helper_(helper), stats_("DisjunctiveSimplePrecedences", model) {}
220 bool Propagate() final;
222
223 private:
224 bool PropagateOneDirection();
225 bool Push(TaskTime before, int t);
226
229};
230
232 public:
235 Model* model = nullptr)
236 : time_direction_(time_direction),
237 helper_(helper),
238 stats_("DisjunctiveDetectablePrecedences", model) {
239 ranks_.resize(helper->NumTasks());
240 to_add_.ClearAndReserve(helper->NumTasks());
241 }
242 bool Propagate() final;
244
245 private:
246 bool PropagateWithRanks();
247 bool Push(IntegerValue task_set_end_min, int t, TaskSet& task_set);
248
249 FixedCapacityVector<int> to_add_;
250 std::vector<int> ranks_;
251
252 const bool time_direction_;
255};
256
257// This propagates the same things as DisjunctiveDetectablePrecedences, except
258// that it only sort the full set of intervals once and then work on a combined
259// set of disjunctives.
260template <bool time_direction>
262 public:
263 explicit CombinedDisjunctive(Model* model);
264
265 // After creation, this must be called for all the disjunctive constraints
266 // in the model which have no enforcement literals.
267 void AddNoOverlap(absl::Span<const IntervalVariable> var);
268
269 bool Propagate() final;
270
271 private:
273 std::vector<std::vector<int>> task_to_disjunctives_;
274 std::vector<bool> task_is_added_;
275 std::vector<TaskSet> task_sets_;
276 std::vector<FixedCapacityVector<TaskSet::Entry>> task_set_storage_;
277 std::vector<IntegerValue> end_mins_;
278};
279
281 public:
282 DisjunctiveNotLast(bool time_direction, SchedulingConstraintHelper* helper,
283 Model* model = nullptr)
284 : time_direction_(time_direction),
285 helper_(helper),
286 stats_("DisjunctiveNotLast", model) {}
287 bool Propagate() final;
289
290 private:
291 bool PropagateSubwindow(TaskSet& task_set,
292 absl::Span<TaskTime> task_by_increasing_start_max,
293 absl::Span<TaskTime> task_by_increasing_end_max);
294
295 const bool time_direction_;
298};
299
301 public:
302 DisjunctiveEdgeFinding(bool time_direction,
304 Model* model = nullptr)
305 : time_direction_(time_direction),
306 helper_(helper),
307 stats_("DisjunctiveEdgeFinding", model) {
308 event_size_.ClearAndReserve(helper->NumTasks());
309 }
310 bool Propagate() final;
312
313 private:
314 bool PropagateSubwindow(
315 IntegerValue window_end_min, absl::Span<const TaskTime> window,
316 FixedCapacityVector<TaskTime>& task_by_increasing_end_max);
317
318 const bool time_direction_;
320
321 // All these member are indexed in the same way.
322 ThetaLambdaTree<IntegerValue> theta_tree_;
323 FixedCapacityVector<IntegerValue> event_size_;
324
325 // Task indexed.
326 std::vector<int> non_gray_task_to_event_;
327 std::vector<bool> is_gray_;
328
330};
331
332// Exploits the precedences relations of the form "this set of disjoint
333// IntervalVariables must be performed before a given IntegerVariable". The
334// relations are computed with PrecedencesPropagator::ComputePrecedences().
336 public:
337 DisjunctivePrecedences(bool time_direction,
338 SchedulingConstraintHelper* helper, Model* model)
339 : time_direction_(time_direction),
340 helper_(helper),
341 integer_trail_(*model->GetOrCreate<IntegerTrail>()),
342 precedence_relations_(model->GetOrCreate<EnforcedLinear2Bounds>()),
343 linear2_bounds_(model->GetOrCreate<Linear2Bounds>()),
344 time_limit_(model->GetOrCreate<TimeLimit>()),
345 stats_("DisjunctivePrecedences", model) {
346 index_to_end_vars_.ClearAndReserve(helper->NumTasks());
347 indices_before_.ClearAndReserve(helper->NumTasks());
348 }
349
350 bool Propagate() final;
352
353 private:
354 bool PropagateSubwindow(absl::Span<TaskTime> window);
355
356 const bool time_direction_;
358 const IntegerTrail& integer_trail_;
359 EnforcedLinear2Bounds* precedence_relations_;
360 Linear2Bounds* linear2_bounds_;
361 TimeLimit* time_limit_;
362
363 FixedCapacityVector<IntegerVariable> index_to_end_vars_;
364
365 FixedCapacityVector<std::pair<int, LinearExpression2Index>> indices_before_;
366 std::vector<bool> skip_;
367 std::vector<EnforcedLinear2Bounds::PrecedenceData> before_;
368
370};
371
372// This is an optimization for the case when we have a big number of such
373// pairwise constraints. This should be roughly equivalent to what the general
374// disjunctive case is doing, but it dealt with variable size better and has a
375// lot less overhead.
377 public:
379 : helper_(helper) {}
380 bool Propagate() final;
382
383 private:
385};
386
387} // namespace sat
388} // namespace operations_research
389
390#endif // ORTOOLS_SAT_DISJUNCTIVE_H_
void AddNoOverlap(absl::Span< const IntervalVariable > var)
DisjunctiveDetectablePrecedences(bool time_direction, SchedulingConstraintHelper *helper, Model *model=nullptr)
DisjunctiveEdgeFinding(bool time_direction, SchedulingConstraintHelper *helper, Model *model=nullptr)
DisjunctiveNotLast(bool time_direction, SchedulingConstraintHelper *helper, Model *model=nullptr)
int RegisterWith(GenericLiteralWatcher *watcher)
DisjunctiveOverloadChecker(SchedulingConstraintHelper *helper, Model *model=nullptr)
DisjunctivePrecedences(bool time_direction, SchedulingConstraintHelper *helper, Model *model)
DisjunctiveSimplePrecedences(SchedulingConstraintHelper *helper, Model *model=nullptr)
DisjunctiveWithTwoItems(SchedulingConstraintHelper *helper)
int RegisterWith(GenericLiteralWatcher *watcher)
void NotifyEntryIsNowLastIfPresent(const Entry &e)
SchedulingConstraintHelper::TaskInfo Entry
Definition disjunctive.h:65
TaskSet(FixedCapacityVector< Entry > &storage)
Definition disjunctive.h:66
void AddUnsortedEntry(const Entry &e)
Definition disjunctive.h:88
IntegerValue ComputeEndMin() const
absl::Span< const Entry > SortedTasks() const
void AddShiftedStartMinEntry(const SchedulingConstraintHelper &helper, int t)
void AddDisjunctive(const std::vector< Literal > &enforcement_literals, const std::vector< IntervalVariable > &intervals, Model *model)
void AddDisjunctiveWithBooleanPrecedencesOnly(absl::Span< const IntervalVariable > intervals, Model *model)
OR-Tools root namespace.
STL namespace.
PropagationStatistics(std::string _name, Model *model=nullptr)