OR-Tools  9.1
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"
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) {
56 return has_objective ? MPSOLVER_FEASIBLE : MPSOLVER_OPTIMAL;
60 return MPSOLVER_OPTIMAL;
61 default: {
62 }
63 }
64 return MPSOLVER_ABNORMAL;
65}
66} // namespace
67
68absl::StatusOr<MPSolutionResponse> SatSolveProto(
69 MPModelRequest request, std::atomic<bool>* interrupt_solve,
70 std::function<void(const std::string&)> logging_callback,
71 std::function<void(const MPSolution&)> solution_callback) {
72 sat::SatParameters params;
74 if (request.has_solver_specific_parameters()) {
75 // See EncodeSatParametersAsString() documentation.
76 if (kProtoLiteSatParameters) {
77 if (!params.MergeFromString(request.solver_specific_parameters())) {
78 return absl::InvalidArgumentError(
79 "solver_specific_parameters is not a valid binary stream of the "
80 "SatParameters proto");
81 }
82 } else {
84 request.solver_specific_parameters(), &params)) {
85 return absl::InvalidArgumentError(
86 "solver_specific_parameters is not a valid textual representation "
87 "of the SatParameters proto");
88 }
89 }
90 }
91 if (request.has_solver_time_limit_seconds()) {
93 }
95
96 // TODO(user): We do not support all the parameters here. In particular the
97 // logs before the solver is called will not be appended to the response. Fix
98 // that, and remove code duplication for the logger config. One way should be
99 // to not touch/configure anything if the logger is already created while
100 // calling SolveCpModel() and call a common config function from here or from
101 // inside Solve()?
102 SolverLogger logger;
103 if (logging_callback != nullptr) {
104 logger.AddInfoLoggingCallback(logging_callback);
105 }
106 logger.EnableLogging(params.log_search_progress());
107 logger.SetLogToStdOut(params.log_to_stdout());
108
111 &response)) {
112 if (logger.LoggingIsEnabled()) {
113 // This is needed for our benchmark scripts.
114 sat::CpSolverResponse cp_response;
116 SOLVER_LOG(&logger, CpSolverResponseStats(cp_response));
117 }
118 return response;
119 }
120
121 // Note(user): the LP presolvers API is a bit weird and keep a reference to
122 // the given GlopParameters, so we need to make sure it outlive them.
123 const glop::GlopParameters glop_params;
124 MPModelProto* const mp_model = request.mutable_model();
125 std::vector<std::unique_ptr<glop::Preprocessor>> for_postsolve;
126 if (!params.enumerate_all_solutions()) {
127 const auto status =
128 ApplyMipPresolveSteps(glop_params, mp_model, &for_postsolve, &logger);
130 if (params.log_search_progress()) {
131 // This is needed for our benchmark scripts.
132 sat::CpSolverResponse cp_response;
134 LOG(INFO) << CpSolverResponseStats(cp_response);
135 }
137 response.set_status_str("Problem proven infeasible during MIP presolve");
138 return response;
139 }
140 }
141
142 // We need to do that before the automatic detection of integers.
143 RemoveNearZeroTerms(params, mp_model, &logger);
144
145 SOLVER_LOG(&logger, "");
146 SOLVER_LOG(&logger, "Scaling to pure integer problem.");
147
148 const int num_variables = mp_model->variable_size();
149 std::vector<double> var_scaling(num_variables, 1.0);
151 var_scaling = sat::DetectImpliedIntegers(mp_model, &logger);
152 }
153 if (params.mip_var_scaling() != 1.0) {
154 const std::vector<double> other_scaling = sat::ScaleContinuousVariables(
155 params.mip_var_scaling(), params.mip_max_bound(), mp_model);
156 for (int i = 0; i < var_scaling.size(); ++i) {
157 var_scaling[i] *= other_scaling[i];
158 }
159 }
160
161 sat::CpModelProto cp_model;
162 if (!ConvertMPModelProtoToCpModelProto(params, *mp_model, &cp_model,
163 &logger)) {
164 if (params.log_search_progress()) {
165 // This is needed for our benchmark scripts.
166 sat::CpSolverResponse cp_response;
168 LOG(INFO) << CpSolverResponseStats(cp_response);
169 }
171 response.set_status_str("Failed to convert model into CP-SAT model");
172 return response;
173 }
174 DCHECK_EQ(cp_model.variables().size(), var_scaling.size());
175 DCHECK_EQ(cp_model.variables().size(), mp_model->variable().size());
176
177 // Copy and scale the hint if there is one.
178 if (request.model().has_solution_hint()) {
179 auto* cp_model_hint = cp_model.mutable_solution_hint();
180 const int size = request.model().solution_hint().var_index().size();
181 for (int i = 0; i < size; ++i) {
182 const int var = request.model().solution_hint().var_index(i);
183 if (var >= var_scaling.size()) continue;
184
185 // To handle weird hint input values, we cap any large value to +/-
186 // mip_max_bound() which is also the min/max value of any variable once
187 // scaled.
188 double value =
189 request.model().solution_hint().var_value(i) * var_scaling[var];
190 if (std::abs(value) > params.mip_max_bound()) {
191 value = value > 0 ? params.mip_max_bound() : -params.mip_max_bound();
192 }
193
194 cp_model_hint->add_vars(var);
195 cp_model_hint->add_values(static_cast<int64_t>(std::round(value)));
196 }
197 }
198
199 // We no longer need the request. Reclaim its memory.
200 const int old_num_variables = mp_model->variable().size();
201 const int old_num_constraints = mp_model->constraint().size();
202 request.Clear();
203
204 // Configure model.
205 sat::Model sat_model;
206 sat_model.Register<SolverLogger>(&logger);
207 sat_model.Add(NewSatParameters(params));
208 if (interrupt_solve != nullptr) {
209 sat_model.GetOrCreate<TimeLimit>()->RegisterExternalBooleanAsLimit(
210 interrupt_solve);
211 }
212
213 auto post_solve = [&](const sat::CpSolverResponse& cp_response) {
214 MPSolution mp_solution;
215 mp_solution.set_objective_value(cp_response.objective_value());
216 // Postsolve the bound shift and scaling.
217 glop::ProblemSolution glop_solution((glop::RowIndex(old_num_constraints)),
218 (glop::ColIndex(old_num_variables)));
219 for (int v = 0; v < glop_solution.primal_values.size(); ++v) {
220 glop_solution.primal_values[glop::ColIndex(v)] =
221 static_cast<double>(cp_response.solution(v)) / var_scaling[v];
222 }
223 for (int i = for_postsolve.size(); --i >= 0;) {
224 for_postsolve[i]->RecoverSolution(&glop_solution);
225 }
226 for (int v = 0; v < glop_solution.primal_values.size(); ++v) {
227 mp_solution.add_variable_value(
228 glop_solution.primal_values[glop::ColIndex(v)]);
229 }
230 return mp_solution;
231 };
232
233 if (solution_callback != nullptr) {
235 [&](const sat::CpSolverResponse& cp_response) {
236 solution_callback(post_solve(cp_response));
237 }));
238 }
239
240 // Solve.
241 const sat::CpSolverResponse cp_response =
242 sat::SolveCpModel(cp_model, &sat_model);
243
244 // Convert the response.
245 //
246 // TODO(user): Implement the row and column status.
247 response.set_status(
248 ToMPSolverResponseStatus(cp_response.status(), cp_model.has_objective()));
249 if (response.status() == MPSOLVER_FEASIBLE ||
250 response.status() == MPSOLVER_OPTIMAL) {
251 response.set_objective_value(cp_response.objective_value());
252 response.set_best_objective_bound(cp_response.best_objective_bound());
253 MPSolution post_solved_solution = post_solve(cp_response);
254 *response.mutable_variable_value() =
255 std::move(*post_solved_solution.mutable_variable_value());
256 }
257
258 return response;
259}
260
262 if (kProtoLiteSatParameters) {
263 // Here we use SerializeToString() instead of SerializeAsString() since the
264 // later ignores errors and returns an empty string instead (which can be a
265 // valid value when no fields are set).
266 std::string bytes;
267 CHECK(parameters.SerializeToString(&bytes));
268 return bytes;
269 }
270
271 return parameters.ShortDebugString();
272}
273
274} // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:491
#define LOG(severity)
Definition: base/logging.h:416
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:886
const ::operations_research::MPConstraintProto & constraint(int index) const
const ::operations_research::MPVariableProto & variable(int index) const
const ::operations_research::PartialVariableAssignment & solution_hint() const
::operations_research::MPModelProto * mutable_model()
const std::string & solver_specific_parameters() const
PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final
const ::operations_research::MPModelProto & model() const
::PROTOBUF_NAMESPACE_ID::RepeatedField< double > * mutable_variable_value()
void set_objective_value(double value)
::PROTOBUF_NAMESPACE_ID::int32 var_index(int index) const
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:105
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
::operations_research::sat::PartialVariableAssignment * mutable_solution_hint()
void set_status(::operations_research::sat::CpSolverStatus value)
::operations_research::sat::CpSolverStatus status() const
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
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:81
void Register(T *non_owned_class)
Register a non-owned class that will be "singleton" in the model.
Definition: sat/model.h:169
T * GetOrCreate()
Returns an object of type T that is unique to this model (like a "local" singleton).
Definition: sat/model.h:106
void set_linearization_level(::PROTOBUF_NAMESPACE_ID::int32 value)
SatParameters parameters
SharedResponseManager * response
int64_t value
IntVar * var
Definition: expr_array.cc:1874
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)
CpSolverResponse SolveCpModel(const CpModelProto &model_proto, Model *model)
Solves the given CpModelProto.
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...
MPSolverResponseStatus ApplyMipPresolveSteps(const glop::GlopParameters &glop_params, MPModelProto *model, std::vector< std::unique_ptr< glop::Preprocessor > > *for_postsolve, SolverLogger *logger)
bool ProtobufTextFormatMergeFromString(const std::string &proto_text_string, ProtoType *proto)
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)
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:63