Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
circuit.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
14#include "ortools/sat/circuit.h"
15
16#include <functional>
17#include <utility>
18#include <vector>
19
20#include "absl/container/flat_hash_map.h"
21#include "absl/container/flat_hash_set.h"
22#include "absl/log/check.h"
23#include "absl/types/span.h"
27#include "ortools/sat/clause.h"
29#include "ortools/sat/integer.h"
30#include "ortools/sat/model.h"
35
36namespace operations_research {
37namespace sat {
38
40 const int num_nodes, absl::Span<const int> tails,
41 absl::Span<const int> heads, absl::Span<const Literal> enforcement_literals,
42 absl::Span<const Literal> literals, Options options, Model* model)
43 : num_nodes_(num_nodes),
44 options_(options),
45 trail_(*model->GetOrCreate<Trail>()),
46 enforcement_helper_(*model->GetOrCreate<EnforcementHelper>()),
47 assignment_(trail_.Assignment()) {
48 CHECK(!tails.empty()) << "Empty constraint, shouldn't be constructed!";
49 next_.resize(num_nodes_, -1);
50 prev_.resize(num_nodes_, -1);
51 next_literal_.resize(num_nodes_);
52 must_be_in_cycle_.resize(num_nodes_);
53 absl::flat_hash_map<LiteralIndex, int> literal_to_watch_index;
54
55 // Temporary data to fill watch_index_to_arcs_.
56 const int num_arcs = tails.size();
57 std::vector<int> keys;
58 std::vector<Arc> values;
59 keys.reserve(num_arcs);
60 values.reserve(num_arcs);
61
62 graph_.reserve(num_arcs);
63 self_arcs_.resize(num_nodes_, kFalseLiteralIndex);
64 enabled_ = true;
65 for (int arc = 0; arc < num_arcs; ++arc) {
66 const int head = heads[arc];
67 const int tail = tails[arc];
68 const Literal literal = literals[arc];
69 if (assignment_.LiteralIsFalse(literal)) continue;
70
71 if (tail == head) {
72 self_arcs_[tail] = literal.Index();
73 } else {
74 graph_[{tail, head}] = literal;
75 }
76
77 if (assignment_.LiteralIsTrue(literal)) {
78 if (next_[tail] != -1 || prev_[head] != -1) {
79 SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
80 if (enforcement_literals.empty()) {
81 VLOG(1) << "Trivially UNSAT or duplicate arcs while adding " << tail
82 << " -> " << head;
83 sat_solver->NotifyThatModelIsUnsat();
84 } else {
85 std::vector<Literal> negated_enforcement_literals;
86 for (const Literal literal : enforcement_literals) {
87 negated_enforcement_literals.push_back(literal.Negated());
88 }
89 sat_solver->AddProblemClause(negated_enforcement_literals);
90 enabled_ = false;
91 }
92 return;
93 }
94 AddArc(tail, head, kNoLiteralIndex);
95 continue;
96 }
97
98 // Tricky: For self-arc, we watch instead when the arc become false.
99 const Literal watched_literal = tail == head ? literal.Negated() : literal;
100 const auto& it = literal_to_watch_index.find(watched_literal.Index());
101 int watch_index = it != literal_to_watch_index.end() ? it->second : -1;
102 if (watch_index == -1) {
103 watch_index = watch_index_to_literal_.size();
104 literal_to_watch_index[watched_literal.Index()] = watch_index;
105 watch_index_to_literal_.push_back(watched_literal);
106 }
107
108 keys.push_back(watch_index);
109 values.push_back({tail, head});
110 }
111 watch_index_to_arcs_.ResetFromFlatMapping(keys, values);
112
113 for (int node = 0; node < num_nodes_; ++node) {
114 if (self_arcs_[node] == kFalseLiteralIndex ||
115 assignment_.LiteralIsFalse(Literal(self_arcs_[node]))) {
116 // For the multiple_subcircuit_through_zero case, must_be_in_cycle_ will
117 // be const and only contains zero.
118 if (node == 0 || !options_.multiple_subcircuit_through_zero) {
119 must_be_in_cycle_[rev_must_be_in_cycle_size_++] = node;
120 }
121 }
122 }
123
125 enforcement_id_ = enforcement_helper_.Register(enforcement_literals, watcher,
126 RegisterWith(watcher));
127}
128
129int CircuitPropagator::RegisterWith(GenericLiteralWatcher* watcher) {
130 const int id = watcher->Register(this);
131 for (int w = 0; w < watch_index_to_literal_.size(); ++w) {
132 watcher->WatchLiteral(watch_index_to_literal_[w], id, w);
133 }
134 watcher->RegisterReversibleClass(id, this);
135 watcher->RegisterReversibleInt(id, &rev_must_be_in_cycle_size_);
136
137 // This is needed in case a Literal is used for more than one arc, we may
138 // propagate it to false/true here, and it might trigger more propagation.
139 //
140 // TODO(user): come up with a test that fail when this is not here.
142 return id;
143}
144
146 if (!enabled_) return;
147 if (level == level_ends_.size()) return;
148 if (level > level_ends_.size()) {
149 while (level > level_ends_.size()) {
150 level_ends_.push_back(added_arcs_.size());
151 }
152 return;
153 }
154
155 // Backtrack.
156 for (int i = level_ends_[level]; i < added_arcs_.size(); ++i) {
157 const Arc arc = added_arcs_[i];
158 next_[arc.tail] = -1;
159 prev_[arc.head] = -1;
160 }
161 added_arcs_.resize(level_ends_[level]);
162 level_ends_.resize(level);
163}
164
165void CircuitPropagator::FillReasonForPath(int start_node,
166 std::vector<Literal>* reason) const {
167 CHECK_NE(start_node, -1);
168 reason->clear();
169 int node = start_node;
170 while (next_[node] != -1) {
171 if (next_literal_[node] != kNoLiteralIndex) {
172 reason->push_back(Literal(next_literal_[node]).Negated());
173 }
174 node = next_[node];
175 if (node == start_node) break;
176 }
177}
178
179bool CircuitPropagator::ReportConflictOrPropagateEnforcement(
180 std::vector<Literal>* reason) {
181 if (enforcement_helper_.Status(enforcement_id_) ==
183 enforcement_helper_.AddEnforcementReason(enforcement_id_, reason);
184 trail_.MutableConflict()->assign(reason->begin(), reason->end());
185 return false;
186 } else {
187 return enforcement_helper_.PropagateWhenFalse(enforcement_id_, *reason,
188 /*integer_reason=*/{});
189 }
190}
191
192// If multiple_subcircuit_through_zero is true, we never fill next_[0] and
193// prev_[0].
194void CircuitPropagator::AddArc(int tail, int head, LiteralIndex literal_index) {
195 if (tail != 0 || !options_.multiple_subcircuit_through_zero) {
196 next_[tail] = head;
197 next_literal_[tail] = literal_index;
198 }
199 if (head != 0 || !options_.multiple_subcircuit_through_zero) {
200 prev_[head] = tail;
201 }
202}
203
205 const std::vector<int>& watch_indices) {
206 if (!enabled_) return true;
207 const EnforcementStatus status = enforcement_helper_.Status(enforcement_id_);
210 return true;
211 }
212
213 for (const int w : watch_indices) {
214 const Literal literal = watch_index_to_literal_[w];
215 for (const Arc arc : watch_index_to_arcs_[w]) {
216 // Special case for self-arc.
217 if (arc.tail == arc.head) {
218 must_be_in_cycle_[rev_must_be_in_cycle_size_++] = arc.tail;
219 continue;
220 }
221
222 // Get rid of the trivial conflicts: At most one incoming and one outgoing
223 // arc for each nodes.
224 if (next_[arc.tail] != -1) {
225 if (next_literal_[arc.tail] != kNoLiteralIndex) {
226 temp_reason_ = {Literal(next_literal_[arc.tail]).Negated(),
227 literal.Negated()};
228 } else {
229 temp_reason_ = {literal.Negated()};
230 }
231 return ReportConflictOrPropagateEnforcement(&temp_reason_);
232 }
233 if (prev_[arc.head] != -1) {
234 if (next_literal_[prev_[arc.head]] != kNoLiteralIndex) {
235 temp_reason_ = {Literal(next_literal_[prev_[arc.head]]).Negated(),
236 literal.Negated()};
237 } else {
238 temp_reason_ = {literal.Negated()};
239 }
240 return ReportConflictOrPropagateEnforcement(&temp_reason_);
241 }
242
243 // Add the arc.
244 AddArc(arc.tail, arc.head, literal.Index());
245 added_arcs_.push_back(arc);
246 }
247 }
248 return Propagate();
249}
250
251// This function assumes that next_, prev_, next_literal_ and must_be_in_cycle_
252// are all up to date.
254 if (!enabled_) return true;
255 const EnforcementStatus status = enforcement_helper_.Status(enforcement_id_);
258 return true;
259 }
260
261 processed_.assign(num_nodes_, false);
262 for (int n = 0; n < num_nodes_; ++n) {
263 if (processed_[n]) continue;
264 if (next_[n] == n) continue;
265 if (next_[n] == -1 && prev_[n] == -1) continue;
266
267 // TODO(user): both this and the loop on must_be_in_cycle_ might take some
268 // time on large graph. Optimize if this become an issue.
269 in_current_path_.assign(num_nodes_, false);
270
271 // Find the start and end of the path containing node n. If this is a
272 // circuit, we will have start_node == end_node.
273 int start_node = n;
274 int end_node = n;
275 in_current_path_[n] = true;
276 processed_[n] = true;
277 while (next_[end_node] != -1) {
278 end_node = next_[end_node];
279 in_current_path_[end_node] = true;
280 processed_[end_node] = true;
281 if (end_node == n) break;
282 }
283 while (prev_[start_node] != -1) {
284 start_node = prev_[start_node];
285 in_current_path_[start_node] = true;
286 processed_[start_node] = true;
287 if (start_node == n) break;
288 }
289
290 // TODO(user): we can fail early in more case, like no more possible path
291 // to any of the mandatory node.
292 if (options_.multiple_subcircuit_through_zero) {
293 // Any cycle must contain zero.
294 if (start_node == end_node && !in_current_path_[0]) {
295 FillReasonForPath(start_node, &temp_reason_);
296 return ReportConflictOrPropagateEnforcement(&temp_reason_);
297 }
298
299 // An incomplete path cannot be closed except if one of the end-points
300 // is zero.
301 if (start_node != end_node && start_node != 0 && end_node != 0 &&
303 const auto it = graph_.find({end_node, start_node});
304 if (it == graph_.end()) continue;
305 const Literal literal = it->second;
306 if (assignment_.LiteralIsFalse(literal)) continue;
307
308 std::vector<Literal>* reason = trail_.GetEmptyVectorToStoreReason();
309 FillReasonForPath(start_node, reason);
310 enforcement_helper_.AddEnforcementReason(enforcement_id_, reason);
311 if (!trail_.EnqueueWithStoredReason(kNoClauseId, literal.Negated())) {
312 return false;
313 }
314 }
315
316 // None of the other propagation below are valid in case of multiple
317 // circuits.
318 continue;
319 }
320
321 // Check if we miss any node that must be in the circuit. Note that the ones
322 // for which self_arcs_[i] is kFalseLiteralIndex are first. This is good as
323 // it will produce shorter reason. Otherwise we prefer the first that was
324 // assigned in the trail.
325 bool miss_some_nodes = false;
326 LiteralIndex extra_reason = kFalseLiteralIndex;
327 for (int i = 0; i < rev_must_be_in_cycle_size_; ++i) {
328 const int node = must_be_in_cycle_[i];
329 if (!in_current_path_[node]) {
330 miss_some_nodes = true;
331 extra_reason = self_arcs_[node];
332 break;
333 }
334 }
335
336 if (miss_some_nodes) {
337 // A circuit that miss a mandatory node is a conflict.
338 if (start_node == end_node) {
339 FillReasonForPath(start_node, &temp_reason_);
340 if (extra_reason != kFalseLiteralIndex) {
341 temp_reason_.push_back(Literal(extra_reason));
342 }
343 return ReportConflictOrPropagateEnforcement(&temp_reason_);
344 }
345
346 // We have an unclosed path. Propagate the fact that it cannot
347 // be closed into a cycle, i.e. not(end_node -> start_node).
348 if (start_node != end_node && status == EnforcementStatus::IS_ENFORCED) {
349 const auto it = graph_.find({end_node, start_node});
350 if (it == graph_.end()) continue;
351 const Literal literal = it->second;
352 if (assignment_.LiteralIsFalse(literal)) continue;
353
354 std::vector<Literal>* reason = trail_.GetEmptyVectorToStoreReason();
355 FillReasonForPath(start_node, reason);
356 enforcement_helper_.AddEnforcementReason(enforcement_id_, reason);
357 if (extra_reason != kFalseLiteralIndex) {
358 reason->push_back(Literal(extra_reason));
359 }
360 const bool ok =
361 trail_.EnqueueWithStoredReason(kNoClauseId, literal.Negated());
362 if (!ok) return false;
363 continue;
364 }
365 }
366
367 // If we have a cycle, we can propagate all the other nodes to point to
368 // themselves. Otherwise there is nothing else to do.
369 if (start_node != end_node) continue;
370 BooleanVariable variable_with_same_reason = kNoBooleanVariable;
371 for (int node = 0; node < num_nodes_; ++node) {
372 if (in_current_path_[node]) continue;
373 if (self_arcs_[node] >= 0 &&
374 assignment_.LiteralIsTrue(Literal(self_arcs_[node]))) {
375 continue;
376 }
377
378 // This shouldn't happen because ExactlyOnePerRowAndPerColumn() should
379 // have executed first and propagated self_arcs_[node] to false.
380 CHECK_EQ(next_[node], -1);
381
382 // We should have detected that above (miss_some_nodes == true). But we
383 // still need this for corner cases where the same literal is used for
384 // many arcs, and we just propagated it here.
385 if (self_arcs_[node] == kFalseLiteralIndex ||
386 assignment_.LiteralIsFalse(Literal(self_arcs_[node]))) {
387 FillReasonForPath(start_node, &temp_reason_);
388 if (self_arcs_[node] != kFalseLiteralIndex) {
389 temp_reason_.push_back(Literal(self_arcs_[node]));
390 }
391 return ReportConflictOrPropagateEnforcement(&temp_reason_);
392 }
393
394 // Propagate.
395 if (status == EnforcementStatus::IS_ENFORCED) {
396 const Literal literal(self_arcs_[node]);
397 if (variable_with_same_reason == kNoBooleanVariable) {
398 variable_with_same_reason = literal.Variable();
399 std::vector<Literal>* reason = trail_.GetEmptyVectorToStoreReason();
400 FillReasonForPath(start_node, reason);
401 enforcement_helper_.AddEnforcementReason(enforcement_id_, reason);
402 const bool ok = trail_.EnqueueWithStoredReason(kNoClauseId, literal);
403 if (!ok) return false;
404 } else {
405 trail_.EnqueueWithSameReasonAs(literal, variable_with_same_reason);
406 }
407 }
408 }
409 }
410 return true;
411}
412
413NoCyclePropagator::NoCyclePropagator(int num_nodes, absl::Span<const int> tails,
414 absl::Span<const int> heads,
415 absl::Span<const Literal> literals,
416 Model* model)
417 : num_nodes_(num_nodes),
418 trail_(model->GetOrCreate<Trail>()),
419 assignment_(trail_->Assignment()) {
420 CHECK(!tails.empty()) << "Empty constraint, shouldn't be constructed!";
421
422 graph_.resize(num_nodes);
423 graph_literals_.resize(num_nodes);
424
425 const int num_arcs = tails.size();
426 absl::flat_hash_map<LiteralIndex, int> literal_to_watch_index;
427 for (int arc = 0; arc < num_arcs; ++arc) {
428 const int head = heads[arc];
429 const int tail = tails[arc];
430 const Literal literal = literals[arc];
431
432 if (assignment_.LiteralIsFalse(literal)) continue;
433 if (assignment_.LiteralIsTrue(literal)) {
434 // Fixed arc. It will never be removed.
435 graph_[tail].push_back(head);
436 graph_literals_[tail].push_back(literal);
437 continue;
438 }
439
440 // We have to deal with the same literal controlling more than one arc.
441 const auto [it, inserted] = literal_to_watch_index.insert(
442 {literal.Index(), watch_index_to_literal_.size()});
443 if (inserted) {
444 watch_index_to_literal_.push_back(literal);
445 watch_index_to_arcs_.push_back({});
446 }
447 watch_index_to_arcs_[it->second].push_back({tail, head});
448 }
449
450 // We register at construction.
451 //
452 // TODO(user): Uniformize this across propagator. Sometimes it is nice not
453 // to register them, but most of them can be registered right away.
454 RegisterWith(model->GetOrCreate<GenericLiteralWatcher>());
455}
456
457void NoCyclePropagator::RegisterWith(GenericLiteralWatcher* watcher) {
458 const int id = watcher->Register(this);
459 for (int w = 0; w < watch_index_to_literal_.size(); ++w) {
460 watcher->WatchLiteral(watch_index_to_literal_[w], id, w);
461 }
462 watcher->RegisterReversibleClass(id, this);
463
464 // This class currently only test for conflict, so no need to call it twice.
465 // watcher->NotifyThatPropagatorMayNotReachFixedPointInOnePass(id);
466}
467
469 if (level == level_ends_.size()) return;
470 if (level > level_ends_.size()) {
471 while (level > level_ends_.size()) {
472 level_ends_.push_back(touched_nodes_.size());
473 }
474 return;
475 }
476
477 // Backtrack.
478 for (int i = level_ends_[level]; i < touched_nodes_.size(); ++i) {
479 graph_literals_[touched_nodes_[i]].pop_back();
480 graph_[touched_nodes_[i]].pop_back();
481 }
482 touched_nodes_.resize(level_ends_[level]);
483 level_ends_.resize(level);
484}
485
487 const std::vector<int>& watch_indices) {
488 for (const int w : watch_indices) {
489 const Literal literal = watch_index_to_literal_[w];
490 for (const auto& [tail, head] : watch_index_to_arcs_[w]) {
491 graph_[tail].push_back(head);
492 graph_literals_[tail].push_back(literal);
493 touched_nodes_.push_back(tail);
494 }
495 }
496 return Propagate();
497}
498
499// TODO(user): only explore node with newly added arcs.
500//
501// TODO(user): We could easily re-index the graph so that only nodes with arcs
502// are used. Because right now we are in O(num_nodes) even if the graph is
503// empty.
505 // The graph should be up to date when this is called thanks to
506 // IncrementalPropagate(). We just do a SCC on the graph.
507 components_.clear();
508 FindStronglyConnectedComponents(num_nodes_, graph_, &components_);
509
510 for (const std::vector<int>& compo : components_) {
511 if (compo.size() <= 1) continue;
512
513 // We collect all arc from this compo.
514 //
515 // TODO(user): We could be more efficient here, but this is only executed on
516 // conflicts. We should at least make sure we return a single cycle even
517 // though if this is called often enough, we shouldn't have a lot more than
518 // this.
519 absl::flat_hash_set<int> nodes(compo.begin(), compo.end());
520 std::vector<Literal>* conflict = trail_->MutableConflict();
521 conflict->clear();
522 for (const int tail : compo) {
523 const int degree = graph_[tail].size();
524 CHECK_EQ(degree, graph_literals_[tail].size());
525 for (int i = 0; i < degree; ++i) {
526 if (nodes.contains(graph_[tail][i])) {
527 conflict->push_back(graph_literals_[tail][i].Negated());
528 }
529 }
530 }
531 return false;
532 }
533
534 return true;
535}
536
538 std::vector<std::vector<Literal>> graph,
539 absl::Span<const int> distinguished_nodes, Model* model)
540 : graph_(std::move(graph)),
541 num_nodes_(graph_.size()),
542 trail_(model->GetOrCreate<Trail>()) {
543 node_is_distinguished_.resize(num_nodes_, false);
544 for (const int node : distinguished_nodes) {
545 node_is_distinguished_[node] = true;
546 }
547}
548
550 const int watcher_id = watcher->Register(this);
551
552 // Fill fixed_arcs_ with arcs that are initially fixed to true,
553 // assign arcs to watch indices.
554 for (int node1 = 0; node1 < num_nodes_; node1++) {
555 for (int node2 = 0; node2 < num_nodes_; node2++) {
556 const Literal l = graph_[node1][node2];
557 if (trail_->Assignment().LiteralIsFalse(l)) continue;
558 if (trail_->Assignment().LiteralIsTrue(l)) {
559 fixed_arcs_.emplace_back(node1, node2);
560 } else {
561 watcher->WatchLiteral(l, watcher_id, watch_index_to_arc_.size());
562 watch_index_to_arc_.emplace_back(node1, node2);
563 }
564 }
565 }
566 watcher->RegisterReversibleClass(watcher_id, this);
567}
568
570 if (level == level_ends_.size()) return;
571 if (level > level_ends_.size()) {
572 while (level > level_ends_.size()) {
573 level_ends_.push_back(fixed_arcs_.size());
574 }
575 } else {
576 // Backtrack.
577 fixed_arcs_.resize(level_ends_[level]);
578 level_ends_.resize(level);
579 }
580}
581
583 const std::vector<int>& watch_indices) {
584 for (const int w : watch_indices) {
585 const auto& arc = watch_index_to_arc_[w];
586 fixed_arcs_.push_back(arc);
587 }
588 return Propagate();
589}
590
591void CircuitCoveringPropagator::FillFixedPathInReason(
592 int start, int end, std::vector<Literal>* reason) {
593 reason->clear();
594 int current = start;
595 do {
596 DCHECK_NE(next_[current], -1);
597 DCHECK(trail_->Assignment().LiteralIsTrue(graph_[current][next_[current]]));
598 reason->push_back(graph_[current][next_[current]].Negated());
599 current = next_[current];
600 } while (current != end);
601}
602
604 // Gather next_ and prev_ from fixed arcs.
605 next_.assign(num_nodes_, -1);
606 prev_.assign(num_nodes_, -1);
607 for (const auto& arc : fixed_arcs_) {
608 // Two arcs go out of arc.first, forbidden.
609 if (next_[arc.first] != -1) {
610 *trail_->MutableConflict() = {
611 graph_[arc.first][next_[arc.first]].Negated(),
612 graph_[arc.first][arc.second].Negated()};
613 return false;
614 }
615 next_[arc.first] = arc.second;
616 // Two arcs come into arc.second, forbidden.
617 if (prev_[arc.second] != -1) {
618 *trail_->MutableConflict() = {
619 graph_[prev_[arc.second]][arc.second].Negated(),
620 graph_[arc.first][arc.second].Negated()};
621 return false;
622 }
623 prev_[arc.second] = arc.first;
624 }
625
626 // For every node, find partial path/circuit in which the node is.
627 // Use visited_ to visit each path/circuit only once.
628 visited_.assign(num_nodes_, false);
629 for (int node = 0; node < num_nodes_; node++) {
630 // Skip if already visited, isolated or loop.
631 if (visited_[node]) continue;
632 if (prev_[node] == -1 && next_[node] == -1) continue;
633 if (prev_[node] == node) continue;
634
635 // Find start of path/circuit.
636 int start = node;
637 for (int current = prev_[node]; current != -1 && current != node;
638 current = prev_[current]) {
639 start = current;
640 }
641
642 // Find distinguished node of path. Fail if there are several,
643 // fail if this is a non loop circuit and there are none.
644 int distinguished = node_is_distinguished_[start] ? start : -1;
645 int current = next_[start];
646 int end = start;
647 visited_[start] = true;
648 while (current != -1 && current != start) {
649 if (node_is_distinguished_[current]) {
650 if (distinguished != -1) {
651 FillFixedPathInReason(distinguished, current,
652 trail_->MutableConflict());
653 return false;
654 }
655 distinguished = current;
656 }
657 visited_[current] = true;
658 end = current;
659 current = next_[current];
660 }
661
662 // Circuit with no distinguished nodes, forbidden.
663 if (start == current && distinguished == -1) {
664 FillFixedPathInReason(start, start, trail_->MutableConflict());
665 return false;
666 }
667
668 // Path with no distinguished node: forbid to close it.
669 if (current == -1 && distinguished == -1 &&
670 !trail_->Assignment().LiteralIsFalse(graph_[end][start])) {
671 auto* reason = trail_->GetEmptyVectorToStoreReason();
672 FillFixedPathInReason(start, end, reason);
673 const bool ok = trail_->EnqueueWithStoredReason(
674 kNoClauseId, graph_[end][start].Negated());
675 if (!ok) return false;
676 }
677 }
678 return true;
679}
680
682 absl::Span<const std::vector<Literal>> graph) {
683 return [=, graph = std::vector<std::vector<Literal>>(
684 graph.begin(), graph.end())](Model* model) {
685 const int n = graph.size();
686 std::vector<Literal> exactly_one_constraint;
687 exactly_one_constraint.reserve(n);
688 for (const bool transpose : {false, true}) {
689 for (int i = 0; i < n; ++i) {
690 exactly_one_constraint.clear();
691 for (int j = 0; j < n; ++j) {
692 exactly_one_constraint.push_back(transpose ? graph[j][i]
693 : graph[i][j]);
694 }
695 model->Add(ExactlyOneConstraint(exactly_one_constraint));
696 }
697 }
698 };
699}
700
701namespace {
702bool AddAtMostOne(absl::Span<const Literal> enforcement_literals,
703 absl::Span<const Literal> literals, Model* model) {
704 if (enforcement_literals.empty()) {
705 return model->GetOrCreate<BinaryImplicationGraph>()->AddAtMostOne(literals);
706 }
707 std::vector<Literal> enforcement(enforcement_literals.begin(),
708 enforcement_literals.end());
709 std::vector<LiteralWithCoeff> cst;
710 cst.reserve(literals.size());
711 for (const Literal l : literals) {
712 cst.emplace_back(l, Coefficient(1));
713 }
714 return model->GetOrCreate<SatSolver>()->AddLinearConstraint(
715 /*use_lower_bound=*/false, Coefficient(0),
716 /*use_upper_bound=*/true, Coefficient(1), &enforcement, &cst);
717}
718} // namespace
719
720void LoadSubcircuitConstraint(int num_nodes, absl::Span<const int> tails,
721 absl::Span<const int> heads,
722 absl::Span<const Literal> enforcement_literals,
723 absl::Span<const Literal> literals, Model* model,
724 bool multiple_subcircuit_through_zero) {
725 const int num_arcs = tails.size();
726 CHECK_GT(num_arcs, 0);
727 CHECK_EQ(heads.size(), num_arcs);
728 CHECK_EQ(literals.size(), num_arcs);
729
730 // If a node has no outgoing or no incoming arc, the model will be unsat
731 // as soon as we add the corresponding ExactlyOneConstraint().
732 auto sat_solver = model->GetOrCreate<SatSolver>();
733
734 std::vector<std::vector<Literal>> exactly_one_incoming(num_nodes);
735 std::vector<std::vector<Literal>> exactly_one_outgoing(num_nodes);
736 for (int arc = 0; arc < num_arcs; arc++) {
737 const int tail = tails[arc];
738 const int head = heads[arc];
739 exactly_one_outgoing[tail].push_back(literals[arc]);
740 exactly_one_incoming[head].push_back(literals[arc]);
741 }
742 for (int i = 0; i < exactly_one_incoming.size(); ++i) {
743 if (i == 0 && multiple_subcircuit_through_zero) continue;
744 if (!AddAtMostOne(enforcement_literals, exactly_one_incoming[i], model)) {
745 sat_solver->NotifyThatModelIsUnsat();
746 return;
747 }
748 model->Add(EnforcedClause(enforcement_literals, exactly_one_incoming[i]));
749 if (sat_solver->ModelIsUnsat()) return;
750 }
751 for (int i = 0; i < exactly_one_outgoing.size(); ++i) {
752 if (i == 0 && multiple_subcircuit_through_zero) continue;
753 if (!AddAtMostOne(enforcement_literals, exactly_one_outgoing[i], model)) {
754 sat_solver->NotifyThatModelIsUnsat();
755 return;
756 }
757 model->Add(EnforcedClause(enforcement_literals, exactly_one_outgoing[i]));
758 if (sat_solver->ModelIsUnsat()) return;
759 }
760
762 options.multiple_subcircuit_through_zero = multiple_subcircuit_through_zero;
764 num_nodes, tails, heads, enforcement_literals, literals, options, model));
765
766 // TODO(user): Just ignore node zero if multiple_subcircuit_through_zero is
767 // true.
768 // TODO(user): add support for enforcement literals in
769 // AllDifferentConstraint?
771 enforcement_literals.empty() && !multiple_subcircuit_through_zero) {
772 AllDifferentConstraint* constraint =
773 new AllDifferentConstraint(num_nodes, tails, heads, literals, model);
774 constraint->RegisterWith(model->GetOrCreate<GenericLiteralWatcher>());
775 model->TakeOwnership(constraint);
776 }
777}
778
779std::function<void(Model*)> CircuitCovering(
780 absl::Span<const std::vector<Literal>> graph,
781 absl::Span<const int> distinguished_nodes) {
782 return [=,
783 distinguished_nodes = std::vector<int>(distinguished_nodes.begin(),
784 distinguished_nodes.end()),
785 graph = std::vector<std::vector<Literal>>(
786 graph.begin(), graph.end())](Model* model) {
787 CircuitCoveringPropagator* constraint =
788 new CircuitCoveringPropagator(graph, distinguished_nodes, model);
789 constraint->RegisterWith(model->GetOrCreate<GenericLiteralWatcher>());
790 model->TakeOwnership(constraint);
791 };
792}
793
794} // namespace sat
795} // namespace operations_research
Definition model.h:345
void RegisterWith(GenericLiteralWatcher *watcher)
CircuitCoveringPropagator(std::vector< std::vector< Literal > > graph, absl::Span< const int > distinguished_nodes, Model *model)
Definition circuit.cc:537
void RegisterWith(GenericLiteralWatcher *watcher)
Definition circuit.cc:549
bool IncrementalPropagate(const std::vector< int > &watch_indices) final
Definition circuit.cc:582
bool IncrementalPropagate(const std::vector< int > &watch_indices) final
Definition circuit.cc:204
CircuitPropagator(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > enforcement_literals, absl::Span< const Literal > literals, Options options, Model *model)
Definition circuit.cc:39
void WatchLiteral(Literal l, int id, int watch_index=-1)
Definition integer.h:1708
void RegisterReversibleClass(int id, ReversibleInterface *rev)
Definition integer.cc:2716
int Register(PropagatorInterface *propagator)
Definition integer.cc:2674
LiteralIndex Index() const
Definition sat_base.h:92
BooleanVariable Variable() const
Definition sat_base.h:88
T Add(std::function< T(Model *)> f)
Definition model.h:104
bool IncrementalPropagate(const std::vector< int > &watch_indices) final
Definition circuit.cc:486
NoCyclePropagator(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > literals, Model *model)
Definition circuit.cc:413
bool AddProblemClause(absl::Span< const Literal > literals, bool shared=false)
const VariablesAssignment & Assignment() const
Definition sat_base.h:654
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:206
std::tuple< int64_t, int64_t, const double > Coefficient
const LiteralIndex kNoLiteralIndex(-1)
std::function< void(Model *)> EnforcedClause(absl::Span< const Literal > enforcement_literals, absl::Span< const Literal > clause)
std::function< void(Model *)> CircuitCovering(absl::Span< const std::vector< Literal > > graph, absl::Span< const int > distinguished_nodes)
Definition circuit.cc:779
constexpr ClauseId kNoClauseId(0)
void LoadSubcircuitConstraint(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > enforcement_literals, absl::Span< const Literal > literals, Model *model, bool multiple_subcircuit_through_zero)
Definition circuit.cc:720
const LiteralIndex kFalseLiteralIndex(-3)
std::function< void(Model *)> ExactlyOneConstraint(absl::Span< const Literal > literals)
Definition sat_solver.h:964
std::function< void(Model *)> ExactlyOnePerRowAndPerColumn(absl::Span< const std::vector< Literal > > graph)
Definition circuit.cc:681
const BooleanVariable kNoBooleanVariable(-1)
OR-Tools root namespace.
ClosedInterval::Iterator end(ClosedInterval interval)
STL namespace.
trees with all degrees equal w the current value of w
void FindStronglyConnectedComponents(NodeIndex num_nodes, const Graph &graph, SccOutput *components)