23#include "absl/algorithm/container.h"
24#include "absl/base/log_severity.h"
25#include "absl/cleanup/cleanup.h"
26#include "absl/container/flat_hash_set.h"
27#include "absl/log/check.h"
28#include "absl/log/log.h"
29#include "absl/random/distributions.h"
30#include "absl/strings/string_view.h"
31#include "absl/types/span.h"
46void EquivalenceSatSweeping::LoadClausesInModel(
47 absl::Span<const SatClause* const> clauses,
Model* m) {
48 const int num_booleans = big_model_to_small_model_.size();
49 auto* sat_solver = m->GetOrCreate<SatSolver>();
50 CHECK_EQ(sat_solver->NumVariables(), 0);
52 sat_solver->SetNumVariables(num_booleans);
54 std::vector<Literal> literals;
55 for (
const SatClause* clause : clauses) {
57 for (
const Literal l : clause->AsSpan()) {
59 Literal(big_model_to_small_model_[l.Variable()], l.IsPositive()));
61 sat_solver->AddProblemClause(literals);
75std::vector<absl::Span<const Literal>> EquivalenceSatSweeping::GetNeighborhood(
76 BooleanVariable var) {
77 std::vector<absl::Span<const Literal>> neighborhood;
78 absl::flat_hash_set<BooleanVariable> seen_bools = {var};
79 const int binary_clause_slack = max_num_clauses_ / 10;
80 std::deque<BooleanVariable> bools_to_add;
81 bools_to_add.push_back(var);
82 while (!bools_to_add.empty()) {
87 const BooleanVariable cur_var = bools_to_add.front();
88 bools_to_add.pop_front();
89 for (
const ClauseIndex clause_index : var_to_clauses_[cur_var]) {
90 const absl::Span<const Literal> cur_clause = clauses_[clause_index];
91 const int num_unseen_bools = absl::c_count_if(cur_clause, [&](Literal l) {
92 return !seen_bools.contains(l.Variable());
94 if (seen_bools.size() + num_unseen_bools >= max_num_boolean_variables_) {
97 if (cur_clause.size() == 2) {
98 const Literal other_lit =
99 cur_clause[0].Variable() == cur_var ? cur_clause[1] : cur_clause[0];
100 if (implication_graph_->RepresentativeOf(other_lit).Variable() ==
110 if (neighborhood.size() >= max_num_clauses_ - binary_clause_slack &&
111 cur_clause.size() > 2) {
116 for (
const Literal l : cur_clause) {
117 if (seen_bools.contains(l.Variable()))
continue;
118 bools_to_add.push_back(l.Variable());
119 seen_bools.insert(l.Variable());
121 neighborhood.push_back(cur_clause);
122 if (neighborhood.size() >= max_num_clauses_)
return neighborhood;
129void RefinePartitions(std::vector<std::vector<Literal>>& partitions,
133 const int original_num_partitions = partitions.size();
134 for (
int i = 0;
i < original_num_partitions;
i++) {
135 std::vector<Literal>& partition_for_negated = partitions.emplace_back();
136 std::vector<Literal>& partition_for_true = partitions[
i];
139 int new_partition_for_true_size = 0;
140 for (
int j = 0; j < partition_for_true.size(); j++) {
141 const Literal lit = partition_for_true[j];
143 partition_for_negated.push_back(lit);
146 partition_for_true[new_partition_for_true_size++] = lit;
148 partition_for_true.resize(new_partition_for_true_size);
150 if (partition_for_negated.size() <= 1) {
151 partitions.pop_back();
154 int new_num_partitions = 0;
155 for (
int i = 0;
i < partitions.size();
i++) {
156 if (partitions[
i].size() > 1) {
157 if (new_num_partitions !=
i) {
158 partitions[new_num_partitions] = std::move(partitions[
i]);
160 new_num_partitions++;
163 partitions.resize(new_num_partitions);
168 std::function<
void(
Model*)> run_inprocessing) {
172 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
173 if (sat_solver_->AssumptionLevel() != 0) {
175 <<
"Assumption level is not 0 (should not happen), skipping sweeping.";
180 struct ExtractedClausesHelper {
181 explicit ExtractedClausesHelper(
184 int clause_size_limit_var)
185 : clause_size_limit(clause_size_limit_var), clauses(clauses_vec) {}
187 binary_clauses.push_back({a,
b});
188 clauses.push_back(absl::MakeConstSpan(binary_clauses.back()));
190 void AddClause(absl::Span<const Literal> clause) {
191 if (clause.size() > clause_size_limit)
return;
192 clauses.push_back(clause);
194 void SetNumVariables(
int ) {}
196 int clause_size_limit;
198 std::deque<std::array<Literal, 2>> binary_clauses;
202 ExtractedClausesHelper helper(clauses_, max_num_boolean_variables_);
203 if (!sat_solver_->ExtractClauses(&helper))
return false;
205 if (clauses_.empty()) {
206 VLOG(2) <<
"No clauses extracted, skipping sweeping.";
210 const int num_vars = sat_solver_->NumVariables();
212 struct GetVarMapper {
215 var_to_clauses_.ResetFromTransposeMap<GetVarMapper>(clauses_, num_vars);
217 global_time_limit_->AdvanceDeterministicTime(clause_manager_->num_clauses() *
222 std::vector<std::pair<Literal, Literal>> binary_clauses;
223 std::vector<Literal> unary_clauses;
224 for (
int i = 0;
i < 50; ++
i) {
225 BooleanVariable boolean_for_neighborhood;
228 constexpr int kMaxTries = 10;
229 for (tries = 0; tries < kMaxTries; ++tries) {
230 boolean_for_neighborhood = absl::Uniform<int>(*random_, 0, num_vars);
231 if (var_to_clauses_[boolean_for_neighborhood].size() < 2)
continue;
232 const Literal positive_lit(boolean_for_neighborhood,
true);
233 if (implication_graph_->RepresentativeOf(positive_lit) !=
239 if (tries == kMaxTries)
continue;
242 const std::vector<absl::Span<const Literal>> neighborhood =
243 GetNeighborhood(boolean_for_neighborhood);
245 if (neighborhood.empty()) {
246 VLOG(2) <<
"Neighborhood is empty for " << boolean_for_neighborhood;
251 big_model_to_small_model_.
clear();
252 small_model_to_big_model_.clear();
253 for (
const absl::Span<const Literal> clause : neighborhood) {
254 neighborhood_clauses.
Add({});
255 for (
const Literal l : clause) {
256 const BooleanVariable new_var(big_model_to_small_model_.size());
257 auto [it, inserted] =
258 big_model_to_small_model_.insert({l.Variable(), new_var});
260 small_model_to_big_model_.push_back(l.Variable());
263 Literal(it->second, l.IsPositive()));
268 neighborhood_clauses, &sweep_time_limit, run_inprocessing);
271 sat_solver_->NotifyThatModelIsUnsat();
276 Literal(small_model_to_big_model_[l1.Variable()], l1.IsPositive());
278 Literal(small_model_to_big_model_[l2.Variable()], l2.IsPositive());
279 if (implication_graph_->IsRemoved(mapped_l1) ||
280 implication_graph_->IsRemoved(mapped_l2)) {
283 binary_clauses.push_back({mapped_l1, mapped_l2});
288 if (implication_graph_->IsRemoved(mapped_l))
continue;
289 unary_clauses.push_back(mapped_l);
295 global_time_limit_->AdvanceDeterministicTime(
297 if (binary_clauses.empty() && unary_clauses.empty()) {
301 clause_manager_->DetachAllClauses();
302 for (
const auto& [l1, l2] : binary_clauses) {
303 if (!implication_graph_->AddBinaryClause(l1, l2))
return false;
305 for (
const Literal l : unary_clauses) {
306 if (!implication_graph_->FixLiteral(l, {}))
return false;
313 std::function<
void(
Model*)> configure_model_before_first_solve) {
316 Model neighborhood_model;
319 absl::Cleanup update_elapsed_dtime =
320 [model_time_limit, time_limit,
321 time_limit_dtime_start =
323 time_limit->AdvanceDeterministicTime(
325 time_limit_dtime_start);
343 BooleanVariable max_boolean = BooleanVariable(0);
344 for (
int i = 0;
i < clauses.
size(); ++
i) {
345 for (
const Literal l : clauses[
i]) {
346 max_boolean = std::max(max_boolean, l.Variable());
349 CHECK_EQ(sat_solver->NumVariables(), 0);
350 sat_solver->SetNumVariables(max_boolean.value() + 1);
352 for (
int i = 0;
i < clauses.
size(); ++
i) {
353 sat_solver->AddProblemClause(clauses[
i]);
355 configure_model_before_first_solve(&neighborhood_model);
376 int num_sat_calls = 1;
377 std::vector<Literal> possible_backbone;
379 possible_backbone.reserve(num_variables);
380 for (BooleanVariable var{0}; var < num_variables; ++var) {
381 possible_backbone.push_back(
384 std::vector<std::vector<Literal>> partitions = {possible_backbone};
385 while (!possible_backbone.empty()) {
389 const int index = absl::Uniform<int>(*random, 0, possible_backbone.size());
390 const Literal l = possible_backbone[index];
391 std::swap(possible_backbone[index], possible_backbone.back());
392 possible_backbone.pop_back();
411 for (std::vector<Literal>& partition : partitions) {
412 int new_partition_size = 0;
413 for (
int i = 0;
i < partition.size();
i++) {
414 const Literal literal = partition[
i];
415 if (literal == l || literal == l.
Negated())
continue;
416 partition[new_partition_size++] = literal;
418 partition.resize(new_partition_size);
426 int new_possible_backbone_size = 0;
427 for (
int i = 0;
i < possible_backbone.size(); ++
i) {
433 possible_backbone[new_possible_backbone_size++] = possible_backbone[
i];
435 possible_backbone.resize(new_possible_backbone_size);
438 RefinePartitions(partitions, nh_solver->
Assignment());
441 const int num_partitions = partitions.size();
442 std::vector<std::pair<Literal, Literal>> equivalences;
443 int num_equivalences = 0;
444 while (!partitions.empty()) {
445 std::vector<Literal>& partition = partitions.back();
446 if (partition.size() <= 1) {
447 partitions.pop_back();
450 const Literal l1 = partition[0];
451 const Literal l2 = partition.back();
471 equivalences.push_back({l1, l2});
472 partition.pop_back();
483 RefinePartitions(partitions, nh_solver->
Assignment());
492 struct GetBinaryClause {
493 explicit GetBinaryClause(std::vector<std::pair<Literal, Literal>>& clauses)
494 : binary_clauses(clauses) {}
496 binary_clauses.push_back({a,
b});
498 std::vector<std::pair<Literal, Literal>>& binary_clauses;
509 int num_equivalences_in_model = 0;
510 for (BooleanVariable var{0}; var < num_variables; ++var) {
514 DCHECK_EQ(num_equivalences_in_model, num_equivalences);
518 absl::flat_hash_set<std::pair<Literal, Literal>> input_clauses;
519 for (
int i = 0;
i < clauses.
size();
i++) {
520 const absl::Span<const Literal> clause = clauses[
i];
521 if (clause.size() != 2)
continue;
524 if (l1 < l2) std::swap(l1, l2);
525 input_clauses.insert({l1, l2});
527 int new_binary_clauses_size = 0;
530 if (clause.first < clause.second) {
531 std::swap(clause.first, clause.second);
533 if (input_clauses.contains(clause))
continue;
539 VLOG(2) <<
"num_booleans: " << num_variables
540 <<
" num_clauses: " << clauses.
size()
541 <<
" num_partitions: " << num_partitions
544 <<
" num_equivalences: " << num_equivalences
545 <<
" num_sat_calls: " << num_sat_calls
547 <<
" wtime: " << wall_timer.
Get();
void MergeWithGlobalTimeLimit(const TimeLimit *other)
double GetElapsedDeterministicTime() const
void ChangeDeterministicLimit(double new_limit)
Literal RepresentativeOf(Literal l) const
void ExtractAllBinaryClauses(Output *out) const
bool DetectEquivalences(bool log_info=false)
void AppendToLastVector(const V &value)
int Add(absl::Span< const V > values)
bool DoOneRound(std::function< void(Model *)> run_inprocessing)
BooleanVariable Variable() const
void ResetDecisionHeuristic()
void set_random_branches_ratio(double value)
void set_preferred_variable_order(::operations_research::sat::SatParameters_VariableOrder value)
void set_initial_polarity(::operations_research::sat::SatParameters_Polarity value)
void set_random_polarity_ratio(double value)
static constexpr VariableOrder IN_RANDOM_ORDER
static constexpr Polarity POLARITY_RANDOM
bool AddBinaryClause(Literal a, Literal b)
ABSL_MUST_USE_RESULT bool FinishPropagation(std::optional< ConflictCallback > callback=std::nullopt)
ABSL_MUST_USE_RESULT bool AddUnitClause(Literal true_literal)
Status ResetAndSolveWithGivenAssumptions(const std::vector< Literal > &assumptions, int64_t max_number_of_conflicts=-1)
const VariablesAssignment & Assignment() const
ABSL_MUST_USE_RESULT bool ResetToLevelZero()
Literal GetTrueLiteralForAssignedVariable(BooleanVariable var) const
bool LiteralIsTrue(Literal literal) const
SatSweepingResult DoFullSatSweeping(const CompactVectorVector< int, Literal > &clauses, TimeLimit *time_limit, std::function< void(Model *)> configure_model_before_first_solve)
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
std::vector< Literal > unary_clauses
std::vector< std::pair< Literal, Literal > > binary_clauses