Google OR-Tools: ortools/sat/sat_runner.cc Source File
23#include "absl/base/thread_annotations.h"
24#include "absl/flags/flag.h"
25#include "absl/flags/parse.h"
26#include "absl/flags/usage.h"
29#include "absl/log/initialize.h"
31#include "absl/strings/match.h"
32#include "absl/strings/str_format.h"
33#include "absl/strings/str_split.h"
34#include "absl/strings/string_view.h"
35#include "absl/synchronization/mutex.h"
36#include "absl/types/span.h"
37#include "google/protobuf/arena.h"
38#include "google/protobuf/text_format.h"
56 std::string, input, "",
57 "Required: input file of the problem to solve. Many format are supported:"
58 ".cnf (sat, max-sat, weighted max-sat), .opb (pseudo-boolean sat/optim) "
59 "and by default the CpModelProto proto (binary or text).");
62 std::string, hint_file, "",
63 "Protobuf file containing a CpModelResponse. The solution will be used as a"
64 " hint to bootstrap the search.");
67 "Protobuf file containing a CpModelResponse. If present, the "
68 "tightened models will be used to reduce the domain of variables.");
71 "If non-empty, write the response there. By default it uses the "
72 "binary format except if the file extension is '.txt'.");
75 "Parameters for the sat solver in a text format of the "
76 "SatParameters proto, example: --params=use_conflicts:true.");
78ABSL_FLAG(bool, wcnf_use_strong_slack, true,
79 "If true, when we add a slack variable to reify a soft clause, we "
80 "enforce the fact that when it is true, the clause must be false.");
81ABSL_FLAG(bool, fingerprint_intermediate_solutions, false,
82 "Attach the fingerprint of intermediate solutions to the output.");
84 "If true, output the log in a competition format.");
85ABSL_FLAG(bool, force_interleave_search, false,
86 "If true, enable interleaved workers when num_workers is 1.");
92void TryToRemoveSuffix(absl::string_view suffix, std::string* str) {
96std::string ExtractName(absl::string_view full_filename) {
97 std::string filename = std::string(file::Basename(full_filename));
99 TryToRemoveSuffix("gz", &filename);
100 TryToRemoveSuffix("txt", &filename);
101 TryToRemoveSuffix("pb", &filename);
102 TryToRemoveSuffix("pbtxt", &filename);
103 TryToRemoveSuffix("proto", &filename);
104 TryToRemoveSuffix("prototxt", &filename);
105 TryToRemoveSuffix("textproto", &filename);
106 TryToRemoveSuffix("bin", &filename);
110class LastSolutionPrinter {
113 void MaybePrintLastSolution() {
114 absl::MutexLock lock(mutex_);
115 if (last_solution_printed_) return;
116 last_solution_printed_ = true;
118 if (last_solution_.empty()) {
119 std::cout << "s UNKNOWN" << std::endl;
121 std::cout << "s SATISFIABLE" << std::endl;
123 for (int i = 0; i < num_variables_; ++i) {
124 if (last_solution_[i]) {
125 absl::StrAppend(&line, "x", i + 1, " ");
127 absl::StrAppend(&line, "-x", i + 1, " ");
130 std::cout << "v " << line << std::endl;
135 std::cout << "v " << line << std::endl;
140 void set_num_variables(int num_variables) { num_variables_ = num_variables; }
142 void set_last_solution(absl::Span<const int64_t> solution) {
143 absl::MutexLock lock(mutex_);
144 if (last_solution_printed_) return;
150 bool mark_last_solution_printed() {
151 const absl::MutexLock lock(mutex_);
152 if (last_solution_printed_) {
155 last_solution_printed_ = true;
161 std::vector<int64_t> last_solution_ ABSL_GUARDED_BY(mutex_);
162 bool last_solution_printed_ ABSL_GUARDED_BY(mutex_) = false;
166void LogInPbCompetitionFormat(
167 int num_variables, bool has_objective, Model* model,
169 std::shared_ptr<LastSolutionPrinter> last_solution_printer) {
170 CHECK(last_solution_printer != nullptr);
171 last_solution_printer->set_num_variables(num_variables);
173 const auto log_callback = [](const std::string& multi_line_input) {
174 if (multi_line_input.empty()) {
175 std::cout << "c" << std::endl;
178 const std::vector<absl::string_view> lines =
179 absl::StrSplit(multi_line_input, '\n');
180 for (const absl::string_view& line : lines) {
181 std::cout << "c " << line << std::endl;
184 model->GetOrCreate<SolverLogger>()->AddInfoLoggingCallback(log_callback);
185 parameters->set_log_to_stdout(false);
187 const auto response_callback = [last_solution_printer](
189 std::cout << "o " << static_cast<int64_t>(r.objective_value()) << std::endl;
190 last_solution_printer->set_last_solution(r.solution());
194 const auto final_response_callback =
195 [num_variables, has_objective,
197 if (!last_solution_printer->mark_last_solution_printed()) return;
202 std::cout << "s OPTIMUM FOUND " << std::endl;
204 std::cout << "s SATISFIABLE" << std::endl;
208 std::cout << "s SATISFIABLE" << std::endl;
211 std::cout << "s UNSATISFIABLE" << std::endl;
214 std::cout << "s UNSUPPORTED" << std::endl;
217 std::cout << "s UNKNOWN" << std::endl;
225 for (int i = 0; i < num_variables; ++i) {
226 if (r->solution(i)) {
227 absl::StrAppend(&line, "x", i + 1, " ");
229 absl::StrAppend(&line, "-x", i + 1, " ");
232 std::cout << "v " << line << std::endl;
237 std::cout << "v " << line << std::endl;
245void SetInterleavedWorkers(SatParameters* parameters) {
247 if (parameters->num_workers() == 1) {
248 parameters->set_interleave_search(true);
249 parameters->set_use_rins_lns(false);
250 parameters->add_subsolvers("default_lp");
251 parameters->add_subsolvers("max_lp");
252 parameters->add_subsolvers("quick_restart");
253 parameters->add_subsolvers("core_or_no_lp");
254 parameters->set_num_violation_ls(1);
258bool LoadProblem(const std::string& filename, absl::string_view hint_file,
259 absl::string_view domain_file, CpModelProto* cp_model,
261 std::shared_ptr<LastSolutionPrinter> last_solution_printer) {
262 if (absl::EndsWith(filename, ".opb") ||
263 absl::EndsWith(filename, ".opb.bz2") ||
264 absl::EndsWith(filename, ".opb.gz") || absl::EndsWith(filename, ".wbo") ||
265 absl::EndsWith(filename, ".wbo.bz2") ||
266 absl::EndsWith(filename, ".wbo.gz")) {
268 if (!reader.LoadAndValidate(filename, cp_model)) {
269 if (!reader.model_is_supported()) {
270 if (absl::GetFlag(FLAGS_competition_mode)) {
272 std::cout << "s UNSUPPORTED" << std::endl;
279 var->add_domain(std::numeric_limits<int64_t>::min());
280 var->add_domain(std::numeric_limits<int64_t>::max());
288 if (absl::GetFlag(FLAGS_competition_mode)) {
290 reader.model_is_supported() ? reader.num_variables() : 1;
291 LogInPbCompetitionFormat(num_variables, cp_model->has_objective(), model,
292 parameters, last_solution_printer);
294 if (absl::GetFlag(FLAGS_force_interleave_search)) {
295 SetInterleavedWorkers(parameters);
297 if (parameters->num_workers() >= 2 && parameters->num_workers() <= 15) {
300 parameters->add_ignore_subsolvers("max_lp_sym");
301 parameters->add_extra_subsolvers("max_lp");
303 } else if (absl::EndsWith(filename, ".cnf") ||
304 absl::EndsWith(filename, ".cnf.xz") ||
305 absl::EndsWith(filename, ".cnf.gz") ||
306 absl::EndsWith(filename, ".wcnf") ||
307 absl::EndsWith(filename, ".wcnf.xz") ||
308 absl::EndsWith(filename, ".wcnf.gz")) {
309 SatCnfReader reader(absl::GetFlag(FLAGS_wcnf_use_strong_slack));
310 if (!reader.Load(filename, cp_model)) {
311 LOG(FATAL) << "Cannot load file '" << filename << "'.";
321 if (!response.solution().empty()) {
322 CHECK_EQ(response.solution_size(), cp_model->variables_size())
323 << "The hint from the response proto is not compatible with the "
326 cp_model->clear_solution_hint();
327 for (int i = 0; i < cp_model->variables_size(); ++i) {
328 cp_model->mutable_solution_hint()->add_vars(i);
329 cp_model->mutable_solution_hint()->add_values(response.solution(i));
332 LOG(INFO) << "The response proto has no solutions, ignoring.";
337 if (!domain_file.empty()) {
340 if (!response.tightened_variables().empty()) {
341 CHECK_EQ(response.tightened_variables_size(), cp_model->variables_size())
342 << "The tighened variables from the response proto is not "
343 "compatible with the model proto";
345 for (int i = 0; i < cp_model->variables_size(); ++i) {
347 const Domain tightened_domain =
354 LOG(INFO) << "The response proto has no tightened variable domains, "
360 if (cp_model->name().empty()) {
361 cp_model->set_name(ExtractName(filename));
366int Run() {
369 if (absl::GetFlag(FLAGS_input).empty()) {
370 LOG(FATAL) << "Please supply a data file with --input=";
375 if (!absl::GetFlag(FLAGS_params).empty()) {
376 CHECK(google::protobuf::TextFormat::MergeFromString(
377 absl::GetFlag(FLAGS_params), ¶meters))
378 << absl::GetFlag(FLAGS_params);
382 google::protobuf::Arena arena;
384 google::protobuf::Arena::Create<CpModelProto>(&arena);
385 std::shared_ptr<LastSolutionPrinter> last_solution_printer;
386 if (absl::GetFlag(FLAGS_competition_mode)) {
387 last_solution_printer = std::make_shared<LastSolutionPrinter>();
389 if (!LoadProblem(absl::GetFlag(FLAGS_input), absl::GetFlag(FLAGS_hint_file),
390 absl::GetFlag(FLAGS_domain_file), cp_model, &model,
391 ¶meters, last_solution_printer)) {
392 if (!absl::GetFlag(FLAGS_competition_mode)) {
393 LOG(FATAL) << "Cannot load file '" << absl::GetFlag(FLAGS_input) << "'.";
399 if (absl::GetFlag(FLAGS_fingerprint_intermediate_solutions)) {
409 if (absl::GetFlag(FLAGS_competition_mode)) {
410 model.GetOrCreate<SigtermHandler>()->Register([last_solution_printer]() {
411 last_solution_printer->MaybePrintLastSolution();
418 if (!absl::GetFlag(FLAGS_output).empty()) {
419 if (absl::EndsWith(absl::GetFlag(FLAGS_output), "txt")) {
442 "This program solves a given problem with the CP-SAT solver.";
444int main(int argc, char** argv) {
446 absl::SetProgramUsageMessage(kUsage);
447 absl::ParseCommandLine(argc, argv);
Domain IntersectionWith(const Domain &domain) const
void add_domain(::int64_t value)
void set_log_search_progress(bool value)
absl::Status SetBinaryProto(absl::string_view file_name, const google::protobuf::Message &proto, Options options)
absl::string_view Extension(absl::string_view path)
absl::Status SetTextProto(absl::string_view file_name, const google::protobuf::Message &proto, Options options)
absl::string_view Stem(absl::string_view path)
absl::string_view Basename(absl::string_view path)
constexpr uint64_t kDefaultFingerprintSeed
uint64_t FingerprintRepeatedField(const google::protobuf::RepeatedField< T > &sequence, uint64_t seed)
std::function< SatParameters(Model *)> NewSatParameters(absl::string_view params)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
CpSolverResponse SolveCpModel(const CpModelProto &model_proto, Model *model)
std::function< void(Model *)> NewFeasibleSolutionLogCallback(const std::function< std::string(const CpSolverResponse &response)> &callback)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
std::function< void(Model *)> NewFeasibleSolutionObserver(const std::function< void(const CpSolverResponse &response)> &callback)
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
absl::Status ReadFileToProto(absl::string_view filename, google::protobuf::Message *proto, bool allow_partial)
static int input(yyscan_t yyscanner)
int main(int argc, char **argv)
Definition sat_runner.cc:444
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 CpModelProto proto (binary or text).")
static const char kUsage[]
Definition sat_runner.cc:440