27 #include "absl/container/flat_hash_set.h"
28 #include "absl/memory/memory.h"
29 #include "absl/status/status.h"
30 #include "absl/status/statusor.h"
31 #include "absl/strings/match.h"
32 #include "absl/strings/str_cat.h"
33 #include "absl/strings/str_join.h"
34 #include "absl/strings/str_split.h"
35 #include "absl/time/clock.h"
36 #include "absl/time/time.h"
37 #include "absl/types/span.h"
38 #include "absl/log/check.h"
42 #include "ortools/linear_solver/linear_solver.pb.h"
44 #include "ortools/math_opt/callback.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"
67 constexpr
double kInf = std::numeric_limits<double>::infinity();
69 constexpr SupportedProblemStructures kCpSatSupportedStructures = {
78 bool ApplyCutoff(
const double cutoff, MPModelProto*
model) {
81 if (
model->has_quadratic_objective()) {
87 MPConstraintProto*
const cutoff_constraint =
model->add_constraint();
88 for (
int i = 0; i <
model->variable_size(); ++i) {
89 const double obj_coef =
model->variable(i).objective_coefficient();
91 cutoff_constraint->add_var_index(i);
92 cutoff_constraint->add_coefficient(obj_coef);
95 const double cutoff_minus_offset = cutoff -
model->objective_offset();
96 if (
model->maximize()) {
98 cutoff_constraint->set_lower_bound(cutoff_minus_offset);
101 cutoff_constraint->set_upper_bound(cutoff_minus_offset);
108 std::vector<std::string> SetSolveParameters(
109 const SolveParametersProto&
parameters,
const bool has_message_callback,
110 MPModelRequest& request) {
111 std::vector<std::string> warnings;
113 request.set_solver_time_limit_seconds(absl::ToDoubleSeconds(
117 warnings.push_back(
"The node_limit parameter is not supported for CP-SAT.");
127 sat::SatParameters sat_parameters;
131 sat_parameters.set_catch_sigint_signal(
false);
134 sat_parameters.set_random_seed(
parameters.random_seed());
137 sat_parameters.set_num_search_workers(
parameters.threads());
139 if (
parameters.has_relative_gap_tolerance()) {
140 sat_parameters.set_relative_gap_limit(
parameters.relative_gap_tolerance());
143 if (
parameters.has_absolute_gap_tolerance()) {
144 sat_parameters.set_absolute_gap_limit(
parameters.absolute_gap_tolerance());
149 "The best_bound_limit parameter is not supported for CP-SAT.");
153 "The objective_limit parameter is not supported for CP-SAT.");
157 sat_parameters.set_stop_after_first_solution(
true);
159 warnings.push_back(absl::StrCat(
160 "The CP-SAT solver only supports value 1 for solution_limit, found: ",
165 sat_parameters.set_solution_pool_size(
parameters.solution_pool_size());
166 sat_parameters.set_fill_additional_solutions_in_response(
true);
168 if (
parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
170 absl::StrCat(
"Setting the LP Algorithm (was set to ",
172 ") is not supported for CP_SAT solver"));
174 if (
parameters.presolve() != EMPHASIS_UNSPECIFIED) {
177 sat_parameters.set_cp_model_presolve(
false);
180 case EMPHASIS_MEDIUM:
182 case EMPHASIS_VERY_HIGH:
183 sat_parameters.set_cp_model_presolve(
true);
186 LOG(FATAL) <<
"Presolve emphasis: "
188 <<
" unknown, error setting CP-SAT parameters";
191 if (
parameters.scaling() != EMPHASIS_UNSPECIFIED) {
192 warnings.push_back(absl::StrCat(
"Setting the scaling (was set to ",
194 ") is not supported for CP_SAT solver"));
196 if (
parameters.cuts() != EMPHASIS_UNSPECIFIED) {
201 sat_parameters.set_add_cg_cuts(
false);
202 sat_parameters.set_add_mir_cuts(
false);
203 sat_parameters.set_add_zero_half_cuts(
false);
204 sat_parameters.set_add_clique_cuts(
false);
205 sat_parameters.set_max_all_diff_cut_size(0);
206 sat_parameters.set_add_lin_max_cuts(
false);
209 case EMPHASIS_MEDIUM:
211 case EMPHASIS_VERY_HIGH:
215 <<
" unknown, error setting CP-SAT parameters";
218 if (
parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
219 warnings.push_back(absl::StrCat(
"Setting the heuristics (was set to ",
221 ") is not supported for CP_SAT solver"));
223 sat_parameters.MergeFrom(
parameters.cp_sat());
228 if (has_message_callback) {
231 sat_parameters.set_log_search_progress(
true);
235 sat_parameters.set_log_to_stdout(
false);
239 request.set_enable_internal_solver_output(
parameters.enable_output());
242 request.set_solver_specific_parameters(
247 absl::StatusOr<std::pair<SolveStatsProto, TerminationProto>>
248 GetTerminationAndStats(
const bool is_interrupted,
const bool maximize,
249 const bool used_cutoff,
250 const MPSolutionResponse&
response) {
251 SolveStatsProto solve_stats;
252 TerminationProto termination;
255 solve_stats.mutable_problem_status()->set_primal_status(
256 FEASIBILITY_STATUS_UNDETERMINED);
257 solve_stats.set_best_primal_bound(maximize ? -
kInf :
kInf);
258 solve_stats.mutable_problem_status()->set_dual_status(
259 FEASIBILITY_STATUS_UNDETERMINED);
260 solve_stats.set_best_dual_bound(maximize ?
kInf : -
kInf);
264 case MPSOLVER_OPTIMAL:
267 solve_stats.mutable_problem_status()->set_primal_status(
268 FEASIBILITY_STATUS_FEASIBLE);
269 solve_stats.set_best_primal_bound(
response.objective_value());
270 solve_stats.mutable_problem_status()->set_dual_status(
271 FEASIBILITY_STATUS_FEASIBLE);
272 solve_stats.set_best_dual_bound(
response.best_objective_bound());
274 case MPSOLVER_INFEASIBLE:
281 solve_stats.mutable_problem_status()->set_primal_status(
282 FEASIBILITY_STATUS_INFEASIBLE);
285 case MPSOLVER_UNKNOWN_STATUS:
299 if (absl::StrContains(
response.status_str(),
"infeasible or unbounded")) {
301 TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED,
response.status_str());
302 solve_stats.mutable_problem_status()->set_primal_or_dual_infeasible(
309 case MPSOLVER_FEASIBLE:
311 is_interrupted ? LIMIT_INTERRUPTED : LIMIT_UNDETERMINED,
313 solve_stats.mutable_problem_status()->set_primal_status(
314 FEASIBILITY_STATUS_FEASIBLE);
315 solve_stats.set_best_primal_bound(
response.objective_value());
316 solve_stats.set_best_dual_bound(
response.best_objective_bound());
317 if (std::isfinite(
response.best_objective_bound())) {
318 solve_stats.mutable_problem_status()->set_dual_status(
319 FEASIBILITY_STATUS_FEASIBLE);
322 case MPSOLVER_NOT_SOLVED:
324 is_interrupted ? LIMIT_INTERRUPTED : LIMIT_UNDETERMINED,
327 case MPSOLVER_MODEL_INVALID:
328 return absl::InternalError(
329 absl::StrCat(
"cp-sat solver returned MODEL_INVALID, details: ",
332 return absl::InternalError(
333 absl::StrCat(
"unexpected solve status: ",
response.status()));
335 return std::make_pair(std::move(solve_stats), std::move(termination));
346 model.variables().ids().end());
347 std::vector linear_constraint_ids(
model.linear_constraints().ids().begin(),
348 model.linear_constraints().ids().end());
350 std::move(cp_sat_model),
352 std::move(linear_constraint_ids)));
357 const ModelSolveParametersProto& model_parameters,
359 const CallbackRegistrationProto& callback_registration,
const Callback cb,
361 const absl::Time
start = absl::Now();
364 callback_registration,
365 {CALLBACK_EVENT_MIP_SOLUTION}));
366 if (callback_registration.add_lazy_constraints()) {
367 return absl::InvalidArgumentError(
368 "CallbackRegistrationProto.add_lazy_constraints=true is not supported "
374 SolveResultProto result;
378 *req.mutable_model() = cp_sat_model_;
380 req.set_solver_type(MPModelRequest::SAT_INTEGER_PROGRAMMING);
381 bool used_cutoff =
false;
383 std::vector<std::string> param_warnings =
385 message_cb !=
nullptr, req);
387 used_cutoff = ApplyCutoff(
parameters.cutoff_limit(), req.mutable_model());
389 param_warnings.push_back(
390 "The cutoff_limit parameter not supported for quadratic objectives "
394 if (!param_warnings.empty()) {
395 return absl::InvalidArgumentError(absl::StrJoin(param_warnings,
"; "));
399 if (!model_parameters.solution_hints().empty()) {
401 for (
const auto [
id, val] :
402 MakeView(model_parameters.solution_hints(0).variable_values())) {
403 while (variable_ids_[i] <
id) {
406 req.mutable_model()->mutable_solution_hint()->add_var_index(i);
407 req.mutable_model()->mutable_solution_hint()->add_var_value(val);
415 std::atomic<bool> interrupt_solve =
false;
420 interrupter, [&]() { local_interrupter.
Interrupt(); });
422 std::function<void(
const std::string&)> logging_callback;
423 if (message_cb !=
nullptr) {
424 logging_callback = [&](
const std::string&
message) {
425 message_cb(absl::StrSplit(
message,
'\n'));
429 const absl::flat_hash_set<CallbackEventProto> events =
431 std::function<void(
const MPSolution&)> solution_callback;
432 absl::Status callback_error = absl::OkStatus();
433 if (events.contains(CALLBACK_EVENT_MIP_SOLUTION)) {
435 [
this, &cb, &callback_error, &local_interrupter,
436 &callback_registration](
const MPSolution& mp_solution) {
437 if (!callback_error.ok()) {
441 CallbackDataProto cb_data;
442 cb_data.set_event(CALLBACK_EVENT_MIP_SOLUTION);
443 *cb_data.mutable_primal_solution_vector() =
444 ExtractSolution(mp_solution.variable_value(),
445 callback_registration.mip_solution_filter());
446 const absl::StatusOr<CallbackResultProto> cb_result = cb(cb_data);
447 if (!cb_result.ok()) {
448 callback_error = cb_result.status();
451 local_interrupter.Interrupt();
452 }
else if (cb_result->terminate()) {
453 local_interrupter.Interrupt();
465 logging_callback, solution_callback));
468 (
auto [solve_stats, termination]),
470 cp_sat_model_.maximize(),
472 *result.mutable_solve_stats() = std::move(solve_stats);
473 *result.mutable_termination() = std::move(termination);
474 const SparseVectorFilterProto& var_values_filter =
475 model_parameters.variable_values_filter();
477 [
this, &result, &var_values_filter](
478 const google::protobuf::RepeatedField<double>& variable_values,
480 PrimalSolutionProto& solution =
481 *result.add_solutions()->mutable_primal_solution();
482 *solution.mutable_variable_values() =
483 ExtractSolution(variable_values, var_values_filter);
484 solution.set_objective_value(objective);
485 solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
487 if (
response.status() == MPSOLVER_OPTIMAL ||
488 response.status() == MPSOLVER_FEASIBLE) {
490 for (
const MPSolution& extra_solution :
response.additional_solutions()) {
491 add_solution(extra_solution.variable_value(),
492 extra_solution.objective_value());
497 absl::Now() -
start, result.mutable_solve_stats()->mutable_solve_time()));
506 CpSatSolver::CpSatSolver(MPModelProto cp_sat_model,
508 std::vector<int64_t> linear_constraint_ids)
509 : cp_sat_model_(std::move(cp_sat_model)),
511 linear_constraint_ids_(std::move(linear_constraint_ids)) {}
513 SparseDoubleVectorProto CpSatSolver::ExtractSolution(
514 const absl::Span<const double> cp_sat_variable_values,
515 const SparseVectorFilterProto& filter)
const {
518 CHECK_EQ(cp_sat_variable_values.size(), variable_ids_.size());
520 SparseVectorFilterPredicate predicate(filter);
521 SparseDoubleVectorProto result;
522 for (
int i = 0; i < variable_ids_.size(); ++i) {
523 const int64_t
id = variable_ids_[i];
524 const double value = cp_sat_variable_values[i];
525 if (predicate.AcceptsAndUpdate(
id,
value)) {
527 result.add_values(
value);
533 InvertedBounds CpSatSolver::ListInvertedBounds()
const {
534 InvertedBounds inverted_bounds;
535 for (
int v = 0; v < cp_sat_model_.variable_size(); ++v) {
536 const MPVariableProto&
var = cp_sat_model_.variable(v);
537 if (
var.lower_bound() >
var.upper_bound()) {
538 inverted_bounds.variables.push_back(variable_ids_[v]);
541 for (
int c = 0; c < cp_sat_model_.constraint_size(); ++c) {
542 const MPConstraintProto& cstr = cp_sat_model_.constraint(c);
543 if (cstr.lower_bound() > cstr.upper_bound()) {
544 inverted_bounds.linear_constraints.push_back(linear_constraint_ids_[c]);
548 return inverted_bounds;
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
absl::StatusOr< bool > 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 ¶meters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, SolveInterrupter *interrupter) override
bool IsInterrupted() const
CallbackId AddInterruptionCallback(Callback callback)
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >(const CallbackDataProto &)> Callback
SharedResponseManager * response
absl::Span< const int64_t > variable_ids
TerminationProto FeasibleTermination(const LimitProto limit, const absl::string_view detail)
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto ®istration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
MATH_OPT_REGISTER_SOLVER(SOLVER_TYPE_CP_SAT, CpSatSolver::New)
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
absl::StatusOr<::operations_research::MPModelProto > MathOptModelToMPModelProto(const ::operations_research::math_opt::ModelProto &model)
TerminationProto NoSolutionFoundTermination(const LimitProto limit, const absl::string_view detail)
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
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 ¶meters)
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)