From 6e154670655df032117d2e9d15a848fd0852910c Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Fri, 12 Mar 2021 16:59:39 +0100 Subject: [PATCH] linear solver: int64 -> int64_t; use new CP-SAT logging --- ortools/linear_solver/bop_interface.cc | 9 ++- ortools/linear_solver/cbc_interface.cc | 13 ++-- ortools/linear_solver/clp_interface.cc | 9 ++- ortools/linear_solver/csharp/linear_solver.i | 2 + ortools/linear_solver/glop_interface.cc | 9 ++- ortools/linear_solver/glpk_interface.cc | 9 ++- ortools/linear_solver/gurobi_interface.cc | 21 ++--- ortools/linear_solver/java/linear_solver.i | 2 + ortools/linear_solver/linear_solver.cc | 32 ++++++-- ortools/linear_solver/linear_solver.h | 10 +++ ortools/linear_solver/linear_solver.proto | 1 + .../linear_solver/linear_solver_callback.h | 3 +- ortools/linear_solver/python/linear_solver.i | 2 + ortools/linear_solver/sat_interface.cc | 9 ++- ortools/linear_solver/sat_proto_solver.cc | 40 +++++++--- ortools/linear_solver/sat_solver_utils.cc | 10 ++- ortools/linear_solver/sat_solver_utils.h | 6 +- ortools/linear_solver/scip_callback.cc | 6 +- ortools/linear_solver/scip_callback.h | 5 +- ortools/linear_solver/scip_interface.cc | 13 ++-- ortools/sat/lp_utils.cc | 76 +++++++++---------- ortools/sat/lp_utils.h | 11 ++- 22 files changed, 187 insertions(+), 111 deletions(-) diff --git a/ortools/linear_solver/bop_interface.cc b/ortools/linear_solver/bop_interface.cc index 19604e5c6a..cc390b9289 100644 --- a/ortools/linear_solver/bop_interface.cc +++ b/ortools/linear_solver/bop_interface.cc @@ -12,6 +12,7 @@ // limitations under the License. #include +#include #include #include @@ -73,8 +74,8 @@ class BopInterface : public MPSolverInterface { void ClearObjective() override; // ------ Query statistics on the solution and the solve ------ - int64 iterations() const override; - int64 nodes() const override; + int64_t iterations() const override; + int64_t nodes() const override; MPSolver::BasisStatus row_status(int constraint_index) const override; MPSolver::BasisStatus column_status(int variable_index) const override; @@ -253,12 +254,12 @@ void BopInterface::SetObjectiveOffset(double value) { NonIncrementalChange(); } void BopInterface::ClearObjective() { NonIncrementalChange(); } -int64 BopInterface::iterations() const { +int64_t BopInterface::iterations() const { LOG(DFATAL) << "Number of iterations not available"; return kUnknownNumberOfIterations; } -int64 BopInterface::nodes() const { +int64_t BopInterface::nodes() const { LOG(DFATAL) << "Number of nodes not available"; return kUnknownNumberOfNodes; } diff --git a/ortools/linear_solver/cbc_interface.cc b/ortools/linear_solver/cbc_interface.cc index 8df43e3111..d5268284b0 100644 --- a/ortools/linear_solver/cbc_interface.cc +++ b/ortools/linear_solver/cbc_interface.cc @@ -13,6 +13,7 @@ // +#include #include #include #include @@ -105,9 +106,9 @@ class CBCInterface : public MPSolverInterface { void ClearObjective() override { sync_status_ = MUST_RELOAD; } // Number of simplex iterations - int64 iterations() const override; + int64_t iterations() const override; // Number of branch-and-bound nodes. Only available for discrete problems. - int64 nodes() const override; + int64_t nodes() const override; // Returns the basis status of a row. MPSolver::BasisStatus row_status(int constraint_index) const override { @@ -148,8 +149,8 @@ class CBCInterface : public MPSolverInterface { OsiClpSolverInterface osi_; // TODO(user): remove and query number of iterations directly from CbcModel - int64 iterations_; - int64 nodes_; + int64_t iterations_; + int64_t nodes_; // Special way to handle the relative MIP gap parameter. double relative_mip_gap_; int num_threads_ = 1; @@ -467,12 +468,12 @@ MPSolver::ResultStatus CBCInterface::Solve(const MPSolverParameters& param) { // ------ Query statistics on the solution and the solve ------ -int64 CBCInterface::iterations() const { +int64_t CBCInterface::iterations() const { if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfNodes; return iterations_; } -int64 CBCInterface::nodes() const { +int64_t CBCInterface::nodes() const { if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfIterations; return nodes_; } diff --git a/ortools/linear_solver/clp_interface.cc b/ortools/linear_solver/clp_interface.cc index ed52d2e770..5ccaf7724f 100644 --- a/ortools/linear_solver/clp_interface.cc +++ b/ortools/linear_solver/clp_interface.cc @@ -14,6 +14,7 @@ // #include +#include #include #include #include @@ -82,9 +83,9 @@ class CLPInterface : public MPSolverInterface { // ------ Query statistics on the solution and the solve ------ // Number of simplex iterations - int64 iterations() const override; + int64_t iterations() const override; // Number of branch-and-bound nodes. Only available for discrete problems. - int64 nodes() const override; + int64_t nodes() const override; // Returns the basis status of a row. MPSolver::BasisStatus row_status(int constraint_index) const override; @@ -532,12 +533,12 @@ MPSolver::BasisStatus CLPInterface::TransformCLPBasisStatus( // ------ Query statistics on the solution and the solve ------ -int64 CLPInterface::iterations() const { +int64_t CLPInterface::iterations() const { if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfIterations; return clp_->getIterationCount(); } -int64 CLPInterface::nodes() const { +int64_t CLPInterface::nodes() const { LOG(DFATAL) << "Number of nodes only available for discrete problems"; return kUnknownNumberOfNodes; } diff --git a/ortools/linear_solver/csharp/linear_solver.i b/ortools/linear_solver/csharp/linear_solver.i index 4601a0180b..4150c28c7e 100644 --- a/ortools/linear_solver/csharp/linear_solver.i +++ b/ortools/linear_solver/csharp/linear_solver.i @@ -141,9 +141,11 @@ CONVERT_VECTOR(operations_research::MPVariable, MPVariable) %unignore operations_research::MPSolver::SetSolverSpecificParametersAsString; %rename (WallTime) operations_research::MPSolver::wall_time; %unignore operations_research::MPSolver::Clear; +%rename (Constraint) operations_research::MPSolver::constraint; %unignore operations_research::MPSolver::constraints; %unignore operations_research::MPSolver::NumConstraints; %unignore operations_research::MPSolver::NumVariables; +%rename (Variable) operations_research::MPSolver::variable; %unignore operations_research::MPSolver::variables; %unignore operations_research::MPSolver::EnableOutput; %unignore operations_research::MPSolver::SuppressOutput; diff --git a/ortools/linear_solver/glop_interface.cc b/ortools/linear_solver/glop_interface.cc index ba608c2d08..5a03b3ef46 100644 --- a/ortools/linear_solver/glop_interface.cc +++ b/ortools/linear_solver/glop_interface.cc @@ -12,6 +12,7 @@ // limitations under the License. #include +#include #include #include @@ -58,8 +59,8 @@ class GLOPInterface : public MPSolverInterface { void ClearObjective() override; // ------ Query statistics on the solution and the solve ------ - int64 iterations() const override; - int64 nodes() const override; + int64_t iterations() const override; + int64_t nodes() const override; MPSolver::BasisStatus row_status(int constraint_index) const override; MPSolver::BasisStatus column_status(int variable_index) const override; @@ -235,11 +236,11 @@ void GLOPInterface::SetObjectiveOffset(double value) { NonIncrementalChange(); } void GLOPInterface::ClearObjective() { NonIncrementalChange(); } -int64 GLOPInterface::iterations() const { +int64_t GLOPInterface::iterations() const { return lp_solver_.GetNumberOfSimplexIterations(); } -int64 GLOPInterface::nodes() const { +int64_t GLOPInterface::nodes() const { LOG(DFATAL) << "Number of nodes only available for discrete problems"; return kUnknownNumberOfNodes; } diff --git a/ortools/linear_solver/glpk_interface.cc b/ortools/linear_solver/glpk_interface.cc index cf1235630d..50fe613927 100644 --- a/ortools/linear_solver/glpk_interface.cc +++ b/ortools/linear_solver/glpk_interface.cc @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -133,9 +134,9 @@ class GLPKInterface : public MPSolverInterface { // ------ Query statistics on the solution and the solve ------ // Number of simplex iterations - int64 iterations() const override; + int64_t iterations() const override; // Number of branch-and-bound nodes. Only available for discrete problems. - int64 nodes() const override; + int64_t nodes() const override; // Returns the basis status of a row. MPSolver::BasisStatus row_status(int constraint_index) const override; @@ -667,7 +668,7 @@ MPSolver::BasisStatus GLPKInterface::TransformGLPKBasisStatus( // ------ Query statistics on the solution and the solve ------ -int64 GLPKInterface::iterations() const { +int64_t GLPKInterface::iterations() const { #if GLP_MAJOR_VERSION == 4 && GLP_MINOR_VERSION < 49 if (!mip_ && CheckSolutionIsSynchronized()) { return lpx_get_int_parm(lp_, LPX_K_ITCNT); @@ -681,7 +682,7 @@ int64 GLPKInterface::iterations() const { return kUnknownNumberOfIterations; } -int64 GLPKInterface::nodes() const { +int64_t GLPKInterface::nodes() const { if (mip_) { if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfNodes; return mip_callback_info_->num_all_nodes_; diff --git a/ortools/linear_solver/gurobi_interface.cc b/ortools/linear_solver/gurobi_interface.cc index 669446756c..30ece20039 100644 --- a/ortools/linear_solver/gurobi_interface.cc +++ b/ortools/linear_solver/gurobi_interface.cc @@ -44,6 +44,7 @@ #include #include +#include #include #include #include @@ -118,9 +119,9 @@ class GurobiInterface : public MPSolverInterface { // ------ Query statistics on the solution and the solve ------ // Number of simplex or interior-point iterations - int64 iterations() const override; + int64_t iterations() const override; // Number of branch-and-bound nodes. Only available for discrete problems. - int64 nodes() const override; + int64_t nodes() const override; // Returns the basis status of a row. MPSolver::BasisStatus row_status(int constraint_index) const override; @@ -289,7 +290,7 @@ class GurobiMPCallbackContext : public MPCallbackContext { void AddLazyConstraint(const LinearRange& lazy_constraint) override; double SuggestSolution( const absl::flat_hash_map& solution) override; - int64 NumExploredNodes() override; + int64_t NumExploredNodes() override; // Call this method to update the internal state of the callback context // before passing it to MPCallback::RunCallback(). @@ -342,13 +343,13 @@ void GurobiMPCallbackContext::UpdateFromGurobiState( variable_values_extracted_ = false; } -int64 GurobiMPCallbackContext::NumExploredNodes() { +int64_t GurobiMPCallbackContext::NumExploredNodes() { switch (Event()) { case MPCallbackEvent::kMipNode: - return static_cast(GurobiCallbackGet( + return static_cast(GurobiCallbackGet( current_gurobi_internal_callback_context_, GRB_CB_MIPNODE_NODCNT)); case MPCallbackEvent::kMipSolution: - return static_cast(GurobiCallbackGet( + return static_cast(GurobiCallbackGet( current_gurobi_internal_callback_context_, GRB_CB_MIPSOL_NODCNT)); default: LOG(FATAL) << "Node count is supported only for callback events MIP_NODE " @@ -788,17 +789,17 @@ void GurobiInterface::BranchingPriorityChangedForVariable(int var_index) { // ------ Query statistics on the solution and the solve ------ -int64 GurobiInterface::iterations() const { +int64_t GurobiInterface::iterations() const { double iter; if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfIterations; CheckedGurobiCall(GRBgetdblattr(model_, GRB_DBL_ATTR_ITERCOUNT, &iter)); - return static_cast(iter); + return static_cast(iter); } -int64 GurobiInterface::nodes() const { +int64_t GurobiInterface::nodes() const { if (mip_) { if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfNodes; - return static_cast(GetDoubleAttr(GRB_DBL_ATTR_NODECOUNT)); + return static_cast(GetDoubleAttr(GRB_DBL_ATTR_NODECOUNT)); } else { LOG(DFATAL) << "Number of nodes only available for discrete problems."; return kUnknownNumberOfNodes; diff --git a/ortools/linear_solver/java/linear_solver.i b/ortools/linear_solver/java/linear_solver.i index 13f38895b7..6a5d8e9d5d 100644 --- a/ortools/linear_solver/java/linear_solver.i +++ b/ortools/linear_solver/java/linear_solver.i @@ -363,7 +363,9 @@ PROTO2_RETURN( %rename (interruptSolve) operations_research::MPSolver::InterruptSolve; // no test %rename (wallTime) operations_research::MPSolver::wall_time; %rename (clear) operations_research::MPSolver::Clear; // no test +%unignore operations_research::MPSolver::constraint; %unignore operations_research::MPSolver::constraints; +%unignore operations_research::MPSolver::variable; %unignore operations_research::MPSolver::variables; %rename (numVariables) operations_research::MPSolver::NumVariables; %rename (numConstraints) operations_research::MPSolver::NumConstraints; diff --git a/ortools/linear_solver/linear_solver.cc b/ortools/linear_solver/linear_solver.cc index 9a913d4db7..b53ba314df 100644 --- a/ortools/linear_solver/linear_solver.cc +++ b/ortools/linear_solver/linear_solver.cc @@ -21,6 +21,7 @@ #include #include +#include #include #include "absl/status/status.h" @@ -1041,10 +1042,31 @@ absl::Status MPSolver::LoadSolutionFromProto(const MPSolutionResponse& response, "'")); } } - // TODO(user): Load the reduced costs too, if available. for (int i = 0; i < response.variable_value_size(); ++i) { variables_[i]->set_solution_value(response.variable_value(i)); } + if (response.dual_value_size() > 0) { + if (response.dual_value_size() != constraints_.size()) { + return absl::InvalidArgumentError(absl::StrCat( + "Trying to load a dual solution whose number of entries (", + response.dual_value_size(), ") does not correspond to the Solver's (", + constraints_.size(), ")")); + } + for (int i = 0; i < response.dual_value_size(); ++i) { + constraints_[i]->set_dual_value(response.dual_value(i)); + } + } + if (response.reduced_cost_size() > 0) { + if (response.reduced_cost_size() != variables_.size()) { + return absl::InvalidArgumentError(absl::StrCat( + "Trying to load a reduced cost solution whose number of entries (", + response.reduced_cost_size(), + ") does not correspond to the Solver's (", variables_.size(), ")")); + } + for (int i = 0; i < response.reduced_cost_size(); ++i) { + variables_[i]->set_reduced_cost(response.reduced_cost(i)); + } + } // Set the objective value, if is known. // NOTE(user): We do not verify the objective, even though we could! if (response.has_objective_value()) { @@ -1272,8 +1294,8 @@ std::string PrettyPrintVar(const MPVariable& var) { // Special case: integer variable with at most two possible values // (and potentially none). if (var.integer() && var.ub() - var.lb() <= 1) { - const int64 lb = static_cast(ceil(var.lb())); - const int64 ub = static_cast(floor(var.ub())); + const int64_t lb = static_cast(ceil(var.lb())); + const int64_t ub = static_cast(floor(var.ub())); if (lb > ub) { return prefix + "∅"; } else if (lb == ub) { @@ -1507,9 +1529,9 @@ void MPSolver::EnableOutput() { interface_->set_quiet(false); } void MPSolver::SuppressOutput() { interface_->set_quiet(true); } -int64 MPSolver::iterations() const { return interface_->iterations(); } +int64_t MPSolver::iterations() const { return interface_->iterations(); } -int64 MPSolver::nodes() const { return interface_->nodes(); } +int64_t MPSolver::nodes() const { return interface_->nodes(); } double MPSolver::ComputeExactConditionNumber() const { return interface_->ComputeExactConditionNumber(); diff --git a/ortools/linear_solver/linear_solver.h b/ortools/linear_solver/linear_solver.h index 0fd10d108f..69f06bbb70 100644 --- a/ortools/linear_solver/linear_solver.h +++ b/ortools/linear_solver/linear_solver.h @@ -302,6 +302,11 @@ class MPSolver { */ const std::vector& variables() const { return variables_; } + /** + * Returns the variable at position index. + */ + MPVariable* variable(int index) const { return variables_[index]; } + /** * Looks up a variable by name, and returns nullptr if it does not exist. The * first call has a O(n) complexity, as the variable name index is lazily @@ -368,6 +373,9 @@ class MPSolver { */ const std::vector& constraints() const { return constraints_; } + /** Returns the constraint at the given index. */ + MPConstraint* constraint(int index) const { return constraints_[index]; } + /** * Looks up a constraint by name, and returns nullptr if it does not exist. * @@ -570,6 +578,8 @@ class MPSolver { * like it should be): * - loading a solution whose variables don't correspond to the solver's * current variables + * - loading a dual solution whose constraints don't correspond to the + * solver's current constraints * - loading a solution with a status other than OPTIMAL / FEASIBLE. * * Note: the objective value isn't checked. You can use VerifySolution() for diff --git a/ortools/linear_solver/linear_solver.proto b/ortools/linear_solver/linear_solver.proto index 3c88e96123..430251b6f6 100644 --- a/ortools/linear_solver/linear_solver.proto +++ b/ortools/linear_solver/linear_solver.proto @@ -460,6 +460,7 @@ message MPModelRequest { // definition of MPModelDeltaProto. optional MPModelDeltaProto model_delta = 8; + } // Status returned by the solver. They follow a hierarchical nomenclature, to diff --git a/ortools/linear_solver/linear_solver_callback.h b/ortools/linear_solver/linear_solver_callback.h index 35cca34083..c05fa7a61c 100644 --- a/ortools/linear_solver/linear_solver_callback.h +++ b/ortools/linear_solver/linear_solver_callback.h @@ -16,6 +16,7 @@ #ifndef OR_TOOLS_LINEAR_SOLVER_LINEAR_SOLVER_CALLBACK_H_ #define OR_TOOLS_LINEAR_SOLVER_LINEAR_SOLVER_CALLBACK_H_ +#include #include #include @@ -131,7 +132,7 @@ class MPCallbackContext { // which 0 at the root node and > 0 otherwise. // // Call only when the event is kMipSolution or kMipNode. - virtual int64 NumExploredNodes() = 0; + virtual int64_t NumExploredNodes() = 0; }; // Extend this class with model specific logic, and register through diff --git a/ortools/linear_solver/python/linear_solver.i b/ortools/linear_solver/python/linear_solver.i index ea729e240f..b3cf78e3c8 100644 --- a/ortools/linear_solver/python/linear_solver.i +++ b/ortools/linear_solver/python/linear_solver.i @@ -328,7 +328,9 @@ PY_CONVERT(MPVariable); %unignore operations_research::MPSolver::SupportsProblemType; // No unit test %unignore operations_research::MPSolver::wall_time; // No unit test %unignore operations_research::MPSolver::Clear; // No unit test +%unignore operations_research::MPSolver::constraint; %unignore operations_research::MPSolver::constraints; +%unignore operations_research::MPSolver::variable; %unignore operations_research::MPSolver::variables; %unignore operations_research::MPSolver::NumConstraints; %unignore operations_research::MPSolver::NumVariables; diff --git a/ortools/linear_solver/sat_interface.cc b/ortools/linear_solver/sat_interface.cc index f169eacb1a..15e02af70a 100644 --- a/ortools/linear_solver/sat_interface.cc +++ b/ortools/linear_solver/sat_interface.cc @@ -12,6 +12,7 @@ // limitations under the License. #include +#include #include #include @@ -67,8 +68,8 @@ class SatInterface : public MPSolverInterface { bool AddIndicatorConstraint(MPConstraint* const ct) override { return true; } // ------ Query statistics on the solution and the solve ------ - int64 iterations() const override; - int64 nodes() const override; + int64_t iterations() const override; + int64_t nodes() const override; MPSolver::BasisStatus row_status(int constraint_index) const override; MPSolver::BasisStatus column_status(int variable_index) const override; @@ -231,11 +232,11 @@ void SatInterface::SetObjectiveOffset(double value) { NonIncrementalChange(); } void SatInterface::ClearObjective() { NonIncrementalChange(); } -int64 SatInterface::iterations() const { +int64_t SatInterface::iterations() const { return 0; // FIXME } -int64 SatInterface::nodes() const { return 0; } +int64_t SatInterface::nodes() const { return 0; } MPSolver::BasisStatus SatInterface::row_status(int constraint_index) const { return MPSolver::BasisStatus::FREE; // FIXME diff --git a/ortools/linear_solver/sat_proto_solver.cc b/ortools/linear_solver/sat_proto_solver.cc index 4799973f06..8340099622 100644 --- a/ortools/linear_solver/sat_proto_solver.cc +++ b/ortools/linear_solver/sat_proto_solver.cc @@ -13,6 +13,7 @@ #include "ortools/linear_solver/sat_proto_solver.h" +#include #include #include "absl/status/statusor.h" @@ -24,6 +25,7 @@ #include "ortools/sat/cp_model_solver.h" #include "ortools/sat/lp_utils.h" #include "ortools/sat/sat_parameters.pb.h" +#include "ortools/util/logging.h" #include "ortools/util/time_limit.h" namespace operations_research { @@ -92,14 +94,28 @@ absl::StatusOr SatSolveProto( static_cast(request.solver_time_limit_seconds()) / 1000.0); } + // TODO(user): We do not support all the parameters here. In particular the + // logs before the solver is called will not be appended to the response. Fix + // that, and remove code duplication for the logger config. One way should be + // to not touch/configure anything if the logger is already created while + // calling SolveCpModel() and call a common config function from here or from + // inside Solve()? + SolverLogger logger; + if (params.log_search_progress()) { + logger.EnableLogging(); + logger.SetLogToStdOut(params.log_to_stdout()); + } else { + logger.DisableLogging(); + } + MPSolutionResponse response; if (!ExtractValidMPModelInPlaceOrPopulateResponseStatus(&request, &response)) { - if (params.log_search_progress()) { + if (logger.LoggingIsEnabled()) { // This is needed for our benchmark scripts. sat::CpSolverResponse cp_response; cp_response.set_status(sat::CpSolverStatus::MODEL_INVALID); - LOG(INFO) << CpSolverResponseStats(cp_response); + SOLVER_LOG(&logger, CpSolverResponseStats(cp_response)); } return response; } @@ -109,9 +125,8 @@ absl::StatusOr SatSolveProto( const glop::GlopParameters glop_params; MPModelProto* const mp_model = request.mutable_model(); std::vector> for_postsolve; - const bool log_info = VLOG_IS_ON(1) || params.log_search_progress(); const auto status = - ApplyMipPresolveSteps(log_info, glop_params, mp_model, &for_postsolve); + ApplyMipPresolveSteps(glop_params, mp_model, &for_postsolve, &logger); if (status == MPSolverResponseStatus::MPSOLVER_INFEASIBLE) { if (params.log_search_progress()) { // This is needed for our benchmark scripts. @@ -125,12 +140,15 @@ absl::StatusOr SatSolveProto( } // We need to do that before the automatic detection of integers. - RemoveNearZeroTerms(params, mp_model); + RemoveNearZeroTerms(params, mp_model, &logger); + + SOLVER_LOG(&logger, ""); + SOLVER_LOG(&logger, "Scaling to pure integer problem."); const int num_variables = mp_model->variable_size(); std::vector var_scaling(num_variables, 1.0); if (params.mip_automatically_scale_variables()) { - var_scaling = sat::DetectImpliedIntegers(log_info, mp_model); + var_scaling = sat::DetectImpliedIntegers(mp_model, &logger); } if (params.mip_var_scaling() != 1.0) { const std::vector other_scaling = sat::ScaleContinuousVariables( @@ -141,7 +159,8 @@ absl::StatusOr SatSolveProto( } sat::CpModelProto cp_model; - if (!ConvertMPModelProtoToCpModelProto(params, *mp_model, &cp_model)) { + if (!ConvertMPModelProtoToCpModelProto(params, *mp_model, &cp_model, + &logger)) { if (params.log_search_progress()) { // This is needed for our benchmark scripts. sat::CpSolverResponse cp_response; @@ -173,7 +192,7 @@ absl::StatusOr SatSolveProto( } cp_model_hint->add_vars(var); - cp_model_hint->add_values(static_cast(std::round(value))); + cp_model_hint->add_values(static_cast(std::round(value))); } } @@ -182,13 +201,16 @@ absl::StatusOr SatSolveProto( const int old_num_constraints = mp_model->constraint().size(); request.Clear(); - // Solve. + // Configure model. sat::Model sat_model; + sat_model.Register(&logger); sat_model.Add(NewSatParameters(params)); if (interrupt_solve != nullptr) { sat_model.GetOrCreate()->RegisterExternalBooleanAsLimit( interrupt_solve); } + + // Solve. const sat::CpSolverResponse cp_response = sat::SolveCpModel(cp_model, &sat_model); diff --git a/ortools/linear_solver/sat_solver_utils.cc b/ortools/linear_solver/sat_solver_utils.cc index 35efcdddc2..2f503ba337 100644 --- a/ortools/linear_solver/sat_solver_utils.cc +++ b/ortools/linear_solver/sat_solver_utils.cc @@ -25,8 +25,9 @@ namespace operations_research { lp_preprocessors.push_back(absl::make_unique(&glop_params)); MPSolverResponseStatus ApplyMipPresolveSteps( - bool log_info, const glop::GlopParameters& glop_params, MPModelProto* model, - std::vector>* for_postsolve) { + const glop::GlopParameters& glop_params, MPModelProto* model, + std::vector>* for_postsolve, + SolverLogger* logger) { CHECK(model != nullptr); // TODO(user): General constraints are currently not supported. @@ -50,7 +51,8 @@ MPSolverResponseStatus ApplyMipPresolveSteps( if (!hint_is_present) { const std::string header = "Running basic LP presolve, initial problem dimensions: "; - LOG_IF(INFO, log_info) << header << lp.GetDimensionString(); + SOLVER_LOG(logger, ""); + SOLVER_LOG(logger, header, lp.GetDimensionString()); std::vector names; std::vector> lp_preprocessors; ADD_LP_PREPROCESSOR(glop::FixedVariablePreprocessor); @@ -69,7 +71,7 @@ MPSolverResponseStatus ApplyMipPresolveSteps( preprocessor->UseInMipContext(); const bool need_postsolve = preprocessor->Run(&lp); names[i].resize(header.size(), ' '); // padding. - LOG_IF(INFO, log_info) << names[i] << lp.GetDimensionString(); + SOLVER_LOG(logger, names[i], lp.GetDimensionString()); const glop::ProblemStatus status = preprocessor->status(); if (status != glop::ProblemStatus::INIT) { if (status == glop::ProblemStatus::PRIMAL_INFEASIBLE || diff --git a/ortools/linear_solver/sat_solver_utils.h b/ortools/linear_solver/sat_solver_utils.h index f5bf95db8a..a4eec9067f 100644 --- a/ortools/linear_solver/sat_solver_utils.h +++ b/ortools/linear_solver/sat_solver_utils.h @@ -18,6 +18,7 @@ #include "ortools/glop/preprocessor.h" #include "ortools/linear_solver/linear_solver.pb.h" +#include "ortools/util/logging.h" namespace operations_research { @@ -29,8 +30,9 @@ namespace operations_research { // Returns the presolve status which is currently UNKNOWN for most cases but // might be INFEASIBLE if there is some trivial infeasiblity in the model. MPSolverResponseStatus ApplyMipPresolveSteps( - bool log_info, const glop::GlopParameters& glop_params, MPModelProto* model, - std::vector>* for_postsolve); + const glop::GlopParameters& glop_params, MPModelProto* model, + std::vector>* for_postsolve, + SolverLogger* logger); } // namespace operations_research #endif // OR_TOOLS_LINEAR_SOLVER_SAT_SOLVER_UTILS_H_ diff --git a/ortools/linear_solver/scip_callback.cc b/ortools/linear_solver/scip_callback.cc index 47007995f4..673ef06c27 100644 --- a/ortools/linear_solver/scip_callback.cc +++ b/ortools/linear_solver/scip_callback.cc @@ -15,6 +15,8 @@ #include "ortools/linear_solver/scip_callback.h" +#include + #include "absl/strings/str_cat.h" #include "absl/types/span.h" #include "ortools/base/logging.h" @@ -77,11 +79,11 @@ double ScipConstraintHandlerContext::VariableValue( return SCIPgetSolVal(scip_, solution_, ScipGetVar(scip_, variable->index())); } -int64 ScipConstraintHandlerContext::NumNodesProcessed() const { +int64_t ScipConstraintHandlerContext::NumNodesProcessed() const { return SCIPgetNNodes(scip_); } -int64 ScipConstraintHandlerContext::CurrentNodeId() const { +int64_t ScipConstraintHandlerContext::CurrentNodeId() const { return SCIPgetCurrentNode(scip_)->number; } diff --git a/ortools/linear_solver/scip_callback.h b/ortools/linear_solver/scip_callback.h index e4e3f529ab..28ed826596 100644 --- a/ortools/linear_solver/scip_callback.h +++ b/ortools/linear_solver/scip_callback.h @@ -21,6 +21,7 @@ #ifndef OR_TOOLS_LINEAR_SOLVER_SCIP_CALLBACK_H_ #define OR_TOOLS_LINEAR_SOLVER_SCIP_CALLBACK_H_ +#include #include #include @@ -86,8 +87,8 @@ class ScipConstraintHandlerContext { bool is_pseudo_solution); double VariableValue(const MPVariable* variable) const; - int64 CurrentNodeId() const; - int64 NumNodesProcessed() const; + int64_t CurrentNodeId() const; + int64_t NumNodesProcessed() const; SCIP* scip() const { return scip_; } diff --git a/ortools/linear_solver/scip_interface.cc b/ortools/linear_solver/scip_interface.cc index ee15ab0ab8..b418a8f823 100644 --- a/ortools/linear_solver/scip_interface.cc +++ b/ortools/linear_solver/scip_interface.cc @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -84,8 +85,8 @@ class SCIPInterface : public MPSolverInterface { void ClearObjective() override; void BranchingPriorityChangedForVariable(int var_index) override; - int64 iterations() const override; - int64 nodes() const override; + int64_t iterations() const override; + int64_t nodes() const override; MPSolver::BasisStatus row_status(int constraint_index) const override { LOG(DFATAL) << "Basis status only available for continuous problems"; return MPSolver::FREE; @@ -894,14 +895,14 @@ bool SCIPInterface::NextSolution() { return true; } -int64 SCIPInterface::iterations() const { +int64_t SCIPInterface::iterations() const { // NOTE(user): As of 2018-12 it doesn't run in the stubby server, and is // a specialized call, so it's ok to crash if the status is broken. if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfIterations; return SCIPgetNLPIterations(scip_); } -int64 SCIPInterface::nodes() const { +int64_t SCIPInterface::nodes() const { // NOTE(user): Same story as iterations(): it's OK to crash here. if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfNodes; // This is the total number of nodes used in the solve, potentially across @@ -1087,7 +1088,7 @@ class ScipMPCallbackContext : public MPCallbackContext { LOG(FATAL) << "SuggestSolution() not currently supported for SCIP."; } - int64 NumExploredNodes() override { + int64_t NumExploredNodes() override { // scip_context_->NumNodesProcessed() returns: // 0 before the root node is solved, e.g. if a heuristic finds a solution. // 1 at the root node @@ -1095,7 +1096,7 @@ class ScipMPCallbackContext : public MPCallbackContext { // The NumExploredNodes spec requires that we return 0 at the root node, // (this is consistent with gurobi). Below is a bandaid to try and make the // behavior consistent, although some information is lost. - return std::max(int64{0}, scip_context_->NumNodesProcessed() - 1); + return std::max(int64_t{0}, scip_context_->NumNodesProcessed() - 1); } const std::vector& constraints_added() { diff --git a/ortools/sat/lp_utils.cc b/ortools/sat/lp_utils.cc index cd94b82b43..05c1f9e625 100644 --- a/ortools/sat/lp_utils.cc +++ b/ortools/sat/lp_utils.cc @@ -179,7 +179,8 @@ double GetIntegralityMultiplier(const MPModelProto& mp_model, } // namespace -void RemoveNearZeroTerms(const SatParameters& params, MPModelProto* mp_model) { +void RemoveNearZeroTerms(const SatParameters& params, MPModelProto* mp_model, + SolverLogger* logger) { const int num_variables = mp_model->variable_size(); // Compute for each variable its current maximum magnitude. Note that we will @@ -224,16 +225,15 @@ void RemoveNearZeroTerms(const SatParameters& params, MPModelProto* mp_model) { ct->mutable_coefficient()->Truncate(new_size); } - const bool log_info = VLOG_IS_ON(1) || params.log_search_progress(); - if (log_info && num_removed > 0) { - LOG(INFO) << "Removed " << num_removed - << " near zero terms with largest magnitude of " - << largest_removed << "."; + if (num_removed > 0) { + SOLVER_LOG(logger, "Removed ", num_removed, + " near zero terms with largest magnitude of ", largest_removed, + "."); } } -std::vector DetectImpliedIntegers(bool log_info, - MPModelProto* mp_model) { +std::vector DetectImpliedIntegers(MPModelProto* mp_model, + SolverLogger* logger) { const int num_variables = mp_model->variable_size(); std::vector var_scaling(num_variables, 1.0); @@ -461,18 +461,17 @@ std::vector DetectImpliedIntegers(bool log_info, << " num_rhs_fail: " << num_fail_due_to_rhs << " num_multiplier_fail: " << num_fail_due_to_large_multiplier; - if (log_info && num_to_be_handled > 0) { - LOG(INFO) << "Missed " << num_to_be_handled - << " potential implied integer."; + if (num_to_be_handled > 0) { + SOLVER_LOG(logger, "Missed ", num_to_be_handled, + " potential implied integer."); } const int num_integers = initial_num_integers + num_detected; - LOG_IF(INFO, log_info) << "Num integers: " << num_integers << "/" - << num_variables << " (implied: " << num_detected - << " in_inequalities: " << num_in_inequalities - << " max_scaling: " << max_scaling << ")" - << (num_integers == num_variables ? " [IP] " - : " [MIP] "); + SOLVER_LOG(logger, "Num integers: ", num_integers, "/", num_variables, + " (implied: ", num_detected, + " in_inequalities: ", num_in_inequalities, + " max_scaling: ", max_scaling, ")", + (num_integers == num_variables ? " [IP] " : " [MIP] ")); ApplyVarScaling(var_scaling, mp_model); return var_scaling; @@ -642,7 +641,8 @@ ConstraintProto* ConstraintScaler::AddConstraint( bool ConvertMPModelProtoToCpModelProto(const SatParameters& params, const MPModelProto& mp_model, - CpModelProto* cp_model) { + CpModelProto* cp_model, + SolverLogger* logger) { CHECK(cp_model != nullptr); cp_model->Clear(); cp_model->set_name(mp_model.name()); @@ -808,17 +808,15 @@ bool ConvertMPModelProtoToCpModelProto(const SatParameters& params, } // Display the error/scaling on the constraints. - const bool log_info = VLOG_IS_ON(1) || params.log_search_progress(); - LOG_IF(INFO, log_info) << "Maximum constraint coefficient relative error: " - << scaler.max_relative_coeff_error; - LOG_IF(INFO, log_info) - << "Maximum constraint worst-case activity relative error: " - << scaler.max_relative_rhs_error - << (scaler.max_relative_rhs_error > params.mip_check_precision() - ? " [Potentially IMPRECISE]" - : ""); - LOG_IF(INFO, log_info) << "Maximum constraint scaling factor: " - << scaler.max_scaling_factor; + SOLVER_LOG(logger, "Maximum constraint coefficient relative error: ", + scaler.max_relative_coeff_error); + SOLVER_LOG(logger, "Maximum constraint worst-case activity relative error: ", + scaler.max_relative_rhs_error, + (scaler.max_relative_rhs_error > params.mip_check_precision() + ? " [Potentially IMPRECISE]" + : "")); + SOLVER_LOG(logger, + "Maximum constraint scaling factor: ", scaler.max_scaling_factor); // Add the objective. std::vector var_indices; @@ -848,9 +846,8 @@ bool ConvertMPModelProtoToCpModelProto(const SatParameters& params, if (!coefficients.empty()) { const double average_magnitude = l1_norm / static_cast(coefficients.size()); - LOG_IF(INFO, log_info) << "Objective magnitude in [" << min_magnitude - << ", " << max_magnitude - << "] average = " << average_magnitude; + SOLVER_LOG(logger, "Objective magnitude in [", min_magnitude, ", ", + max_magnitude, "] average = ", average_magnitude); } if (!coefficients.empty() || mp_model.objective_offset() != 0.0) { double scaling_factor = GetBestScalingOfDoublesToInt64( @@ -886,14 +883,13 @@ bool ConvertMPModelProtoToCpModelProto(const SatParameters& params, ComputeGcdOfRoundedDoubles(coefficients, scaling_factor); // Display the objective error/scaling. - LOG_IF(INFO, log_info) - << "Objective coefficient relative error: " << relative_coeff_error - << (relative_coeff_error > params.mip_check_precision() ? " [IMPRECISE]" - : ""); - LOG_IF(INFO, log_info) << "Objective worst-case absolute error: " - << scaled_sum_error / scaling_factor; - LOG_IF(INFO, log_info) << "Objective scaling factor: " - << scaling_factor / gcd; + SOLVER_LOG( + logger, "Objective coefficient relative error: ", relative_coeff_error, + (relative_coeff_error > params.mip_check_precision() ? " [IMPRECISE]" + : "")); + SOLVER_LOG(logger, "Objective worst-case absolute error: ", + scaled_sum_error / scaling_factor); + SOLVER_LOG(logger, "Objective scaling factor: ", scaling_factor / gcd); // Note that here we set the scaling factor for the inverse operation of // getting the "true" objective value from the scaled one. Hence the diff --git a/ortools/sat/lp_utils.h b/ortools/sat/lp_utils.h index f07004fa10..1a04a1fd81 100644 --- a/ortools/sat/lp_utils.h +++ b/ortools/sat/lp_utils.h @@ -22,6 +22,7 @@ #include "ortools/sat/cp_model.pb.h" #include "ortools/sat/sat_parameters.pb.h" #include "ortools/sat/sat_solver.h" +#include "ortools/util/logging.h" namespace operations_research { namespace sat { @@ -57,7 +58,8 @@ std::vector ScaleContinuousVariables(double scaling, double max_bound, // be set to zero. We need to do that before operations like // DetectImpliedIntegers(), becauses really low coefficients can cause issues // and might lead to less detection. -void RemoveNearZeroTerms(const SatParameters& params, MPModelProto* mp_model); +void RemoveNearZeroTerms(const SatParameters& params, MPModelProto* mp_model, + SolverLogger* logger); // This will mark implied integer as such. Note that it can also discover // variable of the form coeff * Integer + offset, and will change the model @@ -66,8 +68,8 @@ void RemoveNearZeroTerms(const SatParameters& params, MPModelProto* mp_model); // // TODO(user): Actually implement the offset part. This currently only happens // on the 3 neos-46470* miplib problems where we have a non-integer rhs. -std::vector DetectImpliedIntegers(bool log_info, - MPModelProto* mp_model); +std::vector DetectImpliedIntegers(MPModelProto* mp_model, + SolverLogger* logger); // Converts a MIP problem to a CpModel. Returns false if the coefficients // couldn't be converted to integers with a good enough precision. @@ -76,7 +78,8 @@ std::vector DetectImpliedIntegers(bool log_info, // SatParameters proto documentation for the mip_* parameters. bool ConvertMPModelProtoToCpModelProto(const SatParameters& params, const MPModelProto& mp_model, - CpModelProto* cp_model); + CpModelProto* cp_model, + SolverLogger* logger); // Converts an integer program with only binary variables to a Boolean // optimization problem. Returns false if the problem didn't contains only