OR-Tools  9.0
cp_sat_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 <limits>
18 #include <memory>
19 #include <string>
20 #include <utility>
21 #include <vector>
22 
24 #include "ortools/base/logging.h"
25 #include "absl/memory/memory.h"
26 #include "absl/status/status.h"
27 #include "absl/status/statusor.h"
28 #include "absl/strings/str_cat.h"
29 #include "absl/strings/str_join.h"
30 #include "absl/time/time.h"
33 #include "ortools/math_opt/callback.pb.h"
35 #include "ortools/math_opt/model.pb.h"
36 #include "ortools/math_opt/model_parameters.pb.h"
37 #include "ortools/math_opt/model_update.pb.h"
38 #include "ortools/math_opt/parameters.pb.h"
40 #include "ortools/math_opt/result.pb.h"
41 #include "ortools/math_opt/solution.pb.h"
43 #include "ortools/math_opt/sparse_containers.pb.h"
47 #include "ortools/base/protoutil.h"
48 
49 namespace operations_research {
50 namespace math_opt {
51 
52 namespace {
53 
54 constexpr double kInf = std::numeric_limits<double>::infinity();
55 
56 void SetTrivialBounds(const bool maximize, SolveStatsProto& stats) {
57  stats.set_best_primal_bound(maximize ? -kInf : kInf);
58  stats.set_best_dual_bound(maximize ? kInf : -kInf);
59 }
60 
61 // Returns a list of warnings from parameter settings that were
62 // invalid/unsupported (specific to CP-SAT), one element per bad parameter.
63 std::vector<std::string> SetSolveParameters(
64  const SolveParametersProto& parameters, MPModelRequest& request) {
65  std::vector<std::string> warnings;
66  const CommonSolveParametersProto& common_parameters =
67  parameters.common_parameters();
68  if (common_parameters.has_time_limit()) {
69  request.set_solver_time_limit_seconds(absl::ToDoubleSeconds(
70  util_time::DecodeGoogleApiProto(common_parameters.time_limit())
71  .value()));
72  }
73  if (common_parameters.has_enable_output()) {
74  request.set_enable_internal_solver_output(
75  common_parameters.enable_output());
76  }
77 
78  // Build CP SAT parameters by first initializing them from the common
79  // parameters, and then using the values in `solver_specific_parameters` to
80  // overwrite them if necessary.
81  //
82  // We don't need to set max_time_in_seconds since we already pass it in the
83  // `request.solver_time_limit_seconds`. The logic of `SatSolveProto()` will
84  // apply the logic we want here.
85  sat::SatParameters sat_parameters;
86  if (common_parameters.has_random_seed()) {
87  sat_parameters.set_random_seed(common_parameters.random_seed());
88  }
89  if (common_parameters.has_threads()) {
90  sat_parameters.set_num_search_workers(common_parameters.threads());
91  }
92  if (common_parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
93  warnings.push_back(
94  absl::StrCat("Setting the LP Algorithm (was set to ",
95  ProtoEnumToString(common_parameters.lp_algorithm()),
96  ") is not supported for CP_SAT solver"));
97  }
98  if (common_parameters.presolve() != EMPHASIS_UNSPECIFIED) {
99  switch (common_parameters.presolve()) {
100  case EMPHASIS_OFF:
101  sat_parameters.set_cp_model_presolve(false);
102  break;
103  case EMPHASIS_LOW:
104  case EMPHASIS_MEDIUM:
105  case EMPHASIS_HIGH:
106  case EMPHASIS_VERY_HIGH:
107  sat_parameters.set_cp_model_presolve(true);
108  break;
109  default:
110  LOG(FATAL) << "Presolve emphasis: "
111  << ProtoEnumToString(common_parameters.presolve())
112  << " unknown, error setting CP-SAT parameters";
113  }
114  }
115  if (common_parameters.scaling() != EMPHASIS_UNSPECIFIED) {
116  warnings.push_back(
117  absl::StrCat("Setting the scaling (was set to ",
118  ProtoEnumToString(common_parameters.scaling()),
119  ") is not supported for CP_SAT solver"));
120  }
121  if (common_parameters.cuts() != EMPHASIS_UNSPECIFIED) {
122  switch (common_parameters.cuts()) {
123  case EMPHASIS_OFF:
124  // This is not very maintainable, but CP-SAT doesn't expose the
125  // parameters we need.
126  sat_parameters.set_add_cg_cuts(false);
127  sat_parameters.set_add_mir_cuts(false);
128  sat_parameters.set_add_zero_half_cuts(false);
129  sat_parameters.set_add_clique_cuts(false);
130  sat_parameters.set_max_all_diff_cut_size(0);
131  sat_parameters.set_add_lin_max_cuts(false);
132  break;
133  case EMPHASIS_LOW:
134  case EMPHASIS_MEDIUM:
135  case EMPHASIS_HIGH:
136  case EMPHASIS_VERY_HIGH:
137  break;
138  default:
139  LOG(FATAL) << "Cut emphasis: "
140  << ProtoEnumToString(common_parameters.cuts())
141  << " unknown, error setting CP-SAT parameters";
142  }
143  }
144  if (common_parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
145  warnings.push_back(
146  absl::StrCat("Setting the heuristics (was set to ",
147  ProtoEnumToString(common_parameters.heuristics()),
148  ") is not supported for CP_SAT solver"));
149  }
150  sat_parameters.MergeFrom(parameters.cp_sat_parameters());
151  request.set_solver_specific_parameters(
152  EncodeSatParametersAsString(sat_parameters));
153  return warnings;
154 }
155 
156 } // namespace
157 
158 absl::StatusOr<std::unique_ptr<SolverInterface>> CpSatSolver::New(
159  const ModelProto& model, const SolverInitializerProto& initializer) {
160  ASSIGN_OR_RETURN(MPModelProto cp_sat_model,
162  std::vector variable_ids(model.variables().ids().begin(),
163  model.variables().ids().end());
164  // We must use WrapUnique here since the constructor is private.
165  return absl::WrapUnique(
166  new CpSatSolver(std::move(cp_sat_model), std::move(variable_ids)));
167 }
168 
169 absl::StatusOr<SolveResultProto> CpSatSolver::Solve(
170  const SolveParametersProto& parameters,
171  const ModelSolveParametersProto& model_parameters,
172  const CallbackRegistrationProto& callback_registration, const Callback cb) {
173  SolveResultProto result;
174  MPModelRequest req;
175  // Here we must make a copy since Solve() can be called multiple times with
176  // different parameters. Hence we can't move `cp_sat_model`.
177  *req.mutable_model() = cp_sat_model_;
178  req.set_solver_type(MPModelRequest::SAT_INTEGER_PROGRAMMING);
179  {
180  std::vector<std::string> param_warnings =
181  SetSolveParameters(parameters, req);
182  if (!param_warnings.empty()) {
183  if (parameters.common_parameters().strictness().bad_parameter()) {
184  return absl::InvalidArgumentError(absl::StrJoin(param_warnings, "; "));
185  } else {
186  for (std::string& warning : param_warnings) {
187  result.add_warnings(std::move(warning));
188  }
189  }
190  }
191  }
192 
193  // The `response` is not const to be able to move out the solution values.
194  ASSIGN_OR_RETURN(const MPSolutionResponse response,
195  SatSolveProto(std::move(req)));
196 
197  switch (response.status()) {
198  case MPSOLVER_FEASIBLE:
199  case MPSOLVER_OPTIMAL: {
200  result.set_termination_reason(response.status() == MPSOLVER_OPTIMAL
202  : SolveResultProto::OTHER_LIMIT);
203  result.set_termination_detail(response.status_str());
204  result.mutable_solve_stats()->set_best_primal_bound(
205  response.objective_value());
206  result.mutable_solve_stats()->set_best_dual_bound(
207  response.best_objective_bound());
208  *result.add_primal_solutions() =
209  ExtractSolution(response, model_parameters);
210  break;
211  }
212  case MPSOLVER_INFEASIBLE:
213  result.set_termination_reason(SolveResultProto::INFEASIBLE);
214  result.set_termination_detail(response.status_str());
215  SetTrivialBounds(cp_sat_model_.maximize(), *result.mutable_solve_stats());
216  break;
217  case MPSOLVER_NOT_SOLVED:
218  result.set_termination_reason(SolveResultProto::OTHER_LIMIT);
219  result.set_termination_detail(response.status_str());
220  SetTrivialBounds(cp_sat_model_.maximize(), *result.mutable_solve_stats());
221  break;
223  return absl::InternalError(
224  absl::StrCat("cp-sat solver returned MODEL_INVALID, details: ",
225  response.status_str()));
226  default:
227  return absl::InternalError(
228  absl::StrCat("unexpected solve status: ", response.status()));
229  }
230 
231  return result;
232 }
233 
234 bool CpSatSolver::CanUpdate(const ModelUpdateProto& model_update) {
235  return false;
236 }
237 
238 absl::Status CpSatSolver::Update(const ModelUpdateProto& model_update) {
239  // This function should never be called since CanUpdate() always returns
240  // false.
241  return absl::InternalError("CP-SAT solver does not support incrementalism");
242 }
243 
244 CpSatSolver::CpSatSolver(MPModelProto cp_sat_model,
245  std::vector<int64_t> variable_ids)
246  : cp_sat_model_(std::move(cp_sat_model)),
247  variable_ids_(std::move(variable_ids)) {}
248 
249 PrimalSolutionProto CpSatSolver::ExtractSolution(
250  const MPSolutionResponse& response,
251  const ModelSolveParametersProto& model_parameters) const {
252  PrimalSolutionProto solution;
253 
254  solution.set_objective_value(response.objective_value());
255 
256  // Pre-condition: we assume one-to-one correspondence of input variables to
257  // solution's variables.
258  CHECK_EQ(response.variable_value_size(), variable_ids_.size());
259 
260  SparseVectorFilterPredicate predicate(
261  model_parameters.primal_variables_filter());
262  auto* const values = solution.mutable_variable_values();
263  for (int i = 0; i < variable_ids_.size(); ++i) {
264  const int64_t id = variable_ids_[i];
265  const double value = response.variable_value(i);
266  if (predicate.AcceptsAndUpdate(id, value)) {
267  values->add_ids(id);
268  values->add_values(value);
269  }
270  }
271 
272  return solution;
273 }
274 
276 
277 } // namespace math_opt
278 } // namespace operations_research
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:705
#define LOG(severity)
Definition: base/logging.h:423
bool CanUpdate(const ModelUpdateProto &model_update) override
absl::Status Update(const ModelUpdateProto &model_update) override
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto &parameters, const ModelSolveParametersProto &model_parameters, const CallbackRegistrationProto &callback_registration, Callback cb) override
static absl::StatusOr< std::unique_ptr< SolverInterface > > New(const ModelProto &model, const SolverInitializerProto &initializer)
std::function< absl::StatusOr< CallbackResultProto >(const CallbackDataProto &)> Callback
SatParameters parameters
SharedResponseManager * response
int64_t value
absl::Span< const int64_t > variable_ids
GRBmodel * model
const int FATAL
Definition: log_severity.h:32
MATH_OPT_REGISTER_SOLVER(SOLVER_TYPE_CP_SAT, CpSatSolver::New)
absl::StatusOr<::operations_research::MPModelProto > MathOptModelToMPModelProto(const ::operations_research::math_opt::ModelProto &model)
Collection of objects used to extend the Constraint Solver library.
std::string ProtoEnumToString(ProtoEnumType enum_value)
std::string EncodeSatParametersAsString(const sat::SatParameters &parameters)
absl::StatusOr< MPSolutionResponse > SatSolveProto(MPModelRequest request, std::atomic< bool > *interrupt_solve)
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
Definition: protoutil.h:42
#define ASSIGN_OR_RETURN(lhs, rexpr)
Definition: status_macros.h:55