2025-01-10 11:35:44 +01:00
|
|
|
|
// Copyright 2010-2025 Google LLC
|
2014-07-24 18:12:50 +00:00
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
|
//
|
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
//
|
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
2017-04-26 17:30:25 +02:00
|
|
|
|
#include "ortools/sat/simplification.h"
|
2014-07-24 18:12:50 +00:00
|
|
|
|
|
2017-07-27 11:28:55 -07:00
|
|
|
|
#include <algorithm>
|
2021-03-04 18:26:01 +01:00
|
|
|
|
#include <cstdint>
|
2022-02-15 18:00:11 +01:00
|
|
|
|
#include <deque>
|
2017-07-27 11:28:55 -07:00
|
|
|
|
#include <limits>
|
|
|
|
|
|
#include <utility>
|
2022-02-15 18:00:11 +01:00
|
|
|
|
#include <vector>
|
2016-06-02 13:19:10 +02:00
|
|
|
|
|
2023-05-24 11:42:11 +02:00
|
|
|
|
#include "absl/container/btree_set.h"
|
|
|
|
|
|
#include "absl/log/check.h"
|
2025-03-04 21:09:32 +01:00
|
|
|
|
#include "absl/log/log.h"
|
2025-03-13 15:38:23 +01:00
|
|
|
|
#include "absl/log/vlog_is_on.h"
|
2022-02-15 18:00:11 +01:00
|
|
|
|
#include "absl/types/span.h"
|
2018-06-08 16:40:43 +02:00
|
|
|
|
#include "ortools/algorithms/dynamic_partition.h"
|
|
|
|
|
|
#include "ortools/base/adjustable_priority_queue-inl.h"
|
2022-02-15 18:00:11 +01:00
|
|
|
|
#include "ortools/base/adjustable_priority_queue.h"
|
2017-07-27 11:28:55 -07:00
|
|
|
|
#include "ortools/base/logging.h"
|
2018-06-08 16:40:43 +02:00
|
|
|
|
#include "ortools/base/stl_util.h"
|
2020-12-18 10:16:56 +01:00
|
|
|
|
#include "ortools/base/strong_vector.h"
|
2017-04-26 17:30:25 +02:00
|
|
|
|
#include "ortools/base/timer.h"
|
2017-10-09 11:21:43 -07:00
|
|
|
|
#include "ortools/graph/strongly_connected_components.h"
|
2022-02-15 18:00:11 +01:00
|
|
|
|
#include "ortools/sat/drat_proof_handler.h"
|
|
|
|
|
|
#include "ortools/sat/sat_base.h"
|
|
|
|
|
|
#include "ortools/sat/sat_parameters.pb.h"
|
|
|
|
|
|
#include "ortools/sat/sat_solver.h"
|
|
|
|
|
|
#include "ortools/util/logging.h"
|
|
|
|
|
|
#include "ortools/util/strong_integers.h"
|
2017-07-27 11:28:55 -07:00
|
|
|
|
#include "ortools/util/time_limit.h"
|
2014-07-24 18:12:50 +00:00
|
|
|
|
|
|
|
|
|
|
namespace operations_research {
|
|
|
|
|
|
namespace sat {
|
|
|
|
|
|
|
2016-06-02 13:19:10 +02:00
|
|
|
|
SatPostsolver::SatPostsolver(int num_variables)
|
|
|
|
|
|
: initial_num_variables_(num_variables), num_variables_(num_variables) {
|
2014-11-07 14:30:26 +00:00
|
|
|
|
reverse_mapping_.resize(num_variables);
|
2016-03-12 06:32:56 -08:00
|
|
|
|
for (BooleanVariable var(0); var < num_variables; ++var) {
|
2014-11-07 14:30:26 +00:00
|
|
|
|
reverse_mapping_[var] = var;
|
|
|
|
|
|
}
|
|
|
|
|
|
assignment_.Resize(num_variables);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-10-31 16:18:18 +01:00
|
|
|
|
void SatPostsolver::Add(Literal x, absl::Span<const Literal> clause) {
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK(!clause.empty());
|
2015-07-06 19:23:12 +02:00
|
|
|
|
DCHECK(std::find(clause.begin(), clause.end(), x) != clause.end());
|
2014-11-07 14:30:26 +00:00
|
|
|
|
associated_literal_.push_back(ApplyReverseMapping(x));
|
2015-07-06 19:23:12 +02:00
|
|
|
|
clauses_start_.push_back(clauses_literals_.size());
|
2020-10-28 13:42:36 +01:00
|
|
|
|
for (const Literal& l : clause) {
|
2015-07-06 19:23:12 +02:00
|
|
|
|
clauses_literals_.push_back(ApplyReverseMapping(l));
|
2014-11-07 14:30:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SatPostsolver::FixVariable(Literal x) {
|
2016-06-02 13:19:10 +02:00
|
|
|
|
const Literal l = ApplyReverseMapping(x);
|
2014-11-07 14:30:26 +00:00
|
|
|
|
assignment_.AssignFromTrueLiteral(l);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SatPostsolver::ApplyMapping(
|
2024-07-12 13:56:11 +02:00
|
|
|
|
const util_intops::StrongVector<BooleanVariable, BooleanVariable>&
|
|
|
|
|
|
mapping) {
|
|
|
|
|
|
util_intops::StrongVector<BooleanVariable, BooleanVariable> new_mapping;
|
2016-06-02 13:19:10 +02:00
|
|
|
|
if (reverse_mapping_.size() < mapping.size()) {
|
|
|
|
|
|
// We have new variables.
|
|
|
|
|
|
while (reverse_mapping_.size() < mapping.size()) {
|
|
|
|
|
|
reverse_mapping_.push_back(BooleanVariable(num_variables_++));
|
|
|
|
|
|
}
|
|
|
|
|
|
assignment_.Resize(num_variables_);
|
|
|
|
|
|
}
|
2016-03-12 06:32:56 -08:00
|
|
|
|
for (BooleanVariable v(0); v < mapping.size(); ++v) {
|
|
|
|
|
|
const BooleanVariable image = mapping[v];
|
|
|
|
|
|
if (image != kNoBooleanVariable) {
|
2016-06-02 13:19:10 +02:00
|
|
|
|
if (image >= new_mapping.size()) {
|
|
|
|
|
|
new_mapping.resize(image.value() + 1, kNoBooleanVariable);
|
|
|
|
|
|
}
|
2014-11-07 14:30:26 +00:00
|
|
|
|
new_mapping[image] = reverse_mapping_[v];
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK_NE(new_mapping[image], kNoBooleanVariable);
|
2014-11-07 14:30:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
std::swap(new_mapping, reverse_mapping_);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Literal SatPostsolver::ApplyReverseMapping(Literal l) {
|
2016-06-02 13:19:10 +02:00
|
|
|
|
if (l.Variable() >= reverse_mapping_.size()) {
|
|
|
|
|
|
// We have new variables.
|
|
|
|
|
|
while (l.Variable() >= reverse_mapping_.size()) {
|
|
|
|
|
|
reverse_mapping_.push_back(BooleanVariable(num_variables_++));
|
|
|
|
|
|
}
|
|
|
|
|
|
assignment_.Resize(num_variables_);
|
|
|
|
|
|
}
|
|
|
|
|
|
DCHECK_NE(reverse_mapping_[l.Variable()], kNoBooleanVariable);
|
|
|
|
|
|
const Literal result(reverse_mapping_[l.Variable()], l.IsPositive());
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK(!assignment_.LiteralIsAssigned(result));
|
2016-06-02 13:19:10 +02:00
|
|
|
|
return result;
|
2014-11-07 14:30:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
|
void SatPostsolver::Postsolve(VariablesAssignment* assignment) const {
|
2014-07-24 18:12:50 +00:00
|
|
|
|
// First, we set all unassigned variable to true.
|
|
|
|
|
|
// This will be a valid assignment of the presolved problem.
|
2016-03-12 06:32:56 -08:00
|
|
|
|
for (BooleanVariable var(0); var < assignment->NumberOfVariables(); ++var) {
|
2015-10-23 13:45:43 +02:00
|
|
|
|
if (!assignment->VariableIsAssigned(var)) {
|
2014-07-24 18:12:50 +00:00
|
|
|
|
assignment->AssignFromTrueLiteral(Literal(var, true));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-07-06 19:23:12 +02:00
|
|
|
|
int previous_start = clauses_literals_.size();
|
|
|
|
|
|
for (int i = static_cast<int>(clauses_start_.size()) - 1; i >= 0; --i) {
|
2014-07-24 18:12:50 +00:00
|
|
|
|
bool set_associated_var = true;
|
2015-07-06 19:23:12 +02:00
|
|
|
|
const int new_start = clauses_start_[i];
|
|
|
|
|
|
for (int j = new_start; j < previous_start; ++j) {
|
2015-10-23 13:45:43 +02:00
|
|
|
|
if (assignment->LiteralIsTrue(clauses_literals_[j])) {
|
2015-07-06 19:23:12 +02:00
|
|
|
|
set_associated_var = false;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
2015-07-06 19:23:12 +02:00
|
|
|
|
previous_start = new_start;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
if (set_associated_var) {
|
2018-02-16 17:08:01 +01:00
|
|
|
|
assignment->UnassignLiteral(associated_literal_[i].Negated());
|
2014-07-24 18:12:50 +00:00
|
|
|
|
assignment->AssignFromTrueLiteral(associated_literal_[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-06-02 13:19:10 +02:00
|
|
|
|
|
|
|
|
|
|
// Ignore the value of any variables added by the presolve.
|
|
|
|
|
|
assignment->Resize(initial_num_variables_);
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
|
std::vector<bool> SatPostsolver::ExtractAndPostsolveSolution(
|
2020-10-28 13:42:36 +01:00
|
|
|
|
const SatSolver& solver) {
|
2014-11-07 14:30:26 +00:00
|
|
|
|
std::vector<bool> solution(solver.NumVariables());
|
2016-03-12 06:32:56 -08:00
|
|
|
|
for (BooleanVariable var(0); var < solver.NumVariables(); ++var) {
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK(solver.Assignment().VariableIsAssigned(var));
|
2014-11-07 14:30:26 +00:00
|
|
|
|
solution[var.value()] =
|
2015-10-23 13:45:43 +02:00
|
|
|
|
solver.Assignment().LiteralIsTrue(Literal(var, true));
|
2014-11-07 14:30:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
return PostsolveSolution(solution);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
|
std::vector<bool> SatPostsolver::PostsolveSolution(
|
2020-10-28 13:42:36 +01:00
|
|
|
|
const std::vector<bool>& solution) {
|
2016-03-12 06:32:56 -08:00
|
|
|
|
for (BooleanVariable var(0); var < solution.size(); ++var) {
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK_LT(var, reverse_mapping_.size());
|
|
|
|
|
|
DCHECK_NE(reverse_mapping_[var], kNoBooleanVariable);
|
|
|
|
|
|
DCHECK(!assignment_.VariableIsAssigned(reverse_mapping_[var]));
|
2014-11-07 14:30:26 +00:00
|
|
|
|
assignment_.AssignFromTrueLiteral(
|
|
|
|
|
|
Literal(reverse_mapping_[var], solution[var.value()]));
|
|
|
|
|
|
}
|
|
|
|
|
|
Postsolve(&assignment_);
|
|
|
|
|
|
std::vector<bool> postsolved_solution;
|
2017-11-07 15:45:52 +01:00
|
|
|
|
postsolved_solution.reserve(initial_num_variables_);
|
2016-06-02 13:19:10 +02:00
|
|
|
|
for (int i = 0; i < initial_num_variables_; ++i) {
|
2014-11-07 14:30:26 +00:00
|
|
|
|
postsolved_solution.push_back(
|
2016-03-12 06:32:56 -08:00
|
|
|
|
assignment_.LiteralIsTrue(Literal(BooleanVariable(i), true)));
|
2014-11-07 14:30:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
return postsolved_solution;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
|
void SatPresolver::AddBinaryClause(Literal a, Literal b) { AddClause({a, b}); }
|
2014-07-24 18:12:50 +00:00
|
|
|
|
|
2018-10-31 16:18:18 +01:00
|
|
|
|
void SatPresolver::AddClause(absl::Span<const Literal> clause) {
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK_GT(clause.size(), 0) << "Added an empty clause to the presolver";
|
2014-07-24 18:12:50 +00:00
|
|
|
|
const ClauseIndex ci(clauses_.size());
|
|
|
|
|
|
clauses_.push_back(std::vector<Literal>(clause.begin(), clause.end()));
|
|
|
|
|
|
in_clause_to_process_.push_back(true);
|
|
|
|
|
|
clause_to_process_.push_back(ci);
|
2014-11-07 14:30:26 +00:00
|
|
|
|
|
2018-04-17 13:00:25 +02:00
|
|
|
|
bool changed = false;
|
2020-10-28 13:42:36 +01:00
|
|
|
|
std::vector<Literal>& clause_ref = clauses_.back();
|
2014-11-07 14:30:26 +00:00
|
|
|
|
if (!equiv_mapping_.empty()) {
|
|
|
|
|
|
for (int i = 0; i < clause_ref.size(); ++i) {
|
2018-04-17 13:00:25 +02:00
|
|
|
|
const Literal old_literal = clause_ref[i];
|
2023-10-12 10:06:27 +02:00
|
|
|
|
clause_ref[i] = Literal(equiv_mapping_[clause_ref[i]]);
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (old_literal != clause_ref[i]) changed = true;
|
2014-11-07 14:30:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
std::sort(clause_ref.begin(), clause_ref.end());
|
|
|
|
|
|
clause_ref.erase(std::unique(clause_ref.begin(), clause_ref.end()),
|
|
|
|
|
|
clause_ref.end());
|
|
|
|
|
|
|
|
|
|
|
|
// Check for trivial clauses:
|
|
|
|
|
|
for (int i = 1; i < clause_ref.size(); ++i) {
|
|
|
|
|
|
if (clause_ref[i] == clause_ref[i - 1].Negated()) {
|
|
|
|
|
|
// The clause is trivial!
|
|
|
|
|
|
++num_trivial_clauses_;
|
|
|
|
|
|
clause_to_process_.pop_back();
|
|
|
|
|
|
clauses_.pop_back();
|
|
|
|
|
|
in_clause_to_process_.pop_back();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-03-12 17:41:14 +01:00
|
|
|
|
// This needs to be done after the basic canonicalization above.
|
|
|
|
|
|
signatures_.push_back(ComputeSignatureOfClauseVariables(ci));
|
|
|
|
|
|
DCHECK_EQ(signatures_.size(), clauses_.size());
|
|
|
|
|
|
|
2018-05-03 15:00:06 +02:00
|
|
|
|
if (drat_proof_handler_ != nullptr && changed) {
|
|
|
|
|
|
drat_proof_handler_->AddClause(clause_ref);
|
|
|
|
|
|
drat_proof_handler_->DeleteClause(clause);
|
2016-06-02 13:19:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-11-07 14:30:26 +00:00
|
|
|
|
const Literal max_literal = clause_ref.back();
|
2016-01-26 13:50:39 +01:00
|
|
|
|
const int required_size = std::max(max_literal.Index().value(),
|
2020-10-22 23:36:58 +02:00
|
|
|
|
max_literal.NegatedIndex().value()) +
|
|
|
|
|
|
1;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
if (required_size > literal_to_clauses_.size()) {
|
|
|
|
|
|
literal_to_clauses_.resize(required_size);
|
2014-11-07 14:30:26 +00:00
|
|
|
|
literal_to_clause_sizes_.resize(required_size);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (Literal e : clause_ref) {
|
2023-10-12 10:06:27 +02:00
|
|
|
|
literal_to_clauses_[e].push_back(ci);
|
|
|
|
|
|
literal_to_clause_sizes_[e]++;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
2014-11-07 14:30:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-02 13:19:10 +02:00
|
|
|
|
void SatPresolver::SetNumVariables(int num_variables) {
|
|
|
|
|
|
const int required_size = 2 * num_variables;
|
|
|
|
|
|
if (required_size > literal_to_clauses_.size()) {
|
|
|
|
|
|
literal_to_clauses_.resize(required_size);
|
|
|
|
|
|
literal_to_clause_sizes_.resize(required_size);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-27 13:49:29 +02:00
|
|
|
|
void SatPresolver::RebuildLiteralToClauses() {
|
|
|
|
|
|
const int size = literal_to_clauses_.size();
|
|
|
|
|
|
literal_to_clauses_.clear();
|
|
|
|
|
|
literal_to_clauses_.resize(size);
|
|
|
|
|
|
for (ClauseIndex ci(0); ci < clauses_.size(); ++ci) {
|
|
|
|
|
|
for (const Literal lit : clauses_[ci]) {
|
|
|
|
|
|
literal_to_clauses_[lit].push_back(ci);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
num_deleted_literals_since_last_cleanup_ = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
|
void SatPresolver::AddClauseInternal(std::vector<Literal>* clause) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (drat_proof_handler_ != nullptr) drat_proof_handler_->AddClause(*clause);
|
2016-06-02 13:19:10 +02:00
|
|
|
|
|
|
|
|
|
|
DCHECK(std::is_sorted(clause->begin(), clause->end()));
|
2021-09-07 11:53:01 +02:00
|
|
|
|
DCHECK_GT(clause->size(), 0) << "TODO(user): Unsat during presolve?";
|
2014-11-07 14:30:26 +00:00
|
|
|
|
const ClauseIndex ci(clauses_.size());
|
|
|
|
|
|
clauses_.push_back(std::vector<Literal>());
|
|
|
|
|
|
clauses_.back().swap(*clause);
|
|
|
|
|
|
in_clause_to_process_.push_back(true);
|
|
|
|
|
|
clause_to_process_.push_back(ci);
|
2019-03-12 17:41:14 +01:00
|
|
|
|
for (const Literal e : clauses_.back()) {
|
2023-10-12 10:06:27 +02:00
|
|
|
|
literal_to_clauses_[e].push_back(ci);
|
|
|
|
|
|
literal_to_clause_sizes_[e]++;
|
2016-06-02 13:19:10 +02:00
|
|
|
|
UpdatePriorityQueue(e.Variable());
|
|
|
|
|
|
UpdateBvaPriorityQueue(e.Index());
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
2019-03-12 17:41:14 +01:00
|
|
|
|
|
|
|
|
|
|
signatures_.push_back(ComputeSignatureOfClauseVariables(ci));
|
|
|
|
|
|
DCHECK_EQ(signatures_.size(), clauses_.size());
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-12 13:56:11 +02:00
|
|
|
|
util_intops::StrongVector<BooleanVariable, BooleanVariable>
|
2020-11-19 00:17:26 +01:00
|
|
|
|
SatPresolver::VariableMapping() const {
|
2024-07-12 13:56:11 +02:00
|
|
|
|
util_intops::StrongVector<BooleanVariable, BooleanVariable> result;
|
2016-03-12 06:32:56 -08:00
|
|
|
|
BooleanVariable new_var(0);
|
|
|
|
|
|
for (BooleanVariable var(0); var < NumVariables(); ++var) {
|
2023-10-12 10:06:27 +02:00
|
|
|
|
if (literal_to_clause_sizes_[Literal(var, true)] > 0 ||
|
|
|
|
|
|
literal_to_clause_sizes_[Literal(var, false)] > 0) {
|
2014-07-24 18:12:50 +00:00
|
|
|
|
result.push_back(new_var);
|
|
|
|
|
|
++new_var;
|
|
|
|
|
|
} else {
|
2016-03-12 06:32:56 -08:00
|
|
|
|
result.push_back(kNoBooleanVariable);
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
|
void SatPresolver::LoadProblemIntoSatSolver(SatSolver* solver) {
|
2015-07-06 19:23:12 +02:00
|
|
|
|
// Cleanup some memory that is not needed anymore. Note that we do need
|
|
|
|
|
|
// literal_to_clause_sizes_ for VariableMapping() to work.
|
|
|
|
|
|
var_pq_.Clear();
|
|
|
|
|
|
var_pq_elements_.clear();
|
|
|
|
|
|
in_clause_to_process_.clear();
|
|
|
|
|
|
clause_to_process_.clear();
|
|
|
|
|
|
literal_to_clauses_.clear();
|
2019-03-12 17:41:14 +01:00
|
|
|
|
signatures_.clear();
|
2014-11-07 14:30:26 +00:00
|
|
|
|
|
2024-07-12 13:56:11 +02:00
|
|
|
|
const util_intops::StrongVector<BooleanVariable, BooleanVariable> mapping =
|
2018-06-26 21:24:10 +02:00
|
|
|
|
VariableMapping();
|
2014-11-07 14:30:26 +00:00
|
|
|
|
int new_size = 0;
|
2016-03-12 06:32:56 -08:00
|
|
|
|
for (BooleanVariable index : mapping) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (index != kNoBooleanVariable) ++new_size;
|
2014-11-07 14:30:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<Literal> temp;
|
|
|
|
|
|
solver->SetNumVariables(new_size);
|
2020-10-28 13:42:36 +01:00
|
|
|
|
for (std::vector<Literal>& clause_ref : clauses_) {
|
2014-11-07 14:30:26 +00:00
|
|
|
|
temp.clear();
|
2015-07-06 19:23:12 +02:00
|
|
|
|
for (Literal l : clause_ref) {
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK_NE(mapping[l.Variable()], kNoBooleanVariable);
|
2014-11-07 14:30:26 +00:00
|
|
|
|
temp.push_back(Literal(mapping[l.Variable()], l.IsPositive()));
|
|
|
|
|
|
}
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (!temp.empty()) solver->AddProblemClause(temp);
|
2018-04-11 13:00:30 +02:00
|
|
|
|
gtl::STLClearObject(&clause_ref);
|
2014-11-07 14:30:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-07-24 18:12:50 +00:00
|
|
|
|
bool SatPresolver::ProcessAllClauses() {
|
2019-10-24 18:24:11 +02:00
|
|
|
|
int num_skipped_checks = 0;
|
|
|
|
|
|
const int kCheckFrequency = 1000;
|
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
|
// Because on large problem we don't have a budget to process all clauses,
|
|
|
|
|
|
// lets start by the smallest ones first.
|
2023-12-04 15:06:08 +01:00
|
|
|
|
std::stable_sort(clause_to_process_.begin(), clause_to_process_.end(),
|
|
|
|
|
|
[this](ClauseIndex c1, ClauseIndex c2) {
|
|
|
|
|
|
return clauses_[c1].size() < clauses_[c2].size();
|
|
|
|
|
|
});
|
2014-07-24 18:12:50 +00:00
|
|
|
|
while (!clause_to_process_.empty()) {
|
|
|
|
|
|
const ClauseIndex ci = clause_to_process_.front();
|
|
|
|
|
|
in_clause_to_process_[ci] = false;
|
|
|
|
|
|
clause_to_process_.pop_front();
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (!ProcessClauseToSimplifyOthers(ci)) return false;
|
2019-10-24 18:24:11 +02:00
|
|
|
|
if (++num_skipped_checks >= kCheckFrequency) {
|
2020-03-19 00:24:10 +01:00
|
|
|
|
if (num_inspected_signatures_ + num_inspected_literals_ > 1e9) {
|
2019-10-24 18:24:11 +02:00
|
|
|
|
VLOG(1) << "Aborting ProcessAllClauses() because work limit has been "
|
|
|
|
|
|
"reached";
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (time_limit_ != nullptr && time_limit_->LimitReached()) return true;
|
2019-10-24 18:24:11 +02:00
|
|
|
|
num_skipped_checks = 0;
|
|
|
|
|
|
}
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool SatPresolver::Presolve() {
|
2017-07-20 11:30:17 -07:00
|
|
|
|
// This is slighlty inefficient, but the presolve algorithm is
|
|
|
|
|
|
// a lot more costly anyway.
|
|
|
|
|
|
std::vector<bool> can_be_removed(NumVariables(), true);
|
|
|
|
|
|
return Presolve(can_be_removed);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-10 11:40:28 +01:00
|
|
|
|
bool SatPresolver::Presolve(const std::vector<bool>& can_be_removed) {
|
2014-07-24 18:12:50 +00:00
|
|
|
|
WallTimer timer;
|
|
|
|
|
|
timer.Start();
|
2021-03-10 11:40:28 +01:00
|
|
|
|
|
|
|
|
|
|
if (logger_->LoggingIsEnabled()) {
|
2021-03-04 18:26:01 +01:00
|
|
|
|
int64_t num_removable = 0;
|
2020-05-06 18:22:10 +02:00
|
|
|
|
for (const bool b : can_be_removed) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (b) ++num_removable;
|
2020-05-06 18:22:10 +02:00
|
|
|
|
}
|
2021-03-11 21:07:17 +01:00
|
|
|
|
SOLVER_LOG(logger_,
|
|
|
|
|
|
"[SAT presolve] num removable Booleans: ", num_removable, " / ",
|
2021-03-10 11:40:28 +01:00
|
|
|
|
can_be_removed.size());
|
2021-03-11 21:07:17 +01:00
|
|
|
|
SOLVER_LOG(logger_,
|
|
|
|
|
|
"[SAT presolve] num trivial clauses: ", num_trivial_clauses_);
|
2020-05-06 18:22:10 +02:00
|
|
|
|
DisplayStats(0);
|
|
|
|
|
|
}
|
2014-07-24 18:12:50 +00:00
|
|
|
|
|
|
|
|
|
|
// TODO(user): When a clause is strengthened, add it to a queue so it can
|
|
|
|
|
|
// be processed again?
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (!ProcessAllClauses()) return false;
|
2021-03-10 11:40:28 +01:00
|
|
|
|
if (logger_->LoggingIsEnabled()) DisplayStats(timer.Get());
|
2014-07-24 18:12:50 +00:00
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (time_limit_ != nullptr && time_limit_->LimitReached()) return true;
|
|
|
|
|
|
if (num_inspected_signatures_ + num_inspected_literals_ > 1e9) return true;
|
2019-10-24 18:24:11 +02:00
|
|
|
|
|
2014-07-24 18:12:50 +00:00
|
|
|
|
InitializePriorityQueue();
|
|
|
|
|
|
while (var_pq_.Size() > 0) {
|
2017-07-20 11:30:17 -07:00
|
|
|
|
const BooleanVariable var = var_pq_.Top()->variable;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
var_pq_.Pop();
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (!can_be_removed[var.value()]) continue;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
if (CrossProduct(Literal(var, true))) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (!ProcessAllClauses()) return false;
|
2025-05-27 13:49:29 +02:00
|
|
|
|
|
|
|
|
|
|
if (num_deleted_literals_since_last_cleanup_ > 1e7) {
|
|
|
|
|
|
RebuildLiteralToClauses();
|
|
|
|
|
|
}
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (time_limit_ != nullptr && time_limit_->LimitReached()) return true;
|
|
|
|
|
|
if (num_inspected_signatures_ + num_inspected_literals_ > 1e9) return true;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
2021-03-10 11:40:28 +01:00
|
|
|
|
if (logger_->LoggingIsEnabled()) DisplayStats(timer.Get());
|
2016-06-02 13:19:10 +02:00
|
|
|
|
|
|
|
|
|
|
// We apply BVA after a pass of the other algorithms.
|
|
|
|
|
|
if (parameters_.presolve_use_bva()) {
|
|
|
|
|
|
PresolveWithBva();
|
2021-03-10 11:40:28 +01:00
|
|
|
|
if (logger_->LoggingIsEnabled()) DisplayStats(timer.Get());
|
2016-06-02 13:19:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-07-24 18:12:50 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-04 15:06:08 +01:00
|
|
|
|
// TODO(user): Put work limit in place !
|
2016-06-02 13:19:10 +02:00
|
|
|
|
void SatPresolver::PresolveWithBva() {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
var_pq_elements_.clear(); // so we don't update it.
|
2016-06-02 13:19:10 +02:00
|
|
|
|
InitializeBvaPriorityQueue();
|
|
|
|
|
|
while (bva_pq_.Size() > 0) {
|
|
|
|
|
|
const LiteralIndex lit = bva_pq_.Top()->literal;
|
|
|
|
|
|
bva_pq_.Pop();
|
|
|
|
|
|
SimpleBva(lit);
|
2023-12-04 15:06:08 +01:00
|
|
|
|
if (time_limit_ != nullptr && time_limit_->LimitReached()) break;
|
2016-06-02 13:19:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-08-15 23:11:16 +07:00
|
|
|
|
// We use the same notation as in the article mentioned in the .h
|
2016-06-02 13:19:10 +02:00
|
|
|
|
void SatPresolver::SimpleBva(LiteralIndex l) {
|
2018-01-15 10:41:09 +01:00
|
|
|
|
literal_to_p_size_.resize(literal_to_clauses_.size(), 0);
|
|
|
|
|
|
DCHECK(std::all_of(literal_to_p_size_.begin(), literal_to_p_size_.end(),
|
2020-10-22 23:36:58 +02:00
|
|
|
|
[](int v) { return v == 0; }));
|
2018-01-15 10:41:09 +01:00
|
|
|
|
|
2016-06-02 13:19:10 +02:00
|
|
|
|
// We will try to add a literal to m_lit_ and take a subset of m_cls_ such
|
|
|
|
|
|
// that |m_lit_| * |m_cls_| - |m_lit_| - |m_cls_| is maximized.
|
2020-10-22 23:36:58 +02:00
|
|
|
|
m_lit_ = {l};
|
2016-06-02 13:19:10 +02:00
|
|
|
|
m_cls_ = literal_to_clauses_[l];
|
|
|
|
|
|
|
|
|
|
|
|
int reduction = 0;
|
2023-12-04 15:06:08 +01:00
|
|
|
|
for (int loop = 0; loop < 100; ++loop) {
|
2018-01-15 10:41:09 +01:00
|
|
|
|
LiteralIndex lmax = kNoLiteralIndex;
|
|
|
|
|
|
int max_size = 0;
|
|
|
|
|
|
|
|
|
|
|
|
flattened_p_.clear();
|
2016-06-02 13:19:10 +02:00
|
|
|
|
for (const ClauseIndex c : m_cls_) {
|
2020-10-28 13:42:36 +01:00
|
|
|
|
const std::vector<Literal>& clause = clauses_[c];
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (clause.empty()) continue; // It has been deleted.
|
2016-06-02 13:19:10 +02:00
|
|
|
|
|
|
|
|
|
|
// Find a literal different from l that occur in the less number of
|
|
|
|
|
|
// clauses.
|
|
|
|
|
|
const LiteralIndex l_min =
|
2017-06-01 16:28:29 +02:00
|
|
|
|
FindLiteralWithShortestOccurrenceListExcluding(clause, Literal(l));
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (l_min == kNoLiteralIndex) continue;
|
2016-06-02 13:19:10 +02:00
|
|
|
|
|
|
|
|
|
|
// Find all the clauses of the form "clause \ {l} + {l'}", for a literal
|
|
|
|
|
|
// l' that is not in the clause.
|
|
|
|
|
|
for (const ClauseIndex d : literal_to_clauses_[l_min]) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (clause.size() != clauses_[d].size()) continue;
|
2016-06-02 13:19:10 +02:00
|
|
|
|
const LiteralIndex l_diff =
|
|
|
|
|
|
DifferAtGivenLiteral(clause, clauses_[d], Literal(l));
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (l_diff == kNoLiteralIndex || m_lit_.count(l_diff) > 0) continue;
|
2016-06-02 13:19:10 +02:00
|
|
|
|
if (l_diff == Literal(l).NegatedIndex()) {
|
|
|
|
|
|
// Self-subsumbtion!
|
|
|
|
|
|
//
|
|
|
|
|
|
// TODO(user): Not sure this can happen after the presolve we did
|
|
|
|
|
|
// before calling SimpleBva().
|
|
|
|
|
|
VLOG(1) << "self-subsumbtion";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
|
flattened_p_.push_back({l_diff, c});
|
2018-01-15 10:41:09 +01:00
|
|
|
|
const int new_size = ++literal_to_p_size_[l_diff];
|
|
|
|
|
|
if (new_size > max_size) {
|
|
|
|
|
|
lmax = l_diff;
|
|
|
|
|
|
max_size = new_size;
|
|
|
|
|
|
}
|
2016-06-02 13:19:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (lmax == kNoLiteralIndex) break;
|
2016-06-02 13:19:10 +02:00
|
|
|
|
const int new_m_lit_size = m_lit_.size() + 1;
|
2018-01-15 10:41:09 +01:00
|
|
|
|
const int new_m_cls_size = max_size;
|
2016-06-02 13:19:10 +02:00
|
|
|
|
const int new_reduction =
|
|
|
|
|
|
new_m_lit_size * new_m_cls_size - new_m_cls_size - new_m_lit_size;
|
2018-01-15 10:41:09 +01:00
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (new_reduction <= reduction) break;
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK_NE(1, new_m_lit_size);
|
|
|
|
|
|
DCHECK_NE(1, new_m_cls_size);
|
2016-06-02 13:19:10 +02:00
|
|
|
|
|
|
|
|
|
|
// TODO(user): Instead of looping and recomputing p_ again, we can instead
|
|
|
|
|
|
// simply intersect the clause indices in p_. This should be a lot faster.
|
|
|
|
|
|
// That said, we loop again only when we have a reduction, so this happens
|
|
|
|
|
|
// not that often compared to the initial computation of p.
|
|
|
|
|
|
reduction = new_reduction;
|
|
|
|
|
|
m_lit_.insert(lmax);
|
2018-01-15 10:41:09 +01:00
|
|
|
|
|
|
|
|
|
|
// Set m_cls_ to p_[lmax].
|
|
|
|
|
|
m_cls_.clear();
|
2022-02-24 14:17:43 +01:00
|
|
|
|
for (const auto& entry : flattened_p_) {
|
2018-01-15 10:41:09 +01:00
|
|
|
|
literal_to_p_size_[entry.first] = 0;
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (entry.first == lmax) m_cls_.push_back(entry.second);
|
2018-01-15 10:41:09 +01:00
|
|
|
|
}
|
|
|
|
|
|
flattened_p_.clear();
|
2016-06-02 13:19:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-01-15 10:41:09 +01:00
|
|
|
|
// Make sure literal_to_p_size_ is all zero.
|
2022-02-24 14:17:43 +01:00
|
|
|
|
for (const auto& entry : flattened_p_) literal_to_p_size_[entry.first] = 0;
|
2018-01-15 10:41:09 +01:00
|
|
|
|
flattened_p_.clear();
|
|
|
|
|
|
|
2016-06-02 13:19:10 +02:00
|
|
|
|
// A strictly positive reduction means that applying the BVA transform will
|
|
|
|
|
|
// reduce the overall number of clauses by that much. Here we can control
|
|
|
|
|
|
// what kind of reduction we want to apply.
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (reduction <= parameters_.presolve_bva_threshold()) return;
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK_GT(m_lit_.size(), 1);
|
2016-06-02 13:19:10 +02:00
|
|
|
|
|
|
|
|
|
|
// Create a new variable.
|
|
|
|
|
|
const int old_size = literal_to_clauses_.size();
|
|
|
|
|
|
const LiteralIndex x_true = LiteralIndex(old_size);
|
|
|
|
|
|
const LiteralIndex x_false = LiteralIndex(old_size + 1);
|
|
|
|
|
|
literal_to_clauses_.resize(old_size + 2);
|
|
|
|
|
|
literal_to_clause_sizes_.resize(old_size + 2);
|
|
|
|
|
|
bva_pq_elements_.resize(old_size + 2);
|
|
|
|
|
|
bva_pq_elements_[x_true.value()].literal = x_true;
|
|
|
|
|
|
bva_pq_elements_[x_false.value()].literal = x_false;
|
|
|
|
|
|
|
|
|
|
|
|
// Add the new clauses.
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (drat_proof_handler_ != nullptr) drat_proof_handler_->AddOneVariable();
|
2016-06-02 13:19:10 +02:00
|
|
|
|
for (const LiteralIndex lit : m_lit_) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
tmp_new_clause_ = {Literal(lit), Literal(x_true)};
|
2016-06-02 13:19:10 +02:00
|
|
|
|
AddClauseInternal(&tmp_new_clause_);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const ClauseIndex ci : m_cls_) {
|
|
|
|
|
|
tmp_new_clause_ = clauses_[ci];
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK(!tmp_new_clause_.empty());
|
2020-10-28 13:42:36 +01:00
|
|
|
|
for (Literal& ref : tmp_new_clause_) {
|
2016-06-02 13:19:10 +02:00
|
|
|
|
if (ref.Index() == l) {
|
|
|
|
|
|
ref = Literal(x_false);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO(user): we can be more efficient here since the clause used to
|
|
|
|
|
|
// derive this one is already sorted. We just need to insert x_false in
|
|
|
|
|
|
// the correct place and remove l.
|
|
|
|
|
|
std::sort(tmp_new_clause_.begin(), tmp_new_clause_.end());
|
|
|
|
|
|
AddClauseInternal(&tmp_new_clause_);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Delete the old clauses.
|
|
|
|
|
|
//
|
|
|
|
|
|
// TODO(user): do that more efficiently? we can simply store the clause d
|
|
|
|
|
|
// instead of finding it again. That said, this is executed only when a
|
|
|
|
|
|
// reduction occur, whereas the start of this function occur all the time, so
|
|
|
|
|
|
// we want it to be as fast as possible.
|
|
|
|
|
|
for (const ClauseIndex c : m_cls_) {
|
2020-10-28 13:42:36 +01:00
|
|
|
|
const std::vector<Literal>& clause = clauses_[c];
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK(!clause.empty());
|
2016-06-02 13:19:10 +02:00
|
|
|
|
const LiteralIndex l_min =
|
2017-06-01 16:28:29 +02:00
|
|
|
|
FindLiteralWithShortestOccurrenceListExcluding(clause, Literal(l));
|
2016-06-02 13:19:10 +02:00
|
|
|
|
for (const LiteralIndex lit : m_lit_) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (lit == l) continue;
|
2016-06-02 13:19:10 +02:00
|
|
|
|
for (const ClauseIndex d : literal_to_clauses_[l_min]) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (clause.size() != clauses_[d].size()) continue;
|
2016-06-02 13:19:10 +02:00
|
|
|
|
const LiteralIndex l_diff =
|
|
|
|
|
|
DifferAtGivenLiteral(clause, clauses_[d], Literal(l));
|
|
|
|
|
|
if (l_diff == lit) {
|
|
|
|
|
|
Remove(d);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
Remove(c);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Add these elements to the priority queue.
|
|
|
|
|
|
//
|
|
|
|
|
|
// TODO(user): It seems some of the element already processed could benefit
|
|
|
|
|
|
// from being processed again by SimpleBva(). It is unclear if it is worth the
|
|
|
|
|
|
// extra time though.
|
|
|
|
|
|
AddToBvaPriorityQueue(x_true);
|
|
|
|
|
|
AddToBvaPriorityQueue(x_false);
|
|
|
|
|
|
AddToBvaPriorityQueue(l);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-04 18:26:01 +01:00
|
|
|
|
uint64_t SatPresolver::ComputeSignatureOfClauseVariables(ClauseIndex ci) {
|
|
|
|
|
|
uint64_t signature = 0;
|
2019-03-12 17:41:14 +01:00
|
|
|
|
for (const Literal l : clauses_[ci]) {
|
2021-03-04 18:26:01 +01:00
|
|
|
|
signature |= (uint64_t{1} << (l.Variable().value() % 64));
|
2019-03-12 17:41:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
DCHECK_EQ(signature == 0, clauses_[ci].empty());
|
|
|
|
|
|
return signature;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// We are looking for clause that contains lit and contains a superset of the
|
|
|
|
|
|
// literals in clauses_[clauses_index] or a superset with just one literal of
|
|
|
|
|
|
// clauses_[clause_index] negated.
|
|
|
|
|
|
bool SatPresolver::ProcessClauseToSimplifyOthersUsingLiteral(
|
|
|
|
|
|
ClauseIndex clause_index, Literal lit) {
|
2020-10-28 13:42:36 +01:00
|
|
|
|
const std::vector<Literal>& clause = clauses_[clause_index];
|
2021-03-04 18:26:01 +01:00
|
|
|
|
const uint64_t clause_signature = signatures_[clause_index];
|
2019-03-12 17:41:14 +01:00
|
|
|
|
LiteralIndex opposite_literal;
|
|
|
|
|
|
|
|
|
|
|
|
// Try to simplify the clauses containing 'lit'. We take advantage of this
|
|
|
|
|
|
// loop to also detect if there is any empty clause, in which case we will
|
|
|
|
|
|
// trigger a "cleaning" below.
|
|
|
|
|
|
bool need_cleaning = false;
|
2023-10-12 10:06:27 +02:00
|
|
|
|
num_inspected_signatures_ += literal_to_clauses_[lit].size();
|
|
|
|
|
|
for (const ClauseIndex ci : literal_to_clauses_[lit]) {
|
2021-03-04 18:26:01 +01:00
|
|
|
|
const uint64_t ci_signature = signatures_[ci];
|
2019-03-12 17:41:14 +01:00
|
|
|
|
|
|
|
|
|
|
// This allows to check for empty clause without fetching the memory at
|
|
|
|
|
|
// clause_[ci]. It can have a huge time impact on large problems.
|
|
|
|
|
|
DCHECK_EQ(ci_signature, ComputeSignatureOfClauseVariables(ci));
|
|
|
|
|
|
if (ci_signature == 0) {
|
|
|
|
|
|
need_cleaning = true;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Note that SimplifyClause() can return true only if the variables in
|
|
|
|
|
|
// 'a' are a subset of the one in 'b'. We use the signatures to abort
|
|
|
|
|
|
// early as a speed optimization.
|
|
|
|
|
|
if (ci != clause_index && (clause_signature & ~ci_signature) == 0 &&
|
2020-03-19 00:24:10 +01:00
|
|
|
|
SimplifyClause(clause, &clauses_[ci], &opposite_literal,
|
|
|
|
|
|
&num_inspected_literals_)) {
|
2019-03-12 17:41:14 +01:00
|
|
|
|
if (opposite_literal == kNoLiteralIndex) {
|
|
|
|
|
|
need_cleaning = true;
|
|
|
|
|
|
Remove(ci);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
DCHECK_NE(opposite_literal, lit.Index());
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (clauses_[ci].empty()) return false; // UNSAT.
|
2019-03-12 17:41:14 +01:00
|
|
|
|
if (drat_proof_handler_ != nullptr) {
|
|
|
|
|
|
// TODO(user): remove the old clauses_[ci] afterwards.
|
|
|
|
|
|
drat_proof_handler_->AddClause(clauses_[ci]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Recompute signature.
|
|
|
|
|
|
signatures_[ci] = ComputeSignatureOfClauseVariables(ci);
|
|
|
|
|
|
|
|
|
|
|
|
// Remove ci from the occurrence list. Note that opposite_literal
|
|
|
|
|
|
// cannot be literal or literal.Negated().
|
|
|
|
|
|
gtl::STLEraseAllFromSequence(&literal_to_clauses_[opposite_literal],
|
|
|
|
|
|
ci);
|
|
|
|
|
|
--literal_to_clause_sizes_[opposite_literal];
|
|
|
|
|
|
UpdatePriorityQueue(Literal(opposite_literal).Variable());
|
|
|
|
|
|
|
|
|
|
|
|
if (!in_clause_to_process_[ci]) {
|
|
|
|
|
|
in_clause_to_process_[ci] = true;
|
|
|
|
|
|
clause_to_process_.push_back(ci);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (need_cleaning) {
|
|
|
|
|
|
int new_index = 0;
|
2023-10-12 10:06:27 +02:00
|
|
|
|
auto& occurrence_list_ref = literal_to_clauses_[lit];
|
2019-03-12 17:41:14 +01:00
|
|
|
|
for (const ClauseIndex ci : occurrence_list_ref) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (signatures_[ci] != 0) occurrence_list_ref[new_index++] = ci;
|
2019-03-12 17:41:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
occurrence_list_ref.resize(new_index);
|
2023-10-12 10:06:27 +02:00
|
|
|
|
DCHECK_EQ(literal_to_clause_sizes_[lit], new_index);
|
2019-03-12 17:41:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-07-24 18:12:50 +00:00
|
|
|
|
// TODO(user): Binary clauses are really common, and we can probably do this
|
|
|
|
|
|
// more efficiently for them. For instance, we could just take the intersection
|
|
|
|
|
|
// of two sorted lists to get the simplified clauses.
|
|
|
|
|
|
bool SatPresolver::ProcessClauseToSimplifyOthers(ClauseIndex clause_index) {
|
2020-10-28 13:42:36 +01:00
|
|
|
|
const std::vector<Literal>& clause = clauses_[clause_index];
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (clause.empty()) return true;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
DCHECK(std::is_sorted(clause.begin(), clause.end()));
|
|
|
|
|
|
|
|
|
|
|
|
LiteralIndex opposite_literal;
|
2017-06-01 16:28:29 +02:00
|
|
|
|
const Literal lit = FindLiteralWithShortestOccurrenceList(clause);
|
2014-07-24 18:12:50 +00:00
|
|
|
|
|
2019-03-12 17:41:14 +01:00
|
|
|
|
if (!ProcessClauseToSimplifyOthersUsingLiteral(clause_index, lit)) {
|
|
|
|
|
|
return false;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-03-12 17:41:14 +01:00
|
|
|
|
// If there is another "short" occurrence list, use it. Otherwise use the
|
|
|
|
|
|
// one corresponding to the negation of lit.
|
|
|
|
|
|
const LiteralIndex other_lit_index =
|
|
|
|
|
|
FindLiteralWithShortestOccurrenceListExcluding(clause, lit);
|
|
|
|
|
|
if (other_lit_index != kNoLiteralIndex &&
|
|
|
|
|
|
literal_to_clause_sizes_[other_lit_index] <
|
|
|
|
|
|
literal_to_clause_sizes_[lit.NegatedIndex()]) {
|
|
|
|
|
|
return ProcessClauseToSimplifyOthersUsingLiteral(clause_index,
|
|
|
|
|
|
Literal(other_lit_index));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Treat the clauses containing lit.Negated().
|
2014-07-24 18:12:50 +00:00
|
|
|
|
int new_index = 0;
|
|
|
|
|
|
bool something_removed = false;
|
2020-10-28 13:42:36 +01:00
|
|
|
|
auto& occurrence_list_ref = literal_to_clauses_[lit.NegatedIndex()];
|
2021-03-04 18:26:01 +01:00
|
|
|
|
const uint64_t clause_signature = signatures_[clause_index];
|
2019-03-12 17:41:14 +01:00
|
|
|
|
for (const ClauseIndex ci : occurrence_list_ref) {
|
2021-03-04 18:26:01 +01:00
|
|
|
|
const uint64_t ci_signature = signatures_[ci];
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK_EQ(ci_signature, ComputeSignatureOfClauseVariables(ci));
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (ci_signature == 0) continue;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
|
|
|
|
|
|
// TODO(user): not super optimal since we could abort earlier if
|
2019-03-12 17:41:14 +01:00
|
|
|
|
// opposite_literal is not the negation of shortest_list. Note that this
|
|
|
|
|
|
// applies to the second call to
|
|
|
|
|
|
// ProcessClauseToSimplifyOthersUsingLiteral() above too.
|
|
|
|
|
|
if ((clause_signature & ~ci_signature) == 0 &&
|
2020-03-19 00:24:10 +01:00
|
|
|
|
SimplifyClause(clause, &clauses_[ci], &opposite_literal,
|
|
|
|
|
|
&num_inspected_literals_)) {
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK_EQ(opposite_literal, lit.NegatedIndex());
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (clauses_[ci].empty()) return false; // UNSAT.
|
2018-05-03 15:00:06 +02:00
|
|
|
|
if (drat_proof_handler_ != nullptr) {
|
2016-06-02 13:19:10 +02:00
|
|
|
|
// TODO(user): remove the old clauses_[ci] afterwards.
|
2018-05-03 15:00:06 +02:00
|
|
|
|
drat_proof_handler_->AddClause(clauses_[ci]);
|
2016-06-02 13:19:10 +02:00
|
|
|
|
}
|
2019-03-12 17:41:14 +01:00
|
|
|
|
|
|
|
|
|
|
// Recompute signature.
|
|
|
|
|
|
signatures_[ci] = ComputeSignatureOfClauseVariables(ci);
|
|
|
|
|
|
|
2014-07-24 18:12:50 +00:00
|
|
|
|
if (!in_clause_to_process_[ci]) {
|
|
|
|
|
|
in_clause_to_process_[ci] = true;
|
|
|
|
|
|
clause_to_process_.push_back(ci);
|
|
|
|
|
|
}
|
|
|
|
|
|
something_removed = true;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2017-06-01 16:28:29 +02:00
|
|
|
|
occurrence_list_ref[new_index] = ci;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
++new_index;
|
|
|
|
|
|
}
|
2017-06-01 16:28:29 +02:00
|
|
|
|
occurrence_list_ref.resize(new_index);
|
2014-11-07 14:30:26 +00:00
|
|
|
|
literal_to_clause_sizes_[lit.NegatedIndex()] = new_index;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
if (something_removed) {
|
|
|
|
|
|
UpdatePriorityQueue(Literal(lit.NegatedIndex()).Variable());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-27 13:49:29 +02:00
|
|
|
|
void SatPresolver::RemoveAllClauseContaining(Literal x,
|
|
|
|
|
|
bool register_for_postsolve) {
|
|
|
|
|
|
if (register_for_postsolve) {
|
|
|
|
|
|
for (ClauseIndex i : literal_to_clauses_[x]) {
|
|
|
|
|
|
if (!clauses_[i].empty()) {
|
|
|
|
|
|
RemoveAndRegisterForPostsolve(i, x);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
for (ClauseIndex i : literal_to_clauses_[x]) {
|
|
|
|
|
|
if (!clauses_[i].empty()) Remove(i);
|
|
|
|
|
|
}
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
2023-10-12 10:06:27 +02:00
|
|
|
|
gtl::STLClearObject(&literal_to_clauses_[x]);
|
|
|
|
|
|
literal_to_clause_sizes_[x] = 0;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool SatPresolver::CrossProduct(Literal x) {
|
2023-10-12 10:06:27 +02:00
|
|
|
|
const int s1 = literal_to_clause_sizes_[x];
|
2014-11-07 14:30:26 +00:00
|
|
|
|
const int s2 = literal_to_clause_sizes_[x.NegatedIndex()];
|
2014-07-24 18:12:50 +00:00
|
|
|
|
|
2021-03-10 11:40:28 +01:00
|
|
|
|
// Note that if s1 or s2 is equal to 0, this function will implicitly just
|
2014-07-24 18:12:50 +00:00
|
|
|
|
// fix the variable x.
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (s1 == 0 && s2 == 0) return false;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
|
2014-11-07 14:30:26 +00:00
|
|
|
|
// Heuristic. Abort if the work required to decide if x should be removed
|
|
|
|
|
|
// seems to big.
|
|
|
|
|
|
if (s1 > 1 && s2 > 1 && s1 * s2 > parameters_.presolve_bve_threshold()) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Compute the threshold under which we don't remove x.Variable().
|
2025-05-27 13:49:29 +02:00
|
|
|
|
int num_clauses = 0;
|
|
|
|
|
|
int64_t sum_for_x = 0;
|
|
|
|
|
|
int64_t sum_for_not_x = 0;
|
2014-11-07 14:30:26 +00:00
|
|
|
|
const int clause_weight = parameters_.presolve_bve_clause_weight();
|
2023-10-12 10:06:27 +02:00
|
|
|
|
for (ClauseIndex i : literal_to_clauses_[x]) {
|
2014-11-07 14:30:26 +00:00
|
|
|
|
if (!clauses_[i].empty()) {
|
2025-05-27 13:49:29 +02:00
|
|
|
|
++num_clauses;
|
|
|
|
|
|
sum_for_x += clauses_[i].size();
|
2014-11-07 14:30:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
for (ClauseIndex i : literal_to_clauses_[x.NegatedIndex()]) {
|
|
|
|
|
|
if (!clauses_[i].empty()) {
|
2025-05-27 13:49:29 +02:00
|
|
|
|
++num_clauses;
|
|
|
|
|
|
sum_for_not_x += clauses_[i].size();
|
2014-11-07 14:30:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-27 13:49:29 +02:00
|
|
|
|
const int64_t threshold =
|
|
|
|
|
|
clause_weight * num_clauses + sum_for_x + sum_for_not_x;
|
2014-11-07 14:30:26 +00:00
|
|
|
|
|
|
|
|
|
|
// For the BCE, we prefer s2 to be small.
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (s1 < s2) x = x.Negated();
|
2014-11-07 14:30:26 +00:00
|
|
|
|
|
|
|
|
|
|
// Test whether we should remove the x.Variable().
|
|
|
|
|
|
int size = 0;
|
2023-10-12 10:06:27 +02:00
|
|
|
|
for (ClauseIndex i : literal_to_clauses_[x]) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (clauses_[i].empty()) continue;
|
2014-11-07 14:30:26 +00:00
|
|
|
|
bool no_resolvant = true;
|
|
|
|
|
|
for (ClauseIndex j : literal_to_clauses_[x.NegatedIndex()]) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (clauses_[j].empty()) continue;
|
2015-07-06 19:23:12 +02:00
|
|
|
|
const int rs = ComputeResolvantSize(x, clauses_[i], clauses_[j]);
|
|
|
|
|
|
if (rs >= 0) {
|
2014-11-07 14:30:26 +00:00
|
|
|
|
no_resolvant = false;
|
2015-07-06 19:23:12 +02:00
|
|
|
|
size += clause_weight + rs;
|
2014-11-07 14:30:26 +00:00
|
|
|
|
|
|
|
|
|
|
// Abort early if the "size" become too big.
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (size > threshold) return false;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-07-20 11:30:17 -07:00
|
|
|
|
if (no_resolvant && parameters_.presolve_blocked_clause()) {
|
2014-11-07 14:30:26 +00:00
|
|
|
|
// This is an incomplete heuristic for blocked clause detection. Here,
|
|
|
|
|
|
// the clause i is "blocked", so we can remove it. Note that the code
|
|
|
|
|
|
// below already do that if we decide to eliminate x.
|
|
|
|
|
|
//
|
|
|
|
|
|
// For more details, see the paper "Blocked clause elimination", Matti
|
|
|
|
|
|
// Jarvisalo, Armin Biere, Marijn Heule. TACAS, volume 6015 of Lecture
|
2018-10-31 16:18:18 +01:00
|
|
|
|
// Notes in Computer Science, pages 129–144. Springer, 2010.
|
2014-11-07 14:30:26 +00:00
|
|
|
|
//
|
|
|
|
|
|
// TODO(user): Choose if we use x or x.Negated() depending on the list
|
|
|
|
|
|
// sizes? The function achieve the same if x = x.Negated(), however the
|
|
|
|
|
|
// loops are not done in the same order which may change this incomplete
|
|
|
|
|
|
// "blocked" clause detection.
|
|
|
|
|
|
RemoveAndRegisterForPostsolve(i, x);
|
|
|
|
|
|
}
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Add all the resolvant clauses.
|
2014-11-07 14:30:26 +00:00
|
|
|
|
// Note that the variable priority queue will only be updated during the
|
|
|
|
|
|
// deletion.
|
2015-07-06 19:23:12 +02:00
|
|
|
|
std::vector<Literal> temp;
|
2023-10-12 10:06:27 +02:00
|
|
|
|
for (ClauseIndex i : literal_to_clauses_[x]) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (clauses_[i].empty()) continue;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
for (ClauseIndex j : literal_to_clauses_[x.NegatedIndex()]) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (clauses_[j].empty()) continue;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
if (ComputeResolvant(x, clauses_[i], clauses_[j], &temp)) {
|
2014-11-07 14:30:26 +00:00
|
|
|
|
AddClauseInternal(&temp);
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Deletes the old clauses.
|
2014-11-07 14:30:26 +00:00
|
|
|
|
//
|
|
|
|
|
|
// TODO(user): We could only update the priority queue once for each variable
|
|
|
|
|
|
// instead of doing it many times.
|
2025-05-27 13:49:29 +02:00
|
|
|
|
bool push_x_for_postsolve = true;
|
|
|
|
|
|
bool push_not_x_for_postsolve = true;
|
|
|
|
|
|
if (parameters_.filter_sat_postsolve_clauses()) {
|
|
|
|
|
|
if (sum_for_x <= sum_for_not_x) {
|
|
|
|
|
|
push_not_x_for_postsolve = false;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
push_x_for_postsolve = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
RemoveAllClauseContaining(x, push_x_for_postsolve);
|
|
|
|
|
|
RemoveAllClauseContaining(x.Negated(), push_not_x_for_postsolve);
|
2014-07-24 18:12:50 +00:00
|
|
|
|
|
|
|
|
|
|
// TODO(user): At this point x.Variable() is added back to the priority queue.
|
|
|
|
|
|
// Avoid doing that.
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SatPresolver::Remove(ClauseIndex ci) {
|
2025-05-27 13:49:29 +02:00
|
|
|
|
num_deleted_literals_since_last_cleanup_ += clauses_[ci].size();
|
2019-03-12 17:41:14 +01:00
|
|
|
|
signatures_[ci] = 0;
|
2025-05-27 13:49:29 +02:00
|
|
|
|
for (const Literal e : clauses_[ci]) {
|
2023-10-12 10:06:27 +02:00
|
|
|
|
literal_to_clause_sizes_[e]--;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
UpdatePriorityQueue(e.Variable());
|
2016-06-02 13:19:10 +02:00
|
|
|
|
UpdateBvaPriorityQueue(Literal(e.Variable(), true).Index());
|
|
|
|
|
|
UpdateBvaPriorityQueue(Literal(e.Variable(), false).Index());
|
|
|
|
|
|
}
|
2018-05-03 15:00:06 +02:00
|
|
|
|
if (drat_proof_handler_ != nullptr) {
|
|
|
|
|
|
drat_proof_handler_->DeleteClause(clauses_[ci]);
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
2018-04-11 13:00:30 +02:00
|
|
|
|
gtl::STLClearObject(&clauses_[ci]);
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SatPresolver::RemoveAndRegisterForPostsolve(ClauseIndex ci, Literal x) {
|
2015-07-06 19:23:12 +02:00
|
|
|
|
postsolver_->Add(x, clauses_[ci]);
|
2016-06-02 13:19:10 +02:00
|
|
|
|
Remove(ci);
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-06-01 16:28:29 +02:00
|
|
|
|
Literal SatPresolver::FindLiteralWithShortestOccurrenceList(
|
2025-04-03 16:12:45 +02:00
|
|
|
|
absl::Span<const Literal> clause) {
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK(!clause.empty());
|
2014-07-24 18:12:50 +00:00
|
|
|
|
Literal result = clause.front();
|
2023-10-12 10:06:27 +02:00
|
|
|
|
int best_size = literal_to_clause_sizes_[result];
|
2015-07-06 19:23:12 +02:00
|
|
|
|
for (const Literal l : clause) {
|
2023-10-12 10:06:27 +02:00
|
|
|
|
const int size = literal_to_clause_sizes_[l];
|
2019-03-12 17:41:14 +01:00
|
|
|
|
if (size < best_size) {
|
2014-07-24 18:12:50 +00:00
|
|
|
|
result = l;
|
2019-03-12 17:41:14 +01:00
|
|
|
|
best_size = size;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-06-01 16:28:29 +02:00
|
|
|
|
LiteralIndex SatPresolver::FindLiteralWithShortestOccurrenceListExcluding(
|
2020-10-28 13:42:36 +01:00
|
|
|
|
const std::vector<Literal>& clause, Literal to_exclude) {
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK(!clause.empty());
|
2016-06-02 13:19:10 +02:00
|
|
|
|
LiteralIndex result = kNoLiteralIndex;
|
2017-06-01 16:28:29 +02:00
|
|
|
|
int num_occurrences = std::numeric_limits<int>::max();
|
2016-06-02 13:19:10 +02:00
|
|
|
|
for (const Literal l : clause) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (l == to_exclude) continue;
|
2023-10-12 10:06:27 +02:00
|
|
|
|
if (literal_to_clause_sizes_[l] < num_occurrences) {
|
2016-06-02 13:19:10 +02:00
|
|
|
|
result = l.Index();
|
2023-10-12 10:06:27 +02:00
|
|
|
|
num_occurrences = literal_to_clause_sizes_[l];
|
2016-06-02 13:19:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-03-12 06:32:56 -08:00
|
|
|
|
void SatPresolver::UpdatePriorityQueue(BooleanVariable var) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (var_pq_elements_.empty()) return; // not initialized.
|
2020-10-28 13:42:36 +01:00
|
|
|
|
PQElement* element = &var_pq_elements_[var];
|
2023-10-12 10:06:27 +02:00
|
|
|
|
element->weight = literal_to_clause_sizes_[Literal(var, true)] +
|
|
|
|
|
|
literal_to_clause_sizes_[Literal(var, false)];
|
2014-07-24 18:12:50 +00:00
|
|
|
|
if (var_pq_.Contains(element)) {
|
|
|
|
|
|
var_pq_.NoteChangedPriority(element);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
var_pq_.Add(element);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SatPresolver::InitializePriorityQueue() {
|
|
|
|
|
|
const int num_vars = NumVariables();
|
|
|
|
|
|
var_pq_elements_.resize(num_vars);
|
2016-03-12 06:32:56 -08:00
|
|
|
|
for (BooleanVariable var(0); var < num_vars; ++var) {
|
2020-10-28 13:42:36 +01:00
|
|
|
|
PQElement* element = &var_pq_elements_[var];
|
2014-07-24 18:12:50 +00:00
|
|
|
|
element->variable = var;
|
2023-10-12 10:06:27 +02:00
|
|
|
|
element->weight = literal_to_clause_sizes_[Literal(var, true)] +
|
|
|
|
|
|
literal_to_clause_sizes_[Literal(var, false)];
|
2014-07-24 18:12:50 +00:00
|
|
|
|
var_pq_.Add(element);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-02 13:19:10 +02:00
|
|
|
|
void SatPresolver::UpdateBvaPriorityQueue(LiteralIndex lit) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (bva_pq_elements_.empty()) return; // not initialized.
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK_LT(lit, bva_pq_elements_.size());
|
2020-10-28 13:42:36 +01:00
|
|
|
|
BvaPqElement* element = &bva_pq_elements_[lit.value()];
|
2016-06-02 13:19:10 +02:00
|
|
|
|
element->weight = literal_to_clause_sizes_[lit];
|
|
|
|
|
|
if (bva_pq_.Contains(element)) {
|
|
|
|
|
|
bva_pq_.NoteChangedPriority(element);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SatPresolver::AddToBvaPriorityQueue(LiteralIndex lit) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (bva_pq_elements_.empty()) return; // not initialized.
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK_LT(lit, bva_pq_elements_.size());
|
2020-10-28 13:42:36 +01:00
|
|
|
|
BvaPqElement* element = &bva_pq_elements_[lit.value()];
|
2016-06-02 13:19:10 +02:00
|
|
|
|
element->weight = literal_to_clause_sizes_[lit];
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK(!bva_pq_.Contains(element));
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (element->weight > 2) bva_pq_.Add(element);
|
2016-06-02 13:19:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SatPresolver::InitializeBvaPriorityQueue() {
|
|
|
|
|
|
const int num_literals = 2 * NumVariables();
|
|
|
|
|
|
bva_pq_.Clear();
|
|
|
|
|
|
bva_pq_elements_.assign(num_literals, BvaPqElement());
|
|
|
|
|
|
for (LiteralIndex lit(0); lit < num_literals; ++lit) {
|
2020-10-28 13:42:36 +01:00
|
|
|
|
BvaPqElement* element = &bva_pq_elements_[lit.value()];
|
2016-06-02 13:19:10 +02:00
|
|
|
|
element->literal = lit;
|
|
|
|
|
|
element->weight = literal_to_clause_sizes_[lit];
|
|
|
|
|
|
|
|
|
|
|
|
// If a literal occur only in two clauses, then there is no point calling
|
|
|
|
|
|
// SimpleBva() on it.
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (element->weight > 2) bva_pq_.Add(element);
|
2016-06-02 13:19:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-07-24 18:12:50 +00:00
|
|
|
|
void SatPresolver::DisplayStats(double elapsed_seconds) {
|
|
|
|
|
|
int num_literals = 0;
|
|
|
|
|
|
int num_clauses = 0;
|
|
|
|
|
|
int num_singleton_clauses = 0;
|
2020-10-28 13:42:36 +01:00
|
|
|
|
for (const std::vector<Literal>& c : clauses_) {
|
2014-07-24 18:12:50 +00:00
|
|
|
|
if (!c.empty()) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (c.size() == 1) ++num_singleton_clauses;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
++num_clauses;
|
|
|
|
|
|
num_literals += c.size();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
int num_one_side = 0;
|
|
|
|
|
|
int num_simple_definition = 0;
|
|
|
|
|
|
int num_vars = 0;
|
2016-03-12 06:32:56 -08:00
|
|
|
|
for (BooleanVariable var(0); var < NumVariables(); ++var) {
|
2023-10-12 10:06:27 +02:00
|
|
|
|
const int s1 = literal_to_clause_sizes_[Literal(var, true)];
|
|
|
|
|
|
const int s2 = literal_to_clause_sizes_[Literal(var, false)];
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (s1 == 0 && s2 == 0) continue;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
|
|
|
|
|
|
++num_vars;
|
|
|
|
|
|
if (s1 == 0 || s2 == 0) {
|
|
|
|
|
|
num_one_side++;
|
|
|
|
|
|
} else if (s1 == 1 || s2 == 1) {
|
|
|
|
|
|
num_simple_definition++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-03-11 21:07:17 +01:00
|
|
|
|
SOLVER_LOG(logger_, "[SAT presolve] [", elapsed_seconds, "s]",
|
|
|
|
|
|
" clauses:", num_clauses, " literals:", num_literals,
|
|
|
|
|
|
" vars:", num_vars, " one_side_vars:", num_one_side,
|
2021-03-10 11:40:28 +01:00
|
|
|
|
" simple_definition:", num_simple_definition,
|
|
|
|
|
|
" singleton_clauses:", num_singleton_clauses);
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
|
bool SimplifyClause(const std::vector<Literal>& a, std::vector<Literal>* b,
|
|
|
|
|
|
LiteralIndex* opposite_literal,
|
2021-03-04 18:26:01 +01:00
|
|
|
|
int64_t* num_inspected_literals) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (b->size() < a.size()) return false;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
DCHECK(std::is_sorted(a.begin(), a.end()));
|
|
|
|
|
|
DCHECK(std::is_sorted(b->begin(), b->end()));
|
2020-03-19 00:24:10 +01:00
|
|
|
|
if (num_inspected_literals != nullptr) {
|
|
|
|
|
|
*num_inspected_literals += a.size();
|
|
|
|
|
|
*num_inspected_literals += b->size();
|
|
|
|
|
|
}
|
2014-07-24 18:12:50 +00:00
|
|
|
|
|
|
|
|
|
|
*opposite_literal = LiteralIndex(-1);
|
|
|
|
|
|
|
|
|
|
|
|
int num_diff = 0;
|
|
|
|
|
|
std::vector<Literal>::const_iterator ia = a.begin();
|
2019-03-12 17:41:14 +01:00
|
|
|
|
std::vector<Literal>::const_iterator ib = b->begin();
|
|
|
|
|
|
std::vector<Literal>::const_iterator to_remove;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
|
|
|
|
|
|
// Because we abort early when size_diff becomes negative, the second test
|
|
|
|
|
|
// in the while loop is not needed.
|
|
|
|
|
|
int size_diff = b->size() - a.size();
|
|
|
|
|
|
while (ia != a.end() /* && ib != b->end() */) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (*ia == *ib) { // Same literal.
|
2014-07-24 18:12:50 +00:00
|
|
|
|
++ia;
|
|
|
|
|
|
++ib;
|
2020-10-22 23:36:58 +02:00
|
|
|
|
} else if (*ia == ib->Negated()) { // Opposite literal.
|
2014-07-24 18:12:50 +00:00
|
|
|
|
++num_diff;
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (num_diff > 1) return false; // Too much difference.
|
2014-07-24 18:12:50 +00:00
|
|
|
|
to_remove = ib;
|
|
|
|
|
|
++ia;
|
|
|
|
|
|
++ib;
|
|
|
|
|
|
} else if (*ia < *ib) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
return false; // A literal of a is not in b.
|
|
|
|
|
|
} else { // *ia > *ib
|
2014-07-24 18:12:50 +00:00
|
|
|
|
++ib;
|
|
|
|
|
|
|
|
|
|
|
|
// A literal of b is not in a, we can abort early by comparing the sizes
|
|
|
|
|
|
// left.
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (--size_diff < 0) return false;
|
2014-07-24 18:12:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (num_diff == 1) {
|
|
|
|
|
|
*opposite_literal = to_remove->Index();
|
|
|
|
|
|
b->erase(to_remove);
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
|
LiteralIndex DifferAtGivenLiteral(const std::vector<Literal>& a,
|
|
|
|
|
|
const std::vector<Literal>& b, Literal l) {
|
2016-06-02 13:19:10 +02:00
|
|
|
|
DCHECK_EQ(b.size(), a.size());
|
|
|
|
|
|
DCHECK(std::is_sorted(a.begin(), a.end()));
|
|
|
|
|
|
DCHECK(std::is_sorted(b.begin(), b.end()));
|
|
|
|
|
|
LiteralIndex result = kNoLiteralIndex;
|
|
|
|
|
|
std::vector<Literal>::const_iterator ia = a.begin();
|
|
|
|
|
|
std::vector<Literal>::const_iterator ib = b.begin();
|
|
|
|
|
|
while (ia != a.end() && ib != b.end()) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (*ia == *ib) { // Same literal.
|
2016-06-02 13:19:10 +02:00
|
|
|
|
++ia;
|
|
|
|
|
|
++ib;
|
|
|
|
|
|
} else if (*ia < *ib) {
|
|
|
|
|
|
// A literal of a is not in b, it must be l.
|
|
|
|
|
|
// Note that this can only happen once.
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (*ia != l) return kNoLiteralIndex;
|
2016-06-02 13:19:10 +02:00
|
|
|
|
++ia;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// A literal of b is not in a, save it.
|
|
|
|
|
|
// We abort if this happen twice.
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (result != kNoLiteralIndex) return kNoLiteralIndex;
|
2016-06-02 13:19:10 +02:00
|
|
|
|
result = (*ib).Index();
|
|
|
|
|
|
++ib;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Check the corner case of the difference at the end.
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (ia != a.end() && *ia != l) return kNoLiteralIndex;
|
2016-06-02 13:19:10 +02:00
|
|
|
|
if (ib != b.end()) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (result != kNoLiteralIndex) return kNoLiteralIndex;
|
2016-06-02 13:19:10 +02:00
|
|
|
|
result = (*ib).Index();
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
|
bool ComputeResolvant(Literal x, const std::vector<Literal>& a,
|
|
|
|
|
|
const std::vector<Literal>& b,
|
|
|
|
|
|
std::vector<Literal>* out) {
|
2014-07-24 18:12:50 +00:00
|
|
|
|
DCHECK(std::is_sorted(a.begin(), a.end()));
|
|
|
|
|
|
DCHECK(std::is_sorted(b.begin(), b.end()));
|
|
|
|
|
|
|
|
|
|
|
|
out->clear();
|
|
|
|
|
|
std::vector<Literal>::const_iterator ia = a.begin();
|
|
|
|
|
|
std::vector<Literal>::const_iterator ib = b.begin();
|
|
|
|
|
|
while ((ia != a.end()) && (ib != b.end())) {
|
|
|
|
|
|
if (*ia == *ib) {
|
|
|
|
|
|
out->push_back(*ia);
|
|
|
|
|
|
++ia;
|
|
|
|
|
|
++ib;
|
|
|
|
|
|
} else if (*ia == ib->Negated()) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (*ia != x) return false; // Trivially true.
|
2014-07-24 18:12:50 +00:00
|
|
|
|
DCHECK_EQ(*ib, x.Negated());
|
|
|
|
|
|
++ia;
|
|
|
|
|
|
++ib;
|
|
|
|
|
|
} else if (*ia < *ib) {
|
|
|
|
|
|
out->push_back(*ia);
|
|
|
|
|
|
++ia;
|
2020-10-22 23:36:58 +02:00
|
|
|
|
} else { // *ia > *ib
|
2014-07-24 18:12:50 +00:00
|
|
|
|
out->push_back(*ib);
|
|
|
|
|
|
++ib;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Copy remaining literals.
|
|
|
|
|
|
out->insert(out->end(), ia, a.end());
|
|
|
|
|
|
out->insert(out->end(), ib, b.end());
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-07-06 19:23:12 +02:00
|
|
|
|
// Note that this function takes a big chunk of the presolve running time.
|
2020-10-28 13:42:36 +01:00
|
|
|
|
int ComputeResolvantSize(Literal x, const std::vector<Literal>& a,
|
|
|
|
|
|
const std::vector<Literal>& b) {
|
2015-07-06 19:23:12 +02:00
|
|
|
|
DCHECK(std::is_sorted(a.begin(), a.end()));
|
|
|
|
|
|
DCHECK(std::is_sorted(b.begin(), b.end()));
|
|
|
|
|
|
|
|
|
|
|
|
int size = static_cast<int>(a.size() + b.size()) - 2;
|
|
|
|
|
|
std::vector<Literal>::const_iterator ia = a.begin();
|
|
|
|
|
|
std::vector<Literal>::const_iterator ib = b.begin();
|
|
|
|
|
|
while ((ia != a.end()) && (ib != b.end())) {
|
|
|
|
|
|
if (*ia == *ib) {
|
|
|
|
|
|
--size;
|
|
|
|
|
|
++ia;
|
|
|
|
|
|
++ib;
|
|
|
|
|
|
} else if (*ia == ib->Negated()) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (*ia != x) return -1; // Trivially true.
|
2015-07-06 19:23:12 +02:00
|
|
|
|
DCHECK_EQ(*ib, x.Negated());
|
|
|
|
|
|
++ia;
|
|
|
|
|
|
++ib;
|
|
|
|
|
|
} else if (*ia < *ib) {
|
|
|
|
|
|
++ia;
|
2020-10-22 23:36:58 +02:00
|
|
|
|
} else { // *ia > *ib
|
2015-07-06 19:23:12 +02:00
|
|
|
|
++ib;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
DCHECK_GE(size, 0);
|
|
|
|
|
|
return size;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-11-07 14:30:26 +00:00
|
|
|
|
// A simple graph where the nodes are the literals and the nodes adjacent to a
|
|
|
|
|
|
// literal l are the propagated literal when l is assigned in the underlying
|
|
|
|
|
|
// sat solver.
|
|
|
|
|
|
//
|
|
|
|
|
|
// This can be used to do a strong component analysis while probing all the
|
|
|
|
|
|
// literals of a solver. Note that this can be expensive, hence the support
|
|
|
|
|
|
// for a deterministic time limit.
|
|
|
|
|
|
class PropagationGraph {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
public:
|
2020-10-28 13:42:36 +01:00
|
|
|
|
PropagationGraph(double deterministic_time_limit, SatSolver* solver)
|
2020-10-22 23:36:58 +02:00
|
|
|
|
: solver_(solver),
|
|
|
|
|
|
deterministic_time_limit(solver->deterministic_time() +
|
|
|
|
|
|
deterministic_time_limit) {}
|
2014-11-07 14:30:26 +00:00
|
|
|
|
|
2023-08-30 10:04:11 -04:00
|
|
|
|
// This type is neither copyable nor movable.
|
|
|
|
|
|
PropagationGraph(const PropagationGraph&) = delete;
|
|
|
|
|
|
PropagationGraph& operator=(const PropagationGraph&) = delete;
|
|
|
|
|
|
|
2014-11-07 14:30:26 +00:00
|
|
|
|
// Returns the set of node adjacent to the given one.
|
|
|
|
|
|
// Interface needed by FindStronglyConnectedComponents(), note that it needs
|
|
|
|
|
|
// to be const.
|
2021-03-04 18:26:01 +01:00
|
|
|
|
const std::vector<int32_t>& operator[](int32_t index) const {
|
2014-11-07 14:30:26 +00:00
|
|
|
|
scratchpad_.clear();
|
|
|
|
|
|
solver_->Backtrack(0);
|
|
|
|
|
|
|
|
|
|
|
|
// Note that when the time limit is reached, we just keep returning empty
|
|
|
|
|
|
// adjacency list. This way, the SCC algorithm will terminate quickly and
|
|
|
|
|
|
// the equivalent literals detection will be incomplete but correct. Note
|
|
|
|
|
|
// also that thanks to the SCC algorithm, we will explore the connected
|
|
|
|
|
|
// components first.
|
|
|
|
|
|
if (solver_->deterministic_time() > deterministic_time_limit) {
|
|
|
|
|
|
return scratchpad_;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const Literal l = Literal(LiteralIndex(index));
|
2017-09-29 15:08:48 +02:00
|
|
|
|
if (!solver_->Assignment().LiteralIsAssigned(l)) {
|
2014-11-07 14:30:26 +00:00
|
|
|
|
const int trail_index = solver_->LiteralTrail().Index();
|
|
|
|
|
|
solver_->EnqueueDecisionAndBackjumpOnConflict(l);
|
|
|
|
|
|
if (solver_->CurrentDecisionLevel() > 0) {
|
|
|
|
|
|
// Note that the +1 is to avoid adding a => a.
|
|
|
|
|
|
for (int i = trail_index + 1; i < solver_->LiteralTrail().Index();
|
|
|
|
|
|
++i) {
|
|
|
|
|
|
scratchpad_.push_back(solver_->LiteralTrail()[i].Index().value());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return scratchpad_;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
|
private:
|
2021-03-04 18:26:01 +01:00
|
|
|
|
mutable std::vector<int32_t> scratchpad_;
|
2020-10-28 13:42:36 +01:00
|
|
|
|
SatSolver* const solver_;
|
2014-11-07 14:30:26 +00:00
|
|
|
|
const double deterministic_time_limit;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void ProbeAndFindEquivalentLiteral(
|
2020-10-28 13:42:36 +01:00
|
|
|
|
SatSolver* solver, SatPostsolver* postsolver,
|
|
|
|
|
|
DratProofHandler* drat_proof_handler,
|
2024-07-12 13:56:11 +02:00
|
|
|
|
util_intops::StrongVector<LiteralIndex, LiteralIndex>* mapping,
|
2023-10-25 15:37:53 +02:00
|
|
|
|
SolverLogger* logger) {
|
2020-05-06 18:22:10 +02:00
|
|
|
|
WallTimer timer;
|
|
|
|
|
|
timer.Start();
|
|
|
|
|
|
|
2014-11-07 14:30:26 +00:00
|
|
|
|
solver->Backtrack(0);
|
|
|
|
|
|
mapping->clear();
|
|
|
|
|
|
const int num_already_fixed_vars = solver->LiteralTrail().Index();
|
|
|
|
|
|
|
2015-07-28 15:11:25 +02:00
|
|
|
|
PropagationGraph graph(
|
|
|
|
|
|
solver->parameters().presolve_probing_deterministic_time_limit(), solver);
|
2021-03-04 18:26:01 +01:00
|
|
|
|
const int32_t size = solver->NumVariables() * 2;
|
|
|
|
|
|
std::vector<std::vector<int32_t>> scc;
|
2015-07-28 15:11:25 +02:00
|
|
|
|
FindStronglyConnectedComponents(size, graph, &scc);
|
|
|
|
|
|
|
|
|
|
|
|
// We have no guarantee that the cycle of x and not(x) touch the same
|
|
|
|
|
|
// variables. This is because we may have more info for the literal probed
|
|
|
|
|
|
// later or the propagation may go only in one direction. For instance if we
|
|
|
|
|
|
// have two clauses (not(x1) v x2) and (not(x1) v not(x2) v x3) then x1
|
|
|
|
|
|
// implies x2 and x3 but not(x3) doesn't imply anything by unit propagation.
|
|
|
|
|
|
//
|
|
|
|
|
|
// TODO(user): Add some constraint so that it does?
|
|
|
|
|
|
//
|
|
|
|
|
|
// Because of this, we "merge" the cycles.
|
|
|
|
|
|
MergingPartition partition(size);
|
2021-03-04 18:26:01 +01:00
|
|
|
|
for (const std::vector<int32_t>& component : scc) {
|
2015-07-28 15:11:25 +02:00
|
|
|
|
if (component.size() > 1) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
if (mapping->empty()) mapping->resize(size, LiteralIndex(-1));
|
2015-07-28 15:11:25 +02:00
|
|
|
|
const Literal representative((LiteralIndex(component[0])));
|
|
|
|
|
|
for (int i = 1; i < component.size(); ++i) {
|
|
|
|
|
|
const Literal l((LiteralIndex(component[i])));
|
|
|
|
|
|
// TODO(user): check compatibility? if x ~ not(x) => unsat.
|
|
|
|
|
|
// but probably, the solver would have found this too? not sure...
|
|
|
|
|
|
partition.MergePartsOf(representative.Index().value(),
|
|
|
|
|
|
l.Index().value());
|
|
|
|
|
|
partition.MergePartsOf(representative.NegatedIndex().value(),
|
|
|
|
|
|
l.NegatedIndex().value());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// We rely on the fact that the representative of a literal x and the one
|
|
|
|
|
|
// of its negation are the same variable.
|
2019-03-12 17:41:14 +01:00
|
|
|
|
DCHECK_EQ(Literal(LiteralIndex(partition.GetRootAndCompressPath(
|
|
|
|
|
|
representative.Index().value()))),
|
|
|
|
|
|
Literal(LiteralIndex(partition.GetRootAndCompressPath(
|
2020-10-22 23:36:58 +02:00
|
|
|
|
representative.NegatedIndex().value())))
|
|
|
|
|
|
.Negated());
|
2015-07-28 15:11:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
solver->Backtrack(0);
|
|
|
|
|
|
int num_equiv = 0;
|
|
|
|
|
|
if (!mapping->empty()) {
|
|
|
|
|
|
// If a variable in a cycle is fixed. We want to fix all of them.
|
2017-07-24 10:31:53 -07:00
|
|
|
|
//
|
|
|
|
|
|
// We first fix all representative if one variable of the cycle is fixed. In
|
|
|
|
|
|
// a second pass we fix all the variable of a cycle whose representative is
|
|
|
|
|
|
// fixed.
|
|
|
|
|
|
//
|
|
|
|
|
|
// TODO(user): Fixing a variable might fix more of them by propagation, so
|
|
|
|
|
|
// we might not fix everything possible with these loops.
|
2020-10-28 13:42:36 +01:00
|
|
|
|
const VariablesAssignment& assignment = solver->Assignment();
|
2015-07-28 15:11:25 +02:00
|
|
|
|
for (LiteralIndex i(0); i < size; ++i) {
|
|
|
|
|
|
const LiteralIndex rep(partition.GetRootAndCompressPath(i.value()));
|
2017-09-29 15:08:48 +02:00
|
|
|
|
if (assignment.LiteralIsAssigned(Literal(i)) &&
|
|
|
|
|
|
!assignment.LiteralIsAssigned(Literal(rep))) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
const Literal true_lit = assignment.LiteralIsTrue(Literal(i))
|
|
|
|
|
|
? Literal(rep)
|
|
|
|
|
|
: Literal(rep).Negated();
|
2023-10-18 15:47:37 +02:00
|
|
|
|
if (!solver->AddUnitClause(true_lit)) return;
|
2018-05-03 15:00:06 +02:00
|
|
|
|
if (drat_proof_handler != nullptr) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
drat_proof_handler->AddClause({true_lit});
|
2018-05-03 15:00:06 +02:00
|
|
|
|
}
|
2015-07-28 15:11:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
for (LiteralIndex i(0); i < size; ++i) {
|
|
|
|
|
|
const LiteralIndex rep(partition.GetRootAndCompressPath(i.value()));
|
|
|
|
|
|
(*mapping)[i] = rep;
|
2017-09-29 15:08:48 +02:00
|
|
|
|
if (assignment.LiteralIsAssigned(Literal(rep))) {
|
|
|
|
|
|
if (!assignment.LiteralIsAssigned(Literal(i))) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
const Literal true_lit = assignment.LiteralIsTrue(Literal(rep))
|
|
|
|
|
|
? Literal(i)
|
|
|
|
|
|
: Literal(i).Negated();
|
2023-10-18 15:47:37 +02:00
|
|
|
|
if (!solver->AddUnitClause(true_lit)) return;
|
2018-05-03 15:00:06 +02:00
|
|
|
|
if (drat_proof_handler != nullptr) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
drat_proof_handler->AddClause({true_lit});
|
2018-05-03 15:00:06 +02:00
|
|
|
|
}
|
2017-07-24 10:31:53 -07:00
|
|
|
|
}
|
2017-09-29 15:08:48 +02:00
|
|
|
|
} else if (assignment.LiteralIsAssigned(Literal(i))) {
|
|
|
|
|
|
if (!assignment.LiteralIsAssigned(Literal(rep))) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
const Literal true_lit = assignment.LiteralIsTrue(Literal(i))
|
|
|
|
|
|
? Literal(rep)
|
|
|
|
|
|
: Literal(rep).Negated();
|
2023-10-18 15:47:37 +02:00
|
|
|
|
if (!solver->AddUnitClause(true_lit)) return;
|
2018-05-03 15:00:06 +02:00
|
|
|
|
if (drat_proof_handler != nullptr) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
drat_proof_handler->AddClause({true_lit});
|
2016-06-02 13:19:10 +02:00
|
|
|
|
}
|
2015-07-28 15:11:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
} else if (rep != i) {
|
|
|
|
|
|
++num_equiv;
|
2020-10-22 23:36:58 +02:00
|
|
|
|
postsolver->Add(Literal(i), {Literal(i), Literal(rep).Negated()});
|
2018-05-03 15:00:06 +02:00
|
|
|
|
if (drat_proof_handler != nullptr) {
|
2020-10-22 23:36:58 +02:00
|
|
|
|
drat_proof_handler->AddClause({Literal(i), Literal(rep).Negated()});
|
2017-07-24 10:31:53 -07:00
|
|
|
|
}
|
2015-07-28 15:11:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-25 15:37:53 +02:00
|
|
|
|
if (logger != nullptr) {
|
|
|
|
|
|
SOLVER_LOG(logger, "[Pure SAT probing] fixed ", num_already_fixed_vars,
|
|
|
|
|
|
" + ", solver->LiteralTrail().Index() - num_already_fixed_vars,
|
|
|
|
|
|
" equiv ", num_equiv / 2, " total ", solver->NumVariables(),
|
|
|
|
|
|
" wtime: ", timer.Get());
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const bool log_info =
|
|
|
|
|
|
solver->parameters().log_search_progress() || VLOG_IS_ON(1);
|
|
|
|
|
|
LOG_IF(INFO, log_info) << "Probing. fixed " << num_already_fixed_vars
|
|
|
|
|
|
<< " + "
|
|
|
|
|
|
<< solver->LiteralTrail().Index() -
|
|
|
|
|
|
num_already_fixed_vars
|
|
|
|
|
|
<< " equiv " << num_equiv / 2 << " total "
|
|
|
|
|
|
<< solver->NumVariables()
|
|
|
|
|
|
<< " wtime: " << timer.Get();
|
|
|
|
|
|
}
|
2014-11-07 14:30:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
|
} // namespace sat
|
|
|
|
|
|
} // namespace operations_research
|