diff --git a/ortools/linear_solver/xpress_interface.cc b/ortools/linear_solver/xpress_interface.cc index 888ba6a4774..9e1abe96697 100644 --- a/ortools/linear_solver/xpress_interface.cc +++ b/ortools/linear_solver/xpress_interface.cc @@ -204,13 +204,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) diff --git a/ortools/math_opt/cpp/parameters.cc b/ortools/math_opt/cpp/parameters.cc index 562367beae8..4469168bb79 100644 --- a/ortools/math_opt/cpp/parameters.cc +++ b/ortools/math_opt/cpp/parameters.cc @@ -85,6 +85,8 @@ std::optional Enum::ToOptString( return "highs"; case SolverType::kSantorini: return "santorini"; + case SolverType::kXpress: + return "xpress"; } return std::nullopt; } diff --git a/ortools/math_opt/cpp/parameters.h b/ortools/math_opt/cpp/parameters.h index ba25d991226..da756cf3ddc 100644 --- a/ortools/math_opt/cpp/parameters.h +++ b/ortools/math_opt/cpp/parameters.h @@ -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); diff --git a/ortools/math_opt/parameters.proto b/ortools/math_opt/parameters.proto index af0c01bb72c..294595fa28b 100644 --- a/ortools/math_opt/parameters.proto +++ b/ortools/math_opt/parameters.proto @@ -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. diff --git a/ortools/math_opt/solver_tests/base_solver_test.cc b/ortools/math_opt/solver_tests/base_solver_test.cc index f88a9084016..1efb52b8c4d 100644 --- a/ortools/math_opt/solver_tests/base_solver_test.cc +++ b/ortools/math_opt/solver_tests/base_solver_test.cc @@ -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 diff --git a/ortools/math_opt/solvers/CMakeLists.txt b/ortools/math_opt/solvers/CMakeLists.txt index e6678ec740f..64cebe10015 100644 --- a/ortools/math_opt/solvers/CMakeLists.txt +++ b/ortools/math_opt/solvers/CMakeLists.txt @@ -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) "$" ) 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 + "$" + "$" + "$" + "$" + "$" + "$" + "$" + "$" + "$" + "$" + "$" + "$" + "$" + "$" + "$" + "$" + "$" + ) +endif() diff --git a/ortools/math_opt/solvers/xpress/g_xpress.cc b/ortools/math_opt/solvers/xpress/g_xpress.cc new file mode 100644 index 00000000000..0d1d8dfe4bc --- /dev/null +++ b/ortools/math_opt/solvers/xpress/g_xpress.cc @@ -0,0 +1,302 @@ +// Copyright 2010-2024 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 +#include +#include +#include +#include + +#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> 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)); +} + +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 obj, + const absl::Span lb, + const absl::Span ub, + const absl::Span vtype) { + return AddVars({}, {}, {}, obj, lb, ub, vtype); +} + +absl::Status Xpress::AddVars(const absl::Span vbegin, + const absl::Span vind, + const absl::Span vval, + const absl::Span obj, + const absl::Span lb, + const absl::Span ub, + const absl::Span vtype) { + if (checkInt32Overflow(lb.size())) { + return absl::InvalidArgumentError( + "XPRESS cannot handle more than 2^31 variables"); + } + const int num_vars = static_cast(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(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 sense, + const absl::Span rhs, + const absl::Span rng) { + const int num_cons = static_cast(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::SetObjective(bool maximize, double offset, + const absl::Span colind, + const absl::Span values) { + RETURN_IF_ERROR(ToStatus(XPRSchgobjsense( + xpress_model_, maximize ? XPRS_OBJ_MAXIMIZE : XPRS_OBJ_MINIMIZE))) + << "Failed to change objective sense in XPRESS"; + + 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(colind.size()); + return ToStatus( + XPRSchgobj(xpress_model_, n_cols, colind.data(), values.data())); +} + +absl::Status Xpress::ChgCoeffs(absl::Span rowind, + absl::Span colind, + absl::Span values) { + const long n_coefs = static_cast(rowind.size()); + return ToStatus(XPRSchgmcoef64(xpress_model_, n_coefs, rowind.data(), + colind.data(), values.data())); +} + +absl::StatusOr Xpress::LpOptimizeAndGetStatus(std::string flags) { + RETURN_IF_ERROR(ToStatus(XPRSlpoptimize(xpress_model_, flags.c_str()))) + << "XPRESS LP solve failed"; + int xpress_status; + RETURN_IF_ERROR( + ToStatus(XPRSgetintattrib(xpress_model_, XPRS_LPSTATUS, &xpress_status))) + << "Could not get XPRESS status"; + return xpress_status; +} +absl::Status Xpress::PostSolve() { + return ToStatus(XPRSpostsolve(xpress_model_)); +} + +absl::StatusOr Xpress::MipOptimizeAndGetStatus() { + RETURN_IF_ERROR(ToStatus(XPRSmipoptimize(xpress_model_, nullptr))) + << "XPRESS MIP solve failed"; + int xpress_status; + RETURN_IF_ERROR( + ToStatus(XPRSgetintattrib(xpress_model_, XPRS_MIPSTATUS, &xpress_status))) + << "Could not get XPRESS status"; + return xpress_status; +} + +void Xpress::Terminate() { XPRSinterrupt(xpress_model_, XPRS_STOP_USER); }; + +absl::StatusOr 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 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 Xpress::GetDoubleAttr(int attribute) const { + double result; + RETURN_IF_ERROR(ToStatus(XPRSgetdblattrib(xpress_model_, attribute, &result))) + << "Error getting Xpress double attribute: " << attribute; + return result; +} + +absl::StatusOr> Xpress::GetPrimalValues() const { + int nVars = GetNumberOfVariables(); + XPRSgetintattrib(xpress_model_, XPRS_COLS, &nVars); + std::vector values(nVars); + RETURN_IF_ERROR(ToStatus( + XPRSgetsolution(xpress_model_, nullptr, values.data(), 0, nVars - 1))) + << "Error getting Xpress LP solution"; + return values; +} + +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> Xpress::GetConstraintDuals() const { + int nCons = GetNumberOfConstraints(); + double values[nCons]; + RETURN_IF_ERROR( + ToStatus(XPRSgetduals(xpress_model_, nullptr, values, 0, nCons - 1))) + << "Failed to retrieve duals from XPRESS"; + std::vector result(values, values + nCons); + return result; +} +absl::StatusOr> Xpress::GetReducedCostValues() const { + int nVars = GetNumberOfVariables(); + double values[nVars]; + RETURN_IF_ERROR( + ToStatus(XPRSgetredcosts(xpress_model_, nullptr, values, 0, nVars - 1))) + << "Failed to retrieve LP solution from XPRESS"; + std::vector result(values, values + nVars); + return result; +} + +absl::Status Xpress::GetBasis(std::vector& rowBasis, + std::vector& colBasis) const { + rowBasis.resize(GetNumberOfConstraints()); + colBasis.resize(GetNumberOfVariables()); + return ToStatus( + XPRSgetbasis(xpress_model_, rowBasis.data(), colBasis.data())); +} + +absl::Status Xpress::SetStartingBasis(std::vector& rowBasis, + std::vector& 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> Xpress::GetVarLb() const { + int nVars = GetNumberOfVariables(); + std::vector 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> Xpress::GetVarUb() const { + int nVars = GetNumberOfVariables(); + std::vector 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 diff --git a/ortools/math_opt/solvers/xpress/g_xpress.h b/ortools/math_opt/solvers/xpress/g_xpress.h new file mode 100644 index 00000000000..9291db70430 --- /dev/null +++ b/ortools/math_opt/solvers/xpress/g_xpress.h @@ -0,0 +1,120 @@ +// Copyright 2010-2024 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 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). +#ifndef OR_TOOLS_MATH_OPT_SOLVERS_XPRESS_G_XPRESS_H_ +#define OR_TOOLS_MATH_OPT_SOLVERS_XPRESS_G_XPRESS_H_ + +#include +#include +#include +#include +#include +#include + +#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> New( + const std::string& model_name); + + ~Xpress(); + + absl::StatusOr GetIntControl(int control) const; + absl::Status SetIntControl(int control, int value); + absl::Status ResetIntControl(int control); // reset to default value + + absl::StatusOr GetIntAttr(int attribute) const; + + absl::StatusOr GetDoubleAttr(int attribute) const; + + absl::Status AddVars(absl::Span obj, + absl::Span lb, absl::Span ub, + absl::Span vtype); + + absl::Status AddVars(absl::Span vbegin, absl::Span vind, + absl::Span vval, + absl::Span obj, + absl::Span lb, absl::Span ub, + absl::Span vtype); + + absl::Status AddConstrs(absl::Span sense, + absl::Span rhs, + absl::Span rng); + + absl::Status SetObjective(bool maximize, double offset, + absl::Span colind, + absl::Span values); + + absl::Status ChgCoeffs(absl::Span cind, absl::Span vind, + absl::Span val); + + absl::StatusOr LpOptimizeAndGetStatus(std::string flags); + absl::StatusOr MipOptimizeAndGetStatus(); + absl::Status PostSolve(); + + void Terminate(); + + absl::StatusOr> GetPrimalValues() const; + absl::StatusOr> GetConstraintDuals() const; + absl::StatusOr> GetReducedCostValues() const; + absl::Status GetBasis(std::vector& rowBasis, + std::vector& colBasis) const; + absl::Status SetStartingBasis(std::vector& rowBasis, + std::vector& 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> GetVarLb() const; + absl::StatusOr> 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_control_defaults_; + void initIntControlDefaults(); +}; + +} // namespace operations_research::math_opt + +#endif // OR_TOOLS_MATH_OPT_SOLVERS_XPRESS_G_XPRESS_H_ diff --git a/ortools/math_opt/solvers/xpress_solver.cc b/ortools/math_opt/solvers/xpress_solver.cc new file mode 100644 index 00000000000..3274fc48995 --- /dev/null +++ b/ortools/math_opt/solvers/xpress_solver.cc @@ -0,0 +1,711 @@ +// Copyright 2010-2024 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 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::kNotSupported, + .quadratic_constraints = SupportType::kNotSupported, + .second_order_cone_constraints = SupportType::kNotSupported, + .sos1_constraints = SupportType::kNotSupported, + .sos2_constraints = SupportType::kNotSupported, + .indicator_constraints = SupportType::kNotSupported}; + +absl::StatusOr> 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 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); + // TODO: set prob name, use XPRSsetprobname (must be added to environment) + // (!) must be truncated to MAXPROBNAMELENGTH + // RETURN_IF_ERROR(xpress_->SetProbName(input_model.name()); + RETURN_IF_ERROR(AddNewVariables(input_model.variables())); + RETURN_IF_ERROR(AddNewLinearConstraints(input_model.linear_constraints())); + // TODO: instead of changing coefficients, set them when adding 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 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 g_xpress) + : xpress_(std::move(g_xpress)) {} + +absl::Status XpressSolver::AddNewLinearConstraints( + const LinearConstraintsProto& constraints) { + const int num_new_constraints = constraints.lower_bounds().size(); + + std::vector constraint_sense; + std::vector constraint_rhs; + std::vector constraint_rng; + std::vector new_slacks; + constraint_rhs.reserve(num_new_constraints); + constraint_sense.reserve(num_new_constraints); + new_slacks.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) { + std::vector 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_->SetObjective(objective.maximize(), objective.offset(), index, + objective.linear_coefficients().values())); + is_maximize_ = objective.maximize(); + return absl::OkStatus(); +} + +absl::Status XpressSolver::ChangeCoefficients( + const SparseDoubleMatrixProto& matrix) { + const int num_coefficients = matrix.row_ids().size(); + std::vector row_index; + row_index.reserve(num_coefficients); + std::vector 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 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; +} + +inline std::string GetOptimizationFlags( + const SolveParametersProto& parameters) { + switch (parameters.lp_algorithm()) { + case LP_ALGORITHM_PRIMAL_SIMPLEX: + return "p"; + case LP_ALGORITHM_DUAL_SIMPLEX: + return "d"; + case LP_ALGORITHM_BARRIER: + return "b"; + default: + // this makes XPRESS use default algorithm (XPRS_DEFAULTALG) + 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_) { + ASSIGN_OR_RETURN(xpress_status_, xpress_->MipOptimizeAndGetStatus()); + } else { + RETURN_IF_ERROR(SetLpIterLimits(parameters)) + << "Could not set iteration limits."; + ASSIGN_OR_RETURN(xpress_status_, xpress_->LpOptimizeAndGetStatus( + GetOptimizationFlags(parameters))); + } + // Post-solve + if (!(is_mip_ ? (xpress_status_ == XPRS_MIP_OPTIMAL) + : (xpress_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 XpressSolver::ExtractSolveResultProto( + absl::Time start, const ModelSolveParametersProto& model_parameters, + const SolveParametersProto& solve_parameters) { + SolveResultProto result; + ASSIGN_OR_RETURN(SolutionsAndClaims solution_and_claims, + GetSolutions(model_parameters, solve_parameters)); + for (SolutionProto& solution : solution_and_claims.solutions) { + *result.add_solutions() = std::move(solution); + } + ASSIGN_OR_RETURN(*result.mutable_solve_stats(), GetSolveStats(start)); + ASSIGN_OR_RETURN(const double best_primal_bound, + GetBestPrimalBound(solve_parameters)); + ASSIGN_OR_RETURN(const double best_dual_bound, + GetBestDualBound(solve_parameters)); + auto solution_claims = solution_and_claims.solution_claims; + ASSIGN_OR_RETURN(*result.mutable_termination(), + ConvertTerminationReason(solution_claims, best_primal_bound, + best_dual_bound)); + return result; +} + +absl::StatusOr XpressSolver::GetBestPrimalBound( + const SolveParametersProto& parameters) const { + // TODO: handle MIP + if (parameters.lp_algorithm() == LP_ALGORITHM_PRIMAL_SIMPLEX && + isFeasible() || + xpress_status_ == XPRS_LP_OPTIMAL) { + return xpress_->GetDoubleAttr(XPRS_LPOBJVAL); + } + return is_maximize_ ? kMinusInf : kPlusInf; +} + +absl::StatusOr XpressSolver::GetBestDualBound( + const SolveParametersProto& parameters) const { + // TODO: handle MIP + if (parameters.lp_algorithm() == LP_ALGORITHM_DUAL_SIMPLEX && isFeasible() || + xpress_status_ == XPRS_LP_OPTIMAL) { + return xpress_->GetDoubleAttr(XPRS_LPOBJVAL); + } + return is_maximize_ ? kPlusInf : kMinusInf; +} + +absl::StatusOr XpressSolver::GetSolutions( + 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 XpressSolver::GetLpSolution( + const ModelSolveParametersProto& model_parameters, + const SolveParametersProto& solve_parameters) { + ASSIGN_OR_RETURN( + auto primal_solution_and_claim, + GetConvexPrimalSolutionIfAvailable(model_parameters, solve_parameters)); + ASSIGN_OR_RETURN( + auto dual_solution_and_claim, + GetConvexDualSolutionIfAvailable(model_parameters, solve_parameters)); + ASSIGN_OR_RETURN(auto basis, GetBasisIfAvailable()); + const SolutionClaims solution_claims = { + .primal_feasible_solution_exists = + primal_solution_and_claim.feasible_solution_exists, + .dual_feasible_solution_exists = + dual_solution_and_claim.feasible_solution_exists}; + + if (!primal_solution_and_claim.solution.has_value() && + !dual_solution_and_claim.solution.has_value() && !basis.has_value()) { + return SolutionsAndClaims{.solution_claims = solution_claims}; + } + SolutionsAndClaims solution_and_claims{.solution_claims = solution_claims}; + SolutionProto& solution = + solution_and_claims.solutions.emplace_back(SolutionProto()); + if (primal_solution_and_claim.solution.has_value()) { + *solution.mutable_primal_solution() = + std::move(*primal_solution_and_claim.solution); + } + if (dual_solution_and_claim.solution.has_value()) { + *solution.mutable_dual_solution() = + std::move(*dual_solution_and_claim.solution); + } + if (basis.has_value()) { + *solution.mutable_basis() = std::move(*basis); + } + return solution_and_claims; +} + +bool XpressSolver::isFeasible() const { + if (is_mip_) { + return xpress_status_ == XPRS_MIP_OPTIMAL || + xpress_status_ == XPRS_MIP_SOLUTION; + } else { + return xpress_status_ == XPRS_LP_OPTIMAL || + xpress_status_ == XPRS_LP_UNFINISHED; + } +} + +SolutionStatusProto XpressSolver::getLpSolutionStatus() const { + switch (xpress_status_) { + case XPRS_LP_OPTIMAL: + case XPRS_LP_UNFINISHED: + // TODO: XPRESS returns XPRS_LP_UNFINISHED even if it found no solution + // maybe we should adapt this code (try it out with LpIncompleteSolveTest, + // by setting "initial basis" support flags to true) + 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; + } +} + +absl::StatusOr> +XpressSolver::GetConvexPrimalSolutionIfAvailable( + const ModelSolveParametersProto& model_parameters, + const SolveParametersProto& solve_parameters) const { + if (!isFeasible()) { + return SolutionAndClaim{ + .solution = std::nullopt, .feasible_solution_exists = false}; + } + PrimalSolutionProto primal_solution; + primal_solution.set_feasibility_status(getLpSolutionStatus()); + ASSIGN_OR_RETURN(const double sol_val, GetBestPrimalBound(solve_parameters)); + primal_solution.set_objective_value(sol_val); + XpressVectorToSparseDoubleVector(xpress_->GetPrimalValues().value(), + variables_map_, + *primal_solution.mutable_variable_values(), + model_parameters.variable_values_filter()); + const bool primal_feasible_solution_exists = + (primal_solution.feasibility_status() == SOLUTION_STATUS_FEASIBLE); + return SolutionAndClaim{ + .solution = std::move(primal_solution), + .feasible_solution_exists = primal_feasible_solution_exists}; +} + +absl::StatusOr> +XpressSolver::GetConvexDualSolutionIfAvailable( + const ModelSolveParametersProto& model_parameters, + const SolveParametersProto& solve_parameters) const { + if (!isFeasible()) { + return SolutionAndClaim{ + .solution = std::nullopt, .feasible_solution_exists = false}; + } + DualSolutionProto dual_solution; + ASSIGN_OR_RETURN(const std::vector xprs_constraint_duals, + xpress_->GetConstraintDuals()); + + XpressVectorToSparseDoubleVector(xprs_constraint_duals, + linear_constraints_map_, + *dual_solution.mutable_dual_values(), + model_parameters.dual_values_filter()); + + ASSIGN_OR_RETURN(const std::vector xprs_reduced_cost_values, + xpress_->GetReducedCostValues()); + XpressVectorToSparseDoubleVector(xprs_reduced_cost_values, variables_map_, + *dual_solution.mutable_reduced_costs(), + model_parameters.reduced_costs_filter()); + ASSIGN_OR_RETURN(const double sol_val, GetBestDualBound(solve_parameters)); + dual_solution.set_objective_value(sol_val); + dual_solution.set_feasibility_status(getLpSolutionStatus()); + ASSIGN_OR_RETURN(const double best_dual_bound, + GetBestDualBound(solve_parameters)); + bool dual_feasible_solution_exists = std::isfinite(best_dual_bound); + return SolutionAndClaim{ + .solution = dual_solution, + .feasible_solution_exists = dual_feasible_solution_exists}; +} + +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 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(value), false); + } + std::vector 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(value), true); + } + return xpress_->SetStartingBasis(xpress_constr_basis_status, + xpress_var_basis_status); +} + +absl::StatusOr> XpressSolver::GetBasisIfAvailable() { + BasisProto basis; + + std::vector xprs_variable_basis_status; + std::vector xprs_constraint_basis_status; + RETURN_IF_ERROR(xpress_->GetBasis(xprs_constraint_basis_status, + xprs_variable_basis_status)) + << "Unable to retrieve basis from XPRESS"; + + // 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(SOLUTION_STATUS_UNDETERMINED); + if (xpress_status_ == XPRS_LP_OPTIMAL) { + basis.set_basic_dual_feasibility(SOLUTION_STATUS_FEASIBLE); + } else if (xpress_status_ == XPRS_LP_UNBOUNDED) { + basis.set_basic_dual_feasibility(SOLUTION_STATUS_INFEASIBLE); + } + + return basis; +} + +absl::StatusOr 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 +void XpressSolver::XpressVectorToSparseDoubleVector( + absl::Span 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 XpressSolver::ConvertTerminationReason( + SolutionClaims solution_claims, double best_primal_bound, + double best_dual_bound) const { + if (!is_mip_) { + switch (xpress_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_, solution_claims.dual_feasible_solution_exists + ? 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_status_)); + } + } else { + return absl::UnimplementedError("XpressSolver does not handle MIPs yet"); + } +} + +absl::StatusOr XpressSolver::Update( + const ModelUpdateProto& model_update) { + // Not implemented yet + return false; +} + +absl::StatusOr +XpressSolver::ComputeInfeasibleSubsystem(const SolveParametersProto& parameters, + MessageCallback message_cb, + const SolveInterrupter* interrupter) { + return absl::UnimplementedError( + "XpressSolver cannot compute infeasible subsystem yet"); +} + +absl::StatusOr XpressSolver::ListInvertedBounds() const { + InvertedBounds inverted_bounds; + { + ASSIGN_OR_RETURN(const std::vector var_lbs, xpress_->GetVarLb()); + ASSIGN_OR_RETURN(const std::vector 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 diff --git a/ortools/math_opt/solvers/xpress_solver.h b/ortools/math_opt/solvers/xpress_solver.h new file mode 100644 index 00000000000..33e35d0cda1 --- /dev/null +++ b/ortools/math_opt/solvers/xpress_solver.h @@ -0,0 +1,219 @@ +// Copyright 2010-2024 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 +#include +#include +#include +#include + +#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> New( + const ModelProto& input_model, + const SolverInterface::InitArgs& init_args); + + // Solves the optimization problem + absl::StatusOr Solve( + const SolveParametersProto& parameters, + const ModelSolveParametersProto& model_parameters, + MessageCallback message_cb, + const CallbackRegistrationProto& callback_registration, Callback cb, + const SolveInterrupter* interrupter) override; + absl::Status CallXpressSolve(const SolveParametersProto& parameters); + + // Updates the problem (not implemented yet) + absl::StatusOr Update(const ModelUpdateProto& model_update) override; + + // Computes the infeasible subsystem (not implemented yet) + absl::StatusOr + ComputeInfeasibleSubsystem(const SolveParametersProto& parameters, + MessageCallback message_cb, + const SolveInterrupter* interrupter) override; + + private: + explicit XpressSolver(std::unique_ptr 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; + // A collection of other constraints (e.g., norm, max, indicator) supported by + // Xpress. All general constraints share the same index set. See for more + // detail: https://www.xpress.com/documentation/9.5/refman/constraints.html. + 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; + + // Data associated with each linear constraint. With it we know if the + // underlying representation is either: + // linear_terms <= upper_bound (if lower bound <= -INFINITY) + // linear_terms >= lower_bound (if upper bound >= INFINTY) + // linear_terms == xxxxx_bound (if upper_bound == lower_bound) + // linear_term - slack == 0 (with slack bounds equal to xxxxx_bound) + struct LinearConstraintData { + XpressLinearConstraintIndex constraint_index = kUnspecifiedConstraint; + // only valid for true ranged constraints. + XpressVariableIndex slack_index = kUnspecifiedIndex; + double lower_bound = kMinusInf; + double upper_bound = kPlusInf; + }; + + struct SolutionClaims { + bool primal_feasible_solution_exists = false; + bool dual_feasible_solution_exists = false; + }; + + struct SolutionsAndClaims { + // TODO: simplify this structure or change it + std::vector solutions; + SolutionClaims solution_claims; + }; + + template + struct SolutionAndClaim { + std::optional solution; + bool feasible_solution_exists = false; + }; + + absl::StatusOr ExtractSolveResultProto( + absl::Time start, const ModelSolveParametersProto& model_parameters, + const SolveParametersProto& solve_parameters); + absl::StatusOr GetSolutions( + const ModelSolveParametersProto& model_parameters, + const SolveParametersProto& solve_parameters); + absl::StatusOr GetSolveStats(absl::Time start) const; + + absl::StatusOr GetBestPrimalBound( + const SolveParametersProto& parameters) const; + absl::StatusOr GetBestDualBound( + const SolveParametersProto& parameters) const; + + absl::StatusOr ConvertTerminationReason( + SolutionClaims solution_claims, double best_primal_bound, + double best_dual_bound) const; + + // Returns solution information appropriate and available for an LP (linear + // constraints + linear objective, only). + absl::StatusOr GetLpSolution( + const ModelSolveParametersProto& model_parameters, + const SolveParametersProto& solve_parameters); + bool isFeasible() const; + + // return bool field should be true if a primal solution exists. + absl::StatusOr> + GetConvexPrimalSolutionIfAvailable( + const ModelSolveParametersProto& model_parameters, + const SolveParametersProto& solve_parameters) const; + absl::StatusOr> + GetConvexDualSolutionIfAvailable( + const ModelSolveParametersProto& model_parameters, + const SolveParametersProto& solve_parameters) const; + absl::StatusOr> GetBasisIfAvailable(); + + absl::Status AddNewLinearConstraints( + const LinearConstraintsProto& constraints); + 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); + + // 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 + void XpressVectorToSparseDoubleVector( + absl::Span xpress_values, const T& map, + SparseDoubleVectorProto& result, + const SparseVectorFilterProto& filter) const; + + const std::unique_ptr xpress_; + + // Note that we use linked_hash_map for the indices of the xpress_model_ + // variables and linear constraints to ensure that iteration over the map + // maintains their insertion order (and, thus, the order in which they appear + // in the model). + + // Internal correspondence from variable proto IDs to Xpress-numbered + // variables. + gtl::linked_hash_map variables_map_; + // Internal correspondence from linear constraint proto IDs to + // Xpress-numbered linear constraint and extra information. + gtl::linked_hash_map + 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; + absl::StatusOr ListInvertedBounds() const; + absl::Status SetXpressStartingBasis(const BasisProto& basis); + absl::Status SetLpIterLimits(const SolveParametersProto& parameters); + + // Fields to track the number of Xpress variables and constraints. These + // quantities are updated immediately after adding or removing to the model, + // so it is correct even if XPRESS C API has not yet been called. + + bool is_mip_ = false; + bool is_maximize_ = false; + int xpress_status_ = 0; +}; + +} // namespace operations_research::math_opt + +#endif // OR_TOOLS_MATH_OPT_SOLVERS_XPRESS_SOLVER_H_ diff --git a/ortools/math_opt/solvers/xpress_solver_test.cc b/ortools/math_opt/solvers/xpress_solver_test.cc new file mode 100644 index 00000000000..64798bed72c --- /dev/null +++ b/ortools/math_opt/solvers/xpress_solver_test.cc @@ -0,0 +1,246 @@ +// Copyright 2010-2024 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "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_initial_basis_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::kNoQpSupport, + /*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())); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(QpDualsTest); + +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 MakeStatusTestConfigs() { + std::vector test_parameters; + for (const auto algorithm : std::vector>( + {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 \ No newline at end of file diff --git a/ortools/service/v1/mathopt/parameters.proto b/ortools/service/v1/mathopt/parameters.proto index 5dc0e4e524c..99a57ee5318 100644 --- a/ortools/service/v1/mathopt/parameters.proto +++ b/ortools/service/v1/mathopt/parameters.proto @@ -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. diff --git a/ortools/xpress/environment.cc b/ortools/xpress/environment.cc index 13b9fccb4da..1bad91c1ff9 100644 --- a/ortools/xpress/environment.cc +++ b/ortools/xpress/environment.cc @@ -69,6 +69,9 @@ std::function XPRSgetrhsr std::function XPRSgetlb = nullptr; std::function XPRSgetub = nullptr; std::function XPRSgetcoef = nullptr; +std::function XPRSgetsolution = nullptr; +std::function XPRSgetduals = nullptr; +std::function XPRSgetredcosts = nullptr; std::function XPRSaddrows = nullptr; std::function XPRSdelrows = nullptr; std::function XPRSaddcols = nullptr; @@ -91,6 +94,7 @@ std::function XPRSgetmipsol = nu std::function XPRSchgobj = nullptr; std::function XPRSchgcoef = nullptr; std::function XPRSchgmcoef = nullptr; +std::function XPRSchgmcoef64 = nullptr; std::function XPRSchgrhs = nullptr; std::function XPRSchgrhsrange = nullptr; std::function XPRSchgrowtype = nullptr; @@ -99,6 +103,7 @@ std::function XPRSaddcbmessage = nullptr; std::function XPRSlpoptimize = nullptr; std::function XPRSmipoptimize = nullptr; +std::function XPRSoptimize = nullptr; void LoadXpressFunctions(DynamicLibrary* xpress_dynamic_library) { // This was generated with the parse_header_xpress.py script. @@ -133,6 +138,9 @@ 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(&XPRSgetsolution, "XPRSgetsolution"); + 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 +163,7 @@ 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(&XPRSchgrhs, "XPRSchgrhs"); xpress_dynamic_library->GetFunction(&XPRSchgrhsrange, "XPRSchgrhsrange"); xpress_dynamic_library->GetFunction(&XPRSchgrowtype, "XPRSchgrowtype"); @@ -163,6 +172,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 diff --git a/ortools/xpress/environment.h b/ortools/xpress/environment.h index 8bcb3a10767..a1015e769fd 100644 --- a/ortools/xpress/environment.h +++ b/ortools/xpress/environment.h @@ -420,19 +420,46 @@ absl::Status LoadXpressDynamicLibrary(std::string& xpresspath); #define XPRS_OBJSENSE 2008 #define XPRS_ROWS 1001 #define XPRS_SIMPLEXITER 1009 +#define XPRS_BARITER 5001 #define XPRS_LPSTATUS 1010 #define XPRS_MIPSTATUS 1011 #define XPRS_NODES 1013 #define XPRS_COLS 1018 +#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_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 @@ -465,6 +492,9 @@ extern std::function XPRS extern std::function XPRSgetlb; extern std::function XPRSgetub; extern std::function XPRSgetcoef; +extern std::function XPRSgetsolution; +extern std::function XPRSgetduals; +extern std::function XPRSgetredcosts; extern std::function XPRSaddrows; extern std::function XPRSdelrows; extern std::function XPRSaddcols; @@ -487,6 +517,7 @@ extern std::function XPRSgetmips extern std::function XPRSchgobj; extern std::function XPRSchgcoef; extern std::function XPRSchgmcoef; +extern std::function XPRSchgmcoef64; extern std::function XPRSchgrhs; extern std::function XPRSchgrhsrange; extern std::function XPRSchgrowtype; @@ -495,6 +526,8 @@ extern std::function XPRSaddcbmessage; extern std::function XPRSlpoptimize; extern std::function XPRSmipoptimize; +extern std::function XPRSoptimize; + // clang-format on } // namespace operations_research