Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
sat_runner.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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 <cstdlib>
15#include <string>
16
17#include "absl/flags/flag.h"
18#include "absl/flags/parse.h"
19#include "absl/flags/usage.h"
20#include "absl/log/check.h"
21#include "absl/log/flags.h"
22#include "absl/log/initialize.h"
23#include "absl/strings/match.h"
24#include "absl/strings/str_format.h"
25#include "absl/strings/string_view.h"
26#include "google/protobuf/arena.h"
27#include "google/protobuf/text_format.h"
31#include "ortools/base/path.h"
33#include "ortools/sat/boolean_problem.pb.h"
34#include "ortools/sat/cp_model.pb.h"
37#include "ortools/sat/model.h"
40#include "ortools/sat/sat_parameters.pb.h"
43
45 std::string, input, "",
46 "Required: input file of the problem to solve. Many format are supported:"
47 ".cnf (sat, max-sat, weighted max-sat), .opb (pseudo-boolean sat/optim) "
48 "and by default the CpModelProto proto (binary or text).");
49
51 std::string, hint_file, "",
52 "Protobuf file containing a CpModelResponse. The solution will be used as a"
53 " hint to bootstrap the search.");
54
55ABSL_FLAG(std::string, domain_file, "",
56 "Protobuf file containing a CpModelResponse. If present, the "
57 "tightened models will be used to reduce the domain of variables.");
58
59ABSL_FLAG(std::string, output, "",
60 "If non-empty, write the response there. By default it uses the "
61 "binary format except if the file extension is '.txt'.");
62
63ABSL_FLAG(std::string, params, "",
64 "Parameters for the sat solver in a text format of the "
65 "SatParameters proto, example: --params=use_conflicts:true.");
66
67ABSL_FLAG(bool, wcnf_use_strong_slack, true,
68 "If true, when we add a slack variable to reify a soft clause, we "
69 "enforce the fact that when it is true, the clause must be false.");
70ABSL_FLAG(bool, fingerprint_intermediate_solutions, false,
71 "Attach the fingerprint of intermediate solutions to the output.");
72
73namespace operations_research {
74namespace sat {
75namespace {
76
77void TryToRemoveSuffix(absl::string_view suffix, std::string* str) {
78 if (file::Extension(*str) == suffix) *str = file::Stem(*str);
79}
80
81std::string ExtractName(absl::string_view full_filename) {
82 std::string filename = std::string(file::Basename(full_filename));
83 // The order is important as '.pb.txt.gz' is a common suffix.
84 TryToRemoveSuffix("gz", &filename);
85 TryToRemoveSuffix("txt", &filename);
86 TryToRemoveSuffix("pb", &filename);
87 TryToRemoveSuffix("pbtxt", &filename);
88 TryToRemoveSuffix("proto", &filename);
89 TryToRemoveSuffix("prototxt", &filename);
90 TryToRemoveSuffix("textproto", &filename);
91 TryToRemoveSuffix("bin", &filename);
92 return filename;
93}
94
95bool LoadProblem(const std::string& filename, absl::string_view hint_file,
96 absl::string_view domain_file, CpModelProto* cp_model) {
97 if (absl::EndsWith(filename, ".opb") ||
98 absl::EndsWith(filename, ".opb.bz2")) {
99 OpbReader reader;
100 LinearBooleanProblem problem;
101 if (!reader.Load(filename, &problem)) {
102 LOG(FATAL) << "Cannot load file '" << filename << "'.";
103 }
104 *cp_model = BooleanProblemToCpModelproto(problem);
105 } else if (absl::EndsWith(filename, ".cnf") ||
106 absl::EndsWith(filename, ".cnf.xz") ||
107 absl::EndsWith(filename, ".cnf.gz") ||
108 absl::EndsWith(filename, ".wcnf") ||
109 absl::EndsWith(filename, ".wcnf.xz") ||
110 absl::EndsWith(filename, ".wcnf.gz")) {
111 SatCnfReader reader(absl::GetFlag(FLAGS_wcnf_use_strong_slack));
112 if (!reader.Load(filename, cp_model)) {
113 LOG(FATAL) << "Cannot load file '" << filename << "'.";
114 }
115 } else {
116 CHECK_OK(ReadFileToProto(filename, cp_model));
117 }
118
119 // Read the hint file.
120 if (!hint_file.empty()) {
121 CpSolverResponse response;
122 CHECK_OK(ReadFileToProto(hint_file, &response));
123 if (!response.solution().empty()) {
124 CHECK_EQ(response.solution_size(), cp_model->variables_size())
125 << "The hint from the response proto is not compatible with the "
126 "model proto";
127
128 cp_model->clear_solution_hint();
129 for (int i = 0; i < cp_model->variables_size(); ++i) {
130 cp_model->mutable_solution_hint()->add_vars(i);
131 cp_model->mutable_solution_hint()->add_values(response.solution(i));
132 }
133 } else {
134 LOG(INFO) << "The response proto has no solutions, ignoring.";
135 }
136 }
137
138 // Read the tightened domain file.
139 if (!domain_file.empty()) {
140 CpSolverResponse response;
141 CHECK_OK(ReadFileToProto(domain_file, &response));
142 if (!response.tightened_variables().empty()) {
143 CHECK_EQ(response.tightened_variables_size(), cp_model->variables_size())
144 << "The tighened variables from the response proto is not "
145 "compatible with the model proto";
146
147 for (int i = 0; i < cp_model->variables_size(); ++i) {
148 IntegerVariableProto* var_proto = cp_model->mutable_variables(i);
149 const Domain tightened_domain =
150 ReadDomainFromProto(response.tightened_variables(i));
151 const Domain new_domain =
152 ReadDomainFromProto(*var_proto).IntersectionWith(tightened_domain);
153 FillDomainInProto(new_domain, var_proto);
154 }
155 } else {
156 LOG(INFO) << "The response proto has no tightened variable domains, "
157 "ignoring.";
158 }
159 }
160
161 // Set the name if not present.
162 if (cp_model->name().empty()) {
163 cp_model->set_name(ExtractName(filename));
164 }
165
166 return true;
167}
168
169int Run() {
170 SatParameters parameters;
171 if (absl::GetFlag(FLAGS_input).empty()) {
172 LOG(FATAL) << "Please supply a data file with --input=";
173 }
174
175 // Parse the --params flag.
176 parameters.set_log_search_progress(true);
177 if (!absl::GetFlag(FLAGS_params).empty()) {
178 CHECK(google::protobuf::TextFormat::MergeFromString(
179 absl::GetFlag(FLAGS_params), &parameters))
180 << absl::GetFlag(FLAGS_params);
181 }
182
183 // Read the problem.
184 google::protobuf::Arena arena;
185 CpModelProto* cp_model =
186 google::protobuf::Arena::Create<CpModelProto>(&arena);
187 if (!LoadProblem(absl::GetFlag(FLAGS_input), absl::GetFlag(FLAGS_hint_file),
188 absl::GetFlag(FLAGS_domain_file), cp_model)) {
189 CpSolverResponse response;
190 response.set_status(CpSolverStatus::MODEL_INVALID);
191 return EXIT_SUCCESS;
192 }
193
194 Model model;
196 if (absl::GetFlag(FLAGS_fingerprint_intermediate_solutions)) {
197 // Let's add a solution callback that will display the fingerprint of all
198 // solutions.
199 model.Add(NewFeasibleSolutionLogCallback([](const CpSolverResponse& r) {
200 return absl::StrFormat(
201 "fingerprint: %#x",
203 }));
204 }
205 const CpSolverResponse response = SolveCpModel(*cp_model, &model);
206
207 if (!absl::GetFlag(FLAGS_output).empty()) {
208 if (absl::EndsWith(absl::GetFlag(FLAGS_output), "txt")) {
209 CHECK_OK(file::SetTextProto(absl::GetFlag(FLAGS_output), response,
210 file::Defaults()));
211 } else {
212 CHECK_OK(file::SetBinaryProto(absl::GetFlag(FLAGS_output), response,
213 file::Defaults()));
214 }
215 }
216
217 // The SAT competition requires a particular exit code and since we don't
218 // really use it for any other purpose, we comply.
219 if (response.status() == CpSolverStatus::OPTIMAL) return 10;
220 if (response.status() == CpSolverStatus::FEASIBLE) return 10;
221 if (response.status() == CpSolverStatus::INFEASIBLE) return 20;
222 return EXIT_SUCCESS;
223}
224
225} // namespace
226} // namespace sat
227} // namespace operations_research
228
229static const char kUsage[] =
230 "Usage: see flags.\n"
231 "This program solves a given problem with the CP-SAT solver.";
232
233int main(int argc, char** argv) {
234 absl::InitializeLog();
235 absl::SetProgramUsageMessage(kUsage);
236 absl::ParseCommandLine(argc, argv);
237 return operations_research::sat::Run();
238}
Domain IntersectionWith(const Domain &domain) const
SatParameters parameters
GRBmodel * model
absl::Status SetTextProto(absl::string_view filename, const google::protobuf::Message &proto, Options options)
Definition file.cc:337
absl::string_view Extension(absl::string_view path)
Definition path.cc:133
absl::Status SetBinaryProto(absl::string_view filename, const google::protobuf::Message &proto, Options options)
Definition file.cc:360
absl::string_view Stem(absl::string_view path)
Definition path.cc:129
absl::string_view Basename(absl::string_view path)
Definition path.cc:109
Options Defaults()
Definition file.h:109
std::function< SatParameters(Model *)> NewSatParameters(const std::string &params)
constexpr uint64_t kDefaultFingerprintSeed
Default seed for fingerprints.
uint64_t FingerprintRepeatedField(const google::protobuf::RepeatedField< T > &sequence, uint64_t seed)
CpModelProto BooleanProblemToCpModelproto(const LinearBooleanProblem &problem)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a 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)
Reads a Domain from the domain field of a proto.
In SWIG mode, we don't want anything besides these top-level includes.
absl::Status ReadFileToProto(absl::string_view filename, google::protobuf::Message *proto, bool allow_partial)
Definition file_util.cc:53
static int input(yyscan_t yyscanner)
int main(int argc, char **argv)
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[]