add minimal highs interface

This commit is contained in:
Laurent Perron
2022-10-03 14:25:58 +02:00
parent 0f260ffea6
commit 4fcef6ee8f
5 changed files with 321 additions and 4 deletions

View File

@@ -75,6 +75,7 @@ cc_library(
"glop_interface.cc",
"glop_utils.cc",
"gurobi_interface.cc",
"highs_interface.cc",
"linear_expr.cc",
"linear_solver_callback.cc",
"linear_solver.cc",
@@ -111,6 +112,7 @@ cc_library(
copts = [
"-DUSE_PDLP",
"-DUSE_SCIP",
"-DUSE_HIGHS",
],
deps = [
":linear_solver_cc_proto",
@@ -160,8 +162,8 @@ cc_library(
visibility = ["//visibility:public"],
deps = [
":linear_solver_cc_proto",
"//ortools/base",
"//ortools/base:accurate_sum",
"//ortools/base:commandlineflags",
"//ortools/base:map_util",
"//ortools/port:file",
"//ortools/port:proto_utils",

View File

@@ -0,0 +1,311 @@
// Copyright 2010-2022 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 <atomic>
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/base/attributes.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/types/optional.h"
#include "google/protobuf/text_format.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/proto_solver/highs_proto_solver.h"
#include "ortools/port/proto_utils.h"
namespace operations_research {
class HighsInterface : public MPSolverInterface {
public:
explicit HighsInterface(MPSolver* const solver, bool solve_as_a_mip);
~HighsInterface() override;
// ----- Solve -----
MPSolver::ResultStatus Solve(const MPSolverParameters& param) override;
std::optional<MPSolutionResponse> DirectlySolveProto(
const MPModelRequest& request, std::atomic<bool>* interrupt) 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;
// ------ 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;
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;
absl::Status SetNumThreads(int num_threads) override;
private:
void NonIncrementalChange();
const bool solve_as_a_mip_;
};
HighsInterface::HighsInterface(MPSolver* const solver, bool solve_as_a_mip)
: MPSolverInterface(solver), solve_as_a_mip_(solve_as_a_mip) {}
HighsInterface::~HighsInterface() {}
MPSolver::ResultStatus HighsInterface::Solve(const MPSolverParameters& param) {
// Reset extraction as this interface is not incremental yet.
Reset();
ExtractModel();
SetParameters(param);
if (quiet_) {
// parameters_.set_verbosity_level(0);
} else {
// parameters_.set_verbosity_level(3);
}
solver_->SetSolverSpecificParametersAsString(
solver_->solver_specific_parameter_string_);
// Time limit.
if (solver_->time_limit()) {
VLOG(1) << "Setting time limit = " << solver_->time_limit() << " ms.";
// parameters_.mutable_termination_criteria()->set_time_sec_limit(
// static_cast<double>(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);
}
MPModelProto model_proto;
solver_->ExportModelToProto(&model_proto);
MPModelRequest request;
// *request.mutable_model() = std::move(model_proto);
// if (!google::protobuf::TextFormat::PrintToString(
// parameters_, request.mutable_solver_specific_parameters())) {
// LOG(QFATAL) << "Error converting parameters to text format: "
// << parameters_.DebugString();
// }
absl::StatusOr<MPSolutionResponse> response =
HighsSolveProto(request, /*relax_integer_variables=*/!solve_as_a_mip_);
if (!response.ok()) {
LOG(ERROR) << "Unexpected error solving with Highs: " << response.status();
return MPSolver::ABNORMAL;
}
// The solution must be marked as synchronized even when no solution exists.
sync_status_ = SOLUTION_SYNCHRONIZED;
result_status_ = static_cast<MPSolver::ResultStatus>(response->status());
LOG_IF(DFATAL, !response->has_solver_specific_info())
<< response->DebugString();
// if (!solve_log_.ParseFromString(response->solver_specific_info())) {
// LOG(DFATAL) << "Unable to parse Highs's SolveLog from
// solver_specific_info";
// }
if (response->status() == MPSOLVER_FEASIBLE ||
response->status() == MPSOLVER_OPTIMAL) {
const absl::Status result = solver_->LoadSolutionFromProto(*response);
if (!result.ok()) {
LOG(ERROR) << "LoadSolutionFromProto failed: " << result;
}
}
return result_status_;
}
std::optional<MPSolutionResponse> HighsInterface::DirectlySolveProto(
const MPModelRequest& request, std::atomic<bool>* interrupt) {
if (interrupt) return std::nullopt;
absl::StatusOr<MPSolutionResponse> response =
HighsSolveProto(request, /*relax_integer_variables=*/false);
if (!response.ok()) {
LOG(ERROR) << "Unexpected error solving with Highs: " << response.status();
MPSolutionResponse error_response;
error_response.set_status(MPSolverResponseStatus::MPSOLVER_ABNORMAL);
error_response.set_status_str(response.status().ToString());
return error_response;
}
return *response;
}
void HighsInterface::Reset() { ResetExtractionInformation(); }
void HighsInterface::SetOptimizationDirection(bool maximize) {
NonIncrementalChange();
}
void HighsInterface::SetVariableBounds(int index, double lb, double ub) {
NonIncrementalChange();
}
void HighsInterface::SetVariableInteger(int index, bool integer) {
NonIncrementalChange();
}
void HighsInterface::SetConstraintBounds(int index, double lb, double ub) {
NonIncrementalChange();
}
void HighsInterface::AddRowConstraint(MPConstraint* const ct) {
NonIncrementalChange();
}
void HighsInterface::AddVariable(MPVariable* const var) {
NonIncrementalChange();
}
void HighsInterface::SetCoefficient(MPConstraint* const constraint,
const MPVariable* const variable,
double new_value, double old_value) {
NonIncrementalChange();
}
void HighsInterface::ClearConstraint(MPConstraint* const constraint) {
NonIncrementalChange();
}
void HighsInterface::SetObjectiveCoefficient(const MPVariable* const variable,
double coefficient) {
NonIncrementalChange();
}
void HighsInterface::SetObjectiveOffset(double value) {
NonIncrementalChange();
}
void HighsInterface::ClearObjective() { NonIncrementalChange(); }
int64_t HighsInterface::iterations() const {
return 0; // FIXME.
}
int64_t HighsInterface::nodes() const {
LOG(DFATAL) << "Number of nodes only available for discrete problems";
return MPSolverInterface::kUnknownNumberOfNodes;
}
MPSolver::BasisStatus HighsInterface::row_status(int constraint_index) const {
// TODO(user): While basis status isn't well defined for PDLP, we could
// guess statuses that might be useful.
return MPSolver::BasisStatus::FREE;
}
MPSolver::BasisStatus HighsInterface::column_status(int variable_index) const {
// TODO(user): While basis status isn't well defined for PDLP, we could
// guess statuses that might be useful.
return MPSolver::BasisStatus::FREE;
}
bool HighsInterface::IsContinuous() const { return true; }
bool HighsInterface::IsLP() const { return true; }
bool HighsInterface::IsMIP() const { return solve_as_a_mip_; }
std::string HighsInterface::SolverVersion() const { return "PDLP Solver"; }
// TODO(user): Consider returning the SolveLog here, as it could be essential
// for interpreting the PDLP solution.
void* HighsInterface::underlying_solver() { return nullptr; }
void HighsInterface::ExtractNewVariables() { NonIncrementalChange(); }
void HighsInterface::ExtractNewConstraints() { NonIncrementalChange(); }
void HighsInterface::ExtractObjective() { NonIncrementalChange(); }
void HighsInterface::SetParameters(const MPSolverParameters& param) {
SetCommonParameters(param);
}
absl::Status HighsInterface::SetNumThreads(int num_threads) {
if (num_threads < 1) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid number of threads: ", num_threads));
}
// parameters_.set_num_threads(num_threads);
return absl::OkStatus();
}
// These have no effect. Use SetSolverSpecificParametersAsString instead.
void HighsInterface::SetPrimalTolerance(double value) {}
void HighsInterface::SetDualTolerance(double value) {}
void HighsInterface::SetScalingMode(int value) {}
void HighsInterface::SetLpAlgorithm(int value) {}
void HighsInterface::SetRelativeMipGap(double value) {}
void HighsInterface::SetPresolveMode(int value) {}
bool HighsInterface::SetSolverSpecificParametersAsString(
const std::string& parameters) {
// return ProtobufTextFormatMergeFromString(parameters, &parameters_);
return false;
}
void HighsInterface::NonIncrementalChange() {
// The current implementation is not incremental.
sync_status_ = MUST_RELOAD;
}
// Register PDLP in the global linear solver factory.
MPSolverInterface* BuildHighsInterface(MPSolver* const solver,
bool solve_as_a_mip) {
return new HighsInterface(solver, solve_as_a_mip);
}
} // namespace operations_research

View File

@@ -859,6 +859,7 @@ class MPSolver {
friend class BopInterface;
friend class SatInterface;
friend class PdlpInterface;
friend class HighsInterface;
friend class KnapsackInterface;
// Debugging: verify that the given MPVariable* belongs to this solver.
@@ -1079,6 +1080,7 @@ class MPObjective {
friend class BopInterface;
friend class SatInterface;
friend class PdlpInterface;
friend class HighsInterface;
friend class KnapsackInterface;
// Constructor. An objective points to a single MPSolverInterface
@@ -1189,6 +1191,7 @@ class MPVariable {
friend class BopInterface;
friend class SatInterface;
friend class PdlpInterface;
friend class HighsInterface;
friend class KnapsackInterface;
// Constructor. A variable points to a single MPSolverInterface that
@@ -1331,6 +1334,7 @@ class MPConstraint {
friend class BopInterface;
friend class SatInterface;
friend class PdlpInterface;
friend class HighsInterface;
friend class KnapsackInterface;
// Constructor. A constraint points to a single MPSolverInterface

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# Copyright 2010-2022 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -47,7 +46,7 @@ class PyWrapLp(unittest.TestCase):
text_format.Merge(TEXT_MODEL, input_proto)
solver = pywraplp.Solver.CreateSolver('CBC')
if not solver:
return
return
# For now, create the model from the proto by parsing the proto
errors = solver.LoadModelFromProto(input_proto)
self.assertFalse(errors)

View File

@@ -32,7 +32,8 @@ cc_library(
copts = [
"-DUSE_HIGHS",
"-DUSE_PDLP",
"-DUSE_SCIP", ],
"-DUSE_SCIP",
],
visibility = ["//visibility:public"],
deps = [
"//ortools/linear_solver",