Merge pull request #4505 from rte-france/feature/xpress_mathopt_from_google_main
Add XPRESS support to MathOpt (LP & QP)
This commit is contained in:
@@ -203,13 +203,6 @@ void interruptXPRESS(XPRSprob& xprsProb, CUSTOM_INTERRUPT_REASON reason) {
|
||||
XPRSinterrupt(xprsProb, 1000 + reason);
|
||||
}
|
||||
|
||||
enum XPRS_BASIS_STATUS {
|
||||
XPRS_AT_LOWER = 0,
|
||||
XPRS_BASIC = 1,
|
||||
XPRS_AT_UPPER = 2,
|
||||
XPRS_FREE_SUPER = 3
|
||||
};
|
||||
|
||||
// In case we need to return a double but don't have a value for that
|
||||
// we just return a NaN.
|
||||
#if !defined(XPRS_NAN)
|
||||
|
||||
@@ -85,6 +85,8 @@ std::optional<absl::string_view> Enum<SolverType>::ToOptString(
|
||||
return "highs";
|
||||
case SolverType::kSantorini:
|
||||
return "santorini";
|
||||
case SolverType::kXpress:
|
||||
return "xpress";
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -109,6 +109,12 @@ enum class SolverType {
|
||||
// Slow/not recommended for production. Not an LP solver (no dual information
|
||||
// returned).
|
||||
kSantorini = SOLVER_TYPE_SANTORINI,
|
||||
|
||||
// Fico XPRESS solver (third party).
|
||||
//
|
||||
// Supports LP, MIP, and nonconvex integer quadratic problems.
|
||||
// A fast option, but has special licensing.
|
||||
kXpress = SOLVER_TYPE_XPRESS
|
||||
};
|
||||
|
||||
MATH_OPT_DEFINE_ENUM(SolverType, SOLVER_TYPE_UNSPECIFIED);
|
||||
|
||||
@@ -105,6 +105,12 @@ enum SolverTypeProto {
|
||||
// Slow/not recommended for production. Not an LP solver (no dual information
|
||||
// returned).
|
||||
SOLVER_TYPE_SANTORINI = 11;
|
||||
|
||||
// Fico XPRESS solver (third party).
|
||||
//
|
||||
// Supports LP, MIP, and nonconvex integer quadratic problems.
|
||||
// A fast option, but has special licensing.
|
||||
SOLVER_TYPE_XPRESS = 12;
|
||||
}
|
||||
|
||||
// Selects an algorithm for solving linear programs.
|
||||
|
||||
@@ -50,6 +50,9 @@ bool ActivatePrimalRay(const SolverType solver_type, SolveParameters& params) {
|
||||
return false;
|
||||
case SolverType::kHighs:
|
||||
return false;
|
||||
case SolverType::kXpress:
|
||||
// TODO: support XPRESS
|
||||
return false;
|
||||
default:
|
||||
LOG(FATAL)
|
||||
<< "Solver " << solver_type
|
||||
@@ -82,6 +85,9 @@ bool ActivateDualRay(const SolverType solver_type, SolveParameters& params) {
|
||||
return false;
|
||||
case SolverType::kHighs:
|
||||
return false;
|
||||
case SolverType::kXpress:
|
||||
// TODO: support XPRESS
|
||||
return false;
|
||||
default:
|
||||
LOG(FATAL)
|
||||
<< "Solver " << solver_type
|
||||
|
||||
@@ -40,6 +40,11 @@ if(NOT USE_SCIP)
|
||||
list(FILTER _SRCS EXCLUDE REGEX "/gscip_.*.h$")
|
||||
list(FILTER _SRCS EXCLUDE REGEX "/gscip_.*.cc$")
|
||||
endif()
|
||||
if(NOT USE_XPRESS)
|
||||
list(FILTER _SRCS EXCLUDE REGEX "/xpress/")
|
||||
list(FILTER _SRCS EXCLUDE REGEX "/xpress_.*.h$")
|
||||
list(FILTER _SRCS EXCLUDE REGEX "/xpress_.*.cc$")
|
||||
endif()
|
||||
target_sources(${NAME} PRIVATE ${_SRCS})
|
||||
set_target_properties(${NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
target_include_directories(${NAME} PUBLIC
|
||||
@@ -233,3 +238,34 @@ if(USE_HIGHS)
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_status_tests>"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(USE_XPRESS)
|
||||
ortools_cxx_test(
|
||||
NAME
|
||||
math_opt_solvers_xpress_solver_test
|
||||
SOURCES
|
||||
"xpress_solver_test.cc"
|
||||
LINK_LIBRARIES
|
||||
GTest::gmock
|
||||
GTest::gmock_main
|
||||
absl::status
|
||||
ortools::math_opt_matchers
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_callback_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_invalid_input_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_generic_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_infeasible_subsystem_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_ip_model_solve_parameters_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_ip_parameter_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_logical_constraint_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_lp_incomplete_solve_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_lp_model_solve_parameters_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_lp_parameter_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_lp_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_mip_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_multi_objective_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_status_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_qp_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_second_order_cone_tests>"
|
||||
"$<LINK_LIBRARY:WHOLE_ARCHIVE,ortools::math_opt_qc_tests>"
|
||||
)
|
||||
endif()
|
||||
320
ortools/math_opt/solvers/xpress/g_xpress.cc
Normal file
320
ortools/math_opt/solvers/xpress/g_xpress.cc
Normal file
@@ -0,0 +1,320 @@
|
||||
// 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 "ortools/math_opt/solvers/xpress/g_xpress.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/log/check.h"
|
||||
#include "absl/log/die_if_null.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/base/logging.h"
|
||||
#include "ortools/base/source_location.h"
|
||||
#include "ortools/base/status_builder.h"
|
||||
#include "ortools/base/status_macros.h"
|
||||
#include "ortools/xpress/environment.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
namespace {
|
||||
bool checkInt32Overflow(const unsigned int value) { return value > INT32_MAX; }
|
||||
} // namespace
|
||||
|
||||
constexpr int kXpressOk = 0;
|
||||
|
||||
absl::Status Xpress::ToStatus(const int xprs_err,
|
||||
const absl::StatusCode code) const {
|
||||
if (xprs_err == kXpressOk) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
char errmsg[512];
|
||||
int status = XPRSgetlasterror(xpress_model_, errmsg);
|
||||
if (status == kXpressOk) {
|
||||
return util::StatusBuilder(code)
|
||||
<< "Xpress error code: " << xprs_err << ", message: " << errmsg;
|
||||
}
|
||||
return util::StatusBuilder(code) << "Xpress error code: " << xprs_err
|
||||
<< " (message could not be fetched)";
|
||||
}
|
||||
|
||||
Xpress::Xpress(XPRSprob& model) : xpress_model_(ABSL_DIE_IF_NULL(model)) {
|
||||
initIntControlDefaults();
|
||||
}
|
||||
|
||||
absl::StatusOr<std::unique_ptr<Xpress>> Xpress::New(
|
||||
const std::string& model_name) {
|
||||
bool correctlyLoaded = initXpressEnv();
|
||||
CHECK(correctlyLoaded);
|
||||
XPRSprob model;
|
||||
CHECK_EQ(kXpressOk, XPRScreateprob(&model));
|
||||
CHECK_EQ(kXpressOk, XPRSaddcbmessage(model, printXpressMessage, nullptr, 0));
|
||||
return absl::WrapUnique(new Xpress(model));
|
||||
}
|
||||
|
||||
absl::Status Xpress::SetProbName(const std::string& name) {
|
||||
std::string truncated = name;
|
||||
auto maxLength = GetIntAttr(XPRS_MAXPROBNAMELENGTH);
|
||||
if (truncated.length() > maxLength.value_or(INT_MAX)) {
|
||||
truncated = truncated.substr(0, maxLength.value_or(INT_MAX));
|
||||
}
|
||||
return ToStatus(XPRSsetprobname(xpress_model_, truncated.c_str()));
|
||||
}
|
||||
|
||||
void XPRS_CC Xpress::printXpressMessage(XPRSprob prob, void* data,
|
||||
const char* sMsg, int nLen,
|
||||
int nMsgLvl) {
|
||||
if (sMsg) {
|
||||
std::cout << sMsg << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
Xpress::~Xpress() {
|
||||
CHECK_EQ(kXpressOk, XPRSdestroyprob(xpress_model_));
|
||||
CHECK_EQ(kXpressOk, XPRSfree());
|
||||
}
|
||||
|
||||
void Xpress::initIntControlDefaults() {
|
||||
std::vector controls = {XPRS_LPITERLIMIT, XPRS_BARITERLIMIT};
|
||||
for (auto control : controls) {
|
||||
int_control_defaults_[control] = GetIntControl(control).value();
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status Xpress::AddVars(const absl::Span<const double> obj,
|
||||
const absl::Span<const double> lb,
|
||||
const absl::Span<const double> ub,
|
||||
const absl::Span<const char> vtype) {
|
||||
return AddVars({}, {}, {}, obj, lb, ub, vtype);
|
||||
}
|
||||
|
||||
absl::Status Xpress::AddVars(const absl::Span<const int> vbegin,
|
||||
const absl::Span<const int> vind,
|
||||
const absl::Span<const double> vval,
|
||||
const absl::Span<const double> obj,
|
||||
const absl::Span<const double> lb,
|
||||
const absl::Span<const double> ub,
|
||||
const absl::Span<const char> vtype) {
|
||||
if (checkInt32Overflow(lb.size())) {
|
||||
return absl::InvalidArgumentError(
|
||||
"XPRESS cannot handle more than 2^31 variables");
|
||||
}
|
||||
const int num_vars = static_cast<int>(lb.size());
|
||||
if (vind.size() != vval.size() || ub.size() != num_vars ||
|
||||
vtype.size() != num_vars || (!obj.empty() && obj.size() != num_vars) ||
|
||||
(!vbegin.empty() && vbegin.size() != num_vars)) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Xpress::AddVars arguments are of inconsistent sizes");
|
||||
}
|
||||
double* c_obj = nullptr;
|
||||
if (!obj.empty()) {
|
||||
c_obj = const_cast<double*>(obj.data());
|
||||
}
|
||||
// TODO: look into int64 support for number of vars (use XPRSaddcols64)
|
||||
return ToStatus(XPRSaddcols(xpress_model_, num_vars, 0, c_obj, nullptr,
|
||||
nullptr, nullptr, lb.data(), ub.data()));
|
||||
}
|
||||
|
||||
absl::Status Xpress::AddConstrs(const absl::Span<const char> sense,
|
||||
const absl::Span<const double> rhs,
|
||||
const absl::Span<const double> rng) {
|
||||
const int num_cons = static_cast<int>(sense.size());
|
||||
if (rhs.size() != num_cons) {
|
||||
return absl::InvalidArgumentError(
|
||||
"RHS must have one element per constraint.");
|
||||
}
|
||||
return ToStatus(XPRSaddrows(xpress_model_, num_cons, 0, sense.data(),
|
||||
rhs.data(), rng.data(), NULL, NULL, NULL));
|
||||
}
|
||||
|
||||
absl::Status Xpress::AddConstrs(const absl::Span<const char> rowtype,
|
||||
const absl::Span<const double> rhs,
|
||||
const absl::Span<const double> rng,
|
||||
const absl::Span<const int> start,
|
||||
const absl::Span<const int> colind,
|
||||
const absl::Span<const double> rowcoef) {
|
||||
const int num_cons = static_cast<int>(rowtype.size());
|
||||
if (rhs.size() != num_cons) {
|
||||
return absl::InvalidArgumentError(
|
||||
"RHS must have one element per constraint.");
|
||||
}
|
||||
if (start.size() != num_cons) {
|
||||
return absl::InvalidArgumentError(
|
||||
"START must have one element per constraint.");
|
||||
}
|
||||
if (colind.size() != rowcoef.size()) {
|
||||
return absl::InvalidArgumentError(
|
||||
"COLIND and ROWCOEF must be of the same size.");
|
||||
}
|
||||
return ToStatus(XPRSaddrows(xpress_model_, num_cons, 0, rowtype.data(),
|
||||
rhs.data(), rng.data(), start.data(),
|
||||
colind.data(), rowcoef.data()));
|
||||
}
|
||||
|
||||
absl::Status Xpress::SetObjectiveSense(bool maximize) {
|
||||
return ToStatus(XPRSchgobjsense(
|
||||
xpress_model_, maximize ? XPRS_OBJ_MAXIMIZE : XPRS_OBJ_MINIMIZE));
|
||||
}
|
||||
|
||||
absl::Status Xpress::SetLinearObjective(
|
||||
double offset, const absl::Span<const int> colind,
|
||||
const absl::Span<const double> coefficients) {
|
||||
static int indexes[1] = {-1};
|
||||
double xprs_values[1] = {-offset};
|
||||
RETURN_IF_ERROR(ToStatus(XPRSchgobj(xpress_model_, 1, indexes, xprs_values)))
|
||||
<< "Failed to set objective offset in XPRESS";
|
||||
|
||||
const int n_cols = static_cast<int>(colind.size());
|
||||
return ToStatus(
|
||||
XPRSchgobj(xpress_model_, n_cols, colind.data(), coefficients.data()));
|
||||
}
|
||||
|
||||
absl::Status Xpress::SetQuadraticObjective(
|
||||
const absl::Span<const int> colind1, const absl::Span<const int> colind2,
|
||||
const absl::Span<const double> coefficients) {
|
||||
const int ncoefs = static_cast<int>(coefficients.size());
|
||||
return ToStatus(XPRSchgmqobj(xpress_model_, ncoefs, colind1.data(),
|
||||
colind2.data(), coefficients.data()));
|
||||
}
|
||||
|
||||
absl::Status Xpress::ChgCoeffs(absl::Span<const int> rowind,
|
||||
absl::Span<const int> colind,
|
||||
absl::Span<const double> values) {
|
||||
const long n_coefs = static_cast<long>(rowind.size());
|
||||
return ToStatus(XPRSchgmcoef64(xpress_model_, n_coefs, rowind.data(),
|
||||
colind.data(), values.data()));
|
||||
}
|
||||
|
||||
absl::Status Xpress::LpOptimize(std::string flags) {
|
||||
return ToStatus(XPRSlpoptimize(xpress_model_, flags.c_str()));
|
||||
}
|
||||
|
||||
absl::Status Xpress::GetLpSol(absl::Span<double> primals,
|
||||
absl::Span<double> duals,
|
||||
absl::Span<double> reducedCosts) {
|
||||
return ToStatus(XPRSgetlpsol(xpress_model_, primals.data(), nullptr,
|
||||
duals.data(), reducedCosts.data()));
|
||||
}
|
||||
|
||||
absl::Status Xpress::PostSolve() {
|
||||
return ToStatus(XPRSpostsolve(xpress_model_));
|
||||
}
|
||||
|
||||
absl::Status Xpress::MipOptimize() {
|
||||
return ToStatus(XPRSmipoptimize(xpress_model_, nullptr));
|
||||
}
|
||||
|
||||
void Xpress::Terminate() { XPRSinterrupt(xpress_model_, XPRS_STOP_USER); };
|
||||
|
||||
absl::StatusOr<int> Xpress::GetIntControl(int control) const {
|
||||
int result;
|
||||
RETURN_IF_ERROR(ToStatus(XPRSgetintcontrol(xpress_model_, control, &result)))
|
||||
<< "Error getting Xpress int control: " << control;
|
||||
return result;
|
||||
}
|
||||
|
||||
absl::Status Xpress::SetIntControl(int control, int value) {
|
||||
return ToStatus(XPRSsetintcontrol(xpress_model_, control, value));
|
||||
}
|
||||
|
||||
absl::Status Xpress::ResetIntControl(int control) {
|
||||
if (int_control_defaults_.count(control)) {
|
||||
return ToStatus(XPRSsetintcontrol(xpress_model_, control,
|
||||
int_control_defaults_[control]));
|
||||
}
|
||||
return absl::InvalidArgumentError(
|
||||
"Default value unknown for control " + std::to_string(control) +
|
||||
", consider adding it to Xpress::initIntControlDefaults");
|
||||
}
|
||||
|
||||
absl::StatusOr<int> Xpress::GetIntAttr(int attribute) const {
|
||||
int result;
|
||||
RETURN_IF_ERROR(ToStatus(XPRSgetintattrib(xpress_model_, attribute, &result)))
|
||||
<< "Error getting Xpress int attribute: " << attribute;
|
||||
return result;
|
||||
}
|
||||
|
||||
absl::StatusOr<double> Xpress::GetDoubleAttr(int attribute) const {
|
||||
double result;
|
||||
RETURN_IF_ERROR(ToStatus(XPRSgetdblattrib(xpress_model_, attribute, &result)))
|
||||
<< "Error getting Xpress double attribute: " << attribute;
|
||||
return result;
|
||||
}
|
||||
|
||||
int Xpress::GetNumberOfConstraints() const {
|
||||
int n;
|
||||
XPRSgetintattrib(xpress_model_, XPRS_ROWS, &n);
|
||||
return n;
|
||||
}
|
||||
|
||||
int Xpress::GetNumberOfVariables() const {
|
||||
int n;
|
||||
XPRSgetintattrib(xpress_model_, XPRS_COLS, &n);
|
||||
return n;
|
||||
}
|
||||
|
||||
absl::StatusOr<int> Xpress::GetDualStatus() const {
|
||||
int status = 0;
|
||||
double values[1];
|
||||
// Even though we do not need the values, we have to fetch them, otherwise
|
||||
// we'd get a segmentation fault
|
||||
RETURN_IF_ERROR(ToStatus(XPRSgetduals(xpress_model_, &status, values, 0, 0)))
|
||||
<< "Failed to retrieve dual status from XPRESS";
|
||||
return status;
|
||||
}
|
||||
|
||||
absl::Status Xpress::GetBasis(std::vector<int>& rowBasis,
|
||||
std::vector<int>& colBasis) const {
|
||||
rowBasis.resize(GetNumberOfConstraints());
|
||||
colBasis.resize(GetNumberOfVariables());
|
||||
return ToStatus(
|
||||
XPRSgetbasis(xpress_model_, rowBasis.data(), colBasis.data()));
|
||||
}
|
||||
|
||||
absl::Status Xpress::SetStartingBasis(std::vector<int>& rowBasis,
|
||||
std::vector<int>& colBasis) const {
|
||||
if (rowBasis.size() != colBasis.size()) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Row basis and column basis must be of same size.");
|
||||
}
|
||||
return ToStatus(
|
||||
XPRSloadbasis(xpress_model_, rowBasis.data(), colBasis.data()));
|
||||
}
|
||||
|
||||
absl::StatusOr<std::vector<double>> Xpress::GetVarLb() const {
|
||||
int nVars = GetNumberOfVariables();
|
||||
std::vector<double> bounds;
|
||||
bounds.reserve(nVars);
|
||||
RETURN_IF_ERROR(
|
||||
ToStatus(XPRSgetlb(xpress_model_, bounds.data(), 0, nVars - 1)))
|
||||
<< "Failed to retrieve variable LB from XPRESS";
|
||||
return bounds;
|
||||
}
|
||||
absl::StatusOr<std::vector<double>> Xpress::GetVarUb() const {
|
||||
int nVars = GetNumberOfVariables();
|
||||
std::vector<double> bounds;
|
||||
bounds.reserve(nVars);
|
||||
RETURN_IF_ERROR(
|
||||
ToStatus(XPRSgetub(xpress_model_, bounds.data(), 0, nVars - 1)))
|
||||
<< "Failed to retrieve variable UB from XPRESS";
|
||||
return bounds;
|
||||
}
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
133
ortools/math_opt/solvers/xpress/g_xpress.h
Normal file
133
ortools/math_opt/solvers/xpress/g_xpress.h
Normal file
@@ -0,0 +1,133 @@
|
||||
// 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.
|
||||
|
||||
// Google C++ bindings for Xpress C API.
|
||||
//
|
||||
// Attempts to be as close to the Xpress C API as possible, with the following
|
||||
// differences:
|
||||
// * Use destructors to automatically clean up the environment and model.
|
||||
// * Use absl::Status to propagate errors.
|
||||
// * Use absl::StatusOr instead of output arguments.
|
||||
// * Use absl::Span<T> instead of T* and size for array args.
|
||||
// * Use std::string instead of null terminated char* for string values (note
|
||||
// that attribute names are still char*).
|
||||
// * When setting array data, accept const data (absl::Span<const T>).
|
||||
#ifndef OR_TOOLS_MATH_OPT_SOLVERS_XPRESS_G_XPRESS_H_
|
||||
#define OR_TOOLS_MATH_OPT_SOLVERS_XPRESS_G_XPRESS_H_
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/xpress/environment.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
class Xpress {
|
||||
public:
|
||||
Xpress() = delete;
|
||||
|
||||
// Creates a new Xpress
|
||||
static absl::StatusOr<std::unique_ptr<Xpress>> New(
|
||||
const std::string& model_name);
|
||||
absl::Status SetProbName(const std::string& name);
|
||||
|
||||
~Xpress();
|
||||
|
||||
absl::StatusOr<int> GetIntControl(int control) const;
|
||||
absl::Status SetIntControl(int control, int value);
|
||||
absl::Status ResetIntControl(int control); // reset to default value
|
||||
|
||||
absl::StatusOr<int> GetIntAttr(int attribute) const;
|
||||
|
||||
absl::StatusOr<double> GetDoubleAttr(int attribute) const;
|
||||
|
||||
absl::Status AddVars(absl::Span<const double> obj,
|
||||
absl::Span<const double> lb, absl::Span<const double> ub,
|
||||
absl::Span<const char> vtype);
|
||||
|
||||
absl::Status AddVars(absl::Span<const int> vbegin, absl::Span<const int> vind,
|
||||
absl::Span<const double> vval,
|
||||
absl::Span<const double> obj,
|
||||
absl::Span<const double> lb, absl::Span<const double> ub,
|
||||
absl::Span<const char> vtype);
|
||||
|
||||
absl::Status AddConstrs(absl::Span<const char> sense,
|
||||
absl::Span<const double> rhs,
|
||||
absl::Span<const double> rng);
|
||||
absl::Status AddConstrs(absl::Span<const char> rowtype,
|
||||
absl::Span<const double> rhs,
|
||||
absl::Span<const double> rng,
|
||||
absl::Span<const int> start,
|
||||
absl::Span<const int> colind,
|
||||
absl::Span<const double> rowcoef);
|
||||
|
||||
absl::Status SetObjectiveSense(bool maximize);
|
||||
absl::Status SetLinearObjective(double offset, absl::Span<const int> colind,
|
||||
absl::Span<const double> values);
|
||||
absl::Status SetQuadraticObjective(absl::Span<const int> colind1,
|
||||
absl::Span<const int> colind2,
|
||||
absl::Span<const double> coefficients);
|
||||
|
||||
absl::Status ChgCoeffs(absl::Span<const int> cind, absl::Span<const int> vind,
|
||||
absl::Span<const double> val);
|
||||
|
||||
absl::Status LpOptimize(std::string flags);
|
||||
// Fetch LP solution (primals, duals, and reduced costs)
|
||||
// The user is responsible for ensuring that the three vectors are of correct
|
||||
// size (nVars, nCons, and nVars respectively)
|
||||
absl::Status GetLpSol(absl::Span<double> primals, absl::Span<double> duals,
|
||||
absl::Span<double> reducedCosts);
|
||||
absl::Status MipOptimize();
|
||||
absl::Status PostSolve();
|
||||
|
||||
void Terminate();
|
||||
|
||||
absl::StatusOr<int> GetDualStatus() const;
|
||||
absl::Status GetBasis(std::vector<int>& rowBasis,
|
||||
std::vector<int>& colBasis) const;
|
||||
absl::Status SetStartingBasis(std::vector<int>& rowBasis,
|
||||
std::vector<int>& colBasis) const;
|
||||
|
||||
static void XPRS_CC printXpressMessage(XPRSprob prob, void* data,
|
||||
const char* sMsg, int nLen,
|
||||
int nMsgLvl);
|
||||
|
||||
int GetNumberOfConstraints() const;
|
||||
int GetNumberOfVariables() const;
|
||||
|
||||
absl::StatusOr<std::vector<double>> GetVarLb() const;
|
||||
absl::StatusOr<std::vector<double>> GetVarUb() const;
|
||||
|
||||
private:
|
||||
XPRSprob xpress_model_;
|
||||
|
||||
explicit Xpress(XPRSprob& model);
|
||||
|
||||
absl::Status ToStatus(
|
||||
int xprs_err,
|
||||
absl::StatusCode code = absl::StatusCode::kInvalidArgument) const;
|
||||
|
||||
std::map<int, int> int_control_defaults_;
|
||||
void initIntControlDefaults();
|
||||
};
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_SOLVERS_XPRESS_G_XPRESS_H_
|
||||
736
ortools/math_opt/solvers/xpress_solver.cc
Normal file
736
ortools/math_opt/solvers/xpress_solver.cc
Normal file
@@ -0,0 +1,736 @@
|
||||
// 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 "ortools/math_opt/solvers/xpress_solver.h"
|
||||
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "ortools/base/map_util.h"
|
||||
#include "ortools/base/protoutil.h"
|
||||
#include "ortools/base/status_macros.h"
|
||||
#include "ortools/math_opt/core/math_opt_proto_utils.h"
|
||||
#include "ortools/math_opt/core/sparse_vector_view.h"
|
||||
#include "ortools/math_opt/cpp/solve_result.h"
|
||||
#include "ortools/math_opt/validators/callback_validator.h"
|
||||
#include "ortools/port/proto_utils.h"
|
||||
#include "ortools/xpress/environment.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
namespace {
|
||||
|
||||
absl::Status CheckParameters(const SolveParametersProto& parameters) {
|
||||
std::vector<std::string> warnings;
|
||||
if (parameters.has_threads() && parameters.threads() > 1) {
|
||||
warnings.push_back(absl::StrCat(
|
||||
"XpressSolver only supports parameters.threads = 1; value ",
|
||||
parameters.threads(), " is not supported"));
|
||||
}
|
||||
if (parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED &&
|
||||
parameters.lp_algorithm() != LP_ALGORITHM_PRIMAL_SIMPLEX &&
|
||||
parameters.lp_algorithm() != LP_ALGORITHM_DUAL_SIMPLEX &&
|
||||
parameters.lp_algorithm() != LP_ALGORITHM_BARRIER) {
|
||||
warnings.emplace_back(absl::StrCat(
|
||||
"XpressSolver does not support the 'lp_algorithm' parameter value: ",
|
||||
ProtoEnumToString(parameters.lp_algorithm())));
|
||||
}
|
||||
if (parameters.has_objective_limit()) {
|
||||
warnings.emplace_back("XpressSolver does not support objective_limit yet");
|
||||
}
|
||||
if (parameters.has_best_bound_limit()) {
|
||||
warnings.emplace_back("XpressSolver does not support best_bound_limit yet");
|
||||
}
|
||||
if (parameters.has_cutoff_limit()) {
|
||||
warnings.emplace_back("XpressSolver does not support cutoff_limit yet");
|
||||
}
|
||||
if (!warnings.empty()) {
|
||||
return absl::InvalidArgumentError(absl::StrJoin(warnings, "; "));
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
constexpr SupportedProblemStructures kXpressSupportedStructures = {
|
||||
.integer_variables = SupportType::kNotSupported,
|
||||
.multi_objectives = SupportType::kNotSupported,
|
||||
.quadratic_objectives = SupportType::kSupported,
|
||||
.quadratic_constraints = SupportType::kNotSupported,
|
||||
.second_order_cone_constraints = SupportType::kNotSupported,
|
||||
.sos1_constraints = SupportType::kNotSupported,
|
||||
.sos2_constraints = SupportType::kNotSupported,
|
||||
.indicator_constraints = SupportType::kNotSupported};
|
||||
|
||||
absl::StatusOr<std::unique_ptr<XpressSolver>> XpressSolver::New(
|
||||
const ModelProto& input_model, const SolverInterface::InitArgs& init_args) {
|
||||
if (!XpressIsCorrectlyInstalled()) {
|
||||
return absl::InvalidArgumentError("Xpress is not correctly installed.");
|
||||
}
|
||||
RETURN_IF_ERROR(
|
||||
ModelIsSupported(input_model, kXpressSupportedStructures, "XPRESS"));
|
||||
|
||||
// We can add here extra checks that are not made in ModelIsSupported
|
||||
// (for example, if XPRESS does not support multi-objective with quad terms)
|
||||
|
||||
ASSIGN_OR_RETURN(std::unique_ptr<Xpress> xpr,
|
||||
Xpress::New(input_model.name()));
|
||||
auto xpress_solver = absl::WrapUnique(new XpressSolver(std::move(xpr)));
|
||||
RETURN_IF_ERROR(xpress_solver->LoadModel(input_model));
|
||||
return xpress_solver;
|
||||
}
|
||||
|
||||
absl::Status XpressSolver::LoadModel(const ModelProto& input_model) {
|
||||
CHECK(xpress_ != nullptr);
|
||||
RETURN_IF_ERROR(xpress_->SetProbName(input_model.name()));
|
||||
RETURN_IF_ERROR(AddNewVariables(input_model.variables()));
|
||||
RETURN_IF_ERROR(AddNewLinearConstraints(input_model.linear_constraints()));
|
||||
RETURN_IF_ERROR(ChangeCoefficients(input_model.linear_constraint_matrix()));
|
||||
RETURN_IF_ERROR(AddSingleObjective(input_model.objective()));
|
||||
return absl::OkStatus();
|
||||
}
|
||||
absl::Status XpressSolver::AddNewVariables(
|
||||
const VariablesProto& new_variables) {
|
||||
const int num_new_variables = new_variables.lower_bounds().size();
|
||||
std::vector<char> variable_type(num_new_variables);
|
||||
int n_variables = xpress_->GetNumberOfVariables();
|
||||
for (int j = 0; j < num_new_variables; ++j) {
|
||||
const VarId id = new_variables.ids(j);
|
||||
InsertOrDie(&variables_map_, id, j + n_variables);
|
||||
variable_type[j] =
|
||||
new_variables.integers(j) ? XPRS_INTEGER : XPRS_CONTINUOUS;
|
||||
if (new_variables.integers(j)) {
|
||||
is_mip_ = true;
|
||||
return absl::UnimplementedError("XpressSolver does not handle MIPs yet");
|
||||
}
|
||||
}
|
||||
RETURN_IF_ERROR(xpress_->AddVars({}, new_variables.lower_bounds(),
|
||||
new_variables.upper_bounds(),
|
||||
variable_type));
|
||||
|
||||
// Not adding names for performance (have to call XPRSaddnames)
|
||||
// TODO: keep names in a cache and add them when needed
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
XpressSolver::XpressSolver(std::unique_ptr<Xpress> g_xpress)
|
||||
: xpress_(std::move(g_xpress)) {}
|
||||
|
||||
absl::Status XpressSolver::AddNewLinearConstraints(
|
||||
const LinearConstraintsProto& constraints) {
|
||||
// TODO: we might be able to improve performance by setting coefs also
|
||||
const int num_new_constraints = constraints.lower_bounds().size();
|
||||
std::vector<char> constraint_sense;
|
||||
constraint_sense.reserve(num_new_constraints);
|
||||
std::vector<double> constraint_rhs;
|
||||
constraint_rhs.reserve(num_new_constraints);
|
||||
std::vector<double> constraint_rng;
|
||||
constraint_rng.reserve(num_new_constraints);
|
||||
int n_constraints = xpress_->GetNumberOfConstraints();
|
||||
for (int i = 0; i < num_new_constraints; ++i) {
|
||||
const int64_t id = constraints.ids(i);
|
||||
LinearConstraintData& constraint_data =
|
||||
InsertKeyOrDie(&linear_constraints_map_, id);
|
||||
const double lb = constraints.lower_bounds(i);
|
||||
const double ub = constraints.upper_bounds(i);
|
||||
constraint_data.lower_bound = lb;
|
||||
constraint_data.upper_bound = ub;
|
||||
constraint_data.constraint_index = i + n_constraints;
|
||||
char sense = XPRS_EQUAL;
|
||||
double rhs = 0.0;
|
||||
double rng = 0.0;
|
||||
const bool lb_is_xprs_neg_inf = lb <= kMinusInf;
|
||||
const bool ub_is_xprs_pos_inf = ub >= kPlusInf;
|
||||
if (lb_is_xprs_neg_inf && !ub_is_xprs_pos_inf) {
|
||||
sense = XPRS_LESS_EQUAL;
|
||||
rhs = ub;
|
||||
} else if (!lb_is_xprs_neg_inf && ub_is_xprs_pos_inf) {
|
||||
sense = XPRS_GREATER_EQUAL;
|
||||
rhs = lb;
|
||||
} else if (lb == ub) {
|
||||
sense = XPRS_EQUAL;
|
||||
rhs = lb;
|
||||
} else {
|
||||
sense = XPRS_RANGE;
|
||||
rhs = ub;
|
||||
rng = ub - lb;
|
||||
}
|
||||
constraint_sense.emplace_back(sense);
|
||||
constraint_rhs.emplace_back(rhs);
|
||||
constraint_rng.emplace_back(rng);
|
||||
}
|
||||
// Add all constraints in one call.
|
||||
return xpress_->AddConstrs(constraint_sense, constraint_rhs, constraint_rng);
|
||||
}
|
||||
|
||||
absl::Status XpressSolver::AddSingleObjective(const ObjectiveProto& objective) {
|
||||
// Sense
|
||||
RETURN_IF_ERROR(xpress_->SetObjectiveSense(objective.maximize()));
|
||||
is_maximize_ = objective.maximize();
|
||||
// Linear terms
|
||||
std::vector<int> index;
|
||||
index.reserve(objective.linear_coefficients().ids_size());
|
||||
for (const int64_t id : objective.linear_coefficients().ids()) {
|
||||
index.push_back(variables_map_.at(id));
|
||||
}
|
||||
RETURN_IF_ERROR(xpress_->SetLinearObjective(
|
||||
objective.offset(), index, objective.linear_coefficients().values()));
|
||||
// Quadratic terms
|
||||
const int num_terms = objective.quadratic_coefficients().row_ids().size();
|
||||
if (num_terms > 0) {
|
||||
std::vector<int> first_var_index(num_terms);
|
||||
std::vector<int> second_var_index(num_terms);
|
||||
std::vector<double> coefficients(num_terms);
|
||||
for (int k = 0; k < num_terms; ++k) {
|
||||
const int64_t row_id = objective.quadratic_coefficients().row_ids(k);
|
||||
const int64_t column_id =
|
||||
objective.quadratic_coefficients().column_ids(k);
|
||||
first_var_index[k] = variables_map_.at(row_id);
|
||||
second_var_index[k] = variables_map_.at(column_id);
|
||||
// XPRESS supposes a 1/2 implicit multiplier to quadratic terms (see doc)
|
||||
// We have to multiply it by 2 for diagonal terms
|
||||
double m = first_var_index[k] == second_var_index[k] ? 2 : 1;
|
||||
coefficients[k] = objective.quadratic_coefficients().coefficients(k) * m;
|
||||
}
|
||||
RETURN_IF_ERROR(xpress_->SetQuadraticObjective(
|
||||
first_var_index, second_var_index, coefficients));
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status XpressSolver::ChangeCoefficients(
|
||||
const SparseDoubleMatrixProto& matrix) {
|
||||
const int num_coefficients = matrix.row_ids().size();
|
||||
std::vector<int> row_index;
|
||||
row_index.reserve(num_coefficients);
|
||||
std::vector<int> col_index;
|
||||
col_index.reserve(num_coefficients);
|
||||
for (int k = 0; k < num_coefficients; ++k) {
|
||||
row_index.push_back(
|
||||
linear_constraints_map_.at(matrix.row_ids(k)).constraint_index);
|
||||
col_index.push_back(variables_map_.at(matrix.column_ids(k)));
|
||||
}
|
||||
return xpress_->ChgCoeffs(row_index, col_index, matrix.coefficients());
|
||||
}
|
||||
|
||||
absl::StatusOr<SolveResultProto> XpressSolver::Solve(
|
||||
const SolveParametersProto& parameters,
|
||||
const ModelSolveParametersProto& model_parameters,
|
||||
MessageCallback message_cb,
|
||||
const CallbackRegistrationProto& callback_registration, Callback cb,
|
||||
const SolveInterrupter* interrupter) {
|
||||
RETURN_IF_ERROR(ModelSolveParametersAreSupported(
|
||||
model_parameters, kXpressSupportedStructures, "XPRESS"));
|
||||
const absl::Time start = absl::Now();
|
||||
|
||||
RETURN_IF_ERROR(CheckRegisteredCallbackEvents(callback_registration,
|
||||
/*supported_events=*/{}));
|
||||
|
||||
RETURN_IF_ERROR(CheckParameters(parameters));
|
||||
|
||||
// Check that bounds are not inverted just before solve
|
||||
// XPRESS returns "infeasible" when bounds are inverted
|
||||
{
|
||||
ASSIGN_OR_RETURN(const InvertedBounds inv_bounds, ListInvertedBounds());
|
||||
RETURN_IF_ERROR(inv_bounds.ToStatus());
|
||||
}
|
||||
|
||||
// Set initial basis
|
||||
if (model_parameters.has_initial_basis()) {
|
||||
RETURN_IF_ERROR(SetXpressStartingBasis(model_parameters.initial_basis()));
|
||||
}
|
||||
|
||||
RETURN_IF_ERROR(CallXpressSolve(parameters)) << "Error during XPRESS solve";
|
||||
|
||||
ASSIGN_OR_RETURN(
|
||||
SolveResultProto solve_result,
|
||||
ExtractSolveResultProto(start, model_parameters, parameters));
|
||||
|
||||
return solve_result;
|
||||
}
|
||||
|
||||
std::string XpressSolver::GetLpOptimizationFlags(
|
||||
const SolveParametersProto& parameters) {
|
||||
switch (parameters.lp_algorithm()) {
|
||||
case LP_ALGORITHM_PRIMAL_SIMPLEX:
|
||||
lp_algorithm_ = LP_ALGORITHM_PRIMAL_SIMPLEX;
|
||||
return "p";
|
||||
case LP_ALGORITHM_DUAL_SIMPLEX:
|
||||
lp_algorithm_ = LP_ALGORITHM_DUAL_SIMPLEX;
|
||||
return "d";
|
||||
case LP_ALGORITHM_BARRIER:
|
||||
lp_algorithm_ = LP_ALGORITHM_BARRIER;
|
||||
return "b";
|
||||
default:
|
||||
// this makes XPRESS use default algorithm (XPRS_DEFAULTALG)
|
||||
// but we have to figure out what it is for solution processing
|
||||
auto default_alg = xpress_->GetIntControl(XPRS_DEFAULTALG);
|
||||
switch (default_alg.value_or(-1)) {
|
||||
case XPRS_ALG_PRIMAL:
|
||||
lp_algorithm_ = LP_ALGORITHM_PRIMAL_SIMPLEX;
|
||||
break;
|
||||
case XPRS_ALG_DUAL:
|
||||
lp_algorithm_ = LP_ALGORITHM_DUAL_SIMPLEX;
|
||||
break;
|
||||
case XPRS_ALG_BARRIER:
|
||||
lp_algorithm_ = LP_ALGORITHM_BARRIER;
|
||||
break;
|
||||
default:
|
||||
lp_algorithm_ = LP_ALGORITHM_UNSPECIFIED;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
absl::Status XpressSolver::CallXpressSolve(
|
||||
const SolveParametersProto& parameters) {
|
||||
// Enable screen output right before solve
|
||||
if (parameters.enable_output()) {
|
||||
RETURN_IF_ERROR(xpress_->SetIntControl(XPRS_OUTPUTLOG, 1))
|
||||
<< "Unable to enable XPRESS logs";
|
||||
}
|
||||
// Solve
|
||||
if (is_mip_) {
|
||||
RETURN_IF_ERROR(xpress_->MipOptimize());
|
||||
ASSIGN_OR_RETURN(xpress_mip_status_, xpress_->GetIntAttr(XPRS_MIPSTATUS));
|
||||
} else {
|
||||
RETURN_IF_ERROR(SetLpIterLimits(parameters))
|
||||
<< "Could not set iteration limits.";
|
||||
RETURN_IF_ERROR(xpress_->LpOptimize(GetLpOptimizationFlags(parameters)));
|
||||
ASSIGN_OR_RETURN(int primal_status, xpress_->GetIntAttr(XPRS_LPSTATUS));
|
||||
ASSIGN_OR_RETURN(int dual_status, xpress_->GetDualStatus());
|
||||
xpress_lp_status_ = {primal_status, dual_status};
|
||||
}
|
||||
// Post-solve
|
||||
if (!(is_mip_ ? (xpress_mip_status_ == XPRS_MIP_OPTIMAL)
|
||||
: (xpress_lp_status_.primal_status == XPRS_LP_OPTIMAL))) {
|
||||
RETURN_IF_ERROR(xpress_->PostSolve()) << "Post-solve failed in XPRESS";
|
||||
}
|
||||
// Disable screen output right after solve
|
||||
if (parameters.enable_output()) {
|
||||
RETURN_IF_ERROR(xpress_->SetIntControl(XPRS_OUTPUTLOG, 0))
|
||||
<< "Unable to disable XPRESS logs";
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status XpressSolver::SetLpIterLimits(
|
||||
const SolveParametersProto& parameters) {
|
||||
// If the user has set no limits, we still have to reset the limits
|
||||
// explicitly to their default values, else the parameters could be kept
|
||||
// between solves.
|
||||
if (parameters.has_iteration_limit()) {
|
||||
RETURN_IF_ERROR(
|
||||
xpress_->SetIntControl(XPRS_LPITERLIMIT, parameters.iteration_limit()))
|
||||
<< "Could not set XPRS_LPITERLIMIT";
|
||||
RETURN_IF_ERROR(
|
||||
xpress_->SetIntControl(XPRS_BARITERLIMIT, parameters.iteration_limit()))
|
||||
<< "Could not set XPRS_BARITERLIMIT";
|
||||
} else {
|
||||
RETURN_IF_ERROR(xpress_->ResetIntControl(XPRS_LPITERLIMIT))
|
||||
<< "Could not reset XPRS_LPITERLIMIT to its default value";
|
||||
RETURN_IF_ERROR(xpress_->ResetIntControl(XPRS_BARITERLIMIT))
|
||||
<< "Could not reset XPRS_BARITERLIMIT to its default value";
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::StatusOr<SolveResultProto> XpressSolver::ExtractSolveResultProto(
|
||||
absl::Time start, const ModelSolveParametersProto& model_parameters,
|
||||
const SolveParametersProto& solve_parameters) {
|
||||
SolveResultProto result;
|
||||
ASSIGN_OR_RETURN(SolutionProto solution,
|
||||
GetSolution(model_parameters, solve_parameters));
|
||||
*result.add_solutions() = std::move(solution);
|
||||
ASSIGN_OR_RETURN(*result.mutable_solve_stats(), GetSolveStats(start));
|
||||
ASSIGN_OR_RETURN(const double best_primal_bound, GetBestPrimalBound());
|
||||
ASSIGN_OR_RETURN(const double best_dual_bound, GetBestDualBound());
|
||||
ASSIGN_OR_RETURN(
|
||||
*result.mutable_termination(),
|
||||
ConvertTerminationReason(best_primal_bound, best_dual_bound));
|
||||
return result;
|
||||
}
|
||||
|
||||
absl::StatusOr<double> XpressSolver::GetBestPrimalBound() const {
|
||||
if (lp_algorithm_ == LP_ALGORITHM_PRIMAL_SIMPLEX && isPrimalFeasible() ||
|
||||
xpress_lp_status_.primal_status == XPRS_LP_OPTIMAL) {
|
||||
// When primal simplex algorithm is used, XPRESS uses LPOBJVAL to store the
|
||||
// primal problem's objective value
|
||||
return xpress_->GetDoubleAttr(XPRS_LPOBJVAL);
|
||||
}
|
||||
return is_maximize_ ? kMinusInf : kPlusInf;
|
||||
}
|
||||
|
||||
absl::StatusOr<double> XpressSolver::GetBestDualBound() const {
|
||||
if (lp_algorithm_ == LP_ALGORITHM_DUAL_SIMPLEX && isPrimalFeasible() ||
|
||||
xpress_lp_status_.primal_status == XPRS_LP_OPTIMAL) {
|
||||
// When dual simplex algorithm is used, XPRESS uses LPOBJVAL to store the
|
||||
// dual problem's objective value
|
||||
return xpress_->GetDoubleAttr(XPRS_LPOBJVAL);
|
||||
}
|
||||
return is_maximize_ ? kPlusInf : kMinusInf;
|
||||
}
|
||||
|
||||
absl::StatusOr<SolutionProto> XpressSolver::GetSolution(
|
||||
const ModelSolveParametersProto& model_parameters,
|
||||
const SolveParametersProto& solve_parameters) {
|
||||
if (is_mip_) {
|
||||
return absl::UnimplementedError("XpressSolver does not handle MIPs yet");
|
||||
} else {
|
||||
return GetLpSolution(model_parameters, solve_parameters);
|
||||
}
|
||||
}
|
||||
|
||||
absl::StatusOr<SolutionProto> XpressSolver::GetLpSolution(
|
||||
const ModelSolveParametersProto& model_parameters,
|
||||
const SolveParametersProto& solve_parameters) {
|
||||
// Fetch all results from XPRESS
|
||||
int nVars = xpress_->GetNumberOfVariables();
|
||||
int nCons = xpress_->GetNumberOfConstraints();
|
||||
std::vector<double> primals(nVars);
|
||||
std::vector<double> duals(nCons);
|
||||
std::vector<double> reducedCosts(nVars);
|
||||
|
||||
auto hasSolution =
|
||||
xpress_
|
||||
->GetLpSol(absl::MakeSpan(primals), absl::MakeSpan(duals),
|
||||
absl::MakeSpan(reducedCosts))
|
||||
.ok();
|
||||
|
||||
SolutionProto solution{};
|
||||
|
||||
if (isPrimalFeasible()) {
|
||||
// Handle primal solution
|
||||
solution.mutable_primal_solution()->set_feasibility_status(
|
||||
getLpSolutionStatus());
|
||||
ASSIGN_OR_RETURN(const double primalBound, GetBestPrimalBound());
|
||||
solution.mutable_primal_solution()->set_objective_value(primalBound);
|
||||
XpressVectorToSparseDoubleVector(
|
||||
primals, variables_map_,
|
||||
*solution.mutable_primal_solution()->mutable_variable_values(),
|
||||
model_parameters.variable_values_filter());
|
||||
}
|
||||
|
||||
if (hasSolution) {
|
||||
// Add dual solution even if not feasible
|
||||
solution.mutable_dual_solution()->set_feasibility_status(
|
||||
getDualSolutionStatus());
|
||||
ASSIGN_OR_RETURN(const double dualBound, GetBestDualBound());
|
||||
solution.mutable_dual_solution()->set_objective_value(dualBound);
|
||||
XpressVectorToSparseDoubleVector(
|
||||
duals, linear_constraints_map_,
|
||||
*solution.mutable_dual_solution()->mutable_dual_values(),
|
||||
model_parameters.dual_values_filter());
|
||||
XpressVectorToSparseDoubleVector(
|
||||
reducedCosts, variables_map_,
|
||||
*solution.mutable_dual_solution()->mutable_reduced_costs(),
|
||||
model_parameters.reduced_costs_filter());
|
||||
}
|
||||
|
||||
// Get basis
|
||||
ASSIGN_OR_RETURN(auto basis, GetBasisIfAvailable(solve_parameters));
|
||||
if (basis.has_value()) {
|
||||
*solution.mutable_basis() = std::move(*basis);
|
||||
}
|
||||
return solution;
|
||||
}
|
||||
|
||||
bool XpressSolver::isPrimalFeasible() const {
|
||||
if (is_mip_) {
|
||||
return xpress_mip_status_ == XPRS_MIP_OPTIMAL ||
|
||||
xpress_mip_status_ == XPRS_MIP_SOLUTION;
|
||||
} else {
|
||||
return xpress_lp_status_.primal_status == XPRS_LP_OPTIMAL ||
|
||||
xpress_lp_status_.primal_status == XPRS_LP_UNFINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
bool XpressSolver::isDualFeasible() const {
|
||||
if (is_mip_) {
|
||||
return isPrimalFeasible();
|
||||
}
|
||||
return xpress_lp_status_.dual_status == XPRS_SOLSTATUS_OPTIMAL ||
|
||||
xpress_lp_status_.dual_status == XPRS_SOLSTATUS_FEASIBLE ||
|
||||
// When using dual simplex algorithm, if we interrupt it, dual_status
|
||||
// is "not found" even if there is a solution. Using the following
|
||||
// as a workaround for now
|
||||
(lp_algorithm_ == LP_ALGORITHM_DUAL_SIMPLEX && isPrimalFeasible());
|
||||
}
|
||||
|
||||
SolutionStatusProto XpressSolver::getLpSolutionStatus() const {
|
||||
switch (xpress_lp_status_.primal_status) {
|
||||
case XPRS_LP_OPTIMAL:
|
||||
case XPRS_LP_UNFINISHED:
|
||||
return SOLUTION_STATUS_FEASIBLE;
|
||||
case XPRS_LP_INFEAS:
|
||||
case XPRS_LP_CUTOFF:
|
||||
case XPRS_LP_CUTOFF_IN_DUAL:
|
||||
case XPRS_LP_NONCONVEX:
|
||||
return SOLUTION_STATUS_INFEASIBLE;
|
||||
case XPRS_LP_UNSTARTED:
|
||||
case XPRS_LP_UNBOUNDED:
|
||||
case XPRS_LP_UNSOLVED:
|
||||
return SOLUTION_STATUS_UNDETERMINED;
|
||||
default:
|
||||
return SOLUTION_STATUS_UNSPECIFIED;
|
||||
}
|
||||
}
|
||||
|
||||
SolutionStatusProto XpressSolver::getDualSolutionStatus() const {
|
||||
// When using dual simplex algorithm, if we interrupt it, dual_status
|
||||
// is "not found" even if there is a solution. Using the following
|
||||
// as a workaround for now
|
||||
if (isDualFeasible()) {
|
||||
return SOLUTION_STATUS_FEASIBLE;
|
||||
}
|
||||
switch (xpress_lp_status_.dual_status) {
|
||||
case XPRS_SOLSTATUS_OPTIMAL:
|
||||
case XPRS_SOLSTATUS_FEASIBLE:
|
||||
return SOLUTION_STATUS_FEASIBLE;
|
||||
case XPRS_SOLSTATUS_INFEASIBLE:
|
||||
return SOLUTION_STATUS_INFEASIBLE;
|
||||
case XPRS_SOLSTATUS_UNBOUNDED:
|
||||
// when primal is unbounded, XPRESS returns unbounded for dual also (known
|
||||
// issue). this is a temporary workaround
|
||||
return (xpress_lp_status_.primal_status == XPRS_LP_UNBOUNDED)
|
||||
? SOLUTION_STATUS_INFEASIBLE
|
||||
: SOLUTION_STATUS_UNDETERMINED;
|
||||
case XPRS_SOLSTATUS_NOTFOUND:
|
||||
return SOLUTION_STATUS_UNDETERMINED;
|
||||
default:
|
||||
return SOLUTION_STATUS_UNSPECIFIED;
|
||||
}
|
||||
}
|
||||
|
||||
inline BasisStatusProto XpressToMathOptBasisStatus(const int status,
|
||||
bool isConstraint) {
|
||||
// XPRESS row basis status is that of the slack variable
|
||||
// For example, if the slack variable is at LB, the constraint is at UB
|
||||
switch (status) {
|
||||
case XPRS_BASIC:
|
||||
return BASIS_STATUS_BASIC;
|
||||
case XPRS_AT_LOWER:
|
||||
return isConstraint ? BASIS_STATUS_AT_UPPER_BOUND
|
||||
: BASIS_STATUS_AT_LOWER_BOUND;
|
||||
case XPRS_AT_UPPER:
|
||||
return isConstraint ? BASIS_STATUS_AT_LOWER_BOUND
|
||||
: BASIS_STATUS_AT_UPPER_BOUND;
|
||||
case XPRS_FREE_SUPER:
|
||||
return BASIS_STATUS_FREE;
|
||||
default:
|
||||
return BASIS_STATUS_UNSPECIFIED;
|
||||
}
|
||||
}
|
||||
|
||||
inline int MathOptToXpressBasisStatus(const BasisStatusProto status,
|
||||
bool isConstraint) {
|
||||
// XPRESS row basis status is that of the slack variable
|
||||
// For example, if the slack variable is at LB, the constraint is at UB
|
||||
switch (status) {
|
||||
case BASIS_STATUS_BASIC:
|
||||
return XPRS_BASIC;
|
||||
case BASIS_STATUS_AT_LOWER_BOUND:
|
||||
return isConstraint ? XPRS_AT_UPPER : XPRS_AT_LOWER;
|
||||
case BASIS_STATUS_AT_UPPER_BOUND:
|
||||
return isConstraint ? XPRS_AT_LOWER : XPRS_AT_UPPER;
|
||||
case BASIS_STATUS_FREE:
|
||||
return XPRS_FREE_SUPER;
|
||||
default:
|
||||
return XPRS_FREE_SUPER;
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status XpressSolver::SetXpressStartingBasis(const BasisProto& basis) {
|
||||
std::vector<int> xpress_var_basis_status(xpress_->GetNumberOfVariables());
|
||||
for (const auto [id, value] : MakeView(basis.variable_status())) {
|
||||
xpress_var_basis_status[variables_map_.at(id)] =
|
||||
MathOptToXpressBasisStatus(static_cast<BasisStatusProto>(value), false);
|
||||
}
|
||||
std::vector<int> xpress_constr_basis_status(
|
||||
xpress_->GetNumberOfConstraints());
|
||||
for (const auto [id, value] : MakeView(basis.constraint_status())) {
|
||||
xpress_constr_basis_status[linear_constraints_map_.at(id)
|
||||
.constraint_index] =
|
||||
MathOptToXpressBasisStatus(static_cast<BasisStatusProto>(value), true);
|
||||
}
|
||||
return xpress_->SetStartingBasis(xpress_constr_basis_status,
|
||||
xpress_var_basis_status);
|
||||
}
|
||||
|
||||
absl::StatusOr<std::optional<BasisProto>> XpressSolver::GetBasisIfAvailable(
|
||||
const SolveParametersProto& parameters) {
|
||||
std::vector<int> xprs_variable_basis_status;
|
||||
std::vector<int> xprs_constraint_basis_status;
|
||||
if (!xpress_
|
||||
->GetBasis(xprs_constraint_basis_status, xprs_variable_basis_status)
|
||||
.ok()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
BasisProto basis;
|
||||
// Variable basis
|
||||
for (auto [variable_id, xprs_variable_index] : variables_map_) {
|
||||
basis.mutable_variable_status()->add_ids(variable_id);
|
||||
const BasisStatusProto variable_status = XpressToMathOptBasisStatus(
|
||||
xprs_variable_basis_status[xprs_variable_index], false);
|
||||
if (variable_status == BASIS_STATUS_UNSPECIFIED) {
|
||||
return absl::InternalError(
|
||||
absl::StrCat("Invalid Xpress variable basis status: ",
|
||||
xprs_variable_basis_status[xprs_variable_index]));
|
||||
}
|
||||
basis.mutable_variable_status()->add_values(variable_status);
|
||||
}
|
||||
|
||||
// Constraint basis
|
||||
for (auto [constraint_id, xprs_ct_index] : linear_constraints_map_) {
|
||||
basis.mutable_constraint_status()->add_ids(constraint_id);
|
||||
const BasisStatusProto status = XpressToMathOptBasisStatus(
|
||||
xprs_constraint_basis_status[xprs_ct_index.constraint_index], true);
|
||||
if (status == BASIS_STATUS_UNSPECIFIED) {
|
||||
return absl::InternalError(absl::StrCat(
|
||||
"Invalid Xpress constraint basis status: ",
|
||||
xprs_constraint_basis_status[xprs_ct_index.constraint_index]));
|
||||
}
|
||||
basis.mutable_constraint_status()->add_values(status);
|
||||
}
|
||||
|
||||
// Dual basis
|
||||
basis.set_basic_dual_feasibility(
|
||||
isDualFeasible() ? SOLUTION_STATUS_FEASIBLE : SOLUTION_STATUS_INFEASIBLE);
|
||||
return basis;
|
||||
}
|
||||
|
||||
absl::StatusOr<SolveStatsProto> XpressSolver::GetSolveStats(
|
||||
absl::Time start) const {
|
||||
SolveStatsProto solve_stats;
|
||||
CHECK_OK(util_time::EncodeGoogleApiProto(absl::Now() - start,
|
||||
solve_stats.mutable_solve_time()));
|
||||
|
||||
// LP simplex iterations
|
||||
{
|
||||
ASSIGN_OR_RETURN(const int iters, xpress_->GetIntAttr(XPRS_SIMPLEXITER));
|
||||
solve_stats.set_simplex_iterations(iters);
|
||||
}
|
||||
// LP barrier iterations
|
||||
{
|
||||
ASSIGN_OR_RETURN(const int iters, xpress_->GetIntAttr(XPRS_BARITER));
|
||||
solve_stats.set_barrier_iterations(iters);
|
||||
}
|
||||
|
||||
// TODO: complete these stats
|
||||
return solve_stats;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void XpressSolver::XpressVectorToSparseDoubleVector(
|
||||
absl::Span<const double> xpress_values, const T& map,
|
||||
SparseDoubleVectorProto& result,
|
||||
const SparseVectorFilterProto& filter) const {
|
||||
SparseVectorFilterPredicate predicate(filter);
|
||||
for (auto [id, xpress_data] : map) {
|
||||
const double value = xpress_values[get_model_index(xpress_data)];
|
||||
if (predicate.AcceptsAndUpdate(id, value)) {
|
||||
result.add_ids(id);
|
||||
result.add_values(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
absl::StatusOr<TerminationProto> XpressSolver::ConvertTerminationReason(
|
||||
double best_primal_bound, double best_dual_bound) const {
|
||||
if (!is_mip_) {
|
||||
switch (xpress_lp_status_.primal_status) {
|
||||
case XPRS_LP_UNSTARTED:
|
||||
return TerminateForReason(
|
||||
is_maximize_, TERMINATION_REASON_OTHER_ERROR,
|
||||
"Problem solve has not started (XPRS_LP_UNSTARTED)");
|
||||
case XPRS_LP_OPTIMAL:
|
||||
return OptimalTerminationProto(best_primal_bound, best_dual_bound);
|
||||
case XPRS_LP_INFEAS:
|
||||
return InfeasibleTerminationProto(
|
||||
is_maximize_, isDualFeasible() ? FEASIBILITY_STATUS_FEASIBLE
|
||||
: FEASIBILITY_STATUS_UNDETERMINED);
|
||||
case XPRS_LP_CUTOFF:
|
||||
return CutoffTerminationProto(
|
||||
is_maximize_, "Objective worse than cutoff (XPRS_LP_CUTOFF)");
|
||||
case XPRS_LP_UNFINISHED:
|
||||
// TODO: add support for more limit types here (this only works for LP
|
||||
// iterations limit for now)
|
||||
return FeasibleTerminationProto(
|
||||
is_maximize_, LIMIT_ITERATION, best_primal_bound, best_dual_bound,
|
||||
"Solve did not finish (XPRS_LP_UNFINISHED)");
|
||||
case XPRS_LP_UNBOUNDED:
|
||||
return UnboundedTerminationProto(is_maximize_,
|
||||
"Xpress status XPRS_LP_UNBOUNDED");
|
||||
case XPRS_LP_CUTOFF_IN_DUAL:
|
||||
return CutoffTerminationProto(
|
||||
is_maximize_, "Cutoff in dual (XPRS_LP_CUTOFF_IN_DUAL)");
|
||||
case XPRS_LP_UNSOLVED:
|
||||
return TerminateForReason(
|
||||
is_maximize_, TERMINATION_REASON_NUMERICAL_ERROR,
|
||||
"Problem could not be solved due to numerical issues "
|
||||
"(XPRS_LP_UNSOLVED)");
|
||||
case XPRS_LP_NONCONVEX:
|
||||
return TerminateForReason(is_maximize_, TERMINATION_REASON_OTHER_ERROR,
|
||||
"Problem contains quadratic data, which is "
|
||||
"not convex (XPRS_LP_NONCONVEX)");
|
||||
default:
|
||||
return absl::InternalError(
|
||||
absl::StrCat("Missing Xpress LP status code case: ",
|
||||
xpress_lp_status_.primal_status));
|
||||
}
|
||||
} else {
|
||||
return absl::UnimplementedError("XpressSolver does not handle MIPs yet");
|
||||
}
|
||||
}
|
||||
|
||||
absl::StatusOr<bool> XpressSolver::Update(
|
||||
const ModelUpdateProto& model_update) {
|
||||
// Not implemented yet
|
||||
return false;
|
||||
}
|
||||
|
||||
absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
|
||||
XpressSolver::ComputeInfeasibleSubsystem(const SolveParametersProto& parameters,
|
||||
MessageCallback message_cb,
|
||||
const SolveInterrupter* interrupter) {
|
||||
return absl::UnimplementedError(
|
||||
"XpressSolver cannot compute infeasible subsystem yet");
|
||||
}
|
||||
|
||||
absl::StatusOr<InvertedBounds> XpressSolver::ListInvertedBounds() const {
|
||||
InvertedBounds inverted_bounds;
|
||||
{
|
||||
ASSIGN_OR_RETURN(const std::vector<double> var_lbs, xpress_->GetVarLb());
|
||||
ASSIGN_OR_RETURN(const std::vector<double> var_ubs, xpress_->GetVarUb());
|
||||
for (const auto& [id, index] : variables_map_) {
|
||||
if (var_lbs[index] > var_ubs[index]) {
|
||||
inverted_bounds.variables.push_back(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
// We could have used XPRSgetrhsrange to check if one is negative. However,
|
||||
// XPRSaddrows ignores the sign of the RHS range and takes the absolute value
|
||||
// in all cases. So we need to do the following checks on the internal model.
|
||||
for (const auto& [id, cstr_data] : linear_constraints_map_) {
|
||||
if (cstr_data.lower_bound > cstr_data.upper_bound) {
|
||||
inverted_bounds.linear_constraints.push_back(id);
|
||||
}
|
||||
}
|
||||
// Above code have inserted ids in non-stable order.
|
||||
std::sort(inverted_bounds.variables.begin(), inverted_bounds.variables.end());
|
||||
std::sort(inverted_bounds.linear_constraints.begin(),
|
||||
inverted_bounds.linear_constraints.end());
|
||||
return inverted_bounds;
|
||||
}
|
||||
|
||||
MATH_OPT_REGISTER_SOLVER(SOLVER_TYPE_XPRESS, XpressSolver::New)
|
||||
} // namespace math_opt
|
||||
} // namespace operations_research
|
||||
185
ortools/math_opt/solvers/xpress_solver.h
Normal file
185
ortools/math_opt/solvers/xpress_solver.h
Normal file
@@ -0,0 +1,185 @@
|
||||
// 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.
|
||||
|
||||
#ifndef OR_TOOLS_MATH_OPT_SOLVERS_XPRESS_SOLVER_H_
|
||||
#define OR_TOOLS_MATH_OPT_SOLVERS_XPRESS_SOLVER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/base/linked_hash_map.h"
|
||||
#include "ortools/math_opt/callback.pb.h"
|
||||
#include "ortools/math_opt/core/inverted_bounds.h"
|
||||
#include "ortools/math_opt/core/solver_interface.h"
|
||||
#include "ortools/math_opt/infeasible_subsystem.pb.h"
|
||||
#include "ortools/math_opt/model.pb.h"
|
||||
#include "ortools/math_opt/model_parameters.pb.h"
|
||||
#include "ortools/math_opt/model_update.pb.h"
|
||||
#include "ortools/math_opt/parameters.pb.h"
|
||||
#include "ortools/math_opt/result.pb.h"
|
||||
#include "ortools/math_opt/solution.pb.h"
|
||||
#include "ortools/math_opt/solvers/xpress/g_xpress.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
#include "ortools/util/solve_interrupter.h"
|
||||
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
// Interface to FICO XPRESS solver
|
||||
// Largely inspired by the Gurobi interface
|
||||
class XpressSolver : public SolverInterface {
|
||||
public:
|
||||
// Creates the XPRESS solver and loads the model into it
|
||||
static absl::StatusOr<std::unique_ptr<XpressSolver>> New(
|
||||
const ModelProto& input_model,
|
||||
const SolverInterface::InitArgs& init_args);
|
||||
|
||||
// Solves the optimization problem
|
||||
absl::StatusOr<SolveResultProto> Solve(
|
||||
const SolveParametersProto& parameters,
|
||||
const ModelSolveParametersProto& model_parameters,
|
||||
MessageCallback message_cb,
|
||||
const CallbackRegistrationProto& callback_registration, Callback cb,
|
||||
const SolveInterrupter* interrupter) override;
|
||||
|
||||
// Updates the problem (not implemented yet)
|
||||
absl::StatusOr<bool> Update(const ModelUpdateProto& model_update) override;
|
||||
|
||||
// Computes the infeasible subsystem (not implemented yet)
|
||||
absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
|
||||
ComputeInfeasibleSubsystem(const SolveParametersProto& parameters,
|
||||
MessageCallback message_cb,
|
||||
const SolveInterrupter* interrupter) override;
|
||||
|
||||
private:
|
||||
explicit XpressSolver(std::unique_ptr<Xpress> g_xpress);
|
||||
|
||||
// For easing reading the code, we declare these types:
|
||||
using VarId = int64_t;
|
||||
using AuxiliaryObjectiveId = int64_t;
|
||||
using LinearConstraintId = int64_t;
|
||||
using QuadraticConstraintId = int64_t;
|
||||
using SecondOrderConeConstraintId = int64_t;
|
||||
using Sos1ConstraintId = int64_t;
|
||||
using Sos2ConstraintId = int64_t;
|
||||
using IndicatorConstraintId = int64_t;
|
||||
using AnyConstraintId = int64_t;
|
||||
using XpressVariableIndex = int;
|
||||
using XpressMultiObjectiveIndex = int;
|
||||
using XpressLinearConstraintIndex = int;
|
||||
using XpressQuadraticConstraintIndex = int;
|
||||
using XpressSosConstraintIndex = int;
|
||||
using XpressGeneralConstraintIndex = int;
|
||||
using XpressAnyConstraintIndex = int;
|
||||
|
||||
static constexpr XpressVariableIndex kUnspecifiedIndex = -1;
|
||||
static constexpr XpressAnyConstraintIndex kUnspecifiedConstraint = -2;
|
||||
static constexpr double kPlusInf = XPRS_PLUSINFINITY;
|
||||
static constexpr double kMinusInf = XPRS_MINUSINFINITY;
|
||||
|
||||
static bool isFinite(double value) {
|
||||
return value < kPlusInf && value > kMinusInf;
|
||||
}
|
||||
|
||||
// Data associated with each linear constraint
|
||||
struct LinearConstraintData {
|
||||
XpressLinearConstraintIndex constraint_index = kUnspecifiedConstraint;
|
||||
double lower_bound = kMinusInf;
|
||||
double upper_bound = kPlusInf;
|
||||
};
|
||||
|
||||
absl::StatusOr<SolveResultProto> ExtractSolveResultProto(
|
||||
absl::Time start, const ModelSolveParametersProto& model_parameters,
|
||||
const SolveParametersProto& solve_parameters);
|
||||
absl::StatusOr<SolutionProto> GetSolution(
|
||||
const ModelSolveParametersProto& model_parameters,
|
||||
const SolveParametersProto& solve_parameters);
|
||||
absl::StatusOr<SolveStatsProto> GetSolveStats(absl::Time start) const;
|
||||
|
||||
absl::StatusOr<double> GetBestPrimalBound() const;
|
||||
absl::StatusOr<double> GetBestDualBound() const;
|
||||
|
||||
absl::StatusOr<TerminationProto> ConvertTerminationReason(
|
||||
double best_primal_bound, double best_dual_bound) const;
|
||||
|
||||
absl::StatusOr<SolutionProto> GetLpSolution(
|
||||
const ModelSolveParametersProto& model_parameters,
|
||||
const SolveParametersProto& solve_parameters);
|
||||
bool isPrimalFeasible() const;
|
||||
bool isDualFeasible() const;
|
||||
|
||||
absl::StatusOr<std::optional<BasisProto>> GetBasisIfAvailable(
|
||||
const SolveParametersProto& parameters);
|
||||
|
||||
absl::Status AddNewLinearConstraints(const LinearConstraintsProto& cts);
|
||||
absl::Status AddNewVariables(const VariablesProto& new_variables);
|
||||
absl::Status AddSingleObjective(const ObjectiveProto& objective);
|
||||
absl::Status ChangeCoefficients(const SparseDoubleMatrixProto& matrix);
|
||||
|
||||
absl::Status LoadModel(const ModelProto& input_model);
|
||||
|
||||
std::string GetLpOptimizationFlags(const SolveParametersProto& parameters);
|
||||
absl::Status CallXpressSolve(const SolveParametersProto& parameters);
|
||||
|
||||
// Fills in result with the values in xpress_values aided by the index
|
||||
// conversion from map which should be either variables_map_ or
|
||||
// linear_constraints_map_ as appropriate. Only key/value pairs that passes
|
||||
// the filter predicate are added.
|
||||
template <typename T>
|
||||
void XpressVectorToSparseDoubleVector(
|
||||
absl::Span<const double> xpress_values, const T& map,
|
||||
SparseDoubleVectorProto& result,
|
||||
const SparseVectorFilterProto& filter) const;
|
||||
|
||||
const std::unique_ptr<Xpress> xpress_;
|
||||
|
||||
// Internal correspondence from variable proto IDs to Xpress-numbered
|
||||
// variables.
|
||||
gtl::linked_hash_map<VarId, XpressVariableIndex> variables_map_;
|
||||
// Internal correspondence from linear constraint proto IDs to
|
||||
// Xpress-numbered linear constraint and extra information.
|
||||
gtl::linked_hash_map<LinearConstraintId, LinearConstraintData>
|
||||
linear_constraints_map_;
|
||||
|
||||
int get_model_index(XpressVariableIndex index) const { return index; }
|
||||
int get_model_index(const LinearConstraintData& index) const {
|
||||
return index.constraint_index;
|
||||
}
|
||||
SolutionStatusProto getLpSolutionStatus() const;
|
||||
SolutionStatusProto getDualSolutionStatus() const;
|
||||
absl::StatusOr<InvertedBounds> ListInvertedBounds() const;
|
||||
absl::Status SetXpressStartingBasis(const BasisProto& basis);
|
||||
absl::Status SetLpIterLimits(const SolveParametersProto& parameters);
|
||||
|
||||
bool is_mip_ = false;
|
||||
bool is_maximize_ = false;
|
||||
|
||||
struct LpStatus {
|
||||
int primal_status = 0;
|
||||
int dual_status = 0;
|
||||
};
|
||||
LpStatus xpress_lp_status_;
|
||||
LPAlgorithmProto lp_algorithm_ = LP_ALGORITHM_UNSPECIFIED;
|
||||
|
||||
int xpress_mip_status_ = 0;
|
||||
};
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_SOLVERS_XPRESS_SOLVER_H_
|
||||
246
ortools/math_opt/solvers/xpress_solver_test.cc
Normal file
246
ortools/math_opt/solvers/xpress_solver_test.cc
Normal file
@@ -0,0 +1,246 @@
|
||||
// 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 <limits>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "ortools/math_opt/cpp/math_opt.h"
|
||||
#include "ortools/math_opt/solver_tests/callback_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/generic_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/infeasible_subsystem_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/invalid_input_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/logical_constraint_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/lp_incomplete_solve_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/lp_model_solve_parameters_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/lp_parameter_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/lp_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/multi_objective_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/qc_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/qp_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/second_order_cone_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/status_tests.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
namespace {
|
||||
using testing::ValuesIn;
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressSolverLpTest, SimpleLpTest,
|
||||
testing::Values(SimpleLpTestParameters(
|
||||
SolverType::kXpress, SolveParameters(), /*supports_duals=*/true,
|
||||
/*supports_basis=*/true,
|
||||
/*ensures_primal_ray=*/false, /*ensures_dual_ray=*/false,
|
||||
/*disallows_infeasible_or_unbounded=*/true)));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressLpModelSolveParametersTest,
|
||||
LpModelSolveParametersTest,
|
||||
testing::Values(LpModelSolveParametersTestParameters(
|
||||
SolverType::kXpress, /*exact_zeros=*/true,
|
||||
/*supports_duals=*/true,
|
||||
/*supports_primal_only_warm_starts=*/false)));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressLpParameterTest, LpParameterTest,
|
||||
testing::Values(LpParameterTestParams(SolverType::kXpress,
|
||||
/*supports_simplex=*/true,
|
||||
/*supports_barrier=*/true,
|
||||
/*supports_first_order=*/false,
|
||||
/*supports_random_seed=*/false,
|
||||
/*supports_presolve=*/false,
|
||||
/*supports_cutoff=*/false,
|
||||
/*supports_objective_limit=*/false,
|
||||
/*supports_best_bound_limit=*/false,
|
||||
/*reports_limits=*/false)));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressPrimalSimplexLpIncompleteSolveTest, LpIncompleteSolveTest,
|
||||
testing::Values(LpIncompleteSolveTestParams(
|
||||
SolverType::kXpress,
|
||||
/*lp_algorithm=*/LPAlgorithm::kPrimalSimplex,
|
||||
/*supports_iteration_limit=*/true, /*supports_initial_basis=*/false,
|
||||
/*supports_incremental_solve=*/false, /*supports_basis=*/true,
|
||||
/*supports_presolve=*/false, /*check_primal_objective=*/true,
|
||||
/*primal_solution_status_always_set=*/true,
|
||||
/*dual_solution_status_always_set=*/true)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressDualSimplexLpIncompleteSolveTest, LpIncompleteSolveTest,
|
||||
testing::Values(LpIncompleteSolveTestParams(
|
||||
SolverType::kXpress,
|
||||
/*lp_algorithm=*/LPAlgorithm::kDualSimplex,
|
||||
/*supports_iteration_limit=*/true, /*supports_initial_basis=*/false,
|
||||
/*supports_incremental_solve=*/false, /*supports_basis=*/true,
|
||||
/*supports_presolve=*/false, /*check_primal_objective=*/true,
|
||||
/*primal_solution_status_always_set=*/true,
|
||||
/*dual_solution_status_always_set=*/true)));
|
||||
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(IncrementalLpTest);
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressMessageCallbackTest, MessageCallbackTest,
|
||||
testing::Values(MessageCallbackTestParams(
|
||||
SolverType::kXpress,
|
||||
/*support_message_callback=*/false,
|
||||
/*support_interrupter=*/false,
|
||||
/*integer_variables=*/false, "")));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressCallbackTest, CallbackTest,
|
||||
testing::Values(CallbackTestParams(SolverType::kXpress,
|
||||
/*integer_variables=*/false,
|
||||
/*add_lazy_constraints=*/false,
|
||||
/*add_cuts=*/false,
|
||||
/*supported_events=*/{},
|
||||
/*all_solutions=*/std::nullopt,
|
||||
/*reaches_cut_callback*/ std::nullopt)));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressInvalidInputTest, InvalidInputTest,
|
||||
testing::Values(InvalidInputTestParameters(
|
||||
SolverType::kXpress,
|
||||
/*use_integer_variables=*/false)));
|
||||
|
||||
InvalidParameterTestParams InvalidThreadsParameters() {
|
||||
SolveParameters params;
|
||||
params.threads = 2;
|
||||
return InvalidParameterTestParams(SolverType::kXpress, std::move(params),
|
||||
{"only supports parameters.threads = 1"});
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressInvalidParameterTest, InvalidParameterTest,
|
||||
ValuesIn({InvalidThreadsParameters()}));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressGenericTest, GenericTest,
|
||||
testing::Values(GenericTestParameters(
|
||||
SolverType::kXpress, /*support_interrupter=*/false,
|
||||
/*integer_variables=*/false,
|
||||
/*expected_log=*/"Optimal solution found")));
|
||||
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TimeLimitTest);
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressInfeasibleSubsystemTest, InfeasibleSubsystemTest,
|
||||
testing::Values(InfeasibleSubsystemTestParameters(
|
||||
{.solver_type = SolverType::kXpress,
|
||||
.support_menu = {
|
||||
.supports_infeasible_subsystems = false}})));
|
||||
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(IpModelSolveParametersTest);
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(IpParameterTest);
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(LargeInstanceIpParameterTest);
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SimpleMipTest);
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(IncrementalMipTest);
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MipSolutionHintTest);
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BranchPrioritiesTest);
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(LazyConstraintsTest);
|
||||
|
||||
LogicalConstraintTestParameters GetXpressLogicalConstraintTestParameters() {
|
||||
return LogicalConstraintTestParameters(
|
||||
SolverType::kXpress, SolveParameters(),
|
||||
/*supports_integer_variables=*/false,
|
||||
/*supports_sos1=*/false,
|
||||
/*supports_sos2=*/false,
|
||||
/*supports_indicator_constraints=*/false,
|
||||
/*supports_incremental_add_and_deletes=*/false,
|
||||
/*supports_incremental_variable_deletions=*/false,
|
||||
/*supports_deleting_indicator_variables=*/false,
|
||||
/*supports_updating_binary_variables=*/false);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressSimpleLogicalConstraintTest, SimpleLogicalConstraintTest,
|
||||
testing::Values(GetXpressLogicalConstraintTestParameters()));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressIncrementalLogicalConstraintTest, IncrementalLogicalConstraintTest,
|
||||
testing::Values(GetXpressLogicalConstraintTestParameters()));
|
||||
|
||||
MultiObjectiveTestParameters GetXpressMultiObjectiveTestParameters() {
|
||||
return MultiObjectiveTestParameters(
|
||||
/*solver_type=*/SolverType::kXpress, /*parameters=*/SolveParameters(),
|
||||
/*supports_auxiliary_objectives=*/false,
|
||||
/*supports_incremental_objective_add_and_delete=*/false,
|
||||
/*supports_incremental_objective_modification=*/false,
|
||||
/*supports_integer_variables=*/false);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressSimpleMultiObjectiveTest, SimpleMultiObjectiveTest,
|
||||
testing::Values(GetXpressMultiObjectiveTestParameters()));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressIncrementalMultiObjectiveTest, IncrementalMultiObjectiveTest,
|
||||
testing::Values(GetXpressMultiObjectiveTestParameters()));
|
||||
|
||||
QpTestParameters GetXpressQpTestParameters() {
|
||||
return QpTestParameters(SolverType::kXpress, SolveParameters(),
|
||||
/*qp_support=*/QpSupportType::kConvexQp,
|
||||
/*supports_incrementalism_not_modifying_qp=*/false,
|
||||
/*supports_qp_incrementalism=*/false,
|
||||
/*use_integer_variables=*/false);
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(XpressSimpleQpTest, SimpleQpTest,
|
||||
testing::Values(GetXpressQpTestParameters()));
|
||||
INSTANTIATE_TEST_SUITE_P(XpressIncrementalQpTest, IncrementalQpTest,
|
||||
testing::Values(GetXpressQpTestParameters()));
|
||||
INSTANTIATE_TEST_SUITE_P(XpressQpDualsTest, QpDualsTest,
|
||||
testing::Values(GetXpressQpTestParameters()));
|
||||
|
||||
QcTestParameters GetXpressQcTestParameters() {
|
||||
return QcTestParameters(SolverType::kXpress, SolveParameters(),
|
||||
/*supports_qc=*/false,
|
||||
/*supports_incremental_add_and_deletes=*/false,
|
||||
/*supports_incremental_variable_deletions=*/false,
|
||||
/*use_integer_variables=*/false);
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(XpressSimpleQcTest, SimpleQcTest,
|
||||
testing::Values(GetXpressQcTestParameters()));
|
||||
INSTANTIATE_TEST_SUITE_P(XpressIncrementalQcTest, IncrementalQcTest,
|
||||
testing::Values(GetXpressQcTestParameters()));
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(QcDualsTest);
|
||||
|
||||
SecondOrderConeTestParameters GetXpressSecondOrderConeTestParameters() {
|
||||
return SecondOrderConeTestParameters(
|
||||
SolverType::kXpress, SolveParameters(),
|
||||
/*supports_soc_constraints=*/false,
|
||||
/*supports_incremental_add_and_deletes=*/false);
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressSimpleSecondOrderConeTest, SimpleSecondOrderConeTest,
|
||||
testing::Values(GetXpressSecondOrderConeTestParameters()));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressIncrementalSecondOrderConeTest, IncrementalSecondOrderConeTest,
|
||||
testing::Values(GetXpressSecondOrderConeTestParameters()));
|
||||
|
||||
std::vector<StatusTestParameters> MakeStatusTestConfigs() {
|
||||
std::vector<StatusTestParameters> test_parameters;
|
||||
for (const auto algorithm : std::vector<std::optional<LPAlgorithm>>(
|
||||
{std::nullopt, LPAlgorithm::kBarrier, LPAlgorithm::kPrimalSimplex,
|
||||
LPAlgorithm::kDualSimplex})) {
|
||||
SolveParameters solve_parameters = {.lp_algorithm = algorithm};
|
||||
test_parameters.push_back(StatusTestParameters(
|
||||
SolverType::kXpress, solve_parameters,
|
||||
/*disallow_primal_or_dual_infeasible=*/false,
|
||||
/*supports_iteration_limit=*/false,
|
||||
/*use_integer_variables=*/false,
|
||||
/*supports_node_limit=*/false,
|
||||
/*support_interrupter=*/false, /*supports_one_thread=*/true));
|
||||
}
|
||||
return test_parameters;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressStatusTest, StatusTest,
|
||||
ValuesIn(MakeStatusTestConfigs()));
|
||||
|
||||
} // namespace
|
||||
} // namespace math_opt
|
||||
} // namespace operations_research
|
||||
@@ -99,6 +99,12 @@ enum SolverTypeProto {
|
||||
// Slow/not recommended for production. Not an LP solver (no dual information
|
||||
// returned).
|
||||
SOLVER_TYPE_SANTORINI = 11;
|
||||
|
||||
// Fico XPRESS solver (third party).
|
||||
//
|
||||
// Supports LP, MIP, and nonconvex integer quadratic problems.
|
||||
// A fast option, but has special licensing.
|
||||
SOLVER_TYPE_XPRESS = 12;
|
||||
}
|
||||
|
||||
// Selects an algorithm for solving linear programs.
|
||||
|
||||
@@ -48,6 +48,7 @@ std::function<int(char* buffer, int maxbytes)> XPRSgetlicerrmsg = nullptr;
|
||||
std::function<int(int* p_i, char* p_c)> XPRSlicense = nullptr;
|
||||
std::function<int(char* banner)> XPRSgetbanner = nullptr;
|
||||
std::function<int(char* version)> XPRSgetversion = nullptr;
|
||||
std::function<int(XPRSprob prob, const char* probname)> XPRSsetprobname = nullptr;
|
||||
std::function<int(XPRSprob prob, int control)> XPRSsetdefaultcontrol = nullptr;
|
||||
std::function<int(XPRSprob prob, int reason)> XPRSinterrupt = nullptr;
|
||||
std::function<int(XPRSprob prob, int control, int value)> XPRSsetintcontrol = nullptr;
|
||||
@@ -69,6 +70,8 @@ std::function<int(XPRSprob prob, double rng[], int first, int last)> XPRSgetrhsr
|
||||
std::function<int(XPRSprob prob, double lb[], int first, int last)> XPRSgetlb = nullptr;
|
||||
std::function<int(XPRSprob prob, double ub[], int first, int last)> XPRSgetub = nullptr;
|
||||
std::function<int(XPRSprob prob, int row, int col, double* p_coef)> XPRSgetcoef = nullptr;
|
||||
std::function<int(XPRSprob prob, int* status, double duals[], int first, int last)> XPRSgetduals = nullptr;
|
||||
std::function<int(XPRSprob prob, int* status, double djs[], int first, int last)> XPRSgetredcosts = nullptr;
|
||||
std::function<int(XPRSprob prob, int nrows, int ncoefs, const char rowtype[], const double rhs[], const double rng[], const int start[], const int colind[], const double rowcoef[])> XPRSaddrows = nullptr;
|
||||
std::function<int(XPRSprob prob, int nrows, const int rowind[])> XPRSdelrows = nullptr;
|
||||
std::function<int(XPRSprob prob, int ncols, int ncoefs, const double objcoef[], const int start[], const int rowind[], const double rowcoef[], const double lb[], const double ub[])> XPRSaddcols = nullptr;
|
||||
@@ -91,6 +94,8 @@ std::function<int(XPRSprob prob, double x[], double slack[])> XPRSgetmipsol = nu
|
||||
std::function<int(XPRSprob prob, int ncols, const int colind[], const double objcoef[])> XPRSchgobj = nullptr;
|
||||
std::function<int(XPRSprob prob, int row, int col, double coef)> XPRSchgcoef = nullptr;
|
||||
std::function<int(XPRSprob prob, int ncoefs, const int rowind[], const int colind[], const double rowcoef[])> XPRSchgmcoef = nullptr;
|
||||
std::function<int(XPRSprob prob, XPRSint64 ncoefs, const int rowind[], const int colind[], const double rowcoef[])> XPRSchgmcoef64 = nullptr;
|
||||
std::function<int(XPRSprob prob, int ncoefs, const int objqcol1[], const int objqcol2[], const double objqcoef[])> XPRSchgmqobj = nullptr;
|
||||
std::function<int(XPRSprob prob, int nrows, const int rowind[], const double rhs[])> XPRSchgrhs = nullptr;
|
||||
std::function<int(XPRSprob prob, int nrows, const int rowind[], const double rng[])> XPRSchgrhsrange = nullptr;
|
||||
std::function<int(XPRSprob prob, int nrows, const int rowind[], const char rowtype[])> XPRSchgrowtype = nullptr;
|
||||
@@ -99,6 +104,7 @@ std::function<int(XPRSprob prob, void (XPRS_CC *f_intsol)(XPRSprob cbprob, void*
|
||||
std::function<int(XPRSprob prob, void (XPRS_CC *f_message)(XPRSprob cbprob, void* cbdata, const char* msg, int msglen, int msgtype), void* p, int priority)> XPRSaddcbmessage = nullptr;
|
||||
std::function<int(XPRSprob prob, const char* flags)> XPRSlpoptimize = nullptr;
|
||||
std::function<int(XPRSprob prob, const char* flags)> XPRSmipoptimize = nullptr;
|
||||
std::function<int(XPRSprob prob, const char* flags, int* solvestatus, int* solstatus)> XPRSoptimize = nullptr;
|
||||
|
||||
void LoadXpressFunctions(DynamicLibrary* xpress_dynamic_library) {
|
||||
// This was generated with the parse_header_xpress.py script.
|
||||
@@ -113,6 +119,7 @@ void LoadXpressFunctions(DynamicLibrary* xpress_dynamic_library) {
|
||||
xpress_dynamic_library->GetFunction(&XPRSlicense, "XPRSlicense");
|
||||
xpress_dynamic_library->GetFunction(&XPRSgetbanner, "XPRSgetbanner");
|
||||
xpress_dynamic_library->GetFunction(&XPRSgetversion, "XPRSgetversion");
|
||||
xpress_dynamic_library->GetFunction(&XPRSsetprobname, "XPRSsetprobname");
|
||||
xpress_dynamic_library->GetFunction(&XPRSsetdefaultcontrol, "XPRSsetdefaultcontrol");
|
||||
xpress_dynamic_library->GetFunction(&XPRSinterrupt, "XPRSinterrupt");
|
||||
xpress_dynamic_library->GetFunction(&XPRSsetintcontrol, "XPRSsetintcontrol");
|
||||
@@ -133,6 +140,8 @@ void LoadXpressFunctions(DynamicLibrary* xpress_dynamic_library) {
|
||||
xpress_dynamic_library->GetFunction(&XPRSgetlb, "XPRSgetlb");
|
||||
xpress_dynamic_library->GetFunction(&XPRSgetub, "XPRSgetub");
|
||||
xpress_dynamic_library->GetFunction(&XPRSgetcoef, "XPRSgetcoef");
|
||||
xpress_dynamic_library->GetFunction(&XPRSgetduals, "XPRSgetduals");
|
||||
xpress_dynamic_library->GetFunction(&XPRSgetredcosts, "XPRSgetredcosts");
|
||||
xpress_dynamic_library->GetFunction(&XPRSaddrows, "XPRSaddrows");
|
||||
xpress_dynamic_library->GetFunction(&XPRSdelrows, "XPRSdelrows");
|
||||
xpress_dynamic_library->GetFunction(&XPRSaddcols, "XPRSaddcols");
|
||||
@@ -155,6 +164,8 @@ void LoadXpressFunctions(DynamicLibrary* xpress_dynamic_library) {
|
||||
xpress_dynamic_library->GetFunction(&XPRSchgobj, "XPRSchgobj");
|
||||
xpress_dynamic_library->GetFunction(&XPRSchgcoef, "XPRSchgcoef");
|
||||
xpress_dynamic_library->GetFunction(&XPRSchgmcoef, "XPRSchgmcoef");
|
||||
xpress_dynamic_library->GetFunction(&XPRSchgmcoef64, "XPRSchgmcoef64");
|
||||
xpress_dynamic_library->GetFunction(&XPRSchgmqobj, "XPRSchgmqobj");
|
||||
xpress_dynamic_library->GetFunction(&XPRSchgrhs, "XPRSchgrhs");
|
||||
xpress_dynamic_library->GetFunction(&XPRSchgrhsrange, "XPRSchgrhsrange");
|
||||
xpress_dynamic_library->GetFunction(&XPRSchgrowtype, "XPRSchgrowtype");
|
||||
@@ -163,6 +174,7 @@ void LoadXpressFunctions(DynamicLibrary* xpress_dynamic_library) {
|
||||
xpress_dynamic_library->GetFunction(&XPRSaddcbmessage, "XPRSaddcbmessage");
|
||||
xpress_dynamic_library->GetFunction(&XPRSlpoptimize, "XPRSlpoptimize");
|
||||
xpress_dynamic_library->GetFunction(&XPRSmipoptimize, "XPRSmipoptimize");
|
||||
xpress_dynamic_library->GetFunction(&XPRSoptimize, "XPRSoptimize");
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -420,19 +420,55 @@ absl::Status LoadXpressDynamicLibrary(std::string& xpresspath);
|
||||
#define XPRS_OBJSENSE 2008
|
||||
#define XPRS_ROWS 1001
|
||||
#define XPRS_SIMPLEXITER 1009
|
||||
#define XPRS_BARITER 5001
|
||||
#define XPRS_SOLSTATUS_NOTFOUND 0
|
||||
#define XPRS_SOLSTATUS_OPTIMAL 1
|
||||
#define XPRS_SOLSTATUS_FEASIBLE 2
|
||||
#define XPRS_SOLSTATUS_INFEASIBLE 3
|
||||
#define XPRS_SOLSTATUS_UNBOUNDED 4
|
||||
#define XPRS_LPSTATUS 1010
|
||||
#define XPRS_MIPSTATUS 1011
|
||||
#define XPRS_NODES 1013
|
||||
#define XPRS_COLS 1018
|
||||
#define XPRS_MAXPROBNAMELENGTH 1158
|
||||
#define XPRS_LP_UNSTARTED 0
|
||||
#define XPRS_LP_OPTIMAL 1
|
||||
#define XPRS_LP_INFEAS 2
|
||||
#define XPRS_LP_CUTOFF 3
|
||||
#define XPRS_LP_UNFINISHED 4
|
||||
#define XPRS_LP_UNBOUNDED 5
|
||||
#define XPRS_LP_CUTOFF_IN_DUAL 6
|
||||
#define XPRS_LP_UNSOLVED 7
|
||||
#define XPRS_LP_NONCONVEX 8
|
||||
#define XPRS_MIP_SOLUTION 4
|
||||
#define XPRS_MIP_INFEAS 5
|
||||
#define XPRS_MIP_OPTIMAL 6
|
||||
#define XPRS_MIP_UNBOUNDED 7
|
||||
#define XPRS_ALG_DUAL 2
|
||||
#define XPRS_ALG_PRIMAL 3
|
||||
#define XPRS_ALG_BARRIER 4
|
||||
#define XPRS_OBJ_MINIMIZE 1
|
||||
#define XPRS_OBJ_MAXIMIZE -1
|
||||
// ***************************************************************************
|
||||
// * variable types *
|
||||
// ***************************************************************************
|
||||
#define XPRS_INTEGER 'I'
|
||||
#define XPRS_CONTINUOUS 'C'
|
||||
// ***************************************************************************
|
||||
// * constraint types *
|
||||
// ***************************************************************************
|
||||
#define XPRS_LESS_EQUAL 'L'
|
||||
#define XPRS_GREATER_EQUAL 'G'
|
||||
#define XPRS_EQUAL 'E'
|
||||
#define XPRS_RANGE 'R'
|
||||
#define XPRS_NONBINDING 'N'
|
||||
// ***************************************************************************
|
||||
// * basis status *
|
||||
// ***************************************************************************
|
||||
#define XPRS_AT_LOWER 0
|
||||
#define XPRS_BASIC 1
|
||||
#define XPRS_AT_UPPER 2
|
||||
#define XPRS_FREE_SUPER 3
|
||||
|
||||
// Let's not reformat for rest of the file.
|
||||
// clang-format off
|
||||
@@ -444,6 +480,7 @@ extern std::function<int(char* buffer, int maxbytes)> XPRSgetlicerrmsg;
|
||||
extern std::function<int(int* p_i, char* p_c)> XPRSlicense;
|
||||
extern std::function<int(char* banner)> XPRSgetbanner;
|
||||
extern std::function<int(char* version)> XPRSgetversion;
|
||||
extern std::function<int(XPRSprob prob, const char* probname)> XPRSsetprobname;
|
||||
extern std::function<int(XPRSprob prob, int control)> XPRSsetdefaultcontrol;
|
||||
extern std::function<int(XPRSprob prob, int reason)> XPRSinterrupt;
|
||||
extern std::function<int(XPRSprob prob, int control, int value)> XPRSsetintcontrol;
|
||||
@@ -465,6 +502,8 @@ extern std::function<int(XPRSprob prob, double rng[], int first, int last)> XPRS
|
||||
extern std::function<int(XPRSprob prob, double lb[], int first, int last)> XPRSgetlb;
|
||||
extern std::function<int(XPRSprob prob, double ub[], int first, int last)> XPRSgetub;
|
||||
extern std::function<int(XPRSprob prob, int row, int col, double* p_coef)> XPRSgetcoef;
|
||||
extern std::function<int(XPRSprob prob, int* status, double duals[], int first, int last)> XPRSgetduals;
|
||||
extern std::function<int(XPRSprob prob, int* status, double djs[], int first, int last)> XPRSgetredcosts;
|
||||
extern std::function<int(XPRSprob prob, int nrows, int ncoefs, const char rowtype[], const double rhs[], const double rng[], const int start[], const int colind[], const double rowcoef[])> XPRSaddrows;
|
||||
extern std::function<int(XPRSprob prob, int nrows, const int rowind[])> XPRSdelrows;
|
||||
extern std::function<int(XPRSprob prob, int ncols, int ncoefs, const double objcoef[], const int start[], const int rowind[], const double rowcoef[], const double lb[], const double ub[])> XPRSaddcols;
|
||||
@@ -487,6 +526,8 @@ extern std::function<int(XPRSprob prob, double x[], double slack[])> XPRSgetmips
|
||||
extern std::function<int(XPRSprob prob, int ncols, const int colind[], const double objcoef[])> XPRSchgobj;
|
||||
extern std::function<int(XPRSprob prob, int row, int col, double coef)> XPRSchgcoef;
|
||||
extern std::function<int(XPRSprob prob, int ncoefs, const int rowind[], const int colind[], const double rowcoef[])> XPRSchgmcoef;
|
||||
extern std::function<int(XPRSprob prob, XPRSint64 ncoefs, const int rowind[], const int colind[], const double rowcoef[])> XPRSchgmcoef64;
|
||||
extern std::function<int(XPRSprob prob, int ncoefs, const int objqcol1[], const int objqcol2[], const double objqcoef[])> XPRSchgmqobj;
|
||||
extern std::function<int(XPRSprob prob, int nrows, const int rowind[], const double rhs[])> XPRSchgrhs;
|
||||
extern std::function<int(XPRSprob prob, int nrows, const int rowind[], const double rng[])> XPRSchgrhsrange;
|
||||
extern std::function<int(XPRSprob prob, int nrows, const int rowind[], const char rowtype[])> XPRSchgrowtype;
|
||||
@@ -495,6 +536,8 @@ extern std::function<int(XPRSprob prob, void (XPRS_CC *f_intsol)(XPRSprob cbprob
|
||||
extern std::function<int(XPRSprob prob, void (XPRS_CC *f_message)(XPRSprob cbprob, void* cbdata, const char* msg, int msglen, int msgtype), void* p, int priority)> XPRSaddcbmessage;
|
||||
extern std::function<int(XPRSprob prob, const char* flags)> XPRSlpoptimize;
|
||||
extern std::function<int(XPRSprob prob, const char* flags)> XPRSmipoptimize;
|
||||
extern std::function<int(XPRSprob prob, const char* flags, int* solvestatus, int* solstatus)> XPRSoptimize;
|
||||
|
||||
// clang-format on
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
Reference in New Issue
Block a user