Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_sat_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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 <cstdint>
18#include <functional>
19#include <memory>
20#include <optional>
21#include <string>
22#include <utility>
23#include <vector>
24
25#include "absl/container/flat_hash_set.h"
26#include "absl/log/check.h"
27#include "absl/memory/memory.h"
28#include "absl/status/status.h"
29#include "absl/status/statusor.h"
30#include "absl/strings/match.h"
31#include "absl/strings/str_cat.h"
32#include "absl/strings/str_join.h"
33#include "absl/strings/str_split.h"
34#include "absl/strings/string_view.h"
35#include "absl/time/clock.h"
36#include "absl/time/time.h"
37#include "absl/types/span.h"
41#include "ortools/linear_solver/linear_solver.pb.h"
44#include "ortools/math_opt/callback.pb.h"
49#include "ortools/math_opt/infeasible_subsystem.pb.h"
51#include "ortools/math_opt/model.pb.h"
52#include "ortools/math_opt/model_parameters.pb.h"
53#include "ortools/math_opt/model_update.pb.h"
54#include "ortools/math_opt/parameters.pb.h"
55#include "ortools/math_opt/result.pb.h"
56#include "ortools/math_opt/solution.pb.h"
57#include "ortools/math_opt/sparse_containers.pb.h"
60#include "ortools/sat/sat_parameters.pb.h"
62
63namespace operations_research {
64namespace math_opt {
65
66namespace {
67
68constexpr SupportedProblemStructures kCpSatSupportedStructures = {
69 .integer_variables = SupportType::kSupported,
70 .quadratic_objectives = SupportType::kNotImplemented,
71 .quadratic_constraints = SupportType::kNotImplemented,
72 .sos1_constraints = SupportType::kNotImplemented,
73 .sos2_constraints = SupportType::kNotImplemented,
74 .indicator_constraints = SupportType::kNotImplemented};
75
76// Returns true on success.
77bool ApplyCutoff(const double cutoff, MPModelProto* model) {
78 // TODO(b/204083726): we need to be careful here if we support quadratic
79 // objectives
80 if (model->has_quadratic_objective()) {
81 return false;
82 }
83 // CP-SAT detects a constraint parallel to the objective and uses it as
84 // an objective bound, which is the closest we can get to cutoff.
85 // See FindDuplicateConstraints() in CP-SAT codebase.
86 MPConstraintProto* const cutoff_constraint = model->add_constraint();
87 for (int i = 0; i < model->variable_size(); ++i) {
88 const double obj_coef = model->variable(i).objective_coefficient();
89 if (obj_coef != 0) {
90 cutoff_constraint->add_var_index(i);
91 cutoff_constraint->add_coefficient(obj_coef);
92 }
93 }
94 const double cutoff_minus_offset = cutoff - model->objective_offset();
95 if (model->maximize()) {
96 // Add the constraint obj >= cutoff
97 cutoff_constraint->set_lower_bound(cutoff_minus_offset);
98 } else {
99 // Add the constraint obj <= cutoff
100 cutoff_constraint->set_upper_bound(cutoff_minus_offset);
101 }
102 return true;
103}
104
105// Returns a list of warnings from parameter settings that were
106// invalid/unsupported (specific to CP-SAT), one element per bad parameter.
107std::vector<std::string> SetSolveParameters(
108 const SolveParametersProto& parameters, const bool has_message_callback,
109 MPModelRequest& request) {
110 std::vector<std::string> warnings;
111 if (parameters.has_time_limit()) {
112 request.set_solver_time_limit_seconds(absl::ToDoubleSeconds(
113 util_time::DecodeGoogleApiProto(parameters.time_limit()).value()));
114 }
115 if (parameters.has_iteration_limit()) {
116 warnings.push_back(
117 "The iteration_limit parameter is not supported for CP-SAT.");
118 }
119 if (parameters.has_node_limit()) {
120 warnings.push_back("The node_limit parameter is not supported for CP-SAT.");
121 }
122
123 // Build CP SAT parameters by first initializing them from the common
124 // parameters, and then using the values in `solver_specific_parameters` to
125 // overwrite them if necessary.
126 //
127 // We don't need to set max_time_in_seconds since we already pass it in the
128 // `request.solver_time_limit_seconds`. The logic of `SatSolveProto()` will
129 // apply the logic we want here.
130 sat::SatParameters sat_parameters;
131
132 // By default CP-SAT catches SIGINT (Ctrl-C) to interrupt the solve but we
133 // don't want this behavior when the users uses CP-SAT through MathOpt.
134 sat_parameters.set_catch_sigint_signal(false);
135
136 if (parameters.has_random_seed()) {
137 sat_parameters.set_random_seed(parameters.random_seed());
138 }
139 if (parameters.has_threads()) {
140 sat_parameters.set_num_search_workers(parameters.threads());
141 }
142 if (parameters.has_relative_gap_tolerance()) {
143 sat_parameters.set_relative_gap_limit(parameters.relative_gap_tolerance());
144 }
145
146 if (parameters.has_absolute_gap_tolerance()) {
147 sat_parameters.set_absolute_gap_limit(parameters.absolute_gap_tolerance());
148 }
149 // cutoff_limit is handled outside this function as it modifies the model.
150 if (parameters.has_best_bound_limit()) {
151 warnings.push_back(
152 "The best_bound_limit parameter is not supported for CP-SAT.");
153 }
154 if (parameters.has_objective_limit()) {
155 warnings.push_back(
156 "The objective_limit parameter is not supported for CP-SAT.");
157 }
158 if (parameters.has_solution_limit()) {
159 if (parameters.solution_limit() == 1) {
160 sat_parameters.set_stop_after_first_solution(true);
161 } else {
162 warnings.push_back(absl::StrCat(
163 "The CP-SAT solver only supports value 1 for solution_limit, found: ",
164 parameters.solution_limit()));
165 }
166 }
167 if (parameters.has_solution_pool_size()) {
168 sat_parameters.set_solution_pool_size(parameters.solution_pool_size());
169 sat_parameters.set_fill_additional_solutions_in_response(true);
170 }
171 if (parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
172 warnings.push_back(
173 absl::StrCat("Setting lp_algorithm (was set to ",
174 ProtoEnumToString(parameters.lp_algorithm()),
175 ") is not supported for CP_SAT solver"));
176 }
177 if (parameters.presolve() != EMPHASIS_UNSPECIFIED) {
178 switch (parameters.presolve()) {
179 case EMPHASIS_OFF:
180 sat_parameters.set_cp_model_presolve(false);
181 break;
182 case EMPHASIS_LOW:
183 case EMPHASIS_MEDIUM:
184 case EMPHASIS_HIGH:
185 case EMPHASIS_VERY_HIGH:
186 sat_parameters.set_cp_model_presolve(true);
187 break;
188 default:
189 LOG(FATAL) << "Presolve emphasis: "
190 << ProtoEnumToString(parameters.presolve())
191 << " unknown, error setting CP-SAT parameters";
192 }
193 }
194 if (parameters.scaling() != EMPHASIS_UNSPECIFIED) {
195 warnings.push_back(absl::StrCat("Setting the scaling (was set to ",
196 ProtoEnumToString(parameters.scaling()),
197 ") is not supported for CP_SAT solver"));
198 }
199 if (parameters.cuts() != EMPHASIS_UNSPECIFIED) {
200 switch (parameters.cuts()) {
201 case EMPHASIS_OFF:
202 // This is not very maintainable, but CP-SAT doesn't expose the
203 // parameters we need.
204 sat_parameters.set_add_cg_cuts(false);
205 sat_parameters.set_add_mir_cuts(false);
206 sat_parameters.set_add_zero_half_cuts(false);
207 sat_parameters.set_add_clique_cuts(false);
208 sat_parameters.set_max_all_diff_cut_size(0);
209 sat_parameters.set_add_lin_max_cuts(false);
210 break;
211 case EMPHASIS_LOW:
212 case EMPHASIS_MEDIUM:
213 case EMPHASIS_HIGH:
214 case EMPHASIS_VERY_HIGH:
215 break;
216 default:
217 LOG(FATAL) << "Cut emphasis: " << ProtoEnumToString(parameters.cuts())
218 << " unknown, error setting CP-SAT parameters";
219 }
220 }
221 if (parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
222 warnings.push_back(absl::StrCat("Setting the heuristics (was set to ",
223 ProtoEnumToString(parameters.heuristics()),
224 ") is not supported for CP_SAT solver"));
225 }
226 sat_parameters.MergeFrom(parameters.cp_sat());
227
228 // We want to override specifically SAT parameters independently from the user
229 // input when a message callback is used to prevent wrongful writes to stdout
230 // or disabling of messages via these parameters.
231 if (has_message_callback) {
232 // When enable_internal_solver_output is used, CP-SAT solver actually has
233 // the same effect as setting log_search_progress to true.
234 sat_parameters.set_log_search_progress(true);
235
236 // Default value of log_to_stdout is true; but even if it was not the case,
237 // we don't want to write to stdout when a message callback is used.
238 sat_parameters.set_log_to_stdout(false);
239 } else {
240 // We only set enable_internal_solver_output when we have no message
241 // callback.
242 request.set_enable_internal_solver_output(parameters.enable_output());
243 }
244
245 request.set_solver_specific_parameters(
246 EncodeParametersAsString(sat_parameters));
247 return warnings;
248}
249
250absl::StatusOr<TerminationProto> GetTermination(
251 const bool is_interrupted, const bool maximize, const bool used_cutoff,
252 const MPSolutionResponse& response) {
253 switch (response.status()) {
254 case MPSOLVER_OPTIMAL:
255 return OptimalTerminationProto(response.objective_value(),
256 response.best_objective_bound(),
257 response.status_str());
258 break;
259 case MPSOLVER_INFEASIBLE:
260 if (used_cutoff) {
261 return CutoffTerminationProto(maximize, response.status_str());
262 } else {
263 // By convention infeasible MIPs are always dual feasible.
265 maximize, /*dual_feasibility_status=*/FEASIBILITY_STATUS_FEASIBLE,
266 response.status_str());
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")) {
285 maximize,
286 /*dual_feasibility_status=*/FEASIBILITY_STATUS_UNDETERMINED,
287 response.status_str());
288 } else {
289 return TerminateForReason(maximize, TERMINATION_REASON_OTHER_ERROR,
290 response.status_str());
291 }
292 break;
293 case MPSOLVER_FEASIBLE:
295 maximize, is_interrupted ? LIMIT_INTERRUPTED : LIMIT_UNDETERMINED,
296 response.objective_value(), response.best_objective_bound(),
297 response.status_str());
298 break;
299 case MPSOLVER_NOT_SOLVED:
301 maximize, is_interrupted ? LIMIT_INTERRUPTED : LIMIT_UNDETERMINED,
302 /*optional_dual_objective=*/std::nullopt, response.status_str());
303 break;
304 case MPSOLVER_MODEL_INVALID:
305 return absl::InternalError(
306 absl::StrCat("cp-sat solver returned MODEL_INVALID, details: ",
307 response.status_str()));
308 case MPSOLVER_MODEL_INVALID_SOLVER_PARAMETERS:
310 << "invalid cp-sat parameters: " << response.status_str();
311 default:
312 return absl::InternalError(
313 absl::StrCat("unexpected solve status: ", response.status()));
314 }
315 return absl::InternalError(
316 absl::StrCat("unimplemented solve status: ", response.status()));
317}
318
319} // namespace
320
321absl::StatusOr<std::unique_ptr<SolverInterface>> CpSatSolver::New(
322 const ModelProto& model, const InitArgs&) {
323 RETURN_IF_ERROR(ModelIsSupported(model, kCpSatSupportedStructures, "CP-SAT"));
324 ASSIGN_OR_RETURN(MPModelProto cp_sat_model,
326 std::vector variable_ids(model.variables().ids().begin(),
327 model.variables().ids().end());
328 std::vector linear_constraint_ids(model.linear_constraints().ids().begin(),
329 model.linear_constraints().ids().end());
330 return absl::WrapUnique(new CpSatSolver(
331 std::move(cp_sat_model),
332 /*variable_ids=*/std::move(variable_ids),
333 /*linear_constraint_ids=*/std::move(linear_constraint_ids)));
334}
335
336absl::StatusOr<SolveResultProto> CpSatSolver::Solve(
337 const SolveParametersProto& parameters,
338 const ModelSolveParametersProto& model_parameters,
339 const MessageCallback message_cb,
340 const CallbackRegistrationProto& callback_registration, const Callback cb,
341 const SolveInterrupter* const interrupter) {
342 const absl::Time start = absl::Now();
343
345 callback_registration,
346 /*supported_events=*/{CALLBACK_EVENT_MIP_SOLUTION}));
347 if (callback_registration.add_lazy_constraints()) {
348 return absl::InvalidArgumentError(
349 "CallbackRegistrationProto.add_lazy_constraints=true is not supported "
350 "for CP-SAT.");
351 }
352 // We need not check callback_registration.add_cuts, as cuts can only be added
353 // at event MIP_NODE which we have already validated is not supported.
354
355 SolveResultProto result;
356 MPModelRequest req;
357 // Here we must make a copy since Solve() can be called multiple times with
358 // different parameters. Hence we can't move `cp_sat_model`.
359 *req.mutable_model() = cp_sat_model_;
360
361 req.set_solver_type(MPModelRequest::SAT_INTEGER_PROGRAMMING);
362 bool used_cutoff = false;
363 {
364 std::vector<std::string> param_warnings =
365 SetSolveParameters(parameters,
366 /*has_message_callback=*/message_cb != nullptr, req);
367 if (parameters.has_cutoff_limit()) {
368 used_cutoff = ApplyCutoff(parameters.cutoff_limit(), req.mutable_model());
369 if (!used_cutoff) {
370 param_warnings.push_back(
371 "The cutoff_limit parameter not supported for quadratic objectives "
372 "with CP-SAT.");
373 }
374 }
375 if (!param_warnings.empty()) {
376 return absl::InvalidArgumentError(absl::StrJoin(param_warnings, "; "));
377 }
378 }
379
380 if (!model_parameters.solution_hints().empty()) {
381 int i = 0;
382 for (const auto [id, val] :
383 MakeView(model_parameters.solution_hints(0).variable_values())) {
384 while (variable_ids_[i] < id) {
385 ++i;
386 }
387 req.mutable_model()->mutable_solution_hint()->add_var_index(i);
388 req.mutable_model()->mutable_solution_hint()->add_var_value(val);
389 }
390 }
391
392 // We need to chain the user interrupter through a local interrupter, because
393 // if we termiante early from a callback request, we don't want to incorrectly
394 // modify the input state.
395 SolveInterrupter local_interrupter;
396 std::atomic<bool> interrupt_solve = false;
397 local_interrupter.AddInterruptionCallback([&]() { interrupt_solve = true; });
398
399 // Setup a callback on the user provided so that we interrupt the solver.
400 const ScopedSolveInterrupterCallback scoped_interrupt_cb(
401 interrupter, [&]() { local_interrupter.Interrupt(); });
402
403 std::function<void(const std::string&)> logging_callback;
404 if (message_cb != nullptr) {
405 logging_callback = [&](absl::string_view message) {
406 message_cb(absl::StrSplit(message, '\n'));
407 };
408 }
409
410 const absl::flat_hash_set<CallbackEventProto> events =
411 EventSet(callback_registration);
412 std::function<void(const MPSolution&)> solution_callback;
413 absl::Status callback_error = absl::OkStatus();
414 if (events.contains(CALLBACK_EVENT_MIP_SOLUTION)) {
415 solution_callback =
416 [this, &cb, &callback_error, &local_interrupter,
417 &callback_registration](const MPSolution& mp_solution) {
418 if (!callback_error.ok()) {
419 // A previous callback failed.
420 return;
421 }
422 CallbackDataProto cb_data;
423 cb_data.set_event(CALLBACK_EVENT_MIP_SOLUTION);
424 *cb_data.mutable_primal_solution_vector() =
425 ExtractSolution(mp_solution.variable_value(),
426 callback_registration.mip_solution_filter());
427 const absl::StatusOr<CallbackResultProto> cb_result = cb(cb_data);
428 if (!cb_result.ok()) {
429 callback_error = cb_result.status();
430 // Note: we will be returning a status error, we do not need to
431 // worry about interpreting this as TERMINATION_REASON_INTERRUPTED.
432 local_interrupter.Interrupt();
433 } else if (cb_result->terminate()) {
434 local_interrupter.Interrupt();
435 }
436 // Note cb_result.cuts and cb_result.suggested solutions are not
437 // supported by CP-SAT and we have validated they are empty.
438 };
439 }
440
441 // CP-SAT returns "infeasible" for inverted bounds.
442 RETURN_IF_ERROR(ListInvertedBounds().ToStatus());
443
444 const MPSolutionResponse response = SatSolveProto(
445 std::move(req), &interrupt_solve, logging_callback, solution_callback);
446 RETURN_IF_ERROR(callback_error) << "error in callback";
447 ASSIGN_OR_RETURN(*result.mutable_termination(),
448 GetTermination(local_interrupter.IsInterrupted(),
449 /*maximize=*/cp_sat_model_.maximize(),
450 /*used_cutoff=*/used_cutoff, response));
451 const SparseVectorFilterProto& var_values_filter =
452 model_parameters.variable_values_filter();
453 auto add_solution =
454 [this, &result, &var_values_filter](
455 const google::protobuf::RepeatedField<double>& variable_values,
456 double objective) {
457 PrimalSolutionProto& solution =
458 *result.add_solutions()->mutable_primal_solution();
459 *solution.mutable_variable_values() =
460 ExtractSolution(variable_values, var_values_filter);
461 solution.set_objective_value(objective);
462 solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
463 };
464 if (response.status() == MPSOLVER_OPTIMAL ||
465 response.status() == MPSOLVER_FEASIBLE) {
466 add_solution(response.variable_value(), response.objective_value());
467 for (const MPSolution& extra_solution : response.additional_solutions()) {
468 add_solution(extra_solution.variable_value(),
469 extra_solution.objective_value());
470 }
471 }
472
474 absl::Now() - start, result.mutable_solve_stats()->mutable_solve_time()));
475
476 return result;
477}
478
479absl::StatusOr<bool> CpSatSolver::Update(const ModelUpdateProto&) {
480 return false;
481}
482
483CpSatSolver::CpSatSolver(MPModelProto cp_sat_model,
484 std::vector<int64_t> variable_ids,
485 std::vector<int64_t> linear_constraint_ids)
486 : cp_sat_model_(std::move(cp_sat_model)),
487 variable_ids_(std::move(variable_ids)),
488 linear_constraint_ids_(std::move(linear_constraint_ids)) {}
489
490SparseDoubleVectorProto CpSatSolver::ExtractSolution(
491 const absl::Span<const double> cp_sat_variable_values,
492 const SparseVectorFilterProto& filter) const {
493 // Pre-condition: we assume one-to-one correspondence of input variables to
494 // solution's variables.
495 CHECK_EQ(cp_sat_variable_values.size(), variable_ids_.size());
496
497 SparseVectorFilterPredicate predicate(filter);
498 SparseDoubleVectorProto result;
499 for (int i = 0; i < variable_ids_.size(); ++i) {
500 const int64_t id = variable_ids_[i];
501 const double value = cp_sat_variable_values[i];
502 if (predicate.AcceptsAndUpdate(id, value)) {
503 result.add_ids(id);
504 result.add_values(value);
505 }
506 }
507 return result;
508}
509
510InvertedBounds CpSatSolver::ListInvertedBounds() const {
511 InvertedBounds inverted_bounds;
512 for (int v = 0; v < cp_sat_model_.variable_size(); ++v) {
513 const MPVariableProto& var = cp_sat_model_.variable(v);
514 if (var.lower_bound() > var.upper_bound()) {
515 inverted_bounds.variables.push_back(variable_ids_[v]);
516 }
517 }
518 for (int c = 0; c < cp_sat_model_.constraint_size(); ++c) {
519 const MPConstraintProto& cstr = cp_sat_model_.constraint(c);
520 if (cstr.lower_bound() > cstr.upper_bound()) {
521 inverted_bounds.linear_constraints.push_back(linear_constraint_ids_[c]);
522 }
523 }
524
525 return inverted_bounds;
526}
527
528absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
529CpSatSolver::ComputeInfeasibleSubsystem(const SolveParametersProto&,
531 const SolveInterrupter*) {
532 return absl::UnimplementedError(
533 "CPSAT does not provide a method to compute an infeasible subsystem");
534}
535
537
538} // namespace math_opt
539} // namespace operations_research
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
CallbackId AddInterruptionCallback(Callback callback) const
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto &parameters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, const SolveInterrupter *interrupter) override
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto &parameters, MessageCallback message_cb, const SolveInterrupter *interrupter) override
static absl::StatusOr< std::unique_ptr< SolverInterface > > New(const ModelProto &model, const InitArgs &init_args)
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >( const CallbackDataProto &)> Callback
SatParameters parameters
int64_t value
IntVar * var
absl::Span< const int64_t > variable_ids
GRBmodel * model
double solution
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
absl::flat_hash_set< CallbackEventProto > EventSet(const CallbackRegistrationProto &callback_registration)
Returns the callback_registration.request_registration as a set of enums.
TerminationProto OptimalTerminationProto(const double finite_primal_objective, const double dual_objective, const absl::string_view detail)
TerminationProto InfeasibleOrUnboundedTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
absl::StatusOr<::operations_research::MPModelProto > MathOptModelToMPModelProto(const ::operations_research::math_opt::ModelProto &model)
TerminationProto FeasibleTerminationProto(const bool is_maximize, const LimitProto limit, const double primal_objective, const std::optional< double > optional_dual_objective, const absl::string_view detail)
TerminationProto NoSolutionFoundTerminationProto(const bool is_maximize, const LimitProto limit, const std::optional< double > optional_dual_objective, const absl::string_view detail)
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto &registration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
TerminationProto CutoffTerminationProto(bool is_maximize, const absl::string_view detail)
Calls NoSolutionFoundTerminationProto() with LIMIT_CUTOFF LIMIT.
TerminationProto InfeasibleTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
In SWIG mode, we don't want anything besides these top-level includes.
MPSolutionResponse SatSolveProto(LazyMutableCopy< MPModelRequest > request, std::atomic< bool > *interrupt_solve, std::function< void(const std::string &)> logging_callback, std::function< void(const MPSolution &)> solution_callback)
std::string ProtoEnumToString(ProtoEnumType enum_value)
Definition proto_utils.h:50
std::string EncodeParametersAsString(const P &parameters)
Definition proto_utils.h:75
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
StatusBuilder InvalidArgumentErrorBuilder()
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)
int64_t start
std::string message
Definition trace.cc:397