OR-Tools  9.6
sat_runner.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 #include <cstdint>
15 #include <cstdlib>
16 #include <memory>
17 #include <string>
18 #include <utility>
19 #include <vector>
20 
21 #include "absl/flags/flag.h"
22 #include "absl/flags/usage.h"
23 #include "absl/log/flags.h"
24 #include "absl/log/initialize.h"
25 #include "absl/random/random.h"
26 #include "absl/status/status.h"
27 #include "absl/strings/match.h"
28 #include "absl/strings/numbers.h"
29 #include "absl/strings/str_cat.h"
30 #include "absl/strings/str_format.h"
31 #include "absl/strings/string_view.h"
33 #include "ortools/base/helpers.h"
34 #include "ortools/base/options.h"
35 #include "ortools/base/timer.h"
36 #include "ortools/linear_solver/linear_solver.pb.h"
41 #include "ortools/sat/boolean_problem.pb.h"
42 #include "ortools/sat/cp_model.pb.h"
44 #include "ortools/sat/lp_utils.h"
45 #include "ortools/sat/model.h"
46 #include "ortools/sat/opb_reader.h"
49 #include "ortools/sat/sat_base.h"
51 #include "ortools/sat/sat_parameters.pb.h"
52 #include "ortools/sat/sat_solver.h"
54 #include "ortools/sat/symmetry.h"
55 #include "ortools/util/file_util.h"
56 #include "ortools/util/logging.h"
59 
61  std::string, input, "",
62  "Required: input file of the problem to solve. Many format are supported:"
63  ".cnf (sat, max-sat, weighted max-sat), .opb (pseudo-boolean sat/optim) "
64  "and by default the LinearBooleanProblem proto (binary or text).");
65 
67  std::string, output, "",
68  "If non-empty, write the input problem as a LinearBooleanProblem proto to "
69  "this file. By default it uses the binary format except if the file "
70  "extension is '.txt'. If the problem is SAT, a satisfiable assignment is "
71  "also written to the file.");
72 
73 ABSL_FLAG(bool, output_cnf_solution, false,
74  "If true and the problem was solved to optimality, this output "
75  "the solution to stdout in cnf form.\n");
76 
77 ABSL_FLAG(std::string, params, "",
78  "Parameters for the sat solver in a text format of the "
79  "SatParameters proto, example: --params=use_conflicts:true.");
80 
81 ABSL_FLAG(bool, strict_validity, false,
82  "If true, stop if the given input is invalid (duplicate literals, "
83  "out of range, zero cofficients, etc.)");
84 
86  std::string, lower_bound, "",
87  "If not empty, look for a solution with an objective value >= this bound.");
88 
90  std::string, upper_bound, "",
91  "If not empty, look for a solution with an objective value <= this bound.");
92 
93 ABSL_FLAG(bool, fu_malik, false,
94  "If true, search the optimal solution with the Fu & Malik algo.");
95 
96 ABSL_FLAG(bool, wpm1, false,
97  "If true, search the optimal solution with the WPM1 algo.");
98 
99 ABSL_FLAG(bool, qmaxsat, false,
100  "If true, search the optimal solution with a linear scan and "
101  " the cardinality encoding used in qmaxsat.");
102 
103 ABSL_FLAG(bool, core_enc, false,
104  "If true, search the optimal solution with the core-based "
105  "cardinality encoding algo.");
106 
107 ABSL_FLAG(bool, linear_scan, false,
108  "If true, search the optimal solution with the linear scan algo.");
109 
110 ABSL_FLAG(int, randomize, 500,
111  "If positive, solve that many times the problem with a random "
112  "decision heuristic before trying to optimize it.");
113 
114 ABSL_FLAG(bool, use_symmetry, false,
115  "If true, find and exploit the eventual symmetries "
116  "of the problem.");
117 
118 ABSL_FLAG(bool, presolve, true,
119  "Only work on pure SAT problem. If true, presolve the problem.");
120 
121 ABSL_FLAG(bool, probing, false, "If true, presolve the problem using probing.");
122 
123 ABSL_FLAG(bool, use_cp_model, true,
124  "Whether to interpret everything as a CpModelProto or "
125  "to read by default a CpModelProto.");
126 
127 ABSL_FLAG(bool, reduce_memory_usage, false,
128  "If true, do not keep a copy of the original problem in memory."
129  "This reduce the memory usage, but disable the solution cheking at "
130  "the end.");
131 
132 namespace operations_research {
133 namespace sat {
134 namespace {
135 
136 // Returns a trivial best bound. The best bound corresponds to the lower bound
137 // (resp. upper bound) in case of a minimization (resp. maximization) problem.
138 double GetScaledTrivialBestBound(const LinearBooleanProblem& problem) {
139  Coefficient best_bound(0);
140  const LinearObjective& objective = problem.objective();
141  for (const int64_t value : objective.coefficients()) {
142  if (value < 0) best_bound += Coefficient(value);
143  }
144  return AddOffsetAndScaleObjectiveValue(problem, best_bound);
145 }
146 
147 bool LoadBooleanProblem(const std::string& filename,
148  LinearBooleanProblem* problem, CpModelProto* cp_model) {
149  if (absl::EndsWith(filename, ".opb") ||
150  absl::EndsWith(filename, ".opb.bz2")) {
151  OpbReader reader;
152  if (!reader.Load(filename, problem)) {
153  LOG(FATAL) << "Cannot load file '" << filename << "'.";
154  }
155  } else if (absl::EndsWith(filename, ".cnf") ||
156  absl::EndsWith(filename, ".cnf.gz") ||
157  absl::EndsWith(filename, ".wcnf") ||
158  absl::EndsWith(filename, ".wcnf.gz")) {
159  SatCnfReader reader;
160  if (absl::GetFlag(FLAGS_fu_malik) || absl::GetFlag(FLAGS_linear_scan) ||
161  absl::GetFlag(FLAGS_wpm1) || absl::GetFlag(FLAGS_qmaxsat) ||
162  absl::GetFlag(FLAGS_core_enc)) {
163  reader.InterpretCnfAsMaxSat(true);
164  }
165  if (absl::GetFlag(FLAGS_use_cp_model)) {
166  if (!reader.Load(filename, cp_model)) {
167  LOG(FATAL) << "Cannot load file '" << filename << "'.";
168  }
169  } else {
170  if (!reader.Load(filename, problem)) {
171  LOG(FATAL) << "Cannot load file '" << filename << "'.";
172  }
173  }
174  } else if (absl::GetFlag(FLAGS_use_cp_model)) {
175  LOG(INFO) << "Reading a CpModelProto.";
176  *cp_model = ReadFileToProtoOrDie<CpModelProto>(filename);
177  } else {
178  LOG(INFO) << "Reading a LinearBooleanProblem.";
179  *problem = ReadFileToProtoOrDie<LinearBooleanProblem>(filename);
180  }
181  return true;
182 }
183 
184 std::string SolutionString(const LinearBooleanProblem& problem,
185  const std::vector<bool>& assignment) {
186  std::string output;
187  BooleanVariable limit(problem.original_num_variables());
188  for (BooleanVariable index(0); index < limit; ++index) {
189  if (index > 0) output += " ";
190  absl::StrAppend(&output,
191  Literal(index, assignment[index.value()]).SignedValue());
192  }
193  return output;
194 }
195 
196 // To benefit from the operations_research namespace, we put all the main() code
197 // here.
198 int Run() {
199  SatParameters parameters;
200  if (absl::GetFlag(FLAGS_input).empty()) {
201  LOG(FATAL) << "Please supply a data file with --input=";
202  }
203 
204  // Parse the --params flag.
205  parameters.set_log_search_progress(true);
206  if (!absl::GetFlag(FLAGS_params).empty()) {
207  CHECK(google::protobuf::TextFormat::MergeFromString(
208  absl::GetFlag(FLAGS_params), &parameters))
209  << absl::GetFlag(FLAGS_params);
210  }
211 
212  // Initialize the solver.
213  std::unique_ptr<SatSolver> solver(new SatSolver());
214  solver->SetParameters(parameters);
215 
216  // Read the problem.
217  LinearBooleanProblem problem;
218  CpModelProto cp_model;
219  if (!LoadBooleanProblem(absl::GetFlag(FLAGS_input), &problem, &cp_model)) {
220  CpSolverResponse response;
222  return EXIT_SUCCESS;
223  }
224  if (!absl::GetFlag(FLAGS_use_cp_model)) {
225  LOG(INFO) << "Converting to CpModelProto ...";
226  cp_model = BooleanProblemToCpModelproto(problem);
227  }
228 
229  // TODO(user): clean this hack. Ideally LinearBooleanProblem should be
230  // completely replaced by the more general CpModelProto.
231  if (absl::GetFlag(FLAGS_use_cp_model)) {
232  problem.Clear(); // We no longer need it, release memory.
233  Model model;
235  const CpSolverResponse response = SolveCpModel(cp_model, &model);
236 
237  if (!absl::GetFlag(FLAGS_output).empty()) {
238  if (absl::EndsWith(absl::GetFlag(FLAGS_output), "txt")) {
239  CHECK_OK(file::SetTextProto(absl::GetFlag(FLAGS_output), response,
240  file::Defaults()));
241  } else {
242  CHECK_OK(file::SetBinaryProto(absl::GetFlag(FLAGS_output), response,
243  file::Defaults()));
244  }
245  }
246 
247  // The SAT competition requires a particular exit code and since we don't
248  // really use it for any other purpose, we comply.
249  if (response.status() == CpSolverStatus::OPTIMAL) return 10;
250  if (response.status() == CpSolverStatus::FEASIBLE) return 10;
251  if (response.status() == CpSolverStatus::INFEASIBLE) return 20;
252  return EXIT_SUCCESS;
253  }
254 
255  if (absl::GetFlag(FLAGS_strict_validity)) {
256  const absl::Status status = ValidateBooleanProblem(problem);
257  if (!status.ok()) {
258  LOG(ERROR) << "Invalid Boolean problem: " << status.message();
259  return EXIT_FAILURE;
260  }
261  }
262 
263  // Count the time from there.
265  UserTimer user_timer;
266  wall_timer.Start();
267  user_timer.Start();
268  double scaled_best_bound = GetScaledTrivialBestBound(problem);
269 
270  // Probing.
271  SatPostsolver probing_postsolver(problem.num_variables());
272  LinearBooleanProblem original_problem;
273  if (absl::GetFlag(FLAGS_probing)) {
274  // TODO(user): This is nice for testing, but consumes memory.
275  original_problem = problem;
276  ProbeAndSimplifyProblem(&probing_postsolver, &problem);
277  }
278 
279  // Load the problem into the solver.
280  if (absl::GetFlag(FLAGS_reduce_memory_usage)) {
281  if (!LoadAndConsumeBooleanProblem(&problem, solver.get())) {
282  LOG(INFO) << "UNSAT when loading the problem.";
283  }
284  } else {
285  if (!LoadBooleanProblem(problem, solver.get())) {
286  LOG(INFO) << "UNSAT when loading the problem.";
287  }
288  }
289  auto strtoint64 = [](const std::string& word) {
290  int64_t value = 0;
291  if (!word.empty()) CHECK(absl::SimpleAtoi(word, &value));
292  return value;
293  };
295  problem, !absl::GetFlag(FLAGS_lower_bound).empty(),
296  Coefficient(strtoint64(absl::GetFlag(FLAGS_lower_bound))),
297  !absl::GetFlag(FLAGS_upper_bound).empty(),
298  Coefficient(strtoint64(absl::GetFlag(FLAGS_upper_bound))),
299  solver.get())) {
300  LOG(INFO) << "UNSAT when setting the objective constraint.";
301  }
302 
303  // Symmetries!
304  //
305  // TODO(user): To make this compatible with presolve, we just need to run
306  // it after the presolve step.
307  if (absl::GetFlag(FLAGS_use_symmetry)) {
308  CHECK(!absl::GetFlag(FLAGS_reduce_memory_usage)) << "incompatible";
309  CHECK(!absl::GetFlag(FLAGS_presolve)) << "incompatible";
310  LOG(INFO) << "Finding symmetries of the problem.";
311  std::vector<std::unique_ptr<SparsePermutation>> generators;
312  FindLinearBooleanProblemSymmetries(problem, &generators);
313  std::unique_ptr<SymmetryPropagator> propagator(new SymmetryPropagator);
314  for (int i = 0; i < generators.size(); ++i) {
315  propagator->AddSymmetry(std::move(generators[i]));
316  }
317  solver->AddPropagator(propagator.get());
318  solver->TakePropagatorOwnership(std::move(propagator));
319  }
320 
321  // Optimize?
322  std::vector<bool> solution;
324  if (absl::GetFlag(FLAGS_fu_malik) || absl::GetFlag(FLAGS_linear_scan) ||
325  absl::GetFlag(FLAGS_wpm1) || absl::GetFlag(FLAGS_qmaxsat) ||
326  absl::GetFlag(FLAGS_core_enc)) {
327  if (absl::GetFlag(FLAGS_randomize) > 0 &&
328  (absl::GetFlag(FLAGS_linear_scan) || absl::GetFlag(FLAGS_qmaxsat))) {
329  CHECK(!absl::GetFlag(FLAGS_reduce_memory_usage)) << "incompatible";
330  absl::BitGen bitgen;
331  result = SolveWithRandomParameters(STDOUT_LOG, problem,
332  absl::GetFlag(FLAGS_randomize), bitgen,
333  solver.get(), &solution);
334  }
335  if (result == SatSolver::LIMIT_REACHED) {
336  if (absl::GetFlag(FLAGS_qmaxsat)) {
337  solver = std::make_unique<SatSolver>();
338  solver->SetParameters(parameters);
339  CHECK(LoadBooleanProblem(problem, solver.get()));
340  result = SolveWithCardinalityEncoding(STDOUT_LOG, problem, solver.get(),
341  &solution);
342  } else if (absl::GetFlag(FLAGS_core_enc)) {
344  solver.get(), &solution);
345  } else if (absl::GetFlag(FLAGS_fu_malik)) {
346  result = SolveWithFuMalik(STDOUT_LOG, problem, solver.get(), &solution);
347  } else if (absl::GetFlag(FLAGS_wpm1)) {
348  result = SolveWithWPM1(STDOUT_LOG, problem, solver.get(), &solution);
349  } else if (absl::GetFlag(FLAGS_linear_scan)) {
350  result =
351  SolveWithLinearScan(STDOUT_LOG, problem, solver.get(), &solution);
352  }
353  }
354  } else {
355  // Only solve the decision version.
356  parameters.set_log_search_progress(true);
357  solver->SetParameters(parameters);
358  if (absl::GetFlag(FLAGS_presolve)) {
359  std::unique_ptr<TimeLimit> time_limit =
361  SolverLogger logger;
362  result = SolveWithPresolve(&solver, time_limit.get(), &solution,
363  /*drat_proof_handler=*/nullptr, &logger);
364  if (result == SatSolver::FEASIBLE) {
365  CHECK(IsAssignmentValid(problem, solution));
366  }
367  } else {
368  result = solver->Solve();
369  if (result == SatSolver::FEASIBLE) {
370  ExtractAssignment(problem, *solver, &solution);
371  CHECK(IsAssignmentValid(problem, solution));
372  }
373  }
374  }
375 
376  // Print the solution status.
377  if (result == SatSolver::FEASIBLE) {
378  if (absl::GetFlag(FLAGS_fu_malik) || absl::GetFlag(FLAGS_linear_scan) ||
379  absl::GetFlag(FLAGS_wpm1) || absl::GetFlag(FLAGS_core_enc)) {
380  absl::PrintF("s OPTIMUM FOUND\n");
381  CHECK(!solution.empty());
382  const Coefficient objective = ComputeObjectiveValue(problem, solution);
383  scaled_best_bound = AddOffsetAndScaleObjectiveValue(problem, objective);
384 
385  // Postsolve.
386  if (absl::GetFlag(FLAGS_probing)) {
387  solution = probing_postsolver.PostsolveSolution(solution);
388  problem = original_problem;
389  }
390  } else {
391  absl::PrintF("s SATISFIABLE\n");
392  }
393 
394  // Check and output the solution.
395  CHECK(IsAssignmentValid(problem, solution));
396  if (absl::GetFlag(FLAGS_output_cnf_solution)) {
397  absl::PrintF("v %s\n", SolutionString(problem, solution));
398  }
399  if (!absl::GetFlag(FLAGS_output).empty()) {
400  CHECK(!absl::GetFlag(FLAGS_reduce_memory_usage)) << "incompatible";
401  if (result == SatSolver::FEASIBLE) {
402  StoreAssignment(solver->Assignment(), problem.mutable_assignment());
403  }
404  if (absl::EndsWith(absl::GetFlag(FLAGS_output), ".txt")) {
405  CHECK_OK(file::SetTextProto(absl::GetFlag(FLAGS_output), problem,
406  file::Defaults()));
407  } else {
408  CHECK_OK(file::SetBinaryProto(absl::GetFlag(FLAGS_output), problem,
409  file::Defaults()));
410  }
411  }
412  }
413  if (result == SatSolver::INFEASIBLE) {
414  absl::PrintF("s UNSATISFIABLE\n");
415  }
416 
417  // Print status.
418  absl::PrintF("c status: %s\n", SatStatusString(result));
419 
420  // Print objective value.
421  if (solution.empty()) {
422  absl::PrintF("c objective: na\n");
423  absl::PrintF("c best bound: na\n");
424  } else {
425  const Coefficient objective = ComputeObjectiveValue(problem, solution);
426  absl::PrintF("c objective: %.16g\n",
427  AddOffsetAndScaleObjectiveValue(problem, objective));
428  absl::PrintF("c best bound: %.16g\n", scaled_best_bound);
429  }
430 
431  // Print final statistics.
432  absl::PrintF("c booleans: %d\n", solver->NumVariables());
433  absl::PrintF("c conflicts: %d\n", solver->num_failures());
434  absl::PrintF("c branches: %d\n", solver->num_branches());
435  absl::PrintF("c propagations: %d\n", solver->num_propagations());
436  absl::PrintF("c walltime: %f\n", wall_timer.Get());
437  absl::PrintF("c usertime: %f\n", user_timer.Get());
438  absl::PrintF("c deterministic_time: %f\n", solver->deterministic_time());
439 
440  return EXIT_SUCCESS;
441 }
442 
443 } // namespace
444 } // namespace sat
445 } // namespace operations_research
446 
447 static const char kUsage[] =
448  "Usage: see flags.\n"
449  "This program solves a given problem with the CP-SAT solver.";
450 
451 int main(int argc, char** argv) {
452  absl::InitializeLog();
453  absl::SetProgramUsageMessage(kUsage);
454  absl::ParseCommandLine(argc, argv);
455  return operations_research::sat::Run();
456 }
void Start()
Definition: timer.h:31
double Get() const
Definition: timer.h:45
static std::unique_ptr< TimeLimit > FromParameters(const Parameters &parameters)
Creates a time limit object initialized from an object that provides methods max_time_in_seconds() an...
Definition: time_limit.h:159
SatParameters parameters
SharedResponseManager * response
WallTimer * wall_timer
ModelSharedTimeLimit * time_limit
int64_t value
absl::Status status
Definition: g_gurobi.cc:41
GRBmodel * model
int index
absl::Status SetTextProto(const absl::string_view &filename, const google::protobuf::Message &proto, int flags)
Definition: base/file.cc:299
Options Defaults()
Definition: base/file.h:123
absl::Status SetBinaryProto(const absl::string_view &filename, const google::protobuf::Message &proto, int flags)
Definition: base/file.cc:322
std::tuple< int64_t, int64_t, const double > Coefficient
bool AddObjectiveConstraint(const LinearBooleanProblem &problem, bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound, Coefficient upper_bound, SatSolver *solver)
std::function< SatParameters(Model *)> NewSatParameters(const std::string &params)
Creates parameters for the solver, which you can add to the model with.
double AddOffsetAndScaleObjectiveValue(const LinearBooleanProblem &problem, Coefficient v)
SatSolver::Status SolveWithCardinalityEncodingAndCore(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
void StoreAssignment(const VariablesAssignment &assignment, BooleanAssignment *output)
SatSolver::Status SolveWithLinearScan(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
SatSolver::Status SolveWithRandomParameters(LogBehavior log, const LinearBooleanProblem &problem, int num_times, absl::BitGenRef random, SatSolver *solver, std::vector< bool > *solution)
absl::Status ValidateBooleanProblem(const LinearBooleanProblem &problem)
void FindLinearBooleanProblemSymmetries(const LinearBooleanProblem &problem, std::vector< std::unique_ptr< SparsePermutation >> *generators)
std::string SatStatusString(SatSolver::Status status)
Definition: sat_solver.cc:2649
SatSolver::Status SolveWithWPM1(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
bool LoadAndConsumeBooleanProblem(LinearBooleanProblem *problem, SatSolver *solver)
CpSolverResponse SolveCpModel(const CpModelProto &model_proto, Model *model)
Solves the given CpModelProto.
bool IsAssignmentValid(const LinearBooleanProblem &problem, const std::vector< bool > &assignment)
void ProbeAndSimplifyProblem(SatPostsolver *postsolver, LinearBooleanProblem *problem)
Coefficient ComputeObjectiveValue(const LinearBooleanProblem &problem, const std::vector< bool > &assignment)
SatSolver::Status SolveWithPresolve(std::unique_ptr< SatSolver > *solver, TimeLimit *time_limit, std::vector< bool > *solution, DratProofHandler *drat_proof_handler, SolverLogger *logger)
SatSolver::Status SolveWithFuMalik(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
CpModelProto BooleanProblemToCpModelproto(const LinearBooleanProblem &problem)
bool LoadBooleanProblem(const LinearBooleanProblem &problem, SatSolver *solver)
SatSolver::Status SolveWithCardinalityEncoding(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
void ExtractAssignment(const LinearBooleanProblem &problem, const SatSolver &solver, std::vector< bool > *assignment)
Collection of objects used to extend the Constraint Solver library.
int64_t strtoint64(absl::string_view word)
Definition: strtoint.cc:32
static int input(yyscan_t yyscanner)
IntVar * upper_bound
Definition: routing.cc:1087
IntVar * lower_bound
Definition: routing.cc:1086
ABSL_FLAG(std::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).")
int main(int argc, char **argv)
Definition: sat_runner.cc:451
static const char kUsage[]
Definition: sat_runner.cc:447