27#include "absl/base/attributes.h"
28#include "absl/base/nullability.h"
29#include "absl/base/thread_annotations.h"
30#include "absl/container/flat_hash_set.h"
31#include "absl/functional/any_invocable.h"
32#include "absl/log/check.h"
33#include "absl/log/log.h"
34#include "absl/memory/memory.h"
35#include "absl/status/status.h"
36#include "absl/status/statusor.h"
37#include "absl/strings/match.h"
38#include "absl/strings/str_cat.h"
39#include "absl/strings/str_join.h"
40#include "absl/strings/str_split.h"
41#include "absl/strings/string_view.h"
42#include "absl/synchronization/mutex.h"
43#include "absl/time/clock.h"
44#include "absl/time/time.h"
45#include "absl/types/span.h"
75constexpr double kInf = std::numeric_limits<double>::infinity();
86bool ApplyCutoff(
const double cutoff, MPModelProto* model) {
89 if (model->has_quadratic_objective()) {
95 MPConstraintProto*
const cutoff_constraint = model->add_constraint();
96 for (
int i = 0;
i < model->variable_size(); ++
i) {
97 const double obj_coef = model->variable(i).objective_coefficient();
99 cutoff_constraint->add_var_index(i);
100 cutoff_constraint->add_coefficient(obj_coef);
103 const double cutoff_minus_offset = cutoff - model->objective_offset();
104 if (model->maximize()) {
106 cutoff_constraint->set_lower_bound(cutoff_minus_offset);
109 cutoff_constraint->set_upper_bound(cutoff_minus_offset);
116std::vector<std::string> SetSolveParameters(
118 MPModelRequest& request) {
119 std::vector<std::string> warnings;
120 if (parameters.has_time_limit()) {
121 request.set_solver_time_limit_seconds(absl::ToDoubleSeconds(
124 if (parameters.has_iteration_limit()) {
126 "The iteration_limit parameter is not supported for CP-SAT.");
128 if (parameters.has_node_limit()) {
129 warnings.push_back(
"The node_limit parameter is not supported for CP-SAT.");
139 sat::SatParameters sat_parameters;
143 sat_parameters.set_catch_sigint_signal(
false);
145 if (parameters.has_random_seed()) {
146 sat_parameters.set_random_seed(parameters.random_seed());
148 if (parameters.has_threads()) {
149 sat_parameters.set_num_workers(parameters.threads());
151 if (parameters.has_relative_gap_tolerance()) {
152 sat_parameters.set_relative_gap_limit(parameters.relative_gap_tolerance());
155 if (parameters.has_absolute_gap_tolerance()) {
156 sat_parameters.set_absolute_gap_limit(parameters.absolute_gap_tolerance());
159 if (parameters.has_best_bound_limit()) {
161 "The best_bound_limit parameter is not supported for CP-SAT.");
163 if (parameters.has_objective_limit()) {
165 "The objective_limit parameter is not supported for CP-SAT.");
167 if (parameters.has_solution_limit()) {
168 if (parameters.solution_limit() == 1) {
169 sat_parameters.set_stop_after_first_solution(
true);
171 warnings.push_back(absl::StrCat(
172 "The CP-SAT solver only supports value 1 for solution_limit, found: ",
173 parameters.solution_limit()));
176 if (parameters.has_solution_pool_size()) {
177 sat_parameters.set_solution_pool_size(parameters.solution_pool_size());
178 sat_parameters.set_fill_additional_solutions_in_response(
true);
182 absl::StrCat(
"Setting lp_algorithm (was set to ",
184 ") is not supported for CP_SAT solver"));
187 switch (parameters.presolve()) {
189 sat_parameters.set_cp_model_presolve(
false);
195 sat_parameters.set_cp_model_presolve(
true);
198 LOG(FATAL) <<
"Presolve emphasis: "
200 <<
" unknown, error setting CP-SAT parameters";
204 warnings.push_back(absl::StrCat(
"Setting the scaling (was set to ",
206 ") is not supported for CP_SAT solver"));
209 switch (parameters.cuts()) {
213 sat_parameters.set_add_cg_cuts(
false);
214 sat_parameters.set_add_mir_cuts(
false);
215 sat_parameters.set_add_zero_half_cuts(
false);
216 sat_parameters.set_add_clique_cuts(
false);
217 sat_parameters.set_max_all_diff_cut_size(0);
218 sat_parameters.set_add_lin_max_cuts(
false);
227 <<
" unknown, error setting CP-SAT parameters";
231 warnings.push_back(absl::StrCat(
"Setting the heuristics (was set to ",
233 ") is not supported for CP_SAT solver"));
235 sat_parameters.MergeFrom(parameters.cp_sat());
240 if (has_message_callback) {
243 sat_parameters.set_log_search_progress(
true);
247 sat_parameters.set_log_to_stdout(
false);
251 request.set_enable_internal_solver_output(parameters.enable_output());
254 request.set_solver_specific_parameters(
259absl::StatusOr<TerminationProto> GetTermination(
260 const bool is_interrupted,
const bool maximize,
const bool used_cutoff,
261 const MPSolutionResponse& response) {
262 switch (response.status()) {
265 response.best_objective_bound(),
266 response.status_str());
275 response.status_str());
292 if (absl::StrContains(response.status_str(),
"infeasible or unbounded")) {
296 response.status_str());
299 response.status_str());
305 response.objective_value(), response.best_objective_bound(),
306 response.status_str());
311 std::nullopt, response.status_str());
314 return absl::InternalError(
315 absl::StrCat(
"cp-sat solver returned MODEL_INVALID, details: ",
316 response.status_str()));
319 <<
"invalid cp-sat parameters: " << response.status_str();
321 return absl::InternalError(
322 absl::StrCat(
"unexpected solve status: ", response.status()));
324 return absl::InternalError(
325 absl::StrCat(
"unimplemented solve status: ", response.status()));
330class CpSatCallbacks {
333 ABSL_ATTRIBUTE_LIFETIME_BOUND,
334 SolveInterrupter* absl_nonnull local_interrupter
335 ABSL_ATTRIBUTE_LIFETIME_BOUND,
336 absl_nonnull absl::AnyInvocable<
337 SparseDoubleVectorProto(absl::Span<const double>)
const>
339 absl::flat_hash_set<CallbackEventProto> events,
343 CpSatCallbacks(
const CpSatCallbacks&) =
delete;
344 CpSatCallbacks& operator=(
const CpSatCallbacks&) =
delete;
348 absl_nullable std::function<void(
const MPSolution&)> MakeSolutionCallback();
352 absl_nullable std::function<void(
const double)> MakeBestBoundCallback();
354 absl::Status error()
const {
355 absl::MutexLock lock(mutex_);
360 void ExecuteCallback(
const CallbackDataProto& cb_data);
361 void UpdateMipStatsFromNewSolution(
const MPSolution& mp_solution)
362 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
365 SolveInterrupter* absl_nonnull
const local_interrupter_;
366 const absl::AnyInvocable<SparseDoubleVectorProto(absl::Span<const double>)
369 const bool has_mip_solution_event_;
370 const bool has_mip_event_;
371 const bool is_maximize_;
373 mutable absl::Mutex mutex_;
374 absl::Status error_ ABSL_GUARDED_BY(mutex_) = absl::OkStatus();
378CpSatCallbacks::CpSatCallbacks(
380 SolveInterrupter* absl_nonnull local_interrupter
381 ABSL_ATTRIBUTE_LIFETIME_BOUND,
384 extract_solution ABSL_ATTRIBUTE_LIFETIME_BOUND,
385 absl::flat_hash_set<CallbackEventProto> events,
const bool is_maximize)
387 local_interrupter_(local_interrupter),
388 extract_solution_(std::move(extract_solution)),
390 has_mip_solution_event_(cb != nullptr &&
393 is_maximize_(is_maximize) {
394 current_mip_stats_.set_primal_bound(is_maximize ? -kInf : kInf);
395 current_mip_stats_.set_dual_bound(is_maximize ? kInf : -kInf);
396 current_mip_stats_.set_number_of_solutions_found(0);
399std::function<void(
const MPSolution&)> absl_nullable
400CpSatCallbacks::MakeSolutionCallback() {
401 if (!has_mip_solution_event_ && !has_mip_event_) {
404 if (!has_mip_solution_event_) {
405 return [
this](
const MPSolution& mp_solution) {
406 absl::MutexLock lock(mutex_);
407 UpdateMipStatsFromNewSolution(mp_solution);
410 return [
this](
const MPSolution& mp_solution) {
411 CallbackDataProto cb_data;
412 cb_data.set_event(CALLBACK_EVENT_MIP_SOLUTION);
413 *cb_data.mutable_primal_solution_vector() =
414 extract_solution_(mp_solution.variable_value());
416 absl::MutexLock lock(mutex_);
417 UpdateMipStatsFromNewSolution(mp_solution);
418 *cb_data.mutable_mip_stats() = current_mip_stats_;
420 ExecuteCallback(cb_data);
424std::function<void(
const double)> absl_nullable
425CpSatCallbacks::MakeBestBoundCallback() {
426 if (!has_mip_solution_event_ && !has_mip_event_) {
429 if (!has_mip_event_) {
430 return [
this](
const double best_bound) {
431 absl::MutexLock lock(mutex_);
432 current_mip_stats_.set_dual_bound(best_bound);
435 return [
this](
const double best_bound) {
436 CallbackDataProto cb_data;
437 cb_data.set_event(CALLBACK_EVENT_MIP);
439 absl::MutexLock lock(mutex_);
440 current_mip_stats_.set_dual_bound(best_bound);
441 *cb_data.mutable_mip_stats() = current_mip_stats_;
443 ExecuteCallback(cb_data);
447void CpSatCallbacks::ExecuteCallback(
const CallbackDataProto& cb_data) {
449 absl::MutexLock lock(mutex_);
455 const absl::StatusOr<CallbackResultProto> cb_result = cb_(cb_data);
458 if (!cb_result.ok()) {
460 absl::MutexLock lock(mutex_);
461 error_ = cb_result.status();
465 local_interrupter_->Interrupt();
466 }
else if (cb_result->terminate()) {
467 local_interrupter_->Interrupt();
471void CpSatCallbacks::UpdateMipStatsFromNewSolution(
472 const MPSolution& mp_solution) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
474 current_mip_stats_.set_primal_bound(std::fmax(
475 current_mip_stats_.primal_bound(), mp_solution.objective_value()));
477 current_mip_stats_.set_primal_bound(std::fmin(
478 current_mip_stats_.primal_bound(), mp_solution.objective_value()));
480 current_mip_stats_.set_number_of_solutions_found(
481 current_mip_stats_.number_of_solutions_found() + 1);
491 std::vector variable_ids(model.
variables().
ids().begin(),
495 return absl::WrapUnique(
new CpSatSolver(
496 std::move(cp_sat_model),
497 std::move(variable_ids),
498 std::move(linear_constraint_ids)));
508 model_parameters, kCpSatSupportedStructures,
"CP-SAT"));
509 const absl::Time start = absl::Now();
512 callback_registration,
515 return absl::InvalidArgumentError(
516 "CallbackRegistrationProto.add_lazy_constraints=true is not supported "
529 bool used_cutoff =
false;
531 std::vector<std::string> param_warnings =
532 SetSolveParameters(parameters,
533 message_cb !=
nullptr, req);
537 param_warnings.push_back(
538 "The cutoff_limit parameter not supported for quadratic objectives "
542 if (!param_warnings.empty()) {
543 return absl::InvalidArgumentError(absl::StrJoin(param_warnings,
"; "));
549 for (
const auto [
id, val] :
551 while (variable_ids_[i] <
id) {
563 std::atomic<bool> interrupt_solve =
false;
568 interrupter, [&]() { local_interrupter.
Interrupt(); });
570 std::function<void(
const std::string&)> logging_callback;
571 if (message_cb !=
nullptr) {
572 logging_callback = [&](absl::string_view message) {
573 message_cb(absl::StrSplit(message,
'\n'));
577 const absl::flat_hash_set<CallbackEventProto> events =
580 extract_solution = [&](absl::Span<const double> cp_sat_variable_values) {
581 return ExtractSolution(cp_sat_variable_values,
584 CpSatCallbacks callbacks(cb, &local_interrupter, std::move(extract_solution),
585 events, cp_sat_model_.maximize());
591 std::move(req), &interrupt_solve, logging_callback,
592 callbacks.MakeSolutionCallback(), callbacks.MakeBestBoundCallback());
596 cp_sat_model_.maximize(),
597 used_cutoff, response));
601 [
this, &result, &var_values_filter](
602 const google::protobuf::RepeatedField<double>& variable_values,
606 *
solution.mutable_variable_values() =
607 ExtractSolution(variable_values, var_values_filter);
608 solution.set_objective_value(objective);
615 add_solution(extra_solution.variable_value(),
616 extra_solution.objective_value());
631 std::vector<int64_t> variable_ids,
632 std::vector<int64_t> linear_constraint_ids)
633 : cp_sat_model_(
std::move(cp_sat_model)),
634 variable_ids_(
std::move(variable_ids)),
635 linear_constraint_ids_(
std::move(linear_constraint_ids)) {}
637SparseDoubleVectorProto CpSatSolver::ExtractSolution(
638 const absl::Span<const double> cp_sat_variable_values,
639 const SparseVectorFilterProto& filter)
const {
642 CHECK_EQ(cp_sat_variable_values.size(), variable_ids_.size());
644 SparseVectorFilterPredicate predicate(filter);
645 SparseDoubleVectorProto result;
646 for (
int i = 0; i < variable_ids_.size(); ++i) {
647 const int64_t
id = variable_ids_[i];
648 const double value = cp_sat_variable_values[i];
649 if (predicate.AcceptsAndUpdate(
id, value)) {
651 result.add_values(value);
658 InvertedBounds inverted_bounds;
659 for (
int v = 0; v < cp_sat_model_.variable_size(); ++v) {
660 const MPVariableProto& var = cp_sat_model_.variable(v);
661 if (var.lower_bound() > var.upper_bound()) {
662 inverted_bounds.variables.push_back(variable_ids_[v]);
665 for (
int c = 0;
c < cp_sat_model_.constraint_size(); ++
c) {
666 const MPConstraintProto& cstr = cp_sat_model_.constraint(
c);
667 if (cstr.lower_bound() > cstr.upper_bound()) {
668 inverted_bounds.linear_constraints.push_back(linear_constraint_ids_[
c]);
672 return inverted_bounds;
675absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
679 return absl::UnimplementedError(
680 "CPSAT does not provide a method to compute an infeasible subsystem");
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
::operations_research::PartialVariableAssignment *PROTOBUF_NONNULL mutable_solution_hint()
static constexpr SolverType SAT_INTEGER_PROGRAMMING
::operations_research::MPModelProto *PROTOBUF_NONNULL mutable_model()
void set_solver_type(::operations_research::MPModelRequest_SolverType value)
::operations_research::MPSolverResponseStatus status() const
double variable_value(int index) const
double objective_value() const
const ::operations_research::MPSolution & additional_solutions(int index) const
void add_var_index(::int32_t value)
void add_var_value(double value)
CallbackId AddInterruptionCallback(absl_nonnull Callback callback) const
bool IsInterrupted() const
CallbackDataProto_MipStats MipStats
const ::operations_research::math_opt::SparseVectorFilterProto & mip_solution_filter() const
bool add_lazy_constraints() const
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, const SolveInterrupter *absl_nullable interrupter) override
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto ¶meters, MessageCallback message_cb, const SolveInterrupter *absl_nullable interrupter) override
::int64_t ids(int index) const
const ::operations_research::math_opt::VariablesProto & variables() const
const ::operations_research::math_opt::LinearConstraintsProto & linear_constraints() const
const ::operations_research::math_opt::SparseVectorFilterProto & variable_values_filter() const
const ::operations_research::math_opt::SolutionHintProto & solution_hints(int index) const
const ::operations_research::math_opt::SparseDoubleVectorProto & variable_values() const
::operations_research::math_opt::PrimalSolutionProto *PROTOBUF_NONNULL mutable_primal_solution()
bool has_cutoff_limit() const
double cutoff_limit() const
::operations_research::math_opt::SolutionProto *PROTOBUF_NONNULL add_solutions()
::operations_research::math_opt::TerminationProto *PROTOBUF_NONNULL mutable_termination()
::operations_research::math_opt::SolveStatsProto *PROTOBUF_NONNULL mutable_solve_stats()
::google::protobuf::Duration *PROTOBUF_NONNULL mutable_solve_time()
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >( const CallbackDataProto &)> Callback
::int64_t ids(int index) const
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)
@ TERMINATION_REASON_OTHER_ERROR
absl::Status ModelSolveParametersAreSupported(const ModelSolveParametersProto &model_parameters, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
@ SOLUTION_STATUS_FEASIBLE
@ LP_ALGORITHM_UNSPECIFIED
absl::flat_hash_set< CallbackEventProto > EventSet(const CallbackRegistrationProto &callback_registration)
TerminationProto OptimalTerminationProto(const double finite_primal_objective, const double dual_objective, const absl::string_view detail)
@ FEASIBILITY_STATUS_FEASIBLE
@ FEASIBILITY_STATUS_UNDETERMINED
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)
@ CALLBACK_EVENT_MIP_SOLUTION
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 InfeasibleTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
TerminationProto CutoffTerminationProto(bool is_maximize, const absl::string_view detail)
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
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::function< void(const double)> best_bound_callback)
std::string ProtoEnumToString(ProtoEnumType enum_value)
std::string EncodeParametersAsString(const P ¶meters)
@ MPSOLVER_UNKNOWN_STATUS
@ MPSOLVER_MODEL_INVALID_SOLVER_PARAMETERS
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)