Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
encoding.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <stddef.h>
17
18#include <algorithm>
19#include <deque>
20#include <functional>
21#include <limits>
22#include <queue>
23#include <string>
24#include <utility>
25#include <vector>
26
27#include "absl/log/check.h"
28#include "absl/strings/str_cat.h"
29#include "absl/types/span.h"
31#include "ortools/sat/boolean_problem.pb.h"
34#include "ortools/sat/sat_parameters.pb.h"
37
38namespace operations_research {
39namespace sat {
40
42 EncodingNode node;
43 node.lb_ = 1;
44 node.ub_ = 1;
45 node.weight_lb_ = 0;
46 node.weight_ = weight;
47 return node;
48}
49
51 EncodingNode node;
52 node.lb_ = 0;
53 node.weight_lb_ = 0;
54 node.ub_ = 1;
55 node.weight_ = weight;
56 node.for_sorting_ = l.Variable();
57 node.literals_ = {l};
58 return node;
59}
60
62 std::function<Literal(int x)> create_lit,
63 Coefficient weight) {
64 EncodingNode node;
65 node.lb_ = lb;
66 node.ub_ = ub;
67 node.weight_lb_ = 0;
68 node.create_lit_ = std::move(create_lit);
69 node.weight_ = weight;
70
71 node.literals_.push_back(node.create_lit_(lb));
72
73 // TODO(user): Not ideal, we should probably just provide index in the
74 // original objective for sorting purpose.
75 node.for_sorting_ = node.literals_[0].Variable();
76 return node;
77}
78
80 SatSolver* solver) {
81 CHECK(literals_.empty()) << "Already initialized";
82 CHECK_GT(n, 0);
83 const BooleanVariable first_var_index(solver->NumVariables());
84 solver->SetNumVariables(solver->NumVariables() + n);
85 for (int i = 0; i < n; ++i) {
86 literals_.push_back(Literal(first_var_index + i, true));
87 if (i > 0) {
88 solver->AddBinaryClause(literal(i - 1), literal(i).Negated());
89 }
90 }
91 lb_ = a->lb_ + b->lb_;
92 ub_ = lb_ + n;
93 depth_ = 1 + std::max(a->depth_, b->depth_);
94 child_a_ = a;
95 child_b_ = b;
96 for_sorting_ = first_var_index;
97}
98
99void EncodingNode::InitializeAmoNode(absl::Span<EncodingNode* const> nodes,
100 SatSolver* solver) {
101 CHECK_GE(nodes.size(), 2);
102 CHECK(literals_.empty()) << "Already initialized";
103 const BooleanVariable var = solver->NewBooleanVariable();
104 const Literal new_literal(var, true);
105 literals_.push_back(new_literal);
106 child_a_ = nullptr;
107 child_b_ = nullptr;
108 lb_ = 0;
109 ub_ = 1;
110 depth_ = 0;
111 for_sorting_ = var;
112 std::vector<Literal> clause{new_literal.Negated()};
113 for (const EncodingNode* node : nodes) {
114 // node_lit => new_lit.
115 clause.push_back(node->literals_[0]);
116 solver->AddBinaryClause(node->literals_[0].Negated(), new_literal);
117 depth_ = std::max(node->depth_ + 1, depth_);
118 for_sorting_ = std::min(for_sorting_, node->for_sorting_);
119 }
120
121 // If new_literal is true then one of the lit must be true.
122 // Note that this is not needed for correctness though.
123 solver->AddProblemClause(clause);
124}
125
127 SatSolver* solver) {
128 CHECK(literals_.empty()) << "Already initialized";
129 const BooleanVariable first_var_index(solver->NumVariables());
130 solver->SetNumVariables(solver->NumVariables() + 1);
131 literals_.emplace_back(first_var_index, true);
132 child_a_ = a;
133 child_b_ = b;
134 ub_ = a->ub_ + b->ub_;
135 lb_ = a->lb_ + b->lb_;
136 depth_ = 1 + std::max(a->depth_, b->depth_);
137
138 // Merging the node of the same depth in order seems to help a bit.
139 for_sorting_ = std::min(a->for_sorting_, b->for_sorting_);
140}
141
143 EncodingNode* b) {
144 CHECK(literals_.empty()) << "Already initialized";
145 child_a_ = a;
146 child_b_ = b;
147 ub_ = a->ub_ + b->ub_;
148 weight_ = weight;
149 weight_lb_ = a->lb_ + b->lb_;
150 lb_ = weight_lb_ + 1;
151 depth_ = 1 + std::max(a->depth_, b->depth_);
152
153 // Merging the node of the same depth in order seems to help a bit.
154 for_sorting_ = std::min(a->for_sorting_, b->for_sorting_);
155}
156
158 if (current_ub() == ub_) return false;
159 if (create_lit_ != nullptr) {
160 literals_.emplace_back(create_lit_(current_ub()));
161 } else {
162 CHECK_NE(solver, nullptr);
163 literals_.emplace_back(BooleanVariable(solver->NumVariables()), true);
164 solver->SetNumVariables(solver->NumVariables() + 1);
165 }
166 if (literals_.size() > 1) {
167 solver->AddBinaryClause(literals_.back().Negated(),
168 literals_[literals_.size() - 2]);
169 }
170 return true;
171}
172
173Coefficient EncodingNode::Reduce(const SatSolver& solver) {
174 if (!literals_.empty()) {
175 int i = 0;
176 while (i < literals_.size() &&
177 solver.Assignment().LiteralIsTrue(literals_[i])) {
178 ++i;
179 ++lb_;
180 }
181 literals_.erase(literals_.begin(), literals_.begin() + i);
182 while (!literals_.empty() &&
183 solver.Assignment().LiteralIsFalse(literals_.back())) {
184 literals_.pop_back();
185 ub_ = lb_ + literals_.size();
186 }
187 }
188
189 if (weight_lb_ >= lb_) return Coefficient(0);
190 const Coefficient result = Coefficient(lb_ - weight_lb_) * weight_;
191 weight_lb_ = lb_;
192 return result;
193}
194
195void EncodingNode::ApplyWeightUpperBound(Coefficient gap, SatSolver* solver) {
196 CHECK_GT(weight_, 0);
197 const Coefficient num_allowed = (gap / weight_);
198 if (num_allowed > std::numeric_limits<int>::max() / 2) return;
199 const int new_size =
200 std::max(0, (weight_lb_ - lb_) + static_cast<int>(num_allowed.value()));
201 if (size() <= new_size) return;
202 for (int i = new_size; i < size(); ++i) {
203 if (!solver->AddUnitClause(literal(i).Negated())) return;
204 }
205 literals_.resize(new_size);
206 ub_ = lb_ + new_size;
207}
208
210 if (size() > 1) {
211 for (int i = 1; i < size(); ++i) {
212 if (!solver->AddUnitClause(literal(i).Negated())) return;
213 }
214 literals_.resize(1);
215 ub_ = lb_ + 1;
216 return;
217 }
218
219 if (current_ub() == ub_) return;
220
221 // TODO(user): Avoid creating a Boolean just to fix it!
222 IncreaseNodeSize(this, solver);
223 CHECK_EQ(size(), 2);
224 if (!solver->AddUnitClause(literal(1).Negated())) return;
225 literals_.resize(1);
226 ub_ = lb_ + 1;
227}
228
230 DCHECK(!HasNoWeight());
231 const int index = weight_lb_ - lb_;
232 return index < literals_.size() && literals_[index].Negated() == other;
233}
234
236 CHECK(!HasNoWeight());
237 const int index = weight_lb_ - lb_;
238 CHECK_GE(index, 0) << "Not reduced?";
239 while (index >= literals_.size()) {
240 IncreaseNodeSize(this, solver);
241 }
242 return literals_[index].Negated();
243}
244
246 CHECK_LT(weight_lb_ - lb_, literals_.size());
247 weight_lb_++;
248}
249
251 return weight_ == 0 || weight_lb_ >= ub_;
252}
253
255 const VariablesAssignment& assignment) const {
256 std::string result;
257 absl::StrAppend(&result, "depth:", depth_);
258 absl::StrAppend(&result, " [", lb_, ",", lb_ + literals_.size(), "]");
259 absl::StrAppend(&result, " ub:", ub_);
260 absl::StrAppend(&result, " weight:", weight_.value());
261 absl::StrAppend(&result, " weight_lb:", weight_lb_);
262 absl::StrAppend(&result, " values:");
263 const size_t limit = 20;
264 int value = 0;
265 for (int i = 0; i < std::min(literals_.size(), limit); ++i) {
266 char c = '?';
267 if (assignment.LiteralIsTrue(literals_[i])) {
268 c = '1';
269 value = i + 1;
270 } else if (assignment.LiteralIsFalse(literals_[i])) {
271 c = '0';
272 }
273 result += c;
274 }
275 absl::StrAppend(&result, " val:", lb_ + value);
276 return result;
277}
278
280 EncodingNode n;
281 n.InitializeLazyNode(a, b, solver);
282 solver->AddBinaryClause(a->literal(0).Negated(), n.literal(0));
283 solver->AddBinaryClause(b->literal(0).Negated(), n.literal(0));
284 solver->AddTernaryClause(n.literal(0).Negated(), a->literal(0),
285 b->literal(0));
286 return n;
287}
288
290 if (!node->IncreaseCurrentUB(solver)) return;
291 std::vector<EncodingNode*> to_process;
292 to_process.push_back(node);
293
294 // Only one side of the constraint is mandatory (the one propagating the ones
295 // to the top of the encoding tree), and it seems more efficient not to encode
296 // the other side.
297 //
298 // TODO(user): Experiment more.
299 const bool complete_encoding = false;
300
301 while (!to_process.empty()) {
302 EncodingNode* n = to_process.back();
303 EncodingNode* a = n->child_a();
304 EncodingNode* b = n->child_b();
305 to_process.pop_back();
306
307 // Integer leaf node.
308 if (a == nullptr) continue;
309 CHECK_NE(solver, nullptr);
310
311 // Note that since we were able to increase its size, n must have children.
312 // n->GreaterThan(target) is the new literal of n.
313 CHECK(a != nullptr);
314 CHECK(b != nullptr);
315 const int target = n->current_ub() - 1;
316
317 // Add a literal to a if needed.
318 // That is, now that the node n can go up to it new current_ub, if we need
319 // to increase the current_ub of a.
320 if (a->current_ub() != a->ub()) {
321 CHECK_GE(a->current_ub() - 1 + b->lb(), target - 1);
322 if (a->current_ub() - 1 + b->lb() < target) {
323 CHECK(a->IncreaseCurrentUB(solver));
324 to_process.push_back(a);
325 }
326 }
327
328 // Add a literal to b if needed.
329 if (b->current_ub() != b->ub()) {
330 CHECK_GE(b->current_ub() - 1 + a->lb(), target - 1);
331 if (b->current_ub() - 1 + a->lb() < target) {
332 CHECK(b->IncreaseCurrentUB(solver));
333 to_process.push_back(b);
334 }
335 }
336
337 // Wire the new literal of n correctly with its two children.
338 for (int ia = a->lb(); ia < a->current_ub(); ++ia) {
339 const int ib = target - ia;
340 if (complete_encoding && ib >= b->lb() && ib < b->current_ub()) {
341 // if x <= ia and y <= ib then x + y <= ia + ib.
342 solver->AddTernaryClause(n->GreaterThan(target).Negated(),
343 a->GreaterThan(ia), b->GreaterThan(ib));
344 }
345 if (complete_encoding && ib == b->ub()) {
346 solver->AddBinaryClause(n->GreaterThan(target).Negated(),
347 a->GreaterThan(ia));
348 }
349
350 if (ib - 1 == b->lb() - 1) {
351 solver->AddBinaryClause(n->GreaterThan(target),
352 a->GreaterThan(ia).Negated());
353 }
354 if ((ib - 1) >= b->lb() && (ib - 1) < b->current_ub()) {
355 // if x > ia and y > ib - 1 then x + y > ia + ib.
356 solver->AddTernaryClause(n->GreaterThan(target),
357 a->GreaterThan(ia).Negated(),
358 b->GreaterThan(ib - 1).Negated());
359 }
360 }
361
362 // Case ia = a->lb() - 1; a->GreaterThan(ia) always true.
363 {
364 const int ib = target - (a->lb() - 1);
365 if ((ib - 1) == b->lb() - 1) {
366 if (!solver->AddUnitClause(n->GreaterThan(target))) return;
367 }
368 if ((ib - 1) >= b->lb() && (ib - 1) < b->current_ub()) {
369 solver->AddBinaryClause(n->GreaterThan(target),
370 b->GreaterThan(ib - 1).Negated());
371 }
372 }
373
374 // case ia == a->ub; a->GreaterThan(ia) always false.
375 {
376 const int ib = target - a->ub();
377 if (complete_encoding && ib >= b->lb() && ib < b->current_ub()) {
378 solver->AddBinaryClause(n->GreaterThan(target).Negated(),
379 b->GreaterThan(ib));
380 }
381 if (ib == b->ub()) {
382 if (!solver->AddUnitClause(n->GreaterThan(target).Negated())) return;
383 }
384 }
385 }
386}
387
389 EncodingNode* b, SatSolver* solver) {
390 EncodingNode n;
391 const int size =
392 std::min(Coefficient(a->size() + b->size()), upper_bound).value();
393 n.InitializeFullNode(size, a, b, solver);
394 for (int ia = 0; ia < a->size(); ++ia) {
395 if (ia + b->size() < size) {
396 solver->AddBinaryClause(n.literal(ia + b->size()).Negated(),
397 a->literal(ia));
398 }
399 if (ia < size) {
400 solver->AddBinaryClause(n.literal(ia), a->literal(ia).Negated());
401 } else {
402 // Fix the variable to false because of the given upper_bound.
403 if (!solver->AddUnitClause(a->literal(ia).Negated())) return n;
404 }
405 }
406 for (int ib = 0; ib < b->size(); ++ib) {
407 if (ib + a->size() < size) {
408 solver->AddBinaryClause(n.literal(ib + a->size()).Negated(),
409 b->literal(ib));
410 }
411 if (ib < size) {
412 solver->AddBinaryClause(n.literal(ib), b->literal(ib).Negated());
413 } else {
414 // Fix the variable to false because of the given upper_bound.
415 if (!solver->AddUnitClause(b->literal(ib).Negated())) return n;
416 }
417 }
418 for (int ia = 0; ia < a->size(); ++ia) {
419 for (int ib = 0; ib < b->size(); ++ib) {
420 if (ia + ib < size) {
421 // if x <= ia and y <= ib, then x + y <= ia + ib.
422 solver->AddTernaryClause(n.literal(ia + ib).Negated(), a->literal(ia),
423 b->literal(ib));
424 }
425 if (ia + ib + 1 < size) {
426 // if x > ia and y > ib, then x + y > ia + ib + 1.
427 solver->AddTernaryClause(n.literal(ia + ib + 1),
428 a->literal(ia).Negated(),
429 b->literal(ib).Negated());
430 } else {
431 solver->AddBinaryClause(a->literal(ia).Negated(),
432 b->literal(ib).Negated());
433 }
434 }
435 }
436 return n;
437}
438
440 const std::vector<EncodingNode*>& nodes,
441 SatSolver* solver,
442 std::deque<EncodingNode>* repository) {
443 std::deque<EncodingNode*> dq(nodes.begin(), nodes.end());
444 while (dq.size() > 1) {
445 EncodingNode* a = dq.front();
446 dq.pop_front();
447 EncodingNode* b = dq.front();
448 dq.pop_front();
449 repository->push_back(FullMerge(upper_bound, a, b, solver));
450 dq.push_back(&repository->back());
451 }
452 return dq.front();
453}
454
455namespace {
456struct SortEncodingNodePointers {
457 bool operator()(EncodingNode* a, EncodingNode* b) const { return *a < *b; }
458};
459} // namespace
460
462 Coefficient weight, const std::vector<EncodingNode*>& nodes,
463 SatSolver* solver, std::deque<EncodingNode>* repository) {
464 std::priority_queue<EncodingNode*, std::vector<EncodingNode*>,
465 SortEncodingNodePointers>
466 pq(nodes.begin(), nodes.end());
467 while (pq.size() > 2) {
468 EncodingNode* a = pq.top();
469 pq.pop();
470 EncodingNode* b = pq.top();
471 pq.pop();
472 repository->push_back(LazyMerge(a, b, solver));
473 pq.push(&repository->back());
474 }
475
476 CHECK_EQ(pq.size(), 2);
477 EncodingNode* a = pq.top();
478 pq.pop();
479 EncodingNode* b = pq.top();
480 pq.pop();
481
482 repository->push_back(EncodingNode());
483 EncodingNode* n = &repository->back();
485 solver->AddBinaryClause(a->literal(0), b->literal(0));
486 return n;
487}
488
489namespace {
490
491bool EncodingNodeByWeight(const EncodingNode* a, const EncodingNode* b) {
492 return a->weight() < b->weight();
493}
494
495bool EncodingNodeByDepth(const EncodingNode* a, const EncodingNode* b) {
496 return a->depth() < b->depth();
497}
498
499} // namespace
500
501void ReduceNodes(Coefficient upper_bound, Coefficient* lower_bound,
502 std::vector<EncodingNode*>* nodes, SatSolver* solver) {
503 // Remove the left-most variables fixed to one from each node.
504 // Also update the lower_bound. Note that Reduce() needs the solver to be
505 // at the root node in order to work.
506 solver->Backtrack(0);
507 for (EncodingNode* n : *nodes) {
508 *lower_bound += n->Reduce(*solver);
509 }
510
511 // Fix the nodes right-most variables that are above the gap.
512 // If we closed the problem, we abort and return and empty vector.
514 const Coefficient gap = upper_bound - *lower_bound;
515 if (gap < 0) {
516 nodes->clear();
517 return;
518 }
519 for (EncodingNode* n : *nodes) {
520 n->ApplyWeightUpperBound(gap, solver);
521 }
522 }
523
524 // Remove the empty nodes.
525 nodes->erase(std::remove_if(nodes->begin(), nodes->end(),
526 [](EncodingNode* a) { return a->HasNoWeight(); }),
527 nodes->end());
528
529 // Sort the nodes.
530 switch (solver->parameters().max_sat_assumption_order()) {
531 case SatParameters::DEFAULT_ASSUMPTION_ORDER:
532 break;
533 case SatParameters::ORDER_ASSUMPTION_BY_DEPTH:
534 std::sort(nodes->begin(), nodes->end(), EncodingNodeByDepth);
535 break;
536 case SatParameters::ORDER_ASSUMPTION_BY_WEIGHT:
537 std::sort(nodes->begin(), nodes->end(), EncodingNodeByWeight);
538 break;
539 }
540 if (solver->parameters().max_sat_reverse_assumption_order()) {
541 // TODO(user): with DEFAULT_ASSUMPTION_ORDER, this will lead to a somewhat
542 // weird behavior, since we will reverse the nodes at each iteration...
543 std::reverse(nodes->begin(), nodes->end());
544 }
545}
546
547std::vector<Literal> ExtractAssumptions(Coefficient stratified_lower_bound,
548 const std::vector<EncodingNode*>& nodes,
549 SatSolver* solver) {
550 // Extract the assumptions from the nodes.
551 std::vector<Literal> assumptions;
552 for (EncodingNode* n : nodes) {
553 if (n->weight() >= stratified_lower_bound) {
554 assumptions.push_back(n->GetAssumption(solver));
555 }
556 }
557 return assumptions;
558}
559
560Coefficient ComputeCoreMinWeight(const std::vector<EncodingNode*>& nodes,
561 const std::vector<Literal>& core) {
562 Coefficient min_weight = kCoefficientMax;
563 int index = 0;
564 for (int i = 0; i < core.size(); ++i) {
565 for (; index < nodes.size() && !nodes[index]->AssumptionIs(core[i]);
566 ++index) {
567 }
568 CHECK_LT(index, nodes.size());
569 min_weight = std::min(min_weight, nodes[index]->weight());
570 }
571 return min_weight;
572}
573
574Coefficient MaxNodeWeightSmallerThan(const std::vector<EncodingNode*>& nodes,
575 Coefficient upper_bound) {
576 Coefficient result(0);
577 for (EncodingNode* n : nodes) {
578 CHECK_GT(n->weight(), 0);
579 if (n->weight() < upper_bound) {
580 result = std::max(result, n->weight());
581 }
582 }
583 return result;
584}
585
586bool ObjectiveEncoder::ProcessCore(absl::Span<const Literal> core,
587 Coefficient min_weight, Coefficient gap,
588 std::string* info) {
589 // Backtrack to be able to add new constraints.
590 if (!sat_solver_->ResetToLevelZero()) return false;
591 if (core.size() == 1) {
592 return sat_solver_->AddUnitClause(core[0].Negated());
593 }
594
595 // Remove from nodes the EncodingNode in the core and put them in to_merge.
596 std::vector<EncodingNode*> to_merge;
597 {
598 int index = 0;
599 std::vector<EncodingNode*> new_nodes;
600 for (int i = 0; i < core.size(); ++i) {
601 // Since the nodes appear in order in the core, we can find the
602 // relevant "objective" variable efficiently with a simple linear scan
603 // in the nodes vector (done with index).
604 for (; !nodes_[index]->AssumptionIs(core[i]); ++index) {
605 CHECK_LT(index, nodes_.size());
606 new_nodes.push_back(nodes_[index]);
607 }
608 CHECK_LT(index, nodes_.size());
609 EncodingNode* node = nodes_[index];
610
611 // TODO(user): propagate proper ub first.
612 if (alternative_encoding_ && node->ub() > node->lb() + 1) {
613 // We can distinguish the first literal of the node.
614 // By not counting its weight in node, and creating a new node for it.
615 node->IncreaseWeightLb();
616 new_nodes.push_back(node);
617
618 // Now keep processing with this new node instead.
619 repository_.push_back(
620 EncodingNode::LiteralNode(core[i].Negated(), node->weight()));
621 repository_.back().set_depth(node->depth());
622 node = &repository_.back();
623 }
624
625 to_merge.push_back(node);
626
627 // Special case if the weight > min_weight. we keep it, but reduce its
628 // cost. This is the same "trick" as in WPM1 used to deal with weight.
629 // We basically split a clause with a larger weight in two identical
630 // clauses, one with weight min_weight that will be merged and one with
631 // the remaining weight.
632 if (node->weight() > min_weight) {
633 node->set_weight(node->weight() - min_weight);
634 new_nodes.push_back(node);
635 }
636 ++index;
637 }
638 for (; index < nodes_.size(); ++index) {
639 new_nodes.push_back(nodes_[index]);
640 }
641 nodes_ = new_nodes;
642 }
643
644 // Are the literal in amo relationship?
645 // - If min_weight is large enough, we can infer that.
646 // - If the size is small we can infer this via propagation.
647 bool in_exactly_one = (2 * min_weight) > gap;
648
649 // Amongst the node to merge, if many are boolean nodes in an "at most one"
650 // relationship, it is super advantageous to exploit it during merging as we
651 // can regroup all nodes from an at most one in a single new node with a depth
652 // of 1.
653 if (!in_exactly_one) {
654 // Collect "boolean nodes".
655 std::vector<Literal> bool_nodes;
656 absl::flat_hash_map<LiteralIndex, int> node_indices;
657 for (int i = 0; i < to_merge.size(); ++i) {
658 const EncodingNode& node = *to_merge[i];
659
660 if (node.size() != 1) continue;
661 if (node.ub() != node.lb() + 1) continue;
662 if (node.weight_lb() != node.lb()) continue;
663 if (node_indices.contains(node.literal(0).Index())) continue;
664 node_indices[node.literal(0).Index()] = i;
665 bool_nodes.push_back(node.literal(0));
666 }
667
668 // For "small" core, with O(n) full propagation, we can discover possible
669 // at most ones. This is a bit costly but can significantly reduce the
670 // number of Booleans needed and has a good positive impact.
671 std::vector<int> buffer;
672 std::vector<absl::Span<const Literal>> decomposition;
673 if (params_.core_minimization_level() > 1 && bool_nodes.size() < 300 &&
674 bool_nodes.size() > 1) {
675 const auto& assignment = sat_solver_->Assignment();
676 const int size = bool_nodes.size();
677 std::vector<std::vector<int>> graph(size);
678 for (int i = 0; i < size; ++i) {
679 if (!sat_solver_->ResetToLevelZero()) return false;
680 if (!sat_solver_->EnqueueDecisionIfNotConflicting(bool_nodes[i])) {
681 // TODO(user): this node is closed and can be removed from the core.
682 continue;
683 }
684 for (int j = 0; j < size; ++j) {
685 if (i == j) continue;
686 if (assignment.LiteralIsFalse(bool_nodes[j])) {
687 graph[i].push_back(j);
688
689 // Unit propagation is not always symmetric.
690 graph[j].push_back(i);
691 }
692
693 // TODO(user): If assignment.LiteralIsTrue(bool_nodes[j]) We can
694 // minimize the core here by removing bool_nodes[i] from it. Note
695 // however that since we already minimized the core, this is
696 // unlikely to happen.
697 }
698 }
699 if (!sat_solver_->ResetToLevelZero()) return false;
700
701 for (std::vector<int>& adj : graph) {
703 }
704 const std::vector<absl::Span<int>> index_decompo =
705 AtMostOneDecomposition(graph, *random_, &buffer);
706
707 // Convert.
708 std::vector<Literal> new_order;
709 for (const int i : buffer) new_order.push_back(bool_nodes[i]);
710 bool_nodes = new_order;
711 for (const auto span : index_decompo) {
712 if (span.size() == 1) continue;
713 decomposition.push_back(absl::MakeSpan(
714 bool_nodes.data() + (span.data() - buffer.data()), span.size()));
715 }
716 } else {
717 decomposition = implications_->HeuristicAmoPartition(&bool_nodes);
718 }
719
720 // Same case as above, all the nodes in the core are in a exactly_one.
721 if (decomposition.size() == 1 && decomposition[0].size() == core.size()) {
722 in_exactly_one = true;
723 }
724
725 int num_in_decompo = 0;
726 if (!in_exactly_one) {
727 for (const auto amo : decomposition) {
728 num_in_decompo += amo.size();
729
730 // Extract Amo nodes and set them to nullptr in to_merge.
731 std::vector<EncodingNode*> amo_nodes;
732 for (const Literal l : amo) {
733 const int index = node_indices.at(l.Index());
734 amo_nodes.push_back(to_merge[index]);
735 to_merge[index] = nullptr;
736 }
737
738 // Create the new node with proper constraints and weight.
739 repository_.push_back(EncodingNode());
740 EncodingNode* n = &repository_.back();
741 n->InitializeAmoNode(amo_nodes, sat_solver_);
742 n->set_weight(min_weight);
743 to_merge.push_back(n);
744 }
745 if (num_in_decompo > 0) {
746 absl::StrAppend(info, " amo:", decomposition.size(),
747 " lit:", num_in_decompo);
748 }
749
750 // Clean-up to_merge.
751 int new_size = 0;
752 for (EncodingNode* node : to_merge) {
753 if (node == nullptr) continue;
754 to_merge[new_size++] = node;
755 }
756 to_merge.resize(new_size);
757 }
758 }
759
760 // If all the literal of the core are in at_most_one, the core is actually an
761 // exactly_one. We just subtracted the min_cost above. We just have to enqueue
762 // a constant node with min_weight for the rest of the code to work.
763 if (in_exactly_one) {
764 // Tricky: We need to enforce an upper bound of 1 on the nodes.
765 for (EncodingNode* node : to_merge) {
766 node->TransformToBoolean(sat_solver_);
767 }
768
769 absl::StrAppend(info, " exo");
770 repository_.push_back(EncodingNode::ConstantNode(min_weight));
771 nodes_.push_back(&repository_.back());
772
773 // The negation of the literal in the core are in exactly one.
774 // TODO(user): If we infered the exactly one from the binary implication
775 // graph, there is no need to add the amo since it is already there.
776 std::vector<LiteralWithCoeff> cst;
777 cst.reserve(core.size());
778 for (const Literal l : core) {
779 cst.emplace_back(l.Negated(), Coefficient(1));
780 }
781 sat_solver_->AddLinearConstraint(
782 /*use_lower_bound=*/true, Coefficient(1),
783 /*use_upper_bound=*/true, Coefficient(1), &cst);
784 return !sat_solver_->ModelIsUnsat();
785 }
786
788 min_weight, to_merge, sat_solver_, &repository_));
789 absl::StrAppend(info, " d:", nodes_.back()->depth());
790 return !sat_solver_->ModelIsUnsat();
791}
792
793} // namespace sat
794} // namespace operations_research
IntegerValue size
std::vector< absl::Span< const Literal > > HeuristicAmoPartition(std::vector< Literal > *literals)
Definition clause.cc:1983
void InitializeLazyCoreNode(Coefficient weight, EncodingNode *a, EncodingNode *b)
Definition encoding.cc:142
Literal GreaterThan(int i) const
Definition encoding.h:97
bool AssumptionIs(Literal other) const
GetAssumption() might need to create new literals.
Definition encoding.cc:229
void InitializeFullNode(int n, EncodingNode *a, EncodingNode *b, SatSolver *solver)
Definition encoding.cc:79
Coefficient Reduce(const SatSolver &solver)
Definition encoding.cc:173
void InitializeLazyNode(EncodingNode *a, EncodingNode *b, SatSolver *solver)
Definition encoding.cc:126
int size() const
Accessors to size() and literals in [lb, current_ub).
Definition encoding.h:100
std::string DebugString(const VariablesAssignment &assignment) const
We use the solver to display the current values of the literals.
Definition encoding.cc:254
static EncodingNode GenericNode(int lb, int ub, std::function< Literal(int x)> create_lit, Coefficient weight)
Definition encoding.cc:61
static EncodingNode ConstantNode(Coefficient weight)
Definition encoding.cc:41
void ApplyWeightUpperBound(Coefficient gap, SatSolver *solver)
Definition encoding.cc:195
bool IncreaseCurrentUB(SatSolver *solver)
Definition encoding.cc:157
static EncodingNode LiteralNode(Literal l, Coefficient weight)
Definition encoding.cc:50
void TransformToBoolean(SatSolver *solver)
Indicate that the node cannot grow further than its current assumption.
Definition encoding.cc:209
EncodingNode * child_b() const
Definition encoding.h:154
EncodingNode * child_a() const
Definition encoding.h:153
void InitializeAmoNode(absl::Span< EncodingNode *const > nodes, SatSolver *solver)
Definition encoding.cc:99
Literal GetAssumption(SatSolver *solver)
Definition encoding.cc:235
LiteralIndex Index() const
Definition sat_base.h:91
BooleanVariable Variable() const
Definition sat_base.h:87
bool ProcessCore(absl::Span< const Literal > core, Coefficient min_weight, Coefficient gap, std::string *info)
Definition encoding.cc:586
bool AddLinearConstraint(bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound, Coefficient upper_bound, std::vector< LiteralWithCoeff > *cst)
bool AddProblemClause(absl::Span< const Literal > literals, bool is_safe=true)
BooleanVariable NewBooleanVariable()
Definition sat_solver.h:94
bool AddBinaryClause(Literal a, Literal b)
Same as AddProblemClause() below, but for small clauses.
void Backtrack(int target_level)
const SatParameters & parameters() const
bool EnqueueDecisionIfNotConflicting(Literal true_literal)
ABSL_MUST_USE_RESULT bool AddUnitClause(Literal true_literal)
bool AddTernaryClause(Literal a, Literal b, Literal c)
const VariablesAssignment & Assignment() const
Definition sat_solver.h:388
ABSL_MUST_USE_RESULT bool ResetToLevelZero()
void SetNumVariables(int num_variables)
Definition sat_solver.cc:85
bool LiteralIsFalse(Literal literal) const
Definition sat_base.h:185
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:188
int64_t a
Definition table.cc:44
GraphType graph
int64_t value
IntVar * var
double upper_bound
double lower_bound
int literal
int index
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
EncodingNode * MergeAllNodesWithDeque(Coefficient upper_bound, const std::vector< EncodingNode * > &nodes, SatSolver *solver, std::deque< EncodingNode > *repository)
Definition encoding.cc:439
std::vector< Literal > ExtractAssumptions(Coefficient stratified_lower_bound, const std::vector< EncodingNode * > &nodes, SatSolver *solver)
Definition encoding.cc:547
void ReduceNodes(Coefficient upper_bound, Coefficient *lower_bound, std::vector< EncodingNode * > *nodes, SatSolver *solver)
Definition encoding.cc:501
EncodingNode LazyMerge(EncodingNode *a, EncodingNode *b, SatSolver *solver)
Definition encoding.cc:279
EncodingNode FullMerge(Coefficient upper_bound, EncodingNode *a, EncodingNode *b, SatSolver *solver)
Definition encoding.cc:388
EncodingNode * LazyMergeAllNodeWithPQAndIncreaseLb(Coefficient weight, const std::vector< EncodingNode * > &nodes, SatSolver *solver, std::deque< EncodingNode > *repository)
Definition encoding.cc:461
Coefficient ComputeCoreMinWeight(const std::vector< EncodingNode * > &nodes, const std::vector< Literal > &core)
Definition encoding.cc:560
void IncreaseNodeSize(EncodingNode *node, SatSolver *solver)
Definition encoding.cc:289
std::vector< absl::Span< int > > AtMostOneDecomposition(const std::vector< std::vector< int > > &graph, absl::BitGenRef random, std::vector< int > *buffer)
Definition util.cc:998
Coefficient MaxNodeWeightSmallerThan(const std::vector< EncodingNode * > &nodes, Coefficient upper_bound)
Definition encoding.cc:574
const Coefficient kCoefficientMax(std::numeric_limits< Coefficient::ValueType >::max())
In SWIG mode, we don't want anything besides these top-level includes.
int64_t weight
Definition pack.cc:510
const Variable x
Definition qp_tests.cc:127
int nodes