sat: backport presolve update from main

This commit is contained in:
Corentin Le Molgat
2025-09-29 16:59:15 +02:00
parent 0cbdbb78f1
commit 1fb6be30b5
2 changed files with 71 additions and 93 deletions

View File

@@ -5603,6 +5603,11 @@ class UniqueNonNegativeValue {
int value_ = -1;
};
std::string Plural(int n, std::string_view s) {
return n <= 1 ? absl::StrCat(n, " ", s)
: absl::StrCat(FormatCounter(n), " ", s, "s");
};
} // namespace
bool CpModelPresolver::PresolveAllDiff(ConstraintProto* ct) {
@@ -8121,11 +8126,11 @@ void CpModelPresolver::Probe() {
if (num_old_cliques != num_new_cliques ||
num_literals_before != num_literals_after) {
timer.AddMessage(absl::StrCat(
"Merged ", FormatCounter(num_old_cliques), "(",
FormatCounter(num_literals_before), " literals) into ",
FormatCounter(num_new_cliques), "(",
FormatCounter(num_literals_after), " literals) at_most_ones. "));
timer.AddMessage(
absl::StrCat("Merged ", Plural(num_old_cliques, "constraint"),
" with ", Plural(num_literals_before, "literal"),
" into ", Plural(num_new_cliques, "constraint"),
" with ", Plural(num_literals_after, "literal")));
}
}
}
@@ -8947,63 +8952,16 @@ void CpModelPresolver::ExpandObjective() {
timer.AddCounter("issues", num_issues);
}
namespace {
bool MaxCliqueHasMadeSomeChanges(
int old_num_constraints, int old_num_entries,
const std::vector<std::vector<Literal>>& cliques, bool no_overlaps) {
int new_num_constraints = 0;
int new_num_entries = 0;
for (const std::vector<Literal>& clique : cliques) {
if (clique.empty()) continue;
new_num_constraints++;
new_num_entries += clique.size();
}
if (old_num_constraints != new_num_constraints ||
old_num_entries != new_num_entries) {
const std::string_view ct_name =
no_overlaps ? "no-overlaps" : "no-overlap_2ds";
const std::string_view entry_name =
no_overlaps ? "intervals" : "rectangles";
VLOG(1) << absl::StrCat("Merged ", old_num_constraints, " ", ct_name, " (",
old_num_entries, " ", entry_name, ") into ",
new_num_constraints, " ", ct_name, " (",
new_num_entries, " ", entry_name, ").");
return true;
}
return false;
}
} // namespace
bool CpModelPresolver::MergeNoOverlapConstraints() {
PresolveTimer timer("MergeNoOverlap", logger_, time_limit_);
if (context_->ModelIsUnsat()) return false;
if (time_limit_->LimitReached()) return true;
bool CpModelPresolver::MergeCliqueConstraintsHelper(
std::vector<std::vector<Literal>>& cliques, std::string_view entry_name,
PresolveTimer& timer) {
if (cliques.empty()) return false; // Nothing has changed.
const int num_constraints = context_->working_model->constraints_size();
int old_num_no_overlaps = 0;
int old_num_intervals = 0;
// Extract the no-overlap constraints with no enforcement literals.
// TODO(user): generalize this to merge constraints with the same
// enforcement literals?
std::vector<int> disjunctive_index;
std::vector<std::vector<Literal>> cliques;
for (int c = 0; c < num_constraints; ++c) {
const ConstraintProto& ct = context_->working_model->constraints(c);
if (ct.constraint_case() != ConstraintProto::kNoOverlap) continue;
if (HasEnforcementLiteral(ct)) continue;
std::vector<Literal> clique;
for (const int i : ct.no_overlap().intervals()) {
clique.push_back(Literal(BooleanVariable(i), true));
}
cliques.push_back(clique);
disjunctive_index.push_back(c);
old_num_no_overlaps++;
old_num_intervals += clique.size();
int old_num_clique_constraints = cliques.size();
int old_num_entries = 0;
for (const std::vector<Literal>& clique : cliques) {
old_num_entries += clique.size();
}
if (old_num_no_overlaps == 0) return true;
// We reuse the max-clique code from sat.
Model local_model;
@@ -9023,12 +8981,55 @@ bool CpModelPresolver::MergeNoOverlapConstraints() {
time_limit_->ResetHistory();
if (!MaxCliqueHasMadeSomeChanges(old_num_no_overlaps, old_num_intervals,
cliques,
/*no_overlaps=*/true)) {
// Update the number of constraints and entries after the max-clique.
int new_num_clique_constraints = 0;
int new_num_entries = 0;
for (const std::vector<Literal>& clique : cliques) {
if (clique.empty()) continue;
new_num_clique_constraints++;
new_num_entries += clique.size();
}
if (old_num_clique_constraints != new_num_clique_constraints ||
old_num_entries != new_num_entries) {
timer.AddMessage(absl::StrCat(
"Merged ", Plural(old_num_clique_constraints, "constraint"), " with ",
Plural(old_num_entries, entry_name), " into ",
Plural(new_num_clique_constraints, "constraint"), " with ",
Plural(new_num_entries, entry_name)));
return true;
}
return false; // Nothing has changed.
}
bool CpModelPresolver::MergeNoOverlapConstraints() {
PresolveTimer timer("MergeNoOverlap", logger_, time_limit_);
if (context_->ModelIsUnsat()) return false;
if (time_limit_->LimitReached()) return true;
const int num_constraints = context_->working_model->constraints_size();
// Extract the no-overlap constraints with no enforcement literals.
// TODO(user): generalize this to merge constraints with the same
// enforcement literals?
std::vector<int> disjunctive_index;
std::vector<std::vector<Literal>> cliques;
for (int c = 0; c < num_constraints; ++c) {
const ConstraintProto& ct = context_->working_model->constraints(c);
if (ct.constraint_case() != ConstraintProto::kNoOverlap) continue;
if (HasEnforcementLiteral(ct)) continue;
std::vector<Literal> clique;
for (const int i : ct.no_overlap().intervals()) {
clique.push_back(Literal(BooleanVariable(i), true));
}
cliques.push_back(clique);
disjunctive_index.push_back(c);
}
if (!MergeCliqueConstraintsHelper(cliques, "interval", timer)) {
return true; // Nothing to do, and model is SAT.
}
// Remove previous no_overlap constraints and add the new recomputed ones.
for (int i = 0; i < cliques.size(); ++i) {
const int ct_index = disjunctive_index[i];
@@ -9056,9 +9057,6 @@ bool CpModelPresolver::MergeNoOverlap2DConstraints() {
if (time_limit_->LimitReached()) return true;
const int num_constraints = context_->working_model->constraints_size();
int old_num_no_overlap_2ds = 0;
int old_num_rectangles = 0;
// Extract the no-overlap constraints with no enforcement literals.
// TODO(user): generalize this to merge constraints with the same
// enforcement literals?
@@ -9081,34 +9079,10 @@ bool CpModelPresolver::MergeNoOverlap2DConstraints() {
}
cliques.push_back(clique);
no_overlap2d_index.push_back(c);
old_num_no_overlap_2ds++;
old_num_rectangles += clique.size();
}
if (old_num_no_overlap_2ds == 0) return true;
// We reuse the max-clique code from sat.
Model local_model;
local_model.GetOrCreate<Trail>()->Resize(num_constraints);
local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
graph->Resize(num_constraints);
for (const std::vector<Literal>& clique : cliques) {
// All variables at false is always a valid solution of the local model,
// so this should never return UNSAT.
CHECK(graph->AddAtMostOne(clique));
}
CHECK(graph->DetectEquivalences());
graph->TransformIntoMaxCliques(
&cliques,
SafeDoubleToInt64(context_->params().merge_no_overlap_work_limit()));
time_limit_->ResetHistory();
if (!MaxCliqueHasMadeSomeChanges(old_num_no_overlap_2ds, old_num_rectangles,
cliques,
/*no_overlaps=*/false)) {
return true;
if (!MergeCliqueConstraintsHelper(cliques, "rectangle", timer)) {
return true; // Nothing to do, and model is SAT.
}
// Remove previous no_overlap constraints and add the new recomputed ones.
@@ -9239,7 +9213,7 @@ void CpModelPresolver::TransformIntoMaxCliques() {
if (num_old_cliques != num_new_cliques ||
num_literals_before != num_literals_after) {
SOLVER_LOG(logger_, "[MaxClique] Merged ", num_old_cliques, "(",
SOLVER_LOG(logger_, "[MaxClique] Merged ", num_old_cliques, " with ",
num_literals_before, " literals) into ", num_new_cliques, "(",
num_literals_after, " literals) at_most_ones.");
}

View File

@@ -18,6 +18,7 @@
#include <cstddef>
#include <cstdint>
#include <deque>
#include <string_view>
#include <utility>
#include <vector>
@@ -303,6 +304,9 @@ class CpModelPresolver {
void LookAtVariableWithDegreeTwo(int var);
void ProcessVariableInTwoAtMostOrExactlyOne(int var);
bool MergeCliqueConstraintsHelper(std::vector<std::vector<Literal>>& cliques,
std::string_view entry_name,
PresolveTimer& timer);
bool MergeNoOverlapConstraints();
bool MergeNoOverlap2DConstraints();