OR-Tools  9.0
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"
26 #include "ortools/sat/lp_utils.h"
28 #include "ortools/util/logging.h"
30 
31 namespace operations_research {
32 
33 namespace {
34 
35 #if defined(PROTOBUF_INTERNAL_IMPL)
36 using google::protobuf::Message;
37 #else
38 using 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).
45 constexpr bool kProtoLiteSatParameters =
47 
48 MPSolverResponseStatus ToMPSolverResponseStatus(sat::CpSolverStatus status,
49  bool has_objective) {
50  switch (status) {
52  return MPSOLVER_NOT_SOLVED;
56  return has_objective ? MPSOLVER_FEASIBLE : MPSOLVER_OPTIMAL;
58  return MPSOLVER_INFEASIBLE;
60  return MPSOLVER_OPTIMAL;
61  default: {
62  }
63  }
64  return MPSOLVER_ABNORMAL;
65 }
66 } // namespace
67 
68 absl::StatusOr<MPSolutionResponse> SatSolveProto(
69  MPModelRequest request, std::atomic<bool>* interrupt_solve) {
70  // By default, we use 8 threads as it allows to try a good set of orthogonal
71  // parameters. This can be overridden by the user.
72  sat::SatParameters params;
73  params.set_num_search_workers(8);
74  params.set_log_search_progress(request.enable_internal_solver_output());
75  if (request.has_solver_specific_parameters()) {
76  // See EncodeSatParametersAsString() documentation.
77  if (kProtoLiteSatParameters) {
78  if (!params.MergeFromString(request.solver_specific_parameters())) {
79  return absl::InvalidArgumentError(
80  "solver_specific_parameters is not a valid binary stream of the "
81  "SatParameters proto");
82  }
83  } else {
85  request.solver_specific_parameters(), &params)) {
86  return absl::InvalidArgumentError(
87  "solver_specific_parameters is not a valid textual representation "
88  "of the SatParameters proto");
89  }
90  }
91  }
92  if (request.has_solver_time_limit_seconds()) {
93  params.set_max_time_in_seconds(
94  static_cast<double>(request.solver_time_limit_seconds()) / 1000.0);
95  }
96 
97  // TODO(user): We do not support all the parameters here. In particular the
98  // logs before the solver is called will not be appended to the response. Fix
99  // that, and remove code duplication for the logger config. One way should be
100  // to not touch/configure anything if the logger is already created while
101  // calling SolveCpModel() and call a common config function from here or from
102  // inside Solve()?
103  SolverLogger logger;
104  logger.EnableLogging(params.log_search_progress());
105  logger.SetLogToStdOut(params.log_to_stdout());
106 
107  MPSolutionResponse response;
109  &response)) {
110  if (logger.LoggingIsEnabled()) {
111  // This is needed for our benchmark scripts.
112  sat::CpSolverResponse cp_response;
113  cp_response.set_status(sat::CpSolverStatus::MODEL_INVALID);
114  SOLVER_LOG(&logger, CpSolverResponseStats(cp_response));
115  }
116  return response;
117  }
118 
119  // Note(user): the LP presolvers API is a bit weird and keep a reference to
120  // the given GlopParameters, so we need to make sure it outlive them.
121  const glop::GlopParameters glop_params;
122  MPModelProto* const mp_model = request.mutable_model();
123  std::vector<std::unique_ptr<glop::Preprocessor>> for_postsolve;
124  const auto status =
125  ApplyMipPresolveSteps(glop_params, mp_model, &for_postsolve, &logger);
127  if (params.log_search_progress()) {
128  // This is needed for our benchmark scripts.
129  sat::CpSolverResponse cp_response;
130  cp_response.set_status(sat::CpSolverStatus::INFEASIBLE);
131  LOG(INFO) << CpSolverResponseStats(cp_response);
132  }
134  response.set_status_str("Problem proven infeasible during MIP presolve");
135  return response;
136  }
137 
138  // We need to do that before the automatic detection of integers.
139  RemoveNearZeroTerms(params, mp_model, &logger);
140 
141  SOLVER_LOG(&logger, "");
142  SOLVER_LOG(&logger, "Scaling to pure integer problem.");
143 
144  const int num_variables = mp_model->variable_size();
145  std::vector<double> var_scaling(num_variables, 1.0);
146  if (params.mip_automatically_scale_variables()) {
147  var_scaling = sat::DetectImpliedIntegers(mp_model, &logger);
148  }
149  if (params.mip_var_scaling() != 1.0) {
150  const std::vector<double> other_scaling = sat::ScaleContinuousVariables(
151  params.mip_var_scaling(), params.mip_max_bound(), mp_model);
152  for (int i = 0; i < var_scaling.size(); ++i) {
153  var_scaling[i] *= other_scaling[i];
154  }
155  }
156 
157  sat::CpModelProto cp_model;
158  if (!ConvertMPModelProtoToCpModelProto(params, *mp_model, &cp_model,
159  &logger)) {
160  if (params.log_search_progress()) {
161  // This is needed for our benchmark scripts.
162  sat::CpSolverResponse cp_response;
163  cp_response.set_status(sat::CpSolverStatus::MODEL_INVALID);
164  LOG(INFO) << CpSolverResponseStats(cp_response);
165  }
166  response.set_status(MPSOLVER_MODEL_INVALID);
167  response.set_status_str("Failed to convert model into CP-SAT model");
168  return response;
169  }
170  DCHECK_EQ(cp_model.variables().size(), var_scaling.size());
171  DCHECK_EQ(cp_model.variables().size(), mp_model->variable().size());
172 
173  // Copy and scale the hint if there is one.
174  if (request.model().has_solution_hint()) {
175  auto* cp_model_hint = cp_model.mutable_solution_hint();
176  const int size = request.model().solution_hint().var_index().size();
177  for (int i = 0; i < size; ++i) {
178  const int var = request.model().solution_hint().var_index(i);
179  if (var >= var_scaling.size()) continue;
180 
181  // To handle weird hint input values, we cap any large value to +/-
182  // mip_max_bound() which is also the min/max value of any variable once
183  // scaled.
184  double value =
185  request.model().solution_hint().var_value(i) * var_scaling[var];
186  if (std::abs(value) > params.mip_max_bound()) {
187  value = value > 0 ? params.mip_max_bound() : -params.mip_max_bound();
188  }
189 
190  cp_model_hint->add_vars(var);
191  cp_model_hint->add_values(static_cast<int64_t>(std::round(value)));
192  }
193  }
194 
195  // We no longer need the request. Reclaim its memory.
196  const int old_num_variables = mp_model->variable().size();
197  const int old_num_constraints = mp_model->constraint().size();
198  request.Clear();
199 
200  // Configure model.
201  sat::Model sat_model;
202  sat_model.Register<SolverLogger>(&logger);
203  sat_model.Add(NewSatParameters(params));
204  if (interrupt_solve != nullptr) {
205  sat_model.GetOrCreate<TimeLimit>()->RegisterExternalBooleanAsLimit(
206  interrupt_solve);
207  }
208 
209  // Solve.
210  const sat::CpSolverResponse cp_response =
211  sat::SolveCpModel(cp_model, &sat_model);
212 
213  // Convert the response.
214  //
215  // TODO(user): Implement the row and column status.
216  response.set_status(
217  ToMPSolverResponseStatus(cp_response.status(), cp_model.has_objective()));
218  if (response.status() == MPSOLVER_FEASIBLE ||
219  response.status() == MPSOLVER_OPTIMAL) {
220  response.set_objective_value(cp_response.objective_value());
221  response.set_best_objective_bound(cp_response.best_objective_bound());
222 
223  // Postsolve the bound shift and scaling.
224  glop::ProblemSolution solution((glop::RowIndex(old_num_constraints)),
225  (glop::ColIndex(old_num_variables)));
226  for (int v = 0; v < solution.primal_values.size(); ++v) {
227  solution.primal_values[glop::ColIndex(v)] =
228  static_cast<double>(cp_response.solution(v)) / var_scaling[v];
229  }
230  for (int i = for_postsolve.size(); --i >= 0;) {
231  for_postsolve[i]->RecoverSolution(&solution);
232  }
233  for (int v = 0; v < solution.primal_values.size(); ++v) {
234  response.add_variable_value(solution.primal_values[glop::ColIndex(v)]);
235  }
236  }
237 
238  return response;
239 }
240 
241 std::string EncodeSatParametersAsString(const sat::SatParameters& parameters) {
242  if (kProtoLiteSatParameters) {
243  // Here we use SerializeToString() instead of SerializeAsString() since the
244  // later ignores errors and returns an empty string instead (which can be a
245  // valid value when no fields are set).
246  std::string bytes;
247  CHECK(parameters.SerializeToString(&bytes));
248  return bytes;
249  }
250 
251  return parameters.ShortDebugString();
252 }
253 
254 } // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:498
#define LOG(severity)
Definition: base/logging.h:423
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:893
void SetLogToStdOut(bool enable)
Definition: util/logging.h:45
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
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
SatParameters parameters
SharedResponseManager * response
int64_t value
IntVar * var
Definition: expr_array.cc:1874
const int INFO
Definition: log_severity.h:31
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...
bool ProtobufTextFormatMergeFromString(const std::string &proto_text_string, ProtoType *proto)
MPSolverResponseStatus ApplyMipPresolveSteps(const glop::GlopParameters &glop_params, MPModelProto *model, std::vector< std::unique_ptr< glop::Preprocessor >> *for_postsolve, SolverLogger *logger)
std::string EncodeSatParametersAsString(const sat::SatParameters &parameters)
absl::StatusOr< MPSolutionResponse > SatSolveProto(MPModelRequest request, std::atomic< bool > *interrupt_solve)
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:63