OR-Tools  9.3
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 <atomic>
17#include <cmath>
18#include <cstdint>
19#include <functional>
20#include <limits>
21#include <memory>
22#include <string>
23#include <utility>
24#include <vector>
25
26#include "absl/memory/memory.h"
27#include "absl/status/status.h"
28#include "absl/status/statusor.h"
29#include "absl/strings/match.h"
30#include "absl/strings/str_cat.h"
31#include "absl/strings/str_join.h"
32#include "absl/strings/str_split.h"
33#include "absl/time/clock.h"
34#include "absl/time/time.h"
38#include "ortools/linear_solver/linear_solver.pb.h"
40#include "ortools/math_opt/callback.pb.h"
46#include "ortools/math_opt/model.pb.h"
47#include "ortools/math_opt/model_parameters.pb.h"
48#include "ortools/math_opt/model_update.pb.h"
49#include "ortools/math_opt/parameters.pb.h"
50#include "ortools/math_opt/result.pb.h"
51#include "ortools/math_opt/solution.pb.h"
52#include "ortools/math_opt/sparse_containers.pb.h"
55#include "ortools/sat/sat_parameters.pb.h"
56
57namespace operations_research {
58namespace math_opt {
59
60namespace {
61
62constexpr double kInf = std::numeric_limits<double>::infinity();
63
64// Returns true on success.
65bool ApplyCutoff(const double cutoff, MPModelProto* model) {
66 // TODO(b/204083726): we need to be careful here if we support quadratic
67 // objectives
68 if (model->has_quadratic_objective()) {
69 return false;
70 }
71 // CP-SAT detects a constraint parallel to the objective and uses it as
72 // an objective bound, which is the closest we can get to cutoff.
73 // See FindDuplicateConstraints() in CP-SAT codebase.
74 MPConstraintProto* const cutoff_constraint = model->add_constraint();
75 for (int i = 0; i < model->variable_size(); ++i) {
76 const double obj_coef = model->variable(i).objective_coefficient();
77 if (obj_coef != 0) {
78 cutoff_constraint->add_var_index(i);
79 cutoff_constraint->add_coefficient(obj_coef);
80 }
81 }
82 const double cutoff_minus_offset = cutoff - model->objective_offset();
83 if (model->maximize()) {
84 // Add the constraint obj >= cutoff
85 cutoff_constraint->set_lower_bound(cutoff_minus_offset);
86 } else {
87 // Add the constraint obj <= cutoff
88 cutoff_constraint->set_upper_bound(cutoff_minus_offset);
89 }
90 return true;
91}
92
93// Returns a list of warnings from parameter settings that were
94// invalid/unsupported (specific to CP-SAT), one element per bad parameter.
95std::vector<std::string> SetSolveParameters(
96 const SolveParametersProto& parameters, const bool has_message_callback,
97 MPModelRequest& request) {
98 std::vector<std::string> warnings;
99 if (parameters.has_time_limit()) {
100 request.set_solver_time_limit_seconds(absl::ToDoubleSeconds(
101 util_time::DecodeGoogleApiProto(parameters.time_limit()).value()));
102 }
103 if (parameters.has_node_limit()) {
104 warnings.push_back("The node_limit parameter is not supported for CP-SAT.");
105 }
106
107 // Build CP SAT parameters by first initializing them from the common
108 // parameters, and then using the values in `solver_specific_parameters` to
109 // overwrite them if necessary.
110 //
111 // We don't need to set max_time_in_seconds since we already pass it in the
112 // `request.solver_time_limit_seconds`. The logic of `SatSolveProto()` will
113 // apply the logic we want here.
114 sat::SatParameters sat_parameters;
115
116 // By default CP-SAT catches SIGINT (Ctrl-C) to interrupt the solve but we
117 // don't want this behavior when the users uses CP-SAT through MathOpt.
118 sat_parameters.set_catch_sigint_signal(false);
119
120 if (parameters.has_random_seed()) {
121 sat_parameters.set_random_seed(parameters.random_seed());
122 }
123 if (parameters.has_threads()) {
124 sat_parameters.set_num_search_workers(parameters.threads());
125 }
126 if (parameters.has_relative_gap_tolerance()) {
127 sat_parameters.set_relative_gap_limit(parameters.relative_gap_tolerance());
128 }
129
130 if (parameters.has_absolute_gap_tolerance()) {
131 sat_parameters.set_absolute_gap_limit(parameters.absolute_gap_tolerance());
132 }
133 // cutoff_limit is handled outside this function as it modifies the model.
134 if (parameters.has_best_bound_limit()) {
135 warnings.push_back(
136 "The best_bound_limit parameter is not supported for CP-SAT.");
137 }
138 if (parameters.has_objective_limit()) {
139 warnings.push_back(
140 "The objective_limit parameter is not supported for CP-SAT.");
141 }
142 if (parameters.has_solution_limit()) {
143 if (parameters.solution_limit() == 1) {
144 sat_parameters.set_stop_after_first_solution(true);
145 } else {
146 warnings.push_back(absl::StrCat(
147 "The CP-SAT solver only supports value 1 for solution_limit, found: ",
148 parameters.solution_limit()));
149 }
150 }
151
152 if (parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
153 warnings.push_back(
154 absl::StrCat("Setting the LP Algorithm (was set to ",
155 ProtoEnumToString(parameters.lp_algorithm()),
156 ") is not supported for CP_SAT solver"));
157 }
158 if (parameters.presolve() != EMPHASIS_UNSPECIFIED) {
159 switch (parameters.presolve()) {
160 case EMPHASIS_OFF:
161 sat_parameters.set_cp_model_presolve(false);
162 break;
163 case EMPHASIS_LOW:
164 case EMPHASIS_MEDIUM:
165 case EMPHASIS_HIGH:
166 case EMPHASIS_VERY_HIGH:
167 sat_parameters.set_cp_model_presolve(true);
168 break;
169 default:
170 LOG(FATAL) << "Presolve emphasis: "
171 << ProtoEnumToString(parameters.presolve())
172 << " unknown, error setting CP-SAT parameters";
173 }
174 }
175 if (parameters.scaling() != EMPHASIS_UNSPECIFIED) {
176 warnings.push_back(absl::StrCat("Setting the scaling (was set to ",
177 ProtoEnumToString(parameters.scaling()),
178 ") is not supported for CP_SAT solver"));
179 }
180 if (parameters.cuts() != EMPHASIS_UNSPECIFIED) {
181 switch (parameters.cuts()) {
182 case EMPHASIS_OFF:
183 // This is not very maintainable, but CP-SAT doesn't expose the
184 // parameters we need.
185 sat_parameters.set_add_cg_cuts(false);
186 sat_parameters.set_add_mir_cuts(false);
187 sat_parameters.set_add_zero_half_cuts(false);
188 sat_parameters.set_add_clique_cuts(false);
189 sat_parameters.set_max_all_diff_cut_size(0);
190 sat_parameters.set_add_lin_max_cuts(false);
191 break;
192 case EMPHASIS_LOW:
193 case EMPHASIS_MEDIUM:
194 case EMPHASIS_HIGH:
195 case EMPHASIS_VERY_HIGH:
196 break;
197 default:
198 LOG(FATAL) << "Cut emphasis: " << ProtoEnumToString(parameters.cuts())
199 << " unknown, error setting CP-SAT parameters";
200 }
201 }
202 if (parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
203 warnings.push_back(absl::StrCat("Setting the heuristics (was set to ",
204 ProtoEnumToString(parameters.heuristics()),
205 ") is not supported for CP_SAT solver"));
206 }
207 sat_parameters.MergeFrom(parameters.cp_sat());
208
209 // We want to override specifically SAT parameters independently from the user
210 // input when a message callback is used to prevent wrongful writes to stdout
211 // or disabling of messages via these parameters.
212 if (has_message_callback) {
213 // When enable_internal_solver_output is used, CP-SAT solver actually has
214 // the same effect as setting log_search_progress to true.
215 sat_parameters.set_log_search_progress(true);
216
217 // Default value of log_to_stdout is true; but even if it was not the case,
218 // we don't want to write to stdout when a message callback is used.
219 sat_parameters.set_log_to_stdout(false);
220 } else {
221 // We only set enable_internal_solver_output when we have no message
222 // callback.
223 request.set_enable_internal_solver_output(parameters.enable_output());
224 }
225
226 request.set_solver_specific_parameters(
227 EncodeSatParametersAsString(sat_parameters));
228 return warnings;
229}
230
231absl::StatusOr<std::pair<SolveStatsProto, TerminationProto>>
232GetTerminationAndStats(const bool is_interrupted, const bool maximize,
233 const bool used_cutoff,
234 const MPSolutionResponse& response) {
235 SolveStatsProto solve_stats;
236 TerminationProto termination;
237
238 // Set default status and bounds.
239 solve_stats.mutable_problem_status()->set_primal_status(
240 FEASIBILITY_STATUS_UNDETERMINED);
241 solve_stats.set_best_primal_bound(maximize ? -kInf : kInf);
242 solve_stats.mutable_problem_status()->set_dual_status(
243 FEASIBILITY_STATUS_UNDETERMINED);
244 solve_stats.set_best_dual_bound(maximize ? kInf : -kInf);
245
246 // Set terminations and update status and bounds as appropriate.
247 switch (response.status()) {
248 case MPSOLVER_OPTIMAL:
249 termination =
250 TerminateForReason(TERMINATION_REASON_OPTIMAL, response.status_str());
251 solve_stats.mutable_problem_status()->set_primal_status(
252 FEASIBILITY_STATUS_FEASIBLE);
253 solve_stats.set_best_primal_bound(response.objective_value());
254 solve_stats.mutable_problem_status()->set_dual_status(
255 FEASIBILITY_STATUS_FEASIBLE);
256 solve_stats.set_best_dual_bound(response.best_objective_bound());
257 break;
258 case MPSOLVER_INFEASIBLE:
259 if (used_cutoff) {
260 termination =
261 NoSolutionFoundTermination(LIMIT_CUTOFF, response.status_str());
262 } else {
263 termination = TerminateForReason(TERMINATION_REASON_INFEASIBLE,
264 response.status_str());
265 solve_stats.mutable_problem_status()->set_primal_status(
266 FEASIBILITY_STATUS_INFEASIBLE);
267 }
268 break;
269 case MPSOLVER_UNKNOWN_STATUS:
270 // For a basic unbounded problem, CP-SAT internally returns
271 // INFEASIBLE_OR_UNBOUNDED after presolve but MPSolver statuses don't
272 // support that thus it get transformed in MPSOLVER_UNKNOWN_STATUS with
273 // a status_str of
274 //
275 // "Problem proven infeasible or unbounded during MIP presolve"
276 //
277 // There may be some other cases where CP-SAT returns UNKNOWN here so we
278 // only return TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED when the
279 // status_str is detected. Otherwise we return OTHER_ERROR.
280 //
281 // TODO(b/202159173): A better solution would be to use CP-SAT API
282 // directly which may help further improve the statuses.
283 if (absl::StrContains(response.status_str(), "infeasible or unbounded")) {
284 termination = TerminateForReason(
285 TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED, response.status_str());
286 solve_stats.mutable_problem_status()->set_primal_or_dual_infeasible(
287 true);
288 } else {
289 termination = TerminateForReason(TERMINATION_REASON_OTHER_ERROR,
290 response.status_str());
291 }
292 break;
293 case MPSOLVER_FEASIBLE:
294 termination = FeasibleTermination(
295 is_interrupted ? LIMIT_INTERRUPTED : LIMIT_UNDETERMINED,
296 response.status_str());
297 solve_stats.mutable_problem_status()->set_primal_status(
298 FEASIBILITY_STATUS_FEASIBLE);
299 solve_stats.set_best_primal_bound(response.objective_value());
300 solve_stats.set_best_dual_bound(response.best_objective_bound());
301 if (std::isfinite(response.best_objective_bound())) {
302 solve_stats.mutable_problem_status()->set_dual_status(
303 FEASIBILITY_STATUS_FEASIBLE);
304 }
305 break;
306 case MPSOLVER_NOT_SOLVED:
307 termination = NoSolutionFoundTermination(
308 is_interrupted ? LIMIT_INTERRUPTED : LIMIT_UNDETERMINED,
309 response.status_str());
310 break;
311 case MPSOLVER_MODEL_INVALID:
312 return absl::InternalError(
313 absl::StrCat("cp-sat solver returned MODEL_INVALID, details: ",
314 response.status_str()));
315 default:
316 return absl::InternalError(
317 absl::StrCat("unexpected solve status: ", response.status()));
318 }
319 return std::make_pair(std::move(solve_stats), std::move(termination));
320}
321
322} // namespace
323
324absl::StatusOr<std::unique_ptr<SolverInterface>> CpSatSolver::New(
325 const ModelProto& model, const InitArgs& init_args) {
326 ASSIGN_OR_RETURN(MPModelProto cp_sat_model,
328 std::vector variable_ids(model.variables().ids().begin(),
329 model.variables().ids().end());
330 std::vector linear_constraint_ids(model.linear_constraints().ids().begin(),
331 model.linear_constraints().ids().end());
332 // TODO(b/204083726): Remove this check if QP support is added
333 if (!model.objective().quadratic_coefficients().row_ids().empty()) {
334 return absl::InvalidArgumentError(
335 "MathOpt does not currently support CP-SAT models with quadratic "
336 "objectives");
337 }
338 return absl::WrapUnique(new CpSatSolver(
339 std::move(cp_sat_model),
340 /*variable_ids=*/std::move(variable_ids),
341 /*linear_constraint_ids=*/std::move(linear_constraint_ids)));
342}
343
344absl::StatusOr<SolveResultProto> CpSatSolver::Solve(
345 const SolveParametersProto& parameters,
346 const ModelSolveParametersProto& model_parameters,
347 const MessageCallback message_cb,
348 const CallbackRegistrationProto& callback_registration, const Callback cb,
349 SolveInterrupter* const interrupter) {
350 const absl::Time start = absl::Now();
351
353 callback_registration,
354 /*supported_events=*/{CALLBACK_EVENT_MIP_SOLUTION}));
355 if (callback_registration.add_lazy_constraints()) {
356 return absl::InvalidArgumentError(
357 "CallbackRegistrationProto.add_lazy_constraints=true is not supported "
358 "for CP-SAT.");
359 }
360 // We need not check callback_registration.add_cuts, as cuts can only be added
361 // at event MIP_NODE which we have already validated is not supported.
362
363 SolveResultProto result;
364 MPModelRequest req;
365 // Here we must make a copy since Solve() can be called multiple times with
366 // different parameters. Hence we can't move `cp_sat_model`.
367 *req.mutable_model() = cp_sat_model_;
368
369 req.set_solver_type(MPModelRequest::SAT_INTEGER_PROGRAMMING);
370 bool used_cutoff = false;
371 {
372 std::vector<std::string> param_warnings =
373 SetSolveParameters(parameters,
374 /*has_message_callback=*/message_cb != nullptr, req);
375 if (parameters.has_cutoff_limit()) {
376 used_cutoff = ApplyCutoff(parameters.cutoff_limit(), req.mutable_model());
377 if (!used_cutoff) {
378 param_warnings.push_back(
379 "The cutoff_limit parameter not supported for quadratic objectives "
380 "with CP-SAT.");
381 }
382 }
383 if (!param_warnings.empty()) {
384 return absl::InvalidArgumentError(absl::StrJoin(param_warnings, "; "));
385 }
386 }
387
388 if (!model_parameters.solution_hints().empty()) {
389 int i = 0;
390 for (const auto [id, val] :
391 MakeView(model_parameters.solution_hints(0).variable_values())) {
392 while (variable_ids_[i] < id) {
393 ++i;
394 }
395 req.mutable_model()->mutable_solution_hint()->add_var_index(i);
396 req.mutable_model()->mutable_solution_hint()->add_var_value(val);
397 }
398 }
399
400 // We need to chain the user interrupter through a local interrupter, because
401 // if we termiante early from a callback request, we don't want to incorrectly
402 // modify the input state.
403 SolveInterrupter local_interrupter;
404 std::atomic<bool> interrupt_solve = false;
405 local_interrupter.AddInterruptionCallback([&]() { interrupt_solve = true; });
406
407 // Setup a callback on the user provided so that we interrupt the solver.
408 const ScopedSolveInterrupterCallback scoped_interrupt_cb(
409 interrupter, [&]() { local_interrupter.Interrupt(); });
410
411 std::function<void(const std::string&)> logging_callback;
412 if (message_cb != nullptr) {
413 logging_callback = [&](const std::string& message) {
414 message_cb(absl::StrSplit(message, '\n'));
415 };
416 }
417
418 const absl::flat_hash_set<CallbackEventProto> events =
419 EventSet(callback_registration);
420 std::function<void(const MPSolution&)> solution_callback;
421 absl::Status callback_error = absl::OkStatus();
422 if (events.contains(CALLBACK_EVENT_MIP_SOLUTION)) {
423 solution_callback = [this, &cb, &callback_error, &local_interrupter,
424 &model_parameters](const MPSolution& mp_solution) {
425 if (!callback_error.ok()) {
426 // A previous callback failed.
427 return;
428 }
429 CallbackDataProto cb_data;
430 cb_data.set_event(CALLBACK_EVENT_MIP_SOLUTION);
431 *cb_data.mutable_primal_solution_vector() =
432 ExtractSolution(mp_solution.variable_value(), model_parameters);
433 const absl::StatusOr<CallbackResultProto> cb_result = cb(cb_data);
434 if (!cb_result.ok()) {
435 callback_error = cb_result.status();
436 // Note: we will be returning a status error, we do not need to worry
437 // about interpreting this as TERMINATION_REASON_INTERRUPTED.
438 local_interrupter.Interrupt();
439 } else if (cb_result->terminate()) {
440 local_interrupter.Interrupt();
441 }
442 // Note cb_result.cuts and cb_result.suggested solutions are not
443 // supported by CP-SAT and we have validated they are empty.
444 };
445 }
446
447 // CP-SAT returns "infeasible" for inverted bounds.
448 RETURN_IF_ERROR(ListInvertedBounds().ToStatus());
449
450 ASSIGN_OR_RETURN(const MPSolutionResponse response,
451 SatSolveProto(std::move(req), &interrupt_solve,
452 logging_callback, solution_callback));
453 RETURN_IF_ERROR(callback_error) << "error in callback";
455 (auto [solve_stats, termination]),
456 GetTerminationAndStats(local_interrupter.IsInterrupted(),
457 /*maximize=*/cp_sat_model_.maximize(),
458 /*used_cutoff=*/used_cutoff, response));
459 *result.mutable_solve_stats() = std::move(solve_stats);
460 *result.mutable_termination() = std::move(termination);
461 if (response.status() == MPSOLVER_OPTIMAL ||
462 response.status() == MPSOLVER_FEASIBLE) {
463 PrimalSolutionProto& solution =
464 *result.add_solutions()->mutable_primal_solution();
465 *solution.mutable_variable_values() =
466 ExtractSolution(response.variable_value(), model_parameters);
467 solution.set_objective_value(response.objective_value());
468 solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
469 }
470
472 absl::Now() - start, result.mutable_solve_stats()->mutable_solve_time()));
473
474 return result;
475}
476
477bool CpSatSolver::CanUpdate(const ModelUpdateProto& model_update) {
478 return false;
479}
480
481absl::Status CpSatSolver::Update(const ModelUpdateProto& model_update) {
482 // This function should never be called since CanUpdate() always returns
483 // false.
484 return absl::InternalError("CP-SAT solver does not support incrementalism");
485}
486
487CpSatSolver::CpSatSolver(MPModelProto cp_sat_model,
488 std::vector<int64_t> variable_ids,
489 std::vector<int64_t> linear_constraint_ids)
490 : cp_sat_model_(std::move(cp_sat_model)),
491 variable_ids_(std::move(variable_ids)),
492 linear_constraint_ids_(std::move(linear_constraint_ids)) {}
493
494SparseDoubleVectorProto CpSatSolver::ExtractSolution(
495 const absl::Span<const double> cp_sat_variable_values,
496 const ModelSolveParametersProto& model_parameters) const {
497 // Pre-condition: we assume one-to-one correspondence of input variables to
498 // solution's variables.
499 CHECK_EQ(cp_sat_variable_values.size(), variable_ids_.size());
500
501 SparseVectorFilterPredicate predicate(
502 model_parameters.variable_values_filter());
503 SparseDoubleVectorProto result;
504 for (int i = 0; i < variable_ids_.size(); ++i) {
505 const int64_t id = variable_ids_[i];
506 const double value = cp_sat_variable_values[i];
507 if (predicate.AcceptsAndUpdate(id, value)) {
508 result.add_ids(id);
509 result.add_values(value);
510 }
511 }
512 return result;
513}
514
515InvertedBounds CpSatSolver::ListInvertedBounds() const {
516 InvertedBounds inverted_bounds;
517 for (int v = 0; v < cp_sat_model_.variable_size(); ++v) {
518 const MPVariableProto& var = cp_sat_model_.variable(v);
519 if (var.lower_bound() > var.upper_bound()) {
520 inverted_bounds.variables.push_back(variable_ids_[v]);
521 }
522 }
523 for (int c = 0; c < cp_sat_model_.constraint_size(); ++c) {
524 const MPConstraintProto& cstr = cp_sat_model_.constraint(c);
525 if (cstr.lower_bound() > cstr.upper_bound()) {
526 inverted_bounds.linear_constraints.push_back(linear_constraint_ids_[c]);
527 }
528 }
529
530 return inverted_bounds;
531}
532
534
535} // namespace math_opt
536} // namespace operations_research
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define CHECK_OK(x)
Definition: base/logging.h:44
#define LOG(severity)
Definition: base/logging.h:420
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
bool CanUpdate(const ModelUpdateProto &model_update) override
absl::Status Update(const ModelUpdateProto &model_update) override
static absl::StatusOr< std::unique_ptr< SolverInterface > > New(const ModelProto &model, const InitArgs &init_args)
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto &parameters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, SolveInterrupter *interrupter) override
CallbackId AddInterruptionCallback(Callback callback)
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >(const CallbackDataProto &)> Callback
SatParameters parameters
SharedResponseManager * response
int64_t value
IntVar * var
Definition: expr_array.cc:1874
absl::Span< const int64_t > variable_ids
GRBmodel * model
const int FATAL
Definition: log_severity.h:32
TerminationProto FeasibleTermination(const LimitProto limit, const absl::string_view detail)
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto &registration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
MATH_OPT_REGISTER_SOLVER(SOLVER_TYPE_CP_SAT, CpSatSolver::New)
absl::StatusOr<::operations_research::MPModelProto > MathOptModelToMPModelProto(const ::operations_research::math_opt::ModelProto &model)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
TerminationProto NoSolutionFoundTermination(const LimitProto limit, const absl::string_view detail)
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
absl::flat_hash_set< CallbackEventProto > EventSet(const CallbackRegistrationProto &callback_registration)
Collection of objects used to extend the Constraint Solver library.
std::string ProtoEnumToString(ProtoEnumType enum_value)
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)
STL namespace.
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
Definition: protoutil.h:42
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
Definition: protoutil.h:27
int64_t start
std::string message
Definition: trace.cc:398