OR-Tools  9.3
sat_proto_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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
15
16#include <cstdint>
17#include <vector>
18
19#include "absl/status/statusor.h"
20#include "ortools/linear_solver/linear_solver.pb.h"
24#include "ortools/sat/cp_model.pb.h"
27#include "ortools/sat/sat_parameters.pb.h"
30
31namespace operations_research {
32
33namespace {
34
35#if defined(PROTOBUF_INTERNAL_IMPL)
36using google::protobuf::Message;
37#else
38using google::protobuf::Message;
39#endif
40
41// Proto-lite disables some features of protos (see
42// go/abp-libraries/proto2-lite) and messages inherit from MessageLite directly
43// instead of inheriting from Message (which is itself a specialization of
44// MessageLite).
45constexpr bool kProtoLiteSatParameters =
47
48MPSolverResponseStatus ToMPSolverResponseStatus(sat::CpSolverStatus status,
49 bool has_objective) {
50 switch (status) {
51 case sat::CpSolverStatus::UNKNOWN:
52 return MPSOLVER_NOT_SOLVED;
53 case sat::CpSolverStatus::MODEL_INVALID:
54 return MPSOLVER_MODEL_INVALID;
55 case sat::CpSolverStatus::FEASIBLE:
56 return MPSOLVER_FEASIBLE;
57 case sat::CpSolverStatus::INFEASIBLE:
58 return MPSOLVER_INFEASIBLE;
59 case sat::CpSolverStatus::OPTIMAL:
60 return MPSOLVER_OPTIMAL;
61 default: {
62 }
63 }
64 return MPSOLVER_ABNORMAL;
65}
66
67sat::CpSolverStatus FromMPSolverResponseStatus(MPSolverResponseStatus status) {
68 switch (status) {
69 case MPSolverResponseStatus::MPSOLVER_OPTIMAL:
70 return sat::OPTIMAL;
71 case MPSolverResponseStatus::MPSOLVER_INFEASIBLE:
72 return sat::INFEASIBLE;
73 case MPSolverResponseStatus::MPSOLVER_MODEL_INVALID:
74 return sat::MODEL_INVALID;
75 default: {
76 }
77 }
78 return sat::UNKNOWN;
79}
80
81MPSolutionResponse InfeasibleResponse(SolverLogger& logger,
82 std::string message) {
83 // This is needed for our benchmark scripts.
84 MPSolutionResponse response;
85 if (logger.LoggingIsEnabled()) {
86 sat::CpSolverResponse cp_response;
87 cp_response.set_status(sat::CpSolverStatus::INFEASIBLE);
88 SOLVER_LOG(&logger, CpSolverResponseStats(cp_response));
89 }
90 response.set_status(MPSolverResponseStatus::MPSOLVER_INFEASIBLE);
91 response.set_status_str(message);
92 return response;
93}
94
95MPSolutionResponse ModelInvalidResponse(SolverLogger& logger,
96 std::string message) {
97 // This is needed for our benchmark scripts.
98 MPSolutionResponse response;
99 if (logger.LoggingIsEnabled()) {
100 sat::CpSolverResponse cp_response;
101 cp_response.set_status(sat::CpSolverStatus::MODEL_INVALID);
102 SOLVER_LOG(&logger, CpSolverResponseStats(cp_response));
103 }
104 response.set_status(MPSolverResponseStatus::MPSOLVER_MODEL_INVALID);
105 response.set_status_str(message);
106 return response;
107}
108
109} // namespace
110
111absl::StatusOr<MPSolutionResponse> SatSolveProto(
112 MPModelRequest request, std::atomic<bool>* interrupt_solve,
113 std::function<void(const std::string&)> logging_callback,
114 std::function<void(const MPSolution&)> solution_callback) {
115 sat::SatParameters params;
116 params.set_log_search_progress(request.enable_internal_solver_output());
117 // Set it now so that it can be overwritten by the solver specific parameters.
118 if (request.has_solver_specific_parameters()) {
119 // See EncodeSatParametersAsString() documentation.
120 if (kProtoLiteSatParameters) {
121 if (!params.MergeFromString(request.solver_specific_parameters())) {
122 return absl::InvalidArgumentError(
123 "solver_specific_parameters is not a valid binary stream of the "
124 "SatParameters proto");
125 }
126 } else {
128 request.solver_specific_parameters(), &params)) {
129 return absl::InvalidArgumentError(
130 "solver_specific_parameters is not a valid textual representation "
131 "of the SatParameters proto");
132 }
133 }
134 }
135 if (request.has_solver_time_limit_seconds()) {
136 params.set_max_time_in_seconds(request.solver_time_limit_seconds());
137 }
138
139 // TODO(user): We do not support all the parameters here. In particular the
140 // logs before the solver is called will not be appended to the response. Fix
141 // that, and remove code duplication for the logger config. One way should be
142 // to not touch/configure anything if the logger is already created while
143 // calling SolveCpModel() and call a common config function from here or from
144 // inside Solve()?
145 SolverLogger logger;
146 if (logging_callback != nullptr) {
147 logger.AddInfoLoggingCallback(logging_callback);
148 }
149 logger.EnableLogging(params.log_search_progress());
150 logger.SetLogToStdOut(params.log_to_stdout());
151
152 // Model validation and delta handling.
153 MPSolutionResponse response;
155 &response)) {
156 // Note that the ExtractValidMPModelInPlaceOrPopulateResponseStatus() can
157 // also close trivial model (empty or trivially infeasible). So this is not
158 // always the MODEL_INVALID status.
159 //
160 // The logging is only needed for our benchmark script, so we use UNKNOWN
161 // here, but we could log the proper status instead.
162 if (logger.LoggingIsEnabled()) {
163 sat::CpSolverResponse cp_response;
164 cp_response.set_status(FromMPSolverResponseStatus(response.status()));
165 SOLVER_LOG(&logger, CpSolverResponseStats(cp_response));
166 }
167 return response;
168 }
169
170 // We start by some extra validation since our code do not accept any kind
171 // of input.
172 MPModelProto* const mp_model = request.mutable_model();
173 if (!sat::MPModelProtoValidationBeforeConversion(params, *mp_model,
174 &logger)) {
175 return ModelInvalidResponse(logger, "Extra CP-SAT validation failed.");
176 }
177
178 // This is good to do before any presolve.
179 if (!sat::MakeBoundsOfIntegerVariablesInteger(params, mp_model, &logger)) {
180 return InfeasibleResponse(logger,
181 "An integer variable has an empty domain");
182 }
183
184 // Note(user): the LP presolvers API is a bit weird and keep a reference to
185 // the given GlopParameters, so we need to make sure it outlive them.
186 const glop::GlopParameters glop_params;
187 std::vector<std::unique_ptr<glop::Preprocessor>> for_postsolve;
188 if (!params.enumerate_all_solutions()) {
190 ApplyMipPresolveSteps(glop_params, mp_model, &for_postsolve, &logger);
191 switch (status) {
193 // Continue with the solve.
194 break;
196 return InfeasibleResponse(
197 logger, "Problem proven infeasible during MIP presolve");
199 return ModelInvalidResponse(
200 logger, "Problem detected invalid during MIP presolve");
201 default:
202 // TODO(user): We put the INFEASIBLE_OR_UNBOUNBED case here since there
203 // is no return status that exactly matches it.
204 if (params.log_search_progress()) {
205 // This is needed for our benchmark scripts.
206 sat::CpSolverResponse cp_response;
207 cp_response.set_status(sat::CpSolverStatus::UNKNOWN);
208 LOG(INFO) << CpSolverResponseStats(cp_response);
209 }
210 response.set_status(MPSolverResponseStatus::MPSOLVER_UNKNOWN_STATUS);
212 response.set_status_str(
213 "Problem proven infeasible or unbounded during MIP presolve");
214 }
215 return response;
216 }
217 }
218
219 // We need to do that before the automatic detection of integers.
220 RemoveNearZeroTerms(params, mp_model, &logger);
221
222 SOLVER_LOG(&logger, "");
223 SOLVER_LOG(&logger, "Scaling to pure integer problem.");
224
225 const int num_variables = mp_model->variable_size();
226 std::vector<double> var_scaling(num_variables, 1.0);
227 if (params.mip_automatically_scale_variables()) {
228 var_scaling = sat::DetectImpliedIntegers(mp_model, &logger);
229 if (!sat::MakeBoundsOfIntegerVariablesInteger(params, mp_model, &logger)) {
230 return InfeasibleResponse(
231 logger, "A detected integer variable has an empty domain");
232 }
233 }
234 if (params.mip_var_scaling() != 1.0) {
235 const std::vector<double> other_scaling = sat::ScaleContinuousVariables(
236 params.mip_var_scaling(), params.mip_max_bound(), mp_model);
237 for (int i = 0; i < var_scaling.size(); ++i) {
238 var_scaling[i] *= other_scaling[i];
239 }
240 }
241
242 sat::CpModelProto cp_model;
243 if (!ConvertMPModelProtoToCpModelProto(params, *mp_model, &cp_model,
244 &logger)) {
245 if (params.log_search_progress()) {
246 // This is needed for our benchmark scripts.
247 sat::CpSolverResponse cp_response;
248 cp_response.set_status(sat::CpSolverStatus::MODEL_INVALID);
249 LOG(INFO) << CpSolverResponseStats(cp_response);
250 }
251 response.set_status(MPSOLVER_MODEL_INVALID);
252 response.set_status_str("Failed to convert model into CP-SAT model");
253 return response;
254 }
255 DCHECK_EQ(cp_model.variables().size(), var_scaling.size());
256 DCHECK_EQ(cp_model.variables().size(), mp_model->variable().size());
257
258 // Copy and scale the hint if there is one.
259 if (request.model().has_solution_hint()) {
260 auto* cp_model_hint = cp_model.mutable_solution_hint();
261 const int size = request.model().solution_hint().var_index().size();
262 for (int i = 0; i < size; ++i) {
263 const int var = request.model().solution_hint().var_index(i);
264 if (var >= var_scaling.size()) continue;
265
266 // To handle weird hint input values, we cap any large value to +/-
267 // mip_max_bound() which is also the min/max value of any variable once
268 // scaled.
269 double value =
270 request.model().solution_hint().var_value(i) * var_scaling[var];
271 if (std::abs(value) > params.mip_max_bound()) {
272 value = value > 0 ? params.mip_max_bound() : -params.mip_max_bound();
273 }
274
275 cp_model_hint->add_vars(var);
276 cp_model_hint->add_values(static_cast<int64_t>(std::round(value)));
277 }
278 }
279
280 // We no longer need the request. Reclaim its memory.
281 const int old_num_variables = mp_model->variable().size();
282 const int old_num_constraints = mp_model->constraint().size();
283 request.Clear();
284
285 // Configure model.
286 sat::Model sat_model;
287 sat_model.Register<SolverLogger>(&logger);
288 sat_model.Add(NewSatParameters(params));
289 if (interrupt_solve != nullptr) {
290 sat_model.GetOrCreate<TimeLimit>()->RegisterExternalBooleanAsLimit(
291 interrupt_solve);
292 }
293
294 auto post_solve = [&](const sat::CpSolverResponse& cp_response) {
295 MPSolution mp_solution;
296 mp_solution.set_objective_value(cp_response.objective_value());
297 // Postsolve the bound shift and scaling.
298 glop::ProblemSolution glop_solution((glop::RowIndex(old_num_constraints)),
299 (glop::ColIndex(old_num_variables)));
300 for (int v = 0; v < glop_solution.primal_values.size(); ++v) {
301 glop_solution.primal_values[glop::ColIndex(v)] =
302 static_cast<double>(cp_response.solution(v)) / var_scaling[v];
303 }
304 for (int i = for_postsolve.size(); --i >= 0;) {
305 for_postsolve[i]->RecoverSolution(&glop_solution);
306 }
307 for (int v = 0; v < glop_solution.primal_values.size(); ++v) {
308 mp_solution.add_variable_value(
309 glop_solution.primal_values[glop::ColIndex(v)]);
310 }
311 return mp_solution;
312 };
313
314 if (solution_callback != nullptr) {
316 [&](const sat::CpSolverResponse& cp_response) {
317 solution_callback(post_solve(cp_response));
318 }));
319 }
320
321 // Solve.
322 const sat::CpSolverResponse cp_response =
323 sat::SolveCpModel(cp_model, &sat_model);
324
325 // Convert the response.
326 //
327 // TODO(user): Implement the row and column status.
328 response.set_status(
329 ToMPSolverResponseStatus(cp_response.status(), cp_model.has_objective()));
330 if (response.status() == MPSOLVER_FEASIBLE ||
331 response.status() == MPSOLVER_OPTIMAL) {
332 response.set_objective_value(cp_response.objective_value());
333 response.set_best_objective_bound(cp_response.best_objective_bound());
334 MPSolution post_solved_solution = post_solve(cp_response);
335 *response.mutable_variable_value() =
336 std::move(*post_solved_solution.mutable_variable_value());
337 }
338
339 // Copy and postsolve any additional solutions.
340 //
341 // TODO(user): Remove the postsolve hack of copying to a response.
342 for (int i = 0; i < cp_response.additional_solutions().size(); ++i) {
343 sat::CpSolverResponse temp;
344 *temp.mutable_solution() = cp_response.additional_solutions(i).values();
345 MPSolution post_solved_solution = post_solve(temp);
346 *(response.add_additional_solutions()->mutable_variable_value()) =
347 std::move(*post_solved_solution.mutable_variable_value());
348 }
349
350 return response;
351}
352
353std::string EncodeSatParametersAsString(const sat::SatParameters& parameters) {
354 if (kProtoLiteSatParameters) {
355 // Here we use SerializeToString() instead of SerializeAsString() since the
356 // later ignores errors and returns an empty string instead (which can be a
357 // valid value when no fields are set).
358 std::string bytes;
359 CHECK(parameters.SerializeToString(&bytes));
360 return bytes;
361 }
362
363 return parameters.ShortDebugString();
364}
365
366} // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:495
#define LOG(severity)
Definition: base/logging.h:420
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:891
void SetLogToStdOut(bool enable)
Definition: util/logging.h:45
void AddInfoLoggingCallback(std::function< void(const std::string &message)> callback)
Definition: util/logging.cc:23
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
T Add(std::function< T(Model *)> f)
This makes it possible to have a nicer API on the client side, and it allows both of these forms:
Definition: sat/model.h:85
void Register(T *non_owned_class)
Register a non-owned class that will be "singleton" in the model.
Definition: sat/model.h:173
T * GetOrCreate()
Returns an object of type T that is unique to this model (like a "local" singleton).
Definition: sat/model.h:110
SatParameters parameters
SharedResponseManager * response
int64_t value
IntVar * var
Definition: expr_array.cc:1874
absl::Status status
Definition: g_gurobi.cc:35
const int INFO
Definition: log_severity.h:31
std::function< void(Model *)> NewFeasibleSolutionObserver(const std::function< void(const CpSolverResponse &response)> &observer)
Creates a solution observer with the model with model.Add(NewFeasibleSolutionObserver([](response){....
std::function< SatParameters(Model *)> NewSatParameters(const std::string &params)
Creates parameters for the solver, which you can add to the model with.
std::string CpSolverResponseStats(const CpSolverResponse &response, bool has_objective)
Returns a string with some statistics on the solver response.
void RemoveNearZeroTerms(const SatParameters &params, MPModelProto *mp_model, SolverLogger *logger)
bool ConvertMPModelProtoToCpModelProto(const SatParameters &params, const MPModelProto &mp_model, CpModelProto *cp_model, SolverLogger *logger)
bool MPModelProtoValidationBeforeConversion(const SatParameters &params, const MPModelProto &mp_model, SolverLogger *logger)
CpSolverResponse SolveCpModel(const CpModelProto &model_proto, Model *model)
Solves the given CpModelProto.
bool MakeBoundsOfIntegerVariablesInteger(const SatParameters &params, MPModelProto *mp_model, SolverLogger *logger)
std::vector< double > ScaleContinuousVariables(double scaling, double max_bound, MPModelProto *mp_model)
std::vector< double > DetectImpliedIntegers(MPModelProto *mp_model, SolverLogger *logger)
Collection of objects used to extend the Constraint Solver library.
bool ExtractValidMPModelInPlaceOrPopulateResponseStatus(MPModelRequest *request, MPSolutionResponse *response)
Like ExtractValidMPModelOrPopulateResponseStatus(), but works in-place: if the MPModel needed extract...
bool ProtobufTextFormatMergeFromString(const std::string &proto_text_string, ProtoType *proto)
glop::ProblemStatus ApplyMipPresolveSteps(const glop::GlopParameters &glop_params, MPModelProto *model, std::vector< std::unique_ptr< glop::Preprocessor > > *for_postsolve, SolverLogger *logger)
absl::StatusOr< MPSolutionResponse > SatSolveProto(MPModelRequest request, std::atomic< bool > *interrupt_solve, std::function< void(const std::string &)> logging_callback, std::function< void(const MPSolution &)> solution_callback)
std::string EncodeSatParametersAsString(const sat::SatParameters &parameters)
std::string message
Definition: trace.cc:398
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69