Google OR-Tools v9.11
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-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
14#ifndef OR_TOOLS_SAT_DISJUNCTIVE_H_
15#define OR_TOOLS_SAT_DISJUNCTIVE_H_
16
17#include <algorithm>
18#include <functional>
19#include <memory>
20#include <vector>
21
22#include "absl/types/span.h"
23#include "ortools/base/macros.h"
24#include "ortools/sat/integer.h"
26#include "ortools/sat/model.h"
31
32namespace operations_research {
33namespace sat {
34
35// Enforces a disjunctive (or no overlap) constraint on the given interval
36// variables. The intervals are interpreted as [start, end) and the constraint
37// enforces that no time point belongs to two intervals.
38//
39// TODO(user): This is not completely true for empty intervals (start == end).
40// Make sure such intervals are ignored by the constraint.
41void AddDisjunctive(const std::vector<IntervalVariable>& intervals,
42 Model* model);
43
44// Creates Boolean variables for all the possible precedences of the form (task
45// i is before task j) and forces that, for each couple of task (i,j), either i
46// is before j or j is before i. Do not create any other propagators.
48 const std::vector<IntervalVariable>& intervals, Model* model);
49
50// Helper class to compute the end-min of a set of tasks given their start-min
51// and size-min. In Petr Vilim's PhD "Global Constraints in Scheduling",
52// this corresponds to his Theta-tree except that we use a O(n) implementation
53// for most of the function here, not a O(log(n)) one.
54class TaskSet {
55 public:
56 explicit TaskSet(int num_tasks) { sorted_tasks_.ClearAndReserve(num_tasks); }
57
58 struct Entry {
59 int task;
60 IntegerValue start_min;
61 IntegerValue size_min;
62
63 // Note that the tie-breaking is not important here.
64 bool operator<(Entry other) const { return start_min < other.start_min; }
65 };
66
67 // Insertion and modification. These leave sorted_tasks_ sorted.
68 void Clear() {
69 sorted_tasks_.clear();
70 optimized_restart_ = 0;
71 }
72 void AddEntry(const Entry& e);
73
74 // Same as AddEntry({t, helper->ShiftedStartMin(t), helper->SizeMin(t)}).
75 // This is a minor optimization to not call SizeMin(t) twice.
76 void AddShiftedStartMinEntry(const SchedulingConstraintHelper& helper, int t);
77
78 // Advanced usage, if the entry is present, this assumes that its start_min is
79 // >= the end min without it, and update the datastructure accordingly.
80 void NotifyEntryIsNowLastIfPresent(const Entry& e);
81
82 // Advanced usage. Instead of calling many AddEntry(), it is more efficient to
83 // call AddUnsortedEntry() instead, but then Sort() MUST be called just after
84 // the insertions. Nothing is checked here, so it is up to the client to do
85 // that properly.
86 void AddUnsortedEntry(const Entry& e) { sorted_tasks_.push_back(e); }
87 void Sort() { std::sort(sorted_tasks_.begin(), sorted_tasks_.end()); }
88
89 // Returns the end-min for the task in the set. The time profile of the tasks
90 // packed to the left will always be a set of contiguous tasks separated by
91 // empty space:
92 //
93 // [Bunch of tasks] ... [Bunch of tasks] ... [critical tasks].
94 //
95 // We call "critical tasks" the last group. These tasks will be solely
96 // responsible for the end-min of the whole set. The returned
97 // critical_index will be the index of the first critical task in
98 // SortedTasks().
99 //
100 // A reason for the min end is:
101 // - The size-min of all the critical tasks.
102 // - The fact that all critical tasks have a start-min greater or equal to the
103 // first of them, that is SortedTasks()[critical_index].start_min.
104 //
105 // It is possible to behave like if one task was not in the set by setting
106 // task_to_ignore to the id of this task. This returns 0 if the set is empty
107 // in which case critical_index will be left unchanged.
108 IntegerValue ComputeEndMin(int task_to_ignore, int* critical_index) const;
109 IntegerValue ComputeEndMin() const;
110
111 // Warning, this is only valid if ComputeEndMin() was just called. It is the
112 // same index as if one called ComputeEndMin(-1, &critical_index), but saves
113 // another unneeded loop.
114 int GetCriticalIndex() const { return optimized_restart_; }
115
116 absl::Span<const Entry> SortedTasks() const { return sorted_tasks_; }
117
118 private:
119 FixedCapacityVector<Entry> sorted_tasks_;
120 mutable int optimized_restart_ = 0;
121};
122
123// Simple class to display statistics at the end if --v=1.
125 explicit PropagationStatistics(std::string _name, Model* model = nullptr)
126 : name(_name),
127 shared_stats(model == nullptr
128 ? nullptr
129 : model->GetOrCreate<SharedStatistics>()) {};
130
132 if (shared_stats == nullptr) return;
133 if (!VLOG_IS_ON(1)) return;
134 std::vector<std::pair<std::string, int64_t>> stats;
135 stats.push_back({absl::StrCat(name, "/num_calls"), num_calls});
136 stats.push_back({absl::StrCat(name, "/num_calls_with_propagation"),
138 stats.push_back(
139 {absl::StrCat(name, "/num_calls_with_conflicts"), num_conflicts});
140 stats.push_back(
141 {absl::StrCat(name, "/num_propagations"), num_propagations});
142 shared_stats->AddStats(stats);
143 }
144
149
155
156 const std::string name;
159
160 int64_t num_calls = 0;
161 int64_t num_calls_with_propagation = 0; // Only count if we did something.
162 int64_t num_conflicts = 0;
163 int64_t num_propagations = 0;
164};
165
166// ============================================================================
167// Below are many of the known propagation techniques for the disjunctive, each
168// implemented in only one time direction and in its own propagator class. The
169// Disjunctive() model function above will instantiate the used ones (according
170// to the solver parameters) in both time directions.
171//
172// See Petr Vilim PhD "Global Constraints in Scheduling" for a description of
173// some of the algorithm.
174// ============================================================================
175
177 public:
179 Model* model = nullptr)
180 : helper_(helper),
181 window_(new TaskTime[helper->NumTasks()]),
182 task_to_event_(new int[helper->NumTasks()]),
183 stats_("DisjunctiveOverloadChecker", model) {
184 task_by_increasing_end_max_.ClearAndReserve(helper->NumTasks());
185 }
186
187 bool Propagate() final;
189
190 private:
191 bool PropagateSubwindow(int relevant_size, IntegerValue global_window_end);
192
194
195 // Size assigned at construction, stay fixed afterwards.
196 std::unique_ptr<TaskTime[]> window_;
197 std::unique_ptr<int[]> task_to_event_;
198
199 FixedCapacityVector<TaskTime> task_by_increasing_end_max_;
200
201 ThetaLambdaTree<IntegerValue> theta_tree_;
203};
204
205// This one is a simpler version of DisjunctiveDetectablePrecedences, it
206// detect all implied precedences between TWO tasks and push bounds accordingly.
207// If we created all pairwise precedence Booleans, this would already be
208// propagated and in this case we don't create this propagator.
209//
210// Otherwise, this generate short reason and is good to do early as it
211// propagates a lot.
213 public:
215 Model* model = nullptr)
216 : helper_(helper), stats_("DisjunctiveSimplePrecedences", model) {}
217 bool Propagate() final;
219
220 private:
221 bool PropagateOneDirection();
222 bool Push(TaskTime before, int t);
223
226};
227
229 public:
232 Model* model = nullptr)
233 : time_direction_(time_direction),
234 helper_(helper),
235 task_set_(helper->NumTasks()),
236 stats_("DisjunctiveDetectablePrecedences", model) {
237 ranks_.resize(helper->NumTasks());
238 to_add_.ClearAndReserve(helper->NumTasks());
239 }
240 bool Propagate() final;
242
243 private:
244 bool PropagateWithRanks();
245 bool Push(IntegerValue task_set_end_min, int t);
246
247 FixedCapacityVector<int> to_add_;
248 std::vector<int> ranks_;
249
250 const bool time_direction_;
252 TaskSet task_set_;
254};
255
256// Singleton model class which is just a SchedulingConstraintHelper will all
257// the intervals.
259 public:
262 model->GetOrCreate<IntervalsRepository>()->AllIntervals(), model) {}
263};
264
265// This propagates the same things as DisjunctiveDetectablePrecedences, except
266// that it only sort the full set of intervals once and then work on a combined
267// set of disjunctives.
268template <bool time_direction>
269class CombinedDisjunctive : public PropagatorInterface {
270 public:
272
273 // After creation, this must be called for all the disjunctive constraints
274 // in the model.
275 void AddNoOverlap(absl::Span<const IntervalVariable> var);
276
277 bool Propagate() final;
278
279 private:
280 AllIntervalsHelper* helper_;
281 std::vector<std::vector<int>> task_to_disjunctives_;
282 std::vector<bool> task_is_added_;
283 std::vector<TaskSet> task_sets_;
284 std::vector<IntegerValue> end_mins_;
285};
286
288 public:
289 DisjunctiveNotLast(bool time_direction, SchedulingConstraintHelper* helper,
290 Model* model = nullptr)
291 : time_direction_(time_direction),
292 helper_(helper),
293 task_set_(helper->NumTasks()),
294 stats_("DisjunctiveNotLast", model) {
295 start_min_window_.ClearAndReserve(helper->NumTasks());
296 start_max_window_.ClearAndReserve(helper->NumTasks());
297 }
298 bool Propagate() final;
299 int RegisterWith(GenericLiteralWatcher* watcher);
300
301 private:
302 bool PropagateSubwindow();
303
304 FixedCapacityVector<TaskTime> start_min_window_;
305 FixedCapacityVector<TaskTime> start_max_window_;
306
307 const bool time_direction_;
309 TaskSet task_set_;
311};
312
314 public:
315 DisjunctiveEdgeFinding(bool time_direction,
317 Model* model = nullptr)
318 : time_direction_(time_direction),
319 helper_(helper),
320 stats_("DisjunctiveEdgeFinding", model) {
321 task_by_increasing_end_max_.ClearAndReserve(helper->NumTasks());
322 window_.ClearAndReserve(helper->NumTasks());
323 event_size_.ClearAndReserve(helper->NumTasks());
324 }
325 bool Propagate() final;
326 int RegisterWith(GenericLiteralWatcher* watcher);
327
328 private:
329 bool PropagateSubwindow(IntegerValue window_end_min);
330
331 const bool time_direction_;
333
334 // This only contains non-gray tasks.
335 FixedCapacityVector<TaskTime> task_by_increasing_end_max_;
336
337 // All these member are indexed in the same way.
339 ThetaLambdaTree<IntegerValue> theta_tree_;
340 FixedCapacityVector<IntegerValue> event_size_;
341
342 // Task indexed.
343 std::vector<int> non_gray_task_to_event_;
344 std::vector<bool> is_gray_;
345
347};
348
349// Exploits the precedences relations of the form "this set of disjoint
350// IntervalVariables must be performed before a given IntegerVariable". The
351// relations are computed with PrecedencesPropagator::ComputePrecedences().
353 public:
354 DisjunctivePrecedences(bool time_direction,
356 : time_direction_(time_direction),
357 helper_(helper),
358 integer_trail_(model->GetOrCreate<IntegerTrail>()),
359 precedence_relations_(model->GetOrCreate<PrecedenceRelations>()),
360 stats_("DisjunctivePrecedences", model) {
361 window_.ClearAndReserve(helper->NumTasks());
362 index_to_end_vars_.ClearAndReserve(helper->NumTasks());
363 indices_before_.ClearAndReserve(helper->NumTasks());
364 }
365
366 bool Propagate() final;
367 int RegisterWith(GenericLiteralWatcher* watcher);
368
369 private:
370 bool PropagateSubwindow();
371
372 const bool time_direction_;
374 IntegerTrail* integer_trail_;
375 PrecedenceRelations* precedence_relations_;
376
378 FixedCapacityVector<IntegerVariable> index_to_end_vars_;
379
380 FixedCapacityVector<int> indices_before_;
381 std::vector<bool> skip_;
382 std::vector<PrecedenceRelations::PrecedenceData> before_;
383
385};
386
387// This is an optimization for the case when we have a big number of such
388// pairwise constraints. This should be roughtly equivalent to what the general
389// disjunctive case is doing, but it dealt with variable size better and has a
390// lot less overhead.
392 public:
394 : helper_(helper) {}
395 bool Propagate() final;
396 int RegisterWith(GenericLiteralWatcher* watcher);
397
398 private:
400};
401
402} // namespace sat
403} // namespace operations_research
404
405#endif // OR_TOOLS_SAT_DISJUNCTIVE_H_
int RegisterWith(GenericLiteralWatcher *watcher)
DisjunctiveOverloadChecker(SchedulingConstraintHelper *helper, Model *model=nullptr)
Base class for CP like propagators.
Definition integer.h:1414
int NumTasks() const
Returns the number of task.
Definition intervals.h:293
Simple class to add statistics by name and print them at the end.
void AddStats(absl::Span< const std::pair< std::string, int64_t > > stats)
Adds a bunch of stats, adding count for the same key together.
void NotifyEntryIsNowLastIfPresent(const Entry &e)
void AddUnsortedEntry(const Entry &e)
Definition disjunctive.h:86
IntegerValue ComputeEndMin() const
absl::Span< const Entry > SortedTasks() const
void AddShiftedStartMinEntry(const SchedulingConstraintHelper &helper, int t)
void Clear()
Insertion and modification. These leave sorted_tasks_ sorted.
Definition disjunctive.h:68
IntVar * var
GRBmodel * model
void AddDisjunctiveWithBooleanPrecedencesOnly(const std::vector< IntervalVariable > &intervals, Model *model)
void AddDisjunctive(const std::vector< IntervalVariable > &intervals, Model *model)
In SWIG mode, we don't want anything besides these top-level includes.
STL namespace.
Simple class to display statistics at the end if –v=1.
PropagationStatistics(std::string _name, Model *model=nullptr)