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