// Copyright 2010-2025 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 #include #include #include "absl/base/attributes.h" #include "ortools/base/logging.h" #include "ortools/bop/bop_parameters.pb.h" #include "ortools/bop/integral_solver.h" #include "ortools/linear_solver/linear_solver.h" namespace operations_research { namespace { MPSolver::ResultStatus TranslateProblemStatus(bop::BopSolveStatus status) { switch (status) { case bop::BopSolveStatus::OPTIMAL_SOLUTION_FOUND: return MPSolver::OPTIMAL; case bop::BopSolveStatus::FEASIBLE_SOLUTION_FOUND: return MPSolver::FEASIBLE; case bop::BopSolveStatus::NO_SOLUTION_FOUND: return MPSolver::NOT_SOLVED; case bop::BopSolveStatus::INFEASIBLE_PROBLEM: return MPSolver::INFEASIBLE; case bop::BopSolveStatus::INVALID_PROBLEM: return MPSolver::ABNORMAL; } LOG(DFATAL) << "Invalid bop::BopSolveStatus"; return MPSolver::ABNORMAL; } } // Anonymous namespace class BopInterface : public MPSolverInterface { public: explicit BopInterface(MPSolver* solver); ~BopInterface() override; // ----- Solve ----- MPSolver::ResultStatus Solve(const MPSolverParameters& param) 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* ct) override; void AddVariable(MPVariable* var) override; void SetCoefficient(MPConstraint* constraint, const MPVariable* variable, double new_value, double old_value) override; void ClearConstraint(MPConstraint* constraint) override; void SetObjectiveCoefficient(const MPVariable* variable, double coefficient) override; void SetObjectiveOffset(double value) override; void ClearObjective() override; // ------ Query statistics on the solution and the solve ------ 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; // ----- Misc ----- bool IsContinuous() const override; bool IsLP() const override; bool IsMIP() const override; std::string SolverVersion() const override; bool InterruptSolve() 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; private: void NonIncrementalChange(); glop::LinearProgram linear_program_; bop::IntegralSolver bop_solver_; std::vector column_status_; std::vector row_status_; bop::BopParameters parameters_; std::atomic interrupt_solver_; }; BopInterface::BopInterface(MPSolver* const solver) : MPSolverInterface(solver), linear_program_(), bop_solver_(), column_status_(), row_status_(), parameters_(), interrupt_solver_(false) {} BopInterface::~BopInterface() {} MPSolver::ResultStatus BopInterface::Solve(const MPSolverParameters& param) { // Check whenever the solve has already been stopped by the user. if (interrupt_solver_) { Reset(); // linear_solver.cc as DCHECK_EQ that interface_->result_status_ is the same // as the status returned by interface_->Solve(). result_status_ = MPSolver::NOT_SOLVED; return result_status_; } // Reset extraction as this interface is not incremental yet. Reset(); ExtractModel(); SetParameters(param); linear_program_.SetMaximizationProblem(maximize_); linear_program_.CleanUp(); // 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); } parameters_.set_log_search_progress(!quiet()); glop::DenseRow initial_solution; if (!solver_->solution_hint_.empty()) { const int num_vars = solver_->variables_.size(); if (solver_->solution_hint_.size() != num_vars) { LOG(WARNING) << "Bop currently doesn't handle partial solution hints. " << "Filling the missing positions with zeros..."; } initial_solution.assign(glop::ColIndex(num_vars), glop::Fractional(0.0)); for (const std::pair& p : solver_->solution_hint_) { initial_solution[glop::ColIndex(p.first->index())] = glop::Fractional(p.second); } } solver_->SetSolverSpecificParametersAsString( solver_->solver_specific_parameter_string_); bop_solver_.SetParameters(parameters_); std::unique_ptr time_limit = TimeLimit::FromParameters(parameters_); time_limit->RegisterExternalBooleanAsLimit(&interrupt_solver_); const bop::BopSolveStatus status = initial_solution.empty() ? bop_solver_.SolveWithTimeLimit(linear_program_, time_limit.get()) : bop_solver_.SolveWithTimeLimit(linear_program_, initial_solution, time_limit.get()); // The solution must be marked as synchronized even when no solution exists. sync_status_ = SOLUTION_SYNCHRONIZED; result_status_ = TranslateProblemStatus(status); if (result_status_ == MPSolver::FEASIBLE || result_status_ == MPSolver::OPTIMAL) { // Get the results. objective_value_ = bop_solver_.objective_value(); best_objective_bound_ = bop_solver_.best_bound(); // TODO(user): Implement the column status. const size_t num_vars = solver_->variables_.size(); column_status_.resize(num_vars, MPSolver::FREE); for (int var_id = 0; var_id < num_vars; ++var_id) { MPVariable* const var = solver_->variables_[var_id]; const glop::ColIndex lp_solver_var_id(var->index()); const glop::Fractional solution_value = bop_solver_.variable_values()[lp_solver_var_id]; var->set_solution_value(static_cast(solution_value)); } // TODO(user): Implement the row status. const size_t num_constraints = solver_->constraints_.size(); row_status_.resize(num_constraints, MPSolver::FREE); } return result_status_; } void BopInterface::Reset() { ResetExtractionInformation(); linear_program_.Clear(); interrupt_solver_ = false; } void BopInterface::SetOptimizationDirection(bool maximize) { NonIncrementalChange(); } void BopInterface::SetVariableBounds(int index, double lb, double ub) { NonIncrementalChange(); } void BopInterface::SetVariableInteger(int index, bool integer) { NonIncrementalChange(); } void BopInterface::SetConstraintBounds(int index, double lb, double ub) { NonIncrementalChange(); } void BopInterface::AddRowConstraint(MPConstraint* const ct) { NonIncrementalChange(); } void BopInterface::AddVariable(MPVariable* const var) { NonIncrementalChange(); } void BopInterface::SetCoefficient(MPConstraint* const constraint, const MPVariable* const variable, double new_value, double old_value) { NonIncrementalChange(); } void BopInterface::ClearConstraint(MPConstraint* const constraint) { NonIncrementalChange(); } void BopInterface::SetObjectiveCoefficient(const MPVariable* const variable, double coefficient) { NonIncrementalChange(); } void BopInterface::SetObjectiveOffset(double value) { NonIncrementalChange(); } void BopInterface::ClearObjective() { NonIncrementalChange(); } int64_t BopInterface::iterations() const { LOG(DFATAL) << "Number of iterations not available"; return kUnknownNumberOfIterations; } int64_t BopInterface::nodes() const { LOG(DFATAL) << "Number of nodes not available"; return kUnknownNumberOfNodes; } MPSolver::BasisStatus BopInterface::row_status(int constraint_index) const { return row_status_[constraint_index]; } MPSolver::BasisStatus BopInterface::column_status(int variable_index) const { return column_status_[variable_index]; } bool BopInterface::IsContinuous() const { return false; } bool BopInterface::IsLP() const { return false; } bool BopInterface::IsMIP() const { return true; } std::string BopInterface::SolverVersion() const { // TODO(user): Decide how to version bop. return "Bop-0.0"; } bool BopInterface::InterruptSolve() { interrupt_solver_ = true; return true; } void* BopInterface::underlying_solver() { return &bop_solver_; } // TODO(user): remove duplication with GlopInterface. void BopInterface::ExtractNewVariables() { DCHECK_EQ(0, last_variable_index_); DCHECK_EQ(0, last_constraint_index_); const glop::ColIndex num_cols(solver_->variables_.size()); for (glop::ColIndex col(last_variable_index_); col < num_cols; ++col) { MPVariable* const var = solver_->variables_[col.value()]; const glop::ColIndex new_col = linear_program_.CreateNewVariable(); DCHECK_EQ(new_col, col); set_variable_as_extracted(col.value(), true); linear_program_.SetVariableBounds(col, var->lb(), var->ub()); if (var->integer()) { linear_program_.SetVariableType( col, glop::LinearProgram::VariableType::INTEGER); } } } // TODO(user): remove duplication with GlopInterface. void BopInterface::ExtractNewConstraints() { DCHECK_EQ(0, last_constraint_index_); const glop::RowIndex num_rows(solver_->constraints_.size()); for (glop::RowIndex row(0); row < num_rows; ++row) { MPConstraint* const ct = solver_->constraints_[row.value()]; set_constraint_as_extracted(row.value(), true); const double lb = ct->lb(); const double ub = ct->ub(); const glop::RowIndex new_row = linear_program_.CreateNewConstraint(); DCHECK_EQ(new_row, row); linear_program_.SetConstraintBounds(row, lb, ub); for (const auto& entry : ct->coefficients_) { const int var_index = entry.first->index(); DCHECK(variable_is_extracted(var_index)); const glop::ColIndex col(var_index); const double coeff = entry.second; linear_program_.SetCoefficient(row, col, coeff); } } } // TODO(user): remove duplication with GlopInterface. void BopInterface::ExtractObjective() { linear_program_.SetObjectiveOffset(solver_->Objective().offset()); for (const auto& entry : solver_->objective_->coefficients_) { const int var_index = entry.first->index(); const glop::ColIndex col(var_index); const double coeff = entry.second; linear_program_.SetObjectiveCoefficient(col, coeff); } } void BopInterface::SetParameters(const MPSolverParameters& param) { parameters_.Clear(); SetCommonParameters(param); } // All these have no effect. void BopInterface::SetPrimalTolerance(double value) {} void BopInterface::SetDualTolerance(double value) {} void BopInterface::SetScalingMode(int value) {} void BopInterface::SetLpAlgorithm(int value) {} void BopInterface::SetRelativeMipGap(double value) {} void BopInterface::SetPresolveMode(int value) { switch (value) { case MPSolverParameters::PRESOLVE_OFF: // TODO(user): add this to BopParameters. break; case MPSolverParameters::PRESOLVE_ON: // TODO(user): add this to BopParameters. break; default: if (value != MPSolverParameters::kDefaultIntegerParamValue) { SetIntegerParamToUnsupportedValue(MPSolverParameters::PRESOLVE, value); } } } bool BopInterface::SetSolverSpecificParametersAsString( const std::string& parameters) { const bool ok = google::protobuf::TextFormat::MergeFromString(parameters, ¶meters_); bop_solver_.SetParameters(parameters_); return ok; } void BopInterface::NonIncrementalChange() { // The current implementation is not incremental. sync_status_ = MUST_RELOAD; } namespace { // See MpSolverInterfaceFactoryRepository for details. const void* const kRegisterBop ABSL_ATTRIBUTE_UNUSED = [] { MPSolverInterfaceFactoryRepository::GetInstance()->Register( [](MPSolver* const solver) { return new BopInterface(solver); }, MPSolver::BOP_INTEGER_PROGRAMMING); return nullptr; }(); } // namespace } // namespace operations_research