diff --git a/ortools/linear_solver/bop_interface.cc b/ortools/linear_solver/bop_interface.cc index e96f6f66e3..57175b7f89 100644 --- a/ortools/linear_solver/bop_interface.cc +++ b/ortools/linear_solver/bop_interface.cc @@ -25,8 +25,6 @@ #include "ortools/bop/integral_solver.h" #include "ortools/linear_solver/linear_solver.h" -#if defined(USE_BOP) - namespace operations_research { namespace { @@ -396,4 +394,3 @@ MPSolverInterface* BuildBopInterface(MPSolver* const solver) { } } // namespace operations_research -#endif // #if defined(USE_BOP) diff --git a/ortools/linear_solver/csharp/linear_solver.i b/ortools/linear_solver/csharp/linear_solver.i index 5077ba03b8..3df9317ec2 100644 --- a/ortools/linear_solver/csharp/linear_solver.i +++ b/ortools/linear_solver/csharp/linear_solver.i @@ -98,6 +98,7 @@ CONVERT_VECTOR(operations_research::MPVariable, MPVariable) %unignore operations_research::MPSolver::XPRESS_LINEAR_PROGRAMMING; %unignore operations_research::MPSolver::XPRESS_MIXED_INTEGER_PROGRAMMING; %unignore operations_research::MPSolver::BOP_INTEGER_PROGRAMMING; +%unignore operations_research::MPSolver::SAT_INTEGER_PROGRAMMING; // Expose the MPSolver::ResultStatus enum. %unignore operations_research::MPSolver::ResultStatus; diff --git a/ortools/linear_solver/linear_solver.cc b/ortools/linear_solver/linear_solver.cc index a384c75590..961f77013b 100644 --- a/ortools/linear_solver/linear_solver.cc +++ b/ortools/linear_solver/linear_solver.cc @@ -331,12 +331,9 @@ extern MPSolverInterface* BuildCBCInterface(MPSolver* const solver); #if defined(USE_GLPK) extern MPSolverInterface* BuildGLPKInterface(bool mip, MPSolver* const solver); #endif -#if defined(USE_BOP) extern MPSolverInterface* BuildBopInterface(MPSolver* const solver); -#endif -#if defined(USE_GLOP) extern MPSolverInterface* BuildGLOPInterface(MPSolver* const solver); -#endif +extern MPSolverInterface* BuildSatInterface(MPSolver* const solver); #if defined(USE_SCIP) extern MPSolverInterface* BuildSCIPInterface(MPSolver* const solver); #endif @@ -358,14 +355,12 @@ namespace { MPSolverInterface* BuildSolverInterface(MPSolver* const solver) { DCHECK(solver != nullptr); switch (solver->ProblemType()) { -#if defined(USE_BOP) case MPSolver::BOP_INTEGER_PROGRAMMING: return BuildBopInterface(solver); -#endif -#if defined(USE_GLOP) + case MPSolver::SAT_INTEGER_PROGRAMMING: + return BuildSatInterface(solver); case MPSolver::GLOP_LINEAR_PROGRAMMING: return BuildGLOPInterface(solver); -#endif #if defined(USE_GLPK) case MPSolver::GLPK_LINEAR_PROGRAMMING: return BuildGLPKInterface(false, solver); @@ -450,12 +445,9 @@ bool MPSolver::SupportsProblemType(OptimizationProblemType problem_type) { if (problem_type == GLPK_LINEAR_PROGRAMMING) return true; if (problem_type == GLPK_MIXED_INTEGER_PROGRAMMING) return true; #endif -#ifdef USE_BOP if (problem_type == BOP_INTEGER_PROGRAMMING) return true; -#endif -#ifdef USE_GLOP + if (problem_type == SAT_INTEGER_PROGRAMMING) return true; if (problem_type == GLOP_LINEAR_PROGRAMMING) return true; -#endif #ifdef USE_GUROBI if (problem_type == GUROBI_LINEAR_PROGRAMMING) return true; if (problem_type == GUROBI_MIXED_INTEGER_PROGRAMMING) return true; @@ -515,9 +507,8 @@ constexpr #if defined(USE_GLPK) {MPSolver::GLPK_MIXED_INTEGER_PROGRAMMING, "glpk_mip"}, #endif -#if defined(USE_BOP) {MPSolver::BOP_INTEGER_PROGRAMMING, "bop"}, -#endif + {MPSolver::SAT_INTEGER_PROGRAMMING, "sat"}, #if defined(USE_GUROBI) {MPSolver::GUROBI_MIXED_INTEGER_PROGRAMMING, "gurobi_mip"}, #endif @@ -1619,20 +1610,16 @@ double MPSolverInterface::ComputeExactConditionNumber() const { } void MPSolverInterface::SetCommonParameters(const MPSolverParameters& param) { -// TODO(user): Overhaul the code that sets parameters to enable changing -// GLOP parameters without issuing warnings. -// By default, we let GLOP keep its own default tolerance, much more accurate -// than for the rest of the solvers. -// -#if defined(USE_GLOP) + // TODO(user): Overhaul the code that sets parameters to enable changing + // GLOP parameters without issuing warnings. + // By default, we let GLOP keep its own default tolerance, much more accurate + // than for the rest of the solvers. + // if (solver_->ProblemType() != MPSolver::GLOP_LINEAR_PROGRAMMING) { -#endif SetPrimalTolerance( param.GetDoubleParam(MPSolverParameters::PRIMAL_TOLERANCE)); SetDualTolerance(param.GetDoubleParam(MPSolverParameters::DUAL_TOLERANCE)); -#if defined(USE_GLOP) } -#endif SetPresolveMode(param.GetIntegerParam(MPSolverParameters::PRESOLVE)); // TODO(user): In the future, we could distinguish between the // algorithm to solve the root LP and the algorithm to solve node @@ -1644,14 +1631,10 @@ void MPSolverInterface::SetCommonParameters(const MPSolverParameters& param) { } void MPSolverInterface::SetMIPParameters(const MPSolverParameters& param) { -#if defined(USE_GLOP) if (solver_->ProblemType() != MPSolver::GLOP_LINEAR_PROGRAMMING) { -#endif SetRelativeMipGap( param.GetDoubleParam(MPSolverParameters::RELATIVE_MIP_GAP)); -#if defined(USE_GLOP) } -#endif } void MPSolverInterface::SetUnsupportedDoubleParam( diff --git a/ortools/linear_solver/linear_solver.h b/ortools/linear_solver/linear_solver.h index a5d41c747a..bbec5b1b18 100644 --- a/ortools/linear_solver/linear_solver.h +++ b/ortools/linear_solver/linear_solver.h @@ -187,10 +187,8 @@ class MPSolver { /// Linear Programming solver using GLPK. GLPK_LINEAR_PROGRAMMING = 1, #endif -#ifdef USE_GLOP /// Linear Programming solver using GLOP (Recommended solver). GLOP_LINEAR_PROGRAMMING = 2, -#endif #ifdef USE_GUROBI /// Linear Programming solver using GUROBI. GUROBI_LINEAR_PROGRAMMING = 6, @@ -221,10 +219,12 @@ class MPSolver { /// Mixed integer Programming Solver using CPLEX. CPLEX_MIXED_INTEGER_PROGRAMMING = 11, #endif -#if defined(USE_BOP) /// Linear Boolean Programming Solver. BOP_INTEGER_PROGRAMMING = 12, -#endif + /// SAT based solver (requires only integer and Boolean variables). + /// If you pass it mixed integer problems, it will scale coefficients to + /// integer values, and solver continuous variables as integral variables. + SAT_INTEGER_PROGRAMMING = 14, #if defined(USE_XPRESS) XPRESS_LINEAR_PROGRAMMING = 101, XPRESS_MIXED_INTEGER_PROGRAMMING = 102, diff --git a/ortools/linear_solver/linear_solver.proto b/ortools/linear_solver/linear_solver.proto index 983ea03a29..7643a26251 100644 --- a/ortools/linear_solver/linear_solver.proto +++ b/ortools/linear_solver/linear_solver.proto @@ -311,19 +311,20 @@ message OptionalDouble { // Some values won't be supported by some solvers. The behavior in that case is // not defined yet. message MPSolverCommonParameters { - // Limit for relative MIP gap. - // If the relative MIP gap reaches this value or below, stop the solve before - // the time limit. + // The solver stops if the relative MIP gap reaches this value or below. // The relative MIP gap is an upper bound of the relative distance to the - // optimal: - // for a maximization problem, it is defined as either - // (best_bound - objective) / abs(objective) (Gurobi) - // abs((best_bound - objective) / best_bound) (SCIP) - // where "objective" is the max objective of any solution found so far, and - // "best_bound" is the tightest (i.e. lowest) upper bound of the objective - // determined so far. - // The MIP Gap is sensitive to objective offset. - // If the denominator is 0 the MIP Gap is INFINITY for SCIP and Gurobi. + // optimum, and it is defined as: + // + // abs(best_bound - incumbent) / abs(incumbent) [Gurobi] + // abs(best_bound - incumbent) / min(abs(best_bound), abs(incumbent)) [SCIP] + // + // where "incumbent" is the objective value of the best solution found so far + // (i.e., lowest when minimizing, highest when maximizing), and "best_bound" + // is the tightest bound of the objective determined so far (i.e., highest + // when minimizing, and lowest when maximizing). The MIP Gap is sensitive to + // objective offset. If the denominator is 0 the MIP Gap is INFINITY for SCIP + // and Gurobi. Of note, "incumbent" and "best bound" are called "primal bound" + // and "dual bound" in SCIP, respectively. // Ask or-core-team@ for other solvers. optional OptionalDouble relative_mip_gap = 1; diff --git a/ortools/linear_solver/python/linear_solver.i b/ortools/linear_solver/python/linear_solver.i index 16ae278c7f..a0296a8cf2 100644 --- a/ortools/linear_solver/python/linear_solver.i +++ b/ortools/linear_solver/python/linear_solver.i @@ -28,11 +28,11 @@ // // TODO(user): test all the APIs that are currently marked as 'untested'. +%include "ortools/base/base.i" + %include "std_string.i" %include "stdint.i" -%include "ortools/base/base.i" - %include "ortools/util/python/proto.i" %import "ortools/util/python/vector.i" diff --git a/ortools/linear_solver/samples/integer_programming_example.py b/ortools/linear_solver/samples/integer_programming_example.py index 18354bf20f..bc8d4f68c8 100644 --- a/ortools/linear_solver/samples/integer_programming_example.py +++ b/ortools/linear_solver/samples/integer_programming_example.py @@ -15,6 +15,7 @@ from __future__ import print_function # [START import] from ortools.linear_solver import pywraplp + # [END import] diff --git a/ortools/linear_solver/samples/simple_lp_program.py b/ortools/linear_solver/samples/simple_lp_program.py index 0f4b4561f1..68912fe76d 100644 --- a/ortools/linear_solver/samples/simple_lp_program.py +++ b/ortools/linear_solver/samples/simple_lp_program.py @@ -15,6 +15,7 @@ # [START import] from __future__ import print_function from ortools.linear_solver import pywraplp + # [END import] diff --git a/ortools/linear_solver/samples/simple_mip_program.py b/ortools/linear_solver/samples/simple_mip_program.py index 167b5584db..d6598ace74 100644 --- a/ortools/linear_solver/samples/simple_mip_program.py +++ b/ortools/linear_solver/samples/simple_mip_program.py @@ -15,6 +15,7 @@ # [START import] from __future__ import print_function from ortools.linear_solver import pywraplp + # [END import] diff --git a/ortools/linear_solver/sat_interface.cc b/ortools/linear_solver/sat_interface.cc new file mode 100644 index 0000000000..851facfbba --- /dev/null +++ b/ortools/linear_solver/sat_interface.cc @@ -0,0 +1,316 @@ +// Copyright 2010-2018 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "ortools/base/hash.h" +#include "ortools/base/integral_types.h" +#include "ortools/base/logging.h" +#include "ortools/linear_solver/linear_solver.h" +#include "ortools/linear_solver/linear_solver.pb.h" +#include "ortools/linear_solver/sat_proto_solver.h" +#include "ortools/port/proto_utils.h" +#include "ortools/sat/cp_model.pb.h" +#include "ortools/sat/cp_model_solver.h" +#include "ortools/sat/lp_utils.h" +#include "ortools/sat/model.h" +#include "ortools/util/time_limit.h" + +namespace operations_research { + +#if defined(PROTOBUF_INTERNAL_IMPL) +using google::protobuf::Message; +#else +using google::protobuf::Message; +#endif + +class SatInterface : public MPSolverInterface { + public: + explicit SatInterface(MPSolver* const solver); + ~SatInterface() override; + + // ----- Solve ----- + MPSolver::ResultStatus Solve(const MPSolverParameters& param) override; + bool InterruptSolve() override; + + // ----- Model modifications and extraction ----- + void Reset() override; + void SetOptimizationDirection(bool maximize) override; + void SetVariableBounds(int index, double lb, double ub) override; + void SetVariableInteger(int index, bool integer) override; + void SetConstraintBounds(int index, double lb, double ub) override; + void AddRowConstraint(MPConstraint* const ct) override; + void AddVariable(MPVariable* const var) override; + void SetCoefficient(MPConstraint* const constraint, + const MPVariable* const variable, double new_value, + double old_value) override; + void ClearConstraint(MPConstraint* const constraint) override; + void SetObjectiveCoefficient(const MPVariable* const variable, + double coefficient) override; + void SetObjectiveOffset(double value) override; + void ClearObjective() override; + + bool AddIndicatorConstraint(MPConstraint* const ct) override { return true; } + + // ------ Query statistics on the solution and the solve ------ + int64 iterations() const override; + int64 nodes() const override; + double best_objective_bound() const override; + MPSolver::BasisStatus row_status(int constraint_index) const override; + MPSolver::BasisStatus column_status(int variable_index) const override; + + // ----- Misc ----- + bool IsContinuous() const override; + bool IsLP() const override; + bool IsMIP() const override; + + std::string SolverVersion() const override; + void* underlying_solver() override; + + void ExtractNewVariables() override; + void ExtractNewConstraints() override; + void ExtractObjective() override; + + void SetParameters(const MPSolverParameters& param) override; + void SetRelativeMipGap(double value) override; + void SetPrimalTolerance(double value) override; + void SetDualTolerance(double value) override; + void SetPresolveMode(int value) override; + void SetScalingMode(int value) override; + void SetLpAlgorithm(int value) override; + bool SetSolverSpecificParametersAsString( + const std::string& parameters) override; + util::Status SetNumThreads(int num_threads) override; + + private: + void NonIncrementalChange(); + + std::atomic interrupt_solve_; + sat::SatParameters parameters_; + int num_threads_ = 8; + double best_objective_bound_ = 0.0; +}; + +SatInterface::SatInterface(MPSolver* const solver) + : MPSolverInterface(solver), interrupt_solve_(false) {} + +SatInterface::~SatInterface() {} + +MPSolver::ResultStatus SatInterface::Solve(const MPSolverParameters& param) { + interrupt_solve_ = false; + + // Reset extraction as this interface is not incremental yet. + Reset(); + ExtractModel(); + + SetParameters(param); + solver_->SetSolverSpecificParametersAsString( + solver_->solver_specific_parameter_string_); + + // Time limit. + if (solver_->time_limit()) { + VLOG(1) << "Setting time limit = " << solver_->time_limit() << " ms."; + parameters_.set_max_time_in_seconds( + static_cast(solver_->time_limit()) / 1000.0); + } + + // Mark variables and constraints as extracted. + for (int i = 0; i < solver_->variables_.size(); ++i) { + set_variable_as_extracted(i, true); + } + for (int i = 0; i < solver_->constraints_.size(); ++i) { + set_constraint_as_extracted(i, true); + } + + MPModelRequest request; + solver_->ExportModelToProto(request.mutable_model()); + // If sat::SatParameters is compiled with proto-lite (go/mobile-cpp-protos), + // then serialize as non-human readable std::string. This is because + // proto-lite does not support reflection mechanism, which is a prerequisite + // for method like `ShortDebugString`. + if (!std::is_base_of::value) { + request.set_solver_specific_parameters(parameters_.SerializeAsString()); + } else { + request.set_solver_specific_parameters(parameters_.ShortDebugString()); + } + request.set_enable_internal_solver_output(!quiet_); + const util::StatusOr status_or = + SatSolveProto(std::move(request), &interrupt_solve_); + + if (!status_or.ok()) return MPSolver::ABNORMAL; + const MPSolutionResponse response = status_or.ValueOrDie(); + + // The solution must be marked as synchronized even when no solution exists. + sync_status_ = SOLUTION_SYNCHRONIZED; + switch (response.status()) { + case MPSOLVER_OPTIMAL: + result_status_ = MPSolver::OPTIMAL; + break; + case MPSOLVER_FEASIBLE: + result_status_ = MPSolver::FEASIBLE; + break; + case MPSOLVER_INFEASIBLE: + result_status_ = MPSolver::INFEASIBLE; + break; + case MPSOLVER_MODEL_INVALID: + result_status_ = MPSolver::MODEL_INVALID; + break; + default: + result_status_ = MPSolver::NOT_SOLVED; + break; + } + + // TODO(user): Just use LoadSolutionFromProto(), but fix that function first + // to load everything and not just the solution. + if (response.status() == MPSOLVER_FEASIBLE || + response.status() == MPSOLVER_OPTIMAL) { + objective_value_ = response.objective_value(); + best_objective_bound_ = response.best_objective_bound(); + const size_t num_vars = solver_->variables_.size(); + for (int var_id = 0; var_id < num_vars; ++var_id) { + MPVariable* const var = solver_->variables_[var_id]; + var->set_solution_value(response.variable_value(var_id)); + } + } + + return result_status_; +} + +bool SatInterface::InterruptSolve() { + interrupt_solve_ = true; + return true; +} + +void SatInterface::Reset() { ResetExtractionInformation(); } + +void SatInterface::SetOptimizationDirection(bool maximize) { + NonIncrementalChange(); +} + +void SatInterface::SetVariableBounds(int index, double lb, double ub) { + NonIncrementalChange(); +} + +void SatInterface::SetVariableInteger(int index, bool integer) { + NonIncrementalChange(); +} + +void SatInterface::SetConstraintBounds(int index, double lb, double ub) { + NonIncrementalChange(); +} + +void SatInterface::AddRowConstraint(MPConstraint* const ct) { + NonIncrementalChange(); +} + +void SatInterface::AddVariable(MPVariable* const var) { + NonIncrementalChange(); +} + +void SatInterface::SetCoefficient(MPConstraint* const constraint, + const MPVariable* const variable, + double new_value, double old_value) { + NonIncrementalChange(); +} + +void SatInterface::ClearConstraint(MPConstraint* const constraint) { + NonIncrementalChange(); +} + +void SatInterface::SetObjectiveCoefficient(const MPVariable* const variable, + double coefficient) { + NonIncrementalChange(); +} + +void SatInterface::SetObjectiveOffset(double value) { NonIncrementalChange(); } + +void SatInterface::ClearObjective() { NonIncrementalChange(); } + +int64 SatInterface::iterations() const { + return 0; // FIXME +} + +int64 SatInterface::nodes() const { return 0; } + +double SatInterface::best_objective_bound() const { + if (!CheckSolutionIsSynchronized() || !CheckBestObjectiveBoundExists()) { + return trivial_worst_objective_bound(); + } + return best_objective_bound_; +} + +MPSolver::BasisStatus SatInterface::row_status(int constraint_index) const { + return MPSolver::BasisStatus::FREE; // FIXME +} + +MPSolver::BasisStatus SatInterface::column_status(int variable_index) const { + return MPSolver::BasisStatus::FREE; // FIXME +} + +bool SatInterface::IsContinuous() const { return false; } +bool SatInterface::IsLP() const { return false; } +bool SatInterface::IsMIP() const { return true; } + +std::string SatInterface::SolverVersion() const { + return "SAT Based MIP Solver"; +} + +void* SatInterface::underlying_solver() { return nullptr; } + +void SatInterface::ExtractNewVariables() { NonIncrementalChange(); } + +void SatInterface::ExtractNewConstraints() { NonIncrementalChange(); } + +void SatInterface::ExtractObjective() { NonIncrementalChange(); } + +void SatInterface::SetParameters(const MPSolverParameters& param) { + // By default, we use 8 threads as it allows to try a good set of orthogonal + // parameters. This can be overridden by the user. + parameters_.set_num_search_workers(num_threads_); + parameters_.set_log_search_progress(!quiet_); + SetCommonParameters(param); +} + +util::Status SatInterface::SetNumThreads(int num_threads) { + num_threads_ = num_threads; + return util::OkStatus(); +} + +// All these have no effect. +void SatInterface::SetPrimalTolerance(double value) {} +void SatInterface::SetDualTolerance(double value) {} +void SatInterface::SetScalingMode(int value) {} +void SatInterface::SetLpAlgorithm(int value) {} +void SatInterface::SetRelativeMipGap(double value) {} + +// TODO(user): Implement me. +void SatInterface::SetPresolveMode(int value) {} + +bool SatInterface::SetSolverSpecificParametersAsString( + const std::string& parameters) { + return ProtobufTextFormatMergeFromString(parameters, ¶meters_); +} + +void SatInterface::NonIncrementalChange() { + // The current implementation is not incremental. + sync_status_ = MUST_RELOAD; +} + +// Register Sat in the global linear solver factory. +MPSolverInterface* BuildSatInterface(MPSolver* const solver) { + return new SatInterface(solver); +} + +} // namespace operations_research diff --git a/ortools/linear_solver/sat_proto_solver.cc b/ortools/linear_solver/sat_proto_solver.cc new file mode 100644 index 0000000000..2576c2406d --- /dev/null +++ b/ortools/linear_solver/sat_proto_solver.cc @@ -0,0 +1,174 @@ +// Copyright 2010-2018 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ortools/linear_solver/sat_proto_solver.h" + +#include + +#include "ortools/linear_solver/linear_solver.pb.h" +#include "ortools/linear_solver/model_validator.h" +#include "ortools/linear_solver/sat_solver_utils.h" +#include "ortools/port/proto_utils.h" +#include "ortools/sat/cp_model.pb.h" +#include "ortools/sat/cp_model_solver.h" +#include "ortools/sat/lp_utils.h" +#include "ortools/sat/sat_parameters.pb.h" +#include "ortools/util/time_limit.h" + +namespace operations_research { + +namespace { + +#if defined(PROTOBUF_INTERNAL_IMPL) +using google::protobuf::Message; +#else +using google::protobuf::Message; +#endif + +MPSolverResponseStatus ToMPSolverResponseStatus(sat::CpSolverStatus status, + bool has_objective) { + switch (status) { + case sat::CpSolverStatus::UNKNOWN: + return MPSOLVER_NOT_SOLVED; + case sat::CpSolverStatus::MODEL_INVALID: + return MPSOLVER_MODEL_INVALID; + case sat::CpSolverStatus::FEASIBLE: + return has_objective ? MPSOLVER_FEASIBLE : MPSOLVER_OPTIMAL; + case sat::CpSolverStatus::INFEASIBLE: + return MPSOLVER_INFEASIBLE; + case sat::CpSolverStatus::OPTIMAL: + return MPSOLVER_OPTIMAL; + default: { + } + } + return MPSOLVER_ABNORMAL; +} +} // namespace + +util::StatusOr SatSolveProto( + MPModelRequest request, std::atomic* interrupt_solve) { + // By default, we use 8 threads as it allows to try a good set of orthogonal + // parameters. This can be overridden by the user. + sat::SatParameters params; + params.set_num_search_workers(8); + params.set_log_search_progress(request.enable_internal_solver_output()); + if (request.has_solver_specific_parameters()) { + // If code is compiled with proto-lite runtime, `solver_specific_parameters` + // should be encoded as non-human readable std::string from + // `SerializeAsString`. + if (!std::is_base_of::value) { + CHECK(params.MergeFromString(request.solver_specific_parameters())); + } else { + ProtobufTextFormatMergeFromString(request.solver_specific_parameters(), + ¶ms); + } + } + if (request.has_solver_time_limit_seconds()) { + params.set_max_time_in_seconds( + static_cast(request.solver_time_limit_seconds()) / 1000.0); + } + + MPSolutionResponse response; + if (!ExtractValidMPModelInPlaceOrPopulateResponseStatus(&request, + &response)) { + if (params.log_search_progress()) { + // This is needed for our benchmark scripts. + sat::CpSolverResponse cp_response; + cp_response.set_status(sat::CpSolverStatus::MODEL_INVALID); + LOG(INFO) << CpSolverResponseStats(cp_response); + } + return response; + } + + MPModelProto* const mp_model = request.mutable_model(); + auto shift_bounds_preprocessor = ApplyMipPresolveSteps(mp_model); + const std::vector var_scaling = + sat::ScaleContinuousVariables(params.mip_var_scaling(), mp_model); + + sat::CpModelProto cp_model; + if (!ConvertMPModelProtoToCpModelProto(params, *mp_model, &cp_model)) { + if (params.log_search_progress()) { + // This is needed for our benchmark scripts. + sat::CpSolverResponse cp_response; + cp_response.set_status(sat::CpSolverStatus::MODEL_INVALID); + LOG(INFO) << CpSolverResponseStats(cp_response); + } + response.set_status(MPSOLVER_MODEL_INVALID); + response.set_status_str("Failed to convert model into CP-SAT model"); + return response; + } + DCHECK_EQ(cp_model.variables().size(), var_scaling.size()); + DCHECK_EQ(cp_model.variables().size(), mp_model->variable().size()); + + // Copy and scale the hint if there is one. Note that we need to shift it + // accordingly if we shifted the domains of the variables. + if (request.model().has_solution_hint()) { + auto* cp_model_hint = cp_model.mutable_solution_hint(); + const int size = request.model().solution_hint().var_index().size(); + glop::DenseRow offsets(glop::ColIndex(size), 0.0); + if (shift_bounds_preprocessor != nullptr) { + offsets = shift_bounds_preprocessor->offsets(); + } + for (int i = 0; i < size; ++i) { + const int var = request.model().solution_hint().var_index(i); + if (var >= var_scaling.size()) continue; + cp_model_hint->add_vars(var); + cp_model_hint->add_values(static_cast( + std::round((request.model().solution_hint().var_value(i) - + offsets[glop::ColIndex(i)]) * + var_scaling[var]))); + } + } + + // We no longer need the request. Reclaim its memory. + const int num_vars = mp_model->variable_size(); + request.Clear(); + + // Solve. + sat::Model sat_model; + sat_model.Add(NewSatParameters(params)); + if (interrupt_solve != nullptr) { + sat_model.GetOrCreate()->RegisterExternalBooleanAsLimit( + interrupt_solve); + } + const sat::CpSolverResponse cp_response = + sat::SolveCpModel(cp_model, &sat_model); + + // Convert the response. + // + // TODO(user): Implement the row and column status. + response.set_status( + ToMPSolverResponseStatus(cp_response.status(), cp_model.has_objective())); + if (response.status() == MPSOLVER_FEASIBLE || + response.status() == MPSOLVER_OPTIMAL) { + response.set_objective_value(cp_response.objective_value()); + response.set_best_objective_bound(cp_response.best_objective_bound()); + + // Postsolve the bound shift and scaling. + glop::ProblemSolution solution(glop::RowIndex(0), glop::ColIndex(num_vars)); + for (int v = 0; v < num_vars; ++v) { + solution.primal_values[glop::ColIndex(v)] = + static_cast(cp_response.solution(v)) / var_scaling[v]; + } + if (shift_bounds_preprocessor) { + shift_bounds_preprocessor->RecoverSolution(&solution); + } + + for (int v = 0; v < num_vars; ++v) { + response.add_variable_value(solution.primal_values[glop::ColIndex(v)]); + } + } + + return response; +} +} // namespace operations_research diff --git a/ortools/linear_solver/sat_solver_utils.cc b/ortools/linear_solver/sat_solver_utils.cc new file mode 100644 index 0000000000..7f509040b2 --- /dev/null +++ b/ortools/linear_solver/sat_solver_utils.cc @@ -0,0 +1,28 @@ +// Copyright 2010-2018 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ortools/linear_solver/sat_solver_utils.h" + +#include "absl/memory/memory.h" +#include "ortools/glop/parameters.pb.h" +#include "ortools/glop/preprocessor.h" +#include "ortools/lp_data/proto_utils.h" + +namespace operations_research { + +std::unique_ptr ApplyMipPresolveSteps( + MPModelProto* model) { + return nullptr; +} + +} // namespace operations_research diff --git a/ortools/linear_solver/sat_solver_utils.h b/ortools/linear_solver/sat_solver_utils.h new file mode 100644 index 0000000000..1044d120cd --- /dev/null +++ b/ortools/linear_solver/sat_solver_utils.h @@ -0,0 +1,32 @@ +// Copyright 2010-2018 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef OR_TOOLS_LINEAR_SOLVER_SAT_SOLVER_UTILS_H_ +#define OR_TOOLS_LINEAR_SOLVER_SAT_SOLVER_UTILS_H_ + +#include + +#include "ortools/glop/preprocessor.h" +#include "ortools/linear_solver/linear_solver.pb.h" + +namespace operations_research { + +// Apply presolve steps to improve the MIP -> IP imperfect conversion. The +// stricter the domain of the variable, the more room we have for scaling the +// constraint to integers and prevent overflow. +// If the bounds were shifted, returns the shifting preprocessor. +std::unique_ptr ApplyMipPresolveSteps( + MPModelProto* model); + +} // namespace operations_research +#endif // OR_TOOLS_LINEAR_SOLVER_SAT_SOLVER_UTILS_H_ diff --git a/ortools/linear_solver/scip_helper_macros.h b/ortools/linear_solver/scip_helper_macros.h index 00fadb2dfd..9557a0cefa 100644 --- a/ortools/linear_solver/scip_helper_macros.h +++ b/ortools/linear_solver/scip_helper_macros.h @@ -17,6 +17,7 @@ #include "absl/strings/str_format.h" #include "ortools/base/canonical_errors.h" #include "ortools/base/status.h" +#include "ortools/base/status_macros.h" namespace operations_research { namespace internal { diff --git a/ortools/linear_solver/scip_proto_solver.cc b/ortools/linear_solver/scip_proto_solver.cc index 952e935577..01b9596f8d 100644 --- a/ortools/linear_solver/scip_proto_solver.cc +++ b/ortools/linear_solver/scip_proto_solver.cc @@ -138,14 +138,15 @@ util::Status ScipSetSolverSpecificParameters(const std::string& parameters, namespace { // This function will create a new constraint if the indicator constraint has // both a lower bound and an upper bound. -util::Status AddIndicatorConstraint( - const MPGeneralConstraintProto& gen_cst, - const std::vector& scip_variables, SCIP* scip, - SCIP_CONS** scip_cst, std::vector* scip_constraints, - std::vector* tmp_variables, - std::vector* tmp_coefficients) { +util::Status AddIndicatorConstraint(const MPGeneralConstraintProto& gen_cst, + SCIP* scip, SCIP_CONS** scip_cst, + std::vector* scip_variables, + std::vector* scip_constraints, + std::vector* tmp_variables, + std::vector* tmp_coefficients) { CHECK(scip != nullptr); CHECK(scip_cst != nullptr); + CHECK(scip_variables != nullptr); CHECK(scip_constraints != nullptr); CHECK(tmp_variables != nullptr); CHECK(tmp_coefficients != nullptr); @@ -160,14 +161,14 @@ util::Status AddIndicatorConstraint( tmp_variables->resize(size, nullptr); tmp_coefficients->resize(size, 0); for (int i = 0; i < size; ++i) { - (*tmp_variables)[i] = scip_variables[constraint.var_index(i)]; + (*tmp_variables)[i] = (*scip_variables)[constraint.var_index(i)]; (*tmp_coefficients)[i] = constraint.coefficient(i); } - SCIP_VAR* ind_var = scip_variables[ind.var_index()]; + SCIP_VAR* ind_var = (*scip_variables)[ind.var_index()]; if (ind.var_value() == 0) { RETURN_IF_SCIP_ERROR( - SCIPgetNegatedVar(scip, scip_variables[ind.var_index()], &ind_var)); + SCIPgetNegatedVar(scip, (*scip_variables)[ind.var_index()], &ind_var)); } if (ind.constraint().upper_bound() < kInfinity) { @@ -618,7 +619,7 @@ bool MPModelIsInvalidForScip(const MPModelProto& model, SCIP* scip, if (variable.upper_bound() <= -infinity) { response->set_status(MPSOLVER_MODEL_INVALID); response->set_status_str(absl::StrFormat( - "Variable %i's lower bound is considered -infinity", v)); + "Variable %i's upper bound is considered -infinity", v)); return true; } const double coeff = variable.objective_coefficient(); @@ -772,8 +773,9 @@ util::StatusOr ScipSolveProto( switch (gen_cst.general_constraint_case()) { case MPGeneralConstraintProto::kIndicatorConstraint: { RETURN_IF_ERROR(AddIndicatorConstraint( - gen_cst, scip_variables, scip, &scip_constraints[lincst_size + c], - &scip_constraints, &ct_variables, &ct_coefficients)); + gen_cst, scip, &scip_constraints[lincst_size + c], + &scip_variables, &scip_constraints, &ct_variables, + &ct_coefficients)); break; } case MPGeneralConstraintProto::kSosConstraint: { @@ -880,6 +882,8 @@ util::StatusOr ScipSolveProto( break; } + VLOG(1) << "ScipSolveProto() status=" + << MPSolverResponseStatus_Name(response.status()) << "."; return response; }