OR-Tools  9.3
precedences.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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 <deque>
18#include <vector>
19
20#include "absl/container/btree_set.h"
21#include "absl/container/inlined_vector.h"
22#include "absl/types/span.h"
28#include "ortools/sat/clause.h"
30#include "ortools/sat/integer.h"
31#include "ortools/sat/model.h"
34#include "ortools/util/bitset.h"
37
38namespace operations_research {
39namespace sat {
40
41namespace {
42
43void AppendLowerBoundReasonIfValid(IntegerVariable var,
44 const IntegerTrail& i_trail,
45 std::vector<IntegerLiteral>* reason) {
46 if (var != kNoIntegerVariable) {
47 reason->push_back(i_trail.LowerBoundAsLiteral(var));
48 }
49}
50
51} // namespace
52
54
56 while (propagation_trail_index_ < trail_->Index()) {
57 const Literal literal = (*trail_)[propagation_trail_index_++];
58 if (literal.Index() >= literal_to_new_impacted_arcs_.size()) continue;
59
60 // IMPORTANT: Because of the way Untrail() work, we need to add all the
61 // potential arcs before we can abort. It is why we iterate twice here.
62 for (const ArcIndex arc_index :
63 literal_to_new_impacted_arcs_[literal.Index()]) {
64 if (--arc_counts_[arc_index] == 0) {
65 const ArcInfo& arc = arcs_[arc_index];
66 impacted_arcs_[arc.tail_var].push_back(arc_index);
67 }
68 }
69
70 // Iterate again to check for a propagation and indirectly update
71 // modified_vars_.
72 for (const ArcIndex arc_index :
73 literal_to_new_impacted_arcs_[literal.Index()]) {
74 if (arc_counts_[arc_index] > 0) continue;
75 const ArcInfo& arc = arcs_[arc_index];
76 if (integer_trail_->IsCurrentlyIgnored(arc.head_var)) continue;
77 const IntegerValue new_head_lb =
78 integer_trail_->LowerBound(arc.tail_var) + ArcOffset(arc);
79 if (new_head_lb > integer_trail_->LowerBound(arc.head_var)) {
80 if (!EnqueueAndCheck(arc, new_head_lb, trail_)) return false;
81 }
82 }
83 }
84
85 // Do the actual propagation of the IntegerVariable bounds.
86 InitializeBFQueueWithModifiedNodes();
87 if (!BellmanFordTarjan(trail_)) return false;
88
89 // We can only test that no propagation is left if we didn't enqueue new
90 // literal in the presence of optional variables.
91 //
92 // TODO(user): Because of our code to deal with InPropagationLoop(), this is
93 // not always true. Find a cleaner way to DCHECK() while not failing in this
94 // corner case.
95 if (/*DISABLES CODE*/ (false) &&
96 propagation_trail_index_ == trail_->Index()) {
97 DCHECK(NoPropagationLeft(*trail_));
98 }
99
100 // Propagate the presence literals of the arcs that can't be added.
101 PropagateOptionalArcs(trail_);
102
103 // Clean-up modified_vars_ to do as little as possible on the next call.
104 modified_vars_.ClearAndResize(integer_trail_->NumIntegerVariables());
105 return true;
106}
107
110 if (var >= impacted_arcs_.size()) return true;
111 for (const ArcIndex arc_index : impacted_arcs_[var]) {
112 const ArcInfo& arc = arcs_[arc_index];
113 if (integer_trail_->IsCurrentlyIgnored(arc.head_var)) continue;
114 const IntegerValue new_head_lb =
115 integer_trail_->LowerBound(arc.tail_var) + ArcOffset(arc);
116 if (new_head_lb > integer_trail_->LowerBound(arc.head_var)) {
117 if (!EnqueueAndCheck(arc, new_head_lb, trail_)) return false;
118 }
119 }
120 return true;
121}
122
123void PrecedencesPropagator::Untrail(const Trail& trail, int trail_index) {
124 if (propagation_trail_index_ > trail_index) {
125 // This means that we already propagated all there is to propagate
126 // at the level trail_index, so we can safely clear modified_vars_ in case
127 // it wasn't already done.
128 modified_vars_.ClearAndResize(integer_trail_->NumIntegerVariables());
129 }
130 while (propagation_trail_index_ > trail_index) {
132 if (literal.Index() >= literal_to_new_impacted_arcs_.size()) continue;
133 for (const ArcIndex arc_index :
134 literal_to_new_impacted_arcs_[literal.Index()]) {
135 if (arc_counts_[arc_index]++ == 0) {
136 const ArcInfo& arc = arcs_[arc_index];
137 impacted_arcs_[arc.tail_var].pop_back();
138 }
139 }
140 }
141}
142
143// Instead of simply sorting the IntegerPrecedences returned by .var,
144// experiments showed that it is faster to regroup all the same .var "by hand"
145// by first computing how many times they appear and then apply the sorting
146// permutation.
148 const std::vector<IntegerVariable>& vars,
149 std::vector<IntegerPrecedences>* output) {
150 tmp_sorted_vars_.clear();
151 tmp_precedences_.clear();
152 for (int index = 0; index < vars.size(); ++index) {
153 const IntegerVariable var = vars[index];
155 if (var >= impacted_arcs_.size()) continue;
156 for (const ArcIndex arc_index : impacted_arcs_[var]) {
157 const ArcInfo& arc = arcs_[arc_index];
158 if (integer_trail_->IsCurrentlyIgnored(arc.head_var)) continue;
159
160 IntegerValue offset = arc.offset;
161 if (arc.offset_var != kNoIntegerVariable) {
162 offset += integer_trail_->LowerBound(arc.offset_var);
163 }
164
165 // TODO(user): it seems better to ignore negative min offset as we will
166 // often have relation of the form interval_start >= interval_end -
167 // offset, and such relation are usually not useful. Revisit this in case
168 // we see problems where we can propagate more without this test.
169 if (offset < 0) continue;
170
171 if (var_to_degree_[arc.head_var] == 0) {
172 tmp_sorted_vars_.push_back(
173 {arc.head_var, integer_trail_->LowerBound(arc.head_var)});
174 } else {
175 // This "seen" mechanism is needed because we may have multi-arc and we
176 // don't want any duplicates in the "is_before" relation. Note that it
177 // works because var_to_last_index_ is reset by the var_to_degree_ == 0
178 // case.
179 if (var_to_last_index_[arc.head_var] == index) continue;
180 }
181 var_to_last_index_[arc.head_var] = index;
182 var_to_degree_[arc.head_var]++;
183 tmp_precedences_.push_back(
184 {index, arc.head_var, arc_index.value(), offset});
185 }
186 }
187
188 // This order is a topological order for the precedences relation order
189 // provided that all the offset between the involved IntegerVariable are
190 // positive.
191 //
192 // TODO(user): use an order that is always topological? This is not clear
193 // since it may be slower to compute and not worth it because the order below
194 // is more natural and may work better.
195 std::sort(tmp_sorted_vars_.begin(), tmp_sorted_vars_.end());
196
197 // Permute tmp_precedences_ into the output to put it in the correct order.
198 // For that we transform var_to_degree_ to point to the first position of
199 // each lbvar in the output vector.
200 int start = 0;
201 for (const SortedVar pair : tmp_sorted_vars_) {
202 const int degree = var_to_degree_[pair.var];
203 if (degree > 1) {
204 var_to_degree_[pair.var] = start;
205 start += degree;
206 } else {
207 // Optimization: we remove degree one relations.
208 var_to_degree_[pair.var] = -1;
209 }
210 }
211 output->resize(start);
212 for (const IntegerPrecedences& precedence : tmp_precedences_) {
213 if (var_to_degree_[precedence.var] < 0) continue;
214 (*output)[var_to_degree_[precedence.var]++] = precedence;
215 }
216
217 // Cleanup var_to_degree_, note that we don't need to clean
218 // var_to_last_index_.
219 for (const SortedVar pair : tmp_sorted_vars_) {
220 var_to_degree_[pair.var] = 0;
221 }
222}
223
225 int arc_index, IntegerValue min_offset,
226 std::vector<Literal>* literal_reason,
227 std::vector<IntegerLiteral>* integer_reason) const {
228 const ArcInfo& arc = arcs_[ArcIndex(arc_index)];
229 for (const Literal l : arc.presence_literals) {
230 literal_reason->push_back(l.Negated());
231 }
232 if (arc.offset_var != kNoIntegerVariable) {
233 // Reason for ArcOffset(arc) to be >= min_offset.
234 integer_reason->push_back(IntegerLiteral::GreaterOrEqual(
235 arc.offset_var, min_offset - arc.offset));
236 }
237}
238
239void PrecedencesPropagator::AdjustSizeFor(IntegerVariable i) {
240 const int index = std::max(i.value(), NegationOf(i).value());
241 if (index >= impacted_arcs_.size()) {
242 // TODO(user): only watch lower bound of the relevant variable instead
243 // of watching everything in [0, max_index_of_variable_used_in_this_class).
244 for (IntegerVariable var(impacted_arcs_.size()); var <= index; ++var) {
245 watcher_->WatchLowerBound(var, watcher_id_);
246 }
247 impacted_arcs_.resize(index + 1);
248 impacted_potential_arcs_.resize(index + 1);
249 var_to_degree_.resize(index + 1);
250 var_to_last_index_.resize(index + 1);
251 }
252}
253
254void PrecedencesPropagator::AddArc(
255 IntegerVariable tail, IntegerVariable head, IntegerValue offset,
256 IntegerVariable offset_var, absl::Span<const Literal> presence_literals) {
257 DCHECK_EQ(trail_->CurrentDecisionLevel(), 0);
258 AdjustSizeFor(tail);
259 AdjustSizeFor(head);
260 if (offset_var != kNoIntegerVariable) AdjustSizeFor(offset_var);
261
262 // This arc is present iff all the literals here are true.
263 absl::InlinedVector<Literal, 6> enforcement_literals;
264 {
265 for (const Literal l : presence_literals) {
266 enforcement_literals.push_back(l);
267 }
268 if (integer_trail_->IsOptional(tail)) {
269 enforcement_literals.push_back(
270 integer_trail_->IsIgnoredLiteral(tail).Negated());
271 }
272 if (integer_trail_->IsOptional(head)) {
273 enforcement_literals.push_back(
274 integer_trail_->IsIgnoredLiteral(head).Negated());
275 }
276 if (offset_var != kNoIntegerVariable &&
277 integer_trail_->IsOptional(offset_var)) {
278 enforcement_literals.push_back(
279 integer_trail_->IsIgnoredLiteral(offset_var).Negated());
280 }
281 gtl::STLSortAndRemoveDuplicates(&enforcement_literals);
282 int new_size = 0;
283 for (const Literal l : enforcement_literals) {
284 if (trail_->Assignment().LiteralIsTrue(Literal(l))) {
285 continue; // At true, ignore this literal.
286 } else if (trail_->Assignment().LiteralIsFalse(Literal(l))) {
287 return; // At false, ignore completely this arc.
288 }
289 enforcement_literals[new_size++] = l;
290 }
291 enforcement_literals.resize(new_size);
292 }
293
294 if (head == tail) {
295 // A self-arc is either plain SAT or plain UNSAT or it forces something on
296 // the given offset_var or presence_literal_index. In any case it could be
297 // presolved in something more efficient.
298 VLOG(1) << "Self arc! This could be presolved. "
299 << "var:" << tail << " offset:" << offset
300 << " offset_var:" << offset_var
301 << " conditioned_by:" << presence_literals;
302 }
303
304 // Remove the offset_var if it is fixed.
305 // TODO(user): We should also handle the case where tail or head is fixed.
306 if (offset_var != kNoIntegerVariable) {
307 const IntegerValue lb = integer_trail_->LowerBound(offset_var);
308 if (lb == integer_trail_->UpperBound(offset_var)) {
309 offset += lb;
310 offset_var = kNoIntegerVariable;
311 }
312 }
313
314 // Deal first with impacted_potential_arcs_/potential_arcs_.
315 if (!enforcement_literals.empty()) {
316 const OptionalArcIndex arc_index(potential_arcs_.size());
317 potential_arcs_.push_back(
318 {tail, head, offset, offset_var, enforcement_literals});
319 impacted_potential_arcs_[tail].push_back(arc_index);
320 impacted_potential_arcs_[NegationOf(head)].push_back(arc_index);
321 if (offset_var != kNoIntegerVariable) {
322 impacted_potential_arcs_[offset_var].push_back(arc_index);
323 }
324 }
325
326 // Now deal with impacted_arcs_/arcs_.
327 struct InternalArc {
328 IntegerVariable tail_var;
329 IntegerVariable head_var;
330 IntegerVariable offset_var;
331 };
332 std::vector<InternalArc> to_add;
333 if (offset_var == kNoIntegerVariable) {
334 // a + offset <= b and -b + offset <= -a
335 to_add.push_back({tail, head, kNoIntegerVariable});
336 to_add.push_back({NegationOf(head), NegationOf(tail), kNoIntegerVariable});
337 } else {
338 // tail (a) and offset_var (b) are symmetric, so we add:
339 // - a + b + offset <= c
340 to_add.push_back({tail, head, offset_var});
341 to_add.push_back({offset_var, head, tail});
342 // - a - c + offset <= -b
343 to_add.push_back({tail, NegationOf(offset_var), NegationOf(head)});
344 to_add.push_back({NegationOf(head), NegationOf(offset_var), tail});
345 // - b - c + offset <= -a
346 to_add.push_back({offset_var, NegationOf(tail), NegationOf(head)});
347 to_add.push_back({NegationOf(head), NegationOf(tail), offset_var});
348 }
349 for (const InternalArc a : to_add) {
350 // Since we add a new arc, we will need to consider its tail during the next
351 // propagation. Note that the size of modified_vars_ will be automatically
352 // updated when new integer variables are created since we register it with
353 // IntegerTrail in this class constructor.
354 //
355 // TODO(user): Adding arcs and then calling Untrail() before Propagate()
356 // will cause this mecanism to break. Find a more robust implementation.
357 //
358 // TODO(user): In some rare corner case, rescanning the whole list of arc
359 // leaving tail_var can make AddVar() have a quadratic complexity where it
360 // shouldn't. A better solution would be to see if this new arc currently
361 // propagate something, and if it does, just update the lower bound of
362 // a.head_var and let the normal "is modified" mecanism handle any eventual
363 // follow up propagations.
364 modified_vars_.Set(a.tail_var);
365
366 // If a.head_var is optional, we can potentially remove some literal from
367 // enforcement_literals.
368 const ArcIndex arc_index(arcs_.size());
369 arcs_.push_back(
370 {a.tail_var, a.head_var, offset, a.offset_var, enforcement_literals});
371 auto& presence_literals = arcs_.back().presence_literals;
372 if (integer_trail_->IsOptional(a.head_var)) {
373 // TODO(user): More generally, we can remove any literal that is implied
374 // by to_remove.
375 const Literal to_remove =
376 integer_trail_->IsIgnoredLiteral(a.head_var).Negated();
377 const auto it = std::find(presence_literals.begin(),
378 presence_literals.end(), to_remove);
379 if (it != presence_literals.end()) presence_literals.erase(it);
380 }
381
382 if (presence_literals.empty()) {
383 impacted_arcs_[a.tail_var].push_back(arc_index);
384 } else {
385 for (const Literal l : presence_literals) {
386 if (l.Index() >= literal_to_new_impacted_arcs_.size()) {
387 literal_to_new_impacted_arcs_.resize(l.Index().value() + 1);
388 }
389 literal_to_new_impacted_arcs_[l.Index()].push_back(arc_index);
390 }
391 }
392 arc_counts_.push_back(presence_literals.size());
393 }
394}
395
396// TODO(user): On jobshop problems with a lot of tasks per machine (500), this
397// takes up a big chunck of the running time even before we find a solution.
398// This is because, for each lower bound changed, we inspect 500 arcs even
399// though they will never be propagated because the other bound is still at the
400// horizon. Find an even sparser algorithm?
401void PrecedencesPropagator::PropagateOptionalArcs(Trail* trail) {
402 for (const IntegerVariable var : modified_vars_.PositionsSetAtLeastOnce()) {
403 // The variables are not in increasing order, so we need to continue.
404 if (var >= impacted_potential_arcs_.size()) continue;
405
406 // Note that we can currently check the same ArcInfo up to 3 times, one for
407 // each of the arc variables: tail, NegationOf(head) and offset_var.
408 for (const OptionalArcIndex arc_index : impacted_potential_arcs_[var]) {
409 const ArcInfo& arc = potential_arcs_[arc_index];
410 int num_not_true = 0;
411 Literal to_propagate;
412 for (const Literal l : arc.presence_literals) {
413 if (!trail->Assignment().LiteralIsTrue(l)) {
414 ++num_not_true;
415 to_propagate = l;
416 }
417 }
418 if (num_not_true != 1) continue;
419 if (trail->Assignment().LiteralIsFalse(to_propagate)) continue;
420
421 // Test if this arc can be present or not.
422 // Important arc.tail_var can be different from var here.
423 const IntegerValue tail_lb = integer_trail_->LowerBound(arc.tail_var);
424 const IntegerValue head_ub = integer_trail_->UpperBound(arc.head_var);
425 if (tail_lb + ArcOffset(arc) > head_ub) {
426 integer_reason_.clear();
427 integer_reason_.push_back(
428 integer_trail_->LowerBoundAsLiteral(arc.tail_var));
429 integer_reason_.push_back(
430 integer_trail_->UpperBoundAsLiteral(arc.head_var));
431 AppendLowerBoundReasonIfValid(arc.offset_var, *integer_trail_,
432 &integer_reason_);
433 literal_reason_.clear();
434 for (const Literal l : arc.presence_literals) {
435 if (l != to_propagate) literal_reason_.push_back(l.Negated());
436 }
437 integer_trail_->EnqueueLiteral(to_propagate.Negated(), literal_reason_,
438 integer_reason_);
439 }
440 }
441 }
442}
443
444IntegerValue PrecedencesPropagator::ArcOffset(const ArcInfo& arc) const {
445 return arc.offset + (arc.offset_var == kNoIntegerVariable
446 ? IntegerValue(0)
447 : integer_trail_->LowerBound(arc.offset_var));
448}
449
450bool PrecedencesPropagator::EnqueueAndCheck(const ArcInfo& arc,
451 IntegerValue new_head_lb,
452 Trail* trail) {
453 DCHECK_GT(new_head_lb, integer_trail_->LowerBound(arc.head_var));
454
455 // Compute the reason for new_head_lb.
456 //
457 // TODO(user): do like for clause and keep the negation of
458 // arc.presence_literals? I think we could change the integer.h API to accept
459 // true literal like for IntegerVariable, it is really confusing currently.
460 literal_reason_.clear();
461 for (const Literal l : arc.presence_literals) {
462 literal_reason_.push_back(l.Negated());
463 }
464
465 integer_reason_.clear();
466 integer_reason_.push_back(integer_trail_->LowerBoundAsLiteral(arc.tail_var));
467 AppendLowerBoundReasonIfValid(arc.offset_var, *integer_trail_,
468 &integer_reason_);
469
470 // The code works without this block since Enqueue() below can already take
471 // care of conflicts. However, it is better to deal with the conflict
472 // ourselves because we can be smarter about the reason this way.
473 //
474 // The reason for a "precedence" conflict is always a linear reason
475 // involving the tail lower_bound, the head upper bound and eventually the
476 // size lower bound. Because of that, we can use the RelaxLinearReason()
477 // code.
478 if (new_head_lb > integer_trail_->UpperBound(arc.head_var)) {
479 const IntegerValue slack =
480 new_head_lb - integer_trail_->UpperBound(arc.head_var) - 1;
481 integer_reason_.push_back(
482 integer_trail_->UpperBoundAsLiteral(arc.head_var));
483 std::vector<IntegerValue> coeffs(integer_reason_.size(), IntegerValue(1));
484 integer_trail_->RelaxLinearReason(slack, coeffs, &integer_reason_);
485
486 if (!integer_trail_->IsOptional(arc.head_var)) {
487 return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
488 } else {
489 CHECK(!integer_trail_->IsCurrentlyIgnored(arc.head_var));
490 const Literal l = integer_trail_->IsIgnoredLiteral(arc.head_var);
491 if (trail->Assignment().LiteralIsFalse(l)) {
492 literal_reason_.push_back(l);
493 return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
494 } else {
495 integer_trail_->EnqueueLiteral(l, literal_reason_, integer_reason_);
496 return true;
497 }
498 }
499 }
500
501 return integer_trail_->Enqueue(
502 IntegerLiteral::GreaterOrEqual(arc.head_var, new_head_lb),
503 literal_reason_, integer_reason_);
504}
505
506bool PrecedencesPropagator::NoPropagationLeft(const Trail& trail) const {
507 const int num_nodes = impacted_arcs_.size();
508 for (IntegerVariable var(0); var < num_nodes; ++var) {
509 for (const ArcIndex arc_index : impacted_arcs_[var]) {
510 const ArcInfo& arc = arcs_[arc_index];
511 if (integer_trail_->IsCurrentlyIgnored(arc.head_var)) continue;
512 if (integer_trail_->LowerBound(arc.tail_var) + ArcOffset(arc) >
513 integer_trail_->LowerBound(arc.head_var)) {
514 return false;
515 }
516 }
517 }
518 return true;
519}
520
521void PrecedencesPropagator::InitializeBFQueueWithModifiedNodes() {
522 // Sparse clear of the queue. TODO(user): only use the sparse version if
523 // queue.size() is small or use SparseBitset.
524 const int num_nodes = impacted_arcs_.size();
525 bf_in_queue_.resize(num_nodes, false);
526 for (const int node : bf_queue_) bf_in_queue_[node] = false;
527 bf_queue_.clear();
528 DCHECK(std::none_of(bf_in_queue_.begin(), bf_in_queue_.end(),
529 [](bool v) { return v; }));
530 for (const IntegerVariable var : modified_vars_.PositionsSetAtLeastOnce()) {
531 if (var >= num_nodes) continue;
532 bf_queue_.push_back(var.value());
533 bf_in_queue_[var.value()] = true;
534 }
535}
536
537void PrecedencesPropagator::CleanUpMarkedArcsAndParents() {
538 // To be sparse, we use the fact that each node with a parent must be in
539 // modified_vars_.
540 const int num_nodes = impacted_arcs_.size();
541 for (const IntegerVariable var : modified_vars_.PositionsSetAtLeastOnce()) {
542 if (var >= num_nodes) continue;
543 const ArcIndex parent_arc_index = bf_parent_arc_of_[var.value()];
544 if (parent_arc_index != -1) {
545 arcs_[parent_arc_index].is_marked = false;
546 bf_parent_arc_of_[var.value()] = -1;
547 bf_can_be_skipped_[var.value()] = false;
548 }
549 }
550 DCHECK(std::none_of(bf_parent_arc_of_.begin(), bf_parent_arc_of_.end(),
551 [](ArcIndex v) { return v != -1; }));
552 DCHECK(std::none_of(bf_can_be_skipped_.begin(), bf_can_be_skipped_.end(),
553 [](bool v) { return v; }));
554}
555
556bool PrecedencesPropagator::DisassembleSubtree(
557 int source, int target, std::vector<bool>* can_be_skipped) {
558 // Note that we explore a tree, so we can do it in any order, and the one
559 // below seems to be the fastest.
560 tmp_vector_.clear();
561 tmp_vector_.push_back(source);
562 while (!tmp_vector_.empty()) {
563 const int tail = tmp_vector_.back();
564 tmp_vector_.pop_back();
565 for (const ArcIndex arc_index : impacted_arcs_[IntegerVariable(tail)]) {
566 const ArcInfo& arc = arcs_[arc_index];
567 if (arc.is_marked) {
568 arc.is_marked = false; // mutable.
569 if (arc.head_var.value() == target) return true;
570 DCHECK(!(*can_be_skipped)[arc.head_var.value()]);
571 (*can_be_skipped)[arc.head_var.value()] = true;
572 tmp_vector_.push_back(arc.head_var.value());
573 }
574 }
575 }
576 return false;
577}
578
579void PrecedencesPropagator::AnalyzePositiveCycle(
580 ArcIndex first_arc, Trail* trail, std::vector<Literal>* must_be_all_true,
581 std::vector<Literal>* literal_reason,
582 std::vector<IntegerLiteral>* integer_reason) {
583 must_be_all_true->clear();
584 literal_reason->clear();
585 integer_reason->clear();
586
587 // Follow bf_parent_arc_of_[] to find the cycle containing first_arc.
588 const IntegerVariable first_arc_head = arcs_[first_arc].head_var;
589 ArcIndex arc_index = first_arc;
590 std::vector<ArcIndex> arc_on_cycle;
591
592 // Just to be safe and avoid an infinite loop we use the fact that the maximum
593 // cycle size on a graph with n nodes is of size n. If we have more in the
594 // code below, it means first_arc is not part of a cycle according to
595 // bf_parent_arc_of_[], which should never happen.
596 const int num_nodes = impacted_arcs_.size();
597 while (arc_on_cycle.size() <= num_nodes) {
598 arc_on_cycle.push_back(arc_index);
599 const ArcInfo& arc = arcs_[arc_index];
600 if (arc.tail_var == first_arc_head) break;
601 arc_index = bf_parent_arc_of_[arc.tail_var.value()];
602 CHECK_NE(arc_index, ArcIndex(-1));
603 }
604 CHECK_NE(arc_on_cycle.size(), num_nodes + 1) << "Infinite loop.";
605
606 // Compute the reason for this cycle.
607 IntegerValue sum(0);
608 for (const ArcIndex arc_index : arc_on_cycle) {
609 const ArcInfo& arc = arcs_[arc_index];
610 sum += ArcOffset(arc);
611 AppendLowerBoundReasonIfValid(arc.offset_var, *integer_trail_,
612 integer_reason);
613 for (const Literal l : arc.presence_literals) {
614 literal_reason->push_back(l.Negated());
615 }
616
617 // If the cycle happens to contain optional variable not yet ignored, then
618 // it is not a conflict anymore, but we can infer that these variable must
619 // all be ignored. This is because since we propagated them even if they
620 // where not present for sure, their presence literal must form a cycle
621 // together (i.e. they are all absent or present at the same time).
622 if (integer_trail_->IsOptional(arc.head_var)) {
623 must_be_all_true->push_back(
624 integer_trail_->IsIgnoredLiteral(arc.head_var));
625 }
626 }
627
628 // TODO(user): what if the sum overflow? this is just a check so I guess
629 // we don't really care, but fix the issue.
630 CHECK_GT(sum, 0);
631}
632
633// Note that in our settings it is important to use an algorithm that tries to
634// minimize the number of integer_trail_->Enqueue() as much as possible.
635//
636// TODO(user): The current algorithm is quite efficient, but there is probably
637// still room for improvements.
638bool PrecedencesPropagator::BellmanFordTarjan(Trail* trail) {
639 const int num_nodes = impacted_arcs_.size();
640
641 // These vector are reset by CleanUpMarkedArcsAndParents() so resize is ok.
642 bf_can_be_skipped_.resize(num_nodes, false);
643 bf_parent_arc_of_.resize(num_nodes, ArcIndex(-1));
644 const auto cleanup =
645 ::absl::MakeCleanup([this]() { CleanUpMarkedArcsAndParents(); });
646
647 // The queue initialization is done by InitializeBFQueueWithModifiedNodes().
648 while (!bf_queue_.empty()) {
649 const int node = bf_queue_.front();
650 bf_queue_.pop_front();
651 bf_in_queue_[node] = false;
652
653 // TODO(user): we don't need bf_can_be_skipped_ since we can detect this
654 // if this node has a parent arc which is not marked. Investigate if it is
655 // faster without the vector<bool>.
656 //
657 // TODO(user): An alternative algorithm is to remove all these nodes from
658 // the queue instead of simply marking them. This should also lead to a
659 // better "relaxation" order of the arcs. It is however a bit more work to
660 // remove them since we need to track their position.
661 if (bf_can_be_skipped_[node]) {
662 DCHECK_NE(bf_parent_arc_of_[node], -1);
663 DCHECK(!arcs_[bf_parent_arc_of_[node]].is_marked);
664 continue;
665 }
666
667 const IntegerValue tail_lb =
668 integer_trail_->LowerBound(IntegerVariable(node));
669 for (const ArcIndex arc_index : impacted_arcs_[IntegerVariable(node)]) {
670 const ArcInfo& arc = arcs_[arc_index];
671 DCHECK_EQ(arc.tail_var, node);
672 const IntegerValue candidate = tail_lb + ArcOffset(arc);
673 if (candidate > integer_trail_->LowerBound(arc.head_var)) {
674 if (integer_trail_->IsCurrentlyIgnored(arc.head_var)) continue;
675 if (!EnqueueAndCheck(arc, candidate, trail)) return false;
676
677 // This is the Tarjan contribution to Bellman-Ford. This code detect
678 // positive cycle, and because it disassemble the subtree while doing
679 // so, the cost is amortized during the algorithm execution. Another
680 // advantages is that it will mark the node explored here as skippable
681 // which will avoid to propagate them too early (knowing that they will
682 // need to be propagated again later).
683 if (DisassembleSubtree(arc.head_var.value(), arc.tail_var.value(),
684 &bf_can_be_skipped_)) {
685 std::vector<Literal> must_be_all_true;
686 AnalyzePositiveCycle(arc_index, trail, &must_be_all_true,
687 &literal_reason_, &integer_reason_);
688 if (must_be_all_true.empty()) {
689 return integer_trail_->ReportConflict(literal_reason_,
690 integer_reason_);
691 } else {
692 gtl::STLSortAndRemoveDuplicates(&must_be_all_true);
693 for (const Literal l : must_be_all_true) {
694 if (trail_->Assignment().LiteralIsFalse(l)) {
695 literal_reason_.push_back(l);
696 return integer_trail_->ReportConflict(literal_reason_,
697 integer_reason_);
698 }
699 }
700 for (const Literal l : must_be_all_true) {
701 if (trail_->Assignment().LiteralIsTrue(l)) continue;
702 integer_trail_->EnqueueLiteral(l, literal_reason_,
703 integer_reason_);
704 }
705
706 // We just marked some optional variable as ignored, no need
707 // to update bf_parent_arc_of_[].
708 continue;
709 }
710 }
711
712 // We need to enforce the invariant that only the arc_index in
713 // bf_parent_arc_of_[] are marked (but not necessarily all of them
714 // since we unmark some in DisassembleSubtree()).
715 if (bf_parent_arc_of_[arc.head_var.value()] != -1) {
716 arcs_[bf_parent_arc_of_[arc.head_var.value()]].is_marked = false;
717 }
718
719 // Tricky: We just enqueued the fact that the lower-bound of head is
720 // candidate. However, because the domain of head may be discrete, it is
721 // possible that the lower-bound of head is now higher than candidate!
722 // If this is the case, we don't update bf_parent_arc_of_[] so that we
723 // don't wrongly detect a positive weight cycle because of this "extra
724 // push".
725 const IntegerValue new_bound = integer_trail_->LowerBound(arc.head_var);
726 if (new_bound == candidate) {
727 bf_parent_arc_of_[arc.head_var.value()] = arc_index;
728 arcs_[arc_index].is_marked = true;
729 } else {
730 // We still unmark any previous dependency, since we have pushed the
731 // value of arc.head_var further.
732 bf_parent_arc_of_[arc.head_var.value()] = -1;
733 }
734
735 // We do not re-enqueue if we are in a propagation loop and new_bound
736 // was not pushed to candidate or higher.
737 bf_can_be_skipped_[arc.head_var.value()] = false;
738 if (!bf_in_queue_[arc.head_var.value()] && new_bound >= candidate) {
739 bf_queue_.push_back(arc.head_var.value());
740 bf_in_queue_[arc.head_var.value()] = true;
741 }
742 }
743 }
744 }
745 return true;
746}
747
748int PrecedencesPropagator::AddGreaterThanAtLeastOneOfConstraintsFromClause(
749 const absl::Span<const Literal> clause, Model* model) {
750 CHECK_EQ(model->GetOrCreate<Trail>()->CurrentDecisionLevel(), 0);
751 if (clause.size() < 2) return 0;
752
753 // Collect all arcs impacted by this clause.
754 std::vector<ArcInfo> infos;
755 for (const Literal l : clause) {
756 if (l.Index() >= literal_to_new_impacted_arcs_.size()) continue;
757 for (const ArcIndex arc_index : literal_to_new_impacted_arcs_[l.Index()]) {
758 const ArcInfo& arc = arcs_[arc_index];
759 if (arc.presence_literals.size() != 1) continue;
760
761 // TODO(user): Support variable offset.
762 if (arc.offset_var != kNoIntegerVariable) continue;
763 infos.push_back(arc);
764 }
765 }
766 if (infos.size() <= 1) return 0;
767
768 // Stable sort by head_var so that for a same head_var, the entry are sorted
769 // by Literal as they appear in clause.
770 std::stable_sort(infos.begin(), infos.end(),
771 [](const ArcInfo& a, const ArcInfo& b) {
772 return a.head_var < b.head_var;
773 });
774
775 // We process ArcInfo with the same head_var toghether.
776 int num_added_constraints = 0;
777 auto* solver = model->GetOrCreate<SatSolver>();
778 for (int i = 0; i < infos.size();) {
779 const int start = i;
780 const IntegerVariable head_var = infos[start].head_var;
781 for (i++; i < infos.size() && infos[i].head_var == head_var; ++i) {
782 }
783 const absl::Span<ArcInfo> arcs(&infos[start], i - start);
784
785 // Skip single arcs since it will already be fully propagated.
786 if (arcs.size() < 2) continue;
787
788 // Heuristic. Look for full or almost full clauses. We could add
789 // GreaterThanAtLeastOneOf() with more enforcement literals. TODO(user):
790 // experiments.
791 if (arcs.size() + 1 < clause.size()) continue;
792
793 std::vector<IntegerVariable> vars;
794 std::vector<IntegerValue> offsets;
795 std::vector<Literal> selectors;
796 std::vector<Literal> enforcements;
797
798 int j = 0;
799 for (const Literal l : clause) {
800 bool added = false;
801 for (; j < arcs.size() && l == arcs[j].presence_literals.front(); ++j) {
802 added = true;
803 vars.push_back(arcs[j].tail_var);
804 offsets.push_back(arcs[j].offset);
805
806 // Note that duplicate selector are supported.
807 //
808 // TODO(user): If we support variable offset, we should regroup the arcs
809 // into one (tail + offset <= head) though, instead of having too
810 // identical entries.
811 selectors.push_back(l);
812 }
813 if (!added) {
814 enforcements.push_back(l.Negated());
815 }
816 }
817
818 // No point adding a constraint if there is not at least two different
819 // literals in selectors.
820 if (enforcements.size() + 1 == clause.size()) continue;
821
822 ++num_added_constraints;
823 model->Add(GreaterThanAtLeastOneOf(head_var, vars, offsets, selectors,
824 enforcements));
825 if (!solver->FinishPropagation()) return num_added_constraints;
826 }
827 return num_added_constraints;
828}
829
830int PrecedencesPropagator::
831 AddGreaterThanAtLeastOneOfConstraintsWithClauseAutoDetection(Model* model) {
832 auto* time_limit = model->GetOrCreate<TimeLimit>();
833 auto* solver = model->GetOrCreate<SatSolver>();
834
835 // Fill the set of incoming conditional arcs for each variables.
837 for (ArcIndex arc_index(0); arc_index < arcs_.size(); ++arc_index) {
838 const ArcInfo& arc = arcs_[arc_index];
839
840 // Only keep arc that have a fixed offset and a single presence_literals.
841 if (arc.offset_var != kNoIntegerVariable) continue;
842 if (arc.tail_var == arc.head_var) continue;
843 if (arc.presence_literals.size() != 1) continue;
844
845 if (arc.head_var >= incoming_arcs_.size()) {
846 incoming_arcs_.resize(arc.head_var.value() + 1);
847 }
848 incoming_arcs_[arc.head_var].push_back(arc_index);
849 }
850
851 int num_added_constraints = 0;
852 for (IntegerVariable target(0); target < incoming_arcs_.size(); ++target) {
853 if (incoming_arcs_[target].size() <= 1) continue;
854 if (time_limit->LimitReached()) return num_added_constraints;
855
856 // Detect set of incoming arcs for which at least one must be present.
857 // TODO(user): Find more than one disjoint set of incoming arcs.
858 // TODO(user): call MinimizeCoreWithPropagation() on the clause.
859 solver->Backtrack(0);
860 if (solver->IsModelUnsat()) return num_added_constraints;
861 std::vector<Literal> clause;
862 for (const ArcIndex arc_index : incoming_arcs_[target]) {
863 const Literal literal = arcs_[arc_index].presence_literals.front();
864 if (solver->Assignment().LiteralIsFalse(literal)) continue;
865
866 const int old_level = solver->CurrentDecisionLevel();
867 solver->EnqueueDecisionAndBacktrackOnConflict(literal.Negated());
868 if (solver->IsModelUnsat()) return num_added_constraints;
869 const int new_level = solver->CurrentDecisionLevel();
870 if (new_level <= old_level) {
871 clause = solver->GetLastIncompatibleDecisions();
872 break;
873 }
874 }
875 solver->Backtrack(0);
876
877 if (clause.size() > 1) {
878 // Extract the set of arc for which at least one must be present.
879 const absl::btree_set<Literal> clause_set(clause.begin(), clause.end());
880 std::vector<ArcIndex> arcs_in_clause;
881 for (const ArcIndex arc_index : incoming_arcs_[target]) {
882 const Literal literal(arcs_[arc_index].presence_literals.front());
883 if (gtl::ContainsKey(clause_set, literal.Negated())) {
884 arcs_in_clause.push_back(arc_index);
885 }
886 }
887
888 VLOG(2) << arcs_in_clause.size() << "/" << incoming_arcs_[target].size();
889
890 ++num_added_constraints;
891 std::vector<IntegerVariable> vars;
892 std::vector<IntegerValue> offsets;
893 std::vector<Literal> selectors;
894 for (const ArcIndex a : arcs_in_clause) {
895 vars.push_back(arcs_[a].tail_var);
896 offsets.push_back(arcs_[a].offset);
897 selectors.push_back(Literal(arcs_[a].presence_literals.front()));
898 }
899 model->Add(GreaterThanAtLeastOneOf(target, vars, offsets, selectors));
900 if (!solver->FinishPropagation()) return num_added_constraints;
901 }
902 }
903
904 return num_added_constraints;
905}
906
908 VLOG(1) << "Detecting GreaterThanAtLeastOneOf() constraints...";
909 auto* time_limit = model->GetOrCreate<TimeLimit>();
910 auto* solver = model->GetOrCreate<SatSolver>();
911 auto* clauses = model->GetOrCreate<LiteralWatchers>();
912 int num_added_constraints = 0;
913
914 // We have two possible approaches. For now, we prefer the first one except if
915 // there is too many clauses in the problem.
916 //
917 // TODO(user): Do more extensive experiment. Remove the second approach as
918 // it is more time consuming? or identify when it make sense. Note that the
919 // first approach also allows to use "incomplete" at least one between arcs.
920 if (clauses->AllClausesInCreationOrder().size() < 1e6) {
921 // TODO(user): This does not take into account clause of size 2 since they
922 // are stored in the BinaryImplicationGraph instead. Some ideas specific
923 // to size 2:
924 // - There can be a lot of such clauses, but it might be nice to consider
925 // them. we need to experiments.
926 // - The automatic clause detection might be a better approach and it
927 // could be combined with probing.
928 for (const SatClause* clause : clauses->AllClausesInCreationOrder()) {
929 if (time_limit->LimitReached()) return num_added_constraints;
930 if (solver->IsModelUnsat()) return num_added_constraints;
931 num_added_constraints += AddGreaterThanAtLeastOneOfConstraintsFromClause(
932 clause->AsSpan(), model);
933 }
934
935 // It is common that there is only two alternatives to push a variable.
936 // In this case, our presolve most likely made sure that the two are
937 // controlled by a single Boolean. This allows to detect this and add the
938 // appropriate greater than at least one of.
939 const int num_booleans = solver->NumVariables();
940 if (num_booleans < 1e6) {
941 for (int i = 0; i < num_booleans; ++i) {
942 if (time_limit->LimitReached()) return num_added_constraints;
943 if (solver->IsModelUnsat()) return num_added_constraints;
944 num_added_constraints +=
945 AddGreaterThanAtLeastOneOfConstraintsFromClause(
946 {Literal(BooleanVariable(i), true),
947 Literal(BooleanVariable(i), false)},
948 model);
949 }
950 }
951
952 } else {
953 num_added_constraints +=
954 AddGreaterThanAtLeastOneOfConstraintsWithClauseAutoDetection(model);
955 }
956
957 VLOG(1) << "Added " << num_added_constraints
958 << " GreaterThanAtLeastOneOf() constraints.";
959 return num_added_constraints;
960}
961
962} // namespace sat
963} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
#define CHECK(condition)
Definition: base/logging.h:495
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:892
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define CHECK_GT(val1, val2)
Definition: base/logging.h:708
#define CHECK_NE(val1, val2)
Definition: base/logging.h:704
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:896
#define DCHECK(condition)
Definition: base/logging.h:890
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:891
#define VLOG(verboselevel)
Definition: base/logging.h:984
void resize(size_type new_size)
size_type size() const
void push_back(const value_type &x)
void Set(IntegerType index)
Definition: bitset.h:809
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition: bitset.h:819
void ClearAndResize(IntegerType size)
Definition: bitset.h:784
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1569
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1048
bool IsCurrentlyIgnored(IntegerVariable i) const
Definition: integer.h:705
IntegerLiteral LowerBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1477
bool ReportConflict(absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.h:924
void EnqueueLiteral(Literal literal, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1162
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1449
void RelaxLinearReason(IntegerValue slack, absl::Span< const IntegerValue > coeffs, std::vector< IntegerLiteral > *reason) const
Definition: integer.cc:826
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1445
IntegerLiteral UpperBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1482
Literal IsIgnoredLiteral(IntegerVariable i) const
Definition: integer.h:710
bool IsOptional(IntegerVariable i) const
Definition: integer.h:702
IntegerVariable NumIntegerVariables() const
Definition: integer.h:645
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
void AddPrecedenceReason(int arc_index, IntegerValue min_offset, std::vector< Literal > *literal_reason, std::vector< IntegerLiteral > *integer_reason) const
Definition: precedences.cc:224
void ComputePrecedences(const std::vector< IntegerVariable > &vars, std::vector< IntegerPrecedences > *output)
Definition: precedences.cc:147
void Untrail(const Trail &trail, int trail_index) final
Definition: precedences.cc:123
bool PropagateOutgoingArcs(IntegerVariable var)
Definition: precedences.cc:108
const VariablesAssignment & Assignment() const
Definition: sat_base.h:383
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:153
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:150
int64_t b
int64_t a
SharedClausesManager * clauses
ModelSharedTimeLimit * time_limit
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
int arc
int index
absl::Cleanup< absl::decay_t< Callback > > MakeCleanup(Callback &&callback)
Definition: cleanup.h:125
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:200
std::function< int64_t(const Model &)> LowerBound(IntegerVariable v)
Definition: integer.h:1663
const IntegerVariable kNoIntegerVariable(-1)
std::function< void(Model *)> GreaterThanAtLeastOneOf(IntegerVariable target_var, const absl::Span< const IntegerVariable > vars, const absl::Span< const IntegerValue > offsets, const absl::Span< const Literal > selectors)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:47
Collection of objects used to extend the Constraint Solver library.
Literal literal
Definition: optimization.cc:89
int64_t tail
int64_t head
int64_t start
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1387