OR-Tools  9.1
circuit.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#include "ortools/sat/circuit.h"
15
16#include <algorithm>
17
18#include "absl/container/flat_hash_map.h"
21
22namespace operations_research {
23namespace sat {
24
26 const std::vector<int>& tails,
27 const std::vector<int>& heads,
28 const std::vector<Literal>& literals,
29 Options options, Model* model)
30 : num_nodes_(num_nodes),
31 options_(options),
32 trail_(model->GetOrCreate<Trail>()),
33 assignment_(trail_->Assignment()) {
34 CHECK(!tails.empty()) << "Empty constraint, shouldn't be constructed!";
35 next_.resize(num_nodes_, -1);
36 prev_.resize(num_nodes_, -1);
37 next_literal_.resize(num_nodes_);
38 must_be_in_cycle_.resize(num_nodes_);
39 absl::flat_hash_map<LiteralIndex, int> literal_to_watch_index;
40
41 const int num_arcs = tails.size();
42 graph_.reserve(num_arcs);
43 self_arcs_.resize(num_nodes_,
44 model->GetOrCreate<IntegerEncoder>()->GetFalseLiteral());
45 for (int arc = 0; arc < num_arcs; ++arc) {
46 const int head = heads[arc];
47 const int tail = tails[arc];
48 const Literal literal = literals[arc];
49 if (assignment_.LiteralIsFalse(literal)) continue;
50
51 if (tail == head) {
52 self_arcs_[tail] = literal;
53 } else {
54 graph_[{tail, head}] = literal;
55 }
56
57 if (assignment_.LiteralIsTrue(literal)) {
58 if (next_[tail] != -1 || prev_[head] != -1) {
59 VLOG(1) << "Trivially UNSAT or duplicate arcs while adding " << tail
60 << " -> " << head;
61 model->GetOrCreate<SatSolver>()->NotifyThatModelIsUnsat();
62 return;
63 }
64 AddArc(tail, head, kNoLiteralIndex);
65 continue;
66 }
67
68 // Tricky: For self-arc, we watch instead when the arc become false.
69 const Literal watched_literal = tail == head ? literal.Negated() : literal;
70 int watch_index = gtl::FindWithDefault(literal_to_watch_index,
71 watched_literal.Index(), -1);
72 if (watch_index == -1) {
73 watch_index = watch_index_to_literal_.size();
74 literal_to_watch_index[watched_literal.Index()] = watch_index;
75 watch_index_to_literal_.push_back(watched_literal);
76 watch_index_to_arcs_.push_back(std::vector<Arc>());
77 }
78 watch_index_to_arcs_[watch_index].push_back({tail, head});
79 }
80
81 for (int node = 0; node < num_nodes_; ++node) {
82 if (assignment_.LiteralIsFalse(self_arcs_[node])) {
83 // For the multiple_subcircuit_through_zero case, must_be_in_cycle_ will
84 // be const and only contains zero.
85 if (node == 0 || !options_.multiple_subcircuit_through_zero) {
86 must_be_in_cycle_[rev_must_be_in_cycle_size_++] = node;
87 }
88 }
89 }
90}
91
93 const int id = watcher->Register(this);
94 for (int w = 0; w < watch_index_to_literal_.size(); ++w) {
95 watcher->WatchLiteral(watch_index_to_literal_[w], id, w);
96 }
97 watcher->RegisterReversibleClass(id, this);
98 watcher->RegisterReversibleInt(id, &propagation_trail_index_);
99 watcher->RegisterReversibleInt(id, &rev_must_be_in_cycle_size_);
100
101 // This is needed in case a Literal is used for more than one arc, we may
102 // propagate it to false/true here, and it might trigger more propagation.
103 //
104 // TODO(user): come up with a test that fail when this is not here.
106}
107
109 if (level == level_ends_.size()) return;
110 if (level > level_ends_.size()) {
111 while (level > level_ends_.size()) {
112 level_ends_.push_back(added_arcs_.size());
113 }
114 return;
115 }
116
117 // Backtrack.
118 for (int i = level_ends_[level]; i < added_arcs_.size(); ++i) {
119 const Arc arc = added_arcs_[i];
120 next_[arc.tail] = -1;
121 prev_[arc.head] = -1;
122 }
123 added_arcs_.resize(level_ends_[level]);
124 level_ends_.resize(level);
125}
126
127void CircuitPropagator::FillReasonForPath(int start_node,
128 std::vector<Literal>* reason) const {
129 CHECK_NE(start_node, -1);
130 reason->clear();
131 int node = start_node;
132 while (next_[node] != -1) {
133 if (next_literal_[node] != kNoLiteralIndex) {
134 reason->push_back(Literal(next_literal_[node]).Negated());
135 }
136 node = next_[node];
137 if (node == start_node) break;
138 }
139}
140
141// If multiple_subcircuit_through_zero is true, we never fill next_[0] and
142// prev_[0].
143void CircuitPropagator::AddArc(int tail, int head, LiteralIndex literal_index) {
144 if (tail != 0 || !options_.multiple_subcircuit_through_zero) {
145 next_[tail] = head;
146 next_literal_[tail] = literal_index;
147 }
148 if (head != 0 || !options_.multiple_subcircuit_through_zero) {
149 prev_[head] = tail;
150 }
151}
152
154 const std::vector<int>& watch_indices) {
155 for (const int w : watch_indices) {
156 const Literal literal = watch_index_to_literal_[w];
157 for (const Arc arc : watch_index_to_arcs_[w]) {
158 // Special case for self-arc.
159 if (arc.tail == arc.head) {
160 must_be_in_cycle_[rev_must_be_in_cycle_size_++] = arc.tail;
161 continue;
162 }
163
164 // Get rid of the trivial conflicts: At most one incoming and one outgoing
165 // arc for each nodes.
166 if (next_[arc.tail] != -1) {
167 std::vector<Literal>* conflict = trail_->MutableConflict();
168 if (next_literal_[arc.tail] != kNoLiteralIndex) {
169 *conflict = {Literal(next_literal_[arc.tail]).Negated(),
170 literal.Negated()};
171 } else {
172 *conflict = {literal.Negated()};
173 }
174 return false;
175 }
176 if (prev_[arc.head] != -1) {
177 std::vector<Literal>* conflict = trail_->MutableConflict();
178 if (next_literal_[prev_[arc.head]] != kNoLiteralIndex) {
179 *conflict = {Literal(next_literal_[prev_[arc.head]]).Negated(),
180 literal.Negated()};
181 } else {
182 *conflict = {literal.Negated()};
183 }
184 return false;
185 }
186
187 // Add the arc.
188 AddArc(arc.tail, arc.head, literal.Index());
189 added_arcs_.push_back(arc);
190 }
191 }
192 return Propagate();
193}
194
195// This function assumes that next_, prev_, next_literal_ and must_be_in_cycle_
196// are all up to date.
198 processed_.assign(num_nodes_, false);
199 for (int n = 0; n < num_nodes_; ++n) {
200 if (processed_[n]) continue;
201 if (next_[n] == n) continue;
202 if (next_[n] == -1 && prev_[n] == -1) continue;
203
204 // TODO(user): both this and the loop on must_be_in_cycle_ might take some
205 // time on large graph. Optimize if this become an issue.
206 in_current_path_.assign(num_nodes_, false);
207
208 // Find the start and end of the path containing node n. If this is a
209 // circuit, we will have start_node == end_node.
210 int start_node = n;
211 int end_node = n;
212 in_current_path_[n] = true;
213 processed_[n] = true;
214 while (next_[end_node] != -1) {
215 end_node = next_[end_node];
216 in_current_path_[end_node] = true;
217 processed_[end_node] = true;
218 if (end_node == n) break;
219 }
220 while (prev_[start_node] != -1) {
221 start_node = prev_[start_node];
222 in_current_path_[start_node] = true;
223 processed_[start_node] = true;
224 if (start_node == n) break;
225 }
226
227 // TODO(user): we can fail early in more case, like no more possible path
228 // to any of the mandatory node.
230 // Any cycle must contain zero.
231 if (start_node == end_node && !in_current_path_[0]) {
232 FillReasonForPath(start_node, trail_->MutableConflict());
233 return false;
234 }
235
236 // An incomplete path cannot be closed except if one of the end-points
237 // is zero.
238 if (start_node != end_node && start_node != 0 && end_node != 0) {
239 const auto it = graph_.find({end_node, start_node});
240 if (it == graph_.end()) continue;
241 const Literal literal = it->second;
242 if (assignment_.LiteralIsFalse(literal)) continue;
243
244 std::vector<Literal>* reason = trail_->GetEmptyVectorToStoreReason();
245 FillReasonForPath(start_node, reason);
246 if (!trail_->EnqueueWithStoredReason(literal.Negated())) {
247 return false;
248 }
249 }
250
251 // None of the other propagation below are valid in case of multiple
252 // circuits.
253 continue;
254 }
255
256 // Check if we miss any node that must be in the circuit. Note that the ones
257 // for which self_arcs_[i] is kFalseLiteralIndex are first. This is good as
258 // it will produce shorter reason. Otherwise we prefer the first that was
259 // assigned in the trail.
260 bool miss_some_nodes = false;
261 LiteralIndex extra_reason = kFalseLiteralIndex;
262 for (int i = 0; i < rev_must_be_in_cycle_size_; ++i) {
263 const int node = must_be_in_cycle_[i];
264 if (!in_current_path_[node]) {
265 miss_some_nodes = true;
266 extra_reason = self_arcs_[node].Index();
267 break;
268 }
269 }
270
271 if (miss_some_nodes) {
272 // A circuit that miss a mandatory node is a conflict.
273 if (start_node == end_node) {
274 FillReasonForPath(start_node, trail_->MutableConflict());
275 if (extra_reason != kFalseLiteralIndex) {
276 trail_->MutableConflict()->push_back(Literal(extra_reason));
277 }
278 return false;
279 }
280
281 // We have an unclosed path. Propagate the fact that it cannot
282 // be closed into a cycle, i.e. not(end_node -> start_node).
283 if (start_node != end_node) {
284 const auto it = graph_.find({end_node, start_node});
285 if (it == graph_.end()) continue;
286 const Literal literal = it->second;
287 if (assignment_.LiteralIsFalse(literal)) continue;
288
289 std::vector<Literal>* reason = trail_->GetEmptyVectorToStoreReason();
290 FillReasonForPath(start_node, reason);
291 if (extra_reason != kFalseLiteralIndex) {
292 reason->push_back(Literal(extra_reason));
293 }
294 const bool ok = trail_->EnqueueWithStoredReason(literal.Negated());
295 if (!ok) return false;
296 continue;
297 }
298 }
299
300 // If we have a cycle, we can propagate all the other nodes to point to
301 // themselves. Otherwise there is nothing else to do.
302 if (start_node != end_node) continue;
303 BooleanVariable variable_with_same_reason = kNoBooleanVariable;
304 for (int node = 0; node < num_nodes_; ++node) {
305 if (in_current_path_[node]) continue;
306 if (assignment_.LiteralIsTrue(self_arcs_[node])) continue;
307
308 // This shouldn't happen because ExactlyOnePerRowAndPerColumn() should
309 // have executed first and propagated self_arcs_[node] to false.
310 CHECK_EQ(next_[node], -1);
311
312 // We should have detected that above (miss_some_nodes == true). But we
313 // still need this for corner cases where the same literal is used for
314 // many arcs, and we just propagated it here.
315 if (assignment_.LiteralIsFalse(self_arcs_[node])) {
316 FillReasonForPath(start_node, trail_->MutableConflict());
317 trail_->MutableConflict()->push_back(self_arcs_[node]);
318 return false;
319 }
320
321 // Propagate.
322 const Literal literal(self_arcs_[node]);
323 if (variable_with_same_reason == kNoBooleanVariable) {
324 variable_with_same_reason = literal.Variable();
325 FillReasonForPath(start_node, trail_->GetEmptyVectorToStoreReason());
326 const bool ok = trail_->EnqueueWithStoredReason(literal);
327 if (!ok) return false;
328 } else {
329 trail_->EnqueueWithSameReasonAs(literal, variable_with_same_reason);
330 }
331 }
332 }
333 return true;
334}
335
337 std::vector<std::vector<Literal>> graph,
338 const std::vector<int>& distinguished_nodes, Model* model)
339 : graph_(std::move(graph)),
340 num_nodes_(graph_.size()),
341 trail_(model->GetOrCreate<Trail>()) {
342 node_is_distinguished_.resize(num_nodes_, false);
343 for (const int node : distinguished_nodes) {
344 node_is_distinguished_[node] = true;
345 }
346}
347
349 const int watcher_id = watcher->Register(this);
350
351 // Fill fixed_arcs_ with arcs that are initially fixed to true,
352 // assign arcs to watch indices.
353 for (int node1 = 0; node1 < num_nodes_; node1++) {
354 for (int node2 = 0; node2 < num_nodes_; node2++) {
355 const Literal l = graph_[node1][node2];
356 if (trail_->Assignment().LiteralIsFalse(l)) continue;
357 if (trail_->Assignment().LiteralIsTrue(l)) {
358 fixed_arcs_.emplace_back(node1, node2);
359 } else {
360 watcher->WatchLiteral(l, watcher_id, watch_index_to_arc_.size());
361 watch_index_to_arc_.emplace_back(node1, node2);
362 }
363 }
364 }
365 watcher->RegisterReversibleClass(watcher_id, this);
366}
367
369 if (level == level_ends_.size()) return;
370 if (level > level_ends_.size()) {
371 while (level > level_ends_.size()) {
372 level_ends_.push_back(fixed_arcs_.size());
373 }
374 } else {
375 // Backtrack.
376 fixed_arcs_.resize(level_ends_[level]);
377 level_ends_.resize(level);
378 }
379}
380
382 const std::vector<int>& watch_indices) {
383 for (const int w : watch_indices) {
384 const auto& arc = watch_index_to_arc_[w];
385 fixed_arcs_.push_back(arc);
386 }
387 return Propagate();
388}
389
390void CircuitCoveringPropagator::FillFixedPathInReason(
391 int start, int end, std::vector<Literal>* reason) {
392 reason->clear();
393 int current = start;
394 do {
395 DCHECK_NE(next_[current], -1);
396 DCHECK(trail_->Assignment().LiteralIsTrue(graph_[current][next_[current]]));
397 reason->push_back(graph_[current][next_[current]].Negated());
398 current = next_[current];
399 } while (current != end);
400}
401
403 // Gather next_ and prev_ from fixed arcs.
404 next_.assign(num_nodes_, -1);
405 prev_.assign(num_nodes_, -1);
406 for (const auto& arc : fixed_arcs_) {
407 // Two arcs go out of arc.first, forbidden.
408 if (next_[arc.first] != -1) {
409 *trail_->MutableConflict() = {
410 graph_[arc.first][next_[arc.first]].Negated(),
411 graph_[arc.first][arc.second].Negated()};
412 return false;
413 }
414 next_[arc.first] = arc.second;
415 // Two arcs come into arc.second, forbidden.
416 if (prev_[arc.second] != -1) {
417 *trail_->MutableConflict() = {
418 graph_[prev_[arc.second]][arc.second].Negated(),
419 graph_[arc.first][arc.second].Negated()};
420 return false;
421 }
422 prev_[arc.second] = arc.first;
423 }
424
425 // For every node, find partial path/circuit in which the node is.
426 // Use visited_ to visit each path/circuit only once.
427 visited_.assign(num_nodes_, false);
428 for (int node = 0; node < num_nodes_; node++) {
429 // Skip if already visited, isolated or loop.
430 if (visited_[node]) continue;
431 if (prev_[node] == -1 && next_[node] == -1) continue;
432 if (prev_[node] == node) continue;
433
434 // Find start of path/circuit.
435 int start = node;
436 for (int current = prev_[node]; current != -1 && current != node;
437 current = prev_[current]) {
438 start = current;
439 }
440
441 // Find distinguished node of path. Fail if there are several,
442 // fail if this is a non loop circuit and there are none.
443 int distinguished = node_is_distinguished_[start] ? start : -1;
444 int current = next_[start];
445 int end = start;
446 visited_[start] = true;
447 while (current != -1 && current != start) {
448 if (node_is_distinguished_[current]) {
449 if (distinguished != -1) {
450 FillFixedPathInReason(distinguished, current,
451 trail_->MutableConflict());
452 return false;
453 }
454 distinguished = current;
455 }
456 visited_[current] = true;
457 end = current;
458 current = next_[current];
459 }
460
461 // Circuit with no distinguished nodes, forbidden.
462 if (start == current && distinguished == -1) {
463 FillFixedPathInReason(start, start, trail_->MutableConflict());
464 return false;
465 }
466
467 // Path with no distinguished node: forbid to close it.
468 if (current == -1 && distinguished == -1 &&
469 !trail_->Assignment().LiteralIsFalse(graph_[end][start])) {
470 auto* reason = trail_->GetEmptyVectorToStoreReason();
471 FillFixedPathInReason(start, end, reason);
472 const bool ok =
473 trail_->EnqueueWithStoredReason(graph_[end][start].Negated());
474 if (!ok) return false;
475 }
476 }
477 return true;
478}
479
481 const std::vector<std::vector<Literal>>& graph) {
482 return [=](Model* model) {
483 const int n = graph.size();
484 std::vector<Literal> exactly_one_constraint;
485 exactly_one_constraint.reserve(n);
486 for (const bool transpose : {false, true}) {
487 for (int i = 0; i < n; ++i) {
488 exactly_one_constraint.clear();
489 for (int j = 0; j < n; ++j) {
490 exactly_one_constraint.push_back(transpose ? graph[j][i]
491 : graph[i][j]);
492 }
493 model->Add(ExactlyOneConstraint(exactly_one_constraint));
494 }
495 }
496 };
497}
498
499std::function<void(Model*)> SubcircuitConstraint(
500 int num_nodes, const std::vector<int>& tails, const std::vector<int>& heads,
501 const std::vector<Literal>& literals,
502 bool multiple_subcircuit_through_zero) {
503 return [=](Model* model) {
504 const int num_arcs = tails.size();
505 CHECK_GT(num_arcs, 0);
506 CHECK_EQ(heads.size(), num_arcs);
507 CHECK_EQ(literals.size(), num_arcs);
508
509 // If a node has no outgoing or no incoming arc, the model will be unsat
510 // as soon as we add the corresponding ExactlyOneConstraint().
511 auto sat_solver = model->GetOrCreate<SatSolver>();
512
513 std::vector<std::vector<Literal>> exactly_one_incoming(num_nodes);
514 std::vector<std::vector<Literal>> exactly_one_outgoing(num_nodes);
515 for (int arc = 0; arc < num_arcs; arc++) {
516 const int tail = tails[arc];
517 const int head = heads[arc];
518 exactly_one_outgoing[tail].push_back(literals[arc]);
519 exactly_one_incoming[head].push_back(literals[arc]);
520 }
521 for (int i = 0; i < exactly_one_incoming.size(); ++i) {
522 if (i == 0 && multiple_subcircuit_through_zero) continue;
523 model->Add(ExactlyOneConstraint(exactly_one_incoming[i]));
524 if (sat_solver->IsModelUnsat()) return;
525 }
526 for (int i = 0; i < exactly_one_outgoing.size(); ++i) {
527 if (i == 0 && multiple_subcircuit_through_zero) continue;
528 model->Add(ExactlyOneConstraint(exactly_one_outgoing[i]));
529 if (sat_solver->IsModelUnsat()) return;
530 }
531
533 options.multiple_subcircuit_through_zero = multiple_subcircuit_through_zero;
534 CircuitPropagator* constraint = new CircuitPropagator(
535 num_nodes, tails, heads, literals, options, model);
536 constraint->RegisterWith(model->GetOrCreate<GenericLiteralWatcher>());
537 model->TakeOwnership(constraint);
538 };
539}
540
541std::function<void(Model*)> CircuitCovering(
542 const std::vector<std::vector<Literal>>& graph,
543 const std::vector<int>& distinguished_nodes) {
544 return [=](Model* model) {
545 CircuitCoveringPropagator* constraint =
546 new CircuitCoveringPropagator(graph, distinguished_nodes, model);
547 constraint->RegisterWith(model->GetOrCreate<GenericLiteralWatcher>());
548 model->TakeOwnership(constraint);
549 };
550}
551
552} // namespace sat
553} // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:491
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:887
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
#define CHECK_GT(val1, val2)
Definition: base/logging.h:703
#define CHECK_NE(val1, val2)
Definition: base/logging.h:699
#define DCHECK(condition)
Definition: base/logging.h:885
#define VLOG(verboselevel)
Definition: base/logging.h:979
An Assignment is a variable -> domains mapping, used to report solutions to the user.
CircuitCoveringPropagator(std::vector< std::vector< Literal > > graph, const std::vector< int > &distinguished_nodes, Model *model)
Definition: circuit.cc:336
bool IncrementalPropagate(const std::vector< int > &watch_indices) final
Definition: circuit.cc:381
void RegisterWith(GenericLiteralWatcher *watcher)
Definition: circuit.cc:348
bool IncrementalPropagate(const std::vector< int > &watch_indices) final
Definition: circuit.cc:153
void RegisterWith(GenericLiteralWatcher *watcher)
Definition: circuit.cc:92
CircuitPropagator(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< Literal > &literals, Options options, Model *model)
Definition: circuit.cc:25
void RegisterReversibleClass(int id, ReversibleInterface *rev)
Definition: integer.cc:2035
void WatchLiteral(Literal l, int id, int watch_index=-1)
Definition: integer.h:1422
int Register(PropagatorInterface *propagator)
Definition: integer.cc:1996
LiteralIndex Index() const
Definition: sat_base.h:85
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
std::vector< Literal > * GetEmptyVectorToStoreReason(int trail_index) const
Definition: sat_base.h:321
void EnqueueWithSameReasonAs(Literal true_literal, BooleanVariable reference_var)
Definition: sat_base.h:273
const VariablesAssignment & Assignment() const
Definition: sat_base.h:381
ABSL_MUST_USE_RESULT bool EnqueueWithStoredReason(Literal true_literal)
Definition: sat_base.h:285
std::vector< Literal > * MutableConflict()
Definition: sat_base.h:362
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:151
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:148
GRBmodel * model
const Collection::value_type::second_type & FindWithDefault(const Collection &collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
Definition: map_util.h:29
const LiteralIndex kNoLiteralIndex(-1)
std::function< void(Model *)> SubcircuitConstraint(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< Literal > &literals, bool multiple_subcircuit_through_zero)
Definition: circuit.cc:499
std::function< void(Model *)> CircuitCovering(const std::vector< std::vector< Literal > > &graph, const std::vector< int > &distinguished_nodes)
Definition: circuit.cc:541
std::function< void(Model *)> ExactlyOnePerRowAndPerColumn(const std::vector< std::vector< Literal > > &graph)
Definition: circuit.cc:480
const LiteralIndex kFalseLiteralIndex(-3)
std::function< void(Model *)> ExactlyOneConstraint(const std::vector< Literal > &literals)
Definition: sat_solver.h:878
const BooleanVariable kNoBooleanVariable(-1)
Collection of objects used to extend the Constraint Solver library.
std::pair< int64_t, int64_t > Arc
Definition: search.cc:3383
STL namespace.
Literal literal
Definition: optimization.cc:85
int64_t tail
int64_t head