Skip to content

Commit 70e56de

Browse files
committed
[CP-SAT] fix bugs in lrat, inner clause handling, variable expansion
1 parent 01c684a commit 70e56de

File tree

11 files changed

+281
-166
lines changed

11 files changed

+281
-166
lines changed

ortools/sat/clause.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,9 @@ SatClause* ClauseManager::ReasonClause(int trail_index) const {
292292
SatClause* ClauseManager::ReasonClauseOrNull(BooleanVariable var) const {
293293
if (!trail_->Assignment().VariableIsAssigned(var)) return nullptr;
294294
if (trail_->AssignmentType(var) != propagator_id_) return nullptr;
295+
296+
DCHECK_EQ(trail_->Reason(var),
297+
reasons_[trail_->Info(var).trail_index]->PropagationReason());
295298
return reasons_[trail_->Info(var).trail_index];
296299
}
297300

@@ -546,6 +549,14 @@ bool ClauseManager::InprocessingRewriteClause(
546549
AttachOnFalse(new_clause[0], new_clause[1], clause);
547550
AttachOnFalse(new_clause[1], new_clause[0], clause);
548551
}
552+
553+
// We need to change the reason now that the clause size changed because
554+
// we cache the span into the reason data directly.
555+
if (is_reason) {
556+
trail_->ChangeReason(trail_->Info(new_clause[0].Variable()).trail_index,
557+
propagator_id_);
558+
}
559+
549560
return true;
550561
}
551562

