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"
68constexpr SupportedProblemStructures kCpSatSupportedStructures = {
77bool ApplyCutoff(
const double cutoff, MPModelProto*
model) {
80 if (
model->has_quadratic_objective()) {
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();
90 cutoff_constraint->add_var_index(i);
91 cutoff_constraint->add_coefficient(obj_coef);
94 const double cutoff_minus_offset = cutoff -
model->objective_offset();
95 if (
model->maximize()) {
97 cutoff_constraint->set_lower_bound(cutoff_minus_offset);
100 cutoff_constraint->set_upper_bound(cutoff_minus_offset);
107std::vector<std::string> SetSolveParameters(
108 const SolveParametersProto&
parameters,
const bool has_message_callback,
109 MPModelRequest& request) {
110 std::vector<std::string> warnings;
112 request.set_solver_time_limit_seconds(absl::ToDoubleSeconds(
117 "The iteration_limit parameter is not supported for CP-SAT.");
120 warnings.push_back(
"The node_limit parameter is not supported for CP-SAT.");
130 sat::SatParameters sat_parameters;
134 sat_parameters.set_catch_sigint_signal(
false);
137 sat_parameters.set_random_seed(
parameters.random_seed());
140 sat_parameters.set_num_search_workers(
parameters.threads());
142 if (
parameters.has_relative_gap_tolerance()) {
143 sat_parameters.set_relative_gap_limit(
parameters.relative_gap_tolerance());
146 if (
parameters.has_absolute_gap_tolerance()) {
147 sat_parameters.set_absolute_gap_limit(
parameters.absolute_gap_tolerance());
152 "The best_bound_limit parameter is not supported for CP-SAT.");
156 "The objective_limit parameter is not supported for CP-SAT.");
160 sat_parameters.set_stop_after_first_solution(
true);
162 warnings.push_back(absl::StrCat(
163 "The CP-SAT solver only supports value 1 for solution_limit, found: ",
168 sat_parameters.set_solution_pool_size(
parameters.solution_pool_size());
169 sat_parameters.set_fill_additional_solutions_in_response(
true);
171 if (
parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
173 absl::StrCat(
"Setting lp_algorithm (was set to ",
175 ") is not supported for CP_SAT solver"));
177 if (
parameters.presolve() != EMPHASIS_UNSPECIFIED) {
180 sat_parameters.set_cp_model_presolve(
false);
183 case EMPHASIS_MEDIUM:
185 case EMPHASIS_VERY_HIGH:
186 sat_parameters.set_cp_model_presolve(
true);
189 LOG(FATAL) <<
"Presolve emphasis: "
191 <<
" unknown, error setting CP-SAT parameters";
194 if (
parameters.scaling() != EMPHASIS_UNSPECIFIED) {
195 warnings.push_back(absl::StrCat(
"Setting the scaling (was set to ",
197 ") is not supported for CP_SAT solver"));
199 if (
parameters.cuts() != EMPHASIS_UNSPECIFIED) {
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);
212 case EMPHASIS_MEDIUM:
214 case EMPHASIS_VERY_HIGH:
218 <<
" unknown, error setting CP-SAT parameters";
221 if (
parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
222 warnings.push_back(absl::StrCat(
"Setting the heuristics (was set to ",
224 ") is not supported for CP_SAT solver"));
226 sat_parameters.MergeFrom(
parameters.cp_sat());
231 if (has_message_callback) {
234 sat_parameters.set_log_search_progress(
true);
238 sat_parameters.set_log_to_stdout(
false);
242 request.set_enable_internal_solver_output(
parameters.enable_output());
245 request.set_solver_specific_parameters(
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:
256 response.best_objective_bound(),
257 response.status_str());
259 case MPSOLVER_INFEASIBLE:
265 maximize, FEASIBILITY_STATUS_FEASIBLE,
266 response.status_str());
269 case MPSOLVER_UNKNOWN_STATUS:
283 if (absl::StrContains(response.status_str(),
"infeasible or unbounded")) {
286 FEASIBILITY_STATUS_UNDETERMINED,
287 response.status_str());
290 response.status_str());
293 case MPSOLVER_FEASIBLE:
295 maximize, is_interrupted ? LIMIT_INTERRUPTED : LIMIT_UNDETERMINED,
296 response.objective_value(), response.best_objective_bound(),
297 response.status_str());
299 case MPSOLVER_NOT_SOLVED:
301 maximize, is_interrupted ? LIMIT_INTERRUPTED : LIMIT_UNDETERMINED,
302 std::nullopt, response.status_str());
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();
312 return absl::InternalError(
313 absl::StrCat(
"unexpected solve status: ", response.status()));
315 return absl::InternalError(
316 absl::StrCat(
"unimplemented solve status: ", response.status()));
327 model.variables().ids().end());
328 std::vector linear_constraint_ids(
model.linear_constraints().ids().begin(),
329 model.linear_constraints().ids().end());
331 std::move(cp_sat_model),
333 std::move(linear_constraint_ids)));
338 const ModelSolveParametersProto& model_parameters,
340 const CallbackRegistrationProto& callback_registration,
const Callback cb,
342 const absl::Time
start = absl::Now();
345 callback_registration,
346 {CALLBACK_EVENT_MIP_SOLUTION}));
347 if (callback_registration.add_lazy_constraints()) {
348 return absl::InvalidArgumentError(
349 "CallbackRegistrationProto.add_lazy_constraints=true is not supported "
355 SolveResultProto result;
359 *req.mutable_model() = cp_sat_model_;
361 req.set_solver_type(MPModelRequest::SAT_INTEGER_PROGRAMMING);
362 bool used_cutoff =
false;
364 std::vector<std::string> param_warnings =
366 message_cb !=
nullptr, req);
368 used_cutoff = ApplyCutoff(
parameters.cutoff_limit(), req.mutable_model());
370 param_warnings.push_back(
371 "The cutoff_limit parameter not supported for quadratic objectives "
375 if (!param_warnings.empty()) {
376 return absl::InvalidArgumentError(absl::StrJoin(param_warnings,
"; "));
380 if (!model_parameters.solution_hints().empty()) {
382 for (
const auto [
id, val] :
383 MakeView(model_parameters.solution_hints(0).variable_values())) {
384 while (variable_ids_[i] <
id) {
387 req.mutable_model()->mutable_solution_hint()->add_var_index(i);
388 req.mutable_model()->mutable_solution_hint()->add_var_value(val);
396 std::atomic<bool> interrupt_solve =
false;
401 interrupter, [&]() { local_interrupter.
Interrupt(); });
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'));
410 const absl::flat_hash_set<CallbackEventProto> events =
412 std::function<void(
const MPSolution&)> solution_callback;
413 absl::Status callback_error = absl::OkStatus();
414 if (events.contains(CALLBACK_EVENT_MIP_SOLUTION)) {
416 [
this, &cb, &callback_error, &local_interrupter,
417 &callback_registration](
const MPSolution& mp_solution) {
418 if (!callback_error.ok()) {
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();
432 local_interrupter.Interrupt();
433 }
else if (cb_result->terminate()) {
434 local_interrupter.Interrupt();
445 std::move(req), &interrupt_solve, logging_callback, solution_callback);
449 cp_sat_model_.maximize(),
450 used_cutoff, response));
451 const SparseVectorFilterProto& var_values_filter =
452 model_parameters.variable_values_filter();
454 [
this, &result, &var_values_filter](
455 const google::protobuf::RepeatedField<double>& variable_values,
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);
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());
474 absl::Now() -
start, result.mutable_solve_stats()->mutable_solve_time()));
483CpSatSolver::CpSatSolver(MPModelProto cp_sat_model,
485 std::vector<int64_t> linear_constraint_ids)
486 : cp_sat_model_(
std::move(cp_sat_model)),
488 linear_constraint_ids_(
std::move(linear_constraint_ids)) {}
490SparseDoubleVectorProto CpSatSolver::ExtractSolution(
491 const absl::Span<const double> cp_sat_variable_values,
492 const SparseVectorFilterProto& filter)
const {
495 CHECK_EQ(cp_sat_variable_values.size(), variable_ids_.size());
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)) {
504 result.add_values(
value);
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]);
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]);
525 return inverted_bounds;
528absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
532 return absl::UnimplementedError(
533 "CPSAT does not provide a method to compute an infeasible subsystem");
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
bool IsInterrupted() const
CallbackId AddInterruptionCallback(Callback callback) const
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto ¶meters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, const SolveInterrupter *interrupter) override
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto ¶meters, 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
absl::Span< const int64_t > variable_ids
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 ®istration, 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)
std::string EncodeParametersAsString(const P ¶meters)
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
StatusBuilder InvalidArgumentErrorBuilder()
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)
Initialization arguments.