23 #include <type_traits>
27 #include "absl/status/statusor.h"
28 #include "absl/types/span.h"
29 #include "ortools/linear_solver/linear_solver.pb.h"
33 #include "ortools/sat/cp_model.pb.h"
37 #include "ortools/sat/sat_parameters.pb.h"
45 #if defined(PROTOBUF_INTERNAL_IMPL)
46 using google::protobuf::Message;
48 using google::protobuf::Message;
55 constexpr
bool kProtoLiteSatParameters =
58 MPSolverResponseStatus ToMPSolverResponseStatus(sat::CpSolverStatus
status,
61 case sat::CpSolverStatus::UNKNOWN:
62 return MPSOLVER_NOT_SOLVED;
64 return MPSOLVER_MODEL_INVALID;
66 return MPSOLVER_FEASIBLE;
68 return MPSOLVER_INFEASIBLE;
70 return MPSOLVER_OPTIMAL;
74 return MPSOLVER_ABNORMAL;
77 sat::CpSolverStatus FromMPSolverResponseStatus(MPSolverResponseStatus
status) {
79 case MPSolverResponseStatus::MPSOLVER_OPTIMAL:
81 case MPSolverResponseStatus::MPSOLVER_INFEASIBLE:
83 case MPSolverResponseStatus::MPSOLVER_MODEL_INVALID:
91 MPSolutionResponse InfeasibleResponse(SolverLogger& logger,
93 SOLVER_LOG(&logger,
"Infeasible model detected in sat_solve_proto.\n",
97 if (logger.LoggingIsEnabled()) {
98 sat::CpSolverResponse cp_response;
104 response.set_status(MPSolverResponseStatus::MPSOLVER_INFEASIBLE);
109 MPSolutionResponse ModelInvalidResponse(SolverLogger& logger,
111 SOLVER_LOG(&logger,
"Invalid model/parameters in sat_solve_proto.\n",
115 if (logger.LoggingIsEnabled()) {
116 sat::CpSolverResponse cp_response;
122 response.set_status(MPSolverResponseStatus::MPSOLVER_MODEL_INVALID);
130 MPModelRequest request, std::atomic<bool>* interrupt_solve,
131 std::function<
void(
const std::string&)> logging_callback,
132 std::function<
void(
const MPSolution&)> solution_callback) {
133 sat::SatParameters params;
134 params.set_log_search_progress(request.enable_internal_solver_output());
136 if (request.has_solver_specific_parameters()) {
138 if (kProtoLiteSatParameters) {
139 if (!params.MergeFromString(request.solver_specific_parameters())) {
140 return absl::InvalidArgumentError(
141 "solver_specific_parameters is not a valid binary stream of the "
142 "SatParameters proto");
146 request.solver_specific_parameters(), ¶ms)) {
147 return absl::InvalidArgumentError(
148 "solver_specific_parameters is not a valid textual representation "
149 "of the SatParameters proto");
153 if (request.has_solver_time_limit_seconds()) {
154 params.set_max_time_in_seconds(request.solver_time_limit_seconds());
164 if (logging_callback !=
nullptr) {
181 sat::CpSolverResponse cp_response;
182 cp_response.set_status(FromMPSolverResponseStatus(
response.status()));
190 MPModelProto*
const mp_model = request.mutable_model();
193 return ModelInvalidResponse(logger,
"Extra CP-SAT validation failed.");
198 if (!error.empty()) {
199 return ModelInvalidResponse(
200 logger, absl::StrCat(
"Invalid CP-SAT parameters: ", error));
206 return InfeasibleResponse(logger,
207 "An integer variable has an empty domain");
216 const glop::GlopParameters glop_params;
217 std::vector<std::unique_ptr<glop::Preprocessor>> for_postsolve;
218 if (!params.enumerate_all_solutions()) {
226 return InfeasibleResponse(
227 logger,
"Problem proven infeasible during MIP presolve");
229 return ModelInvalidResponse(
230 logger,
"Problem detected invalid during MIP presolve");
234 if (params.log_search_progress()) {
236 sat::CpSolverResponse cp_response;
237 cp_response.set_status(sat::CpSolverStatus::UNKNOWN);
240 response.set_status(MPSolverResponseStatus::MPSOLVER_UNKNOWN_STATUS);
243 "Problem proven infeasible or unbounded during MIP presolve");
253 SOLVER_LOG(&logger,
"Scaling to pure integer problem.");
255 const int num_variables = mp_model->variable_size();
256 std::vector<double> var_scaling(num_variables, 1.0);
257 if (params.mip_automatically_scale_variables()) {
260 return InfeasibleResponse(
261 logger,
"A detected integer variable has an empty domain");
264 if (params.mip_var_scaling() != 1.0) {
265 const double max_bound = params.mip_scale_large_domain()
266 ? std::numeric_limits<double>::infinity()
267 : params.mip_max_bound();
269 params.mip_var_scaling(), max_bound, mp_model);
270 for (
int i = 0; i < var_scaling.size(); ++i) {
271 var_scaling[i] *= other_scaling[i];
276 if (params.only_solve_ip()) {
277 bool all_integer =
true;
278 for (
const MPVariableProto&
var : mp_model->variable()) {
279 if (!
var.is_integer()) {
285 return ModelInvalidResponse(
287 "The model contains non-integer variables but the parameter "
288 "'only_solve_ip' was set. Change this parameter if you "
289 "still want to solve a more constrained version of the original MIP "
290 "where non-integer variables can only take a finite set of values.");
294 sat::CpModelProto cp_model;
297 return ModelInvalidResponse(logger,
298 "Failed to convert model into CP-SAT model");
300 DCHECK_EQ(cp_model.variables().size(), var_scaling.size());
301 DCHECK_EQ(cp_model.variables().size(), mp_model->variable().size());
304 if (request.model().has_solution_hint()) {
305 auto* cp_model_hint = cp_model.mutable_solution_hint();
306 const int size = request.model().solution_hint().var_index().size();
307 for (
int i = 0; i < size; ++i) {
308 const int var = request.model().solution_hint().var_index(i);
309 if (
var >= var_scaling.size())
continue;
315 request.model().solution_hint().var_value(i) * var_scaling[
var];
316 if (std::abs(
value) > params.mip_max_bound()) {
317 value =
value > 0 ? params.mip_max_bound() : -params.mip_max_bound();
320 cp_model_hint->add_vars(
var);
321 cp_model_hint->add_values(
static_cast<int64_t
>(std::round(
value)));
326 const int old_num_variables = mp_model->variable().size();
327 const int old_num_constraints = mp_model->constraint().size();
334 if (interrupt_solve !=
nullptr) {
339 auto post_solve = [&](
const sat::CpSolverResponse& cp_response) {
340 MPSolution mp_solution;
341 mp_solution.set_objective_value(cp_response.objective_value());
344 (glop::ColIndex(old_num_variables)));
347 static_cast<double>(cp_response.solution(v)) / var_scaling[v];
349 for (
int i = for_postsolve.size(); --i >= 0;) {
350 for_postsolve[i]->RecoverSolution(&glop_solution);
353 mp_solution.add_variable_value(
359 if (solution_callback !=
nullptr) {
361 [&](
const sat::CpSolverResponse& cp_response) {
362 solution_callback(post_solve(cp_response));
367 const sat::CpSolverResponse cp_response =
373 response.mutable_solve_info()->set_solve_wall_time_seconds(
374 cp_response.wall_time());
375 response.mutable_solve_info()->set_solve_user_time_seconds(
376 cp_response.user_time());
378 ToMPSolverResponseStatus(cp_response.status(), cp_model.has_objective()));
379 if (
response.status() == MPSOLVER_FEASIBLE ||
380 response.status() == MPSOLVER_OPTIMAL) {
381 response.set_objective_value(cp_response.objective_value());
382 response.set_best_objective_bound(cp_response.best_objective_bound());
383 MPSolution post_solved_solution = post_solve(cp_response);
384 *
response.mutable_variable_value() =
385 std::move(*post_solved_solution.mutable_variable_value());
391 for (
const sat::CpSolverSolution& additional_solution :
392 cp_response.additional_solutions()) {
393 if (absl::MakeConstSpan(additional_solution.values()) ==
394 absl::MakeConstSpan(cp_response.solution())) {
397 double obj = cp_model.floating_point_objective().offset();
398 for (
int i = 0; i < cp_model.floating_point_objective().vars_size(); ++i) {
399 const int32_t
var = cp_model.floating_point_objective().vars(i);
400 const double obj_coef = cp_model.floating_point_objective().coeffs(i);
401 obj += additional_solution.values(
var) * obj_coef;
404 if (cp_model.objective().scaling_factor() != 0.0) {
405 obj *= cp_model.objective().scaling_factor();
407 sat::CpSolverResponse temp;
408 *temp.mutable_solution() = additional_solution.values();
409 temp.set_objective_value(obj);
410 *
response.add_additional_solutions() = post_solve(temp);
412 const bool is_maximize = request.model().maximize();
413 std::sort(
response.mutable_additional_solutions()->pointer_begin(),
414 response.mutable_additional_solutions()->pointer_end(),
415 [is_maximize](
const MPSolution* left,
const MPSolution* right) {
417 return left->objective_value() > right->objective_value();
419 return left->objective_value() < right->objective_value();
426 if (kProtoLiteSatParameters) {
void SetLogToStdOut(bool enable)
bool LoggingIsEnabled() const
void AddInfoLoggingCallback(std::function< void(const std::string &message)> callback)
void EnableLogging(bool enable)
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Class that owns everything related to a particular optimization model.
T Add(std::function< T(Model *)> f)
This makes it possible to have a nicer API on the client side, and it allows both of these forms:
void Register(T *non_owned_class)
Register a non-owned class that will be "singleton" in the model.
T * GetOrCreate()
Returns an object of type T that is unique to this model (like a "local" singleton).
SharedResponseManager * response
@ INFEASIBLE_OR_UNBOUNDED
std::function< void(Model *)> NewFeasibleSolutionObserver(const std::function< void(const CpSolverResponse &response)> &observer)
Creates a solution observer with the model with model.Add(NewFeasibleSolutionObserver([](response){....
std::function< SatParameters(Model *)> NewSatParameters(const std::string ¶ms)
Creates parameters for the solver, which you can add to the model with.
std::string CpSolverResponseStats(const CpSolverResponse &response, bool has_objective)
Returns a string with some statistics on the solver response.
std::string ValidateParameters(const SatParameters ¶ms)
std::string CpSatSolverVersion()
Returns a string that describes the version of the solver.
void RemoveNearZeroTerms(const SatParameters ¶ms, MPModelProto *mp_model, SolverLogger *logger)
bool ConvertMPModelProtoToCpModelProto(const SatParameters ¶ms, const MPModelProto &mp_model, CpModelProto *cp_model, SolverLogger *logger)
bool MPModelProtoValidationBeforeConversion(const SatParameters ¶ms, const MPModelProto &mp_model, SolverLogger *logger)
CpSolverResponse SolveCpModel(const CpModelProto &model_proto, Model *model)
Solves the given CpModelProto.
bool MakeBoundsOfIntegerVariablesInteger(const SatParameters ¶ms, MPModelProto *mp_model, SolverLogger *logger)
std::vector< double > ScaleContinuousVariables(double scaling, double max_bound, MPModelProto *mp_model)
std::vector< double > DetectImpliedIntegers(MPModelProto *mp_model, SolverLogger *logger)
Collection of objects used to extend the Constraint Solver library.
bool ExtractValidMPModelInPlaceOrPopulateResponseStatus(MPModelRequest *request, MPSolutionResponse *response)
Like ExtractValidMPModelOrPopulateResponseStatus(), but works in-place: if the MPModel needed extract...
std::string ProtobufShortDebugString(const P &message)
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)
glop::ProblemStatus ApplyMipPresolveSteps(const glop::GlopParameters &glop_params, MPModelProto *model, std::vector< std::unique_ptr< glop::Preprocessor >> *for_postsolve, SolverLogger *logger)
std::string SatSolverVersion()
std::string EncodeSatParametersAsString(const sat::SatParameters ¶meters)
bool ProtobufTextFormatMergeFromString(absl::string_view proto_text_string, ProtoType *proto)
#define SOLVER_LOG(logger,...)