Skip to content

Commit 3c5c99c

Browse files
committed
[CP-SAT] speed up processing of at most ones; add named subsolver parameters for lns and shared_tree workers
1 parent caef642 commit 3c5c99c

12 files changed

+202
-151
lines changed

ortools/sat/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ cc_library(
556556
"//ortools/base:timer",
557557
"//ortools/graph:strongly_connected_components",
558558
"//ortools/graph:topologicalsorter",
559+
"//ortools/port:proto_utils",
559560
"//ortools/util:affine_relation",
560561
"//ortools/util:bitset",
561562
"//ortools/util:logging",
@@ -591,6 +592,7 @@ cc_library(
591592
":cp_model_utils",
592593
"//ortools/base",
593594
"//ortools/base:types",
595+
"//ortools/port:proto_utils",
594596
"//ortools/util:sorted_interval_list",
595597
"@com_google_absl//absl/log:check",
596598
],
@@ -1615,6 +1617,7 @@ cc_library(
16151617
"//ortools/linear_solver:linear_solver_cc_proto",
16161618
"//ortools/lp_data",
16171619
"//ortools/lp_data:base",
1620+
"//ortools/port:proto_utils",
16181621
"//ortools/util:fp_utils",
16191622
"//ortools/util:logging",
16201623
"//ortools/util:saturated_arithmetic",

ortools/sat/clause.cc

Lines changed: 99 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -592,9 +592,9 @@ bool BinaryImplicationGraph::AddAtMostOne(
592592
// at_most_one_buffer_. It will be cleaned up and added by
593593
// CleanUpAndAddAtMostOnes().
594594
const int base_index = at_most_one_buffer_.size();
595+
at_most_one_buffer_.push_back(Literal(LiteralIndex(at_most_one.size())));
595596
at_most_one_buffer_.insert(at_most_one_buffer_.end(), at_most_one.begin(),
596597
at_most_one.end());
597-
at_most_one_buffer_.push_back(Literal(kNoLiteralIndex));
598598

599599
is_dag_ = false;
600600
return CleanUpAndAddAtMostOnes(base_index);
@@ -624,27 +624,27 @@ bool BinaryImplicationGraph::CleanUpAndAddAtMostOnes(int base_index) {
624624
const VariablesAssignment& assignment = trail_->Assignment();
625625
int local_end = base_index;
626626
const int buffer_size = at_most_one_buffer_.size();
627-
for (int i = base_index; i < buffer_size; ++i) {
628-
if (at_most_one_buffer_[i].Index() == kNoLiteralIndex) continue;
629-
627+
for (int i = base_index; i < buffer_size;) {
630628
// Process a new at most one.
631-
// It will be copied into buffer[local_start, local_end].
629+
// It will be copied into buffer[local_start + 1, local_end].
630+
// With its size at buffer[local_start].
632631
const int local_start = local_end;
633632

634633
// Update the iterator now. Even if the current at_most_one is reduced away,
635634
// local_start will still point to the next one, or to the end of the
636635
// buffer.
637636
if (i == at_most_one_iterator_) {
638637
at_most_one_iterator_ = local_start;
639-
DCHECK(at_most_one_iterator_ == 0 ||
640-
at_most_one_buffer_[at_most_one_iterator_ - 1].Index() ==
641-
kNoLiteralIndex);
642638
}
643639

640+
// We have an at_most_one starting at i, and we increment i to the next one.
641+
const absl::Span<const Literal> initial_amo = AtMostOne(i);
642+
i += initial_amo.size() + 1;
643+
644+
// Reserve space for size.
645+
local_end++;
644646
bool set_all_left_to_false = false;
645-
for (;; ++i) {
646-
const Literal l = at_most_one_buffer_[i];
647-
if (l.Index() == kNoLiteralIndex) break;
647+
for (const Literal l : initial_amo) {
648648
if (assignment.LiteralIsFalse(l)) continue;
649649
if (is_removed_[l]) continue;
650650
if (!set_all_left_to_false && assignment.LiteralIsTrue(l)) {
@@ -653,18 +653,22 @@ bool BinaryImplicationGraph::CleanUpAndAddAtMostOnes(int base_index) {
653653
}
654654
at_most_one_buffer_[local_end++] = RepresentativeOf(l);
655655
}
656+
at_most_one_buffer_[local_start] =
657+
Literal(LiteralIndex(local_end - local_start - 1));
656658

657659
// Deal with duplicates.
658660
// Any duplicate in an "at most one" must be false.
659661
bool some_duplicates = false;
660662
if (!set_all_left_to_false) {
661-
int new_local_end = local_start;
662-
std::sort(&at_most_one_buffer_[local_start],
663-
&at_most_one_buffer_[local_end]);
663+
// Sort the copied amo.
664+
// We only want to expose a const AtMostOne() so we sort directly here.
665+
Literal* pt = &at_most_one_buffer_[local_start + 1];
666+
std::sort(pt, pt + AtMostOne(local_start).size());
667+
664668
LiteralIndex previous = kNoLiteralIndex;
665669
bool remove_previous = false;
666-
for (int j = local_start; j < local_end; ++j) {
667-
const Literal l = at_most_one_buffer_[j];
670+
int new_local_end = local_start + 1;
671+
for (const Literal l : AtMostOne(local_start)) {
668672
if (l.Index() == previous) {
669673
if (assignment.LiteralIsTrue(l)) return false;
670674
if (!assignment.LiteralIsFalse(l)) {
@@ -685,40 +689,47 @@ bool BinaryImplicationGraph::CleanUpAndAddAtMostOnes(int base_index) {
685689
at_most_one_buffer_[new_local_end++] = l;
686690
}
687691
if (remove_previous) --new_local_end;
692+
693+
// Update local end and the amo size.
688694
local_end = new_local_end;
695+
at_most_one_buffer_[local_start] =
696+
Literal(LiteralIndex(local_end - local_start - 1));
689697
}
690698

691699
// If there was some duplicates, we need to rescan to see if a literal
692700
// didn't become true because its negation was appearing twice!
693701
if (some_duplicates) {
694-
int new_local_end = local_start;
695-
for (int j = local_start; j < local_end; ++j) {
696-
const Literal l = at_most_one_buffer_[j];
702+
int new_local_end = local_start + 1;
703+
for (const Literal l : AtMostOne(local_start)) {
697704
if (assignment.LiteralIsFalse(l)) continue;
698705
if (!set_all_left_to_false && assignment.LiteralIsTrue(l)) {
699706
set_all_left_to_false = true;
700707
continue;
701708
}
702709
at_most_one_buffer_[new_local_end++] = l;
703710
}
711+
712+
// Update local end and the amo size.
704713
local_end = new_local_end;
714+
at_most_one_buffer_[local_start] =
715+
Literal(LiteralIndex(local_end - local_start - 1));
705716
}
706717

707718
// Deal with all false.
708719
if (set_all_left_to_false) {
709-
for (int j = local_start; j < local_end; ++j) {
710-
const Literal l = at_most_one_buffer_[j];
720+
for (const Literal l : AtMostOne(local_start)) {
711721
if (assignment.LiteralIsFalse(l)) continue;
712722
if (assignment.LiteralIsTrue(l)) return false;
713723
if (!FixLiteral(l.Negated())) return false;
714724
}
725+
726+
// Erase this at_most_one.
715727
local_end = local_start;
716728
continue;
717729
}
718730

719731
// Create a Span<> to simplify the code below.
720-
const absl::Span<const Literal> at_most_one(
721-
&at_most_one_buffer_[local_start], local_end - local_start);
732+
const absl::Span<const Literal> at_most_one = AtMostOne(local_start);
722733

723734
// We expand small sizes into implications.
724735
// TODO(user): Investigate what the best threshold is.
@@ -747,9 +758,6 @@ bool BinaryImplicationGraph::CleanUpAndAddAtMostOnes(int base_index) {
747758
at_most_ones_[l].push_back(local_start);
748759
implies_something_.Set(l);
749760
}
750-
751-
// Add sentinel.
752-
at_most_one_buffer_[local_end++] = Literal(kNoLiteralIndex);
753761
}
754762

755763
at_most_one_buffer_.resize(local_end);
@@ -794,10 +802,7 @@ bool BinaryImplicationGraph::PropagateOnTrue(Literal true_literal,
794802
if (true_literal.Index() < at_most_ones_.size()) {
795803
for (const int start : at_most_ones_[true_literal]) {
796804
bool seen = false;
797-
for (int i = start;; ++i) {
798-
const Literal literal = at_most_one_buffer_[i];
799-
if (literal.Index() == kNoLiteralIndex) break;
800-
805+
for (const Literal literal : AtMostOne(start)) {
801806
++num_inspections_;
802807
if (literal == true_literal) {
803808
if (DEBUG_MODE) {
@@ -1128,16 +1133,16 @@ void BinaryImplicationGraph::RemoveFixedVariables() {
11281133

11291134
class SccGraph {
11301135
public:
1131-
using Implication =
1136+
using Implications =
11321137
absl::StrongVector<LiteralIndex, absl::InlinedVector<Literal, 6>>;
1133-
using AtMostOne =
1138+
using AtMostOnes =
11341139
absl::StrongVector<LiteralIndex, absl::InlinedVector<int32_t, 6>>;
11351140
using SccFinder =
11361141
StronglyConnectedComponentsFinder<int32_t, SccGraph,
11371142
CompactVectorVector<int32_t, int32_t>>;
11381143

1139-
explicit SccGraph(SccFinder* finder, Implication* graph,
1140-
AtMostOne* at_most_ones,
1144+
explicit SccGraph(SccFinder* finder, Implications* graph,
1145+
AtMostOnes* at_most_ones,
11411146
std::vector<Literal>* at_most_one_buffer)
11421147
: finder_(*finder),
11431148
implications_(*graph),
@@ -1200,9 +1205,10 @@ class SccGraph {
12001205
previous_node_to_explore_at_most_one_[start] = node;
12011206
}
12021207

1203-
for (int i = start;; ++i) {
1204-
const Literal l = at_most_one_buffer_[i];
1205-
if (l.Index() == kNoLiteralIndex) break;
1208+
const absl::Span<const Literal> amo =
1209+
absl::MakeSpan(&at_most_one_buffer_[start + 1],
1210+
at_most_one_buffer_[start].Index().value());
1211+
for (const Literal l : amo) {
12061212
if (l.Index() == node) continue;
12071213
tmp_.push_back(l.NegatedIndex().value());
12081214
if (finder_.NodeIsInCurrentDfsPath(l.Index().value())) {
@@ -1223,8 +1229,8 @@ class SccGraph {
12231229

12241230
private:
12251231
const SccFinder& finder_;
1226-
const Implication& implications_;
1227-
const AtMostOne& at_most_ones_;
1232+
const Implications& implications_;
1233+
const AtMostOnes& at_most_ones_;
12281234
const std::vector<Literal>& at_most_one_buffer_;
12291235

12301236
mutable std::vector<int32_t> tmp_;
@@ -1524,9 +1530,7 @@ bool BinaryImplicationGraph::ComputeTransitiveReduction(bool log_info) {
15241530
// Also mark all the ones reachable through the root AMOs.
15251531
if (root < at_most_ones_.size()) {
15261532
for (const int start : at_most_ones_[root]) {
1527-
for (int i = start;; ++i) {
1528-
const Literal l = at_most_one_buffer_[i];
1529-
if (l.Index() == kNoLiteralIndex) break;
1533+
for (const Literal l : AtMostOne(start)) {
15301534
if (l.Index() == root) continue;
15311535
if (!is_marked_[l.Negated()] && !is_redundant_[l.Negated()]) {
15321536
is_marked_.SetUnsafe(l.Negated());
@@ -1991,9 +1995,7 @@ BinaryImplicationGraph::HeuristicAmoPartition(std::vector<Literal>* literals) {
19911995
if (!inserted) continue;
19921996

19931997
int intersection_size = 0;
1994-
for (int i = start;; ++i) {
1995-
const Literal l = at_most_one_buffer_[i];
1996-
if (l.Index() == kNoLiteralIndex) break;
1998+
for (const Literal l : AtMostOne(start)) {
19971999
if (to_consider[l]) ++intersection_size;
19982000
}
19992001
if (intersection_size > 1) {
@@ -2013,9 +2015,7 @@ BinaryImplicationGraph::HeuristicAmoPartition(std::vector<Literal>* literals) {
20132015

20142016
// Recompute size.
20152017
int intersection_size = 0;
2016-
for (int i = start;; ++i) {
2017-
const Literal l = at_most_one_buffer_[i];
2018-
if (l.Index() == kNoLiteralIndex) break;
2018+
for (const Literal l : AtMostOne(start)) {
20192019
if (to_consider[l]) ++intersection_size;
20202020
}
20212021
if (intersection_size != old_size) {
@@ -2027,9 +2027,7 @@ BinaryImplicationGraph::HeuristicAmoPartition(std::vector<Literal>* literals) {
20272027
}
20282028

20292029
// Mark the literal of that at most one at false.
2030-
for (int i = start;; ++i) {
2031-
const Literal l = at_most_one_buffer_[i];
2032-
if (l.Index() == kNoLiteralIndex) break;
2030+
for (const Literal l : AtMostOne(start)) {
20332031
to_consider[l] = false;
20342032
}
20352033

@@ -2050,7 +2048,6 @@ BinaryImplicationGraph::HeuristicAmoPartition(std::vector<Literal>* literals) {
20502048
void BinaryImplicationGraph::MarkDescendants(Literal root) {
20512049
bfs_stack_.resize(implications_.size());
20522050
auto* const stack = bfs_stack_.data();
2053-
auto* const amo_buffer = at_most_one_buffer_.data();
20542051
const int amo_size = static_cast<int>(at_most_ones_.size());
20552052
auto is_marked = is_marked_.const_view();
20562053
auto is_redundant = is_redundant_.const_view();
@@ -2072,9 +2069,7 @@ void BinaryImplicationGraph::MarkDescendants(Literal root) {
20722069

20732070
if (current.Index() >= amo_size) continue;
20742071
for (const int start : at_most_ones_[current]) {
2075-
for (int i = start;; ++i) {
2076-
const Literal l = amo_buffer[i];
2077-
if (l.Index() == kNoLiteralIndex) break;
2072+
for (const Literal l : AtMostOne(start)) {
20782073
if (l == current) continue;
20792074
if (!is_marked[l.NegatedIndex()] && !is_redundant[l.NegatedIndex()]) {
20802075
is_marked_.SetUnsafe(l.NegatedIndex());
@@ -2161,9 +2156,7 @@ const std::vector<Literal>& BinaryImplicationGraph::DirectImplications(
21612156
DCHECK(at_most_ones_[literal].empty());
21622157
}
21632158
for (const int start : at_most_ones_[literal]) {
2164-
for (int i = start;; ++i) {
2165-
const Literal l = at_most_one_buffer_[i];
2166-
if (l.Index() == kNoLiteralIndex) break;
2159+
for (const Literal l : AtMostOne(start)) {
21672160
if (l == literal) continue;
21682161
if (assignment.LiteralIsAssigned(l)) continue;
21692162
if (!is_removed_[l] && !in_direct_implications_[l.NegatedIndex()]) {
@@ -2177,6 +2170,38 @@ const std::vector<Literal>& BinaryImplicationGraph::DirectImplications(
21772170
return direct_implications_;
21782171
}
21792172

2173+
absl::Span<const Literal> BinaryImplicationGraph::AtMostOne(int start) const {
2174+
const int size = at_most_one_buffer_[start].Index().value();
2175+
return absl::MakeSpan(&at_most_one_buffer_[start + 1], size);
2176+
}
2177+
2178+
LiteralIndex BinaryImplicationGraph::RandomImpliedLiteral(Literal lhs) {
2179+
const int size1 = implications_[lhs].size();
2180+
const int size2 =
2181+
lhs.Index() < at_most_ones_.size() ? at_most_ones_[lhs].size() : 0;
2182+
if (size1 + size2 == 0) return kNoLiteralIndex;
2183+
2184+
const int choice = absl::Uniform<int>(*random_, 0, size1 + size2);
2185+
if (choice < size1) {
2186+
return implications_[lhs][choice].Index();
2187+
}
2188+
2189+
const absl::Span<const Literal> amo =
2190+
AtMostOne(at_most_ones_[lhs][choice - size1]);
2191+
CHECK_GE(amo.size(), 2);
2192+
const int first_choice = absl::Uniform<int>(*random_, 0, amo.size());
2193+
const Literal lit = amo[first_choice];
2194+
if (lit != lhs) return lit.NegatedIndex();
2195+
2196+
// We are unlucky and just picked the wrong literal: take a different one.
2197+
int next_choice = absl::Uniform<int>(*random_, 0, amo.size() - 1);
2198+
if (next_choice >= first_choice) {
2199+
next_choice += 1;
2200+
}
2201+
CHECK_NE(amo[next_choice], lhs);
2202+
return amo[next_choice].NegatedIndex();
2203+
}
2204+
21802205
bool BinaryImplicationGraph::FindFailedLiteralAroundVar(BooleanVariable var,
21812206
bool* is_unsat) {
21822207
const int saved_index = propagation_trail_index_;
@@ -2413,22 +2438,19 @@ bool BinaryImplicationGraph::InvariantsAreOk() {
24132438
lit_to_start.insert({i, start});
24142439
}
24152440
}
2416-
int index = 0;
2417-
int start = 0;
2418-
for (int i = 0; i < at_most_one_buffer_.size(); ++i) {
2419-
if (at_most_one_buffer_[i].Index() == kNoLiteralIndex) {
2420-
++index;
2421-
start = i + 1;
2422-
continue;
2423-
}
2424-
if (is_removed_[at_most_one_buffer_[i]]) {
2425-
LOG(ERROR) << "A removed literal still appear in an amo "
2426-
<< at_most_one_buffer_[i];
2427-
return false;
2428-
}
2429-
if (!lit_to_start.contains({at_most_one_buffer_[i], start})) {
2430-
return false;
2441+
2442+
for (int start = 0; start < at_most_one_buffer_.size();) {
2443+
const absl::Span<const Literal> amo = AtMostOne(start);
2444+
for (const Literal l : amo) {
2445+
if (is_removed_[l]) {
2446+
LOG(ERROR) << "A removed literal still appear in an amo " << l;
2447+
return false;
2448+
}
2449+
if (!lit_to_start.contains({l, start})) {
2450+
return false;
2451+
}
24312452
}
2453+
start += amo.size() + 1;
24322454
}
24332455

24342456
return true;
@@ -2438,19 +2460,10 @@ absl::Span<const Literal> BinaryImplicationGraph::NextAtMostOne() {
24382460
if (at_most_one_iterator_ >= at_most_one_buffer_.size()) {
24392461
return absl::Span<const Literal>();
24402462
}
2441-
const int local_start = at_most_one_iterator_;
2442-
DCHECK_NE(at_most_one_buffer_[local_start].Index(), kNoLiteralIndex);
2443-
DCHECK(at_most_one_iterator_ == 0 ||
2444-
at_most_one_iterator_ >= at_most_one_buffer_.size() ||
2445-
at_most_one_buffer_[at_most_one_iterator_ - 1].Index() ==
2446-
kNoLiteralIndex);
2447-
int local_end = at_most_one_iterator_ + 1;
2448-
while (at_most_one_buffer_[local_end].Index() != kNoLiteralIndex) {
2449-
++local_end;
2450-
}
2451-
at_most_one_iterator_ = local_end + 1;
2452-
return absl::MakeSpan(at_most_one_buffer_.data() + local_start,
2453-
local_end - local_start);
2463+
2464+
const absl::Span<const Literal> result = AtMostOne(at_most_one_iterator_);
2465+
at_most_one_iterator_ += result.size() + 1;
2466+
return result;
24542467
}
24552468

24562469
// ----- SatClause -----

0 commit comments

Comments
 (0)