// Copyright 2010-2014 Google // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include "base/commandlineflags.h" #include "base/commandlineflags.h" #include "base/integral_types.h" #include "base/logging.h" #include "base/strtoint.h" #include "base/timer.h" #include "base/file.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/message.h" #include "google/protobuf/text_format.h" #include "base/strutil.h" #include "algorithms/sparse_permutation.h" #include "sat/boolean_problem.h" #include "sat/drat.h" #include "cpp/opb_reader.h" #include "sat/optimization.h" #include "cpp/sat_cnf_reader.h" #include "sat/sat_solver.h" #include "sat/simplification.h" #include "sat/symmetry.h" #include "util/time_limit.h" #include "base/random.h" #include "base/status.h" DEFINE_string( input, "", "Required: input file of the problem to solve. Many format are supported:" ".cnf (sat, max-sat, weighted max-sat), .opb (pseudo-boolean sat/optim) " "and by default the LinearBooleanProblem proto (binary or text)."); DEFINE_string( output, "", "If non-empty, write the input problem as a LinearBooleanProblem proto to " "this file. By default it uses the binary format except if the file " "extension is '.txt'. If the problem is SAT, a satisfiable assignment is " "also writen to the file."); DEFINE_bool(output_cnf_solution, false, "If true and the problem was solved to optimality, this output " "the solution to stdout in cnf form.\n"); DEFINE_string(params, "", "Parameters for the sat solver in a text format of the " "SatParameters proto, example: --params=use_conflicts:true."); DEFINE_bool(strict_validity, false, "If true, stop if the given input is invalid (duplicate literals, " "out of range, zero cofficients, etc.)"); DEFINE_string( lower_bound, "", "If not empty, look for a solution with an objective value >= this bound."); DEFINE_string( upper_bound, "", "If not empty, look for a solution with an objective value <= this bound."); DEFINE_bool(fu_malik, false, "If true, search the optimal solution with the Fu & Malik algo."); DEFINE_bool(wpm1, false, "If true, search the optimal solution with the WPM1 algo."); DEFINE_bool(qmaxsat, false, "If true, search the optimal solution with a linear scan and " " the cardinality encoding used in qmaxsat."); DEFINE_bool(core_enc, false, "If true, search the optimal solution with the core-based " "cardinality encoding algo."); DEFINE_bool(linear_scan, false, "If true, search the optimal solution with the linear scan algo."); DEFINE_int32(randomize, 500, "If positive, solve that many times the problem with a random " "decision heuristic before trying to optimize it."); DEFINE_bool(use_symmetry, false, "If true, find and exploit the eventual symmetries " "of the problem."); DEFINE_bool(presolve, false, "Only work on pure SAT problem. If true, presolve the problem."); DEFINE_bool(probing, false, "If true, presolve the problem using probing."); DEFINE_bool(reduce_memory_usage, false, "If true, do not keep a copy of the original problem in memory." "This reduce the memory usage, but disable the solution cheking at " "the end."); namespace operations_research { namespace sat { namespace { // Returns a trivial best bound. The best bound corresponds to the lower bound // (resp. upper bound) in case of a minimization (resp. maximization) problem. double GetScaledTrivialBestBound(const LinearBooleanProblem& problem) { Coefficient best_bound(0); const LinearObjective& objective = problem.objective(); for (const int64 value : objective.coefficients()) { if (value < 0) best_bound += Coefficient(value); } return AddOffsetAndScaleObjectiveValue(problem, best_bound); } void LoadBooleanProblem(std::string filename, LinearBooleanProblem* problem) { if (HasSuffixString(filename, ".opb") || HasSuffixString(filename, ".opb.bz2")) { OpbReader reader; if (!reader.Load(filename, problem)) { LOG(FATAL) << "Cannot load file '" << filename << "'."; } } else if (HasSuffixString(filename, ".cnf") || HasSuffixString(filename, ".cnf.gz") || HasSuffixString(filename, ".wcnf") || HasSuffixString(filename, ".wcnf.gz")) { SatCnfReader reader; if (FLAGS_fu_malik || FLAGS_linear_scan || FLAGS_wpm1 || FLAGS_qmaxsat || FLAGS_core_enc) { reader.InterpretCnfAsMaxSat(true); } if (!reader.Load(filename, problem)) { LOG(FATAL) << "Cannot load file '" << filename << "'."; } } else { file::ReadFileToProtoOrDie(filename, problem); } } std::string SolutionString(const LinearBooleanProblem& problem, const std::vector& assignment) { std::string output; BooleanVariable limit(problem.original_num_variables()); for (BooleanVariable index(0); index < limit; ++index) { if (index > 0) output += " "; output += StringPrintf( "%d", Literal(index, assignment[index.value()]).SignedValue()); } return output; } // To benefit from the operations_research namespace, we put all the main() code // here. int Run() { SatParameters parameters; if (FLAGS_input.empty()) { LOG(FATAL) << "Please supply a data file with --input="; } // In the algorithms below, this seems like a good parameter. parameters.set_count_assumption_levels_in_lbd(false); // Parse the --params flag. if (!FLAGS_params.empty()) { CHECK(google::protobuf::TextFormat::MergeFromString(FLAGS_params, ¶meters)) << FLAGS_params; } Model model; DratWriter* drat_writer = model.GetOrCreate(); // Initialize the solver. std::unique_ptr solver(new SatSolver()); solver->SetDratWriter(drat_writer); solver->SetParameters(parameters); // The global time limit. std::unique_ptr time_limit(TimeLimit::FromParameters(parameters)); // Read the problem. LinearBooleanProblem problem; LoadBooleanProblem(FLAGS_input, &problem); if (FLAGS_strict_validity) { const util::Status status = ValidateBooleanProblem(problem); if (!status.ok()) { LOG(ERROR) << "Invalid Boolean problem: " << status.error_message(); return EXIT_FAILURE; } } // Count the time from there. WallTimer wall_timer; UserTimer user_timer; wall_timer.Start(); user_timer.Start(); double scaled_best_bound = GetScaledTrivialBestBound(problem); // Probing. SatPostsolver probing_postsolver(problem.num_variables()); LinearBooleanProblem original_problem; if (FLAGS_probing) { // TODO(user): This is nice for testing, but consumes memory. original_problem = problem; ProbeAndSimplifyProblem(&probing_postsolver, &problem); } // Load the problem into the solver. if (FLAGS_reduce_memory_usage) { if (!LoadAndConsumeBooleanProblem(&problem, solver.get())) { LOG(INFO) << "UNSAT when loading the problem."; } } else { if (!LoadBooleanProblem(problem, solver.get())) { LOG(INFO) << "UNSAT when loading the problem."; } } if (!AddObjectiveConstraint( problem, !FLAGS_lower_bound.empty(), Coefficient(atoi64(FLAGS_lower_bound)), !FLAGS_upper_bound.empty(), Coefficient(atoi64(FLAGS_upper_bound)), solver.get())) { LOG(INFO) << "UNSAT when setting the objective constraint."; } if (drat_writer != nullptr) { drat_writer->SetNumVariables(solver->NumVariables()); } // Symmetries! if (FLAGS_use_symmetry) { CHECK(!FLAGS_reduce_memory_usage) << "incompatible"; LOG(INFO) << "Finding symmetries of the problem."; std::vector> generators; FindLinearBooleanProblemSymmetries(problem, &generators); std::unique_ptr propagator(new SymmetryPropagator); for (int i = 0; i < generators.size(); ++i) { propagator->AddSymmetry(std::move(generators[i])); } solver->AddPropagator(std::move(propagator)); } // Optimize? std::vector solution; SatSolver::Status result = SatSolver::LIMIT_REACHED; if (FLAGS_fu_malik || FLAGS_linear_scan || FLAGS_wpm1 || FLAGS_qmaxsat || FLAGS_core_enc) { if (FLAGS_randomize > 0 && (FLAGS_linear_scan || FLAGS_qmaxsat)) { CHECK(!FLAGS_reduce_memory_usage) << "incompatible"; result = SolveWithRandomParameters(STDOUT_LOG, problem, FLAGS_randomize, solver.get(), &solution); } if (result == SatSolver::LIMIT_REACHED) { if (FLAGS_qmaxsat) { solver.reset(new SatSolver()); solver->SetParameters(parameters); CHECK(LoadBooleanProblem(problem, solver.get())); result = SolveWithCardinalityEncoding(STDOUT_LOG, problem, solver.get(), &solution); } else if (FLAGS_core_enc) { result = SolveWithCardinalityEncodingAndCore(STDOUT_LOG, problem, solver.get(), &solution); } else if (FLAGS_fu_malik) { result = SolveWithFuMalik(STDOUT_LOG, problem, solver.get(), &solution); } else if (FLAGS_wpm1) { result = SolveWithWPM1(STDOUT_LOG, problem, solver.get(), &solution); } else if (FLAGS_linear_scan) { result = SolveWithLinearScan(STDOUT_LOG, problem, solver.get(), &solution); } } } else { // Only solve the decision version. parameters.set_log_search_progress(true); solver->SetParameters(parameters); // Presolve. if (FLAGS_presolve) { SatPostsolver postsolver(problem.num_variables()); // Some problems are formulated in such a way that our SAT heuristics // simply works without conflict. Get them out of the way first because it // is possible that the presolve lose this "lucky" ordering. This is in // particular the case on the SAT14.crafted.complete-xxx-... problems. // // TODO(user): Use the SolveWithRandomParameters() instead, it sounds // like a better idea to try a bunch of different heuristic rather than // just the default one. result = SatSolver::LIMIT_REACHED; { MTRandom random("A random seed."); SatParameters new_params = parameters; new_params.set_log_search_progress(false); new_params.set_max_number_of_conflicts(1); const int num_times = 1000; for (int i = 0; i < num_times && !time_limit->LimitReached(); ++i) { solver->SetParameters(new_params); result = solver->SolveWithTimeLimit(time_limit.get()); if (result != SatSolver::LIMIT_REACHED) { LOG(INFO) << "Problem solved by trivial heuristic!"; break; } // We randomize at the end so that the default params is executed // at least once. solver->RestoreSolverToAssumptionLevel(); RandomizeDecisionHeuristic(&random, &new_params); new_params.set_random_seed(i); solver->SetParameters(new_params); solver->ResetDecisionHeuristic(); } // Restore the initial parameters. solver->SetParameters(parameters); solver->ResetDecisionHeuristic(); } // We use a new block so the memory used by the presolver can be // reclaimed as soon as it is no longer needed. const int max_num_passes = result == SatSolver::LIMIT_REACHED ? 4 : 0; for (int i = 0; i < max_num_passes && !time_limit->LimitReached(); ++i) { const int saved_num_variables = solver->NumVariables(); // Probe + find equivalent literals. // TODO(user): Use a derived time limit in the probing phase. ITIVector equiv_map; ProbeAndFindEquivalentLiteral(solver.get(), &postsolver, drat_writer, &equiv_map); if (solver->IsModelUnsat()) { printf("c unsat during probing!\n"); result = SatSolver::MODEL_UNSAT; break; } // Register the fixed variables with the presolver. // TODO(user): Find a better place for this? solver->Backtrack(0); for (int i = 0; i < solver->LiteralTrail().Index(); ++i) { postsolver.FixVariable(solver->LiteralTrail()[i]); } // TODO(user): Pass the time_limit to the presolver. SatPresolver presolver(&postsolver); presolver.SetParameters(parameters); presolver.SetDratWriter(drat_writer); presolver.SetEquivalentLiteralMapping(equiv_map); solver->ExtractClauses(&presolver); solver.release(); if (!presolver.Presolve()) { printf("c unsat during presolve!\n"); // This is just here for the satistics display below to work. solver.reset(new SatSolver()); result = SatSolver::MODEL_UNSAT; break; } postsolver.ApplyMapping(presolver.VariableMapping()); if (drat_writer != nullptr) { drat_writer->ApplyMapping(presolver.VariableMapping()); } // Load the presolved problem in a new solver. solver.reset(new SatSolver()); solver->SetDratWriter(drat_writer); solver->SetParameters(parameters); presolver.LoadProblemIntoSatSolver(solver.get()); // Stop if a fixed point has been reached. if (solver->NumVariables() == saved_num_variables) break; } // Solve. if (result == SatSolver::LIMIT_REACHED) { result = solver->SolveWithTimeLimit(time_limit.get()); } // Recover the solution. if (result == SatSolver::MODEL_SAT) { solution = postsolver.ExtractAndPostsolveSolution(*solver); CHECK(IsAssignmentValid(problem, solution)); } } else { result = solver->Solve(); if (result == SatSolver::MODEL_SAT) { ExtractAssignment(problem, *solver, &solution); CHECK(IsAssignmentValid(problem, solution)); } } } // Print the solution status. if (result == SatSolver::MODEL_SAT) { if (FLAGS_fu_malik || FLAGS_linear_scan || FLAGS_wpm1 || FLAGS_core_enc) { printf("s OPTIMUM FOUND\n"); CHECK(!solution.empty()); const Coefficient objective = ComputeObjectiveValue(problem, solution); scaled_best_bound = AddOffsetAndScaleObjectiveValue(problem, objective); // Postsolve. if (FLAGS_probing) { solution = probing_postsolver.PostsolveSolution(solution); problem = original_problem; } } else { printf("s SATISFIABLE\n"); } // Check and output the solution. CHECK(IsAssignmentValid(problem, solution)); if (FLAGS_output_cnf_solution) { printf("v %s\n", SolutionString(problem, solution).c_str()); } if (!FLAGS_output.empty()) { CHECK(!FLAGS_reduce_memory_usage) << "incompatible"; if (result == SatSolver::MODEL_SAT) { StoreAssignment(solver->Assignment(), problem.mutable_assignment()); } if (HasSuffixString(FLAGS_output, ".txt")) { CHECK_OK(file::SetTextProto(FLAGS_output, problem, file::Defaults())); } else { CHECK_OK(file::SetBinaryProto(FLAGS_output, problem, file::Defaults())); } } } if (result == SatSolver::MODEL_UNSAT) { printf("s UNSATISFIABLE\n"); } // Print objective value. if (solution.empty()) { printf("c objective: na\n"); printf("c best bound: na\n"); } else { const Coefficient objective = ComputeObjectiveValue(problem, solution); printf("c objective: %.16g\n", AddOffsetAndScaleObjectiveValue(problem, objective)); printf("c best bound: %.16g\n", scaled_best_bound); } // Print final statistics. printf("c status: %s\n", SatStatusString(result).c_str()); printf("c conflicts: %lld\n", solver->num_failures()); printf("c branches: %lld\n", solver->num_branches()); printf("c propagations: %lld\n", solver->num_propagations()); printf("c walltime: %f\n", wall_timer.Get()); printf("c usertime: %f\n", user_timer.Get()); printf("c deterministic time: %f\n", solver->deterministic_time()); // The SAT competition requires a particular exit code and since we don't // really use it for any other purpose, we comply. if (result == SatSolver::MODEL_SAT) return 10; if (result == SatSolver::MODEL_UNSAT) return 20; return 0; } } // namespace } // namespace sat } // namespace operations_research static const char kUsage[] = "Usage: see flags.\n" "This program solves a given Boolean linear problem."; int main(int argc, char** argv) { gflags::SetUsageMessage(kUsage); gflags::ParseCommandLineFlags(&argc, &argv, true); return operations_research::sat::Run(); }