Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
precedences.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
15
16#include <stdint.h>
17
18#include <algorithm>
19#include <deque>
20#include <memory>
21#include <string>
22#include <utility>
23#include <vector>
24
25#include "absl/cleanup/cleanup.h"
26#include "absl/container/btree_set.h"
27#include "absl/container/flat_hash_map.h"
28#include "absl/container/flat_hash_set.h"
29#include "absl/container/inlined_vector.h"
30#include "absl/log/check.h"
31#include "absl/meta/type_traits.h"
32#include "absl/types/span.h"
36#include "ortools/graph/graph.h"
38#include "ortools/sat/clause.h"
40#include "ortools/sat/integer.h"
41#include "ortools/sat/model.h"
45#include "ortools/sat/util.h"
46#include "ortools/util/bitset.h"
50
51namespace operations_research {
52namespace sat {
53
54bool PrecedenceRelations::Add(IntegerVariable tail, IntegerVariable head,
55 IntegerValue offset) {
56 // Ignore trivial relation: tail + offset <= head.
57 if (integer_trail_->LevelZeroUpperBound(tail) + offset <=
58 integer_trail_->LevelZeroLowerBound(head)) {
59 return false;
60 }
61
62 // TODO(user): Return infeasible if tail == head and offset > 0.
63 // TODO(user): if tail = Negation(head) also update Domain.
64 if (tail == head) return false;
65
66 // Add to root_relations_.
67 //
68 // TODO(user): AddInternal() only returns true if this is the first relation
69 // between head and tail. But we can still avoid an extra lookup.
70 if (offset <= GetOffset(tail, head)) return false;
71 AddInternal(tail, head, offset);
72
73 // If we are not built, make sure there is enough room in the graph.
74 // TODO(user): Alternatively, force caller to do a Resize().
75 const int max_node =
76 std::max(PositiveVariable(tail), PositiveVariable(head)).value() + 1;
77 if (!is_built_ && max_node >= graph_.num_nodes()) {
78 graph_.AddNode(max_node);
79 }
80 return true;
81}
82
84 absl::Span<const Literal> enforcements, IntegerVariable a,
85 IntegerVariable b, IntegerValue rhs) {
86 // This must be currently true.
87 if (DEBUG_MODE) {
88 for (const Literal l : enforcements) {
89 CHECK(trail_->Assignment().LiteralIsTrue(l));
90 }
91 }
92
93 if (enforcements.empty() || trail_->CurrentDecisionLevel() == 0) {
94 Add(a, NegationOf(b), -rhs);
95 return;
96 }
97
98 // Ignore if no better than best_relations, otherwise increase it.
99 const auto key = GetKey(a, b);
100 {
101 const auto [it, inserted] = best_relations_.insert({key, rhs});
102 if (!inserted) {
103 if (rhs >= it->second) return; // Ignore.
104 it->second = rhs;
105 }
106 }
107
108 const int new_index = conditional_stack_.size();
109 const auto [it, inserted] = conditional_relations_.insert({key, new_index});
110 if (inserted) {
111 CreateLevelEntryIfNeeded();
112 conditional_stack_.emplace_back(/*prev_entry=*/-1, rhs, key, enforcements);
113
114 const int new_size = std::max(a.value(), b.value()) + 1;
115 if (new_size > conditional_after_.size()) {
116 conditional_after_.resize(new_size);
117 }
118 conditional_after_[a].push_back(NegationOf(b));
119 conditional_after_[b].push_back(NegationOf(a));
120 } else {
121 // We should only decrease because we ignored entry worse than the one in
122 // best_relations_.
123 const int prev_entry = it->second;
124 DCHECK_LT(rhs, conditional_stack_[prev_entry].rhs);
125
126 // Update.
127 it->second = new_index;
128 CreateLevelEntryIfNeeded();
129 conditional_stack_.emplace_back(prev_entry, rhs, key, enforcements);
130 }
131}
132
133void PrecedenceRelations::CreateLevelEntryIfNeeded() {
134 const int current = trail_->CurrentDecisionLevel();
135 if (!level_to_stack_size_.empty() &&
136 level_to_stack_size_.back().first == current)
137 return;
138 level_to_stack_size_.push_back({current, conditional_stack_.size()});
139}
140
141// We only pop what is needed.
143 while (!level_to_stack_size_.empty() &&
144 level_to_stack_size_.back().first > level) {
145 const int target = level_to_stack_size_.back().second;
146 DCHECK_GE(conditional_stack_.size(), target);
147 while (conditional_stack_.size() > target) {
148 const ConditionalEntry& back = conditional_stack_.back();
149 if (back.prev_entry != -1) {
150 conditional_relations_[back.key] = back.prev_entry;
151 UpdateBestRelation(back.key, conditional_stack_[back.prev_entry].rhs);
152 } else {
153 UpdateBestRelation(back.key, kMaxIntegerValue);
154 conditional_relations_.erase(back.key);
155
156 DCHECK_EQ(conditional_after_[back.key.first].back(),
157 NegationOf(back.key.second));
158 DCHECK_EQ(conditional_after_[back.key.second].back(),
159 NegationOf(back.key.first));
160 conditional_after_[back.key.first].pop_back();
161 conditional_after_[back.key.second].pop_back();
162 }
163 conditional_stack_.pop_back();
164 }
165 level_to_stack_size_.pop_back();
166 }
167}
168
169IntegerValue PrecedenceRelations::GetOffset(IntegerVariable a,
170 IntegerVariable b) const {
171 const auto it = root_relations_.find(GetKey(a, NegationOf(b)));
172 if (it != root_relations_.end()) {
173 return -it->second;
174 }
175 return kMinIntegerValue;
176}
177
179 IntegerVariable a, IntegerVariable b) const {
180 const auto it = conditional_relations_.find(GetKey(a, NegationOf(b)));
181 if (it == conditional_relations_.end()) return {};
182
183 const ConditionalEntry& entry = conditional_stack_[it->second];
184 if (DEBUG_MODE) {
185 for (const Literal l : entry.enforcements) {
186 CHECK(trail_->Assignment().LiteralIsTrue(l));
187 }
188 }
189 const IntegerValue root_level_offset = GetOffset(a, b);
190 const IntegerValue conditional_offset = -entry.rhs;
191 if (conditional_offset <= root_level_offset) return {};
192
193 DCHECK_EQ(entry.rhs, -GetConditionalOffset(a, b));
194 return entry.enforcements;
195}
196
198 IntegerVariable a, IntegerVariable b) const {
199 const auto it = best_relations_.find(GetKey(a, NegationOf(b)));
200 if (it != best_relations_.end()) {
201 return -it->second;
202 }
203 DCHECK(!root_relations_.contains(GetKey(a, NegationOf(b))));
204 DCHECK(!conditional_relations_.contains(GetKey(a, NegationOf(b))));
205 return kMinIntegerValue;
206}
207
209 if (is_built_) return;
210 is_built_ = true;
211
212 const int num_nodes = graph_.num_nodes();
214 before(num_nodes);
215
216 // We will construct a graph with the current relation from all_relations_.
217 // And use this to compute the "closure".
218 // Note that the non-determinism of the arcs order shouldn't matter.
219 CHECK(arc_offsets_.empty());
220 graph_.ReserveArcs(2 * root_relations_.size());
221 for (const auto [var_pair, negated_offset] : root_relations_) {
222 // TODO(user): Support negative offset?
223 //
224 // Note that if we only have >= 0 ones, if we do have a cycle, we could
225 // make sure all variales are the same, and otherwise, we have a DAG or a
226 // conflict.
227 const IntegerValue offset = -negated_offset;
228 if (offset < 0) continue;
229
230 // We have two arcs.
231 {
232 const IntegerVariable tail = var_pair.first;
233 const IntegerVariable head = NegationOf(var_pair.second);
234 graph_.AddArc(tail.value(), head.value());
235 arc_offsets_.push_back(offset);
236 CHECK_LT(var_pair.second, before.size());
237 before[head].push_back(tail);
238 }
239 {
240 const IntegerVariable tail = var_pair.second;
241 const IntegerVariable head = NegationOf(var_pair.first);
242 graph_.AddArc(tail.value(), head.value());
243 arc_offsets_.push_back(offset);
244 CHECK_LT(var_pair.second, before.size());
245 before[head].push_back(tail);
246 }
247 }
248
249 std::vector<int> permutation;
250 graph_.Build(&permutation);
251 util::Permute(permutation, &arc_offsets_);
252
253 // Is it a DAG?
254 // Get a topological order of the DAG formed by all the arcs that are present.
255 //
256 // TODO(user): This can fail if we don't have a DAG. We could just skip Bad
257 // edges instead, and have a sub-DAG as an heuristic. Or analyze the arc
258 // weight and make sure cycle are not an issue. We can also start with arcs
259 // with strictly positive weight.
260 //
261 // TODO(user): Only explore the sub-graph reachable from "vars".
262 DenseIntStableTopologicalSorter sorter(num_nodes);
263 for (int arc = 0; arc < graph_.num_arcs(); ++arc) {
264 sorter.AddEdge(graph_.Tail(arc), graph_.Head(arc));
265 }
266 int next;
267 bool graph_has_cycle = false;
268 topological_order_.clear();
269 while (sorter.GetNext(&next, &graph_has_cycle, nullptr)) {
270 topological_order_.push_back(IntegerVariable(next));
271 if (graph_has_cycle) {
272 is_dag_ = false;
273 return;
274 }
275 }
276 is_dag_ = !graph_has_cycle;
277
278 // Lets build full precedences if we don't have too many of them.
279 // TODO(user): Also do that if we don't have a DAG?
280 if (!is_dag_) return;
281
282 int work = 0;
283 const int kWorkLimit = 1e6;
284 for (const IntegerVariable tail_var : topological_order_) {
285 if (++work > kWorkLimit) break;
286 for (const int arc : graph_.OutgoingArcs(tail_var.value())) {
287 DCHECK_EQ(tail_var.value(), graph_.Tail(arc));
288 const IntegerVariable head_var = IntegerVariable(graph_.Head(arc));
289 const IntegerValue arc_offset = arc_offsets_[arc];
290
291 if (++work > kWorkLimit) break;
292 if (AddInternal(tail_var, head_var, arc_offset)) {
293 before[head_var].push_back(tail_var);
294 }
295
296 for (const IntegerVariable before_var : before[tail_var]) {
297 if (++work > kWorkLimit) break;
298 const IntegerValue offset =
299 -root_relations_.at(GetKey(before_var, NegationOf(tail_var))) +
300 arc_offset;
301 if (AddInternal(before_var, head_var, offset)) {
302 before[head_var].push_back(before_var);
303 }
304 }
305 }
306 }
307
308 VLOG(2) << "Full precedences. Work=" << work
309 << " Relations=" << root_relations_.size();
310}
311
313 absl::Span<const IntegerVariable> vars,
314 std::vector<FullIntegerPrecedence>* output) {
315 output->clear();
316 if (!is_built_) Build();
317 if (!is_dag_) return;
318
319 VLOG(2) << "num_nodes: " << graph_.num_nodes()
320 << " num_arcs: " << graph_.num_arcs() << " is_dag: " << is_dag_;
321
322 // Compute all precedences.
323 // We loop over the node in topological order, and we maintain for all
324 // variable we encounter, the list of "to_consider" variables that are before.
325 //
326 // TODO(user): use vector of fixed size.
327 absl::flat_hash_set<IntegerVariable> is_interesting;
328 absl::flat_hash_set<IntegerVariable> to_consider(vars.begin(), vars.end());
329 absl::flat_hash_map<IntegerVariable,
330 absl::flat_hash_map<IntegerVariable, IntegerValue>>
331 vars_before_with_offset;
332 absl::flat_hash_map<IntegerVariable, IntegerValue> tail_map;
333 for (const IntegerVariable tail_var : topological_order_) {
334 if (!to_consider.contains(tail_var) &&
335 !vars_before_with_offset.contains(tail_var)) {
336 continue;
337 }
338
339 // We copy the data for tail_var here, because the pointer is not stable.
340 // TODO(user): optimize when needed.
341 tail_map.clear();
342 {
343 const auto it = vars_before_with_offset.find(tail_var);
344 if (it != vars_before_with_offset.end()) {
345 tail_map = it->second;
346 }
347 }
348
349 for (const int arc : graph_.OutgoingArcs(tail_var.value())) {
350 CHECK_EQ(tail_var.value(), graph_.Tail(arc));
351 const IntegerVariable head_var = IntegerVariable(graph_.Head(arc));
352 const IntegerValue arc_offset = arc_offsets_[arc];
353
354 // No need to create an empty entry in this case.
355 if (tail_map.empty() && !to_consider.contains(tail_var)) continue;
356
357 auto& to_update = vars_before_with_offset[head_var];
358 for (const auto& [var_before, offset] : tail_map) {
359 if (!to_update.contains(var_before)) {
360 to_update[var_before] = arc_offset + offset;
361 } else {
362 to_update[var_before] =
363 std::max(arc_offset + offset, to_update[var_before]);
364 }
365 }
366 if (to_consider.contains(tail_var)) {
367 if (!to_update.contains(tail_var)) {
368 to_update[tail_var] = arc_offset;
369 } else {
370 to_update[tail_var] = std::max(arc_offset, to_update[tail_var]);
371 }
372 }
373
374 // Small filtering heuristic: if we have (before) < tail, and tail < head,
375 // we really do not need to list (before, tail) < head. We only need that
376 // if the list of variable before head contains some variable that are not
377 // already before tail.
378 if (to_update.size() > tail_map.size() + 1) {
379 is_interesting.insert(head_var);
380 } else {
381 is_interesting.erase(head_var);
382 }
383 }
384
385 // Extract the output for tail_var. Because of the topological ordering, the
386 // data for tail_var is already final now.
387 //
388 // TODO(user): Release the memory right away.
389 if (!is_interesting.contains(tail_var)) continue;
390 if (tail_map.size() == 1) continue;
391
393 data.var = tail_var;
394 IntegerValue min_offset = kMaxIntegerValue;
395 for (int i = 0; i < vars.size(); ++i) {
396 const auto offset_it = tail_map.find(vars[i]);
397 if (offset_it == tail_map.end()) continue;
398 data.indices.push_back(i);
399 data.offsets.push_back(offset_it->second);
400 min_offset = std::min(data.offsets.back(), min_offset);
401 }
402 output->push_back(std::move(data));
403 }
404}
405
407 absl::Span<const IntegerVariable> vars,
408 std::vector<PrecedenceData>* output) {
409 // +1 for the negation.
410 const int needed_size =
411 std::max(after_.size(), conditional_after_.size()) + 1;
412 var_to_degree_.resize(needed_size);
413 var_to_last_index_.resize(needed_size);
414 var_with_positive_degree_.resize(needed_size);
415 tmp_precedences_.clear();
416
417 // We first compute the degree/size in order to perform the transposition.
418 // Note that we also remove duplicates.
419 int num_relevants = 0;
420 IntegerVariable* var_with_positive_degree = var_with_positive_degree_.data();
421 int* var_to_degree = var_to_degree_.data();
422 int* var_to_last_index = var_to_last_index_.data();
423 const auto process = [&](int index, absl::Span<const IntegerVariable> v) {
424 for (const IntegerVariable after : v) {
425 DCHECK_LT(after, needed_size);
426 if (var_to_degree[after.value()] == 0) {
427 var_with_positive_degree[num_relevants++] = after;
428 } else {
429 // We do not want duplicates.
430 if (var_to_last_index[after.value()] == index) continue;
431 }
432
433 tmp_precedences_.push_back({after, index});
434 var_to_degree[after.value()]++;
435 var_to_last_index[after.value()] = index;
436 }
437 };
438
439 for (int index = 0; index < vars.size(); ++index) {
440 const IntegerVariable var = vars[index];
441 if (var < after_.size()) {
442 process(index, after_[var]);
443 }
444 if (var < conditional_after_.size()) {
445 process(index, conditional_after_[var]);
446 }
447 }
448
449 // Permute tmp_precedences_ into the output to put it in the correct order.
450 // For that we transform var_to_degree to point to the first position of
451 // each lbvar in the output vector.
452 int start = 0;
453 for (int i = 0; i < num_relevants; ++i) {
454 const IntegerVariable var = var_with_positive_degree[i];
455 const int degree = var_to_degree[var.value()];
456 if (degree > 1) {
457 var_to_degree[var.value()] = start;
458 start += degree;
459 } else {
460 // Optimization: we remove degree one relations.
461 var_to_degree[var.value()] = -1;
462 }
463 }
464
465 output->resize(start);
466 for (const auto& precedence : tmp_precedences_) {
467 // Note that it is okay to increase the -1 pos since they appear only once.
468 const int pos = var_to_degree[precedence.var.value()]++;
469 if (pos < 0) continue;
470 (*output)[pos] = precedence;
471 }
472
473 // Cleanup var_to_degree, note that we don't need to clean
474 // var_to_last_index_.
475 for (int i = 0; i < num_relevants; ++i) {
476 const IntegerVariable var = var_with_positive_degree[i];
477 var_to_degree[var.value()] = 0;
478 }
479}
480
481namespace {
482
483void AppendLowerBoundReasonIfValid(IntegerVariable var,
484 const IntegerTrail& i_trail,
485 std::vector<IntegerLiteral>* reason) {
486 if (var != kNoIntegerVariable) {
487 reason->push_back(i_trail.LowerBoundAsLiteral(var));
488 }
489}
490
491} // namespace
492
494 if (!VLOG_IS_ON(1)) return;
495 if (shared_stats_ == nullptr) return;
496 std::vector<std::pair<std::string, int64_t>> stats;
497 stats.push_back({"precedences/num_cycles", num_cycles_});
498 stats.push_back({"precedences/num_pushes", num_pushes_});
499 stats.push_back(
500 {"precedences/num_enforcement_pushes", num_enforcement_pushes_});
501 shared_stats_->AddStats(stats);
502}
503
505
508 const Literal literal = (*trail_)[propagation_trail_index_++];
509 if (literal.Index() >= literal_to_new_impacted_arcs_.size()) continue;
510
511 // IMPORTANT: Because of the way Untrail() work, we need to add all the
512 // potential arcs before we can abort. It is why we iterate twice here.
513 for (const ArcIndex arc_index :
514 literal_to_new_impacted_arcs_[literal.Index()]) {
515 if (--arc_counts_[arc_index] == 0) {
516 const ArcInfo& arc = arcs_[arc_index];
517 PushConditionalRelations(arc);
518 impacted_arcs_[arc.tail_var].push_back(arc_index);
519 }
520 }
521
522 // Iterate again to check for a propagation and indirectly update
523 // modified_vars_.
524 for (const ArcIndex arc_index :
525 literal_to_new_impacted_arcs_[literal.Index()]) {
526 if (arc_counts_[arc_index] > 0) continue;
527 const ArcInfo& arc = arcs_[arc_index];
528 const IntegerValue new_head_lb =
529 integer_trail_->LowerBound(arc.tail_var) + ArcOffset(arc);
530 if (new_head_lb > integer_trail_->LowerBound(arc.head_var)) {
531 if (!EnqueueAndCheck(arc, new_head_lb, trail_)) return false;
532 }
533 }
534 }
535
536 // Do the actual propagation of the IntegerVariable bounds.
537 InitializeBFQueueWithModifiedNodes();
538 if (!BellmanFordTarjan(trail_)) return false;
539
540 // We can only test that no propagation is left if we didn't enqueue new
541 // literal in the presence of optional variables.
542 //
543 // TODO(user): Because of our code to deal with InPropagationLoop(), this is
544 // not always true. Find a cleaner way to DCHECK() while not failing in this
545 // corner case.
546 if (/*DISABLES CODE*/ (false) &&
547 propagation_trail_index_ == trail_->Index()) {
548 DCHECK(NoPropagationLeft(*trail_));
549 }
550
551 // Propagate the presence literals of the arcs that can't be added.
552 PropagateOptionalArcs(trail_);
553
554 // Clean-up modified_vars_ to do as little as possible on the next call.
555 modified_vars_.ClearAndResize(integer_trail_->NumIntegerVariables());
556 return true;
557}
558
560 CHECK_NE(var, kNoIntegerVariable);
561 if (var >= impacted_arcs_.size()) return true;
562 for (const ArcIndex arc_index : impacted_arcs_[var]) {
563 const ArcInfo& arc = arcs_[arc_index];
564 const IntegerValue new_head_lb =
565 integer_trail_->LowerBound(arc.tail_var) + ArcOffset(arc);
566 if (new_head_lb > integer_trail_->LowerBound(arc.head_var)) {
567 if (!EnqueueAndCheck(arc, new_head_lb, trail_)) return false;
568 }
569 }
570 return true;
571}
572
573// TODO(user): Remove literal fixed at level zero from there.
574void PrecedencesPropagator::PushConditionalRelations(const ArcInfo& arc) {
575 // We currently do not handle variable size in the reasons.
576 // TODO(user): we could easily take a level zero ArcOffset() instead, or
577 // add this to the reason though.
578 if (arc.offset_var != kNoIntegerVariable) return;
579 const IntegerValue offset = ArcOffset(arc);
580 relations_->PushConditionalRelation(arc.presence_literals, arc.tail_var,
581 NegationOf(arc.head_var), -offset);
582}
583
584void PrecedencesPropagator::Untrail(const Trail& trail, int trail_index) {
585 if (propagation_trail_index_ > trail_index) {
586 // This means that we already propagated all there is to propagate
587 // at the level trail_index, so we can safely clear modified_vars_ in case
588 // it wasn't already done.
589 modified_vars_.ClearAndResize(integer_trail_->NumIntegerVariables());
590 }
591 while (propagation_trail_index_ > trail_index) {
593 if (literal.Index() >= literal_to_new_impacted_arcs_.size()) continue;
594 for (const ArcIndex arc_index :
595 literal_to_new_impacted_arcs_[literal.Index()]) {
596 if (arc_counts_[arc_index]++ == 0) {
597 const ArcInfo& arc = arcs_[arc_index];
598 impacted_arcs_[arc.tail_var].pop_back();
599 }
600 }
601 }
602}
603
604void PrecedencesPropagator::AdjustSizeFor(IntegerVariable i) {
605 const int index = std::max(i.value(), NegationOf(i).value());
606 if (index >= impacted_arcs_.size()) {
607 // TODO(user): only watch lower bound of the relevant variable instead
608 // of watching everything in [0, max_index_of_variable_used_in_this_class).
609 for (IntegerVariable var(impacted_arcs_.size()); var <= index; ++var) {
610 watcher_->WatchLowerBound(var, watcher_id_);
611 }
612 impacted_arcs_.resize(index + 1);
613 impacted_potential_arcs_.resize(index + 1);
614 }
615}
616
617void PrecedencesPropagator::AddArc(
618 IntegerVariable tail, IntegerVariable head, IntegerValue offset,
619 IntegerVariable offset_var, absl::Span<const Literal> presence_literals) {
620 AdjustSizeFor(tail);
621 AdjustSizeFor(head);
622 if (offset_var != kNoIntegerVariable) AdjustSizeFor(offset_var);
623
624 // This arc is present iff all the literals here are true.
625 absl::InlinedVector<Literal, 6> enforcement_literals;
626 {
627 for (const Literal l : presence_literals) {
628 enforcement_literals.push_back(l);
629 }
630 gtl::STLSortAndRemoveDuplicates(&enforcement_literals);
631
632 if (trail_->CurrentDecisionLevel() == 0) {
633 int new_size = 0;
634 for (const Literal l : enforcement_literals) {
635 if (trail_->Assignment().LiteralIsTrue(Literal(l))) {
636 continue; // At true, ignore this literal.
637 } else if (trail_->Assignment().LiteralIsFalse(Literal(l))) {
638 return; // At false, ignore completely this arc.
639 }
640 enforcement_literals[new_size++] = l;
641 }
642 enforcement_literals.resize(new_size);
643 }
644 }
645
646 if (head == tail) {
647 // A self-arc is either plain SAT or plain UNSAT or it forces something on
648 // the given offset_var or presence_literal_index. In any case it could be
649 // presolved in something more efficient.
650 VLOG(1) << "Self arc! This could be presolved. " << "var:" << tail
651 << " offset:" << offset << " offset_var:" << offset_var
652 << " conditioned_by:" << presence_literals;
653 }
654
655 // Remove the offset_var if it is fixed.
656 // TODO(user): We should also handle the case where tail or head is fixed.
657 if (offset_var != kNoIntegerVariable) {
658 const IntegerValue lb = integer_trail_->LevelZeroLowerBound(offset_var);
659 if (lb == integer_trail_->LevelZeroUpperBound(offset_var)) {
660 offset += lb;
661 offset_var = kNoIntegerVariable;
662 }
663 }
664
665 // Deal first with impacted_potential_arcs_/potential_arcs_.
666 if (!enforcement_literals.empty()) {
667 const OptionalArcIndex arc_index(potential_arcs_.size());
668 potential_arcs_.push_back(
669 {tail, head, offset, offset_var, enforcement_literals});
670 impacted_potential_arcs_[tail].push_back(arc_index);
671 impacted_potential_arcs_[NegationOf(head)].push_back(arc_index);
672 if (offset_var != kNoIntegerVariable) {
673 impacted_potential_arcs_[offset_var].push_back(arc_index);
674 }
675 }
676
677 // Now deal with impacted_arcs_/arcs_.
678 struct InternalArc {
679 IntegerVariable tail_var;
680 IntegerVariable head_var;
681 IntegerVariable offset_var;
682 };
683 std::vector<InternalArc> to_add;
684 if (offset_var == kNoIntegerVariable) {
685 // a + offset <= b and -b + offset <= -a
686 to_add.push_back({tail, head, kNoIntegerVariable});
687 to_add.push_back({NegationOf(head), NegationOf(tail), kNoIntegerVariable});
688 } else {
689 // tail (a) and offset_var (b) are symmetric, so we add:
690 // - a + b + offset <= c
691 to_add.push_back({tail, head, offset_var});
692 to_add.push_back({offset_var, head, tail});
693 // - a - c + offset <= -b
694 to_add.push_back({tail, NegationOf(offset_var), NegationOf(head)});
695 to_add.push_back({NegationOf(head), NegationOf(offset_var), tail});
696 // - b - c + offset <= -a
697 to_add.push_back({offset_var, NegationOf(tail), NegationOf(head)});
698 to_add.push_back({NegationOf(head), NegationOf(tail), offset_var});
699 }
700 for (const InternalArc a : to_add) {
701 // Since we add a new arc, we will need to consider its tail during the next
702 // propagation. Note that the size of modified_vars_ will be automatically
703 // updated when new integer variables are created since we register it with
704 // IntegerTrail in this class constructor.
705 //
706 // TODO(user): Adding arcs and then calling Untrail() before Propagate()
707 // will cause this mecanism to break. Find a more robust implementation.
708 //
709 // TODO(user): In some rare corner case, rescanning the whole list of arc
710 // leaving tail_var can make AddVar() have a quadratic complexity where it
711 // shouldn't. A better solution would be to see if this new arc currently
712 // propagate something, and if it does, just update the lower bound of
713 // a.head_var and let the normal "is modified" mecanism handle any eventual
714 // follow up propagations.
715 modified_vars_.Set(a.tail_var);
716
717 // If a.head_var is optional, we can potentially remove some literal from
718 // enforcement_literals.
719 const ArcIndex arc_index(arcs_.size());
720 arcs_.push_back(
721 {a.tail_var, a.head_var, offset, a.offset_var, enforcement_literals});
722 auto& presence_literals = arcs_.back().presence_literals;
723
724 if (presence_literals.empty()) {
725 impacted_arcs_[a.tail_var].push_back(arc_index);
726 } else {
727 for (const Literal l : presence_literals) {
728 if (l.Index() >= literal_to_new_impacted_arcs_.size()) {
729 literal_to_new_impacted_arcs_.resize(l.Index().value() + 1);
730 }
731 literal_to_new_impacted_arcs_[l.Index()].push_back(arc_index);
732 }
733 }
734
735 if (trail_->CurrentDecisionLevel() == 0) {
736 arc_counts_.push_back(presence_literals.size());
737 } else {
738 arc_counts_.push_back(0);
739 for (const Literal l : presence_literals) {
740 if (!trail_->Assignment().LiteralIsTrue(l)) {
741 ++arc_counts_.back();
742 }
743 }
744 CHECK(presence_literals.empty() || arc_counts_.back() > 0);
745 }
746 }
747}
748
750 IntegerVariable i2,
751 IntegerValue offset) {
752 DCHECK_EQ(trail_->CurrentDecisionLevel(), 0);
753 if (i1 < impacted_arcs_.size() && i2 < impacted_arcs_.size()) {
754 for (const ArcIndex index : impacted_arcs_[i1]) {
755 const ArcInfo& arc = arcs_[index];
756 if (arc.head_var == i2) {
757 const IntegerValue current = ArcOffset(arc);
758 if (offset <= current) {
759 return false;
760 } else {
761 // TODO(user): Modify arc in place!
762 }
763 break;
764 }
765 }
766 }
767
768 AddPrecedenceWithOffset(i1, i2, offset);
769 return true;
770}
771
772// TODO(user): On jobshop problems with a lot of tasks per machine (500), this
773// takes up a big chunk of the running time even before we find a solution.
774// This is because, for each lower bound changed, we inspect 500 arcs even
775// though they will never be propagated because the other bound is still at the
776// horizon. Find an even sparser algorithm?
777void PrecedencesPropagator::PropagateOptionalArcs(Trail* trail) {
778 for (const IntegerVariable var : modified_vars_.PositionsSetAtLeastOnce()) {
779 // The variables are not in increasing order, so we need to continue.
780 if (var >= impacted_potential_arcs_.size()) continue;
781
782 // Note that we can currently check the same ArcInfo up to 3 times, one for
783 // each of the arc variables: tail, NegationOf(head) and offset_var.
784 for (const OptionalArcIndex arc_index : impacted_potential_arcs_[var]) {
785 const ArcInfo& arc = potential_arcs_[arc_index];
786 int num_not_true = 0;
787 Literal to_propagate;
788 for (const Literal l : arc.presence_literals) {
789 if (!trail->Assignment().LiteralIsTrue(l)) {
790 ++num_not_true;
791 to_propagate = l;
792 }
793 }
794 if (num_not_true != 1) continue;
795 if (trail->Assignment().LiteralIsFalse(to_propagate)) continue;
796
797 // Test if this arc can be present or not.
798 // Important arc.tail_var can be different from var here.
799 const IntegerValue tail_lb = integer_trail_->LowerBound(arc.tail_var);
800 const IntegerValue head_ub = integer_trail_->UpperBound(arc.head_var);
801 if (tail_lb + ArcOffset(arc) > head_ub) {
802 integer_reason_.clear();
803 integer_reason_.push_back(
804 integer_trail_->LowerBoundAsLiteral(arc.tail_var));
805 integer_reason_.push_back(
806 integer_trail_->UpperBoundAsLiteral(arc.head_var));
807 AppendLowerBoundReasonIfValid(arc.offset_var, *integer_trail_,
808 &integer_reason_);
809 literal_reason_.clear();
810 for (const Literal l : arc.presence_literals) {
811 if (l != to_propagate) literal_reason_.push_back(l.Negated());
812 }
813 ++num_enforcement_pushes_;
814 integer_trail_->EnqueueLiteral(to_propagate.Negated(), literal_reason_,
815 integer_reason_);
816 }
817 }
818 }
819}
820
821IntegerValue PrecedencesPropagator::ArcOffset(const ArcInfo& arc) const {
822 return arc.offset + (arc.offset_var == kNoIntegerVariable
823 ? IntegerValue(0)
824 : integer_trail_->LowerBound(arc.offset_var));
825}
826
827bool PrecedencesPropagator::EnqueueAndCheck(const ArcInfo& arc,
828 IntegerValue new_head_lb,
829 Trail* trail) {
830 ++num_pushes_;
831 DCHECK_GT(new_head_lb, integer_trail_->LowerBound(arc.head_var));
832
833 // Compute the reason for new_head_lb.
834 //
835 // TODO(user): do like for clause and keep the negation of
836 // arc.presence_literals? I think we could change the integer.h API to accept
837 // true literal like for IntegerVariable, it is really confusing currently.
838 literal_reason_.clear();
839 for (const Literal l : arc.presence_literals) {
840 literal_reason_.push_back(l.Negated());
841 }
842
843 integer_reason_.clear();
844 integer_reason_.push_back(integer_trail_->LowerBoundAsLiteral(arc.tail_var));
845 AppendLowerBoundReasonIfValid(arc.offset_var, *integer_trail_,
846 &integer_reason_);
847
848 // The code works without this block since Enqueue() below can already take
849 // care of conflicts. However, it is better to deal with the conflict
850 // ourselves because we can be smarter about the reason this way.
851 //
852 // The reason for a "precedence" conflict is always a linear reason
853 // involving the tail lower_bound, the head upper bound and eventually the
854 // size lower bound. Because of that, we can use the RelaxLinearReason()
855 // code.
856 if (new_head_lb > integer_trail_->UpperBound(arc.head_var)) {
857 const IntegerValue slack =
858 new_head_lb - integer_trail_->UpperBound(arc.head_var) - 1;
859 integer_reason_.push_back(
860 integer_trail_->UpperBoundAsLiteral(arc.head_var));
861 std::vector<IntegerValue> coeffs(integer_reason_.size(), IntegerValue(1));
862 integer_trail_->RelaxLinearReason(slack, coeffs, &integer_reason_);
863 return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
864 }
865
866 return integer_trail_->Enqueue(
867 IntegerLiteral::GreaterOrEqual(arc.head_var, new_head_lb),
868 literal_reason_, integer_reason_);
869}
870
871bool PrecedencesPropagator::NoPropagationLeft(const Trail& trail) const {
872 const int num_nodes = impacted_arcs_.size();
873 for (IntegerVariable var(0); var < num_nodes; ++var) {
874 for (const ArcIndex arc_index : impacted_arcs_[var]) {
875 const ArcInfo& arc = arcs_[arc_index];
876 if (integer_trail_->LowerBound(arc.tail_var) + ArcOffset(arc) >
877 integer_trail_->LowerBound(arc.head_var)) {
878 return false;
879 }
880 }
881 }
882 return true;
883}
884
885void PrecedencesPropagator::InitializeBFQueueWithModifiedNodes() {
886 // Sparse clear of the queue. TODO(user): only use the sparse version if
887 // queue.size() is small or use SparseBitset.
888 const int num_nodes = impacted_arcs_.size();
889 bf_in_queue_.resize(num_nodes, false);
890 for (const int node : bf_queue_) bf_in_queue_[node] = false;
891 bf_queue_.clear();
892 DCHECK(std::none_of(bf_in_queue_.begin(), bf_in_queue_.end(),
893 [](bool v) { return v; }));
894 for (const IntegerVariable var : modified_vars_.PositionsSetAtLeastOnce()) {
895 if (var >= num_nodes) continue;
896 bf_queue_.push_back(var.value());
897 bf_in_queue_[var.value()] = true;
898 }
899}
900
901void PrecedencesPropagator::CleanUpMarkedArcsAndParents() {
902 // To be sparse, we use the fact that each node with a parent must be in
903 // modified_vars_.
904 const int num_nodes = impacted_arcs_.size();
905 for (const IntegerVariable var : modified_vars_.PositionsSetAtLeastOnce()) {
906 if (var >= num_nodes) continue;
907 const ArcIndex parent_arc_index = bf_parent_arc_of_[var.value()];
908 if (parent_arc_index != -1) {
909 arcs_[parent_arc_index].is_marked = false;
910 bf_parent_arc_of_[var.value()] = -1;
911 bf_can_be_skipped_[var.value()] = false;
912 }
913 }
914 DCHECK(std::none_of(bf_parent_arc_of_.begin(), bf_parent_arc_of_.end(),
915 [](ArcIndex v) { return v != -1; }));
916 DCHECK(std::none_of(bf_can_be_skipped_.begin(), bf_can_be_skipped_.end(),
917 [](bool v) { return v; }));
918}
919
920bool PrecedencesPropagator::DisassembleSubtree(
921 int source, int target, std::vector<bool>* can_be_skipped) {
922 // Note that we explore a tree, so we can do it in any order, and the one
923 // below seems to be the fastest.
924 tmp_vector_.clear();
925 tmp_vector_.push_back(source);
926 while (!tmp_vector_.empty()) {
927 const int tail = tmp_vector_.back();
928 tmp_vector_.pop_back();
929 for (const ArcIndex arc_index : impacted_arcs_[IntegerVariable(tail)]) {
930 const ArcInfo& arc = arcs_[arc_index];
931 if (arc.is_marked) {
932 arc.is_marked = false; // mutable.
933 if (arc.head_var.value() == target) return true;
934 DCHECK(!(*can_be_skipped)[arc.head_var.value()]);
935 (*can_be_skipped)[arc.head_var.value()] = true;
936 tmp_vector_.push_back(arc.head_var.value());
937 }
938 }
939 }
940 return false;
941}
942
943void PrecedencesPropagator::AnalyzePositiveCycle(
944 ArcIndex first_arc, Trail* trail, std::vector<Literal>* must_be_all_true,
945 std::vector<Literal>* literal_reason,
946 std::vector<IntegerLiteral>* integer_reason) {
947 must_be_all_true->clear();
948 literal_reason->clear();
949 integer_reason->clear();
950
951 // Follow bf_parent_arc_of_[] to find the cycle containing first_arc.
952 const IntegerVariable first_arc_head = arcs_[first_arc].head_var;
953 ArcIndex arc_index = first_arc;
954 std::vector<ArcIndex> arc_on_cycle;
955
956 // Just to be safe and avoid an infinite loop we use the fact that the maximum
957 // cycle size on a graph with n nodes is of size n. If we have more in the
958 // code below, it means first_arc is not part of a cycle according to
959 // bf_parent_arc_of_[], which should never happen.
960 const int num_nodes = impacted_arcs_.size();
961 while (arc_on_cycle.size() <= num_nodes) {
962 arc_on_cycle.push_back(arc_index);
963 const ArcInfo& arc = arcs_[arc_index];
964 if (arc.tail_var == first_arc_head) break;
965 arc_index = bf_parent_arc_of_[arc.tail_var.value()];
966 CHECK_NE(arc_index, ArcIndex(-1));
967 }
968 CHECK_NE(arc_on_cycle.size(), num_nodes + 1) << "Infinite loop.";
969
970 // Compute the reason for this cycle.
971 IntegerValue sum(0);
972 for (const ArcIndex arc_index : arc_on_cycle) {
973 const ArcInfo& arc = arcs_[arc_index];
974 sum += ArcOffset(arc);
975 AppendLowerBoundReasonIfValid(arc.offset_var, *integer_trail_,
976 integer_reason);
977 for (const Literal l : arc.presence_literals) {
978 literal_reason->push_back(l.Negated());
979 }
980 }
981
982 // TODO(user): what if the sum overflow? this is just a check so I guess
983 // we don't really care, but fix the issue.
984 CHECK_GT(sum, 0);
985}
986
987// Note that in our settings it is important to use an algorithm that tries to
988// minimize the number of integer_trail_->Enqueue() as much as possible.
989//
990// TODO(user): The current algorithm is quite efficient, but there is probably
991// still room for improvements.
992bool PrecedencesPropagator::BellmanFordTarjan(Trail* trail) {
993 const int num_nodes = impacted_arcs_.size();
994
995 // These vector are reset by CleanUpMarkedArcsAndParents() so resize is ok.
996 bf_can_be_skipped_.resize(num_nodes, false);
997 bf_parent_arc_of_.resize(num_nodes, ArcIndex(-1));
998 const auto cleanup =
999 ::absl::MakeCleanup([this]() { CleanUpMarkedArcsAndParents(); });
1000
1001 // The queue initialization is done by InitializeBFQueueWithModifiedNodes().
1002 while (!bf_queue_.empty()) {
1003 const int node = bf_queue_.front();
1004 bf_queue_.pop_front();
1005 bf_in_queue_[node] = false;
1006
1007 // TODO(user): we don't need bf_can_be_skipped_ since we can detect this
1008 // if this node has a parent arc which is not marked. Investigate if it is
1009 // faster without the vector<bool>.
1010 //
1011 // TODO(user): An alternative algorithm is to remove all these nodes from
1012 // the queue instead of simply marking them. This should also lead to a
1013 // better "relaxation" order of the arcs. It is however a bit more work to
1014 // remove them since we need to track their position.
1015 if (bf_can_be_skipped_[node]) {
1016 DCHECK_NE(bf_parent_arc_of_[node], -1);
1017 DCHECK(!arcs_[bf_parent_arc_of_[node]].is_marked);
1018 continue;
1019 }
1020
1021 const IntegerValue tail_lb =
1022 integer_trail_->LowerBound(IntegerVariable(node));
1023 for (const ArcIndex arc_index : impacted_arcs_[IntegerVariable(node)]) {
1024 const ArcInfo& arc = arcs_[arc_index];
1025 DCHECK_EQ(arc.tail_var, node);
1026 const IntegerValue candidate = tail_lb + ArcOffset(arc);
1027 if (candidate > integer_trail_->LowerBound(arc.head_var)) {
1028 if (!EnqueueAndCheck(arc, candidate, trail)) return false;
1029
1030 // This is the Tarjan contribution to Bellman-Ford. This code detect
1031 // positive cycle, and because it disassemble the subtree while doing
1032 // so, the cost is amortized during the algorithm execution. Another
1033 // advantages is that it will mark the node explored here as skippable
1034 // which will avoid to propagate them too early (knowing that they will
1035 // need to be propagated again later).
1036 if (DisassembleSubtree(arc.head_var.value(), arc.tail_var.value(),
1037 &bf_can_be_skipped_)) {
1038 std::vector<Literal> must_be_all_true;
1039 AnalyzePositiveCycle(arc_index, trail, &must_be_all_true,
1040 &literal_reason_, &integer_reason_);
1041 if (must_be_all_true.empty()) {
1042 ++num_cycles_;
1043 return integer_trail_->ReportConflict(literal_reason_,
1044 integer_reason_);
1045 } else {
1046 gtl::STLSortAndRemoveDuplicates(&must_be_all_true);
1047 for (const Literal l : must_be_all_true) {
1048 if (trail_->Assignment().LiteralIsFalse(l)) {
1049 literal_reason_.push_back(l);
1050 return integer_trail_->ReportConflict(literal_reason_,
1051 integer_reason_);
1052 }
1053 }
1054 for (const Literal l : must_be_all_true) {
1055 if (trail_->Assignment().LiteralIsTrue(l)) continue;
1056 integer_trail_->EnqueueLiteral(l, literal_reason_,
1057 integer_reason_);
1058 }
1059
1060 // We just marked some optional variable as ignored, no need
1061 // to update bf_parent_arc_of_[].
1062 continue;
1063 }
1064 }
1065
1066 // We need to enforce the invariant that only the arc_index in
1067 // bf_parent_arc_of_[] are marked (but not necessarily all of them
1068 // since we unmark some in DisassembleSubtree()).
1069 if (bf_parent_arc_of_[arc.head_var.value()] != -1) {
1070 arcs_[bf_parent_arc_of_[arc.head_var.value()]].is_marked = false;
1071 }
1072
1073 // Tricky: We just enqueued the fact that the lower-bound of head is
1074 // candidate. However, because the domain of head may be discrete, it is
1075 // possible that the lower-bound of head is now higher than candidate!
1076 // If this is the case, we don't update bf_parent_arc_of_[] so that we
1077 // don't wrongly detect a positive weight cycle because of this "extra
1078 // push".
1079 const IntegerValue new_bound = integer_trail_->LowerBound(arc.head_var);
1080 if (new_bound == candidate) {
1081 bf_parent_arc_of_[arc.head_var.value()] = arc_index;
1082 arcs_[arc_index].is_marked = true;
1083 } else {
1084 // We still unmark any previous dependency, since we have pushed the
1085 // value of arc.head_var further.
1086 bf_parent_arc_of_[arc.head_var.value()] = -1;
1087 }
1088
1089 // We do not re-enqueue if we are in a propagation loop and new_bound
1090 // was not pushed to candidate or higher.
1091 bf_can_be_skipped_[arc.head_var.value()] = false;
1092 if (!bf_in_queue_[arc.head_var.value()] && new_bound >= candidate) {
1093 bf_queue_.push_back(arc.head_var.value());
1094 bf_in_queue_[arc.head_var.value()] = true;
1095 }
1096 }
1097 }
1098 }
1099 return true;
1100}
1101
1103 LinearTerm b, IntegerValue lhs,
1104 IntegerValue rhs) {
1105 Relation r;
1106 r.enforcement = lit;
1107 r.a = a;
1108 r.b = b;
1109 r.lhs = lhs;
1110 r.rhs = rhs;
1111
1112 // We shall only consider positive variable here.
1113 if (r.a.var != kNoIntegerVariable && !VariableIsPositive(r.a.var)) {
1114 r.a.var = NegationOf(r.a.var);
1115 r.a.coeff = -r.a.coeff;
1116 }
1117 if (r.b.var != kNoIntegerVariable && !VariableIsPositive(r.b.var)) {
1118 r.b.var = NegationOf(r.b.var);
1119 r.b.coeff = -r.b.coeff;
1120 }
1121
1122 relations_.push_back(std::move(r));
1123}
1124
1125bool GreaterThanAtLeastOneOfDetector::AddRelationFromIndices(
1126 IntegerVariable var, absl::Span<const Literal> clause,
1127 absl::Span<const int> indices, Model* model) {
1128 std::vector<AffineExpression> exprs;
1129 std::vector<Literal> selectors;
1130 absl::flat_hash_set<LiteralIndex> used;
1131 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
1132
1133 const IntegerValue var_lb = integer_trail->LevelZeroLowerBound(var);
1134 for (const int index : indices) {
1135 Relation r = relations_[index];
1136 if (r.a.var != PositiveVariable(var)) std::swap(r.a, r.b);
1137 CHECK_EQ(r.a.var, PositiveVariable(var));
1138
1139 if ((r.a.coeff == 1) == VariableIsPositive(var)) {
1140 // a + b >= lhs
1141 if (r.lhs <= kMinIntegerValue) continue;
1142 exprs.push_back(AffineExpression(r.b.var, -r.b.coeff, r.lhs));
1143 } else {
1144 // -a + b <= rhs.
1145 if (r.rhs >= kMaxIntegerValue) continue;
1146 exprs.push_back(AffineExpression(r.b.var, r.b.coeff, -r.rhs));
1147 }
1148
1149 // Ignore this entry if it is always true.
1150 if (var_lb >= integer_trail->LevelZeroUpperBound(exprs.back())) {
1151 exprs.pop_back();
1152 continue;
1153 }
1154
1155 // Note that duplicate selector are supported.
1156 selectors.push_back(r.enforcement);
1157 used.insert(r.enforcement);
1158 }
1159
1160 // The enforcement of the new constraint are simply the literal not used
1161 // above.
1162 std::vector<Literal> enforcements;
1163 for (const Literal l : clause) {
1164 if (!used.contains(l.Index())) {
1165 enforcements.push_back(l.Negated());
1166 }
1167 }
1168
1169 // No point adding a constraint if there is not at least two different
1170 // literals in selectors.
1171 if (used.size() <= 1) return false;
1172
1173 // Add the constraint.
1174 GreaterThanAtLeastOneOfPropagator* constraint =
1175 new GreaterThanAtLeastOneOfPropagator(var, exprs, selectors, enforcements,
1176 model);
1177 constraint->RegisterWith(model->GetOrCreate<GenericLiteralWatcher>());
1178 model->TakeOwnership(constraint);
1179 return true;
1180}
1181
1182int GreaterThanAtLeastOneOfDetector::
1183 AddGreaterThanAtLeastOneOfConstraintsFromClause(
1184 const absl::Span<const Literal> clause, Model* model) {
1185 CHECK_EQ(model->GetOrCreate<Trail>()->CurrentDecisionLevel(), 0);
1186 if (clause.size() < 2) return 0;
1187
1188 // Collect all relations impacted by this clause.
1189 std::vector<std::pair<IntegerVariable, int>> infos;
1190 for (const Literal l : clause) {
1191 if (l.Index() >= lit_to_relations_->size()) continue;
1192 for (const int index : (*lit_to_relations_)[l.Index()]) {
1193 const Relation& r = relations_[index];
1194 if (r.a.var != kNoIntegerVariable && IntTypeAbs(r.a.coeff) == 1) {
1195 infos.push_back({r.a.var, index});
1196 }
1197 if (r.b.var != kNoIntegerVariable && IntTypeAbs(r.b.coeff) == 1) {
1198 infos.push_back({r.b.var, index});
1199 }
1200 }
1201 }
1202 if (infos.size() <= 1) return 0;
1203
1204 // Stable sort to regroup by var.
1205 std::stable_sort(infos.begin(), infos.end());
1206
1207 // We process the info with same variable together.
1208 int num_added_constraints = 0;
1209 std::vector<int> indices;
1210 for (int i = 0; i < infos.size();) {
1211 const int start = i;
1212 const IntegerVariable var = infos[start].first;
1213
1214 indices.clear();
1215 for (; i < infos.size() && infos[i].first == var; ++i) {
1216 indices.push_back(infos[i].second);
1217 }
1218
1219 // Skip single relations, we are not interested in these.
1220 if (indices.size() < 2) continue;
1221
1222 // Heuristic. Look for full or almost full clauses.
1223 //
1224 // TODO(user): We could add GreaterThanAtLeastOneOf() with more enforcement
1225 // literals. Experiment.
1226 if (indices.size() + 1 < clause.size()) continue;
1227
1228 if (AddRelationFromIndices(var, clause, indices, model)) {
1229 ++num_added_constraints;
1230 }
1231 if (AddRelationFromIndices(NegationOf(var), clause, indices, model)) {
1232 ++num_added_constraints;
1233 }
1234 }
1235 return num_added_constraints;
1236}
1237
1238int GreaterThanAtLeastOneOfDetector::
1239 AddGreaterThanAtLeastOneOfConstraintsWithClauseAutoDetection(Model* model) {
1240 auto* time_limit = model->GetOrCreate<TimeLimit>();
1241 auto* solver = model->GetOrCreate<SatSolver>();
1242
1243 // Fill the set of interesting relations for each variables.
1245 for (int index = 0; index < relations_.size(); ++index) {
1246 const Relation& r = relations_[index];
1247 if (r.a.var != kNoIntegerVariable && IntTypeAbs(r.a.coeff) == 1) {
1248 if (r.a.var >= var_to_relations.size()) {
1249 var_to_relations.resize(r.a.var + 1);
1250 }
1251 var_to_relations[r.a.var].push_back(index);
1252 }
1253 if (r.b.var != kNoIntegerVariable && IntTypeAbs(r.b.coeff) == 1) {
1254 if (r.b.var >= var_to_relations.size()) {
1255 var_to_relations.resize(r.b.var + 1);
1256 }
1257 var_to_relations[r.b.var].push_back(index);
1258 }
1259 }
1260
1261 int num_added_constraints = 0;
1262 for (IntegerVariable target(0); target < var_to_relations.size(); ++target) {
1263 if (var_to_relations[target].size() <= 1) continue;
1264 if (time_limit->LimitReached()) return num_added_constraints;
1265
1266 // Detect set of incoming arcs for which at least one must be present.
1267 // TODO(user): Find more than one disjoint set of incoming arcs.
1268 // TODO(user): call MinimizeCoreWithPropagation() on the clause.
1269 solver->Backtrack(0);
1270 if (solver->ModelIsUnsat()) return num_added_constraints;
1271 std::vector<Literal> clause;
1272 for (const int index : var_to_relations[target]) {
1273 const Literal literal = relations_[index].enforcement;
1274 if (solver->Assignment().LiteralIsFalse(literal)) continue;
1276 solver->EnqueueDecisionAndBacktrackOnConflict(literal.Negated());
1277 if (status == SatSolver::INFEASIBLE) return num_added_constraints;
1279 // We need to invert it, since a clause is not all false.
1280 clause = solver->GetLastIncompatibleDecisions();
1281 for (Literal& ref : clause) ref = ref.Negated();
1282 break;
1283 }
1284 }
1285 solver->Backtrack(0);
1286 if (clause.size() <= 1) continue;
1287
1288 // Recover the indices corresponding to this clause.
1289 const absl::btree_set<Literal> clause_set(clause.begin(), clause.end());
1290
1291 std::vector<int> indices;
1292 for (const int index : var_to_relations[target]) {
1293 const Literal literal = relations_[index].enforcement;
1294 if (clause_set.contains(literal)) {
1295 indices.push_back(index);
1296 }
1297 }
1298
1299 // Try both direction.
1300 if (AddRelationFromIndices(target, clause, indices, model)) {
1301 ++num_added_constraints;
1302 }
1303 if (AddRelationFromIndices(NegationOf(target), clause, indices, model)) {
1304 ++num_added_constraints;
1305 }
1306 }
1307
1308 solver->Backtrack(0);
1309 return num_added_constraints;
1310}
1311
1313 Model* model, bool auto_detect_clauses) {
1314 auto* time_limit = model->GetOrCreate<TimeLimit>();
1315 auto* solver = model->GetOrCreate<SatSolver>();
1316 auto* clauses = model->GetOrCreate<ClauseManager>();
1317 auto* logger = model->GetOrCreate<SolverLogger>();
1318
1319 int num_added_constraints = 0;
1320 SOLVER_LOG(logger, "[Precedences] num_relations=", relations_.size(),
1321 " num_clauses=", clauses->AllClausesInCreationOrder().size());
1322
1323 // Initialize lit_to_relations_.
1324 {
1325 std::vector<LiteralIndex> keys;
1326 const int num_relations = relations_.size();
1327 keys.reserve(num_relations);
1328 for (int i = 0; i < num_relations; ++i) {
1329 keys.push_back(relations_[i].enforcement.Index());
1330 }
1331 lit_to_relations_ =
1332 std::make_unique<CompactVectorVector<LiteralIndex, int>>();
1333 lit_to_relations_->ResetFromFlatMapping(keys, IdentityMap<int>());
1334 }
1335
1336 // We have two possible approaches. For now, we prefer the first one except if
1337 // there is too many clauses in the problem.
1338 //
1339 // TODO(user): Do more extensive experiment. Remove the second approach as
1340 // it is more time consuming? or identify when it make sense. Note that the
1341 // first approach also allows to use "incomplete" at least one between arcs.
1342 if (!auto_detect_clauses &&
1343 clauses->AllClausesInCreationOrder().size() < 1e6) {
1344 // TODO(user): This does not take into account clause of size 2 since they
1345 // are stored in the BinaryImplicationGraph instead. Some ideas specific
1346 // to size 2:
1347 // - There can be a lot of such clauses, but it might be nice to consider
1348 // them. we need to experiments.
1349 // - The automatic clause detection might be a better approach and it
1350 // could be combined with probing.
1351 for (const SatClause* clause : clauses->AllClausesInCreationOrder()) {
1352 if (time_limit->LimitReached()) return num_added_constraints;
1353 if (solver->ModelIsUnsat()) return num_added_constraints;
1354 num_added_constraints += AddGreaterThanAtLeastOneOfConstraintsFromClause(
1355 clause->AsSpan(), model);
1356 }
1357
1358 // It is common that there is only two alternatives to push a variable.
1359 // In this case, our presolve most likely made sure that the two are
1360 // controlled by a single Boolean. This allows to detect this and add the
1361 // appropriate greater than at least one of.
1362 const int num_booleans = solver->NumVariables();
1363 if (num_booleans < 1e6) {
1364 for (int i = 0; i < num_booleans; ++i) {
1365 if (time_limit->LimitReached()) return num_added_constraints;
1366 if (solver->ModelIsUnsat()) return num_added_constraints;
1367 num_added_constraints +=
1368 AddGreaterThanAtLeastOneOfConstraintsFromClause(
1369 {Literal(BooleanVariable(i), true),
1370 Literal(BooleanVariable(i), false)},
1371 model);
1372 }
1373 }
1374
1375 } else {
1376 num_added_constraints +=
1377 AddGreaterThanAtLeastOneOfConstraintsWithClauseAutoDetection(model);
1378 }
1379
1380 if (num_added_constraints > 0) {
1381 SOLVER_LOG(logger, "[Precedences] Added ", num_added_constraints,
1382 " GreaterThanAtLeastOneOf() constraints.");
1383 }
1384
1385 // Release the memory, it is not longer needed.
1386 lit_to_relations_.reset(nullptr);
1387 gtl::STLClearObject(&relations_);
1388 return num_added_constraints;
1389}
1390
1391} // namespace sat
1392} // namespace operations_research
IntegerValue size
void ClearAndResize(IntegerType size)
Definition bitset.h:839
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition bitset.h:878
void Set(IntegerType index)
Definition bitset.h:864
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
Definition integer.h:1852
int AddGreaterThanAtLeastOneOfConstraints(Model *model, bool auto_detect_clauses=false)
void Add(Literal lit, LinearTerm a, LinearTerm b, IntegerValue lhs, IntegerValue rhs)
Adds a relation lit => a + b \in [lhs, rhs].
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
Definition integer.h:1717
IntegerLiteral UpperBoundAsLiteral(IntegerVariable i) const
Definition integer.h:1765
void RelaxLinearReason(IntegerValue slack, absl::Span< const IntegerValue > coeffs, std::vector< IntegerLiteral > *reason) const
Definition integer.cc:1025
IntegerValue UpperBound(IntegerVariable i) const
Definition integer.h:1721
IntegerLiteral LowerBoundAsLiteral(IntegerVariable i) const
Definition integer.h:1760
bool ReportConflict(absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.h:1076
IntegerVariable NumIntegerVariables() const
Definition integer.h:806
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition integer.h:1817
void EnqueueLiteral(Literal literal, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.cc:1392
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Returns globally valid lower/upper bound on the given integer variable.
Definition integer.h:1812
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit)
Definition integer.h:972
void PushConditionalRelation(absl::Span< const Literal > enforcements, IntegerVariable a, IntegerVariable b, IntegerValue rhs)
void CollectPrecedences(absl::Span< const IntegerVariable > vars, std::vector< PrecedenceData > *output)
bool Add(IntegerVariable tail, IntegerVariable head, IntegerValue offset)
IntegerValue GetConditionalOffset(IntegerVariable a, IntegerVariable b) const
void ComputeFullPrecedences(absl::Span< const IntegerVariable > vars, std::vector< FullIntegerPrecedence > *output)
void SetLevel(int level) final
Called each time we change decision level.
IntegerValue GetOffset(IntegerVariable a, IntegerVariable b) const
absl::Span< const Literal > GetConditionalEnforcements(IntegerVariable a, IntegerVariable b) const
bool AddPrecedenceWithOffsetIfNew(IntegerVariable i1, IntegerVariable i2, IntegerValue offset)
This version check current precedence. It is however "slow".
void AddPrecedenceWithOffset(IntegerVariable i1, IntegerVariable i2, IntegerValue offset)
void Untrail(const Trail &trail, int trail_index) final
void AddStats(absl::Span< const std::pair< std::string, int64_t > > stats)
Adds a bunch of stats, adding count for the same key together.
const VariablesAssignment & Assignment() const
Definition sat_base.h:462
bool LiteralIsFalse(Literal literal) const
Definition sat_base.h:185
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:188
NodeIndexType num_nodes() const
Definition graph.h:211
ArcIndexType num_arcs() const
Returns the number of valid arcs in the graph.
Definition graph.h:215
ArcIndexType AddArc(NodeIndexType tail, NodeIndexType head)
Definition graph.h:1332
BeginEndWrapper< OutgoingArcIterator > OutgoingArcs(NodeIndexType node) const
NodeIndexType Tail(ArcIndexType arc) const
Definition graph.h:1353
void ReserveArcs(ArcIndexType bound) override
Definition graph.h:1316
void AddNode(NodeIndexType node)
Definition graph.h:1324
NodeIndexType Head(ArcIndexType arc) const
Definition graph.h:1360
bool GetNext(int *next_node_index, bool *cyclic, std::vector< int > *output_cycle_nodes=nullptr)
value_type * data()
– Pass-through methods to STL vector ----------------------------------—
void push_back(const value_type &val)
void resize(size_type new_size)
int64_t a
Definition table.cc:44
Block * next
int64_t value
IntVar * var
absl::Status status
Definition g_gurobi.cc:44
GRBmodel * model
int lit
int literal
time_limit
Definition solve.cc:22
int arc
int index
const bool DEBUG_MODE
Definition macros.h:24
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
void STLClearObject(T *obj)
Definition stl_util.h:123
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntType IntTypeAbs(IntType t)
Definition integer.h:81
std::function< int64_t(const Model &)> LowerBound(IntegerVariable v)
Definition integer.h:1955
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Returns the vector of the negated variables.
Definition integer.cc:51
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition integer.h:193
bool VariableIsPositive(IntegerVariable i)
Definition integer.h:189
In SWIG mode, we don't want anything besides these top-level includes.
void Permute(const IntVector &permutation, Array *array_to_permute)
Definition graph.h:758
int head
int tail
int64_t start
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition integer.h:1667
#define SOLVER_LOG(logger,...)
Definition logging.h:109