Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
sched_constraints.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
14// This file contains implementations of several scheduling constraints.
15// The implemented constraints are:
16//
17// * Cover constraints: ensure that an interval is the convex hull of
18// a set of interval variables. This includes the performed status
19// (one interval performed implies the cover var performed, all
20// intervals unperformed implies the cover var unperformed, cover
21// var unperformed implies all intervals unperformed, cover var
22// performed implis at least one interval performed).
23
24#include <algorithm>
25#include <cstdint>
26#include <limits>
27#include <string>
28#include <vector>
29
30#include "absl/base/attributes.h"
31#include "absl/log/check.h"
32#include "absl/strings/str_format.h"
33#include "absl/strings/string_view.h"
37
38namespace operations_research {
39namespace {
40class TreeArrayConstraint : public Constraint {
41 public:
42 enum PerformedStatus { UNPERFORMED, PERFORMED, UNDECIDED };
43
44 TreeArrayConstraint(Solver* const solver,
45 const std::vector<IntervalVar*>& vars,
46 IntervalVar* const target_var)
47 : Constraint(solver),
48 vars_(vars),
49 target_var_(target_var),
50 block_size_(solver->parameters().array_split_size()) {
51 std::vector<int> lengths;
52 lengths.push_back(vars_.size());
53 while (lengths.back() > 1) {
54 const int current = lengths.back();
55 lengths.push_back((current + block_size_ - 1) / block_size_);
56 }
57 tree_.resize(lengths.size());
58 for (int i = 0; i < lengths.size(); ++i) {
59 tree_[i].resize(lengths[lengths.size() - i - 1]);
60 }
61 DCHECK_GE(tree_.size(), 1);
62 DCHECK_EQ(1, tree_[0].size());
63 root_node_ = &tree_[0][0];
64 }
65
66 std::string DebugStringInternal(absl::string_view name) const {
67 return absl::StrFormat("Cover(%s) == %s", JoinDebugStringPtr(vars_, ", "),
68 target_var_->DebugString());
69 }
70
71 void AcceptInternal(const std::string& name,
72 ModelVisitor* const visitor) const {
73 visitor->BeginVisitConstraint(name, this);
74 visitor->VisitIntervalArrayArgument(ModelVisitor::kIntervalsArgument,
75 vars_);
76 visitor->VisitIntervalArgument(ModelVisitor::kTargetArgument, target_var_);
77 visitor->EndVisitConstraint(name, this);
78 }
79
80 // Reduce the range of a given node (interval, state).
81 void ReduceDomain(int depth, int position, int64_t new_start_min,
82 int64_t new_start_max, int64_t new_end_min,
83 int64_t new_end_max, PerformedStatus performed) {
84 NodeInfo* const info = &tree_[depth][position];
85 if (new_start_min > info->start_min.Value()) {
86 info->start_min.SetValue(solver(), new_start_min);
87 }
88 if (new_start_max < info->start_max.Value()) {
89 info->start_max.SetValue(solver(), new_start_max);
90 }
91 if (new_end_min > info->end_min.Value()) {
92 info->end_min.SetValue(solver(), new_end_min);
93 }
94 if (new_end_max < info->end_max.Value()) {
95 info->end_max.SetValue(solver(), new_end_max);
96 }
97 if (performed != UNDECIDED) {
98 CHECK(performed == info->performed.Value() ||
99 info->performed.Value() == UNDECIDED);
100 info->performed.SetValue(solver(), performed);
101 }
102 }
103
104 void InitLeaf(int position, int64_t start_min, int64_t start_max,
105 int64_t end_min, int64_t end_max, PerformedStatus performed) {
106 InitNode(MaxDepth(), position, start_min, start_max, end_min, end_max,
107 performed);
108 }
109
110 void InitNode(int depth, int position, int64_t start_min, int64_t start_max,
111 int64_t end_min, int64_t end_max, PerformedStatus performed) {
112 tree_[depth][position].start_min.SetValue(solver(), start_min);
113 tree_[depth][position].start_max.SetValue(solver(), start_max);
114 tree_[depth][position].end_min.SetValue(solver(), end_min);
115 tree_[depth][position].end_max.SetValue(solver(), end_max);
116 tree_[depth][position].performed.SetValue(solver(),
117 static_cast<int>(performed));
118 }
119
120 int64_t StartMin(int depth, int position) const {
121 return tree_[depth][position].start_min.Value();
122 }
123
124 int64_t StartMax(int depth, int position) const {
125 return tree_[depth][position].start_max.Value();
126 }
127
128 int64_t EndMax(int depth, int position) const {
129 return tree_[depth][position].end_max.Value();
130 }
131
132 int64_t EndMin(int depth, int position) const {
133 return tree_[depth][position].end_min.Value();
134 }
135
136 PerformedStatus Performed(int depth, int position) const {
137 const int p = tree_[depth][position].performed.Value();
138 CHECK_GE(p, UNPERFORMED);
139 CHECK_LE(p, UNDECIDED);
140 return static_cast<PerformedStatus>(p);
141 }
142
143 int64_t RootStartMin() const { return root_node_->start_min.Value(); }
144
145 int64_t RootStartMax() const { return root_node_->start_max.Value(); }
146
147 int64_t RootEndMin() const { return root_node_->end_min.Value(); }
148
149 int64_t RootEndMax() const { return root_node_->end_max.Value(); }
150
151 PerformedStatus RootPerformed() const { return Performed(0, 0); }
152
153 // This getters query first if the var can be performed, and will
154 // return a default value if not.
155 int64_t VarStartMin(int position) const {
156 return vars_[position]->MayBePerformed() ? vars_[position]->StartMin() : 0;
157 }
158
159 int64_t VarStartMax(int position) const {
160 return vars_[position]->MayBePerformed() ? vars_[position]->StartMax() : 0;
161 }
162
163 int64_t VarEndMin(int position) const {
164 return vars_[position]->MayBePerformed() ? vars_[position]->EndMin() : 0;
165 }
166
167 int64_t VarEndMax(int position) const {
168 return vars_[position]->MayBePerformed() ? vars_[position]->EndMax() : 0;
169 }
170
171 int64_t TargetVarStartMin() const {
172 return target_var_->MayBePerformed() ? target_var_->StartMin() : 0;
173 }
174
175 int64_t TargetVarStartMax() const {
176 return target_var_->MayBePerformed() ? target_var_->StartMax() : 0;
177 }
178
179 int64_t TargetVarEndMin() const {
180 return target_var_->MayBePerformed() ? target_var_->EndMin() : 0;
181 }
182
183 int64_t TargetVarEndMax() const {
184 return target_var_->MayBePerformed() ? target_var_->EndMax() : 0;
185 }
186
187 // Returns the performed status of the 'position' nth interval
188 // var of the problem.
189 PerformedStatus VarPerformed(int position) const {
190 IntervalVar* const var = vars_[position];
191 if (var->MustBePerformed()) {
192 return PERFORMED;
193 } else if (var->MayBePerformed()) {
194 return UNDECIDED;
195 } else {
196 return UNPERFORMED;
197 }
198 }
199
200 // Returns the performed status of the target var.
201 PerformedStatus TargetVarPerformed() const {
202 if (target_var_->MustBePerformed()) {
203 return PERFORMED;
204 } else if (target_var_->MayBePerformed()) {
205 return UNDECIDED;
206 } else {
207 return UNPERFORMED;
208 }
209 }
210
211 // Returns the position of the parent of a node with a given position.
212 int Parent(int position) const { return position / block_size_; }
213
214 // Returns the index of the first child of a node at a given 'position'.
215 int ChildStart(int position) const { return position * block_size_; }
216
217 // Returns the index of the last child of a node at a given
218 // 'position'. The depth is needed to make sure that do not overlap
219 // the width of the tree at a given depth.
220 int ChildEnd(int depth, int position) const {
221 DCHECK_LT(depth + 1, tree_.size());
222 return std::min((position + 1) * block_size_ - 1, Width(depth + 1) - 1);
223 }
224
225 bool IsLeaf(int depth) const { return depth == MaxDepth(); }
226
227 int MaxDepth() const { return tree_.size() - 1; }
228
229 int Width(int depth) const { return tree_[depth].size(); }
230
231 protected:
232 const std::vector<IntervalVar*> vars_;
233 IntervalVar* const target_var_;
234
235 private:
236 struct NodeInfo {
237 NodeInfo()
238 : start_min(0),
239 start_max(0),
240 end_min(0),
241 end_max(0),
242 performed(UNDECIDED) {}
243
244 Rev<int64_t> start_min;
245 Rev<int64_t> start_max;
246 Rev<int64_t> end_min;
247 Rev<int64_t> end_max;
248 Rev<int> performed;
249 };
250
251 std::vector<std::vector<NodeInfo> > tree_;
252 const int block_size_;
253 NodeInfo* root_node_;
254};
255
256// This constraint implements cover(vars) == cover_var.
257class CoverConstraint : public TreeArrayConstraint {
258 public:
259 CoverConstraint(Solver* const solver, const std::vector<IntervalVar*>& vars,
260 IntervalVar* const cover_var)
261 : TreeArrayConstraint(solver, vars, cover_var), cover_demon_(nullptr) {}
262
263 ~CoverConstraint() override {}
264
265 void Post() override {
266 for (int i = 0; i < vars_.size(); ++i) {
267 Demon* const demon = MakeConstraintDemon1(
268 solver(), this, &CoverConstraint::LeafChanged, "LeafChanged", i);
269 vars_[i]->WhenStartRange(demon);
270 vars_[i]->WhenEndRange(demon);
271 vars_[i]->WhenPerformedBound(demon);
272 }
273 cover_demon_ = solver()->RegisterDemon(MakeDelayedConstraintDemon0(
274 solver(), this, &CoverConstraint::CoverVarChanged, "CoverVarChanged"));
275 target_var_->WhenStartRange(cover_demon_);
276 target_var_->WhenEndRange(cover_demon_);
277 target_var_->WhenPerformedBound(cover_demon_);
278 }
279
280 void InitialPropagate() override {
281 // Copy vars to leaf nodes.
282 for (int i = 0; i < vars_.size(); ++i) {
283 InitLeaf(i, VarStartMin(i), VarStartMax(i), VarEndMin(i), VarEndMax(i),
284 VarPerformed(i));
285 }
286
287 // Compute up.
288 for (int i = MaxDepth() - 1; i >= 0; --i) {
289 for (int j = 0; j < Width(i); ++j) {
290 int64_t bucket_start_min = std::numeric_limits<int64_t>::max();
291 int64_t bucket_start_max = std::numeric_limits<int64_t>::max();
292 int64_t bucket_end_min = std::numeric_limits<int64_t>::min();
293 int64_t bucket_end_max = std::numeric_limits<int64_t>::min();
294 bool one_undecided = false;
295 const PerformedStatus up_performed = ComputePropagationUp(
296 i, j, &bucket_start_min, &bucket_start_max, &bucket_end_min,
297 &bucket_end_max, &one_undecided);
298 InitNode(i, j, bucket_start_min, bucket_start_max, bucket_end_min,
299 bucket_end_max, up_performed);
300 }
301 }
302 // Compute down.
303 PropagateRoot();
304 }
305
306 void PropagateRoot() {
307 // Propagate from the root of the tree to the target var.
308 switch (RootPerformed()) {
309 case UNPERFORMED:
310 target_var_->SetPerformed(false);
311 break;
312 case PERFORMED:
313 target_var_->SetPerformed(true);
314 ABSL_FALLTHROUGH_INTENDED;
315 case UNDECIDED:
316 target_var_->SetStartRange(RootStartMin(), RootStartMax());
317 target_var_->SetEndRange(RootEndMin(), RootEndMax());
318 break;
319 }
320 // Check if we need to propagate back. This is useful in case the
321 // target var is performed and only one last interval var may be
322 // performed, and thus needs to change is status to performed.
323 CoverVarChanged();
324 }
325
326 // Propagates from top to bottom.
327 void CoverVarChanged() {
328 PushDown(0, 0, TargetVarStartMin(), TargetVarStartMax(), TargetVarEndMin(),
329 TargetVarEndMax(), TargetVarPerformed());
330 }
331
332 void PushDown(int depth, int position, int64_t new_start_min,
333 int64_t new_start_max, int64_t new_end_min, int64_t new_end_max,
334 PerformedStatus performed) {
335 // TODO(user): Propagate start_max and end_min going down.
336 // Nothing to do?
337 if (new_start_min <= StartMin(depth, position) &&
338 new_start_max >= StartMax(depth, position) &&
339 new_end_min <= EndMin(depth, position) &&
340 new_end_max >= EndMax(depth, position) &&
341 (performed == UNDECIDED || performed == Performed(depth, position))) {
342 return;
343 }
344 // Leaf node -> push to leaf var.
345 if (IsLeaf(depth)) {
346 switch (performed) {
347 case UNPERFORMED:
348 vars_[position]->SetPerformed(false);
349 break;
350 case PERFORMED:
351 vars_[position]->SetPerformed(true);
352 ABSL_FALLTHROUGH_INTENDED;
353 case UNDECIDED:
354 vars_[position]->SetStartRange(new_start_min, new_start_max);
355 vars_[position]->SetEndRange(new_end_min, new_end_max);
356 }
357 return;
358 }
359
360 const int block_start = ChildStart(position);
361 const int block_end = ChildEnd(depth, position);
362
363 switch (performed) {
364 case UNPERFORMED: { // Mark all node unperformed.
365 for (int i = block_start; i <= block_end; ++i) {
366 PushDown(depth + 1, i, new_start_min, new_start_max, new_end_min,
367 new_end_max, UNPERFORMED);
368 }
369 break;
370 }
371 case PERFORMED: { // Count number of undecided or performed;
372 int candidate = -1;
373 int may_be_performed_count = 0;
374 int must_be_performed_count = 0;
375 for (int i = block_start; i <= block_end; ++i) {
376 switch (Performed(depth + 1, i)) {
377 case UNPERFORMED:
378 break;
379 case PERFORMED:
380 must_be_performed_count++;
381 ABSL_FALLTHROUGH_INTENDED;
382 case UNDECIDED:
383 may_be_performed_count++;
384 candidate = i;
385 }
386 }
387 if (may_be_performed_count == 0) {
388 solver()->Fail();
389 } else if (may_be_performed_count == 1) {
390 PushDown(depth + 1, candidate, new_start_min, new_start_max,
391 new_end_min, new_end_max, PERFORMED);
392 } else {
393 for (int i = block_start; i <= block_end; ++i) {
394 // Since there are more than 1 active child node, we
395 // cannot propagate on new_start_max and new_end_min. Thus
396 // we substitute them with safe bounds e.g. new_end_max
397 // and new_start_min.
398 PushDown(depth + 1, i, new_start_min, new_end_max, new_start_min,
399 new_end_max, UNDECIDED);
400 }
401 }
402 break;
403 }
404 case UNDECIDED: {
405 for (int i = block_start; i <= block_end; ++i) {
406 // Since there are more than 1 active child node, we
407 // cannot propagate on new_start_max and new_end_min. Thus
408 // we substitute them with safe bounds e.g. new_end_max
409 // and new_start_min.
410 PushDown(depth + 1, i, new_start_min, new_end_max, new_start_min,
411 new_end_max, UNDECIDED);
412 }
413 }
414 }
415 }
416
417 void LeafChanged(int term_index) {
418 ReduceDomain(MaxDepth(), term_index, VarStartMin(term_index),
419 VarStartMax(term_index), VarEndMin(term_index),
420 VarEndMax(term_index), VarPerformed(term_index));
421 // Do we need to propagate up?
422 const int parent = Parent(term_index);
423 const int parent_depth = MaxDepth() - 1;
424 const int64_t parent_start_min = StartMin(parent_depth, parent);
425 const int64_t parent_start_max = StartMax(parent_depth, parent);
426 const int64_t parent_end_min = EndMin(parent_depth, parent);
427 const int64_t parent_end_max = EndMax(parent_depth, parent);
428 IntervalVar* const var = vars_[term_index];
429 const bool performed_bound = var->IsPerformedBound();
430 const bool was_performed_bound = var->WasPerformedBound();
431 if (performed_bound == was_performed_bound && var->MayBePerformed() &&
432 var->OldStartMin() != parent_start_min &&
433 var->OldStartMax() != parent_start_max &&
434 var->OldEndMin() != parent_end_min &&
435 var->OldEndMax() != parent_end_max) {
436 // We were not a support of the parent bounds, and the performed
437 // status has not changed. There is no need to propagate up.
438 return;
439 }
440 PushUp(term_index);
441 }
442
443 void PushUp(int position) {
444 int depth = MaxDepth();
445 while (depth > 0) {
446 const int parent = Parent(position);
447 const int parent_depth = depth - 1;
448 int64_t bucket_start_min = std::numeric_limits<int64_t>::max();
449 int64_t bucket_start_max = std::numeric_limits<int64_t>::max();
450 int64_t bucket_end_min = std::numeric_limits<int64_t>::min();
451 int64_t bucket_end_max = std::numeric_limits<int64_t>::min();
452 bool one_undecided = false;
453 const PerformedStatus status_up = ComputePropagationUp(
454 parent_depth, parent, &bucket_start_min, &bucket_start_max,
455 &bucket_end_min, &bucket_end_max, &one_undecided);
456 if (bucket_start_min > StartMin(parent_depth, parent) ||
457 bucket_start_max < StartMax(parent_depth, parent) ||
458 bucket_end_min > EndMin(parent_depth, parent) ||
459 bucket_end_max < EndMax(parent_depth, parent) ||
460 status_up != Performed(parent_depth, parent)) {
461 ReduceDomain(parent_depth, parent, bucket_start_min, bucket_start_max,
462 bucket_end_min, bucket_end_max, status_up);
463 } else {
464 if (one_undecided && TargetVarPerformed() == PERFORMED) {
465 // This may be the last possible interval that can and
466 // should be performed.
467 PropagateRoot();
468 }
469 // There is nothing more to propagate up. We can stop now.
470 return;
471 }
472 depth = parent_depth;
473 position = parent;
474 }
475 DCHECK_EQ(0, depth);
476 PropagateRoot();
477 }
478
479 std::string DebugString() const override {
480 return DebugStringInternal(ModelVisitor::kCover);
481 }
482
483 void Accept(ModelVisitor* const visitor) const override {
484 AcceptInternal(ModelVisitor::kCover, visitor);
485 }
486
487 private:
488 PerformedStatus ComputePropagationUp(int parent_depth, int parent_position,
489 int64_t* const bucket_start_min,
490 int64_t* const bucket_start_max,
491 int64_t* const bucket_end_min,
492 int64_t* const bucket_end_max,
493 bool* one_undecided) {
494 *bucket_start_min = std::numeric_limits<int64_t>::max();
495 *bucket_start_max = std::numeric_limits<int64_t>::max();
496 *bucket_end_min = std::numeric_limits<int64_t>::min();
497 *bucket_end_max = std::numeric_limits<int64_t>::min();
498
499 int may_be_performed_count = 0;
500 int must_be_performed_count = 0;
501 const int block_start = ChildStart(parent_position);
502 const int block_end = ChildEnd(parent_depth, parent_position);
503 for (int k = block_start; k <= block_end; ++k) {
504 const PerformedStatus performed = Performed(parent_depth + 1, k);
505 if (performed != UNPERFORMED) {
506 *bucket_start_min =
507 std::min(*bucket_start_min, StartMin(parent_depth + 1, k));
508 *bucket_end_max =
509 std::max(*bucket_end_max, EndMax(parent_depth + 1, k));
510 may_be_performed_count++;
511 if (performed == PERFORMED) {
512 *bucket_start_max =
513 std::min(*bucket_start_max, StartMax(parent_depth + 1, k));
514 *bucket_end_min =
515 std::max(*bucket_end_min, EndMin(parent_depth + 1, k));
516 must_be_performed_count++;
517 }
518 }
519 }
520 const PerformedStatus up_performed =
521 must_be_performed_count > 0
522 ? PERFORMED
523 : (may_be_performed_count > 0 ? UNDECIDED : UNPERFORMED);
524 *one_undecided =
525 (may_be_performed_count == 1) && (must_be_performed_count == 0);
526 return up_performed;
527 }
528
529 Demon* cover_demon_;
530};
531
532class IntervalEquality : public Constraint {
533 public:
534 IntervalEquality(Solver* const solver, IntervalVar* const var1,
535 IntervalVar* const var2)
536 : Constraint(solver), var1_(var1), var2_(var2) {}
537
538 ~IntervalEquality() override {}
539
540 void Post() override {
541 Demon* const demon = solver()->MakeConstraintInitialPropagateCallback(this);
542 var1_->WhenAnything(demon);
543 var2_->WhenAnything(demon);
544 }
545
546 void InitialPropagate() override {
547 // Naive code. Can be split by property (performed, start...).
548 if (!var1_->MayBePerformed()) {
549 var2_->SetPerformed(false);
550 } else {
551 if (var1_->MustBePerformed()) {
552 var2_->SetPerformed(true);
553 }
554 var2_->SetStartRange(var1_->StartMin(), var1_->StartMax());
555 var2_->SetDurationRange(var1_->DurationMin(), var1_->DurationMax());
556 var2_->SetEndRange(var1_->EndMin(), var1_->EndMax());
557 }
558 if (!var2_->MayBePerformed()) {
559 var1_->SetPerformed(false);
560 } else {
561 if (var2_->MustBePerformed()) {
562 var1_->SetPerformed(true);
563 }
564 var1_->SetStartRange(var2_->StartMin(), var2_->StartMax());
565 var1_->SetDurationRange(var2_->DurationMin(), var2_->DurationMax());
566 var1_->SetEndRange(var2_->EndMin(), var2_->EndMax());
567 }
568 }
569
570 std::string DebugString() const override {
571 return absl::StrFormat("Equality(%s, %s)", var1_->DebugString(),
572 var2_->DebugString());
573 }
574
575 void Accept(ModelVisitor* const visitor) const override {
576 visitor->BeginVisitConstraint(ModelVisitor::kEquality, this);
577 visitor->VisitIntervalArgument(ModelVisitor::kLeftArgument, var1_);
578 visitor->VisitIntervalArgument(ModelVisitor::kRightArgument, var2_);
579 visitor->EndVisitConstraint(ModelVisitor::kEquality, this);
580 }
581
582 private:
583 IntervalVar* const var1_;
584 IntervalVar* const var2_;
585};
586} // namespace
587
588Constraint* Solver::MakeCover(const std::vector<IntervalVar*>& vars,
589 IntervalVar* const target_var) {
590 CHECK(!vars.empty());
591 if (vars.size() == 1) {
592 return MakeEquality(vars[0], target_var);
593 } else {
594 return RevAlloc(new CoverConstraint(this, vars, target_var));
595 }
596}
597
599 IntervalVar* const var2) {
600 return RevAlloc(new IntervalEquality(this, var1, var2));
601}
602} // namespace operations_research
IntegerValue size
const std::vector< IntVar * > vars_
Constraint * MakeEquality(IntExpr *left, IntExpr *right)
left == right
Definition range_cst.cc:518
Constraint * MakeCover(const std::vector< IntervalVar * > &vars, IntervalVar *target_var)
SatParameters parameters
const std::string name
A name for logging purposes.
IntVar * var
In SWIG mode, we don't want anything besides these top-level includes.
std::string JoinDebugStringPtr(const std::vector< T > &v, absl::string_view separator)
Join v[i]->DebugString().
Demon * MakeDelayedConstraintDemon0(Solver *const s, T *const ct, void(T::*method)(), const std::string &name)
Demon * MakeConstraintDemon1(Solver *const s, T *const ct, void(T::*method)(P), const std::string &name, P param1)
Rev< int64_t > start_max
Rev< int64_t > end_max
Rev< int > performed
Rev< int64_t > start_min
Rev< int64_t > end_min