ortools/sat/cp_model_solver_helpers.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,10 @@ int RegisterClausesLevelZeroImport(int id,
11681168
}
11691169
}
11701170
clause_manager->SetAddClauseCallback(std::move(callback));
1171+
if (new_clauses > 0) {
1172+
shared_clauses_manager->NotifyNumImported(id, new_clauses);
1173+
}
1174+
11711175
if (new_clauses > 0 && !sat_solver->FinishPropagation()) return false;
11721176
if (minimize_shared_clauses && new_clauses > 0) {
11731177
// The new clauses may be subsumed, so try to minimize them to reduce

ortools/sat/presolve_context.cc

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,12 @@ bool PresolveContext::CanonicalizeObjective(bool simplify_domain) {
21112111
.IntersectionWith(Domain(std::numeric_limits<int64_t>::min(),
21122112
objective_domain_.Max()))
21132113
.IsIncludedIn(objective_domain_);
2114+
if (objective_domain_is_constraining_) {
2115+
VLOG(3) << "objective domain is constraining: size: "
2116+
<< objective_map_.size()
2117+
<< ", implied: " << implied_domain.ToString()
2118+
<< " objective: " << objective_domain_.ToString();
2119+
}
21142120
return true;
21152121
}
21162122

@@ -2247,14 +2253,53 @@ bool PresolveContext::SubstituteVariableInObjective(
22472253

22482254
RemoveVariableFromObjective(var_in_equality);
22492255

2250-
// Because we can assume that the constraint we used was constraining
2251-
// (otherwise it would have been removed), the objective domain should be now
2252-
// constraining.
2253-
objective_domain_is_constraining_ = true;
2256+
// If the objective is small enough, recompute the value of
2257+
// objective_domain_is_constrainting_, otherwise, we just assume it to be
2258+
// true. This uses the updated objective_map_.
2259+
if (objective_map_.size() < 256) {
2260+
Domain implied_domain(0);
22542261

2255-
if (objective_domain_.IsEmpty()) {
2256-
return NotifyThatModelIsUnsat();
2262+
// We need to sort the entries to be deterministic.
2263+
tmp_entries_.clear();
2264+
for (const auto& entry : objective_map_) {
2265+
tmp_entries_.push_back(entry);
2266+
}
2267+
std::sort(tmp_entries_.begin(), tmp_entries_.end());
2268+
for (const auto& entry : tmp_entries_) {
2269+
const int var = entry.first;
2270+
const int64_t coeff = entry.second;
2271+
implied_domain =
2272+
implied_domain.AdditionWith(DomainOf(var).MultiplicationBy(coeff))
2273+
.RelaxIfTooComplex();
2274+
}
2275+
2276+
// This is the new domain.
2277+
// Note that the domain never include the offset.
2278+
objective_domain_ = objective_domain_.IntersectionWith(implied_domain)
2279+
.SimplifyUsingImpliedDomain(implied_domain);
2280+
2281+
if (objective_domain_.IsEmpty()) {
2282+
return NotifyThatModelIsUnsat("empty objective domain");
2283+
}
2284+
2285+
// Detect if the objective domain do not limit the "optimal" objective
2286+
// value. If this is true, then we can apply any reduction that reduce the
2287+
// objective value without any issues.
2288+
objective_domain_is_constraining_ =
2289+
!implied_domain
2290+
.IntersectionWith(Domain(std::numeric_limits<int64_t>::min(),
2291+
objective_domain_.Max()))
2292+
.IsIncludedIn(objective_domain_);
2293+
if (objective_domain_is_constraining_) {
2294+
VLOG(3) << "objective domain is constraining: size: "
2295+
<< objective_map_.size()
2296+
<< ", implied: " << implied_domain.ToString()
2297+
<< " objective: " << objective_domain_.ToString();
2298+
}
2299+
} else {
2300+
objective_domain_is_constraining_ = true;
22572301
}
2302+
22582303
return true;
22592304
}
22602305

ortools/sat/presolve_context_test.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ TEST(PresolveContextTest, ObjectiveSubstitutionWithLargeCoeff) {
506506
objective {
507507
vars: [ 1, 2 ]
508508
coeffs: [ 2, 2 ]
509-
domain: [ 12, 1012 ] # [0, 1000] initially, + 2*6 offset.
509+
domain: [ 12, 36 ] # [0, 1000] initially, + 2*6 offset.
510510
offset: -9
511511
integer_before_offset: -12
512512
scaling_factor: 1

ortools/sat/probing.cc

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,6 @@ bool FailedLiteralProbing::DoOneRound(ProbingOptions options) {
654654
}
655655

656656
binary_clause_extracted_.assign(trail_.Index(), false);
657-
subsumed_clauses_.clear();
658657

659658
while (!time_limit_->LimitReached() &&
660659
time_limit_->GetElapsedDeterministicTime() <= limit) {
@@ -738,15 +737,7 @@ bool FailedLiteralProbing::DoOneRound(ProbingOptions options) {
738737

739738
if (!sat_solver_->ResetToLevelZero()) return false;
740739
if (!ProcessLiteralsToFix()) return false;
741-
if (!subsumed_clauses_.empty()) {
742-
for (SatClause* clause : subsumed_clauses_) {
743-
if (clause->size() == 0) continue;
744-
++num_subsumed_;
745-
clause_manager_->LazyDelete(clause,
746-
DeletionSourceForStat::SUBSUMPTION_PROBING);
747-
}
748-
clause_manager_->CleanUpWatchers();
749-
}
740+
clause_manager_->CleanUpWatchers();
750741

751742
// Display stats.
752743
const int num_fixed = sat_solver_->LiteralTrail().Index();
@@ -994,7 +985,31 @@ void FailedLiteralProbing::MaybeExtractImplication(const Literal last_decision,
994985
// of all literals in the reason for this propagation. And use this
995986
// as a reason for later hyber binary resolution. Like we do when
996987
// this clause subsumes the reason.
997-
AddImplication(last_decision, l);
988+
++num_new_binary_;
989+
DCHECK(assignment_.LiteralIsTrue(l));
990+
CHECK_NE(l.Variable(), last_decision.Variable());
991+
992+
// We should never probe a redundant literal.
993+
//
994+
// TODO(user): We should be able to enforce that l is non-redundant either if
995+
// we made sure the clause database is cleaned up before FailedLiteralProbing
996+
// is called. This should maybe simplify the ChangeReason() handling.
997+
DCHECK(!implication_graph_->IsRedundant(last_decision));
998+
999+
ClauseId clause_id = kNoClauseId;
1000+
if (lrat_proof_handler_ != nullptr) {
1001+
clause_id = clause_id_generator_->GetNextId();
1002+
tmp_clause_ids_.clear();
1003+
sat_solver_->AppendClausesFixing({l}, &tmp_clause_ids_, last_decision);
1004+
lrat_proof_handler_->AddInferredClause(
1005+
clause_id, {last_decision.Negated(), l}, tmp_clause_ids_);
1006+
}
1007+
1008+
// Each time we extract a binary clause, we change the reason in the trail.
1009+
// This is important as it will allow us to remove clauses that are now
1010+
// subsumed by this binary, even if it was a reason.
1011+
CHECK(implication_graph_->AddBinaryClauseAndChangeReason(
1012+
clause_id, l, last_decision.Negated()));
9981013
}
9991014

10001015
// If we can extract a binary clause that subsume the reason clause, we do add
@@ -1006,6 +1021,7 @@ void FailedLiteralProbing::MaybeSubsumeWithBinaryClause(
10061021
const Literal last_decision, const Literal l) {
10071022
const int trail_index = trail_.Info(l.Variable()).trail_index;
10081023
if (trail_.AssignmentType(l.Variable()) != clause_propagator_id_) return;
1024+
SatClause* clause = clause_manager_->ReasonClause(trail_index);
10091025

10101026
bool subsumed = false;
10111027
for (const Literal lit : trail_.Reason(l.Variable())) {
@@ -1027,7 +1043,12 @@ void FailedLiteralProbing::MaybeSubsumeWithBinaryClause(
10271043
if (lit == last_decision.Negated()) ++test;
10281044
}
10291045
CHECK_EQ(test, 2);
1030-
subsumed_clauses_.push_back(clause_manager_->ReasonClause(trail_index));
1046+
1047+
// Because of MaybeExtractImplication(), this shouldn't be a reason anymore.
1048+
CHECK(!clause_manager_->ClauseIsUsedAsReason(clause));
1049+
++num_subsumed_;
1050+
clause_manager_->LazyDelete(clause,
1051+
DeletionSourceForStat::SUBSUMPTION_PROBING);
10311052
}
10321053

10331054
// Inspect the watcher list for last_decision, If we have a blocking
@@ -1043,34 +1064,21 @@ void FailedLiteralProbing::SubsumeWithBinaryClauseUsingBlockingLiteral(
10431064
const Literal last_decision) {
10441065
for (const auto& w :
10451066
clause_manager_->WatcherListOnFalse(last_decision.Negated())) {
1046-
if (assignment_.LiteralIsTrue(w.blocking_literal)) {
1047-
if (w.clause->IsRemoved()) continue;
1067+
if (!assignment_.LiteralIsTrue(w.blocking_literal)) continue;
1068+
if (w.clause->IsRemoved()) continue;
10481069

1049-
// This should be enough for proof correctness.
1050-
if (clause_manager_->ClauseIsUsedAsReason(w.clause)) {
1051-
MaybeExtractImplication(last_decision, w.blocking_literal);
1052-
}
1053-
subsumed_clauses_.push_back(w.clause);
1054-
}
1055-
}
1056-
}
1070+
// This should be enough for proof correctness.
1071+
if (clause_manager_->ClauseIsUsedAsReason(w.clause)) {
1072+
MaybeExtractImplication(last_decision, w.blocking_literal);
10571073

1058-
void FailedLiteralProbing::AddImplication(const Literal last_decision,
1059-
const Literal l) {
1060-
++num_new_binary_;
1061-
DCHECK(assignment_.LiteralIsTrue(l));
1062-
CHECK_NE(l.Variable(), last_decision.Variable());
1074+
// It should have been replaced by a binary clause now.
1075+
CHECK(!clause_manager_->ClauseIsUsedAsReason(w.clause));
1076+
}
10631077

1064-
ClauseId clause_id = kNoClauseId;
1065-
if (lrat_proof_handler_ != nullptr) {
1066-
clause_id = clause_id_generator_->GetNextId();
1067-
tmp_clause_ids_.clear();
1068-
sat_solver_->AppendClausesFixing({l}, &tmp_clause_ids_, last_decision);
1069-
lrat_proof_handler_->AddInferredClause(
1070-
clause_id, {last_decision.Negated(), l}, tmp_clause_ids_);
1078+
++num_subsumed_;
1079+
clause_manager_->LazyDelete(w.clause,
1080+
DeletionSourceForStat::SUBSUMPTION_PROBING);
10711081
}
1072-
CHECK(implication_graph_->AddBinaryClause(clause_id, last_decision.Negated(),
1073-
l));
10741082
}
10751083

10761084
// Adds 'not(literal)' to `to_fix_`, assuming that 'literal' directly implies

ortools/sat/probing.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,6 @@ class FailedLiteralProbing {
325325
// which is quite cheap to test here.
326326
void SubsumeWithBinaryClauseUsingBlockingLiteral(Literal last_decision);
327327

328-
void AddImplication(Literal last_decision, Literal l);
329-
330328
// Adds 'not(literal)' to `to_fix_`, assuming that 'literal' directly implies
331329
// the current decision, which itself implies all the previous decisions, with
332330
// some of them propagating 'not(literal)'.
@@ -366,7 +364,6 @@ class FailedLiteralProbing {
366364
// For each literal 'l' in the trail, whether a binary clause "d => l" has
367365
// been extracted, with 'd' the decision at the same level as 'l'.
368366
std::vector<bool> binary_clause_extracted_;
369-
std::vector<SatClause*> subsumed_clauses_;
370367

371368
// Temporary vector used for LRAT proofs.
372369
std::vector<ClauseId> tmp_clause_ids_;

0 commit comments

Comments
 (0)