@@ -7017,6 +7017,9 @@ void CpModelPresolver::Probe() {
7017
7017
}
7018
7018
probing_timer->AddCounter("fixed_bools", num_fixed);
7019
7019
7020
+ DetectDuplicateConstraintsWithDifferentEnforcements(
7021
+ mapping, implication_graph, model.GetOrCreate<Trail>());
7022
+
7020
7023
int num_equiv = 0;
7021
7024
int num_changed_bounds = 0;
7022
7025
const int num_variables = context_->working_model->variables().size();
@@ -8758,7 +8761,23 @@ void CpModelPresolver::DetectDuplicateConstraints() {
8758
8761
context_->UpdateConstraintVariableUsage(dup);
8759
8762
context_->UpdateRuleStats("duplicate: removed constraint");
8760
8763
}
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_);
8761
8772
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;
8762
8781
// TODO(user): We can also do similar stuff to linear constraint that just
8763
8782
// differ at a singleton variable. Or that are equalities. Like if expr + X =
8764
8783
// cte and expr + Y = other_cte, we can see that X is in affine relation with
@@ -8774,6 +8793,35 @@ void CpModelPresolver::DetectDuplicateConstraints() {
8774
8793
continue;
8775
8794
}
8776
8795
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
+
8777
8825
// If one of them has no enforcement, then the other can be ignored.
8778
8826
// We always keep rep, but clear its enforcement if any.
8779
8827
if (dup_ct->enforcement_literal().empty() ||
@@ -8847,10 +8895,67 @@ void CpModelPresolver::DetectDuplicateConstraints() {
8847
8895
rep_ct->enforcement_literal().size() == 1) {
8848
8896
dup_ct->Clear();
8849
8897
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
+ }
8850
8958
}
8851
- } else {
8852
- context_->UpdateRuleStats(
8853
- "TODO duplicate: identical constraint with different enforcements");
8854
8959
}
8855
8960
}
8856
8961
}
@@ -12635,6 +12740,7 @@ CpSolverStatus CpModelPresolver::Presolve() {
12635
12740
// TODO(user): merge these code instead of doing many passes?
12636
12741
ProcessAtMostOneAndLinear();
12637
12742
DetectDuplicateConstraints();
12743
+ DetectDuplicateConstraintsWithDifferentEnforcements();
12638
12744
DetectDominatedLinearConstraints();
12639
12745
DetectDifferentVariables();
12640
12746
ProcessSetPPC();
0 commit comments