Skip to content

Commit 0f6ed93

Browse files
committed
[CP-SAT] more presolve
1 parent 4a2ffa9 commit 0f6ed93

File tree

3 files changed

+121
-3
lines changed

3 files changed

+121
-3
lines changed

ortools/sat/cp_model_presolve.cc

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7017,6 +7017,9 @@ void CpModelPresolver::Probe() {
70177017
}
70187018
probing_timer->AddCounter("fixed_bools", num_fixed);
70197019

7020+
DetectDuplicateConstraintsWithDifferentEnforcements(
7021+
mapping, implication_graph, model.GetOrCreate<Trail>());
7022+
70207023
int num_equiv = 0;
70217024
int num_changed_bounds = 0;
70227025
const int num_variables = context_->working_model->variables().size();
@@ -8758,7 +8761,23 @@ void CpModelPresolver::DetectDuplicateConstraints() {
87588761
context_->UpdateConstraintVariableUsage(dup);
87598762
context_->UpdateRuleStats("duplicate: removed constraint");
87608763
}
8764+
}
8765+
8766+
void CpModelPresolver::DetectDuplicateConstraintsWithDifferentEnforcements(
8767+
const CpModelMapping* mapping, BinaryImplicationGraph* implication_graph,
8768+
Trail* trail) {
8769+
if (time_limit_->LimitReached()) return;
8770+
if (context_->ModelIsUnsat()) return;
8771+
PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
87618772

8773+
// We need the objective written for this.
8774+
if (context_->working_model->has_objective()) {
8775+
if (!context_->CanonicalizeObjective()) return;
8776+
context_->WriteObjectiveToProto();
8777+
}
8778+
8779+
absl::flat_hash_set<Literal> enforcement_vars;
8780+
std::vector<std::pair<Literal, Literal>> implications_used;
87628781
// TODO(user): We can also do similar stuff to linear constraint that just
87638782
// differ at a singleton variable. Or that are equalities. Like if expr + X =
87648783
// cte and expr + Y = other_cte, we can see that X is in affine relation with
@@ -8774,6 +8793,35 @@ void CpModelPresolver::DetectDuplicateConstraints() {
87748793
continue;
87758794
}
87768795

8796+
// If we have a trail, we can check if any variable of the enforcement is
8797+
// fixed to false. This is useful for what follows since calling
8798+
// implication_graph->DirectImplications() is invalid for fixed variables.
8799+
if (trail != nullptr) {
8800+
bool found_false_enforcement = false;
8801+
for (const int c : {dup, rep}) {
8802+
for (const int l :
8803+
context_->working_model->constraints(c).enforcement_literal()) {
8804+
if (trail->Assignment().LiteralIsFalse(mapping->Literal(l))) {
8805+
found_false_enforcement = true;
8806+
break;
8807+
}
8808+
}
8809+
if (found_false_enforcement) {
8810+
context_->UpdateRuleStats("enforcement: false literal");
8811+
if (c == rep) {
8812+
rep_ct->Swap(dup_ct);
8813+
context_->UpdateConstraintVariableUsage(rep);
8814+
}
8815+
dup_ct->Clear();
8816+
context_->UpdateConstraintVariableUsage(dup);
8817+
break;
8818+
}
8819+
}
8820+
if (found_false_enforcement) {
8821+
continue;
8822+
}
8823+
}
8824+
87778825
// If one of them has no enforcement, then the other can be ignored.
87788826
// We always keep rep, but clear its enforcement if any.
87798827
if (dup_ct->enforcement_literal().empty() ||
@@ -8847,10 +8895,67 @@ void CpModelPresolver::DetectDuplicateConstraints() {
88478895
rep_ct->enforcement_literal().size() == 1) {
88488896
dup_ct->Clear();
88498897
context_->UpdateConstraintVariableUsage(dup);
8898+
continue;
8899+
}
8900+
}
8901+
8902+
// Check if the enforcement of one constraint implies the ones of the other.
8903+
if (implication_graph != nullptr && mapping != nullptr &&
8904+
trail != nullptr) {
8905+
for (int i = 0; i < 2; i++) {
8906+
// When A and B only differ on their enforcement literals and the
8907+
// enforcements of constraint A implies the enforcements of constraint
8908+
// B, then constraint A is redundant and we can remove it.
8909+
const int c_a = i == 0 ? dup : rep;
8910+
const int c_b = i == 0 ? rep : dup;
8911+
8912+
enforcement_vars.clear();
8913+
implications_used.clear();
8914+
for (const int proto_lit :
8915+
context_->working_model->constraints(c_b).enforcement_literal()) {
8916+
const Literal lit = mapping->Literal(proto_lit);
8917+
if (trail->Assignment().LiteralIsTrue(lit)) continue;
8918+
enforcement_vars.insert(lit);
8919+
}
8920+
for (const int proto_lit :
8921+
context_->working_model->constraints(c_a).enforcement_literal()) {
8922+
const Literal lit = mapping->Literal(proto_lit);
8923+
if (trail->Assignment().LiteralIsTrue(lit)) continue;
8924+
for (const Literal implication_lit :
8925+
implication_graph->DirectImplications(lit)) {
8926+
auto extracted = enforcement_vars.extract(implication_lit);
8927+
if (!extracted.empty() && lit != implication_lit) {
8928+
implications_used.push_back({lit, implication_lit});
8929+
}
8930+
}
8931+
}
8932+
if (enforcement_vars.empty()) {
8933+
context_->UpdateRuleStats(
8934+
"duplicate: identical constraint with implied enforcements");
8935+
if (c_a == rep) {
8936+
// We don't want to remove the representative element of the
8937+
// duplicates detection, so swap the constraints.
8938+
rep_ct->Swap(dup_ct);
8939+
context_->UpdateConstraintVariableUsage(rep);
8940+
}
8941+
dup_ct->Clear();
8942+
context_->UpdateConstraintVariableUsage(dup);
8943+
// Subtle point: we need to add the implications we used back to the
8944+
// graph. This is because in some case the implications are only true
8945+
// in the presence of the "duplicated" constraints.
8946+
for (const auto& [a, b] : implications_used) {
8947+
const int var_a =
8948+
mapping->GetProtoVariableFromBooleanVariable(a.Variable());
8949+
const int proto_lit_a = a.IsPositive() ? var_a : NegatedRef(var_a);
8950+
const int var_b =
8951+
mapping->GetProtoVariableFromBooleanVariable(b.Variable());
8952+
const int proto_lit_b = b.IsPositive() ? var_b : NegatedRef(var_b);
8953+
context_->AddImplication(proto_lit_a, proto_lit_b);
8954+
}
8955+
context_->UpdateNewConstraintsVariableUsage();
8956+
break;
8957+
}
88508958
}
8851-
} else {
8852-
context_->UpdateRuleStats(
8853-
"TODO duplicate: identical constraint with different enforcements");
88548959
}
88558960
}
88568961
}
@@ -12635,6 +12740,7 @@ CpSolverStatus CpModelPresolver::Presolve() {
1263512740
// TODO(user): merge these code instead of doing many passes?
1263612741
ProcessAtMostOneAndLinear();
1263712742
DetectDuplicateConstraints();
12743+
DetectDuplicateConstraintsWithDifferentEnforcements();
1263812744
DetectDominatedLinearConstraints();
1263912745
DetectDifferentVariables();
1264012746
ProcessSetPPC();

ortools/sat/cp_model_presolve.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
#include "absl/base/attributes.h"
2626
#include "absl/container/flat_hash_map.h"
2727
#include "absl/container/flat_hash_set.h"
28+
#include "ortools/sat/clause.h"
2829
#include "ortools/sat/cp_model.pb.h"
30+
#include "ortools/sat/cp_model_mapping.h"
2931
#include "ortools/sat/presolve_context.h"
3032
#include "ortools/sat/presolve_util.h"
33+
#include "ortools/sat/sat_base.h"
3134
#include "ortools/sat/sat_parameters.pb.h"
3235
#include "ortools/sat/util.h"
3336
#include "ortools/util/logging.h"
@@ -190,6 +193,10 @@ class CpModelPresolver {
190193
// Remove duplicate constraints. This also merge domain of linear constraints
191194
// with duplicate linear expressions.
192195
void DetectDuplicateConstraints();
196+
void DetectDuplicateConstraintsWithDifferentEnforcements(
197+
const CpModelMapping* mapping = nullptr,
198+
BinaryImplicationGraph* implication_graph = nullptr,
199+
Trail* trail = nullptr);
193200

194201
// Detects variable that must take different values.
195202
void DetectDifferentVariables();

ortools/sat/sat_base.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ class Literal {
105105
bool operator!=(Literal other) const { return index_ != other.index_; }
106106
bool operator<(const Literal& other) const { return index_ < other.index_; }
107107

108+
template <typename H>
109+
friend H AbslHashValue(H h, Literal literal) {
110+
return H::combine(std::move(h), literal.index_);
111+
}
112+
108113
private:
109114
int index_;
110115
};

0 commit comments

Comments
 (0)