@@ -6108,7 +6108,7 @@ void ExtractClauses(bool merge_into_bool_and,
61086108 // how we perform the postsolve.
61096109 absl::flat_hash_map<int, int> ref_to_bool_and;
61106110 for (int i = 0; i < container.NumClauses(); ++i) {
6111- const std::vector<Literal> & clause = container.Clause(i);
6111+ const auto & clause = container.Clause(i);
61126112 if (clause.empty()) continue;
61136113
61146114 // bool_and.
@@ -8926,6 +8926,176 @@ bool CpModelPresolver::PresolvePureSatPart() {
89268926 return true;
89278927}
89288928
8929+ namespace {
8930+ class BasicClauseContainer {
8931+ public:
8932+ void SetNumVariables(int /*num_variables*/) {}
8933+ void AddBinaryClause(Literal a, Literal b) { AddClause({a, b}); }
8934+ void AddClause(absl::Span<const Literal> clause) { clauses_.Add(clause); }
8935+
8936+ int NumClauses() const { return clauses_.size(); }
8937+ absl::Span<const Literal> Clause(int index) const { return clauses_[index]; }
8938+
8939+ private:
8940+ CompactVectorVector<int, Literal> clauses_;
8941+ };
8942+ } // namespace
8943+
8944+ bool CpModelPresolver::PresolvePureSatProblem() {
8945+ if (context_->ModelIsUnsat()) return true;
8946+ if (context_->params().keep_all_feasible_solutions_in_presolve()) return true;
8947+
8948+ // Crash if the problem is not pure SAT.
8949+ const int num_variables = context_->working_model->variables().size();
8950+ const int num_constraints = context_->working_model->constraints().size();
8951+ const std::string error_msg =
8952+ "cp_model_pure_sat_presolve can only be used with pure SAT problems.";
8953+ if (context_->working_model->has_objective()) {
8954+ LOG(FATAL) << error_msg;
8955+ }
8956+ for (int i = 0; i < num_variables; ++i) {
8957+ if (!context_->CanBeUsedAsLiteral(i)) {
8958+ LOG(FATAL) << error_msg;
8959+ }
8960+ }
8961+ for (int i = 0; i < num_constraints; ++i) {
8962+ const ConstraintProto& ct = context_->working_model->constraints(i);
8963+ if (!(ct.constraint_case() == ConstraintProto::kBoolOr ||
8964+ ct.constraint_case() == ConstraintProto::kBoolAnd ||
8965+ ct.constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET)) {
8966+ LOG(FATAL) << error_msg;
8967+ }
8968+ }
8969+
8970+ Model local_model;
8971+ local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
8972+ auto* sat_solver = local_model.GetOrCreate<SatSolver>();
8973+ sat_solver->SetNumVariables(num_variables);
8974+
8975+ std::vector<int> new_to_old_index;
8976+ new_to_old_index.reserve(num_variables);
8977+ for (int i = 0; i < num_variables; ++i) {
8978+ new_to_old_index.push_back(i);
8979+ }
8980+
8981+ // 1) Load the problem in the SAT solver.
8982+ // 1.1) Fix variables if any.
8983+ auto ref_to_literal = [](int ref) {
8984+ return Literal(BooleanVariable(PositiveRef(ref)), RefIsPositive(ref));
8985+ };
8986+ for (int var = 0; var < num_variables; ++var) {
8987+ if (context_->IsFixed(var)) {
8988+ if (context_->LiteralIsTrue(var)) {
8989+ if (!sat_solver->AddUnitClause({ref_to_literal(var)})) return false;
8990+ } else {
8991+ if (!sat_solver->AddUnitClause({ref_to_literal(NegatedRef(var))})) {
8992+ return false;
8993+ }
8994+ }
8995+ }
8996+ }
8997+ // 1.2) Load the clauses.
8998+ std::vector<Literal> clause;
8999+ for (int i = 0; i < context_->working_model->constraints_size(); ++i) {
9000+ const ConstraintProto& ct = context_->working_model->constraints(i);
9001+ if (ct.constraint_case() == ConstraintProto::kBoolOr) {
9002+ clause.clear();
9003+ for (const int ref : ct.enforcement_literal()) {
9004+ clause.push_back(ref_to_literal(ref).Negated());
9005+ }
9006+ for (const int ref : ct.bool_or().literals()) {
9007+ clause.push_back(ref_to_literal(ref));
9008+ }
9009+ sat_solver->AddProblemClause(clause);
9010+ } else if (ct.constraint_case() == ConstraintProto::kBoolAnd) {
9011+ clause.clear();
9012+ for (const int ref : ct.enforcement_literal()) {
9013+ clause.push_back(ref_to_literal(ref).Negated());
9014+ }
9015+ clause.push_back(Literal(kNoLiteralIndex)); // will be replaced below.
9016+ for (const int ref : ct.bool_and().literals()) {
9017+ clause.back() = ref_to_literal(ref);
9018+ sat_solver->AddProblemClause(clause);
9019+ }
9020+ } else {
9021+ DCHECK_EQ(ct.constraint_case(), ConstraintProto::CONSTRAINT_NOT_SET);
9022+ continue;
9023+ }
9024+ context_->working_model->mutable_constraints(i)->Clear();
9025+ context_->UpdateConstraintVariableUsage(i);
9026+ }
9027+ if (sat_solver->ModelIsUnsat()) return false;
9028+
9029+ // Some problems are formulated in such a way that our SAT heuristics
9030+ // simply works without conflict. Get them out of the way first because it
9031+ // is possible that the presolve lose this "lucky" ordering. This is in
9032+ // particular the case on the SAT14.crafted.complete-xxx-... problems.
9033+ if (!LookForTrivialSatSolution(/*deterministic_time_limit=*/1.0, &local_model,
9034+ logger_)) {
9035+ return false;
9036+ }
9037+ if (sat_solver->LiteralTrail().Index() == num_variables) {
9038+ // Problem solved! We should be able to assign the solution.
9039+ CHECK(FixFromAssignment(sat_solver->Assignment(), new_to_old_index,
9040+ context_));
9041+ return true;
9042+ }
9043+
9044+ // 2) Do a few rounds of inprocessing.
9045+ SatPresolveOptions options;
9046+ options.log_info = true; // log_info;
9047+ options.extract_binary_clauses_in_probing = false;
9048+ options.use_transitive_reduction = false;
9049+ options.deterministic_time_limit =
9050+ context_->params().presolve_probing_deterministic_time_limit();
9051+ options.use_equivalence_sat_sweeping =
9052+ context_->params().inprocessing_use_sat_sweeping();
9053+
9054+ auto* inprocessing = local_model.GetOrCreate<Inprocessing>();
9055+ inprocessing->ProvideLogger(logger_);
9056+ // TODO(user): re-index the variables to remove the unused ones between
9057+ // each iteration?
9058+ for (int i = 0; i < context_->params().max_presolve_iterations(); ++i) {
9059+ if (time_limit_->LimitReached()) break;
9060+ context_->UpdateRuleStats("presolve: iteration");
9061+ if (!inprocessing->PresolveLoop(options)) return false;
9062+ time_limit_->AdvanceDeterministicTime(
9063+ local_model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
9064+ }
9065+
9066+ // 3) Extract the new clauses.
9067+ SatPostsolver sat_postsolver(num_variables);
9068+ for (const auto& c : local_model.GetOrCreate<PostsolveClauses>()->clauses) {
9069+ sat_postsolver.Add(c[0], c);
9070+ }
9071+ for (int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) {
9072+ sat_postsolver.FixVariable(sat_solver->LiteralTrail()[i]);
9073+ }
9074+ // TODO(user): can we improve ExtractClauses() to avoid the intermediate
9075+ // container?
9076+ BasicClauseContainer clauses_container;
9077+ if (!sat_solver->ExtractClauses(&clauses_container)) return false;
9078+ ExtractClauses(/*merge_into_bool_and=*/true, new_to_old_index,
9079+ clauses_container, context_->working_model);
9080+ const std::string name =
9081+ absl::GetFlag(FLAGS_cp_model_debug_postsolve) ? "sat_postsolver" : "";
9082+ ExtractClauses(/*merge_into_bool_and=*/false, new_to_old_index,
9083+ sat_postsolver, context_->mapping_model, name);
9084+
9085+ // Update the constraints <-> variables graph.
9086+ context_->UpdateNewConstraintsVariableUsage();
9087+
9088+ // We mark as removed any variables removed by the pure SAT presolve.
9089+ // This is mainly to discover or avoid bug as we might have stale entries
9090+ // in our encoding hash-map for instance.
9091+ for (int var = 0; var < num_variables; ++var) {
9092+ if (context_->VarToConstraints(var).empty()) {
9093+ context_->MarkVariableAsRemoved(var);
9094+ }
9095+ }
9096+ return true;
9097+ }
9098+
89299099void CpModelPresolver::ShiftObjectiveWithExactlyOnes() {
89309100 if (context_->ModelIsUnsat()) return;
89319101
@@ -13993,6 +14163,71 @@ bool CpModelPresolver::CanonicalizeAllLinears() {
1399314163CpSolverStatus CpModelPresolver::Presolve() {
1399414164 context_->InitializeNewDomains();
1399514165
14166+ if (context_->params().cp_model_pure_sat_presolve()) {
14167+ context_->UpdateNewConstraintsVariableUsage();
14168+ DCHECK(context_->ConstraintVariableUsageIsConsistent());
14169+
14170+ if (!PresolvePureSatProblem()) {
14171+ (void)context_->NotifyThatModelIsUnsat(
14172+ "Proved Infeasible during SAT presolve");
14173+ return InfeasibleStatus();
14174+ }
14175+
14176+ // Sync the domains and initialize the mapping model variables.
14177+ context_->WriteVariableDomainsToProto();
14178+
14179+ // Starts the postsolve mapping model.
14180+ std::vector<int> fixed_postsolve_mapping;
14181+ InitializeMappingModelVariables(context_->AllDomains(),
14182+ &fixed_postsolve_mapping,
14183+ context_->mapping_model);
14184+
14185+ // Remove all the unused variables from the presolved model.
14186+ postsolve_mapping_->clear();
14187+ std::vector<int> mapping(context_->working_model->variables_size(), -1);
14188+ int num_unused_variables = 0;
14189+ for (int i = 0; i < context_->working_model->variables_size(); ++i) {
14190+ if (mapping[i] != -1) continue; // Already mapped.
14191+ if (context_->VariableWasRemoved(i)) continue;
14192+
14193+ // Deal with unused variables.
14194+ //
14195+ // If the variable is not fixed, we have multiple feasible solutions for
14196+ // this variable, so we can't remove it if we want all of them.
14197+ if (context_->VariableIsNotUsedAnymore(i) &&
14198+ (!context_->params().keep_all_feasible_solutions_in_presolve() ||
14199+ context_->IsFixed(i))) {
14200+ // Tricky. Variables that were not removed by a presolve rule should be
14201+ // fixed first during postsolve, so that more complex postsolve rules
14202+ // can use their values. One way to do that is to fix them here.
14203+ //
14204+ // We prefer to fix them to zero if possible.
14205+ ++num_unused_variables;
14206+ FillDomainInProto(Domain(context_->DomainOf(i).SmallestValue()),
14207+ context_->mapping_model->mutable_variables(
14208+ fixed_postsolve_mapping[i]));
14209+ continue;
14210+ }
14211+ mapping[i] = postsolve_mapping_->size();
14212+ postsolve_mapping_->push_back(fixed_postsolve_mapping[i]);
14213+ }
14214+ context_->UpdateRuleStats(absl::StrCat("presolve: ", num_unused_variables,
14215+ " unused variables removed."));
14216+
14217+ MaybePermuteVariablesRandomly(mapping);
14218+
14219+ DCHECK(context_->ConstraintVariableUsageIsConsistent());
14220+ const int old_size = postsolve_mapping_->size();
14221+ ApplyVariableMapping(absl::MakeSpan(mapping), context_->working_model,
14222+ postsolve_mapping_);
14223+ CHECK_EQ(old_size, postsolve_mapping_->size());
14224+
14225+ // Compact all non-empty constraint at the beginning.
14226+ RemoveEmptyConstraints();
14227+
14228+ return LogAndValidatePresolvedModel();
14229+ }
14230+
1399614231 // If the objective is a floating point one, we scale it.
1399714232 //
1399814233 // TODO(user): We should probably try to delay this even more. For that we
@@ -14180,7 +14415,7 @@ CpSolverStatus CpModelPresolver::Presolve() {
1418014415 if (!time_limit_->LimitReached()) {
1418114416 if (!PresolvePureSatPart()) {
1418214417 (void)context_->NotifyThatModelIsUnsat(
14183- "Proven Infeasible during SAT presolve");
14418+ "Proved Infeasible during SAT presolve");
1418414419 return InfeasibleStatus();
1418514420 }
1418614421 }
@@ -14386,7 +14621,7 @@ CpSolverStatus CpModelPresolver::Presolve() {
1438614621
1438714622 // Deal with unused variables.
1438814623 //
14389- // If the variable is not fixed, we have multiple feasible solution for
14624+ // If the variable is not fixed, we have multiple feasible solutions for
1439014625 // this variable, so we can't remove it if we want all of them.
1439114626 if (context_->VariableIsNotUsedAnymore(i) &&
1439214627 (!context_->params().keep_all_feasible_solutions_in_presolve() ||
@@ -14420,21 +14655,7 @@ CpSolverStatus CpModelPresolver::Presolve() {
1442014655 context_->UpdateRuleStats(absl::StrCat("presolve: ", num_unused_variables,
1442114656 " unused variables removed."));
1442214657
14423- if (context_->params().permute_variable_randomly()) {
14424- // The mapping might merge variable, so we have to be careful here.
14425- const int n = postsolve_mapping_->size();
14426- std::vector<int> perm(n);
14427- std::iota(perm.begin(), perm.end(), 0);
14428- std::shuffle(perm.begin(), perm.end(), *context_->random());
14429- for (int i = 0; i < context_->working_model->variables_size(); ++i) {
14430- if (mapping[i] != -1) mapping[i] = perm[mapping[i]];
14431- }
14432- std::vector<int> new_postsolve_mapping(n);
14433- for (int i = 0; i < n; ++i) {
14434- new_postsolve_mapping[perm[i]] = (*postsolve_mapping_)[i];
14435- }
14436- *postsolve_mapping_ = std::move(new_postsolve_mapping);
14437- }
14658+ MaybePermuteVariablesRandomly(mapping);
1443814659
1443914660 DCHECK(context_->ConstraintVariableUsageIsConsistent());
1444014661 CanonicalizeRoutesConstraintNodeExpressions(context_);
@@ -14452,7 +14673,28 @@ CpSolverStatus CpModelPresolver::Presolve() {
1445214673 context_->UpdateRuleStats(absl::StrCat(
1445314674 "deductions: ", context_->deductions.NumDeductions(), " stored"));
1445414675 }
14676+ return LogAndValidatePresolvedModel();
14677+ }
14678+
14679+ void CpModelPresolver::MaybePermuteVariablesRandomly(
14680+ std::vector<int>& mapping) {
14681+ if (!context_->params().permute_variable_randomly()) return;
14682+ // The mapping might merge variable, so we have to be careful here.
14683+ const int n = postsolve_mapping_->size();
14684+ std::vector<int> perm(n);
14685+ std::iota(perm.begin(), perm.end(), 0);
14686+ std::shuffle(perm.begin(), perm.end(), *context_->random());
14687+ for (int i = 0; i < context_->working_model->variables_size(); ++i) {
14688+ if (mapping[i] != -1) mapping[i] = perm[mapping[i]];
14689+ }
14690+ std::vector<int> new_postsolve_mapping(n);
14691+ for (int i = 0; i < n; ++i) {
14692+ new_postsolve_mapping[perm[i]] = (*postsolve_mapping_)[i];
14693+ }
14694+ *postsolve_mapping_ = std::move(new_postsolve_mapping);
14695+ }
1445514696
14697+ CpSolverStatus CpModelPresolver::LogAndValidatePresolvedModel() {
1445614698 // Stats and checks.
1445714699 if (logger_->LoggingIsEnabled()) context_->LogInfo();
1445814700
0 commit comments