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:
Laurent Perron
2025-01-23 13:21:09 +01:00
committed by GitHub
14 changed files with 1737 additions and 7 deletions

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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.

View File

@@ -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

View File

@@ -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()

View 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

View 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_

View 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

View 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_

View 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

View File

@@ -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.

View File

@@ -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

View File

@@ -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